[[SQLITE_FCNTL_FILE_POINTER]]
** The [SQLITE_FCNTL_FILE_POINTER] opcode is used to obtain a pointer
** to the [sqlite3_file] object associated with a particular database
-** connection. See the [sqlite3_file_control()] documentation for
-** additional information.
+** connection. See also [SQLITE_FCNTL_JOURNAL_POINTER].
+**
+**
[[SQLITE_FCNTL_JOURNAL_POINTER]]
+** The [SQLITE_FCNTL_JOURNAL_POINTER] opcode is used to obtain a pointer
+** to the [sqlite3_file] object associated with the journal file (either
+** the [rollback journal] or the [write-ahead log]) for a particular database
+** connection. See also [SQLITE_FCNTL_FILE_POINTER].
**
**
[[SQLITE_FCNTL_SYNC_OMITTED]]
** No longer in use.
@@ -882,6 +887,15 @@ struct sqlite3_io_methods {
** pointer in case this file-control is not implemented. This file-control
** is intended for diagnostic use only.
**
+**
[[SQLITE_FCNTL_VFS_POINTER]]
+** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level
+** [VFSes] currently in use. ^(The argument X in
+** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be
+** of type "[sqlite3_vfs] **". This opcodes will set *X
+** to a pointer to the top-level VFS.)^
+** ^When there are multiple VFS shims in the stack, this opcode finds the
+** upper-most shim only.
+**
**
[[SQLITE_FCNTL_PRAGMA]]
** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA]
** file control is sent to the open [sqlite3_file] object corresponding
@@ -1000,6 +1014,8 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_WAL_BLOCK 24
#define SQLITE_FCNTL_ZIPVFS 25
#define SQLITE_FCNTL_RBU 26
+#define SQLITE_FCNTL_VFS_POINTER 27
+#define SQLITE_FCNTL_JOURNAL_POINTER 28
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -4395,8 +4411,8 @@ unsigned int sqlite3_value_subtype(sqlite3_value*);
** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer
** then sqlite3_value_free(V) is a harmless no-op.
*/
-SQLITE_EXPERIMENTAL sqlite3_value *sqlite3_value_dup(const sqlite3_value*);
-SQLITE_EXPERIMENTAL void sqlite3_value_free(sqlite3_value*);
+sqlite3_value *sqlite3_value_dup(const sqlite3_value*);
+void sqlite3_value_free(sqlite3_value*);
/*
** CAPI3REF: Obtain Aggregate Function Context
@@ -5615,6 +5631,17 @@ struct sqlite3_module {
** ^Information about the ORDER BY clause is stored in aOrderBy[].
** ^Each term of aOrderBy records a column of the ORDER BY clause.
**
+** The colUsed field indicates which columns of the virtual table may be
+** required by the current scan. Virtual table columns are numbered from
+** zero in the order in which they appear within the CREATE TABLE statement
+** passed to sqlite3_declare_vtab(). For the first 63 columns (columns 0-62),
+** the corresponding bit is set within the colUsed mask if the column may be
+** required by SQLite. If the table has at least 64 columns and any column
+** to the right of the first 63 is required, then bit 63 of colUsed is also
+** set. In other words, column iCol may be required if the expression
+** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to
+** non-zero.
+**
** The [xBestIndex] method must fill aConstraintUsage[] with information
** about what parameters to pass to xFilter. ^If argvIndex>0 then
** the right-hand side of the corresponding aConstraint[] is evaluated
@@ -5694,6 +5721,8 @@ struct sqlite3_index_info {
sqlite3_int64 estimatedRows; /* Estimated number of rows returned */
/* Fields below are only available in SQLite 3.9.0 and later */
int idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */
+ /* Fields below are only available in SQLite 3.10.0 and later */
+ sqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */
};
/*
@@ -5709,12 +5738,15 @@ struct sqlite3_index_info {
** an operator that is part of a constraint term in the wHERE clause of
** a query that uses a [virtual table].
*/
-#define SQLITE_INDEX_CONSTRAINT_EQ 2
-#define SQLITE_INDEX_CONSTRAINT_GT 4
-#define SQLITE_INDEX_CONSTRAINT_LE 8
-#define SQLITE_INDEX_CONSTRAINT_LT 16
-#define SQLITE_INDEX_CONSTRAINT_GE 32
-#define SQLITE_INDEX_CONSTRAINT_MATCH 64
+#define SQLITE_INDEX_CONSTRAINT_EQ 2
+#define SQLITE_INDEX_CONSTRAINT_GT 4
+#define SQLITE_INDEX_CONSTRAINT_LE 8
+#define SQLITE_INDEX_CONSTRAINT_LT 16
+#define SQLITE_INDEX_CONSTRAINT_GE 32
+#define SQLITE_INDEX_CONSTRAINT_MATCH 64
+#define SQLITE_INDEX_CONSTRAINT_LIKE 65
+#define SQLITE_INDEX_CONSTRAINT_GLOB 66
+#define SQLITE_INDEX_CONSTRAINT_REGEXP 67
/*
** CAPI3REF: Register A Virtual Table Implementation
@@ -7365,18 +7397,43 @@ int sqlite3_strnicmp(const char *, const char *, int);
/*
** CAPI3REF: String Globbing
*
-** ^The [sqlite3_strglob(P,X)] interface returns zero if string X matches
-** the glob pattern P, and it returns non-zero if string X does not match
-** the glob pattern P. ^The definition of glob pattern matching used in
+** ^The [sqlite3_strglob(P,X)] interface returns zero if and only if
+** string X matches the [GLOB] pattern P.
+** ^The definition of [GLOB] pattern matching used in
** [sqlite3_strglob(P,X)] is the same as for the "X GLOB P" operator in the
-** SQL dialect used by SQLite. ^The sqlite3_strglob(P,X) function is case
-** sensitive.
+** SQL dialect understood by SQLite. ^The [sqlite3_strglob(P,X)] function
+** is case sensitive.
**
** Note that this routine returns zero on a match and non-zero if the strings
** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()].
+**
+** See also: [sqlite3_strlike()].
*/
int sqlite3_strglob(const char *zGlob, const char *zStr);
+/*
+** CAPI3REF: String LIKE Matching
+*
+** ^The [sqlite3_strlike(P,X,E)] interface returns zero if and only if
+** string X matches the [LIKE] pattern P with escape character E.
+** ^The definition of [LIKE] pattern matching used in
+** [sqlite3_strlike(P,X,E)] is the same as for the "X LIKE P ESCAPE E"
+** operator in the SQL dialect understood by SQLite. ^For "X LIKE P" without
+** the ESCAPE clause, set the E parameter of [sqlite3_strlike(P,X,E)] to 0.
+** ^As with the LIKE operator, the [sqlite3_strlike(P,X,E)] function is case
+** insensitive - equivalent upper and lower case ASCII characters match
+** one another.
+**
+** ^The [sqlite3_strlike(P,X,E)] function matches Unicode characters, though
+** only ASCII characters are case folded.
+**
+** Note that this routine returns zero on a match and non-zero if the strings
+** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()].
+**
+** See also: [sqlite3_strglob()].
+*/
+int sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc);
+
/*
** CAPI3REF: Error Logging Interface
**
@@ -7800,33 +7857,127 @@ void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
/*
** CAPI3REF: Flush caches to disk mid-transaction
**
-** If a write-transaction is open when this function is called, any dirty
+** ^If a write-transaction is open on [database connection] D when the
+** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
** pages in the pager-cache that are not currently in use are written out
** to disk. A dirty page may be in use if a database cursor created by an
** active SQL statement is reading from it, or if it is page 1 of a database
-** file (page 1 is always "in use"). Dirty pages are flushed for all
-** databases - "main", "temp" and any attached databases.
+** file (page 1 is always "in use"). ^The [sqlite3_db_cacheflush(D)]
+** interface flushes caches for all schemas - "main", "temp", and
+** any [attached] databases.
**
-** If this function needs to obtain extra database locks before dirty pages
-** can be flushed to disk, it does so. If said locks cannot be obtained
+** ^If this function needs to obtain extra database locks before dirty pages
+** can be flushed to disk, it does so. ^If those locks cannot be obtained
** immediately and there is a busy-handler callback configured, it is invoked
-** in the usual manner. If the required lock still cannot be obtained, then
+** in the usual manner. ^If the required lock still cannot be obtained, then
** the database is skipped and an attempt made to flush any dirty pages
-** belonging to the next (if any) database. If any databases are skipped
+** belonging to the next (if any) database. ^If any databases are skipped
** because locks cannot be obtained, but no other error occurs, this
** function returns SQLITE_BUSY.
**
-** If any other error occurs while flushing dirty pages to disk (for
+** ^If any other error occurs while flushing dirty pages to disk (for
** example an IO error or out-of-memory condition), then processing is
-** abandoned and an SQLite error code returned to the caller immediately.
+** abandoned and an SQLite [error code] is returned to the caller immediately.
**
-** Otherwise, if no error occurs, SQLITE_OK is returned.
+** ^Otherwise, if no error occurs, [sqlite3_db_cacheflush()] returns SQLITE_OK.
**
-** This function does not set the database handle error code or message
-** returned by the sqlite3_errcode() and sqlite3_errmsg() functions.
+** ^This function does not set the database handle error code or message
+** returned by the [sqlite3_errcode()] and [sqlite3_errmsg()] functions.
*/
int sqlite3_db_cacheflush(sqlite3*);
+/*
+** CAPI3REF: Database Snapshot
+** KEYWORDS: {snapshot}
+** EXPERIMENTAL
+**
+** An instance of the snapshot object records the state of a [WAL mode]
+** database for some specific point in history.
+**
+** In [WAL mode], multiple [database connections] that are open on the
+** same database file can each be reading a different historical version
+** of the database file. When a [database connection] begins a read
+** transaction, that connection sees an unchanging copy of the database
+** as it existed for the point in time when the transaction first started.
+** Subsequent changes to the database from other connections are not seen
+** by the reader until a new read transaction is started.
+**
+** The sqlite3_snapshot object records state information about an historical
+** version of the database file so that it is possible to later open a new read
+** transaction that sees that historical version of the database rather than
+** the most recent version.
+**
+** The constructor for this object is [sqlite3_snapshot_get()]. The
+** [sqlite3_snapshot_open()] method causes a fresh read transaction to refer
+** to an historical snapshot (if possible). The destructor for
+** sqlite3_snapshot objects is [sqlite3_snapshot_free()].
+*/
+typedef struct sqlite3_snapshot sqlite3_snapshot;
+
+/*
+** CAPI3REF: Record A Database Snapshot
+** EXPERIMENTAL
+**
+** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a
+** new [sqlite3_snapshot] object that records the current state of
+** schema S in database connection D. ^On success, the
+** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly
+** created [sqlite3_snapshot] object into *P and returns SQLITE_OK.
+** ^If schema S of [database connection] D is not a [WAL mode] database
+** that is in a read transaction, then [sqlite3_snapshot_get(D,S,P)]
+** leaves the *P value unchanged and returns an appropriate [error code].
+**
+** The [sqlite3_snapshot] object returned from a successful call to
+** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()]
+** to avoid a memory leak.
+**
+** The [sqlite3_snapshot_get()] interface is only available when the
+** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
+*/
+SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
+ sqlite3 *db,
+ const char *zSchema,
+ sqlite3_snapshot **ppSnapshot
+);
+
+/*
+** CAPI3REF: Start a read transaction on an historical snapshot
+** EXPERIMENTAL
+**
+** ^The [sqlite3_snapshot_open(D,S,P)] interface attempts to move the
+** read transaction that is currently open on schema S of
+** [database connection] D so that it refers to historical [snapshot] P.
+** ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK on success
+** or an appropriate [error code] if it fails.
+**
+** ^In order to succeed, a call to [sqlite3_snapshot_open(D,S,P)] must be
+** the first operation, apart from other sqlite3_snapshot_open() calls,
+** following the [BEGIN] that starts a new read transaction.
+** ^A [snapshot] will fail to open if it has been overwritten by a
+** [checkpoint].
+**
+** The [sqlite3_snapshot_open()] interface is only available when the
+** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
+*/
+SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
+ sqlite3 *db,
+ const char *zSchema,
+ sqlite3_snapshot *pSnapshot
+);
+
+/*
+** CAPI3REF: Destroy a snapshot
+** EXPERIMENTAL
+**
+** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P.
+** The application must eventually free every [sqlite3_snapshot] object
+** using this routine to avoid a memory leak.
+**
+** The [sqlite3_snapshot_free()] interface is only available when the
+** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
+*/
+SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
+
/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h
index 017ea308b1..2e1c764a52 100644
--- a/src/sqlite3ext.h
+++ b/src/sqlite3ext.h
@@ -275,6 +275,10 @@ struct sqlite3_api_routines {
/* Version 3.9.0 and later */
unsigned int (*value_subtype)(sqlite3_value*);
void (*result_subtype)(sqlite3_context*,unsigned int);
+ /* Version 3.10.0 and later */
+ int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
+ int (*strlike)(const char*,const char*,unsigned int);
+ int (*db_cacheflush)(sqlite3*);
};
/*
@@ -514,6 +518,10 @@ struct sqlite3_api_routines {
/* Version 3.9.0 and later */
#define sqlite3_value_subtype sqlite3_api->value_subtype
#define sqlite3_result_subtype sqlite3_api->result_subtype
+/* Version 3.10.0 and later */
+#define sqlite3_status64 sqlite3_api->status64
+#define sqlite3_strlike sqlite3_api->strlike
+#define sqlite3_db_cacheflush sqlite3_api->db_cacheflush
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 338a573253..b536b8ab28 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -173,6 +173,21 @@
# define SQLITE_PTR_TO_INT(X) ((int)(X))
#endif
+/*
+** The SQLITE_WITHIN(P,S,E) macro checks to see if pointer P points to
+** something between S (inclusive) and E (exclusive).
+**
+** In other words, S is a buffer and E is a pointer to the first byte after
+** the end of buffer S. This macro returns true if P points to something
+** contained within the buffer S.
+*/
+#if defined(HAVE_STDINT_H)
+# define SQLITE_WITHIN(P,S,E) \
+ ((uintptr_t)(P)>=(uintptr_t)(S) && (uintptr_t)(P)<(uintptr_t)(E))
+#else
+# define SQLITE_WITHIN(P,S,E) ((P)>=(S) && (P)<(E))
+#endif
+
/*
** A macro to hint to the compiler that a function should not be
** inlined.
@@ -387,6 +402,21 @@
# define NEVER(X) (X)
#endif
+/*
+** Some malloc failures are only possible if SQLITE_TEST_REALLOC_STRESS is
+** defined. We need to defend against those failures when testing with
+** SQLITE_TEST_REALLOC_STRESS, but we don't want the unreachable branches
+** during a normal build. The following macro can be used to disable tests
+** that are always false except when SQLITE_TEST_REALLOC_STRESS is set.
+*/
+#if defined(SQLITE_TEST_REALLOC_STRESS)
+# define ONLY_IF_REALLOC_STRESS(X) (X)
+#elif !defined(NDEBUG)
+# define ONLY_IF_REALLOC_STRESS(X) ((X)?(assert(0),1):0)
+#else
+# define ONLY_IF_REALLOC_STRESS(X) (0)
+#endif
+
/*
** Declarations used for tracing the operating system interfaces.
*/
@@ -677,11 +707,6 @@ typedef INT16_TYPE LogEst;
** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined
** at run-time.
*/
-#ifdef SQLITE_AMALGAMATION
-const int sqlite3one = 1;
-#else
-extern const int sqlite3one;
-#endif
#if (defined(i386) || defined(__i386__) || defined(_M_IX86) || \
defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
@@ -699,6 +724,11 @@ extern const int sqlite3one;
# define SQLITE_UTF16NATIVE SQLITE_UTF16BE
#endif
#if !defined(SQLITE_BYTEORDER)
+# ifdef SQLITE_AMALGAMATION
+ const int sqlite3one = 1;
+# else
+ extern const int sqlite3one;
+# endif
# define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */
# define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0)
# define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1)
@@ -752,10 +782,6 @@ extern const int sqlite3one;
*/
#ifdef __APPLE__
# include
-# if TARGET_OS_IPHONE
-# undef SQLITE_MAX_MMAP_SIZE
-# define SQLITE_MAX_MMAP_SIZE 0
-# endif
#endif
#ifndef SQLITE_MAX_MMAP_SIZE
# if defined(__linux__) \
@@ -1352,9 +1378,8 @@ struct FuncDef {
u16 funcFlags; /* Some combination of SQLITE_FUNC_* */
void *pUserData; /* User data parameter */
FuncDef *pNext; /* Next function with same name */
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**); /* Regular function */
- void (*xStep)(sqlite3_context*,int,sqlite3_value**); /* Aggregate step */
- void (*xFinalize)(sqlite3_context*); /* Aggregate finalizer */
+ void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */
+ void (*xFinalize)(sqlite3_context*); /* Agg finalizer */
char *zName; /* SQL name of the function. */
FuncDef *pHash; /* Next with a different name but the same hash */
FuncDestructor *pDestructor; /* Reference counted destructor function */
@@ -1437,28 +1462,28 @@ struct FuncDestructor {
*/
#define FUNCTION(zName, nArg, iArg, bNC, xFunc) \
{nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
- SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0}
+ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, 0, 0}
#define VFUNCTION(zName, nArg, iArg, bNC, xFunc) \
{nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
- SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0}
+ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, 0, 0}
#define DFUNCTION(zName, nArg, iArg, bNC, xFunc) \
{nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
- SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0}
+ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, 0, 0}
#define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags) \
{nArg,SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\
- SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0}
+ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, 0, 0}
#define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \
{nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
- pArg, 0, xFunc, 0, 0, #zName, 0, 0}
+ pArg, 0, xFunc, 0, #zName, 0, 0}
#define LIKEFUNC(zName, nArg, arg, flags) \
{nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \
- (void *)arg, 0, likeFunc, 0, 0, #zName, 0, 0}
+ (void *)arg, 0, likeFunc, 0, #zName, 0, 0}
#define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal) \
{nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL), \
- SQLITE_INT_TO_PTR(arg), 0, 0, xStep,xFinal,#zName,0,0}
+ SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,#zName,0,0}
#define AGGREGATE2(zName, nArg, arg, nc, xStep, xFinal, extraFlags) \
{nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|extraFlags, \
- SQLITE_INT_TO_PTR(arg), 0, 0, xStep,xFinal,#zName,0,0}
+ SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,#zName,0,0}
/*
** All current savepoints are stored in a linked list starting at
@@ -1507,7 +1532,7 @@ struct Column {
char *zColl; /* Collating sequence. If NULL, use the default */
u8 notNull; /* An OE_ code for handling a NOT NULL constraint */
char affinity; /* One of the SQLITE_AFF_... values */
- u8 szEst; /* Estimated size of this column. INT==1 */
+ u8 szEst; /* Estimated size of value in this column. sizeof(INT)==1 */
u8 colFlags; /* Boolean properties. See COLFLAG_ defines below */
};
@@ -1917,7 +1942,7 @@ struct Index {
Index *pNext; /* The next index associated with the same table */
Schema *pSchema; /* Schema containing this index */
u8 *aSortOrder; /* for each column: True==DESC, False==ASC */
- char **azColl; /* Array of collation sequence names for index */
+ const char **azColl; /* Array of collation sequence names for index */
Expr *pPartIdxWhere; /* WHERE clause for partial indices */
ExprList *aColExpr; /* Column expressions */
int tnum; /* DB Page containing root of this index */
@@ -2717,6 +2742,7 @@ struct Parse {
int nSet; /* Number of sets used so far */
int nOnce; /* Number of OP_Once instructions so far */
int nOpAlloc; /* Number of slots allocated for Vdbe.aOp[] */
+ int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */
int iFixedOp; /* Never back out opcodes iFixedOp-1 or earlier */
int ckBase; /* Base register of data during check constraints */
int iSelfTab; /* Table of an index whose exprs are being coded */
@@ -2768,7 +2794,7 @@ struct Parse {
** in the recursive region.
************************************************************************/
- int nVar; /* Number of '?' variables seen in the SQL so far */
+ ynVar nVar; /* Number of '?' variables seen in the SQL so far */
int nzVar; /* Number of available slots in azVar[] */
u8 iPkSortOrder; /* ASC or DESC for INTEGER PRIMARY KEY */
u8 explain; /* True if the EXPLAIN flag is found on the query */
@@ -2831,7 +2857,7 @@ struct AuthContext {
#define OPFLAG_TYPEOFARG 0x80 /* OP_Column only used for typeof() */
#define OPFLAG_BULKCSR 0x01 /* OP_Open** used to open bulk cursor */
#define OPFLAG_SEEKEQ 0x02 /* OP_Open** cursor uses EQ seek only */
-#define OPFLAG_FORDELETE 0x08 /* OP_Open is opening for-delete csr */
+#define OPFLAG_FORDELETE 0x08 /* OP_Open should use BTREE_FORDELETE */
#define OPFLAG_P2ISREG 0x10 /* P2 to OP_Open** is a register number */
#define OPFLAG_PERMUTE 0x01 /* OP_Compare: use the permutation */
@@ -2948,10 +2974,11 @@ struct StrAccum {
sqlite3 *db; /* Optional database for lookaside. Can be NULL */
char *zBase; /* A base allocation. Not from malloc. */
char *zText; /* The string collected so far */
- int nChar; /* Length of the string so far */
- int nAlloc; /* Amount of space allocated in zText */
- int mxAlloc; /* Maximum allowed allocation. 0 for no malloc usage */
+ u32 nChar; /* Length of the string so far */
+ u32 nAlloc; /* Amount of space allocated in zText */
+ u32 mxAlloc; /* Maximum allowed allocation. 0 for no malloc usage */
u8 accError; /* STRACCUM_NOMEM or STRACCUM_TOOBIG */
+ u8 bMalloced; /* zText points to allocated space */
};
#define STRACCUM_NOMEM 1
#define STRACCUM_TOOBIG 2
@@ -3049,10 +3076,10 @@ struct Sqlite3Config {
** Context pointer passed down through the tree-walk.
*/
struct Walker {
+ Parse *pParse; /* Parser context. */
int (*xExprCallback)(Walker*, Expr*); /* Callback for expressions */
int (*xSelectCallback)(Walker*,Select*); /* Callback for SELECTs */
void (*xSelectCallback2)(Walker*,Select*);/* Second callback for SELECTs */
- Parse *pParse; /* Parser context. */
int walkerDepth; /* Number of subqueries */
u8 eCode; /* A small processing code */
union { /* Extra data for callback */
@@ -3323,7 +3350,6 @@ void sqlite3Pragma(Parse*,Token*,Token*,Token*,int);
void sqlite3ResetAllSchemasOfConnection(sqlite3*);
void sqlite3ResetOneSchema(sqlite3*,int);
void sqlite3CollapseDatabaseArray(sqlite3*);
-void sqlite3BeginParse(Parse*,int);
void sqlite3CommitInternalChanges(sqlite3*);
void sqlite3DeleteColumnNames(sqlite3*,Table*);
int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**);
@@ -3332,7 +3358,11 @@ void sqlite3OpenMasterTable(Parse *, int);
Index *sqlite3PrimaryKeyIndex(Table*);
i16 sqlite3ColumnOfIndex(Index*, i16);
void sqlite3StartTable(Parse*,Token*,Token*,int,int,int,int);
-void sqlite3ColumnPropertiesFromName(Table*, Column*);
+#if SQLITE_ENABLE_HIDDEN_COLUMNS
+ void sqlite3ColumnPropertiesFromName(Table*, Column*);
+#else
+# define sqlite3ColumnPropertiesFromName(T,C) /* no-op */
+#endif
void sqlite3AddColumn(Parse*,Token*);
void sqlite3AddNotNull(Parse*, int);
void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int);
@@ -3445,6 +3475,7 @@ void sqlite3ExprCacheRemove(Parse*, int, int);
void sqlite3ExprCacheClear(Parse*);
void sqlite3ExprCacheAffinityChange(Parse*, int, int);
void sqlite3ExprCode(Parse*, Expr*, int);
+void sqlite3ExprCodeCopy(Parse*, Expr*, int);
void sqlite3ExprCodeFactorable(Parse*, Expr*, int);
void sqlite3ExprCodeAtInit(Parse*, Expr*, int, u8);
int sqlite3ExprCodeTemp(Parse*, Expr*, int*);
@@ -3682,6 +3713,7 @@ int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **);
void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8);
#ifndef SQLITE_AMALGAMATION
extern const unsigned char sqlite3OpcodeProperty[];
+extern const char sqlite3StrBINARY[];
extern const unsigned char sqlite3UpperToLower[];
extern const unsigned char sqlite3CtypeMap[];
extern const Token sqlite3IntTokens[];
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index f024317e90..604e898265 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -2976,6 +2976,10 @@ static int DbMain(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
Tcl_AppendResult(interp,sqlite3_libversion(), (char*)0);
return TCL_OK;
}
+ if( strcmp(zArg,"-sourceid")==0 ){
+ Tcl_AppendResult(interp,sqlite3_sourceid(), (char*)0);
+ return TCL_OK;
+ }
if( strcmp(zArg,"-has-codec")==0 ){
#ifdef SQLITE_HAS_CODEC
Tcl_AppendResult(interp,"1",(char*)0);
diff --git a/src/test1.c b/src/test1.c
index 186e4e4684..c5e71582bc 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -2269,6 +2269,94 @@ static int vfsCurrentTimeInt64(
return TCL_OK;
}
+#ifdef SQLITE_ENABLE_SNAPSHOT
+/*
+** Usage: sqlite3_snapshot_get DB DBNAME
+*/
+static int test_snapshot_get(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int rc;
+ sqlite3 *db;
+ char *zName;
+ sqlite3_snapshot *pSnapshot = 0;
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+ zName = Tcl_GetString(objv[2]);
+
+ rc = sqlite3_snapshot_get(db, zName, &pSnapshot);
+ if( rc!=SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ return TCL_ERROR;
+ }else{
+ char zBuf[100];
+ if( sqlite3TestMakePointerStr(interp, zBuf, pSnapshot) ) return TCL_ERROR;
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(zBuf, -1));
+ }
+ return TCL_OK;
+}
+#endif /* SQLITE_ENABLE_SNAPSHOT */
+
+#ifdef SQLITE_ENABLE_SNAPSHOT
+/*
+** Usage: sqlite3_snapshot_open DB DBNAME SNAPSHOT
+*/
+static int test_snapshot_open(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int rc;
+ sqlite3 *db;
+ char *zName;
+ sqlite3_snapshot *pSnapshot;
+
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME SNAPSHOT");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+ zName = Tcl_GetString(objv[2]);
+ pSnapshot = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[3]));
+
+ rc = sqlite3_snapshot_open(db, zName, pSnapshot);
+ if( rc!=SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ return TCL_ERROR;
+ }
+ return TCL_OK;
+}
+#endif /* SQLITE_ENABLE_SNAPSHOT */
+
+#ifdef SQLITE_ENABLE_SNAPSHOT
+/*
+** Usage: sqlite3_snapshot_free SNAPSHOT
+*/
+static int test_snapshot_free(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3_snapshot *pSnapshot;
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SNAPSHOT");
+ return TCL_ERROR;
+ }
+ pSnapshot = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+ sqlite3_snapshot_free(pSnapshot);
+ return TCL_OK;
+}
+#endif /* SQLITE_ENABLE_SNAPSHOT */
+
/*
** Usage: sqlite3_next_stmt DB STMT
**
@@ -5906,13 +5994,13 @@ static int test_sqlite3_log(
Tcl_DecrRefCount(logcallback.pObj);
logcallback.pObj = 0;
logcallback.pInterp = 0;
- sqlite3_config(SQLITE_CONFIG_LOG, 0, 0);
+ sqlite3_config(SQLITE_CONFIG_LOG, (void*)0, (void*)0);
}
if( objc>1 ){
logcallback.pObj = objv[1];
Tcl_IncrRefCount(logcallback.pObj);
logcallback.pInterp = interp;
- sqlite3_config(SQLITE_CONFIG_LOG, xLogcallback, 0);
+ sqlite3_config(SQLITE_CONFIG_LOG, xLogcallback, (void*)0);
}
return TCL_OK;
}
@@ -7083,6 +7171,11 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "sqlite3_config_sqllog", test_config_sqllog, 0 },
#endif
{ "vfs_current_time_int64", vfsCurrentTimeInt64, 0 },
+#ifdef SQLITE_ENABLE_SNAPSHOT
+ { "sqlite3_snapshot_get", test_snapshot_get, 0 },
+ { "sqlite3_snapshot_open", test_snapshot_open, 0 },
+ { "sqlite3_snapshot_free", test_snapshot_free, 0 },
+#endif
};
static int bitmask_size = sizeof(Bitmask)*8;
static int longdouble_size = sizeof(LONGDOUBLE_TYPE);
diff --git a/src/test8.c b/src/test8.c
index 2107710a99..fb781ac8fd 100644
--- a/src/test8.c
+++ b/src/test8.c
@@ -745,6 +745,34 @@ static void string_concat(char **pzStr, char *zAppend, int doFree, int *pRc){
}
}
+/*
+** This function returns a pointer to an sqlite3_malloc()ed buffer
+** containing the select-list (the thing between keywords SELECT and FROM)
+** to query the underlying real table with for the scan described by
+** argument pIdxInfo.
+**
+** If the current SQLite version is earlier than 3.10.0, this is just "*"
+** (select all columns). Or, for version 3.10.0 and greater, the list of
+** columns identified by the pIdxInfo->colUsed mask.
+*/
+static char *echoSelectList(echo_vtab *pTab, sqlite3_index_info *pIdxInfo){
+ char *zRet = 0;
+ if( sqlite3_libversion_number()<3010000 ){
+ zRet = sqlite3_mprintf(", *");
+ }else{
+ int i;
+ for(i=0; inCol; i++){
+ if( pIdxInfo->colUsed & ((sqlite3_uint64)1 << (i>=63 ? 63 : i)) ){
+ zRet = sqlite3_mprintf("%z, %s", zRet, pTab->aCol[i]);
+ }else{
+ zRet = sqlite3_mprintf("%z, NULL", zRet);
+ }
+ if( !zRet ) break;
+ }
+ }
+ return zRet;
+}
+
/*
** The echo module implements the subset of query constraints and sort
** orders that may take advantage of SQLite indices on the underlying
@@ -770,6 +798,7 @@ static void string_concat(char **pzStr, char *zAppend, int doFree, int *pRc){
static int echoBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
int ii;
char *zQuery = 0;
+ char *zCol = 0;
char *zNew;
int nArg = 0;
const char *zSep = "WHERE";
@@ -817,10 +846,11 @@ static int echoBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
}
}
- zQuery = sqlite3_mprintf("SELECT rowid, * FROM %Q", pVtab->zTableName);
- if( !zQuery ){
- return SQLITE_NOMEM;
- }
+ zCol = echoSelectList(pVtab, pIdxInfo);
+ if( !zCol ) return SQLITE_NOMEM;
+ zQuery = sqlite3_mprintf("SELECT rowid%z FROM %Q", zCol, pVtab->zTableName);
+ if( !zQuery ) return SQLITE_NOMEM;
+
for(ii=0; iinConstraint; ii++){
const struct sqlite3_index_constraint *pConstraint;
struct sqlite3_index_constraint_usage *pUsage;
@@ -833,7 +863,7 @@ static int echoBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
iCol = pConstraint->iColumn;
if( iCol<0 || pVtab->aIndex[iCol] ){
- char *zCol = iCol>=0 ? pVtab->aCol[iCol] : "rowid";
+ char *zNewCol = iCol>=0 ? pVtab->aCol[iCol] : "rowid";
char *zOp = 0;
useIdx = 1;
switch( pConstraint->op ){
@@ -848,13 +878,26 @@ static int echoBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
case SQLITE_INDEX_CONSTRAINT_GE:
zOp = ">="; break;
case SQLITE_INDEX_CONSTRAINT_MATCH:
+ /* Purposely translate the MATCH operator into a LIKE, which
+ ** will be used by the next block of code to construct a new
+ ** query. It should also be noted here that the next block
+ ** of code requires the first letter of this operator to be
+ ** in upper-case to trigger the special MATCH handling (i.e.
+ ** wrapping the bound parameter with literal '%'s).
+ */
zOp = "LIKE"; break;
+ case SQLITE_INDEX_CONSTRAINT_LIKE:
+ zOp = "like"; break;
+ case SQLITE_INDEX_CONSTRAINT_GLOB:
+ zOp = "glob"; break;
+ case SQLITE_INDEX_CONSTRAINT_REGEXP:
+ zOp = "regexp"; break;
}
if( zOp[0]=='L' ){
zNew = sqlite3_mprintf(" %s %s LIKE (SELECT '%%'||?||'%%')",
- zSep, zCol);
+ zSep, zNewCol);
} else {
- zNew = sqlite3_mprintf(" %s %s %s ?", zSep, zCol, zOp);
+ zNew = sqlite3_mprintf(" %s %s %s ?", zSep, zNewCol, zOp);
}
string_concat(&zQuery, zNew, 1, &rc);
@@ -872,9 +915,9 @@ static int echoBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
pIdxInfo->aOrderBy->iColumn<0 ||
pVtab->aIndex[pIdxInfo->aOrderBy->iColumn]) ){
int iCol = pIdxInfo->aOrderBy->iColumn;
- char *zCol = iCol>=0 ? pVtab->aCol[iCol] : "rowid";
+ char *zNewCol = iCol>=0 ? pVtab->aCol[iCol] : "rowid";
char *zDir = pIdxInfo->aOrderBy->desc?"DESC":"ASC";
- zNew = sqlite3_mprintf(" ORDER BY %s %s", zCol, zDir);
+ zNew = sqlite3_mprintf(" ORDER BY %s %s", zNewCol, zDir);
string_concat(&zQuery, zNew, 1, &rc);
pIdxInfo->orderByConsumed = 1;
}
diff --git a/src/test_config.c b/src/test_config.c
index b84424bbdc..30b421e00b 100644
--- a/src/test_config.c
+++ b/src/test_config.c
@@ -143,6 +143,12 @@ static void set_options(Tcl_Interp *interp){
Tcl_SetVar2(interp, "sqlite_options", "mem5", "0", TCL_GLOBAL_ONLY);
#endif
+#ifdef SQLITE_ENABLE_SNAPSHOT
+ Tcl_SetVar2(interp, "sqlite_options", "snapshot", "1", TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar2(interp, "sqlite_options", "snapshot", "0", TCL_GLOBAL_ONLY);
+#endif
+
#ifdef SQLITE_MUTEX_OMIT
Tcl_SetVar2(interp, "sqlite_options", "mutex", "0", TCL_GLOBAL_ONLY);
#else
@@ -185,6 +191,12 @@ static void set_options(Tcl_Interp *interp){
Tcl_SetVar2(interp, "sqlite_options", "json1", "0", TCL_GLOBAL_ONLY);
#endif
+#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS
+ Tcl_SetVar2(interp, "sqlite_options", "like_match_blobs", "0", TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar2(interp, "sqlite_options", "like_match_blobs", "1", TCL_GLOBAL_ONLY);
+#endif
+
#ifdef SQLITE_OMIT_ATTACH
Tcl_SetVar2(interp, "sqlite_options", "attach", "0", TCL_GLOBAL_ONLY);
#else
diff --git a/src/test_fs.c b/src/test_fs.c
index 417c81b49f..45db0b53b8 100644
--- a/src/test_fs.c
+++ b/src/test_fs.c
@@ -29,6 +29,37 @@
** 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.
+**
+*************************************************************************
+** Virtual table module "fsdir"
+**
+** This module is designed to be used as a read-only eponymous virtual table.
+** Its schema is as follows:
+**
+** CREATE TABLE fsdir(dir TEXT, name TEXT);
+**
+** When queried, a WHERE term of the form "dir = $dir" must be provided. The
+** virtual table then appears to have one row for each entry in file-system
+** directory $dir. Column dir contains a copy of $dir, and column "name"
+** contains the name of the directory entry.
+**
+** If the specified $dir cannot be opened or is not a directory, it is not
+** an error. The virtual table appears to be empty in this case.
+**
+*************************************************************************
+** Virtual table module "fstree"
+**
+** This module is also a read-only eponymous virtual table with the
+** following schema:
+**
+** CREATE TABLE fstree(path TEXT, size INT, data BLOB);
+**
+** Running a "SELECT * FROM fstree" query on this table returns the entire
+** contents of the file-system, starting at "/". To restrict the search
+** space, the virtual table supports LIKE and GLOB constraints on the
+** 'path' column. For example:
+**
+** SELECT * FROM fstree WHERE path LIKE '/home/dan/sqlite/%'
*/
#include "sqliteInt.h"
#include "tcl.h"
@@ -39,11 +70,21 @@
#include
#include
-#if SQLITE_OS_UNIX
+#if SQLITE_OS_UNIX || defined(__MINGW_H)
# include
+# include
+# ifndef DIRENT
+# define DIRENT dirent
+# endif
#endif
#if SQLITE_OS_WIN
# include
+# if !defined(__MINGW_H)
+# include "test_windirent.h"
+# endif
+# ifndef S_ISREG
+# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
+# endif
#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
@@ -70,6 +111,498 @@ struct fs_cursor {
int nAlloc;
};
+/*************************************************************************
+** Start of fsdir implementation.
+*/
+typedef struct FsdirVtab FsdirVtab;
+typedef struct FsdirCsr FsdirCsr;
+struct FsdirVtab {
+ sqlite3_vtab base;
+};
+
+struct FsdirCsr {
+ sqlite3_vtab_cursor base;
+ char *zDir; /* Buffer containing directory scanned */
+ DIR *pDir; /* Open directory */
+ sqlite3_int64 iRowid;
+ struct DIRENT entry; /* Current entry */
+};
+
+/*
+** This function is the implementation of both the xConnect and xCreate
+** methods of the fsdir 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 fsdirConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ FsdirVtab *pTab;
+
+ if( argc!=3 ){
+ *pzErr = sqlite3_mprintf("wrong number of arguments");
+ return SQLITE_ERROR;
+ }
+
+ pTab = (FsdirVtab *)sqlite3_malloc(sizeof(FsdirVtab));
+ if( !pTab ) return SQLITE_NOMEM;
+ memset(pTab, 0, sizeof(FsdirVtab));
+
+ *ppVtab = &pTab->base;
+ sqlite3_declare_vtab(db, "CREATE TABLE xyz(dir, name);");
+
+ return SQLITE_OK;
+}
+
+/*
+** xDestroy/xDisconnect implementation.
+*/
+static int fsdirDisconnect(sqlite3_vtab *pVtab){
+ sqlite3_free(pVtab);
+ return SQLITE_OK;
+}
+
+/*
+** xBestIndex implementation. The only constraint supported is:
+**
+** (dir = ?)
+*/
+static int fsdirBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+ int ii;
+
+ pIdxInfo->estimatedCost = 1000000000.0;
+
+ for(ii=0; iinConstraint; ii++){
+ struct sqlite3_index_constraint const *p = &pIdxInfo->aConstraint[ii];
+ if( p->iColumn==0 && p->usable && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+ struct sqlite3_index_constraint_usage *pUsage;
+ pUsage = &pIdxInfo->aConstraintUsage[ii];
+ pUsage->omit = 1;
+ pUsage->argvIndex = 1;
+ pIdxInfo->idxNum = 1;
+ pIdxInfo->estimatedCost = 1.0;
+ break;
+ }
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** xOpen implementation.
+**
+** Open a new fsdir cursor.
+*/
+static int fsdirOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+ FsdirCsr *pCur;
+ /* Allocate an extra 256 bytes because it is undefined how big dirent.d_name
+ ** is and we need enough space. Linux provides plenty already, but
+ ** Solaris only provides one byte. */
+ pCur = (FsdirCsr*)sqlite3_malloc(sizeof(FsdirCsr)+256);
+ if( pCur==0 ) return SQLITE_NOMEM;
+ memset(pCur, 0, sizeof(FsdirCsr));
+ *ppCursor = &pCur->base;
+ return SQLITE_OK;
+}
+
+/*
+** Close a fsdir cursor.
+*/
+static int fsdirClose(sqlite3_vtab_cursor *cur){
+ FsdirCsr *pCur = (FsdirCsr*)cur;
+ if( pCur->pDir ) closedir(pCur->pDir);
+ sqlite3_free(pCur->zDir);
+ sqlite3_free(pCur);
+ return SQLITE_OK;
+}
+
+/*
+** Skip the cursor to the next entry.
+*/
+static int fsdirNext(sqlite3_vtab_cursor *cur){
+ FsdirCsr *pCsr = (FsdirCsr*)cur;
+
+ if( pCsr->pDir ){
+ struct DIRENT *pRes = 0;
+#if defined(__MINGW_H)
+ pRes = readdir(pCsr->pDir);
+ if( pRes!=0 ){
+ memcpy(&pCsr->entry, pRes, sizeof(struct DIRENT));
+ }
+#else
+ readdir_r(pCsr->pDir, &pCsr->entry, &pRes);
+#endif
+ if( pRes==0 ){
+ closedir(pCsr->pDir);
+ pCsr->pDir = 0;
+ }
+ pCsr->iRowid++;
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** xFilter method implementation.
+*/
+static int fsdirFilter(
+ sqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ FsdirCsr *pCsr = (FsdirCsr*)pVtabCursor;
+ const char *zDir;
+ int nDir;
+
+
+ if( idxNum!=1 || argc!=1 ){
+ return SQLITE_ERROR;
+ }
+
+ pCsr->iRowid = 0;
+ sqlite3_free(pCsr->zDir);
+ if( pCsr->pDir ){
+ closedir(pCsr->pDir);
+ pCsr->pDir = 0;
+ }
+
+ zDir = (const char*)sqlite3_value_text(argv[0]);
+ nDir = sqlite3_value_bytes(argv[0]);
+ pCsr->zDir = sqlite3_malloc(nDir+1);
+ if( pCsr->zDir==0 ) return SQLITE_NOMEM;
+ memcpy(pCsr->zDir, zDir, nDir+1);
+
+ pCsr->pDir = opendir(pCsr->zDir);
+ return fsdirNext(pVtabCursor);
+}
+
+/*
+** xEof method implementation.
+*/
+static int fsdirEof(sqlite3_vtab_cursor *cur){
+ FsdirCsr *pCsr = (FsdirCsr*)cur;
+ return pCsr->pDir==0;
+}
+
+/*
+** xColumn method implementation.
+*/
+static int fsdirColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
+ FsdirCsr *pCsr = (FsdirCsr*)cur;
+ switch( i ){
+ case 0: /* dir */
+ sqlite3_result_text(ctx, pCsr->zDir, -1, SQLITE_STATIC);
+ break;
+
+ case 1: /* name */
+ sqlite3_result_text(ctx, pCsr->entry.d_name, -1, SQLITE_TRANSIENT);
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** xRowid method implementation.
+*/
+static int fsdirRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+ FsdirCsr *pCsr = (FsdirCsr*)cur;
+ *pRowid = pCsr->iRowid;
+ return SQLITE_OK;
+}
+/*
+** End of fsdir implementation.
+*************************************************************************/
+
+/*************************************************************************
+** Start of fstree implementation.
+*/
+typedef struct FstreeVtab FstreeVtab;
+typedef struct FstreeCsr FstreeCsr;
+struct FstreeVtab {
+ sqlite3_vtab base;
+ sqlite3 *db;
+};
+
+struct FstreeCsr {
+ sqlite3_vtab_cursor base;
+ sqlite3_stmt *pStmt; /* Statement to list paths */
+ int fd; /* File descriptor open on current path */
+};
+
+/*
+** This function is the implementation of both the xConnect and xCreate
+** methods of the fstree 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 fstreeConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ FstreeVtab *pTab;
+
+ if( argc!=3 ){
+ *pzErr = sqlite3_mprintf("wrong number of arguments");
+ return SQLITE_ERROR;
+ }
+
+ pTab = (FstreeVtab *)sqlite3_malloc(sizeof(FstreeVtab));
+ if( !pTab ) return SQLITE_NOMEM;
+ memset(pTab, 0, sizeof(FstreeVtab));
+ pTab->db = db;
+
+ *ppVtab = &pTab->base;
+ sqlite3_declare_vtab(db, "CREATE TABLE xyz(path, size, data);");
+
+ return SQLITE_OK;
+}
+
+/*
+** xDestroy/xDisconnect implementation.
+*/
+static int fstreeDisconnect(sqlite3_vtab *pVtab){
+ sqlite3_free(pVtab);
+ return SQLITE_OK;
+}
+
+/*
+** xBestIndex implementation. The only constraint supported is:
+**
+** (dir = ?)
+*/
+static int fstreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+ int ii;
+
+ for(ii=0; iinConstraint; ii++){
+ struct sqlite3_index_constraint const *p = &pIdxInfo->aConstraint[ii];
+ if( p->iColumn==0 && p->usable && (
+ p->op==SQLITE_INDEX_CONSTRAINT_GLOB
+ || p->op==SQLITE_INDEX_CONSTRAINT_LIKE
+ || p->op==SQLITE_INDEX_CONSTRAINT_EQ
+ )){
+ struct sqlite3_index_constraint_usage *pUsage;
+ pUsage = &pIdxInfo->aConstraintUsage[ii];
+ pIdxInfo->idxNum = p->op;
+ pUsage->argvIndex = 1;
+ pIdxInfo->estimatedCost = 100000.0;
+ return SQLITE_OK;
+ }
+ }
+
+ pIdxInfo->estimatedCost = 1000000000.0;
+ return SQLITE_OK;
+}
+
+/*
+** xOpen implementation.
+**
+** Open a new fstree cursor.
+*/
+static int fstreeOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+ FstreeCsr *pCur;
+ pCur = (FstreeCsr*)sqlite3_malloc(sizeof(FstreeCsr));
+ if( pCur==0 ) return SQLITE_NOMEM;
+ memset(pCur, 0, sizeof(FstreeCsr));
+ pCur->fd = -1;
+ *ppCursor = &pCur->base;
+ return SQLITE_OK;
+}
+
+static void fstreeCloseFd(FstreeCsr *pCsr){
+ if( pCsr->fd>=0 ){
+ close(pCsr->fd);
+ pCsr->fd = -1;
+ }
+}
+
+/*
+** Close a fstree cursor.
+*/
+static int fstreeClose(sqlite3_vtab_cursor *cur){
+ FstreeCsr *pCsr = (FstreeCsr*)cur;
+ sqlite3_finalize(pCsr->pStmt);
+ fstreeCloseFd(pCsr);
+ sqlite3_free(pCsr);
+ return SQLITE_OK;
+}
+
+/*
+** Skip the cursor to the next entry.
+*/
+static int fstreeNext(sqlite3_vtab_cursor *cur){
+ FstreeCsr *pCsr = (FstreeCsr*)cur;
+ int rc;
+
+ fstreeCloseFd(pCsr);
+ rc = sqlite3_step(pCsr->pStmt);
+ if( rc!=SQLITE_ROW ){
+ rc = sqlite3_finalize(pCsr->pStmt);
+ pCsr->pStmt = 0;
+ }else{
+ rc = SQLITE_OK;
+ pCsr->fd = open((const char*)sqlite3_column_text(pCsr->pStmt, 0), O_RDONLY);
+ }
+
+ return rc;
+}
+
+/*
+** xFilter method implementation.
+*/
+static int fstreeFilter(
+ sqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ FstreeCsr *pCsr = (FstreeCsr*)pVtabCursor;
+ FstreeVtab *pTab = (FstreeVtab*)(pCsr->base.pVtab);
+ int rc;
+ const char *zSql =
+"WITH r(d) AS ("
+" SELECT CASE WHEN dir=?2 THEN ?3 ELSE dir END || '/' || name "
+" FROM fsdir WHERE dir=?1 AND name NOT LIKE '.%'"
+" UNION ALL"
+" SELECT dir || '/' || name FROM r, fsdir WHERE dir=d AND name NOT LIKE '.%'"
+") SELECT d FROM r;";
+
+ char *zRoot;
+ int nRoot;
+ char *zPrefix;
+ int nPrefix;
+ const char *zDir;
+ int nDir;
+ char aWild[2] = { '\0', '\0' };
+
+#if SQLITE_OS_WIN
+ zRoot = sqlite3_mprintf("%s%c", getenv("SystemDrive"), '/');
+ nRoot = strlen(zRoot);
+ zPrefix = sqlite3_mprintf("%s", getenv("SystemDrive"));
+ nPrefix = strlen(zPrefix);
+#else
+ zRoot = "/";
+ nRoot = 1;
+ zPrefix = "";
+ nPrefix = 0;
+#endif
+
+ zDir = zRoot;
+ nDir = nRoot;
+
+ fstreeCloseFd(pCsr);
+ sqlite3_finalize(pCsr->pStmt);
+ pCsr->pStmt = 0;
+ rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
+ if( rc!=SQLITE_OK ) return rc;
+
+ if( idxNum ){
+ const char *zQuery = (const char*)sqlite3_value_text(argv[0]);
+ switch( idxNum ){
+ case SQLITE_INDEX_CONSTRAINT_GLOB:
+ aWild[0] = '*';
+ aWild[1] = '?';
+ break;
+ case SQLITE_INDEX_CONSTRAINT_LIKE:
+ aWild[0] = '_';
+ aWild[1] = '%';
+ break;
+ }
+
+ if( sqlite3_strnicmp(zQuery, zPrefix, nPrefix)==0 ){
+ int i;
+ for(i=nPrefix; zQuery[i]; i++){
+ if( zQuery[i]==aWild[0] || zQuery[i]==aWild[1] ) break;
+ if( zQuery[i]=='/' ) nDir = i;
+ }
+ zDir = zQuery;
+ }
+ }
+
+ sqlite3_bind_text(pCsr->pStmt, 1, zDir, nDir, SQLITE_TRANSIENT);
+ sqlite3_bind_text(pCsr->pStmt, 2, zRoot, nRoot, SQLITE_TRANSIENT);
+ sqlite3_bind_text(pCsr->pStmt, 3, zPrefix, nPrefix, SQLITE_TRANSIENT);
+
+#if SQLITE_OS_WIN
+ sqlite3_free(zPrefix);
+ sqlite3_free(zRoot);
+#endif
+
+ return fstreeNext(pVtabCursor);
+}
+
+/*
+** xEof method implementation.
+*/
+static int fstreeEof(sqlite3_vtab_cursor *cur){
+ FstreeCsr *pCsr = (FstreeCsr*)cur;
+ return pCsr->pStmt==0;
+}
+
+/*
+** xColumn method implementation.
+*/
+static int fstreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
+ FstreeCsr *pCsr = (FstreeCsr*)cur;
+ if( i==0 ){ /* path */
+ sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pStmt, 0));
+ }else{
+ struct stat sBuf;
+ fstat(pCsr->fd, &sBuf);
+
+ if( S_ISREG(sBuf.st_mode) ){
+ if( i==1 ){
+ sqlite3_result_int64(ctx, sBuf.st_size);
+ }else{
+ int nRead;
+ char *aBuf = sqlite3_malloc(sBuf.st_mode+1);
+ if( !aBuf ) return SQLITE_NOMEM;
+ nRead = read(pCsr->fd, aBuf, sBuf.st_mode);
+ if( nRead!=sBuf.st_mode ){
+ return SQLITE_IOERR;
+ }
+ sqlite3_result_blob(ctx, aBuf, nRead, SQLITE_TRANSIENT);
+ sqlite3_free(aBuf);
+ }
+ }
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** xRowid method implementation.
+*/
+static int fstreeRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+ *pRowid = 0;
+ return SQLITE_OK;
+}
+/*
+** End of fstree implementation.
+*************************************************************************/
+
+
+
+
/*
** This function is the implementation of both the xConnect and xCreate
** methods of the fs virtual table.
@@ -109,7 +642,7 @@ static int fsConnect(
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)");
+ sqlite3_declare_vtab(db, "CREATE TABLE x(path TEXT, data TEXT)");
return SQLITE_OK;
}
@@ -188,15 +721,15 @@ static int fsFilter(
static int fsColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
fs_cursor *pCur = (fs_cursor*)cur;
- assert( i==0 || i==1 );
+ assert( i==0 || i==1 || i==2 );
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;
+ int n;
fd = open(zFile, O_RDONLY);
if( fd<0 ) return SQLITE_IOERR;
fstat(fd, &sbuf);
@@ -284,6 +817,52 @@ static sqlite3_module fsModule = {
0, /* xRename */
};
+static sqlite3_module fsdirModule = {
+ 0, /* iVersion */
+ fsdirConnect, /* xCreate */
+ fsdirConnect, /* xConnect */
+ fsdirBestIndex, /* xBestIndex */
+ fsdirDisconnect, /* xDisconnect */
+ fsdirDisconnect, /* xDestroy */
+ fsdirOpen, /* xOpen - open a cursor */
+ fsdirClose, /* xClose - close a cursor */
+ fsdirFilter, /* xFilter - configure scan constraints */
+ fsdirNext, /* xNext - advance a cursor */
+ fsdirEof, /* xEof - check for end of scan */
+ fsdirColumn, /* xColumn - read data */
+ fsdirRowid, /* xRowid - read data */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+};
+
+static sqlite3_module fstreeModule = {
+ 0, /* iVersion */
+ fstreeConnect, /* xCreate */
+ fstreeConnect, /* xConnect */
+ fstreeBestIndex, /* xBestIndex */
+ fstreeDisconnect, /* xDisconnect */
+ fstreeDisconnect, /* xDestroy */
+ fstreeOpen, /* xOpen - open a cursor */
+ fstreeClose, /* xClose - close a cursor */
+ fstreeFilter, /* xFilter - configure scan constraints */
+ fstreeNext, /* xNext - advance a cursor */
+ fstreeEof, /* xEof - check for end of scan */
+ fstreeColumn, /* xColumn - read data */
+ fstreeRowid, /* xRowid - read data */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+};
+
/*
** Decode a pointer to an sqlite3 object.
*/
@@ -306,6 +885,8 @@ static int register_fs_module(
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
#ifndef SQLITE_OMIT_VIRTUALTABLE
sqlite3_create_module(db, "fs", &fsModule, (void *)interp);
+ sqlite3_create_module(db, "fsdir", &fsdirModule, 0);
+ sqlite3_create_module(db, "fstree", &fstreeModule, 0);
#endif
return TCL_OK;
}
diff --git a/src/test_loadext.c b/src/test_loadext.c
index 5a1f46da9c..6404a69714 100644
--- a/src/test_loadext.c
+++ b/src/test_loadext.c
@@ -34,7 +34,7 @@ static void statusFunc(
int argc,
sqlite3_value **argv
){
- int op, mx, cur, resetFlag, rc;
+ int op = 0, 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 ){
diff --git a/src/test_malloc.c b/src/test_malloc.c
index 3ab177dcb7..aaa640b03a 100644
--- a/src/test_malloc.c
+++ b/src/test_malloc.c
@@ -222,7 +222,8 @@ static int faultsimInstall(int install){
assert( memcmp(&m2, &memfault.m, sizeof(m2))==0 );
rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &memfault.m);
- sqlite3_test_control(SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS, 0, 0);
+ sqlite3_test_control(SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,
+ (void*)0, (void*)0);
}
if( rc==SQLITE_OK ){
@@ -910,7 +911,7 @@ static int test_config_scratch(
free(buf);
if( sz<0 ){
buf = 0;
- rc = sqlite3_config(SQLITE_CONFIG_SCRATCH, 0, 0, 0);
+ rc = sqlite3_config(SQLITE_CONFIG_SCRATCH, (void*)0, 0, 0);
}else{
buf = malloc( sz*N + 1 );
rc = sqlite3_config(SQLITE_CONFIG_SCRATCH, buf, sz, N);
@@ -957,7 +958,7 @@ static int test_config_pagecache(
Tcl_SetObjResult(interp, pRes);
if( sz<0 ){
- sqlite3_config(SQLITE_CONFIG_PAGECACHE, 0, 0, 0);
+ sqlite3_config(SQLITE_CONFIG_PAGECACHE, (void*)0, 0, 0);
}else{
buf = malloc( sz*N );
sqlite3_config(SQLITE_CONFIG_PAGECACHE, buf, sz, N);
diff --git a/src/test_multiplex.c b/src/test_multiplex.c
index 843a92ca65..82845ea7e1 100644
--- a/src/test_multiplex.c
+++ b/src/test_multiplex.c
@@ -189,8 +189,11 @@ static struct {
int isInitialized;
/* For run-time access any of the other global data structures in this
- ** shim, the following mutex must be held.
- */
+ ** shim, the following mutex must be held. In practice, all this mutex
+ ** protects is add/remove operations to/from the linked list of group objects
+ ** starting at pGroups below. More specifically, it protects the value of
+ ** pGroups itself, and the pNext/pPrev fields of each multiplexGroup
+ ** structure. */
sqlite3_mutex *pMutex;
/* List of multiplexGroup objects.
@@ -758,11 +761,8 @@ static int multiplexRead(
multiplexConn *p = (multiplexConn*)pConn;
multiplexGroup *pGroup = p->pGroup;
int rc = SQLITE_OK;
- int nMutex = 0;
- multiplexEnter(); nMutex++;
if( !pGroup->bEnabled ){
sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
- multiplexLeave(); nMutex--;
if( pSubOpen==0 ){
rc = SQLITE_IOERR_READ;
}else{
@@ -772,9 +772,7 @@ static int multiplexRead(
while( iAmt > 0 ){
int i = (int)(iOfst / pGroup->szChunk);
sqlite3_file *pSubOpen;
- if( nMutex==0 ){ multiplexEnter(); nMutex++; }
pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1);
- multiplexLeave(); nMutex--;
if( pSubOpen ){
int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) - pGroup->szChunk;
if( extra<0 ) extra = 0;
@@ -791,8 +789,7 @@ static int multiplexRead(
}
}
}
- assert( nMutex==0 || nMutex==1 );
- if( nMutex ) multiplexLeave();
+
return rc;
}
@@ -809,7 +806,6 @@ static int multiplexWrite(
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 ){
@@ -834,7 +830,6 @@ static int multiplexWrite(
}
}
}
- multiplexLeave();
return rc;
}
diff --git a/src/test_tclvar.c b/src/test_tclvar.c
index 1219190c03..63ed394734 100644
--- a/src/test_tclvar.c
+++ b/src/test_tclvar.c
@@ -23,6 +23,15 @@
#ifndef SQLITE_OMIT_VIRTUALTABLE
+/*
+** Characters that make up the idxStr created by xBestIndex for xFilter.
+*/
+#define TCLVAR_NAME_EQ 'e'
+#define TCLVAR_NAME_MATCH 'm'
+#define TCLVAR_VALUE_GLOB 'g'
+#define TCLVAR_VALUE_REGEXP 'r'
+#define TCLVAR_VALUE_LIKE 'l'
+
typedef struct tclvar_vtab tclvar_vtab;
typedef struct tclvar_cursor tclvar_cursor;
@@ -155,15 +164,44 @@ static int tclvarFilter(
){
tclvar_cursor *pCur = (tclvar_cursor *)pVtabCursor;
Tcl_Interp *interp = ((tclvar_vtab *)(pVtabCursor->pVtab))->interp;
+ Tcl_Obj *p = Tcl_NewStringObj("tclvar_filter_cmd", -1);
- Tcl_Obj *p = Tcl_NewStringObj("info vars", -1);
- Tcl_IncrRefCount(p);
+ const char *zEq = "";
+ const char *zMatch = "";
+ const char *zGlob = "";
+ const char *zRegexp = "";
+ const char *zLike = "";
+ int i;
- 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);
+ for(i=0; idxStr[i]; i++){
+ switch( idxStr[i] ){
+ case TCLVAR_NAME_EQ:
+ zEq = (const char*)sqlite3_value_text(argv[i]);
+ break;
+ case TCLVAR_NAME_MATCH:
+ zMatch = (const char*)sqlite3_value_text(argv[i]);
+ break;
+ case TCLVAR_VALUE_GLOB:
+ zGlob = (const char*)sqlite3_value_text(argv[i]);
+ break;
+ case TCLVAR_VALUE_REGEXP:
+ zRegexp = (const char*)sqlite3_value_text(argv[i]);
+ break;
+ case TCLVAR_VALUE_LIKE:
+ zLike = (const char*)sqlite3_value_text(argv[i]);
+ break;
+ default:
+ assert( 0 );
+ }
}
+
+ Tcl_IncrRefCount(p);
+ Tcl_ListObjAppendElement(0, p, Tcl_NewStringObj(zEq, -1));
+ Tcl_ListObjAppendElement(0, p, Tcl_NewStringObj(zMatch, -1));
+ Tcl_ListObjAppendElement(0, p, Tcl_NewStringObj(zGlob, -1));
+ Tcl_ListObjAppendElement(0, p, Tcl_NewStringObj(zRegexp, -1));
+ Tcl_ListObjAppendElement(0, p, Tcl_NewStringObj(zLike, -1));
+
Tcl_EvalObjEx(interp, p, TCL_EVAL_GLOBAL);
if( pCur->pList1 ){
Tcl_DecrRefCount(pCur->pList1);
@@ -176,7 +214,6 @@ static int tclvarFilter(
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);
@@ -224,32 +261,113 @@ static int tclvarEof(sqlite3_vtab_cursor *cur){
return (pCur->pList2?0:1);
}
+/*
+** If nul-terminated string zStr does not already contain the character
+** passed as the second argument, append it and return 0. Or, if there is
+** already an instance of x in zStr, do nothing return 1;
+**
+** There is guaranteed to be enough room in the buffer pointed to by zStr
+** for the new character and nul-terminator.
+*/
+static int tclvarAddToIdxstr(char *zStr, char x){
+ int i;
+ for(i=0; zStr[i]; i++){
+ if( zStr[i]==x ) return 1;
+ }
+ zStr[i] = x;
+ zStr[i+1] = '\0';
+ return 0;
+}
+
+/*
+** Return true if variable $::tclvar_set_omit exists and is set to true.
+** False otherwise.
+*/
+static int tclvarSetOmit(Tcl_Interp *interp){
+ int rc;
+ int res = 0;
+ Tcl_Obj *pRes;
+ rc = Tcl_Eval(interp,
+ "expr {[info exists ::tclvar_set_omit] && $::tclvar_set_omit}"
+ );
+ if( rc==TCL_OK ){
+ pRes = Tcl_GetObjResult(interp);
+ rc = Tcl_GetBooleanFromObj(0, pRes, &res);
+ }
+ return (rc==TCL_OK && res);
+}
+
+/*
+** The xBestIndex() method. This virtual table supports the following
+** operators:
+**
+** name = ? (omit flag clear)
+** name MATCH ? (omit flag set)
+** value GLOB ? (omit flag set iff $::tclvar_set_omit)
+** value REGEXP ? (omit flag set iff $::tclvar_set_omit)
+** value LIKE ? (omit flag set iff $::tclvar_set_omit)
+**
+** For each constraint present, the corresponding TCLVAR_XXX character is
+** appended to the idxStr value.
+*/
static int tclvarBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+ tclvar_vtab *pTab = (tclvar_vtab*)tab;
int ii;
+ char *zStr = sqlite3_malloc(32);
+ int iStr = 0;
+
+ if( zStr==0 ) return SQLITE_NOMEM;
+ zStr[0] = '\0';
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;
- }
- }
+ struct sqlite3_index_constraint_usage *pUsage;
+
+ pUsage = &pIdxInfo->aConstraintUsage[ii];
+ if( pCons->usable ){
+ /* name = ? */
+ if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ && pCons->iColumn==0 ){
+ if( 0==tclvarAddToIdxstr(zStr, TCLVAR_NAME_EQ) ){
+ pUsage->argvIndex = ++iStr;
+ pUsage->omit = 0;
+ }
+ }
- 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;
+ /* name MATCH ? */
+ if( pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH && pCons->iColumn==0 ){
+ if( 0==tclvarAddToIdxstr(zStr, TCLVAR_NAME_MATCH) ){
+ pUsage->argvIndex = ++iStr;
+ pUsage->omit = 1;
+ }
+ }
+
+ /* value GLOB ? */
+ if( pCons->op==SQLITE_INDEX_CONSTRAINT_GLOB && pCons->iColumn==2 ){
+ if( 0==tclvarAddToIdxstr(zStr, TCLVAR_VALUE_GLOB) ){
+ pUsage->argvIndex = ++iStr;
+ pUsage->omit = tclvarSetOmit(pTab->interp);
+ }
+ }
+
+ /* value REGEXP ? */
+ if( pCons->op==SQLITE_INDEX_CONSTRAINT_REGEXP && pCons->iColumn==2 ){
+ if( 0==tclvarAddToIdxstr(zStr, TCLVAR_VALUE_REGEXP) ){
+ pUsage->argvIndex = ++iStr;
+ pUsage->omit = tclvarSetOmit(pTab->interp);
+ }
+ }
+
+ /* value LIKE ? */
+ if( pCons->op==SQLITE_INDEX_CONSTRAINT_LIKE && pCons->iColumn==2 ){
+ if( 0==tclvarAddToIdxstr(zStr, TCLVAR_VALUE_LIKE) ){
+ pUsage->argvIndex = ++iStr;
+ pUsage->omit = tclvarSetOmit(pTab->interp);
+ }
+ }
}
}
+ pIdxInfo->idxStr = zStr;
+ pIdxInfo->needToFreeIdxStr = 1;
return SQLITE_OK;
}
@@ -295,6 +413,7 @@ static int register_tclvar_module(
int objc, /* Number of arguments */
Tcl_Obj *CONST objv[] /* Command arguments */
){
+ int rc = TCL_OK;
sqlite3 *db;
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "DB");
@@ -302,9 +421,30 @@ static int register_tclvar_module(
}
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
#ifndef SQLITE_OMIT_VIRTUALTABLE
- sqlite3_create_module(db, "tclvar", &tclvarModule, (void *)interp);
+ sqlite3_create_module(db, "tclvar", &tclvarModule, (void*)interp);
+ rc = Tcl_Eval(interp,
+ "proc like {pattern str} {\n"
+ " set p [string map {% * _ ?} $pattern]\n"
+ " string match $p $str\n"
+ "}\n"
+ "proc tclvar_filter_cmd {eq match glob regexp like} {\n"
+ " set res {}\n"
+ " set pattern $eq\n"
+ " if {$pattern=={}} { set pattern $match }\n"
+ " if {$pattern=={}} { set pattern * }\n"
+ " foreach v [uplevel #0 info vars $pattern] {\n"
+ " if {($glob=={} || [string match $glob [uplevel #0 set $v]])\n"
+ " && ($like=={} || [like $like [uplevel #0 set $v]])\n"
+ " && ($regexp=={} || [regexp $regexp [uplevel #0 set $v]])\n"
+ " } {\n"
+ " lappend res $v\n"
+ " }\n"
+ " }\n"
+ " set res\n"
+ "}\n"
+ );
#endif
- return TCL_OK;
+ return rc;
}
#endif
diff --git a/src/test_windirent.c b/src/test_windirent.c
new file mode 100644
index 0000000000..11d7dc07d0
--- /dev/null
+++ b/src/test_windirent.c
@@ -0,0 +1,157 @@
+/*
+** 2015 November 30
+**
+** 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 to implement most of the opendir() family of
+** POSIX functions on Win32 using the MSVCRT.
+*/
+
+#if defined(_WIN32) && defined(_MSC_VER)
+
+#include "test_windirent.h"
+
+/*
+** Implementation of the POSIX opendir() function using the MSVCRT.
+*/
+LPDIR opendir(
+ const char *dirname
+){
+ struct _finddata_t data;
+ LPDIR dirp = (LPDIR)sqlite3_malloc(sizeof(DIR));
+ SIZE_T namesize = sizeof(data.name) / sizeof(data.name[0]);
+
+ if( dirp==NULL ) return NULL;
+ memset(dirp, 0, sizeof(DIR));
+
+ /* TODO: Remove this if Unix-style root paths are not used. */
+ if( sqlite3_stricmp(dirname, "/")==0 ){
+ dirname = getenv("SystemDrive");
+ }
+
+ _snprintf(data.name, namesize, "%s\\*", dirname);
+ dirp->d_handle = _findfirst(data.name, &data);
+
+ if( dirp->d_handle==BAD_INTPTR_T ){
+ closedir(dirp);
+ return NULL;
+ }
+
+ /* TODO: Remove this block to allow hidden and system files. */
+ if( data.attrib&_A_HIDDEN || data.attrib&_A_SYSTEM ){
+ if( _findnext(dirp->d_handle, &data)==-1 ){
+ closedir(dirp);
+ return NULL;
+ }
+ }
+
+ dirp->d_first.d_attributes = data.attrib;
+ strncpy(dirp->d_first.d_name, data.name, NAME_MAX);
+ dirp->d_first.d_name[NAME_MAX] = '\0';
+
+ return dirp;
+}
+
+/*
+** Implementation of the POSIX readdir() function using the MSVCRT.
+*/
+LPDIRENT readdir(
+ LPDIR dirp
+){
+ struct _finddata_t data;
+
+ if( dirp==NULL ) return NULL;
+
+ if( dirp->d_first.d_ino==0 ){
+ dirp->d_first.d_ino++;
+ dirp->d_next.d_ino++;
+
+ return &dirp->d_first;
+ }
+
+next:
+
+ if( _findnext(dirp->d_handle, &data)==-1 ) return NULL;
+
+ /* TODO: Remove this block to allow hidden and system files. */
+ if( data.attrib&_A_HIDDEN ) goto next;
+ if( data.attrib&_A_SYSTEM ) goto next;
+
+ dirp->d_next.d_ino++;
+ dirp->d_next.d_attributes = data.attrib;
+ strncpy(dirp->d_next.d_name, data.name, NAME_MAX);
+ dirp->d_next.d_name[NAME_MAX] = '\0';
+
+ return &dirp->d_next;
+}
+
+/*
+** Implementation of the POSIX readdir_r() function using the MSVCRT.
+*/
+INT readdir_r(
+ LPDIR dirp,
+ LPDIRENT entry,
+ LPDIRENT *result
+){
+ struct _finddata_t data;
+
+ if( dirp==NULL ) return EBADF;
+
+ if( dirp->d_first.d_ino==0 ){
+ dirp->d_first.d_ino++;
+ dirp->d_next.d_ino++;
+
+ entry->d_ino = dirp->d_first.d_ino;
+ entry->d_attributes = dirp->d_first.d_attributes;
+ strncpy(entry->d_name, dirp->d_first.d_name, NAME_MAX);
+ entry->d_name[NAME_MAX] = '\0';
+
+ *result = entry;
+ return 0;
+ }
+
+next:
+
+ if( _findnext(dirp->d_handle, &data)==-1 ){
+ *result = NULL;
+ return ENOENT;
+ }
+
+ /* TODO: Remove this block to allow hidden and system files. */
+ if( data.attrib&_A_HIDDEN ) goto next;
+ if( data.attrib&_A_SYSTEM ) goto next;
+
+ entry->d_ino = (ino_t)-1; /* not available */
+ entry->d_attributes = data.attrib;
+ strncpy(entry->d_name, data.name, NAME_MAX);
+ entry->d_name[NAME_MAX] = '\0';
+
+ *result = entry;
+ return 0;
+}
+
+/*
+** Implementation of the POSIX closedir() function using the MSVCRT.
+*/
+INT closedir(
+ LPDIR dirp
+){
+ INT result = 0;
+
+ if( dirp==NULL ) return EINVAL;
+
+ if( dirp->d_handle!=NULL_INTPTR_T && dirp->d_handle!=BAD_INTPTR_T ){
+ result = _findclose(dirp->d_handle);
+ }
+
+ sqlite3_free(dirp);
+ return result;
+}
+
+#endif /* defined(WIN32) && defined(_MSC_VER) */
diff --git a/src/test_windirent.h b/src/test_windirent.h
new file mode 100644
index 0000000000..0b8d1a7b51
--- /dev/null
+++ b/src/test_windirent.h
@@ -0,0 +1,105 @@
+/*
+** 2015 November 30
+**
+** 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 declarations for most of the opendir() family of
+** POSIX functions on Win32 using the MSVCRT.
+*/
+
+#if defined(_WIN32) && defined(_MSC_VER)
+
+/*
+** We need several data types from the Windows SDK header.
+*/
+
+#define WIN32_LEAN_AND_MEAN
+#include "windows.h"
+
+/*
+** We need several support functions from the SQLite core.
+*/
+
+#include "sqlite3.h"
+
+/*
+** We need several things from the ANSI and MSVCRT headers.
+*/
+
+#include
+#include
+#include
+#include
+#include
+
+/*
+** We may need to provide the "ino_t" type.
+*/
+
+#ifndef INO_T_DEFINED
+ #define INO_T_DEFINED
+ typedef unsigned short ino_t;
+#endif
+
+/*
+** We need to define "NAME_MAX" if it was not present in "limits.h".
+*/
+
+#ifndef NAME_MAX
+# ifdef FILENAME_MAX
+# define NAME_MAX (FILENAME_MAX)
+# else
+# define NAME_MAX (260)
+# endif
+#endif
+
+/*
+** We need to define "NULL_INTPTR_T" and "BAD_INTPTR_T".
+*/
+
+#ifndef NULL_INTPTR_T
+# define NULL_INTPTR_T ((intptr_t)(0))
+#endif
+
+#ifndef BAD_INTPTR_T
+# define BAD_INTPTR_T ((intptr_t)(-1))
+#endif
+
+/*
+** We need to provide the necessary structures and related types.
+*/
+
+typedef struct DIRENT DIRENT;
+typedef struct DIR DIR;
+typedef DIRENT *LPDIRENT;
+typedef DIR *LPDIR;
+
+struct DIRENT {
+ ino_t d_ino; /* Sequence number, do not use. */
+ unsigned d_attributes; /* Win32 file attributes. */
+ char d_name[NAME_MAX + 1]; /* Name within the directory. */
+};
+
+struct DIR {
+ intptr_t d_handle; /* Value returned by "_findfirst". */
+ DIRENT d_first; /* DIRENT constructed based on "_findfirst". */
+ DIRENT d_next; /* DIRENT constructed based on "_findnext". */
+};
+
+/*
+** Finally, we can provide the function prototypes for the opendir(),
+** readdir(), readdir_r(), and closedir() POSIX functions.
+*/
+
+extern LPDIR opendir(const char *dirname);
+extern LPDIRENT readdir(LPDIR dirp);
+extern INT readdir_r(LPDIR dirp, LPDIRENT entry, LPDIRENT *result);
+extern INT closedir(LPDIR dirp);
+
+#endif /* defined(WIN32) && defined(_MSC_VER) */
diff --git a/src/trigger.c b/src/trigger.c
index 2eba0cf92c..48d6772992 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -559,31 +559,12 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){
*/
assert( pTable!=0 );
if( (v = sqlite3GetVdbe(pParse))!=0 ){
- int base;
- static const int iLn = VDBE_OFFSET_LINENO(2);
- static const VdbeOpList dropTrigger[] = {
- { OP_Rewind, 0, ADDR(9), 0},
- { OP_String8, 0, 1, 0}, /* 1 */
- { OP_Column, 0, 1, 2},
- { OP_Ne, 2, ADDR(8), 1},
- { OP_String8, 0, 1, 0}, /* 4: "trigger" */
- { OP_Column, 0, 0, 2},
- { OP_Ne, 2, ADDR(8), 1},
- { OP_Delete, 0, 0, 0},
- { OP_Next, 0, ADDR(1), 0}, /* 8 */
- };
-
- sqlite3BeginWriteOperation(pParse, 0, iDb);
- sqlite3OpenMasterTable(pParse, iDb);
- base = sqlite3VdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger, iLn);
- sqlite3VdbeChangeP4(v, base+1, pTrigger->zName, P4_TRANSIENT);
- sqlite3VdbeChangeP4(v, base+4, "trigger", P4_STATIC);
+ sqlite3NestedParse(pParse,
+ "DELETE FROM %Q.%s WHERE name=%Q AND type='trigger'",
+ db->aDb[iDb].zName, SCHEMA_TABLE(iDb), pTrigger->zName
+ );
sqlite3ChangeCookie(pParse, iDb);
- sqlite3VdbeAddOp2(v, OP_Close, 0, 0);
sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0);
- if( pParse->nMem<3 ){
- pParse->nMem = 3;
- }
}
}
@@ -971,8 +952,8 @@ void sqlite3CodeRowTriggerDirect(
if( pPrg ){
int bRecursive = (p->zName && 0==(pParse->db->flags&SQLITE_RecTriggers));
- sqlite3VdbeAddOp3(v, OP_Program, reg, ignoreJump, ++pParse->nMem);
- sqlite3VdbeChangeP4(v, -1, (const char *)pPrg->pProgram, P4_SUBPROGRAM);
+ sqlite3VdbeAddOp4(v, OP_Program, reg, ignoreJump, ++pParse->nMem,
+ (const char *)pPrg->pProgram, P4_SUBPROGRAM);
VdbeComment(
(v, "Call: %s.%s", (p->zName?p->zName:"fkey"), onErrorText(orconf)));
diff --git a/src/update.c b/src/update.c
index 1335c269ed..a9735cadca 100644
--- a/src/update.c
+++ b/src/update.c
@@ -263,10 +263,12 @@ void sqlite3Update(
assert( chngPk==0 || chngPk==1 );
chngKey = chngRowid + chngPk;
- /* The SET expressions are not actually used inside the WHERE loop.
- ** So reset the colUsed mask
+ /* The SET expressions are not actually used inside the WHERE loop.
+ ** So reset the colUsed mask. Unless this is a virtual table. In that
+ ** case, set all bits of the colUsed mask (to ensure that the virtual
+ ** table implementation makes all columns available).
*/
- pTabList->a[0].colUsed = 0;
+ pTabList->a[0].colUsed = IsVirtual(pTab) ? (Bitmask)-1 : 0;
hasFK = sqlite3FkRequired(pParse, pTab, aXRef, chngKey);
diff --git a/src/utf.c b/src/utf.c
index 25f4dadf0c..ee367c1399 100644
--- a/src/utf.c
+++ b/src/utf.c
@@ -37,13 +37,13 @@
#include
#include "vdbeInt.h"
-#ifndef SQLITE_AMALGAMATION
+#if !defined(SQLITE_AMALGAMATION) && SQLITE_BYTEORDER==0
/*
** The following constant value is used by the SQLITE_BIGENDIAN and
** SQLITE_LITTLEENDIAN macros.
*/
const int sqlite3one = 1;
-#endif /* SQLITE_AMALGAMATION */
+#endif /* SQLITE_AMALGAMATION && SQLITE_BYTEORDER==0 */
/*
** This lookup table is used to help decode the first byte of
diff --git a/src/vdbe.c b/src/vdbe.c
index 66b507b285..c6d5f7b0cc 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -1088,6 +1088,7 @@ case OP_String: { /* out2 */
pOut->n = pOp->p1;
pOut->enc = encoding;
UPDATE_MAX_BLOBSIZE(pOut);
+#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS
if( pOp->p5 ){
assert( pOp->p3>0 );
assert( pOp->p3<=(p->nMem-p->nCursor) );
@@ -1095,6 +1096,7 @@ case OP_String: { /* out2 */
assert( pIn3->flags & MEM_Int );
if( pIn3->u.i ) pOut->flags = MEM_Blob|MEM_Static|MEM_Term;
}
+#endif
break;
}
@@ -1660,8 +1662,8 @@ case OP_Function: {
MemSetTypeFlag(pCtx->pOut, MEM_Null);
pCtx->fErrorOrAux = 0;
db->lastRowid = lastRowid;
- (*pCtx->pFunc->xFunc)(pCtx, pCtx->argc, pCtx->argv); /* IMP: R-24505-23230 */
- lastRowid = db->lastRowid; /* Remember rowid changes made by xFunc */
+ (*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */
+ lastRowid = db->lastRowid; /* Remember rowid changes made by xSFunc */
/* If the function returned an error, throw an exception */
if( pCtx->fErrorOrAux ){
@@ -1979,6 +1981,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
*/
if( pOp->p5 & SQLITE_STOREP2 ){
pOut = &aMem[pOp->p2];
+ memAboutToChange(p, pOut);
MemSetTypeFlag(pOut, MEM_Null);
REGISTER_TRACE(pOp->p2, pOut);
}else{
@@ -1993,21 +1996,21 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
/* Neither operand is NULL. Do a comparison. */
affinity = pOp->p5 & SQLITE_AFF_MASK;
if( affinity>=SQLITE_AFF_NUMERIC ){
- if( (pIn1->flags & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){
+ if( (flags1 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){
applyNumericAffinity(pIn1,0);
}
- if( (pIn3->flags & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){
+ if( (flags3 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){
applyNumericAffinity(pIn3,0);
}
}else if( affinity==SQLITE_AFF_TEXT ){
- if( (pIn1->flags & MEM_Str)==0 && (pIn1->flags & (MEM_Int|MEM_Real))!=0 ){
+ if( (flags1 & MEM_Str)==0 && (flags1 & (MEM_Int|MEM_Real))!=0 ){
testcase( pIn1->flags & MEM_Int );
testcase( pIn1->flags & MEM_Real );
sqlite3VdbeMemStringify(pIn1, encoding, 1);
testcase( (flags1&MEM_Dyn) != (pIn1->flags&MEM_Dyn) );
flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask);
}
- if( (pIn3->flags & MEM_Str)==0 && (pIn3->flags & (MEM_Int|MEM_Real))!=0 ){
+ if( (flags3 & MEM_Str)==0 && (flags3 & (MEM_Int|MEM_Real))!=0 ){
testcase( pIn3->flags & MEM_Int );
testcase( pIn3->flags & MEM_Real );
sqlite3VdbeMemStringify(pIn3, encoding, 1);
@@ -2016,15 +2019,14 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
}
}
assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 );
- if( pIn1->flags & MEM_Zero ){
+ if( flags1 & MEM_Zero ){
sqlite3VdbeMemExpandBlob(pIn1);
flags1 &= ~MEM_Zero;
}
- if( pIn3->flags & MEM_Zero ){
+ if( flags3 & MEM_Zero ){
sqlite3VdbeMemExpandBlob(pIn3);
flags3 &= ~MEM_Zero;
}
- if( db->mallocFailed ) goto no_mem;
res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl);
}
switch( pOp->opcode ){
@@ -2372,7 +2374,6 @@ case OP_Column: {
u64 offset64; /* 64-bit offset */
u32 avail; /* Number of bytes of available data */
u32 t; /* A type code from the record header */
- u16 fx; /* pDest->flags value */
Mem *pReg; /* PseudoTable input register */
p2 = pOp->p2;
@@ -2521,6 +2522,8 @@ case OP_Column: {
rc = SQLITE_CORRUPT_BKPT;
goto op_column_error;
}
+ }else{
+ t = 0;
}
/* If after trying to extract new entries from the header, nHdrParsed is
@@ -2548,10 +2551,31 @@ case OP_Column: {
assert( sqlite3VdbeCheckMemInvariants(pDest) );
if( VdbeMemDynamic(pDest) ) sqlite3VdbeMemSetNull(pDest);
assert( t==pC->aType[p2] );
+ pDest->enc = encoding;
if( pC->szRow>=aOffset[p2+1] ){
/* This is the common case where the desired content fits on the original
** page - where the content is not on an overflow page */
- sqlite3VdbeSerialGet(pC->aRow+aOffset[p2], t, pDest);
+ zData = pC->aRow + aOffset[p2];
+ if( t<12 ){
+ sqlite3VdbeSerialGet(zData, t, pDest);
+ }else{
+ /* If the column value is a string, we need a persistent value, not
+ ** a MEM_Ephem value. This branch is a fast short-cut that is equivalent
+ ** to calling sqlite3VdbeSerialGet() and sqlite3VdbeDeephemeralize().
+ */
+ static const u16 aFlag[] = { MEM_Blob, MEM_Str|MEM_Term };
+ pDest->n = len = (t-12)/2;
+ if( pDest->szMalloc < len+2 ){
+ pDest->flags = MEM_Null;
+ if( sqlite3VdbeMemGrow(pDest, len+2, 0) ) goto no_mem;
+ }else{
+ pDest->z = pDest->zMalloc;
+ }
+ memcpy(pDest->z, zData, len);
+ pDest->z[len] = 0;
+ pDest->z[len+1] = 0;
+ pDest->flags = aFlag[t&1];
+ }
}else{
/* This branch happens only when content is on overflow pages */
if( ((pOp->p5 & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG))!=0
@@ -2563,38 +2587,20 @@ case OP_Column: {
** 2. the length(X) function if X is a blob, and
** 3. if the content length is zero.
** So we might as well use bogus content rather than reading
- ** content from disk. NULL will work for the value for strings
- ** and blobs and whatever is in the payloadSize64 variable
- ** will work for everything else. */
- sqlite3VdbeSerialGet(t<=13 ? (u8*)&payloadSize64 : 0, t, pDest);
+ ** content from disk. */
+ static u8 aZero[8]; /* This is the bogus content */
+ sqlite3VdbeSerialGet(aZero, t, pDest);
}else{
rc = sqlite3VdbeMemFromBtree(pCrsr, aOffset[p2], len, !pC->isTable,
pDest);
- if( rc!=SQLITE_OK ){
- goto op_column_error;
+ if( rc==SQLITE_OK ){
+ sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest);
+ pDest->flags &= ~MEM_Ephem;
}
- sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest);
- pDest->flags &= ~MEM_Ephem;
}
}
- pDest->enc = encoding;
op_column_out:
- /* If the column value is an ephemeral string, go ahead and persist
- ** that string in case the cursor moves before the column value is
- ** used. The following code does the equivalent of Deephemeralize()
- ** but does it faster. */
- if( (pDest->flags & MEM_Ephem)!=0 && pDest->z ){
- fx = pDest->flags & (MEM_Str|MEM_Blob);
- assert( fx!=0 );
- zData = (const u8*)pDest->z;
- len = pDest->n;
- if( sqlite3VdbeMemClearAndResize(pDest, len+2) ) goto no_mem;
- memcpy(pDest->z, zData, len);
- pDest->z[len] = 0;
- pDest->z[len+1] = 0;
- pDest->flags = fx|MEM_Term;
- }
op_column_error:
UPDATE_MAX_BLOBSIZE(pDest);
REGISTER_TRACE(pOp->p3, pDest);
@@ -3398,7 +3404,7 @@ open_cursor_set_hints:
assert( OPFLAG_BULKCSR==BTREE_BULKLOAD );
assert( OPFLAG_SEEKEQ==BTREE_SEEK_EQ );
testcase( pOp->p5 & OPFLAG_BULKCSR );
-#ifdef SQLITE_ENABLE_CURSOR_HINT
+#ifdef SQLITE_ENABLE_CURSOR_HINTS
testcase( pOp->p2 & OPFLAG_SEEKEQ );
#endif
sqlite3BtreeCursorHintFlags(pCur->uc.pCursor,
@@ -5093,6 +5099,7 @@ case OP_Destroy: { /* out2 */
int iDb;
assert( p->readOnly==0 );
+ assert( pOp->p1>1 );
pOut = out2Prerelease(p, pOp);
pOut->flags = MEM_Null;
if( db->nVdbeRead > db->nVDestroy+1 ){
@@ -5897,7 +5904,7 @@ case OP_AggStep: {
pCtx->pOut = &t;
pCtx->fErrorOrAux = 0;
pCtx->skipFlag = 0;
- (pCtx->pFunc->xStep)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */
+ (pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */
if( pCtx->fErrorOrAux ){
if( pCtx->isError ){
sqlite3VdbeError(p, "%s", sqlite3_value_text(&t));
diff --git a/src/vdbe.h b/src/vdbe.h
index 68a4f6b2a8..f09997bf94 100644
--- a/src/vdbe.h
+++ b/src/vdbe.h
@@ -180,7 +180,12 @@ int sqlite3VdbeAddOp3(Vdbe*,int,int,int,int);
int sqlite3VdbeAddOp4(Vdbe*,int,int,int,int,const char *zP4,int);
int sqlite3VdbeAddOp4Dup8(Vdbe*,int,int,int,int,const u8*,int);
int sqlite3VdbeAddOp4Int(Vdbe*,int,int,int,int,int);
-int sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp, int iLineno);
+#if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS)
+ void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N);
+#else
+# define sqlite3VdbeVerifyNoMallocRequired(A,B)
+#endif
+VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp, int iLineno);
void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*);
void sqlite3VdbeChangeOpcode(Vdbe*, u32 addr, u8);
void sqlite3VdbeChangeP1(Vdbe*, u32 addr, int P1);
@@ -188,7 +193,7 @@ void sqlite3VdbeChangeP2(Vdbe*, u32 addr, int P2);
void sqlite3VdbeChangeP3(Vdbe*, u32 addr, int P3);
void sqlite3VdbeChangeP5(Vdbe*, u8 P5);
void sqlite3VdbeJumpHere(Vdbe*, int addr);
-void sqlite3VdbeChangeToNoop(Vdbe*, int addr);
+int sqlite3VdbeChangeToNoop(Vdbe*, int addr);
int sqlite3VdbeDeletePriorOpcode(Vdbe*, u8 op);
void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N);
void sqlite3VdbeSetP4KeyInfo(Parse*, Index*);
diff --git a/src/vdbeInt.h b/src/vdbeInt.h
index d1de55eb1c..b231cf908e 100644
--- a/src/vdbeInt.h
+++ b/src/vdbeInt.h
@@ -489,11 +489,15 @@ int sqlite3VdbeSorterRewind(const VdbeCursor *, int *);
int sqlite3VdbeSorterWrite(const VdbeCursor *, Mem *);
int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int, int *);
-#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0
+#if !defined(SQLITE_OMIT_SHARED_CACHE)
void sqlite3VdbeEnter(Vdbe*);
- void sqlite3VdbeLeave(Vdbe*);
#else
# define sqlite3VdbeEnter(X)
+#endif
+
+#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0
+ void sqlite3VdbeLeave(Vdbe*);
+#else
# define sqlite3VdbeLeave(X)
#endif
diff --git a/src/vdbeapi.c b/src/vdbeapi.c
index 33c6ba3b28..4bc912b940 100644
--- a/src/vdbeapi.c
+++ b/src/vdbeapi.c
@@ -779,7 +779,7 @@ static SQLITE_NOINLINE void *createAggContext(sqlite3_context *p, int nByte){
** same context that was returned on prior calls.
*/
void *sqlite3_aggregate_context(sqlite3_context *p, int nByte){
- assert( p && p->pFunc && p->pFunc->xStep );
+ assert( p && p->pFunc && p->pFunc->xFinalize );
assert( sqlite3_mutex_held(p->pOut->db->mutex) );
testcase( nByte<0 );
if( (p->pMem->flags & MEM_Agg)==0 ){
@@ -870,7 +870,7 @@ failed:
** context.
*/
int sqlite3_aggregate_count(sqlite3_context *p){
- assert( p && p->pMem && p->pFunc && p->pFunc->xStep );
+ assert( p && p->pMem && p->pFunc && p->pFunc->xFinalize );
return p->pMem->n;
}
#endif
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index 9ced9480b7..17f1cb7dc4 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -35,6 +35,7 @@ Vdbe *sqlite3VdbeCreate(Parse *pParse){
assert( pParse->aLabel==0 );
assert( pParse->nLabel==0 );
assert( pParse->nOpAlloc==0 );
+ assert( pParse->szOpAlloc==0 );
return p;
}
@@ -124,7 +125,8 @@ static int growOpArray(Vdbe *v, int nOp){
assert( nNew>=(p->nOpAlloc+nOp) );
pNew = sqlite3DbRealloc(p->db, v->aOp, nNew*sizeof(Op));
if( pNew ){
- p->nOpAlloc = sqlite3DbMallocSize(p->db, pNew)/sizeof(Op);
+ p->szOpAlloc = sqlite3DbMallocSize(p->db, pNew);
+ p->nOpAlloc = p->szOpAlloc/sizeof(Op);
v->aOp = pNew;
}
return (pNew ? SQLITE_OK : SQLITE_NOMEM);
@@ -248,8 +250,7 @@ void sqlite3VdbeMultiLoad(Vdbe *p, int iDest, const char *zTypes, ...){
for(i=0; (c = zTypes[i])!=0; i++){
if( c=='s' ){
const char *z = va_arg(ap, const char*);
- int addr = sqlite3VdbeAddOp2(p, z==0 ? OP_Null : OP_String8, 0, iDest++);
- if( z ) sqlite3VdbeChangeP4(p, addr, z, 0);
+ sqlite3VdbeAddOp4(p, z==0 ? OP_Null : OP_String8, 0, iDest++, 0, z, 0);
}else{
assert( c=='i' );
sqlite3VdbeAddOp2(p, OP_Integer, va_arg(ap, int), iDest++);
@@ -303,8 +304,7 @@ int sqlite3VdbeAddOp4Dup8(
*/
void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere){
int j;
- int addr = sqlite3VdbeAddOp3(p, OP_ParseSchema, iDb, 0, 0);
- sqlite3VdbeChangeP4(p, addr, zWhere, P4_DYNAMIC);
+ sqlite3VdbeAddOp4(p, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC);
for(j=0; jdb->nDb; j++) sqlite3VdbeUsesBtree(p, j);
}
@@ -349,7 +349,7 @@ int sqlite3VdbeMakeLabel(Vdbe *v){
if( p->aLabel ){
p->aLabel[i] = -1;
}
- return -1-i;
+ return ADDR(i);
}
/*
@@ -359,7 +359,7 @@ int sqlite3VdbeMakeLabel(Vdbe *v){
*/
void sqlite3VdbeResolveLabel(Vdbe *v, int x){
Parse *p = v->pParse;
- int j = -1-x;
+ int j = ADDR(x);
assert( v->magic==VDBE_MAGIC_INIT );
assert( jnLabel );
assert( j>=0 );
@@ -586,8 +586,8 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
pOp->opflags = sqlite3OpcodeProperty[opcode];
if( (pOp->opflags & OPFLG_JUMP)!=0 && pOp->p2<0 ){
- assert( -1-pOp->p2nLabel );
- pOp->p2 = aLabel[-1-pOp->p2];
+ assert( ADDR(pOp->p2)nLabel );
+ pOp->p2 = aLabel[ADDR(pOp->p2)];
}
}
sqlite3DbFree(p->db, pParse->aLabel);
@@ -605,6 +605,20 @@ int sqlite3VdbeCurrentAddr(Vdbe *p){
return p->nOp;
}
+/*
+** Verify that at least N opcode slots are available in p without
+** having to malloc for more space (except when compiled using
+** SQLITE_TEST_REALLOC_STRESS). This interface is used during testing
+** to verify that certain calls to sqlite3VdbeAddOpList() can never
+** fail due to a OOM fault and hence that the return value from
+** sqlite3VdbeAddOpList() will always be non-NULL.
+*/
+#if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS)
+void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N){
+ assert( p->nOp + N <= p->pParse->nOpAlloc );
+}
+#endif
+
/*
** This function returns a pointer to the array of opcodes associated with
** the Vdbe passed as the first argument. It is the callers responsibility
@@ -630,29 +644,28 @@ VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg){
}
/*
-** Add a whole list of operations to the operation stack. Return the
-** address of the first operation added.
+** Add a whole list of operations to the operation stack. Return a
+** pointer to the first operation inserted.
*/
-int sqlite3VdbeAddOpList(Vdbe *p, int nOp, VdbeOpList const *aOp, int iLineno){
- int addr, i;
- VdbeOp *pOut;
+VdbeOp *sqlite3VdbeAddOpList(
+ Vdbe *p, /* Add opcodes to the prepared statement */
+ int nOp, /* Number of opcodes to add */
+ VdbeOpList const *aOp, /* The opcodes to be added */
+ int iLineno /* Source-file line number of first opcode */
+){
+ int i;
+ VdbeOp *pOut, *pFirst;
assert( nOp>0 );
assert( p->magic==VDBE_MAGIC_INIT );
if( p->nOp + nOp > p->pParse->nOpAlloc && growOpArray(p, nOp) ){
return 0;
}
- addr = p->nOp;
- pOut = &p->aOp[addr];
+ pFirst = pOut = &p->aOp[p->nOp];
for(i=0; ip2;
pOut->opcode = aOp->opcode;
pOut->p1 = aOp->p1;
- if( p2<0 ){
- assert( sqlite3OpcodeProperty[pOut->opcode] & OPFLG_JUMP );
- pOut->p2 = addr + ADDR(p2);
- }else{
- pOut->p2 = p2;
- }
+ pOut->p2 = aOp->p2;
+ assert( aOp->p2>=0 );
pOut->p3 = aOp->p3;
pOut->p4type = P4_NOTUSED;
pOut->p4.p = 0;
@@ -667,12 +680,12 @@ int sqlite3VdbeAddOpList(Vdbe *p, int nOp, VdbeOpList const *aOp, int iLineno){
#endif
#ifdef SQLITE_DEBUG
if( p->db->flags & SQLITE_VdbeAddopTrace ){
- sqlite3VdbePrintOp(0, i+addr, &p->aOp[i+addr]);
+ sqlite3VdbePrintOp(0, i+p->nOp, &p->aOp[i+p->nOp]);
}
#endif
}
p->nOp += nOp;
- return addr;
+ return pFirst;
}
#if defined(SQLITE_ENABLE_STMT_SCANSTATUS)
@@ -720,7 +733,7 @@ void sqlite3VdbeChangeP3(Vdbe *p, u32 addr, int val){
sqlite3VdbeGetOp(p,addr)->p3 = val;
}
void sqlite3VdbeChangeP5(Vdbe *p, u8 p5){
- sqlite3VdbeGetOp(p,-1)->p5 = p5;
+ if( !p->db->mallocFailed ) p->aOp[p->nOp-1].p5 = p5;
}
/*
@@ -808,7 +821,7 @@ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){
if( aOp ){
Op *pOp;
for(pOp=aOp; pOp<&aOp[nOp]; pOp++){
- freeP4(db, pOp->p4type, pOp->p4.p);
+ if( pOp->p4type ) freeP4(db, pOp->p4type, pOp->p4.p);
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
sqlite3DbFree(db, pOp->zComment);
#endif
@@ -830,15 +843,16 @@ void sqlite3VdbeLinkSubProgram(Vdbe *pVdbe, SubProgram *p){
/*
** Change the opcode at addr into OP_Noop
*/
-void sqlite3VdbeChangeToNoop(Vdbe *p, int addr){
- if( addrnOp ){
- VdbeOp *pOp = &p->aOp[addr];
- sqlite3 *db = p->db;
- freeP4(db, pOp->p4type, pOp->p4.p);
- memset(pOp, 0, sizeof(pOp[0]));
- pOp->opcode = OP_Noop;
- if( addr==p->nOp-1 ) p->nOp--;
- }
+int sqlite3VdbeChangeToNoop(Vdbe *p, int addr){
+ VdbeOp *pOp;
+ if( p->db->mallocFailed ) return 0;
+ assert( addr>=0 && addrnOp );
+ pOp = &p->aOp[addr];
+ freeP4(p->db, pOp->p4type, pOp->p4.p);
+ pOp->p4type = P4_NOTUSED;
+ pOp->p4.z = 0;
+ pOp->opcode = OP_Noop;
+ return 1;
}
/*
@@ -847,8 +861,7 @@ void sqlite3VdbeChangeToNoop(Vdbe *p, int addr){
*/
int sqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){
if( (p->nOp-1)>(p->pParse->iFixedOp) && p->aOp[p->nOp-1].opcode==op ){
- sqlite3VdbeChangeToNoop(p, p->nOp-1);
- return 1;
+ return sqlite3VdbeChangeToNoop(p, p->nOp-1);
}else{
return 0;
}
@@ -871,16 +884,34 @@ int sqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){
**
** If addr<0 then change P4 on the most recently inserted instruction.
*/
+static void SQLITE_NOINLINE vdbeChangeP4Full(
+ Vdbe *p,
+ Op *pOp,
+ const char *zP4,
+ int n
+){
+ if( pOp->p4type ){
+ freeP4(p->db, pOp->p4type, pOp->p4.p);
+ pOp->p4type = 0;
+ pOp->p4.p = 0;
+ }
+ if( n<0 ){
+ sqlite3VdbeChangeP4(p, (int)(pOp - p->aOp), zP4, n);
+ }else{
+ if( n==0 ) n = sqlite3Strlen30(zP4);
+ pOp->p4.z = sqlite3DbStrNDup(p->db, zP4, n);
+ pOp->p4type = P4_DYNAMIC;
+ }
+}
void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){
Op *pOp;
sqlite3 *db;
assert( p!=0 );
db = p->db;
assert( p->magic==VDBE_MAGIC_INIT );
- if( p->aOp==0 || db->mallocFailed ){
- if( n!=P4_VTAB ){
- freeP4(db, n, (void*)*(char**)&zP4);
- }
+ assert( p->aOp!=0 || db->mallocFailed );
+ if( db->mallocFailed ){
+ if( n!=P4_VTAB ) freeP4(db, n, (void*)*(char**)&zP4);
return;
}
assert( p->nOp>0 );
@@ -889,43 +920,20 @@ void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){
addr = p->nOp - 1;
}
pOp = &p->aOp[addr];
- assert( pOp->p4type==P4_NOTUSED
- || pOp->p4type==P4_INT32
- || pOp->p4type==P4_KEYINFO );
- freeP4(db, pOp->p4type, pOp->p4.p);
- pOp->p4.p = 0;
+ if( n>=0 || pOp->p4type ){
+ vdbeChangeP4Full(p, pOp, zP4, n);
+ return;
+ }
if( n==P4_INT32 ){
/* Note: this cast is safe, because the origin data point was an int
** that was cast to a (const char *). */
pOp->p4.i = SQLITE_PTR_TO_INT(zP4);
pOp->p4type = P4_INT32;
- }else if( zP4==0 ){
- pOp->p4.p = 0;
- pOp->p4type = P4_NOTUSED;
- }else if( n==P4_KEYINFO ){
- pOp->p4.p = (void*)zP4;
- pOp->p4type = P4_KEYINFO;
-#ifdef SQLITE_ENABLE_CURSOR_HINTS
- }else if( n==P4_EXPR ){
- /* Responsibility for deleting the Expr tree is handed over to the
- ** VDBE by this operation. The caller should have already invoked
- ** sqlite3ExprDup() or whatever other routine is needed to make a
- ** private copy of the tree. */
- pOp->p4.pExpr = (Expr*)zP4;
- pOp->p4type = P4_EXPR;
-#endif
- }else if( n==P4_VTAB ){
- pOp->p4.p = (void*)zP4;
- pOp->p4type = P4_VTAB;
- sqlite3VtabLock((VTable *)zP4);
- assert( ((VTable *)zP4)->db==p->db );
- }else if( n<0 ){
+ }else if( zP4!=0 ){
+ assert( n<0 );
pOp->p4.p = (void*)zP4;
pOp->p4type = (signed char)n;
- }else{
- if( n==0 ) n = sqlite3Strlen30(zP4);
- pOp->p4.z = sqlite3DbStrNDup(p->db, zP4, n);
- pOp->p4type = P4_DYNAMIC;
+ if( n==P4_VTAB ) sqlite3VtabLock((VTable*)zP4);
}
}
@@ -1321,7 +1329,7 @@ void sqlite3VdbeUsesBtree(Vdbe *p, int i){
}
}
-#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0
+#if !defined(SQLITE_OMIT_SHARED_CACHE)
/*
** If SQLite is compiled to support shared-cache mode and to be threadsafe,
** this routine obtains the mutex associated with each BtShared structure
@@ -1726,30 +1734,31 @@ void sqlite3VdbeIOTraceSql(Vdbe *p){
**
** nByte is the number of bytes of space needed.
**
-** *ppFrom points to available space and pEnd points to the end of the
-** available space. When space is allocated, *ppFrom is advanced past
-** the end of the allocated space.
+** pFrom points to *pnFrom bytes of available space. New space is allocated
+** from the end of the pFrom buffer and *pnFrom is decremented.
**
-** *pnByte is a counter of the number of bytes of space that have failed
-** to allocate. If there is insufficient space in *ppFrom to satisfy the
-** request, then increment *pnByte by the amount of the request.
+** *pnNeeded is a counter of the number of bytes of space that have failed
+** to allocate. If there is insufficient space in pFrom to satisfy the
+** request, then increment *pnNeeded by the amount of the request.
*/
static void *allocSpace(
void *pBuf, /* Where return pointer will be stored */
int nByte, /* Number of bytes to allocate */
- u8 **ppFrom, /* IN/OUT: Allocate from *ppFrom */
- u8 *pEnd, /* Pointer to 1 byte past the end of *ppFrom buffer */
- int *pnByte /* If allocation cannot be made, increment *pnByte */
+ u8 *pFrom, /* Memory available for allocation */
+ int *pnFrom, /* IN/OUT: Space available at pFrom */
+ int *pnNeeded /* If allocation cannot be made, increment *pnByte */
){
- assert( EIGHT_BYTE_ALIGNMENT(*ppFrom) );
- if( pBuf ) return pBuf;
- nByte = ROUND8(nByte);
- if( &(*ppFrom)[nByte] <= pEnd ){
- pBuf = (void*)*ppFrom;
- *ppFrom += nByte;
- }else{
- *pnByte += nByte;
+ assert( EIGHT_BYTE_ALIGNMENT(pFrom) );
+ if( pBuf==0 ){
+ nByte = ROUND8(nByte);
+ if( nByte <= *pnFrom ){
+ *pnFrom -= nByte;
+ pBuf = &pFrom[*pnFrom];
+ }else{
+ *pnNeeded += nByte;
+ }
}
+ assert( EIGHT_BYTE_ALIGNMENT(pBuf) );
return pBuf;
}
@@ -1822,8 +1831,8 @@ void sqlite3VdbeMakeReady(
int nArg; /* Number of arguments in subprograms */
int nOnce; /* Number of OP_Once instructions */
int n; /* Loop counter */
+ int nFree; /* Available free space */
u8 *zCsr; /* Memory available for allocation */
- u8 *zEnd; /* First byte past allocated memory */
int nByte; /* How much extra memory is needed */
assert( p!=0 );
@@ -1851,20 +1860,27 @@ void sqlite3VdbeMakeReady(
*/
nMem += nCursor;
- /* Allocate space for memory registers, SQL variables, VDBE cursors and
- ** an array to marshal SQL function arguments in.
+ /* zCsr will initially point to nFree bytes of unused space at the
+ ** end of the opcode array, p->aOp. The computation of nFree is
+ ** conservative - it might be smaller than the true number of free
+ ** bytes, but never larger. nFree must be a multiple of 8 - it is
+ ** rounded down if is not.
*/
- zCsr = (u8*)&p->aOp[p->nOp]; /* Memory avaliable for allocation */
- zEnd = (u8*)&p->aOp[pParse->nOpAlloc]; /* First byte past end of zCsr[] */
+ n = ROUND8(sizeof(Op)*p->nOp); /* Bytes of opcode space used */
+ zCsr = &((u8*)p->aOp)[n]; /* Unused opcode space */
+ assert( EIGHT_BYTE_ALIGNMENT(zCsr) );
+ nFree = ROUNDDOWN8(pParse->szOpAlloc - n); /* Bytes of unused space */
+ assert( nFree>=0 );
+ if( nFree>0 ){
+ memset(zCsr, 0, nFree);
+ assert( EIGHT_BYTE_ALIGNMENT(&zCsr[nFree]) );
+ }
resolveP2Values(p, &nArg);
p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort);
if( pParse->explain && nMem<10 ){
nMem = 10;
}
- memset(zCsr, 0, zEnd-zCsr);
- zCsr += (zCsr - (u8*)0)&7;
- assert( EIGHT_BYTE_ALIGNMENT(zCsr) );
p->expired = 0;
/* Memory for registers, parameters, cursor, etc, is allocated in two
@@ -1879,21 +1895,20 @@ void sqlite3VdbeMakeReady(
*/
do {
nByte = 0;
- p->aMem = allocSpace(p->aMem, nMem*sizeof(Mem), &zCsr, zEnd, &nByte);
- p->aVar = allocSpace(p->aVar, nVar*sizeof(Mem), &zCsr, zEnd, &nByte);
- p->apArg = allocSpace(p->apArg, nArg*sizeof(Mem*), &zCsr, zEnd, &nByte);
- p->azVar = allocSpace(p->azVar, nVar*sizeof(char*), &zCsr, zEnd, &nByte);
+ p->aMem = allocSpace(p->aMem, nMem*sizeof(Mem), zCsr, &nFree, &nByte);
+ p->aVar = allocSpace(p->aVar, nVar*sizeof(Mem), zCsr, &nFree, &nByte);
+ p->apArg = allocSpace(p->apArg, nArg*sizeof(Mem*), zCsr, &nFree, &nByte);
p->apCsr = allocSpace(p->apCsr, nCursor*sizeof(VdbeCursor*),
- &zCsr, zEnd, &nByte);
- p->aOnceFlag = allocSpace(p->aOnceFlag, nOnce, &zCsr, zEnd, &nByte);
+ zCsr, &nFree, &nByte);
+ p->aOnceFlag = allocSpace(p->aOnceFlag, nOnce, zCsr, &nFree, &nByte);
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
- p->anExec = allocSpace(p->anExec, p->nOp*sizeof(i64), &zCsr, zEnd, &nByte);
+ p->anExec = allocSpace(p->anExec, p->nOp*sizeof(i64), zCsr, &nFree, &nByte);
#endif
if( nByte ){
p->pFree = sqlite3DbMallocZero(db, nByte);
}
zCsr = p->pFree;
- zEnd = &zCsr[nByte];
+ nFree = nByte;
}while( nByte && !db->mallocFailed );
p->nCursor = nCursor;
@@ -1905,11 +1920,10 @@ void sqlite3VdbeMakeReady(
p->aVar[n].db = db;
}
}
- if( p->azVar && pParse->nzVar>0 ){
- p->nzVar = pParse->nzVar;
- memcpy(p->azVar, pParse->azVar, p->nzVar*sizeof(p->azVar[0]));
- memset(pParse->azVar, 0, pParse->nzVar*sizeof(pParse->azVar[0]));
- }
+ p->nzVar = pParse->nzVar;
+ p->azVar = pParse->azVar;
+ pParse->nzVar = 0;
+ pParse->azVar = 0;
if( p->aMem ){
p->aMem--; /* aMem[] goes from 1..nMem */
p->nMem = nMem; /* not from 0..nMem-1 */
@@ -2896,6 +2910,7 @@ void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){
sqlite3DbFree(db, pSub);
}
for(i=p->nzVar-1; i>=0; i--) sqlite3DbFree(db, p->azVar[i]);
+ sqlite3DbFree(db, p->azVar);
vdbeFreeOpArray(db, p->aOp, p->nOp);
sqlite3DbFree(db, p->aColName);
sqlite3DbFree(db, p->zSql);
@@ -3236,7 +3251,7 @@ u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){
assert( pMem->n + ((pMem->flags & MEM_Zero)?pMem->u.nZero:0)
== (int)sqlite3VdbeSerialTypeLen(serial_type) );
len = pMem->n;
- memcpy(buf, pMem->z, len);
+ if( len>0 ) memcpy(buf, pMem->z, len);
return len;
}
@@ -3640,9 +3655,9 @@ static int vdbeCompareMemString(
v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc);
n2 = v2==0 ? 0 : c2.n;
rc = pColl->xCmp(pColl->pUser, n1, v1, n2, v2);
+ if( (v1==0 || v2==0) && prcErr ) *prcErr = SQLITE_NOMEM;
sqlite3VdbeMemRelease(&c1);
sqlite3VdbeMemRelease(&c2);
- if( (v1==0 || v2==0) && prcErr ) *prcErr = SQLITE_NOMEM;
return rc;
}
}
@@ -3753,7 +3768,7 @@ int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const CollSeq *pColl){
return -1;
}
- assert( pMem1->enc==pMem2->enc );
+ assert( pMem1->enc==pMem2->enc || pMem1->db->mallocFailed );
assert( pMem1->enc==SQLITE_UTF8 ||
pMem1->enc==SQLITE_UTF16LE || pMem1->enc==SQLITE_UTF16BE );
@@ -4430,10 +4445,12 @@ void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){
** in memory obtained from sqlite3DbMalloc).
*/
void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){
- sqlite3 *db = p->db;
- sqlite3DbFree(db, p->zErrMsg);
- p->zErrMsg = sqlite3DbStrDup(db, pVtab->zErrMsg);
- sqlite3_free(pVtab->zErrMsg);
- pVtab->zErrMsg = 0;
+ if( pVtab->zErrMsg ){
+ sqlite3 *db = p->db;
+ sqlite3DbFree(db, p->zErrMsg);
+ p->zErrMsg = sqlite3DbStrDup(db, pVtab->zErrMsg);
+ sqlite3_free(pVtab->zErrMsg);
+ pVtab->zErrMsg = 0;
+ }
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff --git a/src/vdbeblob.c b/src/vdbeblob.c
index a4718efacc..f9015ad6f4 100644
--- a/src/vdbeblob.c
+++ b/src/vdbeblob.c
@@ -115,38 +115,6 @@ int sqlite3_blob_open(
){
int nAttempt = 0;
int iCol; /* Index of zColumn in row-record */
-
- /* This VDBE program seeks a btree cursor to the identified
- ** db/table/row entry. The reason for using a vdbe program instead
- ** of writing code to use the b-tree layer directly is that the
- ** vdbe program will take advantage of the various transaction,
- ** locking and error handling infrastructure built into the vdbe.
- **
- ** After seeking the cursor, the vdbe executes an OP_ResultRow.
- ** Code external to the Vdbe then "borrows" the b-tree cursor and
- ** uses it to implement the blob_read(), blob_write() and
- ** blob_bytes() functions.
- **
- ** The sqlite3_blob_close() function finalizes the vdbe program,
- ** which closes the b-tree cursor and (possibly) commits the
- ** transaction.
- */
- static const int iLn = VDBE_OFFSET_LINENO(4);
- static const VdbeOpList openBlob[] = {
- /* {OP_Transaction, 0, 0, 0}, // 0: Inserted separately */
- {OP_TableLock, 0, 0, 0}, /* 1: Acquire a read or write lock */
- /* One of the following two instructions is replaced by an OP_Noop. */
- {OP_OpenRead, 0, 0, 0}, /* 2: Open cursor 0 for reading */
- {OP_OpenWrite, 0, 0, 0}, /* 3: Open cursor 0 for read/write */
- {OP_Variable, 1, 1, 1}, /* 4: Push the rowid to the stack */
- {OP_NotExists, 0, 10, 1}, /* 5: Seek the cursor */
- {OP_Column, 0, 0, 1}, /* 6 */
- {OP_ResultRow, 1, 0, 0}, /* 7 */
- {OP_Goto, 0, 4, 0}, /* 8 */
- {OP_Close, 0, 0, 0}, /* 9 */
- {OP_Halt, 0, 0, 0}, /* 10 */
- };
-
int rc = SQLITE_OK;
char *zErr = 0;
Table *pTab;
@@ -265,45 +233,80 @@ int sqlite3_blob_open(
pBlob->pStmt = (sqlite3_stmt *)sqlite3VdbeCreate(pParse);
assert( pBlob->pStmt || db->mallocFailed );
if( pBlob->pStmt ){
+
+ /* This VDBE program seeks a btree cursor to the identified
+ ** db/table/row entry. The reason for using a vdbe program instead
+ ** of writing code to use the b-tree layer directly is that the
+ ** vdbe program will take advantage of the various transaction,
+ ** locking and error handling infrastructure built into the vdbe.
+ **
+ ** After seeking the cursor, the vdbe executes an OP_ResultRow.
+ ** Code external to the Vdbe then "borrows" the b-tree cursor and
+ ** uses it to implement the blob_read(), blob_write() and
+ ** blob_bytes() functions.
+ **
+ ** The sqlite3_blob_close() function finalizes the vdbe program,
+ ** which closes the b-tree cursor and (possibly) commits the
+ ** transaction.
+ */
+ static const int iLn = VDBE_OFFSET_LINENO(4);
+ static const VdbeOpList openBlob[] = {
+ /* addr/ofst */
+ /* {OP_Transaction, 0, 0, 0}, // 0/ inserted separately */
+ {OP_TableLock, 0, 0, 0}, /* 1/0: Acquire a read or write lock */
+ {OP_OpenRead, 0, 0, 0}, /* 2/1: Open a cursor */
+ {OP_Variable, 1, 1, 0}, /* 3/2: Move ?1 into reg[1] */
+ {OP_NotExists, 0, 8, 1}, /* 4/3: Seek the cursor */
+ {OP_Column, 0, 0, 1}, /* 5/4 */
+ {OP_ResultRow, 1, 0, 0}, /* 6/5 */
+ {OP_Goto, 0, 3, 0}, /* 7/6 */
+ {OP_Close, 0, 0, 0}, /* 8/7 */
+ {OP_Halt, 0, 0, 0}, /* 9/8 */
+ };
Vdbe *v = (Vdbe *)pBlob->pStmt;
int iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
-
+ VdbeOp *aOp;
sqlite3VdbeAddOp4Int(v, OP_Transaction, iDb, flags,
pTab->pSchema->schema_cookie,
pTab->pSchema->iGeneration);
sqlite3VdbeChangeP5(v, 1);
- sqlite3VdbeAddOpList(v, ArraySize(openBlob), openBlob, iLn);
+ aOp = sqlite3VdbeAddOpList(v, ArraySize(openBlob), openBlob, iLn);
/* Make sure a mutex is held on the table to be accessed */
sqlite3VdbeUsesBtree(v, iDb);
- /* Configure the OP_TableLock instruction */
+ if( db->mallocFailed==0 ){
+ assert( aOp!=0 );
+ /* Configure the OP_TableLock instruction */
#ifdef SQLITE_OMIT_SHARED_CACHE
- sqlite3VdbeChangeToNoop(v, 1);
+ aOp[0].opcode = OP_Noop;
#else
- sqlite3VdbeChangeP1(v, 1, iDb);
- sqlite3VdbeChangeP2(v, 1, pTab->tnum);
- sqlite3VdbeChangeP3(v, 1, flags);
- sqlite3VdbeChangeP4(v, 1, pTab->zName, P4_TRANSIENT);
+ aOp[0].p1 = iDb;
+ aOp[0].p2 = pTab->tnum;
+ aOp[0].p3 = flags;
+ sqlite3VdbeChangeP4(v, 1, pTab->zName, P4_TRANSIENT);
+ }
+ if( db->mallocFailed==0 ){
#endif
- /* Remove either the OP_OpenWrite or OpenRead. Set the P2
- ** parameter of the other to pTab->tnum. */
- sqlite3VdbeChangeToNoop(v, 3 - flags);
- sqlite3VdbeChangeP2(v, 2 + flags, pTab->tnum);
- sqlite3VdbeChangeP3(v, 2 + flags, iDb);
+ /* Remove either the OP_OpenWrite or OpenRead. Set the P2
+ ** parameter of the other to pTab->tnum. */
+ if( flags ) aOp[1].opcode = OP_OpenWrite;
+ aOp[1].p2 = pTab->tnum;
+ aOp[1].p3 = iDb;
+
+ /* Configure the number of columns. Configure the cursor to
+ ** think that the table has one more column than it really
+ ** does. An OP_Column to retrieve this imaginary column will
+ ** always return an SQL NULL. This is useful because it means
+ ** we can invoke OP_Column to fill in the vdbe cursors type
+ ** and offset cache without causing any IO.
+ */
+ aOp[1].p4type = P4_INT32;
+ aOp[1].p4.i = pTab->nCol+1;
+ aOp[4].p2 = pTab->nCol;
- /* Configure the number of columns. Configure the cursor to
- ** think that the table has one more column than it really
- ** does. An OP_Column to retrieve this imaginary column will
- ** always return an SQL NULL. This is useful because it means
- ** we can invoke OP_Column to fill in the vdbe cursors type
- ** and offset cache without causing any IO.
- */
- sqlite3VdbeChangeP4(v, 2+flags, SQLITE_INT_TO_PTR(pTab->nCol+1),P4_INT32);
- sqlite3VdbeChangeP2(v, 6, pTab->nCol);
- if( !db->mallocFailed ){
pParse->nVar = 1;
pParse->nMem = 1;
pParse->nTab = 1;
diff --git a/src/vdbemem.c b/src/vdbemem.c
index fae69b18a4..0eeb59ef85 100644
--- a/src/vdbemem.c
+++ b/src/vdbemem.c
@@ -1224,7 +1224,7 @@ static int valueFromFunction(
memset(&ctx, 0, sizeof(ctx));
ctx.pOut = pVal;
ctx.pFunc = pFunc;
- pFunc->xFunc(&ctx, nVal, apVal);
+ pFunc->xSFunc(&ctx, nVal, apVal);
if( ctx.isError ){
rc = ctx.isError;
sqlite3ErrorMsg(pCtx->pParse, "%s", sqlite3_value_text(pVal));
diff --git a/src/vdbesort.c b/src/vdbesort.c
index 54e538fd50..e095f80912 100644
--- a/src/vdbesort.c
+++ b/src/vdbesort.c
@@ -737,7 +737,7 @@ static int vdbePmaReaderInit(
rc = vdbePmaReaderSeek(pTask, pReadr, pFile, iStart);
if( rc==SQLITE_OK ){
- u64 nByte; /* Size of PMA in bytes */
+ u64 nByte = 0; /* Size of PMA in bytes */
rc = vdbePmaReadVarint(pReadr, &nByte);
pReadr->iEof = pReadr->iReadOff + nByte;
*pnByte += nByte;
diff --git a/src/vtab.c b/src/vtab.c
index 6054df3d71..ea79cd8cef 100644
--- a/src/vtab.c
+++ b/src/vtab.c
@@ -1016,7 +1016,7 @@ FuncDef *sqlite3VtabOverloadFunction(
Table *pTab;
sqlite3_vtab *pVtab;
sqlite3_module *pMod;
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**) = 0;
+ void (*xSFunc)(sqlite3_context*,int,sqlite3_value**) = 0;
void *pArg = 0;
FuncDef *pNew;
int rc = 0;
@@ -1044,7 +1044,7 @@ FuncDef *sqlite3VtabOverloadFunction(
for(z=(unsigned char*)zLowerName; *z; z++){
*z = sqlite3UpperToLower[*z];
}
- rc = pMod->xFindFunction(pVtab, nArg, zLowerName, &xFunc, &pArg);
+ rc = pMod->xFindFunction(pVtab, nArg, zLowerName, &xSFunc, &pArg);
sqlite3DbFree(db, zLowerName);
}
if( rc==0 ){
@@ -1061,7 +1061,7 @@ FuncDef *sqlite3VtabOverloadFunction(
*pNew = *pDef;
pNew->zName = (char *)&pNew[1];
memcpy(pNew->zName, pDef->zName, sqlite3Strlen30(pDef->zName)+1);
- pNew->xFunc = xFunc;
+ pNew->xSFunc = xSFunc;
pNew->pUserData = pArg;
pNew->funcFlags |= SQLITE_FUNC_EPHEM;
return pNew;
diff --git a/src/vxworks.h b/src/vxworks.h
index 45a44453a7..60c41a19b8 100644
--- a/src/vxworks.h
+++ b/src/vxworks.h
@@ -26,4 +26,6 @@
#else
/* This is not VxWorks. */
#define OS_VXWORKS 0
+#define HAVE_FCHOWN 1
+#define HAVE_READLINK 1
#endif /* defined(_WRS_KERNEL) */
diff --git a/src/wal.c b/src/wal.c
index d87d2c17ce..7cf2cabffb 100644
--- a/src/wal.c
+++ b/src/wal.c
@@ -272,7 +272,8 @@ int sqlite3WalTrace = 0;
/*
** Indices of various locking bytes. WAL_NREADER is the number
-** of available reader locks and should be at least 3.
+** of available reader locks and should be at least 3. The default
+** is SQLITE_SHM_NLOCK==8 and WAL_NREADER==5.
*/
#define WAL_WRITE_LOCK 0
#define WAL_ALL_BUT_WRITE 1
@@ -292,7 +293,10 @@ typedef struct WalCkptInfo WalCkptInfo;
** The following object holds a copy of the wal-index header content.
**
** The actual header in the wal-index consists of two copies of this
-** object.
+** object followed by one instance of the WalCkptInfo object.
+** For all versions of SQLite through 3.10.0 and probably beyond,
+** the locking bytes (WalCkptInfo.aLock) start at offset 120 and
+** the total header size is 136 bytes.
**
** The szPage value can be any power of 2 between 512 and 32768, inclusive.
** Or it can be 1 to represent a 65536-byte page. The latter case was
@@ -325,6 +329,16 @@ struct WalIndexHdr {
** However, a WAL_WRITE_LOCK thread can move the value of nBackfill from
** mxFrame back to zero when the WAL is reset.
**
+** nBackfillAttempted is the largest value of nBackfill that a checkpoint
+** has attempted to achieve. Normally nBackfill==nBackfillAtempted, however
+** the nBackfillAttempted is set before any backfilling is done and the
+** nBackfill is only set after all backfilling completes. So if a checkpoint
+** crashes, nBackfillAttempted might be larger than nBackfill. The
+** WalIndexHdr.mxFrame must never be less than nBackfillAttempted.
+**
+** The aLock[] field is a set of bytes used for locking. These bytes should
+** never be read or written.
+**
** There is one entry in aReadMark[] for each reader lock. If a reader
** holds read-lock K, then the value in aReadMark[K] is no greater than
** the mxFrame for that reader. The value READMARK_NOT_USED (0xffffffff)
@@ -364,6 +378,9 @@ struct WalIndexHdr {
struct WalCkptInfo {
u32 nBackfill; /* Number of WAL frames backfilled into DB */
u32 aReadMark[WAL_NREADER]; /* Reader marks */
+ u8 aLock[SQLITE_SHM_NLOCK]; /* Reserved space for locks */
+ u32 nBackfillAttempted; /* WAL frames perhaps written, or maybe not */
+ u32 notUsed0; /* Available for future enhancements */
};
#define READMARK_NOT_USED 0xffffffff
@@ -373,9 +390,8 @@ struct WalCkptInfo {
** only support mandatory file-locks, we do not read or write data
** from the region of the file on which locks are applied.
*/
-#define WALINDEX_LOCK_OFFSET (sizeof(WalIndexHdr)*2 + sizeof(WalCkptInfo))
-#define WALINDEX_LOCK_RESERVED 16
-#define WALINDEX_HDR_SIZE (WALINDEX_LOCK_OFFSET+WALINDEX_LOCK_RESERVED)
+#define WALINDEX_LOCK_OFFSET (sizeof(WalIndexHdr)*2+offsetof(WalCkptInfo,aLock))
+#define WALINDEX_HDR_SIZE (sizeof(WalIndexHdr)*2+sizeof(WalCkptInfo))
/* Size of header before each frame in wal */
#define WAL_FRAME_HDRSIZE 24
@@ -429,11 +445,15 @@ struct Wal {
u8 padToSectorBoundary; /* Pad transactions out to the next sector */
WalIndexHdr hdr; /* Wal-index header for current transaction */
u32 minFrame; /* Ignore wal frames before this one */
+ u32 iReCksum; /* On commit, recalculate checksums from here */
const char *zWalName; /* Name of WAL file */
u32 nCkpt; /* Checkpoint sequence counter in the wal-header */
#ifdef SQLITE_DEBUG
u8 lockError; /* True if a locking error has occurred */
#endif
+#ifdef SQLITE_ENABLE_SNAPSHOT
+ WalIndexHdr *pSnapshot; /* Start transaction here if not NULL */
+#endif
};
/*
@@ -679,14 +699,18 @@ static void walEncodeFrame(
assert( WAL_FRAME_HDRSIZE==24 );
sqlite3Put4byte(&aFrame[0], iPage);
sqlite3Put4byte(&aFrame[4], nTruncate);
- memcpy(&aFrame[8], pWal->hdr.aSalt, 8);
+ if( pWal->iReCksum==0 ){
+ memcpy(&aFrame[8], pWal->hdr.aSalt, 8);
- nativeCksum = (pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN);
- walChecksumBytes(nativeCksum, aFrame, 8, aCksum, aCksum);
- walChecksumBytes(nativeCksum, aData, pWal->szPage, aCksum, aCksum);
+ nativeCksum = (pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN);
+ walChecksumBytes(nativeCksum, aFrame, 8, aCksum, aCksum);
+ walChecksumBytes(nativeCksum, aData, pWal->szPage, aCksum, aCksum);
- sqlite3Put4byte(&aFrame[16], aCksum[0]);
- sqlite3Put4byte(&aFrame[20], aCksum[1]);
+ sqlite3Put4byte(&aFrame[16], aCksum[0]);
+ sqlite3Put4byte(&aFrame[20], aCksum[1]);
+ }else{
+ memset(&aFrame[8], 0, 16);
+ }
}
/*
@@ -789,10 +813,9 @@ static void walUnlockShared(Wal *pWal, int lockIdx){
SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED);
WALTRACE(("WAL%p: release SHARED-%s\n", pWal, walLockName(lockIdx)));
}
-static int walLockExclusive(Wal *pWal, int lockIdx, int n, int fBlock){
+static int walLockExclusive(Wal *pWal, int lockIdx, int n){
int rc;
if( pWal->exclusiveMode ) return SQLITE_OK;
- if( fBlock ) sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_WAL_BLOCK, 0);
rc = sqlite3OsShmLock(pWal->pDbFd, lockIdx, n,
SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE);
WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal,
@@ -1078,7 +1101,7 @@ static int walIndexRecover(Wal *pWal){
assert( pWal->writeLock );
iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock;
nLock = SQLITE_SHM_NLOCK - iLock;
- rc = walLockExclusive(pWal, iLock, nLock, 0);
+ rc = walLockExclusive(pWal, iLock, nLock);
if( rc ){
return rc;
}
@@ -1199,6 +1222,7 @@ finished:
*/
pInfo = walCkptInfo(pWal);
pInfo->nBackfill = 0;
+ pInfo->nBackfillAttempted = pWal->hdr.mxFrame;
pInfo->aReadMark[0] = 0;
for(i=1; iaReadMark[i] = READMARK_NOT_USED;
if( pWal->hdr.mxFrame ) pInfo->aReadMark[1] = pWal->hdr.mxFrame;
@@ -1270,7 +1294,11 @@ int sqlite3WalOpen(
/* In the amalgamation, the os_unix.c and os_win.c source files come before
** this source file. Verify that the #defines of the locking byte offsets
** in os_unix.c and os_win.c agree with the WALINDEX_LOCK_OFFSET value.
+ ** For that matter, if the lock offset ever changes from its initial design
+ ** value of 120, we need to know that so there is an assert() to check it.
*/
+ assert( 120==WALINDEX_LOCK_OFFSET );
+ assert( 136==WALINDEX_HDR_SIZE );
#ifdef WIN_SHM_BASE
assert( WIN_SHM_BASE==WALINDEX_LOCK_OFFSET );
#endif
@@ -1616,7 +1644,7 @@ static int walBusyLock(
){
int rc;
do {
- rc = walLockExclusive(pWal, lockIdx, n, 0);
+ rc = walLockExclusive(pWal, lockIdx, n);
}while( xBusy && rc==SQLITE_BUSY && xBusy(pBusyArg) );
return rc;
}
@@ -1656,6 +1684,7 @@ static void walRestartHdr(Wal *pWal, u32 salt1){
memcpy(&pWal->hdr.aSalt[1], &salt1, 4);
walIndexWriteHdr(pWal);
pInfo->nBackfill = 0;
+ pInfo->nBackfillAttempted = 0;
pInfo->aReadMark[1] = 0;
for(i=2; iaReadMark[i] = READMARK_NOT_USED;
assert( pInfo->aReadMark[0]==0 );
@@ -1765,6 +1794,8 @@ static int walCheckpoint(
i64 nSize; /* Current size of database file */
u32 nBackfill = pInfo->nBackfill;
+ pInfo->nBackfillAttempted = mxSafeFrame;
+
/* Sync the WAL to disk */
if( sync_flags ){
rc = sqlite3OsSync(pWal->pWalFd, sync_flags);
@@ -2057,7 +2088,7 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
walUnlockShared(pWal, WAL_WRITE_LOCK);
rc = SQLITE_READONLY_RECOVERY;
}
- }else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1, 1)) ){
+ }else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){
pWal->writeLock = 1;
if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){
badHdr = walIndexTryHdr(pWal, pChanged);
@@ -2148,6 +2179,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
int mxI; /* Index of largest aReadMark[] value */
int i; /* Loop counter */
int rc = SQLITE_OK; /* Return code */
+ u32 mxFrame; /* Wal frame to lock to */
assert( pWal->readLock<0 ); /* Not currently locked */
@@ -2211,7 +2243,12 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
}
pInfo = walCkptInfo(pWal);
- if( !useWal && pInfo->nBackfill==pWal->hdr.mxFrame ){
+ if( !useWal && pInfo->nBackfill==pWal->hdr.mxFrame
+#ifdef SQLITE_ENABLE_SNAPSHOT
+ && (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0
+ || 0==memcmp(&pWal->hdr, pWal->pSnapshot, sizeof(WalIndexHdr)))
+#endif
+ ){
/* The WAL has been completely backfilled (or it is empty).
** and can be safely ignored.
*/
@@ -2249,85 +2286,88 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
*/
mxReadMark = 0;
mxI = 0;
+ mxFrame = pWal->hdr.mxFrame;
+#ifdef SQLITE_ENABLE_SNAPSHOT
+ if( pWal->pSnapshot && pWal->pSnapshot->mxFramepSnapshot->mxFrame;
+ }
+#endif
for(i=1; iaReadMark[i];
- if( mxReadMark<=thisMark && thisMark<=pWal->hdr.mxFrame ){
+ if( mxReadMark<=thisMark && thisMark<=mxFrame ){
assert( thisMark!=READMARK_NOT_USED );
mxReadMark = thisMark;
mxI = i;
}
}
- /* There was once an "if" here. The extra "{" is to preserve indentation. */
- {
- if( (pWal->readOnly & WAL_SHM_RDONLY)==0
- && (mxReadMarkhdr.mxFrame || mxI==0)
- ){
- for(i=1; iaReadMark[i] = pWal->hdr.mxFrame;
- mxI = i;
- walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
- break;
- }else if( rc!=SQLITE_BUSY ){
- return rc;
- }
+ if( (pWal->readOnly & WAL_SHM_RDONLY)==0
+ && (mxReadMarkaReadMark[i] = mxFrame;
+ mxI = i;
+ walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
+ break;
+ }else if( rc!=SQLITE_BUSY ){
+ return rc;
}
}
- if( mxI==0 ){
- assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
- return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK;
- }
+ }
+ if( mxI==0 ){
+ assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
+ return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK;
+ }
- rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
- if( rc ){
- return rc==SQLITE_BUSY ? WAL_RETRY : rc;
- }
- /* Now that the read-lock has been obtained, check that neither the
- ** value in the aReadMark[] array or the contents of the wal-index
- ** header have changed.
- **
- ** It is necessary to check that the wal-index header did not change
- ** between the time it was read and when the shared-lock was obtained
- ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility
- ** that the log file may have been wrapped by a writer, or that frames
- ** that occur later in the log than pWal->hdr.mxFrame may have been
- ** copied into the database by a checkpointer. If either of these things
- ** happened, then reading the database with the current value of
- ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry
- ** instead.
- **
- ** Before checking that the live wal-index header has not changed
- ** since it was read, set Wal.minFrame to the first frame in the wal
- ** file that has not yet been checkpointed. This client will not need
- ** to read any frames earlier than minFrame from the wal file - they
- ** can be safely read directly from the database file.
- **
- ** Because a ShmBarrier() call is made between taking the copy of
- ** nBackfill and checking that the wal-header in shared-memory still
- ** matches the one cached in pWal->hdr, it is guaranteed that the
- ** checkpointer that set nBackfill was not working with a wal-index
- ** header newer than that cached in pWal->hdr. If it were, that could
- ** cause a problem. The checkpointer could omit to checkpoint
- ** a version of page X that lies before pWal->minFrame (call that version
- ** A) on the basis that there is a newer version (version B) of the same
- ** page later in the wal file. But if version B happens to like past
- ** frame pWal->hdr.mxFrame - then the client would incorrectly assume
- ** that it can read version A from the database file. However, since
- ** we can guarantee that the checkpointer that set nBackfill could not
- ** see any pages past pWal->hdr.mxFrame, this problem does not come up.
- */
- pWal->minFrame = pInfo->nBackfill+1;
- walShmBarrier(pWal);
- if( pInfo->aReadMark[mxI]!=mxReadMark
- || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
- ){
- walUnlockShared(pWal, WAL_READ_LOCK(mxI));
- return WAL_RETRY;
- }else{
- assert( mxReadMark<=pWal->hdr.mxFrame );
- pWal->readLock = (i16)mxI;
- }
+ rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
+ if( rc ){
+ return rc==SQLITE_BUSY ? WAL_RETRY : rc;
+ }
+ /* Now that the read-lock has been obtained, check that neither the
+ ** value in the aReadMark[] array or the contents of the wal-index
+ ** header have changed.
+ **
+ ** It is necessary to check that the wal-index header did not change
+ ** between the time it was read and when the shared-lock was obtained
+ ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility
+ ** that the log file may have been wrapped by a writer, or that frames
+ ** that occur later in the log than pWal->hdr.mxFrame may have been
+ ** copied into the database by a checkpointer. If either of these things
+ ** happened, then reading the database with the current value of
+ ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry
+ ** instead.
+ **
+ ** Before checking that the live wal-index header has not changed
+ ** since it was read, set Wal.minFrame to the first frame in the wal
+ ** file that has not yet been checkpointed. This client will not need
+ ** to read any frames earlier than minFrame from the wal file - they
+ ** can be safely read directly from the database file.
+ **
+ ** Because a ShmBarrier() call is made between taking the copy of
+ ** nBackfill and checking that the wal-header in shared-memory still
+ ** matches the one cached in pWal->hdr, it is guaranteed that the
+ ** checkpointer that set nBackfill was not working with a wal-index
+ ** header newer than that cached in pWal->hdr. If it were, that could
+ ** cause a problem. The checkpointer could omit to checkpoint
+ ** a version of page X that lies before pWal->minFrame (call that version
+ ** A) on the basis that there is a newer version (version B) of the same
+ ** page later in the wal file. But if version B happens to like past
+ ** frame pWal->hdr.mxFrame - then the client would incorrectly assume
+ ** that it can read version A from the database file. However, since
+ ** we can guarantee that the checkpointer that set nBackfill could not
+ ** see any pages past pWal->hdr.mxFrame, this problem does not come up.
+ */
+ pWal->minFrame = pInfo->nBackfill+1;
+ walShmBarrier(pWal);
+ if( pInfo->aReadMark[mxI]!=mxReadMark
+ || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
+ ){
+ walUnlockShared(pWal, WAL_READ_LOCK(mxI));
+ return WAL_RETRY;
+ }else{
+ assert( mxReadMark<=pWal->hdr.mxFrame );
+ pWal->readLock = (i16)mxI;
}
return rc;
}
@@ -2350,6 +2390,14 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
int rc; /* Return code */
int cnt = 0; /* Number of TryBeginRead attempts */
+#ifdef SQLITE_ENABLE_SNAPSHOT
+ int bChanged = 0;
+ WalIndexHdr *pSnapshot = pWal->pSnapshot;
+ if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){
+ bChanged = 1;
+ }
+#endif
+
do{
rc = walTryBeginRead(pWal, pChanged, 0, ++cnt);
}while( rc==WAL_RETRY );
@@ -2357,6 +2405,66 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
testcase( (rc&0xff)==SQLITE_IOERR );
testcase( rc==SQLITE_PROTOCOL );
testcase( rc==SQLITE_OK );
+
+#ifdef SQLITE_ENABLE_SNAPSHOT
+ if( rc==SQLITE_OK ){
+ if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){
+ /* At this point the client has a lock on an aReadMark[] slot holding
+ ** a value equal to or smaller than pSnapshot->mxFrame, but pWal->hdr
+ ** is populated with the wal-index header corresponding to the head
+ ** of the wal file. Verify that pSnapshot is still valid before
+ ** continuing. Reasons why pSnapshot might no longer be valid:
+ **
+ ** (1) The WAL file has been reset since the snapshot was taken.
+ ** In this case, the salt will have changed.
+ **
+ ** (2) A checkpoint as been attempted that wrote frames past
+ ** pSnapshot->mxFrame into the database file. Note that the
+ ** checkpoint need not have completed for this to cause problems.
+ */
+ volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
+
+ assert( pWal->readLock>0 || pWal->hdr.mxFrame==0 );
+ assert( pInfo->aReadMark[pWal->readLock]<=pSnapshot->mxFrame );
+
+ /* It is possible that there is a checkpointer thread running
+ ** concurrent with this code. If this is the case, it may be that the
+ ** checkpointer has already determined that it will checkpoint
+ ** snapshot X, where X is later in the wal file than pSnapshot, but
+ ** has not yet set the pInfo->nBackfillAttempted variable to indicate
+ ** its intent. To avoid the race condition this leads to, ensure that
+ ** there is no checkpointer process by taking a shared CKPT lock
+ ** before checking pInfo->nBackfillAttempted. */
+ rc = walLockShared(pWal, WAL_CKPT_LOCK);
+
+ if( rc==SQLITE_OK ){
+ /* Check that the wal file has not been wrapped. Assuming that it has
+ ** not, also check that no checkpointer has attempted to checkpoint any
+ ** frames beyond pSnapshot->mxFrame. If either of these conditions are
+ ** true, return SQLITE_BUSY_SNAPSHOT. Otherwise, overwrite pWal->hdr
+ ** with *pSnapshot and set *pChanged as appropriate for opening the
+ ** snapshot. */
+ if( !memcmp(pSnapshot->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt))
+ && pSnapshot->mxFrame>=pInfo->nBackfillAttempted
+ ){
+ assert( pWal->readLock>0 );
+ memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr));
+ *pChanged = bChanged;
+ }else{
+ rc = SQLITE_BUSY_SNAPSHOT;
+ }
+
+ /* Release the shared CKPT lock obtained above. */
+ walUnlockShared(pWal, WAL_CKPT_LOCK);
+ }
+
+
+ if( rc!=SQLITE_OK ){
+ sqlite3WalEndReadTransaction(pWal);
+ }
+ }
+ }
+#endif
return rc;
}
@@ -2529,6 +2637,7 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){
/* Cannot start a write transaction without first holding a read
** transaction. */
assert( pWal->readLock>=0 );
+ assert( pWal->writeLock==0 && pWal->iReCksum==0 );
if( pWal->readOnly ){
return SQLITE_READONLY;
@@ -2537,7 +2646,7 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){
/* Only one writer allowed at a time. Get the write lock. Return
** SQLITE_BUSY if unable.
*/
- rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1, 0);
+ rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1);
if( rc ){
return rc;
}
@@ -2564,6 +2673,7 @@ int sqlite3WalEndWriteTransaction(Wal *pWal){
if( pWal->writeLock ){
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
pWal->writeLock = 0;
+ pWal->iReCksum = 0;
pWal->truncateOnCommit = 0;
}
return SQLITE_OK;
@@ -2682,7 +2792,7 @@ static int walRestartLog(Wal *pWal){
if( pInfo->nBackfill>0 ){
u32 salt1;
sqlite3_randomness(4, &salt1);
- rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1, 0);
+ rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
if( rc==SQLITE_OK ){
/* If all readers are using WAL_READ_LOCK(0) (in other words if no
** readers are currently using the WAL), then the transactions
@@ -2782,6 +2892,59 @@ static int walWriteOneFrame(
return rc;
}
+/*
+** This function is called as part of committing a transaction within which
+** one or more frames have been overwritten. It updates the checksums for
+** all frames written to the wal file by the current transaction starting
+** with the earliest to have been overwritten.
+**
+** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
+*/
+static int walRewriteChecksums(Wal *pWal, u32 iLast){
+ const int szPage = pWal->szPage;/* Database page size */
+ int rc = SQLITE_OK; /* Return code */
+ u8 *aBuf; /* Buffer to load data from wal file into */
+ u8 aFrame[WAL_FRAME_HDRSIZE]; /* Buffer to assemble frame-headers in */
+ u32 iRead; /* Next frame to read from wal file */
+ i64 iCksumOff;
+
+ aBuf = sqlite3_malloc(szPage + WAL_FRAME_HDRSIZE);
+ if( aBuf==0 ) return SQLITE_NOMEM;
+
+ /* Find the checksum values to use as input for the recalculating the
+ ** first checksum. If the first frame is frame 1 (implying that the current
+ ** transaction restarted the wal file), these values must be read from the
+ ** wal-file header. Otherwise, read them from the frame header of the
+ ** previous frame. */
+ assert( pWal->iReCksum>0 );
+ if( pWal->iReCksum==1 ){
+ iCksumOff = 24;
+ }else{
+ iCksumOff = walFrameOffset(pWal->iReCksum-1, szPage) + 16;
+ }
+ rc = sqlite3OsRead(pWal->pWalFd, aBuf, sizeof(u32)*2, iCksumOff);
+ pWal->hdr.aFrameCksum[0] = sqlite3Get4byte(aBuf);
+ pWal->hdr.aFrameCksum[1] = sqlite3Get4byte(&aBuf[sizeof(u32)]);
+
+ iRead = pWal->iReCksum;
+ pWal->iReCksum = 0;
+ for(; rc==SQLITE_OK && iRead<=iLast; iRead++){
+ i64 iOff = walFrameOffset(iRead, szPage);
+ rc = sqlite3OsRead(pWal->pWalFd, aBuf, szPage+WAL_FRAME_HDRSIZE, iOff);
+ if( rc==SQLITE_OK ){
+ u32 iPgno, nDbSize;
+ iPgno = sqlite3Get4byte(aBuf);
+ nDbSize = sqlite3Get4byte(&aBuf[4]);
+
+ walEncodeFrame(pWal, iPgno, nDbSize, &aBuf[WAL_FRAME_HDRSIZE], aFrame);
+ rc = sqlite3OsWrite(pWal->pWalFd, aFrame, sizeof(aFrame), iOff);
+ }
+ }
+
+ sqlite3_free(aBuf);
+ return rc;
+}
+
/*
** Write a set of frames to the log. The caller must hold the write-lock
** on the log file (obtained using sqlite3WalBeginWriteTransaction()).
@@ -2802,6 +2965,8 @@ int sqlite3WalFrames(
int szFrame; /* The size of a single frame */
i64 iOffset; /* Next byte to write in WAL file */
WalWriter w; /* The writer */
+ u32 iFirst = 0; /* First frame that may be overwritten */
+ WalIndexHdr *pLive; /* Pointer to shared header */
assert( pList );
assert( pWal->writeLock );
@@ -2817,6 +2982,11 @@ int sqlite3WalFrames(
}
#endif
+ pLive = (WalIndexHdr*)walIndexHdr(pWal);
+ if( memcmp(&pWal->hdr, (void *)pLive, sizeof(WalIndexHdr))!=0 ){
+ iFirst = pLive->mxFrame+1;
+ }
+
/* See if it is possible to write these frames into the start of the
** log file, instead of appending to it at pWal->hdr.mxFrame.
*/
@@ -2881,6 +3051,27 @@ int sqlite3WalFrames(
/* Write all frames into the log file exactly once */
for(p=pList; p; p=p->pDirty){
int nDbSize; /* 0 normally. Positive == commit flag */
+
+ /* Check if this page has already been written into the wal file by
+ ** the current transaction. If so, overwrite the existing frame and
+ ** set Wal.writeLock to WAL_WRITELOCK_RECKSUM - indicating that
+ ** checksums must be recomputed when the transaction is committed. */
+ if( iFirst && (p->pDirty || isCommit==0) ){
+ u32 iWrite = 0;
+ VVA_ONLY(rc =) sqlite3WalFindFrame(pWal, p->pgno, &iWrite);
+ assert( rc==SQLITE_OK || iWrite==0 );
+ if( iWrite>=iFirst ){
+ i64 iOff = walFrameOffset(iWrite, szPage) + WAL_FRAME_HDRSIZE;
+ if( pWal->iReCksum==0 || iWriteiReCksum ){
+ pWal->iReCksum = iWrite;
+ }
+ rc = sqlite3OsWrite(pWal->pWalFd, p->pData, szPage, iOff);
+ if( rc ) return rc;
+ p->flags &= ~PGHDR_WAL_APPEND;
+ continue;
+ }
+ }
+
iFrame++;
assert( iOffset==walFrameOffset(iFrame, szPage) );
nDbSize = (isCommit && p->pDirty==0) ? nTruncate : 0;
@@ -2888,6 +3079,13 @@ int sqlite3WalFrames(
if( rc ) return rc;
pLast = p;
iOffset += szFrame;
+ p->flags |= PGHDR_WAL_APPEND;
+ }
+
+ /* Recalculate checksums within the wal file if required. */
+ if( isCommit && pWal->iReCksum ){
+ rc = walRewriteChecksums(pWal, iFrame);
+ if( rc ) return rc;
}
/* If this is the end of a transaction, then we might need to pad
@@ -2939,6 +3137,7 @@ int sqlite3WalFrames(
*/
iFrame = pWal->hdr.mxFrame;
for(p=pList; p && rc==SQLITE_OK; p=p->pDirty){
+ if( (p->flags & PGHDR_WAL_APPEND)==0 ) continue;
iFrame++;
rc = walIndexAppend(pWal, iFrame, p->pgno);
}
@@ -3007,7 +3206,7 @@ int sqlite3WalCheckpoint(
/* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive
** "checkpoint" lock on the database file. */
- rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1, 0);
+ rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
if( rc ){
/* EVIDENCE-OF: R-10421-19736 If any other process is running a
** checkpoint operation at the same time, the lock cannot be obtained and
@@ -3051,6 +3250,7 @@ int sqlite3WalCheckpoint(
/* Copy data from the log to the database file. */
if( rc==SQLITE_OK ){
+
if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){
rc = SQLITE_CORRUPT_BKPT;
}else{
@@ -3166,6 +3366,35 @@ int sqlite3WalHeapMemory(Wal *pWal){
return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE );
}
+#ifdef SQLITE_ENABLE_SNAPSHOT
+/* Create a snapshot object. The content of a snapshot is opaque to
+** every other subsystem, so the WAL module can put whatever it needs
+** in the object.
+*/
+int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){
+ int rc = SQLITE_OK;
+ WalIndexHdr *pRet;
+
+ assert( pWal->readLock>=0 && pWal->writeLock==0 );
+
+ pRet = (WalIndexHdr*)sqlite3_malloc(sizeof(WalIndexHdr));
+ if( pRet==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memcpy(pRet, &pWal->hdr, sizeof(WalIndexHdr));
+ *ppSnapshot = (sqlite3_snapshot*)pRet;
+ }
+
+ return rc;
+}
+
+/* Try to open on pSnapshot when the next read-transaction starts
+*/
+void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot){
+ pWal->pSnapshot = (WalIndexHdr*)pSnapshot;
+}
+#endif /* SQLITE_ENABLE_SNAPSHOT */
+
#ifdef SQLITE_ENABLE_ZIPVFS
/*
** If the argument is not NULL, it points to a Wal object that holds a
@@ -3178,4 +3407,10 @@ int sqlite3WalFramesize(Wal *pWal){
}
#endif
+/* Return the sqlite3_file object for the WAL file
+*/
+sqlite3_file *sqlite3WalFile(Wal *pWal){
+ return pWal->pWalFd;
+}
+
#endif /* #ifndef SQLITE_OMIT_WAL */
diff --git a/src/wal.h b/src/wal.h
index 092546354b..97e6ab4f10 100644
--- a/src/wal.h
+++ b/src/wal.h
@@ -44,6 +44,7 @@
# define sqlite3WalHeapMemory(z) 0
# define sqlite3WalFramesize(z) 0
# define sqlite3WalFindFrame(x,y,z) 0
+# define sqlite3WalFile(x) 0
#else
#define WAL_SAVEPOINT_NDATA 4
@@ -126,6 +127,11 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op);
*/
int sqlite3WalHeapMemory(Wal *pWal);
+#ifdef SQLITE_ENABLE_SNAPSHOT
+int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
+void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);
+#endif
+
#ifdef SQLITE_ENABLE_ZIPVFS
/* If the WAL file is not empty, return the number of bytes of content
** stored in each frame (i.e. the db page-size when the WAL was created).
@@ -133,5 +139,8 @@ int sqlite3WalHeapMemory(Wal *pWal);
int sqlite3WalFramesize(Wal *pWal);
#endif
+/* Return the sqlite3_file object for the WAL file */
+sqlite3_file *sqlite3WalFile(Wal *pWal);
+
#endif /* ifndef SQLITE_OMIT_WAL */
#endif /* _WAL_H_ */
diff --git a/src/walker.c b/src/walker.c
index 81e0f2cd60..1e0ad32871 100644
--- a/src/walker.c
+++ b/src/walker.c
@@ -36,9 +36,8 @@
** The return value from this routine is WRC_Abort to abandon the tree walk
** and WRC_Continue to continue.
*/
-int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){
+static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){
int rc;
- if( pExpr==0 ) return WRC_Continue;
testcase( ExprHasProperty(pExpr, EP_TokenOnly) );
testcase( ExprHasProperty(pExpr, EP_Reduced) );
rc = pWalker->xExprCallback(pWalker, pExpr);
@@ -54,6 +53,9 @@ int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){
}
return rc & WRC_Abort;
}
+int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){
+ return pExpr ? walkExpr(pWalker,pExpr) : WRC_Continue;
+}
/*
** Call sqlite3WalkExpr() for every expression in list p or until
diff --git a/src/where.c b/src/where.c
index 770b2f6b39..892633e572 100644
--- a/src/where.c
+++ b/src/where.c
@@ -718,7 +718,7 @@ static void constructAutomaticIndex(
idxCols |= cMask;
pIdx->aiColumn[n] = pTerm->u.leftColumn;
pColl = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight);
- pIdx->azColl[n] = pColl ? pColl->zName : "BINARY";
+ pIdx->azColl[n] = pColl ? pColl->zName : sqlite3StrBINARY;
n++;
}
}
@@ -730,20 +730,20 @@ static void constructAutomaticIndex(
for(i=0; iaiColumn[n] = i;
- pIdx->azColl[n] = "BINARY";
+ pIdx->azColl[n] = sqlite3StrBINARY;
n++;
}
}
if( pSrc->colUsed & MASKBIT(BMS-1) ){
for(i=BMS-1; inCol; i++){
pIdx->aiColumn[n] = i;
- pIdx->azColl[n] = "BINARY";
+ pIdx->azColl[n] = sqlite3StrBINARY;
n++;
}
}
assert( n==nKeyCol );
pIdx->aiColumn[n] = XN_ROWID;
- pIdx->azColl[n] = "BINARY";
+ pIdx->azColl[n] = sqlite3StrBINARY;
/* Create the automatic index */
assert( pLevel->iIdxCur>=0 );
@@ -893,6 +893,9 @@ static sqlite3_index_info *allocateIndexInfo(
pIdxCons[j].iTermOffset = i;
op = (u8)pTerm->eOperator & WO_ALL;
if( op==WO_IN ) op = WO_EQ;
+ if( op==WO_MATCH ){
+ op = pTerm->eMatchOp;
+ }
pIdxCons[j].op = op;
/* The direct assignment in the previous line is possible only because
** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical. The
@@ -2861,6 +2864,7 @@ static int whereLoopAddVirtual(
pIdxInfo->estimatedCost = SQLITE_BIG_DBL / (double)2;
pIdxInfo->estimatedRows = 25;
pIdxInfo->idxFlags = 0;
+ pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed;
rc = vtabBestIndex(pParse, pTab, pIdxInfo);
if( rc ) goto whereLoopAddVtab_exit;
pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint;
@@ -4188,7 +4192,7 @@ WhereInfo *sqlite3WhereBegin(
int ii; /* Loop counter */
sqlite3 *db; /* Database connection */
int rc; /* Return code */
- u8 bFordelete = 0;
+ u8 bFordelete = 0; /* OPFLAG_FORDELETE or zero, as appropriate */
assert( (wctrlFlags & WHERE_ONEPASS_MULTIROW)==0 || (
(wctrlFlags & WHERE_ONEPASS_DESIRED)!=0
@@ -4436,16 +4440,15 @@ WhereInfo *sqlite3WhereBegin(
/* If the caller is an UPDATE or DELETE statement that is requesting
** to use a one-pass algorithm, determine if this is appropriate.
- ** The one-pass algorithm only works if the WHERE clause constrains
- ** the statement to update or delete a single row.
*/
assert( (wctrlFlags & WHERE_ONEPASS_DESIRED)==0 || pWInfo->nLevel==1 );
if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 ){
int wsFlags = pWInfo->a[0].pWLoop->wsFlags;
int bOnerow = (wsFlags & WHERE_ONEROW)!=0;
- if( bOnerow || ( (wctrlFlags & WHERE_ONEPASS_MULTIROW)
- && 0==(wsFlags & WHERE_VIRTUALTABLE)
- )){
+ if( bOnerow
+ || ((wctrlFlags & WHERE_ONEPASS_MULTIROW)!=0
+ && 0==(wsFlags & WHERE_VIRTUALTABLE))
+ ){
pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI;
if( HasRowid(pTabList->a[0].pTab) && (wsFlags & WHERE_IDX_ONLY) ){
if( wctrlFlags & WHERE_ONEPASS_MULTIROW ){
@@ -4495,8 +4498,7 @@ WhereInfo *sqlite3WhereBegin(
Bitmask b = pTabItem->colUsed;
int n = 0;
for(; b; b=b>>1, n++){}
- sqlite3VdbeChangeP4(v, sqlite3VdbeCurrentAddr(v)-1,
- SQLITE_INT_TO_PTR(n), P4_INT32);
+ sqlite3VdbeChangeP4(v, -1, SQLITE_INT_TO_PTR(n), P4_INT32);
assert( n<=pTab->nCol );
}
#ifdef SQLITE_ENABLE_CURSOR_HINTS
@@ -4669,6 +4671,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
sqlite3VdbeJumpHere(v, pLevel->addrSkip);
sqlite3VdbeJumpHere(v, pLevel->addrSkip-2);
}
+#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS
if( pLevel->addrLikeRep ){
int op;
if( sqlite3VdbeGetOp(v, pLevel->addrLikeRep-1)->p1 ){
@@ -4679,6 +4682,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
sqlite3VdbeAddOp2(v, op, pLevel->iLikeRepCntr, pLevel->addrLikeRep);
VdbeCoverage(v);
}
+#endif
if( pLevel->iLeftJoin ){
addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v);
assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0
diff --git a/src/whereInt.h b/src/whereInt.h
index cae09acc82..1a189980ef 100644
--- a/src/whereInt.h
+++ b/src/whereInt.h
@@ -69,8 +69,10 @@ struct WhereLevel {
int addrCont; /* Jump here to continue with the next loop cycle */
int addrFirst; /* First instruction of interior of the loop */
int addrBody; /* Beginning of the body of this loop */
+#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS
int iLikeRepCntr; /* LIKE range processing counter register */
int addrLikeRep; /* LIKE range processing address */
+#endif
u8 iFrom; /* Which entry in the FROM clause */
u8 op, p3, p5; /* Opcode, P3 & P5 of the opcode that ends the loop */
int p1, p2; /* Operands of the opcode used to ends the loop */
@@ -253,6 +255,7 @@ struct WhereTerm {
u16 eOperator; /* A WO_xx value describing */
u16 wtFlags; /* TERM_xxx bit flags. See below */
u8 nChild; /* Number of children that must disable us */
+ u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */
WhereClause *pWC; /* The clause this term is part of */
Bitmask prereqRight; /* Bitmask of tables used by pExpr->pRight */
Bitmask prereqAll; /* Bitmask of tables referenced by pExpr */
@@ -285,7 +288,7 @@ struct WhereTerm {
struct WhereScan {
WhereClause *pOrigWC; /* Original, innermost WhereClause */
WhereClause *pWC; /* WhereClause currently being scanned */
- char *zCollName; /* Required collating sequence, if not NULL */
+ const char *zCollName; /* Required collating sequence, if not NULL */
Expr *pIdxExpr; /* Search for this index expression */
char idxaff; /* Must match this affinity, if zCollName!=NULL */
unsigned char nEquiv; /* Number of entries in aEquiv[] */
diff --git a/src/wherecode.c b/src/wherecode.c
index 87db0e0a25..9d53a20a67 100644
--- a/src/wherecode.c
+++ b/src/wherecode.c
@@ -327,8 +327,7 @@ static void codeApplyAffinity(Parse *pParse, int base, int n, char *zAff){
/* Code the OP_Affinity opcode if there is anything left to do. */
if( n>0 ){
- sqlite3VdbeAddOp2(v, OP_Affinity, base, n);
- sqlite3VdbeChangeP4(v, -1, zAff, n);
+ sqlite3VdbeAddOp4(v, OP_Affinity, base, n, 0, zAff, n);
sqlite3ExprCacheAffinityChange(pParse, base, n);
}
}
@@ -561,6 +560,7 @@ static int codeAllEqualityTerms(
return regBase;
}
+#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS
/*
** If the most recently coded instruction is a constant range contraint
** that originated from the LIKE optimization, then change the P3 to be
@@ -572,6 +572,10 @@ static int codeAllEqualityTerms(
** The OP_String opcodes on the second pass convert the upper and lower
** bound string contants to blobs. This routine makes the necessary changes
** to the OP_String opcodes for that to happen.
+**
+** Except, of course, if SQLITE_LIKE_DOESNT_MATCH_BLOBS is defined, then
+** only the one pass through the string space is required, so this routine
+** becomes a no-op.
*/
static void whereLikeOptimizationStringFixup(
Vdbe *v, /* prepared statement under construction */
@@ -589,6 +593,9 @@ static void whereLikeOptimizationStringFixup(
pOp->p5 = 1;
}
}
+#else
+# define whereLikeOptimizationStringFixup(A,B,C)
+#endif
#ifdef SQLITE_ENABLE_CURSOR_HINTS
/*
@@ -1075,6 +1082,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
if( pLoop->wsFlags & WHERE_TOP_LIMIT ){
pRangeEnd = pLoop->aLTerm[j++];
nExtraReg = 1;
+#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS
if( (pRangeEnd->wtFlags & TERM_LIKEOPT)!=0 ){
assert( pRangeStart!=0 ); /* LIKE opt constraints */
assert( pRangeStart->wtFlags & TERM_LIKEOPT ); /* occur in pairs */
@@ -1087,6 +1095,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
VdbeComment((v, "LIKE loop counter"));
pLevel->addrLikeRep = sqlite3VdbeCurrentAddr(v);
}
+#endif
if( pRangeStart==0
&& (j = pIdx->aiColumn[nEq])>=0
&& pIdx->pTable->aCol[j].notNull==0
@@ -1590,9 +1599,13 @@ Bitmask sqlite3WhereCodeOneLoopStart(
continue;
}
if( pTerm->wtFlags & TERM_LIKECOND ){
+#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS
+ continue;
+#else
assert( pLevel->iLikeRepCntr>0 );
skipLikeAddr = sqlite3VdbeAddOp1(v, OP_IfNot, pLevel->iLikeRepCntr);
VdbeCoverage(v);
+#endif
}
sqlite3ExprIfFalse(pParse, pE, addrCont, SQLITE_JUMPIFNULL);
if( skipLikeAddr ) sqlite3VdbeJumpHere(v, skipLikeAddr);
diff --git a/src/whereexpr.c b/src/whereexpr.c
index 21301ac046..e0b8bac1b7 100644
--- a/src/whereexpr.c
+++ b/src/whereexpr.c
@@ -202,6 +202,7 @@ static int isLikeOrGlob(
sqlite3 *db = pParse->db; /* Database connection */
sqlite3_value *pVal = 0;
int op; /* Opcode of pRight */
+ int rc; /* Result code to return */
if( !sqlite3IsLikeFunction(db, pExpr, pnoCase, wc) ){
return 0;
@@ -267,8 +268,9 @@ static int isLikeOrGlob(
}
}
+ rc = (z!=0);
sqlite3ValueFree(pVal);
- return (z!=0);
+ return rc;
}
#endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */
@@ -277,29 +279,48 @@ static int isLikeOrGlob(
/*
** Check to see if the given expression is of the form
**
-** column MATCH expr
+** column OP expr
+**
+** where OP is one of MATCH, GLOB, LIKE or REGEXP and "column" is a
+** column of a virtual table.
**
** If it is then return TRUE. If not, return FALSE.
*/
static int isMatchOfColumn(
- Expr *pExpr /* Test this expression */
+ Expr *pExpr, /* Test this expression */
+ unsigned char *peOp2 /* OUT: 0 for MATCH, or else an op2 value */
){
+ struct Op2 {
+ const char *zOp;
+ unsigned char eOp2;
+ } aOp[] = {
+ { "match", SQLITE_INDEX_CONSTRAINT_MATCH },
+ { "glob", SQLITE_INDEX_CONSTRAINT_GLOB },
+ { "like", SQLITE_INDEX_CONSTRAINT_LIKE },
+ { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP }
+ };
ExprList *pList;
+ Expr *pCol; /* Column reference */
+ int i;
if( pExpr->op!=TK_FUNCTION ){
return 0;
}
- if( sqlite3StrICmp(pExpr->u.zToken,"match")!=0 ){
- return 0;
- }
pList = pExpr->x.pList;
- if( pList->nExpr!=2 ){
+ if( pList==0 || pList->nExpr!=2 ){
return 0;
}
- if( pList->a[1].pExpr->op != TK_COLUMN ){
+ pCol = pList->a[1].pExpr;
+ if( pCol->op!=TK_COLUMN || !IsVirtual(pCol->pTab) ){
return 0;
}
- return 1;
+ for(i=0; iu.zToken, aOp[i].zOp)==0 ){
+ *peOp2 = aOp[i].eOp2;
+ return 1;
+ }
+ }
+ return 0;
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */
@@ -876,6 +897,7 @@ static void exprAnalyze(
int op; /* Top-level operator. pExpr->op */
Parse *pParse = pWInfo->pParse; /* Parsing context */
sqlite3 *db = pParse->db; /* Database connection */
+ unsigned char eOp2; /* op2 value for LIKE/REGEXP/GLOB */
if( db->mallocFailed ){
return;
@@ -1099,7 +1121,7 @@ static void exprAnalyze(
** virtual tables. The native query optimizer does not attempt
** to do anything with MATCH functions.
*/
- if( isMatchOfColumn(pExpr) ){
+ if( isMatchOfColumn(pExpr, &eOp2) ){
int idxNew;
Expr *pRight, *pLeft;
WhereTerm *pNewTerm;
@@ -1120,6 +1142,7 @@ static void exprAnalyze(
pNewTerm->leftCursor = pLeft->iTable;
pNewTerm->u.leftColumn = pLeft->iColumn;
pNewTerm->eOperator = WO_MATCH;
+ pNewTerm->eMatchOp = eOp2;
markTermAsChild(pWC, idxNew, idxTerm);
pTerm = &pWC->a[idxTerm];
pTerm->wtFlags |= TERM_COPIED;
diff --git a/test/analyze3.test b/test/analyze3.test
index d61d21a947..2fb558d16a 100644
--- a/test/analyze3.test
+++ b/test/analyze3.test
@@ -283,9 +283,17 @@ do_eqp_test analyze3-2.3 {
SELECT count(a) FROM t1 WHERE b LIKE '%a'
} {0 0 0 {SCAN TABLE t1}}
+# Return the first argument if like_match_blobs is true (the default)
+# or the second argument if not
+#
+proc ilmb {a b} {
+ ifcapable like_match_blobs {return $a}
+ return $b
+}
+
do_test analyze3-2.4 {
sf_execsql { SELECT count(*) FROM t1 WHERE b LIKE 'a%' }
-} {102 0 100}
+} [list [ilmb 102 101] 0 100]
do_test analyze3-2.5 {
sf_execsql { SELECT count(*) FROM t1 WHERE b LIKE '%a' }
} {999 999 100}
@@ -293,7 +301,7 @@ do_test analyze3-2.5 {
do_test analyze3-2.6 {
set like "a%"
sf_execsql { SELECT count(*) FROM t1 WHERE b LIKE $like }
-} {102 0 100}
+} [list [ilmb 102 101] 0 100]
do_test analyze3-2.7 {
set like "%a"
sf_execsql { SELECT count(*) FROM t1 WHERE b LIKE $like }
@@ -301,19 +309,19 @@ do_test analyze3-2.7 {
do_test analyze3-2.8 {
set like "a"
sf_execsql { SELECT count(*) FROM t1 WHERE b LIKE $like }
-} {102 0 0}
+} [list [ilmb 102 101] 0 0]
do_test analyze3-2.9 {
set like "ab"
sf_execsql { SELECT count(*) FROM t1 WHERE b LIKE $like }
-} {12 0 0}
+} [list [ilmb 12 11] 0 0]
do_test analyze3-2.10 {
set like "abc"
sf_execsql { SELECT count(*) FROM t1 WHERE b LIKE $like }
-} {3 0 1}
+} [list [ilmb 3 2] 0 1]
do_test analyze3-2.11 {
set like "a_c"
sf_execsql { SELECT count(*) FROM t1 WHERE b LIKE $like }
-} {102 0 10}
+} [list [ilmb 102 101] 0 10]
#-------------------------------------------------------------------------
diff --git a/test/analyzeF.test b/test/analyzeF.test
index 670d178a81..3cbc5f47be 100644
--- a/test/analyzeF.test
+++ b/test/analyzeF.test
@@ -106,7 +106,7 @@ do_catchsql_test 4.1 {
} {1 {error one}}
do_catchsql_test 4.2 {
- SELECT * FROM t1 WHERE x = zeroblob(2000000000) AND y = 4;
+ SELECT * FROM t1 WHERE x = zeroblob(2200000000) AND y = 4;
} {1 {string or blob too big}}
sqlite3_limit db SQLITE_LIMIT_LENGTH 1000000
@@ -122,6 +122,3 @@ do_catchsql_test 4.4 {
finish_test
-
-
-
diff --git a/test/capi3c.test b/test/capi3c.test
index 6ab3bc24f6..15307a7f7a 100644
--- a/test/capi3c.test
+++ b/test/capi3c.test
@@ -18,6 +18,7 @@
set testdir [file dirname $argv0]
source $testdir/tester.tcl
+set testprefix capi3c
# Do not use a codec for tests in this file, as the database file is
# manipulated directly using tcl scripts (using the [hexio_write] command).
@@ -1375,4 +1376,26 @@ do_test capi3c-24.3 {
decltype {SELECT (SELECT x FROM (SELECT t5.a AS x)) FROM t5}
} {INTEGER}
+
+# Further tests of sqlite3_column_decltype():
+#
+do_execsql_test 25.0 {
+ CREATE TABLE t11(a VARCHAR(10), b INTEGER);
+ CREATE TABLE t12(a VARCHAR(15), b FLOAT);
+}
+
+foreach {tn sql} {
+ 1 "SELECT * FROM t11 UNION ALL SELECT * FROM t12"
+ 2 "SELECT * FROM t11 UNION SELECT * FROM t12"
+ 3 "SELECT * FROM t11 EXCEPT SELECT * FROM t12"
+ 4 "SELECT * FROM t11 INTERSECT SELECT * FROM t12"
+
+ 5 "SELECT * FROM t11 UNION ALL SELECT * FROM t12 ORDER BY 1"
+ 6 "SELECT * FROM t11 UNION SELECT * FROM t12 ORDER BY 1"
+ 7 "SELECT * FROM t11 EXCEPT SELECT * FROM t12 ORDER BY 1"
+ 8 "SELECT * FROM t11 INTERSECT SELECT * FROM t12 ORDER BY 1"
+} {
+ do_test 25.$tn { decltype $sql } {VARCHAR(10) INTEGER}
+}
+
finish_test
diff --git a/test/conflict2.test b/test/conflict2.test
index 8419f1a3ff..6496913849 100644
--- a/test/conflict2.test
+++ b/test/conflict2.test
@@ -289,11 +289,11 @@ do_test conflict2-6.0 {
#
foreach {i conf1 cmd t0 t1 t2 t3 t4} {
1 {} UPDATE 1 {6 7 8 9} 1 0 1
- 2 REPLACE UPDATE 0 {7 6 9} 1 0 0
- 3 IGNORE UPDATE 0 {6 7 3 9} 1 0 0
- 4 FAIL UPDATE 1 {6 7 3 4} 1 0 0
+ 2 REPLACE UPDATE 0 {7 6 9} 1 0 1
+ 3 IGNORE UPDATE 0 {6 7 3 9} 1 0 1
+ 4 FAIL UPDATE 1 {6 7 3 4} 1 0 1
5 ABORT UPDATE 1 {1 2 3 4} 1 0 1
- 6 ROLLBACK UPDATE 1 {1 2 3 4} 0 0 0
+ 6 ROLLBACK UPDATE 1 {1 2 3 4} 0 0 1
7 REPLACE {UPDATE OR IGNORE} 0 {6 7 3 9} 1 0 0
8 IGNORE {UPDATE OR REPLACE} 0 {7 6 9} 1 0 1
9 FAIL {UPDATE OR IGNORE} 0 {6 7 3 9} 1 0 0
diff --git a/test/cursorhint.test b/test/cursorhint.test
index 69bc248cd7..ae86120fa8 100644
--- a/test/cursorhint.test
+++ b/test/cursorhint.test
@@ -46,10 +46,10 @@ proc p4_of_opcode {db opcode sql} {
# Run EXPLAIN on $sql. Return a list of P5 values for all $opcode
# opcodes that contain regexp $comment in their comment
#
-proc p5_of_opcode {db opcode comment sql} {
+proc p5_of_opcode {db opcode sql} {
set res {}
$db eval "EXPLAIN $sql" x {
- if {$x(opcode)==$opcode && [regexp $comment $x(comment)]} {
+ if {$x(opcode)==$opcode} {
lappend res $x(p5)
}
}
@@ -66,7 +66,7 @@ do_test 1.1 {
}
} {{EQ(r[1],c0)}}
do_test 1.2 {
- p5_of_opcode db OpenRead . {
+ p5_of_opcode db OpenRead {
SELECT * FROM t1 CROSS JOIN t2 WHERE a=x
}
} {00 00}
@@ -79,7 +79,7 @@ do_test 2.1 {
}
} {{EQ(c0,r[1])}}
do_test 2.2 {
- p5_of_opcode db OpenRead . {
+ p5_of_opcode db OpenRead {
SELECT * FROM t2 CROSS JOIN t1 WHERE a=x
}
} {00 00}
@@ -114,7 +114,7 @@ do_test 4.1desc {
}
} {GT(c0,11)}
do_test 4.2 {
- p5_of_opcode db OpenRead . {
+ p5_of_opcode db OpenRead {
SELECT * FROM t1 WHERE b>11;
}
} {02 00}
@@ -129,7 +129,7 @@ do_test 4.3desc {
}
} {}
do_test 4.4 {
- p5_of_opcode db OpenRead . {
+ p5_of_opcode db OpenRead {
SELECT c FROM t1 WHERE b<11;
}
} {00}
diff --git a/test/date.test b/test/date.test
index b1d1c677c1..2f48b111e6 100644
--- a/test/date.test
+++ b/test/date.test
@@ -336,6 +336,15 @@ if {$tzoffset_new==4} {
datetest 6.8.1 {datetime('2006-04-02 02:00:00','utc')} {2006-04-02 06:00:00}
datetest 6.8.2 {datetime('2007-03-11 02:00:00','utc')} {2007-03-11 06:00:00}
+ # The 'utc' modifier is a no-op if the LHS is known to already be in UTC
+ datetest 6.9.1 {datetime('2015-12-23 12:00:00','utc')} {2015-12-23 17:00:00}
+ datetest 6.9.2 {datetime('2015-12-23 12:00:00z','utc')} {2015-12-23 12:00:00}
+ datetest 6.9.3 {datetime('2015-12-23 12:00:00-03:00','utc')} \
+ {2015-12-23 15:00:00}
+ datetest 6.9.4 {datetime('2015-12-23 12:00:00','utc','utc','utc')} \
+ {2015-12-23 17:00:00}
+
+
datetest 6.10 {datetime('2000-01-01 12:00:00','localtime')} \
{2000-01-01 07:00:00}
datetest 6.11 {datetime('1969-01-01 12:00:00','localtime')} \
diff --git a/test/distinct.test b/test/distinct.test
index 2fb90dc3e3..dac2269b0b 100644
--- a/test/distinct.test
+++ b/test/distinct.test
@@ -252,4 +252,21 @@ do_execsql_test 5.6 {
SELECT DISTINCT x FROM t1 ORDER BY x;
} {1 2 3 4 5 6}
+#-------------------------------------------------------------------------
+# 2015-11-23. Problem discovered by Kostya Serebryany using libFuzzer
+#
+db close
+sqlite3 db :memory:
+do_execsql_test 6.1 {
+ CREATE TABLE jjj(x);
+ SELECT (SELECT 'mmm' UNION SELECT DISTINCT max(name) ORDER BY 1)
+ FROM sqlite_master;
+} {jjj}
+do_execsql_test 6.2 {
+ CREATE TABLE nnn(x);
+ SELECT (SELECT 'mmm' UNION SELECT DISTINCT max(name) ORDER BY 1)
+ FROM sqlite_master;
+} {mmm}
+
+
finish_test
diff --git a/test/enc3.test b/test/enc3.test
index 1d8a258165..7ede2b716f 100644
--- a/test/enc3.test
+++ b/test/enc3.test
@@ -62,7 +62,7 @@ ifcapable {bloblit && utf16} {
execsql {
CREATE TABLE t2(a);
INSERT INTO t2 VALUES(x'61006200630064006500');
- SELECT CAST(a AS text) FROM t2 WHERE a LIKE 'abc%';
+ SELECT CAST(a AS text) FROM t2 WHERE CAST(a AS text) LIKE 'abc%';
}
} {abcde}
do_test enc3-2.3 {
@@ -72,7 +72,8 @@ ifcapable {bloblit && utf16} {
} {abcde}
do_test enc3-2.4 {
execsql {
- SELECT rowid FROM t2 WHERE a LIKE x'610062002500';
+ SELECT rowid FROM t2
+ WHERE CAST(a AS text) LIKE CAST(x'610062002500' AS text);
}
} {1}
}
diff --git a/test/fts4content.test b/test/fts4content.test
index 481c6ec008..5a91987867 100644
--- a/test/fts4content.test
+++ b/test/fts4content.test
@@ -587,7 +587,7 @@ do_execsql_test 10.1 {
INSERT INTO idx VALUES (3, 't3.txt');
CREATE VIRTUAL TABLE vt USING fs(idx);
- SELECT * FROM vt;
+ SELECT path, data FROM vt;
} {
1 {a b c d e f g h i j k l m n o p q r s t u v w x y z}
2 {a b c d e f g h i j k l m a b c d e f g h i j k l m}
@@ -595,7 +595,7 @@ do_execsql_test 10.1 {
}
do_execsql_test 10.2 {
- SELECT * FROM vt WHERE rowid = 2;
+ SELECT path, data FROM vt WHERE rowid = 2;
} {
2 {a b c d e f g h i j k l m a b c d e f g h i j k l m}
}
diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c
index 32690dc466..03776f1e70 100644
--- a/test/fuzzcheck.c
+++ b/test/fuzzcheck.c
@@ -588,18 +588,13 @@ static int inmemFullPathname(
return SQLITE_OK;
}
-/* GetLastError() is never used */
-static int inmemGetLastError(sqlite3_vfs *pVfs, int n, char *z){
- return SQLITE_OK;
-}
-
/*
** Register the VFS that reads from the g.aFile[] set of files.
*/
static void inmemVfsRegister(void){
static sqlite3_vfs inmemVfs;
sqlite3_vfs *pDefault = sqlite3_vfs_find(0);
- inmemVfs.iVersion = 1;
+ inmemVfs.iVersion = 3;
inmemVfs.szOsFile = sizeof(VHandle);
inmemVfs.mxPathname = 200;
inmemVfs.zName = "inmem";
@@ -609,8 +604,7 @@ static void inmemVfsRegister(void){
inmemVfs.xFullPathname = inmemFullPathname;
inmemVfs.xRandomness = pDefault->xRandomness;
inmemVfs.xSleep = pDefault->xSleep;
- inmemVfs.xCurrentTime = pDefault->xCurrentTime;
- inmemVfs.xGetLastError = inmemGetLastError;
+ inmemVfs.xCurrentTimeInt64 = pDefault->xCurrentTimeInt64;
sqlite3_vfs_register(&inmemVfs, 0);
};
@@ -871,8 +865,13 @@ int main(int argc, char **argv){
return 0;
}else
if( strcmp(z,"limit-mem")==0 ){
+#if !defined(SQLITE_ENABLE_MEMSYS3) && !defined(SQLITE_ENABLE_MEMSYS5)
+ fatalError("the %s option requires -DSQLITE_ENABLE_MEMSYS5 or _MEMSYS3",
+ argv[i]);
+#else
if( i>=argc-1 ) fatalError("missing arguments on %s", argv[i]);
nMem = integerValue(argv[++i]);
+#endif
}else
if( strcmp(z,"limit-vdbe")==0 ){
vdbeLimitFlag = 1;
diff --git a/test/hexlit.test b/test/hexlit.test
index 2edd458e89..c48930b49b 100644
--- a/test/hexlit.test
+++ b/test/hexlit.test
@@ -109,6 +109,9 @@ do_execsql_test hexlit-301 {
do_catchsql_test hexlist-400 {
SELECT 0x10000000000000000;
} {1 {hex literal too big: 0x10000000000000000}}
+do_catchsql_test hexlist-401 {
+ SELECT DISTINCT 0x10000000000000000;
+} {1 {hex literal too big: 0x10000000000000000}}
do_catchsql_test hexlist-410 {
DROP TABLE IF EXISTS t1;
CREATE TABLE t1(x);
diff --git a/test/ieee754.test b/test/ieee754.test
index c0bf9d7995..bf0676429b 100644
--- a/test/ieee754.test
+++ b/test/ieee754.test
@@ -43,12 +43,16 @@ foreach {id float rep} {
}
}
-do_execsql_test ieee754-110 {
- SELECT ieee754(1,1024), ieee754(4503599627370495,972);
-} {Inf 1.79769313486232e+308}
-do_execsql_test ieee754-111 {
- SELECT ieee754(-1,1024), ieee754(-4503599627370495,972);
-} {-Inf -1.79769313486232e+308}
+do_test ieee754-110 {
+ string tolower [
+ db eval {SELECT ieee754(1,1024), ieee754(4503599627370495,972);}
+ ]
+} {inf 1.79769313486232e+308}
+do_test ieee754-111 {
+ string tolower [
+ db eval {SELECT ieee754(-1,1024), ieee754(-4503599627370495,972);}
+ ]
+} {-inf -1.79769313486232e+308}
do_execsql_test ieee754-112 {
SELECT ieee754(4503599627370495,973) is null;
} {1}
diff --git a/test/indexexpr1.test b/test/indexexpr1.test
index 89bea1877f..a8a74f259e 100644
--- a/test/indexexpr1.test
+++ b/test/indexexpr1.test
@@ -307,5 +307,21 @@ do_catchsql_test indexexpr1-910 {
INSERT INTO t9(a,b,c,d) VALUES(5,6,7,-8);
} {1 {UNIQUE constraint failed: index 't9x1'}}
+# Test cases derived from a NEVER() maro failure discovered by
+# Jonathan Metzman using AFL
+#
+do_execsql_test indexexpr1-1000 {
+ DROP TABLE IF EXISTS t0;
+ CREATE TABLE t0(a,b,t);
+ CREATE INDEX i ON t0(a in(0,1));
+ INSERT INTO t0 VALUES(0,1,2),(2,3,4),(5,6,7);
+ UPDATE t0 SET b=99 WHERE (a in(0,1))=0;
+ SELECT *, '|' FROM t0 ORDER BY +a;
+} {0 1 2 | 2 99 4 | 5 99 7 |}
+do_execsql_test indexexpr1-1010 {
+ UPDATE t0 SET b=88 WHERE (a in(0,1))=1;
+ SELECT *, '|' FROM t0 ORDER BY +a;
+} {0 88 2 | 2 99 4 | 5 99 7 |}
+
finish_test
diff --git a/test/json103.test b/test/json103.test
new file mode 100644
index 0000000000..0f0241e6fc
--- /dev/null
+++ b/test/json103.test
@@ -0,0 +1,65 @@
+# 2015-12-30
+#
+# 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 tests for JSON aggregate SQL functions
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+ifcapable !json1 {
+ finish_test
+ return
+}
+
+do_execsql_test json103-100 {
+ CREATE TABLE t1(a,b,c);
+ WITH RECURSIVE c(x) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<100)
+ INSERT INTO t1(a,b,c) SELECT x, x%3, printf('n%d',x) FROM c;
+ UPDATE t1 SET a='orange' WHERE rowid=39;
+ UPDATE t1 SET a=32.5 WHERE rowid=31;
+ UPDATE t1 SET a=x'303132' WHERE rowid=29;
+ UPDATE t1 SET a=NULL WHERE rowid=37;
+ SELECT json_group_array(a) FROM t1 WHERE a<0 AND typeof(a)!='blob';
+} {{[]}}
+do_catchsql_test json103-101 {
+ SELECT json_group_array(a) FROM t1;
+} {1 {JSON cannot hold BLOB values}}
+do_execsql_test json103-110 {
+ SELECT json_group_array(a) FROM t1
+ WHERE rowid BETWEEN 31 AND 39;
+} {{[32.5,32,33,34,35,36,null,38,"orange"]}}
+do_execsql_test json103-111 {
+ SELECT json_array_length(json_group_array(a)) FROM t1
+ WHERE rowid BETWEEN 31 AND 39;
+} {9}
+do_execsql_test json103-120 {
+ SELECT b, json_group_array(a) FROM t1 WHERE rowid<10 GROUP BY b ORDER BY b;
+} {0 {[3,6,9]} 1 {[1,4,7]} 2 {[2,5,8]}}
+
+do_execsql_test json103-200 {
+ SELECT json_group_object(c,a) FROM t1 WHERE a<0 AND typeof(a)!='blob';
+} {{{}}}
+do_catchsql_test json103-201 {
+ SELECT json_group_object(c,a) FROM t1;
+} {1 {JSON cannot hold BLOB values}}
+
+do_execsql_test json103-210 {
+ SELECT json_group_object(c,a) FROM t1
+ WHERE rowid BETWEEN 31 AND 39 AND rowid%2==1;
+} {{{"n31":32.5,"n33":33,"n35":35,"n37":null,"n39":"orange"}}}
+do_execsql_test json103-220 {
+ SELECT b, json_group_object(c,a) FROM t1
+ WHERE rowid<7 GROUP BY b ORDER BY b;
+} {0 {{"n3":3,"n6":6}} 1 {{"n1":1,"n4":4}} 2 {{"n2":2,"n5":5}}}
+
+
+
+finish_test
diff --git a/test/like.test b/test/like.test
index 18a01dc996..fba89e9037 100644
--- a/test/like.test
+++ b/test/like.test
@@ -745,11 +745,19 @@ ifcapable like_opt&&!icu {
SELECT a FROM t10 WHERE e LIKE '12%' ORDER BY +a;
}
} {12 123 scan 5 like 6}
- do_test like-10.5 {
- count {
- SELECT a FROM t10 WHERE f LIKE '12%' ORDER BY +a;
- }
- } {12 123 scan 4 like 0}
+ ifcapable like_match_blobs {
+ do_test like-10.5a {
+ count {
+ SELECT a FROM t10 WHERE f LIKE '12%' ORDER BY +a;
+ }
+ } {12 123 scan 4 like 0}
+ } else {
+ do_test like-10.5b {
+ count {
+ SELECT a FROM t10 WHERE f LIKE '12%' ORDER BY +a;
+ }
+ } {12 123 scan 3 like 0}
+ }
do_test like-10.6 {
count {
SELECT a FROM t10 WHERE a LIKE '12%' ORDER BY +a;
@@ -786,11 +794,19 @@ ifcapable like_opt&&!icu {
SELECT a FROM t10b WHERE e GLOB '12*' ORDER BY +a;
}
} {12 123 scan 5 like 6}
- do_test like-10.14 {
- count {
- SELECT a FROM t10b WHERE f GLOB '12*' ORDER BY +a;
- }
- } {12 123 scan 4 like 0}
+ ifcapable like_match_blobs {
+ do_test like-10.14 {
+ count {
+ SELECT a FROM t10b WHERE f GLOB '12*' ORDER BY +a;
+ }
+ } {12 123 scan 4 like 0}
+ } else {
+ do_test like-10.14 {
+ count {
+ SELECT a FROM t10b WHERE f GLOB '12*' ORDER BY +a;
+ }
+ } {12 123 scan 3 like 0}
+ }
do_test like-10.15 {
count {
SELECT a FROM t10b WHERE a GLOB '12*' ORDER BY +a;
@@ -948,5 +964,22 @@ do_execsql_test like-12.16 {
SELECT id FROM t12b WHERE x LIKE 'abc%' COLLATE binary ORDER BY +id;
} {/SCAN/}
+# Ticket [https://www.sqlite.org/src/tktview/80369eddd5c94d49f7fbbcf5]
+# 2016-01-20
+#
+do_execsql_test like-13.1 {
+ SELECT char(0x304d) LIKE char(0x306d);
+} {0}
+do_execsql_test like-13.2 {
+ SELECT char(0x4d) LIKE char(0x306d);
+} {0}
+do_execsql_test like-13.3 {
+ SELECT char(0x304d) LIKE char(0x6d);
+} {0}
+do_execsql_test like-13.4 {
+ SELECT char(0x4d) LIKE char(0x6d);
+} {1}
+
+
finish_test
diff --git a/test/like3.test b/test/like3.test
index a1faf76915..9280c2c5d2 100644
--- a/test/like3.test
+++ b/test/like3.test
@@ -29,6 +29,11 @@
set testdir [file dirname $argv0]
source $testdir/tester.tcl
+ifcapable !like_match_blobs {
+ finish_test
+ return
+}
+
do_execsql_test like3-1.1 {
PRAGMA encoding=UTF8;
CREATE TABLE t1(a,b TEXT COLLATE nocase);
@@ -107,6 +112,4 @@ do_execsql_test like3-4.2ck {
SELECT quote(x) FROM t4 WHERE x LIKE 'ab%' ORDER BY +x ASC;
} {'abc' 'abd' 'abe' X'616263' X'616264' X'616265'}
-
-
finish_test
diff --git a/test/orderby1.test b/test/orderby1.test
index 3e785c54e4..831936ae96 100644
--- a/test/orderby1.test
+++ b/test/orderby1.test
@@ -528,4 +528,21 @@ do_test 8.3 {
set res
} 5000
+#---------------------------------------------------------------------------
+# https://www.sqlite.org/src/tktview/cb3aa0641d9a413841c004293a4fc06cdc122029
+#
+# Adverse interaction between scalar subqueries and the partial-sorting
+# logic.
+#
+do_execsql_test 9.0 {
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1(x INTEGER PRIMARY KEY);
+ INSERT INTO t1 VALUES(1),(2);
+ DROP TABLE IF EXISTS t2;
+ CREATE TABLE t2(y);
+ INSERT INTO t2 VALUES(9),(8),(3),(4);
+ SELECT (SELECT x||y FROM t2, t1 ORDER BY x, y);
+} {13}
+
+
finish_test
diff --git a/test/pragma2.test b/test/pragma2.test
index 64b396bd1f..a4fb2ce657 100644
--- a/test/pragma2.test
+++ b/test/pragma2.test
@@ -125,8 +125,8 @@ ifcapable attach {
# Default setting of PRAGMA cache_spill is always ON
#
-# EVIDENCE-OF: R-51036-62828 PRAGMA cache_spill; PRAGMA
-# cache_spill=boolean;
+# EVIDENCE-OF: R-63549-59887 PRAGMA cache_spill; PRAGMA
+# cache_spill=boolean; PRAGMA schema.cache_spill=N;
#
# EVIDENCE-OF: R-23955-02765 Cache_spill is enabled by default
#
@@ -190,6 +190,11 @@ do_test pragma2-4.5.1 {
PRAGMA lock_status;
}
} {0 main reserved temp unknown} ;# No cache spill, so no exclusive lock
+
+
+# EVIDENCE-OF: R-34657-61226 The "PRAGMA cache_spill=N" form of this
+# pragma sets a minimum cache size threshold required for spilling to
+# occur.
do_test pragma2-4.5.2 {
db eval {
ROLLBACK;
diff --git a/test/releasetest.tcl b/test/releasetest.tcl
index 7f53fd1523..bb902eec3e 100644
--- a/test/releasetest.tcl
+++ b/test/releasetest.tcl
@@ -86,6 +86,8 @@ array set ::Configs [strip_comments {
-DSQLITE_DEFAULT_FILE_FORMAT=4
-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1
-DSQLITE_ENABLE_STMT_SCANSTATUS
+ -DSQLITE_LIKE_DOESNT_MATCH_BLOBS
+ -DSQLITE_ENABLE_CURSOR_HINTS
--enable-json1
}
"Check-Symbols" {
@@ -830,8 +832,8 @@ proc process_options {argv} {
}
default {
- PUTSERR stderr ""
- PUTSERR stderr [string trim $::USAGE_MESSAGE]
+ PUTSERR ""
+ PUTSERR [string trim $::USAGE_MESSAGE]
exit -1
}
}
@@ -945,6 +947,8 @@ proc main {argv} {
set min [expr {($elapsetime/60)%60}]
set sec [expr {$elapsetime%60}]
set etime [format (%02d:%02d:%02d) $hr $min $sec]
+ if {$::JOBS>1} {append etime " $::JOBS cores"}
+ if {[catch {exec hostname} HNAME]==0} {append etime " on $HNAME"}
PUTS [string repeat * 79]
incr ::NERRCASE $::NERR
PUTS "$::NERRCASE failures out of $::NTESTCASE tests in $etime"
diff --git a/test/snapshot.test b/test/snapshot.test
new file mode 100644
index 0000000000..6e160166c3
--- /dev/null
+++ b/test/snapshot.test
@@ -0,0 +1,340 @@
+# 2015 December 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 regression tests for SQLite library. The focus
+# of this file is the sqlite3_snapshot_xxx() APIs.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+ifcapable !snapshot {finish_test; return}
+set testprefix snapshot
+
+#-------------------------------------------------------------------------
+# Check some error conditions in snapshot_get(). It is an error if:
+#
+# 1) snapshot_get() is called on a non-WAL database, or
+# 2) there is an open write transaction on the database.
+#
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a, b);
+ INSERT INTO t1 VALUES(1, 2);
+ INSERT INTO t1 VALUES(3, 4);
+}
+
+do_test 1.1.1 {
+ execsql { BEGIN; SELECT * FROM t1; }
+ list [catch { sqlite3_snapshot_get db main } msg] $msg
+} {1 SQLITE_ERROR}
+do_execsql_test 1.1.2 COMMIT
+
+do_test 1.2.1 {
+ execsql {
+ PRAGMA journal_mode = WAL;
+ BEGIN;
+ INSERT INTO t1 VALUES(5, 6);
+ INSERT INTO t1 VALUES(7, 8);
+ }
+ list [catch { sqlite3_snapshot_get db main } msg] $msg
+} {1 SQLITE_ERROR}
+do_execsql_test 1.3.2 COMMIT
+
+#-------------------------------------------------------------------------
+# Check that a simple case works. Reuse the database created by the
+# block of tests above.
+#
+do_execsql_test 2.1.0 {
+ BEGIN;
+ SELECT * FROM t1;
+} {1 2 3 4 5 6 7 8}
+
+breakpoint
+do_test 2.1.1 {
+ set snapshot [sqlite3_snapshot_get db main]
+ execsql {
+ COMMIT;
+ INSERT INTO t1 VALUES(9, 10);
+ SELECT * FROM t1;
+ }
+} {1 2 3 4 5 6 7 8 9 10}
+
+do_test 2.1.2 {
+ execsql BEGIN
+ sqlite3_snapshot_open db main $snapshot
+ execsql {
+ SELECT * FROM t1;
+ }
+} {1 2 3 4 5 6 7 8}
+
+do_test 2.1.3 {
+ sqlite3_snapshot_free $snapshot
+ execsql COMMIT
+} {}
+
+do_test 2.2.0 {
+ sqlite3 db2 test.db
+ execsql {
+ BEGIN;
+ SELECT * FROM t1;
+ } db2
+} {1 2 3 4 5 6 7 8 9 10}
+
+do_test 2.2.1 {
+ set snapshot [sqlite3_snapshot_get db2 main]
+ execsql {
+ INSERT INTO t1 VALUES(11, 12);
+ SELECT * FROM t1;
+ }
+} {1 2 3 4 5 6 7 8 9 10 11 12}
+
+do_test 2.2.2 {
+ execsql BEGIN
+ sqlite3_snapshot_open db main $snapshot
+ execsql {
+ SELECT * FROM t1;
+ }
+} {1 2 3 4 5 6 7 8 9 10}
+
+do_test 2.2.3 {
+ sqlite3_snapshot_free $snapshot
+ execsql COMMIT
+ execsql COMMIT db2
+ db2 close
+} {}
+
+do_test 2.3.1 {
+ execsql { DELETE FROM t1 WHERE a>6 }
+ set snapshot [sqlite3_snapshot_get db main]
+ execsql {
+ INSERT INTO t1 VALUES('a', 'b');
+ INSERT INTO t1 VALUES('c', 'd');
+ SELECT * FROM t1;
+ }
+} {1 2 3 4 5 6 a b c d}
+do_test 2.3.2 {
+ execsql BEGIN
+ sqlite3_snapshot_open db main $snapshot
+ execsql { SELECT * FROM t1 }
+} {1 2 3 4 5 6}
+
+do_test 2.3.3 {
+ catchsql {
+ INSERT INTO t1 VALUES('x','y')
+ }
+} {1 {database is locked}}
+do_test 2.3.4 {
+ execsql COMMIT
+ sqlite3_snapshot_free $snapshot
+} {}
+
+#-------------------------------------------------------------------------
+# Check some errors in sqlite3_snapshot_open(). It is an error if:
+#
+# 1) the db is in auto-commit mode,
+# 2) the db has an open (read or write) transaction,
+# 3) the db is not a wal database,
+#
+# Reuse the database created by earlier tests.
+#
+do_execsql_test 3.0.0 {
+ CREATE TABLE t2(x, y);
+ INSERT INTO t2 VALUES('a', 'b');
+ INSERT INTO t2 VALUES('c', 'd');
+ BEGIN;
+ SELECT * FROM t2;
+} {a b c d}
+do_test 3.0.1 {
+ set snapshot [sqlite3_snapshot_get db main]
+ execsql { COMMIT }
+ execsql { INSERT INTO t2 VALUES('e', 'f'); }
+} {}
+
+do_test 3.1 {
+ list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
+} {1 SQLITE_ERROR}
+
+do_test 3.2.1 {
+ execsql {
+ BEGIN;
+ SELECT * FROM t2;
+ }
+} {a b c d e f}
+do_test 3.2.2 {
+ list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
+} {1 SQLITE_ERROR}
+
+do_test 3.2.3 {
+ execsql {
+ COMMIT;
+ BEGIN;
+ INSERT INTO t2 VALUES('g', 'h');
+ }
+ list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
+} {1 SQLITE_ERROR}
+do_execsql_test 3.2.4 COMMIT
+
+do_test 3.3.1 {
+ execsql { PRAGMA journal_mode = DELETE }
+ execsql { BEGIN }
+ list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
+} {1 SQLITE_ERROR}
+
+do_test 3.3.2 {
+ sqlite3_snapshot_free $snapshot
+ execsql COMMIT
+} {}
+
+#-------------------------------------------------------------------------
+# Check that SQLITE_BUSY_SNAPSHOT is returned if the specified snapshot
+# no longer exists because the wal file has been checkpointed.
+#
+# 1. Reading a snapshot from the middle of a wal file is not possible
+# after the wal file has been checkpointed.
+#
+# 2. That a snapshot from the end of a wal file can not be read once
+# the wal file has been wrapped.
+#
+do_execsql_test 4.1.0 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t3(i, j);
+ INSERT INTO t3 VALUES('o', 't');
+ INSERT INTO t3 VALUES('t', 'f');
+ BEGIN;
+ SELECT * FROM t3;
+} {wal o t t f}
+
+do_test 4.1.1 {
+ set snapshot [sqlite3_snapshot_get db main]
+ execsql COMMIT
+} {}
+do_test 4.1.2 {
+ execsql {
+ INSERT INTO t3 VALUES('f', 's');
+ BEGIN;
+ }
+ sqlite3_snapshot_open db main $snapshot
+ execsql { SELECT * FROM t3 }
+} {o t t f}
+
+do_test 4.1.3 {
+ execsql {
+ COMMIT;
+ PRAGMA wal_checkpoint;
+ BEGIN;
+ }
+ list [catch {sqlite3_snapshot_open db main $snapshot} msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+do_test 4.1.4 {
+ sqlite3_snapshot_free $snapshot
+ execsql COMMIT
+} {}
+
+do_test 4.2.1 {
+ execsql {
+ INSERT INTO t3 VALUES('s', 'e');
+ INSERT INTO t3 VALUES('n', 't');
+ BEGIN;
+ SELECT * FROM t3;
+ }
+} {o t t f f s s e n t}
+do_test 4.2.2 {
+ set snapshot [sqlite3_snapshot_get db main]
+ execsql {
+ COMMIT;
+ PRAGMA wal_checkpoint;
+ BEGIN;
+ }
+ sqlite3_snapshot_open db main $snapshot
+ execsql { SELECT * FROM t3 }
+} {o t t f f s s e n t}
+do_test 4.2.3 {
+ execsql {
+ COMMIT;
+ INSERT INTO t3 VALUES('e', 't');
+ BEGIN;
+ }
+ list [catch {sqlite3_snapshot_open db main $snapshot} msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+do_test 4.2.4 {
+ sqlite3_snapshot_free $snapshot
+} {}
+
+#-------------------------------------------------------------------------
+# Check that SQLITE_BUSY is returned if a checkpoint is running when
+# sqlite3_snapshot_open() is called.
+#
+reset_db
+db close
+testvfs tvfs
+sqlite3 db test.db -vfs tvfs
+
+do_execsql_test 5.1 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE x1(x, xx, xxx);
+ INSERT INTO x1 VALUES('z', 'zz', 'zzz');
+ BEGIN;
+ SELECT * FROM x1;
+} {wal z zz zzz}
+
+do_test 5.2 {
+ set ::snapshot [sqlite3_snapshot_get db main]
+ sqlite3 db2 test.db -vfs tvfs
+ execsql {
+ INSERT INTO x1 VALUES('a', 'aa', 'aaa');
+ COMMIT;
+ }
+} {}
+
+set t53 0
+proc write_callback {args} {
+ do_test 5.3.[incr ::t53] {
+ execsql BEGIN
+ list [catch { sqlite3_snapshot_open db main $::snapshot } msg] $msg
+ } {1 SQLITE_BUSY}
+ catchsql COMMIT
+}
+
+tvfs filter xWrite
+tvfs script write_callback
+db2 eval { PRAGMA wal_checkpoint }
+db close
+db2 close
+tvfs delete
+sqlite3_snapshot_free $snapshot
+
+#-------------------------------------------------------------------------
+# Test that sqlite3_snapshot_get() may be called immediately after
+# "BEGIN; PRAGMA user_version;". And that sqlite3_snapshot_open() may
+# be called after opening the db handle and running the script
+# "PRAGMA user_version; BEGIN".
+reset_db
+do_execsql_test 6.1 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE x1(x, xx, xxx);
+ INSERT INTO x1 VALUES('z', 'zz', 'zzz');
+ BEGIN;
+ PRAGMA user_version;
+} {wal 0}
+do_test 6.2 {
+ set ::snapshot [sqlite3_snapshot_get db main]
+ execsql {
+ INSERT INTO x1 VALUES('a', 'aa', 'aaa');
+ COMMIT;
+ }
+} {}
+do_test 6.3 {
+ sqlite3 db2 test.db
+ db2 eval "PRAGMA user_version ; BEGIN"
+ sqlite3_snapshot_open db2 main $::snapshot
+ db2 eval { SELECT * FROM x1 }
+} {z zz zzz}
+sqlite3_snapshot_free $snapshot
+
+finish_test
diff --git a/test/snapshot_fault.test b/test/snapshot_fault.test
new file mode 100644
index 0000000000..3ac13daefd
--- /dev/null
+++ b/test/snapshot_fault.test
@@ -0,0 +1,164 @@
+# 2015 December 10
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library. The focus
+# of this file is the sqlite3_snapshot_xxx() APIs.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+ifcapable !snapshot {finish_test; return}
+set testprefix snapshot_fault
+
+#-------------------------------------------------------------------------
+# Check that an sqlite3_snapshot_open() client cannot be tricked into
+# reading a corrupt snapshot even if a second client fails while
+# checkpointing the db.
+#
+do_faultsim_test 1.0 -prep {
+ faultsim_delete_and_reopen
+ sqlite3 db2 test.db
+ db2 eval {
+ CREATE TABLE t1(a, b UNIQUE, c UNIQUE);
+ INSERT INTO t1 VALUES(1, randomblob(500), randomblob(500));
+ INSERT INTO t1 VALUES(2, randomblob(500), randomblob(500));
+ PRAGMA journal_mode = wal;
+ INSERT INTO t1 VALUES(3, randomblob(500), randomblob(500));
+ BEGIN;
+ SELECT a FROM t1;
+ }
+ set ::snapshot [sqlite3_snapshot_get db2 main]
+ db2 eval COMMIT
+ db2 eval {
+ UPDATE t1 SET b=randomblob(501), c=randomblob(501) WHERE a=1;
+ INSERT INTO t1 VALUES(4, randomblob(500), randomblob(500));
+ INSERT INTO t1 VALUES(5, randomblob(500), randomblob(500));
+ INSERT INTO t1 VALUES(6, randomblob(500), randomblob(500));
+ }
+} -body {
+ db eval { PRAGMA wal_checkpoint }
+} -test {
+ db2 eval BEGIN
+ if {[catch { sqlite3_snapshot_open db2 main $::snapshot } msg]} {
+ if {$msg != "SQLITE_BUSY_SNAPSHOT" && $msg != "SQLITE_BUSY"} {
+ error "error is $msg"
+ }
+ } else {
+ set res [db2 eval {
+ SELECT a FROM t1;
+ PRAGMA integrity_check;
+ }]
+ if {$res != "1 2 3 ok"} { error "res is $res" }
+ }
+
+ sqlite3_snapshot_free $::snapshot
+}
+
+#-------------------------------------------------------------------------
+# This test is similar to the previous one. Except, after the
+# "PRAGMA wal_checkpoint" command fails the db is closed and reopened
+# so as to require wal file recovery. It should not be possible to open
+# a snapshot that is part of the body of a recovered wal file.
+#
+do_faultsim_test 2.0 -prep {
+ faultsim_delete_and_reopen
+ db eval {
+ CREATE TABLE t1(a, b UNIQUE, c UNIQUE);
+ INSERT INTO t1 VALUES(1, randomblob(500), randomblob(500));
+ INSERT INTO t1 VALUES(2, randomblob(500), randomblob(500));
+ PRAGMA journal_mode = wal;
+ INSERT INTO t1 VALUES(3, randomblob(500), randomblob(500));
+ BEGIN;
+ SELECT a FROM t1;
+ }
+ set ::snapshot [sqlite3_snapshot_get db main]
+ db eval COMMIT
+
+ db eval {
+ UPDATE t1 SET b=randomblob(501), c=randomblob(501) WHERE a=1;
+ INSERT INTO t1 VALUES(4, randomblob(500), randomblob(500));
+ INSERT INTO t1 VALUES(5, randomblob(500), randomblob(500));
+ INSERT INTO t1 VALUES(6, randomblob(500), randomblob(500));
+ }
+} -body {
+ db eval { PRAGMA wal_checkpoint }
+} -test {
+
+ db_save
+ db close
+ db_restore_and_reopen
+ db eval { SELECT * FROM t1 }
+
+ db eval BEGIN
+ if {[catch { sqlite3_snapshot_open db main $::snapshot } msg]} {
+ if {$msg != "SQLITE_BUSY_SNAPSHOT" && $msg != "SQLITE_BUSY"} {
+ error "error is $msg"
+ }
+ } else {
+ # This branch should actually never be taken. But it was useful in
+ # determining whether or not this test was actually working (by
+ # running a modified version of SQLite that allowed snapshots to be
+ # opened following a recovery).
+ error "TEST HAS FAILED"
+
+ set res [db eval {
+ SELECT a FROM t1;
+ PRAGMA integrity_check;
+ }]
+ if {$res != "1 2 3 ok"} { error "res is $res" }
+ }
+
+ sqlite3_snapshot_free $::snapshot
+}
+
+#-------------------------------------------------------------------------
+# Test the handling of faults that occur within sqlite3_snapshot_open().
+#
+do_faultsim_test 3.0 -prep {
+ faultsim_delete_and_reopen
+ db eval {
+ CREATE TABLE t1(a, b UNIQUE, c UNIQUE);
+ INSERT INTO t1 VALUES(1, randomblob(500), randomblob(500));
+ INSERT INTO t1 VALUES(2, randomblob(500), randomblob(500));
+ PRAGMA journal_mode = wal;
+ INSERT INTO t1 VALUES(3, randomblob(500), randomblob(500));
+ BEGIN;
+ SELECT a FROM t1;
+ }
+ set ::snapshot [sqlite3_snapshot_get db main]
+ db eval COMMIT
+ db eval {
+ UPDATE t1 SET b=randomblob(501), c=randomblob(501) WHERE a=1;
+ INSERT INTO t1 VALUES(4, randomblob(500), randomblob(500));
+ INSERT INTO t1 VALUES(5, randomblob(500), randomblob(500));
+ INSERT INTO t1 VALUES(6, randomblob(500), randomblob(500));
+ BEGIN;
+ }
+} -body {
+ if { [catch { sqlite3_snapshot_open db main $::snapshot } msg] } {
+ error $msg
+ }
+} -test {
+ faultsim_test_result {0 {}} {1 SQLITE_IOERR} \
+ {1 SQLITE_IOERR_NOMEM} {1 SQLITE_IOERR_READ}
+ if {$testrc==0} {
+ set res [db eval {
+ SELECT a FROM t1;
+ PRAGMA integrity_check;
+ }]
+ if {$res != "1 2 3 ok"} { error "res is $res" }
+ }
+
+ sqlite3_snapshot_free $::snapshot
+}
+
+
+
+finish_test
diff --git a/test/spellfix3.test b/test/spellfix3.test
new file mode 100644
index 0000000000..ce002edd4f
--- /dev/null
+++ b/test/spellfix3.test
@@ -0,0 +1,43 @@
+# 2015-12-17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix spellfix3
+
+ifcapable !vtab { finish_test ; return }
+
+load_static_extension db spellfix
+
+do_execsql_test 100 {
+ SELECT spellfix1_scriptcode('And God said, “Let there be light”');
+} {215}
+do_execsql_test 110 {
+ SELECT spellfix1_scriptcode('Бог сказал: "Да будет свет"');
+} {220}
+do_execsql_test 120 {
+ SELECT spellfix1_scriptcode('και ειπεν ο θεος γενηθητω φως και εγενετο φως');
+} {200}
+do_execsql_test 130 {
+ SELECT spellfix1_scriptcode('וַיֹּ֥אמֶר אֱלֹהִ֖ים יְהִ֣י א֑וֹר וַֽיְהִי־אֽוֹר׃');
+} {125}
+do_execsql_test 140 {
+ SELECT spellfix1_scriptcode('فِي ذَلِكَ الوَقتِ، قالَ اللهُ: لِيَكُنْ نُورٌ. فَصَارَ نُورٌ.');
+} {160}
+do_execsql_test 200 {
+ SELECT spellfix1_scriptcode('+3.14159');
+} {999}
+do_execsql_test 210 {
+ SELECT spellfix1_scriptcode('And God said: "Да будет свет"');
+} {998}
+
+finish_test
diff --git a/test/symlink.test b/test/symlink.test
index af7ec2a67b..790624161f 100644
--- a/test/symlink.test
+++ b/test/symlink.test
@@ -116,4 +116,14 @@ do_execsql_test 2.5 {
SELECT * FROM t1;
} {1 2}
+# Try to open a ridiculously long pathname. Bug found by
+# Kostya Serebryany using libFuzzer on 2015-11-30.
+#
+do_test 3.1 {
+ db close
+ catch {sqlite3 db [string repeat [string repeat x 100]/ 6]} res
+ set res
+} {unable to open database file}
+
+
finish_test
diff --git a/test/syscall.test b/test/syscall.test
index 83b8b8b40f..a935957d39 100644
--- a/test/syscall.test
+++ b/test/syscall.test
@@ -60,7 +60,7 @@ foreach s {
open close access getcwd stat fstat ftruncate
fcntl read pread write pwrite fchmod fallocate
pread64 pwrite64 unlink openDirectory mkdir rmdir
- statvfs fchown umask mmap munmap mremap
+ statvfs fchown geteuid umask mmap munmap mremap
getpagesize readlink
} {
if {[test_syscall exists $s]} {lappend syscall_list $s}
diff --git a/test/sysfault.test b/test/sysfault.test
index 92fb534dd0..4c3d34dbb9 100644
--- a/test/sysfault.test
+++ b/test/sysfault.test
@@ -226,6 +226,7 @@ do_faultsim_test 3 -faults vfsfault-* -prep {
faultsim_delete_and_reopen
file_control_chunksize_test db main 8192
execsql {
+ PRAGMA synchronous=OFF;
CREATE TABLE t1(a, b);
BEGIN;
SELECT * FROM t1;
diff --git a/test/tester.tcl b/test/tester.tcl
index 4008a34491..426808ea90 100644
--- a/test/tester.tcl
+++ b/test/tester.tcl
@@ -1028,7 +1028,13 @@ proc finalize_testing {} {
output2 "[expr {$nErr-$nKnown}] new errors and $nKnown known errors\
out of $nTest tests"
} else {
- output2 "$nErr errors out of $nTest tests"
+ set cpuinfo {}
+ if {[catch {exec hostname} hname]==0} {set cpuinfo [string trim $hname]}
+ append cpuinfo " $::tcl_platform(os)"
+ append cpuinfo " [expr {$::tcl_platform(pointerSize)*8}]-bit"
+ append cpuinfo " [string map {E -e} $::tcl_platform(byteOrder)]"
+ output2 "SQLite [sqlite3 -sourceid]"
+ output2 "$nErr errors out of $nTest tests on $cpuinfo"
}
if {$nErr>$nKnown} {
output2 -nonewline "!Failures on these tests:"
diff --git a/test/threadtest3.c b/test/threadtest3.c
index 25caeb89f9..6062b64285 100644
--- a/test/threadtest3.c
+++ b/test/threadtest3.c
@@ -88,6 +88,13 @@
#include
#include
+#include "test_multiplex.h"
+
+/* Required to link test_multiplex.c */
+#ifndef SQLITE_OMIT_WSD
+int sqlite3PendingByte = 0x40000000;
+#endif
+
/*
* This code implements the MD5 message-digest algorithm.
* The algorithm is due to Ron Rivest. This code was
@@ -868,22 +875,28 @@ static void filecopy_x(
** Used by setstoptime() and timetostop().
*/
static double timelimit = 0.0;
-static sqlite3_vfs *pTimelimitVfs = 0;
+
+static double currentTime(void){
+ double t;
+ static sqlite3_vfs *pTimelimitVfs = 0;
+ if( pTimelimitVfs==0 ) pTimelimitVfs = sqlite3_vfs_find(0);
+ if( pTimelimitVfs->iVersion>=2 && pTimelimitVfs->xCurrentTimeInt64!=0 ){
+ sqlite3_int64 tm;
+ pTimelimitVfs->xCurrentTimeInt64(pTimelimitVfs, &tm);
+ t = tm/86400000.0;
+ }else{
+ pTimelimitVfs->xCurrentTime(pTimelimitVfs, &t);
+ }
+ return t;
+}
static void setstoptime_x(
Error *pErr, /* IN/OUT: Error code */
int nMs /* Milliseconds until "stop time" */
){
if( pErr->rc==SQLITE_OK ){
- double t;
- int rc;
- pTimelimitVfs = sqlite3_vfs_find(0);
- rc = pTimelimitVfs->xCurrentTime(pTimelimitVfs, &t);
- if( rc!=SQLITE_OK ){
- pErr->rc = rc;
- }else{
- timelimit = t + ((double)nMs)/(1000.0*60.0*60.0*24.0);
- }
+ double t = currentTime();
+ timelimit = t + ((double)nMs)/(1000.0*60.0*60.0*24.0);
}
}
@@ -892,14 +905,8 @@ static int timetostop_x(
){
int ret = 1;
if( pErr->rc==SQLITE_OK ){
- double t;
- int rc;
- rc = pTimelimitVfs->xCurrentTime(pTimelimitVfs, &t);
- if( rc!=SQLITE_OK ){
- pErr->rc = rc;
- }else{
- ret = (t >= timelimit);
- }
+ double t = currentTime();
+ ret = (t >= timelimit);
}
return ret;
}
@@ -1460,13 +1467,36 @@ int main(int argc, char **argv){
argc = 2;
argv = substArgv;
}
+
+ /* Loop through the command-line arguments to ensure that each argument
+ ** selects at least one test. If not, assume there is a typo on the
+ ** command-line and bail out with the usage message. */
for(iArg=1; iArg=sizeof(aTest)/sizeof(aTest[0]) ) goto usage;
}
+
for(iArg=1; iArg0 ? 255 : 0);
usage:
- printf("Usage: %s [testname|testprefix*]...\n", argv[0]);
+ printf("Usage: %s [-multiplexor] [testname|testprefix*]...\n", argv[0]);
printf("Available tests are:\n");
for(i=0; i= ? AND b <= ?} \
- xFilter {SELECT rowid, * FROM 'treal' WHERE b >= ? AND b <= ?} 5 10 ]
+} [list xBestIndex {SELECT rowid, a, b, c FROM 'treal' WHERE b >= ? AND b <= ?}\
+ xFilter {SELECT rowid, a, b, c FROM 'treal' WHERE b >= ? AND b <= ?}\
+ 5 10 ]
do_test vtab1-3.12 {
set echo_module ""
execsql {
@@ -440,8 +441,9 @@ do_test vtab1-3.12 {
} {1 2 3 4 5 6}
do_test vtab1-3.13 {
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'treal' WHERE b >= ? AND b <= ?} \
- xFilter {SELECT rowid, * FROM 'treal' WHERE b >= ? AND b <= ?} 2 10 ]
+} [list xBestIndex {SELECT rowid, a, b, c FROM 'treal' WHERE b >= ? AND b <= ?}\
+ xFilter {SELECT rowid, a, b, c FROM 'treal' WHERE b >= ? AND b <= ?}\
+ 2 10 ]
# Add a function for the MATCH operator. Everything always matches!
#proc test_match {lhs rhs} {
@@ -459,8 +461,8 @@ do_test vtab1-3.12 {
} {1 {unable to use function MATCH in the requested context}}
do_test vtab1-3.13 {
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'treal'} \
- xFilter {SELECT rowid, * FROM 'treal'}]
+} [list xBestIndex {SELECT rowid, a, b, c FROM 'treal'} \
+ xFilter {SELECT rowid, a, b, c FROM 'treal'}]
ifcapable subquery {
# The echo module uses a subquery internally to implement the MATCH operator.
do_test vtab1-3.14 {
@@ -472,9 +474,9 @@ do_test vtab1-3.14 {
do_test vtab1-3.15 {
set echo_module
} [list xBestIndex \
- {SELECT rowid, * FROM 'treal' WHERE b LIKE (SELECT '%'||?||'%')} \
+ {SELECT rowid, a, b, c FROM 'treal' WHERE b LIKE (SELECT '%'||?||'%')} \
xFilter \
- {SELECT rowid, * FROM 'treal' WHERE b LIKE (SELECT '%'||?||'%')} \
+ {SELECT rowid, a, b, c FROM 'treal' WHERE b LIKE (SELECT '%'||?||'%')} \
string ]
}; #ifcapable subquery
@@ -505,8 +507,8 @@ do_test vtab1-4.1 {
} {2 5 nosort}
do_test vtab1-4.2 {
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'treal' ORDER BY b ASC} \
- xFilter {SELECT rowid, * FROM 'treal' ORDER BY b ASC} ]
+} [list xBestIndex {SELECT rowid, NULL, b, NULL FROM 'treal' ORDER BY b ASC} \
+ xFilter {SELECT rowid, NULL, b, NULL FROM 'treal' ORDER BY b ASC} ]
do_test vtab1-4.3 {
set echo_module ""
cksort {
@@ -515,8 +517,8 @@ do_test vtab1-4.3 {
} {5 2 nosort}
do_test vtab1-4.4 {
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'treal' ORDER BY b DESC} \
- xFilter {SELECT rowid, * FROM 'treal' ORDER BY b DESC} ]
+} [list xBestIndex {SELECT rowid, NULL, b, NULL FROM 'treal' ORDER BY b DESC} \
+ xFilter {SELECT rowid, NULL, b, NULL FROM 'treal' ORDER BY b DESC} ]
do_test vtab1-4.3 {
set echo_module ""
cksort {
@@ -525,8 +527,8 @@ do_test vtab1-4.3 {
} {2 5 sort}
do_test vtab1-4.4 {
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'treal'} \
- xFilter {SELECT rowid, * FROM 'treal'} ]
+} [list xBestIndex {SELECT rowid, NULL, b, NULL FROM 'treal'} \
+ xFilter {SELECT rowid, NULL, b, NULL FROM 'treal'} ]
execsql {
DROP TABLE t1;
@@ -575,9 +577,9 @@ do_test vtab1-5-2 {
do_test vtab1-5-3 {
filter $echo_module
} [list \
- xFilter {SELECT rowid, * FROM 't1'} \
- xFilter {SELECT rowid, * FROM 't2'} \
- xFilter {SELECT rowid, * FROM 't2'} \
+ xFilter {SELECT rowid, a, b, c FROM 't1'} \
+ xFilter {SELECT rowid, d, e, f FROM 't2'} \
+ xFilter {SELECT rowid, d, e, f FROM 't2'} \
]
do_test vtab1-5-4 {
set echo_module ""
@@ -591,9 +593,9 @@ do_test vtab1-5-4 {
do_test vtab1-5-5 {
filter $echo_module
} [list \
- xFilter {SELECT rowid, * FROM 't1'} \
- xFilter {SELECT rowid, * FROM 't2'} \
- xFilter {SELECT rowid, * FROM 't2'} \
+ xFilter {SELECT rowid, a, b, c FROM 't1'} \
+ xFilter {SELECT rowid, d, e, f FROM 't2'} \
+ xFilter {SELECT rowid, d, e, f FROM 't2'} \
]
do_test vtab1-5-6 {
execsql {
@@ -615,9 +617,9 @@ do_test vtab1-5-6 {
do_test vtab1-5-7 {
filter $::echo_module
} [list \
- xFilter {SELECT rowid, * FROM 't1'} \
- xFilter {SELECT rowid, * FROM 't2' WHERE d = ?} \
- xFilter {SELECT rowid, * FROM 't2' WHERE d = ?} \
+ xFilter {SELECT rowid, a, b, c FROM 't1'} \
+ xFilter {SELECT rowid, d, e, f FROM 't2' WHERE d = ?} \
+ xFilter {SELECT rowid, d, e, f FROM 't2' WHERE d = ?} \
]
execsql {
@@ -967,8 +969,8 @@ do_test vtab1.10-5 {
}
set echo_module
} [list \
- xBestIndex {SELECT rowid, * FROM 'r'} \
- xFilter {SELECT rowid, * FROM 'r'} \
+ xBestIndex {SELECT rowid, a, b, c FROM 'r'} \
+ xFilter {SELECT rowid, a, b, c FROM 'r'} \
]
proc match_func {args} {return ""}
do_test vtab1.10-6 {
@@ -979,8 +981,8 @@ do_test vtab1.10-6 {
}
set echo_module
} [list \
- xBestIndex {SELECT rowid, * FROM 'r'} \
- xFilter {SELECT rowid, * FROM 'r'} \
+ xBestIndex {SELECT rowid, a, b, c FROM 'r'} \
+ xFilter {SELECT rowid, a, b, c FROM 'r'} \
]
@@ -1153,13 +1155,15 @@ do_test vtab1-14.2 {
set echo_module ""
execsql { SELECT * FROM echo_c WHERE rowid = 1 }
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'c' WHERE rowid = ?} xFilter {SELECT rowid, * FROM 'c' WHERE rowid = ?} 1]
+} [list xBestIndex {SELECT rowid, a, b, c FROM 'c' WHERE rowid = ?} \
+ xFilter {SELECT rowid, a, b, c FROM 'c' WHERE rowid = ?} 1]
do_test vtab1-14.3 {
set echo_module ""
execsql { SELECT * FROM echo_c WHERE a = 1 }
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'c' WHERE a = ?} xFilter {SELECT rowid, * FROM 'c' WHERE a = ?} 1]
+} [list xBestIndex {SELECT rowid, a, b, c FROM 'c' WHERE a = ?} \
+ xFilter {SELECT rowid, a, b, c FROM 'c' WHERE a = ?} 1]
#do_test vtab1-14.4 {
# set echo_module ""
@@ -1300,16 +1304,16 @@ do_execsql_test 18.1.0 {
foreach {tn sql res filter} {
1.1 "SELECT a FROM e6 WHERE b>'James'" {4 1 5}
- {xFilter {SELECT rowid, * FROM 't6' WHERE b > ?} James}
+ {xFilter {SELECT rowid, a, b FROM 't6' WHERE b > ?} James}
1.2 "SELECT a FROM e6 WHERE b>='J' AND b<'K'" {3 4}
- {xFilter {SELECT rowid, * FROM 't6' WHERE b >= ? AND b < ?} J K}
+ {xFilter {SELECT rowid, a, b FROM 't6' WHERE b >= ? AND b < ?} J K}
1.3 "SELECT a FROM e6 WHERE b LIKE 'J%'" {3 4}
- {xFilter {SELECT rowid, * FROM 't6'}}
+ {xFilter {SELECT rowid, a, b FROM 't6' WHERE b like ?} J%}
1.4 "SELECT a FROM e6 WHERE b LIKE 'j%'" {3 4}
- {xFilter {SELECT rowid, * FROM 't6'}}
+ {xFilter {SELECT rowid, a, b FROM 't6' WHERE b like ?} j%}
} {
set echo_module {}
do_execsql_test 18.$tn.1 $sql $res
@@ -1319,10 +1323,10 @@ foreach {tn sql res filter} {
do_execsql_test 18.2.0 { PRAGMA case_sensitive_like = ON }
foreach {tn sql res filter} {
2.1 "SELECT a FROM e6 WHERE b LIKE 'J%'" {3 4}
- {xFilter {SELECT rowid, * FROM 't6'}}
+ {xFilter {SELECT rowid, a, b FROM 't6' WHERE b like ?} J%}
2.2 "SELECT a FROM e6 WHERE b LIKE 'j%'" {}
- {xFilter {SELECT rowid, * FROM 't6'}}
+ {xFilter {SELECT rowid, a, b FROM 't6' WHERE b like ?} j%}
} {
set echo_module {}
do_execsql_test 18.$tn.1 $sql $res
diff --git a/test/vtab4.test b/test/vtab4.test
index 07b6e839d7..d12ca33cc0 100644
--- a/test/vtab4.test
+++ b/test/vtab4.test
@@ -57,9 +57,9 @@ do_test vtab4-1.3 {
UPDATE techo SET a = 2;
}
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'treal'} \
+} [list xBestIndex {SELECT rowid, a, b, c FROM 'treal'} \
xBegin echo(treal) \
- xFilter {SELECT rowid, * FROM 'treal'} \
+ xFilter {SELECT rowid, a, b, c FROM 'treal'} \
xSync echo(treal) \
xCommit echo(treal) \
]
@@ -69,9 +69,9 @@ do_test vtab4-1.4 {
DELETE FROM techo;
}
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'treal'} \
+} [list xBestIndex {SELECT rowid, NULL, NULL, NULL FROM 'treal'} \
xBegin echo(treal) \
- xFilter {SELECT rowid, * FROM 'treal'} \
+ xFilter {SELECT rowid, NULL, NULL, NULL FROM 'treal'} \
xSync echo(treal) \
xCommit echo(treal) \
]
@@ -105,12 +105,12 @@ do_test vtab4-2.2 {
COMMIT;
}
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'treal'} \
+} [list xBestIndex {SELECT rowid, a, b, c FROM 'treal'} \
xBegin echo(sreal) \
- xFilter {SELECT rowid, * FROM 'treal'} \
- xBestIndex {SELECT rowid, * FROM 'treal'} \
+ xFilter {SELECT rowid, a, b, c FROM 'treal'} \
+ xBestIndex {SELECT rowid, NULL, NULL, NULL FROM 'treal'} \
xBegin echo(treal) \
- xFilter {SELECT rowid, * FROM 'treal'} \
+ xFilter {SELECT rowid, NULL, NULL, NULL FROM 'treal'} \
xSync echo(sreal) \
xSync echo(treal) \
xCommit echo(sreal) \
@@ -137,12 +137,12 @@ do_test vtab4-2.5 {
ROLLBACK;
}
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'sreal'} \
+} [list xBestIndex {SELECT rowid, a, b, c FROM 'sreal'} \
xBegin echo(treal) \
- xFilter {SELECT rowid, * FROM 'sreal'} \
- xBestIndex {SELECT rowid, * FROM 'sreal'} \
+ xFilter {SELECT rowid, a, b, c FROM 'sreal'} \
+ xBestIndex {SELECT rowid, NULL, NULL, NULL FROM 'sreal'} \
xBegin echo(sreal) \
- xFilter {SELECT rowid, * FROM 'sreal'} \
+ xFilter {SELECT rowid, NULL, NULL, NULL FROM 'sreal'} \
xRollback echo(treal) \
xRollback echo(sreal) \
]
@@ -178,12 +178,12 @@ do_test vtab4-3.3 {
COMMIT;
}
set echo_module
-} [list xBestIndex {SELECT rowid, * FROM 'sreal'} \
+} [list xBestIndex {SELECT rowid, a, b, c FROM 'sreal'} \
xBegin echo(treal) \
- xFilter {SELECT rowid, * FROM 'sreal'} \
- xBestIndex {SELECT rowid, * FROM 'sreal'} \
+ xFilter {SELECT rowid, a, b, c FROM 'sreal'} \
+ xBestIndex {SELECT rowid, NULL, NULL, NULL FROM 'sreal'} \
xBegin echo(sreal) \
- xFilter {SELECT rowid, * FROM 'sreal'} \
+ xFilter {SELECT rowid, NULL, NULL, NULL FROM 'sreal'} \
xSync echo(treal) \
xSync echo(sreal) \
xRollback echo(treal) \
diff --git a/test/vtabE.test b/test/vtabE.test
index 22ec0181bf..aeb478e3e8 100644
--- a/test/vtabE.test
+++ b/test/vtabE.test
@@ -46,3 +46,5 @@ do_test vtabE-1 {
ORDER BY t1.value, t2.value;
}
} {vtabE vtabE1 11 vtabE1 w x {} vtabE vtabE1 11 vtabE1 y z {} vtabE vtabE2 22 vtabE2 a b {} vtabE vtabE2 22 vtabE2 c d {}}
+
+finish_test
diff --git a/test/vtabH.test b/test/vtabH.test
new file mode 100644
index 0000000000..d16db13674
--- /dev/null
+++ b/test/vtabH.test
@@ -0,0 +1,202 @@
+# 2015 Nov 24
+#
+# 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 regression tests for SQLite library. Specifically,
+# it tests that the GLOB, LIKE and REGEXP operators are correctly exposed
+# to virtual table implementations.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix vtabH
+
+ifcapable !vtab {
+ finish_test
+ return
+}
+
+register_echo_module db
+
+do_execsql_test 1.0 {
+ CREATE TABLE t6(a, b TEXT);
+ CREATE INDEX i6 ON t6(b, a);
+ CREATE VIRTUAL TABLE e6 USING echo(t6);
+}
+
+foreach {tn sql expect} {
+ 1 "SELECT * FROM e6 WHERE b LIKE 'abc'" {
+ xBestIndex {SELECT rowid, a, b FROM 't6' WHERE b like ?}
+ xFilter {SELECT rowid, a, b FROM 't6' WHERE b like ?} abc
+ }
+
+ 2 "SELECT * FROM e6 WHERE b GLOB 'abc'" {
+ xBestIndex {SELECT rowid, a, b FROM 't6' WHERE b glob ?}
+ xFilter {SELECT rowid, a, b FROM 't6' WHERE b glob ?} abc
+ }
+} {
+ do_test 1.$tn {
+ set echo_module {}
+ execsql $sql
+ set ::echo_module
+ } [list {*}$expect]
+}
+
+
+#--------------------------------------------------------------------------
+
+register_tclvar_module db
+set ::xyz 10
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE vars USING tclvar;
+ SELECT * FROM vars WHERE name = 'xyz';
+} {xyz {} 10}
+
+set x1 aback
+set x2 abaft
+set x3 abandon
+set x4 abandonint
+set x5 babble
+set x6 baboon
+set x7 backbone
+set x8 backarrow
+set x9 castle
+
+db func glob -argcount 2 gfunc
+proc gfunc {a b} {
+ incr ::gfunc
+ return 1
+}
+
+db func like -argcount 2 lfunc
+proc lfunc {a b} {
+ incr ::gfunc 100
+ return 1
+}
+
+db func regexp -argcount 2 rfunc
+proc rfunc {a b} {
+ incr ::gfunc 10000
+ return 1
+}
+
+foreach ::tclvar_set_omit {0 1} {
+ foreach {tn expr res cnt} {
+ 1 {value GLOB 'aban*'} {x3 abandon x4 abandonint} 2
+ 2 {value LIKE '%ac%'} {x1 aback x7 backbone x8 backarrow} 300
+ 3 {value REGEXP '^......$'} {x5 babble x6 baboon x9 castle} 30000
+ } {
+ db cache flush
+ set ::gfunc 0
+ if {$::tclvar_set_omit} {set cnt 0}
+
+ do_test 2.$tclvar_set_omit.$tn.1 {
+ execsql "SELECT name, value FROM vars WHERE name MATCH 'x*' AND $expr"
+ } $res
+
+ do_test 2.$tclvar_set_omit.$tn.2 {
+ set ::gfunc
+ } $cnt
+ }
+}
+
+#-------------------------------------------------------------------------
+#
+if {1} {
+ reset_db
+ register_fs_module db
+ do_execsql_test 3.0 {
+ SELECT name FROM fsdir WHERE dir = '.' AND name = 'test.db';
+ SELECT name FROM fsdir WHERE dir = '.' AND name = '.'
+ } {test.db .}
+
+ proc list_root_files {} {
+ if {$::tcl_platform(platform) eq "windows"} {
+ set res [list]
+ foreach name [glob -directory $::env(SystemDrive)/ -- *] {
+ if {[string index [file tail $name] 0] eq "."} continue
+ lappend res $name
+ }
+ return $res
+ } else {
+ return [string map {/ {}} [glob /*]]
+ }
+ }
+
+ proc list_files { pattern } {
+ if {$::tcl_platform(platform) eq "windows"} {
+ set res [list]
+ foreach name [glob -nocomplain $pattern] {
+ if {[string index [file tail $name] 0] eq "."} continue
+ lappend res $name
+ }
+ return $res
+ } else {
+ return [glob -nocomplain $pattern]
+ }
+ }
+
+ # Read all entries in the current directory.
+ #
+ proc contents {pattern} {
+ set res [list]
+ foreach f [list_files $pattern] {
+ lappend res $f
+ if {[file isdir $f]} {
+ set res [concat $res [contents "$f/*"]]
+ }
+ }
+ set res
+ }
+ set pwd "[pwd]/*"
+ set res [contents $pwd]
+ do_execsql_test 3.2 {
+ SELECT path FROM fstree WHERE path GLOB $pwd ORDER BY 1
+ } [lsort $res]
+
+ # Add some sub-directories and files to the current directory.
+ #
+ do_test 3.3 {
+ catch { file delete -force subdir }
+ foreach {path sz} {
+ subdir/x1.txt 143
+ subdir/x2.txt 153
+ } {
+ set dir [file dirname $path]
+ catch { file mkdir $dir }
+ set fd [open $path w]
+ puts -nonewline $fd [string repeat 1 $sz]
+ close $fd
+ }
+ } {}
+
+ set pwd [pwd]
+ do_execsql_test 3.5 {
+ SELECT path, size FROM fstree WHERE path GLOB $pwd || '/subdir/*' ORDER BY 1
+ } [list \
+ "$pwd/subdir/x1.txt" 143 \
+ "$pwd/subdir/x2.txt" 153 \
+ ]
+ do_execsql_test 3.6 {
+ SELECT path, size FROM fstree WHERE path LIKE $pwd || '/subdir/%' ORDER BY 1
+ } [list \
+ "$pwd/subdir/x1.txt" 143 \
+ "$pwd/subdir/x2.txt" 153 \
+ ]
+ do_execsql_test 3.7 {
+ SELECT sum(size) FROM fstree WHERE path LIKE $pwd || '/subdir/%'
+ } 296
+ do_execsql_test 3.8 {
+ SELECT size FROM fstree WHERE path = $pwd || '/subdir/x1.txt'
+ } 143
+
+}
+
+
+finish_test
diff --git a/test/vtabI.test b/test/vtabI.test
new file mode 100644
index 0000000000..4b5a0a8c32
--- /dev/null
+++ b/test/vtabI.test
@@ -0,0 +1,126 @@
+# 2015 Nov 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.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library. Specifically,
+# it tests the sqlite3_index_info.colUsed variable is set correctly.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix vtabI
+
+ifcapable !vtab {
+ finish_test
+ return
+}
+
+register_echo_module db
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a, b, c, d, e);
+ CREATE VIRTUAL TABLE e1 USING echo(t1);
+}
+
+foreach {tn query filter} {
+ 1 {SELECT * FROM e1}
+ {SELECT rowid, a, b, c, d, e FROM 't1'}
+
+ 2 {SELECT a, b FROM e1}
+ {SELECT rowid, a, b, NULL, NULL, NULL FROM 't1'}
+
+ 3 {SELECT count(*) FROM e1 GROUP BY b}
+ {SELECT rowid, NULL, b, NULL, NULL, NULL FROM 't1'}
+
+ 4 {SELECT count(*) FROM e1 GROUP BY b HAVING a=?}
+ {SELECT rowid, a, b, NULL, NULL, NULL FROM 't1'}
+
+ 5 {SELECT a FROM e1 WHERE c=?}
+ {SELECT rowid, a, NULL, c, NULL, NULL FROM 't1'}
+
+ 6 {SELECT a FROM e1 ORDER BY e}
+ {SELECT rowid, a, NULL, NULL, NULL, e FROM 't1'}
+
+ 7 {SELECT a FROM e1 ORDER BY e, d}
+ {SELECT rowid, a, NULL, NULL, d, e FROM 't1'}
+} {
+ do_test 1.$tn {
+ set ::echo_module [list]
+ execsql $query
+ set idx [lsearch -exact $::echo_module xFilter]
+ lindex $::echo_module [expr $idx+1]
+ } $filter
+}
+
+#-------------------------------------------------------------------------
+# Tests with a table with more than 64 columns.
+#
+proc all_col_list {} {
+ set L [list]
+ for {set i 1} {$i <= 100} {incr i} { lappend L "c$i" }
+ set L
+}
+
+proc part_col_list {cols} {
+ set L [list]
+ for {set i 1} {$i <= 100} {incr i} {
+ set c "c$i"
+ if {[lsearch $cols $c]>=0} {
+ lappend L "c$i"
+ } else {
+ lappend L NULL
+ }
+ }
+ set L
+}
+proc CL {args} {
+ join [part_col_list $args] ", "
+}
+proc CLT {args} {
+ set cols $args
+ for {set i 64} {$i <= 100} {incr i} {
+ lappend cols "c$i"
+ }
+ join [part_col_list $cols] ", "
+}
+
+do_test 2.0 {
+ execsql "CREATE TABLE t2([join [all_col_list] ,])"
+ execsql "CREATE VIRTUAL TABLE e2 USING echo(t2)"
+} {}
+
+foreach {tn query filter} {
+ 1 {SELECT c1, c10, c20 FROM e2}
+ {SELECT rowid, [CL c1 c10 c20] FROM 't2'}
+
+ 2 {SELECT c40, c50, c60 FROM e2}
+ {SELECT rowid, [CL c40 c50 c60] FROM 't2'}
+
+ 3 {SELECT c7, c80, c90 FROM e2}
+ {SELECT rowid, [CLT c7] FROM 't2'}
+
+ 4 {SELECT c64 FROM e2}
+ {SELECT rowid, [CLT c64] FROM 't2'}
+
+ 5 {SELECT c63 FROM e2}
+ {SELECT rowid, [CL c63] FROM 't2'}
+
+ 6 {SELECT c22 FROM e2 ORDER BY c50, c70}
+ {SELECT rowid, [CLT c22 c50] FROM 't2'}
+
+} {
+ do_test 2.$tn {
+ set ::echo_module [list]
+ execsql $query
+ set idx [lsearch -exact $::echo_module xFilter]
+ lindex $::echo_module [expr $idx+1]
+ } [subst $filter]
+}
+
+finish_test
diff --git a/test/wal.test b/test/wal.test
index bfe3634577..abd3a3ce49 100644
--- a/test/wal.test
+++ b/test/wal.test
@@ -712,7 +712,7 @@ do_test wal-11.5 {
do_test wal-11.6 {
execsql COMMIT
list [expr [file size test.db]/1024] [file size test.db-wal]
-} [list 3 [wal_file_size 41 1024]]
+} [list 3 [wal_file_size 40 1024]]
do_test wal-11.7 {
execsql {
SELECT count(*) FROM t1;
@@ -722,15 +722,22 @@ do_test wal-11.7 {
do_test wal-11.8 {
execsql { PRAGMA wal_checkpoint }
list [expr [file size test.db]/1024] [file size test.db-wal]
-} [list 37 [wal_file_size 41 1024]]
+} [list 37 [wal_file_size 40 1024]]
do_test wal-11.9 {
db close
list [expr [file size test.db]/1024] [log_deleted test.db-wal]
} {37 1}
sqlite3_wal db test.db
-set nWal 39
-if {[permutation]!="mmap"} {set nWal 37}
-ifcapable !mmap {set nWal 37}
+
+# After adding the capability of WAL to overwrite prior uncommitted
+# frame in the WAL-file with revised content, the size of the WAL file
+# following cache-spill is smaller.
+#
+#set nWal 39
+#if {[permutation]!="mmap"} {set nWal 37}
+#ifcapable !mmap {set nWal 37}
+set nWal 34
+
do_test wal-11.10 {
execsql {
PRAGMA cache_size = 10;
diff --git a/test/waloverwrite.test b/test/waloverwrite.test
new file mode 100644
index 0000000000..aa1154fb97
--- /dev/null
+++ b/test/waloverwrite.test
@@ -0,0 +1,164 @@
+# 2010 May 5
+#
+# 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 regression tests for SQLite library. The
+# focus of this file is testing the operation of the library in
+# "PRAGMA journal_mode=WAL" mode.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/wal_common.tcl
+set testprefix waloverwrite
+
+ifcapable !wal {finish_test ; return }
+
+# Simple test:
+#
+# Test cases *.1 - *.6:
+#
+# + Create a database of blobs roughly 50 pages in size.
+#
+# + Set the db cache size to something much smaller than this (5 pages)
+#
+# + Within a transaction, loop through the set of blobs 5 times. Update
+# each blob as it is visited.
+#
+# + Test that the wal file is roughly 50 pages in size - even though many
+# database pages have been written to it multiple times.
+#
+# + Take a copy of the database and wal file. Test that recovery can
+# be run on it.
+#
+# Test cases *.7 - *.9:
+#
+# + Same thing, but before committing the statement transaction open
+# a SAVEPOINT, update the blobs another 5 times, then roll it back.
+#
+# + Check that if recovery is run on the resulting wal file, the rolled
+# back changes from within the SAVEPOINT are not present in the db.
+#
+# The above is run twice - once where the wal file is empty at the start of
+# step 3 (tn==1) and once where it already contains a transaction (tn==2).
+#
+foreach {tn xtra} {
+ 1 {}
+ 2 { UPDATE t1 SET y = randomblob(799) WHERE x=4 }
+} {
+ reset_db
+ do_execsql_test 1.$tn.0 {
+ CREATE TABLE t1(x, y);
+ CREATE TABLE t2(x, y);
+ CREATE INDEX i1y ON t1(y);
+
+ WITH cnt(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<20
+ )
+ INSERT INTO t1 SELECT i, randomblob(800) FROM cnt;
+ } {}
+
+ do_test 1.$tn.1 {
+ set nPg [db one { PRAGMA page_count } ]
+ expr $nPg>40 && $nPg<50
+ } {1}
+
+ do_test 1.$tn.2 {
+ db close
+ sqlite3 db test.db
+
+ execsql {PRAGMA journal_mode = wal}
+ execsql {PRAGMA cache_size = 5}
+ execsql $xtra
+
+ db transaction {
+ for {set i 0} {$i < 5} {incr i} {
+ foreach x [db eval {SELECT x FROM t1}] {
+ execsql { UPDATE t1 SET y = randomblob(799) WHERE x=$x }
+ }
+ }
+ }
+
+ set nPg [wal_frame_count test.db-wal 1024]
+ expr $nPg>40 && $nPg<60
+ } {1}
+
+ do_execsql_test 1.$tn.3 { PRAGMA integrity_check } ok
+
+ do_test 1.$tn.4 {
+ forcedelete test.db2 test.db2-wal
+ forcecopy test.db test.db2
+ sqlite3 db2 test.db2
+ execsql { SELECT sum(length(y)) FROM t1 } db2
+ } [expr 20*800]
+
+ do_test 1.$tn.5 {
+ db2 close
+ forcecopy test.db test.db2
+ forcecopy test.db-wal test.db2-wal
+ sqlite3 db2 test.db2
+ execsql { SELECT sum(length(y)) FROM t1 } db2
+ } [expr 20*799]
+
+ do_test 1.$tn.6 {
+ execsql { PRAGMA integrity_check } db2
+ } ok
+ db2 close
+
+ do_test 1.$tn.7 {
+ execsql { PRAGMA wal_checkpoint }
+ db transaction {
+ for {set i 0} {$i < 1} {incr i} {
+ foreach x [db eval {SELECT x FROM t1}] {
+ execsql { UPDATE t1 SET y = randomblob(798) WHERE x=$x }
+ }
+ }
+
+ execsql {
+ WITH cnt(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<20)
+ INSERT INTO t2 SELECT i, randomblob(800) FROM cnt;
+ }
+
+ execsql {SAVEPOINT abc}
+ for {set i 0} {$i < 5} {incr i} {
+ foreach x [db eval {SELECT x FROM t1}] {
+ execsql { UPDATE t1 SET y = randomblob(797) WHERE x=$x }
+ }
+ }
+ breakpoint
+ execsql {ROLLBACK TO abc}
+
+ }
+
+ set nPg [wal_frame_count test.db-wal 1024]
+ expr $nPg>55 && $nPg<75
+ } {1}
+
+ do_test 1.$tn.8 {
+ forcedelete test.db2 test.db2-wal
+ forcecopy test.db test.db2
+ sqlite3 db2 test.db2
+ execsql { SELECT sum(length(y)) FROM t1 } db2
+ } [expr 20*799]
+
+ do_test 1.$tn.9 {
+ db2 close
+ forcecopy test.db-wal test.db2-wal
+ sqlite3 db2 test.db2
+ execsql { SELECT sum(length(y)) FROM t1 } db2
+ } [expr 20*798]
+
+ do_test 1.$tn.10 {
+ execsql { PRAGMA integrity_check } db2
+ } ok
+ db2 close
+}
+
+finish_test
+
diff --git a/test/where7.test b/test/where7.test
index 5032c698b2..00cf5eb278 100644
--- a/test/where7.test
+++ b/test/where7.test
@@ -47,6 +47,18 @@ do_test where7-1.1 {
SELECT * FROM t1;
}
} {1 2 3 4 2 3 4 5 3 4 6 8 4 5 10 15 5 10 100 1000}
+do_execsql_test where7-1.1.1 {
+ CREATE TABLE t(a);
+ CREATE INDEX ta ON t(a);
+ INSERT INTO t(a) VALUES(1),(2);
+ SELECT * FROM t ORDER BY a;
+ SELECT * FROM t WHERE a<2 OR a<3 ORDER BY a;
+ PRAGMA count_changes=ON;
+ DELETE FROM t WHERE a<2 OR a<3;
+ SELECT * FROM t;
+ PRAGMA count_changes=OFF;
+ DROP TABLE t;
+} {1 2 1 2 2}
do_test where7-1.2 {
count_steps {
SELECT a FROM t1 WHERE b=3 OR c=6 ORDER BY a
diff --git a/test/where8.test b/test/where8.test
index a155a95ab2..38214bc895 100644
--- a/test/where8.test
+++ b/test/where8.test
@@ -64,13 +64,21 @@ do_test where8-1.3 {
execsql_status2 { SELECT c FROM t1 WHERE a > 8 OR b = 'two' }
} {IX X II 0 0 6}
-do_test where8-1.4 {
- execsql_status2 { SELECT c FROM t1 WHERE a > 8 OR b GLOB 't*' }
-} {IX X III II 0 0 10}
-
-do_test where8-1.5 {
- execsql_status2 { SELECT c FROM t1 WHERE a > 8 OR b GLOB 'f*' }
-} {IX X V IV 0 0 10}
+ifcapable like_match_blobs {
+ do_test where8-1.4a {
+ execsql_status2 { SELECT c FROM t1 WHERE a > 8 OR b GLOB 't*' }
+ } {IX X III II 0 0 10}
+ do_test where8-1.5a {
+ execsql_status2 { SELECT c FROM t1 WHERE a > 8 OR b GLOB 'f*' }
+ } {IX X V IV 0 0 10}
+} else {
+ do_test where8-1.4b {
+ execsql_status2 { SELECT c FROM t1 WHERE a > 8 OR b GLOB 't*' }
+ } {IX X III II 0 0 9}
+ do_test where8-1.5 {
+ execsql_status2 { SELECT c FROM t1 WHERE a > 8 OR b GLOB 'f*' }
+ } {IX X V IV 0 0 9}
+}
do_test where8-1.6 {
execsql_status { SELECT c FROM t1 WHERE a = 1 OR b = 'three' ORDER BY rowid }
diff --git a/test/with1.test b/test/with1.test
index d98f33dfb0..7345c5ceb3 100644
--- a/test/with1.test
+++ b/test/with1.test
@@ -975,4 +975,20 @@ do_execsql_test 17.9 {
SELECT * FROM x4;
} {10 11}
+# Added to test a fix to a faulty assert() discovered by libFuzzer.
+#
+do_execsql_test 18.1 {
+ WITH xyz(x) AS (VALUES(NULL) UNION SELECT round(1 $TMPSPACE/tmp
+sed "s/--SQLITE-VERSION--/$VERSION/" > $TMPSPACE/tmp
mv $TMPSPACE/tmp $TMPSPACE/configure.ac
cd $TMPSPACE
-aclocal
-autoconf
-automake --add-missing
+autoreconf -i
+#libtoolize
+#aclocal
+#autoconf
+#automake --add-missing
mkdir -p tea/generic
echo "#ifdef USE_SYSTEM_SQLITE" > tea/generic/tclsqlite3.c
diff --git a/tool/mkpragmatab.tcl b/tool/mkpragmatab.tcl
index bcd3ed5d86..145a365c54 100644
--- a/tool/mkpragmatab.tcl
+++ b/tool/mkpragmatab.tcl
@@ -238,7 +238,7 @@ set pragma_def {
IF: !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
NAME: parser_trace
- IF: defined(SQLITE_DEBUG)
+ IF: defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_PARSER_TRACE)
NAME: case_sensitive_like
diff --git a/tool/showdb.c b/tool/showdb.c
index 38b92f1f7d..06cd36cd2c 100644
--- a/tool/showdb.c
+++ b/tool/showdb.c
@@ -1152,10 +1152,10 @@ int main(int argc, char **argv){
}else if( zLeft && zLeft[0]=='t' ){
int detail = 0;
int recursive = 0;
- int i;
- for(i=1; zLeft[i]; i++){
- if( zLeft[i]=='r' ) recursive = 1;
- if( zLeft[i]=='d' ) detail = 1;
+ int j;
+ for(j=1; zLeft[j]; j++){
+ if( zLeft[j]=='r' ) recursive = 1;
+ if( zLeft[j]=='d' ) detail = 1;
}
decode_trunk_page(iStart, detail, recursive);
continue;
diff --git a/tool/sqldiff.c b/tool/sqldiff.c
index 0f406d8a03..ae01cd3c4d 100644
--- a/tool/sqldiff.c
+++ b/tool/sqldiff.c
@@ -155,6 +155,7 @@ static char *safeId(const char *zId){
"WITH", "WITHOUT",
};
int lwr, upr, mid, c, i, x;
+ if( zId[0]==0 ) return sqlite3_mprintf("\"\"");
for(i=x=0; (c = zId[i])!=0; i++){
if( !isalpha(c) && c!='_' ){
if( i>0 && isdigit(c) ){
@@ -993,7 +994,7 @@ static int rbuDeltaCreate(
zDelta += lenOut;
putInt(checksum(zOut, lenOut), &zDelta);
*(zDelta++) = ';';
- return zDelta - zOrigDelta;
+ return (int)(zDelta - zOrigDelta);
}
/* Compute the hash table used to locate matching sections in the
@@ -1140,7 +1141,7 @@ static int rbuDeltaCreate(
putInt(checksum(zOut, lenOut), &zDelta);
*(zDelta++) = ';';
sqlite3_free(collide);
- return zDelta - zOrigDelta;
+ return (int)(zDelta - zOrigDelta);
}
/*