Index: src/analyze.c ================================================================== --- src/analyze.c +++ src/analyze.c @@ -242,12 +242,11 @@ } /* Open the sqlite_stat[134] tables for writing. */ for(i=0; aTable[i].zCols; i++){ assert( inRowid ){ + sqlite3DbFree(db, p->u.aRowid); + p->nRowid = 0; + } +} +#endif + +/* Initialize the BLOB value of a ROWID +*/ +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +static void sampleSetRowid(sqlite3 *db, Stat4Sample *p, int n, const u8 *pData){ + assert( db!=0 ); + if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid); + p->u.aRowid = sqlite3DbMallocRaw(db, n); + if( p->u.aRowid ){ + p->nRowid = n; + memcpy(p->u.aRowid, pData, n); + }else{ + p->nRowid = 0; + } +} +#endif + +/* Initialize the INTEGER value of a ROWID. +*/ +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +static void sampleSetRowidInt64(sqlite3 *db, Stat4Sample *p, i64 iRowid){ + assert( db!=0 ); + if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid); + p->nRowid = 0; + p->u.iRowid = iRowid; +} +#endif + + +/* +** Copy the contents of object (*pFrom) into (*pTo). +*/ +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +static void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){ + pTo->isPSample = pFrom->isPSample; + pTo->iCol = pFrom->iCol; + pTo->iHash = pFrom->iHash; + memcpy(pTo->anEq, pFrom->anEq, sizeof(tRowcnt)*p->nCol); + memcpy(pTo->anLt, pFrom->anLt, sizeof(tRowcnt)*p->nCol); + memcpy(pTo->anDLt, pFrom->anDLt, sizeof(tRowcnt)*p->nCol); + if( pFrom->nRowid ){ + sampleSetRowid(p->db, pTo, pFrom->nRowid, pFrom->u.aRowid); + }else{ + sampleSetRowidInt64(p->db, pTo, pFrom->u.iRowid); + } +} +#endif + +/* +** Reclaim all memory of a Stat4Accum structure. +*/ +static void stat4Destructor(void *pOld){ + Stat4Accum *p = (Stat4Accum*)pOld; +#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + int i; + for(i=0; inCol; i++) sampleClear(p->db, p->aBest+i); + for(i=0; imxSample; i++) sampleClear(p->db, p->a+i); + sampleClear(p->db, &p->current); +#endif + sqlite3DbFree(p->db, p); +} /* ** Implementation of the stat_init(N,C) SQL function. The two parameters ** are the number of rows in the table or index (C) and the number of columns ** in the index (N). The second argument (C) is only used for STAT3 and STAT4. @@ -305,10 +382,11 @@ ){ Stat4Accum *p; int nCol; /* Number of columns in index being sampled */ int nColUp; /* nCol rounded up for alignment */ int n; /* Bytes of space to allocate */ + sqlite3 *db; /* Database connection */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 int mxSample = SQLITE_STAT4_SAMPLES; #endif /* Decode the three function arguments */ @@ -321,20 +399,22 @@ n = sizeof(*p) + sizeof(tRowcnt)*nColUp /* Stat4Accum.anEq */ + sizeof(tRowcnt)*nColUp /* Stat4Accum.anDLt */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + sizeof(tRowcnt)*nColUp /* Stat4Accum.anLt */ - + sizeof(Stat4Sample)*(nCol+mxSample) /* Stat4Accum.aBest[], a[] */ + + sizeof(Stat4Sample)*(nCol+mxSample) /* Stat4Accum.aBest[], a[] */ + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample) #endif ; - p = sqlite3MallocZero(n); + db = sqlite3_context_db_handle(context); + p = sqlite3DbMallocZero(db, n); if( p==0 ){ sqlite3_result_error_nomem(context); return; } + p->db = db; p->nRow = 0; p->nCol = nCol; p->current.anDLt = (tRowcnt*)&p[1]; p->current.anEq = &p->current.anDLt[nColUp]; @@ -365,11 +445,11 @@ } } #endif /* Return a pointer to the allocated object to the caller */ - sqlite3_result_blob(context, p, sizeof(p), sqlite3_free); + sqlite3_result_blob(context, p, sizeof(p), stat4Destructor); } static const FuncDef statInitFuncdef = { 1+IsStat34, /* nArg */ SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ @@ -439,29 +519,16 @@ #else return (nEqNew==nEqOld && pNew->iHash>pOld->iHash); #endif } -/* -** Copy the contents of object (*pFrom) into (*pTo). -*/ -static void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){ - pTo->iRowid = pFrom->iRowid; - pTo->isPSample = pFrom->isPSample; - pTo->iCol = pFrom->iCol; - pTo->iHash = pFrom->iHash; - memcpy(pTo->anEq, pFrom->anEq, sizeof(tRowcnt)*p->nCol); - memcpy(pTo->anLt, pFrom->anLt, sizeof(tRowcnt)*p->nCol); - memcpy(pTo->anDLt, pFrom->anDLt, sizeof(tRowcnt)*p->nCol); -} - /* ** Copy the contents of sample *pNew into the p->a[] array. If necessary, ** remove the least desirable sample from p->a[] to make room. */ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ - Stat4Sample *pSample; + Stat4Sample *pSample = 0; int i; assert( IsStat4 || nEqZero==0 ); #ifdef SQLITE_ENABLE_STAT4 @@ -497,12 +564,14 @@ if( p->nSample>=p->mxSample ){ Stat4Sample *pMin = &p->a[p->iMin]; tRowcnt *anEq = pMin->anEq; tRowcnt *anLt = pMin->anLt; tRowcnt *anDLt = pMin->anDLt; + sampleClear(p->db, pMin); memmove(pMin, &pMin[1], sizeof(p->a[0])*(p->nSample-p->iMin-1)); pSample = &p->a[p->nSample-1]; + pSample->nRowid = 0; pSample->anEq = anEq; pSample->anDLt = anDLt; pSample->anLt = anLt; p->nSample = p->mxSample-1; } @@ -595,20 +664,21 @@ UNUSED_PARAMETER( iChng ); #endif } /* -** Implementation of the stat_push SQL function: stat_push(P,R,C) +** Implementation of the stat_push SQL function: stat_push(P,C,R) ** Arguments: ** ** P Pointer to the Stat4Accum object created by stat_init() ** C Index of left-most column to differ from previous row -** R Rowid for the current row +** R Rowid for the current row. Might be a key record for +** WITHOUT ROWID tables. ** ** The SQL function always returns NULL. ** -** The R parameter is only used for STAT3 and STAT4. +** The R parameter is only used for STAT3 and STAT4 */ static void statPush( sqlite3_context *context, int argc, sqlite3_value **argv @@ -644,11 +714,16 @@ p->current.anEq[i] = 1; } } p->nRow++; #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - p->current.iRowid = sqlite3_value_int64(argv[2]); + if( sqlite3_value_type(argv[2])==SQLITE_INTEGER ){ + sampleSetRowidInt64(p->db, &p->current, sqlite3_value_int64(argv[2])); + }else{ + sampleSetRowid(p->db, &p->current, sqlite3_value_bytes(argv[2]), + sqlite3_value_blob(argv[2])); + } p->current.iHash = p->iPrn = p->iPrn*1103515245 + 12345; #endif #ifdef SQLITE_ENABLE_STAT4 { @@ -768,11 +843,17 @@ if( p->iGet<0 ){ samplePushPrevious(p, 0); p->iGet = 0; } if( p->iGetnSample ){ - sqlite3_result_int64(context, p->a[p->iGet].iRowid); + Stat4Sample *pS = p->a + p->iGet; + if( pS->nRowid==0 ){ + sqlite3_result_int64(context, pS->u.iRowid); + }else{ + sqlite3_result_blob(context, pS->u.aRowid, pS->nRowid, + SQLITE_TRANSIENT); + } } }else{ tRowcnt *aCnt = 0; assert( p->iGetnSample ); @@ -905,26 +986,30 @@ sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0); for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ int nCol; /* Number of columns indexed by pIdx */ - KeyInfo *pKey; /* KeyInfo structure for pIdx */ int *aGotoChng; /* Array of jump instruction addresses */ int addrRewind; /* Address of "OP_Rewind iIdxCur" */ int addrGotoChng0; /* Address of "Goto addr_chng_0" */ int addrNextRow; /* Address of "next_row:" */ + const char *zIdxName; /* Name of the index */ if( pOnlyIdx && pOnlyIdx!=pIdx ) continue; if( pIdx->pPartIdxWhere==0 ) needTableCnt = 0; VdbeNoopComment((v, "Begin analysis of %s", pIdx->zName)); - nCol = pIdx->nColumn; + nCol = pIdx->nKeyCol; aGotoChng = sqlite3DbMallocRaw(db, sizeof(int)*(nCol+1)); if( aGotoChng==0 ) continue; - pKey = sqlite3IndexKeyinfo(pParse, pIdx); /* Populate the register containing the index name. */ - sqlite3VdbeAddOp4(v, OP_String8, 0, regIdxname, 0, pIdx->zName, 0); + if( pIdx->autoIndex==2 && !HasRowid(pTab) ){ + zIdxName = pTab->zName; + }else{ + zIdxName = pIdx->zName; + } + sqlite3VdbeAddOp4(v, OP_String8, 0, regIdxname, 0, zIdxName, 0); /* ** Pseudo-code for loop that calls stat_push(): ** ** Rewind csr @@ -963,11 +1048,11 @@ pParse->nMem = MAX(pParse->nMem, regPrev+nCol); /* Open a read-only cursor on the index being analyzed. */ assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) ); sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum, iDb); - sqlite3VdbeChangeP4(v, -1, (char*)pKey, P4_KEYINFO_HANDOFF); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); /* Invoke the stat_init() function. The arguments are: ** ** (1) the number of columns in the index including the rowid, @@ -1037,12 +1122,25 @@ ** Next csr ** if !eof(csr) goto next_row; */ sqlite3VdbeJumpHere(v, aGotoChng[nCol]); #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, regRowid); assert( regRowid==(regStat4+2) ); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, regRowid); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable); + int j, k, regKey; + regKey = sqlite3GetTempRange(pParse, pPk->nKeyCol); + for(j=0; jnKeyCol; j++){ + k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[j]); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, regKey+j); + VdbeComment((v, "%s", pTab->aCol[pPk->aiColumn[j]].zName)); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regKey, pPk->nKeyCol, regRowid); + sqlite3ReleaseTempRange(pParse, regKey, pPk->nKeyCol); + } #endif assert( regChng==(regStat4+1) ); sqlite3VdbeAddOp3(v, OP_Function, 1, regStat4, regTemp); sqlite3VdbeChangeP4(v, -1, (char*)&statPushFuncdef, P4_FUNCDEF); sqlite3VdbeChangeP5(v, 2+IsStat34); @@ -1064,26 +1162,27 @@ int regSample = regStat1+3; int regCol = regStat1+4; int regSampleRowid = regCol + nCol; int addrNext; int addrIsNull; + u8 seekOp = HasRowid(pTab) ? OP_NotExists : OP_NotFound; pParse->nMem = MAX(pParse->nMem, regCol+nCol+1); addrNext = sqlite3VdbeCurrentAddr(v); callStatGet(v, regStat4, STAT_GET_ROWID, regSampleRowid); addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, regSampleRowid); callStatGet(v, regStat4, STAT_GET_NEQ, regEq); callStatGet(v, regStat4, STAT_GET_NLT, regLt); callStatGet(v, regStat4, STAT_GET_NDLT, regDLt); - sqlite3VdbeAddOp3(v, OP_NotExists, iTabCur, addrNext, regSampleRowid); + sqlite3VdbeAddOp4Int(v, seekOp, iTabCur, addrNext, regSampleRowid, 0); #ifdef SQLITE_ENABLE_STAT3 sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, pIdx->aiColumn[0], regSample); #else for(i=0; iaiColumn[i]; + i16 iCol = pIdx->aiColumn[i]; sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, iCol, regCol+i); } sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, nCol+1, regSample); #endif sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 6, regTemp, "bbbbbb", 0); @@ -1335,11 +1434,11 @@ pIndex = 0; } z = argv[2]; if( pIndex ){ - decodeIntArray((char*)z, pIndex->nColumn+1, pIndex->aiRowEst, pIndex); + decodeIntArray((char*)z, pIndex->nKeyCol+1, pIndex->aiRowEst, pIndex); if( pIndex->pPartIdxWhere==0 ) pTable->nRowEst = pIndex->aiRowEst[0]; }else{ Index fakeIdx; fakeIdx.szIdxRow = pTable->szTabRow; decodeIntArray((char*)z, 1, &pTable->nRowEst, &fakeIdx); @@ -1381,11 +1480,11 @@ static void initAvgEq(Index *pIdx){ if( pIdx ){ IndexSample *aSample = pIdx->aSample; IndexSample *pFinal = &aSample[pIdx->nSample-1]; int iCol; - for(iCol=0; iColnColumn; iCol++){ + for(iCol=0; iColnKeyCol; iCol++){ int i; /* Used to iterate through samples */ tRowcnt sumEq = 0; /* Sum of the nEq values */ tRowcnt nSum = 0; /* Number of terms contributing to sumEq */ tRowcnt avgEq = 0; tRowcnt nDLt = pFinal->anDLt[iCol]; @@ -1408,10 +1507,27 @@ pIdx->aAvgEq[iCol] = avgEq; if( pIdx->nSampleCol==1 ) break; } } } + +/* +** Look up an index by name. Or, if the name of a WITHOUT ROWID table +** is supplied instead, find the PRIMARY KEY index for that table. +*/ +static Index *findIndexOrPrimaryKey( + sqlite3 *db, + const char *zName, + const char *zDb +){ + Index *pIdx = sqlite3FindIndex(db, zName, zDb); + if( pIdx==0 ){ + Table *pTab = sqlite3FindTable(db, zName, zDb); + if( pTab && !HasRowid(pTab) ) pIdx = sqlite3PrimaryKeyIndex(pTab); + } + return pIdx; +} /* ** Load the content from either the sqlite_stat4 or sqlite_stat3 table ** into the relevant Index.aSample[] arrays. ** @@ -1458,18 +1574,18 @@ tRowcnt *pSpace; zIndex = (char *)sqlite3_column_text(pStmt, 0); if( zIndex==0 ) continue; nSample = sqlite3_column_int(pStmt, 1); - pIdx = sqlite3FindIndex(db, zIndex, zDb); + pIdx = findIndexOrPrimaryKey(db, zIndex, zDb); assert( pIdx==0 || bStat3 || pIdx->nSample==0 ); /* Index.nSample is non-zero at this point if data has already been ** loaded from the stat4 table. In this case ignore stat3 data. */ if( pIdx==0 || pIdx->nSample ) continue; if( bStat3==0 ){ - nIdxCol = pIdx->nColumn+1; - nAvgCol = pIdx->nColumn; + nIdxCol = pIdx->nKeyCol+1; + nAvgCol = pIdx->nKeyCol; } pIdx->nSampleCol = nIdxCol; nByte = sizeof(IndexSample) * nSample; nByte += sizeof(tRowcnt) * nIdxCol * 3 * nSample; nByte += nAvgCol * sizeof(tRowcnt); /* Space for Index.aAvgEq[] */ @@ -1504,11 +1620,11 @@ Index *pIdx; /* Pointer to the index object */ int nCol = 1; /* Number of columns in index */ zIndex = (char *)sqlite3_column_text(pStmt, 0); if( zIndex==0 ) continue; - pIdx = sqlite3FindIndex(db, zIndex, zDb); + pIdx = findIndexOrPrimaryKey(db, zIndex, zDb); if( pIdx==0 ) continue; /* This next condition is true if data has already been loaded from ** the sqlite_stat4 table. In this case ignore stat3 data. */ nCol = pIdx->nSampleCol; if( bStat3 && nCol>1 ) continue; Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -682,11 +682,11 @@ int bias, /* Bias search to the high end */ int *pRes /* Write search results here */ ){ int rc; /* Status code */ UnpackedRecord *pIdxKey; /* Unpacked index key */ - char aSpace[150]; /* Temp space for pIdxKey - to avoid a malloc */ + char aSpace[200]; /* Temp space for pIdxKey - to avoid a malloc */ char *pFree = 0; if( pKey ){ assert( nKey==(i64)(int)nKey ); pIdxKey = sqlite3VdbeAllocUnpackedRecord( Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -380,12 +380,14 @@ */ static void freeIndex(sqlite3 *db, Index *p){ #ifndef SQLITE_OMIT_ANALYZE sqlite3DeleteIndexSamples(db, p); #endif + if( db==0 || db->pnBytesFreed==0 ) sqlite3KeyInfoUnref(p->pKeyInfo); sqlite3ExprDelete(db, p->pPartIdxWhere); sqlite3DbFree(db, p->zColAff); + if( p->isResized ) sqlite3DbFree(db, p->azColl); sqlite3DbFree(db, p); } /* ** For the index called zIdxName which is found in the database iDb, @@ -639,12 +641,11 @@ ** writing. The table is opened using cursor 0. */ void sqlite3OpenMasterTable(Parse *p, int iDb){ Vdbe *v = sqlite3GetVdbe(p); sqlite3TableLock(p, iDb, MASTER_ROOT, 1, SCHEMA_TABLE(iDb)); - sqlite3VdbeAddOp3(v, OP_OpenWrite, 0, MASTER_ROOT, iDb); - sqlite3VdbeChangeP4(v, -1, (char *)5, P4_INT32); /* 5 column table */ + sqlite3VdbeAddOp4Int(v, OP_OpenWrite, 0, MASTER_ROOT, iDb, 5); if( p->nTab==0 ){ p->nTab = 1; } } @@ -744,10 +745,31 @@ sqlite3ErrorMsg(pParse, "object name reserved for internal use: %s", zName); return SQLITE_ERROR; } return SQLITE_OK; } + +/* +** Return the PRIMARY KEY index of a table +*/ +Index *sqlite3PrimaryKeyIndex(Table *pTab){ + Index *p; + for(p=pTab->pIndex; p && p->autoIndex!=2; p=p->pNext){} + return p; +} + +/* +** Return the column of index pIdx that corresponds to table +** column iCol. Return -1 if not found. +*/ +i16 sqlite3ColumnOfIndex(Index *pIdx, i16 iCol){ + int i; + for(i=0; inColumn; i++){ + if( iCol==pIdx->aiColumn[i] ) return i; + } + return -1; +} /* ** Begin constructing a new table representation in memory. This is ** the first of several action routines that get called in response ** to a CREATE TABLE statement. In particular, this routine is called @@ -944,11 +966,11 @@ if( isView || isVirtual ){ sqlite3VdbeAddOp2(v, OP_Integer, 0, reg2); }else #endif { - sqlite3VdbeAddOp2(v, OP_CreateTable, iDb, reg2); + pParse->addrCrTab = sqlite3VdbeAddOp2(v, OP_CreateTable, iDb, reg2); } sqlite3OpenMasterTable(pParse, iDb); sqlite3VdbeAddOp2(v, OP_NewRowid, 0, reg1); sqlite3VdbeAddOp2(v, OP_Null, 0, reg3); sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1); @@ -1212,10 +1234,11 @@ int sortOrder /* SQLITE_SO_ASC or SQLITE_SO_DESC */ ){ Table *pTab = pParse->pNewTable; char *zType = 0; int iCol = -1, i; + int nTerm; if( pTab==0 || IN_DECLARE_VTAB ) goto primary_key_exit; if( pTab->tabFlags & TF_HasPrimaryKey ){ sqlite3ErrorMsg(pParse, "table \"%s\" has more than one primary key", pTab->zName); goto primary_key_exit; @@ -1222,43 +1245,47 @@ } pTab->tabFlags |= TF_HasPrimaryKey; if( pList==0 ){ iCol = pTab->nCol - 1; pTab->aCol[iCol].colFlags |= COLFLAG_PRIMKEY; + zType = pTab->aCol[iCol].zType; + nTerm = 1; }else{ - for(i=0; inExpr; i++){ + nTerm = pList->nExpr; + for(i=0; inCol; iCol++){ if( sqlite3StrICmp(pList->a[i].zName, pTab->aCol[iCol].zName)==0 ){ + pTab->aCol[iCol].colFlags |= COLFLAG_PRIMKEY; + zType = pTab->aCol[iCol].zType; break; } } - if( iColnCol ){ - pTab->aCol[iCol].colFlags |= COLFLAG_PRIMKEY; - } - } - if( pList->nExpr>1 ) iCol = -1; - } - if( iCol>=0 && iColnCol ){ - zType = pTab->aCol[iCol].zType; - } - if( zType && sqlite3StrICmp(zType, "INTEGER")==0 - && sortOrder==SQLITE_SO_ASC ){ + } + } + if( nTerm==1 + && zType && sqlite3StrICmp(zType, "INTEGER")==0 + && sortOrder==SQLITE_SO_ASC + ){ pTab->iPKey = iCol; pTab->keyConf = (u8)onError; assert( autoInc==0 || autoInc==1 ); pTab->tabFlags |= autoInc*TF_Autoincrement; + if( pList ) pParse->iPkSortOrder = pList->a[0].sortOrder; }else if( autoInc ){ #ifndef SQLITE_OMIT_AUTOINCREMENT sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an " "INTEGER PRIMARY KEY"); #endif }else{ + Vdbe *v = pParse->pVdbe; Index *p; + if( v ) pParse->addrSkipPK = sqlite3VdbeAddOp0(v, OP_Noop); p = sqlite3CreateIndex(pParse, 0, 0, 0, pList, onError, 0, 0, sortOrder, 0); if( p ){ p->autoIndex = 2; + if( v ) sqlite3VdbeJumpHere(v, pParse->addrSkipPK); } pList = 0; } primary_key_exit: @@ -1311,11 +1338,11 @@ /* If the column is declared as " PRIMARY KEY COLLATE ", ** then an index may have been created on this column before the ** collation type was added. Correct this if it is the case. */ for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){ - assert( pIdx->nColumn==1 ); + assert( pIdx->nKeyCol==1 ); if( pIdx->aiColumn[0]==i ){ pIdx->azColl[0] = p->aCol[i].zColl; } } }else{ @@ -1502,10 +1529,35 @@ assert( k<=n ); } sqlite3_snprintf(n-k, &zStmt[k], "%s", zEnd); return zStmt; } + +/* +** Resize an Index object to hold N columns total. Return SQLITE_OK +** on success and SQLITE_NOMEM on an OOM error. +*/ +static int resizeIndexObject(sqlite3 *db, Index *pIdx, int N){ + char *zExtra; + int nByte; + if( pIdx->nColumn>=N ) return SQLITE_OK; + assert( pIdx->isResized==0 ); + nByte = (sizeof(char*) + sizeof(i16) + 1)*N; + zExtra = sqlite3DbMallocZero(db, nByte); + if( zExtra==0 ) return SQLITE_NOMEM; + memcpy(zExtra, pIdx->azColl, sizeof(char*)*pIdx->nColumn); + pIdx->azColl = (char**)zExtra; + zExtra += sizeof(char*)*N; + memcpy(zExtra, pIdx->aiColumn, sizeof(i16)*pIdx->nColumn); + pIdx->aiColumn = (i16*)zExtra; + zExtra += sizeof(i16)*N; + memcpy(zExtra, pIdx->aSortOrder, pIdx->nColumn); + pIdx->aSortOrder = (u8*)zExtra; + pIdx->nColumn = N; + pIdx->isResized = 1; + return SQLITE_OK; +} /* ** Estimate the total row width for a table. */ static void estimateTableWidth(Table *pTab){ @@ -1521,19 +1573,151 @@ /* ** Estimate the average size of a row for an index. */ static void estimateIndexWidth(Index *pIdx){ - unsigned wIndex = 1; + unsigned wIndex = 0; int i; const Column *aCol = pIdx->pTable->aCol; for(i=0; inColumn; i++){ - assert( pIdx->aiColumn[i]>=0 && pIdx->aiColumn[i]pTable->nCol ); - wIndex += aCol[pIdx->aiColumn[i]].szEst; + i16 x = pIdx->aiColumn[i]; + assert( xpTable->nCol ); + wIndex += x<0 ? 1 : aCol[pIdx->aiColumn[i]].szEst; } pIdx->szIdxRow = sqlite3LogEst(wIndex*4); } + +/* Return true if value x is found any of the first nCol entries of aiCol[] +*/ +static int hasColumn(const i16 *aiCol, int nCol, int x){ + while( nCol-- > 0 ) if( x==*(aiCol++) ) return 1; + return 0; +} + +/* +** This routine runs at the end of parsing a CREATE TABLE statement that +** has a WITHOUT ROWID clause. The job of this routine is to convert both +** internal schema data structures and the generated VDBE code so that they +** are appropriate for a WITHOUT ROWID table instead of a rowid table. +** Changes include: +** +** (1) Convert the OP_CreateTable into an OP_CreateIndex. There is +** no rowid btree for a WITHOUT ROWID. Instead, the canonical +** data storage is a covering index btree. +** (2) Bypass the creation of the sqlite_master table entry +** for the PRIMARY KEY as the the primary key index is now +** identified by the sqlite_master table entry of the table itself. +** (3) Set the Index.tnum of the PRIMARY KEY Index object in the +** schema to the rootpage from the main table. +** (4) Set all columns of the PRIMARY KEY schema object to be NOT NULL. +** (5) Add all table columns to the PRIMARY KEY Index object +** so that the PRIMARY KEY is a covering index. The surplus +** columns are part of KeyInfo.nXField and are not used for +** sorting or lookup or uniqueness checks. +** (6) Replace the rowid tail on all automatically generated UNIQUE +** indices with the PRIMARY KEY columns. +*/ +static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ + Index *pIdx; + Index *pPk; + int nPk; + int i, j; + sqlite3 *db = pParse->db; + Vdbe *v = pParse->pVdbe; + + /* Convert the OP_CreateTable opcode that would normally create the + ** root-page for the table into a OP_CreateIndex opcode. The index + ** created will become the PRIMARY KEY index. + */ + if( pParse->addrCrTab ){ + assert( v ); + sqlite3VdbeGetOp(v, pParse->addrCrTab)->opcode = OP_CreateIndex; + } + + /* Bypass the creation of the PRIMARY KEY btree and the sqlite_master + ** table entry. + */ + if( pParse->addrSkipPK ){ + assert( v ); + sqlite3VdbeGetOp(v, pParse->addrSkipPK)->opcode = OP_Goto; + } + + /* Locate the PRIMARY KEY index. Or, if this table was originally + ** an INTEGER PRIMARY KEY table, create a new PRIMARY KEY index. + */ + if( pTab->iPKey>=0 ){ + ExprList *pList; + pList = sqlite3ExprListAppend(pParse, 0, 0); + if( pList==0 ) return; + pList->a[0].zName = sqlite3DbStrDup(pParse->db, + pTab->aCol[pTab->iPKey].zName); + pList->a[0].sortOrder = pParse->iPkSortOrder; + assert( pParse->pNewTable==pTab ); + pPk = sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0); + if( pPk==0 ) return; + pPk->autoIndex = 2; + pTab->iPKey = -1; + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + } + pPk->isCovering = 1; + assert( pPk!=0 ); + nPk = pPk->nKeyCol; + + /* Make sure every column of the PRIMARY KEY is NOT NULL */ + for(i=0; iaCol[pPk->aiColumn[i]].notNull = 1; + } + pPk->uniqNotNull = 1; + + /* The root page of the PRIMARY KEY is the table root page */ + pPk->tnum = pTab->tnum; + + /* Update the in-memory representation of all UNIQUE indices by converting + ** the final rowid column into one or more columns of the PRIMARY KEY. + */ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + int n; + if( pIdx->autoIndex==2 ) continue; + for(i=n=0; iaiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ) n++; + } + if( n==0 ){ + /* This index is a superset of the primary key */ + pIdx->nColumn = pIdx->nKeyCol; + continue; + } + if( resizeIndexObject(db, pIdx, pIdx->nKeyCol+n) ) return; + for(i=0, j=pIdx->nKeyCol; iaiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ){ + pIdx->aiColumn[j] = pPk->aiColumn[i]; + pIdx->azColl[j] = pPk->azColl[i]; + j++; + } + } + assert( pIdx->nColumn>=pIdx->nKeyCol+n ); + assert( pIdx->nColumn>=j ); + } + + /* Add all table columns to the PRIMARY KEY index + */ + if( nPknCol ){ + if( resizeIndexObject(db, pPk, pTab->nCol) ) return; + for(i=0, j=nPk; inCol; i++){ + if( !hasColumn(pPk->aiColumn, j, i) ){ + assert( jnColumn ); + pPk->aiColumn[j] = i; + pPk->azColl[j] = "BINARY"; + j++; + } + } + assert( pPk->nColumn==j ); + assert( pTab->nCol==j ); + }else{ + pPk->nColumn = pTab->nCol; + } +} /* ** This routine is called to report the final ")" that terminates ** a CREATE TABLE statement. ** @@ -1554,11 +1738,12 @@ ** the new table will match the result set of the SELECT. */ void sqlite3EndTable( Parse *pParse, /* Parse context */ Token *pCons, /* The ',' token after the last column defn. */ - Token *pEnd, /* The final ')' token in the CREATE TABLE */ + Token *pEnd, /* The ')' before options in the CREATE TABLE */ + u8 tabOpts, /* Extra table options. Usually 0. */ Select *pSelect /* Select from a "CREATE ... AS SELECT" */ ){ Table *p; /* The new table */ sqlite3 *db = pParse->db; /* The database connection */ int iDb; /* Database in which the table lives */ @@ -1569,10 +1754,30 @@ } p = pParse->pNewTable; if( p==0 ) return; assert( !db->init.busy || !pSelect ); + + /* If the db->init.busy is 1 it means we are reading the SQL off the + ** "sqlite_master" or "sqlite_temp_master" table on the disk. + ** So do not write to the disk again. Extract the root page number + ** for the table from the db->init.newTnum field. (The page number + ** should have been put there by the sqliteOpenCb routine.) + */ + if( db->init.busy ){ + p->tnum = db->init.newTnum; + } + + /* Special processing for WITHOUT ROWID Tables */ + if( tabOpts & TF_WithoutRowid ){ + if( (p->tabFlags & TF_HasPrimaryKey)==0 ){ + sqlite3ErrorMsg(pParse, "no PRIMARY KEY for table %s", p->zName); + }else{ + p->tabFlags |= TF_WithoutRowid; + convertToWithoutRowidTable(pParse, p); + } + } iDb = sqlite3SchemaToIndex(db, p->pSchema); #ifndef SQLITE_OMIT_CHECK /* Resolve names in all CHECK constraint expressions. @@ -1586,20 +1791,10 @@ estimateTableWidth(p); for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){ estimateIndexWidth(pIdx); } - /* If the db->init.busy is 1 it means we are reading the SQL off the - ** "sqlite_master" or "sqlite_temp_master" table on the disk. - ** So do not write to the disk again. Extract the root page number - ** for the table from the db->init.newTnum field. (The page number - ** should have been put there by the sqliteOpenCb routine.) - */ - if( db->init.busy ){ - p->tnum = db->init.newTnum; - } - /* If not initializing, then create a record for the new table ** in the SQLITE_MASTER table of the database. ** ** If this is a TEMPORARY table, write the entry into the auxiliary ** file instead of into the main database file. @@ -1669,11 +1864,13 @@ /* Compute the complete text of the CREATE statement */ if( pSelect ){ zStmt = createTableStmt(db, p); }else{ - n = (int)(pEnd->z - pParse->sNameToken.z) + 1; + Token *pEnd2 = tabOpts ? &pParse->sLastToken : pEnd; + n = (int)(pEnd2->z - pParse->sNameToken.z); + if( pEnd2->z[0]!=';' ) n += pEnd2->n; zStmt = sqlite3MPrintf(db, "CREATE %s %.*s", zType2, n, pParse->sNameToken.z ); } @@ -1817,11 +2014,11 @@ while( ALWAYS(n>0) && sqlite3Isspace(z[n-1]) ){ n--; } sEnd.z = &z[n-1]; sEnd.n = 1; /* Use sqlite3EndTable() to add the view to the SQLITE_MASTER table */ - sqlite3EndTable(pParse, 0, &sEnd, 0); + sqlite3EndTable(pParse, 0, &sEnd, 0, 0); return; } #endif /* SQLITE_OMIT_VIEW */ #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) @@ -2272,12 +2469,12 @@ /* ** This routine is called to create a new foreign key on the table ** currently under construction. pFromCol determines which columns ** in the current table point to the foreign key. If pFromCol==0 then ** connect the key to the last column inserted. pTo is the name of -** the table referred to. pToCol is a list of tables in the other -** pTo table that the foreign key points to. flags contains all +** the table referred to (a.k.a the "parent" table). pToCol is a list +** of tables in the parent pTo table. flags contains all ** information about the conflict resolution algorithms specified ** in the ON DELETE, ON UPDATE and ON INSERT clauses. ** ** An FKey structure is created and added to the table currently ** under construction in the pParse->pNewTable field. @@ -2456,41 +2653,43 @@ if( v==0 ) return; if( memRootPage>=0 ){ tnum = memRootPage; }else{ tnum = pIndex->tnum; - sqlite3VdbeAddOp2(v, OP_Clear, tnum, iDb); } - pKey = sqlite3IndexKeyinfo(pParse, pIndex); - sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, tnum, iDb, - (char *)pKey, P4_KEYINFO_HANDOFF); - sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR|((memRootPage>=0)?OPFLAG_P2ISREG:0)); + pKey = sqlite3KeyInfoOfIndex(pParse, pIndex); /* Open the sorter cursor if we are to use one. */ iSorter = pParse->nTab++; - sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, 0, (char*)pKey, P4_KEYINFO); + sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, 0, (char*) + sqlite3KeyInfoRef(pKey), P4_KEYINFO); /* Open the table. Loop through all rows of the table, inserting index ** records into the sorter. */ sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead); addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0); regRecord = sqlite3GetTempReg(pParse); - sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1, &iPartIdxLabel); + sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 0, &iPartIdxLabel); sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord); sqlite3VdbeResolveLabel(v, iPartIdxLabel); sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1); sqlite3VdbeJumpHere(v, addr1); + if( memRootPage<0 ) sqlite3VdbeAddOp2(v, OP_Clear, tnum, iDb); + sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, tnum, iDb, + (char *)pKey, P4_KEYINFO); + sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR|((memRootPage>=0)?OPFLAG_P2ISREG:0)); + addr1 = sqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0); - if( pIndex->onError!=OE_None ){ + assert( pKey!=0 || db->mallocFailed || pParse->nErr ); + if( pIndex->onError!=OE_None && pKey!=0 ){ int j2 = sqlite3VdbeCurrentAddr(v) + 3; sqlite3VdbeAddOp2(v, OP_Goto, 0, j2); addr2 = sqlite3VdbeCurrentAddr(v); - sqlite3VdbeAddOp3(v, OP_SorterCompare, iSorter, j2, regRecord); - sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_UNIQUE, - OE_Abort, "indexed columns are not unique", P4_STATIC - ); + sqlite3VdbeAddOp4Int(v, OP_SorterCompare, iSorter, j2, regRecord, + pKey->nField - pIndex->nKeyCol); + sqlite3UniqueConstraint(pParse, OE_Abort, pIndex); }else{ addr2 = sqlite3VdbeCurrentAddr(v); } sqlite3VdbeAddOp2(v, OP_SorterData, iSorter, regRecord); sqlite3VdbeAddOp3(v, OP_IdxInsert, iIdx, regRecord, 1); @@ -2501,10 +2700,45 @@ sqlite3VdbeAddOp1(v, OP_Close, iTab); sqlite3VdbeAddOp1(v, OP_Close, iIdx); sqlite3VdbeAddOp1(v, OP_Close, iSorter); } + +/* +** Allocate heap space to hold an Index object with nCol columns. +** +** Increase the allocation size to provide an extra nExtra bytes +** of 8-byte aligned space after the Index object and return a +** pointer to this extra space in *ppExtra. +*/ +Index *sqlite3AllocateIndexObject( + sqlite3 *db, /* Database connection */ + i16 nCol, /* Total number of columns in the index */ + int nExtra, /* Number of bytes of extra space to alloc */ + char **ppExtra /* Pointer to the "extra" space */ +){ + Index *p; /* Allocated index object */ + int nByte; /* Bytes of space for Index object + arrays */ + + nByte = ROUND8(sizeof(Index)) + /* Index structure */ + ROUND8(sizeof(char*)*nCol) + /* Index.azColl */ + ROUND8(sizeof(tRowcnt)*(nCol+1) + /* Index.aiRowEst */ + sizeof(i16)*nCol + /* Index.aiColumn */ + sizeof(u8)*nCol); /* Index.aSortOrder */ + p = sqlite3DbMallocZero(db, nByte + nExtra); + if( p ){ + char *pExtra = ((char*)p)+ROUND8(sizeof(Index)); + p->azColl = (char**)pExtra; pExtra += ROUND8(sizeof(char*)*nCol); + p->aiRowEst = (tRowcnt*)pExtra; pExtra += sizeof(tRowcnt)*(nCol+1); + p->aiColumn = (i16*)pExtra; pExtra += sizeof(i16)*nCol; + p->aSortOrder = (u8*)pExtra; + p->nColumn = nCol; + p->nKeyCol = nCol - 1; + *ppExtra = ((char*)p) + nByte; + } + return p; +} /* ** Create a new index for an SQL table. pName1.pName2 is the name of the index ** and pTblList is the name of the table that is to be indexed. Both will ** be NULL for a primary key or an index that is created to satisfy a @@ -2536,22 +2770,22 @@ Table *pTab = 0; /* Table to be indexed */ Index *pIndex = 0; /* The index to be created */ char *zName = 0; /* Name of the index */ int nName; /* Number of characters in zName */ int i, j; - Token nullId; /* Fake token for an empty ID list */ DbFixer sFix; /* For assigning database names to pTable */ int sortOrderMask; /* 1 to honor DESC in index. 0 to ignore. */ sqlite3 *db = pParse->db; Db *pDb; /* The specific table containing the indexed database */ int iDb; /* Index of the database that is being written */ Token *pName = 0; /* Unqualified name of the index to create */ struct ExprList_item *pListItem; /* For looping over pList */ const Column *pTabCol; /* A column in the table */ - int nCol; /* Number of columns */ int nExtra = 0; /* Space allocated for zExtra[] */ + int nExtraCol; /* Number of extra columns needed */ char *zExtra; /* Extra space after the Index object */ + Index *pPk = 0; /* PRIMARY KEY index for WITHOUT ROWID tables */ assert( pParse->nErr==0 ); /* Never called with prior errors */ if( db->mallocFailed || IN_DECLARE_VTAB ){ goto exit_create_index; } @@ -2599,10 +2833,11 @@ sqlite3ErrorMsg(pParse, "cannot create a TEMP index on non-TEMP table \"%s\"", pTab->zName); goto exit_create_index; } + if( !HasRowid(pTab) ) pPk = sqlite3PrimaryKeyIndex(pTab); }else{ assert( pName==0 ); assert( pStart==0 ); pTab = pParse->pNewTable; if( !pTab ) goto exit_create_index; @@ -2694,15 +2929,14 @@ /* If pList==0, it means this routine was called to make a primary ** key out of the last column added to the table under construction. ** So create a fake list to simulate this. */ if( pList==0 ){ - nullId.z = pTab->aCol[pTab->nCol-1].zName; - nullId.n = sqlite3Strlen30((char*)nullId.z); pList = sqlite3ExprListAppend(pParse, 0, 0); if( pList==0 ) goto exit_create_index; - sqlite3ExprListSetName(pParse, pList, &nullId, 0); + pList->a[0].zName = sqlite3DbStrDup(pParse->db, + pTab->aCol[pTab->nCol-1].zName); pList->a[0].sortOrder = (u8)sortOrder; } /* Figure out how many bytes of space are required to store explicitly ** specified collation sequence names. @@ -2717,40 +2951,27 @@ /* ** Allocate the index structure. */ nName = sqlite3Strlen30(zName); - nCol = pList->nExpr; - pIndex = sqlite3DbMallocZero(db, - ROUND8(sizeof(Index)) + /* Index structure */ - ROUND8(sizeof(tRowcnt)*(nCol+1)) + /* Index.aiRowEst */ - sizeof(char *)*nCol + /* Index.azColl */ - sizeof(int)*nCol + /* Index.aiColumn */ - sizeof(u8)*nCol + /* Index.aSortOrder */ - nName + 1 + /* Index.zName */ - nExtra /* Collation sequence names */ - ); + nExtraCol = pPk ? pPk->nKeyCol : 1; + pIndex = sqlite3AllocateIndexObject(db, pList->nExpr + nExtraCol, + nName + nExtra + 1, &zExtra); if( db->mallocFailed ){ goto exit_create_index; } - zExtra = (char*)pIndex; - pIndex->aiRowEst = (tRowcnt*)&zExtra[ROUND8(sizeof(Index))]; - pIndex->azColl = (char**) - ((char*)pIndex->aiRowEst + ROUND8(sizeof(tRowcnt)*nCol+1)); assert( EIGHT_BYTE_ALIGNMENT(pIndex->aiRowEst) ); assert( EIGHT_BYTE_ALIGNMENT(pIndex->azColl) ); - pIndex->aiColumn = (int *)(&pIndex->azColl[nCol]); - pIndex->aSortOrder = (u8 *)(&pIndex->aiColumn[nCol]); - pIndex->zName = (char *)(&pIndex->aSortOrder[nCol]); - zExtra = (char *)(&pIndex->zName[nName+1]); + pIndex->zName = zExtra; + zExtra += nName + 1; memcpy(pIndex->zName, zName, nName+1); pIndex->pTable = pTab; - pIndex->nColumn = pList->nExpr; pIndex->onError = (u8)onError; - pIndex->uniqNotNull = onError==OE_Abort; + pIndex->uniqNotNull = onError!=OE_None; pIndex->autoIndex = (u8)(pName==0); pIndex->pSchema = db->aDb[iDb].pSchema; + pIndex->nKeyCol = pList->nExpr; if( pPIWhere ){ sqlite3ResolveSelfReference(pParse, pTab, NC_PartIdx, pPIWhere, 0); pIndex->pPartIdxWhere = pPIWhere; pPIWhere = 0; } @@ -2786,11 +3007,12 @@ sqlite3ErrorMsg(pParse, "table %s has no column named %s", pTab->zName, zColName); pParse->checkSchema = 1; goto exit_create_index; } - pIndex->aiColumn[i] = j; + assert( pTab->nCol<=0x7fff && j<=0x7fff ); + pIndex->aiColumn[i] = (i16)j; if( pListItem->pExpr ){ int nColl; assert( pListItem->pExpr->op==TK_COLLATE ); zColl = pListItem->pExpr->u.zToken; nColl = sqlite3Strlen30(zColl) + 1; @@ -2809,10 +3031,27 @@ pIndex->azColl[i] = zColl; requestedSortOrder = pListItem->sortOrder & sortOrderMask; pIndex->aSortOrder[i] = (u8)requestedSortOrder; if( pTab->aCol[j].notNull==0 ) pIndex->uniqNotNull = 0; } + if( pPk ){ + for(j=0; jnKeyCol; j++){ + int x = pPk->aiColumn[j]; + if( hasColumn(pIndex->aiColumn, pIndex->nKeyCol, x) ){ + pIndex->nColumn--; + }else{ + pIndex->aiColumn[i] = x; + pIndex->azColl[i] = pPk->azColl[j]; + pIndex->aSortOrder[i] = pPk->aSortOrder[j]; + i++; + } + } + assert( i==pIndex->nColumn ); + }else{ + pIndex->aiColumn[i] = -1; + pIndex->azColl[i] = "BINARY"; + } sqlite3DefaultRowEst(pIndex); if( pParse->pNewTable==0 ) estimateIndexWidth(pIndex); if( pTab==pParse->pNewTable ){ /* This routine has been called to create an automatic index as a @@ -2841,20 +3080,20 @@ int k; assert( pIdx->onError!=OE_None ); assert( pIdx->autoIndex ); assert( pIndex->onError!=OE_None ); - if( pIdx->nColumn!=pIndex->nColumn ) continue; - for(k=0; knColumn; k++){ + if( pIdx->nKeyCol!=pIndex->nKeyCol ) continue; + for(k=0; knKeyCol; k++){ const char *z1; const char *z2; if( pIdx->aiColumn[k]!=pIndex->aiColumn[k] ) break; z1 = pIdx->azColl[k]; z2 = pIndex->azColl[k]; if( z1!=z2 && sqlite3StrICmp(z1, z2) ) break; } - if( k==pIdx->nColumn ){ + if( k==pIdx->nKeyCol ){ if( pIdx->onError!=pIndex->onError ){ /* This constraint creates the same index as a previous ** constraint specified somewhere in the CREATE TABLE statement. ** However the ON CONFLICT clauses are different. If both this ** constraint and the previous equivalent constraint have explicit @@ -2892,26 +3131,24 @@ if( pTblName!=0 ){ pIndex->tnum = db->init.newTnum; } } - /* If the db->init.busy is 0 then create the index on disk. This - ** involves writing the index into the master table and filling in the - ** index with the current table contents. - ** - ** The db->init.busy is 0 when the user first enters a CREATE INDEX - ** command. db->init.busy is 1 when a database is opened and - ** CREATE INDEX statements are read out of the master table. In - ** the latter case the index already exists on disk, which is why - ** we don't want to recreate it. - ** - ** If pTblName==0 it means this index is generated as a primary key - ** or UNIQUE constraint of a CREATE TABLE statement. Since the table + /* If this is the initial CREATE INDEX statement (or CREATE TABLE if the + ** index is an implied index for a UNIQUE or PRIMARY KEY constraint) then + ** emit code to allocate the index rootpage on disk and make an entry for + ** the index in the sqlite_master table and populate the index with + ** content. But, do not do this if we are simply reading the sqlite_master + ** table to parse the schema, or if this index is the PRIMARY KEY index + ** of a WITHOUT ROWID table. + ** + ** If pTblName==0 it means this index is generated as an implied PRIMARY KEY + ** or UNIQUE index in a CREATE TABLE statement. Since the table ** has just been created, it contains no data and the index initialization ** step can be skipped. */ - else if( pParse->nErr==0 ){ + else if( pParse->nErr==0 && (HasRowid(pTab) || pTblName!=0) ){ Vdbe *v; char *zStmt; int iMem = ++pParse->nMem; v = sqlite3GetVdbe(pParse); @@ -3019,16 +3256,16 @@ tRowcnt n; assert( a!=0 ); a[0] = pIdx->pTable->nRowEst; if( a[0]<10 ) a[0] = 10; n = 10; - for(i=1; i<=pIdx->nColumn; i++){ + for(i=1; i<=pIdx->nKeyCol; i++){ a[i] = n; if( n>5 ) n--; } if( pIdx->onError!=OE_None ){ - a[pIdx->nColumn] = 1; + a[pIdx->nKeyCol] = 1; } } /* ** This routine will drop an existing named index. This routine @@ -3712,18 +3949,71 @@ void sqlite3HaltConstraint( Parse *pParse, /* Parsing context */ int errCode, /* extended error code */ int onError, /* Constraint type */ char *p4, /* Error message */ - int p4type /* P4_STATIC or P4_TRANSIENT */ + i8 p4type, /* P4_STATIC or P4_TRANSIENT */ + u8 p5Errmsg /* P5_ErrMsg type */ ){ Vdbe *v = sqlite3GetVdbe(pParse); assert( (errCode&0xff)==SQLITE_CONSTRAINT ); if( onError==OE_Abort ){ sqlite3MayAbort(pParse); } sqlite3VdbeAddOp4(v, OP_Halt, errCode, onError, 0, p4, p4type); + if( p5Errmsg ) sqlite3VdbeChangeP5(v, p5Errmsg); +} + +/* +** Code an OP_Halt due to UNIQUE or PRIMARY KEY constraint violation. +*/ +void sqlite3UniqueConstraint( + Parse *pParse, /* Parsing context */ + int onError, /* Constraint type */ + Index *pIdx /* The index that triggers the constraint */ +){ + char *zErr; + int j; + StrAccum errMsg; + Table *pTab = pIdx->pTable; + + sqlite3StrAccumInit(&errMsg, 0, 0, 200); + errMsg.db = pParse->db; + for(j=0; jnKeyCol; j++){ + char *zCol = pTab->aCol[pIdx->aiColumn[j]].zName; + if( j ) sqlite3StrAccumAppend(&errMsg, ", ", 2); + sqlite3StrAccumAppend(&errMsg, pTab->zName, -1); + sqlite3StrAccumAppend(&errMsg, ".", 1); + sqlite3StrAccumAppend(&errMsg, zCol, -1); + } + zErr = sqlite3StrAccumFinish(&errMsg); + sqlite3HaltConstraint(pParse, + (pIdx->autoIndex==2)?SQLITE_CONSTRAINT_PRIMARYKEY:SQLITE_CONSTRAINT_UNIQUE, + onError, zErr, P4_DYNAMIC, P5_ConstraintUnique); +} + + +/* +** Code an OP_Halt due to non-unique rowid. +*/ +void sqlite3RowidConstraint( + Parse *pParse, /* Parsing context */ + int onError, /* Conflict resolution algorithm */ + Table *pTab /* The table with the non-unique rowid */ +){ + char *zMsg; + int rc; + if( pTab->iPKey>=0 ){ + zMsg = sqlite3MPrintf(pParse->db, "%s.%s", pTab->zName, + pTab->aCol[pTab->iPKey].zName); + rc = SQLITE_CONSTRAINT_PRIMARYKEY; + }else{ + zMsg = sqlite3MPrintf(pParse->db, "%s.rowid", pTab->zName); + rc = SQLITE_CONSTRAINT_ROWID; + } + sqlite3HaltConstraint(pParse, rc, onError, zMsg, P4_DYNAMIC, + P5_ConstraintUnique); } /* ** Check to see if pIndex uses the collating sequence pColl. Return ** true if it does and false if it does not. @@ -3732,12 +4022,12 @@ static int collationMatch(const char *zColl, Index *pIndex){ int i; assert( zColl!=0 ); for(i=0; inColumn; i++){ const char *z = pIndex->azColl[i]; - assert( z!=0 ); - if( 0==sqlite3StrICmp(z, zColl) ){ + assert( z!=0 || pIndex->aiColumn[i]<0 ); + if( pIndex->aiColumn[i]>=0 && 0==sqlite3StrICmp(z, zColl) ){ return 1; } } return 0; } @@ -3852,35 +4142,49 @@ sqlite3ErrorMsg(pParse, "unable to identify the object to be reindexed"); } #endif /* -** Return a dynamicly allocated KeyInfo structure that can be used -** with OP_OpenRead or OP_OpenWrite to access database index pIdx. -** -** If successful, a pointer to the new structure is returned. In this case -** the caller is responsible for calling sqlite3DbFree(db, ) on the returned -** pointer. If an error occurs (out of memory or missing collation -** sequence), NULL is returned and the state of pParse updated to reflect -** the error. -*/ -KeyInfo *sqlite3IndexKeyinfo(Parse *pParse, Index *pIdx){ - int i; - int nCol = pIdx->nColumn; - KeyInfo *pKey; - - pKey = sqlite3KeyInfoAlloc(pParse->db, nCol); - if( pKey ){ - for(i=0; iazColl[i]; - assert( zColl ); - pKey->aColl[i] = sqlite3LocateCollSeq(pParse, zColl); - pKey->aSortOrder[i] = pIdx->aSortOrder[i]; - } - } - - if( pParse->nErr ){ - sqlite3DbFree(pParse->db, pKey); - pKey = 0; - } - return pKey; +** Return a KeyInfo structure that is appropriate for the given Index. +** +** The KeyInfo structure for an index is cached in the Index object. +** So there might be multiple references to the returned pointer. The +** caller should not try to modify the KeyInfo object. +** +** The caller should invoke sqlite3KeyInfoUnref() on the returned object +** when it has finished using it. +*/ +KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){ + if( pParse->nErr ) return 0; +#ifndef SQLITE_OMIT_SHARED_CACHE + if( pIdx->pKeyInfo && pIdx->pKeyInfo->db!=pParse->db ){ + sqlite3KeyInfoUnref(pIdx->pKeyInfo); + pIdx->pKeyInfo = 0; + } +#endif + if( pIdx->pKeyInfo==0 ){ + int i; + int nCol = pIdx->nColumn; + int nKey = pIdx->nKeyCol; + KeyInfo *pKey; + if( pIdx->uniqNotNull ){ + pKey = sqlite3KeyInfoAlloc(pParse->db, nKey, nCol-nKey); + }else{ + pKey = sqlite3KeyInfoAlloc(pParse->db, nCol, 0); + } + if( pKey ){ + assert( sqlite3KeyInfoIsWriteable(pKey) ); + for(i=0; iazColl[i]; + if( zColl==0 ) zColl = "BINARY"; + pKey->aColl[i] = sqlite3LocateCollSeq(pParse, zColl); + pKey->aSortOrder[i] = pIdx->aSortOrder[i]; + } + if( pParse->nErr ){ + sqlite3KeyInfoUnref(pKey); + }else{ + pIdx->pKeyInfo = pKey; + } + } + } + return sqlite3KeyInfoRef(pIdx->pKeyInfo); } Index: src/delete.c ================================================================== --- src/delete.c +++ src/delete.c @@ -132,11 +132,11 @@ SrcList *pSrc, /* the FROM clause -- which tables to scan */ Expr *pWhere, /* The WHERE clause. May be null */ ExprList *pOrderBy, /* The ORDER BY clause. May be null */ Expr *pLimit, /* The LIMIT clause. May be null */ Expr *pOffset, /* The OFFSET clause. May be null */ - char *zStmtType /* Either DELETE or UPDATE. For error messages. */ + char *zStmtType /* Either DELETE or UPDATE. For err msgs. */ ){ Expr *pWhereRowid = NULL; /* WHERE rowid .. */ Expr *pInClause = NULL; /* WHERE rowid IN ( select ) */ Expr *pSelectRowid = NULL; /* SELECT rowid ... */ ExprList *pEList = NULL; /* Expression list contaning only pSelectRowid */ @@ -207,11 +207,12 @@ sqlite3ExprListDelete(pParse->db, pOrderBy); sqlite3ExprDelete(pParse->db, pLimit); sqlite3ExprDelete(pParse->db, pOffset); return 0; } -#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) */ +#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */ + /* && !defined(SQLITE_OMIT_SUBQUERY) */ /* ** Generate code for a DELETE FROM statement. ** ** DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL; @@ -228,11 +229,13 @@ const char *zDb; /* Name of database holding pTab */ int end, addr = 0; /* A couple addresses of generated code */ int i; /* Loop counter */ WhereInfo *pWInfo; /* Information about the WHERE clause */ Index *pIdx; /* For looping over indices of the table */ - int iCur; /* VDBE Cursor number for pTab */ + int iTabCur; /* Cursor number for the table */ + int iDataCur; /* VDBE cursor for the canonical data source */ + int iIdxCur; /* Cursor number of the first index */ sqlite3 *db; /* Main database structure */ AuthContext sContext; /* Authorization context */ NameContext sNC; /* Name context to resolve expressions in */ int iDb; /* Database number */ int memCnt = -1; /* Memory cell used for change counting */ @@ -293,11 +296,11 @@ assert(!isView || pTrigger); /* Assign cursor number to the table and all its indices. */ assert( pTabList->nSrc==1 ); - iCur = pTabList->a[0].iCursor = pParse->nTab++; + iTabCur = pTabList->a[0].iCursor = pParse->nTab++; for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ pParse->nTab++; } /* Start the view context @@ -318,11 +321,12 @@ /* If we are trying to delete from a view, realize that view into ** a ephemeral table. */ #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) if( isView ){ - sqlite3MaterializeView(pParse, pTab, pWhere, iCur); + sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur); + iDataCur = iIdxCur = iTabCur; } #endif /* Resolve the column names in the WHERE clause. */ @@ -349,22 +353,78 @@ if( rcauth==SQLITE_OK && pWhere==0 && !pTrigger && !IsVirtual(pTab) && 0==sqlite3FkRequired(pParse, pTab, 0, 0) ){ assert( !isView ); sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName); - sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt, - pTab->zName, P4_STATIC); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt, + pTab->zName, P4_STATIC); + } for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ assert( pIdx->pSchema==pTab->pSchema ); sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb); } }else #endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */ - /* The usual case: There is a WHERE clause so we have to scan through - ** the table and pick which records to delete. - */ - { + if( !HasRowid(pTab) ){ + /* There is a WHERE clause on a WITHOUT ROWID table. + */ + Index *pPk; /* The PRIMARY KEY index on the table */ + int iPk; /* First of nPk memory cells holding PRIMARY KEY value */ + int iEph; /* Ephemeral table holding all primary key values */ + int iKey; /* Key value inserting into iEph */ + i16 nPk; /* Number of components of the PRIMARY KEY */ + + pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + nPk = pPk->nKeyCol; + iPk = pParse->nMem+1; + pParse->nMem += nPk; + iKey = ++pParse->nMem; + iEph = pParse->nTab++; + + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, 0, 0); + if( pWInfo==0 ) goto delete_from_cleanup; + for(i=0; iaiColumn[i],iPk+i); + } + sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, + sqlite3IndexAffinityStr(v, pPk), P4_TRANSIENT); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iEph, iKey); + if( db->flags & SQLITE_CountRows ){ + sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1); + } + sqlite3WhereEnd(pWInfo); + + /* Open cursors for all indices of the table. + */ + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, + iTabCur, &iDataCur, &iIdxCur); + + /* Loop over the primary keys to be deleted. */ + addr = sqlite3VdbeAddOp1(v, OP_Rewind, iEph); + sqlite3VdbeAddOp2(v, OP_RowKey, iEph, iPk); + + /* Delete the row */ + sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + iPk, 0, 1, OE_Default); + + /* End of the delete loop */ + sqlite3VdbeAddOp2(v, OP_Next, iEph, addr+1); + sqlite3VdbeJumpHere(v, addr); + + /* Close the cursors open on the table and its indexes. */ + assert( iDataCur>=iIdxCur ); + for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + sqlite3VdbeAddOp1(v, OP_Close, iIdxCur+i); + } + }else{ + /* There is a WHERE clause on a rowid table. Run a loop that extracts + ** all rowids to be deleted into a RowSet. + */ int iRowSet = ++pParse->nMem; /* Register for rowset of rows to delete */ int iRowid = ++pParse->nMem; /* Used for storing rowid values. */ int regRowid; /* Actual register containing rowids */ /* Collect rowids of every row to be deleted. @@ -372,11 +432,11 @@ sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet); pWInfo = sqlite3WhereBegin( pParse, pTabList, pWhere, 0, 0, WHERE_DUPLICATES_OK, 0 ); if( pWInfo==0 ) goto delete_from_cleanup; - regRowid = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iCur, iRowid, 0); + regRowid = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iTabCur, iRowid, 0); sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, regRowid); if( db->flags & SQLITE_CountRows ){ sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1); } sqlite3WhereEnd(pWInfo); @@ -389,11 +449,14 @@ /* Unless this is a view, open cursors for the table we are ** deleting from and all its indices. If this is a view, then the ** only effect this statement has is to fire the INSTEAD OF ** triggers. */ if( !isView ){ - sqlite3OpenTableAndIndices(pParse, pTab, iCur, OP_OpenWrite); + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, iTabCur, + &iDataCur, &iIdxCur); + assert( iDataCur==iTabCur ); + assert( iIdxCur==iDataCur+1 ); } addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, end, iRowid); /* Delete the row */ @@ -406,23 +469,24 @@ sqlite3MayAbort(pParse); }else #endif { int count = (pParse->nested==0); /* True to count changes */ - sqlite3GenerateRowDelete(pParse, pTab, iCur, iRowid, count, pTrigger, OE_Default); + sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + iRowid, 1, count, OE_Default); } /* End of the delete loop */ sqlite3VdbeAddOp2(v, OP_Goto, 0, addr); sqlite3VdbeResolveLabel(v, end); /* Close the cursors open on the table and its indexes. */ if( !isView && !IsVirtual(pTab) ){ - for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - sqlite3VdbeAddOp2(v, OP_Close, iCur + i, pIdx->tnum); + sqlite3VdbeAddOp1(v, OP_Close, iDataCur); + for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + sqlite3VdbeAddOp1(v, OP_Close, iIdxCur + i); } - sqlite3VdbeAddOp1(v, OP_Close, iCur); } } /* Update the sqlite_sequence table by storing the content of the ** maximum rowid counter values recorded while inserting into @@ -458,48 +522,55 @@ #undef pTrigger #endif /* ** This routine generates VDBE code that causes a single row of a -** single table to be deleted. +** single table to be deleted. Both the original table entry and +** all indices are removed. ** -** The VDBE must be in a particular state when this routine is called. -** These are the requirements: +** Preconditions: ** -** 1. A read/write cursor pointing to pTab, the table containing the row -** to be deleted, must be opened as cursor number $iCur. +** 1. iDataCur is an open cursor on the btree that is the canonical data +** store for the table. (This will be either the table itself, +** in the case of a rowid table, or the PRIMARY KEY index in the case +** of a WITHOUT ROWID table.) ** ** 2. Read/write cursors for all indices of pTab must be open as -** cursor number base+i for the i-th index. +** cursor number iIdxCur+i for the i-th index. ** -** 3. The record number of the row to be deleted must be stored in -** memory cell iRowid. -** -** This routine generates code to remove both the table record and all -** index entries that point to that record. +** 3. The primary key for the row to be deleted must be stored in a +** sequence of nPk memory cells starting at iPk. If nPk==0 that means +** that a search record formed from OP_MakeRecord is contained in the +** single memory location iPk. */ void sqlite3GenerateRowDelete( Parse *pParse, /* Parsing context */ Table *pTab, /* Table containing the row to be deleted */ - int iCur, /* Cursor number for the table */ - int iRowid, /* Memory cell that contains the rowid to delete */ - int count, /* If non-zero, increment the row change counter */ Trigger *pTrigger, /* List of triggers to (potentially) fire */ - int onconf /* Default ON CONFLICT policy for triggers */ + int iDataCur, /* Cursor from which column data is extracted */ + int iIdxCur, /* First index cursor */ + int iPk, /* First memory cell containing the PRIMARY KEY */ + i16 nPk, /* Number of PRIMARY KEY memory cells */ + u8 count, /* If non-zero, increment the row change counter */ + u8 onconf /* Default ON CONFLICT policy for triggers */ ){ Vdbe *v = pParse->pVdbe; /* Vdbe */ int iOld = 0; /* First register in OLD.* array */ int iLabel; /* Label resolved to end of generated code */ + u8 opSeek; /* Seek opcode */ /* Vdbe is guaranteed to have been allocated by this stage. */ assert( v ); + VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)", + iDataCur, iIdxCur, iPk, (int)nPk)); /* Seek cursor iCur to the row to delete. If this row no longer exists ** (this can happen if a trigger program has already deleted it), do ** not attempt to delete it or fire any DELETE triggers. */ iLabel = sqlite3VdbeMakeLabel(v); - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, iLabel, iRowid); + opSeek = HasRowid(pTab) ? OP_NotExists : OP_NotFound; + sqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk); /* If there are any triggers to fire, allocate a range of registers to ** use for the old.* references in the triggers. */ if( sqlite3FkRequired(pParse, pTab, 0, 0) || pTrigger ){ u32 mask; /* Mask of OLD.* columns in use */ @@ -514,14 +585,14 @@ iOld = pParse->nMem+1; pParse->nMem += (1 + pTab->nCol); /* Populate the OLD.* pseudo-table register array. These values will be ** used by any BEFORE and AFTER triggers that exist. */ - sqlite3VdbeAddOp2(v, OP_Copy, iRowid, iOld); + sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld); for(iCol=0; iColnCol; iCol++){ if( mask==0xffffffff || mask&(1<pSelect==0 ){ - sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, 0); - sqlite3VdbeAddOp2(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0)); + sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, 0); + sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); if( count ){ sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT); } } @@ -563,53 +634,66 @@ /* Jump here if the row had already been deleted before any BEFORE ** trigger programs were invoked. Or if a trigger program throws a ** RAISE(IGNORE) exception. */ sqlite3VdbeResolveLabel(v, iLabel); + VdbeModuleComment((v, "END: GenRowDel()")); } /* ** This routine generates VDBE code that causes the deletion of all -** index entries associated with a single row of a single table. +** index entries associated with a single row of a single table, pTab ** -** The VDBE must be in a particular state when this routine is called. -** These are the requirements: +** Preconditions: ** -** 1. A read/write cursor pointing to pTab, the table containing the row -** to be deleted, must be opened as cursor number "iCur". +** 1. A read/write cursor "iDataCur" must be open on the canonical storage +** btree for the table pTab. (This will be either the table itself +** for rowid tables or to the primary key index for WITHOUT ROWID +** tables.) ** ** 2. Read/write cursors for all indices of pTab must be open as -** cursor number iCur+i for the i-th index. +** cursor number iIdxCur+i for the i-th index. (The pTab->pIndex +** index is the 0-th index.) ** -** 3. The "iCur" cursor must be pointing to the row that is to be -** deleted. +** 3. The "iDataCur" cursor must be already be positioned on the row +** that is to be deleted. */ void sqlite3GenerateRowIndexDelete( Parse *pParse, /* Parsing and code generating context */ Table *pTab, /* Table containing the row to be deleted */ - int iCur, /* Cursor number for the table */ + int iDataCur, /* Cursor of table holding data. */ + int iIdxCur, /* First index cursor */ int *aRegIdx /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */ ){ - int i; - Index *pIdx; - int r1; - int iPartIdxLabel; - Vdbe *v = pParse->pVdbe; - - for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - if( aRegIdx!=0 && aRegIdx[i-1]==0 ) continue; - r1 = sqlite3GenerateIndexKey(pParse, pIdx, iCur, 0, 0, &iPartIdxLabel); - sqlite3VdbeAddOp3(v, OP_IdxDelete, iCur+i, r1, pIdx->nColumn+1); + int i; /* Index loop counter */ + int r1; /* Register holding an index key */ + int iPartIdxLabel; /* Jump destination for skipping partial index entries */ + Index *pIdx; /* Current index */ + Vdbe *v; /* The prepared statement under construction */ + Index *pPk; /* PRIMARY KEY index, or NULL for rowid tables */ + + v = pParse->pVdbe; + VdbeModuleComment((v, "BEGIN: GenRowIdxDel(%d,%d)", iDataCur, iIdxCur)); + pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); + for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + assert( iIdxCur+i!=iDataCur || pPk==pIdx ); + if( aRegIdx!=0 && aRegIdx[i]==0 ) continue; + if( pIdx==pPk ) continue; + r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 1, &iPartIdxLabel); + sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1, + pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn); sqlite3VdbeResolveLabel(v, iPartIdxLabel); } + VdbeModuleComment((v, "END: GenRowIdxDel()")); } /* -** Generate code that will assemble an index key and put it in register +** Generate code that will assemble an index key and stores it in register ** regOut. The key with be for index pIdx which is an index on pTab. ** iCur is the index of a cursor open on the pTab table and pointing to -** the entry that needs indexing. +** the entry that needs indexing. If pTab is a WITHOUT ROWID table, then +** iCur must be the cursor of the PRIMARY KEY index. ** ** Return a register number which is the first in a block of ** registers that holds the elements of the index key. The ** block of registers has already been deallocated by the time ** this routine returns. @@ -622,53 +706,55 @@ ** sqlite3VdbeResolveLabel(). */ int sqlite3GenerateIndexKey( Parse *pParse, /* Parsing context */ Index *pIdx, /* The index for which to generate a key */ - int iCur, /* Cursor number for the pIdx->pTable table */ - int regOut, /* Write the new index key to this register */ - int doMakeRec, /* Run the OP_MakeRecord instruction if true */ + int iDataCur, /* Cursor number from which to take column data */ + int regOut, /* Put the new key into this register if not 0 */ + int prefixOnly, /* Compute only a unique prefix of the key */ int *piPartIdxLabel /* OUT: Jump to this label to skip partial index */ ){ Vdbe *v = pParse->pVdbe; int j; Table *pTab = pIdx->pTable; int regBase; int nCol; + Index *pPk; if( piPartIdxLabel ){ if( pIdx->pPartIdxWhere ){ *piPartIdxLabel = sqlite3VdbeMakeLabel(v); - pParse->iPartIdxTab = iCur; + pParse->iPartIdxTab = iDataCur; sqlite3ExprIfFalse(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel, SQLITE_JUMPIFNULL); }else{ *piPartIdxLabel = 0; } } - nCol = pIdx->nColumn; - regBase = sqlite3GetTempRange(pParse, nCol+1); - sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regBase+nCol); + nCol = (prefixOnly && pIdx->uniqNotNull) ? pIdx->nKeyCol : pIdx->nColumn; + regBase = sqlite3GetTempRange(pParse, nCol); + pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); for(j=0; jaiColumn[j]; - if( idx==pTab->iPKey ){ - sqlite3VdbeAddOp2(v, OP_SCopy, regBase+nCol, regBase+j); + i16 idx = pIdx->aiColumn[j]; + if( pPk ) idx = sqlite3ColumnOfIndex(pPk, idx); + if( idx<0 || idx==pTab->iPKey ){ + sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regBase+j); }else{ - sqlite3VdbeAddOp3(v, OP_Column, iCur, idx, regBase+j); - sqlite3ColumnDefault(v, pTab, idx, -1); + sqlite3VdbeAddOp3(v, OP_Column, iDataCur, idx, regBase+j); + sqlite3ColumnDefault(v, pTab, pIdx->aiColumn[j], -1); } } - if( doMakeRec ){ + if( regOut ){ const char *zAff; if( pTab->pSelect || OptimizationDisabled(pParse->db, SQLITE_IdxRealAsInt) ){ zAff = 0; }else{ zAff = sqlite3IndexAffinityStr(v, pIdx); } - sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol+1, regOut); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut); sqlite3VdbeChangeP4(v, -1, zAff, P4_TRANSIENT); } - sqlite3ReleaseTempRange(pParse, regBase, nCol+1); + sqlite3ReleaseTempRange(pParse, regBase, nCol); return regBase; } Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -1530,20 +1530,20 @@ p = (ExprHasProperty(pX, EP_xIsSelect) ? pX->x.pSelect : 0); if( ALWAYS(pParse->nErr==0) && isCandidateForInOpt(p) ){ sqlite3 *db = pParse->db; /* Database connection */ Table *pTab; /* Table . */ Expr *pExpr; /* Expression */ - int iCol; /* Index of column */ - int iDb; /* Database idx for pTab */ + i16 iCol; /* Index of column */ + i16 iDb; /* Database idx for pTab */ assert( p ); /* Because of isCandidateForInOpt(p) */ assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */ pTab = p->pSrc->a[0].pTab; pExpr = p->pEList->a[0].pExpr; - iCol = pExpr->iColumn; + iCol = (i16)pExpr->iColumn; /* Code an OP_VerifyCookie and OP_TableLock for
. */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); sqlite3CodeVerifySchema(pParse, iDb); sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); @@ -1577,20 +1577,15 @@ int affinity_ok = sqlite3IndexAffinityOk(pX, pTab->aCol[iCol].affinity); for(pIdx=pTab->pIndex; pIdx && eType==0 && affinity_ok; pIdx=pIdx->pNext){ if( (pIdx->aiColumn[0]==iCol) && sqlite3FindCollSeq(db, ENC(db), pIdx->azColl[0], 0)==pReq - && (!mustBeUnique || (pIdx->nColumn==1 && pIdx->onError!=OE_None)) + && (!mustBeUnique || (pIdx->nKeyCol==1 && pIdx->onError!=OE_None)) ){ - int iAddr; - char *pKey; - - pKey = (char *)sqlite3IndexKeyinfo(pParse, pIdx); - iAddr = sqlite3CodeOnce(pParse); - - sqlite3VdbeAddOp4(v, OP_OpenRead, iTab, pIdx->tnum, iDb, - pKey,P4_KEYINFO_HANDOFF); + int iAddr = sqlite3CodeOnce(pParse); + sqlite3VdbeAddOp3(v, OP_OpenRead, iTab, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 ); eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0]; sqlite3VdbeJumpHere(v, iAddr); @@ -1726,11 +1721,11 @@ ** is used. */ pExpr->iTable = pParse->nTab++; addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, !isRowid); if( rMayHaveNull==0 ) sqlite3VdbeChangeP5(v, BTREE_UNORDERED); - pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, 1); + pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, 1, 1); if( ExprHasProperty(pExpr, EP_xIsSelect) ){ /* Case 1: expr IN (SELECT ...) ** ** Generate code to write the results of the select into the temporary @@ -1744,17 +1739,18 @@ dest.affSdst = (u8)affinity; assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable ); pExpr->x.pSelect->iLimit = 0; testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ if( sqlite3Select(pParse, pExpr->x.pSelect, &dest) ){ - sqlite3DbFree(pParse->db, pKeyInfo); + sqlite3KeyInfoUnref(pKeyInfo); return 0; } pEList = pExpr->x.pSelect->pEList; assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ assert( pEList!=0 ); assert( pEList->nExpr>0 ); + assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); pKeyInfo->aColl[0] = sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, pEList->a[0].pExpr); }else if( ALWAYS(pExpr->x.pList!=0) ){ /* Case 2: expr IN (exprlist) ** @@ -1770,10 +1766,11 @@ if( !affinity ){ affinity = SQLITE_AFF_NONE; } if( pKeyInfo ){ + assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); pKeyInfo->aColl[0] = sqlite3ExprCollSeq(pParse, pExpr->pLeft); } /* Loop through each expression in . */ r1 = sqlite3GetTempReg(pParse); @@ -1811,11 +1808,11 @@ } sqlite3ReleaseTempReg(pParse, r1); sqlite3ReleaseTempReg(pParse, r2); } if( pKeyInfo ){ - sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO_HANDOFF); + sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO); } break; } case TK_EXISTS: @@ -2212,19 +2209,23 @@ ** Generate code to extract the value of the iCol-th column of a table. */ void sqlite3ExprCodeGetColumnOfTable( Vdbe *v, /* The VDBE under construction */ Table *pTab, /* The table containing the value */ - int iTabCur, /* The cursor for this table */ + int iTabCur, /* The table cursor. Or the PK cursor for WITHOUT ROWID */ int iCol, /* Index of the column to extract */ - int regOut /* Extract the valud into this register */ + int regOut /* Extract the value into this register */ ){ if( iCol<0 || iCol==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut); }else{ int op = IsVirtual(pTab) ? OP_VColumn : OP_Column; - sqlite3VdbeAddOp3(v, op, iTabCur, iCol, regOut); + int x = iCol; + if( !HasRowid(pTab) ){ + x = sqlite3ColumnOfIndex(sqlite3PrimaryKeyIndex(pTab), iCol); + } + sqlite3VdbeAddOp3(v, op, iTabCur, x, regOut); } if( iCol>=0 ){ sqlite3ColumnDefault(v, pTab, iCol, regOut); } } @@ -2966,11 +2967,11 @@ if( pExpr->affinity==OE_Ignore ){ sqlite3VdbeAddOp4( v, OP_Halt, SQLITE_OK, OE_Ignore, 0, pExpr->u.zToken,0); }else{ sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_TRIGGER, - pExpr->affinity, pExpr->u.zToken, 0); + pExpr->affinity, pExpr->u.zToken, 0, 0); } break; } #endif Index: src/fkey.c ================================================================== --- src/fkey.c +++ src/fkey.c @@ -223,11 +223,11 @@ if( !aiCol ) return 1; *paiCol = aiCol; } for(pIdx=pParent->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->nColumn==nCol && pIdx->onError!=OE_None ){ + if( pIdx->nKeyCol==nCol && pIdx->onError!=OE_None ){ /* pIdx is a UNIQUE index (or a PRIMARY KEY) and has the right number ** of columns. If each indexed column corresponds to a foreign key ** column of pFKey, then this index is a winner. */ if( zKey==0 ){ @@ -246,11 +246,11 @@ ** map to an explicit list of columns in table pParent. Check if this ** index matches those columns. Also, check that the index uses ** the default collation sequences for each column. */ int i, j; for(i=0; iaiColumn[i]; /* Index of column in parent tbl */ + i16 iCol = pIdx->aiColumn[i]; /* Index of column in parent tbl */ char *zDfltColl; /* Def. collation for column */ char *zIdxCol; /* Name of indexed column */ /* If the index uses a collation sequence that is different from ** the default collation sequence for the column, this index is @@ -377,14 +377,13 @@ sqlite3ReleaseTempReg(pParse, regTemp); }else{ int nCol = pFKey->nCol; int regTemp = sqlite3GetTempRange(pParse, nCol); int regRec = sqlite3GetTempReg(pParse); - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb); - sqlite3VdbeChangeP4(v, -1, (char*)pKey, P4_KEYINFO_HANDOFF); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); for(i=0; i0 && pFKey->isDeferred==0 ){ sqlite3ParseToplevel(pParse)->mayAbort = 1; } sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); @@ -443,10 +441,66 @@ sqlite3VdbeResolveLabel(v, iOk); sqlite3VdbeAddOp1(v, OP_Close, iCur); } + +/* +** Return an Expr object that refers to a memory register corresponding +** to column iCol of table pTab. +** +** regBase is the first of an array of register that contains the data +** for pTab. regBase itself holds the rowid. regBase+1 holds the first +** column. regBase+2 holds the second column, and so forth. +*/ +static Expr *exprTableRegister( + Parse *pParse, /* Parsing and code generating context */ + Table *pTab, /* The table whose content is at r[regBase]... */ + int regBase, /* Contents of table pTab */ + i16 iCol /* Which column of pTab is desired */ +){ + Expr *pExpr; + Column *pCol; + const char *zColl; + sqlite3 *db = pParse->db; + + pExpr = sqlite3Expr(db, TK_REGISTER, 0); + if( pExpr ){ + if( iCol>=0 && iCol!=pTab->iPKey ){ + pCol = &pTab->aCol[iCol]; + pExpr->iTable = regBase + iCol + 1; + pExpr->affinity = pCol->affinity; + zColl = pCol->zColl; + if( zColl==0 ) zColl = db->pDfltColl->zName; + pExpr = sqlite3ExprAddCollateString(pParse, pExpr, zColl); + }else{ + pExpr->iTable = regBase; + pExpr->affinity = SQLITE_AFF_INTEGER; + } + } + return pExpr; +} + +/* +** Return an Expr object that refers to column iCol of table pTab which +** has cursor iCur. +*/ +static Expr *exprTableColumn( + sqlite3 *db, /* The database connection */ + Table *pTab, /* The table whose column is desired */ + int iCursor, /* The open cursor on the table */ + i16 iCol /* The column that is wanted */ +){ + Expr *pExpr = sqlite3Expr(db, TK_COLUMN, 0); + if( pExpr ){ + pExpr->pTab = pTab; + pExpr->iTable = iCursor; + pExpr->iColumn = iCol; + } + return pExpr; +} + /* ** This function is called to generate code executed when a row is deleted ** from the parent table of foreign key constraint pFKey and, if pFKey is ** deferred, when a row is inserted into the same table. When generating ** code for an SQL UPDATE operation, this function may be called twice - @@ -458,31 +512,31 @@ ** ** Operation | FK type | Action taken ** -------------------------------------------------------------------------- ** DELETE immediate Increment the "immediate constraint counter". ** Or, if the ON (UPDATE|DELETE) action is RESTRICT, -** throw a "foreign key constraint failed" exception. +** throw a "FOREIGN KEY constraint failed" exception. ** ** INSERT immediate Decrement the "immediate constraint counter". ** ** DELETE deferred Increment the "deferred constraint counter". ** Or, if the ON (UPDATE|DELETE) action is RESTRICT, -** throw a "foreign key constraint failed" exception. +** throw a "FOREIGN KEY constraint failed" exception. ** ** INSERT deferred Decrement the "deferred constraint counter". ** ** These operations are identified in the comment at the top of this file ** (fkey.c) as "I.2" and "D.2". */ static void fkScanChildren( Parse *pParse, /* Parse context */ - SrcList *pSrc, /* SrcList containing the table to scan */ - Table *pTab, - Index *pIdx, /* Foreign key index */ - FKey *pFKey, /* Foreign key relationship */ + SrcList *pSrc, /* The child table to be scanned */ + Table *pTab, /* The parent table */ + Index *pIdx, /* Index on parent covering the foreign key */ + FKey *pFKey, /* The foreign key linking pSrc to pTab */ int *aiCol, /* Map from pIdx cols to child table cols */ - int regData, /* Referenced table data starts here */ + int regData, /* Parent row data starts here */ int nIncr /* Amount to increment deferred counter by */ ){ sqlite3 *db = pParse->db; /* Database handle */ int i; /* Iterator variable */ Expr *pWhere = 0; /* WHERE clause to scan with */ @@ -489,11 +543,13 @@ NameContext sNameContext; /* Context used to resolve WHERE clause */ WhereInfo *pWInfo; /* Context used by sqlite3WhereXXX() */ int iFkIfZero = 0; /* Address of OP_FkIfZero */ Vdbe *v = sqlite3GetVdbe(pParse); - assert( !pIdx || pIdx->pTable==pTab ); + assert( pIdx==0 || pIdx->pTable==pTab ); + assert( pIdx==0 || pIdx->nKeyCol==pFKey->nCol ); + assert( pIdx!=0 || pFKey->nCol==1 ); if( nIncr<0 ){ iFkIfZero = sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, 0); } @@ -507,59 +563,56 @@ */ for(i=0; inCol; i++){ Expr *pLeft; /* Value from parent table row */ Expr *pRight; /* Column ref to child table */ Expr *pEq; /* Expression (pLeft = pRight) */ - int iCol; /* Index of column in child table */ + i16 iCol; /* Index of column in child table */ const char *zCol; /* Name of column in child table */ - pLeft = sqlite3Expr(db, TK_REGISTER, 0); - if( pLeft ){ - /* Set the collation sequence and affinity of the LHS of each TK_EQ - ** expression to the parent key column defaults. */ - if( pIdx ){ - Column *pCol; - const char *zColl; - iCol = pIdx->aiColumn[i]; - pCol = &pTab->aCol[iCol]; - if( pTab->iPKey==iCol ) iCol = -1; - pLeft->iTable = regData+iCol+1; - pLeft->affinity = pCol->affinity; - zColl = pCol->zColl; - if( zColl==0 ) zColl = db->pDfltColl->zName; - pLeft = sqlite3ExprAddCollateString(pParse, pLeft, zColl); - }else{ - pLeft->iTable = regData; - pLeft->affinity = SQLITE_AFF_INTEGER; - } - } + iCol = pIdx ? pIdx->aiColumn[i] : -1; + pLeft = exprTableRegister(pParse, pTab, regData, iCol); iCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom; assert( iCol>=0 ); zCol = pFKey->pFrom->aCol[iCol].zName; pRight = sqlite3Expr(db, TK_ID, zCol); pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0); pWhere = sqlite3ExprAnd(db, pWhere, pEq); } - /* If the child table is the same as the parent table, and this scan - ** is taking place as part of a DELETE operation (operation D.2), omit the - ** row being deleted from the scan by adding ($rowid != rowid) to the WHERE - ** clause, where $rowid is the rowid of the row being deleted. */ + /* If the child table is the same as the parent table, then add terms + ** to the WHERE clause that prevent this entry from being scanned. + ** The added WHERE clause terms are like this: + ** + ** $current_rowid!=rowid + ** NOT( $current_a==a AND $current_b==b AND ... ) + ** + ** The first form is used for rowid tables. The second form is used + ** for WITHOUT ROWID tables. In the second form, the primary key is + ** (a,b,...) + */ if( pTab==pFKey->pFrom && nIncr>0 ){ - Expr *pEq; /* Expression (pLeft = pRight) */ + Expr *pNe; /* Expression (pLeft != pRight) */ Expr *pLeft; /* Value from parent table row */ Expr *pRight; /* Column ref to child table */ - pLeft = sqlite3Expr(db, TK_REGISTER, 0); - pRight = sqlite3Expr(db, TK_COLUMN, 0); - if( pLeft && pRight ){ - pLeft->iTable = regData; - pLeft->affinity = SQLITE_AFF_INTEGER; - pRight->iTable = pSrc->a[0].iCursor; - pRight->iColumn = -1; - } - pEq = sqlite3PExpr(pParse, TK_NE, pLeft, pRight, 0); - pWhere = sqlite3ExprAnd(db, pWhere, pEq); + if( HasRowid(pTab) ){ + pLeft = exprTableRegister(pParse, pTab, regData, -1); + pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, -1); + pNe = sqlite3PExpr(pParse, TK_NE, pLeft, pRight, 0); + }else{ + int i; + Expr *pEq, *pAll = 0; + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + for(i=0; inKeyCol; i++){ + i16 iCol = pIdx->aiColumn[i]; + pLeft = exprTableRegister(pParse, pTab, regData, iCol); + pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, iCol); + pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0); + pAll = sqlite3ExprAnd(db, pAll, pEq); + } + pNe = sqlite3PExpr(pParse, TK_NOT, pAll, 0, 0); + } + pWhere = sqlite3ExprAnd(db, pWhere, pNe); } /* Resolve the references in the WHERE clause. */ memset(&sNameContext, 0, sizeof(NameContext)); sNameContext.pSrcList = pSrc; @@ -585,12 +638,12 @@ sqlite3VdbeJumpHere(v, iFkIfZero); } } /* -** This function returns a pointer to the head of a linked list of FK -** constraints for which table pTab is the parent table. For example, +** This function returns a linked list of FKey objects (connected by +** FKey.pNextTo) holding all children of table pTab. For example, ** given the following schema: ** ** CREATE TABLE t1(a PRIMARY KEY); ** CREATE TABLE t2(b REFERENCES t1(a); ** @@ -677,12 +730,11 @@ ** constraints are violated. */ if( (db->flags & SQLITE_DeferFKs)==0 ){ sqlite3VdbeAddOp2(v, OP_FkIfZero, 0, sqlite3VdbeCurrentAddr(v)+2); sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY, - OE_Abort, "foreign key constraint failed", P4_STATIC - ); + OE_Abort, 0, P4_STATIC, P5_ConstraintFK); } if( iSkip ){ sqlite3VdbeResolveLabel(v, iSkip); } @@ -888,11 +940,12 @@ } sqlite3DbFree(db, aiFree); } - /* Loop through all the foreign key constraints that refer to this table */ + /* Loop through all the foreign key constraints that refer to this table. + ** (the "child" constraints) */ for(pFKey = sqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){ Index *pIdx = 0; /* Foreign key index for pFKey */ SrcList *pSrc; int *aiCol = 0; @@ -913,13 +966,12 @@ if( !isIgnoreErrors || db->mallocFailed ) return; continue; } assert( aiCol || pFKey->nCol==1 ); - /* Create a SrcList structure containing a single table (the table - ** the foreign key that refers to this table is attached to). This - ** is required for the sqlite3WhereXXX() interface. */ + /* Create a SrcList structure containing the child table. We need the + ** child table as a SrcList for sqlite3WhereBegin() */ pSrc = sqlite3SrcListAppend(db, 0, 0, 0); if( pSrc ){ struct SrcList_item *pItem = pSrc->a; pItem->pTab = pFKey->pFrom; pItem->zName = pFKey->pFrom->zName; @@ -964,11 +1016,11 @@ } for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ Index *pIdx = 0; sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0); if( pIdx ){ - for(i=0; inColumn; i++) mask |= COLUMN_MASK(pIdx->aiColumn[i]); + for(i=0; inKeyCol; i++) mask |= COLUMN_MASK(pIdx->aiColumn[i]); } } } return mask; } @@ -1156,11 +1208,11 @@ Token tFrom; Expr *pRaise; tFrom.z = zFrom; tFrom.n = nFrom; - pRaise = sqlite3Expr(db, TK_RAISE, "foreign key constraint failed"); + pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed"); if( pRaise ){ pRaise->affinity = OE_Abort; } pSelect = sqlite3SelectNew(pParse, sqlite3ExprListAppend(pParse, 0, pRaise), Index: src/insert.c ================================================================== --- src/insert.c +++ src/insert.c @@ -13,27 +13,42 @@ ** to handle INSERT statements in SQLite. */ #include "sqliteInt.h" /* -** Generate code that will open a table for reading. +** Generate code that will +** +** (1) acquire a lock for table pTab then +** (2) open pTab as cursor iCur. +** +** If pTab is a WITHOUT ROWID table, then it is the PRIMARY KEY index +** for that table that is actually opened. */ void sqlite3OpenTable( - Parse *p, /* Generate code into this VDBE */ + Parse *pParse, /* Generate code into this VDBE */ int iCur, /* The cursor number of the table */ int iDb, /* The database index in sqlite3.aDb[] */ Table *pTab, /* The table to be opened */ int opcode /* OP_OpenRead or OP_OpenWrite */ ){ Vdbe *v; assert( !IsVirtual(pTab) ); - v = sqlite3GetVdbe(p); + v = sqlite3GetVdbe(pParse); assert( opcode==OP_OpenWrite || opcode==OP_OpenRead ); - sqlite3TableLock(p, iDb, pTab->tnum, (opcode==OP_OpenWrite)?1:0, pTab->zName); - sqlite3VdbeAddOp3(v, opcode, iCur, pTab->tnum, iDb); - sqlite3VdbeChangeP4(v, -1, SQLITE_INT_TO_PTR(pTab->nCol), P4_INT32); - VdbeComment((v, "%s", pTab->zName)); + sqlite3TableLock(pParse, iDb, pTab->tnum, + (opcode==OP_OpenWrite)?1:0, pTab->zName); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp4Int(v, opcode, iCur, pTab->tnum, iDb, pTab->nCol); + VdbeComment((v, "%s", pTab->zName)); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + assert( pPk->tnum=pTab->tnum ); + sqlite3VdbeAddOp3(v, opcode, iCur, pPk->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + VdbeComment((v, "%s", pTab->zName)); + } } /* ** Return a pointer to the column affinity string associated with index ** pIdx. A column affinity string has one character for each column in @@ -65,19 +80,19 @@ ** up. */ int n; Table *pTab = pIdx->pTable; sqlite3 *db = sqlite3VdbeDb(v); - pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, pIdx->nColumn+2); + pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, pIdx->nColumn+1); if( !pIdx->zColAff ){ db->mallocFailed = 1; return 0; } for(n=0; nnColumn; n++){ - pIdx->zColAff[n] = pTab->aCol[pIdx->aiColumn[n]].affinity; + i16 x = pIdx->aiColumn[n]; + pIdx->zColAff[n] = x<0 ? SQLITE_AFF_INTEGER : pTab->aCol[x].affinity; } - pIdx->zColAff[n++] = SQLITE_AFF_INTEGER; pIdx->zColAff[n] = 0; } return pIdx->zColAff; } @@ -419,11 +434,11 @@ int onError, /* How to handle constraint errors */ int iDbDest /* The database of pDest */ ); /* -** This routine is call to handle SQL of the following forms: +** This routine is called to handle SQL of the following forms: ** ** insert into TABLE (IDLIST) values(EXPRLIST) ** insert into TABLE (IDLIST) select ** ** The IDLIST following the table name is always optional. If omitted, @@ -434,16 +449,16 @@ ** statement above, and pSelect is NULL. For the second form, pList is ** NULL and pSelect is a pointer to the select statement used to generate ** data for the insert. ** ** The code generated follows one of four templates. For a simple -** select with data coming from a VALUES clause, the code executes +** insert with data coming from a VALUES clause, the code executes ** once straight down through. Pseudo-code follows (we call this ** the "1st template"): ** ** open write cursor to
and its indices -** puts VALUES clause expressions onto the stack +** put VALUES clause expressions into registers ** write the resulting record into
** cleanup ** ** The three remaining templates assume the statement is of the form ** @@ -537,12 +552,13 @@ int i, j, idx; /* Loop counters */ Vdbe *v; /* Generate code into this virtual machine */ Index *pIdx; /* For looping over indices of the table */ int nColumn; /* Number of columns in the data */ int nHidden = 0; /* Number of hidden columns if TABLE is virtual */ - int baseCur = 0; /* VDBE Cursor number for pTab */ - int keyColumn = -1; /* Column that is the INTEGER PRIMARY KEY */ + int iDataCur = 0; /* VDBE cursor that is the main data repository */ + int iIdxCur = 0; /* First index cursor */ + int ipkColumn = -1; /* Column that is the INTEGER PRIMARY KEY */ int endOfLoop; /* Label for the end of the insertion loop */ int useTempTable = 0; /* Store SELECT results in intermediate table */ int srcTab = 0; /* Data comes from this temporary cursor if >=0 */ int addrInsTop = 0; /* Jump to label "D" */ int addrCont = 0; /* Top of insert loop. Label "C" in templates 3 and 4 */ @@ -549,10 +565,11 @@ int addrSelect = 0; /* Address of coroutine that implements the SELECT */ SelectDest dest; /* Destination for SELECT on rhs of INSERT */ int iDb; /* Index of database holding TABLE */ Db *pDb; /* The database containing table being inserted into */ int appendFlag = 0; /* True if the insert is likely to be an append */ + int withoutRowid; /* 0 for normal table. 1 for WITHOUT ROWID table */ /* Register allocations */ int regFromSelect = 0;/* Base register for data coming from SELECT */ int regAutoinc = 0; /* Register holding the AUTOINCREMENT counter */ int regRowCount = 0; /* Memory cell used for the row counter */ @@ -588,10 +605,11 @@ pDb = &db->aDb[iDb]; zDb = pDb->zName; if( sqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, zDb) ){ goto insert_cleanup; } + withoutRowid = !HasRowid(pTab); /* Figure out if we have any triggers and if the table being ** inserted into is a view */ #ifndef SQLITE_OMIT_TRIGGER @@ -607,20 +625,17 @@ # define isView 0 #endif assert( (pTrigger && tmask) || (pTrigger==0 && tmask==0) ); /* If pTab is really a view, make sure it has been initialized. - ** ViewGetColumnNames() is a no-op if pTab is not a view (or virtual - ** module table). + ** ViewGetColumnNames() is a no-op if pTab is not a view. */ if( sqlite3ViewGetColumnNames(pParse, pTab) ){ goto insert_cleanup; } - /* Ensure that: - * (a) the table is not read-only, - * (b) that if it is a view then ON INSERT triggers exist + /* Cannot insert into a read-only table. */ if( sqlite3IsReadOnly(pParse, pTab, tmask) ){ goto insert_cleanup; } @@ -657,12 +672,11 @@ ** is coming from a SELECT statement, then generate a co-routine that ** produces a single row of the SELECT on each invocation. The ** co-routine is the common header to the 3rd and 4th templates. */ if( pSelect ){ - /* Data is coming from a SELECT. Generate a co-routine to run that - ** SELECT. */ + /* Data is coming from a SELECT. Generate a co-routine to run the SELECT */ int rc = sqlite3CodeCoroutine(pParse, pSelect, &dest); if( rc ) goto insert_cleanup; regEof = dest.iSDParm + 1; regFromSelect = dest.iSdst; @@ -670,11 +684,11 @@ nColumn = pSelect->pEList->nExpr; assert( dest.nSdst==nColumn ); /* Set useTempTable to TRUE if the result of the SELECT statement ** should be written into a temporary table (template 4). Set to - ** FALSE if each* row of the SELECT can be written directly into + ** FALSE if each output row of the SELECT can be written directly into ** the destination table (template 3). ** ** A temp table must be used if the table being updated is also one ** of the tables being read by the SELECT statement. Also use a ** temp table in the case of row triggers. @@ -753,15 +767,15 @@ /* If the INSERT statement included an IDLIST term, then make sure ** all elements of the IDLIST really are columns of the table and ** remember the column indices. ** ** If the table has an INTEGER PRIMARY KEY column and that column - ** is named in the IDLIST, then record in the keyColumn variable - ** the index into IDLIST of the primary key column. keyColumn is + ** is named in the IDLIST, then record in the ipkColumn variable + ** the index into IDLIST of the primary key column. ipkColumn is ** the index of the primary key as it appears in IDLIST, not as - ** is appears in the original table. (The index of the primary - ** key in the original table is pTab->iPKey.) + ** is appears in the original table. (The index of the INTEGER + ** PRIMARY KEY in the original table is pTab->iPKey.) */ if( pColumn ){ for(i=0; inId; i++){ pColumn->a[i].idx = -1; } @@ -768,18 +782,18 @@ for(i=0; inId; i++){ for(j=0; jnCol; j++){ if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){ pColumn->a[i].idx = j; if( j==pTab->iPKey ){ - keyColumn = i; + ipkColumn = i; assert( !withoutRowid ); } break; } } if( j>=pTab->nCol ){ - if( sqlite3IsRowid(pColumn->a[i].zName) ){ - keyColumn = i; + if( sqlite3IsRowid(pColumn->a[i].zName) && !withoutRowid ){ + ipkColumn = i; }else{ sqlite3ErrorMsg(pParse, "table %S has no column named %s", pTabList, 0, pColumn->a[i].zName); pParse->checkSchema = 1; goto insert_cleanup; @@ -787,15 +801,15 @@ } } } /* If there is no IDLIST term but the table has an integer primary - ** key, the set the keyColumn variable to the primary key column index - ** in the original table definition. + ** key, the set the ipkColumn variable to the integer primary key + ** column index in the original table definition. */ if( pColumn==0 && nColumn>0 ){ - keyColumn = pTab->iPKey; + ipkColumn = pTab->iPKey; } /* Initialize the count of rows to be inserted */ if( db->flags & SQLITE_CountRows ){ @@ -804,13 +818,12 @@ } /* If this is not a view, open the table and and all indices */ if( !isView ){ int nIdx; - - baseCur = pParse->nTab; - nIdx = sqlite3OpenTableAndIndices(pParse, pTab, baseCur, OP_OpenWrite); + nIdx = sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, -1, + &iDataCur, &iIdxCur); aRegIdx = sqlite3DbMallocRaw(db, sizeof(int)*(nIdx+1)); if( aRegIdx==0 ){ goto insert_cleanup; } for(i=0; ia[keyColumn].pExpr, regCols); + sqlite3ExprCode(pParse, pList->a[ipkColumn].pExpr, regCols); } j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regCols); sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols); sqlite3VdbeJumpHere(v, j1); sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); @@ -924,33 +938,31 @@ pTab, regCols-pTab->nCol-1, onError, endOfLoop); sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol+1); } - /* Push the record number for the new entry onto the stack. The - ** record number is a randomly generate integer created by NewRowid - ** except when the table has an INTEGER PRIMARY KEY column, in which - ** case the record number is the same as that column. + /* Compute the content of the next row to insert into a range of + ** registers beginning at regIns. */ if( !isView ){ if( IsVirtual(pTab) ){ /* The row that the VUpdate opcode will delete: none */ sqlite3VdbeAddOp2(v, OP_Null, 0, regIns); } - if( keyColumn>=0 ){ + if( ipkColumn>=0 ){ if( useTempTable ){ - sqlite3VdbeAddOp3(v, OP_Column, srcTab, keyColumn, regRowid); + sqlite3VdbeAddOp3(v, OP_Column, srcTab, ipkColumn, regRowid); }else if( pSelect ){ - sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+keyColumn, regRowid); + sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+ipkColumn, regRowid); }else{ VdbeOp *pOp; - sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr, regRowid); + sqlite3ExprCode(pParse, pList->a[ipkColumn].pExpr, regRowid); pOp = sqlite3VdbeGetOp(v, -1); if( ALWAYS(pOp) && pOp->opcode==OP_Null && !IsVirtual(pTab) ){ appendFlag = 1; pOp->opcode = OP_NewRowid; - pOp->p1 = baseCur; + pOp->p1 = iDataCur; pOp->p2 = regRowid; pOp->p3 = regAutoinc; } } /* If the PRIMARY KEY expression is NULL, then use OP_NewRowid @@ -958,36 +970,36 @@ */ if( !appendFlag ){ int j1; if( !IsVirtual(pTab) ){ j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regRowid); - sqlite3VdbeAddOp3(v, OP_NewRowid, baseCur, regRowid, regAutoinc); + sqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc); sqlite3VdbeJumpHere(v, j1); }else{ j1 = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp2(v, OP_IsNull, regRowid, j1+2); } sqlite3VdbeAddOp1(v, OP_MustBeInt, regRowid); } - }else if( IsVirtual(pTab) ){ + }else if( IsVirtual(pTab) || withoutRowid ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regRowid); }else{ - sqlite3VdbeAddOp3(v, OP_NewRowid, baseCur, regRowid, regAutoinc); + sqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc); appendFlag = 1; } autoIncStep(pParse, regAutoinc, regRowid); - /* Push onto the stack, data for all columns of the new entry, beginning + /* Compute data for all columns of the new entry, beginning ** with the first column. */ nHidden = 0; for(i=0; inCol; i++){ int iRegStore = regRowid+1+i; if( i==pTab->iPKey ){ /* The value of the INTEGER PRIMARY KEY column is always a NULL. - ** Whenever this column is read, the record number will be substituted - ** in its place. So will fill this column with a NULL to avoid + ** Whenever this column is read, the rowid will be substituted + ** in its place. Hence, fill this column with a NULL to avoid ** taking up data space with information that will never be used. */ sqlite3VdbeAddOp2(v, OP_Null, 0, iRegStore); continue; } if( pColumn==0 ){ @@ -1026,17 +1038,16 @@ sqlite3MayAbort(pParse); }else #endif { int isReplace; /* Set to true if constraints may cause a replace */ - sqlite3GenerateConstraintChecks(pParse, pTab, baseCur, regIns, aRegIdx, - keyColumn>=0, 0, onError, endOfLoop, &isReplace + sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, + regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace ); sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); - sqlite3CompleteInsertion( - pParse, pTab, baseCur, regIns, aRegIdx, 0, appendFlag, isReplace==0 - ); + sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur, + regIns, aRegIdx, 0, appendFlag, isReplace==0); } } /* Update the count of rows that are inserted */ @@ -1063,13 +1074,13 @@ sqlite3VdbeJumpHere(v, addrInsTop); } if( !IsVirtual(pTab) && !isView ){ /* Close all tables opened */ - sqlite3VdbeAddOp1(v, OP_Close, baseCur); - for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ - sqlite3VdbeAddOp1(v, OP_Close, idx+baseCur); + if( iDataCurpIndex; pIdx; pIdx=pIdx->pNext, idx++){ + sqlite3VdbeAddOp1(v, OP_Close, idx+iIdxCur); } } insert_end: /* Update the sqlite_sequence table by storing the content of the @@ -1110,65 +1121,78 @@ #endif #ifdef tmask #undef tmask #endif - /* -** Generate code to do constraint checks prior to an INSERT or an UPDATE. -** -** The input is a range of consecutive registers as follows: -** -** 1. The rowid of the row after the update. -** -** 2. The data in the first column of the entry after the update. -** -** i. Data from middle columns... -** -** N. The data in the last column of the entry after the update. -** -** The regRowid parameter is the index of the register containing (1). -** -** If isUpdate is true and rowidChng is non-zero, then rowidChng contains -** the address of a register containing the rowid before the update takes -** place. isUpdate is true for UPDATEs and false for INSERTs. If isUpdate -** is false, indicating an INSERT statement, then a non-zero rowidChng -** indicates that the rowid was explicitly specified as part of the -** INSERT statement. If rowidChng is false, it means that the rowid is -** computed automatically in an insert or that the rowid value is not -** modified by an update. -** -** The code generated by this routine store new index entries into +** Generate code to do constraint checks prior to an INSERT or an UPDATE +** on table pTab. +** +** The regNewData parameter is the first register in a range that contains +** the data to be inserted or the data after the update. There will be +** pTab->nCol+1 registers in this range. The first register (the one +** that regNewData points to) will contain the new rowid, or NULL in the +** case of a WITHOUT ROWID table. The second register in the range will +** contain the content of the first table column. The third register will +** contain the content of the second table column. And so forth. +** +** The regOldData parameter is similar to regNewData except that it contains +** the data prior to an UPDATE rather than afterwards. regOldData is zero +** for an INSERT. This routine can distinguish between UPDATE and INSERT by +** checking regOldData for zero. +** +** For an UPDATE, the pkChng boolean is true if the true primary key (the +** rowid for a normal table or the PRIMARY KEY for a WITHOUT ROWID table) +** might be modified by the UPDATE. If pkChng is false, then the key of +** the iDataCur content table is guaranteed to be unchanged by the UPDATE. +** +** For an INSERT, the pkChng boolean indicates whether or not the rowid +** was explicitly specified as part of the INSERT statement. If pkChng +** is zero, it means that the either rowid is computed automatically or +** that the table is a WITHOUT ROWID table and has no rowid. On an INSERT, +** pkChng will only be true if the INSERT statement provides an integer +** value for either the rowid column or its INTEGER PRIMARY KEY alias. +** +** The code generated by this routine will store new index entries into ** registers identified by aRegIdx[]. No index entry is created for ** indices where aRegIdx[i]==0. The order of indices in aRegIdx[] is ** the same as the order of indices on the linked list of indices -** attached to the table. +** at pTab->pIndex. +** +** The caller must have already opened writeable cursors on the main +** table and all applicable indices (that is to say, all indices for which +** aRegIdx[] is not zero). iDataCur is the cursor for the main table when +** inserting or updating a rowid table, or the cursor for the PRIMARY KEY +** index when operating on a WITHOUT ROWID table. iIdxCur is the cursor +** for the first index in the pTab->pIndex list. Cursors for other indices +** are at iIdxCur+N for the N-th element of the pTab->pIndex list. ** ** This routine also generates code to check constraints. NOT NULL, ** CHECK, and UNIQUE constraints are all checked. If a constraint fails, ** then the appropriate action is performed. There are five possible ** actions: ROLLBACK, ABORT, FAIL, REPLACE, and IGNORE. ** ** Constraint type Action What Happens ** --------------- ---------- ---------------------------------------- ** any ROLLBACK The current transaction is rolled back and -** sqlite3_exec() returns immediately with a +** sqlite3_step() returns immediately with a ** return code of SQLITE_CONSTRAINT. ** ** any ABORT Back out changes from the current command ** only (do not do a complete rollback) then -** cause sqlite3_exec() to return immediately +** cause sqlite3_step() to return immediately ** with SQLITE_CONSTRAINT. ** -** any FAIL Sqlite3_exec() returns immediately with a +** any FAIL Sqlite3_step() returns immediately with a ** return code of SQLITE_CONSTRAINT. The ** transaction is not rolled back and any -** prior changes are retained. +** changes to prior rows are retained. ** -** any IGNORE The record number and data is popped from -** the stack and there is an immediate jump -** to label ignoreDest. +** any IGNORE The attempt in insert or update the current +** row is skipped, without throwing an error. +** Processing continues with the next row. +** (There is an immediate jump to ignoreDest.) ** ** NOT NULL REPLACE The NULL value is replace by the default ** value for that column. If the default value ** is NULL, the action is the same as ABORT. ** @@ -1179,48 +1203,61 @@ ** ** Which action to take is determined by the overrideError parameter. ** Or if overrideError==OE_Default, then the pParse->onError parameter ** is used. Or if pParse->onError==OE_Default then the onError value ** for the constraint is used. -** -** The calling routine must open a read/write cursor for pTab with -** cursor number "baseCur". All indices of pTab must also have open -** read/write cursors with cursor number baseCur+i for the i-th cursor. -** Except, if there is no possibility of a REPLACE action then -** cursors do not need to be open for indices where aRegIdx[i]==0. */ void sqlite3GenerateConstraintChecks( - Parse *pParse, /* The parser context */ - Table *pTab, /* the table into which we are inserting */ - int baseCur, /* Index of a read/write cursor pointing at pTab */ - int regRowid, /* Index of the range of input registers */ - int *aRegIdx, /* Register used by each index. 0 for unused indices */ - int rowidChng, /* True if the rowid might collide with existing entry */ - int isUpdate, /* True for UPDATE, False for INSERT */ - int overrideError, /* Override onError to this if not OE_Default */ - int ignoreDest, /* Jump to this label on an OE_Ignore resolution */ - int *pbMayReplace /* OUT: Set to true if constraint may cause a replace */ + Parse *pParse, /* The parser context */ + Table *pTab, /* The table being inserted or updated */ + int *aRegIdx, /* Use register aRegIdx[i] for index i. 0 for unused */ + int iDataCur, /* Canonical data cursor (main table or PK index) */ + int iIdxCur, /* First index cursor */ + int regNewData, /* First register in a range holding values to insert */ + int regOldData, /* Previous content. 0 for INSERTs */ + u8 pkChng, /* Non-zero if the rowid or PRIMARY KEY changed */ + u8 overrideError, /* Override onError to this if not OE_Default */ + int ignoreDest, /* Jump to this label on an OE_Ignore resolution */ + int *pbMayReplace /* OUT: Set to true if constraint may cause a replace */ ){ - int i; /* loop counter */ - Vdbe *v; /* VDBE under constrution */ - int nCol; /* Number of columns */ - int onError; /* Conflict resolution strategy */ - int j1; /* Addresss of jump instruction */ - int j2 = 0, j3; /* Addresses of jump instructions */ - int regData; /* Register containing first data column */ - int iCur; /* Table cursor number */ + Vdbe *v; /* VDBE under constrution */ Index *pIdx; /* Pointer to one of the indices */ + Index *pPk = 0; /* The PRIMARY KEY index */ sqlite3 *db; /* Database connection */ + int i; /* loop counter */ + int ix; /* Index loop counter */ + int nCol; /* Number of columns */ + int onError; /* Conflict resolution strategy */ + int j1; /* Addresss of jump instruction */ int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */ - int regOldRowid = (rowidChng && isUpdate) ? rowidChng : regRowid; + int nPkField; /* Number of fields in PRIMARY KEY. 1 for ROWID tables */ + int ipkTop = 0; /* Top of the rowid change constraint check */ + int ipkBottom = 0; /* Bottom of the rowid change constraint check */ + u8 isUpdate; /* True if this is an UPDATE operation */ + isUpdate = regOldData!=0; db = pParse->db; v = sqlite3GetVdbe(pParse); assert( v!=0 ); assert( pTab->pSelect==0 ); /* This table is not a VIEW */ nCol = pTab->nCol; - regData = regRowid + 1; + + /* pPk is the PRIMARY KEY index for WITHOUT ROWID tables and NULL for + ** normal rowid tables. nPkField is the number of key fields in the + ** pPk index or 1 for a rowid table. In other words, nPkField is the + ** number of fields in the true primary key of the table. */ + if( HasRowid(pTab) ){ + pPk = 0; + nPkField = 1; + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + nPkField = pPk->nKeyCol; + } + + /* Record that this module has started */ + VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)", + iDataCur, iIdxCur, regNewData, regOldData, pkChng)); /* Test all NOT NULL constraints. */ for(i=0; iiPKey ){ @@ -1239,28 +1276,28 @@ assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail || onError==OE_Ignore || onError==OE_Replace ); switch( onError ){ case OE_Abort: sqlite3MayAbort(pParse); + /* Fall through */ case OE_Rollback: case OE_Fail: { - char *zMsg; - sqlite3VdbeAddOp3(v, OP_HaltIfNull, - SQLITE_CONSTRAINT_NOTNULL, onError, regData+i); - zMsg = sqlite3MPrintf(db, "%s.%s may not be NULL", - pTab->zName, pTab->aCol[i].zName); - sqlite3VdbeChangeP4(v, -1, zMsg, P4_DYNAMIC); + char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, + pTab->aCol[i].zName); + sqlite3VdbeAddOp4(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, + regNewData+1+i, zMsg, P4_DYNAMIC); + sqlite3VdbeChangeP5(v, P5_ConstraintNotNull); break; } case OE_Ignore: { - sqlite3VdbeAddOp2(v, OP_IsNull, regData+i, ignoreDest); + sqlite3VdbeAddOp2(v, OP_IsNull, regNewData+1+i, ignoreDest); break; } default: { assert( onError==OE_Replace ); - j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regData+i); - sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regData+i); + j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regNewData+1+i); + sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regNewData+1+i); sqlite3VdbeJumpHere(v, j1); break; } } } @@ -1268,59 +1305,79 @@ /* Test all CHECK constraints */ #ifndef SQLITE_OMIT_CHECK if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ ExprList *pCheck = pTab->pCheck; - pParse->ckBase = regData; + pParse->ckBase = regNewData+1; onError = overrideError!=OE_Default ? overrideError : OE_Abort; for(i=0; inExpr; i++){ int allOk = sqlite3VdbeMakeLabel(v); sqlite3ExprIfTrue(pParse, pCheck->a[i].pExpr, allOk, SQLITE_JUMPIFNULL); if( onError==OE_Ignore ){ sqlite3VdbeAddOp2(v, OP_Goto, 0, ignoreDest); }else{ - char *zConsName = pCheck->a[i].zName; + char *zName = pCheck->a[i].zName; + if( zName==0 ) zName = pTab->zName; if( onError==OE_Replace ) onError = OE_Abort; /* IMP: R-15569-63625 */ - if( zConsName ){ - zConsName = sqlite3MPrintf(db, "constraint %s failed", zConsName); - }else{ - zConsName = 0; - } sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_CHECK, - onError, zConsName, P4_DYNAMIC); + onError, zName, P4_TRANSIENT, + P5_ConstraintCheck); } sqlite3VdbeResolveLabel(v, allOk); } } #endif /* !defined(SQLITE_OMIT_CHECK) */ - /* If we have an INTEGER PRIMARY KEY, make sure the primary key - ** of the new record does not previously exist. Except, if this - ** is an UPDATE and the primary key is not changing, that is OK. + /* If rowid is changing, make sure the new rowid does not previously + ** exist in the table. */ - if( rowidChng ){ + if( pkChng && pPk==0 ){ + int addrRowidOk = sqlite3VdbeMakeLabel(v); + + /* Figure out what action to take in case of a rowid collision */ onError = pTab->keyConf; if( overrideError!=OE_Default ){ onError = overrideError; }else if( onError==OE_Default ){ onError = OE_Abort; } - + if( isUpdate ){ - j2 = sqlite3VdbeAddOp3(v, OP_Eq, regRowid, 0, rowidChng); + /* pkChng!=0 does not mean that the rowid has change, only that + ** it might have changed. Skip the conflict logic below if the rowid + ** is unchanged. */ + sqlite3VdbeAddOp3(v, OP_Eq, regNewData, addrRowidOk, regOldData); + } + + /* If the response to a rowid conflict is REPLACE but the response + ** to some other UNIQUE constraint is FAIL or IGNORE, then we need + ** to defer the running of the rowid conflict checking until after + ** the UNIQUE constraints have run. + */ + if( onError==OE_Replace && overrideError!=OE_Replace ){ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->onError==OE_Ignore || pIdx->onError==OE_Fail ){ + ipkTop = sqlite3VdbeAddOp0(v, OP_Goto); + break; + } + } } - j3 = sqlite3VdbeAddOp3(v, OP_NotExists, baseCur, 0, regRowid); + + /* Check to see if the new rowid already exists in the table. Skip + ** the following conflict logic if it does not. */ + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, addrRowidOk, regNewData); + + /* Generate code that deals with a rowid collision */ switch( onError ){ default: { onError = OE_Abort; /* Fall thru into the next case */ } case OE_Rollback: case OE_Abort: case OE_Fail: { - sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_PRIMARYKEY, - onError, "PRIMARY KEY must be unique", P4_STATIC); + sqlite3RowidConstraint(pParse, onError, pTab); break; } case OE_Replace: { /* If there are DELETE triggers on this table and the ** recursive-triggers flag is set, call GenerateRowDelete() to @@ -1348,123 +1405,163 @@ if( db->flags&SQLITE_RecTriggers ){ pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); } if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){ sqlite3MultiWrite(pParse); - sqlite3GenerateRowDelete( - pParse, pTab, baseCur, regRowid, 0, pTrigger, OE_Replace - ); + sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + regNewData, 1, 0, OE_Replace); }else if( pTab->pIndex ){ sqlite3MultiWrite(pParse); - sqlite3GenerateRowIndexDelete(pParse, pTab, baseCur, 0); + sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, 0); } seenReplace = 1; break; } case OE_Ignore: { - assert( seenReplace==0 ); + /*assert( seenReplace==0 );*/ sqlite3VdbeAddOp2(v, OP_Goto, 0, ignoreDest); break; } } - sqlite3VdbeJumpHere(v, j3); - if( isUpdate ){ - sqlite3VdbeJumpHere(v, j2); + sqlite3VdbeResolveLabel(v, addrRowidOk); + if( ipkTop ){ + ipkBottom = sqlite3VdbeAddOp0(v, OP_Goto); + sqlite3VdbeJumpHere(v, ipkTop); } } /* Test all UNIQUE constraints by creating entries for each UNIQUE ** index and making sure that duplicate entries do not already exist. - ** Add the new records to the indices as we go. + ** Compute the revised record entries for indices as we go. + ** + ** This loop also handles the case of the PRIMARY KEY index for a + ** WITHOUT ROWID table. */ - for(iCur=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, iCur++){ - int regIdx; - int regR; - int addrSkipRow = 0; + for(ix=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, ix++){ + int regIdx; /* Range of registers hold conent for pIdx */ + int regR; /* Range of registers holding conflicting PK */ + int iThisCur; /* Cursor for this UNIQUE index */ + int addrUniqueOk; /* Jump here if the UNIQUE constraint is satisfied */ + + if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */ + iThisCur = iIdxCur+ix; + addrUniqueOk = sqlite3VdbeMakeLabel(v); - if( aRegIdx[iCur]==0 ) continue; /* Skip unused indices */ - + /* Skip partial indices for which the WHERE clause is not true */ if( pIdx->pPartIdxWhere ){ - sqlite3VdbeAddOp2(v, OP_Null, 0, aRegIdx[iCur]); - addrSkipRow = sqlite3VdbeMakeLabel(v); - pParse->ckBase = regData; - sqlite3ExprIfFalse(pParse, pIdx->pPartIdxWhere, addrSkipRow, + sqlite3VdbeAddOp2(v, OP_Null, 0, aRegIdx[ix]); + pParse->ckBase = regNewData+1; + sqlite3ExprIfFalse(pParse, pIdx->pPartIdxWhere, addrUniqueOk, SQLITE_JUMPIFNULL); pParse->ckBase = 0; } - /* Create a key for accessing the index entry */ - regIdx = sqlite3GetTempRange(pParse, pIdx->nColumn+1); + /* Create a record for this index entry as it should appear after + ** the insert or update. Store that record in the aRegIdx[ix] register + */ + regIdx = sqlite3GetTempRange(pParse, pIdx->nColumn); for(i=0; inColumn; i++){ - int idx = pIdx->aiColumn[i]; - if( idx==pTab->iPKey ){ - sqlite3VdbeAddOp2(v, OP_SCopy, regRowid, regIdx+i); + int iField = pIdx->aiColumn[i]; + int x; + if( iField<0 || iField==pTab->iPKey ){ + x = regNewData; }else{ - sqlite3VdbeAddOp2(v, OP_SCopy, regData+idx, regIdx+i); + x = iField + regNewData + 1; } + sqlite3VdbeAddOp2(v, OP_SCopy, x, regIdx+i); + VdbeComment((v, "%s", iField<0 ? "rowid" : pTab->aCol[iField].zName)); } - sqlite3VdbeAddOp2(v, OP_SCopy, regRowid, regIdx+i); - sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn+1, aRegIdx[iCur]); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn, aRegIdx[ix]); sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v, pIdx), P4_TRANSIENT); - sqlite3ExprCacheAffinityChange(pParse, regIdx, pIdx->nColumn+1); + VdbeComment((v, "for %s", pIdx->zName)); + sqlite3ExprCacheAffinityChange(pParse, regIdx, pIdx->nColumn); + + /* In an UPDATE operation, if this index is the PRIMARY KEY index + ** of a WITHOUT ROWID table and there has been no change the + ** primary key, then no collision is possible. The collision detection + ** logic below can all be skipped. */ + if( isUpdate && pPk==pIdx && pkChng==0 ){ + sqlite3VdbeResolveLabel(v, addrUniqueOk); + continue; + } - /* Find out what action to take in case there is an indexing conflict */ + /* Find out what action to take in case there is a uniqueness conflict */ onError = pIdx->onError; if( onError==OE_None ){ - sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn+1); - sqlite3VdbeResolveLabel(v, addrSkipRow); + sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn); + sqlite3VdbeResolveLabel(v, addrUniqueOk); continue; /* pIdx is not a UNIQUE index */ } if( overrideError!=OE_Default ){ onError = overrideError; }else if( onError==OE_Default ){ onError = OE_Abort; } - if( seenReplace ){ - if( onError==OE_Ignore ) onError = OE_Replace; - else if( onError==OE_Fail ) onError = OE_Abort; - } /* Check to see if the new index entry will be unique */ - regR = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp2(v, OP_SCopy, regOldRowid, regR); - j3 = sqlite3VdbeAddOp4(v, OP_IsUnique, baseCur+iCur+1, 0, - regR, SQLITE_INT_TO_PTR(regIdx), - P4_INT32); - sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn+1); + regR = sqlite3GetTempRange(pParse, nPkField); + sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur, addrUniqueOk, + regIdx, pIdx->nKeyCol); + + /* Generate code to handle collisions */ + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_IdxRowid, iThisCur, regR); + /* Conflict only if the rowid of the existing index entry + ** is different from old-rowid */ + if( isUpdate ){ + sqlite3VdbeAddOp3(v, OP_Eq, regR, addrUniqueOk, regOldData); + } + }else{ + int x; + /* Extract the PRIMARY KEY from the end of the index entry and + ** store it in registers regR..regR+nPk-1 */ + if( isUpdate || onError==OE_Replace ){ + for(i=0; inKeyCol; i++){ + x = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[i]); + sqlite3VdbeAddOp3(v, OP_Column, iThisCur, x, regR+i); + VdbeComment((v, "%s.%s", pTab->zName, + pTab->aCol[pPk->aiColumn[i]].zName)); + } + } + if( isUpdate ){ + /* If currently processing the PRIMARY KEY of a WITHOUT ROWID + ** table, only conflict if the new PRIMARY KEY values are actually + ** different from the old. + ** + ** For a UNIQUE index, only conflict if the PRIMARY KEY values + ** of the matched index row are different from the original PRIMARY + ** KEY values of this row before the update. */ + int addrJump = sqlite3VdbeCurrentAddr(v)+pPk->nKeyCol; + int op = OP_Ne; + int regCmp = (pIdx->autoIndex==2 ? regIdx : regR); + + for(i=0; inKeyCol; i++){ + char *p4 = (char*)sqlite3LocateCollSeq(pParse, pPk->azColl[i]); + x = pPk->aiColumn[i]; + if( i==(pPk->nKeyCol-1) ){ + addrJump = addrUniqueOk; + op = OP_Eq; + } + sqlite3VdbeAddOp4(v, op, + regOldData+1+x, addrJump, regCmp+i, p4, P4_COLLSEQ + ); + } + } + } + sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn); /* Generate code that executes if the new index entry is not unique */ assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail || onError==OE_Ignore || onError==OE_Replace ); switch( onError ){ case OE_Rollback: case OE_Abort: case OE_Fail: { - int j; - StrAccum errMsg; - const char *zSep; - char *zErr; - - sqlite3StrAccumInit(&errMsg, 0, 0, 200); - errMsg.db = db; - zSep = pIdx->nColumn>1 ? "columns " : "column "; - for(j=0; jnColumn; j++){ - char *zCol = pTab->aCol[pIdx->aiColumn[j]].zName; - sqlite3StrAccumAppend(&errMsg, zSep, -1); - zSep = ", "; - sqlite3StrAccumAppend(&errMsg, zCol, -1); - } - sqlite3StrAccumAppend(&errMsg, - pIdx->nColumn>1 ? " are not unique" : " is not unique", -1); - zErr = sqlite3StrAccumFinish(&errMsg); - sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_UNIQUE, - onError, zErr, 0); - sqlite3DbFree(errMsg.db, zErr); + sqlite3UniqueConstraint(pParse, onError, pIdx); break; } case OE_Ignore: { - assert( seenReplace==0 ); sqlite3VdbeAddOp2(v, OP_Goto, 0, ignoreDest); break; } default: { Trigger *pTrigger = 0; @@ -1471,67 +1568,75 @@ assert( onError==OE_Replace ); sqlite3MultiWrite(pParse); if( db->flags&SQLITE_RecTriggers ){ pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); } - sqlite3GenerateRowDelete( - pParse, pTab, baseCur, regR, 0, pTrigger, OE_Replace - ); + sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + regR, nPkField, 0, OE_Replace); seenReplace = 1; break; } } - sqlite3VdbeJumpHere(v, j3); - sqlite3VdbeResolveLabel(v, addrSkipRow); - sqlite3ReleaseTempReg(pParse, regR); + sqlite3VdbeResolveLabel(v, addrUniqueOk); + sqlite3ReleaseTempRange(pParse, regR, nPkField); + } + if( ipkTop ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, ipkTop+1); + sqlite3VdbeJumpHere(v, ipkBottom); } if( pbMayReplace ){ *pbMayReplace = seenReplace; } + VdbeModuleComment((v, "END: GenCnstCks()")); } /* ** This routine generates code to finish the INSERT or UPDATE operation ** that was started by a prior call to sqlite3GenerateConstraintChecks. -** A consecutive range of registers starting at regRowid contains the +** A consecutive range of registers starting at regNewData contains the ** rowid and the content to be inserted. ** ** The arguments to this routine should be the same as the first six ** arguments to sqlite3GenerateConstraintChecks. */ void sqlite3CompleteInsertion( Parse *pParse, /* The parser context */ Table *pTab, /* the table into which we are inserting */ - int baseCur, /* Index of a read/write cursor pointing at pTab */ - int regRowid, /* Range of content */ + int iDataCur, /* Cursor of the canonical data source */ + int iIdxCur, /* First index cursor */ + int regNewData, /* Range of content */ int *aRegIdx, /* Register used by each index. 0 for unused indices */ int isUpdate, /* True for UPDATE, False for INSERT */ int appendBias, /* True if this is likely to be an append */ int useSeekResult /* True to set the USESEEKRESULT flag on OP_[Idx]Insert */ ){ - int i; - Vdbe *v; - Index *pIdx; - u8 pik_flags; - int regData; - int regRec; + Vdbe *v; /* Prepared statements under construction */ + Index *pIdx; /* An index being inserted or updated */ + u8 pik_flags; /* flag values passed to the btree insert */ + int regData; /* Content registers (after the rowid) */ + int regRec; /* Register holding assemblied record for the table */ + int i; /* Loop counter */ v = sqlite3GetVdbe(pParse); assert( v!=0 ); assert( pTab->pSelect==0 ); /* This table is not a VIEW */ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ if( aRegIdx[i]==0 ) continue; if( pIdx->pPartIdxWhere ){ sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2); } - sqlite3VdbeAddOp2(v, OP_IdxInsert, baseCur+i+1, aRegIdx[i]); - if( useSeekResult ){ - sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i]); + pik_flags = 0; + if( useSeekResult ) pik_flags = OPFLAG_USESEEKRESULT; + if( pIdx->autoIndex==2 && !HasRowid(pTab) && pParse->nested==0 ){ + pik_flags |= OPFLAG_NCHANGE; } + if( pik_flags ) sqlite3VdbeChangeP5(v, pik_flags); } - regData = regRowid + 1; + if( !HasRowid(pTab) ) return; + regData = regNewData + 1; regRec = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec); sqlite3TableAffinityStr(v, pTab); sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol); if( pParse->nested ){ @@ -1544,51 +1649,75 @@ pik_flags |= OPFLAG_APPEND; } if( useSeekResult ){ pik_flags |= OPFLAG_USESEEKRESULT; } - sqlite3VdbeAddOp3(v, OP_Insert, baseCur, regRec, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iDataCur, regRec, regNewData); if( !pParse->nested ){ sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT); } sqlite3VdbeChangeP5(v, pik_flags); } /* -** Generate code that will open cursors for a table and for all -** indices of that table. The "baseCur" parameter is the cursor number used -** for the table. Indices are opened on subsequent cursors. +** Allocate cursors for the pTab table and all its indices and generate +** code to open and initialized those cursors. +** +** The cursor for the object that contains the complete data (normally +** the table itself, but the PRIMARY KEY index in the case of a WITHOUT +** ROWID table) is returned in *piDataCur. The first index cursor is +** returned in *piIdxCur. The number of indices is returned. +** +** Use iBase as the first cursor (either the *piDataCur for rowid tables +** or the first index for WITHOUT ROWID tables) if it is non-negative. +** If iBase is negative, then allocate the next available cursor. ** -** Return the number of indices on the table. +** For a rowid table, *piDataCur will be exactly one less than *piIdxCur. +** For a WITHOUT ROWID table, *piDataCur will be somewhere in the range +** of *piIdxCurs, depending on where the PRIMARY KEY index appears on the +** pTab->pIndex list. */ int sqlite3OpenTableAndIndices( Parse *pParse, /* Parsing context */ Table *pTab, /* Table to be opened */ - int baseCur, /* Cursor number assigned to the table */ - int op /* OP_OpenRead or OP_OpenWrite */ + int op, /* OP_OpenRead or OP_OpenWrite */ + int iBase, /* Use this for the table cursor, if there is one */ + int *piDataCur, /* Write the database source cursor number here */ + int *piIdxCur /* Write the first index cursor number here */ ){ int i; int iDb; Index *pIdx; Vdbe *v; - if( IsVirtual(pTab) ) return 0; + assert( op==OP_OpenRead || op==OP_OpenWrite ); + if( IsVirtual(pTab) ){ + *piDataCur = 0; + *piIdxCur = 1; + return 0; + } iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); v = sqlite3GetVdbe(pParse); assert( v!=0 ); - sqlite3OpenTable(pParse, baseCur, iDb, pTab, op); - for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); + if( iBase<0 ) iBase = pParse->nTab; + if( HasRowid(pTab) ){ + *piDataCur = iBase++; + sqlite3OpenTable(pParse, *piDataCur, iDb, pTab, op); + }else{ + sqlite3TableLock(pParse, iDb, pTab->tnum, op==OP_OpenWrite, pTab->zName); + } + *piIdxCur = iBase; + for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + int iIdxCur = iBase++; assert( pIdx->pSchema==pTab->pSchema ); - sqlite3VdbeAddOp4(v, op, i+baseCur, pIdx->tnum, iDb, - (char*)pKey, P4_KEYINFO_HANDOFF); + if( pIdx->autoIndex==2 && !HasRowid(pTab) ) *piDataCur = iIdxCur; + sqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); } - if( pParse->nTabnTab = baseCur+i; - } - return i-1; + if( iBase>pParse->nTab ) pParse->nTab = iBase; + return i; } #ifdef SQLITE_TEST /* @@ -1629,17 +1758,17 @@ */ static int xferCompatibleIndex(Index *pDest, Index *pSrc){ int i; assert( pDest && pSrc ); assert( pDest->pTable!=pSrc->pTable ); - if( pDest->nColumn!=pSrc->nColumn ){ + if( pDest->nKeyCol!=pSrc->nKeyCol ){ return 0; /* Different number of columns */ } if( pDest->onError!=pSrc->onError ){ return 0; /* Different conflict resolution strategies */ } - for(i=0; inColumn; i++){ + for(i=0; inKeyCol; i++){ if( pSrc->aiColumn[i]!=pDest->aiColumn[i] ){ return 0; /* Different columns indexed */ } if( pSrc->aSortOrder[i]!=pDest->aSortOrder[i] ){ return 0; /* Different sort orders */ @@ -1694,14 +1823,13 @@ struct SrcList_item *pItem; /* An element of pSelect->pSrc */ int i; /* Loop counter */ int iDbSrc; /* The database of pSrc */ int iSrc, iDest; /* Cursors from source and destination */ int addr1, addr2; /* Loop addresses */ - int emptyDestTest; /* Address of test for empty pDest */ - int emptySrcTest; /* Address of test for empty pSrc */ + int emptyDestTest = 0; /* Address of test for empty pDest */ + int emptySrcTest = 0; /* Address of test for empty pSrc */ Vdbe *v; /* The VDBE we are building */ - KeyInfo *pKey; /* Key information for an index */ int regAutoinc; /* Memory register used by AUTOINC */ int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */ int regData, regRowid; /* Registers holding data and rowid */ if( pSelect==0 ){ @@ -1767,10 +1895,13 @@ return 0; /* FROM clause does not contain a real table */ } if( pSrc==pDest ){ return 0; /* tab1 and tab2 may not be the same table */ } + if( HasRowid(pDest)!=HasRowid(pSrc) ){ + return 0; /* source and destination must both be WITHOUT ROWID or not */ + } #ifndef SQLITE_OMIT_VIRTUALTABLE if( pSrc->tabFlags & TF_Virtual ){ return 0; /* tab2 must not be a virtual table */ } #endif @@ -1837,11 +1968,14 @@ v = sqlite3GetVdbe(pParse); sqlite3CodeVerifySchema(pParse, iDbSrc); iSrc = pParse->nTab++; iDest = pParse->nTab++; regAutoinc = autoIncBegin(pParse, iDbDest, pDest); + regData = sqlite3GetTempReg(pParse); + regRowid = sqlite3GetTempReg(pParse); sqlite3OpenTable(pParse, iDest, iDbDest, pDest, OP_OpenWrite); + assert( HasRowid(pDest) || destHasUniqueIdx ); if( (pDest->iPKey<0 && pDest->pIndex!=0) /* (1) */ || destHasUniqueIdx /* (2) */ || (onError!=OE_Abort && onError!=OE_Rollback) /* (3) */ ){ /* In some circumstances, we are able to run the xfer optimization @@ -1859,62 +1993,60 @@ ** (3) onError is something other than OE_Abort and OE_Rollback. */ addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0); emptyDestTest = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0); sqlite3VdbeJumpHere(v, addr1); - }else{ - emptyDestTest = 0; - } - sqlite3OpenTable(pParse, iSrc, iDbSrc, pSrc, OP_OpenRead); - emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); - regData = sqlite3GetTempReg(pParse); - regRowid = sqlite3GetTempReg(pParse); - if( pDest->iPKey>=0 ){ - addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); - addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid); - sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_PRIMARYKEY, - onError, "PRIMARY KEY must be unique", P4_STATIC); - sqlite3VdbeJumpHere(v, addr2); - autoIncStep(pParse, regAutoinc, regRowid); - }else if( pDest->pIndex==0 ){ - addr1 = sqlite3VdbeAddOp2(v, OP_NewRowid, iDest, regRowid); - }else{ - addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); - assert( (pDest->tabFlags & TF_Autoincrement)==0 ); - } - sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData); - sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid); - sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND); - sqlite3VdbeChangeP4(v, -1, pDest->zName, 0); - sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); + } + if( HasRowid(pSrc) ){ + sqlite3OpenTable(pParse, iSrc, iDbSrc, pSrc, OP_OpenRead); + emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); + if( pDest->iPKey>=0 ){ + addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); + addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid); + sqlite3RowidConstraint(pParse, onError, pDest); + sqlite3VdbeJumpHere(v, addr2); + autoIncStep(pParse, regAutoinc, regRowid); + }else if( pDest->pIndex==0 ){ + addr1 = sqlite3VdbeAddOp2(v, OP_NewRowid, iDest, regRowid); + }else{ + addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); + assert( (pDest->tabFlags & TF_Autoincrement)==0 ); + } + sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData); + sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid); + sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND); + sqlite3VdbeChangeP4(v, -1, pDest->zName, 0); + sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); + sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); + sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); + }else{ + sqlite3TableLock(pParse, iDbDest, pDest->tnum, 1, pDest->zName); + sqlite3TableLock(pParse, iDbSrc, pSrc->tnum, 0, pSrc->zName); + } for(pDestIdx=pDest->pIndex; pDestIdx; pDestIdx=pDestIdx->pNext){ for(pSrcIdx=pSrc->pIndex; ALWAYS(pSrcIdx); pSrcIdx=pSrcIdx->pNext){ if( xferCompatibleIndex(pDestIdx, pSrcIdx) ) break; } assert( pSrcIdx ); - sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); - sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); - pKey = sqlite3IndexKeyinfo(pParse, pSrcIdx); - sqlite3VdbeAddOp4(v, OP_OpenRead, iSrc, pSrcIdx->tnum, iDbSrc, - (char*)pKey, P4_KEYINFO_HANDOFF); + sqlite3VdbeAddOp3(v, OP_OpenRead, iSrc, pSrcIdx->tnum, iDbSrc); + sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx); VdbeComment((v, "%s", pSrcIdx->zName)); - pKey = sqlite3IndexKeyinfo(pParse, pDestIdx); - sqlite3VdbeAddOp4(v, OP_OpenWrite, iDest, pDestIdx->tnum, iDbDest, - (char*)pKey, P4_KEYINFO_HANDOFF); + sqlite3VdbeAddOp3(v, OP_OpenWrite, iDest, pDestIdx->tnum, iDbDest); + sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx); sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR); VdbeComment((v, "%s", pDestIdx->zName)); addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); sqlite3VdbeAddOp2(v, OP_RowKey, iSrc, regData); sqlite3VdbeAddOp3(v, OP_IdxInsert, iDest, regData, 1); sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1); sqlite3VdbeJumpHere(v, addr1); + sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); + sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); } sqlite3VdbeJumpHere(v, emptySrcTest); sqlite3ReleaseTempReg(pParse, regRowid); sqlite3ReleaseTempReg(pParse, regData); - sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); - sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); if( emptyDestTest ){ sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_OK, 0); sqlite3VdbeJumpHere(v, emptyDestTest); sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); return 0; Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -1133,10 +1133,11 @@ case SQLITE_CONSTRAINT_COMMITHOOK: zName = "SQLITE_CONSTRAINT_COMMITHOOK"; break; case SQLITE_CONSTRAINT_VTAB: zName = "SQLITE_CONSTRAINT_VTAB"; break; case SQLITE_CONSTRAINT_FUNCTION: zName = "SQLITE_CONSTRAINT_FUNCTION"; break; + case SQLITE_CONSTRAINT_ROWID: zName = "SQLITE_CONSTRAINT_ROWID"; break; case SQLITE_MISMATCH: zName = "SQLITE_MISMATCH"; break; case SQLITE_MISUSE: zName = "SQLITE_MISUSE"; break; case SQLITE_NOLFS: zName = "SQLITE_NOLFS"; break; case SQLITE_AUTH: zName = "SQLITE_AUTH"; break; case SQLITE_FORMAT: zName = "SQLITE_FORMAT"; break; @@ -1947,10 +1948,36 @@ ** function. */ const char *sqlite3_errstr(int rc){ return sqlite3ErrStr(rc); } + +/* +** Invalidate all cached KeyInfo objects for database connection "db" +*/ +static void invalidateCachedKeyInfo(sqlite3 *db){ + Db *pDb; /* A single database */ + int iDb; /* The database index number */ + HashElem *k; /* For looping over tables in pDb */ + Table *pTab; /* A table in the database */ + Index *pIdx; /* Each index */ + + for(iDb=0, pDb=db->aDb; iDbnDb; iDb++, pDb++){ + if( pDb->pBt==0 ) continue; + sqlite3BtreeEnter(pDb->pBt); + for(k=sqliteHashFirst(&pDb->pSchema->tblHash); k; k=sqliteHashNext(k)){ + pTab = (Table*)sqliteHashData(k); + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->pKeyInfo && pIdx->pKeyInfo->db==db ){ + sqlite3KeyInfoUnref(pIdx->pKeyInfo); + pIdx->pKeyInfo = 0; + } + } + } + sqlite3BtreeLeave(pDb->pBt); + } +} /* ** Create a new collating function for database "db". The name is zName ** and the encoding is enc. */ @@ -1992,10 +2019,11 @@ sqlite3Error(db, SQLITE_BUSY, "unable to delete/modify collation sequence due to active statements"); return SQLITE_BUSY; } sqlite3ExpirePreparedStatements(db); + invalidateCachedKeyInfo(db); /* If collation sequence pColl was created directly by a call to ** sqlite3_create_collation, and not generated by synthCollSeq(), ** then any copies made by synthCollSeq() need to be invalidated. ** Also, collation destructor - CollSeq.xDel() - function may need Index: src/parse.y ================================================================== --- src/parse.y +++ src/parse.y @@ -161,16 +161,26 @@ %type temp {int} %ifndef SQLITE_OMIT_TEMPDB temp(A) ::= TEMP. {A = 1;} %endif SQLITE_OMIT_TEMPDB temp(A) ::= . {A = 0;} -create_table_args ::= LP columnlist conslist_opt(X) RP(Y). { - sqlite3EndTable(pParse,&X,&Y,0); +create_table_args ::= LP columnlist conslist_opt(X) RP(E) table_options(F). { + sqlite3EndTable(pParse,&X,&E,F,0); } create_table_args ::= AS select(S). { - sqlite3EndTable(pParse,0,0,S); + sqlite3EndTable(pParse,0,0,0,S); sqlite3SelectDelete(pParse->db, S); +} +%type table_options {u8} +table_options(A) ::= . {A = 0;} +table_options(A) ::= WITHOUT nm(X). { + if( X.n==5 && sqlite3_strnicmp(X.z,"rowid",5)==0 ){ + A = TF_WithoutRowid; + }else{ + A = 0; + sqlite3ErrorMsg(pParse, "unknown table option: %.*s", X.n, X.z); + } } columnlist ::= columnlist COMMA column. columnlist ::= column. // A "column" is a complete description of a single column in a @@ -203,11 +213,11 @@ %fallback ID ABORT ACTION AFTER ANALYZE ASC ATTACH BEFORE BEGIN BY CASCADE CAST COLUMNKW CONFLICT DATABASE DEFERRED DESC DETACH EACH END EXCLUSIVE EXPLAIN FAIL FOR IGNORE IMMEDIATE INITIALLY INSTEAD LIKE_KW MATCH NO PLAN QUERY KEY OF OFFSET PRAGMA RAISE RELEASE REPLACE RESTRICT ROW ROLLBACK - SAVEPOINT TEMP TRIGGER VACUUM VIEW VIRTUAL + SAVEPOINT TEMP TRIGGER VACUUM VIEW VIRTUAL WITHOUT %ifdef SQLITE_OMIT_COMPOUND_SELECT EXCEPT INTERSECT UNION %endif SQLITE_OMIT_COMPOUND_SELECT REINDEX RENAME CTIME_KW IF . @@ -571,11 +581,11 @@ indexed_opt(A) ::= INDEXED BY nm(X). {A = X;} indexed_opt(A) ::= NOT INDEXED. {A.z=0; A.n=1;} %type using_opt {IdList*} %destructor using_opt {sqlite3IdListDelete(pParse->db, $$);} -using_opt(U) ::= USING LP inscollist(L) RP. {U = L;} +using_opt(U) ::= USING LP idlist(L) RP. {U = L;} using_opt(U) ::= . {U = 0;} %type orderby_opt {ExprList*} %destructor orderby_opt {sqlite3ExprListDelete(pParse->db, $$);} @@ -738,18 +748,18 @@ } %endif SQLITE_OMIT_COMPOUND_SELECT %type inscollist_opt {IdList*} %destructor inscollist_opt {sqlite3IdListDelete(pParse->db, $$);} -%type inscollist {IdList*} -%destructor inscollist {sqlite3IdListDelete(pParse->db, $$);} +%type idlist {IdList*} +%destructor idlist {sqlite3IdListDelete(pParse->db, $$);} inscollist_opt(A) ::= . {A = 0;} -inscollist_opt(A) ::= LP inscollist(X) RP. {A = X;} -inscollist(A) ::= inscollist(X) COMMA nm(Y). +inscollist_opt(A) ::= LP idlist(X) RP. {A = X;} +idlist(A) ::= idlist(X) COMMA nm(Y). {A = sqlite3IdListAppend(pParse->db,X,&Y);} -inscollist(A) ::= nm(Y). +idlist(A) ::= nm(Y). {A = sqlite3IdListAppend(pParse->db,0,&Y);} /////////////////////////// Expression Processing ///////////////////////////// // @@ -1225,11 +1235,11 @@ %type trigger_event {struct TrigEvent} %destructor trigger_event {sqlite3IdListDelete(pParse->db, $$.b);} trigger_event(A) ::= DELETE|INSERT(OP). {A.a = @OP; A.b = 0;} trigger_event(A) ::= UPDATE(OP). {A.a = @OP; A.b = 0;} -trigger_event(A) ::= UPDATE OF inscollist(X). {A.a = TK_UPDATE; A.b = X;} +trigger_event(A) ::= UPDATE OF idlist(X). {A.a = TK_UPDATE; A.b = X;} foreach_clause ::= . foreach_clause ::= FOR EACH ROW. %type when_clause {Expr*} Index: src/pragma.c ================================================================== --- src/pragma.c +++ src/pragma.c @@ -1423,12 +1423,11 @@ pTab = sqlite3FindTable(db, zRight, zDb); if( pTab ){ int i, k; int nHidden = 0; Column *pCol; - Index *pPk; - for(pPk=pTab->pIndex; pPk && pPk->autoIndex!=2; pPk=pPk->pNext){} + Index *pPk = sqlite3PrimaryKeyIndex(pTab); sqlite3VdbeSetNumCols(v, 6); pParse->nMem = 6; sqlite3CodeVerifySchema(pParse, iDb); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "cid", SQLITE_STATIC); sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "name", SQLITE_STATIC); @@ -1507,12 +1506,12 @@ pParse->nMem = 3; sqlite3CodeVerifySchema(pParse, iDb); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "seqno", SQLITE_STATIC); sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "cid", SQLITE_STATIC); sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "name", SQLITE_STATIC); - for(i=0; inColumn; i++){ - int cnum = pIdx->aiColumn[i]; + for(i=0; inKeyCol; i++){ + i16 cnum = pIdx->aiColumn[i]; sqlite3VdbeAddOp2(v, OP_Integer, i, 1); sqlite3VdbeAddOp2(v, OP_Integer, cnum, 2); assert( pTab->nCol>cnum ); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, pTab->aCol[cnum].zName, 0); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); @@ -1679,13 +1678,12 @@ x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, 0); if( x==0 ){ if( pIdx==0 ){ sqlite3OpenTable(pParse, i, iDb, pParent, OP_OpenRead); }else{ - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); sqlite3VdbeAddOp3(v, OP_OpenRead, i, pIdx->tnum, iDb); - sqlite3VdbeChangeP4(v, -1, (char*)pKey, P4_KEYINFO_HANDOFF); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); } }else{ k = 0; break; } @@ -1845,20 +1843,24 @@ assert( sqlite3SchemaMutexHeld(db, i, 0) ); pTbls = &db->aDb[i].pSchema->tblHash; for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ Table *pTab = sqliteHashData(x); Index *pIdx; - sqlite3VdbeAddOp2(v, OP_Integer, pTab->tnum, 2+cnt); - cnt++; + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_Integer, pTab->tnum, 2+cnt); + VdbeComment((v, "%s", pTab->zName)); + cnt++; + } for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ sqlite3VdbeAddOp2(v, OP_Integer, pIdx->tnum, 2+cnt); + VdbeComment((v, "%s", pIdx->zName)); cnt++; } } /* Make sure sufficient number of registers have been allocated */ - pParse->nMem = MAX( pParse->nMem, cnt+7 ); + pParse->nMem = MAX( pParse->nMem, cnt+8 ); /* Do the b-tree integrity checks */ sqlite3VdbeAddOp3(v, OP_IntegrityCk, 2, cnt, 1); sqlite3VdbeChangeP5(v, (u8)i); addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); @@ -1872,62 +1874,64 @@ /* Make sure all the indices are constructed correctly. */ for(x=sqliteHashFirst(pTbls); x && !isQuick; x=sqliteHashNext(x)){ Table *pTab = sqliteHashData(x); - Index *pIdx; + Index *pIdx, *pPk; int loopTop; + int iDataCur, iIdxCur; if( pTab->pIndex==0 ) continue; + pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); addr = sqlite3VdbeAddOp1(v, OP_IfPos, 1); /* Stop if out of errors */ sqlite3VdbeAddOp2(v, OP_Halt, 0, 0); sqlite3VdbeJumpHere(v, addr); sqlite3ExprCacheClear(pParse); - sqlite3OpenTableAndIndices(pParse, pTab, 1, OP_OpenRead); - for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ - sqlite3VdbeAddOp2(v, OP_Integer, 0, 7+j); /* index entries counter */ - } - pParse->nMem = MAX(pParse->nMem, 7+j); - loopTop = sqlite3VdbeAddOp2(v, OP_Rewind, 1, 0) + 1; - for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ - int jmp2, jmp3; + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, + 1, &iDataCur, &iIdxCur); + sqlite3VdbeAddOp2(v, OP_Integer, 0, 7); + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, 8+j); /* index entries counter */ + } + pParse->nMem = MAX(pParse->nMem, 8+j); + sqlite3VdbeAddOp2(v, OP_Rewind, iDataCur, 0); + loopTop = sqlite3VdbeAddOp2(v, OP_AddImm, 7, 1); + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + int jmp2, jmp3, jmp4; int r1; - static const VdbeOpList idxErr[] = { - { OP_AddImm, 1, -1, 0}, - { OP_String8, 0, 3, 0}, /* 1 */ - { OP_Rowid, 1, 4, 0}, - { OP_String8, 0, 5, 0}, /* 3 */ - { OP_String8, 0, 6, 0}, /* 4 */ - { OP_Concat, 4, 3, 3}, - { OP_Concat, 5, 3, 3}, - { OP_Concat, 6, 3, 3}, - { OP_ResultRow, 3, 1, 0}, - { OP_IfPos, 1, 0, 0}, /* 9 */ - { OP_Halt, 0, 0, 0}, - }; - r1 = sqlite3GenerateIndexKey(pParse, pIdx, 1, 3, 0, &jmp3); - sqlite3VdbeAddOp2(v, OP_AddImm, 7+j, 1); /* increment entry count */ - jmp2 = sqlite3VdbeAddOp4Int(v, OP_Found, j+2, 0, r1, pIdx->nColumn+1); - addr = sqlite3VdbeAddOpList(v, ArraySize(idxErr), idxErr); - sqlite3VdbeChangeP4(v, addr+1, "rowid ", P4_STATIC); - sqlite3VdbeChangeP4(v, addr+3, " missing from index ", P4_STATIC); - sqlite3VdbeChangeP4(v, addr+4, pIdx->zName, P4_TRANSIENT); - sqlite3VdbeJumpHere(v, addr+9); + if( pPk==pIdx ) continue; + r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 0, &jmp3); + sqlite3VdbeAddOp2(v, OP_AddImm, 8+j, 1); /* increment entry count */ + jmp2 = sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, 0, r1, + pIdx->nColumn); + sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */ + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, "row ", P4_STATIC); + sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); + sqlite3VdbeAddOp4(v, OP_String8, 0, 4, 0, " missing from index ", + P4_STATIC); + sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); + sqlite3VdbeAddOp4(v, OP_String8, 0, 4, 0, pIdx->zName, P4_TRANSIENT); + sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); + sqlite3VdbeAddOp2(v, OP_ResultRow, 3, 1); + jmp4 = sqlite3VdbeAddOp1(v, OP_IfPos, 1); + sqlite3VdbeAddOp0(v, OP_Halt); + sqlite3VdbeJumpHere(v, jmp4); sqlite3VdbeJumpHere(v, jmp2); sqlite3VdbeResolveLabel(v, jmp3); } - sqlite3VdbeAddOp2(v, OP_Next, 1, loopTop); + sqlite3VdbeAddOp2(v, OP_Next, iDataCur, loopTop); sqlite3VdbeJumpHere(v, loopTop-1); #ifndef SQLITE_OMIT_BTREECOUNT sqlite3VdbeAddOp4(v, OP_String8, 0, 2, 0, "wrong # of entries in index ", P4_STATIC); for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + if( pPk==pIdx ) continue; addr = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp2(v, OP_IfPos, 1, addr+2); sqlite3VdbeAddOp2(v, OP_Halt, 0, 0); - sqlite3VdbeAddOp2(v, OP_Count, j+2, 3); - sqlite3VdbeAddOp3(v, OP_Eq, 7+j, addr+8, 3); + sqlite3VdbeAddOp2(v, OP_Count, iIdxCur+j, 3); + sqlite3VdbeAddOp3(v, OP_Eq, 8+j, addr+8, 3); sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, pIdx->zName, P4_TRANSIENT); sqlite3VdbeAddOp3(v, OP_Concat, 3, 2, 7); sqlite3VdbeAddOp2(v, OP_ResultRow, 7, 1); } Index: src/resolve.c ================================================================== --- src/resolve.c +++ src/resolve.c @@ -224,11 +224,13 @@ sqlite3 *db = pParse->db; /* The database connection */ struct SrcList_item *pItem; /* Use for looping over pSrcList items */ struct SrcList_item *pMatch = 0; /* The matching pSrcList item */ NameContext *pTopNC = pNC; /* First namecontext in the list */ Schema *pSchema = 0; /* Schema of the expression */ - int isTrigger = 0; + int isTrigger = 0; /* True if resolved to a trigger column */ + Table *pTab = 0; /* Table hold the row */ + Column *pCol; /* A column of pTab */ assert( pNC ); /* the name context cannot be NULL. */ assert( zCol ); /* The Z in X.Y.Z cannot be NULL */ assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); @@ -265,13 +267,10 @@ ExprList *pEList; SrcList *pSrcList = pNC->pSrcList; if( pSrcList ){ for(i=0, pItem=pSrcList->a; inSrc; i++, pItem++){ - Table *pTab; - Column *pCol; - pTab = pItem->pTab; assert( pTab!=0 && pTab->zName!=0 ); assert( pTab->nCol>0 ); if( pItem->pSelect && (pItem->pSelect->selFlags & SF_NestedFrom)!=0 ){ int hit = 0; @@ -327,13 +326,12 @@ #ifndef SQLITE_OMIT_TRIGGER /* If we have not already resolved the name, then maybe ** it is a new.* or old.* trigger argument reference */ - if( zDb==0 && zTab!=0 && cnt==0 && pParse->pTriggerTab!=0 ){ + if( zDb==0 && zTab!=0 && cntTab==0 && pParse->pTriggerTab!=0 ){ int op = pParse->eTriggerOp; - Table *pTab = 0; assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT ); if( op!=TK_DELETE && sqlite3StrICmp("new",zTab) == 0 ){ pExpr->iTable = 1; pTab = pParse->pTriggerTab; }else if( op!=TK_INSERT && sqlite3StrICmp("old",zTab)==0 ){ @@ -352,11 +350,11 @@ iCol = -1; } break; } } - if( iCol>=pTab->nCol && sqlite3IsRowid(zCol) ){ + if( iCol>=pTab->nCol && sqlite3IsRowid(zCol) && HasRowid(pTab) ){ iCol = -1; /* IMP: R-44911-55124 */ } if( iColnCol ){ cnt++; if( iCol<0 ){ @@ -379,11 +377,12 @@ #endif /* !defined(SQLITE_OMIT_TRIGGER) */ /* ** Perhaps the name is a reference to the ROWID */ - if( cnt==0 && cntTab==1 && sqlite3IsRowid(zCol) ){ + assert( pTab!=0 || cntTab==0 ); + if( cnt==0 && cntTab==1 && sqlite3IsRowid(zCol) && HasRowid(pTab) ){ cnt = 1; pExpr->iColumn = -1; /* IMP: R-44911-55124 */ pExpr->affinity = SQLITE_AFF_INTEGER; } Index: src/select.c ================================================================== --- src/select.c +++ src/select.c @@ -801,28 +801,61 @@ sqlite3VdbeAddOp3(v, OP_IfZero, p->iLimit, iBreak, -1); } } /* -** Allocate a KeyInfo object sufficient for an index of N columns. -** -** Actually, always allocate one extra column for the rowid at the end -** of the index. So the KeyInfo returned will have space sufficient for -** N+1 columns. +** Allocate a KeyInfo object sufficient for an index of N key columns and +** X extra columns. */ -KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N){ - KeyInfo *p = sqlite3DbMallocZero(db, - sizeof(KeyInfo) + (N+1)*(sizeof(CollSeq*)+1)); +KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ + KeyInfo *p = sqlite3DbMallocZero(0, + sizeof(KeyInfo) + (N+X)*(sizeof(CollSeq*)+1)); if( p ){ - p->aSortOrder = (u8*)&p->aColl[N+1]; + p->aSortOrder = (u8*)&p->aColl[N+X]; p->nField = (u16)N; + p->nXField = (u16)X; p->enc = ENC(db); p->db = db; + p->nRef = 1; + }else{ + db->mallocFailed = 1; + } + return p; +} + +/* +** Deallocate a KeyInfo object +*/ +void sqlite3KeyInfoUnref(KeyInfo *p){ + if( p ){ + assert( p->nRef>0 ); + p->nRef--; + if( p->nRef==0 ) sqlite3_free(p); + } +} + +/* +** Make a new pointer to a KeyInfo object +*/ +KeyInfo *sqlite3KeyInfoRef(KeyInfo *p){ + if( p ){ + assert( p->nRef>0 ); + p->nRef++; } return p; } +#ifdef SQLITE_DEBUG +/* +** Return TRUE if a KeyInfo object can be change. The KeyInfo object +** can only be changed if this is just a single reference to the object. +** +** This routine is used only inside of assert() statements. +*/ +int sqlite3KeyInfoIsWriteable(KeyInfo *p){ return p->nRef==1; } +#endif /* SQLITE_DEBUG */ + /* ** Given an expression list, generate a KeyInfo structure that records ** the collating sequence for each expression in that expression list. ** ** If the ExprList is an ORDER BY or GROUP BY clause then the resulting @@ -831,23 +864,23 @@ ** then the KeyInfo structure is appropriate for initializing a virtual ** index to implement a DISTINCT test. ** ** Space to hold the KeyInfo structure is obtain from malloc. The calling ** function is responsible for seeing that this structure is eventually -** freed. Add the KeyInfo structure to the P4 field of an opcode using -** P4_KEYINFO_HANDOFF is the usual way of dealing with this. +** freed. */ static KeyInfo *keyInfoFromExprList(Parse *pParse, ExprList *pList){ int nExpr; KeyInfo *pInfo; struct ExprList_item *pItem; sqlite3 *db = pParse->db; int i; nExpr = pList->nExpr; - pInfo = sqlite3KeyInfoAlloc(db, nExpr); + pInfo = sqlite3KeyInfoAlloc(db, nExpr, 1); if( pInfo ){ + assert( sqlite3KeyInfoIsWriteable(pInfo) ); for(i=0, pItem=pList->a; ipExpr); if( !pColl ) pColl = db->pDfltColl; pInfo->aColl[i] = pColl; @@ -1986,11 +2019,11 @@ CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */ int nCol; /* Number of columns in result set */ assert( p->pRightmost==p ); nCol = p->pEList->nExpr; - pKeyInfo = sqlite3KeyInfoAlloc(db, nCol); + pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1); if( !pKeyInfo ){ rc = SQLITE_NOMEM; goto multi_select_end; } for(i=0, apColl=pKeyInfo->aColl; iaddrOpenEphm[1]<0 ); break; } sqlite3VdbeChangeP2(v, addr, nCol); - sqlite3VdbeChangeP4(v, addr, (char*)pKeyInfo, P4_KEYINFO); + sqlite3VdbeChangeP4(v, addr, (char*)sqlite3KeyInfoRef(pKeyInfo), + P4_KEYINFO); pLoop->addrOpenEphm[i] = -1; } } - sqlite3DbFree(db, pKeyInfo); + sqlite3KeyInfoUnref(pKeyInfo); } multi_select_end: pDest->iSdst = dest.iSdst; pDest->nSdst = dest.nSdst; @@ -2051,11 +2085,10 @@ SelectDest *pIn, /* Coroutine supplying data */ SelectDest *pDest, /* Where to send the data */ int regReturn, /* The return address register */ int regPrev, /* Previous result register. No uniqueness if 0 */ KeyInfo *pKeyInfo, /* For comparing with previous entry */ - int p4type, /* The p4 type for pKeyInfo */ int iBreak /* Jump here if we hit the LIMIT */ ){ Vdbe *v = pParse->pVdbe; int iContinue; int addr; @@ -2067,11 +2100,11 @@ */ if( regPrev ){ int j1, j2; j1 = sqlite3VdbeAddOp1(v, OP_IfNot, regPrev); j2 = sqlite3VdbeAddOp4(v, OP_Compare, pIn->iSdst, regPrev+1, pIn->nSdst, - (char*)pKeyInfo, p4type); + (char*)sqlite3KeyInfoRef(pKeyInfo), P4_KEYINFO); sqlite3VdbeAddOp3(v, OP_Jump, j2+2, iContinue, j2+2); sqlite3VdbeJumpHere(v, j1); sqlite3VdbeAddOp3(v, OP_Copy, pIn->iSdst, regPrev+1, pIn->nSdst-1); sqlite3VdbeAddOp2(v, OP_Integer, 1, regPrev); } @@ -2365,11 +2398,11 @@ struct ExprList_item *pItem; for(i=0, pItem=pOrderBy->a; iiOrderByCol>0 && pItem->iOrderByCol<=p->pEList->nExpr ); aPermute[i] = pItem->iOrderByCol - 1; } - pKeyMerge = sqlite3KeyInfoAlloc(db, nOrderBy); + pKeyMerge = sqlite3KeyInfoAlloc(db, nOrderBy, 1); if( pKeyMerge ){ for(i=0; ia[i].pExpr; if( pTerm->flags & EP_Collate ){ @@ -2378,10 +2411,11 @@ pColl = multiSelectCollSeq(pParse, p, aPermute[i]); if( pColl==0 ) pColl = db->pDfltColl; pOrderBy->a[i].pExpr = sqlite3ExprAddCollateString(pParse, pTerm, pColl->zName); } + assert( sqlite3KeyInfoIsWriteable(pKeyMerge) ); pKeyMerge->aColl[i] = pColl; pKeyMerge->aSortOrder[i] = pOrderBy->a[i].sortOrder; } } }else{ @@ -2403,12 +2437,13 @@ int nExpr = p->pEList->nExpr; assert( nOrderBy>=nExpr || db->mallocFailed ); regPrev = pParse->nMem+1; pParse->nMem += nExpr+1; sqlite3VdbeAddOp2(v, OP_Integer, 0, regPrev); - pKeyDup = sqlite3KeyInfoAlloc(db, nExpr); + pKeyDup = sqlite3KeyInfoAlloc(db, nExpr, 1); if( pKeyDup ){ + assert( sqlite3KeyInfoIsWriteable(pKeyDup) ); for(i=0; iaColl[i] = multiSelectCollSeq(pParse, p, i); pKeyDup->aSortOrder[i] = 0; } } @@ -2486,21 +2521,22 @@ ** select as the next output row of the compound select. */ VdbeNoopComment((v, "Output routine for A")); addrOutA = generateOutputSubroutine(pParse, p, &destA, pDest, regOutA, - regPrev, pKeyDup, P4_KEYINFO_HANDOFF, labelEnd); + regPrev, pKeyDup, labelEnd); /* Generate a subroutine that outputs the current row of the B ** select as the next output row of the compound select. */ if( op==TK_ALL || op==TK_UNION ){ VdbeNoopComment((v, "Output routine for B")); addrOutB = generateOutputSubroutine(pParse, p, &destB, pDest, regOutB, - regPrev, pKeyDup, P4_KEYINFO_STATIC, labelEnd); + regPrev, pKeyDup, labelEnd); } + sqlite3KeyInfoUnref(pKeyDup); /* Generate a subroutine to run when the results from select A ** are exhausted and only data in select B remains. */ VdbeNoopComment((v, "eof-A subroutine")); @@ -2575,11 +2611,11 @@ /* Implement the main merge loop */ sqlite3VdbeResolveLabel(v, labelCmpr); sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY); sqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy, - (char*)pKeyMerge, P4_KEYINFO_HANDOFF); + (char*)pKeyMerge, P4_KEYINFO); sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE); sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); /* Jump to the this point in order to terminate the query. */ @@ -3801,11 +3837,11 @@ "argument"); pFunc->iDistinct = -1; }else{ KeyInfo *pKeyInfo = keyInfoFromExprList(pParse, pE->x.pList); sqlite3VdbeAddOp4(v, OP_OpenEphemeral, pFunc->iDistinct, 0, 0, - (char*)pKeyInfo, P4_KEYINFO_HANDOFF); + (char*)pKeyInfo, P4_KEYINFO); } } } } @@ -4256,11 +4292,11 @@ pKeyInfo = keyInfoFromExprList(pParse, pOrderBy); pOrderBy->iECursor = pParse->nTab++; p->addrOpenEphm[2] = addrSortIndex = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, pOrderBy->iECursor, pOrderBy->nExpr+2, 0, - (char*)pKeyInfo, P4_KEYINFO_HANDOFF); + (char*)pKeyInfo, P4_KEYINFO); }else{ addrSortIndex = -1; } /* If the output is destined for a temporary table, open that table. @@ -4284,11 +4320,11 @@ if( p->selFlags & SF_Distinct ){ sDistinct.tabTnct = pParse->nTab++; sDistinct.addrTnct = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, sDistinct.tabTnct, 0, 0, (char*)keyInfoFromExprList(pParse, p->pEList), - P4_KEYINFO_HANDOFF); + P4_KEYINFO); sqlite3VdbeChangeP5(v, BTREE_UNORDERED); sDistinct.eTnctType = WHERE_DISTINCT_UNORDERED; }else{ sDistinct.eTnctType = WHERE_DISTINCT_NOOP; } @@ -4408,11 +4444,11 @@ */ sAggInfo.sortingIdx = pParse->nTab++; pKeyInfo = keyInfoFromExprList(pParse, pGroupBy); addrSortingIdx = sqlite3VdbeAddOp4(v, OP_SorterOpen, sAggInfo.sortingIdx, sAggInfo.nSortingColumn, - 0, (char*)pKeyInfo, P4_KEYINFO_HANDOFF); + 0, (char*)pKeyInfo, P4_KEYINFO); /* Initialize memory locations used by GROUP BY aggregate processing */ iUseFlag = ++pParse->nMem; iAbortFlag = ++pParse->nMem; @@ -4522,11 +4558,11 @@ sAggInfo.directMode = 1; sqlite3ExprCode(pParse, pGroupBy->a[j].pExpr, iBMem+j); } } sqlite3VdbeAddOp4(v, OP_Compare, iAMem, iBMem, pGroupBy->nExpr, - (char*)pKeyInfo, P4_KEYINFO); + (char*)sqlite3KeyInfoRef(pKeyInfo), P4_KEYINFO); j1 = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp3(v, OP_Jump, j1+1, 0, j1+1); /* Generate code that runs whenever the GROUP BY changes. ** Changes in the GROUP BY are detected by the previous code @@ -4648,17 +4684,17 @@ pBest = pIdx; } } if( pBest ){ iRoot = pBest->tnum; - pKeyInfo = sqlite3IndexKeyinfo(pParse, pBest); + pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pBest); } /* Open a read-only cursor, execute the OP_Count, close the cursor. */ - sqlite3VdbeAddOp3(v, OP_OpenRead, iCsr, iRoot, iDb); + sqlite3VdbeAddOp4Int(v, OP_OpenRead, iCsr, iRoot, iDb, 1); if( pKeyInfo ){ - sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO_HANDOFF); + sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO); } sqlite3VdbeAddOp2(v, OP_Count, iCsr, sAggInfo.aFunc[0].iMem); sqlite3VdbeAddOp1(v, OP_Close, iCsr); explainSimpleCount(pParse, pTab, pBest); }else Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -494,10 +494,11 @@ #define SQLITE_CONSTRAINT_NOTNULL (SQLITE_CONSTRAINT | (5<<8)) #define SQLITE_CONSTRAINT_PRIMARYKEY (SQLITE_CONSTRAINT | (6<<8)) #define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7<<8)) #define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) #define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) +#define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) #define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) #define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) /* Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -1416,10 +1416,11 @@ #define TF_Readonly 0x01 /* Read-only system table */ #define TF_Ephemeral 0x02 /* An ephemeral table */ #define TF_HasPrimaryKey 0x04 /* Table has a primary key */ #define TF_Autoincrement 0x08 /* Integer primary key is autoincrement */ #define TF_Virtual 0x10 /* Is a virtual table */ +#define TF_WithoutRowid 0x20 /* No rowid used. PRIMARY KEY is the key */ /* ** Test to see whether or not a table is a virtual table. This is ** done as a macro so that it will be optimized out when virtual @@ -1431,10 +1432,13 @@ #else # define IsVirtual(X) 0 # define IsHiddenColumn(X) 0 #endif +/* Does the table have a rowid */ +#define HasRowid(X) (((X)->tabFlags & TF_WithoutRowid)==0) + /* ** Each foreign key constraint is an instance of the following structure. ** ** A foreign key is associated with two tables. The "from" table is ** the table that contains the REFERENCES clause that creates the foreign @@ -1445,30 +1449,39 @@ ** a INTEGER PRIMARY KEY, ** b INTEGER CONSTRAINT fk1 REFERENCES ex2(x) ** ); ** ** For foreign key "fk1", the from-table is "ex1" and the to-table is "ex2". +** Equivalent names: +** +** from-table == child-table +** to-table == parent-table ** ** Each REFERENCES clause generates an instance of the following structure ** which is attached to the from-table. The to-table need not exist when ** the from-table is created. The existence of the to-table is not checked. +** +** The list of all parents for child Table X is held at X.pFKey. +** +** A list of all children for a table named Z (which might not even exist) +** is held in Schema.fkeyHash with a hash key of Z. */ struct FKey { Table *pFrom; /* Table containing the REFERENCES clause (aka: Child) */ - FKey *pNextFrom; /* Next foreign key in pFrom */ + FKey *pNextFrom; /* Next FKey with the same in pFrom. Next parent of pFrom */ char *zTo; /* Name of table that the key points to (aka: Parent) */ - FKey *pNextTo; /* Next foreign key on table named zTo */ - FKey *pPrevTo; /* Previous foreign key on table named zTo */ + FKey *pNextTo; /* Next with the same zTo. Next child of zTo. */ + FKey *pPrevTo; /* Previous with the same zTo */ int nCol; /* Number of columns in this key */ /* EV: R-30323-21917 */ - u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ - u8 aAction[2]; /* ON DELETE and ON UPDATE actions, respectively */ - Trigger *apTrigger[2]; /* Triggers for aAction[] actions */ - struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ - int iFrom; /* Index of column in pFrom */ - char *zCol; /* Name of column in zTo. If 0 use PRIMARY KEY */ - } aCol[1]; /* One entry for each of nCol column s */ + u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ + u8 aAction[2]; /* ON DELETE and ON UPDATE actions, respectively */ + Trigger *apTrigger[2];/* Triggers for aAction[] actions */ + struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ + int iFrom; /* Index of column in pFrom */ + char *zCol; /* Name of column in zTo. If NULL use PRIMARY KEY */ + } aCol[1]; /* One entry for each of nCol columns */ }; /* ** SQLite supports many different ways to resolve a constraint ** error. ROLLBACK processing means that a constraint violation @@ -1517,13 +1530,15 @@ ** Note that aSortOrder[] and aColl[] have nField+1 slots. There ** are nField slots for the columns of an index then one extra slot ** for the rowid at the end. */ struct KeyInfo { - sqlite3 *db; /* The database connection */ + u32 nRef; /* Number of references to this KeyInfo object */ u8 enc; /* Text encoding - one of the SQLITE_UTF* values */ - u16 nField; /* Maximum index for aColl[] and aSortOrder[] */ + u16 nField; /* Number of key columns in the index */ + u16 nXField; /* Number of columns beyond the key columns */ + sqlite3 *db; /* The database connection */ u8 *aSortOrder; /* Sort order for each column. */ CollSeq *aColl[1]; /* Collating sequence for each term of the key */ }; /* @@ -1542,20 +1557,18 @@ */ struct UnpackedRecord { KeyInfo *pKeyInfo; /* Collation and sort-order information */ u16 nField; /* Number of entries in apMem[] */ u8 flags; /* Boolean settings. UNPACKED_... below */ - i64 rowid; /* Used by UNPACKED_PREFIX_SEARCH */ Mem *aMem; /* Values */ }; /* ** Allowed values of UnpackedRecord.flags */ #define UNPACKED_INCRKEY 0x01 /* Make this key an epsilon larger */ #define UNPACKED_PREFIX_MATCH 0x02 /* A prefix match is considered OK */ -#define UNPACKED_PREFIX_SEARCH 0x04 /* Ignore final (rowid) field */ /* ** Each SQL index is represented in memory by an ** instance of the following structure. ** @@ -1581,26 +1594,30 @@ ** algorithm to employ whenever an attempt is made to insert a non-unique ** element. */ struct Index { char *zName; /* Name of this index */ - int *aiColumn; /* Which columns are used by this index. 1st is 0 */ + i16 *aiColumn; /* Which columns are used by this index. 1st is 0 */ tRowcnt *aiRowEst; /* From ANALYZE: Est. rows selected by each column */ Table *pTable; /* The SQL table being indexed */ char *zColAff; /* String defining the affinity of each column */ Index *pNext; /* The next index associated with the same table */ Schema *pSchema; /* Schema containing this index */ u8 *aSortOrder; /* for each column: True==DESC, False==ASC */ char **azColl; /* Array of collation sequence names for index */ Expr *pPartIdxWhere; /* WHERE clause for partial indices */ + KeyInfo *pKeyInfo; /* A KeyInfo object suitable for this index */ int tnum; /* DB Page containing root of this index */ LogEst szIdxRow; /* Estimated average row size in bytes */ - u16 nColumn; /* Number of columns in table used by this index */ + u16 nKeyCol; /* Number of columns forming the key */ + u16 nColumn; /* Number of columns stored in the index */ u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ unsigned autoIndex:2; /* 1==UNIQUE, 2==PRIMARY KEY, 0==CREATE INDEX */ unsigned bUnordered:1; /* Use this index for == or IN queries only */ unsigned uniqNotNull:1; /* True if UNIQUE and NOT NULL for all columns */ + unsigned isResized:1; /* True if resizeIndexObject() has been called */ + unsigned isCovering:1; /* True if this is a covering index */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 int nSample; /* Number of elements in aSample[] */ int nSampleCol; /* Size of IndexSample.anEq[] and so on */ tRowcnt *aAvgEq; /* Average nEq values for keys not in aSample */ IndexSample *aSample; /* Samples of the left-most key */ @@ -2275,10 +2292,12 @@ AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */ /* Information used while coding trigger programs. */ Parse *pToplevel; /* Parse structure for main program (or NULL) */ Table *pTriggerTab; /* Table triggers are being coded for */ + int addrCrTab; /* Address of OP_CreateTable opcode on CREATE TABLE */ + int addrSkipPK; /* Address of instruction to skip PRIMARY KEY index */ u32 nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */ u32 oldmask; /* Mask of old.* columns referenced */ u32 newmask; /* Mask of new.* columns referenced */ u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ @@ -2287,10 +2306,11 @@ /* Above is constant between recursions. Below is reset before and after ** each recursion */ int nVar; /* Number of '?' variables seen in the SQL so far */ int nzVar; /* Number of available slots in azVar[] */ + u8 iPkSortOrder; /* ASC or DESC for INTEGER PRIMARY KEY */ u8 explain; /* True if the EXPLAIN flag is found on the query */ #ifndef SQLITE_OMIT_VIRTUALTABLE u8 declareVtab; /* True if inside sqlite3_declare_vtab() */ int nVtabLock; /* Number of virtual tables to lock */ #endif @@ -2300,11 +2320,10 @@ int iSelectId; /* ID of current select for EXPLAIN output */ int iNextSelectId; /* Next available select ID for EXPLAIN output */ #endif char **azVar; /* Pointers to names of parameters */ Vdbe *pReprepare; /* VM being reprepared (sqlite3Reprepare()) */ - int *aAlias; /* Register used to hold aliased result */ const char *zTail; /* All SQL text past the last semicolon parsed */ Table *pNewTable; /* A table being constructed by CREATE TABLE */ Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */ const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */ Token sNameToken; /* Token with unqualified schema object name */ @@ -2769,19 +2788,21 @@ void sqlite3CollapseDatabaseArray(sqlite3*); void sqlite3BeginParse(Parse*,int); void sqlite3CommitInternalChanges(sqlite3*); Table *sqlite3ResultSetOfSelect(Parse*,Select*); void sqlite3OpenMasterTable(Parse *, int); +Index *sqlite3PrimaryKeyIndex(Table*); +i16 sqlite3ColumnOfIndex(Index*, i16); void sqlite3StartTable(Parse*,Token*,Token*,int,int,int,int); void sqlite3AddColumn(Parse*,Token*); void sqlite3AddNotNull(Parse*, int); void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int); void sqlite3AddCheckConstraint(Parse*, Expr*); void sqlite3AddColumnType(Parse*,Token*); void sqlite3AddDefaultValue(Parse*,ExprSpan*); void sqlite3AddCollateType(Parse*, Token*); -void sqlite3EndTable(Parse*,Token*,Token*,Select*); +void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*); int sqlite3ParseUri(const char*,const char*,unsigned int*, sqlite3_vfs**,char**,char **); Btree *sqlite3DbNameToBtree(sqlite3*,const char*); int sqlite3CodeOnce(Parse *); @@ -2830,10 +2851,11 @@ int sqlite3IndexedByLookup(Parse *, struct SrcList_item *); void sqlite3SrcListShiftJoinType(SrcList*); void sqlite3SrcListAssignCursors(Parse*, SrcList*); void sqlite3IdListDelete(sqlite3*, IdList*); void sqlite3SrcListDelete(sqlite3*, SrcList*); +Index *sqlite3AllocateIndexObject(sqlite3*,i16,int,char**); Index *sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*, Expr*, int, int); void sqlite3DropIndex(Parse*, SrcList*, int); int sqlite3Select(Parse*, Select*, SelectDest*); Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*, @@ -2906,21 +2928,23 @@ int sqlite3ExprIsInteger(Expr*, int*); int sqlite3ExprCanBeNull(const Expr*); void sqlite3ExprCodeIsNullJump(Vdbe*, const Expr*, int, int); int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); int sqlite3IsRowid(const char*); -void sqlite3GenerateRowDelete(Parse*, Table*, int, int, int, Trigger *, int); -void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int*); +void sqlite3GenerateRowDelete(Parse*,Table*,Trigger*,int,int,int,i16,u8,u8); +void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*); int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*); -void sqlite3GenerateConstraintChecks(Parse*,Table*,int,int, - int*,int,int,int,int,int*); -void sqlite3CompleteInsertion(Parse*, Table*, int, int, int*, int, int, int); -int sqlite3OpenTableAndIndices(Parse*, Table*, int, int); +void sqlite3GenerateConstraintChecks(Parse*,Table*,int*,int,int,int,int, + u8,u8,int,int*); +void sqlite3CompleteInsertion(Parse*,Table*,int,int,int,int*,int,int,int); +int sqlite3OpenTableAndIndices(Parse*, Table*, int, int, int*, int*); void sqlite3BeginWriteOperation(Parse*, int, int); void sqlite3MultiWrite(Parse*); void sqlite3MayAbort(Parse*); -void sqlite3HaltConstraint(Parse*, int, int, char*, int); +void sqlite3HaltConstraint(Parse*, int, int, char*, i8, u8); +void sqlite3UniqueConstraint(Parse*, int, Index*); +void sqlite3RowidConstraint(Parse*, int, Table*); Expr *sqlite3ExprDup(sqlite3*,Expr*,int); ExprList *sqlite3ExprListDup(sqlite3*,ExprList*,int); SrcList *sqlite3SrcListDup(sqlite3*,SrcList*,int); IdList *sqlite3IdListDup(sqlite3*,IdList*); Select *sqlite3SelectDup(sqlite3*,Select*,int); @@ -3134,12 +3158,17 @@ int sqlite3IsLikeFunction(sqlite3*,Expr*,int*,char*); void sqlite3MinimumFileFormat(Parse*, int, int); void sqlite3SchemaClear(void *); Schema *sqlite3SchemaGet(sqlite3 *, Btree *); int sqlite3SchemaToIndex(sqlite3 *db, Schema *); -KeyInfo *sqlite3KeyInfoAlloc(sqlite3*,int); -KeyInfo *sqlite3IndexKeyinfo(Parse *, Index *); +KeyInfo *sqlite3KeyInfoAlloc(sqlite3*,int,int); +void sqlite3KeyInfoUnref(KeyInfo*); +KeyInfo *sqlite3KeyInfoRef(KeyInfo*); +KeyInfo *sqlite3KeyInfoOfIndex(Parse*, Index*); +#ifdef SQLITE_DEBUG +int sqlite3KeyInfoIsWriteable(KeyInfo*); +#endif int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*), FuncDestructor *pDestructor ); Index: src/tokenize.c ================================================================== --- src/tokenize.c +++ src/tokenize.c @@ -504,11 +504,10 @@ } sqlite3DeleteTrigger(db, pParse->pNewTrigger); for(i=pParse->nzVar-1; i>=0; i--) sqlite3DbFree(db, pParse->azVar[i]); sqlite3DbFree(db, pParse->azVar); - sqlite3DbFree(db, pParse->aAlias); while( pParse->pAinc ){ AutoincInfo *p = pParse->pAinc; pParse->pAinc = p->pNext; sqlite3DbFree(db, p); } Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -93,44 +93,52 @@ Expr *pWhere, /* The WHERE clause. May be null */ int onError /* How to handle constraint errors */ ){ int i, j; /* Loop counters */ Table *pTab; /* The table to be updated */ - int addr = 0; /* VDBE instruction address of the start of the loop */ + int addrTop = 0; /* VDBE instruction address of the start of the loop */ WhereInfo *pWInfo; /* Information about the WHERE clause */ Vdbe *v; /* The virtual database engine */ Index *pIdx; /* For looping over indices */ + Index *pPk; /* The PRIMARY KEY index for WITHOUT ROWID tables */ int nIdx; /* Number of indices that need updating */ - int iCur; /* VDBE Cursor number of pTab */ + int iDataCur; /* Cursor for the canonical data btree */ + int iIdxCur; /* Cursor for the first index */ sqlite3 *db; /* The database structure */ int *aRegIdx = 0; /* One register assigned to each index to be updated */ int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the ** an expression for the i-th column of the table. ** aXRef[i]==-1 if the i-th column is not changed. */ - int chngRowid; /* True if the record number is being changed */ + u8 chngPk; /* PRIMARY KEY changed in a WITHOUT ROWID table */ + u8 chngRowid; /* Rowid changed in a normal table */ + u8 chngKey; /* Either chngPk or chngRowid */ Expr *pRowidExpr = 0; /* Expression defining the new record number */ int openAll = 0; /* True if all indices need to be opened */ AuthContext sContext; /* The authorization context */ NameContext sNC; /* The name-context to resolve expressions in */ int iDb; /* Database containing the table being updated */ int okOnePass; /* True for one-pass algorithm without the FIFO */ int hasFK; /* True if foreign key processing is required */ + int labelBreak; /* Jump here to break out of UPDATE loop */ + int labelContinue; /* Jump here to continue next step of UPDATE loop */ #ifndef SQLITE_OMIT_TRIGGER int isView; /* True when updating a view (INSTEAD OF trigger) */ Trigger *pTrigger; /* List of triggers on pTab, if required */ int tmask; /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ #endif int newmask; /* Mask of NEW.* columns accessed by BEFORE triggers */ + int iEph = 0; /* Ephemeral table holding all primary key values */ /* Register Allocations */ int regRowCount = 0; /* A count of rows changed */ int regOldRowid; /* The old rowid */ int regNewRowid; /* The new rowid */ int regNew; /* Content of the NEW.* table in triggers */ int regOld = 0; /* Content of OLD.* table in triggers */ int regRowSet = 0; /* Rowset of rows to be updated */ + int regKey = 0; /* composite PRIMARY KEY value */ memset(&sContext, 0, sizeof(sContext)); db = pParse->db; if( pParse->nErr || db->mallocFailed ){ goto update_cleanup; @@ -173,12 +181,18 @@ /* Allocate a cursors for the main database table and for all indices. ** The index cursors might not be used, but if they are used they ** need to occur right after the database cursor. So go ahead and ** allocate enough space, just in case. */ - pTabList->a[0].iCursor = iCur = pParse->nTab++; - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + pTabList->a[0].iCursor = iDataCur = pParse->nTab++; + iIdxCur = iDataCur+1; + pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ + if( pIdx->autoIndex==2 && pPk!=0 ){ + iDataCur = pParse->nTab; + pTabList->a[0].iCursor = iDataCur; + } pParse->nTab++; } /* Initialize the name-context */ memset(&sNC, 0, sizeof(sNC)); @@ -189,27 +203,29 @@ ** of the UPDATE statement. Also find the column index ** for each column to be updated in the pChanges array. For each ** column to be updated, make sure we have authorization to change ** that column. */ - chngRowid = 0; + chngRowid = chngPk = 0; for(i=0; inExpr; i++){ if( sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){ goto update_cleanup; } for(j=0; jnCol; j++){ if( sqlite3StrICmp(pTab->aCol[j].zName, pChanges->a[i].zName)==0 ){ if( j==pTab->iPKey ){ chngRowid = 1; pRowidExpr = pChanges->a[i].pExpr; + }else if( pPk && (pTab->aCol[j].colFlags & COLFLAG_PRIMKEY)!=0 ){ + chngPk = 1; } aXRef[j] = i; break; } } if( j>=pTab->nCol ){ - if( sqlite3IsRowid(pChanges->a[i].zName) ){ + if( pPk==0 && sqlite3IsRowid(pChanges->a[i].zName) ){ j = -1; chngRowid = 1; pRowidExpr = pChanges->a[i].pExpr; }else{ sqlite3ErrorMsg(pParse, "no such column: %s", pChanges->a[i].zName); @@ -229,30 +245,33 @@ aXRef[j] = -1; } } #endif } + assert( (chngRowid & chngPk)==0 ); + assert( chngRowid==0 || chngRowid==1 ); + assert( chngPk==0 || chngPk==1 ); + chngKey = chngRowid + chngPk; - hasFK = sqlite3FkRequired(pParse, pTab, aXRef, chngRowid); + hasFK = sqlite3FkRequired(pParse, pTab, aXRef, chngKey); /* Allocate memory for the array aRegIdx[]. There is one entry in the ** array for each index associated with table being updated. Fill in ** the value with a register number for indices that are to be used ** and with zero for unused indices. */ - for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){} if( nIdx>0 ){ aRegIdx = sqlite3DbMallocRaw(db, sizeof(Index*) * nIdx ); if( aRegIdx==0 ) goto update_cleanup; } for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ int reg; - if( hasFK || chngRowid || pIdx->pPartIdxWhere ){ + if( chngKey || hasFK || pIdx->pPartIdxWhere || pIdx==pPk ){ reg = ++pParse->nMem; }else{ reg = 0; - for(i=0; inColumn; i++){ + for(i=0; inKeyCol; i++){ if( aXRef[pIdx->aiColumn[i]]>=0 ){ reg = ++pParse->nMem; break; } } @@ -278,15 +297,15 @@ #endif /* Allocate required registers. */ regRowSet = ++pParse->nMem; regOldRowid = regNewRowid = ++pParse->nMem; - if( pTrigger || hasFK ){ + if( chngPk || pTrigger || hasFK ){ regOld = pParse->nMem + 1; pParse->nMem += pTab->nCol; } - if( chngRowid || pTrigger || hasFK ){ + if( chngKey || pTrigger || hasFK ){ regNewRowid = ++pParse->nMem; } regNew = pParse->nMem + 1; pParse->nMem += pTab->nCol; @@ -298,11 +317,11 @@ /* If we are trying to update a view, realize that view into ** a ephemeral table. */ #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) if( isView ){ - sqlite3MaterializeView(pParse, pTab, pWhere, iCur); + sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur); } #endif /* Resolve the column names in all the expressions in the ** WHERE clause. @@ -311,27 +330,52 @@ goto update_cleanup; } /* Begin the database scan */ - sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid); - pWInfo = sqlite3WhereBegin( - pParse, pTabList, pWhere, 0, 0, WHERE_ONEPASS_DESIRED, 0 - ); - if( pWInfo==0 ) goto update_cleanup; - okOnePass = sqlite3WhereOkOnePass(pWInfo); - - /* Remember the rowid of every item to be updated. - */ - sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regOldRowid); - if( !okOnePass ){ - sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid); - } - - /* End the database scan loop. - */ - sqlite3WhereEnd(pWInfo); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid); + pWInfo = sqlite3WhereBegin( + pParse, pTabList, pWhere, 0, 0, WHERE_ONEPASS_DESIRED, 0 + ); + if( pWInfo==0 ) goto update_cleanup; + okOnePass = sqlite3WhereOkOnePass(pWInfo); + + /* Remember the rowid of every item to be updated. + */ + sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid); + if( !okOnePass ){ + sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid); + } + + /* End the database scan loop. + */ + sqlite3WhereEnd(pWInfo); + }else{ + int iPk; /* First of nPk memory cells holding PRIMARY KEY value */ + i16 nPk; /* Number of components of the PRIMARY KEY */ + + assert( pPk!=0 ); + nPk = pPk->nKeyCol; + iPk = pParse->nMem+1; + pParse->nMem += nPk; + regKey = ++pParse->nMem; + iEph = pParse->nTab++; + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, 0, 0); + if( pWInfo==0 ) goto update_cleanup; + for(i=0; iaiColumn[i], + iPk+i); + } + sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey, + sqlite3IndexAffinityStr(v, pPk), P4_TRANSIENT); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iEph, regKey); + sqlite3WhereEnd(pWInfo); + okOnePass = 0; + } /* Initialize the count of updated rows */ if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab ){ regRowCount = ++pParse->nMem; @@ -343,11 +387,14 @@ ** Open every index that needs updating. Note that if any ** index could potentially invoke a REPLACE conflict resolution ** action, then we need to open all indices because we might need ** to be deleting some records. */ - if( !okOnePass ) sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenWrite); + if( !okOnePass && HasRowid(pTab) ){ + sqlite3OpenTable(pParse, iDataCur, iDb, pTab, OP_OpenWrite); + } + sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName); if( onError==OE_Replace ){ openAll = 1; }else{ openAll = 0; for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ @@ -358,57 +405,62 @@ } } for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ assert( aRegIdx ); if( openAll || aRegIdx[i]>0 ){ - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); - sqlite3VdbeAddOp4(v, OP_OpenWrite, iCur+i+1, pIdx->tnum, iDb, - (char*)pKey, P4_KEYINFO_HANDOFF); - assert( pParse->nTab>iCur+i+1 ); + sqlite3VdbeAddOp3(v, OP_OpenWrite, iIdxCur+i, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + assert( pParse->nTab>iIdxCur+i ); + VdbeComment((v, "%s", pIdx->zName)); } } } /* Top of the update loop */ - if( okOnePass ){ - int a1 = sqlite3VdbeAddOp1(v, OP_NotNull, regOldRowid); - addr = sqlite3VdbeAddOp0(v, OP_Goto); - sqlite3VdbeJumpHere(v, a1); + labelBreak = sqlite3VdbeMakeLabel(v); + if( pPk ){ + labelContinue = sqlite3VdbeMakeLabel(v); + sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); + addrTop = sqlite3VdbeAddOp2(v, OP_RowKey, iEph, regKey); + sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0); + }else if( okOnePass ){ + labelContinue = labelBreak; + sqlite3VdbeAddOp2(v, OP_IsNull, regOldRowid, labelBreak); }else{ - addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet, 0, regOldRowid); + labelContinue = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet, labelBreak, + regOldRowid); + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid); } - /* Make cursor iCur point to the record that is being updated. If - ** this record does not exist for some reason (deleted by a trigger, - ** for example, then jump to the next iteration of the RowSet loop. */ - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid); - /* If the record number will change, set register regNewRowid to ** contain the new value. If the record number is not being modified, ** then regNewRowid is the same register as regOldRowid, which is ** already populated. */ - assert( chngRowid || pTrigger || hasFK || regOldRowid==regNewRowid ); + assert( chngKey || pTrigger || hasFK || regOldRowid==regNewRowid ); if( chngRowid ){ sqlite3ExprCode(pParse, pRowidExpr, regNewRowid); sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid); } - /* If there are triggers on this table, populate an array of registers - ** with the required old.* column data. */ - if( hasFK || pTrigger ){ + /* Compute the old pre-UPDATE content of the row being changed, if that + ** information is needed */ + if( chngPk || hasFK || pTrigger ){ u32 oldmask = (hasFK ? sqlite3FkOldmask(pParse, pTab) : 0); oldmask |= sqlite3TriggerColmask(pParse, pTrigger, pChanges, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onError ); for(i=0; inCol; i++){ - if( aXRef[i]<0 || oldmask==0xffffffff || (i<32 && (oldmask & (1<aCol[i].colFlags & COLFLAG_PRIMKEY)!=0 + ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regOld+i); }else{ sqlite3VdbeAddOp2(v, OP_Null, 0, regOld+i); } } - if( chngRowid==0 ){ + if( chngRowid==0 && pPk==0 ){ sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid); } } /* Populate the array of registers beginning at regNew with the new @@ -441,12 +493,11 @@ ** if there are one or more BEFORE triggers that use this value via ** a new.* reference in a trigger program. */ testcase( i==31 ); testcase( i==32 ); - sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regNew+i); - sqlite3ColumnDefault(v, pTab, i, regNew+i); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regNew+i); } } } /* Fire any BEFORE UPDATE triggers. This happens before constraints are @@ -454,67 +505,80 @@ */ if( tmask&TRIGGER_BEFORE ){ sqlite3VdbeAddOp2(v, OP_Affinity, regNew, pTab->nCol); sqlite3TableAffinityStr(v, pTab); sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, - TRIGGER_BEFORE, pTab, regOldRowid, onError, addr); + TRIGGER_BEFORE, pTab, regOldRowid, onError, labelContinue); /* The row-trigger may have deleted the row being updated. In this ** case, jump to the next row. No updates or AFTER triggers are ** required. This behavior - what happens when the row being updated ** is deleted or renamed by a BEFORE trigger - is left undefined in the ** documentation. */ - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid); + if( pPk ){ + sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0); + }else{ + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid); + } /* If it did not delete it, the row-trigger may still have modified ** some of the columns of the row being updated. Load the values for ** all columns not modified by the update statement into their ** registers in case this has happened. */ for(i=0; inCol; i++){ if( aXRef[i]<0 && i!=pTab->iPKey ){ - sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regNew+i); - sqlite3ColumnDefault(v, pTab, i, regNew+i); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regNew+i); } } } if( !isView ){ int j1; /* Address of jump instruction */ /* Do constraint checks. */ - sqlite3GenerateConstraintChecks(pParse, pTab, iCur, regNewRowid, - aRegIdx, (chngRowid?regOldRowid:0), 1, onError, addr, 0); + assert( regOldRowid>0 ); + sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, + regNewRowid, regOldRowid, chngKey, onError, labelContinue, 0); /* Do FK constraint checks. */ if( hasFK ){ - sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngRowid); + sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngKey); } /* Delete the index entries associated with the current record. */ - j1 = sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regOldRowid); - sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, aRegIdx); + if( pPk ){ + j1 = sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, 0, regKey, 0); + }else{ + j1 = sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, 0, regOldRowid); + } + sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, aRegIdx); /* If changing the record number, delete the old record. */ - if( hasFK || chngRowid ){ - sqlite3VdbeAddOp2(v, OP_Delete, iCur, 0); + if( hasFK || chngKey || pPk!=0 ){ + sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0); } - sqlite3VdbeJumpHere(v, j1); + if( sqlite3VdbeCurrentAddr(v)==j1+1 ){ + sqlite3VdbeChangeToNoop(v, j1); + }else{ + sqlite3VdbeJumpHere(v, j1); + } if( hasFK ){ - sqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngRowid); + sqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngKey); } /* Insert the new index entries and the new record. */ - sqlite3CompleteInsertion(pParse, pTab, iCur, regNewRowid, aRegIdx, 1, 0, 0); + sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur, + regNewRowid, aRegIdx, 1, 0, 0); /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key ** to the row just updated. */ if( hasFK ){ - sqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngRowid); + sqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngKey); } } /* Increment the row counter */ @@ -521,26 +585,31 @@ if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab){ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); } sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, - TRIGGER_AFTER, pTab, regOldRowid, onError, addr); + TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue); /* Repeat the above with the next record to be updated, until ** all record selected by the WHERE clause have been updated. */ - sqlite3VdbeAddOp2(v, OP_Goto, 0, addr); - sqlite3VdbeJumpHere(v, addr); + if( pPk ){ + sqlite3VdbeResolveLabel(v, labelContinue); + sqlite3VdbeAddOp2(v, OP_Next, iEph, addrTop); + }else if( !okOnePass ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, labelContinue); + } + sqlite3VdbeResolveLabel(v, labelBreak); /* Close all tables */ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ assert( aRegIdx ); if( openAll || aRegIdx[i]>0 ){ - sqlite3VdbeAddOp2(v, OP_Close, iCur+i+1, 0); + sqlite3VdbeAddOp2(v, OP_Close, iIdxCur, 0); } } - sqlite3VdbeAddOp2(v, OP_Close, iCur, 0); + if( iDataCur0" + " AND coalesce(rootpage,1)>0" ); if( rc!=SQLITE_OK ) goto end_of_vacuum; rc = execExecSql(db, pzErrMsg, "SELECT 'CREATE INDEX vacuum_db.' || substr(sql,14)" " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %' "); @@ -253,11 +253,11 @@ rc = execExecSql(db, pzErrMsg, "SELECT 'INSERT INTO vacuum_db.' || quote(name) " "|| ' SELECT * FROM main.' || quote(name) || ';'" "FROM main.sqlite_master " "WHERE type = 'table' AND name!='sqlite_sequence' " - " AND rootpage>0" + " AND coalesce(rootpage,1)>0" ); if( rc!=SQLITE_OK ) goto end_of_vacuum; /* Copy over the sequence table */ Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -802,24 +802,25 @@ REGISTER_TRACE(pOp->p1, pIn1); pc = pcDest; break; } -/* Opcode: HaltIfNull P1 P2 P3 P4 * +/* Opcode: HaltIfNull P1 P2 P3 P4 P5 ** Synopsis: if r[P3] null then halt ** ** Check the value in register P3. If it is NULL then Halt using ** parameter P1, P2, and P4 as if this were a Halt instruction. If the ** value in register P3 is not NULL, then this routine is a no-op. +** The P5 parameter should be 1. */ case OP_HaltIfNull: { /* in3 */ pIn3 = &aMem[pOp->p3]; if( (pIn3->flags & MEM_Null)==0 ) break; /* Fall through into OP_Halt */ } -/* Opcode: Halt P1 P2 * P4 * +/* Opcode: Halt P1 P2 * P4 P5 ** ** Exit immediately. All open cursors, etc are closed ** automatically. ** ** P1 is the result code returned by sqlite3_exec(), sqlite3_reset(), @@ -829,16 +830,30 @@ ** if P2==OE_Fail. Do the rollback if P2==OE_Rollback. If P2==OE_Abort, ** then back out all changes that have occurred during this execution of the ** VDBE, but do not rollback the transaction. ** ** If P4 is not null then it is an error message string. +** +** P5 is a value between 0 and 4, inclusive, that modifies the P4 string. +** +** 0: (no change) +** 1: NOT NULL contraint failed: P4 +** 2: UNIQUE constraint failed: P4 +** 3: CHECK constraint failed: P4 +** 4: FOREIGN KEY constraint failed: P4 +** +** If P5 is not zero and P4 is NULL, then everything after the ":" is +** omitted. ** ** There is an implied "Halt 0 0 0" instruction inserted at the very end of ** every program. So a jump past the last instruction of the program ** is the same as executing Halt. */ case OP_Halt: { + const char *zType; + const char *zLogFmt; + if( pOp->p1==SQLITE_OK && p->pFrame ){ /* Halt the sub-program. Return control to the parent frame. */ VdbeFrame *pFrame = p->pFrame; p->pFrame = pFrame->pParent; p->nFrame--; @@ -855,22 +870,38 @@ } aOp = p->aOp; aMem = p->aMem; break; } - p->rc = pOp->p1; p->errorAction = (u8)pOp->p2; p->pc = pc; - if( pOp->p4.z ){ - assert( p->rc!=SQLITE_OK ); - sqlite3SetString(&p->zErrMsg, db, "%s", pOp->p4.z); - testcase( sqlite3GlobalConfig.xLog!=0 ); - sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pc, p->zSql, pOp->p4.z); - }else if( p->rc ){ - testcase( sqlite3GlobalConfig.xLog!=0 ); - sqlite3_log(pOp->p1, "constraint failed at %d in [%s]", pc, p->zSql); + if( p->rc ){ + if( pOp->p5 ){ + static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK", + "FOREIGN KEY" }; + assert( pOp->p5>=1 && pOp->p5<=4 ); + testcase( pOp->p5==1 ); + testcase( pOp->p5==2 ); + testcase( pOp->p5==3 ); + testcase( pOp->p5==4 ); + zType = azType[pOp->p5-1]; + }else{ + zType = 0; + } + zLogFmt = "abort at %d in [%s]: %s"; + if( zType && pOp->p4.z ){ + sqlite3SetString(&p->zErrMsg, db, "%s constraint failed: %s", + zType, pOp->p4.z); + }else if( pOp->p4.z ){ + sqlite3SetString(&p->zErrMsg, db, "%s", pOp->p4.z); + }else if( zType ){ + sqlite3SetString(&p->zErrMsg, db, "%s constraint failed", zType); + }else{ + zLogFmt = "abort at %d in [%s]"; + } + sqlite3_log(pOp->p1, zLogFmt, pc, p->zSql, p->zErrMsg); } rc = sqlite3VdbeHalt(p); assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR ); if( rc==SQLITE_BUSY ){ p->rc = rc = SQLITE_BUSY; @@ -1117,24 +1148,23 @@ ** Worse, if the original is deallocated, the copy becomes invalid. ** Thus the program must guarantee that the original will not change ** during the lifetime of the copy. Use OP_Copy to make a complete ** copy. */ -case OP_SCopy: { /* in1, out2 */ +case OP_SCopy: { /* out2 */ pIn1 = &aMem[pOp->p1]; pOut = &aMem[pOp->p2]; assert( pOut!=pIn1 ); sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem); #ifdef SQLITE_DEBUG if( pOut->pScopyFrom==0 ) pOut->pScopyFrom = pIn1; #endif - REGISTER_TRACE(pOp->p2, pOut); break; } /* Opcode: ResultRow P1 P2 * * * -** Synopsis: output=r[P1].. columns=P1 +** Synopsis: output=r[P1@P2] ** ** The registers P1 through P1+P2-1 contain a single row of ** results. This opcode causes the sqlite3_step() call to terminate ** with an SQLITE_ROW return code and it sets up the sqlite3_stmt ** structure to provide access to the top P1 values as the result @@ -1202,11 +1232,11 @@ rc = SQLITE_ROW; goto vdbe_return; } /* Opcode: Concat P1 P2 P3 * * -** Synopsis: r[P3]=r[P2]+r[P3] +** Synopsis: r[P3]=r[P2]+r[P1] ** ** Add the text in register P1 onto the end of the text in ** register P2 and store the result in register P3. ** If either the P1 or P2 text are NULL then store NULL in P3. ** @@ -1747,11 +1777,11 @@ break; } #endif /* !defined(SQLITE_OMIT_CAST) && !defined(SQLITE_OMIT_FLOATING_POINT) */ /* Opcode: Lt P1 P2 P3 P4 P5 -** Synopsis: r[P1] < r[P3] +** Synopsis: if r[P1] r[P3] +** Synopsis: if r[P1]>r[P3] goto P2 ** ** This works just like the Lt opcode except that the jump is taken if ** the content of register P3 is greater than the content of ** register P1. See the Lt opcode for additional information. */ /* Opcode: Ge P1 P2 P3 P4 P5 -** Synopsis: r[P1] >= r[P3] +** Synopsis: if r[P1]>=r[P3] goto P2 ** ** This works just like the Lt opcode except that the jump is taken if ** the content of register P3 is greater than or equal to the content of ** register P1. See the Lt opcode for additional information. */ @@ -3256,12 +3286,13 @@ goto abort_due_to_error; } } if( pOp->p4type==P4_KEYINFO ){ pKeyInfo = pOp->p4.pKeyInfo; - pKeyInfo->enc = ENC(p->db); - nField = pKeyInfo->nField+1; + assert( pKeyInfo->enc==ENC(db) ); + assert( pKeyInfo->db==db ); + nField = pKeyInfo->nField+pKeyInfo->nXField; }else if( pOp->p4type==P4_INT32 ){ nField = pOp->p4.i; } assert( pOp->p1>=0 ); pCur = allocateCursor(p, pOp->p1, nField, iDb, 1); @@ -3313,17 +3344,18 @@ ** indices in joins. */ case OP_OpenAutoindex: case OP_OpenEphemeral: { VdbeCursor *pCx; + KeyInfo *pKeyInfo; + static const int vfsFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE | SQLITE_OPEN_TRANSIENT_DB; - assert( pOp->p1>=0 ); pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, 1); if( pCx==0 ) goto no_mem; pCx->nullRow = 1; rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBt, @@ -3335,20 +3367,20 @@ /* If a transient index is required, create it by calling ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before ** opening it. If a transient table is required, just use the ** automatically created table with root-page 1 (an BLOB_INTKEY table). */ - if( pOp->p4.pKeyInfo ){ + if( (pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ int pgno; assert( pOp->p4type==P4_KEYINFO ); rc = sqlite3BtreeCreateTable(pCx->pBt, &pgno, BTREE_BLOBKEY | pOp->p5); if( rc==SQLITE_OK ){ assert( pgno==MASTER_ROOT+1 ); - rc = sqlite3BtreeCursor(pCx->pBt, pgno, 1, - (KeyInfo*)pOp->p4.z, pCx->pCursor); - pCx->pKeyInfo = pOp->p4.pKeyInfo; - pCx->pKeyInfo->enc = ENC(p->db); + assert( pKeyInfo->db==db ); + assert( pKeyInfo->enc==ENC(db) ); + pCx->pKeyInfo = pKeyInfo; + rc = sqlite3BtreeCursor(pCx->pBt, pgno, 1, pKeyInfo, pCx->pCursor); } pCx->isTable = 0; }else{ rc = sqlite3BtreeCursor(pCx->pBt, MASTER_ROOT, 1, 0, pCx->pCursor); pCx->isTable = 1; @@ -3357,12 +3389,11 @@ pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); pCx->isIndex = !pCx->isTable; break; } -/* Opcode: SorterOpen P1 P2 * P4 * -** Synopsis: nColumn=P2 +/* Opcode: SorterOpen P1 * * P4 * ** ** This opcode works like OP_OpenEphemeral except that it opens ** a transient index that is specifically designed to sort large ** tables using an external merge-sort algorithm. */ @@ -3370,11 +3401,12 @@ VdbeCursor *pCx; pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, 1); if( pCx==0 ) goto no_mem; pCx->pKeyInfo = pOp->p4.pKeyInfo; - pCx->pKeyInfo->enc = ENC(p->db); + assert( pCx->pKeyInfo->db==db ); + assert( pCx->pKeyInfo->enc==ENC(db) ); pCx->isSorter = 1; rc = sqlite3VdbeSorterInit(db, pCx); break; } @@ -3665,10 +3697,12 @@ ** record. ** ** Cursor P1 is on an index btree. If the record identified by P3 and P4 ** is a prefix of any entry in P1 then a jump is made to P2 and ** P1 is left pointing at the matching entry. +** +** See also: NotFound, NoConflict, NotExists. SeekGe */ /* Opcode: NotFound P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** ** If P4==0 then register P3 holds a blob constructed by MakeRecord. If @@ -3679,24 +3713,45 @@ ** is not the prefix of any entry in P1 then a jump is made to P2. If P1 ** does contain an entry whose prefix matches the P3/P4 record then control ** falls through to the next instruction and P1 is left pointing at the ** matching entry. ** -** See also: Found, NotExists, IsUnique +** See also: Found, NotExists, NoConflict */ +/* Opcode: NoConflict P1 P2 P3 P4 * +** Synopsis: key=r[P3@P4] +** +** If P4==0 then register P3 holds a blob constructed by MakeRecord. If +** P4>0 then register P3 is the first of P4 registers that form an unpacked +** record. +** +** Cursor P1 is on an index btree. If the record identified by P3 and P4 +** contains any NULL value, jump immediately to P2. If all terms of the +** record are not-NULL then a check is done to determine if any row in the +** P1 index btree has a matching key prefix. If there are no matches, jump +** immediately to P2. If there is a match, fall through and leave the P1 +** cursor pointing to the matching row. +** +** This opcode is similar to OP_NotFound with the exceptions that the +** branch is always taken if any part of the search key input is NULL. +** +** See also: NotFound, Found, NotExists +*/ +case OP_NoConflict: /* jump, in3 */ case OP_NotFound: /* jump, in3 */ case OP_Found: { /* jump, in3 */ int alreadyExists; + int ii; VdbeCursor *pC; int res; char *pFree; UnpackedRecord *pIdxKey; UnpackedRecord r; - char aTempRec[ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*3 + 7]; + char aTempRec[ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*4 + 7]; #ifdef SQLITE_TEST - sqlite3_found_count++; + if( pOp->opcode!=OP_NoConflict ) sqlite3_found_count++; #endif alreadyExists = 0; assert( pOp->p1>=0 && pOp->p1nCursor ); assert( pOp->p4type==P4_INT32 ); @@ -3709,11 +3764,17 @@ if( pOp->p4.i>0 ){ r.pKeyInfo = pC->pKeyInfo; r.nField = (u16)pOp->p4.i; r.aMem = pIn3; #ifdef SQLITE_DEBUG - { int i; for(i=0; ip3+i, &r.aMem[i]); + } + } #endif r.flags = UNPACKED_PREFIX_MATCH; pIdxKey = &r; }else{ pIdxKey = sqlite3VdbeAllocUnpackedRecord( @@ -3722,19 +3783,32 @@ if( pIdxKey==0 ) goto no_mem; assert( pIn3->flags & MEM_Blob ); assert( (pIn3->flags & MEM_Zero)==0 ); /* zeroblobs already expanded */ sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey); pIdxKey->flags |= UNPACKED_PREFIX_MATCH; + } + if( pOp->opcode==OP_NoConflict ){ + /* For the OP_NoConflict opcode, take the jump if any of the + ** input fields are NULL, since any key with a NULL will not + ** conflict */ + for(ii=0; iip2 - 1; + break; + } + } } rc = sqlite3BtreeMovetoUnpacked(pC->pCursor, pIdxKey, 0, 0, &res); if( pOp->p4.i==0 ){ sqlite3DbFree(db, pFree); } if( rc!=SQLITE_OK ){ break; } + pC->seekResult = res; alreadyExists = (res==0); + pC->nullRow = 1-alreadyExists; pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; } if( pOp->opcode==OP_Found ){ if( alreadyExists ) pc = pOp->p2 - 1; @@ -3742,111 +3816,23 @@ if( !alreadyExists ) pc = pOp->p2 - 1; } break; } -/* Opcode: IsUnique P1 P2 P3 P4 * -** -** Cursor P1 is open on an index b-tree - that is to say, a btree which -** no data and where the key are records generated by OP_MakeRecord with -** the list field being the integer ROWID of the entry that the index -** entry refers to. -** -** The P3 register contains an integer record number. Call this record -** number R. Register P4 is the first in a set of N contiguous registers -** that make up an unpacked index key that can be used with cursor P1. -** The value of N can be inferred from the cursor. N includes the rowid -** value appended to the end of the index record. This rowid value may -** or may not be the same as R. -** -** If any of the N registers beginning with register P4 contains a NULL -** value, jump immediately to P2. -** -** Otherwise, this instruction checks if cursor P1 contains an entry -** where the first (N-1) fields match but the rowid value at the end -** of the index entry is not R. If there is no such entry, control jumps -** to instruction P2. Otherwise, the rowid of the conflicting index -** entry is copied to register P3 and control falls through to the next -** instruction. -** -** See also: NotFound, NotExists, Found -*/ -case OP_IsUnique: { /* jump, in3 */ - u16 ii; - VdbeCursor *pCx; - BtCursor *pCrsr; - u16 nField; - Mem *aMx; - UnpackedRecord r; /* B-Tree index search key */ - i64 R; /* Rowid stored in register P3 */ - - pIn3 = &aMem[pOp->p3]; - aMx = &aMem[pOp->p4.i]; - /* Assert that the values of parameters P1 and P4 are in range. */ - assert( pOp->p4type==P4_INT32 ); - assert( pOp->p4.i>0 && pOp->p4.i<=(p->nMem-p->nCursor) ); - assert( pOp->p1>=0 && pOp->p1nCursor ); - - /* Find the index cursor. */ - pCx = p->apCsr[pOp->p1]; - assert( pCx->deferredMoveto==0 ); - pCx->seekResult = 0; - pCx->cacheStatus = CACHE_STALE; - pCrsr = pCx->pCursor; - - /* If any of the values are NULL, take the jump. */ - nField = pCx->pKeyInfo->nField; - for(ii=0; iip2 - 1; - pCrsr = 0; - break; - } - } - assert( (aMx[nField].flags & MEM_Null)==0 ); - - if( pCrsr!=0 ){ - /* Populate the index search key. */ - r.pKeyInfo = pCx->pKeyInfo; - r.nField = nField + 1; - r.flags = UNPACKED_PREFIX_SEARCH; - r.aMem = aMx; -#ifdef SQLITE_DEBUG - { int i; for(i=0; iu.i; - - /* Search the B-Tree index. If no conflicting record is found, jump - ** to P2. Otherwise, copy the rowid of the conflicting record to - ** register P3 and fall through to the next instruction. */ - rc = sqlite3BtreeMovetoUnpacked(pCrsr, &r, 0, 0, &pCx->seekResult); - if( (r.flags & UNPACKED_PREFIX_SEARCH) || r.rowid==R ){ - pc = pOp->p2 - 1; - }else{ - pIn3->u.i = r.rowid; - } - } - break; -} - /* Opcode: NotExists P1 P2 P3 * * ** Synopsis: intkey=r[P3] ** -** Use the content of register P3 as an integer key. If a record -** with that key does not exist in table of P1, then jump to P2. -** If the record does exist, then fall through. The cursor is left -** pointing to the record if it exists. -** -** The difference between this operation and NotFound is that this -** operation assumes the key is an integer and that P1 is a table whereas -** NotFound assumes key is a blob constructed from MakeRecord and -** P1 is an index. -** -** See also: Found, NotFound, IsUnique +** P1 is the index of a cursor open on an SQL table btree (with integer +** keys). P3 is an integer rowid. If P1 does not contain a record with +** rowid P3 then jump immediately to P2. If P1 does contain a record +** with rowid P3 then leave the cursor pointing at that record and fall +** through to the next instruction. +** +** The OP_NotFound opcode performs the same operation on index btrees +** (with arbitrary multi-value keys). +** +** See also: Found, NotFound, NoConflict */ case OP_NotExists: { /* jump, in3 */ VdbeCursor *pC; BtCursor *pCrsr; int res; @@ -4240,26 +4226,36 @@ sqlite3VdbeSetChanges(db, p->nChange); p->nChange = 0; break; } -/* Opcode: SorterCompare P1 P2 P3 -** Synopsis: if key(P1)!=r[P3] goto P2 +/* Opcode: SorterCompare P1 P2 P3 P4 +** Synopsis: if key(P1)!=rtrim(r[P3],P4) goto P2 ** -** P1 is a sorter cursor. This instruction compares the record blob in -** register P3 with the entry that the sorter cursor currently points to. -** If, excluding the rowid fields at the end, the two records are a match, -** fall through to the next instruction. Otherwise, jump to instruction P2. +** P1 is a sorter cursor. This instruction compares a prefix of the +** the record blob in register P3 against a prefix of the entry that +** the sorter cursor currently points to. The final P4 fields of both +** the P3 and sorter record are ignored. +** +** If either P3 or the sorter contains a NULL in one of their significant +** fields (not counting the P4 fields at the end which are ignored) then +** the comparison is assumed to be equal. +** +** Fall through to next instruction if the two records compare equal to +** each other. Jump to P2 if they are different. */ case OP_SorterCompare: { VdbeCursor *pC; int res; + int nIgnore; pC = p->apCsr[pOp->p1]; assert( isSorter(pC) ); + assert( pOp->p4type==P4_INT32 ); pIn3 = &aMem[pOp->p3]; - rc = sqlite3VdbeSorterCompare(pC, pIn3, &res); + nIgnore = pOp->p4.i; + rc = sqlite3VdbeSorterCompare(pC, pIn3, nIgnore, &res); if( res ){ pc = pOp->p2-1; } break; }; @@ -4358,10 +4354,11 @@ }else{ rc = sqlite3BtreeData(pCrsr, 0, n, pOut->z); } pOut->enc = SQLITE_UTF8; /* In case the blob is ever cast to text */ UPDATE_MAX_BLOBSIZE(pOut); + REGISTER_TRACE(pOp->p2, pOut); break; } /* Opcode: Rowid P1 P2 * * * ** Synopsis: r[P2]=rowid @@ -4616,10 +4613,11 @@ assert( pC!=0 ); assert( pC->isSorter==(pOp->opcode==OP_SorterInsert) ); pIn2 = &aMem[pOp->p2]; assert( pIn2->flags & MEM_Blob ); pCrsr = pC->pCursor; + if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; if( ALWAYS(pCrsr!=0) ){ assert( pC->isTable==0 ); rc = ExpandBlob(pIn2); if( rc==SQLITE_OK ){ if( isSorter(pC) ){ @@ -4636,11 +4634,11 @@ } } break; } -/* Opcode: IdxDelete P1 P2 P3 * * +/* Opcode: IdxDelete P1 P2 P3 * P5 ** Synopsis: key=r[P2@P3] ** ** The content of P3 registers starting at register P2 form ** an unpacked index key. This opcode removes that entry from the ** index opened by cursor P1. @@ -4655,10 +4653,11 @@ assert( pOp->p2>0 && pOp->p2+pOp->p3<=(p->nMem-p->nCursor)+1 ); assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); pCrsr = pC->pCursor; + if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; if( ALWAYS(pCrsr!=0) ){ r.pKeyInfo = pC->pKeyInfo; r.nField = (u16)pOp->p3; r.flags = 0; r.aMem = &aMem[pOp->p2]; Index: src/vdbe.h ================================================================== --- src/vdbe.h +++ src/vdbe.h @@ -115,19 +115,15 @@ #define P4_INT32 (-14) /* P4 is a 32-bit signed integer */ #define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ #define P4_SUBPROGRAM (-18) /* P4 is a pointer to a SubProgram structure */ #define P4_ADVANCE (-19) /* P4 is a pointer to BtreeNext() or BtreePrev() */ -/* When adding a P4 argument using P4_KEYINFO, a copy of the KeyInfo structure -** is made. That copy is freed when the Vdbe is finalized. But if the -** argument is P4_KEYINFO_HANDOFF, the passed in pointer is used. It still -** gets freed when the Vdbe is finalized so it still should be obtained -** from a single sqliteMalloc(). But no copy is made and the calling -** function should *not* try to free the KeyInfo. -*/ -#define P4_KEYINFO_HANDOFF (-16) -#define P4_KEYINFO_STATIC (-17) +/* Error message codes for OP_Halt */ +#define P5_ConstraintNotNull 1 +#define P5_ConstraintUnique 2 +#define P5_ConstraintCheck 3 +#define P5_ConstraintFK 4 /* ** The Vdbe.aColName array contains 5n Mem structures, where n is the ** number of columns of data returned by the statement. */ @@ -178,10 +174,11 @@ void sqlite3VdbeChangeP3(Vdbe*, u32 addr, int P3); void sqlite3VdbeChangeP5(Vdbe*, u8 P5); void sqlite3VdbeJumpHere(Vdbe*, int addr); void sqlite3VdbeChangeToNoop(Vdbe*, int addr); void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N); +void sqlite3VdbeSetP4KeyInfo(Parse*, Index*); void sqlite3VdbeUsesBtree(Vdbe*, int); VdbeOp *sqlite3VdbeGetOp(Vdbe*, int); int sqlite3VdbeMakeLabel(Vdbe*); void sqlite3VdbeRunOnlyOnce(Vdbe*); void sqlite3VdbeDelete(Vdbe*); @@ -216,17 +213,29 @@ #ifndef SQLITE_OMIT_TRIGGER void sqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *); #endif - +/* Use SQLITE_ENABLE_COMMENTS to enable generation of extra comments on +** each VDBE opcode. +** +** Use the SQLITE_ENABLE_MODULE_COMMENTS macro to see some extra no-op +** comments in VDBE programs that show key decision points in the code +** generator. +*/ #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS void sqlite3VdbeComment(Vdbe*, const char*, ...); # define VdbeComment(X) sqlite3VdbeComment X void sqlite3VdbeNoopComment(Vdbe*, const char*, ...); # define VdbeNoopComment(X) sqlite3VdbeNoopComment X +# ifdef SQLITE_ENABLE_MODULE_COMMENTS +# define VdbeModuleComment(X) sqlite3VdbeNoopComment X +# else +# define VdbeModuleComment(X) +# endif #else # define VdbeComment(X) # define VdbeNoopComment(X) +# define VdbeModuleComment(X) #endif #endif Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -80,12 +80,11 @@ i64 seqCount; /* Sequence counter */ i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */ i64 lastRowid; /* Last rowid from a Next or NextIdx operation */ VdbeSorter *pSorter; /* Sorter object for OP_SorterOpen cursors */ - /* Result of last sqlite3BtreeMoveto() done by an OP_NotExists or - ** OP_IsUnique opcode on this cursor. */ + /* Result of last sqlite3BtreeMoveto() done by an OP_NotExists */ int seekResult; /* Cached information about the header for the data record that the ** cursor is currently pointing to. Only valid if cacheStatus matches ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of @@ -446,11 +445,11 @@ void sqlite3VdbeSorterClose(sqlite3 *, VdbeCursor *); int sqlite3VdbeSorterRowkey(const VdbeCursor *, Mem *); int sqlite3VdbeSorterNext(sqlite3 *, const VdbeCursor *, int *); int sqlite3VdbeSorterRewind(sqlite3 *, const VdbeCursor *, int *); int sqlite3VdbeSorterWrite(sqlite3 *, const VdbeCursor *, Mem *); -int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int *); +int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int, int *); #if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0 void sqlite3VdbeEnter(Vdbe*); void sqlite3VdbeLeave(Vdbe*); #else Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -105,10 +105,21 @@ p->aOp = pNew; } return (pNew ? SQLITE_OK : SQLITE_NOMEM); } +#ifdef SQLITE_DEBUG +/* This routine is just a convenient place to set a breakpoint that will +** fire after each opcode is inserted and displayed using +** "PRAGMA vdbe_addoptrace=on". +*/ +static void test_addop_breakpoint(void){ + static int n = 0; + n++; +} +#endif + /* ** Add a new instruction to the list of instructions current in the ** VDBE. Return the address of the new instruction. ** ** Parameters: @@ -148,10 +159,11 @@ pOp->zComment = 0; #endif #ifdef SQLITE_DEBUG if( p->db->flags & SQLITE_VdbeAddopTrace ){ sqlite3VdbePrintOp(0, i, &p->aOp[i]); + test_addop_breakpoint(); } #endif #ifdef VDBE_PROFILE pOp->cycles = 0; pOp->cnt = 0; @@ -623,15 +635,17 @@ assert( db ); switch( p4type ){ case P4_REAL: case P4_INT64: case P4_DYNAMIC: - case P4_KEYINFO: - case P4_INTARRAY: - case P4_KEYINFO_HANDOFF: { + case P4_INTARRAY: { sqlite3DbFree(db, p4); break; + } + case P4_KEYINFO: { + if( db->pnBytesFreed==0 ) sqlite3KeyInfoUnref((KeyInfo*)p4); + break; } case P4_MPRINTF: { if( db->pnBytesFreed==0 ) sqlite3_free(p4); break; } @@ -693,10 +707,11 @@ VdbeOp *pOp = &p->aOp[addr]; sqlite3 *db = p->db; freeP4(db, pOp->p4type, pOp->p4.p); memset(pOp, 0, sizeof(pOp[0])); pOp->opcode = OP_Noop; + if( addr==p->nOp-1 ) p->nOp--; } } /* ** Change the value of the P4 operand for a specific instruction. @@ -706,18 +721,10 @@ ** ** If n>=0 then the P4 operand is dynamic, meaning that a copy of ** the string is made into memory obtained from sqlite3_malloc(). ** A value of n==0 means copy bytes of zP4 up to and including the ** first null byte. If n>0 then copy n+1 bytes of zP4. -** -** If n==P4_KEYINFO it means that zP4 is a pointer to a KeyInfo structure. -** A copy is made of the KeyInfo structure into memory obtained from -** sqlite3_malloc, to be freed when the Vdbe is finalized. -** n==P4_KEYINFO_HANDOFF indicates that zP4 points to a KeyInfo structure -** stored in memory that the caller has obtained from sqlite3_malloc. The -** caller should not free the allocation, it will be freed when the Vdbe is -** finalized. ** ** Other values of n (P4_STATIC, P4_COLLSEQ etc.) indicate that zP4 points ** to a string or structure that is guaranteed to exist for the lifetime of ** the Vdbe. In these cases we can just copy the pointer. ** @@ -728,11 +735,11 @@ sqlite3 *db; assert( p!=0 ); db = p->db; assert( p->magic==VDBE_MAGIC_INIT ); if( p->aOp==0 || db->mallocFailed ){ - if ( n!=P4_KEYINFO && n!=P4_VTAB ) { + if( n!=P4_VTAB ){ freeP4(db, n, (void*)*(char**)&zP4); } return; } assert( p->nOp>0 ); @@ -751,23 +758,10 @@ pOp->p4type = P4_INT32; }else if( zP4==0 ){ pOp->p4.p = 0; pOp->p4type = P4_NOTUSED; }else if( n==P4_KEYINFO ){ - KeyInfo *pOrig, *pNew; - - pOrig = (KeyInfo*)zP4; - pOp->p4.pKeyInfo = pNew = sqlite3KeyInfoAlloc(db, pOrig->nField); - if( pNew ){ - memcpy(pNew->aColl, pOrig->aColl, pOrig->nField*sizeof(pNew->aColl[0])); - memcpy(pNew->aSortOrder, pOrig->aSortOrder, pOrig->nField); - pOp->p4type = P4_KEYINFO; - }else{ - p->db->mallocFailed = 1; - pOp->p4type = P4_NOTUSED; - } - }else if( n==P4_KEYINFO_HANDOFF ){ pOp->p4.p = (void*)zP4; pOp->p4type = P4_KEYINFO; }else if( n==P4_VTAB ){ pOp->p4.p = (void*)zP4; pOp->p4type = P4_VTAB; @@ -780,10 +774,22 @@ if( n==0 ) n = sqlite3Strlen30(zP4); pOp->p4.z = sqlite3DbStrNDup(p->db, zP4, n); pOp->p4type = P4_DYNAMIC; } } + +/* +** Set the P4 on the most recently added opcode to the KeyInfo for the +** index given. +*/ +void sqlite3VdbeSetP4KeyInfo(Parse *pParse, Index *pIdx){ + Vdbe *v = pParse->pVdbe; + assert( v!=0 ); + assert( pIdx!=0 ); + sqlite3VdbeChangeP4(v, -1, (char*)sqlite3KeyInfoOfIndex(pParse, pIdx), + P4_KEYINFO); +} #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS /* ** Change the comment on the most recently coded instruction. Or ** insert a No-op and add the comment to that new instruction. This @@ -941,21 +947,24 @@ */ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ char *zP4 = zTemp; assert( nTemp>=20 ); switch( pOp->p4type ){ - case P4_KEYINFO_STATIC: case P4_KEYINFO: { int i, j; KeyInfo *pKeyInfo = pOp->p4.pKeyInfo; assert( pKeyInfo->aSortOrder!=0 ); - sqlite3_snprintf(nTemp, zTemp, "keyinfo(%d", pKeyInfo->nField); + sqlite3_snprintf(nTemp, zTemp, "k(%d", pKeyInfo->nField); i = sqlite3Strlen30(zTemp); for(j=0; jnField; j++){ CollSeq *pColl = pKeyInfo->aColl[j]; const char *zColl = pColl ? pColl->zName : "nil"; int n = sqlite3Strlen30(zColl); + if( n==6 && memcmp(zColl,"BINARY",6)==0 ){ + zColl = "B"; + n = 1; + } if( i+n>nTemp-6 ){ memcpy(&zTemp[i],",...",4); break; } zTemp[i++] = ','; @@ -1125,11 +1134,11 @@ */ void sqlite3VdbePrintOp(FILE *pOut, int pc, Op *pOp){ char *zP4; char zPtr[50]; char zCom[100]; - static const char *zFormat1 = "%4d %-13s %4d %4d %4d %-4s %.2X %s\n"; + static const char *zFormat1 = "%4d %-13s %4d %4d %4d %-13s %.2X %s\n"; if( pOut==0 ) pOut = stdout; zP4 = displayP4(pOp, zPtr, sizeof(zPtr)); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS displayComment(pOp, zP4, zCom, sizeof(zCom)); #else @@ -2177,11 +2186,11 @@ if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0) || (!deferred && p->nFkConstraint>0) ){ p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; p->errorAction = OE_Abort; - sqlite3SetString(&p->zErrMsg, db, "foreign key constraint failed"); + sqlite3SetString(&p->zErrMsg, db, "FOREIGN KEY constraint failed"); return SQLITE_ERROR; } return SQLITE_OK; } #endif @@ -3108,11 +3117,11 @@ */ /* mem1.u.i = 0; // not needed, here to silence compiler warning */ idx1 = getVarint32(aKey1, szHdr1); d1 = szHdr1; - assert( pKeyInfo->nField+1>=pPKey2->nField ); + assert( pKeyInfo->nField+pKeyInfo->nXField>=pPKey2->nField ); assert( pKeyInfo->aSortOrder!=0 ); while( idx1nField ){ u32 serial_type1; /* Read the serial types for the next element in each key. */ @@ -3137,28 +3146,13 @@ /* Do the comparison */ rc = sqlite3MemCompare(&mem1, &pPKey2->aMem[i], pKeyInfo->aColl[i]); if( rc!=0 ){ assert( mem1.zMalloc==0 ); /* See comment below */ - - /* Invert the result if we are using DESC sort order. */ if( pKeyInfo->aSortOrder[i] ){ - rc = -rc; - } - - /* If the PREFIX_SEARCH flag is set and all fields except the final - ** rowid field were equal, then clear the PREFIX_SEARCH flag and set - ** pPKey2->rowid to the value of the rowid field in (pKey1, nKey1). - ** This is used by the OP_IsUnique opcode. - */ - if( (pPKey2->flags & UNPACKED_PREFIX_SEARCH) && i==(pPKey2->nField-1) ){ - assert( idx1==szHdr1 && rc ); - assert( mem1.flags & MEM_Int ); - pPKey2->flags &= ~UNPACKED_PREFIX_SEARCH; - pPKey2->rowid = mem1.u.i; - } - + rc = -rc; /* Invert the result for DESC sort order. */ + } return rc; } i++; } Index: src/vdbeblob.c ================================================================== --- src/vdbeblob.c +++ src/vdbeblob.c @@ -176,10 +176,14 @@ pTab = sqlite3LocateTable(pParse, 0, zTable, zDb); if( pTab && IsVirtual(pTab) ){ pTab = 0; sqlite3ErrorMsg(pParse, "cannot open virtual table: %s", zTable); } + if( pTab && !HasRowid(pTab) ){ + pTab = 0; + sqlite3ErrorMsg(pParse, "cannot open table without rowid: %s", zTable); + } #ifndef SQLITE_OMIT_VIEW if( pTab && pTab->pSelect ){ pTab = 0; sqlite3ErrorMsg(pParse, "cannot open view: %s", zTable); } @@ -233,11 +237,11 @@ } } #endif for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ int j; - for(j=0; jnColumn; j++){ + for(j=0; jnKeyCol; j++){ if( pIdx->aiColumn[j]==iCol ){ zFault = "indexed"; } } } Index: src/vdbemem.c ================================================================== --- src/vdbemem.c +++ src/vdbemem.c @@ -1029,19 +1029,19 @@ if( pRec==0 ){ Index *pIdx = p->pIdx; /* Index being probed */ int nByte; /* Bytes of space to allocate */ int i; /* Counter variable */ - int nCol = pIdx->nColumn+1; /* Number of index columns including rowid */ + int nCol = pIdx->nColumn; /* Number of index columns including rowid */ nByte = sizeof(Mem) * nCol + sizeof(UnpackedRecord); pRec = (UnpackedRecord*)sqlite3DbMallocZero(db, nByte); if( pRec ){ - pRec->pKeyInfo = sqlite3IndexKeyinfo(p->pParse, pIdx); + pRec->pKeyInfo = sqlite3KeyInfoOfIndex(p->pParse, pIdx); if( pRec->pKeyInfo ){ - assert( pRec->pKeyInfo->nField+1==nCol ); - pRec->pKeyInfo->enc = ENC(db); + assert( pRec->pKeyInfo->nField+pRec->pKeyInfo->nXField==nCol ); + assert( pRec->pKeyInfo->enc==ENC(db) ); pRec->flags = UNPACKED_PREFIX_MATCH; pRec->aMem = (Mem *)&pRec[1]; for(i=0; iaMem[i].flags = MEM_Null; pRec->aMem[i].type = SQLITE_NULL; @@ -1360,17 +1360,17 @@ ** the object. */ void sqlite3Stat4ProbeFree(UnpackedRecord *pRec){ if( pRec ){ int i; - int nCol = pRec->pKeyInfo->nField+1; + int nCol = pRec->pKeyInfo->nField+pRec->pKeyInfo->nXField; Mem *aMem = pRec->aMem; sqlite3 *db = aMem[0].db; for(i=0; ipKeyInfo); + sqlite3KeyInfoUnref(pRec->pKeyInfo); sqlite3DbFree(db, pRec); } } #endif /* ifdef SQLITE_ENABLE_STAT4 */ Index: src/vdbesort.c ================================================================== --- src/vdbesort.c +++ src/vdbesort.c @@ -384,11 +384,11 @@ ** If pKey2 is passed a NULL pointer, then it is assumed that the pCsr->aSpace ** has been allocated and contains an unpacked record that is used as key2. */ static void vdbeSorterCompare( const VdbeCursor *pCsr, /* Cursor object (for pKeyInfo) */ - int bOmitRowid, /* Ignore rowid field at end of keys */ + int nIgnore, /* Ignore the last nIgnore fields */ const void *pKey1, int nKey1, /* Left side of comparison */ const void *pKey2, int nKey2, /* Right side of comparison */ int *pRes /* OUT: Result of comparison */ ){ KeyInfo *pKeyInfo = pCsr->pKeyInfo; @@ -398,12 +398,12 @@ if( pKey2 ){ sqlite3VdbeRecordUnpack(pKeyInfo, nKey2, pKey2, r2); } - if( bOmitRowid ){ - r2->nField = pKeyInfo->nField; + if( nIgnore ){ + r2->nField = pKeyInfo->nField - nIgnore; assert( r2->nField>0 ); for(i=0; inField; i++){ if( r2->aMem[i].flags & MEM_Null ){ *pRes = -1; return; @@ -1025,14 +1025,15 @@ ** key. */ int sqlite3VdbeSorterCompare( const VdbeCursor *pCsr, /* Sorter cursor */ Mem *pVal, /* Value to compare to current sorter key */ + int nIgnore, /* Ignore this many fields at the end */ int *pRes /* OUT: Result of comparison */ ){ VdbeSorter *pSorter = pCsr->pSorter; void *pKey; int nKey; /* Sorter key to compare pVal with */ pKey = vdbeSorterRowkey(pSorter, &nKey); - vdbeSorterCompare(pCsr, 1, pVal->z, pVal->n, pKey, nKey, pRes); + vdbeSorterCompare(pCsr, nIgnore, pVal->z, pVal->n, pKey, nKey, pRes); return SQLITE_OK; } Index: src/where.c ================================================================== --- src/where.c +++ src/where.c @@ -969,11 +969,11 @@ pScan->pOrigWC = pWC; pScan->pWC = pWC; if( pIdx && iColumn>=0 ){ pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; for(j=0; pIdx->aiColumn[j]!=iColumn; j++){ - if( NEVER(j>=pIdx->nColumn) ) return 0; + if( NEVER(j>=pIdx->nKeyCol) ) return 0; } pScan->zCollName = pIdx->azColl[j]; }else{ pScan->idxaff = 0; pScan->zCollName = 0; @@ -1899,20 +1899,20 @@ ** 3. All of those index columns for which the WHERE clause does not ** contain a "col=X" term are subject to a NOT NULL constraint. */ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ if( pIdx->onError==OE_None ) continue; - for(i=0; inColumn; i++){ - int iCol = pIdx->aiColumn[i]; + for(i=0; inKeyCol; i++){ + i16 iCol = pIdx->aiColumn[i]; if( 0==findTerm(pWC, iBase, iCol, ~(Bitmask)0, WO_EQ, pIdx) ){ int iIdxCol = findIndexCol(pParse, pDistinct, iBase, pIdx, i); - if( iIdxCol<0 || pTab->aCol[pIdx->aiColumn[i]].notNull==0 ){ + if( iIdxCol<0 || pTab->aCol[iCol].notNull==0 ){ break; } } } - if( i==pIdx->nColumn ){ + if( i==pIdx->nKeyCol ){ /* This index implies that the DISTINCT qualifier is redundant. */ return 1; } } @@ -2006,26 +2006,25 @@ WhereClause *pWC, /* The WHERE clause */ struct SrcList_item *pSrc, /* The FROM clause term to get the next index */ Bitmask notReady, /* Mask of cursors that are not available */ WhereLevel *pLevel /* Write new index here */ ){ - int nColumn; /* Number of columns in the constructed index */ + int nKeyCol; /* Number of columns in the constructed index */ WhereTerm *pTerm; /* A single term of the WHERE clause */ WhereTerm *pWCEnd; /* End of pWC->a[] */ - int nByte; /* Byte of memory needed for pIdx */ Index *pIdx; /* Object describing the transient index */ Vdbe *v; /* Prepared statement under construction */ int addrInit; /* Address of the initialization bypass jump */ Table *pTable; /* The table being indexed */ - KeyInfo *pKeyinfo; /* Key information for the index */ int addrTop; /* Top of the index fill loop */ int regRecord; /* Register holding an index record */ int n; /* Column counter */ int i; /* Loop counter */ int mxBitCol; /* Maximum column in pSrc->colUsed */ CollSeq *pColl; /* Collating sequence to on a column */ WhereLoop *pLoop; /* The Loop object */ + char *zNotUsed; /* Extra space on the end of pIdx */ Bitmask idxCols; /* Bitmap of columns used for indexing */ Bitmask extraCols; /* Bitmap of additional columns */ u8 sentWarning = 0; /* True if a warnning has been issued */ /* Generate code to skip over the creation and initialization of the @@ -2034,11 +2033,11 @@ assert( v!=0 ); addrInit = sqlite3CodeOnce(pParse); /* Count the number of columns that will be added to the index ** and used to match WHERE clause constraints */ - nColumn = 0; + nKeyCol = 0; pTable = pSrc->pTab; pWCEnd = &pWC->a[pWC->nTerm]; pLoop = pLevel->pWLoop; idxCols = 0; for(pTerm=pWC->a; pTermzName, pTable->aCol[iCol].zName); sentWarning = 1; } if( (idxCols & cMask)==0 ){ - if( whereLoopResize(pParse->db, pLoop, nColumn+1) ) return; - pLoop->aLTerm[nColumn++] = pTerm; + if( whereLoopResize(pParse->db, pLoop, nKeyCol+1) ) return; + pLoop->aLTerm[nKeyCol++] = pTerm; idxCols |= cMask; } } } - assert( nColumn>0 ); - pLoop->u.btree.nEq = pLoop->nLTerm = nColumn; + assert( nKeyCol>0 ); + pLoop->u.btree.nEq = pLoop->nLTerm = nKeyCol; pLoop->wsFlags = WHERE_COLUMN_EQ | WHERE_IDX_ONLY | WHERE_INDEXED | WHERE_AUTO_INDEX; /* Count the number of additional columns needed to create a ** covering index. A "covering index" is an index that contains all @@ -2076,30 +2075,22 @@ extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1)); mxBitCol = (pTable->nCol >= BMS-1) ? BMS-1 : pTable->nCol; testcase( pTable->nCol==BMS-1 ); testcase( pTable->nCol==BMS-2 ); for(i=0; icolUsed & MASKBIT(BMS-1) ){ - nColumn += pTable->nCol - BMS + 1; + nKeyCol += pTable->nCol - BMS + 1; } pLoop->wsFlags |= WHERE_COLUMN_EQ | WHERE_IDX_ONLY; /* Construct the Index object to describe this index */ - nByte = sizeof(Index); - nByte += nColumn*sizeof(int); /* Index.aiColumn */ - nByte += nColumn*sizeof(char*); /* Index.azColl */ - nByte += nColumn; /* Index.aSortOrder */ - pIdx = sqlite3DbMallocZero(pParse->db, nByte); + pIdx = sqlite3AllocateIndexObject(pParse->db, nKeyCol+1, 0, &zNotUsed); if( pIdx==0 ) return; pLoop->u.btree.pIndex = pIdx; - pIdx->azColl = (char**)&pIdx[1]; - pIdx->aiColumn = (int*)&pIdx->azColl[nColumn]; - pIdx->aSortOrder = (u8*)&pIdx->aiColumn[nColumn]; pIdx->zName = "auto-index"; - pIdx->nColumn = nColumn; pIdx->pTable = pTable; n = 0; idxCols = 0; for(pTerm=pWC->a; pTermaiColumn[n] = i; pIdx->azColl[n] = "BINARY"; n++; } } - assert( n==nColumn ); + assert( n==nKeyCol ); + pIdx->aiColumn[n] = -1; + pIdx->azColl[n] = "BINARY"; /* Create the automatic index */ - pKeyinfo = sqlite3IndexKeyinfo(pParse, pIdx); assert( pLevel->iIdxCur>=0 ); pLevel->iIdxCur = pParse->nTab++; - sqlite3VdbeAddOp4(v, OP_OpenAutoindex, pLevel->iIdxCur, nColumn+1, 0, - (char*)pKeyinfo, P4_KEYINFO_HANDOFF); + sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "for %s", pTable->zName)); /* Fill the automatic index with content */ addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); regRecord = sqlite3GetTempReg(pParse); - sqlite3GenerateIndexKey(pParse, pIdx, pLevel->iTabCur, regRecord, 1, 0); + sqlite3GenerateIndexKey(pParse, pIdx, pLevel->iTabCur, regRecord, 0, 0); sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX); sqlite3VdbeJumpHere(v, addrTop); @@ -2393,11 +2385,11 @@ iUpper = aSample[0].anLt[iCol]; }else{ iUpper = i>=pIdx->nSample ? pIdx->aiRowEst[0] : aSample[i].anLt[iCol]; iLower = aSample[i-1].anEq[iCol] + aSample[i-1].anLt[iCol]; } - aStat[1] = (pIdx->nColumn>iCol ? pIdx->aAvgEq[iCol] : 1); + aStat[1] = (pIdx->nKeyCol>iCol ? pIdx->aAvgEq[iCol] : 1); if( iLower>=iUpper ){ iGap = 0; }else{ iGap = iUpper - iLower; } @@ -2493,11 +2485,11 @@ ** of iUpper are requested of whereKeyStats() and the smaller used. */ tRowcnt iLower; tRowcnt iUpper; - if( nEq==p->nColumn ){ + if( nEq==p->nKeyCol ){ aff = SQLITE_AFF_INTEGER; }else{ aff = p->pTable->aCol[p->aiColumn[nEq]].affinity; } /* Determine iLower and iUpper using ($P) only. */ @@ -2611,11 +2603,11 @@ int rc; /* Subfunction return code */ tRowcnt a[2]; /* Statistics */ int bOk; assert( nEq>=1 ); - assert( nEq<=(p->nColumn+1) ); + assert( nEq<=(p->nKeyCol+1) ); assert( p->aSample!=0 ); assert( p->nSample>0 ); assert( pBuilder->nRecValidp->nColumn ){ + if( nEq>p->nKeyCol ){ *pnRow = 1; return SQLITE_OK; } aff = p->pTable->aCol[p->aiColumn[nEq-1]].affinity; @@ -3011,31 +3003,31 @@ static char *explainIndexRange(sqlite3 *db, WhereLoop *pLoop, Table *pTab){ Index *pIndex = pLoop->u.btree.pIndex; int nEq = pLoop->u.btree.nEq; int i, j; Column *aCol = pTab->aCol; - int *aiColumn = pIndex->aiColumn; + i16 *aiColumn = pIndex->aiColumn; StrAccum txt; if( nEq==0 && (pLoop->wsFlags & (WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))==0 ){ return 0; } sqlite3StrAccumInit(&txt, 0, 0, SQLITE_MAX_LENGTH); txt.db = db; sqlite3StrAccumAppend(&txt, " (", 2); for(i=0; inColumn ) ? "rowid" : aCol[aiColumn[i]].zName; + char *z = (i==pIndex->nKeyCol ) ? "rowid" : aCol[aiColumn[i]].zName; explainAppendTerm(&txt, i, z, "="); } j = i; if( pLoop->wsFlags&WHERE_BTM_LIMIT ){ - char *z = (j==pIndex->nColumn ) ? "rowid" : aCol[aiColumn[j]].zName; + char *z = (j==pIndex->nKeyCol ) ? "rowid" : aCol[aiColumn[j]].zName; explainAppendTerm(&txt, i++, z, ">"); } if( pLoop->wsFlags&WHERE_TOP_LIMIT ){ - char *z = (j==pIndex->nColumn ) ? "rowid" : aCol[aiColumn[j]].zName; + char *z = (j==pIndex->nKeyCol ) ? "rowid" : aCol[aiColumn[j]].zName; explainAppendTerm(&txt, i, z, "<"); } sqlite3StrAccumAppend(&txt, ")", 1); return sqlite3StrAccumFinish(&txt); } @@ -3415,11 +3407,11 @@ ** the first one after the nEq equality constraints in the index, ** this requires some special handling. */ if( (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)!=0 && (pWInfo->bOBSat!=0) - && (pIdx->nColumn>nEq) + && (pIdx->nKeyCol>nEq) ){ /* assert( pOrderBy->nExpr==1 ); */ /* assert( pOrderBy->a[0].pExpr->iColumn==pIdx->aiColumn[nEq] ); */ isMinQuery = 1; nExtraReg = 1; @@ -3448,12 +3440,12 @@ /* If we are doing a reverse order scan on an ascending index, or ** a forward order scan on a descending index, interchange the ** start and end terms (pRangeStart and pRangeEnd). */ - if( (nEqnColumn && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) - || (bRev && pIdx->nColumn==nEq) + if( (nEqnKeyCol && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) + || (bRev && pIdx->nKeyCol==nEq) ){ SWAP(WhereTerm *, pRangeEnd, pRangeStart); } testcase( pRangeStart && (pRangeStart->eOperator & WO_LE)!=0 ); @@ -3558,15 +3550,26 @@ sqlite3ReleaseTempReg(pParse, r1); /* Seek the table cursor, if required */ disableTerm(pLevel, pRangeStart); disableTerm(pLevel, pRangeEnd); - if( !omitTable ){ + if( omitTable ){ + /* pIdx is a covering index. No need to access the main table. */ + }else if( HasRowid(pIdx->pTable) ){ iRowidReg = iReleaseReg = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, iRowidReg); sqlite3ExprCacheStore(pParse, iCur, -1, iRowidReg); sqlite3VdbeAddOp2(v, OP_Seek, iCur, iRowidReg); /* Deferred seek */ + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable); + iRowidReg = sqlite3GetTempRange(pParse, pPk->nKeyCol); + for(j=0; jnKeyCol; j++){ + k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[j]); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, iRowidReg+j); + } + sqlite3VdbeAddOp4Int(v, OP_NotFound, iCur, addrCont, + iRowidReg, pPk->nKeyCol); } /* Record the instruction used to terminate the loop. Disable ** WHERE clause terms made redundant by the index range scan. */ @@ -3989,10 +3992,11 @@ sqlite3_free(p->u.vtab.idxStr); p->u.vtab.needFree = 0; p->u.vtab.idxStr = 0; }else if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 && p->u.btree.pIndex!=0 ){ sqlite3DbFree(db, p->u.btree.pIndex->zColAff); + sqlite3KeyInfoUnref(p->u.btree.pIndex->pKeyInfo); sqlite3DbFree(db, p->u.btree.pIndex); p->u.btree.pIndex = 0; } } } @@ -4288,12 +4292,12 @@ }else{ opMask = WO_EQ|WO_IN|WO_ISNULL|WO_GT|WO_GE|WO_LT|WO_LE; } if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); - assert( pNew->u.btree.nEq<=pProbe->nColumn ); - if( pNew->u.btree.nEq < pProbe->nColumn ){ + assert( pNew->u.btree.nEq<=pProbe->nKeyCol ); + if( pNew->u.btree.nEq < pProbe->nKeyCol ){ iCol = pProbe->aiColumn[pNew->u.btree.nEq]; nRowEst = sqlite3LogEst(pProbe->aiRowEst[pNew->u.btree.nEq+1]); if( nRowEst==0 && pProbe->onError==OE_None ) nRowEst = 1; }else{ iCol = -1; @@ -4346,11 +4350,11 @@ assert( (pNew->wsFlags & (WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0 || nInMul==0 ); pNew->wsFlags |= WHERE_COLUMN_EQ; if( iCol<0 || (pProbe->onError!=OE_None && nInMul==0 - && pNew->u.btree.nEq==pProbe->nColumn-1) + && pNew->u.btree.nEq==pProbe->nKeyCol-1) ){ assert( (pNew->wsFlags & WHERE_COLUMN_IN)==0 || iCol<0 ); pNew->wsFlags |= WHERE_ONEROW; } pNew->u.btree.nEq++; @@ -4412,11 +4416,11 @@ /* Step cost for each output row */ pNew->rRun = sqlite3LogEstAdd(pNew->rRun, pNew->nOut); whereLoopOutputAdjust(pBuilder->pWC, pNew); rc = whereLoopInsert(pBuilder, pNew); if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 - && pNew->u.btree.nEq<(pProbe->nColumn + (pProbe->zName!=0)) + && pNew->u.btree.nEq<(pProbe->nKeyCol + (pProbe->zName!=0)) ){ whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn); } pNew->nOut = saved_nOut; #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 @@ -4451,11 +4455,11 @@ if( (pOB = pBuilder->pWInfo->pOrderBy)==0 ) return 0; for(ii=0; iinExpr; ii++){ Expr *pExpr = sqlite3ExprSkipCollate(pOB->a[ii].pExpr); if( pExpr->op!=TK_COLUMN ) return 0; if( pExpr->iTable==iCursor ){ - for(jj=0; jjnColumn; jj++){ + for(jj=0; jjnKeyCol; jj++){ if( pExpr->iColumn==pIndex->aiColumn[jj] ) return 1; } } } return 0; @@ -4468,14 +4472,15 @@ static Bitmask columnsInIndex(Index *pIdx){ Bitmask m = 0; int j; for(j=pIdx->nColumn-1; j>=0; j--){ int x = pIdx->aiColumn[j]; - assert( x>=0 ); - testcase( x==BMS-1 ); - testcase( x==BMS-2 ); - if( x=0 ){ + testcase( x==BMS-1 ); + testcase( x==BMS-2 ); + if( xpTab) ); if( pSrc->pIndex ){ /* An INDEXED BY clause specifies a particular index to use */ pProbe = pSrc->pIndex; + }else if( !HasRowid(pTab) ){ + pProbe = pTab->pIndex; }else{ /* There is no INDEXED BY clause. Create a fake Index object in local ** variable sPk to represent the rowid primary key index. Make this ** fake index the first in a chain of Index objects with all of the real ** indices to follow */ Index *pFirst; /* First of real indices on the table */ memset(&sPk, 0, sizeof(Index)); - sPk.nColumn = 1; + sPk.nKeyCol = 1; sPk.aiColumn = &aiColumnPk; sPk.aiRowEst = aiRowEstPk; sPk.onError = OE_Replace; sPk.pTable = pTab; aiRowEstPk[0] = pTab->nRowEst; @@ -4556,10 +4563,11 @@ if( !pBuilder->pOrSet && (pWInfo->pParse->db->flags & SQLITE_AutoIndex)!=0 && pSrc->pIndex==0 && !pSrc->viaCoroutine && !pSrc->notIndexed + && HasRowid(pTab) && !pSrc->isCorrelated ){ /* Generate auto-index WhereLoops */ WhereTerm *pTerm; WhereTerm *pWCEnd = pWC->a + pWC->nTerm; @@ -4618,18 +4626,24 @@ whereLoopOutputAdjust(pWC, pNew); rc = whereLoopInsert(pBuilder, pNew); pNew->nOut = rSize; if( rc ) break; }else{ - Bitmask m = pSrc->colUsed & ~columnsInIndex(pProbe); - pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED; + Bitmask m; + if( pProbe->isCovering ){ + pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; + m = 0; + }else{ + m = pSrc->colUsed & ~columnsInIndex(pProbe); + pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED; + } /* Full scan via index */ if( b || ( m==0 && pProbe->bUnordered==0 - && pProbe->szIdxRowszTabRow + && (!HasRowid(pTab) || pProbe->szIdxRowszTabRow) && (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 && sqlite3GlobalConfig.bUseCis && OptimizationEnabled(pWInfo->pParse->db, SQLITE_CoverIdxScan) ) ){ @@ -4857,10 +4871,11 @@ if( pWInfo->wctrlFlags & WHERE_AND_ONLY ) return SQLITE_OK; pWCEnd = pWC->a + pWC->nTerm; pNew = pBuilder->pNew; memset(&sSum, 0, sizeof(sSum)); pItem = pWInfo->pTabList->a + pNew->iTab; + if( !HasRowid(pItem->pTab) ) return SQLITE_OK; iCur = pItem->iCursor; for(pTerm=pWC->a; pTermeOperator & WO_OR)!=0 && (pTerm->u.pOrInfo->indexable & pNew->maskSelf)!=0 @@ -5006,11 +5021,12 @@ u8 rev; /* Composite sort order */ u8 revIdx; /* Index sort order */ u8 isOrderDistinct; /* All prior WhereLoops are order-distinct */ u8 distinctColumns; /* True if the loop has UNIQUE NOT NULL columns */ u8 isMatch; /* iColumn matches a term of the ORDER BY clause */ - u16 nColumn; /* Number of columns in pIndex */ + u16 nKeyCol; /* Number of key columns in pIndex */ + u16 nColumn; /* Total number of ordered columns in the index */ u16 nOrderBy; /* Number terms in the ORDER BY clause */ int iLoop; /* Index of WhereLoop in pPath being processed */ int i, j; /* Loop counters */ int iCur; /* Cursor number for current WhereLoop */ int iColumn; /* A column number within table iCur */ @@ -5098,24 +5114,28 @@ } if( (pLoop->wsFlags & WHERE_ONEROW)==0 ){ if( pLoop->wsFlags & WHERE_IPK ){ pIndex = 0; - nColumn = 0; + nKeyCol = 0; + nColumn = 1; }else if( (pIndex = pLoop->u.btree.pIndex)==0 || pIndex->bUnordered ){ return 0; }else{ + nKeyCol = pIndex->nKeyCol; nColumn = pIndex->nColumn; + assert( nColumn==nKeyCol+1 || !HasRowid(pIndex->pTable) ); + assert( pIndex->aiColumn[nColumn-1]==(-1) || !HasRowid(pIndex->pTable)); isOrderDistinct = pIndex->onError!=OE_None; } /* Loop through all columns of the index and deal with the ones ** that are not constrained by == or IN. */ rev = revSet = 0; distinctColumns = 0; - for(j=0; j<=nColumn; j++){ + for(j=0; ju.btree.nEq && ((i = pLoop->aLTerm[j]->eOperator) & (WO_EQ|WO_ISNULL))!=0 @@ -5128,24 +5148,21 @@ } /* Get the column number in the table (iColumn) and sort order ** (revIdx) for the j-th column of the index. */ - if( jaiColumn[j]; revIdx = pIndex->aSortOrder[j]; if( iColumn==pIndex->pTable->iPKey ) iColumn = -1; }else{ - /* The ROWID column at the end */ - assert( j==nColumn ); iColumn = -1; revIdx = 0; } /* An unconstrained column that might be NULL means that this - ** WhereLoop is not well-ordered + ** WhereLoop is not well-ordered */ if( isOrderDistinct && iColumn>=0 && j>=pLoop->u.btree.nEq && pIndex->pTable->aCol[iColumn].notNull==0 @@ -5192,11 +5209,11 @@ revSet = 1; } } }else{ /* No match found */ - if( j==0 || jpIndex; pIdx; pIdx=pIdx->pNext){ assert( pLoop->aLTermSpace==pLoop->aLTerm ); assert( ArraySize(pLoop->aLTermSpace)==4 ); if( pIdx->onError==OE_None || pIdx->pPartIdxWhere!=0 - || pIdx->nColumn>ArraySize(pLoop->aLTermSpace) + || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace) ) continue; - for(j=0; jnColumn; j++){ + for(j=0; jnKeyCol; j++){ pTerm = findTerm(pWC, iCur, pIdx->aiColumn[j], 0, WO_EQ, pIdx); if( pTerm==0 ) break; pLoop->aLTerm[j] = pTerm; } - if( j!=pIdx->nColumn ) continue; + if( j!=pIdx->nKeyCol ) continue; pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_ONEROW|WHERE_INDEXED; - if( (pItem->colUsed & ~columnsInIndex(pIdx))==0 ){ + if( pIdx->isCovering || (pItem->colUsed & ~columnsInIndex(pIdx))==0 ){ pLoop->wsFlags |= WHERE_IDX_ONLY; } pLoop->nLTerm = j; pLoop->u.btree.nEq = j; pLoop->u.btree.pIndex = pIdx; @@ -6005,11 +6022,11 @@ && (wctrlFlags & WHERE_OMIT_OPEN_CLOSE)==0 ){ int op = pWInfo->okOnePass ? OP_OpenWrite : OP_OpenRead; sqlite3OpenTable(pParse, pTabItem->iCursor, iDb, pTab, op); testcase( !pWInfo->okOnePass && pTab->nCol==BMS-1 ); testcase( !pWInfo->okOnePass && pTab->nCol==BMS ); - if( !pWInfo->okOnePass && pTab->nColokOnePass && pTab->nColcolUsed; int n = 0; for(; b; b=b>>1, n++){} sqlite3VdbeChangeP4(v, sqlite3VdbeCurrentAddr(v)-1, SQLITE_INT_TO_PTR(n), P4_INT32); @@ -6018,17 +6035,16 @@ }else{ sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); } if( pLoop->wsFlags & WHERE_INDEXED ){ Index *pIx = pLoop->u.btree.pIndex; - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIx); /* FIXME: As an optimization use pTabItem->iCursor if WHERE_IDX_ONLY */ int iIndexCur = pLevel->iIdxCur = iIdxCur ? iIdxCur : pParse->nTab++; assert( pIx->pSchema==pTab->pSchema ); assert( iIndexCur>=0 ); - sqlite3VdbeAddOp4(v, OP_OpenRead, iIndexCur, pIx->tnum, iDb, - (char*)pKey, P4_KEYINFO_HANDOFF); + sqlite3VdbeAddOp3(v, OP_OpenRead, iIndexCur, pIx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIx); VdbeComment((v, "%s", pIx->zName)); } sqlite3CodeVerifySchema(pParse, iDb); notReady &= ~getMask(&pWInfo->sMaskSet, pTabItem->iCursor); } @@ -6165,27 +6181,31 @@ pIdx = pLoop->u.btree.pIndex; }else if( pLoop->wsFlags & WHERE_MULTI_OR ){ pIdx = pLevel->u.pCovidx; } if( pIdx && !db->mallocFailed ){ - int k, j, last; + int k, last; VdbeOp *pOp; last = sqlite3VdbeCurrentAddr(v); k = pLevel->addrBody; pOp = sqlite3VdbeGetOp(v, k); for(; kp1!=pLevel->iTabCur ) continue; if( pOp->opcode==OP_Column ){ - for(j=0; jnColumn; j++){ - if( pOp->p2==pIdx->aiColumn[j] ){ - pOp->p2 = j; - pOp->p1 = pLevel->iIdxCur; - break; - } - } - assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || jnColumn ); + int x = pOp->p2; + Table *pTab = pIdx->pTable; + if( !HasRowid(pTab) ){ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + x = pPk->aiColumn[x]; + } + x = sqlite3ColumnOfIndex(pIdx, x); + if( x>=0 ){ + pOp->p2 = x; + pOp->p1 = pLevel->iIdxCur; + } + assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || x>=0 ); }else if( pOp->opcode==OP_Rowid ){ pOp->p1 = pLevel->iIdxCur; pOp->opcode = OP_IdxRowid; } } Index: test/alter.test ================================================================== --- test/alter.test +++ test/alter.test @@ -858,7 +858,21 @@ do_test alter-15.$tn.2 { catchsql "ALTER TABLE $tbl ADD COLUMN xyz" } [list 1 "table $tbl may not be altered"] } +#------------------------------------------------------------------------ +# Verify that ALTER TABLE works on tables with the WITHOUT rowid option. +# +do_execsql_test alter-16.1 { + CREATE TABLE t16a(a TEXT, b REAL, c INT, PRIMARY KEY(a,b)) WITHOUT rowid; + INSERT INTO t16a VALUES('abc',1.25,99); + ALTER TABLE t16a ADD COLUMN d TEXT DEFAULT 'xyzzy'; + INSERT INTO t16a VALUES('cba',5.5,98,'fizzle'); + SELECT * FROM t16a ORDER BY a; +} {abc 1.25 99 xyzzy cba 5.5 98 fizzle} +do_execsql_test alter-16.2 { + ALTER TABLE t16a RENAME TO t16a_rn; + SELECT * FROM t16a_rn ORDER BY a; +} {abc 1.25 99 xyzzy cba 5.5 98 fizzle} finish_test Index: test/autovacuum.test ================================================================== --- test/autovacuum.test +++ test/autovacuum.test @@ -524,11 +524,11 @@ } {5049} do_test autovacuum-4.2 { catchsql { CREATE UNIQUE INDEX av1_i ON av1(a); } -} {1 {indexed columns are not unique}} +} {1 {UNIQUE constraint failed: av1.a}} do_test autovacuum-4.3 { execsql { SELECT sum(a) FROM av1; } } {5049} Index: test/backcompat.test ================================================================== --- test/backcompat.test +++ test/backcompat.test @@ -198,10 +198,46 @@ set same [expr {[sql2 {SELECT md5sum(a), md5sum(b) FROM t1}] == $cksum2}] do_test backcompat-1.2.6 [list set {} $same] 1 do_test backcompat-1.2.7 { sql1 { PRAGMA integrity_check } } {ok} do_test backcompat-1.2.8 { sql2 { PRAGMA integrity_check } } {ok} + + do_test backcompat-2.1 { + sql1 { + CREATE TABLE t2(a UNIQUE, b PRIMARY KEY, c UNIQUE); + INSERT INTO t2 VALUES(1,9,5); + INSERT INTO t2 VALUES(5,5,1); + INSERT INTO t2 VALUES(9,1,9); + SELECT * FROM t2 ORDER BY a; + } + } {1 9 5 5 5 1 9 1 9} + do_test backcompat-2.2 { + sql2 { + SELECT * FROM sqlite_master WHERE rootpage=-1; + SELECT * FROM t2 ORDER BY a; + } + } {1 9 5 5 5 1 9 1 9} + do_test backcompat-2.3 { + sql1 { + SELECT * FROM t2 ORDER BY b; + } + } {9 1 9 5 5 1 1 9 5} + do_test backcompat-2.4 { + sql2 { + SELECT * FROM t2 ORDER BY b; + } + } {9 1 9 5 5 1 1 9 5} + do_test backcompat-2.5 { + sql1 { + SELECT * FROM t2 ORDER BY c; + } + } {5 5 1 1 9 5 9 1 9} + do_test backcompat-2.6 { + sql2 { + SELECT * FROM t2 ORDER BY c; + } + } {5 5 1 1 9 5 9 1 9} } foreach k [lsort [array names ::incompatible]] { puts "ERROR: Detected journal incompatibility with version $k" } unset ::incompatible Index: test/capi2.test ================================================================== --- test/capi2.test +++ test/capi2.test @@ -235,11 +235,11 @@ do_test capi2-3.13b {db changes} {0} do_test capi2-3.14 { list [sqlite3_finalize $VM] [sqlite3_errmsg $DB] \ [sqlite3_extended_errcode $DB] -} {SQLITE_CONSTRAINT {column a is not unique} SQLITE_CONSTRAINT_UNIQUE} +} {SQLITE_CONSTRAINT {UNIQUE constraint failed: t1.a} SQLITE_CONSTRAINT_UNIQUE} do_test capi2-3.15 { set VM [sqlite3_prepare $DB {CREATE TABLE t2(a NOT NULL, b)} -1 TAIL] set TAIL } {} do_test capi2-3.16 { @@ -259,11 +259,11 @@ [get_column_names $VM] } {SQLITE_ERROR 0 {} {}} do_test capi2-3.19 { list [sqlite3_finalize $VM] [sqlite3_errmsg $DB] \ [sqlite3_extended_errcode $DB] -} {SQLITE_CONSTRAINT {t2.a may not be NULL} SQLITE_CONSTRAINT_NOTNULL} +} {SQLITE_CONSTRAINT {NOT NULL constraint failed: t2.a} SQLITE_CONSTRAINT_NOTNULL} do_test capi2-3.20 { execsql { CREATE TABLE a1(message_id, name , UNIQUE(message_id, name) ); INSERT INTO a1 VALUES(1, 1); @@ -553,11 +553,11 @@ do_test capi2-6.27 { catchsql { INSERT INTO t1 VALUES(2,4,5); SELECT * FROM t1; } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: t1.a}} do_test capi2-6.28 { list [sqlite3_step $VM1] \ [sqlite3_column_count $VM1] \ [get_row_values $VM1] \ [get_column_names $VM1] Index: test/check.test ================================================================== --- test/check.test +++ test/check.test @@ -39,21 +39,21 @@ } {3 4.0} do_test check-1.3 { catchsql { INSERT INTO t1 VALUES(6,7); } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t1}} do_test check-1.4 { execsql { SELECT * FROM t1; } } {3 4.0} do_test check-1.5 { catchsql { INSERT INTO t1 VALUES(4,3); } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t1}} do_test check-1.6 { execsql { SELECT * FROM t1; } } {3 4.0} @@ -86,21 +86,21 @@ } {2 4.0} do_test check-1.12 { catchsql { UPDATE t1 SET x=7 WHERE x==2 } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t1}} do_test check-1.13 { execsql { SELECT * FROM t1; } } {2 4.0} do_test check-1.14 { catchsql { UPDATE t1 SET x=5 WHERE x==2 } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t1}} do_test check-1.15 { execsql { SELECT * FROM t1; } } {2 4.0} @@ -140,21 +140,21 @@ } {1 2.2 three {} {} {}} do_test check-2.4 { catchsql { INSERT INTO t2 VALUES(1.1, NULL, NULL); } -} {1 {constraint one failed}} +} {1 {CHECK constraint failed: one}} do_test check-2.5 { catchsql { INSERT INTO t2 VALUES(NULL, 5, NULL); } -} {1 {constraint two failed}} +} {1 {CHECK constraint failed: two}} do_test check-2.6 { catchsql { INSERT INTO t2 VALUES(NULL, NULL, 3.14159); } -} {1 {constraint three failed}} +} {1 {CHECK constraint failed: three}} # Undocumented behavior: The CONSTRAINT name clause can follow a constraint. # Such a clause is ignored. But the parser must accept it for backwards # compatibility. # @@ -170,11 +170,11 @@ } {} do_test check-2.11 { catchsql { INSERT INTO t2b VALUES('xyzzy','hi',5); } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t2b}} do_test check-2.12 { execsql { CREATE TABLE t2c( x INTEGER CONSTRAINT x_one CONSTRAINT x_two CHECK( typeof(coalesce(x,0))=='integer' ) @@ -186,11 +186,11 @@ } {} do_test check-2.13 { catchsql { INSERT INTO t2c VALUES('xyzzy',7,8); } -} {1 {constraint x_two failed}} +} {1 {CHECK constraint failed: x_two}} do_test check-2.cleanup { execsql { DROP TABLE IF EXISTS t2b; DROP TABLE IF EXISTS t2c; } @@ -254,11 +254,11 @@ } {1 2 3} do_test check-3.9 { catchsql { INSERT INTO t3 VALUES(111,222,333); } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t3}} do_test check-4.1 { execsql { CREATE TABLE t4(x, y, CHECK ( @@ -296,11 +296,11 @@ } {12 -22} do_test check-4.6 { catchsql { UPDATE t4 SET x=0, y=1; } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t4}} do_test check-4.7 { execsql { SELECT * FROM t4; } } {12 -22} @@ -314,11 +314,11 @@ do_test check-4.9 { catchsql { PRAGMA ignore_check_constraints=OFF; UPDATE t4 SET x=0, y=2; } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t4}} ifcapable vacuum { do_test check_4.10 { catchsql { VACUUM } @@ -365,11 +365,11 @@ } {4 11.0 2 20.0} do_test check-6.5 { catchsql { UPDATE OR FAIL t1 SET x=7-x, y=y+1; } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t1}} do_test check-6.6 { execsql { SELECT * FROM t1; } } {3 12.0 2 20.0} @@ -377,11 +377,11 @@ catchsql { BEGIN; INSERT INTO t1 VALUES(1,30.0); INSERT OR ROLLBACK INTO t1 VALUES(8,40.0); } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t1}} do_test check-6.8 { catchsql { COMMIT; } } {1 {cannot commit - no transaction is active}} @@ -396,11 +396,11 @@ } {3 12.0 2 20.0} do_test check-6.12 { catchsql { REPLACE INTO t1 VALUES(6,7); } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t1}} do_test check-6.13 { execsql {SELECT * FROM t1} } {3 12.0 2 20.0} do_test check-6.14 { catchsql { @@ -424,11 +424,12 @@ proc myfunc {x} {expr $x < 10} db func myfunc myfunc do_execsql_test 7.1 { CREATE TABLE t6(a CHECK (myfunc(a))) } do_execsql_test 7.2 { INSERT INTO t6 VALUES(9) } -do_catchsql_test 7.3 { INSERT INTO t6 VALUES(11) } {1 {constraint failed}} +do_catchsql_test 7.3 { INSERT INTO t6 VALUES(11) } \ + {1 {CHECK constraint failed: t6}} do_test 7.4 { sqlite3 db2 test.db execsql { SELECT * FROM t6 } db2 } {9} @@ -447,15 +448,15 @@ } {} do_test 7.8 { db2 func myfunc myfunc catchsql { INSERT INTO t6 VALUES(12) } db2 -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t6}} # 2013-08-02: Silently ignore database name qualifiers in CHECK constraints. # do_execsql_test 8.1 { CREATE TABLE t810(a, CHECK( main.t810.a>0 )); CREATE TABLE t811(b, CHECK( xyzzy.t811.b BETWEEN 5 AND 10 )); } {} finish_test Index: test/collate4.test ================================================================== --- test/collate4.test +++ test/collate4.test @@ -471,27 +471,27 @@ do_test collate4-3.1 { catchsql { INSERT INTO collate4t1 VALUES('abc'); INSERT INTO collate4t1 VALUES('ABC'); } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: collate4t1.a}} do_test collate4-3.2 { execsql { SELECT * FROM collate4t1; } } {abc} do_test collate4-3.3 { catchsql { INSERT INTO collate4t1 SELECT upper(a) FROM collate4t1; } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: collate4t1.a}} do_test collate4-3.4 { catchsql { INSERT INTO collate4t1 VALUES(1); UPDATE collate4t1 SET a = 'abc'; } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: collate4t1.a}} do_test collate4-3.5 { execsql { DROP TABLE collate4t1; CREATE TABLE collate4t1(a COLLATE NOCASE UNIQUE); } @@ -499,27 +499,27 @@ do_test collate4-3.6 { catchsql { INSERT INTO collate4t1 VALUES('abc'); INSERT INTO collate4t1 VALUES('ABC'); } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: collate4t1.a}} do_test collate4-3.7 { execsql { SELECT * FROM collate4t1; } } {abc} do_test collate4-3.8 { catchsql { INSERT INTO collate4t1 SELECT upper(a) FROM collate4t1; } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: collate4t1.a}} do_test collate4-3.9 { catchsql { INSERT INTO collate4t1 VALUES(1); UPDATE collate4t1 SET a = 'abc'; } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: collate4t1.a}} do_test collate4-3.10 { execsql { DROP TABLE collate4t1; CREATE TABLE collate4t1(a); CREATE UNIQUE INDEX collate4i1 ON collate4t1(a COLLATE NOCASE); @@ -528,27 +528,27 @@ do_test collate4-3.11 { catchsql { INSERT INTO collate4t1 VALUES('abc'); INSERT INTO collate4t1 VALUES('ABC'); } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: collate4t1.a}} do_test collate4-3.12 { execsql { SELECT * FROM collate4t1; } } {abc} do_test collate4-3.13 { catchsql { INSERT INTO collate4t1 SELECT upper(a) FROM collate4t1; } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: collate4t1.a}} do_test collate4-3.14 { catchsql { INSERT INTO collate4t1 VALUES(1); UPDATE collate4t1 SET a = 'abc'; } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: collate4t1.a}} do_test collate4-3.15 { execsql { DROP TABLE collate4t1; } Index: test/conflict.test ================================================================== --- test/conflict.test +++ test/conflict.test @@ -240,11 +240,11 @@ 13 {} {INSERT OR REPLACE} 0 5 1 14 {} {INSERT OR FAIL} 1 {} 1 15 {} {INSERT OR ABORT} 1 {} 1 16 {} {INSERT OR ROLLBACK} 1 {} {} } { - if {$t0} {set t1 {t1.c may not be NULL}} + if {$t0} {set t1 {NOT NULL constraint failed: t1.c}} do_test conflict-5.$i { if {$conf1!=""} {set conf1 "ON CONFLICT $conf1"} set r0 [catch {execsql [subst { DROP TABLE t1; CREATE TABLE t1(a,b,c NOT NULL $conf1 DEFAULT 5); @@ -304,11 +304,11 @@ 13 {} {UPDATE OR REPLACE} 0 {7 6 9} 1 0 0 14 {} {UPDATE OR FAIL} 1 {6 7 3 4} 1 0 0 15 {} {UPDATE OR ABORT} 1 {1 2 3 4} 1 0 1 16 {} {UPDATE OR ROLLBACK} 1 {1 2 3 4} 0 0 0 } { - if {$t0} {set t1 {column a is not unique}} + if {$t0} {set t1 {UNIQUE constraint failed: t1.a}} if {[info exists TEMP_STORE] && $TEMP_STORE==3} { set t3 0 } else { set t3 [expr {$t3+$t4}] } @@ -491,25 +491,25 @@ do_test conflict-9.5 { catchsql { INSERT INTO t2 VALUES(3,1,3,3,3); SELECT * FROM t2; } -} {1 {column b is not unique}} +} {1 {UNIQUE constraint failed: t2.b}} do_test conflict-9.6 { catchsql { UPDATE t2 SET b=b+1 WHERE b=1; SELECT * FROM t2; } -} {1 {column b is not unique}} +} {1 {UNIQUE constraint failed: t2.b}} do_test conflict-9.7 { catchsql { BEGIN; UPDATE t3 SET x=x+1; INSERT INTO t2 VALUES(3,1,3,3,3); SELECT * FROM t2; } -} {1 {column b is not unique}} +} {1 {UNIQUE constraint failed: t2.b}} do_test conflict-9.8 { execsql {COMMIT} execsql {SELECT * FROM t3} } {2} do_test conflict-9.9 { @@ -517,35 +517,35 @@ BEGIN; UPDATE t3 SET x=x+1; UPDATE t2 SET b=b+1 WHERE b=1; SELECT * FROM t2; } -} {1 {column b is not unique}} +} {1 {UNIQUE constraint failed: t2.b}} do_test conflict-9.10 { execsql {COMMIT} execsql {SELECT * FROM t3} } {3} do_test conflict-9.11 { catchsql { INSERT INTO t2 VALUES(3,3,3,1,3); SELECT * FROM t2; } -} {1 {column d is not unique}} +} {1 {UNIQUE constraint failed: t2.d}} do_test conflict-9.12 { catchsql { UPDATE t2 SET d=d+1 WHERE d=1; SELECT * FROM t2; } -} {1 {column d is not unique}} +} {1 {UNIQUE constraint failed: t2.d}} do_test conflict-9.13 { catchsql { BEGIN; UPDATE t3 SET x=x+1; INSERT INTO t2 VALUES(3,3,3,1,3); SELECT * FROM t2; } -} {1 {column d is not unique}} +} {1 {UNIQUE constraint failed: t2.d}} do_test conflict-9.14 { execsql {COMMIT} execsql {SELECT * FROM t3} } {4} do_test conflict-9.15 { @@ -553,35 +553,35 @@ BEGIN; UPDATE t3 SET x=x+1; UPDATE t2 SET d=d+1 WHERE d=1; SELECT * FROM t2; } -} {1 {column d is not unique}} +} {1 {UNIQUE constraint failed: t2.d}} do_test conflict-9.16 { execsql {COMMIT} execsql {SELECT * FROM t3} } {5} do_test conflict-9.17 { catchsql { INSERT INTO t2 VALUES(3,3,3,3,1); SELECT * FROM t2; } -} {1 {column e is not unique}} +} {1 {UNIQUE constraint failed: t2.e}} do_test conflict-9.18 { catchsql { UPDATE t2 SET e=e+1 WHERE e=1; SELECT * FROM t2; } -} {1 {column e is not unique}} +} {1 {UNIQUE constraint failed: t2.e}} do_test conflict-9.19 { catchsql { BEGIN; UPDATE t3 SET x=x+1; INSERT INTO t2 VALUES(3,3,3,3,1); SELECT * FROM t2; } -} {1 {column e is not unique}} +} {1 {UNIQUE constraint failed: t2.e}} verify_ex_errcode conflict-9.21b SQLITE_CONSTRAINT_UNIQUE do_test conflict-9.20 { catch {execsql {COMMIT}} execsql {SELECT * FROM t3} } {5} @@ -590,11 +590,11 @@ BEGIN; UPDATE t3 SET x=x+1; UPDATE t2 SET e=e+1 WHERE e=1; SELECT * FROM t2; } -} {1 {column e is not unique}} +} {1 {UNIQUE constraint failed: t2.e}} verify_ex_errcode conflict-9.21b SQLITE_CONSTRAINT_UNIQUE do_test conflict-9.22 { catch {execsql {COMMIT}} execsql {SELECT * FROM t3} } {5} @@ -780,18 +780,26 @@ } {1 one 2 two} do_test conflict-12.3 { catchsql { UPDATE t5 SET a=a+1 WHERE a=1; } -} {1 {PRIMARY KEY must be unique}} +} {1 {UNIQUE constraint failed: t5.a}} verify_ex_errcode conflict-12.3b SQLITE_CONSTRAINT_PRIMARYKEY do_test conflict-12.4 { execsql { UPDATE OR REPLACE t5 SET a=a+1 WHERE a=1; SELECT * FROM t5; } } {2 one} +do_test conflict-12.5 { + catchsql { + CREATE TABLE t5b(x); + INSERT INTO t5b(rowid, x) VALUES(1,10),(2,11); + UPDATE t5b SET rowid=rowid+1 WHERE x=10; + } +} {1 {UNIQUE constraint failed: t5b.rowid}} +verify_ex_errcode conflict-12.5b SQLITE_CONSTRAINT_ROWID # Ticket [c38baa3d969eab7946dc50ba9d9b4f0057a19437] # REPLACE works like ABORT on a CHECK constraint. # @@ -802,11 +810,11 @@ REPLACE INTO t13 VALUES(1); } catchsql { REPLACE INTO t13 VALUES(2); } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t13}} verify_ex_errcode conflict-13.1b SQLITE_CONSTRAINT_CHECK do_test conflict-13.2 { execsql { REPLACE INTO t13 VALUES(3); COMMIT; ADDED test/conflict2.test Index: test/conflict2.test ================================================================== --- /dev/null +++ test/conflict2.test @@ -0,0 +1,817 @@ +# 2013-11-04 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# +# This file implements tests for the conflict resolution extension +# in WITHOUT ROWID tables +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +ifcapable !conflict { + finish_test + return +} + +# Create tables for the first group of tests. +# +do_test conflict2-1.0 { + execsql { + CREATE TABLE t1(a, b, c, PRIMARY KEY(a,b)) WITHOUT rowid; + CREATE TABLE t2(x); + SELECT c FROM t1 ORDER BY c; + } +} {} + +# Six columns of configuration data as follows: +# +# i The reference number of the test +# cmd An INSERT or REPLACE command to execute against table t1 +# t0 True if there is an error from $cmd +# t1 Content of "c" column of t1 assuming no error in $cmd +# t2 Content of "x" column of t2 +# t3 Number of temporary files created by this test +# +foreach {i cmd t0 t1 t2 t3} { + 1 INSERT 1 {} 1 0 + 2 {INSERT OR IGNORE} 0 3 1 0 + 3 {INSERT OR REPLACE} 0 4 1 0 + 4 REPLACE 0 4 1 0 + 5 {INSERT OR FAIL} 1 {} 1 0 + 6 {INSERT OR ABORT} 1 {} 1 0 + 7 {INSERT OR ROLLBACK} 1 {} {} 0 +} { + do_test conflict2-1.$i { + set ::sqlite_opentemp_count 0 + set r0 [catch {execsql [subst { + DELETE FROM t1; + DELETE FROM t2; + INSERT INTO t1 VALUES(1,2,3); + BEGIN; + INSERT INTO t2 VALUES(1); + $cmd INTO t1 VALUES(1,2,4); + }]} r1] + catch {execsql {COMMIT}} + if {$r0} {set r1 {}} {set r1 [execsql {SELECT c FROM t1}]} + set r2 [execsql {SELECT x FROM t2}] + set r3 $::sqlite_opentemp_count + list $r0 $r1 $r2 $r3 + } [list $t0 $t1 $t2 $t3] +} + +# Create tables for the first group of tests. +# +do_test conflict2-2.0 { + execsql { + DROP TABLE t1; + DROP TABLE t2; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, UNIQUE(a,b)) WITHOUT rowid; + CREATE TABLE t2(x); + SELECT c FROM t1 ORDER BY c; + } +} {} + +# Six columns of configuration data as follows: +# +# i The reference number of the test +# cmd An INSERT or REPLACE command to execute against table t1 +# t0 True if there is an error from $cmd +# t1 Content of "c" column of t1 assuming no error in $cmd +# t2 Content of "x" column of t2 +# +foreach {i cmd t0 t1 t2} { + 1 INSERT 1 {} 1 + 2 {INSERT OR IGNORE} 0 3 1 + 3 {INSERT OR REPLACE} 0 4 1 + 4 REPLACE 0 4 1 + 5 {INSERT OR FAIL} 1 {} 1 + 6 {INSERT OR ABORT} 1 {} 1 + 7 {INSERT OR ROLLBACK} 1 {} {} +} { + do_test conflict2-2.$i { + set r0 [catch {execsql [subst { + DELETE FROM t1; + DELETE FROM t2; + INSERT INTO t1 VALUES(1,2,3); + BEGIN; + INSERT INTO t2 VALUES(1); + $cmd INTO t1 VALUES(1,2,4); + }]} r1] + catch {execsql {COMMIT}} + if {$r0} {set r1 {}} {set r1 [execsql {SELECT c FROM t1}]} + set r2 [execsql {SELECT x FROM t2}] + list $r0 $r1 $r2 + } [list $t0 $t1 $t2] +} + +# Create tables for the first group of tests. +# +do_test conflict2-3.0 { + execsql { + DROP TABLE t1; + DROP TABLE t2; + CREATE TABLE t1(a, b, c INTEGER, PRIMARY KEY(c), UNIQUE(a,b)) WITHOUT rowid; + CREATE TABLE t2(x); + SELECT c FROM t1 ORDER BY c; + } +} {} + +# Six columns of configuration data as follows: +# +# i The reference number of the test +# cmd An INSERT or REPLACE command to execute against table t1 +# t0 True if there is an error from $cmd +# t1 Content of "c" column of t1 assuming no error in $cmd +# t2 Content of "x" column of t2 +# +foreach {i cmd t0 t1 t2} { + 1 INSERT 1 {} 1 + 2 {INSERT OR IGNORE} 0 3 1 + 3 {INSERT OR REPLACE} 0 4 1 + 4 REPLACE 0 4 1 + 5 {INSERT OR FAIL} 1 {} 1 + 6 {INSERT OR ABORT} 1 {} 1 + 7 {INSERT OR ROLLBACK} 1 {} {} +} { + do_test conflict2-3.$i { + set r0 [catch {execsql [subst { + DELETE FROM t1; + DELETE FROM t2; + INSERT INTO t1 VALUES(1,2,3); + BEGIN; + INSERT INTO t2 VALUES(1); + $cmd INTO t1 VALUES(1,2,4); + }]} r1] + catch {execsql {COMMIT}} + if {$r0} {set r1 {}} {set r1 [execsql {SELECT c FROM t1}]} + set r2 [execsql {SELECT x FROM t2}] + list $r0 $r1 $r2 + } [list $t0 $t1 $t2] +} + +do_test conflict2-4.0 { + execsql { + DROP TABLE t2; + CREATE TABLE t2(x); + SELECT x FROM t2; + } +} {} + +# Six columns of configuration data as follows: +# +# i The reference number of the test +# conf1 The conflict resolution algorithm on the UNIQUE constraint +# cmd An INSERT or REPLACE command to execute against table t1 +# t0 True if there is an error from $cmd +# t1 Content of "c" column of t1 assuming no error in $cmd +# t2 Content of "x" column of t2 +# +foreach {i conf1 cmd t0 t1 t2} { + 1 {} INSERT 1 {} 1 + 2 REPLACE INSERT 0 4 1 + 3 IGNORE INSERT 0 3 1 + 4 FAIL INSERT 1 {} 1 + 5 ABORT INSERT 1 {} 1 + 6 ROLLBACK INSERT 1 {} {} + 7 REPLACE {INSERT OR IGNORE} 0 3 1 + 8 IGNORE {INSERT OR REPLACE} 0 4 1 + 9 FAIL {INSERT OR IGNORE} 0 3 1 + 10 ABORT {INSERT OR REPLACE} 0 4 1 + 11 ROLLBACK {INSERT OR IGNORE } 0 3 1 +} { + do_test conflict2-4.$i { + if {$conf1!=""} {set conf1 "ON CONFLICT $conf1"} + set r0 [catch {execsql [subst { + DROP TABLE t1; + CREATE TABLE t1(a,b,c,PRIMARY KEY(a,b) $conf1) WITHOUT rowid; + DELETE FROM t2; + INSERT INTO t1 VALUES(1,2,3); + BEGIN; + INSERT INTO t2 VALUES(1); + $cmd INTO t1 VALUES(1,2,4); + }]} r1] + catch {execsql {COMMIT}} + if {$r0} {set r1 {}} {set r1 [execsql {SELECT c FROM t1}]} + set r2 [execsql {SELECT x FROM t2}] + list $r0 $r1 $r2 + } [list $t0 $t1 $t2] +} + +do_test conflict2-5.0 { + execsql { + DROP TABLE t2; + CREATE TABLE t2(x); + SELECT x FROM t2; + } +} {} + +# Six columns of configuration data as follows: +# +# i The reference number of the test +# conf1 The conflict resolution algorithm on the NOT NULL constraint +# cmd An INSERT or REPLACE command to execute against table t1 +# t0 True if there is an error from $cmd +# t1 Content of "c" column of t1 assuming no error in $cmd +# t2 Content of "x" column of t2 +# +foreach {i conf1 cmd t0 t1 t2} { + 1 {} INSERT 1 {} 1 + 2 REPLACE INSERT 0 5 1 + 3 IGNORE INSERT 0 {} 1 + 4 FAIL INSERT 1 {} 1 + 5 ABORT INSERT 1 {} 1 + 6 ROLLBACK INSERT 1 {} {} + 7 REPLACE {INSERT OR IGNORE} 0 {} 1 + 8 IGNORE {INSERT OR REPLACE} 0 5 1 + 9 FAIL {INSERT OR IGNORE} 0 {} 1 + 10 ABORT {INSERT OR REPLACE} 0 5 1 + 11 ROLLBACK {INSERT OR IGNORE} 0 {} 1 + 12 {} {INSERT OR IGNORE} 0 {} 1 + 13 {} {INSERT OR REPLACE} 0 5 1 + 14 {} {INSERT OR FAIL} 1 {} 1 + 15 {} {INSERT OR ABORT} 1 {} 1 + 16 {} {INSERT OR ROLLBACK} 1 {} {} +} { + if {$t0} {set t1 {NOT NULL constraint failed: t1.c}} + do_test conflict2-5.$i { + if {$conf1!=""} {set conf1 "ON CONFLICT $conf1"} + set r0 [catch {execsql [subst { + DROP TABLE t1; + CREATE TABLE t1(a,b,c NOT NULL $conf1 DEFAULT 5); + DELETE FROM t2; + BEGIN; + INSERT INTO t2 VALUES(1); + $cmd INTO t1 VALUES(1,2,NULL); + }]} r1] + catch {execsql {COMMIT}} + if {!$r0} {set r1 [execsql {SELECT c FROM t1}]} + set r2 [execsql {SELECT x FROM t2}] + list $r0 $r1 $r2 + } [list $t0 $t1 $t2] +} + +do_test conflict2-6.0 { + execsql { + DROP TABLE t2; + CREATE TABLE t2(a,b,c); + INSERT INTO t2 VALUES(1,2,1); + INSERT INTO t2 VALUES(2,3,2); + INSERT INTO t2 VALUES(3,4,1); + INSERT INTO t2 VALUES(4,5,4); + SELECT c FROM t2 ORDER BY b; + CREATE TABLE t3(x); + INSERT INTO t3 VALUES(1); + } +} {1 2 1 4} + +# Six columns of configuration data as follows: +# +# i The reference number of the test +# conf1 The conflict resolution algorithm on the UNIQUE constraint +# cmd An UPDATE command to execute against table t1 +# t0 True if there is an error from $cmd +# t1 Content of "b" column of t1 assuming no error in $cmd +# t2 Content of "x" column of t3 +# t3 Number of temporary files for tables +# t4 Number of temporary files for statement journals +# +# Update: Since temporary table files are now opened lazily, and none +# of the following tests use large quantities of data, t3 is always 0. +# +foreach {i conf1 cmd t0 t1 t2 t3 t4} { + 1 {} UPDATE 1 {6 7 8 9} 1 0 1 + 2 REPLACE UPDATE 0 {7 6 9} 1 0 0 + 3 IGNORE UPDATE 0 {6 7 3 9} 1 0 0 + 4 FAIL UPDATE 1 {6 7 3 4} 1 0 0 + 5 ABORT UPDATE 1 {1 2 3 4} 1 0 1 + 6 ROLLBACK UPDATE 1 {1 2 3 4} 0 0 0 + 7 REPLACE {UPDATE OR IGNORE} 0 {6 7 3 9} 1 0 0 + 8 IGNORE {UPDATE OR REPLACE} 0 {7 6 9} 1 0 1 + 9 FAIL {UPDATE OR IGNORE} 0 {6 7 3 9} 1 0 0 + 10 ABORT {UPDATE OR REPLACE} 0 {7 6 9} 1 0 1 + 11 ROLLBACK {UPDATE OR IGNORE} 0 {6 7 3 9} 1 0 0 + 12 {} {UPDATE OR IGNORE} 0 {6 7 3 9} 1 0 0 + 13 {} {UPDATE OR REPLACE} 0 {7 6 9} 1 0 1 + 14 {} {UPDATE OR FAIL} 1 {6 7 3 4} 1 0 0 + 15 {} {UPDATE OR ABORT} 1 {1 2 3 4} 1 0 1 + 16 {} {UPDATE OR ROLLBACK} 1 {1 2 3 4} 0 0 0 +} { + if {$t0} {set t1 {UNIQUE constraint failed: t1.a}} + if {[info exists TEMP_STORE] && $TEMP_STORE==3} { + set t3 0 + } else { + set t3 [expr {$t3+$t4}] + } + do_test conflict2-6.$i { + db close + sqlite3 db test.db + if {$conf1!=""} {set conf1 "ON CONFLICT $conf1"} + execsql {pragma temp_store=file} + set ::sqlite_opentemp_count 0 + set r0 [catch {execsql [subst { + DROP TABLE t1; + CREATE TABLE t1(a,b,c, PRIMARY KEY(a) $conf1) WITHOUT rowid; + INSERT INTO t1 SELECT * FROM t2; + UPDATE t3 SET x=0; + BEGIN; + $cmd t3 SET x=1; + $cmd t1 SET b=b*2; + $cmd t1 SET a=c+5; + }]} r1] + catch {execsql {COMMIT}} + if {!$r0} {set r1 [execsql {SELECT a FROM t1 ORDER BY b}]} + set r2 [execsql {SELECT x FROM t3}] + list $r0 $r1 $r2 $::sqlite_opentemp_count + } [list $t0 $t1 $t2 $t3] +} + +# Test to make sure a lot of IGNOREs don't cause a stack overflow +# +do_test conflict2-7.1 { + execsql { + DROP TABLE t1; + DROP TABLE t2; + DROP TABLE t3; + CREATE TABLE t1(a PRIMARY KEY, b) without rowid; + } + for {set i 1} {$i<=50} {incr i} { + execsql "INSERT into t1 values($i,[expr {$i+1}]);" + } + execsql { + SELECT count(*), min(a), max(b) FROM t1; + } +} {50 1 51} +do_test conflict2-7.2 { + execsql { + PRAGMA count_changes=on; + UPDATE OR IGNORE t1 SET a=1000; + } +} {1} +do_test conflict2-7.2.1 { + db changes +} {1} +do_test conflict2-7.3 { + execsql { + SELECT b FROM t1 WHERE a=1000; + } +} {2} +do_test conflict2-7.4 { + execsql { + SELECT count(*) FROM t1; + } +} {50} +do_test conflict2-7.5 { + execsql { + PRAGMA count_changes=on; + UPDATE OR REPLACE t1 SET a=1001; + } +} {50} +do_test conflict2-7.5.1 { + db changes +} {50} +do_test conflict2-7.7 { + execsql { + SELECT count(*) FROM t1; + } +} {1} + +# Update for version 3: A SELECT statement no longer resets the change +# counter (Test result changes from 0 to 50). +do_test conflict2-7.7.1 { + db changes +} {50} + +# Make sure the row count is right for rows that are ignored on +# an insert. +# +do_test conflict2-8.1 { + execsql { + DELETE FROM t1; + INSERT INTO t1 VALUES(1,2); + } + execsql { + INSERT OR IGNORE INTO t1 VALUES(2,3); + } +} {1} +do_test conflict2-8.1.1 { + db changes +} {1} +do_test conflict2-8.2 { + execsql { + INSERT OR IGNORE INTO t1 VALUES(2,4); + } +} {0} +do_test conflict2-8.2.1 { + db changes +} {0} +do_test conflict2-8.3 { + execsql { + INSERT OR REPLACE INTO t1 VALUES(2,4); + } +} {1} +do_test conflict2-8.3.1 { + db changes +} {1} +do_test conflict2-8.4 { + execsql { + INSERT OR IGNORE INTO t1 SELECT * FROM t1; + } +} {0} +do_test conflict2-8.4.1 { + db changes +} {0} +do_test conflict2-8.5 { + execsql { + INSERT OR IGNORE INTO t1 SELECT a+2,b+2 FROM t1; + } +} {2} +do_test conflict2-8.5.1 { + db changes +} {2} +do_test conflict2-8.6 { + execsql { + INSERT OR IGNORE INTO t1 SELECT a+3,b+3 FROM t1; + } +} {3} +do_test conflict2-8.6.1 { + db changes +} {3} + +integrity_check conflict2-8.99 + +do_test conflict2-9.1 { + execsql { + PRAGMA count_changes=0; + CREATE TABLE t2( + a INTEGER PRIMARY KEY ON CONFLICT IGNORE, + b INTEGER UNIQUE ON CONFLICT FAIL, + c INTEGER UNIQUE ON CONFLICT REPLACE, + d INTEGER UNIQUE ON CONFLICT ABORT, + e INTEGER UNIQUE ON CONFLICT ROLLBACK + ) WITHOUT rowid; + CREATE TABLE t3(x); + INSERT INTO t3 VALUES(1); + SELECT * FROM t3; + } +} {1} +do_test conflict2-9.2 { + catchsql { + INSERT INTO t2 VALUES(1,1,1,1,1); + INSERT INTO t2 VALUES(2,2,2,2,2); + SELECT * FROM t2; + } +} {0 {1 1 1 1 1 2 2 2 2 2}} +do_test conflict2-9.3 { + catchsql { + INSERT INTO t2 VALUES(1,3,3,3,3); + SELECT * FROM t2; + } +} {0 {1 1 1 1 1 2 2 2 2 2}} +do_test conflict2-9.4 { + catchsql { + UPDATE t2 SET a=a+1 WHERE a=1; + SELECT * FROM t2; + } +} {0 {1 1 1 1 1 2 2 2 2 2}} +do_test conflict2-9.5 { + catchsql { + INSERT INTO t2 VALUES(3,1,3,3,3); + } +} {1 {UNIQUE constraint failed: t2.b}} +do_test conflict2-9.5b { + db eval {SELECT * FROM t2;} +} {1 1 1 1 1 2 2 2 2 2} +do_test conflict2-9.6 { + catchsql { + UPDATE t2 SET b=b+1 WHERE b=1; + SELECT * FROM t2; + } +} {1 {UNIQUE constraint failed: t2.b}} +do_test conflict2-9.6b { + db eval {SELECT * FROM t2;} +} {1 1 1 1 1 2 2 2 2 2} +do_test conflict2-9.7 { + catchsql { + BEGIN; + UPDATE t3 SET x=x+1; + INSERT INTO t2 VALUES(3,1,3,3,3); + SELECT * FROM t2; + } +} {1 {UNIQUE constraint failed: t2.b}} +do_test conflict2-9.8 { + execsql {COMMIT} + execsql {SELECT * FROM t3} +} {2} +do_test conflict2-9.9 { + catchsql { + BEGIN; + UPDATE t3 SET x=x+1; + UPDATE t2 SET b=b+1 WHERE b=1; + SELECT * FROM t2; + } +} {1 {UNIQUE constraint failed: t2.b}} +do_test conflict2-9.10 { + execsql {COMMIT} + execsql {SELECT * FROM t3} +} {3} +do_test conflict2-9.11 { + catchsql { + INSERT INTO t2 VALUES(3,3,3,1,3); + SELECT * FROM t2; + } +} {1 {UNIQUE constraint failed: t2.d}} +do_test conflict2-9.12 { + catchsql { + UPDATE t2 SET d=d+1 WHERE d=1; + SELECT * FROM t2; + } +} {1 {UNIQUE constraint failed: t2.d}} +do_test conflict2-9.13 { + catchsql { + BEGIN; + UPDATE t3 SET x=x+1; + INSERT INTO t2 VALUES(3,3,3,1,3); + SELECT * FROM t2; + } +} {1 {UNIQUE constraint failed: t2.d}} +do_test conflict2-9.14 { + execsql {COMMIT} + execsql {SELECT * FROM t3} +} {4} +do_test conflict2-9.15 { + catchsql { + BEGIN; + UPDATE t3 SET x=x+1; + UPDATE t2 SET d=d+1 WHERE d=1; + SELECT * FROM t2; + } +} {1 {UNIQUE constraint failed: t2.d}} +do_test conflict2-9.16 { + execsql {COMMIT} + execsql {SELECT * FROM t3} +} {5} +do_test conflict2-9.17 { + catchsql { + INSERT INTO t2 VALUES(3,3,3,3,1); + SELECT * FROM t2; + } +} {1 {UNIQUE constraint failed: t2.e}} +do_test conflict2-9.18 { + catchsql { + UPDATE t2 SET e=e+1 WHERE e=1; + SELECT * FROM t2; + } +} {1 {UNIQUE constraint failed: t2.e}} +do_test conflict2-9.19 { + catchsql { + BEGIN; + UPDATE t3 SET x=x+1; + INSERT INTO t2 VALUES(3,3,3,3,1); + SELECT * FROM t2; + } +} {1 {UNIQUE constraint failed: t2.e}} +verify_ex_errcode conflict2-9.21b SQLITE_CONSTRAINT_UNIQUE +do_test conflict2-9.20 { + catch {execsql {COMMIT}} + execsql {SELECT * FROM t3} +} {5} +do_test conflict2-9.21 { + catchsql { + BEGIN; + UPDATE t3 SET x=x+1; + UPDATE t2 SET e=e+1 WHERE e=1; + SELECT * FROM t2; + } +} {1 {UNIQUE constraint failed: t2.e}} +verify_ex_errcode conflict2-9.21b SQLITE_CONSTRAINT_UNIQUE +do_test conflict2-9.22 { + catch {execsql {COMMIT}} + execsql {SELECT * FROM t3} +} {5} +do_test conflict2-9.23 { + catchsql { + INSERT INTO t2 VALUES(3,3,1,3,3); + SELECT * FROM t2; + } +} {0 {2 2 2 2 2 3 3 1 3 3}} +do_test conflict2-9.24 { + catchsql { + UPDATE t2 SET c=c-1 WHERE c=2; + SELECT * FROM t2; + } +} {0 {2 2 1 2 2}} +do_test conflict2-9.25 { + catchsql { + BEGIN; + UPDATE t3 SET x=x+1; + INSERT INTO t2 VALUES(3,3,1,3,3); + SELECT * FROM t2; + } +} {0 {3 3 1 3 3}} +do_test conflict2-9.26 { + catch {execsql {COMMIT}} + execsql {SELECT * FROM t3} +} {6} + +do_test conflict2-10.1 { + catchsql { + DELETE FROM t1; + BEGIN; + INSERT OR ROLLBACK INTO t1 VALUES(1,2); + INSERT OR ROLLBACK INTO t1 VALUES(1,3); + COMMIT; + } + execsql {SELECT * FROM t1} +} {} +do_test conflict2-10.2 { + catchsql { + CREATE TABLE t4(x); + CREATE UNIQUE INDEX t4x ON t4(x); + BEGIN; + INSERT OR ROLLBACK INTO t4 VALUES(1); + INSERT OR ROLLBACK INTO t4 VALUES(1); + COMMIT; + } + execsql {SELECT * FROM t4} +} {} + +# Ticket #1171. Make sure statement rollbacks do not +# damage the database. +# +do_test conflict2-11.1 { + execsql { + -- Create a database object (pages 2, 3 of the file) + BEGIN; + CREATE TABLE abc(a PRIMARY KEY, b, c) WITHOUT rowid; + INSERT INTO abc VALUES(1, 2, 3); + INSERT INTO abc VALUES(4, 5, 6); + INSERT INTO abc VALUES(7, 8, 9); + COMMIT; + } + + + # Set a small cache size so that changes will spill into + # the database file. + execsql { + PRAGMA cache_size = 10; + } + + # Make lots of changes. Because of the small cache, some + # (most?) of these changes will spill into the disk file. + # In other words, some of the changes will not be held in + # cache. + # + execsql { + BEGIN; + -- Make sure the pager is in EXCLUSIVE state. + CREATE TABLE def(d, e, f); + INSERT INTO def VALUES + ('xxxxxxxxxxxxxxx', 'yyyyyyyyyyyyyyyy', 'zzzzzzzzzzzzzzzz'); + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + DELETE FROM abc WHERE a = 4; + } + + # Execute a statement that does a statement rollback due to + # a constraint failure. + # + catchsql { + INSERT INTO abc SELECT 10, 20, 30 FROM def; + } + + # Rollback the database. Verify that the state of the ABC table + # is unchanged from the beginning of the transaction. In other words, + # make sure the DELETE on table ABC that occurred within the transaction + # had no effect. + # + execsql { + ROLLBACK; + SELECT * FROM abc; + } +} {1 2 3 4 5 6 7 8 9} +integrity_check conflict2-11.2 + +# Repeat test conflict2-11.1 but this time commit. +# +do_test conflict2-11.3 { + execsql { + BEGIN; + -- Make sure the pager is in EXCLUSIVE state. + UPDATE abc SET a=a+1; + CREATE TABLE def(d, e, f); + INSERT INTO def VALUES + ('xxxxxxxxxxxxxxx', 'yyyyyyyyyyyyyyyy', 'zzzzzzzzzzzzzzzz'); + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + DELETE FROM abc WHERE a = 4; + } + catchsql { + INSERT INTO abc SELECT 10, 20, 30 FROM def; + } + execsql { + ROLLBACK; + SELECT * FROM abc; + } +} {1 2 3 4 5 6 7 8 9} +# Repeat test conflict2-11.1 but this time commit. +# +do_test conflict2-11.5 { + execsql { + BEGIN; + -- Make sure the pager is in EXCLUSIVE state. + CREATE TABLE def(d, e, f); + INSERT INTO def VALUES + ('xxxxxxxxxxxxxxx', 'yyyyyyyyyyyyyyyy', 'zzzzzzzzzzzzzzzz'); + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + INSERT INTO def SELECT * FROM def; + DELETE FROM abc WHERE a = 4; + } + catchsql { + INSERT INTO abc SELECT 10, 20, 30 FROM def; + } + execsql { + COMMIT; + SELECT * FROM abc; + } +} {1 2 3 7 8 9} +integrity_check conflict2-11.6 + +# Make sure UPDATE OR REPLACE works on tables that have only +# an INTEGER PRIMARY KEY. +# +do_test conflict2-12.1 { + execsql { + CREATE TABLE t5(a INTEGER PRIMARY KEY, b text) WITHOUT rowid; + INSERT INTO t5 VALUES(1,'one'); + INSERT INTO t5 VALUES(2,'two'); + SELECT * FROM t5 + } +} {1 one 2 two} +do_test conflict2-12.2 { + execsql { + UPDATE OR IGNORE t5 SET a=a+1 WHERE a=1; + SELECT * FROM t5; + } +} {1 one 2 two} +do_test conflict2-12.3 { + catchsql { + UPDATE t5 SET a=a+1 WHERE a=1; + } +} {1 {UNIQUE constraint failed: t5.a}} +verify_ex_errcode conflict2-12.3b SQLITE_CONSTRAINT_PRIMARYKEY +do_test conflict2-12.4 { + execsql { + UPDATE OR REPLACE t5 SET a=a+1 WHERE a=1; + SELECT * FROM t5; + } +} {2 one} + + +# Ticket [c38baa3d969eab7946dc50ba9d9b4f0057a19437] +# REPLACE works like ABORT on a CHECK constraint. +# +do_test conflict2-13.1 { + execsql { + CREATE TABLE t13(a PRIMARY KEY CHECK(a!=2)) WITHOUT rowid; + BEGIN; + REPLACE INTO t13 VALUES(1); + } + catchsql { + REPLACE INTO t13 VALUES(2); + } +} {1 {CHECK constraint failed: t13}} +verify_ex_errcode conflict2-13.1b SQLITE_CONSTRAINT_CHECK +do_test conflict2-13.2 { + execsql { + REPLACE INTO t13 VALUES(3); + COMMIT; + SELECT * FROM t13; + } +} {1 3} + + +finish_test ADDED test/conflict3.test Index: test/conflict3.test ================================================================== --- /dev/null +++ test/conflict3.test @@ -0,0 +1,356 @@ +# 2013-11-05 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# +# This file implements tests for the conflict resolution extension +# to SQLite. +# +# This file focuses on making sure that combinations of REPLACE, +# IGNORE, and FAIL conflict resolution play well together. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +ifcapable !conflict { + finish_test + return +} + +do_execsql_test conflict-1.1 { + CREATE TABLE t1( + a INTEGER PRIMARY KEY ON CONFLICT REPLACE, + b UNIQUE ON CONFLICT IGNORE, + c UNIQUE ON CONFLICT FAIL + ); + INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert a row that conflicts on column B. The insert should be ignored. +# +do_execsql_test conflict-1.2 { + INSERT INTO t1(a,b,c) VALUES(3,2,5); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert two rows where the second conflicts on C. The first row show go +# and and then there should be a constraint error. +# +do_test conflict-1.3 { + catchsql {INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);} +} {1 {UNIQUE constraint failed: t1.c}} +do_execsql_test conflict-1.4 { + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4 4 5 6} + +# Replete the tests above, but this time on a table non-INTEGER primary key. +# +do_execsql_test conflict-2.1 { + DROP TABLE t1; + CREATE TABLE t1( + a INT PRIMARY KEY ON CONFLICT REPLACE, + b UNIQUE ON CONFLICT IGNORE, + c UNIQUE ON CONFLICT FAIL + ); + INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert a row that conflicts on column B. The insert should be ignored. +# +do_execsql_test conflict-2.2 { + INSERT INTO t1(a,b,c) VALUES(3,2,5); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert two rows where the second conflicts on C. The first row show go +# and and then there should be a constraint error. +# +do_test conflict-2.3 { + catchsql {INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);} +} {1 {UNIQUE constraint failed: t1.c}} +do_execsql_test conflict-2.4 { + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4 4 5 6} + +# Replete again on a WITHOUT ROWID table. +# +do_execsql_test conflict-3.1 { + DROP TABLE t1; + CREATE TABLE t1( + a INT PRIMARY KEY ON CONFLICT REPLACE, + b UNIQUE ON CONFLICT IGNORE, + c UNIQUE ON CONFLICT FAIL + ) WITHOUT ROWID; + INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert a row that conflicts on column B. The insert should be ignored. +# +do_execsql_test conflict-3.2 { + INSERT INTO t1(a,b,c) VALUES(3,2,5); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert two rows where the second conflicts on C. The first row show go +# and and then there should be a constraint error. +# +do_test conflict-3.3 { + catchsql {INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);} +} {1 {UNIQUE constraint failed: t1.c}} +do_execsql_test conflict-3.4 { + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4 4 5 6} + +# Arrange the table rows in a different order and repeat. +# +do_execsql_test conflict-4.1 { + DROP TABLE t1; + CREATE TABLE t1( + b UNIQUE ON CONFLICT IGNORE, + c UNIQUE ON CONFLICT FAIL, + a INT PRIMARY KEY ON CONFLICT REPLACE + ) WITHOUT ROWID; + INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert a row that conflicts on column B. The insert should be ignored. +# +do_execsql_test conflict-4.2 { + INSERT INTO t1(a,b,c) VALUES(3,2,5); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert two rows where the second conflicts on C. The first row show go +# and and then there should be a constraint error. +# +do_test conflict-4.3 { + catchsql {INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);} +} {1 {UNIQUE constraint failed: t1.c}} +do_execsql_test conflict-4.4 { + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4 4 5 6} + +# Arrange the table rows in a different order and repeat. +# +do_execsql_test conflict-5.1 { + DROP TABLE t1; + CREATE TABLE t1( + b UNIQUE ON CONFLICT IGNORE, + a INT PRIMARY KEY ON CONFLICT REPLACE, + c UNIQUE ON CONFLICT FAIL + ); + INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert a row that conflicts on column B. The insert should be ignored. +# +do_execsql_test conflict-5.2 { + INSERT INTO t1(a,b,c) VALUES(3,2,5); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert two rows where the second conflicts on C. The first row show go +# and and then there should be a constraint error. +# +do_test conflict-5.3 { + catchsql {INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);} +} {1 {UNIQUE constraint failed: t1.c}} +do_execsql_test conflict-5.4 { + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4 4 5 6} + +# Arrange the table rows in a different order and repeat. +# +do_execsql_test conflict-6.1 { + DROP TABLE t1; + CREATE TABLE t1( + c UNIQUE ON CONFLICT FAIL, + a INT PRIMARY KEY ON CONFLICT REPLACE, + b UNIQUE ON CONFLICT IGNORE + ); + INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert a row that conflicts on column B. The insert should be ignored. +# +do_execsql_test conflict-6.2 { + INSERT INTO t1(a,b,c) VALUES(3,2,5); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert two rows where the second conflicts on C. The first row show go +# and and then there should be a constraint error. +# +do_test conflict-6.3 { + catchsql {INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);} +} {1 {UNIQUE constraint failed: t1.c}} +do_execsql_test conflict-6.4 { + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4 4 5 6} + +# Change which column is the PRIMARY KEY +# +do_execsql_test conflict-7.1 { + DROP TABLE t1; + CREATE TABLE t1( + a UNIQUE ON CONFLICT REPLACE, + b INTEGER PRIMARY KEY ON CONFLICT IGNORE, + c UNIQUE ON CONFLICT FAIL + ); + INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert a row that conflicts on column B. The insert should be ignored. +# +do_execsql_test conflict-7.2 { + INSERT INTO t1(a,b,c) VALUES(3,2,5); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert two rows where the second conflicts on C. The first row show go +# and and then there should be a constraint error. +# +do_test conflict-7.3 { + catchsql {INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);} +} {1 {UNIQUE constraint failed: t1.c}} +do_execsql_test conflict-7.4 { + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4 4 5 6} + +# Change which column is the PRIMARY KEY +# +do_execsql_test conflict-8.1 { + DROP TABLE t1; + CREATE TABLE t1( + a UNIQUE ON CONFLICT REPLACE, + b INT PRIMARY KEY ON CONFLICT IGNORE, + c UNIQUE ON CONFLICT FAIL + ); + INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert a row that conflicts on column B. The insert should be ignored. +# +do_execsql_test conflict-8.2 { + INSERT INTO t1(a,b,c) VALUES(3,2,5); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert two rows where the second conflicts on C. The first row show go +# and and then there should be a constraint error. +# +do_test conflict-8.3 { + catchsql {INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);} +} {1 {UNIQUE constraint failed: t1.c}} +do_execsql_test conflict-8.4 { + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4 4 5 6} + +# Change which column is the PRIMARY KEY +# +do_execsql_test conflict-9.1 { + DROP TABLE t1; + CREATE TABLE t1( + a UNIQUE ON CONFLICT REPLACE, + b INT PRIMARY KEY ON CONFLICT IGNORE, + c UNIQUE ON CONFLICT FAIL + ) WITHOUT ROWID; + INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert a row that conflicts on column B. The insert should be ignored. +# +do_execsql_test conflict-9.2 { + INSERT INTO t1(a,b,c) VALUES(3,2,5); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert two rows where the second conflicts on C. The first row show go +# and and then there should be a constraint error. +# +do_test conflict-9.3 { + catchsql {INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);} +} {1 {UNIQUE constraint failed: t1.c}} +do_execsql_test conflict-9.4 { + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4 4 5 6} + +# Change which column is the PRIMARY KEY +# +do_execsql_test conflict-10.1 { + DROP TABLE t1; + CREATE TABLE t1( + a UNIQUE ON CONFLICT REPLACE, + b UNIQUE ON CONFLICT IGNORE, + c INTEGER PRIMARY KEY ON CONFLICT FAIL + ); + INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert a row that conflicts on column B. The insert should be ignored. +# +do_execsql_test conflict-10.2 { + INSERT INTO t1(a,b,c) VALUES(3,2,5); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert two rows where the second conflicts on C. The first row show go +# and and then there should be a constraint error. +# +do_test conflict-10.3 { + catchsql {INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);} +} {1 {UNIQUE constraint failed: t1.c}} +do_execsql_test conflict-10.4 { + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4 4 5 6} + +# Change which column is the PRIMARY KEY +# +do_execsql_test conflict-11.1 { + DROP TABLE t1; + CREATE TABLE t1( + a UNIQUE ON CONFLICT REPLACE, + b UNIQUE ON CONFLICT IGNORE, + c PRIMARY KEY ON CONFLICT FAIL + ) WITHOUT ROWID; + INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert a row that conflicts on column B. The insert should be ignored. +# +do_execsql_test conflict-11.2 { + INSERT INTO t1(a,b,c) VALUES(3,2,5); + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4} + +# Insert two rows where the second conflicts on C. The first row show go +# and and then there should be a constraint error. +# +do_test conflict-11.3 { + catchsql {INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);} +} {1 {UNIQUE constraint failed: t1.c}} +do_execsql_test conflict-11.4 { + SELECT a,b,c FROM t1 ORDER BY a; +} {1 2 3 2 3 4 4 5 6} + + +finish_test Index: test/e_createtable.test ================================================================== --- test/e_createtable.test +++ test/e_createtable.test @@ -1180,17 +1180,18 @@ INSERT INTO t2 VALUES(45.5, 'one'); INSERT INTO t2 VALUES('brambles', 'two'); INSERT INTO t2 VALUES(X'ABCDEF', 'three'); } {} -do_createtable_tests 4.3.1 -error { %s not unique } { +do_createtable_tests 4.3.1 -error {UNIQUE constraint failed: t1.x} { 1 "INSERT INTO t1 VALUES(0, 0)" {"column x is"} 2 "INSERT INTO t1 VALUES(45.5, 'abc')" {"column x is"} 3 "INSERT INTO t1 VALUES(0.0, 'abc')" {"column x is"} 4 "INSERT INTO t1 VALUES('brambles', 'abc')" {"column x is"} 5 "INSERT INTO t1 VALUES(X'ABCDEF', 'abc')" {"column x is"} - +} +do_createtable_tests 4.3.1 -error {UNIQUE constraint failed: t2.x, t2.y} { 6 "INSERT INTO t2 VALUES(0, 'zero')" {"columns x, y are"} 7 "INSERT INTO t2 VALUES(45.5, 'one')" {"columns x, y are"} 8 "INSERT INTO t2 VALUES(0.0, 'zero')" {"columns x, y are"} 9 "INSERT INTO t2 VALUES('brambles', 'two')" {"columns x, y are"} 10 "INSERT INTO t2 VALUES(X'ABCDEF', 'three')" {"columns x, y are"} @@ -1206,17 +1207,18 @@ 7 "INSERT INTO t2 VALUES(45.5, 'abc')" {} 8 "INSERT INTO t2 VALUES(0.0, 'abc')" {} 9 "INSERT INTO t2 VALUES('brambles', 'abc')" {} 10 "INSERT INTO t2 VALUES(X'ABCDEF', 'abc')" {} } -do_createtable_tests 4.3.3 -error { %s not unique } { +do_createtable_tests 4.3.3 -error {UNIQUE constraint failed: t1.x} { 1 "UPDATE t1 SET x=0 WHERE y='two'" {"column x is"} 2 "UPDATE t1 SET x='brambles' WHERE y='three'" {"column x is"} 3 "UPDATE t1 SET x=45.5 WHERE y='zero'" {"column x is"} 4 "UPDATE t1 SET x=X'ABCDEF' WHERE y='one'" {"column x is"} 5 "UPDATE t1 SET x=0.0 WHERE y='three'" {"column x is"} - +} +do_createtable_tests 4.3.3 -error {UNIQUE constraint failed: t2.x, t2.y} { 6 "UPDATE t2 SET x=0, y='zero' WHERE y='two'" {"columns x, y are"} 7 "UPDATE t2 SET x='brambles', y='two' WHERE y='three'" {"columns x, y are"} 8 "UPDATE t2 SET x=45.5, y='one' WHERE y='zero'" {"columns x, y are"} 9 "UPDATE t2 SET x=X'ABCDEF', y='three' WHERE y='one'" @@ -1303,28 +1305,28 @@ INSERT INTO t4 VALUES('xyx', 1, 1); INSERT INTO t4 VALUES('xyx', 2, 1); INSERT INTO t4 VALUES('uvw', 1, 1); } -do_createtable_tests 4.7.1 -error { %s not unique } { - 1 "INSERT INTO t1 VALUES(1, 'one')" {{column a is}} - 2 "INSERT INTO t1 VALUES(4.3, 'two')" {{column a is}} - 3 "INSERT INTO t1 VALUES('reveal', 'three')" {{column a is}} - 4 "INSERT INTO t1 VALUES(X'123456', 'four')" {{column a is}} - - 5 "UPDATE t1 SET a = 1 WHERE rowid=2" {{column a is}} - 6 "UPDATE t1 SET a = 4.3 WHERE rowid=3" {{column a is}} - 7 "UPDATE t1 SET a = 'reveal' WHERE rowid=4" {{column a is}} - 8 "UPDATE t1 SET a = X'123456' WHERE rowid=1" {{column a is}} - - 9 "INSERT INTO t4 VALUES('xyx', 1, 1)" {{columns a, b, c are}} - 10 "INSERT INTO t4 VALUES('xyx', 2, 1)" {{columns a, b, c are}} - 11 "INSERT INTO t4 VALUES('uvw', 1, 1)" {{columns a, b, c are}} - - 12 "UPDATE t4 SET a='xyx' WHERE rowid=3" {{columns a, b, c are}} - 13 "UPDATE t4 SET b=1 WHERE rowid=2" {{columns a, b, c are}} - 14 "UPDATE t4 SET a=0, b=0, c=0" {{columns a, b, c are}} +do_createtable_tests 4.7.1 -error {UNIQUE constraint failed: %s} { + 1 "INSERT INTO t1 VALUES(1, 'one')" {{t1.a}} + 2 "INSERT INTO t1 VALUES(4.3, 'two')" {{t1.a}} + 3 "INSERT INTO t1 VALUES('reveal', 'three')" {{t1.a}} + 4 "INSERT INTO t1 VALUES(X'123456', 'four')" {{t1.a}} + + 5 "UPDATE t1 SET a = 1 WHERE rowid=2" {{t1.a}} + 6 "UPDATE t1 SET a = 4.3 WHERE rowid=3" {{t1.a}} + 7 "UPDATE t1 SET a = 'reveal' WHERE rowid=4" {{t1.a}} + 8 "UPDATE t1 SET a = X'123456' WHERE rowid=1" {{t1.a}} + + 9 "INSERT INTO t4 VALUES('xyx', 1, 1)" {{t4.a, t4.b, t4.c}} + 10 "INSERT INTO t4 VALUES('xyx', 2, 1)" {{t4.a, t4.b, t4.c}} + 11 "INSERT INTO t4 VALUES('uvw', 1, 1)" {{t4.a, t4.b, t4.c}} + + 12 "UPDATE t4 SET a='xyx' WHERE rowid=3" {{t4.a, t4.b, t4.c}} + 13 "UPDATE t4 SET b=1 WHERE rowid=2" {{t4.a, t4.b, t4.c}} + 14 "UPDATE t4 SET a=0, b=0, c=0" {{t4.a, t4.b, t4.c}} } # EVIDENCE-OF: R-21289-11559 As with PRIMARY KEY constraints, for the # purposes of UNIQUE constraints NULL values are considered distinct # from all other values (including other NULLs). @@ -1402,25 +1404,25 @@ INSERT INTO x2 VALUES(1, 'xx'); INSERT INTO x2 VALUES(1, 'yy'); INSERT INTO t2 SELECT * FROM x2; } -do_createtable_tests 4.11 -error {constraint failed} { - 1a "INSERT INTO x1 VALUES('one', 0)" {} - 1b "INSERT INTO t1 VALUES('one', -4.0)" {} - - 2a "INSERT INTO x2 VALUES('abc', 1)" {} - 2b "INSERT INTO t2 VALUES('abc', 1)" {} - - 3a "INSERT INTO x2 VALUES(0, 'abc')" {} - 3b "INSERT INTO t2 VALUES(0, 'abc')" {} - - 4a "UPDATE t1 SET b=-1 WHERE rowid=1" {} - 4b "UPDATE x1 SET b=-1 WHERE rowid=1" {} - - 4a "UPDATE x2 SET a='' WHERE rowid=1" {} - 4b "UPDATE t2 SET a='' WHERE rowid=1" {} +do_createtable_tests 4.11 -error {CHECK constraint failed: %s} { + 1a "INSERT INTO x1 VALUES('one', 0)" {x1} + 1b "INSERT INTO t1 VALUES('one', -4.0)" {t1} + + 2a "INSERT INTO x2 VALUES('abc', 1)" {x2} + 2b "INSERT INTO t2 VALUES('abc', 1)" {t2} + + 3a "INSERT INTO x2 VALUES(0, 'abc')" {x2} + 3b "INSERT INTO t2 VALUES(0, 'abc')" {t2} + + 4a "UPDATE t1 SET b=-1 WHERE rowid=1" {t1} + 4b "UPDATE x1 SET b=-1 WHERE rowid=1" {x1} + + 4a "UPDATE x2 SET a='' WHERE rowid=1" {x2} + 4b "UPDATE t2 SET a='' WHERE rowid=1" {t2} } # EVIDENCE-OF: R-34109-39108 If the CHECK expression evaluates to NULL, # or any other non-zero value, it is not a constraint violation. # @@ -1467,13 +1469,11 @@ INSERT INTO t2 VALUES('z', NULL); INSERT INTO t3 VALUES('x', 'y', 'z'); INSERT INTO t3 VALUES(1, 2, 3); } -do_createtable_tests 4.14 -error { - %s may not be NULL -} { +do_createtable_tests 4.14 -error {NOT NULL constraint failed: %s} { 1 "INSERT INTO t1 VALUES(NULL, 'a')" {t1.a} 2 "INSERT INTO t2 VALUES(NULL, 'b')" {t2.a} 3 "INSERT INTO t3 VALUES('c', 'd', NULL)" {t3.c} 4 "INSERT INTO t3 VALUES('e', NULL, 'f')" {t3.b} 5 "INSERT INTO t3 VALUES(NULL, 'g', 'h')" {t3.a} @@ -1535,16 +1535,16 @@ INSERT INTO t3_re SELECT * FROM t3_ab; INSERT INTO t3_xx SELECT * FROM t3_ab; } foreach {tn tbl res ac data} { - 1 t1_ab {1 {column a is not unique}} 0 {1 one 2 two 3 three} - 2 t1_ro {1 {column a is not unique}} 1 {1 one 2 two} - 3 t1_fa {1 {column a is not unique}} 0 {1 one 2 two 3 three 4 string} + 1 t1_ab {1 {UNIQUE constraint failed: t1_ab.a}} 0 {1 one 2 two 3 three} + 2 t1_ro {1 {UNIQUE constraint failed: t1_ro.a}} 1 {1 one 2 two} + 3 t1_fa {1 {UNIQUE constraint failed: t1_fa.a}} 0 {1 one 2 two 3 three 4 string} 4 t1_ig {0 {}} 0 {1 one 2 two 3 three 4 string 6 string} 5 t1_re {0 {}} 0 {1 one 2 two 4 string 3 string 6 string} - 6 t1_xx {1 {column a is not unique}} 0 {1 one 2 two 3 three} + 6 t1_xx {1 {UNIQUE constraint failed: t1_xx.a}} 0 {1 one 2 two 3 three} } { catchsql COMMIT do_execsql_test 4.15.$tn.1 "BEGIN; INSERT INTO $tbl VALUES(3, 'three')" do_catchsql_test 4.15.$tn.2 " @@ -1553,16 +1553,16 @@ do_test e_createtable-4.15.$tn.3 { sqlite3_get_autocommit db } $ac do_execsql_test 4.15.$tn.4 "SELECT * FROM $tbl" $data } foreach {tn tbl res ac data} { - 1 t2_ab {1 {t2_ab.b may not be NULL}} 0 {1 one 2 two 3 three} - 2 t2_ro {1 {t2_ro.b may not be NULL}} 1 {1 one 2 two} - 3 t2_fa {1 {t2_fa.b may not be NULL}} 0 {1 one 2 two 3 three 4 xx} + 1 t2_ab {1 {NOT NULL constraint failed: t2_ab.b}} 0 {1 one 2 two 3 three} + 2 t2_ro {1 {NOT NULL constraint failed: t2_ro.b}} 1 {1 one 2 two} + 3 t2_fa {1 {NOT NULL constraint failed: t2_fa.b}} 0 {1 one 2 two 3 three 4 xx} 4 t2_ig {0 {}} 0 {1 one 2 two 3 three 4 xx 6 xx} - 5 t2_re {1 {t2_re.b may not be NULL}} 0 {1 one 2 two 3 three} - 6 t2_xx {1 {t2_xx.b may not be NULL}} 0 {1 one 2 two 3 three} + 5 t2_re {1 {NOT NULL constraint failed: t2_re.b}} 0 {1 one 2 two 3 three} + 6 t2_xx {1 {NOT NULL constraint failed: t2_xx.b}} 0 {1 one 2 two 3 three} } { catchsql COMMIT do_execsql_test 4.16.$tn.1 "BEGIN; INSERT INTO $tbl VALUES(3, 'three')" do_catchsql_test 4.16.$tn.2 " @@ -1571,16 +1571,20 @@ do_test e_createtable-4.16.$tn.3 { sqlite3_get_autocommit db } $ac do_execsql_test 4.16.$tn.4 "SELECT * FROM $tbl" $data } foreach {tn tbl res ac data} { - 1 t3_ab {1 {columns a, b are not unique}} 0 {1 one 2 two 3 three} - 2 t3_ro {1 {columns a, b are not unique}} 1 {1 one 2 two} - 3 t3_fa {1 {columns a, b are not unique}} 0 {1 one 2 two 3 three 4 three} + 1 t3_ab {1 {UNIQUE constraint failed: t3_ab.a, t3_ab.b}} + 0 {1 one 2 two 3 three} + 2 t3_ro {1 {UNIQUE constraint failed: t3_ro.a, t3_ro.b}} + 1 {1 one 2 two} + 3 t3_fa {1 {UNIQUE constraint failed: t3_fa.a, t3_fa.b}} + 0 {1 one 2 two 3 three 4 three} 4 t3_ig {0 {}} 0 {1 one 2 two 3 three 4 three 6 three} 5 t3_re {0 {}} 0 {1 one 2 two 4 three 3 three 6 three} - 6 t3_xx {1 {columns a, b are not unique}} 0 {1 one 2 two 3 three} + 6 t3_xx {1 {UNIQUE constraint failed: t3_xx.a, t3_xx.b}} + 0 {1 one 2 two 3 three} } { catchsql COMMIT do_execsql_test 4.17.$tn.1 "BEGIN; INSERT INTO $tbl VALUES(3, 'three')" do_catchsql_test 4.17.$tn.2 " @@ -1607,11 +1611,11 @@ INSERT INTO t4 VALUES(3, 4); } do_execsql_test 4.18.2 { BEGIN; INSERT INTO t4 VALUES(5, 6) } do_catchsql_test 4.18.3 { INSERT INTO t4 SELECT a+4, b+4 FROM t4 -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t4}} do_test e_createtable-4.18.4 { sqlite3_get_autocommit db } 0 do_execsql_test 4.18.5 { SELECT * FROM t4 } {1 2 3 4 5 6} # EVIDENCE-OF: R-19114-56113 Different constraints within the same table # may have different default conflict resolution algorithms. @@ -1620,11 +1624,11 @@ CREATE TABLE t5(a NOT NULL ON CONFLICT IGNORE, b NOT NULL ON CONFLICT ABORT); } do_catchsql_test 4.19.1 { INSERT INTO t5 VALUES(NULL, 'not null') } {0 {}} do_execsql_test 4.19.2 { SELECT * FROM t5 } {} do_catchsql_test 4.19.3 { INSERT INTO t5 VALUES('not null', NULL) } \ - {1 {t5.b may not be NULL}} + {1 {NOT NULL constraint failed: t5.b}} do_execsql_test 4.19.4 { SELECT * FROM t5 } {} #------------------------------------------------------------------------ # Tests for INTEGER PRIMARY KEY and rowid related statements. # @@ -1745,20 +1749,20 @@ SELECT typeof(pk), pk FROM t9; } {integer 2 integer 2 integer 2 integer 2} do_catchsql_test 5.4.4.1 { INSERT INTO t6 VALUES(2) -} {1 {column pk is not unique}} +} {1 {UNIQUE constraint failed: t6.pk}} do_catchsql_test 5.4.4.2 { INSERT INTO t7 VALUES(2) -} {1 {column pk is not unique}} +} {1 {UNIQUE constraint failed: t7.pk}} do_catchsql_test 5.4.4.3 { INSERT INTO t8 VALUES(2) -} {1 {column pk is not unique}} +} {1 {UNIQUE constraint failed: t8.pk}} do_catchsql_test 5.4.4.4 { INSERT INTO t9 VALUES(2) -} {1 {column pk is not unique}} +} {1 {UNIQUE constraint failed: t9.pk}} # EVIDENCE-OF: R-56094-57830 the following three table declarations all # cause the column "x" to be an alias for the rowid (an integer primary # key): CREATE TABLE t(x INTEGER PRIMARY KEY ASC, y, z); CREATE TABLE # t(x INTEGER, y, z, PRIMARY KEY(x ASC)); CREATE TABLE t(x INTEGER, y, Index: test/e_fkey.test ================================================================== --- test/e_fkey.test +++ test/e_fkey.test @@ -209,11 +209,11 @@ PRAGMA foreign_keys = OFF; } catchsql { DELETE FROM t1 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-6.2 { execsql { PRAGMA foreign_keys } } {1} do_test e_fkey-6.3 { execsql { @@ -263,15 +263,15 @@ # table that does not correspond to any row in the artist table will # fail, # do_test e_fkey-8.1 { catchsql { INSERT INTO track VALUES(1, 'track 1', 1) } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-8.2 { execsql { INSERT INTO artist VALUES(2, 'artist 1') } catchsql { INSERT INTO track VALUES(1, 'track 1', 1) } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-8.2 { execsql { INSERT INTO track VALUES(1, 'track 1', 2) } } {} #------------------------------------------------------------------------- @@ -281,11 +281,11 @@ # EVIDENCE-OF: R-24401-52400 as will attempting to delete a row from the # artist table when there exist dependent rows in the track table # do_test e_fkey-9.1 { catchsql { DELETE FROM artist WHERE artistid = 2 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-9.2 { execsql { DELETE FROM track WHERE trackartist = 2; DELETE FROM artist WHERE artistid = 2; } @@ -309,18 +309,18 @@ execsql { SELECT * FROM artist } } {} do_test e_fkey-10.3 { # Setting the trackid to a non-NULL value fails, of course. catchsql { UPDATE track SET trackartist = 5 WHERE trackid = 1 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-10.4 { execsql { INSERT INTO artist VALUES(5, 'artist 5'); UPDATE track SET trackartist = 5 WHERE trackid = 1; } catchsql { DELETE FROM artist WHERE artistid = 5} -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-10.5 { execsql { UPDATE track SET trackartist = NULL WHERE trackid = 1; DELETE FROM artist WHERE artistid = 5; } @@ -342,12 +342,12 @@ # as the second argument. proc test_r52486_21352 {tn sql} { set res [catchsql $sql] set results { {0 {}} - {1 {PRIMARY KEY must be unique}} - {1 {foreign key constraint failed}} + {1 {UNIQUE constraint failed: artist.artistid}} + {1 {FOREIGN KEY constraint failed}} } if {[lsearch $results $res]<0} { error $res } @@ -407,11 +407,11 @@ ); } } {} do_test e_fkey-12.2 { catchsql { INSERT INTO track VALUES(14, 'Mr. Bojangles', NULL) } -} {1 {track.trackartist may not be NULL}} +} {1 {NOT NULL constraint failed: track.trackartist}} #------------------------------------------------------------------------- # EVIDENCE-OF: R-16127-35442 # # Test an example from foreignkeys.html. @@ -436,19 +436,19 @@ INSERT INTO track VALUES(13, 'My Way', 2); } } {} do_test e_fkey-13.2 { catchsql { INSERT INTO track VALUES(14, 'Mr. Bojangles', 3) } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-13.3 { execsql { INSERT INTO track VALUES(14, 'Mr. Bojangles', NULL) } } {} do_test e_fkey-13.4 { catchsql { UPDATE track SET trackartist = 3 WHERE trackname = 'Mr. Bojangles'; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-13.5 { execsql { INSERT INTO artist VALUES(3, 'Sammy Davis Jr.'); UPDATE track SET trackartist = 3 WHERE trackname = 'Mr. Bojangles'; INSERT INTO track VALUES(15, 'Boogie Woogie', 3); @@ -462,11 +462,11 @@ # do_test e_fkey-14.1 { catchsql { DELETE FROM artist WHERE artistname = 'Frank Sinatra'; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-14.2 { execsql { DELETE FROM track WHERE trackname = 'My Way'; DELETE FROM artist WHERE artistname = 'Frank Sinatra'; } @@ -473,11 +473,11 @@ } {} do_test e_fkey-14.3 { catchsql { UPDATE artist SET artistid=4 WHERE artistname = 'Dean Martin'; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-14.4 { execsql { DELETE FROM track WHERE trackname IN('That''s Amore', 'Christmas Blues'); UPDATE artist SET artistid=4 WHERE artistname = 'Dean Martin'; } @@ -511,11 +511,11 @@ } {integer text blob} proc test_efkey_45 {tn isError sql} { do_test e_fkey-15.$tn.1 " catchsql {$sql} - " [lindex {{0 {}} {1 {foreign key constraint failed}}} $isError] + " [lindex {{0 {}} {1 {FOREIGN KEY constraint failed}}} $isError] do_test e_fkey-15.$tn.2 { execsql { SELECT * FROM chi WHERE c IS NOT NULL AND c NOT IN (SELECT p FROM par) } @@ -555,14 +555,14 @@ UPDATE t1 SET a = 'ONE'; } } {} do_test e_fkey-16.3 { catchsql { UPDATE t2 SET b = 'two' WHERE rowid = 1 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-16.4 { catchsql { DELETE FROM t1 WHERE rowid = 1 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} #------------------------------------------------------------------------- # Specifically, test that when comparing child and parent key values the # affinity of the parent key column is applied to the child key value # before the comparison takes place. @@ -590,11 +590,11 @@ do_test e_fkey-17.3 { execsql { SELECT typeof(a) FROM t1 } } {integer integer text} do_test e_fkey-17.4 { catchsql { DELETE FROM t1 WHERE rowid = 2 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} ########################################################################### ### SECTION 3: Required and Suggested Database Indexes ########################################################################### @@ -894,11 +894,11 @@ } } {} proc test_efkey_60 {tn isError sql} { do_test e_fkey-23.$tn " catchsql {$sql} - " [lindex {{0 {}} {1 {foreign key constraint failed}}} $isError] + " [lindex {{0 {}} {1 {FOREIGN KEY constraint failed}}} $isError] } test_efkey_60 2 1 "INSERT INTO c1 VALUES(239, 231)" test_efkey_60 3 0 "INSERT INTO p1 VALUES(239, 231)" test_efkey_60 4 0 "INSERT INTO c1 VALUES(239, 231)" @@ -931,11 +931,11 @@ } } {} proc test_efkey_61 {tn isError sql} { do_test e_fkey-24.$tn " catchsql {$sql} - " [lindex {{0 {}} {1 {foreign key constraint failed}}} $isError] + " [lindex {{0 {}} {1 {FOREIGN KEY constraint failed}}} $isError] } foreach {tn c} [list 2 c1 3 c2 4 c3] { test_efkey_61 $tn.1 1 "INSERT INTO $c VALUES(1, 2)" test_efkey_61 $tn.2 0 "INSERT INTO parent VALUES(1, 2)" test_efkey_61 $tn.3 0 "INSERT INTO $c VALUES(1, 2)" @@ -996,11 +996,11 @@ do_test e_fkey-25.5 { concat \ [execsql { SELECT rowid FROM track WHERE trackartist = 5 }] \ [catchsql { DELETE FROM artist WHERE artistid = 5 }] -} {1 1 {foreign key constraint failed}} +} {1 1 {FOREIGN KEY constraint failed}} do_test e_fkey-25.6 { concat \ [execsql { SELECT rowid FROM track WHERE trackartist = 7 }] \ [catchsql { DELETE FROM artist WHERE artistid = 7 }] @@ -1008,11 +1008,11 @@ do_test e_fkey-25.7 { concat \ [execsql { SELECT rowid FROM track WHERE trackartist = 6 }] \ [catchsql { DELETE FROM artist WHERE artistid = 6 }] -} {2 1 {foreign key constraint failed}} +} {2 1 {FOREIGN KEY constraint failed}} #------------------------------------------------------------------------- # EVIDENCE-OF: R-47936-10044 Or, more generally: # SELECT rowid FROM WHERE = :parent_key_value # @@ -1197,11 +1197,11 @@ } {} do_test e_fkey-29.3 { catchsql { INSERT INTO song VALUES(2, 'Elvis Presley', 'Elvis Is Back!', 'Fever'); } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} #------------------------------------------------------------------------- # EVIDENCE-OF: R-33626-48418 In SQLite, if any of the child key columns # (in this case songartist and songalbum) are NULL, then there is no @@ -1238,11 +1238,11 @@ } {} do_test e_fkey-31.2 { # Execute a statement that violates the immediate FK constraint. catchsql { INSERT INTO prince VALUES(1, 2) } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-31.3 { # This time, use a trigger to fix the constraint violation before the # statement has finished executing. Then execute the same statement as # in the previous test case. This time, no error. @@ -1263,11 +1263,11 @@ BEGIN; INSERT INTO prince VALUES(2, 3); DROP TRIGGER kt; } catchsql { INSERT INTO prince VALUES(3, 4) } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-31.5 { execsql { COMMIT; SELECT * FROM king; } @@ -1294,11 +1294,11 @@ # foreign key constraints remain in violation. # proc test_efkey_34 {tn isError sql} { do_test e_fkey-32.$tn " catchsql {$sql} - " [lindex {{0 {}} {1 {foreign key constraint failed}}} $isError] + " [lindex {{0 {}} {1 {FOREIGN KEY constraint failed}}} $isError] } drop_all_tables test_efkey_34 1 0 { CREATE TABLE ll(k PRIMARY KEY); @@ -1325,11 +1325,11 @@ # drop_all_tables proc test_efkey_35 {tn isError sql} { do_test e_fkey-33.$tn " catchsql {$sql} - " [lindex {{0 {}} {1 {foreign key constraint failed}}} $isError] + " [lindex {{0 {}} {1 {FOREIGN KEY constraint failed}}} $isError] } do_test e_fkey-33.1 { execsql { CREATE TABLE parent(x, y); CREATE UNIQUE INDEX pi ON parent(x, y); @@ -1415,11 +1415,11 @@ } } {} proc test_efkey_29 {tn sql isError} { do_test e_fkey-34.$tn "catchsql {$sql}" [ - lindex {{0 {}} {1 {foreign key constraint failed}}} $isError + lindex {{0 {}} {1 {FOREIGN KEY constraint failed}}} $isError ] } test_efkey_29 2 "BEGIN" 0 test_efkey_29 3 "DELETE FROM parent WHERE x = 'a'" 1 test_efkey_29 4 "DELETE FROM parent WHERE x = 'd'" 1 @@ -1489,11 +1489,11 @@ execsql { BEGIN; INSERT INTO track VALUES(1, 'White Christmas', 5); } catchsql COMMIT -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-35.3 { execsql { INSERT INTO artist VALUES(5, 'Bing Crosby'); COMMIT; } @@ -1526,11 +1526,11 @@ RELEASE one; } } {} do_test e_fkey-36.3 { catchsql COMMIT -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-36.4 { execsql { UPDATE t1 SET a = 5 WHERE a = 4; COMMIT; } @@ -1556,11 +1556,11 @@ RELEASE two; } } {} do_test e_fkey-37.2 { catchsql {RELEASE one} -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-37.3 { execsql { UPDATE t1 SET a = 7 WHERE a = 6; RELEASE one; } @@ -1573,11 +1573,11 @@ RELEASE two; } } {} do_test e_fkey-37.5 { catchsql {RELEASE one} -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-37.6 { execsql {ROLLBACK TO one ; RELEASE one} } {} #------------------------------------------------------------------------- @@ -1604,11 +1604,11 @@ SELECT * FROM t1; } } {1 1 2 2 3 3 4 4 5 6} do_test e_fkey-38.3 { catchsql COMMIT -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-38.4 { execsql { ROLLBACK TO one; COMMIT; SELECT * FROM t1; @@ -1625,15 +1625,15 @@ INSERT INTO t1 VALUES(7, 8); } } {} do_test e_fkey-38.6 { catchsql {RELEASE a} -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-38.7 { execsql {ROLLBACK TO c} catchsql {RELEASE a} -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-38.8 { execsql { ROLLBACK TO b; RELEASE a; SELECT * FROM t1; @@ -1780,11 +1780,11 @@ SELECT * FROM child; } } {j k l m} do_test e_fkey-41.3 { catchsql COMMIT -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-41.4 { execsql ROLLBACK } {} #------------------------------------------------------------------------- @@ -1818,14 +1818,14 @@ INSERT INTO child2 VALUES('d', 'c'); } } {} do_test e_fkey-41.3 { catchsql { DELETE FROM parent WHERE p1 = 'a' } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-41.4 { catchsql { UPDATE parent SET p2 = 'e' WHERE p1 = 'c' } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} #------------------------------------------------------------------------- # Test that RESTRICT is slightly different from NO ACTION for IMMEDIATE # constraints, in that it is enforced immediately, not at the end of the # statement. @@ -1855,11 +1855,11 @@ END; } } {} do_test e_fkey-42.2 { catchsql { UPDATE parent SET x = 'key one' WHERE x = 'key1' } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-42.3 { execsql { UPDATE parent SET x = 'key two' WHERE x = 'key2'; SELECT * FROM child2; } @@ -1883,11 +1883,11 @@ END; } } {} do_test e_fkey-42.5 { catchsql { DELETE FROM parent WHERE x = 'key1' } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-42.6 { execsql { DELETE FROM parent WHERE x = 'key2'; SELECT * FROM child2; } @@ -1906,11 +1906,11 @@ INSERT INTO child2 VALUES('key2'); } } {} do_test e_fkey-42.8 { catchsql { REPLACE INTO parent VALUES('key1') } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-42.9 { execsql { REPLACE INTO parent VALUES('key2'); SELECT * FROM child2; } @@ -1942,17 +1942,17 @@ BEGIN; } } {} do_test e_fkey-43.2 { catchsql { UPDATE parent SET x = 'key one' WHERE x = 'key1' } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-43.3 { execsql { UPDATE parent SET x = 'key two' WHERE x = 'key2' } } {} do_test e_fkey-43.4 { catchsql COMMIT -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-43.5 { execsql { UPDATE child2 SET c = 'key two'; COMMIT; } @@ -1976,17 +1976,17 @@ BEGIN; } } {} do_test e_fkey-43.7 { catchsql { DELETE FROM parent WHERE x = 'key1' } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-43.8 { execsql { DELETE FROM parent WHERE x = 'key2' } } {} do_test e_fkey-43.9 { catchsql COMMIT -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-43.10 { execsql { UPDATE child2 SET c = NULL; COMMIT; } @@ -2238,11 +2238,11 @@ SELECT * FROM parent; } } {ONE two three} do_test e_fkey-49.4 { catchsql { UPDATE parent SET a = '' WHERE a = 'oNe' } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} #------------------------------------------------------------------------- # EVIDENCE-OF: R-11856-19836 # @@ -2273,11 +2273,11 @@ INSERT INTO track VALUES(14, 'Mr. Bojangles', 3); } } {} do_test e_fkey-50.2 { catchsql { DELETE FROM artist WHERE artistname = 'Sammy Davis Jr.' } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-50.3 { execsql { INSERT INTO artist VALUES(0, 'Unknown Artist'); DELETE FROM artist WHERE artistname = 'Sammy Davis Jr.'; } @@ -2637,20 +2637,20 @@ DELETE FROM c2; DELETE FROM c3; } execsql { INSERT INTO c5 VALUES('a', 'b') } catchsql { DROP TABLE p } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-58.2 { execsql { SELECT * FROM p } } {a b} do_test e_fkey-58.3 { catchsql { BEGIN; DROP TABLE p; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-58.4 { execsql { SELECT * FROM p; SELECT * FROM c5; ROLLBACK; @@ -2680,15 +2680,15 @@ DROP TABLE p; } } {} do_test e_fkey-59.3 { catchsql COMMIT -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-59.4 { execsql { CREATE TABLE p(a, b, PRIMARY KEY(a, b)) } catchsql COMMIT -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-59.5 { execsql { INSERT INTO p VALUES('a', 'b') } execsql COMMIT } {} @@ -2847,11 +2847,11 @@ execsql { INSERT INTO c VALUES('z', NULL, NULL) } # Check that the FK is enforced properly if there are no NULL values # in the child key columns. catchsql { INSERT INTO c VALUES('a', 2, 4) } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} } #------------------------------------------------------------------------- # Test that SQLite does not support the SET CONSTRAINT statement. And # that it is possible to create both immediate and deferred constraints. @@ -2877,17 +2877,17 @@ BEGIN; } } {} do_test e_fkey-62.4 { catchsql { INSERT INTO ci VALUES('x', 'y') } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-62.5 { catchsql { INSERT INTO cd VALUES('x', 'y') } } {0 {}} do_test e_fkey-62.6 { catchsql { COMMIT } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test e_fkey-62.7 { execsql { DELETE FROM cd; COMMIT; } Index: test/e_insert.test ================================================================== --- test/e_insert.test +++ test/e_insert.test @@ -369,26 +369,26 @@ INSERT INTO a4 VALUES(1, 'a'); INSERT INTO a4 VALUES(2, 'a'); INSERT INTO a4 VALUES(3, 'a'); } {} foreach {tn sql error ac data } { - 1.1 "INSERT INTO a4 VALUES(2,'b')" {column c is not unique} 1 {1 a 2 a 3 a} + 1.1 "INSERT INTO a4 VALUES(2,'b')" {UNIQUE constraint failed: a4.c} 1 {1 a 2 a 3 a} 1.2 "INSERT OR REPLACE INTO a4 VALUES(2, 'b')" {} 1 {1 a 3 a 2 b} 1.3 "INSERT OR IGNORE INTO a4 VALUES(3, 'c')" {} 1 {1 a 3 a 2 b} 1.4 "BEGIN" {} 0 {1 a 3 a 2 b} - 1.5 "INSERT INTO a4 VALUES(1, 'd')" {column c is not unique} 0 {1 a 3 a 2 b} + 1.5 "INSERT INTO a4 VALUES(1, 'd')" {UNIQUE constraint failed: a4.c} 0 {1 a 3 a 2 b} 1.6 "INSERT OR ABORT INTO a4 VALUES(1, 'd')" - {column c is not unique} 0 {1 a 3 a 2 b} + {UNIQUE constraint failed: a4.c} 0 {1 a 3 a 2 b} 1.7 "INSERT OR ROLLBACK INTO a4 VALUES(1, 'd')" - {column c is not unique} 1 {1 a 3 a 2 b} + {UNIQUE constraint failed: a4.c} 1 {1 a 3 a 2 b} 1.8 "INSERT INTO a4 SELECT 4, 'e' UNION ALL SELECT 3, 'e'" - {column c is not unique} 1 {1 a 3 a 2 b} + {UNIQUE constraint failed: a4.c} 1 {1 a 3 a 2 b} 1.9 "INSERT OR FAIL INTO a4 SELECT 4, 'e' UNION ALL SELECT 3, 'e'" - {column c is not unique} 1 {1 a 3 a 2 b 4 e} + {UNIQUE constraint failed: a4.c} 1 {1 a 3 a 2 b 4 e} 2.1 "INSERT INTO a4 VALUES(2,'f')" - {column c is not unique} 1 {1 a 3 a 2 b 4 e} + {UNIQUE constraint failed: a4.c} 1 {1 a 3 a 2 b 4 e} 2.2 "REPLACE INTO a4 VALUES(2, 'f')" {} 1 {1 a 3 a 4 e 2 f} } { do_catchsql_test e_insert-4.1.$tn.1 $sql [list [expr {$error!=""}] $error] do_execsql_test e_insert-4.1.$tn.2 {SELECT * FROM a4} [list {*}$data] do_test e_insert-4.1.$tn.3 {sqlite3_get_autocommit db} $ac Index: test/e_reindex.test ================================================================== --- test/e_reindex.test +++ test/e_reindex.test @@ -65,14 +65,14 @@ db close sqlite3 db test.db do_execsql_test e_reindex-1.3 { PRAGMA integrity_check; } [list \ - {rowid 4 missing from index i2} \ - {rowid 4 missing from index i1} \ - {rowid 5 missing from index i2} \ - {rowid 5 missing from index i1} \ + {row 3 missing from index i2} \ + {row 3 missing from index i1} \ + {row 4 missing from index i2} \ + {row 4 missing from index i1} \ {wrong # of entries in index i2} \ {wrong # of entries in index i1} ] do_execsql_test e_reindex-1.4 { Index: test/e_update.test ================================================================== --- test/e_update.test +++ test/e_update.test @@ -276,43 +276,43 @@ INSERT INTO t3 VALUES(3, 'three'); INSERT INTO t3 VALUES(4, 'four'); } {} foreach {tn sql error ac data } { 1 "UPDATE t3 SET b='one' WHERE a=3" - {column b is not unique} 1 {1 one 2 two 3 three 4 four} + {UNIQUE constraint failed: t3.b} 1 {1 one 2 two 3 three 4 four} 2 "UPDATE OR REPLACE t3 SET b='one' WHERE a=3" {} 1 {2 two 3 one 4 four} 3 "UPDATE OR FAIL t3 SET b='three'" - {column b is not unique} 1 {2 three 3 one 4 four} + {UNIQUE constraint failed: t3.b} 1 {2 three 3 one 4 four} 4 "UPDATE OR IGNORE t3 SET b='three' WHERE a=3" {} 1 {2 three 3 one 4 four} 5 "UPDATE OR ABORT t3 SET b='three' WHERE a=3" - {column b is not unique} 1 {2 three 3 one 4 four} + {UNIQUE constraint failed: t3.b} 1 {2 three 3 one 4 four} 6 "BEGIN" {} 0 {2 three 3 one 4 four} 7 "UPDATE t3 SET b='three' WHERE a=3" - {column b is not unique} 0 {2 three 3 one 4 four} + {UNIQUE constraint failed: t3.b} 0 {2 three 3 one 4 four} 8 "UPDATE OR ABORT t3 SET b='three' WHERE a=3" - {column b is not unique} 0 {2 three 3 one 4 four} + {UNIQUE constraint failed: t3.b} 0 {2 three 3 one 4 four} 9 "UPDATE OR FAIL t3 SET b='two'" - {column b is not unique} 0 {2 two 3 one 4 four} + {UNIQUE constraint failed: t3.b} 0 {2 two 3 one 4 four} 10 "UPDATE OR IGNORE t3 SET b='four' WHERE a=3" {} 0 {2 two 3 one 4 four} 11 "UPDATE OR REPLACE t3 SET b='four' WHERE a=3" {} 0 {2 two 3 four} 12 "UPDATE OR ROLLBACK t3 SET b='four'" - {column b is not unique} 1 {2 three 3 one 4 four} + {UNIQUE constraint failed: t3.b} 1 {2 three 3 one 4 four} } { do_catchsql_test e_update-1.8.$tn.1 $sql [list [expr {$error!=""}] $error] do_execsql_test e_update-1.8.$tn.2 {SELECT * FROM t3} [list {*}$data] do_test e_update-1.8.$tn.3 {sqlite3_get_autocommit db} $ac } Index: test/errmsg.test ================================================================== --- test/errmsg.test +++ test/errmsg.test @@ -76,18 +76,18 @@ } do_test 2.2 { error_messages "INSERT INTO t1 VALUES('ghi', 'def')" } [list {*}{ SQLITE_ERROR {SQL logic error or missing database} - SQLITE_CONSTRAINT {column b is not unique} + SQLITE_CONSTRAINT {UNIQUE constraint failed: t1.b} }] verify_ex_errcode 2.2b SQLITE_CONSTRAINT_UNIQUE do_test 2.3 { error_messages_v2 "INSERT INTO t1 VALUES('ghi', 'def')" } [list {*}{ - SQLITE_CONSTRAINT {column b is not unique} - SQLITE_CONSTRAINT {column b is not unique} + SQLITE_CONSTRAINT {UNIQUE constraint failed: t1.b} + SQLITE_CONSTRAINT {UNIQUE constraint failed: t1.b} }] verify_ex_errcode 2.3b SQLITE_CONSTRAINT_UNIQUE #------------------------------------------------------------------------- # Test SQLITE_SCHEMA errors. And, for _v2(), test that if the schema Index: test/fkey2.test ================================================================== --- test/fkey2.test +++ test/fkey2.test @@ -102,42 +102,42 @@ CREATE TABLE t10(a REFERENCES t9(c) /D/, b); } set FkeySimpleTests { - 1.1 "INSERT INTO t2 VALUES(1, 3)" {1 {foreign key constraint failed}} + 1.1 "INSERT INTO t2 VALUES(1, 3)" {1 {FOREIGN KEY constraint failed}} 1.2 "INSERT INTO t1 VALUES(1, 2)" {0 {}} 1.3 "INSERT INTO t2 VALUES(1, 3)" {0 {}} - 1.4 "INSERT INTO t2 VALUES(2, 4)" {1 {foreign key constraint failed}} + 1.4 "INSERT INTO t2 VALUES(2, 4)" {1 {FOREIGN KEY constraint failed}} 1.5 "INSERT INTO t2 VALUES(NULL, 4)" {0 {}} - 1.6 "UPDATE t2 SET c=2 WHERE d=4" {1 {foreign key constraint failed}} + 1.6 "UPDATE t2 SET c=2 WHERE d=4" {1 {FOREIGN KEY constraint failed}} 1.7 "UPDATE t2 SET c=1 WHERE d=4" {0 {}} 1.9 "UPDATE t2 SET c=1 WHERE d=4" {0 {}} 1.10 "UPDATE t2 SET c=NULL WHERE d=4" {0 {}} - 1.11 "DELETE FROM t1 WHERE a=1" {1 {foreign key constraint failed}} - 1.12 "UPDATE t1 SET a = 2" {1 {foreign key constraint failed}} + 1.11 "DELETE FROM t1 WHERE a=1" {1 {FOREIGN KEY constraint failed}} + 1.12 "UPDATE t1 SET a = 2" {1 {FOREIGN KEY constraint failed}} 1.13 "UPDATE t1 SET a = 1" {0 {}} - 2.1 "INSERT INTO t4 VALUES(1, 3)" {1 {foreign key constraint failed}} + 2.1 "INSERT INTO t4 VALUES(1, 3)" {1 {FOREIGN KEY constraint failed}} 2.2 "INSERT INTO t3 VALUES(1, 2)" {0 {}} 2.3 "INSERT INTO t4 VALUES(1, 3)" {0 {}} - 4.1 "INSERT INTO t8 VALUES(1, 3)" {1 {foreign key constraint failed}} + 4.1 "INSERT INTO t8 VALUES(1, 3)" {1 {FOREIGN KEY constraint failed}} 4.2 "INSERT INTO t7 VALUES(2, 1)" {0 {}} 4.3 "INSERT INTO t8 VALUES(1, 3)" {0 {}} - 4.4 "INSERT INTO t8 VALUES(2, 4)" {1 {foreign key constraint failed}} + 4.4 "INSERT INTO t8 VALUES(2, 4)" {1 {FOREIGN KEY constraint failed}} 4.5 "INSERT INTO t8 VALUES(NULL, 4)" {0 {}} - 4.6 "UPDATE t8 SET c=2 WHERE d=4" {1 {foreign key constraint failed}} + 4.6 "UPDATE t8 SET c=2 WHERE d=4" {1 {FOREIGN KEY constraint failed}} 4.7 "UPDATE t8 SET c=1 WHERE d=4" {0 {}} 4.9 "UPDATE t8 SET c=1 WHERE d=4" {0 {}} 4.10 "UPDATE t8 SET c=NULL WHERE d=4" {0 {}} - 4.11 "DELETE FROM t7 WHERE b=1" {1 {foreign key constraint failed}} - 4.12 "UPDATE t7 SET b = 2" {1 {foreign key constraint failed}} + 4.11 "DELETE FROM t7 WHERE b=1" {1 {FOREIGN KEY constraint failed}} + 4.12 "UPDATE t7 SET b = 2" {1 {FOREIGN KEY constraint failed}} 4.13 "UPDATE t7 SET b = 1" {0 {}} - 4.14 "INSERT INTO t8 VALUES('a', 'b')" {1 {foreign key constraint failed}} - 4.15 "UPDATE t7 SET b = 5" {1 {foreign key constraint failed}} - 4.16 "UPDATE t7 SET rowid = 5" {1 {foreign key constraint failed}} + 4.14 "INSERT INTO t8 VALUES('a', 'b')" {1 {FOREIGN KEY constraint failed}} + 4.15 "UPDATE t7 SET b = 5" {1 {FOREIGN KEY constraint failed}} + 4.16 "UPDATE t7 SET rowid = 5" {1 {FOREIGN KEY constraint failed}} 4.17 "UPDATE t7 SET a = 10" {0 {}} 5.1 "INSERT INTO t9 VALUES(1, 3)" {1 {no such table: main.nosuchtable}} 5.2 "INSERT INTO t10 VALUES(1, 3)" {1 {foreign key mismatch - "t10" referencing "t9"}} @@ -213,11 +213,11 @@ SELECT j, typeof(j) FROM j; } } {35.0 text} do_test fkey2-1.5.2 { catchsql { DELETE FROM i } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} # Same test using a regular primary key with integer affinity. drop_all_tables do_test fkey2-1.6.1 { execsql { @@ -229,11 +229,11 @@ SELECT i, typeof(i) FROM i; } } {35.0 text 35 integer} do_test fkey2-1.6.2 { catchsql { DELETE FROM i } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} # Use a collation sequence on the parent key. drop_all_tables do_test fkey2-1.7.1 { execsql { @@ -241,11 +241,11 @@ CREATE TABLE j(j TEXT COLLATE binary REFERENCES i(i)); INSERT INTO i VALUES('SQLite'); INSERT INTO j VALUES('sqlite'); } catchsql { DELETE FROM i } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} # Use the parent key collation even if it is default and the child key # has an explicit value. drop_all_tables do_test fkey2-1.7.2 { @@ -253,35 +253,35 @@ CREATE TABLE i(i TEXT PRIMARY KEY); -- Colseq is "BINARY" CREATE TABLE j(j TEXT COLLATE nocase REFERENCES i(i)); INSERT INTO i VALUES('SQLite'); } catchsql { INSERT INTO j VALUES('sqlite') } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-1.7.3 { execsql { INSERT INTO i VALUES('sqlite'); INSERT INTO j VALUES('sqlite'); DELETE FROM i WHERE i = 'SQLite'; } catchsql { DELETE FROM i WHERE i = 'sqlite' } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} #------------------------------------------------------------------------- # This section (test cases fkey2-2.*) contains tests to check that the # deferred foreign key constraint logic works. # proc fkey2-2-test {tn nocommit sql {res {}}} { if {$res eq "FKV"} { - set expected {1 {foreign key constraint failed}} + set expected {1 {FOREIGN KEY constraint failed}} } else { set expected [list 0 $res] } do_test fkey2-2.$tn [list catchsql $sql] $expected if {$nocommit} { do_test fkey2-2.${tn}c { catchsql COMMIT - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} } } fkey2-2-test 1 0 { CREATE TABLE node( @@ -373,11 +373,11 @@ fkey2-2-test 64 1 "INSERT INTO leaf VALUES('a', 1)" fkey2-2-test 65 1 "INSERT INTO leaf VALUES('b', 2)" fkey2-2-test 66 1 "INSERT INTO leaf VALUES('c', 1)" do_test fkey2-2-test-67 { catchsql "INSERT INTO node SELECT parent, 3 FROM leaf" -} {1 {column nodeid is not unique}} +} {1 {UNIQUE constraint failed: node.nodeid}} fkey2-2-test 68 0 "COMMIT" FKV fkey2-2-test 69 1 "INSERT INTO node VALUES(1, NULL)" fkey2-2-test 70 0 "INSERT INTO node VALUES(2, NULL)" fkey2-2-test 71 0 "COMMIT" @@ -415,27 +415,27 @@ INSERT INTO ef VALUES(1, 'e'); } } {} do_test fkey2-3.1.3 { catchsql { UPDATE ab SET a = 5 } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: ef}} do_test fkey2-3.1.4 { execsql { SELECT * FROM ab } } {1 b} do_test fkey2-3.1.4 { execsql BEGIN; catchsql { UPDATE ab SET a = 5 } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: ef}} do_test fkey2-3.1.5 { execsql COMMIT; execsql { SELECT * FROM ab; SELECT * FROM cd; SELECT * FROM ef } } {1 b 1 d 1 e} do_test fkey2-3.2.1 { execsql BEGIN; catchsql { DELETE FROM ab } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-3.2.2 { execsql COMMIT execsql { SELECT * FROM ab; SELECT * FROM cd; SELECT * FROM ef } } {1 b 1 d 1 e} @@ -553,11 +553,11 @@ CREATE TABLE t2(c INTEGER PRIMARY KEY REFERENCES t1, b); } } {} do_test fkey2-7.2 { catchsql { INSERT INTO t2 VALUES(1, 'A'); } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-7.3 { execsql { INSERT INTO t1 VALUES(1, 2); INSERT INTO t1 VALUES(2, 3); INSERT INTO t2 VALUES(1, 'A'); @@ -566,23 +566,23 @@ do_test fkey2-7.4 { execsql { UPDATE t2 SET c = 2 } } {} do_test fkey2-7.5 { catchsql { UPDATE t2 SET c = 3 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-7.6 { catchsql { DELETE FROM t1 WHERE a = 2 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-7.7 { execsql { DELETE FROM t1 WHERE a = 1 } } {} do_test fkey2-7.8 { catchsql { UPDATE t1 SET a = 3 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-7.9 { catchsql { UPDATE t2 SET rowid = 3 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} #------------------------------------------------------------------------- # Test that it is not possible to enable/disable FK support while a # transaction is open. # @@ -643,11 +643,11 @@ do_test fkey2-9.1.4 { execsql { SELECT * FROM t1 } } {2 two} do_test fkey2-9.1.5 { catchsql { DELETE FROM t1 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-9.2.1 { execsql { CREATE TABLE pp(a, b, c, PRIMARY KEY(b, c)); CREATE TABLE cc(d DEFAULT 3, e DEFAULT 1, f DEFAULT 2, @@ -778,17 +778,17 @@ do_test fkey2-12.1.3 { execsql "UPDATE t1 SET b = 'four' WHERE b = 'one'" } {} do_test fkey2-12.1.4 { catchsql "UPDATE t1 SET b = 'five' WHERE b = 'two'" -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-12.1.5 { execsql "DELETE FROM t1 WHERE b = 'two'" } {} do_test fkey2-12.1.6 { catchsql "COMMIT" -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-12.1.7 { execsql { INSERT INTO t1 VALUES(2, 'two'); COMMIT; } @@ -826,11 +826,11 @@ CREATE TABLE t2(y REFERENCES t1 ON DELETE RESTRICT); INSERT INTO t2 VALUES('a'); INSERT INTO t2 VALUES('b'); } catchsql { DELETE FROM t1 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-12.2.4 { execsql { SELECT * FROM t1; SELECT * FROM t2; } @@ -864,18 +864,18 @@ DELETE FROM down; } } {no possibly} do_test fkey2-12.3.3 { catchsql { INSERT INTO down(c39, c38) VALUES('yes', 'no') } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-12.3.4 { execsql { INSERT INTO up(c34, c35) VALUES('yes', 'no'); INSERT INTO down(c39, c38) VALUES('yes', 'no'); } catchsql { DELETE FROM up WHERE c34 = 'yes' } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-12.3.5 { execsql { DELETE FROM up WHERE c34 = 'possibly'; SELECT c34, c35 FROM up; SELECT c39, c38 FROM down; @@ -899,21 +899,21 @@ 1 "REPLACE INTO pp VALUES(1, 4, 5)" 2 "REPLACE INTO pp(rowid, a, b, c) VALUES(1, 2, 3, 4)" } { do_test fkey2-13.1.$tn.1 { catchsql $stmt - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-13.1.$tn.2 { execsql { SELECT * FROM pp; SELECT * FROM cc; } } {1 2 3 2 3 1} do_test fkey2-13.1.$tn.3 { execsql BEGIN; catchsql $stmt - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-13.1.$tn.4 { execsql { COMMIT; SELECT * FROM pp; SELECT * FROM cc; @@ -1013,17 +1013,17 @@ {CREATE TABLE t2(a PRIMARY KEY, b REFERENCES "t4", c REFERENCES t2)} \ {CREATE TABLE t3(a REFERENCES "t4", b REFERENCES t2, c REFERENCES "t4")} \ ] do_test fkey2-14.2.2.3 { catchsql { INSERT INTO t3 VALUES(1, 2, 3) } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-14.2.2.4 { execsql { INSERT INTO t4 VALUES(1, NULL) } } {} do_test fkey2-14.2.2.5 { catchsql { UPDATE t4 SET b = 5 } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-14.2.2.6 { catchsql { UPDATE t4 SET b = 1 } } {0 {}} do_test fkey2-14.2.2.7 { execsql { INSERT INTO t3 VALUES(1, NULL, 1) } @@ -1094,17 +1094,17 @@ {CREATE TABLE t2(a PRIMARY KEY, b REFERENCES "t4", c REFERENCES t2)} \ {CREATE TABLE t3(a REFERENCES "t4", b REFERENCES t2, c REFERENCES "t4")} \ ] do_test fkey2-14.2tmp.2.3 { catchsql { INSERT INTO t3 VALUES(1, 2, 3) } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-14.2tmp.2.4 { execsql { INSERT INTO t4 VALUES(1, NULL) } } {} do_test fkey2-14.2tmp.2.5 { catchsql { UPDATE t4 SET b = 5 } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-14.2tmp.2.6 { catchsql { UPDATE t4 SET b = 1 } } {0 {}} do_test fkey2-14.2tmp.2.7 { execsql { INSERT INTO t3 VALUES(1, NULL, 1) } @@ -1176,17 +1176,17 @@ {CREATE TABLE t2(a PRIMARY KEY, b REFERENCES "t4", c REFERENCES t2)} \ {CREATE TABLE t3(a REFERENCES "t4", b REFERENCES t2, c REFERENCES "t4")} \ ] do_test fkey2-14.2aux.2.3 { catchsql { INSERT INTO t3 VALUES(1, 2, 3) } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-14.2aux.2.4 { execsql { INSERT INTO t4 VALUES(1, NULL) } } {} do_test fkey2-14.2aux.2.5 { catchsql { UPDATE t4 SET b = 5 } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-14.2aux.2.6 { catchsql { UPDATE t4 SET b = 1 } } {0 {}} do_test fkey2-14.2aux.2.7 { execsql { INSERT INTO t3 VALUES(1, NULL, 1) } @@ -1208,11 +1208,11 @@ INSERT INTO t2 VALUES('a'); } } {} do_test fkey-2.14.3.3 { catchsql { DROP TABLE t1 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey-2.14.3.4 { execsql { DELETE FROM t2; DROP TABLE t1; } @@ -1227,11 +1227,11 @@ } execsql { INSERT INTO t2 VALUES('x') } } {} do_test fkey-2.14.3.6 { catchsql { DROP TABLE t1 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey-2.14.3.7 { execsql { DROP TABLE t2; DROP TABLE t1; } @@ -1385,19 +1385,19 @@ execsql { UPDATE self SET a = 14, b = 14 } } {} do_test fkey2-16.1.$tn.3 { catchsql { UPDATE self SET b = 15 } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-16.1.$tn.4 { catchsql { UPDATE self SET a = 15 } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-16.1.$tn.5 { catchsql { UPDATE self SET a = 15, b = 16 } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-16.1.$tn.6 { catchsql { UPDATE self SET a = 17, b = 17 } } {0 {}} @@ -1404,11 +1404,11 @@ do_test fkey2-16.1.$tn.7 { execsql { DELETE FROM self } } {} do_test fkey2-16.1.$tn.8 { catchsql { INSERT INTO self VALUES(20, 21) } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} } #------------------------------------------------------------------------- # This next block of tests, fkey2-17.*, tests that if "PRAGMA count_changes" # is turned on statements that violate immediate FK constraints return @@ -1461,11 +1461,11 @@ catchsql { BEGIN; INSERT INTO one VALUES(0, 0, 0); UPDATE two SET e=e+1, f=f+1; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-17.1.7 { execsql { SELECT * FROM one } } {1 2 3 2 3 4 3 4 5 0 0 0} do_test fkey2-17.1.8 { execsql { SELECT * FROM two } @@ -1617,20 +1617,20 @@ if {[lindex $args 1] == "long"} {return SQLITE_IGNORE} return SQLITE_OK } do_test fkey2-18.8 { catchsql { INSERT INTO short VALUES(1, 3, 2) } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-18.9 { execsql { INSERT INTO short VALUES(1, 3, NULL) } } {} do_test fkey2-18.10 { execsql { SELECT * FROM short } } {1 3 2 1 3 {}} do_test fkey2-18.11 { catchsql { UPDATE short SET f = 2 WHERE f IS NULL } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} db auth {} unset authargs } @@ -1678,11 +1678,11 @@ 5 "INSERT OR REPLACE" 6 "INSERT OR FAIL" } { do_test fkey2-20.2.$tn.1 { catchsql "$insert INTO cc VALUES(1, 2)" - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-20.2.$tn.2 { execsql { SELECT * FROM cc } } {} do_test fkey2-20.2.$tn.3 { execsql { @@ -1689,11 +1689,11 @@ BEGIN; INSERT INTO pp VALUES(2, 'two'); INSERT INTO cc VALUES(1, 2); } catchsql "$insert INTO cc VALUES(3, 4)" - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-20.2.$tn.4 { execsql { COMMIT ; SELECT * FROM cc } } {1 2} do_test fkey2-20.2.$tn.5 { execsql { DELETE FROM cc ; DELETE FROM pp } @@ -1714,37 +1714,37 @@ INSERT INTO cc VALUES(1, 2); } } {} do_test fkey2-20.3.$tn.2 { catchsql "$update pp SET a = 1" - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-20.3.$tn.3 { execsql { SELECT * FROM pp } } {2 two} do_test fkey2-20.3.$tn.4 { catchsql "$update cc SET d = 1" - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-20.3.$tn.5 { execsql { SELECT * FROM cc } } {1 2} do_test fkey2-20.3.$tn.6 { execsql { BEGIN; INSERT INTO pp VALUES(3, 'three'); } catchsql "$update pp SET a = 1 WHERE a = 2" - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-20.3.$tn.7 { execsql { COMMIT ; SELECT * FROM pp } } {2 two 3 three} do_test fkey2-20.3.$tn.8 { execsql { BEGIN; INSERT INTO cc VALUES(2, 2); } catchsql "$update cc SET d = 1 WHERE c = 1" - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test fkey2-20.3.$tn.9 { execsql { COMMIT ; SELECT * FROM cc } } {1 2 2 2} do_test fkey2-20.3.$tn.10 { execsql { DELETE FROM cc ; DELETE FROM pp } @@ -1766,11 +1766,11 @@ CREATE TABLE t3(g, h, i, FOREIGN KEY (h, i) REFERENCES t1(b, c)); } } {} do_test fkey2-genfkey.1.2 { catchsql { INSERT INTO t2 VALUES(1, 2) } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-genfkey.1.3 { execsql { INSERT INTO t1 VALUES(1, 2, 3); INSERT INTO t2 VALUES(1, 2); } @@ -1778,26 +1778,26 @@ do_test fkey2-genfkey.1.4 { execsql { INSERT INTO t2 VALUES(NULL, 3) } } {} do_test fkey2-genfkey.1.5 { catchsql { UPDATE t2 SET e = 5 WHERE e IS NULL } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-genfkey.1.6 { execsql { UPDATE t2 SET e = 1 WHERE e IS NULL } } {} do_test fkey2-genfkey.1.7 { execsql { UPDATE t2 SET e = NULL WHERE f = 3 } } {} do_test fkey2-genfkey.1.8 { catchsql { UPDATE t1 SET a = 10 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-genfkey.1.9 { catchsql { UPDATE t1 SET a = NULL } } {1 {datatype mismatch}} do_test fkey2-genfkey.1.10 { catchsql { DELETE FROM t1 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-genfkey.1.11 { execsql { UPDATE t2 SET e = NULL } } {} do_test fkey2-genfkey.1.12 { execsql { @@ -1813,29 +1813,29 @@ INSERT INTO t3 VALUES(1, NULL, 3); } } {} do_test fkey2-genfkey.1.14 { catchsql { INSERT INTO t3 VALUES(3, 1, 4) } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-genfkey.1.15 { execsql { INSERT INTO t1 VALUES(1, 1, 4); INSERT INTO t3 VALUES(3, 1, 4); } } {} do_test fkey2-genfkey.1.16 { catchsql { DELETE FROM t1 } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-genfkey.1.17 { catchsql { UPDATE t1 SET b = 10} -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-genfkey.1.18 { execsql { UPDATE t1 SET a = 10} } {} do_test fkey2-genfkey.1.19 { catchsql { UPDATE t3 SET h = 'hello' WHERE i = 3} -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} drop_all_tables do_test fkey2-genfkey.2.1 { execsql { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, UNIQUE(b, c)); @@ -1944,31 +1944,31 @@ } {} do_test fkey2-dd08e5.1.2 { catchsql { DELETE FROM tdd08; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-dd08e5.1.3 { execsql { SELECT * FROM tdd08; } } {200 300} do_test fkey2-dd08e5.1.4 { catchsql { INSERT INTO tdd08_b VALUES(400,500,300); } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-dd08e5.1.5 { catchsql { UPDATE tdd08_b SET x=x+1; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-dd08e5.1.6 { catchsql { UPDATE tdd08 SET a=a+1; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} #------------------------------------------------------------------------- # Verify that ticket ce7c133ea6cc9ccdc1a60d80441f80b6180f5eba # fixed. # @@ -1985,16 +1985,16 @@ } {100 200 300 100 200} do_test fkey2-ce7c13.1.2 { catchsql { UPDATE tce71 set b = 201 where a = 100; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-ce7c13.1.3 { catchsql { UPDATE tce71 set a = 101 where a = 100; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-ce7c13.1.4 { execsql { CREATE TABLE tce73(a INTEGER PRIMARY KEY, b, UNIQUE(a,b)); INSERT INTO tce73 VALUES(100,200); CREATE TABLE tce74(w, x, y, FOREIGN KEY(x,y) REFERENCES tce73(a,b)); @@ -2005,13 +2005,13 @@ } {100 200 300 100 200} do_test fkey2-ce7c13.1.5 { catchsql { UPDATE tce73 set b = 201 where a = 100; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey2-ce7c13.1.6 { catchsql { UPDATE tce73 set a = 101 where a = 100; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} finish_test Index: test/fkey3.test ================================================================== --- test/fkey3.test +++ test/fkey3.test @@ -41,17 +41,17 @@ do_test fkey3-1.2 { catchsql { DELETE FROM t1 WHERE x=100; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey3-1.3 { catchsql { DROP TABLE t1; } -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey3-1.4 { execsql { DROP TABLE t2; } @@ -93,29 +93,29 @@ ); INSERT INTO t3 VALUES(1, 2, 1, 2); } {} do_catchsql_test 3.1.2 { INSERT INTO t3 VALUES(NULL, 2, 5, 2); -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_catchsql_test 3.1.3 { INSERT INTO t3 VALUES(NULL, 3, 5, 2); -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_execsql_test 3.2.1 { CREATE TABLE t4(a UNIQUE, b REFERENCES t4(a)); } do_catchsql_test 3.2.2 { INSERT INTO t4 VALUES(NULL, 1); -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_execsql_test 3.3.1 { CREATE TABLE t5(a INTEGER PRIMARY KEY, b REFERENCES t5(a)); INSERT INTO t5 VALUES(NULL, 1); } {} do_catchsql_test 3.3.2 { INSERT INTO t5 VALUES(NULL, 3); -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_execsql_test 3.4.1 { CREATE TABLE t6(a INTEGER PRIMARY KEY, b, c, d, FOREIGN KEY(c, d) REFERENCES t6(a, b) ); @@ -125,11 +125,11 @@ do_execsql_test 3.4.3 { INSERT INTO t6 VALUES(2, 'a', 2, 'a'); } {} do_execsql_test 3.4.4 { INSERT INTO t6 VALUES(NULL, 'a', 1, 'a'); } {} do_execsql_test 3.4.5 { INSERT INTO t6 VALUES(5, 'a', 2, 'a'); } {} do_catchsql_test 3.4.6 { INSERT INTO t6 VALUES(NULL, 'a', 65, 'a'); -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_execsql_test 3.4.7 { INSERT INTO t6 VALUES(100, 'one', 100, 'one'); DELETE FROM t6 WHERE a = 100; } @@ -147,14 +147,14 @@ } do_execsql_test 3.5.2 { INSERT INTO t7 VALUES('x', 1, 'x', NULL) } {} do_execsql_test 3.5.3 { INSERT INTO t7 VALUES('x', 2, 'x', 2) } {} do_catchsql_test 3.5.4 { INSERT INTO t7 VALUES('x', 450, 'x', NULL); -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_catchsql_test 3.5.5 { INSERT INTO t7 VALUES('x', 450, 'x', 451); -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_execsql_test 3.6.1 { CREATE TABLE t8(a, b, c, d, e, FOREIGN KEY(c, d) REFERENCES t8(a, b)); CREATE UNIQUE INDEX t8i1 ON t8(a, b); @@ -161,11 +161,11 @@ CREATE UNIQUE INDEX t8i2 ON t8(c); INSERT INTO t8 VALUES(1, 1, 1, 1, 1); } do_catchsql_test 3.6.2 { UPDATE t8 SET d = 2; -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_execsql_test 3.6.3 { UPDATE t8 SET d = 1; } do_execsql_test 3.6.4 { UPDATE t8 SET e = 2; } do_catchsql_test 3.6.5 { CREATE TABLE TestTable ( @@ -179,8 +179,8 @@ CREATE UNIQUE INDEX testindex on TestTable(source_id, id); PRAGMA foreign_keys=1; INSERT INTO TestTable VALUES (1, 'parent', 1, null); INSERT INTO TestTable VALUES (2, 'child', 1, 1); UPDATE TestTable SET parent_id=1000 where id=2; -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} finish_test Index: test/fkey6.test ================================================================== --- test/fkey6.test +++ test/fkey6.test @@ -45,11 +45,11 @@ INSERT INTO t2 VALUES(1,1),(2,2); INSERT INTO t3 VALUES(3,3),(4,4); } {} do_test fkey6-1.2 { catchsql {DELETE FROM t1 WHERE x=2;} -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_test fkey6-1.3 { sqlite3_db_status db DBSTATUS_DEFERRED_FKS 0 } {0 0 0} do_test fkey6-1.4 { execsql { @@ -97,11 +97,11 @@ PRAGMA defer_foreign_keys; BEGIN; } {1 0 1 0} do_test fkey6-1.10.2 { catchsql {DELETE FROM t1 WHERE x=3} -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} db eval {ROLLBACK} do_test fkey6-1.20 { execsql { BEGIN; @@ -171,6 +171,5 @@ PRAGMA defer_foreign_keys; } {0} finish_test - Index: test/func4.test ================================================================== --- test/func4.test +++ test/func4.test @@ -379,97 +379,97 @@ } {} do_test func4-3.2 { catchsql { INSERT INTO t1 (x) VALUES (NULL); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.3 { catchsql { INSERT INTO t1 (x) VALUES (NULL); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.4 { catchsql { INSERT INTO t1 (x) VALUES (''); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.5 { catchsql { INSERT INTO t1 (x) VALUES ('bad'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.6 { catchsql { INSERT INTO t1 (x) VALUES ('1234bad'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.7 { catchsql { INSERT INTO t1 (x) VALUES ('1234.56bad'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.8 { catchsql { INSERT INTO t1 (x) VALUES (1234); } } {0 {}} do_test func4-3.9 { catchsql { INSERT INTO t1 (x) VALUES (1234.56); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.10 { catchsql { INSERT INTO t1 (x) VALUES ('1234'); } } {0 {}} do_test func4-3.11 { catchsql { INSERT INTO t1 (x) VALUES ('1234.56'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.12 { catchsql { INSERT INTO t1 (x) VALUES (ZEROBLOB(4)); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.13 { catchsql { INSERT INTO t1 (x) VALUES (X''); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.14 { catchsql { INSERT INTO t1 (x) VALUES (X'1234'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.15 { catchsql { INSERT INTO t1 (x) VALUES (X'12345678'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.16 { catchsql { INSERT INTO t1 (x) VALUES ('1234.00'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} do_test func4-3.17 { catchsql { INSERT INTO t1 (x) VALUES (1234.00); } } {0 {}} do_test func4-3.18 { catchsql { INSERT INTO t1 (x) VALUES ('-9223372036854775809'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} if {$highPrecision(1)} { do_test func4-3.19 { catchsql { INSERT INTO t1 (x) VALUES (9223372036854775808); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t1}} } do_execsql_test func4-3.20 { SELECT x FROM t1 ORDER BY x; } {1234 1234 1234} @@ -481,36 +481,36 @@ } {} do_test func4-4.2 { catchsql { INSERT INTO t2 (x) VALUES (NULL); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t2}} do_test func4-4.3 { catchsql { INSERT INTO t2 (x) VALUES (NULL); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t2}} do_test func4-4.4 { catchsql { INSERT INTO t2 (x) VALUES (''); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t2}} do_test func4-4.5 { catchsql { INSERT INTO t2 (x) VALUES ('bad'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t2}} do_test func4-4.6 { catchsql { INSERT INTO t2 (x) VALUES ('1234bad'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t2}} do_test func4-4.7 { catchsql { INSERT INTO t2 (x) VALUES ('1234.56bad'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t2}} do_test func4-4.8 { catchsql { INSERT INTO t2 (x) VALUES (1234); } } {0 {}} @@ -531,26 +531,26 @@ } {0 {}} do_test func4-4.12 { catchsql { INSERT INTO t2 (x) VALUES (ZEROBLOB(4)); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t2}} do_test func4-4.13 { catchsql { INSERT INTO t2 (x) VALUES (X''); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t2}} do_test func4-4.14 { catchsql { INSERT INTO t2 (x) VALUES (X'1234'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t2}} do_test func4-4.15 { catchsql { INSERT INTO t2 (x) VALUES (X'12345678'); } - } {1 {constraint failed}} + } {1 {CHECK constraint failed: t2}} do_execsql_test func4-4.16 { SELECT x FROM t2 ORDER BY x; } {1234.0 1234.0 1234.56 1234.56} } } Index: test/in.test ================================================================== --- test/in.test +++ test/in.test @@ -330,11 +330,11 @@ } {111} do_test in-10.2 { catchsql { INSERT INTO t5 VALUES(4); } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t5}} # Ticket #1821 # # Type affinity applied to the right-hand side of an IN operator. # Index: test/incrblob2.test ================================================================== --- test/incrblob2.test +++ test/incrblob2.test @@ -395,20 +395,20 @@ set h [db incrblob t3 b 3] sqlite3_blob_read $h 0 20 } {cccccccccccccccccccc} do_test incrblob2-8.5 { catchsql {UPDATE t3 SET a = 6 WHERE a > 3} -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: t3.a}} do_test incrblob2-8.6 { catchsql {UPDATE t3 SET a = 6 WHERE a > 3} -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: t3.a}} do_test incrblob2-8.7 { sqlite3_blob_read $h 0 20 } {cccccccccccccccccccc} do_test incrblob2-8.8 { catchsql {UPDATE t3 SET a = 6 WHERE a = 3 OR a = 5} -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: t3.a}} do_test incrblob2-8.9 { set rc [catch {sqlite3_blob_read $h 0 20} msg] list $rc $msg } {1 SQLITE_ABORT} do_test incrblob2-8.X { Index: test/index.test ================================================================== --- test/index.test +++ test/index.test @@ -664,21 +664,21 @@ do_test index-19.2 { catchsql { BEGIN; INSERT INTO t7 VALUES(1); } - } {1 {column a is not unique}} + } {1 {UNIQUE constraint failed: t7.a}} do_test index-19.3 { catchsql { BEGIN; } } {1 {cannot start a transaction within a transaction}} do_test index-19.4 { catchsql { INSERT INTO t8 VALUES(1); } - } {1 {column a is not unique}} + } {1 {UNIQUE constraint failed: t8.a}} do_test index-19.5 { catchsql { BEGIN; COMMIT; } Index: test/index3.test ================================================================== --- test/index3.test +++ test/index3.test @@ -32,11 +32,11 @@ do_test index3-1.2 { catchsql { BEGIN; CREATE UNIQUE INDEX i1 ON t1(a); } -} {1 {indexed columns are not unique}} +} {1 {UNIQUE constraint failed: t1.a}} do_test index3-1.3 { catchsql COMMIT; } {0 {}} integrity_check index3-1.4 Index: test/index4.test ================================================================== --- test/index4.test +++ test/index4.test @@ -118,9 +118,9 @@ INSERT INTO t2 VALUES(16); COMMIT; } do_catchsql_test 2.2 { CREATE UNIQUE INDEX i3 ON t2(x); -} {1 {indexed columns are not unique}} +} {1 {UNIQUE constraint failed: t2.x}} finish_test Index: test/index6.test ================================================================== --- test/index6.test +++ test/index6.test @@ -218,11 +218,11 @@ do_test index6-3.2 { # unable to insert a duplicate row a-value that is not 999. catchsql { INSERT INTO t3(a,b) VALUES(150, 'test1'); } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: t3.a}} do_test index6-3.3 { # can insert multiple rows with a==999 because such rows are not # part of the unique index. catchsql { INSERT INTO t3(a,b) VALUES(999, 'test1'), (999, 'test2'); ADDED test/index7.test Index: test/index7.test ================================================================== --- /dev/null +++ test/index7.test @@ -0,0 +1,251 @@ +# 2013-11-04 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test cases for partial indices in WITHOUT ROWID tables +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +ifcapable !vtab { + finish_test + return +} + +load_static_extension db wholenumber; +do_test index7-1.1 { + # Able to parse and manage partial indices + execsql { + CREATE TABLE t1(a,b,c PRIMARY KEY) WITHOUT rowid; + CREATE INDEX t1a ON t1(a) WHERE a IS NOT NULL; + CREATE INDEX t1b ON t1(b) WHERE b>10; + CREATE VIRTUAL TABLE nums USING wholenumber; + INSERT INTO t1(a,b,c) + SELECT CASE WHEN value%3!=0 THEN value END, value, value + FROM nums WHERE value<=20; + SELECT count(a), count(b) FROM t1; + PRAGMA integrity_check; + } +} {14 20 ok} + +# Make sure the count(*) optimization works correctly with +# partial indices. Ticket [a5c8ed66cae16243be6] 2013-10-03. +# +do_execsql_test index7-1.1.1 { + SELECT count(*) FROM t1; +} {20} + +# Error conditions during parsing... +# +do_test index7-1.2 { + catchsql { + CREATE INDEX bad1 ON t1(a,b) WHERE x IS NOT NULL; + } +} {1 {no such column: x}} +do_test index7-1.3 { + catchsql { + CREATE INDEX bad1 ON t1(a,b) WHERE EXISTS(SELECT * FROM t1); + } +} {1 {subqueries prohibited in partial index WHERE clauses}} +do_test index7-1.4 { + catchsql { + CREATE INDEX bad1 ON t1(a,b) WHERE a!=?1; + } +} {1 {parameters prohibited in partial index WHERE clauses}} +do_test index7-1.5 { + catchsql { + CREATE INDEX bad1 ON t1(a,b) WHERE a!=random(); + } +} {1 {functions prohibited in partial index WHERE clauses}} +do_test index7-1.6 { + catchsql { + CREATE INDEX bad1 ON t1(a,b) WHERE a NOT LIKE 'abc%'; + } +} {1 {functions prohibited in partial index WHERE clauses}} + +do_test index7-1.10 { + execsql { + ANALYZE; + SELECT idx, stat FROM sqlite_stat1 ORDER BY idx; + PRAGMA integrity_check; + } +} {t1 {20 1} t1a {14 1} t1b {10 1} ok} + +# STAT1 shows the partial indices have a reduced number of +# rows. +# +do_test index7-1.11 { + execsql { + UPDATE t1 SET a=b; + ANALYZE; + SELECT idx, stat FROM sqlite_stat1 ORDER BY idx; + PRAGMA integrity_check; + } +} {t1 {20 1} t1a {20 1} t1b {10 1} ok} + +do_test index7-1.11b { + execsql { + UPDATE t1 SET a=NULL WHERE b%3!=0; + UPDATE t1 SET b=b+100; + ANALYZE; + SELECT idx, stat FROM sqlite_stat1 ORDER BY idx; + PRAGMA integrity_check; + } +} {t1 {20 1} t1a {6 1} t1b {20 1} ok} + +do_test index7-1.12 { + execsql { + UPDATE t1 SET a=CASE WHEN b%3!=0 THEN b END; + UPDATE t1 SET b=b-100; + ANALYZE; + SELECT idx, stat FROM sqlite_stat1 ORDER BY idx; + PRAGMA integrity_check; + } +} {t1 {20 1} t1a {13 1} t1b {10 1} ok} + +do_test index7-1.13 { + execsql { + DELETE FROM t1 WHERE b BETWEEN 8 AND 12; + ANALYZE; + SELECT idx, stat FROM sqlite_stat1 ORDER BY idx; + PRAGMA integrity_check; + } +} {t1 {15 1} t1a {10 1} t1b {8 1} ok} + +do_test index7-1.14 { + execsql { + REINDEX; + ANALYZE; + SELECT idx, stat FROM sqlite_stat1 ORDER BY idx; + PRAGMA integrity_check; + } +} {t1 {15 1} t1a {10 1} t1b {8 1} ok} + +do_test index7-1.15 { + execsql { + CREATE INDEX t1c ON t1(c); + ANALYZE; + SELECT idx, stat FROM sqlite_stat1 ORDER BY idx; + PRAGMA integrity_check; + } +} {t1 {15 1} t1a {10 1} t1b {8 1} t1c {15 1} ok} + +# Queries use partial indices as appropriate times. +# +do_test index7-2.1 { + execsql { + CREATE TABLE t2(a,b PRIMARY KEY) without rowid; + INSERT INTO t2(a,b) SELECT value, value FROM nums WHERE value<1000; + UPDATE t2 SET a=NULL WHERE b%5==0; + CREATE INDEX t2a1 ON t2(a) WHERE a IS NOT NULL; + SELECT count(*) FROM t2 WHERE a IS NOT NULL; + } +} {800} +do_test index7-2.2 { + execsql { + EXPLAIN QUERY PLAN + SELECT * FROM t2 WHERE a=5; + } +} {/.* TABLE t2 USING COVERING INDEX t2a1 .*/} +ifcapable stat4||stat3 { + do_test index7-2.3stat4 { + execsql { + EXPLAIN QUERY PLAN + SELECT * FROM t2 WHERE a IS NOT NULL; + } + } {/.* TABLE t2 USING COVERING INDEX t2a1 .*/} +} else { + do_test index7-2.3stat4 { + execsql { + EXPLAIN QUERY PLAN + SELECT * FROM t2 WHERE a IS NOT NULL AND a>0; + } + } {/.* TABLE t2 USING COVERING INDEX t2a1 .*/} +} +do_test index7-2.4 { + execsql { + EXPLAIN QUERY PLAN + SELECT * FROM t2 WHERE a IS NULL; + } +} {~/.*INDEX t2a1.*/} + +do_execsql_test index7-2.101 { + DROP INDEX t2a1; + UPDATE t2 SET a=b, b=b+10000; + SELECT b FROM t2 WHERE a=15; +} {10015} +do_execsql_test index7-2.102 { + CREATE INDEX t2a2 ON t2(a) WHERE a<100 OR a>200; + SELECT b FROM t2 WHERE a=15; + PRAGMA integrity_check; +} {10015 ok} +do_execsql_test index7-2.102eqp { + EXPLAIN QUERY PLAN + SELECT b FROM t2 WHERE a=15; +} {~/.*INDEX t2a2.*/} +do_execsql_test index7-2.103 { + SELECT b FROM t2 WHERE a=15 AND a<100; +} {10015} +do_execsql_test index7-2.103eqp { + EXPLAIN QUERY PLAN + SELECT b FROM t2 WHERE a=15 AND a<100; +} {/.*INDEX t2a2.*/} +do_execsql_test index7-2.104 { + SELECT b FROM t2 WHERE a=515 AND a>200; +} {10515} +do_execsql_test index7-2.104eqp { + EXPLAIN QUERY PLAN + SELECT b FROM t2 WHERE a=515 AND a>200; +} {/.*INDEX t2a2.*/} + +# Partial UNIQUE indices +# +do_execsql_test index7-3.1 { + CREATE TABLE t3(a,b PRIMARY KEY) without rowid; + INSERT INTO t3 SELECT value, value FROM nums WHERE value<200; + UPDATE t3 SET a=999 WHERE b%5!=0; + CREATE UNIQUE INDEX t3a ON t3(a) WHERE a<>999; +} {} +do_test index7-3.2 { + # unable to insert a duplicate row a-value that is not 999. + catchsql { + INSERT INTO t3(a,b) VALUES(150, 'test1'); + } +} {1 {UNIQUE constraint failed: t3.a}} +do_test index7-3.3 { + # can insert multiple rows with a==999 because such rows are not + # part of the unique index. + catchsql { + INSERT INTO t3(a,b) VALUES(999, 'test1'), (999, 'test2'); + } +} {0 {}} +do_execsql_test index7-3.4 { + SELECT count(*) FROM t3 WHERE a=999; +} {162} +integrity_check index7-3.5 + +do_execsql_test index7-4.0 { + VACUUM; + PRAGMA integrity_check; +} {ok} + +# Silently ignore database name qualifiers in partial indices. +# +do_execsql_test index7-5.0 { + CREATE INDEX t3b ON t3(b) WHERE xyzzy.t3.b BETWEEN 5 AND 10; + /* ^^^^^-- ignored */ + ANALYZE; + SELECT count(*) FROM t3 WHERE t3.b BETWEEN 5 AND 10; + SELECT stat+0 FROM sqlite_stat1 WHERE idx='t3b'; +} {6 6} + +finish_test Index: test/insert4.test ================================================================== --- test/insert4.test +++ test/insert4.test @@ -52,11 +52,11 @@ INSERT INTO t2 VALUES(9,1); } catchsql { INSERT INTO t1 SELECT * FROM t2; } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t1}} xferopt_test insert4-1.2 0 do_test insert4-1.3 { execsql { SELECT * FROM t1; } @@ -99,11 +99,11 @@ catchsql { DELETE FROM t1; INSERT INTO t1 SELECT * FROM t2 LIMIT 1; SELECT * FROM t1; } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t1}} xferopt_test insert4-2.3.4 0 # Do not run the transfer optimization if there is a DISTINCT # do_test insert4-2.4.1 { @@ -117,11 +117,11 @@ do_test insert4-2.4.3 { catchsql { DELETE FROM t1; INSERT INTO t1 SELECT DISTINCT * FROM t2; } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t1}} xferopt_test insert4-2.4.4 0 # The following procedure constructs two tables then tries to transfer # data from one table to the other. Checks are made to make sure the # transfer is successful and that the transfer optimization was used or @@ -313,20 +313,20 @@ CREATE TABLE t6b(x CHECK( x<>'abc' COLLATE nocase )); } catchsql { INSERT INTO t6b SELECT * FROM t6a; } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t6b}} do_test insert4-6.7 { execsql { DROP TABLE t6b; CREATE TABLE t6b(x CHECK( x COLLATE nocase <>'abc' )); } catchsql { INSERT INTO t6b SELECT * FROM t6a; } -} {1 {constraint failed}} +} {1 {CHECK constraint failed: t6b}} # Ticket [6284df89debdfa61db8073e062908af0c9b6118e] # Disable the xfer optimization if the destination table contains # a foreign key constraint # @@ -351,11 +351,11 @@ PRAGMA foreign_keys=ON; } catchsql { INSERT INTO t7b SELECT * FROM t7c; } - } {1 {foreign key constraint failed}} + } {1 {FOREIGN KEY constraint failed}} do_test insert4-7.4 { execsql {SELECT * FROM t7b} } {} do_test insert4-7.5 { set ::sqlite3_xferopt_count @@ -450,11 +450,11 @@ SELECT * FROM t1; } catchsql { INSERT INTO t1 SELECT * FROM t2; } -} {1 {PRIMARY KEY must be unique}} +} {1 {UNIQUE constraint failed: t1.a}} do_test insert4-8.6 { execsql { SELECT * FROM t1; } } {-99 100 1 2} @@ -470,11 +470,11 @@ SELECT * FROM t1; } catchsql { INSERT INTO t1 SELECT * FROM t2; } -} {1 {PRIMARY KEY must be unique}} +} {1 {UNIQUE constraint failed: t1.a}} do_test insert4-8.8 { execsql { SELECT * FROM t1; } } {1 2} @@ -492,11 +492,11 @@ catchsql { BEGIN; INSERT INTO t1 VALUES(2,3); INSERT INTO t1 SELECT * FROM t2; } -} {1 {PRIMARY KEY must be unique}} +} {1 {UNIQUE constraint failed: t1.a}} do_test insert4-8.10 { catchsql {COMMIT} } {1 {cannot commit - no transaction is active}} do_test insert4-8.11 { execsql { Index: test/intpkey.test ================================================================== --- test/intpkey.test +++ test/intpkey.test @@ -74,11 +74,11 @@ do_test intpkey-1.6 { set r [catch {execsql { INSERT INTO t1 VALUES(5,'second','entry'); }} msg] lappend r $msg -} {1 {PRIMARY KEY must be unique}} +} {1 {UNIQUE constraint failed: t1.a}} do_test intpkey-1.7 { execsql { SELECT rowid, * FROM t1; } } {5 5 hello world} Index: test/memdb.test ================================================================== --- test/memdb.test +++ test/memdb.test @@ -238,11 +238,11 @@ # loop if that is not available in this build. ifcapable !conflict { if {$i>1} break } - if {$t0} {set t1 {column a is not unique}} + if {$t0} {set t1 {UNIQUE constraint failed: t1.a}} do_test memdb-5.$i { if {$conf1!=""} {set conf1 "ON CONFLICT $conf1"} if {$conf2!=""} {set conf2 "ON CONFLICT $conf2"} set r0 [catch {execsql " DROP TABLE t1; Index: test/misc1.test ================================================================== --- test/misc1.test +++ test/misc1.test @@ -233,11 +233,11 @@ } {1 2 3} do_test misc1-7.4 { catchsql { INSERT INTO t5 VALUES(1,2,4); } -} {1 {columns a, b are not unique}} +} {1 {UNIQUE constraint failed: t5.a, t5.b}} do_test misc1-7.5 { catchsql { INSERT INTO t5 VALUES(0,2,4); } } {0 {}} Index: test/misc3.test ================================================================== --- test/misc3.test +++ test/misc3.test @@ -281,21 +281,21 @@ set x [execsql { EXPLAIN SELECT a+123456789012, b*4.5678, c FROM ex1 ORDER BY +a, b DESC }] set y [regexp { 123456789012 } $x] lappend y [regexp { 4.5678 } $x] - lappend y [regexp {,-BINARY} $x] + lappend y [regexp {,-B} $x] } {1 1 1} } else { do_test misc3-6.11-utf8 { set x [execsql { EXPLAIN SELECT a+123456789012, b*4.5678, c FROM ex1 ORDER BY +a, b DESC }] set y [regexp { 123456789012 } $x] lappend y [regexp { 4.5678 } $x] lappend y [regexp { hello } $x] - lappend y [regexp {,-BINARY} $x] + lappend y [regexp {,-B} $x] } {1 1 1 1} } } ifcapable {trigger} { Index: test/notnull.test ================================================================== --- test/notnull.test +++ test/notnull.test @@ -45,11 +45,11 @@ catchsql { DELETE FROM t1; INSERT INTO t1(b,c,d,e) VALUES(2,3,4,5); SELECT * FROM t1 order by a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-1.2b SQLITE_CONSTRAINT_NOTNULL do_test notnull-1.3 { catchsql { DELETE FROM t1; INSERT OR IGNORE INTO t1(b,c,d,e) VALUES(2,3,4,5); @@ -60,19 +60,19 @@ catchsql { DELETE FROM t1; INSERT OR REPLACE INTO t1(b,c,d,e) VALUES(2,3,4,5); SELECT * FROM t1 order by a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-1.4b SQLITE_CONSTRAINT_NOTNULL do_test notnull-1.5 { catchsql { DELETE FROM t1; INSERT OR ABORT INTO t1(b,c,d,e) VALUES(2,3,4,5); SELECT * FROM t1 order by a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-1.5b SQLITE_CONSTRAINT_NOTNULL do_test notnull-1.6 { catchsql { DELETE FROM t1; INSERT INTO t1(a,c,d,e) VALUES(1,3,4,5); @@ -104,11 +104,11 @@ catchsql { DELETE FROM t1; INSERT INTO t1(a,b,c,d,e) VALUES(1,null,3,4,5); SELECT * FROM t1 order by a; } -} {1 {t1.b may not be NULL}} +} {1 {NOT NULL constraint failed: t1.b}} verify_ex_errcode notnull-1.10b SQLITE_CONSTRAINT_NOTNULL do_test notnull-1.11 { catchsql { DELETE FROM t1; INSERT OR IGNORE INTO t1(a,b,c,d,e) VALUES(1,null,3,4,5); @@ -147,19 +147,19 @@ catchsql { DELETE FROM t1; INSERT OR ABORT INTO t1(a,b,c,d,e) VALUES(1,2,null,4,5); SELECT * FROM t1 order by a; } -} {1 {t1.c may not be NULL}} +} {1 {NOT NULL constraint failed: t1.c}} verify_ex_errcode notnull-1.16b SQLITE_CONSTRAINT_NOTNULL do_test notnull-1.17 { catchsql { DELETE FROM t1; INSERT OR ABORT INTO t1(a,b,c,d,e) VALUES(1,2,3,null,5); SELECT * FROM t1 order by a; } -} {1 {t1.d may not be NULL}} +} {1 {NOT NULL constraint failed: t1.d}} verify_ex_errcode notnull-1.17b SQLITE_CONSTRAINT_NOTNULL do_test notnull-1.18 { catchsql { DELETE FROM t1; INSERT OR ABORT INTO t1(a,b,c,e) VALUES(1,2,3,5); @@ -177,11 +177,11 @@ catchsql { DELETE FROM t1; INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,null); SELECT * FROM t1 order by a; } -} {1 {t1.e may not be NULL}} +} {1 {NOT NULL constraint failed: t1.e}} verify_ex_errcode notnull-1.20b SQLITE_CONSTRAINT_NOTNULL do_test notnull-1.21 { catchsql { DELETE FROM t1; INSERT OR REPLACE INTO t1(e,d,c,b,a) VALUES(1,2,3,null,5); @@ -194,20 +194,20 @@ DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); UPDATE t1 SET a=null; SELECT * FROM t1 ORDER BY a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-2.1b SQLITE_CONSTRAINT_NOTNULL do_test notnull-2.2 { catchsql { DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); UPDATE OR REPLACE t1 SET a=null; SELECT * FROM t1 ORDER BY a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-2.2b SQLITE_CONSTRAINT_NOTNULL do_test notnull-2.3 { catchsql { DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); @@ -220,20 +220,20 @@ DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); UPDATE OR ABORT t1 SET a=null; SELECT * FROM t1 ORDER BY a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-2.4b SQLITE_CONSTRAINT_NOTNULL do_test notnull-2.5 { catchsql { DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); UPDATE t1 SET b=null; SELECT * FROM t1 ORDER BY a; } -} {1 {t1.b may not be NULL}} +} {1 {NOT NULL constraint failed: t1.b}} verify_ex_errcode notnull-2.6b SQLITE_CONSTRAINT_NOTNULL do_test notnull-2.6 { catchsql { DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); @@ -270,11 +270,11 @@ DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); UPDATE t1 SET e=null, a=b, b=a; SELECT * FROM t1 ORDER BY a; } -} {1 {t1.e may not be NULL}} +} {1 {NOT NULL constraint failed: t1.e}} verify_ex_errcode notnull-2.10b SQLITE_CONSTRAINT_NOTNULL do_test notnull-3.0 { execsql { CREATE INDEX t1a ON t1(a); @@ -296,11 +296,11 @@ catchsql { DELETE FROM t1; INSERT INTO t1(b,c,d,e) VALUES(2,3,4,5); SELECT * FROM t1 order by a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-3.2b SQLITE_CONSTRAINT_NOTNULL do_test notnull-3.3 { catchsql { DELETE FROM t1; INSERT OR IGNORE INTO t1(b,c,d,e) VALUES(2,3,4,5); @@ -311,19 +311,19 @@ catchsql { DELETE FROM t1; INSERT OR REPLACE INTO t1(b,c,d,e) VALUES(2,3,4,5); SELECT * FROM t1 order by a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-3.4b SQLITE_CONSTRAINT_NOTNULL do_test notnull-3.5 { catchsql { DELETE FROM t1; INSERT OR ABORT INTO t1(b,c,d,e) VALUES(2,3,4,5); SELECT * FROM t1 order by a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-3.5b SQLITE_CONSTRAINT_NOTNULL do_test notnull-3.6 { catchsql { DELETE FROM t1; INSERT INTO t1(a,c,d,e) VALUES(1,3,4,5); @@ -355,11 +355,11 @@ catchsql { DELETE FROM t1; INSERT INTO t1(a,b,c,d,e) VALUES(1,null,3,4,5); SELECT * FROM t1 order by a; } -} {1 {t1.b may not be NULL}} +} {1 {NOT NULL constraint failed: t1.b}} verify_ex_errcode notnull-3.10b SQLITE_CONSTRAINT_NOTNULL do_test notnull-3.11 { catchsql { DELETE FROM t1; INSERT OR IGNORE INTO t1(a,b,c,d,e) VALUES(1,null,3,4,5); @@ -398,19 +398,19 @@ catchsql { DELETE FROM t1; INSERT OR ABORT INTO t1(a,b,c,d,e) VALUES(1,2,null,4,5); SELECT * FROM t1 order by a; } -} {1 {t1.c may not be NULL}} +} {1 {NOT NULL constraint failed: t1.c}} verify_ex_errcode notnull-3.16b SQLITE_CONSTRAINT_NOTNULL do_test notnull-3.17 { catchsql { DELETE FROM t1; INSERT OR ABORT INTO t1(a,b,c,d,e) VALUES(1,2,3,null,5); SELECT * FROM t1 order by a; } -} {1 {t1.d may not be NULL}} +} {1 {NOT NULL constraint failed: t1.d}} verify_ex_errcode notnull-3.17b SQLITE_CONSTRAINT_NOTNULL do_test notnull-3.18 { catchsql { DELETE FROM t1; INSERT OR ABORT INTO t1(a,b,c,e) VALUES(1,2,3,5); @@ -428,11 +428,11 @@ catchsql { DELETE FROM t1; INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,null); SELECT * FROM t1 order by a; } -} {1 {t1.e may not be NULL}} +} {1 {NOT NULL constraint failed: t1.e}} verify_ex_errcode notnull-3.20b SQLITE_CONSTRAINT_NOTNULL do_test notnull-3.21 { catchsql { DELETE FROM t1; INSERT OR REPLACE INTO t1(e,d,c,b,a) VALUES(1,2,3,null,5); @@ -445,20 +445,20 @@ DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); UPDATE t1 SET a=null; SELECT * FROM t1 ORDER BY a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-4.1b SQLITE_CONSTRAINT_NOTNULL do_test notnull-4.2 { catchsql { DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); UPDATE OR REPLACE t1 SET a=null; SELECT * FROM t1 ORDER BY a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-4.2b SQLITE_CONSTRAINT_NOTNULL do_test notnull-4.3 { catchsql { DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); @@ -471,20 +471,20 @@ DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); UPDATE OR ABORT t1 SET a=null; SELECT * FROM t1 ORDER BY a; } -} {1 {t1.a may not be NULL}} +} {1 {NOT NULL constraint failed: t1.a}} verify_ex_errcode notnull-4.4b SQLITE_CONSTRAINT_NOTNULL do_test notnull-4.5 { catchsql { DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); UPDATE t1 SET b=null; SELECT * FROM t1 ORDER BY a; } -} {1 {t1.b may not be NULL}} +} {1 {NOT NULL constraint failed: t1.b}} verify_ex_errcode notnull-4.5b SQLITE_CONSTRAINT_NOTNULL do_test notnull-4.6 { catchsql { DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); @@ -521,11 +521,11 @@ DELETE FROM t1; INSERT INTO t1 VALUES(1,2,3,4,5); UPDATE t1 SET e=null, a=b, b=a; SELECT * FROM t1 ORDER BY a; } -} {1 {t1.e may not be NULL}} +} {1 {NOT NULL constraint failed: t1.e}} verify_ex_errcode notnull-4.10b SQLITE_CONSTRAINT_NOTNULL # Test that bug 29ab7be99f is fixed. # do_test notnull-5.1 { @@ -540,11 +540,11 @@ do_test notnull-5.2 { catchsql { INSERT INTO t1 VALUES(1, 2); INSERT INTO t1 SELECT * FROM t2; } -} {1 {t1.b may not be NULL}} +} {1 {NOT NULL constraint failed: t1.b}} verify_ex_errcode notnull-5.2b SQLITE_CONSTRAINT_NOTNULL do_test notnull-5.3 { execsql { SELECT * FROM t1 } } {1 2} do_test notnull-5.4 { @@ -553,12 +553,12 @@ BEGIN; INSERT INTO t1 VALUES(1, 2); INSERT INTO t1 SELECT * FROM t2; COMMIT; } -} {1 {t1.b may not be NULL}} +} {1 {NOT NULL constraint failed: t1.b}} verify_ex_errcode notnull-5.4b SQLITE_CONSTRAINT_NOTNULL do_test notnull-5.5 { execsql { SELECT * FROM t1 } } {1 2} finish_test Index: test/orderby5.test ================================================================== --- test/orderby5.test +++ test/orderby5.test @@ -91,7 +91,22 @@ do_execsql_test 2.7 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=0 ORDER BY c, b, a; } {/B-TREE/} + +do_execsql_test 3.0 { + CREATE TABLE t3(a INTEGER PRIMARY KEY, b, c, d, e, f); + CREATE INDEX t3bcde ON t3(b, c, d, e); + EXPLAIN QUERY PLAN + SELECT a FROM t3 WHERE b=2 AND c=3 ORDER BY d DESC, e DESC, b, c, a DESC; +} {~/B-TREE/} +do_execsql_test 3.1 { + DROP TABLE t3; + CREATE TABLE t3(a INTEGER PRIMARY KEY, b, c, d, e, f) WITHOUT rowid; + CREATE INDEX t3bcde ON t3(b, c, d, e); + EXPLAIN QUERY PLAN + SELECT a FROM t3 WHERE b=2 AND c=3 ORDER BY d DESC, e DESC, b, c, a DESC; +} {~/B-TREE/} + finish_test Index: test/pager1.test ================================================================== --- test/pager1.test +++ test/pager1.test @@ -269,11 +269,11 @@ INSERT INTO t1 VALUES(3, randomblob(1500)); SELECT * FROM counter; } {3 0} do_catchsql_test pager1-3.1.3 { INSERT INTO t1 SELECT a+3, randomblob(1500) FROM t1 -} {1 {constraint failed}} +} {1 {CHECK constraint failed: counter}} do_execsql_test pager1-3.4 { SELECT * FROM counter } {3 0} do_execsql_test pager1-3.5 { SELECT a FROM t1 } {1 2 3} do_execsql_test pager1-3.6 { COMMIT } {} foreach {tn sql tcl} { @@ -1701,11 +1701,11 @@ } {1 2} do_catchsql_test pager1-14.1.4 { BEGIN; INSERT INTO t1(rowid, a, b) SELECT a+3, b, b FROM t1; INSERT INTO t1(rowid, a, b) SELECT a+3, b, b FROM t1; -} {1 {PRIMARY KEY must be unique}} +} {1 {UNIQUE constraint failed: t1.rowid}} do_execsql_test pager1-14.1.5 { COMMIT; SELECT * FROM t1; } {1 2 2 2} Index: test/pragma.test ================================================================== --- test/pragma.test +++ test/pragma.test @@ -283,35 +283,35 @@ set offset [expr {$pgsz*($rootpage-1)}] hexio_write test.db $offset 0a00000000040000000000 db close sqlite3 db test.db execsql {PRAGMA integrity_check} - } {{rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2}} + } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} do_test pragma-3.3 { execsql {PRAGMA integrity_check=1} - } {{rowid 1 missing from index i2}} + } {{row 1 missing from index i2}} do_test pragma-3.4 { execsql { ATTACH DATABASE 'test.db' AS t2; PRAGMA integrity_check } - } {{rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2} {rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2}} + } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} do_test pragma-3.5 { execsql { PRAGMA integrity_check=4 } - } {{rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2} {rowid 1 missing from index i2}} + } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {row 1 missing from index i2}} do_test pragma-3.6 { execsql { PRAGMA integrity_check=xyz } - } {{rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2} {rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2}} + } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} do_test pragma-3.7 { execsql { PRAGMA integrity_check=0 } - } {{rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2} {rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2}} + } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} # Add additional corruption by appending unused pages to the end of # the database file testerr.db # do_test pragma-3.8 { @@ -342,11 +342,11 @@ PRAGMA integrity_check } } {{*** in database t2 *** Page 4 is never used Page 5 is never used -Page 6 is never used} {rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2}} +Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} do_test pragma-3.10 { execsql { PRAGMA integrity_check=1 } } {{*** in database t2 *** @@ -356,19 +356,19 @@ PRAGMA integrity_check=5 } } {{*** in database t2 *** Page 4 is never used Page 5 is never used -Page 6 is never used} {rowid 1 missing from index i2} {rowid 2 missing from index i2}} +Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2}} do_test pragma-3.12 { execsql { PRAGMA integrity_check=4 } } {{*** in database t2 *** Page 4 is never used Page 5 is never used -Page 6 is never used} {rowid 1 missing from index i2}} +Page 6 is never used} {row 1 missing from index i2}} do_test pragma-3.13 { execsql { PRAGMA integrity_check=3 } } {{*** in database t2 *** @@ -388,43 +388,43 @@ PRAGMA integrity_check } } {{*** in database t2 *** Page 4 is never used Page 5 is never used -Page 6 is never used} {rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** +Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** Page 4 is never used Page 5 is never used -Page 6 is never used} {rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2}} +Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} do_test pragma-3.16 { execsql { PRAGMA integrity_check(10) } } {{*** in database t2 *** Page 4 is never used Page 5 is never used -Page 6 is never used} {rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** +Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** Page 4 is never used Page 5 is never used -Page 6 is never used} {rowid 1 missing from index i2}} +Page 6 is never used} {row 1 missing from index i2}} do_test pragma-3.17 { execsql { PRAGMA integrity_check=8 } } {{*** in database t2 *** Page 4 is never used Page 5 is never used -Page 6 is never used} {rowid 1 missing from index i2} {rowid 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** +Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {*** in database t3 *** Page 4 is never used Page 5 is never used}} do_test pragma-3.18 { execsql { PRAGMA integrity_check=4 } } {{*** in database t2 *** Page 4 is never used Page 5 is never used -Page 6 is never used} {rowid 1 missing from index i2}} +Page 6 is never used} {row 1 missing from index i2}} } do_test pragma-3.19 { catch {db close} forcedelete test.db test.db-journal sqlite3 db test.db Index: test/rollback.test ================================================================== --- test/rollback.test +++ test/rollback.test @@ -52,11 +52,11 @@ # do_test rollback-1.4 { catchsql { INSERT INTO t3 SELECT a FROM t1; } - } {1 {column a is not unique}} + } {1 {UNIQUE constraint failed: t3.a}} # Try to continue with the SELECT statement # do_test rollback-1.5 { sqlite3_step $STMT Index: test/savepoint.test ================================================================== --- test/savepoint.test +++ test/savepoint.test @@ -856,11 +856,11 @@ SAVEPOINT sp1; INSERT INTO t4 VALUES(3, 'three'); SAVEPOINT sp2; INSERT OR ROLLBACK INTO t4 VALUES(1, 'one'); } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: t4.a}} do_test savepoint-12.3 { sqlite3_get_autocommit db } {1} do_test savepoint-12.4 { execsql { SAVEPOINT one } Index: test/schema5.test ================================================================== --- test/schema5.test +++ test/schema5.test @@ -28,11 +28,11 @@ SELECT * FROM t1; } } {1 2 3} do_test schema5-1.2 { catchsql {INSERT INTO t1 VALUES(1,3,4);} -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: t1.a}} do_test schema5-1.3 { db eval { DROP TABLE t1; CREATE TABLE t1(a,b,c, CONSTRAINT one PRIMARY KEY(a) CONSTRAINT two CHECK(b<10) UNIQUE(b) @@ -42,11 +42,11 @@ SELECT * FROM t1; } } {1 2 3} do_test schema5-1.4 { catchsql {INSERT INTO t1 VALUES(10,11,12);} -} {1 {constraint two failed}} +} {1 {CHECK constraint failed: two}} do_test schema5-1.5 { db eval { DROP TABLE t1; CREATE TABLE t1(a,b,c, UNIQUE(a) CONSTRAINT one, @@ -55,15 +55,15 @@ INSERT INTO t1 VALUES(1,2,3); } } {} do_test schema5-1.6 { catchsql {INSERT INTO t1 VALUES(1,3,4)} -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: t1.a}} do_test schema5-1.7 { catchsql {INSERT INTO t1 VALUES(10,2,3)} -} {1 {columns b, c are not unique}} +} {1 {UNIQUE constraint failed: t1.b, t1.c}} finish_test Index: test/table.test ================================================================== --- test/table.test +++ test/table.test @@ -462,11 +462,11 @@ do_test table-10.1 { catchsql { CREATE TABLE t6(a REFERENCES t4(a) NOT NULL); INSERT INTO t6 VALUES(NULL); } -} {1 {t6.a may not be NULL}} +} {1 {NOT NULL constraint failed: t6.a}} do_test table-10.2 { catchsql { DROP TABLE t6; CREATE TABLE t6(a REFERENCES t4(a) MATCH PARTIAL); } ADDED test/tableopts.test Index: test/tableopts.test ================================================================== --- /dev/null +++ test/tableopts.test @@ -0,0 +1,71 @@ +# 2013-10-19 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test the operation of table-options in the WITH clause of the +# CREATE TABLE statement. +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_test tableopt-1.1 { + catchsql { + CREATE TABLE t1(a,b) WITHOUT rowid; + } +} {1 {no PRIMARY KEY for table t1}} +do_test tableopt-1.2 { + catchsql { + CREATE TABLE t1(a,b) WITHOUT unknown2; + } +} {1 {unknown table option: unknown2}} + +do_execsql_test tableopt-2.1 { + CREATE TABLE t1(a, b, c, PRIMARY KEY(a,b)) WITHOUT rowid; + INSERT INTO t1 VALUES(1,2,3),(2,3,4); + SELECT c FROM t1 WHERE a IN (1,2) ORDER BY b; +} {3 4} +do_test tableopt-2.1.1 { + catchsql { + SELECT rowid, * FROM t1; + } +} {1 {no such column: rowid}} +do_test tableopt-2.1.2 { + catchsql { + SELECT _rowid_, * FROM t1; + } +} {1 {no such column: _rowid_}} +do_test tableopt-2.1.3 { + catchsql { + SELECT oid, * FROM t1; + } +} {1 {no such column: oid}} +do_execsql_test tableopt-2.2 { + VACUUM; + SELECT c FROM t1 WHERE a IN (1,2) ORDER BY b; +} {3 4} +do_test tableopt-2.3 { + sqlite3 db2 test.db + db2 eval {SELECT c FROM t1 WHERE a IN (1,2) ORDER BY b;} +} {3 4} +db2 close + +# Make sure the "without" keyword is still usable as a table or +# column name. +# +do_execsql_test tableopt-3.1 { + CREATE TABLE without(x INTEGER PRIMARY KEY, without TEXT); + INSERT INTO without VALUES(1, 'xyzzy'), (2, 'fizzle'); + SELECT * FROM without WHERE without='xyzzy'; +} {1 xyzzy} + + +finish_test Index: test/tester.tcl ================================================================== --- test/tester.tcl +++ test/tester.tcl @@ -1020,10 +1020,82 @@ puts [format {%-4d %-12.12s %-6d %-6d %-6d % -17s %s %s} \ $addr $opcode $p1 $p2 $p3 $p4 $p5 $comment ] } } + +proc explain_i {sql {db db}} { + puts "" + puts "addr opcode p1 p2 p3 p4 p5 #" + puts "---- ------------ ------ ------ ------ ---------------- -- -" + + + # Set up colors for the different opcodes. Scheme is as follows: + # + # Red: Opcodes that write to a b-tree. + # Blue: Opcodes that reposition or seek a cursor. + # Green: The ResultRow opcode. + # + set R "\033\[31;1m" ;# Red fg + set G "\033\[32;1m" ;# Green fg + set B "\033\[34;1m" ;# Red fg + set D "\033\[39;0m" ;# Default fg + foreach opcode { + Seek SeekGe SeekGt SeekLe SeekLt NotFound Last Rewind + NoConflict Next Prev + } { + set color($opcode) $B + } + foreach opcode {ResultRow} { + set color($opcode) $G + } + foreach opcode {IdxInsert Insert Delete IdxDelete} { + set color($opcode) $R + } + + set bSeenGoto 0 + $db eval "explain $sql" {} { + set x($addr) 0 + set op($addr) $opcode + + if {$opcode == "Goto" && ($bSeenGoto==0 || ($p2 > $addr+10))} { + set linebreak($p2) 1 + set bSeenGoto 1 + } + + if {$opcode == "Next" || $opcode=="Prev"} { + for {set i $p2} {$i<$addr} {incr i} { + incr x($i) 2 + } + } + + if {$opcode == "Goto" && $p2<$addr && $op($p2)=="Yield"} { + for {set i [expr $p2+1]} {$i<$addr} {incr i} { + incr x($i) 2 + } + } + + if {$opcode == "Halt" && $comment == "End of coroutine"} { + set linebreak([expr $addr+1]) 1 + } + } + + $db eval "explain $sql" {} { + if {[info exists linebreak($addr)]} { + puts "" + } + set I [string repeat " " $x($addr)] + + set col "" + catch { set col $color($opcode) } + + puts [format {%-4d %s%s%-12.12s%s %-6d %-6d %-6d % -17s %s %s} \ + $addr $I $col $opcode $D $p1 $p2 $p3 $p4 $p5 $comment + ] + } + puts "---- ------------ ------ ------ ------ ---------------- -- -" +} # Show the VDBE program for an SQL statement but omit the Trace # opcode at the beginning. This procedure can be used to prove # that different SQL statements generate exactly the same VDBE code. # Index: test/tkt-4a03edc4c8.test ================================================================== --- test/tkt-4a03edc4c8.test +++ test/tkt-4a03edc4c8.test @@ -29,11 +29,11 @@ catchsql { BEGIN; INSERT INTO t1 VALUES(1, 2); COMMIT; } -} {1 {column b is not unique}} +} {1 {UNIQUE constraint failed: t1.b}} do_test tkt-4a03ed-1.2 { db eval { PRAGMA integrity_check; } } {ok} Index: test/tkt-78e04e52ea.test ================================================================== --- test/tkt-78e04e52ea.test +++ test/tkt-78e04e52ea.test @@ -27,11 +27,11 @@ } {1 {} 2} do_test tkt-78e04-1.1 { catchsql { INSERT INTO ""("") VALUES(1); } -} {1 {column is not unique}} +} {1 {UNIQUE constraint failed: .}} do_test tkt-78e04-1.2 { execsql { PRAGMA table_info(""); } } {0 {} {} 0 {} 0 1 x CHAR(100) 0 {} 0} Index: test/tkt-b1d3a2e531.test ================================================================== --- test/tkt-b1d3a2e531.test +++ test/tkt-b1d3a2e531.test @@ -96,11 +96,11 @@ BEGIN; DELETE FROM pp2; DROP TABLE pp1; DROP TABLE cc1; COMMIT; -} {1 {foreign key constraint failed}} +} {1 {FOREIGN KEY constraint failed}} do_catchsql_test 3.3 { DROP TABLE cc2; COMMIT; } {0 {}} Index: test/tkt1567.test ================================================================== --- test/tkt1567.test +++ test/tkt1567.test @@ -38,14 +38,45 @@ } {} do_test tkt1567-1.4 { catchsql { UPDATE t1 SET a = CASE WHEN rowid<90 THEN substr(a,1,10) ELSE '9999' END; } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: t1.a}} do_test tkt1567-1.5 { execsql { COMMIT; } } {} integrity_check tkt1567-1.6 +do_test tkt1567-2.1 { + execsql { + CREATE TABLE t2(a TEXT PRIMARY KEY, rowid INT) WITHOUT rowid; + } + set bigstr abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + for {set i 0} {$i<100} {incr i} { + set x [format %5d [expr $i*2]] + set sql "INSERT INTO t2 VALUES('$x-$bigstr', $i+1)" + execsql $sql + } +} {} +integrity_check tkt1567-2.2 + +do_test tkt1567-2.3 { + execsql { + BEGIN; + UPDATE t2 SET a = a||'x' WHERE rowid%2==0; + } +} {} +do_test tkt1567-2.4 { + catchsql { + UPDATE t2 SET a = CASE WHEN rowid<90 THEN substr(a,1,10) ELSE '9999' END; + } +} {1 {UNIQUE constraint failed: t2.a}} +do_test tkt1567-2.5 { + execsql { + COMMIT; + } +} {} +integrity_check tkt1567-2.6 + finish_test Index: test/tkt35xx.test ================================================================== --- test/tkt35xx.test +++ test/tkt35xx.test @@ -72,11 +72,11 @@ BEGIN; CREATE TABLE t5(e PRIMARY KEY, f); DROP TABLE t5; INSERT INTO t3(a, b) SELECT c, d FROM t4; } -} {1 {PRIMARY KEY must be unique}} +} {1 {UNIQUE constraint failed: t3.a}} do_test tkt35xx-1.2.3 { # Show that the transaction has not been rolled back. catchsql BEGIN } {1 {cannot start a transaction within a transaction}} do_test tkt35xx-1.2.4 { Index: test/trigger2.test ================================================================== --- test/trigger2.test +++ test/trigger2.test @@ -495,21 +495,21 @@ } {1 2 3} do_test trigger2-6.1b { catchsql { INSERT OR ABORT INTO tbl values (2, 2, 3); } - } {1 {column a is not unique}} + } {1 {UNIQUE constraint failed: tbl.a}} do_test trigger2-6.1c { execsql { SELECT * from tbl; } } {1 2 3} do_test trigger2-6.1d { catchsql { INSERT OR FAIL INTO tbl values (2, 2, 3); } - } {1 {column a is not unique}} + } {1 {UNIQUE constraint failed: tbl.a}} do_test trigger2-6.1e { execsql { SELECT * from tbl; } } {1 2 3 2 2 3} @@ -521,11 +521,11 @@ } {1 2 3 2 0 0} do_test trigger2-6.1g { catchsql { INSERT OR ROLLBACK INTO tbl values (3, 2, 3); } - } {1 {column a is not unique}} + } {1 {UNIQUE constraint failed: tbl.a}} do_test trigger2-6.1h { execsql { SELECT * from tbl; } } {} @@ -549,21 +549,21 @@ } {1 2 10 6 3 4} do_test trigger2-6.2b { catchsql { UPDATE OR ABORT tbl SET a = 4 WHERE a = 1; } - } {1 {column a is not unique}} + } {1 {UNIQUE constraint failed: tbl.a}} do_test trigger2-6.2c { execsql { SELECT * from tbl; } } {1 2 10 6 3 4} do_test trigger2-6.2d { catchsql { UPDATE OR FAIL tbl SET a = 4 WHERE a = 1; } - } {1 {column a is not unique}} + } {1 {UNIQUE constraint failed: tbl.a}} do_test trigger2-6.2e { execsql { SELECT * from tbl; } } {4 2 10 6 3 4} @@ -581,11 +581,11 @@ } {1 3 10 2 3 4} do_test trigger2-6.2g { catchsql { UPDATE OR ROLLBACK tbl SET a = 4 WHERE a = 1; } - } {1 {column a is not unique}} + } {1 {UNIQUE constraint failed: tbl.a}} do_test trigger2-6.2h { execsql { SELECT * from tbl; } } {4 2 3 6 3 4} Index: test/triggerC.test ================================================================== --- test/triggerC.test +++ test/triggerC.test @@ -155,11 +155,11 @@ INSERT INTO t1 VALUES(11,12,13,14,15); } } {} do_test triggerC-1.15 { catchsql { UPDATE OR ROLLBACK t1 SET a=100 } -} {1 {PRIMARY KEY must be unique}} +} {1 {UNIQUE constraint failed: t1.a}} #------------------------------------------------------------------------- # This block of tests, triggerC-2.*, tests that recursive trigger # programs (triggers that fire themselves) work. More specifically, Index: test/unique.test ================================================================== --- test/unique.test +++ test/unique.test @@ -45,22 +45,22 @@ } {0 {}} do_test unique-1.3 { catchsql { INSERT INTO t1(a,b,c) VALUES(1,3,4) } -} {1 {column a is not unique}} -verify_ex_errcode unique-1.3b SQLITE_CONSTRAINT_UNIQUE +} {1 {UNIQUE constraint failed: t1.a}} +verify_ex_errcode unique-1.3b SQLITE_CONSTRAINT_PRIMARYKEY do_test unique-1.4 { execsql { SELECT * FROM t1 ORDER BY a; } } {1 2 3} do_test unique-1.5 { catchsql { INSERT INTO t1(a,b,c) VALUES(3,2,4) } -} {1 {column b is not unique}} +} {1 {UNIQUE constraint failed: t1.b}} verify_ex_errcode unique-1.5b SQLITE_CONSTRAINT_UNIQUE do_test unique-1.6 { execsql { SELECT * FROM t1 ORDER BY a; } @@ -98,11 +98,11 @@ } {0 {1 2 3 4}} do_test unique-2.3 { catchsql { INSERT INTO t2 VALUES(1,5); } -} {1 {column a is not unique}} +} {1 {UNIQUE constraint failed: t2.a}} verify_ex_errcode unique-2.3b SQLITE_CONSTRAINT_UNIQUE do_test unique-2.4 { catchsql { SELECT * FROM t2 ORDER BY a } @@ -125,11 +125,11 @@ } {0 {1 2 1 5 3 4}} do_test unique-2.8 { catchsql { CREATE UNIQUE INDEX i2 ON t2(a); } -} {1 {indexed columns are not unique}} +} {1 {UNIQUE constraint failed: t2.a}} verify_ex_errcode unique-2.8b SQLITE_CONSTRAINT_UNIQUE do_test unique-2.9 { catchsql { CREATE INDEX i2 ON t2(a); } @@ -164,11 +164,11 @@ do_test unique-3.4 { catchsql { INSERT INTO t3(a,b,c,d) VALUES(1,4,3,5); SELECT * FROM t3 ORDER BY a,b,c,d; } -} {1 {columns a, c, d are not unique}} +} {1 {UNIQUE constraint failed: t3.a, t3.c, t3.d}} verify_ex_errcode unique-3.4b SQLITE_CONSTRAINT_UNIQUE integrity_check unique-3.5 # Make sure NULLs are distinct as far as the UNIQUE tests are # concerned. @@ -219,11 +219,11 @@ do_test unique-4.9 { catchsql {CREATE UNIQUE INDEX i4b ON t4(a,b,c)} } {0 {}} do_test unique-4.10 { catchsql {CREATE UNIQUE INDEX i4c ON t4(b)} -} {1 {indexed columns are not unique}} +} {1 {UNIQUE constraint failed: t4.b}} verify_ex_errcode unique-4.10b SQLITE_CONSTRAINT_UNIQUE integrity_check unique-4.99 # Test the error message generation logic. In particular, make sure we # do not overflow the static buffer used to generate the error message. @@ -252,10 +252,10 @@ } {1 2 3 4 5 6} do_test unique-5.2 { catchsql { INSERT INTO t5 VALUES(1,2,3,4,5,6); } -} {1 {columns first_column_with_long_name, second_column_with_long_name, third_column_with_long_name, fourth_column_with_long_name, fifth_column_with_long_name, sixth_column_with_long_name are not unique}} +} {1 {UNIQUE constraint failed: t5.first_column_with_long_name, t5.second_column_with_long_name, t5.third_column_with_long_name, t5.fourth_column_with_long_name, t5.fifth_column_with_long_name, t5.sixth_column_with_long_name}} verify_ex_errcode unique-5.2b SQLITE_CONSTRAINT_UNIQUE finish_test Index: test/update.test ================================================================== --- test/update.test +++ test/update.test @@ -450,11 +450,11 @@ do_test update-10.3 { catchsql { UPDATE t1 SET a=1, e=10 WHERE f=7; SELECT * FROM t1; } -} {1 {PRIMARY KEY must be unique}} +} {1 {UNIQUE constraint failed: t1.a}} do_test update-10.4 { catchsql { SELECT * FROM t1; } } {0 {1 2 3 4 9 6 2 3 4 4 6 7}} @@ -467,11 +467,11 @@ do_test update-10.6 { catchsql { UPDATE t1 SET b=2, e=12 WHERE f=7; SELECT * FROM t1; } -} {1 {column b is not unique}} +} {1 {UNIQUE constraint failed: t1.b}} do_test update-10.7 { catchsql { SELECT * FROM t1; } } {0 {1 2 3 4 11 6 2 3 4 4 6 7}} @@ -484,11 +484,11 @@ do_test update-10.9 { catchsql { UPDATE t1 SET c=3, d=4, e=14 WHERE f=7; SELECT * FROM t1; } -} {1 {columns c, d are not unique}} +} {1 {UNIQUE constraint failed: t1.c, t1.d}} do_test update-10.10 { catchsql { SELECT * FROM t1; } } {0 {1 2 3 4 13 6 2 3 4 4 6 7}} Index: test/vtab1.test ================================================================== --- test/vtab1.test +++ test/vtab1.test @@ -1038,23 +1038,23 @@ } {} # First test outside of a transaction. do_test vtab1.12-2 { catchsql { INSERT INTO echo_c SELECT * FROM b; } -} {1 {echo-vtab-error: column a is not unique}} +} {1 {echo-vtab-error: UNIQUE constraint failed: c.a}} do_test vtab1.12-2.1 { sqlite3_errmsg db -} {echo-vtab-error: column a is not unique} +} {echo-vtab-error: UNIQUE constraint failed: c.a} do_test vtab1.12-3 { execsql { SELECT * FROM c } } {3 G H} # Now the real test - wrapped in a transaction. do_test vtab1.12-4 { execsql {BEGIN} catchsql { INSERT INTO echo_c SELECT * FROM b; } -} {1 {echo-vtab-error: column a is not unique}} +} {1 {echo-vtab-error: UNIQUE constraint failed: c.a}} do_test vtab1.12-5 { execsql { SELECT * FROM c } } {3 G H} do_test vtab1.12-6 { execsql { COMMIT } Index: test/wal.test ================================================================== --- test/wal.test +++ test/wal.test @@ -320,11 +320,11 @@ BEGIN; INSERT INTO t2 VALUES(3, 4); INSERT INTO t3 VALUES('abc'); } catchsql { INSERT INTO t3 VALUES('abc') } -} {1 {column x is not unique}} +} {1 {UNIQUE constraint failed: t3.x}} do_test wal-5.5 { execsql { COMMIT; SELECT * FROM t2; } Index: test/where8.test ================================================================== --- test/where8.test +++ test/where8.test @@ -10,11 +10,10 @@ #*********************************************************************** # This file implements regression tests for SQLite library. The focus # is testing of where.c. More specifically, the focus is the optimization # of WHERE clauses that feature the OR operator. # -# $Id: where8.test,v 1.9 2009/07/31 06:14:52 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl # Test organization: @@ -746,7 +745,16 @@ i=1 AND j=2 AND k=3 AND l=4 AND m=5 AND n=6 AND o=7 AND (p = 1 OR p = 2 OR p = 3) ) } } {1 {}} + +# The OR optimization and WITHOUT ROWID +# +do_execsql_test where8-6.1 { + CREATE TABLE t600(a PRIMARY KEY, b) WITHOUT rowid; + CREATE INDEX t600b ON t600(b); + INSERT INTO t600 VALUES('state','screen'),('exact','dolphin'),('green','mercury'); + SELECT a, b, '|' FROM t600 WHERE a=='state' OR b='mercury' ORDER BY +a; +} {green mercury | state screen |} finish_test ADDED test/without_rowid1.test Index: test/without_rowid1.test ================================================================== --- /dev/null +++ test/without_rowid1.test @@ -0,0 +1,203 @@ +# 2013-10-30 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# This file implements regression tests for SQLite library. The +# focus of this file is testing WITHOUT ROWID tables. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix without_rowid1 + +# Create and query a WITHOUT ROWID table. +# +do_execsql_test without_rowid1-1.0 { + CREATE TABLE t1(a,b,c,d, PRIMARY KEY(c,a)) WITHOUT ROWID; + CREATE INDEX t1bd ON t1(b, d); + INSERT INTO t1 VALUES('journal','sherman','ammonia','helena'); + INSERT INTO t1 VALUES('dynamic','juliet','flipper','command'); + INSERT INTO t1 VALUES('journal','sherman','gamma','patriot'); + INSERT INTO t1 VALUES('arctic','sleep','ammonia','helena'); + SELECT *, '|' FROM t1 ORDER BY c, a; +} {arctic sleep ammonia helena | journal sherman ammonia helena | dynamic juliet flipper command | journal sherman gamma patriot |} + +integrity_check without_rowid1-1.0ic + +do_execsql_test without_rowid1-1.1 { + SELECT *, '|' FROM t1 ORDER BY +c, a; +} {arctic sleep ammonia helena | journal sherman ammonia helena | dynamic juliet flipper command | journal sherman gamma patriot |} + +do_execsql_test without_rowid1-1.2 { + SELECT *, '|' FROM t1 ORDER BY c DESC, a DESC; +} {journal sherman gamma patriot | dynamic juliet flipper command | journal sherman ammonia helena | arctic sleep ammonia helena |} + +do_execsql_test without_rowid1-1.11 { + SELECT *, '|' FROM t1 ORDER BY b, d; +} {dynamic juliet flipper command | journal sherman ammonia helena | journal sherman gamma patriot | arctic sleep ammonia helena |} + +do_execsql_test without_rowid1-1.12 { + SELECT *, '|' FROM t1 ORDER BY +b, d; +} {dynamic juliet flipper command | journal sherman ammonia helena | journal sherman gamma patriot | arctic sleep ammonia helena |} + +# Trying to insert a duplicate PRIMARY KEY fails. +# +do_test without_rowid1-1.21 { + catchsql { + INSERT INTO t1 VALUES('dynamic','phone','flipper','harvard'); + } +} {1 {UNIQUE constraint failed: t1.c, t1.a}} + +# REPLACE INTO works, however. +# +do_execsql_test without_rowid1-1.22 { + REPLACE INTO t1 VALUES('dynamic','phone','flipper','harvard'); + SELECT *, '|' FROM t1 ORDER BY c, a; +} {arctic sleep ammonia helena | journal sherman ammonia helena | dynamic phone flipper harvard | journal sherman gamma patriot |} + +do_execsql_test without_rowid1-1.23 { + SELECT *, '|' FROM t1 ORDER BY b, d; +} {dynamic phone flipper harvard | journal sherman ammonia helena | journal sherman gamma patriot | arctic sleep ammonia helena |} + +# UPDATE statements. +# +do_execsql_test without_rowid1-1.31 { + UPDATE t1 SET d=3.1415926 WHERE a='journal'; + SELECT *, '|' FROM t1 ORDER BY c, a; +} {arctic sleep ammonia helena | journal sherman ammonia 3.1415926 | dynamic phone flipper harvard | journal sherman gamma 3.1415926 |} +do_execsql_test without_rowid1-1.32 { + SELECT *, '|' FROM t1 ORDER BY b, d; +} {dynamic phone flipper harvard | journal sherman ammonia 3.1415926 | journal sherman gamma 3.1415926 | arctic sleep ammonia helena |} + +do_execsql_test without_rowid1-1.35 { + UPDATE t1 SET a=1250 WHERE b='phone'; + SELECT *, '|' FROM t1 ORDER BY c, a; +} {arctic sleep ammonia helena | journal sherman ammonia 3.1415926 | 1250 phone flipper harvard | journal sherman gamma 3.1415926 |} +integrity_check without_rowid1-1.36 + +do_execsql_test without_rowid1-1.37 { + SELECT *, '|' FROM t1 ORDER BY b, d; +} {1250 phone flipper harvard | journal sherman ammonia 3.1415926 | journal sherman gamma 3.1415926 | arctic sleep ammonia helena |} + +do_execsql_test without_rowid1-1.40 { + VACUUM; + SELECT *, '|' FROM t1 ORDER BY b, d; +} {1250 phone flipper harvard | journal sherman ammonia 3.1415926 | journal sherman gamma 3.1415926 | arctic sleep ammonia helena |} +integrity_check without_rowid1-1.41 + +# Verify that ANALYZE works +# +do_execsql_test without_rowid1-1.50 { + ANALYZE; + SELECT * FROM sqlite_stat1 ORDER BY idx; +} {t1 t1 {4 2 1} t1 t1bd {4 2 2}} +ifcapable stat3 { + do_execsql_test without_rowid1-1.51 { + SELECT DISTINCT tbl, idx FROM sqlite_stat3 ORDER BY idx; + } {t1 t1 t1 t1bd} +} +ifcapable stat4 { + do_execsql_test without_rowid1-1.52 { + SELECT DISTINCT tbl, idx FROM sqlite_stat4 ORDER BY idx; + } {t1 t1 t1 t1bd} +} + +#---------- + +do_execsql_test 2.1.1 { + CREATE TABLE t4 (a COLLATE nocase PRIMARY KEY, b) WITHOUT ROWID; + INSERT INTO t4 VALUES('abc', 'def'); + SELECT * FROM t4; +} {abc def} +do_execsql_test 2.1.2 { + UPDATE t4 SET a = 'ABC'; + SELECT * FROM t4; +} {ABC def} + +do_execsql_test 2.2.1 { + DROP TABLE t4; + CREATE TABLE t4 (b, a COLLATE nocase PRIMARY KEY) WITHOUT ROWID; + INSERT INTO t4(a, b) VALUES('abc', 'def'); + SELECT * FROM t4; +} {def abc} + +do_execsql_test 2.2.2 { + UPDATE t4 SET a = 'ABC', b = 'xyz'; + SELECT * FROM t4; +} {xyz ABC} + +do_execsql_test 2.3.1 { + CREATE TABLE t5 (a, b, PRIMARY KEY(b, a)) WITHOUT ROWID; + INSERT INTO t5(a, b) VALUES('abc', 'def'); + UPDATE t5 SET a='abc', b='def'; +} {} + +do_execsql_test 2.4.1 { + CREATE TABLE t6 ( + a COLLATE nocase, b, c UNIQUE, PRIMARY KEY(b, a) + ) WITHOUT ROWID; + + INSERT INTO t6(a, b, c) VALUES('abc', 'def', 'ghi'); + UPDATE t6 SET a='ABC', c='ghi'; +} {} + +do_execsql_test 2.4.2 { + SELECT * FROM t6 ORDER BY b, a; + SELECT * FROM t6 ORDER BY c; +} {ABC def ghi ABC def ghi} + +#------------------------------------------------------------------------- +# Unless the destination table is completely empty, the xfer optimization +# is disabled for WITHOUT ROWID tables. The following tests check for +# some problems that might occur if this were not the case. +# +reset_db +do_execsql_test 3.1.1 { + CREATE TABLE t1(a, b, PRIMARY KEY(a)) WITHOUT ROWID; + CREATE UNIQUE INDEX i1 ON t1(b); + + CREATE TABLE t2(a, b, PRIMARY KEY(a)) WITHOUT ROWID; + CREATE UNIQUE INDEX i2 ON t2(b); + + INSERT INTO t1 VALUES('one', 'two'); + INSERT INTO t2 VALUES('three', 'two'); +} + +do_execsql_test 3.1.2 { + INSERT OR REPLACE INTO t1 SELECT * FROM t2; + SELECT * FROM t1; +} {three two} + +do_execsql_test 3.1.3 { + DELETE FROM t1; + INSERT INTO t1 SELECT * FROM t2; + SELECT * FROM t1; +} {three two} + +do_catchsql_test 3.1.4 { + INSERT INTO t2 VALUES('four', 'four'); + INSERT INTO t2 VALUES('six', 'two'); + INSERT INTO t1 SELECT * FROM t2; +} {1 {UNIQUE constraint failed: t2.b}} + +do_execsql_test 3.1.5 { + CREATE TABLE t3(a PRIMARY KEY); + CREATE TABLE t4(a PRIMARY KEY); + + INSERT INTO t4 VALUES('i'); + INSERT INTO t4 VALUES('ii'); + INSERT INTO t4 VALUES('iii'); + + INSERT INTO t3 SELECT * FROM t4; + SELECT * FROM t3; +} {i ii iii} + +finish_test + ADDED test/without_rowid2.test Index: test/without_rowid2.test ================================================================== --- /dev/null +++ test/without_rowid2.test @@ -0,0 +1,125 @@ +# 2013-11-02 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# This file implements regression tests for SQLite library. The +# focus of this file is testing WITHOUT ROWID tables, and especially +# FOREIGN KEY constraints. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +ifcapable {!foreignkey} { + finish_test + return +} + +# Create a table and some data to work with. +# +do_test without_rowid2-1.0 { + execsql { + CREATE TABLE t1( + a INT PRIMARY KEY, + b INT + REFERENCES t1 ON DELETE CASCADE + REFERENCES t2, + c TEXT, + FOREIGN KEY (b,c) REFERENCES t2(x,y) ON UPDATE CASCADE + ) WITHOUT rowid; + } +} {} +do_test without_rowid2-1.1 { + execsql { + CREATE TABLE t2( + x INT PRIMARY KEY, + y TEXT + ) WITHOUT rowid; + } +} {} +do_test without_rowid2-1.2 { + execsql { + CREATE TABLE t3( + a INT REFERENCES t2, + b INT REFERENCES t1, + FOREIGN KEY (a,b) REFERENCES t2(x,y) + ); + } +} {} + +do_test without_rowid2-2.1 { + execsql { + CREATE TABLE t4(a int primary key) WITHOUT rowid; + CREATE TABLE t5(x references t4); + CREATE TABLE t6(x references t4); + CREATE TABLE t7(x references t4); + CREATE TABLE t8(x references t4); + CREATE TABLE t9(x references t4); + CREATE TABLE t10(x references t4); + DROP TABLE t7; + DROP TABLE t9; + DROP TABLE t5; + DROP TABLE t8; + DROP TABLE t6; + DROP TABLE t10; + } +} {} + +do_test without_rowid2-3.1 { + execsql { + CREATE TABLE t5(a PRIMARY KEY, b, c) WITHOUT rowid; + CREATE TABLE t6( + d REFERENCES t5, + e REFERENCES t5(c) + ); + PRAGMA foreign_key_list(t6); + } +} [concat \ + {0 0 t5 e c {NO ACTION} {NO ACTION} NONE} \ + {1 0 t5 d {} {NO ACTION} {NO ACTION} NONE} \ +] +do_test without_rowid2-3.2 { + execsql { + CREATE TABLE t7(d, e, f, + FOREIGN KEY (d, e) REFERENCES t5(a, b) + ); + PRAGMA foreign_key_list(t7); + } +} [concat \ + {0 0 t5 d a {NO ACTION} {NO ACTION} NONE} \ + {0 1 t5 e b {NO ACTION} {NO ACTION} NONE} \ +] +do_test without_rowid2-3.3 { + execsql { + CREATE TABLE t8(d, e, f, + FOREIGN KEY (d, e) REFERENCES t5 ON DELETE CASCADE ON UPDATE SET NULL + ); + PRAGMA foreign_key_list(t8); + } +} [concat \ + {0 0 t5 d {} {SET NULL} CASCADE NONE} \ + {0 1 t5 e {} {SET NULL} CASCADE NONE} \ +] +do_test without_rowid2-3.4 { + execsql { + CREATE TABLE t9(d, e, f, + FOREIGN KEY (d, e) REFERENCES t5 ON DELETE CASCADE ON UPDATE SET DEFAULT + ); + PRAGMA foreign_key_list(t9); + } +} [concat \ + {0 0 t5 d {} {SET DEFAULT} CASCADE NONE} \ + {0 1 t5 e {} {SET DEFAULT} CASCADE NONE} \ +] +do_test without_rowid2-3.5 { + sqlite3_db_status db DBSTATUS_DEFERRED_FKS 0 +} {0 0 0} + +finish_test ADDED test/without_rowid3.test Index: test/without_rowid3.test ================================================================== --- /dev/null +++ test/without_rowid3.test @@ -0,0 +1,2084 @@ +# 2013-11-02 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# +# This file implements tests for foreign keys on WITHOUT ROWID +# tables. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +ifcapable {!foreignkey||!trigger} { + finish_test + return +} + +#------------------------------------------------------------------------- +# Test structure: +# +# without_rowid3-1.*: Simple tests to check that immediate and deferred foreign key +# constraints work when not inside a transaction. +# +# without_rowid3-2.*: Tests to verify that deferred foreign keys work inside +# explicit transactions (i.e that processing really is deferred). +# +# without_rowid3-3.*: Tests that a statement transaction is rolled back if an +# immediate foreign key constraint is violated. +# +# without_rowid3-4.*: Test that FK actions may recurse even when recursive triggers +# are disabled. +# +# without_rowid3-5.*: Check that if foreign-keys are enabled, it is not possible +# to write to an FK column using the incremental blob API. +# +# without_rowid3-6.*: Test that FK processing is automatically disabled when +# running VACUUM. +# +# without_rowid3-7.*: Test using an IPK as the key in the child (referencing) table. +# +# without_rowid3-8.*: Test that enabling/disabling foreign key support while a +# transaction is active is not possible. +# +# without_rowid3-9.*: Test SET DEFAULT actions. +# +# without_rowid3-10.*: Test errors. +# +# without_rowid3-11.*: Test CASCADE actions. +# +# without_rowid3-12.*: Test RESTRICT actions. +# +# without_rowid3-13.*: Test that FK processing is performed when a row is REPLACED by +# an UPDATE or INSERT statement. +# +# without_rowid3-14.*: Test the ALTER TABLE and DROP TABLE commands. +# +# without_rowid3-15.*: Test that if there are no (known) outstanding foreign key +# constraint violations in the database, inserting into a parent +# table or deleting from a child table does not cause SQLite +# to check if this has repaired an outstanding violation. +# +# without_rowid3-16.*: Test that rows that refer to themselves may be inserted, +# updated and deleted. +# +# without_rowid3-17.*: Test that the "count_changes" pragma does not interfere with +# FK constraint processing. +# +# without_rowid3-18.*: Test that the authorization callback is invoked when processing +# FK constraints. +# +# without_rowid3-20.*: Test that ON CONFLICT clauses specified as part of statements +# do not affect the operation of FK constraints. +# +# without_rowid3-genfkey.*: Tests that were used with the shell tool .genfkey +# command. Recycled to test the built-in implementation. +# +# without_rowid3-dd08e5.*: Tests to verify that ticket dd08e5a988d00decc4a543daa8d +# has been fixed. +# + + +execsql { PRAGMA foreign_keys = on } + +set FkeySimpleSchema { + PRAGMA foreign_keys = on; + CREATE TABLE t1(a PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE t2(c REFERENCES t1(a) /D/ , d); + + CREATE TABLE t3(a PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE t4(c REFERENCES t3 /D/, d); + + CREATE TABLE t7(a, b INT PRIMARY KEY) WITHOUT rowid; + CREATE TABLE t8(c REFERENCES t7 /D/, d); + + CREATE TABLE t9(a REFERENCES nosuchtable, b); + CREATE TABLE t10(a REFERENCES t9(c) /D/, b); +} + + +set FkeySimpleTests { + 1.1 "INSERT INTO t2 VALUES(1, 3)" {1 {FOREIGN KEY constraint failed}} + 1.2 "INSERT INTO t1 VALUES(1, 2)" {0 {}} + 1.3 "INSERT INTO t2 VALUES(1, 3)" {0 {}} + 1.4 "INSERT INTO t2 VALUES(2, 4)" {1 {FOREIGN KEY constraint failed}} + 1.5 "INSERT INTO t2 VALUES(NULL, 4)" {0 {}} + 1.6 "UPDATE t2 SET c=2 WHERE d=4" {1 {FOREIGN KEY constraint failed}} + 1.7 "UPDATE t2 SET c=1 WHERE d=4" {0 {}} + 1.9 "UPDATE t2 SET c=1 WHERE d=4" {0 {}} + 1.10 "UPDATE t2 SET c=NULL WHERE d=4" {0 {}} + 1.11 "DELETE FROM t1 WHERE a=1" {1 {FOREIGN KEY constraint failed}} + 1.12 "UPDATE t1 SET a = 2" {1 {FOREIGN KEY constraint failed}} + 1.13 "UPDATE t1 SET a = 1" {0 {}} + + 2.1 "INSERT INTO t4 VALUES(1, 3)" {1 {FOREIGN KEY constraint failed}} + 2.2 "INSERT INTO t3 VALUES(1, 2)" {0 {}} + 2.3 "INSERT INTO t4 VALUES(1, 3)" {0 {}} + + 4.1 "INSERT INTO t8 VALUES(1, 3)" {1 {FOREIGN KEY constraint failed}} + 4.2 "INSERT INTO t7 VALUES(2, 1)" {0 {}} + 4.3 "INSERT INTO t8 VALUES(1, 3)" {0 {}} + 4.4 "INSERT INTO t8 VALUES(2, 4)" {1 {FOREIGN KEY constraint failed}} + 4.5 "INSERT INTO t8 VALUES(NULL, 4)" {0 {}} + 4.6 "UPDATE t8 SET c=2 WHERE d=4" {1 {FOREIGN KEY constraint failed}} + 4.7 "UPDATE t8 SET c=1 WHERE d=4" {0 {}} + 4.9 "UPDATE t8 SET c=1 WHERE d=4" {0 {}} + 4.10 "UPDATE t8 SET c=NULL WHERE d=4" {0 {}} + 4.11 "DELETE FROM t7 WHERE b=1" {1 {FOREIGN KEY constraint failed}} + 4.12 "UPDATE t7 SET b = 2" {1 {FOREIGN KEY constraint failed}} + 4.13 "UPDATE t7 SET b = 1" {0 {}} + 4.14 "INSERT INTO t8 VALUES('a', 'b')" {1 {FOREIGN KEY constraint failed}} + 4.15 "UPDATE t7 SET b = 5" {1 {FOREIGN KEY constraint failed}} + 4.17 "UPDATE t7 SET a = 10" {0 {}} + + 5.1 "INSERT INTO t9 VALUES(1, 3)" {1 {no such table: main.nosuchtable}} + 5.2 "INSERT INTO t10 VALUES(1, 3)" + {1 {foreign key mismatch - "t10" referencing "t9"}} +} + +do_test without_rowid3-1.1.0 { + execsql [string map {/D/ {}} $FkeySimpleSchema] +} {} +foreach {tn zSql res} $FkeySimpleTests { + do_test without_rowid3-1.1.$tn.1 { catchsql $zSql } $res + do_test without_rowid3-1.1.$tn.2 { execsql {PRAGMA foreign_key_check(t1)} } {} + do_test without_rowid3-1.1.$tn.3 { execsql {PRAGMA foreign_key_check(t2)} } {} + do_test without_rowid3-1.1.$tn.4 { execsql {PRAGMA foreign_key_check(t3)} } {} + do_test without_rowid3-1.1.$tn.5 { execsql {PRAGMA foreign_key_check(t4)} } {} + do_test without_rowid3-1.1.$tn.6 { execsql {PRAGMA foreign_key_check(t7)} } {} + do_test without_rowid3-1.1.$tn.7 { execsql {PRAGMA foreign_key_check(t8)} } {} +} +drop_all_tables + +do_test without_rowid3-1.2.0 { + execsql [string map {/D/ {DEFERRABLE INITIALLY DEFERRED}} $FkeySimpleSchema] +} {} +foreach {tn zSql res} $FkeySimpleTests { + do_test without_rowid3-1.2.$tn { catchsql $zSql } $res + do_test without_rowid3-1.2.$tn.2 { execsql {PRAGMA foreign_key_check(t1)} } {} + do_test without_rowid3-1.2.$tn.3 { execsql {PRAGMA foreign_key_check(t2)} } {} + do_test without_rowid3-1.2.$tn.4 { execsql {PRAGMA foreign_key_check(t3)} } {} + do_test without_rowid3-1.2.$tn.5 { execsql {PRAGMA foreign_key_check(t4)} } {} + do_test without_rowid3-1.2.$tn.6 { execsql {PRAGMA foreign_key_check(t7)} } {} + do_test without_rowid3-1.2.$tn.7 { execsql {PRAGMA foreign_key_check(t8)} } {} +} +drop_all_tables + +do_test without_rowid3-1.3.0 { + execsql [string map {/D/ {}} $FkeySimpleSchema] + execsql { PRAGMA count_changes = 1 } +} {} +foreach {tn zSql res} $FkeySimpleTests { + if {$res == "0 {}"} { set res {0 1} } + do_test without_rowid3-1.3.$tn { catchsql $zSql } $res + do_test without_rowid3-1.3.$tn.2 { execsql {PRAGMA foreign_key_check(t1)} } {} + do_test without_rowid3-1.3.$tn.3 { execsql {PRAGMA foreign_key_check(t2)} } {} + do_test without_rowid3-1.3.$tn.4 { execsql {PRAGMA foreign_key_check(t3)} } {} + do_test without_rowid3-1.3.$tn.5 { execsql {PRAGMA foreign_key_check(t4)} } {} + do_test without_rowid3-1.3.$tn.6 { execsql {PRAGMA foreign_key_check(t7)} } {} + do_test without_rowid3-1.3.$tn.7 { execsql {PRAGMA foreign_key_check(t8)} } {} +} +execsql { PRAGMA count_changes = 0 } +drop_all_tables + +do_test without_rowid3-1.4.0 { + execsql [string map {/D/ {}} $FkeySimpleSchema] + execsql { PRAGMA count_changes = 1 } +} {} +foreach {tn zSql res} $FkeySimpleTests { + if {$res == "0 {}"} { set res {0 1} } + execsql BEGIN + do_test without_rowid3-1.4.$tn { catchsql $zSql } $res + execsql COMMIT +} +execsql { PRAGMA count_changes = 0 } +drop_all_tables + +# Special test: When the parent key is an IPK, make sure the affinity of +# the IPK is not applied to the child key value before it is inserted +# into the child table. +do_test without_rowid3-1.5.1 { + execsql { + CREATE TABLE i(i INT PRIMARY KEY) WITHOUT rowid; + CREATE TABLE j(j REFERENCES i); + INSERT INTO i VALUES(35); + INSERT INTO j VALUES('35.0'); + SELECT j, typeof(j) FROM j; + } +} {35.0 text} +do_test without_rowid3-1.5.2 { + catchsql { DELETE FROM i } +} {1 {FOREIGN KEY constraint failed}} + +# Same test using a regular primary key with integer affinity. +drop_all_tables +do_test without_rowid3-1.6.1 { + execsql { + CREATE TABLE i(i INT UNIQUE); + CREATE TABLE j(j REFERENCES i(i)); + INSERT INTO i VALUES('35.0'); + INSERT INTO j VALUES('35.0'); + SELECT j, typeof(j) FROM j; + SELECT i, typeof(i) FROM i; + } +} {35.0 text 35 integer} +do_test without_rowid3-1.6.2 { + catchsql { DELETE FROM i } +} {1 {FOREIGN KEY constraint failed}} + +# Use a collation sequence on the parent key. +drop_all_tables +do_test without_rowid3-1.7.1 { + execsql { + CREATE TABLE i(i TEXT COLLATE nocase PRIMARY KEY) WITHOUT rowid; + CREATE TABLE j(j TEXT COLLATE binary REFERENCES i(i)); + INSERT INTO i VALUES('SQLite'); + INSERT INTO j VALUES('sqlite'); + } + catchsql { DELETE FROM i } +} {1 {FOREIGN KEY constraint failed}} + +# Use the parent key collation even if it is default and the child key +# has an explicit value. +drop_all_tables +do_test without_rowid3-1.7.2 { + execsql { + CREATE TABLE i(i TEXT PRIMARY KEY) WITHOUT rowid; -- Colseq is "BINARY" + CREATE TABLE j(j TEXT COLLATE nocase REFERENCES i(i)); + INSERT INTO i VALUES('SQLite'); + } + catchsql { INSERT INTO j VALUES('sqlite') } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-1.7.3 { + execsql { + INSERT INTO i VALUES('sqlite'); + INSERT INTO j VALUES('sqlite'); + DELETE FROM i WHERE i = 'SQLite'; + } + catchsql { DELETE FROM i WHERE i = 'sqlite' } +} {1 {FOREIGN KEY constraint failed}} + +#------------------------------------------------------------------------- +# This section (test cases without_rowid3-2.*) contains tests to check that the +# deferred foreign key constraint logic works. +# +proc without_rowid3-2-test {tn nocommit sql {res {}}} { + if {$res eq "FKV"} { + set expected {1 {FOREIGN KEY constraint failed}} + } else { + set expected [list 0 $res] + } + do_test without_rowid3-2.$tn [list catchsql $sql] $expected + if {$nocommit} { + do_test without_rowid3-2.${tn}c { + catchsql COMMIT + } {1 {FOREIGN KEY constraint failed}} + } +} + +without_rowid3-2-test 1 0 { + CREATE TABLE node( + nodeid PRIMARY KEY, + parent REFERENCES node DEFERRABLE INITIALLY DEFERRED + ) WITHOUT rowid; + CREATE TABLE leaf( + cellid PRIMARY KEY, + parent REFERENCES node DEFERRABLE INITIALLY DEFERRED + ) WITHOUT rowid; +} + +without_rowid3-2-test 1 0 "INSERT INTO node VALUES(1, 0)" FKV +without_rowid3-2-test 2 0 "BEGIN" +without_rowid3-2-test 3 1 "INSERT INTO node VALUES(1, 0)" +without_rowid3-2-test 4 0 "UPDATE node SET parent = NULL" +without_rowid3-2-test 5 0 "COMMIT" +without_rowid3-2-test 6 0 "SELECT * FROM node" {1 {}} + +without_rowid3-2-test 7 0 "BEGIN" +without_rowid3-2-test 8 1 "INSERT INTO leaf VALUES('a', 2)" +without_rowid3-2-test 9 1 "INSERT INTO node VALUES(2, 0)" +without_rowid3-2-test 10 0 "UPDATE node SET parent = 1 WHERE nodeid = 2" +without_rowid3-2-test 11 0 "COMMIT" +without_rowid3-2-test 12 0 "SELECT * FROM node" {1 {} 2 1} +without_rowid3-2-test 13 0 "SELECT * FROM leaf" {a 2} + +without_rowid3-2-test 14 0 "BEGIN" +without_rowid3-2-test 15 1 "DELETE FROM node WHERE nodeid = 2" +without_rowid3-2-test 16 0 "INSERT INTO node VALUES(2, NULL)" +without_rowid3-2-test 17 0 "COMMIT" +without_rowid3-2-test 18 0 "SELECT * FROM node" {1 {} 2 {}} +without_rowid3-2-test 19 0 "SELECT * FROM leaf" {a 2} + +without_rowid3-2-test 20 0 "BEGIN" +without_rowid3-2-test 21 0 "INSERT INTO leaf VALUES('b', 1)" +without_rowid3-2-test 22 0 "SAVEPOINT save" +without_rowid3-2-test 23 0 "DELETE FROM node WHERE nodeid = 1" +without_rowid3-2-test 24 0 "ROLLBACK TO save" +without_rowid3-2-test 25 0 "COMMIT" +without_rowid3-2-test 26 0 "SELECT * FROM node" {1 {} 2 {}} +without_rowid3-2-test 27 0 "SELECT * FROM leaf" {a 2 b 1} + +without_rowid3-2-test 28 0 "BEGIN" +without_rowid3-2-test 29 0 "INSERT INTO leaf VALUES('c', 1)" +without_rowid3-2-test 30 0 "SAVEPOINT save" +without_rowid3-2-test 31 0 "DELETE FROM node WHERE nodeid = 1" +without_rowid3-2-test 32 1 "RELEASE save" +without_rowid3-2-test 33 1 "DELETE FROM leaf WHERE cellid = 'b'" +without_rowid3-2-test 34 0 "DELETE FROM leaf WHERE cellid = 'c'" +without_rowid3-2-test 35 0 "COMMIT" +without_rowid3-2-test 36 0 "SELECT * FROM node" {2 {}} +without_rowid3-2-test 37 0 "SELECT * FROM leaf" {a 2} + +without_rowid3-2-test 38 0 "SAVEPOINT outer" +without_rowid3-2-test 39 1 "INSERT INTO leaf VALUES('d', 3)" +without_rowid3-2-test 40 1 "RELEASE outer" FKV +without_rowid3-2-test 41 1 "INSERT INTO leaf VALUES('e', 3)" +without_rowid3-2-test 42 0 "INSERT INTO node VALUES(3, 2)" +without_rowid3-2-test 43 0 "RELEASE outer" + +without_rowid3-2-test 44 0 "SAVEPOINT outer" +without_rowid3-2-test 45 1 "DELETE FROM node WHERE nodeid=3" +without_rowid3-2-test 47 0 "INSERT INTO node VALUES(3, 2)" +without_rowid3-2-test 48 0 "ROLLBACK TO outer" +without_rowid3-2-test 49 0 "RELEASE outer" + +without_rowid3-2-test 50 0 "SAVEPOINT outer" +without_rowid3-2-test 51 1 "INSERT INTO leaf VALUES('f', 4)" +without_rowid3-2-test 52 1 "SAVEPOINT inner" +without_rowid3-2-test 53 1 "INSERT INTO leaf VALUES('g', 4)" +without_rowid3-2-test 54 1 "RELEASE outer" FKV +without_rowid3-2-test 55 1 "ROLLBACK TO inner" +without_rowid3-2-test 56 0 "COMMIT" FKV +without_rowid3-2-test 57 0 "INSERT INTO node VALUES(4, NULL)" +without_rowid3-2-test 58 0 "RELEASE outer" +without_rowid3-2-test 59 0 "SELECT * FROM node" {2 {} 3 2 4 {}} +without_rowid3-2-test 60 0 "SELECT * FROM leaf" {a 2 d 3 e 3 f 4} + +# The following set of tests check that if a statement that affects +# multiple rows violates some foreign key constraints, then strikes a +# constraint that causes the statement-transaction to be rolled back, +# the deferred constraint counter is correctly reset to the value it +# had before the statement-transaction was opened. +# +without_rowid3-2-test 61 0 "BEGIN" +without_rowid3-2-test 62 0 "DELETE FROM leaf" +without_rowid3-2-test 63 0 "DELETE FROM node" +without_rowid3-2-test 64 1 "INSERT INTO leaf VALUES('a', 1)" +without_rowid3-2-test 65 1 "INSERT INTO leaf VALUES('b', 2)" +without_rowid3-2-test 66 1 "INSERT INTO leaf VALUES('c', 1)" +do_test without_rowid3-2-test-67 { + catchsql "INSERT INTO node SELECT parent, 3 FROM leaf" +} {1 {UNIQUE constraint failed: node.nodeid}} +without_rowid3-2-test 68 0 "COMMIT" FKV +without_rowid3-2-test 69 1 "INSERT INTO node VALUES(1, NULL)" +without_rowid3-2-test 70 0 "INSERT INTO node VALUES(2, NULL)" +without_rowid3-2-test 71 0 "COMMIT" + +without_rowid3-2-test 72 0 "BEGIN" +without_rowid3-2-test 73 1 "DELETE FROM node" +without_rowid3-2-test 74 0 "INSERT INTO node(nodeid) SELECT DISTINCT parent FROM leaf" +without_rowid3-2-test 75 0 "COMMIT" + +#------------------------------------------------------------------------- +# Test cases without_rowid3-3.* test that a program that executes foreign key +# actions (CASCADE, SET DEFAULT, SET NULL etc.) or tests FK constraints +# opens a statement transaction if required. +# +# without_rowid3-3.1.*: Test UPDATE statements. +# without_rowid3-3.2.*: Test DELETE statements. +# +drop_all_tables +do_test without_rowid3-3.1.1 { + execsql { + CREATE TABLE ab(a PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE cd( + c PRIMARY KEY REFERENCES ab ON UPDATE CASCADE ON DELETE CASCADE, + d + ) WITHOUT rowid; + CREATE TABLE ef( + e REFERENCES cd ON UPDATE CASCADE, + f, CHECK (e!=5) + ); + } +} {} +do_test without_rowid3-3.1.2 { + execsql { + INSERT INTO ab VALUES(1, 'b'); + INSERT INTO cd VALUES(1, 'd'); + INSERT INTO ef VALUES(1, 'e'); + } +} {} +do_test without_rowid3-3.1.3 { + catchsql { UPDATE ab SET a = 5 } +} {1 {CHECK constraint failed: ef}} +do_test without_rowid3-3.1.4 { + execsql { SELECT * FROM ab } +} {1 b} +do_test without_rowid3-3.1.4 { + execsql BEGIN; + catchsql { UPDATE ab SET a = 5 } +} {1 {CHECK constraint failed: ef}} +do_test without_rowid3-3.1.5 { + execsql COMMIT; + execsql { SELECT * FROM ab; SELECT * FROM cd; SELECT * FROM ef } +} {1 b 1 d 1 e} + +do_test without_rowid3-3.2.1 { + execsql BEGIN; + catchsql { DELETE FROM ab } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-3.2.2 { + execsql COMMIT + execsql { SELECT * FROM ab; SELECT * FROM cd; SELECT * FROM ef } +} {1 b 1 d 1 e} + +#------------------------------------------------------------------------- +# Test cases without_rowid3-4.* test that recursive foreign key actions +# (i.e. CASCADE) are allowed even if recursive triggers are disabled. +# +drop_all_tables +do_test without_rowid3-4.1 { + execsql { + CREATE TABLE t1( + node PRIMARY KEY, + parent REFERENCES t1 ON DELETE CASCADE + ) WITHOUT rowid; + CREATE TABLE t2(node PRIMARY KEY, parent) WITHOUT rowid; + CREATE TRIGGER t2t AFTER DELETE ON t2 BEGIN + DELETE FROM t2 WHERE parent = old.node; + END; + INSERT INTO t1 VALUES(1, NULL); + INSERT INTO t1 VALUES(2, 1); + INSERT INTO t1 VALUES(3, 1); + INSERT INTO t1 VALUES(4, 2); + INSERT INTO t1 VALUES(5, 2); + INSERT INTO t1 VALUES(6, 3); + INSERT INTO t1 VALUES(7, 3); + INSERT INTO t2 SELECT * FROM t1; + } +} {} +do_test without_rowid3-4.2 { + execsql { PRAGMA recursive_triggers = off } + execsql { + BEGIN; + DELETE FROM t1 WHERE node = 1; + SELECT node FROM t1; + } +} {} +do_test without_rowid3-4.3 { + execsql { + DELETE FROM t2 WHERE node = 1; + SELECT node FROM t2; + ROLLBACK; + } +} {4 5 6 7} +do_test without_rowid3-4.4 { + execsql { PRAGMA recursive_triggers = on } + execsql { + BEGIN; + DELETE FROM t1 WHERE node = 1; + SELECT node FROM t1; + } +} {} +do_test without_rowid3-4.3 { + execsql { + DELETE FROM t2 WHERE node = 1; + SELECT node FROM t2; + ROLLBACK; + } +} {} + +#------------------------------------------------------------------------- +# Test cases without_rowid3-5.* verify that the incremental blob API may not +# write to a foreign key column while foreign-keys are enabled. +# +drop_all_tables +ifcapable incrblob { + do_test without_rowid3-5.1 { + execsql { + CREATE TABLE t1(a PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE t2(a PRIMARY KEY, b REFERENCES t1(a)) WITHOUT rowid; + INSERT INTO t1 VALUES('hello', 'world'); + INSERT INTO t2 VALUES('key', 'hello'); + } + } {} + do_test without_rowid3-5.2 { + set rc [catch { set fd [db incrblob t2 b 1] } msg] + list $rc $msg + } {1 {cannot open table without rowid: t2}} + do_test without_rowid3-5.5 { + execsql { PRAGMA foreign_keys = on } + } {} +} + +drop_all_tables +ifcapable vacuum { + do_test without_rowid3-6.1 { + execsql { + CREATE TABLE t1(a REFERENCES t2(c), b); + CREATE TABLE t2(c UNIQUE, b); + INSERT INTO t2 VALUES(1, 2); + INSERT INTO t1 VALUES(1, 2); + VACUUM; + } + } {} +} + +#------------------------------------------------------------------------- +# Test that it is possible to use an INT PRIMARY KEY as the child key +# of a foreign constraint. +# +drop_all_tables +do_test without_rowid3-7.1 { + execsql { + CREATE TABLE t1(a PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE t2(c INT PRIMARY KEY REFERENCES t1, b) WITHOUT rowid; + } +} {} +do_test without_rowid3-7.2 { + catchsql { INSERT INTO t2 VALUES(1, 'A'); } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-7.3 { + execsql { + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(2, 3); + INSERT INTO t2 VALUES(1, 'A'); + } +} {} +do_test without_rowid3-7.4 { + execsql { UPDATE t2 SET c = 2 } +} {} +do_test without_rowid3-7.5 { + catchsql { UPDATE t2 SET c = 3 } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-7.6 { + catchsql { DELETE FROM t1 WHERE a = 2 } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-7.7 { + execsql { DELETE FROM t1 WHERE a = 1 } +} {} +do_test without_rowid3-7.8 { + catchsql { UPDATE t1 SET a = 3 } +} {1 {FOREIGN KEY constraint failed}} + +#------------------------------------------------------------------------- +# Test that it is not possible to enable/disable FK support while a +# transaction is open. +# +drop_all_tables +proc without_rowid3-8-test {tn zSql value} { + do_test without_rowid3-2.8.$tn.1 [list execsql $zSql] {} + do_test without_rowid3-2.8.$tn.2 { execsql "PRAGMA foreign_keys" } $value +} +without_rowid3-8-test 1 { PRAGMA foreign_keys = 0 } 0 +without_rowid3-8-test 2 { PRAGMA foreign_keys = 1 } 1 +without_rowid3-8-test 3 { BEGIN } 1 +without_rowid3-8-test 4 { PRAGMA foreign_keys = 0 } 1 +without_rowid3-8-test 5 { COMMIT } 1 +without_rowid3-8-test 6 { PRAGMA foreign_keys = 0 } 0 +without_rowid3-8-test 7 { BEGIN } 0 +without_rowid3-8-test 8 { PRAGMA foreign_keys = 1 } 0 +without_rowid3-8-test 9 { COMMIT } 0 +without_rowid3-8-test 10 { PRAGMA foreign_keys = 1 } 1 +without_rowid3-8-test 11 { PRAGMA foreign_keys = off } 0 +without_rowid3-8-test 12 { PRAGMA foreign_keys = on } 1 +without_rowid3-8-test 13 { PRAGMA foreign_keys = no } 0 +without_rowid3-8-test 14 { PRAGMA foreign_keys = yes } 1 +without_rowid3-8-test 15 { PRAGMA foreign_keys = false } 0 +without_rowid3-8-test 16 { PRAGMA foreign_keys = true } 1 + +#------------------------------------------------------------------------- +# The following tests, without_rowid3-9.*, test SET DEFAULT actions. +# +drop_all_tables +do_test without_rowid3-9.1.1 { + execsql { + CREATE TABLE t1(a INT PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE t2( + c INT PRIMARY KEY, + d INTEGER DEFAULT 1 REFERENCES t1 ON DELETE SET DEFAULT + ) WITHOUT rowid; + DELETE FROM t1; + } +} {} +do_test without_rowid3-9.1.2 { + execsql { + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + INSERT INTO t2 VALUES(1, 2); + SELECT * FROM t2; + DELETE FROM t1 WHERE a = 2; + SELECT * FROM t2; + } +} {1 2 1 1} +do_test without_rowid3-9.1.3 { + execsql { + INSERT INTO t1 VALUES(2, 'two'); + UPDATE t2 SET d = 2; + DELETE FROM t1 WHERE a = 1; + SELECT * FROM t2; + } +} {1 2} +do_test without_rowid3-9.1.4 { + execsql { SELECT * FROM t1 } +} {2 two} +do_test without_rowid3-9.1.5 { + catchsql { DELETE FROM t1 } +} {1 {FOREIGN KEY constraint failed}} + +do_test without_rowid3-9.2.1 { + execsql { + CREATE TABLE pp(a, b, c, PRIMARY KEY(b, c)) WITHOUT rowid; + CREATE TABLE cc(d DEFAULT 3, e DEFAULT 1, f DEFAULT 2, + FOREIGN KEY(f, d) REFERENCES pp + ON UPDATE SET DEFAULT + ON DELETE SET NULL + ); + INSERT INTO pp VALUES(1, 2, 3); + INSERT INTO pp VALUES(4, 5, 6); + INSERT INTO pp VALUES(7, 8, 9); + } +} {} +do_test without_rowid3-9.2.2 { + execsql { + INSERT INTO cc VALUES(6, 'A', 5); + INSERT INTO cc VALUES(6, 'B', 5); + INSERT INTO cc VALUES(9, 'A', 8); + INSERT INTO cc VALUES(9, 'B', 8); + UPDATE pp SET b = 1 WHERE a = 7; + SELECT * FROM cc; + } +} {6 A 5 6 B 5 3 A 2 3 B 2} +do_test without_rowid3-9.2.3 { + execsql { + DELETE FROM pp WHERE a = 4; + SELECT * FROM cc; + } +} {{} A {} {} B {} 3 A 2 3 B 2} + +#------------------------------------------------------------------------- +# The following tests, without_rowid3-10.*, test "foreign key mismatch" and +# other errors. +# +set tn 0 +foreach zSql [list { + CREATE TABLE p(a PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE c(x REFERENCES p(c)); +} { + CREATE TABLE c(x REFERENCES v(y)); + CREATE VIEW v AS SELECT x AS y FROM c; +} { + CREATE TABLE p(a, b, PRIMARY KEY(a, b)) WITHOUT rowid; + CREATE TABLE c(x REFERENCES p); +} { + CREATE TABLE p(a COLLATE binary, b); + CREATE UNIQUE INDEX i ON p(a COLLATE nocase); + CREATE TABLE c(x REFERENCES p(a)); +}] { + drop_all_tables + do_test without_rowid3-10.1.[incr tn] { + execsql $zSql + catchsql { INSERT INTO c DEFAULT VALUES } + } {/1 {foreign key mismatch - "c" referencing "."}/} +} + +# "rowid" cannot be used as part of a child or parent key definition +# unless it happens to be the name of an explicitly declared column. +# +do_test without_rowid3-10.2.1 { + drop_all_tables + catchsql { + CREATE TABLE t1(a PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE t2(c, d, FOREIGN KEY(rowid) REFERENCES t1(a)); + } +} {1 {unknown column "rowid" in foreign key definition}} +do_test without_rowid3-10.2.2 { + drop_all_tables + catchsql { + CREATE TABLE t1(a PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE t2(rowid, d, FOREIGN KEY(rowid) REFERENCES t1(a)); + } +} {0 {}} +do_test without_rowid3-10.2.1 { + drop_all_tables + catchsql { + CREATE TABLE t1(a, b); + CREATE TABLE t2(c, d, FOREIGN KEY(c) REFERENCES t1(rowid)); + INSERT INTO t1(rowid, a, b) VALUES(1, 1, 1); + INSERT INTO t2 VALUES(1, 1); + } +} {1 {foreign key mismatch - "t2" referencing "t1"}} +do_test without_rowid3-10.2.2 { + drop_all_tables + catchsql { + CREATE TABLE t1(rowid PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE t2(c, d, FOREIGN KEY(c) REFERENCES t1(rowid)); + INSERT INTO t1(rowid, b) VALUES(1, 1); + INSERT INTO t2 VALUES(1, 1); + } +} {0 {}} + + +#------------------------------------------------------------------------- +# The following tests, without_rowid3-11.*, test CASCADE actions. +# +drop_all_tables +do_test without_rowid3-11.1.1 { + execsql { + CREATE TABLE t1(a INT PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE t2(c, d, FOREIGN KEY(c) REFERENCES t1(a) ON UPDATE CASCADE); + + INSERT INTO t1 VALUES(10, 100); + INSERT INTO t2 VALUES(10, 100); + UPDATE t1 SET a = 15; + SELECT * FROM t2; + } +} {15 100} + +#------------------------------------------------------------------------- +# The following tests, without_rowid3-12.*, test RESTRICT actions. +# +drop_all_tables +do_test without_rowid3-12.1.1 { + execsql { + CREATE TABLE t1(a, b PRIMARY KEY) WITHOUT rowid; + CREATE TABLE t2( + x REFERENCES t1 ON UPDATE RESTRICT DEFERRABLE INITIALLY DEFERRED + ); + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + INSERT INTO t1 VALUES(3, 'three'); + } +} {} +do_test without_rowid3-12.1.2 { + execsql "BEGIN" + execsql "INSERT INTO t2 VALUES('two')" +} {} +do_test without_rowid3-12.1.3 { + execsql "UPDATE t1 SET b = 'four' WHERE b = 'one'" +} {} +do_test without_rowid3-12.1.4 { + catchsql "UPDATE t1 SET b = 'five' WHERE b = 'two'" +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-12.1.5 { + execsql "DELETE FROM t1 WHERE b = 'two'" +} {} +do_test without_rowid3-12.1.6 { + catchsql "COMMIT" +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-12.1.7 { + execsql { + INSERT INTO t1 VALUES(2, 'two'); + COMMIT; + } +} {} + +drop_all_tables +do_test without_rowid3-12.2.1 { + execsql { + CREATE TABLE t1(x COLLATE NOCASE PRIMARY KEY) WITHOUT rowid; + CREATE TRIGGER tt1 AFTER DELETE ON t1 + WHEN EXISTS ( SELECT 1 FROM t2 WHERE old.x = y ) + BEGIN + INSERT INTO t1 VALUES(old.x); + END; + CREATE TABLE t2(y REFERENCES t1); + INSERT INTO t1 VALUES('A'); + INSERT INTO t1 VALUES('B'); + INSERT INTO t2 VALUES('a'); + INSERT INTO t2 VALUES('b'); + + SELECT * FROM t1; + SELECT * FROM t2; + } +} {A B a b} +do_test without_rowid3-12.2.2 { + execsql { DELETE FROM t1 } + execsql { + SELECT * FROM t1; + SELECT * FROM t2; + } +} {A B a b} +do_test without_rowid3-12.2.3 { + execsql { + DROP TABLE t2; + CREATE TABLE t2(y REFERENCES t1 ON DELETE RESTRICT); + INSERT INTO t2 VALUES('a'); + INSERT INTO t2 VALUES('b'); + } + catchsql { DELETE FROM t1 } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-12.2.4 { + execsql { + SELECT * FROM t1; + SELECT * FROM t2; + } +} {A B a b} + +drop_all_tables +do_test without_rowid3-12.3.1 { + execsql { + CREATE TABLE up( + c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, + c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, + c20, c21, c22, c23, c24, c25, c26, c27, c28, c29, + c30, c31, c32, c33, c34, c35, c36, c37, c38, c39, + PRIMARY KEY(c34, c35) + ) WITHOUT rowid; + CREATE TABLE down( + c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, + c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, + c20, c21, c22, c23, c24, c25, c26, c27, c28, c29, + c30, c31, c32, c33, c34, c35, c36, c37, c38, c39, + FOREIGN KEY(c39, c38) REFERENCES up ON UPDATE CASCADE + ); + } +} {} +do_test without_rowid3-12.3.2 { + execsql { + INSERT INTO up(c34, c35) VALUES('yes', 'no'); + INSERT INTO down(c39, c38) VALUES('yes', 'no'); + UPDATE up SET c34 = 'possibly'; + SELECT c38, c39 FROM down; + DELETE FROM down; + } +} {no possibly} +do_test without_rowid3-12.3.3 { + catchsql { INSERT INTO down(c39, c38) VALUES('yes', 'no') } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-12.3.4 { + execsql { + INSERT INTO up(c34, c35) VALUES('yes', 'no'); + INSERT INTO down(c39, c38) VALUES('yes', 'no'); + } + catchsql { DELETE FROM up WHERE c34 = 'yes' } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-12.3.5 { + execsql { + DELETE FROM up WHERE c34 = 'possibly'; + SELECT c34, c35 FROM up; + SELECT c39, c38 FROM down; + } +} {yes no yes no} + +#------------------------------------------------------------------------- +# The following tests, without_rowid3-13.*, test that FK processing is performed +# when rows are REPLACEd. +# +drop_all_tables +do_test without_rowid3-13.1.1 { + execsql { + CREATE TABLE pp(a UNIQUE, b, c, PRIMARY KEY(b, c)) WITHOUT rowid; + CREATE TABLE cc(d, e, f UNIQUE, FOREIGN KEY(d, e) REFERENCES pp); + INSERT INTO pp VALUES(1, 2, 3); + INSERT INTO cc VALUES(2, 3, 1); + } +} {} +foreach {tn stmt} { + 1 "REPLACE INTO pp VALUES(1, 4, 5)" +} { + do_test without_rowid3-13.1.$tn.1 { + catchsql $stmt + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-13.1.$tn.2 { + execsql { + SELECT * FROM pp; + SELECT * FROM cc; + } + } {1 2 3 2 3 1} + do_test without_rowid3-13.1.$tn.3 { + execsql BEGIN; + catchsql $stmt + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-13.1.$tn.4 { + execsql { + COMMIT; + SELECT * FROM pp; + SELECT * FROM cc; + } + } {1 2 3 2 3 1} +} + +#------------------------------------------------------------------------- +# The following tests, without_rowid3-14.*, test that the "DROP TABLE" and "ALTER +# TABLE" commands work as expected wrt foreign key constraints. +# +# without_rowid3-14.1*: ALTER TABLE ADD COLUMN +# without_rowid3-14.2*: ALTER TABLE RENAME TABLE +# without_rowid3-14.3*: DROP TABLE +# +drop_all_tables +ifcapable altertable { + do_test without_rowid3-14.1.1 { + # Adding a column with a REFERENCES clause is not supported. + execsql { + CREATE TABLE t1(a PRIMARY KEY) WITHOUT rowid; + CREATE TABLE t2(a, b); + } + catchsql { ALTER TABLE t2 ADD COLUMN c REFERENCES t1 } + } {0 {}} + do_test without_rowid3-14.1.2 { + catchsql { ALTER TABLE t2 ADD COLUMN d DEFAULT NULL REFERENCES t1 } + } {0 {}} + do_test without_rowid3-14.1.3 { + catchsql { ALTER TABLE t2 ADD COLUMN e REFERENCES t1 DEFAULT NULL} + } {0 {}} + do_test without_rowid3-14.1.4 { + catchsql { ALTER TABLE t2 ADD COLUMN f REFERENCES t1 DEFAULT 'text'} + } {1 {Cannot add a REFERENCES column with non-NULL default value}} + do_test without_rowid3-14.1.5 { + catchsql { ALTER TABLE t2 ADD COLUMN g DEFAULT CURRENT_TIME REFERENCES t1 } + } {1 {Cannot add a REFERENCES column with non-NULL default value}} + do_test without_rowid3-14.1.6 { + execsql { + PRAGMA foreign_keys = off; + ALTER TABLE t2 ADD COLUMN h DEFAULT 'text' REFERENCES t1; + PRAGMA foreign_keys = on; + SELECT sql FROM sqlite_master WHERE name='t2'; + } + } {{CREATE TABLE t2(a, b, c REFERENCES t1, d DEFAULT NULL REFERENCES t1, e REFERENCES t1 DEFAULT NULL, h DEFAULT 'text' REFERENCES t1)}} + + + # Test the sqlite_rename_parent() function directly. + # + proc test_rename_parent {zCreate zOld zNew} { + db eval {SELECT sqlite_rename_parent($zCreate, $zOld, $zNew)} + } + do_test without_rowid3-14.2.1.1 { + test_rename_parent {CREATE TABLE t1(a REFERENCES t2)} t2 t3 + } {{CREATE TABLE t1(a REFERENCES "t3")}} + do_test without_rowid3-14.2.1.2 { + test_rename_parent {CREATE TABLE t1(a REFERENCES t2)} t4 t3 + } {{CREATE TABLE t1(a REFERENCES t2)}} + do_test without_rowid3-14.2.1.3 { + test_rename_parent {CREATE TABLE t1(a REFERENCES "t2")} t2 t3 + } {{CREATE TABLE t1(a REFERENCES "t3")}} + + # Test ALTER TABLE RENAME TABLE a bit. + # + do_test without_rowid3-14.2.2.1 { + drop_all_tables + execsql { + CREATE TABLE t1(a PRIMARY KEY, b REFERENCES t1) WITHOUT rowid; + CREATE TABLE t2(a PRIMARY KEY, b REFERENCES t1, c REFERENCES t2) + WITHOUT rowid; + CREATE TABLE t3(a REFERENCES t1, b REFERENCES t2, c REFERENCES t1); + } + execsql { SELECT sql FROM sqlite_master WHERE type = 'table'} + } [list \ + {CREATE TABLE t1(a PRIMARY KEY, b REFERENCES t1) WITHOUT rowid} \ + {CREATE TABLE t2(a PRIMARY KEY, b REFERENCES t1, c REFERENCES t2) + WITHOUT rowid} \ + {CREATE TABLE t3(a REFERENCES t1, b REFERENCES t2, c REFERENCES t1)} \ + ] + do_test without_rowid3-14.2.2.2 { + execsql { ALTER TABLE t1 RENAME TO t4 } + execsql { SELECT sql FROM sqlite_master WHERE type = 'table'} + } [list \ + {CREATE TABLE "t4"(a PRIMARY KEY, b REFERENCES "t4") WITHOUT rowid} \ + {CREATE TABLE t2(a PRIMARY KEY, b REFERENCES "t4", c REFERENCES t2) + WITHOUT rowid} \ + {CREATE TABLE t3(a REFERENCES "t4", b REFERENCES t2, c REFERENCES "t4")} \ + ] + do_test without_rowid3-14.2.2.3 { + catchsql { INSERT INTO t3 VALUES(1, 2, 3) } + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-14.2.2.4 { + execsql { INSERT INTO t4 VALUES(1, NULL) } + } {} + do_test without_rowid3-14.2.2.5 { + catchsql { UPDATE t4 SET b = 5 } + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-14.2.2.6 { + catchsql { UPDATE t4 SET b = 1 } + } {0 {}} + do_test without_rowid3-14.2.2.7 { + execsql { INSERT INTO t3 VALUES(1, NULL, 1) } + } {} + + # Repeat for TEMP tables + # + drop_all_tables + do_test without_rowid3-14.1tmp.1 { + # Adding a column with a REFERENCES clause is not supported. + execsql { + CREATE TEMP TABLE t1(a PRIMARY KEY) WITHOUT rowid; + CREATE TEMP TABLE t2(a, b); + } + catchsql { ALTER TABLE t2 ADD COLUMN c REFERENCES t1 } + } {0 {}} + do_test without_rowid3-14.1tmp.2 { + catchsql { ALTER TABLE t2 ADD COLUMN d DEFAULT NULL REFERENCES t1 } + } {0 {}} + do_test without_rowid3-14.1tmp.3 { + catchsql { ALTER TABLE t2 ADD COLUMN e REFERENCES t1 DEFAULT NULL} + } {0 {}} + do_test without_rowid3-14.1tmp.4 { + catchsql { ALTER TABLE t2 ADD COLUMN f REFERENCES t1 DEFAULT 'text'} + } {1 {Cannot add a REFERENCES column with non-NULL default value}} + do_test without_rowid3-14.1tmp.5 { + catchsql { ALTER TABLE t2 ADD COLUMN g DEFAULT CURRENT_TIME REFERENCES t1 } + } {1 {Cannot add a REFERENCES column with non-NULL default value}} + do_test without_rowid3-14.1tmp.6 { + execsql { + PRAGMA foreign_keys = off; + ALTER TABLE t2 ADD COLUMN h DEFAULT 'text' REFERENCES t1; + PRAGMA foreign_keys = on; + SELECT sql FROM sqlite_temp_master WHERE name='t2'; + } + } {{CREATE TABLE t2(a, b, c REFERENCES t1, d DEFAULT NULL REFERENCES t1, e REFERENCES t1 DEFAULT NULL, h DEFAULT 'text' REFERENCES t1)}} + + do_test without_rowid3-14.2tmp.1.1 { + test_rename_parent {CREATE TABLE t1(a REFERENCES t2)} t2 t3 + } {{CREATE TABLE t1(a REFERENCES "t3")}} + do_test without_rowid3-14.2tmp.1.2 { + test_rename_parent {CREATE TABLE t1(a REFERENCES t2)} t4 t3 + } {{CREATE TABLE t1(a REFERENCES t2)}} + do_test without_rowid3-14.2tmp.1.3 { + test_rename_parent {CREATE TABLE t1(a REFERENCES "t2")} t2 t3 + } {{CREATE TABLE t1(a REFERENCES "t3")}} + + # Test ALTER TABLE RENAME TABLE a bit. + # + do_test without_rowid3-14.2tmp.2.1 { + drop_all_tables + execsql { + CREATE TEMP TABLE t1(a PRIMARY KEY, b REFERENCES t1) WITHOUT rowid; + CREATE TEMP TABLE t2(a PRIMARY KEY, b REFERENCES t1, c REFERENCES t2) + WITHOUT rowid; + CREATE TEMP TABLE t3(a REFERENCES t1, b REFERENCES t2, c REFERENCES t1); + } + execsql { SELECT sql FROM sqlite_temp_master WHERE type = 'table'} + } [list \ + {CREATE TABLE t1(a PRIMARY KEY, b REFERENCES t1) WITHOUT rowid} \ + {CREATE TABLE t2(a PRIMARY KEY, b REFERENCES t1, c REFERENCES t2) + WITHOUT rowid} \ + {CREATE TABLE t3(a REFERENCES t1, b REFERENCES t2, c REFERENCES t1)} \ + ] + do_test without_rowid3-14.2tmp.2.2 { + execsql { ALTER TABLE t1 RENAME TO t4 } + execsql { SELECT sql FROM sqlite_temp_master WHERE type = 'table'} + } [list \ + {CREATE TABLE "t4"(a PRIMARY KEY, b REFERENCES "t4") WITHOUT rowid} \ + {CREATE TABLE t2(a PRIMARY KEY, b REFERENCES "t4", c REFERENCES t2) + WITHOUT rowid} \ + {CREATE TABLE t3(a REFERENCES "t4", b REFERENCES t2, c REFERENCES "t4")} \ + ] + do_test without_rowid3-14.2tmp.2.3 { + catchsql { INSERT INTO t3 VALUES(1, 2, 3) } + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-14.2tmp.2.4 { + execsql { INSERT INTO t4 VALUES(1, NULL) } + } {} + do_test without_rowid3-14.2tmp.2.5 { + catchsql { UPDATE t4 SET b = 5 } + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-14.2tmp.2.6 { + catchsql { UPDATE t4 SET b = 1 } + } {0 {}} + do_test without_rowid3-14.2tmp.2.7 { + execsql { INSERT INTO t3 VALUES(1, NULL, 1) } + } {} + + # Repeat for ATTACH-ed tables + # + drop_all_tables + do_test without_rowid3-14.1aux.1 { + # Adding a column with a REFERENCES clause is not supported. + execsql { + ATTACH ':memory:' AS aux; + CREATE TABLE aux.t1(a PRIMARY KEY) WITHOUT rowid; + CREATE TABLE aux.t2(a, b); + } + catchsql { ALTER TABLE t2 ADD COLUMN c REFERENCES t1 } + } {0 {}} + do_test without_rowid3-14.1aux.2 { + catchsql { ALTER TABLE t2 ADD COLUMN d DEFAULT NULL REFERENCES t1 } + } {0 {}} + do_test without_rowid3-14.1aux.3 { + catchsql { ALTER TABLE t2 ADD COLUMN e REFERENCES t1 DEFAULT NULL} + } {0 {}} + do_test without_rowid3-14.1aux.4 { + catchsql { ALTER TABLE t2 ADD COLUMN f REFERENCES t1 DEFAULT 'text'} + } {1 {Cannot add a REFERENCES column with non-NULL default value}} + do_test without_rowid3-14.1aux.5 { + catchsql { ALTER TABLE t2 ADD COLUMN g DEFAULT CURRENT_TIME REFERENCES t1 } + } {1 {Cannot add a REFERENCES column with non-NULL default value}} + do_test without_rowid3-14.1aux.6 { + execsql { + PRAGMA foreign_keys = off; + ALTER TABLE t2 ADD COLUMN h DEFAULT 'text' REFERENCES t1; + PRAGMA foreign_keys = on; + SELECT sql FROM aux.sqlite_master WHERE name='t2'; + } + } {{CREATE TABLE t2(a, b, c REFERENCES t1, d DEFAULT NULL REFERENCES t1, e REFERENCES t1 DEFAULT NULL, h DEFAULT 'text' REFERENCES t1)}} + + do_test without_rowid3-14.2aux.1.1 { + test_rename_parent {CREATE TABLE t1(a REFERENCES t2)} t2 t3 + } {{CREATE TABLE t1(a REFERENCES "t3")}} + do_test without_rowid3-14.2aux.1.2 { + test_rename_parent {CREATE TABLE t1(a REFERENCES t2)} t4 t3 + } {{CREATE TABLE t1(a REFERENCES t2)}} + do_test without_rowid3-14.2aux.1.3 { + test_rename_parent {CREATE TABLE t1(a REFERENCES "t2")} t2 t3 + } {{CREATE TABLE t1(a REFERENCES "t3")}} + + # Test ALTER TABLE RENAME TABLE a bit. + # + do_test without_rowid3-14.2aux.2.1 { + drop_all_tables + execsql { + CREATE TABLE aux.t1(a PRIMARY KEY, b REFERENCES t1) WITHOUT rowid; + CREATE TABLE aux.t2(a PRIMARY KEY, b REFERENCES t1, c REFERENCES t2) + WITHOUT rowid; + CREATE TABLE aux.t3(a REFERENCES t1, b REFERENCES t2, c REFERENCES t1); + } + execsql { SELECT sql FROM aux.sqlite_master WHERE type = 'table'} + } [list \ + {CREATE TABLE t1(a PRIMARY KEY, b REFERENCES t1) WITHOUT rowid} \ + {CREATE TABLE t2(a PRIMARY KEY, b REFERENCES t1, c REFERENCES t2) + WITHOUT rowid} \ + {CREATE TABLE t3(a REFERENCES t1, b REFERENCES t2, c REFERENCES t1)} \ + ] + do_test without_rowid3-14.2aux.2.2 { + execsql { ALTER TABLE t1 RENAME TO t4 } + execsql { SELECT sql FROM aux.sqlite_master WHERE type = 'table'} + } [list \ + {CREATE TABLE "t4"(a PRIMARY KEY, b REFERENCES "t4") WITHOUT rowid} \ + {CREATE TABLE t2(a PRIMARY KEY, b REFERENCES "t4", c REFERENCES t2) + WITHOUT rowid} \ + {CREATE TABLE t3(a REFERENCES "t4", b REFERENCES t2, c REFERENCES "t4")} \ + ] + do_test without_rowid3-14.2aux.2.3 { + catchsql { INSERT INTO t3 VALUES(1, 2, 3) } + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-14.2aux.2.4 { + execsql { INSERT INTO t4 VALUES(1, NULL) } + } {} + do_test without_rowid3-14.2aux.2.5 { + catchsql { UPDATE t4 SET b = 5 } + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-14.2aux.2.6 { + catchsql { UPDATE t4 SET b = 1 } + } {0 {}} + do_test without_rowid3-14.2aux.2.7 { + execsql { INSERT INTO t3 VALUES(1, NULL, 1) } + } {} +} + +do_test without_rowid3-2.14.3.1 { + drop_all_tables + execsql { + CREATE TABLE t1(a, b REFERENCES nosuchtable); + DROP TABLE t1; + } +} {} +do_test without_rowid3-2.14.3.2 { + execsql { + CREATE TABLE t1(a PRIMARY KEY, b) WITHOUT rowid; + INSERT INTO t1 VALUES('a', 1); + CREATE TABLE t2(x REFERENCES t1); + INSERT INTO t2 VALUES('a'); + } +} {} +do_test without_rowid3-2.14.3.3 { + catchsql { DROP TABLE t1 } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-2.14.3.4 { + execsql { + DELETE FROM t2; + DROP TABLE t1; + } +} {} +do_test without_rowid3-2.14.3.4 { + catchsql { INSERT INTO t2 VALUES('x') } +} {1 {no such table: main.t1}} +do_test without_rowid3-2.14.3.5 { + execsql { + CREATE TABLE t1(x PRIMARY KEY) WITHOUT rowid; + INSERT INTO t1 VALUES('x'); + } + execsql { INSERT INTO t2 VALUES('x') } +} {} +do_test without_rowid3-2.14.3.6 { + catchsql { DROP TABLE t1 } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-2.14.3.7 { + execsql { + DROP TABLE t2; + DROP TABLE t1; + } +} {} +do_test without_rowid3-2.14.3.8 { + execsql { + CREATE TABLE pp(x, y, PRIMARY KEY(x, y)) WITHOUT ROWID; + CREATE TABLE cc(a, b, FOREIGN KEY(a, b) REFERENCES pp(x, z)); + } + catchsql { INSERT INTO cc VALUES(1, 2) } +} {1 {foreign key mismatch - "cc" referencing "pp"}} +do_test without_rowid3-2.14.3.9 { + execsql { DROP TABLE cc } +} {} +do_test without_rowid3-2.14.3.10 { + execsql { + CREATE TABLE cc(a, b, + FOREIGN KEY(a, b) REFERENCES pp DEFERRABLE INITIALLY DEFERRED + ); + } + execsql { + INSERT INTO pp VALUES('a', 'b'); + INSERT INTO cc VALUES('a', 'b'); + BEGIN; + DROP TABLE pp; + CREATE TABLE pp(a, b, c, PRIMARY KEY(b, c)) WITHOUT rowid; + INSERT INTO pp VALUES(1, 'a', 'b'); + COMMIT; + } +} {} +do_test without_rowid3-2.14.3.11 { + execsql { + BEGIN; + DROP TABLE cc; + DROP TABLE pp; + COMMIT; + } +} {} +do_test without_rowid3-2.14.3.12 { + execsql { + CREATE TABLE b1(a, b); + CREATE TABLE b2(a, b REFERENCES b1); + DROP TABLE b1; + } +} {} +do_test without_rowid3-2.14.3.13 { + execsql { + CREATE TABLE b3(a, b REFERENCES b2 DEFERRABLE INITIALLY DEFERRED); + DROP TABLE b2; + } +} {} + +# Test that nothing goes wrong when dropping a table that refers to a view. +# Or dropping a view that an existing FK (incorrectly) refers to. Or either +# of the above scenarios with a virtual table. +drop_all_tables +do_test without_rowid3-2.14.4.1 { + execsql { + CREATE TABLE t1(x REFERENCES v); + CREATE VIEW v AS SELECT * FROM t1; + } +} {} +do_test without_rowid3-2.14.4.2 { + execsql { + DROP VIEW v; + } +} {} +ifcapable vtab { + register_echo_module db + do_test without_rowid3-2.14.4.3 { + execsql { CREATE VIRTUAL TABLE v USING echo(t1) } + } {} + do_test without_rowid3-2.14.4.2 { + execsql { + DROP TABLE v; + } + } {} +} + +#------------------------------------------------------------------------- +# The following tests, without_rowid3-15.*, test that unnecessary FK related scans +# and lookups are avoided when the constraint counters are zero. +# +drop_all_tables +proc execsqlS {zSql} { + set ::sqlite_search_count 0 + set ::sqlite_found_count 0 + set res [uplevel [list execsql $zSql]] + concat [expr $::sqlite_found_count + $::sqlite_search_count] $res +} +do_test without_rowid3-15.1.1 { + execsql { + CREATE TABLE pp(a PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE cc(x, y REFERENCES pp DEFERRABLE INITIALLY DEFERRED); + INSERT INTO pp VALUES(1, 'one'); + INSERT INTO pp VALUES(2, 'two'); + INSERT INTO cc VALUES('neung', 1); + INSERT INTO cc VALUES('song', 2); + } +} {} +do_test without_rowid3-15.1.2 { + execsqlS { INSERT INTO pp VALUES(3, 'three') } +} {0} +do_test without_rowid3-15.1.3 { + execsql { + BEGIN; + INSERT INTO cc VALUES('see', 4); -- Violates deferred constraint + } + execsqlS { INSERT INTO pp VALUES(5, 'five') } +} {2} +do_test without_rowid3-15.1.4 { + execsql { DELETE FROM cc WHERE x = 'see' } + execsqlS { INSERT INTO pp VALUES(6, 'six') } +} {0} +do_test without_rowid3-15.1.5 { + execsql COMMIT +} {} +do_test without_rowid3-15.1.6 { + execsql BEGIN + execsqlS { + DELETE FROM cc WHERE x = 'neung'; + ROLLBACK; + } +} {1} +do_test without_rowid3-15.1.7 { + execsql { + BEGIN; + DELETE FROM pp WHERE a = 2; + } + execsqlS { + DELETE FROM cc WHERE x = 'neung'; + ROLLBACK; + } +} {2} + +#------------------------------------------------------------------------- +# This next block of tests, without_rowid3-16.*, test that rows that refer to +# themselves may be inserted and deleted. +# +foreach {tn zSchema} { + 1 { CREATE TABLE self(a INTEGER PRIMARY KEY, b REFERENCES self(a)) + WITHOUT rowid } + 2 { CREATE TABLE self(a PRIMARY KEY, b REFERENCES self(a)) WITHOUT rowid } + 3 { CREATE TABLE self(a UNIQUE, b INT PRIMARY KEY REFERENCES self(a)) + WITHOUT rowid } +} { + drop_all_tables + do_test without_rowid3-16.1.$tn.1 { + execsql $zSchema + execsql { INSERT INTO self VALUES(13, 13) } + } {} + do_test without_rowid3-16.1.$tn.2 { + execsql { UPDATE self SET a = 14, b = 14 } + } {} + + do_test without_rowid3-16.1.$tn.3 { + catchsql { UPDATE self SET b = 15 } + } {1 {FOREIGN KEY constraint failed}} + + do_test without_rowid3-16.1.$tn.4 { + catchsql { UPDATE self SET a = 15 } + } {1 {FOREIGN KEY constraint failed}} + + do_test without_rowid3-16.1.$tn.5 { + catchsql { UPDATE self SET a = 15, b = 16 } + } {1 {FOREIGN KEY constraint failed}} + + do_test without_rowid3-16.1.$tn.6 { + catchsql { UPDATE self SET a = 17, b = 17 } + } {0 {}} + + do_test without_rowid3-16.1.$tn.7 { + execsql { DELETE FROM self } + } {} + do_test without_rowid3-16.1.$tn.8 { + catchsql { INSERT INTO self VALUES(20, 21) } + } {1 {FOREIGN KEY constraint failed}} +} + +# Additional tests cases using multi-column self-referential +# FOREIGN KEY constraints. +# +drop_all_tables +do_execsql_test without_rowid3-16.4.1.1 { + PRAGMA foreign_keys=ON; + CREATE TABLE t1(a,b,c,d,e,f, + UNIQUE (a,b), + PRIMARY KEY (e,c), + FOREIGN KEY (d,f) REFERENCES t1(e,c) + ) WITHOUT rowid; + INSERT INTO t1 VALUES(1,2,3,5,5,3); + INSERT INTO t1 VALUES(2,3,4,6,6,4); + INSERT INTO t1 VALUES('x','y',1.5,'fizzle','fizzle',1.5); + SELECT *, '|' FROM t1 ORDER BY a, b; +} {1 2 3 5 5 3 | 2 3 4 6 6 4 | x y 1.5 fizzle fizzle 1.5 |} + +do_execsql_test without_rowid3-16.4.1.2 { + UPDATE t1 SET c=99, f=99 WHERE a=1; + SELECT *, '|' FROM t1 ORDER BY a, b; +} {1 2 99 5 5 99 | 2 3 4 6 6 4 | x y 1.5 fizzle fizzle 1.5 |} + +do_execsql_test without_rowid3-16.4.1.3 { + UPDATE t1 SET e=876, d=876 WHERE a=2; + SELECT *, '|' FROM t1 ORDER BY a, b; +} {1 2 99 5 5 99 | 2 3 4 876 876 4 | x y 1.5 fizzle fizzle 1.5 |} + +do_test without_rowid3-16.4.1.4 { + catchsql { + UPDATE t1 SET c=11, e=22 WHERE a=1; + } +} {1 {FOREIGN KEY constraint failed}} + +do_test without_rowid3-16.4.1.5 { + catchsql { + UPDATE t1 SET d=11, f=22 WHERE a=1; + } +} {1 {FOREIGN KEY constraint failed}} + +do_execsql_test without_rowid3-16.4.1.6 { + DELETE FROM t1 WHERE a=1; + SELECT *, '|' FROM t1 ORDER BY a, b; +} {2 3 4 876 876 4 | x y 1.5 fizzle fizzle 1.5 |} + +do_execsql_test without_rowid3-16.4.2.1 { + DROP TABLE t1; + CREATE TABLE t1(a,b,c,d,e,f, + PRIMARY KEY (a,b), + UNIQUE (e,c), + FOREIGN KEY (d,f) REFERENCES t1(e,c) + ) WITHOUT rowid; + INSERT INTO t1 VALUES(1,2,3,5,5,3); + INSERT INTO t1 VALUES(2,3,4,6,6,4); + INSERT INTO t1 VALUES('x','y',1.5,'fizzle','fizzle',1.5); + SELECT *, '|' FROM t1 ORDER BY a, b; +} {1 2 3 5 5 3 | 2 3 4 6 6 4 | x y 1.5 fizzle fizzle 1.5 |} + +do_execsql_test without_rowid3-16.4.2.2 { + UPDATE t1 SET c=99, f=99 WHERE a=1; + SELECT *, '|' FROM t1 ORDER BY a, b; +} {1 2 99 5 5 99 | 2 3 4 6 6 4 | x y 1.5 fizzle fizzle 1.5 |} + +do_execsql_test without_rowid3-16.4.2.3 { + UPDATE t1 SET e=876, d=876 WHERE a=2; + SELECT *, '|' FROM t1 ORDER BY a, b; +} {1 2 99 5 5 99 | 2 3 4 876 876 4 | x y 1.5 fizzle fizzle 1.5 |} + +do_test without_rowid3-16.4.2.4 { + catchsql { + UPDATE t1 SET c=11, e=22 WHERE a=1; + } +} {1 {FOREIGN KEY constraint failed}} + +do_test without_rowid3-16.4.2.5 { + catchsql { + UPDATE t1 SET d=11, f=22 WHERE a=1; + } +} {1 {FOREIGN KEY constraint failed}} + +do_execsql_test without_rowid3-16.4.2.6 { + DELETE FROM t1 WHERE a=1; + SELECT *, '|' FROM t1 ORDER BY a, b; +} {2 3 4 876 876 4 | x y 1.5 fizzle fizzle 1.5 |} + + +#------------------------------------------------------------------------- +# This next block of tests, without_rowid3-17.*, tests that if "PRAGMA count_changes" +# is turned on statements that violate immediate FK constraints return +# SQLITE_CONSTRAINT immediately, not after returning a number of rows. +# Whereas statements that violate deferred FK constraints return the number +# of rows before failing. +# +# Also test that rows modified by FK actions are not counted in either the +# returned row count or the values returned by sqlite3_changes(). Like +# trigger related changes, they are included in sqlite3_total_changes() though. +# +drop_all_tables +do_test without_rowid3-17.1.1 { + execsql { PRAGMA count_changes = 1 } + execsql { + CREATE TABLE one(a, b, c, UNIQUE(b, c)); + CREATE TABLE two(d, e, f, FOREIGN KEY(e, f) REFERENCES one(b, c)); + INSERT INTO one VALUES(1, 2, 3); + } +} {1} +do_test without_rowid3-17.1.2 { + set STMT [sqlite3_prepare_v2 db "INSERT INTO two VALUES(4, 5, 6)" -1 dummy] + sqlite3_step $STMT +} {SQLITE_CONSTRAINT} +verify_ex_errcode without_rowid3-17.1.2b SQLITE_CONSTRAINT_FOREIGNKEY +ifcapable autoreset { + do_test without_rowid3-17.1.3 { + sqlite3_step $STMT + } {SQLITE_CONSTRAINT} + verify_ex_errcode without_rowid3-17.1.3b SQLITE_CONSTRAINT_FOREIGNKEY +} else { + do_test without_rowid3-17.1.3 { + sqlite3_step $STMT + } {SQLITE_MISUSE} +} +do_test without_rowid3-17.1.4 { + sqlite3_finalize $STMT +} {SQLITE_CONSTRAINT} +verify_ex_errcode without_rowid3-17.1.4b SQLITE_CONSTRAINT_FOREIGNKEY +do_test without_rowid3-17.1.5 { + execsql { + INSERT INTO one VALUES(2, 3, 4); + INSERT INTO one VALUES(3, 4, 5); + INSERT INTO two VALUES(1, 2, 3); + INSERT INTO two VALUES(2, 3, 4); + INSERT INTO two VALUES(3, 4, 5); + } +} {1 1 1 1 1} +do_test without_rowid3-17.1.6 { + catchsql { + BEGIN; + INSERT INTO one VALUES(0, 0, 0); + UPDATE two SET e=e+1, f=f+1; + } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-17.1.7 { + execsql { SELECT * FROM one } +} {1 2 3 2 3 4 3 4 5 0 0 0} +do_test without_rowid3-17.1.8 { + execsql { SELECT * FROM two } +} {1 2 3 2 3 4 3 4 5} +do_test without_rowid3-17.1.9 { + execsql COMMIT +} {} +do_test without_rowid3-17.1.10 { + execsql { + CREATE TABLE three( + g, h, i, + FOREIGN KEY(h, i) REFERENCES one(b, c) DEFERRABLE INITIALLY DEFERRED + ); + } +} {} +do_test without_rowid3-17.1.11 { + set STMT [sqlite3_prepare_v2 db "INSERT INTO three VALUES(7, 8, 9)" -1 dummy] + sqlite3_step $STMT +} {SQLITE_ROW} +do_test without_rowid3-17.1.12 { + sqlite3_column_text $STMT 0 +} {1} +do_test without_rowid3-17.1.13 { + sqlite3_step $STMT +} {SQLITE_CONSTRAINT} +verify_ex_errcode without_rowid3-17.1.13b SQLITE_CONSTRAINT_FOREIGNKEY +do_test without_rowid3-17.1.14 { + sqlite3_finalize $STMT +} {SQLITE_CONSTRAINT} +verify_ex_errcode without_rowid3-17.1.14b SQLITE_CONSTRAINT_FOREIGNKEY + +drop_all_tables +do_test without_rowid3-17.2.1 { + execsql { + CREATE TABLE high("a'b!" PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE low( + c, + "d&6" REFERENCES high ON UPDATE CASCADE ON DELETE CASCADE + ); + } +} {} +do_test without_rowid3-17.2.2 { + execsql { + INSERT INTO high VALUES('a', 'b'); + INSERT INTO low VALUES('b', 'a'); + } + db changes +} {1} +set nTotal [db total_changes] +do_test without_rowid3-17.2.3 { + execsql { UPDATE high SET "a'b!" = 'c' } +} {1} +do_test without_rowid3-17.2.4 { + db changes +} {1} +do_test without_rowid3-17.2.5 { + expr [db total_changes] - $nTotal +} {2} +do_test without_rowid3-17.2.6 { + execsql { SELECT * FROM high ; SELECT * FROM low } +} {c b b c} +do_test without_rowid3-17.2.7 { + execsql { DELETE FROM high } +} {1} +do_test without_rowid3-17.2.8 { + db changes +} {1} +do_test without_rowid3-17.2.9 { + expr [db total_changes] - $nTotal +} {4} +do_test without_rowid3-17.2.10 { + execsql { SELECT * FROM high ; SELECT * FROM low } +} {} +execsql { PRAGMA count_changes = 0 } + +#------------------------------------------------------------------------- +# Test that the authorization callback works. +# + +ifcapable auth { + do_test without_rowid3-18.1 { + execsql { + CREATE TABLE long(a, b PRIMARY KEY, c) WITHOUT rowid; + CREATE TABLE short(d, e, f REFERENCES long); + CREATE TABLE mid(g, h, i REFERENCES long DEFERRABLE INITIALLY DEFERRED); + } + } {} + + proc auth {args} {eval lappend ::authargs $args ; return SQLITE_OK} + db auth auth + + # An insert on the parent table must read the child key of any deferred + # foreign key constraints. But not the child key of immediate constraints. + set authargs {} + do_test without_rowid3-18.2 { + execsql { INSERT INTO long VALUES(1, 2, 3) } + set authargs + } {SQLITE_INSERT long {} main {} SQLITE_READ mid i main {}} + + # An insert on the child table of an immediate constraint must read the + # parent key columns (to see if it is a violation or not). + set authargs {} + do_test without_rowid3-18.3 { + execsql { INSERT INTO short VALUES(1, 3, 2) } + set authargs + } {SQLITE_INSERT short {} main {} SQLITE_READ long b main {}} + + # As must an insert on the child table of a deferred constraint. + set authargs {} + do_test without_rowid3-18.4 { + execsql { INSERT INTO mid VALUES(1, 3, 2) } + set authargs + } {SQLITE_INSERT mid {} main {} SQLITE_READ long b main {}} + + do_test without_rowid3-18.5 { + execsql { + CREATE TABLE nought(a, b PRIMARY KEY, c) WITHOUT rowid; + CREATE TABLE cross(d, e, f, + FOREIGN KEY(e) REFERENCES nought(b) ON UPDATE CASCADE + ); + } + execsql { INSERT INTO nought VALUES(2, 1, 2) } + execsql { INSERT INTO cross VALUES(0, 1, 0) } + set authargs [list] + execsql { UPDATE nought SET b = 5 } + set authargs + } {SQLITE_UPDATE nought b main {} SQLITE_READ cross e main {} SQLITE_READ cross e main {} SQLITE_READ nought b main {} SQLITE_READ nought b main {} SQLITE_READ nought b main {} SQLITE_UPDATE cross e main {} SQLITE_READ nought b main {} SQLITE_READ cross e main {} SQLITE_READ nought b main {} SQLITE_READ nought b main {}} + + do_test without_rowid3-18.6 { + execsql {SELECT * FROM cross} + } {0 5 0} + + do_test without_rowid3-18.7 { + execsql { + CREATE TABLE one(a INT PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE two(b, c REFERENCES one); + INSERT INTO one VALUES(101, 102); + } + set authargs [list] + execsql { INSERT INTO two VALUES(100, 101); } + set authargs + } {SQLITE_INSERT two {} main {} SQLITE_READ one a main {}} + + # Return SQLITE_IGNORE to requests to read from the parent table. This + # causes inserts of non-NULL keys into the child table to fail. + # + rename auth {} + proc auth {args} { + if {[lindex $args 1] == "long"} {return SQLITE_IGNORE} + return SQLITE_OK + } + do_test without_rowid3-18.8 { + catchsql { INSERT INTO short VALUES(1, 3, 2) } + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-18.9 { + execsql { INSERT INTO short VALUES(1, 3, NULL) } + } {} + do_test without_rowid3-18.10 { + execsql { SELECT * FROM short } + } {1 3 2 1 3 {}} + do_test without_rowid3-18.11 { + catchsql { UPDATE short SET f = 2 WHERE f IS NULL } + } {1 {FOREIGN KEY constraint failed}} + + db auth {} + unset authargs +} + + +do_test without_rowid3-19.1 { + execsql { + CREATE TABLE main(id INT PRIMARY KEY) WITHOUT rowid; + CREATE TABLE sub(id INT REFERENCES main(id)); + INSERT INTO main VALUES(1); + INSERT INTO main VALUES(2); + INSERT INTO sub VALUES(2); + } +} {} +do_test without_rowid3-19.2 { + set S [sqlite3_prepare_v2 db "DELETE FROM main WHERE id = ?" -1 dummy] + sqlite3_bind_int $S 1 2 + sqlite3_step $S +} {SQLITE_CONSTRAINT} +verify_ex_errcode without_rowid3-19.2b SQLITE_CONSTRAINT_FOREIGNKEY +do_test without_rowid3-19.3 { + sqlite3_reset $S +} {SQLITE_CONSTRAINT} +verify_ex_errcode without_rowid3-19.3b SQLITE_CONSTRAINT_FOREIGNKEY +do_test without_rowid3-19.4 { + sqlite3_bind_int $S 1 1 + sqlite3_step $S +} {SQLITE_DONE} +do_test without_rowid3-19.4 { + sqlite3_finalize $S +} {SQLITE_OK} + +drop_all_tables +do_test without_rowid3-20.1 { + execsql { + CREATE TABLE pp(a PRIMARY KEY, b) WITHOUT rowid; + CREATE TABLE cc(c PRIMARY KEY, d REFERENCES pp) WITHOUT rowid; + } +} {} + +foreach {tn insert} { + 1 "INSERT" + 2 "INSERT OR IGNORE" + 3 "INSERT OR ABORT" + 4 "INSERT OR ROLLBACK" + 5 "INSERT OR REPLACE" + 6 "INSERT OR FAIL" +} { + do_test without_rowid3-20.2.$tn.1 { + catchsql "$insert INTO cc VALUES(1, 2)" + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-20.2.$tn.2 { + execsql { SELECT * FROM cc } + } {} + do_test without_rowid3-20.2.$tn.3 { + execsql { + BEGIN; + INSERT INTO pp VALUES(2, 'two'); + INSERT INTO cc VALUES(1, 2); + } + catchsql "$insert INTO cc VALUES(3, 4)" + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-20.2.$tn.4 { + execsql { COMMIT ; SELECT * FROM cc } + } {1 2} + do_test without_rowid3-20.2.$tn.5 { + execsql { DELETE FROM cc ; DELETE FROM pp } + } {} +} + +foreach {tn update} { + 1 "UPDATE" + 2 "UPDATE OR IGNORE" + 3 "UPDATE OR ABORT" + 4 "UPDATE OR ROLLBACK" + 5 "UPDATE OR REPLACE" + 6 "UPDATE OR FAIL" +} { + do_test without_rowid3-20.3.$tn.1 { + execsql { + INSERT INTO pp VALUES(2, 'two'); + INSERT INTO cc VALUES(1, 2); + } + } {} + do_test without_rowid3-20.3.$tn.2 { + catchsql "$update pp SET a = 1" + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-20.3.$tn.3 { + execsql { SELECT * FROM pp } + } {2 two} + do_test without_rowid3-20.3.$tn.4 { + catchsql "$update cc SET d = 1" + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-20.3.$tn.5 { + execsql { SELECT * FROM cc } + } {1 2} + do_test without_rowid3-20.3.$tn.6 { + execsql { + BEGIN; + INSERT INTO pp VALUES(3, 'three'); + } + catchsql "$update pp SET a = 1 WHERE a = 2" + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-20.3.$tn.7 { + execsql { COMMIT ; SELECT * FROM pp } + } {2 two 3 three} + do_test without_rowid3-20.3.$tn.8 { + execsql { + BEGIN; + INSERT INTO cc VALUES(2, 2); + } + catchsql "$update cc SET d = 1 WHERE c = 1" + } {1 {FOREIGN KEY constraint failed}} + do_test without_rowid3-20.3.$tn.9 { + execsql { COMMIT ; SELECT * FROM cc } + } {1 2 2 2} + do_test without_rowid3-20.3.$tn.10 { + execsql { DELETE FROM cc ; DELETE FROM pp } + } {} +} + +#------------------------------------------------------------------------- +# The following block of tests, those prefixed with "without_rowid3-genfkey.", +# are the same tests that were used to test the ".genfkey" command provided +# by the shell tool. So these tests show that the built-in foreign key +# implementation is more or less compatible with the triggers generated +# by genfkey. +# +drop_all_tables +do_test without_rowid3-genfkey.1.1 { + execsql { + CREATE TABLE t1(a INT PRIMARY KEY, b, c, UNIQUE(b, c)) WITHOUT rowid; + CREATE TABLE t2(e REFERENCES t1, f); + CREATE TABLE t3(g, h, i, FOREIGN KEY (h, i) REFERENCES t1(b, c)); + } +} {} +do_test without_rowid3-genfkey.1.2 { + catchsql { INSERT INTO t2 VALUES(1, 2) } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-genfkey.1.3 { + execsql { + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t2 VALUES(1, 2); + } +} {} +do_test without_rowid3-genfkey.1.4 { + execsql { INSERT INTO t2 VALUES(NULL, 3) } +} {} +do_test without_rowid3-genfkey.1.5 { + catchsql { UPDATE t2 SET e = 5 WHERE e IS NULL } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-genfkey.1.6 { + execsql { UPDATE t2 SET e = 1 WHERE e IS NULL } +} {} +do_test without_rowid3-genfkey.1.7 { + execsql { UPDATE t2 SET e = NULL WHERE f = 3 } +} {} +do_test without_rowid3-genfkey.1.8 { + catchsql { UPDATE t1 SET a = 10 } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-genfkey.1.9 { + catchsql { UPDATE t1 SET a = NULL } +} {1 {NOT NULL constraint failed: t1.a}} +do_test without_rowid3-genfkey.1.10 { + catchsql { DELETE FROM t1 } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-genfkey.1.11 { + execsql { UPDATE t2 SET e = NULL } +} {} +do_test without_rowid3-genfkey.1.12 { + execsql { + UPDATE t1 SET a = 10; + DELETE FROM t1; + DELETE FROM t2; + } +} {} +do_test without_rowid3-genfkey.1.13 { + execsql { + INSERT INTO t3 VALUES(1, NULL, NULL); + INSERT INTO t3 VALUES(1, 2, NULL); + INSERT INTO t3 VALUES(1, NULL, 3); + } +} {} +do_test without_rowid3-genfkey.1.14 { + catchsql { INSERT INTO t3 VALUES(3, 1, 4) } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-genfkey.1.15 { + execsql { + INSERT INTO t1 VALUES(1, 1, 4); + INSERT INTO t3 VALUES(3, 1, 4); + } +} {} +do_test without_rowid3-genfkey.1.16 { + catchsql { DELETE FROM t1 } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-genfkey.1.17 { + catchsql { UPDATE t1 SET b = 10} +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-genfkey.1.18 { + execsql { UPDATE t1 SET a = 10} +} {} +do_test without_rowid3-genfkey.1.19 { + catchsql { UPDATE t3 SET h = 'hello' WHERE i = 3} +} {1 {FOREIGN KEY constraint failed}} + +drop_all_tables +do_test without_rowid3-genfkey.2.1 { + execsql { + CREATE TABLE t1(a INT PRIMARY KEY, b, c, UNIQUE(b, c)) WITHOUT rowid; + CREATE TABLE t2(e REFERENCES t1 ON UPDATE CASCADE ON DELETE CASCADE, f); + CREATE TABLE t3(g, h, i, + FOREIGN KEY (h, i) + REFERENCES t1(b, c) ON UPDATE CASCADE ON DELETE CASCADE + ); + } +} {} +do_test without_rowid3-genfkey.2.2 { + execsql { + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); + INSERT INTO t2 VALUES(1, 'one'); + INSERT INTO t2 VALUES(4, 'four'); + } +} {} +do_test without_rowid3-genfkey.2.3 { + execsql { + UPDATE t1 SET a = 2 WHERE a = 1; + SELECT * FROM t2; + } +} {2 one 4 four} +do_test without_rowid3-genfkey.2.4 { + execsql { + DELETE FROM t1 WHERE a = 4; + SELECT * FROM t2; + } +} {2 one} + +do_test without_rowid3-genfkey.2.5 { + execsql { + INSERT INTO t3 VALUES('hello', 2, 3); + UPDATE t1 SET c = 2; + SELECT * FROM t3; + } +} {hello 2 2} +do_test without_rowid3-genfkey.2.6 { + execsql { + DELETE FROM t1; + SELECT * FROM t3; + } +} {} + +drop_all_tables +do_test without_rowid3-genfkey.3.1 { + execsql { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, UNIQUE(c, b)) WITHOUT rowid; + CREATE TABLE t2(e REFERENCES t1 ON UPDATE SET NULL ON DELETE SET NULL, f); + CREATE TABLE t3(g, h, i, + FOREIGN KEY (h, i) + REFERENCES t1(b, c) ON UPDATE SET NULL ON DELETE SET NULL + ); + } +} {} +do_test without_rowid3-genfkey.3.2 { + execsql { + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); + INSERT INTO t2 VALUES(1, 'one'); + INSERT INTO t2 VALUES(4, 'four'); + } +} {} +do_test without_rowid3-genfkey.3.3 { + execsql { + UPDATE t1 SET a = 2 WHERE a = 1; + SELECT * FROM t2; + } +} {{} one 4 four} +do_test without_rowid3-genfkey.3.4 { + execsql { + DELETE FROM t1 WHERE a = 4; + SELECT * FROM t2; + } +} {{} one {} four} +do_test without_rowid3-genfkey.3.5 { + execsql { + INSERT INTO t3 VALUES('hello', 2, 3); + UPDATE t1 SET c = 2; + SELECT * FROM t3; + } +} {hello {} {}} +do_test without_rowid3-genfkey.3.6 { + execsql { + UPDATE t3 SET h = 2, i = 2; + DELETE FROM t1; + SELECT * FROM t3; + } +} {hello {} {}} + +#------------------------------------------------------------------------- +# Verify that ticket dd08e5a988d00decc4a543daa8dbbfab9c577ad8 has been +# fixed. +# +do_test without_rowid3-dd08e5.1.1 { + execsql { + PRAGMA foreign_keys=ON; + CREATE TABLE tdd08(a INTEGER PRIMARY KEY, b) WITHOUT rowid; + CREATE UNIQUE INDEX idd08 ON tdd08(a,b); + INSERT INTO tdd08 VALUES(200,300); + + CREATE TABLE tdd08_b(w,x,y, FOREIGN KEY(x,y) REFERENCES tdd08(a,b)); + INSERT INTO tdd08_b VALUES(100,200,300); + } +} {} +do_test without_rowid3-dd08e5.1.2 { + catchsql { + DELETE FROM tdd08; + } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-dd08e5.1.3 { + execsql { + SELECT * FROM tdd08; + } +} {200 300} +do_test without_rowid3-dd08e5.1.4 { + catchsql { + INSERT INTO tdd08_b VALUES(400,500,300); + } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-dd08e5.1.5 { + catchsql { + UPDATE tdd08_b SET x=x+1; + } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-dd08e5.1.6 { + catchsql { + UPDATE tdd08 SET a=a+1; + } +} {1 {FOREIGN KEY constraint failed}} + +#------------------------------------------------------------------------- +# Verify that ticket ce7c133ea6cc9ccdc1a60d80441f80b6180f5eba +# fixed. +# +do_test without_rowid3-ce7c13.1.1 { + execsql { + CREATE TABLE tce71(a INTEGER PRIMARY KEY, b) WITHOUT rowid; + CREATE UNIQUE INDEX ice71 ON tce71(a,b); + INSERT INTO tce71 VALUES(100,200); + CREATE TABLE tce72(w, x, y, FOREIGN KEY(x,y) REFERENCES tce71(a,b)); + INSERT INTO tce72 VALUES(300,100,200); + UPDATE tce71 set b = 200 where a = 100; + SELECT * FROM tce71, tce72; + } +} {100 200 300 100 200} +do_test without_rowid3-ce7c13.1.2 { + catchsql { + UPDATE tce71 set b = 201 where a = 100; + } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-ce7c13.1.3 { + catchsql { + UPDATE tce71 set a = 101 where a = 100; + } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-ce7c13.1.4 { + execsql { + CREATE TABLE tce73(a INTEGER PRIMARY KEY, b, UNIQUE(a,b)) WITHOUT rowid; + INSERT INTO tce73 VALUES(100,200); + CREATE TABLE tce74(w, x, y, FOREIGN KEY(x,y) REFERENCES tce73(a,b)); + INSERT INTO tce74 VALUES(300,100,200); + UPDATE tce73 set b = 200 where a = 100; + SELECT * FROM tce73, tce74; + } +} {100 200 300 100 200} +do_test without_rowid3-ce7c13.1.5 { + catchsql { + UPDATE tce73 set b = 201 where a = 100; + } +} {1 {FOREIGN KEY constraint failed}} +do_test without_rowid3-ce7c13.1.6 { + catchsql { + UPDATE tce73 set a = 101 where a = 100; + } +} {1 {FOREIGN KEY constraint failed}} + +finish_test ADDED test/without_rowid4.test Index: test/without_rowid4.test ================================================================== --- /dev/null +++ test/without_rowid4.test @@ -0,0 +1,764 @@ +# 2013-11-04 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Regression testing of FOR EACH ROW table triggers on WITHOUT ROWID +# tables. +# +# 1. Trigger execution order tests. +# These tests ensure that BEFORE and AFTER triggers are fired at the correct +# times relative to each other and the triggering statement. +# +# without_rowid4-1.1.*: ON UPDATE trigger execution model. +# without_rowid4-1.2.*: DELETE trigger execution model. +# without_rowid4-1.3.*: INSERT trigger execution model. +# +# 2. Trigger program execution tests. +# These tests ensure that trigger programs execute correctly (ie. that a +# trigger program can correctly execute INSERT, UPDATE, DELETE * SELECT +# statements, and combinations thereof). +# +# 3. Selective trigger execution +# This tests that conditional triggers (ie. UPDATE OF triggers and triggers +# with WHEN clauses) are fired only fired when they are supposed to be. +# +# without_rowid4-3.1: UPDATE OF triggers +# without_rowid4-3.2: WHEN clause +# +# 4. Cascaded trigger execution +# Tests that trigger-programs may cause other triggers to fire. Also that a +# trigger-program is never executed recursively. +# +# without_rowid4-4.1: Trivial cascading trigger +# without_rowid4-4.2: Trivial recursive trigger handling +# +# 5. Count changes behaviour. +# Verify that rows altered by triggers are not included in the return value +# of the "count changes" interface. +# +# 6. ON CONFLICT clause handling +# without_rowid4-6.1[a-f]: INSERT statements +# without_rowid4-6.2[a-f]: UPDATE statements +# +# 7. & 8. Triggers on views fire correctly. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +ifcapable {!trigger} { + finish_test + return +} + +# The tests in this file were written before SQLite supported recursive +# trigger invocation, and some tests depend on that to pass. So disable +# recursive triggers for this file. +catchsql { pragma recursive_triggers = off } + +# 1. +ifcapable subquery { + set ii 0 + set tbl_definitions [list \ + {CREATE TABLE tbl (a INTEGER PRIMARY KEY, b) WITHOUT rowid;} \ + {CREATE TABLE tbl (a, b PRIMARY KEY) WITHOUT rowid;} \ + {CREATE TABLE tbl (a PRIMARY KEY, b) WITHOUT rowid; + CREATE INDEX tbl_idx ON tbl(b);} \ + ] + ifcapable tempdb { + lappend tbl_definitions \ + {CREATE TEMP TABLE tbl (a PRIMARY KEY, b) WITHOUT rowid; + CREATE INDEX tbl_idx ON tbl(b);} + lappend tbl_definitions \ + {CREATE TEMP TABLE tbl (a PRIMARY KEY, b) WITHOUT rowid} + lappend tbl_definitions \ + {CREATE TEMPORARY TABLE tbl (a INTEGER PRIMARY KEY, b) WITHOUT rowid;} + } + foreach tbl_defn $tbl_definitions { + incr ii + catchsql { DROP INDEX tbl_idx; } + catchsql { + DROP TABLE rlog; + DROP TABLE clog; + DROP TABLE tbl; + DROP TABLE other_tbl; + } + + execsql $tbl_defn + + execsql { + INSERT INTO tbl VALUES(1, 2); + INSERT INTO tbl VALUES(3, 4); + + CREATE TABLE rlog (idx, old_a, old_b, db_sum_a, db_sum_b, new_a, new_b); + CREATE TABLE clog (idx, old_a, old_b, db_sum_a, db_sum_b, new_a, new_b); + + CREATE TRIGGER before_update_row BEFORE UPDATE ON tbl FOR EACH ROW + BEGIN + INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog), + old.a, old.b, + (SELECT coalesce(sum(a),0) FROM tbl), + (SELECT coalesce(sum(b),0) FROM tbl), + new.a, new.b); + END; + + CREATE TRIGGER after_update_row AFTER UPDATE ON tbl FOR EACH ROW + BEGIN + INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog), + old.a, old.b, + (SELECT coalesce(sum(a),0) FROM tbl), + (SELECT coalesce(sum(b),0) FROM tbl), + new.a, new.b); + END; + + CREATE TRIGGER conditional_update_row AFTER UPDATE ON tbl FOR EACH ROW + WHEN old.a = 1 + BEGIN + INSERT INTO clog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM clog), + old.a, old.b, + (SELECT coalesce(sum(a),0) FROM tbl), + (SELECT coalesce(sum(b),0) FROM tbl), + new.a, new.b); + END; + } + + do_test without_rowid4-1.$ii.1 { + set r {} + foreach v [execsql { + UPDATE tbl SET a = a * 10, b = b * 10; + SELECT * FROM rlog ORDER BY idx; + SELECT * FROM clog ORDER BY idx; + }] { + lappend r [expr {int($v)}] + } + set r + } [list 1 1 2 4 6 10 20 \ + 2 1 2 13 24 10 20 \ + 3 3 4 13 24 30 40 \ + 4 3 4 40 60 30 40 \ + 1 1 2 13 24 10 20 ] + + execsql { + DELETE FROM rlog; + DELETE FROM tbl; + INSERT INTO tbl VALUES (100, 100); + INSERT INTO tbl VALUES (300, 200); + CREATE TRIGGER delete_before_row BEFORE DELETE ON tbl FOR EACH ROW + BEGIN + INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog), + old.a, old.b, + (SELECT coalesce(sum(a),0) FROM tbl), + (SELECT coalesce(sum(b),0) FROM tbl), + 0, 0); + END; + + CREATE TRIGGER delete_after_row AFTER DELETE ON tbl FOR EACH ROW + BEGIN + INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog), + old.a, old.b, + (SELECT coalesce(sum(a),0) FROM tbl), + (SELECT coalesce(sum(b),0) FROM tbl), + 0, 0); + END; + } + do_test without_rowid4-1.$ii.2 { + set r {} + foreach v [execsql { + DELETE FROM tbl; + SELECT * FROM rlog; + }] { + lappend r [expr {int($v)}] + } + set r + } [list 1 100 100 400 300 0 0 \ + 2 100 100 300 200 0 0 \ + 3 300 200 300 200 0 0 \ + 4 300 200 0 0 0 0 ] + + execsql { + DELETE FROM rlog; + CREATE TRIGGER insert_before_row BEFORE INSERT ON tbl FOR EACH ROW + BEGIN + INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog), + 0, 0, + (SELECT coalesce(sum(a),0) FROM tbl), + (SELECT coalesce(sum(b),0) FROM tbl), + new.a, new.b); + END; + + CREATE TRIGGER insert_after_row AFTER INSERT ON tbl FOR EACH ROW + BEGIN + INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog), + 0, 0, + (SELECT coalesce(sum(a),0) FROM tbl), + (SELECT coalesce(sum(b),0) FROM tbl), + new.a, new.b); + END; + } + do_test without_rowid4-1.$ii.3 { + execsql { + + CREATE TABLE other_tbl(a, b); + INSERT INTO other_tbl VALUES(1, 2); + INSERT INTO other_tbl VALUES(3, 4); + -- INSERT INTO tbl SELECT * FROM other_tbl; + INSERT INTO tbl VALUES(5, 6); + DROP TABLE other_tbl; + + SELECT * FROM rlog; + } + } [list 1 0 0 0 0 5 6 \ + 2 0 0 5 6 5 6 ] + + integrity_check without_rowid4-1.$ii.4 + } + catchsql { + DROP TABLE rlog; + DROP TABLE clog; + DROP TABLE tbl; + DROP TABLE other_tbl; + } +} + +# 2. +set ii 0 +foreach tr_program { + {UPDATE tbl SET b = old.b;} + {INSERT INTO log VALUES(new.c, 2, 3);} + {DELETE FROM log WHERE a = 1;} + {INSERT INTO tbl VALUES(500, new.b * 10, 700); + UPDATE tbl SET c = old.c; + DELETE FROM log;} + {INSERT INTO log select * from tbl;} +} { + foreach test_varset [ list \ + { + set statement {UPDATE tbl SET c = 10 WHERE a = 1;} + set prep {INSERT INTO tbl VALUES(1, 2, 3);} + set newC 10 + set newB 2 + set newA 1 + set oldA 1 + set oldB 2 + set oldC 3 + } \ + { + set statement {DELETE FROM tbl WHERE a = 1;} + set prep {INSERT INTO tbl VALUES(1, 2, 3);} + set oldA 1 + set oldB 2 + set oldC 3 + } \ + { + set statement {INSERT INTO tbl VALUES(1, 2, 3);} + set newA 1 + set newB 2 + set newC 3 + } + ] \ + { + set statement {} + set prep {} + set newA {''} + set newB {''} + set newC {''} + set oldA {''} + set oldB {''} + set oldC {''} + + incr ii + + eval $test_varset + + set statement_type [string range $statement 0 5] + set tr_program_fixed $tr_program + if {$statement_type == "DELETE"} { + regsub -all new\.a $tr_program_fixed {''} tr_program_fixed + regsub -all new\.b $tr_program_fixed {''} tr_program_fixed + regsub -all new\.c $tr_program_fixed {''} tr_program_fixed + } + if {$statement_type == "INSERT"} { + regsub -all old\.a $tr_program_fixed {''} tr_program_fixed + regsub -all old\.b $tr_program_fixed {''} tr_program_fixed + regsub -all old\.c $tr_program_fixed {''} tr_program_fixed + } + + + set tr_program_cooked $tr_program + regsub -all new\.a $tr_program_cooked $newA tr_program_cooked + regsub -all new\.b $tr_program_cooked $newB tr_program_cooked + regsub -all new\.c $tr_program_cooked $newC tr_program_cooked + regsub -all old\.a $tr_program_cooked $oldA tr_program_cooked + regsub -all old\.b $tr_program_cooked $oldB tr_program_cooked + regsub -all old\.c $tr_program_cooked $oldC tr_program_cooked + + catchsql { + DROP TABLE tbl; + DROP TABLE log; + } + + execsql { + CREATE TABLE tbl(a PRIMARY KEY, b, c) WITHOUT rowid; + CREATE TABLE log(a, b, c); + } + + set query {SELECT * FROM tbl; SELECT * FROM log;} + set prep "$prep; INSERT INTO log VALUES(1, 2, 3);\ + INSERT INTO log VALUES(10, 20, 30);" + +# Check execution of BEFORE programs: + + set before_data [ execsql "$prep $tr_program_cooked $statement $query" ] + + execsql "DELETE FROM tbl; DELETE FROM log; $prep"; + execsql "CREATE TRIGGER the_trigger BEFORE [string range $statement 0 6]\ + ON tbl BEGIN $tr_program_fixed END;" + + do_test without_rowid4-2.$ii-before "execsql {$statement $query}" $before_data + + execsql "DROP TRIGGER the_trigger;" + execsql "DELETE FROM tbl; DELETE FROM log;" + +# Check execution of AFTER programs + set after_data [ execsql "$prep $statement $tr_program_cooked $query" ] + + execsql "DELETE FROM tbl; DELETE FROM log; $prep"; + execsql "CREATE TRIGGER the_trigger AFTER [string range $statement 0 6]\ + ON tbl BEGIN $tr_program_fixed END;" + + do_test without_rowid4-2.$ii-after "execsql {$statement $query}" $after_data + execsql "DROP TRIGGER the_trigger;" + + integrity_check without_rowid4-2.$ii-integrity + } +} +catchsql { + DROP TABLE tbl; + DROP TABLE log; +} + +# 3. + +# without_rowid4-3.1: UPDATE OF triggers +execsql { + CREATE TABLE tbl (a, b, c, d, PRIMARY KEY(a,b,c,d)) WITHOUT rowid; + CREATE TABLE log (a); + INSERT INTO log VALUES (0); + INSERT INTO tbl VALUES (0, 0, 0, 0); + INSERT INTO tbl VALUES (1, 0, 0, 0); + CREATE TRIGGER tbl_after_update_cd BEFORE UPDATE OF c, d ON tbl + BEGIN + UPDATE log SET a = a + 1; + END; +} +do_test without_rowid4-3.1 { + execsql { + UPDATE tbl SET b = 1, c = 10; -- 2 + UPDATE tbl SET b = 10; -- 0 + UPDATE tbl SET d = 4 WHERE a = 0; --1 + UPDATE tbl SET a = 4, b = 10; --0 + SELECT * FROM log; + } +} {3} +execsql { + DROP TABLE tbl; + DROP TABLE log; +} + +# without_rowid4-3.2: WHEN clause +set when_triggers [list {t1 BEFORE INSERT ON tbl WHEN new.a > 20}] +ifcapable subquery { + lappend when_triggers \ + {t2 BEFORE INSERT ON tbl WHEN (SELECT count(*) FROM tbl) = 0} +} + +execsql { + CREATE TABLE tbl (a, b, c, d); + CREATE TABLE log (a); + INSERT INTO log VALUES (0); +} + +foreach trig $when_triggers { + execsql "CREATE TRIGGER $trig BEGIN UPDATE log set a = a + 1; END;" +} + +ifcapable subquery { + set t232 {1 0 1} +} else { + set t232 {0 0 1} +} +do_test without_rowid4-3.2 { + execsql { + + INSERT INTO tbl VALUES(0, 0, 0, 0); -- 1 (ifcapable subquery) + SELECT * FROM log; + UPDATE log SET a = 0; + + INSERT INTO tbl VALUES(0, 0, 0, 0); -- 0 + SELECT * FROM log; + UPDATE log SET a = 0; + + INSERT INTO tbl VALUES(200, 0, 0, 0); -- 1 + SELECT * FROM log; + UPDATE log SET a = 0; + } +} $t232 +execsql { + DROP TABLE tbl; + DROP TABLE log; +} +integrity_check without_rowid4-3.3 + +# Simple cascaded trigger +execsql { + CREATE TABLE tblA(a, b, PRIMARY KEY(a,b)) WITHOUT rowid; + CREATE TABLE tblB(a, b, PRIMARY KEY(a,b)) WITHOUT rowid; + CREATE TABLE tblC(a, b, PRIMARY KEY(a,b)) WITHOUT rowid; + + CREATE TRIGGER tr1 BEFORE INSERT ON tblA BEGIN + INSERT INTO tblB values(new.a, new.b); + END; + + CREATE TRIGGER tr2 BEFORE INSERT ON tblB BEGIN + INSERT INTO tblC values(new.a, new.b); + END; +} +do_test without_rowid4-4.1 { + execsql { + INSERT INTO tblA values(1, 2); + SELECT * FROM tblA; + SELECT * FROM tblB; + SELECT * FROM tblC; + } +} {1 2 1 2 1 2} +execsql { + DROP TABLE tblA; + DROP TABLE tblB; + DROP TABLE tblC; +} + +# Simple recursive trigger +execsql { + CREATE TABLE tbl(a, b, c, PRIMARY KEY(c,a,b)) WITHOUT rowid; + CREATE TRIGGER tbl_trig BEFORE INSERT ON tbl + BEGIN + INSERT INTO tbl VALUES (new.a, new.b, new.c+1); + END; +} +do_test without_rowid4-4.2 { + execsql { + INSERT INTO tbl VALUES (1, 2, 3); + select * from tbl; + } +} {1 2 3 1 2 4} +execsql { + DROP TABLE tbl; +} + +# 5. +execsql { + CREATE TABLE tbl(a, b, c, PRIMARY KEY(c,a,b)) WITHOUT rowid; + CREATE TRIGGER tbl_trig BEFORE INSERT ON tbl + BEGIN + INSERT INTO tbl VALUES (1, 2, 3); + INSERT INTO tbl VALUES (2, 2, 3); + UPDATE tbl set b = 10 WHERE a = 1; + DELETE FROM tbl WHERE a = 1; + DELETE FROM tbl; + END; +} +do_test without_rowid4-5 { + execsql { + INSERT INTO tbl VALUES(100, 200, 300); + } + db changes +} {1} +execsql { + DROP TABLE tbl; +} + +ifcapable conflict { + # Handling of ON CONFLICT by INSERT statements inside triggers + execsql { + CREATE TABLE tbl (a PRIMARY KEY, b, c) WITHOUT rowid; + CREATE TRIGGER ai_tbl AFTER INSERT ON tbl BEGIN + INSERT OR IGNORE INTO tbl values (new.a, 0, 0); + END; + } + do_test without_rowid4-6.1a { + execsql { + BEGIN; + INSERT INTO tbl values (1, 2, 3); + SELECT * from tbl; + } + } {1 2 3} + do_test without_rowid4-6.1b { + catchsql { + INSERT OR ABORT INTO tbl values (2, 2, 3); + } + } {1 {UNIQUE constraint failed: tbl.a}} + do_test without_rowid4-6.1c { + execsql { + SELECT * from tbl; + } + } {1 2 3} + do_test without_rowid4-6.1d { + catchsql { + INSERT OR FAIL INTO tbl values (2, 2, 3); + } + } {1 {UNIQUE constraint failed: tbl.a}} + do_test without_rowid4-6.1e { + execsql { + SELECT * from tbl; + } + } {1 2 3 2 2 3} + do_test without_rowid4-6.1f { + execsql { + INSERT OR REPLACE INTO tbl values (2, 2, 3); + SELECT * from tbl; + } + } {1 2 3 2 0 0} + do_test without_rowid4-6.1g { + catchsql { + INSERT OR ROLLBACK INTO tbl values (3, 2, 3); + } + } {1 {UNIQUE constraint failed: tbl.a}} + do_test without_rowid4-6.1h { + execsql { + SELECT * from tbl; + } + } {} + execsql {DELETE FROM tbl} + + + # Handling of ON CONFLICT by UPDATE statements inside triggers + execsql { + INSERT INTO tbl values (4, 2, 3); + INSERT INTO tbl values (6, 3, 4); + CREATE TRIGGER au_tbl AFTER UPDATE ON tbl BEGIN + UPDATE OR IGNORE tbl SET a = new.a, c = 10; + END; + } + do_test without_rowid4-6.2a { + execsql { + BEGIN; + UPDATE tbl SET a = 1 WHERE a = 4; + SELECT * from tbl; + } + } {1 2 10 6 3 4} + do_test without_rowid4-6.2b { + catchsql { + UPDATE OR ABORT tbl SET a = 4 WHERE a = 1; + } + } {1 {UNIQUE constraint failed: tbl.a}} + do_test without_rowid4-6.2c { + execsql { + SELECT * from tbl; + } + } {1 2 10 6 3 4} + do_test without_rowid4-6.2d { + catchsql { + UPDATE OR FAIL tbl SET a = 4 WHERE a = 1; + } + } {1 {UNIQUE constraint failed: tbl.a}} + do_test without_rowid4-6.2e { + execsql { + SELECT * from tbl; + } + } {4 2 10 6 3 4} + do_test without_rowid4-6.2f.1 { + execsql { + UPDATE OR REPLACE tbl SET a = 1 WHERE a = 4; + SELECT * from tbl; + } + } {1 3 10} + do_test without_rowid4-6.2f.2 { + execsql { + INSERT INTO tbl VALUES (2, 3, 4); + SELECT * FROM tbl; + } + } {1 3 10 2 3 4} + do_test without_rowid4-6.2g { + catchsql { + UPDATE OR ROLLBACK tbl SET a = 4 WHERE a = 1; + } + } {1 {UNIQUE constraint failed: tbl.a}} + do_test without_rowid4-6.2h { + execsql { + SELECT * from tbl; + } + } {4 2 3 6 3 4} + execsql { + DROP TABLE tbl; + } +} ; # ifcapable conflict + +# 7. Triggers on views +ifcapable view { + +do_test without_rowid4-7.1 { + execsql { + CREATE TABLE ab(a, b, PRIMARY KEY(a,b)) WITHOUT rowid; + CREATE TABLE cd(c, d, PRIMARY KEY(c,d)) WITHOUT rowid; + INSERT INTO ab VALUES (1, 2); + INSERT INTO ab VALUES (0, 0); + INSERT INTO cd VALUES (3, 4); + + CREATE TABLE tlog(ii INTEGER PRIMARY KEY, + olda, oldb, oldc, oldd, newa, newb, newc, newd); + + CREATE VIEW abcd AS SELECT a, b, c, d FROM ab, cd; + + CREATE TRIGGER before_update INSTEAD OF UPDATE ON abcd BEGIN + INSERT INTO tlog VALUES(NULL, + old.a, old.b, old.c, old.d, new.a, new.b, new.c, new.d); + END; + CREATE TRIGGER after_update INSTEAD OF UPDATE ON abcd BEGIN + INSERT INTO tlog VALUES(NULL, + old.a, old.b, old.c, old.d, new.a, new.b, new.c, new.d); + END; + + CREATE TRIGGER before_delete INSTEAD OF DELETE ON abcd BEGIN + INSERT INTO tlog VALUES(NULL, + old.a, old.b, old.c, old.d, 0, 0, 0, 0); + END; + CREATE TRIGGER after_delete INSTEAD OF DELETE ON abcd BEGIN + INSERT INTO tlog VALUES(NULL, + old.a, old.b, old.c, old.d, 0, 0, 0, 0); + END; + + CREATE TRIGGER before_insert INSTEAD OF INSERT ON abcd BEGIN + INSERT INTO tlog VALUES(NULL, + 0, 0, 0, 0, new.a, new.b, new.c, new.d); + END; + CREATE TRIGGER after_insert INSTEAD OF INSERT ON abcd BEGIN + INSERT INTO tlog VALUES(NULL, + 0, 0, 0, 0, new.a, new.b, new.c, new.d); + END; + } +} {}; + +do_test without_rowid4-7.2 { + execsql { + UPDATE abcd SET a = 100, b = 5*5 WHERE a = 1; + DELETE FROM abcd WHERE a = 1; + INSERT INTO abcd VALUES(10, 20, 30, 40); + SELECT * FROM tlog; + } +} [ list 1 1 2 3 4 100 25 3 4 \ + 2 1 2 3 4 100 25 3 4 \ + 3 1 2 3 4 0 0 0 0 \ + 4 1 2 3 4 0 0 0 0 \ + 5 0 0 0 0 10 20 30 40 \ + 6 0 0 0 0 10 20 30 40 ] + +do_test without_rowid4-7.3 { + execsql { + DELETE FROM tlog; + INSERT INTO abcd VALUES(10, 20, 30, 40); + UPDATE abcd SET a = 100, b = 5*5 WHERE a = 1; + DELETE FROM abcd WHERE a = 1; + SELECT * FROM tlog; + } +} [ list \ + 1 0 0 0 0 10 20 30 40 \ + 2 0 0 0 0 10 20 30 40 \ + 3 1 2 3 4 100 25 3 4 \ + 4 1 2 3 4 100 25 3 4 \ + 5 1 2 3 4 0 0 0 0 \ + 6 1 2 3 4 0 0 0 0 \ +] +do_test without_rowid4-7.4 { + execsql { + DELETE FROM tlog; + DELETE FROM abcd WHERE a = 1; + INSERT INTO abcd VALUES(10, 20, 30, 40); + UPDATE abcd SET a = 100, b = 5*5 WHERE a = 1; + SELECT * FROM tlog; + } +} [ list \ + 1 1 2 3 4 0 0 0 0 \ + 2 1 2 3 4 0 0 0 0 \ + 3 0 0 0 0 10 20 30 40 \ + 4 0 0 0 0 10 20 30 40 \ + 5 1 2 3 4 100 25 3 4 \ + 6 1 2 3 4 100 25 3 4 \ +] + +do_test without_rowid4-8.1 { + execsql { + CREATE TABLE t1(a,b,c, PRIMARY KEY(a,b,c)) WITHOUT rowid; + INSERT INTO t1 VALUES(1,2,3); + CREATE VIEW v1 AS + SELECT a+b AS x, b+c AS y, a+c AS z FROM t1; + SELECT * FROM v1; + } +} {3 5 4} +do_test without_rowid4-8.2 { + execsql { + CREATE TABLE v1log(a,b,c,d,e,f); + CREATE TRIGGER r1 INSTEAD OF DELETE ON v1 BEGIN + INSERT INTO v1log VALUES(OLD.x,NULL,OLD.y,NULL,OLD.z,NULL); + END; + DELETE FROM v1 WHERE x=1; + SELECT * FROM v1log; + } +} {} +do_test without_rowid4-8.3 { + execsql { + DELETE FROM v1 WHERE x=3; + SELECT * FROM v1log; + } +} {3 {} 5 {} 4 {}} +do_test without_rowid4-8.4 { + execsql { + INSERT INTO t1 VALUES(4,5,6); + DELETE FROM v1log; + DELETE FROM v1 WHERE y=11; + SELECT * FROM v1log; + } +} {9 {} 11 {} 10 {}} +do_test without_rowid4-8.5 { + execsql { + CREATE TRIGGER r2 INSTEAD OF INSERT ON v1 BEGIN + INSERT INTO v1log VALUES(NULL,NEW.x,NULL,NEW.y,NULL,NEW.z); + END; + DELETE FROM v1log; + INSERT INTO v1 VALUES(1,2,3); + SELECT * FROM v1log; + } +} {{} 1 {} 2 {} 3} +do_test without_rowid4-8.6 { + execsql { + CREATE TRIGGER r3 INSTEAD OF UPDATE ON v1 BEGIN + INSERT INTO v1log VALUES(OLD.x,NEW.x,OLD.y,NEW.y,OLD.z,NEW.z); + END; + DELETE FROM v1log; + UPDATE v1 SET x=x+100, y=y+200, z=z+300; + SELECT * FROM v1log; + } +} {3 103 5 205 4 304 9 109 11 211 10 310} + +# At one point the following was causing a segfault. +do_test without_rowid4-9.1 { + execsql { + CREATE TABLE t3(a TEXT, b TEXT); + CREATE VIEW v3 AS SELECT t3.a FROM t3; + CREATE TRIGGER trig1 INSTEAD OF DELETE ON v3 BEGIN + SELECT 1; + END; + DELETE FROM v3 WHERE a = 1; + } +} {} + +} ;# ifcapable view + +integrity_check without_rowid4-9.9 + +finish_test Index: tool/mkkeywordhash.c ================================================================== --- tool/mkkeywordhash.c +++ tool/mkkeywordhash.c @@ -260,10 +260,11 @@ { "USING", "TK_USING", ALWAYS }, { "VACUUM", "TK_VACUUM", VACUUM }, { "VALUES", "TK_VALUES", ALWAYS }, { "VIEW", "TK_VIEW", VIEW }, { "VIRTUAL", "TK_VIRTUAL", VTAB }, + { "WITHOUT", "TK_WITHOUT", ALWAYS }, { "WHEN", "TK_WHEN", ALWAYS }, { "WHERE", "TK_WHERE", ALWAYS }, }; /* Number of keywords */ Index: tool/spaceanal.tcl ================================================================== --- tool/spaceanal.tcl +++ tool/spaceanal.tcl @@ -569,11 +569,11 @@ } subreport {All tables} {NOT is_index} 0 if {$nindex>0} { subreport {All indices} {is_index} 0 } -foreach tbl [mem eval {SELECT name FROM space_used WHERE NOT is_index +foreach tbl [mem eval {SELECT DISTINCT tblname name FROM space_used ORDER BY name}] { set qn [quote $tbl] set name [string toupper $tbl] set n [mem eval {SELECT count(*) FROM space_used WHERE tblname=$tbl}] if {$n>1} {