^sqlite3_prepare_v3() differs from sqlite3_prepare_v2() only in having
** the extra prepFlags parameter, which is a bit array consisting of zero or
** more of the [SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_*] flags. ^The
** sqlite3_prepare_v2() interface works exactly the same as
** sqlite3_prepare_v3() with a zero prepFlags parameter.
-**
*/
int sqlite3_prepare(
sqlite3 *db, /* Database handle */
@@ -4785,6 +4734,9 @@ SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),
** datatype of the value
**
sqlite3_value_nochange
+** | → | True if the column is unchanged in an UPDATE
+** against a virtual table.
**
**
** Details:
@@ -4833,6 +4785,19 @@ SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),
** then the conversion is performed. Otherwise no conversion occurs.
** The [SQLITE_INTEGER | datatype] after conversion is returned.)^
**
+** ^Within the [xUpdate] method of a [virtual table], the
+** sqlite3_value_nochange(X) interface returns true if and only if
+** the column corresponding to X is unchanged by the UPDATE operation
+** that the xUpdate method call was invoked to implement and if
+** and the prior [xColumn] method call that was invoked to extracted
+** the value for that column returned without setting a result (probably
+** because it queried [sqlite3_vtab_nochange()] and found that the column
+** was unchanging). ^Within an [xUpdate] method, any value for which
+** sqlite3_value_nochange(X) is true will in all other respects appear
+** to be a NULL value. If sqlite3_value_nochange(X) is invoked anywhere other
+** than within an [xUpdate] method call for an UPDATE statement, then
+** the return value is arbitrary and meaningless.
+**
** Please pay particular attention to the fact that the pointer returned
** from [sqlite3_value_blob()], [sqlite3_value_text()], or
** [sqlite3_value_text16()] can be invalidated by a subsequent call to
@@ -4855,6 +4820,7 @@ int sqlite3_value_bytes(sqlite3_value*);
int sqlite3_value_bytes16(sqlite3_value*);
int sqlite3_value_type(sqlite3_value*);
int sqlite3_value_numeric_type(sqlite3_value*);
+int sqlite3_value_nochange(sqlite3_value*);
/*
** CAPI3REF: Finding The Subtype Of SQL Values
@@ -6957,9 +6923,9 @@ sqlite3_mutex *sqlite3_db_mutex(sqlite3*);
** the xFileControl method. ^The return value of the xFileControl
** method becomes the return value of this routine.
**
-** ^The SQLITE_FCNTL_FILE_POINTER value for the op parameter causes
+** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes
** a pointer to the underlying [sqlite3_file] object to be written into
-** the space pointed to by the 4th parameter. ^The SQLITE_FCNTL_FILE_POINTER
+** the space pointed to by the 4th parameter. ^The [SQLITE_FCNTL_FILE_POINTER]
** case is a short-circuit path which does not actually invoke the
** underlying sqlite3_io_methods.xFileControl method.
**
@@ -6971,7 +6937,7 @@ sqlite3_mutex *sqlite3_db_mutex(sqlite3*);
** an incorrect zDbName and an SQLITE_ERROR return from the underlying
** xFileControl method.
**
-** See also: [SQLITE_FCNTL_LOCKSTATE]
+** See also: [file control opcodes]
*/
int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*);
@@ -7028,7 +6994,8 @@ int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_ISINIT 23
#define SQLITE_TESTCTRL_SORTER_MMAP 24
#define SQLITE_TESTCTRL_IMPOSTER 25
-#define SQLITE_TESTCTRL_LAST 25
+#define SQLITE_TESTCTRL_PARSER_COVERAGE 26
+#define SQLITE_TESTCTRL_LAST 26 /* Largest TESTCTRL */
/*
** CAPI3REF: SQLite Runtime Status
@@ -8282,6 +8249,40 @@ int sqlite3_vtab_config(sqlite3*, int op, ...);
*/
int sqlite3_vtab_on_conflict(sqlite3 *);
+/*
+** CAPI3REF: Determine If Virtual Table Column Access Is For UPDATE
+**
+** If the sqlite3_vtab_nochange(X) routine is called within the [xColumn]
+** method of a [virtual table], then it returns true if and only if the
+** column is being fetched as part of an UPDATE operation during which the
+** column value will not change. Applications might use this to substitute
+** a lighter-weight value to return that the corresponding [xUpdate] method
+** understands as a "no-change" value.
+**
+** If the [xColumn] method calls sqlite3_vtab_nochange() and finds that
+** the column is not changed by the UPDATE statement, they the xColumn
+** method can optionally return without setting a result, without calling
+** any of the [sqlite3_result_int|sqlite3_result_xxxxx() interfaces].
+** In that case, [sqlite3_value_nochange(X)] will return true for the
+** same column in the [xUpdate] method.
+*/
+int sqlite3_vtab_nochange(sqlite3_context*);
+
+/*
+** CAPI3REF: Determine The Collation For a Virtual Table Constraint
+**
+** This function may only be called from within a call to the [xBestIndex]
+** method of a [virtual table].
+**
+** The first argument must be the sqlite3_index_info object that is the
+** first parameter to the xBestIndex() method. The second argument must be
+** an index into the aConstraint[] array belonging to the sqlite3_index_info
+** structure passed to xBestIndex. This function returns a pointer to a buffer
+** containing the name of the collation sequence for the corresponding
+** constraint.
+*/
+SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
+
/*
** CAPI3REF: Conflict resolution modes
** KEYWORDS: {conflict resolution mode}
diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h
index d1d2c574ae..1409370a6f 100644
--- a/src/sqlite3ext.h
+++ b/src/sqlite3ext.h
@@ -292,6 +292,9 @@ struct sqlite3_api_routines {
int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
void *(*value_pointer)(sqlite3_value*,const char*);
+ int (*vtab_nochange)(sqlite3_context*);
+ int (*value_nochange)(sqlite3_value*);
+ const char *(*vtab_collation)(sqlite3_index_info*,int);
};
/*
@@ -558,6 +561,10 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_bind_pointer sqlite3_api->bind_pointer
#define sqlite3_result_pointer sqlite3_api->result_pointer
#define sqlite3_value_pointer sqlite3_api->value_pointer
+/* Version 3.22.0 and later */
+#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
+#define sqlite3_value_nochange sqlite3_api->value_nochange
+#define sqlite3_vtab_collation sqlite3_api->vtab_collation
#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 71d8783c8d..43883d3979 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -1063,7 +1063,6 @@ typedef struct Db Db;
typedef struct Schema Schema;
typedef struct Expr Expr;
typedef struct ExprList ExprList;
-typedef struct ExprSpan ExprSpan;
typedef struct FKey FKey;
typedef struct FuncDestructor FuncDestructor;
typedef struct FuncDef FuncDef;
@@ -1421,7 +1420,7 @@ struct sqlite3 {
Hash aModule; /* populated by sqlite3_create_module() */
VtabCtx *pVtabCtx; /* Context for active vtab connect/create */
VTable **aVTrans; /* Virtual tables with open transactions */
- VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */
+ VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */
#endif
Hash aFunc; /* Hash table of connection functions */
Hash aCollSeq; /* All collating sequences */
@@ -1496,7 +1495,9 @@ struct sqlite3 {
#define SQLITE_QueryOnly 0x00100000 /* Disable database changes */
#define SQLITE_CellSizeCk 0x00200000 /* Check btree cell sizes on load */
#define SQLITE_Fts3Tokenizer 0x00400000 /* Enable fts3_tokenizer(2) */
-#define SQLITE_EnableQPSG 0x00800000 /* Query Planner Stability Guarantee */
+#define SQLITE_EnableQPSG 0x00800000 /* Query Planner Stability Guarantee*/
+#define SQLITE_TriggerEQP 0x01000000 /* Show trigger EXPLAIN QUERY PLAN */
+
/* Flags used only if debugging */
#ifdef SQLITE_DEBUG
#define SQLITE_SqlTrace 0x08000000 /* Debug print SQL as it executes */
@@ -1629,6 +1630,7 @@ struct FuncDestructor {
#define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a
** single query - might change over time */
#define SQLITE_FUNC_AFFINITY 0x4000 /* Built-in affinity() function */
+#define SQLITE_FUNC_OFFSET 0x8000 /* Built-in sqlite_offset() function */
/*
** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are
@@ -1753,6 +1755,7 @@ struct Column {
#define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */
#define COLFLAG_HIDDEN 0x0002 /* A hidden column in a virtual table */
#define COLFLAG_HASTYPE 0x0004 /* Type name follows column name */
+#define COLFLAG_UNIQUE 0x0008 /* Column def contains "UNIQUE" or "PK" */
/*
** A "Collating Sequence" is defined by an instance of the following
@@ -2173,6 +2176,7 @@ struct Index {
unsigned isCovering:1; /* True if this is a covering index */
unsigned noSkipScan:1; /* Do not try to use skip-scan if true */
unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */
+ unsigned bNoQuery:1; /* Do not use this index to optimize queries */
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
int nSample; /* Number of elements in aSample[] */
int nSampleCol; /* Size of IndexSample.anEq[] and so on */
@@ -2502,17 +2506,6 @@ struct ExprList {
} a[1]; /* One slot for each expression in the list */
};
-/*
-** An instance of this structure is used by the parser to record both
-** the parse tree for an expression and the span of input text for an
-** expression.
-*/
-struct ExprSpan {
- Expr *pExpr; /* The expression parse tree */
- const char *zStart; /* First character of input text */
- const char *zEnd; /* One character past the end of input text */
-};
-
/*
** An instance of this structure can hold a simple list of identifiers,
** such as the list "a,b,c" in the following statements:
@@ -2750,7 +2743,6 @@ struct Select {
Select *pPrior; /* Prior select in a compound select statement */
Select *pNext; /* Next select to the left in a compound */
Expr *pLimit; /* LIMIT expression. NULL means not used. */
- Expr *pOffset; /* OFFSET expression. NULL means not used. */
With *pWith; /* WITH clause attached to this select. Or NULL. */
};
@@ -2986,7 +2978,7 @@ struct Parse {
int nMem; /* Number of memory cells used so far */
int nOpAlloc; /* Number of slots allocated for Vdbe.aOp[] */
int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */
- int iSelfTab; /* Table for associated with an index on expr, or negative
+ int iSelfTab; /* Table associated with an index on expr, or negative
** of the base register during check-constraint eval */
int iCacheLevel; /* ColCache valid when aColCache[].iLevel<=iCacheLevel */
int iCacheCnt; /* Counter used to generate aColCache[].lru values */
@@ -3127,6 +3119,7 @@ struct AuthContext {
#define OPFLAG_PERMUTE 0x01 /* OP_Compare: use the permutation */
#define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete/Insert: save cursor pos */
#define OPFLAG_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */
+#define OPFLAG_NOCHNG_MAGIC 0x6d /* OP_MakeRecord: serialtype 10 is ok */
/*
* Each trigger present in the database schema is stored as an instance of
@@ -3214,6 +3207,7 @@ struct TriggerStep {
Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */
ExprList *pExprList; /* SET clause for UPDATE. */
IdList *pIdList; /* Column names for INSERT */
+ char *zSpan; /* Original SQL text of this command */
TriggerStep *pNext; /* Next in the link-list */
TriggerStep *pLast; /* Last element in link-list. Valid for 1st elem only */
};
@@ -3433,6 +3427,7 @@ struct TreeView {
** using sqlite3_log(). The routines also provide a convenient place
** to set a debugger breakpoint.
*/
+int sqlite3ReportError(int iErr, int lineno, const char *zType);
int sqlite3CorruptError(int);
int sqlite3MisuseError(int);
int sqlite3CantopenError(int);
@@ -3523,6 +3518,7 @@ void *sqlite3DbMallocRaw(sqlite3*, u64);
void *sqlite3DbMallocRawNN(sqlite3*, u64);
char *sqlite3DbStrDup(sqlite3*,const char*);
char *sqlite3DbStrNDup(sqlite3*,const char*, u64);
+char *sqlite3DbSpanDup(sqlite3*,const char*,const char*);
void *sqlite3Realloc(void*, u64);
void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64);
void *sqlite3DbRealloc(sqlite3 *, void *, u64);
@@ -3591,6 +3587,12 @@ int sqlite3LookasideUsed(sqlite3*,int*);
sqlite3_mutex *sqlite3Pcache1Mutex(void);
sqlite3_mutex *sqlite3MallocMutex(void);
+#if defined(SQLITE_ENABLE_MULTITHREADED_CHECKS) && !defined(SQLITE_MUTEX_OMIT)
+void sqlite3MutexWarnOnContention(sqlite3_mutex*);
+#else
+# define sqlite3MutexWarnOnContention(x)
+#endif
+
#ifndef SQLITE_OMIT_FLOATING_POINT
int sqlite3IsNaN(double);
#else
@@ -3655,7 +3657,7 @@ ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*);
ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*);
void sqlite3ExprListSetSortOrder(ExprList*,int);
void sqlite3ExprListSetName(Parse*,ExprList*,Token*,int);
-void sqlite3ExprListSetSpan(Parse*,ExprList*,ExprSpan*);
+void sqlite3ExprListSetSpan(Parse*,ExprList*,const char*,const char*);
void sqlite3ExprListDelete(sqlite3*, ExprList*);
u32 sqlite3ExprListFlags(const ExprList*);
int sqlite3Init(sqlite3*, char**);
@@ -3685,7 +3687,7 @@ void sqlite3AddColumn(Parse*,Token*,Token*);
void sqlite3AddNotNull(Parse*, int);
void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int);
void sqlite3AddCheckConstraint(Parse*, Expr*);
-void sqlite3AddDefaultValue(Parse*,ExprSpan*);
+void sqlite3AddDefaultValue(Parse*,Expr*,const char*,const char*);
void sqlite3AddCollateType(Parse*, Token*);
void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*);
int sqlite3ParseUri(const char*,const char*,unsigned int*,
@@ -3757,16 +3759,16 @@ void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*,
void sqlite3DropIndex(Parse*, SrcList*, int);
int sqlite3Select(Parse*, Select*, SelectDest*);
Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*,
- Expr*,ExprList*,u32,Expr*,Expr*);
+ Expr*,ExprList*,u32,Expr*);
void sqlite3SelectDelete(sqlite3*, Select*);
Table *sqlite3SrcListLookup(Parse*, SrcList*);
int sqlite3IsReadOnly(Parse*, Table*, int);
void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int);
#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY)
-Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,Expr*,char*);
+Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,char*);
#endif
-void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*, Expr*);
-void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*,Expr*);
+void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*);
+void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*);
WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,u16,int);
void sqlite3WhereEnd(WhereInfo*);
LogEst sqlite3WhereOutputRowCount(WhereInfo*);
@@ -3838,6 +3840,8 @@ void sqlite3EndTransaction(Parse*,int);
void sqlite3Savepoint(Parse*, int, Token*);
void sqlite3CloseSavepoints(sqlite3 *);
void sqlite3LeaveMutexAndCloseZombie(sqlite3*);
+int sqlite3ExprIdToTrueFalse(Expr*);
+int sqlite3ExprTruthValue(const Expr*);
int sqlite3ExprIsConstant(Expr*);
int sqlite3ExprIsConstantNotJoin(Expr*);
int sqlite3ExprIsConstantOrFunction(Expr*, u8);
@@ -3890,7 +3894,7 @@ int sqlite3SafetyCheckSickOrOk(sqlite3*);
void sqlite3ChangeCookie(Parse*, int);
#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,Expr*,int);
+void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int);
#endif
#ifndef SQLITE_OMIT_TRIGGER
@@ -3906,11 +3910,14 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,Expr*,int);
void sqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, int, int, int);
void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*);
void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*);
- TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*);
+ TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*,
+ const char*,const char*);
TriggerStep *sqlite3TriggerInsertStep(sqlite3*,Token*, IdList*,
- Select*,u8);
- TriggerStep *sqlite3TriggerUpdateStep(sqlite3*,Token*,ExprList*, Expr*, u8);
- TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*);
+ Select*,u8,const char*,const char*);
+ TriggerStep *sqlite3TriggerUpdateStep(sqlite3*,Token*,ExprList*, Expr*, u8,
+ const char*,const char*);
+ TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*,
+ const char*,const char*);
void sqlite3DeleteTrigger(sqlite3*, Trigger*);
void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*);
u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int);
@@ -4065,6 +4072,9 @@ extern FuncDefHash sqlite3BuiltinFunctions;
extern int sqlite3PendingByte;
#endif
#endif
+#ifdef VDBE_PROFILE
+extern sqlite3_uint64 sqlite3NProfileCnt;
+#endif
void sqlite3RootPageMoved(sqlite3*, int, int, int);
void sqlite3Reindex(Parse*, Token*, Token*);
void sqlite3AlterFunctions(void);
@@ -4340,6 +4350,9 @@ void sqlite3Put4byte(u8*, u32);
#ifdef SQLITE_DEBUG
void sqlite3ParserTrace(FILE*, char *);
#endif
+#if defined(YYCOVERAGE)
+ int sqlite3ParserCoverage(FILE*);
+#endif
/*
** If the SQLITE_ENABLE IOTRACE exists then the global variable
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index eed86eee34..252b246e6e 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -64,7 +64,9 @@
# define GETPID getpid
#elif !defined(_WIN32_WCE)
# ifndef SQLITE_AMALGAMATION
-# define WIN32_LEAN_AND_MEAN
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
# include
# endif
# define GETPID (int)GetCurrentProcessId
diff --git a/src/test1.c b/src/test1.c
index 26d8439ea8..d24fd8b679 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -4559,6 +4559,35 @@ static int SQLITE_TCLAPI test_complete16(
return TCL_OK;
}
+/*
+** Usage: sqlite3_normalize SQL
+**
+** Return the normalized value for an SQL statement.
+*/
+static int SQLITE_TCLAPI test_normalize(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ char *zSql;
+ char *zNorm;
+ extern char *sqlite3_normalize(const char*);
+
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SQL");
+ return TCL_ERROR;
+ }
+
+ zSql = (char*)Tcl_GetString(objv[1]);
+ zNorm = sqlite3_normalize(zSql);
+ if( zNorm ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(zNorm, -1));
+ sqlite3_free(zNorm);
+ }
+ return TCL_OK;
+}
+
/*
** Usage: sqlite3_step STMT
**
@@ -6960,6 +6989,9 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd(
extern int sqlite3_totype_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_wholenumber_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_unionvtab_init(sqlite3*,char**,const sqlite3_api_routines*);
+#ifdef SQLITE_HAVE_ZLIB
+ extern int sqlite3_zipfile_init(sqlite3*,char**,const sqlite3_api_routines*);
+#endif
static const struct {
const char *zExtName;
int (*pInit)(sqlite3*,char**,const sqlite3_api_routines*);
@@ -6981,6 +7013,9 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd(
{ "totype", sqlite3_totype_init },
{ "unionvtab", sqlite3_unionvtab_init },
{ "wholenumber", sqlite3_wholenumber_init },
+#ifdef SQLITE_HAVE_ZLIB
+ { "zipfile", sqlite3_zipfile_init },
+#endif
};
sqlite3 *db;
const char *zName;
@@ -7576,6 +7611,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "sqlite3_open16", test_open16 ,0 },
{ "sqlite3_open_v2", test_open_v2 ,0 },
{ "sqlite3_complete16", test_complete16 ,0 },
+ { "sqlite3_normalize", test_normalize ,0 },
{ "sqlite3_prepare", test_prepare ,0 },
{ "sqlite3_prepare16", test_prepare16 ,0 },
diff --git a/src/test_config.c b/src/test_config.c
index 2083ebb381..c9f6e6941e 100644
--- a/src/test_config.c
+++ b/src/test_config.c
@@ -160,6 +160,12 @@ static void set_options(Tcl_Interp *interp){
Tcl_SetVar2(interp, "sqlite_options", "mem5", "0", TCL_GLOBAL_ONLY);
#endif
+#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
+ Tcl_SetVar2(interp, "sqlite_options", "offset_sql_func","1",TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar2(interp, "sqlite_options", "offset_sql_func","0",TCL_GLOBAL_ONLY);
+#endif
+
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
Tcl_SetVar2(interp, "sqlite_options", "preupdate", "1", TCL_GLOBAL_ONLY);
#else
@@ -429,6 +435,12 @@ static void set_options(Tcl_Interp *interp){
Tcl_SetVar2(interp, "sqlite_options", "icu", "0", TCL_GLOBAL_ONLY);
#endif
+#ifdef SQLITE_ENABLE_ICU_COLLATIONS
+ Tcl_SetVar2(interp, "sqlite_options", "icu_collations", "1", TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar2(interp, "sqlite_options", "icu_collations", "0", TCL_GLOBAL_ONLY);
+#endif
+
#ifdef SQLITE_OMIT_INCRBLOB
Tcl_SetVar2(interp, "sqlite_options", "incrblob", "0", TCL_GLOBAL_ONLY);
#else
@@ -489,6 +501,12 @@ Tcl_SetVar2(interp, "sqlite_options", "long_double",
Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY);
+#ifdef SQLITE_ENABLE_NULL_TRIM
+ Tcl_SetVar2(interp, "sqlite_options", "null_trim", "1", TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar2(interp, "sqlite_options", "null_trim", "0", TCL_GLOBAL_ONLY);
+#endif
+
#ifdef SQLITE_OMIT_OR_OPTIMIZATION
Tcl_SetVar2(interp, "sqlite_options", "or_opt", "0", TCL_GLOBAL_ONLY);
#else
@@ -702,6 +720,12 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY);
Tcl_SetVar2(interp, "sqlite_options", "unlock_notify", "0", TCL_GLOBAL_ONLY);
#endif
+#ifdef SQLITE_FAST_SECURE_DELETE
+ Tcl_SetVar2(interp, "sqlite_options", "fast_secure_delete", "1", TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar2(interp, "sqlite_options", "fast_secure_delete", "0", TCL_GLOBAL_ONLY);
+#endif
+
#ifdef SQLITE_SECURE_DELETE
Tcl_SetVar2(interp, "sqlite_options", "secure_delete", "1", TCL_GLOBAL_ONLY);
#else
diff --git a/src/test_tclsh.c b/src/test_tclsh.c
index 976f7cb248..97f7f5d7a1 100644
--- a/src/test_tclsh.c
+++ b/src/test_tclsh.c
@@ -104,6 +104,8 @@ const char *sqlite3TestInit(Tcl_Interp *interp){
#ifdef SQLITE_ENABLE_ZIPVFS
extern int Zipvfs_Init(Tcl_Interp*);
#endif
+ extern int TestExpert_Init(Tcl_Interp*);
+
Tcl_CmdInfo cmdInfo;
/* Since the primary use case for this binary is testing of SQLite,
@@ -166,6 +168,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){
#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
Sqlitetestfts3_Init(interp);
#endif
+ TestExpert_Init(interp);
Tcl_CreateObjCommand(
interp, "load_testfixture_extensions", load_testfixture_extensions,0,0
diff --git a/src/test_windirent.c b/src/test_windirent.c
index ca78d345d9..62165c4bea 100644
--- a/src/test_windirent.c
+++ b/src/test_windirent.c
@@ -14,7 +14,6 @@
*/
#if defined(_WIN32) && defined(_MSC_VER)
-
#include "test_windirent.h"
/*
diff --git a/src/test_windirent.h b/src/test_windirent.h
index 578e2a7c22..ada5322530 100644
--- a/src/test_windirent.h
+++ b/src/test_windirent.h
@@ -13,13 +13,17 @@
** POSIX functions on Win32 using the MSVCRT.
*/
-#if defined(_WIN32) && defined(_MSC_VER)
+#if defined(_WIN32) && defined(_MSC_VER) && !defined(SQLITE_WINDIRENT_H)
+#define SQLITE_WINDIRENT_H
/*
** We need several data types from the Windows SDK header.
*/
+#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
+#endif
+
#include "windows.h"
/*
@@ -37,6 +41,33 @@
#include
#include
#include
+#include
+#include
+
+/*
+** We may need several defines that should have been in "sys/stat.h".
+*/
+
+#ifndef S_ISREG
+#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
+#endif
+
+#ifndef S_ISDIR
+#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
+#endif
+
+#ifndef S_ISLNK
+#define S_ISLNK(mode) (0)
+#endif
+
+/*
+** We may need to provide the "mode_t" type.
+*/
+
+#ifndef MODE_T_DEFINED
+ #define MODE_T_DEFINED
+ typedef unsigned short mode_t;
+#endif
/*
** We may need to provide the "ino_t" type.
@@ -75,22 +106,27 @@
** We need to provide the necessary structures and related types.
*/
+#ifndef DIRENT_DEFINED
+#define DIRENT_DEFINED
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. */
};
+#endif
+#ifndef DIR_DEFINED
+#define DIR_DEFINED
+typedef struct DIR DIR;
+typedef DIR *LPDIR;
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". */
};
+#endif
/*
** Provide a macro, for use by the implementation, to determine if a
diff --git a/src/tokenize.c b/src/tokenize.c
index 2aab334ae9..e6da3fb547 100644
--- a/src/tokenize.c
+++ b/src/tokenize.c
@@ -526,7 +526,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){
}else{
tokenType = TK_SEMI;
}
- zSql -= n;
+ n = 0;
}
if( tokenType>=TK_SPACE ){
assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL );
diff --git a/src/treeview.c b/src/treeview.c
index ba9fa7b2f0..e33fe6e227 100644
--- a/src/treeview.c
+++ b/src/treeview.c
@@ -153,7 +153,6 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){
if( p->pHaving ) n++;
if( p->pOrderBy ) n++;
if( p->pLimit ) n++;
- if( p->pOffset ) n++;
}
sqlite3TreeViewExprList(pView, p->pEList, (n--)>0, "result-set");
if( p->pSrc && p->pSrc->nSrc ){
@@ -210,12 +209,12 @@ void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){
}
if( p->pLimit ){
sqlite3TreeViewItem(pView, "LIMIT", (n--)>0);
- sqlite3TreeViewExpr(pView, p->pLimit, 0);
- sqlite3TreeViewPop(pView);
- }
- if( p->pOffset ){
- sqlite3TreeViewItem(pView, "OFFSET", (n--)>0);
- sqlite3TreeViewExpr(pView, p->pOffset, 0);
+ sqlite3TreeViewExpr(pView, p->pLimit->pLeft, p->pLimit->pRight!=0);
+ if( p->pLimit->pRight ){
+ sqlite3TreeViewItem(pView, "OFFSET", (n--)>0);
+ sqlite3TreeViewExpr(pView, p->pLimit->pRight, 0);
+ sqlite3TreeViewPop(pView);
+ }
sqlite3TreeViewPop(pView);
}
if( p->pPrior ){
@@ -293,6 +292,11 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
sqlite3TreeViewLine(pView,"NULL");
break;
}
+ case TK_TRUEFALSE: {
+ sqlite3TreeViewLine(pView,
+ sqlite3ExprTruthValue(pExpr) ? "TRUE" : "FALSE");
+ break;
+ }
#ifndef SQLITE_OMIT_BLOB_LITERAL
case TK_BLOB: {
sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken);
@@ -349,6 +353,19 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
case TK_ISNULL: zUniOp = "ISNULL"; break;
case TK_NOTNULL: zUniOp = "NOTNULL"; break;
+ case TK_TRUTH: {
+ int x;
+ const char *azOp[] = {
+ "IS-FALSE", "IS-TRUE", "IS-NOT-FALSE", "IS-NOT-TRUE"
+ };
+ assert( pExpr->op2==TK_IS || pExpr->op2==TK_ISNOT );
+ assert( pExpr->pRight );
+ assert( pExpr->pRight->op==TK_TRUEFALSE );
+ x = (pExpr->op2==TK_ISNOT)*2 + sqlite3ExprTruthValue(pExpr->pRight);
+ zUniOp = azOp[x];
+ break;
+ }
+
case TK_SPAN: {
sqlite3TreeViewLine(pView, "SPAN %Q", pExpr->u.zToken);
sqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
@@ -508,12 +525,20 @@ void sqlite3TreeViewBareExprList(
sqlite3TreeViewLine(pView, "%s", zLabel);
for(i=0; inExpr; i++){
int j = pList->a[i].u.x.iOrderByCol;
- if( j ){
+ char *zName = pList->a[i].zName;
+ if( j || zName ){
sqlite3TreeViewPush(pView, 0);
+ }
+ if( zName ){
+ sqlite3TreeViewLine(pView, "AS %s", zName);
+ }
+ if( j ){
sqlite3TreeViewLine(pView, "iOrderByCol=%d", j);
}
sqlite3TreeViewExpr(pView, pList->a[i].pExpr, inExpr-1);
- if( j ) sqlite3TreeViewPop(pView);
+ if( j || zName ){
+ sqlite3TreeViewPop(pView);
+ }
}
}
}
diff --git a/src/trigger.c b/src/trigger.c
index a17769ae98..9f7bff5052 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -25,6 +25,7 @@ void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerStep){
sqlite3ExprListDelete(db, pTmp->pExprList);
sqlite3SelectDelete(db, pTmp->pSelect);
sqlite3IdListDelete(db, pTmp->pIdList);
+ sqlite3DbFree(db, pTmp->zSpan);
sqlite3DbFree(db, pTmp);
}
@@ -339,6 +340,17 @@ triggerfinish_cleanup:
sqlite3DeleteTriggerStep(db, pStepList);
}
+/*
+** Duplicate a range of text from an SQL statement, then convert all
+** whitespace characters into ordinary space characters.
+*/
+static char *triggerSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){
+ char *z = sqlite3DbSpanDup(db, zStart, zEnd);
+ int i;
+ if( z ) for(i=0; z[i]; i++) if( sqlite3Isspace(z[i]) ) z[i] = ' ';
+ return z;
+}
+
/*
** Turn a SELECT statement (that the pSelect parameter points to) into
** a trigger step. Return a pointer to a TriggerStep structure.
@@ -346,7 +358,12 @@ triggerfinish_cleanup:
** The parser calls this routine when it finds a SELECT statement in
** body of a TRIGGER.
*/
-TriggerStep *sqlite3TriggerSelectStep(sqlite3 *db, Select *pSelect){
+TriggerStep *sqlite3TriggerSelectStep(
+ sqlite3 *db, /* Database connection */
+ Select *pSelect, /* The SELECT statement */
+ const char *zStart, /* Start of SQL text */
+ const char *zEnd /* End of SQL text */
+){
TriggerStep *pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep));
if( pTriggerStep==0 ) {
sqlite3SelectDelete(db, pSelect);
@@ -355,6 +372,7 @@ TriggerStep *sqlite3TriggerSelectStep(sqlite3 *db, Select *pSelect){
pTriggerStep->op = TK_SELECT;
pTriggerStep->pSelect = pSelect;
pTriggerStep->orconf = OE_Default;
+ pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd);
return pTriggerStep;
}
@@ -367,7 +385,9 @@ TriggerStep *sqlite3TriggerSelectStep(sqlite3 *db, Select *pSelect){
static TriggerStep *triggerStepAllocate(
sqlite3 *db, /* Database connection */
u8 op, /* Trigger opcode */
- Token *pName /* The target name */
+ Token *pName, /* The target name */
+ const char *zStart, /* Start of SQL text */
+ const char *zEnd /* End of SQL text */
){
TriggerStep *pTriggerStep;
@@ -378,6 +398,7 @@ static TriggerStep *triggerStepAllocate(
sqlite3Dequote(z);
pTriggerStep->zTarget = z;
pTriggerStep->op = op;
+ pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd);
}
return pTriggerStep;
}
@@ -394,13 +415,15 @@ TriggerStep *sqlite3TriggerInsertStep(
Token *pTableName, /* Name of the table into which we insert */
IdList *pColumn, /* List of columns in pTableName to insert into */
Select *pSelect, /* A SELECT statement that supplies values */
- u8 orconf /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */
+ u8 orconf, /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */
+ const char *zStart, /* Start of SQL text */
+ const char *zEnd /* End of SQL text */
){
TriggerStep *pTriggerStep;
assert(pSelect != 0 || db->mallocFailed);
- pTriggerStep = triggerStepAllocate(db, TK_INSERT, pTableName);
+ pTriggerStep = triggerStepAllocate(db, TK_INSERT, pTableName, zStart, zEnd);
if( pTriggerStep ){
pTriggerStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE);
pTriggerStep->pIdList = pColumn;
@@ -423,11 +446,13 @@ TriggerStep *sqlite3TriggerUpdateStep(
Token *pTableName, /* Name of the table to be updated */
ExprList *pEList, /* The SET clause: list of column and new values */
Expr *pWhere, /* The WHERE clause */
- u8 orconf /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */
+ u8 orconf, /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */
+ const char *zStart, /* Start of SQL text */
+ const char *zEnd /* End of SQL text */
){
TriggerStep *pTriggerStep;
- pTriggerStep = triggerStepAllocate(db, TK_UPDATE, pTableName);
+ pTriggerStep = triggerStepAllocate(db, TK_UPDATE, pTableName, zStart, zEnd);
if( pTriggerStep ){
pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE);
pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE);
@@ -446,11 +471,13 @@ TriggerStep *sqlite3TriggerUpdateStep(
TriggerStep *sqlite3TriggerDeleteStep(
sqlite3 *db, /* Database connection */
Token *pTableName, /* The table from which rows are deleted */
- Expr *pWhere /* The WHERE clause */
+ Expr *pWhere, /* The WHERE clause */
+ const char *zStart, /* Start of SQL text */
+ const char *zEnd /* End of SQL text */
){
TriggerStep *pTriggerStep;
- pTriggerStep = triggerStepAllocate(db, TK_DELETE, pTableName);
+ pTriggerStep = triggerStepAllocate(db, TK_DELETE, pTableName, zStart, zEnd);
if( pTriggerStep ){
pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE);
pTriggerStep->orconf = OE_Default;
@@ -705,13 +732,21 @@ static int codeTriggerProgram(
pParse->eOrconf = (orconf==OE_Default)?pStep->orconf:(u8)orconf;
assert( pParse->okConstFactor==0 );
+#ifndef SQLITE_OMIT_TRACE
+ if( pStep->zSpan ){
+ sqlite3VdbeAddOp4(v, OP_Trace, 0x7fffffff, 1, 0,
+ sqlite3MPrintf(db, "-- %s", pStep->zSpan),
+ P4_DYNAMIC);
+ }
+#endif
+
switch( pStep->op ){
case TK_UPDATE: {
sqlite3Update(pParse,
targetSrcList(pParse, pStep),
sqlite3ExprListDup(db, pStep->pExprList, 0),
sqlite3ExprDup(db, pStep->pWhere, 0),
- pParse->eOrconf, 0, 0, 0
+ pParse->eOrconf, 0, 0
);
break;
}
@@ -727,7 +762,7 @@ static int codeTriggerProgram(
case TK_DELETE: {
sqlite3DeleteFrom(pParse,
targetSrcList(pParse, pStep),
- sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0, 0
+ sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0
);
break;
}
@@ -845,9 +880,11 @@ static TriggerPrg *codeRowTrigger(
pTab->zName
));
#ifndef SQLITE_OMIT_TRACE
- sqlite3VdbeChangeP4(v, -1,
- sqlite3MPrintf(db, "-- TRIGGER %s", pTrigger->zName), P4_DYNAMIC
- );
+ if( pTrigger->zName ){
+ sqlite3VdbeChangeP4(v, -1,
+ sqlite3MPrintf(db, "-- TRIGGER %s", pTrigger->zName), P4_DYNAMIC
+ );
+ }
#endif
/* If one was specified, code the WHEN clause. If it evaluates to false
@@ -875,7 +912,7 @@ static TriggerPrg *codeRowTrigger(
VdbeComment((v, "End: %s.%s", pTrigger->zName, onErrorText(orconf)));
transferParseError(pParse, pSubParse);
- if( db->mallocFailed==0 ){
+ if( db->mallocFailed==0 && pParse->nErr==0 ){
pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg);
}
pProgram->nMem = pSubParse->nMem;
diff --git a/src/update.c b/src/update.c
index 4cfa4019eb..32c1a8371a 100644
--- a/src/update.c
+++ b/src/update.c
@@ -93,8 +93,7 @@ void sqlite3Update(
Expr *pWhere, /* The WHERE clause. May be null */
int onError, /* How to handle constraint errors */
ExprList *pOrderBy, /* ORDER BY clause. May be null */
- Expr *pLimit, /* LIMIT clause. May be null */
- Expr *pOffset /* OFFSET clause. May be null */
+ Expr *pLimit /* LIMIT clause. May be null */
){
int i, j; /* Loop counters */
Table *pTab; /* The table to be updated */
@@ -182,10 +181,10 @@ void sqlite3Update(
#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
if( !isView ){
pWhere = sqlite3LimitWhere(
- pParse, pTabList, pWhere, pOrderBy, pLimit, pOffset, "UPDATE"
+ pParse, pTabList, pWhere, pOrderBy, pLimit, "UPDATE"
);
pOrderBy = 0;
- pLimit = pOffset = 0;
+ pLimit = 0;
}
#endif
@@ -358,10 +357,10 @@ void sqlite3Update(
#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
if( isView ){
sqlite3MaterializeView(pParse, pTab,
- pWhere, pOrderBy, pLimit, pOffset, iDataCur
+ pWhere, pOrderBy, pLimit, iDataCur
);
pOrderBy = 0;
- pLimit = pOffset = 0;
+ pLimit = 0;
}
#endif
@@ -748,7 +747,6 @@ update_cleanup:
#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
sqlite3ExprListDelete(db, pOrderBy);
sqlite3ExprDelete(db, pLimit);
- sqlite3ExprDelete(db, pOffset);
#endif
return;
}
@@ -809,7 +807,7 @@ static void updateVirtualTable(
int bOnePass; /* True to use onepass strategy */
int addr; /* Address of OP_OpenEphemeral */
- /* Allocate nArg registers to martial the arguments to VUpdate. Then
+ /* Allocate nArg registers in which to gather the arguments for VUpdate. Then
** create and open the ephemeral table in which the records created from
** these arguments will be temporarily stored. */
assert( v );
@@ -830,6 +828,7 @@ static void updateVirtualTable(
sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i);
}else{
sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i);
+ sqlite3VdbeChangeP5(v, 1); /* Enable sqlite3_vtab_nochange() */
}
}
if( HasRowid(pTab) ){
@@ -864,6 +863,11 @@ static void updateVirtualTable(
/* Create a record from the argument register contents and insert it into
** the ephemeral table. */
sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec);
+#ifdef SQLITE_DEBUG
+ /* Signal an assert() within OP_MakeRecord that it is allowed to
+ ** accept no-change records with serial_type 10 */
+ sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC);
+#endif
sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid);
sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid);
}
diff --git a/src/util.c b/src/util.c
index a4dbe8fdaf..54f9b93887 100644
--- a/src/util.c
+++ b/src/util.c
@@ -320,6 +320,45 @@ int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){
return N<0 ? 0 : UpperToLower[*a] - UpperToLower[*b];
}
+/*
+** Compute 10 to the E-th power. Examples: E==1 results in 10.
+** E==2 results in 100. E==50 results in 1.0e50.
+**
+** This routine only works for values of E between 1 and 341.
+*/
+static LONGDOUBLE_TYPE sqlite3Pow10(int E){
+#if defined(_MSC_VER)
+ static const LONGDOUBLE_TYPE x[] = {
+ 1.0e+001,
+ 1.0e+002,
+ 1.0e+004,
+ 1.0e+008,
+ 1.0e+016,
+ 1.0e+032,
+ 1.0e+064,
+ 1.0e+128,
+ 1.0e+256
+ };
+ LONGDOUBLE_TYPE r = 1.0;
+ int i;
+ assert( E>=0 && E<=307 );
+ for(i=0; E!=0; i++, E >>=1){
+ if( E & 1 ) r *= x[i];
+ }
+ return r;
+#else
+ LONGDOUBLE_TYPE x = 10.0;
+ LONGDOUBLE_TYPE r = 1.0;
+ while(1){
+ if( E & 1 ) r *= x;
+ E >>= 1;
+ if( E==0 ) break;
+ x *= x;
+ }
+ return r;
+#endif
+}
+
/*
** The string z[] is an text representation of a real number.
** Convert this string to a double and write it into *pResult.
@@ -475,11 +514,10 @@ do_atof_calc:
if( e==0 ){ /*OPTIMIZATION-IF-TRUE*/
result = (double)s;
}else{
- LONGDOUBLE_TYPE scale = 1.0;
/* attempt to handle extremely small/large numbers better */
if( e>307 ){ /*OPTIMIZATION-IF-TRUE*/
if( e<342 ){ /*OPTIMIZATION-IF-TRUE*/
- while( e%308 ) { scale *= 1.0e+1; e -= 1; }
+ LONGDOUBLE_TYPE scale = sqlite3Pow10(e-308);
if( esign<0 ){
result = s / scale;
result /= 1.0e+308;
@@ -499,10 +537,7 @@ do_atof_calc:
}
}
}else{
- /* 1.0e+22 is the largest power of 10 than can be
- ** represented exactly. */
- while( e%22 ) { scale *= 1.0e+1; e -= 1; }
- while( e>0 ) { scale *= 1.0e+22; e -= 22; }
+ LONGDOUBLE_TYPE scale = sqlite3Pow10(e);
if( esign<0 ){
result = s / scale;
}else{
@@ -560,7 +595,7 @@ static int compare2pow63(const char *zNum, int incr){
** Returns:
**
** 0 Successful transformation. Fits in a 64-bit signed integer.
-** 1 Excess text after the integer value
+** 1 Excess non-space text after the integer value
** 2 Integer too large for a 64-bit signed integer or is malformed
** 3 Special case of 9223372036854775808
**
@@ -603,47 +638,57 @@ int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc){
for(i=0; &zNum[i]='0' && c<='9'; i+=incr){
u = u*10 + c - '0';
}
+ testcase( i==18*incr );
+ testcase( i==19*incr );
+ testcase( i==20*incr );
if( u>LARGEST_INT64 ){
+ /* This test and assignment is needed only to suppress UB warnings
+ ** from clang and -fsanitize=undefined. This test and assignment make
+ ** the code a little larger and slower, and no harm comes from omitting
+ ** them, but we must appaise the undefined-behavior pharisees. */
*pNum = neg ? SMALLEST_INT64 : LARGEST_INT64;
}else if( neg ){
*pNum = -(i64)u;
}else{
*pNum = (i64)u;
}
- testcase( i==18 );
- testcase( i==19 );
- testcase( i==20 );
- if( &zNum[i]19*incr ){ /* Too many digits */
- /* zNum is empty or contains non-numeric text or is longer
- ** than 19 digits (thus guaranteeing that it is too large) */
- return 2;
- }else if( i<19*incr ){
+ if( i<19*incr ){
/* Less than 19 digits, so we know that it fits in 64 bits */
assert( u<=LARGEST_INT64 );
return rc;
}else{
/* zNum is a 19-digit numbers. Compare it against 9223372036854775808. */
- c = compare2pow63(zNum, incr);
+ c = i>19*incr ? 1 : compare2pow63(zNum, incr);
if( c<0 ){
/* zNum is less than 9223372036854775808 so it fits */
assert( u<=LARGEST_INT64 );
return rc;
- }else if( c>0 ){
- /* zNum is greater than 9223372036854775808 so it overflows */
- return 2;
}else{
- /* zNum is exactly 9223372036854775808. Fits if negative. The
- ** special case 2 overflow if positive */
- assert( u-1==LARGEST_INT64 );
- return neg ? rc : 3;
+ *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64;
+ if( c>0 ){
+ /* zNum is greater than 9223372036854775808 so it overflows */
+ return 2;
+ }else{
+ /* zNum is exactly 9223372036854775808. Fits if negative. The
+ ** special case 2 overflow if positive */
+ assert( u-1==LARGEST_INT64 );
+ return neg ? rc : 3;
+ }
}
}
}
diff --git a/src/vacuum.c b/src/vacuum.c
index a94328e1a9..4219e010ed 100644
--- a/src/vacuum.c
+++ b/src/vacuum.c
@@ -39,8 +39,8 @@ static int execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){
while( SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){
const char *zSubSql = (const char*)sqlite3_column_text(pStmt,0);
assert( sqlite3_strnicmp(zSql,"SELECT",6)==0 );
- if( zSubSql ){
- assert( zSubSql[0]!='S' );
+ assert( sqlite3_strnicmp(zSubSql,"SELECT",6)!=0 || CORRUPT_DB );
+ if( zSubSql && zSubSql[0]!='S' ){
rc = execSql(db, pzErrMsg, zSubSql);
if( rc!=SQLITE_OK ) break;
}
diff --git a/src/vdbe.c b/src/vdbe.c
index f250661aea..cb12332df8 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -264,6 +264,11 @@ static void applyNumericAffinity(Mem *pRec, int bTryForInt){
pRec->flags |= MEM_Real;
if( bTryForInt ) sqlite3VdbeIntegerAffinity(pRec);
}
+ /* TEXT->NUMERIC is many->one. Hence, it is important to invalidate the
+ ** string representation after computing a numeric equivalent, because the
+ ** string representation might not be the canonical representation for the
+ ** numeric value. Ticket [343634942dd54ab57b7024] 2018-01-31. */
+ pRec->flags &= ~MEM_Str;
}
/*
@@ -464,7 +469,7 @@ static void memTracePrint(Mem *p){
if( p->flags & MEM_Undefined ){
printf(" undefined");
}else if( p->flags & MEM_Null ){
- printf(" NULL");
+ printf(p->flags & MEM_Zero ? " NULL-nochng" : " NULL");
}else if( (p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str) ){
printf(" si:%lld", p->u.i);
}else if( p->flags & MEM_Int ){
@@ -643,7 +648,7 @@ int sqlite3VdbeExec(
assert( pOp>=aOp && pOp<&aOp[p->nOp]);
#ifdef VDBE_PROFILE
- start = sqlite3Hwtime();
+ start = sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime();
#endif
nVmStep++;
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
@@ -2167,18 +2172,8 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */
int v1; /* Left operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */
int v2; /* Right operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */
- pIn1 = &aMem[pOp->p1];
- if( pIn1->flags & MEM_Null ){
- v1 = 2;
- }else{
- v1 = sqlite3VdbeIntValue(pIn1)!=0;
- }
- pIn2 = &aMem[pOp->p2];
- if( pIn2->flags & MEM_Null ){
- v2 = 2;
- }else{
- v2 = sqlite3VdbeIntValue(pIn2)!=0;
- }
+ v1 = sqlite3VdbeBooleanValue(&aMem[pOp->p1], 2);
+ v2 = sqlite3VdbeBooleanValue(&aMem[pOp->p2], 2);
if( pOp->opcode==OP_And ){
static const unsigned char and_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 };
v1 = and_logic[v1*3+v2];
@@ -2196,6 +2191,35 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */
break;
}
+/* Opcode: IsTrue P1 P2 P3 P4 *
+** Synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4
+**
+** This opcode implements the IS TRUE, IS FALSE, IS NOT TRUE, and
+** IS NOT FALSE operators.
+**
+** Interpret the value in register P1 as a boolean value. Store that
+** boolean (a 0 or 1) in register P2. Or if the value in register P1 is
+** NULL, then the P3 is stored in register P2. Invert the answer if P4
+** is 1.
+**
+** The logic is summarized like this:
+**
+**
+** - If P3==0 and P4==0 then r[P2] := r[P1] IS TRUE
+**
- If P3==1 and P4==1 then r[P2] := r[P1] IS FALSE
+**
- If P3==0 and P4==1 then r[P2] := r[P1] IS NOT TRUE
+**
- If P3==1 and P4==0 then r[P2] := r[P1] IS NOT FALSE
+**
+*/
+case OP_IsTrue: { /* in1, out2 */
+ assert( pOp->p4type==P4_INT32 );
+ assert( pOp->p4.i==0 || pOp->p4.i==1 );
+ assert( pOp->p3==0 || pOp->p3==1 );
+ sqlite3VdbeMemSetInt64(&aMem[pOp->p2],
+ sqlite3VdbeBooleanValue(&aMem[pOp->p1], pOp->p3) ^ pOp->p4.i);
+ break;
+}
+
/* Opcode: Not P1 P2 * * *
** Synopsis: r[P2]= !r[P1]
**
@@ -2206,10 +2230,10 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */
case OP_Not: { /* same as TK_NOT, in1, out2 */
pIn1 = &aMem[pOp->p1];
pOut = &aMem[pOp->p2];
- sqlite3VdbeMemSetNull(pOut);
if( (pIn1->flags & MEM_Null)==0 ){
- pOut->flags = MEM_Int;
- pOut->u.i = !sqlite3VdbeIntValue(pIn1);
+ sqlite3VdbeMemSetInt64(pOut, !sqlite3VdbeBooleanValue(pIn1,0));
+ }else{
+ sqlite3VdbeMemSetNull(pOut);
}
break;
}
@@ -2276,30 +2300,25 @@ case OP_Once: { /* jump */
** is considered true if it is numeric and non-zero. If the value
** in P1 is NULL then take the jump if and only if P3 is non-zero.
*/
+case OP_If: { /* jump, in1 */
+ int c;
+ c = sqlite3VdbeBooleanValue(&aMem[pOp->p1], pOp->p3);
+ VdbeBranchTaken(c!=0, 2);
+ if( c ) goto jump_to_p2;
+ break;
+}
+
/* Opcode: IfNot P1 P2 P3 * *
**
** Jump to P2 if the value in register P1 is False. The value
** is considered false if it has a numeric value of zero. If the value
** in P1 is NULL then take the jump if and only if P3 is non-zero.
*/
-case OP_If: /* jump, in1 */
case OP_IfNot: { /* jump, in1 */
int c;
- pIn1 = &aMem[pOp->p1];
- if( pIn1->flags & MEM_Null ){
- c = pOp->p3;
- }else{
-#ifdef SQLITE_OMIT_FLOATING_POINT
- c = sqlite3VdbeIntValue(pIn1)!=0;
-#else
- c = sqlite3VdbeRealValue(pIn1)!=0.0;
-#endif
- if( pOp->opcode==OP_IfNot ) c = !c;
- }
+ c = !sqlite3VdbeBooleanValue(&aMem[pOp->p1], !pOp->p3);
VdbeBranchTaken(c!=0, 2);
- if( c ){
- goto jump_to_p2;
- }
+ if( c ) goto jump_to_p2;
break;
}
@@ -2349,6 +2368,36 @@ case OP_IfNullRow: { /* jump */
break;
}
+#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
+/* Opcode: Offset P1 P2 P3 * *
+** Synopsis: r[P3] = sqlite_offset(P1)
+**
+** Store in register r[P3] the byte offset into the database file that is the
+** start of the payload for the record at which that cursor P1 is currently
+** pointing.
+**
+** P2 is the column number for the argument to the sqlite_offset() function.
+** This opcode does not use P2 itself, but the P2 value is used by the
+** code generator. The P1, P2, and P3 operands to this opcode are the
+** same as for OP_Column.
+**
+** This opcode is only available if SQLite is compiled with the
+** -DSQLITE_ENABLE_OFFSET_SQL_FUNC option.
+*/
+case OP_Offset: { /* out3 */
+ VdbeCursor *pC; /* The VDBE cursor */
+ assert( pOp->p1>=0 && pOp->p1nCursor );
+ pC = p->apCsr[pOp->p1];
+ pOut = &p->aMem[pOp->p3];
+ if( NEVER(pC==0) || pC->eCurType!=CURTYPE_BTREE ){
+ sqlite3VdbeMemSetNull(pOut);
+ }else{
+ sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor));
+ }
+ break;
+}
+#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */
+
/* Opcode: Column P1 P2 P3 P4 P5
** Synopsis: r[P3]=PX
**
@@ -2762,9 +2811,18 @@ case OP_MakeRecord: {
pRec = pLast;
do{
assert( memIsValid(pRec) );
- pRec->uTemp = serial_type = sqlite3VdbeSerialType(pRec, file_format, &len);
+ serial_type = sqlite3VdbeSerialType(pRec, file_format, &len);
if( pRec->flags & MEM_Zero ){
- if( nData ){
+ if( serial_type==0 ){
+ /* Values with MEM_Null and MEM_Zero are created by xColumn virtual
+ ** table methods that never invoke sqlite3_result_xxxxx() while
+ ** computing an unchanging column value in an UPDATE statement.
+ ** Give such values a special internal-use-only serial-type of 10
+ ** so that they can be passed through to xUpdate and have
+ ** a true sqlite3_value_nochange(). */
+ assert( pOp->p5==OPFLAG_NOCHNG_MAGIC || CORRUPT_DB );
+ serial_type = 10;
+ }else if( nData ){
if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem;
}else{
nZero += pRec->u.nZero;
@@ -2775,6 +2833,7 @@ case OP_MakeRecord: {
testcase( serial_type==127 );
testcase( serial_type==128 );
nHdr += serial_type<=127 ? 1 : sqlite3VarintLen(serial_type);
+ pRec->uTemp = serial_type;
if( pRec==pData0 ) break;
pRec--;
}while(1);
@@ -4416,10 +4475,8 @@ case OP_InsertInt: {
int seekResult; /* Result of prior seek or 0 if no USESEEKRESULT flag */
const char *zDb; /* database name - used by the update hook */
Table *pTab; /* Table structure - used by update and pre-update hooks */
- int op; /* Opcode for update hook: SQLITE_UPDATE or SQLITE_INSERT */
BtreePayload x; /* Payload to be inserted */
- op = 0;
pData = &aMem[pOp->p2];
assert( pOp->p1>=0 && pOp->p1nCursor );
assert( memIsValid(pData) );
@@ -4447,19 +4504,21 @@ case OP_InsertInt: {
zDb = db->aDb[pC->iDb].zDbSName;
pTab = pOp->p4.pTab;
assert( (pOp->p5 & OPFLAG_ISNOOP) || HasRowid(pTab) );
- op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT);
}else{
- pTab = 0; /* Not needed. Silence a compiler warning. */
+ pTab = 0;
zDb = 0; /* Not needed. Silence a compiler warning. */
}
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
/* Invoke the pre-update hook, if any */
- if( db->xPreUpdateCallback
- && pOp->p4type==P4_TABLE
- && !(pOp->p5 & OPFLAG_ISUPDATE)
- ){
- sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey, pOp->p2);
+ if( pTab ){
+ if( db->xPreUpdateCallback && !(pOp->p5 & OPFLAG_ISUPDATE) ){
+ sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey,pOp->p2);
+ }
+ if( db->xUpdateCallback==0 || pTab->aCol==0 ){
+ /* Prevent post-update hook from running in cases when it should not */
+ pTab = 0;
+ }
}
if( pOp->p5 & OPFLAG_ISNOOP ) break;
#endif
@@ -4484,8 +4543,12 @@ case OP_InsertInt: {
/* Invoke the update-hook if required. */
if( rc ) goto abort_due_to_error;
- if( db->xUpdateCallback && op ){
- db->xUpdateCallback(db->pUpdateArg, op, zDb, pTab->zName, x.nKey);
+ if( pTab ){
+ assert( db->xUpdateCallback!=0 );
+ assert( pTab->aCol!=0 );
+ db->xUpdateCallback(db->pUpdateArg,
+ (pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT,
+ zDb, pTab->zName, x.nKey);
}
break;
}
@@ -6191,12 +6254,17 @@ case OP_AggStep0: {
assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) );
assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1) );
assert( pOp->p3p2 || pOp->p3>=pOp->p2+n );
- pCtx = sqlite3DbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*));
+ pCtx = sqlite3DbMallocRawNN(db, n*sizeof(sqlite3_value*) +
+ (sizeof(pCtx[0]) + sizeof(Mem) - sizeof(sqlite3_value*)));
if( pCtx==0 ) goto no_mem;
pCtx->pMem = 0;
+ pCtx->pOut = (Mem*)&(pCtx->argv[n]);
+ sqlite3VdbeMemInit(pCtx->pOut, db, MEM_Null);
pCtx->pFunc = pOp->p4.pFunc;
pCtx->iOp = (int)(pOp - aOp);
pCtx->pVdbe = p;
+ pCtx->skipFlag = 0;
+ pCtx->isError = 0;
pCtx->argc = n;
pOp->p4type = P4_FUNCCTX;
pOp->p4.pCtx = pCtx;
@@ -6207,7 +6275,6 @@ case OP_AggStep: {
int i;
sqlite3_context *pCtx;
Mem *pMem;
- Mem t;
assert( pOp->p4type==P4_FUNCCTX );
pCtx = pOp->p4.pCtx;
@@ -6230,26 +6297,28 @@ case OP_AggStep: {
#endif
pMem->n++;
- sqlite3VdbeMemInit(&t, db, MEM_Null);
- pCtx->pOut = &t;
- pCtx->fErrorOrAux = 0;
- pCtx->skipFlag = 0;
+ assert( pCtx->pOut->flags==MEM_Null );
+ assert( pCtx->isError==0 );
+ assert( pCtx->skipFlag==0 );
(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));
+ if( pCtx->isError ){
+ if( pCtx->isError>0 ){
+ sqlite3VdbeError(p, "%s", sqlite3_value_text(pCtx->pOut));
rc = pCtx->isError;
}
- sqlite3VdbeMemRelease(&t);
+ if( pCtx->skipFlag ){
+ assert( pOp[-1].opcode==OP_CollSeq );
+ i = pOp[-1].p1;
+ if( i ) sqlite3VdbeMemSetInt64(&aMem[i], 1);
+ pCtx->skipFlag = 0;
+ }
+ sqlite3VdbeMemRelease(pCtx->pOut);
+ pCtx->pOut->flags = MEM_Null;
+ pCtx->isError = 0;
if( rc ) goto abort_due_to_error;
- }else{
- assert( t.flags==MEM_Null );
- }
- if( pCtx->skipFlag ){
- assert( pOp[-1].opcode==OP_CollSeq );
- i = pOp[-1].p1;
- if( i ) sqlite3VdbeMemSetInt64(&aMem[i], 1);
}
+ assert( pCtx->pOut->flags==MEM_Null );
+ assert( pCtx->skipFlag==0 );
break;
}
@@ -6702,12 +6771,18 @@ case OP_VFilter: { /* jump */
#endif /* SQLITE_OMIT_VIRTUALTABLE */
#ifndef SQLITE_OMIT_VIRTUALTABLE
-/* Opcode: VColumn P1 P2 P3 * *
+/* Opcode: VColumn P1 P2 P3 * P5
** Synopsis: r[P3]=vcolumn(P2)
**
-** Store the value of the P2-th column of
-** the row of the virtual-table that the
-** P1 cursor is pointing to into register P3.
+** Store in register P3 the value of the P2-th column of
+** the current row of the virtual-table of cursor P1.
+**
+** If the VColumn opcode is being used to fetch the value of
+** an unchanging column during an UPDATE operation, then the P5
+** value is 1. Otherwise, P5 is 0. The P5 value is returned
+** by sqlite3_vtab_nochange() routine can can be used
+** by virtual table implementations to return special "no-change"
+** marks which can be more efficient, depending on the virtual table.
*/
case OP_VColumn: {
sqlite3_vtab *pVtab;
@@ -6729,10 +6804,17 @@ case OP_VColumn: {
assert( pModule->xColumn );
memset(&sContext, 0, sizeof(sContext));
sContext.pOut = pDest;
- MemSetTypeFlag(pDest, MEM_Null);
+ if( pOp->p5 ){
+ sqlite3VdbeMemSetNull(pDest);
+ pDest->flags = MEM_Null|MEM_Zero;
+ pDest->u.nZero = 0;
+ }else{
+ MemSetTypeFlag(pDest, MEM_Null);
+ }
rc = pModule->xColumn(pCur->uc.pVCur, &sContext, pOp->p2);
sqlite3VtabImportErrmsg(p, pVtab);
- if( sContext.isError ){
+ if( sContext.isError>0 ){
+ sqlite3VdbeError(p, "%s", sqlite3_value_text(pDest));
rc = sContext.isError;
}
sqlite3VdbeChangeEncoding(pDest, encoding);
@@ -6997,6 +7079,7 @@ case OP_Function0: {
pCtx->pFunc = pOp->p4.pFunc;
pCtx->iOp = (int)(pOp - aOp);
pCtx->pVdbe = p;
+ pCtx->isError = 0;
pCtx->argc = n;
pOp->p4type = P4_FUNCCTX;
pOp->p4.pCtx = pCtx;
@@ -7031,16 +7114,17 @@ case OP_Function: {
}
#endif
MemSetTypeFlag(pOut, MEM_Null);
- pCtx->fErrorOrAux = 0;
+ assert( pCtx->isError==0 );
(*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */
/* If the function returned an error, throw an exception */
- if( pCtx->fErrorOrAux ){
- if( pCtx->isError ){
+ if( pCtx->isError ){
+ if( pCtx->isError>0 ){
sqlite3VdbeError(p, "%s", sqlite3_value_text(pOut));
rc = pCtx->isError;
}
sqlite3VdbeDeleteAuxData(db, &p->pAuxData, pCtx->iOp, pOp->p1);
+ pCtx->isError = 0;
if( rc ) goto abort_due_to_error;
}
@@ -7055,7 +7139,13 @@ case OP_Function: {
break;
}
-
+/* Opcode: Trace P1 P2 * P4 *
+**
+** Write P4 on the statement trace output if statement tracing is
+** enabled.
+**
+** Operand P1 must be 0x7fffffff and P2 must positive.
+*/
/* Opcode: Init P1 P2 P3 P4 *
** Synopsis: Start at P2
**
@@ -7074,9 +7164,12 @@ case OP_Function: {
** If P3 is not zero, then it is an address to jump to if an SQLITE_CORRUPT
** error is encountered.
*/
+case OP_Trace:
case OP_Init: { /* jump */
- char *zTrace;
int i;
+#ifndef SQLITE_OMIT_TRACE
+ char *zTrace;
+#endif
/* If the P4 argument is not NULL, then it must be an SQL comment string.
** The "--" string is broken up to prevent false-positives with srcck1.c.
@@ -7088,7 +7181,9 @@ case OP_Init: { /* jump */
** sqlite3_expanded_sql(P) otherwise.
*/
assert( pOp->p4.z==0 || strncmp(pOp->p4.z, "-" "- ", 3)==0 );
- assert( pOp==p->aOp ); /* Always instruction 0 */
+
+ /* OP_Init is always instruction 0 */
+ assert( pOp==p->aOp || pOp->opcode==OP_Trace );
#ifndef SQLITE_OMIT_TRACE
if( (db->mTrace & (SQLITE_TRACE_STMT|SQLITE_TRACE_LEGACY))!=0
@@ -7131,6 +7226,7 @@ case OP_Init: { /* jump */
#endif /* SQLITE_OMIT_TRACE */
assert( pOp->p2>0 );
if( pOp->p1>=sqlite3GlobalConfig.iOnceResetThreshold ){
+ if( pOp->opcode==OP_Trace ) break;
for(i=1; inOp; i++){
if( p->aOp[i].opcode==OP_Once ) p->aOp[i].p1 = 0;
}
@@ -7190,7 +7286,7 @@ default: { /* This is really OP_Noop and OP_Explain */
#ifdef VDBE_PROFILE
{
- u64 endTime = sqlite3Hwtime();
+ u64 endTime = sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime();
if( endTime>start ) pOrigOp->cycles += endTime - start;
pOrigOp->cnt++;
}
diff --git a/src/vdbe.h b/src/vdbe.h
index 3e77eb9db5..f002e05d81 100644
--- a/src/vdbe.h
+++ b/src/vdbe.h
@@ -127,6 +127,7 @@ typedef struct VdbeOpList VdbeOpList;
#define P4_INT64 (-14) /* P4 is a 64-bit signed integer */
#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */
#define P4_FUNCCTX (-16) /* P4 is a pointer to an sqlite3_context object */
+#define P4_DYNBLOB (-17) /* Pointer to memory from sqliteMalloc() */
/* Error message codes for OP_Halt */
#define P5_ConstraintNotNull 1
diff --git a/src/vdbeInt.h b/src/vdbeInt.h
index cb783653c0..44f901abf7 100644
--- a/src/vdbeInt.h
+++ b/src/vdbeInt.h
@@ -317,7 +317,6 @@ struct sqlite3_context {
int iOp; /* Instruction number of OP_Function */
int isError; /* Error code returned by the function. */
u8 skipFlag; /* Skip accumulator loading if true */
- u8 fErrorOrAux; /* isError!=0 or pVdbe->pAuxData modified */
u8 argc; /* Number of arguments */
sqlite3_value *argv[1]; /* Argument set */
};
@@ -487,6 +486,7 @@ int sqlite3VdbeMemStringify(Mem*, u8, u8);
i64 sqlite3VdbeIntValue(Mem*);
int sqlite3VdbeMemIntegerify(Mem*);
double sqlite3VdbeRealValue(Mem*);
+int sqlite3VdbeBooleanValue(Mem*, int ifNull);
void sqlite3VdbeIntegerAffinity(Mem*);
int sqlite3VdbeMemRealify(Mem*);
int sqlite3VdbeMemNumerify(Mem*);
diff --git a/src/vdbeapi.c b/src/vdbeapi.c
index b9df40b8fd..dd4a352003 100644
--- a/src/vdbeapi.c
+++ b/src/vdbeapi.c
@@ -268,6 +268,11 @@ int sqlite3_value_type(sqlite3_value* pVal){
return aType[pVal->flags&MEM_AffMask];
}
+/* Return true if a parameter to xUpdate represents an unchanged column */
+int sqlite3_value_nochange(sqlite3_value *pVal){
+ return (pVal->flags&(MEM_Null|MEM_Zero))==(MEM_Null|MEM_Zero);
+}
+
/* Make a copy of an sqlite3_value object
*/
sqlite3_value *sqlite3_value_dup(const sqlite3_value *pOrig){
@@ -367,14 +372,12 @@ void sqlite3_result_double(sqlite3_context *pCtx, double rVal){
void sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){
assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
pCtx->isError = SQLITE_ERROR;
- pCtx->fErrorOrAux = 1;
sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF8, SQLITE_TRANSIENT);
}
#ifndef SQLITE_OMIT_UTF16
void sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){
assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
pCtx->isError = SQLITE_ERROR;
- pCtx->fErrorOrAux = 1;
sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT);
}
#endif
@@ -480,8 +483,7 @@ int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){
return SQLITE_OK;
}
void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){
- pCtx->isError = errCode;
- pCtx->fErrorOrAux = 1;
+ pCtx->isError = errCode ? errCode : -1;
#ifdef SQLITE_DEBUG
if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode;
#endif
@@ -495,7 +497,6 @@ void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){
void sqlite3_result_error_toobig(sqlite3_context *pCtx){
assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
pCtx->isError = SQLITE_TOOBIG;
- pCtx->fErrorOrAux = 1;
sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1,
SQLITE_UTF8, SQLITE_STATIC);
}
@@ -505,7 +506,6 @@ void sqlite3_result_error_nomem(sqlite3_context *pCtx){
assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
sqlite3VdbeMemSetNull(pCtx->pOut);
pCtx->isError = SQLITE_NOMEM_BKPT;
- pCtx->fErrorOrAux = 1;
sqlite3OomFault(pCtx->pOut->db);
}
@@ -745,6 +745,25 @@ sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){
return p->pOut->db;
}
+/*
+** If this routine is invoked from within an xColumn method of a virtual
+** table, then it returns true if and only if the the call is during an
+** UPDATE operation and the value of the column will not be modified
+** by the UPDATE.
+**
+** If this routine is called from any context other than within the
+** xColumn method of a virtual table, then the return value is meaningless
+** and arbitrary.
+**
+** Virtual table implements might use this routine to optimize their
+** performance by substituting a NULL result, or some other light-weight
+** value, as a signal to the xUpdate routine that the column is unchanged.
+*/
+int sqlite3_vtab_nochange(sqlite3_context *p){
+ assert( p );
+ return sqlite3_value_nochange(p->pOut);
+}
+
/*
** Return the current time for a statement. If the current time
** is requested more than once within the same run of a single prepared
@@ -893,10 +912,7 @@ void sqlite3_set_auxdata(
pAuxData->iAuxArg = iArg;
pAuxData->pNextAux = pVdbe->pAuxData;
pVdbe->pAuxData = pAuxData;
- if( pCtx->fErrorOrAux==0 ){
- pCtx->isError = 0;
- pCtx->fErrorOrAux = 1;
- }
+ if( pCtx->isError==0 ) pCtx->isError = -1;
}else if( pAuxData->xDeleteAux ){
pAuxData->xDeleteAux(pAuxData->pAux);
}
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index 30c616ddb1..ada03f05d4 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -866,6 +866,7 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){
case P4_REAL:
case P4_INT64:
case P4_DYNAMIC:
+ case P4_DYNBLOB:
case P4_INTARRAY: {
sqlite3DbFree(db, p4);
break;
@@ -1407,6 +1408,7 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
sqlite3XPrintf(&x, "program");
break;
}
+ case P4_DYNBLOB:
case P4_ADVANCE: {
zTemp[0] = 0;
break;
@@ -1639,6 +1641,8 @@ int sqlite3VdbeList(
int i; /* Loop counter */
int rc = SQLITE_OK; /* Return code */
Mem *pMem = &p->aMem[1]; /* First Mem of result set */
+ int bListSubprogs = (p->explain==1 || (db->flags & SQLITE_TriggerEQP)!=0);
+ Op *pOp = 0;
assert( p->explain );
assert( p->magic==VDBE_MAGIC_RUN );
@@ -1651,7 +1655,7 @@ int sqlite3VdbeList(
releaseMemArray(pMem, 8);
p->pResultSet = 0;
- if( p->rc==SQLITE_NOMEM_BKPT ){
+ if( p->rc==SQLITE_NOMEM ){
/* This happens if a malloc() inside a call to sqlite3_column_text() or
** sqlite3_column_text16() failed. */
sqlite3OomFault(db);
@@ -1666,7 +1670,7 @@ int sqlite3VdbeList(
** encountered, but p->pc will eventually catch up to nRow.
*/
nRow = p->nOp;
- if( p->explain==1 ){
+ if( bListSubprogs ){
/* The first 8 memory cells are used for the result set. So we will
** commandeer the 9th cell to use as storage for an array of pointers
** to trigger subprograms. The VDBE is guaranteed to have at least 9
@@ -1686,17 +1690,11 @@ int sqlite3VdbeList(
do{
i = p->pc++;
- }while( iexplain==2 && p->aOp[i].opcode!=OP_Explain );
- if( i>=nRow ){
- p->rc = SQLITE_OK;
- rc = SQLITE_DONE;
- }else if( db->u1.isInterrupted ){
- p->rc = SQLITE_INTERRUPT;
- rc = SQLITE_ERROR;
- sqlite3VdbeError(p, sqlite3ErrStr(p->rc));
- }else{
- char *zP4;
- Op *pOp;
+ if( i>=nRow ){
+ p->rc = SQLITE_OK;
+ rc = SQLITE_DONE;
+ break;
+ }
if( inOp ){
/* The output line number is small enough that we are still in the
** main program. */
@@ -1711,94 +1709,110 @@ int sqlite3VdbeList(
}
pOp = &apSub[j]->aOp[i];
}
- if( p->explain==1 ){
- pMem->flags = MEM_Int;
- pMem->u.i = i; /* Program counter */
- pMem++;
-
- pMem->flags = MEM_Static|MEM_Str|MEM_Term;
- pMem->z = (char*)sqlite3OpcodeName(pOp->opcode); /* Opcode */
- assert( pMem->z!=0 );
- pMem->n = sqlite3Strlen30(pMem->z);
- pMem->enc = SQLITE_UTF8;
- pMem++;
- /* When an OP_Program opcode is encounter (the only opcode that has
- ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms
- ** kept in p->aMem[9].z to hold the new program - assuming this subprogram
- ** has not already been seen.
- */
- if( pOp->p4type==P4_SUBPROGRAM ){
- int nByte = (nSub+1)*sizeof(SubProgram*);
- int j;
- for(j=0; jp4.pProgram ) break;
- }
- if( j==nSub && SQLITE_OK==sqlite3VdbeMemGrow(pSub, nByte, nSub!=0) ){
- apSub = (SubProgram **)pSub->z;
- apSub[nSub++] = pOp->p4.pProgram;
- pSub->flags |= MEM_Blob;
- pSub->n = nSub*sizeof(SubProgram*);
+ /* When an OP_Program opcode is encounter (the only opcode that has
+ ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms
+ ** kept in p->aMem[9].z to hold the new program - assuming this subprogram
+ ** has not already been seen.
+ */
+ if( bListSubprogs && pOp->p4type==P4_SUBPROGRAM ){
+ int nByte = (nSub+1)*sizeof(SubProgram*);
+ int j;
+ for(j=0; jp4.pProgram ) break;
+ }
+ if( j==nSub ){
+ p->rc = sqlite3VdbeMemGrow(pSub, nByte, nSub!=0);
+ if( p->rc!=SQLITE_OK ){
+ rc = SQLITE_ERROR;
+ break;
}
+ apSub = (SubProgram **)pSub->z;
+ apSub[nSub++] = pOp->p4.pProgram;
+ pSub->flags |= MEM_Blob;
+ pSub->n = nSub*sizeof(SubProgram*);
+ nRow += pOp->p4.pProgram->nOp;
}
}
+ }while( p->explain==2 && pOp->opcode!=OP_Explain );
- pMem->flags = MEM_Int;
- pMem->u.i = pOp->p1; /* P1 */
- pMem++;
-
- pMem->flags = MEM_Int;
- pMem->u.i = pOp->p2; /* P2 */
- pMem++;
-
- pMem->flags = MEM_Int;
- pMem->u.i = pOp->p3; /* P3 */
- pMem++;
-
- if( sqlite3VdbeMemClearAndResize(pMem, 100) ){ /* P4 */
- assert( p->db->mallocFailed );
- return SQLITE_ERROR;
- }
- pMem->flags = MEM_Str|MEM_Term;
- zP4 = displayP4(pOp, pMem->z, pMem->szMalloc);
- if( zP4!=pMem->z ){
- pMem->n = 0;
- sqlite3VdbeMemSetStr(pMem, zP4, -1, SQLITE_UTF8, 0);
+ if( rc==SQLITE_OK ){
+ if( db->u1.isInterrupted ){
+ p->rc = SQLITE_INTERRUPT;
+ rc = SQLITE_ERROR;
+ sqlite3VdbeError(p, sqlite3ErrStr(p->rc));
}else{
- assert( pMem->z!=0 );
- pMem->n = sqlite3Strlen30(pMem->z);
- pMem->enc = SQLITE_UTF8;
- }
- pMem++;
-
- if( p->explain==1 ){
- if( sqlite3VdbeMemClearAndResize(pMem, 4) ){
- assert( p->db->mallocFailed );
- return SQLITE_ERROR;
+ char *zP4;
+ if( p->explain==1 ){
+ pMem->flags = MEM_Int;
+ pMem->u.i = i; /* Program counter */
+ pMem++;
+
+ pMem->flags = MEM_Static|MEM_Str|MEM_Term;
+ pMem->z = (char*)sqlite3OpcodeName(pOp->opcode); /* Opcode */
+ assert( pMem->z!=0 );
+ pMem->n = sqlite3Strlen30(pMem->z);
+ pMem->enc = SQLITE_UTF8;
+ pMem++;
}
- pMem->flags = MEM_Str|MEM_Term;
- pMem->n = 2;
- sqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */
- pMem->enc = SQLITE_UTF8;
+
+ pMem->flags = MEM_Int;
+ pMem->u.i = pOp->p1; /* P1 */
pMem++;
-
-#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
- if( sqlite3VdbeMemClearAndResize(pMem, 500) ){
+
+ pMem->flags = MEM_Int;
+ pMem->u.i = pOp->p2; /* P2 */
+ pMem++;
+
+ pMem->flags = MEM_Int;
+ pMem->u.i = pOp->p3; /* P3 */
+ pMem++;
+
+ if( sqlite3VdbeMemClearAndResize(pMem, 100) ){ /* P4 */
assert( p->db->mallocFailed );
return SQLITE_ERROR;
}
pMem->flags = MEM_Str|MEM_Term;
- pMem->n = displayComment(pOp, zP4, pMem->z, 500);
- pMem->enc = SQLITE_UTF8;
-#else
- pMem->flags = MEM_Null; /* Comment */
-#endif
- }
+ zP4 = displayP4(pOp, pMem->z, pMem->szMalloc);
+ if( zP4!=pMem->z ){
+ pMem->n = 0;
+ sqlite3VdbeMemSetStr(pMem, zP4, -1, SQLITE_UTF8, 0);
+ }else{
+ assert( pMem->z!=0 );
+ pMem->n = sqlite3Strlen30(pMem->z);
+ pMem->enc = SQLITE_UTF8;
+ }
+ pMem++;
- p->nResColumn = 8 - 4*(p->explain-1);
- p->pResultSet = &p->aMem[1];
- p->rc = SQLITE_OK;
- rc = SQLITE_ROW;
+ if( p->explain==1 ){
+ if( sqlite3VdbeMemClearAndResize(pMem, 4) ){
+ assert( p->db->mallocFailed );
+ return SQLITE_ERROR;
+ }
+ pMem->flags = MEM_Str|MEM_Term;
+ pMem->n = 2;
+ sqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */
+ pMem->enc = SQLITE_UTF8;
+ pMem++;
+
+#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
+ if( sqlite3VdbeMemClearAndResize(pMem, 500) ){
+ assert( p->db->mallocFailed );
+ return SQLITE_ERROR;
+ }
+ pMem->flags = MEM_Str|MEM_Term;
+ pMem->n = displayComment(pOp, zP4, pMem->z, 500);
+ pMem->enc = SQLITE_UTF8;
+#else
+ pMem->flags = MEM_Null; /* Comment */
+#endif
+ }
+
+ p->nResColumn = 8 - 4*(p->explain-1);
+ p->pResultSet = &p->aMem[1];
+ p->rc = SQLITE_OK;
+ rc = SQLITE_ROW;
+ }
}
return rc;
}
@@ -2268,6 +2282,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
pPager = sqlite3BtreePager(pBt);
if( db->aDb[i].safety_level!=PAGER_SYNCHRONOUS_OFF
&& aMJNeeded[sqlite3PagerGetJournalMode(pPager)]
+ && sqlite3PagerIsMemdb(pPager)==0
){
assert( i!=1 );
nTrans++;
@@ -3459,7 +3474,13 @@ u32 sqlite3VdbeSerialGet(
Mem *pMem /* Memory cell to write value into */
){
switch( serial_type ){
- case 10: /* Reserved for future use */
+ case 10: { /* Internal use only: NULL with virtual table
+ ** UPDATE no-change flag set */
+ pMem->flags = MEM_Null|MEM_Zero;
+ pMem->n = 0;
+ pMem->u.nZero = 0;
+ break;
+ }
case 11: /* Reserved for future use */
case 0: { /* Null */
/* EVIDENCE-OF: R-24078-09375 Value is a NULL. */
diff --git a/src/vdbemem.c b/src/vdbemem.c
index f9f58c43f4..8df3c0d53b 100644
--- a/src/vdbemem.c
+++ b/src/vdbemem.c
@@ -43,7 +43,7 @@ int sqlite3VdbeCheckMemInvariants(Mem *p){
if( p->flags & MEM_Null ){
/* Cannot be both MEM_Null and some other type */
assert( (p->flags & (MEM_Int|MEM_Real|MEM_Str|MEM_Blob
- |MEM_RowSet|MEM_Frame|MEM_Agg|MEM_Zero))==0 );
+ |MEM_RowSet|MEM_Frame|MEM_Agg))==0 );
/* If MEM_Null is set, then either the value is a pure NULL (the usual
** case) or it is a pointer set using sqlite3_bind_pointer() or
@@ -93,6 +93,51 @@ int sqlite3VdbeCheckMemInvariants(Mem *p){
}
#endif
+#ifdef SQLITE_DEBUG
+/*
+** Check that string value of pMem agrees with its integer or real value.
+**
+** A single int or real value always converts to the same strings. But
+** many different strings can be converted into the same int or real.
+** If a table contains a numeric value and an index is based on the
+** corresponding string value, then it is important that the string be
+** derived from the numeric value, not the other way around, to ensure
+** that the index and table are consistent. See ticket
+** https://www.sqlite.org/src/info/343634942dd54ab (2018-01-31) for
+** an example.
+**
+** This routine looks at pMem to verify that if it has both a numeric
+** representation and a string representation then the string rep has
+** been derived from the numeric and not the other way around. It returns
+** true if everything is ok and false if there is a problem.
+**
+** This routine is for use inside of assert() statements only.
+*/
+int sqlite3VdbeMemConsistentDualRep(Mem *p){
+ char zBuf[100];
+ char *z;
+ int i, j, incr;
+ if( (p->flags & MEM_Str)==0 ) return 1;
+ if( (p->flags & (MEM_Int|MEM_Real))==0 ) return 1;
+ if( p->flags & MEM_Int ){
+ sqlite3_snprintf(sizeof(zBuf),zBuf,"%lld",p->u.i);
+ }else{
+ sqlite3_snprintf(sizeof(zBuf),zBuf,"%!.15g",p->u.r);
+ }
+ z = p->z;
+ i = j = 0;
+ incr = 1;
+ if( p->enc!=SQLITE_UTF8 ){
+ incr = 2;
+ if( p->enc==SQLITE_UTF16BE ) z++;
+ }
+ while( zBuf[j] ){
+ if( zBuf[j++]!=z[i] ) return 0;
+ i += incr;
+ }
+ return 1;
+}
+#endif /* SQLITE_DEBUG */
/*
** If pMem is an object with a valid string representation, this routine
@@ -527,6 +572,16 @@ double sqlite3VdbeRealValue(Mem *pMem){
}
}
+/*
+** Return 1 if pMem represents true, and return 0 if pMem represents false.
+** Return the value ifNull if pMem is NULL.
+*/
+int sqlite3VdbeBooleanValue(Mem *pMem, int ifNull){
+ if( pMem->flags & MEM_Int ) return pMem->u.i!=0;
+ if( pMem->flags & MEM_Null ) return ifNull;
+ return sqlite3VdbeRealValue(pMem)!=0.0;
+}
+
/*
** The MEM structure is already a MEM_Real. Try to also make it a
** MEM_Int if we can.
@@ -582,6 +637,18 @@ int sqlite3VdbeMemRealify(Mem *pMem){
return SQLITE_OK;
}
+/* Compare a floating point value to an integer. Return true if the two
+** values are the same within the precision of the floating point value.
+**
+** For some versions of GCC on 32-bit machines, if you do the more obvious
+** comparison of "r1==(double)i" you sometimes get an answer of false even
+** though the r1 and (double)i values are bit-for-bit the same.
+*/
+static int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){
+ double r2 = (double)i;
+ return memcmp(&r1, &r2, sizeof(r1))==0;
+}
+
/*
** Convert pMem so that it has types MEM_Real or MEM_Int or both.
** Invalidate any prior representations.
@@ -601,7 +668,7 @@ int sqlite3VdbeMemNumerify(Mem *pMem){
}else{
i64 i = pMem->u.i;
sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc);
- if( rc==1 && pMem->u.r==(double)i ){
+ if( rc==1 && sqlite3RealSameAsInt(pMem->u.r, i) ){
pMem->u.i = i;
MemSetTypeFlag(pMem, MEM_Int);
}else{
@@ -1084,6 +1151,7 @@ static SQLITE_NOINLINE const void *valueToText(sqlite3_value* pVal, u8 enc){
assert(pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) || pVal->db==0
|| pVal->db->mallocFailed );
if( pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) ){
+ assert( sqlite3VdbeMemConsistentDualRep(pVal) );
return pVal->z;
}else{
return 0;
@@ -1106,6 +1174,7 @@ const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){
assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) );
assert( (pVal->flags & MEM_RowSet)==0 );
if( (pVal->flags&(MEM_Str|MEM_Term))==(MEM_Str|MEM_Term) && pVal->enc==enc ){
+ assert( sqlite3VdbeMemConsistentDualRep(pVal) );
return pVal->z;
}
if( pVal->flags&MEM_Null ){
@@ -1321,7 +1390,11 @@ static int valueFromExpr(
assert( pExpr!=0 );
while( (op = pExpr->op)==TK_UPLUS || op==TK_SPAN ) pExpr = pExpr->pLeft;
+#if defined(SQLITE_ENABLE_STAT3_OR_STAT4)
+ if( op==TK_REGISTER ) op = pExpr->op2;
+#else
if( NEVER(op==TK_REGISTER) ) op = pExpr->op2;
+#endif
/* Compressed expressions only appear when parsing the DEFAULT clause
** on a table column definition, and hence only when pCtx==0. This
@@ -1416,7 +1489,10 @@ static int valueFromExpr(
return rc;
no_mem:
- sqlite3OomFault(db);
+#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+ if( pCtx==0 || pCtx->pParse->nErr==0 )
+#endif
+ sqlite3OomFault(db);
sqlite3DbFree(db, zVal);
assert( *ppVal==0 );
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
diff --git a/src/wal.c b/src/wal.c
index 94f06e1537..75cb9bf665 100644
--- a/src/wal.c
+++ b/src/wal.c
@@ -455,6 +455,7 @@ struct Wal {
u8 truncateOnCommit; /* True to truncate WAL file on commit */
u8 syncHeader; /* Fsync the WAL header if true */
u8 padToSectorBoundary; /* Pad transactions out to the next sector */
+ u8 bShmUnreliable; /* SHM content is read-only and unreliable */
WalIndexHdr hdr; /* Wal-index header for current transaction */
u32 minFrame; /* Ignore wal frames before this one */
u32 iReCksum; /* On commit, recalculate checksums from here */
@@ -545,11 +546,20 @@ struct WalIterator {
** is broken into pages of WALINDEX_PGSZ bytes. Wal-index pages are
** numbered from zero.
**
+** If the wal-index is currently smaller the iPage pages then the size
+** of the wal-index might be increased, but only if it is safe to do
+** so. It is safe to enlarge the wal-index if pWal->writeLock is true
+** or pWal->exclusiveMode==WAL_HEAPMEMORY_MODE.
+**
** If this call is successful, *ppPage is set to point to the wal-index
** page and SQLITE_OK is returned. If an error (an OOM or VFS error) occurs,
** then an SQLite error code is returned and *ppPage is set to 0.
*/
-static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
+static SQLITE_NOINLINE int walIndexPageRealloc(
+ Wal *pWal, /* The WAL context */
+ int iPage, /* The page we seek */
+ volatile u32 **ppPage /* Write the page pointer here */
+){
int rc = SQLITE_OK;
/* Enlarge the pWal->apWiData[] array if required */
@@ -568,16 +578,19 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
}
/* Request a pointer to the required page from the VFS */
- if( pWal->apWiData[iPage]==0 ){
- if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){
- pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ);
- if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT;
- }else{
- rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
- pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
- );
+ assert( pWal->apWiData[iPage]==0 );
+ if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){
+ pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ);
+ if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT;
+ }else{
+ rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
+ pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
+ );
+ assert( pWal->apWiData[iPage]!=0 || rc!=SQLITE_OK || pWal->writeLock==0 );
+ testcase( pWal->apWiData[iPage]==0 && rc==SQLITE_OK );
+ if( (rc&0xff)==SQLITE_READONLY ){
+ pWal->readOnly |= WAL_SHM_RDONLY;
if( rc==SQLITE_READONLY ){
- pWal->readOnly |= WAL_SHM_RDONLY;
rc = SQLITE_OK;
}
}
@@ -587,6 +600,16 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
assert( iPage==0 || *ppPage || rc!=SQLITE_OK );
return rc;
}
+static int walIndexPage(
+ Wal *pWal, /* The WAL context */
+ int iPage, /* The page we seek */
+ volatile u32 **ppPage /* Write the page pointer here */
+){
+ if( pWal->nWiData<=iPage || (*ppPage = pWal->apWiData[iPage])==0 ){
+ return walIndexPageRealloc(pWal, iPage, ppPage);
+ }
+ return SQLITE_OK;
+}
/*
** Return a pointer to the WalCkptInfo structure in the wal-index.
@@ -1100,7 +1123,6 @@ static int walIndexRecover(Wal *pWal){
i64 nSize; /* Size of log file */
u32 aFrameCksum[2] = {0, 0};
int iLock; /* Lock offset to lock for checkpoint */
- int nLock; /* Number of locks to hold */
/* Obtain an exclusive lock on all byte in the locking range not already
** locked by the caller. The caller is guaranteed to have locked the
@@ -1113,11 +1135,17 @@ static int walIndexRecover(Wal *pWal){
assert( WAL_CKPT_LOCK==WAL_ALL_BUT_WRITE );
assert( pWal->writeLock );
iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock;
- nLock = SQLITE_SHM_NLOCK - iLock;
- rc = walLockExclusive(pWal, iLock, nLock);
+ rc = walLockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock);
+ if( rc==SQLITE_OK ){
+ rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
+ if( rc!=SQLITE_OK ){
+ walUnlockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock);
+ }
+ }
if( rc ){
return rc;
}
+
WALTRACE(("WAL%p: recovery begin...\n", pWal));
memset(&pWal->hdr, 0, sizeof(WalIndexHdr));
@@ -1255,7 +1283,8 @@ finished:
recovery_error:
WALTRACE(("WAL%p: recovery %s\n", pWal, rc ? "failed" : "ok"));
- walUnlockExclusive(pWal, iLock, nLock);
+ walUnlockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock);
+ walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
return rc;
}
@@ -1263,13 +1292,14 @@ recovery_error:
** Close an open wal-index.
*/
static void walIndexClose(Wal *pWal, int isDelete){
- if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){
+ if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE || pWal->bShmUnreliable ){
int i;
for(i=0; inWiData; i++){
sqlite3_free((void *)pWal->apWiData[i]);
pWal->apWiData[i] = 0;
}
- }else{
+ }
+ if( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE ){
sqlite3OsShmUnmap(pWal->pDbFd, isDelete);
}
}
@@ -2078,6 +2108,12 @@ static int walIndexTryHdr(Wal *pWal, int *pChanged){
return 0;
}
+/*
+** This is the value that walTryBeginRead returns when it needs to
+** be retried.
+*/
+#define WAL_RETRY (-1)
+
/*
** Read the wal-index header from the wal-index and into pWal->hdr.
** If the wal-header appears to be corrupt, try to reconstruct the
@@ -2101,9 +2137,29 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
assert( pChanged );
rc = walIndexPage(pWal, 0, &page0);
if( rc!=SQLITE_OK ){
- return rc;
- };
- assert( page0 || pWal->writeLock==0 );
+ assert( rc!=SQLITE_READONLY ); /* READONLY changed to OK in walIndexPage */
+ if( rc==SQLITE_READONLY_CANTINIT ){
+ /* The SQLITE_READONLY_CANTINIT return means that the shared-memory
+ ** was openable but is not writable, and this thread is unable to
+ ** confirm that another write-capable connection has the shared-memory
+ ** open, and hence the content of the shared-memory is unreliable,
+ ** since the shared-memory might be inconsistent with the WAL file
+ ** and there is no writer on hand to fix it. */
+ assert( page0==0 );
+ assert( pWal->writeLock==0 );
+ assert( pWal->readOnly & WAL_SHM_RDONLY );
+ pWal->bShmUnreliable = 1;
+ pWal->exclusiveMode = WAL_HEAPMEMORY_MODE;
+ *pChanged = 1;
+ }else{
+ return rc; /* Any other non-OK return is just an error */
+ }
+ }else{
+ /* page0 can be NULL if the SHM is zero bytes in size and pWal->writeLock
+ ** is zero, which prevents the SHM from growing */
+ testcase( page0!=0 );
+ }
+ assert( page0!=0 || pWal->writeLock==0 );
/* If the first page of the wal-index has been mapped, try to read the
** wal-index header immediately, without holding any lock. This usually
@@ -2117,7 +2173,7 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
*/
assert( badHdr==0 || pWal->writeLock==0 );
if( badHdr ){
- if( pWal->readOnly & WAL_SHM_RDONLY ){
+ if( pWal->bShmUnreliable==0 && (pWal->readOnly & WAL_SHM_RDONLY) ){
if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){
walUnlockShared(pWal, WAL_WRITE_LOCK);
rc = SQLITE_READONLY_RECOVERY;
@@ -2147,15 +2203,193 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
if( badHdr==0 && pWal->hdr.iVersion!=WALINDEX_MAX_VERSION ){
rc = SQLITE_CANTOPEN_BKPT;
}
+ if( pWal->bShmUnreliable ){
+ if( rc!=SQLITE_OK ){
+ walIndexClose(pWal, 0);
+ pWal->bShmUnreliable = 0;
+ assert( pWal->nWiData>0 && pWal->apWiData[0]==0 );
+ /* walIndexRecover() might have returned SHORT_READ if a concurrent
+ ** writer truncated the WAL out from under it. If that happens, it
+ ** indicates that a writer has fixed the SHM file for us, so retry */
+ if( rc==SQLITE_IOERR_SHORT_READ ) rc = WAL_RETRY;
+ }
+ pWal->exclusiveMode = WAL_NORMAL_MODE;
+ }
return rc;
}
/*
-** This is the value that walTryBeginRead returns when it needs to
-** be retried.
+** Open a transaction in a connection where the shared-memory is read-only
+** and where we cannot verify that there is a separate write-capable connection
+** on hand to keep the shared-memory up-to-date with the WAL file.
+**
+** This can happen, for example, when the shared-memory is implemented by
+** memory-mapping a *-shm file, where a prior writer has shut down and
+** left the *-shm file on disk, and now the present connection is trying
+** to use that database but lacks write permission on the *-shm file.
+** Other scenarios are also possible, depending on the VFS implementation.
+**
+** Precondition:
+**
+** The *-wal file has been read and an appropriate wal-index has been
+** constructed in pWal->apWiData[] using heap memory instead of shared
+** memory.
+**
+** If this function returns SQLITE_OK, then the read transaction has
+** been successfully opened. In this case output variable (*pChanged)
+** is set to true before returning if the caller should discard the
+** contents of the page cache before proceeding. Or, if it returns
+** WAL_RETRY, then the heap memory wal-index has been discarded and
+** the caller should retry opening the read transaction from the
+** beginning (including attempting to map the *-shm file).
+**
+** If an error occurs, an SQLite error code is returned.
*/
-#define WAL_RETRY (-1)
+static int walBeginShmUnreliable(Wal *pWal, int *pChanged){
+ i64 szWal; /* Size of wal file on disk in bytes */
+ i64 iOffset; /* Current offset when reading wal file */
+ u8 aBuf[WAL_HDRSIZE]; /* Buffer to load WAL header into */
+ u8 *aFrame = 0; /* Malloc'd buffer to load entire frame */
+ int szFrame; /* Number of bytes in buffer aFrame[] */
+ u8 *aData; /* Pointer to data part of aFrame buffer */
+ volatile void *pDummy; /* Dummy argument for xShmMap */
+ int rc; /* Return code */
+ u32 aSaveCksum[2]; /* Saved copy of pWal->hdr.aFrameCksum */
+
+ assert( pWal->bShmUnreliable );
+ assert( pWal->readOnly & WAL_SHM_RDONLY );
+ assert( pWal->nWiData>0 && pWal->apWiData[0] );
+
+ /* Take WAL_READ_LOCK(0). This has the effect of preventing any
+ ** writers from running a checkpoint, but does not stop them
+ ** from running recovery. */
+ rc = walLockShared(pWal, WAL_READ_LOCK(0));
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_BUSY ) rc = WAL_RETRY;
+ goto begin_unreliable_shm_out;
+ }
+ pWal->readLock = 0;
+
+ /* Check to see if a separate writer has attached to the shared-memory area,
+ ** thus making the shared-memory "reliable" again. Do this by invoking
+ ** the xShmMap() routine of the VFS and looking to see if the return
+ ** is SQLITE_READONLY instead of SQLITE_READONLY_CANTINIT.
+ **
+ ** If the shared-memory is now "reliable" return WAL_RETRY, which will
+ ** cause the heap-memory WAL-index to be discarded and the actual
+ ** shared memory to be used in its place.
+ **
+ ** This step is important because, even though this connection is holding
+ ** the WAL_READ_LOCK(0) which prevents a checkpoint, a writer might
+ ** have already checkpointed the WAL file and, while the current
+ ** is active, wrap the WAL and start overwriting frames that this
+ ** process wants to use.
+ **
+ ** Once sqlite3OsShmMap() has been called for an sqlite3_file and has
+ ** returned any SQLITE_READONLY value, it must return only SQLITE_READONLY
+ ** or SQLITE_READONLY_CANTINIT or some error for all subsequent invocations,
+ ** even if some external agent does a "chmod" to make the shared-memory
+ ** writable by us, until sqlite3OsShmUnmap() has been called.
+ ** This is a requirement on the VFS implementation.
+ */
+ rc = sqlite3OsShmMap(pWal->pDbFd, 0, WALINDEX_PGSZ, 0, &pDummy);
+ assert( rc!=SQLITE_OK ); /* SQLITE_OK not possible for read-only connection */
+ if( rc!=SQLITE_READONLY_CANTINIT ){
+ rc = (rc==SQLITE_READONLY ? WAL_RETRY : rc);
+ goto begin_unreliable_shm_out;
+ }
+
+ /* We reach this point only if the real shared-memory is still unreliable.
+ ** Assume the in-memory WAL-index substitute is correct and load it
+ ** into pWal->hdr.
+ */
+ memcpy(&pWal->hdr, (void*)walIndexHdr(pWal), sizeof(WalIndexHdr));
+
+ /* Make sure some writer hasn't come in and changed the WAL file out
+ ** from under us, then disconnected, while we were not looking.
+ */
+ rc = sqlite3OsFileSize(pWal->pWalFd, &szWal);
+ if( rc!=SQLITE_OK ){
+ goto begin_unreliable_shm_out;
+ }
+ if( szWalhdr.mxFrame==0 ? SQLITE_OK : WAL_RETRY);
+ goto begin_unreliable_shm_out;
+ }
+
+ /* Check the salt keys at the start of the wal file still match. */
+ rc = sqlite3OsRead(pWal->pWalFd, aBuf, WAL_HDRSIZE, 0);
+ if( rc!=SQLITE_OK ){
+ goto begin_unreliable_shm_out;
+ }
+ if( memcmp(&pWal->hdr.aSalt, &aBuf[16], 8) ){
+ /* Some writer has wrapped the WAL file while we were not looking.
+ ** Return WAL_RETRY which will cause the in-memory WAL-index to be
+ ** rebuilt. */
+ rc = WAL_RETRY;
+ goto begin_unreliable_shm_out;
+ }
+
+ /* Allocate a buffer to read frames into */
+ szFrame = pWal->hdr.szPage + WAL_FRAME_HDRSIZE;
+ aFrame = (u8 *)sqlite3_malloc64(szFrame);
+ if( aFrame==0 ){
+ rc = SQLITE_NOMEM_BKPT;
+ goto begin_unreliable_shm_out;
+ }
+ aData = &aFrame[WAL_FRAME_HDRSIZE];
+
+ /* Check to see if a complete transaction has been appended to the
+ ** wal file since the heap-memory wal-index was created. If so, the
+ ** heap-memory wal-index is discarded and WAL_RETRY returned to
+ ** the caller. */
+ aSaveCksum[0] = pWal->hdr.aFrameCksum[0];
+ aSaveCksum[1] = pWal->hdr.aFrameCksum[1];
+ for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->hdr.szPage);
+ iOffset+szFrame<=szWal;
+ iOffset+=szFrame
+ ){
+ u32 pgno; /* Database page number for frame */
+ u32 nTruncate; /* dbsize field from frame header */
+
+ /* Read and decode the next log frame. */
+ rc = sqlite3OsRead(pWal->pWalFd, aFrame, szFrame, iOffset);
+ if( rc!=SQLITE_OK ) break;
+ if( !walDecodeFrame(pWal, &pgno, &nTruncate, aData, aFrame) ) break;
+
+ /* If nTruncate is non-zero, then a complete transaction has been
+ ** appended to this wal file. Set rc to WAL_RETRY and break out of
+ ** the loop. */
+ if( nTruncate ){
+ rc = WAL_RETRY;
+ break;
+ }
+ }
+ pWal->hdr.aFrameCksum[0] = aSaveCksum[0];
+ pWal->hdr.aFrameCksum[1] = aSaveCksum[1];
+
+ begin_unreliable_shm_out:
+ sqlite3_free(aFrame);
+ if( rc!=SQLITE_OK ){
+ int i;
+ for(i=0; inWiData; i++){
+ sqlite3_free((void*)pWal->apWiData[i]);
+ pWal->apWiData[i] = 0;
+ }
+ pWal->bShmUnreliable = 0;
+ sqlite3WalEndReadTransaction(pWal);
+ *pChanged = 1;
+ }
+ return rc;
+}
/*
** Attempt to start a read transaction. This might fail due to a race or
@@ -2217,6 +2451,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
assert( pWal->readLock<0 ); /* Not currently locked */
+ /* useWal may only be set for read/write connections */
+ assert( (pWal->readOnly & WAL_SHM_RDONLY)==0 || useWal==0 );
+
/* Take steps to avoid spinning forever if there is a protocol error.
**
** Circumstances that cause a RETRY should only last for the briefest
@@ -2245,7 +2482,10 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
}
if( !useWal ){
- rc = walIndexReadHdr(pWal, pChanged);
+ assert( rc==SQLITE_OK );
+ if( pWal->bShmUnreliable==0 ){
+ rc = walIndexReadHdr(pWal, pChanged);
+ }
if( rc==SQLITE_BUSY ){
/* If there is not a recovery running in another thread or process
** then convert BUSY errors to WAL_RETRY. If recovery is known to
@@ -2274,13 +2514,17 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
if( rc!=SQLITE_OK ){
return rc;
}
+ else if( pWal->bShmUnreliable ){
+ return walBeginShmUnreliable(pWal, pChanged);
+ }
}
+ assert( pWal->nWiData>0 );
+ assert( pWal->apWiData[0]!=0 );
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)))
+ && (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0)
#endif
){
/* The WAL has been completely backfilled (or it is empty).
@@ -2351,7 +2595,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
}
if( mxI==0 ){
assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
- return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK;
+ return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT;
}
rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
@@ -2636,7 +2880,7 @@ static int walFindFrame(
** table after the current read-transaction had started.
*/
iMinHash = walFramePage(pWal->minFrame);
- for(iHash=walFramePage(iLast); iHash>=iMinHash && iRead==0; iHash--){
+ for(iHash=walFramePage(iLast); iHash>=iMinHash; iHash--){
volatile ht_slot *aHash; /* Pointer to hash table */
volatile u32 *aPgno; /* Pointer to array of page numbers */
u32 iZero; /* Frame number corresponding to aPgno[0] */
@@ -2659,6 +2903,7 @@ static int walFindFrame(
return SQLITE_CORRUPT_BKPT;
}
}
+ if( iRead ) break;
}
*piRead = iRead;
@@ -2691,7 +2936,7 @@ int sqlite3WalFindFrame(
** then the WAL is ignored by the reader so return early, as if the
** WAL were empty.
*/
- if( iLast==0 || pWal->readLock==0 ){
+ if( iLast==0 || (pWal->readLock==0 && pWal->bShmUnreliable==0) ){
*piRead = 0;
return SQLITE_OK;
}
@@ -2705,8 +2950,8 @@ int sqlite3WalFindFrame(
if( rc==SQLITE_OK ){
u32 iRead2 = 0;
u32 iTest;
- assert( pWal->minFrame>0 );
- for(iTest=iLast; iTest>=pWal->minFrame; iTest--){
+ assert( pWal->bShmUnreliable || pWal->minFrame>0 );
+ for(iTest=iLast; iTest>=pWal->minFrame && iTest>0; iTest--){
if( walFramePgno(pWal, iTest)==pgno ){
iRead2 = iTest;
break;
@@ -3675,24 +3920,24 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op){
assert( pWal->readLock>=0 || (op<=0 && pWal->exclusiveMode==0) );
if( op==0 ){
- if( pWal->exclusiveMode ){
- pWal->exclusiveMode = 0;
+ if( pWal->exclusiveMode!=WAL_NORMAL_MODE ){
+ pWal->exclusiveMode = WAL_NORMAL_MODE;
if( walLockShared(pWal, WAL_READ_LOCK(pWal->readLock))!=SQLITE_OK ){
- pWal->exclusiveMode = 1;
+ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE;
}
- rc = pWal->exclusiveMode==0;
+ rc = pWal->exclusiveMode==WAL_NORMAL_MODE;
}else{
/* Already in locking_mode=NORMAL */
rc = 0;
}
}else if( op>0 ){
- assert( pWal->exclusiveMode==0 );
+ assert( pWal->exclusiveMode==WAL_NORMAL_MODE );
assert( pWal->readLock>=0 );
walUnlockShared(pWal, WAL_READ_LOCK(pWal->readLock));
- pWal->exclusiveMode = 1;
+ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE;
rc = 1;
}else{
- rc = pWal->exclusiveMode==0;
+ rc = pWal->exclusiveMode==WAL_NORMAL_MODE;
}
return rc;
}
diff --git a/src/walker.c b/src/walker.c
index ae7545bf5f..60bf8226fe 100644
--- a/src/walker.c
+++ b/src/walker.c
@@ -91,7 +91,6 @@ int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){
if( sqlite3WalkExpr(pWalker, p->pHaving) ) return WRC_Abort;
if( sqlite3WalkExprList(pWalker, p->pOrderBy) ) return WRC_Abort;
if( sqlite3WalkExpr(pWalker, p->pLimit) ) return WRC_Abort;
- if( sqlite3WalkExpr(pWalker, p->pOffset) ) return WRC_Abort;
return WRC_Continue;
}
diff --git a/src/where.c b/src/where.c
index 5545a45e87..8c1e3cd01f 100644
--- a/src/where.c
+++ b/src/where.c
@@ -19,6 +19,21 @@
#include "sqliteInt.h"
#include "whereInt.h"
+/*
+** Extra information appended to the end of sqlite3_index_info but not
+** visible to the xBestIndex function, at least not directly. The
+** sqlite3_vtab_collation() interface knows how to reach it, however.
+**
+** This object is not an API and can be changed from one release to the
+** next. As long as allocateIndexInfo() and sqlite3_vtab_collation()
+** agree on the structure, all will be well.
+*/
+typedef struct HiddenIndexInfo HiddenIndexInfo;
+struct HiddenIndexInfo {
+ WhereClause *pWC; /* The Where clause being analyzed */
+ Parse *pParse; /* The parsing context */
+};
+
/* Forward declaration of methods */
static int whereLoopResize(sqlite3*, WhereLoop*, int);
@@ -841,11 +856,11 @@ end_auto_index_create:
** by passing the pointer returned by this function to sqlite3_free().
*/
static sqlite3_index_info *allocateIndexInfo(
- Parse *pParse,
- WhereClause *pWC,
+ Parse *pParse, /* The parsing context */
+ WhereClause *pWC, /* The WHERE clause being analyzed */
Bitmask mUnusable, /* Ignore terms with these prereqs */
- struct SrcList_item *pSrc,
- ExprList *pOrderBy,
+ struct SrcList_item *pSrc, /* The FROM clause term that is the vtab */
+ ExprList *pOrderBy, /* The ORDER BY clause */
u16 *pmNoOmit /* Mask of terms not to omit */
){
int i, j;
@@ -853,6 +868,7 @@ static sqlite3_index_info *allocateIndexInfo(
struct sqlite3_index_constraint *pIdxCons;
struct sqlite3_index_orderby *pIdxOrderBy;
struct sqlite3_index_constraint_usage *pUsage;
+ struct HiddenIndexInfo *pHidden;
WhereTerm *pTerm;
int nOrderBy;
sqlite3_index_info *pIdxInfo;
@@ -894,7 +910,7 @@ static sqlite3_index_info *allocateIndexInfo(
*/
pIdxInfo = sqlite3DbMallocZero(pParse->db, sizeof(*pIdxInfo)
+ (sizeof(*pIdxCons) + sizeof(*pUsage))*nTerm
- + sizeof(*pIdxOrderBy)*nOrderBy );
+ + sizeof(*pIdxOrderBy)*nOrderBy + sizeof(*pHidden) );
if( pIdxInfo==0 ){
sqlite3ErrorMsg(pParse, "out of memory");
return 0;
@@ -905,7 +921,8 @@ static sqlite3_index_info *allocateIndexInfo(
** changing them. We have to do some funky casting in order to
** initialize those fields.
*/
- pIdxCons = (struct sqlite3_index_constraint*)&pIdxInfo[1];
+ pHidden = (struct HiddenIndexInfo*)&pIdxInfo[1];
+ pIdxCons = (struct sqlite3_index_constraint*)&pHidden[1];
pIdxOrderBy = (struct sqlite3_index_orderby*)&pIdxCons[nTerm];
pUsage = (struct sqlite3_index_constraint_usage*)&pIdxOrderBy[nOrderBy];
*(int*)&pIdxInfo->nConstraint = nTerm;
@@ -915,6 +932,8 @@ static sqlite3_index_info *allocateIndexInfo(
*(struct sqlite3_index_constraint_usage**)&pIdxInfo->aConstraintUsage =
pUsage;
+ pHidden->pWC = pWC;
+ pHidden->pParse = pParse;
for(i=j=0, pTerm=pWC->a; inTerm; i++, pTerm++){
u16 op;
if( pTerm->leftCursor != pSrc->iCursor ) continue;
@@ -2460,12 +2479,14 @@ static int whereLoopAddBtreeIndex(
pNew->wsFlags |= WHERE_COLUMN_EQ;
assert( saved_nEq==pNew->u.btree.nEq );
if( iCol==XN_ROWID
- || (iCol>0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1)
+ || (iCol>=0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1)
){
- if( iCol>=0 && pProbe->uniqNotNull==0 ){
- pNew->wsFlags |= WHERE_UNQ_WANTED;
- }else{
+ if( iCol==XN_ROWID || pProbe->uniqNotNull
+ || (pProbe->nKeyCol==1 && pProbe->onError && eOp==WO_EQ)
+ ){
pNew->wsFlags |= WHERE_ONEROW;
+ }else{
+ pNew->wsFlags |= WHERE_UNQ_WANTED;
}
}
}else if( eOp & WO_ISNULL ){
@@ -2879,6 +2900,7 @@ static int whereLoopAddBtree(
testcase( pNew->iTab!=pSrc->iCursor ); /* See ticket [98d973b8f5] */
continue; /* Partial index inappropriate for this query */
}
+ if( pProbe->bNoQuery ) continue;
rSize = pProbe->aiRowLogEst[0];
pNew->u.btree.nEq = 0;
pNew->u.btree.nBtm = 0;
@@ -3137,6 +3159,27 @@ static int whereLoopAddVirtualOne(
return rc;
}
+/*
+** If this function is invoked from within an xBestIndex() callback, it
+** returns a pointer to a buffer containing the name of the collation
+** sequence associated with element iCons of the sqlite3_index_info.aConstraint
+** array. Or, if iCons is out of range or there is no active xBestIndex
+** call, return NULL.
+*/
+const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int iCons){
+ HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1];
+ const char *zRet = 0;
+ if( iCons>=0 && iConsnConstraint ){
+ CollSeq *pC = 0;
+ int iTerm = pIdxInfo->aConstraint[iCons].iTermOffset;
+ Expr *pX = pHidden->pWC->a[iTerm].pExpr;
+ if( pX->pLeft ){
+ pC = sqlite3BinaryCompareCollSeq(pHidden->pParse, pX->pLeft, pX->pRight);
+ }
+ zRet = (pC ? pC->zName : "BINARY");
+ }
+ return zRet;
+}
/*
** Add all WhereLoop objects for a table of the join identified by
@@ -4590,6 +4633,7 @@ WhereInfo *sqlite3WhereBegin(
*/
for(ii=0; iinTerm; ii++){
WhereTerm *pT = &sWLB.pWC->a[ii];
+ if( pT->wtFlags & TERM_VIRTUAL ) continue;
if( pT->prereqAll==0 && (nTabList==0 || exprIsDeterministic(pT->pExpr)) ){
sqlite3ExprIfFalse(pParse, pT->pExpr, pWInfo->iBreak, SQLITE_JUMPIFNULL);
pT->wtFlags |= TERM_CODED;
@@ -4677,35 +4721,80 @@ WhereInfo *sqlite3WhereBegin(
}
}
#endif
- /* Attempt to omit tables from the join that do not effect the result */
+
+ /* Attempt to omit tables from the join that do not affect the result.
+ ** For a table to not affect the result, the following must be true:
+ **
+ ** 1) The query must not be an aggregate.
+ ** 2) The table must be the RHS of a LEFT JOIN.
+ ** 3) Either the query must be DISTINCT, or else the ON or USING clause
+ ** must contain a constraint that limits the scan of the table to
+ ** at most a single row.
+ ** 4) The table must not be referenced by any part of the query apart
+ ** from its own USING or ON clause.
+ **
+ ** For example, given:
+ **
+ ** CREATE TABLE t1(ipk INTEGER PRIMARY KEY, v1);
+ ** CREATE TABLE t2(ipk INTEGER PRIMARY KEY, v2);
+ ** CREATE TABLE t3(ipk INTEGER PRIMARY KEY, v3);
+ **
+ ** then table t2 can be omitted from the following:
+ **
+ ** SELECT v1, v3 FROM t1
+ ** LEFT JOIN t2 USING (t1.ipk=t2.ipk)
+ ** LEFT JOIN t3 USING (t1.ipk=t3.ipk)
+ **
+ ** or from:
+ **
+ ** SELECT DISTINCT v1, v3 FROM t1
+ ** LEFT JOIN t2
+ ** LEFT JOIN t3 USING (t1.ipk=t3.ipk)
+ */
+ notReady = ~(Bitmask)0;
if( pWInfo->nLevel>=2
- && pResultSet!=0
+ && pResultSet!=0 /* guarantees condition (1) above */
&& OptimizationEnabled(db, SQLITE_OmitNoopJoin)
){
+ int i;
Bitmask tabUsed = sqlite3WhereExprListUsage(pMaskSet, pResultSet);
if( sWLB.pOrderBy ){
tabUsed |= sqlite3WhereExprListUsage(pMaskSet, sWLB.pOrderBy);
}
- while( pWInfo->nLevel>=2 ){
+ for(i=pWInfo->nLevel-1; i>=1; i--){
WhereTerm *pTerm, *pEnd;
- pLoop = pWInfo->a[pWInfo->nLevel-1].pWLoop;
- if( (pWInfo->pTabList->a[pLoop->iTab].fg.jointype & JT_LEFT)==0 ) break;
+ struct SrcList_item *pItem;
+ pLoop = pWInfo->a[i].pWLoop;
+ pItem = &pWInfo->pTabList->a[pLoop->iTab];
+ if( (pItem->fg.jointype & JT_LEFT)==0 ) continue;
if( (wctrlFlags & WHERE_WANT_DISTINCT)==0
&& (pLoop->wsFlags & WHERE_ONEROW)==0
){
- break;
+ continue;
}
- if( (tabUsed & pLoop->maskSelf)!=0 ) break;
+ if( (tabUsed & pLoop->maskSelf)!=0 ) continue;
pEnd = sWLB.pWC->a + sWLB.pWC->nTerm;
for(pTerm=sWLB.pWC->a; pTermprereqAll & pLoop->maskSelf)!=0
- && !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
- ){
- break;
+ if( (pTerm->prereqAll & pLoop->maskSelf)!=0 ){
+ if( !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
+ || pTerm->pExpr->iRightJoinTable!=pItem->iCursor
+ ){
+ break;
+ }
}
}
- if( pTerm drop loop %c not used\n", pLoop->cId));
+ notReady &= ~pLoop->maskSelf;
+ for(pTerm=sWLB.pWC->a; pTermprereqAll & pLoop->maskSelf)!=0 ){
+ pTerm->wtFlags |= TERM_CODED;
+ }
+ }
+ if( i!=pWInfo->nLevel-1 ){
+ int nByte = (pWInfo->nLevel-1-i) * sizeof(WhereLevel);
+ memmove(&pWInfo->a[i], &pWInfo->a[i+1], nByte);
+ }
pWInfo->nLevel--;
nTabList--;
}
@@ -4715,15 +4804,32 @@ 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.
+ **
+ ** A one-pass approach can be used if the caller has requested one
+ ** and either (a) the scan visits at most one row or (b) each
+ ** of the following are true:
+ **
+ ** * the caller has indicated that a one-pass approach can be used
+ ** with multiple rows (by setting WHERE_ONEPASS_MULTIROW), and
+ ** * the table is not a virtual table, and
+ ** * either the scan does not use the OR optimization or the caller
+ ** is a DELETE operation (WHERE_DUPLICATES_OK is only specified
+ ** for DELETE).
+ **
+ ** The last qualification is because an UPDATE statement uses
+ ** WhereInfo.aiCurOnePass[1] to determine whether or not it really can
+ ** use a one-pass approach, and this is not set accurately for scans
+ ** that use the OR optimization.
*/
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
- && 0==(wsFlags & WHERE_VIRTUALTABLE))
- ){
+ if( bOnerow || (
+ 0!=(wctrlFlags & WHERE_ONEPASS_MULTIROW)
+ && 0==(wsFlags & WHERE_VIRTUALTABLE)
+ && (0==(wsFlags & WHERE_MULTI_OR) || (wctrlFlags & WHERE_DUPLICATES_OK))
+ )){
pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI;
if( HasRowid(pTabList->a[0].pTab) && (wsFlags & WHERE_IDX_ONLY) ){
if( wctrlFlags & WHERE_ONEPASS_MULTIROW ){
@@ -4860,7 +4966,6 @@ WhereInfo *sqlite3WhereBegin(
** loop below generates code for a single nested loop of the VM
** program.
*/
- notReady = ~(Bitmask)0;
for(ii=0; iieDistinct==WHERE_DISTINCT_ORDERED
+ && i==pWInfo->nLevel-1 /* Ticket [ef9318757b152e3] 2017-10-21 */
&& (pLoop->wsFlags & WHERE_INDEXED)!=0
&& (pIdx = pLoop->u.btree.pIndex)->hasStat1
&& (n = pLoop->u.btree.nIdxCol)>0
@@ -4990,7 +5096,8 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v);
assert( (ws & WHERE_IDX_ONLY)==0 || (ws & WHERE_INDEXED)!=0 );
if( (ws & WHERE_IDX_ONLY)==0 ){
- sqlite3VdbeAddOp1(v, OP_NullRow, pTabList->a[i].iCursor);
+ assert( pLevel->iTabCur==pTabList->a[pLevel->iFrom].iCursor );
+ sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur);
}
if( (ws & WHERE_INDEXED)
|| ((ws & WHERE_MULTI_OR) && pLevel->u.pCovidx)
@@ -5059,7 +5166,11 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
pOp = sqlite3VdbeGetOp(v, k);
for(; kp1!=pLevel->iTabCur ) continue;
- if( pOp->opcode==OP_Column ){
+ if( pOp->opcode==OP_Column
+#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
+ || pOp->opcode==OP_Offset
+#endif
+ ){
int x = pOp->p2;
assert( pIdx->pTable==pTab );
if( !HasRowid(pTab) ){
diff --git a/src/wherecode.c b/src/wherecode.c
index da5c686a95..e40a940ac1 100644
--- a/src/wherecode.c
+++ b/src/wherecode.c
@@ -128,7 +128,7 @@ int sqlite3WhereExplainOneScan(
){
int ret = 0;
#if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS)
- if( pParse->explain==2 )
+ if( sqlite3ParseToplevel(pParse)->explain==2 )
#endif
{
struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom];
@@ -377,6 +377,102 @@ static void updateRangeAffinityStr(
}
}
+
+/*
+** pX is an expression of the form: (vector) IN (SELECT ...)
+** In other words, it is a vector IN operator with a SELECT clause on the
+** LHS. But not all terms in the vector are indexable and the terms might
+** not be in the correct order for indexing.
+**
+** This routine makes a copy of the input pX expression and then adjusts
+** the vector on the LHS with corresponding changes to the SELECT so that
+** the vector contains only index terms and those terms are in the correct
+** order. The modified IN expression is returned. The caller is responsible
+** for deleting the returned expression.
+**
+** Example:
+**
+** CREATE TABLE t1(a,b,c,d,e,f);
+** CREATE INDEX t1x1 ON t1(e,c);
+** SELECT * FROM t1 WHERE (a,b,c,d,e) IN (SELECT v,w,x,y,z FROM t2)
+** \_______________________________________/
+** The pX expression
+**
+** Since only columns e and c can be used with the index, in that order,
+** the modified IN expression that is returned will be:
+**
+** (e,c) IN (SELECT z,x FROM t2)
+**
+** The reduced pX is different from the original (obviously) and thus is
+** only used for indexing, to improve performance. The original unaltered
+** IN expression must also be run on each output row for correctness.
+*/
+static Expr *removeUnindexableInClauseTerms(
+ Parse *pParse, /* The parsing context */
+ int iEq, /* Look at loop terms starting here */
+ WhereLoop *pLoop, /* The current loop */
+ Expr *pX /* The IN expression to be reduced */
+){
+ sqlite3 *db = pParse->db;
+ Expr *pNew = sqlite3ExprDup(db, pX, 0);
+ if( db->mallocFailed==0 ){
+ ExprList *pOrigRhs = pNew->x.pSelect->pEList; /* Original unmodified RHS */
+ ExprList *pOrigLhs = pNew->pLeft->x.pList; /* Original unmodified LHS */
+ ExprList *pRhs = 0; /* New RHS after modifications */
+ ExprList *pLhs = 0; /* New LHS after mods */
+ int i; /* Loop counter */
+ Select *pSelect; /* Pointer to the SELECT on the RHS */
+
+ for(i=iEq; inLTerm; i++){
+ if( pLoop->aLTerm[i]->pExpr==pX ){
+ int iField = pLoop->aLTerm[i]->iField - 1;
+ assert( pOrigRhs->a[iField].pExpr!=0 );
+ pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr);
+ pOrigRhs->a[iField].pExpr = 0;
+ assert( pOrigLhs->a[iField].pExpr!=0 );
+ pLhs = sqlite3ExprListAppend(pParse, pLhs, pOrigLhs->a[iField].pExpr);
+ pOrigLhs->a[iField].pExpr = 0;
+ }
+ }
+ sqlite3ExprListDelete(db, pOrigRhs);
+ sqlite3ExprListDelete(db, pOrigLhs);
+ pNew->pLeft->x.pList = pLhs;
+ pNew->x.pSelect->pEList = pRhs;
+ if( pLhs && pLhs->nExpr==1 ){
+ /* Take care here not to generate a TK_VECTOR containing only a
+ ** single value. Since the parser never creates such a vector, some
+ ** of the subroutines do not handle this case. */
+ Expr *p = pLhs->a[0].pExpr;
+ pLhs->a[0].pExpr = 0;
+ sqlite3ExprDelete(db, pNew->pLeft);
+ pNew->pLeft = p;
+ }
+ pSelect = pNew->x.pSelect;
+ if( pSelect->pOrderBy ){
+ /* If the SELECT statement has an ORDER BY clause, zero the
+ ** iOrderByCol variables. These are set to non-zero when an
+ ** ORDER BY term exactly matches one of the terms of the
+ ** result-set. Since the result-set of the SELECT statement may
+ ** have been modified or reordered, these variables are no longer
+ ** set correctly. Since setting them is just an optimization,
+ ** it's easiest just to zero them here. */
+ ExprList *pOrderBy = pSelect->pOrderBy;
+ for(i=0; inExpr; i++){
+ pOrderBy->a[i].u.x.iOrderByCol = 0;
+ }
+ }
+
+#if 0
+ printf("For indexing, change the IN expr:\n");
+ sqlite3TreeViewExpr(0, pX, 0);
+ printf("Into:\n");
+ sqlite3TreeViewExpr(0, pNew, 0);
+#endif
+ }
+ return pNew;
+}
+
+
/*
** Generate code for a single equality term of the WHERE clause. An equality
** term can be either X=expr or X IN (...). pTerm is the term to be
@@ -439,68 +535,23 @@ static int codeEqualityTerm(
}
}
for(i=iEq;inLTerm; i++){
- if( ALWAYS(pLoop->aLTerm[i]) && pLoop->aLTerm[i]->pExpr==pX ) nEq++;
+ assert( pLoop->aLTerm[i]!=0 );
+ if( pLoop->aLTerm[i]->pExpr==pX ) nEq++;
}
if( (pX->flags & EP_xIsSelect)==0 || pX->x.pSelect->pEList->nExpr==1 ){
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0);
}else{
- Select *pSelect = pX->x.pSelect;
sqlite3 *db = pParse->db;
- u16 savedDbOptFlags = db->dbOptFlags;
- ExprList *pOrigRhs = pSelect->pEList;
- ExprList *pOrigLhs = pX->pLeft->x.pList;
- ExprList *pRhs = 0; /* New Select.pEList for RHS */
- ExprList *pLhs = 0; /* New pX->pLeft vector */
+ pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX);
- for(i=iEq;inLTerm; i++){
- if( pLoop->aLTerm[i]->pExpr==pX ){
- int iField = pLoop->aLTerm[i]->iField - 1;
- Expr *pNewRhs = sqlite3ExprDup(db, pOrigRhs->a[iField].pExpr, 0);
- Expr *pNewLhs = sqlite3ExprDup(db, pOrigLhs->a[iField].pExpr, 0);
-
- pRhs = sqlite3ExprListAppend(pParse, pRhs, pNewRhs);
- pLhs = sqlite3ExprListAppend(pParse, pLhs, pNewLhs);
- }
- }
if( !db->mallocFailed ){
- Expr *pLeft = pX->pLeft;
-
- if( pSelect->pOrderBy ){
- /* If the SELECT statement has an ORDER BY clause, zero the
- ** iOrderByCol variables. These are set to non-zero when an
- ** ORDER BY term exactly matches one of the terms of the
- ** result-set. Since the result-set of the SELECT statement may
- ** have been modified or reordered, these variables are no longer
- ** set correctly. Since setting them is just an optimization,
- ** it's easiest just to zero them here. */
- ExprList *pOrderBy = pSelect->pOrderBy;
- for(i=0; inExpr; i++){
- pOrderBy->a[i].u.x.iOrderByCol = 0;
- }
- }
-
- /* Take care here not to generate a TK_VECTOR containing only a
- ** single value. Since the parser never creates such a vector, some
- ** of the subroutines do not handle this case. */
- if( pLhs->nExpr==1 ){
- pX->pLeft = pLhs->a[0].pExpr;
- }else{
- pLeft->x.pList = pLhs;
- aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int) * nEq);
- testcase( aiMap==0 );
- }
- pSelect->pEList = pRhs;
- db->dbOptFlags |= SQLITE_QueryFlattener;
+ aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq);
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap);
- db->dbOptFlags = savedDbOptFlags;
- testcase( aiMap!=0 && aiMap[0]!=0 );
- pSelect->pEList = pOrigRhs;
- pLeft->x.pList = pOrigLhs;
- pX->pLeft = pLeft;
+ pTerm->pExpr->iTable = pX->iTable;
}
- sqlite3ExprListDelete(pParse->db, pLhs);
- sqlite3ExprListDelete(pParse->db, pRhs);
+ sqlite3ExprDelete(db, pX);
+ pX = pTerm->pExpr;
}
if( eType==IN_INDEX_INDEX_DESC ){
@@ -1344,7 +1395,15 @@ Bitmask sqlite3WhereCodeOneLoopStart(
if( sqlite3ExprIsVector(pX->pRight) ){
r1 = rTemp = sqlite3GetTempReg(pParse);
codeExprOrVector(pParse, pX->pRight, r1, 1);
- op = aMoveOp[(pX->op - TK_GT) | 0x0001];
+ testcase( pX->op==TK_GT );
+ testcase( pX->op==TK_GE );
+ testcase( pX->op==TK_LT );
+ testcase( pX->op==TK_LE );
+ op = aMoveOp[((pX->op - TK_GT - 1) & 0x3) | 0x1];
+ assert( pX->op!=TK_GT || op==OP_SeekGE );
+ assert( pX->op!=TK_GE || op==OP_SeekGE );
+ assert( pX->op!=TK_LT || op==OP_SeekLE );
+ assert( pX->op!=TK_LE || op==OP_SeekLE );
}else{
r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp);
disableTerm(pLevel, pStart);
@@ -1639,6 +1698,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
}
}else if( bStopAtNull ){
sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq);
+ sqlite3ExprCacheRemove(pParse, regBase+nEq, 1);
endEq = 0;
nConstraint++;
}
@@ -2118,6 +2178,12 @@ Bitmask sqlite3WhereCodeOneLoopStart(
WO_EQ|WO_IN|WO_IS, 0);
if( pAlt==0 ) continue;
if( pAlt->wtFlags & (TERM_CODED) ) continue;
+ if( (pAlt->eOperator & WO_IN)
+ && (pAlt->pExpr->flags & EP_xIsSelect)
+ && (pAlt->pExpr->x.pSelect->pEList->nExpr>1)
+ ){
+ continue;
+ }
testcase( pAlt->eOperator & WO_EQ );
testcase( pAlt->eOperator & WO_IS );
testcase( pAlt->eOperator & WO_IN );
diff --git a/src/whereexpr.c b/src/whereexpr.c
index 58f1908cf8..313c5ee9bc 100644
--- a/src/whereexpr.c
+++ b/src/whereexpr.c
@@ -876,6 +876,9 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){
for(i=0; inSrc; i++){
mask |= exprSelectUsage(pMaskSet, pSrc->a[i].pSelect);
mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].pOn);
+ if( pSrc->a[i].fg.isTabFunc ){
+ mask |= sqlite3WhereExprListUsage(pMaskSet, pSrc->a[i].u1.pFuncArg);
+ }
}
}
pS = pS->pPrior;
@@ -1288,7 +1291,7 @@ static void exprAnalyze(
exprAnalyze(pSrc, pWC, idxNew);
}
pTerm = &pWC->a[idxTerm];
- pTerm->wtFlags = TERM_CODED|TERM_VIRTUAL; /* Disable the original */
+ pTerm->wtFlags |= TERM_CODED|TERM_VIRTUAL; /* Disable the original */
pTerm->eOperator = 0;
}
diff --git a/test/capi2.test b/test/capi2.test
index 39f50dd079..0680cf530c 100644
--- a/test/capi2.test
+++ b/test/capi2.test
@@ -163,7 +163,7 @@ do_test capi2-3.2 {
sqlite3_prepare $DB {select bogus from } -1 TAIL
} msg]
lappend rc $msg $TAIL
-} {1 {(1) near " ": syntax error} {}}
+} {1 {(1) incomplete input} {}}
do_test capi2-3.3 {
set rc [catch {
sqlite3_prepare $DB {;;;;select bogus from sqlite_master} -1 TAIL
@@ -184,7 +184,7 @@ do_test capi2-3.5 {
} {1 {(1) no such column: bogus} {;;x;}}
do_test capi2-3.6 {
set rc [catch {
- sqlite3_prepare $DB {select 5/0} -1 TAIL
+ sqlite3_prepare $DB {select 5/0;} -1 TAIL
} VM]
lappend rc $TAIL
} {0 {}}
diff --git a/test/capi3.test b/test/capi3.test
index becf1bf5db..01ce65b6a4 100644
--- a/test/capi3.test
+++ b/test/capi3.test
@@ -649,6 +649,18 @@ do_test capi3-5.33 {
sqlite3_finalize $STMT
} SQLITE_OK
+# 2018-01-09: If a column is the last token if a string, the column name
+# was not being set correctly, due to changes in check-in
+# https://sqlite.org/src/info/0fdf97efe5df7455
+#
+# This problem was detected by the community during beta-testing.
+#
+do_test capi3-5.34 {
+ set STMT [sqlite3_prepare $DB {SELECT :a, :b} -1 TAIL]
+ sqlite3_column_count $STMT
+} 2
+check_header $STMT capi-5.35 {:a :b} {{} {}}
+sqlite3_finalize $STMT
set ::ENC [execsql {pragma encoding}]
db close
diff --git a/test/cast.test b/test/cast.test
index f47f4bb2bf..f43aa48560 100644
--- a/test/cast.test
+++ b/test/cast.test
@@ -343,4 +343,49 @@ do_test cast-4.4 {
}
} {0 abc 0.0 abc}
+# Added 2018-01-26
+#
+# EVIDENCE-OF: R-48741-32454 If the prefix integer is greater than
+# +9223372036854775807 then the result of the cast is exactly
+# +9223372036854775807.
+do_execsql_test cast-5.1 {
+ SELECT CAST('9223372036854775808' AS integer);
+ SELECT CAST(' +000009223372036854775808' AS integer);
+ SELECT CAST('12345678901234567890123' AS INTEGER);
+} {9223372036854775807 9223372036854775807 9223372036854775807}
+
+# EVIDENCE-OF: R-06028-16857 Similarly, if the prefix integer is less
+# than -9223372036854775808 then the result of the cast is exactly
+# -9223372036854775808.
+do_execsql_test cast-5.2 {
+ SELECT CAST('-9223372036854775808' AS integer);
+ SELECT CAST('-9223372036854775809' AS integer);
+ SELECT CAST('-12345678901234567890123' AS INTEGER);
+} {-9223372036854775808 -9223372036854775808 -9223372036854775808}
+
+# EVIDENCE-OF: R-33990-33527 When casting to INTEGER, if the text looks
+# like a floating point value with an exponent, the exponent will be
+# ignored because it is no part of the integer prefix.
+# EVIDENCE-OF: R-24225-46995 For example, "(CAST '123e+5' AS INTEGER)"
+# results in 123, not in 12300000.
+do_execsql_test case-5.3 {
+ SELECT CAST('123e+5' AS INTEGER);
+ SELECT CAST('123e+5' AS NUMERIC);
+} {123 12300000.0}
+
+
+# The following does not have anything to do with the CAST operator,
+# but it does deal with affinity transformations.
+#
+do_execsql_test case-6.1 {
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1(a NUMERIC);
+ INSERT INTO t1 VALUES
+ ('9000000000000000001'),
+ ('9000000000000000001 '),
+ (' 9000000000000000001'),
+ (' 9000000000000000001 ');
+ SELECT * FROM t1;
+} {9000000000000000001 9000000000000000001 9000000000000000001 9000000000000000001}
+
finish_test
diff --git a/test/colname.test b/test/colname.test
index 2e4ae89008..f314f94f6e 100644
--- a/test/colname.test
+++ b/test/colname.test
@@ -378,6 +378,48 @@ do_test colname-9.210 {
execsql2 {SELECT t1.a, v3.a AS n FROM t1 JOIN v3}
} {a 1 n 3}
+# 2017-12-23: Ticket https://www.sqlite.org/src/info/3b4450072511e621
+# Inconsistent column names in CREATE TABLE AS
+#
+# Verify that the names of columns in the created table of a CREATE TABLE AS
+# are the same as the names of result columns in the SELECT statement.
+#
+do_execsql_test colname-9.300 {
+ DROP TABLE IF EXISTS t1;
+ DROP TABLE IF EXISTS t2;
+ CREATE TABLE t1(aaa INT);
+ INSERT INTO t1(aaa) VALUES(123);
+}
+do_test colname-9.310 {
+ execsql2 {SELECT BBb FROM (SELECT aaa AS Bbb FROM t1)}
+} {Bbb 123}
+ifcapable vtab {
+ do_execsql_test colname-9.320 {
+ CREATE TABLE t2 AS SELECT BBb FROM (SELECT aaa AS Bbb FROM t1);
+ SELECT name FROM pragma_table_info('t2');
+ } {Bbb}
+}
+
+# Issue detected by OSSFuzz on 2017-12-24 (Christmas Eve)
+# caused by check-in https://sqlite.org/src/info/6b2ff26c25
+#
+# Prior to being fixed, the following CREATE TABLE was dereferencing
+# a NULL pointer and segfaulting.
+#
+do_catchsql_test colname-9.400 {
+ CREATE TABLE t4 AS SELECT #0;
+} {1 {near "#0": syntax error}}
+
+# Issue detected by OSSFuzz on 2017-12-25 (Christmas Day)
+# also caused by check-in https://sqlite.org/src/info/6b2ff26c25
+#
+# Prior to being fixed, the following CREATE TABLE caused an
+# assertion fault.
+#
+do_catchsql_test colname-9.410 {
+ CREATE TABLE t5 AS SELECT RAISE(abort,a);
+} {1 {RAISE() may only be used within a trigger-program}}
+
# Make sure the quotation marks get removed from the column names
# when constructing a new table from an aggregate SELECT.
# Email from Juergen Palm on 2017-07-11.
diff --git a/test/crash8.test b/test/crash8.test
index f3b6f6e244..7916e9b641 100644
--- a/test/crash8.test
+++ b/test/crash8.test
@@ -142,6 +142,7 @@ proc write_file {zFile zData} {
# b) Less than 512, or
# c) Greater than SQLITE_MAX_PAGE_SIZE
#
+if {[atomic_batch_write test.db]==0} {
do_test crash8-3.1 {
list [file exists test.db-joural] [file exists test.db]
} {0 1}
@@ -228,6 +229,7 @@ do_test crash8-3.11 {
PRAGMA integrity_check
}
} {6 ok}
+}
# If a connection running in persistent-journal mode is part of a
@@ -266,8 +268,12 @@ ifcapable pragma {
UPDATE aux.ab SET b = randstr(1000,1000) WHERE a>=1;
UPDATE ab SET b = randstr(1000,1000) WHERE a>=1;
}
- list [file exists test.db-journal] [file exists test2.db-journal]
- } {1 1}
+ } {persist persist}
+ if {[atomic_batch_write test.db]==0} {
+ do_test crash8.4.1.1 {
+ list [file exists test.db-journal] [file exists test2.db-journal]
+ } {1 1}
+ }
do_test crash8-4.2 {
execsql {
diff --git a/test/delete_db.test b/test/delete_db.test
index 09c44ff9f3..6edd9c242e 100644
--- a/test/delete_db.test
+++ b/test/delete_db.test
@@ -17,6 +17,11 @@ set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix delete_db
+if {[atomic_batch_write test.db]} {
+ finish_test
+ return
+}
+
proc delete_all {} {
foreach f [glob -nocomplain test2*] { file delete $f }
foreach f [glob -nocomplain test3*] { file delete $f }
diff --git a/test/distinct2.test b/test/distinct2.test
index a7d59db705..31ab355132 100644
--- a/test/distinct2.test
+++ b/test/distinct2.test
@@ -179,5 +179,55 @@ do_execsql_test 920 {
wxYZ wxYz wxYz wxyZ wxyZ wxyz wxyz
}
+# Ticket https://sqlite.org/src/info/ef9318757b152e3a on 2017-11-21
+# Incorrect result due to a skip-ahead-distinct optimization on a
+# join where no rows of the inner loop appear in the result set.
+#
+db close
+sqlite3 db :memory:
+do_execsql_test 1000 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ CREATE INDEX t1b ON t1(b);
+ CREATE TABLE t2(x INTEGER PRIMARY KEY, y INTEGER);
+ CREATE INDEX t2y ON t2(y);
+ WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<49)
+ INSERT INTO t1(b) SELECT x/10 - 1 FROM c;
+ WITH RECURSIVE c(x) AS (VALUES(-1) UNION ALL SELECT x+1 FROM c WHERE x<19)
+ INSERT INTO t2(x,y) SELECT x, 1 FROM c;
+ SELECT DISTINCT y FROM t1, t2 WHERE b=x AND b<>-1;
+ ANALYZE;
+ SELECT DISTINCT y FROM t1, t2 WHERE b=x AND b<>-1;
+} {1 1}
+db close
+sqlite3 db :memory:
+do_execsql_test 1010 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER);
+ CREATE INDEX t1b ON t1(b);
+ CREATE TABLE t2(x INTEGER PRIMARY KEY, y INTEGER);
+ CREATE INDEX t2y ON t2(y);
+ WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<49)
+ INSERT INTO t1(b) SELECT -(x/10 - 1) FROM c;
+ WITH RECURSIVE c(x) AS (VALUES(-1) UNION ALL SELECT x+1 FROM c WHERE x<19)
+ INSERT INTO t2(x,y) SELECT -x, 1 FROM c;
+ SELECT DISTINCT y FROM t1, t2 WHERE b=x AND b<>1 ORDER BY y DESC;
+ ANALYZE;
+ SELECT DISTINCT y FROM t1, t2 WHERE b=x AND b<>1 ORDER BY y DESC;
+} {1 1}
+db close
+sqlite3 db :memory:
+do_execsql_test 1020 {
+ CREATE TABLE t1(a, b);
+ CREATE INDEX t1a ON t1(a, b);
+ -- Lots of rows of (1, 'no'), followed by a single (1, 'yes').
+ WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100)
+ INSERT INTO t1(a, b) SELECT 1, 'no' FROM c;
+ INSERT INTO t1(a, b) VALUES(1, 'yes');
+ CREATE TABLE t2(x PRIMARY KEY);
+ INSERT INTO t2 VALUES('yes');
+ SELECT DISTINCT a FROM t1, t2 WHERE x=b;
+ ANALYZE;
+ SELECT DISTINCT a FROM t1, t2 WHERE x=b;
+} {1 1}
+
finish_test
diff --git a/test/exclusive.test b/test/exclusive.test
index 45f9318205..04de529137 100644
--- a/test/exclusive.test
+++ b/test/exclusive.test
@@ -252,7 +252,9 @@ db2 close
# opens the journal file for exclusive access, preventing its contents
# from being inspected externally.
#
-if {$tcl_platform(platform) != "windows"} {
+if {$tcl_platform(platform) != "windows"
+ && [atomic_batch_write test.db]==0
+} {
# Return a list of two booleans (either 0 or 1). The first is true
# if the named file exists. The second is true only if the file
@@ -391,6 +393,7 @@ do_test exclusive-4.5 {
# Tests exclusive-5.X - test that statement journals are truncated
# instead of deleted when in exclusive access mode.
#
+if {[atomic_batch_write test.db]==0} {
# Close and reopen the database so that the temp database is no
# longer active.
@@ -508,4 +511,6 @@ do_execsql_test exclusive-6.5 {
SELECT * FROM sqlite_master;
} {exclusive}
+} ;# atomic_batch_write==0
+
finish_test
diff --git a/test/expr.test b/test/expr.test
index 7a6d477259..3cdc9180e8 100644
--- a/test/expr.test
+++ b/test/expr.test
@@ -977,6 +977,63 @@ do_execsql_test expr-13.9 {
SELECT '' <= "";
} {1}
+# 2018-02-26. Ticket https://www.sqlite.org/src/tktview/36fae083b450e3af85
+#
+do_execsql_test expr-14.1 {
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1(x);
+ INSERT INTO t1 VALUES(0),(1),(NULL),(0.5),('1x'),('0x');
+ SELECT count(*) FROM t1
+ WHERE (x OR (8==9)) != (CASE WHEN x THEN 1 ELSE 0 END);
+} {0}
+do_execsql_test expr-14.2 {
+ SELECT count(*) FROM t1
+ WHERE (x OR (8==9)) != (NOT NOT x);
+} {0}
+do_execsql_test expr-14.3 {
+ SELECT sum(NOT x) FROM t1
+ WHERE x
+} {0}
+do_execsql_test expr-14.4 {
+ SELECT sum(CASE WHEN x THEN 0 ELSE 1 END) FROM t1
+ WHERE x
+} {0}
+foreach {tn val} [list 1 NaN 2 -NaN 3 NaN0 4 -NaN0 5 Inf 6 -Inf] {
+ do_execsql_test expr-15.$tn.1 {
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1(x);
+ INSERT INTO t1 VALUES(0),(1),(NULL),(0.5),('1x'),('0x');
+ }
+
+ do_test expr-15.$tn.2 {
+ set ::STMT [sqlite3_prepare db "INSERT INTO t1 VALUES(?)" -1 TAIL]
+ sqlite3_bind_double $::STMT 1 $val
+ sqlite3_step $::STMT
+ sqlite3_reset $::STMT
+ sqlite3_finalize $::STMT
+ } {SQLITE_OK}
+
+ do_execsql_test expr-15.$tn.3 {
+ SELECT count(*) FROM t1
+ WHERE (x OR (8==9)) != (CASE WHEN x THEN 1 ELSE 0 END);
+ } {0}
+
+ do_execsql_test expr-15.$tn.4 {
+ SELECT count(*) FROM t1
+ WHERE (x OR (8==9)) != (NOT NOT x);
+ } {0}
+
+ do_execsql_test expr-15.$tn.5 {
+ SELECT sum(NOT x) FROM t1
+ WHERE x
+ } {0}
+
+ do_execsql_test expr-15.$tn.6 {
+ SELECT sum(CASE WHEN x THEN 0 ELSE 1 END) FROM t1
+ WHERE x
+ } {0}
+}
+
finish_test
diff --git a/test/fallocate.test b/test/fallocate.test
index 63d88ea885..0c971c08c1 100644
--- a/test/fallocate.test
+++ b/test/fallocate.test
@@ -61,6 +61,7 @@ do_test fallocate-1.7 {
execsql { BEGIN; INSERT INTO t1 VALUES(1, 2); }
if {[permutation] != "inmemory_journal"
&& [permutation] != "atomic-batch-write"
+ && [atomic_batch_write test.db]==0
} {
hexio_get_int [hexio_read test.db-journal 16 4]
} else {
diff --git a/test/fkey1.test b/test/fkey1.test
index d9b038a022..fa87335888 100644
--- a/test/fkey1.test
+++ b/test/fkey1.test
@@ -171,6 +171,22 @@ do_catchsql_test fkey1-5.2 {
INSERT OR REPLACE INTO t11 VALUES (2, 3);
} {1 {FOREIGN KEY constraint failed}}
+# Make sure sqlite3_trace() output works with triggers used to implement
+# FK constraints
+#
+ifcapable trace {
+ proc sqltrace {txt} {
+ global traceoutput
+ lappend traceoutput $txt
+ }
+ do_test fkey1-5.2.1 {
+ unset -nocomplain traceoutput
+ db trace sqltrace
+ catch {db eval {INSERT OR REPLACE INTO t11 VALUES(2,3);}}
+ set traceoutput
+ } {{INSERT OR REPLACE INTO t11 VALUES(2,3);} {INSERT OR REPLACE INTO t11 VALUES(2,3);} {INSERT OR REPLACE INTO t11 VALUES(2,3);}}
+}
+
# A similar test to the above.
do_execsql_test fkey1-5.3 {
CREATE TABLE Foo (
diff --git a/test/fkey7.test b/test/fkey7.test
index 6c646a9a7f..e86fc5c57b 100644
--- a/test/fkey7.test
+++ b/test/fkey7.test
@@ -68,4 +68,18 @@ ifcapable incrblob {
} {SQLITE_CONSTRAINT}
}
+ifcapable stat4 {
+ do_execsql_test 3.0 {
+ CREATE TABLE p4 (id INTEGER NOT NULL PRIMARY KEY);
+ INSERT INTO p4 VALUES(1), (2), (3);
+
+ CREATE TABLE c4(x INTEGER REFERENCES p4(id) DEFERRABLE INITIALLY DEFERRED);
+ CREATE INDEX c4_x ON c4(x);
+ INSERT INTO c4 VALUES(1), (2), (3);
+
+ ANALYZE;
+ INSERT INTO p4(id) VALUES(4);
+ }
+}
+
finish_test
diff --git a/test/fts3aa.test b/test/fts3aa.test
index 10ec273cbf..d5f96d81a7 100644
--- a/test/fts3aa.test
+++ b/test/fts3aa.test
@@ -250,4 +250,5 @@ do_execsql_test 9.2 {
CREATE VIRTUAL TABLE t10 USING fts3(<, b, c);
}
+expand_all_sql db
finish_test
diff --git a/test/fts3rank.test b/test/fts3rank.test
index 7ee3143a76..fd1a1c89d7 100644
--- a/test/fts3rank.test
+++ b/test/fts3rank.test
@@ -14,7 +14,7 @@
set testdir [file dirname $argv0]
source $testdir/tester.tcl
-set testprefix fts3expr5
+set testprefix fts3rank
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts3 {
@@ -56,9 +56,14 @@ do_catchsql_test 1.4 {
SELECT * FROM t1 ORDER BY rank(x'0000000000000000') DESC, rowid
} {0 {{one two} one {one two} three {one two} two}}
-do_catchsql_test 1.5 {
- SELECT * FROM t1 ORDER BY rank(x'0100000001000000') DESC, rowid
-} {1 {invalid matchinfo blob passed to function rank()}}
+if {$tcl_platform(byteOrder)=="littleEndian"} {
+ do_catchsql_test 1.5le {
+ SELECT * FROM t1 ORDER BY rank(x'0100000001000000') DESC, rowid
+ } {1 {invalid matchinfo blob passed to function rank()}}
+} else {
+ do_catchsql_test 1.5be {
+ SELECT * FROM t1 ORDER BY rank(x'0000000100000001') DESC, rowid
+ } {1 {invalid matchinfo blob passed to function rank()}}
+}
finish_test
-
diff --git a/test/fts4onepass.test b/test/fts4onepass.test
index 46cb4b794b..344be4b1df 100644
--- a/test/fts4onepass.test
+++ b/test/fts4onepass.test
@@ -143,4 +143,18 @@ foreach {tn tcl1 tcl2} {
eval $tcl2
}
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE zt USING fts4(a, b);
+ INSERT INTO zt(rowid, a, b) VALUES(1, 'unus duo', NULL);
+ INSERT INTO zt(rowid, a, b) VALUES(2, NULL, NULL);
+
+ BEGIN;
+ UPDATE zt SET b='septum' WHERE rowid = 1;
+ UPDATE zt SET b='octo' WHERE rowid = 1;
+ COMMIT;
+
+ SELECT count(*) FROM zt_segdir;
+} {3}
+
+
finish_test
diff --git a/test/func.test b/test/func.test
index 98ae8ddeb5..23a3ae4392 100644
--- a/test/func.test
+++ b/test/func.test
@@ -507,6 +507,17 @@ if {$encoding=="UTF-16le"} {
execsql {SELECT hex(replace('aabcdefg','a','aaa'))}
} {616161616161626364656667}
}
+do_execsql_test func-9.14 {
+ WITH RECURSIVE c(x) AS (
+ VALUES(1)
+ UNION ALL
+ SELECT x+1 FROM c WHERE x<1040
+ )
+ SELECT
+ count(*),
+ sum(length(replace(printf('abc%.*cxyz',x,'m'),'m','nnnn'))-(6+x*4))
+ FROM c;
+} {1040 0}
# Use the "sqlite_register_test_function" TCL command which is part of
# the text fixture in order to verify correct operation of some of
diff --git a/test/func6.test b/test/func6.test
new file mode 100644
index 0000000000..1e16a7ca38
--- /dev/null
+++ b/test/func6.test
@@ -0,0 +1,174 @@
+# 2017-12-16
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+# Test cases for the sqlite_offset() function.
+#
+# Some of the tests in this file depend on the exact placement of content
+# within b-tree pages. Such placement is at the implementations discretion,
+# and so it is possible for results to change from one release to the next.
+#
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+ifcapable !offset_sql_func {
+ finish_test
+ return
+}
+
+set bNullTrim 0
+ifcapable null_trim {
+ set bNullTrim 1
+}
+
+do_execsql_test func6-100 {
+ PRAGMA page_size=4096;
+ PRAGMA auto_vacuum=NONE;
+ CREATE TABLE t1(a,b,c,d);
+ WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100)
+ INSERT INTO t1(a,b,c,d) SELECT printf('abc%03x',x), x, 1000-x, NULL FROM c;
+ CREATE INDEX t1a ON t1(a);
+ CREATE INDEX t1bc ON t1(b,c);
+ CREATE TABLE t2(x TEXT PRIMARY KEY, y) WITHOUT ROWID;
+ INSERT INTO t2(x,y) SELECT a, b FROM t1;
+}
+
+# Load the contents of $file from disk and return it encoded as a hex
+# string.
+proc loadhex {file} {
+ set fd [open $file]
+ fconfigure $fd -translation binary -encoding binary
+ set data [read $fd]
+ close $fd
+ binary encode hex $data
+}
+
+# Each argument is either an integer between 0 and 65535, a text value, or
+# an empty string representing an SQL NULL. This command builds an SQLite
+# record containing the values passed as arguments and returns it encoded
+# as a hex string.
+proc hexrecord {args} {
+ set hdr ""
+ set body ""
+
+ if {$::bNullTrim} {
+ while {[llength $args] && [lindex $args end]=={}} {
+ set args [lrange $args 0 end-1]
+ }
+ }
+
+ foreach x $args {
+ if {$x==""} {
+ append hdr 00
+ } elseif {[string is integer $x]==0} {
+ set n [string length $x]
+ append hdr [format %02x [expr $n*2 + 13]]
+ append body [binary encode hex $x]
+ } elseif {$x == 0} {
+ append hdr 08
+ } elseif {$x == 1} {
+ append hdr 09
+ } elseif {$x <= 127} {
+ append hdr 01
+ append body [format %02x $x]
+ } else {
+ append hdr 02
+ append body [format %04x $x]
+ }
+ }
+ set res [format %02x [expr 1 + [string length $hdr]/2]]
+ append res $hdr
+ append res $body
+}
+
+# Argument $off is an offset into the database image encoded as a hex string
+# in argument $hexdb. This command returns 0 if the offset contains the hex
+# $hexrec, or throws an exception otherwise.
+#
+proc offset_contains_record {off hexdb hexrec} {
+ set n [string length $hexrec]
+ set off [expr $off*2]
+ if { [string compare $hexrec [string range $hexdb $off [expr $off+$n-1]]] } {
+ error "record not found!"
+ }
+ return 0
+}
+
+# This command is the implementation of SQL function "offrec()". The first
+# argument to this is an offset value. The remaining values are used to
+# formulate an SQLite record. If database file test.db does not contain
+# an equivalent record at the specified offset, an exception is thrown.
+# Otherwise, 0 is returned.
+#
+proc offrec {args} {
+ set offset [lindex $args 0]
+ set rec [hexrecord {*}[lrange $args 1 end]]
+ offset_contains_record $offset $::F $rec
+}
+set F [loadhex test.db]
+db func offrec offrec
+
+# Test the sanity of the tests.
+if {$bNullTrim} {
+ set offset 8180
+} else {
+ set offset 8179
+}
+do_execsql_test func6-105 {
+ SELECT sqlite_offset(d) FROM t1 ORDER BY rowid LIMIT 1;
+} $offset
+do_test func6-106 {
+ set r [hexrecord abc001 1 999 {}]
+ offset_contains_record $offset $F $r
+} 0
+
+set z100 [string trim [string repeat "0 " 100]]
+
+# Test offsets within table b-tree t1.
+do_execsql_test func6-110 {
+ SELECT offrec(sqlite_offset(d), a, b, c, d) FROM t1 ORDER BY rowid
+} $z100
+
+do_execsql_test func6-120 {
+ SELECT a, typeof(sqlite_offset(+a)) FROM t1
+ ORDER BY rowid LIMIT 2;
+} {abc001 null abc002 null}
+
+# Test offsets within index b-tree t1a.
+do_execsql_test func6-130 {
+ SELECT offrec(sqlite_offset(a), a, rowid) FROM t1 ORDER BY a
+} $z100
+
+# Test offsets within table b-tree t1 with a temp b-tree ORDER BY.
+do_execsql_test func6-140 {
+ SELECT offrec(sqlite_offset(d), a, b, c, d) FROM t1 ORDER BY a
+} $z100
+
+# Test offsets from both index t1a and table t1 in the same query.
+do_execsql_test func6-150 {
+ SELECT offrec(sqlite_offset(a), a, rowid),
+ offrec(sqlite_offset(d), a, b, c, d)
+ FROM t1 ORDER BY a
+} [concat $z100 $z100]
+
+# Test offsets from both index t1bc and table t1 in the same query.
+do_execsql_test func6-160 {
+ SELECT offrec(sqlite_offset(b), b, c, rowid),
+ offrec(sqlite_offset(c), b, c, rowid),
+ offrec(sqlite_offset(d), a, b, c, d)
+ FROM t1
+ ORDER BY b
+} [concat $z100 $z100 $z100]
+
+# Test offsets in WITHOUT ROWID table t2.
+do_execsql_test func6-200 {
+ SELECT offrec( sqlite_offset(y), x, y ) FROM t2 ORDER BY x
+} $z100
+
+finish_test
diff --git a/test/hook.test b/test/hook.test
index 9ba220cded..1c9145baef 100644
--- a/test/hook.test
+++ b/test/hook.test
@@ -906,5 +906,56 @@ do_preupdate_test 10.3 {
DELETE FROM t3 WHERE b=1
} {DELETE main t3 1 1 0 {} 1}
+#-------------------------------------------------------------------------
+# Test that the "update" hook is not fired for operations on the
+# sqlite_stat1 table performed by ANALYZE, even if a pre-update hook is
+# registered.
+ifcapable analyze {
+ reset_db
+ do_execsql_test 11.1 {
+ CREATE TABLE t1(a, b);
+ CREATE INDEX idx1 ON t1(a);
+ CREATE INDEX idx2 ON t1(b);
+
+ INSERT INTO t1 VALUES(1, 2);
+ INSERT INTO t1 VALUES(3, 4);
+ INSERT INTO t1 VALUES(5, 6);
+ INSERT INTO t1 VALUES(7, 8);
+ }
+
+ db preupdate hook preupdate_cb
+ db update_hook update_cb
+
+ proc preupdate_cb {args} { lappend ::res "preupdate" $args }
+ proc update_cb {args} { lappend ::res "update" $args }
+
+ set ::res [list]
+ do_test 11.2 {
+ execsql ANALYZE
+ set ::res
+ } [list {*}{
+ preupdate {INSERT main sqlite_stat1 1 1}
+ preupdate {INSERT main sqlite_stat1 2 2}
+ }]
+
+ do_execsql_test 11.3 {
+ INSERT INTO t1 VALUES(9, 10);
+ INSERT INTO t1 VALUES(11, 12);
+ INSERT INTO t1 VALUES(13, 14);
+ INSERT INTO t1 VALUES(15, 16);
+ }
+
+ set ::res [list]
+ do_test 11.4 {
+ execsql ANALYZE
+ set ::res
+ } [list {*}{
+ preupdate {DELETE main sqlite_stat1 1 1}
+ preupdate {DELETE main sqlite_stat1 2 2}
+ preupdate {INSERT main sqlite_stat1 1 1}
+ preupdate {INSERT main sqlite_stat1 2 2}
+ }]
+}
+
finish_test
diff --git a/test/icu.test b/test/icu.test
index 743bcfaea1..4c4e6d14ec 100644
--- a/test/icu.test
+++ b/test/icu.test
@@ -15,7 +15,7 @@
set testdir [file dirname $argv0]
source $testdir/tester.tcl
-ifcapable !icu {
+ifcapable !icu&&!icu_collations {
finish_test
return
}
@@ -35,54 +35,57 @@ proc test_expr {name settings expr result} {
} $settings $expr] $result
}
-# Tests of the REGEXP operator.
-#
-test_expr icu-1.1 {i1='hello'} {i1 REGEXP 'hello'} 1
-test_expr icu-1.2 {i1='hello'} {i1 REGEXP '.ello'} 1
-test_expr icu-1.3 {i1='hello'} {i1 REGEXP '.ell'} 0
-test_expr icu-1.4 {i1='hello'} {i1 REGEXP '.ell.*'} 1
-test_expr icu-1.5 {i1=NULL} {i1 REGEXP '.ell.*'} {}
+ifcapable icu {
-# Some non-ascii characters with defined case mappings
-#
-set ::EGRAVE "\xC8"
-set ::egrave "\xE8"
+ # Tests of the REGEXP operator.
+ #
+ test_expr icu-1.1 {i1='hello'} {i1 REGEXP 'hello'} 1
+ test_expr icu-1.2 {i1='hello'} {i1 REGEXP '.ello'} 1
+ test_expr icu-1.3 {i1='hello'} {i1 REGEXP '.ell'} 0
+ test_expr icu-1.4 {i1='hello'} {i1 REGEXP '.ell.*'} 1
+ test_expr icu-1.5 {i1=NULL} {i1 REGEXP '.ell.*'} {}
-set ::OGRAVE "\xD2"
-set ::ograve "\xF2"
+ # Some non-ascii characters with defined case mappings
+ #
+ set ::EGRAVE "\xC8"
+ set ::egrave "\xE8"
-# That German letter that looks a bit like a B. The
-# upper-case version of which is "SS" (two characters).
-#
-set ::szlig "\xDF"
+ set ::OGRAVE "\xD2"
+ set ::ograve "\xF2"
-# Tests of the upper()/lower() functions.
-#
-test_expr icu-2.1 {i1='HellO WorlD'} {upper(i1)} {HELLO WORLD}
-test_expr icu-2.2 {i1='HellO WorlD'} {lower(i1)} {hello world}
-test_expr icu-2.3 {i1=$::egrave} {lower(i1)} $::egrave
-test_expr icu-2.4 {i1=$::egrave} {upper(i1)} $::EGRAVE
-test_expr icu-2.5 {i1=$::ograve} {lower(i1)} $::ograve
-test_expr icu-2.6 {i1=$::ograve} {upper(i1)} $::OGRAVE
-test_expr icu-2.3 {i1=$::EGRAVE} {lower(i1)} $::egrave
-test_expr icu-2.4 {i1=$::EGRAVE} {upper(i1)} $::EGRAVE
-test_expr icu-2.5 {i1=$::OGRAVE} {lower(i1)} $::ograve
-test_expr icu-2.6 {i1=$::OGRAVE} {upper(i1)} $::OGRAVE
+ # That German letter that looks a bit like a B. The
+ # upper-case version of which is "SS" (two characters).
+ #
+ set ::szlig "\xDF"
-test_expr icu-2.7 {i1=$::szlig} {upper(i1)} "SS"
-test_expr icu-2.8 {i1='SS'} {lower(i1)} "ss"
+ # Tests of the upper()/lower() functions.
+ #
+ test_expr icu-2.1 {i1='HellO WorlD'} {upper(i1)} {HELLO WORLD}
+ test_expr icu-2.2 {i1='HellO WorlD'} {lower(i1)} {hello world}
+ test_expr icu-2.3 {i1=$::egrave} {lower(i1)} $::egrave
+ test_expr icu-2.4 {i1=$::egrave} {upper(i1)} $::EGRAVE
+ test_expr icu-2.5 {i1=$::ograve} {lower(i1)} $::ograve
+ test_expr icu-2.6 {i1=$::ograve} {upper(i1)} $::OGRAVE
+ test_expr icu-2.3 {i1=$::EGRAVE} {lower(i1)} $::egrave
+ test_expr icu-2.4 {i1=$::EGRAVE} {upper(i1)} $::EGRAVE
+ test_expr icu-2.5 {i1=$::OGRAVE} {lower(i1)} $::ograve
+ test_expr icu-2.6 {i1=$::OGRAVE} {upper(i1)} $::OGRAVE
-do_execsql_test icu-2.9 {
- SELECT upper(char(0xfb04,0xfb04,0xfb04,0xfb04));
-} {FFLFFLFFLFFL}
+ test_expr icu-2.7 {i1=$::szlig} {upper(i1)} "SS"
+ test_expr icu-2.8 {i1='SS'} {lower(i1)} "ss"
-# In turkish (locale="tr_TR"), the lower case version of I
-# is "small dotless i" (code point 0x131 (decimal 305)).
-#
-set ::small_dotless_i "\u0131"
-test_expr icu-3.1 {i1='I'} {lower(i1)} "i"
-test_expr icu-3.2 {i1='I'} {lower(i1, 'tr_tr')} $::small_dotless_i
-test_expr icu-3.3 {i1='I'} {lower(i1, 'en_AU')} "i"
+ do_execsql_test icu-2.9 {
+ SELECT upper(char(0xfb04,0xfb04,0xfb04,0xfb04));
+ } {FFLFFLFFLFFL}
+
+ # In turkish (locale="tr_TR"), the lower case version of I
+ # is "small dotless i" (code point 0x131 (decimal 305)).
+ #
+ set ::small_dotless_i "\u0131"
+ test_expr icu-3.1 {i1='I'} {lower(i1)} "i"
+ test_expr icu-3.2 {i1='I'} {lower(i1, 'tr_tr')} $::small_dotless_i
+ test_expr icu-3.3 {i1='I'} {lower(i1, 'en_AU')} "i"
+}
#--------------------------------------------------------------------
# Test the collation sequence function.
@@ -124,22 +127,23 @@ do_test icu-4.3 {
#
# http://src.chromium.org/viewvc/chrome/trunk/src/third_party/sqlite/icu-regexp.patch?revision=34807&view=markup
#
-do_catchsql_test icu-5.1 { SELECT regexp('a[abc]c.*', 'abc') } {0 1}
-do_catchsql_test icu-5.2 {
- SELECT regexp('a[abc]c.*')
-} {1 {wrong number of arguments to function regexp()}}
-do_catchsql_test icu-5.3 {
- SELECT regexp('a[abc]c.*', 'abc', 'c')
-} {1 {wrong number of arguments to function regexp()}}
-do_catchsql_test icu-5.4 {
- SELECT 'abc' REGEXP 'a[abc]c.*'
-} {0 1}
-do_catchsql_test icu-5.4 { SELECT 'abc' REGEXP } {1 {near " ": syntax error}}
-do_catchsql_test icu-5.5 { SELECT 'abc' REGEXP, 1 } {1 {near ",": syntax error}}
-
-
-do_malloc_test icu-6.10 -sqlbody {
- SELECT upper(char(0xfb04,0xdf,0xfb04,0xe8,0xfb04));
+ifcapable icu {
+ do_catchsql_test icu-5.1 { SELECT regexp('a[abc]c.*', 'abc') } {0 1}
+ do_catchsql_test icu-5.2 {
+ SELECT regexp('a[abc]c.*')
+ } {1 {wrong number of arguments to function regexp()}}
+ do_catchsql_test icu-5.3 {
+ SELECT regexp('a[abc]c.*', 'abc', 'c')
+ } {1 {wrong number of arguments to function regexp()}}
+ do_catchsql_test icu-5.4 {
+ SELECT 'abc' REGEXP 'a[abc]c.*'
+ } {0 1}
+ do_catchsql_test icu-5.5 {SELECT 'abc' REGEXP } {1 {incomplete input}}
+ do_catchsql_test icu-5.6 {SELECT 'abc' REGEXP, 1} {1 {near ",": syntax error}}
+
+ do_malloc_test icu-6.10 -sqlbody {
+ SELECT upper(char(0xfb04,0xdf,0xfb04,0xe8,0xfb04));
+ }
}
finish_test
diff --git a/test/indexexpr1.test b/test/indexexpr1.test
index 0e24c8066f..e93dcc0cd1 100644
--- a/test/indexexpr1.test
+++ b/test/indexexpr1.test
@@ -401,5 +401,49 @@ do_execsql_test indexexpr1-1430 {
SELECT abs(15+3) IN (SELECT 17 UNION ALL SELECT 18) FROM t1;
} {1 1}
+# 2018-01-02 ticket https://sqlite.org/src/info/dc3f932f5a147771
+# A REPLACE into a table that uses an index on an expression causes
+# an assertion fault. Problem discovered by OSSFuzz.
+#
+do_execsql_test indexexpr1-1500 {
+ CREATE TABLE t1500(a INT PRIMARY KEY, b INT UNIQUE);
+ CREATE INDEX t1500ab ON t1500(a*b);
+ INSERT INTO t1500(a,b) VALUES(1,2);
+ REPLACE INTO t1500(a,b) VALUES(1,3); -- formerly caused assertion fault
+ SELECT * FROM t1500;
+} {1 3}
+
+# 2018-01-03 OSSFuzz discovers another test case for the same problem
+# above.
+#
+do_execsql_test indexexpr-1510 {
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1(a PRIMARY KEY,b UNIQUE);
+ REPLACE INTO t1 VALUES(2, 1);
+ REPLACE INTO t1 SELECT 6,1;
+ CREATE INDEX t1aa ON t1(a-a);
+ REPLACE INTO t1 SELECT a, randomblob(a) FROM t1
+} {}
+
+# 2018-01-31 https://www.sqlite.org/src/tktview/343634942dd54ab57b702411
+# When an index on an expression depends on the string representation of
+# a numeric table column, trouble can arise since there are multiple
+# string that can map to the same numeric value. (Ex: 123, 0123, 000123).
+#
+do_execsql_test indexexpr-1600 {
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1 (a INTEGER, b);
+ CREATE INDEX idx1 ON t1 (lower(a));
+ INSERT INTO t1 VALUES('0001234',3);
+ PRAGMA integrity_check;
+} {ok}
+do_execsql_test indexexpr-1610 {
+ INSERT INTO t1 VALUES('1234',0),('001234',2),('01234',1);
+ SELECT b FROM t1 WHERE lower(a)='1234' ORDER BY +b;
+} {0 1 2 3}
+do_execsql_test indexexpr-1620 {
+ SELECT b FROM t1 WHERE lower(a)='01234' ORDER BY +b;
+} {}
+
finish_test
diff --git a/test/ioerr.test b/test/ioerr.test
index e59647fe50..f42beef5b4 100644
--- a/test/ioerr.test
+++ b/test/ioerr.test
@@ -172,7 +172,7 @@ ifcapable crashtest&&attach {
# These tests can't be run on windows because the windows version of
# SQLite holds a mandatory exclusive lock on journal files it has open.
#
-if {$tcl_platform(platform)!="windows"} {
+if {$tcl_platform(platform)!="windows" && ![atomic_batch_write test.db]} {
do_ioerr_test ioerr-7 -tclprep {
db close
sqlite3 db2 test2.db
@@ -211,7 +211,7 @@ do_ioerr_test ioerr-8 -ckrefcount true -tclprep {
# For test coverage: Cause an IO error whilst reading the master-journal
# name from a journal file.
-if {$tcl_platform(platform)=="unix"} {
+if {$tcl_platform(platform)=="unix" && [atomic_batch_write test.db]==0} {
do_ioerr_test ioerr-9 -ckrefcount true -tclprep {
execsql {
CREATE TABLE t1(a,b,c);
diff --git a/test/istrue.test b/test/istrue.test
new file mode 100644
index 0000000000..250f1f9d5d
--- /dev/null
+++ b/test/istrue.test
@@ -0,0 +1,126 @@
+# 2018-02-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. The
+# focus of this file is testing expressions of the form
+#
+# x IS TRUE
+# x IS FALSE
+# x IS NOT TRUE
+# x IS NOT FALSE
+#
+# Tests are also included for the use of TRUE and FALSE as
+# literal values.
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+do_execsql_test istrue-100 {
+ CREATE TABLE t1(x INTEGER PRIMARY KEY, y BOOLEAN);
+ INSERT INTO t1 VALUES(1, true),(2, false),(3, null);
+ SELECT x FROM t1 WHERE y IS TRUE;
+} {1}
+do_execsql_test istrue-110 {
+ SELECT x FROM t1 WHERE y IS FALSE;
+} {2}
+do_execsql_test istrue-120 {
+ SELECT x FROM t1 WHERE y IS NULL;
+} {3}
+do_execsql_test istrue-130 {
+ SELECT x FROM t1 WHERE y IS NOT TRUE;
+} {2 3}
+do_execsql_test istrue-140 {
+ SELECT x FROM t1 WHERE y IS NOT FALSE;
+} {1 3}
+do_execsql_test istrue-150 {
+ SELECT x FROM t1 WHERE y IS NOT NULL;
+} {1 2}
+unset -nocomplain X
+set X 9
+do_execsql_test istrue-160 {
+ SELECT x FROM t1 WHERE y IS TRUE OR (8==$X)
+} {1}
+do_execsql_test istrue-170 {
+ SELECT x FROM t1 WHERE y IS FALSE OR (8==$X)
+} {2}
+do_execsql_test istrue-180 {
+ SELECT x FROM t1 WHERE y IS NULL OR (8==$X);
+} {3}
+do_execsql_test istrue-190 {
+ SELECT x FROM t1 WHERE y IS NOT TRUE OR (8==$X);
+} {2 3}
+do_execsql_test istrue-200 {
+ SELECT x FROM t1 WHERE y IS NOT FALSE OR (8==$X);
+} {1 3}
+do_execsql_test istrue-210 {
+ SELECT x FROM t1 WHERE y IS NOT NULL OR (8==$X);
+} {1 2}
+
+do_execsql_test istrue-300 {
+ SELECT x,
+ y IS TRUE, y IS FALSE, y is NULL,
+ y IS NOT TRUE, y IS NOT FALSE, y IS NOT NULL, '|'
+ FROM t1 ORDER BY x;
+} {1 1 0 0 0 1 1 | 2 0 1 0 1 0 1 | 3 0 0 1 1 1 0 |}
+
+do_execsql_test istrue-400 {
+ SELECT x FROM t1 WHERE true;
+} {1 2 3}
+do_execsql_test istrue-410 {
+ SELECT x FROM t1 WHERE false;
+} {}
+
+do_execsql_test istrue-500 {
+ CREATE TABLE t2(
+ a INTEGER PRIMARY KEY,
+ b BOOLEAN DEFAULT true,
+ c BOOLEAN DEFAULT(true),
+ d BOOLEAN DEFAULT false,
+ e BOOLEAN DEFAULT(false)
+ );
+ INSERT INTO t2 DEFAULT VALUES;
+ SELECT * FROM t2;
+} {1 1 1 0 0}
+do_execsql_test istrue-510 {
+ DROP TABLE t2;
+ CREATE TABLE t2(
+ a INTEGER PRIMARY KEY,
+ b BOOLEAN DEFAULT(not true),
+ c BOOLEAN DEFAULT(not false)
+ );
+ INSERT INTO t2(a) VALUES(99);
+ SELECT * FROM t2;
+} {99 0 1}
+do_execsql_test istrue-520 {
+ DROP TABLE t2;
+ CREATE TABLE t2(
+ a INTEGER PRIMARY KEY,
+ b BOOLEAN CHECK(b IS TRUE),
+ c BOOLEAN CHECK(c IS FALSE),
+ d BOOLEAN CHECK(d IS NOT TRUE),
+ e BOOLEAN CHECK(e IS NOT FALSE)
+ );
+ INSERT INTO t2 VALUES(1,true,false,null,null);
+ SELECT * FROM t2;
+} {1 1 0 {} {}}
+do_catchsql_test istrue-521 {
+ INSERT INTO t2 VALUES(2,false,false,null,null);
+} {1 {CHECK constraint failed: t2}}
+do_catchsql_test istrue-522 {
+ INSERT INTO t2 VALUES(2,true,true,null,null);
+} {1 {CHECK constraint failed: t2}}
+do_catchsql_test istrue-523 {
+ INSERT INTO t2 VALUES(2,true,false,true,null);
+} {1 {CHECK constraint failed: t2}}
+do_catchsql_test istrue-524 {
+ INSERT INTO t2 VALUES(2,true,false,null,false);
+} {1 {CHECK constraint failed: t2}}
+
+finish_test
diff --git a/test/join2.test b/test/join2.test
index 9372e770c3..256846ac91 100644
--- a/test/join2.test
+++ b/test/join2.test
@@ -92,4 +92,177 @@ do_catchsql_test 2.2 {
SELECT * FROM aa JOIN cc ON (a=b) JOIN bb ON (b=c);
} {0 {one one one}}
+#-------------------------------------------------------------------------
+# Test that a problem causing where.c to overlook opportunities to
+# omit unnecessary tables from a LEFT JOIN when UNIQUE, NOT NULL column
+# that makes this possible happens to be the leftmost in its table.
+#
+reset_db
+do_execsql_test 3.0 {
+ CREATE TABLE t1(k1 INTEGER PRIMARY KEY, k2, k3);
+ CREATE TABLE t2(k2 INTEGER PRIMARY KEY, v2);
+
+ -- Prior to this problem being fixed, table t3_2 would be omitted from
+ -- the join queries below, but if t3_1 were used in its place it would
+ -- not.
+ CREATE TABLE t3_1(k3 PRIMARY KEY, v3) WITHOUT ROWID;
+ CREATE TABLE t3_2(v3, k3 PRIMARY KEY) WITHOUT ROWID;
+}
+
+do_eqp_test 3.1 {
+ SELECT v2 FROM t1 LEFT JOIN t2 USING (k2) LEFT JOIN t3_1 USING (k3);
+} {
+ 0 0 0 {SCAN TABLE t1}
+ 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)}
+}
+
+do_eqp_test 3.2 {
+ SELECT v2 FROM t1 LEFT JOIN t2 USING (k2) LEFT JOIN t3_2 USING (k3);
+} {
+ 0 0 0 {SCAN TABLE t1}
+ 0 1 1 {SEARCH TABLE t2 USING INTEGER PRIMARY KEY (rowid=?)}
+}
+
+#-------------------------------------------------------------------------
+# Test that tables other than the rightmost can be omitted from a
+# LEFT JOIN query.
+#
+do_execsql_test 4.0 {
+ CREATE TABLE c1(k INTEGER PRIMARY KEY, v1);
+ CREATE TABLE c2(k INTEGER PRIMARY KEY, v2);
+ CREATE TABLE c3(k INTEGER PRIMARY KEY, v3);
+
+ INSERT INTO c1 VALUES(1, 2);
+ INSERT INTO c2 VALUES(2, 3);
+ INSERT INTO c3 VALUES(3, 'v3');
+
+ INSERT INTO c1 VALUES(111, 1112);
+ INSERT INTO c2 VALUES(112, 1113);
+ INSERT INTO c3 VALUES(113, 'v1113');
+}
+do_execsql_test 4.1.1 {
+ SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2);
+} {2 v3 1112 {}}
+do_execsql_test 4.1.2 {
+ SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1);
+} {2 v3 1112 {}}
+
+do_execsql_test 4.1.3 {
+ SELECT DISTINCT v1, v3 FROM c1 LEFT JOIN c2 LEFT JOIN c3 ON (c3.k=v1+1);
+} {2 v3 1112 {}}
+
+do_execsql_test 4.1.4 {
+ SELECT v1, v3 FROM c1 LEFT JOIN c2 LEFT JOIN c3 ON (c3.k=v1+1);
+} {2 v3 2 v3 1112 {} 1112 {}}
+
+do_eqp_test 4.1.5 {
+ SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2);
+} {
+ 0 0 0 {SCAN TABLE c1}
+ 0 1 1 {SEARCH TABLE c2 USING INTEGER PRIMARY KEY (rowid=?)}
+ 0 2 2 {SEARCH TABLE c3 USING INTEGER PRIMARY KEY (rowid=?)}
+}
+do_eqp_test 4.1.6 {
+ SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1);
+} {
+ 0 0 0 {SCAN TABLE c1}
+ 0 1 2 {SEARCH TABLE c3 USING INTEGER PRIMARY KEY (rowid=?)}
+}
+
+do_execsql_test 4.2.0 {
+ DROP TABLE c1;
+ DROP TABLE c2;
+ DROP TABLE c3;
+ CREATE TABLE c1(k UNIQUE, v1);
+ CREATE TABLE c2(k UNIQUE, v2);
+ CREATE TABLE c3(k UNIQUE, v3);
+
+ INSERT INTO c1 VALUES(1, 2);
+ INSERT INTO c2 VALUES(2, 3);
+ INSERT INTO c3 VALUES(3, 'v3');
+
+ INSERT INTO c1 VALUES(111, 1112);
+ INSERT INTO c2 VALUES(112, 1113);
+ INSERT INTO c3 VALUES(113, 'v1113');
+}
+do_execsql_test 4.2.1 {
+ SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2);
+} {2 v3 1112 {}}
+do_execsql_test 4.2.2 {
+ SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1);
+} {2 v3 1112 {}}
+
+do_execsql_test 4.2.3 {
+ SELECT DISTINCT v1, v3 FROM c1 LEFT JOIN c2 LEFT JOIN c3 ON (c3.k=v1+1);
+} {2 v3 1112 {}}
+
+do_execsql_test 4.2.4 {
+ SELECT v1, v3 FROM c1 LEFT JOIN c2 LEFT JOIN c3 ON (c3.k=v1+1);
+} {2 v3 2 v3 1112 {} 1112 {}}
+
+do_eqp_test 4.2.5 {
+ SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v2);
+} {
+ 0 0 0 {SCAN TABLE c1}
+ 0 1 1 {SEARCH TABLE c2 USING INDEX sqlite_autoindex_c2_1 (k=?)}
+ 0 2 2 {SEARCH TABLE c3 USING INDEX sqlite_autoindex_c3_1 (k=?)}
+}
+do_eqp_test 4.2.6 {
+ SELECT v1, v3 FROM c1 LEFT JOIN c2 ON (c2.k=v1) LEFT JOIN c3 ON (c3.k=v1+1);
+} {
+ 0 0 0 {SCAN TABLE c1}
+ 0 1 2 {SEARCH TABLE c3 USING INDEX sqlite_autoindex_c3_1 (k=?)}
+}
+
+# 2017-11-23 (Thanksgiving day)
+# OSSFuzz found an assertion fault in the new LEFT JOIN eliminator code.
+#
+do_execsql_test 4.3.0 {
+ DROP TABLE IF EXISTS t1;
+ DROP TABLE IF EXISTS t2;
+ CREATE TABLE t1(x PRIMARY KEY) WITHOUT ROWID;
+ CREATE TABLE t2(x);
+ SELECT a.x
+ FROM t1 AS a
+ LEFT JOIN t1 AS b ON (a.x=b.x)
+ LEFT JOIN t2 AS c ON (a.x=c.x);
+} {}
+do_execsql_test 4.3.1 {
+ WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<10)
+ INSERT INTO t1(x) SELECT x FROM c;
+ INSERT INTO t2(x) SELECT x+9 FROM t1;
+ SELECT a.x, c.x
+ FROM t1 AS a
+ LEFT JOIN t1 AS b ON (a.x=b.x)
+ LEFT JOIN t2 AS c ON (a.x=c.x);
+} {1 {} 2 {} 3 {} 4 {} 5 {} 6 {} 7 {} 8 {} 9 {} 10 10}
+
+do_execsql_test 5.0 {
+ CREATE TABLE s1 (a INTEGER PRIMARY KEY);
+ CREATE TABLE s2 (a INTEGER PRIMARY KEY);
+ CREATE TABLE s3 (a INTEGER);
+ CREATE UNIQUE INDEX ndx on s3(a);
+}
+do_eqp_test 5.1 {
+ SELECT s1.a FROM s1 left join s2 using (a);
+} {
+ 0 0 0 {SCAN TABLE s1}
+}
+do_eqp_test 5.2 {
+ SELECT s1.a FROM s1 left join s3 using (a);
+} {
+ 0 0 0 {SCAN TABLE s1}
+}
+
+do_execsql_test 6.0 {
+ CREATE TABLE u1(a INTEGER PRIMARY KEY, b, c);
+ CREATE TABLE u2(a INTEGER PRIMARY KEY, b, c);
+ CREATE INDEX u1ab ON u1(b, c);
+}
+do_eqp_test 6.1 {
+ SELECT u2.* FROM u2 LEFT JOIN u1 ON( u1.a=u2.a AND u1.b=u2.b AND u1.c=u2.c );
+} {
+ 0 0 0 {SCAN TABLE u2}
+}
+
finish_test
diff --git a/test/journal1.test b/test/journal1.test
index c89dd2b4c9..bcbafe30f6 100644
--- a/test/journal1.test
+++ b/test/journal1.test
@@ -22,7 +22,12 @@ source $testdir/tester.tcl
# These tests will not work on windows because windows uses
# manditory file locking which breaks the copy_file command.
#
-if {$tcl_platform(platform)=="windows"} {
+# Or with atomic_batch_write systems, as journal files are
+# not created.
+#
+if {$tcl_platform(platform)=="windows"
+ || [atomic_batch_write test.db]
+} {
finish_test
return
}
diff --git a/test/journal3.test b/test/journal3.test
index 939cc27c70..b907352329 100644
--- a/test/journal3.test
+++ b/test/journal3.test
@@ -20,7 +20,9 @@ source $testdir/malloc_common.tcl
# If a connection is required to create a journal file, it creates it with
# the same file-system permissions as the database file itself. Test this.
#
-if {$::tcl_platform(platform) == "unix"} {
+if {$::tcl_platform(platform) == "unix"
+ && [atomic_batch_write test.db]==0
+} {
# Changed on 2012-02-13: umask is deliberately ignored for -wal, -journal,
# and -shm files.
diff --git a/test/jrnlmode.test b/test/jrnlmode.test
index 2ba56f2b00..3112f6184e 100644
--- a/test/jrnlmode.test
+++ b/test/jrnlmode.test
@@ -302,6 +302,7 @@ ifcapable autovacuum&&pragma {
# The following test caes, jrnlmode-5.*, test the journal_size_limit
# pragma.
ifcapable pragma {
+if {[atomic_batch_write test.db]==0} {
db close
forcedelete test.db test2.db test3.db
sqlite3 db test.db
@@ -454,8 +455,10 @@ ifcapable pragma {
list [file exists test.db-journal] [file size test.db-journal]
} {1 0}
}
+}
ifcapable pragma {
+if {[atomic_batch_write test.db]==0} {
# These tests are not run as part of the "journaltest" permutation,
# as the test_journal.c layer is incompatible with in-memory journaling.
if {[permutation] ne "journaltest"} {
@@ -507,6 +510,7 @@ ifcapable pragma {
} {0}
}
}
+}
ifcapable pragma {
catch { db close }
diff --git a/test/jrnlmode2.test b/test/jrnlmode2.test
index 6ea87d704b..6cc54dc5df 100644
--- a/test/jrnlmode2.test
+++ b/test/jrnlmode2.test
@@ -18,6 +18,11 @@ ifcapable {!pager_pragmas} {
return
}
+if {[atomic_batch_write test.db]} {
+ finish_test
+ return
+}
+
#-------------------------------------------------------------------------
# The tests in this file check that the following two bugs (both now fixed)
# do not reappear.
diff --git a/test/json101.test b/test/json101.test
index 4bfcc2d80d..ac4e11ebc2 100644
--- a/test/json101.test
+++ b/test/json101.test
@@ -759,7 +759,30 @@ do_execsql_test json-12.120 {
FROM t12;
} {0}
-
+# 2018-01-26
+# ticket https://www.sqlite.org/src/tktview/80177f0c226ff54f6ddd41
+# Make sure the query planner knows about the arguments to table-valued functions.
+#
+do_execsql_test json-13.100 {
+ DROP TABLE IF EXISTS t1;
+ DROP TABLE IF EXISTS t2;
+ CREATE TABLE t1(id, json);
+ INSERT INTO t1(id,json) VALUES(1,'{"items":[3,5]}');
+ CREATE TABLE t2(id, json);
+ INSERT INTO t2(id,json) VALUES(2,'{"value":2}');
+ INSERT INTO t2(id,json) VALUES(3,'{"value":3}');
+ INSERT INTO t2(id,json) VALUES(4,'{"value":4}');
+ INSERT INTO t2(id,json) VALUES(5,'{"value":5}');
+ INSERT INTO t2(id,json) VALUES(6,'{"value":6}');
+ SELECT * FROM t1 CROSS JOIN t2
+ WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z
+ WHERE Z.value==t2.id);
+} {1 {{"items":[3,5]}} 3 {{"value":3}} 1 {{"items":[3,5]}} 5 {{"value":5}}}
+do_execsql_test json-13.110 {
+ SELECT * FROM t2 CROSS JOIN t1
+ WHERE EXISTS(SELECT 1 FROM json_each(t1.json,'$.items') AS Z
+ WHERE Z.value==t2.id);
+} {3 {{"value":3}} 1 {{"items":[3,5]}} 5 {{"value":5}} 1 {{"items":[3,5]}}}
finish_test
diff --git a/test/limit2.test b/test/limit2.test
index f6eaefb8aa..83c67506f5 100644
--- a/test/limit2.test
+++ b/test/limit2.test
@@ -150,4 +150,21 @@ do_execsql_test 502 {
SELECT j FROM t502 WHERE i IN (1,2,3,4,5) ORDER BY j LIMIT 3;
} {1 3 4}
+# Ticket https://www.sqlite.org/src/info/123c9ba32130a6c9 2017-12-13
+# Incorrect result when an idnex is used for an ordered join.
+#
+# This test case is in the limit2.test module because the problem was first
+# exposed by check-in https://www.sqlite.org/src/info/559733b09e which
+# implemented the ORDER BY LIMIT optimization that limit2.test strives to
+# test.
+#
+do_execsql_test 600 {
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1(a, b); INSERT INTO t1 VALUES(1,2);
+ DROP TABLE IF EXISTS t2;
+ CREATE TABLE t2(x, y); INSERT INTO t2 VALUES(1,3);
+ CREATE INDEX t1ab ON t1(a,b);
+ SELECT y FROM t1, t2 WHERE a=x AND b<=y ORDER BY b DESC;
+} {3}
+
finish_test
diff --git a/test/lock4.test b/test/lock4.test
index b0b1c74fbe..58dd206997 100644
--- a/test/lock4.test
+++ b/test/lock4.test
@@ -17,6 +17,14 @@
set testdir [file dirname $argv0]
source $testdir/tester.tcl
+if {[atomic_batch_write test.db]} {
+ # This test uses two processes, one of which blocks until the other
+ # creates a *-journal file. Which doesn't work if atomic writes are
+ # available.
+ finish_test
+ return
+}
+
do_not_use_codec
# Initialize the test.db database so that it is non-empty
diff --git a/test/main.test b/test/main.test
index 9346cf6ced..13a385b7c4 100644
--- a/test/main.test
+++ b/test/main.test
@@ -434,7 +434,7 @@ do_test main-3.2.28 {
} {0 246}
do_test main-3.2.29 {
catchsql {select 123/}
-} {1 {near "/": syntax error}}
+} {1 {incomplete input}}
do_test main-3.2.30 {
catchsql {select 123--5}
} {0 123}
@@ -467,7 +467,7 @@ do_test main-3.4 {
do_test main-3.5 {
set v [catch {execsql {create}} msg]
lappend v $msg
-} {1 {near "create": syntax error}}
+} {1 {incomplete input}}
do_test main-3.6 {
catchsql {SELECT 'abc' + #9}
} {1 {near "#9": syntax error}}
diff --git a/test/malloc.test b/test/malloc.test
index dbf4699b27..5e82e8028b 100644
--- a/test/malloc.test
+++ b/test/malloc.test
@@ -329,7 +329,7 @@ ifcapable crashtest&&attach {
}
}
-if {$tcl_platform(platform)!="windows"} {
+if {$tcl_platform(platform)!="windows" && [atomic_batch_write test.db]==0} {
do_malloc_test 14 -tclprep {
catch {db close}
sqlite3 db2 test2.db
diff --git a/test/malloc3.test b/test/malloc3.test
index f4a6c3bbe9..b497ab66e9 100644
--- a/test/malloc3.test
+++ b/test/malloc3.test
@@ -27,6 +27,17 @@ if {!$MEMDEBUG} {
return
}
+# Do not run these tests if F2FS batch writes are supported. In this case,
+# it is possible for a single DML statement in an implicit transaction
+# to fail with SQLITE_NOMEM, but for the transaction to still end up
+# committed to disk. Which confuses the tests in this module.
+#
+if {[atomic_batch_write test.db]} {
+ puts "Skipping malloc3 tests: atomic-batch support"
+ finish_test
+ return
+}
+
# Do not run these tests with an in-memory journal.
#
diff --git a/test/misc1.test b/test/misc1.test
index 2acfa5c2dd..05b1b1980f 100644
--- a/test/misc1.test
+++ b/test/misc1.test
@@ -722,4 +722,24 @@ do_execsql_test misc1-26.0 {
SELECT randomblob(min(max(coalesce(EXISTS (SELECT 1 FROM ( SELECT (SELECT 2147483647) NOT IN (SELECT 2147483649 UNION ALL SELECT DISTINCT -1) IN (SELECT 2147483649), 'fault', (SELECT ALL -1 INTERSECT SELECT 'experiments') IN (SELECT ALL 56.1 ORDER BY 'experiments' DESC) FROM (SELECT DISTINCT 2147483648, 'hardware' UNION ALL SELECT -2147483648, 'experiments' ORDER BY 2147483648 LIMIT 1 OFFSET 123456789.1234567899) GROUP BY (SELECT ALL 0 INTERSECT SELECT 'in') IN (SELECT DISTINCT 'experiments' ORDER BY zeroblob(1000) LIMIT 56.1 OFFSET -456) HAVING EXISTS (SELECT 'fault' EXCEPT SELECT DISTINCT 56.1) UNION SELECT 'The', 'The', 2147483649 UNION ALL SELECT DISTINCT 'hardware', 'first', 'experiments' ORDER BY 'hardware' LIMIT 123456789.1234567899 OFFSET -2147483647)) NOT IN (SELECT (SELECT DISTINCT (SELECT 'The') FROM abc ORDER BY EXISTS (SELECT -1 INTERSECT SELECT ALL NULL) ASC) IN (SELECT DISTINCT EXISTS (SELECT ALL 123456789.1234567899 ORDER BY 1 ASC, NULL DESC) FROM sqlite_master INTERSECT SELECT 456)), (SELECT ALL 'injection' UNION ALL SELECT ALL (SELECT DISTINCT 'first' UNION SELECT DISTINCT 'The') FROM (SELECT 456, 'in', 2147483649))),1), 500)), 'first', EXISTS (SELECT DISTINCT 456 FROM abc ORDER BY 'experiments' DESC) FROM abc;
} {}
+# 2017-12-29
+#
+# The following behaviors (duplicate column names on an INSERT or UPDATE)
+# are undocumented. These tests are added to ensure that historical behavior
+# does not change accidentally.
+#
+# For duplication columns on an INSERT, the first value is used.
+# For duplication columns on an UPDATE, the last value is used.
+#
+do_execsql_test misc1-27.0 {
+ CREATE TABLE dup1(a,b,c);
+ INSERT INTO dup1(a,b,c,a,b,c) VALUES(1,2,3,4,5,6);
+ SELECT a,b,c FROM dup1;
+} {1 2 3}
+do_execsql_test misc1-27.1 {
+ UPDATE dup1 SET a=7, b=8, c=9, a=10, b=11, c=12;
+ SELECT a,b,c FROM dup1;
+} {10 11 12}
+
+
finish_test
diff --git a/test/misc7.test b/test/misc7.test
index 8fd5fe7546..8df95575c1 100644
--- a/test/misc7.test
+++ b/test/misc7.test
@@ -14,6 +14,7 @@
set testdir [file dirname $argv0]
source $testdir/tester.tcl
+set testprefix misc7
if {[clang_sanitize_address]==0} {
do_test misc7-1-misuse {
@@ -42,15 +43,17 @@ do_test misc7-4 {
# Try to open a file with a directory where its journal file should be.
#
-do_test misc7-5 {
- delete_file mydir
- file mkdir mydir-journal
- sqlite3 db2 ./mydir
- catchsql {
- CREATE TABLE abc(a, b, c);
- } db2
-} {1 {unable to open database file}}
-db2 close
+if {[atomic_batch_write test.db]==0} {
+ do_test misc7-5 {
+ delete_file mydir
+ file mkdir mydir-journal
+ sqlite3 db2 ./mydir
+ catchsql {
+ CREATE TABLE abc(a, b, c);
+ } db2
+ } {1 {unable to open database file}}
+ db2 close
+}
#--------------------------------------------------------------------
# The following tests, misc7-6.* test the libraries behaviour when
@@ -518,8 +521,43 @@ do_test misc7-22.3 {
do_test misc7-22.4 {
sqlite3_extended_errcode db
} SQLITE_READONLY_ROLLBACK
-
-db close
+catch { db close }
forcedelete test.db
+if {$::tcl_platform(platform)=="unix"
+ && [atomic_batch_write test.db]==0
+} {
+ reset_db
+ do_execsql_test 23.0 {
+ CREATE TABLE t1(x, y);
+ INSERT INTO t1 VALUES(1, 2);
+ }
+
+ do_test 23.1 {
+ db close
+ forcedelete tst
+ file mkdir tst
+ forcecopy test.db tst/test.db
+ file attributes tst -permissions r-xr-xr-x
+ } {}
+
+ sqlite3 db tst/test.db
+ do_execsql_test 23.2 {
+ SELECT * FROM t1;
+ } {1 2}
+
+ do_catchsql_test 23.3 {
+ INSERT INTO t1 VALUES(3, 4);
+ } {1 {attempt to write a readonly database}}
+
+ do_test 23.4 {
+ sqlite3_extended_errcode db
+ } {SQLITE_READONLY_DIRECTORY}
+
+ do_test 23.5 {
+ db close
+ forcedelete tst
+ } {}
+}
+
finish_test
diff --git a/test/mjournal.test b/test/mjournal.test
index aab2c08b51..539ebc6946 100644
--- a/test/mjournal.test
+++ b/test/mjournal.test
@@ -15,6 +15,11 @@ set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix mjournal
+if {[permutation]=="inmemory_journal"} {
+ finish_test
+ return
+}
+
# Test that nothing bad happens if a journal file contains a pointer to
# a master journal file that does not have a "-" in the name. At one point
# this was causing a segfault on unix.
@@ -79,5 +84,80 @@ do_execsql_test 1.6 {
SELECT * FROM t1;
}
-
+#-------------------------------------------------------------------------
+# Check that master journals are not created if the transaction involves
+# multiple temp files.
+#
+db close
+testvfs tvfs
+tvfs filter xOpen
+tvfs script open_cb
+set ::open ""
+proc open_cb {method file arglist} {
+ lappend ::open $file
+}
+
+proc contains_mj {} {
+ foreach f $::open {
+ set t [file tail $f]
+ if {[string match *mj* $t]} { return 1 }
+ }
+ return 0
+}
+
+# Like [do_execsql_test], except that a boolean indicating whether or
+# not a master journal file was opened ([file tail] contains "mj") or
+# not. Example:
+#
+# do_hasmj_test 1.0 { SELECT 'a', 'b' } {0 a b}
+#
+proc do_hasmj_test {tn sql expected} {
+ set ::open [list]
+ uplevel [list do_test $tn [subst -nocommands {
+ set res [execsql "$sql"]
+ concat [contains_mj] [set res]
+ }] [list {*}$expected]]
+}
+
+forcedelete test.db
+forcedelete test.db2
+forcedelete test.db3
+sqlite3 db test.db -vfs tvfs
+
+do_execsql_test 2.0 {
+ ATTACH 'test.db2' AS dbfile;
+ ATTACH '' AS dbtemp;
+ ATTACH ':memory:' AS dbmem;
+
+ CREATE TABLE t1(x);
+ CREATE TABLE dbfile.t2(x);
+ CREATE TABLE dbtemp.t3(x);
+ CREATE TABLE dbmem.t4(x);
+}
+
+# Two real files.
+do_hasmj_test 2.1 {
+ BEGIN;
+ INSERT INTO t1 VALUES(1);
+ INSERT INTO t2 VALUES(1);
+ COMMIT;
+} {1}
+
+# One real, one temp file.
+do_hasmj_test 2.2 {
+ BEGIN;
+ INSERT INTO t1 VALUES(1);
+ INSERT INTO t3 VALUES(1);
+ COMMIT;
+} {0}
+
+# One file, one :memory: db.
+do_hasmj_test 2.3 {
+ BEGIN;
+ INSERT INTO t1 VALUES(1);
+ INSERT INTO t4 VALUES(1);
+ COMMIT;
+} {0}
+
finish_test
+
diff --git a/test/nockpt.test b/test/nockpt.test
index bd3953f1ee..6da3313ad8 100644
--- a/test/nockpt.test
+++ b/test/nockpt.test
@@ -61,6 +61,86 @@ do_test 1.14 { sqlite3_db_config db NO_CKPT_ON_CLOSE 1 } {1}
do_execsql_test 1.14 { PRAGMA main.journal_mode = delete } {delete}
do_test 1.15 { file exists test.db-wal } {0}
+if {$::tcl_platform(platform)!="windows"} {
+#-------------------------------------------------------------------------
+# Test an unusual scenario:
+#
+# 1. A wal mode db is opened and written. Then sqlite3_close_v2() used
+# to close the db handle while there is still an unfinalized
+# statement (so the db handle stays open).
+#
+# 2. The db, wal and *-shm files are deleted from the file system.
+#
+# 3. Another connection creates a new wal mode db at the same file-system
+# location as the previous one.
+#
+# 4. The statement left unfinalized in (1) is finalized.
+#
+# The test is to ensure that the connection left open in step (1) does
+# not try to delete the wal file from the file-system as part of step
+# 4.
+#
+reset_db
+db close
+
+# Open a connection on a wal database. Write to it a bit. Then prepare
+# a statement and call sqlite3_close_v2() (so that the statement handle
+# holds the db connection open).
+#
+set ::db1 [sqlite3_open_v2 test.db SQLITE_OPEN_READWRITE ""]
+do_test 2.0 {
+ lindex [
+ sqlite3_exec $::db1 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t1(x PRIMARY KEY, y UNIQUE, z);
+ INSERT INTO t1 VALUES(1, 2, 3);
+ PRAGMA wal_checkpoint;
+ }] 0
+} {0}
+set ::stmt [sqlite3_prepare $::db1 "SELECT * FROM t1" -1 dummy]
+sqlite3_close_v2 $::db1
+
+# Delete the database, wal and shm files.
+#
+forcedelete test.db test.db-wal test.db-shm
+
+# Open and populate a new database file at the same file-system location
+# as the one just deleted. Contrive a partial checkpoint on it.
+#
+sqlite3 db test.db
+sqlite3 db2 test.db
+do_execsql_test 2.1 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE y1(a PRIMARY KEY, b UNIQUE, c);
+ INSERT INTO y1 VALUES('a', 'b', 'c');
+ INSERT INTO y1 VALUES('d', 'e', 'f');
+} {wal}
+do_execsql_test -db db2 2.2 {
+ BEGIN;
+ SELECT * FROM y1;
+} {a b c d e f}
+do_execsql_test 2.3 {
+ UPDATE y1 SET c='g' WHERE a='d';
+ PRAGMA wal_checkpoint;
+} {0 11 10}
+do_execsql_test -db db2 2.4 {
+ COMMIT
+}
+
+# Finalize the statement handle, causing the first connection to be
+# closed. Test that this has not corrupted the database file by
+# deleting the new wal file from the file-system. If it has, this
+# test should fail with an IO or corruption error.
+#
+do_test 2.5 {
+ sqlite3_finalize $::stmt
+ sqlite3 db3 test.db
+ execsql {
+ PRAGMA integrity_check;
+ SELECT * FROM y1;
+ } db3
+} {ok a b c d e g}
+}
finish_test
diff --git a/test/normalize.test b/test/normalize.test
new file mode 100644
index 0000000000..8932650c83
--- /dev/null
+++ b/test/normalize.test
@@ -0,0 +1,72 @@
+# 2018-01-08
+#
+# 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.
+#
+#***********************************************************************
+#
+# Tests for the sqlite3_normalize() extension function.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix normalize
+
+foreach {tnum sql norm} {
+ 100
+ {SELECT * FROM t1 WHERE a IN (1) AND b=51.42}
+ {select*from t1 where a in(?,?,?)and b=?;}
+
+ 110
+ {SELECT a, b+15, c FROM t1 WHERE d NOT IN (SELECT x FROM t2);}
+ {select a,b+?,c from t1 where d not in(select x from t2);}
+
+ 120
+ { SELECT NULL, b FROM t1 -- comment text
+ WHERE d IN (WITH t(a) AS (VALUES(5)) /* CTE */
+ SELECT a FROM t)
+ OR e='hello';
+ }
+ {select?,b from t1 where d in(with t(a)as(values(?))select a from t)or e=?;}
+
+ 121
+ {/*Initial comment*/
+ -- another comment line
+ SELECT NULL /* comment */ , b FROM t1 -- comment text
+ WHERE d IN (WITH t(a) AS (VALUES(5)) /* CTE */
+ SELECT a FROM t)
+ OR e='hello';
+ }
+ {select?,b from t1 where d in(with t(a)as(values(?))select a from t)or e=?;}
+
+ 130
+ {/* Query containing parameters */
+ SELECT x,$::abc(15),y,@abc,z,?99,w FROM t1 /* Trailing comment */}
+ {select x,?,y,?,z,?,w from t1;}
+
+ 140
+ {/* Long list on the RHS of IN */
+ SELECT 15 IN (1,2,3,(SELECT * FROM t1),'xyz',x'abcd',22*(x+5),null);}
+ {select?in(?,?,?);}
+
+ 150
+ {SELECT x'abc'; -- illegal token}
+ {}
+
+ 160
+ {SELECT a,NULL,b FROM t1 WHERE c IS NOT NULL or D is null or e=5}
+ {select a,?,b from t1 where c is not null or d is null or e=?;}
+
+ 170
+ {/* IN list exactly 5 bytes long */
+ SELECT * FROM t1 WHERE x IN (1,2,3);}
+ {select*from t1 where x in(?,?,?);}
+} {
+ do_test $tnum [list sqlite3_normalize $sql] $norm
+}
+
+finish_test
diff --git a/test/notnull.test b/test/notnull.test
index 23fd33d4ba..32d95eaf24 100644
--- a/test/notnull.test
+++ b/test/notnull.test
@@ -561,4 +561,49 @@ do_test notnull-5.5 {
execsql { SELECT * FROM t1 }
} {1 2}
+#-------------------------------------------------------------------------
+# Check that UNIQUE NOT NULL indexes are always recognized as such.
+#
+proc uses_op_next {sql} {
+ db eval "EXPLAIN $sql" a {
+ if {$a(opcode)=="Next"} { return 1 }
+ }
+ return 0
+}
+
+proc do_uses_op_next_test {tn sql res} {
+ uplevel [list do_test $tn [list uses_op_next $sql] $res]
+}
+
+reset_db
+do_execsql_test notnull-6.0 {
+ CREATE TABLE t1(a UNIQUE);
+ CREATE TABLE t2(a NOT NULL UNIQUE);
+ CREATE TABLE t3(a UNIQUE NOT NULL);
+ CREATE TABLE t4(a NOT NULL);
+ CREATE UNIQUE INDEX t4a ON t4(a);
+
+ CREATE TABLE t5(a PRIMARY KEY);
+ CREATE TABLE t6(a PRIMARY KEY NOT NULL);
+ CREATE TABLE t7(a NOT NULL PRIMARY KEY);
+ CREATE TABLE t8(a PRIMARY KEY) WITHOUT ROWID;
+
+ CREATE TABLE t9(a PRIMARY KEY UNIQUE NOT NULL);
+ CREATE TABLE t10(a UNIQUE PRIMARY KEY NOT NULL);
+}
+
+do_uses_op_next_test notnull-6.1 "SELECT * FROM t1 WHERE a IS ?" 1
+do_uses_op_next_test notnull-6.2 "SELECT * FROM t2 WHERE a IS ?" 0
+do_uses_op_next_test notnull-6.3 "SELECT * FROM t3 WHERE a IS ?" 0
+do_uses_op_next_test notnull-6.4 "SELECT * FROM t4 WHERE a IS ?" 0
+
+do_uses_op_next_test notnull-6.5 "SELECT * FROM t5 WHERE a IS ?" 1
+do_uses_op_next_test notnull-6.6 "SELECT * FROM t6 WHERE a IS ?" 0
+do_uses_op_next_test notnull-6.7 "SELECT * FROM t7 WHERE a IS ?" 0
+do_uses_op_next_test notnull-6.8 "SELECT * FROM t8 WHERE a IS ?" 0
+
+do_uses_op_next_test notnull-6.9 "SELECT * FROM t8 WHERE a IS ?" 0
+do_uses_op_next_test notnull-6.10 "SELECT * FROM t8 WHERE a IS ?" 0
+
finish_test
+
diff --git a/test/ossfuzz.c b/test/ossfuzz.c
index 7b28cf6a7e..fa6e9142fe 100644
--- a/test/ossfuzz.c
+++ b/test/ossfuzz.c
@@ -160,6 +160,9 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
/* Run the SQL. The sqlite_exec() interface expects a zero-terminated
** string, so make a copy. */
zSql = sqlite3_mprintf("%.*s", (int)size, data);
+#ifndef SQLITE_OMIT_COMPLETE
+ sqlite3_complete(zSql);
+#endif
sqlite3_exec(cx.db, zSql, exec_handler, (void*)&execCnt, &zErrMsg);
/* Show any errors */
diff --git a/test/pager1.test b/test/pager1.test
index 8451e0b3d2..42edc3d16b 100644
--- a/test/pager1.test
+++ b/test/pager1.test
@@ -17,6 +17,11 @@ source $testdir/malloc_common.tcl
source $testdir/wal_common.tcl
set testprefix pager1
+if {[atomic_batch_write test.db]} {
+ finish_test
+ return
+}
+
# 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).
#
diff --git a/test/pager3.test b/test/pager3.test
index 23435a79b7..e815f2788b 100644
--- a/test/pager3.test
+++ b/test/pager3.test
@@ -16,6 +16,10 @@ source $testdir/lock_common.tcl
source $testdir/malloc_common.tcl
source $testdir/wal_common.tcl
+if {[atomic_batch_write test.db]} {
+ finish_test
+ return
+}
foreach {tn sql res j} {
1 "PRAGMA journal_mode = DELETE" delete 0
diff --git a/test/pagerfault.test b/test/pagerfault.test
index 392c1a2b98..3006dad7cc 100644
--- a/test/pagerfault.test
+++ b/test/pagerfault.test
@@ -1203,12 +1203,14 @@ do_faultsim_test pagerfault-26 -prep {
set contents [db eval {SELECT * FROM t1}]
if {$contents != "1 2"} { error "Bad database contents ($contents)" }
- set sz [file size test.db]
- if {$testrc!=0 && $sz!=1024*3 && $sz!=4096*3} {
- error "Expected file size to be 3072 or 12288 bytes - actual size $sz bytes"
- }
- if {$testrc==0 && $sz!=4096*3} {
- error "Expected file size to be 12288 bytes - actual size $sz bytes"
+ if {[atomic_batch_write test.db]==0} {
+ set sz [file size test.db]
+ if {$testrc!=0 && $sz!=1024*3 && $sz!=4096*3} {
+ error "Expected file size 3072 or 12288 bytes - actual size $sz bytes"
+ }
+ if {$testrc==0 && $sz!=4096*3} {
+ error "Expected file size to be 12288 bytes - actual size $sz bytes"
+ }
}
}
diff --git a/test/permutations.test b/test/permutations.test
index 76fb72ace5..c1d28d4e09 100644
--- a/test/permutations.test
+++ b/test/permutations.test
@@ -86,9 +86,10 @@ proc test_set {args} {
#
set alltests [list]
foreach f [glob $testdir/*.test] { lappend alltests [file tail $f] }
-foreach f [glob -nocomplain \
- $testdir/../ext/rtree/*.test \
+foreach f [glob -nocomplain \
+ $testdir/../ext/rtree/*.test \
$testdir/../ext/fts5/test/*.test \
+ $testdir/../ext/expert/*.test \
$testdir/../ext/lsm1/test/*.test \
] {
lappend alltests $f
diff --git a/test/pragma5.test b/test/pragma5.test
index 625ab92012..d2c58000cf 100644
--- a/test/pragma5.test
+++ b/test/pragma5.test
@@ -34,7 +34,7 @@ do_execsql_test 1.0 {
1 builtin {} 0 {} 0
}
do_execsql_test 1.1 {
- SELECT * FROM pragma_function_list WHERE name='upper'
+ SELECT * FROM pragma_function_list WHERE name='upper' AND builtin
} {upper 1}
do_execsql_test 1.2 {
SELECT * FROM pragma_function_list WHERE name LIKE 'exter%';
diff --git a/test/printf2.test b/test/printf2.test
index d30966d167..998038f88e 100644
--- a/test/printf2.test
+++ b/test/printf2.test
@@ -148,6 +148,63 @@ do_execsql_test printf2-4.10 {
SELECT printf('|%,d|%,d|',1234567890,-1234567890);
} {|1,234,567,890|-1,234,567,890|}
+# 2018-02-19. Unicode characters with %c
+do_execsql_test printf2-5.100 {
+ SELECT printf('(%8c)',char(11106));
+} {{( ⭢)}}
+do_execsql_test printf2-5.101 {
+ SELECT printf('(%-8c)',char(11106));
+} {{(⭢ )}}
+do_execsql_test printf2-5.102 {
+ SELECT printf('(%5.3c)',char(1492));
+} {{( ההה)}}
+do_execsql_test printf2-5.103 {
+ SELECT printf('(%-5.3c)',char(1492));
+} {{(ההה )}}
+do_execsql_test printf2-5.104 {
+ SELECT printf('(%3.3c)',char(1492));
+} {{(ההה)}}
+do_execsql_test printf2-5.105 {
+ SELECT printf('(%-3.3c)',char(1492));
+} {{(ההה)}}
+do_execsql_test printf2-5.104 {
+ SELECT printf('(%2c)',char(1513));
+} {{( ש)}}
+do_execsql_test printf2-5.106 {
+ SELECT printf('(%-2c)',char(1513));
+} {{(ש )}}
+
+# 2018-02-19. Unicode characters with the "!" flag in %s and friends.
+do_execsql_test printf2-6.100 {
+ SELECT printf('(%!.3s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד');
+} {(הנה)}
+do_execsql_test printf2-6.101 {
+ SELECT printf('(%.6s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד');
+} {(הנה)}
+do_execsql_test printf2-6.102 {
+ SELECT printf('(%!5.3s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד');
+} {{( הנה)}}
+do_execsql_test printf2-6.103 {
+ SELECT printf('(%8.6s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד');
+} {{( הנה)}}
+do_execsql_test printf2-6.104 {
+ SELECT printf('(%!-5.3s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד');
+} {{(הנה )}}
+do_execsql_test printf2-6.105 {
+ SELECT printf('(%-8.6s)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד');
+} {{(הנה )}}
+do_execsql_test printf2-6.106 {
+ SELECT printf('(%!.3Q)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד');
+} {('הנה')}
+do_execsql_test printf2-6.107 {
+ SELECT printf('(%.6Q)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד');
+} {('הנה')}
+do_execsql_test printf2-6.108 {
+ SELECT printf('(%!7.3Q)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד');
+} {{( 'הנה')}}
+do_execsql_test printf2-6.109 {
+ SELECT printf('(%10.6Q)','הנה מה־טוב ומה־נעים שבת אחים גם־יחד');
+} {{( 'הנה')}}
finish_test
diff --git a/test/releasetest.tcl b/test/releasetest.tcl
index 5055e81129..599ebd791d 100755
--- a/test/releasetest.tcl
+++ b/test/releasetest.tcl
@@ -734,6 +734,9 @@ proc makeCommand { targets makeOpts cflags opts } {
set nmakeDir [file nativename $::SRCDIR]
set nmakeFile [file nativename [file join $nmakeDir Makefile.msc]]
lappend result nmake /f $nmakeFile TOP=$nmakeDir
+ set tclDir [file nativename [file normalize \
+ [file dirname [file dirname [info nameofexecutable]]]]]
+ lappend result "TCLDIR=$tclDir"
if {[regexp {USE_STDCALL=1} $cflags]} {
lappend result USE_STDCALL=1
}
diff --git a/test/rollback.test b/test/rollback.test
index 60a6190317..423bf20fce 100644
--- a/test/rollback.test
+++ b/test/rollback.test
@@ -83,6 +83,7 @@ if {$tcl_platform(platform) == "unix"
&& [permutation] ne "onefile"
&& [permutation] ne "inmemory_journal"
&& [permutation] ne "atomic-batch-write"
+ && [atomic_batch_write test.db]==0
} {
do_test rollback-2.1 {
execsql {
diff --git a/test/rowvalue.test b/test/rowvalue.test
index 5f2701c733..00d9395331 100644
--- a/test/rowvalue.test
+++ b/test/rowvalue.test
@@ -394,4 +394,156 @@ do_execsql_test 16.5 {
3 i ii iii iv
}
+do_execsql_test 17.0 {
+ CREATE TABLE b1(a, b);
+ CREATE TABLE b2(x);
+}
+
+do_execsql_test 17.1 {
+ SELECT * FROM b2 CROSS JOIN b1
+ WHERE b2.x=b1.a AND (b1.a, 2)
+ IN (VALUES(1, 2));
+} {}
+
+do_execsql_test 18.0 {
+ CREATE TABLE b3 ( a, b, PRIMARY KEY (a, b) );
+ CREATE TABLE b4 ( a );
+ CREATE TABLE b5 ( a, b );
+ INSERT INTO b3 VALUES (1, 1), (1, 2);
+ INSERT INTO b4 VALUES (1);
+ INSERT INTO b5 VALUES (1, 1), (1, 2);
+}
+
+do_execsql_test 18.1 {
+ SELECT * FROM b3 WHERE (SELECT b3.a, b3.b) IN ( SELECT a, b FROM b5 )
+} {1 1 1 2}
+do_execsql_test 18.2 {
+ SELECT * FROM b3 WHERE (VALUES(b3.a, b3.b)) IN ( SELECT a, b FROM b5 );
+} {1 1 1 2}
+do_execsql_test 18.3 {
+ SELECT * FROM b3 WHERE (b3.a, b3.b) IN ( SELECT a, b FROM b5 );
+} {1 1 1 2}
+do_execsql_test 18.4 {
+ SELECT * FROM b3 JOIN b4 ON b4.a = b3.a
+ WHERE (SELECT b3.a, b3.b) IN ( SELECT a, b FROM b5 );
+} {1 1 1 1 2 1}
+do_execsql_test 18.5 {
+ SELECT * FROM b3 JOIN b4 ON b4.a = b3.a
+ WHERE (VALUES(b3.a, b3.b)) IN ( SELECT a, b FROM b5 );
+} {1 1 1 1 2 1}
+do_execsql_test 18.6 {
+ SELECT * FROM b3 JOIN b4 ON b4.a = b3.a
+ WHERE (b3.a, b3.b) IN ( SELECT a, b FROM b5 );
+} {1 1 1 1 2 1}
+
+
+# 2018-02-13 Ticket https://www.sqlite.org/src/tktview/f484b65f3d6230593c3
+# Incorrect result from a row-value comparison in the WHERE clause.
+#
+do_execsql_test 19.1 {
+ DROP TABLE IF EXISTS t1;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY,b);
+ INSERT INTO t1(a,b) VALUES(1,11),(2,22),(3,33),(4,44);
+ SELECT * FROM t1 WHERE (a,b)>(0,0) ORDER BY a;
+} {1 11 2 22 3 33 4 44}
+do_execsql_test 19.2 {
+ SELECT * FROM t1 WHERE (a,b)>=(0,0) ORDER BY a;
+} {1 11 2 22 3 33 4 44}
+do_execsql_test 19.3 {
+ SELECT * FROM t1 WHERE (a,b)<(5,0) ORDER BY a DESC;
+} {4 44 3 33 2 22 1 11}
+do_execsql_test 19.4 {
+ SELECT * FROM t1 WHERE (a,b)<=(5,0) ORDER BY a DESC;
+} {4 44 3 33 2 22 1 11}
+do_execsql_test 19.5 {
+ SELECT * FROM t1 WHERE (a,b)>(3,0) ORDER BY a;
+} {3 33 4 44}
+do_execsql_test 19.6 {
+ SELECT * FROM t1 WHERE (a,b)>=(3,0) ORDER BY a;
+} {3 33 4 44}
+do_execsql_test 19.7 {
+ SELECT * FROM t1 WHERE (a,b)<(3,0) ORDER BY a DESC;
+} {2 22 1 11}
+do_execsql_test 19.8 {
+ SELECT * FROM t1 WHERE (a,b)<=(3,0) ORDER BY a DESC;
+} {2 22 1 11}
+do_execsql_test 19.9 {
+ SELECT * FROM t1 WHERE (a,b)>(3,32) ORDER BY a;
+} {3 33 4 44}
+do_execsql_test 19.10 {
+ SELECT * FROM t1 WHERE (a,b)>(3,33) ORDER BY a;
+} {4 44}
+do_execsql_test 19.11 {
+ SELECT * FROM t1 WHERE (a,b)>=(3,33) ORDER BY a;
+} {3 33 4 44}
+do_execsql_test 19.12 {
+ SELECT * FROM t1 WHERE (a,b)>=(3,34) ORDER BY a;
+} {4 44}
+do_execsql_test 19.13 {
+ SELECT * FROM t1 WHERE (a,b)<(3,34) ORDER BY a DESC;
+} {3 33 2 22 1 11}
+do_execsql_test 19.14 {
+ SELECT * FROM t1 WHERE (a,b)<(3,33) ORDER BY a DESC;
+} {2 22 1 11}
+do_execsql_test 19.15 {
+ SELECT * FROM t1 WHERE (a,b)<=(3,33) ORDER BY a DESC;
+} {3 33 2 22 1 11}
+do_execsql_test 19.16 {
+ SELECT * FROM t1 WHERE (a,b)<=(3,32) ORDER BY a DESC;
+} {2 22 1 11}
+do_execsql_test 19.21 {
+ SELECT * FROM t1 WHERE (0,0)<(a,b) ORDER BY a;
+} {1 11 2 22 3 33 4 44}
+do_execsql_test 19.22 {
+ SELECT * FROM t1 WHERE (0,0)<=(a,b) ORDER BY a;
+} {1 11 2 22 3 33 4 44}
+do_execsql_test 19.23 {
+ SELECT * FROM t1 WHERE (5,0)>(a,b) ORDER BY a DESC;
+} {4 44 3 33 2 22 1 11}
+do_execsql_test 19.24 {
+ SELECT * FROM t1 WHERE (5,0)>=(a,b) ORDER BY a DESC;
+} {4 44 3 33 2 22 1 11}
+do_execsql_test 19.25 {
+ SELECT * FROM t1 WHERE (3,0)<(a,b) ORDER BY a;
+} {3 33 4 44}
+do_execsql_test 19.26 {
+ SELECT * FROM t1 WHERE (3,0)<=(a,b) ORDER BY a;
+} {3 33 4 44}
+do_execsql_test 19.27 {
+ SELECT * FROM t1 WHERE (3,0)>(a,b) ORDER BY a DESC;
+} {2 22 1 11}
+do_execsql_test 19.28 {
+ SELECT * FROM t1 WHERE (3,0)>=(a,b) ORDER BY a DESC;
+} {2 22 1 11}
+do_execsql_test 19.29 {
+ SELECT * FROM t1 WHERE (3,32)<(a,b) ORDER BY a;
+} {3 33 4 44}
+do_execsql_test 19.30 {
+ SELECT * FROM t1 WHERE (3,33)<(a,b) ORDER BY a;
+} {4 44}
+do_execsql_test 19.31 {
+ SELECT * FROM t1 WHERE (3,33)<=(a,b) ORDER BY a;
+} {3 33 4 44}
+do_execsql_test 19.32 {
+ SELECT * FROM t1 WHERE (3,34)<=(a,b) ORDER BY a;
+} {4 44}
+do_execsql_test 19.33 {
+ SELECT * FROM t1 WHERE (3,34)>(a,b) ORDER BY a DESC;
+} {3 33 2 22 1 11}
+do_execsql_test 19.34 {
+ SELECT * FROM t1 WHERE (3,33)>(a,b) ORDER BY a DESC;
+} {2 22 1 11}
+do_execsql_test 19.35 {
+ SELECT * FROM t1 WHERE (3,33)>=(a,b) ORDER BY a DESC;
+} {3 33 2 22 1 11}
+do_execsql_test 19.36 {
+ SELECT * FROM t1 WHERE (3,32)>=(a,b) ORDER BY a DESC;
+} {2 22 1 11}
+
+# 2018-02-18: Memory leak nexted row-value. Detected by OSSFuzz.
+#
+do_catchsql_test 20.1 {
+ SELECT 1 WHERE (2,(2,0)) IS (2,(2,0));
+} {0 1}
+
finish_test
diff --git a/test/securedel.test b/test/securedel.test
index a78f466031..8323a30497 100644
--- a/test/securedel.test
+++ b/test/securedel.test
@@ -17,8 +17,12 @@ source $testdir/tester.tcl
unset -nocomplain DEFAULT_SECDEL
set DEFAULT_SECDEL 0
-ifcapable secure_delete {
- set DEFAULT_SECDEL 1
+ifcapable fast_secure_delete {
+ set DEFAULT_SECDEL 2
+} else {
+ ifcapable secure_delete {
+ set DEFAULT_SECDEL 1
+ }
}
diff --git a/test/select1.test b/test/select1.test
index 43b20f6d15..7023a6e65d 100644
--- a/test/select1.test
+++ b/test/select1.test
@@ -688,7 +688,7 @@ do_test select1-7.2 {
do_test select1-7.3 {
set v [catch {execsql {SELECT f1 FROM test1 as 'hi', test2 as}} msg]
lappend v $msg
-} {1 {near "as": syntax error}}
+} {1 {incomplete input}}
do_test select1-7.4 {
set v [catch {execsql {
SELECT f1 FROM test1 ORDER BY;
diff --git a/test/selectG.test b/test/selectG.test
index 86d89b121b..fab4c4ed4d 100644
--- a/test/selectG.test
+++ b/test/selectG.test
@@ -36,4 +36,24 @@ do_test 100 {
}
} {100000 5000050000 50000.5 1}
+# 2018-01-14. A 100K-entry VALUES clause within a scalar expression does
+# not cause processor stack overflow.
+#
+do_test 110 {
+ set sql "SELECT (VALUES"
+ for {set i 1} {$i<100000} {incr i} {
+ append sql "($i),"
+ }
+ append sql "($i));"
+ db eval $sql
+} {1}
+
+# Only the left-most term of a multi-valued VALUES within a scalar
+# expression is evaluated.
+#
+do_test 120 {
+ set n [llength [split [db eval "explain $sql"] \n]]
+ expr {$n<10}
+} {1}
+
finish_test
diff --git a/test/sharedA.test b/test/sharedA.test
index 146fb26be0..55ed5749bb 100644
--- a/test/sharedA.test
+++ b/test/sharedA.test
@@ -19,6 +19,11 @@ if {[run_thread_tests]==0} { finish_test ; return }
db close
set ::testprefix sharedA
+if {[atomic_batch_write test.db]} {
+ finish_test
+ return
+}
+
set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
#-------------------------------------------------------------------------
diff --git a/test/shell1.test b/test/shell1.test
index ddd72c7180..81883a8d18 100644
--- a/test/shell1.test
+++ b/test/shell1.test
@@ -495,7 +495,7 @@ do_test shell1-3.15.2 {
do_test shell1-3.15.3 {
# too many arguments
catchcmd "test.db" ".output FOO BAD"
-} {1 {Usage: .output FILE}}
+} {1 {Usage: .output [-e|-x|FILE]}}
# .output stdout Send output to the screen
do_test shell1-3.16.1 {
@@ -504,7 +504,7 @@ do_test shell1-3.16.1 {
do_test shell1-3.16.2 {
# too many arguments
catchcmd "test.db" ".output stdout BAD"
-} {1 {Usage: .output FILE}}
+} {1 {Usage: .output [-e|-x|FILE]}}
# .prompt MAIN CONTINUE Replace the standard prompts
do_test shell1-3.17.1 {
@@ -581,8 +581,10 @@ do_test shell1-3.21.4 {
}
catchcmd "test.db" ".schema"
} {0 {CREATE TABLE t1(x);
-CREATE VIEW v2 AS SELECT x+1 AS y FROM t1;
-CREATE VIEW v1 AS SELECT y+1 FROM v2;}}
+CREATE VIEW v2 AS SELECT x+1 AS y FROM t1
+/* v2(y) */;
+CREATE VIEW v1 AS SELECT y+1 FROM v2
+/* v1("y+1") */;}}
db eval {DROP VIEW v1; DROP VIEW v2; DROP TABLE t1;}
}
diff --git a/test/shell3.test b/test/shell3.test
index bb2524c1cc..63c30a2682 100644
--- a/test/shell3.test
+++ b/test/shell3.test
@@ -66,7 +66,7 @@ do_test shell3-1.6 {
} {0 {}}
do_test shell3-1.7 {
catchcmd "foo.db \"CREATE TABLE\""
-} {1 {Error: near "TABLE": syntax error}}
+} {1 {Error: incomplete input}}
#----------------------------------------------------------------------------
# shell3-2.*: Basic tests for running SQL file from command line.
@@ -96,6 +96,6 @@ do_test shell3-2.6 {
} {0 {}}
do_test shell3-2.7 {
catchcmd "foo.db" "CREATE TABLE"
-} {1 {Error: near line 1: near "TABLE": syntax error}}
+} {1 {Error: near line 1: incomplete input}}
finish_test
diff --git a/test/shell6.test b/test/shell6.test
index 25bfa32338..49b4cc3344 100644
--- a/test/shell6.test
+++ b/test/shell6.test
@@ -93,6 +93,14 @@ foreach {tn schema output} {
} {
}
+ 10 {
+ CREATE TABLE parent (id INTEGER PRIMARY KEY);
+ CREATE TABLE child2 (id INT PRIMARY KEY, parentID INT REFERENCES parent)
+ WITHOUT ROWID;
+ } {
+ CREATE INDEX 'child2_parentID' ON 'child2'('parentID'); --> parent(id)
+ }
+
} {
forcedelete test.db
sqlite3 db test.db
diff --git a/test/shell8.test b/test/shell8.test
new file mode 100644
index 0000000000..3658a8ac5d
--- /dev/null
+++ b/test/shell8.test
@@ -0,0 +1,177 @@
+# 2017 December 9
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Test the shell tool ".ar" command.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix shell8
+
+ifcapable !vtab {
+ finish_test; return
+}
+set CLI [test_find_cli]
+
+# Check to make sure the shell has been compiled with ".archive" support.
+#
+if {[string match {*unknown command*} [catchcmd :memory: .archive]]} {
+ finish_test; return
+}
+
+proc populate_dir {dirname spec} {
+ # First delete the current tree, if one exists.
+ file delete -force $dirname
+
+ # Recreate the root of the new tree.
+ file mkdir $dirname
+
+ # Add each file to the new tree.
+ foreach {f d} $spec {
+ set path [file join $dirname $f]
+ file mkdir [file dirname $path]
+ set fd [open $path w]
+ puts -nonewline $fd $d
+ close $fd
+ }
+}
+
+proc dir_to_list {dirname {n -1}} {
+ if {$n<0} {set n [llength [file split $dirname]]}
+
+ set res [list]
+ foreach f [glob -nocomplain $dirname/*] {
+ set mtime [file mtime $f]
+ if {$::tcl_platform(platform)!="windows"} {
+ set perm [file attributes $f -perm]
+ } else {
+ set perm 0
+ }
+ set relpath [file join {*}[lrange [file split $f] $n end]]
+ lappend res
+ if {[file isdirectory $f]} {
+ lappend res [list $relpath / $mtime $perm]
+ lappend res {*}[dir_to_list $f]
+ } else {
+ set fd [open $f]
+ set data [read $fd]
+ close $fd
+ lappend res [list $relpath $data $mtime $perm]
+ }
+ }
+ lsort $res
+}
+
+proc dir_compare {d1 d2} {
+ set l1 [dir_to_list $d1]
+ set l2 [dir_to_list $d1]
+ string compare $l1 $l2
+}
+
+foreach {tn tcl} {
+ 1 {
+ set c1 ".ar c ar1"
+ set x1 ".ar x"
+
+ set c2 ".ar cC ar1 ."
+ set x2 ".ar Cx ar3"
+
+ set c3 ".ar cCf ar1 test_xyz.db ."
+ set x3 ".ar Cfx ar3 test_xyz.db"
+ }
+
+ 2 {
+ set c1 ".ar -c ar1"
+ set x1 ".ar -x"
+
+ set c2 ".ar -cC ar1 ."
+ set x2 ".ar -xC ar3"
+
+ set c3 ".ar -cCar1 -ftest_xyz.db ."
+ set x3 ".ar -x -C ar3 -f test_xyz.db"
+ }
+
+ 3 {
+ set c1 ".ar --create ar1"
+ set x1 ".ar --extract"
+
+ set c2 ".ar --directory ar1 --create ."
+ set x2 ".ar --extract --dir ar3"
+
+ set c3 ".ar --creat --dir ar1 --file test_xyz.db ."
+ set x3 ".ar --e --dir ar3 --f test_xyz.db"
+ }
+
+ 4 {
+ set c1 ".ar --cr ar1"
+ set x1 ".ar --e"
+
+ set c2 ".ar -C ar1 -c ."
+ set x2 ".ar -x -C ar3"
+
+ set c3 ".ar -c --directory ar1 --file test_xyz.db ."
+ set x3 ".ar -x --directory ar3 --file test_xyz.db"
+ }
+} {
+ eval $tcl
+
+ # Populate directory "ar1" with some files.
+ #
+ populate_dir ar1 {
+ file1 "abcd"
+ file2 "efgh"
+ dir1/file3 "ijkl"
+ }
+ set expected [dir_to_list ar1]
+
+ do_test 1.$tn.1 {
+ catchcmd test_ar.db $c1
+ file delete -force ar1
+ catchcmd test_ar.db $x1
+ dir_to_list ar1
+ } $expected
+
+ do_test 1.$tn.2 {
+ file delete -force ar3
+ catchcmd test_ar.db $c2
+ catchcmd test_ar.db $x2
+ dir_to_list ar3
+ } $expected
+
+ do_test 1.$tn.3 {
+ file delete -force ar3
+ file delete -force test_xyz.db
+ catchcmd ":memory:" $c3
+ catchcmd ":memory:" $x3
+ dir_to_list ar3
+ } $expected
+
+ # This is a repeat of test 1.$tn.1, except that there is a 2 second
+ # pause between creating the archive and extracting its contents.
+ # This is to test that timestamps are set correctly.
+ #
+ # Because it is slow, only do this for $tn==1.
+ if {$tn==1} {
+ do_test 1.$tn.1 {
+ catchcmd test_ar.db $c1
+ file delete -force ar1
+ after 2000
+ catchcmd test_ar.db $x1
+ dir_to_list ar1
+ } $expected
+ }
+}
+
+finish_test
+
+
+
+finish_test
diff --git a/test/snapshot3.test b/test/snapshot3.test
new file mode 100644
index 0000000000..266f43dfd4
--- /dev/null
+++ b/test/snapshot3.test
@@ -0,0 +1,100 @@
+# 2016 September 23
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# 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 snapshot3
+
+# This test does not work with the inmemory_journal permutation. The reason
+# is that each connection opened as part of this permutation executes
+# "PRAGMA journal_mode=memory", which fails if the database is in wal mode
+# and there are one or more existing connections.
+if {[permutation]=="inmemory_journal"} {
+ finish_test
+ return
+}
+
+#-------------------------------------------------------------------------
+# This block of tests verifies that it is not possible to wrap the wal
+# file - using a writer or a "PRAGMA wal_checkpoint = TRUNCATE" - while
+# there is an open snapshot transaction (transaction opened using
+# sqlite3_snapshot_open()).
+#
+do_execsql_test 1.0 {
+ CREATE TABLE t1(y);
+ PRAGMA journal_mode = wal;
+ INSERT INTO t1 VALUES(1);
+ INSERT INTO t1 VALUES(2);
+ INSERT INTO t1 VALUES(3);
+ INSERT INTO t1 VALUES(4);
+} {wal}
+
+do_test 1.1 {
+ sqlite3 db2 test.db
+ sqlite3 db3 test.db
+
+ execsql {SELECT * FROM sqlite_master} db2
+ execsql {SELECT * FROM sqlite_master} db3
+
+ db2 trans { set snap [sqlite3_snapshot_get_blob db2 main] }
+ db2 eval { SELECT * FROM t1 }
+} {1 2 3 4}
+
+do_test 1.2 {
+ execsql BEGIN db2
+ sqlite3_snapshot_open_blob db2 main $snap
+ db2 eval { SELECT * FROM t1 }
+} {1 2 3 4}
+
+do_test 1.2 {
+ execsql END db2
+ execsql { PRAGMA wal_checkpoint }
+
+ execsql BEGIN db2
+ sqlite3_snapshot_open_blob db2 main $snap
+ db2 eval { SELECT * FROM t1 }
+} {1 2 3 4}
+
+set sz [file size test.db-wal]
+do_test 1.3 {
+ execsql { PRAGMA wal_checkpoint = truncate }
+ file size test.db-wal
+} $sz
+
+do_test 1.4 {
+ execsql BEGIN db3
+ list [catch { sqlite3_snapshot_open_blob db3 main $snap } msg] $msg
+} {0 {}}
+
+do_test 1.5 {
+ db3 eval { SELECT * FROM t1; END }
+} {1 2 3 4}
+
+do_test 1.6 {
+ db2 eval { SELECT * FROM t1; END }
+} {1 2 3 4}
+
+do_test 1.7 {
+ execsql { PRAGMA wal_checkpoint = truncate }
+ file size test.db-wal
+} 0
+
+do_test 1.8 {
+ execsql BEGIN db3
+ list [catch { sqlite3_snapshot_open_blob db3 main $snap } msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+
+finish_test
+
diff --git a/test/speedtest1.c b/test/speedtest1.c
index b92801a208..9342da79bf 100644
--- a/test/speedtest1.c
+++ b/test/speedtest1.c
@@ -32,7 +32,7 @@ static const char zHelp[] =
" --size N Relative test size. Default=100\n"
" --stats Show statistics at the end\n"
" --temp N N from 0 to 9. 0: no temp table. 9: all temp tables\n"
- " --testset T Run test-set T (main, cte, rtree, orm, debug)\n"
+ " --testset T Run test-set T (main, cte, rtree, orm, fp, debug)\n"
" --trace Turn on SQL tracing\n"
" --threads N Use up to N threads for sorting\n"
" --utf16be Set text encoding to UTF-16BE\n"
@@ -1120,7 +1120,77 @@ void testset_cte(void){
);
speedtest1_run();
speedtest1_end_test();
+}
+/*
+** Compute a pseudo-random floating point ascii number.
+*/
+void speedtest1_random_ascii_fp(char *zFP){
+ int x = speedtest1_random();
+ int y = speedtest1_random();
+ int z;
+ z = y%10;
+ if( z<0 ) z = -z;
+ y /= 10;
+ sqlite3_snprintf(100,zFP,"%d.%de%d",y,z,x%200);
+}
+
+/*
+** A testset for floating-point numbers.
+*/
+void testset_fp(void){
+ int n;
+ int i;
+ char zFP1[100];
+ char zFP2[100];
+
+ n = g.szTest*5000;
+ speedtest1_begin_test(100, "Fill a table with %d FP values", n*2);
+ speedtest1_exec("BEGIN");
+ speedtest1_exec("CREATE%s TABLE t1(a REAL %s, b REAL %s);",
+ isTemp(1), g.zNN, g.zNN);
+ speedtest1_prepare("INSERT INTO t1 VALUES(?1,?2); -- %d times", n);
+ for(i=1; i<=n; i++){
+ speedtest1_random_ascii_fp(zFP1);
+ speedtest1_random_ascii_fp(zFP2);
+ sqlite3_bind_text(g.pStmt, 1, zFP1, -1, SQLITE_STATIC);
+ sqlite3_bind_text(g.pStmt, 2, zFP2, -1, SQLITE_STATIC);
+ speedtest1_run();
+ }
+ speedtest1_exec("COMMIT");
+ speedtest1_end_test();
+
+ n = g.szTest/25 + 2;
+ speedtest1_begin_test(110, "%d range queries", n);
+ speedtest1_prepare("SELECT sum(b) FROM t1 WHERE a BETWEEN ?1 AND ?2");
+ for(i=1; i<=n; i++){
+ speedtest1_random_ascii_fp(zFP1);
+ speedtest1_random_ascii_fp(zFP2);
+ sqlite3_bind_text(g.pStmt, 1, zFP1, -1, SQLITE_STATIC);
+ sqlite3_bind_text(g.pStmt, 2, zFP2, -1, SQLITE_STATIC);
+ speedtest1_run();
+ }
+ speedtest1_end_test();
+
+ speedtest1_begin_test(120, "CREATE INDEX three times");
+ speedtest1_exec("BEGIN;");
+ speedtest1_exec("CREATE INDEX t1a ON t1(a);");
+ speedtest1_exec("CREATE INDEX t1b ON t1(b);");
+ speedtest1_exec("CREATE INDEX t1ab ON t1(a,b);");
+ speedtest1_exec("COMMIT;");
+ speedtest1_end_test();
+
+ n = g.szTest/3 + 2;
+ speedtest1_begin_test(130, "%d indexed range queries", n);
+ speedtest1_prepare("SELECT sum(b) FROM t1 WHERE a BETWEEN ?1 AND ?2");
+ for(i=1; i<=n; i++){
+ speedtest1_random_ascii_fp(zFP1);
+ speedtest1_random_ascii_fp(zFP2);
+ sqlite3_bind_text(g.pStmt, 1, zFP1, -1, SQLITE_STATIC);
+ sqlite3_bind_text(g.pStmt, 2, zFP2, -1, SQLITE_STATIC);
+ speedtest1_run();
+ }
+ speedtest1_end_test();
}
#ifdef SQLITE_ENABLE_RTREE
@@ -1873,6 +1943,8 @@ int main(int argc, char **argv){
testset_orm();
}else if( strcmp(zTSet,"cte")==0 ){
testset_cte();
+ }else if( strcmp(zTSet,"fp")==0 ){
+ testset_fp();
}else if( strcmp(zTSet,"rtree")==0 ){
#ifdef SQLITE_ENABLE_RTREE
testset_rtree(6, 147);
@@ -1881,7 +1953,7 @@ int main(int argc, char **argv){
"the R-Tree tests\n");
#endif
}else{
- fatal_error("unknown testset: \"%s\"\nChoices: main debug1 cte rtree\n",
+ fatal_error("unknown testset: \"%s\"\nChoices: main debug1 cte rtree fp\n",
zTSet);
}
speedtest1_final();
diff --git a/test/spellfix.test b/test/spellfix.test
index 8128bb59d2..68bcfd5adb 100644
--- a/test/spellfix.test
+++ b/test/spellfix.test
@@ -279,7 +279,7 @@ ifcapable trace {
do_tracesql_test 6.2.3 {
SELECT word, distance FROM t3 WHERE rowid = 10 AND word MATCH 'kiiner';
} {keener 300
- {SELECT id, word, rank, k1 FROM "main"."t3_vocab" WHERE langid=0 AND k2>=?1 AND k22}
+ {SELECT id, word, rank, coalesce(k1,word) FROM "main"."t3_vocab" WHERE langid=0 AND k2>=?1 AND k22}
}
}
diff --git a/test/spellfix4.test b/test/spellfix4.test
new file mode 100644
index 0000000000..caf6d5139a
--- /dev/null
+++ b/test/spellfix4.test
@@ -0,0 +1,353 @@
+# 2018-02-14
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Test cases for the editdist3() function in the spellfix extension.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix spellfix4
+
+ifcapable !vtab { finish_test ; return }
+
+load_static_extension db spellfix
+
+do_execsql_test 100 {
+ CREATE TABLE cost1(iLang, cFrom, cTo, iCost);
+ INSERT INTO cost1 VALUES
+ (0, '', '?', 97),
+ (0, '?', '', 98),
+ (0, '?', '?', 99),
+ (0, 'm', 'n', 50),
+ (0, 'n', 'm', 50)
+ ;
+ SELECT editdist3('cost1');
+ SELECT editdist3('anchor','amchor');
+} {{} 50}
+do_execsql_test 110 {
+ SELECT editdist3('anchor','anchoxr');
+} {97}
+do_execsql_test 111 {
+ SELECT editdist3('anchor','xanchor');
+} {97}
+do_execsql_test 112 {
+ SELECT editdist3('anchor','anchorx');
+} {97}
+do_execsql_test 120 {
+ SELECT editdist3('anchor','anchr');
+} {98}
+do_execsql_test 121 {
+ SELECT editdist3('anchor','ancho');
+} {98}
+do_execsql_test 122 {
+ SELECT editdist3('anchor','nchor');
+} {98}
+do_execsql_test 130 {
+ SELECT editdist3('anchor','anchur');
+} {99}
+do_execsql_test 131 {
+ SELECT editdist3('anchor','onchor');
+} {99}
+do_execsql_test 132 {
+ SELECT editdist3('anchor','anchot');
+} {99}
+do_execsql_test 140 {
+ SELECT editdist3('anchor','omchor');
+} {149}
+
+do_execsql_test 200 {
+ INSERT INTO cost1 VALUES
+ (0, 'a', 'ä', 5),
+ (0, 'ss', 'ß', 8)
+ ;
+ SELECT editdist3('cost1');
+ SELECT editdist3('strasse','straße');
+ SELECT editdist3('straße','strasse');
+} {{} 8 196}
+do_execsql_test 210 {
+ SELECT editdist3('baume','bäume');
+} {5}
+do_execsql_test 220 {
+ SELECT editdist3('baum','bäume');
+} {102}
+do_execsql_test 230 {
+ INSERT INTO cost1 VALUES
+ (0, 'ä', 'a', 5),
+ (0, 'ß', 'ss', 8)
+ ;
+ SELECT editdist3('cost1');
+ SELECT editdist3('strasse','straße');
+ SELECT editdist3('straße','strasse');
+} {{} 8 8}
+
+do_execsql_test 300 {
+ DELETE FROM cost1;
+ INSERT INTO cost1 VALUES
+ (0, '', '?', 97),
+ (0, '?', '', 98),
+ (0, '?', '?', 99),
+ (0, 'a', 'e', 50),
+ (0, 'a', 'i', 70),
+ (0, 'a', 'o', 75),
+ (0, 'a', 'u', 81),
+ (0, 'e', 'a', 50),
+ (0, 'e', 'i', 52),
+ (0, 'e', 'o', 72),
+ (0, 'e', 'u', 82),
+ (0, 'i', 'a', 70),
+ (0, 'i', 'e', 52),
+ (0, 'i', 'o', 75),
+ (0, 'i', 'u', 83),
+ (0, 'o', 'a', 75),
+ (0, 'o', 'e', 72),
+ (0, 'o', 'i', 75),
+ (0, 'o', 'u', 40),
+ (0, 'u', 'a', 81),
+ (0, 'u', 'e', 82),
+ (0, 'u', 'i', 83),
+ (0, 'u', 'o', 40),
+ (0, 'm', 'n', 45),
+ (0, 'n', 'm', 45)
+ ;
+ CREATE TABLE words(x TEXT);
+ INSERT INTO words VALUES
+ ('abraham'),
+ ('action'),
+ ('africa'),
+ ('aladdin'),
+ ('alert'),
+ ('alien'),
+ ('amazon'),
+ ('analog'),
+ ('animal'),
+ ('apollo'),
+ ('archive'),
+ ('arnold'),
+ ('aspirin'),
+ ('august'),
+ ('average'),
+ ('bahama'),
+ ('bambino'),
+ ('barcode'),
+ ('bazooka'),
+ ('belgium'),
+ ('between'),
+ ('biology'),
+ ('blonde'),
+ ('border'),
+ ('brave'),
+ ('british'),
+ ('bucket'),
+ ('button'),
+ ('caesar'),
+ ('camilla'),
+ ('cannon'),
+ ('caramel'),
+ ('carpet'),
+ ('catalog'),
+ ('century'),
+ ('chaos'),
+ ('chef'),
+ ('china'),
+ ('circus'),
+ ('classic'),
+ ('clinic'),
+ ('coconut'),
+ ('combine'),
+ ('complex'),
+ ('congo'),
+ ('convert'),
+ ('cosmos'),
+ ('crack'),
+ ('crown'),
+ ('cyclone'),
+ ('deal'),
+ ('delete'),
+ ('denver'),
+ ('detail'),
+ ('diana'),
+ ('direct'),
+ ('dolby'),
+ ('double'),
+ ('dublin'),
+ ('echo'),
+ ('edition'),
+ ('electra'),
+ ('emotion'),
+ ('enjoy'),
+ ('escape'),
+ ('everest'),
+ ('exile'),
+ ('express'),
+ ('family'),
+ ('ferrari'),
+ ('filter'),
+ ('fish'),
+ ('florida'),
+ ('ford'),
+ ('forum'),
+ ('frank'),
+ ('frozen'),
+ ('gallery'),
+ ('garlic'),
+ ('geneva'),
+ ('gibson'),
+ ('gloria'),
+ ('gordon'),
+ ('gravity'),
+ ('ground'),
+ ('habitat'),
+ ('harlem'),
+ ('hazard'),
+ ('herbert'),
+ ('hobby'),
+ ('house'),
+ ('icon'),
+ ('immune'),
+ ('india'),
+ ('inside'),
+ ('isotope'),
+ ('jamaica'),
+ ('jazz'),
+ ('joker'),
+ ('juliet'),
+ ('jupiter'),
+ ('kevin'),
+ ('korea'),
+ ('latin'),
+ ('legal'),
+ ('lexicon'),
+ ('limbo'),
+ ('lithium'),
+ ('logo'),
+ ('lucas'),
+ ('madrid'),
+ ('major'),
+ ('manual'),
+ ('mars'),
+ ('maximum'),
+ ('medical'),
+ ('mental'),
+ ('meter'),
+ ('miguel'),
+ ('mimosa'),
+ ('miranda'),
+ ('modern'),
+ ('money'),
+ ('morgan'),
+ ('motor'),
+ ('mystic'),
+ ('nebula'),
+ ('network'),
+ ('nice'),
+ ('nitro'),
+ ('norway'),
+ ('nurse'),
+ ('octavia'),
+ ('olympic'),
+ ('opus'),
+ ('orient'),
+ ('othello'),
+ ('pacific'),
+ ('panama'),
+ ('paper'),
+ ('parking'),
+ ('pasta'),
+ ('paul'),
+ ('people'),
+ ('permit'),
+ ('phrase'),
+ ('pilgrim'),
+ ('planet'),
+ ('pocket'),
+ ('police'),
+ ('popular'),
+ ('prefer'),
+ ('presto'),
+ ('private'),
+ ('project'),
+ ('proxy'),
+ ('python'),
+ ('quota'),
+ ('rainbow'),
+ ('raymond'),
+ ('region'),
+ ('report'),
+ ('reward'),
+ ('risk'),
+ ('robot'),
+ ('rose'),
+ ('russian'),
+ ('sailor'),
+ ('salt'),
+ ('saturn'),
+ ('scorpio'),
+ ('second'),
+ ('seminar'),
+ ('shadow'),
+ ('shave'),
+ ('shock'),
+ ('silence'),
+ ('sinatra'),
+ ('sleep'),
+ ('social'),
+ ('sonata'),
+ ('spain'),
+ ('sphere'),
+ ('spray'),
+ ('state'),
+ ('stone'),
+ ('strong'),
+ ('sugar'),
+ ('supreme'),
+ ('swing'),
+ ('talent'),
+ ('telecom'),
+ ('thermos'),
+ ('tina'),
+ ('tommy'),
+ ('torso'),
+ ('trade'),
+ ('trick'),
+ ('tropic'),
+ ('turtle'),
+ ('uniform'),
+ ('user'),
+ ('vega'),
+ ('vertigo'),
+ ('village'),
+ ('visible'),
+ ('vocal'),
+ ('voyage'),
+ ('weekend'),
+ ('winter'),
+ ('year'),
+ ('zipper')
+ ;
+ SELECT editdist3('cost1');
+} {{}}
+do_execsql_test 310 {
+ SELECT editdist3(a.x,b.x), a.x, b.x
+ FROM words a, words b
+ WHERE a.x0 OR b>0;
+}
+
+do_execsql_test 6.2 {
+ SELECT * FROM d1;
+} {3 2}
finish_test
diff --git a/test/vacuum4.test b/test/vacuum4.test
index 326d037276..bbf0dd4379 100644
--- a/test/vacuum4.test
+++ b/test/vacuum4.test
@@ -65,3 +65,5 @@ do_test vacuum4-1.1 {
VACUUM;
}
} {}
+
+finish_test
diff --git a/test/vacuum5.test b/test/vacuum5.test
index 8e76a9393c..f203fb87ba 100644
--- a/test/vacuum5.test
+++ b/test/vacuum5.test
@@ -143,9 +143,11 @@ if {$::TEMP_STORE<3 && [permutation]!="inmemory_journal"} {
db close
tvfs delete
- do_test 3.2 {
- lrange $::openfiles 0 4
- } {test.db test.db-journal test.db-journal {} test.db-journal}
+ if {[atomic_batch_write test.db]==0} {
+ do_test 3.2 {
+ lrange $::openfiles 0 4
+ } {test.db test.db-journal test.db-journal {} test.db-journal}
+ }
}
diff --git a/test/varint.test b/test/varint.test
index 974e88f2a6..fd0ec0d415 100644
--- a/test/varint.test
+++ b/test/varint.test
@@ -30,3 +30,5 @@ foreach start {0 100 10000 1000000 0x10000000} {
}
}
}
+
+finish_test
diff --git a/test/wal2.test b/test/wal2.test
index a27f44b2ee..ac4cb9f133 100644
--- a/test/wal2.test
+++ b/test/wal2.test
@@ -85,8 +85,8 @@ do_test wal2-1.1 {
} {4 10}
set RECOVER [list \
- {0 1 lock exclusive} {1 7 lock exclusive} \
- {1 7 unlock exclusive} {0 1 unlock exclusive} \
+ {0 1 lock exclusive} {1 2 lock exclusive} {4 4 lock exclusive} \
+ {1 2 unlock exclusive} {4 4 unlock exclusive} {0 1 unlock exclusive} \
]
set READ [list \
{4 1 lock shared} {4 1 unlock shared} \
@@ -356,8 +356,10 @@ tvfs delete
set expected_locks [list]
lappend expected_locks {1 1 lock exclusive} ;# Lock checkpoint
lappend expected_locks {0 1 lock exclusive} ;# Lock writer
-lappend expected_locks {2 6 lock exclusive} ;# Lock recovery & all aReadMark[]
-lappend expected_locks {2 6 unlock exclusive} ;# Unlock recovery & aReadMark[]
+lappend expected_locks {2 1 lock exclusive} ;# Lock recovery
+lappend expected_locks {4 4 lock exclusive} ;# Lock all aReadMark[]
+lappend expected_locks {2 1 unlock exclusive} ;# Unlock recovery
+lappend expected_locks {4 4 unlock exclusive} ;# Unlock all aReadMark[]
lappend expected_locks {0 1 unlock exclusive} ;# Unlock writer
lappend expected_locks {3 1 lock exclusive} ;# Lock aReadMark[0]
lappend expected_locks {3 1 unlock exclusive} ;# Unlock aReadMark[0]
@@ -545,15 +547,23 @@ do_test wal2-6.3.4 {
BEGIN;
INSERT INTO t1 VALUES('Groucho');
}
- list [file exists test.db-wal] [file exists test.db-journal]
-} {0 1}
+} {}
+if {[atomic_batch_write test.db]==0} {
+ do_test wal2-6.3.4.1 {
+ list [file exists test.db-wal] [file exists test.db-journal]
+ } {0 1}
+}
do_test wal2-6.3.5 {
execsql { PRAGMA lock_status }
} {main exclusive temp closed}
do_test wal2-6.3.6 {
execsql { COMMIT }
- list [file exists test.db-wal] [file exists test.db-journal]
-} {0 1}
+} {}
+if {[atomic_batch_write test.db]==0} {
+ do_test wal2-6.3.6.1 {
+ list [file exists test.db-wal] [file exists test.db-journal]
+ } {0 1}
+}
do_test wal2-6.3.7 {
execsql { PRAGMA lock_status }
} {main exclusive temp closed}
@@ -578,8 +588,8 @@ do_test wal2-6.4.1 {
} {}
set RECOVERY {
- {0 1 lock exclusive} {1 7 lock exclusive}
- {1 7 unlock exclusive} {0 1 unlock exclusive}
+ {0 1 lock exclusive} {1 2 lock exclusive} {4 4 lock exclusive}
+ {1 2 unlock exclusive} {4 4 unlock exclusive} {0 1 unlock exclusive}
}
set READMARK0_READ {
{3 1 lock shared} {3 1 unlock shared}
@@ -1091,7 +1101,7 @@ if {$::tcl_platform(platform) == "unix"} {
foreach {tn db_perm wal_perm shm_perm can_open can_read can_write} {
2 00644 00644 00644 1 1 1
3 00644 00400 00644 1 1 0
- 4 00644 00644 00400 1 0 0
+ 4 00644 00644 00400 1 1 0
5 00400 00644 00644 1 1 0
7 00644 00000 00644 1 0 0
diff --git a/test/walfault.test b/test/walfault.test
index 4e7064d53b..6cb760b258 100644
--- a/test/walfault.test
+++ b/test/walfault.test
@@ -552,7 +552,7 @@ do_faultsim_test walfault-14 -prep {
#-------------------------------------------------------------------------
# Test fault-handling when switching out of exclusive-locking mode.
#
-do_test walfault-14-pre {
+do_test walfault-15-pre {
faultsim_delete_and_reopen
execsql {
PRAGMA auto_vacuum = 0;
@@ -565,7 +565,7 @@ do_test walfault-14-pre {
}
faultsim_save_and_close
} {}
-do_faultsim_test walfault-14 -prep {
+do_faultsim_test walfault-15 -prep {
faultsim_restore_and_reopen
execsql {
SELECT count(*) FROM abc;
diff --git a/test/walmode.test b/test/walmode.test
index 4e14d54d4f..f760823c8d 100644
--- a/test/walmode.test
+++ b/test/walmode.test
@@ -45,15 +45,17 @@ do_test walmode-1.2 {
file size test.db
} {1024}
-set expected_sync_count 3
-if {$::tcl_platform(platform)!="windows"} {
- ifcapable dirsync {
- incr expected_sync_count
+if {[atomic_batch_write test.db]==0} {
+ set expected_sync_count 3
+ if {$::tcl_platform(platform)!="windows"} {
+ ifcapable dirsync {
+ incr expected_sync_count
+ }
}
+ do_test walmode-1.3 {
+ set sqlite_sync_count
+ } $expected_sync_count
}
-do_test walmode-1.3 {
- set sqlite_sync_count
-} $expected_sync_count
do_test walmode-1.4 {
file exists test.db-wal
@@ -106,9 +108,11 @@ do_test walmode-4.1 {
execsql { INSERT INTO t1 VALUES(1, 2) }
execsql { PRAGMA journal_mode = persist }
} {persist}
-do_test walmode-4.2 {
- list [file exists test.db-journal] [file exists test.db-wal]
-} {1 0}
+if {[atomic_batch_write test.db]==0} {
+ do_test walmode-4.2 {
+ list [file exists test.db-journal] [file exists test.db-wal]
+ } {1 0}
+}
do_test walmode-4.3 {
execsql { SELECT * FROM t1 }
} {1 2}
@@ -117,9 +121,11 @@ do_test walmode-4.4 {
sqlite3 db test.db
execsql { SELECT * FROM t1 }
} {1 2}
-do_test walmode-4.5 {
- list [file exists test.db-journal] [file exists test.db-wal]
-} {1 0}
+if {[atomic_batch_write test.db]==0} {
+ do_test walmode-4.5 {
+ list [file exists test.db-journal] [file exists test.db-wal]
+ } {1 0}
+}
# Test that nothing goes wrong if a connection is prevented from changing
# from WAL to rollback mode because a second connection has the database
diff --git a/test/walprotocol.test b/test/walprotocol.test
index ee8d0b72a5..b1d9e8c01f 100644
--- a/test/walprotocol.test
+++ b/test/walprotocol.test
@@ -52,21 +52,21 @@ do_test 1.1 {
set ::locks [list]
sqlite3 db test.db -vfs T
execsql { SELECT * FROM x }
- lrange $::locks 0 3
-} [list {0 1 lock exclusive} {1 7 lock exclusive} \
- {1 7 unlock exclusive} {0 1 unlock exclusive} \
+ lrange $::locks 0 5
+} [list {0 1 lock exclusive} {1 2 lock exclusive} {4 4 lock exclusive} \
+ {1 2 unlock exclusive} {4 4 unlock exclusive} {0 1 unlock exclusive} \
]
do_test 1.2 {
db close
set ::locks [list]
sqlite3 db test.db -vfs T
execsql { SELECT * FROM x }
- lrange $::locks 0 3
-} [list {0 1 lock exclusive} {1 7 lock exclusive} \
- {1 7 unlock exclusive} {0 1 unlock exclusive} \
+ lrange $::locks 0 5
+} [list {0 1 lock exclusive} {1 2 lock exclusive} {4 4 lock exclusive} \
+ {1 2 unlock exclusive} {4 4 unlock exclusive} {0 1 unlock exclusive} \
]
proc lock_callback {method filename handle lock} {
- if {$lock == "1 7 lock exclusive"} { return SQLITE_BUSY }
+ if {$lock == "1 2 lock exclusive"} { return SQLITE_BUSY }
return SQLITE_OK
}
puts "# Warning: This next test case causes SQLite to call xSleep(1) 100 times."
@@ -90,6 +90,18 @@ do_test 1.4 {
sqlite3 db test.db -vfs T
catchsql { SELECT * FROM x }
} {1 {locking protocol}}
+
+puts "# Warning: Third time!"
+proc lock_callback {method filename handle lock} {
+ if {$lock == "4 4 lock exclusive"} { return SQLITE_BUSY }
+ return SQLITE_OK
+}
+do_test 1.5 {
+ db close
+ set ::locks [list]
+ sqlite3 db test.db -vfs T
+ catchsql { SELECT * FROM x }
+} {1 {locking protocol}}
db close
T delete
@@ -135,13 +147,14 @@ T filter xShmLock
T script lock_callback
proc lock_callback {method file handle spec} {
- if {$spec == "1 7 unlock exclusive"} {
+ if {$spec == "1 2 unlock exclusive"} {
T filter {}
set ::r [catchsql { SELECT * FROM b } db2]
}
}
sqlite3 db test.db
sqlite3 db2 test.db
+puts "# Warning: Another slow test!"
do_test 2.5 {
execsql { SELECT * FROM b }
} {Tehran Qom Markazi Qazvin Gilan Ardabil}
@@ -157,12 +170,13 @@ sqlite3 db2 test.db
T filter xShmLock
T script lock_callback
proc lock_callback {method file handle spec} {
- if {$spec == "1 7 unlock exclusive"} {
+ if {$spec == "1 2 unlock exclusive"} {
T filter {}
set ::r [catchsql { SELECT * FROM b } db2]
}
}
unset ::r
+puts "# Warning: Last one!"
do_test 2.7 {
execsql { SELECT * FROM b }
} {Tehran Qom Markazi Qazvin Gilan Ardabil}
diff --git a/test/walro.test b/test/walro.test
index f46e44d4cb..cae52db6d3 100644
--- a/test/walro.test
+++ b/test/walro.test
@@ -101,10 +101,11 @@ do_multiclient_test tn {
code1 { db close }
list [file exists test.db-wal] [file exists test.db-shm]
} {1 1}
+
do_test 1.2.2 {
code1 { sqlite3 db file:test.db?readonly_shm=1 }
- sql1 { SELECT * FROM t1 }
- } {a b c d e f g h i j}
+ list [catch { sql1 { SELECT * FROM t1 } } msg] $msg
+ } {0 {a b c d e f g h i j}}
do_test 1.2.3 {
code1 { db close }
@@ -113,10 +114,10 @@ do_multiclient_test tn {
file attributes test.db-shm -permissions r--r--r--
code1 { sqlite3 db file:test.db?readonly_shm=1 }
csql1 { SELECT * FROM t1 }
- } {1 {attempt to write a readonly database}}
+ } {0 {a b c d e f g h i j}}
do_test 1.2.4 {
code1 { sqlite3_extended_errcode db }
- } {SQLITE_READONLY_RECOVERY}
+ } {SQLITE_OK}
do_test 1.2.5 {
file attributes test.db-shm -permissions rw-r--r--
@@ -138,11 +139,15 @@ do_multiclient_test tn {
# Now check that if the readonly_shm option is not supplied, or if it
# is set to zero, it is not possible to connect to the database without
# read-write access to the shm.
+ #
+ # UPDATE: os_unix.c now opens the *-shm file in readonly mode
+ # automatically.
+ #
do_test 1.3.1 {
code1 { db close }
code1 { sqlite3 db test.db }
csql1 { SELECT * FROM t1 }
- } {1 {unable to open database file}}
+ } {0 {a b c d e f g h i j k l}}
# Also test that if the -shm file can be opened for read/write access,
# it is not if readonly_shm=1 is present in the URI.
@@ -161,10 +166,10 @@ do_multiclient_test tn {
file attributes test.db-shm -permissions r--r--r--
code1 { sqlite3 db file:test.db?readonly_shm=1 }
csql1 { SELECT * FROM t1 }
- } {1 {attempt to write a readonly database}}
+ } {0 {a b c d e f g h i j k l}}
do_test 1.3.2.4 {
code1 { sqlite3_extended_errcode db }
- } {SQLITE_READONLY_RECOVERY}
+ } {SQLITE_OK}
#-----------------------------------------------------------------------
# Test cases 1.4.* check that checkpoints and log wraps don't prevent
diff --git a/test/walro2.test b/test/walro2.test
new file mode 100644
index 0000000000..34408c1695
--- /dev/null
+++ b/test/walro2.test
@@ -0,0 +1,406 @@
+# 2011 May 09
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for using WAL databases in read-only mode.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/lock_common.tcl
+source $testdir/wal_common.tcl
+set ::testprefix walro2
+
+# And only if the build is WAL-capable.
+#
+ifcapable !wal {
+ finish_test
+ return
+}
+
+proc copy_to_test2 {bZeroShm} {
+ forcecopy test.db test.db2
+ forcecopy test.db-wal test.db2-wal
+ if {$bZeroShm} {
+ forcedelete test.db2-shm
+ set fd [open test.db2-shm w]
+ seek $fd [expr [file size test.db-shm]-1]
+ puts -nonewline $fd "\0"
+ close $fd
+ } else {
+ forcecopy test.db-shm test.db2-shm
+ }
+}
+
+# Most systems allocate the *-shm file in 32KB trunks. But on UNIX systems
+# for which the getpagesize() call returns greater than 32K, the *-shm
+# file is allocated in page-sized units (since you cannot mmap part of
+# a page). The following code sets variable $MINSHMSZ to the smallest
+# possible *-shm file (i.e. the greater of 32KB and the system page-size).
+#
+do_execsql_test 0.0 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t1(x);
+} {wal}
+set MINSHMSZ [file size test.db-shm]
+
+foreach bZeroShm {0 1} {
+set TN [expr $bZeroShm+1]
+do_multiclient_test tn {
+
+ # Close all connections and delete the database.
+ #
+ code1 { db close }
+ code2 { db2 close }
+ code3 { db3 close }
+ forcedelete test.db
+
+ # Do not run tests with the connections in the same process.
+ #
+ if {$tn==2} continue
+
+ foreach c {code1 code2 code3} {
+ $c {
+ sqlite3_shutdown
+ sqlite3_config_uri 1
+ }
+ }
+
+ do_test $TN.1.1 {
+ code2 { sqlite3 db2 test.db }
+ sql2 {
+ CREATE TABLE t1(x, y);
+ PRAGMA journal_mode = WAL;
+ INSERT INTO t1 VALUES('a', 'b');
+ INSERT INTO t1 VALUES('c', 'd');
+ }
+ file exists test.db-shm
+ } {1}
+
+ do_test $TN.1.2.1 {
+ copy_to_test2 $bZeroShm
+ code1 {
+ sqlite3 db file:test.db2?readonly_shm=1
+ }
+
+ sql1 { SELECT * FROM t1 }
+ } {a b c d}
+ do_test $TN.1.2.2 {
+ sql1 { SELECT * FROM t1 }
+ } {a b c d}
+
+ do_test $TN.1.3.1 {
+ code3 { sqlite3 db3 test.db2 }
+ sql3 { SELECT * FROM t1 }
+ } {a b c d}
+
+ do_test $TN.1.3.2 {
+ sql1 { SELECT * FROM t1 }
+ } {a b c d}
+
+ code1 { db close }
+ code2 { db2 close }
+ code3 { db3 close }
+
+ do_test $TN.2.1 {
+ code2 { sqlite3 db2 test.db }
+ sql2 {
+ INSERT INTO t1 VALUES('e', 'f');
+ INSERT INTO t1 VALUES('g', 'h');
+ }
+ file exists test.db-shm
+ } {1}
+
+ do_test $TN.2.2 {
+ copy_to_test2 $bZeroShm
+ code1 {
+ sqlite3 db file:test.db2?readonly_shm=1
+ }
+ sql1 {
+ BEGIN;
+ SELECT * FROM t1;
+ }
+ } {a b c d e f g h}
+
+ do_test $TN.2.3.1 {
+ code3 { sqlite3 db3 test.db2 }
+ sql3 { SELECT * FROM t1 }
+ } {a b c d e f g h}
+ do_test $TN.2.3.2 {
+ sql3 { INSERT INTO t1 VALUES('i', 'j') }
+ code3 { db3 close }
+ sql1 { COMMIT }
+ } {}
+ do_test $TN.2.3.3 {
+ sql1 { SELECT * FROM t1 }
+ } {a b c d e f g h i j}
+
+
+ #-----------------------------------------------------------------------
+ # 3.1.*: That a readonly_shm connection can read a database file if both
+ # the *-wal and *-shm files are zero bytes in size.
+ #
+ # 3.2.*: That it flushes the cache if, between transactions on a db with a
+ # zero byte *-wal file, some other connection modifies the db, then
+ # does "PRAGMA wal_checkpoint=truncate" to truncate the wal file
+ # back to zero bytes in size.
+ #
+ # 3.3.*: That, if between transactions some other process wraps the wal
+ # file, the readonly_shm client reruns recovery.
+ #
+ catch { code1 { db close } }
+ catch { code2 { db2 close } }
+ catch { code3 { db3 close } }
+ do_test $TN.3.1.0 {
+ list [file exists test.db-wal] [file exists test.db-shm]
+ } {0 0}
+ do_test $TN.3.1.1 {
+ close [open test.db-wal w]
+ close [open test.db-shm w]
+ code1 {
+ sqlite3 db file:test.db?readonly_shm=1
+ }
+ sql1 { SELECT * FROM t1 }
+ } {a b c d e f g h}
+
+ do_test $TN.3.2.0 {
+ list [file size test.db-wal] [file size test.db-shm]
+ } {0 0}
+ do_test $TN.3.2.1 {
+ code2 { sqlite3 db2 test.db }
+ sql2 { INSERT INTO t1 VALUES(1, 2) ; PRAGMA wal_checkpoint=truncate }
+ code2 { db2 close }
+ sql1 { SELECT * FROM t1 }
+ } {a b c d e f g h 1 2}
+ do_test $TN.3.2.2 {
+ list [file size test.db-wal] [file size test.db-shm]
+ } [list 0 $MINSHMSZ]
+
+ do_test $TN.3.3.0 {
+ code2 { sqlite3 db2 test.db }
+ sql2 {
+ INSERT INTO t1 VALUES(3, 4);
+ INSERT INTO t1 VALUES(5, 6);
+ INSERT INTO t1 VALUES(7, 8);
+ INSERT INTO t1 VALUES(9, 10);
+ }
+ code2 { db2 close }
+ code1 { db close }
+ list [file size test.db-wal] [file size test.db-shm]
+ } [list [wal_file_size 4 1024] $MINSHMSZ]
+ do_test $TN.3.3.1 {
+ code1 { sqlite3 db file:test.db?readonly_shm=1 }
+ sql1 { SELECT * FROM t1 }
+ } {a b c d e f g h 1 2 3 4 5 6 7 8 9 10}
+ do_test $TN.3.3.2 {
+ code2 { sqlite3 db2 test.db }
+ sql2 {
+ PRAGMA wal_checkpoint;
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES('i', 'ii');
+ }
+ code2 { db2 close }
+ list [file size test.db-wal] [file size test.db-shm]
+ } [list [wal_file_size 4 1024] $MINSHMSZ]
+ do_test $TN.3.3.3 {
+ sql1 { SELECT * FROM t1 }
+ } {i ii}
+
+ #-----------------------------------------------------------------------
+ #
+ #
+ catch { code1 { db close } }
+ catch { code2 { db2 close } }
+ catch { code3 { db3 close } }
+
+ do_test $TN.4.0 {
+ code1 { forcedelete test.db }
+ code1 { sqlite3 db test.db }
+ sql1 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t1(x);
+ INSERT INTO t1 VALUES('hello');
+ INSERT INTO t1 VALUES('world');
+ }
+
+ copy_to_test2 $bZeroShm
+
+ code1 { db close }
+ } {}
+
+ do_test $TN.4.1.1 {
+ code2 { sqlite3 db2 file:test.db2?readonly_shm=1 }
+ sql2 { SELECT * FROM t1 }
+ } {hello world}
+
+ do_test $TN.4.1.2 {
+ code3 { sqlite3 db3 test.db2 }
+ sql3 {
+ INSERT INTO t1 VALUES('!');
+ PRAGMA wal_checkpoint = truncate;
+ }
+ code3 { db3 close }
+ } {}
+ do_test $TN.4.1.3 {
+ sql2 { SELECT * FROM t1 }
+ } {hello world !}
+
+ catch { code1 { db close } }
+ catch { code2 { db2 close } }
+ catch { code3 { db3 close } }
+
+ do_test $TN.4.2.1 {
+ code1 { sqlite3 db test.db }
+ sql1 {
+ INSERT INTO t1 VALUES('!');
+ INSERT INTO t1 VALUES('!');
+
+ PRAGMA cache_size = 10;
+ CREATE TABLE t2(x);
+
+ BEGIN;
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<500
+ )
+ INSERT INTO t2 SELECT randomblob(500) FROM s;
+ SELECT count(*) FROM t2;
+ }
+ } {500}
+ set sz [file size test.db-wal]
+ do_test $TN.4.2.2.(sz=$sz) {
+ expr {$sz>400000}
+ } {1}
+ do_test $TN.4.2.4 {
+ file_control_persist_wal db 1; db close
+
+ copy_to_test2 $bZeroShm
+ code2 { sqlite3 db2 file:test.db2?readonly_shm=1 }
+ sql2 {
+ SELECT * FROM t1;
+ SELECT count(*) FROM t2;
+ }
+ } {hello world ! ! 0}
+
+ #-----------------------------------------------------------------------
+ #
+ #
+ catch { code1 { db close } }
+ catch { code2 { db2 close } }
+ catch { code3 { db3 close } }
+
+ do_test $TN.5.0 {
+ code1 { forcedelete test.db }
+ code1 { sqlite3 db test.db }
+ sql1 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t1(x);
+ INSERT INTO t1 VALUES('hello');
+ INSERT INTO t1 VALUES('world');
+ INSERT INTO t1 VALUES('!');
+ INSERT INTO t1 VALUES('world');
+ INSERT INTO t1 VALUES('hello');
+ }
+
+ copy_to_test2 $bZeroShm
+
+ code1 { db close }
+ } {}
+
+ do_test $TN.5.1 {
+ code2 { sqlite3 db2 file:test.db2?readonly_shm=1 }
+ sql2 {
+ SELECT * FROM t1;
+ }
+ } {hello world ! world hello}
+
+ do_test $TN.5.2 {
+ code1 {
+ proc handle_read {op args} {
+ if {$op=="xRead" && [file tail [lindex $args 0]]=="test.db2-wal"} {
+ set ::res2 [sql2 { SELECT * FROM t1 }]
+ }
+ puts "$msg xRead $args"
+ return "SQLITE_OK"
+ }
+ testvfs tvfs -fullshm 1
+
+ sqlite3 db file:test.db2?vfs=tvfs
+ db eval { SELECT * FROM sqlite_master }
+
+ tvfs filter xRead
+ tvfs script handle_read
+ }
+ sql1 {
+ PRAGMA wal_checkpoint = truncate;
+ }
+ code1 { set ::res2 }
+ } {hello world ! world hello}
+
+ do_test $TN.5.3 {
+ code1 { db close }
+ code1 { tvfs delete }
+ } {}
+
+ #-----------------------------------------------------------------------
+ #
+ #
+ catch { code1 { db close } }
+ catch { code2 { db2 close } }
+ catch { code3 { db3 close } }
+
+ do_test $TN.6.1 {
+ code1 { forcedelete test.db }
+ code1 { sqlite3 db test.db }
+ sql1 {
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t1(x);
+ INSERT INTO t1 VALUES('hello');
+ INSERT INTO t1 VALUES('world');
+ INSERT INTO t1 VALUES('!');
+ INSERT INTO t1 VALUES('world');
+ INSERT INTO t1 VALUES('hello');
+ }
+
+ copy_to_test2 $bZeroShm
+
+ code1 { db close }
+ } {}
+
+ do_test $TN.6.2 {
+ code1 {
+ set ::nRem 5
+ proc handle_read {op args} {
+ if {$op=="xRead" && [file tail [lindex $args 0]]=="test.db2-wal"} {
+ incr ::nRem -1
+ if {$::nRem==0} {
+ code2 { sqlite3 db2 test.db2 }
+ sql2 { PRAGMA wal_checkpoint = truncate }
+ }
+ }
+ return "SQLITE_OK"
+ }
+ testvfs tvfs -fullshm 1
+
+ tvfs filter xRead
+ tvfs script handle_read
+
+ sqlite3 db file:test.db2?readonly_shm=1&vfs=tvfs
+ db eval { SELECT * FROM t1 }
+ }
+ } {hello world ! world hello}
+
+ do_test $TN.6.3 {
+ code1 { db close }
+ code1 { tvfs delete }
+ } {}
+}
+} ;# foreach bZeroShm
+
+finish_test
diff --git a/test/walrofault.test b/test/walrofault.test
new file mode 100644
index 0000000000..3e66e2d920
--- /dev/null
+++ b/test/walrofault.test
@@ -0,0 +1,60 @@
+# 2011 May 09
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for using WAL databases in read-only mode.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/malloc_common.tcl
+set ::testprefix walro2
+
+# And only if the build is WAL-capable.
+#
+ifcapable !wal {
+ finish_test
+ return
+}
+
+db close
+sqlite3_shutdown
+sqlite3_config_uri 1
+sqlite3 db test.db
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(b);
+ PRAGMA journal_mode = wal;
+ INSERT INTO t1 VALUES('hello');
+ INSERT INTO t1 VALUES('world');
+ INSERT INTO t1 VALUES('!');
+ INSERT INTO t1 VALUES('world');
+ INSERT INTO t1 VALUES('hello');
+ PRAGMA cache_size = 10;
+ BEGIN;
+ WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<30 )
+ INSERT INTO t1(b) SELECT randomblob(800) FROM s;
+} {wal}
+file_control_persist_wal db 1; db close
+faultsim_save_and_close
+
+do_faultsim_test 1 -faults oom* -prep {
+ catch { db close }
+ faultsim_restore
+ sqlite3 db file:test.db?readonly_shm=1
+} -body {
+ execsql { SELECT * FROM t1 }
+} -test {
+ faultsim_test_result {0 {hello world ! world hello}}
+}
+
+
+
+finish_test
diff --git a/test/walthread.test b/test/walthread.test
index 6249ce11af..8e5df9e589 100644
--- a/test/walthread.test
+++ b/test/walthread.test
@@ -327,59 +327,61 @@ do_thread_test2 walthread-1 -seconds $seconds(walthread-1) -init {
# the number of write-transactions performed using a rollback journal.
# For example, "192 w, 185 r".
#
-do_thread_test2 walthread-2 -seconds $seconds(walthread-2) -init {
- execsql { CREATE TABLE t1(x INTEGER PRIMARY KEY, y UNIQUE) }
-} -thread RB 2 {
+if {[atomic_batch_write test.db]==0} {
+ do_thread_test2 walthread-2 -seconds $seconds(walthread-2) -init {
+ execsql { CREATE TABLE t1(x INTEGER PRIMARY KEY, y UNIQUE) }
+ } -thread RB 2 {
- db close
- set nRun 0
- set nDel 0
- while {[tt_continue]} {
- sqlite3 db test.db
- db busy busyhandler
- db eval { SELECT * FROM sqlite_master }
- catch { db eval { PRAGMA journal_mode = DELETE } }
- db eval {
- BEGIN;
- INSERT INTO t1 VALUES(NULL, randomblob(100+$E(pid)));
- }
- incr nRun 1
- incr nDel [file exists test.db-journal]
- if {[file exists test.db-journal] + [file exists test.db-wal] != 1} {
- error "File-system looks bad..."
- }
- db eval COMMIT
-
- integrity_check
db close
- }
- list $nRun $nDel
- set {} "[expr $nRun-$nDel] w, $nDel r"
-
-} -thread WAL 2 {
- db close
- set nRun 0
- set nDel 0
- while {[tt_continue]} {
- sqlite3 db test.db
- db busy busyhandler
- db eval { SELECT * FROM sqlite_master }
- catch { db eval { PRAGMA journal_mode = WAL } }
- db eval {
- BEGIN;
- INSERT INTO t1 VALUES(NULL, randomblob(110+$E(pid)));
+ set nRun 0
+ set nDel 0
+ while {[tt_continue]} {
+ sqlite3 db test.db
+ db busy busyhandler
+ db eval { SELECT * FROM sqlite_master }
+ catch { db eval { PRAGMA journal_mode = DELETE } }
+ db eval {
+ BEGIN;
+ INSERT INTO t1 VALUES(NULL, randomblob(100+$E(pid)));
+ }
+ incr nRun 1
+ incr nDel [file exists test.db-journal]
+ if {[file exists test.db-journal] + [file exists test.db-wal] != 1} {
+ error "File-system looks bad..."
+ }
+ db eval COMMIT
+
+ integrity_check
+ db close
}
- incr nRun 1
- incr nDel [file exists test.db-journal]
- if {[file exists test.db-journal] + [file exists test.db-wal] != 1} {
- error "File-system looks bad..."
- }
- db eval COMMIT
-
- integrity_check
+ list $nRun $nDel
+ set {} "[expr $nRun-$nDel] w, $nDel r"
+
+ } -thread WAL 2 {
db close
+ set nRun 0
+ set nDel 0
+ while {[tt_continue]} {
+ sqlite3 db test.db
+ db busy busyhandler
+ db eval { SELECT * FROM sqlite_master }
+ catch { db eval { PRAGMA journal_mode = WAL } }
+ db eval {
+ BEGIN;
+ INSERT INTO t1 VALUES(NULL, randomblob(110+$E(pid)));
+ }
+ incr nRun 1
+ incr nDel [file exists test.db-journal]
+ if {[file exists test.db-journal] + [file exists test.db-wal] != 1} {
+ error "File-system looks bad..."
+ }
+ db eval COMMIT
+
+ integrity_check
+ db close
+ }
+ set {} "[expr $nRun-$nDel] w, $nDel r"
}
- set {} "[expr $nRun-$nDel] w, $nDel r"
}
do_thread_test walthread-3 -seconds $seconds(walthread-3) -init {
diff --git a/test/whereF.test b/test/whereF.test
index 8b272a5c53..121cc3cf22 100644
--- a/test/whereF.test
+++ b/test/whereF.test
@@ -215,4 +215,98 @@ ifcapable json1&&vtab {
} {{{"foo":"meep","other":12345}}}
}
+# 2018-01-27
+# Ticket https://sqlite.org/src/tktview/ec32177c99ccac2b180fd3ea2083
+# Incorrect result when using the new OR clause factoring optimization
+#
+# This is the original test case as reported on the sqlite-users mailing
+# list
+#
+do_execsql_test 7.1 {
+ DROP TABLE IF EXISTS cd;
+ CREATE TABLE cd ( cdid INTEGER PRIMARY KEY NOT NULL, genreid integer );
+ CREATE INDEX cd_idx_genreid ON cd (genreid);
+ INSERT INTO cd ( cdid, genreid ) VALUES
+ ( 1, 1 ),
+ ( 2, NULL ),
+ ( 3, NULL ),
+ ( 4, NULL ),
+ ( 5, NULL );
+
+ SELECT cdid
+ FROM cd me
+ WHERE 2 > (
+ SELECT COUNT( * )
+ FROM cd rownum__emulation
+ WHERE
+ (
+ me.genreid IS NOT NULL
+ AND
+ rownum__emulation.genreid IS NULL
+ )
+ OR
+ (
+ me.genreid IS NOT NULL
+ AND
+ rownum__emulation.genreid IS NOT NULL
+ AND
+ rownum__emulation.genreid < me.genreid
+ )
+ OR
+ (
+ ( me.genreid = rownum__emulation.genreid OR ( me.genreid IS NULL
+ AND rownum__emulation.genreid IS NULL ) )
+ AND
+ rownum__emulation.cdid > me.cdid
+ )
+ );
+} {4 5}
+
+# Simplified test cases from the ticket
+#
+do_execsql_test 7.2 {
+ DROP TABLE IF EXISTS t1;
+ DROP TABLE IF EXISTS t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1(a,b) VALUES(1,1);
+ CREATE TABLE t2(aa INTEGER PRIMARY KEY, bb);
+ INSERT INTO t2(aa,bb) VALUES(1,1),(2,NULL),(3,NULL);
+ SELECT (
+ SELECT COUNT(*) FROM t2
+ WHERE ( t1.b IS NOT NULL AND t2.bb IS NULL )
+ OR ( t2.bb < t1.b )
+ OR ( t1.b IS t2.bb AND t2.aa > t1.a )
+ )
+ FROM t1;
+} {2}
+
+# The fix for ticket ec32177c99ccac2b180fd3ea2083 only makes a difference
+# in the output when there is a TERM_VNULL entry in the WhereClause array.
+# And TERM_VNULL entries are only generated when compiling with
+# SQLITE_ENABLE_STAT4. Nevertheless, it is correct that TERM_VIRTUAL terms
+# should not participate in the factoring optimization. In all cases other
+# than TERM_VNULL, participation is harmless, but it does consume a few
+# extra CPU cycles.
+#
+# The following test verifies that the TERM_VIRTUAL terms resulting from
+# a GLOB operator do not appear anywhere in the generated code. This
+# confirms that the problem is fixed, even on builds that omit STAT4.
+#
+do_execsql_test 7.3 {
+ DROP TABLE IF EXISTS t1;
+ DROP TABLE IF EXISTS t2;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
+ INSERT INTO t1(a,b) VALUES(1,'abcxyz');
+ CREATE TABLE t2(aa INTEGER PRIMARY KEY, bb TEXT);
+ INSERT INTO t2(aa,bb) VALUES(1,'abc'),(2,'wxyz'),(3,'xyz');
+ CREATE INDEX t2bb ON t2(bb);
+ EXPLAIN SELECT (
+ SELECT COUNT(*) FROM t2
+ WHERE ( t1.b GLOB 'a*z' AND t2.bb='xyz' )
+ OR ( t2.bb = t1.b )
+ OR ( t2.aa = t1.a )
+ )
+ FROM t1;
+} {~/ (Lt|Ge) /}
+
finish_test
diff --git a/test/with2.test b/test/with2.test
index 02d10b5112..004ec94b97 100644
--- a/test/with2.test
+++ b/test/with2.test
@@ -326,7 +326,7 @@ do_catchsql_test 6.5 {
do_catchsql_test 6.6 {
WITH x AS (SELECT * FROM t1) DELETE FROM t2 WHERE
-} {/1 {near .* syntax error}/}
+} {1 {incomplete input}}
do_catchsql_test 6.7 {
WITH x AS (SELECT * FROM t1) DELETE FROM t2 WHRE 1;
diff --git a/test/with4.test b/test/with4.test
new file mode 100644
index 0000000000..b0eeba6d14
--- /dev/null
+++ b/test/with4.test
@@ -0,0 +1,52 @@
+# 2018-02-15
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this file is testing the WITH clause in TRIGGERs and VIEWs.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set ::testprefix with4
+
+ifcapable {!cte} {
+ finish_test
+ return
+}
+
+do_execsql_test 100 {
+ ATTACH ':memory:' AS aux;
+ CREATE TABLE main.t1(a,b);
+ CREATE TABLE aux.t2(x,y);
+ INSERT INTO t1 VALUES(1,2);
+ INSERT INTO t2 VALUES(3,4);
+} {}
+do_catchsql_test 110 {
+ CREATE VIEW v1 AS SELECT * FROM t1, aux.t2;
+} {1 {view v1 cannot reference objects in database aux}}
+do_catchsql_test 120 {
+ CREATE VIEW v2 AS WITH v(m,n) AS (SELECT x,y FROM aux.t2) SELECT * FROM t1, v;
+} {1 {view v2 cannot reference objects in database aux}}
+do_catchsql_test 130 {
+ CREATE VIEW v2 AS WITH v(m,n) AS (SELECT 5,?2) SELECT * FROM t1, v;
+} {1 {parameters are not allowed in views}}
+
+do_catchsql_test 200 {
+ CREATE TRIGGER r1 AFTER INSERT ON t1 BEGIN
+ WITH v(m,n) AS (SELECT x,y FROM aux.t2) SELECT * FROM t1, v;
+ END;
+} {1 {trigger r1 cannot reference objects in database aux}}
+do_catchsql_test 210 {
+ CREATE TRIGGER r1 AFTER INSERT ON t1 BEGIN
+ WITH v(m,n) AS (SELECT 5,?2) SELECT * FROM t1, v;
+ END;
+} {1 {trigger cannot use variables}}
+
+finish_test
diff --git a/test/zerodamage.test b/test/zerodamage.test
index a87e50b7b5..83bae737df 100644
--- a/test/zerodamage.test
+++ b/test/zerodamage.test
@@ -74,7 +74,7 @@ do_test zerodamage-2.0 {
UPDATE t1 SET y=randomblob(50) WHERE x=123;
}
concat [file_control_powersafe_overwrite db -1] [set ::max_journal_size]
-} {0 1 2576}
+} [list 0 1 [expr ([atomic_batch_write test.db]==0)*2576]]
# Repeat the previous step with zero-damage turned off. This time the
# maximum rollback journal size should be much larger.
@@ -87,7 +87,7 @@ do_test zerodamage-2.1 {
UPDATE t1 SET y=randomblob(50) WHERE x=124;
}
concat [file_control_powersafe_overwrite db -1] [set ::max_journal_size]
-} {0 0 24704}
+} [list 0 0 [expr ([atomic_batch_write test.db]==0)*24704]]
if {[wal_is_capable]} {
# Run a WAL-mode transaction with POWERSAFE_OVERWRITE on to verify that the
diff --git a/test/zipfile.test b/test/zipfile.test
new file mode 100644
index 0000000000..2bb3f07892
--- /dev/null
+++ b/test/zipfile.test
@@ -0,0 +1,640 @@
+# 2017 December 9
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix zipfile
+
+ifcapable !vtab {
+ finish_test; return
+}
+if {[catch {load_static_extension db zipfile} error]} {
+ puts "Skipping zipfile tests, hit load error: $error"
+ finish_test; return
+}
+
+proc readfile {f} {
+ set fd [open $f]
+ fconfigure $fd -translation binary -encoding binary
+ set data [read $fd]
+ close $fd
+ set data
+}
+
+if {$::tcl_platform(platform)=="unix" && [catch {exec unzip}]==0} {
+ set ::UNZIP 1
+ load_static_extension db fileio
+ proc do_unzip {file} {
+ forcedelete test_unzip
+ file mkdir test_unzip
+ exec unzip -d test_unzip $file
+
+ set res [db eval {
+ SELECT replace(name,'test_unzip/',''),mode,mtime,data
+ FROM fsdir('test_unzip')
+ WHERE name!='test_unzip'
+ ORDER BY name
+ }]
+ set res
+ }
+}
+
+
+# The argument is a blob (not a hex string) containing a zip archive.
+# This proc removes the extended timestamp fields from the archive
+# and returns the result.
+#
+proc remove_timestamps {blob} {
+ set hex [binary encode hex $blob]
+ set hex [string map {55540500 00000500} $hex]
+ binary decode hex $hex
+}
+
+
+# Argument $file is the name of a zip archive on disk. This function
+# executes test cases to check that the results of each of the following
+# are the same:
+#
+# SELECT * FROM zipfile($file)
+# SELECT * FROM zipfile( readfile($file) )
+# SELECT * FROM zipfile(
+# (SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file))
+# )
+#
+proc do_zipfile_blob_test {tn file} {
+
+ db func r readfile
+ set q1 {SELECT name,mode,mtime,method,quote(data) FROM zipfile($file)}
+ set q2 {SELECT name,mode,mtime,method,quote(data) FROM zipfile( r($file) )}
+ set q3 {SELECT name,mode,mtime,method,quote(data) FROM zipfile(
+ ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) )
+ )}
+
+
+ set r1 [db eval $q1]
+ set r2 [db eval $q2]
+ set r3 [db eval $q3]
+ #puts $r1
+ #puts $r2
+ #puts $r3
+
+ uplevel [list do_test $tn.1 [list set {} $r2] $r1]
+ uplevel [list do_test $tn.2 [list set {} $r3] $r1]
+}
+
+# Argument $file is a zip file on disk. This command runs tests to:
+#
+# 1. Unpack the archive with unix command [unzip] and compare the
+# results to reading the same archive using the zipfile() table
+# valued function.
+#
+# 2. Creates a new archive with the same contents using the zipfile()
+# aggregate function as follows:
+#
+# SELECT writefile('test_unzip.zip',
+# ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) )
+# );
+#
+# Then tests that unpacking the new archive using [unzip] produces
+# the same results as in (1).
+#
+proc do_unzip_test {tn file} {
+ if {[info vars ::UNZIP]==""} { return }
+ db func sss strip_slash
+
+ db eval {
+ SELECT writefile('test_unzip.zip',
+ ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) )
+ );
+ }
+
+ set r1 [db eval {
+ SELECT sss(name),mode,mtime,data FROM zipfile($file) ORDER BY name
+ }]
+ set r2 [do_unzip $file]
+ set r3 [do_unzip test_unzip.zip]
+
+ uplevel [list do_test $tn.1 [list set {} $r2] $r1]
+ uplevel [list do_test $tn.2 [list set {} $r3] $r1]
+}
+proc strip_slash {in} { regsub {/$} $in {} }
+
+proc do_zip_tests {tn file} {
+ uplevel do_zipfile_blob_test $tn.1 $file
+ uplevel do_unzip_test $tn.2 $file
+}
+
+forcedelete test.zip
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE temp.zz USING zipfile('test.zip');
+ PRAGMA table_info(zz);
+} {
+ 0 name {} 1 {} 1
+ 1 mode {} 0 {} 0
+ 2 mtime {} 0 {} 0
+ 3 sz {} 0 {} 0
+ 4 rawdata {} 0 {} 0
+ 5 data {} 0 {} 0
+ 6 method {} 0 {} 0
+}
+
+do_catchsql_test 1.1.0.1 {
+ INSERT INTO zz(name, mode, mtime, sz, rawdata, method)
+ VALUES('f.txt', '-rw-r--r--', 1000000000, 5, 'abcde', 0);
+} {1 {constraint failed}}
+do_catchsql_test 1.1.0.2 {
+ INSERT INTO zz(name, mtime, sz, data, method)
+ VALUES('g.txt', 1000000002, 5, '12345', 0);
+} {1 {constraint failed}}
+do_catchsql_test 1.1.0.3 {
+ INSERT INTO zz(name, mtime, rawdata, method)
+ VALUES('g.txt', 1000000002, '12345', 0);
+} {1 {constraint failed}}
+do_catchsql_test 1.1.0.4 {
+ INSERT INTO zz(name, data, method)
+ VALUES('g.txt', '12345', 7);
+} {1 {constraint failed}}
+
+do_execsql_test 1.1.1 {
+ INSERT INTO zz(name, mode, mtime, data, method)
+ VALUES('f.txt', '-rw-r--r--', 1000000000, 'abcde', 0);
+}
+do_execsql_test 1.1.2 {
+ INSERT INTO zz(name, mode, mtime, data, method)
+ VALUES('g.txt', NULL, 1000000002, '12345', 0);
+}
+
+do_execsql_test 1.2 {
+ SELECT name, mtime, data FROM zipfile('test.zip')
+} {
+ f.txt 1000000000 abcde
+ g.txt 1000000002 12345
+}
+do_zip_tests 1.2a test.zip
+
+do_execsql_test 1.3 {
+ INSERT INTO zz(name, mode, mtime, data) VALUES('h.txt',
+ '-rw-r--r--', 1000000004, 'aaaaaaaaaabbbbbbbbbb'
+ );
+}
+do_zip_tests 1.3a test.zip
+
+do_execsql_test 1.4 {
+ SELECT name, mtime, data, method FROM zipfile('test.zip');
+} {
+ f.txt 1000000000 abcde 0
+ g.txt 1000000002 12345 0
+ h.txt 1000000004 aaaaaaaaaabbbbbbbbbb 8
+}
+
+ifcapable json1 {
+ do_execsql_test 1.4.1 {
+ SELECT name, json_extract( zipfile_cds(z) , '$.crc32')!=0
+ FROM zipfile('test.zip');
+ } {
+ f.txt 1
+ g.txt 1
+ h.txt 1
+ }
+}
+do_catchsql_test 1.4.2 {
+ SELECT zipfile_cds(mode) FROM zipfile('test.zip');
+} {0 {{} {} {}}}
+
+do_execsql_test 1.5.1 {
+ BEGIN;
+ INSERT INTO zz(name, mode, mtime, data, method)
+ VALUES('i.txt', '-rw-r--r--', 1000000006, 'zxcvb', 0);
+ SELECT name FROM zz;
+ COMMIT;
+} {f.txt g.txt h.txt i.txt}
+do_execsql_test 1.5.2 {
+ SELECT name FROM zz;
+} {f.txt g.txt h.txt i.txt}
+do_execsql_test 1.5.3 {
+ SELECT data FROM zz WHERE name='i.txt';
+} {zxcvb}
+
+do_execsql_test 1.6.0 {
+ DELETE FROM zz WHERE name='g.txt';
+ SELECT name FROM zz;
+} {f.txt h.txt i.txt}
+
+do_execsql_test 1.6.1 {
+ SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
+} {
+ f.txt 33188 1000000000 abcde 0
+ h.txt 33188 1000000004 aaaaaaaaaabbbbbbbbbb 8
+ i.txt 33188 1000000006 zxcvb 0
+}
+do_zip_tests 1.6.1a test.zip
+
+do_execsql_test 1.6.2 {
+ UPDATE zz SET mtime=4 WHERE name='i.txt';
+ SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
+} {
+ f.txt 33188 1000000000 abcde 0
+ h.txt 33188 1000000004 aaaaaaaaaabbbbbbbbbb 8
+ i.txt 33188 4 zxcvb 0
+}
+
+do_execsql_test 1.6.3 {
+ UPDATE zz SET mode='-rw-r--r-x' WHERE name='h.txt';
+ SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
+} {
+ f.txt 33188 1000000000 abcde 0
+ h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8
+ i.txt 33188 4 zxcvb 0
+}
+do_zip_tests 1.6.3a test.zip
+
+do_execsql_test 1.6.4 {
+ UPDATE zz SET name = 'blue.txt' WHERE name='f.txt';
+ SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
+} {
+ blue.txt 33188 1000000000 abcde 0
+ h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8
+ i.txt 33188 4 zxcvb 0
+}
+do_zip_tests 1.6.4a test.zip
+
+do_execsql_test 1.6.5 {
+ UPDATE zz SET data = 'edcba' WHERE name='blue.txt';
+ SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
+} {
+ blue.txt 33188 1000000000 edcba 0
+ h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8
+ i.txt 33188 4 zxcvb 0
+}
+
+do_execsql_test 1.6.6 {
+ UPDATE zz SET mode=NULL, data = NULL WHERE name='blue.txt';
+ SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
+} {
+ blue.txt/ 16877 1000000000 {} 0
+ h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8
+ i.txt 33188 4 zxcvb 0
+}
+
+do_catchsql_test 1.6.7 {
+ UPDATE zz SET data=NULL WHERE name='i.txt'
+} {1 {constraint failed}}
+do_execsql_test 1.6.8 {
+ SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
+} {
+ blue.txt/ 16877 1000000000 {} 0
+ h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8
+ i.txt 33188 4 zxcvb 0
+}
+
+do_execsql_test 1.6.8 {
+ UPDATE zz SET data = '' WHERE name='i.txt';
+ SELECT name,mode,mtime,data,method from zipfile('test.zip');
+} {
+ blue.txt/ 16877 1000000000 {} 0
+ h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8
+ i.txt 33188 4 {} 0
+}
+
+do_execsql_test 1.6.9 {
+ SELECT a.name, a.data
+ FROM zz AS a, zz AS b
+ WHERE a.name=+b.name AND +a.mode=b.mode
+} {
+ blue.txt/ {}
+ h.txt aaaaaaaaaabbbbbbbbbb
+ i.txt {}
+}
+
+do_execsql_test 1.6.10 {
+ SELECT name, data FROM zz WHERE name LIKE '%txt'
+} {
+ h.txt aaaaaaaaaabbbbbbbbbb
+ i.txt {}
+}
+
+do_execsql_test 1.7 {
+ DELETE FROM zz;
+ SELECT * FROM zz;
+} {}
+
+#-------------------------------------------------------------------------
+db close
+forcedelete test.zip
+reset_db
+load_static_extension db fileio
+load_static_extension db zipfile
+do_execsql_test 2.1 {
+ CREATE VIRTUAL TABLE zzz USING zipfile('test.zip');
+ INSERT INTO zzz(name, mode) VALUES('dirname', 'drwxr-xr-x');
+ SELECT name, mode, data FROM zzz;
+} {dirname/ 16877 {}}
+do_execsql_test 2.2 {
+ INSERT INTO zzz(name, data) VALUES('dirname2', NULL);
+ INSERT INTO zzz(name, data) VALUES('dirname2/file1.txt', 'abcdefghijklmnop');
+ SELECT name, mode, data FROM zzz;
+} {
+ dirname/ 16877 {}
+ dirname2/ 16877 {}
+ dirname2/file1.txt 33188 abcdefghijklmnop
+}
+
+do_catchsql_test 2.3 {
+ UPDATE zzz SET name = 'dirname3' WHERE name = 'dirname/';
+} {0 {}}
+do_execsql_test 2.4 {
+ SELECT name, mode, data FROM zzz;
+} {
+ dirname3/ 16877 {}
+ dirname2/ 16877 {}
+ dirname2/file1.txt 33188 abcdefghijklmnop
+}
+do_zip_tests 2.4a test.zip
+
+# If on unix, check that the [unzip] utility can unpack our archive.
+#
+if {$::tcl_platform(platform)=="unix"} {
+ do_test 2.5.1 {
+ forcedelete dirname
+ forcedelete dirname2
+ set rc [catch { exec unzip test.zip > /dev/null } msg]
+ list $rc $msg
+ } {0 {}}
+ do_test 2.5.2 { file isdir dirname3 } 1
+ do_test 2.5.3 { file isdir dirname2 } 1
+ do_test 2.5.4 { file isdir dirname2/file1.txt } 0
+ do_test 2.5.5 {
+ set fd [open dirname2/file1.txt]
+ set data [read $fd]
+ close $fd
+ set data
+ } {abcdefghijklmnop}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+forcedelete test.zip
+load_static_extension db zipfile
+
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE temp.x1 USING zipfile('test.zip');
+ INSERT INTO x1(name, data) VALUES('dir1/', NULL);
+ INSERT INTO x1(name, data) VALUES('file1', '1234');
+ INSERT INTO x1(name, data) VALUES('dir1/file2', '5678');
+}
+foreach {tn fname} {
+ 1 dir1
+ 2 file1
+ 3 dir1/file2
+} {
+ do_catchsql_test 3.1.$tn.0 {
+ INSERT INTO x1(name, data) VALUES($fname, NULL);
+ } {1 {constraint failed}}
+ do_catchsql_test 3.1.$tn.1 {
+ INSERT INTO x1(name, data) VALUES($fname || '/', NULL);
+ } {1 {constraint failed}}
+ do_catchsql_test 3.1.$tn.2 {
+ INSERT INTO x1(name, data) VALUES($fname, 'abcd');
+ } {1 {constraint failed}}
+}
+
+do_catchsql_test 3.2 {
+ SELECT rowid FROM x1
+} {1 {no such column: rowid}}
+
+#-------------------------------------------------------------------------
+# Test some error conditions.
+#
+do_catchsql_test 4.1 {
+ CREATE VIRTUAL TABLE yyy USING zipfile();
+} {1 {zipfile constructor requires one argument}}
+do_catchsql_test 4.2 {
+ CREATE VIRTUAL TABLE yyy USING zipfile('test.zip', 'test.zip');
+} {1 {zipfile constructor requires one argument}}
+
+do_catchsql_test 4.3 {
+ SELECT * FROM zipfile()
+} {1 {zipfile() function requires an argument}}
+
+do_catchsql_test 4.4 {
+ SELECT * FROM zipfile('/path/that/does/not/exist')
+} {1 {cannot open file: /path/that/does/not/exist}}
+
+foreach {tn mode} {
+ 1 abcd
+ 2 brwxrwxrwx
+ 3 lrwxrrxrwx
+} {
+ do_catchsql_test 4.5.$tn {
+ WITH m(m) AS ( SELECT $mode)
+ SELECT zipfile('a.txt', m, 1000, 'xyz') FROM m
+ } [list 1 "zipfile: parse error in mode: $mode"]
+}
+
+do_catchsql_test 4.6 {
+ WITH c(name,data) AS ( SELECT 'a.txt', 'abc')
+ SELECT zipfile(name) FROM c
+} {1 {wrong number of arguments to function zipfile()}}
+
+do_catchsql_test 4.7 {
+ WITH c(name,data) AS (
+ SELECT 'a.txt', 'abc' UNION ALL
+ SELECT NULL, 'def'
+ )
+ SELECT zipfile(name,data) FROM c
+} {1 {first argument to zipfile() must be non-NULL}}
+
+do_catchsql_test 4.7 {
+ WITH c(name,data,method) AS (
+ SELECT 'a.txt', 'abc', 0
+ UNION SELECT 'b.txt', 'def', 8
+ UNION SELECT 'c.txt', 'ghi', 16
+ )
+ SELECT zipfile(name,NULL,NULL,data,method) FROM c
+} {1 {illegal method value: 16}}
+
+do_catchsql_test 4.8 {
+ WITH c(name,data) AS (
+ SELECT 'a.txt', 'abc'
+ UNION SELECT 'b.txt', 'def'
+ UNION SELECT 'c.txt/', 'ghi'
+ )
+ SELECT zipfile(name,NULL,NULL,data) FROM c
+} {1 {non-directory name must not end with /}}
+
+#--------------------------------------------------------------------------
+
+db func rt remove_timestamps
+do_execsql_test 5.0 {
+ WITH c(name,mtime,data) AS (
+ SELECT 'a.txt', 946684800, 'abc'
+ )
+ SELECT name,mtime,data FROM zipfile(
+ ( SELECT rt( zipfile(name,NULL,mtime,data,NULL) ) FROM c )
+ )
+} {
+ a.txt 946684800 abc
+}
+
+if {[info vars ::UNZIP]!=""} {
+ifcapable datetime {
+ load_static_extension db fileio
+ forcedelete test1.zip test2.zip
+ do_test 6.0 {
+ execsql {
+ WITH c(name,mtime,data) AS (
+ SELECT 'a.txt', 946684800, 'abc' UNION ALL
+ SELECT 'b.txt', 1000000000, 'abc' UNION ALL
+ SELECT 'c.txt', 1111111000, 'abc'
+ )
+ SELECT writefile('test1.zip', rt( zipfile(name, NULL, mtime, data) ) ),
+ writefile('test2.zip', ( zipfile(name, NULL, mtime, data) ) )
+ FROM c;
+ }
+ forcedelete test_unzip
+ file mkdir test_unzip
+ exec unzip -d test_unzip test1.zip
+
+ db eval {
+ SELECT name, strftime('%s', mtime, 'unixepoch', 'localtime')
+ FROM fsdir('test_unzip') WHERE name!='test_unzip'
+ ORDER BY name
+ }
+ } [list {*}{
+ test_unzip/a.txt 946684800
+ test_unzip/b.txt 1000000000
+ test_unzip/c.txt 1111111000
+ }]
+
+ do_execsql_test 6.1 {
+ SELECT name, mtime, data FROM zipfile('test1.zip')
+ } {
+ a.txt 946684800 abc
+ b.txt 1000000000 abc
+ c.txt 1111111000 abc
+ }
+
+ do_test 6.2 {
+ forcedelete test_unzip
+ file mkdir test_unzip
+ exec unzip -d test_unzip test2.zip
+
+ db eval {
+ SELECT name, mtime
+ FROM fsdir('test_unzip') WHERE name!='test_unzip'
+ ORDER BY name
+ }
+ } [list {*}{
+ test_unzip/a.txt 946684800
+ test_unzip/b.txt 1000000000
+ test_unzip/c.txt 1111111000
+ }]
+
+ do_execsql_test 6.3 {
+ SELECT name, mtime, sz, rawdata, data FROM zipfile('test2.zip')
+ } {
+ a.txt 946684800 3 abc abc
+ b.txt 1000000000 3 abc abc
+ c.txt 1111111000 3 abc abc
+ }
+}
+}
+
+#-------------------------------------------------------------------------
+# Force an IO error by truncating the zip archive to zero bytes in size
+# while it is being read.
+forcedelete test.zip
+do_test 7.0 {
+ execsql {
+ WITH c(name,data) AS (
+ SELECT '1', randomblob(1000000) UNION ALL
+ SELECT '2', randomblob(1000000) UNION ALL
+ SELECT '3', randomblob(1000000)
+ )
+ SELECT writefile('test.zip', zipfile(name, data) ) FROM c;
+ }
+
+ list [catch {
+ db eval { SELECT name, data FROM zipfile('test.zip') } {
+ if {$name==2} { close [open test.zip w+] }
+ }
+ } msg] $msg
+} {1 {error in fread()}}
+
+forcedelete test.zip
+do_execsql_test 8.0.1 {
+ CREATE VIRTUAL TABLE zz USING zipfile('test.zip');
+ BEGIN;
+ INSERT INTO zz(name, data) VALUES('a.txt', '1');
+ INSERT INTO zz(name, data) VALUES('b.txt', '2');
+ INSERT INTO zz(name, data) VALUES('c.txt', '1');
+ INSERT INTO zz(name, data) VALUES('d.txt', '2');
+ SELECT name, data FROM zz;
+} {
+ a.txt 1 b.txt 2 c.txt 1 d.txt 2
+}
+do_test 8.0.2 {
+ db eval { SELECT name, data FROM zz } {
+ if { $data=="2" } { db eval { DELETE FROM zz WHERE name=$name } }
+ }
+ execsql { SELECT name, data FROM zz }
+} {a.txt 1 c.txt 1}
+do_test 8.0.3 {
+ db eval { SELECT name, data FROM zz } {
+ db eval { DELETE FROM zz WHERE name=$name }
+ }
+ execsql { SELECT name, data FROM zz }
+} {}
+execsql COMMIT
+
+do_execsql_test 8.1.1 {
+ CREATE VIRTUAL TABLE nogood USING zipfile('test_unzip');
+}
+do_catchsql_test 8.1.2 {
+ INSERT INTO nogood(name, data) VALUES('abc', 'def');
+} {1 {zipfile: failed to open file test_unzip for writing}}
+
+do_execsql_test 8.2.1 {
+ DROP TABLE nogood;
+ BEGIN;
+ CREATE VIRTUAL TABLE nogood USING zipfile('test_unzip');
+}
+do_catchsql_test 8.2.2 {
+ INSERT INTO nogood(name, data) VALUES('abc', 'def');
+} {1 {zipfile: failed to open file test_unzip for writing}}
+do_execsql_test 8.2.3 {
+ COMMIT;
+}
+
+forcedelete test.zip
+do_execsql_test 8.3.1 {
+ BEGIN;
+ CREATE VIRTUAL TABLE ok USING zipfile('test.zip');
+ INSERT INTO ok(name, data) VALUES ('sqlite3', 'elf');
+ COMMIT;
+}
+
+#-------------------------------------------------------------------------
+# Test that the zipfile aggregate correctly adds and removes "/" from
+# the ends of directory file names.
+do_execsql_test 9.0 {
+ WITH src(nm) AS (
+ VALUES('dir1') UNION ALL
+ VALUES('dir2/') UNION ALL
+ VALUES('dir3//') UNION ALL
+ VALUES('dir4///') UNION ALL
+ VALUES('/')
+ )
+ SELECT name FROM zipfile((SELECT zipfile(nm, NULL) FROM src))
+} {dir1/ dir2/ dir3/ dir4/ /}
+finish_test
+
diff --git a/test/zipfile2.test b/test/zipfile2.test
new file mode 100644
index 0000000000..f3509d0a54
--- /dev/null
+++ b/test/zipfile2.test
@@ -0,0 +1,208 @@
+# 2018 January 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.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix zipfile2
+
+ifcapable !vtab {
+ finish_test; return
+}
+if {[catch {load_static_extension db zipfile} error]} {
+ puts "Skipping zipfile2 tests, hit load error: $error"
+ finish_test; return
+}
+
+proc blobliteral {str} {
+ set concat [string map {" " "" "\n" ""} $str]
+ return "X'$concat'"
+}
+
+proc blob {str} {
+ binary decode hex $str
+}
+
+proc findall {needle haystack} {
+ set L [list]
+ set start 0
+ while { [set idx [string first $needle $haystack $start]]>=0 } {
+ lappend L $idx
+ set start [expr $idx+1]
+ }
+ set L
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE aaa USING zipfile('testzip');
+ CREATE VIRTUAL TABLE bbb USING zipfile("testzip");
+ CREATE VIRTUAL TABLE ccc USING zipfile(`testzip`);
+ CREATE VIRTUAL TABLE ddd USING zipfile([testzip]);
+ CREATE VIRTUAL TABLE eee USING zipfile(testzip);
+ CREATE VIRTUAL TABLE fff USING zipfile('test''zip');
+}
+
+do_test 2.0 {
+ forcedelete testdir
+ file mkdir testdir
+ execsql { CREATE VIRTUAL TABLE hhh USING zipfile('testdir') }
+ catchsql { SELECT * FROM hhh }
+} {1 {error in fread()}}
+
+
+set archive {
+ 504B0304140000080000D4A52BEC09F3B6E0110000001100000005000900612E
+ 747874555405000140420F00636F6E74656E7473206F6620612E747874504B03
+ 04140000080000D4A52BECD98916A7110000001100000005000900622E747874
+ 555405000140420F00636F6E74656E7473206F6620622E747874504B01021E03
+ 140000080000D4A52BEC09F3B6E0110000001100000005000900000000000000
+ 0000A48100000000612E747874555405000140420F00504B01021E0314000008
+ 0000D4A52BECD98916A71100000011000000050009000000000000000000A481
+ 3D000000622E747874555405000140420F00504B050600000000020002007800
+ 00007A0000000000
+}
+
+if 0 {
+ # This test is broken - the archive generated is slightly different
+ # depending on the zlib version used.
+ do_execsql_test 3.1 {
+ WITH contents(name,mtime,data) AS (
+ VALUES('a.txt', 1000000, 'contents of a.txt') UNION ALL
+ VALUES('b.txt', 1000000, 'contents of b.txt')
+ ) SELECT quote( zipfile(name,NULL,mtime,data) ) FROM contents;
+ } [blobliteral $archive]
+}
+
+
+set blob [blob $archive]
+do_execsql_test 3.2 {
+ SELECT name,mtime,data FROM zipfile($blob)
+} {
+ a.txt 1000000 {contents of a.txt}
+ b.txt 1000000 {contents of b.txt}
+}
+
+# Corrupt each of the 0x50 0x4B (ascii "PK") headers in the file
+# Test that in each case this causes an error.
+#
+set L [findall 504B $archive]
+for {set i 0} {$i < [llength $L]} {incr i} {
+ set idx [lindex $L $i]
+ set a [string replace $archive $idx [expr $idx+3] 0000]
+ set blob [blob $a]
+ do_catchsql_test 3.3.$i {
+ SELECT name,mtime,data FROM zipfile($blob)
+ } {/1 .*/}
+}
+
+# Change the "extra info id" for all extended-timestamp fields.
+set L [findall 5554 $archive]
+for {set i 0} {$i < [llength $L]} {incr i} {
+ set idx [lindex $L $i]
+ set a [string replace $archive $idx [expr $idx+3] 1234]
+ set blob [blob $a]
+ do_execsql_test 3.4.$i {
+ SELECT name,data FROM zipfile($blob)
+ } {
+ a.txt {contents of a.txt}
+ b.txt {contents of b.txt}
+ }
+}
+
+for {set i 0} {$i < [llength $L]} {incr i} {
+ set idx [lindex $L $i]
+ set a [string replace $archive [expr $idx+8] [expr $idx+9] 00]
+ set blob [blob $a]
+ do_execsql_test 3.5.$i {
+ SELECT name,data FROM zipfile($blob)
+ } {
+ a.txt {contents of a.txt}
+ b.txt {contents of b.txt}
+ }
+}
+
+# set blob [db one {
+# WITH contents(name,mtime,data) AS (
+# VALUES('a.txt', 1000000, 'aaaaaaaaaaaaaaaaaaaaaaa')
+# ) SELECT quote( zipfile(name,NULL,mtime,data) ) FROM contents;
+# }]
+# set blob [string range $blob 2 end]
+# set blob [string range $blob 0 end-1]
+# while {[string length $blob]>0} {
+# puts [string range $blob 0 63]
+# set blob [string range $blob 64 end]
+# }
+# exit
+
+set archive2 {
+ 504B0304140000080800D4A52BEC08F54C6E050000001700000005000900612E
+ 747874555405000140420F004B4CC40A00504B01021E03140000080800D4A52B
+ EC08F54C6E0500000017000000050009000000000000000000A4810000000061
+ 2E747874555405000140420F00504B050600000000010001003C000000310000
+ 000000
+}
+set blob [blob $archive2]
+do_execsql_test 4.0 {
+ SELECT name,mtime,data,method FROM zipfile($blob)
+} {
+ a.txt 1000000 aaaaaaaaaaaaaaaaaaaaaaa 8
+}
+
+set L [findall 17000000 $archive2]
+set a $archive2
+foreach i $L { set a [string replace $a $i [expr $i+7] 16000000] }
+set blob [blob $a]
+do_catchsql_test 4.1 {
+ SELECT name,mtime,data,method FROM zipfile($blob)
+} {1 {inflate() failed (0)}}
+
+# Check the response to an unknown compression method (set data to NULL).
+set blob [blob [string map {0800 0900} $archive2]]
+do_execsql_test 4.2 {
+ SELECT name,mtime,data IS NULL,method FROM zipfile($blob)
+} {a.txt 1000000 1 9}
+
+# Corrupt the EOCDS signature bytes in various ways.
+foreach {tn sub} {
+ 1 {504B0500}
+ 2 {504B0006}
+ 3 {50000506}
+ 4 {004B0506}
+} {
+ set blob [blob [string map [list 504B0506 $sub] $archive2]]
+ do_catchsql_test 4.3.$tn {
+ SELECT * FROM zipfile($blob)
+ } {1 {cannot find end of central directory record}}
+}
+
+#-------------------------------------------------------------------------
+# Test that a zero-length file with a '/' at the end is treated as
+# a directory (data IS NULL). Even if the mode doesn't indicate
+# that it is a directory.
+
+do_test 5.0 {
+ set blob [db one {
+ WITH c(n, d) AS (
+ SELECT 'notadir', ''
+ )
+ SELECT zipfile(n, d) FROM c
+ }]
+
+ set hex [binary encode hex $blob]
+ set hex [string map {6e6f7461646972 6e6f746164692f} $hex]
+ set blob2 [binary decode hex $hex]
+
+ execsql { SELECT name, data IS NULL FROM zipfile($blob2) }
+} {notadi/ 1}
+
+
+finish_test
+
diff --git a/test/zipfilefault.test b/test/zipfilefault.test
new file mode 100644
index 0000000000..158370695e
--- /dev/null
+++ b/test/zipfilefault.test
@@ -0,0 +1,165 @@
+# 2018 January 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.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/malloc_common.tcl
+set testprefix zipfilefault
+
+ifcapable !vtab {
+ finish_test; return
+}
+if {[catch {load_static_extension db zipfile} error]} {
+ puts "Skipping zipfile2 tests, hit load error: $error"
+ finish_test; return
+}
+
+faultsim_save_and_close
+do_faultsim_test 1 -prep {
+ faultsim_restore_and_reopen
+ load_static_extension db zipfile
+ execsql { DROP TABLE IF EXISTS aaa }
+} -body {
+ execsql { CREATE VIRTUAL TABLE aaa USING zipfile('test.zip') }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+forcedelete test.zip
+sqlite3 db test.db
+load_static_extension db zipfile
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE setup USING zipfile('test.zip');
+ INSERT INTO setup(name, data) VALUES('a.txt', '1234567890');
+}
+
+do_faultsim_test 2.1 -faults oom* -body {
+ execsql { SELECT name,data FROM zipfile('test.zip') }
+} -test {
+ faultsim_test_result {0 {a.txt 1234567890}}
+}
+do_faultsim_test 2.2 -faults oom* -body {
+ execsql {
+ SELECT json_extract( zipfile_cds(z), '$.version-made-by' )
+ FROM zipfile('test.zip')
+ }
+} -test {
+ faultsim_test_result {0 798}
+}
+
+forcedelete test.zip
+reset_db
+load_static_extension db zipfile
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE setup USING zipfile('test.zip');
+ INSERT INTO setup(name, data) VALUES('a.txt', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+}
+
+do_faultsim_test 3 -faults oom* -body {
+ execsql { SELECT name,data FROM zipfile('test.zip') }
+} -test {
+ faultsim_test_result {0 {a.txt aaaaaaaaaaaaaaaaaaaaaaaaaaaa}}
+}
+
+do_faultsim_test 4 -faults oom* -body {
+ execsql {
+ WITH c(n, d) AS (
+ SELECT 1, 'aaaaaaaaaaabbbbbbbbbbaaaaaaaaaabbbbbbbbbb'
+ )
+ SELECT name, data FROM zipfile(
+ (SELECT zipfile(n, d) FROM c)
+ );
+ }
+} -test {
+ faultsim_test_result {0 {1 aaaaaaaaaaabbbbbbbbbbaaaaaaaaaabbbbbbbbbb}}
+}
+
+reset_db
+sqlite3_db_config_lookaside db 0 0 0
+load_static_extension db zipfile
+
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE setup USING zipfile('test.zip')
+}
+
+do_faultsim_test 5.1 -faults oom* -prep {
+ forcedelete test.zip
+} -body {
+ execsql {
+ INSERT INTO setup(name, data)
+ VALUES('a.txt', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+do_faultsim_test 5.2 -faults oom* -prep {
+ forcedelete test.zip
+} -body {
+ execsql {
+ INSERT INTO setup(name, data) VALUES('dir', NULL)
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+do_faultsim_test 5.3 -faults oom* -prep {
+ forcedelete test.zip
+ execsql {
+ DROP TABLE IF EXISTS setup;
+ BEGIN;
+ CREATE VIRTUAL TABLE setup USING zipfile('test.zip')
+ }
+} -body {
+ execsql {
+ INSERT INTO setup(name, data) VALUES('dir', NULL)
+ }
+} -test {
+ catchsql { COMMIT }
+ faultsim_test_result {0 {}}
+}
+
+do_faultsim_test 6.1 -faults oom* -body {
+ execsql {
+ WITH c(n, d) AS (
+ VALUES('a.txt', '1234567890') UNION ALL
+ VALUES('dir', NULL)
+ )
+ SELECT zipfile(n, d) IS NULL FROM c;
+ }
+} -test {
+ faultsim_test_result {0 0}
+}
+
+set big [string repeat 0123456789 1000]
+do_faultsim_test 6.2 -faults oom* -body {
+ execsql {
+ WITH c(n, d) AS (
+ VALUES('a.txt', $big)
+ )
+ SELECT zipfile(n, NULL, NULL, d, 0) IS NULL FROM c;
+ }
+} -test {
+ faultsim_test_result {0 0}
+}
+
+do_faultsim_test 7.0 -faults oom* -prep {
+ catch { db close }
+ sqlite3 db ""
+} -body {
+ load_static_extension db zipfile
+} -test {
+}
+
+
+finish_test
+
diff --git a/tool/addopcodes.tcl b/tool/addopcodes.tcl
index d5f843616a..798fbb15ba 100644
--- a/tool/addopcodes.tcl
+++ b/tool/addopcodes.tcl
@@ -22,6 +22,7 @@ close $in
# ILLEGAL *must* be the last two token codes and they must be in that order.
#
set extras {
+ TRUEFALSE
ISNOT
FUNCTION
COLUMN
@@ -29,6 +30,7 @@ set extras {
AGG_COLUMN
UMINUS
UPLUS
+ TRUTH
REGISTER
CONCURRENT
VECTOR
diff --git a/tool/lemon.c b/tool/lemon.c
index acc5450c99..96bbed7473 100644
--- a/tool/lemon.c
+++ b/tool/lemon.c
@@ -384,6 +384,12 @@ struct lemon {
int nrule; /* Number of rules */
int nsymbol; /* Number of terminal and nonterminal symbols */
int nterminal; /* Number of terminal symbols */
+ int minShiftReduce; /* Minimum shift-reduce action value */
+ int errAction; /* Error action value */
+ int accAction; /* Accept action value */
+ int noAction; /* No-op action value */
+ int minReduce; /* Minimum reduce action */
+ int maxAction; /* Maximum action value of any kind */
struct symbol **symbols; /* Sorted array of pointers to symbols */
int errorcnt; /* Number of errors */
struct symbol *errsym; /* The error symbol */
@@ -407,6 +413,7 @@ struct lemon {
char *tokenprefix; /* A prefix added to token names in the .h file */
int nconflict; /* Number of parsing conflicts */
int nactiontab; /* Number of entries in the yy_action[] table */
+ int nlookaheadtab; /* Number of entries in yy_lookahead[] */
int tablesize; /* Total table size of all tables in bytes */
int basisflag; /* Print only basis configurations */
int has_fallback; /* True if any %fallback is seen in the grammar */
@@ -583,10 +590,12 @@ struct acttab {
int mxLookahead; /* Maximum aLookahead[].lookahead */
int nLookahead; /* Used slots in aLookahead[] */
int nLookaheadAlloc; /* Slots allocated in aLookahead[] */
+ int nterminal; /* Number of terminal symbols */
+ int nsymbol; /* total number of symbols */
};
/* Return the number of entries in the yy_action table */
-#define acttab_size(X) ((X)->nAction)
+#define acttab_lookahead_size(X) ((X)->nAction)
/* The value for the N-th entry in yy_action */
#define acttab_yyaction(X,N) ((X)->aAction[N].action)
@@ -602,13 +611,15 @@ void acttab_free(acttab *p){
}
/* Allocate a new acttab structure */
-acttab *acttab_alloc(void){
+acttab *acttab_alloc(int nsymbol, int nterminal){
acttab *p = (acttab *) calloc( 1, sizeof(*p) );
if( p==0 ){
fprintf(stderr,"Unable to allocate memory for a new acttab.");
exit(1);
}
memset(p, 0, sizeof(*p));
+ p->nsymbol = nsymbol;
+ p->nterminal = nterminal;
return p;
}
@@ -649,16 +660,24 @@ void acttab_action(acttab *p, int lookahead, int action){
** to an empty set in preparation for a new round of acttab_action() calls.
**
** Return the offset into the action table of the new transaction.
+**
+** If the makeItSafe parameter is true, then the offset is chosen so that
+** it is impossible to overread the yy_lookaside[] table regardless of
+** the lookaside token. This is done for the terminal symbols, as they
+** come from external inputs and can contain syntax errors. When makeItSafe
+** is false, there is more flexibility in selecting offsets, resulting in
+** a smaller table. For non-terminal symbols, which are never syntax errors,
+** makeItSafe can be false.
*/
-int acttab_insert(acttab *p){
- int i, j, k, n;
+int acttab_insert(acttab *p, int makeItSafe){
+ int i, j, k, n, end;
assert( p->nLookahead>0 );
/* Make sure we have enough space to hold the expanded action table
** in the worst case. The worst case occurs if the transaction set
** must be appended to the current action table
*/
- n = p->mxLookahead + 1;
+ n = p->nsymbol + 1;
if( p->nAction + n >= p->nActionAlloc ){
int oldAlloc = p->nActionAlloc;
p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20;
@@ -680,7 +699,8 @@ int acttab_insert(acttab *p){
**
** i is the index in p->aAction[] where p->mnLookahead is inserted.
*/
- for(i=p->nAction-1; i>=0; i--){
+ end = makeItSafe ? p->mnLookahead : 0;
+ for(i=p->nAction-1; i>=end; i--){
if( p->aAction[i].lookahead==p->mnLookahead ){
/* All lookaheads and actions in the aLookahead[] transaction
** must match against the candidate aAction[i] entry. */
@@ -710,12 +730,13 @@ int acttab_insert(acttab *p){
** an empty offset in the aAction[] table in which we can add the
** aLookahead[] transaction.
*/
- if( i<0 ){
+ if( inAction, which means the
** transaction will be appended. */
- for(i=0; inActionAlloc - p->mxLookahead; i++){
+ i = makeItSafe ? p->mnLookahead : 0;
+ for(; inActionAlloc - p->mxLookahead; i++){
if( p->aAction[i].lookahead<0 ){
for(j=0; jnLookahead; j++){
k = p->aLookahead[j].lookahead - p->mnLookahead + i;
@@ -733,11 +754,19 @@ int acttab_insert(acttab *p){
}
}
/* Insert transaction set at index i. */
+#if 0
+ printf("Acttab:");
+ for(j=0; jnLookahead; j++){
+ printf(" %d", p->aLookahead[j].lookahead);
+ }
+ printf(" inserted at %d\n", i);
+#endif
for(j=0; jnLookahead; j++){
k = p->aLookahead[j].lookahead - p->mnLookahead + i;
p->aAction[k] = p->aLookahead[j];
if( k>=p->nAction ) p->nAction = k+1;
}
+ if( makeItSafe && i+p->nterminal>=p->nAction ) p->nAction = i+p->nterminal+1;
p->nLookahead = 0;
/* Return the offset that is added to the lookahead in order to get the
@@ -745,6 +774,16 @@ int acttab_insert(acttab *p){
return i - p->mnLookahead;
}
+/*
+** Return the size of the action table without the trailing syntax error
+** entries.
+*/
+int acttab_action_size(acttab *p){
+ int n = p->nAction;
+ while( n>0 && p->aAction[n-1].lookahead<0 ){ n--; }
+ return n;
+}
+
/********************** From the file "build.c" *****************************/
/*
** Routines to construction the finite state machine for the LEMON
@@ -1718,6 +1757,7 @@ int main(int argc, char **argv)
stats_line("states", lem.nxstate);
stats_line("conflicts", lem.nconflict);
stats_line("action table entries", lem.nactiontab);
+ stats_line("lookahead table entries", lem.nlookaheadtab);
stats_line("total table size (bytes)", lem.tablesize);
}
if( lem.nconflict > 0 ){
@@ -3020,6 +3060,27 @@ PRIVATE FILE *file_open(
return fp;
}
+/* Print the text of a rule
+*/
+void rule_print(FILE *out, struct rule *rp){
+ int i, j;
+ fprintf(out, "%s",rp->lhs->name);
+ /* if( rp->lhsalias ) fprintf(out,"(%s)",rp->lhsalias); */
+ fprintf(out," ::=");
+ for(i=0; inrhs; i++){
+ struct symbol *sp = rp->rhs[i];
+ if( sp->type==MULTITERMINAL ){
+ fprintf(out," %s", sp->subsym[0]->name);
+ for(j=1; jnsubsym; j++){
+ fprintf(out,"|%s", sp->subsym[j]->name);
+ }
+ }else{
+ fprintf(out," %s", sp->name);
+ }
+ /* if( rp->rhsalias[i] ) fprintf(out,"(%s)",rp->rhsalias[i]); */
+ }
+}
+
/* Duplicate the input file without comments and without actions
** on rules */
void Reprint(struct lemon *lemp)
@@ -3047,21 +3108,7 @@ void Reprint(struct lemon *lemp)
printf("\n");
}
for(rp=lemp->rule; rp; rp=rp->next){
- printf("%s",rp->lhs->name);
- /* if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */
- printf(" ::=");
- for(i=0; inrhs; i++){
- sp = rp->rhs[i];
- if( sp->type==MULTITERMINAL ){
- printf(" %s", sp->subsym[0]->name);
- for(j=1; jnsubsym; j++){
- printf("|%s", sp->subsym[j]->name);
- }
- }else{
- printf(" %s", sp->name);
- }
- /* if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */
- }
+ rule_print(stdout, rp);
printf(".");
if( rp->precsym ) printf(" [%s]",rp->precsym->name);
/* if( rp->code ) printf("\n %s",rp->code); */
@@ -3321,16 +3368,19 @@ PRIVATE int compute_action(struct lemon *lemp, struct action *ap)
switch( ap->type ){
case SHIFT: act = ap->x.stp->statenum; break;
case SHIFTREDUCE: {
- act = ap->x.rp->iRule + lemp->nstate;
/* Since a SHIFT is inherient after a prior REDUCE, convert any
** SHIFTREDUCE action with a nonterminal on the LHS into a simple
** REDUCE action: */
- if( ap->sp->index>=lemp->nterminal ) act += lemp->nrule;
+ if( ap->sp->index>=lemp->nterminal ){
+ act = lemp->minReduce + ap->x.rp->iRule;
+ }else{
+ act = lemp->minShiftReduce + ap->x.rp->iRule;
+ }
break;
}
- case REDUCE: act = ap->x.rp->iRule + lemp->nstate+lemp->nrule; break;
- case ERROR: act = lemp->nstate + lemp->nrule*2; break;
- case ACCEPT: act = lemp->nstate + lemp->nrule*2 + 1; break;
+ case REDUCE: act = lemp->minReduce + ap->x.rp->iRule; break;
+ case ERROR: act = lemp->errAction; break;
+ case ACCEPT: act = lemp->accAction; break;
default: act = -1; break;
}
return act;
@@ -4038,6 +4088,13 @@ void ReportTable(
int mnNtOfst, mxNtOfst;
struct axset *ax;
+ lemp->minShiftReduce = lemp->nstate;
+ lemp->errAction = lemp->minShiftReduce + lemp->nrule;
+ lemp->accAction = lemp->errAction + 1;
+ lemp->noAction = lemp->accAction + 1;
+ lemp->minReduce = lemp->noAction + 1;
+ lemp->maxAction = lemp->minReduce + lemp->nrule;
+
in = tplt_open(lemp);
if( in==0 ) return;
out = file_open(lemp,".c","wb");
@@ -4076,7 +4133,7 @@ void ReportTable(
minimum_size_type(0, lemp->nsymbol+1, &szCodeType)); lineno++;
fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++;
fprintf(out,"#define YYACTIONTYPE %s\n",
- minimum_size_type(0,lemp->nstate+lemp->nrule*2+5,&szActionType)); lineno++;
+ minimum_size_type(0,lemp->maxAction,&szActionType)); lineno++;
if( lemp->wildcard ){
fprintf(out,"#define YYWILDCARD %d\n",
lemp->wildcard->index); lineno++;
@@ -4144,7 +4201,7 @@ void ReportTable(
** of placing the largest action sets first */
for(i=0; inxstate*2; i++) ax[i].iOrder = i;
qsort(ax, lemp->nxstate*2, sizeof(ax[0]), axset_compare);
- pActtab = acttab_alloc();
+ pActtab = acttab_alloc(lemp->nsymbol, lemp->nterminal);
for(i=0; inxstate*2 && ax[i].nAction>0; i++){
stp = ax[i].stp;
if( ax[i].isTkn ){
@@ -4155,7 +4212,7 @@ void ReportTable(
if( action<0 ) continue;
acttab_action(pActtab, ap->sp->index, action);
}
- stp->iTknOfst = acttab_insert(pActtab);
+ stp->iTknOfst = acttab_insert(pActtab, 1);
if( stp->iTknOfstiTknOfst;
if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst;
}else{
@@ -4167,7 +4224,7 @@ void ReportTable(
if( action<0 ) continue;
acttab_action(pActtab, ap->sp->index, action);
}
- stp->iNtOfst = acttab_insert(pActtab);
+ stp->iNtOfst = acttab_insert(pActtab, 0);
if( stp->iNtOfstiNtOfst;
if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst;
}
@@ -4200,16 +4257,18 @@ void ReportTable(
** been computed */
fprintf(out,"#define YYNSTATE %d\n",lemp->nxstate); lineno++;
fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++;
+ fprintf(out,"#define YYNTOKEN %d\n",lemp->nterminal); lineno++;
fprintf(out,"#define YY_MAX_SHIFT %d\n",lemp->nxstate-1); lineno++;
- fprintf(out,"#define YY_MIN_SHIFTREDUCE %d\n",lemp->nstate); lineno++;
- i = lemp->nstate + lemp->nrule;
+ i = lemp->minShiftReduce;
+ fprintf(out,"#define YY_MIN_SHIFTREDUCE %d\n",i); lineno++;
+ i += lemp->nrule;
fprintf(out,"#define YY_MAX_SHIFTREDUCE %d\n", i-1); lineno++;
- fprintf(out,"#define YY_MIN_REDUCE %d\n", i); lineno++;
- i = lemp->nstate + lemp->nrule*2;
+ fprintf(out,"#define YY_ERROR_ACTION %d\n", lemp->errAction); lineno++;
+ fprintf(out,"#define YY_ACCEPT_ACTION %d\n", lemp->accAction); lineno++;
+ fprintf(out,"#define YY_NO_ACTION %d\n", lemp->noAction); lineno++;
+ fprintf(out,"#define YY_MIN_REDUCE %d\n", lemp->minReduce); lineno++;
+ i = lemp->minReduce + lemp->nrule;
fprintf(out,"#define YY_MAX_REDUCE %d\n", i-1); lineno++;
- fprintf(out,"#define YY_ERROR_ACTION %d\n", i); lineno++;
- fprintf(out,"#define YY_ACCEPT_ACTION %d\n", i+1); lineno++;
- fprintf(out,"#define YY_NO_ACTION %d\n", i+2); lineno++;
tplt_xfer(lemp->name,in,out,&lineno);
/* Now output the action table and its associates:
@@ -4225,13 +4284,13 @@ void ReportTable(
*/
/* Output the yy_action table */
- lemp->nactiontab = n = acttab_size(pActtab);
+ lemp->nactiontab = n = acttab_action_size(pActtab);
lemp->tablesize += n*szActionType;
fprintf(out,"#define YY_ACTTAB_COUNT (%d)\n", n); lineno++;
fprintf(out,"static const YYACTIONTYPE yy_action[] = {\n"); lineno++;
for(i=j=0; instate + lemp->nrule + 2;
+ if( action<0 ) action = lemp->noAction;
if( j==0 ) fprintf(out," /* %5d */ ", i);
fprintf(out, " %4d,", action);
if( j==9 || i==n-1 ){
@@ -4244,6 +4303,7 @@ void ReportTable(
fprintf(out, "};\n"); lineno++;
/* Output the yy_lookahead table */
+ lemp->nlookaheadtab = n = acttab_lookahead_size(pActtab);
lemp->tablesize += n*szCodeType;
fprintf(out,"static const YYCODETYPE yy_lookahead[] = {\n"); lineno++;
for(i=j=0; inxstate;
while( n>0 && lemp->sorted[n-1]->iTknOfst==NO_OFFSET ) n--;
- fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", lemp->nactiontab); lineno++;
fprintf(out, "#define YY_SHIFT_COUNT (%d)\n", n-1); lineno++;
fprintf(out, "#define YY_SHIFT_MIN (%d)\n", mnTknOfst); lineno++;
fprintf(out, "#define YY_SHIFT_MAX (%d)\n", mxTknOfst); lineno++;
@@ -4288,7 +4347,6 @@ void ReportTable(
fprintf(out, "};\n"); lineno++;
/* Output the yy_reduce_ofst[] table */
- fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++;
n = lemp->nxstate;
while( n>0 && lemp->sorted[n-1]->iNtOfst==NO_OFFSET ) n--;
fprintf(out, "#define YY_REDUCE_COUNT (%d)\n", n-1); lineno++;
@@ -4320,7 +4378,11 @@ void ReportTable(
for(i=j=0; isorted[i];
if( j==0 ) fprintf(out," /* %5d */ ", i);
- fprintf(out, " %4d,", stp->iDfltReduce+lemp->nstate+lemp->nrule);
+ if( stp->iDfltReduce<0 ){
+ fprintf(out, " %4d,", lemp->errAction);
+ }else{
+ fprintf(out, " %4d,", stp->iDfltReduce + lemp->minReduce);
+ }
if( j==9 || i==n-1 ){
fprintf(out, "\n"); lineno++;
j = 0;
@@ -4354,10 +4416,8 @@ void ReportTable(
*/
for(i=0; insymbol; i++){
lemon_sprintf(line,"\"%s\",",lemp->symbols[i]->name);
- fprintf(out," %-15s",line);
- if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; }
+ fprintf(out," /* %4d */ \"%s\",\n",i, lemp->symbols[i]->name); lineno++;
}
- if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; }
tplt_xfer(lemp->name,in,out,&lineno);
/* Generate a table containing a text string that describes every
@@ -4401,7 +4461,7 @@ void ReportTable(
if( sp==0 || sp->type==TERMINAL ||
sp->index<=0 || sp->destructor!=0 ) continue;
if( once ){
- fprintf(out, " /* Default NON-TERMINAL Destructor */\n"); lineno++;
+ fprintf(out, " /* Default NON-TERMINAL Destructor */\n");lineno++;
once = 0;
}
fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++;
@@ -4444,8 +4504,10 @@ void ReportTable(
** Note: This code depends on the fact that rules are number
** sequentually beginning with 0.
*/
- for(rp=lemp->rule; rp; rp=rp->next){
- fprintf(out," { %d, %d },\n",rp->lhs->index,-rp->nrhs); lineno++;
+ for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+ fprintf(out," { %4d, %4d }, /* (%d) ",rp->lhs->index,-rp->nrhs,i);
+ rule_print(out, rp);
+ fprintf(out," */\n"); lineno++;
}
tplt_xfer(lemp->name,in,out,&lineno);
@@ -4711,7 +4773,7 @@ void ResortStates(struct lemon *lemp)
for(i=0; instate; i++){
stp = lemp->sorted[i];
stp->nTknAct = stp->nNtAct = 0;
- stp->iDfltReduce = lemp->nrule; /* Init dflt action to "syntax error" */
+ stp->iDfltReduce = -1; /* Init dflt action to "syntax error" */
stp->iTknOfst = NO_OFFSET;
stp->iNtOfst = NO_OFFSET;
for(ap=stp->ap; ap; ap=ap->next){
@@ -4723,7 +4785,7 @@ void ResortStates(struct lemon *lemp)
stp->nNtAct++;
}else{
assert( stp->autoReduce==0 || stp->pDfltReduce==ap->x.rp );
- stp->iDfltReduce = iAction - lemp->nstate - lemp->nrule;
+ stp->iDfltReduce = iAction;
}
}
}
diff --git a/tool/lempar.c b/tool/lempar.c
index 37a5892195..1ade666916 100644
--- a/tool/lempar.c
+++ b/tool/lempar.c
@@ -72,14 +72,15 @@
** defined, then do no error processing.
** YYNSTATE the combined number of states.
** YYNRULE the number of rules in the grammar
+** YYNTOKEN Number of terminal symbols
** YY_MAX_SHIFT Maximum value for shift actions
** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions
** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions
-** YY_MIN_REDUCE Minimum value for reduce actions
-** YY_MAX_REDUCE Maximum value for reduce actions
** YY_ERROR_ACTION The yy_action[] code for syntax error
** YY_ACCEPT_ACTION The yy_action[] code for accept
** YY_NO_ACTION The yy_action[] code for no-op
+** YY_MIN_REDUCE Minimum value for reduce actions
+** YY_MAX_REDUCE Maximum value for reduce actions
*/
#ifndef INTERFACE
# define INTERFACE 1
@@ -115,9 +116,6 @@
** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then
** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE.
**
-** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE
-** and YY_MAX_REDUCE
-**
** N == YY_ERROR_ACTION A syntax error has occurred.
**
** N == YY_ACCEPT_ACTION The parser accepts its input.
@@ -125,25 +123,22 @@
** N == YY_NO_ACTION No such action. Denotes unused
** slots in the yy_action[] table.
**
+** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE
+** and YY_MAX_REDUCE
+**
** The action table is constructed as a single large table named yy_action[].
** Given state S and lookahead X, the action is computed as either:
**
** (A) N = yy_action[ yy_shift_ofst[S] + X ]
** (B) N = yy_default[S]
**
-** The (A) formula is preferred. The B formula is used instead if:
-** (1) The yy_shift_ofst[S]+X value is out of range, or
-** (2) yy_lookahead[yy_shift_ofst[S]+X] is not equal to X, or
-** (3) yy_shift_ofst[S] equal YY_SHIFT_USE_DFLT.
-** (Implementation note: YY_SHIFT_USE_DFLT is chosen so that
-** YY_SHIFT_USE_DFLT+X will be out of range for all possible lookaheads X.
-** Hence only tests (1) and (2) need to be evaluated.)
+** The (A) formula is preferred. The B formula is used instead if
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X.
**
** The formulas above are for computing the action when the lookahead is
** a terminal symbol. If the lookahead is a non-terminal (as occurs after
** a reduce action) then the yy_reduce_ofst[] array is used in place of
-** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
-** YY_SHIFT_USE_DFLT.
+** the yy_shift_ofst[] array.
**
** The following are the tables generated in this section:
**
@@ -259,13 +254,13 @@ void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
}
#endif /* NDEBUG */
-#ifndef NDEBUG
+#if defined(YYCOVERAGE) || !defined(NDEBUG)
/* For tracing shifts, the names of all terminals and nonterminals
** are required. The following table supplies these names */
static const char *const yyTokenName[] = {
%%
};
-#endif /* NDEBUG */
+#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */
#ifndef NDEBUG
/* For tracing reduce actions, the names of all rules are required.
@@ -461,6 +456,43 @@ int ParseStackPeak(void *p){
}
#endif
+/* This array of booleans keeps track of the parser statement
+** coverage. The element yycoverage[X][Y] is set when the parser
+** is in state X and has a lookahead token Y. In a well-tested
+** systems, every element of this matrix should end up being set.
+*/
+#if defined(YYCOVERAGE)
+static unsigned char yycoverage[YYNSTATE][YYNTOKEN];
+#endif
+
+/*
+** Write into out a description of every state/lookahead combination that
+**
+** (1) has not been used by the parser, and
+** (2) is not a syntax error.
+**
+** Return the number of missed state/lookahead combinations.
+*/
+#if defined(YYCOVERAGE)
+int ParseCoverage(FILE *out){
+ int stateno, iLookAhead, i;
+ int nMissed = 0;
+ for(stateno=0; statenoyytos->stateno;
- if( stateno>=YY_MIN_REDUCE ) return stateno;
+ if( stateno>YY_MAX_SHIFT ) return stateno;
assert( stateno <= YY_SHIFT_COUNT );
+#if defined(YYCOVERAGE)
+ yycoverage[stateno][iLookAhead] = 1;
+#endif
do{
i = yy_shift_ofst[stateno];
+ assert( i>=0 );
+ assert( i+YYNTOKEN<=(int)sizeof(yy_lookahead)/sizeof(yy_lookahead[0]) );
assert( iLookAhead!=YYNOCODE );
+ assert( iLookAhead < YYNTOKEN );
i += iLookAhead;
- if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){
+ if( yy_lookahead[i]!=iLookAhead ){
#ifdef YYFALLBACK
YYCODETYPE iFallback; /* Fallback token */
if( iLookAheadyytos->major],
+ fprintf(yyTraceFILE,"%s%s '%s', go to state %d\n",
+ yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major],
yyNewState);
}else{
- fprintf(yyTraceFILE,"%sShift '%s'\n",
- yyTracePrompt,yyTokenName[yypParser->yytos->major]);
+ fprintf(yyTraceFILE,"%s%s '%s', pending reduce %d\n",
+ yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major],
+ yyNewState - YY_MIN_REDUCE);
}
}
}
#else
-# define yyTraceShift(X,Y)
+# define yyTraceShift(X,Y,Z)
#endif
/*
@@ -633,7 +671,7 @@ static void yy_shift(
yytos->stateno = (YYACTIONTYPE)yyNewState;
yytos->major = (YYCODETYPE)yyMajor;
yytos->minor.yy0 = yyMinor;
- yyTraceShift(yypParser, yyNewState);
+ yyTraceShift(yypParser, yyNewState, "Shift");
}
/* The following table contains information about every rule that
@@ -651,22 +689,38 @@ static void yy_accept(yyParser*); /* Forward Declaration */
/*
** Perform a reduce action and the shift that must immediately
** follow the reduce.
+**
+** The yyLookahead and yyLookaheadToken parameters provide reduce actions
+** access to the lookahead token (if any). The yyLookahead will be YYNOCODE
+** if the lookahead token has already been consumed. As this procedure is
+** only called from one place, optimizing compilers will in-line it, which
+** means that the extra parameters have no performance impact.
*/
static void yy_reduce(
yyParser *yypParser, /* The parser */
- unsigned int yyruleno /* Number of the rule by which to reduce */
+ unsigned int yyruleno, /* Number of the rule by which to reduce */
+ int yyLookahead, /* Lookahead token, or YYNOCODE if none */
+ ParseTOKENTYPE yyLookaheadToken /* Value of the lookahead token */
){
int yygoto; /* The next state */
int yyact; /* The next action */
yyStackEntry *yymsp; /* The top of the parser's stack */
int yysize; /* Amount to pop the stack */
ParseARG_FETCH;
+ (void)yyLookahead;
+ (void)yyLookaheadToken;
yymsp = yypParser->yytos;
#ifndef NDEBUG
if( yyTraceFILE && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){
yysize = yyRuleInfo[yyruleno].nrhs;
- fprintf(yyTraceFILE, "%sReduce [%s], go to state %d.\n", yyTracePrompt,
- yyRuleName[yyruleno], yymsp[yysize].stateno);
+ if( yysize ){
+ fprintf(yyTraceFILE, "%sReduce %d [%s], go to state %d.\n",
+ yyTracePrompt,
+ yyruleno, yyRuleName[yyruleno], yymsp[yysize].stateno);
+ }else{
+ fprintf(yyTraceFILE, "%sReduce %d [%s].\n",
+ yyTracePrompt, yyruleno, yyRuleName[yyruleno]);
+ }
}
#endif /* NDEBUG */
@@ -721,16 +775,11 @@ static void yy_reduce(
/* It is not possible for a REDUCE to be followed by an error */
assert( yyact!=YY_ERROR_ACTION );
- if( yyact==YY_ACCEPT_ACTION ){
- yypParser->yytos += yysize;
- yy_accept(yypParser);
- }else{
- yymsp += yysize+1;
- yypParser->yytos = yymsp;
- yymsp->stateno = (YYACTIONTYPE)yyact;
- yymsp->major = (YYCODETYPE)yygoto;
- yyTraceShift(yypParser, yyact);
- }
+ yymsp += yysize+1;
+ yypParser->yytos = yymsp;
+ yymsp->stateno = (YYACTIONTYPE)yyact;
+ yymsp->major = (YYCODETYPE)yygoto;
+ yyTraceShift(yypParser, yyact, "... then shift");
}
/*
@@ -840,20 +889,31 @@ void Parse(
#ifndef NDEBUG
if( yyTraceFILE ){
- fprintf(yyTraceFILE,"%sInput '%s'\n",yyTracePrompt,yyTokenName[yymajor]);
+ int stateno = yypParser->yytos->stateno;
+ if( stateno < YY_MIN_REDUCE ){
+ fprintf(yyTraceFILE,"%sInput '%s' in state %d\n",
+ yyTracePrompt,yyTokenName[yymajor],stateno);
+ }else{
+ fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n",
+ yyTracePrompt,yyTokenName[yymajor],stateno-YY_MIN_REDUCE);
+ }
}
#endif
do{
yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor);
- if( yyact <= YY_MAX_SHIFTREDUCE ){
+ if( yyact >= YY_MIN_REDUCE ){
+ yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor,yyminor);
+ }else if( yyact <= YY_MAX_SHIFTREDUCE ){
yy_shift(yypParser,yyact,yymajor,yyminor);
#ifndef YYNOERRORRECOVERY
yypParser->yyerrcnt--;
#endif
yymajor = YYNOCODE;
- }else if( yyact <= YY_MAX_REDUCE ){
- yy_reduce(yypParser,yyact-YY_MIN_REDUCE);
+ }else if( yyact==YY_ACCEPT_ACTION ){
+ yypParser->yytos--;
+ yy_accept(yypParser);
+ return;
}else{
assert( yyact == YY_ERROR_ACTION );
yyminorunion.yy0 = yyminor;
diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl
index 807a169826..534ac6156a 100644
--- a/tool/mkshellc.tcl
+++ b/tool/mkshellc.tcl
@@ -30,16 +30,29 @@ puts $out {/* DO NOT EDIT!
** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script.
*/}
set in [open $topdir/src/shell.c.in rb]
+proc omit_redundant_typedefs {line} {
+ global typedef_seen
+ if {[regexp {^typedef .*;} $line]} {
+ if {[info exists typedef_seen($line)]} {
+ return "/* $line */"
+ }
+ set typedef_seen($line) 1
+ }
+ return $line
+}
while {1} {
- set lx [gets $in]
+ set lx [omit_redundant_typedefs [gets $in]]
if {[eof $in]} break;
if {[regexp {^INCLUDE } $lx]} {
set cfile [lindex $lx 1]
puts $out "/************************* Begin $cfile ******************/"
set in2 [open $topdir/src/$cfile rb]
while {![eof $in2]} {
- set lx [gets $in2]
+ set lx [omit_redundant_typedefs [gets $in2]]
if {[regexp {^#include "sqlite} $lx]} continue
+ if {[regexp {^# *include "test_windirent.h"} $lx]} {
+ set lx "/* $lx */"
+ }
set lx [string map [list __declspec(dllexport) {}] $lx]
puts $out $lx
}
diff --git a/tool/speed-check.sh b/tool/speed-check.sh
index 2cda5c8078..fc05ac98ef 100644
--- a/tool/speed-check.sh
+++ b/tool/speed-check.sh
@@ -39,6 +39,8 @@ LEAN_OPTS="$LEAN_OPTS -DSQLITE_USE_ALLOCA"
BASELINE="trunk"
doExplain=0
doCachegrind=1
+doVdbeProfile=0
+doWal=1
while test "$1" != ""; do
case $1 in
--reprepare)
@@ -62,8 +64,11 @@ while test "$1" != ""; do
--temp)
SPEEDTEST_OPTS="$SPEEDTEST_OPTS --temp 6"
;;
+ --legacy)
+ doWal=0
+ ;;
--wal)
- SPEEDTEST_OPTS="$SPEEDTEST_OPTS --journal wal"
+ doWal=1
;;
--size)
shift; SIZE=$1
@@ -78,6 +83,7 @@ while test "$1" != ""; do
rm -f vdbe_profile.out
CC_OPTS="$CC_OPTS -DVDBE_PROFILE"
doCachegrind=0
+ doVdbeProfile=1
;;
--lean)
CC_OPTS="$CC_OPTS $LEAN_OPTS"
@@ -117,6 +123,12 @@ while test "$1" != ""; do
--orm)
SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset orm"
;;
+ --cte)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset cte"
+ ;;
+ --fp)
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset fp"
+ ;;
-*)
CC_OPTS="$CC_OPTS $1"
;;
@@ -126,11 +138,17 @@ while test "$1" != ""; do
esac
shift
done
+if test $doWal -eq 1; then
+ SPEEDTEST_OPTS="$SPEEDTEST_OPTS --journal wal"
+fi
SPEEDTEST_OPTS="$SPEEDTEST_OPTS --size $SIZE"
echo "NAME = $NAME" | tee summary-$NAME.txt
echo "SPEEDTEST_OPTS = $SPEEDTEST_OPTS" | tee -a summary-$NAME.txt
echo "CC_OPTS = $CC_OPTS" | tee -a summary-$NAME.txt
rm -f cachegrind.out.* speedtest1 speedtest1.db sqlite3.o
+if test $doVdbeProfile -eq 1; then
+ rm -f vdbe_profile.out
+fi
$CC -g -Os -Wall -I. $CC_OPTS -c sqlite3.c
size sqlite3.o | tee -a summary-$NAME.txt
if test $doExplain -eq 1; then
@@ -157,6 +175,10 @@ fi
if test $doExplain -eq 1; then
./speedtest1 --explain $SPEEDTEST_OPTS | ./sqlite3 >explain-$NAME.txt
fi
-if test "$NAME" != "$BASELINE"; then
+if test $doVdbeProfile -eq 1; then
+ tclsh ../sqlite/tool/vdbe_profile.tcl >vdbeprofile-$NAME.txt
+ open vdbeprofile-$NAME.txt
+fi
+if test "$NAME" != "$BASELINE" -a $doVdbeProfile -ne 1; then
fossil test-diff --tk -c 20 cout-$BASELINE.txt cout-$NAME.txt
fi
diff --git a/tool/sqltclsh.c.in b/tool/sqltclsh.c.in
new file mode 100644
index 0000000000..da354ee935
--- /dev/null
+++ b/tool/sqltclsh.c.in
@@ -0,0 +1,51 @@
+/*
+** This is the source code to a "tclsh" that has SQLite built-in.
+**
+** The startup script is located as follows:
+**
+** (1) Open the executable as an appended SQLite database and try to
+** read the startup script out of that database.
+**
+** (2) If the first argument is a readable file, try to open that file
+** as an SQLite database and read the startup script out of that
+** database.
+**
+** (3) If the first argument is a readable file with a ".tcl" extension,
+** then try to run that script directly.
+**
+** If none of the above steps work, then the program runs as an interactive
+** tclsh.
+*/
+#define TCLSH_INIT_PROC sqlite3_tclapp_init_proc
+#define SQLITE_ENABLE_DBSTAT_VTAB 1
+#undef SQLITE_THREADSAFE
+#define SQLITE_THREADSAFE 0
+#undef SQLITE_ENABLE_COLUMN_METADATA
+#define SQLITE_OMIT_DECLTYPE 1
+#define SQLITE_OMIT_DEPRECATED 1
+#define SQLITE_OMIT_PROGRESS_CALLBACK 1
+#define SQLITE_OMIT_SHARED_CACHE 1
+#define SQLITE_DEFAULT_MEMSTATUS 0
+#define SQLITE_MAX_EXPR_DEPTH 0
+INCLUDE sqlite3.c
+INCLUDE $ROOT/ext/misc/appendvfs.c
+#ifdef SQLITE_HAVE_ZLIB
+INCLUDE $ROOT/ext/misc/zipfile.c
+INCLUDE $ROOT/ext/misc/sqlar.c
+#endif
+INCLUDE $ROOT/src/tclsqlite.c
+
+const char *sqlite3_tclapp_init_proc(Tcl_Interp *interp){
+ (void)interp;
+ sqlite3_appendvfs_init(0,0,0);
+#ifdef SQLITE_HAVE_ZLIB
+ sqlite3_auto_extension((void(*)(void))sqlite3_sqlar_init);
+ sqlite3_auto_extension((void(*)(void))sqlite3_zipfile_init);
+#endif
+
+ return
+BEGIN_STRING
+INCLUDE $ROOT/tool/sqltclsh.tcl
+END_STRING
+;
+}
diff --git a/tool/sqltclsh.tcl b/tool/sqltclsh.tcl
new file mode 100644
index 0000000000..6a4b1fe1f0
--- /dev/null
+++ b/tool/sqltclsh.tcl
@@ -0,0 +1,71 @@
+# Try to open the executable as a database and read the "scripts.data"
+# field where "scripts.name" is 'main.tcl'
+#
+catch {
+ if {![file exists $argv0] && [file exists $argv0.exe]} {
+ append argv0 .exe
+ }
+ sqlite3 db $argv0 -vfs apndvfs -create 0
+ set mainscript [db one {
+ SELECT sqlar_uncompress(data,sz) FROM sqlar WHERE name='main.tcl'
+ }]
+}
+if {[info exists mainscript]} {
+ eval $mainscript
+ return
+} else {
+ catch {db close}
+}
+
+# Try to open file named in the first argument as a database and
+# read the "scripts.data" field where "scripts.name" is 'main.tcl'
+#
+if {[llength $argv]>0 && [file readable [lindex $argv 0]]} {
+ catch {
+ sqlite3 db [lindex $argv 0] -vfs apndvfs -create 0
+ set mainscript [db one {SELECT data FROM scripts WHERE name='main.tcl'}]
+ set argv0 [lindex $argv 0]
+ set argv [lrange $argv 1 end]
+ }
+ if {[info exists mainscript]} {
+ eval $mainscript
+ return
+ } else {
+ catch {db close}
+ }
+ if {[string match *.tcl [lindex $argv 0]]} {
+ set fd [open [lindex $argv 0] rb]
+ set mainscript [read $fd]
+ close $fd
+ unset fd
+ set argv0 [lindex $argv 0]
+ set argv [lrange $argv 1 end]
+ }
+ if {[info exists mainscript]} {
+ eval $mainscript
+ return
+ }
+}
+
+# If all else fails, do an interactive loop
+#
+set line {}
+while {![eof stdin]} {
+ if {$line!=""} {
+ puts -nonewline "> "
+ } else {
+ puts -nonewline "% "
+ }
+ flush stdout
+ append line [gets stdin]
+ if {[info complete $line]} {
+ if {[catch {uplevel #0 $line} result]} {
+ puts stderr "Error: $result"
+ } elseif {$result!=""} {
+ puts $result
+ }
+ set line {}
+ } else {
+ append line \\n"
+ }
+}
|