Index: ext/ota/otafault.test ================================================================== --- ext/ota/otafault.test +++ ext/ota/otafault.test @@ -62,11 +62,10 @@ faultsim_test_result {0 SQLITE_DONE} \ {1 {SQLITE_NOMEM - out of memory}} \ {1 SQLITE_NOMEM} \ {1 SQLITE_IOERR_NOMEM} \ {1 {SQLITE_NOMEM - unable to open a temporary database file for storing temporary tables}} - if {$testrc==0} { sqlite3 db test.db faultsim_integrity_check set res [db eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2; Index: ext/ota/sqlite3ota.c ================================================================== --- ext/ota/sqlite3ota.c +++ ext/ota/sqlite3ota.c @@ -94,28 +94,27 @@ ** the target database that require updating. For each such table, the ** iterator visits, in order: ** ** * the table itself, ** * each index of the table (zero or more points to visit), and -** * a special "cleanup table" point. +** * a special "cleanup table" state. */ struct OtaObjIter { sqlite3_stmt *pTblIter; /* Iterate through tables */ sqlite3_stmt *pIdxIter; /* Index iterator */ int nTblCol; /* Size of azTblCol[] array */ char **azTblCol; /* Array of quoted column names */ + char **azTblType; /* Array of column types */ unsigned char *abTblPk; /* Array of flags - true for PK columns */ int eType; -#if 0 - unsigned char bRowid; /* True for implicit IPK tables */ - unsigned char bVtab; /* True for a virtual table */ -#endif /* Output variables. zTbl==0 implies EOF. */ int bCleanup; /* True in "cleanup" state */ const char *zTbl; /* Name of target db table */ const char *zIdx; /* Name of target db index (or null) */ + int tnum; /* Root page of index (not table) */ + int bUnique; /* Current index is unique */ int iVisit; /* Number of points visited, incl. current */ /* Statements created by otaObjIterPrepareAll() */ int nCol; /* Number of columns in current object */ sqlite3_stmt *pSelect; /* Source data */ @@ -230,13 +229,15 @@ */ static void otaObjIterFreeCols(OtaObjIter *pIter){ int i; for(i=0; inTblCol; i++){ sqlite3_free(pIter->azTblCol[i]); + sqlite3_free(pIter->azTblType[i]); } sqlite3_free(pIter->azTblCol); pIter->azTblCol = 0; + pIter->azTblType = 0; pIter->abTblPk = 0; pIter->nTblCol = 0; sqlite3_free(pIter->zMask); pIter->zMask = 0; pIter->eType = 0; /* Invalid value */ @@ -305,10 +306,12 @@ rc = sqlite3_reset(pIter->pIdxIter); pIter->bCleanup = 1; pIter->zIdx = 0; }else{ pIter->zIdx = (const char*)sqlite3_column_text(pIter->pIdxIter, 0); + pIter->tnum = sqlite3_column_int(pIter->pIdxIter, 1); + pIter->bUnique = sqlite3_column_int(pIter->pIdxIter, 2); rc = SQLITE_OK; } } } @@ -337,12 +340,13 @@ "WHERE type='table' AND name LIKE 'data_%'" ); if( rc==SQLITE_OK ){ rc = prepareAndCollectError(p->db, &pIter->pIdxIter, &p->zErrmsg, - "SELECT name FROM main.sqlite_master " - "WHERE type='index' AND tbl_name = ?" + "SELECT name, rootpage, sql IS NULL OR substr(8, 6)=='UNIQUE' " + " FROM main.sqlite_master " + " WHERE type='index' AND tbl_name = ?" ); } pIter->bCleanup = 1; p->rc = rc; @@ -426,23 +430,43 @@ ** Allocate and zero the pIter->azTblCol[] and abTblPk[] arrays so that ** there is room for at least nCol elements. If an OOM occurs, store an ** error code in the OTA handle passed as the first argument. */ static void otaAllocateIterArrays(sqlite3ota *p, OtaObjIter *pIter, int nCol){ - int nByte = sizeof(char*) * nCol + sizeof(unsigned char*) * nCol; + int nByte = sizeof(char*) * nCol * 2 + sizeof(unsigned char*) * nCol; char **azNew; assert( p->rc==SQLITE_OK ); azNew = (char**)sqlite3_malloc(nByte); if( azNew ){ memset(azNew, 0, nByte); pIter->azTblCol = azNew; - pIter->abTblPk = (unsigned char*)&pIter->azTblCol[nCol]; + pIter->azTblType = &azNew[nCol]; + pIter->abTblPk = (unsigned char*)&pIter->azTblType[nCol]; }else{ p->rc = SQLITE_NOMEM; } } + +static char *otaStrndup(const char *zStr, int nStr, int *pRc){ + char *zRet = 0; + assert( *pRc==SQLITE_OK ); + + if( zStr ){ + int nCopy = nStr; + if( nCopy<0 ) nCopy = strlen(zStr) + 1; + zRet = (char*)sqlite3_malloc(nCopy); + if( zRet ){ + memcpy(zRet, zStr, nCopy); + }else{ + *pRc = SQLITE_NOMEM; + } + } + + return zRet; +} + /* ** Return true if zTab is the name of a virtual table within the target ** database. */ @@ -529,10 +553,12 @@ p->zErrmsg = sqlite3_mprintf("column missing from data_%q: %s", pIter->zTbl, zName ); }else{ int iPk = sqlite3_column_int(pStmt, 5); + const char *zType = (const char*)sqlite3_column_text(pStmt, 2); + pIter->azTblType[i] = otaStrndup(zType, -1, &p->rc); pIter->abTblPk[i] = (iPk!=0); if( iPk ){ pIter->eType = (iPk<0) ? OTA_PK_EXTERNAL : OTA_PK_REAL; } } @@ -641,10 +667,119 @@ } } } return zList; } + +/* +** This function is used to create a SELECT list (the list of SQL +** expressions that follows a SELECT keyword) for a SELECT statement +** used to read from an ota_xxx table while updating the index object +** currently indicated by the iterator object passed as the second +** argument. A "PRAGMA index_xinfo = " statement is used to +** obtain the required information. +** +** If the index is of the following form: +** +** CREATE INDEX i1 ON t1(c, b COLLATE nocase); +** +** and "t1" is a table with an explicit INTEGER PRIMARY KEY column +** "ipk", the returned string is: +** +** "`c` COLLATE 'BINARY', `b` COLLATE 'NOCASE', `ipk` COLLATE 'BINARY'" +** +** As well as the returned string, three other malloc'd strings are +** returned via output parameters. As follows: +** +** pzImposterCols: ... +** pzImposterPk: ... +** pzWhere: ... +*/ +static char *otaObjIterGetIndexCols( + sqlite3ota *p, /* OTA object */ + OtaObjIter *pIter, /* Object iterator for column names */ + char **pzImposterCols, /* OUT: Columns for imposter table */ + char **pzImposterPk, /* OUT: Imposter PK clause */ + char **pzWhere, /* OUT: WHERE clause */ + int *pnBind /* OUT: Total number of columns */ +){ + int rc = p->rc; /* Error code */ + int rc2; /* sqlite3_finalize() return code */ + char *zRet = 0; /* String to return */ + char *zImpCols = 0; /* String to return via *pzImposterCols */ + char *zImpPK = 0; /* String to return via *pzImposterPK */ + char *zWhere = 0; /* String to return via *pzWhere */ + int nBind = 0; /* Value to return via *pnBind */ + const char *zComma = ""; /* Set to ", " later on */ + const char *zAnd = ""; /* Set to " AND " later on */ + sqlite3_stmt *pXInfo = 0; /* PRAGMA index_xinfo = ? */ + + if( rc==SQLITE_OK ){ + assert( p->zErrmsg==0 ); + rc = prepareFreeAndCollectError(p->db, &pXInfo, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", pIter->zIdx) + ); + } + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ + const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4); + int iCid = sqlite3_column_int(pXInfo, 1); + const char *zCol; + const char *zType; + + if( iCid<0 ){ + /* An integer primary key. If the table has an explicit IPK, use + ** its name. Otherwise, use "ota_rowid". */ + if( pIter->eType==OTA_PK_REAL ){ + int i; + for(i=0; inTblCol && pIter->abTblPk[i]==0; i++); + assert( inTblCol ); + zCol = pIter->azTblCol[i]; + }else{ + zCol = "ota_rowid"; + } + zType = "INTEGER"; + }else{ + zCol = pIter->azTblCol[iCid]; + zType = pIter->azTblType[iCid]; + } + + zRet = sqlite3_mprintf("%z%s%s COLLATE %Q", zRet, zComma, zCol, zCollate); + if( pIter->bUnique==0 || sqlite3_column_int(pXInfo, 5) ){ + zImpPK = sqlite3_mprintf("%z%sc%d", zImpPK, zComma, nBind); + } + zImpCols = sqlite3_mprintf( + "%z%sc%d %s COLLATE %Q", zImpCols, zComma, nBind, zType, zCollate + ); + zWhere = sqlite3_mprintf("%z%sc%d IS ?", zWhere, zAnd, nBind); + if( zRet==0 || zImpPK==0 || zImpCols==0 || zWhere==0 ) rc = SQLITE_NOMEM; + zComma = ", "; + zAnd = " AND "; + nBind++; + } + + rc2 = sqlite3_finalize(pXInfo); + if( rc==SQLITE_OK ) rc = rc2; + + if( rc!=SQLITE_OK ){ + sqlite3_free(zRet); + sqlite3_free(zImpCols); + sqlite3_free(zImpPK); + sqlite3_free(zWhere); + zRet = 0; + zImpCols = 0; + zImpPK = 0; + zWhere = 0; + p->rc = rc; + } + + *pzImposterCols = zImpCols; + *pzImposterPk = zImpPK; + *pzWhere = zWhere; + *pnBind = nBind; + return zRet; +} /* ** Assuming the current table columns are "a", "b" and "c", and the zObj ** paramter is passed "old", return a string of the form: ** @@ -790,10 +925,11 @@ OtaObjIter *pIter, int nOffset /* Add "LIMIT -1 OFFSET $nOffset" to SELECT */ ){ assert( pIter->bCleanup==0 ); if( pIter->pSelect==0 && otaObjIterGetCols(p, pIter)==SQLITE_OK ){ + const int tnum = pIter->tnum; char *zCollist = 0; /* List of indexed columns */ char **pz = &p->zErrmsg; const char *zIdx = pIter->zIdx; char *zLimit = 0; @@ -801,29 +937,47 @@ zLimit = sqlite3_mprintf(" LIMIT -1 OFFSET %d", nOffset); if( !zLimit ) p->rc = SQLITE_NOMEM; } if( zIdx ){ - int *aiCol; /* Column map */ - const char **azColl; /* Collation sequences */ + char *zImposterCols = 0; + char *zImposterPK = 0; + char *zWhere = 0; + char *zBind = 0; + int nBind = 0; assert( pIter->eType!=OTA_PK_VTAB ); + zCollist = otaObjIterGetIndexCols( + p, pIter, &zImposterCols, &zImposterPK, &zWhere, &nBind + ); + zBind = otaObjIterGetBindlist(p, nBind); - /* Create the index writers */ + /* Create the imposter table used to write to this index. */ + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1); + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum); + otaMPrintfExec(p, + "CREATE TABLE ota_imposter( %s, PRIMARY KEY( %s ) ) WITHOUT ROWID", + zImposterCols, zImposterPK + ); + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); + + /* Create the statement to insert index entries */ + pIter->nCol = nBind; if( p->rc==SQLITE_OK ){ - p->rc = sqlite3_index_writer( - p->db, 0, zIdx, &pIter->pInsert, &azColl, &aiCol, &pIter->nCol + p->rc = prepareFreeAndCollectError(p->db, &pIter->pInsert, &p->zErrmsg, + sqlite3_mprintf("INSERT INTO ota_imposter VALUES(%s)", zBind) ); } + + /* And to delete index entries */ if( p->rc==SQLITE_OK ){ - p->rc = sqlite3_index_writer( - p->db, 1, zIdx, &pIter->pDelete, &azColl, &aiCol, &pIter->nCol + p->rc = prepareFreeAndCollectError(p->db, &pIter->pDelete, &p->zErrmsg, + sqlite3_mprintf("DELETE FROM ota_imposter WHERE %s", zWhere) ); } /* Create the SELECT statement to read keys in sorted order */ - zCollist = otaObjIterGetCollist(p, pIter, pIter->nCol, aiCol, azColl); if( p->rc==SQLITE_OK ){ char *zSql; if( pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_NONE ){ zSql = sqlite3_mprintf( "SELECT %s, ota_control FROM ota.'ota_tmp_%q' ORDER BY %s%s", @@ -842,10 +996,15 @@ zCollist, zLimit ); } p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz, zSql); } + + sqlite3_free(zImposterCols); + sqlite3_free(zImposterPK); + sqlite3_free(zWhere); + sqlite3_free(zBind); }else{ int bOtaRowid = (pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE); const char *zTbl = pIter->zTbl; char *zWhere = otaObjIterGetWhere(p, pIter); char *zOldlist = otaObjIterGetOldlist(p, pIter, "old"); @@ -1203,11 +1362,11 @@ }else if( eType==OTA_UPDATE ){ sqlite3_value *pVal; sqlite3_stmt *pUpdate = 0; otaGetUpdateStmt(p, pIter, zMask, &pUpdate); if( pUpdate ){ - for(i=0; inCol; i++){ + for(i=0; p->rc==SQLITE_OK && inCol; i++){ pVal = sqlite3_column_value(pIter->pSelect, i); sqlite3_bind_value(pUpdate, i+1, pVal); } if( pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE ){ /* Bind the ota_rowid value to column _rowid_ */ @@ -1373,28 +1532,10 @@ if( rc!=SQLITE_OK ){ p->rc = rc; } } -static char *otaStrndup(char *zStr, int nStr, int *pRc){ - char *zRet = 0; - assert( *pRc==SQLITE_OK ); - - if( zStr ){ - int nCopy = nStr; - if( nCopy<0 ) nCopy = strlen(zStr) + 1; - zRet = (char*)sqlite3_malloc(nCopy); - if( zRet ){ - memcpy(zRet, zStr, nCopy); - }else{ - *pRc = SQLITE_NOMEM; - } - } - - return zRet; -} - static void otaFreeState(OtaState *p){ if( p ){ sqlite3_free(p->zTbl); sqlite3_free(p->zIdx); sqlite3_free(p->pCkptState); @@ -1738,10 +1879,11 @@ sqlite3 *db = sqlite3ota_db(pOta); int rc = sqlite3_create_function( db, "ota_delta", -1, SQLITE_UTF8, (void*)interp, test_ota_delta, 0, 0 ); Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); + sqlite3_exec(db, "PRAGMA vdbe_trace = 1", 0, 0, 0); ret = (rc==SQLITE_OK ? TCL_OK : TCL_ERROR); break; } default: /* seems unlikely */ Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -3642,16 +3642,18 @@ ** the schema to be reparsed the next time it is needed. This has the ** effect of erasing all imposter tables. */ case SQLITE_TESTCTRL_IMPOSTER: { sqlite3 *db = va_arg(ap, sqlite3*); + sqlite3_mutex_enter(db->mutex); db->init.iDb = sqlite3FindDbName(db, va_arg(ap,const char*)); db->init.busy = db->init.imposterTable = va_arg(ap,int); db->init.newTnum = va_arg(ap,int); if( db->init.busy==0 && db->init.newTnum>0 ){ sqlite3ResetAllSchemasOfConnection(db); } + sqlite3_mutex_leave(db->mutex); break; } } va_end(ap); #endif /* SQLITE_OMIT_BUILTIN_TEST */