Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -49,5 +49,26 @@ include $(SRCDIR)/main.mk distclean: clean rm -f autoconfig.h config.log Makefile + +reconfig: + @AUTOREMAKE@ + +# Automatically reconfigure whenever an autosetup file or one of the +# make source files change. +# +# The "touch" is necessary to avoid a make loop due to a new upstream +# feature in autosetup (GH 0a71e3c3b7) which rewrites *.in outputs only +# if doing so will write different contents; otherwise, it leaves them +# alone so the mtime doesn't change. This means that if you change one +# our depdendencies besides Makefile.in, we'll reconfigure but Makefile +# won't change, so this rule will remain out of date, so we'll reconfig +# but Makefile won't change, so we'll reconfig but... endlessly. +# +# This is also why we repeat the reconfig target's command here instead +# of delegating to it with "$(MAKE) reconfig": having children running +# around interfering makes this failure mode even worse. +Makefile: @srcdir@/Makefile.in $(SRCDIR)/main.mk @AUTODEPS@ + @AUTOREMAKE@ + touch @builddir@/Makefile Index: autosetup/system.tcl ================================================================== --- autosetup/system.tcl +++ autosetup/system.tcl @@ -205,13 +205,13 @@ } continue } lappend result $line } - writefile $out [string map $mapping [join $result \n]]\n - - msg-result "Created [relative-path $out] from [relative-path $template]" + write-if-changed $out [string map $mapping [join $result \n]]\n { + msg-result "Created [relative-path $out] from [relative-path $template]" + } } # build/host tuples and cross-compilation prefix set build [opt-val build] define build_alias $build Index: src/clone.c ================================================================== --- src/clone.c +++ src/clone.c @@ -293,5 +293,50 @@ void clone_ssh_db_set_options(void){ if( g.zSshCmd && g.zSshCmd[0] ){ db_set("ssh-command", g.zSshCmd, 0); } } + +/* +** WEBPAGE: download +** +** Provide a simple page that enables newbies to download the latest tarball or +** ZIP archive, and provides instructions on how to clone. +*/ +void download_page(void){ + login_check_credentials(); + style_header("Download Page"); + if( !g.perm.Zip ){ + @

Bummer. You do not have permission to download. + if( g.zLogin==0 || g.zLogin[0]==0 ){ + @ Maybe it would work better if you + @ logged in. + }else{ + @ Contact the site administrator and ask them to give + @ you "Download Zip" privileges. + } + }else{ + const char *zDLTag = db_get("download-tag","trunk"); + const char *zNm = db_get("short-project-name","download"); + char *zUrl = href("%R/zip/%t.zip?uuid=%t", zNm, zDLTag); + @

ZIP Archive: %z(zUrl)%h(zNm).zip + zUrl = href("%R/tarball/%t.tar.gz?uuid=%t", zNm, zDLTag); + @

Tarball: %z(zUrl)%h(zNm).tar.gz + } + if( !g.perm.Clone ){ + @

You are not authorized to clone this repository. + if( g.zLogin==0 || g.zLogin[0]==0 ){ + @ Maybe you would be able to clone if you + @ logged in. + }else{ + @ Contact the site administrator and ask them to give + @ you "Clone" privileges in order to clone. + } + }else{ + const char *zNm = db_get("short-project-name","clone"); + @

Clone the repository using this command: + @

+    @ fossil  clone  %s(g.zBaseURL)  %h(zNm).fossil
+    @ 
+ } + style_footer(); +} Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -2926,11 +2926,11 @@ ** When executing certain external commands (e.g. diff and ** gdiff), use relative paths. */ #endif /* -** SETTING; gdiff-command width=40 default=gdiff +** SETTING: gdiff-command width=40 default=gdiff ** The value is an external command to run when performing a graphical ** diff. If undefined, text diff will be used. */ /* ** SETTING: gmerge-command width=40 @@ -3016,14 +3016,13 @@ ** and "delete" commands will also remove the associated ** files from within the checkout. */ #endif /* -** SETTING; pgp-command width=40 -** DEFAULT: gpg --clearsign -o -** +** SETTING: pgp-command width=40 ** Command used to clear-sign manifests at check-in. +** Default value is "gpg --clearsign -o" */ /* ** SETTING: proxy width=32 default=off ** URL of the HTTP proxy. If undefined or "off" then ** the "http_proxy" environment variable is consulted. Index: src/delta.c ================================================================== --- src/delta.c +++ src/delta.c @@ -571,11 +571,11 @@ int lenDelta, /* Length of the delta */ char *zOut /* Write the output into this preallocated buffer */ ){ unsigned int limit; unsigned int total = 0; -#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST +#ifdef FOSSIL_ENABLE_DELTA_CKSUM_TEST char *zOrigOut = zOut; #endif limit = getInt(&zDelta, &lenDelta); if( *zDelta!='\n' ){ @@ -628,11 +628,11 @@ break; } case ';': { zDelta++; lenDelta--; zOut[0] = 0; -#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST +#ifdef FOSSIL_ENABLE_DELTA_CKSUM_TEST if( cnt!=checksum(zOrigOut, total) ){ /* ERROR: bad checksum */ return -1; } #endif Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -2259,11 +2259,11 @@ file_tree_name(zFilename, &treename, 0, 1); zFilename = blob_str(&treename); fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename); db_prepare(&q, - "SELECT" + "SELECT DISTINCT" " (SELECT uuid FROM blob WHERE rid=mlink.fid)," " (SELECT uuid FROM blob WHERE rid=mlink.mid)," " date(event.mtime)," " coalesce(event.euser,event.user)," " mlink.fid" Index: src/finfo.c ================================================================== --- src/finfo.c +++ src/finfo.c @@ -596,12 +596,18 @@ ** WEBPAGE: mlink ** URL: /mlink?name=FILENAME ** URL: /mlink?ci=NAME ** ** Show all MLINK table entries for a particular file, or for -** a particular check-in. This screen is intended for use by developers -** in debugging Fossil. +** a particular check-in. +** +** This screen is intended for use by Fossil developers to help +** in debugging Fossil itself. Ordinary Fossil users are not +** expected to know what the MLINK table is or why it is important. +** +** To avoid confusing ordinary users, this page is only available +** to adminstrators. */ void mlink_page(void){ const char *zFName = P("name"); const char *zCI = P("ci"); Stmt q; @@ -638,16 +644,16 @@ @
@ @ @ @ - @ + @ @ @ @ - @ - @ + @ + @ @ @ while( db_step(&q)==SQLITE_ROW ){ const char *zDate = db_column_text(&q,0); const char *zCkin = db_column_text(&q,1); @@ -659,11 +665,11 @@ const char *zPrior = db_column_text(&q,8); @ @ @ if( zParent ){ - @ + @ }else{ @ } @ if( zFid ){ @@ -711,16 +717,16 @@ @
@
@
DateCheck-inParent Check-inParent
Check-in
Merge?NewOldExe Bit?Prior NameExe
Bit?
Prior
Name
%s(zDate)%S(zCkin)%S(zParent)%S(zParent)(New)%s(isMerge?"✓":"")
@ @ - @ + @ @ @ @ - @ - @ + @ + @ @ @ 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 @@ @ @ @@ -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 + @

    1. 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 + @

    2. the user agent is able to + @ run Javascript in order to set the href= attribute of hyperlinks, and + @
    3. mouse movement is detected (optional - see the checkbox below), and + @
    4. 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 ){ - @

    - @
    FileFromParent
    Check-in
    Merge?NewOldExe Bit?Prior NameExe
    Bit?
    Prior
    Name
    @ %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 } @
    - @ - @ - @ - } - @ - if( isDeleted ){ - sqlite3_snprintf(sizeof(zSzName), zSzName, "Deleted"); - zHash = ""; - fullSize = 0; - @ - }else{ - approxSizeName(sizeof(zSzName), zSzName, fullSize); - iTotalSz += fullSize; - cnt++; - @ - } - @ - @ - @ - @ - if( g.perm.Admin ){ - if( rcvid ){ - @ - fossil_free(zAge); - } - db_finalize(&q); - if( n ){ - approxSizeName(sizeof(zSzName), zSzName, iTotalSz); - @ - @ - @
    Name - @ Age - @ Size - @ User - @ SHA1 - if( g.perm.Admin ){ - @ rcvid - } - @
    %h(zName) %h(zName) %s(zAge) %s(zSzName) %h(zLogin) %h(zHash) %d(rcvid) - }else{ - @ - } - } - @
    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 ){ + @
    + @ + @ + @ + @ + } + @ + if( isDeleted ){ + sqlite3_snprintf(sizeof(zSzName), zSzName, "Deleted"); + zHash = ""; + fullSize = 0; + @ + }else{ + approxSizeName(sizeof(zSzName), zSzName, fullSize); + iTotalSz += fullSize; + cnt++; + @ + } + @ + @ + @ + @ + if( g.perm.Admin ){ + if( rcvid ){ + @ + fossil_free(zAge); + } + db_finalize(&q); + if( n ){ + approxSizeName(sizeof(zSzName), zSzName, iTotalSz); + @ + @ + @
    Name + @ Age + @ Size + @ User + @ SHA1 + if( g.perm.Admin ){ + @ rcvid + } + @
    %h(zName) %h(zName) %s(zAge) %s(zSzName) %h(zLogin) %h(zHash) %d(rcvid) + }else{ + @ + } + } + @
    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