From d47febdc65e1cdbfc7a9ac1639495b9ef2295128 Mon Sep 17 00:00:00 2001 From: geniusgogo Date: Sun, 17 Nov 2013 00:17:50 +0800 Subject: [PATCH] port complete SQLite-3.8.1 to RT-Thread --- components/external/sqlite/README | 26 + components/external/sqlite/SConscript | 4 +- components/external/sqlite/config.h | 114 + components/external/sqlite/sqlite3.c | 3610 ++++++++- components/external/sqlite/test/test1.c | 6549 +++++++++++++++++ components/external/sqlite/test/test2.c | 664 ++ components/external/sqlite/test/test3.c | 628 ++ components/external/sqlite/test/test4.c | 722 ++ components/external/sqlite/test/test5.c | 217 + components/external/sqlite/test/test6.c | 1020 +++ components/external/sqlite/test/test7.c | 714 ++ components/external/sqlite/test/test8.c | 1396 ++++ components/external/sqlite/test/test9.c | 200 + components/external/sqlite/test/test_async.c | 241 + .../external/sqlite/test/test_autoext.c | 221 + components/external/sqlite/test/test_backup.c | 150 + components/external/sqlite/test/test_btree.c | 62 + components/external/sqlite/test/test_config.c | 656 ++ .../external/sqlite/test/test_demovfs.c | 679 ++ components/external/sqlite/test/test_devsym.c | 398 + components/external/sqlite/test/test_fs.c | 335 + components/external/sqlite/test/test_func.c | 767 ++ components/external/sqlite/test/test_hexio.c | 388 + components/external/sqlite/test/test_init.c | 291 + .../external/sqlite/test/test_intarray.c | 382 + .../external/sqlite/test/test_intarray.h | 128 + .../external/sqlite/test/test_journal.c | 857 +++ .../external/sqlite/test/test_loadext.c | 122 + components/external/sqlite/test/test_malloc.c | 1494 ++++ .../external/sqlite/test/test_multiplex.c | 1385 ++++ .../external/sqlite/test/test_multiplex.h | 99 + components/external/sqlite/test/test_mutex.c | 439 ++ .../external/sqlite/test/test_onefile.c | 830 +++ components/external/sqlite/test/test_osinst.c | 1215 +++ components/external/sqlite/test/test_pcache.c | 467 ++ components/external/sqlite/test/test_quota.c | 2008 +++++ components/external/sqlite/test/test_quota.h | 274 + components/external/sqlite/test/test_rtree.c | 305 + components/external/sqlite/test/test_schema.c | 362 + components/external/sqlite/test/test_server.c | 516 ++ components/external/sqlite/test/test_sqllog.c | 507 ++ components/external/sqlite/test/test_stat.c | 639 ++ .../external/sqlite/test/test_superlock.c | 356 + .../external/sqlite/test/test_syscall.c | 705 ++ components/external/sqlite/test/test_tclvar.c | 332 + components/external/sqlite/test/test_thread.c | 647 ++ components/external/sqlite/test/test_vfs.c | 1510 ++++ .../external/sqlite/test/test_vfstrace.c | 887 +++ components/external/sqlite/test/test_wsd.c | 84 + 49 files changed, 36412 insertions(+), 190 deletions(-) create mode 100644 components/external/sqlite/config.h create mode 100644 components/external/sqlite/test/test1.c create mode 100644 components/external/sqlite/test/test2.c create mode 100644 components/external/sqlite/test/test3.c create mode 100644 components/external/sqlite/test/test4.c create mode 100644 components/external/sqlite/test/test5.c create mode 100644 components/external/sqlite/test/test6.c create mode 100644 components/external/sqlite/test/test7.c create mode 100644 components/external/sqlite/test/test8.c create mode 100644 components/external/sqlite/test/test9.c create mode 100644 components/external/sqlite/test/test_async.c create mode 100644 components/external/sqlite/test/test_autoext.c create mode 100644 components/external/sqlite/test/test_backup.c create mode 100644 components/external/sqlite/test/test_btree.c create mode 100644 components/external/sqlite/test/test_config.c create mode 100644 components/external/sqlite/test/test_demovfs.c create mode 100644 components/external/sqlite/test/test_devsym.c create mode 100644 components/external/sqlite/test/test_fs.c create mode 100644 components/external/sqlite/test/test_func.c create mode 100644 components/external/sqlite/test/test_hexio.c create mode 100644 components/external/sqlite/test/test_init.c create mode 100644 components/external/sqlite/test/test_intarray.c create mode 100644 components/external/sqlite/test/test_intarray.h create mode 100644 components/external/sqlite/test/test_journal.c create mode 100644 components/external/sqlite/test/test_loadext.c create mode 100644 components/external/sqlite/test/test_malloc.c create mode 100644 components/external/sqlite/test/test_multiplex.c create mode 100644 components/external/sqlite/test/test_multiplex.h create mode 100644 components/external/sqlite/test/test_mutex.c create mode 100644 components/external/sqlite/test/test_onefile.c create mode 100644 components/external/sqlite/test/test_osinst.c create mode 100644 components/external/sqlite/test/test_pcache.c create mode 100644 components/external/sqlite/test/test_quota.c create mode 100644 components/external/sqlite/test/test_quota.h create mode 100644 components/external/sqlite/test/test_rtree.c create mode 100644 components/external/sqlite/test/test_schema.c create mode 100644 components/external/sqlite/test/test_server.c create mode 100644 components/external/sqlite/test/test_sqllog.c create mode 100644 components/external/sqlite/test/test_stat.c create mode 100644 components/external/sqlite/test/test_superlock.c create mode 100644 components/external/sqlite/test/test_syscall.c create mode 100644 components/external/sqlite/test/test_tclvar.c create mode 100644 components/external/sqlite/test/test_thread.c create mode 100644 components/external/sqlite/test/test_vfs.c create mode 100644 components/external/sqlite/test/test_vfstrace.c create mode 100644 components/external/sqlite/test/test_wsd.c diff --git a/components/external/sqlite/README b/components/external/sqlite/README index 1820573d37..39bcad5157 100644 --- a/components/external/sqlite/README +++ b/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还没有移植的。请不要使用。 diff --git a/components/external/sqlite/SConscript b/components/external/sqlite/SConscript index f335e14537..bb6f1e90a2 100644 --- a/components/external/sqlite/SConscript +++ b/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) diff --git a/components/external/sqlite/config.h b/components/external/sqlite/config.h new file mode 100644 index 0000000000..8b711f73a5 --- /dev/null +++ b/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 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 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 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 header file. */ +/* #undef HAVE_MEMORY_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STRINGS_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the 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 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 */ diff --git a/components/external/sqlite/sqlite3.c b/components/external/sqlite/sqlite3.c index 2d27e750d0..912a68378a 100644 --- a/components/external/sqlite/sqlite3.c +++ b/components/external/sqlite/sqlite3.c @@ -68,7 +68,7 @@ #if 0 extern "C" { #endif - +#include /* ** Add the ability to override 'extern' @@ -7628,7 +7628,7 @@ struct sqlite3_rtree_geometry { ** to the next, so we have developed the following set of #if statements ** to generate appropriate macros for a wide range of compilers. ** -** The correct "ANSI" way to do this is to use the intptr_t type. +** The correct "ANSI" way to do this is to use the intptr_t type. ** Unfortunately, that typedef is not available on all compilers, or ** if it is available, it requires an #include of specific headers ** that vary from one machine to the next. @@ -7681,7 +7681,7 @@ struct sqlite3_rtree_geometry { /* ** The SQLITE_DEFAULT_MEMSTATUS macro must be defined as either 0 or 1. -** It determines whether or not the features related to +** It determines whether or not the features related to ** SQLITE_CONFIG_MEMSTATUS are available by default or not. This value can ** be overridden at runtime using the sqlite3_config() API. */ @@ -7751,7 +7751,7 @@ struct sqlite3_rtree_geometry { ** is set. Thus NDEBUG becomes an opt-in rather than an opt-out ** feature. */ -#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) # define NDEBUG 1 #endif #if defined(NDEBUG) && defined(SQLITE_DEBUG) @@ -7759,7 +7759,7 @@ struct sqlite3_rtree_geometry { #endif /* -** The testcase() macro is used to aid in coverage testing. When +** The testcase() macro is used to aid in coverage testing. When ** doing coverage testing, the condition inside the argument to ** testcase() must be evaluated both true and false in order to ** get full branch coverage. The testcase() macro is inserted @@ -7805,7 +7805,7 @@ SQLITE_PRIVATE void sqlite3Coverage(int); #endif /* -** The ALWAYS and NEVER macros surround boolean expressions which +** The ALWAYS and NEVER macros surround boolean expressions which ** are intended to always be true or false, respectively. Such ** expressions could be omitted from the code completely. But they ** are included in a few cases in order to enhance the resilience @@ -8138,7 +8138,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); /* ** OMIT_TEMPDB is set to 1 if SQLITE_OMIT_TEMPDB is defined, or 0 -** afterward. Having this macro allows us to cause the C compiler +** afterward. Having this macro allows us to cause the C compiler ** to omit code used by TEMP tables without messy #ifndef statements. */ #ifdef SQLITE_OMIT_TEMPDB @@ -8290,7 +8290,7 @@ typedef INT8_TYPE i8; /* 1-byte signed integer */ ** 4 -> 20 1000 -> 99 1048576 -> 200 ** 10 -> 33 1024 -> 100 4294967296 -> 320 ** -** The LogEst can be negative to indicate fractional values. +** The LogEst can be negative to indicate fractional values. ** Examples: ** ** 0.5 -> -10 0.1 -> -33 0.0625 -> -40 @@ -8325,7 +8325,7 @@ SQLITE_PRIVATE const int sqlite3one; #define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) #define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) -/* +/* ** Round up a number to the next larger multiple of 8. This is used ** to force 8-byte alignment on 64-bit architectures. */ @@ -8370,7 +8370,9 @@ SQLITE_PRIVATE const int sqlite3one; # endif #endif #ifndef SQLITE_MAX_MMAP_SIZE -# if defined(__linux__) \ +# if defined(SQLITE_OS_RTT) +# define SQLITE_MAX_MMAP_SIZE 0 +# elif defined(__linux__) \ || defined(_WIN32) \ || (defined(__APPLE__) && defined(__MACH__)) \ || defined(__sun) @@ -8411,7 +8413,7 @@ SQLITE_PRIVATE const int sqlite3one; /* ** An instance of the following structure is used to store the busy-handler -** callback for a given sqlite handle. +** callback for a given sqlite handle. ** ** The sqlite.busyHandler member of the sqlite struct contains the busy ** callback for the database handle. Each pager opened via the sqlite @@ -8456,9 +8458,9 @@ struct BusyHandler { /* ** The following value as a destructor means to use sqlite3DbFree(). -** The sqlite3DbFree() routine requires two parameters instead of the -** one parameter that destructors normally want. So we have to introduce -** this magic value that the code knows to handle differently. Any +** The sqlite3DbFree() routine requires two parameters instead of the +** one parameter that destructors normally want. So we have to introduce +** this magic value that the code knows to handle differently. Any ** pointer will work here as long as it is distinct from SQLITE_STATIC ** and SQLITE_TRANSIENT. */ @@ -8485,16 +8487,16 @@ struct BusyHandler { SQLITE_API int sqlite3_wsd_init(int N, int J); SQLITE_API void *sqlite3_wsd_find(void *K, int L); #else - #define SQLITE_WSD + #define SQLITE_WSD #define GLOBAL(t,v) v #define sqlite3GlobalConfig sqlite3Config #endif /* ** The following macros are used to suppress compiler warnings and to -** make it clear to human readers when a function parameter is deliberately +** make it clear to human readers when a function parameter is deliberately ** left unused within the body of a function. This usually happens when -** a function is called via a function pointer. For example the +** a function is called via a function pointer. For example the ** implementation of an SQL aggregate step callback may not use the ** parameter indicating the number of arguments passed to the aggregate, ** if it knows that this is enforced elsewhere. @@ -8556,7 +8558,7 @@ typedef struct Walker Walker; typedef struct WhereInfo WhereInfo; /* -** Defer sourcing vdbe.h and btree.h until after the "u8" and +** Defer sourcing vdbe.h and btree.h until after the "u8" and ** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque ** pointer types (i.e. FuncDef) defined above. */ @@ -9652,8 +9654,8 @@ SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); /* ** 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 +** 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) @@ -9662,6 +9664,8 @@ SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); # define SQLITE_OS_UNIX 0 # undef SQLITE_OS_WIN # define SQLITE_OS_WIN 0 +# undef SQLITE_OS_RTT +# define SQLITE_OS_RTT 1 # else # undef SQLITE_OS_OTHER # endif @@ -9754,10 +9758,10 @@ SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); ** 2006-10-31: The default prefix used to be "sqlite_". But then ** Mcafee started using SQLite in their anti-virus product and it ** started putting files with the "sqlite" name in the c:/temp folder. -** This annoyed many windows users. Those users would then do a +** This annoyed many windows users. Those users would then do a ** Google search for "sqlite", find the telephone numbers of the ** developers and call to wake them up at night and complain. -** For this reason, the default name prefix is changed to be "sqlite" +** For this reason, the default name prefix is changed to be "sqlite" ** spelled backwards. So the temp files are still identified, but ** anybody smart enough to figure out the code is also likely smart ** enough to know that calling the developer will not help get rid @@ -9798,9 +9802,9 @@ SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); ** UnlockFile(). ** ** LockFile() prevents not just writing but also reading by other processes. -** A SHARED_LOCK is obtained by locking a single randomly-chosen -** byte out of a specific range of bytes. The lock byte is obtained at -** random so two separate readers can probably access the file at the +** A SHARED_LOCK is obtained by locking a single randomly-chosen +** byte out of a specific range of bytes. The lock byte is obtained at +** random so two separate readers can probably access the file at the ** same time, unless they are unlucky and choose the same lock byte. ** An EXCLUSIVE_LOCK is obtained by locking all bytes in the range. ** There can only be one writer. A RESERVED_LOCK is obtained by locking @@ -9819,7 +9823,7 @@ SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); ** The following #defines specify the range of bytes used for locking. ** SHARED_SIZE is the number of bytes available in the pool from which ** a random byte is selected for a shared lock. The pool of bytes for -** shared locks begins at SHARED_FIRST. +** shared locks begins at SHARED_FIRST. ** ** The same locking strategy and ** byte ranges are used for Unix. This leaves open the possiblity of having @@ -9835,7 +9839,7 @@ SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); ** that all locks will fit on a single page even at the minimum page size. ** PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE ** is set high so that we don't have to allocate an unused page except -** for very large databases. But one should test the page skipping logic +** for very large databases. But one should test the page skipping logic ** by setting PENDING_BYTE low and running the entire regression suite. ** ** Changing the value of PENDING_BYTE results in a subtly incompatible @@ -9859,8 +9863,8 @@ SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); */ SQLITE_PRIVATE int sqlite3OsInit(void); -/* -** Functions for accessing sqlite3_file methods +/* +** Functions for accessing sqlite3_file methods */ SQLITE_PRIVATE int sqlite3OsClose(sqlite3_file*); SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file*, void*, int amt, i64 offset); @@ -9884,8 +9888,8 @@ SQLITE_PRIVATE int sqlite3OsFetch(sqlite3_file *id, i64, int, void **); SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *, i64, void *); -/* -** Functions for accessing sqlite3_vfs methods +/* +** Functions for accessing sqlite3_vfs methods */ SQLITE_PRIVATE int sqlite3OsOpen(sqlite3_vfs *, const char *, sqlite3_file*, int, int *); SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *, const char *, int); @@ -9902,7 +9906,7 @@ SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *, int); SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *, sqlite3_int64*); /* -** Convenience functions for opening and closing files using +** Convenience functions for opening and closing files using ** sqlite3_malloc() to obtain space for the file-handle structure. */ SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*); @@ -9949,6 +9953,8 @@ SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *); ** implementation can be overridden at ** start-time. ** +** SQLITE_MUTEX_RTT For multi_threaded applications on rt-thread. +** ** SQLITE_MUTEX_PTHREADS For multi-threaded applications on Unix. ** ** SQLITE_MUTEX_W32 For multi-threaded applications on Win32. @@ -9961,6 +9967,8 @@ SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *); # define SQLITE_MUTEX_PTHREADS # elif SQLITE_OS_WIN # define SQLITE_MUTEX_W32 +# elif SQLITE_OS_RTT +# define SQLITE_MUTEX_RTT # else # define SQLITE_MUTEX_NOOP # endif @@ -9972,9 +9980,9 @@ SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *); */ #define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) #define sqlite3_mutex_free(X) -#define sqlite3_mutex_enter(X) +#define sqlite3_mutex_enter(X) #define sqlite3_mutex_try(X) SQLITE_OK -#define sqlite3_mutex_leave(X) +#define sqlite3_mutex_leave(X) #define sqlite3_mutex_held(X) ((void)(X),1) #define sqlite3_mutex_notheld(X) ((void)(X),1) #define sqlite3MutexAlloc(X) ((sqlite3_mutex*)8) @@ -10010,7 +10018,7 @@ struct Db { ** the Schema for the TEMP databaes (sqlite3.aDb[1]) which is free-standing. ** In shared cache mode, a single Schema object can be shared by multiple ** Btrees that refer to the same underlying BtShared object. -** +** ** Schema objects are automatically deallocated when the last Btree that ** references them is destroyed. The TEMP Schema is manually freed by ** sqlite3_close(). @@ -10035,7 +10043,7 @@ struct Schema { }; /* -** These macros can be used to test, set, or clear bits in the +** These macros can be used to test, set, or clear bits in the ** Db.pSchema->flags field. */ #define DbHasProperty(D,I,P) (((D)->aDb[I].pSchema->flags&(P))==(P)) @@ -10154,9 +10162,9 @@ struct sqlite3 { void *pTraceArg; /* Argument to the trace function */ void (*xProfile)(void*,const char*,u64); /* Profiling function */ void *pProfileArg; /* Argument to profile function */ - void *pCommitArg; /* Argument to xCommitCallback() */ + void *pCommitArg; /* Argument to xCommitCallback() */ int (*xCommitCallback)(void*); /* Invoked at every commit. */ - void *pRollbackArg; /* Argument to xRollbackCallback() */ + void *pRollbackArg; /* Argument to xRollbackCallback() */ void (*xRollbackCallback)(void*); /* Invoked at every commit. */ void *pUpdateArg; void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64); @@ -10203,8 +10211,8 @@ struct sqlite3 { int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY - /* The following variables are all protected by the STATIC_MASTER - ** mutex, not by sqlite3.mutex. They are used by code in notify.c. + /* The following variables are all protected by the STATIC_MASTER + ** mutex, not by sqlite3.mutex. They are used by code in notify.c. ** ** When X.pUnlockConnection==Y, that means that X is waiting for Y to ** unlock so that it can proceed. @@ -10326,7 +10334,7 @@ struct FuncDef { ** This structure encapsulates a user-function destructor callback (as ** configured using create_function_v2()) and a reference counter. When ** create_function_v2() is called to create a function with a destructor, -** a single object of this type is allocated. FuncDestructor.nRef is set to +** a single object of this type is allocated. FuncDestructor.nRef is set to ** the number of FuncDef objects created (either 1 or 3, depending on whether ** or not the specified encoding is SQLITE_ANY). The FuncDef.pDestructor ** member of each of the new FuncDef objects is set to point to the allocated @@ -10363,10 +10371,10 @@ struct FuncDestructor { ** used to create the initializers for the FuncDef structures. ** ** FUNCTION(zName, nArg, iArg, bNC, xFunc) -** Used to create a scalar function definition of a function zName +** Used to create a scalar function definition of a function zName ** implemented by C function xFunc that accepts nArg arguments. The ** value passed as iArg is cast to a (void*) and made available -** as the user-data (sqlite3_user_data()) for the function. If +** as the user-data (sqlite3_user_data()) for the function. If ** argument bNC is true, then the SQLITE_FUNC_NEEDCOLL flag is set. ** ** AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal) @@ -10376,8 +10384,8 @@ struct FuncDestructor { ** FUNCTION(). ** ** LIKEFUNC(zName, nArg, pArg, flags) -** Used to create a scalar function definition of a function zName -** that accepts nArg arguments and is implemented by a call to C +** Used to create a scalar function definition of a function zName +** that accepts nArg arguments and is implemented by a call to C ** function likeFunc. Argument pArg is cast to a (void *) and made ** available as the function user-data (sqlite3_user_data()). The ** FuncDef.flags variable is set to the value passed as the flags @@ -10481,7 +10489,7 @@ struct CollSeq { ** ** These used to have mnemonic name like 'i' for SQLITE_AFF_INTEGER and ** 't' for SQLITE_AFF_TEXT. But we can save a little space and improve -** the speed a little by numbering the values consecutively. +** the speed a little by numbering the values consecutively. ** ** But rather than start with 0 or 1, we begin with 'a'. That way, ** when multiple affinity types are concatenated into a string and @@ -10500,7 +10508,7 @@ struct CollSeq { /* ** The SQLITE_AFF_MASK values masks off the significant bits of an -** affinity value. +** affinity value. */ #define SQLITE_AFF_MASK 0x67 @@ -10514,20 +10522,20 @@ struct CollSeq { /* ** An object of this type is created for each virtual table present in -** the database schema. +** the database schema. ** ** If the database schema is shared, then there is one instance of this ** structure for each database connection (sqlite3*) that uses the shared ** schema. This is because each database connection requires its own unique -** instance of the sqlite3_vtab* handle used to access the virtual table -** implementation. sqlite3_vtab* handles can not be shared between -** database connections, even when the rest of the in-memory database +** instance of the sqlite3_vtab* handle used to access the virtual table +** implementation. sqlite3_vtab* handles can not be shared between +** database connections, even when the rest of the in-memory database ** schema is shared, as the implementation often stores the database ** connection handle passed to it via the xConnect() or xCreate() method ** during initialization internally. This database connection handle may -** then be used by the virtual table implementation to access real tables -** within the database. So that they appear as part of the callers -** transaction, these accesses need to be made via the same database +** then be used by the virtual table implementation to access real tables +** within the database. So that they appear as part of the callers +** transaction, these accesses need to be made via the same database ** connection as that used to execute SQL operations on the virtual table. ** ** All VTable objects that correspond to a single table in a shared @@ -10539,19 +10547,19 @@ struct CollSeq { ** sqlite3_vtab* handle in the compiled query. ** ** When an in-memory Table object is deleted (for example when the -** schema is being reloaded for some reason), the VTable objects are not -** deleted and the sqlite3_vtab* handles are not xDisconnect()ed +** schema is being reloaded for some reason), the VTable objects are not +** deleted and the sqlite3_vtab* handles are not xDisconnect()ed ** immediately. Instead, they are moved from the Table.pVTable list to ** another linked list headed by the sqlite3.pDisconnect member of the -** corresponding sqlite3 structure. They are then deleted/xDisconnected +** corresponding sqlite3 structure. They are then deleted/xDisconnected ** next time a statement is prepared using said sqlite3*. This is done ** to avoid deadlock issues involving multiple sqlite3.mutex mutexes. ** Refer to comments above function sqlite3VtabUnlockList() for an ** explanation as to why it is safe to add an entry to an sqlite3.pDisconnect ** list without holding the corresponding sqlite3.mutex mutex. ** -** The memory for objects of this type is always allocated by -** sqlite3DbMalloc(), using the connection handle stored in VTable.db as +** The memory for objects of this type is always allocated by +** sqlite3DbMalloc(), using the connection handle stored in VTable.db as ** the first argument. */ struct VTable { @@ -10588,10 +10596,10 @@ struct VTable { ** in sqlite.aDb[]. 0 is for the main database and 1 is for the file that ** holds temporary tables and indices. If TF_Ephemeral is set ** then the table is stored in a file that is automatically deleted -** when the VDBE cursor to the table is closed. In this case Table.tnum +** when the VDBE cursor to the table is closed. In this case Table.tnum ** refers VDBE cursor number that holds the table open, not to the root ** page number. Transient tables are used to hold the results of a -** sub-query that appears instead of a real table name in the FROM clause +** sub-query that appears instead of a real table name in the FROM clause ** of a SELECT statement. */ struct Table { @@ -10705,7 +10713,7 @@ struct FKey { ** key is set to NULL. CASCADE means that a DELETE or UPDATE of the ** referenced table row is propagated into the row that holds the ** foreign key. -** +** ** The following symbolic values are used to record which type ** of action to take. */ @@ -10726,7 +10734,7 @@ struct FKey { /* ** An instance of the following structure is passed as the first -** argument to sqlite3VdbeKeyCompare and is used to control the +** argument to sqlite3VdbeKeyCompare and is used to control the ** comparison of the two index keys. ** ** Note that aSortOrder[] and aColl[] have nField+1 slots. There @@ -10784,7 +10792,7 @@ struct UnpackedRecord { ** In the Table structure describing Ex1, nCol==3 because there are ** three columns in the table. In the Index structure describing ** Ex2, nColumn==2 since 2 of the 3 columns of Ex1 are indexed. -** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the +** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the ** first column to be indexed (c3) has an index of 2 in Ex1.aCol[]. ** The second column to be indexed (c1) has an index of 0 in ** Ex1.aCol[], hence Ex2.aiColumn[1]==0. @@ -10792,7 +10800,7 @@ struct UnpackedRecord { ** The Index.onError field determines whether or not the indexed columns ** must be unique and what to do if they are not. When Index.onError=OE_None, ** it means this is not a unique index. Otherwise it is a unique index -** and the value of Index.onError indicate the which conflict resolution +** and the value of Index.onError indicate the which conflict resolution ** algorithm to employ whenever an attempt is made to insert a non-unique ** element. */ @@ -10823,7 +10831,7 @@ struct Index { }; /* -** Each sample stored in the sqlite_stat3 table is represented in memory +** Each sample stored in the sqlite_stat3 table is represented in memory ** using a structure of this type. See documentation at the top of the ** analyze.c source file for additional information. */ @@ -10917,9 +10925,9 @@ typedef int ynVar; ** to represent the greater-than-or-equal-to operator in the expression ** tree. ** -** If the expression is an SQL literal (TK_INTEGER, TK_FLOAT, TK_BLOB, +** If the expression is an SQL literal (TK_INTEGER, TK_FLOAT, TK_BLOB, ** or TK_STRING), then Expr.token contains the text of the SQL literal. If -** the expression is a variable (TK_VARIABLE), then Expr.token contains the +** the expression is a variable (TK_VARIABLE), then Expr.token contains the ** variable name. Finally, if the expression is an SQL function (TK_FUNCTION), ** then Expr.token contains the name of the function. ** @@ -10930,7 +10938,7 @@ typedef int ynVar; ** a CASE expression or an IN expression of the form " IN (, ...)". ** Expr.x.pSelect is used if the expression is a sub-select or an expression of ** the form " IN (SELECT ...)". If the EP_xIsSelect bit is set in the -** Expr.flags mask, then Expr.x.pSelect is valid. Otherwise, Expr.x.pList is +** Expr.flags mask, then Expr.x.pSelect is valid. Otherwise, Expr.x.pList is ** valid. ** ** An expression of the form ID or ID.ID refers to a column in a table. @@ -10941,8 +10949,8 @@ typedef int ynVar; ** value is also stored in the Expr.iAgg column in the aggregate so that ** it can be accessed after all aggregates are computed. ** -** If the expression is an unbound variable marker (a question mark -** character '?' in the original SQL) then the Expr.iTable holds the index +** If the expression is an unbound variable marker (a question mark +** character '?' in the original SQL) then the Expr.iTable holds the index ** number for that variable. ** ** If the expression is a subquery then Expr.iColumn holds an integer @@ -10981,7 +10989,7 @@ struct Expr { /* If the EP_TokenOnly flag is set in the Expr.flags mask, then no ** space is allocated for the fields below this point. An attempt to - ** access them will result in a segfault or malfunction. + ** access them will result in a segfault or malfunction. *********************************************************************/ Expr *pLeft; /* Left subnode */ @@ -11038,7 +11046,7 @@ struct Expr { #define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */ /* -** These macros can be used to test, set, or clear bits in the +** These macros can be used to test, set, or clear bits in the ** Expr.flags field. */ #define ExprHasProperty(E,P) (((E)->flags&(P))!=0) @@ -11057,8 +11065,8 @@ struct Expr { #endif /* -** Macros to determine the number of bytes required by a normal Expr -** struct, an Expr struct with the EP_Reduced flag set in Expr.flags +** Macros to determine the number of bytes required by a normal Expr +** struct, an Expr struct with the EP_Reduced flag set in Expr.flags ** and an Expr struct with the EP_TokenOnly flag set. */ #define EXPR_FULLSIZE sizeof(Expr) /* Full size */ @@ -11066,7 +11074,7 @@ struct Expr { #define EXPR_TOKENONLYSIZE offsetof(Expr,pLeft) /* Fewer features */ /* -** Flags passed to the sqlite3ExprDup() function. See the header comment +** Flags passed to the sqlite3ExprDup() function. See the header comment ** above sqlite3ExprDup() for details. */ #define EXPRDUP_REDUCE 0x0001 /* Used reduced-size Expr nodes */ @@ -11247,12 +11255,12 @@ struct SrcList { ** pEList corresponds to the result set of a SELECT and is NULL for ** other statements. ** -** NameContexts can be nested. When resolving names, the inner-most +** NameContexts can be nested. When resolving names, the inner-most ** context is searched first. If no match is found, the next outer ** context is checked. If there is still no match, the next context ** is checked. This process continues until either a match is found ** or all contexts are check. When a match is found, the nRef member of -** the context containing the match is incremented. +** the context containing the match is incremented. ** ** Each subquery gets a new NameContext. The pNext field points to the ** NameContext in the parent query. Thus the process of scanning the @@ -11367,7 +11375,7 @@ struct SelectDest { }; /* -** During code generation of statements that do inserts into AUTOINCREMENT +** During code generation of statements that do inserts into AUTOINCREMENT ** tables, the following information is attached to the Table.u.autoInc.p ** pointer of each autoincrement table to record some side information that ** the code generator needs. We have to keep per-table autoincrement @@ -11390,7 +11398,7 @@ struct AutoincInfo { #endif /* -** At least one instance of the following structure is created for each +** At least one instance of the following structure is created for each ** trigger that may be fired while parsing an INSERT, UPDATE or DELETE ** statement. All such objects are stored in the linked list headed at ** Parse.pTriggerPrg and deleted once statement compilation has been @@ -11403,7 +11411,7 @@ struct AutoincInfo { ** values for both pTrigger and orconf. ** ** The TriggerPrg.aColmask[0] variable is set to a mask of old.* columns -** accessed (or set to 0 for triggers fired as a result of INSERT +** accessed (or set to 0 for triggers fired as a result of INSERT ** statements). Similarly, the TriggerPrg.aColmask[1] variable is set to ** a mask of new.* columns used by the program. */ @@ -11434,7 +11442,7 @@ struct TriggerPrg { ** is constant but the second part is reset at the beginning and end of ** each recursion. ** -** The nTableLock and aTableLock variables are only used if the shared-cache +** The nTableLock and aTableLock variables are only used if the shared-cache ** feature is enabled (if sqlite3Tsd()->useSharedData is true). They are ** used to store the set of table-locks required by the statement being ** compiled. Function sqlite3TableLock() is used to add entries to the @@ -11567,10 +11575,10 @@ struct AuthContext { /* * Each trigger present in the database schema is stored as an instance of - * struct Trigger. + * struct Trigger. * * Pointers to instances of struct Trigger are stored in two ways. - * 1. In the "trigHash" hash table (part of the sqlite3* that represents the + * 1. In the "trigHash" hash table (part of the sqlite3* that represents the * database). This allows Trigger structures to be retrieved by name. * 2. All triggers associated with a single table form a linked list, using the * pNext member of struct Trigger. A pointer to the first element of the @@ -11596,7 +11604,7 @@ struct Trigger { /* ** A trigger is either a BEFORE or an AFTER trigger. The following constants -** determine which. +** determine which. ** ** If there are multiple triggers, you might of some BEFORE and some AFTER. ** In that cases, the constants below can be ORed together. @@ -11606,15 +11614,15 @@ struct Trigger { /* * An instance of struct TriggerStep is used to store a single SQL statement - * that is a part of a trigger-program. + * that is a part of a trigger-program. * * Instances of struct TriggerStep are stored in a singly linked list (linked - * using the "pNext" member) referenced by the "step_list" member of the + * using the "pNext" member) referenced by the "step_list" member of the * associated struct Trigger instance. The first element of the linked list is * the first step of the trigger-program. - * + * * The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or - * "SELECT" statement. The meanings of the other members is determined by the + * "SELECT" statement. The meanings of the other members is determined by the * value of "op" as follows: * * (op == TK_INSERT) @@ -11624,7 +11632,7 @@ struct Trigger { * target -> A token holding the quoted name of the table to insert into. * pExprList -> If this is an INSERT INTO ... VALUES ... statement, then * this stores values to be inserted. Otherwise NULL. - * pIdList -> If this is an INSERT INTO ... () VALUES ... + * pIdList -> If this is an INSERT INTO ... () VALUES ... * statement, then this stores the column-names to be * inserted into. * @@ -11632,7 +11640,7 @@ struct Trigger { * target -> A token holding the quoted name of the table to delete from. * pWhere -> The WHERE clause of the DELETE statement if one is specified. * Otherwise NULL. - * + * * (op == TK_UPDATE) * target -> A token holding the quoted name of the table to update rows of. * pWhere -> The WHERE clause of the UPDATE statement if one is specified. @@ -11640,7 +11648,7 @@ struct Trigger { * pExprList -> A list of the columns to update and the expressions to update * them to. See sqlite3Update() documentation of "pChanges" * argument. - * + * */ struct TriggerStep { u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */ @@ -11658,7 +11666,7 @@ struct TriggerStep { /* ** The following structure contains information used by the sqliteFix... ** routines as they walk the parse tree to make database references -** explicit. +** explicit. */ typedef struct DbFixer DbFixer; struct DbFixer { @@ -11884,7 +11892,7 @@ SQLITE_PRIVATE int sqlite3HeapNearlyFull(void); #ifdef SQLITE_USE_ALLOCA # define sqlite3StackAllocRaw(D,N) alloca(N) # define sqlite3StackAllocZero(D,N) memset(alloca(N), 0, N) -# define sqlite3StackFree(D,P) +# define sqlite3StackFree(D,P) #else # define sqlite3StackAllocRaw(D,N) sqlite3DbMallocRaw(D,N) # define sqlite3StackAllocZero(D,N) sqlite3DbMallocZero(D,N) @@ -12226,7 +12234,7 @@ SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst); ** Routines to read and write variable-length integers. These used to ** be defined locally, but now we use the varint routines in the util.c ** file. Code should use the MACRO forms below, as the Varint32 versions -** are coded to assume the single byte case is already handled (which +** are coded to assume the single byte case is already handled (which ** the MACRO form does). */ SQLITE_PRIVATE int sqlite3PutVarint(unsigned char*, u64); @@ -12301,7 +12309,7 @@ SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z,int); SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value*, u8); SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value*, u8); -SQLITE_PRIVATE void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, +SQLITE_PRIVATE void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*)); SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value*); SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *); @@ -12353,7 +12361,7 @@ SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *, Btree *); SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *); SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3*,int); SQLITE_PRIVATE KeyInfo *sqlite3IndexKeyinfo(Parse *, Index *); -SQLITE_PRIVATE int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, +SQLITE_PRIVATE int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*), FuncDestructor *pDestructor @@ -12411,7 +12419,7 @@ SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char*); # define sqlite3VtabRollback(X) # define sqlite3VtabCommit(X) # define sqlite3VtabInSync(db) 0 -# define sqlite3VtabLock(X) +# define sqlite3VtabLock(X) # define sqlite3VtabUnlock(X) # define sqlite3VtabUnlockList(X) # define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK @@ -12458,7 +12466,7 @@ SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int); ** no-op macros if OMIT_FOREIGN_KEY is defined. In this case no foreign ** key functionality is available. If OMIT_TRIGGER is defined but ** OMIT_FOREIGN_KEY is not, only some of the functions are no-oped. In -** this case foreign keys are parsed, but no other functionality is +** this case foreign keys are parsed, but no other functionality is ** provided (enforcement of FK constraints requires the triggers sub-system). */ #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) @@ -12553,7 +12561,7 @@ SQLITE_PRIVATE void sqlite3ParserTrace(FILE*, char *); /* ** If the SQLITE_ENABLE IOTRACE exists then the global variable ** sqlite3IoTrace is a pointer to a printf-like routine used to -** print I/O tracing messages. +** print I/O tracing messages. */ #ifdef SQLITE_ENABLE_IOTRACE # define IOTRACE(A) if( sqlite3IoTrace ){ sqlite3IoTrace A; } @@ -12587,7 +12595,7 @@ SQLITE_PRIVATE void (*sqlite3IoTrace)(const char*,...); ** that allocations that might have been satisfied by lookaside are not ** passed back to non-lookaside free() routines. Asserts such as the ** example above are placed on the non-lookaside free() routines to verify -** this constraint. +** this constraint. ** ** All of this is no-op for a production build. It only comes into ** play when the SQLITE_MEMDEBUG compile-time option is used. @@ -15748,10 +15756,28 @@ static malloc_zone_t* _sqliteZone_; #define SQLITE_MALLOCSIZE(x) \ (_sqliteZone_ ? _sqliteZone_->size(_sqliteZone_,x) : malloc_size(x)) +#elif defined(SQLITE_OS_RTT) +#include +/* +** Use standard C library malloc and free on non-Apple systems. +** Also used by rt-thread systems if SQLITE_WITHOUT_ZONEMALLOC is defined. +*/ +#define SQLITE_MALLOC(x) rt_malloc((rt_size_t)x) +#define SQLITE_FREE(x) rt_free(x) +#define SQLITE_REALLOC(x,y) rt_realloc((x),(rt_size_t)(y)) + +#if (!defined(SQLITE_WITHOUT_MSIZE)) \ + && (defined(HAVE_MALLOC_H) && defined(HAVE_MALLOC_USABLE_SIZE)) +# error "not have malloc_usable_size()" +#endif +#ifdef HAVE_MALLOC_USABLE_SIZE +# undef SQLITE_MALLOCSIZE +#endif + #else /* if not __APPLE__ */ /* -** Use standard C library malloc and free on non-Apple systems. +** Use standard C library malloc and free on non-Apple systems. ** Also used by Apple systems if SQLITE_WITHOUT_ZONEMALLOC is defined. */ #define SQLITE_MALLOC(x) malloc(x) @@ -15902,13 +15928,13 @@ static int sqlite3MemInit(void *NotUsed){ /* defer MT decisions to system malloc */ _sqliteZone_ = malloc_default_zone(); }else{ - /* only 1 core, use our own zone to contention over global locks, + /* only 1 core, use our own zone to contention over global locks, ** e.g. we have our own dedicated locks */ bool success; malloc_zone_t* newzone = malloc_create_zone(4096, 0); malloc_set_zone_name(newzone, "Sqlite_Heap"); do{ - success = OSAtomicCompareAndSwapPtrBarrier(NULL, newzone, + success = OSAtomicCompareAndSwapPtrBarrier(NULL, newzone, (void * volatile *)&_sqliteZone_); }while(!_sqliteZone_); if( !success ){ @@ -18814,6 +18840,351 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ #endif /* SQLITE_MUTEX_W32 */ /************** End of mutex_w32.c *******************************************/ +/************** Begin file mutex_rtt.c ***************************************/ +/* +** 2007 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. +** +************************************************************************* +** This file contains the C functions that implement mutexes for rtthread +*/ + +/* +** The code in this file is only used if we are compiling threadsafe +** under rt-thread with rt_mutex. +** +** Note that this implementation requires a version of rt-thread that +** supports recursive mutexes. +*/ +#ifdef SQLITE_MUTEX_RTT + +/* #include */ + +/* +** The sqlite3_mutex.id, sqlite3_mutex.nRef, and sqlite3_mutex.owner fields +** are necessary under two condidtions: (1) Debug builds and (2) using +** home-grown mutexes. Encapsulate these conditions into a single #define. +*/ +#if defined(SQLITE_DEBUG) || defined(SQLITE_HOMEGROWN_RECURSIVE_MUTEX) +# define SQLITE_MUTEX_NREF 1 +#else +# define SQLITE_MUTEX_NREF 0 +#endif + +/* +** Each recursive mutex is an instance of the following structure. +*/ +struct sqlite3_mutex { + struct rt_mutex mutex; /* Mutex controlling the lock */ +#if SQLITE_MUTEX_NREF + int id; /* Mutex type */ + volatile int nRef; /* Number of entrances */ + volatile rt_thread_t owner; /* Thread that is within this mutex */ + int trace; /* True to trace changes */ +#endif +}; +#define RTT_MUTEX_INITIALIZER { 0 } +#if SQLITE_MUTEX_NREF +#define SQLITE3_MUTEX_INITIALIZER { RTT_MUTEX_INITIALIZER, 0, 0, (rt_thread_t)0, 0 } +#else +#define SQLITE3_MUTEX_INITIALIZER { RTT_MUTEX_INITIALIZER } +#endif + +/* +** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are +** intended for use only inside assert() statements. On some platforms, +** there might be race conditions that can cause these routines to +** deliver incorrect results. In particular, if rtt_equal() is +** not an atomic operation, then these routines might delivery +** incorrect results. On most platforms, rtt_equal() is a +** comparison of two integers and is therefore atomic. But we are +** told that HPUX is not such a platform. If so, then these routines +** will not always work correctly on HPUX. +** +** On those platforms where rtt_equal() is not atomic, SQLite +** should be compiled without -DSQLITE_DEBUG and with -DNDEBUG to +** make sure no assert() statements are evaluated and hence these +** routines are never called. +*/ +#if !defined(NDEBUG) || defined(SQLITE_DEBUG) +static int rttMutexHeld(sqlite3_mutex *p){ + return (p->nRef != 0 && p->owner == rt_thread_self()); +} +static int rttMutexNotheld(sqlite3_mutex *p){ + return (p->nRef == 0 || p->owner != rt_thread_self()); +} +#endif + +/* +** Initialize and deinitialize the mutex subsystem. +*/ +static int rttMutexInit(void){ return SQLITE_OK; } +static int rttMutexEnd(void){ return SQLITE_OK; } + +/* +** The sqlite3_mutex_alloc() routine allocates a new +** mutex and returns a pointer to it. If it returns NULL +** that means that a mutex could not be allocated. SQLite +** will unwind its stack and return an error. The argument +** to sqlite3_mutex_alloc() is one of these integer constants: +** +**
    +**
  • SQLITE_MUTEX_FAST +**
  • SQLITE_MUTEX_RECURSIVE +**
  • SQLITE_MUTEX_STATIC_MASTER +**
  • SQLITE_MUTEX_STATIC_MEM +**
  • SQLITE_MUTEX_STATIC_MEM2 +**
  • SQLITE_MUTEX_STATIC_PRNG +**
  • SQLITE_MUTEX_STATIC_LRU +**
  • SQLITE_MUTEX_STATIC_PMEM +**
+** +** The first two constants cause sqlite3_mutex_alloc() to create +** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE +** is used but not necessarily so when SQLITE_MUTEX_FAST is used. +** The mutex implementation does not need to make a distinction +** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does +** not want to. But SQLite will only request a recursive mutex in +** cases where it really needs one. If a faster non-recursive mutex +** implementation is available on the host platform, the mutex subsystem +** might return such a mutex in response to SQLITE_MUTEX_FAST. +** +** The other allowed parameters to sqlite3_mutex_alloc() each return +** a pointer to a static preexisting mutex. Six static mutexes are +** used by the current version of SQLite. Future versions of SQLite +** may add additional static mutexes. Static mutexes are for internal +** use by SQLite only. Applications that use SQLite mutexes should +** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or +** SQLITE_MUTEX_RECURSIVE. +** +** Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST +** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() +** returns a different mutex on every call. But for the static +** mutex types, the same mutex is returned on every call that has +** the same type number. +*/ +static sqlite3_mutex *rttMutexAlloc(int iType){ + static sqlite3_mutex staticMutexes[] = { + SQLITE3_MUTEX_INITIALIZER, + SQLITE3_MUTEX_INITIALIZER, + SQLITE3_MUTEX_INITIALIZER, + SQLITE3_MUTEX_INITIALIZER, + SQLITE3_MUTEX_INITIALIZER, + SQLITE3_MUTEX_INITIALIZER + }; + sqlite3_mutex *p; + switch( iType ){ + case SQLITE_MUTEX_RECURSIVE: { + p = sqlite3MallocZero( sizeof(*p) ); + if( p ){ + /* Use a recursive mutex if it is available */ + rt_mutex_init(&p->mutex, "sqlmtx", RT_IPC_FLAG_PRIO); +#if SQLITE_MUTEX_NREF + p->id = iType; +#endif + } + break; + } + case SQLITE_MUTEX_FAST: { + p = sqlite3MallocZero( sizeof(*p) ); + if( p ){ +#if SQLITE_MUTEX_NREF + p->id = iType; +#endif + rt_mutex_init(&p->mutex, "sqlmtx", RT_IPC_FLAG_PRIO); + } + break; + } + default: { + assert( iType-2 >= 0 ); + assert( iType-2 < ArraySize(staticMutexes) ); + p = &staticMutexes[iType-2]; +#if SQLITE_MUTEX_NREF + p->id = iType; +#endif + break; + } + } + return p; +} + + +/* +** This routine deallocates a previously +** allocated mutex. SQLite is careful to deallocate every +** mutex that it allocates. +*/ +static void rttMutexFree(sqlite3_mutex *p){ + assert( p->nRef==0 ); + assert( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ); + rt_mutex_delete(&p->mutex); + sqlite3_free(p); +} + +/* +** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt +** to enter a mutex. If another thread is already within the mutex, +** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return +** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK +** upon successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can +** be entered multiple times by the same thread. In such cases the, +** mutex must be exited an equal number of times before another thread +** can enter. If the same thread tries to enter any other kind of mutex +** more than once, the behavior is undefined. +*/ +static void rttMutexEnter(sqlite3_mutex *p){ + assert( p->id==SQLITE_MUTEX_RECURSIVE || rttMutexNotheld(p) ); + +#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX + /* If recursive mutexes are not available, then we have to grow + ** our own. This implementation assumes that rtt_equal() + ** is atomic - that it cannot be deceived into thinking self + ** and p->owner are equal if p->owner changes between two values + ** that are not equal to self while the comparison is taking place. + ** This implementation also assumes a coherent cache - that + ** separate processes cannot read different values from the same + ** address at the same time. If either of these two conditions + ** are not met, then the mutexes will fail and problems will result. + */ + { + rt_thread_t self = rt_thread_self(); + if( p->nRef>0 && (p->owner == self) ){ + p->nRef++; + }else{ + rt_mutex_take(&p->mutex, RT_WAITING_FOREVER); + assert( p->nRef==0 ); + p->owner = self; + p->nRef = 1; + } + } +#else + /* Use the built-in recursive mutexes if they are available. + */ + rt_mutex_take(&p->mutex, RT_WAITING_FOREVER); +#if SQLITE_MUTEX_NREF + assert( p->nRef>0 || p->owner==0 ); + p->owner = rt_thread_self(); + p->nRef++; +#endif +#endif + +#ifdef SQLITE_DEBUG + if( p->trace ){ + rt_kprintf("enter mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef); + } +#endif +} +static int rttMutexTry(sqlite3_mutex *p){ + int rc; + assert( p->id==SQLITE_MUTEX_RECURSIVE || rttMutexNotheld(p) ); + +#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX + /* If recursive mutexes are not available, then we have to grow + ** our own. This implementation assumes that rtt_equal() + ** is atomic - that it cannot be deceived into thinking self + ** and p->owner are equal if p->owner changes between two values + ** that are not equal to self while the comparison is taking place. + ** This implementation also assumes a coherent cache - that + ** separate processes cannot read different values from the same + ** address at the same time. If either of these two conditions + ** are not met, then the mutexes will fail and problems will result. + */ + { + rt_thread_t self = rt_thread_self(); + if( p->nRef>0 && (p->owner == self) ){ + p->nRef++; + rc = SQLITE_OK; + }else if( rt_mutex_take(&p->mutex, RT_WAITING_NO)==RT_EOK ){ + assert( p->nRef==0 ); + p->owner = self; + p->nRef = 1; + rc = SQLITE_OK; + }else{ + rc = SQLITE_BUSY; + } + } +#else + /* Use the built-in recursive mutexes if they are available. + */ + if( rt_mutex_take(&p->mutex, RT_WAITING_NO)==RT_EOK ){ +#if SQLITE_MUTEX_NREF + p->owner = rt_thread_self(); + p->nRef++; +#endif + rc = SQLITE_OK; + }else{ + rc = SQLITE_BUSY; + } +#endif + +#ifdef SQLITE_DEBUG + if( rc==SQLITE_OK && p->trace ){ + rt_kprintf("enter mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef); + } +#endif + return rc; +} + +/* +** The sqlite3_mutex_leave() routine exits a mutex that was +** previously entered by the same thread. The behavior +** is undefined if the mutex is not currently entered or +** is not currently allocated. SQLite will never do either. +*/ +static void rttMutexLeave(sqlite3_mutex *p){ + assert( rttMutexHeld(p) ); +#if SQLITE_MUTEX_NREF + p->nRef--; + if( p->nRef==0 ) p->owner = 0; +#endif + assert( p->nRef==0 || p->id==SQLITE_MUTEX_RECURSIVE ); + +#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX + if( p->nRef==0 ){ + rt_mutex_release(&p->mutex); + } +#else + rt_mutex_release(&p->mutex); +#endif + +#ifdef SQLITE_DEBUG + if( p->trace ){ + rt_kprintf("leave mutex %p (%d) with nRef=%d\n", p, p->trace, p->nRef); + } +#endif +} + +SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ + static const sqlite3_mutex_methods sMutex = { + rttMutexInit, + rttMutexEnd, + rttMutexAlloc, + rttMutexFree, + rttMutexEnter, + rttMutexTry, + rttMutexLeave, +#ifdef SQLITE_DEBUG + rttMutexHeld, + rttMutexNotheld +#else + 0, + 0 +#endif + }; + + return &sMutex; +} + +#endif /* SQLITE_MUTEX_RTT */ + + +/************** End of mutex_rtt.c *******************************************/ /************** Begin file malloc.c ******************************************/ /* ** 2001 September 15 @@ -30987,7 +31358,7 @@ WINBASEAPI BOOL WINAPI UnmapViewOfFile(LPCVOID); ** Some Microsoft compilers lack this definition. */ #ifndef INVALID_FILE_ATTRIBUTES -# define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +# define INVALID_FILE_ATTRIBUTES ((DWORD)-1) #endif #ifndef FILE_FLAG_MASK @@ -31037,7 +31408,7 @@ struct winFile { int szChunk; /* Chunk size configured by FCNTL_CHUNK_SIZE */ #if SQLITE_OS_WINCE LPWSTR zDeleteOnClose; /* Name of file to delete when closing */ - HANDLE hMutex; /* Mutex used to control access to shared lock */ + HANDLE hMutex; /* Mutex used to control access to shared lock */ HANDLE hShared; /* Shared memory segment used for locking */ winceLock local; /* Locks obtained by this instance of winFile */ winceLock *shared; /* Global shared lock memory for the file */ @@ -32186,7 +32557,7 @@ SQLITE_PRIVATE void sqlite3MemSetDefault(void){ #endif /* SQLITE_WIN32_MALLOC */ /* -** Convert a UTF-8 string to Microsoft Unicode (UTF-16?). +** Convert a UTF-8 string to Microsoft Unicode (UTF-16?). ** ** Space to hold the returned string is obtained from malloc. */ @@ -32239,7 +32610,7 @@ static char *winUnicodeToUtf8(LPCWSTR zWideFilename){ /* ** Convert an ANSI string to Microsoft Unicode, based on the ** current codepage settings for file apis. -** +** ** Space to hold the returned string is obtained ** from sqlite3_malloc. */ @@ -32313,7 +32684,7 @@ SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zFilename){ } /* -** Convert UTF-8 to multibyte character string. Space to hold the +** Convert UTF-8 to multibyte character string. Space to hold the ** returned string is obtained from sqlite3_malloc(). */ SQLITE_API char *sqlite3_win32_utf8_to_mbcs(const char *zFilename){ @@ -32453,11 +32824,11 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){ ** ** This routine is invoked after an error occurs in an OS function. ** It logs a message using sqlite3_log() containing the current value of -** error code and, if possible, the human-readable equivalent from +** error code and, if possible, the human-readable equivalent from ** FormatMessage. ** ** The first argument passed to the macro should be the error code that -** will be returned to SQLite (e.g. SQLITE_IOERR_DELETE, SQLITE_CANTOPEN). +** will be returned to SQLite (e.g. SQLITE_IOERR_DELETE, SQLITE_CANTOPEN). ** The two subsequent arguments should be the name of the OS function that ** failed and the associated file-system path, if any. */ @@ -32488,7 +32859,7 @@ static int winLogErrorAtLine( /* ** The number of times that a ReadFile(), WriteFile(), and DeleteFile() -** will be retried following a locking error - probably caused by +** will be retried following a locking error - probably caused by ** antivirus software. Also the initial delay before the first retry. ** The delay increases linearly with each retry. */ @@ -32532,7 +32903,7 @@ static int winRetryIoerr(int *pnRetry, DWORD *pError){ */ static void winLogIoerr(int nRetry){ if( nRetry ){ - sqlite3_log(SQLITE_IOERR, + sqlite3_log(SQLITE_IOERR, "delayed %dms for lock/sharing conflict", winIoerrRetryDelay*nRetry*(nRetry+1)/2 ); @@ -32626,17 +32997,17 @@ static int winceCreateLock(const char *zFilename, winFile *pFile){ /* Acquire the mutex before continuing */ winceMutexAcquire(pFile->hMutex); - - /* Since the names of named mutexes, semaphores, file mappings etc are + + /* Since the names of named mutexes, semaphores, file mappings etc are ** case-sensitive, take advantage of that by uppercasing the mutex name ** and using that as the shared filemapping name. */ osCharUpperW(zName); pFile->hShared = osCreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(winceLock), - zName); + zName); - /* Set a flag that indicates we're the first to create the memory so it + /* Set a flag that indicates we're the first to create the memory so it ** must be zero-initialized */ lastErrno = osGetLastError(); if (lastErrno == ERROR_ALREADY_EXISTS){ @@ -32647,7 +33018,7 @@ static int winceCreateLock(const char *zFilename, winFile *pFile){ /* If we succeeded in making the shared memory handle, map it. */ if( pFile->hShared ){ - pFile->shared = (winceLock*)osMapViewOfFile(pFile->hShared, + pFile->shared = (winceLock*)osMapViewOfFile(pFile->hShared, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, sizeof(winceLock)); /* If mapping failed, close the shared memory handle and erase it */ if( !pFile->shared ){ @@ -32673,7 +33044,7 @@ static int winceCreateLock(const char *zFilename, winFile *pFile){ pFile->hMutex = NULL; return SQLITE_IOERR; } - + /* Initialize the shared memory if we're supposed to */ if( bInit ){ memset(pFile->shared, 0, sizeof(winceLock)); @@ -32711,13 +33082,13 @@ static void winceDestroyLock(winFile *pFile){ osCloseHandle(pFile->hShared); /* Done with the mutex */ - winceMutexRelease(pFile->hMutex); + winceMutexRelease(pFile->hMutex); osCloseHandle(pFile->hMutex); pFile->hMutex = NULL; } } -/* +/* ** An implementation of the LockFile() API of Windows for CE */ static BOOL winceLockFile( @@ -32928,8 +33299,8 @@ static BOOL winUnlockFile( #endif /* -** Move the current position of the file handle passed as the first -** argument to offset iOffset within the file. If successful, return 0. +** Move the current position of the file handle passed as the first +** argument to offset iOffset within the file. If successful, return 0. ** Otherwise, set pFile->lastErrno and return non-zero. */ static int winSeekFile(winFile *pFile, sqlite3_int64 iOffset){ @@ -32944,11 +33315,11 @@ static int winSeekFile(winFile *pFile, sqlite3_int64 iOffset){ upperBits = (LONG)((iOffset>>32) & 0x7fffffff); lowerBits = (LONG)(iOffset & 0xffffffff); - /* API oddity: If successful, SetFilePointer() returns a dword + /* API oddity: If successful, SetFilePointer() returns a dword ** containing the lower 32-bits of the new file-offset. Or, if it fails, - ** it returns INVALID_SET_FILE_POINTER. However according to MSDN, - ** INVALID_SET_FILE_POINTER may also be a valid new offset. So to determine - ** whether an error has actually occurred, it is also necessary to call + ** it returns INVALID_SET_FILE_POINTER. However according to MSDN, + ** INVALID_SET_FILE_POINTER may also be a valid new offset. So to determine + ** whether an error has actually occurred, it is also necessary to call ** GetLastError(). */ dwRet = osSetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN); @@ -33031,7 +33402,7 @@ static int winClose(sqlite3_file *id){ int cnt = 0; while( osDeleteFileW(pFile->zDeleteOnClose)==0 - && osGetFileAttributesW(pFile->zDeleteOnClose)!=0xffffffff + && osGetFileAttributesW(pFile->zDeleteOnClose)!=0xffffffff && cnt++ < WINCE_DELETION_ATTEMPTS ){ sqlite3_win32_sleep(100); /* Wait a little before trying again */ @@ -33860,7 +34231,7 @@ static int winDeviceCharacteristics(sqlite3_file *id){ ((p->ctrlFlags & WINFILE_PSOW)?SQLITE_IOCAP_POWERSAFE_OVERWRITE:0); } -/* +/* ** Windows will only let you create file view mappings ** on allocation size granularity boundaries. ** During sqlite3_os_init() we do a GetSystemInfo() @@ -33872,11 +34243,11 @@ SYSTEM_INFO winSysInfo; /* ** Helper functions to obtain and relinquish the global mutex. The -** global mutex is used to protect the winLockInfo objects used by +** global mutex is used to protect the winLockInfo objects used by ** this file, all of which may be shared by multiple threads. ** -** Function winShmMutexHeld() is used to assert() that the global mutex -** is held when required. This function is only used as part of assert() +** Function winShmMutexHeld() is used to assert() that the global mutex +** is held when required. This function is only used as part of assert() ** statements. e.g. ** ** winShmEnterMutex() @@ -33906,10 +34277,10 @@ static int winShmMutexHeld(void) { ** this object or while reading or writing the following fields: ** ** nRef -** pNext +** pNext ** ** The following fields are read-only after the object is created: -** +** ** fid ** zFilename ** @@ -34005,7 +34376,7 @@ static int winShmSystemLock( if( lockType == _SHM_WRLCK ) dwFlags |= LOCKFILE_EXCLUSIVE_LOCK; rc = winLockFile(&pFile->hFile.h, dwFlags, ofst, 0, nByte, 0); } - + if( rc!= 0 ){ rc = SQLITE_OK; }else{ @@ -34101,7 +34472,7 @@ static int winOpenSharedMemory(winFile *pDbFd){ } pNew->zFilename = (char*)&pNew[1]; sqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath); - sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename); + sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename); /* Look to see if there is an existing winShmNode that can be used. ** If no matching winShmNode currently exists, create a new one. @@ -34138,7 +34509,7 @@ static int winOpenSharedMemory(winFile *pDbFd){ } /* Check to see if another process is holding the dead-man switch. - ** If not, truncate the file to zero length. + ** If not, truncate the file to zero length. */ if( winShmSystemLock(pShmNode, _SHM_WRLCK, WIN_SHM_DMS, 1)==SQLITE_OK ){ rc = winTruncate((sqlite3_file *)&pShmNode->hFile, 0); @@ -34167,7 +34538,7 @@ static int winOpenSharedMemory(winFile *pDbFd){ ** the cover of the winShmEnterMutex() mutex and the pointer from the ** new (struct winShm) object to the pShmNode has been set. All that is ** left to do is to link the new object into the linked list starting - ** at pShmNode->pFirst. This must be done while holding the pShmNode->mutex + ** at pShmNode->pFirst. This must be done while holding the pShmNode->mutex ** mutex. */ sqlite3_mutex_enter(pShmNode->mutex); @@ -34187,7 +34558,7 @@ shm_open_err: } /* -** Close a connection to shared-memory. Delete the underlying +** Close a connection to shared-memory. Delete the underlying ** storage if deleteFlag is true. */ static int winShmUnmap( @@ -34276,7 +34647,7 @@ static int winShmLock( if( rc==SQLITE_OK ){ p->exclMask &= ~mask; p->sharedMask &= ~mask; - } + } }else if( flags & SQLITE_SHM_SHARED ){ u16 allShared = 0; /* Union of locks held by connections other than "p" */ @@ -34315,7 +34686,7 @@ static int winShmLock( break; } } - + /* Get the exclusive locks at the system level. Then if successful ** also mark the local connection as being locked. */ @@ -34335,7 +34706,7 @@ static int winShmLock( } /* -** Implement a memory barrier or memory fence on shared memory. +** Implement a memory barrier or memory fence on shared memory. ** ** All loads and stores begun before the barrier must complete before ** any load or store begun after the barrier. @@ -34350,22 +34721,22 @@ static void winShmBarrier( } /* -** This function is called to obtain a pointer to region iRegion of the -** shared-memory associated with the database file fd. Shared-memory regions -** are numbered starting from zero. Each shared-memory region is szRegion +** This function is called to obtain a pointer to region iRegion of the +** shared-memory associated with the database file fd. Shared-memory regions +** are numbered starting from zero. Each shared-memory region is szRegion ** bytes in size. ** ** If an error occurs, an error code is returned and *pp is set to NULL. ** ** Otherwise, if the isWrite parameter is 0 and the requested shared-memory ** region has not been allocated (by any client, including one running in a -** separate process), then *pp is set to NULL and SQLITE_OK returned. If -** isWrite is non-zero and the requested shared-memory region has not yet +** separate process), then *pp is set to NULL and SQLITE_OK returned. If +** isWrite is non-zero and the requested shared-memory region has not yet ** been allocated, it is allocated by this function. ** ** If the shared-memory region has already been allocated or is allocated by -** this call as described above, then it is mapped into this processes -** address space (if it is not already), *pp is set to point to the mapped +** this call as described above, then it is mapped into this processes +** address space (if it is not already), *pp is set to point to the mapped ** memory and SQLITE_OK returned. */ static int winShmMap( @@ -34437,17 +34808,17 @@ static int winShmMap( while( pShmNode->nRegion<=iRegion ){ HANDLE hMap = NULL; /* file-mapping handle */ void *pMap = 0; /* Mapped memory region */ - + #if SQLITE_OS_WINRT hMap = osCreateFileMappingFromApp(pShmNode->hFile.h, NULL, PAGE_READWRITE, nByte, NULL ); #elif defined(SQLITE_WIN32_HAS_WIDE) - hMap = osCreateFileMappingW(pShmNode->hFile.h, + hMap = osCreateFileMappingW(pShmNode->hFile.h, NULL, PAGE_READWRITE, 0, nByte, NULL ); #elif defined(SQLITE_WIN32_HAS_ANSI) - hMap = osCreateFileMappingA(pShmNode->hFile.h, + hMap = osCreateFileMappingA(pShmNode->hFile.h, NULL, PAGE_READWRITE, 0, nByte, NULL ); #endif @@ -34544,14 +34915,14 @@ static int winUnmapfile(winFile *pFile){ /* ** Memory map or remap the file opened by file-descriptor pFd (if the file -** is already mapped, the existing mapping is replaced by the new). Or, if -** there already exists a mapping for this file, and there are still +** is already mapped, the existing mapping is replaced by the new). Or, if +** there already exists a mapping for this file, and there are still ** outstanding xFetch() references to it, this function is a no-op. ** -** If parameter nByte is non-negative, then it is the requested size of -** the mapping to create. Otherwise, if nByte is less than zero, then the +** If parameter nByte is non-negative, then it is the requested size of +** the mapping to create. Otherwise, if nByte is less than zero, then the ** requested size is the size of the file on disk. The actual size of the -** created mapping is either the requested size or the value configured +** created mapping is either the requested size or the value configured ** using SQLITE_FCNTL_MMAP_SIZE, whichever is smaller. ** ** SQLITE_OK is returned if no error occurs (even if the mapping is not @@ -34580,7 +34951,7 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ nMap = pFd->mmapSizeMax; } nMap &= ~(sqlite3_int64)(winSysInfo.dwPageSize - 1); - + if( nMap==0 && pFd->mmapSize>0 ){ winUnmapfile(pFd); } @@ -34652,7 +35023,7 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ ** Finally, if an error does occur, return an SQLite error code. The final ** value of *pp is undefined in this case. ** -** If this function does return a pointer, the caller must eventually +** If this function does return a pointer, the caller must eventually ** release the reference by calling winUnfetch(). */ static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ @@ -34687,20 +35058,20 @@ static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ } /* -** If the third argument is non-NULL, then this function releases a +** If the third argument is non-NULL, then this function releases a ** reference obtained by an earlier call to winFetch(). The second ** argument passed to this function must be the same as the corresponding -** argument that was passed to the winFetch() invocation. +** argument that was passed to the winFetch() invocation. ** -** Or, if the third argument is NULL, then this function is being called -** to inform the VFS layer that, according to POSIX, any existing mapping +** Or, if the third argument is NULL, then this function is being called +** to inform the VFS layer that, according to POSIX, any existing mapping ** may now be invalid and should be unmapped. */ static int winUnfetch(sqlite3_file *fd, i64 iOff, void *p){ #if SQLITE_MAX_MMAP_SIZE>0 winFile *pFd = (winFile*)fd; /* The underlying database file */ - /* If p==0 (unmap the entire file) then there must be no outstanding + /* If p==0 (unmap the entire file) then there must be no outstanding ** xFetch references. Or, if p!=0 (meaning it is an xFetch reference), ** then there must be at least one outstanding. */ assert( (p==0)==(pFd->nFetchOut==0) ); @@ -34836,7 +35207,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this - ** function failing. + ** function failing. */ SimulateIOError( return SQLITE_IOERR ); @@ -35002,7 +35373,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ #endif /* SQLITE_WIN32_HAS_ANSI */ #endif /* !SQLITE_OS_WINRT */ - /* Check that the output buffer is large enough for the temporary file + /* Check that the output buffer is large enough for the temporary file ** name. If it is not, return SQLITE_ERROR. */ nLen = sqlite3Strlen30(zBuf); @@ -35099,8 +35470,8 @@ static int winOpen( #ifndef NDEBUG int isOpenJournal = (isCreate && ( - eType==SQLITE_OPEN_MASTER_JOURNAL - || eType==SQLITE_OPEN_MAIN_JOURNAL + eType==SQLITE_OPEN_MASTER_JOURNAL + || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_WAL )); #endif @@ -35108,9 +35479,9 @@ static int winOpen( OSTRACE(("OPEN name=%s, pFile=%p, flags=%x, pOutFlags=%p\n", zUtf8Name, id, flags, pOutFlags)); - /* Check the following statements are true: + /* Check the following statements are true: ** - ** (a) Exactly one of the READWRITE and READONLY flags must be set, and + ** (a) Exactly one of the READWRITE and READONLY flags must be set, and ** (b) if CREATE is set, then READWRITE must also be set, and ** (c) if EXCLUSIVE is set, then CREATE must also be set. ** (d) if DELETEONCLOSE is set, then CREATE must also be set. @@ -35120,7 +35491,7 @@ static int winOpen( assert(isExclusive==0 || isCreate); assert(isDelete==0 || isCreate); - /* The main DB, main journal, WAL file and master journal are never + /* The main DB, main journal, WAL file and master journal are never ** automatically deleted. Nor are they ever temporary files. */ assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_DB ); assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_JOURNAL ); @@ -35128,9 +35499,9 @@ static int winOpen( assert( (!isDelete && zName) || eType!=SQLITE_OPEN_WAL ); /* Assert that the upper layer has set one of the "file-type" flags. */ - assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB - || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL - || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_MASTER_JOURNAL + assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB + || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL + || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_MASTER_JOURNAL || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL ); @@ -35145,8 +35516,8 @@ static int winOpen( } #endif - /* If the second argument to this function is NULL, generate a - ** temporary file name to use + /* If the second argument to this function is NULL, generate a + ** temporary file name to use */ if( !zUtf8Name ){ assert( isDelete && !isOpenJournal ); @@ -35186,8 +35557,8 @@ static int winOpen( dwDesiredAccess = GENERIC_READ; } - /* SQLITE_OPEN_EXCLUSIVE is used to make sure that a new file is - ** created. SQLite doesn't use it to indicate "exclusive access" + /* SQLITE_OPEN_EXCLUSIVE is used to make sure that a new file is + ** created. SQLite doesn't use it to indicate "exclusive access" ** as it is usually understood. */ if( isExclusive ){ @@ -35276,7 +35647,7 @@ static int winOpen( sqlite3_free(zConverted); sqlite3_free(zTmpname); if( isReadWrite && !isExclusive ){ - return winOpen(pVfs, zName, id, + return winOpen(pVfs, zName, id, ((flags|SQLITE_OPEN_READONLY) & ~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)), pOutFlags); @@ -35485,14 +35856,14 @@ static int winAccess( WIN32_FILE_ATTRIBUTE_DATA sAttrData; memset(&sAttrData, 0, sizeof(sAttrData)); while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted, - GetFileExInfoStandard, + GetFileExInfoStandard, &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){} if( rc ){ /* For an SQLITE_ACCESS_EXISTS query, treat a zero-length file ** as if it does not exist. */ if( flags==SQLITE_ACCESS_EXISTS - && sAttrData.nFileSizeHigh==0 + && sAttrData.nFileSizeHigh==0 && sAttrData.nFileSizeLow==0 ){ attr = INVALID_FILE_ATTRIBUTES; }else{ @@ -35591,7 +35962,7 @@ static int winFullPathname( int nFull, /* Size of output buffer in bytes */ char *zFull /* Output buffer */ ){ - + #if defined(__CYGWIN__) SimulateIOError( return SQLITE_ERROR ); UNUSED_PARAMETER(nFull); @@ -35860,12 +36231,12 @@ SQLITE_API int sqlite3_current_time = 0; /* Fake system time in seconds since 1 ** epoch of noon in Greenwich on November 24, 4714 B.C according to the ** proleptic Gregorian calendar. ** -** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date ** cannot be found. */ static int winCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *piNow){ - /* FILETIME structure is a 64-bit value representing the number of - 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5). + /* FILETIME structure is a 64-bit value representing the number of + 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5). */ FILETIME ft; static const sqlite3_int64 winFiletimeEpoch = 23058135*(sqlite3_int64)8640000; @@ -35873,7 +36244,7 @@ static int winCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *piNow){ static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000; #endif /* 2^32 - to avoid use of LL and warnings in gcc */ - static const sqlite3_int64 max32BitValue = + static const sqlite3_int64 max32BitValue = (sqlite3_int64)2000000000 + (sqlite3_int64)2000000000 + (sqlite3_int64)294967296; @@ -35889,7 +36260,7 @@ static int winCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *piNow){ #endif *piNow = winFiletimeEpoch + - ((((sqlite3_int64)ft.dwHighDateTime)*max32BitValue) + + ((((sqlite3_int64)ft.dwHighDateTime)*max32BitValue) + (sqlite3_int64)ft.dwLowDateTime)/(sqlite3_int64)10000; #ifdef SQLITE_TEST @@ -36026,10 +36397,10 @@ SQLITE_API int sqlite3_os_init(void){ sqlite3_vfs_register(&winLongPathVfs, 0); #endif - return SQLITE_OK; + return SQLITE_OK; } -SQLITE_API int sqlite3_os_end(void){ +SQLITE_API int sqlite3_os_end(void){ #if SQLITE_OS_WINRT if( sleepObj!=NULL ){ osCloseHandle(sleepObj); @@ -36042,6 +36413,2867 @@ SQLITE_API int sqlite3_os_end(void){ #endif /* SQLITE_OS_WIN */ /************** End of os_win.c **********************************************/ +/************** Begin file os_rtt.c ******************************************/ +/* +** 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 is specific to Windows. +*/ +#if SQLITE_OS_RTT /* This file is used for rt-thread only */ + +/* #include */ + +/* +** Include code that is common to all os_*.c files +*/ +/************** Include os_common.h in the middle of os_rtt.c ****************/ +/************** Begin file os_common.h ***************************************/ +/* +** 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 macros and a little bit of code that is common to +** all of the platform-specific files (os_*.c) and is #included into those +** files. +** +** This file should be #included by the os_*.c files only. It is not a +** general purpose header file. +*/ +#ifndef _OS_COMMON_H_ +#define _OS_COMMON_H_ + +/* +** At least two bugs have slipped in because we changed the MEMORY_DEBUG +** macro to SQLITE_DEBUG and some older makefiles have not yet made the +** switch. The following code should catch this problem at compile-time. +*/ +#ifdef MEMORY_DEBUG +# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." +#endif + +#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) +# ifndef SQLITE_DEBUG_OS_TRACE +# define SQLITE_DEBUG_OS_TRACE 0 +# endif + int sqlite3OSTrace = SQLITE_DEBUG_OS_TRACE; +# define OSTRACE(X) if( sqlite3OSTrace ) sqlite3DebugPrintf X +#else +# define OSTRACE(X) +#endif + +/* +** Macros for performance tracing. Normally turned off. Only works +** on i486 hardware. +*/ +#ifdef SQLITE_PERFORMANCE_TRACE + +/* +** hwtime.h contains inline assembler code for implementing +** high-performance timing routines. +*/ +/************** Include hwtime.h in the middle of os_common.h ****************/ +/************** Begin file hwtime.h ******************************************/ +/* +** 2008 May 27 +** +** 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 inline asm code for retrieving "high-performance" +** counters for x86 class CPUs. +*/ +#ifndef _HWTIME_H_ +#define _HWTIME_H_ + +/* +** The following routine only works on pentium-class (or newer) processors. +** It uses the RDTSC opcode to read the cycle count value out of the +** processor and returns that value. This can be used for high-res +** profiling. +*/ +#if (defined(__GNUC__) || defined(_MSC_VER)) && \ + (defined(i386) || defined(__i386__) || defined(_M_IX86)) + + #if defined(__GNUC__) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned int lo, hi; + __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); + return (sqlite_uint64)hi << 32 | lo; + } + + #elif defined(_MSC_VER) + + __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ + __asm { + rdtsc + ret ; return value at EDX:EAX + } + } + + #endif + +#elif (defined(__GNUC__) && defined(__x86_64__)) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned long val; + __asm__ __volatile__ ("rdtsc" : "=A" (val)); + return val; + } + +#elif (defined(__GNUC__) && defined(__ppc__)) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned long long retval; + unsigned long junk; + __asm__ __volatile__ ("\n\ + 1: mftbu %1\n\ + mftb %L0\n\ + mftbu %0\n\ + cmpw %0,%1\n\ + bne 1b" + : "=r" (retval), "=r" (junk)); + return retval; + } + +#else + + #error Need implementation of sqlite3Hwtime() for your platform. + + /* + ** To compile without implementing sqlite3Hwtime() for your platform, + ** you can remove the above #error and use the following + ** stub function. You will lose timing support for many + ** of the debugging and testing utilities, but it should at + ** least compile and run. + */ +SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } + +#endif + +#endif /* !defined(_HWTIME_H_) */ + +/************** End of hwtime.h **********************************************/ +/************** Continuing where we left off in os_common.h ******************/ + +static sqlite_uint64 g_start; +static sqlite_uint64 g_elapsed; +#define TIMER_START g_start=sqlite3Hwtime() +#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start +#define TIMER_ELAPSED g_elapsed +#else +#define TIMER_START +#define TIMER_END +#define TIMER_ELAPSED ((sqlite_uint64)0) +#endif + +/* +** If we compile with the SQLITE_TEST macro set, then the following block +** of code will give us the ability to simulate a disk I/O error. This +** is used for testing the I/O recovery logic. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_io_error_hit = 0; /* Total number of I/O Errors */ +SQLITE_API int sqlite3_io_error_hardhit = 0; /* Number of non-benign errors */ +SQLITE_API int sqlite3_io_error_pending = 0; /* Count down to first I/O error */ +SQLITE_API int sqlite3_io_error_persist = 0; /* True if I/O errors persist */ +SQLITE_API int sqlite3_io_error_benign = 0; /* True if errors are benign */ +SQLITE_API int sqlite3_diskfull_pending = 0; +SQLITE_API int sqlite3_diskfull = 0; +#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X) +#define SimulateIOError(CODE) \ + if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ + || sqlite3_io_error_pending-- == 1 ) \ + { local_ioerr(); CODE; } +static void local_ioerr(){ + IOTRACE(("IOERR\n")); + sqlite3_io_error_hit++; + if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++; +} +#define SimulateDiskfullError(CODE) \ + if( sqlite3_diskfull_pending ){ \ + if( sqlite3_diskfull_pending == 1 ){ \ + local_ioerr(); \ + sqlite3_diskfull = 1; \ + sqlite3_io_error_hit = 1; \ + CODE; \ + }else{ \ + sqlite3_diskfull_pending--; \ + } \ + } +#else +#define SimulateIOErrorBenign(X) +#define SimulateIOError(A) +#define SimulateDiskfullError(A) +#endif + +/* +** When testing, keep a count of the number of open files. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_open_file_count = 0; +#define OpenCounter(X) sqlite3_open_file_count+=(X) +#else +#define OpenCounter(X) +#endif + +#endif /* !defined(_OS_COMMON_H_) */ + +/************** End of os_common.h *******************************************/ +/************** Continuing where we left off in os_rtt.c *********************/ + +/* +** Compiling and using WAL mode requires several APIs that are not +** available in rt-thread. +*/ +#if !defined(SQLITE_OMIT_WAL) +# error "WAL mode requires not support from the rt-thread, compile\ + with SQLITE_OMIT_WAL." +#endif + +/* +** Are most of the rtt ANSI APIs available (i.e. with certain exceptions +** based on the sub-platform)? +*/ +#if !defined(SQLITE_RTT_NO_ANSI) +# warning "please ensure rtt ANSI APIs is available, otherwise compile with\ + SQLITE_RTT_NO_ANSI" +# define SQLITE_RTT_HAS_ANSI +#endif + +/* +** Are most of the rtt Unicode APIs available (i.e. with certain exceptions +** based on the sub-platform)? +*/ +#if !defined(SQLITE_RTT_NO_WIDE) +# error "rtt not support Unicode APIs" +# define SQLITE_RTT_HAS_WIDE +#endif + +/* +** Make sure at least one set of rtt APIs is available. +*/ +#if !defined(SQLITE_RTT_HAS_ANSI) && !defined(SQLITE_RTT_HAS_WIDE) +# error "At least one of SQLITE_RTT_HAS_ANSI and SQLITE_RTT_HAS_WIDE\ + must be defined." +#endif + +/* +** Maximum pathname length (in chars) for rtt. This should normally be +** MAX_PATH. +*/ +#ifndef SQLITE_RTT_MAX_PATH_CHARS +# warning "default Maximum pathname length be 255, otherwise compile with\ + SQLITE_RTT_MAX_PATH_CHARS=?" +# define SQLITE_RTT_MAX_PATH_CHARS (255) +#endif + +/* +** Maximum supported path-length. +*/ +#define MAX_PATHNAME 512 + +/* +** Returns non-zero if the character should be treated as a directory +** separator. +*/ +#ifndef rttIsDirSep +# define rttIsDirSep(a) ((a) == '/') +#endif + +/* +** This macro is used when a local variable is set to a value that is +** [sometimes] not used by the code (e.g. via conditional compilation). +*/ +#ifndef UNUSED_VARIABLE_VALUE +# define UNUSED_VARIABLE_VALUE(x) (void)(x) +#endif + +/* +** Returns the string that should be used as the directory separator. +*/ +#ifndef rttGetDirDep +# define rttGetDirDep() "/" +#endif + +/* +** The winFile structure is a subclass of sqlite3_file* specific to the win32 +** portability layer. +*/ +typedef struct rttFile rttFile; +struct rttFile { + sqlite3_io_methods const *pMethod; /* Always the first entry */ + sqlite3_vfs *pVfs; /* The VFS that created this rttFile */ + int h; /* The file descriptor */ + unsigned short int ctrlFlags; /* Behavioral bits. UNIXFILE_* flags */ + unsigned char eFileLock; /* The type of lock held on this fd */ + int lastErrno; /* The unix errno from last I/O error */ + void *lockingContext; /* Locking style specific state */ + const char *zPath; /* Name of the file */ + int szChunk; /* Configured by FCNTL_CHUNK_SIZE */ +#if SQLITE_ENABLE_LOCKING_STYLE + int openFlags; /* The flags specified at open() */ +#endif +#if SQLITE_ENABLE_LOCKING_STYLE + unsigned fsFlags; /* cached details from statfs() */ +#endif +#ifdef SQLITE_DEBUG + /* The next group of variables are used to track whether or not the + ** transaction counter in bytes 24-27 of database files are updated + ** whenever any part of the database changes. An assertion fault will + ** occur if a file is updated without also updating the transaction + ** counter. This test is made to avoid new problems similar to the + ** one described by ticket #3584. + */ + unsigned char transCntrChng; /* True if the transaction counter changed */ + unsigned char dbUpdate; /* True if any part of database file changed */ + unsigned char inNormalWrite; /* True if in a normal write operation */ + +#endif + +#ifdef SQLITE_TEST + /* In test mode, increase the size of this structure a bit so that + ** it is larger than the struct CrashFile defined in test6.c. + */ + char aPadding[32]; +#endif +}; + +/* +** Allowed values for the rttFile.ctrlFlags bitmask: +*/ +#define UNIXFILE_EXCL 0x01 /* Connections from one process only */ +#define UNIXFILE_RDONLY 0x02 /* Connection is read only */ +#define UNIXFILE_PERSIST_WAL 0x04 /* Persistent WAL mode */ +#ifndef SQLITE_DISABLE_DIRSYNC +# define UNIXFILE_DIRSYNC 0x08 /* Directory sync needed */ +#else +# define UNIXFILE_DIRSYNC 0x00 +#endif +#define UNIXFILE_PSOW 0x10 /* SQLITE_IOCAP_POWERSAFE_OVERWRITE */ +#define UNIXFILE_DELETE 0x20 /* Delete on close */ +#define UNIXFILE_URI 0x40 /* Filename might have query parameters */ +#define UNIXFILE_NOLOCK 0x80 /* Do no file locking */ +#define UNIXFILE_WARNED 0x0100 /* verifyDbFile() warnings have been issued */ + +/* +** The following variable is (normally) set once and never changes +** thereafter. It records whether the operating system is Win9x +** or WinNT. +** +** 0: Operating system unknown. +** 1: Operating system is rtt. +** +** In order to facilitate testing on a rtt system, the test fixture +** can manually set this value to 1 to emulate Win98 behavior. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_os_type = 0; +#elif !SQLITE_OS_RTT && \ + defined(SQLITE_RTT_HAS_ANSI) && defined(SQLITE_RTT_HAS_WIDE) +static int sqlite3_os_type = 0; +#endif + +#ifndef SYSCALL +# define SYSCALL sqlite3_syscall_ptr +#endif + +#include + +static int _Access(const char *pathname, int mode) +{ + int fd; + + fd = open(pathname, O_RDONLY, mode); + + if (fd >= 0) + { + close(fd); + return 0; + } + + return -1; +} + +/* +** Invoke open(). Do so multiple times, until it either succeeds or +** fails for some reason other than EINTR. +** +** If the file creation mode "m" is 0 then set it to the default for +** SQLite. The default is SQLITE_DEFAULT_FILE_PERMISSIONS (normally +** 0644) as modified by the system umask. If m is not 0, then +** make the file creation mode be exactly m ignoring the umask. +** +** The m parameter will be non-zero only when creating -wal, -journal, +** and -shm files. We want those files to have *exactly* the same +** permissions as their original database, unadulterated by the umask. +** In that way, if a database file is -rw-rw-rw or -rw-rw-r-, and a +** transaction crashes and leaves behind hot journals, then any +** process that is able to write to the database will also be able to +** recover the hot journals. +*/ +static int robust_open(const char *z, int f, mode_t m); + +/* +** Open a file descriptor to the directory containing file zFilename. +** If successful, *pFd is set to the opened file descriptor and +** SQLITE_OK is returned. If an error occurs, either SQLITE_NOMEM +** or SQLITE_CANTOPEN is returned and *pFd is set to an undefined +** value. +** +** The directory file descriptor is used for only one thing - to +** fsync() a directory to make sure file creation and deletion events +** are flushed to disk. Such fsyncs are not needed on newer +** journaling filesystems, but are required on older filesystems. +** +** This routine can be overridden using the xSetSysCall interface. +** The ability to override this routine was added in support of the +** chromium sandbox. Opening a directory is a security risk (we are +** told) so making it overrideable allows the chromium sandbox to +** replace this routine with a harmless no-op. To make this routine +** a no-op, replace it with a stub that returns SQLITE_OK but leaves +** *pFd set to a negative number. +** +** If SQLITE_OK is returned, the caller is responsible for closing +** the file descriptor *pFd using close(). +*/ +static int openDirectory(const char *zFilename, int *pFd); + +/* +** Many system calls are accessed through pointer-to-functions so that +** they may be overridden at runtime to facilitate fault injection during +** testing and sandboxing. The following array holds the names and pointers +** to all overrideable system calls. +*/ +static struct rtt_syscall { + const char *zName; /* Name of the system call */ + sqlite3_syscall_ptr pCurrent; /* Current value of the system call */ + sqlite3_syscall_ptr pDefault; /* Default value */ +} aSyscall[] = { + {"sleep", (sqlite3_syscall_ptr)rt_thread_delay, 0}, +#define osSleep ((rt_err_t(*)(rt_tick_t))aSyscall[0].pCurrent) + + { "open", (sqlite3_syscall_ptr)open, 0 }, +#define osOpen ((int(*)(const char*,int,int))aSyscall[1].pCurrent) + + { "close", (sqlite3_syscall_ptr)close, 0 }, +#define osClose ((int(*)(int))aSyscall[2].pCurrent) + + { "getcwd", (sqlite3_syscall_ptr)getcwd, 0 }, +#define osGetcwd ((char*(*)(char*,size_t))aSyscall[3].pCurrent) + + { "stat", (sqlite3_syscall_ptr)stat, 0 }, +#define osStat ((int(*)(const char*,struct stat*))aSyscall[4].pCurrent) + + { "fstat", (sqlite3_syscall_ptr)fstat, 0 }, +#define osFstat ((int(*)(int,struct stat*))aSyscall[5].pCurrent) + + { "read", (sqlite3_syscall_ptr)read, 0 }, +#define osRead ((ssize_t(*)(int,void*,size_t))aSyscall[6].pCurrent) + + { "write", (sqlite3_syscall_ptr)write, 0 }, +#define osWrite ((ssize_t(*)(int,const void*,size_t))aSyscall[7].pCurrent) + + { "unlink", (sqlite3_syscall_ptr)unlink, 0 }, +#define osUnlink ((int(*)(const char*))aSyscall[8].pCurrent) + + { "openDirectory", (sqlite3_syscall_ptr)openDirectory, 0 }, +#define osOpenDirectory ((int(*)(const char*,int*))aSyscall[9].pCurrent) + + { "mkdir", (sqlite3_syscall_ptr)mkdir, 0 }, +#define osMkdir ((int(*)(const char*,mode_t))aSyscall[10].pCurrent) + + { "rmdir", (sqlite3_syscall_ptr)rmdir, 0 }, +#define osRmdir ((int(*)(const char*))aSyscall[11].pCurrent) + + {"access", (sqlite3_syscall_ptr)_Access, 0 }, +#define osAccess ((int(*)(const char*, int))aSyscall[12].pCurrent) +}; /* End of the overrideable system calls */ + +/* +** Do not accept any file descriptor less than this value, in order to avoid +** opening database file using file descriptors that are commonly used for +** standard input, output, and error. +*/ +#ifndef SQLITE_MINIMUM_FILE_DESCRIPTOR +# define SQLITE_MINIMUM_FILE_DESCRIPTOR 3 +#endif + +/* +** Invoke open(). Do so multiple times, until it either succeeds or +** fails for some reason other than EINTR. +** +** If the file creation mode "m" is 0 then set it to the default for +** SQLite. The default is SQLITE_DEFAULT_FILE_PERMISSIONS (normally +** 0644) as modified by the system umask. If m is not 0, then +** make the file creation mode be exactly m ignoring the umask. +** +** The m parameter will be non-zero only when creating -wal, -journal, +** and -shm files. We want those files to have *exactly* the same +** permissions as their original database, unadulterated by the umask. +** In that way, if a database file is -rw-rw-rw or -rw-rw-r-, and a +** transaction crashes and leaves behind hot journals, then any +** process that is able to write to the database will also be able to +** recover the hot journals. +*/ +static int robust_open(const char *z, int f, mode_t m){ + int fd; + mode_t m2 = m ; + while(1){ +#if defined(O_CLOEXEC) + fd = osOpen(z,f|O_CLOEXEC,m2); +#else + fd = osOpen(z,f,m2); +#endif + if( fd<0 ){ + if( errno==EINTR ) continue; + break; + } + if( fd>=SQLITE_MINIMUM_FILE_DESCRIPTOR ) break; + osClose(fd); + sqlite3_log(SQLITE_WARNING, + "attempt to open \"%s\" as file descriptor %d", z, fd); + fd = -1; + if( osOpen("/dev/null", f, m)<0 ) break; + } + + return fd; +} + +/* +** Open a file descriptor to the directory containing file zFilename. +** If successful, *pFd is set to the opened file descriptor and +** SQLITE_OK is returned. If an error occurs, either SQLITE_NOMEM +** or SQLITE_CANTOPEN is returned and *pFd is set to an undefined +** value. +** +** The directory file descriptor is used for only one thing - to +** fsync() a directory to make sure file creation and deletion events +** are flushed to disk. Such fsyncs are not needed on newer +** journaling filesystems, but are required on older filesystems. +** +** This routine can be overridden using the xSetSysCall interface. +** The ability to override this routine was added in support of the +** chromium sandbox. Opening a directory is a security risk (we are +** told) so making it overrideable allows the chromium sandbox to +** replace this routine with a harmless no-op. To make this routine +** a no-op, replace it with a stub that returns SQLITE_OK but leaves +** *pFd set to a negative number. +** +** If SQLITE_OK is returned, the caller is responsible for closing +** the file descriptor *pFd using close(). +*/ +static int openDirectory(const char *zFilename, int *pFd){ + int ii; + int fd = -1; + char zDirname[MAX_PATHNAME+1]; + + sqlite3_snprintf(MAX_PATHNAME, zDirname, "%s", zFilename); + for(ii=(int)strlen(zDirname); ii>1 && zDirname[ii]!='/'; ii--); + if( ii>0 ){ + zDirname[ii] = '\0'; + fd = robust_open(zDirname, O_RDONLY|O_BINARY, 0); + if( fd>=0 ){ + OSTRACE(("OPENDIR %-3d %s\n", fd, zDirname)); + } + } + *pFd = fd; + return (fd>=0?SQLITE_OK:rttLogError(SQLITE_CANTOPEN_BKPT, "open", zDirname)); +} + + + +/* +** This is the xSetSystemCall() method of sqlite3_vfs for all of the +** "win32" VFSes. Return SQLITE_OK opon successfully updating the +** system call pointer, or SQLITE_NOTFOUND if there is no configurable +** system call named zName. +*/ +static int rttSetSystemCall( + sqlite3_vfs *pNotUsed, /* The VFS pointer. Not used */ + const char *zName, /* Name of system call to override */ + sqlite3_syscall_ptr pNewFunc /* Pointer to new system call value */ +){ + unsigned int i; + int rc = SQLITE_NOTFOUND; + + UNUSED_PARAMETER(pNotUsed); + if( zName==0 ){ + /* If no zName is given, restore all system calls to their default + ** settings and return NULL + */ + rc = SQLITE_OK; + for(i=0; izPath : 0, lineno); + } +} + +/* +** Check a rttFile that is a database. Verify the following: +** +** (1) There is exactly one hard link on the file +** (2) The file is not a symbolic link +** (3) The file has not been renamed or unlinked +** +** Issue sqlite3_log(SQLITE_WARNING,...) messages if anything is not right. +*/ +static void verifyDbFile(rttFile *pFile){ + struct stat buf; + int rc; + if( pFile->ctrlFlags & UNIXFILE_WARNED ){ + /* One or more of the following warnings have already been issued. Do not + ** repeat them so as not to clutter the error log */ + return; + } + rc = osFstat(pFile->h, &buf); + if( rc!=0 ){ + sqlite3_log(SQLITE_WARNING, "cannot fstat db file %s", pFile->zPath); + pFile->ctrlFlags |= UNIXFILE_WARNED; + return; + } + if( buf.st_nlink==0 && (pFile->ctrlFlags & UNIXFILE_DELETE)==0 ){ + sqlite3_log(SQLITE_WARNING, "file unlinked while open: %s", pFile->zPath); + pFile->ctrlFlags |= UNIXFILE_WARNED; + return; + } +} + +/* +** This function performs the parts of the "close file" operation +** common to all locking schemes. It closes the directory and file +** handles, if they are valid, and sets all fields of the rttFile +** structure to 0. +** +** It is *not* necessary to hold the mutex when this routine is called, +** even on VxWorks. A mutex will be acquired on VxWorks by the +** vxworksReleaseFileId() routine. +*/ +static int closeRttFile(sqlite3_file *id){ + rttFile *pFile = (rttFile*)id; + if( pFile->h>=0 ){ + robust_close(pFile, pFile->h, __LINE__); + pFile->h = -1; + } + OSTRACE(("CLOSE %-3d\n", pFile->h)); + OpenCounter(-1); + memset(pFile, 0, sizeof(rttFile)); + return SQLITE_OK; +} + +/************** End of the posix advisory lock implementation ***************** +******************************************************************************/ + +/****************************************************************************** +****************************** No-op Locking ********************************** +** +** Of the various locking implementations available, this is by far the +** simplest: locking is ignored. No attempt is made to lock the database +** file for reading or writing. +** +** This locking mode is appropriate for use on read-only databases +** (ex: databases that are burned into CD-ROM, for example.) It can +** also be used if the application employs some external mechanism to +** prevent simultaneous access of the same database by two or more +** database connections. But there is a serious risk of database +** corruption if this locking mode is used in situations where multiple +** database connections are accessing the same database file at the same +** time and one or more of those connections are writing. +*/ + +static int nolockCheckReservedLock(sqlite3_file *NotUsed, int *pResOut){ + UNUSED_PARAMETER(NotUsed); + *pResOut = 0; + return SQLITE_OK; +} +static int nolockLock(sqlite3_file *NotUsed, int NotUsed2){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + return SQLITE_OK; +} +static int nolockUnlock(sqlite3_file *NotUsed, int NotUsed2){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + return SQLITE_OK; +} + +/* +** Close the file. +*/ +static int nolockClose(sqlite3_file *id) { + return closeRttFile(id); +} + +/******************* End of the no-op lock implementation ********************* +******************************************************************************/ + +/****************************************************************************** +************************* Begin dot-file Locking ****************************** +** +** The dotfile locking implementation uses the existence of separate lock +** files (really a directory) to control access to the database. This works +** on just about every filesystem imaginable. But there are serious downsides: +** +** (1) There is zero concurrency. A single reader blocks all other +** connections from reading or writing the database. +** +** (2) An application crash or power loss can leave stale lock files +** sitting around that need to be cleared manually. +** +** Nevertheless, a dotlock is an appropriate locking mode for use if no +** other locking strategy is available. +** +** Dotfile locking works by creating a subdirectory in the same directory as +** the database and with the same name but with a ".lock" extension added. +** The existence of a lock directory implies an EXCLUSIVE lock. All other +** lock types (SHARED, RESERVED, PENDING) are mapped into EXCLUSIVE. +*/ + +/* +** The file suffix added to the data base filename in order to create the +** lock directory. +*/ +#define DOTLOCK_SUFFIX ".lock" + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +** +** In dotfile locking, either a lock exists or it does not. So in this +** variation of CheckReservedLock(), *pResOut is set to true if any lock +** is held on the file and false if the file is unlocked. +*/ +static int dotlockCheckReservedLock(sqlite3_file *id, int *pResOut) { + int rc = SQLITE_OK; + int reserved = 0; + rttFile *pFile = (rttFile*)id; + + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); + + assert( pFile ); + + /* Check if a thread in this process holds such a lock */ + if( pFile->eFileLock>SHARED_LOCK ){ + /* Either this connection or some other connection in the same process + ** holds a lock on the file. No need to check further. */ + reserved = 1; + }else{ + /* The lock is held if and only if the lockfile exists */ + const char *zLockFile = (const char*)pFile->lockingContext; + reserved = 0; + } + OSTRACE(("TEST WR-LOCK %d %d %d (dotlock)\n", pFile->h, rc, reserved)); + *pResOut = reserved; + return rc; +} + +/* +** Lock the file with the lock specified by parameter eFileLock - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +** +** With dotfile locking, we really only support state (4): EXCLUSIVE. +** But we track the other locking levels internally. +*/ +static int dotlockLock(sqlite3_file *id, int eFileLock) { + rttFile *pFile = (rttFile*)id; + char *zLockFile = (char *)pFile->lockingContext; + int rc = SQLITE_OK; + + + /* If we have any lock, then the lock file already exists. All we have + ** to do is adjust our internal record of the lock level. + */ + if( pFile->eFileLock > NO_LOCK ){ + pFile->eFileLock = eFileLock; + return SQLITE_OK; + } + + /* grab an exclusive lock */ + rc = osMkdir(zLockFile, 0777); + if( rc<0 ){ + /* failed to open/create the lock directory */ + int tErrno = errno; + if( EEXIST == tErrno ){ + rc = SQLITE_BUSY; + } else { + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + } + return rc; + } + + /* got it, set the type and return ok */ + pFile->eFileLock = eFileLock; + return rc; +} + +/* +** Lower the locking level on file descriptor pFile to eFileLock. eFileLock +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +** +** When the locking level reaches NO_LOCK, delete the lock file. +*/ +static int dotlockUnlock(sqlite3_file *id, int eFileLock) { + rttFile *pFile = (rttFile*)id; + char *zLockFile = (char *)pFile->lockingContext; + int rc; + + assert( pFile ); + OSTRACE(("UNLOCK %d %d was %d tnm=%s (dotlock)\n", pFile->h, eFileLock, + pFile->eFileLock, rt_thread_self()->name )); + assert( eFileLock<=SHARED_LOCK ); + + /* no-op if possible */ + if( pFile->eFileLock==eFileLock ){ + return SQLITE_OK; + } + + /* To downgrade to shared, simply update our internal notion of the + ** lock state. No need to mess with the file on disk. + */ + if( eFileLock==SHARED_LOCK ){ + pFile->eFileLock = SHARED_LOCK; + return SQLITE_OK; + } + + /* To fully unlock the database, delete the lock file */ + assert( eFileLock==NO_LOCK ); + rc = osRmdir(zLockFile); + if( rc<0 && errno==ENOTDIR ) rc = osUnlink(zLockFile); + if( rc<0 ){ + int tErrno = errno; + rc = 0; + if( ENOENT != tErrno ){ + rc = SQLITE_IOERR_UNLOCK; + } + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + return rc; + } + pFile->eFileLock = NO_LOCK; + return SQLITE_OK; +} + +/* +** Close a file. Make sure the lock has been released before closing. +*/ +static int dotlockClose(sqlite3_file *id) { + int rc = SQLITE_OK; + if( id ){ + rttFile *pFile = (rttFile*)id; + dotlockUnlock(id, NO_LOCK); + sqlite3_free(pFile->lockingContext); + rc = closeRttFile(id); + } + return rc; +} +/****************** End of the dot-file lock implementation ******************* +******************************************************************************/ + +/****************************************************************************** +************************** Begin flock Locking ******************************** +** +** Use the flock() system call to do file locking. +** +** flock() locking is like dot-file locking in that the various +** fine-grain locking levels supported by SQLite are collapsed into +** a single exclusive lock. In other words, SHARED, RESERVED, and +** PENDING locks are the same thing as an EXCLUSIVE lock. SQLite +** still works when you do this, but concurrency is reduced since +** only a single process can be reading the database at a time. +** +** Omit this section if SQLITE_ENABLE_LOCKING_STYLE is turned off or if +** compiling for VXWORKS. +*/ +#if SQLITE_ENABLE_LOCKING_STYLE +#warning "rtt file lock not available" +/* +** Retry flock() calls that fail with EINTR +*/ +static int robust_flock(int fd, int op){ + int rc = 0; + + return rc; +} + +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to SQLITE_OK unless an I/O error occurs during lock checking. +*/ +static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){ + int rc = SQLITE_OK; + int reserved = 0; + rttFile *pFile = (rttFile*)id; + + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); + + assert( pFile ); + + /* Check if a thread in this process holds such a lock */ + if( pFile->eFileLock>SHARED_LOCK ){ + reserved = 1; + } + + /* Otherwise see if some other process holds it. */ + if( !reserved ){ + /* attempt to get the lock */ + int lrc = robust_flock(pFile->h, LOCK_EX | LOCK_NB); + if( !lrc ){ + /* got the lock, unlock it */ + lrc = robust_flock(pFile->h, LOCK_UN); + if ( lrc ) { + int tErrno = errno; + /* unlock failed with an error */ + lrc = SQLITE_IOERR_UNLOCK; + if( IS_LOCK_ERROR(lrc) ){ + pFile->lastErrno = tErrno; + rc = lrc; + } + } + } else { + int tErrno = errno; + reserved = 1; + /* someone else might have it reserved */ + lrc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); + if( IS_LOCK_ERROR(lrc) ){ + pFile->lastErrno = tErrno; + rc = lrc; + } + } + } + OSTRACE(("TEST WR-LOCK %d %d %d (flock)\n", pFile->h, rc, reserved)); + +#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS + if( (rc & SQLITE_IOERR) == SQLITE_IOERR ){ + rc = SQLITE_OK; + reserved=1; + } +#endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */ + *pResOut = reserved; + return rc; +} + +/* +** Lock the file with the lock specified by parameter eFileLock - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** flock() only really support EXCLUSIVE locks. We track intermediate +** lock states in the sqlite3_file structure, but all locks SHARED or +** above are really EXCLUSIVE locks and exclude all other processes from +** access the file. +** +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. +*/ +static int flockLock(sqlite3_file *id, int eFileLock) { + int rc = SQLITE_OK; + rttFile *pFile = (rttFile*)id; + + assert( pFile ); + + /* if we already have a lock, it is exclusive. + ** Just adjust level and punt on outta here. */ + if (pFile->eFileLock > NO_LOCK) { + pFile->eFileLock = eFileLock; + return SQLITE_OK; + } + + /* grab an exclusive lock */ + + if (robust_flock(pFile->h, LOCK_EX | LOCK_NB)) { + int tErrno = errno; + /* didn't get, must be busy */ + rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + } else { + /* got it, set the type and return ok */ + pFile->eFileLock = eFileLock; + } + OSTRACE(("LOCK %d %s %s (flock)\n", pFile->h, azFileLock(eFileLock), + rc==SQLITE_OK ? "ok" : "failed")); +#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS + if( (rc & SQLITE_IOERR) == SQLITE_IOERR ){ + rc = SQLITE_BUSY; + } +#endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */ + return rc; +} + + +/* +** Lower the locking level on file descriptor pFile to eFileLock. eFileLock +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +*/ +static int flockUnlock(sqlite3_file *id, int eFileLock) { + rttFile *pFile = (rttFile*)id; + + assert( pFile ); + OSTRACE(("UNLOCK %d %d was %d tnm=%s (flock)\n", pFile->h, eFileLock, + pFile->eFileLock, rt_thread_self()->name)); + assert( eFileLock<=SHARED_LOCK ); + + /* no-op if possible */ + if( pFile->eFileLock==eFileLock ){ + return SQLITE_OK; + } + + /* shared can just be set because we always have an exclusive */ + if (eFileLock==SHARED_LOCK) { + pFile->eFileLock = eFileLock; + return SQLITE_OK; + } + + /* no, really, unlock. */ + if( robust_flock(pFile->h, LOCK_UN) ){ +#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS + return SQLITE_OK; +#endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */ + return SQLITE_IOERR_UNLOCK; + }else{ + pFile->eFileLock = NO_LOCK; + return SQLITE_OK; + } +} + +/* +** Close a file. +*/ +static int flockClose(sqlite3_file *id) { + int rc = SQLITE_OK; + if( id ){ + flockUnlock(id, NO_LOCK); + rc = closeRttFile(id); + } + return rc; +} + +#endif /* SQLITE_ENABLE_LOCKING_STYLE && !OS_VXWORK */ + +/******************* End of the flock lock implementation ********************* +******************************************************************************/ + +/****************************************************************************** +**************** Non-locking sqlite3_file methods ***************************** +** +** The next division contains implementations for all methods of the +** sqlite3_file object other than the locking methods. The locking +** methods were defined in divisions above (one locking method per +** division). Those methods that are common to all locking modes +** are gather together into this division. +*/ + +/* +** Seek to the offset passed as the second argument, then read cnt +** bytes into pBuf. Return the number of bytes actually read. +** +** NB: If you define USE_PREAD or USE_PREAD64, then it might also +** be necessary to define _XOPEN_SOURCE to be 500. This varies from +** one system to another. Since SQLite does not define USE_PREAD +** any any form by default, we will not attempt to define _XOPEN_SOURCE. +** See tickets #2741 and #2681. +** +** To avoid stomping the errno value on a failed read the lastErrno value +** is set before returning. +*/ +static int seekAndRead(rttFile *id, sqlite3_int64 offset, void *pBuf, int cnt){ + int got; + int prior = 0; +#if (!defined(USE_PREAD) && !defined(USE_PREAD64)) + i64 newOffset; +#endif + TIMER_START; + assert( cnt==(cnt&0x1ffff) ); + assert( id->h>2 ); + cnt &= 0x1ffff; + do{ +#if defined(USE_PREAD) + #error "rtt pread not support" + got = osPread(id->h, pBuf, cnt, offset); + SimulateIOError( got = -1 ); +#elif defined(USE_PREAD64) + #error "rtt pread64 not support" + got = osPread64(id->h, pBuf, cnt, offset); + SimulateIOError( got = -1 ); +#else + newOffset = lseek(id->h, offset, SEEK_SET); + SimulateIOError( newOffset-- ); + if( newOffset!=offset ){ + if( newOffset == -1 ){ + ((rttFile*)id)->lastErrno = errno; + }else{ + ((rttFile*)id)->lastErrno = 0; + } + return -1; + } + got = osRead(id->h, pBuf, cnt); +#endif + if( got==cnt ) break; + if( got<0 ){ + if( errno==EINTR ){ got = 1; continue; } + prior = 0; + ((rttFile*)id)->lastErrno = errno; + break; + }else if( got>0 ){ + cnt -= got; + offset += got; + prior += got; + pBuf = (void*)(got + (char*)pBuf); + } + }while( got>0 ); + TIMER_END; + OSTRACE(("READ %-3d %5d %7lld %llu\n", + id->h, got+prior, offset-prior, TIMER_ELAPSED)); + return got+prior; +} + +/* +** Read data from a file into a buffer. Return SQLITE_OK if all +** bytes were read successfully and SQLITE_IOERR if anything goes +** wrong. +*/ +static int rttRead( + sqlite3_file *id, + void *pBuf, + int amt, + sqlite3_int64 offset +){ + rttFile *pFile = (rttFile *)id; + int got; + assert( id ); + assert( offset>=0 ); + assert( amt>0 ); + + got = seekAndRead(pFile, offset, pBuf, amt); + if( got==amt ){ + return SQLITE_OK; + }else if( got<0 ){ + /* lastErrno set by seekAndRead */ + return SQLITE_IOERR_READ; + }else{ + pFile->lastErrno = 0; /* not a system error */ + /* Unread parts of the buffer must be zero-filled */ + memset(&((char*)pBuf)[got], 0, amt-got); + return SQLITE_IOERR_SHORT_READ; + } +} + +/* +** Attempt to seek the file-descriptor passed as the first argument to +** absolute offset iOff, then attempt to write nBuf bytes of data from +** pBuf to it. If an error occurs, return -1 and set *piErrno. Otherwise, +** return the actual number of bytes written (which may be less than +** nBuf). +*/ +static int seekAndWriteFd( + int fd, /* File descriptor to write to */ + i64 iOff, /* File offset to begin writing at */ + const void *pBuf, /* Copy data from this buffer to the file */ + int nBuf, /* Size of buffer pBuf in bytes */ + int *piErrno /* OUT: Error number if error occurs */ +){ + int rc = 0; /* Value returned by system call */ + + assert( nBuf==(nBuf&0x1ffff) ); + assert( fd>2 ); + nBuf &= 0x1ffff; + TIMER_START; + +#if defined(USE_PREAD) + do{ rc = osPwrite(fd, pBuf, nBuf, iOff); }while( rc<0 && errno==EINTR ); +#elif defined(USE_PREAD64) + do{ rc = osPwrite64(fd, pBuf, nBuf, iOff);}while( rc<0 && errno==EINTR); +#else + do{ + i64 iSeek = lseek(fd, iOff, SEEK_SET); + SimulateIOError( iSeek-- ); + + if( iSeek!=iOff ){ + if( piErrno ) *piErrno = (iSeek==-1 ? errno : 0); + return -1; + } + rc = osWrite(fd, pBuf, nBuf); + }while( rc<0 && errno==EINTR ); +#endif + + TIMER_END; + OSTRACE(("WRITE %-3d %5d %7lld %llu\n", fd, rc, iOff, TIMER_ELAPSED)); + + if( rc<0 && piErrno ) *piErrno = errno; + return rc; +} + + +/* +** Seek to the offset in id->offset then read cnt bytes into pBuf. +** Return the number of bytes actually read. Update the offset. +** +** To avoid stomping the errno value on a failed write the lastErrno value +** is set before returning. +*/ +static int seekAndWrite(rttFile *id, i64 offset, const void *pBuf, int cnt){ + return seekAndWriteFd(id->h, offset, pBuf, cnt, &id->lastErrno); +} + + +/* +** Write data from a buffer into a file. Return SQLITE_OK on success +** or some other error code on failure. +*/ +static int rttWrite( + sqlite3_file *id, + const void *pBuf, + int amt, + sqlite3_int64 offset +){ + rttFile *pFile = (rttFile*)id; + int wrote = 0; + assert( id ); + assert( amt>0 ); + +#ifdef SQLITE_DEBUG + /* If we are doing a normal write to a database file (as opposed to + ** doing a hot-journal rollback or a write to some file other than a + ** normal database file) then record the fact that the database + ** has changed. If the transaction counter is modified, record that + ** fact too. + */ + if( pFile->inNormalWrite ){ + pFile->dbUpdate = 1; /* The database has been modified */ + if( offset<=24 && offset+amt>=27 ){ + int rc; + char oldCntr[4]; + SimulateIOErrorBenign(1); + rc = seekAndRead(pFile, 24, oldCntr, 4); + SimulateIOErrorBenign(0); + if( rc!=4 || memcmp(oldCntr, &((char*)pBuf)[24-offset], 4)!=0 ){ + pFile->transCntrChng = 1; /* The transaction counter has changed */ + } + } + } +#endif + + while( amt>0 && (wrote = seekAndWrite(pFile, offset, pBuf, amt))>0 ){ + amt -= wrote; + offset += wrote; + pBuf = &((char*)pBuf)[wrote]; + } + SimulateIOError(( wrote=(-1), amt=1 )); + SimulateDiskfullError(( wrote=0, amt=1 )); + + if( amt>0 ){ + if( wrote<0 && pFile->lastErrno!=ENOSPC ){ + /* lastErrno set by seekAndWrite */ + return SQLITE_IOERR_WRITE; + }else{ + pFile->lastErrno = 0; /* not a system error */ + return SQLITE_FULL; + } + } + + return SQLITE_OK; +} + +#ifdef SQLITE_TEST +/* +** Count the number of fullsyncs and normal syncs. This is used to test +** that syncs and fullsyncs are occurring at the right times. +*/ +SQLITE_API int sqlite3_sync_count = 0; +SQLITE_API int sqlite3_fullsync_count = 0; +#endif + +/* +** We do not trust systems to provide a working fdatasync(). Some do. +** Others do no. To be safe, we will stick with the (slightly slower) +** fsync(). If you know that your system does support fdatasync() correctly, +** then simply compile with -Dfdatasync=fdatasync +*/ +#if !defined(fdatasync) +#include "dfs.h" +#include "dfs_file.h" +int fdatasync(fd) +{ + struct dfs_fd *dfs_fd; + dfs_fd = fd_get(fd); + return dfs_file_flush(dfs_fd); +} +#endif + +/* +** Define HAVE_FULLFSYNC to 0 or 1 depending on whether or not +** the F_FULLFSYNC macro is defined. F_FULLFSYNC is currently +** only available on Mac OS X. But that could change. +*/ +#ifdef F_FULLFSYNC +# define HAVE_FULLFSYNC 0 +#endif + + +/* +** The fsync() system call does not work as advertised on many +** unix systems. The following procedure is an attempt to make +** it work better. +** +** The SQLITE_NO_SYNC macro disables all fsync()s. This is useful +** for testing when we want to run through the test suite quickly. +** You are strongly advised *not* to deploy with SQLITE_NO_SYNC +** enabled, however, since with SQLITE_NO_SYNC enabled, an OS crash +** or power failure will likely corrupt the database file. +** +** SQLite sets the dataOnly flag if the size of the file is unchanged. +** The idea behind dataOnly is that it should only write the file content +** to disk, not the inode. We only set dataOnly if the file size is +** unchanged since the file size is part of the inode. However, +** Ted Ts'o tells us that fdatasync() will also write the inode if the +** file size has changed. The only real difference between fdatasync() +** and fsync(), Ted tells us, is that fdatasync() will not flush the +** inode if the mtime or owner or other inode attributes have changed. +** We only care about the file size, not the other file attributes, so +** as far as SQLite is concerned, an fdatasync() is always adequate. +** So, we always use fdatasync() if it is available, regardless of +** the value of the dataOnly flag. +*/ +static int full_fsync(int fd, int fullSync, int dataOnly){ + int rc; + + /* The following "ifdef/elif/else/" block has the same structure as + ** the one below. It is replicated here solely to avoid cluttering + ** up the real code with the UNUSED_PARAMETER() macros. + */ +#ifdef SQLITE_NO_SYNC + UNUSED_PARAMETER(fd); + UNUSED_PARAMETER(fullSync); + UNUSED_PARAMETER(dataOnly); +#elif HAVE_FULLFSYNC + UNUSED_PARAMETER(dataOnly); +#else + UNUSED_PARAMETER(fullSync); + UNUSED_PARAMETER(dataOnly); +#endif + + /* Record the number of times that we do a normal fsync() and + ** FULLSYNC. This is used during testing to verify that this procedure + ** gets called with the correct arguments. + */ +#ifdef SQLITE_TEST + if( fullSync ) sqlite3_fullsync_count++; + sqlite3_sync_count++; +#endif + + /* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a + ** no-op + */ +#ifdef SQLITE_NO_SYNC + rc = SQLITE_OK; +#elif HAVE_FULLFSYNC + #error "rtt not support FULLFSYNC" +#else + rc = fdatasync(fd); +#endif /* ifdef SQLITE_NO_SYNC elif HAVE_FULLFSYNC */ + + return rc; +} + +/* +** Make sure all writes to a particular file are committed to disk. +** +** If dataOnly==0 then both the file itself and its metadata (file +** size, access time, etc) are synced. If dataOnly!=0 then only the +** file data is synced. +** +** Under Rtt, also make sure that the directory entry for the file +** has been created by fsync-ing the directory that contains the file. +** If we do not do this and we encounter a power failure, the directory +** entry for the journal might not exist after we reboot. The next +** SQLite to access the file will not know that the journal exists (because +** the directory entry for the journal was never created) and the transaction +** will not roll back - possibly leading to database corruption. +*/ +static int rttSync(sqlite3_file *id, int flags){ + int rc; + rttFile *pFile = (rttFile*)id; + + int isDataOnly = (flags&SQLITE_SYNC_DATAONLY); + int isFullsync = (flags&0x0F)==SQLITE_SYNC_FULL; + + /* Check that one of SQLITE_SYNC_NORMAL or FULL was passed */ + assert((flags&0x0F)==SQLITE_SYNC_NORMAL + || (flags&0x0F)==SQLITE_SYNC_FULL + ); + + /* Rtt cannot, but some systems may return SQLITE_FULL from here. This + ** line is to test that doing so does not cause any problems. + */ + SimulateDiskfullError( return SQLITE_FULL ); + + assert( pFile ); + OSTRACE(("SYNC %-3d\n", pFile->h)); + rc = full_fsync(pFile->h, isFullsync, isDataOnly); + SimulateIOError( rc=1 ); + if( rc ){ + pFile->lastErrno = errno; + return rttLogError(SQLITE_IOERR_FSYNC, "full_fsync", pFile->zPath); + } + + /* Also fsync the directory containing the file if the DIRSYNC flag + ** is set. This is a one-time occurrence. Many systems (examples: AIX) + ** are unable to fsync a directory, so ignore errors on the fsync. + */ + if( pFile->ctrlFlags & UNIXFILE_DIRSYNC ){ + int dirfd; + OSTRACE(("DIRSYNC %s (have_fullfsync=%d fullsync=%d)\n", pFile->zPath, + HAVE_FULLFSYNC, isFullsync)); + rc = osOpenDirectory(pFile->zPath, &dirfd); + if( rc==SQLITE_OK && dirfd>=0 ){ + full_fsync(dirfd, 0, 0); + robust_close(pFile, dirfd, __LINE__); + }else if( rc==SQLITE_CANTOPEN ){ + rc = SQLITE_OK; + } + pFile->ctrlFlags &= ~UNIXFILE_DIRSYNC; + } + return rc; +} + +/* +** Truncate an open file to a specified size +*/ +static int rttTruncate(sqlite3_file *id, i64 nByte){ + rttFile *pFile = (rttFile *)id; + int rc; + assert( pFile ); + SimulateIOError( return SQLITE_IOERR_TRUNCATE ); + + /* If the user has configured a chunk-size for this file, truncate the + ** file so that it consists of an integer number of chunks (i.e. the + ** actual file size after the operation may be larger than the requested + ** size). + */ + if( pFile->szChunk>0 ){ + nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk; + } + + rc = robust_ftruncate(pFile->h, (off_t)nByte); + if( rc ){ + pFile->lastErrno = errno; + return rttLogError(SQLITE_IOERR_TRUNCATE, "ftruncate", pFile->zPath); + }else{ +#ifdef SQLITE_DEBUG + /* If we are doing a normal write to a database file (as opposed to + ** doing a hot-journal rollback or a write to some file other than a + ** normal database file) and we truncate the file to zero length, + ** that effectively updates the change counter. This might happen + ** when restoring a database using the backup API from a zero-length + ** source. + */ + if( pFile->inNormalWrite && nByte==0 ){ + pFile->transCntrChng = 1; + } +#endif + + return SQLITE_OK; + } +} + +/* +** Determine the current size of a file in bytes +*/ +static int rttFileSize(sqlite3_file *id, i64 *pSize){ + int rc; + struct stat buf; + assert( id ); + rc = osFstat(((rttFile*)id)->h, &buf); + SimulateIOError( rc=1 ); + if( rc!=0 ){ + ((rttFile*)id)->lastErrno = errno; + return SQLITE_IOERR_FSTAT; + } + *pSize = buf.st_size; + + /* When opening a zero-size database, the findInodeInfo() procedure + ** writes a single byte into that file in order to work around a bug + ** in the OS-X msdos filesystem. In order to avoid problems with upper + ** layers, we need to report this file size as zero even though it is + ** really 1. Ticket #3260. + */ + if( *pSize==1 ) *pSize = 0; + + + return SQLITE_OK; +} + +/* +** This function is called to handle the SQLITE_FCNTL_SIZE_HINT +** file-control operation. Enlarge the database to nBytes in size +** (rounded up to the next chunk-size). If the database is already +** nBytes or larger, this routine is a no-op. +*/ +static int fcntlSizeHint(rttFile *pFile, i64 nByte){ + if( pFile->szChunk>0 ){ + i64 nSize; /* Required file size */ + struct stat buf; /* Used to hold return values of fstat() */ + + if( osFstat(pFile->h, &buf) ) return SQLITE_IOERR_FSTAT; + + nSize = ((nByte+pFile->szChunk-1) / pFile->szChunk) * pFile->szChunk; + if( nSize>(i64)buf.st_size ){ + /* If the OS does not have posix_fallocate(), fake it. First use + ** ftruncate() to set the file size, then write a single byte to + ** the last byte in each block within the extended region. This + ** is the same technique used by glibc to implement posix_fallocate() + ** on systems that do not have a real fallocate() system call. + */ + int nBlk = buf.st_blksize; /* File-system block size */ + i64 iWrite; /* Next offset to write to */ + + if( robust_ftruncate(pFile->h, nSize) ){ + pFile->lastErrno = errno; + return rttLogError(SQLITE_IOERR_TRUNCATE, "ftruncate", pFile->zPath); + } + iWrite = ((buf.st_size + 2*nBlk - 1)/nBlk)*nBlk-1; + while( iWritectrlFlags is set. +** +** If *pArg is 0 or 1, then clear or set the mask bit of pFile->ctrlFlags. +*/ +static void rttModeBit(rttFile *pFile, unsigned char mask, int *pArg){ + if( *pArg<0 ){ + *pArg = (pFile->ctrlFlags & mask)!=0; + }else if( (*pArg)==0 ){ + pFile->ctrlFlags &= ~mask; + }else{ + pFile->ctrlFlags |= mask; + } +} + +/* Forward declaration */ +static int rttGetTempname(int nBuf, char *zBuf); + +/* +** Information and control of an open file handle. +*/ +static int rttFileControl(sqlite3_file *id, int op, void *pArg){ + rttFile *pFile = (rttFile*)id; + switch( op ){ + case SQLITE_FCNTL_LOCKSTATE: { + *(int*)pArg = pFile->eFileLock; + return SQLITE_OK; + } + case SQLITE_LAST_ERRNO: { + *(int*)pArg = pFile->lastErrno; + return SQLITE_OK; + } + case SQLITE_FCNTL_CHUNK_SIZE: { + pFile->szChunk = *(int *)pArg; + return SQLITE_OK; + } + case SQLITE_FCNTL_SIZE_HINT: { + int rc; + SimulateIOErrorBenign(1); + rc = fcntlSizeHint(pFile, *(i64 *)pArg); + SimulateIOErrorBenign(0); + return rc; + } + case SQLITE_FCNTL_PERSIST_WAL: { + rttModeBit(pFile, UNIXFILE_PERSIST_WAL, (int*)pArg); + return SQLITE_OK; + } + case SQLITE_FCNTL_POWERSAFE_OVERWRITE: { + rttModeBit(pFile, UNIXFILE_PSOW, (int*)pArg); + return SQLITE_OK; + } + case SQLITE_FCNTL_VFSNAME: { + *(char**)pArg = sqlite3_mprintf("%s", pFile->pVfs->zName); + return SQLITE_OK; + } + case SQLITE_FCNTL_TEMPFILENAME: { + char *zTFile = sqlite3_malloc( pFile->pVfs->mxPathname ); + if( zTFile ){ + rttGetTempname(pFile->pVfs->mxPathname, zTFile); + *(char**)pArg = zTFile; + } + return SQLITE_OK; + } +#ifdef SQLITE_DEBUG + /* The pager calls this method to signal that it has done + ** a rollback and that the database is therefore unchanged and + ** it hence it is OK for the transaction change counter to be + ** unchanged. + */ + case SQLITE_FCNTL_DB_UNCHANGED: { + ((rttFile*)id)->dbUpdate = 0; + return SQLITE_OK; + } +#endif + } + return SQLITE_NOTFOUND; +} + +/* +** Return the sector size in bytes of the underlying block device for +** the specified file. This is almost always 512 bytes, but may be +** larger for some devices. +** +** SQLite code assumes this function cannot fail. It also assumes that +** if two files are created in the same file-system directory (i.e. +** a database and its journal file) that the sector size will be the +** same for both. +*/ +static int rttSectorSize(sqlite3_file *NotUsed){ + UNUSED_PARAMETER(NotUsed); + return SQLITE_DEFAULT_SECTOR_SIZE; +} + + +/* +** Return the device characteristics for the file. +** +** This VFS is set up to return SQLITE_IOCAP_POWERSAFE_OVERWRITE by default. +** However, that choice is contraversial since technically the underlying +** file system does not always provide powersafe overwrites. (In other +** words, after a power-loss event, parts of the file that were never +** written might end up being altered.) However, non-PSOW behavior is very, +** very rare. And asserting PSOW makes a large reduction in the amount +** of required I/O for journaling, since a lot of padding is eliminated. +** Hence, while POWERSAFE_OVERWRITE is on by default, there is a file-control +** available to turn it off and URI query parameter available to turn it off. +*/ +static int rttDeviceCharacteristics(sqlite3_file *id){ + rttFile *p = (rttFile*)id; + int rc = 0; + + if( p->ctrlFlags & UNIXFILE_PSOW ){ + rc |= SQLITE_IOCAP_POWERSAFE_OVERWRITE; + } + return rc; +} + +#ifndef SQLITE_OMIT_WAL +# error "WAL mode requires not support from the rt-thread, compile\ + with SQLITE_OMIT_WAL." +#else +# define rttShmMap 0 +# define rttShmLock 0 +# define rttShmBarrier 0 +# define rttShmUnmap 0 +#endif /* #ifndef SQLITE_OMIT_WAL */ + +#if SQLITE_MAX_MMAP_SIZE>0 +#error "rtt not spportt mmap" +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + +/* +** If possible, return a pointer to a mapping of file fd starting at offset +** iOff. The mapping must be valid for at least nAmt bytes. +** +** If such a pointer can be obtained, store it in *pp and return SQLITE_OK. +** Or, if one cannot but no error occurs, set *pp to 0 and return SQLITE_OK. +** Finally, if an error does occur, return an SQLite error code. The final +** value of *pp is undefined in this case. +** +** If this function does return a pointer, the caller must eventually +** release the reference by calling unixUnfetch(). +*/ +static int rttFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ + + *pp = 0; + + return SQLITE_OK; +} + +/* +** If the third argument is non-NULL, then this function releases a +** reference obtained by an earlier call to unixFetch(). The second +** argument passed to this function must be the same as the corresponding +** argument that was passed to the unixFetch() invocation. +** +** Or, if the third argument is NULL, then this function is being called +** to inform the VFS layer that, according to POSIX, any existing mapping +** may now be invalid and should be unmapped. +*/ +static int rttUnfetch(sqlite3_file *fd, i64 iOff, void *p){ + rttFile *pFd = (rttFile *)fd; /* The underlying database file */ + UNUSED_PARAMETER(iOff); + + return SQLITE_OK; +} + +/* +** Here ends the implementation of all sqlite3_file methods. +** +********************** End sqlite3_file Methods ******************************* +******************************************************************************/ + +/* +** This division contains definitions of sqlite3_io_methods objects that +** implement various file locking strategies. It also contains definitions +** of "finder" functions. A finder-function is used to locate the appropriate +** sqlite3_io_methods object for a particular database file. The pAppData +** field of the sqlite3_vfs VFS objects are initialized to be pointers to +** the correct finder-function for that VFS. +** +** Most finder functions return a pointer to a fixed sqlite3_io_methods +** object. The only interesting finder-function is autolockIoFinder, which +** looks at the filesystem type and tries to guess the best locking +** strategy from that. +** +** For finder-funtion F, two objects are created: +** +** (1) The real finder-function named "FImpt()". +** +** (2) A constant pointer to this function named just "F". +** +** +** A pointer to the F pointer is used as the pAppData value for VFS +** objects. We have to do this instead of letting pAppData point +** directly at the finder-function since C90 rules prevent a void* +** from be cast into a function pointer. +** +** +** Each instance of this macro generates two objects: +** +** * A constant sqlite3_io_methods object call METHOD that has locking +** methods CLOSE, LOCK, UNLOCK, CKRESLOCK. +** +** * An I/O method finder function called FINDER that returns a pointer +** to the METHOD object in the previous bullet. +*/ +#define IOMETHODS(FINDER, METHOD, VERSION, CLOSE, LOCK, UNLOCK, CKLOCK) \ +static const sqlite3_io_methods METHOD = { \ + VERSION, /* iVersion */ \ + CLOSE, /* xClose */ \ + rttRead, /* xRead */ \ + rttWrite, /* xWrite */ \ + rttTruncate, /* xTruncate */ \ + rttSync, /* xSync */ \ + rttFileSize, /* xFileSize */ \ + LOCK, /* xLock */ \ + UNLOCK, /* xUnlock */ \ + CKLOCK, /* xCheckReservedLock */ \ + rttFileControl, /* xFileControl */ \ + rttSectorSize, /* xSectorSize */ \ + rttDeviceCharacteristics, /* xDeviceCapabilities */ \ + rttShmMap, /* xShmMap */ \ + rttShmLock, /* xShmLock */ \ + rttShmBarrier, /* xShmBarrier */ \ + rttShmUnmap, /* xShmUnmap */ \ + rttFetch, /* xFetch */ \ + rttUnfetch, /* xUnfetch */ \ +}; \ +static const sqlite3_io_methods *FINDER##Impl(const char *z, rttFile *p){ \ + UNUSED_PARAMETER(z); UNUSED_PARAMETER(p); \ + return &METHOD; \ +} \ +static const sqlite3_io_methods *(*const FINDER)(const char*,rttFile *p) \ + = FINDER##Impl; + +/* +** Here are all of the sqlite3_io_methods objects for each of the +** locking strategies. Functions that return pointers to these methods +** are also created. +*/ +IOMETHODS( + nolockIoFinder, /* Finder function name */ + nolockIoMethods, /* sqlite3_io_methods object name */ + 1, /* shared memory is disabled */ + nolockClose, /* xClose method */ + nolockLock, /* xLock method */ + nolockUnlock, /* xUnlock method */ + nolockCheckReservedLock /* xCheckReservedLock method */ +) +IOMETHODS( + dotlockIoFinder, /* Finder function name */ + dotlockIoMethods, /* sqlite3_io_methods object name */ + 1, /* shared memory is disabled */ + dotlockClose, /* xClose method */ + dotlockLock, /* xLock method */ + dotlockUnlock, /* xUnlock method */ + dotlockCheckReservedLock /* xCheckReservedLock method */ +) + +#if SQLITE_ENABLE_LOCKING_STYLE +IOMETHODS( + flockIoFinder, /* Finder function name */ + flockIoMethods, /* sqlite3_io_methods object name */ + 1, /* shared memory is disabled */ + flockClose, /* xClose method */ + flockLock, /* xLock method */ + flockUnlock, /* xUnlock method */ + flockCheckReservedLock /* xCheckReservedLock method */ +) +#endif + +/* +** An abstract type for a pointer to a IO method finder function: +*/ +typedef const sqlite3_io_methods *(*finder_type)(const char*,rttFile*); + + +/**************************************************************************** +**************************** sqlite3_vfs methods **************************** +** +** This division contains the implementation of methods on the +** sqlite3_vfs object. +*/ + +/* +** Initialize the contents of the rttFile structure pointed to by pId. +*/ +static int fillInRttFile( + sqlite3_vfs *pVfs, /* Pointer to vfs object */ + int h, /* Open file descriptor of file being opened */ + sqlite3_file *pId, /* Write to the rttFile structure here */ + const char *zFilename, /* Name of the file being opened */ + int ctrlFlags /* Zero or more UNIXFILE_* values */ +){ + const sqlite3_io_methods *pLockingStyle; + rttFile *pNew = (rttFile *)pId; + int rc = SQLITE_OK; + + assert( pNew->pInode==NULL ); + + /* Usually the path zFilename should not be a relative pathname. The + ** exception is when opening the proxy "conch" file in builds that + ** include the special Apple locking styles. + */ + assert( zFilename==0 || zFilename[0]=='/' ); + + /* No locking occurs in temporary files */ + assert( zFilename!=0 || (ctrlFlags & UNIXFILE_NOLOCK)!=0 ); + + OSTRACE(("OPEN %-3d %s\n", h, zFilename)); + pNew->h = h; + pNew->pVfs = pVfs; + pNew->zPath = zFilename; + pNew->ctrlFlags = (u8)ctrlFlags; + if( sqlite3_uri_boolean(((ctrlFlags & UNIXFILE_URI) ? zFilename : 0), + "psow", SQLITE_POWERSAFE_OVERWRITE) ){ + pNew->ctrlFlags |= UNIXFILE_PSOW; + } + if( strcmp(pVfs->zName,"unix-excl")==0 ){ + pNew->ctrlFlags |= UNIXFILE_EXCL; + } + + if( ctrlFlags & UNIXFILE_NOLOCK ){ + pLockingStyle = &nolockIoMethods; + }else{ + pLockingStyle = (**(finder_type*)pVfs->pAppData)(zFilename, pNew); +#if SQLITE_ENABLE_LOCKING_STYLE + /* Cache zFilename in the locking context (AFP and dotlock override) for + ** proxyLock activation is possible (remote proxy is based on db name) + ** zFilename remains valid until file is closed, to support */ + pNew->lockingContext = (void*)zFilename; +#endif + } + + if( pLockingStyle == &dotlockIoMethods ){ + /* Dotfile locking uses the file path so it needs to be included in + ** the dotlockLockingContext + */ + char *zLockFile; + int nFilename; + assert( zFilename!=0 ); + nFilename = (int)strlen(zFilename) + 6; + zLockFile = (char *)sqlite3_malloc(nFilename); + if( zLockFile==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_snprintf(nFilename, zLockFile, "%s" DOTLOCK_SUFFIX, zFilename); + } + pNew->lockingContext = zLockFile; + } + + pNew->lastErrno = 0; + if( rc!=SQLITE_OK ){ + if( h>=0 ) robust_close(pNew, h, __LINE__); + }else{ + pNew->pMethod = pLockingStyle; + OpenCounter(+1); + verifyDbFile(pNew); + } + return rc; +} + +/* +** Return the name of a directory in which to put temporary files. +** If no suitable temporary file directory can be found, return NULL. +*/ +static const char *rttTempFileDir(void){ + static const char *azDirs[] = { + 0, + "/sql", + "/sql/tmp" + "/tmp", + 0 /* List terminator */ + }; + unsigned int i; + struct stat buf; + const char *zDir = 0; + + azDirs[0] = sqlite3_temp_directory; + + for(i=0; imxPathname bytes. +*/ +static int rttGetTempname(int nBuf, char *zBuf){ + static const unsigned char zChars[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + unsigned int i, j; + const char *zDir; + + /* It's odd to simulate an io-error here, but really this is just + ** using the io-error infrastructure to test that SQLite handles this + ** function failing. + */ + SimulateIOError( return SQLITE_IOERR ); + + zDir = rttTempFileDir(); + if( zDir==0 ) zDir = "."; + + /* Check that the output buffer is large enough for the temporary file + ** name. If it is not, return SQLITE_ERROR. + */ + if( (strlen(zDir) + strlen(SQLITE_TEMP_FILE_PREFIX) + 18) >= (size_t)nBuf ){ + return SQLITE_ERROR; + } + + do{ + sqlite3_snprintf(nBuf-18, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX, zDir); + j = (int)strlen(zBuf); + sqlite3_randomness(15, &zBuf[j]); + for(i=0; i<15; i++, j++){ + zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; + } + zBuf[j] = 0; + zBuf[j+1] = 0; + }while( osAccess(zBuf,0)==0 ); + return SQLITE_OK; +} + +/* +** Open the file zPath. +** +** Previously, the SQLite OS layer used three functions in place of this +** one: +** +** sqlite3OsOpenReadWrite(); +** sqlite3OsOpenReadOnly(); +** sqlite3OsOpenExclusive(); +** +** These calls correspond to the following combinations of flags: +** +** ReadWrite() -> (READWRITE | CREATE) +** ReadOnly() -> (READONLY) +** OpenExclusive() -> (READWRITE | CREATE | EXCLUSIVE) +** +** The old OpenExclusive() accepted a boolean argument - "delFlag". If +** true, the file was configured to be automatically deleted when the +** file handle closed. To achieve the same effect using this new +** interface, add the DELETEONCLOSE flag to those specified above for +** OpenExclusive(). +*/ +static int rttOpen( + sqlite3_vfs *pVfs, /* The VFS for which this is the xOpen method */ + const char *zPath, /* Pathname of file to be opened */ + sqlite3_file *pFile, /* The file descriptor to be filled in */ + int flags, /* Input flags to control the opening */ + int *pOutFlags /* Output flags returned to SQLite core */ +){ + rttFile *p = (rttFile *)pFile; + int fd = -1; /* File descriptor returned by open() */ + int openFlags = 0; /* Flags to pass to open() */ + int eType = flags&0xFFFFFF00; /* Type of file to open */ + int noLock; /* True to omit locking primitives */ + int rc = SQLITE_OK; /* Function Return Code */ + int ctrlFlags = 0; /* UNIXFILE_* flags */ + + int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE); + int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE); + int isCreate = (flags & SQLITE_OPEN_CREATE); + int isReadonly = (flags & SQLITE_OPEN_READONLY); + int isReadWrite = (flags & SQLITE_OPEN_READWRITE); +#if SQLITE_ENABLE_LOCKING_STYLE + int isAutoProxy = (flags & SQLITE_OPEN_AUTOPROXY); +#endif + + /* If creating a master or main-file journal, this function will open + ** a file-descriptor on the directory too. The first time unixSync() + ** is called the directory file descriptor will be fsync()ed and close()d. + */ + int syncDir = (isCreate && ( + eType==SQLITE_OPEN_MASTER_JOURNAL + || eType==SQLITE_OPEN_MAIN_JOURNAL + || eType==SQLITE_OPEN_WAL + )); + + /* If argument zPath is a NULL pointer, this function is required to open + ** a temporary file. Use this buffer to store the file name in. + */ + char zTmpname[MAX_PATHNAME+2]; + const char *zName = zPath; + + /* Check the following statements are true: + ** + ** (a) Exactly one of the READWRITE and READONLY flags must be set, and + ** (b) if CREATE is set, then READWRITE must also be set, and + ** (c) if EXCLUSIVE is set, then CREATE must also be set. + ** (d) if DELETEONCLOSE is set, then CREATE must also be set. + */ + assert((isReadonly==0 || isReadWrite==0) && (isReadWrite || isReadonly)); + assert(isCreate==0 || isReadWrite); + assert(isExclusive==0 || isCreate); + assert(isDelete==0 || isCreate); + + /* The main DB, main journal, WAL file and master journal are never + ** automatically deleted. Nor are they ever temporary files. */ + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_DB ); + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_JOURNAL ); + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MASTER_JOURNAL ); + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_WAL ); + + /* Assert that the upper layer has set one of the "file-type" flags. */ + assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB + || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL + || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_MASTER_JOURNAL + || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL + ); + + memset(p, 0, sizeof(rttFile)); + if( !zName ){ + /* If zName is NULL, the upper layer is requesting a temp file. */ + assert(isDelete && !syncDir); + rc = rttGetTempname(MAX_PATHNAME+2, zTmpname); + if( rc!=SQLITE_OK ){ + return rc; + } + zName = zTmpname; + + /* Generated temporary filenames are always double-zero terminated + ** for use by sqlite3_uri_parameter(). */ + assert( zName[strlen(zName)+1]==0 ); + } + + /* Determine the value of the flags parameter passed to POSIX function + ** open(). These must be calculated even if open() is not called, as + ** they may be stored as part of the file handle and used by the + ** 'conch file' locking functions later on. */ + if( isReadonly ) openFlags |= O_RDONLY; + if( isReadWrite ) openFlags |= O_RDWR; + if( isCreate ) openFlags |= O_CREAT; + if( isExclusive ) openFlags |= (O_EXCL|0/*O_NOFOLLOW8*/); + openFlags |= (0/*O_LARGEFILE*/|O_BINARY); + + if( fd<0 ){ + mode_t openMode = 0; /* Permissions to create file with */ + + fd = robust_open(zName, openFlags, openMode); + OSTRACE(("OPENX %-3d %s 0%o\n", fd, zName, openFlags)); + if( fd<0 && errno!=EISDIR && isReadWrite && !isExclusive ){ + /* Failed to open the file for read/write access. Try read-only. */ + flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); + openFlags &= ~(O_RDWR|O_CREAT); + flags |= SQLITE_OPEN_READONLY; + openFlags |= O_RDONLY; + isReadonly = 1; + fd = robust_open(zName, openFlags, openMode); + } + if( fd<0 ){ + rc = rttLogError(SQLITE_CANTOPEN_BKPT, "open", zName); + goto open_finished; + } + } + assert( fd>=0 ); + if( pOutFlags ){ + *pOutFlags = flags; + } + + if( isDelete ){ + osUnlink(zName); + } +#if SQLITE_ENABLE_LOCKING_STYLE + else{ + p->openFlags = openFlags; + } +#endif + + noLock = eType!=SQLITE_OPEN_MAIN_DB; + + /* Set up appropriate ctrlFlags */ + if( isDelete ) ctrlFlags |= UNIXFILE_DELETE; + if( isReadonly ) ctrlFlags |= UNIXFILE_RDONLY; + if( noLock ) ctrlFlags |= UNIXFILE_NOLOCK; + if( syncDir ) ctrlFlags |= UNIXFILE_DIRSYNC; + if( flags & SQLITE_OPEN_URI ) ctrlFlags |= UNIXFILE_URI; + + rc = fillInRttFile(pVfs, fd, pFile, zPath, ctrlFlags); + +open_finished: + + return rc; +} + + +/* +** Delete the file at zPath. If the dirSync argument is true, fsync() +** the directory after deleting the file. +*/ +static int rttDelete( + sqlite3_vfs *NotUsed, /* VFS containing this as the xDelete method */ + const char *zPath, /* Name of file to be deleted */ + int dirSync /* If true, fsync() directory after deleting file */ +){ + int rc = SQLITE_OK; + UNUSED_PARAMETER(NotUsed); + SimulateIOError(return SQLITE_IOERR_DELETE); + if( osUnlink(zPath)==(-1) ){ + if( errno==ENOENT ){ + rc = SQLITE_IOERR_DELETE_NOENT; + }else{ + rc = rttLogError(SQLITE_IOERR_DELETE, "unlink", zPath); + } + return rc; + } +#ifndef SQLITE_DISABLE_DIRSYNC + if( (dirSync & 1)!=0 ){ + int fd; + rc = osOpenDirectory(zPath, &fd); + if( rc==SQLITE_OK ){ + robust_close(0, fd, __LINE__); + }else if( rc==SQLITE_CANTOPEN ){ + rc = SQLITE_OK; + } + } +#endif + return rc; +} + +/* +** Test the existence of or access permissions of file zPath. The +** test performed depends on the value of flags: +** +** SQLITE_ACCESS_EXISTS: Return 1 if the file exists +** SQLITE_ACCESS_READWRITE: Return 1 if the file is read and writable. +** SQLITE_ACCESS_READONLY: Return 1 if the file is readable. +** +** Otherwise return 0. +*/ +static int rttAccess( + sqlite3_vfs *NotUsed, /* The VFS containing this xAccess method */ + const char *zPath, /* Path of the file to examine */ + int flags, /* What do we want to learn about the zPath file? */ + int *pResOut /* Write result boolean here */ +){ + int amode = 0; + UNUSED_PARAMETER(NotUsed); + SimulateIOError( return SQLITE_IOERR_ACCESS; ); + switch( flags ){ + case SQLITE_ACCESS_EXISTS: + amode = F_OK; + break; + case SQLITE_ACCESS_READWRITE: + amode = W_OK|R_OK; + break; + case SQLITE_ACCESS_READ: + amode = R_OK; + break; + + default: + assert(!"Invalid flags argument"); + } + *pResOut = (osAccess(zPath, amode)==0); + if( flags==SQLITE_ACCESS_EXISTS && *pResOut ){ + struct stat buf; + if( 0==osStat(zPath, &buf) && buf.st_size==0 ){ + *pResOut = 0; + } + } + return SQLITE_OK; +} + + +/* +** Turn a relative pathname into a full pathname. The relative path +** is stored as a nul-terminated string in the buffer pointed to by +** zPath. +** +** zOut points to a buffer of at least sqlite3_vfs.mxPathname bytes +** (in this case, MAX_PATHNAME bytes). The full-path is written to +** this buffer before returning. +*/ +static int rttFullPathname( + 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 */ +){ + + /* It's odd to simulate an io-error here, but really this is just + ** using the io-error infrastructure to test that SQLite handles this + ** function failing. This function could fail if, for example, the + ** current working directory has been unlinked. + */ + SimulateIOError( return SQLITE_ERROR ); + + assert( pVfs->mxPathname==MAX_PATHNAME ); + UNUSED_PARAMETER(pVfs); + + zOut[nOut-1] = '\0'; + if( zPath[0]=='/' ){ + sqlite3_snprintf(nOut, zOut, "%s", zPath); + }else{ + int nCwd; + if( osGetcwd(zOut, nOut-1)==0 ){ + return rttLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath); + } + nCwd = (int)strlen(zOut); + sqlite3_snprintf(nOut-nCwd, &zOut[nCwd], "/%s", zPath); + } + return SQLITE_OK; +} + + +#ifndef SQLITE_OMIT_LOAD_EXTENSION +# error "rtt not support load extension, compile with SQLITE_OMIT_WAL." +#else /* if SQLITE_OMIT_LOAD_EXTENSION is defined: */ + #define rttDlOpen 0 + #define rttDlError 0 + #define rttDlSym 0 + #define rttDlClose 0 +#endif + +/* +** Write nBuf bytes of random data to the supplied buffer zBuf. +*/ +static int rttRandomness(sqlite3_vfs *NotUsed, int nBuf, char *zBuf){ + UNUSED_PARAMETER(NotUsed); + assert((size_t)nBuf>=(sizeof(time_t)+sizeof(int))); + + /* We have to initialize zBuf to prevent valgrind from reporting + ** errors. The reports issued by valgrind are incorrect - we would + ** prefer that the randomness be increased by making use of the + ** uninitialized space in zBuf - but valgrind errors tend to worry + ** some users. Rather than argue, it seems easier just to initialize + ** the whole array and silence valgrind, even if that means less randomness + ** in the random seed. + ** + ** When testing, initializing zBuf[] to zero is all we do. That means + ** that we always use the same random number sequence. This makes the + ** tests repeatable. + */ + memset(zBuf, 0, nBuf); + { + int i; + char tick8, tick16; + tick8 = (char)rt_tick_get(); + tick16 = (char)(rt_tick_get() >> 8); + + for (i=0; i +#include + +/* +** 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; imutex); + 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; isizeof(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; inUsed + 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; imutex); + 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, ¬null, &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, ¬Used); + 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; ipFunc = 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), " ", 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) ){ + 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), " ", 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), " ", 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 +** +** 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 . 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 " +** +** The and are the two values being compared, encoded in UTF-8. +** The 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), " ", 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 = nKey10 && 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 +** +** 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 . 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 " +** +** Where is one of UTF-8, UTF-16LE or UTF16BE, and 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), " ", 0); +#endif /* SQLITE_OMIT_UTF16 */ + return TCL_ERROR; +} + +/* +** Usage: sqlite3_test_errstr +** +** 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, ""); + } + + 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]) && + 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 of the supplied SQL string using +** database handle . The parameter is the name of a global +** variable that is set to the unused portion of (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 of the supplied SQL string using +** database handle . The parameter is the name of a global +** variable that is set to the unused portion of (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 of the supplied SQL string using +** database handle . The parameter is the name of a global +** variable that is set to the unused portion of (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 of the supplied SQL string using +** database handle . The parameter is the name of a global +** variable that is set to the unused portion of (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 +** +** 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, ""); + 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=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; inDb; 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; izName) ); + 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; ipNext){ + 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]) ){ + 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; ii2 ){ + 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 +#include + +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 +/* +** 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]) ){ + Tcl_AppendResult(interp, "unknown optimization - should be one of:", + (char*)0); + for(i=0; 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 +#include +#include + +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 +#include + +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 +#include +#include +#include +#include + +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=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; ipStmt); i++){ + p->argv[i] = (char*)sqlite3_column_text(p->pStmt, i); + } + for(i=0; iargc; 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 +#include + +/* +** 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 . +** +** 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 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), " ", 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; izName; 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 ?? +** +*/ +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), + " ", 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; iflags&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; iOffiSize; 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; i11 || 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; jmxPathname; + 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 */ diff --git a/components/external/sqlite/test/test7.c b/components/external/sqlite/test/test7.c new file mode 100644 index 0000000000..93bf1e4898 --- /dev/null +++ b/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 +#include +#include +#include +#include + +/* +** 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 ){ + sqlite3_server_stop(); + while( 1 ){ + for(i=0; i=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; ipStmt); i++){ + p->argv[i] = (char*)sqlite3_column_text(p->pStmt, i); + } + for(i=0; iargc; 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 +#include + +#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 ". The column names + ** of the result set of the compiled SELECT will be the same as + ** the column names of table . + */ + zSql = sqlite3_mprintf("SELECT * FROM %Q", zTab); + if( !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=0 && cidzTableName ){ + 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; iinterp, 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; iinterp, (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 && ipStmt, 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 ?? ?? +** +** where the and 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; iinConstraint; 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<estimatedCost = (double)ii; + } + } + }else{ + pIdxInfo->estimatedCost = (double)nRow; + } + return rc; +} + +/* +** The xUpdate method for echo module virtual tables. +** +** apData[0] apData[1] apData[2..] +** +** INTEGER DELETE +** +** INTEGER NULL (nCol args) UPDATE (do not set rowid) +** INTEGER INTEGER (nCol args) UPDATE (with SET rowid = ) +** +** NULL NULL (nCol args) INSERT INTO (automatic rowid value) +** NULL INTEGER (nCol args) INSERT (incl. rowid value) +** +*/ +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; iaCol[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; iiaCol[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; izErrMsg = 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; iinterp; + 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 +#include + +/* +** 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 + +#ifdef SQLITE_ENABLE_ASYNCIO + +#include "sqlite3async.h" +#include "sqlite3.h" +#include + +/* 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; +} diff --git a/components/external/sqlite/test/test_autoext.c b/components/external/sqlite/test/test_autoext.c new file mode 100644 index 0000000000..a5236d2390 --- /dev/null +++ b/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; +} diff --git a/components/external/sqlite/test/test_backup.c b/components/external/sqlite/test/test_backup.c new file mode 100644 index 0000000000..e967424a29 --- /dev/null +++ b/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 +#include + +/* 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; +} diff --git a/components/external/sqlite/test/test_btree.c b/components/external/sqlite/test/test_btree.c new file mode 100644 index 0000000000..db72889b2a --- /dev/null +++ b/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 + +/* +** 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 +} diff --git a/components/external/sqlite/test/test_config.c b/components/external/sqlite/test/test_config.c new file mode 100644 index 0000000000..de1822e3b1 --- /dev/null +++ b/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 +#include + +/* +** 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; +} diff --git a/components/external/sqlite/test/test_demovfs.c b/components/external/sqlite/test/test_demovfs.c new file mode 100644 index 0000000000..c63b0a8b7a --- /dev/null +++ b/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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* +** 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 + +#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 */ diff --git a/components/external/sqlite/test/test_devsym.c b/components/external/sqlite/test/test_devsym.c new file mode 100644 index 0000000000..21f0f684d8 --- /dev/null +++ b/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 diff --git a/components/external/sqlite/test/test_fs.c b/components/external/sqlite/test/test_fs.c new file mode 100644 index 0000000000..417c81b49f --- /dev/null +++ b/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 +#include +#include +#include +#include + +#if SQLITE_OS_UNIX +# include +#endif +#if SQLITE_OS_WIN +# include +#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; iinConstraint; 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 +#include +#include + +#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=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='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> 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 +#include +#include + + +/* +** 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=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 +#include + +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 +#include + + +/* +** 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->ipContent->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 + +/* +** 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; ipWritable); + 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; ipReal; + 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 && iLeafpWritable, 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=(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 && ii0 && 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 && eLockeLock ){ + 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 diff --git a/components/external/sqlite/test/test_loadext.c b/components/external/sqlite/test/test_loadext.c new file mode 100644 index 0000000000..1137e3a9aa --- /dev/null +++ b/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 +#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 ){ + 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; +} diff --git a/components/external/sqlite/test/test_malloc.c b/components/external/sqlite/test/test_malloc.c new file mode 100644 index 0000000000..f513e24bf4 --- /dev/null +++ b/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 +#include +#include + +/* +** 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>= 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; isizeof(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; i0 ){ + 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 +** -benigncnt +** +** 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; ii1 && 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)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; ii5 ){ + 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) ){ + 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) ){ + 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 +#include +#include +#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; inReal; 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; inReal; 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 +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; inReal; 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,); +** +** =1 MULTIPLEX_CTRL_ENABLE +** =0 disable +** =1 enable +** +** =2 MULTIPLEX_CTRL_SET_CHUNK_SIZE +** int, chunk size +** +** =3 MULTIPLEX_CTRL_SET_MAX_CHUNKS +** 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 */ diff --git a/components/external/sqlite/test/test_mutex.c b/components/external/sqlite/test/test_mutex.c new file mode 100644 index 0000000000..c9b4a29ab7 --- /dev/null +++ b/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 +#include +#include + +/* 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 +#include + +/* +** 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 diff --git a/components/external/sqlite/test/test_osinst.c b/components/external/sqlite/test/test_osinst.c new file mode 100644 index 0000000000..531433313e --- /dev/null +++ b/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 +#include + + +/* +** 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 +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 +#include +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; inFile; 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 + +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 */ diff --git a/components/external/sqlite/test/test_pcache.c b/components/external/sqlite/test/test_pcache.c new file mode 100644 index 0000000000..8fcfe7e26e --- /dev/null +++ b/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 +#include + +/* +** 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; ia[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; ia[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; ia[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; ia[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; ia[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; ia[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; ia[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; ia[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; + } +} diff --git a/components/external/sqlite/test/test_quota.c b/components/external/sqlite/test/test_quota.c new file mode 100644 index 0000000000..e590996ca4 --- /dev/null +++ b/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 +#include + +/* +** 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 +#endif +#if SQLITE_OS_WIN +# include +# include +#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->iSizepGroup; + 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->iSizepGroup; + 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( rcpGroup->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->iSizeiSizepGroup; + 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 + +/* +** 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 +#include +#include +#if SQLITE_OS_UNIX +# include +#endif +#if SQLITE_OS_WIN +# include +#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_ */ diff --git a/components/external/sqlite/test/test_rtree.c b/components/external/sqlite/test/test_rtree.c new file mode 100644 index 0000000000..e1966c2437 --- /dev/null +++ b/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 +#include + +/* 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 +#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; +} diff --git a/components/external/sqlite/test/test_schema.c b/components/external/sqlite/test/test_schema.c new file mode 100644 index 0000000000..00a9f4dd90 --- /dev/null +++ b/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 +#include +#include + +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 +#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) */ diff --git a/components/external/sqlite/test/test_sqllog.c b/components/external/sqlite/test/test_sqllog.c new file mode 100644 index 0000000000..4aa68b7c42 --- /dev/null +++ b/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 +#include +#include +#include + +#include +#include +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, ©); + 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; idb==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; + } + } +} diff --git a/components/external/sqlite/test/test_stat.c b/components/external/sqlite/test/test_stat.c new file mode 100644 index 0000000000..d4c902b5ea --- /dev/null +++ b/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; inCell; 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; iaPage); 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; inCell; 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; jaOvfl[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->iCellnCell ){ + StatCell *pCell = &p->aCell[p->iCell]; + if( pCell->iOvflnOvfl ){ + 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->iOvflnOvfl-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; inCell; 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 + +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 */ diff --git a/components/external/sqlite/test/test_superlock.c b/components/external/sqlite/test/test_superlock.c new file mode 100644 index 0000000000..936fcad0c5 --- /dev/null +++ b/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 +#include /* memset(), strlen() */ +#include /* 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 + +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 diff --git a/components/external/sqlite/test/test_syscall.c b/components/external/sqlite/test/test_syscall.c new file mode 100644 index 0000000000..7c0873c16d --- /dev/null +++ b/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 +#include +#include + +#if SQLITE_OS_UNIX + +/* From main.c */ +extern const char *sqlite3ErrName(int); + +#include +#include +#include + +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; ixGetSystemCall(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 +#include + +#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->i1pList1, 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; iinConstraint; 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; iinConstraint; 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 + +#if SQLITE_THREADSAFE + +#include + +#if !defined(_MSC_VER) +#include +#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 + +/* +** 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; imutex); + 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 diff --git a/components/external/sqlite/test/test_vfs.c b/components/external/sqlite/test/test_vfs.c new file mode 100644 index 0000000000..613b0fce77 --- /dev/null +++ b/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 + +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; ieFault ){ + 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( iPageaPage[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<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*pgszaPage[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; imask = 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; j1 ){ + 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; iFlagiDevchar & 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; i2 && 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( szOsFileiDevchar = -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 && mxPathnamemxPathname ){ + 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 diff --git a/components/external/sqlite/test/test_vfstrace.c b/components/external/sqlite/test/test_vfstrace.c new file mode 100644 index 0000000000..0aacc01fe4 --- /dev/null +++ b/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 +#include +#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) : ""; + 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); +} diff --git a/components/external/sqlite/test/test_wsd.c b/components/external/sqlite/test/test_wsd.c new file mode 100644 index 0000000000..99e4a05658 --- /dev/null +++ b/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; iaData[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