diff options
Diffstat (limited to 'src/libs')
26 files changed, 1302 insertions, 1136 deletions
diff --git a/src/libs/3rdparty/googletest b/src/libs/3rdparty/googletest -Subproject b796f7d44681514f58a683a3a71ff17c94edb0c +Subproject f8d7d77c06936315286eb55f8de22cd23c18857 diff --git a/src/libs/3rdparty/sqlite/sqlite3.c b/src/libs/3rdparty/sqlite/sqlite3.c index 139ee46a6a..08c593e55c 100644 --- a/src/libs/3rdparty/sqlite/sqlite3.c +++ b/src/libs/3rdparty/sqlite/sqlite3.c @@ -1,6 +1,6 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.45.1. By combining all the individual C code files into this +** version 3.45.3. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -18,7 +18,7 @@ ** separate file. This file contains only code for the core SQLite library. ** ** The content in this amalgamation comes from Fossil check-in -** e876e51a0ed5c5b3126f52e532044363a014. +** 8653b758870e6ef0c98d46b3ace27849054a. */ #define SQLITE_CORE 1 #define SQLITE_AMALGAMATION 1 @@ -459,9 +459,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.45.1" -#define SQLITE_VERSION_NUMBER 3045001 -#define SQLITE_SOURCE_ID "2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a" +#define SQLITE_VERSION "3.45.3" +#define SQLITE_VERSION_NUMBER 3045003 +#define SQLITE_SOURCE_ID "2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -733,6 +733,8 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running. ** <li> The application must not modify the SQL statement text passed into ** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running. +** <li> The application must not dereference the arrays or string pointers +** passed as the 3rd and 4th callback parameters after it returns. ** </ul> */ SQLITE_API int sqlite3_exec( @@ -2454,6 +2456,22 @@ struct sqlite3_mem_methods { ** configuration setting is never used, then the default maximum is determined ** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that ** compile-time option is not set, then the default maximum is 1073741824. +** +** [[SQLITE_CONFIG_ROWID_IN_VIEW]] +** <dt>SQLITE_CONFIG_ROWID_IN_VIEW +** <dd>The SQLITE_CONFIG_ROWID_IN_VIEW option enables or disables the ability +** for VIEWs to have a ROWID. The capability can only be enabled if SQLite is +** compiled with -DSQLITE_ALLOW_ROWID_IN_VIEW, in which case the capability +** defaults to on. This configuration option queries the current setting or +** changes the setting to off or on. The argument is a pointer to an integer. +** If that integer initially holds a value of 1, then the ability for VIEWs to +** have ROWIDs is activated. If the integer initially holds zero, then the +** ability is deactivated. Any other initial value for the integer leaves the +** setting unchanged. After changes, if any, the integer is written with +** a 1 or 0, if the ability for VIEWs to have ROWIDs is on or off. If SQLite +** is compiled without -DSQLITE_ALLOW_ROWID_IN_VIEW (which is the usual and +** recommended case) then the integer is always filled with zero, regardless +** if its initial value. ** </dl> */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ @@ -2485,6 +2503,7 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */ #define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ #define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ +#define SQLITE_CONFIG_ROWID_IN_VIEW 30 /* int* */ /* ** CAPI3REF: Database Connection Configuration Options @@ -15097,6 +15116,7 @@ SQLITE_PRIVATE u32 sqlite3TreeTrace; ** 0x00010000 Beginning of DELETE/INSERT/UPDATE processing ** 0x00020000 Transform DISTINCT into GROUP BY ** 0x00040000 SELECT tree dump after all code has been generated +** 0x00080000 NOT NULL strength reduction */ /* @@ -18427,6 +18447,15 @@ struct Table { #define HasRowid(X) (((X)->tabFlags & TF_WithoutRowid)==0) #define VisibleRowid(X) (((X)->tabFlags & TF_NoVisibleRowid)==0) +/* Macro is true if the SQLITE_ALLOW_ROWID_IN_VIEW (mis-)feature is +** available. By default, this macro is false +*/ +#ifndef SQLITE_ALLOW_ROWID_IN_VIEW +# define ViewCanHaveRowid 0 +#else +# define ViewCanHaveRowid (sqlite3Config.mNoVisibleRowid==0) +#endif + /* ** Each foreign key constraint is an instance of the following structure. ** @@ -19346,6 +19375,7 @@ struct NameContext { #define NC_InAggFunc 0x020000 /* True if analyzing arguments to an agg func */ #define NC_FromDDL 0x040000 /* SQL text comes from sqlite_schema */ #define NC_NoSelect 0x080000 /* Do not descend into sub-selects */ +#define NC_Where 0x100000 /* Processing WHERE clause of a SELECT */ #define NC_OrderAgg 0x8000000 /* Has an aggregate other than count/min/max */ /* @@ -19369,6 +19399,7 @@ struct Upsert { Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */ Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */ u8 isDoUpdate; /* True for DO UPDATE. False for DO NOTHING */ + u8 isDup; /* True if 2nd or later with same pUpsertIdx */ /* Above this point is the parse tree for the ON CONFLICT clauses. ** The next group of fields stores intermediate data. */ void *pToFree; /* Free memory when deleting the Upsert object */ @@ -20140,6 +20171,11 @@ struct Sqlite3Config { #ifndef SQLITE_UNTESTABLE int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */ #endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + u32 mNoVisibleRowid; /* TF_NoVisibleRowid if the ROWID_IN_VIEW + ** feature is disabled. 0 if rowids can + ** occur in views. */ +#endif int bLocaltimeFault; /* True to fail localtime() calls */ int (*xAltLocaltime)(const void*,void*); /* Alternative localtime() routine */ int iOnceResetThreshold; /* When to reset OP_Once counters */ @@ -20595,10 +20631,13 @@ SQLITE_PRIVATE void sqlite3MutexWarnOnContention(sqlite3_mutex*); # define EXP754 (((u64)0x7ff)<<52) # define MAN754 ((((u64)1)<<52)-1) # define IsNaN(X) (((X)&EXP754)==EXP754 && ((X)&MAN754)!=0) +# define IsOvfl(X) (((X)&EXP754)==EXP754) SQLITE_PRIVATE int sqlite3IsNaN(double); +SQLITE_PRIVATE int sqlite3IsOverflow(double); #else -# define IsNaN(X) 0 -# define sqlite3IsNaN(X) 0 +# define IsNaN(X) 0 +# define sqlite3IsNaN(X) 0 +# define sqlite3IsOVerflow(X) 0 #endif /* @@ -21444,7 +21483,7 @@ SQLITE_PRIVATE With *sqlite3WithPush(Parse*, With*, u8); SQLITE_PRIVATE Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*); SQLITE_PRIVATE void sqlite3UpsertDelete(sqlite3*,Upsert*); SQLITE_PRIVATE Upsert *sqlite3UpsertDup(sqlite3*,Upsert*); -SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*); +SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*,Upsert*); SQLITE_PRIVATE void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int); SQLITE_PRIVATE Upsert *sqlite3UpsertOfIndex(Upsert*,Index*); SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert*); @@ -21834,6 +21873,9 @@ static const char * const sqlite3azCompileOpt[] = { "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), # endif #endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + "ALLOW_ROWID_IN_VIEW", +#endif #ifdef SQLITE_ALLOW_URI_AUTHORITY "ALLOW_URI_AUTHORITY", #endif @@ -22854,6 +22896,9 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { #ifndef SQLITE_UNTESTABLE 0, /* xTestCallback */ #endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + 0, /* mNoVisibleRowid. 0 == allow rowid-in-view */ +#endif 0, /* bLocaltimeFault */ 0, /* xAltLocaltime */ 0x7ffffffe, /* iOnceResetThreshold */ @@ -31309,6 +31354,7 @@ SQLITE_API void sqlite3_str_vappendf( if( xtype==etFLOAT ){ iRound = -precision; }else if( xtype==etGENERIC ){ + if( precision==0 ) precision = 1; iRound = precision; }else{ iRound = precision+1; @@ -34640,6 +34686,19 @@ SQLITE_PRIVATE int sqlite3IsNaN(double x){ } #endif /* SQLITE_OMIT_FLOATING_POINT */ +#ifndef SQLITE_OMIT_FLOATING_POINT +/* +** Return true if the floating point value is NaN or +Inf or -Inf. +*/ +SQLITE_PRIVATE int sqlite3IsOverflow(double x){ + int rc; /* The value return */ + u64 y; + memcpy(&y,&x,sizeof(y)); + rc = IsOvfl(y); + return rc; +} +#endif /* SQLITE_OMIT_FLOATING_POINT */ + /* ** Compute a string length that is limited to what can be stored in ** lower 30 bits of a 32-bit signed integer. @@ -35199,6 +35258,9 @@ do_atof_calc: u64 s2; rr[0] = (double)s; s2 = (u64)rr[0]; +#if defined(_MSC_VER) && _MSC_VER<1700 + if( s2==0x8000000000000000LL ){ s2 = 2*(u64)(0.5*rr[0]); } +#endif rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); if( e>0 ){ while( e>=100 ){ @@ -35641,7 +35703,7 @@ SQLITE_PRIVATE void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRou assert( p->n>0 ); assert( p->n<sizeof(p->zBuf) ); p->iDP = p->n + exp; - if( iRound<0 ){ + if( iRound<=0 ){ iRound = p->iDP - iRound; if( iRound==0 && p->zBuf[i+1]>='5' ){ iRound = 1; @@ -53262,6 +53324,14 @@ SQLITE_API unsigned char *sqlite3_serialize( pOut = 0; }else{ sz = sqlite3_column_int64(pStmt, 0)*szPage; + if( sz==0 ){ + sqlite3_reset(pStmt); + sqlite3_exec(db, "BEGIN IMMEDIATE; COMMIT;", 0, 0, 0); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW ){ + sz = sqlite3_column_int64(pStmt, 0)*szPage; + } + } if( piSize ) *piSize = sz; if( mFlags & SQLITE_SERIALIZE_NOCOPY ){ pOut = 0; @@ -63785,7 +63855,7 @@ SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager *pPager){ ** This will be either the rollback journal or the WAL file. */ SQLITE_PRIVATE sqlite3_file *sqlite3PagerJrnlFile(Pager *pPager){ -#if SQLITE_OMIT_WAL +#ifdef SQLITE_OMIT_WAL return pPager->jfd; #else return pPager->pWal ? sqlite3WalFile(pPager->pWal) : pPager->jfd; @@ -77088,7 +77158,10 @@ static int fillInCell( n = nHeader + nPayload; testcase( n==3 ); testcase( n==4 ); - if( n<4 ) n = 4; + if( n<4 ){ + n = 4; + pPayload[nPayload] = 0; + } *pnSize = n; assert( nSrc<=nPayload ); testcase( nSrc<nPayload ); @@ -79534,7 +79607,10 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( if( flags & BTREE_PREFORMAT ){ rc = SQLITE_OK; szNew = p->pBt->nPreformatSize; - if( szNew<4 ) szNew = 4; + if( szNew<4 ){ + szNew = 4; + newCell[3] = 0; + } if( ISAUTOVACUUM(p->pBt) && szNew>pPage->maxLocal ){ CellInfo info; pPage->xParseCell(pPage, newCell, &info); @@ -79596,7 +79672,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( }else if( loc<0 && pPage->nCell>0 ){ assert( pPage->leaf ); idx = ++pCur->ix; - pCur->curFlags &= ~BTCF_ValidNKey; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); }else{ assert( pPage->leaf ); } @@ -79626,7 +79702,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( */ if( pPage->nOverflow ){ assert( rc==SQLITE_OK ); - pCur->curFlags &= ~(BTCF_ValidNKey); + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); rc = balance(pCur); /* Must make sure nOverflow is reset to zero even if the balance() @@ -88379,6 +88455,23 @@ static void serialGet( pMem->flags = IsNaN(x) ? MEM_Null : MEM_Real; } } +static int serialGet7( + const unsigned char *buf, /* Buffer to deserialize from */ + Mem *pMem /* Memory cell to write value into */ +){ + u64 x = FOUR_BYTE_UINT(buf); + u32 y = FOUR_BYTE_UINT(buf+4); + x = (x<<32) + y; + assert( sizeof(x)==8 && sizeof(pMem->u.r)==8 ); + swapMixedEndianFloat(x); + memcpy(&pMem->u.r, &x, sizeof(x)); + if( IsNaN(x) ){ + pMem->flags = MEM_Null; + return 1; + } + pMem->flags = MEM_Real; + return 0; +} SQLITE_PRIVATE void sqlite3VdbeSerialGet( const unsigned char *buf, /* Buffer to deserialize from */ u32 serial_type, /* Serial type to deserialize */ @@ -89058,7 +89151,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( }else if( serial_type==0 ){ rc = -1; }else if( serial_type==7 ){ - sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); + serialGet7(&aKey1[d1], &mem1); rc = -sqlite3IntFloatCompare(pRhs->u.i, mem1.u.r); }else{ i64 lhs = vdbeRecordDecodeInt(serial_type, &aKey1[d1]); @@ -89083,14 +89176,18 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( }else if( serial_type==0 ){ rc = -1; }else{ - sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); if( serial_type==7 ){ - if( mem1.u.r<pRhs->u.r ){ + if( serialGet7(&aKey1[d1], &mem1) ){ + rc = -1; /* mem1 is a NaN */ + }else if( mem1.u.r<pRhs->u.r ){ rc = -1; }else if( mem1.u.r>pRhs->u.r ){ rc = +1; + }else{ + assert( rc==0 ); } }else{ + sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); rc = sqlite3IntFloatCompare(mem1.u.i, pRhs->u.r); } } @@ -89160,7 +89257,14 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( /* RHS is null */ else{ serial_type = aKey1[idx1]; - rc = (serial_type!=0 && serial_type!=10); + if( serial_type==0 + || serial_type==10 + || (serial_type==7 && serialGet7(&aKey1[d1], &mem1)!=0) + ){ + assert( rc==0 ); + }else{ + rc = 1; + } } if( rc!=0 ){ @@ -94858,7 +94962,9 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ } } }else if( affinity==SQLITE_AFF_TEXT && ((flags1 | flags3) & MEM_Str)!=0 ){ - if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ + if( (flags1 & MEM_Str)!=0 ){ + pIn1->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal); + }else if( (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn1->flags & MEM_Int ); testcase( pIn1->flags & MEM_Real ); testcase( pIn1->flags & MEM_IntReal ); @@ -94867,7 +94973,9 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask); if( NEVER(pIn1==pIn3) ) flags3 = flags1 | MEM_Str; } - if( (flags3 & MEM_Str)==0 && (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ + if( (flags3 & MEM_Str)!=0 ){ + pIn3->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal); + }else if( (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn3->flags & MEM_Int ); testcase( pIn3->flags & MEM_Real ); testcase( pIn3->flags & MEM_IntReal ); @@ -106212,6 +106320,8 @@ static void resolveAlias( assert( iCol>=0 && iCol<pEList->nExpr ); pOrig = pEList->a[iCol].pExpr; assert( pOrig!=0 ); + assert( !ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) ); + if( pExpr->pAggInfo ) return; db = pParse->db; pDup = sqlite3ExprDup(db, pOrig, 0); if( db->mallocFailed ){ @@ -106599,8 +106709,37 @@ static int lookupName( } } if( 0==cnt && VisibleRowid(pTab) ){ + /* pTab is a potential ROWID match. Keep track of it and match + ** the ROWID later if that seems appropriate. (Search for "cntTab" + ** to find related code.) Only allow a ROWID match if there is + ** a single ROWID match candidate. + */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + /* In SQLITE_ALLOW_ROWID_IN_VIEW mode, allow a ROWID match + ** if there is a single VIEW candidate or if there is a single + ** non-VIEW candidate plus multiple VIEW candidates. In other + ** words non-VIEW candidate terms take precedence over VIEWs. + */ + if( cntTab==0 + || (cntTab==1 + && ALWAYS(pMatch!=0) + && ALWAYS(pMatch->pTab!=0) + && (pMatch->pTab->tabFlags & TF_Ephemeral)!=0 + && (pTab->tabFlags & TF_Ephemeral)==0) + ){ + cntTab = 1; + pMatch = pItem; + }else{ + cntTab++; + } +#else + /* The (much more common) non-SQLITE_ALLOW_ROWID_IN_VIEW case is + ** simpler since we require exactly one candidate, which will + ** always be a non-VIEW + */ cntTab++; pMatch = pItem; +#endif } } if( pMatch ){ @@ -106726,13 +106865,13 @@ static int lookupName( ** Perhaps the name is a reference to the ROWID */ if( cnt==0 - && cntTab==1 + && cntTab>=1 && pMatch && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0 && sqlite3IsRowid(zCol) && ALWAYS(VisibleRowid(pMatch->pTab) || pMatch->fg.isNestedFrom) ){ - cnt = 1; + cnt = cntTab; if( pMatch->fg.isNestedFrom==0 ) pExpr->iColumn = -1; pExpr->affExpr = SQLITE_AFF_INTEGER; } @@ -107097,6 +107236,19 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ ** resolved. This prevents "column" from being counted as having been ** referenced, which might prevent a SELECT from being erroneously ** marked as correlated. + ** + ** 2024-03-28: Beware of aggregates. A bare column of aggregated table + ** can still evaluate to NULL even though it is marked as NOT NULL. + ** Example: + ** + ** CREATE TABLE t1(a INT NOT NULL); + ** SELECT a, a IS NULL, a IS NOT NULL, count(*) FROM t1; + ** + ** The "a IS NULL" and "a IS NOT NULL" expressions cannot be optimized + ** here because at the time this case is hit, we do not yet know whether + ** or not t1 is being aggregated. We have to assume the worst and omit + ** the optimization. The only time it is safe to apply this optimization + ** is within the WHERE clause. */ case TK_NOTNULL: case TK_ISNULL: { @@ -107107,19 +107259,36 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ anRef[i] = p->nRef; } sqlite3WalkExpr(pWalker, pExpr->pLeft); - if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){ - testcase( ExprHasProperty(pExpr, EP_OuterON) ); - assert( !ExprHasProperty(pExpr, EP_IntValue) ); - pExpr->u.iValue = (pExpr->op==TK_NOTNULL); - pExpr->flags |= EP_IntValue; - pExpr->op = TK_INTEGER; + if( IN_RENAME_OBJECT ) return WRC_Prune; + if( sqlite3ExprCanBeNull(pExpr->pLeft) ){ + /* The expression can be NULL. So the optimization does not apply */ + return WRC_Prune; + } - for(i=0, p=pNC; p && i<ArraySize(anRef); p=p->pNext, i++){ - p->nRef = anRef[i]; + for(i=0, p=pNC; p; p=p->pNext, i++){ + if( (p->ncFlags & NC_Where)==0 ){ + return WRC_Prune; /* Not in a WHERE clause. Unsafe to optimize. */ } - sqlite3ExprDelete(pParse->db, pExpr->pLeft); - pExpr->pLeft = 0; } + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x80000 ){ + sqlite3DebugPrintf( + "NOT NULL strength reduction converts the following to %d:\n", + pExpr->op==TK_NOTNULL + ); + sqlite3ShowExpr(pExpr); + } +#endif /* TREETRACE_ENABLED */ + pExpr->u.iValue = (pExpr->op==TK_NOTNULL); + pExpr->flags |= EP_IntValue; + pExpr->op = TK_INTEGER; + for(i=0, p=pNC; p && i<ArraySize(anRef); p=p->pNext, i++){ + p->nRef = anRef[i]; + } + sqlite3ExprDelete(pParse->db, pExpr->pLeft); + pExpr->pLeft = 0; return WRC_Prune; } @@ -108019,7 +108188,9 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; } + sNC.ncFlags |= NC_Where; if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort; + sNC.ncFlags &= ~NC_Where; /* Resolve names in table-valued-function arguments */ for(i=0; i<p->pSrc->nSrc; i++){ @@ -108558,9 +108729,10 @@ SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr *pExpr){ assert( pExpr->x.pList->nExpr>0 ); assert( pExpr->op==TK_FUNCTION ); pExpr = pExpr->x.pList->a[0].pExpr; - }else{ - assert( pExpr->op==TK_COLLATE ); + }else if( pExpr->op==TK_COLLATE ){ pExpr = pExpr->pLeft; + }else{ + break; } } return pExpr; @@ -111079,9 +111251,12 @@ SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){ return 0; case TK_COLUMN: assert( ExprUseYTab(p) ); - return ExprHasProperty(p, EP_CanBeNull) || - NEVER(p->y.pTab==0) || /* Reference to column of index on expr */ - (p->iColumn>=0 + return ExprHasProperty(p, EP_CanBeNull) + || NEVER(p->y.pTab==0) /* Reference to column of index on expr */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + || (p->iColumn==XN_ROWID && IsView(p->y.pTab)) +#endif + || (p->iColumn>=0 && p->y.pTab->aCol!=0 /* Possible due to prior error */ && ALWAYS(p->iColumn<p->y.pTab->nCol) && p->y.pTab->aCol[p->iColumn].notNull==0); @@ -123572,9 +123747,12 @@ SQLITE_PRIVATE void sqlite3CreateView( ** on a view, even though views do not have rowids. The following flag ** setting fixes this problem. But the fix can be disabled by compiling ** with -DSQLITE_ALLOW_ROWID_IN_VIEW in case there are legacy apps that - ** depend upon the old buggy behavior. */ -#ifndef SQLITE_ALLOW_ROWID_IN_VIEW - p->tabFlags |= TF_NoVisibleRowid; + ** depend upon the old buggy behavior. The ability can also be toggled + ** using sqlite3_config(SQLITE_CONFIG_ROWID_IN_VIEW,...) */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + p->tabFlags |= sqlite3Config.mNoVisibleRowid; /* Optional. Allow by default */ +#else + p->tabFlags |= TF_NoVisibleRowid; /* Never allow rowid in view */ #endif sqlite3TwoPartName(pParse, pName1, pName2, &pName); @@ -128947,13 +129125,13 @@ SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue){ double r1, r2; const char *zVal; r1 = sqlite3_value_double(pValue); - sqlite3_str_appendf(pStr, "%!.15g", r1); + sqlite3_str_appendf(pStr, "%!0.15g", r1); zVal = sqlite3_str_value(pStr); if( zVal ){ sqlite3AtoF(zVal, &r2, pStr->nChar, SQLITE_UTF8); if( r1!=r2 ){ sqlite3_str_reset(pStr); - sqlite3_str_appendf(pStr, "%!.20e", r1); + sqlite3_str_appendf(pStr, "%!0.20e", r1); } } break; @@ -129255,7 +129433,7 @@ static void replaceFunc( } if( zPattern[0]==0 ){ assert( sqlite3_value_type(argv[1])!=SQLITE_NULL ); - sqlite3_result_value(context, argv[0]); + sqlite3_result_text(context, (const char*)zStr, nStr, SQLITE_TRANSIENT); return; } nPattern = sqlite3_value_bytes(argv[1]); @@ -129738,7 +129916,7 @@ static void sumFinalize(sqlite3_context *context){ if( p->approx ){ if( p->ovrfl ){ sqlite3_result_error(context,"integer overflow",-1); - }else if( !sqlite3IsNaN(p->rErr) ){ + }else if( !sqlite3IsOverflow(p->rErr) ){ sqlite3_result_double(context, p->rSum+p->rErr); }else{ sqlite3_result_double(context, p->rSum); @@ -129755,7 +129933,7 @@ static void avgFinalize(sqlite3_context *context){ double r; if( p->approx ){ r = p->rSum; - if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; + if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr; }else{ r = (double)(p->iSum); } @@ -129769,7 +129947,7 @@ static void totalFinalize(sqlite3_context *context){ if( p ){ if( p->approx ){ r = p->rSum; - if( !sqlite3IsNaN(p->rErr) ) r += p->rErr; + if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr; }else{ r = (double)(p->iSum); } @@ -133175,7 +133353,7 @@ SQLITE_PRIVATE void sqlite3Insert( pNx->iDataCur = iDataCur; pNx->iIdxCur = iIdxCur; if( pNx->pUpsertTarget ){ - if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx) ){ + if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx, pUpsert) ){ goto insert_cleanup; } } @@ -135067,7 +135245,10 @@ static int xferOptimization( } } #ifndef SQLITE_OMIT_CHECK - if( pDest->pCheck && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) ){ + if( pDest->pCheck + && (db->mDbFlags & DBFLAG_Vacuum)==0 + && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) + ){ return 0; /* Tables have different CHECK constraints. Ticket #2252 */ } #endif @@ -139474,31 +139655,7 @@ SQLITE_PRIVATE void sqlite3Pragma( int mxCol; /* Maximum non-virtual column number */ if( pObjTab && pObjTab!=pTab ) continue; - if( !IsOrdinaryTable(pTab) ){ -#ifndef SQLITE_OMIT_VIRTUALTABLE - sqlite3_vtab *pVTab; - int a1; - if( !IsVirtual(pTab) ) continue; - if( pTab->nCol<=0 ){ - const char *zMod = pTab->u.vtab.azArg[0]; - if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue; - } - sqlite3ViewGetColumnNames(pParse, pTab); - if( pTab->u.vtab.p==0 ) continue; - pVTab = pTab->u.vtab.p->pVtab; - if( NEVER(pVTab==0) ) continue; - if( NEVER(pVTab->pModule==0) ) continue; - if( pVTab->pModule->iVersion<4 ) continue; - if( pVTab->pModule->xIntegrity==0 ) continue; - sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick); - pTab->nTabRef++; - sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF); - a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v); - integrityCheckResultRow(v); - sqlite3VdbeJumpHere(v, a1); -#endif - continue; - } + if( !IsOrdinaryTable(pTab) ) continue; if( isQuick || HasRowid(pTab) ){ pPk = 0; r2 = 0; @@ -139633,6 +139790,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** is REAL, we have to load the actual data using OP_Column ** to reliably determine if the value is a NULL. */ sqlite3VdbeAddOp3(v, OP_Column, p1, p3, 3); + sqlite3ColumnDefault(v, pTab, j, 3); jmp3 = sqlite3VdbeAddOp2(v, OP_NotNull, 3, labelOk); VdbeCoverage(v); } @@ -139823,6 +139981,38 @@ SQLITE_PRIVATE void sqlite3Pragma( } } } + +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* Second pass to invoke the xIntegrity method on all virtual + ** tables. + */ + for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ + Table *pTab = sqliteHashData(x); + sqlite3_vtab *pVTab; + int a1; + if( pObjTab && pObjTab!=pTab ) continue; + if( IsOrdinaryTable(pTab) ) continue; + if( !IsVirtual(pTab) ) continue; + if( pTab->nCol<=0 ){ + const char *zMod = pTab->u.vtab.azArg[0]; + if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue; + } + sqlite3ViewGetColumnNames(pParse, pTab); + if( pTab->u.vtab.p==0 ) continue; + pVTab = pTab->u.vtab.p->pVtab; + if( NEVER(pVTab==0) ) continue; + if( NEVER(pVTab->pModule==0) ) continue; + if( pVTab->pModule->iVersion<4 ) continue; + if( pVTab->pModule->xIntegrity==0 ) continue; + sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick); + pTab->nTabRef++; + sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF); + a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, a1); + continue; + } +#endif } { static const int iLn = VDBE_OFFSET_LINENO(2); @@ -140459,7 +140649,11 @@ static int pragmaVtabBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ j = seen[0]-1; pIdxInfo->aConstraintUsage[j].argvIndex = 1; pIdxInfo->aConstraintUsage[j].omit = 1; - if( seen[1]==0 ) return SQLITE_OK; + if( seen[1]==0 ){ + pIdxInfo->estimatedCost = (double)1000; + pIdxInfo->estimatedRows = 1000; + return SQLITE_OK; + } pIdxInfo->estimatedCost = (double)20; pIdxInfo->estimatedRows = 20; j = seen[1]-1; @@ -143686,11 +143880,7 @@ static const char *columnTypeImpl( ** data for the result-set column of the sub-select. */ if( iCol<pS->pEList->nExpr -#ifdef SQLITE_ALLOW_ROWID_IN_VIEW - && iCol>=0 -#else - && ALWAYS(iCol>=0) -#endif + && (!ViewCanHaveRowid || iCol>=0) ){ /* If iCol is less than zero, then the expression requests the ** rowid of the sub-select or view. This expression is legal (see @@ -146865,6 +147055,10 @@ static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ ** ** (11) The subquery is not a VALUES clause ** +** (12) The WHERE clause is not "rowid ISNULL" or the equivalent. This +** case only comes up if SQLite is compiled using +** SQLITE_ALLOW_ROWID_IN_VIEW. +** ** Return 0 if no changes are made and non-zero if one or more WHERE clause ** terms are duplicated into the subquery. */ @@ -146975,6 +147169,18 @@ static int pushDownWhereTerms( } #endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( ViewCanHaveRowid && (pWhere->op==TK_ISNULL || pWhere->op==TK_NOTNULL) ){ + Expr *pLeft = pWhere->pLeft; + if( ALWAYS(pLeft) + && pLeft->op==TK_COLUMN + && pLeft->iColumn < 0 + ){ + return 0; /* Restriction (12) */ + } + } +#endif + if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc) ){ nChng++; pSubq->selFlags |= SF_PushDown; @@ -147602,12 +147808,14 @@ SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){ while( pSel->pPrior ){ pSel = pSel->pPrior; } sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol); pTab->iPKey = -1; + pTab->eTabType = TABTYP_VIEW; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); #ifndef SQLITE_ALLOW_ROWID_IN_VIEW /* The usual case - do not allow ROWID on a subquery */ pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; #else - pTab->tabFlags |= TF_Ephemeral; /* Legacy compatibility mode */ + /* Legacy compatibility mode */ + pTab->tabFlags |= TF_Ephemeral | sqlite3Config.mNoVisibleRowid; #endif return pParse->nErr ? SQLITE_ERROR : SQLITE_OK; } @@ -147875,7 +148083,7 @@ static int selectExpander(Walker *pWalker, Select *p){ pNestedFrom = pFrom->pSelect->pEList; assert( pNestedFrom!=0 ); assert( pNestedFrom->nExpr==pTab->nCol ); - assert( VisibleRowid(pTab)==0 ); + assert( VisibleRowid(pTab)==0 || ViewCanHaveRowid ); }else{ if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){ continue; @@ -147907,7 +148115,8 @@ static int selectExpander(Walker *pWalker, Select *p){ pUsing = 0; } - nAdd = pTab->nCol + (VisibleRowid(pTab) && (selFlags&SF_NestedFrom)); + nAdd = pTab->nCol; + if( VisibleRowid(pTab) && (selFlags & SF_NestedFrom)!=0 ) nAdd++; for(j=0; j<nAdd; j++){ const char *zName; struct ExprList_item *pX; /* Newly added ExprList term */ @@ -147989,7 +148198,8 @@ static int selectExpander(Walker *pWalker, Select *p){ pX = &pNew->a[pNew->nExpr-1]; assert( pX->zEName==0 ); if( (selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){ - if( pNestedFrom ){ + if( pNestedFrom && (!ViewCanHaveRowid || j<pNestedFrom->nExpr) ){ + assert( j<pNestedFrom->nExpr ); pX->zEName = sqlite3DbStrDup(db, pNestedFrom->a[j].zEName); testcase( pX->zEName==0 ); }else{ @@ -152923,6 +153133,9 @@ SQLITE_PRIVATE void sqlite3Update( } } if( chngRowid==0 && pPk==0 ){ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( isView ) sqlite3VdbeAddOp2(v, OP_Null, 0, regOldRowid); +#endif sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid); } } @@ -153460,7 +153673,8 @@ SQLITE_PRIVATE Upsert *sqlite3UpsertNew( SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget( Parse *pParse, /* The parsing context */ SrcList *pTabList, /* Table into which we are inserting */ - Upsert *pUpsert /* The ON CONFLICT clauses */ + Upsert *pUpsert, /* The ON CONFLICT clauses */ + Upsert *pAll /* Complete list of all ON CONFLICT clauses */ ){ Table *pTab; /* That table into which we are inserting */ int rc; /* Result code */ @@ -153563,6 +153777,14 @@ SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget( continue; } pUpsert->pUpsertIdx = pIdx; + if( sqlite3UpsertOfIndex(pAll,pIdx)!=pUpsert ){ + /* Really this should be an error. The isDup ON CONFLICT clause will + ** never fire. But this problem was not discovered until three years + ** after multi-CONFLICT upsert was added, and so we silently ignore + ** the problem to prevent breaking applications that might actually + ** have redundant ON CONFLICT clauses. */ + pUpsert->isDup = 1; + } break; } if( pUpsert->pUpsertIdx==0 ){ @@ -153589,9 +153811,13 @@ SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert *pUpsert){ Upsert *pNext; if( NEVER(pUpsert==0) ) return 0; pNext = pUpsert->pNextUpsert; - if( pNext==0 ) return 1; - if( pNext->pUpsertTarget==0 ) return 1; - if( pNext->pUpsertIdx==0 ) return 1; + while( 1 /*exit-by-return*/ ){ + if( pNext==0 ) return 1; + if( pNext->pUpsertTarget==0 ) return 1; + if( pNext->pUpsertIdx==0 ) return 1; + if( !pNext->isDup ) return 0; + pNext = pNext->pNextUpsert; + } return 0; } @@ -166619,16 +166845,10 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( for(i=0; i<pIdx->nColumn; i++){ Expr *pExpr; int j = pIdx->aiColumn[i]; - int bMaybeNullRow; if( j==XN_EXPR ){ pExpr = pIdx->aColExpr->a[i].pExpr; - testcase( pTabItem->fg.jointype & JT_LEFT ); - testcase( pTabItem->fg.jointype & JT_RIGHT ); - testcase( pTabItem->fg.jointype & JT_LTORJ ); - bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0; }else if( j>=0 && (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)!=0 ){ pExpr = sqlite3ColumnExpr(pTab, &pTab->aCol[j]); - bMaybeNullRow = 0; }else{ continue; } @@ -166660,7 +166880,7 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( p->iDataCur = pTabItem->iCursor; p->iIdxCur = iIdxCur; p->iIdxCol = i; - p->bMaybeNullRow = bMaybeNullRow; + p->bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0; if( sqlite3IndexAffinityStr(pParse->db, pIdx) ){ p->aff = pIdx->zColAff[i]; } @@ -178865,6 +179085,18 @@ SQLITE_API int sqlite3_config(int op, ...){ } #endif /* SQLITE_OMIT_DESERIALIZE */ + case SQLITE_CONFIG_ROWID_IN_VIEW: { + int *pVal = va_arg(ap,int*); +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( 0==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = TF_NoVisibleRowid; + if( 1==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = 0; + *pVal = (sqlite3GlobalConfig.mNoVisibleRowid==0); +#else + *pVal = 0; +#endif + break; + } + default: { rc = SQLITE_ERROR; break; @@ -204785,6 +205017,7 @@ json_parse_restart: case '[': { /* Parse array */ iThis = pParse->nBlob; + assert( i<=(u32)pParse->nJson ); jsonBlobAppendNode(pParse, JSONB_ARRAY, pParse->nJson - i, 0); iStart = pParse->nBlob; if( pParse->oom ) return -1; @@ -205183,6 +205416,10 @@ static void jsonReturnStringAsBlob(JsonString *pStr){ JsonParse px; memset(&px, 0, sizeof(px)); jsonStringTerminate(pStr); + if( pStr->eErr ){ + sqlite3_result_error_nomem(pStr->pCtx); + return; + } px.zJson = pStr->zBuf; px.nJson = pStr->nUsed; px.db = sqlite3_context_db_handle(pStr->pCtx); @@ -206508,8 +206745,9 @@ rebuild_from_cache: } p->zJson = (char*)sqlite3_value_text(pArg); p->nJson = sqlite3_value_bytes(pArg); + if( db->mallocFailed ) goto json_pfa_oom; if( p->nJson==0 ) goto json_pfa_malformed; - if( NEVER(p->zJson==0) ) goto json_pfa_oom; + assert( p->zJson!=0 ); if( jsonConvertTextToBlob(p, (flgs & JSON_KEEPERROR) ? 0 : ctx) ){ if( flgs & JSON_KEEPERROR ){ p->nErr = 1; @@ -206675,10 +206913,10 @@ static void jsonDebugPrintBlob( if( sz==0 && x<=JSONB_FALSE ){ sqlite3_str_append(pOut, "\n", 1); }else{ - u32 i; + u32 j; sqlite3_str_appendall(pOut, ": \""); - for(i=iStart+n; i<iStart+n+sz; i++){ - u8 c = pParse->aBlob[i]; + for(j=iStart+n; j<iStart+n+sz; j++){ + u8 c = pParse->aBlob[j]; if( c<0x20 || c>=0x7f ) c = '.'; sqlite3_str_append(pOut, (char*)&c, 1); } @@ -208086,6 +208324,9 @@ static int jsonEachColumn( case JEACH_VALUE: { u32 i = jsonSkipLabel(p); jsonReturnFromBlob(&p->sParse, i, ctx, 1); + if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY ){ + sqlite3_result_subtype(ctx, JSON_SUBTYPE); + } break; } case JEACH_TYPE: { @@ -208132,9 +208373,9 @@ static int jsonEachColumn( case JEACH_JSON: { if( p->sParse.zJson==0 ){ sqlite3_result_blob(ctx, p->sParse.aBlob, p->sParse.nBlob, - SQLITE_STATIC); + SQLITE_TRANSIENT); }else{ - sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC); + sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_TRANSIENT); } break; } @@ -209160,11 +209401,9 @@ static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){ ** Clear the Rtree.pNodeBlob object */ static void nodeBlobReset(Rtree *pRtree){ - if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){ - sqlite3_blob *pBlob = pRtree->pNodeBlob; - pRtree->pNodeBlob = 0; - sqlite3_blob_close(pBlob); - } + sqlite3_blob *pBlob = pRtree->pNodeBlob; + pRtree->pNodeBlob = 0; + sqlite3_blob_close(pBlob); } /* @@ -209208,7 +209447,6 @@ static int nodeAcquire( &pRtree->pNodeBlob); } if( rc ){ - nodeBlobReset(pRtree); *ppNode = 0; /* If unable to open an sqlite3_blob on the desired row, that can only ** be because the shadow tables hold erroneous data. */ @@ -209268,6 +209506,7 @@ static int nodeAcquire( } *ppNode = pNode; }else{ + nodeBlobReset(pRtree); if( pNode ){ pRtree->nNodeRef--; sqlite3_free(pNode); @@ -209412,6 +209651,7 @@ static void nodeGetCoord( int iCoord, /* Which coordinate to extract */ RtreeCoord *pCoord /* OUT: Space to write result to */ ){ + assert( iCell<NCELL(pNode) ); readCoord(&pNode->zData[12 + pRtree->nBytesPerCell*iCell + 4*iCoord], pCoord); } @@ -209601,7 +209841,9 @@ static int rtreeClose(sqlite3_vtab_cursor *cur){ sqlite3_finalize(pCsr->pReadAux); sqlite3_free(pCsr); pRtree->nCursor--; - nodeBlobReset(pRtree); + if( pRtree->nCursor==0 && pRtree->inWrTrans==0 ){ + nodeBlobReset(pRtree); + } return SQLITE_OK; } @@ -210186,7 +210428,11 @@ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){ int rc = SQLITE_OK; RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); if( rc==SQLITE_OK && ALWAYS(p) ){ - *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); + if( p->iCell>=NCELL(pNode) ){ + rc = SQLITE_ABORT; + }else{ + *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); + } } return rc; } @@ -210204,6 +210450,7 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ if( rc ) return rc; if( NEVER(p==0) ) return SQLITE_OK; + if( p->iCell>=NCELL(pNode) ) return SQLITE_ABORT; if( i==0 ){ sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell)); }else if( i<=pRtree->nDim2 ){ @@ -211685,8 +211932,7 @@ constraint: */ static int rtreeBeginTransaction(sqlite3_vtab *pVtab){ Rtree *pRtree = (Rtree *)pVtab; - assert( pRtree->inWrTrans==0 ); - pRtree->inWrTrans++; + pRtree->inWrTrans = 1; return SQLITE_OK; } @@ -211700,6 +211946,9 @@ static int rtreeEndTransaction(sqlite3_vtab *pVtab){ nodeBlobReset(pRtree); return SQLITE_OK; } +static int rtreeRollback(sqlite3_vtab *pVtab){ + return rtreeEndTransaction(pVtab); +} /* ** The xRename method for rtree module virtual tables. @@ -211818,7 +212067,7 @@ static sqlite3_module rtreeModule = { rtreeBeginTransaction, /* xBegin - begin transaction */ rtreeEndTransaction, /* xSync - sync transaction */ rtreeEndTransaction, /* xCommit - commit transaction */ - rtreeEndTransaction, /* xRollback - rollback transaction */ + rtreeRollback, /* xRollback - rollback transaction */ 0, /* xFindFunction - function overloading */ rtreeRename, /* xRename - rename the table */ rtreeSavepoint, /* xSavepoint */ @@ -245377,23 +245626,26 @@ static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){ static void fts5TokendataIterNext(Fts5Iter *pIter, int bFrom, i64 iFrom){ int ii; Fts5TokenDataIter *pT = pIter->pTokenDataIter; + Fts5Index *pIndex = pIter->pIndex; for(ii=0; ii<pT->nIter; ii++){ Fts5Iter *p = pT->apIter[ii]; if( p->base.bEof==0 && (p->base.iRowid==pIter->base.iRowid || (bFrom && p->base.iRowid<iFrom)) ){ - fts5MultiIterNext(p->pIndex, p, bFrom, iFrom); + fts5MultiIterNext(pIndex, p, bFrom, iFrom); while( bFrom && p->base.bEof==0 && p->base.iRowid<iFrom - && p->pIndex->rc==SQLITE_OK + && pIndex->rc==SQLITE_OK ){ - fts5MultiIterNext(p->pIndex, p, 0, 0); + fts5MultiIterNext(pIndex, p, 0, 0); } } } - fts5IterSetOutputsTokendata(pIter); + if( pIndex->rc==SQLITE_OK ){ + fts5IterSetOutputsTokendata(pIter); + } } /* @@ -250547,7 +250799,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355", -1, SQLITE_TRANSIENT); } /* diff --git a/src/libs/3rdparty/sqlite/sqlite3.h b/src/libs/3rdparty/sqlite/sqlite3.h index 4fdfde004e..2618b37a7b 100644 --- a/src/libs/3rdparty/sqlite/sqlite3.h +++ b/src/libs/3rdparty/sqlite/sqlite3.h @@ -146,9 +146,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.45.1" -#define SQLITE_VERSION_NUMBER 3045001 -#define SQLITE_SOURCE_ID "2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a" +#define SQLITE_VERSION "3.45.3" +#define SQLITE_VERSION_NUMBER 3045003 +#define SQLITE_SOURCE_ID "2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -420,6 +420,8 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running. ** <li> The application must not modify the SQL statement text passed into ** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running. +** <li> The application must not dereference the arrays or string pointers +** passed as the 3rd and 4th callback parameters after it returns. ** </ul> */ SQLITE_API int sqlite3_exec( @@ -2141,6 +2143,22 @@ struct sqlite3_mem_methods { ** configuration setting is never used, then the default maximum is determined ** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that ** compile-time option is not set, then the default maximum is 1073741824. +** +** [[SQLITE_CONFIG_ROWID_IN_VIEW]] +** <dt>SQLITE_CONFIG_ROWID_IN_VIEW +** <dd>The SQLITE_CONFIG_ROWID_IN_VIEW option enables or disables the ability +** for VIEWs to have a ROWID. The capability can only be enabled if SQLite is +** compiled with -DSQLITE_ALLOW_ROWID_IN_VIEW, in which case the capability +** defaults to on. This configuration option queries the current setting or +** changes the setting to off or on. The argument is a pointer to an integer. +** If that integer initially holds a value of 1, then the ability for VIEWs to +** have ROWIDs is activated. If the integer initially holds zero, then the +** ability is deactivated. Any other initial value for the integer leaves the +** setting unchanged. After changes, if any, the integer is written with +** a 1 or 0, if the ability for VIEWs to have ROWIDs is on or off. If SQLite +** is compiled without -DSQLITE_ALLOW_ROWID_IN_VIEW (which is the usual and +** recommended case) then the integer is always filled with zero, regardless +** if its initial value. ** </dl> */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ @@ -2172,6 +2190,7 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */ #define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ #define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ +#define SQLITE_CONFIG_ROWID_IN_VIEW 30 /* int* */ /* ** CAPI3REF: Database Connection Configuration Options diff --git a/src/libs/languageutils/componentversion.cpp b/src/libs/languageutils/componentversion.cpp index 6c599e300b..f041740d3b 100644 --- a/src/libs/languageutils/componentversion.cpp +++ b/src/libs/languageutils/componentversion.cpp @@ -10,27 +10,17 @@ namespace LanguageUtils { -const int ComponentVersion::NoVersion = -1; -const int ComponentVersion::MaxVersion = std::numeric_limits<int>::max(); +// QTC_TEMP -ComponentVersion::ComponentVersion() - : _major(NoVersion), _minor(NoVersion) +ComponentVersion::ComponentVersion(QStringView versionString) + : _major(NoVersion) + , _minor(NoVersion) { -} - -ComponentVersion::ComponentVersion(int major, int minor) - : _major(major), _minor(minor) -{ -} - -ComponentVersion::ComponentVersion(const QString &versionString) - : _major(NoVersion), _minor(NoVersion) -{ - int dotIdx = versionString.indexOf(QLatin1Char('.')); + auto dotIdx = versionString.indexOf(QLatin1Char('.')); if (dotIdx == -1) return; bool ok = false; - int maybeMajor = versionString.left(dotIdx).toInt(&ok); + auto maybeMajor = versionString.left(dotIdx).toInt(&ok); if (!ok) return; int maybeMinor = versionString.mid(dotIdx + 1).toInt(&ok); @@ -40,15 +30,6 @@ ComponentVersion::ComponentVersion(const QString &versionString) _minor = maybeMinor; } -ComponentVersion::~ComponentVersion() -{ -} - -bool ComponentVersion::isValid() const -{ - return _major >= 0 && _minor >= 0; -} - QString ComponentVersion::toString() const { QByteArray temp; @@ -65,36 +46,4 @@ void ComponentVersion::addToHash(QCryptographicHash &hash) const hash.addData(reinterpret_cast<const char *>(&_minor), sizeof(_minor)); } -bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return lhs.majorVersion() < rhs.majorVersion() - || (lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() < rhs.minorVersion()); -} - -bool operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return lhs.majorVersion() < rhs.majorVersion() - || (lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() <= rhs.minorVersion()); -} - -bool operator>(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return rhs < lhs; -} - -bool operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return rhs <= lhs; -} - -bool operator==(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() == rhs.minorVersion(); -} - -bool operator!=(const ComponentVersion &lhs, const ComponentVersion &rhs) -{ - return !(lhs == rhs); -} - } // namespace LanguageUtils diff --git a/src/libs/languageutils/componentversion.h b/src/libs/languageutils/componentversion.h index 4b39e8402a..2b8311a6cb 100644 --- a/src/libs/languageutils/componentversion.h +++ b/src/libs/languageutils/componentversion.h @@ -9,6 +9,10 @@ QT_BEGIN_NAMESPACE class QCryptographicHash; QT_END_NAMESPACE +#include <QStringView> + +#include <limits> + namespace LanguageUtils { class LANGUAGEUTILS_EXPORT ComponentVersion @@ -17,25 +21,56 @@ class LANGUAGEUTILS_EXPORT ComponentVersion int _minor; public: - static const int NoVersion; - static const int MaxVersion; + static constexpr int NoVersion = -1; + static constexpr int MaxVersion = std::numeric_limits<int>::max(); + + ComponentVersion() + : _major(NoVersion) + , _minor(NoVersion) + {} - ComponentVersion(); - ComponentVersion(int major, int minor); - explicit ComponentVersion(const QString &versionString); - ~ComponentVersion(); + ComponentVersion(int major, int minor) + : _major(major) + , _minor(minor) + {} + + explicit ComponentVersion(QStringView versionString); + ~ComponentVersion() = default; int majorVersion() const { return _major; } int minorVersion() const { return _minor; } - friend bool LANGUAGEUTILS_EXPORT operator<(const ComponentVersion &lhs, const ComponentVersion &rhs); - friend bool LANGUAGEUTILS_EXPORT operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs); - friend bool LANGUAGEUTILS_EXPORT operator>(const ComponentVersion &lhs, const ComponentVersion &rhs); - friend bool LANGUAGEUTILS_EXPORT operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs); - friend bool LANGUAGEUTILS_EXPORT operator==(const ComponentVersion &lhs, const ComponentVersion &rhs); - friend bool LANGUAGEUTILS_EXPORT operator!=(const ComponentVersion &lhs, const ComponentVersion &rhs); + friend bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs) + { + return std::tie(lhs._major, lhs._minor) < std::tie(rhs._major, rhs._minor); + } + + friend bool operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs) + { + return std::tie(lhs._major, lhs._minor) <= std::tie(rhs._major, rhs._minor); + } + + friend bool operator>(const ComponentVersion &lhs, const ComponentVersion &rhs) + { + return rhs < lhs; + } + + friend bool operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs) + { + return rhs <= lhs; + } + + friend bool operator==(const ComponentVersion &lhs, const ComponentVersion &rhs) + { + return lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() == rhs.minorVersion(); + } + + friend bool operator!=(const ComponentVersion &lhs, const ComponentVersion &rhs) + { + return !(lhs == rhs); + } - bool isValid() const; + bool isValid() const { return _major >= 0 && _minor >= 0; } QString toString() const; void addToHash(QCryptographicHash &hash) const; }; diff --git a/src/libs/nanotrace/CMakeLists.txt b/src/libs/nanotrace/CMakeLists.txt index 011c26f049..50693644ea 100644 --- a/src/libs/nanotrace/CMakeLists.txt +++ b/src/libs/nanotrace/CMakeLists.txt @@ -4,6 +4,7 @@ add_qtc_library(Nanotrace nanotraceglobals.h nanotrace.cpp nanotrace.h nanotracehr.cpp nanotracehr.h + staticstring.h PUBLIC_DEPENDS Qt::Core Qt::Gui PROPERTIES CXX_VISIBILITY_PRESET default diff --git a/src/libs/nanotrace/nanotracehr.cpp b/src/libs/nanotrace/nanotracehr.cpp index 07d2ff95ac..0006227434 100644 --- a/src/libs/nanotrace/nanotracehr.cpp +++ b/src/libs/nanotrace/nanotracehr.cpp @@ -45,6 +45,18 @@ unsigned int getUnsignedIntegerHash(std::thread::id id) return static_cast<unsigned int>(std::hash<std::thread::id>{}(id) & 0xFFFFFFFF); } +template<std::size_t capacity> +constexpr bool isArgumentValid(const StaticString<capacity> &string) +{ + return string.isValid() && string.size(); +} + +template<typename String> +constexpr bool isArgumentValid(const String &string) +{ + return string.size(); +} + template<typename TraceEvent> void printEvent(std::ostream &out, const TraceEvent &event, qint64 processId, std::thread::id threadId) { @@ -67,23 +79,24 @@ void printEvent(std::ostream &out, const TraceEvent &event, qint64 processId, st out << R"(,"flow_in":true)"; } - if (event.arguments.size()) + if (isArgumentValid(event.arguments)) { out << R"(,"args":)" << event.arguments; + } out << "}"; } -void writeMetaEvent(TraceFile<Tracing::IsEnabled> *file, std::string_view key, std::string_view value) +void writeMetaEvent(TraceFile<Tracing::IsEnabled> &file, std::string_view key, std::string_view value) { - std::lock_guard lock{file->fileMutex}; - auto &out = file->out; + std::lock_guard lock{file.fileMutex}; + auto &out = file.out; if (out.is_open()) { - file->out << R"({"name":")" << key << R"(","ph":"M", "pid":)" - << getUnsignedIntegerHash(QCoreApplication::applicationPid()) << R"(,"tid":)" - << getUnsignedIntegerHash(std::this_thread::get_id()) << R"(,"args":{"name":")" - << value << R"("}})" - << ",\n"; + file.out << R"({"name":")" << key << R"(","ph":"M", "pid":)" + << getUnsignedIntegerHash(QCoreApplication::applicationPid()) << R"(,"tid":)" + << getUnsignedIntegerHash(std::this_thread::get_id()) << R"(,"args":{"name":")" + << value << R"("}})" + << ",\n"; } } @@ -103,7 +116,6 @@ std::string getThreadName() } // namespace -namespace Internal { template<typename String> void convertToString(String &string, const QImage &image) { @@ -132,14 +144,11 @@ void convertToString(String &string, const QImage &image) return "alpha premultiplied"sv; })))); - Internal::convertToString(string, dict); + convertToString(string, dict); } -template NANOTRACE_EXPORT void convertToString(std::string &string, const QImage &image); template NANOTRACE_EXPORT void convertToString(ArgumentsString &string, const QImage &image); -} // namespace Internal - template<typename TraceEvent> void flushEvents(const Utils::span<TraceEvent> events, std::thread::id threadId, @@ -148,8 +157,8 @@ void flushEvents(const Utils::span<TraceEvent> events, if (events.empty()) return; - std::lock_guard lock{eventQueue.file->fileMutex}; - auto &out = eventQueue.file->out; + std::lock_guard lock{eventQueue.file.fileMutex}; + auto &out = eventQueue.file.out; if (out.is_open()) { auto processId = QCoreApplication::applicationPid(); @@ -200,17 +209,17 @@ void finalizeFile(EnabledTraceFile &file) template<typename TraceEvent> void flushInThread(EnabledEventQueue<TraceEvent> &eventQueue) { - if (eventQueue.file->processing.valid()) - eventQueue.file->processing.wait(); + if (eventQueue.file.processing.valid()) + eventQueue.file.processing.wait(); auto flush = [&](const Utils::span<TraceEvent> &events, std::thread::id threadId) { flushEvents(events, threadId, eventQueue); }; - eventQueue.file->processing = std::async(std::launch::async, - flush, - eventQueue.currentEvents.subspan(0, eventQueue.eventsIndex), - eventQueue.threadId); + eventQueue.file.processing = std::async(std::launch::async, + flush, + eventQueue.currentEvents.subspan(0, eventQueue.eventsIndex), + eventQueue.threadId); eventQueue.currentEvents = eventQueue.currentEvents.data() == eventQueue.eventsOne.data() ? eventQueue.eventsTwo : eventQueue.eventsOne; @@ -223,10 +232,11 @@ template NANOTRACE_EXPORT void flushInThread( EnabledEventQueue<StringViewWithStringArgumentsTraceEvent> &eventQueue); template<typename TraceEvent> -EventQueue<TraceEvent, Tracing::IsEnabled>::EventQueue(EnabledTraceFile *file) +EventQueue<TraceEvent, Tracing::IsEnabled>::EventQueue(EnabledTraceFile &file) : file{file} , threadId{std::this_thread::get_id()} { + setEventsSpans(*eventArrayOne.get(), *eventArrayTwo.get()); Internal::EventQueueTracker<TraceEvent>::get().addQueue(this); if (auto thread = QThread::currentThread()) { auto name = getThreadName(); diff --git a/src/libs/nanotrace/nanotracehr.h b/src/libs/nanotrace/nanotracehr.h index 7856ea3582..810e4e3c5d 100644 --- a/src/libs/nanotrace/nanotracehr.h +++ b/src/libs/nanotrace/nanotracehr.h @@ -5,16 +5,22 @@ #include "nanotraceglobals.h" +#include "staticstring.h" + #include <utils/smallstring.h> #include <utils/span.h> #include <QByteArrayView> +#include <QList> +#include <QMap> #include <QStringView> +#include <QVarLengthArray> #include <QVariant> #include <array> #include <atomic> #include <chrono> +#include <exception> #include <fstream> #include <future> #include <mutex> @@ -41,7 +47,7 @@ enum class Tracing { IsDisabled, IsEnabled }; # define NO_UNIQUE_ADDRESS #endif -using ArgumentsString = Utils::BasicSmallString<510>; +using ArgumentsString = StaticString<3700>; namespace Literals { struct TracerLiteral @@ -80,44 +86,41 @@ struct IsDictonary inline constexpr IsDictonary isDictonary; -namespace Internal { - template<typename String> void convertToString(String &string, std::string_view text) { - string.append(R"(")"); + string.append('\"'); string.append(text); - string.append(R"(")"); + string.append('\"'); } template<typename String> void convertToString(String &string, const QImage &image); -extern template NANOTRACE_EXPORT void convertToString(std::string &string, const QImage &image); extern template NANOTRACE_EXPORT void convertToString(ArgumentsString &string, const QImage &image); template<typename String, std::size_t size> void convertToString(String &string, const char (&text)[size]) { - string.append(R"(")"); - string.append(text); - string.append(R"(")"); + string.append('\"'); + string.append(std::string_view{text, size - 1}); + string.append('\"'); } template<typename String> void convertToString(String &string, QStringView text) { - string.append(R"(")"); + string.append('\"'); string.append(Utils::PathString{text}); - string.append(R"(")"); + string.append('\"'); } template<typename String> void convertToString(String &string, const QByteArray &text) { - string.append(R"(")"); + string.append('\"'); string.append(std::string_view(text.data(), Utils::usize(text))); - string.append(R"(")"); + string.append('\"'); } template<typename String> @@ -129,28 +132,29 @@ void convertToString(String &string, bool isTrue) string.append("false"); } -template<typename String, typename Callable, typename = std::enable_if_t<std::is_invocable_v<Callable>>> +template<typename String, typename Callable, typename std::enable_if_t<std::is_invocable_v<Callable>, bool> = true> void convertToString(String &string, Callable &&callable) { convertToString(string, callable()); } -template<typename String> -void convertToString(String &string, int number) +template<typename String, typename Number, typename std::enable_if_t<std::is_arithmetic_v<Number>, bool> = true> +void convertToString(String &string, Number number) { - string.append(Utils::SmallString::number(number)); + string.append(number); } -template<typename String> -void convertToString(String &string, long long number) +template<typename Enumeration> +constexpr std::underlying_type_t<Enumeration> to_underlying(Enumeration enumeration) noexcept { - string.append(Utils::SmallString::number(number)); + static_assert(std::is_enum_v<Enumeration>, "to_underlying expect an enumeration"); + return static_cast<std::underlying_type_t<Enumeration>>(enumeration); } -template<typename String> -void convertToString(String &string, double number) +template<typename String, typename Enumeration, typename std::enable_if_t<std::is_enum_v<Enumeration>, bool> = true> +void convertToString(String &string, Enumeration enumeration) { - string.append(Utils::SmallString::number(number)); + string.append(to_underlying(enumeration)); } template<typename String> @@ -165,14 +169,14 @@ void convertToString(String &string, const QVariant &value) convertToString(string, value.toString()); } -template<typename String, typename... Arguments> -void convertToString(String &string, const std::tuple<const IsDictonary &, Arguments...> &dictonary); - -template<typename String, typename... Arguments> -void convertToString(String &string, const std::tuple<const IsArray &, Arguments...> &list); - -template<typename String, template<typename...> typename Container, typename... Arguments> -void convertToString(String &string, const Container<Arguments...> &container); +template<typename String, typename Type> +void convertToString(String &string, const std::optional<Type> &value) +{ + if (value) + convertToString(string, *value); + else + convertToString(string, "empty optional"); +} template<typename String, typename Value> void convertArrayEntryToString(String &string, const Value &value) @@ -184,15 +188,15 @@ void convertArrayEntryToString(String &string, const Value &value) template<typename String, typename... Entries> void convertArrayToString(String &string, const IsArray &, Entries &...entries) { - string.append(R"([)"); + string.append('['); (convertArrayEntryToString(string, entries), ...); if (sizeof...(entries)) string.pop_back(); - string.append("]"); + string.append(']'); } template<typename String, typename... Arguments> -void convertToString(String &string, const std::tuple<const IsArray &, Arguments...> &list) +void convertToString(String &string, const std::tuple<IsArray, Arguments...> &list) { std::apply([&](auto &&...entries) { convertArrayToString(string, entries...); }, list); } @@ -202,55 +206,107 @@ void convertDictonaryEntryToString(String &string, const std::tuple<Key, Value> { const auto &[key, value] = argument; convertToString(string, key); - string.append(":"); + string.append(':'); convertToString(string, value); - string.append(","); + string.append(','); } template<typename String, typename... Entries> void convertDictonaryToString(String &string, const IsDictonary &, Entries &...entries) { - string.append(R"({)"); + string.append('{'); (convertDictonaryEntryToString(string, entries), ...); if (sizeof...(entries)) string.pop_back(); - string.append("}"); + string.append('}'); } template<typename String, typename... Arguments> -void convertToString(String &string, const std::tuple<const IsDictonary &, Arguments...> &dictonary) +void convertToString(String &string, const std::tuple<IsDictonary, Arguments...> &dictonary) { std::apply([&](auto &&...entries) { convertDictonaryToString(string, entries...); }, dictonary); } -template<typename String, template<typename...> typename Container, typename... Arguments> -void convertToString(String &string, const Container<Arguments...> &container) +template<typename T> +struct is_container : std::false_type +{}; + +template<typename... Arguments> +struct is_container<std::vector<Arguments...>> : std::true_type +{}; + +template<typename... Arguments> +struct is_container<QList<Arguments...>> : std::true_type +{}; + +template<typename T, qsizetype Prealloc> +struct is_container<QVarLengthArray<T, Prealloc>> : std::true_type +{}; + +template<typename String, typename Container, typename std::enable_if_t<is_container<Container>::value, bool> = true> +void convertToString(String &string, const Container &values) +{ + string.append('['); + + for (const auto &value : values) { + convertToString(string, value); + string.append(','); + } + + if (values.size()) + string.pop_back(); + + string.append(']'); +} + +template<typename T> +struct is_map : std::false_type +{}; + +template<typename... Arguments> +struct is_map<QtPrivate::QKeyValueRange<Arguments...>> : std::true_type +{}; + +template<typename... Arguments> +struct is_map<std::map<Arguments...>> : std::true_type +{}; + +template<typename String, typename Map, typename std::enable_if_t<is_map<Map>::value, bool> = true> +void convertToString(String &string, const Map &map) { - string.append("["); - for (const auto &entry : container) { - convertToString(string, entry); - string.append(","); + string.append('{'); + + for (const auto &[key, value] : map) { + convertToString(string, key); + string.append(':'); + convertToString(string, value); + string.append(','); } - if (container.size()) + if (map.begin() != map.end()) string.pop_back(); - string.append("]"); + string.append('}'); +} + +template<typename String, typename Key, typename Value> +void convertToString(String &string, const QMap<Key, Value> &dictonary) +{ + convertToString(string, dictonary.asKeyValueRange()); } +namespace Internal { + template<typename String, typename... Arguments> -String toArguments(Arguments &&...arguments) +void toArguments(String &text, Arguments &&...arguments) { - String text; constexpr auto argumentCount = sizeof...(Arguments); - text.append("{"); + text.append('{'); (convertDictonaryEntryToString(text, arguments), ...); if (argumentCount) text.pop_back(); - text.append("}"); - - return text; + text.append('}'); } inline std::string_view toArguments(std::string_view arguments) @@ -259,48 +315,58 @@ inline std::string_view toArguments(std::string_view arguments) } template<typename String> -void appendArguments(String &eventArguments) +void setArguments(String &eventArguments) { - eventArguments = {}; + if constexpr (std::is_same_v<String, std::string_view>) + eventArguments = {}; + else + eventArguments.clear(); } template<typename String> -void appendArguments(String &eventArguments, TracerLiteral arguments) +void setArguments(String &eventArguments, TracerLiteral arguments) { eventArguments = arguments; } template<typename String, typename... Arguments> -[[maybe_unused]] void appendArguments(String &eventArguments, Arguments &&...arguments) +[[maybe_unused]] void setArguments(String &eventArguments, Arguments &&...arguments) { static_assert( !std::is_same_v<String, std::string_view>, R"(The arguments type of the tracing event queue is a string view. You can only provide trace token arguments as TracerLiteral (""_t).)"); - eventArguments = Internal::toArguments<String>(std::forward<Arguments>(arguments)...); + if constexpr (std::is_same_v<String, std::string_view>) + eventArguments = {}; + else + eventArguments.clear(); + Internal::toArguments(eventArguments, std::forward<Arguments>(arguments)...); } } // namespace Internal template<typename Key, typename Value> -auto keyValue(Key &&key, Value &&value) +auto keyValue(const Key &key, Value &&value) { - return std::forward_as_tuple(std::forward<Key>(key), std::forward<Value>(value)); + if constexpr (std::is_lvalue_reference_v<Value>) + return std::tuple<const Key &, const Value &>(key, value); + else + return std::tuple<const Key &, std::decay_t<Value>>(key, value); } template<typename... Entries> auto dictonary(Entries &&...entries) { - return std::forward_as_tuple(isDictonary, std::forward<Entries>(entries)...); + return std::make_tuple(isDictonary, std::forward<Entries>(entries)...); } template<typename... Entries> auto array(Entries &&...entries) { - return std::forward_as_tuple(isArray, std::forward<Entries>(entries)...); + return std::make_tuple(isArray, std::forward<Entries>(entries)...); } -enum class IsFlow : std::size_t { No = 0, Out = 1 << 0, In = 1 << 1, InOut = In | Out }; +enum class IsFlow : char { No = 0, Out = 1 << 0, In = 1 << 1, InOut = In | Out }; inline bool operator&(IsFlow first, IsFlow second) { @@ -309,7 +375,7 @@ inline bool operator&(IsFlow first, IsFlow second) } template<typename String, typename ArgumentsString> -struct TraceEvent +struct alignas(4096) TraceEvent { using StringType = String; using ArgumentType = std::conditional_t<std::is_same_v<String, std::string_view>, TracerLiteral, String>; @@ -324,13 +390,13 @@ struct TraceEvent String name; String category; - ArgumentsString arguments; TimePoint time; Duration duration; std::size_t id = 0; - std::size_t bindId : 62; - IsFlow flow : 2; + std::size_t bindId = 0; + IsFlow flow = IsFlow::No; char type = ' '; + ArgumentsString arguments; }; using StringViewTraceEvent = TraceEvent<std::string_view, std::string_view>; @@ -413,6 +479,8 @@ class EventQueue { public: using IsActive = std::false_type; + + template<typename TraceFile> EventQueue(TraceFile &) {} }; namespace Internal { @@ -423,7 +491,13 @@ class EventQueueTracker using Queue = EventQueue<TraceEvent, Tracing::IsEnabled>; public: - EventQueueTracker() = default; + EventQueueTracker() + { + terminateHandler = std::get_terminate(); + + std::set_terminate([]() { EventQueueTracker::get().terminate(); }); + } + EventQueueTracker(const EventQueueTracker &) = delete; EventQueueTracker(EventQueueTracker &&) = delete; EventQueueTracker &operator=(const EventQueueTracker &) = delete; @@ -457,8 +531,25 @@ public: } private: + void terminate() + { + flushAll(); + if (terminateHandler) + terminateHandler(); + } + + void flushAll() + { + std::lock_guard lock{mutex}; + + for (auto queue : queues) + queue->flush(); + } + +private: std::mutex mutex; std::vector<Queue *> queues; + std::terminate_handler terminateHandler = nullptr; }; } // namespace Internal @@ -466,11 +557,12 @@ template<typename TraceEvent> class EventQueue<TraceEvent, Tracing::IsEnabled> { using TraceEventsSpan = Utils::span<TraceEvent>; + using TraceEvents = std::array<TraceEvent, 1000>; public: using IsActive = std::true_type; - EventQueue(EnabledTraceFile *file); + EventQueue(EnabledTraceFile &file); ~EventQueue(); @@ -483,7 +575,9 @@ public: EventQueue &operator=(const EventQueue &) = delete; EventQueue &operator=(EventQueue &&) = delete; - EnabledTraceFile *file = nullptr; + EnabledTraceFile &file; + std::unique_ptr<TraceEvents> eventArrayOne = std::make_unique<TraceEvents>(); + std::unique_ptr<TraceEvents> eventArrayTwo = std::make_unique<TraceEvents>(); TraceEventsSpan eventsOne; TraceEventsSpan eventsTwo; TraceEventsSpan currentEvents; @@ -505,35 +599,6 @@ extern template class NANOTRACE_EXPORT_EXTERN_TEMPLATE EventQueue<StringTraceEve extern template class NANOTRACE_EXPORT_EXTERN_TEMPLATE EventQueue<StringViewWithStringArgumentsTraceEvent, Tracing::IsEnabled>; -template<typename TraceEvent, std::size_t eventCount, Tracing isEnabled> -class EventQueueData : public EventQueue<TraceEvent, isEnabled> -{ -public: - using IsActive = std::true_type; - - EventQueueData(TraceFile<Tracing::IsDisabled> &) {} -}; - -template<typename TraceEvent, std::size_t eventCount> -class EventQueueData<TraceEvent, eventCount, Tracing::IsEnabled> - : public EventQueue<TraceEvent, Tracing::IsEnabled> -{ - using TraceEvents = std::array<TraceEvent, eventCount>; - using Base = EventQueue<TraceEvent, Tracing::IsEnabled>; - -public: - using IsActive = std::true_type; - - EventQueueData(EnabledTraceFile &file) - : Base{&file} - { - Base::setEventsSpans(*eventsOne.get(), *eventsTwo.get()); - } - - std::unique_ptr<TraceEvents> eventsOne = std::make_unique<TraceEvents>(); - std::unique_ptr<TraceEvents> eventsTwo = std::make_unique<TraceEvents>(); -}; - template<typename TraceEvent> TraceEvent &getTraceEvent(EnabledEventQueue<TraceEvent> &eventQueue) { @@ -1150,6 +1215,10 @@ public: return std::pair<TracerType, FlowTokenType>(); } + template<typename... Arguments> + void threadEvent(ArgumentType, Arguments &&...) + {} + static constexpr bool isActive() { return false; } }; @@ -1237,6 +1306,24 @@ public: std::forward_as_tuple(PrivateTag{}, traceName, bindId, m_self)}; } + template<typename... Arguments> + void threadEvent(ArgumentType traceName, Arguments &&...arguments) + { + if (isEnabled == IsEnabled::No) + return; + + auto &traceEvent = getTraceEvent(m_eventQueue); + + traceEvent.time = Clock::now(); + traceEvent.name = std::move(traceName); + traceEvent.category = traceName; + traceEvent.type = 'i'; + traceEvent.id = 0; + traceEvent.bindId = 0; + traceEvent.flow = IsFlow::No; + Internal::setArguments(traceEvent.arguments, std::forward<Arguments>(arguments)...); + } + EnabledEventQueue<TraceEvent> &eventQueue() const { return m_eventQueue; } std::string_view name() const { return m_name; } @@ -1270,7 +1357,7 @@ private: traceEvent.id = id; traceEvent.bindId = bindId; traceEvent.flow = flow; - Internal::appendArguments(traceEvent.arguments, std::forward<Arguments>(arguments)...); + Internal::setArguments(traceEvent.arguments, std::forward<Arguments>(arguments)...); traceEvent.time = Clock::now(); } @@ -1296,7 +1383,7 @@ private: traceEvent.id = id; traceEvent.bindId = bindId; traceEvent.flow = flow; - Internal::appendArguments(traceEvent.arguments, std::forward<Arguments>(arguments)...); + Internal::setArguments(traceEvent.arguments, std::forward<Arguments>(arguments)...); } template<typename... Arguments> @@ -1316,9 +1403,11 @@ private: traceEvent.id = id; traceEvent.bindId = 0; traceEvent.flow = IsFlow::No; - Internal::appendArguments(traceEvent.arguments, std::forward<Arguments>(arguments)...); + Internal::setArguments(traceEvent.arguments, std::forward<Arguments>(arguments)...); } + CategoryFunctionPointer self() { return m_self; } + private: StringType m_name; EnabledEventQueue<TraceEvent> &m_eventQueue; @@ -1403,11 +1492,8 @@ class Tracer<Category, std::true_type> , flow{flow} , m_category{category} { - if (category().isEnabled == IsEnabled::Yes) { - Internal::appendArguments<ArgumentsStringType>(m_arguments, - std::forward<Arguments>(arguments)...); - m_start = Clock::now(); - } + if (category().isEnabled == IsEnabled::Yes) + sendBeginTrace(std::forward<Arguments>(arguments)...); } public: @@ -1426,13 +1512,15 @@ public: : m_name{name} , m_category{category} { - if (category().isEnabled == IsEnabled::Yes) { - Internal::appendArguments<ArgumentsStringType>(m_arguments, - std::forward<Arguments>(arguments)...); - m_start = Clock::now(); - } + if (category().isEnabled == IsEnabled::Yes) + sendBeginTrace(std::forward<Arguments>(arguments)...); } + template<typename... Arguments> + [[nodiscard]] Tracer(ArgumentType name, Category &category, Arguments &&...arguments) + : Tracer(std::move(name), category.self(), std::forward<Arguments>(arguments)...) + {} + Tracer(const Tracer &) = delete; Tracer &operator=(const Tracer &) = delete; Tracer(Tracer &&other) noexcept = delete; @@ -1440,7 +1528,7 @@ public: TokenType createToken() { return {0, m_category}; } - ~Tracer() { sendTrace(); } + ~Tracer() { sendEndTrace(); } template<typename... Arguments> Tracer beginDuration(ArgumentType name, Arguments &&...arguments) @@ -1457,44 +1545,52 @@ public: template<typename... Arguments> void end(Arguments &&...arguments) { - sendTrace(std::forward<Arguments>(arguments)...); + sendEndTrace(std::forward<Arguments>(arguments)...); m_name = {}; } private: template<typename... Arguments> - void sendTrace(Arguments &&...arguments) + void sendBeginTrace(Arguments &&...arguments) + { + auto &category = m_category(); + if (category.isEnabled == IsEnabled::Yes) { + auto &traceEvent = getTraceEvent(category.eventQueue()); + traceEvent.name = m_name; + traceEvent.category = category.name(); + traceEvent.bindId = m_bindId; + traceEvent.flow = flow; + traceEvent.type = 'B'; + Internal::setArguments<ArgumentsStringType>(traceEvent.arguments, + std::forward<Arguments>(arguments)...); + traceEvent.time = Clock::now(); + } + } + + template<typename... Arguments> + void sendEndTrace(Arguments &&...arguments) { if (m_name.size()) { - auto category = m_category(); + auto &category = m_category(); if (category.isEnabled == IsEnabled::Yes) { - auto duration = Clock::now() - m_start; + auto end = Clock::now(); auto &traceEvent = getTraceEvent(category.eventQueue()); - traceEvent.name = m_name; + traceEvent.name = std::move(m_name); traceEvent.category = category.name(); - traceEvent.time = m_start; - traceEvent.duration = duration; + traceEvent.time = end; traceEvent.bindId = m_bindId; traceEvent.flow = flow; - traceEvent.type = 'X'; - if (sizeof...(arguments)) { - m_arguments.clear(); - Internal::appendArguments<ArgumentsStringType>(traceEvent.arguments, - std::forward<Arguments>( - arguments)...); - } else { - traceEvent.arguments = m_arguments; - } + traceEvent.type = 'E'; + Internal::setArguments<ArgumentsStringType>(traceEvent.arguments, + std::forward<Arguments>(arguments)...); } } } private: - TimePoint m_start; StringType m_name; - ArgumentsStringType m_arguments; - std::size_t m_bindId; - IsFlow flow; + std::size_t m_bindId = 0; + IsFlow flow = IsFlow::No; CategoryFunctionPointer m_category; }; diff --git a/src/libs/nanotrace/staticstring.h b/src/libs/nanotrace/staticstring.h new file mode 100644 index 0000000000..d787bd2fe3 --- /dev/null +++ b/src/libs/nanotrace/staticstring.h @@ -0,0 +1,116 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include <utils/smallstringview.h> + +#if !(defined(__cpp_lib_to_chars) && (__cpp_lib_to_chars >= 201611L)) +# include <QLocale> +#endif + +#include <array> +#include <charconv> +#include <limits> + +namespace NanotraceHR { + +template<std::size_t Capacity> +class StaticString +{ +public: + StaticString() = default; + StaticString(const StaticString &) = delete; + StaticString &operator=(const StaticString &) = delete; + + char *data() { return m_data.data(); } + + const char *data() const { return m_data.data(); } + + void append(Utils::SmallStringView string) noexcept + { + auto newSize = m_size + string.size(); + + if (newSize <= Capacity) { + std::char_traits<char>::copy(std::next(data(), static_cast<std::ptrdiff_t>(m_size)), + string.data(), + string.size()); + m_size = newSize; + } else { + m_size = Capacity + 1; + } + } + + void append(char character) noexcept + { + auto newSize = m_size + 1; + + if (newSize <= Capacity) { + auto current = std::next(data(), static_cast<std::ptrdiff_t>(m_size)); + *current = character; + + m_size = newSize; + } else { + m_size = Capacity + 1; + } + } + + template<typename Type, typename std::enable_if_t<std::is_arithmetic_v<Type>, bool> = true> + void append(Type number) + { +#if !(defined(__cpp_lib_to_chars) && (__cpp_lib_to_chars >= 201611L)) + if constexpr (std::is_floating_point_v<Type>) { + QLocale locale{QLocale::Language::C}; + append(locale.toString(number).toStdString()); + return; + } +#endif + // 2 bytes for the sign and because digits10 returns the floor + char buffer[std::numeric_limits<Type>::digits10 + 2]; + auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); + auto endOfConversionString = result.ptr; + + append({buffer, endOfConversionString}); + } + + void pop_back() { --m_size; } + + StaticString &operator+=(Utils::SmallStringView string) noexcept + { + append(string); + + return *this; + } + + StaticString &operator+=(char character) noexcept + { + append(character); + + return *this; + } + + template<typename Type, typename = std::enable_if_t<std::is_arithmetic_v<Type>>> + StaticString &operator+=(Type number) noexcept + { + append(number); + + return *this; + } + + bool isValid() const { return m_size <= Capacity; } + + std::size_t size() const { return m_size; } + + friend std::ostream &operator<<(std::ostream &out, const StaticString &text) + { + return out << std::string_view{text.data(), text.size()}; + } + + void clear() { m_size = 0; } + +private: + std::array<char, Capacity> m_data; + std::size_t m_size = 0; +}; + +} // namespace NanotraceHR diff --git a/src/libs/qmljs/CMakeLists.txt b/src/libs/qmljs/CMakeLists.txt index 4ca15cf7cc..a4966a8b4a 100644 --- a/src/libs/qmljs/CMakeLists.txt +++ b/src/libs/qmljs/CMakeLists.txt @@ -33,7 +33,6 @@ add_qtc_library(QmlJS qmljsfindexportedcpptypes.cpp qmljsfindexportedcpptypes.h qmljsicons.cpp qmljsicons.h qmljsimportdependencies.cpp qmljsimportdependencies.h - qmljsindenter.cpp qmljsindenter.h qmljsinterpreter.cpp qmljsinterpreter.h qmljslineinfo.cpp qmljslineinfo.h qmljslink.cpp qmljslink.h diff --git a/src/libs/qmljs/qmljs.qbs b/src/libs/qmljs/qmljs.qbs index 18434c83ba..d5cc21faf1 100644 --- a/src/libs/qmljs/qmljs.qbs +++ b/src/libs/qmljs/qmljs.qbs @@ -28,7 +28,6 @@ QtcLibrary { "qmljsfindexportedcpptypes.cpp", "qmljsfindexportedcpptypes.h", "qmljsicons.cpp", "qmljsicons.h", "qmljsimportdependencies.cpp", "qmljsimportdependencies.h", - "qmljsindenter.cpp", "qmljsindenter.h", "qmljsinterpreter.cpp", "qmljsinterpreter.h", "qmljsdialect.cpp", "qmljsdialect.h", "qmljslineinfo.cpp", "qmljslineinfo.h", diff --git a/src/libs/qmljs/qmljsindenter.cpp b/src/libs/qmljs/qmljsindenter.cpp deleted file mode 100644 index 8055847d90..0000000000 --- a/src/libs/qmljs/qmljsindenter.cpp +++ /dev/null @@ -1,603 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -/* - This file is a self-contained interactive indenter for Qt Script. - - The general problem of indenting a program is ill posed. On - the one hand, an indenter has to analyze programs written in a - free-form formal language that is best described in terms of - tokens, not characters, not lines. On the other hand, indentation - applies to lines and white space characters matter, and otherwise - the programs to indent are formally invalid in general, as they - are begin edited. - - The approach taken here works line by line. We receive a program - consisting of N lines or more, and we want to compute the - indentation appropriate for the Nth line. Lines beyond the Nth - lines are of no concern to us, so for simplicity we pretend the - program has exactly N lines and we call the Nth line the "bottom - line". Typically, we have to indent the bottom line when it's - still empty, so we concentrate our analysis on the N - 1 lines - that precede. - - By inspecting the (N - 1)-th line, the (N - 2)-th line, ... - backwards, we determine the kind of the bottom line and indent it - accordingly. - - * The bottom line is a comment line. See - bottomLineStartsInCComment() and - indentWhenBottomLineStartsInCComment(). - * The bottom line is a continuation line. See isContinuationLine() - and indentForContinuationLine(). - * The bottom line is a standalone line. See - indentForStandaloneLine(). - - Certain tokens that influence the indentation, notably braces, - are looked for in the lines. This is done by simple string - comparison, without a real tokenizer. Confusing constructs such - as comments and string literals are removed beforehand. -*/ - -#include <qmljs/qmljsindenter.h> -#include <qmljs/qmljsscanner.h> - -#include <QTextBlock> - -using namespace QmlJS; - -/* - Saves and restores the state of the global linizer. This enables - backtracking. - - Identical to the defines in qmljslineinfo.cpp -*/ -#define YY_SAVE() LinizerState savedState = yyLinizerState -#define YY_RESTORE() yyLinizerState = savedState - - -QmlJSIndenter::QmlJSIndenter() - : caseOrDefault(QRegularExpression(QLatin1String( - "^\\s*(?:" - "case\\b[^:]+|" - "default)" - "\\s*:.*$"))) - -{ - - /* - The indenter supports a few parameters: - - * ppHardwareTabSize is the size of a '\t' in your favorite editor. - * ppIndentSize is the size of an indentation, or software tab - size. - * ppContinuationIndentSize is the extra indent for a continuation - line, when there is nothing to align against on the previous - line. - * ppCommentOffset is the indentation within a C-style comment, - when it cannot be picked up. - */ - - ppHardwareTabSize = 8; - ppIndentSize = 4; - ppContinuationIndentSize = 8; - ppCommentOffset = 2; -} - -QmlJSIndenter::~QmlJSIndenter() -{ -} - -void QmlJSIndenter::setTabSize(int size) -{ - ppHardwareTabSize = size; -} - -void QmlJSIndenter::setIndentSize(int size) -{ - ppIndentSize = size; - ppContinuationIndentSize = 2 * size; -} - -/* - Returns true if string t is made only of white space; otherwise - returns false. -*/ -bool QmlJSIndenter::isOnlyWhiteSpace(const QString &t) const -{ - return firstNonWhiteSpace(t).isNull(); -} - -/* - Assuming string t is a line, returns the column number of a given - index. Column numbers and index are identical for strings that don't - contain '\t's. -*/ -int QmlJSIndenter::columnForIndex(const QString &t, int index) const -{ - int col = 0; - if (index > t.length()) - index = t.length(); - - for (int i = 0; i < index; i++) { - if (t.at(i) == QLatin1Char('\t')) - col = ((col / ppHardwareTabSize) + 1) * ppHardwareTabSize; - else - col++; - } - return col; -} - -/* - Returns the indentation size of string t. -*/ -int QmlJSIndenter::indentOfLine(const QString &t) const -{ - return columnForIndex(t, t.indexOf(firstNonWhiteSpace(t))); -} - -/* - Replaces t[k] by ch, unless t[k] is '\t'. Tab characters are better - left alone since they break the "index equals column" rule. No - provisions are taken against '\n' or '\r', which shouldn't occur in - t anyway. -*/ -void QmlJSIndenter::eraseChar(QString &t, int k, QChar ch) const -{ - if (t.at(k) != QLatin1Char('\t')) - t[k] = ch; -} - -/* - Returns '(' if the last parenthesis is opening, ')' if it is - closing, and QChar() if there are no parentheses in t. -*/ -QChar QmlJSIndenter::lastParen() const -{ - for (int index = yyLinizerState.tokens.size() - 1; index != -1; --index) { - const Token &token = yyLinizerState.tokens.at(index); - - if (token.is(Token::LeftParenthesis)) - return QLatin1Char('('); - - else if (token.is(Token::RightParenthesis)) - return QLatin1Char(')'); - } - - return QChar(); -} - -/* - Returns true if typedIn the same as okayCh or is null; otherwise - returns false. -*/ -bool QmlJSIndenter::okay(QChar typedIn, QChar okayCh) const -{ - return typedIn == QChar() || typedIn == okayCh; -} - -/* - Returns the recommended indent for the bottom line of yyProgram - assuming that it starts in a C-style comment, a condition that is - tested elsewhere. - - Essentially, we're trying to align against some text on the - previous line. -*/ -int QmlJSIndenter::indentWhenBottomLineStartsInMultiLineComment() -{ - QTextBlock block = yyProgram.lastBlock().previous(); - QString blockText; - - for (; block.isValid(); block = block.previous()) { - blockText = block.text(); - - if (! isOnlyWhiteSpace(blockText)) - break; - } - - return indentOfLine(blockText); -} - -/* - Returns the recommended indent for the bottom line of yyProgram, - assuming it's a continuation line. - - We're trying to align the continuation line against some parenthesis - or other bracked left opened on a previous line, or some interesting - operator such as '='. -*/ -int QmlJSIndenter::indentForContinuationLine() -{ - int braceDepth = 0; - int delimDepth = 0; - - bool leftBraceFollowed = *yyLeftBraceFollows; - - for (int i = 0; i < SmallRoof; i++) { - int hook = -1; - - int j = yyLine->length(); - while (j > 0 && hook < 0) { - j--; - QChar ch = yyLine->at(j); - - switch (ch.unicode()) { - case ')': - delimDepth++; - break; - case ']': - braceDepth++; - break; - case '}': - braceDepth++; - break; - case '(': - delimDepth--; - /* - An unclosed delimiter is a good place to align at, - at least for some styles (including Qt's). - */ - if (delimDepth == -1) - hook = j; - break; - - case '[': - braceDepth--; - /* - An unclosed delimiter is a good place to align at, - at least for some styles (including Qt's). - */ - if (braceDepth == -1) - hook = j; - break; - case '{': - braceDepth--; - /* - A left brace followed by other stuff on the same - line is typically for an enum or an initializer. - Such a brace must be treated just like the other - delimiters. - */ - if (braceDepth == -1) { - if (j < yyLine->length() - 1) - hook = j; - else - return 0; // shouldn't happen - } - break; - case '=': - /* - An equal sign is a very natural alignment hook - because it's usually the operator with the lowest - precedence in statements it appears in. Case in - point: - - int x = 1 + - 2; - - However, we have to beware of constructs such as - default arguments and explicit enum constant - values: - - void foo(int x = 0, - int y = 0); - - And not - - void foo(int x = 0, - int y = 0); - - These constructs are caracterized by a ',' at the - end of the unfinished lines or by unbalanced - parentheses. - */ - Q_ASSERT(j - 1 >= 0); - - if (QString::fromLatin1("!=<>").indexOf(yyLine->at(j - 1)) == -1 && - j + 1 < yyLine->length() && yyLine->at(j + 1) != QLatin1Char('=')) { - if (braceDepth == 0 && delimDepth == 0 && - j < yyLine->length() - 1 && - !yyLine->endsWith(QLatin1Char(',')) && - (yyLine->contains(QLatin1Char('(')) == yyLine->contains(QLatin1Char(')')))) - hook = j; - } - } - } - - if (hook >= 0) { - /* - Yes, we have a delimiter or an operator to align - against! We don't really align against it, but rather - against the following token, if any. In this example, - the following token is "11": - - int x = (11 + - 2); - - If there is no such token, we use a continuation indent: - - static QRegExp foo(QString( - "foo foo foo foo foo foo foo foo foo")); - */ - hook++; - while (hook < yyLine->length()) { - if (!yyLine->at(hook).isSpace()) - return columnForIndex(*yyLine, hook); - hook++; - } - return indentOfLine(*yyLine) + ppContinuationIndentSize; - } - - if (braceDepth != 0) - break; - - /* - The line's delimiters are balanced. It looks like a - continuation line or something. - */ - if (delimDepth == 0) { - if (leftBraceFollowed) { - /* - We have - - int main() - { - - or - - Bar::Bar() - : Foo(x) - { - - The "{" should be flush left. - */ - if (!isContinuationLine()) - return indentOfLine(*yyLine); - } else if (isContinuationLine() || yyLine->endsWith(QLatin1Char(','))) { - /* - We have - - x = a + - b + - c; - - or - - int t[] = { - 1, 2, 3, - 4, 5, 6 - - The "c;" should fall right under the "b +", and the - "4, 5, 6" right under the "1, 2, 3,". - */ - return indentOfLine(*yyLine); - } else { - /* - We have - - stream << 1 + - 2; - - We could, but we don't, try to analyze which - operator has precedence over which and so on, to - obtain the excellent result - - stream << 1 + - 2; - - We do have a special trick above for the assignment - operator above, though. - */ - return indentOfLine(*yyLine) + ppContinuationIndentSize; - } - } - - if (!readLine()) - break; - } - return 0; -} - -/* - Returns the recommended indent for the bottom line of yyProgram if - that line is standalone (or should be indented likewise). - - Indenting a standalone line is tricky, mostly because of braceless - control statements. Grossly, we are looking backwards for a special - line, a "hook line", that we can use as a starting point to indent, - and then modify the indentation level according to the braces met - along the way to that hook. - - Let's consider a few examples. In all cases, we want to indent the - bottom line. - - Example 1: - - x = 1; - y = 2; - - The hook line is "x = 1;". We met 0 opening braces and 0 closing - braces. Therefore, "y = 2;" inherits the indent of "x = 1;". - - Example 2: - - if (x) { - y; - - The hook line is "if (x) {". No matter what precedes it, "y;" has - to be indented one level deeper than the hook line, since we met one - opening brace along the way. - - Example 3: - - if (a) - while (b) { - c; - } - d; - - To indent "d;" correctly, we have to go as far as the "if (a)". - Compare with - - if (a) { - while (b) { - c; - } - d; - - Still, we're striving to go back as little as possible to - accommodate people with irregular indentation schemes. A hook line - near at hand is much more reliable than a remote one. -*/ -int QmlJSIndenter::indentForStandaloneLine() -{ - for (int i = 0; i < SmallRoof; i++) { - if (!*yyLeftBraceFollows) { - YY_SAVE(); - - if (matchBracelessControlStatement()) { - /* - The situation is this, and we want to indent "z;": - - if (x && - y) - z; - - yyLine is "if (x &&". - */ - return indentOfLine(*yyLine) + ppIndentSize; - } - YY_RESTORE(); - } - - if (yyLine->endsWith(QLatin1Char(';')) || yyLine->contains(QLatin1Char('{'))) { - /* - The situation is possibly this, and we want to indent - "z;": - - while (x) - y; - z; - - We return the indent of "while (x)". In place of "y;", - any arbitrarily complex compound statement can appear. - */ - - if (*yyBraceDepth > 0) { - do { - if (!readLine()) - break; - } while (*yyBraceDepth > 0); - } - - LinizerState hookState; - - while (isContinuationLine()) - readLine(); - hookState = yyLinizerState; - - readLine(); - if (*yyBraceDepth <= 0) { - do { - if (!matchBracelessControlStatement()) - break; - hookState = yyLinizerState; - } while (readLine()); - } - - yyLinizerState = hookState; - - while (isContinuationLine()) - readLine(); - - int indentChange = - *yyBraceDepth; - if (caseOrDefault.match(*yyLine).hasMatch()) - ++indentChange; - - /* - Never trust lines containing only '{' or '}', as some - people (Richard M. Stallman) format them weirdly. - */ - if (yyLine->trimmed().length() > 1) - return indentOfLine(*yyLine) + indentChange * ppIndentSize; - } - - if (!readLine()) - return -*yyBraceDepth * ppIndentSize; - } - return 0; -} - -/* - Returns the recommended indent for the bottom line of program. - Unless null, typedIn stores the character of yyProgram that - triggered reindentation. - - This function works better if typedIn is set properly; it is - slightly more conservative if typedIn is completely wild, and - slighly more liberal if typedIn is always null. The user might be - annoyed by the liberal behavior. -*/ -int QmlJSIndenter::indentForBottomLine(QTextBlock begin, QTextBlock end, QChar typedIn) -{ - if (begin == end) - return 0; - - const QTextBlock last = end.previous(); - - initialize(begin, last); - - QString bottomLine = last.text(); - QChar firstCh = firstNonWhiteSpace(bottomLine); - int indent = 0; - - if (bottomLineStartsInMultilineComment()) { - /* - The bottom line starts in a C-style comment. Indent it - smartly, unless the user has already played around with it, - in which case it's better to leave her stuff alone. - */ - if (isOnlyWhiteSpace(bottomLine)) - indent = indentWhenBottomLineStartsInMultiLineComment(); - else - indent = indentOfLine(bottomLine); - } else { - if (isUnfinishedLine()) - indent = indentForContinuationLine(); - else - indent = indentForStandaloneLine(); - - if ((okay(typedIn, QLatin1Char('}')) && firstCh == QLatin1Char('}')) - || (okay(typedIn, QLatin1Char(']')) && firstCh == QLatin1Char(']'))) { - /* - A closing brace is one level more to the left than the - code it follows. - */ - indent -= ppIndentSize; - } else if (okay(typedIn, QLatin1Char(':'))) { - if (caseOrDefault.match(bottomLine).hasMatch()) { - /* - Move a case label (or the ':' in front of a - constructor initialization list) one level to the - left, but only if the user did not play around with - it yet. Some users have exotic tastes in the - matter, and most users probably are not patient - enough to wait for the final ':' to format their - code properly. - - We don't attempt the same for goto labels, as the - user is probably the middle of "foo::bar". (Who - uses goto, anyway?) - */ - if (indentOfLine(bottomLine) <= indent) - indent -= ppIndentSize; - else - indent = indentOfLine(bottomLine); - } - } - } - - return qMax(0, indent); -} - diff --git a/src/libs/qmljs/qmljsindenter.h b/src/libs/qmljs/qmljsindenter.h deleted file mode 100644 index 55141c1b3b..0000000000 --- a/src/libs/qmljs/qmljsindenter.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include <qmljs/qmljs_global.h> -#include <qmljs/qmljslineinfo.h> - -#include <QRegularExpression> - -QT_FORWARD_DECLARE_CLASS(QTextBlock) - -namespace QmlJS { - -class QMLJS_EXPORT QmlJSIndenter : public LineInfo -{ - Q_DISABLE_COPY(QmlJSIndenter) - -public: - QmlJSIndenter(); - ~QmlJSIndenter(); - - void setTabSize(int size); - void setIndentSize(int size); - - int indentForBottomLine(QTextBlock firstBlock, QTextBlock lastBlock, QChar typedIn); - -private: - bool isOnlyWhiteSpace(const QString &t) const; - int columnForIndex(const QString &t, int index) const; - int indentOfLine(const QString &t) const; - - void eraseChar(QString &t, int k, QChar ch) const; - QChar lastParen() const; - bool okay(QChar typedIn, QChar okayCh) const; - - int indentWhenBottomLineStartsInMultiLineComment(); - int indentForContinuationLine(); - int indentForStandaloneLine(); - -private: - int ppHardwareTabSize; - int ppIndentSize; - int ppContinuationIndentSize; - int ppCommentOffset; - -private: - QRegularExpression caseOrDefault; -}; - -} // namespace QmlJS diff --git a/src/libs/qmljs/qmljsstaticanalysismessage.cpp b/src/libs/qmljs/qmljsstaticanalysismessage.cpp index 5b15e69428..432b97aba7 100644 --- a/src/libs/qmljs/qmljsstaticanalysismessage.cpp +++ b/src/libs/qmljs/qmljsstaticanalysismessage.cpp @@ -189,8 +189,9 @@ StaticAnalysisMessages::StaticAnalysisMessages() Tr::tr("Maximum string value length is %1."), 1); newMsg(ErrInvalidArrayValueLength, Error, Tr::tr("%1 elements expected in array value."), 1); - newMsg(WarnImperativeCodeNotEditableInVisualDesigner, Warning, - Tr::tr("Imperative code is not supported in Qt Design Studio.")); + newMsg(WarnImperativeCodeNotEditableInVisualDesigner, + Warning, + Tr::tr("JavaScript can break the visual tooling in Qt Design Studio.")); newMsg(WarnUnsupportedTypeInVisualDesigner, Warning, Tr::tr("This type (%1) is not supported in Qt Design Studio."), 1); newMsg(WarnReferenceToParentItemNotSupportedByVisualDesigner, Warning, diff --git a/src/libs/qmlpuppetcommunication/container/imagecontainer.cpp b/src/libs/qmlpuppetcommunication/container/imagecontainer.cpp index 545d4d927d..7747a9d118 100644 --- a/src/libs/qmlpuppetcommunication/container/imagecontainer.cpp +++ b/src/libs/qmlpuppetcommunication/container/imagecontainer.cpp @@ -15,7 +15,7 @@ #define QTC_ASSERT_STRING(cond) qDebug("SOFT ASSERT: \"" cond"\" in file " __FILE__ ", line " QTC_ASSERT_STRINGIFY(__LINE__)) #define QTC_ASSERT(cond, action) if (cond) {} else { QTC_ASSERT_STRING(#cond); action; } do {} while (0) -static Q_LOGGING_CATEGORY(imageContainerDebug, "qtc.imagecontainer.debug", QtDebugMsg) +static Q_LOGGING_CATEGORY(imageContainerDebug, "qtc.imagecontainer") namespace QmlDesigner { @@ -194,9 +194,10 @@ static void readSharedMemory(qint32 key, ImageContainer &container) QImage image = QImage(imageWidth, imageHeight, QImage::Format(imageFormat)); image.setDevicePixelRatio(pixelRatio); - if (image.isNull()) - qCInfo(imageContainerDebug()) << Q_FUNC_INFO << "Not able to create image:" << imageWidth << imageHeight << imageFormat; - else + if (image.isNull()) { + if (imageWidth || imageHeight || imageFormat) + qCWarning(imageContainerDebug) << Q_FUNC_INFO << "Not able to create image:" << imageWidth << imageHeight << imageFormat; + } else std::memcpy(image.bits(), reinterpret_cast<const qint32*>(sharedMemory.constData()) + 6, byteCount); container.setImage(image); diff --git a/src/libs/sqlite/CMakeLists.txt b/src/libs/sqlite/CMakeLists.txt index 2c7e1ebbf3..f86f31871a 100644 --- a/src/libs/sqlite/CMakeLists.txt +++ b/src/libs/sqlite/CMakeLists.txt @@ -31,7 +31,7 @@ endif() add_qtc_library(Sqlite PROPERTIES AUTOMOC OFF AUTOUIC OFF - DEPENDS Qt::Core Threads::Threads ${CMAKE_DL_LIBS} SqliteInternal + DEPENDS Qt::Core Threads::Threads ${CMAKE_DL_LIBS} SqliteInternal Nanotrace INCLUDES ../3rdparty/sqlite PUBLIC_INCLUDES @@ -58,7 +58,7 @@ add_qtc_library(Sqlite sqlitesessionchangeset.cpp sqlitesessionchangeset.h sqlitesessions.cpp sqlitesessions.h sqlitetable.h - sqlitetransaction.h + sqlitetracing.cpp sqlitetracing.h sqlitetransaction.h sqlitevalue.h sqlitewritestatement.h diff --git a/src/libs/sqlite/sqlitebasestatement.cpp b/src/libs/sqlite/sqlitebasestatement.cpp index 23466c0cea..8557bf6ad2 100644 --- a/src/libs/sqlite/sqlitebasestatement.cpp +++ b/src/libs/sqlite/sqlitebasestatement.cpp @@ -26,32 +26,7 @@ extern "C" int sqlite3_carray_bind( namespace Sqlite { -namespace { -using TraceFile = NanotraceHR::TraceFile<sqliteTracingStatus()>; - -TraceFile traceFile{"sqlite.json"}; - -thread_local NanotraceHR::EventQueueData<NanotraceHR::StringViewTraceEvent, 10000, sqliteTracingStatus()> - eventQueueData(traceFile); - -NanotraceHR::StringViewCategory<sqliteTracingStatus()> &sqliteLowLevelCategory(); - -thread_local NanotraceHR::StringViewCategory<sqliteTracingStatus()> sqliteLowLevelCategory_{ - "sqlite low level"_t, eventQueueData, sqliteLowLevelCategory}; - -NanotraceHR::StringViewCategory<sqliteTracingStatus()> &sqliteLowLevelCategory() -{ - return sqliteLowLevelCategory_; -} - -thread_local NanotraceHR::StringViewCategory<sqliteTracingStatus()> sqliteHighLevelCategory_{ - "sqlite high level"_t, eventQueueData, sqliteHighLevelCategory}; -} // namespace - -NanotraceHR::StringViewCategory<sqliteTracingStatus()> &sqliteHighLevelCategory() -{ - return sqliteHighLevelCategory_; -} +using NanotraceHR::keyValue; BaseStatement::BaseStatement(Utils::SmallStringView sqlStatement, Database &database) : m_database(database) @@ -107,14 +82,18 @@ void BaseStatement::waitForUnlockNotify() const void BaseStatement::reset() const noexcept { - NanotraceHR::Tracer tracer{"reset"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"reset"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle())}; sqlite3_reset(m_compiledStatement.get()); } bool BaseStatement::next() const { - NanotraceHR::Tracer tracer{"next"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"next"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle())}; int resultCode; do { @@ -141,7 +120,10 @@ void BaseStatement::step() const void BaseStatement::bindNull(int index) { - NanotraceHR::Tracer tracer{"bind null"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"bind null"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("index", index)}; int resultCode = sqlite3_bind_null(m_compiledStatement.get(), index); if (resultCode != SQLITE_OK) @@ -155,7 +137,11 @@ void BaseStatement::bind(int index, NullValue) void BaseStatement::bind(int index, int value) { - NanotraceHR::Tracer tracer{"bind int"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"bind int"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("index", index), + keyValue("value", value)}; int resultCode = sqlite3_bind_int(m_compiledStatement.get(), index, value); if (resultCode != SQLITE_OK) @@ -164,7 +150,11 @@ void BaseStatement::bind(int index, int value) void BaseStatement::bind(int index, long long value) { - NanotraceHR::Tracer tracer{"bind long long"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"bind long long"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("index", index), + keyValue("value", value)}; int resultCode = sqlite3_bind_int64(m_compiledStatement.get(), index, value); if (resultCode != SQLITE_OK) @@ -173,7 +163,11 @@ void BaseStatement::bind(int index, long long value) void BaseStatement::bind(int index, double value) { - NanotraceHR::Tracer tracer{"bind double"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"bind double"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("index", index), + keyValue("value", value)}; int resultCode = sqlite3_bind_double(m_compiledStatement.get(), index, value); if (resultCode != SQLITE_OK) @@ -182,7 +176,11 @@ void BaseStatement::bind(int index, double value) void BaseStatement::bind(int index, void *pointer) { - NanotraceHR::Tracer tracer{"bind pointer"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"bind pointer"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("index", index), + keyValue("pointer", reinterpret_cast<std::uintptr_t>(pointer))}; int resultCode = sqlite3_bind_pointer(m_compiledStatement.get(), index, pointer, "carray", nullptr); if (resultCode != SQLITE_OK) @@ -191,7 +189,12 @@ void BaseStatement::bind(int index, void *pointer) void BaseStatement::bind(int index, Utils::span<const int> values) { - NanotraceHR::Tracer tracer{"bind int span"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"bind int span"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("index", index), + keyValue("pointer", reinterpret_cast<std::uintptr_t>(values.data())), + keyValue("size", values.size())}; int resultCode = sqlite3_carray_bind(m_compiledStatement.get(), index, @@ -205,7 +208,12 @@ void BaseStatement::bind(int index, Utils::span<const int> values) void BaseStatement::bind(int index, Utils::span<const long long> values) { - NanotraceHR::Tracer tracer{"bind long long span"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"bind long long span"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("index", index), + keyValue("pointer", reinterpret_cast<std::uintptr_t>(values.data())), + keyValue("size", values.size())}; int resultCode = sqlite3_carray_bind(m_compiledStatement.get(), index, @@ -219,7 +227,12 @@ void BaseStatement::bind(int index, Utils::span<const long long> values) void BaseStatement::bind(int index, Utils::span<const double> values) { - NanotraceHR::Tracer tracer{"bind double span"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"bind double span"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("index", index), + keyValue("pointer", reinterpret_cast<std::uintptr_t>(values.data())), + keyValue("size", values.size())}; int resultCode = sqlite3_carray_bind(m_compiledStatement.get(), index, @@ -233,7 +246,12 @@ void BaseStatement::bind(int index, Utils::span<const double> values) void BaseStatement::bind(int index, Utils::span<const char *> values) { - NanotraceHR::Tracer tracer{"bind const char* span"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"bind const char* span"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("index", index), + keyValue("pointer", reinterpret_cast<std::uintptr_t>(values.data())), + keyValue("size", values.size())}; int resultCode = sqlite3_carray_bind(m_compiledStatement.get(), index, @@ -247,7 +265,11 @@ void BaseStatement::bind(int index, Utils::span<const char *> values) void BaseStatement::bind(int index, Utils::SmallStringView text) { - NanotraceHR::Tracer tracer{"bind string"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"bind string"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("index", index), + keyValue("text", text)}; int resultCode = sqlite3_bind_text(m_compiledStatement.get(), index, @@ -260,7 +282,12 @@ void BaseStatement::bind(int index, Utils::SmallStringView text) void BaseStatement::bind(int index, BlobView blobView) { - NanotraceHR::Tracer tracer{"bind blob"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"bind blob"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("index", index), + keyValue("pointer", reinterpret_cast<std::uintptr_t>(blobView.data())), + keyValue("size", blobView.size())}; int resultCode = SQLITE_OK; @@ -280,7 +307,11 @@ void BaseStatement::bind(int index, BlobView blobView) void BaseStatement::bind(int index, const Value &value) { - NanotraceHR::Tracer tracer{"bind value"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{ + "bind value"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + }; switch (value.type()) { case ValueType::Integer: @@ -303,7 +334,11 @@ void BaseStatement::bind(int index, const Value &value) void BaseStatement::bind(int index, ValueView value) { - NanotraceHR::Tracer tracer{"bind value"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{ + "bind value"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + }; switch (value.type()) { case ValueType::Integer: @@ -326,7 +361,9 @@ void BaseStatement::bind(int index, ValueView value) void BaseStatement::prepare(Utils::SmallStringView sqlStatement) { - NanotraceHR::Tracer tracer{"prepare"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"prepare"_t, + sqliteLowLevelCategory(), + keyValue("sql statement", sqlStatement)}; if (!m_database.isLocked()) throw DatabaseIsNotLocked{}; @@ -342,14 +379,18 @@ void BaseStatement::prepare(Utils::SmallStringView sqlStatement) nullptr); m_compiledStatement.reset(sqliteStatement); - if (resultCode == SQLITE_LOCKED) + if (resultCode == SQLITE_LOCKED) { + tracer.tick("wait for unlock"_t); waitForUnlockNotify(); + } } while (resultCode == SQLITE_LOCKED); if (resultCode != SQLITE_OK) Sqlite::throwError(resultCode, sqliteDatabaseHandle()); + + tracer.end(keyValue("sqlite statement", handle())); } sqlite3 *BaseStatement::sqliteDatabaseHandle() const @@ -427,7 +468,10 @@ StringType convertToTextForColumn(sqlite3_stmt *sqlStatment, int column) Type BaseStatement::fetchType(int column) const { - NanotraceHR::Tracer tracer{"fetch type"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"fetch type"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("column", column)}; auto dataType = sqlite3_column_type(m_compiledStatement.get(), column); @@ -449,9 +493,16 @@ Type BaseStatement::fetchType(int column) const int BaseStatement::fetchIntValue(int column) const { - NanotraceHR::Tracer tracer{"fetch int"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"fetch int"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("column", column)}; + + auto value = sqlite3_column_int(m_compiledStatement.get(), column); - return sqlite3_column_int(m_compiledStatement.get(), column); + tracer.end(keyValue("value", value)); + + return value; } template<> @@ -473,9 +524,16 @@ long BaseStatement::fetchValue<long>(int column) const long long BaseStatement::fetchLongLongValue(int column) const { - NanotraceHR::Tracer tracer{"fetch long long"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"fetch long long"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("column", column)}; + + auto value = sqlite3_column_int64(m_compiledStatement.get(), column); - return sqlite3_column_int64(m_compiledStatement.get(), column); + tracer.end(keyValue("value", value)); + + return value; } template<> @@ -486,14 +544,24 @@ long long BaseStatement::fetchValue<long long>(int column) const double BaseStatement::fetchDoubleValue(int column) const { - NanotraceHR::Tracer tracer{"fetch double"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"fetch double"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("column", column)}; + + auto value = sqlite3_column_double(m_compiledStatement.get(), column); + + tracer.end(keyValue("value", value)); - return sqlite3_column_double(m_compiledStatement.get(), column); + return value; } BlobView BaseStatement::fetchBlobValue(int column) const { - NanotraceHR::Tracer tracer{"fetch blob"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{"fetch blob"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("column", column)}; return convertToBlobForColumn(m_compiledStatement.get(), column); } @@ -507,7 +575,16 @@ double BaseStatement::fetchValue<double>(int column) const template<typename StringType> StringType BaseStatement::fetchValue(int column) const { - return convertToTextForColumn<StringType>(m_compiledStatement.get(), column); + NanotraceHR::Tracer tracer{"fetch string value"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("column", column)}; + + auto text = convertToTextForColumn<StringType>(m_compiledStatement.get(), column); + + tracer.end(keyValue("text", text)); + + return text; } template SQLITE_EXPORT Utils::SmallStringView BaseStatement::fetchValue<Utils::SmallStringView>( @@ -519,11 +596,25 @@ template SQLITE_EXPORT Utils::PathString BaseStatement::fetchValue<Utils::PathSt Utils::SmallStringView BaseStatement::fetchSmallStringViewValue(int column) const { - return fetchValue<Utils::SmallStringView>(column); + NanotraceHR::Tracer tracer{"fetch string view"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("column", column)}; + + auto text = fetchValue<Utils::SmallStringView>(column); + + tracer.end(keyValue("text", text)); + + return text; } ValueView BaseStatement::fetchValueView(int column) const { + NanotraceHR::Tracer tracer{"fetch value view"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", handle()), + keyValue("column", column)}; + int dataType = sqlite3_column_type(m_compiledStatement.get(), column); switch (dataType) { case SQLITE_NULL: @@ -543,7 +634,11 @@ ValueView BaseStatement::fetchValueView(int column) const void BaseStatement::Deleter::operator()(sqlite3_stmt *statement) { - NanotraceHR::Tracer tracer{"finalize"_t, sqliteLowLevelCategory()}; + NanotraceHR::Tracer tracer{ + "finalize"_t, + sqliteLowLevelCategory(), + keyValue("sqlite statement", reinterpret_cast<std::uintptr_t>(statement)), + }; sqlite3_finalize(statement); } diff --git a/src/libs/sqlite/sqlitebasestatement.h b/src/libs/sqlite/sqlitebasestatement.h index d2306c67a5..3710021ff5 100644 --- a/src/libs/sqlite/sqlitebasestatement.h +++ b/src/libs/sqlite/sqlitebasestatement.h @@ -9,6 +9,7 @@ #include "sqliteblob.h" #include "sqliteexception.h" #include "sqliteids.h" +#include "sqlitetracing.h" #include "sqlitetransaction.h" #include "sqlitevalue.h" @@ -30,8 +31,6 @@ using std::int64_t; namespace Sqlite { -using namespace NanotraceHR::Literals; - class Database; class DatabaseBackend; @@ -44,17 +43,6 @@ constexpr static std::underlying_type_t<Enumeration> to_underlying(Enumeration e return static_cast<std::underlying_type_t<Enumeration>>(enumeration); } -constexpr NanotraceHR::Tracing sqliteTracingStatus() -{ -#ifdef ENABLE_SQLITE_TRACING - return NanotraceHR::tracingStatus(); -#else - return NanotraceHR::Tracing::IsDisabled; -#endif -} - -SQLITE_EXPORT NanotraceHR::StringViewCategory<sqliteTracingStatus()> &sqliteHighLevelCategory(); - class SQLITE_EXPORT BaseStatement { public: @@ -136,6 +124,11 @@ public: protected: ~BaseStatement() = default; + std::uintptr_t handle() const + { + return reinterpret_cast<std::uintptr_t>(m_compiledStatement.get()); + } + private: struct Deleter { @@ -166,7 +159,12 @@ public: void execute() { - NanotraceHR::Tracer tracer{"execute"_t, sqliteHighLevelCategory()}; + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{ + "execute"_t, + sqliteHighLevelCategory(), + keyValue("sqlite statement", BaseStatement::handle()), + }; Resetter resetter{this}; BaseStatement::next(); @@ -175,7 +173,10 @@ public: template<typename... ValueType> void bindValues(const ValueType &...values) { - NanotraceHR::Tracer tracer{"bind"_t, sqliteHighLevelCategory()}; + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"bind"_t, + sqliteHighLevelCategory(), + keyValue("sqlite statement", BaseStatement::handle())}; static_assert(BindParameterCount == sizeof...(values), "Wrong binding parameter count!"); @@ -186,7 +187,10 @@ public: template<typename... ValueType> void write(const ValueType&... values) { - NanotraceHR::Tracer tracer{"write"_t, sqliteHighLevelCategory()}; + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"write"_t, + sqliteHighLevelCategory(), + keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; bindValues(values...); @@ -206,24 +210,37 @@ public: struct is_container<QVarLengthArray<T, Prealloc>> : std::true_type {}; + template<typename T> + struct is_small_container : std::false_type + {}; + + template<typename T, qsizetype Prealloc> + struct is_small_container<QVarLengthArray<T, Prealloc>> : std::true_type + {}; + template<typename Container, std::size_t capacity = 32, typename = std::enable_if_t<is_container<Container>::value>, typename... QueryTypes> auto values(const QueryTypes &...queryValues) { - NanotraceHR::Tracer tracer{"values"_t, sqliteHighLevelCategory()}; + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"values"_t, + sqliteHighLevelCategory(), + keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; Container resultValues; - resultValues.reserve(std::max(capacity, m_maximumResultCount)); + using size_tupe = typename Container::size_type; + if constexpr (!is_small_container<Container>::value) + resultValues.reserve(static_cast<size_tupe>(std::max(capacity, m_maximumResultCount))); bindValues(queryValues...); while (BaseStatement::next()) emplaceBackValues(resultValues); - setMaximumResultCount(resultValues.size()); + setMaximumResultCount(static_cast<std::size_t>(resultValues.size())); return resultValues; } @@ -241,7 +258,10 @@ public: template<typename ResultType, typename... QueryTypes> auto value(const QueryTypes &...queryValues) { - NanotraceHR::Tracer tracer{"value"_t, sqliteHighLevelCategory()}; + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"value"_t, + sqliteHighLevelCategory(), + keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; ResultType resultValue{}; @@ -257,7 +277,10 @@ public: template<typename ResultType, typename... QueryTypes> auto optionalValue(const QueryTypes &...queryValues) { - NanotraceHR::Tracer tracer{"optionalValue"_t, sqliteHighLevelCategory()}; + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"optionalValue"_t, + sqliteHighLevelCategory(), + keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; std::optional<ResultType> resultValue; @@ -273,6 +296,7 @@ public: template<typename Type> static auto toValue(Utils::SmallStringView sqlStatement, Database &database) { + using NanotraceHR::keyValue; NanotraceHR::Tracer tracer{"toValue"_t, sqliteHighLevelCategory()}; StatementImplementation statement(sqlStatement, database); @@ -287,7 +311,10 @@ public: template<typename Callable, typename... QueryTypes> void readCallback(Callable &&callable, const QueryTypes &...queryValues) { - NanotraceHR::Tracer tracer{"readCallback"_t, sqliteHighLevelCategory()}; + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"readCallback"_t, + sqliteHighLevelCategory(), + keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; @@ -304,7 +331,10 @@ public: template<typename Container, typename... QueryTypes> void readTo(Container &container, const QueryTypes &...queryValues) { - NanotraceHR::Tracer tracer{"readTo"_t, sqliteHighLevelCategory()}; + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"readTo"_t, + sqliteHighLevelCategory(), + keyValue("sqlite statement", BaseStatement::handle())}; Resetter resetter{this}; @@ -402,9 +432,11 @@ public: private: using TracerCategory = std::decay_t<decltype(sqliteHighLevelCategory())>; - NanotraceHR::Tracer<TracerCategory, typename TracerCategory::IsActive> tracer{ - "range"_t, sqliteHighLevelCategory()}; StatementImplementation &m_statement; + NanotraceHR::Tracer<TracerCategory, typename TracerCategory::IsActive> tracer{ + "range"_t, + sqliteHighLevelCategory(), + NanotraceHR::keyValue("sqlite statement", m_statement.handle())}; }; template<typename ResultType> diff --git a/src/libs/sqlite/sqliteexception.cpp b/src/libs/sqlite/sqliteexception.cpp index b5f581ad68..bb4a474adb 100644 --- a/src/libs/sqlite/sqliteexception.cpp +++ b/src/libs/sqlite/sqliteexception.cpp @@ -3,14 +3,20 @@ #include "sqliteexception.h" +#include "sqlitetracing.h" + #include <utils/smallstringio.h> +#include <nanotrace/nanotracehr.h> + #include <sqlite.h> #include <QDebug> namespace Sqlite { +using NanotraceHR::keyValue; + const char *Exception::what() const noexcept { return "Sqlite::Exception"; @@ -18,7 +24,10 @@ const char *Exception::what() const noexcept const char *ExceptionWithMessage::what() const noexcept { - return "Sqlite::ExceptionWithMessage"; + static Utils::SmallString text = Utils::SmallString::join( + {"Sqlite::ExceptionWithMessage", m_sqliteErrorMessage}); + + return text.data(); } void ExceptionWithMessage::printWarning() const @@ -26,6 +35,13 @@ void ExceptionWithMessage::printWarning() const qWarning() << what() << m_sqliteErrorMessage; } +StatementIsBusy::StatementIsBusy(Utils::SmallString &&sqliteErrorMessage) + : ExceptionWithMessage{std::move(sqliteErrorMessage)} +{ + sqliteHighLevelCategory().threadEvent("StatementIsBusy"_t, + keyValue("error message", std::string_view{what()})); +} + const char *StatementIsBusy::what() const noexcept { return "Sqlite::StatementIsBusy"; @@ -36,9 +52,19 @@ const char *DatabaseIsBusy::what() const noexcept return "Sqlite::DatabaseIsBusy"; } +StatementHasError::StatementHasError(Utils::SmallString &&sqliteErrorMessage) + : ExceptionWithMessage{std::move(sqliteErrorMessage)} +{ + sqliteHighLevelCategory().threadEvent("StatementHasError"_t, + keyValue("error message", std::string_view{what()})); +} + const char *StatementHasError::what() const noexcept { - return "Sqlite::StatementHasError"; + static Utils::SmallString text = Utils::SmallString::join( + {"Sqlite::StatementHasError: ", message()}); + + return text.data(); } const char *StatementIsMisused::what() const noexcept diff --git a/src/libs/sqlite/sqliteexception.h b/src/libs/sqlite/sqliteexception.h index f0cadfc748..17a0639e19 100644 --- a/src/libs/sqlite/sqliteexception.h +++ b/src/libs/sqlite/sqliteexception.h @@ -23,13 +23,15 @@ public: class SQLITE_EXPORT ExceptionWithMessage : public Exception { public: - ExceptionWithMessage(Utils::SmallString &&sqliteErrorMessage = Utils::SmallString{}) + ExceptionWithMessage(Utils::SmallString &&sqliteErrorMessage = {}) : m_sqliteErrorMessage(std::move(sqliteErrorMessage)) {} const char *what() const noexcept override; void printWarning() const; + std::string_view message() const noexcept { return m_sqliteErrorMessage; } + private: Utils::SmallString m_sqliteErrorMessage; }; @@ -37,7 +39,7 @@ private: class SQLITE_EXPORT StatementIsBusy : public ExceptionWithMessage { public: - using ExceptionWithMessage::ExceptionWithMessage; + StatementIsBusy(Utils::SmallString &&sqliteErrorMessage); const char *what() const noexcept override; }; @@ -90,7 +92,8 @@ public: class SQLITE_EXPORT StatementHasError : public ExceptionWithMessage { public: - using ExceptionWithMessage::ExceptionWithMessage; + StatementHasError(Utils::SmallString &&sqliteErrorMessage); + const char *what() const noexcept override; }; diff --git a/src/libs/sqlite/sqliteids.h b/src/libs/sqlite/sqliteids.h index d64e4d9645..1ffd546d9f 100644 --- a/src/libs/sqlite/sqliteids.h +++ b/src/libs/sqlite/sqliteids.h @@ -5,6 +5,7 @@ #include <utils/span.h> +#include <nanotrace/nanotracehr.h> #include <type_traits> #include <vector> @@ -64,6 +65,15 @@ public: [[noreturn, deprecated]] InternalIntegerType operator&() const { throw std::exception{}; } + template<typename String> + friend void convertToString(String &string, BasicId id) + { + if (id.isValid()) + NanotraceHR::convertToString(string, id.internalId()); + else + NanotraceHR::convertToString(string, "invalid"); + } + private: InternalIntegerType id = 0; }; @@ -88,4 +98,5 @@ struct hash<Sqlite::BasicId<Type, InternalIntegerType>> return std::hash<InternalIntegerType>{}(id.internalId()); } }; + } // namespace std diff --git a/src/libs/sqlite/sqliteindex.h b/src/libs/sqlite/sqliteindex.h index f320fcd599..7c7a8dbb2b 100644 --- a/src/libs/sqlite/sqliteindex.h +++ b/src/libs/sqlite/sqliteindex.h @@ -40,6 +40,8 @@ public: return Utils::SmallString::join({"CREATE ", m_indexType == IndexType::Unique ? "UNIQUE " : "", "INDEX IF NOT EXISTS index_", + kindName(), + "_", m_tableName, "_", m_columnNames.join("_"), @@ -65,6 +67,23 @@ public: } private: + std::string_view kindName() const + { + using namespace std::string_view_literals; + + if (m_indexType == IndexType::Unique && m_condition.hasContent()) + return "unique_partial"sv; + + if (m_indexType == IndexType::Unique) + return "unique"sv; + + if (m_condition.hasContent()) + return "partial"sv; + + return "normal"sv; + } + +private: Utils::SmallString m_tableName; Utils::SmallStringVector m_columnNames; IndexType m_indexType; diff --git a/src/libs/sqlite/sqlitetracing.cpp b/src/libs/sqlite/sqlitetracing.cpp new file mode 100644 index 0000000000..700546f146 --- /dev/null +++ b/src/libs/sqlite/sqlitetracing.cpp @@ -0,0 +1,38 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "sqlitetracing.h" + +namespace Sqlite { + +TraceFile &traceFile() +{ + static TraceFile traceFile{"tracing.json"}; + + return traceFile; +} + +namespace { + +thread_local NanotraceHR::EventQueue<NanotraceHR::StringViewWithStringArgumentsTraceEvent, + sqliteTracingStatus()> + eventQueue(traceFile()); + +} // namespace + +NanotraceHR::StringViewWithStringArgumentsCategory<sqliteTracingStatus()> &sqliteLowLevelCategory() +{ + thread_local NanotraceHR::StringViewWithStringArgumentsCategory<sqliteTracingStatus()> + sqliteLowLevelCategory_{"sqlite low level"_t, eventQueue, sqliteLowLevelCategory}; + return sqliteLowLevelCategory_; +} + +NanotraceHR::StringViewWithStringArgumentsCategory<sqliteTracingStatus()> &sqliteHighLevelCategory() +{ + thread_local NanotraceHR::StringViewWithStringArgumentsCategory<sqliteTracingStatus()> + sqliteHighLevelCategory_{"sqlite high level"_t, eventQueue, sqliteHighLevelCategory}; + + return sqliteHighLevelCategory_; +} + +} // namespace Sqlite diff --git a/src/libs/sqlite/sqlitetracing.h b/src/libs/sqlite/sqlitetracing.h new file mode 100644 index 0000000000..8dadc6de0d --- /dev/null +++ b/src/libs/sqlite/sqlitetracing.h @@ -0,0 +1,29 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "sqliteglobal.h" + +#include <nanotrace/nanotracehr.h> + +namespace Sqlite { +using namespace NanotraceHR::Literals; + +constexpr NanotraceHR::Tracing sqliteTracingStatus() +{ +#ifdef ENABLE_SQLITE_TRACING + return NanotraceHR::Tracing::IsEnabled; +#else + return NanotraceHR::Tracing::IsDisabled; +#endif +} + +using TraceFile = NanotraceHR::TraceFile<sqliteTracingStatus()>; + +SQLITE_EXPORT TraceFile &traceFile(); + +NanotraceHR::StringViewWithStringArgumentsCategory<sqliteTracingStatus()> &sqliteLowLevelCategory(); + +SQLITE_EXPORT NanotraceHR::StringViewWithStringArgumentsCategory<sqliteTracingStatus()> & +sqliteHighLevelCategory(); + +} // namespace Sqlite diff --git a/src/libs/sqlite/sqlitevalue.h b/src/libs/sqlite/sqlitevalue.h index fe576f3fec..725682494b 100644 --- a/src/libs/sqlite/sqlitevalue.h +++ b/src/libs/sqlite/sqlitevalue.h @@ -6,6 +6,7 @@ #include "sqliteblob.h" #include "sqliteexception.h" +#include <nanotrace/nanotracehr.h> #include <utils/smallstring.h> #include <QVariant> @@ -33,7 +34,7 @@ public: explicit ValueBase(NullValue) {} explicit ValueBase(VariantType &&value) - : value(value) + : value(std::move(value)) {} explicit ValueBase(const char *value) @@ -43,6 +44,7 @@ public: explicit ValueBase(long long value) : value(value) {} + explicit ValueBase(int value) : value(static_cast<long long>(value)) {} @@ -60,11 +62,6 @@ public: {} - explicit ValueBase(StringType &&value) - : value(std::move(value)) - - {} - explicit ValueBase(BlobView value) : value(value) @@ -229,14 +226,42 @@ public: class ValueView : public ValueBase<Utils::SmallStringView, BlobView> { public: + ValueView() = default; + + explicit ValueView(NullValue) {} + explicit ValueView(ValueBase &&base) : ValueBase(std::move(base)) {} + explicit ValueView(Utils::SmallStringView value) + : ValueBase(value) + {} + + explicit ValueView(BlobView value) + : ValueBase(value) + {} + + explicit ValueView(long long value) + : ValueBase(value) + {} + + explicit ValueView(int value) + : ValueBase(static_cast<long long>(value)) + {} + + explicit ValueView(uint value) + : ValueBase(static_cast<long long>(value)) + {} + + explicit ValueView(double value) + : ValueBase(value) + {} + template<typename Type> static ValueView create(Type &&value_) { - return ValueView{ValueBase{value_}}; + return ValueView(std::forward<Type>(value_)); } }; @@ -386,4 +411,27 @@ private: }; using Values = std::vector<Value>; + +template<typename String> +void convertToString(String &string, const Value &value) +{ + switch (value.type()) { + case ValueType::Null: + convertToString(string, "null"); + break; + case ValueType::Integer: + convertToString(string, value.toInteger()); + break; + case ValueType::Float: + convertToString(string, value.toFloat()); + break; + case ValueType::String: + convertToString(string, value.toStringView()); + break; + case ValueType::Blob: + convertToString(string, "blob"); + break; + } +} + } // namespace Sqlite diff --git a/src/libs/utils/smallstring.h b/src/libs/utils/smallstring.h index c522a6cae9..e8c3d74b92 100644 --- a/src/libs/utils/smallstring.h +++ b/src/libs/utils/smallstring.h @@ -93,7 +93,7 @@ public: static_cast<std::size_t>(std::distance(begin, end))} {} - template<typename Type, typename = std::enable_if_t<std::is_pointer<Type>::value>> + template<typename Type, typename std::enable_if_t<std::is_pointer<Type>::value, bool> = true> BasicSmallString(Type characterPointer) noexcept : BasicSmallString(characterPointer, std::char_traits<char>::length(characterPointer)) { @@ -118,7 +118,7 @@ public: template<typename BeginIterator, typename EndIterator, - typename = std::enable_if_t<std::is_same<BeginIterator, EndIterator>::value>> + typename std::enable_if_t<std::is_same<BeginIterator, EndIterator>::value, bool> = true> BasicSmallString(BeginIterator begin, EndIterator end) noexcept : BasicSmallString(&(*begin), size_type(end - begin)) {} @@ -354,6 +354,14 @@ public: return false; } + bool startsWith(QStringView subStringToSearch) const noexcept + { + if (size() >= Utils::usize(subStringToSearch)) + return subStringToSearch == QLatin1StringView{data(), subStringToSearch.size()}; + + return false; + } + bool startsWith(char characterToSearch) const noexcept { return data()[0] == characterToSearch; @@ -423,13 +431,55 @@ public: size_type oldSize = size(); size_type newSize = oldSize + string.size(); - reserve(optimalCapacity(newSize)); + if (fitsNotInCapacity(newSize)) + reserve(optimalCapacity(newSize)); + std::char_traits<char>::copy(std::next(data(), static_cast<std::ptrdiff_t>(oldSize)), string.data(), string.size()); setSize(newSize); } + void append(char character) noexcept + { + size_type oldSize = size(); + size_type newSize = oldSize + 1; + + if (fitsNotInCapacity(newSize)) + reserve(optimalCapacity(newSize)); + + auto current = std::next(data(), static_cast<std::ptrdiff_t>(oldSize)); + *current = character; + setSize(newSize); + } + + template<typename Type, typename std::enable_if_t<std::is_arithmetic_v<Type>, bool> = true> + void append(Type number) + { +#if defined(__cpp_lib_to_chars) && (__cpp_lib_to_chars >= 201611L) + // 2 bytes for the sign and because digits10 returns the floor + char buffer[std::numeric_limits<Type>::digits10 + 2]; + auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); + auto endOfConversionString = result.ptr; + + append({buffer, endOfConversionString}); +#else + if constexpr (std::is_floating_point_v<Type>) { + QLocale locale{QLocale::Language::C}; + append(locale.toString(number)); + return; + } else { + // 2 bytes for the sign and because digits10 returns the floor + char buffer[std::numeric_limits<Type>::digits10 + 2]; + auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); + auto endOfConversionString = result.ptr; + + append({buffer, endOfConversionString}); + } + +#endif + } + void append(QStringView string) noexcept { QStringEncoder encoder{QStringEncoder::Utf8}; @@ -469,6 +519,13 @@ public: return *this; } + BasicSmallString &operator+=(char character) noexcept + { + append(character); + + return *this; + } + BasicSmallString &operator+=(QStringView string) noexcept { append(string); @@ -476,6 +533,14 @@ public: return *this; } + template<typename Type, typename std::enable_if_t<std::is_arithmetic_v<Type>, bool> = true> + BasicSmallString &operator+=(Type number) noexcept + { + append(number); + + return *this; + } + BasicSmallString &operator+=(std::initializer_list<SmallStringView> list) noexcept { appendInitializerList(list, size()); @@ -580,37 +645,12 @@ public: return joinedString; } - static - BasicSmallString number(int number) + template<typename Type, typename std::enable_if_t<std::is_arithmetic_v<Type>, bool> = true> + static BasicSmallString number(Type number) { - // 2 bytes for the sign and because digits10 returns the floor - char buffer[std::numeric_limits<int>::digits10 + 2]; - auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); - auto endOfConversionString = result.ptr; - return BasicSmallString(buffer, endOfConversionString); - } - - static BasicSmallString number(long long int number) noexcept - { - // 2 bytes for the sign and because digits10 returns the floor - char buffer[std::numeric_limits<long long int>::digits10 + 2]; - auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); - auto endOfConversionString = result.ptr; - return BasicSmallString(buffer, endOfConversionString); - } - - static BasicSmallString number(double number) noexcept - { -#if defined(__cpp_lib_to_chars) && (__cpp_lib_to_chars >= 201611L) - // 2 bytes for the sign and because digits10 returns the floor - char buffer[std::numeric_limits<double>::digits10 + 2]; - auto result = std::to_chars(buffer, buffer + sizeof(buffer), number); - auto endOfConversionString = result.ptr; - return BasicSmallString(buffer, endOfConversionString); -#else - QLocale locale{QLocale::Language::C}; - return BasicSmallString{locale.toString(number)}; -#endif + BasicSmallString string; + string.append(number); + return string; } char &operator[](std::size_t index) noexcept { return *(data() + index); } @@ -655,7 +695,6 @@ public: friend BasicSmallString operator+(const BasicSmallString &first, const char (&second)[ArraySize]) noexcept { - return operator+(first, SmallStringView(second)); } @@ -687,8 +726,10 @@ unittest_public: bool fitsNotInCapacity(size_type capacity) const noexcept { - return (isShortString() && capacity > shortStringCapacity()) - || (!isShortString() && capacity > m_data.reference.capacity); + if (isShortString()) + return capacity > shortStringCapacity(); + + return capacity > m_data.reference.capacity; } static size_type optimalHeapCapacity(const size_type size) noexcept |