Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -1,1 +1,1 @@ -2.25 +2.24 Index: extsrc/shell.c ================================================================== --- extsrc/shell.c +++ extsrc/shell.c @@ -6002,17 +6002,17 @@ ** ** The query plan selected by seriesBestIndex is passed in the idxNum ** parameter. (idxStr is not used in this implementation.) idxNum ** is a bitmask showing which constraints are available: ** -** 0x01: start=VALUE -** 0x02: stop=VALUE -** 0x04: step=VALUE -** 0x08: descending order -** 0x10: ascending order -** 0x20: LIMIT VALUE -** 0x40: OFFSET VALUE +** 1: start=VALUE +** 2: stop=VALUE +** 4: step=VALUE +** +** Also, if bit 8 is set, that means that the series should be output +** in descending order rather than in ascending order. If bit 16 is +** set, then output must appear in ascending order. ** ** This routine should initialize the cursor and position it so that it ** is pointing at the first row, or pointing off the end of the table ** (so that seriesEof() will return true) if the table is empty. */ @@ -6022,48 +6022,30 @@ int argc, sqlite3_value **argv ){ series_cursor *pCur = (series_cursor *)pVtabCursor; int i = 0; (void)idxStrUnused; - if( idxNum & 0x01 ){ + if( idxNum & 1 ){ pCur->ss.iBase = sqlite3_value_int64(argv[i++]); }else{ pCur->ss.iBase = 0; } - if( idxNum & 0x02 ){ + if( idxNum & 2 ){ pCur->ss.iTerm = sqlite3_value_int64(argv[i++]); }else{ pCur->ss.iTerm = 0xffffffff; } - if( idxNum & 0x04 ){ + if( idxNum & 4 ){ pCur->ss.iStep = sqlite3_value_int64(argv[i++]); if( pCur->ss.iStep==0 ){ pCur->ss.iStep = 1; }else if( pCur->ss.iStep<0 ){ - if( (idxNum & 0x10)==0 ) idxNum |= 0x08; + if( (idxNum & 16)==0 ) idxNum |= 8; } }else{ pCur->ss.iStep = 1; } - if( idxNum & 0x20 ){ - sqlite3_int64 iLimit = sqlite3_value_int64(argv[i++]); - sqlite3_int64 iTerm; - if( idxNum & 0x40 ){ - sqlite3_int64 iOffset = sqlite3_value_int64(argv[i++]); - if( iOffset>0 ){ - pCur->ss.iBase += pCur->ss.iStep*iOffset; - } - } - if( iLimit>=0 ){ - iTerm = pCur->ss.iBase + (iLimit - 1)*pCur->ss.iStep; - if( pCur->ss.iStep<0 ){ - if( iTerm>pCur->ss.iTerm ) pCur->ss.iTerm = iTerm; - }else{ - if( iTermss.iTerm ) pCur->ss.iTerm = iTerm; - } - } - } for(i=0; iss.iBase = 1; @@ -6070,11 +6052,11 @@ pCur->ss.iTerm = 0; pCur->ss.iStep = 1; break; } } - if( idxNum & 0x08 ){ + if( idxNum & 8 ){ pCur->ss.isReversing = pCur->ss.iStep > 0; }else{ pCur->ss.isReversing = pCur->ss.iStep < 0; } setupSequence( &pCur->ss ); @@ -6090,85 +6072,54 @@ ** In this implementation idxNum is used to represent the ** query plan. idxStr is unused. ** ** The query plan is represented by bits in idxNum: ** -** 0x01 start = $value -- constraint exists -** 0x02 stop = $value -- constraint exists -** 0x04 step = $value -- constraint exists -** 0x08 output is in descending order -** 0x10 output is in ascending order -** 0x20 LIMIT $value -- constraint exists -** 0x40 OFFSET $value -- constraint exists +** (1) start = $value -- constraint exists +** (2) stop = $value -- constraint exists +** (4) step = $value -- constraint exists +** (8) output in descending order */ static int seriesBestIndex( sqlite3_vtab *pVTab, sqlite3_index_info *pIdxInfo ){ int i, j; /* Loop over constraints */ int idxNum = 0; /* The query plan bitmask */ -#ifndef ZERO_ARGUMENT_GENERATE_SERIES int bStartSeen = 0; /* EQ constraint seen on the START column */ -#endif int unusableMask = 0; /* Mask of unusable constraints */ int nArg = 0; /* Number of arguments that seriesFilter() expects */ - int aIdx[5]; /* Constraints on start, stop, step, LIMIT, OFFSET */ + int aIdx[3]; /* Constraints on start, stop, and step */ const struct sqlite3_index_constraint *pConstraint; /* This implementation assumes that the start, stop, and step columns ** are the last three columns in the virtual table. */ assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 ); assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 ); - aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = -1; + aIdx[0] = aIdx[1] = aIdx[2] = -1; pConstraint = pIdxInfo->aConstraint; for(i=0; inConstraint; i++, pConstraint++){ int iCol; /* 0 for start, 1 for stop, 2 for step */ int iMask; /* bitmask for those column */ - int op = pConstraint->op; - if( op>=SQLITE_INDEX_CONSTRAINT_LIMIT - && op<=SQLITE_INDEX_CONSTRAINT_OFFSET - ){ - if( pConstraint->usable==0 ){ - /* do nothing */ - }else if( op==SQLITE_INDEX_CONSTRAINT_LIMIT ){ - aIdx[3] = i; - idxNum |= 0x20; - }else{ - assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET ); - aIdx[4] = i; - idxNum |= 0x40; - } - continue; - } if( pConstraint->iColumniColumn - SERIES_COLUMN_START; assert( iCol>=0 && iCol<=2 ); iMask = 1 << iCol; -#ifndef ZERO_ARGUMENT_GENERATE_SERIES - if( iCol==0 && op==SQLITE_INDEX_CONSTRAINT_EQ ){ - bStartSeen = 1; - } -#endif + if( iCol==0 ) bStartSeen = 1; if( pConstraint->usable==0 ){ unusableMask |= iMask; continue; - }else if( op==SQLITE_INDEX_CONSTRAINT_EQ ){ + }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ idxNum |= iMask; aIdx[iCol] = i; } } - if( aIdx[3]==0 ){ - /* Ignore OFFSET if LIMIT is omitted */ - idxNum &= ~0x60; - aIdx[4] = 0; - } - for(i=0; i<5; i++){ + for(i=0; i<3; i++){ if( (j = aIdx[i])>=0 ){ pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg; - pIdxInfo->aConstraintUsage[j].omit = - !SQLITE_SERIES_CONSTRAINT_VERIFY || i>=3; + pIdxInfo->aConstraintUsage[j].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY; } } /* The current generate_column() implementation requires at least one ** argument (the START value). Legacy versions assumed START=0 if the ** first argument was omitted. Compile with -DZERO_ARGUMENT_GENERATE_SERIES @@ -6185,26 +6136,23 @@ /* The start, stop, and step columns are inputs. Therefore if there ** are unusable constraints on any of start, stop, or step then ** this plan is unusable */ return SQLITE_CONSTRAINT; } - if( (idxNum & 0x03)==0x03 ){ + if( (idxNum & 3)==3 ){ /* Both start= and stop= boundaries are available. This is the ** the preferred case */ pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0)); pIdxInfo->estimatedRows = 1000; if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){ if( pIdxInfo->aOrderBy[0].desc ){ - idxNum |= 0x08; + idxNum |= 8; }else{ - idxNum |= 0x10; + idxNum |= 16; } pIdxInfo->orderByConsumed = 1; } - }else if( (idxNum & 0x21)==0x21 ){ - /* We have start= and LIMIT */ - pIdxInfo->estimatedRows = 2500; }else{ /* If either boundary is missing, we have to generate a huge span ** of numbers. Make this case very expensive so that the query ** planner will work hard to avoid it. */ pIdxInfo->estimatedRows = 2147483647; @@ -7527,13 +7475,11 @@ ){ if( zFile==0 ) return 1; #if !defined(_WIN32) && !defined(WIN32) if( S_ISLNK(mode) ){ const char *zTo = (const char*)sqlite3_value_text(pData); - if( zTo==0 ) return 1; - unlink(zFile); - if( symlink(zTo, zFile)<0 ) return 1; + if( zTo==0 || symlink(zTo, zFile)<0 ) return 1; }else #endif { if( S_ISDIR(mode) ){ if( mkdir(zFile, mode) ){ @@ -7615,23 +7561,17 @@ times[1].tv_sec = mtime; if( utimensat(AT_FDCWD, zFile, times, AT_SYMLINK_NOFOLLOW) ){ return 1; } #else - /* Legacy unix. - ** - ** Do not use utimes() on a symbolic link - it sees through the link and - ** modifies the timestamps on the target. Or fails if the target does - ** not exist. */ - if( 0==S_ISLNK(mode) ){ - struct timeval times[2]; - times[0].tv_usec = times[1].tv_usec = 0; - times[0].tv_sec = time(0); - times[1].tv_sec = mtime; - if( utimes(zFile, times) ){ - return 1; - } + /* Legacy unix */ + struct timeval times[2]; + times[0].tv_usec = times[1].tv_usec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimes(zFile, times) ){ + return 1; } #endif } return 0; @@ -11699,27 +11639,26 @@ sqlite3_context *context, int argc, sqlite3_value **argv ){ uLong nData; - sqlite3_int64 sz; + uLongf sz; assert( argc==2 ); sz = sqlite3_value_int(argv[1]); if( sz<=0 || sz==(nData = sqlite3_value_bytes(argv[0])) ){ sqlite3_result_value(context, argv[0]); }else{ - uLongf szf = sz; const Bytef *pData= sqlite3_value_blob(argv[0]); Bytef *pOut = sqlite3_malloc(sz); if( pOut==0 ){ sqlite3_result_error_nomem(context); - }else if( Z_OK!=uncompress(pOut, &szf, pData, nData) ){ + }else if( Z_OK!=uncompress(pOut, &sz, pData, nData) ){ sqlite3_result_error(context, "error in uncompress()", -1); }else{ - sqlite3_result_blob(context, pOut, szf, SQLITE_TRANSIENT); + sqlite3_result_blob(context, pOut, sz, SQLITE_TRANSIENT); } sqlite3_free(pOut); } } @@ -15537,19 +15476,10 @@ #define DBDATA_PADDING_BYTES 100 typedef struct DbdataTable DbdataTable; typedef struct DbdataCursor DbdataCursor; -typedef struct DbdataBuffer DbdataBuffer; - -/* -** Buffer type. -*/ -struct DbdataBuffer { - u8 *aBuf; - sqlite3_int64 nBuf; -}; /* Cursor object */ struct DbdataCursor { sqlite3_vtab_cursor base; /* Base class. Must be first */ sqlite3_stmt *pStmt; /* For fetching database pages */ @@ -15562,11 +15492,11 @@ int bOnePage; /* True to stop after one page */ int szDb; sqlite3_int64 iRowid; /* Only for the sqlite_dbdata table */ - DbdataBuffer rec; + u8 *pRec; /* Buffer containing current record */ sqlite3_int64 nRec; /* Size of pRec[] in bytes */ sqlite3_int64 nHdr; /* Size of header in bytes */ int iField; /* Current field number */ u8 *pHdrPtr; u8 *pPtr; @@ -15607,35 +15537,10 @@ " pgno INTEGER," \ " child INTEGER," \ " schema TEXT HIDDEN" \ ")" -/* -** Ensure the buffer passed as the first argument is at least nMin bytes -** in size. If an error occurs while attempting to resize the buffer, -** SQLITE_NOMEM is returned. Otherwise, SQLITE_OK. -*/ -static int dbdataBufferSize(DbdataBuffer *pBuf, sqlite3_int64 nMin){ - if( nMin>pBuf->nBuf ){ - sqlite3_int64 nNew = nMin+16384; - u8 *aNew = (u8*)sqlite3_realloc64(pBuf->aBuf, nNew); - - if( aNew==0 ) return SQLITE_NOMEM; - pBuf->aBuf = aNew; - pBuf->nBuf = nNew; - } - return SQLITE_OK; -} - -/* -** Release the allocation managed by buffer pBuf. -*/ -static void dbdataBufferFree(DbdataBuffer *pBuf){ - sqlite3_free(pBuf->aBuf); - memset(pBuf, 0, sizeof(*pBuf)); -} - /* ** Connect to an sqlite_dbdata (pAux==0) or sqlite_dbptr (pAux!=0) virtual ** table. */ static int dbdataConnect( @@ -15772,11 +15677,12 @@ pCsr->iPgno = 1; pCsr->iCell = 0; pCsr->iField = 0; pCsr->bOnePage = 0; sqlite3_free(pCsr->aPage); - dbdataBufferFree(&pCsr->rec); + sqlite3_free(pCsr->pRec); + pCsr->pRec = 0; pCsr->aPage = 0; } /* ** Close an sqlite_dbdata or sqlite_dbptr cursor. @@ -16033,11 +15939,11 @@ }else{ return SQLITE_OK; } }else{ /* If there is no record loaded, load it now. */ - if( pCsr->nRec==0 ){ + if( pCsr->pRec==0 ){ int bHasRowid = 0; int nPointer = 0; sqlite3_int64 nPayload = 0; sqlite3_int64 nHdr = 0; int iHdr; @@ -16077,11 +15983,10 @@ if( bNextPage || iOff>pCsr->nPage || iOff<=iCellPtr ){ bNextPage = 1; }else{ iOff += dbdataGetVarintU32(&pCsr->aPage[iOff], &nPayload); if( nPayload>0x7fffff00 ) nPayload &= 0x3fff; - if( nPayload==0 ) nPayload = 1; } /* If this is a leaf intkey cell, load the rowid */ if( bHasRowid && !bNextPage && iOffnPage ){ iOff += dbdataGetVarint(&pCsr->aPage[iOff], &pCsr->iIntkey); @@ -16112,16 +16017,17 @@ }else{ /* Allocate space for payload. And a bit more to catch small buffer ** overruns caused by attempting to read a varint or similar from ** near the end of a corrupt record. */ - rc = dbdataBufferSize(&pCsr->rec, nPayload+DBDATA_PADDING_BYTES); - if( rc!=SQLITE_OK ) return rc; - assert( nPayload!=0 ); + pCsr->pRec = (u8*)sqlite3_malloc64(nPayload+DBDATA_PADDING_BYTES); + if( pCsr->pRec==0 ) return SQLITE_NOMEM; + memset(pCsr->pRec, 0, nPayload+DBDATA_PADDING_BYTES); + pCsr->nRec = nPayload; /* Load the nLocal bytes of payload */ - memcpy(pCsr->rec.aBuf, &pCsr->aPage[iOff], nLocal); + memcpy(pCsr->pRec, &pCsr->aPage[iOff], nLocal); iOff += nLocal; /* Load content from overflow pages */ if( nPayload>nLocal ){ sqlite3_int64 nRem = nPayload - nLocal; @@ -16135,64 +16041,63 @@ if( rc!=SQLITE_OK ) return rc; if( aOvfl==0 ) break; nCopy = U-4; if( nCopy>nRem ) nCopy = nRem; - memcpy(&pCsr->rec.aBuf[nPayload-nRem], &aOvfl[4], nCopy); + memcpy(&pCsr->pRec[nPayload-nRem], &aOvfl[4], nCopy); nRem -= nCopy; pgnoOvfl = get_uint32(aOvfl); sqlite3_free(aOvfl); } - nPayload -= nRem; } - memset(&pCsr->rec.aBuf[nPayload], 0, DBDATA_PADDING_BYTES); - pCsr->nRec = nPayload; - iHdr = dbdataGetVarintU32(pCsr->rec.aBuf, &nHdr); + iHdr = dbdataGetVarintU32(pCsr->pRec, &nHdr); if( nHdr>nPayload ) nHdr = 0; pCsr->nHdr = nHdr; - pCsr->pHdrPtr = &pCsr->rec.aBuf[iHdr]; - pCsr->pPtr = &pCsr->rec.aBuf[pCsr->nHdr]; + pCsr->pHdrPtr = &pCsr->pRec[iHdr]; + pCsr->pPtr = &pCsr->pRec[pCsr->nHdr]; pCsr->iField = (bHasRowid ? -1 : 0); } } }else{ pCsr->iField++; if( pCsr->iField>0 ){ sqlite3_int64 iType; - if( pCsr->pHdrPtr>=&pCsr->rec.aBuf[pCsr->nRec] + if( pCsr->pHdrPtr>=&pCsr->pRec[pCsr->nRec] || pCsr->iField>=DBDATA_MX_FIELD ){ bNextPage = 1; }else{ int szField = 0; pCsr->pHdrPtr += dbdataGetVarintU32(pCsr->pHdrPtr, &iType); szField = dbdataValueBytes(iType); - if( (pCsr->nRec - (pCsr->pPtr - pCsr->rec.aBuf))pPtr = &pCsr->rec.aBuf[pCsr->nRec]; + if( (pCsr->nRec - (pCsr->pPtr - pCsr->pRec))pPtr = &pCsr->pRec[pCsr->nRec]; }else{ pCsr->pPtr += szField; } } } } if( bNextPage ){ sqlite3_free(pCsr->aPage); + sqlite3_free(pCsr->pRec); pCsr->aPage = 0; - pCsr->nRec = 0; + pCsr->pRec = 0; if( pCsr->bOnePage ) return SQLITE_OK; pCsr->iPgno++; }else{ - if( pCsr->iField<0 || pCsr->pHdrPtr<&pCsr->rec.aBuf[pCsr->nHdr] ){ + if( pCsr->iField<0 || pCsr->pHdrPtr<&pCsr->pRec[pCsr->nHdr] ){ return SQLITE_OK; } /* Advance to the next cell. The next iteration of the loop will load ** the record and so on. */ - pCsr->nRec = 0; + sqlite3_free(pCsr->pRec); + pCsr->pRec = 0; pCsr->iCell++; } } } @@ -16378,16 +16283,16 @@ sqlite3_result_int(ctx, pCsr->iField); break; case DBDATA_COLUMN_VALUE: { if( pCsr->iField<0 ){ sqlite3_result_int64(ctx, pCsr->iIntkey); - }else if( &pCsr->rec.aBuf[pCsr->nRec] >= pCsr->pPtr ){ + }else if( &pCsr->pRec[pCsr->nRec] >= pCsr->pPtr ){ sqlite3_int64 iType; dbdataGetVarintU32(pCsr->pHdrPtr, &iType); dbdataValue( ctx, pCsr->enc, iType, pCsr->pPtr, - &pCsr->rec.aBuf[pCsr->nRec] - pCsr->pPtr + &pCsr->pRec[pCsr->nRec] - pCsr->pPtr ); } break; } } Index: extsrc/sqlite3.c ================================================================== --- extsrc/sqlite3.c +++ extsrc/sqlite3.c @@ -16,11 +16,11 @@ ** if you want a wrapper to interface SQLite with your choice of programming ** language. The code for the "sqlite3" command-line shell is also in a ** separate file. This file contains only code for the core SQLite library. ** ** The content in this amalgamation comes from Fossil check-in -** 42d67c6fed3a5f21d7b71515aca471ba61d3. +** 8c0f69e0e4ae0a446838cc193bfd4395fd25. */ #define SQLITE_CORE 1 #define SQLITE_AMALGAMATION 1 #ifndef SQLITE_PRIVATE # define SQLITE_PRIVATE static @@ -459,11 +459,11 @@ ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ #define SQLITE_VERSION "3.46.0" #define SQLITE_VERSION_NUMBER 3046000 -#define SQLITE_SOURCE_ID "2024-05-08 11:51:56 42d67c6fed3a5f21d7b71515aca471ba61d387e620022735a2e7929fa3a237cf" +#define SQLITE_SOURCE_ID "2024-04-18 16:11:01 8c0f69e0e4ae0a446838cc193bfd4395fd251f3c7659b35ac388e5a0a7650a66" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version sqlite3_sourceid ** @@ -12314,34 +12314,10 @@ ** ** In all cases, if an error occurs the state of the final contents of the ** changegroup is undefined. If no error occurs, SQLITE_OK is returned. */ SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); - -/* -** CAPI3REF: Add A Single Change To A Changegroup -** METHOD: sqlite3_changegroup -** -** This function adds the single change currently indicated by the iterator -** passed as the second argument to the changegroup object. The rules for -** adding the change are just as described for [sqlite3changegroup_add()]. -** -** If the change is successfully added to the changegroup, SQLITE_OK is -** returned. Otherwise, an SQLite error code is returned. -** -** The iterator must point to a valid entry when this function is called. -** If it does not, SQLITE_ERROR is returned and no change is added to the -** changegroup. Additionally, the iterator must not have been opened with -** the SQLITE_CHANGESETAPPLY_INVERT flag. In this case SQLITE_ERROR is also -** returned. -*/ -SQLITE_API int sqlite3changegroup_add_change( - sqlite3_changegroup*, - sqlite3_changeset_iter* -); - - /* ** CAPI3REF: Obtain A Composite Changeset From A Changegroup ** METHOD: sqlite3_changegroup ** @@ -14636,12 +14612,12 @@ #define TK_AGG_FUNCTION 168 #define TK_AGG_COLUMN 169 #define TK_TRUEFALSE 170 #define TK_ISNOT 171 #define TK_FUNCTION 172 -#define TK_UPLUS 173 -#define TK_UMINUS 174 +#define TK_UMINUS 173 +#define TK_UPLUS 174 #define TK_TRUTH 175 #define TK_REGISTER 176 #define TK_VECTOR 177 #define TK_SELECT_COLUMN 178 #define TK_IF_NULL_ROW 179 @@ -31969,11 +31945,11 @@ sqlite3_str_appendall(pAccum, pItem->zName); }else if( pItem->zAlias ){ sqlite3_str_appendall(pAccum, pItem->zAlias); }else{ Select *pSel = pItem->pSelect; - assert( pSel!=0 ); /* Because of tag-20240424-1 */ + assert( pSel!=0 ); if( pSel->selFlags & SF_NestedFrom ){ sqlite3_str_appendf(pAccum, "(join-%u)", pSel->selId); }else if( pSel->selFlags & SF_MultiValue ){ assert( !pItem->fg.isTabFunc && !pItem->fg.isIndexedBy ); sqlite3_str_appendf(pAccum, "%u-ROW VALUES CLAUSE", @@ -32793,18 +32769,16 @@ if( pItem->fg.isUsing ) n++; if( pItem->fg.isUsing ){ sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING"); } if( pItem->pSelect ){ - sqlite3TreeViewPush(&pView, i+1nSrc); if( pItem->pTab ){ Table *pTab = pItem->pTab; sqlite3TreeViewColumnList(pView, pTab->aCol, pTab->nCol, 1); } assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem->pSelect) ); sqlite3TreeViewSelect(pView, pItem->pSelect, (--n)>0); - sqlite3TreeViewPop(&pView); } if( pItem->fg.isTabFunc ){ sqlite3TreeViewExprList(pView, pItem->u1.pFuncArg, 0, "func-args:"); } sqlite3TreeViewPop(&pView); @@ -32904,11 +32878,11 @@ } if( p->pLimit ){ sqlite3TreeViewItem(pView, "LIMIT", (n--)>0); sqlite3TreeViewExpr(pView, p->pLimit->pLeft, p->pLimit->pRight!=0); if( p->pLimit->pRight ){ - sqlite3TreeViewItem(pView, "OFFSET", 0); + sqlite3TreeViewItem(pView, "OFFSET", (n--)>0); sqlite3TreeViewExpr(pView, p->pLimit->pRight, 0); sqlite3TreeViewPop(&pView); } sqlite3TreeViewPop(&pView); } @@ -84703,14 +84677,14 @@ int nRec, /* Size of buffer pRec in bytes */ int iCol, /* Column to extract */ sqlite3_value **ppVal /* OUT: Extracted value */ ){ u32 t = 0; /* a column type code */ - u32 nHdr; /* Size of the header in the record */ - u32 iHdr; /* Next unread header byte */ - i64 iField; /* Next unread data byte */ - u32 szField = 0; /* Size of the current data field */ + int nHdr; /* Size of the header in the record */ + int iHdr; /* Next unread header byte */ + int iField; /* Next unread data byte */ + int szField = 0; /* Size of the current data field */ int i; /* Column index */ u8 *a = (u8*)pRec; /* Typecast byte array */ Mem *pMem = *ppVal; /* Write result into this Mem object */ assert( iCol>0 ); @@ -97667,12 +97641,11 @@ ** row output from the sorter so that the row can be decomposed into ** individual columns using the OP_Column opcode. The OP_Column opcode ** is the only cursor opcode that works with a pseudo-table. ** ** P3 is the number of fields in the records that will be stored by -** the pseudo-table. If P2 is 0 or negative then the pseudo-cursor -** will return NULL for every column. +** the pseudo-table. */ case OP_OpenPseudo: { VdbeCursor *pCx; assert( pOp->p1>=0 ); @@ -105826,14 +105799,14 @@ break; } #ifdef SQLITE_ENABLE_STMT_SCANSTATUS case 9: /* nexec */ - sqlite3_result_int64(ctx, pOp->nExec); + sqlite3_result_int(ctx, pOp->nExec); break; case 10: /* ncycle */ - sqlite3_result_int64(ctx, pOp->nCycle); + sqlite3_result_int(ctx, pOp->nCycle); break; #else case 9: /* nexec */ case 10: /* ncycle */ sqlite3_result_int(ctx, 0); @@ -107222,12 +107195,11 @@ int op = pParse->eTriggerOp; assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT ); if( pParse->bReturning ){ if( (pNC->ncFlags & NC_UBaseReg)!=0 && ALWAYS(zTab==0 - || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0 - || isValidSchemaTableName(zTab, pParse->pTriggerTab, 0)) + || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0) ){ pExpr->iTable = op!=TK_DELETE; pTab = pParse->pTriggerTab; } }else if( op!=TK_DELETE && zTab && sqlite3StrICmp("new",zTab) == 0 ){ @@ -108584,11 +108556,10 @@ /* Recursively resolve names in all subqueries in the FROM clause */ if( pOuterNC ) pOuterNC->nNestedSelect++; for(i=0; ipSrc->nSrc; i++){ SrcItem *pItem = &p->pSrc->a[i]; - assert( pItem->zName!=0 || pItem->pSelect!=0 );/* Test of tag-20240424-1*/ if( pItem->pSelect && (pItem->pSelect->selFlags & SF_Resolved)==0 ){ int nRef = pOuterNC ? pOuterNC->nRef : 0; const char *zSavedContext = pParse->zAuthContext; if( pItem->zName ) pParse->zAuthContext = pItem->zName; @@ -110344,11 +110315,10 @@ ** Recursively delete an expression tree. */ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ assert( p!=0 ); assert( db!=0 ); -exprDeleteRestart: assert( !ExprUseUValue(p) || p->u.iValue>=0 ); assert( !ExprUseYWin(p) || !ExprUseYSub(p) ); assert( !ExprUseYWin(p) || p->y.pWin!=0 || db->mallocFailed ); assert( p->op!=TK_FUNCTION || !ExprUseYSub(p) ); #ifdef SQLITE_DEBUG @@ -110360,10 +110330,11 @@ } #endif if( !ExprHasProperty(p, (EP_TokenOnly|EP_Leaf)) ){ /* The Expr.x union is never used at the same time as Expr.pRight */ assert( (ExprUseXList(p) && p->x.pList==0) || p->pRight==0 ); + if( p->pLeft && p->op!=TK_SELECT_COLUMN ) sqlite3ExprDeleteNN(db, p->pLeft); if( p->pRight ){ assert( !ExprHasProperty(p, EP_WinFunc) ); sqlite3ExprDeleteNN(db, p->pRight); }else if( ExprUseXSelect(p) ){ assert( !ExprHasProperty(p, EP_WinFunc) ); @@ -110374,23 +110345,10 @@ if( ExprHasProperty(p, EP_WinFunc) ){ sqlite3WindowDelete(db, p->y.pWin); } #endif } - if( p->pLeft && p->op!=TK_SELECT_COLUMN ){ - Expr *pLeft = p->pLeft; - if( !ExprHasProperty(p, EP_Static) - && !ExprHasProperty(pLeft, EP_Static) - ){ - /* Avoid unnecessary recursion on unary operators */ - sqlite3DbNNFreeNN(db, p); - p = pLeft; - goto exprDeleteRestart; - }else{ - sqlite3ExprDeleteNN(db, pLeft); - } - } } if( !ExprHasProperty(p, EP_Static) ){ sqlite3DbNNFreeNN(db, p); } } @@ -111382,11 +111340,11 @@ || ExprHasProperty(pExpr, EP_WinFunc) ){ pWalker->eCode = 0; return WRC_Abort; } - return WRC_Prune; + return WRC_Continue; } /* ** These routines are Walker callbacks used to check expressions to @@ -124261,10 +124219,13 @@ if( p->tabFlags & TF_HasGenerated ){ sqlite3VdbeAddOp4(v, OP_SqlExec, 0x0001, 0, 0, sqlite3MPrintf(db, "SELECT*FROM\"%w\".\"%w\"", db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC); } + sqlite3VdbeAddOp4(v, OP_SqlExec, 0x0001, 0, 0, + sqlite3MPrintf(db, "PRAGMA \"%w\".integrity_check(%Q)", + db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC); } /* Add the table to the in-memory representation of the database. */ if( db->init.busy ){ @@ -140390,11 +140351,11 @@ pParse->nMem = 6; /* Set the maximum error count */ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; if( zRight ){ - if( sqlite3GetInt32(pValue->z, &mxErr) ){ + if( sqlite3GetInt32(zRight, &mxErr) ){ if( mxErr<=0 ){ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; } }else{ pObjTab = sqlite3LocateTable(pParse, 0, zRight, @@ -141598,11 +141559,10 @@ /* Clear all content from pragma virtual table cursor. */ static void pragmaVtabCursorClear(PragmaVtabCursor *pCsr){ int i; sqlite3_finalize(pCsr->pPragma); pCsr->pPragma = 0; - pCsr->iRowid = 0; for(i=0; iazArg); i++){ sqlite3_free(pCsr->azArg[i]); pCsr->azArg[i] = 0; } } @@ -145184,26 +145144,21 @@ memset(&sNC, 0, sizeof(sNC)); sNC.pSrcList = pSelect->pSrc; for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ const char *zType; i64 n; - int m = 0; - Select *pS2 = pSelect; pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT); p = a[i].pExpr; /* pCol->szEst = ... // Column size est for SELECT tables never used */ pCol->affinity = sqlite3ExprAffinity(p); - while( pCol->affinity<=SQLITE_AFF_NONE && pS2->pNext!=0 ){ - m |= sqlite3ExprDataType(pS2->pEList->a[i].pExpr); - pS2 = pS2->pNext; - pCol->affinity = sqlite3ExprAffinity(pS2->pEList->a[i].pExpr); - } if( pCol->affinity<=SQLITE_AFF_NONE ){ pCol->affinity = aff; } - if( pCol->affinity>=SQLITE_AFF_TEXT && (pS2->pNext || pS2!=pSelect) ){ - for(pS2=pS2->pNext; pS2; pS2=pS2->pNext){ + if( pCol->affinity>=SQLITE_AFF_TEXT && pSelect->pNext ){ + int m = 0; + Select *pS2; + for(m=0, pS2=pSelect->pNext; pS2; pS2=pS2->pNext){ m |= sqlite3ExprDataType(pS2->pEList->a[i].pExpr); } if( pCol->affinity==SQLITE_AFF_TEXT && (m&0x01)!=0 ){ pCol->affinity = SQLITE_AFF_BLOB; }else @@ -152622,76 +152577,10 @@ } } return pNew; } -/* If the Expr node is a subquery or an EXISTS operator or an IN operator that -** uses a subquery, and if the subquery is SF_Correlated, then mark the -** expression as EP_VarSelect. -*/ -static int sqlite3ReturningSubqueryVarSelect(Walker *NotUsed, Expr *pExpr){ - UNUSED_PARAMETER(NotUsed); - if( ExprUseXSelect(pExpr) - && (pExpr->x.pSelect->selFlags & SF_Correlated)!=0 - ){ - testcase( ExprHasProperty(pExpr, EP_VarSelect) ); - ExprSetProperty(pExpr, EP_VarSelect); - } - return WRC_Continue; -} - - -/* -** If the SELECT references the table pWalker->u.pTab, then do two things: -** -** (1) Mark the SELECT as as SF_Correlated. -** (2) Set pWalker->eCode to non-zero so that the caller will know -** that (1) has happened. -*/ -static int sqlite3ReturningSubqueryCorrelated(Walker *pWalker, Select *pSelect){ - int i; - SrcList *pSrc; - assert( pSelect!=0 ); - pSrc = pSelect->pSrc; - assert( pSrc!=0 ); - for(i=0; inSrc; i++){ - if( pSrc->a[i].pTab==pWalker->u.pTab ){ - testcase( pSelect->selFlags & SF_Correlated ); - pSelect->selFlags |= SF_Correlated; - pWalker->eCode = 1; - break; - } - } - return WRC_Continue; -} - -/* -** Scan the expression list that is the argument to RETURNING looking -** for subqueries that depend on the table which is being modified in the -** statement that is hosting the RETURNING clause (pTab). Mark all such -** subqueries as SF_Correlated. If the subqueries are part of an -** expression, mark the expression as EP_VarSelect. -** -** https://sqlite.org/forum/forumpost/2c83569ce8945d39 -*/ -static void sqlite3ProcessReturningSubqueries( - ExprList *pEList, - Table *pTab -){ - Walker w; - memset(&w, 0, sizeof(w)); - w.xExprCallback = sqlite3ExprWalkNoop; - w.xSelectCallback = sqlite3ReturningSubqueryCorrelated; - w.u.pTab = pTab; - sqlite3WalkExprList(&w, pEList); - if( w.eCode ){ - w.xExprCallback = sqlite3ReturningSubqueryVarSelect; - w.xSelectCallback = sqlite3SelectWalkNoop; - sqlite3WalkExprList(&w, pEList); - } -} - /* ** Generate code for the RETURNING trigger. Unlike other triggers ** that invoke a subprogram in the bytecode, the code for RETURNING ** is generated in-line. */ @@ -152724,11 +152613,10 @@ memset(&sFrom, 0, sizeof(sFrom)); sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0); sSelect.pSrc = &sFrom; sFrom.nSrc = 1; sFrom.a[0].pTab = pTab; - sFrom.a[0].zName = pTab->zName; /* tag-20240424-1 */ sFrom.a[0].iCursor = -1; sqlite3SelectPrep(pParse, &sSelect, 0); if( pParse->nErr==0 ){ assert( db->mallocFailed==0 ); sqlite3GenerateColumnNames(pParse, &sSelect); @@ -152751,11 +152639,10 @@ && ALWAYS(!db->mallocFailed) ){ int i; int nCol = pNew->nExpr; int reg = pParse->nMem+1; - sqlite3ProcessReturningSubqueries(pNew, pTab); pParse->nMem += nCol+2; pReturning->iRetReg = reg; for(i=0; ia[i].pExpr; assert( pCol!=0 ); /* Due to !db->mallocFailed ~9 lines above */ @@ -158704,31 +158591,10 @@ pLevel->regFilter = 0; pLevel->addrBrk = 0; } } -/* -** Loop pLoop is a WHERE_INDEXED level that uses at least one IN(...) -** operator. Return true if level pLoop is guaranteed to visit only one -** row for each key generated for the index. -*/ -static int whereLoopIsOneRow(WhereLoop *pLoop){ - if( pLoop->u.btree.pIndex->onError - && pLoop->nSkip==0 - && pLoop->u.btree.nEq==pLoop->u.btree.pIndex->nKeyCol - ){ - int ii; - for(ii=0; iiu.btree.nEq; ii++){ - if( pLoop->aLTerm[ii]->eOperator & (WO_IS|WO_ISNULL) ){ - return 0; - } - } - return 1; - } - return 0; -} - /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. */ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( @@ -159472,13 +159338,11 @@ ** a LEFT JOIN: */ assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0 ); } /* Record the instruction used to terminate the loop. */ - if( (pLoop->wsFlags & WHERE_ONEROW) - || (pLevel->u.in.nIn && regBignull==0 && whereLoopIsOneRow(pLoop)) - ){ + if( pLoop->wsFlags & WHERE_ONEROW ){ pLevel->op = OP_Noop; }else if( bRev ){ pLevel->op = OP_Prev; }else{ pLevel->op = OP_Next; @@ -160129,15 +159993,16 @@ if( pRight->fg.viaCoroutine ){ sqlite3VdbeAddOp3( v, OP_Null, 0, pRight->regResult, pRight->regResult + pRight->pSelect->pEList->nExpr-1 ); - } - sqlite3VdbeAddOp1(v, OP_NullRow, pWInfo->a[k].iTabCur); - iIdxCur = pWInfo->a[k].iIdxCur; - if( iIdxCur ){ - sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur); + }else{ + sqlite3VdbeAddOp1(v, OP_NullRow, pWInfo->a[k].iTabCur); + iIdxCur = pWInfo->a[k].iIdxCur; + if( iIdxCur ){ + sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur); + } } } if( (pTabItem->fg.jointype & JT_LTORJ)==0 ){ mAll |= pLoop->maskSelf; for(k=0; knTerm; k++){ @@ -161835,11 +161700,10 @@ ** will only be added if each of the child terms passes the ** (leftCursor==iCsr) test below. */ continue; } if( pWC->a[ii].leftCursor!=iCsr ) return; - if( pWC->a[ii].prereqRight!=0 ) return; } /* Check condition (5). Return early if it is not met. */ if( pOrderBy ){ for(ii=0; iinExpr; ii++){ @@ -161850,18 +161714,16 @@ } } /* All conditions are met. Add the terms to the where-clause object. */ assert( p->pLimit->op==TK_LIMIT ); - if( p->iOffset!=0 && (p->selFlags & SF_Compound)==0 ){ + whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft, + iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT); + if( p->iOffset>0 ){ whereAddLimitExpr(pWC, p->iOffset, p->pLimit->pRight, iCsr, SQLITE_INDEX_CONSTRAINT_OFFSET); } - if( p->iOffset==0 || (p->selFlags & SF_Compound)==0 ){ - whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft, - iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT); - } } } /* ** Initialize a preallocated WhereClause structure. @@ -162375,46 +162237,10 @@ return p; } return 0; } -/* -** Term pTerm is guaranteed to be a WO_IN term. It may be a component term -** of a vector IN expression of the form "(x, y, ...) IN (SELECT ...)". -** This function checks to see if the term is compatible with an index -** column with affinity idxaff (one of the SQLITE_AFF_XYZ values). If so, -** it returns a pointer to the name of the collation sequence (e.g. "BINARY" -** or "NOCASE") used by the comparison in pTerm. If it is not compatible -** with affinity idxaff, NULL is returned. -*/ -static SQLITE_NOINLINE const char *indexInAffinityOk( - Parse *pParse, - WhereTerm *pTerm, - u8 idxaff -){ - Expr *pX = pTerm->pExpr; - Expr inexpr; - - assert( pTerm->eOperator & WO_IN ); - - if( sqlite3ExprIsVector(pX->pLeft) ){ - int iField = pTerm->u.x.iField - 1; - inexpr.flags = 0; - inexpr.op = TK_EQ; - inexpr.pLeft = pX->pLeft->x.pList->a[iField].pExpr; - assert( ExprUseXSelect(pX) ); - inexpr.pRight = pX->x.pSelect->pEList->a[iField].pExpr; - pX = &inexpr; - } - - if( sqlite3IndexAffinityOk(pX, idxaff) ){ - CollSeq *pRet = sqlite3ExprCompareCollSeq(pParse, pX); - return pRet ? pRet->zName : sqlite3StrBINARY; - } - return 0; -} - /* ** Advance to the next WhereTerm that matches according to the criteria ** established when the pScan object was initialized by whereScanInit(). ** Return NULL if there are no more matching WhereTerms. */ @@ -162461,28 +162287,20 @@ } } if( (pTerm->eOperator & pScan->opMask)!=0 ){ /* Verify the affinity and collating sequence match */ if( pScan->zCollName && (pTerm->eOperator & WO_ISNULL)==0 ){ - const char *zCollName; + CollSeq *pColl; Parse *pParse = pWC->pWInfo->pParse; pX = pTerm->pExpr; - - if( (pTerm->eOperator & WO_IN) ){ - zCollName = indexInAffinityOk(pParse, pTerm, pScan->idxaff); - if( !zCollName ) continue; - }else{ - CollSeq *pColl; - if( !sqlite3IndexAffinityOk(pX, pScan->idxaff) ){ - continue; - } - assert(pX->pLeft); - pColl = sqlite3ExprCompareCollSeq(pParse, pX); - zCollName = pColl ? pColl->zName : sqlite3StrBINARY; - } - - if( sqlite3StrICmp(zCollName, pScan->zCollName) ){ + if( !sqlite3IndexAffinityOk(pX, pScan->idxaff) ){ + continue; + } + assert(pX->pLeft); + pColl = sqlite3ExprCompareCollSeq(pParse, pX); + if( pColl==0 ) pColl = pParse->db->pDfltColl; + if( sqlite3StrICmp(pColl->zName, pScan->zCollName) ){ continue; } } if( (pTerm->eOperator & (WO_EQ|WO_IS))!=0 && (pX = pTerm->pExpr->pRight, ALWAYS(pX!=0)) @@ -166130,25 +165948,10 @@ assert( pTerm->eOperator==WO_AUX || pTerm->eMatchOp==0 ); return pTerm->eMatchOp>=SQLITE_INDEX_CONSTRAINT_LIMIT && pTerm->eMatchOp<=SQLITE_INDEX_CONSTRAINT_OFFSET; } -/* -** Return true if the first nCons constraints in the pUsage array are -** marked as in-use (have argvIndex>0). False otherwise. -*/ -static int allConstraintsUsed( - struct sqlite3_index_constraint_usage *aUsage, - int nCons -){ - int ii; - for(ii=0; iipNew->iTab. This ** function marks a subset of those constraints usable, invokes the ** xBestIndex method and adds the returned plan to pBuilder. @@ -166285,24 +166088,17 @@ pIdxInfo->orderByConsumed = 0; pIdxInfo->idxFlags &= ~SQLITE_INDEX_SCAN_UNIQUE; *pbIn = 1; assert( (mExclude & WO_IN)==0 ); } - /* Unless pbRetryLimit is non-NULL, there should be no LIMIT/OFFSET - ** terms. And if there are any, they should follow all other terms. */ assert( pbRetryLimit || !isLimitTerm(pTerm) ); - assert( !isLimitTerm(pTerm) || i>=nConstraint-2 ); - assert( !isLimitTerm(pTerm) || i==nConstraint-1 || isLimitTerm(pTerm+1) ); - - if( isLimitTerm(pTerm) && (*pbIn || !allConstraintsUsed(pUsage, i)) ){ + if( isLimitTerm(pTerm) && *pbIn ){ /* If there is an IN(...) term handled as an == (separate call to ** xFilter for each value on the RHS of the IN) and a LIMIT or - ** OFFSET term handled as well, the plan is unusable. Similarly, - ** if there is a LIMIT/OFFSET and there are other unused terms, - ** the plan cannot be used. In these cases set variable *pbRetryLimit - ** to true to tell the caller to retry with LIMIT and OFFSET - ** disabled. */ + ** OFFSET term handled as well, the plan is unusable. Set output + ** variable *pbRetryLimit to true to tell the caller to retry with + ** LIMIT and OFFSET disabled. */ if( pIdxInfo->needToFreeIdxStr ){ sqlite3_free(pIdxInfo->idxStr); pIdxInfo->idxStr = 0; pIdxInfo->needToFreeIdxStr = 0; } @@ -168062,62 +167858,10 @@ } nSearch += pLoop->nOut; } } -/* -** Expression Node callback for sqlite3ExprCanReturnSubtype(). -** -** Only a function call is able to return a subtype. So if the node -** is not a function call, return WRC_Prune immediately. -** -** A function call is able to return a subtype if it has the -** SQLITE_RESULT_SUBTYPE property. -** -** Assume that every function is able to pass-through a subtype from -** one of its argument (using sqlite3_result_value()). Most functions -** are not this way, but we don't have a mechanism to distinguish those -** that are from those that are not, so assume they all work this way. -** That means that if one of its arguments is another function and that -** other function is able to return a subtype, then this function is -** able to return a subtype. -*/ -static int exprNodeCanReturnSubtype(Walker *pWalker, Expr *pExpr){ - int n; - FuncDef *pDef; - sqlite3 *db; - if( pExpr->op!=TK_FUNCTION ){ - return WRC_Prune; - } - assert( ExprUseXList(pExpr) ); - db = pWalker->pParse->db; - n = pExpr->x.pList ? pExpr->x.pList->nExpr : 0; - pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); - if( pDef==0 || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ - pWalker->eCode = 1; - return WRC_Prune; - } - return WRC_Continue; -} - -/* -** Return TRUE if expression pExpr is able to return a subtype. -** -** A TRUE return does not guarantee that a subtype will be returned. -** It only indicates that a subtype return is possible. False positives -** are acceptable as they only disable an optimization. False negatives, -** on the other hand, can lead to incorrect answers. -*/ -static int sqlite3ExprCanReturnSubtype(Parse *pParse, Expr *pExpr){ - Walker w; - memset(&w, 0, sizeof(w)); - w.pParse = pParse; - w.xExprCallback = exprNodeCanReturnSubtype; - sqlite3WalkExpr(&w, pExpr); - return w.eCode; -} - /* ** The index pIdx is used by a query and contains one or more expressions. ** In other words pIdx is an index on an expression. iIdxCur is the cursor ** number for the index and iDataCur is the cursor number for the corresponding ** table. @@ -168147,15 +167891,23 @@ pExpr = sqlite3ColumnExpr(pTab, &pTab->aCol[j]); }else{ continue; } if( sqlite3ExprIsConstant(0,pExpr) ) continue; - if( pExpr->op==TK_FUNCTION && sqlite3ExprCanReturnSubtype(pParse,pExpr) ){ + if( pExpr->op==TK_FUNCTION ){ /* Functions that might set a subtype should not be replaced by the ** value taken from an expression index since the index omits the ** subtype. https://sqlite.org/forum/forumpost/68d284c86b082c3e */ - continue; + int n; + FuncDef *pDef; + sqlite3 *db = pParse->db; + assert( ExprUseXList(pExpr) ); + n = pExpr->x.pList ? pExpr->x.pList->nExpr : 0; + pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); + if( pDef==0 || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ + continue; + } } p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); if( p==0 ) break; p->pIENext = pParse->pIdxEpr; #ifdef WHERETRACE_ENABLED @@ -169131,12 +168883,13 @@ int m, n; n = pSrc->regResult; assert( pSrc->pTab!=0 ); m = pSrc->pTab->nCol; sqlite3VdbeAddOp3(v, OP_Null, 0, n, n+m-1); + }else{ + sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur); } - sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur); } if( (ws & WHERE_INDEXED) || ((ws & WHERE_MULTI_OR) && pLevel->u.pCoveringIdx) ){ if( ws & WHERE_MULTI_OR ){ @@ -172835,12 +172588,12 @@ #define TK_AGG_FUNCTION 168 #define TK_AGG_COLUMN 169 #define TK_TRUEFALSE 170 #define TK_ISNOT 171 #define TK_FUNCTION 172 -#define TK_UPLUS 173 -#define TK_UMINUS 174 +#define TK_UMINUS 173 +#define TK_UPLUS 174 #define TK_TRUTH 175 #define TK_REGISTER 176 #define TK_VECTOR 177 #define TK_SELECT_COLUMN 178 #define TK_IF_NULL_ROW 179 @@ -173867,12 +173620,12 @@ 0, /* AGG_FUNCTION => nothing */ 0, /* AGG_COLUMN => nothing */ 0, /* TRUEFALSE => nothing */ 0, /* ISNOT => nothing */ 0, /* FUNCTION => nothing */ - 0, /* UPLUS => nothing */ 0, /* UMINUS => nothing */ + 0, /* UPLUS => nothing */ 0, /* TRUTH => nothing */ 0, /* REGISTER => nothing */ 0, /* VECTOR => nothing */ 0, /* SELECT_COLUMN => nothing */ 0, /* IF_NULL_ROW => nothing */ @@ -174136,12 +173889,12 @@ /* 168 */ "AGG_FUNCTION", /* 169 */ "AGG_COLUMN", /* 170 */ "TRUEFALSE", /* 171 */ "ISNOT", /* 172 */ "FUNCTION", - /* 173 */ "UPLUS", - /* 174 */ "UMINUS", + /* 173 */ "UMINUS", + /* 174 */ "UPLUS", /* 175 */ "TRUTH", /* 176 */ "REGISTER", /* 177 */ "VECTOR", /* 178 */ "SELECT_COLUMN", /* 179 */ "IF_NULL_ROW", @@ -176998,21 +176751,12 @@ case 215: /* expr ::= BITNOT expr */ yytestcase(yyruleno==215); {yymsp[-1].minor.yy454 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy454, 0);/*A-overwrites-B*/} break; case 216: /* expr ::= PLUS|MINUS expr */ { - Expr *p = yymsp[0].minor.yy454; - u8 op = yymsp[-1].major + (TK_UPLUS-TK_PLUS); - assert( TK_UPLUS>TK_PLUS ); - assert( TK_UMINUS == TK_MINUS + (TK_UPLUS - TK_PLUS) ); - if( p && p->op==TK_UPLUS ){ - p->op = op; - yymsp[-1].minor.yy454 = p; - }else{ - yymsp[-1].minor.yy454 = sqlite3PExpr(pParse, op, p, 0); - /*A-overwrites-B*/ - } + yymsp[-1].minor.yy454 = sqlite3PExpr(pParse, yymsp[-1].major==TK_PLUS ? TK_UPLUS : TK_UMINUS, yymsp[0].minor.yy454, 0); + /*A-overwrites-B*/ } break; case 217: /* expr ::= expr PTR expr */ { ExprList *pList = sqlite3ExprListAppend(pParse, 0, yymsp[-2].minor.yy454); @@ -228552,18 +228296,18 @@ ** sufficient either for the 'T' or 'P' byte and the varint that follows ** it, or for the two single byte values otherwise. */ p->rc = sessionInputBuffer(&p->in, 2); if( p->rc!=SQLITE_OK ) return p->rc; - sessionDiscardData(&p->in); - p->in.iCurrent = p->in.iNext; - /* If the iterator is already at the end of the changeset, return DONE. */ if( p->in.iNext>=p->in.nData ){ return SQLITE_DONE; } + sessionDiscardData(&p->in); + p->in.iCurrent = p->in.iNext; + op = p->in.aData[p->in.iNext++]; while( op=='T' || op=='P' ){ if( pbNew ) *pbNew = 1; p->bPatchset = (op=='P'); if( sessionChangesetReadTblhdr(p) ) return p->rc; @@ -230294,11 +230038,10 @@ */ struct sqlite3_changegroup { int rc; /* Error code */ int bPatch; /* True to accumulate patchsets */ SessionTable *pList; /* List of tables in current patch */ - SessionBuffer rec; sqlite3 *db; /* Configured by changegroup_schema() */ char *zDb; /* Configured by changegroup_schema() */ }; @@ -230593,132 +230336,112 @@ return rc; } /* -** Locate or create a SessionTable object that may be used to add the -** change currently pointed to by iterator pIter to changegroup pGrp. -** If successful, set output variable (*ppTab) to point to the table -** object and return SQLITE_OK. Otherwise, if some error occurs, return -** an SQLite error code and leave (*ppTab) set to NULL. -*/ -static int sessionChangesetFindTable( - sqlite3_changegroup *pGrp, - const char *zTab, - sqlite3_changeset_iter *pIter, - SessionTable **ppTab -){ - int rc = SQLITE_OK; - SessionTable *pTab = 0; - int nTab = (int)strlen(zTab); - u8 *abPK = 0; - int nCol = 0; - - *ppTab = 0; - sqlite3changeset_pk(pIter, &abPK, &nCol); - - /* Search the list for an existing table */ - for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){ - if( 0==sqlite3_strnicmp(pTab->zName, zTab, nTab+1) ) break; - } - - /* If one was not found above, create a new table now */ - if( !pTab ){ - SessionTable **ppNew; - - pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nTab+1); - if( !pTab ){ - return SQLITE_NOMEM; - } - memset(pTab, 0, sizeof(SessionTable)); - pTab->nCol = nCol; - pTab->abPK = (u8*)&pTab[1]; - memcpy(pTab->abPK, abPK, nCol); - pTab->zName = (char*)&pTab->abPK[nCol]; - memcpy(pTab->zName, zTab, nTab+1); - - if( pGrp->db ){ - pTab->nCol = 0; - rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb); - if( rc ){ - assert( pTab->azCol==0 ); - sqlite3_free(pTab); - return rc; - } - } - - /* The new object must be linked on to the end of the list, not - ** simply added to the start of it. This is to ensure that the - ** tables within the output of sqlite3changegroup_output() are in - ** the right order. */ - for(ppNew=&pGrp->pList; *ppNew; ppNew=&(*ppNew)->pNext); - *ppNew = pTab; - } - - /* Check that the table is compatible. */ - if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){ - rc = SQLITE_SCHEMA; - } - - *ppTab = pTab; - return rc; -} - -/* -** Add the change currently indicated by iterator pIter to the hash table -** belonging to changegroup pGrp. -*/ -static int sessionOneChangeToHash( - sqlite3_changegroup *pGrp, - sqlite3_changeset_iter *pIter, - int bRebase -){ - int rc = SQLITE_OK; - int nCol = 0; - int op = 0; - int iHash = 0; - int bIndirect = 0; - SessionChange *pChange = 0; - SessionChange *pExist = 0; - SessionChange **pp = 0; - SessionTable *pTab = 0; - u8 *aRec = &pIter->in.aData[pIter->in.iCurrent + 2]; - int nRec = (pIter->in.iNext - pIter->in.iCurrent) - 2; - - /* Ensure that only changesets, or only patchsets, but not a mixture - ** of both, are being combined. It is an error to try to combine a - ** changeset and a patchset. */ - if( pGrp->pList==0 ){ - pGrp->bPatch = pIter->bPatchset; - }else if( pIter->bPatchset!=pGrp->bPatch ){ - rc = SQLITE_ERROR; - } - - if( rc==SQLITE_OK ){ - const char *zTab = 0; - sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); - rc = sessionChangesetFindTable(pGrp, zTab, pIter, &pTab); - } - - if( rc==SQLITE_OK && nColnCol ){ - SessionBuffer *pBuf = &pGrp->rec; - rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, pBuf); - aRec = pBuf->aBuf; - nRec = pBuf->nBuf; - assert( pGrp->db ); - } - - if( rc==SQLITE_OK && sessionGrowHash(0, pIter->bPatchset, pTab) ){ - rc = SQLITE_NOMEM; - } - - if( rc==SQLITE_OK ){ - /* Search for existing entry. If found, remove it from the hash table. - ** Code below may link it back in. */ +** Add all changes in the changeset traversed by the iterator passed as +** the first argument to the changegroup hash tables. +*/ +static int sessionChangesetToHash( + sqlite3_changeset_iter *pIter, /* Iterator to read from */ + sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */ + int bRebase /* True if hash table is for rebasing */ +){ + u8 *aRec; + int nRec; + int rc = SQLITE_OK; + SessionTable *pTab = 0; + SessionBuffer rec = {0, 0, 0}; + + while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){ + const char *zNew; + int nCol; + int op; + int iHash; + int bIndirect; + SessionChange *pChange; + SessionChange *pExist = 0; + SessionChange **pp; + + /* Ensure that only changesets, or only patchsets, but not a mixture + ** of both, are being combined. It is an error to try to combine a + ** changeset and a patchset. */ + if( pGrp->pList==0 ){ + pGrp->bPatch = pIter->bPatchset; + }else if( pIter->bPatchset!=pGrp->bPatch ){ + rc = SQLITE_ERROR; + break; + } + + sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect); + if( !pTab || sqlite3_stricmp(zNew, pTab->zName) ){ + /* Search the list for a matching table */ + int nNew = (int)strlen(zNew); + u8 *abPK; + + sqlite3changeset_pk(pIter, &abPK, 0); + for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break; + } + if( !pTab ){ + SessionTable **ppTab; + + pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nNew+1); + if( !pTab ){ + rc = SQLITE_NOMEM; + break; + } + memset(pTab, 0, sizeof(SessionTable)); + pTab->nCol = nCol; + pTab->abPK = (u8*)&pTab[1]; + memcpy(pTab->abPK, abPK, nCol); + pTab->zName = (char*)&pTab->abPK[nCol]; + memcpy(pTab->zName, zNew, nNew+1); + + if( pGrp->db ){ + pTab->nCol = 0; + rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb); + if( rc ){ + assert( pTab->azCol==0 ); + sqlite3_free(pTab); + break; + } + } + + /* The new object must be linked on to the end of the list, not + ** simply added to the start of it. This is to ensure that the + ** tables within the output of sqlite3changegroup_output() are in + ** the right order. */ + for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext); + *ppTab = pTab; + } + + if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){ + rc = SQLITE_SCHEMA; + break; + } + } + + if( nColnCol ){ + assert( pGrp->db ); + rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, &rec); + if( rc ) break; + aRec = rec.aBuf; + nRec = rec.nBuf; + } + + if( sessionGrowHash(0, pIter->bPatchset, pTab) ){ + rc = SQLITE_NOMEM; + break; + } iHash = sessionChangeHash( pTab, (pIter->bPatchset && op==SQLITE_DELETE), aRec, pTab->nChange ); + + /* Search for existing entry. If found, remove it from the hash table. + ** Code below may link it back in. + */ for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){ int bPkOnly1 = 0; int bPkOnly2 = 0; if( pIter->bPatchset ){ bPkOnly1 = (*pp)->op==SQLITE_DELETE; @@ -230729,45 +230452,23 @@ *pp = (*pp)->pNext; pTab->nEntry--; break; } } - } - if( rc==SQLITE_OK ){ rc = sessionChangeMerge(pTab, bRebase, pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange ); - } - if( rc==SQLITE_OK && pChange ){ - pChange->pNext = pTab->apChange[iHash]; - pTab->apChange[iHash] = pChange; - pTab->nEntry++; - } - - if( rc==SQLITE_OK ) rc = pIter->rc; - return rc; -} - -/* -** Add all changes in the changeset traversed by the iterator passed as -** the first argument to the changegroup hash tables. -*/ -static int sessionChangesetToHash( - sqlite3_changeset_iter *pIter, /* Iterator to read from */ - sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */ - int bRebase /* True if hash table is for rebasing */ -){ - u8 *aRec; - int nRec; - int rc = SQLITE_OK; - - while( SQLITE_ROW==(sessionChangesetNext(pIter, &aRec, &nRec, 0)) ){ - rc = sessionOneChangeToHash(pGrp, pIter, bRebase); - if( rc!=SQLITE_OK ) break; - } - + if( rc ) break; + if( pChange ){ + pChange->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pChange; + pTab->nEntry++; + } + } + + sqlite3_free(rec.aBuf); if( rc==SQLITE_OK ) rc = pIter->rc; return rc; } /* @@ -230891,27 +230592,10 @@ } sqlite3changeset_finalize(pIter); return rc; } -/* -** Add a single change to a changeset-group. -*/ -SQLITE_API int sqlite3changegroup_add_change( - sqlite3_changegroup *pGrp, - sqlite3_changeset_iter *pIter -){ - if( pIter->in.iCurrent==pIter->in.iNext - || pIter->rc!=SQLITE_OK - || pIter->bInvert - ){ - /* Iterator does not point to any valid entry or is an INVERT iterator. */ - return SQLITE_ERROR; - } - return sessionOneChangeToHash(pGrp, pIter, 0); -} - /* ** Obtain a buffer containing a changeset representing the concatenation ** of all changesets added to the group so far. */ SQLITE_API int sqlite3changegroup_output( @@ -230957,11 +230641,10 @@ */ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){ if( pGrp ){ sqlite3_free(pGrp->zDb); sessionDeleteTable(0, pGrp->pList); - sqlite3_free(pGrp->rec.aBuf); sqlite3_free(pGrp); } } /* @@ -231359,11 +231042,10 @@ ** Destroy a rebaser object */ SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p){ if( p ){ sessionDeleteTable(0, p->grp.pList); - sqlite3_free(p->grp.rec.aBuf); sqlite3_free(p); } } /* @@ -252511,11 +252193,11 @@ int nArg, /* Number of args */ sqlite3_value **apUnused /* Function arguments */ ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2024-05-08 11:51:56 42d67c6fed3a5f21d7b71515aca471ba61d387e620022735a2e7929fa3a237cf", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2024-04-18 16:11:01 8c0f69e0e4ae0a446838cc193bfd4395fd251f3c7659b35ac388e5a0a7650a66", -1, SQLITE_TRANSIENT); } /* ** Return true if zName is the extension on one of the shadow tables used ** by this module. Index: extsrc/sqlite3.h ================================================================== --- extsrc/sqlite3.h +++ extsrc/sqlite3.h @@ -146,11 +146,11 @@ ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ #define SQLITE_VERSION "3.46.0" #define SQLITE_VERSION_NUMBER 3046000 -#define SQLITE_SOURCE_ID "2024-05-08 11:51:56 42d67c6fed3a5f21d7b71515aca471ba61d387e620022735a2e7929fa3a237cf" +#define SQLITE_SOURCE_ID "2024-04-18 16:11:01 8c0f69e0e4ae0a446838cc193bfd4395fd251f3c7659b35ac388e5a0a7650a66" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version sqlite3_sourceid ** @@ -12001,34 +12001,10 @@ ** ** In all cases, if an error occurs the state of the final contents of the ** changegroup is undefined. If no error occurs, SQLITE_OK is returned. */ SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); - -/* -** CAPI3REF: Add A Single Change To A Changegroup -** METHOD: sqlite3_changegroup -** -** This function adds the single change currently indicated by the iterator -** passed as the second argument to the changegroup object. The rules for -** adding the change are just as described for [sqlite3changegroup_add()]. -** -** If the change is successfully added to the changegroup, SQLITE_OK is -** returned. Otherwise, an SQLite error code is returned. -** -** The iterator must point to a valid entry when this function is called. -** If it does not, SQLITE_ERROR is returned and no change is added to the -** changegroup. Additionally, the iterator must not have been opened with -** the SQLITE_CHANGESETAPPLY_INVERT flag. In this case SQLITE_ERROR is also -** returned. -*/ -SQLITE_API int sqlite3changegroup_add_change( - sqlite3_changegroup*, - sqlite3_changeset_iter* -); - - /* ** CAPI3REF: Obtain A Composite Changeset From A Changegroup ** METHOD: sqlite3_changegroup ** Index: src/add.c ================================================================== --- src/add.c +++ src/add.c @@ -1057,11 +1057,11 @@ fossil_fatal("no check-out in which to rename files"); } if( g.argc<4 ){ usage("OLDNAME NEWNAME"); } - zDest = file_case_preferred_name(".",g.argv[g.argc-1]); + zDest = g.argv[g.argc-1]; db_begin_transaction(); if( g.argv[1][0]=='r' ){ /* i.e. "rename" */ moveFiles = 0; }else if( softFlag ){ moveFiles = 0; @@ -1143,15 +1143,14 @@ } db_finalize(&q); undo_reset(); db_end_transaction(0); if( moveFiles ) process_files_to_move(dryRunFlag); - fossil_free(zDest); } /* ** Function for stash_apply to be able to restore a file and indicate ** newly ADDED state. */ int stash_add_files_in_sfile(int vid){ return add_files_in_sfile(vid); } Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -593,106 +593,25 @@ if( command==STATUS ){ leaf_ambiguity_warning(vid, vid); } } -/* zIn is a string that is guaranteed to be followed by \n. Return -** a pointer to the next line after the \n. The returned value might -** point to the \000 string terminator. -*/ -static const char *next_line(const char *zIn){ - const char *z = strchr(zIn, '\n'); - assert( z!=0 ); - return z+1; -} - -/* zIn is a non-empty list of filenames in sorted order and separated -** by \n. There might be a cluster of lines that have the same n-character -** prefix. Return a pointer to the start of the last line of that -** cluster. The return value might be zIn if the first line of zIn is -** unique in its first n character. -*/ -static const char *last_line(const char *zIn, int n){ - const char *zLast = zIn; - const char *z; - while( 1 ){ - z = next_line(zLast); - if( z[0]==0 || (n>0 && strncmp(zIn, z, n)!=0) ) break; - zLast = z; - } - return zLast; -} - -/* -** Print a section of a filelist hierarchy graph. This is a helper -** routine for print_filelist_as_tree() below. -*/ -static const char *print_filelist_section( - const char *zIn, /* List of filenames, separated by \n */ - const char *zLast, /* Last filename in the list to print */ - const char *zPrefix, /* Prefix so put before each output line */ - int nDir /* Ignore this many characters of directory name */ -){ - /* Unicode box-drawing characters: U+251C, U+2514, U+2502 */ - const char *zENTRY = "\342\224\234\342\224\200\342\224\200 "; - const char *zLASTE = "\342\224\224\342\224\200\342\224\200 "; - const char *zCONTU = "\342\224\202 "; - const char *zBLANK = " "; - - while( zIn<=zLast ){ - int i; - for(i=nDir; zIn[i]!='\n' && zIn[i]!='/'; i++){} - if( zIn[i]=='/' ){ - char *zSubPrefix; - const char *zSubLast = last_line(zIn, i+1); - zSubPrefix = mprintf("%s%s", zPrefix, zSubLast==zLast ? zBLANK : zCONTU); - fossil_print("%s%s%.*s\n", zPrefix, zSubLast==zLast ? zLASTE : zENTRY, - i-nDir, &zIn[nDir]); - zIn = print_filelist_section(zIn, zSubLast, zSubPrefix, i+1); - fossil_free(zSubPrefix); - }else{ - fossil_print("%s%s%.*s\n", zPrefix, zIn==zLast ? zLASTE : zENTRY, - i-nDir, &zIn[nDir]); - zIn = next_line(zIn); - } - } - return zIn; -} - -/* -** Input blob pList is a list of filenames, one filename per line, -** in sorted order and with / directory separators. Output this list -** as a tree in a manner similar to the "tree" command on Linux. -*/ -static void print_filelist_as_tree(Blob *pList){ - char *zAll; - const char *zLast; - fossil_print("%s\n", g.zLocalRoot); - zAll = blob_str(pList); - if( zAll[0] ){ - zLast = last_line(zAll, 0); - print_filelist_section(zAll, zLast, "", 0); - } -} - /* ** Take care of -r version of ls command */ static void ls_cmd_rev( const char *zRev, /* Revision string given */ int verboseFlag, /* Verbose flag given */ int showAge, /* Age flag given */ - int timeOrder, /* Order by time flag given */ - int treeFmt /* Show output in the tree format */ + int timeOrder /* Order by time flag given */ ){ Stmt q; char *zOrderBy = "pathname COLLATE nocase"; char *zName; Blob where; int rid; int i; - Blob out; /* Handle given file names */ blob_zero(&where); for(i=2; i"), g.zLocalRoot); } - if( treeFmt ){ - print_filelist_as_tree(&report); - }else{ - blob_write_to_file(&report, "-"); - } + blob_write_to_file(&report, "-"); } blob_reset(&report); } /* Index: src/file.c ================================================================== --- src/file.c +++ src/file.c @@ -2409,13 +2409,10 @@ /* ** Count the number of objects (files and subdirectories) in a given ** directory. Return the count. Return -1 if the object is not a ** directory. -** -** This routine never counts the two "." and ".." special directory -** entries, even if the provided glob would match them. */ int file_directory_size(const char *zDir, const char *zGlob, int omitDotFiles){ void *zNative; DIR *d; int n = -1; @@ -2424,17 +2421,11 @@ if( d ){ struct dirent *pEntry; n = 0; while( (pEntry=readdir(d))!=0 ){ if( pEntry->d_name[0]==0 ) continue; - if( pEntry->d_name[0]=='.' && - (omitDotFiles - /* Skip the special "." and ".." entries. */ - || pEntry->d_name[1]==0 - || (pEntry->d_name[1]=='.' && pEntry->d_name[2]==0))){ - continue; - } + if( omitDotFiles && pEntry->d_name[0]=='.' ) continue; if( zGlob ){ char *zUtf8 = fossil_path_to_utf8(pEntry->d_name); int rc = sqlite3_strglob(zGlob, zUtf8); fossil_path_free(zUtf8); if( rc ) continue; Index: src/fossil.page.chat.js ================================================================== --- src/fossil.page.chat.js +++ src/fossil.page.chat.js @@ -127,11 +127,10 @@ fossil.FRK = ForceResizeKludge/*for debugging*/; const Chat = ForceResizeKludge.chat = (function(){ const cs = { // the "Chat" object (result of this function) verboseErrors: false /* if true then certain, mostly extraneous, error messages may be sent to the console. */, - playedBeep: false /* used for the beep-once setting */, e:{/*map of certain DOM elements.*/ messageInjectPoint: E1('#message-inject-point'), pageTitle: E1('head title'), loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */, inputWrapper: E1("#chat-input-area"), @@ -411,13 +410,10 @@ /* When set to a URI, it is assumed to be an audio file, which gets played when new messages arrive. When true, the first entry in the audio file selection list will be used. */ "audible-alert": true, - /* - */ - "beep-once": false, /* When on, show the list of "active" users - those from whom we have messages in the currently-loaded history (noting that deletions are also messages). */ "active-user-list": false, /* When on, the [active-user-list] setting includes the @@ -438,17 +434,11 @@ /** Plays a new-message notification sound IF the audible-alert setting is true, else this is a no-op. Returns this. */ playNewMessageSound: function f(){ if(f.uri){ - if(!cs.pageIsActive - /* ^^^ this could also arguably apply when chat is visible */ - && this.playedBeep && this.settings.getBool('beep-once',false)){ - return; - } try{ - this.playedBeep = true; if(!f.audio) f.audio = new Audio(f.uri); f.audio.currentTime = 0; f.audio.play(); }catch(e){ console.error("Audio playblack failed.", f.uri, e); @@ -461,11 +451,10 @@ repository-relative path which responds with an audio file). Pass a falsy value to disable audio alerts. Returns this. */ setNewMessageSound: function f(uri){ - this.playedBeep = false; delete this.playNewMessageSound.audio; this.playNewMessageSound.uri = uri; this.settings.set('audible-alert', uri); return this; }, @@ -791,19 +780,19 @@ cs._newResponseError = function(response){ return new Error([ "HTTP status ", response.status,": ",response.url,": ", response.statusText].join('')); }; - + /** Helper for reporting HTTP-level response errors via fetch(). If response.ok then response.json() is returned, else an Error is thrown. */ cs._fetchJsonOrError = function(response){ if(response.ok) return response.json(); else throw cs._newResponseError(response); }; - + /** Removes the given message ID from the local chat record and, if the message was posted by this user OR this user in an admin/setup, also submits it for removal on the remote. @@ -830,11 +819,10 @@ this.deleteMessageElem(id); } }; document.addEventListener('visibilitychange', function(ev){ cs.pageIsActive = ('visible' === document.visibilityState); - cs.playedBeep = false; if(cs.pageIsActive){ cs.e.pageTitle.innerText = cs.pageTitleOrig; if(document.activeElement!==cs.inputElement()){ /* An attempt to resolve usability problem reported by Joe M. where the Pale Moon browser is giving input focus to @@ -1751,14 +1739,10 @@ Chat.setNewMessageSound(v); F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+"."); if(v) setTimeout(()=>Chat.playNewMessageSound(), 0); } },{ - label: "Notify only once when away", - hint: "Notify only for the first message received after chat is hidden from view.", - boolValue: 'beep-once' - },{ label: "Play notification for your own messages", hint: "When enabled, the audio notification will be played for all messages, "+ "including your own. When disabled only messages from other users "+ "will trigger a notification.", boolValue: 'alert-own-messages' @@ -1938,11 +1922,11 @@ Chat.settings.set(k,v) /* fires event listeners so that the Config area checkboxes get in sync */; }); })(); - + (function(){/*set up message preview*/ const btnPreview = Chat.e.btnPreview; Chat.setPreviewText = function(t){ this.setCurrentView(this.e.viewPreview); this.e.previewContent.innerHTML = t; Index: src/graph.c ================================================================== --- src/graph.c +++ src/graph.c @@ -979,14 +979,11 @@ if( nTimewarp==0 ){ /* Priority bits: ** ** 0x04 The preferred branch ** - ** 0x02 A merge rail - a rail that contains merge lines into - ** the preferred branch. Only applies if a preferred branch - ** is defined. This improves the display of r=BRANCH - ** options to /timeline. + ** 0x02 A merge rail - a rail that contains merge lines ** ** 0x01 A rail that merges with the preferred branch */ u8 aPriority[GR_MAX_RAIL]; memset(aPriority, 0, p->mxRail+1); @@ -999,15 +996,10 @@ if( pRow->mergeIn[i] ) aPriority[i] |= 1; } if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1; } } - for(i=0; i<=p->mxRail; i++){ - if( p->mergeRail & BIT(i) ){ - aPriority[i] |= 2; - } - } }else{ j = 1; aPriority[0] = 4; for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ if( pRow->iRail==0 ){ @@ -1015,10 +1007,15 @@ if( pRow->mergeIn[i] ) aPriority[i] |= 1; } if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1; } } + } + for(i=0; i<=p->mxRail; i++){ + if( p->mergeRail & BIT(i) ){ + aPriority[i] |= 2; + } } #if 0 fprintf(stderr,"mergeRail: 0x%llx\n", p->mergeRail); fprintf(stderr,"Priority:"); Index: src/merge.c ================================================================== --- src/merge.c +++ src/merge.c @@ -274,35 +274,29 @@ } /* ** COMMAND: merge -** COMMAND: cherry-pick alias -** COMMAND: cherrypick +** COMMAND: cherry-pick ** -** Usage: %fossil merge ?OPTIONS? ?VERSION ...? -** Or: %fossil cherrypick ?OPTIONS? ?VERSION ...? +** Usage: %fossil merge ?OPTIONS? ?VERSION? ** ** The argument VERSION is a version that should be merged into the ** current check-out. All changes from VERSION back to the nearest ** common ancestor are merged. Except, if either of the --cherrypick ** or --backout options are used only the changes associated with the ** single check-in VERSION are merged. The --backout option causes ** the changes associated with VERSION to be removed from the current -** check-out rather than added. When invoked with the name -** "cherrypick" instead of "merge", this command works exactly like -** "merge --cherrypick". +** check-out rather than added. When invoked with the name +** cherry-pick, this command works exactly like merge --cherrypick. ** ** Files which are renamed in the merged-in branch will be renamed in ** the current check-out. ** ** If the VERSION argument is omitted, then Fossil attempts to find ** a recent fork on the current branch to merge. ** -** If there are multiple VERSION arguments, then each VERSION is merged -** (or cherrypicked) in the order that they appear on the command-line. -** ** Options: ** --backout Do a reverse cherrypick merge against VERSION. ** In other words, back out the changes that were ** added by VERSION. ** --baseline BASELINE Use BASELINE as the "pivot" of the merge instead @@ -343,14 +337,11 @@ int showVfileFlag; /* True if the --show-vfile flag is present */ int keepMergeFlag; /* True if --keep-merge-files is present */ int nConflict = 0; /* Number of conflicts seen */ int nOverwrite = 0; /* Number of unmanaged files overwritten */ char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */ - const char *zVersion; /* The VERSION argument */ - int bMultiMerge = 0; /* True if there are two or more VERSION arguments */ - int nMerge = 0; /* Number of prior merges processed */ - Stmt q; /* SQL statment used for merge processing */ + Stmt q; /* Notation: ** ** V The current check-out @@ -364,11 +355,11 @@ forceMissingFlag = find_option("force-missing",0,0)!=0; if( !verboseFlag ){ verboseFlag = find_option("detail",0,0)!=0; /* deprecated */ } pickFlag = find_option("cherrypick",0,0)!=0; - if('c'==*g.zCmdName /*called as cherrypick, possibly a short form*/){ + if('c'==*g.zCmdName/*called as cherry-pick, possibly a short form*/){ pickFlag = 1; } integrateFlag = find_option("integrate",0,0)!=0; backoutFlag = find_option("backout",0,0)!=0; zBinGlob = find_option("binary",0,1); @@ -410,40 +401,17 @@ if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, 1, "merge") ){ fossil_fatal("merge abandoned due to sync failure"); } } - /* - ** A "multi-merge" means two or more other check-ins are being merged - ** into the current check-in. In other words, there are two or more - ** VERSION arguments on the command-line. Multi-merge works by doing - ** the merges one by one, as long as there are no conflicts. At the - ** bottom of this routine, a jump is made back up to this point if there - ** are more merges yet to be done and no errors have yet been seen. - ** - ** Related variables: - ** bMultiMerge True if there are one or more merges yet to do - ** zVersion The name of the current checking being merged in - ** nMerge Number of prior merges - */ -merge_next_child: - - /* Find mid, the artifactID of the version to be merged into - ** the current check-out. - */ - if( g.argc>=3 ){ - int i; + /* Find mid, the artifactID of the version to be merged into the current + ** check-out */ + if( g.argc==3 ){ /* Mid is specified as an argument on the command-line */ - zVersion = g.argv[2]; - mid = name_to_typed_rid(zVersion, "ci"); + mid = name_to_typed_rid(g.argv[2], "ci"); if( mid==0 || !is_a_version(mid) ){ - fossil_fatal("not a version: %s", zVersion); - } - bMultiMerge = g.argc>3; - if( bMultiMerge ){ - for(i=3; ifromIsParent && (p->u.pTo==0 || p->u.pTo->fromIsParent) ){ /* Skip nodes where the parent is not on the path */ continue; } db_bind_int(&q1, ":mid", p->rid); - if( zDebug ){ - fossil_print("%s check-in %.16z %z rid %d\n", - zDebug, - db_text(0, "SELECT uuid FROM blob WHERE rid=%d", p->rid), - db_text(0, "SELECT date(mtime) FROM event WHERE objid=%d", p->rid), - p->rid - ); - } while( db_step(&q1)==SQLITE_ROW ){ fnid = db_column_int(&q1, 1); pfnid = db_column_int(&q1, 0); if( pfnid==0 ){ pfnid = fnid; @@ -489,12 +481,13 @@ int t = fnid; fnid = pfnid; pfnid = t; } if( zDebug ){ - fossil_print("%s %d[%z] -> %d[%z]\n", - zDebug, + fossil_print("%s at %d%s %.10z: %d[%z] -> %d[%z]\n", + zDebug, p->rid, p->fromIsParent ? ">" : "<", + db_text(0, "SELECT uuid FROM blob WHERE rid=%d", p->rid), pfnid, db_text(0, "SELECT name FROM filename WHERE fnid=%d", pfnid), fnid, db_text(0, "SELECT name FROM filename WHERE fnid=%d", fnid)); } Index: src/stash.c ================================================================== --- src/stash.c +++ src/stash.c @@ -581,26 +581,23 @@ stashid = stash_create(); undo_disable(); if( g.argc>=2 ){ int nFile = db_int(0, "SELECT count(*) FROM stashfile WHERE stashid=%d", stashid); - char **newArgv; + char **newArgv = fossil_malloc( sizeof(char*)*(nFile+2) ); int i = 2; Stmt q; - if( nFile==0 ){ - fossil_fatal("No modified files match the provided pattern."); - } - newArgv = fossil_malloc( sizeof(char*)*(nFile+2) ); db_prepare(&q,"SELECT origname FROM stashfile WHERE stashid=%d", stashid); while( db_step(&q)==SQLITE_ROW ){ newArgv[i++] = mprintf("%s%s", g.zLocalRoot, db_column_text(&q, 0)); } db_finalize(&q); newArgv[0] = g.argv[0]; newArgv[1] = 0; g.argv = newArgv; g.argc = nFile+2; + if( nFile==0 ) return; } /* Make sure the stash has committed before running the revert, so that ** we have a copy of the changes before deleting them. */ db_commit_transaction(); g.argv[1] = "revert"; Index: src/tag.c ================================================================== --- src/tag.c +++ src/tag.c @@ -468,17 +468,12 @@ ** (technote), and "tkt-" (ticket). The ** prefix is stripped from the resulting ** list unless --raw is provided. Ignored if ** ARTIFACT-ID is provided. ** --raw List raw names of tags -** --sep SEP Separator when concatenating values ** --tagtype TYPE List only tags of type TYPE, which must ** be one of: cancel, singleton, propagated -** --values List tag values -** If --sep is supplied, list all values of a tag on -** the same line, separated by SEP; otherwise list -** each value on its own line. ** ** The option --raw allows the manipulation of all types of tags ** used for various internal purposes in fossil. It also shows ** "cancel" tags for the "find" and "list" subcommands. You should ** not use this option to make changes unless you are sure what @@ -640,13 +635,10 @@ const int fRaw = find_option("raw","",0)!=0; const char *zTagType = find_option("tagtype","t",1); const int fInverse = find_option("inverse","v",0)!=0; const char *zTagPrefix = find_option("prefix","",1); int nTagType = fRaw ? -1 : 0; - int fValues = find_option("values","",0)!=0; - const char *zSep = find_option("sep","",1); - if( zTagType!=0 ){ int l = strlen(zTagType); if( strncmp(zTagType,"cancel",l)==0 ){ nTagType = 0; @@ -658,61 +650,29 @@ fossil_fatal("unrecognized tag type"); } } if( g.argc==3 ){ const int nTagPrefix = zTagPrefix ? (int)strlen(zTagPrefix) : 0; - if( !fValues ){ - db_prepare(&q, - "SELECT tagname FROM tag" - " WHERE EXISTS(SELECT 1 FROM tagxref" - " WHERE tagid=tag.tagid" - " AND tagtype%s%d)" - " AND CASE WHEN %Q IS NULL THEN 1 ELSE tagname GLOB %Q||'*' " - " END ORDER BY tagname COLLATE uintnocase", - zTagType!=0 ? (fInverse!=0?"<>":"=") : ">"/*safe-for-%s*/, - nTagType, zTagPrefix, zTagPrefix - ); - }else{ - if( zSep ){ - db_prepare(&q, - /* work around group_concat() with DISTINCT and custom separator */ - "SELECT tagname," - " rtrim(replace(group_concat(DISTINCT value||'@!' " - " ORDER BY value ASC), '@!,', %Q),'@!')" - " FROM tagxref, tag" - " WHERE tagxref.tagid=tag.tagid AND tagtype%s%d" - " AND CASE WHEN %Q IS NULL THEN 1 ELSE tagname GLOB %Q||'*' END" - " GROUP BY tagname" - " ORDER BY tagname COLLATE uintnocase", - ( zSep && strlen(zSep)>0 ) ? zSep : ",", - zTagType!=0 ? (fInverse!=0?"<>":"=") : ">"/*safe-for-%s*/, - nTagType, zTagPrefix, zTagPrefix - ); - }else{ - db_prepare(&q, - "SELECT DISTINCT tagname, value" - " FROM tagxref, tag" - " WHERE tagxref.tagid=tag.tagid AND tagtype%s%d" - " AND CASE WHEN %Q IS NULL THEN 1 ELSE tagname GLOB %Q||'*' END" - " ORDER BY tagname, value COLLATE uintnocase", - zTagType!=0 ? (fInverse!=0?"<>":"=") : ">"/*safe-for-%s*/, - nTagType, zTagPrefix, zTagPrefix - ); - } - } + db_prepare(&q, + "SELECT tagname FROM tag" + " WHERE EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=tag.tagid" + " AND tagtype%s%d)" + " AND CASE WHEN %Q IS NULL THEN 1 ELSE tagname GLOB %Q||'*' " + " END ORDER BY tagname COLLATE uintnocase", + zTagType!=0 ? (fInverse!=0?"<>":"=") : ">"/*safe-for-%s*/, + nTagType, zTagPrefix, zTagPrefix + ); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); - const char *zValue = db_column_text(&q, 1); - int nWidth = fValues ? 20 : 0; - const char *zzValue = (fValues && zValue) ? mprintf(" %s", zValue) : ""; if( fRaw ){ - fossil_print("%-*s%s\n", nWidth, zName, zzValue); + fossil_print("%s\n", zName); }else if( nTagPrefix>0 ){ assert(db_column_bytes(&q,0)>=nTagPrefix); - fossil_print("%-*s%s\n", nWidth, &zName[nTagPrefix], zzValue); + fossil_print("%s\n", &zName[nTagPrefix]); }else if( strncmp(zName, "sym-", 4)==0 ){ - fossil_print("%-*s%s\n", nWidth, &zName[4], zzValue); + fossil_print("%s\n", &zName[4]); } } db_finalize(&q); }else if( g.argc==4 ){ char const *zObjId = g.argv[3]; Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -1718,16 +1718,13 @@ ** n=COUNT Maximum number of events. "all" for no limit ** n1=COUNT Same as "n" but doesn't set the display-preference cookie ** Use "n1=COUNT" for a one-time display change ** p=CHECKIN Parents and ancestors of CHECKIN ** bt=PRIOR ... going back to PRIOR -** p2=CKIN2 ... use CKIN2 if CHECKIN is not found ** d=CHECKIN Children and descendants of CHECKIN -** d2=CKIN2 ... Use CKIN2 if CHECKIN is not found ** ft=DESCENDANT ... going forward to DESCENDANT ** dp=CHECKIN Same as 'd=CHECKIN&p=CHECKIN' -** dp2=CKIN2 Same as 'd2=CKIN2&p2=CKIN2' ** df=CHECKIN Same as 'd=CHECKIN&n1=all&nd'. Mnemonic: "Derived From" ** bt=CHECKIN "Back To". Show ancenstors going back to CHECKIN ** p=CX ... from CX back to time of CHECKIN ** from=CX ... shortest path from CX back to CHECKIN ** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN @@ -1847,11 +1844,10 @@ int to_rid = name_choice("to","to2",&zTo2); /* to= for path timelines */ int noMerge = P("shortest")==0; /* Follow merge links if shorter */ int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */ int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */ int pd_rid; - const char *zDPName; /* Value of p=, d=, or dp= params */ double rBefore, rAfter, rCirca; /* Boundary times */ const char *z; char *zOlderButton = 0; /* URL for Older button at the bottom */ char *zOlderButtonLabel = 0; /* Label for the Older Button */ char *zNewerButton = 0; /* URL for Newer button at the top */ @@ -1906,20 +1902,21 @@ }else{ nEntry = 50; } /* Query parameters d=, p=, and f= and variants */ - p_rid = name_choice("p","p2", &zDPName); - d_rid = name_choice("d","d2", &zDPName); + z = P("p"); + p_rid = z ? name_to_typed_rid(z,"ci") : 0; + z = P("d"); + d_rid = z ? name_to_typed_rid(z,"ci") : 0; z = P("f"); f_rid = z ? name_to_typed_rid(z,"ci") : 0; z = P("df"); if( z && (d_rid = name_to_typed_rid(z,"ci"))!=0 ){ nEntry = 0; useDividers = 0; cgi_replace_query_parameter("d",fossil_strdup(z)); - zDPName = z; } /* Undocumented query parameter to set JS mode */ builtin_set_js_delivery_mode(P("jsmode"),1); @@ -1939,11 +1936,11 @@ showCherrypicks = 0; } /* To view the timeline, must have permission to read project data. */ - pd_rid = name_choice("dp","dp2",&zDPName); + pd_rid = name_to_typed_rid(P("dp"),"ci"); if( pd_rid ){ p_rid = d_rid = pd_rid; } login_check_credentials(); if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum) @@ -2332,11 +2329,11 @@ db_multi_exec( "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)" ); zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", p_rid ? p_rid : d_rid); - zCiName = zDPName; + zCiName = pd_rid ? P("pd") : p_rid ? P("p") : P("d"); if( zCiName==0 ) zCiName = zUuid; blob_append_sql(&sql, " AND event.objid IN ok"); nd = 0; if( d_rid ){ Stmt s; Index: test/amend.test ================================================================== --- test/amend.test +++ test/amend.test @@ -296,19 +296,20 @@ set tc 0 foreach {tagt result} $tagtests { incr tc set tags {} set cancels {} - set t1exp [join $result ", "] + set t1exp "" set t2exp "*" set t3exp "*" set t5exp "*" foreach tag $tagt { lappend tags -tag $tag lappend cancels -cancel $tag } foreach res $result { + append t1exp ", $res" append t3exp "Add*tag*\"$res\".*" append t5exp "Cancel*tag*\"$res\".*" } foreach res [lsort -nocase $result] { append t2exp "sym-$res*" Index: test/merge5.test ================================================================== --- test/merge5.test +++ test/merge5.test @@ -23,11 +23,11 @@ } protOut { fossil sqlite3 --no-repository reacts badly to SQL dumped from repositories created from fossil older than version 2.0. } -#test merge5-sqlite3-issue false knownBug +test merge5-sqlite3-issue false knownBug test_cleanup_then_return # Verify the results of a check-out # proc checkout-test {testid expected_content} { Index: test/stash.test ================================================================== --- test/stash.test +++ test/stash.test @@ -168,14 +168,14 @@ ######## # fossil stash show|cat ?STASHID? ?DIFF-OPTIONS? # fossil stash [g]diff ?STASHID? ?DIFF-OPTIONS? -#fossil stash show -#test stash-1-show {[normalize_result] eq $diff_stash_1} -#fossil stash diff -#test stash-1-diff {[normalize_result] eq $diff_stash_1} knownBug +fossil stash show +test stash-1-show {[normalize_result] eq $diff_stash_1} +fossil stash diff +test stash-1-diff {[normalize_result] eq $diff_stash_1} knownBug ######## # fossil stash pop stash-test 2 pop { @@ -206,16 +206,16 @@ # fossil: ./src/delta.c:231: checksum: Assertion '...' failed. # Should be triggered by this stash-WY-1 test. fossil checkout --force c1 fossil clean fossil mv --soft f1 f1new -#stash-test WY-1 {-expectError save -m "Reported 2016-02-09"} { -# REVERT f1 -# DELETE f1new -#} -changes { -#} -addremove { -#} -exists {f1 f2 f3} -notexists {f1new} -knownbugs {-code -result} +stash-test WY-1 {-expectError save -m "Reported 2016-02-09"} { + REVERT f1 + DELETE f1new +} -changes { +} -addremove { +} -exists {f1 f2 f3} -notexists {f1new} -knownbugs {-code -result} # TODO: add tests that verify the saved stash is sensible. Possibly # by applying it and checking results. But until the SQLITE_CONSTRAINT # error is fixed, there is nothing stashed to test. @@ -296,17 +296,16 @@ }] -changes { RENAMED f2 -> f2n } -addremove { } -exists {f1 f2n} -notexists {f2} -fossil stash save -m f2n -#stash-test 3-2 {save -m f2n} { -# REVERT f2 -# DELETE f2n -#} -exists {f1 f2} -notexists {f2n} -knownbugs {-result} +stash-test 3-2 {save -m f2n} { + REVERT f2 + DELETE f2n +} -exists {f1 f2} -notexists {f2n} -knownbugs {-result} fossil stash show -#test stash-3-2-show-1 {![regexp {\sf1} $RESULT]} knownBug +test stash-3-2-show-1 {![regexp {\sf1} $RESULT]} knownBug test stash-3-2-show-2 {[regexp {\sf2n} $RESULT]} stash-test 3-2-pop {pop} { UPDATE f1 UPDATE f2n } -changes { Index: test/tester.tcl ================================================================== --- test/tester.tcl +++ test/tester.tcl @@ -308,11 +308,10 @@ comment-format \ crlf-glob \ crnl-glob \ default-csp \ default-perms \ - default-skin \ diff-binary \ diff-command \ dont-commit \ dont-push \ dotfiles \ Index: test/utf.test ================================================================== --- test/utf.test +++ test/utf.test @@ -35,11 +35,11 @@ set i 1 foreach {fileName result} $args { set fileName [file join $tempPath $fileName] fossil test-looks-like-utf $fileName set result [string map [list %TEMP% $tempPath \r\n \n] $result] - # if {$::RESULT ne $result} {puts stdout $::RESULT; exit} + # if {$::RESULT ne $result} {puts stdout $::RESULT} test utf-check-$testname.$i {$::RESULT eq $result} incr i } } @@ -17609,25 +17609,25 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -#utf-check 1179 utf-check-1179-2-129-1.jnk \ -#{File "%TEMP%/utf-check-1179-2-129-1.jnk" has 7 bytes. -#Starts with UTF-8 BOM: no -#Starts with UTF-16 BOM: yes -#Looks like UTF-8: yes -#Has flag LOOK_NUL: no -#Has flag LOOK_CR: no -#Has flag LOOK_LONE_CR: no -#Has flag LOOK_LF: no -#Has flag LOOK_LONE_LF: no -#Has flag LOOK_CRLF: no -#Has flag LOOK_LONG: no -#Has flag LOOK_INVALID: yes -#Has flag LOOK_ODD: no -#Has flag LOOK_SHORT: no} +utf-check 1179 utf-check-1179-2-129-1.jnk \ +{File "%TEMP%/utf-check-1179-2-129-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: yes +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} utf-check 1180 utf-check-1180-2-130-0.jnk \ {File "%TEMP%/utf-check-1180-2-130-0.jnk" has 4 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: yes @@ -24121,41 +24121,41 @@ Has flag LOOK_LONG: no Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} -#utf-check 1586 utf-check-1586-3-128-0.jnk \ -#{File "%TEMP%/utf-check-1586-3-128-0.jnk" has 6 bytes. -#Starts with UTF-8 BOM: no -#Starts with UTF-16 BOM: reversed -#Looks like UTF-16: no -#Has flag LOOK_NUL: yes -#Has flag LOOK_CR: no -#Has flag LOOK_LONE_CR: no -#Has flag LOOK_LF: no -#Has flag LOOK_LONE_LF: no -#Has flag LOOK_CRLF: no -#Has flag LOOK_LONG: no -#Has flag LOOK_INVALID: no -#Has flag LOOK_ODD: no -#Has flag LOOK_SHORT: no} - -#utf-check 1587 utf-check-1587-3-128-1.jnk \ -#{File "%TEMP%/utf-check-1587-3-128-1.jnk" has 7 bytes. -#Starts with UTF-8 BOM: no -#Starts with UTF-16 BOM: reversed -#Looks like UTF-8: no -#Has flag LOOK_NUL: yes -#Has flag LOOK_CR: no -#Has flag LOOK_LONE_CR: no -#Has flag LOOK_LF: no -#Has flag LOOK_LONE_LF: no -#Has flag LOOK_CRLF: no -#Has flag LOOK_LONG: no -#Has flag LOOK_INVALID: yes -#Has flag LOOK_ODD: no -#Has flag LOOK_SHORT: no} +utf-check 1586 utf-check-1586-3-128-0.jnk \ +{File "%TEMP%/utf-check-1586-3-128-0.jnk" has 6 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} + +utf-check 1587 utf-check-1587-3-128-1.jnk \ +{File "%TEMP%/utf-check-1587-3-128-1.jnk" has 7 bytes. +Starts with UTF-8 BOM: no +Starts with UTF-16 BOM: no +Looks like UTF-8: no +Has flag LOOK_NUL: yes +Has flag LOOK_CR: no +Has flag LOOK_LONE_CR: no +Has flag LOOK_LF: no +Has flag LOOK_LONE_LF: no +Has flag LOOK_CRLF: no +Has flag LOOK_LONG: no +Has flag LOOK_INVALID: yes +Has flag LOOK_ODD: no +Has flag LOOK_SHORT: no} utf-check 1588 utf-check-1588-3-129-0.jnk \ {File "%TEMP%/utf-check-1588-3-129-0.jnk" has 6 bytes. Starts with UTF-8 BOM: no Starts with UTF-16 BOM: no Index: www/changes.wiki ================================================================== --- www/changes.wiki +++ www/changes.wiki @@ -1,15 +1,15 @@ Change Log -

Changes for version 2.24 (2024-04-23)

+

Changes for version 2.24 (pending)

* Apache change work-around → As part of a security fix, the Apache webserver mod_cgi module has stopped relaying the Content-Length field of the HTTP reply header from the CGI programs back to the client in cases where the connection is to be closed and the client is able to read until end-of-file. The HTTP and CGI specs allow for this, though it does seem rude. - Older versions of Fossil were depending on the Content-Length header field + Older versions of Fossil was depending on the Content-Length header field being set. To work around the change to Apache, Fossil has been enhanced to cope with a missing Content-Length in the reply header. See [forum:/forumpost/12ac403fd29cfc89|forum thread 12ac403fd29cfc89]. * [./customskin.md|Skin] enhancements:
    @@ -41,11 +41,11 @@ path from X to Z is shown if Y cannot be found.
* Moved the /museum/repo.fossil file referenced from the Dockerfile from the ENTRYPOINT to the CMD part to allow use of --repolist mode. * The [/uvlist] page now shows the hash algorithm used so that - viewers don't have to guess. The hash is shown in a fixed-width + viewers don't have to guess. And the hash is shows in a fixed-width font for a more visually pleasing display. * If the [/help?cmd=autosync|autosync setting] contains keyword "all", the automatic sync occurs against all defined remote repositories, not just the default. * Markdown formatter: improved handling of indented fenced code blocks DELETED www/colordiff.md Index: www/colordiff.md ================================================================== --- www/colordiff.md +++ /dev/null @@ -1,104 +0,0 @@ -# Colorized Diffs - -The oldest and most widely compatible method to get colorized diffs in -Fossil is to use its web UI: - - fossil ui --page '/vdiff?from=2024-04-01&to=trunk' - -That syntax is admittedly awkward, and it doesn’t work where “from” is -the current checkout. Fortunately, there are many other methods to get -colorized `diff` output from Fossil. - - - -## `fossil diff -b` - -This produces a graphical diff in HTML format and sends it to the -user’s default web browser for viewing. - - - - -## `fossil diff -tk` - -You may be surprised to learn that the prior feature doesn’t use any of -the skinning or chrome from Fossil UI. This is because it is meant as a -functional replacement for an older method of getting colorized diffs, -“`fossil diff -tk`”. The feature was added after Apple stopped shipping -Tcl/Tk in macOS, and the third-party replacements often failed to work -correctly. It’s useful on other platforms as well. - - - -## Delegate to Git - -It may be considered sacrilege by some, but the most direct method for -those who want Git-like diff behavior may be to delegate diff behavior -to Git: - - fossil set --global diff-command 'git diff --no-index' - -The flag permits it to diff files that aren’t inside a Git repository. - - - -## GNU Diffutils - -If your system is from 2016 or later, it may include [GNU Diffutils][gd] -3.4 or newer, which lets you say: - - fossil set --global diff-command 'diff -dwu --color=always' - -You might think you could give `--color=auto`, but that fails with -commands like “`fossil diff | less`” since the pipe turns the output -non-interactive from the perspective of the underlying `diff` instance. - -This use of unconditional colorization means you will then have to -remember to add the `-i` option to `fossil diff` commands when producing -`patch(1)` files or piping diff output to another command that doesn’t -understand ANSI escape sequences, such as [`diffstat`][ds]. - -[ds]: https://invisible-island.net/diffstat/ -[gd]: https://www.gnu.org/software/diffutils/ - - - -## Bat, the Cat with Wings - -We can work around the `--color=auto` problem by switching from GNU less -as our pager to [`bat`][bat], as it can detect GNU diff output and -colorize it for you: - - fossil set --global diff-command 'diff -dwu --color=auto' - fossil diff | bat - -In this author’s experience, that works a lot more reliably than GNU -less’s ANSI color escape code handling, even when you set `LESS=-R` in -your environment. - -The reason we don’t leave the `diff-command` unset in this case is that -Fossil produces additional lines at the start which confuse the diff -format detection in `bat`. Forcing output through an external diff -command solves that. It also means that if you forget to pipe the output -through `bat`, you still get colorized output from GNU diff. - -[bat]: https://github.com/sharkdp/bat - - - -## Colordiff - -A method that works on systems predating GNU diffutils 3.4 or the -widespread availability of `bat` is to install [`colordiff`][cdurl], as -it is included in [many package systems][cdpkg], including ones for -outdated OSes. That then lets you say: - - fossil set --global diff-command 'colordiff -dwu' - -The main reason we list this alternative last is that it has the same -limitation of unconditional color as [above](#diffutils). - -[cdurl]: https://www.colordiff.org/ -[cdpkg]: https://repology.org/project/colordiff/versions - -
Index: www/embeddeddoc.wiki ================================================================== --- www/embeddeddoc.wiki +++ www/embeddeddoc.wiki @@ -241,11 +241,11 @@ When the symbolic name is a date and time, fossil shows the version of the document that was most recently checked in as of the date and time specified. So, for example, to see what the fossil website looked like at the beginning of 2010, enter: -
https://fossil-scm.org/home/doc/2010-01-01/www/index.wiki
+
https://fossil-scm.org/home/doc/2010-01-01/www/index.wiki
 
The file that encodes this document is stored in the fossil source tree under the name "www/embeddeddoc.wiki" and so that name forms the last part of the URL for this document. Index: www/gitusers.md ================================================================== --- www/gitusers.md +++ www/gitusers.md @@ -139,13 +139,13 @@ #### Closing a Check-Out The [`fossil close`][close] command dissociates a check-out directory from the -Fossil repository database, _nondestructively_ inverting [`fossil open`][open]. -(Contrast Git’s [closest alternative](#worktree), `git worktree remove`, which *is* -destructive!) This Fossil command does not +Fossil repository database, nondestructively inverting [`fossil open`][open]. +(Contrast [its closest inverse](#worktree), `git worktree remove`, which *is* +destructive in Git!) This Fossil command does not remove the managed files, and unless you give the `--force` option, it won’t let you close the check-out with uncommitted changes to those managed files. The `close` command also refuses to run without `--force` when you have @@ -300,15 +300,15 @@ Yet the constants are *not* equal because Fossil reads from a single disk file rather than visit potentially many files in sequence as Git must, so the OS’s buffer cache can result in [still better performance][35pct]. -Unlike Git’s log, Fossil’s timeline shows info across all branches by -default, a feature for maintaining better situational awareness. -It is possible to restrict the timeline to a single branch using `fossil timeline -b`. -Similarly, to restrict the timeline using the web UI equivalent, -click the name of a branch on the `/timeline` or `/brlist` page. (Or +Unlike Git’s log, Fossil’s timeline shows info across branches by +default, a feature for maintaining better situational awareness. Although the +`fossil timeline` command has no way to show a single branch’s commits, +you can restrict your view like this using the web UI equivalent by +clicking the name of a branch on the `/timeline` or `/brlist` page. (Or manually, by adding the `r=` query parameter.) Note that even in this case, the Fossil timeline still shows other branches where they interact with the one you’ve referenced in this way; again, better situational awareness. @@ -457,67 +457,28 @@ [cskin]: ./customskin.md [lsl]: https://chris.beams.io/posts/git-commit/#limit-50 - -## Fossil Never Auto-Commits - -There are several features in Git besides its `commit` command that -produce a new commit to the repository, and by default, they do it -without prompting or even asking for a commit message. These include -Git’s [`rebase`](#rebase), `merge`, and [`cherrypick`](#cpickrev) -commands, plus the [commit splitting](#comsplit) sub-feature -“`git commit -p`”. - -Fossil never does this, on firm philosophical grounds: we wish to be -able to test that each potentially repository-changing command does not -break anything _before_ freezing it immutably into the [Merkle -tree](./blockchain.md). Where Fossil has equivalent commands, they -modify the checkout tree alone, requiring a separate `commit` command -afterward, withheld until the user has satisfied themselves that the -command’s result is correct. - -We believe this is the main reason Git lacks an [autosync](#autosync) -feature: making push a manual step gives the user a chance to rewrite -history after triggering one of these autocommits locally, should the -automatic commit fail to work out as expected. Fossil chooses the -inverse path under the philosophy that commits are *commitments,* not -something you’re allowed to go back and rewrite later. - -This is also why there is no automatic commit message writing feature in -Fossil, as in these autocommit-triggering Git commands. The user is -meant to write the commit message by hand after they are sure it’s -correct, in clear-headed retrospective fashion. Having the tool do it -prospectively before one can test the result is simply backwards. - ## There Is No Staging Area Fossil omits the "Git index" or "staging area" concept. When you type "`fossil commit`" _all_ changes in your check-out are committed, -by default. There is no need for the "-a" option as with Git. +automatically. There is no need for the "-a" option as with Git. If you only want to commit _some_ of the changes, list the names of the files or directories you want to commit as arguments, like this: fossil commit src/feature.c doc/feature.md examples/feature Note that the last element is a directory name, meaning “any changed file under the `examples/feature` directory.” - - -## Commit Splitting - -[Git’s commit splitting features][gcspl] rely on -other features of Git that Fossil purposefully lacks, as covered in the -prior two sections: [autocommit](#autocommit) and [the staging -area](#staging). - -While there is no direct Fossil equivalent for +Although there are currently no +[commit splitting][gcspl] features in Fossil like `git add -p`, `git commit -p`, or `git rebase -i`, you can get the same effect by converting an uncommitted change set to a patch and then running it through [Patchouli]. Rather than use `fossil diff -i` to produce such a patch, a safer and @@ -533,17 +494,28 @@ hunks already applied. In this way, the combination of working tree and stash replaces the need for Git’s index feature. -We believe we know how to do commit splitting in a way compatible with -the Fossil philosophy, without following Git’s ill-considered design -leads. It amounts to automating the above process through an interactive -variant of [`fossil stash apply`][stash], as currently prototyped in the -third-party tool [`fnc`][fnc] and [its interactive `stash` -command][fncsta]. We merely await someone’s [contribution][ctrb] of this -feature into Fossil proper. +This also solves a philosophical problem with `git commit -p`: how can +you test that a split commit doesn’t break anything if you do it as part +of the commit action? Git’s lack of an autosync feature means you can +commit locally and then rewrite history if the commit doesn’t work out, +but we’d rather make changes only to the working directory, test the +changes there, and only commit once we’re sure it’s right. + +This also explains why we don’t have anything like `git rebase -i` +to split an existing commit: in Fossil, commits are *commitments,* not +something you’re allowed to go back and rewrite later. + +If someone does [contribute][ctrb] a commit splitting feature to Fossil, +we’d expect it to be an interactive form of +[`fossil stash apply`][stash], rather than follow Git’s ill-considered +design leads. + +Until then, there’s the third-party tool [`fnc`][fnc] and +[its interactive `stash` command][fncsta]. [ctrb]: https://fossil-scm.org/fossil/doc/trunk/www/contribute.wiki [fnc]: https://fnc.bsdbox.org/ [fncsta]: https://fnc.bsdbox.org/uv/doc/fnc.1.html#stash [gcspl]: https://git-scm.com/docs/git-rebase#_splitting_commits @@ -845,27 +817,57 @@ come up with a new objection that we haven’t already considered and addressed there. There is only one sub-feature of `git rebase` that is philosophically compatible with Fossil yet which currently has no functional equivalent. -We [covered this and the workaround for its lack](#comsplit) above. +We [covered this and the workaround for its lack](#csplit) above. [3]: ./rebaseharm.md - -## Colorized Diffs +## Colorized Diffs When you run `git diff` on an ANSI X3.64 capable terminal, it uses color to distinguish insertions, deletions, and replacements, but as of this writing, `fossil diff` produces traditional uncolored [unified diff format][udiff] output, suitable for producing a [patch file][pfile]. -There are [many methods](./colordiff.md) for solving this. +Nevertheless, there are multiple ways to get colorized diff output from +Fossil: + +* The most direct method is to delegate diff behavior back to Git: + + fossil set --global diff-command 'git diff --no-index' + + The flag permits it to diff files that aren’t inside a Git repository. + +* Another method is to install [`colordiff`][cdiff] — included in + [many package systems][cdpkg] — then say: + + fossil set --global diff-command 'colordiff -wu' + + Because this is unconditional, unlike `git diff --color=auto`, you + will then have to remember to add the `-i` option to `fossil diff` + commands when you want color disabled, such as when producing + `patch(1)` files or piping diff output to another command that + doesn’t understand ANSI escape sequences. There’s an example of this + [below](#dstat). + +* Use the Fossil web UI to diff existing commits. + +* To diff the current working directory contents against some parent + instead, Fossil’s diff command can produce + colorized HTML output and open it in the OS’s default web browser. + For example, `fossil diff -by` will show side-by-side diffs. + +* Use the older `fossil diff --tk` option to do much the same using + Tcl/Tk instead of a browser. + Viewed this way, Fossil doesn’t lack colorized diffs, it simply has *one* method where they *aren’t* colorized. +[cdpkg]: https://repology.org/project/colordiff/versions [pfile]: https://en.wikipedia.org/wiki/Patch_(Unix) [udiff]: https://en.wikipedia.org/wiki/Diff#Unified_format ## Showing Information About Commits @@ -934,17 +936,19 @@ a histogram in its default output mode rather than bare integers: fossil diff -i -v --from 2020-04-01 | diffstat We gave the `-i` flag in both cases to force Fossil to use its internal -diff implementation, bypassing [your local `diff-command` setting][dcset] -since the `--numstat` option has no effect when you have an external diff -command set. +diff implementation, bypassing [your local `diff-command` setting][dcset]. +The `--numstat` option has no effect when you have an external diff +command set, and some diff command alternatives like +[`colordiff`][cdiff] (covered [above](#cdiff)) produce output that confuses `diffstat`. If you leave off the `-v` flag in the second example, the `diffstat` output won’t include info about any newly-added files. +[cdiff]: https://www.colordiff.org/ [dcset]: https://fossil-scm.org/home/help?cmd=diff-command [dst]: https://invisible-island.net/diffstat/diffstat.html Index: www/hints.wiki ================================================================== --- www/hints.wiki +++ www/hints.wiki @@ -1,11 +1,9 @@ Fossil Tips And Usage Hints -A collection of useful hints and tricks in no particular order: - - 1. Click on two nodes of any timeline graph in succession - to see a diff between the two versions. + 1. Click on nodes of any timeline graph to see diffs between the two + selected versions. 2. Add the "--tk" option to "[/help?cmd=diff | fossil diff]" commands to get a pop-up window containing a complete side-by-side diff. (NB: The pop-up window is run as a separate Tcl/Tk process, so you will need to @@ -60,15 +58,5 @@ 10. When editing documentation to be checked in as managed files, you can preview what the documentation will look like by using the special "ckout" branch name in the "doc" URL while running "fossil ui". See the [./embeddeddoc.wiki | embedded documentation] for details. - - 11. Use the "[/help?cmd=ui|fossil ui /]" command to bring up a menu of - all of your local Fossil repositories in your web browser. - - 12. If you have a bunch of Fossil repositories living on a remote machine - that you are able to access using ssh using a command like - "ssh login@remote", then you can bring up a user interface for all - those remote repositories using the command: - "[/help?cmd=ui|fossil ui login@remote:/]". This works by tunneling - all HTTP traffic through SSH to the remote machine. Index: www/index.wiki ================================================================== --- www/index.wiki +++ www/index.wiki @@ -84,16 +84,16 @@ the repository are consistent prior to each commit. 8. Free and Open-Source — [../COPYRIGHT-BSD2.txt|2-clause BSD license].
-

Latest Release: 2.24 ([/timeline?c=version-2.24|2024-04-23])

+

Latest Release: 2.23 ([/timeline?c=version-2.23|2023-11-01])

* [/uv/download.html|Download] - * [./changes.wiki#v2_24|Change Summary] - * [/timeline?p=version-2.24&bt=version-2.23&y=ci|Check-ins in version 2.24] - * [/timeline?df=version-2.24&y=ci|Check-ins derived from the 2.24 release] + * [./changes.wiki#v2_23|Change Summary] + * [/timeline?p=version-2.23&bt=version-2.22&y=ci|Check-ins in version 2.23] + * [/timeline?df=version-2.23&y=ci|Check-ins derived from the 2.23 release] * [/timeline?t=release|Timeline of all past releases]

Quick Start

Index: www/mkindex.tcl ================================================================== --- www/mkindex.tcl +++ www/mkindex.tcl @@ -35,11 +35,10 @@ checkin.wiki {Check-in Checklist} childprojects.wiki {Child Projects} chroot.md {Server Chroot Jail} ckout-workflows.md {Check-Out Workflows} co-vs-up.md {Checkout vs Update} - colordiff.md {Colorized Diffs} copyright-release.html {Contributor License Agreement} concepts.wiki {Fossil Core Concepts} contact.md {Developer Contact Information} containers.md {OCI Containers} contribute.wiki {Contributing Code or Documentation To The Fossil Project} Index: www/permutedindex.html ================================================================== --- www/permutedindex.html +++ www/permutedindex.html @@ -35,11 +35,10 @@
  • Check-in Checklist
  • Check-Out Workflows
  • Checklist For Successful Open-Source Projects
  • Checkout vs Update
  • Child Projects
  • -
  • Colorized Diffs
  • Compiling and Installing Fossil
  • Contributing Code or Documentation To The Fossil Project
  • Contributor License Agreement
  • Creating, Syncing, and Deleting Private Branches
  • Custom Skins