ソースを参照

port complete SQLite-3.8.1 to RT-Thread

geniusgogo 12 年 前
コミット
d47febdc65
49 ファイル変更33466 行追加105 行削除
  1. 26 0
      components/external/sqlite/README
  2. 3 1
      components/external/sqlite/SConscript
  3. 114 0
      components/external/sqlite/config.h
  4. 475 104
      components/external/sqlite/sqlite3.c
  5. 6549 0
      components/external/sqlite/test/test1.c
  6. 664 0
      components/external/sqlite/test/test2.c
  7. 628 0
      components/external/sqlite/test/test3.c
  8. 722 0
      components/external/sqlite/test/test4.c
  9. 217 0
      components/external/sqlite/test/test5.c
  10. 1020 0
      components/external/sqlite/test/test6.c
  11. 714 0
      components/external/sqlite/test/test7.c
  12. 1396 0
      components/external/sqlite/test/test8.c
  13. 200 0
      components/external/sqlite/test/test9.c
  14. 241 0
      components/external/sqlite/test/test_async.c
  15. 221 0
      components/external/sqlite/test/test_autoext.c
  16. 150 0
      components/external/sqlite/test/test_backup.c
  17. 62 0
      components/external/sqlite/test/test_btree.c
  18. 656 0
      components/external/sqlite/test/test_config.c
  19. 679 0
      components/external/sqlite/test/test_demovfs.c
  20. 398 0
      components/external/sqlite/test/test_devsym.c
  21. 335 0
      components/external/sqlite/test/test_fs.c
  22. 767 0
      components/external/sqlite/test/test_func.c
  23. 388 0
      components/external/sqlite/test/test_hexio.c
  24. 291 0
      components/external/sqlite/test/test_init.c
  25. 382 0
      components/external/sqlite/test/test_intarray.c
  26. 128 0
      components/external/sqlite/test/test_intarray.h
  27. 857 0
      components/external/sqlite/test/test_journal.c
  28. 122 0
      components/external/sqlite/test/test_loadext.c
  29. 1494 0
      components/external/sqlite/test/test_malloc.c
  30. 1385 0
      components/external/sqlite/test/test_multiplex.c
  31. 99 0
      components/external/sqlite/test/test_multiplex.h
  32. 439 0
      components/external/sqlite/test/test_mutex.c
  33. 830 0
      components/external/sqlite/test/test_onefile.c
  34. 1215 0
      components/external/sqlite/test/test_osinst.c
  35. 467 0
      components/external/sqlite/test/test_pcache.c
  36. 2008 0
      components/external/sqlite/test/test_quota.c
  37. 274 0
      components/external/sqlite/test/test_quota.h
  38. 305 0
      components/external/sqlite/test/test_rtree.c
  39. 362 0
      components/external/sqlite/test/test_schema.c
  40. 516 0
      components/external/sqlite/test/test_server.c
  41. 507 0
      components/external/sqlite/test/test_sqllog.c
  42. 639 0
      components/external/sqlite/test/test_stat.c
  43. 356 0
      components/external/sqlite/test/test_superlock.c
  44. 705 0
      components/external/sqlite/test/test_syscall.c
  45. 332 0
      components/external/sqlite/test/test_tclvar.c
  46. 647 0
      components/external/sqlite/test/test_thread.c
  47. 1510 0
      components/external/sqlite/test/test_vfs.c
  48. 887 0
      components/external/sqlite/test/test_vfstrace.c
  49. 84 0
      components/external/sqlite/test/test_wsd.c

+ 26 - 0
components/external/sqlite/README

@@ -2,3 +2,29 @@
 
 ## 简介
 初始版本基于SQLite 3.8.1版本,使用混合单文件结构源代码
+
+测试方法:
+1.   
+    在rtconfig.h中定义一下宏
+    /*
+    * SQLite compile macro
+    */
+    #define RT_USING_SQLITE
+    #define SQLITE_OMIT_LOAD_EXTENSION 1 
+    #define SQLITE_RTT_NO_WIDE 1   
+    #define SQLITE_OMIT_WAL 
+    #define SQLITE_ENABLE_LOCKING_STYLE 0 
+    #define SQLITE_DISABLE_LOCKING_STYLE 1
+    #define SQLITE_TEMP_STORE 1
+    #define SQLITE_THREADSAFE 1
+    #define HAVE_READLINE 0 
+    #define NDEBUG 
+    #define _HAVE_SQLITE_CONFIG_H 
+    #define BUILD_sqlite
+    #define SQLITE_OS_OTHER 1 
+    #define SQLITE_OS_RTT 1
+2.
+    在test目录下找一个测试样例来加入工程进行测试.
+    推荐用mini2440bsp,因为板子的ram较大。
+
+注意shell.c还没有移植的。请不要使用。

+ 3 - 1
components/external/sqlite/SConscript

@@ -3,7 +3,9 @@ import os
 from building import *
 
 cwd = GetCurrentDir()
-src	= Glob('*.c')
+src = Split("""
+sqlite3.c
+""")
 CPPPATH = [cwd, str(Dir('#'))]
 
 group = DefineGroup('sqlite', src, depend = ['RT_USING_SQLITE'], CPPPATH = CPPPATH)

+ 114 - 0
components/external/sqlite/config.h

@@ -0,0 +1,114 @@
+/* config.h.  Generated from config.h.in by configure.  */
+/* config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+/* #undef HAVE_DLFCN_H */
+
+/* Define to 1 if you have the `fdatasync' function. */
+/* #undef HAVE_FDATASYNC */
+
+/* Define to 1 if you have the `gmtime_r' function. */
+#define HAVE_GMTIME_R 1
+
+/* Define to 1 if the system has the type `int16_t'. */
+#define HAVE_INT16_T 1
+
+/* Define to 1 if the system has the type `int32_t'. */
+#define HAVE_INT32_T 1
+
+/* Define to 1 if the system has the type `int64_t'. */
+#define HAVE_INT64_T 1
+
+/* Define to 1 if the system has the type `int8_t'. */
+#define HAVE_INT8_T 1
+
+/* Define to 1 if the system has the type `intptr_t'. */
+#define HAVE_INTPTR_T 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `localtime_r' function. */
+#define HAVE_LOCALTIME_R 1
+
+/* Define to 1 if you have the `localtime_s' function. */
+/* #undef HAVE_LOCALTIME_S */
+
+/* Define to 1 if you have the <malloc.h> header file. */
+/* #define HAVE_MALLOC_H 0 */
+
+/* Define to 1 if you have the `malloc_usable_size' function. */
+/* #define HAVE_MALLOC_USABLE_SIZE 0 */
+
+/* Define to 1 if you have the <memory.h> header file. */
+/* #undef HAVE_MEMORY_H */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+/* #undef HAVE_STRINGS_H */
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if the system has the type `uint16_t'. */
+#define HAVE_UINT16_T 1
+
+/* Define to 1 if the system has the type `uint32_t'. */
+#define HAVE_UINT32_T 1
+
+/* Define to 1 if the system has the type `uint64_t'. */
+#define HAVE_UINT64_T 1
+
+/* Define to 1 if the system has the type `uint8_t'. */
+#define HAVE_UINT8_T 1
+
+/* Define to 1 if the system has the type `uintptr_t'. */
+#define HAVE_UINTPTR_T 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the `usleep' function. */
+/* #undef HAVE_USLEEP */
+
+/* Define to 1 if you have the utime() library function. */
+/* #undef HAVE_UTIME */
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+   */
+#define LT_OBJDIR ".libs/"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "sqlite"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "sqlite 3.8.1"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "sqlite"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "3.8.1"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */

ファイルの差分が大きいため隠しています
+ 475 - 104
components/external/sqlite/sqlite3.c


+ 6549 - 0
components/external/sqlite/test/test1.c

@@ -0,0 +1,6549 @@
+/*
+** 2001 September 15
+**
+** 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 all sorts of SQLite interfaces.  This code
+** is not included in the SQLite library.  It is used for automated
+** testing of the SQLite library.
+*/
+#include "sqliteInt.h"
+#include "vdbeInt.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+
+/*
+** This is a copy of the first part of the SqliteDb structure in 
+** tclsqlite.c.  We need it here so that the get_sqlite_pointer routine
+** can extract the sqlite3* pointer from an existing Tcl SQLite
+** connection.
+*/
+struct SqliteDb {
+  sqlite3 *db;
+};
+
+/*
+** Convert text generated by the "%p" conversion format back into
+** a pointer.
+*/
+static int testHexToInt(int h){
+  if( h>='0' && h<='9' ){
+    return h - '0';
+  }else if( h>='a' && h<='f' ){
+    return h - 'a' + 10;
+  }else{
+    assert( h>='A' && h<='F' );
+    return h - 'A' + 10;
+  }
+}
+void *sqlite3TestTextToPtr(const char *z){
+  void *p;
+  u64 v;
+  u32 v2;
+  if( z[0]=='0' && z[1]=='x' ){
+    z += 2;
+  }
+  v = 0;
+  while( *z ){
+    v = (v<<4) + testHexToInt(*z);
+    z++;
+  }
+  if( sizeof(p)==sizeof(v) ){
+    memcpy(&p, &v, sizeof(p));
+  }else{
+    assert( sizeof(p)==sizeof(v2) );
+    v2 = (u32)v;
+    memcpy(&p, &v2, sizeof(p));
+  }
+  return p;
+}
+
+
+/*
+** A TCL command that returns the address of the sqlite* pointer
+** for an sqlite connection instance.  Bad things happen if the
+** input is not an sqlite connection.
+*/
+static int get_sqlite_pointer(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  struct SqliteDb *p;
+  Tcl_CmdInfo cmdInfo;
+  char zBuf[100];
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SQLITE-CONNECTION");
+    return TCL_ERROR;
+  }
+  if( !Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
+    Tcl_AppendResult(interp, "command not found: ",
+           Tcl_GetString(objv[1]), (char*)0);
+    return TCL_ERROR;
+  }
+  p = (struct SqliteDb*)cmdInfo.objClientData;
+  sprintf(zBuf, "%p", p->db);
+  if( strncmp(zBuf,"0x",2) ){
+    sprintf(zBuf, "0x%p", p->db);
+  }
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Decode a pointer to an sqlite3 object.
+*/
+int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb){
+  struct SqliteDb *p;
+  Tcl_CmdInfo cmdInfo;
+  if( Tcl_GetCommandInfo(interp, zA, &cmdInfo) ){
+    p = (struct SqliteDb*)cmdInfo.objClientData;
+    *ppDb = p->db;
+  }else{
+    *ppDb = (sqlite3*)sqlite3TestTextToPtr(zA);
+  }
+  return TCL_OK;
+}
+
+extern const char *sqlite3ErrName(int);
+#define t1ErrorName sqlite3ErrName
+
+/*
+** Convert an sqlite3_stmt* into an sqlite3*.  This depends on the
+** fact that the sqlite3* is the first field in the Vdbe structure.
+*/
+#define StmtToDb(X)   sqlite3_db_handle(X)
+
+/*
+** Check a return value to make sure it agrees with the results
+** from sqlite3_errcode.
+*/
+int sqlite3TestErrCode(Tcl_Interp *interp, sqlite3 *db, int rc){
+  if( sqlite3_threadsafe()==0 && rc!=SQLITE_MISUSE && rc!=SQLITE_OK
+   && sqlite3_errcode(db)!=rc ){
+    char zBuf[200];
+    int r2 = sqlite3_errcode(db);
+    sprintf(zBuf, "error code %s (%d) does not match sqlite3_errcode %s (%d)",
+       t1ErrorName(rc), rc, t1ErrorName(r2), r2);
+    Tcl_ResetResult(interp);
+    Tcl_AppendResult(interp, zBuf, 0);
+    return 1;
+  }
+  return 0;
+}
+
+/*
+** Decode a pointer to an sqlite3_stmt object.
+*/
+static int getStmtPointer(
+  Tcl_Interp *interp, 
+  const char *zArg,  
+  sqlite3_stmt **ppStmt
+){
+  *ppStmt = (sqlite3_stmt*)sqlite3TestTextToPtr(zArg);
+  return TCL_OK;
+}
+
+/*
+** Generate a text representation of a pointer that can be understood
+** by the getDbPointer and getVmPointer routines above.
+**
+** The problem is, on some machines (Solaris) if you do a printf with
+** "%p" you cannot turn around and do a scanf with the same "%p" and
+** get your pointer back.  You have to prepend a "0x" before it will
+** work.  Or at least that is what is reported to me (drh).  But this
+** behavior varies from machine to machine.  The solution used her is
+** to test the string right after it is generated to see if it can be
+** understood by scanf, and if not, try prepending an "0x" to see if
+** that helps.  If nothing works, a fatal error is generated.
+*/
+int sqlite3TestMakePointerStr(Tcl_Interp *interp, char *zPtr, void *p){
+  sqlite3_snprintf(100, zPtr, "%p", p);
+  return TCL_OK;
+}
+
+/*
+** The callback routine for sqlite3_exec_printf().
+*/
+static int exec_printf_cb(void *pArg, int argc, char **argv, char **name){
+  Tcl_DString *str = (Tcl_DString*)pArg;
+  int i;
+
+  if( Tcl_DStringLength(str)==0 ){
+    for(i=0; i<argc; i++){
+      Tcl_DStringAppendElement(str, name[i] ? name[i] : "NULL");
+    }
+  }
+  for(i=0; i<argc; i++){
+    Tcl_DStringAppendElement(str, argv[i] ? argv[i] : "NULL");
+  }
+  return 0;
+}
+
+/*
+** The I/O tracing callback.
+*/
+#if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE)
+static FILE *iotrace_file = 0;
+static void io_trace_callback(const char *zFormat, ...){
+  va_list ap;
+  va_start(ap, zFormat);
+  vfprintf(iotrace_file, zFormat, ap);
+  va_end(ap);
+  fflush(iotrace_file);
+}
+#endif
+
+/*
+** Usage:  io_trace FILENAME
+**
+** Turn I/O tracing on or off.  If FILENAME is not an empty string,
+** I/O tracing begins going into FILENAME. If FILENAME is an empty
+** string, I/O tracing is turned off.
+*/
+static int test_io_trace(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+#if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE)
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+          " FILENAME\"", 0);
+    return TCL_ERROR;
+  }
+  if( iotrace_file ){
+    if( iotrace_file!=stdout && iotrace_file!=stderr ){
+      fclose(iotrace_file);
+    }
+    iotrace_file = 0;
+    sqlite3IoTrace = 0;
+  }
+  if( argv[1][0] ){
+    if( strcmp(argv[1],"stdout")==0 ){
+      iotrace_file = stdout;
+    }else if( strcmp(argv[1],"stderr")==0 ){
+      iotrace_file = stderr;
+    }else{
+      iotrace_file = fopen(argv[1], "w");
+    }
+    sqlite3IoTrace = io_trace_callback;
+  }
+#endif
+  return TCL_OK;
+}
+
+
+/*
+** Usage:  sqlite3_exec_printf  DB  FORMAT  STRING
+**
+** Invoke the sqlite3_exec_printf() interface using the open database
+** DB.  The SQL is the string FORMAT.  The format string should contain
+** one %s or %q.  STRING is the value inserted into %s or %q.
+*/
+static int test_exec_printf(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  Tcl_DString str;
+  int rc;
+  char *zErr = 0;
+  char *zSql;
+  char zBuf[30];
+  if( argc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+       " DB FORMAT STRING", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  Tcl_DStringInit(&str);
+  zSql = sqlite3_mprintf(argv[2], argv[3]);
+  rc = sqlite3_exec(db, zSql, exec_printf_cb, &str, &zErr);
+  sqlite3_free(zSql);
+  sprintf(zBuf, "%d", rc);
+  Tcl_AppendElement(interp, zBuf);
+  Tcl_AppendElement(interp, rc==SQLITE_OK ? Tcl_DStringValue(&str) : zErr);
+  Tcl_DStringFree(&str);
+  if( zErr ) sqlite3_free(zErr);
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_exec_hex  DB  HEX
+**
+** Invoke the sqlite3_exec() on a string that is obtained by translating
+** HEX into ASCII.  Most characters are translated as is.  %HH becomes
+** a hex character.
+*/
+static int test_exec_hex(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  Tcl_DString str;
+  int rc, i, j;
+  char *zErr = 0;
+  char *zHex;
+  char zSql[500];
+  char zBuf[30];
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+       " DB HEX", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  zHex = argv[2];
+  for(i=j=0; i<sizeof(zSql) && zHex[j]; i++, j++){
+    if( zHex[j]=='%' && zHex[j+2] && zHex[j+2] ){
+      zSql[i] = (testHexToInt(zHex[j+1])<<4) + testHexToInt(zHex[j+2]);
+      j += 2;
+    }else{
+      zSql[i] = zHex[j];
+    }
+  }
+  zSql[i] = 0;
+  Tcl_DStringInit(&str);
+  rc = sqlite3_exec(db, zSql, exec_printf_cb, &str, &zErr);
+  sprintf(zBuf, "%d", rc);
+  Tcl_AppendElement(interp, zBuf);
+  Tcl_AppendElement(interp, rc==SQLITE_OK ? Tcl_DStringValue(&str) : zErr);
+  Tcl_DStringFree(&str);
+  if( zErr ) sqlite3_free(zErr);
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  return TCL_OK;
+}
+
+/*
+** Usage:  db_enter DB
+**         db_leave DB
+**
+** Enter or leave the mutex on a database connection.
+*/
+static int db_enter(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+       " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  sqlite3_mutex_enter(db->mutex);
+  return TCL_OK;
+}
+static int db_leave(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+       " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  sqlite3_mutex_leave(db->mutex);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_exec  DB  SQL
+**
+** Invoke the sqlite3_exec interface using the open database DB
+*/
+static int test_exec(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  Tcl_DString str;
+  int rc;
+  char *zErr = 0;
+  char *zSql;
+  int i, j;
+  char zBuf[30];
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+       " DB SQL", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  Tcl_DStringInit(&str);
+  zSql = sqlite3_mprintf("%s", argv[2]);
+  for(i=j=0; zSql[i];){
+    if( zSql[i]=='%' ){
+      zSql[j++] = (testHexToInt(zSql[i+1])<<4) + testHexToInt(zSql[i+2]);
+      i += 3;
+    }else{
+      zSql[j++] = zSql[i++];
+    }
+  }
+  zSql[j] = 0;
+  rc = sqlite3_exec(db, zSql, exec_printf_cb, &str, &zErr);
+  sqlite3_free(zSql);
+  sprintf(zBuf, "%d", rc);
+  Tcl_AppendElement(interp, zBuf);
+  Tcl_AppendElement(interp, rc==SQLITE_OK ? Tcl_DStringValue(&str) : zErr);
+  Tcl_DStringFree(&str);
+  if( zErr ) sqlite3_free(zErr);
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_exec_nr  DB  SQL
+**
+** Invoke the sqlite3_exec interface using the open database DB.  Discard
+** all results
+*/
+static int test_exec_nr(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  int rc;
+  char *zErr = 0;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+       " DB SQL", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  rc = sqlite3_exec(db, argv[2], 0, 0, &zErr);
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_mprintf_z_test  SEPARATOR  ARG0  ARG1 ...
+**
+** Test the %z format of sqlite_mprintf().  Use multiple mprintf() calls to 
+** concatenate arg0 through argn using separator as the separator.
+** Return the result.
+*/
+static int test_mprintf_z(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  char *zResult = 0;
+  int i;
+
+  for(i=2; i<argc && (i==2 || zResult); i++){
+    zResult = sqlite3_mprintf("%z%s%s", zResult, argv[1], argv[i]);
+  }
+  Tcl_AppendResult(interp, zResult, 0);
+  sqlite3_free(zResult);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_mprintf_n_test  STRING
+**
+** Test the %n format of sqlite_mprintf().  Return the length of the
+** input string.
+*/
+static int test_mprintf_n(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  char *zStr;
+  int n = 0;
+  zStr = sqlite3_mprintf("%s%n", argv[1], &n);
+  sqlite3_free(zStr);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(n));
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_snprintf_int  SIZE FORMAT  INT
+**
+** Test the of sqlite3_snprintf() routine.  SIZE is the size of the
+** output buffer in bytes.  The maximum size is 100.  FORMAT is the
+** format string.  INT is a single integer argument.  The FORMAT
+** string must require no more than this one integer argument.  If
+** You pass in a format string that requires more than one argument,
+** bad things will happen.
+*/
+static int test_snprintf_int(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  char zStr[100];
+  int n = atoi(argv[1]);
+  const char *zFormat = argv[2];
+  int a1 = atoi(argv[3]);
+  if( n>sizeof(zStr) ) n = sizeof(zStr);
+  sqlite3_snprintf(sizeof(zStr), zStr, "abcdefghijklmnopqrstuvwxyz");
+  sqlite3_snprintf(n, zStr, zFormat, a1);
+  Tcl_AppendResult(interp, zStr, 0);
+  return TCL_OK;
+}
+
+#ifndef SQLITE_OMIT_GET_TABLE
+
+/*
+** Usage:  sqlite3_get_table_printf  DB  FORMAT  STRING  ?--no-counts?
+**
+** Invoke the sqlite3_get_table_printf() interface using the open database
+** DB.  The SQL is the string FORMAT.  The format string should contain
+** one %s or %q.  STRING is the value inserted into %s or %q.
+*/
+static int test_get_table_printf(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  Tcl_DString str;
+  int rc;
+  char *zErr = 0;
+  int nRow, nCol;
+  char **aResult;
+  int i;
+  char zBuf[30];
+  char *zSql;
+  int resCount = -1;
+  if( argc==5 ){
+    if( Tcl_GetInt(interp, argv[4], &resCount) ) return TCL_ERROR;
+  }
+  if( argc!=4 && argc!=5 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+       " DB FORMAT STRING ?COUNT?", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  Tcl_DStringInit(&str);
+  zSql = sqlite3_mprintf(argv[2],argv[3]);
+  if( argc==5 ){
+    rc = sqlite3_get_table(db, zSql, &aResult, 0, 0, &zErr);
+  }else{
+    rc = sqlite3_get_table(db, zSql, &aResult, &nRow, &nCol, &zErr);
+    resCount = (nRow+1)*nCol;
+  }
+  sqlite3_free(zSql);
+  sprintf(zBuf, "%d", rc);
+  Tcl_AppendElement(interp, zBuf);
+  if( rc==SQLITE_OK ){
+    if( argc==4 ){
+      sprintf(zBuf, "%d", nRow);
+      Tcl_AppendElement(interp, zBuf);
+      sprintf(zBuf, "%d", nCol);
+      Tcl_AppendElement(interp, zBuf);
+    }
+    for(i=0; i<resCount; i++){
+      Tcl_AppendElement(interp, aResult[i] ? aResult[i] : "NULL");
+    }
+  }else{
+    Tcl_AppendElement(interp, zErr);
+  }
+  sqlite3_free_table(aResult);
+  if( zErr ) sqlite3_free(zErr);
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  return TCL_OK;
+}
+
+#endif /* SQLITE_OMIT_GET_TABLE */
+
+
+/*
+** Usage:  sqlite3_last_insert_rowid DB
+**
+** Returns the integer ROWID of the most recent insert.
+*/
+static int test_last_rowid(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  char zBuf[30];
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " DB\"", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  sprintf(zBuf, "%lld", sqlite3_last_insert_rowid(db));
+  Tcl_AppendResult(interp, zBuf, 0);
+  return SQLITE_OK;
+}
+
+/*
+** Usage:  sqlite3_key DB KEY
+**
+** Set the codec key.
+*/
+static int test_key(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+#ifdef SQLITE_HAS_CODEC
+  sqlite3 *db;
+  const char *zKey;
+  int nKey;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FILENAME\"", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  zKey = argv[2];
+  nKey = strlen(zKey);
+  sqlite3_key(db, zKey, nKey);
+#endif
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_rekey DB KEY
+**
+** Change the codec key.
+*/
+static int test_rekey(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+#ifdef SQLITE_HAS_CODEC
+  sqlite3 *db;
+  const char *zKey;
+  int nKey;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FILENAME\"", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  zKey = argv[2];
+  nKey = strlen(zKey);
+  sqlite3_rekey(db, zKey, nKey);
+#endif
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_close DB
+**
+** Closes the database opened by sqlite3_open.
+*/
+static int sqlite_test_close(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FILENAME\"", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  rc = sqlite3_close(db);
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_close_v2 DB
+**
+** Closes the database opened by sqlite3_open.
+*/
+static int sqlite_test_close_v2(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FILENAME\"", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  rc = sqlite3_close_v2(db);
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+  return TCL_OK;
+}
+
+/*
+** Implementation of the x_coalesce() function.
+** Return the first argument non-NULL argument.
+*/
+static void t1_ifnullFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  int i;
+  for(i=0; i<argc; i++){
+    if( SQLITE_NULL!=sqlite3_value_type(argv[i]) ){
+      int n = sqlite3_value_bytes(argv[i]);
+      sqlite3_result_text(context, (char*)sqlite3_value_text(argv[i]),
+          n, SQLITE_TRANSIENT);
+      break;
+    }
+  }
+}
+
+/*
+** These are test functions.    hex8() interprets its argument as
+** UTF8 and returns a hex encoding.  hex16le() interprets its argument
+** as UTF16le and returns a hex encoding.
+*/
+static void hex8Func(sqlite3_context *p, int argc, sqlite3_value **argv){
+  const unsigned char *z;
+  int i;
+  char zBuf[200];
+  z = sqlite3_value_text(argv[0]);
+  for(i=0; i<sizeof(zBuf)/2 - 2 && z[i]; i++){
+    sprintf(&zBuf[i*2], "%02x", z[i]&0xff);
+  }
+  zBuf[i*2] = 0;
+  sqlite3_result_text(p, (char*)zBuf, -1, SQLITE_TRANSIENT);
+}
+#ifndef SQLITE_OMIT_UTF16
+static void hex16Func(sqlite3_context *p, int argc, sqlite3_value **argv){
+  const unsigned short int *z;
+  int i;
+  char zBuf[400];
+  z = sqlite3_value_text16(argv[0]);
+  for(i=0; i<sizeof(zBuf)/4 - 4 && z[i]; i++){
+    sprintf(&zBuf[i*4], "%04x", z[i]&0xff);
+  }
+  zBuf[i*4] = 0;
+  sqlite3_result_text(p, (char*)zBuf, -1, SQLITE_TRANSIENT);
+}
+#endif
+
+/*
+** A structure into which to accumulate text.
+*/
+struct dstr {
+  int nAlloc;  /* Space allocated */
+  int nUsed;   /* Space used */
+  char *z;     /* The space */
+};
+
+/*
+** Append text to a dstr
+*/
+static void dstrAppend(struct dstr *p, const char *z, int divider){
+  int n = (int)strlen(z);
+  if( p->nUsed + n + 2 > p->nAlloc ){
+    char *zNew;
+    p->nAlloc = p->nAlloc*2 + n + 200;
+    zNew = sqlite3_realloc(p->z, p->nAlloc);
+    if( zNew==0 ){
+      sqlite3_free(p->z);
+      memset(p, 0, sizeof(*p));
+      return;
+    }
+    p->z = zNew;
+  }
+  if( divider && p->nUsed>0 ){
+    p->z[p->nUsed++] = divider;
+  }
+  memcpy(&p->z[p->nUsed], z, n+1);
+  p->nUsed += n;
+}
+
+/*
+** Invoked for each callback from sqlite3ExecFunc
+*/
+static int execFuncCallback(void *pData, int argc, char **argv, char **NotUsed){
+  struct dstr *p = (struct dstr*)pData;
+  int i;
+  for(i=0; i<argc; i++){
+    if( argv[i]==0 ){
+      dstrAppend(p, "NULL", ' ');
+    }else{
+      dstrAppend(p, argv[i], ' ');
+    }
+  }
+  return 0;
+}
+
+/*
+** Implementation of the x_sqlite_exec() function.  This function takes
+** a single argument and attempts to execute that argument as SQL code.
+** This is illegal and should set the SQLITE_MISUSE flag on the database.
+**
+** 2004-Jan-07:  We have changed this to make it legal to call sqlite3_exec()
+** from within a function call.  
+** 
+** This routine simulates the effect of having two threads attempt to
+** use the same database at the same time.
+*/
+static void sqlite3ExecFunc(
+  sqlite3_context *context, 
+  int argc,  
+  sqlite3_value **argv
+){
+  struct dstr x;
+  memset(&x, 0, sizeof(x));
+  (void)sqlite3_exec((sqlite3*)sqlite3_user_data(context),
+      (char*)sqlite3_value_text(argv[0]),
+      execFuncCallback, &x, 0);
+  sqlite3_result_text(context, x.z, x.nUsed, SQLITE_TRANSIENT);
+  sqlite3_free(x.z);
+}
+
+/*
+** Implementation of tkt2213func(), a scalar function that takes exactly
+** one argument. It has two interesting features:
+**
+** * It calls sqlite3_value_text() 3 times on the argument sqlite3_value*.
+**   If the three pointers returned are not the same an SQL error is raised.
+**
+** * Otherwise it returns a copy of the text representation of its 
+**   argument in such a way as the VDBE representation is a Mem* cell 
+**   with the MEM_Term flag clear. 
+**
+** Ticket #2213 can therefore be tested by evaluating the following
+** SQL expression:
+**
+**   tkt2213func(tkt2213func('a string'));
+*/
+static void tkt2213Function(
+  sqlite3_context *context, 
+  int argc,  
+  sqlite3_value **argv
+){
+  int nText;
+  unsigned char const *zText1;
+  unsigned char const *zText2;
+  unsigned char const *zText3;
+
+  nText = sqlite3_value_bytes(argv[0]);
+  zText1 = sqlite3_value_text(argv[0]);
+  zText2 = sqlite3_value_text(argv[0]);
+  zText3 = sqlite3_value_text(argv[0]);
+
+  if( zText1!=zText2 || zText2!=zText3 ){
+    sqlite3_result_error(context, "tkt2213 is not fixed", -1);
+  }else{
+    char *zCopy = (char *)sqlite3_malloc(nText);
+    memcpy(zCopy, zText1, nText);
+    sqlite3_result_text(context, zCopy, nText, sqlite3_free);
+  }
+}
+
+/*
+** The following SQL function takes 4 arguments.  The 2nd and
+** 4th argument must be one of these strings:  'text', 'text16',
+** or 'blob' corresponding to API functions
+**
+**      sqlite3_value_text()
+**      sqlite3_value_text16()
+**      sqlite3_value_blob()
+**
+** The third argument is a string, either 'bytes' or 'bytes16' or 'noop',
+** corresponding to APIs:
+**
+**      sqlite3_value_bytes()
+**      sqlite3_value_bytes16()
+**      noop
+**
+** The APIs designated by the 2nd through 4th arguments are applied
+** to the first argument in order.  If the pointers returned by the
+** second and fourth are different, this routine returns 1.  Otherwise,
+** this routine returns 0.
+**
+** This function is used to test to see when returned pointers from
+** the _text(), _text16() and _blob() APIs become invalidated.
+*/
+static void ptrChngFunction(
+  sqlite3_context *context, 
+  int argc,  
+  sqlite3_value **argv
+){
+  const void *p1, *p2;
+  const char *zCmd;
+  if( argc!=4 ) return;
+  zCmd = (const char*)sqlite3_value_text(argv[1]);
+  if( zCmd==0 ) return;
+  if( strcmp(zCmd,"text")==0 ){
+    p1 = (const void*)sqlite3_value_text(argv[0]);
+#ifndef SQLITE_OMIT_UTF16
+  }else if( strcmp(zCmd, "text16")==0 ){
+    p1 = (const void*)sqlite3_value_text16(argv[0]);
+#endif
+  }else if( strcmp(zCmd, "blob")==0 ){
+    p1 = (const void*)sqlite3_value_blob(argv[0]);
+  }else{
+    return;
+  }
+  zCmd = (const char*)sqlite3_value_text(argv[2]);
+  if( zCmd==0 ) return;
+  if( strcmp(zCmd,"bytes")==0 ){
+    sqlite3_value_bytes(argv[0]);
+#ifndef SQLITE_OMIT_UTF16
+  }else if( strcmp(zCmd, "bytes16")==0 ){
+    sqlite3_value_bytes16(argv[0]);
+#endif
+  }else if( strcmp(zCmd, "noop")==0 ){
+    /* do nothing */
+  }else{
+    return;
+  }
+  zCmd = (const char*)sqlite3_value_text(argv[3]);
+  if( zCmd==0 ) return;
+  if( strcmp(zCmd,"text")==0 ){
+    p2 = (const void*)sqlite3_value_text(argv[0]);
+#ifndef SQLITE_OMIT_UTF16
+  }else if( strcmp(zCmd, "text16")==0 ){
+    p2 = (const void*)sqlite3_value_text16(argv[0]);
+#endif
+  }else if( strcmp(zCmd, "blob")==0 ){
+    p2 = (const void*)sqlite3_value_blob(argv[0]);
+  }else{
+    return;
+  }
+  sqlite3_result_int(context, p1!=p2);
+}
+
+
+/*
+** Usage:  sqlite_test_create_function DB
+**
+** Call the sqlite3_create_function API on the given database in order
+** to create a function named "x_coalesce".  This function does the same thing
+** as the "coalesce" function.  This function also registers an SQL function
+** named "x_sqlite_exec" that invokes sqlite3_exec().  Invoking sqlite3_exec()
+** in this way is illegal recursion and should raise an SQLITE_MISUSE error.
+** The effect is similar to trying to use the same database connection from
+** two threads at the same time.
+**
+** The original motivation for this routine was to be able to call the
+** sqlite3_create_function function while a query is in progress in order
+** to test the SQLITE_MISUSE detection logic.
+*/
+static int test_create_function(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  int rc;
+  sqlite3 *db;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " DB\"", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  rc = sqlite3_create_function(db, "x_coalesce", -1, SQLITE_ANY, 0, 
+        t1_ifnullFunc, 0, 0);
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_create_function(db, "hex8", 1, SQLITE_ANY, 0, 
+          hex8Func, 0, 0);
+  }
+#ifndef SQLITE_OMIT_UTF16
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_create_function(db, "hex16", 1, SQLITE_ANY, 0, 
+          hex16Func, 0, 0);
+  }
+#endif
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_create_function(db, "tkt2213func", 1, SQLITE_ANY, 0, 
+          tkt2213Function, 0, 0);
+  }
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_create_function(db, "pointer_change", 4, SQLITE_ANY, 0, 
+          ptrChngFunction, 0, 0);
+  }
+
+#ifndef SQLITE_OMIT_UTF16
+  /* Use the sqlite3_create_function16() API here. Mainly for fun, but also 
+  ** because it is not tested anywhere else. */
+  if( rc==SQLITE_OK ){
+    const void *zUtf16;
+    sqlite3_value *pVal;
+    sqlite3_mutex_enter(db->mutex);
+    pVal = sqlite3ValueNew(db);
+    sqlite3ValueSetStr(pVal, -1, "x_sqlite_exec", SQLITE_UTF8, SQLITE_STATIC);
+    zUtf16 = sqlite3ValueText(pVal, SQLITE_UTF16NATIVE);
+    if( db->mallocFailed ){
+      rc = SQLITE_NOMEM;
+    }else{
+      rc = sqlite3_create_function16(db, zUtf16, 
+                1, SQLITE_UTF16, db, sqlite3ExecFunc, 0, 0);
+    }
+    sqlite3ValueFree(pVal);
+    sqlite3_mutex_leave(db->mutex);
+  }
+#endif
+
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), 0);
+  return TCL_OK;
+}
+
+/*
+** Routines to implement the x_count() aggregate function.
+**
+** x_count() counts the number of non-null arguments.  But there are
+** some twists for testing purposes.
+**
+** If the argument to x_count() is 40 then a UTF-8 error is reported
+** on the step function.  If x_count(41) is seen, then a UTF-16 error
+** is reported on the step function.  If the total count is 42, then
+** a UTF-8 error is reported on the finalize function.
+*/
+typedef struct t1CountCtx t1CountCtx;
+struct t1CountCtx {
+  int n;
+};
+static void t1CountStep(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  t1CountCtx *p;
+  p = sqlite3_aggregate_context(context, sizeof(*p));
+  if( (argc==0 || SQLITE_NULL!=sqlite3_value_type(argv[0]) ) && p ){
+    p->n++;
+  }
+  if( argc>0 ){
+    int v = sqlite3_value_int(argv[0]);
+    if( v==40 ){
+      sqlite3_result_error(context, "value of 40 handed to x_count", -1);
+#ifndef SQLITE_OMIT_UTF16
+    }else if( v==41 ){
+      const char zUtf16ErrMsg[] = { 0, 0x61, 0, 0x62, 0, 0x63, 0, 0, 0};
+      sqlite3_result_error16(context, &zUtf16ErrMsg[1-SQLITE_BIGENDIAN], -1);
+#endif
+    }
+  }
+}   
+static void t1CountFinalize(sqlite3_context *context){
+  t1CountCtx *p;
+  p = sqlite3_aggregate_context(context, sizeof(*p));
+  if( p ){
+    if( p->n==42 ){
+      sqlite3_result_error(context, "x_count totals to 42", -1);
+    }else{
+      sqlite3_result_int(context, p ? p->n : 0);
+    }
+  }
+}
+
+#ifndef SQLITE_OMIT_DEPRECATED
+static void legacyCountStep(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  /* no-op */
+}
+
+static void legacyCountFinalize(sqlite3_context *context){
+  sqlite3_result_int(context, sqlite3_aggregate_count(context));
+}
+#endif
+
+/*
+** Usage:  sqlite3_create_aggregate DB
+**
+** Call the sqlite3_create_function API on the given database in order
+** to create a function named "x_count".  This function is similar
+** to the built-in count() function, with a few special quirks
+** for testing the sqlite3_result_error() APIs.
+**
+** The original motivation for this routine was to be able to call the
+** sqlite3_create_aggregate function while a query is in progress in order
+** to test the SQLITE_MISUSE detection logic.  See misuse.test.
+**
+** This routine was later extended to test the use of sqlite3_result_error()
+** within aggregate functions.
+**
+** Later: It is now also extended to register the aggregate function
+** "legacy_count()" with the supplied database handle. This is used
+** to test the deprecated sqlite3_aggregate_count() API.
+*/
+static int test_create_aggregate(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FILENAME\"", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  rc = sqlite3_create_function(db, "x_count", 0, SQLITE_UTF8, 0, 0,
+      t1CountStep,t1CountFinalize);
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_create_function(db, "x_count", 1, SQLITE_UTF8, 0, 0,
+        t1CountStep,t1CountFinalize);
+  }
+#ifndef SQLITE_OMIT_DEPRECATED
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_create_function(db, "legacy_count", 0, SQLITE_ANY, 0, 0,
+        legacyCountStep, legacyCountFinalize
+    );
+  }
+#endif
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), 0);
+  return TCL_OK;
+}
+
+
+/*
+** Usage:  printf TEXT
+**
+** Send output to printf.  Use this rather than puts to merge the output
+** in the correct sequence with debugging printfs inserted into C code.
+** Puts uses a separate buffer and debugging statements will be out of
+** sequence if it is used.
+*/
+static int test_printf(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " TEXT\"", 0);
+    return TCL_ERROR;
+  }
+  printf("%s\n", argv[1]);
+  return TCL_OK;
+}
+
+
+
+/*
+** Usage:  sqlite3_mprintf_int FORMAT INTEGER INTEGER INTEGER
+**
+** Call mprintf with three integer arguments
+*/
+static int sqlite3_mprintf_int(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  int a[3], i;
+  char *z;
+  if( argc!=5 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FORMAT INT INT INT\"", 0);
+    return TCL_ERROR;
+  }
+  for(i=2; i<5; i++){
+    if( Tcl_GetInt(interp, argv[i], &a[i-2]) ) return TCL_ERROR;
+  }
+  z = sqlite3_mprintf(argv[1], a[0], a[1], a[2]);
+  Tcl_AppendResult(interp, z, 0);
+  sqlite3_free(z);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_mprintf_int64 FORMAT INTEGER INTEGER INTEGER
+**
+** Call mprintf with three 64-bit integer arguments
+*/
+static int sqlite3_mprintf_int64(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  int i;
+  sqlite_int64 a[3];
+  char *z;
+  if( argc!=5 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FORMAT INT INT INT\"", 0);
+    return TCL_ERROR;
+  }
+  for(i=2; i<5; i++){
+    if( sqlite3Atoi64(argv[i], &a[i-2], 1000000, SQLITE_UTF8) ){
+      Tcl_AppendResult(interp, "argument is not a valid 64-bit integer", 0);
+      return TCL_ERROR;
+    }
+  }
+  z = sqlite3_mprintf(argv[1], a[0], a[1], a[2]);
+  Tcl_AppendResult(interp, z, 0);
+  sqlite3_free(z);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_mprintf_long FORMAT INTEGER INTEGER INTEGER
+**
+** Call mprintf with three long integer arguments.   This might be the
+** same as sqlite3_mprintf_int or sqlite3_mprintf_int64, depending on
+** platform.
+*/
+static int sqlite3_mprintf_long(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  int i;
+  long int a[3];
+  int b[3];
+  char *z;
+  if( argc!=5 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FORMAT INT INT INT\"", 0);
+    return TCL_ERROR;
+  }
+  for(i=2; i<5; i++){
+    if( Tcl_GetInt(interp, argv[i], &b[i-2]) ) return TCL_ERROR;
+    a[i-2] = (long int)b[i-2];
+    a[i-2] &= (((u64)1)<<(sizeof(int)*8))-1;
+  }
+  z = sqlite3_mprintf(argv[1], a[0], a[1], a[2]);
+  Tcl_AppendResult(interp, z, 0);
+  sqlite3_free(z);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_mprintf_str FORMAT INTEGER INTEGER STRING
+**
+** Call mprintf with two integer arguments and one string argument
+*/
+static int sqlite3_mprintf_str(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  int a[3], i;
+  char *z;
+  if( argc<4 || argc>5 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FORMAT INT INT ?STRING?\"", 0);
+    return TCL_ERROR;
+  }
+  for(i=2; i<4; i++){
+    if( Tcl_GetInt(interp, argv[i], &a[i-2]) ) return TCL_ERROR;
+  }
+  z = sqlite3_mprintf(argv[1], a[0], a[1], argc>4 ? argv[4] : NULL);
+  Tcl_AppendResult(interp, z, 0);
+  sqlite3_free(z);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_snprintf_str INTEGER FORMAT INTEGER INTEGER STRING
+**
+** Call mprintf with two integer arguments and one string argument
+*/
+static int sqlite3_snprintf_str(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  int a[3], i;
+  int n;
+  char *z;
+  if( argc<5 || argc>6 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " INT FORMAT INT INT ?STRING?\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], &n) ) return TCL_ERROR;
+  if( n<0 ){
+    Tcl_AppendResult(interp, "N must be non-negative", 0);
+    return TCL_ERROR;
+  }
+  for(i=3; i<5; i++){
+    if( Tcl_GetInt(interp, argv[i], &a[i-3]) ) return TCL_ERROR;
+  }
+  z = sqlite3_malloc( n+1 );
+  sqlite3_snprintf(n, z, argv[2], a[0], a[1], argc>4 ? argv[5] : NULL);
+  Tcl_AppendResult(interp, z, 0);
+  sqlite3_free(z);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_mprintf_double FORMAT INTEGER INTEGER DOUBLE
+**
+** Call mprintf with two integer arguments and one double argument
+*/
+static int sqlite3_mprintf_double(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  int a[3], i;
+  double r;
+  char *z;
+  if( argc!=5 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FORMAT INT INT DOUBLE\"", 0);
+    return TCL_ERROR;
+  }
+  for(i=2; i<4; i++){
+    if( Tcl_GetInt(interp, argv[i], &a[i-2]) ) return TCL_ERROR;
+  }
+  if( Tcl_GetDouble(interp, argv[4], &r) ) return TCL_ERROR;
+  z = sqlite3_mprintf(argv[1], a[0], a[1], r);
+  Tcl_AppendResult(interp, z, 0);
+  sqlite3_free(z);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_mprintf_scaled FORMAT DOUBLE DOUBLE
+**
+** Call mprintf with a single double argument which is the product of the
+** two arguments given above.  This is used to generate overflow and underflow
+** doubles to test that they are converted properly.
+*/
+static int sqlite3_mprintf_scaled(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  int i;
+  double r[2];
+  char *z;
+  if( argc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FORMAT DOUBLE DOUBLE\"", 0);
+    return TCL_ERROR;
+  }
+  for(i=2; i<4; i++){
+    if( Tcl_GetDouble(interp, argv[i], &r[i-2]) ) return TCL_ERROR;
+  }
+  z = sqlite3_mprintf(argv[1], r[0]*r[1]);
+  Tcl_AppendResult(interp, z, 0);
+  sqlite3_free(z);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_mprintf_stronly FORMAT STRING
+**
+** Call mprintf with a single double argument which is the product of the
+** two arguments given above.  This is used to generate overflow and underflow
+** doubles to test that they are converted properly.
+*/
+static int sqlite3_mprintf_stronly(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  char *z;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FORMAT STRING\"", 0);
+    return TCL_ERROR;
+  }
+  z = sqlite3_mprintf(argv[1], argv[2]);
+  Tcl_AppendResult(interp, z, 0);
+  sqlite3_free(z);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_mprintf_hexdouble FORMAT HEX
+**
+** Call mprintf with a single double argument which is derived from the
+** hexadecimal encoding of an IEEE double.
+*/
+static int sqlite3_mprintf_hexdouble(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  char *z;
+  double r;
+  unsigned int x1, x2;
+  sqlite_uint64 d;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FORMAT STRING\"", 0);
+    return TCL_ERROR;
+  }
+  if( sscanf(argv[2], "%08x%08x", &x2, &x1)!=2 ){
+    Tcl_AppendResult(interp, "2nd argument should be 16-characters of hex", 0);
+    return TCL_ERROR;
+  }
+  d = x2;
+  d = (d<<32) + x1;
+  memcpy(&r, &d, sizeof(r));
+  z = sqlite3_mprintf(argv[1], r);
+  Tcl_AppendResult(interp, z, 0);
+  sqlite3_free(z);
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_enable_shared_cache ?BOOLEAN?
+**
+*/
+#if !defined(SQLITE_OMIT_SHARED_CACHE)
+static int test_enable_shared(
+  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 *CONST objv[]  /* Command arguments */
+){
+  int rc;
+  int enable;
+  int ret = 0;
+
+  if( objc!=2 && objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "?BOOLEAN?");
+    return TCL_ERROR;
+  }
+  ret = sqlite3GlobalConfig.sharedCacheEnabled;
+
+  if( objc==2 ){
+    if( Tcl_GetBooleanFromObj(interp, objv[1], &enable) ){
+      return TCL_ERROR;
+    }
+    rc = sqlite3_enable_shared_cache(enable);
+    if( rc!=SQLITE_OK ){
+      Tcl_SetResult(interp, (char *)sqlite3ErrStr(rc), TCL_STATIC);
+      return TCL_ERROR;
+    }
+  }
+  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(ret));
+  return TCL_OK;
+}
+#endif
+
+
+
+/*
+** Usage: sqlite3_extended_result_codes   DB    BOOLEAN
+**
+*/
+static int test_extended_result_codes(
+  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 *CONST objv[]  /* Command arguments */
+){
+  int enable;
+  sqlite3 *db;
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB BOOLEAN");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  if( Tcl_GetBooleanFromObj(interp, objv[2], &enable) ) return TCL_ERROR;
+  sqlite3_extended_result_codes(db, enable);
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_libversion_number
+**
+*/
+static int test_libversion_number(
+  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 *CONST objv[]  /* Command arguments */
+){
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_libversion_number()));
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_table_column_metadata DB dbname tblname colname
+**
+*/
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+static int test_table_column_metadata(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  const char *zDb;
+  const char *zTbl;
+  const char *zCol;
+  int rc;
+  Tcl_Obj *pRet;
+
+  const char *zDatatype;
+  const char *zCollseq;
+  int notnull;
+  int primarykey;
+  int autoincrement;
+
+  if( objc!=5 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB dbname tblname colname");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zDb = Tcl_GetString(objv[2]);
+  zTbl = Tcl_GetString(objv[3]);
+  zCol = Tcl_GetString(objv[4]);
+
+  if( strlen(zDb)==0 ) zDb = 0;
+
+  rc = sqlite3_table_column_metadata(db, zDb, zTbl, zCol, 
+      &zDatatype, &zCollseq, &notnull, &primarykey, &autoincrement);
+
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3_errmsg(db), 0);
+    return TCL_ERROR;
+  }
+
+  pRet = Tcl_NewObj();
+  Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zDatatype, -1));
+  Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zCollseq, -1));
+  Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(notnull));
+  Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(primarykey));
+  Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(autoincrement));
+  Tcl_SetObjResult(interp, pRet);
+
+  return TCL_OK;
+}
+#endif
+
+#ifndef SQLITE_OMIT_INCRBLOB
+
+static int blobHandleFromObj(
+  Tcl_Interp *interp, 
+  Tcl_Obj *pObj,
+  sqlite3_blob **ppBlob
+){
+  char *z;
+  int n;
+
+  z = Tcl_GetStringFromObj(pObj, &n);
+  if( n==0 ){
+    *ppBlob = 0;
+  }else{
+    int notUsed;
+    Tcl_Channel channel;
+    ClientData instanceData;
+    
+    channel = Tcl_GetChannel(interp, z, &notUsed);
+    if( !channel ) return TCL_ERROR;
+
+    Tcl_Flush(channel);
+    Tcl_Seek(channel, 0, SEEK_SET);
+
+    instanceData = Tcl_GetChannelInstanceData(channel);
+    *ppBlob = *((sqlite3_blob **)instanceData);
+  }
+
+  return TCL_OK;
+}
+
+/*
+** sqlite3_blob_bytes  CHANNEL
+*/
+static int test_blob_bytes(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  sqlite3_blob *pBlob;
+  int nByte;
+  
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "CHANNEL");
+    return TCL_ERROR;
+  }
+
+  if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR;
+  nByte = sqlite3_blob_bytes(pBlob);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(nByte));
+
+  return TCL_OK;
+}
+
+/*
+** sqlite3_blob_close  CHANNEL
+*/
+static int test_blob_close(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  sqlite3_blob *pBlob;
+  
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "CHANNEL");
+    return TCL_ERROR;
+  }
+
+  if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR;
+  sqlite3_blob_close(pBlob);
+
+  return TCL_OK;
+}
+
+/*
+** sqlite3_blob_read  CHANNEL OFFSET N
+**
+**   This command is used to test the sqlite3_blob_read() in ways that
+**   the Tcl channel interface does not. The first argument should
+**   be the name of a valid channel created by the [incrblob] method
+**   of a database handle. This function calls sqlite3_blob_read()
+**   to read N bytes from offset OFFSET from the underlying SQLite
+**   blob handle.
+**
+**   On success, a byte-array object containing the read data is 
+**   returned. On failure, the interpreter result is set to the
+**   text representation of the returned error code (i.e. "SQLITE_NOMEM")
+**   and a Tcl exception is thrown.
+*/
+static int test_blob_read(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  sqlite3_blob *pBlob;
+  int nByte;
+  int iOffset;
+  unsigned char *zBuf = 0;
+  int rc;
+  
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "CHANNEL OFFSET N");
+    return TCL_ERROR;
+  }
+
+  if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR;
+  if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iOffset)
+   || TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &nByte)
+  ){ 
+    return TCL_ERROR;
+  }
+
+  if( nByte>0 ){
+    zBuf = (unsigned char *)Tcl_Alloc(nByte);
+  }
+  rc = sqlite3_blob_read(pBlob, zBuf, nByte, iOffset);
+  if( rc==SQLITE_OK ){
+    Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(zBuf, nByte));
+  }else{
+    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+  }
+  Tcl_Free((char *)zBuf);
+
+  return (rc==SQLITE_OK ? TCL_OK : TCL_ERROR);
+}
+
+/*
+** sqlite3_blob_write CHANNEL OFFSET DATA ?NDATA?
+**
+**   This command is used to test the sqlite3_blob_write() in ways that
+**   the Tcl channel interface does not. The first argument should
+**   be the name of a valid channel created by the [incrblob] method
+**   of a database handle. This function calls sqlite3_blob_write()
+**   to write the DATA byte-array to the underlying SQLite blob handle.
+**   at offset OFFSET.
+**
+**   On success, an empty string is returned. On failure, the interpreter
+**   result is set to the text representation of the returned error code 
+**   (i.e. "SQLITE_NOMEM") and a Tcl exception is thrown.
+*/
+static int test_blob_write(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  sqlite3_blob *pBlob;
+  int iOffset;
+  int rc;
+
+  unsigned char *zBuf;
+  int nBuf;
+  
+  if( objc!=4 && objc!=5 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "CHANNEL OFFSET DATA ?NDATA?");
+    return TCL_ERROR;
+  }
+
+  if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR;
+  if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iOffset) ){ 
+    return TCL_ERROR;
+  }
+
+  zBuf = Tcl_GetByteArrayFromObj(objv[3], &nBuf);
+  if( objc==5 && Tcl_GetIntFromObj(interp, objv[4], &nBuf) ){
+    return TCL_ERROR;
+  }
+  rc = sqlite3_blob_write(pBlob, zBuf, nBuf, iOffset);
+  if( rc!=SQLITE_OK ){
+    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+  }
+
+  return (rc==SQLITE_OK ? TCL_OK : TCL_ERROR);
+}
+
+static int test_blob_reopen(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  Tcl_WideInt iRowid;
+  sqlite3_blob *pBlob;
+  int rc;
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "CHANNEL ROWID");
+    return TCL_ERROR;
+  }
+
+  if( blobHandleFromObj(interp, objv[1], &pBlob) ) return TCL_ERROR;
+  if( Tcl_GetWideIntFromObj(interp, objv[2], &iRowid) ) return TCL_ERROR;
+
+  rc = sqlite3_blob_reopen(pBlob, iRowid);
+  if( rc!=SQLITE_OK ){
+    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+  }
+
+  return (rc==SQLITE_OK ? TCL_OK : TCL_ERROR);
+}
+
+#endif
+
+/*
+** Usage: sqlite3_create_collation_v2 DB-HANDLE NAME CMP-PROC DEL-PROC
+**
+**   This Tcl proc is used for testing the experimental
+**   sqlite3_create_collation_v2() interface.
+*/
+struct TestCollationX {
+  Tcl_Interp *interp;
+  Tcl_Obj *pCmp;
+  Tcl_Obj *pDel;
+};
+typedef struct TestCollationX TestCollationX;
+static void testCreateCollationDel(void *pCtx){
+  TestCollationX *p = (TestCollationX *)pCtx;
+
+  int rc = Tcl_EvalObjEx(p->interp, p->pDel, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
+  if( rc!=TCL_OK ){
+    Tcl_BackgroundError(p->interp);
+  }
+
+  Tcl_DecrRefCount(p->pCmp);
+  Tcl_DecrRefCount(p->pDel);
+  sqlite3_free((void *)p);
+}
+static int testCreateCollationCmp(
+  void *pCtx,
+  int nLeft,
+  const void *zLeft,
+  int nRight,
+  const void *zRight
+){
+  TestCollationX *p = (TestCollationX *)pCtx;
+  Tcl_Obj *pScript = Tcl_DuplicateObj(p->pCmp);
+  int iRes = 0;
+
+  Tcl_IncrRefCount(pScript);
+  Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj((char *)zLeft, nLeft));
+  Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj((char *)zRight,nRight));
+
+  if( TCL_OK!=Tcl_EvalObjEx(p->interp, pScript, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL)
+   || TCL_OK!=Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iRes)
+  ){
+    Tcl_BackgroundError(p->interp);
+  }
+  Tcl_DecrRefCount(pScript);
+
+  return iRes;
+}
+static int test_create_collation_v2(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  TestCollationX *p;
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=5 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB-HANDLE NAME CMP-PROC DEL-PROC");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+
+  p = (TestCollationX *)sqlite3_malloc(sizeof(TestCollationX));
+  p->pCmp = objv[3];
+  p->pDel = objv[4];
+  p->interp = interp;
+  Tcl_IncrRefCount(p->pCmp);
+  Tcl_IncrRefCount(p->pDel);
+
+  rc = sqlite3_create_collation_v2(db, Tcl_GetString(objv[2]), 16, 
+      (void *)p, testCreateCollationCmp, testCreateCollationDel
+  );
+  if( rc!=SQLITE_MISUSE ){
+    Tcl_AppendResult(interp, "sqlite3_create_collate_v2() failed to detect "
+      "an invalid encoding", (char*)0);
+    return TCL_ERROR;
+  }
+  rc = sqlite3_create_collation_v2(db, Tcl_GetString(objv[2]), SQLITE_UTF8, 
+      (void *)p, testCreateCollationCmp, testCreateCollationDel
+  );
+  return TCL_OK;
+}
+
+/*
+** USAGE: sqlite3_create_function_v2 DB NAME NARG ENC ?SWITCHES?
+**
+** Available switches are:
+**
+**   -func    SCRIPT
+**   -step    SCRIPT
+**   -final   SCRIPT
+**   -destroy SCRIPT
+*/
+typedef struct CreateFunctionV2 CreateFunctionV2;
+struct CreateFunctionV2 {
+  Tcl_Interp *interp;
+  Tcl_Obj *pFunc;                 /* Script for function invocation */
+  Tcl_Obj *pStep;                 /* Script for agg. step invocation */
+  Tcl_Obj *pFinal;                /* Script for agg. finalization invocation */
+  Tcl_Obj *pDestroy;              /* Destructor script */
+};
+static void cf2Func(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){
+}
+static void cf2Step(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){
+}
+static void cf2Final(sqlite3_context *ctx){
+}
+static void cf2Destroy(void *pUser){
+  CreateFunctionV2 *p = (CreateFunctionV2 *)pUser;
+
+  if( p->interp && p->pDestroy ){
+    int rc = Tcl_EvalObjEx(p->interp, p->pDestroy, 0);
+    if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp);
+  }
+
+  if( p->pFunc ) Tcl_DecrRefCount(p->pFunc); 
+  if( p->pStep ) Tcl_DecrRefCount(p->pStep); 
+  if( p->pFinal ) Tcl_DecrRefCount(p->pFinal); 
+  if( p->pDestroy ) Tcl_DecrRefCount(p->pDestroy); 
+  sqlite3_free(p);
+}
+static int test_create_function_v2(
+  ClientData clientData,          /* Not used */
+  Tcl_Interp *interp,             /* The invoking TCL interpreter */
+  int objc,                       /* Number of arguments */
+  Tcl_Obj *CONST objv[]           /* Command arguments */
+){
+  sqlite3 *db;
+  const char *zFunc;
+  int nArg;
+  int enc;
+  CreateFunctionV2 *p;
+  int i;
+  int rc;
+
+  struct EncTable {
+    const char *zEnc;
+    int enc;
+  } aEnc[] = {
+    {"utf8",    SQLITE_UTF8 },
+    {"utf16",   SQLITE_UTF16 },
+    {"utf16le", SQLITE_UTF16LE },
+    {"utf16be", SQLITE_UTF16BE },
+    {"any",     SQLITE_ANY },
+    {"0", 0 }
+  };
+
+  if( objc<5 || (objc%2)==0 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB NAME NARG ENC SWITCHES...");
+    return TCL_ERROR;
+  }
+
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zFunc = Tcl_GetString(objv[2]);
+  if( Tcl_GetIntFromObj(interp, objv[3], &nArg) ) return TCL_ERROR;
+  if( Tcl_GetIndexFromObjStruct(interp, objv[4], aEnc, sizeof(aEnc[0]), 
+          "encoding", 0, &enc)
+  ){
+    return TCL_ERROR;
+  }
+  enc = aEnc[enc].enc;
+
+  p = sqlite3_malloc(sizeof(CreateFunctionV2));
+  assert( p );
+  memset(p, 0, sizeof(CreateFunctionV2));
+  p->interp = interp;
+
+  for(i=5; i<objc; i+=2){
+    int iSwitch;
+    const char *azSwitch[] = {"-func", "-step", "-final", "-destroy", 0};
+    if( Tcl_GetIndexFromObj(interp, objv[i], azSwitch, "switch", 0, &iSwitch) ){
+      sqlite3_free(p);
+      return TCL_ERROR;
+    }
+
+    switch( iSwitch ){
+      case 0: p->pFunc = objv[i+1];      break;
+      case 1: p->pStep = objv[i+1];      break;
+      case 2: p->pFinal = objv[i+1];     break;
+      case 3: p->pDestroy = objv[i+1];   break;
+    }
+  }
+  if( p->pFunc ) p->pFunc = Tcl_DuplicateObj(p->pFunc); 
+  if( p->pStep ) p->pStep = Tcl_DuplicateObj(p->pStep); 
+  if( p->pFinal ) p->pFinal = Tcl_DuplicateObj(p->pFinal); 
+  if( p->pDestroy ) p->pDestroy = Tcl_DuplicateObj(p->pDestroy); 
+
+  if( p->pFunc ) Tcl_IncrRefCount(p->pFunc); 
+  if( p->pStep ) Tcl_IncrRefCount(p->pStep); 
+  if( p->pFinal ) Tcl_IncrRefCount(p->pFinal); 
+  if( p->pDestroy ) Tcl_IncrRefCount(p->pDestroy); 
+
+  rc = sqlite3_create_function_v2(db, zFunc, nArg, enc, (void *)p, 
+      (p->pFunc ? cf2Func : 0),
+      (p->pStep ? cf2Step : 0),
+      (p->pFinal ? cf2Final : 0),
+      cf2Destroy
+  );
+  if( rc!=SQLITE_OK ){
+    Tcl_ResetResult(interp);
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_load_extension DB-HANDLE FILE ?PROC?
+*/
+static int test_load_extension(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  Tcl_CmdInfo cmdInfo;
+  sqlite3 *db;
+  int rc;
+  char *zDb;
+  char *zFile;
+  char *zProc = 0;
+  char *zErr = 0;
+
+  if( objc!=4 && objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB-HANDLE FILE ?PROC?");
+    return TCL_ERROR;
+  }
+  zDb = Tcl_GetString(objv[1]);
+  zFile = Tcl_GetString(objv[2]);
+  if( objc==4 ){
+    zProc = Tcl_GetString(objv[3]);
+  }
+
+  /* Extract the C database handle from the Tcl command name */
+  if( !Tcl_GetCommandInfo(interp, zDb, &cmdInfo) ){
+    Tcl_AppendResult(interp, "command not found: ", zDb, (char*)0);
+    return TCL_ERROR;
+  }
+  db = ((struct SqliteDb*)cmdInfo.objClientData)->db;
+  assert(db);
+
+  /* Call the underlying C function. If an error occurs, set rc to 
+  ** TCL_ERROR and load any error string into the interpreter. If no 
+  ** error occurs, set rc to TCL_OK.
+  */
+#ifdef SQLITE_OMIT_LOAD_EXTENSION
+  rc = SQLITE_ERROR;
+  zErr = sqlite3_mprintf("this build omits sqlite3_load_extension()");
+#else
+  rc = sqlite3_load_extension(db, zFile, zProc, &zErr);
+#endif
+  if( rc!=SQLITE_OK ){
+    Tcl_SetResult(interp, zErr ? zErr : "", TCL_VOLATILE);
+    rc = TCL_ERROR;
+  }else{
+    rc = TCL_OK;
+  }
+  sqlite3_free(zErr);
+
+  return rc;
+}
+
+/*
+** Usage: sqlite3_enable_load_extension DB-HANDLE ONOFF
+*/
+static int test_enable_load(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  Tcl_CmdInfo cmdInfo;
+  sqlite3 *db;
+  char *zDb;
+  int onoff;
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB-HANDLE ONOFF");
+    return TCL_ERROR;
+  }
+  zDb = Tcl_GetString(objv[1]);
+
+  /* Extract the C database handle from the Tcl command name */
+  if( !Tcl_GetCommandInfo(interp, zDb, &cmdInfo) ){
+    Tcl_AppendResult(interp, "command not found: ", zDb, (char*)0);
+    return TCL_ERROR;
+  }
+  db = ((struct SqliteDb*)cmdInfo.objClientData)->db;
+  assert(db);
+
+  /* Get the onoff parameter */
+  if( Tcl_GetBooleanFromObj(interp, objv[2], &onoff) ){
+    return TCL_ERROR;
+  }
+
+#ifdef SQLITE_OMIT_LOAD_EXTENSION
+  Tcl_AppendResult(interp, "this build omits sqlite3_load_extension()");
+  return TCL_ERROR;
+#else
+  sqlite3_enable_load_extension(db, onoff);
+  return TCL_OK;
+#endif
+}
+
+/*
+** Usage:  sqlite_abort
+**
+** Shutdown the process immediately.  This is not a clean shutdown.
+** This command is used to test the recoverability of a database in
+** the event of a program crash.
+*/
+static int sqlite_abort(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+#if defined(_MSC_VER)
+  /* We do this, otherwise the test will halt with a popup message
+   * that we have to click away before the test will continue.
+   */
+  _set_abort_behavior( 0, _CALL_REPORTFAULT );
+#endif
+  exit(255);
+  assert( interp==0 );   /* This will always fail */
+  return TCL_OK;
+}
+
+/*
+** The following routine is a user-defined SQL function whose purpose
+** is to test the sqlite_set_result() API.
+*/
+static void testFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+  while( argc>=2 ){
+    const char *zArg0 = (char*)sqlite3_value_text(argv[0]);
+    if( zArg0 ){
+      if( 0==sqlite3StrICmp(zArg0, "int") ){
+        sqlite3_result_int(context, sqlite3_value_int(argv[1]));
+      }else if( sqlite3StrICmp(zArg0,"int64")==0 ){
+        sqlite3_result_int64(context, sqlite3_value_int64(argv[1]));
+      }else if( sqlite3StrICmp(zArg0,"string")==0 ){
+        sqlite3_result_text(context, (char*)sqlite3_value_text(argv[1]), -1,
+            SQLITE_TRANSIENT);
+      }else if( sqlite3StrICmp(zArg0,"double")==0 ){
+        sqlite3_result_double(context, sqlite3_value_double(argv[1]));
+      }else if( sqlite3StrICmp(zArg0,"null")==0 ){
+        sqlite3_result_null(context);
+      }else if( sqlite3StrICmp(zArg0,"value")==0 ){
+        sqlite3_result_value(context, argv[sqlite3_value_int(argv[1])]);
+      }else{
+        goto error_out;
+      }
+    }else{
+      goto error_out;
+    }
+    argc -= 2;
+    argv += 2;
+  }
+  return;
+
+error_out:
+  sqlite3_result_error(context,"first argument should be one of: "
+      "int int64 string double null value", -1);
+}
+
+/*
+** Usage:   sqlite_register_test_function  DB  NAME
+**
+** Register the test SQL function on the database DB under the name NAME.
+*/
+static int test_register_func(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3 *db;
+  int rc;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+       " DB FUNCTION-NAME", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  rc = sqlite3_create_function(db, argv[2], -1, SQLITE_UTF8, 0, 
+      testFunc, 0, 0);
+  if( rc!=0 ){
+    Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0);
+    return TCL_ERROR;
+  }
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_finalize  STMT 
+**
+** Finalize a statement handle.
+*/
+static int test_finalize(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int rc;
+  sqlite3 *db = 0;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " <STMT>", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+
+  if( pStmt ){
+    db = StmtToDb(pStmt);
+  }
+  rc = sqlite3_finalize(pStmt);
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+  if( db && sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_stmt_status  STMT  CODE  RESETFLAG
+**
+** Get the value of a status counter from a statement.
+*/
+static int test_stmt_status(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int iValue;
+  int i, op, resetFlag;
+  const char *zOpName;
+  sqlite3_stmt *pStmt;
+
+  static const struct {
+    const char *zName;
+    int op;
+  } aOp[] = {
+    { "SQLITE_STMTSTATUS_FULLSCAN_STEP",   SQLITE_STMTSTATUS_FULLSCAN_STEP   },
+    { "SQLITE_STMTSTATUS_SORT",            SQLITE_STMTSTATUS_SORT            },
+    { "SQLITE_STMTSTATUS_AUTOINDEX",       SQLITE_STMTSTATUS_AUTOINDEX       },
+    { "SQLITE_STMTSTATUS_VM_STEP",         SQLITE_STMTSTATUS_VM_STEP         },
+  };
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT PARAMETER RESETFLAG");
+    return TCL_ERROR;
+  }
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  zOpName = Tcl_GetString(objv[2]);
+  for(i=0; i<ArraySize(aOp); i++){
+    if( strcmp(aOp[i].zName, zOpName)==0 ){
+      op = aOp[i].op;
+      break;
+    }
+  }
+  if( i>=ArraySize(aOp) ){
+    if( Tcl_GetIntFromObj(interp, objv[2], &op) ) return TCL_ERROR;
+  }
+  if( Tcl_GetBooleanFromObj(interp, objv[3], &resetFlag) ) return TCL_ERROR;
+  iValue = sqlite3_stmt_status(pStmt, op, resetFlag);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(iValue));
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_next_stmt  DB  STMT
+**
+** Return the next statment in sequence after STMT.
+*/
+static int test_next_stmt(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  sqlite3 *db = 0;
+  char zBuf[50];
+
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " DB STMT", 0);
+    return TCL_ERROR;
+  }
+
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  if( getStmtPointer(interp, Tcl_GetString(objv[2]), &pStmt) ) return TCL_ERROR;
+  pStmt = sqlite3_next_stmt(db, pStmt);
+  if( pStmt ){
+    if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR;
+    Tcl_AppendResult(interp, zBuf, 0);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_stmt_readonly  STMT
+**
+** Return true if STMT is a NULL pointer or a pointer to a statement
+** that is guaranteed to leave the database unmodified.
+*/
+static int test_stmt_readonly(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " STMT", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  rc = sqlite3_stmt_readonly(pStmt);
+  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(rc));
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_stmt_busy  STMT
+**
+** Return true if STMT is a non-NULL pointer to a statement
+** that has been stepped but not to completion.
+*/
+static int test_stmt_busy(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " STMT", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  rc = sqlite3_stmt_busy(pStmt);
+  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(rc));
+  return TCL_OK;
+}
+
+/*
+** Usage:  uses_stmt_journal  STMT
+**
+** Return true if STMT uses a statement journal.
+*/
+static int uses_stmt_journal(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " STMT", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  sqlite3_stmt_readonly(pStmt);
+  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(((Vdbe *)pStmt)->usesStmtJournal));
+  return TCL_OK;
+}
+
+
+/*
+** Usage:  sqlite3_reset  STMT 
+**
+** Reset a statement handle.
+*/
+static int test_reset(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " <STMT>", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+
+  rc = sqlite3_reset(pStmt);
+  if( pStmt && sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ){
+    return TCL_ERROR;
+  }
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+/*
+  if( rc ){
+    return TCL_ERROR;
+  }
+*/
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_expired STMT 
+**
+** Return TRUE if a recompilation of the statement is recommended.
+*/
+static int test_expired(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_DEPRECATED
+  sqlite3_stmt *pStmt;
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " <STMT>", 0);
+    return TCL_ERROR;
+  }
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(sqlite3_expired(pStmt)));
+#endif
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_transfer_bindings FROMSTMT TOSTMT
+**
+** Transfer all bindings from FROMSTMT over to TOSTMT
+*/
+static int test_transfer_bind(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_DEPRECATED
+  sqlite3_stmt *pStmt1, *pStmt2;
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " FROM-STMT TO-STMT", 0);
+    return TCL_ERROR;
+  }
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt1)) return TCL_ERROR;
+  if( getStmtPointer(interp, Tcl_GetString(objv[2]), &pStmt2)) return TCL_ERROR;
+  Tcl_SetObjResult(interp, 
+     Tcl_NewIntObj(sqlite3_transfer_bindings(pStmt1,pStmt2)));
+#endif
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_changes DB
+**
+** Return the number of changes made to the database by the last SQL
+** execution.
+*/
+static int test_changes(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+       Tcl_GetString(objv[0]), " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_changes(db)));
+  return TCL_OK;
+}
+
+/*
+** This is the "static_bind_value" that variables are bound to when
+** the FLAG option of sqlite3_bind is "static"
+*/
+static char *sqlite_static_bind_value = 0;
+static int sqlite_static_bind_nbyte = 0;
+
+/*
+** Usage:  sqlite3_bind  VM  IDX  VALUE  FLAGS
+**
+** Sets the value of the IDX-th occurrence of "?" in the original SQL
+** string.  VALUE is the new value.  If FLAGS=="null" then VALUE is
+** ignored and the value is set to NULL.  If FLAGS=="static" then
+** the value is set to the value of a static variable named
+** "sqlite_static_bind_value".  If FLAGS=="normal" then a copy
+** of the VALUE is made.  If FLAGS=="blob10" then a VALUE is ignored
+** an a 10-byte blob "abc\000xyz\000pq" is inserted.
+*/
+static int test_bind(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  sqlite3_stmt *pStmt;
+  int rc;
+  int idx;
+  if( argc!=5 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+       " VM IDX VALUE (null|static|normal)\"", 0);
+    return TCL_ERROR;
+  }
+  if( getStmtPointer(interp, argv[1], &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[2], &idx) ) return TCL_ERROR;
+  if( strcmp(argv[4],"null")==0 ){
+    rc = sqlite3_bind_null(pStmt, idx);
+  }else if( strcmp(argv[4],"static")==0 ){
+    rc = sqlite3_bind_text(pStmt, idx, sqlite_static_bind_value, -1, 0);
+  }else if( strcmp(argv[4],"static-nbytes")==0 ){
+    rc = sqlite3_bind_text(pStmt, idx, sqlite_static_bind_value,
+                                       sqlite_static_bind_nbyte, 0);
+  }else if( strcmp(argv[4],"normal")==0 ){
+    rc = sqlite3_bind_text(pStmt, idx, argv[3], -1, SQLITE_TRANSIENT);
+  }else if( strcmp(argv[4],"blob10")==0 ){
+    rc = sqlite3_bind_text(pStmt, idx, "abc\000xyz\000pq", 10, SQLITE_STATIC);
+  }else{
+    Tcl_AppendResult(interp, "4th argument should be "
+        "\"null\" or \"static\" or \"normal\"", 0);
+    return TCL_ERROR;
+  }
+  if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
+  if( rc ){
+    char zBuf[50];
+    sprintf(zBuf, "(%d) ", rc);
+    Tcl_AppendResult(interp, zBuf, sqlite3ErrStr(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+#ifndef SQLITE_OMIT_UTF16
+/*
+** Usage: add_test_collate <db ptr> <utf8> <utf16le> <utf16be>
+**
+** This function is used to test that SQLite selects the correct collation
+** sequence callback when multiple versions (for different text encodings)
+** are available.
+**
+** Calling this routine registers the collation sequence "test_collate"
+** with database handle <db>. The second argument must be a list of three
+** boolean values. If the first is true, then a version of test_collate is
+** registered for UTF-8, if the second is true, a version is registered for
+** UTF-16le, if the third is true, a UTF-16be version is available.
+** Previous versions of test_collate are deleted.
+**
+** The collation sequence test_collate is implemented by calling the
+** following TCL script:
+**
+**   "test_collate <enc> <lhs> <rhs>"
+**
+** The <lhs> and <rhs> are the two values being compared, encoded in UTF-8.
+** The <enc> parameter is the encoding of the collation function that
+** SQLite selected to call. The TCL test script implements the
+** "test_collate" proc.
+**
+** Note that this will only work with one intepreter at a time, as the
+** interp pointer to use when evaluating the TCL script is stored in
+** pTestCollateInterp.
+*/
+static Tcl_Interp* pTestCollateInterp;
+static int test_collate_func(
+  void *pCtx, 
+  int nA, const void *zA,
+  int nB, const void *zB
+){
+  Tcl_Interp *i = pTestCollateInterp;
+  int encin = SQLITE_PTR_TO_INT(pCtx);
+  int res;
+  int n;
+
+  sqlite3_value *pVal;
+  Tcl_Obj *pX;
+
+  pX = Tcl_NewStringObj("test_collate", -1);
+  Tcl_IncrRefCount(pX);
+
+  switch( encin ){
+    case SQLITE_UTF8:
+      Tcl_ListObjAppendElement(i,pX,Tcl_NewStringObj("UTF-8",-1));
+      break;
+    case SQLITE_UTF16LE:
+      Tcl_ListObjAppendElement(i,pX,Tcl_NewStringObj("UTF-16LE",-1));
+      break;
+    case SQLITE_UTF16BE:
+      Tcl_ListObjAppendElement(i,pX,Tcl_NewStringObj("UTF-16BE",-1));
+      break;
+    default:
+      assert(0);
+  }
+
+  sqlite3BeginBenignMalloc();
+  pVal = sqlite3ValueNew(0);
+  if( pVal ){
+    sqlite3ValueSetStr(pVal, nA, zA, encin, SQLITE_STATIC);
+    n = sqlite3_value_bytes(pVal);
+    Tcl_ListObjAppendElement(i,pX,
+        Tcl_NewStringObj((char*)sqlite3_value_text(pVal),n));
+    sqlite3ValueSetStr(pVal, nB, zB, encin, SQLITE_STATIC);
+    n = sqlite3_value_bytes(pVal);
+    Tcl_ListObjAppendElement(i,pX,
+        Tcl_NewStringObj((char*)sqlite3_value_text(pVal),n));
+    sqlite3ValueFree(pVal);
+  }
+  sqlite3EndBenignMalloc();
+
+  Tcl_EvalObjEx(i, pX, 0);
+  Tcl_DecrRefCount(pX);
+  Tcl_GetIntFromObj(i, Tcl_GetObjResult(i), &res);
+  return res;
+}
+static int test_collate(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  int val;
+  sqlite3_value *pVal;
+  int rc;
+
+  if( objc!=5 ) goto bad_args;
+  pTestCollateInterp = interp;
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+
+  if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR;
+  rc = sqlite3_create_collation(db, "test_collate", SQLITE_UTF8, 
+          (void *)SQLITE_UTF8, val?test_collate_func:0);
+  if( rc==SQLITE_OK ){
+    const void *zUtf16;
+    if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[3], &val) ) return TCL_ERROR;
+    rc = sqlite3_create_collation(db, "test_collate", SQLITE_UTF16LE, 
+            (void *)SQLITE_UTF16LE, val?test_collate_func:0);
+    if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[4], &val) ) return TCL_ERROR;
+
+#if 0
+    if( sqlite3_iMallocFail>0 ){
+      sqlite3_iMallocFail++;
+    }
+#endif
+    sqlite3_mutex_enter(db->mutex);
+    pVal = sqlite3ValueNew(db);
+    sqlite3ValueSetStr(pVal, -1, "test_collate", SQLITE_UTF8, SQLITE_STATIC);
+    zUtf16 = sqlite3ValueText(pVal, SQLITE_UTF16NATIVE);
+    if( db->mallocFailed ){
+      rc = SQLITE_NOMEM;
+    }else{
+      rc = sqlite3_create_collation16(db, zUtf16, SQLITE_UTF16BE, 
+          (void *)SQLITE_UTF16BE, val?test_collate_func:0);
+    }
+    sqlite3ValueFree(pVal);
+    sqlite3_mutex_leave(db->mutex);
+  }
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+
+bad_args:
+  Tcl_AppendResult(interp, "wrong # args: should be \"",
+      Tcl_GetStringFromObj(objv[0], 0), " <DB> <utf8> <utf16le> <utf16be>", 0);
+  return TCL_ERROR;
+}
+
+/*
+** When the collation needed callback is invoked, record the name of 
+** the requested collating function here.  The recorded name is linked
+** to a TCL variable and used to make sure that the requested collation
+** name is correct.
+*/
+static char zNeededCollation[200];
+static char *pzNeededCollation = zNeededCollation;
+
+
+/*
+** Called when a collating sequence is needed.  Registered using
+** sqlite3_collation_needed16().
+*/
+static void test_collate_needed_cb(
+  void *pCtx, 
+  sqlite3 *db,
+  int eTextRep,
+  const void *pName
+){
+  int enc = ENC(db);
+  int i;
+  char *z;
+  for(z = (char*)pName, i=0; *z || z[1]; z++){
+    if( *z ) zNeededCollation[i++] = *z;
+  }
+  zNeededCollation[i] = 0;
+  sqlite3_create_collation(
+      db, "test_collate", ENC(db), SQLITE_INT_TO_PTR(enc), test_collate_func);
+}
+
+/*
+** Usage: add_test_collate_needed DB
+*/
+static int test_collate_needed(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=2 ) goto bad_args;
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  rc = sqlite3_collation_needed16(db, 0, test_collate_needed_cb);
+  zNeededCollation[0] = 0;
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  return TCL_OK;
+
+bad_args:
+  Tcl_WrongNumArgs(interp, 1, objv, "DB");
+  return TCL_ERROR;
+}
+
+/*
+** tclcmd:   add_alignment_test_collations  DB
+**
+** Add two new collating sequences to the database DB
+**
+**     utf16_aligned
+**     utf16_unaligned
+**
+** Both collating sequences use the same sort order as BINARY.
+** The only difference is that the utf16_aligned collating
+** sequence is declared with the SQLITE_UTF16_ALIGNED flag.
+** Both collating functions increment the unaligned utf16 counter
+** whenever they see a string that begins on an odd byte boundary.
+*/
+static int unaligned_string_counter = 0;
+static int alignmentCollFunc(
+  void *NotUsed,
+  int nKey1, const void *pKey1,
+  int nKey2, const void *pKey2
+){
+  int rc, n;
+  n = nKey1<nKey2 ? nKey1 : nKey2;
+  if( nKey1>0 && 1==(1&(SQLITE_PTR_TO_INT(pKey1))) ) unaligned_string_counter++;
+  if( nKey2>0 && 1==(1&(SQLITE_PTR_TO_INT(pKey2))) ) unaligned_string_counter++;
+  rc = memcmp(pKey1, pKey2, n);
+  if( rc==0 ){
+    rc = nKey1 - nKey2;
+  }
+  return rc;
+}
+static int add_alignment_test_collations(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  if( objc>=2 ){
+    if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+    sqlite3_create_collation(db, "utf16_unaligned", SQLITE_UTF16, 
+        0, alignmentCollFunc);
+    sqlite3_create_collation(db, "utf16_aligned", SQLITE_UTF16_ALIGNED, 
+        0, alignmentCollFunc);
+  }
+  return SQLITE_OK;
+}
+#endif /* !defined(SQLITE_OMIT_UTF16) */
+
+/*
+** Usage: add_test_function <db ptr> <utf8> <utf16le> <utf16be>
+**
+** This function is used to test that SQLite selects the correct user
+** function callback when multiple versions (for different text encodings)
+** are available.
+**
+** Calling this routine registers up to three versions of the user function
+** "test_function" with database handle <db>.  If the second argument is
+** true, then a version of test_function is registered for UTF-8, if the
+** third is true, a version is registered for UTF-16le, if the fourth is
+** true, a UTF-16be version is available.  Previous versions of
+** test_function are deleted.
+**
+** The user function is implemented by calling the following TCL script:
+**
+**   "test_function <enc> <arg>"
+**
+** Where <enc> is one of UTF-8, UTF-16LE or UTF16BE, and <arg> is the
+** single argument passed to the SQL function. The value returned by
+** the TCL script is used as the return value of the SQL function. It
+** is passed to SQLite using UTF-16BE for a UTF-8 test_function(), UTF-8
+** for a UTF-16LE test_function(), and UTF-16LE for an implementation that
+** prefers UTF-16BE.
+*/
+#ifndef SQLITE_OMIT_UTF16
+static void test_function_utf8(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  Tcl_Interp *interp;
+  Tcl_Obj *pX;
+  sqlite3_value *pVal;
+  interp = (Tcl_Interp *)sqlite3_user_data(pCtx);
+  pX = Tcl_NewStringObj("test_function", -1);
+  Tcl_IncrRefCount(pX);
+  Tcl_ListObjAppendElement(interp, pX, Tcl_NewStringObj("UTF-8", -1));
+  Tcl_ListObjAppendElement(interp, pX, 
+      Tcl_NewStringObj((char*)sqlite3_value_text(argv[0]), -1));
+  Tcl_EvalObjEx(interp, pX, 0);
+  Tcl_DecrRefCount(pX);
+  sqlite3_result_text(pCtx, Tcl_GetStringResult(interp), -1, SQLITE_TRANSIENT);
+  pVal = sqlite3ValueNew(0);
+  sqlite3ValueSetStr(pVal, -1, Tcl_GetStringResult(interp), 
+      SQLITE_UTF8, SQLITE_STATIC);
+  sqlite3_result_text16be(pCtx, sqlite3_value_text16be(pVal),
+      -1, SQLITE_TRANSIENT);
+  sqlite3ValueFree(pVal);
+}
+static void test_function_utf16le(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  Tcl_Interp *interp;
+  Tcl_Obj *pX;
+  sqlite3_value *pVal;
+  interp = (Tcl_Interp *)sqlite3_user_data(pCtx);
+  pX = Tcl_NewStringObj("test_function", -1);
+  Tcl_IncrRefCount(pX);
+  Tcl_ListObjAppendElement(interp, pX, Tcl_NewStringObj("UTF-16LE", -1));
+  Tcl_ListObjAppendElement(interp, pX, 
+      Tcl_NewStringObj((char*)sqlite3_value_text(argv[0]), -1));
+  Tcl_EvalObjEx(interp, pX, 0);
+  Tcl_DecrRefCount(pX);
+  pVal = sqlite3ValueNew(0);
+  sqlite3ValueSetStr(pVal, -1, Tcl_GetStringResult(interp), 
+      SQLITE_UTF8, SQLITE_STATIC);
+  sqlite3_result_text(pCtx,(char*)sqlite3_value_text(pVal),-1,SQLITE_TRANSIENT);
+  sqlite3ValueFree(pVal);
+}
+static void test_function_utf16be(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  Tcl_Interp *interp;
+  Tcl_Obj *pX;
+  sqlite3_value *pVal;
+  interp = (Tcl_Interp *)sqlite3_user_data(pCtx);
+  pX = Tcl_NewStringObj("test_function", -1);
+  Tcl_IncrRefCount(pX);
+  Tcl_ListObjAppendElement(interp, pX, Tcl_NewStringObj("UTF-16BE", -1));
+  Tcl_ListObjAppendElement(interp, pX, 
+      Tcl_NewStringObj((char*)sqlite3_value_text(argv[0]), -1));
+  Tcl_EvalObjEx(interp, pX, 0);
+  Tcl_DecrRefCount(pX);
+  pVal = sqlite3ValueNew(0);
+  sqlite3ValueSetStr(pVal, -1, Tcl_GetStringResult(interp), 
+      SQLITE_UTF8, SQLITE_STATIC);
+  sqlite3_result_text16(pCtx, sqlite3_value_text16le(pVal),
+      -1, SQLITE_TRANSIENT);
+  sqlite3_result_text16be(pCtx, sqlite3_value_text16le(pVal),
+      -1, SQLITE_TRANSIENT);
+  sqlite3_result_text16le(pCtx, sqlite3_value_text16le(pVal),
+      -1, SQLITE_TRANSIENT);
+  sqlite3ValueFree(pVal);
+}
+#endif /* SQLITE_OMIT_UTF16 */
+static int test_function(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_UTF16
+  sqlite3 *db;
+  int val;
+
+  if( objc!=5 ) goto bad_args;
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+
+  if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR;
+  if( val ){
+    sqlite3_create_function(db, "test_function", 1, SQLITE_UTF8, 
+        interp, test_function_utf8, 0, 0);
+  }
+  if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[3], &val) ) return TCL_ERROR;
+  if( val ){
+    sqlite3_create_function(db, "test_function", 1, SQLITE_UTF16LE, 
+        interp, test_function_utf16le, 0, 0);
+  }
+  if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[4], &val) ) return TCL_ERROR;
+  if( val ){
+    sqlite3_create_function(db, "test_function", 1, SQLITE_UTF16BE, 
+        interp, test_function_utf16be, 0, 0);
+  }
+
+  return TCL_OK;
+bad_args:
+  Tcl_AppendResult(interp, "wrong # args: should be \"",
+      Tcl_GetStringFromObj(objv[0], 0), " <DB> <utf8> <utf16le> <utf16be>", 0);
+#endif /* SQLITE_OMIT_UTF16 */
+  return TCL_ERROR;
+}
+
+/*
+** Usage:         sqlite3_test_errstr <err code>
+**
+** Test that the english language string equivalents for sqlite error codes
+** are sane. The parameter is an integer representing an sqlite error code.
+** The result is a list of two elements, the string representation of the
+** error code and the english language explanation.
+*/
+static int test_errstr(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  char *zCode;
+  int i;
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "<error code>");
+  }
+
+  zCode = Tcl_GetString(objv[1]);
+  for(i=0; i<200; i++){
+    if( 0==strcmp(t1ErrorName(i), zCode) ) break;
+  }
+  Tcl_SetResult(interp, (char *)sqlite3ErrStr(i), 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:    breakpoint
+**
+** This routine exists for one purpose - to provide a place to put a
+** breakpoint with GDB that can be triggered using TCL code.  The use
+** for this is when a particular test fails on (say) the 1485th iteration.
+** In the TCL test script, we can add code like this:
+**
+**     if {$i==1485} breakpoint
+**
+** Then run testfixture in the debugger and wait for the breakpoint to
+** fire.  Then additional breakpoints can be set to trace down the bug.
+*/
+static int test_breakpoint(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  return TCL_OK;         /* Do nothing */
+}
+
+/*
+** Usage:   sqlite3_bind_zeroblob  STMT IDX N
+**
+** Test the sqlite3_bind_zeroblob interface.  STMT is a prepared statement.
+** IDX is the index of a wildcard in the prepared statement.  This command
+** binds a N-byte zero-filled BLOB to the wildcard.
+*/
+static int test_bind_zeroblob(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int idx;
+  int n;
+  int rc;
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT IDX N");
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[3], &n) ) return TCL_ERROR;
+
+  rc = sqlite3_bind_zeroblob(pStmt, idx, n);
+  if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
+  if( rc!=SQLITE_OK ){
+    return TCL_ERROR;
+  }
+
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_bind_int  STMT N VALUE
+**
+** Test the sqlite3_bind_int interface.  STMT is a prepared statement.
+** N is the index of a wildcard in the prepared statement.  This command
+** binds a 32-bit integer VALUE to that wildcard.
+*/
+static int test_bind_int(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int idx;
+  int value;
+  int rc;
+
+  if( objc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " STMT N VALUE", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[3], &value) ) return TCL_ERROR;
+
+  rc = sqlite3_bind_int(pStmt, idx, value);
+  if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
+  if( rc!=SQLITE_OK ){
+    return TCL_ERROR;
+  }
+
+  return TCL_OK;
+}
+
+
+/*
+** Usage:   sqlite3_bind_int64  STMT N VALUE
+**
+** Test the sqlite3_bind_int64 interface.  STMT is a prepared statement.
+** N is the index of a wildcard in the prepared statement.  This command
+** binds a 64-bit integer VALUE to that wildcard.
+*/
+static int test_bind_int64(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int idx;
+  Tcl_WideInt value;
+  int rc;
+
+  if( objc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " STMT N VALUE", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
+  if( Tcl_GetWideIntFromObj(interp, objv[3], &value) ) return TCL_ERROR;
+
+  rc = sqlite3_bind_int64(pStmt, idx, value);
+  if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
+  if( rc!=SQLITE_OK ){
+    return TCL_ERROR;
+  }
+
+  return TCL_OK;
+}
+
+
+/*
+** Usage:   sqlite3_bind_double  STMT N VALUE
+**
+** Test the sqlite3_bind_double interface.  STMT is a prepared statement.
+** N is the index of a wildcard in the prepared statement.  This command
+** binds a 64-bit integer VALUE to that wildcard.
+*/
+static int test_bind_double(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int idx;
+  double value;
+  int rc;
+  const char *zVal;
+  int i;
+  static const struct {
+    const char *zName;     /* Name of the special floating point value */
+    unsigned int iUpper;   /* Upper 32 bits */
+    unsigned int iLower;   /* Lower 32 bits */
+  } aSpecialFp[] = {
+    {  "NaN",      0x7fffffff, 0xffffffff },
+    {  "SNaN",     0x7ff7ffff, 0xffffffff },
+    {  "-NaN",     0xffffffff, 0xffffffff },
+    {  "-SNaN",    0xfff7ffff, 0xffffffff },
+    {  "+Inf",     0x7ff00000, 0x00000000 },
+    {  "-Inf",     0xfff00000, 0x00000000 },
+    {  "Epsilon",  0x00000000, 0x00000001 },
+    {  "-Epsilon", 0x80000000, 0x00000001 },
+    {  "NaN0",     0x7ff80000, 0x00000000 },
+    {  "-NaN0",    0xfff80000, 0x00000000 },
+  };
+
+  if( objc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " STMT N VALUE", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
+
+  /* Intercept the string "NaN" and generate a NaN value for it.
+  ** All other strings are passed through to Tcl_GetDoubleFromObj().
+  ** Tcl_GetDoubleFromObj() should understand "NaN" but some versions
+  ** contain a bug.
+  */
+  zVal = Tcl_GetString(objv[3]);
+  for(i=0; i<sizeof(aSpecialFp)/sizeof(aSpecialFp[0]); i++){
+    if( strcmp(aSpecialFp[i].zName, zVal)==0 ){
+      sqlite3_uint64 x;
+      x = aSpecialFp[i].iUpper;
+      x <<= 32;
+      x |= aSpecialFp[i].iLower;
+      assert( sizeof(value)==8 );
+      assert( sizeof(x)==8 );
+      memcpy(&value, &x, 8);
+      break;
+    }
+  }
+  if( i>=sizeof(aSpecialFp)/sizeof(aSpecialFp[0]) &&
+         Tcl_GetDoubleFromObj(interp, objv[3], &value) ){
+    return TCL_ERROR;
+  }
+  rc = sqlite3_bind_double(pStmt, idx, value);
+  if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
+  if( rc!=SQLITE_OK ){
+    return TCL_ERROR;
+  }
+
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_bind_null  STMT N
+**
+** Test the sqlite3_bind_null interface.  STMT is a prepared statement.
+** N is the index of a wildcard in the prepared statement.  This command
+** binds a NULL to the wildcard.
+*/
+static int test_bind_null(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int idx;
+  int rc;
+
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " STMT N", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
+
+  rc = sqlite3_bind_null(pStmt, idx);
+  if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
+  if( rc!=SQLITE_OK ){
+    return TCL_ERROR;
+  }
+
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_bind_text  STMT N STRING BYTES
+**
+** Test the sqlite3_bind_text interface.  STMT is a prepared statement.
+** N is the index of a wildcard in the prepared statement.  This command
+** binds a UTF-8 string STRING to the wildcard.  The string is BYTES bytes
+** long.
+*/
+static int test_bind_text(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int idx;
+  int bytes;
+  char *value;
+  int rc;
+
+  if( objc!=5 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " STMT N VALUE BYTES", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
+  value = (char*)Tcl_GetByteArrayFromObj(objv[3], &bytes);
+  if( Tcl_GetIntFromObj(interp, objv[4], &bytes) ) return TCL_ERROR;
+
+  rc = sqlite3_bind_text(pStmt, idx, value, bytes, SQLITE_TRANSIENT);
+  if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_bind_text16 ?-static? STMT N STRING BYTES
+**
+** Test the sqlite3_bind_text16 interface.  STMT is a prepared statement.
+** N is the index of a wildcard in the prepared statement.  This command
+** binds a UTF-16 string STRING to the wildcard.  The string is BYTES bytes
+** long.
+*/
+static int test_bind_text16(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_UTF16
+  sqlite3_stmt *pStmt;
+  int idx;
+  int bytes;
+  char *value;
+  int rc;
+
+  void (*xDel)(void*) = (objc==6?SQLITE_STATIC:SQLITE_TRANSIENT);
+  Tcl_Obj *oStmt    = objv[objc-4];
+  Tcl_Obj *oN       = objv[objc-3];
+  Tcl_Obj *oString  = objv[objc-2];
+  Tcl_Obj *oBytes   = objv[objc-1];
+
+  if( objc!=5 && objc!=6){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " STMT N VALUE BYTES", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(oStmt), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, oN, &idx) ) return TCL_ERROR;
+  value = (char*)Tcl_GetByteArrayFromObj(oString, 0);
+  if( Tcl_GetIntFromObj(interp, oBytes, &bytes) ) return TCL_ERROR;
+
+  rc = sqlite3_bind_text16(pStmt, idx, (void *)value, bytes, xDel);
+  if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+
+#endif /* SQLITE_OMIT_UTF16 */
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_bind_blob ?-static? STMT N DATA BYTES
+**
+** Test the sqlite3_bind_blob interface.  STMT is a prepared statement.
+** N is the index of a wildcard in the prepared statement.  This command
+** binds a BLOB to the wildcard.  The BLOB is BYTES bytes in size.
+*/
+static int test_bind_blob(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int idx;
+  int bytes;
+  char *value;
+  int rc;
+  sqlite3_destructor_type xDestructor = SQLITE_TRANSIENT;
+
+  if( objc!=5 && objc!=6 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " STMT N DATA BYTES", 0);
+    return TCL_ERROR;
+  }
+
+  if( objc==6 ){
+    xDestructor = SQLITE_STATIC;
+    objv++;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
+  value = Tcl_GetString(objv[3]);
+  if( Tcl_GetIntFromObj(interp, objv[4], &bytes) ) return TCL_ERROR;
+
+  rc = sqlite3_bind_blob(pStmt, idx, value, bytes, xDestructor);
+  if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
+  if( rc!=SQLITE_OK ){
+    return TCL_ERROR;
+  }
+
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_bind_parameter_count  STMT
+**
+** Return the number of wildcards in the given statement.
+*/
+static int test_bind_parameter_count(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+    return TCL_ERROR;
+  }
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_bind_parameter_count(pStmt)));
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_bind_parameter_name  STMT  N
+**
+** Return the name of the Nth wildcard.  The first wildcard is 1.
+** An empty string is returned if N is out of range or if the wildcard
+** is nameless.
+*/
+static int test_bind_parameter_name(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int i;
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT N");
+    return TCL_ERROR;
+  }
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &i) ) return TCL_ERROR;
+  Tcl_SetObjResult(interp, 
+     Tcl_NewStringObj(sqlite3_bind_parameter_name(pStmt,i),-1)
+  );
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_bind_parameter_index  STMT  NAME
+**
+** Return the index of the wildcard called NAME.  Return 0 if there is
+** no such wildcard.
+*/
+static int test_bind_parameter_index(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT NAME");
+    return TCL_ERROR;
+  }
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  Tcl_SetObjResult(interp, 
+     Tcl_NewIntObj(
+       sqlite3_bind_parameter_index(pStmt,Tcl_GetString(objv[2]))
+     )
+  );
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_clear_bindings STMT
+**
+*/
+static int test_clear_bindings(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+    return TCL_ERROR;
+  }
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_clear_bindings(pStmt)));
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_sleep MILLISECONDS
+*/
+static int test_sleep(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int ms;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "MILLISECONDS");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[1], &ms) ){
+    return TCL_ERROR;
+  }
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_sleep(ms)));
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_extended_errcode DB
+**
+** Return the string representation of the most recent sqlite3_* API
+** error code. e.g. "SQLITE_ERROR".
+*/
+static int test_ex_errcode(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  rc = sqlite3_extended_errcode(db);
+  Tcl_AppendResult(interp, (char *)t1ErrorName(rc), 0);
+  return TCL_OK;
+}
+
+
+/*
+** Usage: sqlite3_errcode DB
+**
+** Return the string representation of the most recent sqlite3_* API
+** error code. e.g. "SQLITE_ERROR".
+*/
+static int test_errcode(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  rc = sqlite3_errcode(db);
+  Tcl_AppendResult(interp, (char *)t1ErrorName(rc), 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_errmsg DB
+**
+** Returns the UTF-8 representation of the error message string for the
+** most recent sqlite3_* API call.
+*/
+static int test_errmsg(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  const char *zErr;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+
+  zErr = sqlite3_errmsg(db);
+  Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
+  return TCL_OK;
+}
+
+/*
+** Usage:   test_errmsg16 DB
+**
+** Returns the UTF-16 representation of the error message string for the
+** most recent sqlite3_* API call. This is a byte array object at the TCL 
+** level, and it includes the 0x00 0x00 terminator bytes at the end of the
+** UTF-16 string.
+*/
+static int test_errmsg16(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_UTF16
+  sqlite3 *db;
+  const void *zErr;
+  const char *z;
+  int bytes = 0;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+
+  zErr = sqlite3_errmsg16(db);
+  if( zErr ){
+    z = zErr;
+    for(bytes=0; z[bytes] || z[bytes+1]; bytes+=2){}
+  }
+  Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(zErr, bytes));
+#endif /* SQLITE_OMIT_UTF16 */
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_prepare DB sql bytes ?tailvar?
+**
+** Compile up to <bytes> bytes of the supplied SQL string <sql> using
+** database handle <DB>. The parameter <tailval> is the name of a global
+** variable that is set to the unused portion of <sql> (if any). A
+** STMT handle is returned.
+*/
+static int test_prepare(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  const char *zSql;
+  int bytes;
+  const char *zTail = 0;
+  sqlite3_stmt *pStmt = 0;
+  char zBuf[50];
+  int rc;
+
+  if( objc!=5 && objc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " DB sql bytes ?tailvar?", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zSql = Tcl_GetString(objv[2]);
+  if( Tcl_GetIntFromObj(interp, objv[3], &bytes) ) return TCL_ERROR;
+
+  rc = sqlite3_prepare(db, zSql, bytes, &pStmt, objc>=5 ? &zTail : 0);
+  Tcl_ResetResult(interp);
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  if( zTail && objc>=5 ){
+    if( bytes>=0 ){
+      bytes = bytes - (int)(zTail-zSql);
+    }
+    if( (int)strlen(zTail)<bytes ){
+      bytes = (int)strlen(zTail);
+    }
+    Tcl_ObjSetVar2(interp, objv[4], 0, Tcl_NewStringObj(zTail, bytes), 0);
+  }
+  if( rc!=SQLITE_OK ){
+    assert( pStmt==0 );
+    sprintf(zBuf, "(%d) ", rc);
+    Tcl_AppendResult(interp, zBuf, sqlite3_errmsg(db), 0);
+    return TCL_ERROR;
+  }
+
+  if( pStmt ){
+    if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR;
+    Tcl_AppendResult(interp, zBuf, 0);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_prepare_v2 DB sql bytes ?tailvar?
+**
+** Compile up to <bytes> bytes of the supplied SQL string <sql> using
+** database handle <DB>. The parameter <tailval> is the name of a global
+** variable that is set to the unused portion of <sql> (if any). A
+** STMT handle is returned.
+*/
+static int test_prepare_v2(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  const char *zSql;
+  int bytes;
+  const char *zTail = 0;
+  sqlite3_stmt *pStmt = 0;
+  char zBuf[50];
+  int rc;
+
+  if( objc!=5 && objc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " DB sql bytes tailvar", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zSql = Tcl_GetString(objv[2]);
+  if( Tcl_GetIntFromObj(interp, objv[3], &bytes) ) return TCL_ERROR;
+
+  rc = sqlite3_prepare_v2(db, zSql, bytes, &pStmt, objc>=5 ? &zTail : 0);
+  assert(rc==SQLITE_OK || pStmt==0);
+  Tcl_ResetResult(interp);
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  if( zTail && objc>=5 ){
+    if( bytes>=0 ){
+      bytes = bytes - (int)(zTail-zSql);
+    }
+    Tcl_ObjSetVar2(interp, objv[4], 0, Tcl_NewStringObj(zTail, bytes), 0);
+  }
+  if( rc!=SQLITE_OK ){
+    assert( pStmt==0 );
+    sprintf(zBuf, "(%d) ", rc);
+    Tcl_AppendResult(interp, zBuf, sqlite3_errmsg(db), 0);
+    return TCL_ERROR;
+  }
+
+  if( pStmt ){
+    if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR;
+    Tcl_AppendResult(interp, zBuf, 0);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_prepare_tkt3134 DB
+**
+** Generate a prepared statement for a zero-byte string as a test
+** for ticket #3134.  The string should be preceeded by a zero byte.
+*/
+static int test_prepare_tkt3134(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  static const char zSql[] = "\000SELECT 1";
+  sqlite3_stmt *pStmt = 0;
+  char zBuf[50];
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " DB sql bytes tailvar", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  rc = sqlite3_prepare_v2(db, &zSql[1], 0, &pStmt, 0);
+  assert(rc==SQLITE_OK || pStmt==0);
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  if( rc!=SQLITE_OK ){
+    assert( pStmt==0 );
+    sprintf(zBuf, "(%d) ", rc);
+    Tcl_AppendResult(interp, zBuf, sqlite3_errmsg(db), 0);
+    return TCL_ERROR;
+  }
+
+  if( pStmt ){
+    if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR;
+    Tcl_AppendResult(interp, zBuf, 0);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_prepare16 DB sql bytes tailvar
+**
+** Compile up to <bytes> bytes of the supplied SQL string <sql> using
+** database handle <DB>. The parameter <tailval> is the name of a global
+** variable that is set to the unused portion of <sql> (if any). A
+** STMT handle is returned.
+*/
+static int test_prepare16(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_UTF16
+  sqlite3 *db;
+  const void *zSql;
+  const void *zTail = 0;
+  Tcl_Obj *pTail = 0;
+  sqlite3_stmt *pStmt = 0;
+  char zBuf[50]; 
+  int rc;
+  int bytes;                /* The integer specified as arg 3 */
+  int objlen;               /* The byte-array length of arg 2 */
+
+  if( objc!=5 && objc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " DB sql bytes ?tailvar?", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zSql = Tcl_GetByteArrayFromObj(objv[2], &objlen);
+  if( Tcl_GetIntFromObj(interp, objv[3], &bytes) ) return TCL_ERROR;
+
+  rc = sqlite3_prepare16(db, zSql, bytes, &pStmt, objc>=5 ? &zTail : 0);
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  if( rc ){
+    return TCL_ERROR;
+  }
+
+  if( objc>=5 ){
+    if( zTail ){
+      objlen = objlen - (int)((u8 *)zTail-(u8 *)zSql);
+    }else{
+      objlen = 0;
+    }
+    pTail = Tcl_NewByteArrayObj((u8 *)zTail, objlen);
+    Tcl_IncrRefCount(pTail);
+    Tcl_ObjSetVar2(interp, objv[4], 0, pTail, 0);
+    Tcl_DecrRefCount(pTail);
+  }
+
+  if( pStmt ){
+    if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR;
+  }
+  Tcl_AppendResult(interp, zBuf, 0);
+#endif /* SQLITE_OMIT_UTF16 */
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_prepare16_v2 DB sql bytes ?tailvar?
+**
+** Compile up to <bytes> bytes of the supplied SQL string <sql> using
+** database handle <DB>. The parameter <tailval> is the name of a global
+** variable that is set to the unused portion of <sql> (if any). A
+** STMT handle is returned.
+*/
+static int test_prepare16_v2(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_UTF16
+  sqlite3 *db;
+  const void *zSql;
+  const void *zTail = 0;
+  Tcl_Obj *pTail = 0;
+  sqlite3_stmt *pStmt = 0;
+  char zBuf[50]; 
+  int rc;
+  int bytes;                /* The integer specified as arg 3 */
+  int objlen;               /* The byte-array length of arg 2 */
+
+  if( objc!=5 && objc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " DB sql bytes ?tailvar?", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zSql = Tcl_GetByteArrayFromObj(objv[2], &objlen);
+  if( Tcl_GetIntFromObj(interp, objv[3], &bytes) ) return TCL_ERROR;
+
+  rc = sqlite3_prepare16_v2(db, zSql, bytes, &pStmt, objc>=5 ? &zTail : 0);
+  if( sqlite3TestErrCode(interp, db, rc) ) return TCL_ERROR;
+  if( rc ){
+    return TCL_ERROR;
+  }
+
+  if( objc>=5 ){
+    if( zTail ){
+      objlen = objlen - (int)((u8 *)zTail-(u8 *)zSql);
+    }else{
+      objlen = 0;
+    }
+    pTail = Tcl_NewByteArrayObj((u8 *)zTail, objlen);
+    Tcl_IncrRefCount(pTail);
+    Tcl_ObjSetVar2(interp, objv[4], 0, pTail, 0);
+    Tcl_DecrRefCount(pTail);
+  }
+
+  if( pStmt ){
+    if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR;
+  }
+  Tcl_AppendResult(interp, zBuf, 0);
+#endif /* SQLITE_OMIT_UTF16 */
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_open filename ?options-list?
+*/
+static int test_open(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zFilename;
+  sqlite3 *db;
+  char zBuf[100];
+
+  if( objc!=3 && objc!=2 && objc!=1 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " filename options-list", 0);
+    return TCL_ERROR;
+  }
+
+  zFilename = objc>1 ? Tcl_GetString(objv[1]) : 0;
+  sqlite3_open(zFilename, &db);
+  
+  if( sqlite3TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR;
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_open_v2 FILENAME FLAGS VFS
+*/
+static int test_open_v2(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zFilename;
+  const char *zVfs;
+  int flags = 0;
+  sqlite3 *db;
+  int rc;
+  char zBuf[100];
+
+  int nFlag;
+  Tcl_Obj **apFlag;
+  int i;
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME FLAGS VFS");
+    return TCL_ERROR;
+  }
+  zFilename = Tcl_GetString(objv[1]);
+  zVfs = Tcl_GetString(objv[3]);
+  if( zVfs[0]==0x00 ) zVfs = 0;
+
+  rc = Tcl_ListObjGetElements(interp, objv[2], &nFlag, &apFlag);
+  if( rc!=TCL_OK ) return rc;
+  for(i=0; i<nFlag; i++){
+    int iFlag;
+    struct OpenFlag {
+      const char *zFlag;
+      int flag;
+    } aFlag[] = {
+      { "SQLITE_OPEN_READONLY", SQLITE_OPEN_READONLY },
+      { "SQLITE_OPEN_READWRITE", SQLITE_OPEN_READWRITE },
+      { "SQLITE_OPEN_CREATE", SQLITE_OPEN_CREATE },
+      { "SQLITE_OPEN_DELETEONCLOSE", SQLITE_OPEN_DELETEONCLOSE },
+      { "SQLITE_OPEN_EXCLUSIVE", SQLITE_OPEN_EXCLUSIVE },
+      { "SQLITE_OPEN_AUTOPROXY", SQLITE_OPEN_AUTOPROXY },
+      { "SQLITE_OPEN_MAIN_DB", SQLITE_OPEN_MAIN_DB },
+      { "SQLITE_OPEN_TEMP_DB", SQLITE_OPEN_TEMP_DB },
+      { "SQLITE_OPEN_TRANSIENT_DB", SQLITE_OPEN_TRANSIENT_DB },
+      { "SQLITE_OPEN_MAIN_JOURNAL", SQLITE_OPEN_MAIN_JOURNAL },
+      { "SQLITE_OPEN_TEMP_JOURNAL", SQLITE_OPEN_TEMP_JOURNAL },
+      { "SQLITE_OPEN_SUBJOURNAL", SQLITE_OPEN_SUBJOURNAL },
+      { "SQLITE_OPEN_MASTER_JOURNAL", SQLITE_OPEN_MASTER_JOURNAL },
+      { "SQLITE_OPEN_NOMUTEX", SQLITE_OPEN_NOMUTEX },
+      { "SQLITE_OPEN_FULLMUTEX", SQLITE_OPEN_FULLMUTEX },
+      { "SQLITE_OPEN_SHAREDCACHE", SQLITE_OPEN_SHAREDCACHE },
+      { "SQLITE_OPEN_PRIVATECACHE", SQLITE_OPEN_PRIVATECACHE },
+      { "SQLITE_OPEN_WAL", SQLITE_OPEN_WAL },
+      { "SQLITE_OPEN_URI", SQLITE_OPEN_URI },
+      { 0, 0 }
+    };
+    rc = Tcl_GetIndexFromObjStruct(interp, apFlag[i], aFlag, sizeof(aFlag[0]), 
+        "flag", 0, &iFlag
+    );
+    if( rc!=TCL_OK ) return rc;
+    flags |= aFlag[iFlag].flag;
+  }
+
+  rc = sqlite3_open_v2(zFilename, &db, flags, zVfs);
+  if( sqlite3TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR;
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_open16 filename options
+*/
+static int test_open16(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_UTF16
+  const void *zFilename;
+  sqlite3 *db;
+  char zBuf[100];
+
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " filename options-list", 0);
+    return TCL_ERROR;
+  }
+
+  zFilename = Tcl_GetByteArrayFromObj(objv[1], 0);
+  sqlite3_open16(zFilename, &db);
+  
+  if( sqlite3TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR;
+  Tcl_AppendResult(interp, zBuf, 0);
+#endif /* SQLITE_OMIT_UTF16 */
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_complete16 <UTF-16 string>
+**
+** Return 1 if the supplied argument is a complete SQL statement, or zero
+** otherwise.
+*/
+static int test_complete16(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#if !defined(SQLITE_OMIT_COMPLETE) && !defined(SQLITE_OMIT_UTF16)
+  char *zBuf;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "<utf-16 sql>");
+    return TCL_ERROR;
+  }
+
+  zBuf = (char*)Tcl_GetByteArrayFromObj(objv[1], 0);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_complete16(zBuf)));
+#endif /* SQLITE_OMIT_COMPLETE && SQLITE_OMIT_UTF16 */
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_step STMT
+**
+** Advance the statement to the next row.
+*/
+static int test_step(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " STMT", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  rc = sqlite3_step(pStmt);
+
+  /* if( rc!=SQLITE_DONE && rc!=SQLITE_ROW ) return TCL_ERROR; */
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), 0);
+  return TCL_OK;
+}
+
+static int test_sql(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  Tcl_SetResult(interp, (char *)sqlite3_sql(pStmt), TCL_VOLATILE);
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_column_count STMT 
+**
+** Return the number of columns returned by the sql statement STMT.
+*/
+static int test_column_count(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " STMT column", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_column_count(pStmt)));
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_column_type STMT column
+**
+** Return the type of the data in column 'column' of the current row.
+*/
+static int test_column_type(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int col;
+  int tp;
+
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " STMT column", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;
+
+  tp = sqlite3_column_type(pStmt, col);
+  switch( tp ){
+    case SQLITE_INTEGER: 
+      Tcl_SetResult(interp, "INTEGER", TCL_STATIC); 
+      break;
+    case SQLITE_NULL:
+      Tcl_SetResult(interp, "NULL", TCL_STATIC); 
+      break;
+    case SQLITE_FLOAT:
+      Tcl_SetResult(interp, "FLOAT", TCL_STATIC); 
+      break;
+    case SQLITE_TEXT:
+      Tcl_SetResult(interp, "TEXT", TCL_STATIC); 
+      break;
+    case SQLITE_BLOB:
+      Tcl_SetResult(interp, "BLOB", TCL_STATIC); 
+      break;
+    default:
+      assert(0);
+  }
+
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_column_int64 STMT column
+**
+** Return the data in column 'column' of the current row cast as an
+** wide (64-bit) integer.
+*/
+static int test_column_int64(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int col;
+  i64 iVal;
+
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " STMT column", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;
+
+  iVal = sqlite3_column_int64(pStmt, col);
+  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(iVal));
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_column_blob STMT column
+*/
+static int test_column_blob(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int col;
+
+  int len;
+  const void *pBlob;
+
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " STMT column", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;
+
+  len = sqlite3_column_bytes(pStmt, col);
+  pBlob = sqlite3_column_blob(pStmt, col);
+  Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pBlob, len));
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_column_double STMT column
+**
+** Return the data in column 'column' of the current row cast as a double.
+*/
+static int test_column_double(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int col;
+  double rVal;
+
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " STMT column", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;
+
+  rVal = sqlite3_column_double(pStmt, col);
+  Tcl_SetObjResult(interp, Tcl_NewDoubleObj(rVal));
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_data_count STMT 
+**
+** Return the number of columns returned by the sql statement STMT.
+*/
+static int test_data_count(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " STMT column", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_data_count(pStmt)));
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_column_text STMT column
+**
+** Usage: sqlite3_column_decltype STMT column
+**
+** Usage: sqlite3_column_name STMT column
+*/
+static int test_stmt_utf8(
+  void * clientData,        /* Pointer to SQLite API function to be invoke */
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int col;
+  const char *(*xFunc)(sqlite3_stmt*, int);
+  const char *zRet;
+
+  xFunc = (const char *(*)(sqlite3_stmt*, int))clientData;
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " STMT column", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;
+  zRet = xFunc(pStmt, col);
+  if( zRet ){
+    Tcl_SetResult(interp, (char *)zRet, 0);
+  }
+  return TCL_OK;
+}
+
+static int test_global_recover(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_DEPRECATED
+  int rc;
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+  rc = sqlite3_global_recover();
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+#endif
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_column_text STMT column
+**
+** Usage: sqlite3_column_decltype STMT column
+**
+** Usage: sqlite3_column_name STMT column
+*/
+static int test_stmt_utf16(
+  void * clientData,     /* Pointer to SQLite API function to be invoked */
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_UTF16
+  sqlite3_stmt *pStmt;
+  int col;
+  Tcl_Obj *pRet;
+  const void *zName16;
+  const void *(*xFunc)(sqlite3_stmt*, int);
+
+  xFunc = (const void *(*)(sqlite3_stmt*, int))clientData;
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " STMT column", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;
+
+  zName16 = xFunc(pStmt, col);
+  if( zName16 ){
+    int n;
+    const char *z = zName16;
+    for(n=0; z[n] || z[n+1]; n+=2){}
+    pRet = Tcl_NewByteArrayObj(zName16, n+2);
+    Tcl_SetObjResult(interp, pRet);
+  }
+#endif /* SQLITE_OMIT_UTF16 */
+
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_column_int STMT column
+**
+** Usage: sqlite3_column_bytes STMT column
+**
+** Usage: sqlite3_column_bytes16 STMT column
+**
+*/
+static int test_stmt_int(
+  void * clientData,    /* Pointer to SQLite API function to be invoked */
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+  int col;
+  int (*xFunc)(sqlite3_stmt*, int);
+
+  xFunc = (int (*)(sqlite3_stmt*, int))clientData;
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " STMT column", 0);
+    return TCL_ERROR;
+  }
+
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;
+
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(xFunc(pStmt, col)));
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite_set_magic  DB  MAGIC-NUMBER
+**
+** Set the db->magic value.  This is used to test error recovery logic.
+*/
+static int sqlite_set_magic(
+  void * clientData,
+  Tcl_Interp *interp,
+  int argc,
+  char **argv
+){
+  sqlite3 *db;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+         " DB MAGIC", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  if( strcmp(argv[2], "SQLITE_MAGIC_OPEN")==0 ){
+    db->magic = SQLITE_MAGIC_OPEN;
+  }else if( strcmp(argv[2], "SQLITE_MAGIC_CLOSED")==0 ){
+    db->magic = SQLITE_MAGIC_CLOSED;
+  }else if( strcmp(argv[2], "SQLITE_MAGIC_BUSY")==0 ){
+    db->magic = SQLITE_MAGIC_BUSY;
+  }else if( strcmp(argv[2], "SQLITE_MAGIC_ERROR")==0 ){
+    db->magic = SQLITE_MAGIC_ERROR;
+  }else if( Tcl_GetInt(interp, argv[2], (int*)&db->magic) ){
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_interrupt  DB 
+**
+** Trigger an interrupt on DB
+*/
+static int test_interrupt(
+  void * clientData,
+  Tcl_Interp *interp,
+  int argc,
+  char **argv
+){
+  sqlite3 *db;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  sqlite3_interrupt(db);
+  return TCL_OK;
+}
+
+static u8 *sqlite3_stack_baseline = 0;
+
+/*
+** Fill the stack with a known bitpattern.
+*/
+static void prepStack(void){
+  int i;
+  u32 bigBuf[65536];
+  for(i=0; i<sizeof(bigBuf)/sizeof(bigBuf[0]); i++) bigBuf[i] = 0xdeadbeef;
+  sqlite3_stack_baseline = (u8*)&bigBuf[65536];
+}
+
+/*
+** Get the current stack depth.  Used for debugging only.
+*/
+u64 sqlite3StackDepth(void){
+  u8 x;
+  return (u64)(sqlite3_stack_baseline - &x);
+}
+
+/*
+** Usage:  sqlite3_stack_used DB SQL
+**
+** Try to measure the amount of stack space used by a call to sqlite3_exec
+*/
+static int test_stack_used(
+  void * clientData,
+  Tcl_Interp *interp,
+  int argc,
+  char **argv
+){
+  sqlite3 *db;
+  int i;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+        " DB SQL", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  prepStack();
+  (void)sqlite3_exec(db, argv[2], 0, 0, 0);
+  for(i=65535; i>=0 && ((u32*)sqlite3_stack_baseline)[-i]==0xdeadbeef; i--){}
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(i*4));
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite_delete_function DB function-name
+**
+** Delete the user function 'function-name' from database handle DB. It
+** is assumed that the user function was created as UTF8, any number of
+** arguments (the way the TCL interface does it).
+*/
+static int delete_function(
+  void * clientData,
+  Tcl_Interp *interp,
+  int argc,
+  char **argv
+){
+  int rc;
+  sqlite3 *db;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+        " DB function-name", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  rc = sqlite3_create_function(db, argv[2], -1, SQLITE_UTF8, 0, 0, 0, 0);
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite_delete_collation DB collation-name
+**
+** Delete the collation sequence 'collation-name' from database handle 
+** DB. It is assumed that the collation sequence was created as UTF8 (the 
+** way the TCL interface does it).
+*/
+static int delete_collation(
+  void * clientData,
+  Tcl_Interp *interp,
+  int argc,
+  char **argv
+){
+  int rc;
+  sqlite3 *db;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+        " DB function-name", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  rc = sqlite3_create_collation(db, argv[2], SQLITE_UTF8, 0, 0);
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_get_autocommit DB
+**
+** Return true if the database DB is currently in auto-commit mode.
+** Return false if not.
+*/
+static int get_autocommit(
+  void * clientData,
+  Tcl_Interp *interp,
+  int argc,
+  char **argv
+){
+  char zBuf[30];
+  sqlite3 *db;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+        " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  sprintf(zBuf, "%d", sqlite3_get_autocommit(db));
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_busy_timeout DB MS
+**
+** Set the busy timeout.  This is more easily done using the timeout
+** method of the TCL interface.  But we need a way to test the case
+** where it returns SQLITE_MISUSE.
+*/
+static int test_busy_timeout(
+  void * clientData,
+  Tcl_Interp *interp,
+  int argc,
+  char **argv
+){
+  int rc, ms;
+  sqlite3 *db;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
+        " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[2], &ms) ) return TCL_ERROR;
+  rc = sqlite3_busy_timeout(db, ms);
+  Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:  tcl_variable_type VARIABLENAME
+**
+** Return the name of the internal representation for the
+** value of the given variable.
+*/
+static int tcl_variable_type(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  Tcl_Obj *pVar;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "VARIABLE");
+    return TCL_ERROR;
+  }
+  pVar = Tcl_GetVar2Ex(interp, Tcl_GetString(objv[1]), 0, TCL_LEAVE_ERR_MSG);
+  if( pVar==0 ) return TCL_ERROR;
+  if( pVar->typePtr ){
+    Tcl_SetObjResult(interp, Tcl_NewStringObj(pVar->typePtr->name, -1));
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_release_memory ?N?
+**
+** Attempt to release memory currently held but not actually required.
+** The integer N is the number of bytes we are trying to release.  The 
+** return value is the amount of memory actually released.
+*/
+static int test_release_memory(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#if defined(SQLITE_ENABLE_MEMORY_MANAGEMENT) && !defined(SQLITE_OMIT_DISKIO)
+  int N;
+  int amt;
+  if( objc!=1 && objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "?N?");
+    return TCL_ERROR;
+  }
+  if( objc==2 ){
+    if( Tcl_GetIntFromObj(interp, objv[1], &N) ) return TCL_ERROR;
+  }else{
+    N = -1;
+  }
+  amt = sqlite3_release_memory(N);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(amt));
+#endif
+  return TCL_OK;
+}
+
+
+/*
+** Usage:  sqlite3_db_release_memory DB
+**
+** Attempt to release memory currently held by database DB.  Return the
+** result code (which in the current implementation is always zero).
+*/
+static int test_db_release_memory(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  int rc;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  rc = sqlite3_db_release_memory(db);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_db_filename DB DBNAME
+**
+** Return the name of a file associated with a database.
+*/
+static int test_db_filename(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  const char *zDbName;
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zDbName = Tcl_GetString(objv[2]);
+  Tcl_AppendResult(interp, sqlite3_db_filename(db, zDbName), (void*)0);
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_db_readonly DB DBNAME
+**
+** Return 1 or 0 if DBNAME is readonly or not.  Return -1 if DBNAME does
+** not exist.
+*/
+static int test_db_readonly(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  const char *zDbName;
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zDbName = Tcl_GetString(objv[2]);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_db_readonly(db, zDbName)));
+  return TCL_OK;
+}
+
+/*
+** Usage:  sqlite3_soft_heap_limit ?N?
+**
+** Query or set the soft heap limit for the current thread.  The
+** limit is only changed if the N is present.  The previous limit
+** is returned.
+*/
+static int test_soft_heap_limit(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_int64 amt;
+  Tcl_WideInt N = -1;
+  if( objc!=1 && objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "?N?");
+    return TCL_ERROR;
+  }
+  if( objc==2 ){
+    if( Tcl_GetWideIntFromObj(interp, objv[1], &N) ) return TCL_ERROR;
+  }
+  amt = sqlite3_soft_heap_limit64(N);
+  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(amt));
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_thread_cleanup
+**
+** Call the sqlite3_thread_cleanup API.
+*/
+static int test_thread_cleanup(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_DEPRECATED
+  sqlite3_thread_cleanup();
+#endif
+  return TCL_OK;
+}
+
+/*
+** Usage:   sqlite3_pager_refcounts  DB
+**
+** Return a list of numbers which are the PagerRefcount for all
+** pagers on each database connection.
+*/
+static int test_pager_refcounts(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  int i;
+  int v, *a;
+  Tcl_Obj *pResult;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  pResult = Tcl_NewObj();
+  for(i=0; i<db->nDb; i++){
+    if( db->aDb[i].pBt==0 ){
+      v = -1;
+    }else{
+      sqlite3_mutex_enter(db->mutex);
+      a = sqlite3PagerStats(sqlite3BtreePager(db->aDb[i].pBt));
+      v = a[0];
+      sqlite3_mutex_leave(db->mutex);
+    }
+    Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(v));
+  }
+  Tcl_SetObjResult(interp, pResult);
+  return TCL_OK;
+}
+
+
+/*
+** tclcmd:   working_64bit_int
+**
+** Some TCL builds (ex: cygwin) do not support 64-bit integers.  This
+** leads to a number of test failures.  The present command checks the
+** TCL build to see whether or not it supports 64-bit integers.  It
+** returns TRUE if it does and FALSE if not.
+**
+** This command is used to warn users that their TCL build is defective
+** and that the errors they are seeing in the test scripts might be
+** a result of their defective TCL rather than problems in SQLite.
+*/
+static int working_64bit_int(
+  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 *CONST objv[]  /* Command arguments */
+){
+  Tcl_Obj *pTestObj;
+  int working = 0;
+
+  pTestObj = Tcl_NewWideIntObj(1000000*(i64)1234567890);
+  working = strcmp(Tcl_GetString(pTestObj), "1234567890000000")==0;
+  Tcl_DecrRefCount(pTestObj);
+  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(working));
+  return TCL_OK;
+}
+
+
+/*
+** tclcmd:   vfs_unlink_test
+**
+** This TCL command unregisters the primary VFS and then registers
+** it back again.  This is used to test the ability to register a
+** VFS when none are previously registered, and the ability to 
+** unregister the only available VFS.  Ticket #2738
+*/
+static int vfs_unlink_test(
+  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 *CONST objv[]  /* Command arguments */
+){
+  int i;
+  sqlite3_vfs *pMain;
+  sqlite3_vfs *apVfs[20];
+  sqlite3_vfs one, two;
+
+  sqlite3_vfs_unregister(0);   /* Unregister of NULL is harmless */
+  one.zName = "__one";
+  two.zName = "__two";
+
+  /* Calling sqlite3_vfs_register with 2nd argument of 0 does not
+  ** change the default VFS
+  */
+  pMain = sqlite3_vfs_find(0);
+  sqlite3_vfs_register(&one, 0);
+  assert( pMain==0 || pMain==sqlite3_vfs_find(0) );
+  sqlite3_vfs_register(&two, 0);
+  assert( pMain==0 || pMain==sqlite3_vfs_find(0) );
+
+  /* We can find a VFS by its name */
+  assert( sqlite3_vfs_find("__one")==&one );
+  assert( sqlite3_vfs_find("__two")==&two );
+
+  /* Calling sqlite_vfs_register with non-zero second parameter changes the
+  ** default VFS, even if the 1st parameter is an existig VFS that is
+  ** previously registered as the non-default.
+  */
+  sqlite3_vfs_register(&one, 1);
+  assert( sqlite3_vfs_find("__one")==&one );
+  assert( sqlite3_vfs_find("__two")==&two );
+  assert( sqlite3_vfs_find(0)==&one );
+  sqlite3_vfs_register(&two, 1);
+  assert( sqlite3_vfs_find("__one")==&one );
+  assert( sqlite3_vfs_find("__two")==&two );
+  assert( sqlite3_vfs_find(0)==&two );
+  if( pMain ){
+    sqlite3_vfs_register(pMain, 1);
+    assert( sqlite3_vfs_find("__one")==&one );
+    assert( sqlite3_vfs_find("__two")==&two );
+    assert( sqlite3_vfs_find(0)==pMain );
+  }
+  
+  /* Unlink the default VFS.  Repeat until there are no more VFSes
+  ** registered.
+  */
+  for(i=0; i<sizeof(apVfs)/sizeof(apVfs[0]); i++){
+    apVfs[i] = sqlite3_vfs_find(0);
+    if( apVfs[i] ){
+      assert( apVfs[i]==sqlite3_vfs_find(apVfs[i]->zName) );
+      sqlite3_vfs_unregister(apVfs[i]);
+      assert( 0==sqlite3_vfs_find(apVfs[i]->zName) );
+    }
+  }
+  assert( 0==sqlite3_vfs_find(0) );
+  
+  /* Register the main VFS as non-default (will be made default, since
+  ** it'll be the only one in existence).
+  */
+  sqlite3_vfs_register(pMain, 0);
+  assert( sqlite3_vfs_find(0)==pMain );
+  
+  /* Un-register the main VFS again to restore an empty VFS list */
+  sqlite3_vfs_unregister(pMain);
+  assert( 0==sqlite3_vfs_find(0) );
+
+  /* Relink all VFSes in reverse order. */  
+  for(i=sizeof(apVfs)/sizeof(apVfs[0])-1; i>=0; i--){
+    if( apVfs[i] ){
+      sqlite3_vfs_register(apVfs[i], 1);
+      assert( apVfs[i]==sqlite3_vfs_find(0) );
+      assert( apVfs[i]==sqlite3_vfs_find(apVfs[i]->zName) );
+    }
+  }
+
+  /* Unregister out sample VFSes. */
+  sqlite3_vfs_unregister(&one);
+  sqlite3_vfs_unregister(&two);
+
+  /* Unregistering a VFS that is not currently registered is harmless */
+  sqlite3_vfs_unregister(&one);
+  sqlite3_vfs_unregister(&two);
+  assert( sqlite3_vfs_find("__one")==0 );
+  assert( sqlite3_vfs_find("__two")==0 );
+
+  /* We should be left with the original default VFS back as the
+  ** original */
+  assert( sqlite3_vfs_find(0)==pMain );
+
+  return TCL_OK;
+}
+
+/*
+** tclcmd:   vfs_initfail_test
+**
+** This TCL command attempts to vfs_find and vfs_register when the
+** sqlite3_initialize() interface is failing.  All calls should fail.
+*/
+static int vfs_initfail_test(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3_vfs one;
+  one.zName = "__one";
+
+  if( sqlite3_vfs_find(0) ) return TCL_ERROR;
+  sqlite3_vfs_register(&one, 0);
+  if( sqlite3_vfs_find(0) ) return TCL_ERROR;
+  sqlite3_vfs_register(&one, 1);
+  if( sqlite3_vfs_find(0) ) return TCL_ERROR;
+  return TCL_OK;
+}
+
+/*
+** Saved VFSes
+*/
+static sqlite3_vfs *apVfs[20];
+static int nVfs = 0;
+
+/*
+** tclcmd:   vfs_unregister_all
+**
+** Unregister all VFSes.
+*/
+static int vfs_unregister_all(
+  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 *CONST objv[]  /* Command arguments */
+){
+  int i;
+  for(i=0; i<ArraySize(apVfs); i++){
+    apVfs[i] = sqlite3_vfs_find(0);
+    if( apVfs[i]==0 ) break;
+    sqlite3_vfs_unregister(apVfs[i]);
+  }
+  nVfs = i;
+  return TCL_OK;
+}
+/*
+** tclcmd:   vfs_reregister_all
+**
+** Restore all VFSes that were removed using vfs_unregister_all
+*/
+static int vfs_reregister_all(
+  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 *CONST objv[]  /* Command arguments */
+){
+  int i;
+  for(i=0; i<nVfs; i++){
+    sqlite3_vfs_register(apVfs[i], i==0);
+  }
+  return TCL_OK;
+}
+
+
+/*
+** tclcmd:   file_control_test DB
+**
+** This TCL command runs the sqlite3_file_control interface and
+** verifies correct operation of the same.
+*/
+static int file_control_test(
+  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 *CONST objv[]  /* Command arguments */
+){
+  int iArg = 0;
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  rc = sqlite3_file_control(db, 0, 0, &iArg);
+  assert( rc==SQLITE_NOTFOUND );
+  rc = sqlite3_file_control(db, "notadatabase", SQLITE_FCNTL_LOCKSTATE, &iArg);
+  assert( rc==SQLITE_ERROR );
+  rc = sqlite3_file_control(db, "main", -1, &iArg);
+  assert( rc==SQLITE_NOTFOUND );
+  rc = sqlite3_file_control(db, "temp", -1, &iArg);
+  assert( rc==SQLITE_NOTFOUND || rc==SQLITE_ERROR );
+
+  return TCL_OK;
+}
+
+
+/*
+** tclcmd:   file_control_lasterrno_test DB
+**
+** This TCL command runs the sqlite3_file_control interface and
+** verifies correct operation of the SQLITE_LAST_ERRNO verb.
+*/
+static int file_control_lasterrno_test(
+  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 *CONST objv[]  /* Command arguments */
+){
+  int iArg = 0;
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " DB", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+    return TCL_ERROR;
+  }
+  rc = sqlite3_file_control(db, NULL, SQLITE_LAST_ERRNO, &iArg);
+  if( rc ){ 
+    Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 
+    return TCL_ERROR; 
+  }
+  if( iArg!=0 ) {
+    Tcl_AppendResult(interp, "Unexpected non-zero errno: ",
+                     Tcl_GetStringFromObj(Tcl_NewIntObj(iArg), 0), " ", 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;  
+}
+
+/*
+** tclcmd:   file_control_chunksize_test DB DBNAME SIZE
+**
+** This TCL command runs the sqlite3_file_control interface and
+** verifies correct operation of the SQLITE_GET_LOCKPROXYFILE and
+** SQLITE_SET_LOCKPROXYFILE verbs.
+*/
+static int file_control_chunksize_test(
+  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 *CONST objv[]  /* Command arguments */
+){
+  int nSize;                      /* New chunk size */
+  char *zDb;                      /* Db name ("main", "temp" etc.) */
+  sqlite3 *db;                    /* Database handle */
+  int rc;                         /* file_control() return code */
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME SIZE");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) 
+   || Tcl_GetIntFromObj(interp, objv[3], &nSize)
+  ){
+   return TCL_ERROR;
+  }
+  zDb = Tcl_GetString(objv[2]);
+  if( zDb[0]=='\0' ) zDb = NULL;
+
+  rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_CHUNK_SIZE, (void *)&nSize);
+  if( rc ){
+    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** tclcmd:   file_control_sizehint_test DB DBNAME SIZE
+**
+** This TCL command runs the sqlite3_file_control interface 
+** with SQLITE_FCNTL_SIZE_HINT
+*/
+static int file_control_sizehint_test(
+  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 *CONST objv[]  /* Command arguments */
+){
+  Tcl_WideInt nSize;              /* Hinted size */
+  char *zDb;                      /* Db name ("main", "temp" etc.) */
+  sqlite3 *db;                    /* Database handle */
+  int rc;                         /* file_control() return code */
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME SIZE");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) 
+   || Tcl_GetWideIntFromObj(interp, objv[3], &nSize)
+  ){
+   return TCL_ERROR;
+  }
+  zDb = Tcl_GetString(objv[2]);
+  if( zDb[0]=='\0' ) zDb = NULL;
+
+  rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_SIZE_HINT, (void *)&nSize);
+  if( rc ){
+    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** tclcmd:   file_control_lockproxy_test DB PWD
+**
+** This TCL command runs the sqlite3_file_control interface and
+** verifies correct operation of the SQLITE_GET_LOCKPROXYFILE and
+** SQLITE_SET_LOCKPROXYFILE verbs.
+*/
+static int file_control_lockproxy_test(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+                     Tcl_GetStringFromObj(objv[0], 0), " DB PWD", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+   return TCL_ERROR;
+  }
+  
+#if !defined(SQLITE_ENABLE_LOCKING_STYLE)
+#  if defined(__APPLE__)
+#    define SQLITE_ENABLE_LOCKING_STYLE 1
+#  else
+#    define SQLITE_ENABLE_LOCKING_STYLE 0
+#  endif
+#endif
+#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__)
+  {
+    char *testPath;
+    int rc;
+    int nPwd;
+    const char *zPwd;
+    char proxyPath[400];
+    
+    zPwd = Tcl_GetStringFromObj(objv[2], &nPwd);
+    if( sizeof(proxyPath)<nPwd+20 ){
+      Tcl_AppendResult(interp, "PWD too big", (void*)0);
+      return TCL_ERROR;
+    }
+    sprintf(proxyPath, "%s/test.proxy", zPwd);
+    rc = sqlite3_file_control(db, NULL, SQLITE_SET_LOCKPROXYFILE, proxyPath);
+    if( rc ){
+      Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); 
+      return TCL_ERROR;
+    }
+    rc = sqlite3_file_control(db, NULL, SQLITE_GET_LOCKPROXYFILE, &testPath);
+    if( strncmp(proxyPath,testPath,11) ){
+      Tcl_AppendResult(interp, "Lock proxy file did not match the "
+                               "previously assigned value", 0);
+      return TCL_ERROR;
+    }
+    if( rc ){
+      Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+      return TCL_ERROR;
+    }
+    rc = sqlite3_file_control(db, NULL, SQLITE_SET_LOCKPROXYFILE, proxyPath);
+    if( rc ){
+      Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+      return TCL_ERROR;
+    }
+  }
+#endif
+  return TCL_OK;  
+}
+
+/*
+** tclcmd:   file_control_win32_av_retry DB  NRETRY  DELAY
+**
+** This TCL command runs the sqlite3_file_control interface with
+** the SQLITE_FCNTL_WIN32_AV_RETRY opcode.
+*/
+static int file_control_win32_av_retry(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  int rc;
+  int a[2];
+  char z[100];
+
+  if( objc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " DB NRETRY DELAY", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[2], &a[0]) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[3], &a[1]) ) return TCL_ERROR;
+  rc = sqlite3_file_control(db, NULL, SQLITE_FCNTL_WIN32_AV_RETRY, (void*)a);
+  sqlite3_snprintf(sizeof(z), z, "%d %d %d", rc, a[0], a[1]);
+  Tcl_AppendResult(interp, z, (char*)0);
+  return TCL_OK;  
+}
+
+/*
+** tclcmd:   file_control_persist_wal DB PERSIST-FLAG
+**
+** This TCL command runs the sqlite3_file_control interface with
+** the SQLITE_FCNTL_PERSIST_WAL opcode.
+*/
+static int file_control_persist_wal(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  int rc;
+  int bPersist;
+  char z[100];
+
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " DB FLAG", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[2], &bPersist) ) return TCL_ERROR;
+  rc = sqlite3_file_control(db, NULL, SQLITE_FCNTL_PERSIST_WAL, (void*)&bPersist);
+  sqlite3_snprintf(sizeof(z), z, "%d %d", rc, bPersist);
+  Tcl_AppendResult(interp, z, (char*)0);
+  return TCL_OK;  
+}
+
+/*
+** tclcmd:   file_control_powersafe_overwrite DB PSOW-FLAG
+**
+** This TCL command runs the sqlite3_file_control interface with
+** the SQLITE_FCNTL_POWERSAFE_OVERWRITE opcode.
+*/
+static int file_control_powersafe_overwrite(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  int rc;
+  int b;
+  char z[100];
+
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " DB FLAG", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[2], &b) ) return TCL_ERROR;
+  rc = sqlite3_file_control(db,NULL,SQLITE_FCNTL_POWERSAFE_OVERWRITE,(void*)&b);
+  sqlite3_snprintf(sizeof(z), z, "%d %d", rc, b);
+  Tcl_AppendResult(interp, z, (char*)0);
+  return TCL_OK;  
+}
+
+
+/*
+** tclcmd:   file_control_vfsname DB ?AUXDB?
+**
+** Return a string that describes the stack of VFSes.
+*/
+static int file_control_vfsname(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  const char *zDbName = "main";
+  char *zVfsName = 0;
+
+  if( objc!=2 && objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " DB ?AUXDB?", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+    return TCL_ERROR;
+  }
+  if( objc==3 ){
+    zDbName = Tcl_GetString(objv[2]);
+  }
+  sqlite3_file_control(db, zDbName, SQLITE_FCNTL_VFSNAME,(void*)&zVfsName);
+  Tcl_AppendResult(interp, zVfsName, (char*)0);
+  sqlite3_free(zVfsName);
+  return TCL_OK;  
+}
+
+/*
+** tclcmd:   file_control_tempfilename DB ?AUXDB?
+**
+** Return a string that is a temporary filename
+*/
+static int file_control_tempfilename(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  const char *zDbName = "main";
+  char *zTName = 0;
+
+  if( objc!=2 && objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " DB ?AUXDB?", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+    return TCL_ERROR;
+  }
+  if( objc==3 ){
+    zDbName = Tcl_GetString(objv[2]);
+  }
+  sqlite3_file_control(db, zDbName, SQLITE_FCNTL_TEMPFILENAME, (void*)&zTName);
+  Tcl_AppendResult(interp, zTName, (char*)0);
+  sqlite3_free(zTName);
+  return TCL_OK;  
+}
+
+
+/*
+** tclcmd:   sqlite3_vfs_list
+**
+**   Return a tcl list containing the names of all registered vfs's.
+*/
+static int vfs_list(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3_vfs *pVfs;
+  Tcl_Obj *pRet = Tcl_NewObj();
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+  for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
+    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj(pVfs->zName, -1));
+  }
+  Tcl_SetObjResult(interp, pRet);
+  return TCL_OK;  
+}
+
+/*
+** tclcmd:   sqlite3_limit DB ID VALUE
+**
+** This TCL command runs the sqlite3_limit interface and
+** verifies correct operation of the same.
+*/
+static int test_limit(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  int rc;
+  static const struct {
+     char *zName;
+     int id;
+  } aId[] = {
+    { "SQLITE_LIMIT_LENGTH",              SQLITE_LIMIT_LENGTH               },
+    { "SQLITE_LIMIT_SQL_LENGTH",          SQLITE_LIMIT_SQL_LENGTH           },
+    { "SQLITE_LIMIT_COLUMN",              SQLITE_LIMIT_COLUMN               },
+    { "SQLITE_LIMIT_EXPR_DEPTH",          SQLITE_LIMIT_EXPR_DEPTH           },
+    { "SQLITE_LIMIT_COMPOUND_SELECT",     SQLITE_LIMIT_COMPOUND_SELECT      },
+    { "SQLITE_LIMIT_VDBE_OP",             SQLITE_LIMIT_VDBE_OP              },
+    { "SQLITE_LIMIT_FUNCTION_ARG",        SQLITE_LIMIT_FUNCTION_ARG         },
+    { "SQLITE_LIMIT_ATTACHED",            SQLITE_LIMIT_ATTACHED             },
+    { "SQLITE_LIMIT_LIKE_PATTERN_LENGTH", SQLITE_LIMIT_LIKE_PATTERN_LENGTH  },
+    { "SQLITE_LIMIT_VARIABLE_NUMBER",     SQLITE_LIMIT_VARIABLE_NUMBER      },
+    { "SQLITE_LIMIT_TRIGGER_DEPTH",       SQLITE_LIMIT_TRIGGER_DEPTH        },
+    
+    /* Out of range test cases */
+    { "SQLITE_LIMIT_TOOSMALL",            -1,                               },
+    { "SQLITE_LIMIT_TOOBIG",              SQLITE_LIMIT_TRIGGER_DEPTH+1      },
+  };
+  int i, id;
+  int val;
+  const char *zId;
+
+  if( objc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " DB ID VALUE", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zId = Tcl_GetString(objv[2]);
+  for(i=0; i<sizeof(aId)/sizeof(aId[0]); i++){
+    if( strcmp(zId, aId[i].zName)==0 ){
+      id = aId[i].id;
+      break;
+    }
+  }
+  if( i>=sizeof(aId)/sizeof(aId[0]) ){
+    Tcl_AppendResult(interp, "unknown limit type: ", zId, (char*)0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[3], &val) ) return TCL_ERROR;
+  rc = sqlite3_limit(db, id, val);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;  
+}
+
+/*
+** tclcmd:  save_prng_state
+**
+** Save the state of the pseudo-random number generator.
+** At the same time, verify that sqlite3_test_control works even when
+** called with an out-of-range opcode.
+*/
+static int save_prng_state(
+  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 *CONST objv[]  /* Command arguments */
+){
+  int rc = sqlite3_test_control(9999);
+  assert( rc==0 );
+  rc = sqlite3_test_control(-1);
+  assert( rc==0 );
+  sqlite3_test_control(SQLITE_TESTCTRL_PRNG_SAVE);
+  return TCL_OK;
+}
+/*
+** tclcmd:  restore_prng_state
+*/
+static int restore_prng_state(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3_test_control(SQLITE_TESTCTRL_PRNG_RESTORE);
+  return TCL_OK;
+}
+/*
+** tclcmd:  reset_prng_state
+*/
+static int reset_prng_state(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3_test_control(SQLITE_TESTCTRL_PRNG_RESET);
+  return TCL_OK;
+}
+
+/*
+** tclcmd:  pcache_stats
+*/
+static int test_pcache_stats(
+  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 *CONST objv[]  /* Command arguments */
+){
+  int nMin;
+  int nMax;
+  int nCurrent;
+  int nRecyclable;
+  Tcl_Obj *pRet;
+
+  sqlite3PcacheStats(&nCurrent, &nMax, &nMin, &nRecyclable);
+
+  pRet = Tcl_NewObj();
+  Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj("current", -1));
+  Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(nCurrent));
+  Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj("max", -1));
+  Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(nMax));
+  Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj("min", -1));
+  Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(nMin));
+  Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj("recyclable", -1));
+  Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(nRecyclable));
+
+  Tcl_SetObjResult(interp, pRet);
+
+  return TCL_OK;
+}
+
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+static void test_unlock_notify_cb(void **aArg, int nArg){
+  int ii;
+  for(ii=0; ii<nArg; ii++){
+    Tcl_EvalEx((Tcl_Interp *)aArg[ii], "unlock_notify", -1, TCL_EVAL_GLOBAL);
+  }
+}
+#endif /* SQLITE_ENABLE_UNLOCK_NOTIFY */
+
+/*
+** tclcmd:  sqlite3_unlock_notify db
+*/
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+static int test_unlock_notify(
+  ClientData clientData, /* Unused */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+    return TCL_ERROR;
+  }
+  rc = sqlite3_unlock_notify(db, test_unlock_notify_cb, (void *)interp);
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+  return TCL_OK;
+}
+#endif
+
+/*
+** tclcmd:  sqlite3_wal_checkpoint db ?NAME?
+*/
+static int test_wal_checkpoint(
+  ClientData clientData, /* Unused */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  char *zDb = 0;
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=3 && objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB ?NAME?");
+    return TCL_ERROR;
+  }
+
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+    return TCL_ERROR;
+  }
+  if( objc==3 ){
+    zDb = Tcl_GetString(objv[2]);
+  }
+  rc = sqlite3_wal_checkpoint(db, zDb);
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+  return TCL_OK;
+}
+
+/*
+** tclcmd:  sqlite3_wal_checkpoint_v2 db MODE ?NAME?
+**
+** This command calls the wal_checkpoint_v2() function with the specified
+** mode argument (passive, full or restart). If present, the database name
+** NAME is passed as the second argument to wal_checkpoint_v2(). If it the
+** NAME argument is not present, a NULL pointer is passed instead.
+**
+** If wal_checkpoint_v2() returns any value other than SQLITE_BUSY or
+** SQLITE_OK, then this command returns TCL_ERROR. The Tcl result is set
+** to the error message obtained from sqlite3_errmsg().
+**
+** Otherwise, this command returns a list of three integers. The first integer
+** is 1 if SQLITE_BUSY was returned, or 0 otherwise. The following two integers
+** are the values returned via the output parameters by wal_checkpoint_v2() -
+** the number of frames in the log and the number of frames in the log
+** that have been checkpointed.
+*/
+static int test_wal_checkpoint_v2(
+  ClientData clientData, /* Unused */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  char *zDb = 0;
+  sqlite3 *db;
+  int rc;
+
+  int eMode;
+  int nLog = -555;
+  int nCkpt = -555;
+  Tcl_Obj *pRet;
+
+  const char * aMode[] = { "passive", "full", "restart", 0 };
+  assert( SQLITE_CHECKPOINT_PASSIVE==0 );
+  assert( SQLITE_CHECKPOINT_FULL==1 );
+  assert( SQLITE_CHECKPOINT_RESTART==2 );
+
+  if( objc!=3 && objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB MODE ?NAME?");
+    return TCL_ERROR;
+  }
+
+  if( objc==4 ){
+    zDb = Tcl_GetString(objv[3]);
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db)
+   || Tcl_GetIndexFromObj(interp, objv[2], aMode, "mode", 0, &eMode) 
+  ){
+    return TCL_ERROR;
+  }
+
+  rc = sqlite3_wal_checkpoint_v2(db, zDb, eMode, &nLog, &nCkpt);
+  if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){
+    Tcl_SetResult(interp, (char *)sqlite3_errmsg(db), TCL_VOLATILE);
+    return TCL_ERROR;
+  }
+
+  pRet = Tcl_NewObj();
+  Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(rc==SQLITE_BUSY?1:0));
+  Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(nLog));
+  Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(nCkpt));
+  Tcl_SetObjResult(interp, pRet);
+
+  return TCL_OK;
+}
+
+/*
+** tclcmd:  test_sqlite3_log ?SCRIPT?
+*/
+static struct LogCallback {
+  Tcl_Interp *pInterp;
+  Tcl_Obj *pObj;
+} logcallback = {0, 0};
+static void xLogcallback(void *unused, int err, char *zMsg){
+  Tcl_Obj *pNew = Tcl_DuplicateObj(logcallback.pObj);
+  Tcl_IncrRefCount(pNew);
+  Tcl_ListObjAppendElement(
+      0, pNew, Tcl_NewStringObj(sqlite3ErrName(err), -1)
+  );
+  Tcl_ListObjAppendElement(0, pNew, Tcl_NewStringObj(zMsg, -1));
+  Tcl_EvalObjEx(logcallback.pInterp, pNew, TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);
+  Tcl_DecrRefCount(pNew);
+}
+static int test_sqlite3_log(
+  ClientData clientData,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  if( objc>2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SCRIPT");
+    return TCL_ERROR;
+  }
+  if( logcallback.pObj ){
+    Tcl_DecrRefCount(logcallback.pObj);
+    logcallback.pObj = 0;
+    logcallback.pInterp = 0;
+    sqlite3_config(SQLITE_CONFIG_LOG, 0, 0);
+  }
+  if( objc>1 ){
+    logcallback.pObj = objv[1];
+    Tcl_IncrRefCount(logcallback.pObj);
+    logcallback.pInterp = interp;
+    sqlite3_config(SQLITE_CONFIG_LOG, xLogcallback, 0);
+  }
+  return TCL_OK;
+}
+
+/*
+**     tcl_objproc COMMANDNAME ARGS...
+**
+** Run a TCL command using its objProc interface.  Throw an error if
+** the command has no objProc interface.
+*/
+static int runAsObjProc(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  Tcl_CmdInfo cmdInfo;
+  if( objc<2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "COMMAND ...");
+    return TCL_ERROR;
+  }
+  if( !Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
+    Tcl_AppendResult(interp, "command not found: ",
+           Tcl_GetString(objv[1]), (char*)0);
+    return TCL_ERROR;
+  }
+  if( cmdInfo.objProc==0 ){
+    Tcl_AppendResult(interp, "command has no objProc: ",
+           Tcl_GetString(objv[1]), (char*)0);
+    return TCL_ERROR;
+  }
+  return cmdInfo.objProc(cmdInfo.objClientData, interp, objc-1, objv+1);
+}
+
+#ifndef SQLITE_OMIT_EXPLAIN
+/*
+** WARNING: The following function, printExplainQueryPlan() is an exact
+** copy of example code from eqp.in (eqp.html). If this code is modified,
+** then the documentation copy needs to be modified as well.
+*/
+/*
+** Argument pStmt is a prepared SQL statement. This function compiles
+** an EXPLAIN QUERY PLAN command to report on the prepared statement,
+** and prints the report to stdout using printf().
+*/
+int printExplainQueryPlan(sqlite3_stmt *pStmt){
+  const char *zSql;               /* Input SQL */
+  char *zExplain;                 /* SQL with EXPLAIN QUERY PLAN prepended */
+  sqlite3_stmt *pExplain;         /* Compiled EXPLAIN QUERY PLAN command */
+  int rc;                         /* Return code from sqlite3_prepare_v2() */
+
+  zSql = sqlite3_sql(pStmt);
+  if( zSql==0 ) return SQLITE_ERROR;
+
+  zExplain = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zSql);
+  if( zExplain==0 ) return SQLITE_NOMEM;
+
+  rc = sqlite3_prepare_v2(sqlite3_db_handle(pStmt), zExplain, -1, &pExplain, 0);
+  sqlite3_free(zExplain);
+  if( rc!=SQLITE_OK ) return rc;
+
+  while( SQLITE_ROW==sqlite3_step(pExplain) ){
+    int iSelectid = sqlite3_column_int(pExplain, 0);
+    int iOrder = sqlite3_column_int(pExplain, 1);
+    int iFrom = sqlite3_column_int(pExplain, 2);
+    const char *zDetail = (const char *)sqlite3_column_text(pExplain, 3);
+
+    printf("%d %d %d %s\n", iSelectid, iOrder, iFrom, zDetail);
+  }
+
+  return sqlite3_finalize(pExplain);
+}
+
+static int test_print_eqp(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc;
+  sqlite3_stmt *pStmt;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+    return TCL_ERROR;
+  }
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  rc = printExplainQueryPlan(pStmt);
+  /* This is needed on Windows so that a test case using this 
+  ** function can open a read pipe and get the output of
+  ** printExplainQueryPlan() immediately.
+  */
+  fflush(stdout);
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), 0);
+  return TCL_OK;
+}
+#endif /* SQLITE_OMIT_EXPLAIN */
+
+/*
+** sqlite3_test_control VERB ARGS...
+*/
+static int test_test_control(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  struct Verb {
+    const char *zName;
+    int i;
+  } aVerb[] = {
+    { "SQLITE_TESTCTRL_LOCALTIME_FAULT", SQLITE_TESTCTRL_LOCALTIME_FAULT }, 
+  };
+  int iVerb;
+  int iFlag;
+  int rc;
+
+  if( objc<2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "VERB ARGS...");
+    return TCL_ERROR;
+  }
+
+  rc = Tcl_GetIndexFromObjStruct(
+      interp, objv[1], aVerb, sizeof(aVerb[0]), "VERB", 0, &iVerb
+  );
+  if( rc!=TCL_OK ) return rc;
+
+  iFlag = aVerb[iVerb].i;
+  switch( iFlag ){
+    case SQLITE_TESTCTRL_LOCALTIME_FAULT: {
+      int val;
+      if( objc!=3 ){
+        Tcl_WrongNumArgs(interp, 2, objv, "ONOFF");
+        return TCL_ERROR;
+      }
+      if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR;
+      sqlite3_test_control(SQLITE_TESTCTRL_LOCALTIME_FAULT, val);
+      break;
+    }
+  }
+
+  Tcl_ResetResult(interp);
+  return TCL_OK;
+}
+
+#if SQLITE_OS_UNIX
+#include <sys/time.h>
+#include <sys/resource.h>
+
+static int test_getrusage(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  char buf[1024];
+  struct rusage r;
+  memset(&r, 0, sizeof(r));
+  getrusage(RUSAGE_SELF, &r);
+
+  sprintf(buf, "ru_utime=%d.%06d ru_stime=%d.%06d ru_minflt=%d ru_majflt=%d", 
+    (int)r.ru_utime.tv_sec, (int)r.ru_utime.tv_usec, 
+    (int)r.ru_stime.tv_sec, (int)r.ru_stime.tv_usec, 
+    (int)r.ru_minflt, (int)r.ru_majflt
+  );
+  Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, -1));
+  return TCL_OK;
+}
+#endif
+
+#if SQLITE_OS_WIN
+/*
+** Information passed from the main thread into the windows file locker
+** background thread.
+*/
+struct win32FileLocker {
+  char *evName;       /* Name of event to signal thread startup */
+  HANDLE h;           /* Handle of the file to be locked */
+  int delay1;         /* Delay before locking */
+  int delay2;         /* Delay before unlocking */
+  int ok;             /* Finished ok */
+  int err;            /* True if an error occurs */
+};
+#endif
+
+
+#if SQLITE_OS_WIN
+#include <process.h>
+/*
+** The background thread that does file locking.
+*/
+static void win32_file_locker(void *pAppData){
+  struct win32FileLocker *p = (struct win32FileLocker*)pAppData;
+  if( p->evName ){
+    HANDLE ev = OpenEvent(EVENT_MODIFY_STATE, FALSE, p->evName);
+    if ( ev ){
+      SetEvent(ev);
+      CloseHandle(ev);
+    }
+  }
+  if( p->delay1 ) Sleep(p->delay1);
+  if( LockFile(p->h, 0, 0, 100000000, 0) ){
+    Sleep(p->delay2);
+    UnlockFile(p->h, 0, 0, 100000000, 0);
+    p->ok = 1;
+  }else{
+    p->err = 1;
+  }
+  CloseHandle(p->h);
+  p->h = 0;
+  p->delay1 = 0;
+  p->delay2 = 0;
+}
+#endif
+
+#if SQLITE_OS_WIN
+/*
+**      lock_win32_file FILENAME DELAY1 DELAY2
+**
+** Get an exclusive manditory lock on file for DELAY2 milliseconds.
+** Wait DELAY1 milliseconds before acquiring the lock.
+*/
+static int win32_file_lock(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  static struct win32FileLocker x = { "win32_file_lock", 0, 0, 0, 0, 0 };
+  const char *zFilename;
+  char zBuf[200];
+  int retry = 0;
+  HANDLE ev;
+  DWORD wResult;
+  
+  if( objc!=4 && objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME DELAY1 DELAY2");
+    return TCL_ERROR;
+  }
+  if( objc==1 ){
+    sqlite3_snprintf(sizeof(zBuf), zBuf, "%d %d %d %d %d",
+                     x.ok, x.err, x.delay1, x.delay2, x.h);
+    Tcl_AppendResult(interp, zBuf, (char*)0);
+    return TCL_OK;
+  }
+  while( x.h && retry<30 ){
+    retry++;
+    Sleep(100);
+  }
+  if( x.h ){
+    Tcl_AppendResult(interp, "busy", (char*)0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[2], &x.delay1) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[3], &x.delay2) ) return TCL_ERROR;
+  zFilename = Tcl_GetString(objv[1]);
+  x.h = CreateFile(zFilename, GENERIC_READ|GENERIC_WRITE,
+              FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_ALWAYS,
+              FILE_ATTRIBUTE_NORMAL, 0);
+  if( !x.h ){
+    Tcl_AppendResult(interp, "cannot open file: ", zFilename, (char*)0);
+    return TCL_ERROR;
+  }
+  ev = CreateEvent(NULL, TRUE, FALSE, x.evName);
+  if ( !ev ){
+    Tcl_AppendResult(interp, "cannot create event: ", x.evName, (char*)0);
+    return TCL_ERROR;
+  }
+  _beginthread(win32_file_locker, 0, (void*)&x);
+  Sleep(0);
+  if ( (wResult = WaitForSingleObject(ev, 10000))!=WAIT_OBJECT_0 ){
+    sqlite3_snprintf(sizeof(zBuf), zBuf, "0x%x", wResult);
+    Tcl_AppendResult(interp, "wait failed: ", zBuf, (char*)0);
+    CloseHandle(ev);
+    return TCL_ERROR;
+  }
+  CloseHandle(ev);
+  return TCL_OK;
+}
+
+/*
+**      exists_win32_path PATH
+**
+** Returns non-zero if the specified path exists, whose fully qualified name
+** may exceed 260 characters if it is prefixed with "\\?\".
+*/
+static int win32_exists_path(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PATH");
+    return TCL_ERROR;
+  }
+  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(
+      GetFileAttributesW( Tcl_GetUnicode(objv[1]))!=INVALID_FILE_ATTRIBUTES ));
+  return TCL_OK;
+}
+
+/*
+**      find_win32_file PATTERN
+**
+** Returns a list of entries in a directory that match the specified pattern,
+** whose fully qualified name may exceed 248 characters if it is prefixed with
+** "\\?\".
+*/
+static int win32_find_file(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  HANDLE hFindFile = INVALID_HANDLE_VALUE;
+  WIN32_FIND_DATAW findData;
+  Tcl_Obj *listObj;
+  DWORD lastErrno;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PATTERN");
+    return TCL_ERROR;
+  }
+  hFindFile = FindFirstFileW(Tcl_GetUnicode(objv[1]), &findData);
+  if( hFindFile==INVALID_HANDLE_VALUE ){
+    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(GetLastError()));
+    return TCL_ERROR;
+  }
+  listObj = Tcl_NewObj();
+  Tcl_IncrRefCount(listObj);
+  do {
+    Tcl_ListObjAppendElement(interp, listObj, Tcl_NewUnicodeObj(
+        findData.cFileName, -1));
+    Tcl_ListObjAppendElement(interp, listObj, Tcl_NewWideIntObj(
+        findData.dwFileAttributes));
+  } while( FindNextFileW(hFindFile, &findData) );
+  lastErrno = GetLastError();
+  if( lastErrno!=NO_ERROR && lastErrno!=ERROR_NO_MORE_FILES ){
+    FindClose(hFindFile);
+    Tcl_DecrRefCount(listObj);
+    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(GetLastError()));
+    return TCL_ERROR;
+  }
+  FindClose(hFindFile);
+  Tcl_SetObjResult(interp, listObj);
+  return TCL_OK;
+}
+
+/*
+**      delete_win32_file FILENAME
+**
+** Deletes the specified file, whose fully qualified name may exceed 260
+** characters if it is prefixed with "\\?\".
+*/
+static int win32_delete_file(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
+    return TCL_ERROR;
+  }
+  if( !DeleteFileW(Tcl_GetUnicode(objv[1])) ){
+    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(GetLastError()));
+    return TCL_ERROR;
+  }
+  Tcl_ResetResult(interp);
+  return TCL_OK;
+}
+
+/*
+**      make_win32_dir DIRECTORY
+**
+** Creates the specified directory, whose fully qualified name may exceed 248
+** characters if it is prefixed with "\\?\".
+*/
+static int win32_mkdir(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DIRECTORY");
+    return TCL_ERROR;
+  }
+  if( !CreateDirectoryW(Tcl_GetUnicode(objv[1]), NULL) ){
+    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(GetLastError()));
+    return TCL_ERROR;
+  }
+  Tcl_ResetResult(interp);
+  return TCL_OK;
+}
+
+/*
+**      remove_win32_dir DIRECTORY
+**
+** Removes the specified directory, whose fully qualified name may exceed 248
+** characters if it is prefixed with "\\?\".
+*/
+static int win32_rmdir(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DIRECTORY");
+    return TCL_ERROR;
+  }
+  if( !RemoveDirectoryW(Tcl_GetUnicode(objv[1])) ){
+    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(GetLastError()));
+    return TCL_ERROR;
+  }
+  Tcl_ResetResult(interp);
+  return TCL_OK;
+}
+#endif
+
+
+/*
+**      optimization_control DB OPT BOOLEAN
+**
+** Enable or disable query optimizations using the sqlite3_test_control()
+** interface.  Disable if BOOLEAN is false and enable if BOOLEAN is true.
+** OPT is the name of the optimization to be disabled.
+*/
+static int optimization_control(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int i;
+  sqlite3 *db;
+  const char *zOpt;
+  int onoff;
+  int mask = 0;
+  static const struct {
+    const char *zOptName;
+    int mask;
+  } aOpt[] = {
+    { "all",                 SQLITE_AllOpts        },
+    { "none",                0                     },
+    { "query-flattener",     SQLITE_QueryFlattener },
+    { "column-cache",        SQLITE_ColumnCache    },
+    { "groupby-order",       SQLITE_GroupByOrder   },
+    { "factor-constants",    SQLITE_FactorOutConst },
+    { "real-as-int",         SQLITE_IdxRealAsInt   },
+    { "distinct-opt",        SQLITE_DistinctOpt    },
+    { "cover-idx-scan",      SQLITE_CoverIdxScan   },
+    { "order-by-idx-join",   SQLITE_OrderByIdxJoin },
+    { "transitive",          SQLITE_Transitive     },
+    { "subquery-coroutine",  SQLITE_SubqCoroutine  },
+    { "omit-noop-join",      SQLITE_OmitNoopJoin   },
+    { "stat3",               SQLITE_Stat3          },
+  };
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB OPT BOOLEAN");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  if( Tcl_GetBooleanFromObj(interp, objv[3], &onoff) ) return TCL_ERROR;
+  zOpt = Tcl_GetString(objv[2]);
+  for(i=0; i<sizeof(aOpt)/sizeof(aOpt[0]); i++){
+    if( strcmp(zOpt, aOpt[i].zOptName)==0 ){
+      mask = aOpt[i].mask;
+      break;
+    }
+  }
+  if( onoff ) mask = ~mask;
+  if( i>=sizeof(aOpt)/sizeof(aOpt[0]) ){
+    Tcl_AppendResult(interp, "unknown optimization - should be one of:",
+                     (char*)0);
+    for(i=0; i<sizeof(aOpt)/sizeof(aOpt[0]); i++){
+      Tcl_AppendResult(interp, " ", aOpt[i].zOptName, (char*)0);
+    }
+    return TCL_ERROR;
+  }
+  sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, db, mask);
+  return TCL_OK;
+}
+
+typedef struct sqlite3_api_routines sqlite3_api_routines;
+/*
+**     load_static_extension DB NAME ...
+**
+** Load one or more statically linked extensions.
+*/
+static int tclLoadStaticExtensionCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  extern int sqlite3_amatch_init(sqlite3*,char**,const sqlite3_api_routines*);
+  extern int sqlite3_closure_init(sqlite3*,char**,const sqlite3_api_routines*);
+  extern int sqlite3_fuzzer_init(sqlite3*,char**,const sqlite3_api_routines*);
+  extern int sqlite3_ieee_init(sqlite3*,char**,const sqlite3_api_routines*);
+  extern int sqlite3_nextchar_init(sqlite3*,char**,const sqlite3_api_routines*);
+  extern int sqlite3_percentile_init(sqlite3*,char**,const sqlite3_api_routines*);
+  extern int sqlite3_regexp_init(sqlite3*,char**,const sqlite3_api_routines*);
+  extern int sqlite3_spellfix_init(sqlite3*,char**,const sqlite3_api_routines*);
+  extern int sqlite3_totype_init(sqlite3*,char**,const sqlite3_api_routines*);
+  extern int sqlite3_wholenumber_init(sqlite3*,char**,const sqlite3_api_routines*);
+  static const struct {
+    const char *zExtName;
+    int (*pInit)(sqlite3*,char**,const sqlite3_api_routines*);
+  } aExtension[] = {
+    { "amatch",                sqlite3_amatch_init               },
+    { "closure",               sqlite3_closure_init              },
+    { "fuzzer",                sqlite3_fuzzer_init               },
+    { "ieee754",               sqlite3_ieee_init                 },
+    { "nextchar",              sqlite3_nextchar_init             },
+    { "percentile",            sqlite3_percentile_init           },
+    { "regexp",                sqlite3_regexp_init               },
+    { "spellfix",              sqlite3_spellfix_init             },
+    { "totype",                sqlite3_totype_init               },
+    { "wholenumber",           sqlite3_wholenumber_init          },
+  };
+  sqlite3 *db;
+  const char *zName;
+  int i, j, rc;
+  char *zErrMsg = 0;
+  if( objc<3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB NAME ...");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  for(j=2; j<objc; j++){
+    zName = Tcl_GetString(objv[j]);
+    for(i=0; i<ArraySize(aExtension); i++){
+      if( strcmp(zName, aExtension[i].zExtName)==0 ) break;
+    }
+    if( i>=ArraySize(aExtension) ){
+      Tcl_AppendResult(interp, "no such extension: ", zName, (char*)0);
+      return TCL_ERROR;
+    }
+    rc = aExtension[i].pInit(db, &zErrMsg, 0);
+    if( rc!=SQLITE_OK || zErrMsg ){
+      Tcl_AppendResult(interp, "initialization of ", zName, " failed: ", zErrMsg,
+                       (char*)0);
+      sqlite3_free(zErrMsg);
+      return TCL_ERROR;
+    }
+  }
+  return TCL_OK;
+}
+
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest1_Init(Tcl_Interp *interp){
+  extern int sqlite3_search_count;
+  extern int sqlite3_found_count;
+  extern int sqlite3_interrupt_count;
+  extern int sqlite3_open_file_count;
+  extern int sqlite3_sort_count;
+  extern int sqlite3_current_time;
+#if SQLITE_OS_UNIX && defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE
+  extern int sqlite3_hostid_num;
+#endif
+  extern int sqlite3_max_blobsize;
+  extern int sqlite3BtreeSharedCacheReport(void*,
+                                          Tcl_Interp*,int,Tcl_Obj*CONST*);
+  static struct {
+     char *zName;
+     Tcl_CmdProc *xProc;
+  } aCmd[] = {
+     { "db_enter",                      (Tcl_CmdProc*)db_enter               },
+     { "db_leave",                      (Tcl_CmdProc*)db_leave               },
+     { "sqlite3_mprintf_int",           (Tcl_CmdProc*)sqlite3_mprintf_int    },
+     { "sqlite3_mprintf_int64",         (Tcl_CmdProc*)sqlite3_mprintf_int64  },
+     { "sqlite3_mprintf_long",          (Tcl_CmdProc*)sqlite3_mprintf_long   },
+     { "sqlite3_mprintf_str",           (Tcl_CmdProc*)sqlite3_mprintf_str    },
+     { "sqlite3_snprintf_str",          (Tcl_CmdProc*)sqlite3_snprintf_str   },
+     { "sqlite3_mprintf_stronly",       (Tcl_CmdProc*)sqlite3_mprintf_stronly},
+     { "sqlite3_mprintf_double",        (Tcl_CmdProc*)sqlite3_mprintf_double },
+     { "sqlite3_mprintf_scaled",        (Tcl_CmdProc*)sqlite3_mprintf_scaled },
+     { "sqlite3_mprintf_hexdouble",   (Tcl_CmdProc*)sqlite3_mprintf_hexdouble},
+     { "sqlite3_mprintf_z_test",        (Tcl_CmdProc*)test_mprintf_z        },
+     { "sqlite3_mprintf_n_test",        (Tcl_CmdProc*)test_mprintf_n        },
+     { "sqlite3_snprintf_int",          (Tcl_CmdProc*)test_snprintf_int     },
+     { "sqlite3_last_insert_rowid",     (Tcl_CmdProc*)test_last_rowid       },
+     { "sqlite3_exec_printf",           (Tcl_CmdProc*)test_exec_printf      },
+     { "sqlite3_exec_hex",              (Tcl_CmdProc*)test_exec_hex         },
+     { "sqlite3_exec",                  (Tcl_CmdProc*)test_exec             },
+     { "sqlite3_exec_nr",               (Tcl_CmdProc*)test_exec_nr          },
+#ifndef SQLITE_OMIT_GET_TABLE
+     { "sqlite3_get_table_printf",      (Tcl_CmdProc*)test_get_table_printf },
+#endif
+     { "sqlite3_close",                 (Tcl_CmdProc*)sqlite_test_close     },
+     { "sqlite3_close_v2",              (Tcl_CmdProc*)sqlite_test_close_v2  },
+     { "sqlite3_create_function",       (Tcl_CmdProc*)test_create_function  },
+     { "sqlite3_create_aggregate",      (Tcl_CmdProc*)test_create_aggregate },
+     { "sqlite_register_test_function", (Tcl_CmdProc*)test_register_func    },
+     { "sqlite_abort",                  (Tcl_CmdProc*)sqlite_abort          },
+     { "sqlite_bind",                   (Tcl_CmdProc*)test_bind             },
+     { "breakpoint",                    (Tcl_CmdProc*)test_breakpoint       },
+     { "sqlite3_key",                   (Tcl_CmdProc*)test_key              },
+     { "sqlite3_rekey",                 (Tcl_CmdProc*)test_rekey            },
+     { "sqlite_set_magic",              (Tcl_CmdProc*)sqlite_set_magic      },
+     { "sqlite3_interrupt",             (Tcl_CmdProc*)test_interrupt        },
+     { "sqlite_delete_function",        (Tcl_CmdProc*)delete_function       },
+     { "sqlite_delete_collation",       (Tcl_CmdProc*)delete_collation      },
+     { "sqlite3_get_autocommit",        (Tcl_CmdProc*)get_autocommit        },
+     { "sqlite3_stack_used",            (Tcl_CmdProc*)test_stack_used       },
+     { "sqlite3_busy_timeout",          (Tcl_CmdProc*)test_busy_timeout     },
+     { "printf",                        (Tcl_CmdProc*)test_printf           },
+     { "sqlite3IoTrace",              (Tcl_CmdProc*)test_io_trace         },
+  };
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+     void *clientData;
+  } aObjCmd[] = {
+     { "sqlite3_connection_pointer",    get_sqlite_pointer, 0 },
+     { "sqlite3_bind_int",              test_bind_int,      0 },
+     { "sqlite3_bind_zeroblob",         test_bind_zeroblob, 0 },
+     { "sqlite3_bind_int64",            test_bind_int64,    0 },
+     { "sqlite3_bind_double",           test_bind_double,   0 },
+     { "sqlite3_bind_null",             test_bind_null     ,0 },
+     { "sqlite3_bind_text",             test_bind_text     ,0 },
+     { "sqlite3_bind_text16",           test_bind_text16   ,0 },
+     { "sqlite3_bind_blob",             test_bind_blob     ,0 },
+     { "sqlite3_bind_parameter_count",  test_bind_parameter_count, 0},
+     { "sqlite3_bind_parameter_name",   test_bind_parameter_name,  0},
+     { "sqlite3_bind_parameter_index",  test_bind_parameter_index, 0},
+     { "sqlite3_clear_bindings",        test_clear_bindings, 0},
+     { "sqlite3_sleep",                 test_sleep,          0},
+     { "sqlite3_errcode",               test_errcode       ,0 },
+     { "sqlite3_extended_errcode",      test_ex_errcode    ,0 },
+     { "sqlite3_errmsg",                test_errmsg        ,0 },
+     { "sqlite3_errmsg16",              test_errmsg16      ,0 },
+     { "sqlite3_open",                  test_open          ,0 },
+     { "sqlite3_open16",                test_open16        ,0 },
+     { "sqlite3_open_v2",               test_open_v2       ,0 },
+     { "sqlite3_complete16",            test_complete16    ,0 },
+
+     { "sqlite3_prepare",               test_prepare       ,0 },
+     { "sqlite3_prepare16",             test_prepare16     ,0 },
+     { "sqlite3_prepare_v2",            test_prepare_v2    ,0 },
+     { "sqlite3_prepare_tkt3134",       test_prepare_tkt3134, 0},
+     { "sqlite3_prepare16_v2",          test_prepare16_v2  ,0 },
+     { "sqlite3_finalize",              test_finalize      ,0 },
+     { "sqlite3_stmt_status",           test_stmt_status   ,0 },
+     { "sqlite3_reset",                 test_reset         ,0 },
+     { "sqlite3_expired",               test_expired       ,0 },
+     { "sqlite3_transfer_bindings",     test_transfer_bind ,0 },
+     { "sqlite3_changes",               test_changes       ,0 },
+     { "sqlite3_step",                  test_step          ,0 },
+     { "sqlite3_sql",                   test_sql           ,0 },
+     { "sqlite3_next_stmt",             test_next_stmt     ,0 },
+     { "sqlite3_stmt_readonly",         test_stmt_readonly ,0 },
+     { "sqlite3_stmt_busy",             test_stmt_busy     ,0 },
+     { "uses_stmt_journal",             uses_stmt_journal ,0 },
+
+     { "sqlite3_release_memory",        test_release_memory,     0},
+     { "sqlite3_db_release_memory",     test_db_release_memory,  0},
+     { "sqlite3_db_filename",           test_db_filename,        0},
+     { "sqlite3_db_readonly",           test_db_readonly,        0},
+     { "sqlite3_soft_heap_limit",       test_soft_heap_limit,    0},
+     { "sqlite3_thread_cleanup",        test_thread_cleanup,     0},
+     { "sqlite3_pager_refcounts",       test_pager_refcounts,    0},
+
+     { "sqlite3_load_extension",        test_load_extension,     0},
+     { "sqlite3_enable_load_extension", test_enable_load,        0},
+     { "sqlite3_extended_result_codes", test_extended_result_codes, 0},
+     { "sqlite3_limit",                 test_limit,                 0},
+
+     { "save_prng_state",               save_prng_state,    0 },
+     { "restore_prng_state",            restore_prng_state, 0 },
+     { "reset_prng_state",              reset_prng_state,   0 },
+     { "optimization_control",          optimization_control,0},
+#if SQLITE_OS_WIN
+     { "lock_win32_file",               win32_file_lock,    0 },
+     { "exists_win32_path",             win32_exists_path,  0 },
+     { "find_win32_file",               win32_find_file,    0 },
+     { "delete_win32_file",             win32_delete_file,  0 },
+     { "make_win32_dir",                win32_mkdir,        0 },
+     { "remove_win32_dir",              win32_rmdir,        0 },
+#endif
+     { "tcl_objproc",                   runAsObjProc,       0 },
+
+     /* sqlite3_column_*() API */
+     { "sqlite3_column_count",          test_column_count  ,0 },
+     { "sqlite3_data_count",            test_data_count    ,0 },
+     { "sqlite3_column_type",           test_column_type   ,0 },
+     { "sqlite3_column_blob",           test_column_blob   ,0 },
+     { "sqlite3_column_double",         test_column_double ,0 },
+     { "sqlite3_column_int64",          test_column_int64  ,0 },
+     { "sqlite3_column_text",   test_stmt_utf8,  (void*)sqlite3_column_text },
+     { "sqlite3_column_name",   test_stmt_utf8,  (void*)sqlite3_column_name },
+     { "sqlite3_column_int",    test_stmt_int,   (void*)sqlite3_column_int  },
+     { "sqlite3_column_bytes",  test_stmt_int,   (void*)sqlite3_column_bytes},
+#ifndef SQLITE_OMIT_DECLTYPE
+     { "sqlite3_column_decltype",test_stmt_utf8,(void*)sqlite3_column_decltype},
+#endif
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+{ "sqlite3_column_database_name",test_stmt_utf8,(void*)sqlite3_column_database_name},
+{ "sqlite3_column_table_name",test_stmt_utf8,(void*)sqlite3_column_table_name},
+{ "sqlite3_column_origin_name",test_stmt_utf8,(void*)sqlite3_column_origin_name},
+#endif
+
+#ifndef SQLITE_OMIT_UTF16
+     { "sqlite3_column_bytes16", test_stmt_int, (void*)sqlite3_column_bytes16 },
+     { "sqlite3_column_text16",  test_stmt_utf16, (void*)sqlite3_column_text16},
+     { "sqlite3_column_name16",  test_stmt_utf16, (void*)sqlite3_column_name16},
+     { "add_alignment_test_collations", add_alignment_test_collations, 0      },
+#ifndef SQLITE_OMIT_DECLTYPE
+     { "sqlite3_column_decltype16",test_stmt_utf16,(void*)sqlite3_column_decltype16},
+#endif
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+{"sqlite3_column_database_name16",
+  test_stmt_utf16, (void*)sqlite3_column_database_name16},
+{"sqlite3_column_table_name16", test_stmt_utf16, (void*)sqlite3_column_table_name16},
+{"sqlite3_column_origin_name16", test_stmt_utf16, (void*)sqlite3_column_origin_name16},
+#endif
+#endif
+     { "sqlite3_create_collation_v2", test_create_collation_v2, 0 },
+     { "sqlite3_global_recover",     test_global_recover, 0   },
+     { "working_64bit_int",          working_64bit_int,   0   },
+     { "vfs_unlink_test",            vfs_unlink_test,     0   },
+     { "vfs_initfail_test",          vfs_initfail_test,   0   },
+     { "vfs_unregister_all",         vfs_unregister_all,  0   },
+     { "vfs_reregister_all",         vfs_reregister_all,  0   },
+     { "file_control_test",          file_control_test,   0   },
+     { "file_control_lasterrno_test", file_control_lasterrno_test,  0   },
+     { "file_control_lockproxy_test", file_control_lockproxy_test,  0   },
+     { "file_control_chunksize_test", file_control_chunksize_test,  0   },
+     { "file_control_sizehint_test",  file_control_sizehint_test,   0   },
+     { "file_control_win32_av_retry", file_control_win32_av_retry,  0   },
+     { "file_control_persist_wal",    file_control_persist_wal,     0   },
+     { "file_control_powersafe_overwrite",file_control_powersafe_overwrite,0},
+     { "file_control_vfsname",        file_control_vfsname,         0   },
+     { "file_control_tempfilename",   file_control_tempfilename,    0   },
+     { "sqlite3_vfs_list",           vfs_list,     0   },
+     { "sqlite3_create_function_v2", test_create_function_v2, 0 },
+
+     /* Functions from os.h */
+#ifndef SQLITE_OMIT_UTF16
+     { "add_test_collate",        test_collate, 0            },
+     { "add_test_collate_needed", test_collate_needed, 0     },
+     { "add_test_function",       test_function, 0           },
+#endif
+     { "sqlite3_test_errstr",     test_errstr, 0             },
+     { "tcl_variable_type",       tcl_variable_type, 0       },
+#ifndef SQLITE_OMIT_SHARED_CACHE
+     { "sqlite3_enable_shared_cache", test_enable_shared, 0  },
+     { "sqlite3_shared_cache_report", sqlite3BtreeSharedCacheReport, 0},
+#endif
+     { "sqlite3_libversion_number", test_libversion_number, 0  },
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+     { "sqlite3_table_column_metadata", test_table_column_metadata, 0  },
+#endif
+#ifndef SQLITE_OMIT_INCRBLOB
+     { "sqlite3_blob_read",   test_blob_read, 0  },
+     { "sqlite3_blob_write",  test_blob_write, 0  },
+     { "sqlite3_blob_reopen", test_blob_reopen, 0  },
+     { "sqlite3_blob_bytes",  test_blob_bytes, 0  },
+     { "sqlite3_blob_close",  test_blob_close, 0  },
+#endif
+     { "pcache_stats",       test_pcache_stats, 0  },
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+     { "sqlite3_unlock_notify", test_unlock_notify, 0  },
+#endif
+     { "sqlite3_wal_checkpoint",   test_wal_checkpoint, 0  },
+     { "sqlite3_wal_checkpoint_v2",test_wal_checkpoint_v2, 0  },
+     { "test_sqlite3_log",         test_sqlite3_log, 0  },
+#ifndef SQLITE_OMIT_EXPLAIN
+     { "print_explain_query_plan", test_print_eqp, 0  },
+#endif
+     { "sqlite3_test_control", test_test_control },
+#if SQLITE_OS_UNIX
+     { "getrusage", test_getrusage },
+#endif
+     { "load_static_extension", tclLoadStaticExtensionCmd },
+  };
+  static int bitmask_size = sizeof(Bitmask)*8;
+  int i;
+  extern int sqlite3_sync_count, sqlite3_fullsync_count;
+  extern int sqlite3_opentemp_count;
+  extern int sqlite3_like_count;
+  extern int sqlite3_xferopt_count;
+  extern int sqlite3_pager_readdb_count;
+  extern int sqlite3_pager_writedb_count;
+  extern int sqlite3_pager_writej_count;
+#if SQLITE_OS_WIN
+  extern int sqlite3_os_type;
+#endif
+#ifdef SQLITE_DEBUG
+  extern int sqlite3WhereTrace;
+  extern int sqlite3OSTrace;
+  extern int sqlite3WalTrace;
+#endif
+#ifdef SQLITE_TEST
+#ifdef SQLITE_ENABLE_FTS3
+  extern int sqlite3_fts3_enable_parentheses;
+#endif
+#endif
+
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
+  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, 
+        aObjCmd[i].xProc, aObjCmd[i].clientData, 0);
+  }
+  Tcl_LinkVar(interp, "sqlite_search_count", 
+      (char*)&sqlite3_search_count, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_found_count", 
+      (char*)&sqlite3_found_count, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_sort_count", 
+      (char*)&sqlite3_sort_count, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite3_max_blobsize", 
+      (char*)&sqlite3_max_blobsize, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_like_count", 
+      (char*)&sqlite3_like_count, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_interrupt_count", 
+      (char*)&sqlite3_interrupt_count, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_open_file_count", 
+      (char*)&sqlite3_open_file_count, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_current_time", 
+      (char*)&sqlite3_current_time, TCL_LINK_INT);
+#if SQLITE_OS_UNIX && defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE
+  Tcl_LinkVar(interp, "sqlite_hostid_num", 
+      (char*)&sqlite3_hostid_num, TCL_LINK_INT);
+#endif
+  Tcl_LinkVar(interp, "sqlite3_xferopt_count",
+      (char*)&sqlite3_xferopt_count, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite3_pager_readdb_count",
+      (char*)&sqlite3_pager_readdb_count, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite3_pager_writedb_count",
+      (char*)&sqlite3_pager_writedb_count, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite3_pager_writej_count",
+      (char*)&sqlite3_pager_writej_count, TCL_LINK_INT);
+#ifndef SQLITE_OMIT_UTF16
+  Tcl_LinkVar(interp, "unaligned_string_counter",
+      (char*)&unaligned_string_counter, TCL_LINK_INT);
+#endif
+#ifndef SQLITE_OMIT_UTF16
+  Tcl_LinkVar(interp, "sqlite_last_needed_collation",
+      (char*)&pzNeededCollation, TCL_LINK_STRING|TCL_LINK_READ_ONLY);
+#endif
+#if SQLITE_OS_WIN
+  Tcl_LinkVar(interp, "sqlite_os_type",
+      (char*)&sqlite3_os_type, TCL_LINK_INT);
+#endif
+#ifdef SQLITE_TEST
+  {
+    static const char *query_plan = "*** OBSOLETE VARIABLE ***";
+    Tcl_LinkVar(interp, "sqlite_query_plan",
+       (char*)&query_plan, TCL_LINK_STRING|TCL_LINK_READ_ONLY);
+  }
+#endif
+#ifdef SQLITE_DEBUG
+  Tcl_LinkVar(interp, "sqlite_where_trace",
+      (char*)&sqlite3WhereTrace, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_os_trace",
+      (char*)&sqlite3OSTrace, TCL_LINK_INT);
+#ifndef SQLITE_OMIT_WAL
+  Tcl_LinkVar(interp, "sqlite_wal_trace",
+      (char*)&sqlite3WalTrace, TCL_LINK_INT);
+#endif
+#endif
+#ifndef SQLITE_OMIT_DISKIO
+  Tcl_LinkVar(interp, "sqlite_opentemp_count",
+      (char*)&sqlite3_opentemp_count, TCL_LINK_INT);
+#endif
+  Tcl_LinkVar(interp, "sqlite_static_bind_value",
+      (char*)&sqlite_static_bind_value, TCL_LINK_STRING);
+  Tcl_LinkVar(interp, "sqlite_static_bind_nbyte",
+      (char*)&sqlite_static_bind_nbyte, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_temp_directory",
+      (char*)&sqlite3_temp_directory, TCL_LINK_STRING);
+  Tcl_LinkVar(interp, "sqlite_data_directory",
+      (char*)&sqlite3_data_directory, TCL_LINK_STRING);
+  Tcl_LinkVar(interp, "bitmask_size",
+      (char*)&bitmask_size, TCL_LINK_INT|TCL_LINK_READ_ONLY);
+  Tcl_LinkVar(interp, "sqlite_sync_count",
+      (char*)&sqlite3_sync_count, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_fullsync_count",
+      (char*)&sqlite3_fullsync_count, TCL_LINK_INT);
+#if defined(SQLITE_ENABLE_FTS3) && defined(SQLITE_TEST)
+  Tcl_LinkVar(interp, "sqlite_fts3_enable_parentheses",
+      (char*)&sqlite3_fts3_enable_parentheses, TCL_LINK_INT);
+#endif
+  return TCL_OK;
+}

+ 664 - 0
components/external/sqlite/test/test2.c

@@ -0,0 +1,664 @@
+/*
+** 2001 September 15
+**
+** 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 pager.c module in SQLite.  This code
+** is not included in the SQLite library.  It is used for automated
+** testing of the SQLite library.
+*/
+#include "sqliteInt.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+extern const char *sqlite3ErrName(int);
+
+/*
+** Page size and reserved size used for testing.
+*/
+static int test_pagesize = 1024;
+
+/*
+** Dummy page reinitializer
+*/
+static void pager_test_reiniter(DbPage *pNotUsed){
+  return;
+}
+
+/*
+** Usage:   pager_open FILENAME N-PAGE
+**
+** Open a new pager
+*/
+static int pager_open(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  u32 pageSize;
+  Pager *pPager;
+  int nPage;
+  int rc;
+  char zBuf[100];
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FILENAME N-PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[2], &nPage) ) return TCL_ERROR;
+  rc = sqlite3PagerOpen(sqlite3_vfs_find(0), &pPager, argv[1], 0, 0,
+      SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB,
+      pager_test_reiniter);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  sqlite3PagerSetCachesize(pPager, nPage);
+  pageSize = test_pagesize;
+  sqlite3PagerSetPagesize(pPager, &pageSize, -1);
+  sqlite3_snprintf(sizeof(zBuf),zBuf,"%p",pPager);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_close ID
+**
+** Close the given pager.
+*/
+static int pager_close(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pPager = sqlite3TestTextToPtr(argv[1]);
+  rc = sqlite3PagerClose(pPager);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_rollback ID
+**
+** Rollback changes
+*/
+static int pager_rollback(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pPager = sqlite3TestTextToPtr(argv[1]);
+  rc = sqlite3PagerRollback(pPager);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_commit ID
+**
+** Commit all changes
+*/
+static int pager_commit(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pPager = sqlite3TestTextToPtr(argv[1]);
+  rc = sqlite3PagerCommitPhaseOne(pPager, 0, 0);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  rc = sqlite3PagerCommitPhaseTwo(pPager);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_stmt_begin ID
+**
+** Start a new checkpoint.
+*/
+static int pager_stmt_begin(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pPager = sqlite3TestTextToPtr(argv[1]);
+  rc = sqlite3PagerOpenSavepoint(pPager, 1);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_stmt_rollback ID
+**
+** Rollback changes to a checkpoint
+*/
+static int pager_stmt_rollback(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pPager = sqlite3TestTextToPtr(argv[1]);
+  rc = sqlite3PagerSavepoint(pPager, SAVEPOINT_ROLLBACK, 0);
+  sqlite3PagerSavepoint(pPager, SAVEPOINT_RELEASE, 0);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_stmt_commit ID
+**
+** Commit changes to a checkpoint
+*/
+static int pager_stmt_commit(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pPager = sqlite3TestTextToPtr(argv[1]);
+  rc = sqlite3PagerSavepoint(pPager, SAVEPOINT_RELEASE, 0);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_stats ID
+**
+** Return pager statistics.
+*/
+static int pager_stats(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Pager *pPager;
+  int i, *a;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pPager = sqlite3TestTextToPtr(argv[1]);
+  a = sqlite3PagerStats(pPager);
+  for(i=0; i<9; i++){
+    static char *zName[] = {
+      "ref", "page", "max", "size", "state", "err",
+      "hit", "miss", "ovfl",
+    };
+    char zBuf[100];
+    Tcl_AppendElement(interp, zName[i]);
+    sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",a[i]);
+    Tcl_AppendElement(interp, zBuf);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_pagecount ID
+**
+** Return the size of the database file.
+*/
+static int pager_pagecount(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Pager *pPager;
+  char zBuf[100];
+  int nPage;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pPager = sqlite3TestTextToPtr(argv[1]);
+  sqlite3PagerPagecount(pPager, &nPage);
+  sqlite3_snprintf(sizeof(zBuf), zBuf, "%d", nPage);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_get ID PGNO
+**
+** Return a pointer to a page from the database.
+*/
+static int page_get(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Pager *pPager;
+  char zBuf[100];
+  DbPage *pPage;
+  int pgno;
+  int rc;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID PGNO\"", 0);
+    return TCL_ERROR;
+  }
+  pPager = sqlite3TestTextToPtr(argv[1]);
+  if( Tcl_GetInt(interp, argv[2], &pgno) ) return TCL_ERROR;
+  rc = sqlite3PagerSharedLock(pPager);
+  if( rc==SQLITE_OK ){
+    rc = sqlite3PagerGet(pPager, pgno, &pPage);
+  }
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  sqlite3_snprintf(sizeof(zBuf),zBuf,"%p",pPage);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_lookup ID PGNO
+**
+** Return a pointer to a page if the page is already in cache.
+** If not in cache, return an empty string.
+*/
+static int page_lookup(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Pager *pPager;
+  char zBuf[100];
+  DbPage *pPage;
+  int pgno;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID PGNO\"", 0);
+    return TCL_ERROR;
+  }
+  pPager = sqlite3TestTextToPtr(argv[1]);
+  if( Tcl_GetInt(interp, argv[2], &pgno) ) return TCL_ERROR;
+  pPage = sqlite3PagerLookup(pPager, pgno);
+  if( pPage ){
+    sqlite3_snprintf(sizeof(zBuf),zBuf,"%p",pPage);
+    Tcl_AppendResult(interp, zBuf, 0);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_truncate ID PGNO
+*/
+static int pager_truncate(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Pager *pPager;
+  int pgno;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID PGNO\"", 0);
+    return TCL_ERROR;
+  }
+  pPager = sqlite3TestTextToPtr(argv[1]);
+  if( Tcl_GetInt(interp, argv[2], &pgno) ) return TCL_ERROR;
+  sqlite3PagerTruncateImage(pPager, pgno);
+  return TCL_OK;
+}
+
+
+/*
+** Usage:   page_unref PAGE
+**
+** Drop a pointer to a page.
+*/
+static int page_unref(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  DbPage *pPage;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  pPage = (DbPage *)sqlite3TestTextToPtr(argv[1]);
+  sqlite3PagerUnref(pPage);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_read PAGE
+**
+** Return the content of a page
+*/
+static int page_read(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  char zBuf[100];
+  DbPage *pPage;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  pPage = sqlite3TestTextToPtr(argv[1]);
+  memcpy(zBuf, sqlite3PagerGetData(pPage), sizeof(zBuf));
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_number PAGE
+**
+** Return the page number for a page.
+*/
+static int page_number(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  char zBuf[100];
+  DbPage *pPage;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  pPage = (DbPage *)sqlite3TestTextToPtr(argv[1]);
+  sqlite3_snprintf(sizeof(zBuf), zBuf, "%d", sqlite3PagerPagenumber(pPage));
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_write PAGE DATA
+**
+** Write something into a page.
+*/
+static int page_write(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  DbPage *pPage;
+  char *pData;
+  int rc;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE DATA\"", 0);
+    return TCL_ERROR;
+  }
+  pPage = (DbPage *)sqlite3TestTextToPtr(argv[1]);
+  rc = sqlite3PagerWrite(pPage);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  pData = sqlite3PagerGetData(pPage);
+  strncpy(pData, argv[2], test_pagesize-1);
+  pData[test_pagesize-1] = 0;
+  return TCL_OK;
+}
+
+#ifndef SQLITE_OMIT_DISKIO
+/*
+** Usage:   fake_big_file  N  FILENAME
+**
+** Write a few bytes at the N megabyte point of FILENAME.  This will
+** create a large file.  If the file was a valid SQLite database, then
+** the next time the database is opened, SQLite will begin allocating
+** new pages after N.  If N is 2096 or bigger, this will test the
+** ability of SQLite to write to large files.
+*/
+static int fake_big_file(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  sqlite3_vfs *pVfs;
+  sqlite3_file *fd = 0;
+  int rc;
+  int n;
+  i64 offset;
+  char *zFile;
+  int nFile;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " N-MEGABYTES FILE\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], &n) ) return TCL_ERROR;
+
+  pVfs = sqlite3_vfs_find(0);
+  nFile = (int)strlen(argv[2]);
+  zFile = sqlite3_malloc( nFile+2 );
+  if( zFile==0 ) return TCL_ERROR;
+  memcpy(zFile, argv[2], nFile+1);
+  zFile[nFile+1] = 0;
+  rc = sqlite3OsOpenMalloc(pVfs, zFile, &fd, 
+      (SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_DB), 0
+  );
+  if( rc ){
+    Tcl_AppendResult(interp, "open failed: ", sqlite3ErrName(rc), 0);
+    sqlite3_free(zFile);
+    return TCL_ERROR;
+  }
+  offset = n;
+  offset *= 1024*1024;
+  rc = sqlite3OsWrite(fd, "Hello, World!", 14, offset);
+  sqlite3OsCloseFree(fd);
+  sqlite3_free(zFile);
+  if( rc ){
+    Tcl_AppendResult(interp, "write failed: ", sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+#endif
+
+
+/*
+** test_control_pending_byte  PENDING_BYTE
+**
+** Set the PENDING_BYTE using the sqlite3_test_control() interface.
+*/
+static int testPendingByte(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int pbyte;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+                     " PENDING-BYTE\"", (void*)0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], &pbyte) ) return TCL_ERROR;
+  rc = sqlite3_test_control(SQLITE_TESTCTRL_PENDING_BYTE, pbyte);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}  
+
+/*
+** sqlite3BitvecBuiltinTest SIZE PROGRAM
+**
+** Invoke the SQLITE_TESTCTRL_BITVEC_TEST operator on test_control.
+** See comments on sqlite3BitvecBuiltinTest() for additional information.
+*/
+static int testBitvecBuiltinTest(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int sz, rc;
+  int nProg = 0;
+  int aProg[100];
+  const char *z;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+                     " SIZE PROGRAM\"", (void*)0);
+  }
+  if( Tcl_GetInt(interp, argv[1], &sz) ) return TCL_ERROR;
+  z = argv[2];
+  while( nProg<99 && *z ){
+    while( *z && !sqlite3Isdigit(*z) ){ z++; }
+    if( *z==0 ) break;
+    aProg[nProg++] = atoi(z);
+    while( sqlite3Isdigit(*z) ){ z++; }
+  }
+  aProg[nProg] = 0;
+  rc = sqlite3_test_control(SQLITE_TESTCTRL_BITVEC_TEST, sz, aProg);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}  
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest2_Init(Tcl_Interp *interp){
+  extern int sqlite3_io_error_persist;
+  extern int sqlite3_io_error_pending;
+  extern int sqlite3_io_error_hit;
+  extern int sqlite3_io_error_hardhit;
+  extern int sqlite3_diskfull_pending;
+  extern int sqlite3_diskfull;
+  static struct {
+    char *zName;
+    Tcl_CmdProc *xProc;
+  } aCmd[] = {
+    { "pager_open",              (Tcl_CmdProc*)pager_open          },
+    { "pager_close",             (Tcl_CmdProc*)pager_close         },
+    { "pager_commit",            (Tcl_CmdProc*)pager_commit        },
+    { "pager_rollback",          (Tcl_CmdProc*)pager_rollback      },
+    { "pager_stmt_begin",        (Tcl_CmdProc*)pager_stmt_begin    },
+    { "pager_stmt_commit",       (Tcl_CmdProc*)pager_stmt_commit   },
+    { "pager_stmt_rollback",     (Tcl_CmdProc*)pager_stmt_rollback },
+    { "pager_stats",             (Tcl_CmdProc*)pager_stats         },
+    { "pager_pagecount",         (Tcl_CmdProc*)pager_pagecount     },
+    { "page_get",                (Tcl_CmdProc*)page_get            },
+    { "page_lookup",             (Tcl_CmdProc*)page_lookup         },
+    { "page_unref",              (Tcl_CmdProc*)page_unref          },
+    { "page_read",               (Tcl_CmdProc*)page_read           },
+    { "page_write",              (Tcl_CmdProc*)page_write          },
+    { "page_number",             (Tcl_CmdProc*)page_number         },
+    { "pager_truncate",          (Tcl_CmdProc*)pager_truncate      },
+#ifndef SQLITE_OMIT_DISKIO
+    { "fake_big_file",           (Tcl_CmdProc*)fake_big_file       },
+#endif
+    { "sqlite3BitvecBuiltinTest",(Tcl_CmdProc*)testBitvecBuiltinTest     },
+    { "sqlite3_test_control_pending_byte", (Tcl_CmdProc*)testPendingByte },
+  };
+  int i;
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
+  Tcl_LinkVar(interp, "sqlite_io_error_pending",
+     (char*)&sqlite3_io_error_pending, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_io_error_persist",
+     (char*)&sqlite3_io_error_persist, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_io_error_hit",
+     (char*)&sqlite3_io_error_hit, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_io_error_hardhit",
+     (char*)&sqlite3_io_error_hardhit, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_diskfull_pending",
+     (char*)&sqlite3_diskfull_pending, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "sqlite_diskfull",
+     (char*)&sqlite3_diskfull, TCL_LINK_INT);
+#ifndef SQLITE_OMIT_WSD
+  Tcl_LinkVar(interp, "sqlite_pending_byte",
+     (char*)&sqlite3PendingByte, TCL_LINK_INT | TCL_LINK_READ_ONLY);
+#endif
+  return TCL_OK;
+}

+ 628 - 0
components/external/sqlite/test/test3.c

@@ -0,0 +1,628 @@
+/*
+** 2001 September 15
+**
+** 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 btree.c module in SQLite.  This code
+** is not included in the SQLite library.  It is used for automated
+** testing of the SQLite library.
+*/
+#include "sqliteInt.h"
+#include "btreeInt.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+
+extern const char *sqlite3ErrName(int);
+
+/*
+** A bogus sqlite3 connection structure for use in the btree
+** tests.
+*/
+static sqlite3 sDb;
+static int nRefSqlite3 = 0;
+
+/*
+** Usage:   btree_open FILENAME NCACHE
+**
+** Open a new database
+*/
+static int btree_open(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Btree *pBt;
+  int rc, nCache;
+  char zBuf[100];
+  int n;
+  char *zFilename;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FILENAME NCACHE FLAGS\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[2], &nCache) ) return TCL_ERROR;
+  nRefSqlite3++;
+  if( nRefSqlite3==1 ){
+    sDb.pVfs = sqlite3_vfs_find(0);
+    sDb.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE);
+    sqlite3_mutex_enter(sDb.mutex);
+  }
+  n = (int)strlen(argv[1]);
+  zFilename = sqlite3_malloc( n+2 );
+  if( zFilename==0 ) return TCL_ERROR;
+  memcpy(zFilename, argv[1], n+1);
+  zFilename[n+1] = 0;
+  rc = sqlite3BtreeOpen(sDb.pVfs, zFilename, &sDb, &pBt, 0, 
+     SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB);
+  sqlite3_free(zFilename);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  sqlite3BtreeSetCacheSize(pBt, nCache);
+  sqlite3_snprintf(sizeof(zBuf), zBuf,"%p", pBt);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_close ID
+**
+** Close the given database.
+*/
+static int btree_close(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Btree *pBt;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pBt = sqlite3TestTextToPtr(argv[1]);
+  rc = sqlite3BtreeClose(pBt);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  nRefSqlite3--;
+  if( nRefSqlite3==0 ){
+    sqlite3_mutex_leave(sDb.mutex);
+    sqlite3_mutex_free(sDb.mutex);
+    sDb.mutex = 0;
+    sDb.pVfs = 0;
+  }
+  return TCL_OK;
+}
+
+
+/*
+** Usage:   btree_begin_transaction ID
+**
+** Start a new transaction
+*/
+static int btree_begin_transaction(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Btree *pBt;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pBt = sqlite3TestTextToPtr(argv[1]);
+  sqlite3BtreeEnter(pBt);
+  rc = sqlite3BtreeBeginTrans(pBt, 1);
+  sqlite3BtreeLeave(pBt);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_pager_stats ID
+**
+** Returns pager statistics
+*/
+static int btree_pager_stats(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Btree *pBt;
+  int i;
+  int *a;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pBt = sqlite3TestTextToPtr(argv[1]);
+ 
+  /* Normally in this file, with a b-tree handle opened using the 
+  ** [btree_open] command it is safe to call sqlite3BtreeEnter() directly.
+  ** But this function is sometimes called with a btree handle obtained
+  ** from an open SQLite connection (using [btree_from_db]). In this case
+  ** we need to obtain the mutex for the controlling SQLite handle before
+  ** it is safe to call sqlite3BtreeEnter().
+  */
+  sqlite3_mutex_enter(pBt->db->mutex);
+
+  sqlite3BtreeEnter(pBt);
+  a = sqlite3PagerStats(sqlite3BtreePager(pBt));
+  for(i=0; i<11; i++){
+    static char *zName[] = {
+      "ref", "page", "max", "size", "state", "err",
+      "hit", "miss", "ovfl", "read", "write"
+    };
+    char zBuf[100];
+    Tcl_AppendElement(interp, zName[i]);
+    sqlite3_snprintf(sizeof(zBuf), zBuf,"%d",a[i]);
+    Tcl_AppendElement(interp, zBuf);
+  }
+  sqlite3BtreeLeave(pBt);
+
+  /* Release the mutex on the SQLite handle that controls this b-tree */
+  sqlite3_mutex_leave(pBt->db->mutex);
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_cursor ID TABLENUM WRITEABLE
+**
+** Create a new cursor.  Return the ID for the cursor.
+*/
+static int btree_cursor(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Btree *pBt;
+  int iTable;
+  BtCursor *pCur;
+  int rc = SQLITE_OK;
+  int wrFlag;
+  char zBuf[30];
+
+  if( argc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID TABLENUM WRITEABLE\"", 0);
+    return TCL_ERROR;
+  }
+  pBt = sqlite3TestTextToPtr(argv[1]);
+  if( Tcl_GetInt(interp, argv[2], &iTable) ) return TCL_ERROR;
+  if( Tcl_GetBoolean(interp, argv[3], &wrFlag) ) return TCL_ERROR;
+  pCur = (BtCursor *)ckalloc(sqlite3BtreeCursorSize());
+  memset(pCur, 0, sqlite3BtreeCursorSize());
+  sqlite3BtreeEnter(pBt);
+#ifndef SQLITE_OMIT_SHARED_CACHE
+  rc = sqlite3BtreeLockTable(pBt, iTable, wrFlag);
+#endif
+  if( rc==SQLITE_OK ){
+    rc = sqlite3BtreeCursor(pBt, iTable, wrFlag, 0, pCur);
+  }
+  sqlite3BtreeLeave(pBt);
+  if( rc ){
+    ckfree((char *)pCur);
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  sqlite3_snprintf(sizeof(zBuf), zBuf,"%p", pCur);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_close_cursor ID
+**
+** Close a cursor opened using btree_cursor.
+*/
+static int btree_close_cursor(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  BtCursor *pCur;
+  Btree *pBt;
+  int rc;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pCur = sqlite3TestTextToPtr(argv[1]);
+  pBt = pCur->pBtree;
+  sqlite3BtreeEnter(pBt);
+  rc = sqlite3BtreeCloseCursor(pCur);
+  sqlite3BtreeLeave(pBt);
+  ckfree((char *)pCur);
+  if( rc ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_next ID
+**
+** Move the cursor to the next entry in the table.  Return 0 on success
+** or 1 if the cursor was already on the last entry in the table or if
+** the table is empty.
+*/
+static int btree_next(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  BtCursor *pCur;
+  int rc;
+  int res = 0;
+  char zBuf[100];
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pCur = sqlite3TestTextToPtr(argv[1]);
+  sqlite3BtreeEnter(pCur->pBtree);
+  rc = sqlite3BtreeNext(pCur, &res);
+  sqlite3BtreeLeave(pCur->pBtree);
+  if( rc ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",res);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_first ID
+**
+** Move the cursor to the first entry in the table.  Return 0 if the
+** cursor was left point to something and 1 if the table is empty.
+*/
+static int btree_first(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  BtCursor *pCur;
+  int rc;
+  int res = 0;
+  char zBuf[100];
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pCur = sqlite3TestTextToPtr(argv[1]);
+  sqlite3BtreeEnter(pCur->pBtree);
+  rc = sqlite3BtreeFirst(pCur, &res);
+  sqlite3BtreeLeave(pCur->pBtree);
+  if( rc ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
+    return TCL_ERROR;
+  }
+  sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",res);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_eof ID
+**
+** Return TRUE if the given cursor is not pointing at a valid entry.
+** Return FALSE if the cursor does point to a valid entry.
+*/
+static int btree_eof(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  BtCursor *pCur;
+  int rc;
+  char zBuf[50];
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pCur = sqlite3TestTextToPtr(argv[1]);
+  sqlite3BtreeEnter(pCur->pBtree);
+  rc = sqlite3BtreeEof(pCur);
+  sqlite3BtreeLeave(pCur->pBtree);
+  sqlite3_snprintf(sizeof(zBuf),zBuf, "%d", rc);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_payload_size ID
+**
+** Return the number of bytes of payload
+*/
+static int btree_payload_size(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  BtCursor *pCur;
+  int n2;
+  u64 n1;
+  char zBuf[50];
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pCur = sqlite3TestTextToPtr(argv[1]);
+  sqlite3BtreeEnter(pCur->pBtree);
+
+  /* The cursor may be in "require-seek" state. If this is the case, the
+  ** call to BtreeDataSize() will fix it. */
+  sqlite3BtreeDataSize(pCur, (u32*)&n2);
+  if( pCur->apPage[pCur->iPage]->intKey ){
+    n1 = 0;
+  }else{
+    sqlite3BtreeKeySize(pCur, (i64*)&n1);
+  }
+  sqlite3BtreeLeave(pCur->pBtree);
+  sqlite3_snprintf(sizeof(zBuf),zBuf, "%d", (int)(n1+n2));
+  Tcl_AppendResult(interp, zBuf, 0);
+  return SQLITE_OK;
+}
+
+/*
+** usage:   varint_test  START  MULTIPLIER  COUNT  INCREMENT
+**
+** This command tests the putVarint() and getVarint()
+** routines, both for accuracy and for speed.
+**
+** An integer is written using putVarint() and read back with
+** getVarint() and varified to be unchanged.  This repeats COUNT
+** times.  The first integer is START*MULTIPLIER.  Each iteration
+** increases the integer by INCREMENT.
+**
+** This command returns nothing if it works.  It returns an error message
+** if something goes wrong.
+*/
+static int btree_varint_test(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  u32 start, mult, count, incr;
+  u64 in, out;
+  int n1, n2, i, j;
+  unsigned char zBuf[100];
+  if( argc!=5 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " START MULTIPLIER COUNT INCREMENT\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&start) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[2], (int*)&mult) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[3], (int*)&count) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[4], (int*)&incr) ) return TCL_ERROR;
+  in = start;
+  in *= mult;
+  for(i=0; i<(int)count; i++){
+    char zErr[200];
+    n1 = putVarint(zBuf, in);
+    if( n1>9 || n1<1 ){
+      sprintf(zErr, "putVarint returned %d - should be between 1 and 9", n1);
+      Tcl_AppendResult(interp, zErr, 0);
+      return TCL_ERROR;
+    }
+    n2 = getVarint(zBuf, &out);
+    if( n1!=n2 ){
+      sprintf(zErr, "putVarint returned %d and getVarint returned %d", n1, n2);
+      Tcl_AppendResult(interp, zErr, 0);
+      return TCL_ERROR;
+    }
+    if( in!=out ){
+      sprintf(zErr, "Wrote 0x%016llx and got back 0x%016llx", in, out);
+      Tcl_AppendResult(interp, zErr, 0);
+      return TCL_ERROR;
+    }
+    if( (in & 0xffffffff)==in ){
+      u32 out32;
+      n2 = getVarint32(zBuf, out32);
+      out = out32;
+      if( n1!=n2 ){
+        sprintf(zErr, "putVarint returned %d and GetVarint32 returned %d", 
+                  n1, n2);
+        Tcl_AppendResult(interp, zErr, 0);
+        return TCL_ERROR;
+      }
+      if( in!=out ){
+        sprintf(zErr, "Wrote 0x%016llx and got back 0x%016llx from GetVarint32",
+            in, out);
+        Tcl_AppendResult(interp, zErr, 0);
+        return TCL_ERROR;
+      }
+    }
+
+    /* In order to get realistic timings, run getVarint 19 more times.
+    ** This is because getVarint is called about 20 times more often
+    ** than putVarint.
+    */
+    for(j=0; j<19; j++){
+      getVarint(zBuf, &out);
+    }
+    in += incr;
+  }
+  return TCL_OK;
+}
+
+/*
+** usage:   btree_from_db  DB-HANDLE
+**
+** This command returns the btree handle for the main database associated
+** with the database-handle passed as the argument. Example usage:
+**
+** sqlite3 db test.db
+** set bt [btree_from_db db]
+*/
+static int btree_from_db(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  char zBuf[100];
+  Tcl_CmdInfo info;
+  sqlite3 *db;
+  Btree *pBt;
+  int iDb = 0;
+
+  if( argc!=2 && argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " DB-HANDLE ?N?\"", 0);
+    return TCL_ERROR;
+  }
+
+  if( 1!=Tcl_GetCommandInfo(interp, argv[1], &info) ){
+    Tcl_AppendResult(interp, "No such db-handle: \"", argv[1], "\"", 0);
+    return TCL_ERROR;
+  }
+  if( argc==3 ){
+    iDb = atoi(argv[2]);
+  }
+
+  db = *((sqlite3 **)info.objClientData);
+  assert( db );
+
+  pBt = db->aDb[iDb].pBt;
+  sqlite3_snprintf(sizeof(zBuf), zBuf, "%p", pBt);
+  Tcl_SetResult(interp, zBuf, TCL_VOLATILE);
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_ismemdb ID
+**
+** Return true if the B-Tree is in-memory.
+*/
+static int btree_ismemdb(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  Btree *pBt;
+  int res;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  pBt = sqlite3TestTextToPtr(argv[1]);
+  sqlite3_mutex_enter(pBt->db->mutex);
+  sqlite3BtreeEnter(pBt);
+  res = sqlite3PagerIsMemdb(sqlite3BtreePager(pBt));
+  sqlite3BtreeLeave(pBt);
+  sqlite3_mutex_leave(pBt->db->mutex);
+  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(res));
+  return SQLITE_OK;
+}
+
+/*
+** usage:   btree_set_cache_size ID NCACHE
+**
+** Set the size of the cache used by btree $ID.
+*/
+static int btree_set_cache_size(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int nCache;
+  Btree *pBt;
+  
+  if( argc!=3 ){
+    Tcl_AppendResult(
+        interp, "wrong # args: should be \"", argv[0], " BT NCACHE\"", 0);
+    return TCL_ERROR;
+  }
+  pBt = sqlite3TestTextToPtr(argv[1]);
+  if( Tcl_GetInt(interp, argv[2], &nCache) ) return TCL_ERROR;
+
+  sqlite3_mutex_enter(pBt->db->mutex);
+  sqlite3BtreeEnter(pBt);
+  sqlite3BtreeSetCacheSize(pBt, nCache);
+  sqlite3BtreeLeave(pBt);
+  sqlite3_mutex_leave(pBt->db->mutex);
+  return TCL_OK;
+}      
+
+
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest3_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_CmdProc *xProc;
+  } aCmd[] = {
+     { "btree_open",               (Tcl_CmdProc*)btree_open               },
+     { "btree_close",              (Tcl_CmdProc*)btree_close              },
+     { "btree_begin_transaction",  (Tcl_CmdProc*)btree_begin_transaction  },
+     { "btree_pager_stats",        (Tcl_CmdProc*)btree_pager_stats        },
+     { "btree_cursor",             (Tcl_CmdProc*)btree_cursor             },
+     { "btree_close_cursor",       (Tcl_CmdProc*)btree_close_cursor       },
+     { "btree_next",               (Tcl_CmdProc*)btree_next               },
+     { "btree_eof",                (Tcl_CmdProc*)btree_eof                },
+     { "btree_payload_size",       (Tcl_CmdProc*)btree_payload_size       },
+     { "btree_first",              (Tcl_CmdProc*)btree_first              },
+     { "btree_varint_test",        (Tcl_CmdProc*)btree_varint_test        },
+     { "btree_from_db",            (Tcl_CmdProc*)btree_from_db            },
+     { "btree_ismemdb",            (Tcl_CmdProc*)btree_ismemdb            },
+     { "btree_set_cache_size",     (Tcl_CmdProc*)btree_set_cache_size     }
+  };
+  int i;
+
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
+
+  return TCL_OK;
+}

+ 722 - 0
components/external/sqlite/test/test4.c

@@ -0,0 +1,722 @@
+/*
+** 2003 December 18
+**
+** 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 SQLite library in a multithreaded environment.
+*/
+#include "sqliteInt.h"
+#include "tcl.h"
+#if SQLITE_OS_UNIX && SQLITE_THREADSAFE
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <sched.h>
+#include <ctype.h>
+
+extern const char *sqlite3ErrName(int);
+
+/*
+** Each thread is controlled by an instance of the following
+** structure.
+*/
+typedef struct Thread Thread;
+struct Thread {
+  /* The first group of fields are writable by the master and read-only
+  ** to the thread. */
+  char *zFilename;       /* Name of database file */
+  void (*xOp)(Thread*);  /* next operation to do */
+  char *zArg;            /* argument usable by xOp */
+  int opnum;             /* Operation number */
+  int busy;              /* True if this thread is in use */
+
+  /* The next group of fields are writable by the thread but read-only to the
+  ** master. */
+  int completed;        /* Number of operations completed */
+  sqlite3 *db;           /* Open database */
+  sqlite3_stmt *pStmt;     /* Pending operation */
+  char *zErr;           /* operation error */
+  char *zStaticErr;     /* Static error message */
+  int rc;               /* operation return code */
+  int argc;             /* number of columns in result */
+  const char *argv[100];    /* result columns */
+  const char *colv[100];    /* result column names */
+};
+
+/*
+** There can be as many as 26 threads running at once.  Each is named
+** by a capital letter: A, B, C, ..., Y, Z.
+*/
+#define N_THREAD 26
+static Thread threadset[N_THREAD];
+
+
+/*
+** The main loop for a thread.  Threads use busy waiting. 
+*/
+static void *thread_main(void *pArg){
+  Thread *p = (Thread*)pArg;
+  if( p->db ){
+    sqlite3_close(p->db);
+  }
+  sqlite3_open(p->zFilename, &p->db);
+  if( SQLITE_OK!=sqlite3_errcode(p->db) ){
+    p->zErr = strdup(sqlite3_errmsg(p->db));
+    sqlite3_close(p->db);
+    p->db = 0;
+  }
+  p->pStmt = 0;
+  p->completed = 1;
+  while( p->opnum<=p->completed ) sched_yield();
+  while( p->xOp ){
+    if( p->zErr && p->zErr!=p->zStaticErr ){
+      sqlite3_free(p->zErr);
+      p->zErr = 0;
+    }
+    (*p->xOp)(p);
+    p->completed++;
+    while( p->opnum<=p->completed ) sched_yield();
+  }
+  if( p->pStmt ){
+    sqlite3_finalize(p->pStmt);
+    p->pStmt = 0;
+  }
+  if( p->db ){
+    sqlite3_close(p->db);
+    p->db = 0;
+  }
+  if( p->zErr && p->zErr!=p->zStaticErr ){
+    sqlite3_free(p->zErr);
+    p->zErr = 0;
+  }
+  p->completed++;
+#ifndef SQLITE_OMIT_DEPRECATED
+  sqlite3_thread_cleanup();
+#endif
+  return 0;
+}
+
+/*
+** Get a thread ID which is an upper case letter.  Return the index.
+** If the argument is not a valid thread ID put an error message in
+** the interpreter and return -1.
+*/
+static int parse_thread_id(Tcl_Interp *interp, const char *zArg){
+  if( zArg==0 || zArg[0]==0 || zArg[1]!=0 || !isupper((unsigned char)zArg[0]) ){
+    Tcl_AppendResult(interp, "thread ID must be an upper case letter", 0);
+    return -1;
+  }
+  return zArg[0] - 'A';
+}
+
+/*
+** Usage:    thread_create NAME  FILENAME
+**
+** NAME should be an upper case letter.  Start the thread running with
+** an open connection to the given database.
+*/
+static int tcl_thread_create(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  pthread_t x;
+  int rc;
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID FILENAME", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( threadset[i].busy ){
+    Tcl_AppendResult(interp, "thread ", argv[1], " is already running", 0);
+    return TCL_ERROR;
+  }
+  threadset[i].busy = 1;
+  sqlite3_free(threadset[i].zFilename);
+  threadset[i].zFilename = sqlite3_mprintf("%s", argv[2]);
+  threadset[i].opnum = 1;
+  threadset[i].completed = 0;
+  rc = pthread_create(&x, 0, thread_main, &threadset[i]);
+  if( rc ){
+    Tcl_AppendResult(interp, "failed to create the thread", 0);
+    sqlite3_free(threadset[i].zFilename);
+    threadset[i].busy = 0;
+    return TCL_ERROR;
+  }
+  pthread_detach(x);
+  return TCL_OK;
+}
+
+/*
+** Wait for a thread to reach its idle state.
+*/
+static void thread_wait(Thread *p){
+  while( p->opnum>p->completed ) sched_yield();
+}
+
+/*
+** Usage:  thread_wait ID
+**
+** Wait on thread ID to reach its idle state.
+*/
+static int tcl_thread_wait(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  return TCL_OK;
+}
+
+/*
+** Stop a thread.
+*/
+static void stop_thread(Thread *p){
+  thread_wait(p);
+  p->xOp = 0;
+  p->opnum++;
+  thread_wait(p);
+  sqlite3_free(p->zArg);
+  p->zArg = 0;
+  sqlite3_free(p->zFilename);
+  p->zFilename = 0;
+  p->busy = 0;
+}
+
+/*
+** Usage:  thread_halt ID
+**
+** Cause a thread to shut itself down.  Wait for the shutdown to be
+** completed.  If ID is "*" then stop all threads.
+*/
+static int tcl_thread_halt(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  if( argv[1][0]=='*' && argv[1][1]==0 ){
+    for(i=0; i<N_THREAD; i++){
+      if( threadset[i].busy ) stop_thread(&threadset[i]);
+    }
+  }else{
+    i = parse_thread_id(interp, argv[1]);
+    if( i<0 ) return TCL_ERROR;
+    if( !threadset[i].busy ){
+      Tcl_AppendResult(interp, "no such thread", 0);
+      return TCL_ERROR;
+    }
+    stop_thread(&threadset[i]);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_argc  ID
+**
+** Wait on the most recent thread_step to complete, then return the
+** number of columns in the result set.
+*/
+static int tcl_thread_argc(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  char zBuf[100];
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  sprintf(zBuf, "%d", threadset[i].argc);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_argv  ID   N
+**
+** Wait on the most recent thread_step to complete, then return the
+** value of the N-th columns in the result set.
+*/
+static int tcl_thread_argv(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  int n;
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID N", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
+  thread_wait(&threadset[i]);
+  if( n<0 || n>=threadset[i].argc ){
+    Tcl_AppendResult(interp, "column number out of range", 0);
+    return TCL_ERROR;
+  }
+  Tcl_AppendResult(interp, threadset[i].argv[n], 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_colname  ID   N
+**
+** Wait on the most recent thread_step to complete, then return the
+** name of the N-th columns in the result set.
+*/
+static int tcl_thread_colname(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  int n;
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID N", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
+  thread_wait(&threadset[i]);
+  if( n<0 || n>=threadset[i].argc ){
+    Tcl_AppendResult(interp, "column number out of range", 0);
+    return TCL_ERROR;
+  }
+  Tcl_AppendResult(interp, threadset[i].colv[n], 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_result  ID
+**
+** Wait on the most recent operation to complete, then return the
+** result code from that operation.
+*/
+static int tcl_thread_result(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  const char *zName;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  zName = sqlite3ErrName(threadset[i].rc);
+  Tcl_AppendResult(interp, zName, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_error  ID
+**
+** Wait on the most recent operation to complete, then return the
+** error string.
+*/
+static int tcl_thread_error(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  Tcl_AppendResult(interp, threadset[i].zErr, 0);
+  return TCL_OK;
+}
+
+/*
+** This procedure runs in the thread to compile an SQL statement.
+*/
+static void do_compile(Thread *p){
+  if( p->db==0 ){
+    p->zErr = p->zStaticErr = "no database is open";
+    p->rc = SQLITE_ERROR;
+    return;
+  }
+  if( p->pStmt ){
+    sqlite3_finalize(p->pStmt);
+    p->pStmt = 0;
+  }
+  p->rc = sqlite3_prepare(p->db, p->zArg, -1, &p->pStmt, 0);
+}
+
+/*
+** Usage: thread_compile ID SQL
+**
+** Compile a new virtual machine.
+*/
+static int tcl_thread_compile(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID SQL", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  threadset[i].xOp = do_compile;
+  sqlite3_free(threadset[i].zArg);
+  threadset[i].zArg = sqlite3_mprintf("%s", argv[2]);
+  threadset[i].opnum++;
+  return TCL_OK;
+}
+
+/*
+** This procedure runs in the thread to step the virtual machine.
+*/
+static void do_step(Thread *p){
+  int i;
+  if( p->pStmt==0 ){
+    p->zErr = p->zStaticErr = "no virtual machine available";
+    p->rc = SQLITE_ERROR;
+    return;
+  }
+  p->rc = sqlite3_step(p->pStmt);
+  if( p->rc==SQLITE_ROW ){
+    p->argc = sqlite3_column_count(p->pStmt);
+    for(i=0; i<sqlite3_data_count(p->pStmt); i++){
+      p->argv[i] = (char*)sqlite3_column_text(p->pStmt, i);
+    }
+    for(i=0; i<p->argc; i++){
+      p->colv[i] = sqlite3_column_name(p->pStmt, i);
+    }
+  }
+}
+
+/*
+** Usage: thread_step ID
+**
+** Advance the virtual machine by one step
+*/
+static int tcl_thread_step(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " IDL", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  threadset[i].xOp = do_step;
+  threadset[i].opnum++;
+  return TCL_OK;
+}
+
+/*
+** This procedure runs in the thread to finalize a virtual machine.
+*/
+static void do_finalize(Thread *p){
+  if( p->pStmt==0 ){
+    p->zErr = p->zStaticErr = "no virtual machine available";
+    p->rc = SQLITE_ERROR;
+    return;
+  }
+  p->rc = sqlite3_finalize(p->pStmt);
+  p->pStmt = 0;
+}
+
+/*
+** Usage: thread_finalize ID
+**
+** Finalize the virtual machine.
+*/
+static int tcl_thread_finalize(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " IDL", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  threadset[i].xOp = do_finalize;
+  sqlite3_free(threadset[i].zArg);
+  threadset[i].zArg = 0;
+  threadset[i].opnum++;
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_swap ID ID
+**
+** Interchange the sqlite* pointer between two threads.
+*/
+static int tcl_thread_swap(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i, j;
+  sqlite3 *temp;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID1 ID2", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  j = parse_thread_id(interp, argv[2]);
+  if( j<0 ) return TCL_ERROR;
+  if( !threadset[j].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[j]);
+  temp = threadset[i].db;
+  threadset[i].db = threadset[j].db;
+  threadset[j].db = temp;
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_db_get ID
+**
+** Return the database connection pointer for the given thread.  Then
+** remove the pointer from the thread itself.  Afterwards, the thread
+** can be stopped and the connection can be used by the main thread.
+*/
+static int tcl_thread_db_get(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  char zBuf[100];
+  extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  sqlite3TestMakePointerStr(interp, zBuf, threadset[i].db);
+  threadset[i].db = 0;
+  Tcl_AppendResult(interp, zBuf, (char*)0);
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_db_put ID DB
+**
+*/
+static int tcl_thread_db_put(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);
+  extern void *sqlite3TestTextToPtr(const char *);
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID DB", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  assert( !threadset[i].db );
+  threadset[i].db = (sqlite3*)sqlite3TestTextToPtr(argv[2]);
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_stmt_get ID
+**
+** Return the database stmt pointer for the given thread.  Then
+** remove the pointer from the thread itself. 
+*/
+static int tcl_thread_stmt_get(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  char zBuf[100];
+  extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  sqlite3TestMakePointerStr(interp, zBuf, threadset[i].pStmt);
+  threadset[i].pStmt = 0;
+  Tcl_AppendResult(interp, zBuf, (char*)0);
+  return TCL_OK;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest4_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_CmdProc *xProc;
+  } aCmd[] = {
+     { "thread_create",     (Tcl_CmdProc*)tcl_thread_create     },
+     { "thread_wait",       (Tcl_CmdProc*)tcl_thread_wait       },
+     { "thread_halt",       (Tcl_CmdProc*)tcl_thread_halt       },
+     { "thread_argc",       (Tcl_CmdProc*)tcl_thread_argc       },
+     { "thread_argv",       (Tcl_CmdProc*)tcl_thread_argv       },
+     { "thread_colname",    (Tcl_CmdProc*)tcl_thread_colname    },
+     { "thread_result",     (Tcl_CmdProc*)tcl_thread_result     },
+     { "thread_error",      (Tcl_CmdProc*)tcl_thread_error      },
+     { "thread_compile",    (Tcl_CmdProc*)tcl_thread_compile    },
+     { "thread_step",       (Tcl_CmdProc*)tcl_thread_step       },
+     { "thread_finalize",   (Tcl_CmdProc*)tcl_thread_finalize   },
+     { "thread_swap",       (Tcl_CmdProc*)tcl_thread_swap       },
+     { "thread_db_get",     (Tcl_CmdProc*)tcl_thread_db_get     },
+     { "thread_db_put",     (Tcl_CmdProc*)tcl_thread_db_put     },
+     { "thread_stmt_get",   (Tcl_CmdProc*)tcl_thread_stmt_get   },
+  };
+  int i;
+
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
+  return TCL_OK;
+}
+#else
+int Sqlitetest4_Init(Tcl_Interp *interp){ return TCL_OK; }
+#endif /* SQLITE_OS_UNIX */

+ 217 - 0
components/external/sqlite/test/test5.c

@@ -0,0 +1,217 @@
+/*
+** 2001 September 15
+**
+** 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 utf.c module in SQLite.  This code
+** is not included in the SQLite library.  It is used for automated
+** testing of the SQLite library. Specifically, the code in this file
+** is used for testing the SQLite routines for converting between
+** the various supported unicode encodings.
+*/
+#include "sqliteInt.h"
+#include "vdbeInt.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+
+/*
+** The first argument is a TCL UTF-8 string. Return the byte array
+** object with the encoded representation of the string, including
+** the NULL terminator.
+*/
+static int binarize(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int len;
+  char *bytes;
+  Tcl_Obj *pRet;
+  assert(objc==2);
+
+  bytes = Tcl_GetStringFromObj(objv[1], &len);
+  pRet = Tcl_NewByteArrayObj((u8*)bytes, len+1);
+  Tcl_SetObjResult(interp, pRet);
+  return TCL_OK;
+}
+
+/*
+** Usage: test_value_overhead <repeat-count> <do-calls>.
+**
+** This routine is used to test the overhead of calls to
+** sqlite3_value_text(), on a value that contains a UTF-8 string. The idea
+** is to figure out whether or not it is a problem to use sqlite3_value
+** structures with collation sequence functions.
+**
+** If <do-calls> is 0, then the calls to sqlite3_value_text() are not
+** actually made.
+*/
+static int test_value_overhead(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int do_calls;
+  int repeat_count;
+  int i;
+  Mem val;
+
+  if( objc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), " <repeat-count> <do-calls>", 0);
+    return TCL_ERROR;
+  }
+
+  if( Tcl_GetIntFromObj(interp, objv[1], &repeat_count) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &do_calls) ) return TCL_ERROR;
+
+  val.flags = MEM_Str|MEM_Term|MEM_Static;
+  val.z = "hello world";
+  val.type = SQLITE_TEXT;
+  val.enc = SQLITE_UTF8;
+
+  for(i=0; i<repeat_count; i++){
+    if( do_calls ){
+      sqlite3_value_text(&val);
+    }
+  }
+
+  return TCL_OK;
+}
+
+static u8 name_to_enc(Tcl_Interp *interp, Tcl_Obj *pObj){
+  struct EncName {
+    char *zName;
+    u8 enc;
+  } encnames[] = {
+    { "UTF8", SQLITE_UTF8 },
+    { "UTF16LE", SQLITE_UTF16LE },
+    { "UTF16BE", SQLITE_UTF16BE },
+    { "UTF16", SQLITE_UTF16 },
+    { 0, 0 }
+  };
+  struct EncName *pEnc;
+  char *z = Tcl_GetString(pObj);
+  for(pEnc=&encnames[0]; pEnc->zName; pEnc++){
+    if( 0==sqlite3StrICmp(z, pEnc->zName) ){
+      break;
+    }
+  }
+  if( !pEnc->enc ){
+    Tcl_AppendResult(interp, "No such encoding: ", z, 0);
+  }
+  if( pEnc->enc==SQLITE_UTF16 ){
+    return SQLITE_UTF16NATIVE;
+  }
+  return pEnc->enc;
+}
+
+/*
+** Usage:   test_translate <string/blob> <from enc> <to enc> ?<transient>?
+**
+*/
+static int test_translate(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  u8 enc_from;
+  u8 enc_to;
+  sqlite3_value *pVal;
+
+  char *z;
+  int len;
+  void (*xDel)(void *p) = SQLITE_STATIC;
+
+  if( objc!=4 && objc!=5 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"",
+        Tcl_GetStringFromObj(objv[0], 0), 
+        " <string/blob> <from enc> <to enc>", 0
+    );
+    return TCL_ERROR;
+  }
+  if( objc==5 ){
+    xDel = sqlite3_free;
+  }
+
+  enc_from = name_to_enc(interp, objv[2]);
+  if( !enc_from ) return TCL_ERROR;
+  enc_to = name_to_enc(interp, objv[3]);
+  if( !enc_to ) return TCL_ERROR;
+
+  pVal = sqlite3ValueNew(0);
+
+  if( enc_from==SQLITE_UTF8 ){
+    z = Tcl_GetString(objv[1]);
+    if( objc==5 ){
+      z = sqlite3_mprintf("%s", z);
+    }
+    sqlite3ValueSetStr(pVal, -1, z, enc_from, xDel);
+  }else{
+    z = (char*)Tcl_GetByteArrayFromObj(objv[1], &len);
+    if( objc==5 ){
+      char *zTmp = z;
+      z = sqlite3_malloc(len);
+      memcpy(z, zTmp, len);
+    }
+    sqlite3ValueSetStr(pVal, -1, z, enc_from, xDel);
+  }
+
+  z = (char *)sqlite3ValueText(pVal, enc_to);
+  len = sqlite3ValueBytes(pVal, enc_to) + (enc_to==SQLITE_UTF8?1:2);
+  Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((u8*)z, len));
+
+  sqlite3ValueFree(pVal);
+
+  return TCL_OK;
+}
+
+/*
+** Usage: translate_selftest
+**
+** Call sqlite3UtfSelfTest() to run the internal tests for unicode
+** translation. If there is a problem an assert() will fail.
+**/
+void sqlite3UtfSelfTest(void);
+static int test_translate_selftest(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_UTF16
+  sqlite3UtfSelfTest();
+#endif
+  return SQLITE_OK;
+}
+
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest5_Init(Tcl_Interp *interp){
+  static struct {
+    char *zName;
+    Tcl_ObjCmdProc *xProc;
+  } aCmd[] = {
+    { "binarize",                (Tcl_ObjCmdProc*)binarize },
+    { "test_value_overhead",     (Tcl_ObjCmdProc*)test_value_overhead },
+    { "test_translate",          (Tcl_ObjCmdProc*)test_translate     },
+    { "translate_selftest",      (Tcl_ObjCmdProc*)test_translate_selftest},
+  };
+  int i;
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
+  return SQLITE_OK;
+}

+ 1020 - 0
components/external/sqlite/test/test6.c

@@ -0,0 +1,1020 @@
+/*
+** 2004 May 22
+**
+** 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 contains code that modified the OS layer in order to simulate
+** the effect on the database file of an OS crash or power failure.  This
+** is used to test the ability of SQLite to recover from those situations.
+*/
+#if SQLITE_TEST          /* This file is used for testing only */
+#include "sqliteInt.h"
+#include "tcl.h"
+
+#ifndef SQLITE_OMIT_DISKIO  /* This file is a no-op if disk I/O is disabled */
+
+/* #define TRACE_CRASHTEST */
+
+typedef struct CrashFile CrashFile;
+typedef struct CrashGlobal CrashGlobal;
+typedef struct WriteBuffer WriteBuffer;
+
+/*
+** Method:
+**
+**   This layer is implemented as a wrapper around the "real" 
+**   sqlite3_file object for the host system. Each time data is 
+**   written to the file object, instead of being written to the
+**   underlying file, the write operation is stored in an in-memory 
+**   structure (type WriteBuffer). This structure is placed at the
+**   end of a global ordered list (the write-list).
+**
+**   When data is read from a file object, the requested region is
+**   first retrieved from the real file. The write-list is then 
+**   traversed and data copied from any overlapping WriteBuffer 
+**   structures to the output buffer. i.e. a read() operation following
+**   one or more write() operations works as expected, even if no
+**   data has actually been written out to the real file.
+**
+**   When a fsync() operation is performed, an operating system crash 
+**   may be simulated, in which case exit(-1) is called (the call to 
+**   xSync() never returns). Whether or not a crash is simulated,
+**   the data associated with a subset of the WriteBuffer structures 
+**   stored in the write-list is written to the real underlying files 
+**   and the entries removed from the write-list. If a crash is simulated,
+**   a subset of the buffers may be corrupted before the data is written.
+**
+**   The exact subset of the write-list written and/or corrupted is
+**   determined by the simulated device characteristics and sector-size.
+**
+** "Normal" mode:
+**
+**   Normal mode is used when the simulated device has none of the
+**   SQLITE_IOCAP_XXX flags set.
+**
+**   In normal mode, if the fsync() is not a simulated crash, the 
+**   write-list is traversed from beginning to end. Each WriteBuffer
+**   structure associated with the file handle used to call xSync()
+**   is written to the real file and removed from the write-list.
+**
+**   If a crash is simulated, one of the following takes place for 
+**   each WriteBuffer in the write-list, regardless of which 
+**   file-handle it is associated with:
+**
+**     1. The buffer is correctly written to the file, just as if
+**        a crash were not being simulated.
+**
+**     2. Nothing is done.
+**
+**     3. Garbage data is written to all sectors of the file that 
+**        overlap the region specified by the WriteBuffer. Or garbage
+**        data is written to some contiguous section within the 
+**        overlapped sectors.
+**
+** Device Characteristic flag handling:
+**
+**   If the IOCAP_ATOMIC flag is set, then option (3) above is 
+**   never selected.
+**
+**   If the IOCAP_ATOMIC512 flag is set, and the WriteBuffer represents
+**   an aligned write() of an integer number of 512 byte regions, then
+**   option (3) above is never selected. Instead, each 512 byte region
+**   is either correctly written or left completely untouched. Similar
+**   logic governs the behavior if any of the other ATOMICXXX flags
+**   is set.
+**
+**   If either the IOCAP_SAFEAPPEND or IOCAP_SEQUENTIAL flags are set
+**   and a crash is being simulated, then an entry of the write-list is
+**   selected at random. Everything in the list after the selected entry 
+**   is discarded before processing begins.
+**
+**   If IOCAP_SEQUENTIAL is set and a crash is being simulated, option 
+**   (1) is selected for all write-list entries except the last. If a 
+**   crash is not being simulated, then all entries in the write-list
+**   that occur before at least one write() on the file-handle specified
+**   as part of the xSync() are written to their associated real files.
+**
+**   If IOCAP_SAFEAPPEND is set and the first byte written by the write()
+**   operation is one byte past the current end of the file, then option
+**   (1) is always selected.
+*/
+
+/*
+** Each write operation in the write-list is represented by an instance
+** of the following structure.
+**
+** If zBuf is 0, then this structure represents a call to xTruncate(), 
+** not xWrite(). In that case, iOffset is the size that the file is
+** truncated to.
+*/
+struct WriteBuffer {
+  i64 iOffset;                 /* Byte offset of the start of this write() */
+  int nBuf;                    /* Number of bytes written */
+  u8 *zBuf;                    /* Pointer to copy of written data */
+  CrashFile *pFile;            /* File this write() applies to */
+
+  WriteBuffer *pNext;          /* Next in CrashGlobal.pWriteList */
+};
+
+struct CrashFile {
+  const sqlite3_io_methods *pMethod;   /* Must be first */
+  sqlite3_file *pRealFile;             /* Underlying "real" file handle */
+  char *zName;
+  int flags;                           /* Flags the file was opened with */
+
+  /* Cache of the entire file. This is used to speed up OsRead() and 
+  ** OsFileSize() calls. Although both could be done by traversing the
+  ** write-list, in practice this is impractically slow.
+  */
+  u8 *zData;                           /* Buffer containing file contents */
+  int nData;                           /* Size of buffer allocated at zData */
+  i64 iSize;                           /* Size of file in bytes */
+};
+
+struct CrashGlobal {
+  WriteBuffer *pWriteList;     /* Head of write-list */
+  WriteBuffer *pWriteListEnd;  /* End of write-list */
+
+  int iSectorSize;             /* Value of simulated sector size */
+  int iDeviceCharacteristics;  /* Value of simulated device characteristics */
+
+  int iCrash;                  /* Crash on the iCrash'th call to xSync() */
+  char zCrashFile[500];        /* Crash during an xSync() on this file */ 
+};
+
+static CrashGlobal g = {0, 0, SQLITE_DEFAULT_SECTOR_SIZE, 0, 0};
+
+/*
+** Set this global variable to 1 to enable crash testing.
+*/
+static int sqlite3CrashTestEnable = 0;
+
+static void *crash_malloc(int nByte){
+  return (void *)Tcl_Alloc((size_t)nByte);
+}
+static void crash_free(void *p){
+  Tcl_Free(p);
+}
+static void *crash_realloc(void *p, int n){
+  return (void *)Tcl_Realloc(p, (size_t)n);
+}
+
+/*
+** Wrapper around the sqlite3OsWrite() function that avoids writing to the
+** 512 byte block begining at offset PENDING_BYTE.
+*/
+static int writeDbFile(CrashFile *p, u8 *z, i64 iAmt, i64 iOff){
+  int rc = SQLITE_OK;
+  int iSkip = 0;
+  if( iOff==PENDING_BYTE && (p->flags&SQLITE_OPEN_MAIN_DB) ){
+    iSkip = 512;
+  }
+  if( (iAmt-iSkip)>0 ){
+    rc = sqlite3OsWrite(p->pRealFile, &z[iSkip], (int)(iAmt-iSkip), iOff+iSkip);
+  }
+  return rc;
+}
+
+/*
+** Flush the write-list as if xSync() had been called on file handle
+** pFile. If isCrash is true, simulate a crash.
+*/
+static int writeListSync(CrashFile *pFile, int isCrash){
+  int rc = SQLITE_OK;
+  int iDc = g.iDeviceCharacteristics;
+
+  WriteBuffer *pWrite;
+  WriteBuffer **ppPtr;
+
+  /* If this is not a crash simulation, set pFinal to point to the 
+  ** last element of the write-list that is associated with file handle
+  ** pFile.
+  **
+  ** If this is a crash simulation, set pFinal to an arbitrarily selected
+  ** element of the write-list.
+  */
+  WriteBuffer *pFinal = 0;
+  if( !isCrash ){
+    for(pWrite=g.pWriteList; pWrite; pWrite=pWrite->pNext){
+      if( pWrite->pFile==pFile ){
+        pFinal = pWrite;
+      }
+    }
+  }else if( iDc&(SQLITE_IOCAP_SEQUENTIAL|SQLITE_IOCAP_SAFE_APPEND) ){
+    int nWrite = 0;
+    int iFinal;
+    for(pWrite=g.pWriteList; pWrite; pWrite=pWrite->pNext) nWrite++;
+    sqlite3_randomness(sizeof(int), &iFinal);
+    iFinal = ((iFinal<0)?-1*iFinal:iFinal)%nWrite;
+    for(pWrite=g.pWriteList; iFinal>0; pWrite=pWrite->pNext) iFinal--;
+    pFinal = pWrite;
+  }
+
+#ifdef TRACE_CRASHTEST
+  printf("Sync %s (is %s crash)\n", pFile->zName, (isCrash?"a":"not a"));
+#endif
+
+  ppPtr = &g.pWriteList;
+  for(pWrite=*ppPtr; rc==SQLITE_OK && pWrite; pWrite=*ppPtr){
+    sqlite3_file *pRealFile = pWrite->pFile->pRealFile;
+
+    /* (eAction==1)      -> write block out normally,
+    ** (eAction==2)      -> do nothing,
+    ** (eAction==3)      -> trash sectors.
+    */
+    int eAction = 0;
+    if( !isCrash ){
+      eAction = 2;
+      if( (pWrite->pFile==pFile || iDc&SQLITE_IOCAP_SEQUENTIAL) ){
+        eAction = 1;
+      }
+    }else{
+      char random;
+      sqlite3_randomness(1, &random);
+
+      /* Do not select option 3 (sector trashing) if the IOCAP_ATOMIC flag 
+      ** is set or this is an OsTruncate(), not an Oswrite().
+      */
+      if( (iDc&SQLITE_IOCAP_ATOMIC) || (pWrite->zBuf==0) ){
+        random &= 0x01;
+      }
+
+      /* If IOCAP_SEQUENTIAL is set and this is not the final entry
+      ** in the truncated write-list, always select option 1 (write
+      ** out correctly).
+      */
+      if( (iDc&SQLITE_IOCAP_SEQUENTIAL && pWrite!=pFinal) ){
+        random = 0;
+      }
+
+      /* If IOCAP_SAFE_APPEND is set and this OsWrite() operation is
+      ** an append (first byte of the written region is 1 byte past the
+      ** current EOF), always select option 1 (write out correctly).
+      */
+      if( iDc&SQLITE_IOCAP_SAFE_APPEND && pWrite->zBuf ){
+        i64 iSize;
+        sqlite3OsFileSize(pRealFile, &iSize);
+        if( iSize==pWrite->iOffset ){
+          random = 0;
+        }
+      }
+
+      if( (random&0x06)==0x06 ){
+        eAction = 3;
+      }else{
+        eAction = ((random&0x01)?2:1);
+      }
+    }
+
+    switch( eAction ){
+      case 1: {               /* Write out correctly */
+        if( pWrite->zBuf ){
+          rc = writeDbFile(
+              pWrite->pFile, pWrite->zBuf, pWrite->nBuf, pWrite->iOffset
+          );
+        }else{
+          rc = sqlite3OsTruncate(pRealFile, pWrite->iOffset);
+        }
+        *ppPtr = pWrite->pNext;
+#ifdef TRACE_CRASHTEST
+        if( isCrash ){
+          printf("Writing %d bytes @ %d (%s)\n", 
+            pWrite->nBuf, (int)pWrite->iOffset, pWrite->pFile->zName
+          );
+        }
+#endif
+        crash_free(pWrite);
+        break;
+      }
+      case 2: {               /* Do nothing */
+        ppPtr = &pWrite->pNext;
+#ifdef TRACE_CRASHTEST
+        if( isCrash ){
+          printf("Omiting %d bytes @ %d (%s)\n", 
+            pWrite->nBuf, (int)pWrite->iOffset, pWrite->pFile->zName
+          );
+        }
+#endif
+        break;
+      }
+      case 3: {               /* Trash sectors */
+        u8 *zGarbage;
+        int iFirst = (int)(pWrite->iOffset/g.iSectorSize);
+        int iLast = (int)((pWrite->iOffset+pWrite->nBuf-1)/g.iSectorSize);
+
+        assert(pWrite->zBuf);
+
+#ifdef TRACE_CRASHTEST
+        printf("Trashing %d sectors @ %lld (sector %d) (%s)\n", 
+            1+iLast-iFirst, pWrite->iOffset, iFirst, pWrite->pFile->zName
+        );
+#endif
+
+        zGarbage = crash_malloc(g.iSectorSize);
+        if( zGarbage ){
+          sqlite3_int64 i;
+          for(i=iFirst; rc==SQLITE_OK && i<=iLast; i++){
+            sqlite3_randomness(g.iSectorSize, zGarbage); 
+            rc = writeDbFile(
+              pWrite->pFile, zGarbage, g.iSectorSize, i*g.iSectorSize
+            );
+          }
+          crash_free(zGarbage);
+        }else{
+          rc = SQLITE_NOMEM;
+        }
+
+        ppPtr = &pWrite->pNext;
+        break;
+      }
+
+      default:
+        assert(!"Cannot happen");
+    }
+
+    if( pWrite==pFinal ) break;
+  }
+
+  if( rc==SQLITE_OK && isCrash ){
+    exit(-1);
+  }
+
+  for(pWrite=g.pWriteList; pWrite && pWrite->pNext; pWrite=pWrite->pNext);
+  g.pWriteListEnd = pWrite;
+
+  return rc;
+}
+
+/*
+** Add an entry to the end of the write-list.
+*/
+static int writeListAppend(
+  sqlite3_file *pFile,
+  sqlite3_int64 iOffset,
+  const u8 *zBuf,
+  int nBuf
+){
+  WriteBuffer *pNew;
+
+  assert((zBuf && nBuf) || (!nBuf && !zBuf));
+
+  pNew = (WriteBuffer *)crash_malloc(sizeof(WriteBuffer) + nBuf);
+  if( pNew==0 ){
+    fprintf(stderr, "out of memory in the crash simulator\n");
+  }
+  memset(pNew, 0, sizeof(WriteBuffer)+nBuf);
+  pNew->iOffset = iOffset;
+  pNew->nBuf = nBuf;
+  pNew->pFile = (CrashFile *)pFile;
+  if( zBuf ){
+    pNew->zBuf = (u8 *)&pNew[1];
+    memcpy(pNew->zBuf, zBuf, nBuf);
+  }
+
+  if( g.pWriteList ){
+    assert(g.pWriteListEnd);
+    g.pWriteListEnd->pNext = pNew;
+  }else{
+    g.pWriteList = pNew;
+  }
+  g.pWriteListEnd = pNew;
+  
+  return SQLITE_OK;
+}
+
+/*
+** Close a crash-file.
+*/
+static int cfClose(sqlite3_file *pFile){
+  CrashFile *pCrash = (CrashFile *)pFile;
+  writeListSync(pCrash, 0);
+  sqlite3OsClose(pCrash->pRealFile);
+  return SQLITE_OK;
+}
+
+/*
+** Read data from a crash-file.
+*/
+static int cfRead(
+  sqlite3_file *pFile, 
+  void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  CrashFile *pCrash = (CrashFile *)pFile;
+
+  /* Check the file-size to see if this is a short-read */
+  if( pCrash->iSize<(iOfst+iAmt) ){
+    return SQLITE_IOERR_SHORT_READ;
+  }
+
+  memcpy(zBuf, &pCrash->zData[iOfst], iAmt);
+  return SQLITE_OK;
+}
+
+/*
+** Write data to a crash-file.
+*/
+static int cfWrite(
+  sqlite3_file *pFile, 
+  const void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  CrashFile *pCrash = (CrashFile *)pFile;
+  if( iAmt+iOfst>pCrash->iSize ){
+    pCrash->iSize = (int)(iAmt+iOfst);
+  }
+  while( pCrash->iSize>pCrash->nData ){
+    u8 *zNew;
+    int nNew = (pCrash->nData*2) + 4096;
+    zNew = crash_realloc(pCrash->zData, nNew);
+    if( !zNew ){
+      return SQLITE_NOMEM;
+    }
+    memset(&zNew[pCrash->nData], 0, nNew-pCrash->nData);
+    pCrash->nData = nNew;
+    pCrash->zData = zNew;
+  }
+  memcpy(&pCrash->zData[iOfst], zBuf, iAmt);
+  return writeListAppend(pFile, iOfst, zBuf, iAmt);
+}
+
+/*
+** Truncate a crash-file.
+*/
+static int cfTruncate(sqlite3_file *pFile, sqlite_int64 size){
+  CrashFile *pCrash = (CrashFile *)pFile;
+  assert(size>=0);
+  if( pCrash->iSize>size ){
+    pCrash->iSize = (int)size;
+  }
+  return writeListAppend(pFile, size, 0, 0);
+}
+
+/*
+** Sync a crash-file.
+*/
+static int cfSync(sqlite3_file *pFile, int flags){
+  CrashFile *pCrash = (CrashFile *)pFile;
+  int isCrash = 0;
+
+  const char *zName = pCrash->zName;
+  const char *zCrashFile = g.zCrashFile;
+  int nName = (int)strlen(zName);
+  int nCrashFile = (int)strlen(zCrashFile);
+
+  if( nCrashFile>0 && zCrashFile[nCrashFile-1]=='*' ){
+    nCrashFile--;
+    if( nName>nCrashFile ) nName = nCrashFile;
+  }
+
+#ifdef TRACE_CRASHTEST
+  printf("cfSync(): nName = %d, nCrashFile = %d, zName = %s, zCrashFile = %s\n",
+         nName, nCrashFile, zName, zCrashFile);
+#endif
+
+  if( nName==nCrashFile && 0==memcmp(zName, zCrashFile, nName) ){
+#ifdef TRACE_CRASHTEST
+    printf("cfSync(): name matched, g.iCrash = %d\n", g.iCrash);
+#endif
+    if( (--g.iCrash)==0 ) isCrash = 1;
+  }
+
+  return writeListSync(pCrash, isCrash);
+}
+
+/*
+** Return the current file-size of the crash-file.
+*/
+static int cfFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+  CrashFile *pCrash = (CrashFile *)pFile;
+  *pSize = (i64)pCrash->iSize;
+  return SQLITE_OK;
+}
+
+/*
+** Calls related to file-locks are passed on to the real file handle.
+*/
+static int cfLock(sqlite3_file *pFile, int eLock){
+  return sqlite3OsLock(((CrashFile *)pFile)->pRealFile, eLock);
+}
+static int cfUnlock(sqlite3_file *pFile, int eLock){
+  return sqlite3OsUnlock(((CrashFile *)pFile)->pRealFile, eLock);
+}
+static int cfCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+  return sqlite3OsCheckReservedLock(((CrashFile *)pFile)->pRealFile, pResOut);
+}
+static int cfFileControl(sqlite3_file *pFile, int op, void *pArg){
+  if( op==SQLITE_FCNTL_SIZE_HINT ){
+    CrashFile *pCrash = (CrashFile *)pFile;
+    i64 nByte = *(i64 *)pArg;
+    if( nByte>pCrash->iSize ){
+      if( SQLITE_OK==writeListAppend(pFile, nByte, 0, 0) ){
+        pCrash->iSize = (int)nByte;
+      }
+    }
+    return SQLITE_OK;
+  }
+  return sqlite3OsFileControl(((CrashFile *)pFile)->pRealFile, op, pArg);
+}
+
+/*
+** The xSectorSize() and xDeviceCharacteristics() functions return
+** the global values configured by the [sqlite_crashparams] tcl
+*  interface.
+*/
+static int cfSectorSize(sqlite3_file *pFile){
+  return g.iSectorSize;
+}
+static int cfDeviceCharacteristics(sqlite3_file *pFile){
+  return g.iDeviceCharacteristics;
+}
+
+/*
+** Pass-throughs for WAL support.
+*/
+static int cfShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
+  return sqlite3OsShmLock(((CrashFile*)pFile)->pRealFile, ofst, n, flags);
+}
+static void cfShmBarrier(sqlite3_file *pFile){
+  sqlite3OsShmBarrier(((CrashFile*)pFile)->pRealFile);
+}
+static int cfShmUnmap(sqlite3_file *pFile, int delFlag){
+  return sqlite3OsShmUnmap(((CrashFile*)pFile)->pRealFile, delFlag);
+}
+static int cfShmMap(
+  sqlite3_file *pFile,            /* Handle open on database file */
+  int iRegion,                    /* Region to retrieve */
+  int sz,                         /* Size of regions */
+  int w,                          /* True to extend file if necessary */
+  void volatile **pp              /* OUT: Mapped memory */
+){
+  return sqlite3OsShmMap(((CrashFile*)pFile)->pRealFile, iRegion, sz, w, pp);
+}
+
+static const sqlite3_io_methods CrashFileVtab = {
+  2,                            /* iVersion */
+  cfClose,                      /* xClose */
+  cfRead,                       /* xRead */
+  cfWrite,                      /* xWrite */
+  cfTruncate,                   /* xTruncate */
+  cfSync,                       /* xSync */
+  cfFileSize,                   /* xFileSize */
+  cfLock,                       /* xLock */
+  cfUnlock,                     /* xUnlock */
+  cfCheckReservedLock,          /* xCheckReservedLock */
+  cfFileControl,                /* xFileControl */
+  cfSectorSize,                 /* xSectorSize */
+  cfDeviceCharacteristics,      /* xDeviceCharacteristics */
+  cfShmMap,                     /* xShmMap */
+  cfShmLock,                    /* xShmLock */
+  cfShmBarrier,                 /* xShmBarrier */
+  cfShmUnmap                    /* xShmUnmap */
+};
+
+/*
+** Application data for the crash VFS
+*/
+struct crashAppData {
+  sqlite3_vfs *pOrig;                   /* Wrapped vfs structure */
+};
+
+/*
+** Open a crash-file file handle.
+**
+** The caller will have allocated pVfs->szOsFile bytes of space
+** at pFile. This file uses this space for the CrashFile structure
+** and allocates space for the "real" file structure using 
+** sqlite3_malloc(). The assumption here is (pVfs->szOsFile) is
+** equal or greater than sizeof(CrashFile).
+*/
+static int cfOpen(
+  sqlite3_vfs *pCfVfs,
+  const char *zName,
+  sqlite3_file *pFile,
+  int flags,
+  int *pOutFlags
+){
+  sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData;
+  int rc;
+  CrashFile *pWrapper = (CrashFile *)pFile;
+  sqlite3_file *pReal = (sqlite3_file*)&pWrapper[1];
+
+  memset(pWrapper, 0, sizeof(CrashFile));
+  rc = sqlite3OsOpen(pVfs, zName, pReal, flags, pOutFlags);
+
+  if( rc==SQLITE_OK ){
+    i64 iSize;
+    pWrapper->pMethod = &CrashFileVtab;
+    pWrapper->zName = (char *)zName;
+    pWrapper->pRealFile = pReal;
+    rc = sqlite3OsFileSize(pReal, &iSize);
+    pWrapper->iSize = (int)iSize;
+    pWrapper->flags = flags;
+  }
+  if( rc==SQLITE_OK ){
+    pWrapper->nData = (4096 + pWrapper->iSize);
+    pWrapper->zData = crash_malloc(pWrapper->nData);
+    if( pWrapper->zData ){
+      /* os_unix.c contains an assert() that fails if the caller attempts
+      ** to read data from the 512-byte locking region of a file opened
+      ** with the SQLITE_OPEN_MAIN_DB flag. This region of a database file
+      ** never contains valid data anyhow. So avoid doing such a read here.
+      **
+      ** UPDATE: It also contains an assert() verifying that each call
+      ** to the xRead() method reads less than 128KB of data.
+      */
+      const int isDb = (flags&SQLITE_OPEN_MAIN_DB);
+      i64 iOff;
+
+      memset(pWrapper->zData, 0, pWrapper->nData);
+      for(iOff=0; iOff<pWrapper->iSize; iOff += 512){
+        int nRead = pWrapper->iSize - (int)iOff;
+        if( nRead>512 ) nRead = 512;
+        if( isDb && iOff==PENDING_BYTE ) continue;
+        rc = sqlite3OsRead(pReal, &pWrapper->zData[iOff], nRead, iOff);
+      }
+    }else{
+      rc = SQLITE_NOMEM;
+    }
+  }
+  if( rc!=SQLITE_OK && pWrapper->pMethod ){
+    sqlite3OsClose(pFile);
+  }
+  return rc;
+}
+
+static int cfDelete(sqlite3_vfs *pCfVfs, const char *zPath, int dirSync){
+  sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData;
+  return pVfs->xDelete(pVfs, zPath, dirSync);
+}
+static int cfAccess(
+  sqlite3_vfs *pCfVfs, 
+  const char *zPath, 
+  int flags, 
+  int *pResOut
+){
+  sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData;
+  return pVfs->xAccess(pVfs, zPath, flags, pResOut);
+}
+static int cfFullPathname(
+  sqlite3_vfs *pCfVfs, 
+  const char *zPath, 
+  int nPathOut,
+  char *zPathOut
+){
+  sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData;
+  return pVfs->xFullPathname(pVfs, zPath, nPathOut, zPathOut);
+}
+static void *cfDlOpen(sqlite3_vfs *pCfVfs, const char *zPath){
+  sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData;
+  return pVfs->xDlOpen(pVfs, zPath);
+}
+static void cfDlError(sqlite3_vfs *pCfVfs, int nByte, char *zErrMsg){
+  sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData;
+  pVfs->xDlError(pVfs, nByte, zErrMsg);
+}
+static void (*cfDlSym(sqlite3_vfs *pCfVfs, void *pH, const char *zSym))(void){
+  sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData;
+  return pVfs->xDlSym(pVfs, pH, zSym);
+}
+static void cfDlClose(sqlite3_vfs *pCfVfs, void *pHandle){
+  sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData;
+  pVfs->xDlClose(pVfs, pHandle);
+}
+static int cfRandomness(sqlite3_vfs *pCfVfs, int nByte, char *zBufOut){
+  sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData;
+  return pVfs->xRandomness(pVfs, nByte, zBufOut);
+}
+static int cfSleep(sqlite3_vfs *pCfVfs, int nMicro){
+  sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData;
+  return pVfs->xSleep(pVfs, nMicro);
+}
+static int cfCurrentTime(sqlite3_vfs *pCfVfs, double *pTimeOut){
+  sqlite3_vfs *pVfs = (sqlite3_vfs *)pCfVfs->pAppData;
+  return pVfs->xCurrentTime(pVfs, pTimeOut);
+}
+
+static int processDevSymArgs(
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[],
+  int *piDeviceChar,
+  int *piSectorSize
+){
+  struct DeviceFlag {
+    char *zName;
+    int iValue;
+  } aFlag[] = {
+    { "atomic",              SQLITE_IOCAP_ATOMIC                },
+    { "atomic512",           SQLITE_IOCAP_ATOMIC512             },
+    { "atomic1k",            SQLITE_IOCAP_ATOMIC1K              },
+    { "atomic2k",            SQLITE_IOCAP_ATOMIC2K              },
+    { "atomic4k",            SQLITE_IOCAP_ATOMIC4K              },
+    { "atomic8k",            SQLITE_IOCAP_ATOMIC8K              },
+    { "atomic16k",           SQLITE_IOCAP_ATOMIC16K             },
+    { "atomic32k",           SQLITE_IOCAP_ATOMIC32K             },
+    { "atomic64k",           SQLITE_IOCAP_ATOMIC64K             },
+    { "sequential",          SQLITE_IOCAP_SEQUENTIAL            },
+    { "safe_append",         SQLITE_IOCAP_SAFE_APPEND           },
+    { "powersafe_overwrite", SQLITE_IOCAP_POWERSAFE_OVERWRITE   },
+    { 0, 0 }
+  };
+
+  int i;
+  int iDc = 0;
+  int iSectorSize = 0;
+  int setSectorsize = 0;
+  int setDeviceChar = 0;
+
+  for(i=0; i<objc; i+=2){
+    int nOpt;
+    char *zOpt = Tcl_GetStringFromObj(objv[i], &nOpt);
+
+    if( (nOpt>11 || nOpt<2 || strncmp("-sectorsize", zOpt, nOpt)) 
+     && (nOpt>16 || nOpt<2 || strncmp("-characteristics", zOpt, nOpt))
+    ){
+      Tcl_AppendResult(interp, 
+        "Bad option: \"", zOpt, 
+        "\" - must be \"-characteristics\" or \"-sectorsize\"", 0
+      );
+      return TCL_ERROR;
+    }
+    if( i==objc-1 ){
+      Tcl_AppendResult(interp, "Option requires an argument: \"", zOpt, "\"",0);
+      return TCL_ERROR;
+    }
+
+    if( zOpt[1]=='s' ){
+      if( Tcl_GetIntFromObj(interp, objv[i+1], &iSectorSize) ){
+        return TCL_ERROR;
+      }
+      setSectorsize = 1;
+    }else{
+      int j;
+      Tcl_Obj **apObj;
+      int nObj;
+      if( Tcl_ListObjGetElements(interp, objv[i+1], &nObj, &apObj) ){
+        return TCL_ERROR;
+      }
+      for(j=0; j<nObj; j++){
+        int rc;
+        int iChoice;
+        Tcl_Obj *pFlag = Tcl_DuplicateObj(apObj[j]);
+        Tcl_IncrRefCount(pFlag);
+        Tcl_UtfToLower(Tcl_GetString(pFlag));
+ 
+        rc = Tcl_GetIndexFromObjStruct(
+            interp, pFlag, aFlag, sizeof(aFlag[0]), "no such flag", 0, &iChoice
+        );
+        Tcl_DecrRefCount(pFlag);
+        if( rc ){
+          return TCL_ERROR;
+        }
+
+        iDc |= aFlag[iChoice].iValue;
+      }
+      setDeviceChar = 1;
+    }
+  }
+
+  if( setDeviceChar ){
+    *piDeviceChar = iDc;
+  }
+  if( setSectorsize ){
+    *piSectorSize = iSectorSize;
+  }
+
+  return TCL_OK;
+}
+
+/*
+** tclcmd:   sqlite_crash_enable ENABLE
+**
+** Parameter ENABLE must be a boolean value. If true, then the "crash"
+** vfs is added to the system. If false, it is removed.
+*/
+static int crashEnableCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int isEnable;
+  static sqlite3_vfs crashVfs = {
+    2,                  /* iVersion */
+    0,                  /* szOsFile */
+    0,                  /* mxPathname */
+    0,                  /* pNext */
+    "crash",            /* zName */
+    0,                  /* pAppData */
+  
+    cfOpen,               /* xOpen */
+    cfDelete,             /* xDelete */
+    cfAccess,             /* xAccess */
+    cfFullPathname,       /* xFullPathname */
+    cfDlOpen,             /* xDlOpen */
+    cfDlError,            /* xDlError */
+    cfDlSym,              /* xDlSym */
+    cfDlClose,            /* xDlClose */
+    cfRandomness,         /* xRandomness */
+    cfSleep,              /* xSleep */
+    cfCurrentTime,        /* xCurrentTime */
+    0,                    /* xGetlastError */
+    0,                    /* xCurrentTimeInt64 */
+  };
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "ENABLE");
+    return TCL_ERROR;
+  }
+
+  if( Tcl_GetBooleanFromObj(interp, objv[1], &isEnable) ){
+    return TCL_ERROR;
+  }
+
+  if( (isEnable && crashVfs.pAppData) || (!isEnable && !crashVfs.pAppData) ){
+    return TCL_OK;
+  }
+
+  if( crashVfs.pAppData==0 ){
+    sqlite3_vfs *pOriginalVfs = sqlite3_vfs_find(0);
+    crashVfs.mxPathname = pOriginalVfs->mxPathname;
+    crashVfs.pAppData = (void *)pOriginalVfs;
+    crashVfs.szOsFile = sizeof(CrashFile) + pOriginalVfs->szOsFile;
+    sqlite3_vfs_register(&crashVfs, 0);
+  }else{
+    crashVfs.pAppData = 0;
+    sqlite3_vfs_unregister(&crashVfs);
+  }
+
+  return TCL_OK;
+}
+
+/*
+** tclcmd:   sqlite_crashparams ?OPTIONS? DELAY CRASHFILE
+**
+** This procedure implements a TCL command that enables crash testing
+** in testfixture.  Once enabled, crash testing cannot be disabled.
+**
+** Available options are "-characteristics" and "-sectorsize". Both require
+** an argument. For -sectorsize, this is the simulated sector size in
+** bytes. For -characteristics, the argument must be a list of io-capability
+** flags to simulate. Valid flags are "atomic", "atomic512", "atomic1K",
+** "atomic2K", "atomic4K", "atomic8K", "atomic16K", "atomic32K", 
+** "atomic64K", "sequential" and "safe_append".
+**
+** Example:
+**
+**   sqlite_crashparams -sect 1024 -char {atomic sequential} ./test.db 1
+**
+*/
+static int crashParamsObjCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int iDelay;
+  const char *zCrashFile;
+  int nCrashFile, iDc, iSectorSize;
+
+  iDc = -1;
+  iSectorSize = -1;
+
+  if( objc<3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "?OPTIONS? DELAY CRASHFILE");
+    goto error;
+  }
+
+  zCrashFile = Tcl_GetStringFromObj(objv[objc-1], &nCrashFile);
+  if( nCrashFile>=sizeof(g.zCrashFile) ){
+    Tcl_AppendResult(interp, "Filename is too long: \"", zCrashFile, "\"", 0);
+    goto error;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[objc-2], &iDelay) ){
+    goto error;
+  }
+
+  if( processDevSymArgs(interp, objc-3, &objv[1], &iDc, &iSectorSize) ){
+    return TCL_ERROR;
+  }
+
+  if( iDc>=0 ){
+    g.iDeviceCharacteristics = iDc;
+  }
+  if( iSectorSize>=0 ){
+    g.iSectorSize = iSectorSize;
+  }
+
+  g.iCrash = iDelay;
+  memcpy(g.zCrashFile, zCrashFile, nCrashFile+1);
+  sqlite3CrashTestEnable = 1;
+  return TCL_OK;
+
+error:
+  return TCL_ERROR;
+}
+
+static int devSymObjCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  void devsym_register(int iDeviceChar, int iSectorSize);
+
+  int iDc = -1;
+  int iSectorSize = -1;
+
+  if( processDevSymArgs(interp, objc-1, &objv[1], &iDc, &iSectorSize) ){
+    return TCL_ERROR;
+  }
+  devsym_register(iDc, iSectorSize);
+
+  return TCL_OK;
+}
+
+/*
+** tclcmd: register_jt_vfs ?-default? PARENT-VFS
+*/
+static int jtObjCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int jt_register(char *, int);
+  char *zParent = 0;
+
+  if( objc!=2 && objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "?-default? PARENT-VFS");
+    return TCL_ERROR;
+  }
+  zParent = Tcl_GetString(objv[1]);
+  if( objc==3 ){
+    if( strcmp(zParent, "-default") ){
+      Tcl_AppendResult(interp, 
+          "bad option \"", zParent, "\": must be -default", 0
+      );
+      return TCL_ERROR;
+    }
+    zParent = Tcl_GetString(objv[2]);
+  }
+
+  if( !(*zParent) ){
+    zParent = 0;
+  }
+  if( jt_register(zParent, objc==3) ){
+    Tcl_AppendResult(interp, "Error in jt_register", 0);
+    return TCL_ERROR;
+  }
+
+  return TCL_OK;
+}
+
+/*
+** tclcmd: unregister_jt_vfs
+*/
+static int jtUnregisterObjCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  void jt_unregister(void);
+
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  jt_unregister();
+  return TCL_OK;
+}
+
+#endif /* SQLITE_OMIT_DISKIO */
+
+/*
+** This procedure registers the TCL procedures defined in this file.
+*/
+int Sqlitetest6_Init(Tcl_Interp *interp){
+#ifndef SQLITE_OMIT_DISKIO
+  Tcl_CreateObjCommand(interp, "sqlite3_crash_enable", crashEnableCmd, 0, 0);
+  Tcl_CreateObjCommand(interp, "sqlite3_crashparams", crashParamsObjCmd, 0, 0);
+  Tcl_CreateObjCommand(interp, "sqlite3_simulate_device", devSymObjCmd, 0, 0);
+  Tcl_CreateObjCommand(interp, "register_jt_vfs", jtObjCmd, 0, 0);
+  Tcl_CreateObjCommand(interp, "unregister_jt_vfs", jtUnregisterObjCmd, 0, 0);
+#endif
+  return TCL_OK;
+}
+
+#endif /* SQLITE_TEST */

+ 714 - 0
components/external/sqlite/test/test7.c

@@ -0,0 +1,714 @@
+/*
+** 2006 January 09
+**
+** 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 client/server version of the SQLite library.
+** Derived from test4.c.
+*/
+#include "sqliteInt.h"
+#include "tcl.h"
+
+/*
+** This test only works on UNIX with a SQLITE_THREADSAFE build that includes
+** the SQLITE_SERVER option.
+*/
+#if defined(SQLITE_SERVER) && !defined(SQLITE_OMIT_SHARED_CACHE) && \
+    SQLITE_OS_UNIX && SQLITE_THREADSAFE
+
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <sched.h>
+#include <ctype.h>
+
+/*
+** Interfaces defined in server.c
+*/
+int sqlite3_client_open(const char*, sqlite3**);
+int sqlite3_client_prepare(sqlite3*,const char*,int,
+                           sqlite3_stmt**,const char**);
+int sqlite3_client_step(sqlite3_stmt*);
+int sqlite3_client_reset(sqlite3_stmt*);
+int sqlite3_client_finalize(sqlite3_stmt*);
+int sqlite3_client_close(sqlite3*);
+int sqlite3_server_start(void);
+int sqlite3_server_stop(void);
+void sqlite3_server_start2(int *pnDecr);
+
+/*
+** Each thread is controlled by an instance of the following
+** structure.
+*/
+typedef struct Thread Thread;
+struct Thread {
+  /* The first group of fields are writable by the supervisor thread
+  ** and read-only to the client threads
+  */
+  char *zFilename;         /* Name of database file */
+  void (*xOp)(Thread*);    /* next operation to do */
+  char *zArg;              /* argument usable by xOp */
+  volatile int opnum;      /* Operation number */
+  volatile int busy;       /* True if this thread is in use */
+
+  /* The next group of fields are writable by the client threads 
+  ** but read-only to the superviser thread.
+  */
+  volatile int completed;  /* Number of operations completed */
+  sqlite3 *db;             /* Open database */
+  sqlite3_stmt *pStmt;     /* Pending operation */
+  char *zErr;              /* operation error */
+  char *zStaticErr;        /* Static error message */
+  int rc;                  /* operation return code */
+  int argc;                /* number of columns in result */
+  const char *argv[100];   /* result columns */
+  const char *colv[100];   /* result column names */
+
+  /* Initialized to 1 by the supervisor thread when the client is 
+  ** created, and then deemed read-only to the supervisor thread. 
+  ** Is set to 0 by the server thread belonging to this client 
+  ** just before it exits.  
+  */
+  int nServer;             /* Number of server threads running */
+};
+
+/*
+** There can be as many as 26 threads running at once.  Each is named
+** by a capital letter: A, B, C, ..., Y, Z.
+*/
+#define N_THREAD 26
+static Thread threadset[N_THREAD];
+
+/*
+** The main loop for a thread.  Threads use busy waiting. 
+*/
+static void *client_main(void *pArg){
+  Thread *p = (Thread*)pArg;
+  if( p->db ){
+    sqlite3_client_close(p->db);
+  }
+  sqlite3_client_open(p->zFilename, &p->db);
+  if( SQLITE_OK!=sqlite3_errcode(p->db) ){
+    p->zErr = strdup(sqlite3_errmsg(p->db));
+    sqlite3_client_close(p->db);
+    p->db = 0;
+  }
+  p->pStmt = 0;
+  p->completed = 1;
+  while( p->opnum<=p->completed ) sched_yield();
+  while( p->xOp ){
+    if( p->zErr && p->zErr!=p->zStaticErr ){
+      sqlite3_free(p->zErr);
+      p->zErr = 0;
+    }
+    (*p->xOp)(p);
+    p->completed++;
+    while( p->opnum<=p->completed ) sched_yield();
+  }
+  if( p->pStmt ){
+    sqlite3_client_finalize(p->pStmt);
+    p->pStmt = 0;
+  }
+  if( p->db ){
+    sqlite3_client_close(p->db);
+    p->db = 0;
+  }
+  if( p->zErr && p->zErr!=p->zStaticErr ){
+    sqlite3_free(p->zErr);
+    p->zErr = 0;
+  }
+  p->completed++;
+#ifndef SQLITE_OMIT_DEPRECATED
+  sqlite3_thread_cleanup();
+#endif
+  return 0;
+}
+
+/*
+** Get a thread ID which is an upper case letter.  Return the index.
+** If the argument is not a valid thread ID put an error message in
+** the interpreter and return -1.
+*/
+static int parse_client_id(Tcl_Interp *interp, const char *zArg){
+  if( zArg==0 || zArg[0]==0 || zArg[1]!=0 || !isupper((unsigned char)zArg[0]) ){
+    Tcl_AppendResult(interp, "thread ID must be an upper case letter", 0);
+    return -1;
+  }
+  return zArg[0] - 'A';
+}
+
+/*
+** Usage:    client_create NAME  FILENAME
+**
+** NAME should be an upper case letter.  Start the thread running with
+** an open connection to the given database.
+*/
+static int tcl_client_create(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  pthread_t x;
+  int rc;
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID FILENAME", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( threadset[i].busy ){
+    Tcl_AppendResult(interp, "thread ", argv[1], " is already running", 0);
+    return TCL_ERROR;
+  }
+  threadset[i].busy = 1;
+  sqlite3_free(threadset[i].zFilename);
+  threadset[i].zFilename = sqlite3_mprintf("%s", argv[2]);
+  threadset[i].opnum = 1;
+  threadset[i].completed = 0;
+  rc = pthread_create(&x, 0, client_main, &threadset[i]);
+  if( rc ){
+    Tcl_AppendResult(interp, "failed to create the thread", 0);
+    sqlite3_free(threadset[i].zFilename);
+    threadset[i].busy = 0;
+    return TCL_ERROR;
+  }
+  pthread_detach(x);
+  if( threadset[i].nServer==0 ){
+    threadset[i].nServer = 1;
+    sqlite3_server_start2(&threadset[i].nServer);
+  }
+  return TCL_OK;
+}
+
+/*
+** Wait for a thread to reach its idle state.
+*/
+static void client_wait(Thread *p){
+  while( p->opnum>p->completed ) sched_yield();
+}
+
+/*
+** Usage:  client_wait ID
+**
+** Wait on thread ID to reach its idle state.
+*/
+static int tcl_client_wait(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  client_wait(&threadset[i]);
+  return TCL_OK;
+}
+
+/*
+** Stop a thread.
+*/
+static void stop_thread(Thread *p){
+  client_wait(p);
+  p->xOp = 0;
+  p->opnum++;
+  client_wait(p);
+  sqlite3_free(p->zArg);
+  p->zArg = 0;
+  sqlite3_free(p->zFilename);
+  p->zFilename = 0;
+  p->busy = 0;
+}
+
+/*
+** Usage:  client_halt ID
+**
+** Cause a client thread to shut itself down.  Wait for the shutdown to be
+** completed.  If ID is "*" then stop all client threads.
+*/
+static int tcl_client_halt(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  if( argv[1][0]=='*' && argv[1][1]==0 ){
+    for(i=0; i<N_THREAD; i++){
+      if( threadset[i].busy ){
+        stop_thread(&threadset[i]);
+      }
+    }
+  }else{
+    i = parse_client_id(interp, argv[1]);
+    if( i<0 ) return TCL_ERROR;
+    if( !threadset[i].busy ){
+      Tcl_AppendResult(interp, "no such thread", 0);
+      return TCL_ERROR;
+    }
+    stop_thread(&threadset[i]);
+  }
+
+  /* If no client threads are still running, also stop the server */
+  for(i=0; i<N_THREAD && threadset[i].busy==0; i++){}
+  if( i>=N_THREAD ){
+    sqlite3_server_stop();
+    while( 1 ){
+      for(i=0; i<N_THREAD && threadset[i].nServer==0; i++);
+      if( i==N_THREAD ) break;
+      sched_yield();
+    }
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage: client_argc  ID
+**
+** Wait on the most recent client_step to complete, then return the
+** number of columns in the result set.
+*/
+static int tcl_client_argc(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  char zBuf[100];
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  client_wait(&threadset[i]);
+  sprintf(zBuf, "%d", threadset[i].argc);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: client_argv  ID   N
+**
+** Wait on the most recent client_step to complete, then return the
+** value of the N-th columns in the result set.
+*/
+static int tcl_client_argv(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  int n;
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID N", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
+  client_wait(&threadset[i]);
+  if( n<0 || n>=threadset[i].argc ){
+    Tcl_AppendResult(interp, "column number out of range", 0);
+    return TCL_ERROR;
+  }
+  Tcl_AppendResult(interp, threadset[i].argv[n], 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: client_colname  ID   N
+**
+** Wait on the most recent client_step to complete, then return the
+** name of the N-th columns in the result set.
+*/
+static int tcl_client_colname(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  int n;
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID N", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
+  client_wait(&threadset[i]);
+  if( n<0 || n>=threadset[i].argc ){
+    Tcl_AppendResult(interp, "column number out of range", 0);
+    return TCL_ERROR;
+  }
+  Tcl_AppendResult(interp, threadset[i].colv[n], 0);
+  return TCL_OK;
+}
+
+extern const char *sqlite3ErrName(int);
+
+/*
+** Usage: client_result  ID
+**
+** Wait on the most recent operation to complete, then return the
+** result code from that operation.
+*/
+static int tcl_client_result(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  const char *zName;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  client_wait(&threadset[i]);
+  zName = sqlite3ErrName(threadset[i].rc);
+  Tcl_AppendResult(interp, zName, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: client_error  ID
+**
+** Wait on the most recent operation to complete, then return the
+** error string.
+*/
+static int tcl_client_error(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  client_wait(&threadset[i]);
+  Tcl_AppendResult(interp, threadset[i].zErr, 0);
+  return TCL_OK;
+}
+
+/*
+** This procedure runs in the thread to compile an SQL statement.
+*/
+static void do_compile(Thread *p){
+  if( p->db==0 ){
+    p->zErr = p->zStaticErr = "no database is open";
+    p->rc = SQLITE_ERROR;
+    return;
+  }
+  if( p->pStmt ){
+    sqlite3_client_finalize(p->pStmt);
+    p->pStmt = 0;
+  }
+  p->rc = sqlite3_client_prepare(p->db, p->zArg, -1, &p->pStmt, 0);
+}
+
+/*
+** Usage: client_compile ID SQL
+**
+** Compile a new virtual machine.
+*/
+static int tcl_client_compile(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID SQL", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  client_wait(&threadset[i]);
+  threadset[i].xOp = do_compile;
+  sqlite3_free(threadset[i].zArg);
+  threadset[i].zArg = sqlite3_mprintf("%s", argv[2]);
+  threadset[i].opnum++;
+  return TCL_OK;
+}
+
+/*
+** This procedure runs in the thread to step the virtual machine.
+*/
+static void do_step(Thread *p){
+  int i;
+  if( p->pStmt==0 ){
+    p->zErr = p->zStaticErr = "no virtual machine available";
+    p->rc = SQLITE_ERROR;
+    return;
+  }
+  p->rc = sqlite3_client_step(p->pStmt);
+  if( p->rc==SQLITE_ROW ){
+    p->argc = sqlite3_column_count(p->pStmt);
+    for(i=0; i<sqlite3_data_count(p->pStmt); i++){
+      p->argv[i] = (char*)sqlite3_column_text(p->pStmt, i);
+    }
+    for(i=0; i<p->argc; i++){
+      p->colv[i] = sqlite3_column_name(p->pStmt, i);
+    }
+  }
+}
+
+/*
+** Usage: client_step ID
+**
+** Advance the virtual machine by one step
+*/
+static int tcl_client_step(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " IDL", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  client_wait(&threadset[i]);
+  threadset[i].xOp = do_step;
+  threadset[i].opnum++;
+  return TCL_OK;
+}
+
+/*
+** This procedure runs in the thread to finalize a virtual machine.
+*/
+static void do_finalize(Thread *p){
+  if( p->pStmt==0 ){
+    p->zErr = p->zStaticErr = "no virtual machine available";
+    p->rc = SQLITE_ERROR;
+    return;
+  }
+  p->rc = sqlite3_client_finalize(p->pStmt);
+  p->pStmt = 0;
+}
+
+/*
+** Usage: client_finalize ID
+**
+** Finalize the virtual machine.
+*/
+static int tcl_client_finalize(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " IDL", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  client_wait(&threadset[i]);
+  threadset[i].xOp = do_finalize;
+  sqlite3_free(threadset[i].zArg);
+  threadset[i].zArg = 0;
+  threadset[i].opnum++;
+  return TCL_OK;
+}
+
+/*
+** This procedure runs in the thread to reset a virtual machine.
+*/
+static void do_reset(Thread *p){
+  if( p->pStmt==0 ){
+    p->zErr = p->zStaticErr = "no virtual machine available";
+    p->rc = SQLITE_ERROR;
+    return;
+  }
+  p->rc = sqlite3_client_reset(p->pStmt);
+  p->pStmt = 0;
+}
+
+/*
+** Usage: client_reset ID
+**
+** Finalize the virtual machine.
+*/
+static int tcl_client_reset(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " IDL", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  client_wait(&threadset[i]);
+  threadset[i].xOp = do_reset;
+  sqlite3_free(threadset[i].zArg);
+  threadset[i].zArg = 0;
+  threadset[i].opnum++;
+  return TCL_OK;
+}
+
+/*
+** Usage: client_swap ID ID
+**
+** Interchange the sqlite* pointer between two threads.
+*/
+static int tcl_client_swap(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i, j;
+  sqlite3 *temp;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID1 ID2", 0);
+    return TCL_ERROR;
+  }
+  i = parse_client_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  client_wait(&threadset[i]);
+  j = parse_client_id(interp, argv[2]);
+  if( j<0 ) return TCL_ERROR;
+  if( !threadset[j].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  client_wait(&threadset[j]);
+  temp = threadset[i].db;
+  threadset[i].db = threadset[j].db;
+  threadset[j].db = temp;
+  return TCL_OK;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest7_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_CmdProc *xProc;
+  } aCmd[] = {
+     { "client_create",     (Tcl_CmdProc*)tcl_client_create     },
+     { "client_wait",       (Tcl_CmdProc*)tcl_client_wait       },
+     { "client_halt",       (Tcl_CmdProc*)tcl_client_halt       },
+     { "client_argc",       (Tcl_CmdProc*)tcl_client_argc       },
+     { "client_argv",       (Tcl_CmdProc*)tcl_client_argv       },
+     { "client_colname",    (Tcl_CmdProc*)tcl_client_colname    },
+     { "client_result",     (Tcl_CmdProc*)tcl_client_result     },
+     { "client_error",      (Tcl_CmdProc*)tcl_client_error      },
+     { "client_compile",    (Tcl_CmdProc*)tcl_client_compile    },
+     { "client_step",       (Tcl_CmdProc*)tcl_client_step       },
+     { "client_reset",      (Tcl_CmdProc*)tcl_client_reset      },
+     { "client_finalize",   (Tcl_CmdProc*)tcl_client_finalize   },
+     { "client_swap",       (Tcl_CmdProc*)tcl_client_swap       },
+  };
+  int i;
+
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
+  return TCL_OK;
+}
+#else
+int Sqlitetest7_Init(Tcl_Interp *interp){ return TCL_OK; }
+#endif /* SQLITE_OS_UNIX */

+ 1396 - 0
components/external/sqlite/test/test8.c

@@ -0,0 +1,1396 @@
+/*
+** 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.
+*/
+#include "sqliteInt.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef 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.
+*/
+struct echo_vtab {
+  sqlite3_vtab base;
+  Tcl_Interp *interp;     /* Tcl interpreter containing debug variables */
+  sqlite3 *db;            /* Database connection */
+
+  int isPattern;
+  int inTransaction;      /* True if within a transaction */
+  char *zThis;            /* Name of the echo table */
+  char *zTableName;       /* Name of the real table */
+  char *zLogName;         /* Name of the log table */
+  int nCol;               /* Number of columns in the real table */
+  int *aIndex;            /* Array of size nCol. True if column has an index */
+  char **aCol;            /* Array of size nCol. Column names */
+};
+
+/* An echo cursor object */
+struct echo_cursor {
+  sqlite3_vtab_cursor base;
+  sqlite3_stmt *pStmt;
+};
+
+static int simulateVtabError(echo_vtab *p, const char *zMethod){
+  const char *zErr;
+  char zVarname[128];
+  zVarname[127] = '\0';
+  sqlite3_snprintf(127, zVarname, "echo_module_fail(%s,%s)", zMethod, p->zTableName);
+  zErr = Tcl_GetVar(p->interp, zVarname, TCL_GLOBAL_ONLY);
+  if( zErr ){
+    p->base.zErrMsg = sqlite3_mprintf("echo-vtab-error: %s", zErr);
+  }
+  return (zErr!=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(char *z){
+  int quote;
+  int i, j;
+  if( z==0 ) return;
+  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, 
+  const char *zTab,
+  char ***paCol, 
+  int *pnCol
+){
+  char **aCol = 0;
+  char *zSql;
+  sqlite3_stmt *pStmt = 0;
+  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( !zSql ){
+    rc = SQLITE_NOMEM;
+    goto out;
+  }
+  rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
+  sqlite3_free(zSql);
+
+  if( rc==SQLITE_OK ){
+    int ii;
+    int nBytes;
+    char *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++){
+      const char *zName = sqlite3_column_name(pStmt, ii);
+      if( !zName ){
+        rc = SQLITE_NOMEM;
+        goto out;
+      }
+      nBytes += (int)strlen(zName)+1;
+    }
+    aCol = (char **)sqlite3MallocZero(nBytes);
+    if( !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++;
+    }
+    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 */
+  const char *zTab,        /* Name of table in database db */
+  int nCol,
+  int **paIndex
+){
+  sqlite3_stmt *pStmt = 0;
+  int *aIndex = 0;
+  int rc;
+  char *zSql;
+
+  /* Allocate space for the index array */
+  aIndex = (int *)sqlite3MallocZero(sizeof(int) * nCol);
+  if( !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( !zSql ){
+    rc = SQLITE_NOMEM;
+    goto get_index_array_out;
+  }
+  rc = sqlite3_prepare(db, zSql, -1, &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 && sqlite3_step(pStmt)==SQLITE_ROW ){
+    const char *zIdx = (const char *)sqlite3_column_text(pStmt, 1);
+    sqlite3_stmt *pStmt2 = 0;
+    if( zIdx==0 ) continue;
+    zSql = sqlite3_mprintf("PRAGMA index_info(%s)", zIdx);
+    if( !zSql ){
+      rc = SQLITE_NOMEM;
+      goto get_index_array_out;
+    }
+    rc = sqlite3_prepare(db, zSql, -1, &pStmt2, 0);
+    sqlite3_free(zSql);
+    if( pStmt2 && sqlite3_step(pStmt2)==SQLITE_ROW ){
+      int cid = sqlite3_column_int(pStmt2, 1);
+      assert( cid>=0 && cid<nCol );
+      aIndex[cid] = 1;
+    }
+    if( pStmt2 ){
+      rc = sqlite3_finalize(pStmt2);
+    }
+    if( rc!=SQLITE_OK ){
+      goto get_index_array_out;
+    }
+  }
+
+
+get_index_array_out:
+  if( pStmt ){
+    int rc2 = sqlite3_finalize(pStmt);
+    if( rc==SQLITE_OK ){
+      rc = rc2;
+    }
+  }
+  if( rc!=SQLITE_OK ){
+    sqlite3_free(aIndex);
+    aIndex = 0;
+  }
+  *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, const char *zArg){
+  int flags = (TCL_APPEND_VALUE | TCL_LIST_ELEMENT | TCL_GLOBAL_ONLY);
+  Tcl_SetVar(interp, "echo_module", (zArg?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 ){
+    sqlite3_stmt *pStmt = 0;
+    rc = sqlite3_prepare(db, 
+        "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?",
+        -1, &pStmt, 0);
+    if( rc==SQLITE_OK ){
+      sqlite3_bind_text(pStmt, 1, pVtab->zTableName, -1, 0);
+      if( sqlite3_step(pStmt)==SQLITE_ROW ){
+        int rc2;
+        const char *zCreateTable = (const char *)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, &pVtab->aCol, &pVtab->nCol);
+      }
+      if( rc==SQLITE_OK ){
+        rc = getIndexArray(db, pVtab->zTableName, pVtab->nCol, &pVtab->aIndex);
+      }
+    }
+  }
+
+  return rc;
+}
+
+/*
+** This function frees all runtime structures associated with the virtual
+** table pVtab.
+*/
+static int echoDestructor(sqlite3_vtab *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);
+  return 0;
+}
+
+typedef struct EchoModule EchoModule;
+struct EchoModule {
+  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,
+  void *pAux,
+  int argc, const char *const*argv,
+  sqlite3_vtab **ppVtab,
+  char **pzErr
+){
+  int rc;
+  int i;
+  echo_vtab *pVtab;
+
+  /* Allocate the sqlite3_vtab/echo_vtab structure itself */
+  pVtab = sqlite3MallocZero( sizeof(*pVtab) );
+  if( !pVtab ){
+    return SQLITE_NOMEM;
+  }
+  pVtab->interp = ((EchoModule *)pAux)->interp;
+  pVtab->db = db;
+
+  /* Allocate echo_vtab.zThis */
+  pVtab->zThis = sqlite3_mprintf("%s", argv[2]);
+  if( !pVtab->zThis ){
+    echoDestructor((sqlite3_vtab *)pVtab);
+    return SQLITE_NOMEM;
+  }
+
+  /* Allocate echo_vtab.zTableName */
+  if( argc>3 ){
+    pVtab->zTableName = sqlite3_mprintf("%s", argv[3]);
+    dequoteString(pVtab->zTableName);
+    if( pVtab->zTableName && pVtab->zTableName[0]=='*' ){
+      char *z = sqlite3_mprintf("%s%s", argv[2], &(pVtab->zTableName[1]));
+      sqlite3_free(pVtab->zTableName);
+      pVtab->zTableName = z;
+      pVtab->isPattern = 1;
+    }
+    if( !pVtab->zTableName ){
+      echoDestructor((sqlite3_vtab *)pVtab);
+      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 ){
+    echoDestructor((sqlite3_vtab *)pVtab);
+    return rc;
+  }
+
+  /* Success. Set *ppVtab and return */
+  *ppVtab = &pVtab->base;
+  return SQLITE_OK;
+}
+
+/* 
+** Echo virtual table module xCreate method.
+*/
+static int echoCreate(
+  sqlite3 *db,
+  void *pAux,
+  int argc, const char *const*argv,
+  sqlite3_vtab **ppVtab,
+  char **pzErr
+){
+  int rc = SQLITE_OK;
+  appendToEchoModule(((EchoModule *)pAux)->interp, "xCreate");
+  rc = echoConstructor(db, pAux, argc, argv, ppVtab, 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 ){
+    char *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 && rc!=SQLITE_OK ){
+    echoDestructor(*ppVtab);
+    *ppVtab = 0;
+  }
+
+  if( rc==SQLITE_OK ){
+    (*(echo_vtab**)ppVtab)->inTransaction = 1;
+  }
+
+  return rc;
+}
+
+/* 
+** Echo virtual table module xConnect method.
+*/
+static int echoConnect(
+  sqlite3 *db,
+  void *pAux,
+  int argc, const char *const*argv,
+  sqlite3_vtab **ppVtab,
+  char **pzErr
+){
+  appendToEchoModule(((EchoModule *)pAux)->interp, "xConnect");
+  return echoConstructor(db, pAux, argc, argv, ppVtab, pzErr);
+}
+
+/* 
+** Echo virtual table module xDisconnect method.
+*/
+static int echoDisconnect(sqlite3_vtab *pVtab){
+  appendToEchoModule(((echo_vtab *)pVtab)->interp, "xDisconnect");
+  return echoDestructor(pVtab);
+}
+
+/* 
+** Echo virtual table module xDestroy method.
+*/
+static int echoDestroy(sqlite3_vtab *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 && p->zLogName ){
+    char *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(pVtab);
+  }
+  return rc;
+}
+
+/* 
+** Echo virtual table module xOpen method.
+*/
+static int echoOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+  echo_cursor *pCur;
+  if( simulateVtabError((echo_vtab *)pVTab, "xOpen") ){
+    return SQLITE_ERROR;
+  }
+  pCur = sqlite3MallocZero(sizeof(echo_cursor));
+  *ppCursor = (sqlite3_vtab_cursor *)pCur;
+  return (pCur ? SQLITE_OK : SQLITE_NOMEM);
+}
+
+/* 
+** Echo virtual table module xClose method.
+*/
+static int echoClose(sqlite3_vtab_cursor *cur){
+  int rc;
+  echo_cursor *pCur = (echo_cursor *)cur;
+  sqlite3_stmt *pStmt = pCur->pStmt;
+  pCur->pStmt = 0;
+  sqlite3_free(pCur);
+  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 ? 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") ){
+    return SQLITE_ERROR;
+  }
+
+  if( pCur->pStmt ){
+    rc = sqlite3_step(pCur->pStmt);
+    if( rc==SQLITE_ROW ){
+      rc = SQLITE_OK;
+    }else{
+      rc = sqlite3_finalize(pCur->pStmt);
+      pCur->pStmt = 0;
+    }
+  }
+
+  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") ){
+    return SQLITE_ERROR;
+  }
+
+  if( !pStmt ){
+    sqlite3_result_null(ctx);
+  }else{
+    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, sqlite_int64 *pRowid){
+  sqlite3_stmt *pStmt = ((echo_cursor *)cur)->pStmt;
+
+  if( simulateVtabError((echo_vtab *)(cur->pVtab), "xRowid") ){
+    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 assert()s that the supplied idxNum value is
+** indeed the hash of the supplied idxStr.
+*/
+static int hashString(const char *zString){
+  int val = 0;
+  int ii;
+  for(ii=0; zString[ii]; ii++){
+    val = (val << 3) + (int)zString[ii];
+  }
+  return val;
+}
+
+/* 
+** Echo virtual table module xFilter method.
+*/
+static int echoFilter(
+  sqlite3_vtab_cursor *pVtabCursor, 
+  int idxNum, const char *idxStr,
+  int argc, sqlite3_value **argv
+){
+  int rc;
+  int i;
+
+  echo_cursor *pCur = (echo_cursor *)pVtabCursor;
+  echo_vtab *pVtab = (echo_vtab *)pVtabCursor->pVtab;
+  sqlite3 *db = pVtab->db;
+
+  if( simulateVtabError(pVtab, "xFilter") ){
+    return SQLITE_ERROR;
+  }
+
+  /* Check that idxNum matches idxStr */
+  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, (const char*)sqlite3_value_text(argv[i]));
+  }
+
+  sqlite3_finalize(pCur->pStmt);
+  pCur->pStmt = 0;
+
+  /* Prepare the SQL statement created by echoBestIndex and bind the
+  ** runtime parameters passed to this function to it.
+  */
+  rc = sqlite3_prepare(db, idxStr, -1, &pCur->pStmt, 0);
+  assert( pCur->pStmt || 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(char **pzStr, char *zAppend, int doFree, int *pRc){
+  char *zIn = *pzStr;
+  if( !zAppend && doFree && *pRc==SQLITE_OK ){
+    *pRc = SQLITE_NOMEM;
+  }
+  if( *pRc!=SQLITE_OK ){
+    sqlite3_free(zIn);
+    zIn = 0;
+  }else{
+    if( zIn ){
+      char *zTemp = zIn;
+      zIn = sqlite3_mprintf("%s%s", zIn, zAppend);
+      sqlite3_free(zTemp);
+    }else{
+      zIn = sqlite3_mprintf("%s", zAppend);
+    }
+    if( !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 *tab, sqlite3_index_info *pIdxInfo){
+  int ii;
+  char *zQuery = 0;
+  char *zNew;
+  int nArg = 0;
+  const char *zSep = "WHERE";
+  echo_vtab *pVtab = (echo_vtab *)tab;
+  sqlite3_stmt *pStmt = 0;
+  Tcl_Interp *interp = pVtab->interp;
+
+  int nRow;
+  int useIdx = 0;
+  int rc = SQLITE_OK;
+  int useCost = 0;
+  double cost;
+  int isIgnoreUsable = 0;
+  if( Tcl_GetVar(interp, "echo_module_ignore_usable", TCL_GLOBAL_ONLY) ){
+    isIgnoreUsable = 1;
+  }
+
+  if( simulateVtabError(pVtab, "xBestIndex") ){
+    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_GetVar(interp, "echo_module_cost", TCL_GLOBAL_ONLY) ){
+    cost = atof(Tcl_GetVar(interp, "echo_module_cost", TCL_GLOBAL_ONLY));
+    useCost = 1;
+  } else {
+    zQuery = sqlite3_mprintf("SELECT count(*) FROM %Q", pVtab->zTableName);
+    if( !zQuery ){
+      return SQLITE_NOMEM;
+    }
+    rc = sqlite3_prepare(pVtab->db, zQuery, -1, &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( !zQuery ){
+    return SQLITE_NOMEM;
+  }
+  for(ii=0; ii<pIdxInfo->nConstraint; ii++){
+    const struct sqlite3_index_constraint *pConstraint;
+    struct sqlite3_index_constraint_usage *pUsage;
+    int iCol;
+
+    pConstraint = &pIdxInfo->aConstraint[ii];
+    pUsage = &pIdxInfo->aConstraintUsage[ii];
+
+    if( !isIgnoreUsable && !pConstraint->usable ) continue;
+
+    iCol = pConstraint->iColumn;
+    if( iCol<0 || pVtab->aIndex[iCol] ){
+      char *zCol = iCol>=0 ? pVtab->aCol[iCol] : "rowid";
+      char *zOp = 0;
+      useIdx = 1;
+      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(&zQuery, zNew, 1, &rc);
+
+      zSep = "AND";
+      pUsage->argvIndex = ++nArg;
+      pUsage->omit = 1;
+    }
+  }
+
+  /* If there is only one term in the ORDER BY clause, and it is
+  ** on a column that this virtual table has an index for, then consume 
+  ** the ORDER BY clause.
+  */
+  if( pIdxInfo->nOrderBy==1 && (
+        pIdxInfo->aOrderBy->iColumn<0 ||
+        pVtab->aIndex[pIdxInfo->aOrderBy->iColumn]) ){
+    int iCol = pIdxInfo->aOrderBy->iColumn;
+    char *zCol = iCol>=0 ? pVtab->aCol[iCol] : "rowid";
+    char *zDir = pIdxInfo->aOrderBy->desc?"DESC":"ASC";
+    zNew = sqlite3_mprintf(" ORDER BY %s %s", zCol, zDir);
+    string_concat(&zQuery, zNew, 1, &rc);
+    pIdxInfo->orderByConsumed = 1;
+  }
+
+  appendToEchoModule(pVtab->interp, "xBestIndex");;
+  appendToEchoModule(pVtab->interp, zQuery);
+
+  if( !zQuery ){
+    return rc;
+  }
+  pIdxInfo->idxNum = hashString(zQuery);
+  pIdxInfo->idxStr = zQuery;
+  pIdxInfo->needToFreeIdxStr = 1;
+  if( useCost ){
+    pIdxInfo->estimatedCost = cost;
+  }else if( useIdx ){
+    /* Approximation of log2(nRow). */
+    for( ii=0; ii<(sizeof(int)*8); ii++ ){
+      if( nRow & (1<<ii) ){
+        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)
+**
+*/
+int echoUpdate(
+  sqlite3_vtab *tab, 
+  int nData, 
+  sqlite3_value **apData, 
+  sqlite_int64 *pRowid
+){
+  echo_vtab *pVtab = (echo_vtab *)tab;
+  sqlite3 *db = pVtab->db;
+  int rc = SQLITE_OK;
+
+  sqlite3_stmt *pStmt;
+  char *z = 0;               /* SQL statement to execute */
+  int bindArgZero = 0;       /* True to bind apData[0] to sql var no. nData */
+  int bindArgOne = 0;        /* True to bind apData[1] to sql var no. 1 */
+  int i;                     /* Counter variable used by for loops */
+
+  assert( nData==pVtab->nCol+2 || nData==1 );
+
+  /* Ticket #3083 - make sure we always start a transaction prior to
+  ** making any changes to a virtual table */
+  assert( pVtab->inTransaction );
+
+  if( simulateVtabError(pVtab, "xUpdate") ){
+    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 ){
+    char *zSep = " SET";
+    z = sqlite3_mprintf("UPDATE %Q", pVtab->zTableName);
+    if( !z ){
+      rc = SQLITE_NOMEM;
+    }
+
+    bindArgOne = (apData[1] && sqlite3_value_type(apData[1])==SQLITE_INTEGER);
+    bindArgZero = 1;
+
+    if( bindArgOne ){
+       string_concat(&z, " SET rowid=?1 ", 0, &rc);
+       zSep = ",";
+    }
+    for(i=2; i<nData; i++){
+      if( apData[i]==0 ) continue;
+      string_concat(&z, sqlite3_mprintf(
+          "%s %Q=?%d", zSep, pVtab->aCol[i-2], i), 1, &rc);
+      zSep = ",";
+    }
+    string_concat(&z, sqlite3_mprintf(" WHERE rowid=?%d", nData), 1, &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( !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;
+    char *zInsert = 0;
+    char *zValues = 0;
+  
+    zInsert = sqlite3_mprintf("INSERT INTO %Q (", pVtab->zTableName);
+    if( !zInsert ){
+      rc = SQLITE_NOMEM;
+    }
+    if( sqlite3_value_type(apData[1])==SQLITE_INTEGER ){
+      bindArgOne = 1;
+      zValues = sqlite3_mprintf("?");
+      string_concat(&zInsert, "rowid", 0, &rc);
+    }
+
+    assert((pVtab->nCol+2)==nData);
+    for(ii=2; ii<nData; ii++){
+      string_concat(&zInsert, 
+          sqlite3_mprintf("%s%Q", zValues?", ":"", pVtab->aCol[ii-2]), 1, &rc);
+      string_concat(&zValues, 
+          sqlite3_mprintf("%s?%d", zValues?", ":"", ii), 1, &rc);
+    }
+
+    string_concat(&z, zInsert, 1, &rc);
+    string_concat(&z, ") VALUES(", 0, &rc);
+    string_concat(&z, zValues, 1, &rc);
+    string_concat(&z, ")", 0, &rc);
+  }
+
+  /* Anything else is an error */
+  else{
+    assert(0);
+    return SQLITE_ERROR;
+  }
+
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_prepare(db, z, -1, &pStmt, 0);
+  }
+  assert( rc!=SQLITE_OK || pStmt );
+  sqlite3_free(z);
+  if( rc==SQLITE_OK ) {
+    if( bindArgZero ){
+      sqlite3_bind_value(pStmt, nData, apData[0]);
+    }
+    if( bindArgOne ){
+      sqlite3_bind_value(pStmt, 1, apData[1]);
+    }
+    for(i=2; i<nData && rc==SQLITE_OK; i++){
+      if( apData[i] ) 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 && rc==SQLITE_OK ){
+    *pRowid = sqlite3_last_insert_rowid(db);
+  }
+  if( rc!=SQLITE_OK ){
+    tab->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 *tab, const char *zCall){
+  char *z;
+  echo_vtab *pVtab = (echo_vtab *)tab;
+  z = sqlite3_mprintf("echo(%s)", pVtab->zTableName);
+  if( z==0 ) return SQLITE_NOMEM;
+  appendToEchoModule(pVtab->interp, zCall);
+  appendToEchoModule(pVtab->interp, z);
+  sqlite3_free(z);
+  return SQLITE_OK;
+}
+static int echoBegin(sqlite3_vtab *tab){
+  int rc;
+  echo_vtab *pVtab = (echo_vtab *)tab;
+  Tcl_Interp *interp = pVtab->interp;
+  const char *zVal; 
+
+  /* Ticket #3083 - do not start a transaction if we are already in
+  ** a transaction */
+  assert( !pVtab->inTransaction );
+
+  if( simulateVtabError(pVtab, "xBegin") ){
+    return SQLITE_ERROR;
+  }
+
+  rc = echoTransactionCall(tab, "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_GetVar(interp, "echo_module_begin_fail", TCL_GLOBAL_ONLY);
+    if( zVal && 0==strcmp(zVal, pVtab->zTableName) ){
+      rc = SQLITE_ERROR;
+    }
+  }
+  if( rc==SQLITE_OK ){
+    pVtab->inTransaction = 1;
+  }
+  return rc;
+}
+static int echoSync(sqlite3_vtab *tab){
+  int rc;
+  echo_vtab *pVtab = (echo_vtab *)tab;
+  Tcl_Interp *interp = pVtab->interp;
+  const char *zVal; 
+
+  /* Ticket #3083 - Only call xSync if we have previously started a
+  ** transaction */
+  assert( pVtab->inTransaction );
+
+  if( simulateVtabError(pVtab, "xSync") ){
+    return SQLITE_ERROR;
+  }
+
+  rc = echoTransactionCall(tab, "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_GetVar(interp, "echo_module_sync_fail", TCL_GLOBAL_ONLY);
+    if( zVal && 0==strcmp(zVal, pVtab->zTableName) ){
+      rc = -1;
+    }
+  }
+  return rc;
+}
+static int echoCommit(sqlite3_vtab *tab){
+  echo_vtab *pVtab = (echo_vtab*)tab;
+  int rc;
+
+  /* Ticket #3083 - Only call xCommit if we have previously started
+  ** a transaction */
+  assert( pVtab->inTransaction );
+
+  if( simulateVtabError(pVtab, "xCommit") ){
+    return SQLITE_ERROR;
+  }
+
+  sqlite3BeginBenignMalloc();
+  rc = echoTransactionCall(tab, "xCommit");
+  sqlite3EndBenignMalloc();
+  pVtab->inTransaction = 0;
+  return rc;
+}
+static int echoRollback(sqlite3_vtab *tab){
+  int rc;
+  echo_vtab *pVtab = (echo_vtab*)tab;
+
+  /* Ticket #3083 - Only call xRollback if we have previously started
+  ** a transaction */
+  assert( pVtab->inTransaction );
+
+  rc = echoTransactionCall(tab, "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 = sqlite3_user_data(pContext);
+  Tcl_DString str;
+  int i;
+  int rc;
+  Tcl_DStringInit(&str);
+  Tcl_DStringAppendElement(&str, "::echo_glob_overload");
+  for(i=0; i<nArg; i++){
+    Tcl_DStringAppendElement(&str, (char*)sqlite3_value_text(apArg[i]));
+  }
+  rc = Tcl_Eval(interp, Tcl_DStringValue(&str));
+  Tcl_DStringFree(&str);
+  if( rc ){
+    sqlite3_result_error(pContext, Tcl_GetStringResult(interp), -1);
+  }else{
+    sqlite3_result_text(pContext, Tcl_GetStringResult(interp),
+                        -1, SQLITE_TRANSIENT);
+  }
+  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,
+  const char *zFuncName,
+  void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
+  void **ppArg
+){
+  echo_vtab *pVtab = (echo_vtab *)vtab;
+  Tcl_Interp *interp = pVtab->interp;
+  Tcl_CmdInfo info;
+  if( strcmp(zFuncName,"glob")!=0 ){
+    return 0;
+  }
+  if( Tcl_GetCommandInfo(interp, "::echo_glob_overload", &info)==0 ){
+    return 0;
+  }
+  *pxFunc = overloadedGlobFunction;
+  *ppArg = interp;
+  return 1;
+}
+
+static int echoRename(sqlite3_vtab *vtab, const char *zNewName){
+  int rc = SQLITE_OK;
+  echo_vtab *p = (echo_vtab *)vtab;
+
+  if( simulateVtabError(p, "xRename") ){
+    return SQLITE_ERROR;
+  }
+
+  if( p->isPattern ){
+    int nThis = (int)strlen(p->zThis);
+    char *zSql = sqlite3_mprintf("ALTER TABLE %s RENAME TO %s%s", 
+        p->zTableName, zNewName, &p->zTableName[nThis]
+    );
+    rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
+    sqlite3_free(zSql);
+  }
+
+  return rc;
+}
+
+static int echoSavepoint(sqlite3_vtab *pVTab, int iSavepoint){
+  assert( pVTab );
+  return SQLITE_OK;
+}
+
+static int echoRelease(sqlite3_vtab *pVTab, int iSavepoint){
+  assert( pVTab );
+  return SQLITE_OK;
+}
+
+static int echoRollbackTo(sqlite3_vtab *pVTab, int iSavepoint){
+  assert( pVTab );
+  return SQLITE_OK;
+}
+
+/*
+** A virtual table module that merely "echos" the contents of another
+** table (like an SQL VIEW).
+*/
+static sqlite3_module echoModule = {
+  1,                         /* 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 */
+};
+
+static sqlite3_module echoModuleV2 = {
+  2,                         /* 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 */
+  echoSavepoint,
+  echoRelease,
+  echoRollbackTo
+};
+
+/*
+** Decode a pointer to an sqlite3 object.
+*/
+extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb);
+extern const char *sqlite3ErrName(int);
+
+static void moduleDestroy(void *p){
+  sqlite3_free(p);
+}
+
+/*
+** 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 *CONST objv[]  /* Command arguments */
+){
+  int rc;
+  sqlite3 *db;
+  EchoModule *pMod;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+
+  /* Virtual table module "echo" */
+  pMod = sqlite3_malloc(sizeof(EchoModule));
+  pMod->interp = interp;
+  rc = sqlite3_create_module_v2(
+      db, "echo", &echoModule, (void*)pMod, moduleDestroy
+  );
+
+  /* Virtual table module "echo_v2" */
+  if( rc==SQLITE_OK ){
+    pMod = sqlite3_malloc(sizeof(EchoModule));
+    pMod->interp = interp;
+    rc = sqlite3_create_module_v2(db, "echo_v2", 
+        &echoModuleV2, (void*)pMod, moduleDestroy
+    );
+  }
+
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+  return 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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  int rc;
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB SQL");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  rc = sqlite3_declare_vtab(db, Tcl_GetString(objv[2]));
+  if( rc!=SQLITE_OK ){
+    Tcl_SetResult(interp, (char *)sqlite3_errmsg(db), TCL_VOLATILE);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest8_Init(Tcl_Interp *interp){
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+     void *clientData;
+  } aObjCmd[] = {
+     { "register_echo_module",       register_echo_module, 0 },
+     { "sqlite3_declare_vtab",       declare_vtab, 0 },
+  };
+  int i;
+  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, 
+        aObjCmd[i].xProc, aObjCmd[i].clientData, 0);
+  }
+#endif
+  return TCL_OK;
+}

+ 200 - 0
components/external/sqlite/test/test9.c

@@ -0,0 +1,200 @@
+/*
+** 2007 March 29
+**
+** 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 contains obscure tests of the C-interface required
+** for completeness. Test code is written in C for these cases
+** as there is not much point in binding to Tcl.
+*/
+#include "sqliteInt.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+
+/*
+** c_collation_test
+*/
+static int c_collation_test(
+  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 *CONST objv[]  /* Command arguments */
+){
+  const char *zErrFunction = "N/A";
+  sqlite3 *db;
+
+  int rc;
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  /* Open a database. */
+  rc = sqlite3_open(":memory:", &db);
+  if( rc!=SQLITE_OK ){
+    zErrFunction = "sqlite3_open";
+    goto error_out;
+  }
+
+  rc = sqlite3_create_collation(db, "collate", 456, 0, 0);
+  if( rc!=SQLITE_MISUSE ){
+    sqlite3_close(db);
+    zErrFunction = "sqlite3_create_collation";
+    goto error_out;
+  }
+
+  sqlite3_close(db);
+  return TCL_OK;
+
+error_out:
+  Tcl_ResetResult(interp);
+  Tcl_AppendResult(interp, "Error testing function: ", zErrFunction, 0);
+  return TCL_ERROR;
+}
+
+/*
+** c_realloc_test
+*/
+static int c_realloc_test(
+  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 *CONST objv[]  /* Command arguments */
+){
+  void *p;
+  const char *zErrFunction = "N/A";
+
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  p = sqlite3_malloc(5);
+  if( !p ){
+    zErrFunction = "sqlite3_malloc";
+    goto error_out;
+  }
+
+  /* Test that realloc()ing a block of memory to a negative size is
+  ** the same as free()ing that memory.
+  */
+  p = sqlite3_realloc(p, -1);
+  if( p ){
+    zErrFunction = "sqlite3_realloc";
+    goto error_out;
+  }
+
+  return TCL_OK;
+
+error_out:
+  Tcl_ResetResult(interp);
+  Tcl_AppendResult(interp, "Error testing function: ", zErrFunction, 0);
+  return TCL_ERROR;
+}
+
+
+/*
+** c_misuse_test
+*/
+static int c_misuse_test(
+  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 *CONST objv[]  /* Command arguments */
+){
+  const char *zErrFunction = "N/A";
+  sqlite3 *db = 0;
+  sqlite3_stmt *pStmt;
+  int rc;
+
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  /* Open a database. Then close it again. We need to do this so that
+  ** we have a "closed database handle" to pass to various API functions.
+  */
+  rc = sqlite3_open(":memory:", &db);
+  if( rc!=SQLITE_OK ){
+    zErrFunction = "sqlite3_open";
+    goto error_out;
+  }
+  sqlite3_close(db);
+
+
+  rc = sqlite3_errcode(db);
+  if( rc!=SQLITE_MISUSE ){
+    zErrFunction = "sqlite3_errcode";
+    goto error_out;
+  }
+
+  pStmt = (sqlite3_stmt*)1234;
+  rc = sqlite3_prepare(db, 0, 0, &pStmt, 0);
+  if( rc!=SQLITE_MISUSE ){
+    zErrFunction = "sqlite3_prepare";
+    goto error_out;
+  }
+  assert( pStmt==0 ); /* Verify that pStmt is zeroed even on a MISUSE error */
+
+  pStmt = (sqlite3_stmt*)1234;
+  rc = sqlite3_prepare_v2(db, 0, 0, &pStmt, 0);
+  if( rc!=SQLITE_MISUSE ){
+    zErrFunction = "sqlite3_prepare_v2";
+    goto error_out;
+  }
+  assert( pStmt==0 );
+
+#ifndef SQLITE_OMIT_UTF16
+  pStmt = (sqlite3_stmt*)1234;
+  rc = sqlite3_prepare16(db, 0, 0, &pStmt, 0);
+  if( rc!=SQLITE_MISUSE ){
+    zErrFunction = "sqlite3_prepare16";
+    goto error_out;
+  }
+  assert( pStmt==0 );
+  pStmt = (sqlite3_stmt*)1234;
+  rc = sqlite3_prepare16_v2(db, 0, 0, &pStmt, 0);
+  if( rc!=SQLITE_MISUSE ){
+    zErrFunction = "sqlite3_prepare16_v2";
+    goto error_out;
+  }
+  assert( pStmt==0 );
+#endif
+
+  return TCL_OK;
+
+error_out:
+  Tcl_ResetResult(interp);
+  Tcl_AppendResult(interp, "Error testing function: ", zErrFunction, 0);
+  return TCL_ERROR;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest9_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+     void *clientData;
+  } aObjCmd[] = {
+     { "c_misuse_test",    c_misuse_test, 0 },
+     { "c_realloc_test",   c_realloc_test, 0 },
+     { "c_collation_test", c_collation_test, 0 },
+  };
+  int i;
+  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, 
+        aObjCmd[i].xProc, aObjCmd[i].clientData, 0);
+  }
+  return TCL_OK;
+}

+ 241 - 0
components/external/sqlite/test/test_async.c

@@ -0,0 +1,241 @@
+/*
+** 2005 December 14
+**
+** 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 contains a binding of the asynchronous IO extension interface
+** (defined in ext/async/sqlite3async.h) to Tcl.
+*/
+
+#define TCL_THREADS 
+#include <tcl.h>
+
+#ifdef SQLITE_ENABLE_ASYNCIO
+
+#include "sqlite3async.h"
+#include "sqlite3.h"
+#include <assert.h>
+
+/* From main.c */
+extern const char *sqlite3ErrName(int);
+
+
+struct TestAsyncGlobal {
+  int isInstalled;                     /* True when async VFS is installed */
+} testasync_g = { 0 };
+
+TCL_DECLARE_MUTEX(testasync_g_writerMutex);
+
+/*
+** sqlite3async_initialize PARENT-VFS ISDEFAULT
+*/
+static int testAsyncInit(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zParent;
+  int isDefault;
+  int rc;
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PARENT-VFS ISDEFAULT");
+    return TCL_ERROR;
+  }
+  zParent = Tcl_GetString(objv[1]);
+  if( !*zParent ) {
+    zParent = 0;
+  }
+  if( Tcl_GetBooleanFromObj(interp, objv[2], &isDefault) ){
+    return TCL_ERROR;
+  }
+
+  rc = sqlite3async_initialize(zParent, isDefault);
+  if( rc!=SQLITE_OK ){
+    Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** sqlite3async_shutdown
+*/
+static int testAsyncShutdown(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3async_shutdown();
+  return TCL_OK;
+}
+
+static Tcl_ThreadCreateType tclWriterThread(ClientData pIsStarted){
+  Tcl_MutexLock(&testasync_g_writerMutex);
+  *((int *)pIsStarted) = 1;
+  sqlite3async_run();
+  Tcl_MutexUnlock(&testasync_g_writerMutex);
+  Tcl_ExitThread(0);
+  TCL_THREAD_CREATE_RETURN;
+}
+
+/*
+** sqlite3async_start
+**
+** Start a new writer thread.
+*/
+static int testAsyncStart(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  volatile int isStarted = 0;
+  ClientData threadData = (ClientData)&isStarted;
+
+  Tcl_ThreadId x;
+  const int nStack = TCL_THREAD_STACK_DEFAULT;
+  const int flags = TCL_THREAD_NOFLAGS;
+  int rc;
+
+  rc = Tcl_CreateThread(&x, tclWriterThread, threadData, nStack, flags);
+  if( rc!=TCL_OK ){
+    Tcl_AppendResult(interp, "Tcl_CreateThread() failed", 0);
+    return TCL_ERROR;
+  }
+
+  while( isStarted==0 ) { /* Busy loop */ }
+  return TCL_OK;
+}
+
+/*
+** sqlite3async_wait
+**
+** Wait for the current writer thread to terminate.
+**
+** If the current writer thread is set to run forever then this
+** command would block forever.  To prevent that, an error is returned. 
+*/
+static int testAsyncWait(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int eCond;
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  sqlite3async_control(SQLITEASYNC_GET_HALT, &eCond);
+  if( eCond==SQLITEASYNC_HALT_NEVER ){
+    Tcl_AppendResult(interp, "would block forever", (char*)0);
+    return TCL_ERROR;
+  }
+
+  Tcl_MutexLock(&testasync_g_writerMutex);
+  Tcl_MutexUnlock(&testasync_g_writerMutex);
+  return TCL_OK;
+}
+
+/*
+** sqlite3async_control OPTION ?VALUE?
+*/
+static int testAsyncControl(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc = SQLITE_OK;
+  int aeOpt[] = { SQLITEASYNC_HALT, SQLITEASYNC_DELAY, SQLITEASYNC_LOCKFILES };
+  const char *azOpt[] = { "halt", "delay", "lockfiles", 0 };
+  const char *az[] = { "never", "now", "idle", 0 };
+  int iVal;
+  int eOpt;
+
+  if( objc!=2 && objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "OPTION ?VALUE?");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIndexFromObj(interp, objv[1], azOpt, "option", 0, &eOpt) ){
+    return TCL_ERROR;
+  }
+  eOpt = aeOpt[eOpt];
+
+  if( objc==3 ){
+    switch( eOpt ){
+      case SQLITEASYNC_HALT: {
+        assert( SQLITEASYNC_HALT_NEVER==0 );
+        assert( SQLITEASYNC_HALT_NOW==1 );
+        assert( SQLITEASYNC_HALT_IDLE==2 );
+        if( Tcl_GetIndexFromObj(interp, objv[2], az, "value", 0, &iVal) ){
+          return TCL_ERROR;
+        }
+        break;
+      }
+      case SQLITEASYNC_DELAY:
+        if( Tcl_GetIntFromObj(interp, objv[2], &iVal) ){
+          return TCL_ERROR;
+        }
+        break;
+
+      case SQLITEASYNC_LOCKFILES:
+        if( Tcl_GetBooleanFromObj(interp, objv[2], &iVal) ){
+          return TCL_ERROR;
+        }
+        break;
+    }
+
+    rc = sqlite3async_control(eOpt, iVal);
+  }
+
+  if( rc==SQLITE_OK ){
+    rc = sqlite3async_control(
+        eOpt==SQLITEASYNC_HALT ? SQLITEASYNC_GET_HALT :
+        eOpt==SQLITEASYNC_DELAY ? SQLITEASYNC_GET_DELAY :
+        SQLITEASYNC_GET_LOCKFILES, &iVal);
+  }
+
+  if( rc!=SQLITE_OK ){
+    Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+    return TCL_ERROR;
+  }
+
+  if( eOpt==SQLITEASYNC_HALT ){
+    Tcl_SetObjResult(interp, Tcl_NewStringObj(az[iVal], -1));
+  }else{
+    Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal));
+  }
+
+  return TCL_OK;
+}
+
+#endif  /* SQLITE_ENABLE_ASYNCIO */
+
+/*
+** This routine registers the custom TCL commands defined in this
+** module.  This should be the only procedure visible from outside
+** of this module.
+*/
+int Sqlitetestasync_Init(Tcl_Interp *interp){
+#ifdef SQLITE_ENABLE_ASYNCIO
+  Tcl_CreateObjCommand(interp,"sqlite3async_start",testAsyncStart,0,0);
+  Tcl_CreateObjCommand(interp,"sqlite3async_wait",testAsyncWait,0,0);
+
+  Tcl_CreateObjCommand(interp,"sqlite3async_control",testAsyncControl,0,0);
+  Tcl_CreateObjCommand(interp,"sqlite3async_initialize",testAsyncInit,0,0);
+  Tcl_CreateObjCommand(interp,"sqlite3async_shutdown",testAsyncShutdown,0,0);
+#endif  /* SQLITE_ENABLE_ASYNCIO */
+  return TCL_OK;
+}

+ 221 - 0
components/external/sqlite/test/test_autoext.c

@@ -0,0 +1,221 @@
+/*
+** 2006 August 23
+**
+** 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.
+**
+*************************************************************************
+** Test extension for testing the sqlite3_auto_extension() function.
+*/
+#include "tcl.h"
+#include "sqlite3ext.h"
+
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+SQLITE_EXTENSION_INIT1
+
+/*
+** The sqr() SQL function returns the square of its input value.
+*/
+static void sqrFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  double r = sqlite3_value_double(argv[0]);
+  sqlite3_result_double(context, r*r);
+}
+
+/*
+** This is the entry point to register the extension for the sqr() function.
+*/
+static int sqr_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  SQLITE_EXTENSION_INIT2(pApi);
+  sqlite3_create_function(db, "sqr", 1, SQLITE_ANY, 0, sqrFunc, 0, 0);
+  return 0;
+}
+
+/*
+** The cube() SQL function returns the cube of its input value.
+*/
+static void cubeFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  double r = sqlite3_value_double(argv[0]);
+  sqlite3_result_double(context, r*r*r);
+}
+
+/*
+** This is the entry point to register the extension for the cube() function.
+*/
+static int cube_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  SQLITE_EXTENSION_INIT2(pApi);
+  sqlite3_create_function(db, "cube", 1, SQLITE_ANY, 0, cubeFunc, 0, 0);
+  return 0;
+}
+
+/*
+** This is a broken extension entry point
+*/
+static int broken_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  char *zErr;
+  SQLITE_EXTENSION_INIT2(pApi);
+  zErr = sqlite3_mprintf("broken autoext!");
+  *pzErrMsg = zErr;
+  return 1;
+}
+
+/*
+** tclcmd:   sqlite3_auto_extension_sqr
+**
+** Register the "sqr" extension to be loaded automatically.
+*/
+static int autoExtSqrObjCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc = sqlite3_auto_extension((void*)sqr_init);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return SQLITE_OK;
+}
+
+/*
+** tclcmd:   sqlite3_cancel_auto_extension_sqr
+**
+** Unregister the "sqr" extension.
+*/
+static int cancelAutoExtSqrObjCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc = sqlite3_cancel_auto_extension((void*)sqr_init);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return SQLITE_OK;
+}
+
+/*
+** tclcmd:   sqlite3_auto_extension_cube
+**
+** Register the "cube" extension to be loaded automatically.
+*/
+static int autoExtCubeObjCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc = sqlite3_auto_extension((void*)cube_init);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return SQLITE_OK;
+}
+
+/*
+** tclcmd:   sqlite3_cancel_auto_extension_cube
+**
+** Unregister the "cube" extension.
+*/
+static int cancelAutoExtCubeObjCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc = sqlite3_cancel_auto_extension((void*)cube_init);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return SQLITE_OK;
+}
+
+/*
+** tclcmd:   sqlite3_auto_extension_broken
+**
+** Register the broken extension to be loaded automatically.
+*/
+static int autoExtBrokenObjCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc = sqlite3_auto_extension((void*)broken_init);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return SQLITE_OK;
+}
+
+/*
+** tclcmd:   sqlite3_cancel_auto_extension_broken
+**
+** Unregister the broken extension.
+*/
+static int cancelAutoExtBrokenObjCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc = sqlite3_cancel_auto_extension((void*)broken_init);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return SQLITE_OK;
+}
+
+#endif /* SQLITE_OMIT_LOAD_EXTENSION */
+
+
+/*
+** tclcmd:   sqlite3_reset_auto_extension
+**
+** Reset all auto-extensions
+*/
+static int resetAutoExtObjCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_reset_auto_extension();
+  return SQLITE_OK;
+}
+
+
+/*
+** This procedure registers the TCL procs defined in this file.
+*/
+int Sqlitetest_autoext_Init(Tcl_Interp *interp){
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+  Tcl_CreateObjCommand(interp, "sqlite3_auto_extension_sqr",
+          autoExtSqrObjCmd, 0, 0);
+  Tcl_CreateObjCommand(interp, "sqlite3_auto_extension_cube",
+          autoExtCubeObjCmd, 0, 0);
+  Tcl_CreateObjCommand(interp, "sqlite3_auto_extension_broken",
+          autoExtBrokenObjCmd, 0, 0);
+  Tcl_CreateObjCommand(interp, "sqlite3_cancel_auto_extension_sqr",
+          cancelAutoExtSqrObjCmd, 0, 0);
+  Tcl_CreateObjCommand(interp, "sqlite3_cancel_auto_extension_cube",
+          cancelAutoExtCubeObjCmd, 0, 0);
+  Tcl_CreateObjCommand(interp, "sqlite3_cancel_auto_extension_broken",
+          cancelAutoExtBrokenObjCmd, 0, 0);
+#endif
+  Tcl_CreateObjCommand(interp, "sqlite3_reset_auto_extension",
+          resetAutoExtObjCmd, 0, 0);
+  return TCL_OK;
+}

+ 150 - 0
components/external/sqlite/test/test_backup.c

@@ -0,0 +1,150 @@
+/*
+** 2009 January 28
+**
+** 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 contains test logic for the sqlite3_backup() interface.
+**
+*/
+
+#include "tcl.h"
+#include <sqlite3.h>
+#include <assert.h>
+
+/* These functions are implemented in main.c. */
+extern const char *sqlite3ErrName(int);
+
+/* These functions are implemented in test1.c. */
+extern int getDbPointer(Tcl_Interp *, const char *, sqlite3 **);
+
+static int backupTestCmd(
+  ClientData clientData, 
+  Tcl_Interp *interp, 
+  int objc,
+  Tcl_Obj *const*objv
+){
+  enum BackupSubCommandEnum {
+    BACKUP_STEP, BACKUP_FINISH, BACKUP_REMAINING, BACKUP_PAGECOUNT
+  };
+  struct BackupSubCommand {
+    const char *zCmd;
+    enum BackupSubCommandEnum eCmd;
+    int nArg;
+    const char *zArg;
+  } aSub[] = {
+    {"step",      BACKUP_STEP      , 1, "npage" },
+    {"finish",    BACKUP_FINISH    , 0, ""      },
+    {"remaining", BACKUP_REMAINING , 0, ""      },
+    {"pagecount", BACKUP_PAGECOUNT , 0, ""      },
+    {0, 0, 0, 0}
+  };
+
+  sqlite3_backup *p = (sqlite3_backup *)clientData;
+  int iCmd;
+  int rc;
+
+  rc = Tcl_GetIndexFromObjStruct(
+      interp, objv[1], aSub, sizeof(aSub[0]), "option", 0, &iCmd
+  );
+  if( rc!=TCL_OK ){
+    return rc;
+  }
+  if( objc!=(2 + aSub[iCmd].nArg) ){
+    Tcl_WrongNumArgs(interp, 2, objv, aSub[iCmd].zArg);
+    return TCL_ERROR;
+  }
+
+  switch( aSub[iCmd].eCmd ){
+
+    case BACKUP_FINISH: {
+      const char *zCmdName;
+      Tcl_CmdInfo cmdInfo;
+      zCmdName = Tcl_GetString(objv[0]);
+      Tcl_GetCommandInfo(interp, zCmdName, &cmdInfo);
+      cmdInfo.deleteProc = 0;
+      Tcl_SetCommandInfo(interp, zCmdName, &cmdInfo);
+      Tcl_DeleteCommand(interp, zCmdName);
+
+      rc = sqlite3_backup_finish(p);
+      Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+      break;
+    }
+
+    case BACKUP_STEP: {
+      int nPage;
+      if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &nPage) ){
+        return TCL_ERROR;
+      }
+      rc = sqlite3_backup_step(p, nPage);
+      Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+      break;
+    }
+
+    case BACKUP_REMAINING:
+      Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_backup_remaining(p)));
+      break;
+
+    case BACKUP_PAGECOUNT:
+      Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_backup_pagecount(p)));
+      break;
+  }
+
+  return TCL_OK;
+}
+
+static void backupTestFinish(ClientData clientData){
+  sqlite3_backup *pBackup = (sqlite3_backup *)clientData;
+  sqlite3_backup_finish(pBackup);
+}
+
+/*
+**     sqlite3_backup CMDNAME DESTHANDLE DESTNAME SRCHANDLE SRCNAME
+**
+*/
+static int backupTestInit(
+  ClientData clientData, 
+  Tcl_Interp *interp, 
+  int objc,
+  Tcl_Obj *const*objv
+){
+  sqlite3_backup *pBackup;
+  sqlite3 *pDestDb;
+  sqlite3 *pSrcDb;
+  const char *zDestName;
+  const char *zSrcName;
+  const char *zCmd;
+
+  if( objc!=6 ){
+    Tcl_WrongNumArgs(
+      interp, 1, objv, "CMDNAME DESTHANDLE DESTNAME SRCHANDLE SRCNAME"
+    );
+    return TCL_ERROR;
+  }
+
+  zCmd = Tcl_GetString(objv[1]);
+  getDbPointer(interp, Tcl_GetString(objv[2]), &pDestDb);
+  zDestName = Tcl_GetString(objv[3]);
+  getDbPointer(interp, Tcl_GetString(objv[4]), &pSrcDb);
+  zSrcName = Tcl_GetString(objv[5]);
+
+  pBackup = sqlite3_backup_init(pDestDb, zDestName, pSrcDb, zSrcName);
+  if( !pBackup ){
+    Tcl_AppendResult(interp, "sqlite3_backup_init() failed", 0);
+    return TCL_ERROR;
+  }
+
+  Tcl_CreateObjCommand(interp, zCmd, backupTestCmd, pBackup, backupTestFinish);
+  Tcl_SetObjResult(interp, objv[1]);
+  return TCL_OK;
+}
+
+int Sqlitetestbackup_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "sqlite3_backup", backupTestInit, 0, 0);
+  return TCL_OK;
+}

+ 62 - 0
components/external/sqlite/test/test_btree.c

@@ -0,0 +1,62 @@
+/*
+** 2007 May 05
+**
+** 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 btree.c module in SQLite.  This code
+** is not included in the SQLite library.  It is used for automated
+** testing of the SQLite library.
+*/
+#include "btreeInt.h"
+#include <tcl.h>
+
+/*
+** Usage: sqlite3_shared_cache_report
+**
+** Return a list of file that are shared and the number of
+** references to each file.
+*/
+int sqlite3BtreeSharedCacheReport(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_OMIT_SHARED_CACHE
+  extern BtShared *sqlite3SharedCacheList;
+  BtShared *pBt;
+  Tcl_Obj *pRet = Tcl_NewObj();
+  for(pBt=GLOBAL(BtShared*,sqlite3SharedCacheList); pBt; pBt=pBt->pNext){
+    const char *zFile = sqlite3PagerFilename(pBt->pPager, 1);
+    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj(zFile, -1));
+    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(pBt->nRef));
+  }
+  Tcl_SetObjResult(interp, pRet);
+#endif
+  return TCL_OK;
+}
+
+/*
+** Print debugging information about all cursors to standard output.
+*/
+void sqlite3BtreeCursorList(Btree *p){
+#ifdef SQLITE_DEBUG
+  BtCursor *pCur;
+  BtShared *pBt = p->pBt;
+  for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
+    MemPage *pPage = pCur->apPage[pCur->iPage];
+    char *zMode = pCur->wrFlag ? "rw" : "ro";
+    sqlite3DebugPrintf("CURSOR %p rooted at %4d(%s) currently at %d.%d%s\n",
+       pCur, pCur->pgnoRoot, zMode,
+       pPage ? pPage->pgno : 0, pCur->aiIdx[pCur->iPage],
+       (pCur->eState==CURSOR_VALID) ? "" : " eof"
+    );
+  }
+#endif
+}

+ 656 - 0
components/external/sqlite/test/test_config.c

@@ -0,0 +1,656 @@
+/*
+** 2007 May 7
+**
+** 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 contains code used for testing the SQLite system.
+** None of the code in this file goes into a deliverable build.
+** 
+** The focus of this file is providing the TCL testing layer
+** access to compile-time constants.
+*/
+
+#include "sqliteLimit.h"
+
+#include "sqliteInt.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+
+/*
+** Macro to stringify the results of the evaluation a pre-processor
+** macro. i.e. so that STRINGVALUE(SQLITE_NOMEM) -> "7".
+*/
+#define STRINGVALUE2(x) #x
+#define STRINGVALUE(x) STRINGVALUE2(x)
+
+/*
+** This routine sets entries in the global ::sqlite_options() array variable
+** according to the compile-time configuration of the database.  Test
+** procedures use this to determine when tests should be omitted.
+*/
+static void set_options(Tcl_Interp *interp){
+#ifdef HAVE_MALLOC_USABLE_SIZE
+  Tcl_SetVar2(interp, "sqlite_options", "malloc_usable_size", "1",
+              TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "malloc_usable_size", "0",
+              TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_32BIT_ROWID
+  Tcl_SetVar2(interp, "sqlite_options", "rowid32", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "rowid32", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_CASE_SENSITIVE_LIKE
+  Tcl_SetVar2(interp, "sqlite_options","casesensitivelike","1",TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options","casesensitivelike","0",TCL_GLOBAL_ONLY);
+#endif
+
+#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
+  Tcl_SetVar2(interp, "sqlite_options", "curdir", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "curdir", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_DEBUG
+  Tcl_SetVar2(interp, "sqlite_options", "debug", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "debug", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_DIRECT_OVERFLOW_READ
+  Tcl_SetVar2(interp, "sqlite_options", "direct_read", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "direct_read", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_DISABLE_DIRSYNC
+  Tcl_SetVar2(interp, "sqlite_options", "dirsync", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "dirsync", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_DISABLE_LFS
+  Tcl_SetVar2(interp, "sqlite_options", "lfs", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "lfs", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#if SQLITE_MAX_MMAP_SIZE>0
+  Tcl_SetVar2(interp, "sqlite_options", "mmap", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "mmap", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#if 1 /* def SQLITE_MEMDEBUG */
+  Tcl_SetVar2(interp, "sqlite_options", "memdebug", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "memdebug", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_8_3_NAMES
+  Tcl_SetVar2(interp, "sqlite_options", "8_3_names", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "8_3_names", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_MEMSYS3
+  Tcl_SetVar2(interp, "sqlite_options", "mem3", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "mem3", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_MEMSYS5
+  Tcl_SetVar2(interp, "sqlite_options", "mem5", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "mem5", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_MUTEX_OMIT
+  Tcl_SetVar2(interp, "sqlite_options", "mutex", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "mutex", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_MUTEX_NOOP
+  Tcl_SetVar2(interp, "sqlite_options", "mutex_noop", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "mutex_noop", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_ALTERTABLE
+  Tcl_SetVar2(interp, "sqlite_options", "altertable", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "altertable", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_ANALYZE
+  Tcl_SetVar2(interp, "sqlite_options", "analyze", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "analyze", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+  Tcl_SetVar2(interp, "sqlite_options", "atomicwrite", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "atomicwrite", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_ATTACH
+  Tcl_SetVar2(interp, "sqlite_options", "attach", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "attach", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_AUTHORIZATION
+  Tcl_SetVar2(interp, "sqlite_options", "auth", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "auth", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_AUTOINCREMENT
+  Tcl_SetVar2(interp, "sqlite_options", "autoinc", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "autoinc", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_AUTOMATIC_INDEX
+  Tcl_SetVar2(interp, "sqlite_options", "autoindex", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "autoindex", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_AUTORESET
+  Tcl_SetVar2(interp, "sqlite_options", "autoreset", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "autoreset", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_AUTOVACUUM
+  Tcl_SetVar2(interp, "sqlite_options", "autovacuum", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "autovacuum", "1", TCL_GLOBAL_ONLY);
+#endif /* SQLITE_OMIT_AUTOVACUUM */
+#if !defined(SQLITE_DEFAULT_AUTOVACUUM)
+  Tcl_SetVar2(interp,"sqlite_options","default_autovacuum","0",TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "default_autovacuum", 
+      STRINGVALUE(SQLITE_DEFAULT_AUTOVACUUM), TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_BETWEEN_OPTIMIZATION
+  Tcl_SetVar2(interp, "sqlite_options", "between_opt", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "between_opt", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_BUILTIN_TEST
+  Tcl_SetVar2(interp, "sqlite_options", "builtin_test", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "builtin_test", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_BLOB_LITERAL
+  Tcl_SetVar2(interp, "sqlite_options", "bloblit", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "bloblit", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_CAST
+  Tcl_SetVar2(interp, "sqlite_options", "cast", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "cast", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_CHECK
+  Tcl_SetVar2(interp, "sqlite_options", "check", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "check", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+  Tcl_SetVar2(interp, "sqlite_options", "columnmetadata", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "columnmetadata", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK
+  Tcl_SetVar2(interp, "sqlite_options", "oversize_cell_check", "1",
+              TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "oversize_cell_check", "0",
+              TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_COMPILEOPTION_DIAGS
+  Tcl_SetVar2(interp, "sqlite_options", "compileoption_diags", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "compileoption_diags", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_COMPLETE
+  Tcl_SetVar2(interp, "sqlite_options", "complete", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "complete", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_COMPOUND_SELECT
+  Tcl_SetVar2(interp, "sqlite_options", "compound", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "compound", "1", TCL_GLOBAL_ONLY);
+#endif
+
+  Tcl_SetVar2(interp, "sqlite_options", "conflict", "1", TCL_GLOBAL_ONLY);
+
+#if SQLITE_OS_UNIX
+  Tcl_SetVar2(interp, "sqlite_options", "crashtest", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "crashtest", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_DATETIME_FUNCS
+  Tcl_SetVar2(interp, "sqlite_options", "datetime", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "datetime", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_DECLTYPE
+  Tcl_SetVar2(interp, "sqlite_options", "decltype", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "decltype", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_DEPRECATED
+  Tcl_SetVar2(interp, "sqlite_options", "deprecated", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "deprecated", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_DISKIO
+  Tcl_SetVar2(interp, "sqlite_options", "diskio", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "diskio", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_EXPLAIN
+  Tcl_SetVar2(interp, "sqlite_options", "explain", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "explain", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_FLOATING_POINT
+  Tcl_SetVar2(interp, "sqlite_options", "floatingpoint", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "floatingpoint", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_FOREIGN_KEY
+  Tcl_SetVar2(interp, "sqlite_options", "foreignkey", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "foreignkey", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_FTS1
+  Tcl_SetVar2(interp, "sqlite_options", "fts1", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "fts1", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_FTS2
+  Tcl_SetVar2(interp, "sqlite_options", "fts2", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "fts2", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_FTS3
+  Tcl_SetVar2(interp, "sqlite_options", "fts3", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "fts3", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#if defined(SQLITE_ENABLE_FTS3) && defined(SQLITE_ENABLE_FTS4_UNICODE61)
+  Tcl_SetVar2(interp, "sqlite_options", "fts3_unicode", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "fts3_unicode", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_DISABLE_FTS4_DEFERRED
+  Tcl_SetVar2(interp, "sqlite_options", "fts4_deferred", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "fts4_deferred", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_GET_TABLE
+  Tcl_SetVar2(interp, "sqlite_options", "gettable", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "gettable", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_ICU
+  Tcl_SetVar2(interp, "sqlite_options", "icu", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "icu", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_INCRBLOB
+  Tcl_SetVar2(interp, "sqlite_options", "incrblob", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "incrblob", "1", TCL_GLOBAL_ONLY);
+#endif /* SQLITE_OMIT_AUTOVACUUM */
+
+#ifdef SQLITE_OMIT_INTEGRITY_CHECK
+  Tcl_SetVar2(interp, "sqlite_options", "integrityck", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "integrityck", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#if defined(SQLITE_DEFAULT_FILE_FORMAT) && SQLITE_DEFAULT_FILE_FORMAT==1
+  Tcl_SetVar2(interp, "sqlite_options", "legacyformat", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "legacyformat", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_LIKE_OPTIMIZATION
+  Tcl_SetVar2(interp, "sqlite_options", "like_opt", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "like_opt", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_LOAD_EXTENSION
+  Tcl_SetVar2(interp, "sqlite_options", "load_ext", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "load_ext", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_LOCALTIME
+  Tcl_SetVar2(interp, "sqlite_options", "localtime", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "localtime", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_LOOKASIDE
+  Tcl_SetVar2(interp, "sqlite_options", "lookaside", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "lookaside", "1", TCL_GLOBAL_ONLY);
+#endif
+
+Tcl_SetVar2(interp, "sqlite_options", "long_double",
+              sizeof(LONGDOUBLE_TYPE)>sizeof(double) ? "1" : "0",
+              TCL_GLOBAL_ONLY);
+
+#ifdef SQLITE_OMIT_MEMORYDB
+  Tcl_SetVar2(interp, "sqlite_options", "memorydb", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "memorydb", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+  Tcl_SetVar2(interp, "sqlite_options", "memorymanage", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "memorymanage", "0", TCL_GLOBAL_ONLY);
+#endif
+
+Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY);
+
+#ifdef SQLITE_OMIT_OR_OPTIMIZATION
+  Tcl_SetVar2(interp, "sqlite_options", "or_opt", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "or_opt", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_PAGER_PRAGMAS
+  Tcl_SetVar2(interp, "sqlite_options", "pager_pragmas", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "pager_pragmas", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#if defined(SQLITE_OMIT_PRAGMA) || defined(SQLITE_OMIT_FLAG_PRAGMAS)
+  Tcl_SetVar2(interp, "sqlite_options", "pragma", "0", TCL_GLOBAL_ONLY);
+  Tcl_SetVar2(interp, "sqlite_options", "integrityck", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "pragma", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_PROGRESS_CALLBACK
+  Tcl_SetVar2(interp, "sqlite_options", "progress", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "progress", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_REINDEX
+  Tcl_SetVar2(interp, "sqlite_options", "reindex", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "reindex", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_RTREE
+  Tcl_SetVar2(interp, "sqlite_options", "rtree", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "rtree", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_RTREE_INT_ONLY
+  Tcl_SetVar2(interp, "sqlite_options", "rtree_int_only", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "rtree_int_only", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_SCHEMA_PRAGMAS
+  Tcl_SetVar2(interp, "sqlite_options", "schema_pragmas", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "schema_pragmas", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
+  Tcl_SetVar2(interp, "sqlite_options", "schema_version", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "schema_version", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_ENABLE_STAT4
+  Tcl_SetVar2(interp, "sqlite_options", "stat4", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "stat4", "0", TCL_GLOBAL_ONLY);
+#endif
+#if defined(SQLITE_ENABLE_STAT3) && !defined(SQLITE_ENABLE_STAT4)
+  Tcl_SetVar2(interp, "sqlite_options", "stat3", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "stat3", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#if !defined(SQLITE_ENABLE_LOCKING_STYLE)
+#  if defined(__APPLE__)
+#    define SQLITE_ENABLE_LOCKING_STYLE 1
+#  else
+#    define SQLITE_ENABLE_LOCKING_STYLE 0
+#  endif
+#endif
+#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__)
+  Tcl_SetVar2(interp,"sqlite_options","lock_proxy_pragmas","1",TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp,"sqlite_options","lock_proxy_pragmas","0",TCL_GLOBAL_ONLY);
+#endif
+#if defined(SQLITE_PREFER_PROXY_LOCKING) && defined(__APPLE__)
+  Tcl_SetVar2(interp,"sqlite_options","prefer_proxy_locking","1",TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp,"sqlite_options","prefer_proxy_locking","0",TCL_GLOBAL_ONLY);
+#endif
+    
+    
+#ifdef SQLITE_OMIT_SHARED_CACHE
+  Tcl_SetVar2(interp, "sqlite_options", "shared_cache", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "shared_cache", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_SUBQUERY
+  Tcl_SetVar2(interp, "sqlite_options", "subquery", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "subquery", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_TCL_VARIABLE
+  Tcl_SetVar2(interp, "sqlite_options", "tclvar", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "tclvar", "1", TCL_GLOBAL_ONLY);
+#endif
+
+  Tcl_SetVar2(interp, "sqlite_options", "threadsafe", 
+      STRINGVALUE(SQLITE_THREADSAFE), TCL_GLOBAL_ONLY);
+  assert( sqlite3_threadsafe()==SQLITE_THREADSAFE );
+
+#ifdef SQLITE_OMIT_TEMPDB
+  Tcl_SetVar2(interp, "sqlite_options", "tempdb", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "tempdb", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_TRACE
+  Tcl_SetVar2(interp, "sqlite_options", "trace", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "trace", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_TRIGGER
+  Tcl_SetVar2(interp, "sqlite_options", "trigger", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "trigger", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_TRUNCATE_OPTIMIZATION
+  Tcl_SetVar2(interp, "sqlite_options", "truncate_opt", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "truncate_opt", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_UTF16
+  Tcl_SetVar2(interp, "sqlite_options", "utf16", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "utf16", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#if defined(SQLITE_OMIT_VACUUM) || defined(SQLITE_OMIT_ATTACH)
+  Tcl_SetVar2(interp, "sqlite_options", "vacuum", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "vacuum", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_VIEW
+  Tcl_SetVar2(interp, "sqlite_options", "view", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "view", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+  Tcl_SetVar2(interp, "sqlite_options", "vtab", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "vtab", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_WAL
+  Tcl_SetVar2(interp, "sqlite_options", "wal", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "wal", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_OMIT_WSD
+  Tcl_SetVar2(interp, "sqlite_options", "wsd", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "wsd", "1", TCL_GLOBAL_ONLY);
+#endif
+
+#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY)
+  Tcl_SetVar2(interp, "sqlite_options", "update_delete_limit", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "update_delete_limit", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#if defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+  Tcl_SetVar2(interp, "sqlite_options", "unlock_notify", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "unlock_notify", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_SECURE_DELETE
+  Tcl_SetVar2(interp, "sqlite_options", "secure_delete", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "secure_delete", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef SQLITE_MULTIPLEX_EXT_OVWR
+  Tcl_SetVar2(interp, "sqlite_options", "multiplex_ext_overwrite", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "multiplex_ext_overwrite", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#ifdef YYTRACKMAXSTACKDEPTH
+  Tcl_SetVar2(interp, "sqlite_options", "yytrackmaxstackdepth", "1", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "yytrackmaxstackdepth", "0", TCL_GLOBAL_ONLY);
+#endif
+
+#define LINKVAR(x) { \
+    static const int cv_ ## x = SQLITE_ ## x; \
+    Tcl_LinkVar(interp, "SQLITE_" #x, (char *)&(cv_ ## x), \
+                TCL_LINK_INT | TCL_LINK_READ_ONLY); }
+
+  LINKVAR( MAX_LENGTH );
+  LINKVAR( MAX_COLUMN );
+  LINKVAR( MAX_SQL_LENGTH );
+  LINKVAR( MAX_EXPR_DEPTH );
+  LINKVAR( MAX_COMPOUND_SELECT );
+  LINKVAR( MAX_VDBE_OP );
+  LINKVAR( MAX_FUNCTION_ARG );
+  LINKVAR( MAX_VARIABLE_NUMBER );
+  LINKVAR( MAX_PAGE_SIZE );
+  LINKVAR( MAX_PAGE_COUNT );
+  LINKVAR( MAX_LIKE_PATTERN_LENGTH );
+  LINKVAR( MAX_TRIGGER_DEPTH );
+  LINKVAR( DEFAULT_TEMP_CACHE_SIZE );
+  LINKVAR( DEFAULT_CACHE_SIZE );
+  LINKVAR( DEFAULT_PAGE_SIZE );
+  LINKVAR( DEFAULT_FILE_FORMAT );
+  LINKVAR( MAX_ATTACHED );
+  LINKVAR( MAX_DEFAULT_PAGE_SIZE );
+
+  {
+    static const int cv_TEMP_STORE = SQLITE_TEMP_STORE;
+    Tcl_LinkVar(interp, "TEMP_STORE", (char *)&(cv_TEMP_STORE),
+                TCL_LINK_INT | TCL_LINK_READ_ONLY);
+  }
+
+#ifdef _MSC_VER
+  {
+    static const int cv__MSC_VER = 1;
+    Tcl_LinkVar(interp, "_MSC_VER", (char *)&(cv__MSC_VER),
+                TCL_LINK_INT | TCL_LINK_READ_ONLY);
+  }
+#endif
+#ifdef __GNUC__
+  {
+    static const int cv___GNUC__ = 1;
+    Tcl_LinkVar(interp, "__GNUC__", (char *)&(cv___GNUC__),
+                TCL_LINK_INT | TCL_LINK_READ_ONLY);
+  }
+#endif
+}
+
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqliteconfig_Init(Tcl_Interp *interp){
+  set_options(interp);
+  return TCL_OK;
+}

+ 679 - 0
components/external/sqlite/test/test_demovfs.c

@@ -0,0 +1,679 @@
+/*
+** 2010 April 7
+**
+** 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 an example of a simple VFS implementation that 
+** omits complex features often not required or not possible on embedded
+** platforms.  Code is included to buffer writes to the journal file, 
+** which can be a significant performance improvement on some embedded
+** platforms.
+**
+** OVERVIEW
+**
+**   The code in this file implements a minimal SQLite VFS that can be 
+**   used on Linux and other posix-like operating systems. The following 
+**   system calls are used:
+**
+**    File-system: access(), unlink(), getcwd()
+**    File IO:     open(), read(), write(), fsync(), close(), fstat()
+**    Other:       sleep(), usleep(), time()
+**
+**   The following VFS features are omitted:
+**
+**     1. File locking. The user must ensure that there is at most one
+**        connection to each database when using this VFS. Multiple
+**        connections to a single shared-cache count as a single connection
+**        for the purposes of the previous statement.
+**
+**     2. The loading of dynamic extensions (shared libraries).
+**
+**     3. Temporary files. The user must configure SQLite to use in-memory
+**        temp files when using this VFS. The easiest way to do this is to
+**        compile with:
+**
+**          -DSQLITE_TEMP_STORE=3
+**
+**     4. File truncation. As of version 3.6.24, SQLite may run without
+**        a working xTruncate() call, providing the user does not configure
+**        SQLite to use "journal_mode=truncate", or use both
+**        "journal_mode=persist" and ATTACHed databases.
+**
+**   It is assumed that the system uses UNIX-like path-names. Specifically,
+**   that '/' characters are used to separate path components and that
+**   a path-name is a relative path unless it begins with a '/'. And that
+**   no UTF-8 encoded paths are greater than 512 bytes in length.
+**
+** JOURNAL WRITE-BUFFERING
+**
+**   To commit a transaction to the database, SQLite first writes rollback
+**   information into the journal file. This usually consists of 4 steps:
+**
+**     1. The rollback information is sequentially written into the journal
+**        file, starting at the start of the file.
+**     2. The journal file is synced to disk.
+**     3. A modification is made to the first few bytes of the journal file.
+**     4. The journal file is synced to disk again.
+**
+**   Most of the data is written in step 1 using a series of calls to the
+**   VFS xWrite() method. The buffers passed to the xWrite() calls are of
+**   various sizes. For example, as of version 3.6.24, when committing a 
+**   transaction that modifies 3 pages of a database file that uses 4096 
+**   byte pages residing on a media with 512 byte sectors, SQLite makes 
+**   eleven calls to the xWrite() method to create the rollback journal, 
+**   as follows:
+**
+**             Write offset | Bytes written
+**             ----------------------------
+**                        0            512
+**                      512              4
+**                      516           4096
+**                     4612              4
+**                     4616              4
+**                     4620           4096
+**                     8716              4
+**                     8720              4
+**                     8724           4096
+**                    12820              4
+**             ++++++++++++SYNC+++++++++++
+**                        0             12
+**             ++++++++++++SYNC+++++++++++
+**
+**   On many operating systems, this is an efficient way to write to a file.
+**   However, on some embedded systems that do not cache writes in OS 
+**   buffers it is much more efficient to write data in blocks that are
+**   an integer multiple of the sector-size in size and aligned at the
+**   start of a sector.
+**
+**   To work around this, the code in this file allocates a fixed size
+**   buffer of SQLITE_DEMOVFS_BUFFERSZ using sqlite3_malloc() whenever a 
+**   journal file is opened. It uses the buffer to coalesce sequential
+**   writes into aligned SQLITE_DEMOVFS_BUFFERSZ blocks. When SQLite
+**   invokes the xSync() method to sync the contents of the file to disk,
+**   all accumulated data is written out, even if it does not constitute
+**   a complete block. This means the actual IO to create the rollback 
+**   journal for the example transaction above is this:
+**
+**             Write offset | Bytes written
+**             ----------------------------
+**                        0           8192
+**                     8192           4632
+**             ++++++++++++SYNC+++++++++++
+**                        0             12
+**             ++++++++++++SYNC+++++++++++
+**
+**   Much more efficient if the underlying OS is not caching write 
+**   operations.
+*/
+
+#if !defined(SQLITE_TEST) || SQLITE_OS_UNIX
+
+#include <sqlite3.h>
+
+#include <assert.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+
+/*
+** Size of the write buffer used by journal files in bytes.
+*/
+#ifndef SQLITE_DEMOVFS_BUFFERSZ
+# define SQLITE_DEMOVFS_BUFFERSZ 8192
+#endif
+
+/*
+** The maximum pathname length supported by this VFS.
+*/
+#define MAXPATHNAME 512
+
+/*
+** When using this VFS, the sqlite3_file* handles that SQLite uses are
+** actually pointers to instances of type DemoFile.
+*/
+typedef struct DemoFile DemoFile;
+struct DemoFile {
+  sqlite3_file base;              /* Base class. Must be first. */
+  int fd;                         /* File descriptor */
+
+  char *aBuffer;                  /* Pointer to malloc'd buffer */
+  int nBuffer;                    /* Valid bytes of data in zBuffer */
+  sqlite3_int64 iBufferOfst;      /* Offset in file of zBuffer[0] */
+};
+
+/*
+** Write directly to the file passed as the first argument. Even if the
+** file has a write-buffer (DemoFile.aBuffer), ignore it.
+*/
+static int demoDirectWrite(
+  DemoFile *p,                    /* File handle */
+  const void *zBuf,               /* Buffer containing data to write */
+  int iAmt,                       /* Size of data to write in bytes */
+  sqlite_int64 iOfst              /* File offset to write to */
+){
+  off_t ofst;                     /* Return value from lseek() */
+  size_t nWrite;                  /* Return value from write() */
+
+  ofst = lseek(p->fd, iOfst, SEEK_SET);
+  if( ofst!=iOfst ){
+    return SQLITE_IOERR_WRITE;
+  }
+
+  nWrite = write(p->fd, zBuf, iAmt);
+  if( nWrite!=iAmt ){
+    return SQLITE_IOERR_WRITE;
+  }
+
+  return SQLITE_OK;
+}
+
+/*
+** Flush the contents of the DemoFile.aBuffer buffer to disk. This is a
+** no-op if this particular file does not have a buffer (i.e. it is not
+** a journal file) or if the buffer is currently empty.
+*/
+static int demoFlushBuffer(DemoFile *p){
+  int rc = SQLITE_OK;
+  if( p->nBuffer ){
+    rc = demoDirectWrite(p, p->aBuffer, p->nBuffer, p->iBufferOfst);
+    p->nBuffer = 0;
+  }
+  return rc;
+}
+
+/*
+** Close a file.
+*/
+static int demoClose(sqlite3_file *pFile){
+  int rc;
+  DemoFile *p = (DemoFile*)pFile;
+  rc = demoFlushBuffer(p);
+  sqlite3_free(p->aBuffer);
+  close(p->fd);
+  return rc;
+}
+
+/*
+** Read data from a file.
+*/
+static int demoRead(
+  sqlite3_file *pFile, 
+  void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  DemoFile *p = (DemoFile*)pFile;
+  off_t ofst;                     /* Return value from lseek() */
+  int nRead;                      /* Return value from read() */
+  int rc;                         /* Return code from demoFlushBuffer() */
+
+  /* Flush any data in the write buffer to disk in case this operation
+  ** is trying to read data the file-region currently cached in the buffer.
+  ** It would be possible to detect this case and possibly save an 
+  ** unnecessary write here, but in practice SQLite will rarely read from
+  ** a journal file when there is data cached in the write-buffer.
+  */
+  rc = demoFlushBuffer(p);
+  if( rc!=SQLITE_OK ){
+    return rc;
+  }
+
+  ofst = lseek(p->fd, iOfst, SEEK_SET);
+  if( ofst!=iOfst ){
+    return SQLITE_IOERR_READ;
+  }
+  nRead = read(p->fd, zBuf, iAmt);
+
+  if( nRead==iAmt ){
+    return SQLITE_OK;
+  }else if( nRead>=0 ){
+    return SQLITE_IOERR_SHORT_READ;
+  }
+
+  return SQLITE_IOERR_READ;
+}
+
+/*
+** Write data to a crash-file.
+*/
+static int demoWrite(
+  sqlite3_file *pFile, 
+  const void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  DemoFile *p = (DemoFile*)pFile;
+  
+  if( p->aBuffer ){
+    char *z = (char *)zBuf;       /* Pointer to remaining data to write */
+    int n = iAmt;                 /* Number of bytes at z */
+    sqlite3_int64 i = iOfst;      /* File offset to write to */
+
+    while( n>0 ){
+      int nCopy;                  /* Number of bytes to copy into buffer */
+
+      /* If the buffer is full, or if this data is not being written directly
+      ** following the data already buffered, flush the buffer. Flushing
+      ** the buffer is a no-op if it is empty.  
+      */
+      if( p->nBuffer==SQLITE_DEMOVFS_BUFFERSZ || p->iBufferOfst+p->nBuffer!=i ){
+        int rc = demoFlushBuffer(p);
+        if( rc!=SQLITE_OK ){
+          return rc;
+        }
+      }
+      assert( p->nBuffer==0 || p->iBufferOfst+p->nBuffer==i );
+      p->iBufferOfst = i - p->nBuffer;
+
+      /* Copy as much data as possible into the buffer. */
+      nCopy = SQLITE_DEMOVFS_BUFFERSZ - p->nBuffer;
+      if( nCopy>n ){
+        nCopy = n;
+      }
+      memcpy(&p->aBuffer[p->nBuffer], z, nCopy);
+      p->nBuffer += nCopy;
+
+      n -= nCopy;
+      i += nCopy;
+      z += nCopy;
+    }
+  }else{
+    return demoDirectWrite(p, zBuf, iAmt, iOfst);
+  }
+
+  return SQLITE_OK;
+}
+
+/*
+** Truncate a file. This is a no-op for this VFS (see header comments at
+** the top of the file).
+*/
+static int demoTruncate(sqlite3_file *pFile, sqlite_int64 size){
+#if 0
+  if( ftruncate(((DemoFile *)pFile)->fd, size) ) return SQLITE_IOERR_TRUNCATE;
+#endif
+  return SQLITE_OK;
+}
+
+/*
+** Sync the contents of the file to the persistent media.
+*/
+static int demoSync(sqlite3_file *pFile, int flags){
+  DemoFile *p = (DemoFile*)pFile;
+  int rc;
+
+  rc = demoFlushBuffer(p);
+  if( rc!=SQLITE_OK ){
+    return rc;
+  }
+
+  rc = fsync(p->fd);
+  return (rc==0 ? SQLITE_OK : SQLITE_IOERR_FSYNC);
+}
+
+/*
+** Write the size of the file in bytes to *pSize.
+*/
+static int demoFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+  DemoFile *p = (DemoFile*)pFile;
+  int rc;                         /* Return code from fstat() call */
+  struct stat sStat;              /* Output of fstat() call */
+
+  /* Flush the contents of the buffer to disk. As with the flush in the
+  ** demoRead() method, it would be possible to avoid this and save a write
+  ** here and there. But in practice this comes up so infrequently it is
+  ** not worth the trouble.
+  */
+  rc = demoFlushBuffer(p);
+  if( rc!=SQLITE_OK ){
+    return rc;
+  }
+
+  rc = fstat(p->fd, &sStat);
+  if( rc!=0 ) return SQLITE_IOERR_FSTAT;
+  *pSize = sStat.st_size;
+  return SQLITE_OK;
+}
+
+/*
+** Locking functions. The xLock() and xUnlock() methods are both no-ops.
+** The xCheckReservedLock() always indicates that no other process holds
+** a reserved lock on the database file. This ensures that if a hot-journal
+** file is found in the file-system it is rolled back.
+*/
+static int demoLock(sqlite3_file *pFile, int eLock){
+  return SQLITE_OK;
+}
+static int demoUnlock(sqlite3_file *pFile, int eLock){
+  return SQLITE_OK;
+}
+static int demoCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+  *pResOut = 0;
+  return SQLITE_OK;
+}
+
+/*
+** No xFileControl() verbs are implemented by this VFS.
+*/
+static int demoFileControl(sqlite3_file *pFile, int op, void *pArg){
+  return SQLITE_OK;
+}
+
+/*
+** The xSectorSize() and xDeviceCharacteristics() methods. These two
+** may return special values allowing SQLite to optimize file-system 
+** access to some extent. But it is also safe to simply return 0.
+*/
+static int demoSectorSize(sqlite3_file *pFile){
+  return 0;
+}
+static int demoDeviceCharacteristics(sqlite3_file *pFile){
+  return 0;
+}
+
+/*
+** Open a file handle.
+*/
+static int demoOpen(
+  sqlite3_vfs *pVfs,              /* VFS */
+  const char *zName,              /* File to open, or 0 for a temp file */
+  sqlite3_file *pFile,            /* Pointer to DemoFile struct to populate */
+  int flags,                      /* Input SQLITE_OPEN_XXX flags */
+  int *pOutFlags                  /* Output SQLITE_OPEN_XXX flags (or NULL) */
+){
+  static const sqlite3_io_methods demoio = {
+    1,                            /* iVersion */
+    demoClose,                    /* xClose */
+    demoRead,                     /* xRead */
+    demoWrite,                    /* xWrite */
+    demoTruncate,                 /* xTruncate */
+    demoSync,                     /* xSync */
+    demoFileSize,                 /* xFileSize */
+    demoLock,                     /* xLock */
+    demoUnlock,                   /* xUnlock */
+    demoCheckReservedLock,        /* xCheckReservedLock */
+    demoFileControl,              /* xFileControl */
+    demoSectorSize,               /* xSectorSize */
+    demoDeviceCharacteristics     /* xDeviceCharacteristics */
+  };
+
+  DemoFile *p = (DemoFile*)pFile; /* Populate this structure */
+  int oflags = 0;                 /* flags to pass to open() call */
+  char *aBuf = 0;
+
+  if( zName==0 ){
+    return SQLITE_IOERR;
+  }
+
+  if( flags&SQLITE_OPEN_MAIN_JOURNAL ){
+    aBuf = (char *)sqlite3_malloc(SQLITE_DEMOVFS_BUFFERSZ);
+    if( !aBuf ){
+      return SQLITE_NOMEM;
+    }
+  }
+
+  if( flags&SQLITE_OPEN_EXCLUSIVE ) oflags |= O_EXCL;
+  if( flags&SQLITE_OPEN_CREATE )    oflags |= O_CREAT;
+  if( flags&SQLITE_OPEN_READONLY )  oflags |= O_RDONLY;
+  if( flags&SQLITE_OPEN_READWRITE ) oflags |= O_RDWR;
+
+  memset(p, 0, sizeof(DemoFile));
+  p->fd = open(zName, oflags, 0600);
+  if( p->fd<0 ){
+    sqlite3_free(aBuf);
+    return SQLITE_CANTOPEN;
+  }
+  p->aBuffer = aBuf;
+
+  if( pOutFlags ){
+    *pOutFlags = flags;
+  }
+  p->base.pMethods = &demoio;
+  return SQLITE_OK;
+}
+
+/*
+** Delete the file identified by argument zPath. If the dirSync parameter
+** is non-zero, then ensure the file-system modification to delete the
+** file has been synced to disk before returning.
+*/
+static int demoDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+  int rc;                         /* Return code */
+
+  rc = unlink(zPath);
+  if( rc!=0 && errno==ENOENT ) return SQLITE_OK;
+
+  if( rc==0 && dirSync ){
+    int dfd;                      /* File descriptor open on directory */
+    int i;                        /* Iterator variable */
+    char zDir[MAXPATHNAME+1];     /* Name of directory containing file zPath */
+
+    /* Figure out the directory name from the path of the file deleted. */
+    sqlite3_snprintf(MAXPATHNAME, zDir, "%s", zPath);
+    zDir[MAXPATHNAME] = '\0';
+    for(i=strlen(zDir); i>1 && zDir[i]!='/'; i++);
+    zDir[i] = '\0';
+
+    /* Open a file-descriptor on the directory. Sync. Close. */
+    dfd = open(zDir, O_RDONLY, 0);
+    if( dfd<0 ){
+      rc = -1;
+    }else{
+      rc = fsync(dfd);
+      close(dfd);
+    }
+  }
+  return (rc==0 ? SQLITE_OK : SQLITE_IOERR_DELETE);
+}
+
+#ifndef F_OK
+# define F_OK 0
+#endif
+#ifndef R_OK
+# define R_OK 4
+#endif
+#ifndef W_OK
+# define W_OK 2
+#endif
+
+/*
+** Query the file-system to see if the named file exists, is readable or
+** is both readable and writable.
+*/
+static int demoAccess(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int flags, 
+  int *pResOut
+){
+  int rc;                         /* access() return code */
+  int eAccess = F_OK;             /* Second argument to access() */
+
+  assert( flags==SQLITE_ACCESS_EXISTS       /* access(zPath, F_OK) */
+       || flags==SQLITE_ACCESS_READ         /* access(zPath, R_OK) */
+       || flags==SQLITE_ACCESS_READWRITE    /* access(zPath, R_OK|W_OK) */
+  );
+
+  if( flags==SQLITE_ACCESS_READWRITE ) eAccess = R_OK|W_OK;
+  if( flags==SQLITE_ACCESS_READ )      eAccess = R_OK;
+
+  rc = access(zPath, eAccess);
+  *pResOut = (rc==0);
+  return SQLITE_OK;
+}
+
+/*
+** Argument zPath points to a nul-terminated string containing a file path.
+** If zPath is an absolute path, then it is copied as is into the output 
+** buffer. Otherwise, if it is a relative path, then the equivalent full
+** path is written to the output buffer.
+**
+** This function assumes that paths are UNIX style. Specifically, that:
+**
+**   1. Path components are separated by a '/'. and 
+**   2. Full paths begin with a '/' character.
+*/
+static int demoFullPathname(
+  sqlite3_vfs *pVfs,              /* VFS */
+  const char *zPath,              /* Input path (possibly a relative path) */
+  int nPathOut,                   /* Size of output buffer in bytes */
+  char *zPathOut                  /* Pointer to output buffer */
+){
+  char zDir[MAXPATHNAME+1];
+  if( zPath[0]=='/' ){
+    zDir[0] = '\0';
+  }else{
+    if( getcwd(zDir, sizeof(zDir))==0 ) return SQLITE_IOERR;
+  }
+  zDir[MAXPATHNAME] = '\0';
+
+  sqlite3_snprintf(nPathOut, zPathOut, "%s/%s", zDir, zPath);
+  zPathOut[nPathOut-1] = '\0';
+
+  return SQLITE_OK;
+}
+
+/*
+** The following four VFS methods:
+**
+**   xDlOpen
+**   xDlError
+**   xDlSym
+**   xDlClose
+**
+** are supposed to implement the functionality needed by SQLite to load
+** extensions compiled as shared objects. This simple VFS does not support
+** this functionality, so the following functions are no-ops.
+*/
+static void *demoDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+  return 0;
+}
+static void demoDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+  sqlite3_snprintf(nByte, zErrMsg, "Loadable extensions are not supported");
+  zErrMsg[nByte-1] = '\0';
+}
+static void (*demoDlSym(sqlite3_vfs *pVfs, void *pH, const char *z))(void){
+  return 0;
+}
+static void demoDlClose(sqlite3_vfs *pVfs, void *pHandle){
+  return;
+}
+
+/*
+** Parameter zByte points to a buffer nByte bytes in size. Populate this
+** buffer with pseudo-random data.
+*/
+static int demoRandomness(sqlite3_vfs *pVfs, int nByte, char *zByte){
+  return SQLITE_OK;
+}
+
+/*
+** Sleep for at least nMicro microseconds. Return the (approximate) number 
+** of microseconds slept for.
+*/
+static int demoSleep(sqlite3_vfs *pVfs, int nMicro){
+  sleep(nMicro / 1000000);
+  usleep(nMicro % 1000000);
+  return nMicro;
+}
+
+/*
+** Set *pTime to the current UTC time expressed as a Julian day. Return
+** SQLITE_OK if successful, or an error code otherwise.
+**
+**   http://en.wikipedia.org/wiki/Julian_day
+**
+** This implementation is not very good. The current time is rounded to
+** an integer number of seconds. Also, assuming time_t is a signed 32-bit 
+** value, it will stop working some time in the year 2038 AD (the so-called
+** "year 2038" problem that afflicts systems that store time this way). 
+*/
+static int demoCurrentTime(sqlite3_vfs *pVfs, double *pTime){
+  time_t t = time(0);
+  *pTime = t/86400.0 + 2440587.5; 
+  return SQLITE_OK;
+}
+
+/*
+** This function returns a pointer to the VFS implemented in this file.
+** To make the VFS available to SQLite:
+**
+**   sqlite3_vfs_register(sqlite3_demovfs(), 0);
+*/
+sqlite3_vfs *sqlite3_demovfs(void){
+  static sqlite3_vfs demovfs = {
+    1,                            /* iVersion */
+    sizeof(DemoFile),             /* szOsFile */
+    MAXPATHNAME,                  /* mxPathname */
+    0,                            /* pNext */
+    "demo",                       /* zName */
+    0,                            /* pAppData */
+    demoOpen,                     /* xOpen */
+    demoDelete,                   /* xDelete */
+    demoAccess,                   /* xAccess */
+    demoFullPathname,             /* xFullPathname */
+    demoDlOpen,                   /* xDlOpen */
+    demoDlError,                  /* xDlError */
+    demoDlSym,                    /* xDlSym */
+    demoDlClose,                  /* xDlClose */
+    demoRandomness,               /* xRandomness */
+    demoSleep,                    /* xSleep */
+    demoCurrentTime,              /* xCurrentTime */
+  };
+  return &demovfs;
+}
+
+#endif /* !defined(SQLITE_TEST) || SQLITE_OS_UNIX */
+
+
+#ifdef SQLITE_TEST
+
+#include <tcl.h>
+
+#if SQLITE_OS_UNIX
+static int register_demovfs(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3_vfs_register(sqlite3_demovfs(), 1);
+  return TCL_OK;
+}
+static int unregister_demovfs(
+  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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3_vfs_unregister(sqlite3_demovfs());
+  return TCL_OK;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest_demovfs_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "register_demovfs", register_demovfs, 0, 0);
+  Tcl_CreateObjCommand(interp, "unregister_demovfs", unregister_demovfs, 0, 0);
+  return TCL_OK;
+}
+
+#else
+int Sqlitetest_demovfs_Init(Tcl_Interp *interp){ return TCL_OK; }
+#endif
+
+#endif /* SQLITE_TEST */

+ 398 - 0
components/external/sqlite/test/test_devsym.c

@@ -0,0 +1,398 @@
+/*
+** 2008 Jan 22
+**
+** 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 contains code that modified the OS layer in order to simulate
+** different device types (by overriding the return values of the 
+** xDeviceCharacteristics() and xSectorSize() methods).
+*/
+#if SQLITE_TEST          /* This file is used for testing only */
+
+#include "sqlite3.h"
+#include "sqliteInt.h"
+
+/*
+** Maximum pathname length supported by the devsym backend.
+*/
+#define DEVSYM_MAX_PATHNAME 512
+
+/*
+** Name used to identify this VFS.
+*/
+#define DEVSYM_VFS_NAME "devsym"
+
+typedef struct devsym_file devsym_file;
+struct devsym_file {
+  sqlite3_file base;
+  sqlite3_file *pReal;
+};
+
+/*
+** Method declarations for devsym_file.
+*/
+static int devsymClose(sqlite3_file*);
+static int devsymRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int devsymWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
+static int devsymTruncate(sqlite3_file*, sqlite3_int64 size);
+static int devsymSync(sqlite3_file*, int flags);
+static int devsymFileSize(sqlite3_file*, sqlite3_int64 *pSize);
+static int devsymLock(sqlite3_file*, int);
+static int devsymUnlock(sqlite3_file*, int);
+static int devsymCheckReservedLock(sqlite3_file*, int *);
+static int devsymFileControl(sqlite3_file*, int op, void *pArg);
+static int devsymSectorSize(sqlite3_file*);
+static int devsymDeviceCharacteristics(sqlite3_file*);
+static int devsymShmLock(sqlite3_file*,int,int,int);
+static int devsymShmMap(sqlite3_file*,int,int,int, void volatile **);
+static void devsymShmBarrier(sqlite3_file*);
+static int devsymShmUnmap(sqlite3_file*,int);
+
+/*
+** Method declarations for devsym_vfs.
+*/
+static int devsymOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+static int devsymDelete(sqlite3_vfs*, const char *zName, int syncDir);
+static int devsymAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+static int devsymFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+static void *devsymDlOpen(sqlite3_vfs*, const char *zFilename);
+static void devsymDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
+static void (*devsymDlSym(sqlite3_vfs*,void*, const char *zSymbol))(void);
+static void devsymDlClose(sqlite3_vfs*, void*);
+#endif /* SQLITE_OMIT_LOAD_EXTENSION */
+static int devsymRandomness(sqlite3_vfs*, int nByte, char *zOut);
+static int devsymSleep(sqlite3_vfs*, int microseconds);
+static int devsymCurrentTime(sqlite3_vfs*, double*);
+
+static sqlite3_vfs devsym_vfs = {
+  2,                     /* iVersion */
+  sizeof(devsym_file),      /* szOsFile */
+  DEVSYM_MAX_PATHNAME,      /* mxPathname */
+  0,                     /* pNext */
+  DEVSYM_VFS_NAME,          /* zName */
+  0,                     /* pAppData */
+  devsymOpen,               /* xOpen */
+  devsymDelete,             /* xDelete */
+  devsymAccess,             /* xAccess */
+  devsymFullPathname,       /* xFullPathname */
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+  devsymDlOpen,             /* xDlOpen */
+  devsymDlError,            /* xDlError */
+  devsymDlSym,              /* xDlSym */
+  devsymDlClose,            /* xDlClose */
+#else
+  0,                        /* xDlOpen */
+  0,                        /* xDlError */
+  0,                        /* xDlSym */
+  0,                        /* xDlClose */
+#endif /* SQLITE_OMIT_LOAD_EXTENSION */
+  devsymRandomness,         /* xRandomness */
+  devsymSleep,              /* xSleep */
+  devsymCurrentTime,        /* xCurrentTime */
+  0,                        /* xGetLastError */
+  0                         /* xCurrentTimeInt64 */
+};
+
+static sqlite3_io_methods devsym_io_methods = {
+  2,                                /* iVersion */
+  devsymClose,                      /* xClose */
+  devsymRead,                       /* xRead */
+  devsymWrite,                      /* xWrite */
+  devsymTruncate,                   /* xTruncate */
+  devsymSync,                       /* xSync */
+  devsymFileSize,                   /* xFileSize */
+  devsymLock,                       /* xLock */
+  devsymUnlock,                     /* xUnlock */
+  devsymCheckReservedLock,          /* xCheckReservedLock */
+  devsymFileControl,                /* xFileControl */
+  devsymSectorSize,                 /* xSectorSize */
+  devsymDeviceCharacteristics,      /* xDeviceCharacteristics */
+  devsymShmMap,                     /* xShmMap */
+  devsymShmLock,                    /* xShmLock */
+  devsymShmBarrier,                 /* xShmBarrier */
+  devsymShmUnmap                    /* xShmUnmap */
+};
+
+struct DevsymGlobal {
+  sqlite3_vfs *pVfs;
+  int iDeviceChar;
+  int iSectorSize;
+};
+struct DevsymGlobal g = {0, 0, 512};
+
+/*
+** Close an devsym-file.
+*/
+static int devsymClose(sqlite3_file *pFile){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsClose(p->pReal);
+}
+
+/*
+** Read data from an devsym-file.
+*/
+static int devsymRead(
+  sqlite3_file *pFile, 
+  void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsRead(p->pReal, zBuf, iAmt, iOfst);
+}
+
+/*
+** Write data to an devsym-file.
+*/
+static int devsymWrite(
+  sqlite3_file *pFile, 
+  const void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsWrite(p->pReal, zBuf, iAmt, iOfst);
+}
+
+/*
+** Truncate an devsym-file.
+*/
+static int devsymTruncate(sqlite3_file *pFile, sqlite_int64 size){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsTruncate(p->pReal, size);
+}
+
+/*
+** Sync an devsym-file.
+*/
+static int devsymSync(sqlite3_file *pFile, int flags){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsSync(p->pReal, flags);
+}
+
+/*
+** Return the current file-size of an devsym-file.
+*/
+static int devsymFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsFileSize(p->pReal, pSize);
+}
+
+/*
+** Lock an devsym-file.
+*/
+static int devsymLock(sqlite3_file *pFile, int eLock){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsLock(p->pReal, eLock);
+}
+
+/*
+** Unlock an devsym-file.
+*/
+static int devsymUnlock(sqlite3_file *pFile, int eLock){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsUnlock(p->pReal, eLock);
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on an devsym-file.
+*/
+static int devsymCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsCheckReservedLock(p->pReal, pResOut);
+}
+
+/*
+** File control method. For custom operations on an devsym-file.
+*/
+static int devsymFileControl(sqlite3_file *pFile, int op, void *pArg){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsFileControl(p->pReal, op, pArg);
+}
+
+/*
+** Return the sector-size in bytes for an devsym-file.
+*/
+static int devsymSectorSize(sqlite3_file *pFile){
+  return g.iSectorSize;
+}
+
+/*
+** Return the device characteristic flags supported by an devsym-file.
+*/
+static int devsymDeviceCharacteristics(sqlite3_file *pFile){
+  return g.iDeviceChar;
+}
+
+/*
+** Shared-memory methods are all pass-thrus.
+*/
+static int devsymShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsShmLock(p->pReal, ofst, n, flags);
+}
+static int devsymShmMap(
+  sqlite3_file *pFile, 
+  int iRegion, 
+  int szRegion, 
+  int isWrite, 
+  void volatile **pp
+){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsShmMap(p->pReal, iRegion, szRegion, isWrite, pp);
+}
+static void devsymShmBarrier(sqlite3_file *pFile){
+  devsym_file *p = (devsym_file *)pFile;
+  sqlite3OsShmBarrier(p->pReal);
+}
+static int devsymShmUnmap(sqlite3_file *pFile, int delFlag){
+  devsym_file *p = (devsym_file *)pFile;
+  return sqlite3OsShmUnmap(p->pReal, delFlag);
+}
+
+
+
+/*
+** Open an devsym file handle.
+*/
+static int devsymOpen(
+  sqlite3_vfs *pVfs,
+  const char *zName,
+  sqlite3_file *pFile,
+  int flags,
+  int *pOutFlags
+){
+  int rc;
+  devsym_file *p = (devsym_file *)pFile;
+  p->pReal = (sqlite3_file *)&p[1];
+  rc = sqlite3OsOpen(g.pVfs, zName, p->pReal, flags, pOutFlags);
+  if( p->pReal->pMethods ){
+    pFile->pMethods = &devsym_io_methods;
+  }
+  return rc;
+}
+
+/*
+** Delete the file located at zPath. If the dirSync argument is true,
+** ensure the file-system modifications are synced to disk before
+** returning.
+*/
+static int devsymDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+  return sqlite3OsDelete(g.pVfs, zPath, dirSync);
+}
+
+/*
+** Test for access permissions. Return true if the requested permission
+** is available, or false otherwise.
+*/
+static int devsymAccess(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int flags, 
+  int *pResOut
+){
+  return sqlite3OsAccess(g.pVfs, zPath, flags, pResOut);
+}
+
+/*
+** Populate buffer zOut with the full canonical pathname corresponding
+** to the pathname in zPath. zOut is guaranteed to point to a buffer
+** of at least (DEVSYM_MAX_PATHNAME+1) bytes.
+*/
+static int devsymFullPathname(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int nOut, 
+  char *zOut
+){
+  return sqlite3OsFullPathname(g.pVfs, zPath, nOut, zOut);
+}
+
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+/*
+** Open the dynamic library located at zPath and return a handle.
+*/
+static void *devsymDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+  return sqlite3OsDlOpen(g.pVfs, zPath);
+}
+
+/*
+** Populate the buffer zErrMsg (size nByte bytes) with a human readable
+** utf-8 string describing the most recent error encountered associated 
+** with dynamic libraries.
+*/
+static void devsymDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+  sqlite3OsDlError(g.pVfs, nByte, zErrMsg);
+}
+
+/*
+** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
+*/
+static void (*devsymDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
+  return sqlite3OsDlSym(g.pVfs, p, zSym);
+}
+
+/*
+** Close the dynamic library handle pHandle.
+*/
+static void devsymDlClose(sqlite3_vfs *pVfs, void *pHandle){
+  sqlite3OsDlClose(g.pVfs, pHandle);
+}
+#endif /* SQLITE_OMIT_LOAD_EXTENSION */
+
+/*
+** Populate the buffer pointed to by zBufOut with nByte bytes of 
+** random data.
+*/
+static int devsymRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+  return sqlite3OsRandomness(g.pVfs, nByte, zBufOut);
+}
+
+/*
+** Sleep for nMicro microseconds. Return the number of microseconds 
+** actually slept.
+*/
+static int devsymSleep(sqlite3_vfs *pVfs, int nMicro){
+  return sqlite3OsSleep(g.pVfs, nMicro);
+}
+
+/*
+** Return the current time as a Julian Day number in *pTimeOut.
+*/
+static int devsymCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+  return g.pVfs->xCurrentTime(g.pVfs, pTimeOut);
+}
+
+
+/*
+** This procedure registers the devsym vfs with SQLite. If the argument is
+** true, the devsym vfs becomes the new default vfs. It is the only publicly
+** available function in this file.
+*/
+void devsym_register(int iDeviceChar, int iSectorSize){
+  if( g.pVfs==0 ){
+    g.pVfs = sqlite3_vfs_find(0);
+    devsym_vfs.szOsFile += g.pVfs->szOsFile;
+    sqlite3_vfs_register(&devsym_vfs, 0);
+  }
+  if( iDeviceChar>=0 ){
+    g.iDeviceChar = iDeviceChar;
+  }else{
+    g.iDeviceChar = 0;
+  }
+  if( iSectorSize>=0 ){
+    g.iSectorSize = iSectorSize;
+  }else{
+    g.iSectorSize = 512;
+  }
+}
+
+#endif

+ 335 - 0
components/external/sqlite/test/test_fs.c

@@ -0,0 +1,335 @@
+/*
+** 2013 Jan 11
+**
+** 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.
+**
+** The FS virtual table is created as follows:
+**
+**   CREATE VIRTUAL TABLE tbl USING fs(idx);
+**
+** where idx is the name of a table in the db with 2 columns.  The virtual
+** table also has two columns - file path and file contents.
+**
+** The first column of table idx must be an IPK, and the second contains file
+** paths. For example:
+**
+**   CREATE TABLE idx(id INTEGER PRIMARY KEY, path TEXT);
+**   INSERT INTO idx VALUES(4, '/etc/passwd');
+**
+** Adding the row to the idx table automatically creates a row in the 
+** virtual table with rowid=4, path=/etc/passwd and a text field that 
+** contains data read from file /etc/passwd on disk.
+*/
+#include "sqliteInt.h"
+#include "tcl.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#if SQLITE_OS_UNIX
+# include <unistd.h>
+#endif
+#if SQLITE_OS_WIN
+# include <io.h>
+#endif
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+typedef struct fs_vtab fs_vtab;
+typedef struct fs_cursor fs_cursor;
+
+/* 
+** A fs virtual-table object 
+*/
+struct fs_vtab {
+  sqlite3_vtab base;
+  sqlite3 *db;
+  char *zDb;                      /* Name of db containing zTbl */
+  char *zTbl;                     /* Name of docid->file map table */
+};
+
+/* A fs cursor object */
+struct fs_cursor {
+  sqlite3_vtab_cursor base;
+  sqlite3_stmt *pStmt;
+  char *zBuf;
+  int nBuf;
+  int nAlloc;
+};
+
+/*
+** This function is the implementation of both the xConnect and xCreate
+** methods of the fs virtual table.
+**
+** The argv[] array contains the following:
+**
+**   argv[0]   -> module name  ("fs")
+**   argv[1]   -> database name
+**   argv[2]   -> table name
+**   argv[...] -> other module argument fields.
+*/
+static int fsConnect(
+  sqlite3 *db,
+  void *pAux,
+  int argc, const char *const*argv,
+  sqlite3_vtab **ppVtab,
+  char **pzErr
+){
+  fs_vtab *pVtab;
+  int nByte;
+  const char *zTbl;
+  const char *zDb = argv[1];
+
+  if( argc!=4 ){
+    *pzErr = sqlite3_mprintf("wrong number of arguments");
+    return SQLITE_ERROR;
+  }
+  zTbl = argv[3];
+
+  nByte = sizeof(fs_vtab) + (int)strlen(zTbl) + 1 + (int)strlen(zDb) + 1;
+  pVtab = (fs_vtab *)sqlite3MallocZero( nByte );
+  if( !pVtab ) return SQLITE_NOMEM;
+
+  pVtab->zTbl = (char *)&pVtab[1];
+  pVtab->zDb = &pVtab->zTbl[strlen(zTbl)+1];
+  pVtab->db = db;
+  memcpy(pVtab->zTbl, zTbl, strlen(zTbl));
+  memcpy(pVtab->zDb, zDb, strlen(zDb));
+  *ppVtab = &pVtab->base;
+  sqlite3_declare_vtab(db, "CREATE TABLE xyz(path TEXT, data TEXT)");
+
+  return SQLITE_OK;
+}
+/* Note that for this virtual table, the xCreate and xConnect
+** methods are identical. */
+
+static int fsDisconnect(sqlite3_vtab *pVtab){
+  sqlite3_free(pVtab);
+  return SQLITE_OK;
+}
+/* The xDisconnect and xDestroy methods are also the same */
+
+/*
+** Open a new fs cursor.
+*/
+static int fsOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+  fs_cursor *pCur;
+  pCur = sqlite3MallocZero(sizeof(fs_cursor));
+  *ppCursor = &pCur->base;
+  return SQLITE_OK;
+}
+
+/*
+** Close a fs cursor.
+*/
+static int fsClose(sqlite3_vtab_cursor *cur){
+  fs_cursor *pCur = (fs_cursor *)cur;
+  sqlite3_finalize(pCur->pStmt);
+  sqlite3_free(pCur->zBuf);
+  sqlite3_free(pCur);
+  return SQLITE_OK;
+}
+
+static int fsNext(sqlite3_vtab_cursor *cur){
+  fs_cursor *pCur = (fs_cursor *)cur;
+  int rc;
+
+  rc = sqlite3_step(pCur->pStmt);
+  if( rc==SQLITE_ROW || rc==SQLITE_DONE ) rc = SQLITE_OK;
+
+  return rc;
+}
+
+static int fsFilter(
+  sqlite3_vtab_cursor *pVtabCursor, 
+  int idxNum, const char *idxStr,
+  int argc, sqlite3_value **argv
+){
+  int rc;
+  fs_cursor *pCur = (fs_cursor *)pVtabCursor;
+  fs_vtab *p = (fs_vtab *)(pVtabCursor->pVtab);
+
+  assert( (idxNum==0 && argc==0) || (idxNum==1 && argc==1) );
+  if( idxNum==1 ){
+    char *zStmt = sqlite3_mprintf(
+        "SELECT * FROM %Q.%Q WHERE rowid=?", p->zDb, p->zTbl);
+    if( !zStmt ) return SQLITE_NOMEM;
+    rc = sqlite3_prepare_v2(p->db, zStmt, -1, &pCur->pStmt, 0);
+    sqlite3_free(zStmt);
+    if( rc==SQLITE_OK ){
+      sqlite3_bind_value(pCur->pStmt, 1, argv[0]);
+    }
+  }else{
+    char *zStmt = sqlite3_mprintf("SELECT * FROM %Q.%Q", p->zDb, p->zTbl);
+    if( !zStmt ) return SQLITE_NOMEM;
+    rc = sqlite3_prepare_v2(p->db, zStmt, -1, &pCur->pStmt, 0);
+    sqlite3_free(zStmt);
+  }
+
+  if( rc==SQLITE_OK ){
+    rc = fsNext(pVtabCursor); 
+  }
+  return rc;
+}
+
+static int fsColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
+  fs_cursor *pCur = (fs_cursor*)cur;
+
+  assert( i==0 || i==1 );
+  if( i==0 ){
+    sqlite3_result_value(ctx, sqlite3_column_value(pCur->pStmt, 0));
+  }else{
+    const char *zFile = (const char *)sqlite3_column_text(pCur->pStmt, 1);
+    struct stat sbuf;
+    int fd;
+    int n;
+
+    fd = open(zFile, O_RDONLY);
+    if( fd<0 ) return SQLITE_IOERR;
+    fstat(fd, &sbuf);
+
+    if( sbuf.st_size>=pCur->nAlloc ){
+      int nNew = sbuf.st_size*2;
+      char *zNew;
+      if( nNew<1024 ) nNew = 1024;
+
+      zNew = sqlite3Realloc(pCur->zBuf, nNew);
+      if( zNew==0 ){
+        close(fd);
+        return SQLITE_NOMEM;
+      }
+      pCur->zBuf = zNew;
+      pCur->nAlloc = nNew;
+    }
+
+    n = (int)read(fd, pCur->zBuf, sbuf.st_size);
+    close(fd);
+    if( n!=sbuf.st_size ) return SQLITE_ERROR;
+    pCur->nBuf = sbuf.st_size;
+    pCur->zBuf[pCur->nBuf] = '\0';
+
+    sqlite3_result_text(ctx, pCur->zBuf, -1, SQLITE_TRANSIENT);
+  }
+  return SQLITE_OK;
+}
+
+static int fsRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+  fs_cursor *pCur = (fs_cursor*)cur;
+  *pRowid = sqlite3_column_int64(pCur->pStmt, 0);
+  return SQLITE_OK;
+}
+
+static int fsEof(sqlite3_vtab_cursor *cur){
+  fs_cursor *pCur = (fs_cursor*)cur;
+  return (sqlite3_data_count(pCur->pStmt)==0);
+}
+
+static int fsBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+  int ii;
+
+  for(ii=0; ii<pIdxInfo->nConstraint; ii++){
+    struct sqlite3_index_constraint const *pCons = &pIdxInfo->aConstraint[ii];
+    if( pCons->iColumn<0 && pCons->usable
+           && pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+      struct sqlite3_index_constraint_usage *pUsage;
+      pUsage = &pIdxInfo->aConstraintUsage[ii];
+      pUsage->omit = 0;
+      pUsage->argvIndex = 1;
+      pIdxInfo->idxNum = 1;
+      pIdxInfo->estimatedCost = 1.0;
+      break;
+    }
+  }
+
+  return SQLITE_OK;
+}
+
+/*
+** A virtual table module that provides read-only access to a
+** Tcl global variable namespace.
+*/
+static sqlite3_module fsModule = {
+  0,                         /* iVersion */
+  fsConnect,
+  fsConnect,
+  fsBestIndex,
+  fsDisconnect, 
+  fsDisconnect,
+  fsOpen,                      /* xOpen - open a cursor */
+  fsClose,                     /* xClose - close a cursor */
+  fsFilter,                    /* xFilter - configure scan constraints */
+  fsNext,                      /* xNext - advance a cursor */
+  fsEof,                       /* xEof - check for end of scan */
+  fsColumn,                    /* xColumn - read data */
+  fsRowid,                     /* xRowid - read data */
+  0,                           /* xUpdate */
+  0,                           /* xBegin */
+  0,                           /* xSync */
+  0,                           /* xCommit */
+  0,                           /* xRollback */
+  0,                           /* xFindMethod */
+  0,                           /* xRename */
+};
+
+/*
+** Decode a pointer to an sqlite3 object.
+*/
+extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb);
+
+/*
+** Register the echo virtual table module.
+*/
+static int register_fs_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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  sqlite3_create_module(db, "fs", &fsModule, (void *)interp);
+#endif
+  return TCL_OK;
+}
+
+#endif
+
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetestfs_Init(Tcl_Interp *interp){
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+     void *clientData;
+  } aObjCmd[] = {
+     { "register_fs_module",   register_fs_module, 0 },
+  };
+  int i;
+  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, 
+        aObjCmd[i].xProc, aObjCmd[i].clientData, 0);
+  }
+#endif
+  return TCL_OK;
+}

+ 767 - 0
components/external/sqlite/test/test_func.c

@@ -0,0 +1,767 @@
+/*
+** 2008 March 19
+**
+** 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 all sorts of SQLite interfaces.  This code
+** implements new SQL functions used by the test scripts.
+*/
+#include "sqlite3.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "sqliteInt.h"
+#include "vdbeInt.h"
+
+
+/*
+** Allocate nByte bytes of space using sqlite3_malloc(). If the
+** allocation fails, call sqlite3_result_error_nomem() to notify
+** the database handle that malloc() has failed.
+*/
+static void *testContextMalloc(sqlite3_context *context, int nByte){
+  char *z = sqlite3_malloc(nByte);
+  if( !z && nByte>0 ){
+    sqlite3_result_error_nomem(context);
+  }
+  return z;
+}
+
+/*
+** This function generates a string of random characters.  Used for
+** generating test data.
+*/
+static void randStr(sqlite3_context *context, int argc, sqlite3_value **argv){
+  static const unsigned char zSrc[] = 
+     "abcdefghijklmnopqrstuvwxyz"
+     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+     "0123456789"
+     ".-!,:*^+=_|?/<> ";
+  int iMin, iMax, n, r, i;
+  unsigned char zBuf[1000];
+
+  /* It used to be possible to call randstr() with any number of arguments,
+  ** but now it is registered with SQLite as requiring exactly 2.
+  */
+  assert(argc==2);
+
+  iMin = sqlite3_value_int(argv[0]);
+  if( iMin<0 ) iMin = 0;
+  if( iMin>=sizeof(zBuf) ) iMin = sizeof(zBuf)-1;
+  iMax = sqlite3_value_int(argv[1]);
+  if( iMax<iMin ) iMax = iMin;
+  if( iMax>=sizeof(zBuf) ) iMax = sizeof(zBuf)-1;
+  n = iMin;
+  if( iMax>iMin ){
+    sqlite3_randomness(sizeof(r), &r);
+    r &= 0x7fffffff;
+    n += r%(iMax + 1 - iMin);
+  }
+  assert( n<sizeof(zBuf) );
+  sqlite3_randomness(n, zBuf);
+  for(i=0; i<n; i++){
+    zBuf[i] = zSrc[zBuf[i]%(sizeof(zSrc)-1)];
+  }
+  zBuf[n] = 0;
+  sqlite3_result_text(context, (char*)zBuf, n, SQLITE_TRANSIENT);
+}
+
+/*
+** The following two SQL functions are used to test returning a text
+** result with a destructor. Function 'test_destructor' takes one argument
+** and returns the same argument interpreted as TEXT. A destructor is
+** passed with the sqlite3_result_text() call.
+**
+** SQL function 'test_destructor_count' returns the number of outstanding 
+** allocations made by 'test_destructor';
+**
+** WARNING: Not threadsafe.
+*/
+static int test_destructor_count_var = 0;
+static void destructor(void *p){
+  char *zVal = (char *)p;
+  assert(zVal);
+  zVal--;
+  sqlite3_free(zVal);
+  test_destructor_count_var--;
+}
+static void test_destructor(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  char *zVal;
+  int len;
+  
+  test_destructor_count_var++;
+  assert( nArg==1 );
+  if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
+  len = sqlite3_value_bytes(argv[0]); 
+  zVal = testContextMalloc(pCtx, len+3);
+  if( !zVal ){
+    return;
+  }
+  zVal[len+1] = 0;
+  zVal[len+2] = 0;
+  zVal++;
+  memcpy(zVal, sqlite3_value_text(argv[0]), len);
+  sqlite3_result_text(pCtx, zVal, -1, destructor);
+}
+#ifndef SQLITE_OMIT_UTF16
+static void test_destructor16(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  char *zVal;
+  int len;
+  
+  test_destructor_count_var++;
+  assert( nArg==1 );
+  if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
+  len = sqlite3_value_bytes16(argv[0]); 
+  zVal = testContextMalloc(pCtx, len+3);
+  if( !zVal ){
+    return;
+  }
+  zVal[len+1] = 0;
+  zVal[len+2] = 0;
+  zVal++;
+  memcpy(zVal, sqlite3_value_text16(argv[0]), len);
+  sqlite3_result_text16(pCtx, zVal, -1, destructor);
+}
+#endif
+static void test_destructor_count(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  sqlite3_result_int(pCtx, test_destructor_count_var);
+}
+
+/*
+** The following aggregate function, test_agg_errmsg16(), takes zero 
+** arguments. It returns the text value returned by the sqlite3_errmsg16()
+** API function.
+*/
+#ifndef SQLITE_OMIT_BUILTIN_TEST
+void sqlite3BeginBenignMalloc(void);
+void sqlite3EndBenignMalloc(void);
+#else
+  #define sqlite3BeginBenignMalloc()
+  #define sqlite3EndBenignMalloc()
+#endif
+static void test_agg_errmsg16_step(sqlite3_context *a, int b,sqlite3_value **c){
+}
+static void test_agg_errmsg16_final(sqlite3_context *ctx){
+#ifndef SQLITE_OMIT_UTF16
+  const void *z;
+  sqlite3 * db = sqlite3_context_db_handle(ctx);
+  sqlite3_aggregate_context(ctx, 2048);
+  sqlite3BeginBenignMalloc();
+  z = sqlite3_errmsg16(db);
+  sqlite3EndBenignMalloc();
+  sqlite3_result_text16(ctx, z, -1, SQLITE_TRANSIENT);
+#endif
+}
+
+/*
+** Routines for testing the sqlite3_get_auxdata() and sqlite3_set_auxdata()
+** interface.
+**
+** The test_auxdata() SQL function attempts to register each of its arguments
+** as auxiliary data.  If there are no prior registrations of aux data for
+** that argument (meaning the argument is not a constant or this is its first
+** call) then the result for that argument is 0.  If there is a prior
+** registration, the result for that argument is 1.  The overall result
+** is the individual argument results separated by spaces.
+*/
+static void free_test_auxdata(void *p) {sqlite3_free(p);}
+static void test_auxdata(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  int i;
+  char *zRet = testContextMalloc(pCtx, nArg*2);
+  if( !zRet ) return;
+  memset(zRet, 0, nArg*2);
+  for(i=0; i<nArg; i++){
+    char const *z = (char*)sqlite3_value_text(argv[i]);
+    if( z ){
+      int n;
+      char *zAux = sqlite3_get_auxdata(pCtx, i);
+      if( zAux ){
+        zRet[i*2] = '1';
+        assert( strcmp(zAux,z)==0 );
+      }else {
+        zRet[i*2] = '0';
+      }
+      n = (int)strlen(z) + 1;
+      zAux = testContextMalloc(pCtx, n);
+      if( zAux ){
+        memcpy(zAux, z, n);
+        sqlite3_set_auxdata(pCtx, i, zAux, free_test_auxdata);
+      }
+      zRet[i*2+1] = ' ';
+    }
+  }
+  sqlite3_result_text(pCtx, zRet, 2*nArg-1, free_test_auxdata);
+}
+
+/*
+** A function to test error reporting from user functions. This function
+** returns a copy of its first argument as the error message.  If the
+** second argument exists, it becomes the error code.
+*/
+static void test_error(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  sqlite3_result_error(pCtx, (char*)sqlite3_value_text(argv[0]), -1);
+  if( nArg==2 ){
+    sqlite3_result_error_code(pCtx, sqlite3_value_int(argv[1]));
+  }
+}
+
+/*
+** Implementation of the counter(X) function.  If X is an integer
+** constant, then the first invocation will return X.  The second X+1.
+** and so forth.  Can be used (for example) to provide a sequence number
+** in a result set.
+*/
+static void counterFunc(
+  sqlite3_context *pCtx,   /* Function context */
+  int nArg,                /* Number of function arguments */
+  sqlite3_value **argv     /* Values for all function arguments */
+){
+  int *pCounter = (int*)sqlite3_get_auxdata(pCtx, 0);
+  if( pCounter==0 ){
+    pCounter = sqlite3_malloc( sizeof(*pCounter) );
+    if( pCounter==0 ){
+      sqlite3_result_error_nomem(pCtx);
+      return;
+    }
+    *pCounter = sqlite3_value_int(argv[0]);
+    sqlite3_set_auxdata(pCtx, 0, pCounter, sqlite3_free);
+  }else{
+    ++*pCounter;
+  }
+  sqlite3_result_int(pCtx, *pCounter);
+}
+
+
+/*
+** This function takes two arguments.  It performance UTF-8/16 type
+** conversions on the first argument then returns a copy of the second
+** argument.
+**
+** This function is used in cases such as the following:
+**
+**      SELECT test_isolation(x,x) FROM t1;
+**
+** We want to verify that the type conversions that occur on the
+** first argument do not invalidate the second argument.
+*/
+static void test_isolation(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+#ifndef SQLITE_OMIT_UTF16
+  sqlite3_value_text16(argv[0]);
+  sqlite3_value_text(argv[0]);
+  sqlite3_value_text16(argv[0]);
+  sqlite3_value_text(argv[0]);
+#endif
+  sqlite3_result_value(pCtx, argv[1]);
+}
+
+/*
+** Invoke an SQL statement recursively.  The function result is the 
+** first column of the first row of the result set.
+*/
+static void test_eval(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  sqlite3_stmt *pStmt;
+  int rc;
+  sqlite3 *db = sqlite3_context_db_handle(pCtx);
+  const char *zSql;
+
+  zSql = (char*)sqlite3_value_text(argv[0]);
+  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_step(pStmt);
+    if( rc==SQLITE_ROW ){
+      sqlite3_result_value(pCtx, sqlite3_column_value(pStmt, 0));
+    }
+    rc = sqlite3_finalize(pStmt);
+  }
+  if( rc ){
+    char *zErr;
+    assert( pStmt==0 );
+    zErr = sqlite3_mprintf("sqlite3_prepare_v2() error: %s",sqlite3_errmsg(db));
+    sqlite3_result_text(pCtx, zErr, -1, sqlite3_free);
+    sqlite3_result_error_code(pCtx, rc);
+  }
+}
+
+
+/*
+** convert one character from hex to binary
+*/
+static int testHexChar(char c){
+  if( c>='0' && c<='9' ){
+    return c - '0';
+  }else if( c>='a' && c<='f' ){
+    return c - 'a' + 10;
+  }else if( c>='A' && c<='F' ){
+    return c - 'A' + 10;
+  }
+  return 0;
+}
+
+/*
+** Convert hex to binary.
+*/
+static void testHexToBin(const char *zIn, char *zOut){
+  while( zIn[0] && zIn[1] ){
+    *(zOut++) = (testHexChar(zIn[0])<<4) + testHexChar(zIn[1]);
+    zIn += 2;
+  }
+}
+
+/*
+**      hex_to_utf16be(HEX)
+**
+** Convert the input string from HEX into binary.  Then return the
+** result using sqlite3_result_text16le().
+*/
+#ifndef SQLITE_OMIT_UTF16
+static void testHexToUtf16be(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  int n;
+  const char *zIn;
+  char *zOut;
+  assert( nArg==1 );
+  n = sqlite3_value_bytes(argv[0]);
+  zIn = (const char*)sqlite3_value_text(argv[0]);
+  zOut = sqlite3_malloc( n/2 );
+  if( zOut==0 ){
+    sqlite3_result_error_nomem(pCtx);
+  }else{
+    testHexToBin(zIn, zOut);
+    sqlite3_result_text16be(pCtx, zOut, n/2, sqlite3_free);
+  }
+}
+#endif
+
+/*
+**      hex_to_utf8(HEX)
+**
+** Convert the input string from HEX into binary.  Then return the
+** result using sqlite3_result_text16le().
+*/
+static void testHexToUtf8(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  int n;
+  const char *zIn;
+  char *zOut;
+  assert( nArg==1 );
+  n = sqlite3_value_bytes(argv[0]);
+  zIn = (const char*)sqlite3_value_text(argv[0]);
+  zOut = sqlite3_malloc( n/2 );
+  if( zOut==0 ){
+    sqlite3_result_error_nomem(pCtx);
+  }else{
+    testHexToBin(zIn, zOut);
+    sqlite3_result_text(pCtx, zOut, n/2, sqlite3_free);
+  }
+}
+
+/*
+**      hex_to_utf16le(HEX)
+**
+** Convert the input string from HEX into binary.  Then return the
+** result using sqlite3_result_text16le().
+*/
+#ifndef SQLITE_OMIT_UTF16
+static void testHexToUtf16le(
+  sqlite3_context *pCtx, 
+  int nArg,
+  sqlite3_value **argv
+){
+  int n;
+  const char *zIn;
+  char *zOut;
+  assert( nArg==1 );
+  n = sqlite3_value_bytes(argv[0]);
+  zIn = (const char*)sqlite3_value_text(argv[0]);
+  zOut = sqlite3_malloc( n/2 );
+  if( zOut==0 ){
+    sqlite3_result_error_nomem(pCtx);
+  }else{
+    testHexToBin(zIn, zOut);
+    sqlite3_result_text16le(pCtx, zOut, n/2, sqlite3_free);
+  }
+}
+#endif
+
+/*
+** SQL function:   real2hex(X)
+**
+** If argument X is a real number, then convert it into a string which is
+** the big-endian hexadecimal representation of the ieee754 encoding of
+** that number.  If X is not a real number, return NULL.
+*/
+static void real2hex(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  union {
+    sqlite3_uint64 i;
+    double r;
+    unsigned char x[8];
+  } v;
+  char zOut[20];
+  int i;
+  int bigEndian;
+  v.i = 1;
+  bigEndian = v.x[0]==0;
+  v.r = sqlite3_value_double(argv[0]);
+  for(i=0; i<8; i++){
+    if( bigEndian ){
+      zOut[i*2]   = "0123456789abcdef"[v.x[i]>>4];
+      zOut[i*2+1] = "0123456789abcdef"[v.x[i]&0xf];
+    }else{
+      zOut[14-i*2]   = "0123456789abcdef"[v.x[i]>>4];
+      zOut[14-i*2+1] = "0123456789abcdef"[v.x[i]&0xf];
+    }
+  }
+  zOut[16] = 0;
+  sqlite3_result_text(context, zOut, -1, SQLITE_TRANSIENT);
+}
+
+/*
+** tclcmd: test_extract(record, field)
+**
+** This function implements an SQL user-function that accepts a blob
+** containing a formatted database record as the first argument. The
+** second argument is the index of the field within that record to
+** extract and return.
+*/
+static void test_extract(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  sqlite3 *db = sqlite3_context_db_handle(context);
+  u8 *pRec;
+  u8 *pEndHdr;                    /* Points to one byte past record header */
+  u8 *pHdr;                       /* Current point in record header */
+  u8 *pBody;                      /* Current point in record data */
+  u64 nHdr;                       /* Bytes in record header */
+  int iIdx;                       /* Required field */
+  int iCurrent = 0;               /* Current field */
+
+  assert( argc==2 );
+  pRec = (u8*)sqlite3_value_blob(argv[0]);
+  iIdx = sqlite3_value_int(argv[1]);
+
+  pHdr = pRec + sqlite3GetVarint(pRec, &nHdr);
+  pBody = pEndHdr = &pRec[nHdr];
+
+  for(iCurrent=0; pHdr<pEndHdr && iCurrent<=iIdx; iCurrent++){
+    u64 iSerialType;
+    Mem mem;
+
+    memset(&mem, 0, sizeof(mem));
+    mem.db = db;
+    mem.enc = ENC(db);
+    pHdr += sqlite3GetVarint(pHdr, &iSerialType);
+    pBody += sqlite3VdbeSerialGet(pBody, (u32)iSerialType, &mem);
+    sqlite3VdbeMemStoreType(&mem);
+
+    if( iCurrent==iIdx ){
+      sqlite3_result_value(context, &mem);
+    }
+
+    sqlite3DbFree(db, mem.zMalloc);
+  }
+}
+
+/*
+** tclcmd: test_decode(record)
+**
+** This function implements an SQL user-function that accepts a blob
+** containing a formatted database record as its only argument. It returns
+** a tcl list (type SQLITE_TEXT) containing each of the values stored
+** in the record.
+*/
+static void test_decode(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  sqlite3 *db = sqlite3_context_db_handle(context);
+  u8 *pRec;
+  u8 *pEndHdr;                    /* Points to one byte past record header */
+  u8 *pHdr;                       /* Current point in record header */
+  u8 *pBody;                      /* Current point in record data */
+  u64 nHdr;                       /* Bytes in record header */
+  Tcl_Obj *pRet;                  /* Return value */
+
+  pRet = Tcl_NewObj();
+  Tcl_IncrRefCount(pRet);
+
+  assert( argc==1 );
+  pRec = (u8*)sqlite3_value_blob(argv[0]);
+
+  pHdr = pRec + sqlite3GetVarint(pRec, &nHdr);
+  pBody = pEndHdr = &pRec[nHdr];
+  while( pHdr<pEndHdr ){
+    Tcl_Obj *pVal = 0;
+    u64 iSerialType;
+    Mem mem;
+
+    memset(&mem, 0, sizeof(mem));
+    mem.db = db;
+    mem.enc = ENC(db);
+    pHdr += sqlite3GetVarint(pHdr, &iSerialType);
+    pBody += sqlite3VdbeSerialGet(pBody, (u32)iSerialType, &mem);
+
+    sqlite3VdbeMemStoreType(&mem);
+    switch( sqlite3_value_type(&mem) ){
+      case SQLITE_TEXT:
+        pVal = Tcl_NewStringObj((const char*)sqlite3_value_text(&mem), -1);
+        break;
+
+      case SQLITE_BLOB: {
+        char hexdigit[] = {
+          '0', '1', '2', '3', '4', '5', '6', '7',
+          '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+        };
+        int n = sqlite3_value_bytes(&mem);
+        u8 *z = (u8*)sqlite3_value_blob(&mem);
+        int i;
+        pVal = Tcl_NewStringObj("x'", -1);
+        for(i=0; i<n; i++){
+          char hex[3];
+          hex[0] = hexdigit[((z[i] >> 4) & 0x0F)];
+          hex[1] = hexdigit[(z[i] & 0x0F)];
+          hex[2] = '\0';
+          Tcl_AppendStringsToObj(pVal, hex, 0);
+        }
+        Tcl_AppendStringsToObj(pVal, "'", 0);
+        break;
+      }
+
+      case SQLITE_FLOAT:
+        pVal = Tcl_NewDoubleObj(sqlite3_value_double(&mem));
+        break;
+
+      case SQLITE_INTEGER:
+        pVal = Tcl_NewWideIntObj(sqlite3_value_int64(&mem));
+        break;
+
+      case SQLITE_NULL:
+        pVal = Tcl_NewStringObj("NULL", -1);
+        break;
+
+      default:
+        assert( 0 );
+    }
+
+    Tcl_ListObjAppendElement(0, pRet, pVal);
+
+    if( mem.zMalloc ){
+      sqlite3DbFree(db, mem.zMalloc);
+    }
+  }
+
+  sqlite3_result_text(context, Tcl_GetString(pRet), -1, SQLITE_TRANSIENT);
+  Tcl_DecrRefCount(pRet);
+}
+
+
+static int registerTestFunctions(sqlite3 *db){
+  static const struct {
+     char *zName;
+     signed char nArg;
+     unsigned char eTextRep; /* 1: UTF-16.  0: UTF-8 */
+     void (*xFunc)(sqlite3_context*,int,sqlite3_value **);
+  } aFuncs[] = {
+    { "randstr",               2, SQLITE_UTF8, randStr    },
+    { "test_destructor",       1, SQLITE_UTF8, test_destructor},
+#ifndef SQLITE_OMIT_UTF16
+    { "test_destructor16",     1, SQLITE_UTF8, test_destructor16},
+    { "hex_to_utf16be",        1, SQLITE_UTF8, testHexToUtf16be},
+    { "hex_to_utf16le",        1, SQLITE_UTF8, testHexToUtf16le},
+#endif
+    { "hex_to_utf8",           1, SQLITE_UTF8, testHexToUtf8},
+    { "test_destructor_count", 0, SQLITE_UTF8, test_destructor_count},
+    { "test_auxdata",         -1, SQLITE_UTF8, test_auxdata},
+    { "test_error",            1, SQLITE_UTF8, test_error},
+    { "test_error",            2, SQLITE_UTF8, test_error},
+    { "test_eval",             1, SQLITE_UTF8, test_eval},
+    { "test_isolation",        2, SQLITE_UTF8, test_isolation},
+    { "test_counter",          1, SQLITE_UTF8, counterFunc},
+    { "real2hex",              1, SQLITE_UTF8, real2hex},
+    { "test_decode",           1, SQLITE_UTF8, test_decode},
+    { "test_extract",          2, SQLITE_UTF8, test_extract},
+  };
+  int i;
+
+  for(i=0; i<sizeof(aFuncs)/sizeof(aFuncs[0]); i++){
+    sqlite3_create_function(db, aFuncs[i].zName, aFuncs[i].nArg,
+        aFuncs[i].eTextRep, 0, aFuncs[i].xFunc, 0, 0);
+  }
+
+  sqlite3_create_function(db, "test_agg_errmsg16", 0, SQLITE_ANY, 0, 0, 
+      test_agg_errmsg16_step, test_agg_errmsg16_final);
+      
+  return SQLITE_OK;
+}
+
+/*
+** TCLCMD:  autoinstall_test_functions
+**
+** Invoke this TCL command to use sqlite3_auto_extension() to cause
+** the standard set of test functions to be loaded into each new
+** database connection.
+*/
+static int autoinstall_test_funcs(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  extern int Md5_Register(sqlite3*);
+  int rc = sqlite3_auto_extension((void*)registerTestFunctions);
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_auto_extension((void*)Md5_Register);
+  }
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** A bogus step function and finalizer function.
+*/
+static void tStep(sqlite3_context *a, int b, sqlite3_value **c){}
+static void tFinal(sqlite3_context *a){}
+
+
+/*
+** tclcmd:  abuse_create_function
+**
+** Make various calls to sqlite3_create_function that do not have valid
+** parameters.  Verify that the error condition is detected and reported.
+*/
+static int abuse_create_function(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
+  sqlite3 *db;
+  int rc;
+  int mxArg;
+
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+
+  rc = sqlite3_create_function(db, "tx", 1, SQLITE_UTF8, 0, tStep,tStep,tFinal);
+  if( rc!=SQLITE_MISUSE ) goto abuse_err;
+
+  rc = sqlite3_create_function(db, "tx", 1, SQLITE_UTF8, 0, tStep, tStep, 0);
+  if( rc!=SQLITE_MISUSE ) goto abuse_err;
+
+  rc = sqlite3_create_function(db, "tx", 1, SQLITE_UTF8, 0, tStep, 0, tFinal);
+  if( rc!=SQLITE_MISUSE) goto abuse_err;
+
+  rc = sqlite3_create_function(db, "tx", 1, SQLITE_UTF8, 0, 0, 0, tFinal);
+  if( rc!=SQLITE_MISUSE ) goto abuse_err;
+
+  rc = sqlite3_create_function(db, "tx", 1, SQLITE_UTF8, 0, 0, tStep, 0);
+  if( rc!=SQLITE_MISUSE ) goto abuse_err;
+
+  rc = sqlite3_create_function(db, "tx", -2, SQLITE_UTF8, 0, tStep, 0, 0);
+  if( rc!=SQLITE_MISUSE ) goto abuse_err;
+
+  rc = sqlite3_create_function(db, "tx", 128, SQLITE_UTF8, 0, tStep, 0, 0);
+  if( rc!=SQLITE_MISUSE ) goto abuse_err;
+
+  rc = sqlite3_create_function(db, "funcxx"
+       "_123456789_123456789_123456789_123456789_123456789"
+       "_123456789_123456789_123456789_123456789_123456789"
+       "_123456789_123456789_123456789_123456789_123456789"
+       "_123456789_123456789_123456789_123456789_123456789"
+       "_123456789_123456789_123456789_123456789_123456789",
+       1, SQLITE_UTF8, 0, tStep, 0, 0);
+  if( rc!=SQLITE_MISUSE ) goto abuse_err;
+
+  /* This last function registration should actually work.  Generate
+  ** a no-op function (that always returns NULL) and which has the
+  ** maximum-length function name and the maximum number of parameters.
+  */
+  sqlite3_limit(db, SQLITE_LIMIT_FUNCTION_ARG, 10000);
+  mxArg = sqlite3_limit(db, SQLITE_LIMIT_FUNCTION_ARG, -1);
+  rc = sqlite3_create_function(db, "nullx"
+       "_123456789_123456789_123456789_123456789_123456789"
+       "_123456789_123456789_123456789_123456789_123456789"
+       "_123456789_123456789_123456789_123456789_123456789"
+       "_123456789_123456789_123456789_123456789_123456789"
+       "_123456789_123456789_123456789_123456789_123456789",
+       mxArg, SQLITE_UTF8, 0, tStep, 0, 0);
+  if( rc!=SQLITE_OK ) goto abuse_err;
+                                
+  return TCL_OK;
+
+abuse_err:
+  Tcl_AppendResult(interp, "sqlite3_create_function abused test failed", 
+                   (char*)0);
+  return TCL_ERROR;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest_func_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+  } aObjCmd[] = {
+     { "autoinstall_test_functions",    autoinstall_test_funcs },
+     { "abuse_create_function",         abuse_create_function  },
+  };
+  int i;
+  extern int Md5_Register(sqlite3*);
+
+  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, aObjCmd[i].xProc, 0, 0);
+  }
+  sqlite3_initialize();
+  sqlite3_auto_extension((void*)registerTestFunctions);
+  sqlite3_auto_extension((void*)Md5_Register);
+  return TCL_OK;
+}

+ 388 - 0
components/external/sqlite/test/test_hexio.c

@@ -0,0 +1,388 @@
+/*
+** 2007 April 6
+**
+** 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 all sorts of SQLite interfaces.  This code
+** implements TCL commands for reading and writing the binary
+** database files and displaying the content of those files as
+** hexadecimal.  We could, in theory, use the built-in "binary"
+** command of TCL to do a lot of this, but there are some issues
+** with historical versions of the "binary" command.  So it seems
+** easier and safer to build our own mechanism.
+*/
+#include "sqliteInt.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+
+/*
+** Convert binary to hex.  The input zBuf[] contains N bytes of
+** binary data.  zBuf[] is 2*n+1 bytes long.  Overwrite zBuf[]
+** with a hexadecimal representation of its original binary input.
+*/
+void sqlite3TestBinToHex(unsigned char *zBuf, int N){
+  const unsigned char zHex[] = "0123456789ABCDEF";
+  int i, j;
+  unsigned char c;
+  i = N*2;
+  zBuf[i--] = 0;
+  for(j=N-1; j>=0; j--){
+    c = zBuf[j];
+    zBuf[i--] = zHex[c&0xf];
+    zBuf[i--] = zHex[c>>4];
+  }
+  assert( i==-1 );
+}
+
+/*
+** Convert hex to binary.  The input zIn[] contains N bytes of
+** hexadecimal.  Convert this into binary and write aOut[] with
+** the binary data.  Spaces in the original input are ignored.
+** Return the number of bytes of binary rendered.
+*/
+int sqlite3TestHexToBin(const unsigned char *zIn, int N, unsigned char *aOut){
+  const unsigned char aMap[] = {
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     1, 2, 3, 4, 5, 6, 7, 8,  9,10, 0, 0, 0, 0, 0, 0,
+     0,11,12,13,14,15,16, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0,11,12,13,14,15,16, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
+  };
+  int i, j;
+  int hi=1;
+  unsigned char c;
+
+  for(i=j=0; i<N; i++){
+    c = aMap[zIn[i]];
+    if( c==0 ) continue;
+    if( hi ){
+      aOut[j] = (c-1)<<4;
+      hi = 0;
+    }else{
+      aOut[j++] |= c-1;
+      hi = 1;
+    }
+  }
+  return j;
+}
+
+
+/*
+** Usage:   hexio_read  FILENAME  OFFSET  AMT
+**
+** Read AMT bytes from file FILENAME beginning at OFFSET from the
+** beginning of the file.  Convert that information to hexadecimal
+** and return the resulting HEX string.
+*/
+static int hexio_read(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int offset;
+  int amt, got;
+  const char *zFile;
+  unsigned char *zBuf;
+  FILE *in;
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME OFFSET AMT");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[2], &offset) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[3], &amt) ) return TCL_ERROR;
+  zFile = Tcl_GetString(objv[1]);
+  zBuf = sqlite3_malloc( amt*2+1 );
+  if( zBuf==0 ){
+    return TCL_ERROR;
+  }
+  in = fopen(zFile, "rb");
+  if( in==0 ){
+    in = fopen(zFile, "r");
+  }
+  if( in==0 ){
+    Tcl_AppendResult(interp, "cannot open input file ", zFile, 0);
+    return TCL_ERROR;
+  }
+  fseek(in, offset, SEEK_SET);
+  got = (int)fread(zBuf, 1, amt, in);
+  fclose(in);
+  if( got<0 ){
+    got = 0;
+  }
+  sqlite3TestBinToHex(zBuf, got);
+  Tcl_AppendResult(interp, zBuf, 0);
+  sqlite3_free(zBuf);
+  return TCL_OK;
+}
+
+
+/*
+** Usage:   hexio_write  FILENAME  OFFSET  DATA
+**
+** Write DATA into file FILENAME beginning at OFFSET from the
+** beginning of the file.  DATA is expressed in hexadecimal.
+*/
+static int hexio_write(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int offset;
+  int nIn, nOut, written;
+  const char *zFile;
+  const unsigned char *zIn;
+  unsigned char *aOut;
+  FILE *out;
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME OFFSET HEXDATA");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[2], &offset) ) return TCL_ERROR;
+  zFile = Tcl_GetString(objv[1]);
+  zIn = (const unsigned char *)Tcl_GetStringFromObj(objv[3], &nIn);
+  aOut = sqlite3_malloc( nIn/2 );
+  if( aOut==0 ){
+    return TCL_ERROR;
+  }
+  nOut = sqlite3TestHexToBin(zIn, nIn, aOut);
+  out = fopen(zFile, "r+b");
+  if( out==0 ){
+    out = fopen(zFile, "r+");
+  }
+  if( out==0 ){
+    Tcl_AppendResult(interp, "cannot open output file ", zFile, 0);
+    return TCL_ERROR;
+  }
+  fseek(out, offset, SEEK_SET);
+  written = (int)fwrite(aOut, 1, nOut, out);
+  sqlite3_free(aOut);
+  fclose(out);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(written));
+  return TCL_OK;
+}
+
+/*
+** USAGE:   hexio_get_int   HEXDATA
+**
+** Interpret the HEXDATA argument as a big-endian integer.  Return
+** the value of that integer.  HEXDATA can contain between 2 and 8
+** hexadecimal digits.
+*/
+static int hexio_get_int(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int val;
+  int nIn, nOut;
+  const unsigned char *zIn;
+  unsigned char *aOut;
+  unsigned char aNum[4];
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HEXDATA");
+    return TCL_ERROR;
+  }
+  zIn = (const unsigned char *)Tcl_GetStringFromObj(objv[1], &nIn);
+  aOut = sqlite3_malloc( nIn/2 );
+  if( aOut==0 ){
+    return TCL_ERROR;
+  }
+  nOut = sqlite3TestHexToBin(zIn, nIn, aOut);
+  if( nOut>=4 ){
+    memcpy(aNum, aOut, 4);
+  }else{
+    memset(aNum, 0, sizeof(aNum));
+    memcpy(&aNum[4-nOut], aOut, nOut);
+  }
+  sqlite3_free(aOut);
+  val = (aNum[0]<<24) | (aNum[1]<<16) | (aNum[2]<<8) | aNum[3];
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(val));
+  return TCL_OK;
+}
+
+
+/*
+** USAGE:   hexio_render_int16   INTEGER
+**
+** Render INTEGER has a 16-bit big-endian integer in hexadecimal.
+*/
+static int hexio_render_int16(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int val;
+  unsigned char aNum[10];
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "INTEGER");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[1], &val) ) return TCL_ERROR;
+  aNum[0] = val>>8;
+  aNum[1] = val;
+  sqlite3TestBinToHex(aNum, 2);
+  Tcl_SetObjResult(interp, Tcl_NewStringObj((char*)aNum, 4));
+  return TCL_OK;
+}
+
+
+/*
+** USAGE:   hexio_render_int32   INTEGER
+**
+** Render INTEGER has a 32-bit big-endian integer in hexadecimal.
+*/
+static int hexio_render_int32(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int val;
+  unsigned char aNum[10];
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "INTEGER");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[1], &val) ) return TCL_ERROR;
+  aNum[0] = val>>24;
+  aNum[1] = val>>16;
+  aNum[2] = val>>8;
+  aNum[3] = val;
+  sqlite3TestBinToHex(aNum, 4);
+  Tcl_SetObjResult(interp, Tcl_NewStringObj((char*)aNum, 8));
+  return TCL_OK;
+}
+
+/*
+** USAGE:  utf8_to_utf8  HEX
+**
+** The argument is a UTF8 string represented in hexadecimal.
+** The UTF8 might not be well-formed.  Run this string through
+** sqlite3Utf8to8() convert it back to hex and return the result.
+*/
+static int utf8_to_utf8(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifdef SQLITE_DEBUG
+  int n;
+  int nOut;
+  const unsigned char *zOrig;
+  unsigned char *z;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HEX");
+    return TCL_ERROR;
+  }
+  zOrig = (unsigned char *)Tcl_GetStringFromObj(objv[1], &n);
+  z = sqlite3_malloc( n+3 );
+  n = sqlite3TestHexToBin(zOrig, n, z);
+  z[n] = 0;
+  nOut = sqlite3Utf8To8(z);
+  sqlite3TestBinToHex(z,nOut);
+  Tcl_AppendResult(interp, (char*)z, 0);
+  sqlite3_free(z);
+  return TCL_OK;
+#else
+  Tcl_AppendResult(interp, 
+      "[utf8_to_utf8] unavailable - SQLITE_DEBUG not defined", 0
+  );
+  return TCL_ERROR;
+#endif
+}
+
+static int getFts3Varint(const char *p, sqlite_int64 *v){
+  const unsigned char *q = (const unsigned char *) p;
+  sqlite_uint64 x = 0, y = 1;
+  while( (*q & 0x80) == 0x80 ){
+    x += y * (*q++ & 0x7f);
+    y <<= 7;
+  }
+  x += y * (*q++);
+  *v = (sqlite_int64) x;
+  return (int) (q - (unsigned char *)p);
+}
+
+
+/*
+** USAGE:  read_fts3varint BLOB VARNAME
+**
+** Read a varint from the start of BLOB. Set variable VARNAME to contain
+** the interpreted value. Return the number of bytes of BLOB consumed.
+*/
+static int read_fts3varint(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int nBlob;
+  unsigned char *zBlob;
+  sqlite3_int64 iVal;
+  int nVal;
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "BLOB VARNAME");
+    return TCL_ERROR;
+  }
+  zBlob = Tcl_GetByteArrayFromObj(objv[1], &nBlob);
+
+  nVal = getFts3Varint((char*)zBlob, (sqlite3_int64 *)(&iVal));
+  Tcl_ObjSetVar2(interp, objv[2], 0, Tcl_NewWideIntObj(iVal), 0);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(nVal));
+  return TCL_OK;
+}
+
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest_hexio_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+  } aObjCmd[] = {
+     { "hexio_read",                   hexio_read            },
+     { "hexio_write",                  hexio_write           },
+     { "hexio_get_int",                hexio_get_int         },
+     { "hexio_render_int16",           hexio_render_int16    },
+     { "hexio_render_int32",           hexio_render_int32    },
+     { "utf8_to_utf8",                 utf8_to_utf8          },
+     { "read_fts3varint",              read_fts3varint       },
+  };
+  int i;
+  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, aObjCmd[i].xProc, 0, 0);
+  }
+  return TCL_OK;
+}

+ 291 - 0
components/external/sqlite/test/test_init.c

@@ -0,0 +1,291 @@
+/*
+** 2009 August 17
+**
+** 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.
+**
+*************************************************************************
+**
+** The code in this file is used for testing SQLite. It is not part of
+** the source code used in production systems.
+**
+** Specifically, this file tests the effect of errors while initializing
+** the various pluggable sub-systems from within sqlite3_initialize().
+** If an error occurs in sqlite3_initialize() the following should be
+** true:
+**
+**   1) An error code is returned to the user, and
+**   2) A subsequent call to sqlite3_shutdown() calls the shutdown method
+**      of those subsystems that were initialized, and
+**   3) A subsequent call to sqlite3_initialize() attempts to initialize
+**      the remaining, uninitialized, subsystems.
+*/
+
+#include "sqliteInt.h"
+#include <string.h>
+#include <tcl.h>
+
+static struct Wrapped {
+  sqlite3_pcache_methods2 pcache;
+  sqlite3_mem_methods     mem;
+  sqlite3_mutex_methods   mutex;
+
+  int mem_init;                /* True if mem subsystem is initalized */
+  int mem_fail;                /* True to fail mem subsystem inialization */
+  int mutex_init;              /* True if mutex subsystem is initalized */
+  int mutex_fail;              /* True to fail mutex subsystem inialization */
+  int pcache_init;             /* True if pcache subsystem is initalized */
+  int pcache_fail;             /* True to fail pcache subsystem inialization */
+} wrapped;
+
+static int wrMemInit(void *pAppData){
+  int rc;
+  if( wrapped.mem_fail ){
+    rc = SQLITE_ERROR;
+  }else{
+    rc = wrapped.mem.xInit(wrapped.mem.pAppData);
+  }
+  if( rc==SQLITE_OK ){
+    wrapped.mem_init = 1;
+  }
+  return rc;
+}
+static void wrMemShutdown(void *pAppData){
+  wrapped.mem.xShutdown(wrapped.mem.pAppData);
+  wrapped.mem_init = 0;
+}
+static void *wrMemMalloc(int n)           {return wrapped.mem.xMalloc(n);}
+static void wrMemFree(void *p)            {wrapped.mem.xFree(p);}
+static void *wrMemRealloc(void *p, int n) {return wrapped.mem.xRealloc(p, n);}
+static int wrMemSize(void *p)             {return wrapped.mem.xSize(p);}
+static int wrMemRoundup(int n)            {return wrapped.mem.xRoundup(n);}
+
+
+static int wrMutexInit(void){
+  int rc;
+  if( wrapped.mutex_fail ){
+    rc = SQLITE_ERROR;
+  }else{
+    rc = wrapped.mutex.xMutexInit();
+  }
+  if( rc==SQLITE_OK ){
+    wrapped.mutex_init = 1;
+  }
+  return rc;
+}
+static int wrMutexEnd(void){
+  wrapped.mutex.xMutexEnd();
+  wrapped.mutex_init = 0;
+  return SQLITE_OK;
+}
+static sqlite3_mutex *wrMutexAlloc(int e){
+  return wrapped.mutex.xMutexAlloc(e);
+}
+static void wrMutexFree(sqlite3_mutex *p){
+  wrapped.mutex.xMutexFree(p);
+}
+static void wrMutexEnter(sqlite3_mutex *p){
+  wrapped.mutex.xMutexEnter(p);
+}
+static int wrMutexTry(sqlite3_mutex *p){
+  return wrapped.mutex.xMutexTry(p);
+}
+static void wrMutexLeave(sqlite3_mutex *p){
+  wrapped.mutex.xMutexLeave(p);
+}
+static int wrMutexHeld(sqlite3_mutex *p){
+  return wrapped.mutex.xMutexHeld(p);
+}
+static int wrMutexNotheld(sqlite3_mutex *p){
+  return wrapped.mutex.xMutexNotheld(p);
+}
+
+
+
+static int wrPCacheInit(void *pArg){
+  int rc;
+  if( wrapped.pcache_fail ){
+    rc = SQLITE_ERROR;
+  }else{
+    rc = wrapped.pcache.xInit(wrapped.pcache.pArg);
+  }
+  if( rc==SQLITE_OK ){
+    wrapped.pcache_init = 1;
+  }
+  return rc;
+}
+static void wrPCacheShutdown(void *pArg){
+  wrapped.pcache.xShutdown(wrapped.pcache.pArg);
+  wrapped.pcache_init = 0;
+}
+
+static sqlite3_pcache *wrPCacheCreate(int a, int b, int c){
+  return wrapped.pcache.xCreate(a, b, c);
+}  
+static void wrPCacheCachesize(sqlite3_pcache *p, int n){
+  wrapped.pcache.xCachesize(p, n);
+}  
+static int wrPCachePagecount(sqlite3_pcache *p){
+  return wrapped.pcache.xPagecount(p);
+}  
+static sqlite3_pcache_page *wrPCacheFetch(sqlite3_pcache *p, unsigned a, int b){
+  return wrapped.pcache.xFetch(p, a, b);
+}  
+static void wrPCacheUnpin(sqlite3_pcache *p, sqlite3_pcache_page *a, int b){
+  wrapped.pcache.xUnpin(p, a, b);
+}  
+static void wrPCacheRekey(
+  sqlite3_pcache *p, 
+  sqlite3_pcache_page *a, 
+  unsigned b, 
+  unsigned c
+){
+  wrapped.pcache.xRekey(p, a, b, c);
+}  
+static void wrPCacheTruncate(sqlite3_pcache *p, unsigned a){
+  wrapped.pcache.xTruncate(p, a);
+}  
+static void wrPCacheDestroy(sqlite3_pcache *p){
+  wrapped.pcache.xDestroy(p);
+}  
+
+static void installInitWrappers(void){
+  sqlite3_mutex_methods mutexmethods = {
+    wrMutexInit,  wrMutexEnd,   wrMutexAlloc,
+    wrMutexFree,  wrMutexEnter, wrMutexTry,
+    wrMutexLeave, wrMutexHeld,  wrMutexNotheld
+  };
+  sqlite3_pcache_methods2 pcachemethods = {
+    1, 0,
+    wrPCacheInit,      wrPCacheShutdown,  wrPCacheCreate, 
+    wrPCacheCachesize, wrPCachePagecount, wrPCacheFetch,
+    wrPCacheUnpin,     wrPCacheRekey,     wrPCacheTruncate,  
+    wrPCacheDestroy
+  };
+  sqlite3_mem_methods memmethods = {
+    wrMemMalloc,   wrMemFree,    wrMemRealloc,
+    wrMemSize,     wrMemRoundup, wrMemInit,
+    wrMemShutdown,
+    0
+  };
+
+  memset(&wrapped, 0, sizeof(wrapped));
+
+  sqlite3_shutdown();
+  sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped.mutex);
+  sqlite3_config(SQLITE_CONFIG_GETMALLOC, &wrapped.mem);
+  sqlite3_config(SQLITE_CONFIG_GETPCACHE2, &wrapped.pcache);
+  sqlite3_config(SQLITE_CONFIG_MUTEX, &mutexmethods);
+  sqlite3_config(SQLITE_CONFIG_MALLOC, &memmethods);
+  sqlite3_config(SQLITE_CONFIG_PCACHE2, &pcachemethods);
+}
+
+static int init_wrapper_install(
+  ClientData clientData, /* Unused */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  int i;
+  installInitWrappers();
+  for(i=1; i<objc; i++){
+    char *z = Tcl_GetString(objv[i]);
+    if( strcmp(z, "mem")==0 ){
+      wrapped.mem_fail = 1;
+    }else if( strcmp(z, "mutex")==0 ){
+      wrapped.mutex_fail = 1;
+    }else if( strcmp(z, "pcache")==0 ){
+      wrapped.pcache_fail = 1;
+    }else{
+      Tcl_AppendResult(interp, "Unknown argument: \"", z, "\"");
+      return TCL_ERROR;
+    }
+  }
+  return TCL_OK;
+}
+
+static int init_wrapper_uninstall(
+  ClientData clientData, /* Unused */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  sqlite3_shutdown();
+  sqlite3_config(SQLITE_CONFIG_MUTEX, &wrapped.mutex);
+  sqlite3_config(SQLITE_CONFIG_MALLOC, &wrapped.mem);
+  sqlite3_config(SQLITE_CONFIG_PCACHE2, &wrapped.pcache);
+  return TCL_OK;
+}
+
+static int init_wrapper_clear(
+  ClientData clientData, /* Unused */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  wrapped.mem_fail = 0;
+  wrapped.mutex_fail = 0;
+  wrapped.pcache_fail = 0;
+  return TCL_OK;
+}
+
+static int init_wrapper_query(
+  ClientData clientData, /* Unused */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  Tcl_Obj *pRet;
+
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  pRet = Tcl_NewObj();
+  if( wrapped.mutex_init ){
+    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj("mutex", -1));
+  }
+  if( wrapped.mem_init ){
+    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj("mem", -1));
+  }
+  if( wrapped.pcache_init ){
+    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj("pcache", -1));
+  }
+
+  Tcl_SetObjResult(interp, pRet);
+  return TCL_OK;
+}
+
+int Sqlitetest_init_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+  } aObjCmd[] = {
+    {"init_wrapper_install",   init_wrapper_install},
+    {"init_wrapper_query",     init_wrapper_query  },
+    {"init_wrapper_uninstall", init_wrapper_uninstall},
+    {"init_wrapper_clear",     init_wrapper_clear}
+  };
+  int i;
+
+  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, aObjCmd[i].xProc, 0, 0);
+  }
+
+  return TCL_OK;
+}

+ 382 - 0
components/external/sqlite/test/test_intarray.c

@@ -0,0 +1,382 @@
+/*
+** 2009 November 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 read-only VIRTUAL TABLE that contains the
+** content of a C-language array of integer values.  See the corresponding
+** header file for full details.
+*/
+#include "test_intarray.h"
+#include <string.h>
+#include <assert.h>
+
+
+/*
+** Definition of the sqlite3_intarray object.
+**
+** The internal representation of an intarray object is subject
+** to change, is not externally visible, and should be used by
+** the implementation of intarray only.  This object is opaque
+** to users.
+*/
+struct sqlite3_intarray {
+  int n;                    /* Number of elements in the array */
+  sqlite3_int64 *a;         /* Contents of the array */
+  void (*xFree)(void*);     /* Function used to free a[] */
+};
+
+/* Objects used internally by the virtual table implementation */
+typedef struct intarray_vtab intarray_vtab;
+typedef struct intarray_cursor intarray_cursor;
+
+/* A intarray table object */
+struct intarray_vtab {
+  sqlite3_vtab base;            /* Base class */
+  sqlite3_intarray *pContent;   /* Content of the integer array */
+};
+
+/* A intarray cursor object */
+struct intarray_cursor {
+  sqlite3_vtab_cursor base;    /* Base class */
+  int i;                       /* Current cursor position */
+};
+
+/*
+** None of this works unless we have virtual tables.
+*/
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+/*
+** Free an sqlite3_intarray object.
+*/
+static void intarrayFree(sqlite3_intarray *p){
+  if( p->xFree ){
+    p->xFree(p->a);
+  }
+  sqlite3_free(p);
+}
+
+/*
+** Table destructor for the intarray module.
+*/
+static int intarrayDestroy(sqlite3_vtab *p){
+  intarray_vtab *pVtab = (intarray_vtab*)p;
+  sqlite3_free(pVtab);
+  return 0;
+}
+
+/*
+** Table constructor for the intarray module.
+*/
+static int intarrayCreate(
+  sqlite3 *db,              /* Database where module is created */
+  void *pAux,               /* clientdata for the module */
+  int argc,                 /* Number of arguments */
+  const char *const*argv,   /* Value for all arguments */
+  sqlite3_vtab **ppVtab,    /* Write the new virtual table object here */
+  char **pzErr              /* Put error message text here */
+){
+  int rc = SQLITE_NOMEM;
+  intarray_vtab *pVtab = sqlite3_malloc(sizeof(intarray_vtab));
+
+  if( pVtab ){
+    memset(pVtab, 0, sizeof(intarray_vtab));
+    pVtab->pContent = (sqlite3_intarray*)pAux;
+    rc = sqlite3_declare_vtab(db, "CREATE TABLE x(value INTEGER PRIMARY KEY)");
+  }
+  *ppVtab = (sqlite3_vtab *)pVtab;
+  return rc;
+}
+
+/*
+** Open a new cursor on the intarray table.
+*/
+static int intarrayOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+  int rc = SQLITE_NOMEM;
+  intarray_cursor *pCur;
+  pCur = sqlite3_malloc(sizeof(intarray_cursor));
+  if( pCur ){
+    memset(pCur, 0, sizeof(intarray_cursor));
+    *ppCursor = (sqlite3_vtab_cursor *)pCur;
+    rc = SQLITE_OK;
+  }
+  return rc;
+}
+
+/*
+** Close a intarray table cursor.
+*/
+static int intarrayClose(sqlite3_vtab_cursor *cur){
+  intarray_cursor *pCur = (intarray_cursor *)cur;
+  sqlite3_free(pCur);
+  return SQLITE_OK;
+}
+
+/*
+** Retrieve a column of data.
+*/
+static int intarrayColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
+  intarray_cursor *pCur = (intarray_cursor*)cur;
+  intarray_vtab *pVtab = (intarray_vtab*)cur->pVtab;
+  if( pCur->i>=0 && pCur->i<pVtab->pContent->n ){
+    sqlite3_result_int64(ctx, pVtab->pContent->a[pCur->i]);
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Retrieve the current rowid.
+*/
+static int intarrayRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+  intarray_cursor *pCur = (intarray_cursor *)cur;
+  *pRowid = pCur->i;
+  return SQLITE_OK;
+}
+
+static int intarrayEof(sqlite3_vtab_cursor *cur){
+  intarray_cursor *pCur = (intarray_cursor *)cur;
+  intarray_vtab *pVtab = (intarray_vtab *)cur->pVtab;
+  return pCur->i>=pVtab->pContent->n;
+}
+
+/*
+** Advance the cursor to the next row.
+*/
+static int intarrayNext(sqlite3_vtab_cursor *cur){
+  intarray_cursor *pCur = (intarray_cursor *)cur;
+  pCur->i++;
+  return SQLITE_OK;
+}
+
+/*
+** Reset a intarray table cursor.
+*/
+static int intarrayFilter(
+  sqlite3_vtab_cursor *pVtabCursor, 
+  int idxNum, const char *idxStr,
+  int argc, sqlite3_value **argv
+){
+  intarray_cursor *pCur = (intarray_cursor *)pVtabCursor;
+  pCur->i = 0;
+  return SQLITE_OK;
+}
+
+/*
+** Analyse the WHERE condition.
+*/
+static int intarrayBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+  return SQLITE_OK;
+}
+
+/*
+** A virtual table module that merely echos method calls into TCL
+** variables.
+*/
+static sqlite3_module intarrayModule = {
+  0,                           /* iVersion */
+  intarrayCreate,              /* xCreate - create a new virtual table */
+  intarrayCreate,              /* xConnect - connect to an existing vtab */
+  intarrayBestIndex,           /* xBestIndex - find the best query index */
+  intarrayDestroy,             /* xDisconnect - disconnect a vtab */
+  intarrayDestroy,             /* xDestroy - destroy a vtab */
+  intarrayOpen,                /* xOpen - open a cursor */
+  intarrayClose,               /* xClose - close a cursor */
+  intarrayFilter,              /* xFilter - configure scan constraints */
+  intarrayNext,                /* xNext - advance a cursor */
+  intarrayEof,                 /* xEof */
+  intarrayColumn,              /* xColumn - read data */
+  intarrayRowid,               /* xRowid - read data */
+  0,                           /* xUpdate */
+  0,                           /* xBegin */
+  0,                           /* xSync */
+  0,                           /* xCommit */
+  0,                           /* xRollback */
+  0,                           /* xFindMethod */
+  0,                           /* xRename */
+};
+
+#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */
+
+/*
+** Invoke this routine to create a specific instance of an intarray object.
+** The new intarray object is returned by the 3rd parameter.
+**
+** Each intarray object corresponds to a virtual table in the TEMP table
+** with a name of zName.
+**
+** Destroy the intarray object by dropping the virtual table.  If not done
+** explicitly by the application, the virtual table will be dropped implicitly
+** by the system when the database connection is closed.
+*/
+int sqlite3_intarray_create(
+  sqlite3 *db,
+  const char *zName,
+  sqlite3_intarray **ppReturn
+){
+  int rc = SQLITE_OK;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  sqlite3_intarray *p;
+
+  *ppReturn = p = sqlite3_malloc( sizeof(*p) );
+  if( p==0 ){
+    return SQLITE_NOMEM;
+  }
+  memset(p, 0, sizeof(*p));
+  rc = sqlite3_create_module_v2(db, zName, &intarrayModule, p,
+                                (void(*)(void*))intarrayFree);
+  if( rc==SQLITE_OK ){
+    char *zSql;
+    zSql = sqlite3_mprintf("CREATE VIRTUAL TABLE temp.%Q USING %Q",
+                           zName, zName);
+    rc = sqlite3_exec(db, zSql, 0, 0, 0);
+    sqlite3_free(zSql);
+  }
+#endif
+  return rc;
+}
+
+/*
+** Bind a new array array of integers to a specific intarray object.
+**
+** The array of integers bound must be unchanged for the duration of
+** any query against the corresponding virtual table.  If the integer
+** array does change or is deallocated undefined behavior will result.
+*/
+int sqlite3_intarray_bind(
+  sqlite3_intarray *pIntArray,   /* The intarray object to bind to */
+  int nElements,                 /* Number of elements in the intarray */
+  sqlite3_int64 *aElements,      /* Content of the intarray */
+  void (*xFree)(void*)           /* How to dispose of the intarray when done */
+){
+  if( pIntArray->xFree ){
+    pIntArray->xFree(pIntArray->a);
+  }
+  pIntArray->n = nElements;
+  pIntArray->a = aElements;
+  pIntArray->xFree = xFree;
+  return SQLITE_OK;
+}
+
+
+/*****************************************************************************
+** Everything below is interface for testing this module.
+*/
+#ifdef SQLITE_TEST
+#include <tcl.h>
+
+/*
+** Routines to encode and decode pointers
+*/
+extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb);
+extern void *sqlite3TestTextToPtr(const char*);
+extern int sqlite3TestMakePointerStr(Tcl_Interp*, char *zPtr, void*);
+extern const char *sqlite3ErrName(int);
+
+/*
+**    sqlite3_intarray_create  DB  NAME
+**
+** Invoke the sqlite3_intarray_create interface.  A string that becomes
+** the first parameter to sqlite3_intarray_bind.
+*/
+static int test_intarray_create(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  const char *zName;
+  sqlite3_intarray *pArray;
+  int rc = SQLITE_OK;
+  char zPtr[100];
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zName = Tcl_GetString(objv[2]);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  rc = sqlite3_intarray_create(db, zName, &pArray);
+#endif
+  if( rc!=SQLITE_OK ){
+    assert( pArray==0 );
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), (char*)0);
+    return TCL_ERROR;
+  }
+  sqlite3TestMakePointerStr(interp, zPtr, pArray);
+  Tcl_AppendResult(interp, zPtr, (char*)0);
+  return TCL_OK;
+}
+
+/*
+**    sqlite3_intarray_bind  INTARRAY  ?VALUE ...?
+**
+** Invoke the sqlite3_intarray_bind interface on the given array of integers.
+*/
+static int test_intarray_bind(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  sqlite3_intarray *pArray;
+  int rc = SQLITE_OK;
+  int i, n;
+  sqlite3_int64 *a;
+
+  if( objc<2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "INTARRAY");
+    return TCL_ERROR;
+  }
+  pArray = (sqlite3_intarray*)sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  n = objc - 2;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  a = sqlite3_malloc( sizeof(a[0])*n );
+  if( a==0 ){
+    Tcl_AppendResult(interp, "SQLITE_NOMEM", (char*)0);
+    return TCL_ERROR;
+  }
+  for(i=0; i<n; i++){
+    Tcl_WideInt x = 0;
+    Tcl_GetWideIntFromObj(0, objv[i+2], &x);
+    a[i] = x;
+  }
+  rc = sqlite3_intarray_bind(pArray, n, a, sqlite3_free);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, sqlite3ErrName(rc), (char*)0);
+    return TCL_ERROR;
+  }
+#endif
+  return TCL_OK;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetestintarray_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+     void *clientData;
+  } aObjCmd[] = {
+     { "sqlite3_intarray_create", test_intarray_create, 0 },
+     { "sqlite3_intarray_bind", test_intarray_bind, 0 },
+  };
+  int i;
+  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, 
+        aObjCmd[i].xProc, aObjCmd[i].clientData, 0);
+  }
+  return TCL_OK;
+}
+
+#endif /* SQLITE_TEST */

+ 128 - 0
components/external/sqlite/test/test_intarray.h

@@ -0,0 +1,128 @@
+/*
+** 2009 November 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 is the C-language interface definition for the "intarray" or
+** integer array virtual table for SQLite.
+**
+** The intarray virtual table is designed to facilitate using an
+** array of integers as the right-hand side of an IN operator.  So
+** instead of doing a prepared statement like this:
+**
+**     SELECT * FROM table WHERE x IN (?,?,?,...,?);
+**
+** And then binding indivdual integers to each of ? slots, a C-language
+** application can create an intarray object (named "ex1" in the following
+** example), prepare a statement like this:
+**
+**     SELECT * FROM table WHERE x IN ex1;
+**
+** Then bind an ordinary C/C++ array of integer values to the ex1 object
+** to run the statement.
+**
+** USAGE:
+**
+** One or more intarray objects can be created as follows:
+**
+**      sqlite3_intarray *p1, *p2, *p3;
+**      sqlite3_intarray_create(db, "ex1", &p1);
+**      sqlite3_intarray_create(db, "ex2", &p2);
+**      sqlite3_intarray_create(db, "ex3", &p3);
+**
+** Each call to sqlite3_intarray_create() generates a new virtual table
+** module and a singleton of that virtual table module in the TEMP
+** database.  Both the module and the virtual table instance use the
+** name given by the second parameter.  The virtual tables can then be
+** used in prepared statements:
+**
+**      SELECT * FROM t1, t2, t3
+**       WHERE t1.x IN ex1
+**         AND t2.y IN ex2
+**         AND t3.z IN ex3;
+**
+** Each integer array is initially empty.  New arrays can be bound to
+** an integer array as follows:
+**
+**     sqlite3_int64 a1[] = { 1, 2, 3, 4 };
+**     sqlite3_int64 a2[] = { 5, 6, 7, 8, 9, 10, 11 };
+**     sqlite3_int64 *a3 = sqlite3_malloc( 100*sizeof(sqlite3_int64) );
+**     // Fill in content of a3[]
+**     sqlite3_intarray_bind(p1, 4, a1, 0);
+**     sqlite3_intarray_bind(p2, 7, a2, 0);
+**     sqlite3_intarray_bind(p3, 100, a3, sqlite3_free);
+**
+** A single intarray object can be rebound multiple times.  But do not
+** attempt to change the bindings of an intarray while it is in the middle
+** of a query.
+**
+** The array that holds the integers is automatically freed by the function
+** in the fourth parameter to sqlite3_intarray_bind() when the array is no
+** longer needed.  The application must not change the intarray values
+** while an intarray is in the middle of a query.
+**
+** The intarray object is automatically destroyed when its corresponding
+** virtual table is dropped.  Since the virtual tables are created in the
+** TEMP database, they are automatically dropped when the database connection
+** closes so the application does not normally need to take any special
+** action to free the intarray objects.
+*/
+#include "sqlite3.h"
+#ifndef _INTARRAY_H_
+#define _INTARRAY_H_
+
+/*
+** Make sure we can call this stuff from C++.
+*/
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+** An sqlite3_intarray is an abstract type to stores an instance of
+** an integer array.
+*/
+typedef struct sqlite3_intarray sqlite3_intarray;
+
+/*
+** Invoke this routine to create a specific instance of an intarray object.
+** The new intarray object is returned by the 3rd parameter.
+**
+** Each intarray object corresponds to a virtual table in the TEMP table
+** with a name of zName.
+**
+** Destroy the intarray object by dropping the virtual table.  If not done
+** explicitly by the application, the virtual table will be dropped implicitly
+** by the system when the database connection is closed.
+*/
+int sqlite3_intarray_create(
+  sqlite3 *db,
+  const char *zName,
+  sqlite3_intarray **ppReturn
+);
+
+/*
+** Bind a new array array of integers to a specific intarray object.
+**
+** The array of integers bound must be unchanged for the duration of
+** any query against the corresponding virtual table.  If the integer
+** array does change or is deallocated undefined behavior will result.
+*/
+int sqlite3_intarray_bind(
+  sqlite3_intarray *pIntArray,   /* The intarray object to bind to */
+  int nElements,                 /* Number of elements in the intarray */
+  sqlite3_int64 *aElements,      /* Content of the intarray */
+  void (*xFree)(void*)           /* How to dispose of the intarray when done */
+);
+
+#ifdef __cplusplus
+}  /* End of the 'extern "C"' block */
+#endif
+#endif /* _INTARRAY_H_ */

+ 857 - 0
components/external/sqlite/test/test_journal.c

@@ -0,0 +1,857 @@
+/*
+** 2008 Jan 22
+**
+** 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 contains code for a VFS layer that acts as a wrapper around
+** an existing VFS. The code in this file attempts to verify that SQLite
+** correctly populates and syncs a journal file before writing to a
+** corresponding database file.
+**
+** INTERFACE
+**
+**   The public interface to this wrapper VFS is two functions:
+**
+**     jt_register()
+**     jt_unregister()
+**
+**   See header comments associated with those two functions below for 
+**   details.
+**
+** LIMITATIONS
+**
+**   This wrapper will not work if "PRAGMA synchronous = off" is used.
+**
+** OPERATION
+**
+**  Starting a Transaction:
+**
+**   When a write-transaction is started, the contents of the database is
+**   inspected and the following data stored as part of the database file 
+**   handle (type struct jt_file):
+**
+**     a) The page-size of the database file.
+**     b) The number of pages that are in the database file.
+**     c) The set of page numbers corresponding to free-list leaf pages.
+**     d) A check-sum for every page in the database file.
+**
+**   The start of a write-transaction is deemed to have occurred when a 
+**   28-byte journal header is written to byte offset 0 of the journal 
+**   file.
+**
+**  Syncing the Journal File:
+**
+**   Whenever the xSync method is invoked to sync a journal-file, the
+**   contents of the journal file are read. For each page written to
+**   the journal file, a check-sum is calculated and compared to the  
+**   check-sum calculated for the corresponding database page when the
+**   write-transaction was initialized. The success of the comparison
+**   is assert()ed. So if SQLite has written something other than the
+**   original content to the database file, an assert() will fail.
+**
+**   Additionally, the set of page numbers for which records exist in
+**   the journal file is added to (unioned with) the set of page numbers
+**   corresponding to free-list leaf pages collected when the 
+**   write-transaction was initialized. This set comprises the page-numbers 
+**   corresponding to those pages that SQLite may now safely modify.
+**
+**  Writing to the Database File:
+**
+**   When a block of data is written to a database file, the following
+**   invariants are asserted:
+**
+**     a) That the block of data is an aligned block of page-size bytes.
+**
+**     b) That if the page being written did not exist when the 
+**        transaction was started (i.e. the database file is growing), then
+**        the journal-file must have been synced at least once since
+**        the start of the transaction.
+**
+**     c) That if the page being written did exist when the transaction 
+**        was started, then the page must have either been a free-list
+**        leaf page at the start of the transaction, or else must have
+**        been stored in the journal file prior to the most recent sync.
+**
+**  Closing a Transaction:
+**
+**   When a transaction is closed, all data collected at the start of
+**   the transaction, or following an xSync of a journal-file, is 
+**   discarded. The end of a transaction is recognized when any one 
+**   of the following occur:
+**
+**     a) A block of zeroes (or anything else that is not a valid 
+**        journal-header) is written to the start of the journal file.
+**
+**     b) A journal file is truncated to zero bytes in size using xTruncate.
+**
+**     c) The journal file is deleted using xDelete.
+*/
+#if SQLITE_TEST          /* This file is used for testing only */
+
+#include "sqlite3.h"
+#include "sqliteInt.h"
+
+/*
+** Maximum pathname length supported by the jt backend.
+*/
+#define JT_MAX_PATHNAME 512
+
+/*
+** Name used to identify this VFS.
+*/
+#define JT_VFS_NAME "jt"
+
+typedef struct jt_file jt_file;
+struct jt_file {
+  sqlite3_file base;
+  const char *zName;       /* Name of open file */
+  int flags;               /* Flags the file was opened with */
+
+  /* The following are only used by database file file handles */
+  int eLock;               /* Current lock held on the file */
+  u32 nPage;               /* Size of file in pages when transaction started */
+  u32 nPagesize;           /* Page size when transaction started */
+  Bitvec *pWritable;       /* Bitvec of pages that may be written to the file */
+  u32 *aCksum;             /* Checksum for first nPage pages */
+  int nSync;               /* Number of times journal file has been synced */
+
+  /* Only used by journal file-handles */
+  sqlite3_int64 iMaxOff;   /* Maximum offset written to this transaction */
+
+  jt_file *pNext;          /* All files are stored in a linked list */
+  sqlite3_file *pReal;     /* The file handle for the underlying vfs */
+};
+
+/*
+** Method declarations for jt_file.
+*/
+static int jtClose(sqlite3_file*);
+static int jtRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int jtWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
+static int jtTruncate(sqlite3_file*, sqlite3_int64 size);
+static int jtSync(sqlite3_file*, int flags);
+static int jtFileSize(sqlite3_file*, sqlite3_int64 *pSize);
+static int jtLock(sqlite3_file*, int);
+static int jtUnlock(sqlite3_file*, int);
+static int jtCheckReservedLock(sqlite3_file*, int *);
+static int jtFileControl(sqlite3_file*, int op, void *pArg);
+static int jtSectorSize(sqlite3_file*);
+static int jtDeviceCharacteristics(sqlite3_file*);
+
+/*
+** Method declarations for jt_vfs.
+*/
+static int jtOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+static int jtDelete(sqlite3_vfs*, const char *zName, int syncDir);
+static int jtAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+static int jtFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
+static void *jtDlOpen(sqlite3_vfs*, const char *zFilename);
+static void jtDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
+static void (*jtDlSym(sqlite3_vfs*,void*, const char *zSymbol))(void);
+static void jtDlClose(sqlite3_vfs*, void*);
+static int jtRandomness(sqlite3_vfs*, int nByte, char *zOut);
+static int jtSleep(sqlite3_vfs*, int microseconds);
+static int jtCurrentTime(sqlite3_vfs*, double*);
+static int jtCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
+
+static sqlite3_vfs jt_vfs = {
+  2,                             /* iVersion */
+  sizeof(jt_file),               /* szOsFile */
+  JT_MAX_PATHNAME,               /* mxPathname */
+  0,                             /* pNext */
+  JT_VFS_NAME,                   /* zName */
+  0,                             /* pAppData */
+  jtOpen,                        /* xOpen */
+  jtDelete,                      /* xDelete */
+  jtAccess,                      /* xAccess */
+  jtFullPathname,                /* xFullPathname */
+  jtDlOpen,                      /* xDlOpen */
+  jtDlError,                     /* xDlError */
+  jtDlSym,                       /* xDlSym */
+  jtDlClose,                     /* xDlClose */
+  jtRandomness,                  /* xRandomness */
+  jtSleep,                       /* xSleep */
+  jtCurrentTime,                 /* xCurrentTime */
+  0,                             /* xGetLastError */
+  jtCurrentTimeInt64             /* xCurrentTimeInt64 */
+};
+
+static sqlite3_io_methods jt_io_methods = {
+  1,                             /* iVersion */
+  jtClose,                       /* xClose */
+  jtRead,                        /* xRead */
+  jtWrite,                       /* xWrite */
+  jtTruncate,                    /* xTruncate */
+  jtSync,                        /* xSync */
+  jtFileSize,                    /* xFileSize */
+  jtLock,                        /* xLock */
+  jtUnlock,                      /* xUnlock */
+  jtCheckReservedLock,           /* xCheckReservedLock */
+  jtFileControl,                 /* xFileControl */
+  jtSectorSize,                  /* xSectorSize */
+  jtDeviceCharacteristics        /* xDeviceCharacteristics */
+};
+
+struct JtGlobal {
+  sqlite3_vfs *pVfs;             /* Parent VFS */
+  jt_file *pList;                /* List of all open files */
+};
+static struct JtGlobal g = {0, 0};
+
+/*
+** Functions to obtain and relinquish a mutex to protect g.pList. The
+** STATIC_PRNG mutex is reused, purely for the sake of convenience.
+*/
+static void enterJtMutex(void){
+  sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_PRNG));
+}
+static void leaveJtMutex(void){
+  sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_PRNG));
+}
+
+extern int sqlite3_io_error_pending;
+extern int sqlite3_io_error_hit;
+static void stop_ioerr_simulation(int *piSave, int *piSave2){
+  *piSave = sqlite3_io_error_pending;
+  *piSave2 = sqlite3_io_error_hit;
+  sqlite3_io_error_pending = -1;
+  sqlite3_io_error_hit = 0;
+}
+static void start_ioerr_simulation(int iSave, int iSave2){
+  sqlite3_io_error_pending = iSave;
+  sqlite3_io_error_hit = iSave2;
+}
+
+/*
+** The jt_file pointed to by the argument may or may not be a file-handle
+** open on a main database file. If it is, and a transaction is currently
+** opened on the file, then discard all transaction related data.
+*/
+static void closeTransaction(jt_file *p){
+  sqlite3BitvecDestroy(p->pWritable);
+  sqlite3_free(p->aCksum);
+  p->pWritable = 0;
+  p->aCksum = 0;
+  p->nSync = 0;
+}
+
+/*
+** Close an jt-file.
+*/
+static int jtClose(sqlite3_file *pFile){
+  jt_file **pp;
+  jt_file *p = (jt_file *)pFile;
+
+  closeTransaction(p);
+  enterJtMutex();
+  if( p->zName ){
+    for(pp=&g.pList; *pp!=p; pp=&(*pp)->pNext);
+    *pp = p->pNext;
+  }
+  leaveJtMutex();
+  return sqlite3OsClose(p->pReal);
+}
+
+/*
+** Read data from an jt-file.
+*/
+static int jtRead(
+  sqlite3_file *pFile, 
+  void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  jt_file *p = (jt_file *)pFile;
+  return sqlite3OsRead(p->pReal, zBuf, iAmt, iOfst);
+}
+
+/*
+** Parameter zJournal is the name of a journal file that is currently 
+** open. This function locates and returns the handle opened on the
+** corresponding database file by the pager that currently has the
+** journal file opened. This file-handle is identified by the 
+** following properties:
+**
+**   a) SQLITE_OPEN_MAIN_DB was specified when the file was opened.
+**
+**   b) The file-name specified when the file was opened matches
+**      all but the final 8 characters of the journal file name.
+**
+**   c) There is currently a reserved lock on the file.
+**/
+static jt_file *locateDatabaseHandle(const char *zJournal){
+  jt_file *pMain = 0;
+  enterJtMutex();
+  for(pMain=g.pList; pMain; pMain=pMain->pNext){
+    int nName = (int)(strlen(zJournal) - strlen("-journal"));
+    if( (pMain->flags&SQLITE_OPEN_MAIN_DB)
+     && ((int)strlen(pMain->zName)==nName)
+     && 0==memcmp(pMain->zName, zJournal, nName)
+     && (pMain->eLock>=SQLITE_LOCK_RESERVED)
+    ){
+      break;
+    }
+  }
+  leaveJtMutex();
+  return pMain;
+}
+
+/*
+** Parameter z points to a buffer of 4 bytes in size containing a 
+** unsigned 32-bit integer stored in big-endian format. Decode the 
+** integer and return its value.
+*/
+static u32 decodeUint32(const unsigned char *z){
+  return (z[0]<<24) + (z[1]<<16) + (z[2]<<8) + z[3];
+}
+
+/*
+** Calculate a checksum from the buffer of length n bytes pointed to
+** by parameter z.
+*/
+static u32 genCksum(const unsigned char *z, int n){
+  int i;
+  u32 cksum = 0;
+  for(i=0; i<n; i++){
+    cksum = cksum + z[i] + (cksum<<3);
+  }
+  return cksum;
+}
+
+/*
+** The first argument, zBuf, points to a buffer containing a 28 byte
+** serialized journal header. This function deserializes four of the
+** integer fields contained in the journal header and writes their
+** values to the output variables.
+**
+** SQLITE_OK is returned if the journal-header is successfully 
+** decoded. Otherwise, SQLITE_ERROR.
+*/
+static int decodeJournalHdr(
+  const unsigned char *zBuf,         /* Input: 28 byte journal header */
+  u32 *pnRec,                        /* Out: Number of journalled records */
+  u32 *pnPage,                       /* Out: Original database page count */
+  u32 *pnSector,                     /* Out: Sector size in bytes */
+  u32 *pnPagesize                    /* Out: Page size in bytes */
+){
+  unsigned char aMagic[] = { 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd7 };
+  if( memcmp(aMagic, zBuf, 8) ) return SQLITE_ERROR;
+  if( pnRec ) *pnRec = decodeUint32(&zBuf[8]);
+  if( pnPage ) *pnPage = decodeUint32(&zBuf[16]);
+  if( pnSector ) *pnSector = decodeUint32(&zBuf[20]);
+  if( pnPagesize ) *pnPagesize = decodeUint32(&zBuf[24]);
+  return SQLITE_OK;
+}
+
+/*
+** This function is called when a new transaction is opened, just after
+** the first journal-header is written to the journal file.
+*/
+static int openTransaction(jt_file *pMain, jt_file *pJournal){
+  unsigned char *aData;
+  sqlite3_file *p = pMain->pReal;
+  int rc = SQLITE_OK;
+
+  closeTransaction(pMain);
+  aData = sqlite3_malloc(pMain->nPagesize);
+  pMain->pWritable = sqlite3BitvecCreate(pMain->nPage);
+  pMain->aCksum = sqlite3_malloc(sizeof(u32) * (pMain->nPage + 1));
+  pJournal->iMaxOff = 0;
+
+  if( !pMain->pWritable || !pMain->aCksum || !aData ){
+    rc = SQLITE_IOERR_NOMEM;
+  }else if( pMain->nPage>0 ){
+    u32 iTrunk;
+    int iSave;
+    int iSave2;
+
+    stop_ioerr_simulation(&iSave, &iSave2);
+
+    /* Read the database free-list. Add the page-number for each free-list
+    ** leaf to the jt_file.pWritable bitvec.
+    */
+    rc = sqlite3OsRead(p, aData, pMain->nPagesize, 0);
+    if( rc==SQLITE_OK ){
+      u32 nDbsize = decodeUint32(&aData[28]);
+      if( nDbsize>0 && memcmp(&aData[24], &aData[92], 4)==0 ){
+        u32 iPg;
+        for(iPg=nDbsize+1; iPg<=pMain->nPage; iPg++){
+          sqlite3BitvecSet(pMain->pWritable, iPg);
+        }
+      }
+    }
+    iTrunk = decodeUint32(&aData[32]);
+    while( rc==SQLITE_OK && iTrunk>0 ){
+      u32 nLeaf;
+      u32 iLeaf;
+      sqlite3_int64 iOff = (i64)(iTrunk-1)*pMain->nPagesize;
+      rc = sqlite3OsRead(p, aData, pMain->nPagesize, iOff);
+      nLeaf = decodeUint32(&aData[4]);
+      for(iLeaf=0; rc==SQLITE_OK && iLeaf<nLeaf; iLeaf++){
+        u32 pgno = decodeUint32(&aData[8+4*iLeaf]);
+        sqlite3BitvecSet(pMain->pWritable, pgno);
+      }
+      iTrunk = decodeUint32(aData);
+    }
+
+    /* Calculate and store a checksum for each page in the database file. */
+    if( rc==SQLITE_OK ){
+      int ii;
+      for(ii=0; rc==SQLITE_OK && ii<(int)pMain->nPage; ii++){
+        i64 iOff = (i64)(pMain->nPagesize) * (i64)ii;
+        if( iOff==PENDING_BYTE ) continue;
+        rc = sqlite3OsRead(pMain->pReal, aData, pMain->nPagesize, iOff);
+        pMain->aCksum[ii] = genCksum(aData, pMain->nPagesize);
+        if( ii+1==pMain->nPage && rc==SQLITE_IOERR_SHORT_READ ) rc = SQLITE_OK;
+      }
+    }
+
+    start_ioerr_simulation(iSave, iSave2);
+  }
+
+  sqlite3_free(aData);
+  return rc;
+}
+
+/*
+** The first argument to this function is a handle open on a journal file.
+** This function reads the journal file and adds the page number for each
+** page in the journal to the Bitvec object passed as the second argument.
+*/
+static int readJournalFile(jt_file *p, jt_file *pMain){
+  int rc = SQLITE_OK;
+  unsigned char zBuf[28];
+  sqlite3_file *pReal = p->pReal;
+  sqlite3_int64 iOff = 0;
+  sqlite3_int64 iSize = p->iMaxOff;
+  unsigned char *aPage;
+  int iSave;
+  int iSave2;
+
+  aPage = sqlite3_malloc(pMain->nPagesize);
+  if( !aPage ){
+    return SQLITE_IOERR_NOMEM;
+  }
+
+  stop_ioerr_simulation(&iSave, &iSave2);
+
+  while( rc==SQLITE_OK && iOff<iSize ){
+    u32 nRec, nPage, nSector, nPagesize;
+    u32 ii;
+
+    /* Read and decode the next journal-header from the journal file. */
+    rc = sqlite3OsRead(pReal, zBuf, 28, iOff);
+    if( rc!=SQLITE_OK 
+     || decodeJournalHdr(zBuf, &nRec, &nPage, &nSector, &nPagesize) 
+    ){
+      goto finish_rjf;
+    }
+    iOff += nSector;
+
+    if( nRec==0 ){
+      /* A trick. There might be another journal-header immediately 
+      ** following this one. In this case, 0 records means 0 records, 
+      ** not "read until the end of the file". See also ticket #2565.
+      */
+      if( iSize>=(iOff+nSector) ){
+        rc = sqlite3OsRead(pReal, zBuf, 28, iOff);
+        if( rc!=SQLITE_OK || 0==decodeJournalHdr(zBuf, 0, 0, 0, 0) ){
+          continue;
+        }
+      }
+      nRec = (u32)((iSize-iOff) / (pMain->nPagesize+8));
+    }
+
+    /* Read all the records that follow the journal-header just read. */
+    for(ii=0; rc==SQLITE_OK && ii<nRec && iOff<iSize; ii++){
+      u32 pgno;
+      rc = sqlite3OsRead(pReal, zBuf, 4, iOff);
+      if( rc==SQLITE_OK ){
+        pgno = decodeUint32(zBuf);
+        if( pgno>0 && pgno<=pMain->nPage ){
+          if( 0==sqlite3BitvecTest(pMain->pWritable, pgno) ){
+            rc = sqlite3OsRead(pReal, aPage, pMain->nPagesize, iOff+4);
+            if( rc==SQLITE_OK ){
+              u32 cksum = genCksum(aPage, pMain->nPagesize);
+              assert( cksum==pMain->aCksum[pgno-1] );
+            }
+          }
+          sqlite3BitvecSet(pMain->pWritable, pgno);
+        }
+        iOff += (8 + pMain->nPagesize);
+      }
+    }
+
+    iOff = ((iOff + (nSector-1)) / nSector) * nSector;
+  }
+
+finish_rjf:
+  start_ioerr_simulation(iSave, iSave2);
+  sqlite3_free(aPage);
+  if( rc==SQLITE_IOERR_SHORT_READ ){
+    rc = SQLITE_OK;
+  }
+  return rc;
+}
+
+/*
+** Write data to an jt-file.
+*/
+static int jtWrite(
+  sqlite3_file *pFile, 
+  const void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  int rc;
+  jt_file *p = (jt_file *)pFile;
+  if( p->flags&SQLITE_OPEN_MAIN_JOURNAL ){
+    if( iOfst==0 ){
+      jt_file *pMain = locateDatabaseHandle(p->zName);
+      assert( pMain );
+  
+      if( iAmt==28 ){
+        /* Zeroing the first journal-file header. This is the end of a
+        ** transaction. */
+        closeTransaction(pMain);
+      }else if( iAmt!=12 ){
+        /* Writing the first journal header to a journal file. This happens
+        ** when a transaction is first started.  */
+        u8 *z = (u8 *)zBuf;
+        pMain->nPage = decodeUint32(&z[16]);
+        pMain->nPagesize = decodeUint32(&z[24]);
+        if( SQLITE_OK!=(rc=openTransaction(pMain, p)) ){
+          return rc;
+        }
+      }
+    }
+    if( p->iMaxOff<(iOfst + iAmt) ){
+      p->iMaxOff = iOfst + iAmt;
+    }
+  }
+
+  if( p->flags&SQLITE_OPEN_MAIN_DB && p->pWritable ){
+    if( iAmt<(int)p->nPagesize 
+     && p->nPagesize%iAmt==0 
+     && iOfst>=(PENDING_BYTE+512) 
+     && iOfst+iAmt<=PENDING_BYTE+p->nPagesize
+    ){
+      /* No-op. This special case is hit when the backup code is copying a
+      ** to a database with a larger page-size than the source database and
+      ** it needs to fill in the non-locking-region part of the original
+      ** pending-byte page.
+      */
+    }else{
+      u32 pgno = (u32)(iOfst/p->nPagesize + 1);
+      assert( (iAmt==1||iAmt==p->nPagesize) && ((iOfst+iAmt)%p->nPagesize)==0 );
+      assert( pgno<=p->nPage || p->nSync>0 );
+      assert( pgno>p->nPage || sqlite3BitvecTest(p->pWritable, pgno) );
+    }
+  }
+
+  rc = sqlite3OsWrite(p->pReal, zBuf, iAmt, iOfst);
+  if( (p->flags&SQLITE_OPEN_MAIN_JOURNAL) && iAmt==12 ){
+    jt_file *pMain = locateDatabaseHandle(p->zName);
+    int rc2 = readJournalFile(p, pMain);
+    if( rc==SQLITE_OK ) rc = rc2;
+  }
+  return rc;
+}
+
+/*
+** Truncate an jt-file.
+*/
+static int jtTruncate(sqlite3_file *pFile, sqlite_int64 size){
+  jt_file *p = (jt_file *)pFile;
+  if( p->flags&SQLITE_OPEN_MAIN_JOURNAL && size==0 ){
+    /* Truncating a journal file. This is the end of a transaction. */
+    jt_file *pMain = locateDatabaseHandle(p->zName);
+    closeTransaction(pMain);
+  }
+  if( p->flags&SQLITE_OPEN_MAIN_DB && p->pWritable ){
+    u32 pgno;
+    u32 locking_page = (u32)(PENDING_BYTE/p->nPagesize+1);
+    for(pgno=(u32)(size/p->nPagesize+1); pgno<=p->nPage; pgno++){
+      assert( pgno==locking_page || sqlite3BitvecTest(p->pWritable, pgno) );
+    }
+  }
+  return sqlite3OsTruncate(p->pReal, size);
+}
+
+/*
+** Sync an jt-file.
+*/
+static int jtSync(sqlite3_file *pFile, int flags){
+  jt_file *p = (jt_file *)pFile;
+
+  if( p->flags&SQLITE_OPEN_MAIN_JOURNAL ){
+    int rc;
+    jt_file *pMain;                   /* The associated database file */
+
+    /* The journal file is being synced. At this point, we inspect the 
+    ** contents of the file up to this point and set each bit in the 
+    ** jt_file.pWritable bitvec of the main database file associated with
+    ** this journal file.
+    */
+    pMain = locateDatabaseHandle(p->zName);
+    assert(pMain);
+
+    /* Set the bitvec values */
+    if( pMain->pWritable ){
+      pMain->nSync++;
+      rc = readJournalFile(p, pMain);
+      if( rc!=SQLITE_OK ){
+        return rc;
+      }
+    }
+  }
+
+  return sqlite3OsSync(p->pReal, flags);
+}
+
+/*
+** Return the current file-size of an jt-file.
+*/
+static int jtFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+  jt_file *p = (jt_file *)pFile;
+  return sqlite3OsFileSize(p->pReal, pSize);
+}
+
+/*
+** Lock an jt-file.
+*/
+static int jtLock(sqlite3_file *pFile, int eLock){
+  int rc;
+  jt_file *p = (jt_file *)pFile;
+  rc = sqlite3OsLock(p->pReal, eLock);
+  if( rc==SQLITE_OK && eLock>p->eLock ){
+    p->eLock = eLock;
+  }
+  return rc;
+}
+
+/*
+** Unlock an jt-file.
+*/
+static int jtUnlock(sqlite3_file *pFile, int eLock){
+  int rc;
+  jt_file *p = (jt_file *)pFile;
+  rc = sqlite3OsUnlock(p->pReal, eLock);
+  if( rc==SQLITE_OK && eLock<p->eLock ){
+    p->eLock = eLock;
+  }
+  return rc;
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on an jt-file.
+*/
+static int jtCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+  jt_file *p = (jt_file *)pFile;
+  return sqlite3OsCheckReservedLock(p->pReal, pResOut);
+}
+
+/*
+** File control method. For custom operations on an jt-file.
+*/
+static int jtFileControl(sqlite3_file *pFile, int op, void *pArg){
+  jt_file *p = (jt_file *)pFile;
+  return p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
+}
+
+/*
+** Return the sector-size in bytes for an jt-file.
+*/
+static int jtSectorSize(sqlite3_file *pFile){
+  jt_file *p = (jt_file *)pFile;
+  return sqlite3OsSectorSize(p->pReal);
+}
+
+/*
+** Return the device characteristic flags supported by an jt-file.
+*/
+static int jtDeviceCharacteristics(sqlite3_file *pFile){
+  jt_file *p = (jt_file *)pFile;
+  return sqlite3OsDeviceCharacteristics(p->pReal);
+}
+
+/*
+** Open an jt file handle.
+*/
+static int jtOpen(
+  sqlite3_vfs *pVfs,
+  const char *zName,
+  sqlite3_file *pFile,
+  int flags,
+  int *pOutFlags
+){
+  int rc;
+  jt_file *p = (jt_file *)pFile;
+  pFile->pMethods = 0;
+  p->pReal = (sqlite3_file *)&p[1];
+  p->pReal->pMethods = 0;
+  rc = sqlite3OsOpen(g.pVfs, zName, p->pReal, flags, pOutFlags);
+  assert( rc==SQLITE_OK || p->pReal->pMethods==0 );
+  if( rc==SQLITE_OK ){
+    pFile->pMethods = &jt_io_methods;
+    p->eLock = 0;
+    p->zName = zName;
+    p->flags = flags;
+    p->pNext = 0;
+    p->pWritable = 0;
+    p->aCksum = 0;
+    enterJtMutex();
+    if( zName ){
+      p->pNext = g.pList;
+      g.pList = p;
+    }
+    leaveJtMutex();
+  }
+  return rc;
+}
+
+/*
+** Delete the file located at zPath. If the dirSync argument is true,
+** ensure the file-system modifications are synced to disk before
+** returning.
+*/
+static int jtDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+  int nPath = (int)strlen(zPath);
+  if( nPath>8 && 0==strcmp("-journal", &zPath[nPath-8]) ){
+    /* Deleting a journal file. The end of a transaction. */
+    jt_file *pMain = locateDatabaseHandle(zPath);
+    if( pMain ){
+      closeTransaction(pMain);
+    }
+  }
+
+  return sqlite3OsDelete(g.pVfs, zPath, dirSync);
+}
+
+/*
+** Test for access permissions. Return true if the requested permission
+** is available, or false otherwise.
+*/
+static int jtAccess(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int flags, 
+  int *pResOut
+){
+  return sqlite3OsAccess(g.pVfs, zPath, flags, pResOut);
+}
+
+/*
+** Populate buffer zOut with the full canonical pathname corresponding
+** to the pathname in zPath. zOut is guaranteed to point to a buffer
+** of at least (JT_MAX_PATHNAME+1) bytes.
+*/
+static int jtFullPathname(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int nOut, 
+  char *zOut
+){
+  return sqlite3OsFullPathname(g.pVfs, zPath, nOut, zOut);
+}
+
+/*
+** Open the dynamic library located at zPath and return a handle.
+*/
+static void *jtDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+  return g.pVfs->xDlOpen(g.pVfs, zPath);
+}
+
+/*
+** Populate the buffer zErrMsg (size nByte bytes) with a human readable
+** utf-8 string describing the most recent error encountered associated 
+** with dynamic libraries.
+*/
+static void jtDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+  g.pVfs->xDlError(g.pVfs, nByte, zErrMsg);
+}
+
+/*
+** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
+*/
+static void (*jtDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
+  return g.pVfs->xDlSym(g.pVfs, p, zSym);
+}
+
+/*
+** Close the dynamic library handle pHandle.
+*/
+static void jtDlClose(sqlite3_vfs *pVfs, void *pHandle){
+  g.pVfs->xDlClose(g.pVfs, pHandle);
+}
+
+/*
+** Populate the buffer pointed to by zBufOut with nByte bytes of 
+** random data.
+*/
+static int jtRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+  return sqlite3OsRandomness(g.pVfs, nByte, zBufOut);
+}
+
+/*
+** Sleep for nMicro microseconds. Return the number of microseconds 
+** actually slept.
+*/
+static int jtSleep(sqlite3_vfs *pVfs, int nMicro){
+  return sqlite3OsSleep(g.pVfs, nMicro);
+}
+
+/*
+** Return the current time as a Julian Day number in *pTimeOut.
+*/
+static int jtCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+  return g.pVfs->xCurrentTime(g.pVfs, pTimeOut);
+}
+/*
+** Return the current time as a Julian Day number in *pTimeOut.
+*/
+static int jtCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
+  return g.pVfs->xCurrentTimeInt64(g.pVfs, pTimeOut);
+}
+
+/**************************************************************************
+** Start of public API.
+*/
+
+/*
+** Configure the jt VFS as a wrapper around the VFS named by parameter 
+** zWrap. If the isDefault parameter is true, then the jt VFS is installed
+** as the new default VFS for SQLite connections. If isDefault is not
+** true, then the jt VFS is installed as non-default. In this case it
+** is available via its name, "jt".
+*/
+int jt_register(char *zWrap, int isDefault){
+  g.pVfs = sqlite3_vfs_find(zWrap);
+  if( g.pVfs==0 ){
+    return SQLITE_ERROR;
+  }
+  jt_vfs.szOsFile = sizeof(jt_file) + g.pVfs->szOsFile;
+  if( g.pVfs->iVersion==1 ){
+    jt_vfs.iVersion = 1;
+  }else if( g.pVfs->xCurrentTimeInt64==0 ){
+    jt_vfs.xCurrentTimeInt64 = 0;
+  }
+  sqlite3_vfs_register(&jt_vfs, isDefault);
+  return SQLITE_OK;
+}
+
+/*
+** Uninstall the jt VFS, if it is installed.
+*/
+void jt_unregister(void){
+  sqlite3_vfs_unregister(&jt_vfs);
+}
+
+#endif

+ 122 - 0
components/external/sqlite/test/test_loadext.c

@@ -0,0 +1,122 @@
+/*
+** 2006 June 14
+**
+** 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.
+**
+*************************************************************************
+** Test extension for testing the sqlite3_load_extension() function.
+*/
+#include <string.h>
+#include "sqlite3ext.h"
+SQLITE_EXTENSION_INIT1
+
+/*
+** The half() SQL function returns half of its input value.
+*/
+static void halfFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  sqlite3_result_double(context, 0.5*sqlite3_value_double(argv[0]));
+}
+
+/*
+** SQL functions to call the sqlite3_status function and return results.
+*/
+static void statusFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  int op, mx, cur, resetFlag, rc;
+  if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){
+    op = sqlite3_value_int(argv[0]);
+  }else if( sqlite3_value_type(argv[0])==SQLITE_TEXT ){
+    int i;
+    const char *zName;
+    static const struct {
+      const char *zName;
+      int op;
+    } aOp[] = {
+      { "MEMORY_USED",         SQLITE_STATUS_MEMORY_USED         },
+      { "PAGECACHE_USED",      SQLITE_STATUS_PAGECACHE_USED      },
+      { "PAGECACHE_OVERFLOW",  SQLITE_STATUS_PAGECACHE_OVERFLOW  },
+      { "SCRATCH_USED",        SQLITE_STATUS_SCRATCH_USED        },
+      { "SCRATCH_OVERFLOW",    SQLITE_STATUS_SCRATCH_OVERFLOW    },
+      { "MALLOC_SIZE",         SQLITE_STATUS_MALLOC_SIZE         },
+    };
+    int nOp = sizeof(aOp)/sizeof(aOp[0]);
+    zName = (const char*)sqlite3_value_text(argv[0]);
+    for(i=0; i<nOp; i++){
+      if( strcmp(aOp[i].zName, zName)==0 ){
+        op = aOp[i].op;
+        break;
+      }
+    }
+    if( i>=nOp ){
+      char *zMsg = sqlite3_mprintf("unknown status property: %s", zName);
+      sqlite3_result_error(context, zMsg, -1);
+      sqlite3_free(zMsg);
+      return;
+    }
+  }else{
+    sqlite3_result_error(context, "unknown status type", -1);
+    return;
+  }
+  if( argc==2 ){
+    resetFlag = sqlite3_value_int(argv[1]);
+  }else{
+    resetFlag = 0;
+  }
+  rc = sqlite3_status(op, &cur, &mx, resetFlag);
+  if( rc!=SQLITE_OK ){
+    char *zMsg = sqlite3_mprintf("sqlite3_status(%d,...) returns %d", op, rc);
+    sqlite3_result_error(context, zMsg, -1);
+    sqlite3_free(zMsg);
+    return;
+  } 
+  if( argc==2 ){
+    sqlite3_result_int(context, mx);
+  }else{
+    sqlite3_result_int(context, cur);
+  }
+}
+
+/*
+** Extension load function.
+*/
+int testloadext_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  int nErr = 0;
+  SQLITE_EXTENSION_INIT2(pApi);
+  nErr |= sqlite3_create_function(db, "half", 1, SQLITE_ANY, 0, halfFunc, 0, 0);
+  nErr |= sqlite3_create_function(db, "sqlite3_status", 1, SQLITE_ANY, 0,
+                          statusFunc, 0, 0);
+  nErr |= sqlite3_create_function(db, "sqlite3_status", 2, SQLITE_ANY, 0,
+                          statusFunc, 0, 0);
+  return nErr ? SQLITE_ERROR : SQLITE_OK;
+}
+
+/*
+** Another extension entry point. This one always fails.
+*/
+int testbrokenext_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  char *zErr;
+  SQLITE_EXTENSION_INIT2(pApi);
+  zErr = sqlite3_mprintf("broken!");
+  *pzErrMsg = zErr;
+  return 1;
+}

+ 1494 - 0
components/external/sqlite/test/test_malloc.c

@@ -0,0 +1,1494 @@
+/*
+** 2007 August 15
+**
+** 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 contains code used to implement test interfaces to the
+** memory allocation subsystem.
+*/
+#include "sqliteInt.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/*
+** This structure is used to encapsulate the global state variables used 
+** by malloc() fault simulation.
+*/
+static struct MemFault {
+  int iCountdown;         /* Number of pending successes before a failure */
+  int nRepeat;            /* Number of times to repeat the failure */
+  int nBenign;            /* Number of benign failures seen since last config */
+  int nFail;              /* Number of failures seen since last config */
+  u8 enable;              /* True if enabled */
+  int isInstalled;        /* True if the fault simulation layer is installed */
+  int isBenignMode;       /* True if malloc failures are considered benign */
+  sqlite3_mem_methods m;  /* 'Real' malloc implementation */
+} memfault;
+
+/*
+** This routine exists as a place to set a breakpoint that will
+** fire on any simulated malloc() failure.
+*/
+static void sqlite3Fault(void){
+  static int cnt = 0;
+  cnt++;
+}
+
+/*
+** Check to see if a fault should be simulated.  Return true to simulate
+** the fault.  Return false if the fault should not be simulated.
+*/
+static int faultsimStep(void){
+  if( likely(!memfault.enable) ){
+    return 0;
+  }
+  if( memfault.iCountdown>0 ){
+    memfault.iCountdown--;
+    return 0;
+  }
+  sqlite3Fault();
+  memfault.nFail++;
+  if( memfault.isBenignMode>0 ){
+    memfault.nBenign++;
+  }
+  memfault.nRepeat--;
+  if( memfault.nRepeat<=0 ){
+    memfault.enable = 0;
+  }
+  return 1;  
+}
+
+/*
+** A version of sqlite3_mem_methods.xMalloc() that includes fault simulation
+** logic.
+*/
+static void *faultsimMalloc(int n){
+  void *p = 0;
+  if( !faultsimStep() ){
+    p = memfault.m.xMalloc(n);
+  }
+  return p;
+}
+
+
+/*
+** A version of sqlite3_mem_methods.xRealloc() that includes fault simulation
+** logic.
+*/
+static void *faultsimRealloc(void *pOld, int n){
+  void *p = 0;
+  if( !faultsimStep() ){
+    p = memfault.m.xRealloc(pOld, n);
+  }
+  return p;
+}
+
+/* 
+** The following method calls are passed directly through to the underlying
+** malloc system:
+**
+**     xFree
+**     xSize
+**     xRoundup
+**     xInit
+**     xShutdown
+*/
+static void faultsimFree(void *p){
+  memfault.m.xFree(p);
+}
+static int faultsimSize(void *p){
+  return memfault.m.xSize(p);
+}
+static int faultsimRoundup(int n){
+  return memfault.m.xRoundup(n);
+}
+static int faultsimInit(void *p){
+  return memfault.m.xInit(memfault.m.pAppData);
+}
+static void faultsimShutdown(void *p){
+  memfault.m.xShutdown(memfault.m.pAppData);
+}
+
+/*
+** This routine configures the malloc failure simulation.  After
+** calling this routine, the next nDelay mallocs will succeed, followed
+** by a block of nRepeat failures, after which malloc() calls will begin
+** to succeed again.
+*/
+static void faultsimConfig(int nDelay, int nRepeat){
+  memfault.iCountdown = nDelay;
+  memfault.nRepeat = nRepeat;
+  memfault.nBenign = 0;
+  memfault.nFail = 0;
+  memfault.enable = nDelay>=0;
+
+  /* Sometimes, when running multi-threaded tests, the isBenignMode 
+  ** variable is not properly incremented/decremented so that it is
+  ** 0 when not inside a benign malloc block. This doesn't affect
+  ** the multi-threaded tests, as they do not use this system. But
+  ** it does affect OOM tests run later in the same process. So
+  ** zero the variable here, just to be sure.
+  */
+  memfault.isBenignMode = 0;
+}
+
+/*
+** Return the number of faults (both hard and benign faults) that have
+** occurred since the injector was last configured.
+*/
+static int faultsimFailures(void){
+  return memfault.nFail;
+}
+
+/*
+** Return the number of benign faults that have occurred since the
+** injector was last configured.
+*/
+static int faultsimBenignFailures(void){
+  return memfault.nBenign;
+}
+
+/*
+** Return the number of successes that will occur before the next failure.
+** If no failures are scheduled, return -1.
+*/
+static int faultsimPending(void){
+  if( memfault.enable ){
+    return memfault.iCountdown;
+  }else{
+    return -1;
+  }
+}
+
+
+static void faultsimBeginBenign(void){
+  memfault.isBenignMode++;
+}
+static void faultsimEndBenign(void){
+  memfault.isBenignMode--;
+}
+
+/*
+** Add or remove the fault-simulation layer using sqlite3_config(). If
+** the argument is non-zero, the 
+*/
+static int faultsimInstall(int install){
+  static struct sqlite3_mem_methods m = {
+    faultsimMalloc,                   /* xMalloc */
+    faultsimFree,                     /* xFree */
+    faultsimRealloc,                  /* xRealloc */
+    faultsimSize,                     /* xSize */
+    faultsimRoundup,                  /* xRoundup */
+    faultsimInit,                     /* xInit */
+    faultsimShutdown,                 /* xShutdown */
+    0                                 /* pAppData */
+  };
+  int rc;
+
+  install = (install ? 1 : 0);
+  assert(memfault.isInstalled==1 || memfault.isInstalled==0);
+
+  if( install==memfault.isInstalled ){
+    return SQLITE_ERROR;
+  }
+
+  if( install ){
+    rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &memfault.m);
+    assert(memfault.m.xMalloc);
+    if( rc==SQLITE_OK ){
+      rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &m);
+    }
+    sqlite3_test_control(SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS, 
+        faultsimBeginBenign, faultsimEndBenign
+    );
+  }else{
+    sqlite3_mem_methods m;
+    assert(memfault.m.xMalloc);
+
+    /* One should be able to reset the default memory allocator by storing
+    ** a zeroed allocator then calling GETMALLOC. */
+    memset(&m, 0, sizeof(m));
+    sqlite3_config(SQLITE_CONFIG_MALLOC, &m);
+    sqlite3_config(SQLITE_CONFIG_GETMALLOC, &m);
+    assert( memcmp(&m, &memfault.m, sizeof(m))==0 );
+
+    rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &memfault.m);
+    sqlite3_test_control(SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS, 0, 0);
+  }
+
+  if( rc==SQLITE_OK ){
+    memfault.isInstalled = 1;
+  }
+  return rc;
+}
+
+#ifdef SQLITE_TEST
+
+/*
+** This function is implemented in main.c. Returns a pointer to a static
+** buffer containing the symbolic SQLite error code that corresponds to
+** the least-significant 8-bits of the integer passed as an argument.
+** For example:
+**
+**   sqlite3ErrName(1) -> "SQLITE_ERROR"
+*/
+extern const char *sqlite3ErrName(int);
+
+/*
+** Transform pointers to text and back again
+*/
+static void pointerToText(void *p, char *z){
+  static const char zHex[] = "0123456789abcdef";
+  int i, k;
+  unsigned int u;
+  sqlite3_uint64 n;
+  if( p==0 ){
+    strcpy(z, "0");
+    return;
+  }
+  if( sizeof(n)==sizeof(p) ){
+    memcpy(&n, &p, sizeof(p));
+  }else if( sizeof(u)==sizeof(p) ){
+    memcpy(&u, &p, sizeof(u));
+    n = u;
+  }else{
+    assert( 0 );
+  }
+  for(i=0, k=sizeof(p)*2-1; i<sizeof(p)*2; i++, k--){
+    z[k] = zHex[n&0xf];
+    n >>= 4;
+  }
+  z[sizeof(p)*2] = 0;
+}
+static int hexToInt(int h){
+  if( h>='0' && h<='9' ){
+    return h - '0';
+  }else if( h>='a' && h<='f' ){
+    return h - 'a' + 10;
+  }else{
+    return -1;
+  }
+}
+static int textToPointer(const char *z, void **pp){
+  sqlite3_uint64 n = 0;
+  int i;
+  unsigned int u;
+  for(i=0; i<sizeof(void*)*2 && z[0]; i++){
+    int v;
+    v = hexToInt(*z++);
+    if( v<0 ) return TCL_ERROR;
+    n = n*16 + v;
+  }
+  if( *z!=0 ) return TCL_ERROR;
+  if( sizeof(n)==sizeof(*pp) ){
+    memcpy(pp, &n, sizeof(n));
+  }else if( sizeof(u)==sizeof(*pp) ){
+    u = (unsigned int)n;
+    memcpy(pp, &u, sizeof(u));
+  }else{
+    assert( 0 );
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_malloc  NBYTES
+**
+** Raw test interface for sqlite3_malloc().
+*/
+static int test_malloc(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int nByte;
+  void *p;
+  char zOut[100];
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "NBYTES");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[1], &nByte) ) return TCL_ERROR;
+  p = sqlite3_malloc((unsigned)nByte);
+  pointerToText(p, zOut);
+  Tcl_AppendResult(interp, zOut, NULL);
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_realloc  PRIOR  NBYTES
+**
+** Raw test interface for sqlite3_realloc().
+*/
+static int test_realloc(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int nByte;
+  void *pPrior, *p;
+  char zOut[100];
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PRIOR NBYTES");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[2], &nByte) ) return TCL_ERROR;
+  if( textToPointer(Tcl_GetString(objv[1]), &pPrior) ){
+    Tcl_AppendResult(interp, "bad pointer: ", Tcl_GetString(objv[1]), (char*)0);
+    return TCL_ERROR;
+  }
+  p = sqlite3_realloc(pPrior, (unsigned)nByte);
+  pointerToText(p, zOut);
+  Tcl_AppendResult(interp, zOut, NULL);
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_free  PRIOR
+**
+** Raw test interface for sqlite3_free().
+*/
+static int test_free(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  void *pPrior;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PRIOR");
+    return TCL_ERROR;
+  }
+  if( textToPointer(Tcl_GetString(objv[1]), &pPrior) ){
+    Tcl_AppendResult(interp, "bad pointer: ", Tcl_GetString(objv[1]), (char*)0);
+    return TCL_ERROR;
+  }
+  sqlite3_free(pPrior);
+  return TCL_OK;
+}
+
+/*
+** These routines are in test_hexio.c
+*/
+int sqlite3TestHexToBin(const char *, int, char *);
+int sqlite3TestBinToHex(char*,int);
+
+/*
+** Usage:    memset  ADDRESS  SIZE  HEX
+**
+** Set a chunk of memory (obtained from malloc, probably) to a
+** specified hex pattern.
+*/
+static int test_memset(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  void *p;
+  int size, n, i;
+  char *zHex;
+  char *zOut;
+  char zBin[100];
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "ADDRESS SIZE HEX");
+    return TCL_ERROR;
+  }
+  if( textToPointer(Tcl_GetString(objv[1]), &p) ){
+    Tcl_AppendResult(interp, "bad pointer: ", Tcl_GetString(objv[1]), (char*)0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[2], &size) ){
+    return TCL_ERROR;
+  }
+  if( size<=0 ){
+    Tcl_AppendResult(interp, "size must be positive", (char*)0);
+    return TCL_ERROR;
+  }
+  zHex = Tcl_GetStringFromObj(objv[3], &n);
+  if( n>sizeof(zBin)*2 ) n = sizeof(zBin)*2;
+  n = sqlite3TestHexToBin(zHex, n, zBin);
+  if( n==0 ){
+    Tcl_AppendResult(interp, "no data", (char*)0);
+    return TCL_ERROR;
+  }
+  zOut = p;
+  for(i=0; i<size; i++){
+    zOut[i] = zBin[i%n];
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:    memget  ADDRESS  SIZE
+**
+** Return memory as hexadecimal text.
+*/
+static int test_memget(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  void *p;
+  int size, n;
+  char *zBin;
+  char zHex[100];
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "ADDRESS SIZE");
+    return TCL_ERROR;
+  }
+  if( textToPointer(Tcl_GetString(objv[1]), &p) ){
+    Tcl_AppendResult(interp, "bad pointer: ", Tcl_GetString(objv[1]), (char*)0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[2], &size) ){
+    return TCL_ERROR;
+  }
+  if( size<=0 ){
+    Tcl_AppendResult(interp, "size must be positive", (char*)0);
+    return TCL_ERROR;
+  }
+  zBin = p;
+  while( size>0 ){
+    if( size>(sizeof(zHex)-1)/2 ){
+      n = (sizeof(zHex)-1)/2;
+    }else{
+      n = size;
+    }
+    memcpy(zHex, zBin, n);
+    zBin += n;
+    size -= n;
+    sqlite3TestBinToHex(zHex, n);
+    Tcl_AppendResult(interp, zHex, (char*)0);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_memory_used
+**
+** Raw test interface for sqlite3_memory_used().
+*/
+static int test_memory_used(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(sqlite3_memory_used()));
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_memory_highwater ?RESETFLAG?
+**
+** Raw test interface for sqlite3_memory_highwater().
+*/
+static int test_memory_highwater(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int resetFlag = 0;
+  if( objc!=1 && objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "?RESET?");
+    return TCL_ERROR;
+  }
+  if( objc==2 ){
+    if( Tcl_GetBooleanFromObj(interp, objv[1], &resetFlag) ) return TCL_ERROR;
+  } 
+  Tcl_SetObjResult(interp, 
+     Tcl_NewWideIntObj(sqlite3_memory_highwater(resetFlag)));
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_memdebug_backtrace DEPTH
+**
+** Set the depth of backtracing.  If SQLITE_MEMDEBUG is not defined
+** then this routine is a no-op.
+*/
+static int test_memdebug_backtrace(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int depth;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DEPT");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[1], &depth) ) return TCL_ERROR;
+#ifdef SQLITE_MEMDEBUG
+  {
+    extern void sqlite3MemdebugBacktrace(int);
+    sqlite3MemdebugBacktrace(depth);
+  }
+#endif
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_memdebug_dump  FILENAME
+**
+** Write a summary of unfreed memory to FILENAME.
+*/
+static int test_memdebug_dump(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
+    return TCL_ERROR;
+  }
+#if defined(SQLITE_MEMDEBUG) || defined(SQLITE_MEMORY_SIZE) \
+     || defined(SQLITE_POW2_MEMORY_SIZE)
+  {
+    extern void sqlite3MemdebugDump(const char*);
+    sqlite3MemdebugDump(Tcl_GetString(objv[1]));
+  }
+#endif
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_memdebug_malloc_count
+**
+** Return the total number of times malloc() has been called.
+*/
+static int test_memdebug_malloc_count(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int nMalloc = -1;
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+#if defined(SQLITE_MEMDEBUG)
+  {
+    extern int sqlite3MemdebugMallocCount();
+    nMalloc = sqlite3MemdebugMallocCount();
+  }
+#endif
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(nMalloc));
+  return TCL_OK;
+}
+
+
+/*
+** Usage:    sqlite3_memdebug_fail  COUNTER  ?OPTIONS?
+**
+** where options are:
+**
+**     -repeat    <count>
+**     -benigncnt <varname>
+**
+** Arrange for a simulated malloc() failure after COUNTER successes.
+** If a repeat count is specified, the fault is repeated that many
+** times.
+**
+** Each call to this routine overrides the prior counter value.
+** This routine returns the number of simulated failures that have
+** happened since the previous call to this routine.
+**
+** To disable simulated failures, use a COUNTER of -1.
+*/
+static int test_memdebug_fail(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int ii;
+  int iFail;
+  int nRepeat = 1;
+  Tcl_Obj *pBenignCnt = 0;
+  int nBenign;
+  int nFail = 0;
+
+  if( objc<2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "COUNTER ?OPTIONS?");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[1], &iFail) ) return TCL_ERROR;
+
+  for(ii=2; ii<objc; ii+=2){
+    int nOption;
+    char *zOption = Tcl_GetStringFromObj(objv[ii], &nOption);
+    char *zErr = 0;
+
+    if( nOption>1 && strncmp(zOption, "-repeat", nOption)==0 ){
+      if( ii==(objc-1) ){
+        zErr = "option requires an argument: ";
+      }else{
+        if( Tcl_GetIntFromObj(interp, objv[ii+1], &nRepeat) ){
+          return TCL_ERROR;
+        }
+      }
+    }else if( nOption>1 && strncmp(zOption, "-benigncnt", nOption)==0 ){
+      if( ii==(objc-1) ){
+        zErr = "option requires an argument: ";
+      }else{
+        pBenignCnt = objv[ii+1];
+      }
+    }else{
+      zErr = "unknown option: ";
+    }
+
+    if( zErr ){
+      Tcl_AppendResult(interp, zErr, zOption, 0);
+      return TCL_ERROR;
+    }
+  }
+  
+  nBenign = faultsimBenignFailures();
+  nFail = faultsimFailures();
+  faultsimConfig(iFail, nRepeat);
+
+  if( pBenignCnt ){
+    Tcl_ObjSetVar2(interp, pBenignCnt, 0, Tcl_NewIntObj(nBenign), 0);
+  }
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(nFail));
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_memdebug_pending
+**
+** Return the number of malloc() calls that will succeed before a 
+** simulated failure occurs. A negative return value indicates that
+** no malloc() failure is scheduled.
+*/
+static int test_memdebug_pending(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int nPending;
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+  nPending = faultsimPending();
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(nPending));
+  return TCL_OK;
+}
+
+
+/*
+** Usage:    sqlite3_memdebug_settitle TITLE
+**
+** Set a title string stored with each allocation.  The TITLE is
+** typically the name of the test that was running when the
+** allocation occurred.  The TITLE is stored with the allocation
+** and can be used to figure out which tests are leaking memory.
+**
+** Each title overwrite the previous.
+*/
+static int test_memdebug_settitle(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "TITLE");
+    return TCL_ERROR;
+  }
+#ifdef SQLITE_MEMDEBUG
+  {
+    const char *zTitle;
+    extern int sqlite3MemdebugSettitle(const char*);
+    zTitle = Tcl_GetString(objv[1]);
+    sqlite3MemdebugSettitle(zTitle);
+  }
+#endif
+  return TCL_OK;
+}
+
+#define MALLOC_LOG_FRAMES  10 
+#define MALLOC_LOG_KEYINTS (                                              \
+    10 * ((sizeof(int)>=sizeof(void*)) ? 1 : sizeof(void*)/sizeof(int))   \
+)
+static Tcl_HashTable aMallocLog;
+static int mallocLogEnabled = 0;
+
+typedef struct MallocLog MallocLog;
+struct MallocLog {
+  int nCall;
+  int nByte;
+};
+
+#ifdef SQLITE_MEMDEBUG
+static void test_memdebug_callback(int nByte, int nFrame, void **aFrame){
+  if( mallocLogEnabled ){
+    MallocLog *pLog;
+    Tcl_HashEntry *pEntry;
+    int isNew;
+
+    int aKey[MALLOC_LOG_KEYINTS];
+    unsigned int nKey = sizeof(int)*MALLOC_LOG_KEYINTS;
+
+    memset(aKey, 0, nKey);
+    if( (sizeof(void*)*nFrame)<nKey ){
+      nKey = nFrame*sizeof(void*);
+    }
+    memcpy(aKey, aFrame, nKey);
+
+    pEntry = Tcl_CreateHashEntry(&aMallocLog, (const char *)aKey, &isNew);
+    if( isNew ){
+      pLog = (MallocLog *)Tcl_Alloc(sizeof(MallocLog));
+      memset(pLog, 0, sizeof(MallocLog));
+      Tcl_SetHashValue(pEntry, (ClientData)pLog);
+    }else{
+      pLog = (MallocLog *)Tcl_GetHashValue(pEntry);
+    }
+
+    pLog->nCall++;
+    pLog->nByte += nByte;
+  }
+}
+#endif /* SQLITE_MEMDEBUG */
+
+static void test_memdebug_log_clear(void){
+  Tcl_HashSearch search;
+  Tcl_HashEntry *pEntry;
+  for(
+    pEntry=Tcl_FirstHashEntry(&aMallocLog, &search);
+    pEntry;
+    pEntry=Tcl_NextHashEntry(&search)
+  ){
+    MallocLog *pLog = (MallocLog *)Tcl_GetHashValue(pEntry);
+    Tcl_Free((char *)pLog);
+  }
+  Tcl_DeleteHashTable(&aMallocLog);
+  Tcl_InitHashTable(&aMallocLog, MALLOC_LOG_KEYINTS);
+}
+
+static int test_memdebug_log(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  static int isInit = 0;
+  int iSub;
+
+  static const char *MB_strs[] = { "start", "stop", "dump", "clear", "sync" };
+  enum MB_enum { 
+      MB_LOG_START, MB_LOG_STOP, MB_LOG_DUMP, MB_LOG_CLEAR, MB_LOG_SYNC 
+  };
+
+  if( !isInit ){
+#ifdef SQLITE_MEMDEBUG
+    extern void sqlite3MemdebugBacktraceCallback(
+        void (*xBacktrace)(int, int, void **));
+    sqlite3MemdebugBacktraceCallback(test_memdebug_callback);
+#endif
+    Tcl_InitHashTable(&aMallocLog, MALLOC_LOG_KEYINTS);
+    isInit = 1;
+  }
+
+  if( objc<2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ...");
+  }
+  if( Tcl_GetIndexFromObj(interp, objv[1], MB_strs, "sub-command", 0, &iSub) ){
+    return TCL_ERROR;
+  }
+
+  switch( (enum MB_enum)iSub ){
+    case MB_LOG_START:
+      mallocLogEnabled = 1;
+      break;
+    case MB_LOG_STOP:
+      mallocLogEnabled = 0;
+      break;
+    case MB_LOG_DUMP: {
+      Tcl_HashSearch search;
+      Tcl_HashEntry *pEntry;
+      Tcl_Obj *pRet = Tcl_NewObj();
+
+      assert(sizeof(Tcl_WideInt)>=sizeof(void*));
+
+      for(
+        pEntry=Tcl_FirstHashEntry(&aMallocLog, &search);
+        pEntry;
+        pEntry=Tcl_NextHashEntry(&search)
+      ){
+        Tcl_Obj *apElem[MALLOC_LOG_FRAMES+2];
+        MallocLog *pLog = (MallocLog *)Tcl_GetHashValue(pEntry);
+        Tcl_WideInt *aKey = (Tcl_WideInt *)Tcl_GetHashKey(&aMallocLog, pEntry);
+        int ii;
+  
+        apElem[0] = Tcl_NewIntObj(pLog->nCall);
+        apElem[1] = Tcl_NewIntObj(pLog->nByte);
+        for(ii=0; ii<MALLOC_LOG_FRAMES; ii++){
+          apElem[ii+2] = Tcl_NewWideIntObj(aKey[ii]);
+        }
+
+        Tcl_ListObjAppendElement(interp, pRet,
+            Tcl_NewListObj(MALLOC_LOG_FRAMES+2, apElem)
+        );
+      }
+
+      Tcl_SetObjResult(interp, pRet);
+      break;
+    }
+    case MB_LOG_CLEAR: {
+      test_memdebug_log_clear();
+      break;
+    }
+
+    case MB_LOG_SYNC: {
+#ifdef SQLITE_MEMDEBUG
+      extern void sqlite3MemdebugSync();
+      test_memdebug_log_clear();
+      mallocLogEnabled = 1;
+      sqlite3MemdebugSync();
+#endif
+      break;
+    }
+  }
+
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_config_scratch SIZE N
+**
+** Set the scratch memory buffer using SQLITE_CONFIG_SCRATCH.
+** The buffer is static and is of limited size.  N might be
+** adjusted downward as needed to accomodate the requested size.
+** The revised value of N is returned.
+**
+** A negative SIZE causes the buffer pointer to be NULL.
+*/
+static int test_config_scratch(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int sz, N, rc;
+  Tcl_Obj *pResult;
+  static char *buf = 0;
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SIZE N");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[1], &sz) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &N) ) return TCL_ERROR;
+  free(buf);
+  if( sz<0 ){
+    buf = 0;
+    rc = sqlite3_config(SQLITE_CONFIG_SCRATCH, 0, 0, 0);
+  }else{
+    buf = malloc( sz*N + 1 );
+    rc = sqlite3_config(SQLITE_CONFIG_SCRATCH, buf, sz, N);
+  }
+  pResult = Tcl_NewObj();
+  Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(rc));
+  Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(N));
+  Tcl_SetObjResult(interp, pResult);
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_config_pagecache SIZE N
+**
+** Set the page-cache memory buffer using SQLITE_CONFIG_PAGECACHE.
+** The buffer is static and is of limited size.  N might be
+** adjusted downward as needed to accomodate the requested size.
+** The revised value of N is returned.
+**
+** A negative SIZE causes the buffer pointer to be NULL.
+*/
+static int test_config_pagecache(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int sz, N, rc;
+  Tcl_Obj *pResult;
+  static char *buf = 0;
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SIZE N");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[1], &sz) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &N) ) return TCL_ERROR;
+  free(buf);
+  if( sz<0 ){
+    buf = 0;
+    rc = sqlite3_config(SQLITE_CONFIG_PAGECACHE, 0, 0, 0);
+  }else{
+    buf = malloc( sz*N );
+    rc = sqlite3_config(SQLITE_CONFIG_PAGECACHE, buf, sz, N);
+  }
+  pResult = Tcl_NewObj();
+  Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(rc));
+  Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(N));
+  Tcl_SetObjResult(interp, pResult);
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_config_alt_pcache INSTALL_FLAG DISCARD_CHANCE PRNG_SEED
+**
+** Set up the alternative test page cache.  Install if INSTALL_FLAG is
+** true and uninstall (reverting to the default page cache) if INSTALL_FLAG
+** is false.  DISCARD_CHANGE is an integer between 0 and 100 inclusive
+** which determines the chance of discarding a page when unpinned.  100
+** is certainty.  0 is never.  PRNG_SEED is the pseudo-random number generator
+** seed.
+*/
+static int test_alt_pcache(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int installFlag;
+  int discardChance = 0;
+  int prngSeed = 0;
+  int highStress = 0;
+  extern void installTestPCache(int,unsigned,unsigned,unsigned);
+  if( objc<2 || objc>5 ){
+    Tcl_WrongNumArgs(interp, 1, objv, 
+        "INSTALLFLAG DISCARDCHANCE PRNGSEEED HIGHSTRESS");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[1], &installFlag) ) return TCL_ERROR;
+  if( objc>=3 && Tcl_GetIntFromObj(interp, objv[2], &discardChance) ){
+     return TCL_ERROR;
+  }
+  if( objc>=4 && Tcl_GetIntFromObj(interp, objv[3], &prngSeed) ){
+     return TCL_ERROR;
+  }
+  if( objc>=5 && Tcl_GetIntFromObj(interp, objv[4], &highStress) ){
+    return TCL_ERROR;
+  }
+  if( discardChance<0 || discardChance>100 ){
+    Tcl_AppendResult(interp, "discard-chance should be between 0 and 100",
+                     (char*)0);
+    return TCL_ERROR;
+  }
+  installTestPCache(installFlag, (unsigned)discardChance, (unsigned)prngSeed,
+                    (unsigned)highStress);
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_config_memstatus BOOLEAN
+**
+** Enable or disable memory status reporting using SQLITE_CONFIG_MEMSTATUS.
+*/
+static int test_config_memstatus(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int enable, rc;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "BOOLEAN");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetBooleanFromObj(interp, objv[1], &enable) ) return TCL_ERROR;
+  rc = sqlite3_config(SQLITE_CONFIG_MEMSTATUS, enable);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_config_lookaside  SIZE  COUNT
+**
+*/
+static int test_config_lookaside(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int sz, cnt;
+  Tcl_Obj *pRet;
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SIZE COUNT");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[1], &sz) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &cnt) ) return TCL_ERROR;
+  pRet = Tcl_NewObj();
+  Tcl_ListObjAppendElement(
+      interp, pRet, Tcl_NewIntObj(sqlite3GlobalConfig.szLookaside)
+  );
+  Tcl_ListObjAppendElement(
+      interp, pRet, Tcl_NewIntObj(sqlite3GlobalConfig.nLookaside)
+  );
+  sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, cnt);
+  Tcl_SetObjResult(interp, pRet);
+  return TCL_OK;
+}
+
+
+/*
+** Usage:    sqlite3_db_config_lookaside  CONNECTION  BUFID  SIZE  COUNT
+**
+** There are two static buffers with BUFID 1 and 2.   Each static buffer
+** is 10KB in size.  A BUFID of 0 indicates that the buffer should be NULL
+** which will cause sqlite3_db_config() to allocate space on its own.
+*/
+static int test_db_config_lookaside(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc;
+  int sz, cnt;
+  sqlite3 *db;
+  int bufid;
+  static char azBuf[2][10000];
+  extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
+  if( objc!=5 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "BUFID SIZE COUNT");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &bufid) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[3], &sz) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[4], &cnt) ) return TCL_ERROR;
+  if( bufid==0 ){
+    rc = sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE, 0, sz, cnt);
+  }else if( bufid>=1 && bufid<=2 && sz*cnt<=sizeof(azBuf[0]) ){
+    rc = sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE, azBuf[bufid], sz,cnt);
+  }else{
+    Tcl_AppendResult(interp, "illegal arguments - see documentation", (char*)0);
+    return TCL_ERROR;
+  }
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_config_heap NBYTE NMINALLOC
+*/
+static int test_config_heap(
+  void * clientData, 
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  static char *zBuf; /* Use this memory */
+  int nByte;         /* Size of buffer to pass to sqlite3_config() */
+  int nMinAlloc;     /* Size of minimum allocation */
+  int rc;            /* Return code of sqlite3_config() */
+
+  Tcl_Obj * CONST *aArg = &objv[1];
+  int nArg = objc-1;
+
+  if( nArg!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "NBYTE NMINALLOC");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, aArg[0], &nByte) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, aArg[1], &nMinAlloc) ) return TCL_ERROR;
+
+  if( nByte==0 ){
+    free( zBuf );
+    zBuf = 0;
+    rc = sqlite3_config(SQLITE_CONFIG_HEAP, (void*)0, 0, 0);
+  }else{
+    zBuf = realloc(zBuf, nByte);
+    rc = sqlite3_config(SQLITE_CONFIG_HEAP, zBuf, nByte, nMinAlloc);
+  }
+
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_config_error  [DB]
+**
+** Invoke sqlite3_config() or sqlite3_db_config() with invalid
+** opcodes and verify that they return errors.
+*/
+static int test_config_error(
+  void * clientData, 
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
+
+  if( objc!=2 && objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "[DB]");
+    return TCL_ERROR;
+  }
+  if( objc==2 ){
+    if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+    if( sqlite3_db_config(db, 99999)!=SQLITE_ERROR ){
+      Tcl_AppendResult(interp, 
+            "sqlite3_db_config(db, 99999) does not return SQLITE_ERROR",
+            (char*)0);
+      return TCL_ERROR;
+    }
+  }else{
+    if( sqlite3_config(99999)!=SQLITE_ERROR ){
+      Tcl_AppendResult(interp, 
+          "sqlite3_config(99999) does not return SQLITE_ERROR",
+          (char*)0);
+      return TCL_ERROR;
+    }
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_config_uri  BOOLEAN
+**
+** Enables or disables interpretation of URI parameters by default using
+** SQLITE_CONFIG_URI.
+*/
+static int test_config_uri(
+  void * clientData, 
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc;
+  int bOpenUri;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "BOOL");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetBooleanFromObj(interp, objv[1], &bOpenUri) ){
+    return TCL_ERROR;
+  }
+
+  rc = sqlite3_config(SQLITE_CONFIG_URI, bOpenUri);
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_config_cis  BOOLEAN
+**
+** Enables or disables the use of the covering-index scan optimization.
+** SQLITE_CONFIG_COVERING_INDEX_SCAN.
+*/
+static int test_config_cis(
+  void * clientData, 
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc;
+  int bUseCis;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "BOOL");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetBooleanFromObj(interp, objv[1], &bUseCis) ){
+    return TCL_ERROR;
+  }
+
+  rc = sqlite3_config(SQLITE_CONFIG_COVERING_INDEX_SCAN, bUseCis);
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_dump_memsys3  FILENAME
+**           sqlite3_dump_memsys5  FILENAME
+**
+** Write a summary of unfreed memsys3 allocations to FILENAME.
+*/
+static int test_dump_memsys3(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
+    return TCL_ERROR;
+  }
+
+  switch( SQLITE_PTR_TO_INT(clientData) ){
+    case 3: {
+#ifdef SQLITE_ENABLE_MEMSYS3
+      extern void sqlite3Memsys3Dump(const char*);
+      sqlite3Memsys3Dump(Tcl_GetString(objv[1]));
+      break;
+#endif
+    }
+    case 5: {
+#ifdef SQLITE_ENABLE_MEMSYS5
+      extern void sqlite3Memsys5Dump(const char*);
+      sqlite3Memsys5Dump(Tcl_GetString(objv[1]));
+      break;
+#endif
+    }
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_status  OPCODE  RESETFLAG
+**
+** Return a list of three elements which are the sqlite3_status() return
+** code, the current value, and the high-water mark value.
+*/
+static int test_status(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc, iValue, mxValue;
+  int i, op, resetFlag;
+  const char *zOpName;
+  static const struct {
+    const char *zName;
+    int op;
+  } aOp[] = {
+    { "SQLITE_STATUS_MEMORY_USED",         SQLITE_STATUS_MEMORY_USED         },
+    { "SQLITE_STATUS_MALLOC_SIZE",         SQLITE_STATUS_MALLOC_SIZE         },
+    { "SQLITE_STATUS_PAGECACHE_USED",      SQLITE_STATUS_PAGECACHE_USED      },
+    { "SQLITE_STATUS_PAGECACHE_OVERFLOW",  SQLITE_STATUS_PAGECACHE_OVERFLOW  },
+    { "SQLITE_STATUS_PAGECACHE_SIZE",      SQLITE_STATUS_PAGECACHE_SIZE      },
+    { "SQLITE_STATUS_SCRATCH_USED",        SQLITE_STATUS_SCRATCH_USED        },
+    { "SQLITE_STATUS_SCRATCH_OVERFLOW",    SQLITE_STATUS_SCRATCH_OVERFLOW    },
+    { "SQLITE_STATUS_SCRATCH_SIZE",        SQLITE_STATUS_SCRATCH_SIZE        },
+    { "SQLITE_STATUS_PARSER_STACK",        SQLITE_STATUS_PARSER_STACK        },
+    { "SQLITE_STATUS_MALLOC_COUNT",        SQLITE_STATUS_MALLOC_COUNT        },
+  };
+  Tcl_Obj *pResult;
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PARAMETER RESETFLAG");
+    return TCL_ERROR;
+  }
+  zOpName = Tcl_GetString(objv[1]);
+  for(i=0; i<ArraySize(aOp); i++){
+    if( strcmp(aOp[i].zName, zOpName)==0 ){
+      op = aOp[i].op;
+      break;
+    }
+  }
+  if( i>=ArraySize(aOp) ){
+    if( Tcl_GetIntFromObj(interp, objv[1], &op) ) return TCL_ERROR;
+  }
+  if( Tcl_GetBooleanFromObj(interp, objv[2], &resetFlag) ) return TCL_ERROR;
+  iValue = 0;
+  mxValue = 0;
+  rc = sqlite3_status(op, &iValue, &mxValue, resetFlag);
+  pResult = Tcl_NewObj();
+  Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(rc));
+  Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(iValue));
+  Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(mxValue));
+  Tcl_SetObjResult(interp, pResult);
+  return TCL_OK;
+}
+
+/*
+** Usage:    sqlite3_db_status  DATABASE  OPCODE  RESETFLAG
+**
+** Return a list of three elements which are the sqlite3_db_status() return
+** code, the current value, and the high-water mark value.
+*/
+static int test_db_status(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc, iValue, mxValue;
+  int i, op, resetFlag;
+  const char *zOpName;
+  sqlite3 *db;
+  extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
+  static const struct {
+    const char *zName;
+    int op;
+  } aOp[] = {
+    { "LOOKASIDE_USED",      SQLITE_DBSTATUS_LOOKASIDE_USED      },
+    { "CACHE_USED",          SQLITE_DBSTATUS_CACHE_USED          },
+    { "SCHEMA_USED",         SQLITE_DBSTATUS_SCHEMA_USED         },
+    { "STMT_USED",           SQLITE_DBSTATUS_STMT_USED           },
+    { "LOOKASIDE_HIT",       SQLITE_DBSTATUS_LOOKASIDE_HIT       },
+    { "LOOKASIDE_MISS_SIZE", SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE },
+    { "LOOKASIDE_MISS_FULL", SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL },
+    { "CACHE_HIT",           SQLITE_DBSTATUS_CACHE_HIT           },
+    { "CACHE_MISS",          SQLITE_DBSTATUS_CACHE_MISS          },
+    { "CACHE_WRITE",         SQLITE_DBSTATUS_CACHE_WRITE         },
+    { "DEFERRED_FKS",        SQLITE_DBSTATUS_DEFERRED_FKS        }
+  };
+  Tcl_Obj *pResult;
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB PARAMETER RESETFLAG");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zOpName = Tcl_GetString(objv[2]);
+  if( memcmp(zOpName, "SQLITE_", 7)==0 ) zOpName += 7;
+  if( memcmp(zOpName, "DBSTATUS_", 9)==0 ) zOpName += 9;
+  for(i=0; i<ArraySize(aOp); i++){
+    if( strcmp(aOp[i].zName, zOpName)==0 ){
+      op = aOp[i].op;
+      break;
+    }
+  }
+  if( i>=ArraySize(aOp) ){
+    if( Tcl_GetIntFromObj(interp, objv[2], &op) ) return TCL_ERROR;
+  }
+  if( Tcl_GetBooleanFromObj(interp, objv[3], &resetFlag) ) return TCL_ERROR;
+  iValue = 0;
+  mxValue = 0;
+  rc = sqlite3_db_status(db, op, &iValue, &mxValue, resetFlag);
+  pResult = Tcl_NewObj();
+  Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(rc));
+  Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(iValue));
+  Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(mxValue));
+  Tcl_SetObjResult(interp, pResult);
+  return TCL_OK;
+}
+
+/*
+** install_malloc_faultsim BOOLEAN
+*/
+static int test_install_malloc_faultsim(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc;
+  int isInstall;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "BOOLEAN");
+    return TCL_ERROR;
+  }
+  if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[1], &isInstall) ){
+    return TCL_ERROR;
+  }
+  rc = faultsimInstall(isInstall);
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+  return TCL_OK;
+}
+
+/*
+** sqlite3_install_memsys3
+*/
+static int test_install_memsys3(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc = SQLITE_MISUSE;
+#ifdef SQLITE_ENABLE_MEMSYS3
+  const sqlite3_mem_methods *sqlite3MemGetMemsys3(void);
+  rc = sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetMemsys3());
+#endif
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+  return TCL_OK;
+}
+
+static int test_vfs_oom_test(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  extern int sqlite3_memdebug_vfs_oom_test;
+  if( objc>2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "?INTEGER?");
+    return TCL_ERROR;
+  }else if( objc==2 ){
+    int iNew;
+    if( Tcl_GetIntFromObj(interp, objv[1], &iNew) ) return TCL_ERROR;
+    sqlite3_memdebug_vfs_oom_test = iNew;
+  }
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_memdebug_vfs_oom_test));
+  return TCL_OK;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest_malloc_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+     int clientData;
+  } aObjCmd[] = {
+     { "sqlite3_malloc",             test_malloc                   ,0 },
+     { "sqlite3_realloc",            test_realloc                  ,0 },
+     { "sqlite3_free",               test_free                     ,0 },
+     { "memset",                     test_memset                   ,0 },
+     { "memget",                     test_memget                   ,0 },
+     { "sqlite3_memory_used",        test_memory_used              ,0 },
+     { "sqlite3_memory_highwater",   test_memory_highwater         ,0 },
+     { "sqlite3_memdebug_backtrace", test_memdebug_backtrace       ,0 },
+     { "sqlite3_memdebug_dump",      test_memdebug_dump            ,0 },
+     { "sqlite3_memdebug_fail",      test_memdebug_fail            ,0 },
+     { "sqlite3_memdebug_pending",   test_memdebug_pending         ,0 },
+     { "sqlite3_memdebug_settitle",  test_memdebug_settitle        ,0 },
+     { "sqlite3_memdebug_malloc_count", test_memdebug_malloc_count ,0 },
+     { "sqlite3_memdebug_log",       test_memdebug_log             ,0 },
+     { "sqlite3_config_scratch",     test_config_scratch           ,0 },
+     { "sqlite3_config_pagecache",   test_config_pagecache         ,0 },
+     { "sqlite3_config_alt_pcache",  test_alt_pcache               ,0 },
+     { "sqlite3_status",             test_status                   ,0 },
+     { "sqlite3_db_status",          test_db_status                ,0 },
+     { "install_malloc_faultsim",    test_install_malloc_faultsim  ,0 },
+     { "sqlite3_config_heap",        test_config_heap              ,0 },
+     { "sqlite3_config_memstatus",   test_config_memstatus         ,0 },
+     { "sqlite3_config_lookaside",   test_config_lookaside         ,0 },
+     { "sqlite3_config_error",       test_config_error             ,0 },
+     { "sqlite3_config_uri",         test_config_uri               ,0 },
+     { "sqlite3_config_cis",         test_config_cis               ,0 },
+     { "sqlite3_db_config_lookaside",test_db_config_lookaside      ,0 },
+     { "sqlite3_dump_memsys3",       test_dump_memsys3             ,3 },
+     { "sqlite3_dump_memsys5",       test_dump_memsys3             ,5 },
+     { "sqlite3_install_memsys3",    test_install_memsys3          ,0 },
+     { "sqlite3_memdebug_vfs_oom_test", test_vfs_oom_test          ,0 },
+  };
+  int i;
+  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    ClientData c = (ClientData)SQLITE_INT_TO_PTR(aObjCmd[i].clientData);
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, aObjCmd[i].xProc, c, 0);
+  }
+  return TCL_OK;
+}
+#endif

+ 1385 - 0
components/external/sqlite/test/test_multiplex.c

@@ -0,0 +1,1385 @@
+/*
+** 2010 October 28
+**
+** 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 contains a VFS "shim" - a layer that sits in between the
+** pager and the real VFS - that breaks up a very large database file
+** into two or more smaller files on disk.  This is useful, for example,
+** in order to support large, multi-gigabyte databases on older filesystems
+** that limit the maximum file size to 2 GiB.
+**
+** USAGE:
+**
+** Compile this source file and link it with your application.  Then
+** at start-time, invoke the following procedure:
+**
+**   int sqlite3_multiplex_initialize(
+**      const char *zOrigVfsName,    // The underlying real VFS
+**      int makeDefault              // True to make multiplex the default VFS
+**   );
+**
+** The procedure call above will create and register a new VFS shim named
+** "multiplex".  The multiplex VFS will use the VFS named by zOrigVfsName to
+** do the actual disk I/O.  (The zOrigVfsName parameter may be NULL, in 
+** which case the default VFS at the moment sqlite3_multiplex_initialize()
+** is called will be used as the underlying real VFS.)  
+**
+** If the makeDefault parameter is TRUE then multiplex becomes the new
+** default VFS.  Otherwise, you can use the multiplex VFS by specifying
+** "multiplex" as the 4th parameter to sqlite3_open_v2() or by employing
+** URI filenames and adding "vfs=multiplex" as a parameter to the filename
+** URI.
+**
+** The multiplex VFS allows databases up to 32 GiB in size.  But it splits
+** the files up into smaller pieces, so that they will work even on 
+** filesystems that do not support large files.  The default chunk size
+** is 2147418112 bytes (which is 64KiB less than 2GiB) but this can be
+** changed at compile-time by defining the SQLITE_MULTIPLEX_CHUNK_SIZE
+** macro.  Use the "chunksize=NNNN" query parameter with a URI filename
+** in order to select an alternative chunk size for individual connections
+** at run-time.
+*/
+#include "sqlite3.h"
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "test_multiplex.h"
+
+#ifndef SQLITE_CORE
+  #define SQLITE_CORE 1  /* Disable the API redefinition in sqlite3ext.h */
+#endif
+#include "sqlite3ext.h"
+
+/* 
+** These should be defined to be the same as the values in 
+** sqliteInt.h.  They are defined separately here so that
+** the multiplex VFS shim can be built as a loadable 
+** module.
+*/
+#define UNUSED_PARAMETER(x) (void)(x)
+#define MAX_PAGE_SIZE       0x10000
+#define DEFAULT_SECTOR_SIZE 0x1000
+
+/*
+** For a build without mutexes, no-op the mutex calls.
+*/
+#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
+#define sqlite3_mutex_alloc(X)    ((sqlite3_mutex*)8)
+#define sqlite3_mutex_free(X)
+#define sqlite3_mutex_enter(X)
+#define sqlite3_mutex_try(X)      SQLITE_OK
+#define sqlite3_mutex_leave(X)
+#define sqlite3_mutex_held(X)     ((void)(X),1)
+#define sqlite3_mutex_notheld(X)  ((void)(X),1)
+#endif /* SQLITE_THREADSAFE==0 */
+
+/* Maximum chunk number */
+#define MX_CHUNK_NUMBER 299
+
+/* First chunk for rollback journal files */
+#define SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET 400
+#define SQLITE_MULTIPLEX_WAL_8_3_OFFSET 700
+
+
+/************************ Shim Definitions ******************************/
+
+#ifndef SQLITE_MULTIPLEX_VFS_NAME
+# define SQLITE_MULTIPLEX_VFS_NAME "multiplex"
+#endif
+
+/* This is the limit on the chunk size.  It may be changed by calling
+** the xFileControl() interface.  It will be rounded up to a 
+** multiple of MAX_PAGE_SIZE.  We default it here to 2GiB less 64KiB.
+*/
+#ifndef SQLITE_MULTIPLEX_CHUNK_SIZE
+# define SQLITE_MULTIPLEX_CHUNK_SIZE 2147418112
+#endif
+
+/* This used to be the default limit on number of chunks, but
+** it is no longer enforced. There is currently no limit to the
+** number of chunks.
+**
+** May be changed by calling the xFileControl() interface.
+*/
+#ifndef SQLITE_MULTIPLEX_MAX_CHUNKS
+# define SQLITE_MULTIPLEX_MAX_CHUNKS 12
+#endif
+
+/************************ Object Definitions ******************************/
+
+/* Forward declaration of all object types */
+typedef struct multiplexGroup multiplexGroup;
+typedef struct multiplexConn multiplexConn;
+
+/*
+** A "multiplex group" is a collection of files that collectively
+** makeup a single SQLite DB file.  This allows the size of the DB
+** to exceed the limits imposed by the file system.
+**
+** There is an instance of the following object for each defined multiplex
+** group.
+*/
+struct multiplexGroup {
+  struct multiplexReal {           /* For each chunk */
+    sqlite3_file *p;                  /* Handle for the chunk */
+    char *z;                          /* Name of this chunk */
+  } *aReal;                        /* list of all chunks */
+  int nReal;                       /* Number of chunks */
+  char *zName;                     /* Base filename of this group */
+  int nName;                       /* Length of base filename */
+  int flags;                       /* Flags used for original opening */
+  unsigned int szChunk;            /* Chunk size used for this group */
+  unsigned char bEnabled;          /* TRUE to use Multiplex VFS for this file */
+  unsigned char bTruncate;         /* TRUE to enable truncation of databases */
+  multiplexGroup *pNext, *pPrev;   /* Doubly linked list of all group objects */
+};
+
+/*
+** An instance of the following object represents each open connection
+** to a file that is multiplex'ed.  This object is a 
+** subclass of sqlite3_file.  The sqlite3_file object for the underlying
+** VFS is appended to this structure.
+*/
+struct multiplexConn {
+  sqlite3_file base;              /* Base class - must be first */
+  multiplexGroup *pGroup;         /* The underlying group of files */
+};
+
+/************************* Global Variables **********************************/
+/*
+** All global variables used by this file are containing within the following
+** gMultiplex structure.
+*/
+static struct {
+  /* The pOrigVfs is the real, original underlying VFS implementation.
+  ** Most operations pass-through to the real VFS.  This value is read-only
+  ** during operation.  It is only modified at start-time and thus does not
+  ** require a mutex.
+  */
+  sqlite3_vfs *pOrigVfs;
+
+  /* The sThisVfs is the VFS structure used by this shim.  It is initialized
+  ** at start-time and thus does not require a mutex
+  */
+  sqlite3_vfs sThisVfs;
+
+  /* The sIoMethods defines the methods used by sqlite3_file objects 
+  ** associated with this shim.  It is initialized at start-time and does
+  ** not require a mutex.
+  **
+  ** When the underlying VFS is called to open a file, it might return 
+  ** either a version 1 or a version 2 sqlite3_file object.  This shim
+  ** has to create a wrapper sqlite3_file of the same version.  Hence
+  ** there are two I/O method structures, one for version 1 and the other
+  ** for version 2.
+  */
+  sqlite3_io_methods sIoMethodsV1;
+  sqlite3_io_methods sIoMethodsV2;
+
+  /* True when this shim has been initialized.
+  */
+  int isInitialized;
+
+  /* For run-time access any of the other global data structures in this
+  ** shim, the following mutex must be held.
+  */
+  sqlite3_mutex *pMutex;
+
+  /* List of multiplexGroup objects.
+  */
+  multiplexGroup *pGroups;
+} gMultiplex;
+
+/************************* Utility Routines *********************************/
+/*
+** Acquire and release the mutex used to serialize access to the
+** list of multiplexGroups.
+*/
+static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); }
+static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); }
+
+/*
+** Compute a string length that is limited to what can be stored in
+** lower 30 bits of a 32-bit signed integer.
+**
+** The value returned will never be negative.  Nor will it ever be greater
+** than the actual length of the string.  For very long strings (greater
+** than 1GiB) the value returned might be less than the true string length.
+*/
+static int multiplexStrlen30(const char *z){
+  const char *z2 = z;
+  if( z==0 ) return 0;
+  while( *z2 ){ z2++; }
+  return 0x3fffffff & (int)(z2 - z);
+}
+
+/*
+** Generate the file-name for chunk iChunk of the group with base name
+** zBase. The file-name is written to buffer zOut before returning. Buffer
+** zOut must be allocated by the caller so that it is at least (nBase+5)
+** bytes in size, where nBase is the length of zBase, not including the
+** nul-terminator.
+**
+** If iChunk is 0 (or 400 - the number for the first journal file chunk),
+** the output is a copy of the input string. Otherwise, if 
+** SQLITE_ENABLE_8_3_NAMES is not defined or the input buffer does not contain
+** a "." character, then the output is a copy of the input string with the 
+** three-digit zero-padded decimal representation if iChunk appended to it. 
+** For example:
+**
+**   zBase="test.db", iChunk=4  ->  zOut="test.db004"
+**
+** Or, if SQLITE_ENABLE_8_3_NAMES is defined and the input buffer contains
+** a "." character, then everything after the "." is replaced by the 
+** three-digit representation of iChunk.
+**
+**   zBase="test.db", iChunk=4  ->  zOut="test.004"
+**
+** The output buffer string is terminated by 2 0x00 bytes. This makes it safe
+** to pass to sqlite3_uri_parameter() and similar.
+*/
+static void multiplexFilename(
+  const char *zBase,              /* Filename for chunk 0 */
+  int nBase,                      /* Size of zBase in bytes (without \0) */
+  int flags,                      /* Flags used to open file */
+  int iChunk,                     /* Chunk to generate filename for */
+  char *zOut                      /* Buffer to write generated name to */
+){
+  int n = nBase;
+  memcpy(zOut, zBase, n+1);
+  if( iChunk!=0 && iChunk<=MX_CHUNK_NUMBER ){
+#ifdef SQLITE_ENABLE_8_3_NAMES
+    int i;
+    for(i=n-1; i>0 && i>=n-4 && zOut[i]!='.'; i--){}
+    if( i>=n-4 ) n = i+1;
+    if( flags & SQLITE_OPEN_MAIN_JOURNAL ){
+      /* The extensions on overflow files for main databases are 001, 002,
+      ** 003 and so forth.  To avoid name collisions, add 400 to the 
+      ** extensions of journal files so that they are 401, 402, 403, ....
+      */
+      iChunk += SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET;
+    }else if( flags & SQLITE_OPEN_WAL ){
+      /* To avoid name collisions, add 700 to the 
+      ** extensions of WAL files so that they are 701, 702, 703, ....
+      */
+      iChunk += SQLITE_MULTIPLEX_WAL_8_3_OFFSET;
+    }
+#endif
+    sqlite3_snprintf(4,&zOut[n],"%03d",iChunk);
+    n += 3;
+  }
+
+  assert( zOut[n]=='\0' );
+  zOut[n+1] = '\0';
+}
+
+/* Compute the filename for the iChunk-th chunk
+*/
+static int multiplexSubFilename(multiplexGroup *pGroup, int iChunk){
+  if( iChunk>=pGroup->nReal ){
+    struct multiplexReal *p;
+    p = sqlite3_realloc(pGroup->aReal, (iChunk+1)*sizeof(*p));
+    if( p==0 ){
+      return SQLITE_NOMEM;
+    }
+    memset(&p[pGroup->nReal], 0, sizeof(p[0])*(iChunk+1-pGroup->nReal));
+    pGroup->aReal = p;
+    pGroup->nReal = iChunk+1;
+  }
+  if( pGroup->zName && pGroup->aReal[iChunk].z==0 ){
+    char *z;
+    int n = pGroup->nName;
+    pGroup->aReal[iChunk].z = z = sqlite3_malloc( n+5 );
+    if( z==0 ){
+      return SQLITE_NOMEM;
+    }
+    multiplexFilename(pGroup->zName, pGroup->nName, pGroup->flags, iChunk, z);
+  }
+  return SQLITE_OK;
+}
+
+/* Translate an sqlite3_file* that is really a multiplexGroup* into
+** the sqlite3_file* for the underlying original VFS.
+**
+** For chunk 0, the pGroup->flags determines whether or not a new file
+** is created if it does not already exist.  For chunks 1 and higher, the
+** file is created only if createFlag is 1.
+*/
+static sqlite3_file *multiplexSubOpen(
+  multiplexGroup *pGroup,    /* The multiplexor group */
+  int iChunk,                /* Which chunk to open.  0==original file */
+  int *rc,                   /* Result code in and out */
+  int *pOutFlags,            /* Output flags */
+  int createFlag             /* True to create if iChunk>0 */
+){
+  sqlite3_file *pSubOpen = 0;
+  sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;        /* Real VFS */
+
+#ifdef SQLITE_ENABLE_8_3_NAMES
+  /* If JOURNAL_8_3_OFFSET is set to (say) 400, then any overflow files are 
+  ** part of a database journal are named db.401, db.402, and so on. A 
+  ** database may therefore not grow to larger than 400 chunks. Attempting
+  ** to open chunk 401 indicates the database is full. */
+  if( iChunk>=SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET ){
+    sqlite3_log(SQLITE_FULL, "multiplexed chunk overflow: %s", pGroup->zName);
+    *rc = SQLITE_FULL;
+    return 0;
+  }
+#endif
+
+  *rc = multiplexSubFilename(pGroup, iChunk);
+  if( (*rc)==SQLITE_OK && (pSubOpen = pGroup->aReal[iChunk].p)==0 ){
+    int flags, bExists;
+    flags = pGroup->flags;
+    if( createFlag ){
+      flags |= SQLITE_OPEN_CREATE;
+    }else if( iChunk==0 ){
+      /* Fall through */
+    }else if( pGroup->aReal[iChunk].z==0 ){
+      return 0;
+    }else{
+      *rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[iChunk].z,
+                              SQLITE_ACCESS_EXISTS, &bExists);
+     if( *rc || !bExists ){
+        if( *rc ){
+          sqlite3_log(*rc, "multiplexor.xAccess failure on %s",
+                      pGroup->aReal[iChunk].z);
+        }
+        return 0;
+      }
+      flags &= ~SQLITE_OPEN_CREATE;
+    }
+    pSubOpen = sqlite3_malloc( pOrigVfs->szOsFile );
+    if( pSubOpen==0 ){
+      *rc = SQLITE_IOERR_NOMEM;
+      return 0;
+    }
+    pGroup->aReal[iChunk].p = pSubOpen;
+    *rc = pOrigVfs->xOpen(pOrigVfs, pGroup->aReal[iChunk].z, pSubOpen,
+                          flags, pOutFlags);
+    if( (*rc)!=SQLITE_OK ){
+      sqlite3_log(*rc, "multiplexor.xOpen failure on %s",
+                  pGroup->aReal[iChunk].z);
+      sqlite3_free(pSubOpen);
+      pGroup->aReal[iChunk].p = 0;
+      return 0;
+    }
+  }
+  return pSubOpen;
+}
+
+/*
+** Return the size, in bytes, of chunk number iChunk.  If that chunk
+** does not exist, then return 0.  This function does not distingish between
+** non-existant files and zero-length files.
+*/
+static sqlite3_int64 multiplexSubSize(
+  multiplexGroup *pGroup,    /* The multiplexor group */
+  int iChunk,                /* Which chunk to open.  0==original file */
+  int *rc                    /* Result code in and out */
+){
+  sqlite3_file *pSub;
+  sqlite3_int64 sz = 0;
+
+  if( *rc ) return 0;
+  pSub = multiplexSubOpen(pGroup, iChunk, rc, NULL, 0);
+  if( pSub==0 ) return 0;
+  *rc = pSub->pMethods->xFileSize(pSub, &sz);
+  return sz;
+}    
+
+/*
+** This is the implementation of the multiplex_control() SQL function.
+*/
+static void multiplexControlFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  int rc = SQLITE_OK;
+  sqlite3 *db = sqlite3_context_db_handle(context);
+  int op;
+  int iVal;
+
+  if( !db || argc!=2 ){ 
+    rc = SQLITE_ERROR; 
+  }else{
+    /* extract params */
+    op = sqlite3_value_int(argv[0]);
+    iVal = sqlite3_value_int(argv[1]);
+    /* map function op to file_control op */
+    switch( op ){
+      case 1: 
+        op = MULTIPLEX_CTRL_ENABLE; 
+        break;
+      case 2: 
+        op = MULTIPLEX_CTRL_SET_CHUNK_SIZE; 
+        break;
+      case 3: 
+        op = MULTIPLEX_CTRL_SET_MAX_CHUNKS; 
+        break;
+      default:
+        rc = SQLITE_NOTFOUND;
+        break;
+    }
+  }
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_file_control(db, 0, op, &iVal);
+  }
+  sqlite3_result_error_code(context, rc);
+}
+
+/*
+** This is the entry point to register the auto-extension for the 
+** multiplex_control() function.
+*/
+static int multiplexFuncInit(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  int rc;
+  rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY, 
+      0, multiplexControlFunc, 0, 0);
+  return rc;
+}
+
+/*
+** Close a single sub-file in the connection group.
+*/
+static void multiplexSubClose(
+  multiplexGroup *pGroup,
+  int iChunk,
+  sqlite3_vfs *pOrigVfs
+){
+  sqlite3_file *pSubOpen = pGroup->aReal[iChunk].p;
+  if( pSubOpen ){
+    pSubOpen->pMethods->xClose(pSubOpen);
+    if( pOrigVfs && pGroup->aReal[iChunk].z ){
+      pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0);
+    }
+    sqlite3_free(pGroup->aReal[iChunk].p);
+  }
+  sqlite3_free(pGroup->aReal[iChunk].z);
+  memset(&pGroup->aReal[iChunk], 0, sizeof(pGroup->aReal[iChunk]));
+}
+
+/*
+** Deallocate memory held by a multiplexGroup
+*/
+static void multiplexFreeComponents(multiplexGroup *pGroup){
+  int i;
+  for(i=0; i<pGroup->nReal; i++){ multiplexSubClose(pGroup, i, 0); }
+  sqlite3_free(pGroup->aReal);
+  pGroup->aReal = 0;
+  pGroup->nReal = 0;
+}
+
+
+/************************* VFS Method Wrappers *****************************/
+
+/*
+** This is the xOpen method used for the "multiplex" VFS.
+**
+** Most of the work is done by the underlying original VFS.  This method
+** simply links the new file into the appropriate multiplex group if it is a
+** file that needs to be tracked.
+*/
+static int multiplexOpen(
+  sqlite3_vfs *pVfs,         /* The multiplex VFS */
+  const char *zName,         /* Name of file to be opened */
+  sqlite3_file *pConn,       /* Fill in this file descriptor */
+  int flags,                 /* Flags to control the opening */
+  int *pOutFlags             /* Flags showing results of opening */
+){
+  int rc = SQLITE_OK;                  /* Result code */
+  multiplexConn *pMultiplexOpen;       /* The new multiplex file descriptor */
+  multiplexGroup *pGroup = 0;          /* Corresponding multiplexGroup object */
+  sqlite3_file *pSubOpen = 0;                    /* Real file descriptor */
+  sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
+  int nName = 0;
+  int sz = 0;
+  char *zToFree = 0;
+
+  UNUSED_PARAMETER(pVfs);
+  memset(pConn, 0, pVfs->szOsFile);
+  assert( zName || (flags & SQLITE_OPEN_DELETEONCLOSE) );
+
+  /* We need to create a group structure and manage
+  ** access to this group of files.
+  */
+  multiplexEnter();
+  pMultiplexOpen = (multiplexConn*)pConn;
+
+  if( rc==SQLITE_OK ){
+    /* allocate space for group */
+    nName = zName ? multiplexStrlen30(zName) : 0;
+    sz = sizeof(multiplexGroup)                             /* multiplexGroup */
+       + nName + 1;                                         /* zName */
+    pGroup = sqlite3_malloc( sz );
+    if( pGroup==0 ){
+      rc = SQLITE_NOMEM;
+    }
+  }
+
+  if( rc==SQLITE_OK ){
+    const char *zUri = (flags & SQLITE_OPEN_URI) ? zName : 0;
+    /* assign pointers to extra space allocated */
+    memset(pGroup, 0, sz);
+    pMultiplexOpen->pGroup = pGroup;
+    pGroup->bEnabled = -1;
+    pGroup->bTruncate = sqlite3_uri_boolean(zUri, "truncate", 
+                                   (flags & SQLITE_OPEN_MAIN_DB)==0);
+    pGroup->szChunk = (int)sqlite3_uri_int64(zUri, "chunksize",
+                                        SQLITE_MULTIPLEX_CHUNK_SIZE);
+    pGroup->szChunk = (pGroup->szChunk+0xffff)&~0xffff;
+    if( zName ){
+      char *p = (char *)&pGroup[1];
+      pGroup->zName = p;
+      memcpy(pGroup->zName, zName, nName+1);
+      pGroup->nName = nName;
+    }
+    if( pGroup->bEnabled ){
+      /* Make sure that the chunksize is such that the pending byte does not
+      ** falls at the end of a chunk.  A region of up to 64K following
+      ** the pending byte is never written, so if the pending byte occurs
+      ** near the end of a chunk, that chunk will be too small. */
+#ifndef SQLITE_OMIT_WSD
+      extern int sqlite3PendingByte;
+#else
+      int sqlite3PendingByte = 0x40000000;
+#endif
+      while( (sqlite3PendingByte % pGroup->szChunk)>=(pGroup->szChunk-65536) ){
+        pGroup->szChunk += 65536;
+      }
+    }
+    pGroup->flags = flags;
+    rc = multiplexSubFilename(pGroup, 1);
+    if( rc==SQLITE_OK ){
+      pSubOpen = multiplexSubOpen(pGroup, 0, &rc, pOutFlags, 0);
+      if( pSubOpen==0 && rc==SQLITE_OK ) rc = SQLITE_CANTOPEN;
+    }
+    if( rc==SQLITE_OK ){
+      sqlite3_int64 sz;
+
+      rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
+      if( rc==SQLITE_OK && zName ){
+        int bExists;
+        if( sz==0 ){
+          if( flags & SQLITE_OPEN_MAIN_JOURNAL ){
+            /* If opening a main journal file and the first chunk is zero
+            ** bytes in size, delete any subsequent chunks from the 
+            ** file-system. */
+            int iChunk = 1;
+            do {
+              rc = pOrigVfs->xAccess(pOrigVfs, 
+                  pGroup->aReal[iChunk].z, SQLITE_ACCESS_EXISTS, &bExists
+              );
+              if( rc==SQLITE_OK && bExists ){
+                rc = pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0);
+                if( rc==SQLITE_OK ){
+                  rc = multiplexSubFilename(pGroup, ++iChunk);
+                }
+              }
+            }while( rc==SQLITE_OK && bExists );
+          }
+        }else{
+          /* If the first overflow file exists and if the size of the main file
+          ** is different from the chunk size, that means the chunk size is set
+          ** set incorrectly.  So fix it.
+          **
+          ** Or, if the first overflow file does not exist and the main file is
+          ** larger than the chunk size, that means the chunk size is too small.
+          ** But we have no way of determining the intended chunk size, so 
+          ** just disable the multiplexor all togethre.
+          */
+          rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[1].z,
+              SQLITE_ACCESS_EXISTS, &bExists);
+          bExists = multiplexSubSize(pGroup, 1, &rc)>0;
+          if( rc==SQLITE_OK && bExists  && sz==(sz&0xffff0000) && sz>0
+              && sz!=pGroup->szChunk ){
+            pGroup->szChunk = (int)sz;
+          }else if( rc==SQLITE_OK && !bExists && sz>pGroup->szChunk ){
+            pGroup->bEnabled = 0;
+          }
+        }
+      }
+    }
+
+    if( rc==SQLITE_OK ){
+      if( pSubOpen->pMethods->iVersion==1 ){
+        pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1;
+      }else{
+        pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV2;
+      }
+      /* place this group at the head of our list */
+      pGroup->pNext = gMultiplex.pGroups;
+      if( gMultiplex.pGroups ) gMultiplex.pGroups->pPrev = pGroup;
+      gMultiplex.pGroups = pGroup;
+    }else{
+      multiplexFreeComponents(pGroup);
+      sqlite3_free(pGroup);
+    }
+  }
+  multiplexLeave();
+  sqlite3_free(zToFree);
+  return rc;
+}
+
+/*
+** This is the xDelete method used for the "multiplex" VFS.
+** It attempts to delete the filename specified.
+*/
+static int multiplexDelete(
+  sqlite3_vfs *pVfs,         /* The multiplex VFS */
+  const char *zName,         /* Name of file to delete */
+  int syncDir
+){
+  int rc;
+  sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
+  rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir);
+  if( rc==SQLITE_OK ){
+    /* If the main chunk was deleted successfully, also delete any subsequent
+    ** chunks - starting with the last (highest numbered). 
+    */
+    int nName = (int)strlen(zName);
+    char *z;
+    z = sqlite3_malloc(nName + 5);
+    if( z==0 ){
+      rc = SQLITE_IOERR_NOMEM;
+    }else{
+      int iChunk = 0;
+      int bExists;
+      do{
+        multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, ++iChunk, z);
+        rc = pOrigVfs->xAccess(pOrigVfs, z, SQLITE_ACCESS_EXISTS, &bExists);
+      }while( rc==SQLITE_OK && bExists );
+      while( rc==SQLITE_OK && iChunk>1 ){
+        multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, --iChunk, z);
+        rc = pOrigVfs->xDelete(pOrigVfs, z, syncDir);
+      }
+      if( rc==SQLITE_OK ){
+        iChunk = 0;
+        do{
+          multiplexFilename(zName, nName, SQLITE_OPEN_WAL, ++iChunk, z);
+          rc = pOrigVfs->xAccess(pOrigVfs, z, SQLITE_ACCESS_EXISTS, &bExists);
+        }while( rc==SQLITE_OK && bExists );
+        while( rc==SQLITE_OK && iChunk>1 ){
+          multiplexFilename(zName, nName, SQLITE_OPEN_WAL, --iChunk, z);
+          rc = pOrigVfs->xDelete(pOrigVfs, z, syncDir);
+        }
+      }
+    }
+    sqlite3_free(z);
+  }
+  return rc;
+}
+
+static int multiplexAccess(sqlite3_vfs *a, const char *b, int c, int *d){
+  return gMultiplex.pOrigVfs->xAccess(gMultiplex.pOrigVfs, b, c, d);
+}
+static int multiplexFullPathname(sqlite3_vfs *a, const char *b, int c, char *d){
+  return gMultiplex.pOrigVfs->xFullPathname(gMultiplex.pOrigVfs, b, c, d);
+}
+static void *multiplexDlOpen(sqlite3_vfs *a, const char *b){
+  return gMultiplex.pOrigVfs->xDlOpen(gMultiplex.pOrigVfs, b);
+}
+static void multiplexDlError(sqlite3_vfs *a, int b, char *c){
+  gMultiplex.pOrigVfs->xDlError(gMultiplex.pOrigVfs, b, c);
+}
+static void (*multiplexDlSym(sqlite3_vfs *a, void *b, const char *c))(void){
+  return gMultiplex.pOrigVfs->xDlSym(gMultiplex.pOrigVfs, b, c);
+}
+static void multiplexDlClose(sqlite3_vfs *a, void *b){
+  gMultiplex.pOrigVfs->xDlClose(gMultiplex.pOrigVfs, b);
+}
+static int multiplexRandomness(sqlite3_vfs *a, int b, char *c){
+  return gMultiplex.pOrigVfs->xRandomness(gMultiplex.pOrigVfs, b, c);
+}
+static int multiplexSleep(sqlite3_vfs *a, int b){
+  return gMultiplex.pOrigVfs->xSleep(gMultiplex.pOrigVfs, b);
+}
+static int multiplexCurrentTime(sqlite3_vfs *a, double *b){
+  return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b);
+}
+static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){
+  return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);
+}
+static int multiplexCurrentTimeInt64(sqlite3_vfs *a, sqlite3_int64 *b){
+  return gMultiplex.pOrigVfs->xCurrentTimeInt64(gMultiplex.pOrigVfs, b);
+}
+
+/************************ I/O Method Wrappers *******************************/
+
+/* xClose requests get passed through to the original VFS.
+** We loop over all open chunk handles and close them.
+** The group structure for this file is unlinked from 
+** our list of groups and freed.
+*/
+static int multiplexClose(sqlite3_file *pConn){
+  multiplexConn *p = (multiplexConn*)pConn;
+  multiplexGroup *pGroup = p->pGroup;
+  int rc = SQLITE_OK;
+  multiplexEnter();
+  multiplexFreeComponents(pGroup);
+  /* remove from linked list */
+  if( pGroup->pNext ) pGroup->pNext->pPrev = pGroup->pPrev;
+  if( pGroup->pPrev ){
+    pGroup->pPrev->pNext = pGroup->pNext;
+  }else{
+    gMultiplex.pGroups = pGroup->pNext;
+  }
+  sqlite3_free(pGroup);
+  multiplexLeave();
+  return rc;
+}
+
+/* Pass xRead requests thru to the original VFS after
+** determining the correct chunk to operate on.
+** Break up reads across chunk boundaries.
+*/
+static int multiplexRead(
+  sqlite3_file *pConn,
+  void *pBuf,
+  int iAmt,
+  sqlite3_int64 iOfst
+){
+  multiplexConn *p = (multiplexConn*)pConn;
+  multiplexGroup *pGroup = p->pGroup;
+  int rc = SQLITE_OK;
+  multiplexEnter();
+  if( !pGroup->bEnabled ){
+    sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
+    if( pSubOpen==0 ){
+      rc = SQLITE_IOERR_READ;
+    }else{
+      rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);
+    }
+  }else{
+    while( iAmt > 0 ){
+      int i = (int)(iOfst / pGroup->szChunk);
+      sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1);
+      if( pSubOpen ){
+        int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) - pGroup->szChunk;
+        if( extra<0 ) extra = 0;
+        iAmt -= extra;
+        rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt,
+                                       iOfst % pGroup->szChunk);
+        if( rc!=SQLITE_OK ) break;
+        pBuf = (char *)pBuf + iAmt;
+        iOfst += iAmt;
+        iAmt = extra;
+      }else{
+        rc = SQLITE_IOERR_READ;
+        break;
+      }
+    }
+  }
+  multiplexLeave();
+  return rc;
+}
+
+/* Pass xWrite requests thru to the original VFS after
+** determining the correct chunk to operate on.
+** Break up writes across chunk boundaries.
+*/
+static int multiplexWrite(
+  sqlite3_file *pConn,
+  const void *pBuf,
+  int iAmt,
+  sqlite3_int64 iOfst
+){
+  multiplexConn *p = (multiplexConn*)pConn;
+  multiplexGroup *pGroup = p->pGroup;
+  int rc = SQLITE_OK;
+  multiplexEnter();
+  if( !pGroup->bEnabled ){
+    sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
+    if( pSubOpen==0 ){
+      rc = SQLITE_IOERR_WRITE;
+    }else{
+      rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);
+    }
+  }else{
+    while( rc==SQLITE_OK && iAmt>0 ){
+      int i = (int)(iOfst / pGroup->szChunk);
+      sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1);
+      if( pSubOpen ){
+        int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) -
+                    pGroup->szChunk;
+        if( extra<0 ) extra = 0;
+        iAmt -= extra;
+        rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt,
+                                        iOfst % pGroup->szChunk);
+        pBuf = (char *)pBuf + iAmt;
+        iOfst += iAmt;
+        iAmt = extra;
+      }
+    }
+  }
+  multiplexLeave();
+  return rc;
+}
+
+/* Pass xTruncate requests thru to the original VFS after
+** determining the correct chunk to operate on.  Delete any
+** chunks above the truncate mark.
+*/
+static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){
+  multiplexConn *p = (multiplexConn*)pConn;
+  multiplexGroup *pGroup = p->pGroup;
+  int rc = SQLITE_OK;
+  multiplexEnter();
+  if( !pGroup->bEnabled ){
+    sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
+    if( pSubOpen==0 ){
+      rc = SQLITE_IOERR_TRUNCATE;
+    }else{
+      rc = pSubOpen->pMethods->xTruncate(pSubOpen, size);
+    }
+  }else{
+    int i;
+    int iBaseGroup = (int)(size / pGroup->szChunk);
+    sqlite3_file *pSubOpen;
+    sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
+    /* delete the chunks above the truncate limit */
+    for(i = pGroup->nReal-1; i>iBaseGroup && rc==SQLITE_OK; i--){
+      if( pGroup->bTruncate ){
+        multiplexSubClose(pGroup, i, pOrigVfs);
+      }else{
+        pSubOpen = multiplexSubOpen(pGroup, i, &rc, 0, 0);
+        if( pSubOpen ){
+          rc = pSubOpen->pMethods->xTruncate(pSubOpen, 0);
+        }
+      }
+    }
+    if( rc==SQLITE_OK ){
+      pSubOpen = multiplexSubOpen(pGroup, iBaseGroup, &rc, 0, 0);
+      if( pSubOpen ){
+        rc = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->szChunk);
+      }
+    }
+    if( rc ) rc = SQLITE_IOERR_TRUNCATE;
+  }
+  multiplexLeave();
+  return rc;
+}
+
+/* Pass xSync requests through to the original VFS without change
+*/
+static int multiplexSync(sqlite3_file *pConn, int flags){
+  multiplexConn *p = (multiplexConn*)pConn;
+  multiplexGroup *pGroup = p->pGroup;
+  int rc = SQLITE_OK;
+  int i;
+  multiplexEnter();
+  for(i=0; i<pGroup->nReal; i++){
+    sqlite3_file *pSubOpen = pGroup->aReal[i].p;
+    if( pSubOpen ){
+      int rc2 = pSubOpen->pMethods->xSync(pSubOpen, flags);
+      if( rc2!=SQLITE_OK ) rc = rc2;
+    }
+  }
+  multiplexLeave();
+  return rc;
+}
+
+/* Pass xFileSize requests through to the original VFS.
+** Aggregate the size of all the chunks before returning.
+*/
+static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
+  multiplexConn *p = (multiplexConn*)pConn;
+  multiplexGroup *pGroup = p->pGroup;
+  int rc = SQLITE_OK;
+  int i;
+  multiplexEnter();
+  if( !pGroup->bEnabled ){
+    sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
+    if( pSubOpen==0 ){
+      rc = SQLITE_IOERR_FSTAT;
+    }else{
+      rc = pSubOpen->pMethods->xFileSize(pSubOpen, pSize);
+    }
+  }else{
+    *pSize = 0;
+    for(i=0; rc==SQLITE_OK; i++){
+      sqlite3_int64 sz = multiplexSubSize(pGroup, i, &rc);
+      if( sz==0 ) break;
+      *pSize = i*(sqlite3_int64)pGroup->szChunk + sz;
+    }
+  }
+  multiplexLeave();
+  return rc;
+}
+
+/* Pass xLock requests through to the original VFS unchanged.
+*/
+static int multiplexLock(sqlite3_file *pConn, int lock){
+  multiplexConn *p = (multiplexConn*)pConn;
+  int rc;
+  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
+  if( pSubOpen ){
+    return pSubOpen->pMethods->xLock(pSubOpen, lock);
+  }
+  return SQLITE_BUSY;
+}
+
+/* Pass xUnlock requests through to the original VFS unchanged.
+*/
+static int multiplexUnlock(sqlite3_file *pConn, int lock){
+  multiplexConn *p = (multiplexConn*)pConn;
+  int rc;
+  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
+  if( pSubOpen ){
+    return pSubOpen->pMethods->xUnlock(pSubOpen, lock);
+  }
+  return SQLITE_IOERR_UNLOCK;
+}
+
+/* Pass xCheckReservedLock requests through to the original VFS unchanged.
+*/
+static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){
+  multiplexConn *p = (multiplexConn*)pConn;
+  int rc;
+  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
+  if( pSubOpen ){
+    return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
+  }
+  return SQLITE_IOERR_CHECKRESERVEDLOCK;
+}
+
+/* Pass xFileControl requests through to the original VFS unchanged,
+** except for any MULTIPLEX_CTRL_* requests here.
+*/
+static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){
+  multiplexConn *p = (multiplexConn*)pConn;
+  multiplexGroup *pGroup = p->pGroup;
+  int rc = SQLITE_ERROR;
+  sqlite3_file *pSubOpen;
+
+  if( !gMultiplex.isInitialized ) return SQLITE_MISUSE;
+  switch( op ){
+    case MULTIPLEX_CTRL_ENABLE:
+      if( pArg ) {
+        int bEnabled = *(int *)pArg;
+        pGroup->bEnabled = bEnabled;
+        rc = SQLITE_OK;
+      }
+      break;
+    case MULTIPLEX_CTRL_SET_CHUNK_SIZE:
+      if( pArg ) {
+        unsigned int szChunk = *(unsigned*)pArg;
+        if( szChunk<1 ){
+          rc = SQLITE_MISUSE;
+        }else{
+          /* Round up to nearest multiple of MAX_PAGE_SIZE. */
+          szChunk = (szChunk + (MAX_PAGE_SIZE-1));
+          szChunk &= ~(MAX_PAGE_SIZE-1);
+          pGroup->szChunk = szChunk;
+          rc = SQLITE_OK;
+        }
+      }
+      break;
+    case MULTIPLEX_CTRL_SET_MAX_CHUNKS:
+      rc = SQLITE_OK;
+      break;
+    case SQLITE_FCNTL_SIZE_HINT:
+    case SQLITE_FCNTL_CHUNK_SIZE:
+      /* no-op these */
+      rc = SQLITE_OK;
+      break;
+    default:
+      pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
+      if( pSubOpen ){
+        rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
+        if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
+         *(char**)pArg = sqlite3_mprintf("multiplex/%z", *(char**)pArg);
+        }
+      }
+      break;
+  }
+  return rc;
+}
+
+/* Pass xSectorSize requests through to the original VFS unchanged.
+*/
+static int multiplexSectorSize(sqlite3_file *pConn){
+  multiplexConn *p = (multiplexConn*)pConn;
+  int rc;
+  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
+  if( pSubOpen && pSubOpen->pMethods->xSectorSize ){
+    return pSubOpen->pMethods->xSectorSize(pSubOpen);
+  }
+  return DEFAULT_SECTOR_SIZE;
+}
+
+/* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
+*/
+static int multiplexDeviceCharacteristics(sqlite3_file *pConn){
+  multiplexConn *p = (multiplexConn*)pConn;
+  int rc;
+  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
+  if( pSubOpen ){
+    return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);
+  }
+  return 0;
+}
+
+/* Pass xShmMap requests through to the original VFS unchanged.
+*/
+static int multiplexShmMap(
+  sqlite3_file *pConn,            /* Handle open on database file */
+  int iRegion,                    /* Region to retrieve */
+  int szRegion,                   /* Size of regions */
+  int bExtend,                    /* True to extend file if necessary */
+  void volatile **pp              /* OUT: Mapped memory */
+){
+  multiplexConn *p = (multiplexConn*)pConn;
+  int rc;
+  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
+  if( pSubOpen ){
+    return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend,pp);
+  }
+  return SQLITE_IOERR;
+}
+
+/* Pass xShmLock requests through to the original VFS unchanged.
+*/
+static int multiplexShmLock(
+  sqlite3_file *pConn,       /* Database file holding the shared memory */
+  int ofst,                  /* First lock to acquire or release */
+  int n,                     /* Number of locks to acquire or release */
+  int flags                  /* What to do with the lock */
+){
+  multiplexConn *p = (multiplexConn*)pConn;
+  int rc;
+  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
+  if( pSubOpen ){
+    return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);
+  }
+  return SQLITE_BUSY;
+}
+
+/* Pass xShmBarrier requests through to the original VFS unchanged.
+*/
+static void multiplexShmBarrier(sqlite3_file *pConn){
+  multiplexConn *p = (multiplexConn*)pConn;
+  int rc;
+  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
+  if( pSubOpen ){
+    pSubOpen->pMethods->xShmBarrier(pSubOpen);
+  }
+}
+
+/* Pass xShmUnmap requests through to the original VFS unchanged.
+*/
+static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){
+  multiplexConn *p = (multiplexConn*)pConn;
+  int rc;
+  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
+  if( pSubOpen ){
+    return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);
+  }
+  return SQLITE_OK;
+}
+
+/************************** Public Interfaces *****************************/
+/*
+** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize()
+**
+** Use the VFS named zOrigVfsName as the VFS that does the actual work.  
+** Use the default if zOrigVfsName==NULL.  
+**
+** The multiplex VFS shim is named "multiplex".  It will become the default
+** VFS if makeDefault is non-zero.
+**
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once
+** during start-up.
+*/
+int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){
+  sqlite3_vfs *pOrigVfs;
+  if( gMultiplex.isInitialized ) return SQLITE_MISUSE;
+  pOrigVfs = sqlite3_vfs_find(zOrigVfsName);
+  if( pOrigVfs==0 ) return SQLITE_ERROR;
+  assert( pOrigVfs!=&gMultiplex.sThisVfs );
+  gMultiplex.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+  if( !gMultiplex.pMutex ){
+    return SQLITE_NOMEM;
+  }
+  gMultiplex.pGroups = NULL;
+  gMultiplex.isInitialized = 1;
+  gMultiplex.pOrigVfs = pOrigVfs;
+  gMultiplex.sThisVfs = *pOrigVfs;
+  gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn);
+  gMultiplex.sThisVfs.zName = SQLITE_MULTIPLEX_VFS_NAME;
+  gMultiplex.sThisVfs.xOpen = multiplexOpen;
+  gMultiplex.sThisVfs.xDelete = multiplexDelete;
+  gMultiplex.sThisVfs.xAccess = multiplexAccess;
+  gMultiplex.sThisVfs.xFullPathname = multiplexFullPathname;
+  gMultiplex.sThisVfs.xDlOpen = multiplexDlOpen;
+  gMultiplex.sThisVfs.xDlError = multiplexDlError;
+  gMultiplex.sThisVfs.xDlSym = multiplexDlSym;
+  gMultiplex.sThisVfs.xDlClose = multiplexDlClose;
+  gMultiplex.sThisVfs.xRandomness = multiplexRandomness;
+  gMultiplex.sThisVfs.xSleep = multiplexSleep;
+  gMultiplex.sThisVfs.xCurrentTime = multiplexCurrentTime;
+  gMultiplex.sThisVfs.xGetLastError = multiplexGetLastError;
+  gMultiplex.sThisVfs.xCurrentTimeInt64 = multiplexCurrentTimeInt64;
+
+  gMultiplex.sIoMethodsV1.iVersion = 1;
+  gMultiplex.sIoMethodsV1.xClose = multiplexClose;
+  gMultiplex.sIoMethodsV1.xRead = multiplexRead;
+  gMultiplex.sIoMethodsV1.xWrite = multiplexWrite;
+  gMultiplex.sIoMethodsV1.xTruncate = multiplexTruncate;
+  gMultiplex.sIoMethodsV1.xSync = multiplexSync;
+  gMultiplex.sIoMethodsV1.xFileSize = multiplexFileSize;
+  gMultiplex.sIoMethodsV1.xLock = multiplexLock;
+  gMultiplex.sIoMethodsV1.xUnlock = multiplexUnlock;
+  gMultiplex.sIoMethodsV1.xCheckReservedLock = multiplexCheckReservedLock;
+  gMultiplex.sIoMethodsV1.xFileControl = multiplexFileControl;
+  gMultiplex.sIoMethodsV1.xSectorSize = multiplexSectorSize;
+  gMultiplex.sIoMethodsV1.xDeviceCharacteristics =
+                                            multiplexDeviceCharacteristics;
+  gMultiplex.sIoMethodsV2 = gMultiplex.sIoMethodsV1;
+  gMultiplex.sIoMethodsV2.iVersion = 2;
+  gMultiplex.sIoMethodsV2.xShmMap = multiplexShmMap;
+  gMultiplex.sIoMethodsV2.xShmLock = multiplexShmLock;
+  gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier;
+  gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap;
+  sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault);
+
+  sqlite3_auto_extension((void*)multiplexFuncInit);
+
+  return SQLITE_OK;
+}
+
+/*
+** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown()
+**
+** All SQLite database connections must be closed before calling this
+** routine.
+**
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while
+** shutting down in order to free all remaining multiplex groups.
+*/
+int sqlite3_multiplex_shutdown(void){
+  if( gMultiplex.isInitialized==0 ) return SQLITE_MISUSE;
+  if( gMultiplex.pGroups ) return SQLITE_MISUSE;
+  gMultiplex.isInitialized = 0;
+  sqlite3_mutex_free(gMultiplex.pMutex);
+  sqlite3_vfs_unregister(&gMultiplex.sThisVfs);
+  memset(&gMultiplex, 0, sizeof(gMultiplex));
+  return SQLITE_OK;
+}
+
+/***************************** Test Code ***********************************/
+#ifdef SQLITE_TEST
+#include <tcl.h>
+extern const char *sqlite3ErrName(int);
+
+
+/*
+** tclcmd: sqlite3_multiplex_initialize NAME MAKEDEFAULT
+*/
+static int test_multiplex_initialize(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zName;              /* Name of new multiplex VFS */
+  int makeDefault;                /* True to make the new VFS the default */
+  int rc;                         /* Value returned by multiplex_initialize() */
+
+  UNUSED_PARAMETER(clientData);
+
+  /* Process arguments */
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");
+    return TCL_ERROR;
+  }
+  zName = Tcl_GetString(objv[1]);
+  if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;
+  if( zName[0]=='\0' ) zName = 0;
+
+  /* Call sqlite3_multiplex_initialize() */
+  rc = sqlite3_multiplex_initialize(zName, makeDefault);
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_multiplex_shutdown
+*/
+static int test_multiplex_shutdown(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc;                         /* Value returned by multiplex_shutdown() */
+
+  UNUSED_PARAMETER(clientData);
+
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  /* Call sqlite3_multiplex_shutdown() */
+  rc = sqlite3_multiplex_shutdown();
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+
+  return TCL_OK;
+}
+
+/*
+** tclcmd:  sqlite3_multiplex_dump
+*/
+static int test_multiplex_dump(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  Tcl_Obj *pResult;
+  Tcl_Obj *pGroupTerm;
+  multiplexGroup *pGroup;
+  int i;
+  int nChunks = 0;
+
+  UNUSED_PARAMETER(clientData);
+  UNUSED_PARAMETER(objc);
+  UNUSED_PARAMETER(objv);
+
+  pResult = Tcl_NewObj();
+  multiplexEnter();
+  for(pGroup=gMultiplex.pGroups; pGroup; pGroup=pGroup->pNext){
+    pGroupTerm = Tcl_NewObj();
+
+    if( pGroup->zName ){
+      pGroup->zName[pGroup->nName] = '\0';
+      Tcl_ListObjAppendElement(interp, pGroupTerm,
+          Tcl_NewStringObj(pGroup->zName, -1));
+    }else{
+      Tcl_ListObjAppendElement(interp, pGroupTerm, Tcl_NewObj());
+    }
+    Tcl_ListObjAppendElement(interp, pGroupTerm,
+          Tcl_NewIntObj(pGroup->nName));
+    Tcl_ListObjAppendElement(interp, pGroupTerm,
+          Tcl_NewIntObj(pGroup->flags));
+
+    /* count number of chunks with open handles */
+    for(i=0; i<pGroup->nReal; i++){
+      if( pGroup->aReal[i].p!=0 ) nChunks++;
+    }
+    Tcl_ListObjAppendElement(interp, pGroupTerm,
+          Tcl_NewIntObj(nChunks));
+
+    Tcl_ListObjAppendElement(interp, pGroupTerm,
+          Tcl_NewIntObj(pGroup->szChunk));
+    Tcl_ListObjAppendElement(interp, pGroupTerm,
+          Tcl_NewIntObj(pGroup->nReal));
+
+    Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
+  }
+  multiplexLeave();
+  Tcl_SetObjResult(interp, pResult);
+  return TCL_OK;
+}
+
+/*
+** Tclcmd: test_multiplex_control HANDLE DBNAME SUB-COMMAND ?INT-VALUE?
+*/
+static int test_multiplex_control(
+  ClientData cd,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc;                         /* Return code from file_control() */
+  int idx;                        /* Index in aSub[] */
+  Tcl_CmdInfo cmdInfo;            /* Command info structure for HANDLE */
+  sqlite3 *db;                    /* Underlying db handle for HANDLE */
+  int iValue = 0;
+  void *pArg = 0;
+
+  struct SubCommand {
+    const char *zName;
+    int op;
+    int argtype;
+  } aSub[] = {
+    { "enable",       MULTIPLEX_CTRL_ENABLE,           1 },
+    { "chunk_size",   MULTIPLEX_CTRL_SET_CHUNK_SIZE,   1 },
+    { "max_chunks",   MULTIPLEX_CTRL_SET_MAX_CHUNKS,   1 },
+    { 0, 0, 0 }
+  };
+
+  if( objc!=5 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE DBNAME SUB-COMMAND INT-VALUE");
+    return TCL_ERROR;
+  }
+
+  if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
+    Tcl_AppendResult(interp, "expected database handle, got \"", 0);
+    Tcl_AppendResult(interp, Tcl_GetString(objv[1]), "\"", 0);
+    return TCL_ERROR;
+  }else{
+    db = *(sqlite3 **)cmdInfo.objClientData;
+  }
+
+  rc = Tcl_GetIndexFromObjStruct(
+      interp, objv[3], aSub, sizeof(aSub[0]), "sub-command", 0, &idx
+  );
+  if( rc!=TCL_OK ) return rc;
+
+  switch( aSub[idx].argtype ){
+    case 1:
+      if( Tcl_GetIntFromObj(interp, objv[4], &iValue) ){
+        return TCL_ERROR;
+      }
+      pArg = (void *)&iValue;
+      break;
+    default:
+      Tcl_WrongNumArgs(interp, 4, objv, "SUB-COMMAND");
+      return TCL_ERROR;
+  }
+
+  rc = sqlite3_file_control(db, Tcl_GetString(objv[2]), aSub[idx].op, pArg);
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+  return (rc==SQLITE_OK) ? TCL_OK : TCL_ERROR;
+}
+
+/*
+** This routine registers the custom TCL commands defined in this
+** module.  This should be the only procedure visible from outside
+** of this module.
+*/
+int Sqlitemultiplex_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+  } aCmd[] = {
+    { "sqlite3_multiplex_initialize", test_multiplex_initialize },
+    { "sqlite3_multiplex_shutdown", test_multiplex_shutdown },
+    { "sqlite3_multiplex_dump", test_multiplex_dump },
+    { "sqlite3_multiplex_control", test_multiplex_control },
+  };
+  int i;
+
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
+
+  return TCL_OK;
+}
+#endif

+ 99 - 0
components/external/sqlite/test/test_multiplex.h

@@ -0,0 +1,99 @@
+/*
+** 2011 March 18
+**
+** 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 contains a VFS "shim" - a layer that sits in between the
+** pager and the real VFS.
+**
+** This particular shim enforces a multiplex system on DB files.  
+** This shim shards/partitions a single DB file into smaller 
+** "chunks" such that the total DB file size may exceed the maximum
+** file size of the underlying file system.
+**
+*/
+
+#ifndef _TEST_MULTIPLEX_H
+#define _TEST_MULTIPLEX_H
+
+/*
+** CAPI: File-control Operations Supported by Multiplex VFS
+**
+** Values interpreted by the xFileControl method of a Multiplex VFS db file-handle.
+**
+** MULTIPLEX_CTRL_ENABLE:
+**   This file control is used to enable or disable the multiplex
+**   shim.
+**
+** MULTIPLEX_CTRL_SET_CHUNK_SIZE:
+**   This file control is used to set the maximum allowed chunk 
+**   size for a multiplex file set.  The chunk size should be 
+**   a multiple of SQLITE_MAX_PAGE_SIZE, and will be rounded up
+**   if not.
+**
+** MULTIPLEX_CTRL_SET_MAX_CHUNKS:
+**   This file control is used to set the maximum number of chunks
+**   allowed to be used for a mutliplex file set.
+*/
+#define MULTIPLEX_CTRL_ENABLE          214014
+#define MULTIPLEX_CTRL_SET_CHUNK_SIZE  214015
+#define MULTIPLEX_CTRL_SET_MAX_CHUNKS  214016
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize()
+**
+** Use the VFS named zOrigVfsName as the VFS that does the actual work.  
+** Use the default if zOrigVfsName==NULL.  
+**
+** The multiplex VFS shim is named "multiplex".  It will become the default
+** VFS if makeDefault is non-zero.
+**
+** An auto-extension is registered which will make the function 
+** multiplex_control() available to database connections.  This
+** function gives access to the xFileControl interface of the 
+** multiplex VFS shim.
+**
+** SELECT multiplex_control(<op>,<val>);
+** 
+**   <op>=1 MULTIPLEX_CTRL_ENABLE
+**   <val>=0 disable
+**   <val>=1 enable
+** 
+**   <op>=2 MULTIPLEX_CTRL_SET_CHUNK_SIZE
+**   <val> int, chunk size
+** 
+**   <op>=3 MULTIPLEX_CTRL_SET_MAX_CHUNKS
+**   <val> int, max chunks
+**
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once
+** during start-up.
+*/
+extern int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault);
+
+/*
+** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown()
+**
+** All SQLite database connections must be closed before calling this
+** routine.
+**
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while
+** shutting down in order to free all remaining multiplex groups.
+*/
+extern int sqlite3_multiplex_shutdown(void);
+
+#ifdef __cplusplus
+}  /* End of the 'extern "C"' block */
+#endif
+
+#endif /* _TEST_MULTIPLEX_H */

+ 439 - 0
components/external/sqlite/test/test_mutex.c

@@ -0,0 +1,439 @@
+/*
+** 2008 June 18
+**
+** 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 contains test logic for the sqlite3_mutex interfaces.
+*/
+
+#include "tcl.h"
+#include "sqlite3.h"
+#include "sqliteInt.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+/* defined in main.c */
+extern const char *sqlite3ErrName(int);
+
+/* A countable mutex */
+struct sqlite3_mutex {
+  sqlite3_mutex *pReal;
+  int eType;
+};
+
+/* State variables */
+static struct test_mutex_globals {
+  int isInstalled;              /* True if installed */
+  int disableInit;              /* True to cause sqlite3_initalize() to fail */
+  int disableTry;               /* True to force sqlite3_mutex_try() to fail */
+  int isInit;                   /* True if initialized */
+  sqlite3_mutex_methods m;      /* Interface to "real" mutex system */
+  int aCounter[8];              /* Number of grabs of each type of mutex */
+  sqlite3_mutex aStatic[6];     /* The six static mutexes */
+} g = {0};
+
+/* Return true if the countable mutex is currently held */
+static int counterMutexHeld(sqlite3_mutex *p){
+  return g.m.xMutexHeld(p->pReal);
+}
+
+/* Return true if the countable mutex is not currently held */
+static int counterMutexNotheld(sqlite3_mutex *p){
+  return g.m.xMutexNotheld(p->pReal);
+}
+
+/* Initialize the countable mutex interface
+** Or, if g.disableInit is non-zero, then do not initialize but instead
+** return the value of g.disableInit as the result code.  This can be used
+** to simulate an initialization failure.
+*/
+static int counterMutexInit(void){ 
+  int rc;
+  if( g.disableInit ) return g.disableInit;
+  rc = g.m.xMutexInit();
+  g.isInit = 1;
+  return rc;
+}
+
+/*
+** Uninitialize the mutex subsystem
+*/
+static int counterMutexEnd(void){ 
+  g.isInit = 0;
+  return g.m.xMutexEnd();
+}
+
+/*
+** Allocate a countable mutex
+*/
+static sqlite3_mutex *counterMutexAlloc(int eType){
+  sqlite3_mutex *pReal;
+  sqlite3_mutex *pRet = 0;
+
+  assert( g.isInit );
+  assert(eType<8 && eType>=0);
+
+  pReal = g.m.xMutexAlloc(eType);
+  if( !pReal ) return 0;
+
+  if( eType==SQLITE_MUTEX_FAST || eType==SQLITE_MUTEX_RECURSIVE ){
+    pRet = (sqlite3_mutex *)malloc(sizeof(sqlite3_mutex));
+  }else{
+    pRet = &g.aStatic[eType-2];
+  }
+
+  pRet->eType = eType;
+  pRet->pReal = pReal;
+  return pRet;
+}
+
+/*
+** Free a countable mutex
+*/
+static void counterMutexFree(sqlite3_mutex *p){
+  assert( g.isInit );
+  g.m.xMutexFree(p->pReal);
+  if( p->eType==SQLITE_MUTEX_FAST || p->eType==SQLITE_MUTEX_RECURSIVE ){
+    free(p);
+  }
+}
+
+/*
+** Enter a countable mutex.  Block until entry is safe.
+*/
+static void counterMutexEnter(sqlite3_mutex *p){
+  assert( g.isInit );
+  g.aCounter[p->eType]++;
+  g.m.xMutexEnter(p->pReal);
+}
+
+/*
+** Try to enter a mutex.  Return true on success.
+*/
+static int counterMutexTry(sqlite3_mutex *p){
+  assert( g.isInit );
+  g.aCounter[p->eType]++;
+  if( g.disableTry ) return SQLITE_BUSY;
+  return g.m.xMutexTry(p->pReal);
+}
+
+/* Leave a mutex
+*/
+static void counterMutexLeave(sqlite3_mutex *p){
+  assert( g.isInit );
+  g.m.xMutexLeave(p->pReal);
+}
+
+/*
+** sqlite3_shutdown
+*/
+static int test_shutdown(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc;
+
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  rc = sqlite3_shutdown();
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+  return TCL_OK;
+}
+
+/*
+** sqlite3_initialize
+*/
+static int test_initialize(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc;
+
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  rc = sqlite3_initialize();
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+  return TCL_OK;
+}
+
+/*
+** install_mutex_counters BOOLEAN
+*/
+static int test_install_mutex_counters(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc = SQLITE_OK;
+  int isInstall;
+
+  sqlite3_mutex_methods counter_methods = {
+    counterMutexInit,
+    counterMutexEnd,
+    counterMutexAlloc,
+    counterMutexFree,
+    counterMutexEnter,
+    counterMutexTry,
+    counterMutexLeave,
+    counterMutexHeld,
+    counterMutexNotheld
+  };
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "BOOLEAN");
+    return TCL_ERROR;
+  }
+  if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[1], &isInstall) ){
+    return TCL_ERROR;
+  }
+
+  assert(isInstall==0 || isInstall==1);
+  assert(g.isInstalled==0 || g.isInstalled==1);
+  if( isInstall==g.isInstalled ){
+    Tcl_AppendResult(interp, "mutex counters are ", 0);
+    Tcl_AppendResult(interp, isInstall?"already installed":"not installed", 0);
+    return TCL_ERROR;
+  }
+
+  if( isInstall ){
+    assert( g.m.xMutexAlloc==0 );
+    rc = sqlite3_config(SQLITE_CONFIG_GETMUTEX, &g.m);
+    if( rc==SQLITE_OK ){
+      sqlite3_config(SQLITE_CONFIG_MUTEX, &counter_methods);
+    }
+    g.disableTry = 0;
+  }else{
+    assert( g.m.xMutexAlloc );
+    rc = sqlite3_config(SQLITE_CONFIG_MUTEX, &g.m);
+    memset(&g.m, 0, sizeof(sqlite3_mutex_methods));
+  }
+
+  if( rc==SQLITE_OK ){
+    g.isInstalled = isInstall;
+  }
+
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+  return TCL_OK;
+}
+
+/*
+** read_mutex_counters
+*/
+static int test_read_mutex_counters(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  Tcl_Obj *pRet;
+  int ii;
+  char *aName[8] = {
+    "fast",        "recursive",   "static_master", "static_mem", 
+    "static_open", "static_prng", "static_lru",    "static_pmem"
+  };
+
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  pRet = Tcl_NewObj();
+  Tcl_IncrRefCount(pRet);
+  for(ii=0; ii<8; ii++){
+    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj(aName[ii], -1));
+    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(g.aCounter[ii]));
+  }
+  Tcl_SetObjResult(interp, pRet);
+  Tcl_DecrRefCount(pRet);
+
+  return TCL_OK;
+}
+
+/*
+** clear_mutex_counters
+*/
+static int test_clear_mutex_counters(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int ii;
+
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  for(ii=0; ii<8; ii++){
+    g.aCounter[ii] = 0;
+  }
+  return TCL_OK;
+}
+
+/*
+** Create and free a mutex.  Return the mutex pointer.  The pointer
+** will be invalid since the mutex has already been freed.  The
+** return pointer just checks to see if the mutex really was allocated.
+*/
+static int test_alloc_mutex(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#if SQLITE_THREADSAFE
+  sqlite3_mutex *p = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+  char zBuf[100];
+  sqlite3_mutex_free(p);
+  sqlite3_snprintf(sizeof(zBuf), zBuf, "%p", p);
+  Tcl_AppendResult(interp, zBuf, (char*)0);
+#endif
+  return TCL_OK;
+}
+
+/*
+** sqlite3_config OPTION
+**
+** OPTION can be either one of the keywords:
+**
+**            SQLITE_CONFIG_SINGLETHREAD
+**            SQLITE_CONFIG_MULTITHREAD
+**            SQLITE_CONFIG_SERIALIZED
+**
+** Or OPTION can be an raw integer.
+*/
+static int test_config(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  struct ConfigOption {
+    const char *zName;
+    int iValue;
+  } aOpt[] = {
+    {"singlethread", SQLITE_CONFIG_SINGLETHREAD},
+    {"multithread",  SQLITE_CONFIG_MULTITHREAD},
+    {"serialized",   SQLITE_CONFIG_SERIALIZED},
+    {0, 0}
+  };
+  int s = sizeof(struct ConfigOption);
+  int i;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  if( Tcl_GetIndexFromObjStruct(interp, objv[1], aOpt, s, "flag", 0, &i) ){
+    if( Tcl_GetIntFromObj(interp, objv[1], &i) ){
+      return TCL_ERROR;
+    }
+  }else{
+    i = aOpt[i].iValue;
+  }
+
+  rc = sqlite3_config(i);
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
+  return TCL_OK;
+}
+
+static sqlite3 *getDbPointer(Tcl_Interp *pInterp, Tcl_Obj *pObj){
+  sqlite3 *db;
+  Tcl_CmdInfo info;
+  char *zCmd = Tcl_GetString(pObj);
+  if( Tcl_GetCommandInfo(pInterp, zCmd, &info) ){
+    db = *((sqlite3 **)info.objClientData);
+  }else{
+    db = (sqlite3*)sqlite3TestTextToPtr(zCmd);
+  }
+  assert( db );
+  return db;
+}
+
+static int test_enter_db_mutex(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+  db = getDbPointer(interp, objv[1]);
+  if( !db ){
+    return TCL_ERROR;
+  }
+  sqlite3_mutex_enter(sqlite3_db_mutex(db));
+  return TCL_OK;
+}
+
+static int test_leave_db_mutex(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+  db = getDbPointer(interp, objv[1]);
+  if( !db ){
+    return TCL_ERROR;
+  }
+  sqlite3_mutex_leave(sqlite3_db_mutex(db));
+  return TCL_OK;
+}
+
+int Sqlitetest_mutex_Init(Tcl_Interp *interp){
+  static struct {
+    char *zName;
+    Tcl_ObjCmdProc *xProc;
+  } aCmd[] = {
+    { "sqlite3_shutdown",        (Tcl_ObjCmdProc*)test_shutdown },
+    { "sqlite3_initialize",      (Tcl_ObjCmdProc*)test_initialize },
+    { "sqlite3_config",          (Tcl_ObjCmdProc*)test_config },
+
+    { "enter_db_mutex",          (Tcl_ObjCmdProc*)test_enter_db_mutex },
+    { "leave_db_mutex",          (Tcl_ObjCmdProc*)test_leave_db_mutex },
+
+    { "alloc_dealloc_mutex",     (Tcl_ObjCmdProc*)test_alloc_mutex },
+    { "install_mutex_counters",  (Tcl_ObjCmdProc*)test_install_mutex_counters },
+    { "read_mutex_counters",     (Tcl_ObjCmdProc*)test_read_mutex_counters },
+    { "clear_mutex_counters",    (Tcl_ObjCmdProc*)test_clear_mutex_counters },
+  };
+  int i;
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
+
+  Tcl_LinkVar(interp, "disable_mutex_init", 
+              (char*)&g.disableInit, TCL_LINK_INT);
+  Tcl_LinkVar(interp, "disable_mutex_try", 
+              (char*)&g.disableTry, TCL_LINK_INT);
+  return SQLITE_OK;
+}

+ 830 - 0
components/external/sqlite/test/test_onefile.c

@@ -0,0 +1,830 @@
+/*
+** 2007 September 14
+**
+** 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.
+**
+*************************************************************************
+**
+** OVERVIEW:
+**
+**   This file contains some example code demonstrating how the SQLite 
+**   vfs feature can be used to have SQLite operate directly on an 
+**   embedded media, without using an intermediate file system.
+**
+**   Because this is only a demo designed to run on a workstation, the
+**   underlying media is simulated using a regular file-system file. The
+**   size of the file is fixed when it is first created (default size 10 MB).
+**   From SQLite's point of view, this space is used to store a single
+**   database file and the journal file. 
+**
+**   Any statement journal created is stored in volatile memory obtained 
+**   from sqlite3_malloc(). Any attempt to create a temporary database file 
+**   will fail (SQLITE_IOERR). To prevent SQLite from attempting this,
+**   it should be configured to store all temporary database files in 
+**   main memory (see pragma "temp_store" or the SQLITE_TEMP_STORE compile 
+**   time option).
+**
+** ASSUMPTIONS:
+**
+**   After it has been created, the blob file is accessed using the
+**   following three functions only:
+**
+**       mediaRead();            - Read a 512 byte block from the file.
+**       mediaWrite();           - Write a 512 byte block to the file.
+**       mediaSync();            - Tell the media hardware to sync.
+**
+**   It is assumed that these can be easily implemented by any "real"
+**   media vfs driver adapting this code.
+**
+** FILE FORMAT:
+**
+**   The basic principle is that the "database file" is stored at the
+**   beginning of the 10 MB blob and grows in a forward direction. The 
+**   "journal file" is stored at the end of the 10MB blob and grows
+**   in the reverse direction. If, during a transaction, insufficient
+**   space is available to expand either the journal or database file,
+**   an SQLITE_FULL error is returned. The database file is never allowed
+**   to consume more than 90% of the blob space. If SQLite tries to
+**   create a file larger than this, SQLITE_FULL is returned.
+**
+**   No allowance is made for "wear-leveling", as is required by.
+**   embedded devices in the absence of equivalent hardware features.
+**
+**   The first 512 block byte of the file is reserved for storing the
+**   size of the "database file". It is updated as part of the sync()
+**   operation. On startup, it can only be trusted if no journal file
+**   exists. If a journal-file does exist, then it stores the real size
+**   of the database region. The second and subsequent blocks store the 
+**   actual database content.
+**
+**   The size of the "journal file" is not stored persistently in the 
+**   file. When the system is running, the size of the journal file is
+**   stored in volatile memory. When recovering from a crash, this vfs
+**   reports a very large size for the journal file. The normal journal
+**   header and checksum mechanisms serve to prevent SQLite from 
+**   processing any data that lies past the logical end of the journal.
+**
+**   When SQLite calls OsDelete() to delete the journal file, the final
+**   512 bytes of the blob (the area containing the first journal header)
+**   are zeroed.
+**
+** LOCKING:
+**
+**   File locking is a no-op. Only one connection may be open at any one
+**   time using this demo vfs.
+*/
+
+#include "sqlite3.h"
+#include <assert.h>
+#include <string.h>
+
+/*
+** Maximum pathname length supported by the fs backend.
+*/
+#define BLOCKSIZE 512
+#define BLOBSIZE 10485760
+
+/*
+** Name used to identify this VFS.
+*/
+#define FS_VFS_NAME "fs"
+
+typedef struct fs_real_file fs_real_file;
+struct fs_real_file {
+  sqlite3_file *pFile;
+  const char *zName;
+  int nDatabase;              /* Current size of database region */
+  int nJournal;               /* Current size of journal region */
+  int nBlob;                  /* Total size of allocated blob */
+  int nRef;                   /* Number of pointers to this structure */
+  fs_real_file *pNext;
+  fs_real_file **ppThis;
+};
+
+typedef struct fs_file fs_file;
+struct fs_file {
+  sqlite3_file base;
+  int eType;
+  fs_real_file *pReal;
+};
+
+typedef struct tmp_file tmp_file;
+struct tmp_file {
+  sqlite3_file base;
+  int nSize;
+  int nAlloc;
+  char *zAlloc;
+};
+
+/* Values for fs_file.eType. */
+#define DATABASE_FILE   1
+#define JOURNAL_FILE    2
+
+/*
+** Method declarations for fs_file.
+*/
+static int fsClose(sqlite3_file*);
+static int fsRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int fsWrite(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
+static int fsTruncate(sqlite3_file*, sqlite3_int64 size);
+static int fsSync(sqlite3_file*, int flags);
+static int fsFileSize(sqlite3_file*, sqlite3_int64 *pSize);
+static int fsLock(sqlite3_file*, int);
+static int fsUnlock(sqlite3_file*, int);
+static int fsCheckReservedLock(sqlite3_file*, int *pResOut);
+static int fsFileControl(sqlite3_file*, int op, void *pArg);
+static int fsSectorSize(sqlite3_file*);
+static int fsDeviceCharacteristics(sqlite3_file*);
+
+/*
+** Method declarations for tmp_file.
+*/
+static int tmpClose(sqlite3_file*);
+static int tmpRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int tmpWrite(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
+static int tmpTruncate(sqlite3_file*, sqlite3_int64 size);
+static int tmpSync(sqlite3_file*, int flags);
+static int tmpFileSize(sqlite3_file*, sqlite3_int64 *pSize);
+static int tmpLock(sqlite3_file*, int);
+static int tmpUnlock(sqlite3_file*, int);
+static int tmpCheckReservedLock(sqlite3_file*, int *pResOut);
+static int tmpFileControl(sqlite3_file*, int op, void *pArg);
+static int tmpSectorSize(sqlite3_file*);
+static int tmpDeviceCharacteristics(sqlite3_file*);
+
+/*
+** Method declarations for fs_vfs.
+*/
+static int fsOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+static int fsDelete(sqlite3_vfs*, const char *zName, int syncDir);
+static int fsAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+static int fsFullPathname(sqlite3_vfs*, const char *zName, int nOut,char *zOut);
+static void *fsDlOpen(sqlite3_vfs*, const char *zFilename);
+static void fsDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
+static void (*fsDlSym(sqlite3_vfs*,void*, const char *zSymbol))(void);
+static void fsDlClose(sqlite3_vfs*, void*);
+static int fsRandomness(sqlite3_vfs*, int nByte, char *zOut);
+static int fsSleep(sqlite3_vfs*, int microseconds);
+static int fsCurrentTime(sqlite3_vfs*, double*);
+
+
+typedef struct fs_vfs_t fs_vfs_t;
+struct fs_vfs_t {
+  sqlite3_vfs base;
+  fs_real_file *pFileList;
+  sqlite3_vfs *pParent;
+};
+
+static fs_vfs_t fs_vfs = {
+  {
+    1,                                          /* iVersion */
+    0,                                          /* szOsFile */
+    0,                                          /* mxPathname */
+    0,                                          /* pNext */
+    FS_VFS_NAME,                                /* zName */
+    0,                                          /* pAppData */
+    fsOpen,                                     /* xOpen */
+    fsDelete,                                   /* xDelete */
+    fsAccess,                                   /* xAccess */
+    fsFullPathname,                             /* xFullPathname */
+    fsDlOpen,                                   /* xDlOpen */
+    fsDlError,                                  /* xDlError */
+    fsDlSym,                                    /* xDlSym */
+    fsDlClose,                                  /* xDlClose */
+    fsRandomness,                               /* xRandomness */
+    fsSleep,                                    /* xSleep */
+    fsCurrentTime,                              /* xCurrentTime */
+    0                                           /* xCurrentTimeInt64 */
+  }, 
+  0,                                            /* pFileList */
+  0                                             /* pParent */
+};
+
+static sqlite3_io_methods fs_io_methods = {
+  1,                            /* iVersion */
+  fsClose,                      /* xClose */
+  fsRead,                       /* xRead */
+  fsWrite,                      /* xWrite */
+  fsTruncate,                   /* xTruncate */
+  fsSync,                       /* xSync */
+  fsFileSize,                   /* xFileSize */
+  fsLock,                       /* xLock */
+  fsUnlock,                     /* xUnlock */
+  fsCheckReservedLock,          /* xCheckReservedLock */
+  fsFileControl,                /* xFileControl */
+  fsSectorSize,                 /* xSectorSize */
+  fsDeviceCharacteristics,      /* xDeviceCharacteristics */
+  0,                            /* xShmMap */
+  0,                            /* xShmLock */
+  0,                            /* xShmBarrier */
+  0                             /* xShmUnmap */
+};
+
+
+static sqlite3_io_methods tmp_io_methods = {
+  1,                            /* iVersion */
+  tmpClose,                     /* xClose */
+  tmpRead,                      /* xRead */
+  tmpWrite,                     /* xWrite */
+  tmpTruncate,                  /* xTruncate */
+  tmpSync,                      /* xSync */
+  tmpFileSize,                  /* xFileSize */
+  tmpLock,                      /* xLock */
+  tmpUnlock,                    /* xUnlock */
+  tmpCheckReservedLock,         /* xCheckReservedLock */
+  tmpFileControl,               /* xFileControl */
+  tmpSectorSize,                /* xSectorSize */
+  tmpDeviceCharacteristics,     /* xDeviceCharacteristics */
+  0,                            /* xShmMap */
+  0,                            /* xShmLock */
+  0,                            /* xShmBarrier */
+  0                             /* xShmUnmap */
+};
+
+/* Useful macros used in several places */
+#define MIN(x,y) ((x)<(y)?(x):(y))
+#define MAX(x,y) ((x)>(y)?(x):(y))
+
+
+/*
+** Close a tmp-file.
+*/
+static int tmpClose(sqlite3_file *pFile){
+  tmp_file *pTmp = (tmp_file *)pFile;
+  sqlite3_free(pTmp->zAlloc);
+  return SQLITE_OK;
+}
+
+/*
+** Read data from a tmp-file.
+*/
+static int tmpRead(
+  sqlite3_file *pFile, 
+  void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  tmp_file *pTmp = (tmp_file *)pFile;
+  if( (iAmt+iOfst)>pTmp->nSize ){
+    return SQLITE_IOERR_SHORT_READ;
+  }
+  memcpy(zBuf, &pTmp->zAlloc[iOfst], iAmt);
+  return SQLITE_OK;
+}
+
+/*
+** Write data to a tmp-file.
+*/
+static int tmpWrite(
+  sqlite3_file *pFile, 
+  const void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  tmp_file *pTmp = (tmp_file *)pFile;
+  if( (iAmt+iOfst)>pTmp->nAlloc ){
+    int nNew = (int)(2*(iAmt+iOfst+pTmp->nAlloc));
+    char *zNew = sqlite3_realloc(pTmp->zAlloc, nNew);
+    if( !zNew ){
+      return SQLITE_NOMEM;
+    }
+    pTmp->zAlloc = zNew;
+    pTmp->nAlloc = nNew;
+  }
+  memcpy(&pTmp->zAlloc[iOfst], zBuf, iAmt);
+  pTmp->nSize = (int)MAX(pTmp->nSize, iOfst+iAmt);
+  return SQLITE_OK;
+}
+
+/*
+** Truncate a tmp-file.
+*/
+static int tmpTruncate(sqlite3_file *pFile, sqlite_int64 size){
+  tmp_file *pTmp = (tmp_file *)pFile;
+  pTmp->nSize = (int)MIN(pTmp->nSize, size);
+  return SQLITE_OK;
+}
+
+/*
+** Sync a tmp-file.
+*/
+static int tmpSync(sqlite3_file *pFile, int flags){
+  return SQLITE_OK;
+}
+
+/*
+** Return the current file-size of a tmp-file.
+*/
+static int tmpFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+  tmp_file *pTmp = (tmp_file *)pFile;
+  *pSize = pTmp->nSize;
+  return SQLITE_OK;
+}
+
+/*
+** Lock a tmp-file.
+*/
+static int tmpLock(sqlite3_file *pFile, int eLock){
+  return SQLITE_OK;
+}
+
+/*
+** Unlock a tmp-file.
+*/
+static int tmpUnlock(sqlite3_file *pFile, int eLock){
+  return SQLITE_OK;
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on a tmp-file.
+*/
+static int tmpCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+  *pResOut = 0;
+  return SQLITE_OK;
+}
+
+/*
+** File control method. For custom operations on a tmp-file.
+*/
+static int tmpFileControl(sqlite3_file *pFile, int op, void *pArg){
+  return SQLITE_OK;
+}
+
+/*
+** Return the sector-size in bytes for a tmp-file.
+*/
+static int tmpSectorSize(sqlite3_file *pFile){
+  return 0;
+}
+
+/*
+** Return the device characteristic flags supported by a tmp-file.
+*/
+static int tmpDeviceCharacteristics(sqlite3_file *pFile){
+  return 0;
+}
+
+/*
+** Close an fs-file.
+*/
+static int fsClose(sqlite3_file *pFile){
+  int rc = SQLITE_OK;
+  fs_file *p = (fs_file *)pFile;
+  fs_real_file *pReal = p->pReal;
+
+  /* Decrement the real_file ref-count. */
+  pReal->nRef--;
+  assert(pReal->nRef>=0);
+
+  /* When the ref-count reaches 0, destroy the structure */
+  if( pReal->nRef==0 ){
+    *pReal->ppThis = pReal->pNext;
+    if( pReal->pNext ){
+      pReal->pNext->ppThis = pReal->ppThis;
+    }
+    rc = pReal->pFile->pMethods->xClose(pReal->pFile);
+    sqlite3_free(pReal);
+  }
+
+  return rc;
+}
+
+/*
+** Read data from an fs-file.
+*/
+static int fsRead(
+  sqlite3_file *pFile, 
+  void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  int rc = SQLITE_OK;
+  fs_file *p = (fs_file *)pFile;
+  fs_real_file *pReal = p->pReal;
+  sqlite3_file *pF = pReal->pFile;
+
+  if( (p->eType==DATABASE_FILE && (iAmt+iOfst)>pReal->nDatabase)
+   || (p->eType==JOURNAL_FILE && (iAmt+iOfst)>pReal->nJournal)
+  ){
+    rc = SQLITE_IOERR_SHORT_READ;
+  }else if( p->eType==DATABASE_FILE ){
+    rc = pF->pMethods->xRead(pF, zBuf, iAmt, iOfst+BLOCKSIZE);
+  }else{
+    /* Journal file. */
+    int iRem = iAmt;
+    int iBuf = 0;
+    int ii = (int)iOfst;
+    while( iRem>0 && rc==SQLITE_OK ){
+      int iRealOff = pReal->nBlob - BLOCKSIZE*((ii/BLOCKSIZE)+1) + ii%BLOCKSIZE;
+      int iRealAmt = MIN(iRem, BLOCKSIZE - (iRealOff%BLOCKSIZE));
+
+      rc = pF->pMethods->xRead(pF, &((char *)zBuf)[iBuf], iRealAmt, iRealOff);
+      ii += iRealAmt;
+      iBuf += iRealAmt;
+      iRem -= iRealAmt;
+    }
+  }
+
+  return rc;
+}
+
+/*
+** Write data to an fs-file.
+*/
+static int fsWrite(
+  sqlite3_file *pFile, 
+  const void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  int rc = SQLITE_OK;
+  fs_file *p = (fs_file *)pFile;
+  fs_real_file *pReal = p->pReal;
+  sqlite3_file *pF = pReal->pFile;
+
+  if( p->eType==DATABASE_FILE ){
+    if( (iAmt+iOfst+BLOCKSIZE)>(pReal->nBlob-pReal->nJournal) ){
+      rc = SQLITE_FULL;
+    }else{
+      rc = pF->pMethods->xWrite(pF, zBuf, iAmt, iOfst+BLOCKSIZE);
+      if( rc==SQLITE_OK ){
+        pReal->nDatabase = (int)MAX(pReal->nDatabase, iAmt+iOfst);
+      }
+    }
+  }else{
+    /* Journal file. */
+    int iRem = iAmt;
+    int iBuf = 0;
+    int ii = (int)iOfst;
+    while( iRem>0 && rc==SQLITE_OK ){
+      int iRealOff = pReal->nBlob - BLOCKSIZE*((ii/BLOCKSIZE)+1) + ii%BLOCKSIZE;
+      int iRealAmt = MIN(iRem, BLOCKSIZE - (iRealOff%BLOCKSIZE));
+
+      if( iRealOff<(pReal->nDatabase+BLOCKSIZE) ){
+        rc = SQLITE_FULL;
+      }else{
+        rc = pF->pMethods->xWrite(pF, &((char *)zBuf)[iBuf], iRealAmt,iRealOff);
+        ii += iRealAmt;
+        iBuf += iRealAmt;
+        iRem -= iRealAmt;
+      }
+    }
+    if( rc==SQLITE_OK ){
+      pReal->nJournal = (int)MAX(pReal->nJournal, iAmt+iOfst);
+    }
+  }
+
+  return rc;
+}
+
+/*
+** Truncate an fs-file.
+*/
+static int fsTruncate(sqlite3_file *pFile, sqlite_int64 size){
+  fs_file *p = (fs_file *)pFile;
+  fs_real_file *pReal = p->pReal;
+  if( p->eType==DATABASE_FILE ){
+    pReal->nDatabase = (int)MIN(pReal->nDatabase, size);
+  }else{
+    pReal->nJournal = (int)MIN(pReal->nJournal, size);
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Sync an fs-file.
+*/
+static int fsSync(sqlite3_file *pFile, int flags){
+  fs_file *p = (fs_file *)pFile;
+  fs_real_file *pReal = p->pReal;
+  sqlite3_file *pRealFile = pReal->pFile;
+  int rc = SQLITE_OK;
+
+  if( p->eType==DATABASE_FILE ){
+    unsigned char zSize[4];
+    zSize[0] = (pReal->nDatabase&0xFF000000)>>24;
+    zSize[1] = (pReal->nDatabase&0x00FF0000)>>16;
+    zSize[2] = (pReal->nDatabase&0x0000FF00)>>8;
+    zSize[3] = (pReal->nDatabase&0x000000FF);
+    rc = pRealFile->pMethods->xWrite(pRealFile, zSize, 4, 0);
+  }
+  if( rc==SQLITE_OK ){
+    rc = pRealFile->pMethods->xSync(pRealFile, flags&(~SQLITE_SYNC_DATAONLY));
+  }
+
+  return rc;
+}
+
+/*
+** Return the current file-size of an fs-file.
+*/
+static int fsFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+  fs_file *p = (fs_file *)pFile;
+  fs_real_file *pReal = p->pReal;
+  if( p->eType==DATABASE_FILE ){
+    *pSize = pReal->nDatabase;
+  }else{
+    *pSize = pReal->nJournal;
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Lock an fs-file.
+*/
+static int fsLock(sqlite3_file *pFile, int eLock){
+  return SQLITE_OK;
+}
+
+/*
+** Unlock an fs-file.
+*/
+static int fsUnlock(sqlite3_file *pFile, int eLock){
+  return SQLITE_OK;
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on an fs-file.
+*/
+static int fsCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+  *pResOut = 0;
+  return SQLITE_OK;
+}
+
+/*
+** File control method. For custom operations on an fs-file.
+*/
+static int fsFileControl(sqlite3_file *pFile, int op, void *pArg){
+  return SQLITE_OK;
+}
+
+/*
+** Return the sector-size in bytes for an fs-file.
+*/
+static int fsSectorSize(sqlite3_file *pFile){
+  return BLOCKSIZE;
+}
+
+/*
+** Return the device characteristic flags supported by an fs-file.
+*/
+static int fsDeviceCharacteristics(sqlite3_file *pFile){
+  return 0;
+}
+
+/*
+** Open an fs file handle.
+*/
+static int fsOpen(
+  sqlite3_vfs *pVfs,
+  const char *zName,
+  sqlite3_file *pFile,
+  int flags,
+  int *pOutFlags
+){
+  fs_vfs_t *pFsVfs = (fs_vfs_t *)pVfs;
+  fs_file *p = (fs_file *)pFile;
+  fs_real_file *pReal = 0;
+  int eType;
+  int nName;
+  int rc = SQLITE_OK;
+
+  if( 0==(flags&(SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_MAIN_JOURNAL)) ){
+    tmp_file *p = (tmp_file *)pFile;
+    memset(p, 0, sizeof(*p));
+    p->base.pMethods = &tmp_io_methods;
+    return SQLITE_OK;
+  }
+
+  eType = ((flags&(SQLITE_OPEN_MAIN_DB))?DATABASE_FILE:JOURNAL_FILE);
+  p->base.pMethods = &fs_io_methods;
+  p->eType = eType;
+
+  assert(strlen("-journal")==8);
+  nName = (int)strlen(zName)-((eType==JOURNAL_FILE)?8:0);
+  pReal=pFsVfs->pFileList; 
+  for(; pReal && strncmp(pReal->zName, zName, nName); pReal=pReal->pNext);
+
+  if( !pReal ){
+    int real_flags = (flags&~(SQLITE_OPEN_MAIN_DB))|SQLITE_OPEN_TEMP_DB;
+    sqlite3_int64 size;
+    sqlite3_file *pRealFile;
+    sqlite3_vfs *pParent = pFsVfs->pParent;
+    assert(eType==DATABASE_FILE);
+
+    pReal = (fs_real_file *)sqlite3_malloc(sizeof(*pReal)+pParent->szOsFile);
+    if( !pReal ){
+      rc = SQLITE_NOMEM;
+      goto open_out;
+    }
+    memset(pReal, 0, sizeof(*pReal)+pParent->szOsFile);
+    pReal->zName = zName;
+    pReal->pFile = (sqlite3_file *)(&pReal[1]);
+
+    rc = pParent->xOpen(pParent, zName, pReal->pFile, real_flags, pOutFlags);
+    if( rc!=SQLITE_OK ){
+      goto open_out;
+    }
+    pRealFile = pReal->pFile;
+
+    rc = pRealFile->pMethods->xFileSize(pRealFile, &size);
+    if( rc!=SQLITE_OK ){
+      goto open_out;
+    }
+    if( size==0 ){
+      rc = pRealFile->pMethods->xWrite(pRealFile, "\0", 1, BLOBSIZE-1);
+      pReal->nBlob = BLOBSIZE;
+    }else{
+      unsigned char zS[4];
+      pReal->nBlob = (int)size;
+      rc = pRealFile->pMethods->xRead(pRealFile, zS, 4, 0);
+      pReal->nDatabase = (zS[0]<<24)+(zS[1]<<16)+(zS[2]<<8)+zS[3];
+      if( rc==SQLITE_OK ){
+        rc = pRealFile->pMethods->xRead(pRealFile, zS, 4, pReal->nBlob-4);
+        if( zS[0] || zS[1] || zS[2] || zS[3] ){
+          pReal->nJournal = pReal->nBlob;
+        }
+      }
+    }
+
+    if( rc==SQLITE_OK ){
+      pReal->pNext = pFsVfs->pFileList;
+      if( pReal->pNext ){
+        pReal->pNext->ppThis = &pReal->pNext;
+      }
+      pReal->ppThis = &pFsVfs->pFileList;
+      pFsVfs->pFileList = pReal;
+    }
+  }
+
+open_out:
+  if( pReal ){
+    if( rc==SQLITE_OK ){
+      p->pReal = pReal;
+      pReal->nRef++;
+    }else{
+      if( pReal->pFile->pMethods ){
+        pReal->pFile->pMethods->xClose(pReal->pFile);
+      }
+      sqlite3_free(pReal);
+    }
+  }
+  return rc;
+}
+
+/*
+** Delete the file located at zPath. If the dirSync argument is true,
+** ensure the file-system modifications are synced to disk before
+** returning.
+*/
+static int fsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+  int rc = SQLITE_OK;
+  fs_vfs_t *pFsVfs = (fs_vfs_t *)pVfs;
+  fs_real_file *pReal;
+  sqlite3_file *pF;
+  int nName = (int)strlen(zPath) - 8;
+
+  assert(strlen("-journal")==8);
+  assert(strcmp("-journal", &zPath[nName])==0);
+
+  pReal = pFsVfs->pFileList; 
+  for(; pReal && strncmp(pReal->zName, zPath, nName); pReal=pReal->pNext);
+  if( pReal ){
+    pF = pReal->pFile;
+    rc = pF->pMethods->xWrite(pF, "\0\0\0\0", 4, pReal->nBlob-BLOCKSIZE);
+    if( rc==SQLITE_OK ){
+      pReal->nJournal = 0;
+    }
+  }
+  return rc;
+}
+
+/*
+** Test for access permissions. Return true if the requested permission
+** is available, or false otherwise.
+*/
+static int fsAccess(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int flags, 
+  int *pResOut
+){
+  fs_vfs_t *pFsVfs = (fs_vfs_t *)pVfs;
+  fs_real_file *pReal;
+  int isJournal = 0;
+  int nName = (int)strlen(zPath);
+
+  if( flags!=SQLITE_ACCESS_EXISTS ){
+    sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
+    return pParent->xAccess(pParent, zPath, flags, pResOut);
+  }
+
+  assert(strlen("-journal")==8);
+  if( nName>8 && strcmp("-journal", &zPath[nName-8])==0 ){
+    nName -= 8;
+    isJournal = 1;
+  }
+
+  pReal = pFsVfs->pFileList; 
+  for(; pReal && strncmp(pReal->zName, zPath, nName); pReal=pReal->pNext);
+
+  *pResOut = (pReal && (!isJournal || pReal->nJournal>0));
+  return SQLITE_OK;
+}
+
+/*
+** Populate buffer zOut with the full canonical pathname corresponding
+** to the pathname in zPath. zOut is guaranteed to point to a buffer
+** of at least (FS_MAX_PATHNAME+1) bytes.
+*/
+static int fsFullPathname(
+  sqlite3_vfs *pVfs,            /* Pointer to vfs object */
+  const char *zPath,            /* Possibly relative input path */
+  int nOut,                     /* Size of output buffer in bytes */
+  char *zOut                    /* Output buffer */
+){
+  sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
+  return pParent->xFullPathname(pParent, zPath, nOut, zOut);
+}
+
+/*
+** Open the dynamic library located at zPath and return a handle.
+*/
+static void *fsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+  sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
+  return pParent->xDlOpen(pParent, zPath);
+}
+
+/*
+** Populate the buffer zErrMsg (size nByte bytes) with a human readable
+** utf-8 string describing the most recent error encountered associated 
+** with dynamic libraries.
+*/
+static void fsDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+  sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
+  pParent->xDlError(pParent, nByte, zErrMsg);
+}
+
+/*
+** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
+*/
+static void (*fsDlSym(sqlite3_vfs *pVfs, void *pH, const char *zSym))(void){
+  sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
+  return pParent->xDlSym(pParent, pH, zSym);
+}
+
+/*
+** Close the dynamic library handle pHandle.
+*/
+static void fsDlClose(sqlite3_vfs *pVfs, void *pHandle){
+  sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
+  pParent->xDlClose(pParent, pHandle);
+}
+
+/*
+** Populate the buffer pointed to by zBufOut with nByte bytes of 
+** random data.
+*/
+static int fsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+  sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
+  return pParent->xRandomness(pParent, nByte, zBufOut);
+}
+
+/*
+** Sleep for nMicro microseconds. Return the number of microseconds 
+** actually slept.
+*/
+static int fsSleep(sqlite3_vfs *pVfs, int nMicro){
+  sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
+  return pParent->xSleep(pParent, nMicro);
+}
+
+/*
+** Return the current time as a Julian Day number in *pTimeOut.
+*/
+static int fsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+  sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
+  return pParent->xCurrentTime(pParent, pTimeOut);
+}
+
+/*
+** This procedure registers the fs vfs with SQLite. If the argument is
+** true, the fs vfs becomes the new default vfs. It is the only publicly
+** available function in this file.
+*/
+int fs_register(void){
+  if( fs_vfs.pParent ) return SQLITE_OK;
+  fs_vfs.pParent = sqlite3_vfs_find(0);
+  fs_vfs.base.mxPathname = fs_vfs.pParent->mxPathname;
+  fs_vfs.base.szOsFile = MAX(sizeof(tmp_file), sizeof(fs_file));
+  return sqlite3_vfs_register(&fs_vfs.base, 0);
+}
+
+#ifdef SQLITE_TEST
+  int SqlitetestOnefile_Init() {return fs_register();}
+#endif

+ 1215 - 0
components/external/sqlite/test/test_osinst.c

@@ -0,0 +1,1215 @@
+/*
+** 2008 April 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 contains the implementation of an SQLite vfs wrapper that
+** adds instrumentation to all vfs and file methods. C and Tcl interfaces
+** are provided to control the instrumentation.
+*/
+
+/*
+** This module contains code for a wrapper VFS that causes a log of
+** most VFS calls to be written into a nominated file on disk. The log 
+** is stored in a compressed binary format to reduce the amount of IO 
+** overhead introduced into the application by logging.
+**
+** All calls on sqlite3_file objects except xFileControl() are logged.
+** Additionally, calls to the xAccess(), xOpen(), and xDelete()
+** methods are logged. The other sqlite3_vfs object methods (xDlXXX,
+** xRandomness, xSleep, xCurrentTime, xGetLastError and xCurrentTimeInt64) 
+** are not logged.
+**
+** The binary log files are read using a virtual table implementation
+** also contained in this file. 
+**
+** CREATING LOG FILES:
+**
+**       int sqlite3_vfslog_new(
+**         const char *zVfs,          // Name of new VFS
+**         const char *zParentVfs,    // Name of parent VFS (or NULL)
+**         const char *zLog           // Name of log file to write to
+**       );
+**
+**       int sqlite3_vfslog_finalize(const char *zVfs);
+**
+** ANNOTATING LOG FILES:
+**
+**   To write an arbitrary message into a log file:
+**
+**       int sqlite3_vfslog_annotate(const char *zVfs, const char *zMsg);
+**
+** READING LOG FILES:
+**
+**   Log files are read using the "vfslog" virtual table implementation
+**   in this file. To register the virtual table with SQLite, use:
+**
+**       int sqlite3_vfslog_register(sqlite3 *db);
+**
+**   Then, if the log file is named "vfs.log", the following SQL command:
+**
+**       CREATE VIRTUAL TABLE v USING vfslog('vfs.log');
+**
+**   creates a virtual table with 6 columns, as follows:
+**
+**       CREATE TABLE v(
+**         event    TEXT,             // "xOpen", "xRead" etc.
+**         file     TEXT,             // Name of file this call applies to
+**         clicks   INTEGER,          // Time spent in call
+**         rc       INTEGER,          // Return value
+**         size     INTEGER,          // Bytes read or written
+**         offset   INTEGER           // File offset read or written
+**       );
+*/
+
+#include "sqlite3.h"
+#include <string.h>
+#include <assert.h>
+
+
+/*
+** Maximum pathname length supported by the vfslog backend.
+*/
+#define INST_MAX_PATHNAME 512
+
+#define OS_ACCESS            1
+#define OS_CHECKRESERVEDLOCK 2
+#define OS_CLOSE             3
+#define OS_CURRENTTIME       4
+#define OS_DELETE            5
+#define OS_DEVCHAR           6
+#define OS_FILECONTROL       7
+#define OS_FILESIZE          8
+#define OS_FULLPATHNAME      9
+#define OS_LOCK              11
+#define OS_OPEN              12
+#define OS_RANDOMNESS        13
+#define OS_READ              14 
+#define OS_SECTORSIZE        15
+#define OS_SLEEP             16
+#define OS_SYNC              17
+#define OS_TRUNCATE          18
+#define OS_UNLOCK            19
+#define OS_WRITE             20
+#define OS_SHMUNMAP          22
+#define OS_SHMMAP            23
+#define OS_SHMLOCK           25
+#define OS_SHMBARRIER        26
+#define OS_ANNOTATE          28
+
+#define OS_NUMEVENTS         29
+
+#define VFSLOG_BUFFERSIZE 8192
+
+typedef struct VfslogVfs VfslogVfs;
+typedef struct VfslogFile VfslogFile;
+
+struct VfslogVfs {
+  sqlite3_vfs base;               /* VFS methods */
+  sqlite3_vfs *pVfs;              /* Parent VFS */
+  int iNextFileId;                /* Next file id */
+  sqlite3_file *pLog;             /* Log file handle */
+  sqlite3_int64 iOffset;          /* Log file offset of start of write buffer */
+  int nBuf;                       /* Number of valid bytes in aBuf[] */
+  char aBuf[VFSLOG_BUFFERSIZE];   /* Write buffer */
+};
+
+struct VfslogFile {
+  sqlite3_file base;              /* IO methods */
+  sqlite3_file *pReal;            /* Underlying file handle */
+  sqlite3_vfs *pVfslog;           /* Associated VsflogVfs object */
+  int iFileId;                    /* File id number */
+};
+
+#define REALVFS(p) (((VfslogVfs *)(p))->pVfs)
+
+
+
+/*
+** Method declarations for vfslog_file.
+*/
+static int vfslogClose(sqlite3_file*);
+static int vfslogRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int vfslogWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
+static int vfslogTruncate(sqlite3_file*, sqlite3_int64 size);
+static int vfslogSync(sqlite3_file*, int flags);
+static int vfslogFileSize(sqlite3_file*, sqlite3_int64 *pSize);
+static int vfslogLock(sqlite3_file*, int);
+static int vfslogUnlock(sqlite3_file*, int);
+static int vfslogCheckReservedLock(sqlite3_file*, int *pResOut);
+static int vfslogFileControl(sqlite3_file*, int op, void *pArg);
+static int vfslogSectorSize(sqlite3_file*);
+static int vfslogDeviceCharacteristics(sqlite3_file*);
+
+static int vfslogShmLock(sqlite3_file *pFile, int ofst, int n, int flags);
+static int vfslogShmMap(sqlite3_file *pFile,int,int,int,volatile void **);
+static void vfslogShmBarrier(sqlite3_file*);
+static int vfslogShmUnmap(sqlite3_file *pFile, int deleteFlag);
+
+/*
+** Method declarations for vfslog_vfs.
+*/
+static int vfslogOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+static int vfslogDelete(sqlite3_vfs*, const char *zName, int syncDir);
+static int vfslogAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+static int vfslogFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
+static void *vfslogDlOpen(sqlite3_vfs*, const char *zFilename);
+static void vfslogDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
+static void (*vfslogDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void);
+static void vfslogDlClose(sqlite3_vfs*, void*);
+static int vfslogRandomness(sqlite3_vfs*, int nByte, char *zOut);
+static int vfslogSleep(sqlite3_vfs*, int microseconds);
+static int vfslogCurrentTime(sqlite3_vfs*, double*);
+
+static int vfslogGetLastError(sqlite3_vfs*, int, char *);
+static int vfslogCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
+
+static sqlite3_vfs vfslog_vfs = {
+  1,                              /* iVersion */
+  sizeof(VfslogFile),             /* szOsFile */
+  INST_MAX_PATHNAME,              /* mxPathname */
+  0,                              /* pNext */
+  0,                              /* zName */
+  0,                              /* pAppData */
+  vfslogOpen,                     /* xOpen */
+  vfslogDelete,                   /* xDelete */
+  vfslogAccess,                   /* xAccess */
+  vfslogFullPathname,             /* xFullPathname */
+  vfslogDlOpen,                   /* xDlOpen */
+  vfslogDlError,                  /* xDlError */
+  vfslogDlSym,                    /* xDlSym */
+  vfslogDlClose,                  /* xDlClose */
+  vfslogRandomness,               /* xRandomness */
+  vfslogSleep,                    /* xSleep */
+  vfslogCurrentTime,              /* xCurrentTime */
+  vfslogGetLastError,             /* xGetLastError */
+  vfslogCurrentTimeInt64          /* xCurrentTime */
+};
+
+static sqlite3_io_methods vfslog_io_methods = {
+  2,                              /* iVersion */
+  vfslogClose,                    /* xClose */
+  vfslogRead,                     /* xRead */
+  vfslogWrite,                    /* xWrite */
+  vfslogTruncate,                 /* xTruncate */
+  vfslogSync,                     /* xSync */
+  vfslogFileSize,                 /* xFileSize */
+  vfslogLock,                     /* xLock */
+  vfslogUnlock,                   /* xUnlock */
+  vfslogCheckReservedLock,        /* xCheckReservedLock */
+  vfslogFileControl,              /* xFileControl */
+  vfslogSectorSize,               /* xSectorSize */
+  vfslogDeviceCharacteristics,    /* xDeviceCharacteristics */
+  vfslogShmMap,                   /* xShmMap */
+  vfslogShmLock,                  /* xShmLock */
+  vfslogShmBarrier,               /* xShmBarrier */
+  vfslogShmUnmap                  /* xShmUnmap */
+};
+
+#if SQLITE_OS_UNIX && !defined(NO_GETTOD)
+#include <sys/time.h>
+static sqlite3_uint64 vfslog_time(){
+  struct timeval sTime;
+  gettimeofday(&sTime, 0);
+  return sTime.tv_usec + (sqlite3_uint64)sTime.tv_sec * 1000000;
+}
+#elif SQLITE_OS_WIN
+#include <windows.h>
+#include <time.h>
+static sqlite3_uint64 vfslog_time(){
+  FILETIME ft;
+  sqlite3_uint64 u64time = 0;
+ 
+  GetSystemTimeAsFileTime(&ft);
+
+  u64time |= ft.dwHighDateTime;
+  u64time <<= 32;
+  u64time |= ft.dwLowDateTime;
+
+  /* ft is 100-nanosecond intervals, we want microseconds */
+  return u64time /(sqlite3_uint64)10;
+}
+#else
+static sqlite3_uint64 vfslog_time(){
+  return 0;
+}
+#endif
+
+static void vfslog_call(sqlite3_vfs *, int, int, sqlite3_int64, int, int, int);
+static void vfslog_string(sqlite3_vfs *, const char *);
+
+/*
+** Close an vfslog-file.
+*/
+static int vfslogClose(sqlite3_file *pFile){
+  sqlite3_uint64 t;
+  int rc = SQLITE_OK;
+  VfslogFile *p = (VfslogFile *)pFile;
+
+  t = vfslog_time();
+  if( p->pReal->pMethods ){
+    rc = p->pReal->pMethods->xClose(p->pReal);
+  }
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_CLOSE, p->iFileId, t, rc, 0, 0);
+  return rc;
+}
+
+/*
+** Read data from an vfslog-file.
+*/
+static int vfslogRead(
+  sqlite3_file *pFile, 
+  void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_READ, p->iFileId, t, rc, iAmt, (int)iOfst);
+  return rc;
+}
+
+/*
+** Write data to an vfslog-file.
+*/
+static int vfslogWrite(
+  sqlite3_file *pFile,
+  const void *z,
+  int iAmt,
+  sqlite_int64 iOfst
+){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xWrite(p->pReal, z, iAmt, iOfst);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_WRITE, p->iFileId, t, rc, iAmt, (int)iOfst);
+  return rc;
+}
+
+/*
+** Truncate an vfslog-file.
+*/
+static int vfslogTruncate(sqlite3_file *pFile, sqlite_int64 size){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xTruncate(p->pReal, size);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_TRUNCATE, p->iFileId, t, rc, 0, (int)size);
+  return rc;
+}
+
+/*
+** Sync an vfslog-file.
+*/
+static int vfslogSync(sqlite3_file *pFile, int flags){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xSync(p->pReal, flags);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_SYNC, p->iFileId, t, rc, flags, 0);
+  return rc;
+}
+
+/*
+** Return the current file-size of an vfslog-file.
+*/
+static int vfslogFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_FILESIZE, p->iFileId, t, rc, 0, (int)*pSize);
+  return rc;
+}
+
+/*
+** Lock an vfslog-file.
+*/
+static int vfslogLock(sqlite3_file *pFile, int eLock){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xLock(p->pReal, eLock);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_LOCK, p->iFileId, t, rc, eLock, 0);
+  return rc;
+}
+
+/*
+** Unlock an vfslog-file.
+*/
+static int vfslogUnlock(sqlite3_file *pFile, int eLock){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xUnlock(p->pReal, eLock);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_UNLOCK, p->iFileId, t, rc, eLock, 0);
+  return rc;
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on an vfslog-file.
+*/
+static int vfslogCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_CHECKRESERVEDLOCK, p->iFileId, t, rc, *pResOut, 0);
+  return rc;
+}
+
+/*
+** File control method. For custom operations on an vfslog-file.
+*/
+static int vfslogFileControl(sqlite3_file *pFile, int op, void *pArg){
+  VfslogFile *p = (VfslogFile *)pFile;
+  int rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
+  if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
+    *(char**)pArg = sqlite3_mprintf("vfslog/%z", *(char**)pArg);
+  }
+  return rc;
+}
+
+/*
+** Return the sector-size in bytes for an vfslog-file.
+*/
+static int vfslogSectorSize(sqlite3_file *pFile){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xSectorSize(p->pReal);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_SECTORSIZE, p->iFileId, t, rc, 0, 0);
+  return rc;
+}
+
+/*
+** Return the device characteristic flags supported by an vfslog-file.
+*/
+static int vfslogDeviceCharacteristics(sqlite3_file *pFile){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_DEVCHAR, p->iFileId, t, rc, 0, 0);
+  return rc;
+}
+
+static int vfslogShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_SHMLOCK, p->iFileId, t, rc, 0, 0);
+  return rc;
+}
+static int vfslogShmMap(
+  sqlite3_file *pFile, 
+  int iRegion, 
+  int szRegion, 
+  int isWrite, 
+  volatile void **pp
+){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_SHMMAP, p->iFileId, t, rc, 0, 0);
+  return rc;
+}
+static void vfslogShmBarrier(sqlite3_file *pFile){
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  p->pReal->pMethods->xShmBarrier(p->pReal);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_SHMBARRIER, p->iFileId, t, SQLITE_OK, 0, 0);
+}
+static int vfslogShmUnmap(sqlite3_file *pFile, int deleteFlag){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  t = vfslog_time();
+  rc = p->pReal->pMethods->xShmUnmap(p->pReal, deleteFlag);
+  t = vfslog_time() - t;
+  vfslog_call(p->pVfslog, OS_SHMUNMAP, p->iFileId, t, rc, 0, 0);
+  return rc;
+}
+
+
+/*
+** Open an vfslog file handle.
+*/
+static int vfslogOpen(
+  sqlite3_vfs *pVfs,
+  const char *zName,
+  sqlite3_file *pFile,
+  int flags,
+  int *pOutFlags
+){
+  int rc;
+  sqlite3_uint64 t;
+  VfslogFile *p = (VfslogFile *)pFile;
+  VfslogVfs *pLog = (VfslogVfs *)pVfs;
+
+  pFile->pMethods = &vfslog_io_methods;
+  p->pReal = (sqlite3_file *)&p[1];
+  p->pVfslog = pVfs;
+  p->iFileId = ++pLog->iNextFileId;
+
+  t = vfslog_time();
+  rc = REALVFS(pVfs)->xOpen(REALVFS(pVfs), zName, p->pReal, flags, pOutFlags);
+  t = vfslog_time() - t;
+
+  vfslog_call(pVfs, OS_OPEN, p->iFileId, t, rc, 0, 0);
+  vfslog_string(pVfs, zName);
+  return rc;
+}
+
+/*
+** Delete the file located at zPath. If the dirSync argument is true,
+** ensure the file-system modifications are synced to disk before
+** returning.
+*/
+static int vfslogDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+  int rc;
+  sqlite3_uint64 t;
+  t = vfslog_time();
+  rc = REALVFS(pVfs)->xDelete(REALVFS(pVfs), zPath, dirSync);
+  t = vfslog_time() - t;
+  vfslog_call(pVfs, OS_DELETE, 0, t, rc, dirSync, 0);
+  vfslog_string(pVfs, zPath);
+  return rc;
+}
+
+/*
+** Test for access permissions. Return true if the requested permission
+** is available, or false otherwise.
+*/
+static int vfslogAccess(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int flags, 
+  int *pResOut
+){
+  int rc;
+  sqlite3_uint64 t;
+  t = vfslog_time();
+  rc = REALVFS(pVfs)->xAccess(REALVFS(pVfs), zPath, flags, pResOut);
+  t = vfslog_time() - t;
+  vfslog_call(pVfs, OS_ACCESS, 0, t, rc, flags, *pResOut);
+  vfslog_string(pVfs, zPath);
+  return rc;
+}
+
+/*
+** Populate buffer zOut with the full canonical pathname corresponding
+** to the pathname in zPath. zOut is guaranteed to point to a buffer
+** of at least (INST_MAX_PATHNAME+1) bytes.
+*/
+static int vfslogFullPathname(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int nOut, 
+  char *zOut
+){
+  return REALVFS(pVfs)->xFullPathname(REALVFS(pVfs), zPath, nOut, zOut);
+}
+
+/*
+** Open the dynamic library located at zPath and return a handle.
+*/
+static void *vfslogDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+  return REALVFS(pVfs)->xDlOpen(REALVFS(pVfs), zPath);
+}
+
+/*
+** Populate the buffer zErrMsg (size nByte bytes) with a human readable
+** utf-8 string describing the most recent error encountered associated 
+** with dynamic libraries.
+*/
+static void vfslogDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+  REALVFS(pVfs)->xDlError(REALVFS(pVfs), nByte, zErrMsg);
+}
+
+/*
+** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
+*/
+static void (*vfslogDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
+  return REALVFS(pVfs)->xDlSym(REALVFS(pVfs), p, zSym);
+}
+
+/*
+** Close the dynamic library handle pHandle.
+*/
+static void vfslogDlClose(sqlite3_vfs *pVfs, void *pHandle){
+  REALVFS(pVfs)->xDlClose(REALVFS(pVfs), pHandle);
+}
+
+/*
+** Populate the buffer pointed to by zBufOut with nByte bytes of 
+** random data.
+*/
+static int vfslogRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+  return REALVFS(pVfs)->xRandomness(REALVFS(pVfs), nByte, zBufOut);
+}
+
+/*
+** Sleep for nMicro microseconds. Return the number of microseconds 
+** actually slept.
+*/
+static int vfslogSleep(sqlite3_vfs *pVfs, int nMicro){
+  return REALVFS(pVfs)->xSleep(REALVFS(pVfs), nMicro);
+}
+
+/*
+** Return the current time as a Julian Day number in *pTimeOut.
+*/
+static int vfslogCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+  return REALVFS(pVfs)->xCurrentTime(REALVFS(pVfs), pTimeOut);
+}
+
+static int vfslogGetLastError(sqlite3_vfs *pVfs, int a, char *b){
+  return REALVFS(pVfs)->xGetLastError(REALVFS(pVfs), a, b);
+}
+static int vfslogCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){
+  return REALVFS(pVfs)->xCurrentTimeInt64(REALVFS(pVfs), p);
+}
+
+static void vfslog_flush(VfslogVfs *p){
+#ifdef SQLITE_TEST
+  extern int sqlite3_io_error_pending;
+  extern int sqlite3_io_error_persist;
+  extern int sqlite3_diskfull_pending;
+
+  int pending = sqlite3_io_error_pending;
+  int persist = sqlite3_io_error_persist;
+  int diskfull = sqlite3_diskfull_pending;
+
+  sqlite3_io_error_pending = 0;
+  sqlite3_io_error_persist = 0;
+  sqlite3_diskfull_pending = 0;
+#endif
+
+  if( p->nBuf ){
+    p->pLog->pMethods->xWrite(p->pLog, p->aBuf, p->nBuf, p->iOffset);
+    p->iOffset += p->nBuf;
+    p->nBuf = 0;
+  }
+
+#ifdef SQLITE_TEST
+  sqlite3_io_error_pending = pending;
+  sqlite3_io_error_persist = persist;
+  sqlite3_diskfull_pending = diskfull;
+#endif
+}
+
+static void put32bits(unsigned char *p, unsigned int v){
+  p[0] = v>>24;
+  p[1] = v>>16;
+  p[2] = v>>8;
+  p[3] = v;
+}
+
+static void vfslog_call(
+  sqlite3_vfs *pVfs,
+  int eEvent,
+  int iFileid,
+  sqlite3_int64 nClick,
+  int return_code,
+  int size,
+  int offset
+){
+  VfslogVfs *p = (VfslogVfs *)pVfs;
+  unsigned char *zRec;
+  if( (24+p->nBuf)>sizeof(p->aBuf) ){
+    vfslog_flush(p);
+  }
+  zRec = (unsigned char *)&p->aBuf[p->nBuf];
+  put32bits(&zRec[0], eEvent);
+  put32bits(&zRec[4], iFileid);
+  put32bits(&zRec[8], (unsigned int)(nClick&0xffff));
+  put32bits(&zRec[12], return_code);
+  put32bits(&zRec[16], size);
+  put32bits(&zRec[20], offset);
+  p->nBuf += 24;
+}
+
+static void vfslog_string(sqlite3_vfs *pVfs, const char *zStr){
+  VfslogVfs *p = (VfslogVfs *)pVfs;
+  unsigned char *zRec;
+  int nStr = zStr ? (int)strlen(zStr) : 0;
+  if( (4+nStr+p->nBuf)>sizeof(p->aBuf) ){
+    vfslog_flush(p);
+  }
+  zRec = (unsigned char *)&p->aBuf[p->nBuf];
+  put32bits(&zRec[0], nStr);
+  if( zStr ){
+    memcpy(&zRec[4], zStr, nStr);
+  }
+  p->nBuf += (4 + nStr);
+}
+
+static void vfslog_finalize(VfslogVfs *p){
+  if( p->pLog->pMethods ){
+    vfslog_flush(p);
+    p->pLog->pMethods->xClose(p->pLog);
+  }
+  sqlite3_free(p);
+}
+
+int sqlite3_vfslog_finalize(const char *zVfs){
+  sqlite3_vfs *pVfs;
+  pVfs = sqlite3_vfs_find(zVfs);
+  if( !pVfs || pVfs->xOpen!=vfslogOpen ){
+    return SQLITE_ERROR;
+  } 
+  sqlite3_vfs_unregister(pVfs);
+  vfslog_finalize((VfslogVfs *)pVfs);
+  return SQLITE_OK;
+}
+
+int sqlite3_vfslog_new(
+  const char *zVfs,               /* New VFS name */
+  const char *zParentVfs,         /* Parent VFS name (or NULL) */
+  const char *zLog                /* Log file name */
+){
+  VfslogVfs *p;
+  sqlite3_vfs *pParent;
+  int nByte;
+  int flags;
+  int rc;
+  char *zFile;
+  int nVfs;
+
+  pParent = sqlite3_vfs_find(zParentVfs);
+  if( !pParent ){
+    return SQLITE_ERROR;
+  }
+
+  nVfs = (int)strlen(zVfs);
+  nByte = sizeof(VfslogVfs) + pParent->szOsFile + nVfs+1+pParent->mxPathname+1;
+  p = (VfslogVfs *)sqlite3_malloc(nByte);
+  memset(p, 0, nByte);
+
+  p->pVfs = pParent;
+  p->pLog = (sqlite3_file *)&p[1];
+  memcpy(&p->base, &vfslog_vfs, sizeof(sqlite3_vfs));
+  p->base.zName = &((char *)p->pLog)[pParent->szOsFile];
+  p->base.szOsFile += pParent->szOsFile;
+  memcpy((char *)p->base.zName, zVfs, nVfs);
+
+  zFile = (char *)&p->base.zName[nVfs+1];
+  pParent->xFullPathname(pParent, zLog, pParent->mxPathname, zFile);
+
+  flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MASTER_JOURNAL;
+  pParent->xDelete(pParent, zFile, 0);
+  rc = pParent->xOpen(pParent, zFile, p->pLog, flags, &flags);
+  if( rc==SQLITE_OK ){
+    memcpy(p->aBuf, "sqlite_ostrace1.....", 20);
+    p->iOffset = 0;
+    p->nBuf = 20;
+    rc = sqlite3_vfs_register((sqlite3_vfs *)p, 1);
+  }
+  if( rc ){
+    vfslog_finalize(p);
+  }
+  return rc;
+}
+
+int sqlite3_vfslog_annotate(const char *zVfs, const char *zMsg){
+  sqlite3_vfs *pVfs;
+  pVfs = sqlite3_vfs_find(zVfs);
+  if( !pVfs || pVfs->xOpen!=vfslogOpen ){
+    return SQLITE_ERROR;
+  } 
+  vfslog_call(pVfs, OS_ANNOTATE, 0, 0, 0, 0, 0);
+  vfslog_string(pVfs, zMsg);
+  return SQLITE_OK;
+}
+
+static const char *vfslog_eventname(int eEvent){
+  const char *zEvent = 0;
+
+  switch( eEvent ){
+    case OS_CLOSE:             zEvent = "xClose"; break;
+    case OS_READ:              zEvent = "xRead"; break;
+    case OS_WRITE:             zEvent = "xWrite"; break;
+    case OS_TRUNCATE:          zEvent = "xTruncate"; break;
+    case OS_SYNC:              zEvent = "xSync"; break;
+    case OS_FILESIZE:          zEvent = "xFilesize"; break;
+    case OS_LOCK:              zEvent = "xLock"; break;
+    case OS_UNLOCK:            zEvent = "xUnlock"; break;
+    case OS_CHECKRESERVEDLOCK: zEvent = "xCheckResLock"; break;
+    case OS_FILECONTROL:       zEvent = "xFileControl"; break;
+    case OS_SECTORSIZE:        zEvent = "xSectorSize"; break;
+    case OS_DEVCHAR:           zEvent = "xDeviceChar"; break;
+    case OS_OPEN:              zEvent = "xOpen"; break;
+    case OS_DELETE:            zEvent = "xDelete"; break;
+    case OS_ACCESS:            zEvent = "xAccess"; break;
+    case OS_FULLPATHNAME:      zEvent = "xFullPathname"; break;
+    case OS_RANDOMNESS:        zEvent = "xRandomness"; break;
+    case OS_SLEEP:             zEvent = "xSleep"; break;
+    case OS_CURRENTTIME:       zEvent = "xCurrentTime"; break;
+
+    case OS_SHMUNMAP:          zEvent = "xShmUnmap"; break;
+    case OS_SHMLOCK:           zEvent = "xShmLock"; break;
+    case OS_SHMBARRIER:        zEvent = "xShmBarrier"; break;
+    case OS_SHMMAP:            zEvent = "xShmMap"; break;
+
+    case OS_ANNOTATE:          zEvent = "annotation"; break;
+  }
+
+  return zEvent;
+}
+
+typedef struct VfslogVtab VfslogVtab;
+typedef struct VfslogCsr VfslogCsr;
+
+/*
+** Virtual table type for the vfslog reader module.
+*/
+struct VfslogVtab {
+  sqlite3_vtab base;              /* Base class */
+  sqlite3_file *pFd;              /* File descriptor open on vfslog file */
+  sqlite3_int64 nByte;            /* Size of file in bytes */
+  char *zFile;                    /* File name for pFd */
+};
+
+/*
+** Virtual table cursor type for the vfslog reader module.
+*/
+struct VfslogCsr {
+  sqlite3_vtab_cursor base;       /* Base class */
+  sqlite3_int64 iRowid;           /* Current rowid. */
+  sqlite3_int64 iOffset;          /* Offset of next record in file */
+  char *zTransient;               /* Transient 'file' string */
+  int nFile;                      /* Size of array azFile[] */
+  char **azFile;                  /* File strings */
+  unsigned char aBuf[1024];       /* Current vfs log entry (read from file) */
+};
+
+static unsigned int get32bits(unsigned char *p){
+  return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3];
+}
+
+/*
+** The argument must point to a buffer containing a nul-terminated string.
+** If the string begins with an SQL quote character it is overwritten by
+** the dequoted version. Otherwise the buffer is left unmodified.
+*/
+static void dequote(char *z){
+  char quote;                     /* Quote character (if any ) */
+  quote = z[0];
+  if( quote=='[' || quote=='\'' || quote=='"' || quote=='`' ){
+    int iIn = 1;                  /* Index of next byte to read from input */
+    int iOut = 0;                 /* Index of next byte to write to output */
+    if( quote=='[' ) quote = ']';  
+    while( z[iIn] ){
+      if( z[iIn]==quote ){
+        if( z[iIn+1]!=quote ) break;
+        z[iOut++] = quote;
+        iIn += 2;
+      }else{
+        z[iOut++] = z[iIn++];
+      }
+    }
+    z[iOut] = '\0';
+  }
+}
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/*
+** Connect to or create a vfslog virtual table.
+*/
+static int vlogConnect(
+  sqlite3 *db,
+  void *pAux,
+  int argc, const char *const*argv,
+  sqlite3_vtab **ppVtab,
+  char **pzErr
+){
+  sqlite3_vfs *pVfs;              /* VFS used to read log file */
+  int flags;                      /* flags passed to pVfs->xOpen() */
+  VfslogVtab *p;
+  int rc;
+  int nByte;
+  char *zFile;
+
+  *ppVtab = 0;
+  pVfs = sqlite3_vfs_find(0);
+  nByte = sizeof(VfslogVtab) + pVfs->szOsFile + pVfs->mxPathname;
+  p = sqlite3_malloc(nByte);
+  if( p==0 ) return SQLITE_NOMEM;
+  memset(p, 0, nByte);
+
+  p->pFd = (sqlite3_file *)&p[1];
+  p->zFile = &((char *)p->pFd)[pVfs->szOsFile];
+
+  zFile = sqlite3_mprintf("%s", argv[3]);
+  if( !zFile ){
+    sqlite3_free(p);
+    return SQLITE_NOMEM;
+  }
+  dequote(zFile);
+  pVfs->xFullPathname(pVfs, zFile, pVfs->mxPathname, p->zFile);
+  sqlite3_free(zFile);
+
+  flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_MASTER_JOURNAL;
+  rc = pVfs->xOpen(pVfs, p->zFile, p->pFd, flags, &flags);
+
+  if( rc==SQLITE_OK ){
+    p->pFd->pMethods->xFileSize(p->pFd, &p->nByte);
+    sqlite3_declare_vtab(db, 
+        "CREATE TABLE xxx(event, file, click, rc, size, offset)"
+    );
+    *ppVtab = &p->base;
+  }else{
+    sqlite3_free(p);
+  }
+
+  return rc;
+}
+
+/*
+** There is no "best-index". This virtual table always does a linear
+** scan of the binary VFS log file.
+*/
+static int vlogBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+  pIdxInfo->estimatedCost = 10.0;
+  return SQLITE_OK;
+}
+
+/*
+** Disconnect from or destroy a vfslog virtual table.
+*/
+static int vlogDisconnect(sqlite3_vtab *pVtab){
+  VfslogVtab *p = (VfslogVtab *)pVtab;
+  if( p->pFd->pMethods ){
+    p->pFd->pMethods->xClose(p->pFd);
+    p->pFd->pMethods = 0;
+  }
+  sqlite3_free(p);
+  return SQLITE_OK;
+}
+
+/*
+** Open a new vfslog cursor.
+*/
+static int vlogOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+  VfslogCsr *pCsr;                /* Newly allocated cursor object */
+
+  pCsr = sqlite3_malloc(sizeof(VfslogCsr));
+  if( !pCsr ) return SQLITE_NOMEM;
+  memset(pCsr, 0, sizeof(VfslogCsr));
+  *ppCursor = &pCsr->base;
+  return SQLITE_OK;
+}
+
+/*
+** Close a vfslog cursor.
+*/
+static int vlogClose(sqlite3_vtab_cursor *pCursor){
+  VfslogCsr *p = (VfslogCsr *)pCursor;
+  int i;
+  for(i=0; i<p->nFile; i++){
+    sqlite3_free(p->azFile[i]);
+  }
+  sqlite3_free(p->azFile);
+  sqlite3_free(p->zTransient);
+  sqlite3_free(p);
+  return SQLITE_OK;
+}
+
+/*
+** Move a vfslog cursor to the next entry in the file.
+*/
+static int vlogNext(sqlite3_vtab_cursor *pCursor){
+  VfslogCsr *pCsr = (VfslogCsr *)pCursor;
+  VfslogVtab *p = (VfslogVtab *)pCursor->pVtab;
+  int rc = SQLITE_OK;
+  int nRead;
+
+  sqlite3_free(pCsr->zTransient);
+  pCsr->zTransient = 0;
+
+  nRead = 24;
+  if( pCsr->iOffset+nRead<=p->nByte ){
+    int eEvent;
+    rc = p->pFd->pMethods->xRead(p->pFd, pCsr->aBuf, nRead, pCsr->iOffset);
+
+    eEvent = get32bits(pCsr->aBuf);
+    if( (rc==SQLITE_OK)
+     && (eEvent==OS_OPEN || eEvent==OS_DELETE || eEvent==OS_ACCESS) 
+    ){
+      char buf[4];
+      rc = p->pFd->pMethods->xRead(p->pFd, buf, 4, pCsr->iOffset+nRead);
+      nRead += 4;
+      if( rc==SQLITE_OK ){
+        int nStr = get32bits((unsigned char *)buf);
+        char *zStr = sqlite3_malloc(nStr+1);
+        rc = p->pFd->pMethods->xRead(p->pFd, zStr, nStr, pCsr->iOffset+nRead);
+        zStr[nStr] = '\0';
+        nRead += nStr;
+
+        if( eEvent==OS_OPEN ){
+          int iFileid = get32bits(&pCsr->aBuf[4]);
+          if( iFileid>=pCsr->nFile ){
+            int nNew = sizeof(pCsr->azFile[0])*(iFileid+1);
+            pCsr->azFile = (char **)sqlite3_realloc(pCsr->azFile, nNew);
+            nNew -= sizeof(pCsr->azFile[0])*pCsr->nFile;
+            memset(&pCsr->azFile[pCsr->nFile], 0, nNew);
+            pCsr->nFile = iFileid+1;
+          }
+          sqlite3_free(pCsr->azFile[iFileid]);
+          pCsr->azFile[iFileid] = zStr;
+        }else{
+          pCsr->zTransient = zStr;
+        }
+      }
+    }
+  }
+
+  pCsr->iRowid += 1;
+  pCsr->iOffset += nRead;
+  return rc;
+}
+
+static int vlogEof(sqlite3_vtab_cursor *pCursor){
+  VfslogCsr *pCsr = (VfslogCsr *)pCursor;
+  VfslogVtab *p = (VfslogVtab *)pCursor->pVtab;
+  return (pCsr->iOffset>=p->nByte);
+}
+
+static int vlogFilter(
+  sqlite3_vtab_cursor *pCursor, 
+  int idxNum, const char *idxStr,
+  int argc, sqlite3_value **argv
+){
+  VfslogCsr *pCsr = (VfslogCsr *)pCursor;
+  pCsr->iRowid = 0;
+  pCsr->iOffset = 20;
+  return vlogNext(pCursor);
+}
+
+static int vlogColumn(
+  sqlite3_vtab_cursor *pCursor, 
+  sqlite3_context *ctx, 
+  int i
+){
+  unsigned int val;
+  VfslogCsr *pCsr = (VfslogCsr *)pCursor;
+
+  assert( i<7 );
+  val = get32bits(&pCsr->aBuf[4*i]);
+
+  switch( i ){
+    case 0: {
+      sqlite3_result_text(ctx, vfslog_eventname(val), -1, SQLITE_STATIC);
+      break;
+    }
+    case 1: {
+      char *zStr = pCsr->zTransient;
+      if( val!=0 && val<(unsigned)pCsr->nFile ){
+        zStr = pCsr->azFile[val];
+      }
+      sqlite3_result_text(ctx, zStr, -1, SQLITE_TRANSIENT);
+      break;
+    }
+    default:
+      sqlite3_result_int(ctx, val);
+      break;
+  }
+
+  return SQLITE_OK;
+}
+
+static int vlogRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+  VfslogCsr *pCsr = (VfslogCsr *)pCursor;
+  *pRowid = pCsr->iRowid;
+  return SQLITE_OK;
+}
+
+int sqlite3_vfslog_register(sqlite3 *db){
+  static sqlite3_module vfslog_module = {
+    0,                            /* iVersion */
+    vlogConnect,                /* xCreate */
+    vlogConnect,                /* xConnect */
+    vlogBestIndex,              /* xBestIndex */
+    vlogDisconnect,             /* xDisconnect */
+    vlogDisconnect,             /* xDestroy */
+    vlogOpen,                   /* xOpen - open a cursor */
+    vlogClose,                  /* xClose - close a cursor */
+    vlogFilter,                 /* xFilter - configure scan constraints */
+    vlogNext,                   /* xNext - advance a cursor */
+    vlogEof,                    /* xEof - check for end of scan */
+    vlogColumn,                 /* xColumn - read data */
+    vlogRowid,                  /* xRowid - read data */
+    0,                            /* xUpdate */
+    0,                            /* xBegin */
+    0,                            /* xSync */
+    0,                            /* xCommit */
+    0,                            /* xRollback */
+    0,                            /* xFindMethod */
+    0,                            /* xRename */
+  };
+
+  sqlite3_create_module(db, "vfslog", &vfslog_module, 0);
+  return SQLITE_OK;
+}
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+/**************************************************************************
+***************************************************************************
+** Tcl interface starts here.
+*/
+
+#if defined(SQLITE_TEST) || defined(TCLSH)
+
+#include <tcl.h>
+
+static int test_vfslog(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  struct SqliteDb { sqlite3 *db; };
+  sqlite3 *db;
+  Tcl_CmdInfo cmdInfo;
+  int rc = SQLITE_ERROR;
+
+  static const char *strs[] = { "annotate", "finalize", "new",  "register", 0 };
+  enum VL_enum { VL_ANNOTATE, VL_FINALIZE, VL_NEW, VL_REGISTER };
+  int iSub;
+
+  if( objc<2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ...");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIndexFromObj(interp, objv[1], strs, "sub-command", 0, &iSub) ){
+    return TCL_ERROR;
+  }
+
+  switch( (enum VL_enum)iSub ){
+    case VL_ANNOTATE: {
+      int rc;
+      char *zVfs;
+      char *zMsg;
+      if( objc!=4 ){
+        Tcl_WrongNumArgs(interp, 3, objv, "VFS");
+        return TCL_ERROR;
+      }
+      zVfs = Tcl_GetString(objv[2]);
+      zMsg = Tcl_GetString(objv[3]);
+      rc = sqlite3_vfslog_annotate(zVfs, zMsg);
+      if( rc!=SQLITE_OK ){
+        Tcl_AppendResult(interp, "failed", 0);
+        return TCL_ERROR;
+      }
+      break;
+    }
+    case VL_FINALIZE: {
+      int rc;
+      char *zVfs;
+      if( objc!=3 ){
+        Tcl_WrongNumArgs(interp, 2, objv, "VFS");
+        return TCL_ERROR;
+      }
+      zVfs = Tcl_GetString(objv[2]);
+      rc = sqlite3_vfslog_finalize(zVfs);
+      if( rc!=SQLITE_OK ){
+        Tcl_AppendResult(interp, "failed", 0);
+        return TCL_ERROR;
+      }
+      break;
+    };
+
+    case VL_NEW: {
+      int rc;
+      char *zVfs;
+      char *zParent;
+      char *zLog;
+      if( objc!=5 ){
+        Tcl_WrongNumArgs(interp, 2, objv, "VFS PARENT LOGFILE");
+        return TCL_ERROR;
+      }
+      zVfs = Tcl_GetString(objv[2]);
+      zParent = Tcl_GetString(objv[3]);
+      zLog = Tcl_GetString(objv[4]);
+      if( *zParent=='\0' ) zParent = 0;
+      rc = sqlite3_vfslog_new(zVfs, zParent, zLog);
+      if( rc!=SQLITE_OK ){
+        Tcl_AppendResult(interp, "failed", 0);
+        return TCL_ERROR;
+      }
+      break;
+    };
+
+    case VL_REGISTER: {
+      char *zDb;
+      if( objc!=3 ){
+        Tcl_WrongNumArgs(interp, 2, objv, "DB");
+        return TCL_ERROR;
+      }
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+      Tcl_AppendResult(interp, "vfslog not available because of "
+                               "SQLITE_OMIT_VIRTUALTABLE", (void*)0);
+      return TCL_ERROR;
+#else
+      zDb = Tcl_GetString(objv[2]);
+      if( Tcl_GetCommandInfo(interp, zDb, &cmdInfo) ){
+        db = ((struct SqliteDb*)cmdInfo.objClientData)->db;
+        rc = sqlite3_vfslog_register(db);
+      }
+      if( rc!=SQLITE_OK ){
+        Tcl_AppendResult(interp, "bad sqlite3 handle: ", zDb, (void*)0);
+        return TCL_ERROR;
+      }
+      break;
+#endif
+    }
+  }
+
+  return TCL_OK;
+}
+
+int SqlitetestOsinst_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "vfslog", test_vfslog, 0, 0);
+  return TCL_OK;
+}
+
+#endif /* SQLITE_TEST */

+ 467 - 0
components/external/sqlite/test/test_pcache.c

@@ -0,0 +1,467 @@
+/*
+** 2008 November 18
+**
+** 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 contains code used for testing the SQLite system.
+** None of the code in this file goes into a deliverable build.
+** 
+** This file contains an application-defined pager cache
+** implementation that can be plugged in in place of the
+** default pcache.  This alternative pager cache will throw
+** some errors that the default cache does not.
+**
+** This pagecache implementation is designed for simplicity
+** not speed.  
+*/
+#include "sqlite3.h"
+#include <string.h>
+#include <assert.h>
+
+/*
+** Global data used by this test implementation.  There is no
+** mutexing, which means this page cache will not work in a
+** multi-threaded test.
+*/
+typedef struct testpcacheGlobalType testpcacheGlobalType;
+struct testpcacheGlobalType {
+  void *pDummy;             /* Dummy allocation to simulate failures */
+  int nInstance;            /* Number of current instances */
+  unsigned discardChance;   /* Chance of discarding on an unpin (0-100) */
+  unsigned prngSeed;        /* Seed for the PRNG */
+  unsigned highStress;      /* Call xStress agressively */
+};
+static testpcacheGlobalType testpcacheGlobal;
+
+/*
+** Initializer.
+**
+** Verify that the initializer is only called when the system is
+** uninitialized.  Allocate some memory and report SQLITE_NOMEM if
+** the allocation fails.  This provides a means to test the recovery
+** from a failed initialization attempt.  It also verifies that the
+** the destructor always gets call - otherwise there would be a
+** memory leak.
+*/
+static int testpcacheInit(void *pArg){
+  assert( pArg==(void*)&testpcacheGlobal );
+  assert( testpcacheGlobal.pDummy==0 );
+  assert( testpcacheGlobal.nInstance==0 );
+  testpcacheGlobal.pDummy = sqlite3_malloc(10);
+  return testpcacheGlobal.pDummy==0 ? SQLITE_NOMEM : SQLITE_OK;
+}
+
+/*
+** Destructor
+**
+** Verify that this is only called after initialization.
+** Free the memory allocated by the initializer.
+*/
+static void testpcacheShutdown(void *pArg){
+  assert( pArg==(void*)&testpcacheGlobal );
+  assert( testpcacheGlobal.pDummy!=0 );
+  assert( testpcacheGlobal.nInstance==0 );
+  sqlite3_free( testpcacheGlobal.pDummy );
+  testpcacheGlobal.pDummy = 0;
+}
+
+/*
+** Number of pages in a cache.
+**
+** The number of pages is a hard upper bound in this test module.
+** If more pages are requested, sqlite3PcacheFetch() returns NULL.
+**
+** If testing with in-memory temp tables, provide a larger pcache.
+** Some of the test cases need this.
+*/
+#if defined(SQLITE_TEMP_STORE) && SQLITE_TEMP_STORE>=2
+# define TESTPCACHE_NPAGE    499
+#else
+# define TESTPCACHE_NPAGE    217
+#endif
+#define TESTPCACHE_RESERVE   17
+
+/*
+** Magic numbers used to determine validity of the page cache.
+*/
+#define TESTPCACHE_VALID  0x364585fd
+#define TESTPCACHE_CLEAR  0xd42670d4
+
+/*
+** Private implementation of a page cache.
+*/
+typedef struct testpcache testpcache;
+struct testpcache {
+  int szPage;               /* Size of each page.  Multiple of 8. */
+  int szExtra;              /* Size of extra data that accompanies each page */
+  int bPurgeable;           /* True if the page cache is purgeable */
+  int nFree;                /* Number of unused slots in a[] */
+  int nPinned;              /* Number of pinned slots in a[] */
+  unsigned iRand;           /* State of the PRNG */
+  unsigned iMagic;          /* Magic number for sanity checking */
+  struct testpcachePage {
+    sqlite3_pcache_page page;  /* Base class */
+    unsigned key;              /* The key for this page. 0 means unallocated */
+    int isPinned;              /* True if the page is pinned */
+  } a[TESTPCACHE_NPAGE];    /* All pages in the cache */
+};
+
+/*
+** Get a random number using the PRNG in the given page cache.
+*/
+static unsigned testpcacheRandom(testpcache *p){
+  unsigned x = 0;
+  int i;
+  for(i=0; i<4; i++){
+    p->iRand = (p->iRand*69069 + 5);
+    x = (x<<8) | ((p->iRand>>16)&0xff);
+  }
+  return x;
+}
+
+
+/*
+** Allocate a new page cache instance.
+*/
+static sqlite3_pcache *testpcacheCreate(
+  int szPage, 
+  int szExtra, 
+  int bPurgeable
+){
+  int nMem;
+  char *x;
+  testpcache *p;
+  int i;
+  assert( testpcacheGlobal.pDummy!=0 );
+  szPage = (szPage+7)&~7;
+  nMem = sizeof(testpcache) + TESTPCACHE_NPAGE*(szPage+szExtra);
+  p = sqlite3_malloc( nMem );
+  if( p==0 ) return 0;
+  x = (char*)&p[1];
+  p->szPage = szPage;
+  p->szExtra = szExtra;
+  p->nFree = TESTPCACHE_NPAGE;
+  p->nPinned = 0;
+  p->iRand = testpcacheGlobal.prngSeed;
+  p->bPurgeable = bPurgeable;
+  p->iMagic = TESTPCACHE_VALID;
+  for(i=0; i<TESTPCACHE_NPAGE; i++, x += (szPage+szExtra)){
+    p->a[i].key = 0;
+    p->a[i].isPinned = 0;
+    p->a[i].page.pBuf = (void*)x;
+    p->a[i].page.pExtra = (void*)&x[szPage];
+  }
+  testpcacheGlobal.nInstance++;
+  return (sqlite3_pcache*)p;
+}
+
+/*
+** Set the cache size
+*/
+static void testpcacheCachesize(sqlite3_pcache *pCache, int newSize){
+  testpcache *p = (testpcache*)pCache;
+  assert( p->iMagic==TESTPCACHE_VALID );
+  assert( testpcacheGlobal.pDummy!=0 );
+  assert( testpcacheGlobal.nInstance>0 );
+}
+
+/*
+** Return the number of pages in the cache that are being used.
+** This includes both pinned and unpinned pages.
+*/
+static int testpcachePagecount(sqlite3_pcache *pCache){
+  testpcache *p = (testpcache*)pCache;
+  assert( p->iMagic==TESTPCACHE_VALID );
+  assert( testpcacheGlobal.pDummy!=0 );
+  assert( testpcacheGlobal.nInstance>0 );
+  return TESTPCACHE_NPAGE - p->nFree;
+}
+
+/*
+** Fetch a page.
+*/
+static sqlite3_pcache_page *testpcacheFetch(
+  sqlite3_pcache *pCache,
+  unsigned key,
+  int createFlag
+){
+  testpcache *p = (testpcache*)pCache;
+  int i, j;
+  assert( p->iMagic==TESTPCACHE_VALID );
+  assert( testpcacheGlobal.pDummy!=0 );
+  assert( testpcacheGlobal.nInstance>0 );
+
+  /* See if the page is already in cache.  Return immediately if it is */
+  for(i=0; i<TESTPCACHE_NPAGE; i++){
+    if( p->a[i].key==key ){
+      if( !p->a[i].isPinned ){
+        p->nPinned++;
+        assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
+        p->a[i].isPinned = 1;
+      }
+      return &p->a[i].page;
+    }
+  }
+
+  /* If createFlag is 0, never allocate a new page */
+  if( createFlag==0 ){
+    return 0;
+  }
+
+  /* If no pages are available, always fail */
+  if( p->nPinned==TESTPCACHE_NPAGE ){
+    return 0;
+  }
+
+  /* Do not allocate the last TESTPCACHE_RESERVE pages unless createFlag is 2 */
+  if( p->nPinned>=TESTPCACHE_NPAGE-TESTPCACHE_RESERVE && createFlag<2 ){
+    return 0;
+  }
+
+  /* Do not allocate if highStress is enabled and createFlag is not 2.  
+  **
+  ** The highStress setting causes pagerStress() to be called much more
+  ** often, which exercises the pager logic more intensely.
+  */
+  if( testpcacheGlobal.highStress && createFlag<2 ){
+    return 0;
+  }
+
+  /* Find a free page to allocate if there are any free pages.
+  ** Withhold TESTPCACHE_RESERVE free pages until createFlag is 2.
+  */
+  if( p->nFree>TESTPCACHE_RESERVE || (createFlag==2 && p->nFree>0) ){
+    j = testpcacheRandom(p) % TESTPCACHE_NPAGE;
+    for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){
+      if( p->a[j].key==0 ){
+        p->a[j].key = key;
+        p->a[j].isPinned = 1;
+        memset(p->a[j].page.pBuf, 0, p->szPage);
+        memset(p->a[j].page.pExtra, 0, p->szExtra);
+        p->nPinned++;
+        p->nFree--;
+        assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
+        return &p->a[j].page;
+      }
+    }
+
+    /* The prior loop always finds a freepage to allocate */
+    assert( 0 );
+  }
+
+  /* If this cache is not purgeable then we have to fail.
+  */
+  if( p->bPurgeable==0 ){
+    return 0;
+  }
+
+  /* If there are no free pages, recycle a page.  The page to
+  ** recycle is selected at random from all unpinned pages.
+  */
+  j = testpcacheRandom(p) % TESTPCACHE_NPAGE;
+  for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){
+    if( p->a[j].key>0 && p->a[j].isPinned==0 ){
+      p->a[j].key = key;
+      p->a[j].isPinned = 1;
+      memset(p->a[j].page.pBuf, 0, p->szPage);
+      memset(p->a[j].page.pExtra, 0, p->szExtra);
+      p->nPinned++;
+      assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
+      return &p->a[j].page;
+    }
+  }
+
+  /* The previous loop always finds a page to recycle. */
+  assert(0);
+  return 0;
+}
+
+/*
+** Unpin a page.
+*/
+static void testpcacheUnpin(
+  sqlite3_pcache *pCache,
+  sqlite3_pcache_page *pOldPage,
+  int discard
+){
+  testpcache *p = (testpcache*)pCache;
+  int i;
+  assert( p->iMagic==TESTPCACHE_VALID );
+  assert( testpcacheGlobal.pDummy!=0 );
+  assert( testpcacheGlobal.nInstance>0 );
+
+  /* Randomly discard pages as they are unpinned according to the
+  ** discardChance setting.  If discardChance is 0, the random discard
+  ** never happens.  If discardChance is 100, it always happens.
+  */
+  if( p->bPurgeable
+  && (100-testpcacheGlobal.discardChance) <= (testpcacheRandom(p)%100)
+  ){
+    discard = 1;
+  }
+
+  for(i=0; i<TESTPCACHE_NPAGE; i++){
+    if( &p->a[i].page==pOldPage ){
+      /* The pOldPage pointer always points to a pinned page */
+      assert( p->a[i].isPinned );
+      p->a[i].isPinned = 0;
+      p->nPinned--;
+      assert( p->nPinned>=0 );
+      if( discard ){
+        p->a[i].key = 0;
+        p->nFree++;
+        assert( p->nFree<=TESTPCACHE_NPAGE );
+      }
+      return;
+    }
+  }
+
+  /* The pOldPage pointer always points to a valid page */
+  assert( 0 );
+}
+
+
+/*
+** Rekey a single page.
+*/
+static void testpcacheRekey(
+  sqlite3_pcache *pCache,
+  sqlite3_pcache_page *pOldPage,
+  unsigned oldKey,
+  unsigned newKey
+){
+  testpcache *p = (testpcache*)pCache;
+  int i;
+  assert( p->iMagic==TESTPCACHE_VALID );
+  assert( testpcacheGlobal.pDummy!=0 );
+  assert( testpcacheGlobal.nInstance>0 );
+
+  /* If there already exists another page at newKey, verify that
+  ** the other page is unpinned and discard it.
+  */
+  for(i=0; i<TESTPCACHE_NPAGE; i++){
+    if( p->a[i].key==newKey ){
+      /* The new key is never a page that is already pinned */
+      assert( p->a[i].isPinned==0 );
+      p->a[i].key = 0;
+      p->nFree++;
+      assert( p->nFree<=TESTPCACHE_NPAGE );
+      break;
+    }
+  }
+
+  /* Find the page to be rekeyed and rekey it.
+  */
+  for(i=0; i<TESTPCACHE_NPAGE; i++){
+    if( p->a[i].key==oldKey ){
+      /* The oldKey and pOldPage parameters match */
+      assert( &p->a[i].page==pOldPage );
+      /* Page to be rekeyed must be pinned */
+      assert( p->a[i].isPinned );
+      p->a[i].key = newKey;
+      return;
+    }
+  }
+
+  /* Rekey is always given a valid page to work with */
+  assert( 0 );
+}
+
+
+/*
+** Truncate the page cache.  Every page with a key of iLimit or larger
+** is discarded.
+*/
+static void testpcacheTruncate(sqlite3_pcache *pCache, unsigned iLimit){
+  testpcache *p = (testpcache*)pCache;
+  unsigned int i;
+  assert( p->iMagic==TESTPCACHE_VALID );
+  assert( testpcacheGlobal.pDummy!=0 );
+  assert( testpcacheGlobal.nInstance>0 );
+  for(i=0; i<TESTPCACHE_NPAGE; i++){
+    if( p->a[i].key>=iLimit ){
+      p->a[i].key = 0;
+      if( p->a[i].isPinned ){
+        p->nPinned--;
+        assert( p->nPinned>=0 );
+      }
+      p->nFree++;
+      assert( p->nFree<=TESTPCACHE_NPAGE );
+    }
+  }
+}
+
+/*
+** Destroy a page cache.
+*/
+static void testpcacheDestroy(sqlite3_pcache *pCache){
+  testpcache *p = (testpcache*)pCache;
+  assert( p->iMagic==TESTPCACHE_VALID );
+  assert( testpcacheGlobal.pDummy!=0 );
+  assert( testpcacheGlobal.nInstance>0 );
+  p->iMagic = TESTPCACHE_CLEAR;
+  sqlite3_free(p);
+  testpcacheGlobal.nInstance--;
+}
+
+
+/*
+** Invoke this routine to register or unregister the testing pager cache
+** implemented by this file.
+**
+** Install the test pager cache if installFlag is 1 and uninstall it if
+** installFlag is 0.
+**
+** When installing, discardChance is a number between 0 and 100 that
+** indicates the probability of discarding a page when unpinning the
+** page.  0 means never discard (unless the discard flag is set).
+** 100 means always discard.
+*/
+void installTestPCache(
+  int installFlag,            /* True to install.  False to uninstall. */
+  unsigned discardChance,     /* 0-100.  Chance to discard on unpin */
+  unsigned prngSeed,          /* Seed for the PRNG */
+  unsigned highStress         /* Call xStress agressively */
+){
+  static const sqlite3_pcache_methods2 testPcache = {
+    1,
+    (void*)&testpcacheGlobal,
+    testpcacheInit,
+    testpcacheShutdown,
+    testpcacheCreate,
+    testpcacheCachesize,
+    testpcachePagecount,
+    testpcacheFetch,
+    testpcacheUnpin,
+    testpcacheRekey,
+    testpcacheTruncate,
+    testpcacheDestroy,
+  };
+  static sqlite3_pcache_methods2 defaultPcache;
+  static int isInstalled = 0;
+
+  assert( testpcacheGlobal.nInstance==0 );
+  assert( testpcacheGlobal.pDummy==0 );
+  assert( discardChance<=100 );
+  testpcacheGlobal.discardChance = discardChance;
+  testpcacheGlobal.prngSeed = prngSeed ^ (prngSeed<<16);
+  testpcacheGlobal.highStress = highStress;
+  if( installFlag!=isInstalled ){
+    if( installFlag ){
+      sqlite3_config(SQLITE_CONFIG_GETPCACHE2, &defaultPcache);
+      assert( defaultPcache.xCreate!=testpcacheCreate );
+      sqlite3_config(SQLITE_CONFIG_PCACHE2, &testPcache);
+    }else{
+      assert( defaultPcache.xCreate!=0 );
+      sqlite3_config(SQLITE_CONFIG_PCACHE2, &defaultPcache);
+    }
+    isInstalled = installFlag;
+  }
+}

+ 2008 - 0
components/external/sqlite/test/test_quota.c

@@ -0,0 +1,2008 @@
+/*
+** 2010 September 31
+**
+** 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 contains a VFS "shim" - a layer that sits in between the
+** pager and the real VFS.
+**
+** This particular shim enforces a quota system on files.  One or more
+** database files are in a "quota group" that is defined by a GLOB
+** pattern.  A quota is set for the combined size of all files in the
+** the group.  A quota of zero means "no limit".  If the total size
+** of all files in the quota group is greater than the limit, then
+** write requests that attempt to enlarge a file fail with SQLITE_FULL.
+**
+** However, before returning SQLITE_FULL, the write requests invoke
+** a callback function that is configurable for each quota group.
+** This callback has the opportunity to enlarge the quota.  If the
+** callback does enlarge the quota such that the total size of all
+** files within the group is less than the new quota, then the write
+** continues as if nothing had happened.
+*/
+#include "test_quota.h"
+#include <string.h>
+#include <assert.h>
+
+/*
+** For an build without mutexes, no-op the mutex calls.
+*/
+#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
+#define sqlite3_mutex_alloc(X)    ((sqlite3_mutex*)8)
+#define sqlite3_mutex_free(X)
+#define sqlite3_mutex_enter(X)
+#define sqlite3_mutex_try(X)      SQLITE_OK
+#define sqlite3_mutex_leave(X)
+#define sqlite3_mutex_held(X)     ((void)(X),1)
+#define sqlite3_mutex_notheld(X)  ((void)(X),1)
+#endif /* SQLITE_THREADSAFE==0 */
+
+
+/*
+** Figure out if we are dealing with Unix, Windows, or some other
+** operating system.  After the following block of preprocess macros,
+** all of SQLITE_OS_UNIX, SQLITE_OS_WIN, and SQLITE_OS_OTHER 
+** will defined to either 1 or 0.  One of the four will be 1.  The other 
+** three will be 0.
+*/
+#if defined(SQLITE_OS_OTHER)
+# if SQLITE_OS_OTHER==1
+#   undef SQLITE_OS_UNIX
+#   define SQLITE_OS_UNIX 0
+#   undef SQLITE_OS_WIN
+#   define SQLITE_OS_WIN 0
+# else
+#   undef SQLITE_OS_OTHER
+# endif
+#endif
+#if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER)
+# define SQLITE_OS_OTHER 0
+# ifndef SQLITE_OS_WIN
+#   if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) \
+                       || defined(__MINGW32__) || defined(__BORLANDC__)
+#     define SQLITE_OS_WIN 1
+#     define SQLITE_OS_UNIX 0
+#   else
+#     define SQLITE_OS_WIN 0
+#     define SQLITE_OS_UNIX 1
+#  endif
+# else
+#  define SQLITE_OS_UNIX 0
+# endif
+#else
+# ifndef SQLITE_OS_WIN
+#  define SQLITE_OS_WIN 0
+# endif
+#endif
+
+#if SQLITE_OS_UNIX
+# include <unistd.h>
+#endif
+#if SQLITE_OS_WIN
+# include <windows.h>
+# include <io.h>
+#endif
+
+
+/************************ Object Definitions ******************************/
+
+/* Forward declaration of all object types */
+typedef struct quotaGroup quotaGroup;
+typedef struct quotaConn quotaConn;
+typedef struct quotaFile quotaFile;
+
+/*
+** A "quota group" is a collection of files whose collective size we want
+** to limit.  Each quota group is defined by a GLOB pattern.
+**
+** There is an instance of the following object for each defined quota
+** group.  This object records the GLOB pattern that defines which files
+** belong to the quota group.  The object also remembers the size limit
+** for the group (the quota) and the callback to be invoked when the
+** sum of the sizes of the files within the group goes over the limit.
+**
+** A quota group must be established (using sqlite3_quota_set(...))
+** prior to opening any of the database connections that access files
+** within the quota group.
+*/
+struct quotaGroup {
+  const char *zPattern;          /* Filename pattern to be quotaed */
+  sqlite3_int64 iLimit;          /* Upper bound on total file size */
+  sqlite3_int64 iSize;           /* Current size of all files */
+  void (*xCallback)(             /* Callback invoked when going over quota */
+     const char *zFilename,         /* Name of file whose size increases */
+     sqlite3_int64 *piLimit,        /* IN/OUT: The current limit */
+     sqlite3_int64 iSize,           /* Total size of all files in the group */
+     void *pArg                     /* Client data */
+  );
+  void *pArg;                    /* Third argument to the xCallback() */
+  void (*xDestroy)(void*);       /* Optional destructor for pArg */
+  quotaGroup *pNext, **ppPrev;   /* Doubly linked list of all quota objects */
+  quotaFile *pFiles;             /* Files within this group */
+};
+
+/*
+** An instance of this structure represents a single file that is part
+** of a quota group.  A single file can be opened multiple times.  In
+** order keep multiple openings of the same file from causing the size
+** of the file to count against the quota multiple times, each file
+** has a unique instance of this object and multiple open connections
+** to the same file each point to a single instance of this object.
+*/
+struct quotaFile {
+  char *zFilename;                /* Name of this file */
+  quotaGroup *pGroup;             /* Quota group to which this file belongs */
+  sqlite3_int64 iSize;            /* Current size of this file */
+  int nRef;                       /* Number of times this file is open */
+  int deleteOnClose;              /* True to delete this file when it closes */
+  quotaFile *pNext, **ppPrev;     /* Linked list of files in the same group */
+};
+
+/*
+** An instance of the following object represents each open connection
+** to a file that participates in quota tracking.  This object is a 
+** subclass of sqlite3_file.  The sqlite3_file object for the underlying
+** VFS is appended to this structure.
+*/
+struct quotaConn {
+  sqlite3_file base;              /* Base class - must be first */
+  quotaFile *pFile;               /* The underlying file */
+  /* The underlying VFS sqlite3_file is appended to this object */
+};
+
+/*
+** An instance of the following object records the state of an
+** open file.  This object is opaque to all users - the internal
+** structure is only visible to the functions below.
+*/
+struct quota_FILE {
+  FILE *f;                /* Open stdio file pointer */
+  sqlite3_int64 iOfst;    /* Current offset into the file */
+  quotaFile *pFile;       /* The file record in the quota system */
+#if SQLITE_OS_WIN
+  char *zMbcsName;        /* Full MBCS pathname of the file */
+#endif
+};
+
+
+/************************* Global Variables **********************************/
+/*
+** All global variables used by this file are containing within the following
+** gQuota structure.
+*/
+static struct {
+  /* The pOrigVfs is the real, original underlying VFS implementation.
+  ** Most operations pass-through to the real VFS.  This value is read-only
+  ** during operation.  It is only modified at start-time and thus does not
+  ** require a mutex.
+  */
+  sqlite3_vfs *pOrigVfs;
+
+  /* The sThisVfs is the VFS structure used by this shim.  It is initialized
+  ** at start-time and thus does not require a mutex
+  */
+  sqlite3_vfs sThisVfs;
+
+  /* The sIoMethods defines the methods used by sqlite3_file objects 
+  ** associated with this shim.  It is initialized at start-time and does
+  ** not require a mutex.
+  **
+  ** When the underlying VFS is called to open a file, it might return 
+  ** either a version 1 or a version 2 sqlite3_file object.  This shim
+  ** has to create a wrapper sqlite3_file of the same version.  Hence
+  ** there are two I/O method structures, one for version 1 and the other
+  ** for version 2.
+  */
+  sqlite3_io_methods sIoMethodsV1;
+  sqlite3_io_methods sIoMethodsV2;
+
+  /* True when this shim as been initialized.
+  */
+  int isInitialized;
+
+  /* For run-time access any of the other global data structures in this
+  ** shim, the following mutex must be held.
+  */
+  sqlite3_mutex *pMutex;
+
+  /* List of quotaGroup objects.
+  */
+  quotaGroup *pGroup;
+
+} gQuota;
+
+/************************* Utility Routines *********************************/
+/*
+** Acquire and release the mutex used to serialize access to the
+** list of quotaGroups.
+*/
+static void quotaEnter(void){ sqlite3_mutex_enter(gQuota.pMutex); }
+static void quotaLeave(void){ sqlite3_mutex_leave(gQuota.pMutex); }
+
+/* Count the number of open files in a quotaGroup 
+*/
+static int quotaGroupOpenFileCount(quotaGroup *pGroup){
+  int N = 0;
+  quotaFile *pFile = pGroup->pFiles;
+  while( pFile ){
+    if( pFile->nRef ) N++;
+    pFile = pFile->pNext;
+  }
+  return N;
+}
+
+/* Remove a file from a quota group.
+*/
+static void quotaRemoveFile(quotaFile *pFile){
+  quotaGroup *pGroup = pFile->pGroup;
+  pGroup->iSize -= pFile->iSize;
+  *pFile->ppPrev = pFile->pNext;
+  if( pFile->pNext ) pFile->pNext->ppPrev = pFile->ppPrev;
+  sqlite3_free(pFile);
+}
+
+/* Remove all files from a quota group.  It is always the case that
+** all files will be closed when this routine is called.
+*/
+static void quotaRemoveAllFiles(quotaGroup *pGroup){
+  while( pGroup->pFiles ){
+    assert( pGroup->pFiles->nRef==0 );
+    quotaRemoveFile(pGroup->pFiles);
+  }
+}
+
+
+/* If the reference count and threshold for a quotaGroup are both
+** zero, then destroy the quotaGroup.
+*/
+static void quotaGroupDeref(quotaGroup *pGroup){
+  if( pGroup->iLimit==0 && quotaGroupOpenFileCount(pGroup)==0 ){
+    quotaRemoveAllFiles(pGroup);
+    *pGroup->ppPrev = pGroup->pNext;
+    if( pGroup->pNext ) pGroup->pNext->ppPrev = pGroup->ppPrev;
+    if( pGroup->xDestroy ) pGroup->xDestroy(pGroup->pArg);
+    sqlite3_free(pGroup);
+  }
+}
+
+/*
+** Return TRUE if string z matches glob pattern zGlob.
+**
+** Globbing rules:
+**
+**      '*'       Matches any sequence of zero or more characters.
+**
+**      '?'       Matches exactly one character.
+**
+**     [...]      Matches one character from the enclosed list of
+**                characters.
+**
+**     [^...]     Matches one character not in the enclosed list.
+**
+**     /          Matches "/" or "\\"
+**
+*/
+static int quotaStrglob(const char *zGlob, const char *z){
+  int c, c2, cx;
+  int invert;
+  int seen;
+
+  while( (c = (*(zGlob++)))!=0 ){
+    if( c=='*' ){
+      while( (c=(*(zGlob++))) == '*' || c=='?' ){
+        if( c=='?' && (*(z++))==0 ) return 0;
+      }
+      if( c==0 ){
+        return 1;
+      }else if( c=='[' ){
+        while( *z && quotaStrglob(zGlob-1,z)==0 ){
+          z++;
+        }
+        return (*z)!=0;
+      }
+      cx = (c=='/') ? '\\' : c;
+      while( (c2 = (*(z++)))!=0 ){
+        while( c2!=c && c2!=cx ){
+          c2 = *(z++);
+          if( c2==0 ) return 0;
+        }
+        if( quotaStrglob(zGlob,z) ) return 1;
+      }
+      return 0;
+    }else if( c=='?' ){
+      if( (*(z++))==0 ) return 0;
+    }else if( c=='[' ){
+      int prior_c = 0;
+      seen = 0;
+      invert = 0;
+      c = *(z++);
+      if( c==0 ) return 0;
+      c2 = *(zGlob++);
+      if( c2=='^' ){
+        invert = 1;
+        c2 = *(zGlob++);
+      }
+      if( c2==']' ){
+        if( c==']' ) seen = 1;
+        c2 = *(zGlob++);
+      }
+      while( c2 && c2!=']' ){
+        if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
+          c2 = *(zGlob++);
+          if( c>=prior_c && c<=c2 ) seen = 1;
+          prior_c = 0;
+        }else{
+          if( c==c2 ){
+            seen = 1;
+          }
+          prior_c = c2;
+        }
+        c2 = *(zGlob++);
+      }
+      if( c2==0 || (seen ^ invert)==0 ) return 0;
+    }else if( c=='/' ){
+      if( z[0]!='/' && z[0]!='\\' ) return 0;
+      z++;
+    }else{
+      if( c!=(*(z++)) ) return 0;
+    }
+  }
+  return *z==0;
+}
+
+
+/* Find a quotaGroup given the filename.
+**
+** Return a pointer to the quotaGroup object. Return NULL if not found.
+*/
+static quotaGroup *quotaGroupFind(const char *zFilename){
+  quotaGroup *p;
+  for(p=gQuota.pGroup; p && quotaStrglob(p->zPattern, zFilename)==0;
+      p=p->pNext){}
+  return p;
+}
+
+/* Translate an sqlite3_file* that is really a quotaConn* into
+** the sqlite3_file* for the underlying original VFS.
+*/
+static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){
+  quotaConn *p = (quotaConn*)pConn;
+  return (sqlite3_file*)&p[1];
+}
+
+/* Find a file in a quota group and return a pointer to that file.
+** Return NULL if the file is not in the group.
+*/
+static quotaFile *quotaFindFile(
+  quotaGroup *pGroup,     /* Group in which to look for the file */
+  const char *zName,      /* Full pathname of the file */
+  int createFlag          /* Try to create the file if not found */
+){
+  quotaFile *pFile = pGroup->pFiles;
+  while( pFile && strcmp(pFile->zFilename, zName)!=0 ){
+    pFile = pFile->pNext;
+  }
+  if( pFile==0 && createFlag ){
+    int nName = (int)(strlen(zName) & 0x3fffffff);
+    pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 );
+    if( pFile ){
+      memset(pFile, 0, sizeof(*pFile));
+      pFile->zFilename = (char*)&pFile[1];
+      memcpy(pFile->zFilename, zName, nName+1);
+      pFile->pNext = pGroup->pFiles;
+      if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext;
+      pFile->ppPrev = &pGroup->pFiles;
+      pGroup->pFiles = pFile;
+      pFile->pGroup = pGroup;
+    }
+  }
+  return pFile;
+}
+/*
+** Translate UTF8 to MBCS for use in fopen() calls.  Return a pointer to the
+** translated text..  Call quota_mbcs_free() to deallocate any memory
+** used to store the returned pointer when done.
+*/
+static char *quota_utf8_to_mbcs(const char *zUtf8){
+#if SQLITE_OS_WIN
+  size_t n;          /* Bytes in zUtf8 */
+  int nWide;         /* number of UTF-16 characters */
+  int nMbcs;         /* Bytes of MBCS */
+  LPWSTR zTmpWide;   /* The UTF16 text */
+  char *zMbcs;       /* The MBCS text */
+  int codepage;      /* Code page used by fopen() */
+
+  n = strlen(zUtf8);
+  nWide = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, NULL, 0);
+  if( nWide==0 ) return 0;
+  zTmpWide = (LPWSTR)sqlite3_malloc( (nWide+1)*sizeof(zTmpWide[0]) );
+  if( zTmpWide==0 ) return 0;
+  MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zTmpWide, nWide);
+  codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
+  nMbcs = WideCharToMultiByte(codepage, 0, zTmpWide, nWide, 0, 0, 0, 0);
+  zMbcs = nMbcs ? (char*)sqlite3_malloc( nMbcs+1 ) : 0;
+  if( zMbcs ){
+    WideCharToMultiByte(codepage, 0, zTmpWide, nWide, zMbcs, nMbcs, 0, 0);
+  }
+  sqlite3_free(zTmpWide);
+  return zMbcs;
+#else
+  return (char*)zUtf8;  /* No-op on unix */
+#endif  
+}
+
+/*
+** Deallocate any memory allocated by quota_utf8_to_mbcs().
+*/
+static void quota_mbcs_free(char *zOld){
+#if SQLITE_OS_WIN
+  sqlite3_free(zOld);
+#else
+  /* No-op on unix */
+#endif  
+}
+
+/************************* VFS Method Wrappers *****************************/
+/*
+** This is the xOpen method used for the "quota" VFS.
+**
+** Most of the work is done by the underlying original VFS.  This method
+** simply links the new file into the appropriate quota group if it is a
+** file that needs to be tracked.
+*/
+static int quotaOpen(
+  sqlite3_vfs *pVfs,          /* The quota VFS */
+  const char *zName,          /* Name of file to be opened */
+  sqlite3_file *pConn,        /* Fill in this file descriptor */
+  int flags,                  /* Flags to control the opening */
+  int *pOutFlags              /* Flags showing results of opening */
+){
+  int rc;                                    /* Result code */         
+  quotaConn *pQuotaOpen;                     /* The new quota file descriptor */
+  quotaFile *pFile;                          /* Corresponding quotaFile obj */
+  quotaGroup *pGroup;                        /* The group file belongs to */
+  sqlite3_file *pSubOpen;                    /* Real file descriptor */
+  sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs;   /* Real VFS */
+
+  /* If the file is not a main database file or a WAL, then use the
+  ** normal xOpen method.
+  */
+  if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){
+    return pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags);
+  }
+
+  /* If the name of the file does not match any quota group, then
+  ** use the normal xOpen method.
+  */
+  quotaEnter();
+  pGroup = quotaGroupFind(zName);
+  if( pGroup==0 ){
+    rc = pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags);
+  }else{
+    /* If we get to this point, it means the file needs to be quota tracked.
+    */
+    pQuotaOpen = (quotaConn*)pConn;
+    pSubOpen = quotaSubOpen(pConn);
+    rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags);
+    if( rc==SQLITE_OK ){
+      pFile = quotaFindFile(pGroup, zName, 1);
+      if( pFile==0 ){
+        quotaLeave();
+        pSubOpen->pMethods->xClose(pSubOpen);
+        return SQLITE_NOMEM;
+      }
+      pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0;
+      pFile->nRef++;
+      pQuotaOpen->pFile = pFile;
+      if( pSubOpen->pMethods->iVersion==1 ){
+        pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV1;
+      }else{
+        pQuotaOpen->base.pMethods = &gQuota.sIoMethodsV2;
+      }
+    }
+  }
+  quotaLeave();
+  return rc;
+}
+
+/*
+** This is the xDelete method used for the "quota" VFS.
+**
+** If the file being deleted is part of the quota group, then reduce
+** the size of the quota group accordingly.  And remove the file from
+** the set of files in the quota group.
+*/
+static int quotaDelete(
+  sqlite3_vfs *pVfs,          /* The quota VFS */
+  const char *zName,          /* Name of file to be deleted */
+  int syncDir                 /* Do a directory sync after deleting */
+){
+  int rc;                                    /* Result code */         
+  quotaFile *pFile;                          /* Files in the quota */
+  quotaGroup *pGroup;                        /* The group file belongs to */
+  sqlite3_vfs *pOrigVfs = gQuota.pOrigVfs;   /* Real VFS */
+
+  /* Do the actual file delete */
+  rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir);
+
+  /* If the file just deleted is a member of a quota group, then remove
+  ** it from that quota group.
+  */
+  if( rc==SQLITE_OK ){
+    quotaEnter();
+    pGroup = quotaGroupFind(zName);
+    if( pGroup ){
+      pFile = quotaFindFile(pGroup, zName, 0);
+      if( pFile ){
+        if( pFile->nRef ){
+          pFile->deleteOnClose = 1;
+        }else{
+          quotaRemoveFile(pFile);
+          quotaGroupDeref(pGroup);
+        }
+      }
+    }
+    quotaLeave();
+  }
+  return rc;
+}
+
+
+/************************ I/O Method Wrappers *******************************/
+
+/* xClose requests get passed through to the original VFS.  But we
+** also have to unlink the quotaConn from the quotaFile and quotaGroup.
+** The quotaFile and/or quotaGroup are freed if they are no longer in use.
+*/
+static int quotaClose(sqlite3_file *pConn){
+  quotaConn *p = (quotaConn*)pConn;
+  quotaFile *pFile = p->pFile;
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  int rc;
+  rc = pSubOpen->pMethods->xClose(pSubOpen);
+  quotaEnter();
+  pFile->nRef--;
+  if( pFile->nRef==0 ){
+    quotaGroup *pGroup = pFile->pGroup;
+    if( pFile->deleteOnClose ){
+      gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
+      quotaRemoveFile(pFile);
+    }
+    quotaGroupDeref(pGroup);
+  }
+  quotaLeave();
+  return rc;
+}
+
+/* Pass xRead requests directory thru to the original VFS without
+** further processing.
+*/
+static int quotaRead(
+  sqlite3_file *pConn,
+  void *pBuf,
+  int iAmt,
+  sqlite3_int64 iOfst
+){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  return pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);
+}
+
+/* Check xWrite requests to see if they expand the file.  If they do,
+** the perform a quota check before passing them through to the
+** original VFS.
+*/
+static int quotaWrite(
+  sqlite3_file *pConn,
+  const void *pBuf,
+  int iAmt,
+  sqlite3_int64 iOfst
+){
+  quotaConn *p = (quotaConn*)pConn;
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  sqlite3_int64 iEnd = iOfst+iAmt;
+  quotaGroup *pGroup;
+  quotaFile *pFile = p->pFile;
+  sqlite3_int64 szNew;
+
+  if( pFile->iSize<iEnd ){
+    pGroup = pFile->pGroup;
+    quotaEnter();
+    szNew = pGroup->iSize - pFile->iSize + iEnd;
+    if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
+      if( pGroup->xCallback ){
+        pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, 
+                          pGroup->pArg);
+      }
+      if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
+        quotaLeave();
+        return SQLITE_FULL;
+      }
+    }
+    pGroup->iSize = szNew;
+    pFile->iSize = iEnd;
+    quotaLeave();
+  }
+  return pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);
+}
+
+/* Pass xTruncate requests thru to the original VFS.  If the
+** success, update the file size.
+*/
+static int quotaTruncate(sqlite3_file *pConn, sqlite3_int64 size){
+  quotaConn *p = (quotaConn*)pConn;
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  int rc = pSubOpen->pMethods->xTruncate(pSubOpen, size);
+  quotaFile *pFile = p->pFile;
+  quotaGroup *pGroup;
+  if( rc==SQLITE_OK ){
+    quotaEnter();
+    pGroup = pFile->pGroup;
+    pGroup->iSize -= pFile->iSize;
+    pFile->iSize = size;
+    pGroup->iSize += size;
+    quotaLeave();
+  }
+  return rc;
+}
+
+/* Pass xSync requests through to the original VFS without change
+*/
+static int quotaSync(sqlite3_file *pConn, int flags){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  return pSubOpen->pMethods->xSync(pSubOpen, flags);
+}
+
+/* Pass xFileSize requests through to the original VFS but then
+** update the quotaGroup with the new size before returning.
+*/
+static int quotaFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
+  quotaConn *p = (quotaConn*)pConn;
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  quotaFile *pFile = p->pFile;
+  quotaGroup *pGroup;
+  sqlite3_int64 sz;
+  int rc;
+
+  rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
+  if( rc==SQLITE_OK ){
+    quotaEnter();
+    pGroup = pFile->pGroup;
+    pGroup->iSize -= pFile->iSize;
+    pFile->iSize = sz;
+    pGroup->iSize += sz;
+    quotaLeave();
+    *pSize = sz;
+  }
+  return rc;
+}
+
+/* Pass xLock requests through to the original VFS unchanged.
+*/
+static int quotaLock(sqlite3_file *pConn, int lock){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  return pSubOpen->pMethods->xLock(pSubOpen, lock);
+}
+
+/* Pass xUnlock requests through to the original VFS unchanged.
+*/
+static int quotaUnlock(sqlite3_file *pConn, int lock){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  return pSubOpen->pMethods->xUnlock(pSubOpen, lock);
+}
+
+/* Pass xCheckReservedLock requests through to the original VFS unchanged.
+*/
+static int quotaCheckReservedLock(sqlite3_file *pConn, int *pResOut){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
+}
+
+/* Pass xFileControl requests through to the original VFS unchanged.
+*/
+static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  int rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
+#if defined(SQLITE_FCNTL_VFSNAME)
+  if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
+    *(char**)pArg = sqlite3_mprintf("quota/%z", *(char**)pArg);
+  }
+#endif
+  return rc;
+}
+
+/* Pass xSectorSize requests through to the original VFS unchanged.
+*/
+static int quotaSectorSize(sqlite3_file *pConn){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  return pSubOpen->pMethods->xSectorSize(pSubOpen);
+}
+
+/* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
+*/
+static int quotaDeviceCharacteristics(sqlite3_file *pConn){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);
+}
+
+/* Pass xShmMap requests through to the original VFS unchanged.
+*/
+static int quotaShmMap(
+  sqlite3_file *pConn,            /* Handle open on database file */
+  int iRegion,                    /* Region to retrieve */
+  int szRegion,                   /* Size of regions */
+  int bExtend,                    /* True to extend file if necessary */
+  void volatile **pp              /* OUT: Mapped memory */
+){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp);
+}
+
+/* Pass xShmLock requests through to the original VFS unchanged.
+*/
+static int quotaShmLock(
+  sqlite3_file *pConn,       /* Database file holding the shared memory */
+  int ofst,                  /* First lock to acquire or release */
+  int n,                     /* Number of locks to acquire or release */
+  int flags                  /* What to do with the lock */
+){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);
+}
+
+/* Pass xShmBarrier requests through to the original VFS unchanged.
+*/
+static void quotaShmBarrier(sqlite3_file *pConn){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  pSubOpen->pMethods->xShmBarrier(pSubOpen);
+}
+
+/* Pass xShmUnmap requests through to the original VFS unchanged.
+*/
+static int quotaShmUnmap(sqlite3_file *pConn, int deleteFlag){
+  sqlite3_file *pSubOpen = quotaSubOpen(pConn);
+  return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);
+}
+
+/************************** Public Interfaces *****************************/
+/*
+** Initialize the quota VFS shim.  Use the VFS named zOrigVfsName
+** as the VFS that does the actual work.  Use the default if
+** zOrigVfsName==NULL.  
+**
+** The quota VFS shim is named "quota".  It will become the default
+** VFS if makeDefault is non-zero.
+**
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once
+** during start-up.
+*/
+int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault){
+  sqlite3_vfs *pOrigVfs;
+  if( gQuota.isInitialized ) return SQLITE_MISUSE;
+  pOrigVfs = sqlite3_vfs_find(zOrigVfsName);
+  if( pOrigVfs==0 ) return SQLITE_ERROR;
+  assert( pOrigVfs!=&gQuota.sThisVfs );
+  gQuota.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+  if( !gQuota.pMutex ){
+    return SQLITE_NOMEM;
+  }
+  gQuota.isInitialized = 1;
+  gQuota.pOrigVfs = pOrigVfs;
+  gQuota.sThisVfs = *pOrigVfs;
+  gQuota.sThisVfs.xOpen = quotaOpen;
+  gQuota.sThisVfs.xDelete = quotaDelete;
+  gQuota.sThisVfs.szOsFile += sizeof(quotaConn);
+  gQuota.sThisVfs.zName = "quota";
+  gQuota.sIoMethodsV1.iVersion = 1;
+  gQuota.sIoMethodsV1.xClose = quotaClose;
+  gQuota.sIoMethodsV1.xRead = quotaRead;
+  gQuota.sIoMethodsV1.xWrite = quotaWrite;
+  gQuota.sIoMethodsV1.xTruncate = quotaTruncate;
+  gQuota.sIoMethodsV1.xSync = quotaSync;
+  gQuota.sIoMethodsV1.xFileSize = quotaFileSize;
+  gQuota.sIoMethodsV1.xLock = quotaLock;
+  gQuota.sIoMethodsV1.xUnlock = quotaUnlock;
+  gQuota.sIoMethodsV1.xCheckReservedLock = quotaCheckReservedLock;
+  gQuota.sIoMethodsV1.xFileControl = quotaFileControl;
+  gQuota.sIoMethodsV1.xSectorSize = quotaSectorSize;
+  gQuota.sIoMethodsV1.xDeviceCharacteristics = quotaDeviceCharacteristics;
+  gQuota.sIoMethodsV2 = gQuota.sIoMethodsV1;
+  gQuota.sIoMethodsV2.iVersion = 2;
+  gQuota.sIoMethodsV2.xShmMap = quotaShmMap;
+  gQuota.sIoMethodsV2.xShmLock = quotaShmLock;
+  gQuota.sIoMethodsV2.xShmBarrier = quotaShmBarrier;
+  gQuota.sIoMethodsV2.xShmUnmap = quotaShmUnmap;
+  sqlite3_vfs_register(&gQuota.sThisVfs, makeDefault);
+  return SQLITE_OK;
+}
+
+/*
+** Shutdown the quota system.
+**
+** All SQLite database connections must be closed before calling this
+** routine.
+**
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while
+** shutting down in order to free all remaining quota groups.
+*/
+int sqlite3_quota_shutdown(void){
+  quotaGroup *pGroup;
+  if( gQuota.isInitialized==0 ) return SQLITE_MISUSE;
+  for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){
+    if( quotaGroupOpenFileCount(pGroup)>0 ) return SQLITE_MISUSE;
+  }
+  while( gQuota.pGroup ){
+    pGroup = gQuota.pGroup;
+    gQuota.pGroup = pGroup->pNext;
+    pGroup->iLimit = 0;
+    assert( quotaGroupOpenFileCount(pGroup)==0 );
+    quotaGroupDeref(pGroup);
+  }
+  gQuota.isInitialized = 0;
+  sqlite3_mutex_free(gQuota.pMutex);
+  sqlite3_vfs_unregister(&gQuota.sThisVfs);
+  memset(&gQuota, 0, sizeof(gQuota));
+  return SQLITE_OK;
+}
+
+/*
+** Create or destroy a quota group.
+**
+** The quota group is defined by the zPattern.  When calling this routine
+** with a zPattern for a quota group that already exists, this routine
+** merely updates the iLimit, xCallback, and pArg values for that quota
+** group.  If zPattern is new, then a new quota group is created.
+**
+** If the iLimit for a quota group is set to zero, then the quota group
+** is disabled and will be deleted when the last database connection using
+** the quota group is closed.
+**
+** Calling this routine on a zPattern that does not exist and with a
+** zero iLimit is a no-op.
+**
+** A quota group must exist with a non-zero iLimit prior to opening
+** database connections if those connections are to participate in the
+** quota group.  Creating a quota group does not affect database connections
+** that are already open.
+*/
+int sqlite3_quota_set(
+  const char *zPattern,           /* The filename pattern */
+  sqlite3_int64 iLimit,           /* New quota to set for this quota group */
+  void (*xCallback)(              /* Callback invoked when going over quota */
+     const char *zFilename,         /* Name of file whose size increases */
+     sqlite3_int64 *piLimit,        /* IN/OUT: The current limit */
+     sqlite3_int64 iSize,           /* Total size of all files in the group */
+     void *pArg                     /* Client data */
+  ),
+  void *pArg,                     /* client data passed thru to callback */
+  void (*xDestroy)(void*)         /* Optional destructor for pArg */
+){
+  quotaGroup *pGroup;
+  quotaEnter();
+  pGroup = gQuota.pGroup;
+  while( pGroup && strcmp(pGroup->zPattern, zPattern)!=0 ){
+    pGroup = pGroup->pNext;
+  }
+  if( pGroup==0 ){
+    int nPattern = (int)(strlen(zPattern) & 0x3fffffff);
+    if( iLimit<=0 ){
+      quotaLeave();
+      return SQLITE_OK;
+    }
+    pGroup = (quotaGroup *)sqlite3_malloc( sizeof(*pGroup) + nPattern + 1 );
+    if( pGroup==0 ){
+      quotaLeave();
+      return SQLITE_NOMEM;
+    }
+    memset(pGroup, 0, sizeof(*pGroup));
+    pGroup->zPattern = (char*)&pGroup[1];
+    memcpy((char *)pGroup->zPattern, zPattern, nPattern+1);
+    if( gQuota.pGroup ) gQuota.pGroup->ppPrev = &pGroup->pNext;
+    pGroup->pNext = gQuota.pGroup;
+    pGroup->ppPrev = &gQuota.pGroup;
+    gQuota.pGroup = pGroup;
+  }
+  pGroup->iLimit = iLimit;
+  pGroup->xCallback = xCallback;
+  if( pGroup->xDestroy && pGroup->pArg!=pArg ){
+    pGroup->xDestroy(pGroup->pArg);
+  }
+  pGroup->pArg = pArg;
+  pGroup->xDestroy = xDestroy;
+  quotaGroupDeref(pGroup);
+  quotaLeave();
+  return SQLITE_OK;
+}
+
+/*
+** Bring the named file under quota management.  Or if it is already under
+** management, update its size.
+*/
+int sqlite3_quota_file(const char *zFilename){
+  char *zFull;
+  sqlite3_file *fd;
+  int rc;
+  int outFlags = 0;
+  sqlite3_int64 iSize;
+  int nAlloc = gQuota.sThisVfs.szOsFile + gQuota.sThisVfs.mxPathname+2;
+
+  /* Allocate space for a file-handle and the full path for file zFilename */
+  fd = (sqlite3_file *)sqlite3_malloc(nAlloc);
+  if( fd==0 ){
+    rc = SQLITE_NOMEM;
+  }else{
+    zFull = &((char *)fd)[gQuota.sThisVfs.szOsFile];
+    rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
+        gQuota.sThisVfs.mxPathname+1, zFull);
+  }
+
+  if( rc==SQLITE_OK ){
+    zFull[strlen(zFull)+1] = '\0';
+    rc = quotaOpen(&gQuota.sThisVfs, zFull, fd, 
+                   SQLITE_OPEN_READONLY | SQLITE_OPEN_MAIN_DB, &outFlags);
+    if( rc==SQLITE_OK ){
+      fd->pMethods->xFileSize(fd, &iSize);
+      fd->pMethods->xClose(fd);
+    }else if( rc==SQLITE_CANTOPEN ){
+      quotaGroup *pGroup;
+      quotaFile *pFile;
+      quotaEnter();
+      pGroup = quotaGroupFind(zFull);
+      if( pGroup ){
+        pFile = quotaFindFile(pGroup, zFull, 0);
+        if( pFile ) quotaRemoveFile(pFile);
+      }
+      quotaLeave();
+    }
+  }
+
+  sqlite3_free(fd);
+  return rc;
+}
+
+/*
+** Open a potentially quotaed file for I/O.
+*/
+quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode){
+  quota_FILE *p = 0;
+  char *zFull = 0;
+  char *zFullTranslated = 0;
+  int rc;
+  quotaGroup *pGroup;
+  quotaFile *pFile;
+
+  zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1);
+  if( zFull==0 ) return 0;
+  rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
+                                      gQuota.sThisVfs.mxPathname+1, zFull);
+  if( rc ) goto quota_fopen_error;
+  p = (quota_FILE*)sqlite3_malloc(sizeof(*p));
+  if( p==0 ) goto quota_fopen_error;
+  memset(p, 0, sizeof(*p));
+  zFullTranslated = quota_utf8_to_mbcs(zFull);
+  if( zFullTranslated==0 ) goto quota_fopen_error;
+  p->f = fopen(zFullTranslated, zMode);
+  if( p->f==0 ) goto quota_fopen_error;
+  quotaEnter();
+  pGroup = quotaGroupFind(zFull);
+  if( pGroup ){
+    pFile = quotaFindFile(pGroup, zFull, 1);
+    if( pFile==0 ){
+      quotaLeave();
+      goto quota_fopen_error;
+    }
+    pFile->nRef++;
+    p->pFile = pFile;
+  }
+  quotaLeave();
+  sqlite3_free(zFull);
+#if SQLITE_OS_WIN
+  p->zMbcsName = zFullTranslated;
+#endif
+  return p;
+
+quota_fopen_error:
+  quota_mbcs_free(zFullTranslated);
+  sqlite3_free(zFull);
+  if( p && p->f ) fclose(p->f);
+  sqlite3_free(p);
+  return 0;
+}
+
+/*
+** Read content from a quota_FILE
+*/
+size_t sqlite3_quota_fread(
+  void *pBuf,            /* Store the content here */
+  size_t size,           /* Size of each element */
+  size_t nmemb,          /* Number of elements to read */
+  quota_FILE *p          /* Read from this quota_FILE object */
+){
+  return fread(pBuf, size, nmemb, p->f);
+}
+
+/*
+** Write content into a quota_FILE.  Invoke the quota callback and block
+** the write if we exceed quota.
+*/
+size_t sqlite3_quota_fwrite(
+  const void *pBuf,      /* Take content to write from here */
+  size_t size,           /* Size of each element */
+  size_t nmemb,          /* Number of elements */
+  quota_FILE *p          /* Write to this quota_FILE objecct */
+){
+  sqlite3_int64 iOfst;
+  sqlite3_int64 iEnd;
+  sqlite3_int64 szNew;
+  quotaFile *pFile;
+  size_t rc;
+
+  iOfst = ftell(p->f);
+  iEnd = iOfst + size*nmemb;
+  pFile = p->pFile;
+  if( pFile && pFile->iSize<iEnd ){
+    quotaGroup *pGroup = pFile->pGroup;
+    quotaEnter();
+    szNew = pGroup->iSize - pFile->iSize + iEnd;
+    if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
+      if( pGroup->xCallback ){
+        pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, 
+                          pGroup->pArg);
+      }
+      if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){
+        iEnd = pGroup->iLimit - pGroup->iSize + pFile->iSize;
+        nmemb = (size_t)((iEnd - iOfst)/size);
+        iEnd = iOfst + size*nmemb;
+        szNew = pGroup->iSize - pFile->iSize + iEnd;
+      }
+    }
+    pGroup->iSize = szNew;
+    pFile->iSize = iEnd;
+    quotaLeave();
+  }else{
+    pFile = 0;
+  }
+  rc = fwrite(pBuf, size, nmemb, p->f);
+
+  /* If the write was incomplete, adjust the file size and group size
+  ** downward */
+  if( rc<nmemb && pFile ){
+    size_t nWritten = rc;
+    sqlite3_int64 iNewEnd = iOfst + size*nWritten;
+    if( iNewEnd<iEnd ) iNewEnd = iEnd;
+    quotaEnter();
+    pFile->pGroup->iSize += iNewEnd - pFile->iSize;
+    pFile->iSize = iNewEnd;
+    quotaLeave();
+  }
+  return rc;
+}
+
+/*
+** Close an open quota_FILE stream.
+*/
+int sqlite3_quota_fclose(quota_FILE *p){
+  int rc;
+  quotaFile *pFile;
+  rc = fclose(p->f);
+  pFile = p->pFile;
+  if( pFile ){
+    quotaEnter();
+    pFile->nRef--;
+    if( pFile->nRef==0 ){
+      quotaGroup *pGroup = pFile->pGroup;
+      if( pFile->deleteOnClose ){
+        gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
+        quotaRemoveFile(pFile);
+      }
+      quotaGroupDeref(pGroup);
+    }
+    quotaLeave();
+  }
+#if SQLITE_OS_WIN
+  quota_mbcs_free(p->zMbcsName);
+#endif
+  sqlite3_free(p);
+  return rc;
+}
+
+/*
+** Flush memory buffers for a quota_FILE to disk.
+*/
+int sqlite3_quota_fflush(quota_FILE *p, int doFsync){
+  int rc;
+  rc = fflush(p->f);
+  if( rc==0 && doFsync ){
+#if SQLITE_OS_UNIX
+    rc = fsync(fileno(p->f));
+#endif
+#if SQLITE_OS_WIN
+    rc = _commit(_fileno(p->f));
+#endif
+  }
+  return rc!=0;
+}
+
+/*
+** Seek on a quota_FILE stream.
+*/
+int sqlite3_quota_fseek(quota_FILE *p, long offset, int whence){
+  return fseek(p->f, offset, whence);
+}
+
+/*
+** rewind a quota_FILE stream.
+*/
+void sqlite3_quota_rewind(quota_FILE *p){
+  rewind(p->f);
+}
+
+/*
+** Tell the current location of a quota_FILE stream.
+*/
+long sqlite3_quota_ftell(quota_FILE *p){
+  return ftell(p->f);
+}
+
+/*
+** Test the error indicator for the given file.
+*/
+int sqlite3_quota_ferror(quota_FILE *p){
+  return ferror(p->f);
+}
+
+/*
+** Truncate a file to szNew bytes.
+*/
+int sqlite3_quota_ftruncate(quota_FILE *p, sqlite3_int64 szNew){
+  quotaFile *pFile = p->pFile;
+  int rc;
+  if( (pFile = p->pFile)!=0 && pFile->iSize<szNew ){
+    quotaGroup *pGroup;
+    if( pFile->iSize<szNew ){
+      /* This routine cannot be used to extend a file that is under
+      ** quota management.  Only true truncation is allowed. */
+      return -1;
+    }
+    pGroup = pFile->pGroup;
+    quotaEnter();
+    pGroup->iSize += szNew - pFile->iSize;
+    quotaLeave();
+  }
+#if SQLITE_OS_UNIX
+  rc = ftruncate(fileno(p->f), szNew);
+#endif
+#if SQLITE_OS_WIN
+#  if defined(__MINGW32__) && defined(SQLITE_TEST)
+     /* _chsize_s() is missing from MingW (as of 2012-11-06).  Use
+     ** _chsize() as a work-around for testing purposes. */
+     rc = _chsize(_fileno(p->f), (long)szNew);
+#  else
+     rc = _chsize_s(_fileno(p->f), szNew);
+#  endif
+#endif
+  if( pFile && rc==0 ){
+    quotaGroup *pGroup = pFile->pGroup;
+    quotaEnter();
+    pGroup->iSize += szNew - pFile->iSize;
+    pFile->iSize = szNew;
+    quotaLeave();
+  }
+  return rc;
+}
+
+/*
+** Determine the time that the given file was last modified, in
+** seconds size 1970.  Write the result into *pTime.  Return 0 on
+** success and non-zero on any kind of error.
+*/
+int sqlite3_quota_file_mtime(quota_FILE *p, time_t *pTime){
+  int rc;
+#if SQLITE_OS_UNIX
+  struct stat buf;
+  rc = fstat(fileno(p->f), &buf);
+#endif
+#if SQLITE_OS_WIN
+  struct _stati64 buf;
+  rc = _stati64(p->zMbcsName, &buf);
+#endif
+  if( rc==0 ) *pTime = buf.st_mtime;
+  return rc;
+}
+
+/*
+** Return the true size of the file, as reported by the operating
+** system.
+*/
+sqlite3_int64 sqlite3_quota_file_truesize(quota_FILE *p){
+  int rc;
+#if SQLITE_OS_UNIX
+  struct stat buf;
+  rc = fstat(fileno(p->f), &buf);
+#endif
+#if SQLITE_OS_WIN
+  struct _stati64 buf;
+  rc = _stati64(p->zMbcsName, &buf);
+#endif
+  return rc==0 ? buf.st_size : -1;
+}
+
+/*
+** Return the size of the file, as it is known to the quota subsystem.
+*/
+sqlite3_int64 sqlite3_quota_file_size(quota_FILE *p){
+  return p->pFile ? p->pFile->iSize : -1;
+}
+ 
+/*
+** Determine the amount of data in bytes available for reading
+** in the given file.
+*/
+long sqlite3_quota_file_available(quota_FILE *p){
+  FILE* f = p->f;
+  long pos1, pos2;
+  int rc;
+  pos1 = ftell(f);
+  if ( pos1 < 0 ) return -1;
+  rc = fseek(f, 0, SEEK_END);
+  if ( rc != 0 ) return -1;
+  pos2 = ftell(f);
+  if ( pos2 < 0 ) return -1;
+  rc = fseek(f, pos1, SEEK_SET);
+  if ( rc != 0 ) return -1;
+  return pos2 - pos1;
+}
+
+/*
+** Remove a managed file.  Update quotas accordingly.
+*/
+int sqlite3_quota_remove(const char *zFilename){
+  char *zFull;            /* Full pathname for zFilename */
+  size_t nFull;           /* Number of bytes in zFilename */
+  int rc;                 /* Result code */
+  quotaGroup *pGroup;     /* Group containing zFilename */
+  quotaFile *pFile;       /* A file in the group */
+  quotaFile *pNextFile;   /* next file in the group */
+  int diff;               /* Difference between filenames */
+  char c;                 /* First character past end of pattern */
+
+  zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1);
+  if( zFull==0 ) return SQLITE_NOMEM;
+  rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename,
+                                      gQuota.sThisVfs.mxPathname+1, zFull);
+  if( rc ){
+    sqlite3_free(zFull);
+    return rc;
+  }
+
+  /* Figure out the length of the full pathname.  If the name ends with
+  ** / (or \ on windows) then remove the trailing /.
+  */
+  nFull = strlen(zFull);
+  if( nFull>0 && (zFull[nFull-1]=='/' || zFull[nFull-1]=='\\') ){
+    nFull--;
+    zFull[nFull] = 0;
+  }
+
+  quotaEnter();
+  pGroup = quotaGroupFind(zFull);
+  if( pGroup ){
+    for(pFile=pGroup->pFiles; pFile && rc==SQLITE_OK; pFile=pNextFile){
+      pNextFile = pFile->pNext;
+      diff = strncmp(zFull, pFile->zFilename, nFull);
+      if( diff==0 && ((c = pFile->zFilename[nFull])==0 || c=='/' || c=='\\') ){
+        if( pFile->nRef ){
+          pFile->deleteOnClose = 1;
+        }else{
+          rc = gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0);
+          quotaRemoveFile(pFile);
+          quotaGroupDeref(pGroup);
+        }
+      }
+    }
+  }
+  quotaLeave();
+  sqlite3_free(zFull);
+  return rc;
+}
+  
+/***************************** Test Code ***********************************/
+#ifdef SQLITE_TEST
+#include <tcl.h>
+
+/*
+** Argument passed to a TCL quota-over-limit callback.
+*/
+typedef struct TclQuotaCallback TclQuotaCallback;
+struct TclQuotaCallback {
+  Tcl_Interp *interp;    /* Interpreter in which to run the script */
+  Tcl_Obj *pScript;      /* Script to be run */
+};
+
+extern const char *sqlite3ErrName(int);
+
+
+/*
+** This is the callback from a quota-over-limit.
+*/
+static void tclQuotaCallback(
+  const char *zFilename,          /* Name of file whose size increases */
+  sqlite3_int64 *piLimit,         /* IN/OUT: The current limit */
+  sqlite3_int64 iSize,            /* Total size of all files in the group */
+  void *pArg                      /* Client data */
+){
+  TclQuotaCallback *p;            /* Callback script object */
+  Tcl_Obj *pEval;                 /* Script to evaluate */
+  Tcl_Obj *pVarname;              /* Name of variable to pass as 2nd arg */
+  unsigned int rnd;               /* Random part of pVarname */
+  int rc;                         /* Tcl error code */
+
+  p = (TclQuotaCallback *)pArg;
+  if( p==0 ) return;
+
+  pVarname = Tcl_NewStringObj("::piLimit_", -1);
+  Tcl_IncrRefCount(pVarname);
+  sqlite3_randomness(sizeof(rnd), (void *)&rnd);
+  Tcl_AppendObjToObj(pVarname, Tcl_NewIntObj((int)(rnd&0x7FFFFFFF)));
+  Tcl_ObjSetVar2(p->interp, pVarname, 0, Tcl_NewWideIntObj(*piLimit), 0);
+
+  pEval = Tcl_DuplicateObj(p->pScript);
+  Tcl_IncrRefCount(pEval);
+  Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zFilename, -1));
+  Tcl_ListObjAppendElement(0, pEval, pVarname);
+  Tcl_ListObjAppendElement(0, pEval, Tcl_NewWideIntObj(iSize));
+  rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
+
+  if( rc==TCL_OK ){
+    Tcl_WideInt x;
+    Tcl_Obj *pLimit = Tcl_ObjGetVar2(p->interp, pVarname, 0, 0);
+    rc = Tcl_GetWideIntFromObj(p->interp, pLimit, &x);
+    *piLimit = x;
+    Tcl_UnsetVar(p->interp, Tcl_GetString(pVarname), 0);
+  }
+
+  Tcl_DecrRefCount(pEval);
+  Tcl_DecrRefCount(pVarname);
+  if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp);
+}
+
+/*
+** Destructor for a TCL quota-over-limit callback.
+*/
+static void tclCallbackDestructor(void *pObj){
+  TclQuotaCallback *p = (TclQuotaCallback*)pObj;
+  if( p ){
+    Tcl_DecrRefCount(p->pScript);
+    sqlite3_free((char *)p);
+  }
+}
+
+/*
+** tclcmd: sqlite3_quota_initialize NAME MAKEDEFAULT
+*/
+static int test_quota_initialize(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zName;              /* Name of new quota VFS */
+  int makeDefault;                /* True to make the new VFS the default */
+  int rc;                         /* Value returned by quota_initialize() */
+
+  /* Process arguments */
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");
+    return TCL_ERROR;
+  }
+  zName = Tcl_GetString(objv[1]);
+  if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;
+  if( zName[0]=='\0' ) zName = 0;
+
+  /* Call sqlite3_quota_initialize() */
+  rc = sqlite3_quota_initialize(zName, makeDefault);
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_shutdown
+*/
+static int test_quota_shutdown(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int rc;                         /* Value returned by quota_shutdown() */
+
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+
+  /* Call sqlite3_quota_shutdown() */
+  rc = sqlite3_quota_shutdown();
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_set PATTERN LIMIT SCRIPT
+*/
+static int test_quota_set(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zPattern;           /* File pattern to configure */
+  Tcl_WideInt iLimit;             /* Initial quota in bytes */
+  Tcl_Obj *pScript;               /* Tcl script to invoke to increase quota */
+  int rc;                         /* Value returned by quota_set() */
+  TclQuotaCallback *p;            /* Callback object */
+  int nScript;                    /* Length of callback script */
+  void (*xDestroy)(void*);        /* Optional destructor for pArg */
+  void (*xCallback)(const char *, sqlite3_int64 *, sqlite3_int64, void *);
+
+  /* Process arguments */
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PATTERN LIMIT SCRIPT");
+    return TCL_ERROR;
+  }
+  zPattern = Tcl_GetString(objv[1]);
+  if( Tcl_GetWideIntFromObj(interp, objv[2], &iLimit) ) return TCL_ERROR;
+  pScript = objv[3];
+  Tcl_GetStringFromObj(pScript, &nScript);
+
+  if( nScript>0 ){
+    /* Allocate a TclQuotaCallback object */
+    p = (TclQuotaCallback *)sqlite3_malloc(sizeof(TclQuotaCallback));
+    if( !p ){
+      Tcl_SetResult(interp, (char *)"SQLITE_NOMEM", TCL_STATIC);
+      return TCL_OK;
+    }
+    memset(p, 0, sizeof(TclQuotaCallback));
+    p->interp = interp;
+    Tcl_IncrRefCount(pScript);
+    p->pScript = pScript;
+    xDestroy = tclCallbackDestructor;
+    xCallback = tclQuotaCallback;
+  }else{
+    p = 0;
+    xDestroy = 0;
+    xCallback = 0;
+  }
+
+  /* Invoke sqlite3_quota_set() */
+  rc = sqlite3_quota_set(zPattern, iLimit, xCallback, (void*)p, xDestroy);
+
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_file FILENAME
+*/
+static int test_quota_file(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zFilename;          /* File pattern to configure */
+  int rc;                         /* Value returned by quota_file() */
+
+  /* Process arguments */
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
+    return TCL_ERROR;
+  }
+  zFilename = Tcl_GetString(objv[1]);
+
+  /* Invoke sqlite3_quota_file() */
+  rc = sqlite3_quota_file(zFilename);
+
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+  return TCL_OK;
+}
+
+/*
+** tclcmd:  sqlite3_quota_dump
+*/
+static int test_quota_dump(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  Tcl_Obj *pResult;
+  Tcl_Obj *pGroupTerm;
+  Tcl_Obj *pFileTerm;
+  quotaGroup *pGroup;
+  quotaFile *pFile;
+
+  pResult = Tcl_NewObj();
+  quotaEnter();
+  for(pGroup=gQuota.pGroup; pGroup; pGroup=pGroup->pNext){
+    pGroupTerm = Tcl_NewObj();
+    Tcl_ListObjAppendElement(interp, pGroupTerm,
+          Tcl_NewStringObj(pGroup->zPattern, -1));
+    Tcl_ListObjAppendElement(interp, pGroupTerm,
+          Tcl_NewWideIntObj(pGroup->iLimit));
+    Tcl_ListObjAppendElement(interp, pGroupTerm,
+          Tcl_NewWideIntObj(pGroup->iSize));
+    for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){
+      int i;
+      char zTemp[1000];
+      pFileTerm = Tcl_NewObj();
+      sqlite3_snprintf(sizeof(zTemp), zTemp, "%s", pFile->zFilename);
+      for(i=0; zTemp[i]; i++){ if( zTemp[i]=='\\' ) zTemp[i] = '/'; }
+      Tcl_ListObjAppendElement(interp, pFileTerm,
+            Tcl_NewStringObj(zTemp, -1));
+      Tcl_ListObjAppendElement(interp, pFileTerm,
+            Tcl_NewWideIntObj(pFile->iSize));
+      Tcl_ListObjAppendElement(interp, pFileTerm,
+            Tcl_NewWideIntObj(pFile->nRef));
+      Tcl_ListObjAppendElement(interp, pFileTerm,
+            Tcl_NewWideIntObj(pFile->deleteOnClose));
+      Tcl_ListObjAppendElement(interp, pGroupTerm, pFileTerm);
+    }
+    Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
+  }
+  quotaLeave();
+  Tcl_SetObjResult(interp, pResult);
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_fopen FILENAME MODE
+*/
+static int test_quota_fopen(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zFilename;          /* File pattern to configure */
+  const char *zMode;              /* Mode string */
+  quota_FILE *p;                  /* Open string object */
+  char zReturn[50];               /* Name of pointer to return */
+
+  /* Process arguments */
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME MODE");
+    return TCL_ERROR;
+  }
+  zFilename = Tcl_GetString(objv[1]);
+  zMode = Tcl_GetString(objv[2]);
+  p = sqlite3_quota_fopen(zFilename, zMode);
+  sqlite3_snprintf(sizeof(zReturn), zReturn, "%p", p);
+  Tcl_SetResult(interp, zReturn, TCL_VOLATILE);
+  return TCL_OK;
+}
+
+/* Defined in test1.c */
+extern void *sqlite3TestTextToPtr(const char*);
+
+/*
+** tclcmd: sqlite3_quota_fread HANDLE SIZE NELEM
+*/
+static int test_quota_fread(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  char *zBuf;
+  int sz;
+  int nElem;
+  size_t got;
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR;
+  zBuf = (char*)sqlite3_malloc( sz*nElem + 1 );
+  if( zBuf==0 ){
+    Tcl_SetResult(interp, "out of memory", TCL_STATIC);
+    return TCL_ERROR;
+  }
+  got = sqlite3_quota_fread(zBuf, sz, nElem, p);
+  zBuf[got*sz] = 0;
+  Tcl_SetResult(interp, zBuf, TCL_VOLATILE);
+  sqlite3_free(zBuf);
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_fwrite HANDLE SIZE NELEM CONTENT
+*/
+static int test_quota_fwrite(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  char *zBuf;
+  int sz;
+  int nElem;
+  size_t got;
+
+  if( objc!=5 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM CONTENT");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR;
+  zBuf = Tcl_GetString(objv[4]);
+  got = sqlite3_quota_fwrite(zBuf, sz, nElem, p);
+  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(got));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_fclose HANDLE
+*/
+static int test_quota_fclose(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  rc = sqlite3_quota_fclose(p);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_fflush HANDLE ?HARDSYNC?
+*/
+static int test_quota_fflush(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  int rc;
+  int doSync = 0;
+
+  if( objc!=2 && objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE ?HARDSYNC?");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  if( objc==3 ){
+    if( Tcl_GetBooleanFromObj(interp, objv[2], &doSync) ) return TCL_ERROR;
+  }
+  rc = sqlite3_quota_fflush(p, doSync);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_fseek HANDLE OFFSET WHENCE
+*/
+static int test_quota_fseek(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  int ofst;
+  const char *zWhence;
+  int whence;
+  int rc;
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE OFFSET WHENCE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  if( Tcl_GetIntFromObj(interp, objv[2], &ofst) ) return TCL_ERROR;
+  zWhence = Tcl_GetString(objv[3]);
+  if( strcmp(zWhence, "SEEK_SET")==0 ){
+    whence = SEEK_SET;
+  }else if( strcmp(zWhence, "SEEK_CUR")==0 ){
+    whence = SEEK_CUR;
+  }else if( strcmp(zWhence, "SEEK_END")==0 ){
+    whence = SEEK_END;
+  }else{
+    Tcl_AppendResult(interp,
+           "WHENCE should be SEEK_SET, SEEK_CUR, or SEEK_END", (char*)0);
+    return TCL_ERROR;
+  }
+  rc = sqlite3_quota_fseek(p, ofst, whence);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_rewind HANDLE
+*/
+static int test_quota_rewind(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  sqlite3_quota_rewind(p);
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_ftell HANDLE
+*/
+static int test_quota_ftell(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  sqlite3_int64 x;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  x = sqlite3_quota_ftell(p);
+  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_ftruncate HANDLE SIZE
+*/
+static int test_quota_ftruncate(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  sqlite3_int64 x;
+  Tcl_WideInt w;
+  int rc;
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  if( Tcl_GetWideIntFromObj(interp, objv[2], &w) ) return TCL_ERROR;
+  x = (sqlite3_int64)w;
+  rc = sqlite3_quota_ftruncate(p, x);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_file_size HANDLE
+*/
+static int test_quota_file_size(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  sqlite3_int64 x;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  x = sqlite3_quota_file_size(p);
+  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_file_truesize HANDLE
+*/
+static int test_quota_file_truesize(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  sqlite3_int64 x;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  x = sqlite3_quota_file_truesize(p);
+  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_file_mtime HANDLE
+*/
+static int test_quota_file_mtime(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  time_t t;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  t = 0;
+  sqlite3_quota_file_mtime(p, &t);
+  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(t));
+  return TCL_OK;
+}
+
+
+/*
+** tclcmd: sqlite3_quota_remove FILENAME
+*/
+static int test_quota_remove(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zFilename;          /* File pattern to configure */
+  int rc;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
+    return TCL_ERROR;
+  }
+  zFilename = Tcl_GetString(objv[1]);
+  rc = sqlite3_quota_remove(zFilename);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_glob PATTERN TEXT
+**
+** Test the glob pattern matching.  Return 1 if TEXT matches PATTERN
+** and return 0 if it does not.
+*/
+static int test_quota_glob(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zPattern;          /* The glob pattern */
+  const char *zText;             /* Text to compare agains the pattern */
+  int rc;
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PATTERN TEXT");
+    return TCL_ERROR;
+  }
+  zPattern = Tcl_GetString(objv[1]);
+  zText = Tcl_GetString(objv[2]);
+  rc = quotaStrglob(zPattern, zText);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_file_available HANDLE
+**
+** Return the number of bytes from the current file point to the end of
+** the file.
+*/
+static int test_quota_file_available(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  sqlite3_int64 x;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  x = sqlite3_quota_file_available(p);
+  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x));
+  return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_quota_ferror HANDLE
+**
+** Return true if the file handle is in the error state.
+*/
+static int test_quota_ferror(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  quota_FILE *p;
+  int x;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "HANDLE");
+    return TCL_ERROR;
+  }
+  p = sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  x = sqlite3_quota_ferror(p);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(x));
+  return TCL_OK;
+}
+
+/*
+** This routine registers the custom TCL commands defined in this
+** module.  This should be the only procedure visible from outside
+** of this module.
+*/
+int Sqlitequota_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+  } aCmd[] = {
+    { "sqlite3_quota_initialize",    test_quota_initialize },
+    { "sqlite3_quota_shutdown",      test_quota_shutdown },
+    { "sqlite3_quota_set",           test_quota_set },
+    { "sqlite3_quota_file",          test_quota_file },
+    { "sqlite3_quota_dump",          test_quota_dump },
+    { "sqlite3_quota_fopen",         test_quota_fopen },
+    { "sqlite3_quota_fread",         test_quota_fread },
+    { "sqlite3_quota_fwrite",        test_quota_fwrite },
+    { "sqlite3_quota_fclose",        test_quota_fclose },
+    { "sqlite3_quota_fflush",        test_quota_fflush },
+    { "sqlite3_quota_fseek",         test_quota_fseek },
+    { "sqlite3_quota_rewind",        test_quota_rewind },
+    { "sqlite3_quota_ftell",         test_quota_ftell },
+    { "sqlite3_quota_ftruncate",     test_quota_ftruncate },
+    { "sqlite3_quota_file_size",     test_quota_file_size },
+    { "sqlite3_quota_file_truesize", test_quota_file_truesize },
+    { "sqlite3_quota_file_mtime",    test_quota_file_mtime },
+    { "sqlite3_quota_remove",        test_quota_remove },
+    { "sqlite3_quota_glob",          test_quota_glob },
+    { "sqlite3_quota_file_available",test_quota_file_available },
+    { "sqlite3_quota_ferror",        test_quota_ferror },
+  };
+  int i;
+
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
+
+  return TCL_OK;
+}
+#endif

+ 274 - 0
components/external/sqlite/test/test_quota.h

@@ -0,0 +1,274 @@
+/*
+** 2011 December 1
+**
+** 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 contains the interface definition for the quota a VFS shim.
+**
+** This particular shim enforces a quota system on files.  One or more
+** database files are in a "quota group" that is defined by a GLOB
+** pattern.  A quota is set for the combined size of all files in the
+** the group.  A quota of zero means "no limit".  If the total size
+** of all files in the quota group is greater than the limit, then
+** write requests that attempt to enlarge a file fail with SQLITE_FULL.
+**
+** However, before returning SQLITE_FULL, the write requests invoke
+** a callback function that is configurable for each quota group.
+** This callback has the opportunity to enlarge the quota.  If the
+** callback does enlarge the quota such that the total size of all
+** files within the group is less than the new quota, then the write
+** continues as if nothing had happened.
+*/
+#ifndef _QUOTA_H_
+#include "sqlite3.h"
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#if SQLITE_OS_UNIX
+# include <unistd.h>
+#endif
+#if SQLITE_OS_WIN
+# include <windows.h>
+#endif
+
+/* Make this callable from C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+** Initialize the quota VFS shim.  Use the VFS named zOrigVfsName
+** as the VFS that does the actual work.  Use the default if
+** zOrigVfsName==NULL.  
+**
+** The quota VFS shim is named "quota".  It will become the default
+** VFS if makeDefault is non-zero.
+**
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once
+** during start-up.
+*/
+int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault);
+
+/*
+** Shutdown the quota system.
+**
+** All SQLite database connections must be closed before calling this
+** routine.
+**
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while
+** shutting down in order to free all remaining quota groups.
+*/
+int sqlite3_quota_shutdown(void);
+
+/*
+** Create or destroy a quota group.
+**
+** The quota group is defined by the zPattern.  When calling this routine
+** with a zPattern for a quota group that already exists, this routine
+** merely updates the iLimit, xCallback, and pArg values for that quota
+** group.  If zPattern is new, then a new quota group is created.
+**
+** The zPattern is always compared against the full pathname of the file.
+** Even if APIs are called with relative pathnames, SQLite converts the
+** name to a full pathname before comparing it against zPattern.  zPattern
+** is a glob pattern with the following matching rules:
+**
+**      '*'       Matches any sequence of zero or more characters.
+**
+**      '?'       Matches exactly one character.
+**
+**     [...]      Matches one character from the enclosed list of
+**                characters.  "]" can be part of the list if it is
+**                the first character.  Within the list "X-Y" matches
+**                characters X or Y or any character in between the
+**                two.  Ex:  "[0-9]" matches any digit.
+**
+**     [^...]     Matches one character not in the enclosed list.
+**
+**     /          Matches either / or \.  This allows glob patterns
+**                containing / to work on both unix and windows.
+**
+** Note that, unlike unix shell globbing, the directory separator "/"
+** can match a wildcard.  So, for example, the pattern "/abc/xyz/" "*"
+** matches any files anywhere in the directory hierarchy beneath
+** /abc/xyz.
+**
+** The glob algorithm works on bytes.  Multi-byte UTF8 characters are
+** matched as if each byte were a separate character.
+**
+** If the iLimit for a quota group is set to zero, then the quota group
+** is disabled and will be deleted when the last database connection using
+** the quota group is closed.
+**
+** Calling this routine on a zPattern that does not exist and with a
+** zero iLimit is a no-op.
+**
+** A quota group must exist with a non-zero iLimit prior to opening
+** database connections if those connections are to participate in the
+** quota group.  Creating a quota group does not affect database connections
+** that are already open.
+**
+** The patterns that define the various quota groups should be distinct.
+** If the same filename matches more than one quota group pattern, then
+** the behavior of this package is undefined.
+*/
+int sqlite3_quota_set(
+  const char *zPattern,           /* The filename pattern */
+  sqlite3_int64 iLimit,           /* New quota to set for this quota group */
+  void (*xCallback)(              /* Callback invoked when going over quota */
+     const char *zFilename,         /* Name of file whose size increases */
+     sqlite3_int64 *piLimit,        /* IN/OUT: The current limit */
+     sqlite3_int64 iSize,           /* Total size of all files in the group */
+     void *pArg                     /* Client data */
+  ),
+  void *pArg,                     /* client data passed thru to callback */
+  void (*xDestroy)(void*)         /* Optional destructor for pArg */
+);
+
+/*
+** Bring the named file under quota management, assuming its name matches
+** the glob pattern of some quota group.  Or if it is already under
+** management, update its size.  If zFilename does not match the glob
+** pattern of any quota group, this routine is a no-op.
+*/
+int sqlite3_quota_file(const char *zFilename);
+
+/*
+** The following object serves the same role as FILE in the standard C
+** library.  It represents an open connection to a file on disk for I/O.
+**
+** A single quota_FILE should not be used by two or more threads at the
+** same time.  Multiple threads can be using different quota_FILE objects
+** simultaneously, but not the same quota_FILE object.
+*/
+typedef struct quota_FILE quota_FILE;
+
+/*
+** Create a new quota_FILE object used to read and/or write to the
+** file zFilename.  The zMode parameter is as with standard library zMode.
+*/
+quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode);
+
+/*
+** Perform I/O against a quota_FILE object.  When doing writes, the
+** quota mechanism may result in a short write, in order to prevent
+** the sum of sizes of all files from going over quota.
+*/
+size_t sqlite3_quota_fread(void*, size_t, size_t, quota_FILE*);
+size_t sqlite3_quota_fwrite(const void*, size_t, size_t, quota_FILE*);
+
+/*
+** Flush all written content held in memory buffers out to disk.
+** This is the equivalent of fflush() in the standard library.
+**
+** If the hardSync parameter is true (non-zero) then this routine
+** also forces OS buffers to disk - the equivalent of fsync().
+**
+** This routine return zero on success and non-zero if something goes
+** wrong.
+*/
+int sqlite3_quota_fflush(quota_FILE*, int hardSync);
+
+/*
+** Close a quota_FILE object and free all associated resources.  The
+** file remains under quota management.
+*/
+int sqlite3_quota_fclose(quota_FILE*);
+
+/*
+** Move the read/write pointer for a quota_FILE object.  Or tell the
+** current location of the read/write pointer.
+*/
+int sqlite3_quota_fseek(quota_FILE*, long, int);
+void sqlite3_quota_rewind(quota_FILE*);
+long sqlite3_quota_ftell(quota_FILE*);
+
+/*
+** Test the error indicator for the given file.
+**
+** Return non-zero if the error indicator is set.
+*/
+int sqlite3_quota_ferror(quota_FILE*);
+
+/*
+** Truncate a file previously opened by sqlite3_quota_fopen().  Return
+** zero on success and non-zero on any kind of failure.
+**
+** The newSize argument must be less than or equal to the current file size.
+** Any attempt to "truncate" a file to a larger size results in 
+** undefined behavior.
+*/
+int sqlite3_quota_ftruncate(quota_FILE*, sqlite3_int64 newSize);
+
+/*
+** Return the last modification time of the opened file, in seconds
+** since 1970.
+*/
+int sqlite3_quota_file_mtime(quota_FILE*, time_t *pTime);
+
+/*
+** Return the size of the file as it is known to the quota system.
+**
+** This size might be different from the true size of the file on
+** disk if some outside process has modified the file without using the
+** quota mechanism, or if calls to sqlite3_quota_fwrite() have occurred
+** which have increased the file size, but those writes have not yet been
+** forced to disk using sqlite3_quota_fflush().
+**
+** Return -1 if the file is not participating in quota management.
+*/
+sqlite3_int64 sqlite3_quota_file_size(quota_FILE*);
+
+/*
+** Return the true size of the file.
+**
+** The true size should be the same as the size of the file as known
+** to the quota system, however the sizes might be different if the
+** file has been extended or truncated via some outside process or if
+** pending writes have not yet been flushed to disk.
+**
+** Return -1 if the file does not exist or if the size of the file
+** cannot be determined for some reason.
+*/
+sqlite3_int64 sqlite3_quota_file_truesize(quota_FILE*);
+
+/*
+** Determine the amount of data in bytes available for reading
+** in the given file.
+**
+** Return -1 if the amount cannot be determined for some reason.
+*/
+long sqlite3_quota_file_available(quota_FILE*);
+
+/*
+** Delete a file from the disk, if that file is under quota management.
+** Adjust quotas accordingly.
+**
+** If zFilename is the name of a directory that matches one of the
+** quota glob patterns, then all files under quota management that
+** are contained within that directory are deleted.
+**
+** A standard SQLite result code is returned (SQLITE_OK, SQLITE_NOMEM, etc.)
+** When deleting a directory of files, if the deletion of any one
+** file fails (for example due to an I/O error), then this routine
+** returns immediately, with the error code, and does not try to 
+** delete any of the other files in the specified directory.
+**
+** All files are removed from quota management and deleted from disk.
+** However, no attempt is made to remove empty directories.
+**
+** This routine is a no-op for files that are not under quota management.
+*/
+int sqlite3_quota_remove(const char *zFilename);
+
+#ifdef __cplusplus
+}  /* end of the 'extern "C"' block */
+#endif
+#endif /* _QUOTA_H_ */

+ 305 - 0
components/external/sqlite/test/test_rtree.c

@@ -0,0 +1,305 @@
+/*
+** 2010 August 28
+**
+** 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 all sorts of SQLite interfaces. This code
+** is not included in the SQLite library. 
+*/
+
+#include <sqlite3.h>
+#include <tcl.h>
+
+/* Solely for the UNUSED_PARAMETER() macro. */
+#include "sqliteInt.h"
+
+#ifdef SQLITE_ENABLE_RTREE
+/* 
+** Type used to cache parameter information for the "circle" r-tree geometry
+** callback.
+*/
+typedef struct Circle Circle;
+struct Circle {
+  struct Box {
+    double xmin;
+    double xmax;
+    double ymin;
+    double ymax;
+  } aBox[2];
+  double centerx;
+  double centery;
+  double radius;
+};
+
+/*
+** Destructor function for Circle objects allocated by circle_geom().
+*/
+static void circle_del(void *p){
+  sqlite3_free(p);
+}
+
+/*
+** Implementation of "circle" r-tree geometry callback.
+*/
+static int circle_geom(
+  sqlite3_rtree_geometry *p,
+  int nCoord, 
+#ifdef SQLITE_RTREE_INT_ONLY
+  sqlite3_int64 *aCoord,
+#else
+  double *aCoord, 
+#endif
+  int *pRes
+){
+  int i;                          /* Iterator variable */
+  Circle *pCircle;                /* Structure defining circular region */
+  double xmin, xmax;              /* X dimensions of box being tested */
+  double ymin, ymax;              /* X dimensions of box being tested */
+
+  if( p->pUser==0 ){
+    /* If pUser is still 0, then the parameter values have not been tested
+    ** for correctness or stored into a Circle structure yet. Do this now. */
+
+    /* This geometry callback is for use with a 2-dimensional r-tree table.
+    ** Return an error if the table does not have exactly 2 dimensions. */
+    if( nCoord!=4 ) return SQLITE_ERROR;
+
+    /* Test that the correct number of parameters (3) have been supplied,
+    ** and that the parameters are in range (that the radius of the circle 
+    ** radius is greater than zero). */
+    if( p->nParam!=3 || p->aParam[2]<0.0 ) return SQLITE_ERROR;
+
+    /* Allocate a structure to cache parameter data in. Return SQLITE_NOMEM
+    ** if the allocation fails. */
+    pCircle = (Circle *)(p->pUser = sqlite3_malloc(sizeof(Circle)));
+    if( !pCircle ) return SQLITE_NOMEM;
+    p->xDelUser = circle_del;
+
+    /* Record the center and radius of the circular region. One way that
+    ** tested bounding boxes that intersect the circular region are detected
+    ** is by testing if each corner of the bounding box lies within radius
+    ** units of the center of the circle. */
+    pCircle->centerx = p->aParam[0];
+    pCircle->centery = p->aParam[1];
+    pCircle->radius = p->aParam[2];
+
+    /* Define two bounding box regions. The first, aBox[0], extends to
+    ** infinity in the X dimension. It covers the same range of the Y dimension
+    ** as the circular region. The second, aBox[1], extends to infinity in
+    ** the Y dimension and is constrained to the range of the circle in the
+    ** X dimension.
+    **
+    ** Then imagine each box is split in half along its short axis by a line
+    ** that intersects the center of the circular region. A bounding box
+    ** being tested can be said to intersect the circular region if it contains
+    ** points from each half of either of the two infinite bounding boxes.
+    */
+    pCircle->aBox[0].xmin = pCircle->centerx;
+    pCircle->aBox[0].xmax = pCircle->centerx;
+    pCircle->aBox[0].ymin = pCircle->centery + pCircle->radius;
+    pCircle->aBox[0].ymax = pCircle->centery - pCircle->radius;
+    pCircle->aBox[1].xmin = pCircle->centerx + pCircle->radius;
+    pCircle->aBox[1].xmax = pCircle->centerx - pCircle->radius;
+    pCircle->aBox[1].ymin = pCircle->centery;
+    pCircle->aBox[1].ymax = pCircle->centery;
+  }
+
+  pCircle = (Circle *)p->pUser;
+  xmin = aCoord[0];
+  xmax = aCoord[1];
+  ymin = aCoord[2];
+  ymax = aCoord[3];
+
+  /* Check if any of the 4 corners of the bounding-box being tested lie 
+  ** inside the circular region. If they do, then the bounding-box does
+  ** intersect the region of interest. Set the output variable to true and
+  ** return SQLITE_OK in this case. */
+  for(i=0; i<4; i++){
+    double x = (i&0x01) ? xmax : xmin;
+    double y = (i&0x02) ? ymax : ymin;
+    double d2;
+    
+    d2  = (x-pCircle->centerx)*(x-pCircle->centerx);
+    d2 += (y-pCircle->centery)*(y-pCircle->centery);
+    if( d2<(pCircle->radius*pCircle->radius) ){
+      *pRes = 1;
+      return SQLITE_OK;
+    }
+  }
+
+  /* Check if the bounding box covers any other part of the circular region.
+  ** See comments above for a description of how this test works. If it does
+  ** cover part of the circular region, set the output variable to true
+  ** and return SQLITE_OK. */
+  for(i=0; i<2; i++){
+    if( xmin<=pCircle->aBox[i].xmin 
+     && xmax>=pCircle->aBox[i].xmax 
+     && ymin<=pCircle->aBox[i].ymin 
+     && ymax>=pCircle->aBox[i].ymax 
+    ){
+      *pRes = 1;
+      return SQLITE_OK;
+    }
+  }
+
+  /* The specified bounding box does not intersect the circular region. Set
+  ** the output variable to zero and return SQLITE_OK. */
+  *pRes = 0;
+  return SQLITE_OK;
+}
+
+/* END of implementation of "circle" geometry callback.
+**************************************************************************
+*************************************************************************/
+
+#include <assert.h>
+#include "tcl.h"
+
+typedef struct Cube Cube;
+struct Cube {
+  double x;
+  double y;
+  double z;
+  double width;
+  double height;
+  double depth;
+};
+
+static void cube_context_free(void *p){
+  sqlite3_free(p);
+}
+
+/*
+** The context pointer registered along with the 'cube' callback is
+** always ((void *)&gHere). This is just to facilitate testing, it is not
+** actually used for anything.
+*/
+static int gHere = 42;
+
+/*
+** Implementation of a simple r-tree geom callback to test for intersection
+** of r-tree rows with a "cube" shape. Cubes are defined by six scalar
+** coordinates as follows:
+**
+**   cube(x, y, z, width, height, depth)
+**
+** The width, height and depth parameters must all be greater than zero.
+*/
+static int cube_geom(
+  sqlite3_rtree_geometry *p,
+  int nCoord,
+#ifdef SQLITE_RTREE_INT_ONLY
+  sqlite3_int64 *aCoord, 
+#else
+  double *aCoord, 
+#endif
+  int *piRes
+){
+  Cube *pCube = (Cube *)p->pUser;
+
+  assert( p->pContext==(void *)&gHere );
+
+  if( pCube==0 ){
+    if( p->nParam!=6 || nCoord!=6
+     || p->aParam[3]<=0.0 || p->aParam[4]<=0.0 || p->aParam[5]<=0.0
+    ){
+      return SQLITE_ERROR;
+    }
+    pCube = (Cube *)sqlite3_malloc(sizeof(Cube));
+    if( !pCube ){
+      return SQLITE_NOMEM;
+    }
+    pCube->x = p->aParam[0];
+    pCube->y = p->aParam[1];
+    pCube->z = p->aParam[2];
+    pCube->width = p->aParam[3];
+    pCube->height = p->aParam[4];
+    pCube->depth = p->aParam[5];
+
+    p->pUser = (void *)pCube;
+    p->xDelUser = cube_context_free;
+  }
+
+  assert( nCoord==6 );
+  *piRes = 0;
+  if( aCoord[0]<=(pCube->x+pCube->width)
+   && aCoord[1]>=pCube->x
+   && aCoord[2]<=(pCube->y+pCube->height)
+   && aCoord[3]>=pCube->y
+   && aCoord[4]<=(pCube->z+pCube->depth)
+   && aCoord[5]>=pCube->z
+  ){
+    *piRes = 1;
+  }
+
+  return SQLITE_OK;
+}
+#endif /* SQLITE_ENABLE_RTREE */
+
+static int register_cube_geom(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_ENABLE_RTREE
+  UNUSED_PARAMETER(clientData);
+  UNUSED_PARAMETER(interp);
+  UNUSED_PARAMETER(objc);
+  UNUSED_PARAMETER(objv);
+#else
+  extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
+  extern const char *sqlite3ErrName(int);
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  rc = sqlite3_rtree_geometry_callback(db, "cube", cube_geom, (void *)&gHere);
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+#endif
+  return TCL_OK;
+}
+
+static int register_circle_geom(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifndef SQLITE_ENABLE_RTREE
+  UNUSED_PARAMETER(clientData);
+  UNUSED_PARAMETER(interp);
+  UNUSED_PARAMETER(objc);
+  UNUSED_PARAMETER(objv);
+#else
+  extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
+  extern const char *sqlite3ErrName(int);
+  sqlite3 *db;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  rc = sqlite3_rtree_geometry_callback(db, "circle", circle_geom, 0);
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
+#endif
+  return TCL_OK;
+}
+
+int Sqlitetestrtree_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "register_cube_geom", register_cube_geom, 0, 0);
+  Tcl_CreateObjCommand(interp, "register_circle_geom",register_circle_geom,0,0);
+  return TCL_OK;
+}

+ 362 - 0
components/external/sqlite/test/test_schema.c

@@ -0,0 +1,362 @@
+/*
+** 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.
+*/
+
+/* The code in this file defines a sqlite3 virtual-table module that
+** provides a read-only view of the current database schema. There is one
+** row in the schema table for each column in the database schema.
+*/
+#define SCHEMA \
+"CREATE TABLE x("                                                            \
+  "database,"          /* Name of database (i.e. main, temp etc.) */         \
+  "tablename,"         /* Name of table */                                   \
+  "cid,"               /* Column number (from left-to-right, 0 upward) */    \
+  "name,"              /* Column name */                                     \
+  "type,"              /* Specified type (i.e. VARCHAR(32)) */               \
+  "not_null,"          /* Boolean. True if NOT NULL was specified */         \
+  "dflt_value,"        /* Default value for this column */                   \
+  "pk"                 /* True if this column is part of the primary key */  \
+")"
+
+/* If SQLITE_TEST is defined this code is preprocessed for use as part
+** of the sqlite test binary "testfixture". Otherwise it is preprocessed
+** to be compiled into an sqlite dynamic extension.
+*/
+#ifdef SQLITE_TEST
+  #include "sqliteInt.h"
+  #include "tcl.h"
+#else
+  #include "sqlite3ext.h"
+  SQLITE_EXTENSION_INIT1
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+typedef struct schema_vtab schema_vtab;
+typedef struct schema_cursor schema_cursor;
+
+/* A schema table object */
+struct schema_vtab {
+  sqlite3_vtab base;
+  sqlite3 *db;
+};
+
+/* A schema table cursor object */
+struct schema_cursor {
+  sqlite3_vtab_cursor base;
+  sqlite3_stmt *pDbList;
+  sqlite3_stmt *pTableList;
+  sqlite3_stmt *pColumnList;
+  int rowid;
+};
+
+/*
+** None of this works unless we have virtual tables.
+*/
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+/*
+** Table destructor for the schema module.
+*/
+static int schemaDestroy(sqlite3_vtab *pVtab){
+  sqlite3_free(pVtab);
+  return 0;
+}
+
+/*
+** Table constructor for the schema module.
+*/
+static int schemaCreate(
+  sqlite3 *db,
+  void *pAux,
+  int argc, const char *const*argv,
+  sqlite3_vtab **ppVtab,
+  char **pzErr
+){
+  int rc = SQLITE_NOMEM;
+  schema_vtab *pVtab = sqlite3_malloc(sizeof(schema_vtab));
+  if( pVtab ){
+    memset(pVtab, 0, sizeof(schema_vtab));
+    pVtab->db = db;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+    rc = sqlite3_declare_vtab(db, SCHEMA);
+#endif
+  }
+  *ppVtab = (sqlite3_vtab *)pVtab;
+  return rc;
+}
+
+/*
+** Open a new cursor on the schema table.
+*/
+static int schemaOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+  int rc = SQLITE_NOMEM;
+  schema_cursor *pCur;
+  pCur = sqlite3_malloc(sizeof(schema_cursor));
+  if( pCur ){
+    memset(pCur, 0, sizeof(schema_cursor));
+    *ppCursor = (sqlite3_vtab_cursor *)pCur;
+    rc = SQLITE_OK;
+  }
+  return rc;
+}
+
+/*
+** Close a schema table cursor.
+*/
+static int schemaClose(sqlite3_vtab_cursor *cur){
+  schema_cursor *pCur = (schema_cursor *)cur;
+  sqlite3_finalize(pCur->pDbList);
+  sqlite3_finalize(pCur->pTableList);
+  sqlite3_finalize(pCur->pColumnList);
+  sqlite3_free(pCur);
+  return SQLITE_OK;
+}
+
+/*
+** Retrieve a column of data.
+*/
+static int schemaColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
+  schema_cursor *pCur = (schema_cursor *)cur;
+  switch( i ){
+    case 0:
+      sqlite3_result_value(ctx, sqlite3_column_value(pCur->pDbList, 1));
+      break;
+    case 1:
+      sqlite3_result_value(ctx, sqlite3_column_value(pCur->pTableList, 0));
+      break;
+    default:
+      sqlite3_result_value(ctx, sqlite3_column_value(pCur->pColumnList, i-2));
+      break;
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Retrieve the current rowid.
+*/
+static int schemaRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+  schema_cursor *pCur = (schema_cursor *)cur;
+  *pRowid = pCur->rowid;
+  return SQLITE_OK;
+}
+
+static int finalize(sqlite3_stmt **ppStmt){
+  int rc = sqlite3_finalize(*ppStmt);
+  *ppStmt = 0;
+  return rc;
+}
+
+static int schemaEof(sqlite3_vtab_cursor *cur){
+  schema_cursor *pCur = (schema_cursor *)cur;
+  return (pCur->pDbList ? 0 : 1);
+}
+
+/*
+** Advance the cursor to the next row.
+*/
+static int schemaNext(sqlite3_vtab_cursor *cur){
+  int rc = SQLITE_OK;
+  schema_cursor *pCur = (schema_cursor *)cur;
+  schema_vtab *pVtab = (schema_vtab *)(cur->pVtab);
+  char *zSql = 0;
+
+  while( !pCur->pColumnList || SQLITE_ROW!=sqlite3_step(pCur->pColumnList) ){
+    if( SQLITE_OK!=(rc = finalize(&pCur->pColumnList)) ) goto next_exit;
+
+    while( !pCur->pTableList || SQLITE_ROW!=sqlite3_step(pCur->pTableList) ){
+      if( SQLITE_OK!=(rc = finalize(&pCur->pTableList)) ) goto next_exit;
+
+      assert(pCur->pDbList);
+      while( SQLITE_ROW!=sqlite3_step(pCur->pDbList) ){
+        rc = finalize(&pCur->pDbList);
+        goto next_exit;
+      }
+
+      /* Set zSql to the SQL to pull the list of tables from the 
+      ** sqlite_master (or sqlite_temp_master) table of the database
+      ** identfied by the row pointed to by the SQL statement pCur->pDbList
+      ** (iterating through a "PRAGMA database_list;" statement).
+      */
+      if( sqlite3_column_int(pCur->pDbList, 0)==1 ){
+        zSql = sqlite3_mprintf(
+            "SELECT name FROM sqlite_temp_master WHERE type='table'"
+        );
+      }else{
+        sqlite3_stmt *pDbList = pCur->pDbList;
+        zSql = sqlite3_mprintf(
+            "SELECT name FROM %Q.sqlite_master WHERE type='table'",
+             sqlite3_column_text(pDbList, 1)
+        );
+      }
+      if( !zSql ){
+        rc = SQLITE_NOMEM;
+        goto next_exit;
+      }
+
+      rc = sqlite3_prepare(pVtab->db, zSql, -1, &pCur->pTableList, 0);
+      sqlite3_free(zSql);
+      if( rc!=SQLITE_OK ) goto next_exit;
+    }
+
+    /* Set zSql to the SQL to the table_info pragma for the table currently
+    ** identified by the rows pointed to by statements pCur->pDbList and
+    ** pCur->pTableList.
+    */
+    zSql = sqlite3_mprintf("PRAGMA %Q.table_info(%Q)", 
+        sqlite3_column_text(pCur->pDbList, 1),
+        sqlite3_column_text(pCur->pTableList, 0)
+    );
+
+    if( !zSql ){
+      rc = SQLITE_NOMEM;
+      goto next_exit;
+    }
+    rc = sqlite3_prepare(pVtab->db, zSql, -1, &pCur->pColumnList, 0);
+    sqlite3_free(zSql);
+    if( rc!=SQLITE_OK ) goto next_exit;
+  }
+  pCur->rowid++;
+
+next_exit:
+  /* TODO: Handle rc */
+  return rc;
+}
+
+/*
+** Reset a schema table cursor.
+*/
+static int schemaFilter(
+  sqlite3_vtab_cursor *pVtabCursor, 
+  int idxNum, const char *idxStr,
+  int argc, sqlite3_value **argv
+){
+  int rc;
+  schema_vtab *pVtab = (schema_vtab *)(pVtabCursor->pVtab);
+  schema_cursor *pCur = (schema_cursor *)pVtabCursor;
+  pCur->rowid = 0;
+  finalize(&pCur->pTableList);
+  finalize(&pCur->pColumnList);
+  finalize(&pCur->pDbList);
+  rc = sqlite3_prepare(pVtab->db,"PRAGMA database_list", -1, &pCur->pDbList, 0);
+  return (rc==SQLITE_OK ? schemaNext(pVtabCursor) : rc);
+}
+
+/*
+** Analyse the WHERE condition.
+*/
+static int schemaBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+  return SQLITE_OK;
+}
+
+/*
+** A virtual table module that merely echos method calls into TCL
+** variables.
+*/
+static sqlite3_module schemaModule = {
+  0,                           /* iVersion */
+  schemaCreate,
+  schemaCreate,
+  schemaBestIndex,
+  schemaDestroy,
+  schemaDestroy,
+  schemaOpen,                  /* xOpen - open a cursor */
+  schemaClose,                 /* xClose - close a cursor */
+  schemaFilter,                /* xFilter - configure scan constraints */
+  schemaNext,                  /* xNext - advance a cursor */
+  schemaEof,                   /* xEof */
+  schemaColumn,                /* xColumn - read data */
+  schemaRowid,                 /* xRowid - read data */
+  0,                           /* xUpdate */
+  0,                           /* xBegin */
+  0,                           /* xSync */
+  0,                           /* xCommit */
+  0,                           /* xRollback */
+  0,                           /* xFindMethod */
+  0,                           /* xRename */
+};
+
+#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */
+
+#ifdef SQLITE_TEST
+
+/*
+** Decode a pointer to an sqlite3 object.
+*/
+extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb);
+
+/*
+** Register the schema virtual table module.
+*/
+static int register_schema_module(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  sqlite3_create_module(db, "schema", &schemaModule, 0);
+#endif
+  return TCL_OK;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetestschema_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+     void *clientData;
+  } aObjCmd[] = {
+     { "register_schema_module", register_schema_module, 0 },
+  };
+  int i;
+  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, 
+        aObjCmd[i].xProc, aObjCmd[i].clientData, 0);
+  }
+  return TCL_OK;
+}
+
+#else
+
+/*
+** Extension load function.
+*/
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_schema_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  SQLITE_EXTENSION_INIT2(pApi);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  sqlite3_create_module(db, "schema", &schemaModule, 0);
+#endif
+  return 0;
+}
+
+#endif

+ 516 - 0
components/external/sqlite/test/test_server.c

@@ -0,0 +1,516 @@
+/*
+** 2006 January 07
+**
+** 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 contains demonstration code.  Nothing in this file gets compiled
+** or linked into the SQLite library unless you use a non-standard option:
+**
+**      -DSQLITE_SERVER=1
+**
+** The configure script will never generate a Makefile with the option
+** above.  You will need to manually modify the Makefile if you want to
+** include any of the code from this file in your project.  Or, at your
+** option, you may copy and paste the code from this file and
+** thereby avoiding a recompile of SQLite.
+**
+**
+** This source file demonstrates how to use SQLite to create an SQL database 
+** server thread in a multiple-threaded program.  One or more client threads
+** send messages to the server thread and the server thread processes those
+** messages in the order received and returns the results to the client.
+**
+** One might ask:  "Why bother?  Why not just let each thread connect
+** to the database directly?"  There are a several of reasons to
+** prefer the client/server approach.
+**
+**    (1)  Some systems (ex: Redhat9) have broken threading implementations
+**         that prevent SQLite database connections from being used in
+**         a thread different from the one where they were created.  With
+**         the client/server approach, all database connections are created
+**         and used within the server thread.  Client calls to the database
+**         can be made from multiple threads (though not at the same time!)
+**
+**    (2)  Beginning with SQLite version 3.3.0, when two or more 
+**         connections to the same database occur within the same thread,
+**         they can optionally share their database cache.  This reduces
+**         I/O and memory requirements.  Cache shared is controlled using
+**         the sqlite3_enable_shared_cache() API.
+**
+**    (3)  Database connections on a shared cache use table-level locking
+**         instead of file-level locking for improved concurrency.
+**
+**    (4)  Database connections on a shared cache can by optionally
+**         set to READ UNCOMMITTED isolation.  (The default isolation for
+**         SQLite is SERIALIZABLE.)  When this occurs, readers will
+**         never be blocked by a writer and writers will not be
+**         blocked by readers.  There can still only be a single writer
+**         at a time, but multiple readers can simultaneously exist with
+**         that writer.  This is a huge increase in concurrency.
+**
+** To summarize the rational for using a client/server approach: prior
+** to SQLite version 3.3.0 it probably was not worth the trouble.  But
+** with SQLite version 3.3.0 and beyond you can get significant performance
+** and concurrency improvements and memory usage reductions by going
+** client/server.
+**
+** Note:  The extra features of version 3.3.0 described by points (2)
+** through (4) above are only available if you compile without the
+** option -DSQLITE_OMIT_SHARED_CACHE. 
+**
+** Here is how the client/server approach works:  The database server
+** thread is started on this procedure:
+**
+**       void *sqlite3_server(void *NotUsed);
+**
+** The sqlite_server procedure runs as long as the g.serverHalt variable
+** is false.  A mutex is used to make sure no more than one server runs
+** at a time.  The server waits for messages to arrive on a message
+** queue and processes the messages in order.
+**
+** Two convenience routines are provided for starting and stopping the
+** server thread:
+**
+**       void sqlite3_server_start(void);
+**       void sqlite3_server_stop(void);
+**
+** Both of the convenience routines return immediately.  Neither will
+** ever give an error.  If a server is already started or already halted,
+** then the routines are effectively no-ops.
+**
+** Clients use the following interfaces:
+**
+**       sqlite3_client_open
+**       sqlite3_client_prepare
+**       sqlite3_client_step
+**       sqlite3_client_reset
+**       sqlite3_client_finalize
+**       sqlite3_client_close
+**
+** These interfaces work exactly like the standard core SQLite interfaces
+** having the same names without the "_client_" infix.  Many other SQLite
+** interfaces can be used directly without having to send messages to the
+** server as long as SQLITE_ENABLE_MEMORY_MANAGEMENT is not defined.
+** The following interfaces fall into this second category:
+**
+**       sqlite3_bind_*
+**       sqlite3_changes
+**       sqlite3_clear_bindings
+**       sqlite3_column_*
+**       sqlite3_complete
+**       sqlite3_create_collation
+**       sqlite3_create_function
+**       sqlite3_data_count
+**       sqlite3_db_handle
+**       sqlite3_errcode
+**       sqlite3_errmsg
+**       sqlite3_last_insert_rowid
+**       sqlite3_total_changes
+**       sqlite3_transfer_bindings
+**
+** A single SQLite connection (an sqlite3* object) or an SQLite statement
+** (an sqlite3_stmt* object) should only be passed to a single interface
+** function at a time.  The connections and statements can be passed from
+** any thread to any of the functions listed in the second group above as
+** long as the same connection is not in use by two threads at once and
+** as long as SQLITE_ENABLE_MEMORY_MANAGEMENT is not defined.  Additional
+** information about the SQLITE_ENABLE_MEMORY_MANAGEMENT constraint is
+** below.
+**
+** The busy handler for all database connections should remain turned
+** off.  That means that any lock contention will cause the associated
+** sqlite3_client_step() call to return immediately with an SQLITE_BUSY
+** error code.  If a busy handler is enabled and lock contention occurs,
+** then the entire server thread will block.  This will cause not only
+** the requesting client to block but every other database client as
+** well.  It is possible to enhance the code below so that lock
+** contention will cause the message to be placed back on the top of
+** the queue to be tried again later.  But such enhanced processing is
+** not included here, in order to keep the example simple.
+**
+** This example code assumes the use of pthreads.  Pthreads
+** implementations are available for windows.  (See, for example
+** http://sourceware.org/pthreads-win32/announcement.html.)  Or, you
+** can translate the locking and thread synchronization code to use
+** windows primitives easily enough.  The details are left as an
+** exercise to the reader.
+**
+**** Restrictions Associated With SQLITE_ENABLE_MEMORY_MANAGEMENT ****
+**
+** If you compile with SQLITE_ENABLE_MEMORY_MANAGEMENT defined, then
+** SQLite includes code that tracks how much memory is being used by
+** each thread.  These memory counts can become confused if memory
+** is allocated by one thread and then freed by another.  For that
+** reason, when SQLITE_ENABLE_MEMORY_MANAGEMENT is used, all operations
+** that might allocate or free memory should be performanced in the same
+** thread that originally created the database connection.  In that case,
+** many of the operations that are listed above as safe to be performed
+** in separate threads would need to be sent over to the server to be
+** done there.  If SQLITE_ENABLE_MEMORY_MANAGEMENT is defined, then
+** the following functions can be used safely from different threads
+** without messing up the allocation counts:
+**
+**       sqlite3_bind_parameter_name
+**       sqlite3_bind_parameter_index
+**       sqlite3_changes
+**       sqlite3_column_blob
+**       sqlite3_column_count
+**       sqlite3_complete
+**       sqlite3_data_count
+**       sqlite3_db_handle
+**       sqlite3_errcode
+**       sqlite3_errmsg
+**       sqlite3_last_insert_rowid
+**       sqlite3_total_changes
+**
+** The remaining functions are not thread-safe when memory management
+** is enabled.  So one would have to define some new interface routines
+** along the following lines:
+**
+**       sqlite3_client_bind_*
+**       sqlite3_client_clear_bindings
+**       sqlite3_client_column_*
+**       sqlite3_client_create_collation
+**       sqlite3_client_create_function
+**       sqlite3_client_transfer_bindings
+**
+** The example code in this file is intended for use with memory
+** management turned off.  So the implementation of these additional
+** client interfaces is left as an exercise to the reader.
+**
+** It may seem surprising to the reader that the list of safe functions
+** above does not include things like sqlite3_bind_int() or
+** sqlite3_column_int().  But those routines might, in fact, allocate
+** or deallocate memory.  In the case of sqlite3_bind_int(), if the
+** parameter was previously bound to a string that string might need
+** to be deallocated before the new integer value is inserted.  In
+** the case of sqlite3_column_int(), the value of the column might be
+** a UTF-16 string which will need to be converted to UTF-8 then into
+** an integer.
+*/
+
+/* Include this to get the definition of SQLITE_THREADSAFE, in the
+** case that default values are used.
+*/
+#include "sqliteInt.h"
+
+/*
+** Only compile the code in this file on UNIX with a SQLITE_THREADSAFE build
+** and only if the SQLITE_SERVER macro is defined.
+*/
+#if defined(SQLITE_SERVER) && !defined(SQLITE_OMIT_SHARED_CACHE)
+#if SQLITE_OS_UNIX && SQLITE_THREADSAFE
+
+/*
+** We require only pthreads and the public interface of SQLite.
+*/
+#include <pthread.h>
+#include "sqlite3.h"
+
+/*
+** Messages are passed from client to server and back again as 
+** instances of the following structure.
+*/
+typedef struct SqlMessage SqlMessage;
+struct SqlMessage {
+  int op;                      /* Opcode for the message */
+  sqlite3 *pDb;                /* The SQLite connection */
+  sqlite3_stmt *pStmt;         /* A specific statement */
+  int errCode;                 /* Error code returned */
+  const char *zIn;             /* Input filename or SQL statement */
+  int nByte;                   /* Size of the zIn parameter for prepare() */
+  const char *zOut;            /* Tail of the SQL statement */
+  SqlMessage *pNext;           /* Next message in the queue */
+  SqlMessage *pPrev;           /* Previous message in the queue */
+  pthread_mutex_t clientMutex; /* Hold this mutex to access the message */
+  pthread_cond_t clientWakeup; /* Signal to wake up the client */
+};
+
+/*
+** Legal values for SqlMessage.op
+*/
+#define MSG_Open       1  /* sqlite3_open(zIn, &pDb) */
+#define MSG_Prepare    2  /* sqlite3_prepare(pDb, zIn, nByte, &pStmt, &zOut) */
+#define MSG_Step       3  /* sqlite3_step(pStmt) */
+#define MSG_Reset      4  /* sqlite3_reset(pStmt) */
+#define MSG_Finalize   5  /* sqlite3_finalize(pStmt) */
+#define MSG_Close      6  /* sqlite3_close(pDb) */
+#define MSG_Done       7  /* Server has finished with this message */
+
+
+/*
+** State information about the server is stored in a static variable
+** named "g" as follows:
+*/
+static struct ServerState {
+  pthread_mutex_t queueMutex;   /* Hold this mutex to access the msg queue */
+  pthread_mutex_t serverMutex;  /* Held by the server while it is running */
+  pthread_cond_t serverWakeup;  /* Signal this condvar to wake up the server */
+  volatile int serverHalt;      /* Server halts itself when true */
+  SqlMessage *pQueueHead;       /* Head of the message queue */
+  SqlMessage *pQueueTail;       /* Tail of the message queue */
+} g = {
+  PTHREAD_MUTEX_INITIALIZER,
+  PTHREAD_MUTEX_INITIALIZER,
+  PTHREAD_COND_INITIALIZER,
+};
+
+/*
+** Send a message to the server.  Block until we get a reply.
+**
+** The mutex and condition variable in the message are uninitialized
+** when this routine is called.  This routine takes care of 
+** initializing them and destroying them when it has finished.
+*/
+static void sendToServer(SqlMessage *pMsg){
+  /* Initialize the mutex and condition variable on the message
+  */
+  pthread_mutex_init(&pMsg->clientMutex, 0);
+  pthread_cond_init(&pMsg->clientWakeup, 0);
+
+  /* Add the message to the head of the server's message queue.
+  */
+  pthread_mutex_lock(&g.queueMutex);
+  pMsg->pNext = g.pQueueHead;
+  if( g.pQueueHead==0 ){
+    g.pQueueTail = pMsg;
+  }else{
+    g.pQueueHead->pPrev = pMsg;
+  }
+  pMsg->pPrev = 0;
+  g.pQueueHead = pMsg;
+  pthread_mutex_unlock(&g.queueMutex);
+
+  /* Signal the server that the new message has be queued, then
+  ** block waiting for the server to process the message.
+  */
+  pthread_mutex_lock(&pMsg->clientMutex);
+  pthread_cond_signal(&g.serverWakeup);
+  while( pMsg->op!=MSG_Done ){
+    pthread_cond_wait(&pMsg->clientWakeup, &pMsg->clientMutex);
+  }
+  pthread_mutex_unlock(&pMsg->clientMutex);
+
+  /* Destroy the mutex and condition variable of the message.
+  */
+  pthread_mutex_destroy(&pMsg->clientMutex);
+  pthread_cond_destroy(&pMsg->clientWakeup);
+}
+
+/*
+** The following 6 routines are client-side implementations of the
+** core SQLite interfaces:
+**
+**        sqlite3_open
+**        sqlite3_prepare
+**        sqlite3_step
+**        sqlite3_reset
+**        sqlite3_finalize
+**        sqlite3_close
+**
+** Clients should use the following client-side routines instead of 
+** the core routines above.
+**
+**        sqlite3_client_open
+**        sqlite3_client_prepare
+**        sqlite3_client_step
+**        sqlite3_client_reset
+**        sqlite3_client_finalize
+**        sqlite3_client_close
+**
+** Each of these routines creates a message for the desired operation,
+** sends that message to the server, waits for the server to process
+** then message and return a response.
+*/
+int sqlite3_client_open(const char *zDatabaseName, sqlite3 **ppDb){
+  SqlMessage msg;
+  msg.op = MSG_Open;
+  msg.zIn = zDatabaseName;
+  sendToServer(&msg);
+  *ppDb = msg.pDb;
+  return msg.errCode;
+}
+int sqlite3_client_prepare(
+  sqlite3 *pDb,
+  const char *zSql,
+  int nByte,
+  sqlite3_stmt **ppStmt,
+  const char **pzTail
+){
+  SqlMessage msg;
+  msg.op = MSG_Prepare;
+  msg.pDb = pDb;
+  msg.zIn = zSql;
+  msg.nByte = nByte;
+  sendToServer(&msg);
+  *ppStmt = msg.pStmt;
+  if( pzTail ) *pzTail = msg.zOut;
+  return msg.errCode;
+}
+int sqlite3_client_step(sqlite3_stmt *pStmt){
+  SqlMessage msg;
+  msg.op = MSG_Step;
+  msg.pStmt = pStmt;
+  sendToServer(&msg);
+  return msg.errCode;
+}
+int sqlite3_client_reset(sqlite3_stmt *pStmt){
+  SqlMessage msg;
+  msg.op = MSG_Reset;
+  msg.pStmt = pStmt;
+  sendToServer(&msg);
+  return msg.errCode;
+}
+int sqlite3_client_finalize(sqlite3_stmt *pStmt){
+  SqlMessage msg;
+  msg.op = MSG_Finalize;
+  msg.pStmt = pStmt;
+  sendToServer(&msg);
+  return msg.errCode;
+}
+int sqlite3_client_close(sqlite3 *pDb){
+  SqlMessage msg;
+  msg.op = MSG_Close;
+  msg.pDb = pDb;
+  sendToServer(&msg);
+  return msg.errCode;
+}
+
+/*
+** This routine implements the server.  To start the server, first
+** make sure g.serverHalt is false, then create a new detached thread
+** on this procedure.  See the sqlite3_server_start() routine below
+** for an example.  This procedure loops until g.serverHalt becomes
+** true.
+*/
+void *sqlite3_server(void *NotUsed){
+  if( pthread_mutex_trylock(&g.serverMutex) ){
+    return 0;  /* Another server is already running */
+  }
+  sqlite3_enable_shared_cache(1);
+  while( !g.serverHalt ){
+    SqlMessage *pMsg;
+
+    /* Remove the last message from the message queue.
+    */
+    pthread_mutex_lock(&g.queueMutex);
+    while( g.pQueueTail==0 && g.serverHalt==0 ){
+      pthread_cond_wait(&g.serverWakeup, &g.queueMutex);
+    }
+    pMsg = g.pQueueTail;
+    if( pMsg ){
+      if( pMsg->pPrev ){
+        pMsg->pPrev->pNext = 0;
+      }else{
+        g.pQueueHead = 0;
+      }
+      g.pQueueTail = pMsg->pPrev;
+    }
+    pthread_mutex_unlock(&g.queueMutex);
+    if( pMsg==0 ) break;
+
+    /* Process the message just removed
+    */
+    pthread_mutex_lock(&pMsg->clientMutex);
+    switch( pMsg->op ){
+      case MSG_Open: {
+        pMsg->errCode = sqlite3_open(pMsg->zIn, &pMsg->pDb);
+        break;
+      }
+      case MSG_Prepare: {
+        pMsg->errCode = sqlite3_prepare(pMsg->pDb, pMsg->zIn, pMsg->nByte,
+                                        &pMsg->pStmt, &pMsg->zOut);
+        break;
+      }
+      case MSG_Step: {
+        pMsg->errCode = sqlite3_step(pMsg->pStmt);
+        break;
+      }
+      case MSG_Reset: {
+        pMsg->errCode = sqlite3_reset(pMsg->pStmt);
+        break;
+      }
+      case MSG_Finalize: {
+        pMsg->errCode = sqlite3_finalize(pMsg->pStmt);
+        break;
+      }
+      case MSG_Close: {
+        pMsg->errCode = sqlite3_close(pMsg->pDb);
+        break;
+      }
+    }
+
+    /* Signal the client that the message has been processed.
+    */
+    pMsg->op = MSG_Done;
+    pthread_mutex_unlock(&pMsg->clientMutex);
+    pthread_cond_signal(&pMsg->clientWakeup);
+  }
+  pthread_mutex_unlock(&g.serverMutex);
+  return 0;
+}
+
+/*
+** Start a server thread if one is not already running.  If there
+** is aleady a server thread running, the new thread will quickly
+** die and this routine is effectively a no-op.
+*/
+void sqlite3_server_start(void){
+  pthread_t x;
+  int rc;
+  g.serverHalt = 0;
+  rc = pthread_create(&x, 0, sqlite3_server, 0);
+  if( rc==0 ){
+    pthread_detach(x);
+  }
+}
+
+/*
+** A wrapper around sqlite3_server() that decrements the int variable
+** pointed to by the first argument after the sqlite3_server() call
+** returns.
+*/
+static void *serverWrapper(void *pnDecr){
+  void *p = sqlite3_server(0);
+  (*(int*)pnDecr)--;
+  return p;
+}
+
+/*
+** This function is the similar to sqlite3_server_start(), except that
+** the integer pointed to by the first argument is decremented when
+** the server thread exits. 
+*/
+void sqlite3_server_start2(int *pnDecr){
+  pthread_t x;
+  int rc;
+  g.serverHalt = 0;
+  rc = pthread_create(&x, 0, serverWrapper, (void*)pnDecr);
+  if( rc==0 ){
+    pthread_detach(x);
+  }
+}
+
+/*
+** If a server thread is running, then stop it.  If no server is
+** running, this routine is effectively a no-op.
+**
+** This routine waits until the server has actually stopped before
+** returning.
+*/
+void sqlite3_server_stop(void){
+  g.serverHalt = 1;
+  pthread_cond_broadcast(&g.serverWakeup);
+  pthread_mutex_lock(&g.serverMutex);
+  pthread_mutex_unlock(&g.serverMutex);
+}
+
+#endif /* SQLITE_OS_UNIX && SQLITE_THREADSAFE */
+#endif /* defined(SQLITE_SERVER) */

+ 507 - 0
components/external/sqlite/test/test_sqllog.c

@@ -0,0 +1,507 @@
+/*
+** 2012 November 26
+**
+** 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.
+**
+*************************************************************************
+**
+** OVERVIEW
+**
+**   This file contains experimental code used to record data from live
+**   SQLite applications that may be useful for offline analysis. 
+**   Specifically, this module can be used to capture the following
+**   information:
+**
+**     1) The initial contents of all database files opened by the 
+**        application, and
+**
+**     2) All SQL statements executed by the application.
+**
+**   The captured information can then be used to run (for example)
+**   performance analysis looking for slow queries or to look for
+**   optimization opportunities in either the application or in SQLite
+**   itself.
+**
+** USAGE
+**
+**   To use this module, SQLite must be compiled with the SQLITE_ENABLE_SQLLOG
+**   pre-processor symbol defined and this file linked into the application.
+**   One way to link this file into the application is to append the content
+**   of this file onto the end of the "sqlite3.c" amalgamation and then 
+**   recompile the application as normal except with the addition  of the
+**   -DSQLITE_ENABLE_SQLLOG option.
+**
+**   At runtime, logging is enabled by setting environment variable
+**   SQLITE_SQLLOG_DIR to the name of a directory in which to store logged 
+**   data. The logging directory must already exist.
+**
+**   Usually, if the application opens the same database file more than once
+**   (either by attaching it or by using more than one database handle), only
+**   a single copy is made. This behavior may be overridden (so that a 
+**   separate copy is taken each time the database file is opened or attached)
+**   by setting the environment variable SQLITE_SQLLOG_REUSE_FILES to 0.
+**
+** OUTPUT:
+**
+**   The SQLITE_SQLLOG_DIR is populated with three types of files:
+**
+**      sqllog_N.db   - Copies of database files. N may be any integer.
+**
+**      sqllog_N.sql  - A list of SQL statements executed by a single
+**                      connection. N may be any integer.
+**
+**      sqllog.idx    - An index mapping from integer N to a database
+**                      file name - indicating the full path of the
+**                      database from which sqllog_N.db was copied.
+**
+** ERROR HANDLING:
+**
+**   This module attempts to make a best effort to continue logging if an
+**   IO or other error is encountered. For example, if a log file cannot 
+**   be opened logs are not collected for that connection, but other
+**   logging proceeds as expected. Errors are logged by calling sqlite3_log().
+*/
+
+#ifndef _SQLITE3_H_
+#include "sqlite3.h"
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+static int getProcessId(void){
+#if SQLITE_OS_WIN
+  return (int)_getpid();
+#else
+  return (int)getpid();
+#endif
+}
+
+/* Names of environment variables to be used */
+#define ENVIRONMENT_VARIABLE1_NAME "SQLITE_SQLLOG_DIR"
+#define ENVIRONMENT_VARIABLE2_NAME "SQLITE_SQLLOG_REUSE_FILES"
+
+/* Assume that all database and database file names are shorted than this. */
+#define SQLLOG_NAMESZ 512
+
+/* Maximum number of simultaneous database connections the process may
+** open (if any more are opened an error is logged using sqlite3_log()
+** and processing is halted).
+*/
+#define MAX_CONNECTIONS 256
+
+/* There is one instance of this object for each SQLite database connection
+** that is being logged.
+*/
+struct SLConn {
+  int isErr;                      /* True if an error has occurred */
+  sqlite3 *db;                    /* Connection handle */
+  int iLog;                       /* First integer value used in file names */
+  FILE *fd;                       /* File descriptor for log file */
+};
+
+/* This object is a singleton that keeps track of all data loggers.
+*/
+static struct SLGlobal {
+  /* Protected by MUTEX_STATIC_MASTER */
+  sqlite3_mutex *mutex;           /* Recursive mutex */
+  int nConn;                      /* Size of aConn[] array */
+
+  /* Protected by SLGlobal.mutex */
+  int bReuse;                     /* True to avoid extra copies of db files */
+  char zPrefix[SQLLOG_NAMESZ];    /* Prefix for all created files */
+  char zIdx[SQLLOG_NAMESZ];       /* Full path to *.idx file */
+  int iNextLog;                   /* Used to allocate file names */
+  int iNextDb;                    /* Used to allocate database file names */
+  int bRec;                       /* True if testSqllog() is called rec. */
+  int iClock;                     /* Clock value */
+  struct SLConn aConn[MAX_CONNECTIONS];
+} sqllogglobal;
+
+/*
+** Return true if c is an ASCII whitespace character.
+*/
+static int sqllog_isspace(char c){
+  return (c==' ' || c=='\t' || c=='\n' || c=='\v' || c=='\f' || c=='\r');
+}
+
+/*
+** The first argument points to a nul-terminated string containing an SQL
+** command. Before returning, this function sets *pz to point to the start
+** of the first token in this command, and *pn to the number of bytes in 
+** the token. This is used to check if the SQL command is an "ATTACH" or 
+** not.
+*/
+static void sqllogTokenize(const char *z, const char **pz, int *pn){
+  const char *p = z;
+  int n;
+
+  /* Skip past any whitespace */
+  while( sqllog_isspace(*p) ){
+    p++;
+  }
+
+  /* Figure out how long the first token is */
+  *pz = p;
+  n = 0;
+  while( (p[n]>='a' && p[n]<='z') || (p[n]>='A' && p[n]<='Z') ) n++;
+  *pn = n;
+}
+
+/*
+** Check if the logs directory already contains a copy of database file 
+** zFile. If so, return a pointer to the full path of the copy. Otherwise,
+** return NULL.
+**
+** If a non-NULL value is returned, then the caller must arrange to 
+** eventually free it using sqlite3_free().
+*/
+static char *sqllogFindFile(const char *zFile){
+  char *zRet = 0;
+  FILE *fd = 0;
+
+  /* Open the index file for reading */
+  fd = fopen(sqllogglobal.zIdx, "r");
+  if( fd==0 ){
+    sqlite3_log(SQLITE_IOERR, "sqllogFindFile(): error in fopen()");
+    return 0;
+  }
+
+  /* Loop through each entry in the index file. If zFile is not NULL and the
+  ** entry is a match, then set zRet to point to the filename of the existing
+  ** copy and break out of the loop.  */
+  while( feof(fd)==0 ){
+    char zLine[SQLLOG_NAMESZ*2+5];
+    if( fgets(zLine, sizeof(zLine), fd) ){
+      int n;
+      char *z;
+
+      zLine[sizeof(zLine)-1] = '\0';
+      z = zLine;
+      while( *z>='0' && *z<='9' ) z++;
+      while( *z==' ' ) z++;
+
+      n = strlen(z);
+      while( n>0 && sqllog_isspace(z[n-1]) ) n--;
+
+      if( n==strlen(zFile) && 0==memcmp(zFile, z, n) ){
+        char zBuf[16];
+        memset(zBuf, 0, sizeof(zBuf));
+        z = zLine;
+        while( *z>='0' && *z<='9' ){
+          zBuf[z-zLine] = *z;
+          z++;
+        }
+        zRet = sqlite3_mprintf("%s_%s.db", sqllogglobal.zPrefix, zBuf);
+        break;
+      }
+    }
+  }
+
+  if( ferror(fd) ){
+    sqlite3_log(SQLITE_IOERR, "sqllogFindFile(): error reading index file");
+  }
+
+  fclose(fd);
+  return zRet;
+}
+
+static int sqllogFindAttached(
+  struct SLConn *p,               /* Database connection */
+  const char *zSearch,            /* Name to search for (or NULL) */
+  char *zName,                    /* OUT: Name of attached database */
+  char *zFile                     /* OUT: Name of attached file */
+){
+  sqlite3_stmt *pStmt;
+  int rc;
+
+  /* The "PRAGMA database_list" command returns a list of databases in the
+  ** order that they were attached. So a newly attached database is 
+  ** described by the last row returned.  */
+  assert( sqllogglobal.bRec==0 );
+  sqllogglobal.bRec = 1;
+  rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
+  if( rc==SQLITE_OK ){
+    while( SQLITE_ROW==sqlite3_step(pStmt) ){
+      const char *zVal1; int nVal1;
+      const char *zVal2; int nVal2;
+
+      zVal1 = (const char*)sqlite3_column_text(pStmt, 1);
+      nVal1 = sqlite3_column_bytes(pStmt, 1);
+      memcpy(zName, zVal1, nVal1+1);
+
+      zVal2 = (const char*)sqlite3_column_text(pStmt, 2);
+      nVal2 = sqlite3_column_bytes(pStmt, 2);
+      memcpy(zFile, zVal2, nVal2+1);
+
+      if( zSearch && strlen(zSearch)==nVal1 
+       && 0==sqlite3_strnicmp(zSearch, zVal1, nVal1)
+      ){
+        break;
+      }
+    }
+    rc = sqlite3_finalize(pStmt);
+  }
+  sqllogglobal.bRec = 0;
+
+  if( rc!=SQLITE_OK ){
+    sqlite3_log(rc, "sqllogFindAttached(): error in \"PRAGMA database_list\"");
+  }
+  return rc;
+}
+
+
+/*
+** Parameter zSearch is the name of a database attached to the database 
+** connection associated with the first argument. This function creates
+** a backup of this database in the logs directory.
+**
+** The name used for the backup file is automatically generated. Call
+** it zFile.
+**
+** If the bLog parameter is true, then a statement of the following form
+** is written to the log file associated with *p:
+**
+**    ATTACH 'zFile' AS 'zName';
+**
+** Otherwise, if bLog is false, a comment is added to the log file:
+**
+**    -- Main database file is 'zFile'
+**
+** The SLGlobal.mutex mutex is always held when this function is called.
+*/
+static void sqllogCopydb(struct SLConn *p, const char *zSearch, int bLog){
+  char zName[SQLLOG_NAMESZ];      /* Attached database name */
+  char zFile[SQLLOG_NAMESZ];      /* Database file name */
+  char *zFree;
+  char *zInit = 0;
+  int rc;
+
+  rc = sqllogFindAttached(p, zSearch, zName, zFile);
+  if( rc!=SQLITE_OK ) return;
+
+  if( zFile[0]=='\0' ){
+    zInit = sqlite3_mprintf("");
+  }else{
+    if( sqllogglobal.bReuse ){
+      zInit = sqllogFindFile(zFile);
+    }else{
+      zInit = 0;
+    }
+    if( zInit==0 ){
+      int rc;
+      sqlite3 *copy = 0;
+      int iDb;
+
+      /* Generate a file-name to use for the copy of this database */
+      iDb = sqllogglobal.iNextDb++;
+      zInit = sqlite3_mprintf("%s_%d.db", sqllogglobal.zPrefix, iDb);
+
+      /* Create the backup */
+      assert( sqllogglobal.bRec==0 );
+      sqllogglobal.bRec = 1;
+      rc = sqlite3_open(zInit, &copy);
+      if( rc==SQLITE_OK ){
+        sqlite3_backup *pBak;
+        sqlite3_exec(copy, "PRAGMA synchronous = 0", 0, 0, 0);
+        pBak = sqlite3_backup_init(copy, "main", p->db, zName);
+        if( pBak ){
+          sqlite3_backup_step(pBak, -1);
+          rc = sqlite3_backup_finish(pBak);
+        }else{
+          rc = sqlite3_errcode(copy);
+        }
+        sqlite3_close(copy);
+      }
+      sqllogglobal.bRec = 0;
+
+      if( rc==SQLITE_OK ){
+        /* Write an entry into the database index file */
+        FILE *fd = fopen(sqllogglobal.zIdx, "a");
+        if( fd ){
+          fprintf(fd, "%d %s\n", iDb, zFile);
+          fclose(fd);
+        }
+      }else{
+        sqlite3_log(rc, "sqllogCopydb(): error backing up database");
+      }
+    }
+  }
+
+  if( bLog ){
+    zFree = sqlite3_mprintf("ATTACH '%q' AS '%q'; -- clock=%d\n", 
+        zInit, zName, sqllogglobal.iClock++
+    );
+  }else{
+    zFree = sqlite3_mprintf("-- Main database is '%q'\n", zInit);
+  }
+  fprintf(p->fd, "%s", zFree);
+  sqlite3_free(zFree);
+
+  sqlite3_free(zInit);
+}
+
+/*
+** If it is not already open, open the log file for connection *p. 
+**
+** The SLGlobal.mutex mutex is always held when this function is called.
+*/
+static void sqllogOpenlog(struct SLConn *p){
+  /* If the log file has not yet been opened, open it now. */
+  if( p->fd==0 ){
+    char *zLog;
+
+    /* If it is still NULL, have global.zPrefix point to a copy of 
+    ** environment variable $ENVIRONMENT_VARIABLE1_NAME.  */
+    if( sqllogglobal.zPrefix[0]==0 ){
+      FILE *fd;
+      char *zVar = getenv(ENVIRONMENT_VARIABLE1_NAME);
+      if( zVar==0 || strlen(zVar)+10>=(sizeof(sqllogglobal.zPrefix)) ) return;
+      sprintf(sqllogglobal.zPrefix, "%s/sqllog_%d", zVar, getProcessId());
+      sprintf(sqllogglobal.zIdx, "%s.idx", sqllogglobal.zPrefix);
+      if( getenv(ENVIRONMENT_VARIABLE2_NAME) ){
+        sqllogglobal.bReuse = atoi(getenv(ENVIRONMENT_VARIABLE2_NAME));
+      }
+      fd = fopen(sqllogglobal.zIdx, "w");
+      if( fd ) fclose(fd);
+    }
+
+    /* Open the log file */
+    zLog = sqlite3_mprintf("%s_%d.sql", sqllogglobal.zPrefix, p->iLog);
+    p->fd = fopen(zLog, "w");
+    sqlite3_free(zLog);
+    if( p->fd==0 ){
+      sqlite3_log(SQLITE_IOERR, "sqllogOpenlog(): Failed to open log file");
+    }
+  }
+}
+
+/*
+** This function is called if the SQLLOG callback is invoked to report
+** execution of an SQL statement. Parameter p is the connection the statement
+** was executed by and parameter zSql is the text of the statement itself.
+*/
+static void testSqllogStmt(struct SLConn *p, const char *zSql){
+  const char *zFirst;             /* Pointer to first token in zSql */
+  int nFirst;                     /* Size of token zFirst in bytes */
+
+  sqllogTokenize(zSql, &zFirst, &nFirst);
+  if( nFirst!=6 || 0!=sqlite3_strnicmp("ATTACH", zFirst, 6) ){
+    /* Not an ATTACH statement. Write this directly to the log. */
+    fprintf(p->fd, "%s; -- clock=%d\n", zSql, sqllogglobal.iClock++);
+  }else{
+    /* This is an ATTACH statement. Copy the database. */
+    sqllogCopydb(p, 0, 1);
+  }
+}
+
+/*
+** The SQLITE_CONFIG_SQLLOG callback registered by sqlite3_init_sqllog().
+**
+** The eType parameter has the following values:
+**
+**    0:  Opening a new database connection.  zSql is the name of the
+**        file being opened.  db is a pointer to the newly created database
+**        connection.
+**
+**    1:  An SQL statement has run to completion.  zSql is the text of the
+**        SQL statement with all parameters expanded to their actual values.
+**
+**    2:  Closing a database connection.  zSql is NULL.  The db pointer to
+**        the database connection being closed has already been shut down
+**        and cannot be used for any further SQL.
+**
+** The pCtx parameter is a copy of the pointer that was originally passed
+** into the sqlite3_config(SQLITE_CONFIG_SQLLOG) statement.  In this
+** particular implementation, pCtx is always a pointer to the 
+** sqllogglobal global variable define above.
+*/
+static void testSqllog(void *pCtx, sqlite3 *db, const char *zSql, int eType){
+  struct SLConn *p = 0;
+  sqlite3_mutex *master = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER);
+
+  assert( eType==0 || eType==1 || eType==2 );
+  assert( (eType==2)==(zSql==0) );
+
+  /* This is a database open command. */
+  if( eType==0 ){
+    sqlite3_mutex_enter(master);
+    if( sqllogglobal.mutex==0 ){
+      sqllogglobal.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_RECURSIVE);
+    }
+    p = &sqllogglobal.aConn[sqllogglobal.nConn++];
+    p->fd = 0;
+    p->db = db;
+    p->iLog = sqllogglobal.iNextLog++;
+    sqlite3_mutex_leave(master);
+
+    /* Open the log and take a copy of the main database file */
+    sqlite3_mutex_enter(sqllogglobal.mutex);
+    if( sqllogglobal.bRec==0 ){
+      sqllogOpenlog(p);
+      if( p->fd ) sqllogCopydb(p, "main", 0);
+    }
+    sqlite3_mutex_leave(sqllogglobal.mutex);
+  }
+
+  else{
+
+    int i;
+    for(i=0; i<sqllogglobal.nConn; i++){
+      p = &sqllogglobal.aConn[i];
+      if( p->db==db ) break;
+    }
+    if( i==sqllogglobal.nConn ) return;
+
+    /* A database handle close command */
+    if( eType==2 ){
+      sqlite3_mutex_enter(master);
+      if( p->fd ) fclose(p->fd);
+      p->db = 0;
+      p->fd = 0;
+
+      sqllogglobal.nConn--;
+      if( sqllogglobal.nConn==0 ){
+        sqlite3_mutex_free(sqllogglobal.mutex);
+        sqllogglobal.mutex = 0;
+      }else{
+        int nShift = &sqllogglobal.aConn[sqllogglobal.nConn] - p;
+        if( nShift>0 ){
+          memmove(p, &p[1], nShift*sizeof(struct SLConn));
+        }
+      }
+      sqlite3_mutex_leave(master);
+
+    /* An ordinary SQL command. */
+    }else if( p->fd ){
+      sqlite3_mutex_enter(sqllogglobal.mutex);
+      if( sqllogglobal.bRec==0 ){
+        testSqllogStmt(p, zSql);
+      }
+      sqlite3_mutex_leave(sqllogglobal.mutex);
+    }
+  }
+}
+
+/*
+** This function is called either before sqlite3_initialized() or by it.
+** It checks if the SQLITE_SQLLOG_DIR variable is defined, and if so 
+** registers an SQLITE_CONFIG_SQLLOG callback to record the applications
+** database activity.
+*/
+void sqlite3_init_sqllog(void){
+  if( getenv(ENVIRONMENT_VARIABLE1_NAME) ){
+    if( SQLITE_OK==sqlite3_config(SQLITE_CONFIG_SQLLOG, testSqllog, 0) ){
+      memset(&sqllogglobal, 0, sizeof(sqllogglobal));
+      sqllogglobal.bReuse = 1;
+    }
+  }
+}

+ 639 - 0
components/external/sqlite/test/test_stat.c

@@ -0,0 +1,639 @@
+/*
+** 2010 July 12
+**
+** 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 contains an implementation of the "dbstat" virtual table.
+**
+** The dbstat virtual table is used to extract low-level formatting
+** information from an SQLite database in order to implement the
+** "sqlite3_analyzer" utility.  See the ../tool/spaceanal.tcl script
+** for an example implementation.
+*/
+
+#ifndef SQLITE_AMALGAMATION
+# include "sqliteInt.h"
+#endif
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+/*
+** Page paths:
+** 
+**   The value of the 'path' column describes the path taken from the 
+**   root-node of the b-tree structure to each page. The value of the 
+**   root-node path is '/'.
+**
+**   The value of the path for the left-most child page of the root of
+**   a b-tree is '/000/'. (Btrees store content ordered from left to right
+**   so the pages to the left have smaller keys than the pages to the right.)
+**   The next to left-most child of the root page is
+**   '/001', and so on, each sibling page identified by a 3-digit hex 
+**   value. The children of the 451st left-most sibling have paths such
+**   as '/1c2/000/, '/1c2/001/' etc.
+**
+**   Overflow pages are specified by appending a '+' character and a 
+**   six-digit hexadecimal value to the path to the cell they are linked
+**   from. For example, the three overflow pages in a chain linked from 
+**   the left-most cell of the 450th child of the root page are identified
+**   by the paths:
+**
+**      '/1c2/000+000000'         // First page in overflow chain
+**      '/1c2/000+000001'         // Second page in overflow chain
+**      '/1c2/000+000002'         // Third page in overflow chain
+**
+**   If the paths are sorted using the BINARY collation sequence, then
+**   the overflow pages associated with a cell will appear earlier in the
+**   sort-order than its child page:
+**
+**      '/1c2/000/'               // Left-most child of 451st child of root
+*/
+#define VTAB_SCHEMA                                                         \
+  "CREATE TABLE xx( "                                                       \
+  "  name       STRING,           /* Name of table or index */"             \
+  "  path       INTEGER,          /* Path to page from root */"             \
+  "  pageno     INTEGER,          /* Page number */"                        \
+  "  pagetype   STRING,           /* 'internal', 'leaf' or 'overflow' */"   \
+  "  ncell      INTEGER,          /* Cells on page (0 for overflow) */"     \
+  "  payload    INTEGER,          /* Bytes of payload on this page */"      \
+  "  unused     INTEGER,          /* Bytes of unused space on this page */" \
+  "  mx_payload INTEGER,          /* Largest payload size of all cells */"  \
+  "  pgoffset   INTEGER,          /* Offset of page in file */"             \
+  "  pgsize     INTEGER           /* Size of the page */"                   \
+  ");"
+
+
+typedef struct StatTable StatTable;
+typedef struct StatCursor StatCursor;
+typedef struct StatPage StatPage;
+typedef struct StatCell StatCell;
+
+struct StatCell {
+  int nLocal;                     /* Bytes of local payload */
+  u32 iChildPg;                   /* Child node (or 0 if this is a leaf) */
+  int nOvfl;                      /* Entries in aOvfl[] */
+  u32 *aOvfl;                     /* Array of overflow page numbers */
+  int nLastOvfl;                  /* Bytes of payload on final overflow page */
+  int iOvfl;                      /* Iterates through aOvfl[] */
+};
+
+struct StatPage {
+  u32 iPgno;
+  DbPage *pPg;
+  int iCell;
+
+  char *zPath;                    /* Path to this page */
+
+  /* Variables populated by statDecodePage(): */
+  u8 flags;                       /* Copy of flags byte */
+  int nCell;                      /* Number of cells on page */
+  int nUnused;                    /* Number of unused bytes on page */
+  StatCell *aCell;                /* Array of parsed cells */
+  u32 iRightChildPg;              /* Right-child page number (or 0) */
+  int nMxPayload;                 /* Largest payload of any cell on this page */
+};
+
+struct StatCursor {
+  sqlite3_vtab_cursor base;
+  sqlite3_stmt *pStmt;            /* Iterates through set of root pages */
+  int isEof;                      /* After pStmt has returned SQLITE_DONE */
+
+  StatPage aPage[32];
+  int iPage;                      /* Current entry in aPage[] */
+
+  /* Values to return. */
+  char *zName;                    /* Value of 'name' column */
+  char *zPath;                    /* Value of 'path' column */
+  u32 iPageno;                    /* Value of 'pageno' column */
+  char *zPagetype;                /* Value of 'pagetype' column */
+  int nCell;                      /* Value of 'ncell' column */
+  int nPayload;                   /* Value of 'payload' column */
+  int nUnused;                    /* Value of 'unused' column */
+  int nMxPayload;                 /* Value of 'mx_payload' column */
+  i64 iOffset;                    /* Value of 'pgOffset' column */
+  int szPage;                     /* Value of 'pgSize' column */
+};
+
+struct StatTable {
+  sqlite3_vtab base;
+  sqlite3 *db;
+};
+
+#ifndef get2byte
+# define get2byte(x)   ((x)[0]<<8 | (x)[1])
+#endif
+
+/*
+** Connect to or create a statvfs virtual table.
+*/
+static int statConnect(
+  sqlite3 *db,
+  void *pAux,
+  int argc, const char *const*argv,
+  sqlite3_vtab **ppVtab,
+  char **pzErr
+){
+  StatTable *pTab;
+
+  pTab = (StatTable *)sqlite3_malloc(sizeof(StatTable));
+  memset(pTab, 0, sizeof(StatTable));
+  pTab->db = db;
+
+  sqlite3_declare_vtab(db, VTAB_SCHEMA);
+  *ppVtab = &pTab->base;
+  return SQLITE_OK;
+}
+
+/*
+** Disconnect from or destroy a statvfs virtual table.
+*/
+static int statDisconnect(sqlite3_vtab *pVtab){
+  sqlite3_free(pVtab);
+  return SQLITE_OK;
+}
+
+/*
+** There is no "best-index". This virtual table always does a linear
+** scan of the binary VFS log file.
+*/
+static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+
+  /* Records are always returned in ascending order of (name, path). 
+  ** If this will satisfy the client, set the orderByConsumed flag so that 
+  ** SQLite does not do an external sort.
+  */
+  if( ( pIdxInfo->nOrderBy==1
+     && pIdxInfo->aOrderBy[0].iColumn==0
+     && pIdxInfo->aOrderBy[0].desc==0
+     ) ||
+      ( pIdxInfo->nOrderBy==2
+     && pIdxInfo->aOrderBy[0].iColumn==0
+     && pIdxInfo->aOrderBy[0].desc==0
+     && pIdxInfo->aOrderBy[1].iColumn==1
+     && pIdxInfo->aOrderBy[1].desc==0
+     )
+  ){
+    pIdxInfo->orderByConsumed = 1;
+  }
+
+  pIdxInfo->estimatedCost = 10.0;
+  return SQLITE_OK;
+}
+
+/*
+** Open a new statvfs cursor.
+*/
+static int statOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+  StatTable *pTab = (StatTable *)pVTab;
+  StatCursor *pCsr;
+  int rc;
+
+  pCsr = (StatCursor *)sqlite3_malloc(sizeof(StatCursor));
+  memset(pCsr, 0, sizeof(StatCursor));
+  pCsr->base.pVtab = pVTab;
+
+  rc = sqlite3_prepare_v2(pTab->db, 
+      "SELECT 'sqlite_master' AS name, 1 AS rootpage, 'table' AS type"
+      "  UNION ALL  "
+      "SELECT name, rootpage, type FROM sqlite_master WHERE rootpage!=0"
+      "  ORDER BY name", -1,
+      &pCsr->pStmt, 0
+  );
+  if( rc!=SQLITE_OK ){
+    sqlite3_free(pCsr);
+    return rc;
+  }
+
+  *ppCursor = (sqlite3_vtab_cursor *)pCsr;
+  return SQLITE_OK;
+}
+
+static void statClearPage(StatPage *p){
+  int i;
+  for(i=0; i<p->nCell; i++){
+    sqlite3_free(p->aCell[i].aOvfl);
+  }
+  sqlite3PagerUnref(p->pPg);
+  sqlite3_free(p->aCell);
+  sqlite3_free(p->zPath);
+  memset(p, 0, sizeof(StatPage));
+}
+
+static void statResetCsr(StatCursor *pCsr){
+  int i;
+  sqlite3_reset(pCsr->pStmt);
+  for(i=0; i<ArraySize(pCsr->aPage); i++){
+    statClearPage(&pCsr->aPage[i]);
+  }
+  pCsr->iPage = 0;
+  sqlite3_free(pCsr->zPath);
+  pCsr->zPath = 0;
+}
+
+/*
+** Close a statvfs cursor.
+*/
+static int statClose(sqlite3_vtab_cursor *pCursor){
+  StatCursor *pCsr = (StatCursor *)pCursor;
+  statResetCsr(pCsr);
+  sqlite3_finalize(pCsr->pStmt);
+  sqlite3_free(pCsr);
+  return SQLITE_OK;
+}
+
+static void getLocalPayload(
+  int nUsable,                    /* Usable bytes per page */
+  u8 flags,                       /* Page flags */
+  int nTotal,                     /* Total record (payload) size */
+  int *pnLocal                    /* OUT: Bytes stored locally */
+){
+  int nLocal;
+  int nMinLocal;
+  int nMaxLocal;
+ 
+  if( flags==0x0D ){              /* Table leaf node */
+    nMinLocal = (nUsable - 12) * 32 / 255 - 23;
+    nMaxLocal = nUsable - 35;
+  }else{                          /* Index interior and leaf nodes */
+    nMinLocal = (nUsable - 12) * 32 / 255 - 23;
+    nMaxLocal = (nUsable - 12) * 64 / 255 - 23;
+  }
+
+  nLocal = nMinLocal + (nTotal - nMinLocal) % (nUsable - 4);
+  if( nLocal>nMaxLocal ) nLocal = nMinLocal;
+  *pnLocal = nLocal;
+}
+
+static int statDecodePage(Btree *pBt, StatPage *p){
+  int nUnused;
+  int iOff;
+  int nHdr;
+  int isLeaf;
+  int szPage;
+
+  u8 *aData = sqlite3PagerGetData(p->pPg);
+  u8 *aHdr = &aData[p->iPgno==1 ? 100 : 0];
+
+  p->flags = aHdr[0];
+  p->nCell = get2byte(&aHdr[3]);
+  p->nMxPayload = 0;
+
+  isLeaf = (p->flags==0x0A || p->flags==0x0D);
+  nHdr = 12 - isLeaf*4 + (p->iPgno==1)*100;
+
+  nUnused = get2byte(&aHdr[5]) - nHdr - 2*p->nCell;
+  nUnused += (int)aHdr[7];
+  iOff = get2byte(&aHdr[1]);
+  while( iOff ){
+    nUnused += get2byte(&aData[iOff+2]);
+    iOff = get2byte(&aData[iOff]);
+  }
+  p->nUnused = nUnused;
+  p->iRightChildPg = isLeaf ? 0 : sqlite3Get4byte(&aHdr[8]);
+  szPage = sqlite3BtreeGetPageSize(pBt);
+
+  if( p->nCell ){
+    int i;                        /* Used to iterate through cells */
+    int nUsable = szPage - sqlite3BtreeGetReserve(pBt);
+
+    p->aCell = sqlite3_malloc((p->nCell+1) * sizeof(StatCell));
+    memset(p->aCell, 0, (p->nCell+1) * sizeof(StatCell));
+
+    for(i=0; i<p->nCell; i++){
+      StatCell *pCell = &p->aCell[i];
+
+      iOff = get2byte(&aData[nHdr+i*2]);
+      if( !isLeaf ){
+        pCell->iChildPg = sqlite3Get4byte(&aData[iOff]);
+        iOff += 4;
+      }
+      if( p->flags==0x05 ){
+        /* A table interior node. nPayload==0. */
+      }else{
+        u32 nPayload;             /* Bytes of payload total (local+overflow) */
+        int nLocal;               /* Bytes of payload stored locally */
+        iOff += getVarint32(&aData[iOff], nPayload);
+        if( p->flags==0x0D ){
+          u64 dummy;
+          iOff += sqlite3GetVarint(&aData[iOff], &dummy);
+        }
+        if( nPayload>(u32)p->nMxPayload ) p->nMxPayload = nPayload;
+        getLocalPayload(nUsable, p->flags, nPayload, &nLocal);
+        pCell->nLocal = nLocal;
+        assert( nLocal>=0 );
+        assert( nPayload>=(u32)nLocal );
+        assert( nLocal<=(nUsable-35) );
+        if( nPayload>(u32)nLocal ){
+          int j;
+          int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4);
+          pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4);
+          pCell->nOvfl = nOvfl;
+          pCell->aOvfl = sqlite3_malloc(sizeof(u32)*nOvfl);
+          pCell->aOvfl[0] = sqlite3Get4byte(&aData[iOff+nLocal]);
+          for(j=1; j<nOvfl; j++){
+            int rc;
+            u32 iPrev = pCell->aOvfl[j-1];
+            DbPage *pPg = 0;
+            rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPrev, &pPg);
+            if( rc!=SQLITE_OK ){
+              assert( pPg==0 );
+              return rc;
+            } 
+            pCell->aOvfl[j] = sqlite3Get4byte(sqlite3PagerGetData(pPg));
+            sqlite3PagerUnref(pPg);
+          }
+        }
+      }
+    }
+  }
+
+  return SQLITE_OK;
+}
+
+/*
+** Populate the pCsr->iOffset and pCsr->szPage member variables. Based on
+** the current value of pCsr->iPageno.
+*/
+static void statSizeAndOffset(StatCursor *pCsr){
+  StatTable *pTab = (StatTable *)((sqlite3_vtab_cursor *)pCsr)->pVtab;
+  Btree *pBt = pTab->db->aDb[0].pBt;
+  Pager *pPager = sqlite3BtreePager(pBt);
+  sqlite3_file *fd;
+  sqlite3_int64 x[2];
+
+  /* The default page size and offset */
+  pCsr->szPage = sqlite3BtreeGetPageSize(pBt);
+  pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1);
+
+  /* If connected to a ZIPVFS backend, override the page size and
+  ** offset with actual values obtained from ZIPVFS.
+  */
+  fd = sqlite3PagerFile(pPager);
+  x[0] = pCsr->iPageno;
+  if( sqlite3OsFileControl(fd, 230440, &x)==SQLITE_OK ){
+    pCsr->iOffset = x[0];
+    pCsr->szPage = (int)x[1];
+  }
+}
+
+/*
+** Move a statvfs cursor to the next entry in the file.
+*/
+static int statNext(sqlite3_vtab_cursor *pCursor){
+  int rc;
+  int nPayload;
+  StatCursor *pCsr = (StatCursor *)pCursor;
+  StatTable *pTab = (StatTable *)pCursor->pVtab;
+  Btree *pBt = pTab->db->aDb[0].pBt;
+  Pager *pPager = sqlite3BtreePager(pBt);
+
+  sqlite3_free(pCsr->zPath);
+  pCsr->zPath = 0;
+
+  if( pCsr->aPage[0].pPg==0 ){
+    rc = sqlite3_step(pCsr->pStmt);
+    if( rc==SQLITE_ROW ){
+      int nPage;
+      u32 iRoot = (u32)sqlite3_column_int64(pCsr->pStmt, 1);
+      sqlite3PagerPagecount(pPager, &nPage);
+      if( nPage==0 ){
+        pCsr->isEof = 1;
+        return sqlite3_reset(pCsr->pStmt);
+      }
+      rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg);
+      pCsr->aPage[0].iPgno = iRoot;
+      pCsr->aPage[0].iCell = 0;
+      pCsr->aPage[0].zPath = sqlite3_mprintf("/");
+      pCsr->iPage = 0;
+    }else{
+      pCsr->isEof = 1;
+      return sqlite3_reset(pCsr->pStmt);
+    }
+  }else{
+
+    /* Page p itself has already been visited. */
+    StatPage *p = &pCsr->aPage[pCsr->iPage];
+
+    while( p->iCell<p->nCell ){
+      StatCell *pCell = &p->aCell[p->iCell];
+      if( pCell->iOvfl<pCell->nOvfl ){
+        int nUsable = sqlite3BtreeGetPageSize(pBt)-sqlite3BtreeGetReserve(pBt);
+        pCsr->zName = (char *)sqlite3_column_text(pCsr->pStmt, 0);
+        pCsr->iPageno = pCell->aOvfl[pCell->iOvfl];
+        pCsr->zPagetype = "overflow";
+        pCsr->nCell = 0;
+        pCsr->nMxPayload = 0;
+        pCsr->zPath = sqlite3_mprintf(
+            "%s%.3x+%.6x", p->zPath, p->iCell, pCell->iOvfl
+        );
+        if( pCell->iOvfl<pCell->nOvfl-1 ){
+          pCsr->nUnused = 0;
+          pCsr->nPayload = nUsable - 4;
+        }else{
+          pCsr->nPayload = pCell->nLastOvfl;
+          pCsr->nUnused = nUsable - 4 - pCsr->nPayload;
+        }
+        pCell->iOvfl++;
+        statSizeAndOffset(pCsr);
+        return SQLITE_OK;
+      }
+      if( p->iRightChildPg ) break;
+      p->iCell++;
+    }
+
+    while( !p->iRightChildPg || p->iCell>p->nCell ){
+      statClearPage(p);
+      if( pCsr->iPage==0 ) return statNext(pCursor);
+      pCsr->iPage--;
+      p = &pCsr->aPage[pCsr->iPage];
+    }
+    pCsr->iPage++;
+    assert( p==&pCsr->aPage[pCsr->iPage-1] );
+
+    if( p->iCell==p->nCell ){
+      p[1].iPgno = p->iRightChildPg;
+    }else{
+      p[1].iPgno = p->aCell[p->iCell].iChildPg;
+    }
+    rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg);
+    p[1].iCell = 0;
+    p[1].zPath = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell);
+    p->iCell++;
+  }
+
+
+  /* Populate the StatCursor fields with the values to be returned
+  ** by the xColumn() and xRowid() methods.
+  */
+  if( rc==SQLITE_OK ){
+    int i;
+    StatPage *p = &pCsr->aPage[pCsr->iPage];
+    pCsr->zName = (char *)sqlite3_column_text(pCsr->pStmt, 0);
+    pCsr->iPageno = p->iPgno;
+
+    statDecodePage(pBt, p);
+    statSizeAndOffset(pCsr);
+
+    switch( p->flags ){
+      case 0x05:             /* table internal */
+      case 0x02:             /* index internal */
+        pCsr->zPagetype = "internal";
+        break;
+      case 0x0D:             /* table leaf */
+      case 0x0A:             /* index leaf */
+        pCsr->zPagetype = "leaf";
+        break;
+      default:
+        pCsr->zPagetype = "corrupted";
+        break;
+    }
+    pCsr->nCell = p->nCell;
+    pCsr->nUnused = p->nUnused;
+    pCsr->nMxPayload = p->nMxPayload;
+    pCsr->zPath = sqlite3_mprintf("%s", p->zPath);
+    nPayload = 0;
+    for(i=0; i<p->nCell; i++){
+      nPayload += p->aCell[i].nLocal;
+    }
+    pCsr->nPayload = nPayload;
+  }
+
+  return rc;
+}
+
+static int statEof(sqlite3_vtab_cursor *pCursor){
+  StatCursor *pCsr = (StatCursor *)pCursor;
+  return pCsr->isEof;
+}
+
+static int statFilter(
+  sqlite3_vtab_cursor *pCursor, 
+  int idxNum, const char *idxStr,
+  int argc, sqlite3_value **argv
+){
+  StatCursor *pCsr = (StatCursor *)pCursor;
+
+  statResetCsr(pCsr);
+  return statNext(pCursor);
+}
+
+static int statColumn(
+  sqlite3_vtab_cursor *pCursor, 
+  sqlite3_context *ctx, 
+  int i
+){
+  StatCursor *pCsr = (StatCursor *)pCursor;
+  switch( i ){
+    case 0:            /* name */
+      sqlite3_result_text(ctx, pCsr->zName, -1, SQLITE_STATIC);
+      break;
+    case 1:            /* path */
+      sqlite3_result_text(ctx, pCsr->zPath, -1, SQLITE_TRANSIENT);
+      break;
+    case 2:            /* pageno */
+      sqlite3_result_int64(ctx, pCsr->iPageno);
+      break;
+    case 3:            /* pagetype */
+      sqlite3_result_text(ctx, pCsr->zPagetype, -1, SQLITE_STATIC);
+      break;
+    case 4:            /* ncell */
+      sqlite3_result_int(ctx, pCsr->nCell);
+      break;
+    case 5:            /* payload */
+      sqlite3_result_int(ctx, pCsr->nPayload);
+      break;
+    case 6:            /* unused */
+      sqlite3_result_int(ctx, pCsr->nUnused);
+      break;
+    case 7:            /* mx_payload */
+      sqlite3_result_int(ctx, pCsr->nMxPayload);
+      break;
+    case 8:            /* pgoffset */
+      sqlite3_result_int64(ctx, pCsr->iOffset);
+      break;
+    case 9:            /* pgsize */
+      sqlite3_result_int(ctx, pCsr->szPage);
+      break;
+  }
+  return SQLITE_OK;
+}
+
+static int statRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+  StatCursor *pCsr = (StatCursor *)pCursor;
+  *pRowid = pCsr->iPageno;
+  return SQLITE_OK;
+}
+
+int sqlite3_dbstat_register(sqlite3 *db){
+  static sqlite3_module dbstat_module = {
+    0,                            /* iVersion */
+    statConnect,                  /* xCreate */
+    statConnect,                  /* xConnect */
+    statBestIndex,                /* xBestIndex */
+    statDisconnect,               /* xDisconnect */
+    statDisconnect,               /* xDestroy */
+    statOpen,                     /* xOpen - open a cursor */
+    statClose,                    /* xClose - close a cursor */
+    statFilter,                   /* xFilter - configure scan constraints */
+    statNext,                     /* xNext - advance a cursor */
+    statEof,                      /* xEof - check for end of scan */
+    statColumn,                   /* xColumn - read data */
+    statRowid,                    /* xRowid - read data */
+    0,                            /* xUpdate */
+    0,                            /* xBegin */
+    0,                            /* xSync */
+    0,                            /* xCommit */
+    0,                            /* xRollback */
+    0,                            /* xFindMethod */
+    0,                            /* xRename */
+  };
+  sqlite3_create_module(db, "dbstat", &dbstat_module, 0);
+  return SQLITE_OK;
+}
+
+#endif
+
+#if defined(SQLITE_TEST) || TCLSH==2
+#include <tcl.h>
+
+static int test_dbstat(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+  Tcl_AppendResult(interp, "dbstat not available because of "
+                           "SQLITE_OMIT_VIRTUALTABLE", (void*)0);
+  return TCL_ERROR;
+#else
+  struct SqliteDb { sqlite3 *db; };
+  char *zDb;
+  Tcl_CmdInfo cmdInfo;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+
+  zDb = Tcl_GetString(objv[1]);
+  if( Tcl_GetCommandInfo(interp, zDb, &cmdInfo) ){
+    sqlite3* db = ((struct SqliteDb*)cmdInfo.objClientData)->db;
+    sqlite3_dbstat_register(db);
+  }
+  return TCL_OK;
+#endif
+}
+
+int SqlitetestStat_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "register_dbstat_vtab", test_dbstat, 0, 0);
+  return TCL_OK;
+}
+#endif /* if defined(SQLITE_TEST) || TCLSH==2 */

+ 356 - 0
components/external/sqlite/test/test_superlock.c

@@ -0,0 +1,356 @@
+/*
+** 2010 November 19
+**
+** 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.
+**
+*************************************************************************
+** Example code for obtaining an exclusive lock on an SQLite database
+** file. This method is complicated, but works for both WAL and rollback
+** mode database files. The interface to the example code in this file 
+** consists of the following two functions:
+**
+**   sqlite3demo_superlock()
+**   sqlite3demo_superunlock()
+*/
+
+#include <sqlite3.h>
+#include <string.h>               /* memset(), strlen() */
+#include <assert.h>               /* assert() */
+
+/*
+** A structure to collect a busy-handler callback and argument and a count
+** of the number of times it has been invoked.
+*/
+struct SuperlockBusy {
+  int (*xBusy)(void*,int);        /* Pointer to busy-handler function */
+  void *pBusyArg;                 /* First arg to pass to xBusy */
+  int nBusy;                      /* Number of times xBusy has been invoked */
+};
+typedef struct SuperlockBusy SuperlockBusy;
+
+/*
+** An instance of the following structure is allocated for each active
+** superlock. The opaque handle returned by sqlite3demo_superlock() is
+** actually a pointer to an instance of this structure.
+*/
+struct Superlock {
+  sqlite3 *db;                    /* Database handle used to lock db */
+  int bWal;                       /* True if db is a WAL database */
+};
+typedef struct Superlock Superlock;
+
+/*
+** The pCtx pointer passed to this function is actually a pointer to a
+** SuperlockBusy structure. Invoke the busy-handler function encapsulated
+** by the structure and return the result.
+*/
+static int superlockBusyHandler(void *pCtx, int UNUSED){
+  SuperlockBusy *pBusy = (SuperlockBusy *)pCtx;
+  if( pBusy->xBusy==0 ) return 0;
+  return pBusy->xBusy(pBusy->pBusyArg, pBusy->nBusy++);
+}
+
+/*
+** This function is used to determine if the main database file for 
+** connection db is open in WAL mode or not. If no error occurs and the
+** database file is in WAL mode, set *pbWal to true and return SQLITE_OK.
+** If it is not in WAL mode, set *pbWal to false.
+**
+** If an error occurs, return an SQLite error code. The value of *pbWal
+** is undefined in this case.
+*/
+static int superlockIsWal(Superlock *pLock){
+  int rc;                         /* Return Code */
+  sqlite3_stmt *pStmt;            /* Compiled PRAGMA journal_mode statement */
+
+  rc = sqlite3_prepare(pLock->db, "PRAGMA main.journal_mode", -1, &pStmt, 0);
+  if( rc!=SQLITE_OK ) return rc;
+
+  pLock->bWal = 0;
+  if( SQLITE_ROW==sqlite3_step(pStmt) ){
+    const char *zMode = (const char *)sqlite3_column_text(pStmt, 0);
+    if( zMode && strlen(zMode)==3 && sqlite3_strnicmp("wal", zMode, 3)==0 ){
+      pLock->bWal = 1;
+    }
+  }
+
+  return sqlite3_finalize(pStmt);
+}
+
+/*
+** Obtain an exclusive shm-lock on nByte bytes starting at offset idx
+** of the file fd. If the lock cannot be obtained immediately, invoke
+** the busy-handler until either it is obtained or the busy-handler
+** callback returns 0.
+*/
+static int superlockShmLock(
+  sqlite3_file *fd,               /* Database file handle */
+  int idx,                        /* Offset of shm-lock to obtain */
+  int nByte,                      /* Number of consective bytes to lock */
+  SuperlockBusy *pBusy            /* Busy-handler wrapper object */
+){
+  int rc;
+  int (*xShmLock)(sqlite3_file*, int, int, int) = fd->pMethods->xShmLock;
+  do {
+    rc = xShmLock(fd, idx, nByte, SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE);
+  }while( rc==SQLITE_BUSY && superlockBusyHandler((void *)pBusy, 0) );
+  return rc;
+}
+
+/*
+** Obtain the extra locks on the database file required for WAL databases.
+** Invoke the supplied busy-handler as required.
+*/
+static int superlockWalLock(
+  sqlite3 *db,                    /* Database handle open on WAL database */
+  SuperlockBusy *pBusy            /* Busy handler wrapper object */
+){
+  int rc;                         /* Return code */
+  sqlite3_file *fd = 0;           /* Main database file handle */
+  void volatile *p = 0;           /* Pointer to first page of shared memory */
+
+  /* Obtain a pointer to the sqlite3_file object open on the main db file. */
+  rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);
+  if( rc!=SQLITE_OK ) return rc;
+
+  /* Obtain the "recovery" lock. Normally, this lock is only obtained by
+  ** clients running database recovery.  
+  */
+  rc = superlockShmLock(fd, 2, 1, pBusy);
+  if( rc!=SQLITE_OK ) return rc;
+
+  /* Zero the start of the first shared-memory page. This means that any
+  ** clients that open read or write transactions from this point on will
+  ** have to run recovery before proceeding. Since they need the "recovery"
+  ** lock that this process is holding to do that, no new read or write
+  ** transactions may now be opened. Nor can a checkpoint be run, for the
+  ** same reason.
+  */
+  rc = fd->pMethods->xShmMap(fd, 0, 32*1024, 1, &p);
+  if( rc!=SQLITE_OK ) return rc;
+  memset((void *)p, 0, 32);
+
+  /* Obtain exclusive locks on all the "read-lock" slots. Once these locks
+  ** are held, it is guaranteed that there are no active reader, writer or 
+  ** checkpointer clients.
+  */
+  rc = superlockShmLock(fd, 3, SQLITE_SHM_NLOCK-3, pBusy);
+  return rc;
+}
+
+/*
+** Release a superlock held on a database file. The argument passed to 
+** this function must have been obtained from a successful call to
+** sqlite3demo_superlock().
+*/
+void sqlite3demo_superunlock(void *pLock){
+  Superlock *p = (Superlock *)pLock;
+  if( p->bWal ){
+    int rc;                         /* Return code */
+    int flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE;
+    sqlite3_file *fd = 0;
+    rc = sqlite3_file_control(p->db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);
+    if( rc==SQLITE_OK ){
+      fd->pMethods->xShmLock(fd, 2, 1, flags);
+      fd->pMethods->xShmLock(fd, 3, SQLITE_SHM_NLOCK-3, flags);
+    }
+  }
+  sqlite3_close(p->db);
+  sqlite3_free(p);
+}
+
+/*
+** Obtain a superlock on the database file identified by zPath, using the
+** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is
+** returned and output variable *ppLock is populated with an opaque handle
+** that may be used with sqlite3demo_superunlock() to release the lock.
+**
+** If an error occurs, *ppLock is set to 0 and an SQLite error code 
+** (e.g. SQLITE_BUSY) is returned.
+**
+** If a required lock cannot be obtained immediately and the xBusy parameter
+** to this function is not NULL, then xBusy is invoked in the same way
+** as a busy-handler registered with SQLite (using sqlite3_busy_handler())
+** until either the lock can be obtained or the busy-handler function returns
+** 0 (indicating "give up").
+*/
+int sqlite3demo_superlock(
+  const char *zPath,              /* Path to database file to lock */
+  const char *zVfs,               /* VFS to use to access database file */
+  int (*xBusy)(void*,int),        /* Busy handler callback */
+  void *pBusyArg,                 /* Context arg for busy handler */
+  void **ppLock                   /* OUT: Context to pass to superunlock() */
+){
+  SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */
+  int rc;                         /* Return code */
+  Superlock *pLock;
+
+  pLock = sqlite3_malloc(sizeof(Superlock));
+  if( !pLock ) return SQLITE_NOMEM;
+  memset(pLock, 0, sizeof(Superlock));
+
+  /* Open a database handle on the file to superlock. */
+  rc = sqlite3_open_v2(
+      zPath, &pLock->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs
+  );
+
+  /* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not
+  ** a WAL database, this is all we need to do.  
+  **
+  ** A wrapper function is used to invoke the busy-handler instead of
+  ** registering the busy-handler function supplied by the user directly
+  ** with SQLite. This is because the same busy-handler function may be
+  ** invoked directly later on when attempting to obtain the extra locks
+  ** required in WAL mode. By using the wrapper, we are able to guarantee
+  ** that the "nBusy" integer parameter passed to the users busy-handler
+  ** represents the total number of busy-handler invocations made within
+  ** this call to sqlite3demo_superlock(), including any made during the
+  ** "BEGIN EXCLUSIVE".
+  */
+  if( rc==SQLITE_OK ){
+    busy.xBusy = xBusy;
+    busy.pBusyArg = pBusyArg;
+    sqlite3_busy_handler(pLock->db, superlockBusyHandler, (void *)&busy);
+    rc = sqlite3_exec(pLock->db, "BEGIN EXCLUSIVE", 0, 0, 0);
+  }
+
+  /* If the BEGIN EXCLUSIVE was executed successfully and this is a WAL
+  ** database, call superlockWalLock() to obtain the extra locks required
+  ** to prevent readers, writers and/or checkpointers from accessing the
+  ** db while this process is holding the superlock.
+  **
+  ** Before attempting any WAL locks, commit the transaction started above
+  ** to drop the WAL read and write locks currently held. Otherwise, the
+  ** new WAL locks may conflict with the old.
+  */
+  if( rc==SQLITE_OK ){
+    if( SQLITE_OK==(rc = superlockIsWal(pLock)) && pLock->bWal ){
+      rc = sqlite3_exec(pLock->db, "COMMIT", 0, 0, 0);
+      if( rc==SQLITE_OK ){
+        rc = superlockWalLock(pLock->db, &busy);
+      }
+    }
+  }
+
+  if( rc!=SQLITE_OK ){
+    sqlite3demo_superunlock(pLock);
+    *ppLock = 0;
+  }else{
+    *ppLock = pLock;
+  }
+
+  return rc;
+}
+
+/*
+** End of example code. Everything below here is the test harness.
+**************************************************************************
+**************************************************************************
+*************************************************************************/
+
+
+#ifdef SQLITE_TEST
+
+#include <tcl.h>
+
+struct InterpAndScript {
+  Tcl_Interp *interp;
+  Tcl_Obj *pScript;
+};
+typedef struct InterpAndScript InterpAndScript;
+
+static void superunlock_del(ClientData cd){
+  sqlite3demo_superunlock((void *)cd);
+}
+
+static int superunlock_cmd(
+  ClientData cd,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=1 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "");
+    return TCL_ERROR;
+  }
+  Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+  return TCL_OK;
+}
+
+static int superlock_busy(void *pCtx, int nBusy){
+  InterpAndScript *p = (InterpAndScript *)pCtx;
+  Tcl_Obj *pEval;                 /* Script to evaluate */
+  int iVal = 0;                   /* Value to return */
+
+  pEval = Tcl_DuplicateObj(p->pScript);
+  Tcl_IncrRefCount(pEval);
+  Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy));
+  Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
+  Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal);
+  Tcl_DecrRefCount(pEval);
+
+  return iVal;
+}
+
+/*
+** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT
+*/
+static int superlock_cmd(
+  ClientData cd,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  void *pLock;                    /* Lock context */
+  char *zPath;
+  char *zVfs = 0;
+  InterpAndScript busy = {0, 0};
+  int (*xBusy)(void*,int) = 0;    /* Busy handler callback */
+  int rc;                         /* Return code from sqlite3demo_superlock() */
+
+  if( objc<3 || objc>5 ){
+    Tcl_WrongNumArgs(
+        interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?");
+    return TCL_ERROR;
+  }
+
+  zPath = Tcl_GetString(objv[2]);
+
+  if( objc>3 ){
+    zVfs = Tcl_GetString(objv[3]);
+    if( strlen(zVfs)==0 ) zVfs = 0;
+  }
+  if( objc>4 ){
+    busy.interp = interp;
+    busy.pScript = objv[4];
+    xBusy = superlock_busy;
+  }
+
+  rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock);
+  assert( rc==SQLITE_OK || pLock==0 );
+  assert( rc!=SQLITE_OK || pLock!=0 );
+
+  if( rc!=SQLITE_OK ){
+    extern const char *sqlite3ErrStr(int);
+    Tcl_ResetResult(interp);
+    Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0);
+    return TCL_ERROR;
+  }
+
+  Tcl_CreateObjCommand(
+      interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del
+  );
+  Tcl_SetObjResult(interp, objv[1]);
+  return TCL_OK;
+}
+
+int SqliteSuperlock_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0);
+  return TCL_OK;
+}
+#endif

+ 705 - 0
components/external/sqlite/test/test_syscall.c

@@ -0,0 +1,705 @@
+/*
+** 2011 March 28
+**
+** 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.
+**
+*************************************************************************
+**
+** The code in this file implements a Tcl interface used to test error
+** handling in the os_unix.c module. Wrapper functions that support fault
+** injection are registered as the low-level OS functions using the 
+** xSetSystemCall() method of the VFS. The Tcl interface is as follows:
+**
+**
+**   test_syscall install LIST
+**     Install wrapper functions for all system calls in argument LIST.
+**     LIST must be a list consisting of zero or more of the following
+**     literal values:
+**
+**         open        close      access   getcwd   stat      fstat    
+**         ftruncate   fcntl      read     pread    pread64   write
+**         pwrite      pwrite64   fchmod   fallocate mmap
+**
+**   test_syscall uninstall
+**     Uninstall all wrapper functions.
+**
+**   test_syscall fault ?COUNT PERSIST?
+**     If [test_syscall fault] is invoked without the two arguments, fault
+**     injection is disabled. Otherwise, fault injection is configured to
+**     cause a failure on the COUNT'th next call to a system call with a
+**     wrapper function installed. A COUNT value of 1 means fail the next
+**     system call. 
+** 
+**     Argument PERSIST is interpreted as a boolean. If true, the all
+**     system calls following the initial failure also fail. Otherwise, only
+**     the single transient failure is injected.
+**
+**   test_syscall errno CALL ERRNO
+**     Set the value that the global "errno" is set to following a fault
+**     in call CALL. Argument CALL must be one of the system call names
+**     listed above (under [test_syscall install]). ERRNO is a symbolic
+**     name (i.e. "EACCES"). Not all errno codes are supported. Add extra
+**     to the aErrno table in function test_syscall_errno() below as 
+**     required.
+**
+**   test_syscall reset ?SYSTEM-CALL?
+**     With no argument, this is an alias for the [uninstall] command. However,
+**     this command uses a VFS call of the form:
+**
+**       xSetSystemCall(pVfs, 0, 0);
+**
+**     To restore the default system calls. The [uninstall] command restores
+**     each system call individually by calling (i.e.):
+**
+**       xSetSystemCall(pVfs, "open", 0);
+**
+**     With an argument, this command attempts to reset the system call named
+**     by the parameter using the same method as [uninstall].
+**
+**   test_syscall exists SYSTEM-CALL
+**     Return true if the named system call exists. Or false otherwise.
+**
+**   test_syscall list
+**     Return a list of all system calls. The list is constructed using
+**     the xNextSystemCall() VFS method.
+*/
+
+#include "sqliteInt.h"
+#include "sqlite3.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#if SQLITE_OS_UNIX
+
+/* From main.c */
+extern const char *sqlite3ErrName(int);
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <errno.h>
+
+static struct TestSyscallGlobal {
+  int bPersist;                   /* 1 for persistent errors, 0 for transient */
+  int nCount;                     /* Fail after this many more calls */
+  int nFail;                      /* Number of failures that have occurred */
+} gSyscall = { 0, 0 };
+
+static int ts_open(const char *, int, int);
+static int ts_close(int fd);
+static int ts_access(const char *zPath, int mode);
+static char *ts_getcwd(char *zPath, size_t nPath);
+static int ts_stat(const char *zPath, struct stat *p);
+static int ts_fstat(int fd, struct stat *p);
+static int ts_ftruncate(int fd, off_t n);
+static int ts_fcntl(int fd, int cmd, ... );
+static int ts_read(int fd, void *aBuf, size_t nBuf);
+static int ts_pread(int fd, void *aBuf, size_t nBuf, off_t off);
+static int ts_pread64(int fd, void *aBuf, size_t nBuf, off_t off);
+static int ts_write(int fd, const void *aBuf, size_t nBuf);
+static int ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off);
+static int ts_pwrite64(int fd, const void *aBuf, size_t nBuf, off_t off);
+static int ts_fchmod(int fd, mode_t mode);
+static int ts_fallocate(int fd, off_t off, off_t len);
+static void *ts_mmap(void *, size_t, int, int, int, off_t);
+static void *ts_mremap(void*, size_t, size_t, int, ...);
+
+struct TestSyscallArray {
+  const char *zName;
+  sqlite3_syscall_ptr xTest;
+  sqlite3_syscall_ptr xOrig;
+  int default_errno;              /* Default value for errno following errors */
+  int custom_errno;               /* Current value for errno if error */
+} aSyscall[] = {
+  /*  0 */ { "open",      (sqlite3_syscall_ptr)ts_open,      0, EACCES, 0 },
+  /*  1 */ { "close",     (sqlite3_syscall_ptr)ts_close,     0, 0, 0 },
+  /*  2 */ { "access",    (sqlite3_syscall_ptr)ts_access,    0, 0, 0 },
+  /*  3 */ { "getcwd",    (sqlite3_syscall_ptr)ts_getcwd,    0, 0, 0 },
+  /*  4 */ { "stat",      (sqlite3_syscall_ptr)ts_stat,      0, 0, 0 },
+  /*  5 */ { "fstat",     (sqlite3_syscall_ptr)ts_fstat,     0, 0, 0 },
+  /*  6 */ { "ftruncate", (sqlite3_syscall_ptr)ts_ftruncate, 0, EIO, 0 },
+  /*  7 */ { "fcntl",     (sqlite3_syscall_ptr)ts_fcntl,     0, EACCES, 0 },
+  /*  8 */ { "read",      (sqlite3_syscall_ptr)ts_read,      0, 0, 0 },
+  /*  9 */ { "pread",     (sqlite3_syscall_ptr)ts_pread,     0, 0, 0 },
+  /* 10 */ { "pread64",   (sqlite3_syscall_ptr)ts_pread64,   0, 0, 0 },
+  /* 11 */ { "write",     (sqlite3_syscall_ptr)ts_write,     0, 0, 0 },
+  /* 12 */ { "pwrite",    (sqlite3_syscall_ptr)ts_pwrite,    0, 0, 0 },
+  /* 13 */ { "pwrite64",  (sqlite3_syscall_ptr)ts_pwrite64,  0, 0, 0 },
+  /* 14 */ { "fchmod",    (sqlite3_syscall_ptr)ts_fchmod,    0, 0, 0 },
+  /* 15 */ { "fallocate", (sqlite3_syscall_ptr)ts_fallocate, 0, 0, 0 },
+  /* 16 */ { "mmap",      (sqlite3_syscall_ptr)ts_mmap,      0, 0, 0 },
+  /* 17 */ { "mremap",    (sqlite3_syscall_ptr)ts_mremap,    0, 0, 0 },
+           { 0, 0, 0, 0, 0 }
+};
+
+#define orig_open      ((int(*)(const char *, int, int))aSyscall[0].xOrig)
+#define orig_close     ((int(*)(int))aSyscall[1].xOrig)
+#define orig_access    ((int(*)(const char*,int))aSyscall[2].xOrig)
+#define orig_getcwd    ((char*(*)(char*,size_t))aSyscall[3].xOrig)
+#define orig_stat      ((int(*)(const char*,struct stat*))aSyscall[4].xOrig)
+#define orig_fstat     ((int(*)(int,struct stat*))aSyscall[5].xOrig)
+#define orig_ftruncate ((int(*)(int,off_t))aSyscall[6].xOrig)
+#define orig_fcntl     ((int(*)(int,int,...))aSyscall[7].xOrig)
+#define orig_read      ((ssize_t(*)(int,void*,size_t))aSyscall[8].xOrig)
+#define orig_pread     ((ssize_t(*)(int,void*,size_t,off_t))aSyscall[9].xOrig)
+#define orig_pread64   ((ssize_t(*)(int,void*,size_t,off_t))aSyscall[10].xOrig)
+#define orig_write     ((ssize_t(*)(int,const void*,size_t))aSyscall[11].xOrig)
+#define orig_pwrite    ((ssize_t(*)(int,const void*,size_t,off_t))\
+                       aSyscall[12].xOrig)
+#define orig_pwrite64  ((ssize_t(*)(int,const void*,size_t,off_t))\
+                       aSyscall[13].xOrig)
+#define orig_fchmod    ((int(*)(int,mode_t))aSyscall[14].xOrig)
+#define orig_fallocate ((int(*)(int,off_t,off_t))aSyscall[15].xOrig)
+#define orig_mmap      ((void*(*)(void*,size_t,int,int,int,off_t))aSyscall[16].xOrig)
+#define orig_mremap    ((void*(*)(void*,size_t,size_t,int,...))aSyscall[17].xOrig)
+
+/*
+** This function is called exactly once from within each invocation of a
+** system call wrapper in this file. It returns 1 if the function should
+** fail, or 0 if it should succeed.
+*/
+static int tsIsFail(void){
+  gSyscall.nCount--;
+  if( gSyscall.nCount==0 || (gSyscall.nFail && gSyscall.bPersist) ){
+    gSyscall.nFail++;
+    return 1;
+  }
+  return 0;
+}
+
+/*
+** Return the current error-number value for function zFunc. zFunc must be
+** the name of a system call in the aSyscall[] table.
+**
+** Usually, the current error-number is the value that errno should be set
+** to if the named system call fails. The exception is "fallocate". See 
+** comments above the implementation of ts_fallocate() for details.
+*/
+static int tsErrno(const char *zFunc){
+  int i;
+  int nFunc = strlen(zFunc);
+  for(i=0; aSyscall[i].zName; i++){
+    if( strlen(aSyscall[i].zName)!=nFunc ) continue;
+    if( memcmp(aSyscall[i].zName, zFunc, nFunc) ) continue;
+    return aSyscall[i].custom_errno;
+  }
+
+  assert(0);
+  return 0;
+}
+
+/*
+** A wrapper around tsIsFail(). If tsIsFail() returns non-zero, set the
+** value of errno before returning.
+*/ 
+static int tsIsFailErrno(const char *zFunc){
+  if( tsIsFail() ){
+    errno = tsErrno(zFunc);
+    return 1;
+  }
+  return 0;
+}
+
+/*
+** A wrapper around open().
+*/
+static int ts_open(const char *zFile, int flags, int mode){
+  if( tsIsFailErrno("open") ){
+    return -1;
+  }
+  return orig_open(zFile, flags, mode);
+}
+
+/*
+** A wrapper around close().
+*/
+static int ts_close(int fd){
+  if( tsIsFail() ){
+    /* Even if simulating an error, close the original file-descriptor. 
+    ** This is to stop the test process from running out of file-descriptors
+    ** when running a long test. If a call to close() appears to fail, SQLite
+    ** never attempts to use the file-descriptor afterwards (or even to close
+    ** it a second time).  */
+    orig_close(fd);
+    return -1;
+  }
+  return orig_close(fd);
+}
+
+/*
+** A wrapper around access().
+*/
+static int ts_access(const char *zPath, int mode){
+  if( tsIsFail() ){
+    return -1;
+  }
+  return orig_access(zPath, mode);
+}
+
+/*
+** A wrapper around getcwd().
+*/
+static char *ts_getcwd(char *zPath, size_t nPath){
+  if( tsIsFail() ){
+    return NULL;
+  }
+  return orig_getcwd(zPath, nPath);
+}
+
+/*
+** A wrapper around stat().
+*/
+static int ts_stat(const char *zPath, struct stat *p){
+  if( tsIsFail() ){
+    return -1;
+  }
+  return orig_stat(zPath, p);
+}
+
+/*
+** A wrapper around fstat().
+*/
+static int ts_fstat(int fd, struct stat *p){
+  if( tsIsFailErrno("fstat") ){
+    return -1;
+  }
+  return orig_fstat(fd, p);
+}
+
+/*
+** A wrapper around ftruncate().
+*/
+static int ts_ftruncate(int fd, off_t n){
+  if( tsIsFailErrno("ftruncate") ){
+    return -1;
+  }
+  return orig_ftruncate(fd, n);
+}
+
+/*
+** A wrapper around fcntl().
+*/
+static int ts_fcntl(int fd, int cmd, ... ){
+  va_list ap;
+  void *pArg;
+  if( tsIsFailErrno("fcntl") ){
+    return -1;
+  }
+  va_start(ap, cmd);
+  pArg = va_arg(ap, void *);
+  return orig_fcntl(fd, cmd, pArg);
+}
+
+/*
+** A wrapper around read().
+*/
+static int ts_read(int fd, void *aBuf, size_t nBuf){
+  if( tsIsFailErrno("read") ){
+    return -1;
+  }
+  return orig_read(fd, aBuf, nBuf);
+}
+
+/*
+** A wrapper around pread().
+*/
+static int ts_pread(int fd, void *aBuf, size_t nBuf, off_t off){
+  if( tsIsFailErrno("pread") ){
+    return -1;
+  }
+  return orig_pread(fd, aBuf, nBuf, off);
+}
+
+/*
+** A wrapper around pread64().
+*/
+static int ts_pread64(int fd, void *aBuf, size_t nBuf, off_t off){
+  if( tsIsFailErrno("pread64") ){
+    return -1;
+  }
+  return orig_pread64(fd, aBuf, nBuf, off);
+}
+
+/*
+** A wrapper around write().
+*/
+static int ts_write(int fd, const void *aBuf, size_t nBuf){
+  if( tsIsFailErrno("write") ){
+    if( tsErrno("write")==EINTR ) orig_write(fd, aBuf, nBuf/2);
+    return -1;
+  }
+  return orig_write(fd, aBuf, nBuf);
+}
+
+/*
+** A wrapper around pwrite().
+*/
+static int ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off){
+  if( tsIsFailErrno("pwrite") ){
+    return -1;
+  }
+  return orig_pwrite(fd, aBuf, nBuf, off);
+}
+
+/*
+** A wrapper around pwrite64().
+*/
+static int ts_pwrite64(int fd, const void *aBuf, size_t nBuf, off_t off){
+  if( tsIsFailErrno("pwrite64") ){
+    return -1;
+  }
+  return orig_pwrite64(fd, aBuf, nBuf, off);
+}
+
+/*
+** A wrapper around fchmod().
+*/
+static int ts_fchmod(int fd, mode_t mode){
+  if( tsIsFail() ){
+    return -1;
+  }
+  return orig_fchmod(fd, mode);
+}
+
+/*
+** A wrapper around fallocate().
+**
+** SQLite assumes that the fallocate() function is compatible with
+** posix_fallocate(). According to the Linux man page (2009-09-30):
+**
+**   posix_fallocate() returns  zero on success, or an error number on
+**   failure. Note that errno is not set.
+*/
+static int ts_fallocate(int fd, off_t off, off_t len){
+  if( tsIsFail() ){
+    return tsErrno("fallocate");
+  }
+  return orig_fallocate(fd, off, len);
+}
+
+static void *ts_mmap(
+  void *pAddr, 
+  size_t nByte, 
+  int prot, 
+  int flags, 
+  int fd, 
+  off_t iOff
+){
+  if( tsIsFailErrno("mmap") ){
+    return MAP_FAILED;
+  }
+  return orig_mmap(pAddr, nByte, prot, flags, fd, iOff);
+}
+
+static void *ts_mremap(void *a, size_t b, size_t c, int d, ...){
+  va_list ap;
+  void *pArg;
+  if( tsIsFailErrno("mremap") ){
+    return MAP_FAILED;
+  }
+  va_start(ap, d);
+  pArg = va_arg(ap, void *);
+  return orig_mremap(a, b, c, d, pArg);
+}
+
+static int test_syscall_install(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_vfs *pVfs; 
+  int nElem;
+  int i;
+  Tcl_Obj **apElem;
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 2, objv, "SYSCALL-LIST");
+    return TCL_ERROR;
+  }
+  if( Tcl_ListObjGetElements(interp, objv[2], &nElem, &apElem) ){
+    return TCL_ERROR;
+  }
+  pVfs = sqlite3_vfs_find(0);
+
+  for(i=0; i<nElem; i++){
+    int iCall;
+    int rc = Tcl_GetIndexFromObjStruct(interp, 
+        apElem[i], aSyscall, sizeof(aSyscall[0]), "system-call", 0, &iCall
+    );
+    if( rc ) return rc;
+    if( aSyscall[iCall].xOrig==0 ){
+      aSyscall[iCall].xOrig = pVfs->xGetSystemCall(pVfs, aSyscall[iCall].zName);
+      pVfs->xSetSystemCall(pVfs, aSyscall[iCall].zName, aSyscall[iCall].xTest);
+    }
+    aSyscall[iCall].custom_errno = aSyscall[iCall].default_errno;
+  }
+
+  return TCL_OK;
+}
+
+static int test_syscall_uninstall(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_vfs *pVfs; 
+  int i;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 2, objv, "");
+    return TCL_ERROR;
+  }
+
+  pVfs = sqlite3_vfs_find(0);
+  for(i=0; aSyscall[i].zName; i++){
+    if( aSyscall[i].xOrig ){
+      pVfs->xSetSystemCall(pVfs, aSyscall[i].zName, 0);
+      aSyscall[i].xOrig = 0;
+    }
+  }
+  return TCL_OK;
+}
+
+static int test_syscall_reset(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_vfs *pVfs; 
+  int i;
+  int rc;
+
+  if( objc!=2 && objc!=3 ){
+    Tcl_WrongNumArgs(interp, 2, objv, "");
+    return TCL_ERROR;
+  }
+
+  pVfs = sqlite3_vfs_find(0);
+  if( objc==2 ){
+    rc = pVfs->xSetSystemCall(pVfs, 0, 0);
+    for(i=0; aSyscall[i].zName; i++) aSyscall[i].xOrig = 0;
+  }else{
+    int nFunc;
+    char *zFunc = Tcl_GetStringFromObj(objv[2], &nFunc);
+    rc = pVfs->xSetSystemCall(pVfs, Tcl_GetString(objv[2]), 0);
+    for(i=0; rc==SQLITE_OK && aSyscall[i].zName; i++){
+      if( strlen(aSyscall[i].zName)!=nFunc ) continue;
+      if( memcmp(aSyscall[i].zName, zFunc, nFunc) ) continue;
+      aSyscall[i].xOrig = 0;
+    }
+  }
+  if( rc!=SQLITE_OK ){
+    Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+    return TCL_ERROR;
+  }
+
+  Tcl_ResetResult(interp);
+  return TCL_OK;
+}
+
+static int test_syscall_exists(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_vfs *pVfs; 
+  sqlite3_syscall_ptr x;
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 2, objv, "");
+    return TCL_ERROR;
+  }
+
+  pVfs = sqlite3_vfs_find(0);
+  x = pVfs->xGetSystemCall(pVfs, Tcl_GetString(objv[2]));
+
+  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(x!=0));
+  return TCL_OK;
+}
+
+static int test_syscall_fault(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int nCount = 0;
+  int bPersist = 0;
+
+  if( objc!=2 && objc!=4 ){
+    Tcl_WrongNumArgs(interp, 2, objv, "?COUNT PERSIST?");
+    return TCL_ERROR;
+  }
+
+  if( objc==4 ){
+    if( Tcl_GetIntFromObj(interp, objv[2], &nCount)
+     || Tcl_GetBooleanFromObj(interp, objv[3], &bPersist)
+    ){
+      return TCL_ERROR;
+    }
+  }
+
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(gSyscall.nFail));
+  gSyscall.nCount = nCount;
+  gSyscall.bPersist = bPersist;
+  gSyscall.nFail = 0;
+  return TCL_OK;
+}
+
+static int test_syscall_errno(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int iCall;
+  int iErrno;
+  int rc;
+
+  struct Errno {
+    const char *z;
+    int i;
+  } aErrno[] = {
+    { "EACCES",    EACCES },
+    { "EINTR",     EINTR },
+    { "EIO",       EIO },
+    { "EOVERFLOW", EOVERFLOW },
+    { "ENOMEM",    ENOMEM },
+    { "EAGAIN",    EAGAIN },
+    { "ETIMEDOUT", ETIMEDOUT },
+    { "EBUSY",     EBUSY },
+    { "EPERM",     EPERM },
+    { "EDEADLK",   EDEADLK },
+    { "ENOLCK",    ENOLCK },
+    { 0, 0 }
+  };
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 2, objv, "SYSCALL ERRNO");
+    return TCL_ERROR;
+  }
+
+  rc = Tcl_GetIndexFromObjStruct(interp, 
+      objv[2], aSyscall, sizeof(aSyscall[0]), "system-call", 0, &iCall
+  );
+  if( rc!=TCL_OK ) return rc;
+  rc = Tcl_GetIndexFromObjStruct(interp, 
+      objv[3], aErrno, sizeof(aErrno[0]), "errno", 0, &iErrno
+  );
+  if( rc!=TCL_OK ) return rc;
+
+  aSyscall[iCall].custom_errno = aErrno[iErrno].i;
+  return TCL_OK;
+}
+
+static int test_syscall_list(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  const char *zSys;
+  sqlite3_vfs *pVfs; 
+  Tcl_Obj *pList;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 2, objv, "");
+    return TCL_ERROR;
+  }
+
+  pVfs = sqlite3_vfs_find(0);
+  pList = Tcl_NewObj();
+  Tcl_IncrRefCount(pList);
+  for(zSys = pVfs->xNextSystemCall(pVfs, 0); 
+      zSys!=0;
+      zSys = pVfs->xNextSystemCall(pVfs, zSys)
+  ){
+    Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj(zSys, -1));
+  }
+
+  Tcl_SetObjResult(interp, pList);
+  Tcl_DecrRefCount(pList);
+  return TCL_OK;
+}
+
+static int test_syscall_defaultvfs(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_vfs *pVfs; 
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 2, objv, "");
+    return TCL_ERROR;
+  }
+
+  pVfs = sqlite3_vfs_find(0);
+  Tcl_SetObjResult(interp, Tcl_NewStringObj(pVfs->zName, -1));
+  return TCL_OK;
+}
+
+static int test_syscall(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  struct SyscallCmd {
+    const char *zName;
+    Tcl_ObjCmdProc *xCmd;
+  } aCmd[] = {
+    { "fault",      test_syscall_fault },
+    { "install",    test_syscall_install },
+    { "uninstall",  test_syscall_uninstall },
+    { "reset",      test_syscall_reset },
+    { "errno",      test_syscall_errno },
+    { "exists",     test_syscall_exists },
+    { "list",       test_syscall_list },
+    { "defaultvfs", test_syscall_defaultvfs },
+    { 0, 0 }
+  };
+  int iCmd;
+  int rc;
+
+  if( objc<2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ...");
+    return TCL_ERROR;
+  }
+  rc = Tcl_GetIndexFromObjStruct(interp, 
+      objv[1], aCmd, sizeof(aCmd[0]), "sub-command", 0, &iCmd
+  );
+  if( rc!=TCL_OK ) return rc;
+  return aCmd[iCmd].xCmd(clientData, interp, objc, objv);
+}
+
+int SqlitetestSyscall_Init(Tcl_Interp *interp){
+  struct SyscallCmd {
+    const char *zName;
+    Tcl_ObjCmdProc *xCmd;
+  } aCmd[] = {
+    { "test_syscall",     test_syscall},
+  };
+  int i;
+
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xCmd, 0, 0);
+  }
+  return TCL_OK;
+}
+#else
+int SqlitetestSyscall_Init(Tcl_Interp *interp){
+  return TCL_OK;
+}
+#endif

+ 332 - 0
components/external/sqlite/test/test_tclvar.c

@@ -0,0 +1,332 @@
+/*
+** 2006 June 13
+**
+** 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.
+**
+** The emphasis of this file is a virtual table that provides
+** access to TCL variables.
+*/
+#include "sqliteInt.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+typedef struct tclvar_vtab tclvar_vtab;
+typedef struct tclvar_cursor tclvar_cursor;
+
+/* 
+** A tclvar virtual-table object 
+*/
+struct tclvar_vtab {
+  sqlite3_vtab base;
+  Tcl_Interp *interp;
+};
+
+/* A tclvar cursor object */
+struct tclvar_cursor {
+  sqlite3_vtab_cursor base;
+
+  Tcl_Obj *pList1;     /* Result of [info vars ?pattern?] */
+  Tcl_Obj *pList2;     /* Result of [array names [lindex $pList1 $i1]] */
+  int i1;              /* Current item in pList1 */
+  int i2;              /* Current item (if any) in pList2 */
+};
+
+/* Methods for the tclvar module */
+static int tclvarConnect(
+  sqlite3 *db,
+  void *pAux,
+  int argc, const char *const*argv,
+  sqlite3_vtab **ppVtab,
+  char **pzErr
+){
+  tclvar_vtab *pVtab;
+  static const char zSchema[] = 
+     "CREATE TABLE whatever(name TEXT, arrayname TEXT, value TEXT)";
+  pVtab = sqlite3MallocZero( sizeof(*pVtab) );
+  if( pVtab==0 ) return SQLITE_NOMEM;
+  *ppVtab = &pVtab->base;
+  pVtab->interp = (Tcl_Interp *)pAux;
+  sqlite3_declare_vtab(db, zSchema);
+  return SQLITE_OK;
+}
+/* Note that for this virtual table, the xCreate and xConnect
+** methods are identical. */
+
+static int tclvarDisconnect(sqlite3_vtab *pVtab){
+  sqlite3_free(pVtab);
+  return SQLITE_OK;
+}
+/* The xDisconnect and xDestroy methods are also the same */
+
+/*
+** Open a new tclvar cursor.
+*/
+static int tclvarOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+  tclvar_cursor *pCur;
+  pCur = sqlite3MallocZero(sizeof(tclvar_cursor));
+  *ppCursor = &pCur->base;
+  return SQLITE_OK;
+}
+
+/*
+** Close a tclvar cursor.
+*/
+static int tclvarClose(sqlite3_vtab_cursor *cur){
+  tclvar_cursor *pCur = (tclvar_cursor *)cur;
+  if( pCur->pList1 ){
+    Tcl_DecrRefCount(pCur->pList1);
+  }
+  if( pCur->pList2 ){
+    Tcl_DecrRefCount(pCur->pList2);
+  }
+  sqlite3_free(pCur);
+  return SQLITE_OK;
+}
+
+/*
+** Returns 1 if data is ready, or 0 if not.
+*/
+static int next2(Tcl_Interp *interp, tclvar_cursor *pCur, Tcl_Obj *pObj){
+  Tcl_Obj *p;
+
+  if( pObj ){
+    if( !pCur->pList2 ){
+      p = Tcl_NewStringObj("array names", -1);
+      Tcl_IncrRefCount(p);
+      Tcl_ListObjAppendElement(0, p, pObj);
+      Tcl_EvalObjEx(interp, p, TCL_EVAL_GLOBAL);
+      Tcl_DecrRefCount(p);
+      pCur->pList2 = Tcl_GetObjResult(interp);
+      Tcl_IncrRefCount(pCur->pList2);
+      assert( pCur->i2==0 );
+    }else{
+      int n = 0;
+      pCur->i2++;
+      Tcl_ListObjLength(0, pCur->pList2, &n);
+      if( pCur->i2>=n ){
+        Tcl_DecrRefCount(pCur->pList2);
+        pCur->pList2 = 0;
+        pCur->i2 = 0;
+        return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+
+static int tclvarNext(sqlite3_vtab_cursor *cur){
+  Tcl_Obj *pObj;
+  int n = 0;
+  int ok = 0;
+
+  tclvar_cursor *pCur = (tclvar_cursor *)cur;
+  Tcl_Interp *interp = ((tclvar_vtab *)(cur->pVtab))->interp;
+
+  Tcl_ListObjLength(0, pCur->pList1, &n);
+  while( !ok && pCur->i1<n ){
+    Tcl_ListObjIndex(0, pCur->pList1, pCur->i1, &pObj);
+    ok = next2(interp, pCur, pObj);
+    if( !ok ){
+      pCur->i1++;
+    }
+  }
+
+  return 0;
+}
+
+static int tclvarFilter(
+  sqlite3_vtab_cursor *pVtabCursor, 
+  int idxNum, const char *idxStr,
+  int argc, sqlite3_value **argv
+){
+  tclvar_cursor *pCur = (tclvar_cursor *)pVtabCursor;
+  Tcl_Interp *interp = ((tclvar_vtab *)(pVtabCursor->pVtab))->interp;
+
+  Tcl_Obj *p = Tcl_NewStringObj("info vars", -1);
+  Tcl_IncrRefCount(p);
+
+  assert( argc==0 || argc==1 );
+  if( argc==1 ){
+    Tcl_Obj *pArg = Tcl_NewStringObj((char*)sqlite3_value_text(argv[0]), -1);
+    Tcl_ListObjAppendElement(0, p, pArg);
+  }
+  Tcl_EvalObjEx(interp, p, TCL_EVAL_GLOBAL);
+  if( pCur->pList1 ){
+    Tcl_DecrRefCount(pCur->pList1);
+  }
+  if( pCur->pList2 ){
+    Tcl_DecrRefCount(pCur->pList2);
+    pCur->pList2 = 0;
+  }
+  pCur->i1 = 0;
+  pCur->i2 = 0;
+  pCur->pList1 = Tcl_GetObjResult(interp);
+  Tcl_IncrRefCount(pCur->pList1);
+  assert( pCur->i1==0 && pCur->i2==0 && pCur->pList2==0 );
+
+  Tcl_DecrRefCount(p);
+  return tclvarNext(pVtabCursor);
+}
+
+static int tclvarColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
+  Tcl_Obj *p1;
+  Tcl_Obj *p2;
+  const char *z1; 
+  const char *z2 = "";
+  tclvar_cursor *pCur = (tclvar_cursor*)cur;
+  Tcl_Interp *interp = ((tclvar_vtab *)cur->pVtab)->interp;
+
+  Tcl_ListObjIndex(interp, pCur->pList1, pCur->i1, &p1);
+  Tcl_ListObjIndex(interp, pCur->pList2, pCur->i2, &p2);
+  z1 = Tcl_GetString(p1);
+  if( p2 ){
+    z2 = Tcl_GetString(p2);
+  }
+  switch (i) {
+    case 0: {
+      sqlite3_result_text(ctx, z1, -1, SQLITE_TRANSIENT);
+      break;
+    }
+    case 1: {
+      sqlite3_result_text(ctx, z2, -1, SQLITE_TRANSIENT);
+      break;
+    }
+    case 2: {
+      Tcl_Obj *pVal = Tcl_GetVar2Ex(interp, z1, *z2?z2:0, TCL_GLOBAL_ONLY);
+      sqlite3_result_text(ctx, Tcl_GetString(pVal), -1, SQLITE_TRANSIENT);
+      break;
+    }
+  }
+  return SQLITE_OK;
+}
+
+static int tclvarRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+  *pRowid = 0;
+  return SQLITE_OK;
+}
+
+static int tclvarEof(sqlite3_vtab_cursor *cur){
+  tclvar_cursor *pCur = (tclvar_cursor*)cur;
+  return (pCur->pList2?0:1);
+}
+
+static int tclvarBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+  int ii;
+
+  for(ii=0; ii<pIdxInfo->nConstraint; ii++){
+    struct sqlite3_index_constraint const *pCons = &pIdxInfo->aConstraint[ii];
+    if( pCons->iColumn==0 && pCons->usable
+           && pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+      struct sqlite3_index_constraint_usage *pUsage;
+      pUsage = &pIdxInfo->aConstraintUsage[ii];
+      pUsage->omit = 0;
+      pUsage->argvIndex = 1;
+      return SQLITE_OK;
+    }
+  }
+
+  for(ii=0; ii<pIdxInfo->nConstraint; ii++){
+    struct sqlite3_index_constraint const *pCons = &pIdxInfo->aConstraint[ii];
+    if( pCons->iColumn==0 && pCons->usable
+           && pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH ){
+      struct sqlite3_index_constraint_usage *pUsage;
+      pUsage = &pIdxInfo->aConstraintUsage[ii];
+      pUsage->omit = 1;
+      pUsage->argvIndex = 1;
+      return SQLITE_OK;
+    }
+  }
+
+  return SQLITE_OK;
+}
+
+/*
+** A virtual table module that provides read-only access to a
+** Tcl global variable namespace.
+*/
+static sqlite3_module tclvarModule = {
+  0,                         /* iVersion */
+  tclvarConnect,
+  tclvarConnect,
+  tclvarBestIndex,
+  tclvarDisconnect, 
+  tclvarDisconnect,
+  tclvarOpen,                  /* xOpen - open a cursor */
+  tclvarClose,                 /* xClose - close a cursor */
+  tclvarFilter,                /* xFilter - configure scan constraints */
+  tclvarNext,                  /* xNext - advance a cursor */
+  tclvarEof,                   /* xEof - check for end of scan */
+  tclvarColumn,                /* xColumn - read data */
+  tclvarRowid,                 /* xRowid - read data */
+  0,                           /* xUpdate */
+  0,                           /* xBegin */
+  0,                           /* xSync */
+  0,                           /* xCommit */
+  0,                           /* xRollback */
+  0,                           /* xFindMethod */
+  0,                           /* xRename */
+};
+
+/*
+** Decode a pointer to an sqlite3 object.
+*/
+extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb);
+
+/*
+** Register the echo virtual table module.
+*/
+static int register_tclvar_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 *CONST objv[]  /* Command arguments */
+){
+  sqlite3 *db;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  sqlite3_create_module(db, "tclvar", &tclvarModule, (void *)interp);
+#endif
+  return TCL_OK;
+}
+
+#endif
+
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetesttclvar_Init(Tcl_Interp *interp){
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+  static struct {
+     char *zName;
+     Tcl_ObjCmdProc *xProc;
+     void *clientData;
+  } aObjCmd[] = {
+     { "register_tclvar_module",   register_tclvar_module, 0 },
+  };
+  int i;
+  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, 
+        aObjCmd[i].xProc, aObjCmd[i].clientData, 0);
+  }
+#endif
+  return TCL_OK;
+}

+ 647 - 0
components/external/sqlite/test/test_thread.c

@@ -0,0 +1,647 @@
+/*
+** 2007 September 9
+**
+** 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 contains the implementation of some Tcl commands used to
+** test that sqlite3 database handles may be concurrently accessed by 
+** multiple threads. Right now this only works on unix.
+*/
+
+#include "sqliteInt.h"
+#include <tcl.h>
+
+#if SQLITE_THREADSAFE
+
+#include <errno.h>
+
+#if !defined(_MSC_VER)
+#include <unistd.h>
+#endif
+
+/*
+** One of these is allocated for each thread created by [sqlthread spawn].
+*/
+typedef struct SqlThread SqlThread;
+struct SqlThread {
+  Tcl_ThreadId parent;     /* Thread id of parent thread */
+  Tcl_Interp *interp;      /* Parent interpreter */
+  char *zScript;           /* The script to execute. */
+  char *zVarname;          /* Varname in parent script */
+};
+
+/*
+** A custom Tcl_Event type used by this module. When the event is
+** handled, script zScript is evaluated in interpreter interp. If
+** the evaluation throws an exception (returns TCL_ERROR), then the
+** error is handled by Tcl_BackgroundError(). If no error occurs,
+** the result is simply discarded.
+*/
+typedef struct EvalEvent EvalEvent;
+struct EvalEvent {
+  Tcl_Event base;          /* Base class of type Tcl_Event */
+  char *zScript;           /* The script to execute. */
+  Tcl_Interp *interp;      /* The interpreter to execute it in. */
+};
+
+static Tcl_ObjCmdProc sqlthread_proc;
+static Tcl_ObjCmdProc clock_seconds_proc;
+#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+static Tcl_ObjCmdProc blocking_step_proc;
+static Tcl_ObjCmdProc blocking_prepare_v2_proc;
+#endif
+int Sqlitetest1_Init(Tcl_Interp *);
+int Sqlite3_Init(Tcl_Interp *);
+
+/* Functions from main.c */
+extern const char *sqlite3ErrName(int);
+
+/* Functions from test1.c */
+extern void *sqlite3TestTextToPtr(const char *);
+extern int getDbPointer(Tcl_Interp *, const char *, sqlite3 **);
+extern int sqlite3TestMakePointerStr(Tcl_Interp *, char *, void *);
+extern int sqlite3TestErrCode(Tcl_Interp *, sqlite3 *, int);
+
+/*
+** Handler for events of type EvalEvent.
+*/
+static int tclScriptEvent(Tcl_Event *evPtr, int flags){
+  int rc;
+  EvalEvent *p = (EvalEvent *)evPtr;
+  rc = Tcl_Eval(p->interp, p->zScript);
+  if( rc!=TCL_OK ){
+    Tcl_BackgroundError(p->interp);
+  }
+  UNUSED_PARAMETER(flags);
+  return 1;
+}
+
+/*
+** Register an EvalEvent to evaluate the script pScript in the
+** parent interpreter/thread of SqlThread p.
+*/
+static void postToParent(SqlThread *p, Tcl_Obj *pScript){
+  EvalEvent *pEvent;
+  char *zMsg;
+  int nMsg;
+
+  zMsg = Tcl_GetStringFromObj(pScript, &nMsg); 
+  pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1);
+  pEvent->base.nextPtr = 0;
+  pEvent->base.proc = tclScriptEvent;
+  pEvent->zScript = (char *)&pEvent[1];
+  memcpy(pEvent->zScript, zMsg, nMsg+1);
+  pEvent->interp = p->interp;
+
+  Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL);
+  Tcl_ThreadAlert(p->parent);
+}
+
+/*
+** The main function for threads created with [sqlthread spawn].
+*/
+static Tcl_ThreadCreateType tclScriptThread(ClientData pSqlThread){
+  Tcl_Interp *interp;
+  Tcl_Obj *pRes;
+  Tcl_Obj *pList;
+  int rc;
+  SqlThread *p = (SqlThread *)pSqlThread;
+  extern int Sqlitetest_mutex_Init(Tcl_Interp*);
+
+  interp = Tcl_CreateInterp();
+  Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0);
+  Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0);
+#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+  Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0);
+  Tcl_CreateObjCommand(interp, 
+      "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0);
+  Tcl_CreateObjCommand(interp, 
+      "sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0);
+#endif
+  Sqlitetest1_Init(interp);
+  Sqlitetest_mutex_Init(interp);
+  Sqlite3_Init(interp);
+
+  rc = Tcl_Eval(interp, p->zScript);
+  pRes = Tcl_GetObjResult(interp);
+  pList = Tcl_NewObj();
+  Tcl_IncrRefCount(pList);
+  Tcl_IncrRefCount(pRes);
+
+  if( rc!=TCL_OK ){
+    Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("error", -1));
+    Tcl_ListObjAppendElement(interp, pList, pRes);
+    postToParent(p, pList);
+    Tcl_DecrRefCount(pList);
+    pList = Tcl_NewObj();
+  }
+
+  Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("set", -1));
+  Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj(p->zVarname, -1));
+  Tcl_ListObjAppendElement(interp, pList, pRes);
+  postToParent(p, pList);
+
+  ckfree((void *)p);
+  Tcl_DecrRefCount(pList);
+  Tcl_DecrRefCount(pRes);
+  Tcl_DeleteInterp(interp);
+  while( Tcl_DoOneEvent(TCL_ALL_EVENTS|TCL_DONT_WAIT) );
+  Tcl_ExitThread(0);
+  TCL_THREAD_CREATE_RETURN;
+}
+
+/*
+** sqlthread spawn VARNAME SCRIPT
+**
+**     Spawn a new thread with its own Tcl interpreter and run the
+**     specified SCRIPT(s) in it. The thread terminates after running
+**     the script. The result of the script is stored in the variable
+**     VARNAME.
+**
+**     The caller can wait for the script to terminate using [vwait VARNAME].
+*/
+static int sqlthread_spawn(
+  ClientData clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  Tcl_ThreadId x;
+  SqlThread *pNew;
+  int rc;
+
+  int nVarname; char *zVarname;
+  int nScript; char *zScript;
+
+  /* Parameters for thread creation */
+  const int nStack = TCL_THREAD_STACK_DEFAULT;
+  const int flags = TCL_THREAD_NOFLAGS;
+
+  assert(objc==4);
+  UNUSED_PARAMETER(clientData);
+  UNUSED_PARAMETER(objc);
+
+  zVarname = Tcl_GetStringFromObj(objv[2], &nVarname);
+  zScript = Tcl_GetStringFromObj(objv[3], &nScript);
+
+  pNew = (SqlThread *)ckalloc(sizeof(SqlThread)+nVarname+nScript+2);
+  pNew->zVarname = (char *)&pNew[1];
+  pNew->zScript = (char *)&pNew->zVarname[nVarname+1];
+  memcpy(pNew->zVarname, zVarname, nVarname+1);
+  memcpy(pNew->zScript, zScript, nScript+1);
+  pNew->parent = Tcl_GetCurrentThread();
+  pNew->interp = interp;
+
+  rc = Tcl_CreateThread(&x, tclScriptThread, (void *)pNew, nStack, flags);
+  if( rc!=TCL_OK ){
+    Tcl_AppendResult(interp, "Error in Tcl_CreateThread()", 0);
+    ckfree((char *)pNew);
+    return TCL_ERROR;
+  }
+
+  return TCL_OK;
+}
+
+/*
+** sqlthread parent SCRIPT
+**
+**     This can be called by spawned threads only. It sends the specified
+**     script back to the parent thread for execution. The result of
+**     evaluating the SCRIPT is returned. The parent thread must enter
+**     the event loop for this to work - otherwise the caller will
+**     block indefinitely.
+**
+**     NOTE: At the moment, this doesn't work. FIXME.
+*/
+static int sqlthread_parent(
+  ClientData clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  EvalEvent *pEvent;
+  char *zMsg;
+  int nMsg;
+  SqlThread *p = (SqlThread *)clientData;
+
+  assert(objc==3);
+  UNUSED_PARAMETER(objc);
+
+  if( p==0 ){
+    Tcl_AppendResult(interp, "no parent thread", 0);
+    return TCL_ERROR;
+  }
+
+  zMsg = Tcl_GetStringFromObj(objv[2], &nMsg);
+  pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1);
+  pEvent->base.nextPtr = 0;
+  pEvent->base.proc = tclScriptEvent;
+  pEvent->zScript = (char *)&pEvent[1];
+  memcpy(pEvent->zScript, zMsg, nMsg+1);
+  pEvent->interp = p->interp;
+  Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL);
+  Tcl_ThreadAlert(p->parent);
+
+  return TCL_OK;
+}
+
+static int xBusy(void *pArg, int nBusy){
+  UNUSED_PARAMETER(pArg);
+  UNUSED_PARAMETER(nBusy);
+  sqlite3_sleep(50);
+  return 1;             /* Try again... */
+}
+
+/*
+** sqlthread open
+**
+**     Open a database handle and return the string representation of
+**     the pointer value.
+*/
+static int sqlthread_open(
+  ClientData clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int sqlite3TestMakePointerStr(Tcl_Interp *interp, char *zPtr, void *p);
+
+  const char *zFilename;
+  sqlite3 *db;
+  char zBuf[100];
+  extern void Md5_Register(sqlite3*);
+
+  UNUSED_PARAMETER(clientData);
+  UNUSED_PARAMETER(objc);
+
+  zFilename = Tcl_GetString(objv[2]);
+  sqlite3_open(zFilename, &db);
+#ifdef SQLITE_HAS_CODEC
+  if( db && objc>=4 ){
+    const char *zKey;
+    int nKey;
+    int rc;
+    zKey = Tcl_GetStringFromObj(objv[3], &nKey);
+    rc = sqlite3_key(db, zKey, nKey);
+    if( rc!=SQLITE_OK ){
+      char *zErrMsg = sqlite3_mprintf("error %d: %s", rc, sqlite3_errmsg(db));
+      sqlite3_close(db);
+      Tcl_AppendResult(interp, zErrMsg, (char*)0);
+      sqlite3_free(zErrMsg);
+      return TCL_ERROR;
+    }
+  }
+#endif
+  Md5_Register(db);
+  sqlite3_busy_handler(db, xBusy, 0);
+  
+  if( sqlite3TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR;
+  Tcl_AppendResult(interp, zBuf, 0);
+
+  return TCL_OK;
+}
+
+
+/*
+** sqlthread open
+**
+**     Return the current thread-id (Tcl_GetCurrentThread()) cast to
+**     an integer.
+*/
+static int sqlthread_id(
+  ClientData clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  Tcl_ThreadId id = Tcl_GetCurrentThread();
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(SQLITE_PTR_TO_INT(id)));
+  UNUSED_PARAMETER(clientData);
+  UNUSED_PARAMETER(objc);
+  UNUSED_PARAMETER(objv);
+  return TCL_OK;
+}
+
+
+/*
+** Dispatch routine for the sub-commands of [sqlthread].
+*/
+static int sqlthread_proc(
+  ClientData clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  struct SubCommand {
+    char *zName;
+    Tcl_ObjCmdProc *xProc;
+    int nArg;
+    char *zUsage;
+  } aSub[] = {
+    {"parent", sqlthread_parent, 1, "SCRIPT"},
+    {"spawn",  sqlthread_spawn,  2, "VARNAME SCRIPT"},
+    {"open",   sqlthread_open,   1, "DBNAME"},
+    {"id",     sqlthread_id,     0, ""},
+    {0, 0, 0}
+  };
+  struct SubCommand *pSub;
+  int rc;
+  int iIndex;
+
+  if( objc<2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND");
+    return TCL_ERROR;
+  }
+
+  rc = Tcl_GetIndexFromObjStruct(
+      interp, objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iIndex
+  );
+  if( rc!=TCL_OK ) return rc;
+  pSub = &aSub[iIndex];
+
+  if( objc<(pSub->nArg+2) ){
+    Tcl_WrongNumArgs(interp, 2, objv, pSub->zUsage);
+    return TCL_ERROR;
+  }
+
+  return pSub->xProc(clientData, interp, objc, objv);
+}
+
+/*
+** The [clock_seconds] command. This is more or less the same as the
+** regular tcl [clock seconds], except that it is available in testfixture
+** when linked against both Tcl 8.4 and 8.5. Because [clock seconds] is
+** implemented as a script in Tcl 8.5, it is not usually available to
+** testfixture.
+*/ 
+static int clock_seconds_proc(
+  ClientData clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  Tcl_Time now;
+  Tcl_GetTime(&now);
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(now.sec));
+  UNUSED_PARAMETER(clientData);
+  UNUSED_PARAMETER(objc);
+  UNUSED_PARAMETER(objv);
+  return TCL_OK;
+}
+
+/*************************************************************************
+** This block contains the implementation of the [sqlite3_blocking_step]
+** command available to threads created by [sqlthread spawn] commands. It
+** is only available on UNIX for now. This is because pthread condition
+** variables are used.
+**
+** The source code for the C functions sqlite3_blocking_step(),
+** blocking_step_notify() and the structure UnlockNotification is
+** automatically extracted from this file and used as part of the
+** documentation for the sqlite3_unlock_notify() API function. This
+** should be considered if these functions are to be extended (i.e. to 
+** support windows) in the future.
+*/ 
+#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+
+/* BEGIN_SQLITE_BLOCKING_STEP */
+/* This example uses the pthreads API */
+#include <pthread.h>
+
+/*
+** A pointer to an instance of this structure is passed as the user-context
+** pointer when registering for an unlock-notify callback.
+*/
+typedef struct UnlockNotification UnlockNotification;
+struct UnlockNotification {
+  int fired;                         /* True after unlock event has occurred */
+  pthread_cond_t cond;               /* Condition variable to wait on */
+  pthread_mutex_t mutex;             /* Mutex to protect structure */
+};
+
+/*
+** This function is an unlock-notify callback registered with SQLite.
+*/
+static void unlock_notify_cb(void **apArg, int nArg){
+  int i;
+  for(i=0; i<nArg; i++){
+    UnlockNotification *p = (UnlockNotification *)apArg[i];
+    pthread_mutex_lock(&p->mutex);
+    p->fired = 1;
+    pthread_cond_signal(&p->cond);
+    pthread_mutex_unlock(&p->mutex);
+  }
+}
+
+/*
+** This function assumes that an SQLite API call (either sqlite3_prepare_v2() 
+** or sqlite3_step()) has just returned SQLITE_LOCKED. The argument is the
+** associated database connection.
+**
+** This function calls sqlite3_unlock_notify() to register for an 
+** unlock-notify callback, then blocks until that callback is delivered 
+** and returns SQLITE_OK. The caller should then retry the failed operation.
+**
+** Or, if sqlite3_unlock_notify() indicates that to block would deadlock 
+** the system, then this function returns SQLITE_LOCKED immediately. In 
+** this case the caller should not retry the operation and should roll 
+** back the current transaction (if any).
+*/
+static int wait_for_unlock_notify(sqlite3 *db){
+  int rc;
+  UnlockNotification un;
+
+  /* Initialize the UnlockNotification structure. */
+  un.fired = 0;
+  pthread_mutex_init(&un.mutex, 0);
+  pthread_cond_init(&un.cond, 0);
+
+  /* Register for an unlock-notify callback. */
+  rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un);
+  assert( rc==SQLITE_LOCKED || rc==SQLITE_OK );
+
+  /* The call to sqlite3_unlock_notify() always returns either SQLITE_LOCKED 
+  ** or SQLITE_OK. 
+  **
+  ** If SQLITE_LOCKED was returned, then the system is deadlocked. In this
+  ** case this function needs to return SQLITE_LOCKED to the caller so 
+  ** that the current transaction can be rolled back. Otherwise, block
+  ** until the unlock-notify callback is invoked, then return SQLITE_OK.
+  */
+  if( rc==SQLITE_OK ){
+    pthread_mutex_lock(&un.mutex);
+    if( !un.fired ){
+      pthread_cond_wait(&un.cond, &un.mutex);
+    }
+    pthread_mutex_unlock(&un.mutex);
+  }
+
+  /* Destroy the mutex and condition variables. */
+  pthread_cond_destroy(&un.cond);
+  pthread_mutex_destroy(&un.mutex);
+
+  return rc;
+}
+
+/*
+** This function is a wrapper around the SQLite function sqlite3_step().
+** It functions in the same way as step(), except that if a required
+** shared-cache lock cannot be obtained, this function may block waiting for
+** the lock to become available. In this scenario the normal API step()
+** function always returns SQLITE_LOCKED.
+**
+** If this function returns SQLITE_LOCKED, the caller should rollback
+** the current transaction (if any) and try again later. Otherwise, the
+** system may become deadlocked.
+*/
+int sqlite3_blocking_step(sqlite3_stmt *pStmt){
+  int rc;
+  while( SQLITE_LOCKED==(rc = sqlite3_step(pStmt)) ){
+    rc = wait_for_unlock_notify(sqlite3_db_handle(pStmt));
+    if( rc!=SQLITE_OK ) break;
+    sqlite3_reset(pStmt);
+  }
+  return rc;
+}
+
+/*
+** This function is a wrapper around the SQLite function sqlite3_prepare_v2().
+** It functions in the same way as prepare_v2(), except that if a required
+** shared-cache lock cannot be obtained, this function may block waiting for
+** the lock to become available. In this scenario the normal API prepare_v2()
+** function always returns SQLITE_LOCKED.
+**
+** If this function returns SQLITE_LOCKED, the caller should rollback
+** the current transaction (if any) and try again later. Otherwise, the
+** system may become deadlocked.
+*/
+int sqlite3_blocking_prepare_v2(
+  sqlite3 *db,              /* Database handle. */
+  const char *zSql,         /* UTF-8 encoded SQL statement. */
+  int nSql,                 /* Length of zSql in bytes. */
+  sqlite3_stmt **ppStmt,    /* OUT: A pointer to the prepared statement */
+  const char **pz           /* OUT: End of parsed string */
+){
+  int rc;
+  while( SQLITE_LOCKED==(rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, pz)) ){
+    rc = wait_for_unlock_notify(db);
+    if( rc!=SQLITE_OK ) break;
+  }
+  return rc;
+}
+/* END_SQLITE_BLOCKING_STEP */
+
+/*
+** Usage: sqlite3_blocking_step STMT
+**
+** Advance the statement to the next row.
+*/
+static int blocking_step_proc(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+
+  sqlite3_stmt *pStmt;
+  int rc;
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+    return TCL_ERROR;
+  }
+
+  pStmt = (sqlite3_stmt*)sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+  rc = sqlite3_blocking_step(pStmt);
+
+  Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: sqlite3_blocking_prepare_v2 DB sql bytes ?tailvar?
+** Usage: sqlite3_nonblocking_prepare_v2 DB sql bytes ?tailvar?
+*/
+static int blocking_prepare_v2_proc(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3 *db;
+  const char *zSql;
+  int bytes;
+  const char *zTail = 0;
+  sqlite3_stmt *pStmt = 0;
+  char zBuf[50];
+  int rc;
+  int isBlocking = !(clientData==0);
+
+  if( objc!=5 && objc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", 
+       Tcl_GetString(objv[0]), " DB sql bytes tailvar", 0);
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+  zSql = Tcl_GetString(objv[2]);
+  if( Tcl_GetIntFromObj(interp, objv[3], &bytes) ) return TCL_ERROR;
+
+  if( isBlocking ){
+    rc = sqlite3_blocking_prepare_v2(db, zSql, bytes, &pStmt, &zTail);
+  }else{
+    rc = sqlite3_prepare_v2(db, zSql, bytes, &pStmt, &zTail);
+  }
+
+  assert(rc==SQLITE_OK || pStmt==0);
+  if( zTail && objc>=5 ){
+    if( bytes>=0 ){
+      bytes = bytes - (zTail-zSql);
+    }
+    Tcl_ObjSetVar2(interp, objv[4], 0, Tcl_NewStringObj(zTail, bytes), 0);
+  }
+  if( rc!=SQLITE_OK ){
+    assert( pStmt==0 );
+    sprintf(zBuf, "%s ", (char *)sqlite3ErrName(rc));
+    Tcl_AppendResult(interp, zBuf, sqlite3_errmsg(db), 0);
+    return TCL_ERROR;
+  }
+
+  if( pStmt ){
+    if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR;
+    Tcl_AppendResult(interp, zBuf, 0);
+  }
+  return TCL_OK;
+}
+
+#endif /* SQLITE_OS_UNIX && SQLITE_ENABLE_UNLOCK_NOTIFY */
+/*
+** End of implementation of [sqlite3_blocking_step].
+************************************************************************/
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int SqlitetestThread_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, 0, 0);
+  Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0);
+#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+  Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0);
+  Tcl_CreateObjCommand(interp, 
+      "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0);
+  Tcl_CreateObjCommand(interp, 
+      "sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0);
+#endif
+  return TCL_OK;
+}
+#else
+int SqlitetestThread_Init(Tcl_Interp *interp){
+  return TCL_OK;
+}
+#endif

+ 1510 - 0
components/external/sqlite/test/test_vfs.c

@@ -0,0 +1,1510 @@
+/*
+** 2010 May 05
+**
+** 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 contains the implementation of the Tcl [testvfs] command,
+** used to create SQLite VFS implementations with various properties and
+** instrumentation to support testing SQLite.
+**
+**   testvfs VFSNAME ?OPTIONS?
+**
+** Available options are:
+**
+**   -noshm      BOOLEAN        (True to omit shm methods. Default false)
+**   -default    BOOLEAN        (True to make the vfs default. Default false)
+**   -szosfile   INTEGER        (Value for sqlite3_vfs.szOsFile)
+**   -mxpathname INTEGER        (Value for sqlite3_vfs.mxPathname)
+**   -iversion   INTEGER        (Value for sqlite3_vfs.iVersion)
+*/
+#if SQLITE_TEST          /* This file is used for testing only */
+
+#include "sqlite3.h"
+#include "sqliteInt.h"
+#include <tcl.h>
+
+typedef struct Testvfs Testvfs;
+typedef struct TestvfsShm TestvfsShm;
+typedef struct TestvfsBuffer TestvfsBuffer;
+typedef struct TestvfsFile TestvfsFile;
+typedef struct TestvfsFd TestvfsFd;
+
+/*
+** An open file handle.
+*/
+struct TestvfsFile {
+  sqlite3_file base;              /* Base class.  Must be first */
+  TestvfsFd *pFd;                 /* File data */
+};
+#define tvfsGetFd(pFile) (((TestvfsFile *)pFile)->pFd)
+
+struct TestvfsFd {
+  sqlite3_vfs *pVfs;              /* The VFS */
+  const char *zFilename;          /* Filename as passed to xOpen() */
+  sqlite3_file *pReal;            /* The real, underlying file descriptor */
+  Tcl_Obj *pShmId;                /* Shared memory id for Tcl callbacks */
+
+  TestvfsBuffer *pShm;            /* Shared memory buffer */
+  u32 excllock;                   /* Mask of exclusive locks */
+  u32 sharedlock;                 /* Mask of shared locks */
+  TestvfsFd *pNext;               /* Next handle opened on the same file */
+};
+
+
+#define FAULT_INJECT_NONE       0
+#define FAULT_INJECT_TRANSIENT  1
+#define FAULT_INJECT_PERSISTENT 2
+
+typedef struct TestFaultInject TestFaultInject;
+struct TestFaultInject {
+  int iCnt;                       /* Remaining calls before fault injection */
+  int eFault;                     /* A FAULT_INJECT_* value */
+  int nFail;                      /* Number of faults injected */
+};
+
+/*
+** An instance of this structure is allocated for each VFS created. The
+** sqlite3_vfs.pAppData field of the VFS structure registered with SQLite
+** is set to point to it.
+*/
+struct Testvfs {
+  char *zName;                    /* Name of this VFS */
+  sqlite3_vfs *pParent;           /* The VFS to use for file IO */
+  sqlite3_vfs *pVfs;              /* The testvfs registered with SQLite */
+  Tcl_Interp *interp;             /* Interpreter to run script in */
+  Tcl_Obj *pScript;               /* Script to execute */
+  TestvfsBuffer *pBuffer;         /* List of shared buffers */
+  int isNoshm;
+  int isFullshm;
+
+  int mask;                       /* Mask controlling [script] and [ioerr] */
+
+  TestFaultInject ioerr_err;
+  TestFaultInject full_err;
+  TestFaultInject cantopen_err;
+
+#if 0
+  int iIoerrCnt;
+  int ioerr;
+  int nIoerrFail;
+  int iFullCnt;
+  int fullerr;
+  int nFullFail;
+#endif
+
+  int iDevchar;
+  int iSectorsize;
+};
+
+/*
+** The Testvfs.mask variable is set to a combination of the following.
+** If a bit is clear in Testvfs.mask, then calls made by SQLite to the 
+** corresponding VFS method is ignored for purposes of:
+**
+**   + Simulating IO errors, and
+**   + Invoking the Tcl callback script.
+*/
+#define TESTVFS_SHMOPEN_MASK      0x00000001
+#define TESTVFS_SHMLOCK_MASK      0x00000010
+#define TESTVFS_SHMMAP_MASK       0x00000020
+#define TESTVFS_SHMBARRIER_MASK   0x00000040
+#define TESTVFS_SHMCLOSE_MASK     0x00000080
+
+#define TESTVFS_OPEN_MASK         0x00000100
+#define TESTVFS_SYNC_MASK         0x00000200
+#define TESTVFS_DELETE_MASK       0x00000400
+#define TESTVFS_CLOSE_MASK        0x00000800
+#define TESTVFS_WRITE_MASK        0x00001000
+#define TESTVFS_TRUNCATE_MASK     0x00002000
+#define TESTVFS_ACCESS_MASK       0x00004000
+#define TESTVFS_FULLPATHNAME_MASK 0x00008000
+#define TESTVFS_READ_MASK         0x00010000
+#define TESTVFS_UNLOCK_MASK       0x00020000
+
+#define TESTVFS_ALL_MASK          0x0003FFFF
+
+
+#define TESTVFS_MAX_PAGES 1024
+
+/*
+** A shared-memory buffer. There is one of these objects for each shared
+** memory region opened by clients. If two clients open the same file,
+** there are two TestvfsFile structures but only one TestvfsBuffer structure.
+*/
+struct TestvfsBuffer {
+  char *zFile;                    /* Associated file name */
+  int pgsz;                       /* Page size */
+  u8 *aPage[TESTVFS_MAX_PAGES];   /* Array of ckalloc'd pages */
+  TestvfsFd *pFile;               /* List of open handles */
+  TestvfsBuffer *pNext;           /* Next in linked list of all buffers */
+};
+
+
+#define PARENTVFS(x) (((Testvfs *)((x)->pAppData))->pParent)
+
+#define TESTVFS_MAX_ARGS 12
+
+
+/*
+** Method declarations for TestvfsFile.
+*/
+static int tvfsClose(sqlite3_file*);
+static int tvfsRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int tvfsWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
+static int tvfsTruncate(sqlite3_file*, sqlite3_int64 size);
+static int tvfsSync(sqlite3_file*, int flags);
+static int tvfsFileSize(sqlite3_file*, sqlite3_int64 *pSize);
+static int tvfsLock(sqlite3_file*, int);
+static int tvfsUnlock(sqlite3_file*, int);
+static int tvfsCheckReservedLock(sqlite3_file*, int *);
+static int tvfsFileControl(sqlite3_file*, int op, void *pArg);
+static int tvfsSectorSize(sqlite3_file*);
+static int tvfsDeviceCharacteristics(sqlite3_file*);
+
+/*
+** Method declarations for tvfs_vfs.
+*/
+static int tvfsOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+static int tvfsDelete(sqlite3_vfs*, const char *zName, int syncDir);
+static int tvfsAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+static int tvfsFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+static void *tvfsDlOpen(sqlite3_vfs*, const char *zFilename);
+static void tvfsDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
+static void (*tvfsDlSym(sqlite3_vfs*,void*, const char *zSymbol))(void);
+static void tvfsDlClose(sqlite3_vfs*, void*);
+#endif /* SQLITE_OMIT_LOAD_EXTENSION */
+static int tvfsRandomness(sqlite3_vfs*, int nByte, char *zOut);
+static int tvfsSleep(sqlite3_vfs*, int microseconds);
+static int tvfsCurrentTime(sqlite3_vfs*, double*);
+
+static int tvfsShmOpen(sqlite3_file*);
+static int tvfsShmLock(sqlite3_file*, int , int, int);
+static int tvfsShmMap(sqlite3_file*,int,int,int, void volatile **);
+static void tvfsShmBarrier(sqlite3_file*);
+static int tvfsShmUnmap(sqlite3_file*, int);
+
+static int tvfsFetch(sqlite3_file*, sqlite3_int64, int, void**);
+static int tvfsUnfetch(sqlite3_file*, sqlite3_int64, void*);
+
+static sqlite3_io_methods tvfs_io_methods = {
+  3,                              /* iVersion */
+  tvfsClose,                      /* xClose */
+  tvfsRead,                       /* xRead */
+  tvfsWrite,                      /* xWrite */
+  tvfsTruncate,                   /* xTruncate */
+  tvfsSync,                       /* xSync */
+  tvfsFileSize,                   /* xFileSize */
+  tvfsLock,                       /* xLock */
+  tvfsUnlock,                     /* xUnlock */
+  tvfsCheckReservedLock,          /* xCheckReservedLock */
+  tvfsFileControl,                /* xFileControl */
+  tvfsSectorSize,                 /* xSectorSize */
+  tvfsDeviceCharacteristics,      /* xDeviceCharacteristics */
+  tvfsShmMap,                     /* xShmMap */
+  tvfsShmLock,                    /* xShmLock */
+  tvfsShmBarrier,                 /* xShmBarrier */
+  tvfsShmUnmap,                   /* xShmUnmap */
+  tvfsFetch,
+  tvfsUnfetch
+};
+
+static int tvfsResultCode(Testvfs *p, int *pRc){
+  struct errcode {
+    int eCode;
+    const char *zCode;
+  } aCode[] = {
+    { SQLITE_OK,     "SQLITE_OK"     },
+    { SQLITE_ERROR,  "SQLITE_ERROR"  },
+    { SQLITE_IOERR,  "SQLITE_IOERR"  },
+    { SQLITE_LOCKED, "SQLITE_LOCKED" },
+    { SQLITE_BUSY,   "SQLITE_BUSY"   },
+  };
+
+  const char *z;
+  int i;
+
+  z = Tcl_GetStringResult(p->interp);
+  for(i=0; i<ArraySize(aCode); i++){
+    if( 0==strcmp(z, aCode[i].zCode) ){
+      *pRc = aCode[i].eCode;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int tvfsInjectFault(TestFaultInject *p){
+  int ret = 0;
+  if( p->eFault ){
+    p->iCnt--;
+    if( p->iCnt==0 || (p->iCnt<0 && p->eFault==FAULT_INJECT_PERSISTENT ) ){
+      ret = 1;
+      p->nFail++;
+    }
+  }
+  return ret;
+}
+
+
+static int tvfsInjectIoerr(Testvfs *p){
+  return tvfsInjectFault(&p->ioerr_err);
+}
+
+static int tvfsInjectFullerr(Testvfs *p){
+  return tvfsInjectFault(&p->full_err);
+}
+static int tvfsInjectCantopenerr(Testvfs *p){
+  return tvfsInjectFault(&p->cantopen_err);
+}
+
+
+static void tvfsExecTcl(
+  Testvfs *p, 
+  const char *zMethod,
+  Tcl_Obj *arg1,
+  Tcl_Obj *arg2,
+  Tcl_Obj *arg3,
+  Tcl_Obj *arg4
+){
+  int rc;                         /* Return code from Tcl_EvalObj() */
+  Tcl_Obj *pEval;
+  assert( p->pScript );
+
+  assert( zMethod );
+  assert( p );
+  assert( arg2==0 || arg1!=0 );
+  assert( arg3==0 || arg2!=0 );
+
+  pEval = Tcl_DuplicateObj(p->pScript);
+  Tcl_IncrRefCount(p->pScript);
+  Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zMethod, -1));
+  if( arg1 ) Tcl_ListObjAppendElement(p->interp, pEval, arg1);
+  if( arg2 ) Tcl_ListObjAppendElement(p->interp, pEval, arg2);
+  if( arg3 ) Tcl_ListObjAppendElement(p->interp, pEval, arg3);
+  if( arg4 ) Tcl_ListObjAppendElement(p->interp, pEval, arg4);
+
+  rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
+  if( rc!=TCL_OK ){
+    Tcl_BackgroundError(p->interp);
+    Tcl_ResetResult(p->interp);
+  }
+}
+
+
+/*
+** Close an tvfs-file.
+*/
+static int tvfsClose(sqlite3_file *pFile){
+  int rc;
+  TestvfsFile *pTestfile = (TestvfsFile *)pFile;
+  TestvfsFd *pFd = pTestfile->pFd;
+  Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+
+  if( p->pScript && p->mask&TESTVFS_CLOSE_MASK ){
+    tvfsExecTcl(p, "xClose", 
+        Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 0, 0
+    );
+  }
+
+  if( pFd->pShmId ){
+    Tcl_DecrRefCount(pFd->pShmId);
+    pFd->pShmId = 0;
+  }
+  if( pFile->pMethods ){
+    ckfree((char *)pFile->pMethods);
+  }
+  rc = sqlite3OsClose(pFd->pReal);
+  ckfree((char *)pFd);
+  pTestfile->pFd = 0;
+  return rc;
+}
+
+/*
+** Read data from an tvfs-file.
+*/
+static int tvfsRead(
+  sqlite3_file *pFile, 
+  void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  int rc = SQLITE_OK;
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+  if( p->pScript && p->mask&TESTVFS_READ_MASK ){
+    tvfsExecTcl(p, "xRead", 
+        Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 0, 0
+    );
+    tvfsResultCode(p, &rc);
+  }
+  if( rc==SQLITE_OK && p->mask&TESTVFS_READ_MASK && tvfsInjectIoerr(p) ){
+    rc = SQLITE_IOERR;
+  }
+  if( rc==SQLITE_OK ){
+    rc = sqlite3OsRead(pFd->pReal, zBuf, iAmt, iOfst);
+  }
+  return rc;
+}
+
+/*
+** Write data to an tvfs-file.
+*/
+static int tvfsWrite(
+  sqlite3_file *pFile, 
+  const void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  int rc = SQLITE_OK;
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+
+  if( p->pScript && p->mask&TESTVFS_WRITE_MASK ){
+    tvfsExecTcl(p, "xWrite", 
+        Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 
+        Tcl_NewWideIntObj(iOfst), Tcl_NewIntObj(iAmt)
+    );
+    tvfsResultCode(p, &rc);
+  }
+
+  if( rc==SQLITE_OK && tvfsInjectFullerr(p) ){
+    rc = SQLITE_FULL;
+  }
+  if( rc==SQLITE_OK && p->mask&TESTVFS_WRITE_MASK && tvfsInjectIoerr(p) ){
+    rc = SQLITE_IOERR;
+  }
+  
+  if( rc==SQLITE_OK ){
+    rc = sqlite3OsWrite(pFd->pReal, zBuf, iAmt, iOfst);
+  }
+  return rc;
+}
+
+/*
+** Truncate an tvfs-file.
+*/
+static int tvfsTruncate(sqlite3_file *pFile, sqlite_int64 size){
+  int rc = SQLITE_OK;
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+
+  if( p->pScript && p->mask&TESTVFS_TRUNCATE_MASK ){
+    tvfsExecTcl(p, "xTruncate", 
+        Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 0, 0
+    );
+    tvfsResultCode(p, &rc);
+  }
+  
+  if( rc==SQLITE_OK ){
+    rc = sqlite3OsTruncate(pFd->pReal, size);
+  }
+  return rc;
+}
+
+/*
+** Sync an tvfs-file.
+*/
+static int tvfsSync(sqlite3_file *pFile, int flags){
+  int rc = SQLITE_OK;
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+
+  if( p->pScript && p->mask&TESTVFS_SYNC_MASK ){
+    char *zFlags;
+
+    switch( flags ){
+      case SQLITE_SYNC_NORMAL:
+        zFlags = "normal";
+        break;
+      case SQLITE_SYNC_FULL:
+        zFlags = "full";
+        break;
+      case SQLITE_SYNC_NORMAL|SQLITE_SYNC_DATAONLY:
+        zFlags = "normal|dataonly";
+        break;
+      case SQLITE_SYNC_FULL|SQLITE_SYNC_DATAONLY:
+        zFlags = "full|dataonly";
+        break;
+      default:
+        assert(0);
+    }
+
+    tvfsExecTcl(p, "xSync", 
+        Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId,
+        Tcl_NewStringObj(zFlags, -1), 0
+    );
+    tvfsResultCode(p, &rc);
+  }
+
+  if( rc==SQLITE_OK && tvfsInjectFullerr(p) ) rc = SQLITE_FULL;
+
+  if( rc==SQLITE_OK ){
+    rc = sqlite3OsSync(pFd->pReal, flags);
+  }
+
+  return rc;
+}
+
+/*
+** Return the current file-size of an tvfs-file.
+*/
+static int tvfsFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+  TestvfsFd *p = tvfsGetFd(pFile);
+  return sqlite3OsFileSize(p->pReal, pSize);
+}
+
+/*
+** Lock an tvfs-file.
+*/
+static int tvfsLock(sqlite3_file *pFile, int eLock){
+  TestvfsFd *p = tvfsGetFd(pFile);
+  return sqlite3OsLock(p->pReal, eLock);
+}
+
+/*
+** Unlock an tvfs-file.
+*/
+static int tvfsUnlock(sqlite3_file *pFile, int eLock){
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+  if( p->mask&TESTVFS_WRITE_MASK && tvfsInjectIoerr(p) ){
+    return SQLITE_IOERR_UNLOCK;
+  }
+  return sqlite3OsUnlock(pFd->pReal, eLock);
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on an tvfs-file.
+*/
+static int tvfsCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+  TestvfsFd *p = tvfsGetFd(pFile);
+  return sqlite3OsCheckReservedLock(p->pReal, pResOut);
+}
+
+/*
+** File control method. For custom operations on an tvfs-file.
+*/
+static int tvfsFileControl(sqlite3_file *pFile, int op, void *pArg){
+  TestvfsFd *p = tvfsGetFd(pFile);
+  if( op==SQLITE_FCNTL_PRAGMA ){
+    char **argv = (char**)pArg;
+    if( sqlite3_stricmp(argv[1],"error")==0 ){
+      int rc = SQLITE_ERROR;
+      if( argv[2] ){
+        const char *z = argv[2];
+        int x = atoi(z);
+        if( x ){
+          rc = x;
+          while( sqlite3Isdigit(z[0]) ){ z++; }
+          while( sqlite3Isspace(z[0]) ){ z++; }
+        }
+        if( z[0] ) argv[0] = sqlite3_mprintf("%s", z);
+      }
+      return rc;
+    }
+    if( sqlite3_stricmp(argv[1], "filename")==0 ){
+      argv[0] = sqlite3_mprintf("%s", p->zFilename);
+      return SQLITE_OK;
+    }
+  }
+  return sqlite3OsFileControl(p->pReal, op, pArg);
+}
+
+/*
+** Return the sector-size in bytes for an tvfs-file.
+*/
+static int tvfsSectorSize(sqlite3_file *pFile){
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+  if( p->iSectorsize>=0 ){
+    return p->iSectorsize;
+  }
+  return sqlite3OsSectorSize(pFd->pReal);
+}
+
+/*
+** Return the device characteristic flags supported by an tvfs-file.
+*/
+static int tvfsDeviceCharacteristics(sqlite3_file *pFile){
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+  if( p->iDevchar>=0 ){
+    return p->iDevchar;
+  }
+  return sqlite3OsDeviceCharacteristics(pFd->pReal);
+}
+
+/*
+** Open an tvfs file handle.
+*/
+static int tvfsOpen(
+  sqlite3_vfs *pVfs,
+  const char *zName,
+  sqlite3_file *pFile,
+  int flags,
+  int *pOutFlags
+){
+  int rc;
+  TestvfsFile *pTestfile = (TestvfsFile *)pFile;
+  TestvfsFd *pFd;
+  Tcl_Obj *pId = 0;
+  Testvfs *p = (Testvfs *)pVfs->pAppData;
+
+  pFd = (TestvfsFd *)ckalloc(sizeof(TestvfsFd) + PARENTVFS(pVfs)->szOsFile);
+  memset(pFd, 0, sizeof(TestvfsFd) + PARENTVFS(pVfs)->szOsFile);
+  pFd->pShm = 0;
+  pFd->pShmId = 0;
+  pFd->zFilename = zName;
+  pFd->pVfs = pVfs;
+  pFd->pReal = (sqlite3_file *)&pFd[1];
+  memset(pTestfile, 0, sizeof(TestvfsFile));
+  pTestfile->pFd = pFd;
+
+  /* Evaluate the Tcl script: 
+  **
+  **   SCRIPT xOpen FILENAME KEY-VALUE-ARGS
+  **
+  ** If the script returns an SQLite error code other than SQLITE_OK, an
+  ** error is returned to the caller. If it returns SQLITE_OK, the new
+  ** connection is named "anon". Otherwise, the value returned by the
+  ** script is used as the connection name.
+  */
+  Tcl_ResetResult(p->interp);
+  if( p->pScript && p->mask&TESTVFS_OPEN_MASK ){
+    Tcl_Obj *pArg = Tcl_NewObj();
+    Tcl_IncrRefCount(pArg);
+    if( flags&SQLITE_OPEN_MAIN_DB ){
+      const char *z = &zName[strlen(zName)+1];
+      while( *z ){
+        Tcl_ListObjAppendElement(0, pArg, Tcl_NewStringObj(z, -1));
+        z += strlen(z) + 1;
+        Tcl_ListObjAppendElement(0, pArg, Tcl_NewStringObj(z, -1));
+        z += strlen(z) + 1;
+      }
+    }
+    tvfsExecTcl(p, "xOpen", Tcl_NewStringObj(pFd->zFilename, -1), pArg, 0, 0);
+    Tcl_DecrRefCount(pArg);
+    if( tvfsResultCode(p, &rc) ){
+      if( rc!=SQLITE_OK ) return rc;
+    }else{
+      pId = Tcl_GetObjResult(p->interp);
+    }
+  }
+
+  if( (p->mask&TESTVFS_OPEN_MASK) &&  tvfsInjectIoerr(p) ) return SQLITE_IOERR;
+  if( tvfsInjectCantopenerr(p) ) return SQLITE_CANTOPEN;
+  if( tvfsInjectFullerr(p) ) return SQLITE_FULL;
+
+  if( !pId ){
+    pId = Tcl_NewStringObj("anon", -1);
+  }
+  Tcl_IncrRefCount(pId);
+  pFd->pShmId = pId;
+  Tcl_ResetResult(p->interp);
+
+  rc = sqlite3OsOpen(PARENTVFS(pVfs), zName, pFd->pReal, flags, pOutFlags);
+  if( pFd->pReal->pMethods ){
+    sqlite3_io_methods *pMethods;
+    int nByte;
+
+    if( pVfs->iVersion>1 ){
+      nByte = sizeof(sqlite3_io_methods);
+    }else{
+      nByte = offsetof(sqlite3_io_methods, xShmMap);
+    }
+
+    pMethods = (sqlite3_io_methods *)ckalloc(nByte);
+    memcpy(pMethods, &tvfs_io_methods, nByte);
+    pMethods->iVersion = pFd->pReal->pMethods->iVersion;
+    if( pMethods->iVersion>pVfs->iVersion ){
+      pMethods->iVersion = pVfs->iVersion;
+    }
+    if( pVfs->iVersion>1 && ((Testvfs *)pVfs->pAppData)->isNoshm ){
+      pMethods->xShmUnmap = 0;
+      pMethods->xShmLock = 0;
+      pMethods->xShmBarrier = 0;
+      pMethods->xShmMap = 0;
+    }
+    pFile->pMethods = pMethods;
+  }
+
+  return rc;
+}
+
+/*
+** Delete the file located at zPath. If the dirSync argument is true,
+** ensure the file-system modifications are synced to disk before
+** returning.
+*/
+static int tvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+  int rc = SQLITE_OK;
+  Testvfs *p = (Testvfs *)pVfs->pAppData;
+
+  if( p->pScript && p->mask&TESTVFS_DELETE_MASK ){
+    tvfsExecTcl(p, "xDelete", 
+        Tcl_NewStringObj(zPath, -1), Tcl_NewIntObj(dirSync), 0, 0
+    );
+    tvfsResultCode(p, &rc);
+  }
+  if( rc==SQLITE_OK ){
+    rc = sqlite3OsDelete(PARENTVFS(pVfs), zPath, dirSync);
+  }
+  return rc;
+}
+
+/*
+** Test for access permissions. Return true if the requested permission
+** is available, or false otherwise.
+*/
+static int tvfsAccess(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int flags, 
+  int *pResOut
+){
+  Testvfs *p = (Testvfs *)pVfs->pAppData;
+  if( p->pScript && p->mask&TESTVFS_ACCESS_MASK ){
+    int rc;
+    char *zArg = 0;
+    if( flags==SQLITE_ACCESS_EXISTS ) zArg = "SQLITE_ACCESS_EXISTS";
+    if( flags==SQLITE_ACCESS_READWRITE ) zArg = "SQLITE_ACCESS_READWRITE";
+    if( flags==SQLITE_ACCESS_READ ) zArg = "SQLITE_ACCESS_READ";
+    tvfsExecTcl(p, "xAccess", 
+        Tcl_NewStringObj(zPath, -1), Tcl_NewStringObj(zArg, -1), 0, 0
+    );
+    if( tvfsResultCode(p, &rc) ){
+      if( rc!=SQLITE_OK ) return rc;
+    }else{
+      Tcl_Interp *interp = p->interp;
+      if( TCL_OK==Tcl_GetBooleanFromObj(0, Tcl_GetObjResult(interp), pResOut) ){
+        return SQLITE_OK;
+      }
+    }
+  }
+  return sqlite3OsAccess(PARENTVFS(pVfs), zPath, flags, pResOut);
+}
+
+/*
+** Populate buffer zOut with the full canonical pathname corresponding
+** to the pathname in zPath. zOut is guaranteed to point to a buffer
+** of at least (DEVSYM_MAX_PATHNAME+1) bytes.
+*/
+static int tvfsFullPathname(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int nOut, 
+  char *zOut
+){
+  Testvfs *p = (Testvfs *)pVfs->pAppData;
+  if( p->pScript && p->mask&TESTVFS_FULLPATHNAME_MASK ){
+    int rc;
+    tvfsExecTcl(p, "xFullPathname", Tcl_NewStringObj(zPath, -1), 0, 0, 0);
+    if( tvfsResultCode(p, &rc) ){
+      if( rc!=SQLITE_OK ) return rc;
+    }
+  }
+  return sqlite3OsFullPathname(PARENTVFS(pVfs), zPath, nOut, zOut);
+}
+
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+/*
+** Open the dynamic library located at zPath and return a handle.
+*/
+static void *tvfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+  return sqlite3OsDlOpen(PARENTVFS(pVfs), zPath);
+}
+
+/*
+** Populate the buffer zErrMsg (size nByte bytes) with a human readable
+** utf-8 string describing the most recent error encountered associated 
+** with dynamic libraries.
+*/
+static void tvfsDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+  sqlite3OsDlError(PARENTVFS(pVfs), nByte, zErrMsg);
+}
+
+/*
+** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
+*/
+static void (*tvfsDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
+  return sqlite3OsDlSym(PARENTVFS(pVfs), p, zSym);
+}
+
+/*
+** Close the dynamic library handle pHandle.
+*/
+static void tvfsDlClose(sqlite3_vfs *pVfs, void *pHandle){
+  sqlite3OsDlClose(PARENTVFS(pVfs), pHandle);
+}
+#endif /* SQLITE_OMIT_LOAD_EXTENSION */
+
+/*
+** Populate the buffer pointed to by zBufOut with nByte bytes of 
+** random data.
+*/
+static int tvfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+  return sqlite3OsRandomness(PARENTVFS(pVfs), nByte, zBufOut);
+}
+
+/*
+** Sleep for nMicro microseconds. Return the number of microseconds 
+** actually slept.
+*/
+static int tvfsSleep(sqlite3_vfs *pVfs, int nMicro){
+  return sqlite3OsSleep(PARENTVFS(pVfs), nMicro);
+}
+
+/*
+** Return the current time as a Julian Day number in *pTimeOut.
+*/
+static int tvfsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+  return PARENTVFS(pVfs)->xCurrentTime(PARENTVFS(pVfs), pTimeOut);
+}
+
+static int tvfsShmOpen(sqlite3_file *pFile){
+  Testvfs *p;
+  int rc = SQLITE_OK;             /* Return code */
+  TestvfsBuffer *pBuffer;         /* Buffer to open connection to */
+  TestvfsFd *pFd;                 /* The testvfs file structure */
+
+  pFd = tvfsGetFd(pFile);
+  p = (Testvfs *)pFd->pVfs->pAppData;
+  assert( 0==p->isFullshm );
+  assert( pFd->pShmId && pFd->pShm==0 && pFd->pNext==0 );
+
+  /* Evaluate the Tcl script: 
+  **
+  **   SCRIPT xShmOpen FILENAME
+  */
+  Tcl_ResetResult(p->interp);
+  if( p->pScript && p->mask&TESTVFS_SHMOPEN_MASK ){
+    tvfsExecTcl(p, "xShmOpen", Tcl_NewStringObj(pFd->zFilename, -1), 0, 0, 0);
+    if( tvfsResultCode(p, &rc) ){
+      if( rc!=SQLITE_OK ) return rc;
+    }
+  }
+
+  assert( rc==SQLITE_OK );
+  if( p->mask&TESTVFS_SHMOPEN_MASK && tvfsInjectIoerr(p) ){
+    return SQLITE_IOERR;
+  }
+
+  /* Search for a TestvfsBuffer. Create a new one if required. */
+  for(pBuffer=p->pBuffer; pBuffer; pBuffer=pBuffer->pNext){
+    if( 0==strcmp(pFd->zFilename, pBuffer->zFile) ) break;
+  }
+  if( !pBuffer ){
+    int nByte = sizeof(TestvfsBuffer) + (int)strlen(pFd->zFilename) + 1;
+    pBuffer = (TestvfsBuffer *)ckalloc(nByte);
+    memset(pBuffer, 0, nByte);
+    pBuffer->zFile = (char *)&pBuffer[1];
+    strcpy(pBuffer->zFile, pFd->zFilename);
+    pBuffer->pNext = p->pBuffer;
+    p->pBuffer = pBuffer;
+  }
+
+  /* Connect the TestvfsBuffer to the new TestvfsShm handle and return. */
+  pFd->pNext = pBuffer->pFile;
+  pBuffer->pFile = pFd;
+  pFd->pShm = pBuffer;
+  return SQLITE_OK;
+}
+
+static void tvfsAllocPage(TestvfsBuffer *p, int iPage, int pgsz){
+  assert( iPage<TESTVFS_MAX_PAGES );
+  if( p->aPage[iPage]==0 ){
+    p->aPage[iPage] = (u8 *)ckalloc(pgsz);
+    memset(p->aPage[iPage], 0, pgsz);
+    p->pgsz = pgsz;
+  }
+}
+
+static int tvfsShmMap(
+  sqlite3_file *pFile,            /* Handle open on database file */
+  int iPage,                      /* Page to retrieve */
+  int pgsz,                       /* Size of pages */
+  int isWrite,                    /* True to extend file if necessary */
+  void volatile **pp              /* OUT: Mapped memory */
+){
+  int rc = SQLITE_OK;
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData);
+
+  if( p->isFullshm ){
+    return sqlite3OsShmMap(pFd->pReal, iPage, pgsz, isWrite, pp);
+  }
+
+  if( 0==pFd->pShm ){
+    rc = tvfsShmOpen(pFile);
+    if( rc!=SQLITE_OK ){
+      return rc;
+    }
+  }
+
+  if( p->pScript && p->mask&TESTVFS_SHMMAP_MASK ){
+    Tcl_Obj *pArg = Tcl_NewObj();
+    Tcl_IncrRefCount(pArg);
+    Tcl_ListObjAppendElement(p->interp, pArg, Tcl_NewIntObj(iPage));
+    Tcl_ListObjAppendElement(p->interp, pArg, Tcl_NewIntObj(pgsz));
+    Tcl_ListObjAppendElement(p->interp, pArg, Tcl_NewIntObj(isWrite));
+    tvfsExecTcl(p, "xShmMap", 
+        Tcl_NewStringObj(pFd->pShm->zFile, -1), pFd->pShmId, pArg, 0
+    );
+    tvfsResultCode(p, &rc);
+    Tcl_DecrRefCount(pArg);
+  }
+  if( rc==SQLITE_OK && p->mask&TESTVFS_SHMMAP_MASK && tvfsInjectIoerr(p) ){
+    rc = SQLITE_IOERR;
+  }
+
+  if( rc==SQLITE_OK && isWrite && !pFd->pShm->aPage[iPage] ){
+    tvfsAllocPage(pFd->pShm, iPage, pgsz);
+  }
+  *pp = (void volatile *)pFd->pShm->aPage[iPage];
+
+  return rc;
+}
+
+
+static int tvfsShmLock(
+  sqlite3_file *pFile,
+  int ofst,
+  int n,
+  int flags
+){
+  int rc = SQLITE_OK;
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData);
+  int nLock;
+  char zLock[80];
+
+  if( p->isFullshm ){
+    return sqlite3OsShmLock(pFd->pReal, ofst, n, flags);
+  }
+
+  if( p->pScript && p->mask&TESTVFS_SHMLOCK_MASK ){
+    sqlite3_snprintf(sizeof(zLock), zLock, "%d %d", ofst, n);
+    nLock = (int)strlen(zLock);
+    if( flags & SQLITE_SHM_LOCK ){
+      strcpy(&zLock[nLock], " lock");
+    }else{
+      strcpy(&zLock[nLock], " unlock");
+    }
+    nLock += (int)strlen(&zLock[nLock]);
+    if( flags & SQLITE_SHM_SHARED ){
+      strcpy(&zLock[nLock], " shared");
+    }else{
+      strcpy(&zLock[nLock], " exclusive");
+    }
+    tvfsExecTcl(p, "xShmLock", 
+        Tcl_NewStringObj(pFd->pShm->zFile, -1), pFd->pShmId,
+        Tcl_NewStringObj(zLock, -1), 0
+    );
+    tvfsResultCode(p, &rc);
+  }
+
+  if( rc==SQLITE_OK && p->mask&TESTVFS_SHMLOCK_MASK && tvfsInjectIoerr(p) ){
+    rc = SQLITE_IOERR;
+  }
+
+  if( rc==SQLITE_OK ){
+    int isLock = (flags & SQLITE_SHM_LOCK);
+    int isExcl = (flags & SQLITE_SHM_EXCLUSIVE);
+    u32 mask = (((1<<n)-1) << ofst);
+    if( isLock ){
+      TestvfsFd *p2;
+      for(p2=pFd->pShm->pFile; p2; p2=p2->pNext){
+        if( p2==pFd ) continue;
+        if( (p2->excllock&mask) || (isExcl && p2->sharedlock&mask) ){
+          rc = SQLITE_BUSY;
+          break;
+        }
+      }
+      if( rc==SQLITE_OK ){
+        if( isExcl )  pFd->excllock |= mask;
+        if( !isExcl ) pFd->sharedlock |= mask;
+      }
+    }else{
+      if( isExcl )  pFd->excllock &= (~mask);
+      if( !isExcl ) pFd->sharedlock &= (~mask);
+    }
+  }
+
+  return rc;
+}
+
+static void tvfsShmBarrier(sqlite3_file *pFile){
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData);
+
+  if( p->isFullshm ){
+    sqlite3OsShmBarrier(pFd->pReal);
+    return;
+  }
+
+  if( p->pScript && p->mask&TESTVFS_SHMBARRIER_MASK ){
+    tvfsExecTcl(p, "xShmBarrier", 
+        Tcl_NewStringObj(pFd->pShm->zFile, -1), pFd->pShmId, 0, 0
+    );
+  }
+}
+
+static int tvfsShmUnmap(
+  sqlite3_file *pFile,
+  int deleteFlag
+){
+  int rc = SQLITE_OK;
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData);
+  TestvfsBuffer *pBuffer = pFd->pShm;
+  TestvfsFd **ppFd;
+
+  if( p->isFullshm ){
+    return sqlite3OsShmUnmap(pFd->pReal, deleteFlag);
+  }
+
+  if( !pBuffer ) return SQLITE_OK;
+  assert( pFd->pShmId && pFd->pShm );
+
+  if( p->pScript && p->mask&TESTVFS_SHMCLOSE_MASK ){
+    tvfsExecTcl(p, "xShmUnmap", 
+        Tcl_NewStringObj(pFd->pShm->zFile, -1), pFd->pShmId, 0, 0
+    );
+    tvfsResultCode(p, &rc);
+  }
+
+  for(ppFd=&pBuffer->pFile; *ppFd!=pFd; ppFd=&((*ppFd)->pNext));
+  assert( (*ppFd)==pFd );
+  *ppFd = pFd->pNext;
+  pFd->pNext = 0;
+
+  if( pBuffer->pFile==0 ){
+    int i;
+    TestvfsBuffer **pp;
+    for(pp=&p->pBuffer; *pp!=pBuffer; pp=&((*pp)->pNext));
+    *pp = (*pp)->pNext;
+    for(i=0; pBuffer->aPage[i]; i++){
+      ckfree((char *)pBuffer->aPage[i]);
+    }
+    ckfree((char *)pBuffer);
+  }
+  pFd->pShm = 0;
+
+  return rc;
+}
+
+static int tvfsFetch(
+    sqlite3_file *pFile, 
+    sqlite3_int64 iOfst, 
+    int iAmt, 
+    void **pp
+){
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  return sqlite3OsFetch(pFd->pReal, iOfst, iAmt, pp);
+}
+
+static int tvfsUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *p){
+  TestvfsFd *pFd = tvfsGetFd(pFile);
+  return sqlite3OsUnfetch(pFd->pReal, iOfst, p);
+}
+
+static int testvfs_obj_cmd(
+  ClientData cd,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  Testvfs *p = (Testvfs *)cd;
+
+  enum DB_enum { 
+    CMD_SHM, CMD_DELETE, CMD_FILTER, CMD_IOERR, CMD_SCRIPT, 
+    CMD_DEVCHAR, CMD_SECTORSIZE, CMD_FULLERR, CMD_CANTOPENERR
+  };
+  struct TestvfsSubcmd {
+    char *zName;
+    enum DB_enum eCmd;
+  } aSubcmd[] = {
+    { "shm",         CMD_SHM         },
+    { "delete",      CMD_DELETE      },
+    { "filter",      CMD_FILTER      },
+    { "ioerr",       CMD_IOERR       },
+    { "fullerr",     CMD_FULLERR     },
+    { "cantopenerr", CMD_CANTOPENERR },
+    { "script",      CMD_SCRIPT      },
+    { "devchar",     CMD_DEVCHAR     },
+    { "sectorsize",  CMD_SECTORSIZE  },
+    { 0, 0 }
+  };
+  int i;
+  
+  if( objc<2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIndexFromObjStruct(
+        interp, objv[1], aSubcmd, sizeof(aSubcmd[0]), "subcommand", 0, &i) 
+  ){
+    return TCL_ERROR;
+  }
+  Tcl_ResetResult(interp);
+
+  switch( aSubcmd[i].eCmd ){
+    case CMD_SHM: {
+      Tcl_Obj *pObj;
+      int i, rc;
+      TestvfsBuffer *pBuffer;
+      char *zName;
+      if( objc!=3 && objc!=4 ){
+        Tcl_WrongNumArgs(interp, 2, objv, "FILE ?VALUE?");
+        return TCL_ERROR;
+      }
+      zName = ckalloc(p->pParent->mxPathname);
+      rc = p->pParent->xFullPathname(
+          p->pParent, Tcl_GetString(objv[2]), 
+          p->pParent->mxPathname, zName
+      );
+      if( rc!=SQLITE_OK ){
+        Tcl_AppendResult(interp, "failed to get full path: ",
+                         Tcl_GetString(objv[2]), 0);
+        ckfree(zName);
+        return TCL_ERROR;
+      }
+      for(pBuffer=p->pBuffer; pBuffer; pBuffer=pBuffer->pNext){
+        if( 0==strcmp(pBuffer->zFile, zName) ) break;
+      }
+      ckfree(zName);
+      if( !pBuffer ){
+        Tcl_AppendResult(interp, "no such file: ", Tcl_GetString(objv[2]), 0);
+        return TCL_ERROR;
+      }
+      if( objc==4 ){
+        int n;
+        u8 *a = Tcl_GetByteArrayFromObj(objv[3], &n);
+        int pgsz = pBuffer->pgsz;
+        if( pgsz==0 ) pgsz = 65536;
+        for(i=0; i*pgsz<n; i++){
+          int nByte = pgsz;
+          tvfsAllocPage(pBuffer, i, pgsz);
+          if( n-i*pgsz<pgsz ){
+            nByte = n;
+          }
+          memcpy(pBuffer->aPage[i], &a[i*pgsz], nByte);
+        }
+      }
+
+      pObj = Tcl_NewObj();
+      for(i=0; pBuffer->aPage[i]; i++){
+        int pgsz = pBuffer->pgsz;
+        if( pgsz==0 ) pgsz = 65536;
+        Tcl_AppendObjToObj(pObj, Tcl_NewByteArrayObj(pBuffer->aPage[i], pgsz));
+      }
+      Tcl_SetObjResult(interp, pObj);
+      break;
+    }
+
+    case CMD_FILTER: {
+      static struct VfsMethod {
+        char *zName;
+        int mask;
+      } vfsmethod [] = {
+        { "xShmOpen",      TESTVFS_SHMOPEN_MASK },
+        { "xShmLock",      TESTVFS_SHMLOCK_MASK },
+        { "xShmBarrier",   TESTVFS_SHMBARRIER_MASK },
+        { "xShmUnmap",     TESTVFS_SHMCLOSE_MASK },
+        { "xShmMap",       TESTVFS_SHMMAP_MASK },
+        { "xSync",         TESTVFS_SYNC_MASK },
+        { "xDelete",       TESTVFS_DELETE_MASK },
+        { "xWrite",        TESTVFS_WRITE_MASK },
+        { "xRead",         TESTVFS_READ_MASK },
+        { "xTruncate",     TESTVFS_TRUNCATE_MASK },
+        { "xOpen",         TESTVFS_OPEN_MASK },
+        { "xClose",        TESTVFS_CLOSE_MASK },
+        { "xAccess",       TESTVFS_ACCESS_MASK },
+        { "xFullPathname", TESTVFS_FULLPATHNAME_MASK },
+        { "xUnlock",       TESTVFS_UNLOCK_MASK },
+      };
+      Tcl_Obj **apElem = 0;
+      int nElem = 0;
+      int i;
+      int mask = 0;
+      if( objc!=3 ){
+        Tcl_WrongNumArgs(interp, 2, objv, "LIST");
+        return TCL_ERROR;
+      }
+      if( Tcl_ListObjGetElements(interp, objv[2], &nElem, &apElem) ){
+        return TCL_ERROR;
+      }
+      Tcl_ResetResult(interp);
+      for(i=0; i<nElem; i++){
+        int iMethod;
+        char *zElem = Tcl_GetString(apElem[i]);
+        for(iMethod=0; iMethod<ArraySize(vfsmethod); iMethod++){
+          if( strcmp(zElem, vfsmethod[iMethod].zName)==0 ){
+            mask |= vfsmethod[iMethod].mask;
+            break;
+          }
+        }
+        if( iMethod==ArraySize(vfsmethod) ){
+          Tcl_AppendResult(interp, "unknown method: ", zElem, 0);
+          return TCL_ERROR;
+        }
+      }
+      p->mask = mask;
+      break;
+    }
+
+    case CMD_SCRIPT: {
+      if( objc==3 ){
+        int nByte;
+        if( p->pScript ){
+          Tcl_DecrRefCount(p->pScript);
+          p->pScript = 0;
+        }
+        Tcl_GetStringFromObj(objv[2], &nByte);
+        if( nByte>0 ){
+          p->pScript = Tcl_DuplicateObj(objv[2]);
+          Tcl_IncrRefCount(p->pScript);
+        }
+      }else if( objc!=2 ){
+        Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
+        return TCL_ERROR;
+      }
+
+      Tcl_ResetResult(interp);
+      if( p->pScript ) Tcl_SetObjResult(interp, p->pScript);
+
+      break;
+    }
+
+    /*
+    ** TESTVFS ioerr ?IFAIL PERSIST?
+    **
+    **   Where IFAIL is an integer and PERSIST is boolean.
+    */
+    case CMD_CANTOPENERR:
+    case CMD_IOERR:
+    case CMD_FULLERR: {
+      TestFaultInject *pTest;
+      int iRet;
+
+      switch( aSubcmd[i].eCmd ){
+        case CMD_IOERR: pTest = &p->ioerr_err; break;
+        case CMD_FULLERR: pTest = &p->full_err; break;
+        case CMD_CANTOPENERR: pTest = &p->cantopen_err; break;
+        default: assert(0);
+      }
+      iRet = pTest->nFail;
+      pTest->nFail = 0;
+      pTest->eFault = 0;
+      pTest->iCnt = 0;
+
+      if( objc==4 ){
+        int iCnt, iPersist;
+        if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iCnt)
+         || TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[3], &iPersist)
+        ){
+          return TCL_ERROR;
+        }
+        pTest->eFault = iPersist?FAULT_INJECT_PERSISTENT:FAULT_INJECT_TRANSIENT;
+        pTest->iCnt = iCnt;
+      }else if( objc!=2 ){
+        Tcl_WrongNumArgs(interp, 2, objv, "?CNT PERSIST?");
+        return TCL_ERROR;
+      }
+      Tcl_SetObjResult(interp, Tcl_NewIntObj(iRet));
+      break;
+    }
+
+    case CMD_DELETE: {
+      Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+      break;
+    }
+
+    case CMD_DEVCHAR: {
+      struct DeviceFlag {
+        char *zName;
+        int iValue;
+      } aFlag[] = {
+        { "default",               -1 },
+        { "atomic",                SQLITE_IOCAP_ATOMIC                },
+        { "atomic512",             SQLITE_IOCAP_ATOMIC512             },
+        { "atomic1k",              SQLITE_IOCAP_ATOMIC1K              },
+        { "atomic2k",              SQLITE_IOCAP_ATOMIC2K              },
+        { "atomic4k",              SQLITE_IOCAP_ATOMIC4K              },
+        { "atomic8k",              SQLITE_IOCAP_ATOMIC8K              },
+        { "atomic16k",             SQLITE_IOCAP_ATOMIC16K             },
+        { "atomic32k",             SQLITE_IOCAP_ATOMIC32K             },
+        { "atomic64k",             SQLITE_IOCAP_ATOMIC64K             },
+        { "sequential",            SQLITE_IOCAP_SEQUENTIAL            },
+        { "safe_append",           SQLITE_IOCAP_SAFE_APPEND           },
+        { "undeletable_when_open", SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN },
+        { "powersafe_overwrite",   SQLITE_IOCAP_POWERSAFE_OVERWRITE   },
+        { 0, 0 }
+      };
+      Tcl_Obj *pRet;
+      int iFlag;
+
+      if( objc>3 ){
+        Tcl_WrongNumArgs(interp, 2, objv, "?ATTR-LIST?");
+        return TCL_ERROR;
+      }
+      if( objc==3 ){
+        int j;
+        int iNew = 0;
+        Tcl_Obj **flags = 0;
+        int nFlags = 0;
+
+        if( Tcl_ListObjGetElements(interp, objv[2], &nFlags, &flags) ){
+          return TCL_ERROR;
+        }
+
+        for(j=0; j<nFlags; j++){
+          int idx = 0;
+          if( Tcl_GetIndexFromObjStruct(interp, flags[j], aFlag, 
+                sizeof(aFlag[0]), "flag", 0, &idx) 
+          ){
+            return TCL_ERROR;
+          }
+          if( aFlag[idx].iValue<0 && nFlags>1 ){
+            Tcl_AppendResult(interp, "bad flags: ", Tcl_GetString(objv[2]), 0);
+            return TCL_ERROR;
+          }
+          iNew |= aFlag[idx].iValue;
+        }
+
+        p->iDevchar = iNew| 0x10000000;
+      }
+
+      pRet = Tcl_NewObj();
+      for(iFlag=0; iFlag<sizeof(aFlag)/sizeof(aFlag[0]); iFlag++){
+        if( p->iDevchar & aFlag[iFlag].iValue ){
+          Tcl_ListObjAppendElement(
+              interp, pRet, Tcl_NewStringObj(aFlag[iFlag].zName, -1)
+          );
+        }
+      }
+      Tcl_SetObjResult(interp, pRet);
+
+      break;
+    }
+
+    case CMD_SECTORSIZE: {
+      if( objc>3 ){
+        Tcl_WrongNumArgs(interp, 2, objv, "?VALUE?");
+        return TCL_ERROR;
+      }
+      if( objc==3 ){
+        int iNew = 0;
+        if( Tcl_GetIntFromObj(interp, objv[2], &iNew) ){
+          return TCL_ERROR;
+        }
+        p->iSectorsize = iNew;
+      }
+      Tcl_SetObjResult(interp, Tcl_NewIntObj(p->iSectorsize));
+      break;
+    }
+  }
+
+  return TCL_OK;
+}
+
+static void testvfs_obj_del(ClientData cd){
+  Testvfs *p = (Testvfs *)cd;
+  if( p->pScript ) Tcl_DecrRefCount(p->pScript);
+  sqlite3_vfs_unregister(p->pVfs);
+  ckfree((char *)p->pVfs);
+  ckfree((char *)p);
+}
+
+/*
+** Usage:  testvfs VFSNAME ?SWITCHES?
+**
+** Switches are:
+**
+**   -noshm   BOOLEAN             (True to omit shm methods. Default false)
+**   -default BOOLEAN             (True to make the vfs default. Default false)
+**
+** This command creates two things when it is invoked: an SQLite VFS, and
+** a Tcl command. Both are named VFSNAME. The VFS is installed. It is not
+** installed as the default VFS.
+**
+** The VFS passes all file I/O calls through to the underlying VFS.
+**
+** Whenever the xShmMap method of the VFS
+** is invoked, the SCRIPT is executed as follows:
+**
+**   SCRIPT xShmMap    FILENAME ID
+**
+** The value returned by the invocation of SCRIPT above is interpreted as
+** an SQLite error code and returned to SQLite. Either a symbolic 
+** "SQLITE_OK" or numeric "0" value may be returned.
+**
+** The contents of the shared-memory buffer associated with a given file
+** may be read and set using the following command:
+**
+**   VFSNAME shm FILENAME ?NEWVALUE?
+**
+** When the xShmLock method is invoked by SQLite, the following script is
+** run:
+**
+**   SCRIPT xShmLock    FILENAME ID LOCK
+**
+** where LOCK is of the form "OFFSET NBYTE lock/unlock shared/exclusive"
+*/
+static int testvfs_cmd(
+  ClientData cd,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  static sqlite3_vfs tvfs_vfs = {
+    3,                            /* iVersion */
+    0,                            /* szOsFile */
+    0,                            /* mxPathname */
+    0,                            /* pNext */
+    0,                            /* zName */
+    0,                            /* pAppData */
+    tvfsOpen,                     /* xOpen */
+    tvfsDelete,                   /* xDelete */
+    tvfsAccess,                   /* xAccess */
+    tvfsFullPathname,             /* xFullPathname */
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+    tvfsDlOpen,                   /* xDlOpen */
+    tvfsDlError,                  /* xDlError */
+    tvfsDlSym,                    /* xDlSym */
+    tvfsDlClose,                  /* xDlClose */
+#else
+    0,                            /* xDlOpen */
+    0,                            /* xDlError */
+    0,                            /* xDlSym */
+    0,                            /* xDlClose */
+#endif /* SQLITE_OMIT_LOAD_EXTENSION */
+    tvfsRandomness,               /* xRandomness */
+    tvfsSleep,                    /* xSleep */
+    tvfsCurrentTime,              /* xCurrentTime */
+    0,                            /* xGetLastError */
+    0,                            /* xCurrentTimeInt64 */
+    0,                            /* xSetSystemCall */
+    0,                            /* xGetSystemCall */
+    0,                            /* xNextSystemCall */
+  };
+
+  Testvfs *p;                     /* New object */
+  sqlite3_vfs *pVfs;              /* New VFS */
+  char *zVfs;
+  int nByte;                      /* Bytes of space to allocate at p */
+
+  int i;
+  int isNoshm = 0;                /* True if -noshm is passed */
+  int isFullshm = 0;              /* True if -fullshm is passed */
+  int isDefault = 0;              /* True if -default is passed */
+  int szOsFile = 0;               /* Value passed to -szosfile */
+  int mxPathname = -1;            /* Value passed to -mxpathname */
+  int iVersion = 3;               /* Value passed to -iversion */
+
+  if( objc<2 || 0!=(objc%2) ) goto bad_args;
+  for(i=2; i<objc; i += 2){
+    int nSwitch;
+    char *zSwitch;
+    zSwitch = Tcl_GetStringFromObj(objv[i], &nSwitch); 
+
+    if( nSwitch>2 && 0==strncmp("-noshm", zSwitch, nSwitch) ){
+      if( Tcl_GetBooleanFromObj(interp, objv[i+1], &isNoshm) ){
+        return TCL_ERROR;
+      }
+      if( isNoshm ) isFullshm = 0;
+    }
+    else if( nSwitch>2 && 0==strncmp("-default", zSwitch, nSwitch) ){
+      if( Tcl_GetBooleanFromObj(interp, objv[i+1], &isDefault) ){
+        return TCL_ERROR;
+      }
+    }
+    else if( nSwitch>2 && 0==strncmp("-szosfile", zSwitch, nSwitch) ){
+      if( Tcl_GetIntFromObj(interp, objv[i+1], &szOsFile) ){
+        return TCL_ERROR;
+      }
+    }
+    else if( nSwitch>2 && 0==strncmp("-mxpathname", zSwitch, nSwitch) ){
+      if( Tcl_GetIntFromObj(interp, objv[i+1], &mxPathname) ){
+        return TCL_ERROR;
+      }
+    }
+    else if( nSwitch>2 && 0==strncmp("-iversion", zSwitch, nSwitch) ){
+      if( Tcl_GetIntFromObj(interp, objv[i+1], &iVersion) ){
+        return TCL_ERROR;
+      }
+    }
+    else if( nSwitch>2 && 0==strncmp("-fullshm", zSwitch, nSwitch) ){
+      if( Tcl_GetBooleanFromObj(interp, objv[i+1], &isFullshm) ){
+        return TCL_ERROR;
+      }
+      if( isFullshm ) isNoshm = 0;
+    }
+    else{
+      goto bad_args;
+    }
+  }
+
+  if( szOsFile<sizeof(TestvfsFile) ){
+    szOsFile = sizeof(TestvfsFile);
+  }
+
+  zVfs = Tcl_GetString(objv[1]);
+  nByte = sizeof(Testvfs) + (int)strlen(zVfs)+1;
+  p = (Testvfs *)ckalloc(nByte);
+  memset(p, 0, nByte);
+  p->iDevchar = -1;
+  p->iSectorsize = -1;
+
+  /* Create the new object command before querying SQLite for a default VFS
+  ** to use for 'real' IO operations. This is because creating the new VFS
+  ** may delete an existing [testvfs] VFS of the same name. If such a VFS
+  ** is currently the default, the new [testvfs] may end up calling the 
+  ** methods of a deleted object.
+  */
+  Tcl_CreateObjCommand(interp, zVfs, testvfs_obj_cmd, p, testvfs_obj_del);
+  p->pParent = sqlite3_vfs_find(0);
+  p->interp = interp;
+
+  p->zName = (char *)&p[1];
+  memcpy(p->zName, zVfs, strlen(zVfs)+1);
+
+  pVfs = (sqlite3_vfs *)ckalloc(sizeof(sqlite3_vfs));
+  memcpy(pVfs, &tvfs_vfs, sizeof(sqlite3_vfs));
+  pVfs->pAppData = (void *)p;
+  pVfs->iVersion = iVersion;
+  pVfs->zName = p->zName;
+  pVfs->mxPathname = p->pParent->mxPathname;
+  if( mxPathname>=0 && mxPathname<pVfs->mxPathname ){
+    pVfs->mxPathname = mxPathname;
+  }
+  pVfs->szOsFile = szOsFile;
+  p->pVfs = pVfs;
+  p->isNoshm = isNoshm;
+  p->isFullshm = isFullshm;
+  p->mask = TESTVFS_ALL_MASK;
+
+  sqlite3_vfs_register(pVfs, isDefault);
+
+  return TCL_OK;
+
+ bad_args:
+  Tcl_WrongNumArgs(interp, 1, objv, "VFSNAME ?-noshm BOOL? ?-default BOOL? ?-mxpathname INT? ?-szosfile INT? ?-iversion INT?");
+  return TCL_ERROR;
+}
+
+int Sqlitetestvfs_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "testvfs", testvfs_cmd, 0, 0);
+  return TCL_OK;
+}
+
+#endif

+ 887 - 0
components/external/sqlite/test/test_vfstrace.c

@@ -0,0 +1,887 @@
+/*
+** 2011 March 16
+**
+** 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 contains code implements a VFS shim that writes diagnostic
+** output for each VFS call, similar to "strace".
+**
+** USAGE:
+**
+** This source file exports a single symbol which is the name of a
+** function:
+**
+**   int vfstrace_register(
+**     const char *zTraceName,         // Name of the newly constructed VFS
+**     const char *zOldVfsName,        // Name of the underlying VFS
+**     int (*xOut)(const char*,void*), // Output routine.  ex: fputs
+**     void *pOutArg,                  // 2nd argument to xOut.  ex: stderr
+**     int makeDefault                 // Make the new VFS the default
+**   );
+**
+** Applications that want to trace their VFS usage must provide a callback
+** function with this prototype:
+**
+**   int traceOutput(const char *zMessage, void *pAppData);
+**
+** This function will "output" the trace messages, where "output" can
+** mean different things to different applications.  The traceOutput function
+** for the command-line shell (see shell.c) is "fputs" from the standard
+** library, which means that all trace output is written on the stream
+** specified by the second argument.  In the case of the command-line shell
+** the second argument is stderr.  Other applications might choose to output
+** trace information to a file, over a socket, or write it into a buffer.
+**
+** The vfstrace_register() function creates a new "shim" VFS named by
+** the zTraceName parameter.  A "shim" VFS is an SQLite backend that does
+** not really perform the duties of a true backend, but simply filters or
+** interprets VFS calls before passing them off to another VFS which does
+** the actual work.  In this case the other VFS - the one that does the
+** real work - is identified by the second parameter, zOldVfsName.  If
+** the 2nd parameter is NULL then the default VFS is used.  The common
+** case is for the 2nd parameter to be NULL.
+**
+** The third and fourth parameters are the pointer to the output function
+** and the second argument to the output function.  For the SQLite
+** command-line shell, when the -vfstrace option is used, these parameters
+** are fputs and stderr, respectively.
+**
+** The fifth argument is true (non-zero) to cause the newly created VFS
+** to become the default VFS.  The common case is for the fifth parameter
+** to be true.
+**
+** The call to vfstrace_register() simply creates the shim VFS that does
+** tracing.  The application must also arrange to use the new VFS for
+** all database connections that are created and for which tracing is 
+** desired.  This can be done by specifying the trace VFS using URI filename
+** notation, or by specifying the trace VFS as the 4th parameter to
+** sqlite3_open_v2() or by making the trace VFS be the default (by setting
+** the 5th parameter of vfstrace_register() to 1).
+**
+**
+** ENABLING VFSTRACE IN A COMMAND-LINE SHELL
+**
+** The SQLite command line shell implemented by the shell.c source file
+** can be used with this module.  To compile in -vfstrace support, first
+** gather this file (test_vfstrace.c), the shell source file (shell.c),
+** and the SQLite amalgamation source files (sqlite3.c, sqlite3.h) into
+** the working directory.  Then compile using a command like the following:
+**
+**    gcc -o sqlite3 -Os -I. -DSQLITE_ENABLE_VFSTRACE \
+**        -DSQLITE_THREADSAFE=0 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE \
+**        -DHAVE_READLINE -DHAVE_USLEEP=1 \
+**        shell.c test_vfstrace.c sqlite3.c -ldl -lreadline -lncurses
+**
+** The gcc command above works on Linux and provides (in addition to the
+** -vfstrace option) support for FTS3 and FTS4, RTREE, and command-line
+** editing using the readline library.  The command-line shell does not
+** use threads so we added -DSQLITE_THREADSAFE=0 just to make the code
+** run a little faster.   For compiling on a Mac, you'll probably need
+** to omit the -DHAVE_READLINE, the -lreadline, and the -lncurses options.
+** The compilation could be simplified to just this:
+**
+**    gcc -DSQLITE_ENABLE_VFSTRACE \
+**         shell.c test_vfstrace.c sqlite3.c -ldl -lpthread
+**
+** In this second example, all unnecessary options have been removed
+** Note that since the code is now threadsafe, we had to add the -lpthread
+** option to pull in the pthreads library.
+**
+** To cross-compile for windows using MinGW, a command like this might
+** work:
+**
+**    /opt/mingw/bin/i386-mingw32msvc-gcc -o sqlite3.exe -Os -I \
+**         -DSQLITE_THREADSAFE=0 -DSQLITE_ENABLE_VFSTRACE \
+**         shell.c test_vfstrace.c sqlite3.c
+**
+** Similar compiler commands will work on different systems.  The key
+** invariants are (1) you must have -DSQLITE_ENABLE_VFSTRACE so that
+** the shell.c source file will know to include the -vfstrace command-line
+** option and (2) you must compile and link the three source files
+** shell,c, test_vfstrace.c, and sqlite3.c.  
+*/
+#include <stdlib.h>
+#include <string.h>
+#include "sqlite3.h"
+
+/*
+** An instance of this structure is attached to the each trace VFS to
+** provide auxiliary information.
+*/
+typedef struct vfstrace_info vfstrace_info;
+struct vfstrace_info {
+  sqlite3_vfs *pRootVfs;              /* The underlying real VFS */
+  int (*xOut)(const char*, void*);    /* Send output here */
+  void *pOutArg;                      /* First argument to xOut */
+  const char *zVfsName;               /* Name of this trace-VFS */
+  sqlite3_vfs *pTraceVfs;             /* Pointer back to the trace VFS */
+};
+
+/*
+** The sqlite3_file object for the trace VFS
+*/
+typedef struct vfstrace_file vfstrace_file;
+struct vfstrace_file {
+  sqlite3_file base;        /* Base class.  Must be first */
+  vfstrace_info *pInfo;     /* The trace-VFS to which this file belongs */
+  const char *zFName;       /* Base name of the file */
+  sqlite3_file *pReal;      /* The real underlying file */
+};
+
+/*
+** Method declarations for vfstrace_file.
+*/
+static int vfstraceClose(sqlite3_file*);
+static int vfstraceRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int vfstraceWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64);
+static int vfstraceTruncate(sqlite3_file*, sqlite3_int64 size);
+static int vfstraceSync(sqlite3_file*, int flags);
+static int vfstraceFileSize(sqlite3_file*, sqlite3_int64 *pSize);
+static int vfstraceLock(sqlite3_file*, int);
+static int vfstraceUnlock(sqlite3_file*, int);
+static int vfstraceCheckReservedLock(sqlite3_file*, int *);
+static int vfstraceFileControl(sqlite3_file*, int op, void *pArg);
+static int vfstraceSectorSize(sqlite3_file*);
+static int vfstraceDeviceCharacteristics(sqlite3_file*);
+static int vfstraceShmLock(sqlite3_file*,int,int,int);
+static int vfstraceShmMap(sqlite3_file*,int,int,int, void volatile **);
+static void vfstraceShmBarrier(sqlite3_file*);
+static int vfstraceShmUnmap(sqlite3_file*,int);
+
+/*
+** Method declarations for vfstrace_vfs.
+*/
+static int vfstraceOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+static int vfstraceDelete(sqlite3_vfs*, const char *zName, int syncDir);
+static int vfstraceAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+static int vfstraceFullPathname(sqlite3_vfs*, const char *zName, int, char *);
+static void *vfstraceDlOpen(sqlite3_vfs*, const char *zFilename);
+static void vfstraceDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
+static void (*vfstraceDlSym(sqlite3_vfs*,void*, const char *zSymbol))(void);
+static void vfstraceDlClose(sqlite3_vfs*, void*);
+static int vfstraceRandomness(sqlite3_vfs*, int nByte, char *zOut);
+static int vfstraceSleep(sqlite3_vfs*, int microseconds);
+static int vfstraceCurrentTime(sqlite3_vfs*, double*);
+static int vfstraceGetLastError(sqlite3_vfs*, int, char*);
+static int vfstraceCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
+static int vfstraceSetSystemCall(sqlite3_vfs*,const char*, sqlite3_syscall_ptr);
+static sqlite3_syscall_ptr vfstraceGetSystemCall(sqlite3_vfs*, const char *);
+static const char *vfstraceNextSystemCall(sqlite3_vfs*, const char *zName);
+
+/*
+** Return a pointer to the tail of the pathname.  Examples:
+**
+**     /home/drh/xyzzy.txt -> xyzzy.txt
+**     xyzzy.txt           -> xyzzy.txt
+*/
+static const char *fileTail(const char *z){
+  int i;
+  if( z==0 ) return 0;
+  i = strlen(z)-1;
+  while( i>0 && z[i-1]!='/' ){ i--; }
+  return &z[i];
+}
+
+/*
+** Send trace output defined by zFormat and subsequent arguments.
+*/
+static void vfstrace_printf(
+  vfstrace_info *pInfo,
+  const char *zFormat,
+  ...
+){
+  va_list ap;
+  char *zMsg;
+  va_start(ap, zFormat);
+  zMsg = sqlite3_vmprintf(zFormat, ap);
+  va_end(ap);
+  pInfo->xOut(zMsg, pInfo->pOutArg);
+  sqlite3_free(zMsg);
+}
+
+/*
+** Convert value rc into a string and print it using zFormat.  zFormat
+** should have exactly one %s
+*/
+static void vfstrace_print_errcode(
+  vfstrace_info *pInfo,
+  const char *zFormat,
+  int rc
+){
+  char zBuf[50];
+  char *zVal;
+  switch( rc ){
+    case SQLITE_OK:         zVal = "SQLITE_OK";          break;
+    case SQLITE_ERROR:      zVal = "SQLITE_ERROR";       break;
+    case SQLITE_PERM:       zVal = "SQLITE_PERM";        break;
+    case SQLITE_ABORT:      zVal = "SQLITE_ABORT";       break;
+    case SQLITE_BUSY:       zVal = "SQLITE_BUSY";        break;
+    case SQLITE_NOMEM:      zVal = "SQLITE_NOMEM";       break;
+    case SQLITE_READONLY:   zVal = "SQLITE_READONLY";    break;
+    case SQLITE_INTERRUPT:  zVal = "SQLITE_INTERRUPT";   break;
+    case SQLITE_IOERR:      zVal = "SQLITE_IOERR";       break;
+    case SQLITE_CORRUPT:    zVal = "SQLITE_CORRUPT";     break;
+    case SQLITE_FULL:       zVal = "SQLITE_FULL";        break;
+    case SQLITE_CANTOPEN:   zVal = "SQLITE_CANTOPEN";    break;
+    case SQLITE_PROTOCOL:   zVal = "SQLITE_PROTOCOL";    break;
+    case SQLITE_EMPTY:      zVal = "SQLITE_EMPTY";       break;
+    case SQLITE_SCHEMA:     zVal = "SQLITE_SCHEMA";      break;
+    case SQLITE_CONSTRAINT: zVal = "SQLITE_CONSTRAINT";  break;
+    case SQLITE_MISMATCH:   zVal = "SQLITE_MISMATCH";    break;
+    case SQLITE_MISUSE:     zVal = "SQLITE_MISUSE";      break;
+    case SQLITE_NOLFS:      zVal = "SQLITE_NOLFS";       break;
+    case SQLITE_IOERR_READ:         zVal = "SQLITE_IOERR_READ";         break;
+    case SQLITE_IOERR_SHORT_READ:   zVal = "SQLITE_IOERR_SHORT_READ";   break;
+    case SQLITE_IOERR_WRITE:        zVal = "SQLITE_IOERR_WRITE";        break;
+    case SQLITE_IOERR_FSYNC:        zVal = "SQLITE_IOERR_FSYNC";        break;
+    case SQLITE_IOERR_DIR_FSYNC:    zVal = "SQLITE_IOERR_DIR_FSYNC";    break;
+    case SQLITE_IOERR_TRUNCATE:     zVal = "SQLITE_IOERR_TRUNCATE";     break;
+    case SQLITE_IOERR_FSTAT:        zVal = "SQLITE_IOERR_FSTAT";        break;
+    case SQLITE_IOERR_UNLOCK:       zVal = "SQLITE_IOERR_UNLOCK";       break;
+    case SQLITE_IOERR_RDLOCK:       zVal = "SQLITE_IOERR_RDLOCK";       break;
+    case SQLITE_IOERR_DELETE:       zVal = "SQLITE_IOERR_DELETE";       break;
+    case SQLITE_IOERR_BLOCKED:      zVal = "SQLITE_IOERR_BLOCKED";      break;
+    case SQLITE_IOERR_NOMEM:        zVal = "SQLITE_IOERR_NOMEM";        break;
+    case SQLITE_IOERR_ACCESS:       zVal = "SQLITE_IOERR_ACCESS";       break;
+    case SQLITE_IOERR_CHECKRESERVEDLOCK:
+                               zVal = "SQLITE_IOERR_CHECKRESERVEDLOCK"; break;
+    case SQLITE_IOERR_LOCK:         zVal = "SQLITE_IOERR_LOCK";         break;
+    case SQLITE_IOERR_CLOSE:        zVal = "SQLITE_IOERR_CLOSE";        break;
+    case SQLITE_IOERR_DIR_CLOSE:    zVal = "SQLITE_IOERR_DIR_CLOSE";    break;
+    case SQLITE_IOERR_SHMOPEN:      zVal = "SQLITE_IOERR_SHMOPEN";      break;
+    case SQLITE_IOERR_SHMSIZE:      zVal = "SQLITE_IOERR_SHMSIZE";      break;
+    case SQLITE_IOERR_SHMLOCK:      zVal = "SQLITE_IOERR_SHMLOCK";      break;
+    case SQLITE_LOCKED_SHAREDCACHE: zVal = "SQLITE_LOCKED_SHAREDCACHE"; break;
+    case SQLITE_BUSY_RECOVERY:      zVal = "SQLITE_BUSY_RECOVERY";      break;
+    case SQLITE_CANTOPEN_NOTEMPDIR: zVal = "SQLITE_CANTOPEN_NOTEMPDIR"; break;
+    default: {
+       sqlite3_snprintf(sizeof(zBuf), zBuf, "%d", rc);
+       zVal = zBuf;
+       break;
+    }
+  }
+  vfstrace_printf(pInfo, zFormat, zVal);
+}
+
+/*
+** Append to a buffer.
+*/
+static void strappend(char *z, int *pI, const char *zAppend){
+  int i = *pI;
+  while( zAppend[0] ){ z[i++] = *(zAppend++); }
+  z[i] = 0;
+  *pI = i;
+}
+
+/*
+** Close an vfstrace-file.
+*/
+static int vfstraceClose(sqlite3_file *pFile){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xClose(%s)", pInfo->zVfsName, p->zFName);
+  rc = p->pReal->pMethods->xClose(p->pReal);
+  vfstrace_print_errcode(pInfo, " -> %s\n", rc);
+  if( rc==SQLITE_OK ){
+    sqlite3_free((void*)p->base.pMethods);
+    p->base.pMethods = 0;
+  }
+  return rc;
+}
+
+/*
+** Read data from an vfstrace-file.
+*/
+static int vfstraceRead(
+  sqlite3_file *pFile, 
+  void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xRead(%s,n=%d,ofst=%lld)",
+                  pInfo->zVfsName, p->zFName, iAmt, iOfst);
+  rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
+  vfstrace_print_errcode(pInfo, " -> %s\n", rc);
+  return rc;
+}
+
+/*
+** Write data to an vfstrace-file.
+*/
+static int vfstraceWrite(
+  sqlite3_file *pFile, 
+  const void *zBuf, 
+  int iAmt, 
+  sqlite_int64 iOfst
+){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xWrite(%s,n=%d,ofst=%lld)",
+                  pInfo->zVfsName, p->zFName, iAmt, iOfst);
+  rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
+  vfstrace_print_errcode(pInfo, " -> %s\n", rc);
+  return rc;
+}
+
+/*
+** Truncate an vfstrace-file.
+*/
+static int vfstraceTruncate(sqlite3_file *pFile, sqlite_int64 size){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xTruncate(%s,%lld)", pInfo->zVfsName, p->zFName,
+                  size);
+  rc = p->pReal->pMethods->xTruncate(p->pReal, size);
+  vfstrace_printf(pInfo, " -> %d\n", rc);
+  return rc;
+}
+
+/*
+** Sync an vfstrace-file.
+*/
+static int vfstraceSync(sqlite3_file *pFile, int flags){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  int i;
+  char zBuf[100];
+  memcpy(zBuf, "|0", 3);
+  i = 0;
+  if( flags & SQLITE_SYNC_FULL )        strappend(zBuf, &i, "|FULL");
+  else if( flags & SQLITE_SYNC_NORMAL ) strappend(zBuf, &i, "|NORMAL");
+  if( flags & SQLITE_SYNC_DATAONLY )    strappend(zBuf, &i, "|DATAONLY");
+  if( flags & ~(SQLITE_SYNC_FULL|SQLITE_SYNC_DATAONLY) ){
+    sqlite3_snprintf(sizeof(zBuf)-i, &zBuf[i], "|0x%x", flags);
+  }
+  vfstrace_printf(pInfo, "%s.xSync(%s,%s)", pInfo->zVfsName, p->zFName,
+                  &zBuf[1]);
+  rc = p->pReal->pMethods->xSync(p->pReal, flags);
+  vfstrace_printf(pInfo, " -> %d\n", rc);
+  return rc;
+}
+
+/*
+** Return the current file-size of an vfstrace-file.
+*/
+static int vfstraceFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xFileSize(%s)", pInfo->zVfsName, p->zFName);
+  rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
+  vfstrace_print_errcode(pInfo, " -> %s,", rc);
+  vfstrace_printf(pInfo, " size=%lld\n", *pSize);
+  return rc;
+}
+
+/*
+** Return the name of a lock.
+*/
+static const char *lockName(int eLock){
+  const char *azLockNames[] = {
+     "NONE", "SHARED", "RESERVED", "PENDING", "EXCLUSIVE"
+  };
+  if( eLock<0 || eLock>=sizeof(azLockNames)/sizeof(azLockNames[0]) ){
+    return "???";
+  }else{
+    return azLockNames[eLock];
+  }
+}
+
+/*
+** Lock an vfstrace-file.
+*/
+static int vfstraceLock(sqlite3_file *pFile, int eLock){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xLock(%s,%s)", pInfo->zVfsName, p->zFName,
+                  lockName(eLock));
+  rc = p->pReal->pMethods->xLock(p->pReal, eLock);
+  vfstrace_print_errcode(pInfo, " -> %s\n", rc);
+  return rc;
+}
+
+/*
+** Unlock an vfstrace-file.
+*/
+static int vfstraceUnlock(sqlite3_file *pFile, int eLock){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xUnlock(%s,%s)", pInfo->zVfsName, p->zFName,
+                  lockName(eLock));
+  rc = p->pReal->pMethods->xUnlock(p->pReal, eLock);
+  vfstrace_print_errcode(pInfo, " -> %s\n", rc);
+  return rc;
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on an vfstrace-file.
+*/
+static int vfstraceCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xCheckReservedLock(%s,%d)", 
+                  pInfo->zVfsName, p->zFName);
+  rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
+  vfstrace_print_errcode(pInfo, " -> %s", rc);
+  vfstrace_printf(pInfo, ", out=%d\n", *pResOut);
+  return rc;
+}
+
+/*
+** File control method. For custom operations on an vfstrace-file.
+*/
+static int vfstraceFileControl(sqlite3_file *pFile, int op, void *pArg){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  char zBuf[100];
+  char *zOp;
+  switch( op ){
+    case SQLITE_FCNTL_LOCKSTATE:    zOp = "LOCKSTATE";          break;
+    case SQLITE_GET_LOCKPROXYFILE:  zOp = "GET_LOCKPROXYFILE";  break;
+    case SQLITE_SET_LOCKPROXYFILE:  zOp = "SET_LOCKPROXYFILE";  break;
+    case SQLITE_LAST_ERRNO:         zOp = "LAST_ERRNO";         break;
+    case SQLITE_FCNTL_SIZE_HINT: {
+      sqlite3_snprintf(sizeof(zBuf), zBuf, "SIZE_HINT,%lld",
+                       *(sqlite3_int64*)pArg);
+      zOp = zBuf;
+      break;
+    }
+    case SQLITE_FCNTL_CHUNK_SIZE: {
+      sqlite3_snprintf(sizeof(zBuf), zBuf, "CHUNK_SIZE,%d", *(int*)pArg);
+      zOp = zBuf;
+      break;
+    }
+    case SQLITE_FCNTL_FILE_POINTER: zOp = "FILE_POINTER";       break;
+    case SQLITE_FCNTL_SYNC_OMITTED: zOp = "SYNC_OMITTED";       break;
+    case SQLITE_FCNTL_WIN32_AV_RETRY: zOp = "WIN32_AV_RETRY";   break;
+    case SQLITE_FCNTL_PERSIST_WAL:  zOp = "PERSIST_WAL";        break;
+    case SQLITE_FCNTL_OVERWRITE:    zOp = "OVERWRITE";          break;
+    case SQLITE_FCNTL_VFSNAME:      zOp = "VFSNAME";            break;
+    case SQLITE_FCNTL_TEMPFILENAME: zOp = "TEMPFILENAME";       break;
+    case 0xca093fa0:                zOp = "DB_UNCHANGED";       break;
+    case SQLITE_FCNTL_PRAGMA: {
+      const char *const* a = (const char*const*)pArg;
+      sqlite3_snprintf(sizeof(zBuf), zBuf, "PRAGMA,[%s,%s]",a[1],a[2]);
+      zOp = zBuf;
+      break;
+    }
+    default: {
+      sqlite3_snprintf(sizeof zBuf, zBuf, "%d", op);
+      zOp = zBuf;
+      break;
+    }
+  }
+  vfstrace_printf(pInfo, "%s.xFileControl(%s,%s)",
+                  pInfo->zVfsName, p->zFName, zOp);
+  rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
+  vfstrace_print_errcode(pInfo, " -> %s\n", rc);
+  if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
+    *(char**)pArg = sqlite3_mprintf("vfstrace.%s/%z",
+                                    pInfo->zVfsName, *(char**)pArg);
+  }
+  if( (op==SQLITE_FCNTL_PRAGMA || op==SQLITE_FCNTL_TEMPFILENAME)
+   && rc==SQLITE_OK && *(char**)pArg ){
+    vfstrace_printf(pInfo, "%s.xFileControl(%s,%s) returns %s",
+                    pInfo->zVfsName, p->zFName, zOp, *(char**)pArg);
+  }
+  return rc;
+}
+
+/*
+** Return the sector-size in bytes for an vfstrace-file.
+*/
+static int vfstraceSectorSize(sqlite3_file *pFile){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xSectorSize(%s)", pInfo->zVfsName, p->zFName);
+  rc = p->pReal->pMethods->xSectorSize(p->pReal);
+  vfstrace_printf(pInfo, " -> %d\n", rc);
+  return rc;
+}
+
+/*
+** Return the device characteristic flags supported by an vfstrace-file.
+*/
+static int vfstraceDeviceCharacteristics(sqlite3_file *pFile){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xDeviceCharacteristics(%s)",
+                  pInfo->zVfsName, p->zFName);
+  rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
+  vfstrace_printf(pInfo, " -> 0x%08x\n", rc);
+  return rc;
+}
+
+/*
+** Shared-memory operations.
+*/
+static int vfstraceShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  char zLck[100];
+  int i = 0;
+  memcpy(zLck, "|0", 3);
+  if( flags & SQLITE_SHM_UNLOCK )    strappend(zLck, &i, "|UNLOCK");
+  if( flags & SQLITE_SHM_LOCK )      strappend(zLck, &i, "|LOCK");
+  if( flags & SQLITE_SHM_SHARED )    strappend(zLck, &i, "|SHARED");
+  if( flags & SQLITE_SHM_EXCLUSIVE ) strappend(zLck, &i, "|EXCLUSIVE");
+  if( flags & ~(0xf) ){
+     sqlite3_snprintf(sizeof(zLck)-i, &zLck[i], "|0x%x", flags);
+  }
+  vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=%d,n=%d,%s)",
+                  pInfo->zVfsName, p->zFName, ofst, n, &zLck[1]);
+  rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags);
+  vfstrace_print_errcode(pInfo, " -> %s\n", rc);
+  return rc;
+}
+static int vfstraceShmMap(
+  sqlite3_file *pFile, 
+  int iRegion, 
+  int szRegion, 
+  int isWrite, 
+  void volatile **pp
+){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xShmMap(%s,iRegion=%d,szRegion=%d,isWrite=%d,*)",
+                  pInfo->zVfsName, p->zFName, iRegion, szRegion, isWrite);
+  rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp);
+  vfstrace_print_errcode(pInfo, " -> %s\n", rc);
+  return rc;
+}
+static void vfstraceShmBarrier(sqlite3_file *pFile){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  vfstrace_printf(pInfo, "%s.xShmBarrier(%s)\n", pInfo->zVfsName, p->zFName);
+  p->pReal->pMethods->xShmBarrier(p->pReal);
+}
+static int vfstraceShmUnmap(sqlite3_file *pFile, int delFlag){
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = p->pInfo;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xShmUnmap(%s,delFlag=%d)",
+                  pInfo->zVfsName, p->zFName, delFlag);
+  rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag);
+  vfstrace_print_errcode(pInfo, " -> %s\n", rc);
+  return rc;
+}
+
+
+
+/*
+** Open an vfstrace file handle.
+*/
+static int vfstraceOpen(
+  sqlite3_vfs *pVfs,
+  const char *zName,
+  sqlite3_file *pFile,
+  int flags,
+  int *pOutFlags
+){
+  int rc;
+  vfstrace_file *p = (vfstrace_file *)pFile;
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  p->pInfo = pInfo;
+  p->zFName = zName ? fileTail(zName) : "<temp>";
+  p->pReal = (sqlite3_file *)&p[1];
+  rc = pRoot->xOpen(pRoot, zName, p->pReal, flags, pOutFlags);
+  vfstrace_printf(pInfo, "%s.xOpen(%s,flags=0x%x)",
+                  pInfo->zVfsName, p->zFName, flags);
+  if( p->pReal->pMethods ){
+    sqlite3_io_methods *pNew = sqlite3_malloc( sizeof(*pNew) );
+    const sqlite3_io_methods *pSub = p->pReal->pMethods;
+    memset(pNew, 0, sizeof(*pNew));
+    pNew->iVersion = pSub->iVersion;
+    pNew->xClose = vfstraceClose;
+    pNew->xRead = vfstraceRead;
+    pNew->xWrite = vfstraceWrite;
+    pNew->xTruncate = vfstraceTruncate;
+    pNew->xSync = vfstraceSync;
+    pNew->xFileSize = vfstraceFileSize;
+    pNew->xLock = vfstraceLock;
+    pNew->xUnlock = vfstraceUnlock;
+    pNew->xCheckReservedLock = vfstraceCheckReservedLock;
+    pNew->xFileControl = vfstraceFileControl;
+    pNew->xSectorSize = vfstraceSectorSize;
+    pNew->xDeviceCharacteristics = vfstraceDeviceCharacteristics;
+    if( pNew->iVersion>=2 ){
+      pNew->xShmMap = pSub->xShmMap ? vfstraceShmMap : 0;
+      pNew->xShmLock = pSub->xShmLock ? vfstraceShmLock : 0;
+      pNew->xShmBarrier = pSub->xShmBarrier ? vfstraceShmBarrier : 0;
+      pNew->xShmUnmap = pSub->xShmUnmap ? vfstraceShmUnmap : 0;
+    }
+    pFile->pMethods = pNew;
+  }
+  vfstrace_print_errcode(pInfo, " -> %s", rc);
+  if( pOutFlags ){
+    vfstrace_printf(pInfo, ", outFlags=0x%x\n", *pOutFlags);
+  }else{
+    vfstrace_printf(pInfo, "\n");
+  }
+  return rc;
+}
+
+/*
+** Delete the file located at zPath. If the dirSync argument is true,
+** ensure the file-system modifications are synced to disk before
+** returning.
+*/
+static int vfstraceDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xDelete(\"%s\",%d)",
+                  pInfo->zVfsName, zPath, dirSync);
+  rc = pRoot->xDelete(pRoot, zPath, dirSync);
+  vfstrace_print_errcode(pInfo, " -> %s\n", rc);
+  return rc;
+}
+
+/*
+** Test for access permissions. Return true if the requested permission
+** is available, or false otherwise.
+*/
+static int vfstraceAccess(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int flags, 
+  int *pResOut
+){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xDelete(\"%s\",%d)",
+                  pInfo->zVfsName, zPath, flags);
+  rc = pRoot->xAccess(pRoot, zPath, flags, pResOut);
+  vfstrace_print_errcode(pInfo, " -> %s", rc);
+  vfstrace_printf(pInfo, ", out=%d\n", *pResOut);
+  return rc;
+}
+
+/*
+** Populate buffer zOut with the full canonical pathname corresponding
+** to the pathname in zPath. zOut is guaranteed to point to a buffer
+** of at least (DEVSYM_MAX_PATHNAME+1) bytes.
+*/
+static int vfstraceFullPathname(
+  sqlite3_vfs *pVfs, 
+  const char *zPath, 
+  int nOut, 
+  char *zOut
+){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  int rc;
+  vfstrace_printf(pInfo, "%s.xFullPathname(\"%s\")",
+                  pInfo->zVfsName, zPath);
+  rc = pRoot->xFullPathname(pRoot, zPath, nOut, zOut);
+  vfstrace_print_errcode(pInfo, " -> %s", rc);
+  vfstrace_printf(pInfo, ", out=\"%.*s\"\n", nOut, zOut);
+  return rc;
+}
+
+/*
+** Open the dynamic library located at zPath and return a handle.
+*/
+static void *vfstraceDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  vfstrace_printf(pInfo, "%s.xDlOpen(\"%s\")\n", pInfo->zVfsName, zPath);
+  return pRoot->xDlOpen(pRoot, zPath);
+}
+
+/*
+** Populate the buffer zErrMsg (size nByte bytes) with a human readable
+** utf-8 string describing the most recent error encountered associated 
+** with dynamic libraries.
+*/
+static void vfstraceDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  vfstrace_printf(pInfo, "%s.xDlError(%d)", pInfo->zVfsName, nByte);
+  pRoot->xDlError(pRoot, nByte, zErrMsg);
+  vfstrace_printf(pInfo, " -> \"%s\"", zErrMsg);
+}
+
+/*
+** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
+*/
+static void (*vfstraceDlSym(sqlite3_vfs *pVfs,void *p,const char *zSym))(void){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  vfstrace_printf(pInfo, "%s.xDlSym(\"%s\")\n", pInfo->zVfsName, zSym);
+  return pRoot->xDlSym(pRoot, p, zSym);
+}
+
+/*
+** Close the dynamic library handle pHandle.
+*/
+static void vfstraceDlClose(sqlite3_vfs *pVfs, void *pHandle){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  vfstrace_printf(pInfo, "%s.xDlOpen()\n", pInfo->zVfsName);
+  pRoot->xDlClose(pRoot, pHandle);
+}
+
+/*
+** Populate the buffer pointed to by zBufOut with nByte bytes of 
+** random data.
+*/
+static int vfstraceRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  vfstrace_printf(pInfo, "%s.xRandomness(%d)\n", pInfo->zVfsName, nByte);
+  return pRoot->xRandomness(pRoot, nByte, zBufOut);
+}
+
+/*
+** Sleep for nMicro microseconds. Return the number of microseconds 
+** actually slept.
+*/
+static int vfstraceSleep(sqlite3_vfs *pVfs, int nMicro){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  return pRoot->xSleep(pRoot, nMicro);
+}
+
+/*
+** Return the current time as a Julian Day number in *pTimeOut.
+*/
+static int vfstraceCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  return pRoot->xCurrentTime(pRoot, pTimeOut);
+}
+static int vfstraceCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  return pRoot->xCurrentTimeInt64(pRoot, pTimeOut);
+}
+
+/*
+** Return th3 emost recent error code and message
+*/
+static int vfstraceGetLastError(sqlite3_vfs *pVfs, int iErr, char *zErr){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  return pRoot->xGetLastError(pRoot, iErr, zErr);
+}
+
+/*
+** Override system calls.
+*/
+static int vfstraceSetSystemCall(
+  sqlite3_vfs *pVfs,
+  const char *zName,
+  sqlite3_syscall_ptr pFunc
+){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  return pRoot->xSetSystemCall(pRoot, zName, pFunc);
+}
+static sqlite3_syscall_ptr vfstraceGetSystemCall(
+  sqlite3_vfs *pVfs,
+  const char *zName
+){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  return pRoot->xGetSystemCall(pRoot, zName);
+}
+static const char *vfstraceNextSystemCall(sqlite3_vfs *pVfs, const char *zName){
+  vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
+  sqlite3_vfs *pRoot = pInfo->pRootVfs;
+  return pRoot->xNextSystemCall(pRoot, zName);
+}
+
+
+/*
+** Clients invoke this routine to construct a new trace-vfs shim.
+**
+** Return SQLITE_OK on success.  
+**
+** SQLITE_NOMEM is returned in the case of a memory allocation error.
+** SQLITE_NOTFOUND is returned if zOldVfsName does not exist.
+*/
+int vfstrace_register(
+   const char *zTraceName,           /* Name of the newly constructed VFS */
+   const char *zOldVfsName,          /* Name of the underlying VFS */
+   int (*xOut)(const char*,void*),   /* Output routine.  ex: fputs */
+   void *pOutArg,                    /* 2nd argument to xOut.  ex: stderr */
+   int makeDefault                   /* True to make the new VFS the default */
+){
+  sqlite3_vfs *pNew;
+  sqlite3_vfs *pRoot;
+  vfstrace_info *pInfo;
+  int nName;
+  int nByte;
+
+  pRoot = sqlite3_vfs_find(zOldVfsName);
+  if( pRoot==0 ) return SQLITE_NOTFOUND;
+  nName = strlen(zTraceName);
+  nByte = sizeof(*pNew) + sizeof(*pInfo) + nName + 1;
+  pNew = sqlite3_malloc( nByte );
+  if( pNew==0 ) return SQLITE_NOMEM;
+  memset(pNew, 0, nByte);
+  pInfo = (vfstrace_info*)&pNew[1];
+  pNew->iVersion = pRoot->iVersion;
+  pNew->szOsFile = pRoot->szOsFile + sizeof(vfstrace_file);
+  pNew->mxPathname = pRoot->mxPathname;
+  pNew->zName = (char*)&pInfo[1];
+  memcpy((char*)&pInfo[1], zTraceName, nName+1);
+  pNew->pAppData = pInfo;
+  pNew->xOpen = vfstraceOpen;
+  pNew->xDelete = vfstraceDelete;
+  pNew->xAccess = vfstraceAccess;
+  pNew->xFullPathname = vfstraceFullPathname;
+  pNew->xDlOpen = pRoot->xDlOpen==0 ? 0 : vfstraceDlOpen;
+  pNew->xDlError = pRoot->xDlError==0 ? 0 : vfstraceDlError;
+  pNew->xDlSym = pRoot->xDlSym==0 ? 0 : vfstraceDlSym;
+  pNew->xDlClose = pRoot->xDlClose==0 ? 0 : vfstraceDlClose;
+  pNew->xRandomness = vfstraceRandomness;
+  pNew->xSleep = vfstraceSleep;
+  pNew->xCurrentTime = vfstraceCurrentTime;
+  pNew->xGetLastError = pRoot->xGetLastError==0 ? 0 : vfstraceGetLastError;
+  if( pNew->iVersion>=2 ){
+    pNew->xCurrentTimeInt64 = pRoot->xCurrentTimeInt64==0 ? 0 :
+                                   vfstraceCurrentTimeInt64;
+    if( pNew->iVersion>=3 ){
+      pNew->xSetSystemCall = pRoot->xSetSystemCall==0 ? 0 : 
+                                   vfstraceSetSystemCall;
+      pNew->xGetSystemCall = pRoot->xGetSystemCall==0 ? 0 : 
+                                   vfstraceGetSystemCall;
+      pNew->xNextSystemCall = pRoot->xNextSystemCall==0 ? 0 : 
+                                   vfstraceNextSystemCall;
+    }
+  }
+  pInfo->pRootVfs = pRoot;
+  pInfo->xOut = xOut;
+  pInfo->pOutArg = pOutArg;
+  pInfo->zVfsName = pNew->zName;
+  pInfo->pTraceVfs = pNew;
+  vfstrace_printf(pInfo, "%s.enabled_for(\"%s\")\n",
+       pInfo->zVfsName, pRoot->zName);
+  return sqlite3_vfs_register(pNew, makeDefault);
+}

+ 84 - 0
components/external/sqlite/test/test_wsd.c

@@ -0,0 +1,84 @@
+/*
+** 2008 September 1
+**
+** 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.
+**
+*************************************************************************
+**
+** The code in this file contains sample implementations of the 
+** sqlite3_wsd_init() and sqlite3_wsd_find() functions required if the
+** SQLITE_OMIT_WSD symbol is defined at build time.
+*/
+
+#if defined(SQLITE_OMIT_WSD) && defined(SQLITE_TEST)
+
+#include "sqliteInt.h"
+
+#define PLS_HASHSIZE 43
+
+typedef struct ProcessLocalStorage ProcessLocalStorage;
+typedef struct ProcessLocalVar ProcessLocalVar;
+
+struct ProcessLocalStorage {
+  ProcessLocalVar *aData[PLS_HASHSIZE];
+  int nFree;
+  u8 *pFree;
+};
+
+struct ProcessLocalVar {
+  void *pKey;
+  ProcessLocalVar *pNext;
+};
+
+static ProcessLocalStorage *pGlobal = 0;
+
+int sqlite3_wsd_init(int N, int J){
+  if( !pGlobal ){
+    int nMalloc = N + sizeof(ProcessLocalStorage) + J*sizeof(ProcessLocalVar);
+    pGlobal = (ProcessLocalStorage *)malloc(nMalloc);
+    if( pGlobal ){
+      memset(pGlobal, 0, sizeof(ProcessLocalStorage));
+      pGlobal->nFree = nMalloc - sizeof(ProcessLocalStorage);
+      pGlobal->pFree = (u8 *)&pGlobal[1];
+    }
+  }
+
+  return pGlobal ? SQLITE_OK : SQLITE_NOMEM;
+}
+
+void *sqlite3_wsd_find(void *K, int L){
+  int i;
+  int iHash = 0;
+  ProcessLocalVar *pVar;
+
+  /* Calculate a hash of K */
+  for(i=0; i<sizeof(void*); i++){
+    iHash = (iHash<<3) + ((unsigned char *)&K)[i];
+  }
+  iHash = iHash%PLS_HASHSIZE;
+
+  /* Search the hash table for K. */
+  for(pVar=pGlobal->aData[iHash]; pVar && pVar->pKey!=K; pVar=pVar->pNext);
+
+  /* If no entry for K was found, create and populate a new one. */
+  if( !pVar ){
+    int nByte = ROUND8(sizeof(ProcessLocalVar) + L);
+    assert( pGlobal->nFree>=nByte );
+    pVar = (ProcessLocalVar *)pGlobal->pFree;
+    pVar->pKey = K;
+    pVar->pNext = pGlobal->aData[iHash];
+    pGlobal->aData[iHash] = pVar;
+    pGlobal->nFree -= nByte;
+    pGlobal->pFree += nByte;
+    memcpy(&pVar[1], K, L);
+  }
+
+  return (void *)&pVar[1];
+}
+
+#endif

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません