=nCol ) break;
}
return -1;
}
diff --git a/src/shell.c.in b/src/shell.c.in
index cad8c92c57..fcc9316b00 100644
--- a/src/shell.c.in
+++ b/src/shell.c.in
@@ -7337,7 +7337,7 @@ static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){
break;
case AR_SWITCH_APPEND:
pAr->bAppend = 1;
- deliberate_fall_through;
+ deliberate_fall_through; /* FALLTHRU */
case AR_SWITCH_FILE:
pAr->zFile = zArg;
break;
@@ -8725,6 +8725,9 @@ static int do_meta_command(char *zLine, ShellState *p){
const char *zName;
int op;
} aDbConfig[] = {
+ { "attach_create", SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE },
+ { "attach_write", SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE },
+ { "comments", SQLITE_DBCONFIG_ENABLE_COMMENTS },
{ "defensive", SQLITE_DBCONFIG_DEFENSIVE },
{ "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL },
{ "dqs_dml", SQLITE_DBCONFIG_DQS_DML },
@@ -10079,6 +10082,7 @@ static int do_meta_command(char *zLine, ShellState *p){
if( zFile==0 ){
zFile = sqlite3_mprintf("stdout");
}
+ shell_check_oom(zFile);
if( bOnce ){
p->outCount = 2;
}else{
@@ -10121,6 +10125,7 @@ static int do_meta_command(char *zLine, ShellState *p){
#else
FILE *pfPipe = sqlite3_popen(zFile + 1, "w");
if( pfPipe==0 ){
+ assert( stderr!=NULL );
sqlite3_fprintf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1);
rc = 1;
}else{
@@ -10133,7 +10138,8 @@ static int do_meta_command(char *zLine, ShellState *p){
FILE *pfFile = output_file_open(zFile);
if( pfFile==0 ){
if( cli_strcmp(zFile,"off")!=0 ){
- sqlite3_fprintf(stderr,"Error: cannot write to \"%s\"\n", zFile);
+ assert( stderr!=NULL );
+ sqlite3_fprintf(stderr,"Error: cannot write to \"%s\"\n", zFile);
}
rc = 1;
} else {
@@ -10237,6 +10243,7 @@ static int do_meta_command(char *zLine, ShellState *p){
rc = 1;
}
}
+ bind_prepared_stmt(p, pStmt);
sqlite3_step(pStmt);
sqlite3_finalize(pStmt);
}else
@@ -11471,6 +11478,7 @@ static int do_meta_command(char *zLine, ShellState *p){
{ 0x04000000, 1, "NullUnusedCols" },
{ 0x08000000, 1, "OnePass" },
{ 0x10000000, 1, "OrderBySubq" },
+ { 0x20000000, 1, "StarQuery" },
{ 0xffffffff, 0, "All" },
};
unsigned int curOpt;
@@ -12025,7 +12033,7 @@ static QuickScanState quickscan(char *zLine, QuickScanState qss,
break;
case '[':
cin = ']';
- deliberate_fall_through;
+ deliberate_fall_through; /* FALLTHRU */
case '`': case '\'': case '"':
cWait = cin;
qss = QSS_HasDark | cWait;
@@ -12060,7 +12068,7 @@ static QuickScanState quickscan(char *zLine, QuickScanState qss,
++zLine;
continue;
}
- deliberate_fall_through;
+ deliberate_fall_through; /* FALLTHRU */
case ']':
CONTINUE_PROMPT_AWAITC(pst, 0);
qss = QSS_SETV(qss, 0);
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index d053eb7d7e..bf1974a862 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -2217,7 +2217,15 @@ struct sqlite3_mem_methods {
** CAPI3REF: Database Connection Configuration Options
**
** These constants are the available integer configuration options that
-** can be passed as the second argument to the [sqlite3_db_config()] interface.
+** can be passed as the second parameter to the [sqlite3_db_config()] interface.
+**
+** The [sqlite3_db_config()] interface is a var-args functions. It takes a
+** variable number of parameters, though always at least two. The number of
+** parameters passed into sqlite3_db_config() depends on which of these
+** constants is given as the second parameter. This documentation page
+** refers to parameters beyond the second as "arguments". Thus, when this
+** page says "the N-th argument" it means "the N-th parameter past the
+** configuration option" or "the (N+2)-th parameter to sqlite3_db_config()".
**
** New configuration options may be added in future releases of SQLite.
** Existing configuration options might be discontinued. Applications
@@ -2229,8 +2237,14 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_DBCONFIG_LOOKASIDE]]
** - SQLITE_DBCONFIG_LOOKASIDE
-** - ^This option takes three additional arguments that determine the
-** [lookaside memory allocator] configuration for the [database connection].
+**
- The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the
+** configuration of the lookaside memory allocator within a database
+** connection.
+** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are not
+** in the [DBCONFIG arguments|usual format].
+** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two,
+** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE
+** should have a total of five parameters.
** ^The first argument (the third parameter to [sqlite3_db_config()] is a
** pointer to a memory buffer to use for lookaside memory.
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
@@ -2253,7 +2267,8 @@ struct sqlite3_mem_methods {
** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
**
- SQLITE_DBCONFIG_ENABLE_FKEY
** - ^This option is used to enable or disable the enforcement of
-** [foreign key constraints]. There should be two additional arguments.
+** [foreign key constraints]. This is the same setting that is
+** enabled or disabled by the [PRAGMA foreign_keys] statement.
** The first argument is an integer which is 0 to disable FK enforcement,
** positive to enable FK enforcement or negative to leave FK enforcement
** unchanged. The second parameter is a pointer to an integer into which
@@ -2275,13 +2290,13 @@ struct sqlite3_mem_methods {
**
Originally this option disabled all triggers. ^(However, since
** SQLite version 3.35.0, TEMP triggers are still allowed even if
** this option is off. So, in other words, this option now only disables
-** triggers in the main database schema or in the schemas of ATTACH-ed
+** triggers in the main database schema or in the schemas of [ATTACH]-ed
** databases.)^
**
** [[SQLITE_DBCONFIG_ENABLE_VIEW]]
** - SQLITE_DBCONFIG_ENABLE_VIEW
** - ^This option is used to enable or disable [CREATE VIEW | views].
-** There should be two additional arguments.
+** There must be two additional arguments.
** The first argument is an integer which is 0 to disable views,
** positive to enable views or negative to leave the setting unchanged.
** The second parameter is a pointer to an integer into which
@@ -2300,7 +2315,7 @@ struct sqlite3_mem_methods {
**
- ^This option is used to enable or disable the
** [fts3_tokenizer()] function which is part of the
** [FTS3] full-text search engine extension.
-** There should be two additional arguments.
+** There must be two additional arguments.
** The first argument is an integer which is 0 to disable fts3_tokenizer() or
** positive to enable fts3_tokenizer() or negative to leave the setting
** unchanged.
@@ -2315,7 +2330,7 @@ struct sqlite3_mem_methods {
** interface independently of the [load_extension()] SQL function.
** The [sqlite3_enable_load_extension()] API enables or disables both the
** C-API [sqlite3_load_extension()] and the SQL function [load_extension()].
-** There should be two additional arguments.
+** There must be two additional arguments.
** When the first argument to this interface is 1, then only the C-API is
** enabled and the SQL function remains disabled. If the first argument to
** this interface is 0, then both the C-API and the SQL function are disabled.
@@ -2329,23 +2344,30 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_DBCONFIG_MAINDBNAME]]
- SQLITE_DBCONFIG_MAINDBNAME
** - ^This option is used to change the name of the "main" database
-** schema. ^The sole argument is a pointer to a constant UTF8 string
-** which will become the new schema name in place of "main". ^SQLite
-** does not make a copy of the new main schema name string, so the application
-** must ensure that the argument passed into this DBCONFIG option is unchanged
-** until after the database connection closes.
+** schema. This option does not follow the
+** [DBCONFIG arguments|usual SQLITE_DBCONFIG argument format].
+** This option takes exactly one additional argument so that the
+** [sqlite3_db_config()] call has a total of three parameters. The
+** extra argument must be a pointer to a constant UTF8 string which
+** will become the new schema name in place of "main". ^SQLite does
+** not make a copy of the new main schema name string, so the application
+** must ensure that the argument passed into SQLITE_DBCONFIG MAINDBNAME
+** is unchanged until after the database connection closes.
**
**
** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]]
** - SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
-** - Usually, when a database in wal mode is closed or detached from a
-** database handle, SQLite checks if this will mean that there are now no
-** connections at all to the database. If so, it performs a checkpoint
-** operation before closing the connection. This option may be used to
-** override this behavior. The first parameter passed to this operation
-** is an integer - positive to disable checkpoints-on-close, or zero (the
-** default) to enable them, and negative to leave the setting unchanged.
-** The second parameter is a pointer to an integer
+**
- Usually, when a database in [WAL mode] is closed or detached from a
+** database handle, SQLite checks if if there are other connections to the
+** same database, and if there are no other database connection (if the
+** connection being closed is the last open connection to the database),
+** then SQLite performs a [checkpoint] before closing the connection and
+** deletes the WAL file. The SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE option can
+** be used to override that behavior. The first argument passed to this
+** operation (the third parameter to [sqlite3_db_config()]) is an integer
+** which is positive to disable checkpoints-on-close, or zero (the default)
+** to enable them, and negative to leave the setting unchanged.
+** The second argument (the fourth parameter) is a pointer to an integer
** into which is written 0 or 1 to indicate whether checkpoints-on-close
** have been disabled - 0 if they are not disabled, 1 if they are.
**
@@ -2506,7 +2528,7 @@ struct sqlite3_mem_methods {
** statistics. For statistics to be collected, the flag must be set on
** the database handle both when the SQL statement is prepared and when it
** is stepped. The flag is set (collection of statistics is enabled)
-** by default. This option takes two arguments: an integer and a pointer to
+** by default. This option takes two arguments: an integer and a pointer to
** an integer.. The first argument is 1, 0, or -1 to enable, disable, or
** leave unchanged the statement scanstatus option. If the second argument
** is not NULL, then the value of the statement scanstatus setting after
@@ -2520,7 +2542,7 @@ struct sqlite3_mem_methods {
** in which tables and indexes are scanned so that the scans start at the end
** and work toward the beginning rather than starting at the beginning and
** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the
-** same as setting [PRAGMA reverse_unordered_selects]. This option takes
+** same as setting [PRAGMA reverse_unordered_selects].
This option takes
** two arguments which are an integer and a pointer to an integer. The first
** argument is 1, 0, or -1 to enable, disable, or leave unchanged the
** reverse scan order flag, respectively. If the second argument is not NULL,
@@ -2529,7 +2551,76 @@ struct sqlite3_mem_methods {
** first argument.
**
**
+** [[SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]]
+** - SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE
+** - The SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE option enables or disables
+** the ability of the [ATTACH DATABASE] SQL command to create a new database
+** file if the database filed named in the ATTACH command does not already
+** exist. This ability of ATTACH to create a new database is enabled by
+** default. Applications can disable or reenable the ability for ATTACH to
+** create new database files using this DBCONFIG option.
+** This option takes two arguments which are an integer and a pointer
+** to an integer. The first argument is 1, 0, or -1 to enable, disable, or
+** leave unchanged the attach-create flag, respectively. If the second
+** argument is not NULL, then 0 or 1 is written into the integer that the
+** second argument points to depending on if the attach-create flag is set
+** after processing the first argument.
+**
+**
+** [[SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE]]
+** - SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE
+** - The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the
+** ability of the [ATTACH DATABASE] SQL command to open a database for writing.
+** This capability is enabled by default. Applications can disable or
+** reenable this capability using the current DBCONFIG option. If the
+** the this capability is disabled, the [ATTACH] command will still work,
+** but the database will be opened read-only. If this option is disabled,
+** then the ability to create a new database using [ATTACH] is also disabled,
+** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]
+** option.
+** This option takes two arguments which are an integer and a pointer
+** to an integer. The first argument is 1, 0, or -1 to enable, disable, or
+** leave unchanged the ability to ATTACH another database for writing,
+** respectively. If the second argument is not NULL, then 0 or 1 is written
+** into the integer to which the second argument points, depending on whether
+** the ability to ATTACH a read/write database is enabled or disabled
+** after processing the first argument.
+**
+**
+** [[SQLITE_DBCONFIG_ENABLE_COMMENTS]]
+** - SQLITE_DBCONFIG_ENABLE_COMMENTS
+** - The SQLITE_DBCONFIG_ENABLE_COMMENTS option enables or disables the
+** ability to include comments in SQL text. Comments are enabled by default.
+** An application can disable or reenable comments in SQL text using this
+** DBCONFIG option.
+** This option takes two arguments which are an integer and a pointer
+** to an integer. The first argument is 1, 0, or -1 to enable, disable, or
+** leave unchanged the ability to use comments in SQL text,
+** respectively. If the second argument is not NULL, then 0 or 1 is written
+** into the integer that the second argument points to depending on if
+** comments are allowed in SQL text after processing the first argument.
+**
+**
**
+**
+** [[DBCONFIG arguments]] Arguments To SQLITE_DBCONFIG Options
+**
+** Most of the SQLITE_DBCONFIG options take two arguments, so that the
+** overall call to [sqlite3_db_config()] has a total of four parameters.
+** The first argument (the third parameter to sqlite3_db_config()) is a integer.
+** The second argument is a pointer to an integer. If the first argument is 1,
+** then the option becomes enabled. If the first integer argument is 0, then the
+** option is disabled. If the first argument is -1, then the option setting
+** is unchanged. The second argument, the pointer to an integer, may be NULL.
+** If the second argument is not NULL, then a value of 0 or 1 is written into
+** the integer to which the second argument points, depending on whether the
+** setting is disabled or enabled after applying any changes specified by
+** the first argument.
+**
+**
While most SQLITE_DBCONFIG options use the argument format
+** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME]
+** and [SQLITE_DBCONFIG_LOOKASIDE] options are different. See the
+** documentation of those exceptional options for details.
*/
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */
@@ -2551,7 +2642,10 @@ struct sqlite3_mem_methods {
#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */
#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */
#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */
-#define SQLITE_DBCONFIG_MAX 1019 /* Largest DBCONFIG */
+#define SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE 1020 /* int int* */
+#define SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE 1021 /* int int* */
+#define SQLITE_DBCONFIG_ENABLE_COMMENTS 1022 /* int int* */
+#define SQLITE_DBCONFIG_MAX 1022 /* Largest DBCONFIG */
/*
** CAPI3REF: Enable Or Disable Extended Result Codes
@@ -10792,8 +10886,9 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
/*
** CAPI3REF: Serialize a database
**
-** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory
-** that is a serialization of the S database on [database connection] D.
+** The sqlite3_serialize(D,S,P,F) interface returns a pointer to
+** memory that is a serialization of the S database on
+** [database connection] D. If S is a NULL pointer, the main database is used.
** If P is not a NULL pointer, then the size of the database in bytes
** is written into *P.
**
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index f56625cef0..336c3a583e 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -843,6 +843,11 @@ typedef INT16_TYPE i16; /* 2-byte signed integer */
typedef UINT8_TYPE u8; /* 1-byte unsigned integer */
typedef INT8_TYPE i8; /* 1-byte signed integer */
+/* A bitfield type for use inside of structures. Always follow with :N where
+** N is the number of bits.
+*/
+typedef unsigned bft; /* Bit Field Type */
+
/*
** SQLITE_MAX_U32 is a u64 constant that is the maximum u64 value
** that can be stored in a u32 without loss of data. The value
@@ -881,6 +886,8 @@ typedef u64 tRowcnt;
** 0.5 -> -10 0.1 -> -33 0.0625 -> -40
*/
typedef INT16_TYPE LogEst;
+#define LOGEST_MIN (-32768)
+#define LOGEST_MAX (32767)
/*
** Set the SQLITE_PTRSIZE macro to the number of bytes in a pointer
@@ -1151,7 +1158,7 @@ extern u32 sqlite3WhereTrace;
** 0xFFFF---- Low-level debug messages
**
** 0x00000001 Code generation
-** 0x00000002 Solver
+** 0x00000002 Solver (Use 0x40000 for less detail)
** 0x00000004 Solver costs
** 0x00000008 WhereLoop inserts
**
@@ -1170,6 +1177,8 @@ extern u32 sqlite3WhereTrace;
**
** 0x00010000 Show more detail when printing WHERE terms
** 0x00020000 Show WHERE terms returned from whereScanNext()
+** 0x00040000 Solver overview messages
+** 0x00080000 Star-query heuristic
*/
@@ -1834,6 +1843,9 @@ struct sqlite3 {
#define SQLITE_CorruptRdOnly HI(0x00002) /* Prohibit writes due to error */
#define SQLITE_ReadUncommit HI(0x00004) /* READ UNCOMMITTED in shared-cache */
#define SQLITE_FkNoAction HI(0x00008) /* Treat all FK as NO ACTION */
+#define SQLITE_AttachCreate HI(0x00010) /* ATTACH allowed to create new dbs */
+#define SQLITE_AttachWrite HI(0x00020) /* ATTACH allowed to open for write */
+#define SQLITE_Comments HI(0x00040) /* Enable SQL comments */
/* Flags used only if debugging */
#ifdef SQLITE_DEBUG
@@ -1893,6 +1905,7 @@ struct sqlite3 {
#define SQLITE_NullUnusedCols 0x04000000 /* NULL unused columns in subqueries */
#define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */
#define SQLITE_OrderBySubq 0x10000000 /* ORDER BY in subquery helps outer */
+#define SQLITE_StarQuery 0x20000000 /* Heurists for star queries */
#define SQLITE_AllOpts 0xffffffff /* All optimizations */
/*
@@ -2422,6 +2435,7 @@ struct Table {
} u;
Trigger *pTrigger; /* List of triggers on this object */
Schema *pSchema; /* Schema that contains this table */
+ u8 aHx[16]; /* Column aHt[K%sizeof(aHt)] might have hash K */
};
/*
@@ -3222,13 +3236,8 @@ struct ExprList {
*/
struct IdList {
int nId; /* Number of identifiers on the list */
- u8 eU4; /* Which element of a.u4 is valid */
struct IdList_item {
char *zName; /* Name of the identifier */
- union {
- int idx; /* Index in some Table.aCol[] of a column named zName */
- Expr *pExpr; /* Expr to implement a USING variable -- NOT USED */
- } u4;
} a[1];
};
@@ -3824,25 +3833,32 @@ struct Parse {
char *zErrMsg; /* An error message */
Vdbe *pVdbe; /* An engine for executing database bytecode */
int rc; /* Return code from execution */
- u8 colNamesSet; /* TRUE after OP_ColumnName has been issued to pVdbe */
- u8 checkSchema; /* Causes schema cookie check after an error */
+ LogEst nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */
u8 nested; /* Number of nested calls to the parser/code generator */
u8 nTempReg; /* Number of temporary registers in aTempReg[] */
u8 isMultiWrite; /* True if statement may modify/insert multiple rows */
u8 mayAbort; /* True if statement may throw an ABORT exception */
u8 hasCompound; /* Need to invoke convertCompoundSelectToSubquery() */
- u8 okConstFactor; /* OK to factor out constants */
u8 disableLookaside; /* Number of times lookaside has been disabled */
u8 prepFlags; /* SQLITE_PREPARE_* flags */
u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */
- u8 bHasWith; /* True if statement contains WITH */
u8 mSubrtnSig; /* mini Bloom filter on available SubrtnSig.selId */
+ u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */
+ u8 bReturning; /* Coding a RETURNING trigger */
+ u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */
+ u8 disableTriggers; /* True to disable triggers */
#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */
#endif
#ifdef SQLITE_DEBUG
u8 ifNotExists; /* Might be true if IF NOT EXISTS. Assert()s only */
+ u8 isCreate; /* CREATE TABLE, INDEX, or VIEW (but not TRIGGER)
+ ** and ALTER TABLE ADD COLUMN. */
#endif
+ bft colNamesSet :1; /* TRUE after OP_ColumnName has been issued to pVdbe */
+ bft bHasWith :1; /* True if statement contains WITH */
+ bft okConstFactor :1; /* OK to factor out constants */
+ bft checkSchema :1; /* Causes schema cookie check after an error */
int nRangeReg; /* Size of the temporary register block */
int iRangeReg; /* First register in temporary register block */
int nErr; /* Number of errors seen */
@@ -3857,12 +3873,9 @@ struct Parse {
ExprList *pConstExpr;/* Constant expressions */
IndexedExpr *pIdxEpr;/* List of expressions used by active indexes */
IndexedExpr *pIdxPartExpr; /* Exprs constrained by index WHERE clauses */
- Token constraintName;/* Name of the constraint currently being parsed */
yDbMask writeMask; /* Start a write transaction on these databases */
yDbMask cookieMask; /* Bitmask of schema verified databases */
- int regRowid; /* Register holding rowid of CREATE TABLE entry */
- int regRoot; /* Register holding root page number for new objects */
- int nMaxArg; /* Max args passed to user function by sub-program */
+ int nMaxArg; /* Max args to xUpdate and xFilter vtab methods */
int nSelect; /* Number of SELECT stmts. Counter for Select.selId */
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
u32 nProgressSteps; /* xProgress steps taken during sqlite3_prepare() */
@@ -3876,17 +3889,6 @@ struct Parse {
Table *pTriggerTab; /* Table triggers are being coded for */
TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */
ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */
- union {
- int addrCrTab; /* Address of OP_CreateBtree on CREATE TABLE */
- Returning *pReturning; /* The RETURNING clause */
- } u1;
- u32 oldmask; /* Mask of old.* columns referenced */
- u32 newmask; /* Mask of new.* columns referenced */
- LogEst nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */
- u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */
- u8 bReturning; /* Coding a RETURNING trigger */
- u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */
- u8 disableTriggers; /* True to disable triggers */
/**************************************************************************
** Fields above must be initialized to zero. The fields that follow,
@@ -3898,6 +3900,19 @@ struct Parse {
int aTempReg[8]; /* Holding area for temporary registers */
Parse *pOuterParse; /* Outer Parse object when nested */
Token sNameToken; /* Token with unqualified schema object name */
+ u32 oldmask; /* Mask of old.* columns referenced */
+ u32 newmask; /* Mask of new.* columns referenced */
+ union {
+ struct { /* These fields available when isCreate is true */
+ int addrCrTab; /* Address of OP_CreateBtree on CREATE TABLE */
+ int regRowid; /* Register holding rowid of CREATE TABLE entry */
+ int regRoot; /* Register holding root page for new objects */
+ Token constraintName; /* Name of the constraint currently being parsed */
+ } cr;
+ struct { /* These fields available to all other statements */
+ Returning *pReturning; /* The RETURNING clause */
+ } d;
+ } u1;
/************************************************************************
** Above is constant between recursions. Below is reset before and after
@@ -3915,9 +3930,7 @@ struct Parse {
int nVtabLock; /* Number of virtual tables to lock */
#endif
int nHeight; /* Expression tree height of current sub-select */
-#ifndef SQLITE_OMIT_EXPLAIN
int addrExplain; /* Address of current OP_Explain opcode */
-#endif
VList *pVList; /* Mapping between variable names and numbers */
Vdbe *pReprepare; /* VM being reprepared (sqlite3Reprepare()) */
const char *zTail; /* All SQL text past the last semicolon parsed */
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index f0b5c3e814..824e8c4d3c 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -341,7 +341,7 @@ static int SQLITE_TCLAPI incrblobInput(
*/
static int SQLITE_TCLAPI incrblobOutput(
ClientData instanceData,
- CONST char *buf,
+ const char *buf,
int toWrite,
int *errorCodePtr
){
@@ -510,7 +510,7 @@ static int createIncrblobChannel(
** or {...} or ; to be seen anywhere. Most callback scripts consist
** of just a single procedure name and they meet this requirement.
*/
-static int safeToUseEvalObjv(Tcl_Interp *interp, Tcl_Obj *pCmd){
+static int safeToUseEvalObjv(Tcl_Obj *pCmd){
/* We could try to do something with Tcl_Parse(). But we will instead
** just do a search for forbidden characters. If any of the forbidden
** characters appear in pCmd, we will report the string as unsafe.
@@ -1097,7 +1097,8 @@ static void tclSqlFunc(sqlite3_context *context, int argc, sqlite3_value**argv){
/* Only return a BLOB type if the Tcl variable is a bytearray and
** has no string representation. */
eType = SQLITE_BLOB;
- }else if( (c=='b' && strcmp(zType,"boolean")==0)
+ }else if( (c=='b' && pVar->bytes==0 && strcmp(zType,"boolean")==0 )
+ || (c=='b' && pVar->bytes==0 && strcmp(zType,"booleanString")==0 )
|| (c=='w' && strcmp(zType,"wideInt")==0)
|| (c=='i' && strcmp(zType,"int")==0)
){
@@ -1505,9 +1506,12 @@ static int dbPrepareAndBind(
sqlite3_bind_blob(pStmt, i, data, n, SQLITE_STATIC);
Tcl_IncrRefCount(pVar);
pPreStmt->apParm[iParm++] = pVar;
- }else if( c=='b' && strcmp(zType,"boolean")==0 ){
+ }else if( c=='b' && pVar->bytes==0
+ && (strcmp(zType,"booleanString")==0
+ || strcmp(zType,"boolean")==0)
+ ){
int nn;
- Tcl_GetIntFromObj(interp, pVar, &nn);
+ Tcl_GetBooleanFromObj(interp, pVar, &nn);
sqlite3_bind_int(pStmt, i, nn);
}else if( c=='d' && strcmp(zType,"double")==0 ){
double r;
@@ -1843,7 +1847,8 @@ static Tcl_Obj *dbEvalColumnValue(DbEvalContext *p, int iCol){
** are 8.6 or newer, the code still tests the Tcl version at runtime.
** This allows stubs-enabled builds to be used with older Tcl libraries.
*/
-#if TCL_MAJOR_VERSION>8 || (TCL_MAJOR_VERSION==8 && TCL_MINOR_VERSION>=6)
+#if TCL_MAJOR_VERSION>8 || !defined(TCL_MINOR_VERSION) \
+ || TCL_MINOR_VERSION>=6
# define SQLITE_TCL_NRE 1
static int DbUseNre(void){
int major, minor;
@@ -1959,7 +1964,7 @@ static void DbHookCmd(
}
if( pArg ){
assert( !(*ppHook) );
- if( Tcl_GetCharLength(pArg)>0 ){
+ if( Tcl_GetString(pArg)[0] ){
*ppHook = pArg;
Tcl_IncrRefCount(*ppHook);
}
@@ -2988,7 +2993,7 @@ deserialize_error:
}
pFunc->pScript = pScript;
Tcl_IncrRefCount(pScript);
- pFunc->useEvalObjv = safeToUseEvalObjv(interp, pScript);
+ pFunc->useEvalObjv = safeToUseEvalObjv(pScript);
pFunc->eType = eType;
rc = sqlite3_create_function(pDb->db, zName, nArg, flags,
pFunc, tclSqlFunc, 0, 0);
@@ -4016,7 +4021,9 @@ EXTERN int Tclsqlite_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; }
EXTERN int Sqlite_SafeInit(Tcl_Interp *interp){ return TCL_ERROR; }
EXTERN int Sqlite_SafeUnload(Tcl_Interp *interp, int flags){return TCL_ERROR;}
-/* Also variants with a lowercase "s" */
+/* Also variants with a lowercase "s". I'm told that these are
+** deprecated in Tcl9, but they continue to be included for backwards
+** compatibility. */
EXTERN int sqlite3_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp);}
EXTERN int sqlite_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp);}
diff --git a/src/test1.c b/src/test1.c
index a0ca93d10c..e45a05fe47 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -600,6 +600,7 @@ static int SQLITE_TCLAPI test_get_table_printf(
}
sqlite3_free(zSql);
sqlite3_snprintf(sizeof(zBuf), zBuf, "%d", rc);
+ Tcl_ResetResult(interp);
Tcl_AppendElement(interp, zBuf);
if( rc==SQLITE_OK ){
if( argc==4 ){
@@ -5680,9 +5681,11 @@ static int SQLITE_TCLAPI test_stmt_utf8(
sqlite3_stmt *pStmt;
int col;
const char *(*xFunc)(sqlite3_stmt*, int);
+ const unsigned char *(*xFuncU)(sqlite3_stmt*, int);
const char *zRet;
xFunc = (const char *(*)(sqlite3_stmt*, int))clientData;
+ xFuncU = (const unsigned char*(*)(sqlite3_stmt*,int))xFunc;
if( objc!=3 ){
Tcl_AppendResult(interp, "wrong # args: should be \"",
Tcl_GetString(objv[0]), " STMT column", 0);
@@ -5691,7 +5694,11 @@ static int SQLITE_TCLAPI test_stmt_utf8(
if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;
- zRet = xFunc(pStmt, col);
+ if( xFunc==sqlite3_column_name || xFunc==sqlite3_column_decltype ){
+ zRet = xFunc(pStmt, col);
+ }else{
+ zRet = (const char*)xFuncU(pStmt, col);
+ }
if( zRet ){
Tcl_SetResult(interp, (char *)zRet, 0);
}
@@ -7588,12 +7595,16 @@ static int SQLITE_TCLAPI test_wal_autocheckpoint(
/*
** tclcmd: test_sqlite3_log ?SCRIPT?
+**
+** Caution: If you register a log callback, you must deregister it (by
+** invoking test_sqlite3_log with no arguments) prior to closing the
+** Tcl interpreter or else a memory error will occur.
*/
static struct LogCallback {
Tcl_Interp *pInterp;
Tcl_Obj *pObj;
} logcallback = {0, 0};
-static void xLogcallback(void *unused, int err, char *zMsg){
+static void xLogcallback(void *unused, int err, const char *zMsg){
Tcl_Obj *pNew = Tcl_DuplicateObj(logcallback.pObj);
Tcl_IncrRefCount(pNew);
Tcl_ListObjAppendElement(
@@ -7619,7 +7630,7 @@ static int SQLITE_TCLAPI test_sqlite3_log(
logcallback.pInterp = 0;
sqlite3_config(SQLITE_CONFIG_LOG, (void*)0, (void*)0);
}
- if( objc>1 ){
+ if( objc>1 && Tcl_GetString(objv[1])[0]!=0 ){
logcallback.pObj = objv[1];
Tcl_IncrRefCount(logcallback.pObj);
logcallback.pInterp = interp;
@@ -8655,7 +8666,6 @@ static int SQLITE_TCLAPI test_decode_hexdb(
const char *zIn = 0;
unsigned char *a = 0;
int n = 0;
- int lineno = 0;
int i, iNext;
int iOffset = 0;
int j, k;
@@ -8667,7 +8677,6 @@ static int SQLITE_TCLAPI test_decode_hexdb(
}
zIn = Tcl_GetString(objv[1]);
for(i=0; zIn[i]; i=iNext){
- lineno++;
for(iNext=i; zIn[iNext] && zIn[iNext]!='\n'; iNext++){}
if( zIn[iNext]=='\n' ) iNext++;
while( zIn[i]==' ' || zIn[i]=='\t' ){ i++; }
diff --git a/src/test_intarray.c b/src/test_intarray.c
index 16c1df2e9c..9e4629467e 100644
--- a/src/test_intarray.c
+++ b/src/test_intarray.c
@@ -61,7 +61,8 @@ struct intarray_cursor {
/*
** Free an sqlite3_intarray object.
*/
-static void intarrayFree(sqlite3_intarray *p){
+static void intarrayFree(void *pX){
+ sqlite3_intarray *p = (sqlite3_intarray*)pX;
if( p->xFree ){
p->xFree(p->a);
}
diff --git a/src/test_malloc.c b/src/test_malloc.c
index 21faa0d291..8d6c4fa505 100644
--- a/src/test_malloc.c
+++ b/src/test_malloc.c
@@ -41,8 +41,9 @@ static struct MemFault {
** fire on any simulated malloc() failure.
*/
static void sqlite3Fault(void){
- static int cnt = 0;
+ static u64 cnt = 0;
cnt++;
+ if( cnt>((u64)1<<63) ) abort();
}
/*
@@ -52,8 +53,9 @@ static void sqlite3Fault(void){
** This routine only runs on the first such failure.
*/
static void sqlite3FirstFault(void){
- static int cnt2 = 0;
+ static u64 cnt2 = 0;
cnt2++;
+ if( cnt2>((u64)1<<63) ) abort();
}
/*
diff --git a/src/tokenize.c b/src/tokenize.c
index b49b2aa16e..fe300ca529 100644
--- a/src/tokenize.c
+++ b/src/tokenize.c
@@ -288,7 +288,7 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){
case CC_MINUS: {
if( z[1]=='-' ){
for(i=2; (c=z[i])!=0 && c!='\n'; i++){}
- *tokenType = TK_SPACE; /* IMP: R-22934-25134 */
+ *tokenType = TK_COMMENT;
return i;
}else if( z[1]=='>' ){
*tokenType = TK_PTR;
@@ -324,7 +324,7 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){
}
for(i=3, c=z[2]; (c!='*' || z[i]!='/') && (c=z[i])!=0; i++){}
if( c ) i++;
- *tokenType = TK_SPACE; /* IMP: R-22934-25134 */
+ *tokenType = TK_COMMENT;
return i;
}
case CC_PERCENT: {
@@ -653,12 +653,12 @@ int sqlite3RunParser(Parse *pParse, const char *zSql){
if( tokenType>=TK_WINDOW ){
assert( tokenType==TK_SPACE || tokenType==TK_OVER || tokenType==TK_FILTER
|| tokenType==TK_ILLEGAL || tokenType==TK_WINDOW
- || tokenType==TK_QNUMBER
+ || tokenType==TK_QNUMBER || tokenType==TK_COMMENT
);
#else
if( tokenType>=TK_SPACE ){
assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL
- || tokenType==TK_QNUMBER
+ || tokenType==TK_QNUMBER || tokenType==TK_COMMENT
);
#endif /* SQLITE_OMIT_WINDOWFUNC */
if( AtomicLoad(&db->u1.isInterrupted) ){
@@ -692,6 +692,9 @@ int sqlite3RunParser(Parse *pParse, const char *zSql){
assert( n==6 );
tokenType = analyzeFilterKeyword((const u8*)&zSql[6], lastTokenParsed);
#endif /* SQLITE_OMIT_WINDOWFUNC */
+ }else if( tokenType==TK_COMMENT && (db->flags & SQLITE_Comments)!=0 ){
+ zSql += n;
+ continue;
}else if( tokenType!=TK_QNUMBER ){
Token x;
x.z = zSql;
@@ -798,6 +801,7 @@ char *sqlite3Normalize(
n = sqlite3GetToken((unsigned char*)zSql+i, &tokenType);
if( NEVER(n<=0) ) break;
switch( tokenType ){
+ case TK_COMMENT:
case TK_SPACE: {
break;
}
diff --git a/src/treeview.c b/src/treeview.c
index 30592d35b2..8329659249 100644
--- a/src/treeview.c
+++ b/src/treeview.c
@@ -215,7 +215,10 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){
sqlite3_str_appendf(&x, " DDL");
}
if( pItem->fg.isCte ){
- sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse);
+ static const char *aMat[] = {",MAT", "", ",NO-MAT"};
+ sqlite3_str_appendf(&x, " CteUse=%d%s",
+ pItem->u2.pCteUse->nUse,
+ aMat[pItem->u2.pCteUse->eM10d]);
}
if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){
sqlite3_str_appendf(&x, " isOn");
@@ -246,9 +249,6 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){
sqlite3TreeViewColumnList(pView, pTab->aCol, pTab->nCol, 1);
}
assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem) );
- sqlite3TreeViewPush(&pView, 0);
- sqlite3TreeViewLine(pView, "SUBQUERY");
- sqlite3TreeViewPop(&pView);
sqlite3TreeViewSelect(pView, pItem->u4.pSubq->pSelect, 0);
}
if( pItem->fg.isTabFunc ){
@@ -978,21 +978,7 @@ void sqlite3TreeViewBareIdList(
if( zName==0 ) zName = "(null)";
sqlite3TreeViewPush(&pView, moreToFollow);
sqlite3TreeViewLine(pView, 0);
- if( pList->eU4==EU4_NONE ){
- fprintf(stdout, "%s\n", zName);
- }else if( pList->eU4==EU4_IDX ){
- fprintf(stdout, "%s (%d)\n", zName, pList->a[i].u4.idx);
- }else{
- assert( pList->eU4==EU4_EXPR );
- if( pList->a[i].u4.pExpr==0 ){
- fprintf(stdout, "%s (pExpr=NULL)\n", zName);
- }else{
- fprintf(stdout, "%s\n", zName);
- sqlite3TreeViewPush(&pView, inId-1);
- sqlite3TreeViewExpr(pView, pList->a[i].u4.pExpr, 0);
- sqlite3TreeViewPop(&pView);
- }
- }
+ fprintf(stdout, "%s\n", zName);
sqlite3TreeViewPop(&pView);
}
}
diff --git a/src/trigger.c b/src/trigger.c
index e306a2e664..604c3ab42f 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -70,7 +70,8 @@ Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){
assert( pParse->db->pVtabCtx==0 );
#endif
assert( pParse->bReturning );
- assert( &(pParse->u1.pReturning->retTrig) == pTrig );
+ assert( !pParse->isCreate );
+ assert( &(pParse->u1.d.pReturning->retTrig) == pTrig );
pTrig->table = pTab->zName;
pTrig->pTabSchema = pTab->pSchema;
pTrig->pNext = pList;
@@ -1047,7 +1048,8 @@ static void codeReturningTrigger(
return;
}
assert( db->pParse==pParse );
- pReturning = pParse->u1.pReturning;
+ assert( !pParse->isCreate );
+ pReturning = pParse->u1.d.pReturning;
if( pTrigger != &(pReturning->retTrig) ){
/* This RETURNING trigger is for a different statement */
return;
@@ -1277,6 +1279,8 @@ static TriggerPrg *codeRowTrigger(
sSubParse.eTriggerOp = pTrigger->op;
sSubParse.nQueryLoop = pParse->nQueryLoop;
sSubParse.prepFlags = pParse->prepFlags;
+ sSubParse.oldmask = 0;
+ sSubParse.newmask = 0;
v = sqlite3GetVdbe(&sSubParse);
if( v ){
diff --git a/src/update.c b/src/update.c
index a8e7f77803..979afea1f5 100644
--- a/src/update.c
+++ b/src/update.c
@@ -465,38 +465,32 @@ void sqlite3Update(
*/
chngRowid = chngPk = 0;
for(i=0; inExpr; i++){
- u8 hCol = sqlite3StrIHash(pChanges->a[i].zEName);
/* If this is an UPDATE with a FROM clause, do not resolve expressions
** here. The call to sqlite3Select() below will do that. */
if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){
goto update_cleanup;
}
- for(j=0; jnCol; j++){
- if( pTab->aCol[j].hName==hCol
- && sqlite3StrICmp(pTab->aCol[j].zCnName, pChanges->a[i].zEName)==0
- ){
- if( j==pTab->iPKey ){
- chngRowid = 1;
- pRowidExpr = pChanges->a[i].pExpr;
- iRowidExpr = i;
- }else if( pPk && (pTab->aCol[j].colFlags & COLFLAG_PRIMKEY)!=0 ){
- chngPk = 1;
- }
-#ifndef SQLITE_OMIT_GENERATED_COLUMNS
- else if( pTab->aCol[j].colFlags & COLFLAG_GENERATED ){
- testcase( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL );
- testcase( pTab->aCol[j].colFlags & COLFLAG_STORED );
- sqlite3ErrorMsg(pParse,
- "cannot UPDATE generated column \"%s\"",
- pTab->aCol[j].zCnName);
- goto update_cleanup;
- }
-#endif
- aXRef[j] = i;
- break;
+ j = sqlite3ColumnIndex(pTab, pChanges->a[i].zEName);
+ if( j>=0 ){
+ if( j==pTab->iPKey ){
+ chngRowid = 1;
+ pRowidExpr = pChanges->a[i].pExpr;
+ iRowidExpr = i;
+ }else if( pPk && (pTab->aCol[j].colFlags & COLFLAG_PRIMKEY)!=0 ){
+ chngPk = 1;
}
- }
- if( j>=pTab->nCol ){
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ else if( pTab->aCol[j].colFlags & COLFLAG_GENERATED ){
+ testcase( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL );
+ testcase( pTab->aCol[j].colFlags & COLFLAG_STORED );
+ sqlite3ErrorMsg(pParse,
+ "cannot UPDATE generated column \"%s\"",
+ pTab->aCol[j].zCnName);
+ goto update_cleanup;
+ }
+#endif
+ aXRef[j] = i;
+ }else{
if( pPk==0 && sqlite3IsRowid(pChanges->a[i].zEName) ){
j = -1;
chngRowid = 1;
diff --git a/src/util.c b/src/util.c
index ecce460e01..703ef0a23a 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1130,7 +1130,11 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){
}
p->z = &p->zBuf[i+1];
assert( i+p->n < sizeof(p->zBuf) );
- while( ALWAYS(p->n>0) && p->z[p->n-1]=='0' ){ p->n--; }
+ assert( p->n>0 );
+ while( p->z[p->n-1]=='0' ){
+ p->n--;
+ assert( p->n>0 );
+ }
}
/*
diff --git a/src/vdbe.c b/src/vdbe.c
index 558970ed95..ec871c5a6e 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -607,6 +607,7 @@ static void registerTrace(int iReg, Mem *p){
printf("R[%d] = ", iReg);
memTracePrint(p);
if( p->pScopyFrom ){
+ assert( p->pScopyFrom->bScopy );
printf(" <== R[%d]", (int)(p->pScopyFrom - &p[-iReg]));
}
printf("\n");
@@ -1590,6 +1591,7 @@ case OP_Move: {
{ int i;
for(i=1; inMem; i++){
if( aMem[i].pScopyFrom==pIn1 ){
+ assert( aMem[i].bScopy );
aMem[i].pScopyFrom = pOut;
}
}
@@ -1662,6 +1664,7 @@ case OP_SCopy: { /* out2 */
#ifdef SQLITE_DEBUG
pOut->pScopyFrom = pIn1;
pOut->mScopyFlags = pIn1->flags;
+ pIn1->bScopy = 1;
#endif
break;
}
@@ -8360,6 +8363,7 @@ case OP_VFilter: { /* jump, ncycle */
/* Invoke the xFilter method */
apArg = p->apArg;
+ assert( nArg<=p->napArg );
for(i = 0; ivtabOnConflict;
apArg = p->apArg;
pX = &aMem[pOp->p3];
+ assert( nArg<=p->napArg );
for(i=0; istartTime>0 );
- assert( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 );
assert( db->init.busy==0 );
assert( p->zSql!=0 );
sqlite3OsCurrentTimeInt64(db->pVfs, &iNow);
@@ -783,7 +782,7 @@ static int sqlite3Step(Vdbe *p){
}
assert( db->nVdbeWrite>0 || db->autoCommit==0
- || (db->nDeferredCons==0 && db->nDeferredImmCons==0)
+ || ((db->nDeferredCons + db->nDeferredImmCons)==0)
);
#ifndef SQLITE_OMIT_TRACE
@@ -1294,6 +1293,7 @@ static const Mem *columnNullValue(void){
#ifdef SQLITE_DEBUG
/* .pScopyFrom = */ (Mem*)0,
/* .mScopyFlags= */ 0,
+ /* .bScopy = */ 0,
#endif
};
return &nullMem;
@@ -2176,6 +2176,7 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
PreUpdate *p;
Mem *pMem;
int rc = SQLITE_OK;
+ int iStore = 0;
#ifdef SQLITE_ENABLE_API_ARMOR
if( db==0 || ppValue==0 ){
@@ -2190,9 +2191,11 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
goto preupdate_old_out;
}
if( p->pPk ){
- iIdx = sqlite3TableColumnToIndex(p->pPk, iIdx);
+ iStore = sqlite3TableColumnToIndex(p->pPk, iIdx);
+ }else{
+ iStore = sqlite3TableColumnToStorage(p->pTab, iIdx);
}
- if( iIdx>=p->pCsr->nField || iIdx<0 ){
+ if( iStore>=p->pCsr->nField || iStore<0 ){
rc = SQLITE_RANGE;
goto preupdate_old_out;
}
@@ -2223,8 +2226,8 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
p->aRecord = aRec;
}
- pMem = *ppValue = &p->pUnpacked->aMem[iIdx];
- if( iIdx>=p->pUnpacked->nField ){
+ pMem = *ppValue = &p->pUnpacked->aMem[iStore];
+ if( iStore>=p->pUnpacked->nField ){
/* This occurs when the table has been extended using ALTER TABLE
** ADD COLUMN. The value to return is the default value of the column. */
Column *pCol = &p->pTab->aCol[iIdx];
@@ -2328,6 +2331,7 @@ int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
PreUpdate *p;
int rc = SQLITE_OK;
Mem *pMem;
+ int iStore = 0;
#ifdef SQLITE_ENABLE_API_ARMOR
if( db==0 || ppValue==0 ){
@@ -2340,9 +2344,12 @@ int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
goto preupdate_new_out;
}
if( p->pPk && p->op!=SQLITE_UPDATE ){
- iIdx = sqlite3TableColumnToIndex(p->pPk, iIdx);
+ iStore = sqlite3TableColumnToIndex(p->pPk, iIdx);
+ }else{
+ iStore = sqlite3TableColumnToStorage(p->pTab, iIdx);
}
- if( iIdx>=p->pCsr->nField || iIdx<0 ){
+
+ if( iStore>=p->pCsr->nField || iStore<0 ){
rc = SQLITE_RANGE;
goto preupdate_new_out;
}
@@ -2362,14 +2369,14 @@ int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
}
p->pNewUnpacked = pUnpack;
}
- pMem = &pUnpack->aMem[iIdx];
+ pMem = &pUnpack->aMem[iStore];
if( iIdx==p->pTab->iPKey ){
sqlite3VdbeMemSetInt64(pMem, p->iKey2);
- }else if( iIdx>=pUnpack->nField ){
+ }else if( iStore>=pUnpack->nField ){
pMem = (sqlite3_value *)columnNullValue();
}
}else{
- /* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required
+ /* For an UPDATE, memory cell (p->iNewReg+1+iStore) contains the required
** value. Make a copy of the cell contents and return a pointer to it.
** It is not safe to return a pointer to the memory cell itself as the
** caller may modify the value text encoding.
@@ -2382,13 +2389,13 @@ int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
goto preupdate_new_out;
}
}
- assert( iIdx>=0 && iIdxpCsr->nField );
- pMem = &p->aNew[iIdx];
+ assert( iStore>=0 && iStorepCsr->nField );
+ pMem = &p->aNew[iStore];
if( pMem->flags==0 ){
if( iIdx==p->pTab->iPKey ){
sqlite3VdbeMemSetInt64(pMem, p->iKey2);
}else{
- rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]);
+ rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iStore]);
if( rc!=SQLITE_OK ) goto preupdate_new_out;
}
}
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index 4414f7a2ec..cf661eb9cb 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -856,8 +856,8 @@ void sqlite3VdbeAssertAbortable(Vdbe *p){
** (1) For each jump instruction with a negative P2 value (a label)
** resolve the P2 value to an actual address.
**
-** (2) Compute the maximum number of arguments used by any SQL function
-** and store that value in *pMaxFuncArgs.
+** (2) Compute the maximum number of arguments used by the xUpdate/xFilter
+** methods of any virtual table and store that value in *pMaxVtabArgs.
**
** (3) Update the Vdbe.readOnly and Vdbe.bIsReader flags to accurately
** indicate what the prepared statement actually does.
@@ -870,8 +870,8 @@ void sqlite3VdbeAssertAbortable(Vdbe *p){
** script numbers the opcodes correctly. Changes to this routine must be
** coordinated with changes to mkopcodeh.tcl.
*/
-static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
- int nMaxArgs = *pMaxFuncArgs;
+static void resolveP2Values(Vdbe *p, int *pMaxVtabArgs){
+ int nMaxVtabArgs = *pMaxVtabArgs;
Op *pOp;
Parse *pParse = p->pParse;
int *aLabel = pParse->aLabel;
@@ -916,15 +916,19 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
case OP_VUpdate: {
- if( pOp->p2>nMaxArgs ) nMaxArgs = pOp->p2;
+ if( pOp->p2>nMaxVtabArgs ) nMaxVtabArgs = pOp->p2;
break;
}
case OP_VFilter: {
int n;
+ /* The instruction immediately prior to VFilter will be an
+ ** OP_Integer that sets the "argc" value for the VFilter. See
+ ** the code where OP_VFilter is generated at tag-20250207a. */
assert( (pOp - p->aOp) >= 3 );
assert( pOp[-1].opcode==OP_Integer );
+ assert( pOp[-1].p2==pOp->p3+1 );
n = pOp[-1].p1;
- if( n>nMaxArgs ) nMaxArgs = n;
+ if( n>nMaxVtabArgs ) nMaxVtabArgs = n;
/* Fall through into the default case */
/* no break */ deliberate_fall_through
}
@@ -965,7 +969,7 @@ resolve_p2_values_loop_exit:
pParse->aLabel = 0;
}
pParse->nLabel = 0;
- *pMaxFuncArgs = nMaxArgs;
+ *pMaxVtabArgs = nMaxVtabArgs;
assert( p->bIsReader!=0 || DbMaskAllZero(p->btreeMask) );
}
@@ -2144,6 +2148,7 @@ void sqlite3VdbePrintOp(FILE *pOut, int pc, VdbeOp *pOp){
** will be initialized before use.
*/
static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){
+ assert( db!=0 );
if( N>0 ){
do{
p->flags = flags;
@@ -2151,6 +2156,7 @@ static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){
p->szMalloc = 0;
#ifdef SQLITE_DEBUG
p->pScopyFrom = 0;
+ p->bScopy = 0;
#endif
p++;
}while( (--N)>0 );
@@ -2169,6 +2175,7 @@ static void releaseMemArray(Mem *p, int N){
if( p && N ){
Mem *pEnd = &p[N];
sqlite3 *db = p->db;
+ assert( db!=0 );
if( db->pnBytesFreed ){
do{
if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc);
@@ -2640,7 +2647,7 @@ void sqlite3VdbeMakeReady(
int nVar; /* Number of parameters */
int nMem; /* Number of VM memory registers */
int nCursor; /* Number of cursors required */
- int nArg; /* Number of arguments in subprograms */
+ int nArg; /* Max number args to xFilter or xUpdate */
int n; /* Loop counter */
struct ReusableSpace x; /* Reusable bulk memory */
@@ -2649,6 +2656,7 @@ void sqlite3VdbeMakeReady(
assert( pParse!=0 );
assert( p->eVdbeState==VDBE_INIT_STATE );
assert( pParse==p->pParse );
+ assert( pParse->db==p->db );
p->pVList = pParse->pVList;
pParse->pVList = 0;
db = p->db;
@@ -2711,6 +2719,9 @@ void sqlite3VdbeMakeReady(
p->apCsr = allocSpace(&x, p->apCsr, nCursor*sizeof(VdbeCursor*));
}
}
+#ifdef SQLITE_DEBUG
+ p->napArg = nArg;
+#endif
if( db->mallocFailed ){
p->nVar = 0;
diff --git a/src/vdbeblob.c b/src/vdbeblob.c
index 6cb36da37a..79698d0af4 100644
--- a/src/vdbeblob.c
+++ b/src/vdbeblob.c
@@ -192,12 +192,8 @@ int sqlite3_blob_open(
pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName;
/* Now search pTab for the exact column. */
- for(iCol=0; iColnCol; iCol++) {
- if( sqlite3StrICmp(pTab->aCol[iCol].zCnName, zColumn)==0 ){
- break;
- }
- }
- if( iCol==pTab->nCol ){
+ iCol = sqlite3ColumnIndex(pTab, zColumn);
+ if( iCol<0 ){
sqlite3DbFree(db, zErr);
zErr = sqlite3MPrintf(db, "no such column: \"%s\"", zColumn);
rc = SQLITE_ERROR;
diff --git a/src/vdbemem.c b/src/vdbemem.c
index 0fc6b68f5e..61298d10ff 100644
--- a/src/vdbemem.c
+++ b/src/vdbemem.c
@@ -327,7 +327,7 @@ void sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){
return;
}
if( pMem->enc!=SQLITE_UTF8 ) return;
- if( NEVER(pMem->z==0) ) return;
+ assert( pMem->z!=0 );
if( pMem->flags & MEM_Dyn ){
if( pMem->xDel==sqlite3_free
&& sqlite3_msize(pMem->z) >= (u64)(pMem->n+1)
@@ -1046,27 +1046,30 @@ int sqlite3VdbeMemTooBig(Mem *p){
void sqlite3VdbeMemAboutToChange(Vdbe *pVdbe, Mem *pMem){
int i;
Mem *pX;
- for(i=1, pX=pVdbe->aMem+1; inMem; i++, pX++){
- if( pX->pScopyFrom==pMem ){
- u16 mFlags;
- if( pVdbe->db->flags & SQLITE_VdbeTrace ){
- sqlite3DebugPrintf("Invalidate R[%d] due to change in R[%d]\n",
- (int)(pX - pVdbe->aMem), (int)(pMem - pVdbe->aMem));
+ if( pMem->bScopy ){
+ for(i=1, pX=pVdbe->aMem+1; inMem; i++, pX++){
+ if( pX->pScopyFrom==pMem ){
+ u16 mFlags;
+ if( pVdbe->db->flags & SQLITE_VdbeTrace ){
+ sqlite3DebugPrintf("Invalidate R[%d] due to change in R[%d]\n",
+ (int)(pX - pVdbe->aMem), (int)(pMem - pVdbe->aMem));
+ }
+ /* If pX is marked as a shallow copy of pMem, then try to verify that
+ ** no significant changes have been made to pX since the OP_SCopy.
+ ** A significant change would indicated a missed call to this
+ ** function for pX. Minor changes, such as adding or removing a
+ ** dual type, are allowed, as long as the underlying value is the
+ ** same. */
+ mFlags = pMem->flags & pX->flags & pX->mScopyFlags;
+ assert( (mFlags&(MEM_Int|MEM_IntReal))==0 || pMem->u.i==pX->u.i );
+
+ /* pMem is the register that is changing. But also mark pX as
+ ** undefined so that we can quickly detect the shallow-copy error */
+ pX->flags = MEM_Undefined;
+ pX->pScopyFrom = 0;
}
- /* If pX is marked as a shallow copy of pMem, then try to verify that
- ** no significant changes have been made to pX since the OP_SCopy.
- ** A significant change would indicated a missed call to this
- ** function for pX. Minor changes, such as adding or removing a
- ** dual type, are allowed, as long as the underlying value is the
- ** same. */
- mFlags = pMem->flags & pX->flags & pX->mScopyFlags;
- assert( (mFlags&(MEM_Int|MEM_IntReal))==0 || pMem->u.i==pX->u.i );
-
- /* pMem is the register that is changing. But also mark pX as
- ** undefined so that we can quickly detect the shallow-copy error */
- pX->flags = MEM_Undefined;
- pX->pScopyFrom = 0;
}
+ pMem->bScopy = 0;
}
pMem->pScopyFrom = 0;
}
diff --git a/src/vtab.c b/src/vtab.c
index 76ad3613e8..e40f60873a 100644
--- a/src/vtab.c
+++ b/src/vtab.c
@@ -479,11 +479,12 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
** schema table. We just need to update that slot with all
** the information we've collected.
**
- ** The VM register number pParse->regRowid holds the rowid of an
+ ** The VM register number pParse->u1.cr.regRowid holds the rowid of an
** entry in the sqlite_schema table that was created for this vtab
** by sqlite3StartTable().
*/
iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ assert( pParse->isCreate );
sqlite3NestedParse(pParse,
"UPDATE %Q." LEGACY_SCHEMA_TABLE " "
"SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q "
@@ -492,7 +493,7 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
pTab->zName,
pTab->zName,
zStmt,
- pParse->regRowid
+ pParse->u1.cr.regRowid
);
v = sqlite3GetVdbe(pParse);
sqlite3ChangeCookie(pParse, iDb);
@@ -830,7 +831,9 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
z = (const unsigned char*)zCreateTable;
for(i=0; aKeyword[i]; i++){
int tokenType = 0;
- do{ z += sqlite3GetToken(z, &tokenType); }while( tokenType==TK_SPACE );
+ do{
+ z += sqlite3GetToken(z, &tokenType);
+ }while( tokenType==TK_SPACE || tokenType==TK_COMMENT );
if( tokenType!=aKeyword[i] ){
sqlite3ErrorWithMsg(db, SQLITE_ERROR, "syntax error");
return SQLITE_ERROR;
diff --git a/src/where.c b/src/where.c
index c9698699b3..5cb52b8adb 100644
--- a/src/where.c
+++ b/src/where.c
@@ -860,6 +860,11 @@ static int constraintCompatibleWithOuterJoin(
** more than 20, then return false.
**
** 3. If no disqualifying conditions above are found, return true.
+**
+** 2025-01-03: I experimented with a new rule that returns false if the
+** the datatype of the column is "BOOLEAN". This did not improve
+** performance on any queries at hand, but it did burn CPU cycles, so the
+** idea was not committed.
*/
static SQLITE_NOINLINE int columnIsGoodIndexCandidate(
const Table *pTab,
@@ -944,7 +949,7 @@ static void explainAutomaticIndex(
sqlite3_str *pStr = sqlite3_str_new(pParse->db);
sqlite3_str_appendf(pStr,"CREATE AUTOMATIC INDEX ON %s(", pTab->zName);
assert( pIdx->nColumn>1 );
- assert( pIdx->aiColumn[pIdx->nColumn-1]==XN_ROWID );
+ assert( pIdx->aiColumn[pIdx->nColumn-1]==XN_ROWID || !HasRowid(pTab) );
for(ii=0; ii<(pIdx->nColumn-1); ii++){
const char *zName = 0;
int iCol = pIdx->aiColumn[ii];
@@ -1075,6 +1080,19 @@ static SQLITE_NOINLINE void constructAutomaticIndex(
}else{
extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1));
}
+ if( !HasRowid(pTable) ){
+ /* For WITHOUT ROWID tables, ensure that all PRIMARY KEY columns are
+ ** either in the idxCols mask or in the extraCols mask */
+ for(i=0; inCol; i++){
+ if( (pTable->aCol[i].colFlags & COLFLAG_PRIMKEY)==0 ) continue;
+ if( i>=BMS-1 ){
+ extraCols |= MASKBIT(BMS-1);
+ break;
+ }
+ if( idxCols & MASKBIT(i) ) continue;
+ extraCols |= MASKBIT(i);
+ }
+ }
mxBitCol = MIN(BMS-1,pTable->nCol);
testcase( pTable->nCol==BMS-1 );
testcase( pTable->nCol==BMS-2 );
@@ -1086,7 +1104,8 @@ static SQLITE_NOINLINE void constructAutomaticIndex(
}
/* Construct the Index object to describe this index */
- pIdx = sqlite3AllocateIndexObject(pParse->db, nKeyCol+1, 0, &zNotUsed);
+ pIdx = sqlite3AllocateIndexObject(pParse->db, nKeyCol+HasRowid(pTable),
+ 0, &zNotUsed);
if( pIdx==0 ) goto end_auto_index_create;
pLoop->u.btree.pIndex = pIdx;
pIdx->zName = "auto-index";
@@ -1142,8 +1161,10 @@ static SQLITE_NOINLINE void constructAutomaticIndex(
}
}
assert( n==nKeyCol );
- pIdx->aiColumn[n] = XN_ROWID;
- pIdx->azColl[n] = sqlite3StrBINARY;
+ if( HasRowid(pTable) ){
+ pIdx->aiColumn[n] = XN_ROWID;
+ pIdx->azColl[n] = sqlite3StrBINARY;
+ }
/* Create the automatic index */
explainAutomaticIndex(pParse, pIdx, pPartial!=0, &addrExp);
@@ -2410,8 +2431,9 @@ void sqlite3WhereClausePrint(WhereClause *pWC){
** 1.002.001 t2.t2xy 2 f 010241 N 2 cost 0,56,31
*/
void sqlite3WhereLoopPrint(const WhereLoop *p, const WhereClause *pWC){
+ WhereInfo *pWInfo;
if( pWC ){
- WhereInfo *pWInfo = pWC->pWInfo;
+ pWInfo = pWC->pWInfo;
int nb = 1+(pWInfo->pTabList->nSrc+3)/4;
SrcItem *pItem = pWInfo->pTabList->a + p->iTab;
Table *pTab = pItem->pSTab;
@@ -2421,6 +2443,7 @@ void sqlite3WhereLoopPrint(const WhereLoop *p, const WhereClause *pWC){
sqlite3DebugPrintf(" %12s",
pItem->zAlias ? pItem->zAlias : pTab->zName);
}else{
+ pWInfo = 0;
sqlite3DebugPrintf("%c%2d.%03llx.%03llx %c%d",
p->cId, p->iTab, p->maskSelf, p->prereq & 0xfff, p->cId, p->iTab);
}
@@ -2452,7 +2475,12 @@ void sqlite3WhereLoopPrint(const WhereLoop *p, const WhereClause *pWC){
}else{
sqlite3DebugPrintf(" f %06x N %d", p->wsFlags, p->nLTerm);
}
- sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut);
+ if( pWInfo && pWInfo->bStarUsed && p->rStarDelta!=0 ){
+ sqlite3DebugPrintf(" cost %d,%d,%d delta=%d\n",
+ p->rSetup, p->rRun, p->nOut, p->rStarDelta);
+ }else{
+ sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut);
+ }
if( p->nLTerm && (sqlite3WhereTrace & 0x4000)!=0 ){
int i;
for(i=0; inLTerm; i++){
@@ -3918,7 +3946,6 @@ static int whereLoopAddBtree(
&& (pWInfo->pParse->db->flags & SQLITE_AutoIndex)!=0
&& !pSrc->fg.isIndexedBy /* Has no INDEXED BY clause */
&& !pSrc->fg.notIndexed /* Has no NOT INDEXED clause */
- && HasRowid(pTab) /* Not WITHOUT ROWID table. (FIXME: Why not?) */
&& !pSrc->fg.isCorrelated /* Not a correlated subquery */
&& !pSrc->fg.isRecursive /* Not a recursive common table expression. */
&& (pSrc->fg.jointype & JT_RIGHT)==0 /* Not the right tab of a RIGHT JOIN */
@@ -5421,68 +5448,201 @@ static LogEst whereSortingCost(
** 18 for star queries
** 12 otherwise
**
-** For the purposes of SQLite, a star-query is defined as a query
-** with a large central table that is joined against four or more
-** smaller tables. The central table is called the "fact" table.
-** The smaller tables that get joined are "dimension tables".
+** For the purposes of this heuristic, a star-query is defined as a query
+** with a large central table that is joined using an INNER JOIN,
+** not CROSS or OUTER JOINs, against four or more smaller tables.
+** The central table is called the "fact" table. The smaller tables
+** that get joined are "dimension tables". Also, any table that is
+** self-joined cannot be a dimension table; we assume that dimension
+** tables may only be joined against fact tables.
**
** SIDE EFFECT: (and really the whole point of this subroutine)
**
-** If pWInfo describes a star-query, then the cost on WhereLoops for the
-** fact table is reduced. This heuristic helps keep fact tables in
-** outer loops. Without this heuristic, paths with fact tables in outer
-** loops tend to get pruned by the mxChoice limit on the number of paths,
-** resulting in poor query plans. The total amount of heuristic cost
-** adjustment is stored in pWInfo->nOutStarDelta and the cost adjustment
-** for each WhereLoop is stored in its rStarDelta field.
+** If pWInfo describes a star-query, then the cost for SCANs of dimension
+** WhereLoops is increased to be slightly larger than the cost of a SCAN
+** in the fact table. Only SCAN costs are increased. SEARCH costs are
+** unchanged. This heuristic helps keep fact tables in outer loops. Without
+** this heuristic, paths with fact tables in outer loops tend to get pruned
+** by the mxChoice limit on the number of paths, resulting in poor query
+** plans. See the starschema1.test test module for examples of queries
+** that need this heuristic to find good query plans.
+**
+** This heuristic can be completely disabled, so that no query is
+** considered a star-query, using SQLITE_TESTCTRL_OPTIMIZATION to
+** disable the SQLITE_StarQuery optimization. In the CLI, the command
+** to do that is: ".testctrl opt -starquery".
+**
+** HISTORICAL NOTES:
+**
+** This optimization was first added on 2024-05-09 by check-in 38db9b5c83d.
+** The original optimization reduced the cost and output size estimate for
+** fact tables to help them move to outer loops. But months later (as people
+** started upgrading) performance regression reports started caming in,
+** including:
+**
+** forum post b18ef983e68d06d1 (2024-12-21)
+** forum post 0025389d0860af82 (2025-01-14)
+** forum post d87570a145599033 (2025-01-17)
+**
+** To address these, the criteria for a star-query was tightened to exclude
+** cases where the fact and dimensions are separated by an outer join, and
+** the affect of star-schema detection was changed to increase the rRun cost
+** on just full table scans of dimension tables, rather than reducing costs
+** in the all access methods of the fact table.
*/
-static int computeMxChoice(WhereInfo *pWInfo, LogEst nRowEst){
+static int computeMxChoice(WhereInfo *pWInfo){
int nLoop = pWInfo->nLevel; /* Number of terms in the join */
- if( nRowEst==0 && nLoop>=5 ){
- /* Check to see if we are dealing with a star schema and if so, reduce
- ** the cost of fact tables relative to dimension tables, as a heuristic
- ** to help keep the fact tables in outer loops.
+ WhereLoop *pWLoop; /* For looping over WhereLoops */
+
+#ifdef SQLITE_DEBUG
+ /* The star-query detection code below makes use of the following
+ ** properties of the WhereLoop list, so verify them before
+ ** continuing:
+ ** (1) .maskSelf is the bitmask corresponding to .iTab
+ ** (2) The WhereLoop list is in ascending .iTab order
+ */
+ for(pWLoop=pWInfo->pLoops; pWLoop; pWLoop=pWLoop->pNextLoop){
+ assert( pWLoop->maskSelf==MASKBIT(pWLoop->iTab) );
+ assert( pWLoop->pNextLoop==0 || pWLoop->iTab<=pWLoop->pNextLoop->iTab );
+ }
+#endif /* SQLITE_DEBUG */
+
+ if( nLoop>=5
+ && !pWInfo->bStarDone
+ && OptimizationEnabled(pWInfo->pParse->db, SQLITE_StarQuery)
+ ){
+ SrcItem *aFromTabs; /* All terms of the FROM clause */
+ int iFromIdx; /* Term of FROM clause is the candidate fact-table */
+ Bitmask m; /* Bitmask for candidate fact-table */
+ Bitmask mSelfJoin = 0; /* Tables that cannot be dimension tables */
+ WhereLoop *pStart; /* Where to start searching for dimension-tables */
+
+ pWInfo->bStarDone = 1; /* Only do this computation once */
+
+ /* Look for fact tables with four or more dimensions where the
+ ** dimension tables are not separately from the fact tables by an outer
+ ** or cross join. Adjust cost weights if found.
*/
- int iLoop; /* Counter over join terms */
- Bitmask m; /* Bitmask for current loop */
- assert( pWInfo->nOutStarDelta==0 );
- for(iLoop=0, m=1; iLoopbStarUsed );
+ aFromTabs = pWInfo->pTabList->a;
+ pStart = pWInfo->pLoops;
+ for(iFromIdx=0, m=1; iFromIdxpLoops; pWLoop; pWLoop=pWLoop->pNextLoop){
- if( (pWLoop->prereq & m)!=0 && (pWLoop->maskSelf & mSeen)==0 ){
- nDep++;
- mSeen |= pWLoop->maskSelf;
+ SrcItem *pFactTab; /* The candidate fact table */
+
+ pFactTab = aFromTabs + iFromIdx;
+ if( (pFactTab->fg.jointype & (JT_OUTER|JT_CROSS))!=0 ){
+ /* If the candidate fact-table is the right table of an outer join
+ ** restrict the search for dimension-tables to be tables to the right
+ ** of the fact-table. */
+ if( iFromIdx+4 > nLoop ) break; /* Impossible to reach nDep>=4 */
+ while( pStart && pStart->iTab<=iFromIdx ){
+ pStart = pStart->pNextLoop;
+ }
+ }
+ for(pWLoop=pStart; pWLoop; pWLoop=pWLoop->pNextLoop){
+ if( (aFromTabs[pWLoop->iTab].fg.jointype & (JT_OUTER|JT_CROSS))!=0 ){
+ /* Fact-tables and dimension-tables cannot be separated by an
+ ** outer join (at least for the definition of fact- and dimension-
+ ** used by this heuristic). */
+ break;
+ }
+ if( (pWLoop->prereq & m)!=0 /* pWInfo depends on iFromIdx */
+ && (pWLoop->maskSelf & mSeen)==0 /* pWInfo not already a dependency */
+ && (pWLoop->maskSelf & mSelfJoin)==0 /* Not a self-join */
+ ){
+ if( aFromTabs[pWLoop->iTab].pSTab==pFactTab->pSTab ){
+ mSelfJoin |= m;
+ }else{
+ nDep++;
+ mSeen |= pWLoop->maskSelf;
+ }
}
}
if( nDep<=3 ) continue;
- rDelta = 15*(nDep-3);
-#ifdef WHERETRACE_ENABLED /* 0x4 */
- if( sqlite3WhereTrace&0x4 ){
- SrcItem *pItem = pWInfo->pTabList->a + iLoop;
- sqlite3DebugPrintf("Fact-table %s: %d dimensions, cost reduced %d\n",
- pItem->zAlias ? pItem->zAlias : pItem->pSTab->zName,
- nDep, rDelta);
- }
-#endif
- if( pWInfo->nOutStarDelta==0 ){
+
+ /* If we reach this point, it means that pFactTab is a fact table
+ ** with four or more dimensions connected by inner joins. Proceed
+ ** to make cost adjustments. */
+
+#ifdef WHERETRACE_ENABLED
+ /* Make sure rStarDelta values are initialized */
+ if( !pWInfo->bStarUsed ){
for(pWLoop=pWInfo->pLoops; pWLoop; pWLoop=pWLoop->pNextLoop){
pWLoop->rStarDelta = 0;
}
}
- pWInfo->nOutStarDelta += rDelta;
- for(pWLoop=pWInfo->pLoops; pWLoop; pWLoop=pWLoop->pNextLoop){
- if( pWLoop->maskSelf==m ){
- pWLoop->rRun -= rDelta;
- pWLoop->nOut -= rDelta;
- pWLoop->rStarDelta = rDelta;
+#endif
+ pWInfo->bStarUsed = 1;
+
+ /* Compute the maximum cost of any WhereLoop for the
+ ** fact table plus one epsilon */
+ mxRun = LOGEST_MIN;
+ for(pWLoop=pStart; pWLoop; pWLoop=pWLoop->pNextLoop){
+ if( pWLoop->iTabiTab>iFromIdx ) break;
+ if( pWLoop->rRun>mxRun ) mxRun = pWLoop->rRun;
+ }
+ if( ALWAYS(mxRunpNextLoop){
+ if( (pWLoop->maskSelf & mSeen)==0 ) continue;
+ if( pWLoop->nLTerm ) continue;
+ if( pWLoop->rRuniTab;
+ sqlite3DebugPrintf(
+ "Increase SCAN cost of dimension %s(%d) of fact %s(%d) to %d\n",
+ pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, pWLoop->iTab,
+ pFactTab->zAlias ? pFactTab->zAlias : pFactTab->pSTab->zName,
+ iFromIdx, mxRun
+ );
+ }
+ pWLoop->rStarDelta = mxRun - pWLoop->rRun;
+#endif /* WHERETRACE_ENABLED */
+ pWLoop->rRun = mxRun;
}
}
- }
+ }
+#ifdef WHERETRACE_ENABLED /* 0x80000 */
+ if( (sqlite3WhereTrace & 0x80000)!=0 && pWInfo->bStarUsed ){
+ sqlite3DebugPrintf("WhereLoops changed by star-query heuristic:\n");
+ for(pWLoop=pWInfo->pLoops; pWLoop; pWLoop=pWLoop->pNextLoop){
+ if( pWLoop->rStarDelta ){
+ sqlite3WhereLoopPrint(pWLoop, &pWInfo->sWC);
+ }
+ }
+ }
+#endif
}
- return pWInfo->nOutStarDelta>0 ? 18 : 12;
+ return pWInfo->bStarUsed ? 18 : 12;
+}
+
+/*
+** Two WhereLoop objects, pCandidate and pBaseline, are known to have the
+** same cost. Look deep into each to see if pCandidate is even slightly
+** better than pBaseline. Return false if it is, if pCandidate is is preferred.
+** Return true if pBaseline is preferred or if we cannot tell the difference.
+**
+** Result Meaning
+** -------- ----------------------------------------------------------
+** true We cannot tell the difference in pCandidate and pBaseline
+** false pCandidate seems like a better choice than pBaseline
+*/
+static SQLITE_NOINLINE int whereLoopIsNoBetter(
+ const WhereLoop *pCandidate,
+ const WhereLoop *pBaseline
+){
+ if( (pCandidate->wsFlags & WHERE_INDEXED)==0 ) return 1;
+ if( (pBaseline->wsFlags & WHERE_INDEXED)==0 ) return 1;
+ if( pCandidate->u.btree.pIndex->szIdxRow <
+ pBaseline->u.btree.pIndex->szIdxRow ) return 0;
+ return 1;
}
/*
@@ -5506,7 +5666,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
int mxI = 0; /* Index of next entry to replace */
int nOrderBy; /* Number of ORDER BY clause terms */
LogEst mxCost = 0; /* Maximum cost of a set of paths */
- LogEst mxUnsorted = 0; /* Maximum unsorted cost of a set of path */
+ LogEst mxUnsort = 0; /* Maximum unsorted cost of a set of path */
int nTo, nFrom; /* Number of valid entries in aTo[] and aFrom[] */
WherePath *aFrom; /* All nFrom paths at the previous level */
WherePath *aTo; /* The nTo best paths at the current level */
@@ -5535,8 +5695,10 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
mxChoice = 1;
}else if( nLoop==2 ){
mxChoice = 5;
+ }else if( pParse->nErr ){
+ mxChoice = 1;
}else{
- mxChoice = computeMxChoice(pWInfo, nRowEst);
+ mxChoice = computeMxChoice(pWInfo);
}
assert( nLoop<=pWInfo->pTabList->nSrc );
@@ -5603,7 +5765,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
for(pWLoop=pWInfo->pLoops; pWLoop; pWLoop=pWLoop->pNextLoop){
LogEst nOut; /* Rows visited by (pFrom+pWLoop) */
LogEst rCost; /* Cost of path (pFrom+pWLoop) */
- LogEst rUnsorted; /* Unsorted cost of (pFrom+pWLoop) */
+ LogEst rUnsort; /* Unsorted cost of (pFrom+pWLoop) */
i8 isOrdered; /* isOrdered for (pFrom+pWLoop) */
Bitmask maskNew; /* Mask of src visited by (..) */
Bitmask revMask; /* Mask of rev-order loops for (..) */
@@ -5621,11 +5783,11 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
/* At this point, pWLoop is a candidate to be the next loop.
** Compute its cost */
- rUnsorted = pWLoop->rRun + pFrom->nRow;
+ rUnsort = pWLoop->rRun + pFrom->nRow;
if( pWLoop->rSetup ){
- rUnsorted = sqlite3LogEstAdd(pWLoop->rSetup, rUnsorted);
+ rUnsort = sqlite3LogEstAdd(pWLoop->rSetup, rUnsort);
}
- rUnsorted = sqlite3LogEstAdd(rUnsorted, pFrom->rUnsorted);
+ rUnsort = sqlite3LogEstAdd(rUnsort, pFrom->rUnsort);
nOut = pFrom->nRow + pWLoop->nOut;
maskNew = pFrom->maskLoop | pWLoop->maskSelf;
isOrdered = pFrom->isOrdered;
@@ -5647,15 +5809,15 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
** extra encouragement to the query planner to select a plan
** where the rows emerge in the correct order without any sorting
** required. */
- rCost = sqlite3LogEstAdd(rUnsorted, aSortCost[isOrdered]) + 3;
+ rCost = sqlite3LogEstAdd(rUnsort, aSortCost[isOrdered]) + 3;
WHERETRACE(0x002,
("---- sort cost=%-3d (%d/%d) increases cost %3d to %-3d\n",
aSortCost[isOrdered], (nOrderBy-isOrdered), nOrderBy,
- rUnsorted, rCost));
+ rUnsort, rCost));
}else{
- rCost = rUnsorted;
- rUnsorted -= 2; /* TUNING: Slight bias in favor of no-sort plans */
+ rCost = rUnsort;
+ rUnsort -= 2; /* TUNING: Slight bias in favor of no-sort plans */
}
/* Check to see if pWLoop should be added to the set of
@@ -5681,7 +5843,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
if( jj>=nTo ){
/* None of the existing best-so-far paths match the candidate. */
if( nTo>=mxChoice
- && (rCost>mxCost || (rCost==mxCost && rUnsorted>=mxUnsorted))
+ && (rCost>mxCost || (rCost==mxCost && rUnsort>=mxUnsort))
){
/* The current candidate is no better than any of the mxChoice
** paths currently in the best-so-far buffer. So discard
@@ -5689,7 +5851,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
#ifdef WHERETRACE_ENABLED /* 0x4 */
if( sqlite3WhereTrace&0x4 ){
sqlite3DebugPrintf("Skip %s cost=%-3d,%3d,%3d order=%c\n",
- wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted,
+ wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsort,
isOrdered>=0 ? isOrdered+'0' : '?');
}
#endif
@@ -5708,7 +5870,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
#ifdef WHERETRACE_ENABLED /* 0x4 */
if( sqlite3WhereTrace&0x4 ){
sqlite3DebugPrintf("New %s cost=%-3d,%3d,%3d order=%c\n",
- wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted,
+ wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsort,
isOrdered>=0 ? isOrdered+'0' : '?');
}
#endif
@@ -5719,24 +5881,23 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
** pTo or if the candidate should be skipped.
**
** The conditional is an expanded vector comparison equivalent to:
- ** (pTo->rCost,pTo->nRow,pTo->rUnsorted) <= (rCost,nOut,rUnsorted)
+ ** (pTo->rCost,pTo->nRow,pTo->rUnsort) <= (rCost,nOut,rUnsort)
*/
- if( pTo->rCostrCost==rCost
- && (pTo->nRownRow==nOut && pTo->rUnsorted<=rUnsorted)
- )
- )
+ if( (pTo->rCostrCost==rCost && pTo->nRowrCost==rCost && pTo->nRow==nOut && pTo->rUnsortrCost==rCost && pTo->nRow==nOut && pTo->rUnsort==rUnsort
+ && whereLoopIsNoBetter(pWLoop, pTo->aLoop[iLoop]) )
){
#ifdef WHERETRACE_ENABLED /* 0x4 */
if( sqlite3WhereTrace&0x4 ){
sqlite3DebugPrintf(
"Skip %s cost=%-3d,%3d,%3d order=%c",
- wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted,
+ wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsort,
isOrdered>=0 ? isOrdered+'0' : '?');
sqlite3DebugPrintf(" vs %s cost=%-3d,%3d,%3d order=%c\n",
wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow,
- pTo->rUnsorted, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?');
+ pTo->rUnsort, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?');
}
#endif
/* Discard the candidate path from further consideration */
@@ -5750,11 +5911,11 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
if( sqlite3WhereTrace&0x4 ){
sqlite3DebugPrintf(
"Update %s cost=%-3d,%3d,%3d order=%c",
- wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted,
+ wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsort,
isOrdered>=0 ? isOrdered+'0' : '?');
sqlite3DebugPrintf(" was %s cost=%-3d,%3d,%3d order=%c\n",
wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow,
- pTo->rUnsorted, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?');
+ pTo->rUnsort, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?');
}
#endif
}
@@ -5763,20 +5924,20 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
pTo->revLoop = revMask;
pTo->nRow = nOut;
pTo->rCost = rCost;
- pTo->rUnsorted = rUnsorted;
+ pTo->rUnsort = rUnsort;
pTo->isOrdered = isOrdered;
memcpy(pTo->aLoop, pFrom->aLoop, sizeof(WhereLoop*)*iLoop);
pTo->aLoop[iLoop] = pWLoop;
if( nTo>=mxChoice ){
mxI = 0;
mxCost = aTo[0].rCost;
- mxUnsorted = aTo[0].nRow;
+ mxUnsort = aTo[0].nRow;
for(jj=1, pTo=&aTo[1]; jjrCost>mxCost
- || (pTo->rCost==mxCost && pTo->rUnsorted>mxUnsorted)
+ || (pTo->rCost==mxCost && pTo->rUnsort>mxUnsort)
){
mxCost = pTo->rCost;
- mxUnsorted = pTo->rUnsorted;
+ mxUnsort = pTo->rUnsort;
mxI = jj;
}
}
@@ -5788,8 +5949,10 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
if( sqlite3WhereTrace & 0x02 ){
LogEst rMin, rFloor = 0;
int nDone = 0;
+ int nProgress;
sqlite3DebugPrintf("---- after round %d ----\n", iLoop);
- while( nDonerCost>rFloor && pTo->rCostrCost;
@@ -5805,10 +5968,11 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
sqlite3DebugPrintf("\n");
}
nDone++;
+ nProgress++;
}
}
rFloor = rMin;
- }
+ }while( nDone0 );
}
#endif
@@ -5902,7 +6066,10 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
}
}
- pWInfo->nRowOut = pFrom->nRow + pWInfo->nOutStarDelta;
+ pWInfo->nRowOut = pFrom->nRow;
+#ifdef WHERETRACE_ENABLED
+ pWInfo->rTotalCost = pFrom->rCost;
+#endif
/* Free temporary memory and return success */
sqlite3StackFreeNN(pParse->db, pSpace);
@@ -6300,7 +6467,6 @@ static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful(
}
}
nSearch += pLoop->nOut;
- if( pWInfo->nOutStarDelta ) nSearch += pLoop->rStarDelta;
}
}
@@ -6783,7 +6949,8 @@ WhereInfo *sqlite3WhereBegin(
assert( db->mallocFailed==0 );
#ifdef WHERETRACE_ENABLED
if( sqlite3WhereTrace ){
- sqlite3DebugPrintf("---- Solution nRow=%d", pWInfo->nRowOut);
+ sqlite3DebugPrintf("---- Solution cost=%d, nRow=%d",
+ pWInfo->rTotalCost, pWInfo->nRowOut);
if( pWInfo->nOBSat>0 ){
sqlite3DebugPrintf(" ORDERBY=%d,0x%llx", pWInfo->nOBSat, pWInfo->revMask);
}
diff --git a/src/whereInt.h b/src/whereInt.h
index f262b0eebc..8ba8a7072d 100644
--- a/src/whereInt.h
+++ b/src/whereInt.h
@@ -162,8 +162,10 @@ struct WhereLoop {
/**** whereLoopXfer() copies fields above ***********************/
# define WHERE_LOOP_XFER_SZ offsetof(WhereLoop,nLSlot)
u16 nLSlot; /* Number of slots allocated for aLTerm[] */
+#ifdef WHERETRACE_ENABLED
LogEst rStarDelta; /* Cost delta due to star-schema heuristic. Not
- ** initialized unless pWInfo->nOutStarDelta>0 */
+ ** initialized unless pWInfo->bStarUsed */
+#endif
WhereTerm **aLTerm; /* WhereTerms used */
WhereLoop *pNextLoop; /* Next WhereLoop object in the WhereClause */
WhereTerm *aLTermSpace[3]; /* Initial aLTerm[] space */
@@ -212,7 +214,7 @@ struct WherePath {
Bitmask revLoop; /* aLoop[]s that should be reversed for ORDER BY */
LogEst nRow; /* Estimated number of rows generated by this path */
LogEst rCost; /* Total cost of this path */
- LogEst rUnsorted; /* Total cost of this path ignoring sorting costs */
+ LogEst rUnsort; /* Total cost of this path ignoring sorting costs */
i8 isOrdered; /* No. of ORDER BY terms satisfied. -1 for unknown */
WhereLoop **aLoop; /* Array of WhereLoop objects implementing this path */
};
@@ -485,9 +487,13 @@ struct WhereInfo {
unsigned bDeferredSeek :1; /* Uses OP_DeferredSeek */
unsigned untestedTerms :1; /* Not all WHERE terms resolved by outer loop */
unsigned bOrderedInnerLoop:1;/* True if only the inner-most loop is ordered */
- unsigned sorted :1; /* True if really sorted (not just grouped) */
- LogEst nOutStarDelta; /* Artifical nOut reduction for star-query */
+ unsigned sorted :1; /* True if really sorted (not just grouped) */
+ unsigned bStarDone :1; /* True if check for star-query is complete */
+ unsigned bStarUsed :1; /* True if star-query heuristic is used */
LogEst nRowOut; /* Estimated number of output rows */
+#ifdef WHERETRACE_ENABLED
+ LogEst rTotalCost; /* Total cost of the solution */
+#endif
int iTop; /* The very beginning of the WHERE loop */
int iEndWhere; /* End of the WHERE clause itself */
WhereLoop *pLoops; /* List of all WhereLoop objects */
diff --git a/src/wherecode.c b/src/wherecode.c
index 045653aac8..1a0cdc6d71 100644
--- a/src/wherecode.c
+++ b/src/wherecode.c
@@ -1608,6 +1608,9 @@ Bitmask sqlite3WhereCodeOneLoopStart(
}
sqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg);
sqlite3VdbeAddOp2(v, OP_Integer, nConstraint, iReg+1);
+ /* The instruction immediately prior to OP_VFilter must be an OP_Integer
+ ** that sets the "argc" value for xVFilter. This is necessary for
+ ** resolveP2() to work correctly. See tag-20250207a. */
sqlite3VdbeAddOp4(v, OP_VFilter, iCur, addrNotFound, iReg,
pLoop->u.vtab.idxStr,
pLoop->u.vtab.needFree ? P4_DYNAMIC : P4_STATIC);
diff --git a/src/whereexpr.c b/src/whereexpr.c
index 2b6eb6a78d..4a24dadd23 100644
--- a/src/whereexpr.c
+++ b/src/whereexpr.c
@@ -219,12 +219,12 @@ static int isLikeOrGlob(
z = (u8*)pRight->u.zToken;
}
if( z ){
- /* Count the number of prefix bytes prior to the first wildcard.
- ** or U+fffd character. If the underlying database has a UTF16LE
- ** encoding, then only consider ASCII characters. Note that the
- ** encoding of z[] is UTF8 - we are dealing with only UTF8 here in
- ** this code, but the database engine itself might be processing
- ** content using a different encoding. */
+ /* Count the number of prefix bytes prior to the first wildcard,
+ ** U+fffd character, or malformed utf-8. If the underlying database
+ ** has a UTF16LE encoding, then only consider ASCII characters. Note that
+ ** the encoding of z[] is UTF8 - we are dealing with only UTF8 here in this
+ ** code, but the database engine itself might be processing content using a
+ ** different encoding. */
cnt = 0;
while( (c=z[cnt])!=0 && c!=wc[0] && c!=wc[1] && c!=wc[2] ){
cnt++;
@@ -232,7 +232,9 @@ static int isLikeOrGlob(
cnt++;
}else if( c>=0x80 ){
const u8 *z2 = z+cnt-1;
- if( sqlite3Utf8Read(&z2)==0xfffd || ENC(db)==SQLITE_UTF16LE ){
+ if( c==0xff || sqlite3Utf8Read(&z2)==0xfffd /* bad utf-8 */
+ || ENC(db)==SQLITE_UTF16LE
+ ){
cnt--;
break;
}else{
@@ -1384,9 +1386,8 @@ static void exprAnalyze(
}
if( !db->mallocFailed ){
- u8 c, *pC; /* Last character before the first wildcard */
+ u8 *pC; /* Last character before the first wildcard */
pC = (u8*)&pStr2->u.zToken[sqlite3Strlen30(pStr2->u.zToken)-1];
- c = *pC;
if( noCase ){
/* The point is to increment the last character before the first
** wildcard. But if we increment '@', that will push it into the
@@ -1394,10 +1395,17 @@ static void exprAnalyze(
** inequality. To avoid this, make sure to also run the full
** LIKE on all candidate expressions by clearing the isComplete flag
*/
- if( c=='A'-1 ) isComplete = 0;
- c = sqlite3UpperToLower[c];
+ if( *pC=='A'-1 ) isComplete = 0;
+ *pC = sqlite3UpperToLower[*pC];
}
- *pC = c + 1;
+
+ /* Increment the value of the last utf8 character in the prefix. */
+ while( *pC==0xBF && pC>(u8*)pStr2->u.zToken ){
+ *pC = 0x80;
+ pC--;
+ }
+ assert( *pC!=0xFF ); /* isLikeOrGlob() guarantees this */
+ (*pC)++;
}
zCollSeqName = noCase ? "NOCASE" : sqlite3StrBINARY;
pNewExpr1 = sqlite3ExprDup(db, pLeft, 0);
diff --git a/test/autoindex1.test b/test/autoindex1.test
index b294a2721f..1c8ce007f0 100644
--- a/test/autoindex1.test
+++ b/test/autoindex1.test
@@ -563,4 +563,32 @@ do_execsql_test autoindex-1120 {
SELECT * FROM t1 LEFT JOIN t2 ON (t2.c=+t1.a) LEFT JOIN t3 ON (t2.d IS NULL);
} {1 1 1 2 {} {}}
+# 2025-01-18
+# Added support for automatic indexes on WITHOUT ROWID tables.
+#
+reset_db
+do_execsql_test autoindex-1200 {
+ CREATE TABLE t1(a INT, b INT, x INT, PRIMARY KEY(a,b)) WITHOUT ROWID;
+ INSERT INTO t1 VALUES(1,2,90),(1,3,91),(1,4,92);
+ CREATE TABLE t2a(c INTEGER PRIMARY KEY, i1 INT);
+ CREATE TABLE t2b(i1 INTEGER PRIMARY KEY, d INT);
+ CREATE VIEW t2(c,d) AS SELECT c, d FROM t2a NATURAL JOIN t2b;
+ INSERT INTO t2a VALUES(3,93),(4,94),(5,95),(6,96),(7,97);
+ INSERT INTO t2b VALUES(91,11),(92,22),(93,33),(94,44),(95,55);
+ CREATE TABLE dual(dummy TEXT);
+ INSERT INTO dual(dummy) VALUES('x');
+}
+db null NULL
+do_execsql_test autoindex-1210 {
+ SELECT t1.*, t2.* FROM t2 LEFT OUTER JOIN t1 ON b=c ORDER BY +b;
+} {
+ NULL NULL NULL 5 55
+ 1 3 91 3 33
+ 1 4 92 4 44
+}
+do_execsql_test autoindex-1211 {
+ EXPLAIN QUERY PLAN
+ SELECT t1.*, t2.* FROM t2 LEFT OUTER JOIN t1 ON b=c ORDER BY +b;
+} {/SEARCH t1 USING AUTOMATIC COVERING INDEX/}
+
finish_test
diff --git a/test/capi3.test b/test/capi3.test
index e65f90e3aa..6319d8284d 100644
--- a/test/capi3.test
+++ b/test/capi3.test
@@ -689,7 +689,9 @@ do_test capi3-6.3 {
sqlite3_finalize $STMT
} {SQLITE_OK}
-if {[clang_sanitize_address]==0} {
+if {0 && [clang_sanitize_address]==0} {
+ # This use-after-free occasionally causes segfaults during ordinary
+ # builds. Let's just disable it completely.
do_test capi3-6.4-misuse {
db cache flush
sqlite3_close $DB
diff --git a/test/date.test b/test/date.test
index d22b652b47..2042880a92 100644
--- a/test/date.test
+++ b/test/date.test
@@ -651,5 +651,13 @@ datetest 19.51 {date('2000-08-31','+0022-06-00','floor')} {2023-02-28}
datetest 19.52 {date('2000-08-31','+0023-06-00','ceiling')} {2024-03-02}
datetest 19.53 {date('2000-08-31','+0022-06-00','ceiling')} {2023-03-03}
+# 2025-01-21
+# https://sqlite.org/forum/forumpost/766a2c9231
+#
+datetest 20.1 {datetime('2024-12-31 23:59:59.9990')} {2024-12-31 23:59:59}
+datetest 20.2 {datetime('2024-12-31 23:59:59.9999999999999')} \
+ {2024-12-31 23:59:59}
+datetest 20.3 {datetime('2024-12-31 23:59:59.9995')} {2024-12-31 23:59:59}
+datetest 20.4 {datetime('2024-12-31 23:59:58.9995')} {2024-12-31 23:59:58}
finish_test
diff --git a/test/dbpage.test b/test/dbpage.test
index 0646a70b02..8039e0e1be 100644
--- a/test/dbpage.test
+++ b/test/dbpage.test
@@ -108,4 +108,148 @@ do_execsql_test 300 {
SELECT * FROM sqlite_temp_schema, sqlite_dbpage;
} {}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 400 {
+ ATTACH ':memory:' AS aux1;
+ BEGIN;
+ CREATE VIRTUAL TABLE aux1.t1 USING sqlite_dbpage;
+ INSERT INTO t1 VALUES(17, NULL);
+ COMMIT;
+}
+
+#-------------------------------------------------------------------------
+reset_db
+forcedelete test.db2
+sqlite3 db2 test.db2
+db2 eval {
+ PRAGMA auto_vacuum=NONE;
+ CREATE TABLE t1(x, y);
+}
+
+do_execsql_test 500 {
+ PRAGMA auto_vacuum=NONE;
+ CREATE TABLE x1(a);
+ INSERT INTO x1 VALUES( hex(randomblob(2000)) );
+ INSERT INTO x1 VALUES( hex(randomblob(2000)) );
+ INSERT INTO x1 VALUES( hex(randomblob(2000)) );
+ INSERT INTO x1 VALUES( hex(randomblob(2000)) );
+ PRAGMA page_count;
+} {18}
+
+do_test 510 {
+ db eval BEGIN
+ db2 eval { PRAGMA page_count } {
+ db eval {
+ INSERT INTO sqlite_dbpage values($page_count, NULL);
+ }
+ }
+ db2 eval { SELECT pgno, data FROM sqlite_dbpage } {
+ db eval {
+ INSERT INTO sqlite_dbpage values($pgno, $data);
+ }
+ }
+
+ db eval COMMIT
+} {}
+
+db close
+sqlite3 db test.db
+
+do_execsql_test 520 {
+ PRAGMA page_count;
+ SELECT * FROM t1;
+} {2}
+
+db2 close
+
+#-------------------------------------------------------------------------
+reset_db
+forcedelete test.db2
+do_execsql_test 610 {
+ ATTACH 'test.db2' AS aux;
+ CREATE TABLE t1(x);
+ CREATE TABLE t2(y);
+ INSERT INTO t1 VALUES(1234);
+ CREATE TABLE aux.x1(z);
+}
+
+set pgno [db one {SELECT max(rootpage) FROM sqlite_schema}]
+sqlite3 db2 test.db2
+db2 eval {
+ BEGIN;
+ SELECT * FROM x1;
+}
+
+do_catchsql_test 620 {
+ UPDATE sqlite_dbpage SET data = (
+ SELECT data FROM sqlite_dbpage WHERE pgno=$pgno-1
+ ) WHERE pgno = $pgno;
+} {1 {database is locked}}
+
+db2 eval {
+ COMMIT;
+}
+
+do_catchsql_test 630 {
+ UPDATE sqlite_dbpage SET data = (
+ SELECT data FROM sqlite_dbpage WHERE pgno=$pgno-1
+ ) WHERE pgno = $pgno;
+} {0 {}}
+
+db close
+sqlite3 db test.db
+
+do_execsql_test 640 {
+ SELECT * FROM t2;
+} {1234}
+
+db2 close
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 700 {
+ CREATE TABLE t1(x);
+ INSERT INTO t1 VALUES( hex(randomblob(1000)) );
+ INSERT INTO t1 VALUES( hex(randomblob(1000)) );
+ INSERT INTO t1 VALUES( hex(randomblob(1000)) );
+}
+
+forcedelete test.db2
+sqlite3 db2 test.db2
+db2 eval {
+ CREATE TABLE y1(y);
+ INSERT INTO y1 VALUES( hex(randomblob(1000)) );
+}
+
+set max [db2 one {PRAGMA page_count}]
+
+do_test 710 {
+ execsql {
+ BEGIN;
+ }
+
+ for {set ii 1} {$ii <= $max} {incr ii} {
+ set data [db2 one {SELECT data FROM sqlite_dbpage WHERE pgno=$ii}]
+ execsql {
+ UPDATE sqlite_dbpage SET data=$data WHERE pgno=$ii
+ }
+ }
+
+ execsql {
+ SAVEPOINT abc;
+ INSERT INTO sqlite_dbpage VALUES(2, NULL);
+ ROLLBACK TO abc;
+ COMMIT;
+ }
+} {}
+
+db close
+sqlite3 db test.db
+
+do_execsql_test 720 {
+ PRAGMA integrity_check
+} {ok}
+
+
finish_test
diff --git a/test/dbpagefault.test b/test/dbpagefault.test
index f27741cba1..e5b246fc94 100644
--- a/test/dbpagefault.test
+++ b/test/dbpagefault.test
@@ -82,5 +82,31 @@ do_catchsql_test 3.2 {
# faultsim_test_result {0 {}}
#}
+reset_db
+forcedelete test.db2
+do_execsql_test 4.0 {
+ CREATE TABLE t1(x);
+ INSERT INTO t1 VALUES('one');
+ CREATE TABLE t2(x);
+ INSERT INTO t2 VALUES('two');
+ ATTACH 'test.db2' AS aux;
+ CREATE TABLE aux.x1(x);
+}
+
+set pgno [db one {SELECT max(rootpage) FROM sqlite_schema}]
+
+faultsim_save_and_close
+do_faultsim_test 4 -prep {
+ faultsim_restore_and_reopen
+ execsql { ATTACH 'test.db2' AS aux; }
+} -body {
+ execsql {
+ UPDATE sqlite_dbpage SET data = (
+ SELECT data FROM sqlite_dbpage WHERE pgno=($pgno-1)
+ ) WHERE pgno = $pgno;
+ }
+} -test {
+ faultsim_test_result {0 {}} {1 {unable to open a temporary database file for storing temporary tables}}
+}
finish_test
diff --git a/test/fkey6.test b/test/fkey6.test
index b658f20fea..72de926b52 100644
--- a/test/fkey6.test
+++ b/test/fkey6.test
@@ -225,5 +225,47 @@ do_execsql_test 3.3.4 {
SELECT * FROM p2;
} {0 one 1 deleted!}
+#-------------------------------------------------------------------------
+# Verify that, even with "PRAGMA defer_foreign_keys", a transaction cannot
+# be committed if there are outstanding foreign key violations.
+#
+reset_db
+do_execsql_test 4.0 {
+ CREATE TABLE p1(a INTEGER PRIMARY KEY, b UNIQUE);
+ CREATE TABLE c1(x REFERENCES p1(b));
+
+ INSERT INTO p1 VALUES(1, 'one'), (2, 'two'), (3, 'three');
+ INSERT INTO c1 VALUES('two');
+
+ PRAGMA foreign_keys = 1;
+ PRAGMA defer_foreign_keys = 1;
+}
+
+do_execsql_test 4.1 {
+ BEGIN;
+ DELETE FROM p1 WHERE a=2;
+}
+
+do_catchsql_test 4.2 {
+ COMMIT;
+} {1 {FOREIGN KEY constraint failed}}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 5.0 {
+ PRAGMA foreign_keys = 1;
+ CREATE TABLE p1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE c1(x REFERENCES p1 DEFERRABLE INITIALLY DEFERRED);
+}
+
+do_execsql_test 5.1 {
+ BEGIN;
+ INSERT INTO c1 VALUES(123);
+ PRAGMA defer_foreign_keys = 1;
+ INSERT INTO p1 VALUES(123, 'one two three');
+ COMMIT;
+}
+
finish_test
diff --git a/test/fuzzcheck.c b/test/fuzzcheck.c
index 9f339096bc..84e3f32895 100644
--- a/test/fuzzcheck.c
+++ b/test/fuzzcheck.c
@@ -507,7 +507,8 @@ static void writefileFunc(
static void blobListLoadFromDb(
sqlite3 *db, /* Read from this database */
const char *zSql, /* Query used to extract the blobs */
- int onlyId, /* Only load where id is this value */
+ int firstId, /* First sqlid to load */
+ int lastId, /* Last sqlid to load */
int *pN, /* OUT: Write number of blobs loaded here */
Blob **ppList /* OUT: Write the head of the blob list here */
){
@@ -518,8 +519,9 @@ static void blobListLoadFromDb(
int rc;
char *z2;
- if( onlyId>0 ){
- z2 = sqlite3_mprintf("%s WHERE rowid=%d", zSql, onlyId);
+ if( firstId>0 ){
+ z2 = sqlite3_mprintf("%s WHERE rowid BETWEEN %d AND %d", zSql,
+ firstId, lastId);
}else{
z2 = sqlite3_mprintf("%s", zSql);
}
@@ -1836,7 +1838,8 @@ static void showHelp(void){
"each database, checking for crashes and memory leaks.\n"
"Options:\n"
" --cell-size-check Set the PRAGMA cell_size_check=ON\n"
-" --dbid N Use only the database where dbid=N\n"
+" --dbid M..N Use only the databases where dbid between M and N\n"
+" \"M..\" for M and afterwards. Just \"M\" for M only\n"
" --export-db DIR Write databases to files(s) in DIR. Works with --dbid\n"
" --export-sql DIR Write SQL to file(s) in DIR. Also works with --sqlid\n"
" --help Show this help text\n"
@@ -1861,7 +1864,8 @@ static void showHelp(void){
" --script Output CLI script instead of running tests\n"
" --skip N Skip the first N test cases\n"
" --spinner Use a spinner to show progress\n"
-" --sqlid N Use only SQL where sqlid=N\n"
+" --sqlid M..N Use only SQL where sqlid between M..N\n"
+" \"M..\" for M and afterwards. Just \"M\" for M only\n"
" --timeout N Maximum time for any one test in N millseconds\n"
" -v|--verbose Increased output. Repeat for more output.\n"
" --vdbe-debug Activate VDBE debugging.\n"
@@ -1883,8 +1887,10 @@ int main(int argc, char **argv){
Blob *pDb; /* For looping over template databases */
int i; /* Loop index for the argv[] loop */
int dbSqlOnly = 0; /* Only use scripts that are dbsqlfuzz */
- int onlySqlid = -1; /* --sqlid */
- int onlyDbid = -1; /* --dbid */
+ int firstSqlid = -1; /* First --sqlid range */
+ int lastSqlid = 0x7fffffff; /* Last --sqlid range */
+ int firstDbid = -1; /* --dbid */
+ int lastDbid = 0x7fffffff; /* --dbid end */
int nativeFlag = 0; /* --native-vfs */
int rebuildFlag = 0; /* --rebuild */
int vdbeLimitFlag = 0; /* --limit-vdbe */
@@ -1917,6 +1923,7 @@ int main(int argc, char **argv){
int bTimer = 0; /* Show elapse time for each test */
int nV; /* How much to increase verbosity with -vvvv */
sqlite3_int64 tmStart; /* Start of each test */
+ int iEstTime = 0; /* LPF for the time-to-go */
sqlite3_config(SQLITE_CONFIG_URI,1);
registerOomSimulator();
@@ -1941,8 +1948,18 @@ int main(int argc, char **argv){
cellSzCkFlag = 1;
}else
if( strcmp(z,"dbid")==0 ){
+ const char *zDotDot;
if( i>=argc-1 ) fatalError("missing arguments on %s", argv[i]);
- onlyDbid = integerValue(argv[++i]);
+ i++;
+ zDotDot = strstr(argv[i], "..");
+ if( zDotDot ){
+ firstDbid = atoi(argv[i]);
+ if( zDotDot[2] ){
+ lastDbid = atoi(&zDotDot[2]);
+ }
+ }else{
+ lastDbid = firstDbid = integerValue(argv[i]);
+ }
}else
if( strcmp(z,"export-db")==0 ){
if( i>=argc-1 ) fatalError("missing arguments on %s", argv[i]);
@@ -2042,8 +2059,19 @@ int main(int argc, char **argv){
bTimer = 1;
}else
if( strcmp(z,"sqlid")==0 ){
+ const char *zDotDot;
if( i>=argc-1 ) fatalError("missing arguments on %s", argv[i]);
- onlySqlid = integerValue(argv[++i]);
+ i++;
+ zDotDot = strstr(argv[i], "..");
+ if( zDotDot ){
+ firstSqlid = atoi(argv[i]);
+ if( zDotDot[2] ){
+ lastSqlid = atoi(&zDotDot[2]);
+ }
+ }else{
+ firstSqlid = integerValue(argv[i]);
+ lastSqlid = firstSqlid;
+ }
}else
if( strcmp(z,"timeout")==0 ){
if( i>=argc-1 ) fatalError("missing arguments on %s", argv[i]);
@@ -2291,13 +2319,14 @@ int main(int argc, char **argv){
const char *zExDb =
"SELECT writefile(printf('%s/db%06d.db',?1,dbid),dbcontent),"
" dbid, printf('%s/db%06d.db',?1,dbid), length(dbcontent)"
- " FROM db WHERE ?2<0 OR dbid=?2;";
+ " FROM db WHERE dbid BETWEEN ?2 AND ?3;";
rc = sqlite3_prepare_v2(db, zExDb, -1, &pStmt, 0);
if( rc ) fatalError("cannot prepare statement [%s]: %s",
zExDb, sqlite3_errmsg(db));
sqlite3_bind_text64(pStmt, 1, zExpDb, strlen(zExpDb),
SQLITE_STATIC, SQLITE_UTF8);
- sqlite3_bind_int(pStmt, 2, onlyDbid);
+ sqlite3_bind_int(pStmt, 2, firstDbid);
+ sqlite3_bind_int(pStmt, 3, lastDbid);
while( sqlite3_step(pStmt)==SQLITE_ROW ){
printf("write db-%d (%d bytes) into %s\n",
sqlite3_column_int(pStmt,1),
@@ -2310,13 +2339,14 @@ int main(int argc, char **argv){
const char *zExSql =
"SELECT writefile(printf('%s/sql%06d.txt',?1,sqlid),sqltext),"
" sqlid, printf('%s/sql%06d.txt',?1,sqlid), length(sqltext)"
- " FROM xsql WHERE ?2<0 OR sqlid=?2;";
+ " FROM xsql WHERE sqlid BETWEEN ?2 AND ?3;";
rc = sqlite3_prepare_v2(db, zExSql, -1, &pStmt, 0);
if( rc ) fatalError("cannot prepare statement [%s]: %s",
zExSql, sqlite3_errmsg(db));
sqlite3_bind_text64(pStmt, 1, zExpSql, strlen(zExpSql),
SQLITE_STATIC, SQLITE_UTF8);
- sqlite3_bind_int(pStmt, 2, onlySqlid);
+ sqlite3_bind_int(pStmt, 2, firstSqlid);
+ sqlite3_bind_int(pStmt, 3, lastSqlid);
while( sqlite3_step(pStmt)==SQLITE_ROW ){
printf("write sql-%d (%d bytes) into %s\n",
sqlite3_column_int(pStmt,1),
@@ -2332,11 +2362,11 @@ int main(int argc, char **argv){
/* Load all SQL script content and all initial database images from the
** source db
*/
- blobListLoadFromDb(db, "SELECT sqlid, sqltext FROM xsql", onlySqlid,
- &g.nSql, &g.pFirstSql);
+ blobListLoadFromDb(db, "SELECT sqlid, sqltext FROM xsql", firstSqlid,
+ lastSqlid, &g.nSql, &g.pFirstSql);
if( g.nSql==0 ) fatalError("need at least one SQL script");
- blobListLoadFromDb(db, "SELECT dbid, dbcontent FROM db", onlyDbid,
- &g.nDb, &g.pFirstDb);
+ blobListLoadFromDb(db, "SELECT dbid, dbcontent FROM db", firstDbid,
+ lastDbid, &g.nDb, &g.pFirstDb);
if( g.nDb==0 ){
g.pFirstDb = safe_realloc(0, sizeof(Blob));
memset(g.pFirstDb, 0, sizeof(Blob));
@@ -2416,9 +2446,29 @@ int main(int argc, char **argv){
if( bScript ){
/* No progress output */
}else if( bSpinner ){
- int nTotal =g.nSql;
+ int nTotal = g.nSql;
int idx = pSql->seq;
- printf("\r%s: %d/%d ", zDbName, idx, nTotal);
+ if( nSrcDb==1 && nTotal>idx && idx>=20 ){
+ int iToGo = (timeOfDay() - iBegin)*(nTotal-idx)/(idx*1000);
+ int hr, min, sec;
+ if( idx==20 ){
+ iEstTime = iToGo;
+ }else{
+ iEstTime = (iToGo + 7*iEstTime)/8;
+ }
+ hr = iEstTime/3600;
+ min = (iEstTime/60)%60;
+ sec = iEstTime%60;
+ if( hr>0 ){
+ printf("\r%s: %d/%d ETC %d:%02d:%02d ",
+ zDbName, idx, nTotal, hr, min, sec);
+ }else{
+ printf("\r%s: %d/%d ETC %02d:%02d ",
+ zDbName, idx, nTotal, min, sec);
+ }
+ }else{
+ printf("\r%s: %d/%d ", zDbName, idx, nTotal);
+ }
fflush(stdout);
}else if( verboseFlag>1 ){
printf("%s\n", g.zTestName);
@@ -2457,7 +2507,7 @@ int main(int argc, char **argv){
}else if( bSpinner ){
int nTotal = g.nDb*g.nSql;
int idx = pSql->seq*g.nDb + pDb->id - 1;
- printf("\r%s: %d/%d ", zDbName, idx, nTotal);
+ printf("\r%s: %d/%d ", zDbName, idx, nTotal);
fflush(stdout);
}else if( verboseFlag>1 ){
printf("%s\n", g.zTestName);
@@ -2560,7 +2610,7 @@ int main(int argc, char **argv){
/* No progress output */
}else if( bSpinner ){
int nTotal = g.nDb*g.nSql;
- printf("\r%s: %d/%d \n", zDbName, nTotal, nTotal);
+ printf("\r%s: %d/%d \n", zDbName, nTotal, nTotal);
}else if( !quietFlag && verboseFlag<2 ){
printf(" 100%% - %d tests\n", g.nDb*g.nSql);
}
diff --git a/test/fuzzdata8.db b/test/fuzzdata8.db
index 3e34180071..469df2c681 100644
Binary files a/test/fuzzdata8.db and b/test/fuzzdata8.db differ
diff --git a/test/in7.test b/test/in7.test
index 4dc0821d18..763396140a 100644
--- a/test/in7.test
+++ b/test/in7.test
@@ -219,4 +219,33 @@ do_execsql_test 3.8 {
SELECT t1.a, t2.b FROM t1, t2 WHERE (t1.a, t2.b) IN ((1, 2));
} {1 2}
+# 2025-01-30 Inifinite loop in byte-code discovered by dbsqlfuzz
+# having to do with SubrtnSig logic. The code was using a Subroutine
+# from within itself resulting in infinite recursion.
+#
+# This test will spin forever if the bug has not been fixed, or if
+# it reappears.
+#
+reset_db
+do_execsql_test 4.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 VALUES(1,x'1111');
+ CREATE TABLE t2(c);
+ CREATE TABLE t3(d);
+ CREATE TRIGGER t1tr UPDATE ON t1 BEGIN
+ UPDATE t1 SET b=x'2222' FROM t2;
+ UPDATE t1
+ SET b = (SELECT a IN (SELECT a
+ FROM t1
+ WHERE (b,a) IN (SELECT rowid, d
+ FROM t3
+ )
+ )
+ FROM t1 NATURAL RIGHT JOIN t1
+ );
+ END;
+ UPDATE t1 SET b=x'3333';
+ SELECT quote(b) FROM t1;
+} {X'3333'}
+
finish_test
diff --git a/test/like3.test b/test/like3.test
index a93e113d62..0b28574376 100644
--- a/test/like3.test
+++ b/test/like3.test
@@ -275,4 +275,84 @@ do_eqp_test like3-6.240 {
}
}
+#-------------------------------------------------------------------------
+
+ifcapable utf16 {
+ reset_db
+ do_execsql_test like3-7.0 {
+ PRAGMA encoding = 'UTF-16be';
+
+ CREATE TABLE Example(word TEXT NOT NULL);
+ CREATE INDEX Example_word on Example(word);
+
+ INSERT INTO Example VALUES(char(0x307F));
+ }
+
+ do_execsql_test like3-7.1 {
+ SELECT char(0x307F)=='み';
+ } {1}
+
+ do_execsql_test like3-7.1 {
+ SELECT * FROM Example WHERE word GLOB 'み*'
+ } {み}
+
+ do_execsql_test like3-7.2 {
+ SELECT * FROM Example WHERE word >= char(0x307F) AND word < char(0x3080);
+ } {み}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+
+foreach enc {
+ UTF-8
+ UTF-16le
+ UTF-16be
+} {
+ foreach {tn expr} {
+ 1 "CAST (X'FF' AS TEXT)"
+ 2 "CAST (X'FFBF' AS TEXT)"
+ 3 "CAST (X'FFBFBF' AS TEXT)"
+ 4 "CAST (X'FFBFBFBF' AS TEXT)"
+
+ 5 "'abc' || CAST (X'FF' AS TEXT)"
+ 6 "'def' || CAST (X'FFBF' AS TEXT)"
+ 7 "'ghi' || CAST (X'FFBFBF' AS TEXT)"
+ 8 "'jkl' || CAST (X'FFBFBFBF' AS TEXT)"
+ } {
+ reset_db
+ execsql "PRAGMA encoding = '$enc'"
+ set tn utf[string range $enc 4 end].$tn
+ do_execsql_test like3-8.$tn.1 {
+ CREATE TABLE t1(x);
+ }
+
+ do_execsql_test like3-8.$tn.2 {
+ PRAGMA encoding
+ } $enc
+
+ do_execsql_test like3-8.$tn.3 "
+ INSERT INTO t1 VALUES( $expr )
+ "
+
+ do_execsql_test like3-8.$tn.4 {
+ SELECT typeof(x) FROM t1
+ } {text}
+
+ set x [db one {SELECT x || '%' FROM t1}]
+
+ do_execsql_test like3-8.$tn.5 {
+ SELECT rowid FROM t1 WHERE x LIKE $x
+ } 1
+
+ do_execsql_test like3-8.$tn.6 {
+ CREATE INDEX i1 ON t1(x);
+ }
+
+ do_execsql_test like3-8.$tn.7 {
+ SELECT rowid FROM t1 WHERE x LIKE $x
+ } 1
+ }
+}
+
finish_test
diff --git a/test/pragma4.test b/test/pragma4.test
index 0466960cab..2ba87c0c60 100644
--- a/test/pragma4.test
+++ b/test/pragma4.test
@@ -301,7 +301,7 @@ ifcapable vtab {
do_test 6.3 {
set ::log
} {}
- test_sqlite3_log {}
+ test_sqlite3_log
}
# 2024-05-08 https://sqlite.org/forum/forumpost/cf29a33e94
diff --git a/test/speedtest.md b/test/speedtest.md
new file mode 100644
index 0000000000..135e562aed
--- /dev/null
+++ b/test/speedtest.md
@@ -0,0 +1,53 @@
+# Performance And Size Measurements
+
+This document shows a procedure for making performance and size
+comparisons between two versions of the SQLite Amalgamation "sqlite3.c".
+You will need:
+
+ * fossil
+ * valgrind
+ * tclsh
+ * A script or program named "open" that brings up *.txt files in an
+ editor for viewing. (Macs provide this by default. You'll need to
+ come up with your own on Linux and Windows.)
+ * An SQLite source tree
+
+The procedure described in this document is not the only way to make
+performance and size measurements. Use this as a guide and make
+adjustments as needed.
+
+## Establish the baseline measurement
+
+ * Begin at the root the SQLite source tree
+ * mkdir -p ../speed
+ ↑ Speed measurement output files will go into this directory.
+ You can actually put those files wherever you want. This is just a
+ suggestion. It might be good to keep these files outside of the
+ source tree so that "fossil clean" does not delete them.
+ * Obtain the baseline SQLite amalgamation. For the purpose of this
+ technical note, assume the baseline SQLite sources are in files
+ "../baseline/sqlite3.c" and "../baseline/sqlite3.h".
+ * test/speedtest.tcl ../baseline/sqlite3.c ../speed/baseline.txt
+ ↑ The performance measure will be written into ../speed/baseline.txt
+ and that file will be brought up in an editor for easy viewing.
+ ↑ The "sqlite3.h" will be taken from the directory that contains
+ the "sqlite3.c" amalgamation file.
+
+## Comparing the current checkout against the baseline
+
+ * make sqlite3.c
+ * test/speedtest.tcl sqlite3.c ../speed/test.txt ../speed/baseline.txt
+ ↑ Test results written into ../speed/test.txt and then
+ "fossil xdiff" is run to compare ../speed/baseline.txt against
+ the new test results.
+
+## When to do this
+
+Performance and size checks should be done prior to trunk check-ins.
+Sometimes a seemingly innocuous change can have large performance
+impacts. A large impact does not mean that the change cannot continue,
+but it is important to be aware of the impact.
+
+## Additional hints
+
+Use the --help option to test/speedtest.tcl to see other available options.
diff --git a/test/speedtest.tcl b/test/speedtest.tcl
new file mode 100755
index 0000000000..1ad92d9ab0
--- /dev/null
+++ b/test/speedtest.tcl
@@ -0,0 +1,304 @@
+#!/bin/sh
+# the next line restarts using tclsh \
+exec tclsh "$0" ${1+"$@"}
+#
+# This program runs performance testing on sqlite3.c. Usage:
+set usage {USAGE:
+
+ speedtest.tcl sqlite3.c x1.txt trunk.txt -Os -DSQLITE_ENABLE_STAT4
+ | | | `-----------------------'
+ File to test ----' | | |
+ | | `- options
+ Output filename --------' |
+ `--- optional prior output to diff
+
+Do a cache-grind performance analysis of the sqlite3.c file named and
+write the results into the output file. The ".txt" is appended to the
+output file (and diff-file) name if it is not already present. If the
+diff-file is specified then show a diff from the diff-file to the new
+output.
+
+Other options include:
+ CC=... Specify an alternative C compiler. Default is "gcc".
+ -D... -D and -O options are passed through to the C compiler.
+ --dryrun Show what would happen but don't do anything.
+ --help Show this help screen.
+ --lean "Lean" mode.
+ --lookaside N SZ Lookahead uses N slots of SZ bytes each.
+ --pagesize N Use N as the page size.
+ --quiet | -q "Quite". Put results in file but don't pop up editor
+ --size N Change the test size. 100 means 100%. Default: 5.
+ --testset TEST Specify the specific testset to use. The default
+ is "mix1". Other options include: "main", "json",
+ "cte", "orm", "fp", "rtree".
+}
+set srcfile {}
+set outfile {}
+set difffile {}
+set cflags {-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_THREADSAFE=0}
+set cc gcc
+set testset mix1
+set dryrun 0
+set quiet 0
+set speedtestflags {--shrink-memory --reprepare --stats --heap 40000000 64}
+lappend speedtestflags --journal wal --size 5
+
+for {set i 0} {$i<[llength $argv]} {incr i} {
+ set arg [lindex $argv $i]
+ if {[string index $arg 0]=="-"} {
+ switch -- $arg {
+ -pagesize -
+ --pagesize {
+ lappend speedtestflags --pagesize
+ incr i
+ lappend speedtestflags [lindex $argv $i]
+ }
+ -lookaside -
+ --lookaside {
+ lappend speedtestflags --lookaside
+ incr i
+ lappend speedtestflags [lindex $argv $i]
+ incr i
+ lappend speedtestflags [lindex $argv $i]
+ }
+ -lean -
+ --lean {
+ lappend cflags \
+ -DSQLITE_DEFAULT_MEMSTATUS=0 \
+ -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
+ -DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1 \
+ -DSQLITE_MAX_EXPR_DEPTH=1 \
+ -DSQLITE_OMIT_DECLTYPE \
+ -DSQLITE_OMIT_DEPRECATED \
+ -DSQLITE_OMIT_PROGRESS_CALLBACK \
+ -DSQLITE_OMIT_SHARED_CACHE \
+ -DSQLITE_USE_ALLOCA
+ }
+ -testset -
+ --testset {
+ incr i
+ set testset [lindex $argv $i]
+ }
+ -size -
+ --size {
+ incr i
+ set newsize [lindex $argv $i]
+ if {$newsize<1} {set newsize 1}
+ set speedtestflags \
+ [regsub {.-size \d+} $speedtestflags "-size $newsize"]
+ }
+ -n -
+ -dryrun -
+ --dryrun {
+ set dryrun 1
+ }
+ -? -
+ -help -
+ --help {
+ puts $usage
+ exit 0
+ }
+ -q -
+ -quiet -
+ --quiet {
+ set quiet 1
+ }
+ default {
+ lappend cflags $arg
+ }
+ }
+ continue
+ }
+ if {[string match CC=* $arg]} {
+ set cc [lrange $arg 3 end]
+ continue
+ }
+ if {[string match *.c $arg]} {
+ if {$srcfile!=""} {
+ puts stderr "multiple source files: $srcfile $arg"
+ exit 1
+ }
+ set srcfile $arg
+ continue
+ }
+ if {[lsearch {main cte rtree orm fp json parsenumber mix1} $arg]>=0} {
+ set testset $arg
+ continue
+ }
+ if {$outfile==""} {
+ set outfile $arg
+ continue
+ }
+ if {$difffile==""} {
+ set difffile $arg
+ continue
+ }
+ puts stderr "unknown option: \"$arg\". Use --help for more info."
+ exit 1
+}
+if {[lsearch -glob $cflags -O*]<0} {
+ lappend cflags -Os
+}
+if {[lsearch -glob $cflags -DSQLITE_ENABLE_MEMSYS*]<0} {
+ lappend cflags -DSQLITE_ENABLE_MEMSYS5
+}
+if {[lsearch -glob $cflags -DSQLITE_ENABLE_RTREE*]<0} {
+ lappend cflags -DSQLITE_ENABLE_RTREE
+}
+if {$srcfile==""} {
+ puts stderr "no sqlite3.c source file specified"
+ exit 1
+}
+if {![file readable $srcfile]} {
+ puts stderr "source file \"$srcfile\" does not exist"
+ exit 1
+}
+if {$outfile==""} {
+ puts stderr "no output file specified"
+ exit 1
+}
+if {![string match *.* [file tail $outfile]]} {
+ append outfile .txt
+}
+if {$difffile!=""} {
+ if {![file exists $difffile]} {
+ if {[file exists $difffile.txt]} {
+ append difffile .txt
+ } else {
+ puts stderr "No such file: \"$difffile\""
+ exit 1
+ }
+ }
+}
+
+set cccmd [list $cc -g]
+lappend cccmd -I[file dir $srcfile]
+lappend cccmd {*}[lsort $cflags]
+lappend cccmd [file dir $argv0]/speedtest1.c
+lappend cccmd $srcfile
+lappend cccmd -o speedtest1
+puts $cccmd
+if {!$dryrun} {
+ exec {*}$cccmd
+}
+lappend speedtestflags --testset $testset
+set stcmd [list valgrind --tool=cachegrind ./speedtest1 {*}$speedtestflags]
+lappend stcmd speedtest1.db
+lappend stcmd >valgrind-out.txt 2>valgrind-err.txt
+puts $stcmd
+if {!$dryrun} {
+ foreach file {speedtest1.db speedtest1.db-journal speedtest1.db-wal
+ speedtest1.db-shm} {
+ if {[file exists $file]} {file delete $file}
+ }
+ exec {*}$stcmd
+}
+
+set maxmtime 0
+set cgfile {}
+foreach cgout [glob -nocomplain cachegrind.out.*] {
+ if {[file mtime $cgout]>$maxmtime} {
+ set cgfile $cgout
+ set maxmtime [file mtime $cgfile]
+ }
+}
+if {$cgfile==""} {
+ puts "no cachegrind output"
+ exit 1
+}
+
+############# Process the cachegrind.out.# file ##########################
+set fd [open $outfile wb]
+set in [open "|cg_annotate --show=Ir --auto=yes --context=40 $cgfile" r]
+set dest !
+set out(!) {}
+set linenum 0
+set cntlines 0 ;# true to remember cycle counts on each line
+set seenSqlite3 0 ;# true if we have seen the sqlite3.c file
+while {![eof $in]} {
+ set line [string map {\t { }} [gets $in]]
+ if {[regexp {^-- Auto-annotated source: (.*)} $line all name]} {
+ set dest $name
+ if {[string match */sqlite3.c $dest]} {
+ set cntlines 1
+ set seenSqlite3 1
+ } else {
+ set cntlines 0
+ }
+ } elseif {[regexp {^-- line (\d+) ------} $line all ln]} {
+ set line [lreplace $line 2 2 {#}]
+ set linenum [expr {$ln-1}]
+ } elseif {[regexp {^The following files chosen for } $line]} {
+ set dest !
+ }
+ append out($dest) $line\n
+ if {$cntlines} {
+ incr linenum
+ if {[regexp {^ *([0-9,]+) } $line all x]} {
+ set x [string map {, {}} $x]
+ set cycles($linenum) $x
+ }
+ }
+}
+foreach x [lsort [array names out]] {
+ puts $fd $out($x)
+}
+# If the sqlite3.c file has been seen, then output a summary of the
+# cycle counts for each file that went into making up sqlite3.c
+#
+if {$seenSqlite3} {
+ close $in
+ set in [open sqlite3.c]
+ set linenum 0
+ set fn sqlite3.c
+ set pattern1 {^/\*+ Begin file ([^ ]+) \*}
+ set pattern2 {^/\*+ Continuing where we left off in ([^ ]+) \*}
+ while {![eof $in]} {
+ set line [gets $in]
+ incr linenum
+ if {[regexp $pattern1 $line all newfn]} {
+ set fn $newfn
+ } elseif {[regexp $pattern2 $line all newfn]} {
+ set fn $newfn
+ } elseif {[info exists cycles($linenum)]} {
+ incr fcycles($fn) $cycles($linenum)
+ }
+ }
+ close $in
+ puts $fd \
+ {**********************************************************************}
+ set lx {}
+ set sum 0
+ foreach {fn cnt} [array get fcycles] {
+ lappend lx [list $cnt $fn]
+ incr sum $cnt
+ }
+ puts $fd [format {%20s %14d %8.3f%%} TOTAL $sum 100]
+ foreach entry [lsort -index 0 -integer -decreasing $lx] {
+ foreach {cnt fn} $entry break
+ puts $fd [format {%20s %14d %8.3f%%} $fn $cnt [expr {$cnt*100.0/$sum}]]
+ }
+}
+puts $fd "Executable size:"
+close $fd
+exec size speedtest1 >>$outfile
+#
+# Processed cachegrind output should now be in the $outfile
+#############################################################################
+
+if {$quiet} {
+ # Skip this last part of popping up a GUI viewer
+} elseif {$difffile!=""} {
+ set fossilcmd {fossil xdiff --tk -c 20}
+ lappend fossilcmd $difffile
+ lappend fossilcmd $outfile
+ lappend fossilcmd &
+ puts $fossilcmd
+ if {!$dryrun} {
+ exec {*}$fossilcmd
+ }
+} else {
+ if {!$dryrun} {
+ exec open $outfile
+ }
+}
diff --git a/test/speedtest1.c b/test/speedtest1.c
index b0817858ae..b49c70098f 100644
--- a/test/speedtest1.c
+++ b/test/speedtest1.c
@@ -1,6 +1,28 @@
/*
** A program for performance testing.
**
+** To build this program against an historical version of SQLite for comparison
+** testing:
+**
+** Unix:
+**
+** ./configure --all
+** make clean speedtest1
+** mv speedtest1 speedtest1-current
+** cp $HISTORICAL_SQLITE3_C_H .
+** touch sqlite3.c sqlite3.h .target_source
+** make speedtest1
+** mv speedtest1 speedtest1-baseline
+**
+** Windows:
+**
+** nmake /f Makefile.msc clean speedtest1.exe
+** mv speedtest1.exe speedtest1-current.exe
+** cp $HISTORICAL_SQLITE_C_H .
+** touch sqlite3.c sqlite3.h .target_source
+** nmake /f Makefile.msc speedtest1.exe
+** mv speedtest1.exe speedtest1-baseline.exe
+**
** The available command-line options are described below:
*/
static const char zHelp[] =
@@ -42,7 +64,9 @@ static const char zHelp[] =
" --stats Show statistics at the end\n"
" --stmtscanstatus Activate SQLITE_DBCONFIG_STMT_SCANSTATUS\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, fp, debug)\n"
+ " --testset T Run test-set T (main, cte, rtree, orm, fp, json,\n"
+ " star, app, debug). Can be a comma-separated list\n"
+ " of values, with /SCALE suffixes or macro \"mix1\"\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"
@@ -88,6 +112,8 @@ struct HashContext {
/* All global state is held in this structure */
static struct Global {
sqlite3 *db; /* The open database connection */
+ const char *zDbName; /* Name of the database file */
+ const char *zVfs; /* --vfs NAME */
sqlite3_stmt *pStmt; /* Current SQL statement */
sqlite3_int64 iStart; /* Start-time for the current test */
sqlite3_int64 iTotal; /* Total time */
@@ -99,6 +125,7 @@ static struct Global {
int bMemShrink; /* Call sqlite3_db_release_memory() often */
int eTemp; /* 0: no TEMP. 9: always TEMP. */
int szTest; /* Scale factor for test iterations */
+ int szBase; /* Base size prior to testset scaling */
int nRepeat; /* Repeat selects this many times */
int doCheckpoint; /* Run PRAGMA wal_checkpoint after each trans */
int nReserve; /* Reserve bytes */
@@ -1432,6 +1459,561 @@ void testset_fp(void){
speedtest1_end_test();
}
+/*
+** A testset for star-schema queries.
+*/
+void testset_star(void){
+ int n;
+ int i;
+ n = g.szTest*50;
+ speedtest1_begin_test(100, "Create a fact table with %d entries", n);
+ speedtest1_exec(
+ "CREATE TABLE facttab("
+ " attr01 INT,"
+ " attr02 INT,"
+ " attr03 INT,"
+ " data01 TEXT,"
+ " attr04 INT,"
+ " attr05 INT,"
+ " attr06 INT,"
+ " attr07 INT,"
+ " attr08 INT,"
+ " factid INTEGER PRIMARY KEY,"
+ " data02 TEXT"
+ ");"
+ );
+ speedtest1_exec(
+ "WITH RECURSIVE counter(nnn) AS"
+ "(VALUES(1) UNION ALL SELECT nnn+1 FROM counter WHERE nnn<%d)"
+ "INSERT INTO facttab(attr01,attr02,attr03,attr04,attr05,"
+ "attr06,attr07,attr08,data01,data02)"
+ "SELECT random()%%12, random()%%13, random()%%14, random()%%15,"
+ "random()%%16, random()%%17, random()%%18, random()%%19,"
+ "concat('data-',nnn), format('%%x',random()) FROM counter;",
+ n
+ );
+ speedtest1_end_test();
+
+ speedtest1_begin_test(110, "Create indexes on all attributes columns");
+ for(i=1; i<=8; i++){
+ speedtest1_exec(
+ "CREATE INDEX fact_attr%02d ON facttab(attr%02d)", i, i
+ );
+ }
+ speedtest1_end_test();
+
+ speedtest1_begin_test(120, "Create dimension tables");
+ for(i=1; i<=8; i++){
+ speedtest1_exec(
+ "CREATE TABLE dimension%02d("
+ "beta%02d INT, "
+ "content%02d TEXT, "
+ "rate%02d REAL)",
+ i, i, i, i
+ );
+ speedtest1_exec(
+ "WITH RECURSIVE ctr(nn) AS"
+ " (VALUES(1) UNION ALL SELECT nn+1 FROM ctr WHERE nn<%d)"
+ " INSERT INTO dimension%02d"
+ " SELECT nn%%(%d), concat('content-%02d-',nn),"
+ " (random()%%10000)*0.125 FROM ctr;",
+ 4*(i+1), i, 2*(i+1), i
+ );
+ if( i&2 ){
+ speedtest1_exec(
+ "CREATE INDEX dim%02d ON dimension%02d(beta%02d);",
+ i, i, i
+ );
+ }else{
+ speedtest1_exec(
+ "CREATE INDEX dim%02d ON dimension%02d(beta%02d,content%02d);",
+ i, i, i, i
+ );
+ }
+ }
+ speedtest1_end_test();
+
+ speedtest1_begin_test(130, "Star query over the entire fact table");
+ speedtest1_exec(
+ "SELECT count(*), max(content04), min(content03), sum(rate04), avg(rate05)"
+ " FROM facttab, dimension01, dimension02, dimension03, dimension04,"
+ " dimension05, dimension06, dimension07, dimension08"
+ " WHERE attr01=beta01"
+ " AND attr02=beta02"
+ " AND attr03=beta03"
+ " AND attr04=beta04"
+ " AND attr05=beta05"
+ " AND attr06=beta06"
+ " AND attr07=beta07"
+ " AND attr08=beta08"
+ ";"
+ );
+ speedtest1_end_test();
+
+ speedtest1_begin_test(130, "Star query with LEFT JOINs");
+ speedtest1_exec(
+ "SELECT count(*), max(content04), min(content03), sum(rate04), avg(rate05)"
+ " FROM facttab LEFT JOIN dimension01 ON attr01=beta01"
+ " LEFT JOIN dimension02 ON attr02=beta02"
+ " JOIN dimension03 ON attr03=beta03"
+ " JOIN dimension04 ON attr04=beta04"
+ " JOIN dimension05 ON attr05=beta05"
+ " LEFT JOIN dimension06 ON attr06=beta06"
+ " JOIN dimension07 ON attr07=beta07"
+ " JOIN dimension08 ON attr08=beta08"
+ " WHERE facttab.data01 LIKE 'data-9%%'"
+ ";"
+ );
+ speedtest1_end_test();
+}
+
+/*
+** Tests that simulate an application opening and closing an SQLite database
+** frequently. Fossil is used as the model. The focus here is on rapidly
+** parsing the database schema and rapidly generating prepared statements,
+** in other words, rapid start-up of Fossil-like applications.
+**
+** The same database has no data, so the performance of sqlite3_step() is
+** not significant to this testset.
+*/
+static void testset_app(void){
+ int i, n;
+ speedtest1_begin_test(100, "Generate a Fossil-like database schema");
+ speedtest1_exec(
+ "BEGIN;"
+ "CREATE TABLE blob(\n"
+ " rid INTEGER PRIMARY KEY,\n"
+ " rcvid INTEGER,\n"
+ " size INTEGER,\n"
+ " uuid TEXT UNIQUE NOT NULL,\n"
+ " content BLOB,\n"
+ " CHECK( length(uuid)>=40 AND rid>0 )\n"
+ ");\n"
+ "CREATE TABLE delta(\n"
+ " rid INTEGER PRIMARY KEY,\n"
+ " srcid INTEGER NOT NULL REFERENCES blob\n"
+ ");\n"
+ "CREATE TABLE rcvfrom(\n"
+ " rcvid INTEGER PRIMARY KEY,\n"
+ " uid INTEGER REFERENCES user,\n"
+ " mtime DATETIME,\n"
+ " nonce TEXT UNIQUE,\n"
+ " ipaddr TEXT\n"
+ ");\n"
+ "CREATE TABLE private(rid INTEGER PRIMARY KEY);\n"
+ "CREATE TABLE accesslog(\n"
+ " uname TEXT,\n"
+ " ipaddr TEXT,\n"
+ " success BOOLEAN,\n"
+ " mtime TIMESTAMP\n"
+ ");\n"
+ "CREATE TABLE user(\n"
+ " uid INTEGER PRIMARY KEY,\n"
+ " login TEXT UNIQUE,\n"
+ " pw TEXT,\n"
+ " cap TEXT,\n"
+ " cookie TEXT,\n"
+ " ipaddr TEXT,\n"
+ " cexpire DATETIME,\n"
+ " info TEXT,\n"
+ " mtime DATE,\n"
+ " photo BLOB\n"
+ ", jx TEXT DEFAULT '{}');\n"
+ "CREATE TABLE reportfmt(\n"
+ " rn INTEGER PRIMARY KEY,\n"
+ " owner TEXT,\n"
+ " title TEXT UNIQUE,\n"
+ " mtime INTEGER,\n"
+ " cols TEXT,\n"
+ " sqlcode TEXT\n"
+ ", jx TEXT DEFAULT '{}');\n"
+ "CREATE TABLE config(\n"
+ " name TEXT PRIMARY KEY NOT NULL,\n"
+ " value CLOB, mtime INTEGER,\n"
+ " CHECK( typeof(name)='text' AND length(name)>=1 )\n"
+ ") WITHOUT ROWID;\n"
+ "CREATE TABLE shun(uuid PRIMARY KEY, mtime INTEGER, scom TEXT)\n"
+ " WITHOUT ROWID;\n"
+ "CREATE TABLE concealed(\n"
+ " hash TEXT PRIMARY KEY,\n"
+ " content TEXT\n"
+ ", mtime INTEGER) WITHOUT ROWID;\n"
+ "CREATE TABLE admin_log(\n"
+ " id INTEGER PRIMARY KEY,\n"
+ " time INTEGER, -- Seconds since 1970\n"
+ " page TEXT, -- path of page\n"
+ " who TEXT, -- User who made the change\n"
+ " what TEXT -- What changed\n"
+ ");\n"
+ "CREATE TABLE unversioned(\n"
+ " name TEXT PRIMARY KEY,\n"
+ " rcvid INTEGER,\n"
+ " mtime DATETIME,\n"
+ " hash TEXT,\n"
+ " sz INTEGER,\n"
+ " encoding INT,\n"
+ " content BLOB\n"
+ ") WITHOUT ROWID;\n"
+ "CREATE TABLE subscriber(\n"
+ " subscriberId INTEGER PRIMARY KEY,\n"
+ " subscriberCode BLOB DEFAULT (randomblob(32)) UNIQUE,\n"
+ " semail TEXT UNIQUE COLLATE nocase,\n"
+ " suname TEXT,\n"
+ " sverified BOOLEAN DEFAULT true,\n"
+ " sdonotcall BOOLEAN,\n"
+ " sdigest BOOLEAN,\n"
+ " ssub TEXT,\n"
+ " sctime INTDATE,\n"
+ " mtime INTDATE,\n"
+ " smip TEXT\n"
+ ", lastContact INT);\n"
+ "CREATE TABLE pending_alert(\n"
+ " eventid TEXT PRIMARY KEY,\n"
+ " sentSep BOOLEAN DEFAULT false,\n"
+ " sentDigest BOOLEAN DEFAULT false\n"
+ ", sentMod BOOLEAN DEFAULT false) WITHOUT ROWID;\n"
+ "CREATE TABLE filename(\n"
+ " fnid INTEGER PRIMARY KEY,\n"
+ " name TEXT UNIQUE\n"
+ ") STRICT;\n"
+ "CREATE TABLE mlink(\n"
+ " mid INTEGER,\n"
+ " fid INTEGER,\n"
+ " pmid INTEGER,\n"
+ " pid INTEGER,\n"
+ " fnid INTEGER REFERENCES filename,\n"
+ " pfnid INTEGER,\n"
+ " mperm INTEGER,\n"
+ " isaux INT DEFAULT 0\n"
+ ") STRICT;\n"
+ "CREATE TABLE plink(\n"
+ " pid INTEGER REFERENCES blob,\n"
+ " cid INTEGER REFERENCES blob,\n"
+ " isprim INT,\n"
+ " mtime REAL,\n"
+ " baseid INTEGER REFERENCES blob,\n"
+ " UNIQUE(pid, cid)\n"
+ ") STRICT;\n"
+ "CREATE TABLE leaf(rid INTEGER PRIMARY KEY);\n"
+ "CREATE TABLE event(\n"
+ " type TEXT,\n"
+ " mtime REAL,\n"
+ " objid INTEGER PRIMARY KEY,\n"
+ " tagid INTEGER,\n"
+ " uid INTEGER REFERENCES user,\n"
+ " bgcolor TEXT,\n"
+ " euser TEXT,\n"
+ " user TEXT,\n"
+ " ecomment TEXT,\n"
+ " comment TEXT,\n"
+ " brief TEXT,\n"
+ " omtime REAL\n"
+ ") STRICT;\n"
+ "CREATE TABLE phantom(\n"
+ " rid INTEGER PRIMARY KEY\n"
+ ");\n"
+ "CREATE TABLE orphan(\n"
+ " rid INTEGER PRIMARY KEY,\n"
+ " baseline INTEGER\n"
+ ") STRICT;\n"
+ "CREATE TABLE unclustered(\n"
+ " rid INTEGER PRIMARY KEY\n"
+ ");\n"
+ "CREATE TABLE unsent(\n"
+ " rid INTEGER PRIMARY KEY\n"
+ ");\n"
+ "CREATE TABLE tag(\n"
+ " tagid INTEGER PRIMARY KEY,\n"
+ " tagname TEXT UNIQUE\n"
+ ") STRICT;\n"
+ "CREATE TABLE tagxref(\n"
+ " tagid INTEGER REFERENCES tag,\n"
+ " tagtype INTEGER,\n"
+ " srcid INTEGER REFERENCES blob,\n"
+ " origid INTEGER REFERENCES blob,\n"
+ " value TEXT,\n"
+ " mtime REAL,\n"
+ " rid INTEGER REFERENCES blob,\n"
+ " UNIQUE(rid, tagid)\n"
+ ") STRICT;\n"
+ "CREATE TABLE backlink(\n"
+ " target TEXT,\n"
+ " srctype INT,\n"
+ " srcid INT,\n"
+ " mtime REAL,\n"
+ " UNIQUE(target, srctype, srcid)\n"
+ ") STRICT;\n"
+ "CREATE TABLE attachment(\n"
+ " attachid INTEGER PRIMARY KEY,\n"
+ " isLatest INT DEFAULT 0,\n"
+ " mtime REAL,\n"
+ " src TEXT,\n"
+ " target TEXT,\n"
+ " filename TEXT,\n"
+ " comment TEXT,\n"
+ " user TEXT\n"
+ ") STRICT;\n"
+ "CREATE TABLE cherrypick(\n"
+ " parentid INT,\n"
+ " childid INT,\n"
+ " isExclude INT DEFAULT false,\n"
+ " PRIMARY KEY(parentid, childid)\n"
+ ") WITHOUT ROWID, STRICT;\n"
+ "CREATE TABLE vcache(\n"
+ " vid INTEGER, -- check-in ID\n"
+ " fname TEXT, -- filename\n"
+ " rid INTEGER, -- artifact ID\n"
+ " PRIMARY KEY(vid,fname)\n"
+ ") WITHOUT ROWID;\n"
+ "CREATE TABLE synclog(\n"
+ " sfrom TEXT,\n"
+ " sto TEXT,\n"
+ " stime INT NOT NULL,\n"
+ " stype TEXT,\n"
+ " PRIMARY KEY(sfrom,sto)\n"
+ ") WITHOUT ROWID;\n"
+ "CREATE TABLE chat(\n"
+ " msgid INTEGER PRIMARY KEY AUTOINCREMENT,\n"
+ " mtime JULIANDAY,\n"
+ " lmtime TEXT,\n"
+ " xfrom TEXT,\n"
+ " xmsg TEXT,\n"
+ " fname TEXT,\n"
+ " fmime TEXT,\n"
+ " mdel INT,\n"
+ " file BLOB\n"
+ ");\n"
+ "CREATE TABLE ftsdocs(\n"
+ " rowid INTEGER PRIMARY KEY,\n"
+ " type CHAR(1),\n"
+ " rid INTEGER,\n"
+ " name TEXT,\n"
+ " idxed BOOLEAN,\n"
+ " label TEXT,\n"
+ " url TEXT,\n"
+ " mtime DATE,\n"
+ " bx TEXT,\n"
+ " UNIQUE(type,rid)\n"
+ ");\n"
+ "CREATE TABLE ticket(\n"
+ " -- Do not change any column that begins with tkt_\n"
+ " tkt_id INTEGER PRIMARY KEY,\n"
+ " tkt_uuid TEXT UNIQUE,\n"
+ " tkt_mtime DATE,\n"
+ " tkt_ctime DATE,\n"
+ " -- Add as many fields as required below this line\n"
+ " type TEXT,\n"
+ " status TEXT,\n"
+ " subsystem TEXT,\n"
+ " priority TEXT,\n"
+ " severity TEXT,\n"
+ " foundin TEXT,\n"
+ " private_contact TEXT,\n"
+ " resolution TEXT,\n"
+ " title TEXT,\n"
+ " comment TEXT\n"
+ ");\n"
+ "CREATE TABLE ticketchng(\n"
+ " -- Do not change any column that begins with tkt_\n"
+ " tkt_id INTEGER REFERENCES ticket,\n"
+ " tkt_rid INTEGER REFERENCES blob,\n"
+ " tkt_mtime DATE,\n"
+ " tkt_user TEXT,\n"
+ " -- Add as many fields as required below this line\n"
+ " login TEXT,\n"
+ " username TEXT,\n"
+ " mimetype TEXT,\n"
+ " icomment TEXT\n"
+ ");\n"
+ "CREATE TABLE forumpost(\n"
+ " fpid INTEGER PRIMARY KEY,\n"
+ " froot INT,\n"
+ " fprev INT,\n"
+ " firt INT,\n"
+ " fmtime REAL\n"
+ ");\n"
+ "CREATE INDEX delta_i1 ON delta(srcid);\n"
+ "CREATE INDEX blob_rcvid ON blob(rcvid);\n"
+ "CREATE INDEX subscriberUname\n"
+ " ON subscriber(suname) WHERE suname IS NOT NULL;\n"
+ "CREATE INDEX mlink_i1 ON mlink(mid);\n"
+ "CREATE INDEX mlink_i2 ON mlink(fnid);\n"
+ "CREATE INDEX mlink_i3 ON mlink(fid);\n"
+ "CREATE INDEX mlink_i4 ON mlink(pid);\n"
+ "CREATE INDEX plink_i2 ON plink(cid,pid);\n"
+ "CREATE INDEX event_i1 ON event(mtime);\n"
+ "CREATE INDEX orphan_baseline ON orphan(baseline);\n"
+ "CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime);\n"
+ "CREATE INDEX backlink_src ON backlink(srcid, srctype);\n"
+ "CREATE INDEX attachment_idx1 ON attachment(target, filename, mtime);\n"
+ "CREATE INDEX attachment_idx2 ON attachment(src);\n"
+ "CREATE INDEX cherrypick_cid ON cherrypick(childid);\n"
+ "CREATE INDEX ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0;\n"
+ "CREATE INDEX ftsdocName ON ftsdocs(name) WHERE type='w';\n"
+ "CREATE INDEX ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);\n"
+ "CREATE INDEX forumthread ON forumpost(froot,fmtime);\n"
+ "CREATE VIEW artifact(rid,rcvid,size,atype,srcid,hash,content) AS\n"
+ " SELECT blob.rid,rcvid,size,1,srcid,uuid,content\n"
+ " FROM blob LEFT JOIN delta ON (blob.rid=delta.rid);\n"
+ "CREATE VIEW ftscontent AS\n"
+ " SELECT rowid, type, rid, name, idxed, label, url, mtime,\n"
+ " title(type,rid,name) AS 'title', body(type,rid,name) AS 'body'\n"
+ " FROM ftsdocs;\n"
+ );
+ if( sqlite3_compileoption_used("ENABLE_FTS5") ){
+ speedtest1_exec(
+ "CREATE VIRTUAL TABLE ftsidx\n"
+ " USING fts5(content=\"ftscontent\", title, body);\n"
+ "CREATE VIRTUAL TABLE chatfts1 USING fts5(\n"
+ " xmsg, content=chat, content_rowid=msgid,tokenize=porter);\n"
+ );
+ }else{
+ speedtest1_exec(
+ "CREATE TABLE ftsidx_data(id INTEGER PRIMARY KEY, block BLOB);\n"
+ "CREATE TABLE ftsidx_idx(segid, term, pgno, PRIMARY KEY(segid, term))\n"
+ " WITHOUT ROWID;\n"
+ "CREATE TABLE ftsidx_docsize(id INTEGER PRIMARY KEY, sz BLOB);\n"
+ "CREATE TABLE ftsidx_config(k PRIMARY KEY, v) WITHOUT ROWID;\n"
+ "CREATE TABLE chatfts1_data(id INTEGER PRIMARY KEY, block BLOB);\n"
+ "CREATE TABLE chatfts1_idx(segid, term, pgno, PRIMARY KEY(segid, term))\n"
+ " WITHOUT ROWID;\n"
+ "CREATE TABLE chatfts1_docsize(id INTEGER PRIMARY KEY, sz BLOB);\n"
+ "CREATE TABLE chatfts1_config(k PRIMARY KEY, v) WITHOUT ROWID;\n"
+ );
+ }
+ speedtest1_exec(
+ "ANALYZE sqlite_schema;\n"
+ "INSERT INTO sqlite_stat1(tbl,idx,stat) VALUES\n"
+ " ('ftsidx_config','ftsidx_config','1 1'),\n"
+ " ('ftsidx_idx','ftsidx_idx','4215 401 1'),\n"
+ " ('user','sqlite_autoindex_user_1','25 1'),\n"
+ " ('phantom',NULL,'26'),\n"
+ " ('reportfmt','sqlite_autoindex_reportfmt_1','9 1'),\n"
+ " ('rcvfrom','sqlite_autoindex_rcvfrom_1','18445 401'),\n"
+ " ('private',NULL,'99'),\n"
+ " ('mlink','mlink_i4','116678 401'),\n"
+ " ('mlink','mlink_i3','121212 2'),\n"
+ " ('mlink','mlink_i2','106372 401'),\n"
+ " ('mlink','mlink_i1','99298 5'),\n"
+ " ('ftsidx_data',NULL,'3795'),\n"
+ " ('leaf',NULL,'1559'),\n"
+ " ('delta','delta_i1','66340 1'),\n"
+ " ('unversioned','unversioned','3 1'),\n"
+ " ('pending_alert','pending_alert','3 1'),\n"
+ " ('cherrypick','cherrypick_cid','680 2'),\n"
+ " ('cherrypick','cherrypick','628 1 1'),\n"
+ " ('config','config','128 1'),\n"
+ " ('ftsidx_docsize',NULL,'33848'),\n"
+ " ('event','event_i1','36096 1'),\n"
+ " ('plink','plink_i2','38236 1 1'),\n"
+ " ('plink','sqlite_autoindex_plink_1','38357 1 1'),\n"
+ " ('shun','shun','10 1'),\n"
+ " ('concealed','concealed','110 1'),\n"
+ " ('vcache','vcache','1888 401 1'),\n"
+ " ('ftsdocs','ftsdocName','19 1'),\n"
+ " ('ftsdocs','ftsdocIdxed','168 84 1 1'),\n"
+ " ('ftsdocs','sqlite_autoindex_ftsdocs_1','37312 401 1'),\n"
+ " ('subscriber','subscriberUname','5 1'),\n"
+ " ('subscriber','sqlite_autoindex_subscriber_2','37 1'),\n"
+ " ('subscriber','sqlite_autoindex_subscriber_1','37 1'),\n"
+ " ('tag','sqlite_autoindex_tag_1','2990 1'),\n"
+ " ('filename','sqlite_autoindex_filename_1','3168 1'),\n"
+ " ('chat',NULL,'56124'),\n"
+ " ('tagxref','tagxref_i1','40992 401 2'),\n"
+ " ('tagxref','sqlite_autoindex_tagxref_1','79233 3 1'),\n"
+ " ('attachment','attachment_idx2','11 1'),\n"
+ " ('attachment','attachment_idx1','11 2 2 1'),\n"
+ " ('blob','blob_rcvid','128240 201'),\n"
+ " ('blob','sqlite_autoindex_blob_1','126480 1'),\n"
+ " ('synclog','synclog','12 3 1'),\n"
+ " ('backlink','backlink_src','2160 2 2'),\n"
+ " ('backlink','sqlite_autoindex_backlink_1','2340 2 2 1'),\n"
+ " ('accesslog',NULL,'38'),\n"
+ " ('chatfts1_config','chatfts1_config','1 1'),\n"
+ " ('chatfts1_idx','chatfts1_idx','688 230 1'),\n"
+ " ('ticket','sqlite_autoindex_ticket_1','794 1'),\n"
+ " ('ticketchng','ticketchng_idx1','2089 3 1'),\n"
+ " ('forumpost','forumthread','4 4 1'),\n"
+ " ('unclustered',NULL,'12');\n"
+ "COMMIT;"
+ );
+ speedtest1_end_test();
+
+ n = g.szTest*3;
+ speedtest1_begin_test(110, "Open and use the database %d times", n);
+ for(i=0; i=$date OR parent.pid=$pid)\n"
+ " ORDER BY mtime DESC LIMIT 10\n"
+ " )\n"
+ " INSERT OR IGNORE INTO ok SELECT rid FROM ancestor;"
+ );
+ sqlite3_close(dbAux);
+ g.db = dbMain;
+ }
+ speedtest1_end_test();
+}
+
#ifdef SQLITE_ENABLE_RTREE
/* Generate two numbers between 1 and mx. The first number is less than
** the second. Usually the numbers are near each other but can sometimes
@@ -2149,6 +2731,120 @@ void testset_debug1(void){
}
}
+/*
+** Performance tests for JSON.
+*/
+void testset_json(void){
+ unsigned int r = 0x12345678;
+ sqlite3_test_control(SQLITE_TESTCTRL_PRNG_SEED, r, g.db);
+ speedtest1_begin_test(100, "table J1 is %d rows of JSONB",
+ g.szTest*5);
+ speedtest1_exec(
+ "CREATE TABLE j1(x JSONB);\n"
+ "WITH RECURSIVE\n"
+ " jval(n,j) AS (\n"
+ " VALUES(0,'{}'),(1,'[]'),(2,'true'),(3,'false'),(4,'null'),\n"
+ " (5,'{x:1,y:2}'),(6,'0.0'),(7,'3.14159'),(8,'-99.9'),\n"
+ " (9,'[1,2,\"\\n\\u2192\\\"\\u2190\",4]')\n"
+ " ),\n"
+ " c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<26*26-1),\n"
+ " array1(y) AS MATERIALIZED (\n"
+ " SELECT jsonb_group_array(\n"
+ " jsonb_object('x',x,\n"
+ " 'y',jsonb(coalesce(j,random()%%10000)),\n"
+ " 'z',hex(randomblob(50)))\n"
+ " )\n"
+ " FROM c LEFT JOIN jval ON (x%%20)=n\n"
+ " ),\n"
+ " object1(z) AS MATERIALIZED (\n"
+ " SELECT jsonb_group_object(char(0x61+x%%26,0x61+(x/26)%%26),\n"
+ " jsonb( coalesce(j,random()%%10000)))\n"
+ " FROM c LEFT JOIN jval ON (x%%20)=n\n"
+ " ),\n"
+ " c2(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c2 WHERE n<%d)\n"
+ "INSERT INTO j1(x)\n"
+ " SELECT jsonb_object('a',n,'b',n+10000,'c',jsonb(y),'d',jsonb(z),\n"
+ " 'e',n+20000,'f',n+30000)\n"
+ " FROM array1, object1, c2;",
+ g.szTest*5
+ );
+ speedtest1_end_test();
+
+ speedtest1_begin_test(110, "table J2 is %d rows from J1 converted to text", g.szTest);
+ speedtest1_exec(
+ "CREATE TABLE j2(x JSON TEXT);\n"
+ "INSERT INTO j2(x) SELECT json(x) FROM j1 LIMIT %d", g.szTest
+ );
+ speedtest1_end_test();
+
+ speedtest1_begin_test(120, "create indexes on JSON expressions on J1");
+ speedtest1_exec(
+ "BEGIN;\n"
+ "CREATE INDEX j1x1 ON j1(x->>'a');\n"
+ "CREATE INDEX j1x2 ON j1(x->>'b');\n"
+ "CREATE INDEX j1x3 ON j1(x->>'f');\n"
+ "COMMIT;\n"
+ );
+ speedtest1_end_test();
+
+ speedtest1_begin_test(130, "create indexes on JSON expressions on J2");
+ speedtest1_exec(
+ "BEGIN;\n"
+ "CREATE INDEX j2x1 ON j2(x->>'a');\n"
+ "CREATE INDEX j2x2 ON j2(x->>'b');\n"
+ "CREATE INDEX j2x3 ON j2(x->>'f');\n"
+ "COMMIT;\n"
+ );
+ speedtest1_end_test();
+
+ speedtest1_begin_test(140, "queries against J1");
+ speedtest1_exec(
+ "WITH c(n) AS (VALUES(0) UNION ALL SELECT n+1 FROM c WHERE n<7)\n"
+ " SELECT sum(x->>format('$.c[%%d].x',n)) FROM c, j1;\n"
+
+ "WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<5)\n"
+ " SELECT sum(x->>format('$.\"c\"[#-%%d].y',n)) FROM c, j1;\n"
+
+ "SELECT sum(x->>'$.d.ez' + x->>'$.d.\"xz\"' + x->>'a' + x->>'$.c[10].y') FROM j1;\n"
+
+ "SELECT x->>'$.d.tz[2]', x->'$.d.tz' FROM j1;\n"
+ );
+ speedtest1_end_test();
+
+ speedtest1_begin_test(141, "queries involving json_type()");
+ speedtest1_exec(
+ "WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<20)\n"
+ " SELECT json_type(x,format('$.c[#-%%d].y',n)), count(*)\n"
+ " FROM c, j1\n"
+ " WHERE j1.rowid=1\n"
+ " GROUP BY 1 ORDER BY 2;"
+ );
+ speedtest1_end_test();
+
+
+ speedtest1_begin_test(150, "json_insert()/set()/remove() on every row of J1");
+ speedtest1_exec(
+ "BEGIN;\n"
+ "UPDATE j1 SET x=jsonb_insert(x,'$.g',(x->>'f')+1,'$.h',3.14159,'$.i','hello',\n"
+ " '$.j',json('{x:99}'),'$.k','{y:98}');\n"
+ "UPDATE j1 SET x=jsonb_set(x,'$.e',(x->>'f')-1);\n"
+ "UPDATE j1 SET x=jsonb_remove(x,'$.d');\n"
+ "COMMIT;\n"
+ );
+ speedtest1_end_test();
+
+ speedtest1_begin_test(160, "json_insert()/set()/remove() on every row of J2");
+ speedtest1_exec(
+ "BEGIN;\n"
+ "UPDATE j2 SET x=json_insert(x,'$.g',(x->>'f')+1);\n"
+ "UPDATE j2 SET x=json_set(x,'$.e',(x->>'f')-1);\n"
+ "UPDATE j2 SET x=json_remove(x,'$.d');\n"
+ "COMMIT;\n"
+ );
+ speedtest1_end_test();
+
+}
+
/*
** This testset focuses on the speed of parsing numeric literals (integers
** and real numbers). This was added to test the impact of allowing "_"
@@ -2168,25 +2864,25 @@ void testset_parsenumber(void){
const int NROW = 100*g.szTest;
int ii;
- speedtest1_begin_test(100, "parsing small integers");
+ speedtest1_begin_test(100, "parsing %d small integers", NROW);
for(ii=0; iixDelete(pVfs, zDbName, 1);
+ pVfs->xDelete(pVfs, g.zDbName, 1);
}
- unlink(zDbName);
+ unlink(g.zDbName);
}
/* Open the database and the input file */
- if( sqlite3_open_v2(memDb ? ":memory:" : zDbName, &g.db,
- openFlags, zVfs) ){
- fatal_error("Cannot open database file: %s\n", zDbName);
+ if( sqlite3_open_v2(memDb ? ":memory:" : g.zDbName, &g.db,
+ openFlags, g.zVfs) ){
+ fatal_error("Cannot open database file: %s\n", g.zDbName);
}
#if SQLITE_VERSION_NUMBER>=3006001
if( nLook>0 && szLook>0 ){
@@ -2572,8 +3273,10 @@ int main(int argc, char **argv){
}
if( g.bExplain ) printf(".explain\n.echo on\n");
+ if( strcmp(zTSet,"mix1")==0 ) zTSet = zMix1Tests;
do{
char *zThisTest = zTSet;
+ char *zSep;
char *zComma = strchr(zThisTest,',');
if( zComma ){
*zComma = 0;
@@ -2581,7 +3284,20 @@ int main(int argc, char **argv){
}else{
zTSet = "";
}
- if( g.iTotal>0 || zComma!=0 ){
+ zSep = strchr(zThisTest, '/');
+ if( zSep ){
+ int kk;
+ for(kk=1; zSep[kk] && ISDIGIT(zSep[kk]); kk++){}
+ if( kk==1 || zSep[kk]!=0 ){
+ fatal_error("bad modifier on testset name: \"%s\"", zThisTest);
+ }
+ g.szTest = g.szBase*integerValue(zSep+1)/100;
+ if( g.szTest<=0 ) g.szTest = 1;
+ zSep[0] = 0;
+ }else{
+ g.szTest = g.szBase;
+ }
+ if( g.iTotal>0 || zComma==0 ){
printf(" Begin testset \"%s\"\n", zThisTest);
}
if( strcmp(zThisTest,"main")==0 ){
@@ -2592,8 +3308,14 @@ int main(int argc, char **argv){
testset_orm();
}else if( strcmp(zThisTest,"cte")==0 ){
testset_cte();
+ }else if( strcmp(zThisTest,"star")==0 ){
+ testset_star();
+ }else if( strcmp(zThisTest,"app")==0 ){
+ testset_app();
}else if( strcmp(zThisTest,"fp")==0 ){
testset_fp();
+ }else if( strcmp(zThisTest,"json")==0 ){
+ testset_json();
}else if( strcmp(zThisTest,"trigger")==0 ){
testset_trigger();
}else if( strcmp(zThisTest,"parsenumber")==0 ){
diff --git a/test/starschema1.test b/test/starschema1.test
index af8168b510..bb7d8aa79b 100644
--- a/test/starschema1.test
+++ b/test/starschema1.test
@@ -10,7 +10,7 @@
#***********************************************************************
#
# Test cases for the ability of the query planner to cope with
-# star-schema queries on databases with goofy indexes.
+# star-schema queries.
#
set testdir [file dirname $argv0]
diff --git a/test/testrunner_data.tcl b/test/testrunner_data.tcl
index c749481f51..4685dabf5f 100644
--- a/test/testrunner_data.tcl
+++ b/test/testrunner_data.tcl
@@ -98,6 +98,7 @@ namespace eval trd {
set build(All-Debug) {
--with-debug --enable-all
-DSQLITE_ENABLE_ORDERED_SET_AGGREGATES
+ -DSQLITE_ENABLE_NORMALIZE
}
set build(All-O0) {
-O0 --enable-all
@@ -111,6 +112,7 @@ namespace eval trd {
CC=clang -fsanitize=address,undefined -fno-sanitize-recover=undefined
-DSQLITE_ENABLE_STAT4
-DSQLITE_OMIT_LOOKASIDE=1
+ -DSQLITE_ENABLE_NORMALIZE
-DCONFIG_SLOWDOWN_FACTOR=5.0
-DSQLITE_ENABLE_RBU
--with-debug
@@ -169,6 +171,7 @@ namespace eval trd {
-DSQLITE_SOUNDEX=1
-DSQLITE_ENABLE_ATOMIC_WRITE=1
-DSQLITE_ENABLE_MEMORY_MANAGEMENT=1
+ -DSQLITE_ENABLE_NORMALIZE
-DSQLITE_ENABLE_OVERSIZE_CELL_CHECK=1
-DSQLITE_ENABLE_STAT4
-DSQLITE_ENABLE_STMT_SCANSTATUS
@@ -184,6 +187,7 @@ namespace eval trd {
-DSQLITE_ENABLE_FTS3=1
-DSQLITE_ENABLE_RTREE=1
-DSQLITE_ENABLE_MEMSYS5=1
+ -DSQLITE_ENABLE_NORMALIZE
-DSQLITE_ENABLE_COLUMN_METADATA=1
-DSQLITE_ENABLE_STAT4
-DSQLITE_ENABLE_HIDDEN_COLUMNS
@@ -300,6 +304,7 @@ namespace eval trd {
-DSQLITE_ENABLE_FTS3=1
-DSQLITE_ENABLE_FTS3_PARENTHESIS=1
-DSQLITE_ENABLE_FTS3_TOKENIZER=1
+ -DSQLITE_ENABLE_NORMALIZE=1
-DSQLITE_ENABLE_PERSIST_WAL=1
-DSQLITE_ENABLE_PURGEABLE_PCACHE=1
-DSQLITE_ENABLE_RTREE=1
diff --git a/test/trace3.test b/test/trace3.test
index 496cc2360a..639aefafa6 100644
--- a/test/trace3.test
+++ b/test/trace3.test
@@ -342,5 +342,21 @@ do_test 12.1.2 {
sqlite3_finalize $STMT
} {SQLITE_OK}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 13.0 {
+ CREATE TABLE T1(a, b);
+ INSERT INTO t1 VALUES(1, 2), (3, 4);
+}
+
+proc trace_callback {args} {}
+db trace_v2 trace_callback profile
+
+do_test 13.1 {
+ db eval { SELECT * FROM t1 } {
+ db trace_v2 "" ""
+ }
+ set {} {}
+} {}
finish_test
diff --git a/test/walsetlk3.test b/test/walsetlk3.test
index cbd2f7247e..efd5cdf92d 100644
--- a/test/walsetlk3.test
+++ b/test/walsetlk3.test
@@ -18,6 +18,7 @@ source $testdir/lock_common.tcl
set testprefix walsetlk3
ifcapable !wal {finish_test ; return }
+ifcapable !setlk_timeout {finish_test ; return }
do_execsql_test 1.0 {
CREATE TABLE t1(x, y);
diff --git a/test/with6.test b/test/with6.test
index 95e6305474..b95ec0b763 100644
--- a/test/with6.test
+++ b/test/with6.test
@@ -325,6 +325,12 @@ do_eqp_test 331 {
# marked with M10d_Yes and hence prohibited from participating in the
# query flattening optimization.
#
+# Updated 2025-01-02.
+# https://sqlite.org/forum/forumpost/8f38fc9878a92aa9
+#
+# The same optimization that made Grunthos's query fast made
+# Jean-Noël Mayor's query slow. Bummer.
+#
reset_db
db eval {
CREATE TABLE raw(country,date,total,delta, UNIQUE(country,date));
diff --git a/tool/buildtclext.tcl b/tool/buildtclext.tcl
index c74540c9ea..905087d1da 100644
--- a/tool/buildtclext.tcl
+++ b/tool/buildtclext.tcl
@@ -15,6 +15,7 @@ Options:
--info Show info on existing SQLite TCL extension installs
--install-only Install an extension previously build
--uninstall Uninstall the extension
+ --version-check Check extension version against this source tree
--destdir DIR Installation root (used by "make install DESTDIR=...")
Other options are retained and passed through into the compiler.}
@@ -24,6 +25,7 @@ set build 1
set install 1
set uninstall 0
set infoonly 0
+set versioncheck 0
set CC {}
set OPTS {}
set DESTDIR ""; # --destdir "$(DESTDIR)"
@@ -36,11 +38,18 @@ for {set ii 0} {$ii<[llength $argv]} {incr ii} {
} elseif {$a0=="--uninstall"} {
set build 0
set install 0
+ set versioncheck 0
set uninstall 1
} elseif {$a0=="--info"} {
set build 0
set install 0
+ set versioncheck 0
set infoonly 1
+ } elseif {$a0=="--version-check"} {
+ set build 0
+ set install 0
+ set infoonly 0
+ set versioncheck 1
} elseif {$a0=="--cc" && $ii+1<[llength $argv]} {
incr ii
set CC [lindex $argv $ii]
@@ -156,6 +165,33 @@ if {$tcl_platform(platform)=="windows"} {
set CMD [subst $cmd]
}
+# Check the SQLite TCL extension that is loaded by default by this running
+# TCL interpreter to see if it has the same SQLITE_SOURCE_ID as the source
+# code in the directory holding this script.
+#
+if {$versioncheck} {
+ if {[catch {package require sqlite3} msg]} {
+ puts stderr "No SQLite TCL extension available: $msg"
+ exit 1
+ }
+ sqlite3 db :memory:
+ set extvers [db one {SELECT sqlite_source_id()}]
+ db close
+ set fd [open sqlite3.h rb]
+ set sqlite3h [read $fd]
+ close $fd
+ regexp {#define SQLITE_SOURCE_ID +"([^"]+)"} $sqlite3h all srcvers
+ set srcvers [string range $srcvers 0 78]
+ set extvers [string range $extvers 0 78]
+ if {$srcvers==$extvers} {
+ puts "source code and extension versions aligned:\n$extvers"
+ exit 0
+ }
+ puts stderr "source code and extension versions differ"
+ puts stderr "source: $srcvers\nextension: $extvers"
+ exit 1
+}
+
# Show information about prior installs
#
if {$infoonly} {
@@ -253,7 +289,7 @@ if {$build} {
# Tcl package index file, version ???
#
package ifneeded sqlite3 $VERSION \\
- [list load [file join \$dir $OUT] sqlite3]
+ [list load [file join \$dir $OUT] Sqlite3]
}]
close $fd
diff --git a/tool/emcc.sh.in b/tool/emcc.sh.in
index 1263e1b0ea..1264df5376 100644
--- a/tool/emcc.sh.in
+++ b/tool/emcc.sh.in
@@ -63,4 +63,4 @@ if [ x = "x${emcc}" ]; then
fi
fi
-exec emcc "$@"
+exec $emcc "$@"
diff --git a/tool/mkamalzip.tcl b/tool/mkamalzip.tcl
new file mode 100644
index 0000000000..92feb4122e
--- /dev/null
+++ b/tool/mkamalzip.tcl
@@ -0,0 +1,23 @@
+#!/usr/bin/tclsh
+#
+# Build a ZIP archive for the amalgamation source code found in the current
+# directory.
+#
+set VERSION-file [file dirname [file dirname [file normalize $argv0]]]/VERSION
+set fd [open ${VERSION-file} rb]
+set vers [read $fd]
+close $fd
+scan $vers %d.%d.%d major minor patch
+set numvers [format {3%02d%02d00} $minor $patch]
+set dir sqlite-amalgamation-$numvers
+file delete -force $dir
+file mkdir $dir
+set filelist {sqlite3.c sqlite3.h shell.c sqlite3ext.h}
+foreach f $filelist {
+ file copy $f $dir/$f
+}
+set cmd "zip -r $dir.zip $dir"
+puts $cmd
+file delete -force $dir.zip
+exec {*}$cmd
+file delete -force $dir
diff --git a/tool/mkautoconfamal.sh b/tool/mkautoconfamal.sh
index 35dbfb41e0..c26ca47bf1 100644
--- a/tool/mkautoconfamal.sh
+++ b/tool/mkautoconfamal.sh
@@ -13,7 +13,7 @@
#
-# Bail out of the script if any command returns a non-zero exit
+# Bail out of the script if any command returns a non-zero exit
# status. Or if the script tries to use an unset variable. These
# may fail for old /bin/sh interpreters.
#
@@ -22,8 +22,8 @@ set -u
TMPSPACE=./mkpkg_tmp_dir
VERSION=`cat $TOP/VERSION`
-HASH=`sed 's/^\(..........\).*/\1/' $TOP/manifest.uuid`
-DATETIME=`grep '^D' $TOP/manifest | sed -e 's/[^0-9]//g' -e 's/\(............\).*/\1/'`
+HASH=`cut -c1-10 $TOP/manifest.uuid`
+DATETIME=`grep '^D' $TOP/manifest | tr -c -d '[0-9]' | cut -c1-12`
# Verify that the version number in the TEA autoconf file is correct.
# Fail with an error if not.
@@ -34,12 +34,12 @@ else echo "TEA version number mismatch. Should be $VERSION"; exit 1
fi
# If this script is given an argument of --snapshot, then generate a
-# snapshot tarball named for the current checkout SHA1 hash, rather than
+# snapshot tarball named for the current checkout SHA hash, rather than
# the version number.
#
if test "$#" -ge 1 -a x$1 != x--snapshot
then
- # Set global variable $ARTIFACT to the "3xxyyzz" string incorporated
+ # Set global variable $ARTIFACT to the "3xxyyzz" string incorporated
# into artifact filenames. And $VERSION2 to the "3.x.y[.z]" form.
xx=`echo $VERSION|sed 's/3\.\([0-9]*\)\..*/\1/'`
yy=`echo $VERSION|sed 's/3\.[^.]*\.\([0-9]*\).*/\1/'`
@@ -54,6 +54,8 @@ fi
rm -rf $TMPSPACE
cp -R $TOP/autoconf $TMPSPACE
+cp -R $TOP/autosetup $TMPSPACE
+cp -p $TOP/configure $TMPSPACE
cp sqlite3.c $TMPSPACE
cp sqlite3.h $TMPSPACE
cp sqlite3ext.h $TMPSPACE
@@ -63,28 +65,33 @@ cp $TOP/sqlite3.pc.in $TMPSPACE
cp shell.c $TMPSPACE
cp $TOP/src/sqlite3.rc $TMPSPACE
cp $TOP/tool/Replace.cs $TMPSPACE
-
-cat $TMPSPACE/configure.ac |
-sed "s/--SQLITE-VERSION--/$VERSION/" > $TMPSPACE/tmp
-mv $TMPSPACE/tmp $TMPSPACE/configure.ac
+cp $TOP/VERSION $TMPSPACE
+cp $TOP/main.mk $TMPSPACE
cd $TMPSPACE
-autoreconf -i
-#libtoolize
-#aclocal
-#autoconf
-#automake --add-missing
+
+# Clean up emacs-generated backup files from the target
+rm -f ./autosetup/*~
+rm -f ./*~
+
+#if true; then
+ # Clean up *~ files (emacs-generated backups).
+ # This bit is only for use during development of
+ # the autoconf bundle.
+# find . -name '*~' -exec rm \{} \;
+#fi
mkdir -p tea/generic
-echo "#ifdef USE_SYSTEM_SQLITE" > tea/generic/tclsqlite3.c
-echo "# include " >> tea/generic/tclsqlite3.c
-echo "#else" >> tea/generic/tclsqlite3.c
-echo "#include \"sqlite3.c\"" >> tea/generic/tclsqlite3.c
-echo "#endif" >> tea/generic/tclsqlite3.c
+cat < tea/generic/tclsqlite3.c
+#ifdef USE_SYSTEM_SQLITE
+# include
+#else
+# include "sqlite3.c"
+#endif
+EOF
cat $TOP/src/tclsqlite.c >> tea/generic/tclsqlite3.c
-cat tea/configure.ac |
- sed "s/AC_INIT(\[sqlite\], .*)/AC_INIT([sqlite], [$VERSION])/" > tmp
+sed "s/AC_INIT(\[sqlite\], .*)/AC_INIT([sqlite], [$VERSION])/" tea/configure.ac > tmp
mv tmp tea/configure.ac
cd tea
@@ -93,9 +100,9 @@ rm -rf autom4te.cache
cd ../
./configure && make dist
-tar -xzf sqlite-$VERSION.tar.gz
+tar xzf sqlite-$VERSION.tar.gz
mv sqlite-$VERSION $TARBALLNAME
-tar -czf $TARBALLNAME.tar.gz $TARBALLNAME
+tar czf $TARBALLNAME.tar.gz $TARBALLNAME
mv $TARBALLNAME.tar.gz ..
cd ..
ls -l $TARBALLNAME.tar.gz
diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl
index af9804e4fa..85e14f8498 100644
--- a/tool/mkshellc.tcl
+++ b/tool/mkshellc.tcl
@@ -12,6 +12,9 @@
set topdir [file dir [file dir [file normal $argv0]]]
set out stdout
fconfigure stdout -translation binary
+if {[lindex $argv 0]!=""} {
+ set out [open [lindex $argv 0] wb]
+}
puts $out {/* DO NOT EDIT!
** This file is automatically generated by the script in the canonical
** SQLite source tree at tool/mkshellc.tcl. That script combines source
diff --git a/tool/mksqlite3c-noext.tcl b/tool/mksqlite3c-noext.tcl
index 8452072564..1148b1c0d5 100644
--- a/tool/mksqlite3c-noext.tcl
+++ b/tool/mksqlite3c-noext.tcl
@@ -57,7 +57,7 @@ close $in
#
set out [open sqlite3.c w]
# Force the output to use unix line endings, even on Windows.
-fconfigure $out -translation lf
+fconfigure $out -translation binary
set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1]
puts $out [subst \
{/******************************************************************************
diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl
index 1b3958f460..1d0f892363 100644
--- a/tool/mksqlite3c.tcl
+++ b/tool/mksqlite3c.tcl
@@ -88,7 +88,7 @@ set fname sqlite3.c
if {$enable_recover} { set fname sqlite3r.c }
set out [open $fname wb]
# Force the output to use unix line endings, even on Windows.
-fconfigure $out -translation lf
+fconfigure $out -translation binary
set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1]
puts $out [subst \
{/******************************************************************************
@@ -130,7 +130,7 @@ if {[file executable $vsrcprog] && [file readable $srcroot/manifest]} {
} else {
puts $out " with changes in files:\n**"
foreach f [lrange $res 1 end] {
- puts $out "** $f"
+ puts $out "** [string trim $f]"
}
}
} else {
diff --git a/tool/mksqlite3h.tcl b/tool/mksqlite3h.tcl
index c242005a07..b1d5ecdcd3 100644
--- a/tool/mksqlite3h.tcl
+++ b/tool/mksqlite3h.tcl
@@ -24,18 +24,36 @@
# 6) Adds the SQLITE_CALLBACK calling convention macro in front of all
# callback declarations.
#
-# This script outputs to stdout.
+# This script outputs to stdout unless the -o FILENAME option is used.
#
# Example usage:
#
-# tclsh mksqlite3h.tcl ../sqlite >sqlite3.h
+# tclsh mksqlite3h.tcl ../sqlite [OPTIONS]
+# ^^^^^^^^^
+# Root of source tree
+#
+# Where options are:
+#
+# --enable-recover Include the sqlite3recover extension
+# -o FILENAME Write results to FILENAME instead of stdout
+# --useapicall SQLITE_APICALL instead of SQLITE_CDECL
#
+# Default output stream
+set out stdout
# Get the source tree root directory from the command-line
#
set TOP [lindex $argv 0]
+# If the -o FILENAME option is present, use FILENAME for output.
+#
+set x [lsearch $argv -o]
+if {$x>0} {
+ incr x
+ set out [open [lindex $argv $x] wb]
+}
+
# Enable use of SQLITE_APICALL macros at the right points?
#
set useapicall 0
@@ -44,6 +62,7 @@ set useapicall 0
#
set enable_recover 0
+# Process command-line arguments
if {[lsearch -regexp [lrange $argv 1 end] {^-+useapicall}] != -1} {
set useapicall 1
}
@@ -62,7 +81,7 @@ set nVersion [eval format "%d%03d%03d" [split $zVersion .]]
#
set PWD [pwd]
cd $TOP
-set tmpfile tmp-[clock millisec]-[expr {int(rand()*100000000000)}].txt
+set tmpfile $PWD/tmp-[clock millisec]-[expr {int(rand()*100000000000)}].txt
exec $PWD/mksourceid manifest > $tmpfile
set fd [open $tmpfile rb]
set zSourceId [string trim [read $fd]]
@@ -88,7 +107,7 @@ set declpattern5 \
{^ *([a-zA-Z][a-zA-Z_0-9 ]+ \**)(sqlite3rebaser_[_a-zA-Z0-9]+)(\(.*)$}
# Force the output to use unix line endings, even on Windows.
-fconfigure stdout -translation lf
+fconfigure stdout -translation binary
set filelist [subst {
$TOP/src/sqlite.h.in
@@ -118,7 +137,7 @@ set cdecllist {
foreach file $filelist {
set in [open $file rb]
if {![regexp {sqlite\.h\.in} $file]} {
- puts "/******** Begin file [file tail $file] *********/"
+ puts $out "/******** Begin file [file tail $file] *********/"
}
while {![eof $in]} {
@@ -161,11 +180,11 @@ foreach file $filelist {
"(SQLITE_SYSAPI *sqlite3_syscall_ptr)"] $line]
regsub {\(\*} $line {(SQLITE_CALLBACK *} line
}
- puts $line
+ puts $out $line
}
close $in
if {![regexp {sqlite\.h\.in} $file]} {
- puts "/******** End of [file tail $file] *********/"
+ puts $out "/******** End of [file tail $file] *********/"
}
}
-puts "#endif /* SQLITE3_H */"
+puts $out "#endif /* SQLITE3_H */"
diff --git a/tool/mksrczip.tcl b/tool/mksrczip.tcl
new file mode 100644
index 0000000000..4431c3d666
--- /dev/null
+++ b/tool/mksrczip.tcl
@@ -0,0 +1,14 @@
+#!/usr/bin/tclsh
+#
+# Build a ZIP archive for the complete, unedited source code that
+# corresponds to the current check-out.
+#
+set VERSION-file [file dirname [file dirname [file normalize $argv0]]]/VERSION
+set fd [open ${VERSION-file} rb]
+set vers [read $fd]
+close $fd
+scan $vers %d.%d.%d major minor patch
+set numvers [format {3%02d%02d00} $minor $patch]
+set cmd "fossil zip current sqlite-src-$numvers.zip --name sqlite-src-$numvers"
+puts $cmd
+exec {*}$cmd
diff --git a/tool/omittest.tcl b/tool/omittest.tcl
index e9033c0bdd..0452a4c6f6 100644
--- a/tool/omittest.tcl
+++ b/tool/omittest.tcl
@@ -200,8 +200,8 @@ foreach sym $CompileOptionsToTest {
} else {
set opts OPT_FEATURE_FLAGS=-D$sym
}
- puts "make tidy sqlite3.lo $opts"
- if {[catch {exec make tidy sqlite3.lo $opts >& $logfile}]} {
+ puts "make tidy sqlite3.o $opts"
+ if {[catch {exec make tidy sqlite3.o $opts >& $logfile}]} {
puts "BUILD FAILED: see $logfile for details"
if {[info exists FailIsOk($sym)]} {
set Failure($sym) 1
diff --git a/tool/split-sqlite3c.tcl b/tool/split-sqlite3c.tcl
index 0308431dab..de4db55a1b 100644
--- a/tool/split-sqlite3c.tcl
+++ b/tool/split-sqlite3c.tcl
@@ -15,7 +15,7 @@ set END {^/\*+ End of %s \*+/}
set in [open sqlite3.c]
set out1 [open sqlite3-all.c w]
-fconfigure $out1 -translation lf
+fconfigure $out1 -translation binary
# Copy the header from sqlite3.c into sqlite3-all.c
#
diff --git a/tool/stripccomments.c b/tool/stripccomments.c
index 53933c0138..1bdb5c6b82 100644
--- a/tool/stripccomments.c
+++ b/tool/stripccomments.c
@@ -111,7 +111,24 @@ void do_it_all(void){
}
else if(slash == ch){
/* MARKER(("state 0 ==> 1 @ %d:%d\n", line, col)); */
- state = S_SLASH1;
+ if( '\\'==prev ){
+ /**
+ JS regexes may contain slash-asterisks, as happened at:
+
+ https://github.com/emscripten-core/emscripten/issues/23412
+
+ Such regexes will always necessarily be preceeded by a
+ backslash, though.
+
+ It is hypothetically possible for a legitimate comment
+ slash-asterisk to appear immediately before a
+ backslash, but that seems like an even rarer corner
+ case than the JS regex case.
+ */
+ fputc(ch, out);
+ }else{
+ state = S_SLASH1;
+ }
break;
}
fputc(ch, out);