while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q,0);
const char *zFid = db_column_text(&q,1);
Index: src/info.c
==================================================================
--- src/info.c
+++ src/info.c
@@ -764,10 +764,13 @@
@
@ %z(href("%R/tree?ci=%!S",zUuid))files
@ | %z(href("%R/fileage?name=%!S",zUuid))file ages
@ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders
@ | %z(href("%R/artifact/%!S",zUuid))manifest
+ if( g.perm.Admin ){
+ @ | %z(href("%R/mlink?ci=%!S",zUuid))mlink table
+ }
if( g.anon.Write ){
@ | %z(href("%R/ci_edit?r=%!S",zUuid))edit
}
@ |
@
@@ -1572,21 +1575,24 @@
** annotation between those version. */
db_prepare(&q,
"SELECT (SELECT substr(uuid,1,20) FROM blob WHERE rid=a.mid),"
" (SELECT substr(uuid,1,20) FROM blob WHERE rid=b.mid),"
" (SELECT name FROM filename WHERE filename.fnid=a.fnid)"
- " FROM mlink a, mlink b"
+ " FROM mlink a, event ea, mlink b, event eb"
" WHERE a.fid=%d"
" AND b.fid=%d"
" AND a.fnid=b.fnid"
" AND a.fid!=a.pid"
- " AND b.fid!=b.pid",
+ " AND b.fid!=b.pid"
+ " AND ea.objid=a.mid"
+ " AND eb.objid=b.mid"
+ " ORDER BY ea.mtime ASC, eb.mtime ASC",
v1, v2
);
if( db_step(&q)==SQLITE_ROW ){
- const char *zOrig = db_column_text(&q, 0);
- const char *zCkin = db_column_text(&q, 1);
+ const char *zCkin = db_column_text(&q, 0);
+ const char *zOrig = db_column_text(&q, 1);
const char *zFN = db_column_text(&q, 2);
style_submenu_element("Annotate",
"%R/annotate?origin=%s&checkin=%s&filename=%T",
zOrig, zCkin, zFN);
}
Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -735,11 +735,11 @@
/*
** The TH1 return codes from the hook will be handled as follows:
**
** TH_OK: The xFunc() and the TH1 notification will both be executed.
**
- ** TH_ERROR: The xFunc() will be executed, the TH1 notification will be
+ ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be
** skipped. If the xFunc() is being hooked, the error message
** will be emitted.
**
** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
**
@@ -949,12 +949,12 @@
fusefs_lib_version());
#endif
#if defined(FOSSIL_DEBUG)
blob_append(pOut, "FOSSIL_DEBUG\n", -1);
#endif
-#if defined(FOSSIL_OMIT_DELTA_CKSUM_TEST)
- blob_append(pOut, "FOSSIL_OMIT_DELTA_CKSUM_TEST\n", -1);
+#if defined(FOSSIL_ENABLE_DELTA_CKSUM_TEST)
+ blob_append(pOut, "FOSSIL_ENABLE_DELTA_CKSUM_TEST\n", -1);
#endif
#if defined(FOSSIL_ENABLE_LEGACY_MV_RM)
blob_append(pOut, "FOSSIL_ENABLE_LEGACY_MV_RM\n", -1);
#endif
#if defined(FOSSIL_ENABLE_EXEC_REL_PATHS)
@@ -1291,10 +1291,13 @@
if( sqlite3_strglob("*.fossil", zName)!=0 ){
/* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands
** do not work for repositories whose names do not end in ".fossil".
** So do not hyperlink those cases. */
@ %h(zName)
+ } else if( sqlite3_strglob("*/.*", zName)==0 ){
+ /* Do not show hidden repos */
+ @ %h(zName) (hidden)
} else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){
@ /%h(zName)
}else{
@ %h(zName)
}
@@ -1664,11 +1667,11 @@
/*
** The TH1 return codes from the hook will be handled as follows:
**
** TH_OK: The xFunc() and the TH1 notification will both be executed.
**
- ** TH_ERROR: The xFunc() will be executed, the TH1 notification will be
+ ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be
** skipped. If the xFunc() is being hooked, the error message
** will be emitted.
**
** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
**
Index: src/path.c
==================================================================
--- src/path.c
+++ src/path.c
@@ -203,30 +203,30 @@
void path_shortest_stored_in_ancestor_table(
int origid, /* RID for check-in at start of the path */
int cid /* RID for check-in at the end of the path */
){
PathNode *pPath;
- Blob sql;
int gen = 0;
- char *zSep = "VALUES";
+ Stmt ins;
pPath = path_shortest(cid, origid, 1, 0);
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS ancestor("
" rid INT UNIQUE,"
" generation INTEGER PRIMARY KEY"
");"
"DELETE FROM ancestor;"
);
- blob_init(&sql, "INSERT INTO ancestor(rid, generation)", -1);
+ db_prepare(&ins, "INSERT INTO ancestor(rid, generation) VALUES(:rid,:gen)");
while( pPath ){
- blob_append_sql(&sql, "%s(%d,%d)", zSep/*safe-for-%s*/, pPath->rid,++gen);
- zSep = ",";
+ db_bind_int(&ins, ":rid", pPath->rid);
+ db_bind_int(&ins, ":gen", ++gen);
+ db_step(&ins);
+ db_reset(&ins);
pPath = pPath->u.pTo;
}
+ db_finalize(&ins);
path_reset();
- db_multi_exec("%s", blob_sql_text(&sql));
- blob_reset(&sql);
}
/*
** COMMAND: test-shortest-path
**
Index: src/setup.c
==================================================================
--- src/setup.c
+++ src/setup.c
@@ -1223,42 +1223,39 @@
@
onoff_attribute(
"Enable hyperlinks for \"nobody\" based on User-Agent and Javascript",
"auto-hyperlink", "autohyperlink", 1, 0);
- @ Enable hyperlinks (the equivalent of the "h" permission) for all users
- @ including user "nobody", as long as (1) the User-Agent string in the
+ @
Enable hyperlinks (the equivalent of the "h" permission) for all users,
+ @ including user "nobody", as long as
+ @
- the User-Agent string in the
@ HTTP header indicates that the request is coming from an actual human
- @ being and not a robot or spider and (2) the user agent is able to
- @ run Javascript in order to set the href= attribute of hyperlinks. Bots
- @ and spiders can forge a User-Agent string that makes them seem to be a
- @ normal browser and they can run javascript just like browsers. But most
- @ bots do not go to that much trouble so this is normally an effective
- @ defense.
- @
- @
You do not normally want a bot to walk your entire repository because
+ @ being, and
+ @
- the user agent is able to
+ @ run Javascript in order to set the href= attribute of hyperlinks, and
+ @
- mouse movement is detected (optional - see the checkbox below), and
+ @
- a number of milliseconds have passed since the page loaded.
+ @
+ @ This setting is designed to give easy access to humans while
+ @ keeping out robots and spiders.
+ @ You do not normally want a robot to walk your entire repository because
@ if it does, your server will end up computing diffs and annotations for
@ every historical version of every file and creating ZIPs and tarballs of
@ every historical check-in, which can use a lot of CPU and bandwidth
@ even for relatively small projects.
@
@ Additional parameters that control this behavior:
@
- onoff_attribute("Enable hyperlinks for humans as deduced from the UserAgent "
- "string", "auto-hyperlink-ishuman", "ahis", 0, 0);
- @
onoff_attribute("Require mouse movement before enabling hyperlinks",
"auto-hyperlink-mouseover", "ahmo", 0, 0);
@
entry_attribute("Delay in milliseconds before enabling hyperlinks", 5,
- "auto-hyperlink-delay", "ah-delay", "10", 0);
+ "auto-hyperlink-delay", "ah-delay", "50", 0);
@
- @ Hyperlinks for user "nobody" are normally enabled as soon as the page
- @ finishes loading. But the first check-box below can be set to require mouse
- @ movement before enabling the links. One can also set a delay prior to enabling
- @ links by enter a positive number of milliseconds in the entry box above.
- @ (Properties: "auto-hyperlink", "auto-hyperlink-ishuman",
+ @ For maximum robot defense, the "require mouse movement" should
+ @ be turned on and the "Delay" should be at least 50 milliseconds.
+ @ (Properties: "auto-hyperlink",
@ "auto-hyperlink-mouseover", and "auto-hyperlink-delay")
@
onoff_attribute("Require a CAPTCHA if not logged in",
"require-captcha", "reqcapt", 1, 0);
@@ -1653,17 +1650,27 @@
"project-description", "pd", "", 0);
@ Describe your project. This will be used in page headers for search
@ engines as well as a short RSS description.
@ (Property: "project-description")
@
- entry_attribute("Tarball and ZIP-archive Prefix", 20, "short-project-name", "spn", "", 0);
+ entry_attribute("Tarball and ZIP-archive Prefix", 20, "short-project-name",
+ "spn", "", 0);
@ This is used as a prefix on the names of generated tarballs and ZIP archive.
@ For best results, keep this prefix brief and avoid special characters such
@ as "/" and "\".
@ If no tarball prefix is specified, then the full Project Name above is used.
@ (Property: "short-project-name")
@
+ @
+ entry_attribute("Download Tag", 20, "download-tag", "dlt", "trunk", 0);
+ @ The /download page is designed to provide
+ @ a convenient place for newbies
+ @ to download a ZIP archive or a tarball of the project. By default, the latest
+ @ trunk check-in is downloaded. Change this tag to something else (ex: release)
+ @ to alter the behavior of the /download page.
+ @ (Property: "download-tag")
+ @
@
onoff_attribute("Enable WYSIWYG Wiki Editing",
"wysiwyg-wiki", "wysiwyg-wiki", 0, 0);
@ Enable what-you-see-is-what-you-get (WYSIWYG) editing of wiki pages.
@ The WYSIWYG editor generates HTML instead of markup, which makes
Index: src/sqlite3.c
==================================================================
--- src/sqlite3.c
+++ src/sqlite3.c
@@ -1147,11 +1147,11 @@
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.21.0"
#define SQLITE_VERSION_NUMBER 3021000
-#define SQLITE_SOURCE_ID "2017-09-21 20:43:48 5d03c738e93d36815248991d9ed3d62297ba1bb966e602e7874410076c144f43"
+#define SQLITE_SOURCE_ID "2017-10-02 02:52:54 c9104b59c7ed360291f7f6fc8caae938e9840c77620d598e4096f78183bf807a"
/*
** CAPI3REF: Run-Time Library Version Numbers
** KEYWORDS: sqlite3_version sqlite3_sourceid
**
@@ -15279,20 +15279,19 @@
*/
#define SQLITE_QueryFlattener 0x0001 /* Query flattening */
#define SQLITE_ColumnCache 0x0002 /* Column cache */
#define SQLITE_GroupByOrder 0x0004 /* GROUPBY cover of ORDERBY */
#define SQLITE_FactorOutConst 0x0008 /* Constant factoring */
-/* not used 0x0010 // Was: SQLITE_IdxRealAsInt */
-#define SQLITE_DistinctOpt 0x0020 /* DISTINCT using indexes */
-#define SQLITE_CoverIdxScan 0x0040 /* Covering index scans */
-#define SQLITE_OrderByIdxJoin 0x0080 /* ORDER BY of joins via index */
-#define SQLITE_SubqCoroutine 0x0100 /* Evaluate subqueries as coroutines */
-#define SQLITE_Transitive 0x0200 /* Transitive constraints */
-#define SQLITE_OmitNoopJoin 0x0400 /* Omit unused tables in joins */
+#define SQLITE_DistinctOpt 0x0010 /* DISTINCT using indexes */
+#define SQLITE_CoverIdxScan 0x0020 /* Covering index scans */
+#define SQLITE_OrderByIdxJoin 0x0040 /* ORDER BY of joins via index */
+#define SQLITE_Transitive 0x0080 /* Transitive constraints */
+#define SQLITE_OmitNoopJoin 0x0100 /* Omit unused tables in joins */
+#define SQLITE_CountOfView 0x0200 /* The count-of-view optimization */
+#define SQLITE_CursorHints 0x0400 /* Add OP_CursorHint opcodes */
#define SQLITE_Stat34 0x0800 /* Use STAT3 or STAT4 data */
-#define SQLITE_CountOfView 0x1000 /* The count-of-view optimization */
-#define SQLITE_CursorHints 0x2000 /* Add OP_CursorHint opcodes */
+ /* TH3 expects the Stat34 ^^^^^^ value to be 0x0800. Don't change it */
#define SQLITE_AllOpts 0xffff /* All optimizations */
/*
** Macros for testing whether or not optimizations are enabled or disabled.
*/
@@ -17777,10 +17776,12 @@
SQLITE_PRIVATE const char *sqlite3ErrStr(int);
SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse);
SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int);
SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName);
SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr);
+SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr);
+SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,Expr*,Expr*);
SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int);
SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*);
SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*);
SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *);
SQLITE_PRIVATE int sqlite3CheckObjectName(Parse *, const char *);
@@ -43282,18 +43283,23 @@
dwFlagsAndAttributes & FILE_ATTRIBUTE_MASK;
extendedParameters.dwFileFlags = dwFlagsAndAttributes & FILE_FLAG_MASK;
extendedParameters.dwSecurityQosFlags = SECURITY_ANONYMOUS;
extendedParameters.lpSecurityAttributes = NULL;
extendedParameters.hTemplateFile = NULL;
- while( (h = osCreateFile2((LPCWSTR)zConverted,
- dwDesiredAccess,
- dwShareMode,
- dwCreationDisposition,
- &extendedParameters))==INVALID_HANDLE_VALUE &&
- winRetryIoerr(&cnt, &lastErrno) ){
- /* Noop */
- }
+ do{
+ h = osCreateFile2((LPCWSTR)zConverted,
+ dwDesiredAccess,
+ dwShareMode,
+ dwCreationDisposition,
+ &extendedParameters);
+ if( h!=INVALID_HANDLE_VALUE ) break;
+ if( isReadWrite ){
+ int isRO = 0;
+ int rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO);
+ if( rc2==SQLITE_OK && isRO ) break;
+ }
+ }while( winRetryIoerr(&cnt, &lastErrno) );
#else
do{
h = osCreateFileW((LPCWSTR)zConverted,
dwDesiredAccess,
dwShareMode, NULL,
@@ -43309,19 +43315,24 @@
}while( winRetryIoerr(&cnt, &lastErrno) );
#endif
}
#ifdef SQLITE_WIN32_HAS_ANSI
else{
- while( (h = osCreateFileA((LPCSTR)zConverted,
- dwDesiredAccess,
- dwShareMode, NULL,
- dwCreationDisposition,
- dwFlagsAndAttributes,
- NULL))==INVALID_HANDLE_VALUE &&
- winRetryIoerr(&cnt, &lastErrno) ){
- /* Noop */
- }
+ do{
+ h = osCreateFileA((LPCSTR)zConverted,
+ dwDesiredAccess,
+ dwShareMode, NULL,
+ dwCreationDisposition,
+ dwFlagsAndAttributes,
+ NULL);
+ if( h!=INVALID_HANDLE_VALUE ) break;
+ if( isReadWrite ){
+ int isRO = 0;
+ int rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO);
+ if( rc2==SQLITE_OK && isRO ) break;
+ }
+ }while( winRetryIoerr(&cnt, &lastErrno) );
}
#endif
winLogIoerr(cnt, __LINE__);
OSTRACE(("OPEN file=%p, name=%s, access=%lx, rc=%s\n", h, zUtf8Name,
@@ -61031,10 +61042,13 @@
u8 *pEnd = &data[cellOffset + nCell*2];
u8 *pAddr;
int sz2 = 0;
int sz = get2byte(&data[iFree+2]);
int top = get2byte(&data[hdr+5]);
+ if( top>=iFree ){
+ return SQLITE_CORRUPT_PGNO(pPage->pgno);
+ }
if( iFree2 ){
assert( iFree+sz<=iFree2 ); /* Verified by pageFindSlot() */
sz2 = get2byte(&data[iFree2+2]);
assert( iFree+sz+sz2+iFree2-(iFree+sz) <= usableSize );
memmove(&data[iFree+sz+sz2], &data[iFree+sz], iFree2-(iFree+sz));
@@ -71383,13 +71397,14 @@
u32 amt, /* Number of bytes to return. */
Mem *pMem /* OUT: Return data in this Mem structure. */
){
int rc;
pMem->flags = MEM_Null;
- if( SQLITE_OK==(rc = sqlite3VdbeMemClearAndResize(pMem, amt)) ){
+ if( SQLITE_OK==(rc = sqlite3VdbeMemClearAndResize(pMem, amt+1)) ){
rc = sqlite3BtreePayload(pCur, offset, amt, pMem->z);
if( rc==SQLITE_OK ){
+ pMem->z[amt] = 0; /* Overrun area used when reading malformed records */
pMem->flags = MEM_Blob;
pMem->n = (int)amt;
}else{
sqlite3VdbeMemRelease(pMem);
}
@@ -91882,10 +91897,15 @@
/*
** Return the collation sequence for the expression pExpr. If
** there is no defined collating sequence, return NULL.
**
+** See also: sqlite3ExprNNCollSeq()
+**
+** The sqlite3ExprNNCollSeq() works the same exact that it returns the
+** default collation if pExpr has no defined collation.
+**
** The collating sequence might be determined by a COLLATE operator
** or by the presence of a column with a defined collating sequence.
** COLLATE operators take first precedence. Left operands take
** precedence over right operands.
*/
@@ -91945,10 +91965,36 @@
if( sqlite3CheckCollSeq(pParse, pColl) ){
pColl = 0;
}
return pColl;
}
+
+/*
+** Return the collation sequence for the expression pExpr. If
+** there is no defined collating sequence, return a pointer to the
+** defautl collation sequence.
+**
+** See also: sqlite3ExprCollSeq()
+**
+** The sqlite3ExprCollSeq() routine works the same except that it
+** returns NULL if there is no defined collation.
+*/
+SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr){
+ CollSeq *p = sqlite3ExprCollSeq(pParse, pExpr);
+ if( p==0 ) p = pParse->db->pDfltColl;
+ assert( p!=0 );
+ return p;
+}
+
+/*
+** Return TRUE if the two expressions have equivalent collating sequences.
+*/
+SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse *pParse, Expr *pE1, Expr *pE2){
+ CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pE1);
+ CollSeq *pColl2 = sqlite3ExprNNCollSeq(pParse, pE2);
+ return sqlite3StrICmp(pColl1->zName, pColl2->zName)==0;
+}
/*
** pExpr is an operand of a comparison operator. aff2 is the
** type affinity of the other operand. This routine returns the
** type affinity that should be used for the comparison operator.
@@ -93439,16 +93485,15 @@
** ExprList.
*/
SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList *pList){
int i;
u32 m = 0;
- if( pList ){
- for(i=0; inExpr; i++){
- Expr *pExpr = pList->a[i].pExpr;
- assert( pExpr!=0 );
- m |= pExpr->flags;
- }
+ assert( pList!=0 );
+ for(i=0; inExpr; i++){
+ Expr *pExpr = pList->a[i].pExpr;
+ assert( pExpr!=0 );
+ m |= pExpr->flags;
}
return m;
}
/*
@@ -93601,12 +93646,12 @@
/* Check if pExpr is identical to any GROUP BY term. If so, consider
** it constant. */
for(i=0; inExpr; i++){
Expr *p = pGroupBy->a[i].pExpr;
if( sqlite3ExprCompare(0, pExpr, p, -1)<2 ){
- CollSeq *pColl = sqlite3ExprCollSeq(pWalker->pParse, p);
- if( pColl==0 || sqlite3_stricmp("BINARY", pColl->zName)==0 ){
+ CollSeq *pColl = sqlite3ExprNNCollSeq(pWalker->pParse, p);
+ if( sqlite3_stricmp("BINARY", pColl->zName)==0 ){
return WRC_Prune;
}
}
}
@@ -117746,11 +117791,12 @@
if( pNew==0 ){
assert( pParse->db->mallocFailed );
pNew = &standin;
}
if( pEList==0 ){
- pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(pParse->db,TK_ASTERISK,0));
+ pEList = sqlite3ExprListAppend(pParse, 0,
+ sqlite3Expr(pParse->db,TK_ASTERISK,0));
}
pNew->pEList = pEList;
pNew->op = TK_SELECT;
pNew->selFlags = selFlags;
pNew->iLimit = 0;
@@ -117770,11 +117816,12 @@
pNew->pPrior = 0;
pNew->pNext = 0;
pNew->pLimit = pLimit;
pNew->pOffset = pOffset;
pNew->pWith = 0;
- assert( pOffset==0 || pLimit!=0 || pParse->nErr>0 || pParse->db->mallocFailed!=0 );
+ assert( pOffset==0 || pLimit!=0 || pParse->nErr>0
+ || pParse->db->mallocFailed!=0 );
if( pParse->db->mallocFailed ) {
clearSelect(pParse->db, pNew, pNew!=&standin);
pNew = 0;
}else{
assert( pNew->pSrc!=0 || pParse->nErr>0 );
@@ -118387,11 +118434,12 @@
}
regOrig = 0;
assert( eDest==SRT_Set || eDest==SRT_Mem
|| eDest==SRT_Coroutine || eDest==SRT_Output );
}
- nResultCol = sqlite3ExprCodeExprList(pParse,p->pEList,regResult,0,ecelFlags);
+ nResultCol = sqlite3ExprCodeExprList(pParse,p->pEList,regResult,
+ 0,ecelFlags);
}
/* If the DISTINCT keyword was present on the SELECT statement
** and this row has been seen before, then do not make this row
** part of the result.
@@ -118737,14 +118785,11 @@
nExpr = pList->nExpr;
pInfo = sqlite3KeyInfoAlloc(db, nExpr-iStart, nExtra+1);
if( pInfo ){
assert( sqlite3KeyInfoIsWriteable(pInfo) );
for(i=iStart, pItem=pList->a+iStart; ipExpr);
- if( !pColl ) pColl = db->pDfltColl;
- pInfo->aColl[i-iStart] = pColl;
+ pInfo->aColl[i-iStart] = sqlite3ExprNNCollSeq(pParse, pItem->pExpr);
pInfo->aSortOrder[i-iStart] = pItem->sortOrder;
}
}
return pInfo;
}
@@ -119201,13 +119246,13 @@
** short=OFF, full=OFF: Column name is the text of the expression has it
** originally appears in the SELECT statement. In
** other words, the zSpan of the result expression.
**
** short=ON, full=OFF: (This is the default setting). If the result
-** refers directly to a table column, then the result
-** column name is just the table column name: COLUMN.
-** Otherwise use zSpan.
+** refers directly to a table column, then the
+** result column name is just the table column
+** name: COLUMN. Otherwise use zSpan.
**
** full=ON, short=ANY: If the result refers directly to a table column,
** then the result column name with the table name
** prefix, ex: TABLE.COLUMN. Otherwise use zSpan.
*/
@@ -119245,11 +119290,11 @@
for(i=0; inExpr; i++){
Expr *p = pEList->a[i].pExpr;
assert( p!=0 );
assert( p->op!=TK_AGG_COLUMN ); /* Agg processing has not run yet */
- assert( p->op!=TK_COLUMN || p->pTab!=0 ); /* Covering indexes not yet coded */
+ assert( p->op!=TK_COLUMN || p->pTab!=0 ); /* Covering idx not yet coded */
if( pEList->a[i].zName ){
/* An AS clause always takes first priority */
char *zName = pEList->a[i].zName;
sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_TRANSIENT);
}else if( srcName && p->op==TK_COLUMN ){
@@ -120809,11 +120854,13 @@
static Expr *substExpr(
SubstContext *pSubst, /* Description of the substitution */
Expr *pExpr /* Expr in which substitution occurs */
){
if( pExpr==0 ) return 0;
- if( ExprHasProperty(pExpr, EP_FromJoin) && pExpr->iRightJoinTable==pSubst->iTable ){
+ if( ExprHasProperty(pExpr, EP_FromJoin)
+ && pExpr->iRightJoinTable==pSubst->iTable
+ ){
pExpr->iRightJoinTable = pSubst->iNewTable;
}
if( pExpr->op==TK_COLUMN && pExpr->iTable==pSubst->iTable ){
if( pExpr->iColumn<0 ){
pExpr->op = TK_NULL;
@@ -120922,72 +120969,78 @@
** The code generated for this simplification gives the same result
** but only has to scan the data once. And because indices might
** exist on the table t1, a complete scan of the data might be
** avoided.
**
-** Flattening is only attempted if all of the following are true:
-**
-** (1) The subquery and the outer query do not both use aggregates.
-**
-** (2) The subquery is not an aggregate or (2a) the outer query is not a join
-** and (2b) the outer query does not use subqueries other than the one
-** FROM-clause subquery that is a candidate for flattening. (2b is
-** due to ticket [2f7170d73bf9abf80] from 2015-02-09.)
-**
-** (3) The subquery is not the right operand of a LEFT JOIN
-** or (a) the subquery is not itself a join and (b) the FROM clause
-** of the subquery does not contain a virtual table and (c) the
-** outer query is not an aggregate.
-**
-** (4) The subquery is not DISTINCT.
+** Flattening is subject to the following constraints:
+**
+** (**) We no longer attempt to flatten aggregate subqueries. Was:
+** The subquery and the outer query cannot both be aggregates.
+**
+** (**) We no longer attempt to flatten aggregate subqueries. Was:
+** (2) If the subquery is an aggregate then
+** (2a) the outer query must not be a join and
+** (2b) the outer query must not use subqueries
+** other than the one FROM-clause subquery that is a candidate
+** for flattening. (This is due to ticket [2f7170d73bf9abf80]
+** from 2015-02-09.)
+**
+** (3) If the subquery is the right operand of a LEFT JOIN then
+** (3a) the subquery may not be a join and
+** (3b) the FROM clause of the subquery may not contain a virtual
+** table and
+** (3c) the outer query may not be an aggregate.
+**
+** (4) The subquery can not be DISTINCT.
**
** (**) At one point restrictions (4) and (5) defined a subset of DISTINCT
** sub-queries that were excluded from this optimization. Restriction
** (4) has since been expanded to exclude all DISTINCT subqueries.
**
-** (6) The subquery does not use aggregates or the outer query is not
-** DISTINCT.
+** (**) We no longer attempt to flatten aggregate subqueries. Was:
+** If the subquery is aggregate, the outer query may not be DISTINCT.
**
-** (7) The subquery has a FROM clause. TODO: For subqueries without
+** (7) The subquery must have a FROM clause. TODO: For subqueries without
** A FROM clause, consider adding a FROM clause with the special
** table sqlite_once that consists of a single row containing a
** single NULL.
**
-** (8) The subquery does not use LIMIT or the outer query is not a join.
+** (8) If the subquery uses LIMIT then the outer query may not be a join.
**
-** (9) The subquery does not use LIMIT or the outer query does not use
-** aggregates.
+** (9) If the subquery uses LIMIT then the outer query may not be aggregate.
**
** (**) Restriction (10) was removed from the code on 2005-02-05 but we
** accidently carried the comment forward until 2014-09-15. Original
-** text: "The subquery does not use aggregates or the outer query
-** does not use LIMIT."
+** constraint: "If the subquery is aggregate then the outer query
+** may not use LIMIT."
**
-** (11) The subquery and the outer query do not both have ORDER BY clauses.
+** (11) The subquery and the outer query may not both have ORDER BY clauses.
**
** (**) Not implemented. Subsumed into restriction (3). Was previously
** a separate restriction deriving from ticket #350.
**
-** (13) The subquery and outer query do not both use LIMIT.
+** (13) The subquery and outer query may not both use LIMIT.
**
-** (14) The subquery does not use OFFSET.
+** (14) The subquery may not use OFFSET.
**
-** (15) The outer query is not part of a compound select or the
-** subquery does not have a LIMIT clause.
+** (15) If the outer query is part of a compound select, then the
+** subquery may not use LIMIT.
** (See ticket #2339 and ticket [02a8e81d44]).
**
-** (16) The outer query is not an aggregate or the subquery does
-** not contain ORDER BY. (Ticket #2942) This used to not matter
+** (16) If the outer query is aggregate, then the subquery may not
+** use ORDER BY. (Ticket #2942) This used to not matter
** until we introduced the group_concat() function.
**
-** (17) The sub-query is not a compound select, or it is a UNION ALL
-** compound clause made up entirely of non-aggregate queries, and
-** the parent query:
-**
-** * is not itself part of a compound select,
-** * is not an aggregate or DISTINCT query, and
-** * is not a join
+** (17) If the subquery is a compound select, then
+** (17a) all compound operators must be a UNION ALL, and
+** (17b) no terms within the subquery compound may be aggregate
+** or DISTINT, and
+** (17c) every term within the subquery compound must have a FROM clause
+** (17d) the outer query may not be
+** (17d1) aggregate, or
+** (17d2) DISTINCT, or
+** (17d3) a join.
**
** The parent and sub-query may contain WHERE clauses. Subject to
** rules (11), (13) and (14), they may also contain ORDER BY,
** LIMIT and OFFSET clauses. The subquery cannot use any compound
** operator other than UNION ALL because all the other compound
@@ -120999,41 +121052,42 @@
** SELECT statement, but all the code here does is make sure that no
** such (illegal) sub-query is flattened. The caller will detect the
** syntax error and return a detailed message.
**
** (18) If the sub-query is a compound select, then all terms of the
-** ORDER by clause of the parent must be simple references to
+** ORDER BY clause of the parent must be simple references to
** columns of the sub-query.
**
-** (19) The subquery does not use LIMIT or the outer query does not
+** (19) If the subquery uses LIMIT then the outer query may not
** have a WHERE clause.
**
** (20) If the sub-query is a compound select, then it must not use
** an ORDER BY clause. Ticket #3773. We could relax this constraint
** somewhat by saying that the terms of the ORDER BY clause must
** appear as unmodified result columns in the outer query. But we
** have other optimizations in mind to deal with that case.
**
-** (21) The subquery does not use LIMIT or the outer query is not
+** (21) If the subquery uses LIMIT then the outer query may not be
** DISTINCT. (See ticket [752e1646fc]).
**
-** (22) The subquery is not a recursive CTE.
+** (22) The subquery may not be a recursive CTE.
**
-** (23) The parent is not a recursive CTE, or the sub-query is not a
-** compound query. This restriction is because transforming the
+** (23) If the outer query is a recursive CTE, then the sub-query may not be
+** a compound query. This restriction is because transforming the
** parent to a compound query confuses the code that handles
** recursive queries in multiSelect().
**
-** (24) The subquery is not an aggregate that uses the built-in min() or
+** (**) We no longer attempt to flatten aggregate subqueries. Was:
+** The subquery may not be an aggregate that uses the built-in min() or
** or max() functions. (Without this restriction, a query like:
** "SELECT x FROM (SELECT max(y), x FROM t1)" would not necessarily
** return the value X for which Y was maximal.)
**
**
** In this routine, the "p" parameter is a pointer to the outer query.
** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query
-** uses aggregates and subqueryIsAgg is true if the subquery uses aggregates.
+** uses aggregates.
**
** If flattening is not attempted, this routine is a no-op and returns 0.
** If flattening is attempted this routine returns 1.
**
** All of the expression analysis must occur on both the outer query and
@@ -121041,12 +121095,11 @@
*/
static int flattenSubquery(
Parse *pParse, /* Parsing context */
Select *p, /* The parent or outer SELECT statement */
int iFrom, /* Index in p->pSrc->a[] of the inner subquery */
- int isAgg, /* True if outer SELECT uses aggregate functions */
- int subqueryIsAgg /* True if the subquery uses aggregate functions */
+ int isAgg /* True if outer SELECT uses aggregate functions */
){
const char *zSavedAuthContext = pParse->zAuthContext;
Select *pParent; /* Current UNION ALL term of the other query */
Select *pSub; /* The inner query or "subquery" */
Select *pSub1; /* Pointer to the rightmost select in sub-query */
@@ -121061,28 +121114,18 @@
sqlite3 *db = pParse->db;
/* Check to see if flattening is permitted. Return 0 if not.
*/
assert( p!=0 );
- assert( p->pPrior==0 ); /* Unable to flatten compound queries */
+ assert( p->pPrior==0 );
if( OptimizationDisabled(db, SQLITE_QueryFlattener) ) return 0;
pSrc = p->pSrc;
assert( pSrc && iFrom>=0 && iFromnSrc );
pSubitem = &pSrc->a[iFrom];
iParent = pSubitem->iCursor;
pSub = pSubitem->pSelect;
assert( pSub!=0 );
- if( subqueryIsAgg ){
- if( isAgg ) return 0; /* Restriction (1) */
- if( pSrc->nSrc>1 ) return 0; /* Restriction (2a) */
- if( (p->pWhere && ExprHasProperty(p->pWhere,EP_Subquery))
- || (sqlite3ExprListFlags(p->pEList) & EP_Subquery)!=0
- || (sqlite3ExprListFlags(p->pOrderBy) & EP_Subquery)!=0
- ){
- return 0; /* Restriction (2b) */
- }
- }
pSubSrc = pSub->pSrc;
assert( pSubSrc );
/* Prior to version 3.1.2, when LIMIT and OFFSET had to be simple constants,
** not arbitrary expressions, we allowed some combining of LIMIT and OFFSET
@@ -121093,37 +121136,33 @@
if( pSub->pOffset ) return 0; /* Restriction (14) */
if( (p->selFlags & SF_Compound)!=0 && pSub->pLimit ){
return 0; /* Restriction (15) */
}
if( pSubSrc->nSrc==0 ) return 0; /* Restriction (7) */
- if( pSub->selFlags & SF_Distinct ) return 0; /* Restriction (5) */
+ if( pSub->selFlags & SF_Distinct ) return 0; /* Restriction (4) */
if( pSub->pLimit && (pSrc->nSrc>1 || isAgg) ){
return 0; /* Restrictions (8)(9) */
}
- if( (p->selFlags & SF_Distinct)!=0 && subqueryIsAgg ){
- return 0; /* Restriction (6) */
- }
if( p->pOrderBy && pSub->pOrderBy ){
return 0; /* Restriction (11) */
}
if( isAgg && pSub->pOrderBy ) return 0; /* Restriction (16) */
if( pSub->pLimit && p->pWhere ) return 0; /* Restriction (19) */
if( pSub->pLimit && (p->selFlags & SF_Distinct)!=0 ){
return 0; /* Restriction (21) */
}
- testcase( pSub->selFlags & SF_Recursive );
- testcase( pSub->selFlags & SF_MinMaxAgg );
- if( pSub->selFlags & (SF_Recursive|SF_MinMaxAgg) ){
- return 0; /* Restrictions (22) and (24) */
+ if( pSub->selFlags & (SF_Recursive) ){
+ return 0; /* Restrictions (22) */
}
if( (p->selFlags & SF_Recursive) && pSub->pPrior ){
return 0; /* Restriction (23) */
}
/*
** If the subquery is the right operand of a LEFT JOIN, then the
- ** subquery may not be a join itself. Example of why this is not allowed:
+ ** subquery may not be a join itself (3a). Example of why this is not
+ ** allowed:
**
** t1 LEFT OUTER JOIN (t2 JOIN t3)
**
** If we flatten the above, we would get
**
@@ -121130,58 +121169,60 @@
** (t1 LEFT OUTER JOIN t2) JOIN t3
**
** which is not at all the same thing.
**
** If the subquery is the right operand of a LEFT JOIN, then the outer
- ** query cannot be an aggregate. This is an artifact of the way aggregates
- ** are processed - there is no mechanism to determine if the LEFT JOIN
- ** table should be all-NULL.
+ ** query cannot be an aggregate. (3c) This is an artifact of the way
+ ** aggregates are processed - there is no mechanism to determine if
+ ** the LEFT JOIN table should be all-NULL.
**
** See also tickets #306, #350, and #3300.
*/
if( (pSubitem->fg.jointype & JT_OUTER)!=0 ){
isLeftJoin = 1;
if( pSubSrc->nSrc>1 || isAgg || IsVirtual(pSubSrc->a[0].pTab) ){
- return 0; /* Restriction (3) */
+ /* (3a) (3c) (3b) */
+ return 0;
}
}
#ifdef SQLITE_EXTRA_IFNULLROW
else if( iFrom>0 && !isAgg ){
/* Setting isLeftJoin to -1 causes OP_IfNullRow opcodes to be generated for
- ** every reference to any result column from subquery in a join, even though
- ** they are not necessary. This will stress-test the OP_IfNullRow opcode. */
+ ** every reference to any result column from subquery in a join, even
+ ** though they are not necessary. This will stress-test the OP_IfNullRow
+ ** opcode. */
isLeftJoin = -1;
}
#endif
- /* Restriction 17: If the sub-query is a compound SELECT, then it must
+ /* Restriction (17): If the sub-query is a compound SELECT, then it must
** use only the UNION ALL operator. And none of the simple select queries
** that make up the compound SELECT are allowed to be aggregate or distinct
** queries.
*/
if( pSub->pPrior ){
if( pSub->pOrderBy ){
- return 0; /* Restriction 20 */
+ return 0; /* Restriction (20) */
}
if( isAgg || (p->selFlags & SF_Distinct)!=0 || pSrc->nSrc!=1 ){
- return 0;
+ return 0; /* (17d1), (17d2), or (17d3) */
}
for(pSub1=pSub; pSub1; pSub1=pSub1->pPrior){
testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct );
testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Aggregate );
assert( pSub->pSrc!=0 );
assert( pSub->pEList->nExpr==pSub1->pEList->nExpr );
- if( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))!=0
- || (pSub1->pPrior && pSub1->op!=TK_ALL)
- || pSub1->pSrc->nSrc<1
+ if( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))!=0 /* (17b) */
+ || (pSub1->pPrior && pSub1->op!=TK_ALL) /* (17a) */
+ || pSub1->pSrc->nSrc<1 /* (17c) */
){
return 0;
}
testcase( pSub1->pSrc->nSrc>1 );
}
- /* Restriction 18. */
+ /* Restriction (18). */
if( p->pOrderBy ){
int ii;
for(ii=0; iipOrderBy->nExpr; ii++){
if( p->pOrderBy->a[ii].u.x.iOrderByCol==0 ) return 0;
}
@@ -121398,22 +121439,11 @@
}
pWhere = sqlite3ExprDup(db, pSub->pWhere, 0);
if( isLeftJoin>0 ){
setJoinExpr(pWhere, iNewParent);
}
- if( subqueryIsAgg ){
- assert( pParent->pHaving==0 );
- pParent->pHaving = pParent->pWhere;
- pParent->pWhere = pWhere;
- pParent->pHaving = sqlite3ExprAnd(db,
- sqlite3ExprDup(db, pSub->pHaving, 0), pParent->pHaving
- );
- assert( pParent->pGroupBy==0 );
- pParent->pGroupBy = sqlite3ExprListDup(db, pSub->pGroupBy, 0);
- }else{
- pParent->pWhere = sqlite3ExprAnd(db, pWhere, pParent->pWhere);
- }
+ pParent->pWhere = sqlite3ExprAnd(db, pWhere, pParent->pWhere);
if( db->mallocFailed==0 ){
SubstContext x;
x.pParse = pParse;
x.iTable = iParent;
x.iNewTable = iNewParent;
@@ -121472,13 +121502,17 @@
** The hope is that the terms added to the inner query will make it more
** efficient.
**
** Do not attempt this optimization if:
**
-** (1) The inner query is an aggregate. (In that case, we'd really want
-** to copy the outer WHERE-clause terms onto the HAVING clause of the
-** inner query. But they probably won't help there so do not bother.)
+** (1) (** This restriction was removed on 2017-09-29. We used to
+** disallow this optimization for aggregate subqueries, but now
+** it is allowed by putting the extra terms on the HAVING clause.
+** The added HAVING clause is pointless if the subquery lacks
+** a GROUP BY clause. But such a HAVING clause is also harmless
+** so there does not appear to be any reason to add extra logic
+** to suppress it. **)
**
** (2) The inner query is the recursive part of a common table expression.
**
** (3) The inner query has a LIMIT clause (since the changes to the WHERE
** close would change the meaning of the LIMIT).
@@ -121499,28 +121533,34 @@
Expr *pWhere, /* The WHERE clause of the outer query */
int iCursor /* Cursor number of the subquery */
){
Expr *pNew;
int nChng = 0;
- Select *pX; /* For looping over compound SELECTs in pSubq */
if( pWhere==0 ) return 0;
- for(pX=pSubq; pX; pX=pX->pPrior){
- if( (pX->selFlags & (SF_Aggregate|SF_Recursive))!=0 ){
- testcase( pX->selFlags & SF_Aggregate );
- testcase( pX->selFlags & SF_Recursive );
- testcase( pX!=pSubq );
- return 0; /* restrictions (1) and (2) */
+ if( pSubq->selFlags & SF_Recursive ) return 0; /* restriction (2) */
+
+#ifdef SQLITE_DEBUG
+ /* Only the first term of a compound can have a WITH clause. But make
+ ** sure no other terms are marked SF_Recursive in case something changes
+ ** in the future.
+ */
+ {
+ Select *pX;
+ for(pX=pSubq; pX; pX=pX->pPrior){
+ assert( (pX->selFlags & (SF_Recursive))==0 );
}
}
+#endif
+
if( pSubq->pLimit!=0 ){
return 0; /* restriction (3) */
}
while( pWhere->op==TK_AND ){
nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, iCursor);
pWhere = pWhere->pLeft;
}
- if( ExprHasProperty(pWhere,EP_FromJoin) ) return 0; /* restriction 5 */
+ if( ExprHasProperty(pWhere,EP_FromJoin) ) return 0; /* restriction (5) */
if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){
nChng++;
while( pSubq ){
SubstContext x;
pNew = sqlite3ExprDup(pParse->db, pWhere, 0);
@@ -121528,11 +121568,15 @@
x.iTable = iCursor;
x.iNewTable = iCursor;
x.isLeftJoin = 0;
x.pEList = pSubq->pEList;
pNew = substExpr(&x, pNew);
- pSubq->pWhere = sqlite3ExprAnd(pParse->db, pSubq->pWhere, pNew);
+ if( pSubq->selFlags & SF_Aggregate ){
+ pSubq->pHaving = sqlite3ExprAnd(pParse->db, pSubq->pHaving, pNew);
+ }else{
+ pSubq->pWhere = sqlite3ExprAnd(pParse->db, pSubq->pWhere, pNew);
+ }
pSubq = pSubq->pPrior;
}
}
return nChng;
}
@@ -121856,11 +121900,12 @@
sqlite3ErrorMsg(
pParse, "multiple references to recursive table: %s", pCte->zName
);
return SQLITE_ERROR;
}
- assert( pTab->nTabRef==1 || ((pSel->selFlags&SF_Recursive) && pTab->nTabRef==2 ));
+ assert( pTab->nTabRef==1 ||
+ ((pSel->selFlags&SF_Recursive) && pTab->nTabRef==2 ));
pCte->zCteErr = "circular reference: %s";
pSavedWith = pParse->pWith;
pParse->pWith = pWith;
if( bMayRecursive ){
@@ -122000,11 +122045,15 @@
assert( pFrom->pTab==0 );
if( sqlite3WalkSelect(pWalker, pSel) ) return WRC_Abort;
pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table));
if( pTab==0 ) return WRC_Abort;
pTab->nTabRef = 1;
- pTab->zName = sqlite3MPrintf(db, "sqlite_sq_%p", (void*)pTab);
+ if( pFrom->zAlias ){
+ pTab->zName = sqlite3DbStrDup(db, pFrom->zAlias);
+ }else{
+ pTab->zName = sqlite3MPrintf(db, "subquery_%p", (void*)pTab);
+ }
while( pSel->pPrior ){ pSel = pSel->pPrior; }
sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol);
pTab->iPKey = -1;
pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) );
pTab->tabFlags |= TF_Ephemeral;
@@ -122660,28 +122709,28 @@
static int countOfViewOptimization(Parse *pParse, Select *p){
Select *pSub, *pPrior;
Expr *pExpr;
Expr *pCount;
sqlite3 *db;
- if( (p->selFlags & SF_Aggregate)==0 ) return 0; /* This is an aggregate query */
+ if( (p->selFlags & SF_Aggregate)==0 ) return 0; /* This is an aggregate */
if( p->pEList->nExpr!=1 ) return 0; /* Single result column */
pExpr = p->pEList->a[0].pExpr;
if( pExpr->op!=TK_AGG_FUNCTION ) return 0; /* Result is an aggregate */
- if( sqlite3_stricmp(pExpr->u.zToken,"count") ) return 0; /* Must be count() */
+ if( sqlite3_stricmp(pExpr->u.zToken,"count") ) return 0; /* Is count() */
if( pExpr->x.pList!=0 ) return 0; /* Must be count(*) */
- if( p->pSrc->nSrc!=1 ) return 0; /* One table in the FROM clause */
+ if( p->pSrc->nSrc!=1 ) return 0; /* One table in FROM */
pSub = p->pSrc->a[0].pSelect;
if( pSub==0 ) return 0; /* The FROM is a subquery */
- if( pSub->pPrior==0 ) return 0; /* Must be a compound subquery */
+ if( pSub->pPrior==0 ) return 0; /* Must be a compound ry */
do{
if( pSub->op!=TK_ALL && pSub->pPrior ) return 0; /* Must be UNION ALL */
if( pSub->pWhere ) return 0; /* No WHERE clause */
if( pSub->selFlags & SF_Aggregate ) return 0; /* Not an aggregate */
- pSub = pSub->pPrior; /* Repeat over compound terms */
+ pSub = pSub->pPrior; /* Repeat over compound */
}while( pSub );
- /* If we reach this point, that means it is OK to perform the transformation */
+ /* If we reach this point then it is OK to perform the transformation */
db = pParse->db;
pCount = pExpr;
pExpr = 0;
pSub = p->pSrc->a[0].pSelect;
@@ -122817,11 +122866,10 @@
*/
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
for(i=0; !p->pPrior && inSrc; i++){
struct SrcList_item *pItem = &pTabList->a[i];
Select *pSub = pItem->pSelect;
- int isAggSub;
Table *pTab = pItem->pTab;
if( pSub==0 ) continue;
/* Catch mismatch in the declared columns of a view and the number of
** columns in the SELECT on the RHS */
@@ -122829,17 +122877,40 @@
sqlite3ErrorMsg(pParse, "expected %d columns for '%s' but got %d",
pTab->nCol, pTab->zName, pSub->pEList->nExpr);
goto select_end;
}
- isAggSub = (pSub->selFlags & SF_Aggregate)!=0;
- if( flattenSubquery(pParse, p, i, isAgg, isAggSub) ){
+ /* Do not try to flatten an aggregate subquery.
+ **
+ ** Flattening an aggregate subquery is only possible if the outer query
+ ** is not a join. But if the outer query is not a join, then the subquery
+ ** will be implemented as a co-routine and there is no advantage to
+ ** flattening in that case.
+ */
+ if( (pSub->selFlags & SF_Aggregate)!=0 ) continue;
+ assert( pSub->pGroupBy==0 );
+
+ /* If the subquery contains an ORDER BY clause and if
+ ** it will be implemented as a co-routine, then do not flatten. This
+ ** restriction allows SQL constructs like this:
+ **
+ ** SELECT expensive_function(x)
+ ** FROM (SELECT x FROM tab ORDER BY y LIMIT 10);
+ **
+ ** The expensive_function() is only computed on the 10 rows that
+ ** are output, rather than every row of the table.
+ */
+ if( pSub->pOrderBy!=0
+ && i==0
+ && (pTabList->nSrc==1
+ || (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0)
+ ){
+ continue;
+ }
+
+ if( flattenSubquery(pParse, p, i, isAgg) ){
/* This subquery can be absorbed into its parent. */
- if( isAggSub ){
- isAgg = 1;
- p->selFlags |= SF_Aggregate;
- }
i = -1;
}
pTabList = p->pSrc;
if( db->mallocFailed ) goto select_end;
if( !IgnorableOrderby(pDest) ){
@@ -122869,25 +122940,29 @@
*/
for(i=0; inSrc; i++){
struct SrcList_item *pItem = &pTabList->a[i];
SelectDest dest;
Select *pSub;
+#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
+ const char *zSavedAuthContext;
+#endif
- /* Issue SQLITE_READ authorizations with a fake column name for any tables that
- ** are referenced but from which no values are extracted. Examples of where these
- ** kinds of null SQLITE_READ authorizations would occur:
+ /* Issue SQLITE_READ authorizations with a fake column name for any
+ ** tables that are referenced but from which no values are extracted.
+ ** Examples of where these kinds of null SQLITE_READ authorizations
+ ** would occur:
**
** SELECT count(*) FROM t1; -- SQLITE_READ t1.""
** SELECT t1.* FROM t1, t2; -- SQLITE_READ t2.""
**
** The fake column name is an empty string. It is possible for a table to
** have a column named by the empty string, in which case there is no way to
** distinguish between an unreferenced table and an actual reference to the
- ** "" column. The original design was for the fake column name to be a NULL,
+ ** "" column. The original design was for the fake column name to be a NULL,
** which would be unambiguous. But legacy authorization callbacks might
- ** assume the column name is non-NULL and segfault. The use of an empty string
- ** for the fake column name seems safer.
+ ** assume the column name is non-NULL and segfault. The use of an empty
+ ** string for the fake column name seems safer.
*/
if( pItem->colUsed==0 ){
sqlite3AuthCheck(pParse, SQLITE_READ, pItem->zName, "", pItem->zDatabase);
}
@@ -122934,35 +123009,32 @@
SELECTTRACE(0x100,pParse,p,("After WHERE-clause push-down:\n"));
sqlite3TreeViewSelect(0, p, 0);
}
#endif
}
+
+ zSavedAuthContext = pParse->zAuthContext;
+ pParse->zAuthContext = pItem->zName;
/* Generate code to implement the subquery
**
- ** The subquery is implemented as a co-routine if all of these are true:
- ** (1) The subquery is guaranteed to be the outer loop (so that it
- ** does not need to be computed more than once)
- ** (2) The ALL keyword after SELECT is omitted. (Applications are
- ** allowed to say "SELECT ALL" instead of just "SELECT" to disable
- ** the use of co-routines.)
- ** (3) Co-routines are not disabled using sqlite3_test_control()
- ** with SQLITE_TESTCTRL_OPTIMIZATIONS.
+ ** The subquery is implemented as a co-routine if the subquery is
+ ** guaranteed to be the outer loop (so that it does not need to be
+ ** computed more than once)
**
** TODO: Are there other reasons beside (1) to use a co-routine
** implementation?
*/
if( i==0
&& (pTabList->nSrc==1
|| (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) /* (1) */
- && (p->selFlags & SF_All)==0 /* (2) */
- && OptimizationEnabled(db, SQLITE_SubqCoroutine) /* (3) */
){
/* Implement a co-routine that will return a single row of the result
** set on each invocation.
*/
int addrTop = sqlite3VdbeCurrentAddr(v)+1;
+
pItem->regReturn = ++pParse->nMem;
sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop);
VdbeComment((v, "%s", pItem->pTab->zName));
pItem->addrFillSub = addrTop;
sqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn);
@@ -123016,10 +123088,11 @@
sqlite3VdbeChangeP1(v, topAddr, retAddr);
sqlite3ClearTempRegCache(pParse);
}
if( db->mallocFailed ) goto select_end;
pParse->nHeight -= sqlite3SelectExprHeight(p);
+ pParse->zAuthContext = zSavedAuthContext;
#endif
}
/* Various elements of the SELECT copied into local variables for
** convenience */
@@ -131021,11 +131094,10 @@
** returned when it should not be, then incorrect answers might result.
*/
static int termIsEquivalence(Parse *pParse, Expr *pExpr){
char aff1, aff2;
CollSeq *pColl;
- const char *zColl1, *zColl2;
if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0;
if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0;
if( ExprHasProperty(pExpr, EP_FromJoin) ) return 0;
aff1 = sqlite3ExprAffinity(pExpr->pLeft);
aff2 = sqlite3ExprAffinity(pExpr->pRight);
@@ -131034,15 +131106,11 @@
){
return 0;
}
pColl = sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, pExpr->pRight);
if( pColl==0 || sqlite3StrICmp(pColl->zName, "BINARY")==0 ) return 1;
- pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft);
- zColl1 = pColl ? pColl->zName : 0;
- pColl = sqlite3ExprCollSeq(pParse, pExpr->pRight);
- zColl2 = pColl ? pColl->zName : 0;
- return sqlite3_stricmp(zColl1, zColl2)==0;
+ return sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight);
}
/*
** Recursively walk the expressions of a SELECT statement and generate
** a bitmask indicating which tables are used in that expression
@@ -132119,12 +132187,12 @@
Expr *p = sqlite3ExprSkipCollate(pList->a[i].pExpr);
if( p->op==TK_COLUMN
&& p->iColumn==pIdx->aiColumn[iCol]
&& p->iTable==iBase
){
- CollSeq *pColl = sqlite3ExprCollSeq(pParse, pList->a[i].pExpr);
- if( pColl && 0==sqlite3StrICmp(pColl->zName, zColl) ){
+ CollSeq *pColl = sqlite3ExprNNCollSeq(pParse, pList->a[i].pExpr);
+ if( 0==sqlite3StrICmp(pColl->zName, zColl) ){
return i;
}
}
}
@@ -134385,11 +134453,11 @@
if( pExpr->iColumn==pIndex->aiColumn[jj] ) return 1;
}
}else if( (aColExpr = pIndex->aColExpr)!=0 ){
for(jj=0; jjnKeyCol; jj++){
if( pIndex->aiColumn[jj]!=XN_EXPR ) continue;
- if( sqlite3ExprCompare(0, pExpr,aColExpr->a[jj].pExpr,iCursor)==0 ){
+ if( sqlite3ExprCompareSkip(pExpr,aColExpr->a[jj].pExpr,iCursor)==0 ){
return 1;
}
}
}
}
@@ -135295,18 +135363,14 @@
assert( wctrlFlags & WHERE_ORDERBY_LIMIT );
for(j=0; jnLTerm && pTerm!=pLoop->aLTerm[j]; j++){}
if( j>=pLoop->nLTerm ) continue;
}
if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 && pOBExpr->iColumn>=0 ){
- const char *z1, *z2;
- pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr);
- if( !pColl ) pColl = db->pDfltColl;
- z1 = pColl->zName;
- pColl = sqlite3ExprCollSeq(pWInfo->pParse, pTerm->pExpr);
- if( !pColl ) pColl = db->pDfltColl;
- z2 = pColl->zName;
- if( sqlite3StrICmp(z1, z2)!=0 ) continue;
+ if( sqlite3ExprCollSeqMatch(pWInfo->pParse,
+ pOrderBy->a[i].pExpr, pTerm->pExpr)==0 ){
+ continue;
+ }
testcase( pTerm->pExpr->op==TK_IS );
}
obSat |= MASKBIT(i);
}
@@ -135374,11 +135438,11 @@
** (revIdx) for the j-th column of the index.
*/
if( pIndex ){
iColumn = pIndex->aiColumn[j];
revIdx = pIndex->aSortOrder[j];
- if( iColumn==pIndex->pTable->iPKey ) iColumn = -1;
+ if( iColumn==pIndex->pTable->iPKey ) iColumn = XN_ROWID;
}else{
iColumn = XN_ROWID;
revIdx = 0;
}
@@ -135401,23 +135465,22 @@
if( MASKBIT(i) & obSat ) continue;
pOBExpr = sqlite3ExprSkipCollate(pOrderBy->a[i].pExpr);
testcase( wctrlFlags & WHERE_GROUPBY );
testcase( wctrlFlags & WHERE_DISTINCTBY );
if( (wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY))==0 ) bOnce = 0;
- if( iColumn>=(-1) ){
+ if( iColumn>=XN_ROWID ){
if( pOBExpr->op!=TK_COLUMN ) continue;
if( pOBExpr->iTable!=iCur ) continue;
if( pOBExpr->iColumn!=iColumn ) continue;
}else{
- if( sqlite3ExprCompare(0,
- pOBExpr,pIndex->aColExpr->a[j].pExpr,iCur) ){
+ Expr *pIdxExpr = pIndex->aColExpr->a[j].pExpr;
+ if( sqlite3ExprCompareSkip(pOBExpr, pIdxExpr, iCur) ){
continue;
}
}
if( iColumn!=XN_ROWID ){
- pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr);
- if( !pColl ) pColl = db->pDfltColl;
+ pColl = sqlite3ExprNNCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr);
if( sqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ) continue;
}
pLoop->u.btree.nIdxCol = j+1;
isMatch = 1;
break;
@@ -137095,11 +137158,12 @@
** YYNSTATE the combined number of states.
** YYNRULE the number of rules in the grammar
** YY_MAX_SHIFT Maximum value for shift actions
** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions
** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions
-** YY_MIN_REDUCE Maximum value for reduce actions
+** YY_MIN_REDUCE Minimum value for reduce actions
+** YY_MAX_REDUCE Maximum value for reduce actions
** YY_ERROR_ACTION The yy_action[] code for syntax error
** YY_ACCEPT_ACTION The yy_action[] code for accept
** YY_NO_ACTION The yy_action[] code for no-op
*/
#ifndef INTERFACE
@@ -184892,11 +184956,12 @@
** fts5YYNSTATE the combined number of states.
** fts5YYNRULE the number of rules in the grammar
** fts5YY_MAX_SHIFT Maximum value for shift actions
** fts5YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions
** fts5YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions
-** fts5YY_MIN_REDUCE Maximum value for reduce actions
+** fts5YY_MIN_REDUCE Minimum value for reduce actions
+** fts5YY_MAX_REDUCE Maximum value for reduce actions
** fts5YY_ERROR_ACTION The fts5yy_action[] code for syntax error
** fts5YY_ACCEPT_ACTION The fts5yy_action[] code for accept
** fts5YY_NO_ACTION The fts5yy_action[] code for no-op
*/
#ifndef INTERFACE
@@ -200632,11 +200697,11 @@
int nArg, /* Number of args */
sqlite3_value **apUnused /* Function arguments */
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
- sqlite3_result_text(pCtx, "fts5: 2017-09-21 20:43:48 5d03c738e93d36815248991d9ed3d62297ba1bb966e602e7874410076c144f43", -1, SQLITE_TRANSIENT);
+ sqlite3_result_text(pCtx, "fts5: 2017-09-29 16:07:56 0840f9f824c16212ce3fd6c859e501176eb0a58924ea1728a54d5bdfd0c25c86", -1, SQLITE_TRANSIENT);
}
static int fts5Init(sqlite3 *db){
static const sqlite3_module fts5Mod = {
/* iVersion */ 2,
@@ -204901,12 +204966,12 @@
}
#endif /* SQLITE_CORE */
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
/************** End of stmt.c ************************************************/
-#if __LINE__!=204906
+#if __LINE__!=204971
#undef SQLITE_SOURCE_ID
-#define SQLITE_SOURCE_ID "2017-09-21 20:43:48 5d03c738e93d36815248991d9ed3d62297ba1bb966e602e7874410076c14alt2"
+#define SQLITE_SOURCE_ID "2017-10-02 02:52:54 c9104b59c7ed360291f7f6fc8caae938e9840c77620d598e4096f78183bfalt2"
#endif
/* Return the source-id for this library */
SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
/************************** End of sqlite3.c ******************************/
Index: src/sqlite3.h
==================================================================
--- src/sqlite3.h
+++ src/sqlite3.h
@@ -123,11 +123,11 @@
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.21.0"
#define SQLITE_VERSION_NUMBER 3021000
-#define SQLITE_SOURCE_ID "2017-09-21 20:43:48 5d03c738e93d36815248991d9ed3d62297ba1bb966e602e7874410076c144f43"
+#define SQLITE_SOURCE_ID "2017-10-02 02:52:54 c9104b59c7ed360291f7f6fc8caae938e9840c77620d598e4096f78183bf807a"
/*
** CAPI3REF: Run-Time Library Version Numbers
** KEYWORDS: sqlite3_version sqlite3_sourceid
**
Index: src/style.c
==================================================================
--- src/style.c
+++ src/style.c
@@ -1425,10 +1425,11 @@
@ border-spacing: 0;
},
{ ".brlist table th", "Branch list table headers",
@ text-align: left;
@ padding: 0px 1em 0.5ex 0px;
+ @ vertical-align: bottom;
},
{ ".brlist table td", "Branch list table headers",
@ padding: 0px 2em 0px 0px;
@ white-space: nowrap;
},
Index: src/th.h
==================================================================
--- src/th.h
+++ src/th.h
@@ -102,10 +102,11 @@
#define TH_OK 0
#define TH_ERROR 1
#define TH_BREAK 2
#define TH_RETURN 3
#define TH_CONTINUE 4
+#define TH_RETURN2 5
/*
** Set and get the interpreter result.
*/
int Th_SetResult(Th_Interp *, const char *, int);
Index: src/th_lang.c
==================================================================
--- src/th_lang.c
+++ src/th_lang.c
@@ -433,10 +433,13 @@
rc = Th_InFrame(interp, proc_call2, (void *)p, (void *)&procargs);
if( rc==TH_RETURN ){
rc = TH_OK;
}
+ if( rc==TH_RETURN2 ){
+ rc = TH_RETURN;
+ }
return rc;
}
/*
** This function is registered as the delete callback for all commands
@@ -731,11 +734,11 @@
if( argc!=4 ){
return Th_WrongNumArgs(interp, "string index string index");
}
if( argl[3]==3 && 0==memcmp("end", argv[3], 3) ){
- iIndex = argl[2];
+ iIndex = argl[2]-1;
}else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
Th_ErrorMessage(
interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
return TH_ERROR;
}
Index: src/th_main.c
==================================================================
--- src/th_main.c
+++ src/th_main.c
@@ -299,10 +299,11 @@
case TH_OK: return nullIfOk ? 0 : "TH_OK";
case TH_ERROR: return "TH_ERROR";
case TH_BREAK: return "TH_BREAK";
case TH_RETURN: return "TH_RETURN";
case TH_CONTINUE: return "TH_CONTINUE";
+ case TH_RETURN2: return "TH_RETURN2";
default: {
sqlite3_snprintf(sizeof(zRc), zRc, "TH1 return code %d", rc);
}
}
return zRc;
Index: src/unversioned.c
==================================================================
--- src/unversioned.c
+++ src/unversioned.c
@@ -484,86 +484,90 @@
return;
}
if( PB("byage") ) zOrderBy = "mtime DESC";
if( PB("showdel") ) showDel = 1;
db_prepare(&q,
- "SELECT"
- " name,"
- " mtime,"
- " hash,"
- " sz,"
- " (SELECT login FROM rcvfrom, user"
- " WHERE user.uid=rcvfrom.uid AND rcvfrom.rcvid=unversioned.rcvid),"
- " rcvid"
- " FROM unversioned %s ORDER BY %s",
- showDel ? "" : "WHERE hash IS NOT NULL" /*safe-for-%s*/,
- zOrderBy/*safe-for-%s*/
- );
- iNow = db_int64(0, "SELECT strftime('%%s','now');");
- while( db_step(&q)==SQLITE_ROW ){
- const char *zName = db_column_text(&q, 0);
- sqlite3_int64 mtime = db_column_int(&q, 1);
- const char *zHash = db_column_text(&q, 2);
- int isDeleted = zHash==0;
- int fullSize = db_column_int(&q, 3);
- char *zAge = human_readable_age((iNow - mtime)/86400.0);
- const char *zLogin = db_column_text(&q, 4);
- int rcvid = db_column_int(&q,5);
- if( zLogin==0 ) zLogin = "";
- if( (n++)==0 ){
- @
- @
- @
- @ Name
- @ | Age
- @ | Size
- @ | User
- @ | SHA1
- if( g.perm.Admin ){
- @ | rcvid
- }
- @ |
- @
- }
- @
- if( isDeleted ){
- sqlite3_snprintf(sizeof(zSzName), zSzName, "Deleted");
- zHash = "";
- fullSize = 0;
- @ %h(zName) |
- }else{
- approxSizeName(sizeof(zSzName), zSzName, fullSize);
- iTotalSz += fullSize;
- cnt++;
- @ %h(zName) |
- }
- @ %s(zAge) |
- @ %s(zSzName) |
- @ %h(zLogin) |
- @ %h(zHash) |
- if( g.perm.Admin ){
- if( rcvid ){
- @ %d(rcvid)
- }else{
- @ |
- }
- }
- @ |
- fossil_free(zAge);
- }
- db_finalize(&q);
- if( n ){
- approxSizeName(sizeof(zSzName), zSzName, iTotalSz);
- @
- @ Total over %d(cnt) files | | %s(zSzName)
- @ | | | |
- @
- output_table_sorting_javascript("uvtab","tkKttN",1);
- }else{
- @ No unversioned files on this server.
- }
- style_footer();
+ "SELECT"
+ " name,"
+ " mtime,"
+ " hash,"
+ " sz,"
+ " (SELECT login FROM rcvfrom, user"
+ " WHERE user.uid=rcvfrom.uid AND rcvfrom.rcvid=unversioned.rcvid),"
+ " rcvid"
+ " FROM unversioned %s ORDER BY %s",
+ showDel ? "" : "WHERE hash IS NOT NULL" /*safe-for-%s*/,
+ zOrderBy/*safe-for-%s*/
+ );
+ iNow = db_int64(0, "SELECT strftime('%%s','now');");
+ while( db_step(&q)==SQLITE_ROW ){
+ const char *zName = db_column_text(&q, 0);
+ sqlite3_int64 mtime = db_column_int(&q, 1);
+ const char *zHash = db_column_text(&q, 2);
+ int isDeleted = zHash==0;
+ int fullSize = db_column_int(&q, 3);
+ char *zAge = human_readable_age((iNow - mtime)/86400.0);
+ const char *zLogin = db_column_text(&q, 4);
+ int rcvid = db_column_int(&q,5);
+ if( zLogin==0 ) zLogin = "";
+ if( (n++)==0 ){
+ @
+ @
+ @
+ @ Name
+ @ | Age
+ @ | Size
+ @ | User
+ @ | SHA1
+ if( g.perm.Admin ){
+ @ | rcvid
+ }
+ @ |
+ @
+ }
+ @
+ if( isDeleted ){
+ sqlite3_snprintf(sizeof(zSzName), zSzName, "Deleted");
+ zHash = "";
+ fullSize = 0;
+ @ %h(zName) |
+ }else{
+ approxSizeName(sizeof(zSzName), zSzName, fullSize);
+ iTotalSz += fullSize;
+ cnt++;
+ @ %h(zName) |
+ }
+ @ %s(zAge) |
+ @ %s(zSzName) |
+ @ %h(zLogin) |
+ @ %h(zHash) |
+ if( g.perm.Admin ){
+ if( rcvid ){
+ @ %d(rcvid)
+ }else{
+ @ |
+ }
+ }
+ @ |
+ fossil_free(zAge);
+ }
+ db_finalize(&q);
+ if( n ){
+ approxSizeName(sizeof(zSzName), zSzName, iTotalSz);
+ @
+ @ Total over %d(cnt) files | | %s(zSzName)
+ @ | |
+ if( g.perm.Admin ){
+ @ |
+ }
+ @ |
+ @
+ output_table_sorting_javascript("uvtab","tkKttN",1);
+ }else{
+ @ No unversioned files on this server.
+ }
+ style_footer();
}
/*
** WEBPAGE: juvlist
**
Index: test/th1-hooks.test
==================================================================
--- test/th1-hooks.test
+++ test/th1-hooks.test
@@ -70,11 +70,11 @@
} elseif {$::cmd_name eq "test3"} {
emit_hook_log
break "TH_BREAK return code"
} elseif {$::cmd_name eq "test4"} {
emit_hook_log
- return -code 2 "TH_RETURN return code"
+ return -code 5 "TH_RETURN return code"
} elseif {$::cmd_name eq "timeline"} {
set length [llength $::cmd_args]
set length [expr {$length - 1}]
if {[lindex $::cmd_args $length] eq "custom"} {
append_hook_log "CUSTOM TIMELINE"
@@ -82,10 +82,13 @@
return "custom timeline"
} elseif {[lindex $::cmd_args $length] eq "custom2"} {
emit_hook_log
puts "+++ some stuff here +++"
continue "custom2 timeline"
+ } elseif {[lindex $::cmd_args $length] eq "custom3"} {
+ emit_hook_log
+ return -code 5 "TH_RETURN return code"
} elseif {[lindex $::cmd_args $length] eq "now"} {
emit_hook_log
return "now timeline"
} else {
emit_hook_log
@@ -142,10 +145,18 @@
+++ some stuff here +++
command_hook timeline command_notify timeline
}}
###############################################################################
+fossil timeline custom3; # NOTE: Bad "WHEN" argument.
+
+test th1-cmd-hooks-1c {[normalize_result] eq \
+{command_hook timeline
+unknown check-in or invalid date: custom3}}
+
+###############################################################################
+
fossil timeline
test th1-cmd-hooks-2a {[first_data_line] eq \
{command_hook timeline
}}
test th1-cmd-hooks-2b {[second_data_line] eq {ERROR: unsupported timeline}}
@@ -185,13 +196,20 @@
{command_hook test3
}}
###############################################################################
fossil test4
-test th1-custom-cmd-4a {[string trim $RESULT] eq \
+
+test th1-custom-cmd-4a {[first_data_line] eq \
{command_hook test4
}}
+test th1-custom-cmd-4b {[regexp -- \
+ {: unknown command: test4$} [second_data_line]]}
+
+test th1-custom-cmd-4d {[regexp -- \
+ {: use "help" for more information$} [third_data_line]]}
+
###############################################################################
set RESULT [test_fossil_http $repository $dataFileName /timeline]
test th1-web-hooks-1a {[regexp \
Index: test/th1.test
==================================================================
--- test/th1.test
+++ test/th1.test
@@ -1433,10 +1433,85 @@
###############################################################################
fossil test-th-eval {string is integer 0xC0DEF00Z}
test th1-string-is-31 {$RESULT eq "0"}
+
+###############################################################################
+
+fossil test-th-eval {string index "" -1}
+test th1-string-index-1 {$RESULT eq ""}
+
+###############################################################################
+
+fossil test-th-eval {string index "" 0}
+test th1-string-index-2 {$RESULT eq ""}
+
+###############################################################################
+
+fossil test-th-eval {string index "" 1}
+test th1-string-index-3 {$RESULT eq ""}
+
+###############################################################################
+
+fossil test-th-eval {string index "" 2}
+test th1-string-index-4 {$RESULT eq ""}
+
+###############################################################################
+
+fossil test-th-eval {string index "" end}
+test th1-string-index-5 {$RESULT eq ""}
+
+###############################################################################
+
+fossil test-th-eval {string index A -1}
+test th1-string-index-6 {$RESULT eq ""}
+
+###############################################################################
+
+fossil test-th-eval {string index A 0}
+test th1-string-index-7 {$RESULT eq "A"}
+
+###############################################################################
+
+fossil test-th-eval {string index A 1}
+test th1-string-index-8 {$RESULT eq ""}
+
+###############################################################################
+
+fossil test-th-eval {string index A 2}
+test th1-string-index-9 {$RESULT eq ""}
+
+###############################################################################
+
+fossil test-th-eval {string index A end}
+test th1-string-index-10 {$RESULT eq "A"}
+
+###############################################################################
+
+fossil test-th-eval {string index ABC -1}
+test th1-string-index-11 {$RESULT eq ""}
+
+###############################################################################
+
+fossil test-th-eval {string index ABC 0}
+test th1-string-index-12 {$RESULT eq "A"}
+
+###############################################################################
+
+fossil test-th-eval {string index ABC 1}
+test th1-string-index-13 {$RESULT eq "B"}
+
+###############################################################################
+
+fossil test-th-eval {string index ABC 2}
+test th1-string-index-14 {$RESULT eq "C"}
+
+###############################################################################
+
+fossil test-th-eval {string index ABC end}
+test th1-string-index-15 {$RESULT eq "C"}
###############################################################################
fossil test-th-eval {markdown}
test th1-markdown-1 {$RESULT eq \
Index: www/index.wiki
==================================================================
--- www/index.wiki
+++ www/index.wiki
@@ -127,10 +127,12 @@
[http://www.sqliteconcepts.org/THManual.pdf | TH1 scripting language],
used to customize [./custom_ticket.wiki | ticketing], and several other
subsystems, including [./customskin.md | theming].
* List of [./th1.md | TH1 commands provided by Fossil itself] that expose
its key functionality to TH1 scripts.
+ * List of [./th1-hooks.md | TH1 hooks exposed by Fossil] that enable
+ customization of commands and web pages.
* A free hosting server for Fossil repositories is available at
[http://chiselapp.com/].
* How to [./server.wiki | set up a server] for your repository.
* Customizing the [./custom_ticket.wiki | ticket system].
* Methods to [./checkin_names.wiki | identify a specific check-in].
ADDED www/th1-hooks.md
Index: www/th1-hooks.md
==================================================================
--- /dev/null
+++ www/th1-hooks.md
@@ -0,0 +1,134 @@
+TH1 Hooks
+=========
+
+** DRAFT **
+
+The **TH1 hooks** feature allows TH1 scripts to be
+configured that can monitor, create, alter, or cancel the execution of
+Fossil commands and web pages.
+
+This feature requires the TH1 hooks feature to be enabled at compile-time.
+Additionally, the "th1-hooks" repository setting must be enabled at runtime
+in order to successfully make use of this feature.
+
+TH1 Hook Related User-Defined Procedures
+----------------------------------------
+
+In order to activate TH1 hooks, one or more of the following user-defined
+procedures should be defined, generally from within the "th1-setup" script
+(setting) for a repository. The following bullets summarize the available
+TH1 hooks:
+
+ * command\_hook -- _Called before execution of a command._
+ * command\_notify -- _Called after execution of a command._
+ * webpage\_hook -- _Called before rendering of a web page._
+ * webpage\_notify -- _Called after rendering of a web page._
+
+TH1 Hook Related Variables for Commands
+---------------------------------------
+
+ * cmd\_name -- _Name of command being executed._
+ * cmd\_args -- _Current command line arguments._
+ * cmd\_flags -- _Bitmask of CMDFLAG values for the command being executed._
+
+TH1 Hook Related Variables for Web Pages
+----------------------------------------
+
+ * web\_name -- _Name of web page being rendered._
+ * web\_args -- _Current web page arguments._
+ * web\_flags -- _Bitmask of CMDFLAG values for the web page being rendered._
+
+TH1 Hook Related Return Codes for Commands
+-----------------------------------------------------------------------
+
+ * TH\_OK -- _Command will be executed, notification will be executed._
+ * TH\_ERROR -- _Command will be skipped, notification will be skipped,
+ error message will be emitted._
+ * TH\_BREAK -- _Command will be skipped, notification will be skipped._
+ * TH\_RETURN -- _Command will be executed, notification will be skipped._
+ * TH\_CONTINUE -- _Command will be skipped, notification will be executed._
+
+For commands that are not included in the Fossil binary, allowing their
+execution will cause the standard "unknown command" error message to be
+generated, which will typically exit the process. Therefore, adding a
+new command generally requires using the TH_CONTINUE return code.
+
+TH1 Hook Related Return Codes for Web Pages
+------------------------------------------------------------------------
+
+ * TH\_OK -- _Web page will be rendered, notification will be executed._
+ * TH\_ERROR -- _Web page will be skipped, notification will be skipped,
+ error message will be emitted._
+ * TH\_BREAK -- _Web page will be skipped, notification will be skipped._
+ * TH\_RETURN -- _Web page will be rendered, notification will be skipped._
+ * TH\_CONTINUE -- _Web page will be skipped, notification will be executed._
+
+For web pages that are not included in the Fossil binary, allowing their
+rendering will cause the standard "Not Found" error message to be generated,
+which will cause an HTTP 404 status code to be sent. Therefore, adding a
+new web page generally requires using the TH_CONTINUE return code.
+
+Triggering TH1 Return Codes from a Script
+--------------------------------------------------------------------------
+
+ * TH\_OK -- _This is the default return code, nothing special needed._
+ * TH\_ERROR -- _Use the **error** command._
+ * TH\_BREAK -- _Use the **break** command._
+ * TH\_RETURN -- _Use the **return -code 5** command._
+ * TH\_CONTINUE -- _Use the **continue** command._
+
+TH1 command_hook Procedure
+-----------------------------------------------------
+
+ * command\_hook
+
+This user-defined procedure, if present, is called just before the
+execution of a command. The name of the command being executed will
+be stored in the "cmd\_name" global variable. The arguments to the
+command being executed will be stored in the "cmd\_args" global variable.
+The associated CMDFLAG value will be stored in the "cmd\_flags" global
+variable. Before exiting, the procedure should trigger the return
+code that corresponds to the desired action
+to take next.
+
+TH1 command_notify Procedure
+---------------------------------------------------------
+
+ * command\_notify
+
+This user-defined procedure, if present, is called just after the
+execution of a command. The name of the command being executed will
+be stored in the "cmd\_name" global variable. The arguments to the
+command being executed will be stored in the "cmd\_args" global variable.
+The associated CMDFLAG value will be stored in the "cmd\_flags" global
+variable. Before exiting, the procedure should trigger the return
+code that corresponds to the desired action
+to take next.
+
+TH1 webpage_hook Procedure
+-----------------------------------------------------
+
+ * webpage\_hook
+
+This user-defined procedure, if present, is called just before the
+rendering of a web page. The name of the web page being rendered will
+be stored in the "web\_name" global variable. The arguments to the
+web page being rendered will be stored in the "web\_args" global variable.
+The associated CMDFLAG value will be stored in the "web\_flags" global
+variable. Before exiting, the procedure should trigger the return
+code that corresponds to the desired action
+to take next.
+
+TH1 webpage_notify Procedure
+---------------------------------------------------------
+
+ * webpage\_notify
+
+This user-defined procedure, if present, is called just after the
+rendering of a web page. The name of the web page being rendered will
+be stored in the "web\_name" global variable. The arguments to the
+web page being rendered will be stored in the "web\_args" global variable.
+The associated CMDFLAG value will be stored in the "web\_flags" global
+variable. Before exiting, the procedure should trigger the return
+code that corresponds to the desired action
+to take next.
Index: www/th1.md
==================================================================
--- www/th1.md
+++ www/th1.md
@@ -105,10 +105,11 @@
* rename OLD NEW
* return ?-code CODE? ?VALUE?
* set VARNAME VALUE
* string compare STR1 STR2
* string first NEEDLE HAYSTACK ?START-INDEX?
+ * string index STRING INDEX
* string is CLASS STRING
* string last NEEDLE HAYSTACK ?START-INDEX?
* string length STRING
* string range STRING FIRST LAST
* string repeat STRING COUNT