Index: src/alter.c ================================================================== --- src/alter.c +++ src/alter.c @@ -194,14 +194,14 @@ if( pTab->pSchema!=pTempSchema ){ sqlite3 *db = pParse->db; for(pTrig=sqlite3TriggerList(pParse, pTab); pTrig; pTrig=pTrig->pNext){ if( pTrig->pSchema==pTempSchema ){ if( !zWhere ){ - zWhere = sqlite3MPrintf(db, "name=%Q", pTrig->name); + zWhere = sqlite3MPrintf(db, "name=%Q", pTrig->zName); }else{ tmp = zWhere; - zWhere = sqlite3MPrintf(db, "%s OR name=%Q", zWhere, pTrig->name); + zWhere = sqlite3MPrintf(db, "%s OR name=%Q", zWhere, pTrig->zName); sqlite3DbFree(db, tmp); } } } } @@ -233,11 +233,11 @@ #ifndef SQLITE_OMIT_TRIGGER /* Drop any table triggers from the internal schema. */ for(pTrig=sqlite3TriggerList(pParse, pTab); pTrig; pTrig=pTrig->pNext){ int iTrigDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema); assert( iTrigDb==iDb || iTrigDb==1 ); - sqlite3VdbeAddOp4(v, OP_DropTrigger, iTrigDb, 0, 0, pTrig->name, 0); + sqlite3VdbeAddOp4(v, OP_DropTrigger, iTrigDb, 0, 0, pTrig->zName, 0); } #endif /* Drop the table and index from the internal schema */ sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0); Index: src/auth.c ================================================================== --- src/auth.c +++ src/auth.c @@ -111,40 +111,38 @@ Table *pTab = 0; /* The table being read */ const char *zCol; /* Name of the column of the table */ int iSrc; /* Index in pTabList->a[] of table being read */ const char *zDBase; /* Name of database being accessed */ int iDb; /* The index of the database the expression refers to */ + int iCol; /* Index of column in table */ if( db->xAuth==0 ) return; - assert( pExpr->op==TK_COLUMN ); iDb = sqlite3SchemaToIndex(pParse->db, pSchema); if( iDb<0 ){ /* An attempt to read a column out of a subquery or other ** temporary table. */ return; } - if( pTabList ){ + + assert( pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER ); + if( pExpr->op==TK_TRIGGER ){ + pTab = pParse->pTriggerTab; + }else{ + assert( pTabList ); for(iSrc=0; iSrcnSrc; iSrc++){ if( pExpr->iTable==pTabList->a[iSrc].iCursor ){ pTab = pTabList->a[iSrc].pTab; - break; + break; } } } - if( !pTab ){ - TriggerStack *pStack = pParse->trigStack; - if( ALWAYS(pStack) ){ - /* This must be an attempt to read the NEW or OLD pseudo-tables - ** of a trigger. */ - assert( pExpr->iTable==pStack->newIdx || pExpr->iTable==pStack->oldIdx ); - pTab = pStack->pTab; - } - } + iCol = pExpr->iColumn; if( NEVER(pTab==0) ) return; - if( pExpr->iColumn>=0 ){ - assert( pExpr->iColumnnCol ); - zCol = pTab->aCol[pExpr->iColumn].zName; + + if( iCol>=0 ){ + assert( iColnCol ); + zCol = pTab->aCol[iCol].zName; }else if( pTab->iPKey>=0 ){ assert( pTab->iPKeynCol ); zCol = pTab->aCol[pTab->iPKey].zName; }else{ zCol = "ROWID"; Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -62,35 +62,36 @@ int iDb, /* Index of the database containing the table to lock */ int iTab, /* Root page number of the table to be locked */ u8 isWriteLock, /* True for a write lock */ const char *zName /* Name of the table to be locked */ ){ + Parse *pToplevel = sqlite3ParseToplevel(pParse); int i; int nBytes; TableLock *p; - assert( iDb>=0 ); - for(i=0; inTableLock; i++){ - p = &pParse->aTableLock[i]; + + for(i=0; inTableLock; i++){ + p = &pToplevel->aTableLock[i]; if( p->iDb==iDb && p->iTab==iTab ){ p->isWriteLock = (p->isWriteLock || isWriteLock); return; } } - nBytes = sizeof(TableLock) * (pParse->nTableLock+1); - pParse->aTableLock = - sqlite3DbReallocOrFree(pParse->db, pParse->aTableLock, nBytes); - if( pParse->aTableLock ){ - p = &pParse->aTableLock[pParse->nTableLock++]; + nBytes = sizeof(TableLock) * (pToplevel->nTableLock+1); + pToplevel->aTableLock = + sqlite3DbReallocOrFree(pToplevel->db, pToplevel->aTableLock, nBytes); + if( pToplevel->aTableLock ){ + p = &pToplevel->aTableLock[pToplevel->nTableLock++]; p->iDb = iDb; p->iTab = iTab; p->isWriteLock = isWriteLock; p->zName = zName; }else{ - pParse->nTableLock = 0; - pParse->db->mallocFailed = 1; + pToplevel->nTableLock = 0; + pToplevel->db->mallocFailed = 1; } } /* ** Code an OP_TableLock instruction for each table locked by the @@ -192,11 +193,11 @@ FILE *trace = (db->flags & SQLITE_VdbeTrace)!=0 ? stdout : 0; sqlite3VdbeTrace(v, trace); #endif assert( pParse->iCacheLevel==0 ); /* Disables and re-enables match */ sqlite3VdbeMakeReady(v, pParse->nVar, pParse->nMem, - pParse->nTab, pParse->explain); + pParse->nTab, pParse->nMaxArg, pParse->explain); pParse->rc = SQLITE_DONE; pParse->colNamesSet = 0; }else if( pParse->rc==SQLITE_OK ){ pParse->rc = SQLITE_ERROR; } @@ -3420,30 +3421,30 @@ ** If iDb<0 then code the OP_Goto only - don't set flag to verify the ** schema on any databases. This can be used to position the OP_Goto ** early in the code, before we know if any database tables will be used. */ void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ - sqlite3 *db; - Vdbe *v; - int mask; - - v = sqlite3GetVdbe(pParse); - if( v==0 ) return; /* This only happens if there was a prior error */ - db = pParse->db; - if( pParse->cookieGoto==0 ){ - pParse->cookieGoto = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0)+1; + Parse *pToplevel = sqlite3ParseToplevel(pParse); + + if( pToplevel->cookieGoto==0 ){ + Vdbe *v = sqlite3GetVdbe(pToplevel); + if( v==0 ) return; /* This only happens if there was a prior error */ + pToplevel->cookieGoto = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0)+1; } if( iDb>=0 ){ + sqlite3 *db = pToplevel->db; + int mask; + assert( iDbnDb ); assert( db->aDb[iDb].pBt!=0 || iDb==1 ); assert( iDbcookieMask & mask)==0 ){ - pParse->cookieMask |= mask; - pParse->cookieValue[iDb] = db->aDb[iDb].pSchema->schema_cookie; + if( (pToplevel->cookieMask & mask)==0 ){ + pToplevel->cookieMask |= mask; + pToplevel->cookieValue[iDb] = db->aDb[iDb].pSchema->schema_cookie; if( !OMIT_TEMPDB && iDb==1 ){ - sqlite3OpenTempDatabase(pParse); + sqlite3OpenTempDatabase(pToplevel); } } } } @@ -3459,13 +3460,14 @@ ** rollback the whole transaction. For operations where all constraints ** can be checked before any changes are made to the database, it is never ** necessary to undo a write and the checkpoint should not be set. */ void sqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){ + Parse *pToplevel = sqlite3ParseToplevel(pParse); sqlite3CodeVerifySchema(pParse, iDb); - pParse->writeMask |= 1<nested==0 ){ + pToplevel->writeMask |= 1<nested==0 && pParse==pToplevel ){ /* Every place where this routine is called with setStatement!=0 has ** already successfully created a VDBE. */ assert( pParse->pVdbe ); sqlite3VdbeAddOp1(pParse->pVdbe, OP_Statement, iDb); } Index: src/delete.c ================================================================== --- src/delete.c +++ src/delete.c @@ -226,25 +226,19 @@ WhereInfo *pWInfo; /* Information about the WHERE clause */ Index *pIdx; /* For looping over indices of the table */ int iCur; /* VDBE Cursor number for pTab */ sqlite3 *db; /* Main database structure */ AuthContext sContext; /* Authorization context */ - int oldIdx = -1; /* Cursor for the OLD table of AFTER triggers */ NameContext sNC; /* Name context to resolve expressions in */ int iDb; /* Database number */ int memCnt = -1; /* Memory cell used for change counting */ int rcauth; /* Value returned by authorization callback */ #ifndef SQLITE_OMIT_TRIGGER int isView; /* True if attempting to delete from a view */ Trigger *pTrigger; /* List of table triggers, if required */ #endif - int iBeginAfterTrigger = 0; /* Address of after trigger program */ - int iEndAfterTrigger = 0; /* Exit of after trigger program */ - int iBeginBeforeTrigger = 0; /* Address of before trigger program */ - int iEndBeforeTrigger = 0; /* Exit of before trigger program */ - u32 old_col_mask = 0; /* Mask of OLD.* columns in use */ memset(&sContext, 0, sizeof(sContext)); db = pParse->db; if( pParse->nErr || db->mallocFailed ){ goto delete_from_cleanup; @@ -291,16 +285,10 @@ if( rcauth==SQLITE_DENY ){ goto delete_from_cleanup; } assert(!isView || pTrigger); - /* Allocate a cursor used to store the old.* data for a trigger. - */ - if( pTrigger ){ - oldIdx = pParse->nTab++; - } - /* Assign cursor number to the table and all its indices. */ assert( pTabList->nSrc==1 ); iCur = pTabList->a[0].iCursor = pParse->nTab++; for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ @@ -320,28 +308,10 @@ goto delete_from_cleanup; } if( pParse->nested==0 ) sqlite3VdbeCountChanges(v); sqlite3BeginWriteOperation(pParse, (pTrigger?1:0), iDb); - if( pTrigger ){ - int orconf = ((pParse->trigStack)?pParse->trigStack->orconf:OE_Default); - int iGoto = sqlite3VdbeAddOp0(v, OP_Goto); - addr = sqlite3VdbeMakeLabel(v); - - iBeginBeforeTrigger = sqlite3VdbeCurrentAddr(v); - (void)sqlite3CodeRowTrigger(pParse, pTrigger, TK_DELETE, 0, - TRIGGER_BEFORE, pTab, -1, oldIdx, orconf, addr, &old_col_mask, 0); - iEndBeforeTrigger = sqlite3VdbeAddOp0(v, OP_Goto); - - iBeginAfterTrigger = sqlite3VdbeCurrentAddr(v); - (void)sqlite3CodeRowTrigger(pParse, pTrigger, TK_DELETE, 0, - TRIGGER_AFTER, pTab, -1, oldIdx, orconf, addr, &old_col_mask, 0); - iEndAfterTrigger = sqlite3VdbeAddOp0(v, OP_Goto); - - sqlite3VdbeJumpHere(v, iGoto); - } - /* 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 ){ @@ -383,12 +353,13 @@ #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. */ { - int iRowid = ++pParse->nMem; /* Used for storing rowid values. */ int iRowSet = ++pParse->nMem; /* Register for rowset of rows to delete */ + int iRowid = ++pParse->nMem; /* Used for storing rowid values. */ + int regOld = pParse->nMem + 1; /* Start of array for old.* (if triggers) */ int regRowid; /* Actual register containing rowids */ /* Collect rowids of every row to be deleted. */ sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet); @@ -399,56 +370,54 @@ if( db->flags & SQLITE_CountRows ){ sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1); } sqlite3WhereEnd(pWInfo); - /* Open the pseudo-table used to store OLD if there are triggers. - */ - if( pTrigger ){ - sqlite3VdbeAddOp3(v, OP_OpenPseudo, oldIdx, 0, pTab->nCol); - } - /* Delete every item whose key was written to the list during the ** database scan. We have to delete items after the scan is complete - ** because deleting an item can change the scan order. - */ + ** because deleting an item can change the scan order. */ end = sqlite3VdbeMakeLabel(v); + /* 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 ){ - /* Open cursors for the table we are deleting from and - ** all its indices. - */ sqlite3OpenTableAndIndices(pParse, pTab, iCur, OP_OpenWrite); } - /* This is the beginning of the delete loop. If a trigger encounters - ** an IGNORE constraint, it jumps back to here. - */ - if( pTrigger ){ - sqlite3VdbeResolveLabel(v, addr); - } addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, end, iRowid); + /* If there are triggers, populate an array of registers with the + ** data required by the old.* references in the trigger bodies. */ if( pTrigger ){ - int iData = ++pParse->nMem; /* For storing row data of OLD table */ + u32 mask = 0; /* Mask of OLD.* columns in use */ + pParse->nMem += pTab->nCol; + + /* Open the pseudo-table used to store OLD if there are triggers. */ + mask = sqlite3TriggerOldmask( + pParse, pTrigger, TK_DELETE, 0, pTab, OE_Default); /* If the record is no longer present in the table, jump to the ** next iteration of the loop through the contents of the fifo. */ sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, iRowid); /* Populate the OLD.* pseudo-table */ - if( old_col_mask ){ - sqlite3VdbeAddOp2(v, OP_RowData, iCur, iData); - }else{ - sqlite3VdbeAddOp2(v, OP_Null, 0, iData); - } - sqlite3VdbeAddOp3(v, OP_Insert, oldIdx, iData, iRowid); - - /* Jump back and run the BEFORE triggers */ - sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginBeforeTrigger); - sqlite3VdbeJumpHere(v, iEndBeforeTrigger); + assert( regOld==iRowid+1 ); + for(i=0; inCol; i++){ + if( mask==0xffffffff || mask&(1<nCol); + sqlite3TableAffinityStr(v, pTab); + + sqlite3CodeRowTrigger(pParse, pTrigger, + TK_DELETE, 0, TRIGGER_BEFORE, pTab, -1, iRowid, OE_Default, addr + ); } if( !isView ){ /* Delete the row */ #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -461,25 +430,21 @@ { sqlite3GenerateRowDelete(pParse, pTab, iCur, iRowid, pParse->nested==0); } } - /* If there are row triggers, close all cursors then invoke - ** the AFTER triggers - */ - if( pTrigger ){ - /* Jump back and run the AFTER triggers */ - sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginAfterTrigger); - sqlite3VdbeJumpHere(v, iEndAfterTrigger); - } + /* Code the AFTER triggers. This is a no-op if there are no triggers. */ + sqlite3CodeRowTrigger(pParse, + pTrigger, TK_DELETE, 0, TRIGGER_AFTER, pTab, -1, iRowid, OE_Default, addr + ); /* End of the delete loop */ sqlite3VdbeAddOp2(v, OP_Goto, 0, addr); sqlite3VdbeResolveLabel(v, end); - /* Close the cursors after the loop if there are no row triggers */ - if( !isView && !IsVirtual(pTab) ){ + /* 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, iCur); } @@ -487,20 +452,20 @@ /* Update the sqlite_sequence table by storing the content of the ** maximum rowid counter values recorded while inserting into ** autoincrement tables. */ - if( pParse->nested==0 && pParse->trigStack==0 ){ + if( pParse->nested==0 && pParse->pTriggerTab==0 ){ sqlite3AutoincrementEnd(pParse); } /* ** Return the number of rows that were deleted. If this routine is ** generating code because of a call to sqlite3NestedParse(), do not ** invoke the callback function. */ - if( db->flags & SQLITE_CountRows && pParse->nested==0 && !pParse->trigStack ){ + if( (db->flags&SQLITE_CountRows) && !pParse->nested && !pParse->pTriggerTab ){ sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1); sqlite3VdbeSetNumCols(v, 1); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC); } Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -88,11 +88,13 @@ while( ALWAYS(p) ){ int op; pColl = p->pColl; if( pColl ) break; op = p->op; - if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_REGISTER) && p->pTab!=0 ){ + if( p->pTab!=0 && ( + op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_REGISTER || op==TK_TRIGGER + )){ /* op==TK_REGISTER && p->pTab!=0 happens when pExpr was originally ** a TK_COLUMN but was previously evaluated and cached in a register */ const char *zColl; int j = p->iColumn; if( j>=0 ){ @@ -1394,11 +1396,10 @@ */ assert(v); if( iCol<0 ){ int iMem = ++pParse->nMem; int iAddr; - sqlite3VdbeUsesBtree(v, iDb); iAddr = sqlite3VdbeAddOp1(v, OP_If, iMem); sqlite3VdbeAddOp2(v, OP_Integer, 1, iMem); sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead); @@ -1428,13 +1429,10 @@ int iMem = ++pParse->nMem; int iAddr; char *pKey; pKey = (char *)sqlite3IndexKeyinfo(pParse, pIdx); - iDb = sqlite3SchemaToIndex(db, pIdx->pSchema); - sqlite3VdbeUsesBtree(v, iDb); - iAddr = sqlite3VdbeAddOp1(v, OP_If, iMem); sqlite3VdbeAddOp2(v, OP_Integer, 1, iMem); sqlite3VdbeAddOp4(v, OP_OpenRead, iTab, pIdx->tnum, iDb, pKey,P4_KEYINFO_HANDOFF); @@ -1519,11 +1517,11 @@ ** * We are inside a trigger ** ** If all of the above are false, then we can run this code just once ** save the results, and reuse the same result on subsequent invocations. */ - if( !ExprHasAnyProperty(pExpr, EP_VarSelect) && !pParse->trigStack ){ + if( !ExprHasAnyProperty(pExpr, EP_VarSelect) && !pParse->pTriggerTab ){ int mem = ++pParse->nMem; sqlite3VdbeAddOp1(v, OP_If, mem); testAddr = sqlite3VdbeAddOp2(v, OP_Integer, 1, mem); assert( testAddr>0 || pParse->db->mallocFailed ); } @@ -2553,10 +2551,62 @@ } case TK_UPLUS: { inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); break; } + + case TK_TRIGGER: { + /* If the opcode is TK_TRIGGER, then the expression is a reference + ** to a column in the new.* or old.* pseudo-tables available to + ** trigger programs. In this case Expr.iTable is set to 1 for the + ** new.* pseudo-table, or 0 for the old.* pseudo-table. Expr.iColumn + ** is set to the column of the pseudo-table to read, or to -1 to + ** read the rowid field. + ** + ** The expression is implemented using an OP_Param opcode. The p1 + ** parameter is set to 0 for an old.rowid reference, or to (i+1) + ** to reference another column of the old.* pseudo-table, where + ** i is the index of the column. For a new.rowid reference, p1 is + ** set to (n+1), where n is the number of columns in each pseudo-table. + ** For a reference to any other column in the new.* pseudo-table, p1 + ** is set to (n+2+i), where n and i are as defined previously. For + ** example, if the table on which triggers are being fired is + ** declared as: + ** + ** CREATE TABLE t1(a, b); + ** + ** Then p1 is interpreted as follows: + ** + ** p1==0 -> old.rowid p1==3 -> new.rowid + ** p1==1 -> old.a p1==4 -> new.a + ** p1==2 -> old.b p1==5 -> new.b + */ + Table *pTab = pExpr->pTab; + int p1 = pExpr->iTable * (pTab->nCol+1) + 1 + pExpr->iColumn; + + assert( pExpr->iTable==0 || pExpr->iTable==1 ); + assert( pExpr->iColumn>=-1 && pExpr->iColumnnCol ); + assert( pTab->iPKey<0 || pExpr->iColumn!=pTab->iPKey ); + assert( p1>=0 && p1<(pTab->nCol*2+2) ); + + sqlite3VdbeAddOp2(v, OP_Param, p1, target); + VdbeComment((v, "%s.%s -> $%d", + (pExpr->iTable ? "new" : "old"), + (pExpr->iColumn<0 ? "rowid" : pExpr->pTab->aCol[pExpr->iColumn].zName), + target + )); + + /* If the column has REAL affinity, it may currently be stored as an + ** integer. Use OP_RealAffinity to make sure it is really real. */ + if( pExpr->iColumn>=0 + && pTab->aCol[pExpr->iColumn].affinity==SQLITE_AFF_REAL + ){ + sqlite3VdbeAddOp1(v, OP_RealAffinity, target); + } + break; + } + /* ** Form A: ** CASE x WHEN e1 THEN r1 WHEN e2 THEN r2 ... WHEN eN THEN rN ELSE y END ** @@ -2638,28 +2688,24 @@ sqlite3VdbeResolveLabel(v, endLabel); break; } #ifndef SQLITE_OMIT_TRIGGER case TK_RAISE: { - if( !pParse->trigStack ){ + int vrc; + if( !pParse->pTriggerTab ){ sqlite3ErrorMsg(pParse, "RAISE() may only be used within a trigger-program"); return 0; } - if( pExpr->affinity!=OE_Ignore ){ - assert( pExpr->affinity==OE_Rollback || - pExpr->affinity == OE_Abort || - pExpr->affinity == OE_Fail ); - assert( !ExprHasProperty(pExpr, EP_IntValue) ); - sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CONSTRAINT, pExpr->affinity, 0, - pExpr->u.zToken, 0); - } else { - assert( pExpr->affinity == OE_Ignore ); - sqlite3VdbeAddOp2(v, OP_ContextPop, 0, 0); - sqlite3VdbeAddOp2(v, OP_Goto, 0, pParse->trigStack->ignoreJump); - VdbeComment((v, "raise(IGNORE)")); - } + assert( pExpr->affinity==OE_Rollback + || pExpr->affinity==OE_Abort + || pExpr->affinity==OE_Fail + || pExpr->affinity==OE_Ignore + ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + vrc = (pExpr->affinity==OE_Ignore ? SQLITE_OK : SQLITE_CONSTRAINT); + sqlite3VdbeAddOp4(v, OP_Halt, vrc, pExpr->affinity, 0, pExpr->u.zToken,0); break; } #endif } sqlite3ReleaseTempReg(pParse, regFree1); Index: src/insert.c ================================================================== --- src/insert.c +++ src/insert.c @@ -195,24 +195,25 @@ int iDb, /* Index of the database holding pTab */ Table *pTab /* The table we are writing to */ ){ int memId = 0; /* Register holding maximum rowid */ if( pTab->tabFlags & TF_Autoincrement ){ + Parse *pToplevel = sqlite3ParseToplevel(pParse); AutoincInfo *pInfo; - pInfo = pParse->pAinc; + pInfo = pToplevel->pAinc; while( pInfo && pInfo->pTab!=pTab ){ pInfo = pInfo->pNext; } if( pInfo==0 ){ pInfo = sqlite3DbMallocRaw(pParse->db, sizeof(*pInfo)); if( pInfo==0 ) return 0; - pInfo->pNext = pParse->pAinc; - pParse->pAinc = pInfo; + pInfo->pNext = pToplevel->pAinc; + pToplevel->pAinc = pInfo; pInfo->pTab = pTab; pInfo->iDb = iDb; - pParse->nMem++; /* Register to hold name of table */ - pInfo->regCtr = ++pParse->nMem; /* Max rowid register */ - pParse->nMem++; /* Rowid in sqlite_sequence */ + pToplevel->nMem++; /* Register to hold name of table */ + pInfo->regCtr = ++pToplevel->nMem; /* Max rowid register */ + pToplevel->nMem++; /* Rowid in sqlite_sequence */ } memId = pInfo->regCtr; } return memId; } @@ -226,10 +227,13 @@ sqlite3 *db = pParse->db; /* The database connection */ Db *pDb; /* Database only autoinc table */ int memId; /* Register holding max rowid */ int addr; /* A VDBE address */ Vdbe *v = pParse->pVdbe; /* VDBE under construction */ + + /* If currently generating a trigger program, this call is a no-op */ + if( pParse->pTriggerTab ) return; assert( v ); /* We failed long ago if this is not so */ for(p = pParse->pAinc; p; p = p->pNext){ pDb = &db->aDb[p->iDb]; memId = p->regCtr; @@ -448,11 +452,10 @@ 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 */ int addrSelect = 0; /* Address of coroutine that implements the SELECT */ SelectDest dest; /* Destination for SELECT on rhs of INSERT */ - int newIdx = -1; /* Cursor for the NEW pseudo-table */ 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 */ /* Register allocations */ @@ -463,11 +466,10 @@ int regRowid; /* registers holding insert rowid */ int regData; /* register holding first column to insert */ int regRecord; /* Holds the assemblied row record */ int regEof = 0; /* Register recording end of SELECT data */ int *aRegIdx = 0; /* One register allocated to each index */ - #ifndef SQLITE_OMIT_TRIGGER int isView; /* True if attempting to insert into a view */ Trigger *pTrigger; /* List of triggers on pTab, if required */ int tmask; /* Mask of trigger times */ @@ -534,15 +536,10 @@ v = sqlite3GetVdbe(pParse); if( v==0 ) goto insert_cleanup; if( pParse->nested==0 ) sqlite3VdbeCountChanges(v); sqlite3BeginWriteOperation(pParse, pSelect || pTrigger, iDb); - /* if there are row triggers, allocate a temp table for new.* references. */ - if( pTrigger ){ - newIdx = pParse->nTab++; - } - #ifndef SQLITE_OMIT_XFER_OPT /* If the statement is of the form ** ** INSERT INTO SELECT * FROM ; ** @@ -742,16 +739,10 @@ ** in the original table definition. */ if( pColumn==0 && nColumn>0 ){ keyColumn = pTab->iPKey; } - - /* Open the temp table for FOR EACH ROW triggers - */ - if( pTrigger ){ - sqlite3VdbeAddOp3(v, OP_OpenPseudo, newIdx, 0, pTab->nCol); - } /* Initialize the count of rows to be inserted */ if( db->flags & SQLITE_CountRows ){ regRowCount = ++pParse->nMem; @@ -814,83 +805,74 @@ /* Run the BEFORE and INSTEAD OF triggers, if there are any */ endOfLoop = sqlite3VdbeMakeLabel(v); if( tmask & TRIGGER_BEFORE ){ - int regTrigRowid; - int regCols; - int regRec; + int regCols = sqlite3GetTempRange(pParse, pTab->nCol+1); /* build the NEW.* reference row. Note that if there is an INTEGER ** PRIMARY KEY into which a NULL is being inserted, that NULL will be ** translated into a unique ID for the row. But on a BEFORE trigger, ** we do not know what the unique ID will be (because the insert has ** not happened yet) so we substitute a rowid of -1 */ - regTrigRowid = sqlite3GetTempReg(pParse); if( keyColumn<0 ){ - sqlite3VdbeAddOp2(v, OP_Integer, -1, regTrigRowid); + sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols); }else{ int j1; if( useTempTable ){ - sqlite3VdbeAddOp3(v, OP_Column, srcTab, keyColumn, regTrigRowid); + sqlite3VdbeAddOp3(v, OP_Column, srcTab, keyColumn, regCols); }else{ assert( pSelect==0 ); /* Otherwise useTempTable is true */ - sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr, regTrigRowid); + sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr, regCols); } - j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regTrigRowid); - sqlite3VdbeAddOp2(v, OP_Integer, -1, regTrigRowid); + j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regCols); + sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols); sqlite3VdbeJumpHere(v, j1); - sqlite3VdbeAddOp1(v, OP_MustBeInt, regTrigRowid); + sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); } /* Cannot have triggers on a virtual table. If it were possible, ** this block would have to account for hidden column. */ - assert(!IsVirtual(pTab)); + assert( !IsVirtual(pTab) ); /* Create the new column data */ - regCols = sqlite3GetTempRange(pParse, pTab->nCol); for(i=0; inCol; i++){ if( pColumn==0 ){ j = i; }else{ for(j=0; jnId; j++){ if( pColumn->a[j].idx==i ) break; } } if( pColumn && j>=pColumn->nId ){ - sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regCols+i); + sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regCols+i+1); }else if( useTempTable ){ - sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, regCols+i); + sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, regCols+i+1); }else{ assert( pSelect==0 ); /* Otherwise useTempTable is true */ - sqlite3ExprCodeAndCache(pParse, pList->a[j].pExpr, regCols+i); + sqlite3ExprCodeAndCache(pParse, pList->a[j].pExpr, regCols+i+1); } } - regRec = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp3(v, OP_MakeRecord, regCols, pTab->nCol, regRec); /* If this is an INSERT on a view with an INSTEAD OF INSERT trigger, ** do not attempt any conversions before assembling the record. ** If this is a real table, attempt conversions as required by the ** table column affinities. */ if( !isView ){ + sqlite3VdbeAddOp2(v, OP_Affinity, regCols+1, pTab->nCol); sqlite3TableAffinityStr(v, pTab); } - sqlite3VdbeAddOp3(v, OP_Insert, newIdx, regRec, regTrigRowid); - sqlite3ReleaseTempReg(pParse, regRec); - sqlite3ReleaseTempReg(pParse, regTrigRowid); - sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol); /* Fire BEFORE or INSTEAD OF triggers */ - if( sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE, - pTab, newIdx, -1, onError, endOfLoop, 0, 0) ){ - goto insert_cleanup; - } + sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE, + pTab, -1, 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 @@ -993,12 +975,11 @@ int isReplace; /* Set to true if constraints may cause a replace */ sqlite3GenerateConstraintChecks(pParse, pTab, baseCur, regIns, aRegIdx, keyColumn>=0, 0, onError, endOfLoop, &isReplace ); sqlite3CompleteInsertion( - pParse, pTab, baseCur, regIns, aRegIdx, 0, - (tmask&TRIGGER_AFTER) ? newIdx : -1, appendFlag, isReplace==0 + pParse, pTab, baseCur, regIns, aRegIdx, 0, appendFlag, isReplace==0 ); } } /* Update the count of rows that are inserted @@ -1007,14 +988,12 @@ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); } if( pTrigger ){ /* Code AFTER triggers */ - if( sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER, - pTab, newIdx, -1, onError, endOfLoop, 0, 0) ){ - goto insert_cleanup; - } + sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER, + pTab, -1, regData-2-pTab->nCol, onError, endOfLoop); } /* The bottom of the main insertion loop, if the data source ** is a SELECT statement. */ @@ -1039,20 +1018,20 @@ insert_end: /* Update the sqlite_sequence table by storing the content of the ** maximum rowid counter values recorded while inserting into ** autoincrement tables. */ - if( pParse->nested==0 && pParse->trigStack==0 ){ + if( pParse->nested==0 && pParse->pTriggerTab==0 ){ sqlite3AutoincrementEnd(pParse); } /* ** Return the number of rows inserted. If this routine is ** generating code because of a call to sqlite3NestedParse(), do not ** invoke the callback function. */ - if( db->flags & SQLITE_CountRows && pParse->nested==0 && !pParse->trigStack ){ + if( (db->flags&SQLITE_CountRows) && !pParse->nested && !pParse->pTriggerTab ){ sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1); sqlite3VdbeSetNumCols(v, 1); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows inserted", SQLITE_STATIC); } @@ -1067,30 +1046,28 @@ /* ** 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 to be updated before the update. This -** value is omitted unless we are doing an UPDATE that involves a -** change to the record number or writing to a virtual table. +** 1. The rowid of the row after the update. ** -** 2. The rowid of the row after the update. -** -** 3. The data in the first column of the entry 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 (2). +** The regRowid parameter is the index of the register containing (1). ** -** The old rowid shown as entry (1) above is omitted unless both isUpdate -** and rowidChng are 1. isUpdate is true for UPDATEs and false for -** INSERTs. RowidChng means that the new rowid is explicitly specified by -** the update or 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 the update. +** 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 ** 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 @@ -1161,18 +1138,17 @@ int j2 = 0, j3; /* Addresses of jump instructions */ int regData; /* Register containing first data column */ int iCur; /* Table cursor number */ Index *pIdx; /* Pointer to one of the indices */ int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */ - int hasTwoRowids = (isUpdate && rowidChng); + int regOldRowid = (rowidChng && isUpdate) ? rowidChng : regRowid; v = sqlite3GetVdbe(pParse); assert( v!=0 ); assert( pTab->pSelect==0 ); /* This table is not a VIEW */ nCol = pTab->nCol; regData = regRowid + 1; - /* Test all NOT NULL constraints. */ for(i=0; iiPKey ){ @@ -1245,11 +1221,11 @@ onError = OE_Abort; } if( onError!=OE_Replace || pTab->pIndex ){ if( isUpdate ){ - j2 = sqlite3VdbeAddOp3(v, OP_Eq, regRowid, 0, regRowid-1); + j2 = sqlite3VdbeAddOp3(v, OP_Eq, regRowid, 0, rowidChng); } j3 = sqlite3VdbeAddOp3(v, OP_NotExists, baseCur, 0, regRowid); switch( onError ){ default: { onError = OE_Abort; @@ -1322,11 +1298,11 @@ } /* Check to see if the new index entry will be unique */ regR = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp2(v, OP_SCopy, regRowid-hasTwoRowids, regR); + 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); @@ -1393,11 +1369,10 @@ 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 *aRegIdx, /* Register used by each index. 0 for unused indices */ int isUpdate, /* True for UPDATE, False for INSERT */ - int newIdx, /* Index of NEW table for triggers. -1 if none */ 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; @@ -1421,15 +1396,10 @@ regData = regRowid + 1; regRec = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec); sqlite3TableAffinityStr(v, pTab); sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol); -#ifndef SQLITE_OMIT_TRIGGER - if( newIdx>=0 ){ - sqlite3VdbeAddOp3(v, OP_Insert, newIdx, regRec, regRowid); - } -#endif if( pParse->nested ){ pik_flags = 0; }else{ pik_flags = OPFLAG_NCHANGE; pik_flags |= (isUpdate?OPFLAG_ISUPDATE:OPFLAG_LASTROWID); Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -1588,10 +1588,13 @@ #if SQLITE_DEFAULT_FILE_FORMAT<4 | SQLITE_LegacyFileFmt #endif #ifdef SQLITE_ENABLE_LOAD_EXTENSION | SQLITE_LoadExtension +#endif +#if 1 || defined(SQLITE_DISABLE_RECURSIVE_TRIGGERS) + | SQLITE_NoRecTriggers #endif ; sqlite3HashInit(&db->aCollSeq); #ifndef SQLITE_OMIT_VIRTUALTABLE sqlite3HashInit(&db->aModule); Index: src/pragma.c ================================================================== --- src/pragma.c +++ src/pragma.c @@ -188,10 +188,11 @@ { "omit_readlock", SQLITE_NoReadlock }, /* TODO: Maybe it shouldn't be possible to change the ReadUncommitted ** flag if there are any active statements. */ { "read_uncommitted", SQLITE_ReadUncommitted }, + { "disable_recursive_triggers", SQLITE_NoRecTriggers }, }; int i; const struct sPragmaType *p; for(i=0, p=aPragma; izName)==0 ){ Index: src/prepare.c ================================================================== --- src/prepare.c +++ src/prepare.c @@ -673,10 +673,18 @@ sqlite3Error(db, rc, "%s", zErrMsg); sqlite3DbFree(db, zErrMsg); }else{ sqlite3Error(db, rc, 0); } + + /* Delete any TriggerPrg structures allocated while parsing this statement. */ + while( pParse->pTriggerPrg ){ + TriggerPrg *pT = pParse->pTriggerPrg; + pParse->pTriggerPrg = pT->pNext; + sqlite3VdbeProgramDelete(db, pT->pProgram, 0); + sqlite3DbFree(db, pT); + } end_prepare: sqlite3StackFree(db, pParse); rc = sqlite3ApiExit(db, rc); Index: src/resolve.c ================================================================== --- src/resolve.c +++ src/resolve.c @@ -135,10 +135,11 @@ 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; assert( pNC ); /* the name context cannot be NULL. */ assert( zCol ); /* The Z in X.Y.Z cannot be NULL */ assert( ~ExprHasAnyProperty(pExpr, EP_TokenOnly|EP_Reduced) ); @@ -220,46 +221,51 @@ #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->trigStack!=0 ){ - TriggerStack *pTriggerStack = pParse->trigStack; + if( zDb==0 && zTab!=0 && cnt==0 && pParse->pTriggerTab!=0 ){ + int op = pParse->eTriggerOp; Table *pTab = 0; - u32 *piColMask = 0; - if( pTriggerStack->newIdx != -1 && sqlite3StrICmp("new", zTab) == 0 ){ - pExpr->iTable = pTriggerStack->newIdx; - assert( pTriggerStack->pTab ); - pTab = pTriggerStack->pTab; - piColMask = &(pTriggerStack->newColMask); - }else if( pTriggerStack->oldIdx != -1 && sqlite3StrICmp("old", zTab)==0 ){ - pExpr->iTable = pTriggerStack->oldIdx; - assert( pTriggerStack->pTab ); - pTab = pTriggerStack->pTab; - piColMask = &(pTriggerStack->oldColMask); + 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 ){ + pExpr->iTable = 0; + pTab = pParse->pTriggerTab; } if( pTab ){ int iCol; - Column *pCol = pTab->aCol; - pSchema = pTab->pSchema; cntTab++; - for(iCol=0; iCol < pTab->nCol; iCol++, pCol++) { - if( sqlite3StrICmp(pCol->zName, zCol)==0 ){ - cnt++; - pExpr->iColumn = iCol==pTab->iPKey ? -1 : (i16)iCol; - pExpr->pTab = pTab; + if( sqlite3IsRowid(zCol) ){ + iCol = -1; + }else{ + for(iCol=0; iColnCol; iCol++){ + Column *pCol = &pTab->aCol[iCol]; + if( sqlite3StrICmp(pCol->zName, zCol)==0 ){ + if( iCol==pTab->iPKey ){ + iCol = -1; + } + break; + } + } + } + if( iColnCol ){ + cnt++; + if( iCol<0 ){ + pExpr->affinity = SQLITE_AFF_INTEGER; + }else if( pExpr->iTable==0 ){ testcase( iCol==31 ); testcase( iCol==32 ); - if( iCol>=32 ){ - *piColMask = 0xffffffff; - }else{ - *piColMask |= ((u32)1)<oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<iColumn = iCol; + pExpr->pTab = pTab; + isTrigger = 1; } } } #endif /* !defined(SQLITE_OMIT_TRIGGER) */ @@ -367,11 +373,11 @@ */ sqlite3ExprDelete(db, pExpr->pLeft); pExpr->pLeft = 0; sqlite3ExprDelete(db, pExpr->pRight); pExpr->pRight = 0; - pExpr->op = TK_COLUMN; + pExpr->op = (isTrigger ? TK_TRIGGER : TK_COLUMN); lookupname_end: if( cnt==1 ){ assert( pNC!=0 ); sqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList); /* Increment the nRef value on all name contexts from TopNC up to Index: src/select.c ================================================================== --- src/select.c +++ src/select.c @@ -2732,12 +2732,13 @@ ** pSubitem->pTab is always non-NULL by test restrictions and tests above. */ if( ALWAYS(pSubitem->pTab!=0) ){ Table *pTabToDel = pSubitem->pTab; if( pTabToDel->nRef==1 ){ - pTabToDel->pNextZombie = pParse->pZombieTab; - pParse->pZombieTab = pTabToDel; + Parse *pToplevel = sqlite3ParseToplevel(pParse); + pTabToDel->pNextZombie = pToplevel->pZombieTab; + pToplevel->pZombieTab = pTabToDel; }else{ pTabToDel->nRef--; } pSubitem->pTab = 0; } Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -609,11 +609,11 @@ typedef struct SrcList SrcList; typedef struct StrAccum StrAccum; typedef struct Table Table; typedef struct TableLock TableLock; typedef struct Token Token; -typedef struct TriggerStack TriggerStack; +typedef struct TriggerPrg TriggerPrg; typedef struct TriggerStep TriggerStep; typedef struct Trigger Trigger; typedef struct UnpackedRecord UnpackedRecord; typedef struct VTable VTable; typedef struct Walker Walker; @@ -909,10 +909,11 @@ #define SQLITE_FullFSync 0x00010000 /* Use full fsync on the backend */ #define SQLITE_LoadExtension 0x00020000 /* Enable load_extension */ #define SQLITE_RecoveryMode 0x00040000 /* Ignore schema errors */ #define SQLITE_ReverseOrder 0x00100000 /* Reverse unordered SELECTs */ +#define SQLITE_NoRecTriggers 0x00200000 /* Disable recursive triggers */ /* ** Possible values for the sqlite.magic field. ** The numbers are obtained at random and have no special meaning, other ** than being distinct from one another. @@ -1574,11 +1575,12 @@ ** space is allocated for the fields below this point. An attempt to ** access them will result in a segfault or malfunction. *********************************************************************/ int iTable; /* TK_COLUMN: cursor number of table holding column - ** TK_REGISTER: register number */ + ** TK_REGISTER: register number + ** TK_TRIGGER: 1 -> new, 0 -> old */ i16 iColumn; /* TK_COLUMN: column index. -1 for rowid */ i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ i16 iRightJoinTable; /* If EP_FromJoin, the right table of the join */ u8 flags2; /* Second set of flags. EP2_... */ u8 op2; /* If a TK_REGISTER, the original value of Expr.op */ @@ -2013,10 +2015,35 @@ */ #ifndef SQLITE_N_COLCACHE # define SQLITE_N_COLCACHE 10 #endif +/* +** At least one instance of the following structure is created for each +** trigger that may be fired while parsing an INSERT, UPDATE or DELETE +** statement. All such objects are stored in the linked list headed at +** Parse.pTriggerPrg and deleted once statement compilation has been +** completed. +** +** A Vdbe sub-program that implements the body and WHEN clause of trigger +** TriggerPrg.pTrigger, assuming a default ON CONFLICT clause of +** TriggerPrg.orconf, is stored in the TriggerPrg.pProgram variable. +** The Parse.pTriggerPrg list never contains two entries with the same +** values for both pTrigger and orconf. +** +** The TriggerPrg.oldmask variable is set to a mask of old.* columns +** accessed (or set to 0 for triggers fired as a result of INSERT +** statements). +*/ +struct TriggerPrg { + Trigger *pTrigger; /* Trigger this program was coded from */ + int orconf; /* Default ON CONFLICT policy */ + SubProgram *pProgram; /* Program implementing pTrigger/orconf */ + u32 oldmask; /* Mask of old.* columns accessed */ + TriggerPrg *pNext; /* Next entry in Parse.pTriggerPrg list */ +}; + /* ** An SQL parser context. A copy of this structure is passed through ** the parser and down into all the parser action routine in order to ** carry around information that is global to the entire parse. ** @@ -2073,10 +2100,18 @@ TableLock *aTableLock; /* Required table locks for shared-cache mode */ #endif int regRowid; /* Register holding rowid of CREATE TABLE entry */ int regRoot; /* Register holding root page number for new objects */ AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */ + int nMaxArg; /* Max args passed to user function by sub-program */ + + /* Information used while coding trigger programs. */ + Parse *pToplevel; /* Parse structure for main program (or NULL) */ + Table *pTriggerTab; /* Table triggers are being coded for */ + u32 oldmask; /* Mask of old.* columns referenced */ + u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ + u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ /* Above is constant between recursions. Below is reset before and after ** each recursion */ int nVar; /* Number of '?' variables seen in the SQL so far */ @@ -2090,20 +2125,20 @@ Token sNameToken; /* Token with unqualified schema object name */ Token sLastToken; /* The last token parsed */ 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 */ - TriggerStack *trigStack; /* Trigger actions being coded */ const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */ #ifndef SQLITE_OMIT_VIRTUALTABLE Token sArg; /* Complete text of a module argument */ u8 declareVtab; /* True if inside sqlite3_declare_vtab() */ int nVtabLock; /* Number of virtual tables to lock */ Table **apVtabLock; /* Pointer to virtual tables needing locking */ #endif int nHeight; /* Expression tree height of current sub-select */ Table *pZombieTab; /* List of Table objects to delete after code gen */ + TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ }; #ifdef SQLITE_OMIT_VIRTUALTABLE #define IN_DECLARE_VTAB 0 #else @@ -2142,11 +2177,11 @@ * * The "step_list" member points to the first element of a linked list * containing the SQL statements specified as the trigger program. */ struct Trigger { - char *name; /* The name of the trigger */ + char *zName; /* The name of the trigger */ char *table; /* The table or view to which the trigger applies */ u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT */ u8 tr_tm; /* One of TRIGGER_BEFORE, TRIGGER_AFTER */ Expr *pWhen; /* The WHEN clause of the expression (may be NULL) */ IdList *pColumns; /* If this is an UPDATE OF trigger, @@ -2216,49 +2251,10 @@ IdList *pIdList; /* Column names for INSERT */ TriggerStep *pNext; /* Next in the link-list */ TriggerStep *pLast; /* Last element in link-list. Valid for 1st elem only */ }; -/* - * An instance of struct TriggerStack stores information required during code - * generation of a single trigger program. While the trigger program is being - * coded, its associated TriggerStack instance is pointed to by the - * "pTriggerStack" member of the Parse structure. - * - * The pTab member points to the table that triggers are being coded on. The - * newIdx member contains the index of the vdbe cursor that points at the temp - * table that stores the new.* references. If new.* references are not valid - * for the trigger being coded (for example an ON DELETE trigger), then newIdx - * is set to -1. The oldIdx member is analogous to newIdx, for old.* references. - * - * The ON CONFLICT policy to be used for the trigger program steps is stored - * as the orconf member. If this is OE_Default, then the ON CONFLICT clause - * specified for individual triggers steps is used. - * - * struct TriggerStack has a "pNext" member, to allow linked lists to be - * constructed. When coding nested triggers (triggers fired by other triggers) - * each nested trigger stores its parent trigger's TriggerStack as the "pNext" - * pointer. Once the nested trigger has been coded, the pNext value is restored - * to the pTriggerStack member of the Parse stucture and coding of the parent - * trigger continues. - * - * Before a nested trigger is coded, the linked list pointed to by the - * pTriggerStack is scanned to ensure that the trigger is not about to be coded - * recursively. If this condition is detected, the nested trigger is not coded. - */ -struct TriggerStack { - Table *pTab; /* Table that triggers are currently being coded on */ - int newIdx; /* Index of vdbe cursor to "new" temp table */ - int oldIdx; /* Index of vdbe cursor to "old" temp table */ - u32 newColMask; - u32 oldColMask; - int orconf; /* Current orconf policy */ - int ignoreJump; /* where to jump to for a RAISE(IGNORE) */ - Trigger *pTrigger; /* The trigger currently being coded */ - TriggerStack *pNext; /* Next trigger down on the trigger stack */ -}; - /* ** The following structure contains information used by the sqliteFix... ** routines as they walk the parse tree to make database references ** explicit. */ @@ -2649,11 +2645,11 @@ void sqlite3GenerateRowDelete(Parse*, Table*, int, int, int); void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int*); int sqlite3GenerateIndexKey(Parse*, Index*, 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); +void sqlite3CompleteInsertion(Parse*, Table*, int, int, int*, int, int, int); int sqlite3OpenTableAndIndices(Parse*, Table*, int, int); void sqlite3BeginWriteOperation(Parse*, int, int); Expr *sqlite3ExprDup(sqlite3*,Expr*,int); ExprList *sqlite3ExprListDup(sqlite3*,ExprList*,int); SrcList *sqlite3SrcListDup(sqlite3*,SrcList*,int); @@ -2685,28 +2681,31 @@ void sqlite3FinishTrigger(Parse*, TriggerStep*, Token*); void sqlite3DropTrigger(Parse*, SrcList*, int); void sqlite3DropTriggerPtr(Parse*, Trigger*); Trigger *sqlite3TriggersExist(Parse *, Table*, int, ExprList*, int *pMask); Trigger *sqlite3TriggerList(Parse *, Table *); - int sqlite3CodeRowTrigger(Parse*, Trigger *, int, ExprList*, int, Table *, - int, int, int, int, u32*, u32*); + void sqlite3CodeRowTrigger(Parse*, Trigger *, int, ExprList*, int, Table *, + int, int, int, int); void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*); void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*); TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*); TriggerStep *sqlite3TriggerInsertStep(sqlite3*,Token*, IdList*, ExprList*,Select*,u8); TriggerStep *sqlite3TriggerUpdateStep(sqlite3*,Token*,ExprList*, Expr*, u8); TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*); void sqlite3DeleteTrigger(sqlite3*, Trigger*); void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); + u32 sqlite3TriggerOldmask(Parse*,Trigger*,int,ExprList*,Table*,int); +# define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p)) #else # define sqlite3TriggersExist(B,C,D,E,F) 0 # define sqlite3DeleteTrigger(A,B) # define sqlite3DropTriggerPtr(A,B) # define sqlite3UnlinkAndDeleteTrigger(A,B,C) -# define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I,J,K,L) 0 +# define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I,J) # define sqlite3TriggerList(X, Y) 0 +# define sqlite3ParseToplevel(p) p #endif int sqlite3JoinType(Parse*, Token*, Token*, Token*); void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int); void sqlite3DeferForeignKey(Parse*, int); Index: src/trigger.c ================================================================== --- src/trigger.c +++ src/trigger.c @@ -217,11 +217,11 @@ } /* Build the Trigger object */ pTrigger = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger)); if( pTrigger==0 ) goto trigger_cleanup; - pTrigger->name = zName; + pTrigger->zName = zName; zName = 0; pTrigger->table = sqlite3DbStrDup(db, pTableName->a[0].zName); pTrigger->pSchema = db->aDb[iDb].pSchema; pTrigger->pTabSchema = pTab->pSchema; pTrigger->op = (u8)op; @@ -260,18 +260,18 @@ Token nameToken; /* Trigger name for error reporting */ pTrig = pParse->pNewTrigger; pParse->pNewTrigger = 0; if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup; - zName = pTrig->name; + zName = pTrig->zName; iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema); pTrig->step_list = pStepList; while( pStepList ){ pStepList->pTrig = pTrig; pStepList = pStepList->pNext; } - nameToken.z = pTrig->name; + nameToken.z = pTrig->zName; nameToken.n = sqlite3Strlen30(nameToken.z); if( sqlite3FixInit(&sFix, pParse, iDb, "trigger", &nameToken) && sqlite3FixTriggerStep(&sFix, pTrig->step_list) ){ goto triggerfinish_cleanup; } @@ -449,11 +449,11 @@ ** Recursively delete a Trigger structure */ void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){ if( pTrigger==0 ) return; sqlite3DeleteTriggerStep(db, pTrigger->step_list); - sqlite3DbFree(db, pTrigger->name); + sqlite3DbFree(db, pTrigger->zName); sqlite3DbFree(db, pTrigger->table); sqlite3ExprDelete(db, pTrigger->pWhen); sqlite3IdListDelete(db, pTrigger->pColumns); sqlite3DbFree(db, pTrigger); } @@ -529,11 +529,11 @@ { int code = SQLITE_DROP_TRIGGER; const char *zDb = db->aDb[iDb].zName; const char *zTab = SCHEMA_TABLE(iDb); if( iDb==1 ) code = SQLITE_DROP_TEMP_TRIGGER; - if( sqlite3AuthCheck(pParse, code, pTrigger->name, pTable->zName, zDb) || + if( sqlite3AuthCheck(pParse, code, pTrigger->zName, pTable->zName, zDb) || sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){ return; } } #endif @@ -556,15 +556,15 @@ }; sqlite3BeginWriteOperation(pParse, 0, iDb); sqlite3OpenMasterTable(pParse, iDb); base = sqlite3VdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger); - sqlite3VdbeChangeP4(v, base+1, pTrigger->name, 0); + sqlite3VdbeChangeP4(v, base+1, pTrigger->zName, 0); sqlite3VdbeChangeP4(v, base+4, "trigger", P4_STATIC); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddOp2(v, OP_Close, 0, 0); - sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->name, 0); + sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0); if( pParse->nMem<3 ){ pParse->nMem = 3; } } } @@ -664,80 +664,256 @@ } return pSrc; } /* -** Generate VDBE code for zero or more statements inside the body of a -** trigger. +** Generate VDBE code for the statements inside the body of a single +** trigger. */ static int codeTriggerProgram( Parse *pParse, /* The parser context */ TriggerStep *pStepList, /* List of statements inside the trigger body */ - int orconfin /* Conflict algorithm. (OE_Abort, etc) */ + int orconf /* Conflict algorithm. (OE_Abort, etc) */ ){ - TriggerStep * pTriggerStep = pStepList; - int orconf; + TriggerStep *pStep; Vdbe *v = pParse->pVdbe; sqlite3 *db = pParse->db; - assert( pTriggerStep!=0 ); + assert( pParse->pTriggerTab && pParse->pToplevel ); + assert( pStepList ); assert( v!=0 ); - sqlite3VdbeAddOp2(v, OP_ContextPush, 0, 0); - VdbeComment((v, "begin trigger %s", pStepList->pTrig->name)); - while( pTriggerStep ){ - sqlite3ExprCacheClear(pParse); - orconf = (orconfin == OE_Default)?pTriggerStep->orconf:orconfin; - pParse->trigStack->orconf = orconf; - switch( pTriggerStep->op ){ + for(pStep=pStepList; pStep; pStep=pStep->pNext){ + /* Figure out the ON CONFLICT policy that will be used for this step + ** of the trigger program. If the statement that caused this trigger + ** to fire had an explicit ON CONFLICT, then use it. Otherwise, use + ** the ON CONFLICT policy that was specified as part of the trigger + ** step statement. Example: + ** + ** CREATE TRIGGER AFTER INSERT ON t1 BEGIN; + ** INSERT OR REPLACE INTO t2 VALUES(new.a, new.b); + ** END; + ** + ** INSERT INTO t1 ... ; -- insert into t2 uses REPLACE policy + ** INSERT OR IGNORE INTO t1 ... ; -- insert into t2 uses IGNORE policy + */ + pParse->eOrconf = (orconf==OE_Default)?pStep->orconf:orconf; + + switch( pStep->op ){ case TK_UPDATE: { - SrcList *pSrc; - pSrc = targetSrcList(pParse, pTriggerStep); - sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0); - sqlite3Update(pParse, pSrc, - sqlite3ExprListDup(db, pTriggerStep->pExprList, 0), - sqlite3ExprDup(db, pTriggerStep->pWhere, 0), orconf); - sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0); + sqlite3Update(pParse, + targetSrcList(pParse, pStep), + sqlite3ExprListDup(db, pStep->pExprList, 0), + sqlite3ExprDup(db, pStep->pWhere, 0), + pParse->eOrconf + ); break; } case TK_INSERT: { - SrcList *pSrc; - pSrc = targetSrcList(pParse, pTriggerStep); - sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0); - sqlite3Insert(pParse, pSrc, - sqlite3ExprListDup(db, pTriggerStep->pExprList, 0), - sqlite3SelectDup(db, pTriggerStep->pSelect, 0), - sqlite3IdListDup(db, pTriggerStep->pIdList), orconf); - sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0); + sqlite3Insert(pParse, + targetSrcList(pParse, pStep), + sqlite3ExprListDup(db, pStep->pExprList, 0), + sqlite3SelectDup(db, pStep->pSelect, 0), + sqlite3IdListDup(db, pStep->pIdList), + pParse->eOrconf + ); break; } case TK_DELETE: { - SrcList *pSrc; - sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0); - pSrc = targetSrcList(pParse, pTriggerStep); - sqlite3DeleteFrom(pParse, pSrc, - sqlite3ExprDup(db, pTriggerStep->pWhere, 0)); - sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0); - break; - } - default: assert( pTriggerStep->op==TK_SELECT ); { - Select *ss = sqlite3SelectDup(db, pTriggerStep->pSelect, 0); - if( ss ){ - SelectDest dest; - - sqlite3SelectDestInit(&dest, SRT_Discard, 0); - sqlite3Select(pParse, ss, &dest); - sqlite3SelectDelete(db, ss); - } - break; - } - } - pTriggerStep = pTriggerStep->pNext; - } - sqlite3VdbeAddOp2(v, OP_ContextPop, 0, 0); - VdbeComment((v, "end trigger %s", pStepList->pTrig->name)); - - return 0; + sqlite3DeleteFrom(pParse, + targetSrcList(pParse, pStep), + sqlite3ExprDup(db, pStep->pWhere, 0) + ); + break; + } + default: assert( pStep->op==TK_SELECT ); { + SelectDest sDest; + Select *pSelect = sqlite3SelectDup(db, pStep->pSelect, 0); + sqlite3SelectDestInit(&sDest, SRT_Discard, 0); + sqlite3Select(pParse, pSelect, &sDest); + sqlite3SelectDelete(db, pSelect); + break; + } + } + if( pStep->op!=TK_SELECT ){ + sqlite3VdbeAddOp1(v, OP_ResetCount, 1); + } + } + + return 0; +} + +#ifdef SQLITE_DEBUG +/* +** This function is used to add VdbeComment() annotations to a VDBE +** program. It is not used in production code, only for debugging. +*/ +static const char *onErrorText(int onError){ + switch( onError ){ + case OE_Abort: return "abort"; + case OE_Rollback: return "rollback"; + case OE_Fail: return "fail"; + case OE_Replace: return "replace"; + case OE_Ignore: return "ignore"; + case OE_Default: return "default"; + } + return "n/a"; +} +#endif + +/* +** Parse context structure pFrom has just been used to create a sub-vdbe +** (trigger program). If an error has occurred, transfer error information +** from pFrom to pTo. +*/ +static void transferParseError(Parse *pTo, Parse *pFrom){ + assert( pFrom->zErrMsg==0 || pFrom->nErr ); + assert( pTo->zErrMsg==0 || pTo->nErr ); + if( pTo->nErr==0 ){ + pTo->zErrMsg = pFrom->zErrMsg; + pTo->nErr = pFrom->nErr; + }else{ + sqlite3DbFree(pFrom->db, pFrom->zErrMsg); + } +} + +/* +** Create and populate a new TriggerPrg object with a sub-program +** implementing trigger pTrigger with ON CONFLICT policy orconf. +*/ +static TriggerPrg *codeRowTrigger( + Parse *pParse, /* Current parse context */ + Trigger *pTrigger, /* Trigger to code */ + Table *pTab, /* The table pTrigger is attached to */ + int orconf /* ON CONFLICT policy to code trigger program with */ +){ + Parse *pTop = sqlite3ParseToplevel(pParse); + sqlite3 *db = pParse->db; /* Database handle */ + TriggerPrg *pPrg; /* Value to return */ + Expr *pWhen = 0; /* Duplicate of trigger WHEN expression */ + Vdbe *v; /* Temporary VM */ + NameContext sNC; /* Name context for sub-vdbe */ + SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */ + Parse *pSubParse; /* Parse context for sub-vdbe */ + int iEndTrigger = 0; /* Label to jump to if WHEN is false */ + + assert( pTab==tableOfTrigger(pTrigger) ); + + /* Allocate the TriggerPrg and SubProgram objects. To ensure that they + ** are freed if an error occurs, link them into the Parse.pTriggerPrg + ** list of the top-level Parse object sooner rather than later. */ + pPrg = sqlite3DbMallocZero(db, sizeof(TriggerPrg)); + if( !pPrg ) return 0; + pPrg->pNext = pTop->pTriggerPrg; + pTop->pTriggerPrg = pPrg; + pPrg->pProgram = pProgram = sqlite3DbMallocZero(db, sizeof(SubProgram)); + if( !pProgram ) return 0; + pProgram->nRef = 1; + pPrg->pTrigger = pTrigger; + pPrg->orconf = orconf; + + /* Allocate and populate a new Parse context to use for coding the + ** trigger sub-program. */ + pSubParse = sqlite3StackAllocZero(db, sizeof(Parse)); + if( !pSubParse ) return 0; + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = pSubParse; + pSubParse->db = db; + pSubParse->pTriggerTab = pTab; + pSubParse->pToplevel = pTop; + pSubParse->zAuthContext = pTrigger->zName; + pSubParse->eTriggerOp = pTrigger->op; + + v = sqlite3GetVdbe(pSubParse); + if( v ){ + VdbeComment((v, "Start: %s.%s (%s %s%s%s ON %s)", + pTrigger->zName, onErrorText(orconf), + (pTrigger->tr_tm==TRIGGER_BEFORE ? "BEFORE" : "AFTER"), + (pTrigger->op==TK_UPDATE ? "UPDATE" : ""), + (pTrigger->op==TK_INSERT ? "INSERT" : ""), + (pTrigger->op==TK_DELETE ? "DELETE" : ""), + pTab->zName + )); +#ifndef SQLITE_OMIT_TRACE + sqlite3VdbeChangeP4(v, -1, + sqlite3MPrintf(db, "-- TRIGGER %s", pTrigger->zName), P4_DYNAMIC + ); +#endif + + /* If one was specified, code the WHEN clause. If it evaluates to false + ** (or NULL) the sub-vdbe is immediately halted by jumping to the + ** OP_Halt inserted at the end of the program. */ + if( pTrigger->pWhen ){ + pWhen = sqlite3ExprDup(db, pTrigger->pWhen, 0); + if( SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen) + && db->mallocFailed==0 + ){ + iEndTrigger = sqlite3VdbeMakeLabel(v); + sqlite3ExprIfFalse(pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL); + } + sqlite3ExprDelete(db, pWhen); + } + + /* Code the trigger program into the sub-vdbe. */ + codeTriggerProgram(pSubParse, pTrigger->step_list, orconf); + + /* Insert an OP_Halt at the end of the sub-program. */ + if( iEndTrigger ){ + sqlite3VdbeResolveLabel(v, iEndTrigger); + } + sqlite3VdbeAddOp0(v, OP_Halt); + VdbeComment((v, "End: %s.%s", pTrigger->zName, onErrorText(orconf))); + + transferParseError(pParse, pSubParse); + if( db->mallocFailed==0 ){ + pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg); + } + pProgram->nMem = pSubParse->nMem; + pProgram->nCsr = pSubParse->nTab; + pProgram->token = (void *)pTrigger; + pPrg->oldmask = pSubParse->oldmask; + sqlite3VdbeDelete(v); + } + + assert( !pSubParse->pAinc && !pSubParse->pZombieTab ); + assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg ); + sqlite3StackFree(db, pSubParse); + + return pPrg; +} + +/* +** Return a pointer to a TriggerPrg object containing the sub-program for +** trigger pTrigger with default ON CONFLICT algorithm orconf. If no such +** TriggerPrg object exists, a new object is allocated and populated before +** being returned. +*/ +static TriggerPrg *getRowTrigger( + Parse *pParse, /* Current parse context */ + Trigger *pTrigger, /* Trigger to code */ + Table *pTab, /* The table trigger pTrigger is attached to */ + int orconf /* ON CONFLICT algorithm. */ +){ + Parse *pRoot = sqlite3ParseToplevel(pParse); + TriggerPrg *pPrg; + + assert( pTab==tableOfTrigger(pTrigger) ); + + /* It may be that this trigger has already been coded (or is in the + ** process of being coded). If this is the case, then an entry with + ** a matching TriggerPrg.pTrigger field will be present somewhere + ** in the Parse.pTriggerPrg list. Search for such an entry. */ + for(pPrg=pRoot->pTriggerPrg; + pPrg && (pPrg->pTrigger!=pTrigger || pPrg->orconf!=orconf); + pPrg=pPrg->pNext + ); + + /* If an existing TriggerPrg could not be located, create a new one. */ + if( !pPrg ){ + pPrg = codeRowTrigger(pParse, pTrigger, pTab, orconf); + } + + return pPrg; } /* ** This is called to code FOR EACH ROW triggers. ** @@ -763,112 +939,99 @@ ** pseudo-table is read at least once, the corresponding bit of the output ** mask is set. If a column with an index greater than 32 is read, the ** output mask is set to the special value 0xffffffff. ** */ -int sqlite3CodeRowTrigger( +void sqlite3CodeRowTrigger( Parse *pParse, /* Parse context */ Trigger *pTrigger, /* List of triggers on table pTab */ int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */ ExprList *pChanges, /* Changes list for any UPDATE OF triggers */ int tr_tm, /* One of TRIGGER_BEFORE, TRIGGER_AFTER */ Table *pTab, /* The table to code triggers from */ int newIdx, /* The indice of the "new" row to access */ int oldIdx, /* The indice of the "old" row to access */ int orconf, /* ON CONFLICT policy */ - int ignoreJump, /* Instruction to jump to for RAISE(IGNORE) */ - u32 *piOldColMask, /* OUT: Mask of columns used from the OLD.* table */ - u32 *piNewColMask /* OUT: Mask of columns used from the NEW.* table */ + int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */ ){ Trigger *p; - sqlite3 *db = pParse->db; - TriggerStack trigStackEntry; - - trigStackEntry.oldColMask = 0; - trigStackEntry.newColMask = 0; assert(op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE); assert(tr_tm == TRIGGER_BEFORE || tr_tm == TRIGGER_AFTER ); - assert(newIdx != -1 || oldIdx != -1); - for(p=pTrigger; p; p=p->pNext){ - int fire_this = 0; /* Sanity checking: The schema for the trigger and for the table are ** always defined. The trigger must be in the same schema as the table ** or else it must be a TEMP trigger. */ assert( p->pSchema!=0 ); assert( p->pTabSchema!=0 ); - assert( p->pSchema==p->pTabSchema || p->pSchema==db->aDb[1].pSchema ); - - /* Determine whether we should code this trigger */ - if( - p->op==op && - p->tr_tm==tr_tm && - checkColumnOverlap(p->pColumns,pChanges) - ){ - TriggerStack *pS; /* Pointer to trigger-stack entry */ - for(pS=pParse->trigStack; pS && p!=pS->pTrigger; pS=pS->pNext){} - if( !pS ){ - fire_this = 1; - } -#if 0 /* Give no warning for recursive triggers. Just do not do them */ - else{ - sqlite3ErrorMsg(pParse, "recursive triggers not supported (%s)", - p->name); - return SQLITE_ERROR; - } -#endif - } - - if( fire_this ){ - int endTrigger; - Expr * whenExpr; - AuthContext sContext; - NameContext sNC; - -#ifndef SQLITE_OMIT_TRACE - sqlite3VdbeAddOp4(pParse->pVdbe, OP_Trace, 0, 0, 0, - sqlite3MPrintf(db, "-- TRIGGER %s", p->name), - P4_DYNAMIC); -#endif - memset(&sNC, 0, sizeof(sNC)); - sNC.pParse = pParse; - - /* Push an entry on to the trigger stack */ - trigStackEntry.pTrigger = p; - trigStackEntry.newIdx = newIdx; - trigStackEntry.oldIdx = oldIdx; - trigStackEntry.pTab = pTab; - trigStackEntry.pNext = pParse->trigStack; - trigStackEntry.ignoreJump = ignoreJump; - pParse->trigStack = &trigStackEntry; - sqlite3AuthContextPush(pParse, &sContext, p->name); - - /* code the WHEN clause */ - endTrigger = sqlite3VdbeMakeLabel(pParse->pVdbe); - whenExpr = sqlite3ExprDup(db, p->pWhen, 0); - if( db->mallocFailed || sqlite3ResolveExprNames(&sNC, whenExpr) ){ - pParse->trigStack = trigStackEntry.pNext; - sqlite3ExprDelete(db, whenExpr); - return 1; - } - sqlite3ExprIfFalse(pParse, whenExpr, endTrigger, SQLITE_JUMPIFNULL); - sqlite3ExprDelete(db, whenExpr); - - sqlite3ExprCachePush(pParse); - codeTriggerProgram(pParse, p->step_list, orconf); - sqlite3ExprCachePop(pParse, 1); - - /* Pop the entry off the trigger stack */ - pParse->trigStack = trigStackEntry.pNext; - sqlite3AuthContextPop(&sContext); - - sqlite3VdbeResolveLabel(pParse->pVdbe, endTrigger); - } - } - if( piOldColMask ) *piOldColMask |= trigStackEntry.oldColMask; - if( piNewColMask ) *piNewColMask |= trigStackEntry.newColMask; - return 0; -} + assert( p->pSchema==p->pTabSchema + || p->pSchema==pParse->db->aDb[1].pSchema ); + + /* Determine whether we should code this trigger */ + if( p->op==op + && p->tr_tm==tr_tm + && checkColumnOverlap(p->pColumns,pChanges) + ){ + Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */ + TriggerPrg *pPrg; + pPrg = getRowTrigger(pParse, p, pTab, orconf); + assert( pPrg || pParse->nErr || pParse->db->mallocFailed ); + + /* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program + ** is a pointer to the sub-vdbe containing the trigger program. */ + if( pPrg ){ + sqlite3VdbeAddOp3(v, OP_Program, oldIdx, ignoreJump, ++pParse->nMem); + pPrg->pProgram->nRef++; + sqlite3VdbeChangeP4(v, -1, (const char *)pPrg->pProgram, P4_SUBPROGRAM); + VdbeComment((v, "Call: %s.%s", p->zName, onErrorText(orconf))); + } + } + } +} + +/* +** Triggers fired by UPDATE or DELETE statements may access values stored +** in the old.* pseudo-table. This function returns a 32-bit bitmask +** indicating which columns of the old.* table actually are used by +** triggers. This information may be used by the caller to avoid having +** to load the entire old.* record into memory when executing an UPDATE +** or DELETE command. +** +** Bit 0 of the returned mask is set if the left-most column of the +** table may be accessed using an old. reference. Bit 1 is set if +** the second leftmost column value is required, and so on. If there +** are more than 32 columns in the table, and at least one of the columns +** with an index greater than 32 may be accessed, 0xffffffff is returned. +** +** It is not possible to determine if the old.rowid column is accessed +** by triggers. The caller must always assume that it is. +** +** There is no equivalent function for new.* references. +*/ +u32 sqlite3TriggerOldmask( + Parse *pParse, /* Parse context */ + Trigger *pTrigger, /* List of triggers on table pTab */ + int op, /* Either TK_UPDATE or TK_DELETE */ + ExprList *pChanges, /* Changes list for any UPDATE OF triggers */ + Table *pTab, /* The table to code triggers from */ + int orconf /* Default ON CONFLICT policy for trigger steps */ +){ + u32 mask = 0; + Trigger *p; + + assert(op==TK_UPDATE || op==TK_DELETE); + for(p=pTrigger; p; p=p->pNext){ + if( p->op==op && checkColumnOverlap(p->pColumns,pChanges) ){ + TriggerPrg *pPrg; + pPrg = getRowTrigger(pParse, p, pTab, orconf); + if( pPrg ){ + mask |= pPrg->oldmask; + } + } + } + + return mask; +} + #endif /* !defined(SQLITE_OMIT_TRIGGER) */ Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -118,26 +118,20 @@ #ifndef SQLITE_OMIT_TRIGGER int isView; /* Trying to update a view */ Trigger *pTrigger; /* List of triggers on pTab, if required */ #endif - int iBeginAfterTrigger = 0; /* Address of after trigger program */ - int iEndAfterTrigger = 0; /* Exit of after trigger program */ - int iBeginBeforeTrigger = 0; /* Address of before trigger program */ - int iEndBeforeTrigger = 0; /* Exit of before trigger program */ - u32 old_col_mask = 0; /* Mask of OLD.* columns in use */ - u32 new_col_mask = 0; /* Mask of NEW.* columns in use */ - - int newIdx = -1; /* index of trigger "new" temp table */ - int oldIdx = -1; /* index of trigger "old" temp table */ + u32 oldmask = 0; /* Mask of OLD.* columns in use */ /* Register Allocations */ int regRowCount = 0; /* A count of rows changed */ int regOldRowid; /* The old rowid */ int regNewRowid; /* The new rowid */ - int regData; /* New data for the row */ + int regNew; + int regOld; int regRowSet = 0; /* Rowset of rows to be updated */ + int regRec; /* Register used for new table record to insert */ memset(&sContext, 0, sizeof(sContext)); db = pParse->db; if( pParse->nErr || db->mallocFailed ){ goto update_cleanup; @@ -149,11 +143,11 @@ pTab = sqlite3SrcListLookup(pParse, pTabList); if( pTab==0 ) goto update_cleanup; iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); /* Figure out if we have any triggers and if the table being - ** updated is a view + ** updated is a view. */ #ifndef SQLITE_OMIT_TRIGGER pTrigger = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, 0); isView = pTab->pSelect!=0; #else @@ -173,18 +167,10 @@ } aXRef = sqlite3DbMallocRaw(db, sizeof(int) * pTab->nCol ); if( aXRef==0 ) goto update_cleanup; for(i=0; inCol; i++) aXRef[i] = -1; - /* If there are FOR EACH ROW triggers, allocate cursors for the - ** special OLD and NEW tables - */ - if( pTrigger ){ - newIdx = pParse->nTab++; - oldIdx = pParse->nTab++; - } - /* 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. */ @@ -266,28 +252,11 @@ } } aRegIdx[j] = reg; } - /* Allocate a block of register used to store the change record - ** sent to sqlite3GenerateConstraintChecks(). There are either - ** one or two registers for holding the rowid. One rowid register - ** is used if chngRowid is false and two are used if chngRowid is - ** true. Following these are pTab->nCol register holding column - ** data. - */ - regOldRowid = regNewRowid = pParse->nMem + 1; - pParse->nMem += pTab->nCol + 1; - if( chngRowid ){ - regNewRowid++; - pParse->nMem++; - } - regData = regNewRowid+1; - - - /* Begin generating code. - */ + /* Begin generating code. */ v = sqlite3GetVdbe(pParse); if( v==0 ) goto update_cleanup; if( pParse->nested==0 ) sqlite3VdbeCountChanges(v); sqlite3BeginWriteOperation(pParse, 1, iDb); @@ -300,44 +269,31 @@ pTabList = 0; goto update_cleanup; } #endif - /* Start the view context - */ + /* Allocate required registers. */ + regOldRowid = regNewRowid = ++pParse->nMem; + if( pTrigger ){ + regOld = pParse->nMem + 1; + pParse->nMem += pTab->nCol; + } + if( chngRowid || pTrigger ){ + regNewRowid = ++pParse->nMem; + } + regNew = pParse->nMem + 1; + pParse->nMem += pTab->nCol; + regRec = ++pParse->nMem; + + /* Start the view context. */ if( isView ){ sqlite3AuthContextPush(pParse, &sContext, pTab->zName); } - /* Generate the code for triggers. - */ - if( pTrigger ){ - int iGoto; - - /* Create pseudo-tables for NEW and OLD - */ - sqlite3VdbeAddOp3(v, OP_OpenPseudo, oldIdx, 0, pTab->nCol); - sqlite3VdbeAddOp3(v, OP_OpenPseudo, newIdx, 0, pTab->nCol); - - iGoto = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0); - addr = sqlite3VdbeMakeLabel(v); - iBeginBeforeTrigger = sqlite3VdbeCurrentAddr(v); - if( sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, - TRIGGER_BEFORE, pTab, newIdx, oldIdx, onError, addr, - &old_col_mask, &new_col_mask) ){ - goto update_cleanup; - } - iEndBeforeTrigger = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0); - iBeginAfterTrigger = sqlite3VdbeCurrentAddr(v); - if( sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, - TRIGGER_AFTER, pTab, newIdx, oldIdx, onError, addr, - &old_col_mask, &new_col_mask) ){ - goto update_cleanup; - } - iEndAfterTrigger = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0); - sqlite3VdbeJumpHere(v, iGoto); - } + /* If there are any triggers, set oldmask and new_col_mask. */ + oldmask = sqlite3TriggerOldmask( + pParse, pTrigger, TK_UPDATE, pChanges, pTab, onError); /* If we are trying to update a view, realize that view into ** a ephemeral table. */ #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) @@ -372,11 +328,11 @@ */ sqlite3WhereEnd(pWInfo); /* Initialize the count of updated rows */ - if( db->flags & SQLITE_CountRows && !pParse->trigStack ){ + if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab ){ regRowCount = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount); } if( !isView ){ @@ -405,15 +361,10 @@ (char*)pKey, P4_KEYINFO_HANDOFF); assert( pParse->nTab>iCur+i+1 ); } } } - - /* Jump back to this point if a trigger encounters an IGNORE constraint. */ - if( pTrigger ){ - sqlite3VdbeResolveLabel(v, addr); - } /* Top of the update loop */ if( okOnePass ){ int a1 = sqlite3VdbeAddOp1(v, OP_NotNull, regOldRowid); addr = sqlite3VdbeAddOp0(v, OP_Goto); @@ -420,143 +371,95 @@ sqlite3VdbeJumpHere(v, a1); }else{ addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet, 0, regOldRowid); } - if( pTrigger ){ - int regRowid; - int regRow; - int regCols; - - /* Make cursor iCur point to the record that is being updated. - */ - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid); - - /* Generate the OLD table - */ - regRowid = sqlite3GetTempReg(pParse); - regRow = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regRowid); - if( !old_col_mask ){ - sqlite3VdbeAddOp2(v, OP_Null, 0, regRow); - }else{ - sqlite3VdbeAddOp2(v, OP_RowData, iCur, regRow); - } - sqlite3VdbeAddOp3(v, OP_Insert, oldIdx, regRow, regRowid); - - /* Generate the NEW table - */ - if( chngRowid ){ - sqlite3ExprCodeAndCache(pParse, pRowidExpr, regRowid); - sqlite3VdbeAddOp1(v, OP_MustBeInt, regRowid); - }else{ - sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regRowid); - } - regCols = sqlite3GetTempRange(pParse, pTab->nCol); - for(i=0; inCol; i++){ - if( i==pTab->iPKey ){ - sqlite3VdbeAddOp2(v, OP_Null, 0, regCols+i); - continue; - } - j = aXRef[i]; - if( (i<32 && (new_col_mask&((u32)1<a[j].pExpr, regCols+i); - } - }else{ - sqlite3VdbeAddOp2(v, OP_Null, 0, regCols+i); - } - } - sqlite3VdbeAddOp3(v, OP_MakeRecord, regCols, pTab->nCol, regRow); - if( !isView ){ - sqlite3TableAffinityStr(v, pTab); - sqlite3ExprCacheAffinityChange(pParse, regCols, pTab->nCol); - } - sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol); - /* if( pParse->nErr ) goto update_cleanup; */ - sqlite3VdbeAddOp3(v, OP_Insert, newIdx, regRow, regRowid); - sqlite3ReleaseTempReg(pParse, regRowid); - sqlite3ReleaseTempReg(pParse, regRow); - - sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginBeforeTrigger); - sqlite3VdbeJumpHere(v, iEndBeforeTrigger); - } - - if( !isView ){ - /* Loop over every record that needs updating. We have to load - ** the old data for each record to be updated because some columns - ** might not change and we will need to copy the old value. - ** Also, the old data is needed to delete the old index entries. - ** So make the cursor point at the old record. - */ - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid); - - /* If the record number will change, push the record number as it - ** will be after the update. (The old record number is currently - ** on top of the stack.) - */ - if( chngRowid ){ - sqlite3ExprCode(pParse, pRowidExpr, regNewRowid); - sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid); - } - - /* Compute new data for this record. - */ - for(i=0; inCol; i++){ - if( i==pTab->iPKey ){ - sqlite3VdbeAddOp2(v, OP_Null, 0, regData+i); - continue; - } - j = aXRef[i]; - if( j<0 ){ - sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regData+i); - sqlite3ColumnDefault(v, pTab, i, regData+i); - }else{ - sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regData+i); - } - } - - /* Do constraint checks - */ - sqlite3GenerateConstraintChecks(pParse, pTab, iCur, regNewRowid, - aRegIdx, chngRowid, 1, - onError, addr, 0); - - /* Delete the old indices for the current record. - */ - j1 = sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regOldRowid); - sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, aRegIdx); - - /* If changing the record number, delete the old record. - */ + /* 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 there are triggers on this table, populate an array of registers + ** with the required old.* column data. */ + if( pTrigger ){ + for(i=0; inCol; i++){ + if( aXRef[i]<0 || oldmask==0xffffffff || (oldmask & (1<nCol; i++){ + if( i==pTab->iPKey ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i); + }else{ + j = aXRef[i]; + if( j<0 ){ + sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regNew+i); + sqlite3ColumnDefault(v, pTab, i, regNew+i); + }else{ + sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regNew+i); + } + } + } + + /* Fire any BEFORE UPDATE triggers. This happens before constraints are + ** verified. One could argue that this is wrong. */ + if( pTrigger ){ + sqlite3VdbeAddOp2(v, OP_Affinity, regNew, pTab->nCol); + sqlite3TableAffinityStr(v, pTab); + sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + TRIGGER_BEFORE, pTab, -1, regOldRowid, onError, addr); + } + + if( !isView ){ + + /* Do constraint checks. */ + sqlite3GenerateConstraintChecks(pParse, pTab, iCur, regNewRowid, + aRegIdx, (chngRowid?regOldRowid:0), 1, onError, addr, 0); + + /* Delete the index entries associated with the current record. */ + j1 = sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regOldRowid); + sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, aRegIdx); + + /* If changing the record number, delete the old record. */ if( chngRowid ){ sqlite3VdbeAddOp2(v, OP_Delete, iCur, 0); } sqlite3VdbeJumpHere(v, j1); - - /* Create the new index entries and the new record. - */ - sqlite3CompleteInsertion(pParse, pTab, iCur, regNewRowid, - aRegIdx, 1, -1, 0, 0); + + /* Insert the new index entries and the new record. */ + sqlite3CompleteInsertion(pParse, pTab, iCur, regNewRowid, aRegIdx, 1, 0, 0); } /* Increment the row counter */ - if( db->flags & SQLITE_CountRows && !pParse->trigStack){ + if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab){ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); } - /* If there are triggers, close all the cursors after each iteration - ** through the loop. The fire the after triggers. - */ - if( pTrigger ){ - sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginAfterTrigger); - sqlite3VdbeJumpHere(v, iEndAfterTrigger); - } + sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + TRIGGER_AFTER, pTab, -1, regOldRowid, onError, addr); /* 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); @@ -567,29 +470,25 @@ if( openAll || aRegIdx[i]>0 ){ sqlite3VdbeAddOp2(v, OP_Close, iCur+i+1, 0); } } sqlite3VdbeAddOp2(v, OP_Close, iCur, 0); - if( pTrigger ){ - sqlite3VdbeAddOp2(v, OP_Close, newIdx, 0); - sqlite3VdbeAddOp2(v, OP_Close, oldIdx, 0); - } /* Update the sqlite_sequence table by storing the content of the ** maximum rowid counter values recorded while inserting into ** autoincrement tables. */ - if( pParse->nested==0 && pParse->trigStack==0 ){ + if( pParse->nested==0 && pParse->pTriggerTab==0 ){ sqlite3AutoincrementEnd(pParse); } /* ** Return the number of rows that were changed. If this routine is ** generating code because of a call to sqlite3NestedParse(), do not ** invoke the callback function. */ - if( db->flags & SQLITE_CountRows && !pParse->trigStack && pParse->nested==0 ){ + if( (db->flags&SQLITE_CountRows) && !pParse->pTriggerTab && !pParse->nested ){ sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1); sqlite3VdbeSetNumCols(v, 1); sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC); } Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -844,13 +844,31 @@ ** 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: { + 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--; + sqlite3VdbeSetChanges(db, p->nChange); + pc = sqlite3VdbeFrameRestore(pFrame); + if( pOp->p2==OE_Ignore ){ + /* Instruction pc is the OP_Program that invoked the sub-program + ** currently being halted. If the p2 instruction of this OP_Halt + ** instruction is set to OE_Ignore, then the sub-program is throwing + ** an IGNORE exception. In this case jump to the address specified + ** as the p2 of the calling OP_Program. */ + pc = p->aOp[pc].p2-1; + } + break; + } + p->rc = pOp->p1; - p->pc = pc; p->errorAction = (u8)pOp->p2; + p->pc = pc; if( pOp->p4.z ){ sqlite3SetString(&p->zErrMsg, db, "%s", pOp->p4.z); } rc = sqlite3VdbeHalt(p); assert( rc==SQLITE_BUSY || rc==SQLITE_OK ); @@ -3535,23 +3553,24 @@ ** Get a new integer record number (a.k.a "rowid") used as the key to a table. ** The record number is not previously used as a key in the database ** table that cursor P1 points to. The new record number is written ** written to register P2. ** -** If P3>0 then P3 is a register that holds the largest previously -** generated record number. No new record numbers are allowed to be less -** than this value. When this value reaches its maximum, a SQLITE_FULL -** error is generated. The P3 register is updated with the generated -** record number. This P3 mechanism is used to help implement the +** If P3>0 then P3 is a register in the root frame of this VDBE that holds +** the largest previously generated record number. No new record numbers are +** allowed to be less than this value. When this value reaches its maximum, +** a SQLITE_FULL error is generated. The P3 register is updated with the ' +** generated record number. This P3 mechanism is used to help implement the ** AUTOINCREMENT feature. */ case OP_NewRowid: { /* out2-prerelease */ i64 v; /* The new rowid */ VdbeCursor *pC; /* Cursor of table to get the new rowid */ int res; /* Result of an sqlite3BtreeLast() */ int cnt; /* Counter to limit the number of searches */ Mem *pMem; /* Register holding largest rowid for AUTOINCREMENT */ + VdbeFrame *pFrame; /* Root frame of VDBE */ v = 0; res = 0; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; @@ -3606,13 +3625,20 @@ } } #ifndef SQLITE_OMIT_AUTOINCREMENT if( pOp->p3 ){ - assert( pOp->p3>0 && pOp->p3<=p->nMem ); /* P3 is a valid memory cell */ - pMem = &p->aMem[pOp->p3]; - REGISTER_TRACE(pOp->p3, pMem); + if( p->pFrame ){ + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + pMem = &pFrame->aMem[pOp->p3]; + }else{ + pMem = &p->aMem[pOp->p3]; + } + /* Assert that P3 is a valid memory cell. */ + assert( pOp->p3>0 && pOp->p3<=(p->pFrame ? pFrame->nMem : p->nMem) ); + + REGISTER_TRACE(pOp->p3, pMem); sqlite3VdbeMemIntegerify(pMem); assert( (pMem->flags & MEM_Int)!=0 ); /* mem(P3) holds an integer */ if( pMem->u.i==MAX_ROWID || pC->useRandomRowid ){ rc = SQLITE_FULL; goto abort_due_to_error; @@ -4729,61 +4755,168 @@ break; } #ifndef SQLITE_OMIT_TRIGGER -/* Opcode: ContextPush * * * -** -** Save the current Vdbe context such that it can be restored by a ContextPop -** opcode. The context stores the last insert row id, the last statement change -** count, and the current statement change count. -*/ -case OP_ContextPush: { - int i; - Context *pContext; - - i = p->contextStackTop++; - assert( i>=0 ); - /* FIX ME: This should be allocated as part of the vdbe at compile-time */ - if( i>=p->contextStackDepth ){ - p->contextStackDepth = i+1; - p->contextStack = sqlite3DbReallocOrFree(db, p->contextStack, - sizeof(Context)*(i+1)); - if( p->contextStack==0 ) goto no_mem; - } - pContext = &p->contextStack[i]; - pContext->lastRowid = db->lastRowid; - pContext->nChange = p->nChange; - break; -} - -/* Opcode: ContextPop * * * -** -** Restore the Vdbe context to the state it was in when contextPush was last -** executed. The context stores the last insert row id, the last statement -** change count, and the current statement change count. -*/ -case OP_ContextPop: { - Context *pContext; - pContext = &p->contextStack[--p->contextStackTop]; - assert( p->contextStackTop>=0 ); - db->lastRowid = pContext->lastRowid; - p->nChange = pContext->nChange; - break; -} + +/* Opcode: Program P1 P2 P3 P4 * +** +** Execute the trigger program passed as P4 (type P4_SUBPROGRAM). +** +** P1 contains the address of the memory cell that contains the first memory +** cell in an array of values used as arguments to the sub-program. P2 +** contains the address to jump to if the sub-program throws an IGNORE +** exception using the RAISE() function. Register P3 contains the address +** of a memory cell in this (the parent) VM that is used to allocate the +** memory required by the sub-vdbe at runtime. +** +** P4 is a pointer to the VM containing the trigger program. +*/ +case OP_Program: { /* jump */ + int nMem; /* Number of memory registers for sub-program */ + int nByte; /* Bytes of runtime space required for sub-program */ + Mem *pRt; /* Register to allocate runtime space */ + Mem *pMem; /* Used to iterate through memory cells */ + Mem *pEnd; /* Last memory cell in new array */ + VdbeFrame *pFrame; /* New vdbe frame to execute in */ + SubProgram *pProgram; /* Sub-program to execute */ + void *t; /* Token identifying trigger */ + + pProgram = pOp->p4.pProgram; + pRt = &p->aMem[pOp->p3]; + assert( pProgram->nOp>0 ); + + /* If the SQLITE_NoRecTriggers flag it set, then recursive invocation of + ** triggers is disabled for backwards compatibility (flag set/cleared by + ** the "PRAGMA disable_recursive_triggers" command). + ** + ** It is recursive invocation of triggers, at the SQL level, that is + ** disabled. In some cases a single trigger may generate more than one + ** SubProgram (if the trigger may be executed with more than one different + ** ON CONFLICT algorithm). SubProgram structures associated with a + ** single trigger all have the same value for the SubProgram.token + ** variable. + */ + if( db->flags&SQLITE_NoRecTriggers ){ + t = pProgram->token; + for(pFrame=p->pFrame; pFrame && pFrame->token!=t; pFrame=pFrame->pParent); + if( pFrame ) break; + } + + /* TODO: This constant should be configurable. */ + if( p->nFrame>1000 ){ + rc = SQLITE_ERROR; + sqlite3SetString(&p->zErrMsg, db, "too many levels of trigger recursion"); + break; + } + + /* Register pRt is used to store the memory required to save the state + ** of the current program, and the memory required at runtime to execute + ** the trigger program. If this trigger has been fired before, then pRt + ** is already allocated. Otherwise, it must be initialized. */ + if( (pRt->flags&MEM_Frame)==0 ){ + /* SubProgram.nMem is set to the number of memory cells used by the + ** program stored in SubProgram.aOp. As well as these, one memory + ** cell is required for each cursor used by the program. Set local + ** variable nMem (and later, VdbeFrame.nChildMem) to this value. + */ + nMem = pProgram->nMem + pProgram->nCsr; + nByte = ROUND8(sizeof(VdbeFrame)) + + nMem * sizeof(Mem) + + pProgram->nCsr * sizeof(VdbeCursor *); + pFrame = sqlite3DbMallocZero(db, nByte); + if( !pFrame ){ + goto no_mem; + } + sqlite3VdbeMemRelease(pRt); + pRt->flags = MEM_Frame; + pRt->u.pFrame = pFrame; + + pFrame->v = p; + pFrame->nChildMem = nMem; + pFrame->nChildCsr = pProgram->nCsr; + pFrame->pc = pc; + pFrame->aMem = p->aMem; + pFrame->nMem = p->nMem; + pFrame->apCsr = p->apCsr; + pFrame->nCursor = p->nCursor; + pFrame->aOp = p->aOp; + pFrame->nOp = p->nOp; + pFrame->token = pProgram->token; + + pEnd = &VdbeFrameMem(pFrame)[pFrame->nChildMem]; + for(pMem=VdbeFrameMem(pFrame); pMem!=pEnd; pMem++){ + pMem->flags = MEM_Null; + pMem->db = db; + } + }else{ + pFrame = pRt->u.pFrame; + assert( pProgram->nMem+pProgram->nCsr==pFrame->nChildMem ); + assert( pProgram->nCsr==pFrame->nChildCsr ); + assert( pc==pFrame->pc ); + } + + p->nFrame++; + pFrame->pParent = p->pFrame; + pFrame->lastRowid = db->lastRowid; + pFrame->nChange = p->nChange; + p->nChange = 0; + p->pFrame = pFrame; + p->aMem = &VdbeFrameMem(pFrame)[-1]; + p->nMem = pFrame->nChildMem; + p->nCursor = pFrame->nChildCsr; + p->apCsr = (VdbeCursor **)&p->aMem[p->nMem+1]; + p->aOp = pProgram->aOp; + p->nOp = pProgram->nOp; + pc = -1; + + break; +} + +/* Opcode: Param P1 P2 * * * +** +** This opcode is only ever present in sub-programs called via the +** OP_Program instruction. Copy a value currently stored in a memory +** cell of the calling (parent) frame to cell P2 in the current frames +** address space. This is used by trigger programs to access the new.* +** and old.* values. +** +** The address of the cell in the parent frame is determined by adding +** the value of the P1 argument to the value of the P1 argument to the +** calling OP_Program instruction. +*/ +case OP_Param: { /* out2-prerelease */ + VdbeFrame *pFrame; + Mem *pIn; + pFrame = p->pFrame; + pIn = &pFrame->aMem[pOp->p1 + pFrame->aOp[pFrame->pc].p1]; + sqlite3VdbeMemShallowCopy(pOut, pIn, MEM_Ephem); + break; +} + #endif /* #ifndef SQLITE_OMIT_TRIGGER */ #ifndef SQLITE_OMIT_AUTOINCREMENT /* Opcode: MemMax P1 P2 * * * ** -** Set the value of register P1 to the maximum of its current value -** and the value in register P2. +** P1 is a register in the root frame of this VM (the root frame is +** different from the current frame if this instruction is being executed +** within a sub-program). Set the value of register P1 to the maximum of +** its current value and the value in register P2. ** ** This instruction throws an error if the memory cell is not initially ** an integer. */ -case OP_MemMax: { /* in1, in2 */ +case OP_MemMax: { /* in2 */ + Mem *pIn1; + VdbeFrame *pFrame; + if( p->pFrame ){ + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + pIn1 = &pFrame->aMem[pOp->p1]; + }else{ + pIn1 = &p->aMem[pOp->p1]; + } sqlite3VdbeMemIntegerify(pIn1); sqlite3VdbeMemIntegerify(pIn2); if( pIn1->u.iu.i){ pIn1->u.i = pIn2->u.i; } Index: src/vdbe.h ================================================================== --- src/vdbe.h +++ src/vdbe.h @@ -32,10 +32,11 @@ ** The names of the following types declared in vdbeInt.h are required ** for the VdbeOp definition. */ typedef struct VdbeFunc VdbeFunc; typedef struct Mem Mem; +typedef struct SubProgram SubProgram; /* ** A single instruction of the virtual machine has an opcode ** and as many as three operands. The instruction is recorded ** as an instance of the following structure: @@ -46,11 +47,11 @@ u8 opflags; /* Not currently used */ u8 p5; /* Fifth parameter is an unsigned character */ int p1; /* First operand */ int p2; /* Second parameter (often the jump destination) */ int p3; /* The third parameter */ - union { /* forth parameter */ + union { /* fourth parameter */ int i; /* Integer value if p4type==P4_INT32 */ void *p; /* Generic pointer */ char *z; /* Pointer to data for string (char array) types */ i64 *pI64; /* Used when p4type is P4_INT64 */ double *pReal; /* Used when p4type is P4_REAL */ @@ -59,10 +60,11 @@ CollSeq *pColl; /* Used when p4type is P4_COLLSEQ */ Mem *pMem; /* Used when p4type is P4_MEM */ VTable *pVtab; /* Used when p4type is P4_VTAB */ KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */ int *ai; /* Used when p4type is P4_INTARRAY */ + SubProgram *pProgram; /* Used when p4type is P4_SUBPROGRAM */ } p4; #ifdef SQLITE_DEBUG char *zComment; /* Comment to improve readability */ #endif #ifdef VDBE_PROFILE @@ -70,10 +72,23 @@ u64 cycles; /* Total time spent executing this instruction */ #endif }; typedef struct VdbeOp VdbeOp; + +/* +** A sub-routine used to implement a trigger program. +*/ +struct SubProgram { + VdbeOp *aOp; /* Array of opcodes for sub-program */ + int nOp; /* Elements in aOp[] */ + int nMem; /* Number of memory cells required */ + int nCsr; /* Number of cursors required */ + int nRef; /* Number of pointers to this structure */ + void *token; /* id that may be used to recursive triggers */ +}; + /* ** A smaller version of VdbeOp used for the VdbeAddOpList() function because ** it takes up less space. */ struct VdbeOpList { @@ -83,11 +98,11 @@ signed char p3; /* Third parameter */ }; typedef struct VdbeOpList VdbeOpList; /* -** Allowed values of VdbeOp.p3type +** Allowed values of VdbeOp.p4type */ #define P4_NOTUSED 0 /* The P4 parameter is not used */ #define P4_DYNAMIC (-1) /* Pointer to a string obtained from sqliteMalloc() */ #define P4_STATIC (-2) /* Pointer to a static string */ #define P4_COLLSEQ (-4) /* P4 is a pointer to a CollSeq structure */ @@ -100,10 +115,11 @@ #define P4_MPRINTF (-11) /* P4 is a string obtained from sqlite3_mprintf() */ #define P4_REAL (-12) /* P4 is a 64-bit floating point value */ #define P4_INT64 (-13) /* P4 is a 64-bit signed integer */ #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 */ /* 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 @@ -166,11 +182,11 @@ void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N); void sqlite3VdbeUsesBtree(Vdbe*, int); VdbeOp *sqlite3VdbeGetOp(Vdbe*, int); int sqlite3VdbeMakeLabel(Vdbe*); void sqlite3VdbeDelete(Vdbe*); -void sqlite3VdbeMakeReady(Vdbe*,int,int,int,int); +void sqlite3VdbeMakeReady(Vdbe*,int,int,int,int,int); int sqlite3VdbeFinalize(Vdbe*); void sqlite3VdbeResolveLabel(Vdbe*, int); int sqlite3VdbeCurrentAddr(Vdbe*); #ifdef SQLITE_DEBUG void sqlite3VdbeTrace(Vdbe*,FILE*); @@ -181,10 +197,12 @@ int sqlite3VdbeSetColName(Vdbe*, int, int, const char *, void(*)(void*)); void sqlite3VdbeCountChanges(Vdbe*); sqlite3 *sqlite3VdbeDb(Vdbe*); void sqlite3VdbeSetSql(Vdbe*, const char *z, int n, int); void sqlite3VdbeSwap(Vdbe*,Vdbe*); +VdbeOp *sqlite3VdbeTakeOpArray(Vdbe*, int*, int*); +void sqlite3VdbeProgramDelete(sqlite3 *, SubProgram *, int); #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT int sqlite3VdbeReleaseMemory(int); #endif UnpackedRecord *sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,char*,int); Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -87,10 +87,43 @@ u32 *aOffset; /* Cached offsets to the start of each columns data */ u8 *aRow; /* Data for the current row, if all on one page */ }; typedef struct VdbeCursor VdbeCursor; +/* +** When a sub-program is executed (OP_Program), a structure of this type +** is allocated to store the current value of the program counter, as +** well as the current memory cell array and various other frame specific +** values stored in the Vdbe struct. When the sub-program is finished, +** these values are copied back to the Vdbe from the VdbeFrame structure, +** restoring the state of the VM to as it was before the sub-program +** began executing. +** +** Frames are stored in a linked list headed at Vdbe.pParent. Vdbe.pParent +** is the parent of the current frame, or zero if the current frame +** is the main Vdbe program. +*/ +typedef struct VdbeFrame VdbeFrame; +struct VdbeFrame { + Vdbe *v; /* VM this frame belongs to */ + int pc; /* Program Counter */ + Op *aOp; /* Program instructions */ + int nOp; /* Size of aOp array */ + Mem *aMem; /* Array of memory cells */ + int nMem; /* Number of entries in aMem */ + VdbeCursor **apCsr; /* Element of Vdbe cursors */ + u16 nCursor; /* Number of entries in apCsr */ + void *token; /* Copy of SubProgram.token */ + int nChildMem; /* Number of memory cells for child frame */ + int nChildCsr; /* Number of cursors for child frame */ + i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */ + int nChange; /* Statement changes (Vdbe.nChanges) */ + VdbeFrame *pParent; /* Parent of this frame */ +}; + +#define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))]) + /* ** A value for VdbeCursor.cacheValid that means the cache is always invalid. */ #define CACHE_STALE 0 @@ -109,10 +142,11 @@ union { i64 i; /* Integer value. */ int nZero; /* Used when bit MEM_Zero is set in flags */ FuncDef *pDef; /* Used only when flags==MEM_Agg */ RowSet *pRowSet; /* Used only when flags==MEM_RowSet */ + VdbeFrame *pFrame; /* Used when flags==MEM_Frame */ } u; double r; /* Real value */ sqlite3 *db; /* The associated database connection */ char *z; /* String or BLOB value */ int n; /* Number of characters in string value, excluding '\0' */ @@ -142,10 +176,11 @@ #define MEM_Str 0x0002 /* Value is a string */ #define MEM_Int 0x0004 /* Value is an integer */ #define MEM_Real 0x0008 /* Value is a real number */ #define MEM_Blob 0x0010 /* Value is a BLOB */ #define MEM_RowSet 0x0020 /* Value is a RowSet object */ +#define MEM_Frame 0x0040 /* Value is a VdbeFrame object */ #define MEM_TypeMask 0x00ff /* Mask of type bits */ /* Whenever Mem contains a valid string or blob representation, one of ** the following flags must be set to determine the memory management ** policy for Mem.z. The MEM_Term flag tells us whether or not the @@ -221,25 +256,10 @@ struct Set { Hash hash; /* A set is just a hash table */ HashElem *prev; /* Previously accessed hash elemen */ }; -/* -** A Context stores the last insert rowid, the last statement change count, -** and the current statement change count (i.e. changes since last statement). -** The current keylist is also stored in the context. -** Elements of Context structure type make up the ContextStack, which is -** updated by the ContextPush and ContextPop opcodes (used by triggers). -** The context is pushed before executing a trigger a popped when the -** trigger finishes. -*/ -typedef struct Context Context; -struct Context { - i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */ - int nChange; /* Statement changes (Vdbe.nChanges) */ -}; - /* ** An instance of the virtual machine. This structure contains the complete ** state of the virtual machine. ** ** The "sqlite3_stmt" structure pointer that is returned by sqlite3_compile() @@ -275,13 +295,10 @@ char **azVar; /* Name of variables */ u32 magic; /* Magic number for sanity checking */ int nMem; /* Number of memory locations currently allocated */ Mem *aMem; /* The memory locations */ int cacheCtr; /* VdbeCursor row cache generation counter */ - int contextStackTop; /* Index of top element in the context stack */ - int contextStackDepth; /* The size of the "context" stack */ - Context *contextStack; /* Stack used by opcodes ContextPush & ContextPop*/ int pc; /* The program counter */ int rc; /* Value to return */ char *zErrMsg; /* Error message written here */ u8 explain; /* True if EXPLAIN present on SQL command */ u8 changeCntOn; /* True to update the change-counter */ @@ -300,10 +317,12 @@ void *pFree; /* Free this when deleting the vdbe */ int iStatement; /* Statement number (or 0 if has not opened stmt) */ #ifdef SQLITE_DEBUG FILE *trace; /* Write an execution trace here, if not NULL */ #endif + VdbeFrame *pFrame; /* Parent frame */ + int nFrame; /* Number of frames in pFrame list */ }; /* ** The following are allowed values for Vdbe.magic */ @@ -360,10 +379,12 @@ int sqlite3VdbeMemFinalize(Mem*, FuncDef*); const char *sqlite3OpcodeName(int); int sqlite3VdbeOpcodeHasProperty(int, int); int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve); int sqlite3VdbeCloseStatement(Vdbe *, int); +void sqlite3VdbeFrameDelete(VdbeFrame*); +int sqlite3VdbeFrameRestore(VdbeFrame *); #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT int sqlite3VdbeReleaseBuffers(Vdbe *p); #endif #ifndef SQLITE_OMIT_SHARED_CACHE Index: src/vdbeapi.c ================================================================== --- src/vdbeapi.c +++ src/vdbeapi.c @@ -74,11 +74,11 @@ rc = SQLITE_OK; }else{ Vdbe *v = (Vdbe*)pStmt; sqlite3_mutex_enter(v->db->mutex); rc = sqlite3VdbeReset(v); - sqlite3VdbeMakeReady(v, -1, 0, 0, 0); + sqlite3VdbeMakeReady(v, -1, 0, 0, 0, 0); assert( (rc & (v->db->errMask))==rc ); rc = sqlite3ApiExit(v->db, rc); sqlite3_mutex_leave(v->db->mutex); } return rc; Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -264,11 +264,11 @@ ** is changed to a Noop. In this way, we avoid creating the statement ** journal file unnecessarily. */ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ int i; - int nMaxArgs = 0; + int nMaxArgs = *pMaxFuncArgs; Op *pOp; int *aLabel = p->aLabel; int doesStatementRollback = 0; int hasStatementBegin = 0; p->readOnly = 1; @@ -293,11 +293,11 @@ }else if( opcode==OP_Destroy ){ doesStatementRollback = 1; }else if( opcode==OP_Transaction && pOp->p2!=0 ){ p->readOnly = 0; #ifndef SQLITE_OMIT_VIRTUALTABLE - }else if( opcode==OP_VUpdate || opcode==OP_VRename ){ + }else if( opcode==OP_VUpdate || opcode==OP_VRename || opcode==OP_Program ){ doesStatementRollback = 1; }else if( opcode==OP_VFilter ){ int n; assert( p->nOp - i >= 3 ); assert( pOp[-1].opcode==OP_Integer ); @@ -336,10 +336,34 @@ */ int sqlite3VdbeCurrentAddr(Vdbe *p){ assert( p->magic==VDBE_MAGIC_INIT ); return p->nOp; } + +/* +** This function returns a pointer to the array of opcodes associated with +** the Vdbe passed as the first argument. It is the callers responsibility +** to arrange for the returned array to be eventually freed using the +** vdbeFreeOpArray() function. +** +** Before returning, *pnOp is set to the number of entries in the returned +** array. Also, *pnMaxArg is set to the larger of its current value and +** the number of entries in the Vdbe.apArg[] array required to execute the +** returned program. +*/ +VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg){ + VdbeOp *aOp = p->aOp; + assert( aOp && !p->db->mallocFailed ); + + /* Check that sqlite3VdbeUsesBtree() was not called on this VM */ + assert( p->aMutex.nMutex==0 ); + + resolveP2Values(p, pnMaxArg); + *pnOp = p->nOp; + p->aOp = 0; + return aOp; +} /* ** Add a whole list of operations to the operation stack. Return the ** address of the first operation added. */ @@ -480,10 +504,61 @@ } case P4_VTAB : { sqlite3VtabUnlock((VTable *)p4); break; } + case P4_SUBPROGRAM : { + sqlite3VdbeProgramDelete(db, (SubProgram *)p4, 1); + break; + } + } + } +} + +/* +** Free the space allocated for aOp and any p4 values allocated for the +** opcodes contained within. If aOp is not NULL it is assumed to contain +** nOp entries. +*/ +static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ + if( aOp ){ + Op *pOp; + for(pOp=aOp; pOp<&aOp[nOp]; pOp++){ + freeP4(db, pOp->p4type, pOp->p4.p); +#ifdef SQLITE_DEBUG + sqlite3DbFree(db, pOp->zComment); +#endif + } + } + sqlite3DbFree(db, aOp); +} + +/* +** Decrement the ref-count on the SubProgram structure passed as the +** second argument. If the ref-count reaches zero, free the structure. +** +** The array of VDBE opcodes stored as SubProgram.aOp is freed if +** either the ref-count reaches zero or parameter freeop is non-zero. +** +** Since the array of opcodes pointed to by SubProgram.aOp may directly +** or indirectly contain a reference to the SubProgram structure itself. +** By passing a non-zero freeop parameter, the caller may ensure that all +** SubProgram structures and their aOp arrays are freed, even when there +** are such circular references. +*/ +void sqlite3VdbeProgramDelete(sqlite3 *db, SubProgram *p, int freeop){ + if( p ){ + assert( p->nRef>0 ); + if( freeop || p->nRef==1 ){ + Op *aOp = p->aOp; + p->aOp = 0; + vdbeFreeOpArray(db, aOp, p->nOp); + p->nOp = 0; + } + p->nRef--; + if( p->nRef==0 ){ + sqlite3DbFree(db, p); } } } @@ -602,10 +677,11 @@ ** makes the code easier to read during debugging. None of this happens ** in a production build. */ void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){ va_list ap; + if( !p ) return; assert( p->nOp>0 || p->aOp==0 ); assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed ); if( p->nOp ){ char **pz = &p->aOp[p->nOp-1].zComment; va_start(ap, zFormat); @@ -614,10 +690,11 @@ va_end(ap); } } void sqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){ va_list ap; + if( !p ) return; sqlite3VdbeAddOp0(p, OP_Noop); assert( p->nOp>0 || p->aOp==0 ); assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed ); if( p->nOp ){ char **pz = &p->aOp[p->nOp-1].zComment; @@ -749,10 +826,14 @@ } #endif case P4_INTARRAY: { sqlite3_snprintf(nTemp, zTemp, "intarray"); break; + } + case P4_SUBPROGRAM: { + sqlite3_snprintf(nTemp, zTemp, "program"); + break; } default: { zP4 = pOp->p4.z; if( zP4==0 ){ zP4 = zTemp; @@ -765,11 +846,10 @@ } #endif /* ** Declare to the Vdbe that the BTree object at db->aDb[i] is used. -** */ void sqlite3VdbeUsesBtree(Vdbe *p, int i){ int mask; assert( i>=0 && idb->nDb && ibtreeMask)*8 ); @@ -824,11 +904,11 @@ ** sqlite3MemRelease() were called from here. With -O2, this jumps ** to 6.6 percent. The test case is inserting 1000 rows into a table ** with no indexes using a single prepared INSERT statement, bind() ** and reset(). Inserts are grouped into a transaction. */ - if( p->flags&(MEM_Agg|MEM_Dyn) ){ + if( p->flags&(MEM_Agg|MEM_Dyn|MEM_Frame|MEM_RowSet) ){ sqlite3VdbeMemRelease(p); }else if( p->zMalloc ){ sqlite3DbFree(db, p->zMalloc); p->zMalloc = 0; } @@ -836,10 +916,26 @@ p->flags = MEM_Null; } db->mallocFailed = malloc_failed; } } + +/* +** Delete a VdbeFrame object and its contents. VdbeFrame objects are +** allocated by the OP_Program opcode in sqlite3VdbeExec(). +*/ +void sqlite3VdbeFrameDelete(VdbeFrame *p){ + int i; + Mem *aMem = VdbeFrameMem(p); + VdbeCursor **apCsr = (VdbeCursor **)&aMem[p->nChildMem]; + for(i=0; inChildCsr; i++){ + sqlite3VdbeFreeCursor(p->v, apCsr[i]); + } + releaseMemArray(aMem, p->nChildMem); + sqlite3DbFree(p->v->db, p); +} + #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT int sqlite3VdbeReleaseBuffers(Vdbe *p){ int ii; int nFree = 0; @@ -873,10 +969,14 @@ ** EXPLAIN QUERY PLAN. */ int sqlite3VdbeList( Vdbe *p /* The VDBE */ ){ + int nRow; /* Total number of rows to return */ + int nSub = 0; /* Number of sub-vdbes seen so far */ + SubProgram **apSub = 0; /* Array of sub-vdbes */ + Mem *pSub = 0; sqlite3 *db = p->db; int i; int rc = SQLITE_OK; Mem *pMem = p->pResultSet = &p->aMem[1]; @@ -887,32 +987,56 @@ /* Even though this opcode does not use dynamic strings for ** the result, result columns may become dynamic if the user calls ** sqlite3_column_text16(), causing a translation to UTF-16 encoding. */ - releaseMemArray(pMem, p->nMem); + releaseMemArray(pMem, 8); if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or ** sqlite3_column_text16() failed. */ db->mallocFailed = 1; return SQLITE_ERROR; } + + /* Figure out total number of rows that will be returned by this + ** EXPLAIN program. */ + nRow = p->nOp; + if( p->explain==1 ){ + pSub = &p->aMem[9]; + if( pSub->flags&MEM_Blob ){ + nSub = pSub->n/sizeof(Vdbe*); + apSub = (SubProgram **)pSub->z; + } + for(i=0; inOp; + } + } do{ i = p->pc++; - }while( inOp && p->explain==2 && p->aOp[i].opcode!=OP_Explain ); - if( i>=p->nOp ){ + }while( iexplain==2 && p->aOp[i].opcode!=OP_Explain ); + if( i>=nRow ){ p->rc = SQLITE_OK; rc = SQLITE_DONE; }else if( db->u1.isInterrupted ){ p->rc = SQLITE_INTERRUPT; rc = SQLITE_ERROR; sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3ErrStr(p->rc)); }else{ char *z; - Op *pOp = &p->aOp[i]; + Op *pOp; + if( inOp ){ + pOp = &p->aOp[i]; + }else{ + int j; + i -= p->nOp; + for(j=0; i>=apSub[j]->nOp; j++){ + i -= apSub[j]->nOp; + } + pOp = &apSub[j]->aOp[i]; + } if( p->explain==1 ){ pMem->flags = MEM_Int; pMem->type = SQLITE_INTEGER; pMem->u.i = i; /* Program counter */ pMem++; @@ -922,10 +1046,24 @@ assert( pMem->z!=0 ); pMem->n = sqlite3Strlen30(pMem->z); pMem->type = SQLITE_TEXT; pMem->enc = SQLITE_UTF8; pMem++; + + if( pOp->p4type==P4_SUBPROGRAM ){ + int nByte = (nSub+1)*sizeof(SubProgram*); + int j; + for(j=0; jp4.pProgram ) break; + } + if( j==nSub && SQLITE_OK==sqlite3VdbeMemGrow(pSub, nByte, 1) ){ + apSub = (SubProgram **)pSub->z; + apSub[nSub++] = pOp->p4.pProgram; + pSub->flags |= MEM_Blob; + pSub->n = nSub*sizeof(SubProgram*); + } + } } pMem->flags = MEM_Int; pMem->u.i = pOp->p1; /* P1 */ pMem->type = SQLITE_INTEGER; @@ -1096,10 +1234,11 @@ void sqlite3VdbeMakeReady( Vdbe *p, /* The VDBE */ int nVar, /* Number of '?' see in the SQL statement */ int nMem, /* Number of memory cells to allocate */ int nCursor, /* Number of cursors to allocate */ + int nArg, /* Maximum number of args in SubPrograms */ int isExplain /* True if the EXPLAIN keywords is present */ ){ int n; sqlite3 *db = p->db; @@ -1131,11 +1270,10 @@ */ if( nVar>=0 && ALWAYS(db->mallocFailed==0) ){ u8 *zCsr = (u8 *)&p->aOp[p->nOp]; u8 *zEnd = (u8 *)&p->aOp[p->nOpAlloc]; int nByte; - int nArg; /* Maximum number of args passed to a user function. */ resolveP2Values(p, &nArg); if( isExplain && nMem<10 ){ nMem = 10; } memset(zCsr, 0, zEnd-zCsr); @@ -1229,23 +1367,58 @@ #endif if( !pCx->ephemPseudoTable ){ sqlite3DbFree(p->db, pCx->pData); } } + +/* +** Copy the values stored in the VdbeFrame structure to its Vdbe. This +** is used, for example, when a trigger sub-program is halted to restore +** control to the main program. +*/ +int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ + Vdbe *v = pFrame->v; + v->aOp = pFrame->aOp; + v->nOp = pFrame->nOp; + v->aMem = pFrame->aMem; + v->nMem = pFrame->nMem; + v->apCsr = pFrame->apCsr; + v->nCursor = pFrame->nCursor; + v->db->lastRowid = pFrame->lastRowid; + v->nChange = pFrame->nChange; + return pFrame->pc; +} /* ** Close all cursors. +** +** Also release any dynamic memory held by the VM in the Vdbe.aMem memory +** cell array. This is necessary as the memory cell array may contain +** pointers to VdbeFrame objects, which may in turn contain pointers to +** open cursors. */ static void closeAllCursors(Vdbe *p){ - int i; - if( p->apCsr==0 ) return; - for(i=0; inCursor; i++){ - VdbeCursor *pC = p->apCsr[i]; - if( pC ){ - sqlite3VdbeFreeCursor(p, pC); - p->apCsr[i] = 0; - } + if( p->pFrame ){ + VdbeFrame *pFrame = p->pFrame; + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + sqlite3VdbeFrameRestore(pFrame); + } + p->pFrame = 0; + p->nFrame = 0; + + if( p->apCsr ){ + int i; + for(i=0; inCursor; i++){ + VdbeCursor *pC = p->apCsr[i]; + if( pC ){ + sqlite3VdbeFreeCursor(p, pC); + p->apCsr[i] = 0; + } + } + } + if( p->aMem ){ + releaseMemArray(&p->aMem[1], p->nMem); } } /* ** Clean up the VM after execution. @@ -1253,27 +1426,20 @@ ** This routine will automatically close any cursors, lists, and/or ** sorters that were left open. It also deletes the values of ** variables in the aVar[] array. */ static void Cleanup(Vdbe *p){ - int i; sqlite3 *db = p->db; - Mem *pMem; - closeAllCursors(p); - for(pMem=&p->aMem[1], i=1; i<=p->nMem; i++, pMem++){ - if( pMem->flags & MEM_RowSet ){ - sqlite3RowSetClear(pMem->u.pRowSet); - } - MemSetTypeFlag(pMem, MEM_Null); - } - releaseMemArray(&p->aMem[1], p->nMem); - if( p->contextStack ){ - sqlite3DbFree(db, p->contextStack); - } - p->contextStack = 0; - p->contextStackDepth = 0; - p->contextStackTop = 0; + +#ifdef SQLITE_DEBUG + /* Execute assert() statements to ensure that the Vdbe.apCsr[] and + ** Vdbe.aMem[] arrays have already been cleaned up. */ + int i; + for(i=0; inCursor; i++) assert( p->apCsr==0 || p->apCsr[i]==0 ); + for(i=1; i<=p->nMem; i++) assert( p->aMem==0 || p->aMem[i].flags==MEM_Null ); +#endif + sqlite3DbFree(db, p->zErrMsg); p->zErrMsg = 0; p->pResultSet = 0; } @@ -1994,11 +2160,10 @@ /* ** Delete an entire VDBE. */ void sqlite3VdbeDelete(Vdbe *p){ - int i; sqlite3 *db; if( NEVER(p==0) ) return; db = p->db; if( p->pPrev ){ @@ -2008,26 +2173,17 @@ db->pVdbe = p->pNext; } if( p->pNext ){ p->pNext->pPrev = p->pPrev; } - if( p->aOp ){ - Op *pOp = p->aOp; - for(i=0; inOp; i++, pOp++){ - freeP4(db, pOp->p4type, pOp->p4.p); -#ifdef SQLITE_DEBUG - sqlite3DbFree(db, pOp->zComment); -#endif - } - } releaseMemArray(p->aVar, p->nVar); - sqlite3DbFree(db, p->aLabel); releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + vdbeFreeOpArray(db, p->aOp, p->nOp); + sqlite3DbFree(db, p->aLabel); sqlite3DbFree(db, p->aColName); sqlite3DbFree(db, p->zSql); p->magic = VDBE_MAGIC_DEAD; - sqlite3DbFree(db, p->aOp); sqlite3DbFree(db, p->pFree); sqlite3DbFree(db, p); } /* Index: src/vdbeblob.c ================================================================== --- src/vdbeblob.c +++ src/vdbeblob.c @@ -202,11 +202,11 @@ ** and offset cache without causing any IO. */ sqlite3VdbeChangeP4(v, 3+flags, SQLITE_INT_TO_PTR(pTab->nCol+1),P4_INT32); sqlite3VdbeChangeP2(v, 7, pTab->nCol); if( !db->mallocFailed ){ - sqlite3VdbeMakeReady(v, 1, 1, 1, 0); + sqlite3VdbeMakeReady(v, 1, 1, 1, 0, 0); } } sqlite3BtreeLeaveAll(db); rc = sqlite3SafetyOff(db); Index: src/vdbemem.c ================================================================== --- src/vdbemem.c +++ src/vdbemem.c @@ -268,11 +268,11 @@ ** invoking an external callback, free it now. Calling this function ** does not free any Mem.zMalloc buffer. */ void sqlite3VdbeMemReleaseExternal(Mem *p){ assert( p->db==0 || sqlite3_mutex_held(p->db->mutex) ); - if( p->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet) ){ + if( p->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet|MEM_Frame) ){ if( p->flags&MEM_Agg ){ sqlite3VdbeMemFinalize(p, p->u.pDef); assert( (p->flags & MEM_Agg)==0 ); sqlite3VdbeMemRelease(p); }else if( p->flags&MEM_Dyn && p->xDel ){ @@ -279,10 +279,13 @@ assert( (p->flags&MEM_RowSet)==0 ); p->xDel((void *)p->z); p->xDel = 0; }else if( p->flags&MEM_RowSet ){ sqlite3RowSetClear(p->u.pRowSet); + }else if( p->flags&MEM_Frame ){ + sqlite3VdbeFrameDelete(p->u.pFrame); + p->flags &= ~MEM_Frame; } } } /* @@ -480,10 +483,13 @@ /* ** Delete any previous value and set the value stored in *pMem to NULL. */ void sqlite3VdbeMemSetNull(Mem *pMem){ + if( pMem->flags & MEM_Frame ){ + sqlite3VdbeFrameDelete(pMem->u.pFrame); + } if( pMem->flags & MEM_RowSet ){ sqlite3RowSetClear(pMem->u.pRowSet); } MemSetTypeFlag(pMem, MEM_Null); pMem->type = SQLITE_NULL; Index: src/vtab.c ================================================================== --- src/vtab.c +++ src/vtab.c @@ -939,23 +939,24 @@ ** array so that an OP_VBegin will get generated for it. Add pTab to the ** array if it is missing. If pTab is already in the array, this routine ** is a no-op. */ void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){ + Parse *pToplevel = sqlite3ParseToplevel(pParse); int i, n; Table **apVtabLock; assert( IsVirtual(pTab) ); - for(i=0; inVtabLock; i++){ - if( pTab==pParse->apVtabLock[i] ) return; + for(i=0; inVtabLock; i++){ + if( pTab==pToplevel->apVtabLock[i] ) return; } - n = (pParse->nVtabLock+1)*sizeof(pParse->apVtabLock[0]); - apVtabLock = sqlite3_realloc(pParse->apVtabLock, n); + n = (pToplevel->nVtabLock+1)*sizeof(pToplevel->apVtabLock[0]); + apVtabLock = sqlite3_realloc(pToplevel->apVtabLock, n); if( apVtabLock ){ - pParse->apVtabLock = apVtabLock; - pParse->apVtabLock[pParse->nVtabLock++] = pTab; + pToplevel->apVtabLock = apVtabLock; + pToplevel->apVtabLock[pToplevel->nVtabLock++] = pTab; }else{ - pParse->db->mallocFailed = 1; + pToplevel->db->mallocFailed = 1; } } #endif /* SQLITE_OMIT_VIRTUALTABLE */ Index: test/attach3.test ================================================================== --- test/attach3.test +++ test/attach3.test @@ -21,10 +21,15 @@ ifcapable !attach { 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 disable_recursive_triggers = 1 } + # Create tables t1 and t2 in the main database execsql { CREATE TABLE t1(a, b); CREATE TABLE t2(c, d); } @@ -315,10 +320,11 @@ ATTACH DATABASE '' AS NULL } db_list } {main temp {}} do_test attach3-12.10 { +breakpoint execsql { DETACH ? } db_list } {main temp} Index: test/auth.test ================================================================== --- test/auth.test +++ test/auth.test @@ -2271,17 +2271,17 @@ DELETE FROM v1 WHERE x=117 } set authargs } [list \ SQLITE_DELETE v1 {} main {} \ - SQLITE_INSERT v1chng {} main r3 \ - SQLITE_READ v1 x main r3 \ SQLITE_SELECT {} {} {} v1 \ SQLITE_READ t2 a main v1 \ SQLITE_READ t2 b main v1 \ SQLITE_SELECT {} {} {} {} \ SQLITE_READ v1 x main v1 \ + SQLITE_INSERT v1chng {} main r3 \ + SQLITE_READ v1 x main r3 \ ] } ;# ifcapable view && trigger # Ticket #1338: Make sure authentication works in the presence of an AS Index: test/autoinc.test ================================================================== --- test/autoinc.test +++ test/autoinc.test @@ -23,10 +23,12 @@ ifcapable {!autoinc} { finish_test return } +sqlite3_db_config_lookaside db 0 0 0 + # The database is initially empty. # do_test autoinc-1.1 { execsql { SELECT name FROM sqlite_master WHERE type='table'; @@ -553,10 +555,12 @@ INSERT INTO t3 SELECT * FROM t2 WHERE y>1; SELECT * FROM sqlite_sequence WHERE name='t3'; } } {t3 0} + +catchsql { pragma disable_recursive_triggers = 1 } # Ticket #3928. Make sure that triggers to not make extra slots in # the SQLITE_SEQUENCE table. # do_test autoinc-3928.1 { Index: test/misc2.test ================================================================== --- test/misc2.test +++ test/misc2.test @@ -16,10 +16,15 @@ # $Id: misc2.test,v 1.28 2007/09/12 17:01:45 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl +# 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 disable_recursive_triggers = 1 } + ifcapable {trigger} { # Test for ticket #360 # do_test misc2-1.1 { catchsql { @@ -356,10 +361,11 @@ } db close file delete -force test.db sqlite3 db test.db +catchsql { pragma disable_recursive_triggers = 1 } # Ticket #453. If the SQL ended with "-", the tokenizer was calling that # an incomplete token, which caused problem. The solution was to just call # it a minus sign. # Index: test/tkt3731.test ================================================================== --- test/tkt3731.test +++ test/tkt3731.test @@ -15,10 +15,15 @@ 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 disable_recursive_triggers = 1 } do_test tkt3731-1.1 { execsql { CREATE TABLE t1(a PRIMARY KEY, b); CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN Index: test/tkt3992.test ================================================================== --- test/tkt3992.test +++ test/tkt3992.test @@ -70,11 +70,8 @@ UPDATE t2 SET a = 'I'; } set res } {real} -explain { - UPDATE t2 SET a = 'I'; -} finish_test Index: test/trigger1.test ================================================================== --- test/trigger1.test +++ test/trigger1.test @@ -61,101 +61,101 @@ SELECT * FROM sqlite_master; END; } } {1 {near "STATEMENT": syntax error}} execsql { - CREATE TRIGGER tr1 INSERT ON t1 BEGIN - INSERT INTO t1 values(1); - END; + CREATE TRIGGER tr1 INSERT ON t1 BEGIN + INSERT INTO t1 values(1); + END; } do_test trigger1-1.2.0 { catchsql { - CREATE TRIGGER IF NOT EXISTS tr1 DELETE ON t1 BEGIN - SELECT * FROM sqlite_master; - END + CREATE TRIGGER IF NOT EXISTS tr1 DELETE ON t1 BEGIN + SELECT * FROM sqlite_master; + END } } {0 {}} do_test trigger1-1.2.1 { catchsql { - CREATE TRIGGER tr1 DELETE ON t1 BEGIN - SELECT * FROM sqlite_master; - END + CREATE TRIGGER tr1 DELETE ON t1 BEGIN + SELECT * FROM sqlite_master; + END } } {1 {trigger tr1 already exists}} do_test trigger1-1.2.2 { catchsql { - CREATE TRIGGER "tr1" DELETE ON t1 BEGIN - SELECT * FROM sqlite_master; - END + CREATE TRIGGER "tr1" DELETE ON t1 BEGIN + SELECT * FROM sqlite_master; + END } } {1 {trigger "tr1" already exists}} do_test trigger1-1.2.3 { catchsql { - CREATE TRIGGER [tr1] DELETE ON t1 BEGIN - SELECT * FROM sqlite_master; - END + CREATE TRIGGER [tr1] DELETE ON t1 BEGIN + SELECT * FROM sqlite_master; + END } } {1 {trigger [tr1] already exists}} do_test trigger1-1.3 { catchsql { - BEGIN; - CREATE TRIGGER tr2 INSERT ON t1 BEGIN - SELECT * from sqlite_master; END; + BEGIN; + CREATE TRIGGER tr2 INSERT ON t1 BEGIN + SELECT * from sqlite_master; END; ROLLBACK; - CREATE TRIGGER tr2 INSERT ON t1 BEGIN - SELECT * from sqlite_master; END; + CREATE TRIGGER tr2 INSERT ON t1 BEGIN + SELECT * from sqlite_master; END; } } {0 {}} do_test trigger1-1.4 { catchsql { - DROP TRIGGER IF EXISTS tr1; - CREATE TRIGGER tr1 DELETE ON t1 BEGIN - SELECT * FROM sqlite_master; - END + DROP TRIGGER IF EXISTS tr1; + CREATE TRIGGER tr1 DELETE ON t1 BEGIN + SELECT * FROM sqlite_master; + END } } {0 {}} do_test trigger1-1.5 { execsql { - BEGIN; - DROP TRIGGER tr2; - ROLLBACK; - DROP TRIGGER tr2; + BEGIN; + DROP TRIGGER tr2; + ROLLBACK; + DROP TRIGGER tr2; } } {} do_test trigger1-1.6.1 { catchsql { - DROP TRIGGER IF EXISTS biggles; + DROP TRIGGER IF EXISTS biggles; } } {0 {}} do_test trigger1-1.6.2 { catchsql { - DROP TRIGGER biggles; + DROP TRIGGER biggles; } } {1 {no such trigger: biggles}} do_test trigger1-1.7 { catchsql { - DROP TABLE t1; - DROP TRIGGER tr1; + DROP TABLE t1; + DROP TRIGGER tr1; } } {1 {no such trigger: tr1}} ifcapable tempdb { execsql { CREATE TEMP TABLE temp_table(a); } do_test trigger1-1.8 { execsql { - CREATE TRIGGER temp_trig UPDATE ON temp_table BEGIN - SELECT * from sqlite_master; - END; - SELECT count(*) FROM sqlite_master WHERE name = 'temp_trig'; + CREATE TRIGGER temp_trig UPDATE ON temp_table BEGIN + SELECT * from sqlite_master; + END; + SELECT count(*) FROM sqlite_master WHERE name = 'temp_trig'; } } {0} } do_test trigger1-1.9 { @@ -401,18 +401,18 @@ execsql {SELECT type, name FROM sqlite_master} } [concat $view_v1 {table t2}] do_test trigger1-6.2 { execsql { CREATE TRIGGER t2 BEFORE DELETE ON t2 BEGIN - SELECT RAISE(ABORT,'deletes are not allows'); + SELECT RAISE(ABORT,'deletes are not permitted'); END; SELECT type, name FROM sqlite_master; } } [concat $view_v1 {table t2 trigger t2}] do_test trigger1-6.3 { catchsql {DELETE FROM t2} -} {1 {deletes are not allows}} +} {1 {deletes are not permitted}} do_test trigger1-6.4 { execsql {SELECT * FROM t2} } {3 4 7 8} do_test trigger1-6.5 { db close Index: test/trigger2.test ================================================================== --- test/trigger2.test +++ test/trigger2.test @@ -51,10 +51,15 @@ 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 disable_recursive_triggers = 1 } # 1. ifcapable subquery { set ii 0 set tbl_definitions [list \ Index: test/trigger3.test ================================================================== --- test/trigger3.test +++ test/trigger3.test @@ -15,15 +15,19 @@ 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 disable_recursive_triggers = 1 } # Test that we can cause ROLLBACK, FAIL and ABORT correctly -# catchsql { DROP TABLE tbl; } -catchsql { CREATE TABLE tbl (a, b, c) } - +# +catchsql { CREATE TABLE tbl(a, b ,c) } execsql { CREATE TRIGGER before_tbl_insert BEFORE INSERT ON tbl BEGIN SELECT CASE WHEN (new.a = 4) THEN RAISE(IGNORE) END; END; @@ -52,11 +56,11 @@ } {} # FAIL do_test trigger3-2.1 { catchsql { - BEGIN; + BEGIN; INSERT INTO tbl VALUES (5, 5, 6); INSERT INTO tbl VALUES (2, 5, 6); } } {1 {Trigger fail}} do_test trigger3-2.2 { Index: test/trigger8.test ================================================================== --- test/trigger8.test +++ test/trigger8.test @@ -30,10 +30,11 @@ set nStatement 10000 if {$tcl_platform(platform) == "symbian"} { set nStatement 1000 } +set nStatement 5 do_test trigger8-1.1 { execsql { CREATE TABLE t1(x); CREATE TABLE t2(y); } Index: test/trigger9.test ================================================================== --- test/trigger9.test +++ test/trigger9.test @@ -73,11 +73,11 @@ SELECT * FROM t2; } } {1 2 3} do_test trigger9-1.3.2 { has_rowdata {DELETE FROM t1} -} 1 +} 0 do_test trigger9-1.3.3 { execsql { ROLLBACK } } {} do_test trigger9-1.4.1 { execsql { BEGIN; @@ -88,11 +88,11 @@ SELECT * FROM t2; } } {1} do_test trigger9-1.4.2 { has_rowdata {DELETE FROM t1} -} 1 +} 0 do_test trigger9-1.4.3 { execsql { ROLLBACK } } {} do_test trigger9-1.5.1 { execsql { BEGIN; @@ -118,11 +118,11 @@ SELECT * FROM t2; } } {1 2 3} do_test trigger9-1.6.2 { has_rowdata {UPDATE t1 SET y = ''} -} 1 +} 0 do_test trigger9-1.6.3 { execsql { ROLLBACK } } {} do_test trigger9-1.7.1 { execsql { BEGIN; @@ -133,11 +133,11 @@ SELECT * FROM t2; } } {2 3} do_test trigger9-1.7.2 { has_rowdata {UPDATE t1 SET y = ''} -} 1 +} 0 do_test trigger9-1.7.3 { execsql { ROLLBACK } } {} do_test trigger9-3.1 { execsql { CREATE TABLE t3(a, b); ADDED test/triggerC.test Index: test/triggerC.test ================================================================== --- /dev/null +++ test/triggerC.test @@ -0,0 +1,460 @@ +# 2009 August 24 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +ifcapable {!trigger} { + finish_test + return +} + +# Enable recursive triggers for this file. +# +execsql { PRAGMA disable_recursive_triggers = 0 } + +#sqlite3_db_config_lookaside db 0 0 0 + +#------------------------------------------------------------------------- +# This block of tests, triggerC-1.*, are not aimed at any specific +# property of the triggers sub-system. They were created to debug +# specific problems while modifying SQLite to support recursive +# triggers. They are left here in case they can help debug the +# same problems again. +# +do_test triggerC-1.1 { + execsql { + CREATE TABLE t1(a, b, c); + CREATE TABLE log(t, a1, b1, c1, a2, b2, c2); + CREATE TRIGGER trig1 BEFORE INSERT ON t1 BEGIN + INSERT INTO log VALUES('before', NULL, NULL, NULL, new.a, new.b, new.c); + END; + CREATE TRIGGER trig2 AFTER INSERT ON t1 BEGIN + INSERT INTO log VALUES('after', NULL, NULL, NULL, new.a, new.b, new.c); + END; + CREATE TRIGGER trig3 BEFORE UPDATE ON t1 BEGIN + INSERT INTO log VALUES('before', old.a,old.b,old.c, new.a,new.b,new.c); + END; + CREATE TRIGGER trig4 AFTER UPDATE ON t1 BEGIN + INSERT INTO log VALUES('after', old.a,old.b,old.c, new.a,new.b,new.c); + END; + + CREATE TRIGGER trig5 BEFORE DELETE ON t1 BEGIN + INSERT INTO log VALUES('before', old.a,old.b,old.c, NULL,NULL,NULL); + END; + CREATE TRIGGER trig6 AFTER DELETE ON t1 BEGIN + INSERT INTO log VALUES('after', old.a,old.b,old.c, NULL,NULL,NULL); + END; + } +} {} +do_test triggerC-1.2 { + execsql { + INSERT INTO t1 VALUES('A', 'B', 'C'); + SELECT * FROM log; + } +} {before {} {} {} A B C after {} {} {} A B C} +do_test triggerC-1.3 { + execsql { SELECT * FROM t1 } +} {A B C} +do_test triggerC-1.4 { + execsql { + DELETE FROM log; + UPDATE t1 SET a = 'a'; + SELECT * FROM log; + } +} {before A B C a B C after A B C a B C} +do_test triggerC-1.5 { + execsql { SELECT * FROM t1 } +} {a B C} +do_test triggerC-1.6 { + execsql { + DELETE FROM log; + DELETE FROM t1; + SELECT * FROM log; + } +} {before a B C {} {} {} after a B C {} {} {}} +do_test triggerC-1.7 { + execsql { SELECT * FROM t1 } +} {} +do_test triggerC-1.8 { + execsql { + CREATE TABLE t4(a, b); + CREATE TRIGGER t4t AFTER DELETE ON t4 BEGIN + SELECT RAISE(ABORT, 'delete is not supported'); + END; + } +} {} +do_test triggerC-1.9 { + execsql { INSERT INTO t4 VALUES(1, 2) } + catchsql { DELETE FROM t4 } +} {1 {delete is not supported}} +do_test triggerC-1.10 { + execsql { SELECT * FROM t4 } +} {1 2} +do_test triggerC-1.11 { + execsql { + CREATE TABLE t5 (a primary key, b, c); + INSERT INTO t5 values (1, 2, 3); + CREATE TRIGGER au_tbl AFTER UPDATE ON t5 BEGIN + UPDATE OR IGNORE t5 SET a = new.a, c = 10; + END; + } +} {} +do_test triggerC-1.12 { + catchsql { UPDATE OR REPLACE t5 SET a = 4 WHERE a = 1 } +} {1 {too many levels of trigger recursion}} +do_test triggerC-1.13 { + execsql { + CREATE TABLE t6(a INTEGER PRIMARY KEY, b); + INSERT INTO t6 VALUES(1, 2); + create trigger r1 after update on t6 for each row begin + SELECT 1; + end; + UPDATE t6 SET a=a; + } +} {} +do_test triggerC-1.14 { + execsql { + DROP TABLE t1; + CREATE TABLE cnt(n); + INSERT INTO cnt VALUES(0); + CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE, c, d, e); + CREATE INDEX t1cd ON t1(c,d); + CREATE TRIGGER t1r1 AFTER UPDATE ON t1 BEGIN UPDATE cnt SET n=n+1; END; + INSERT INTO t1 VALUES(1,2,3,4,5); + INSERT INTO t1 VALUES(6,7,8,9,10); + 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}} + + +#------------------------------------------------------------------------- +# This block of tests, triggerC-2.*, tests that recursive trigger +# programs (triggers that fire themselves) work. More specifically, +# this block focuses on recursive INSERT triggers. +# +do_test triggerC-2.1.0 { + execsql { + CREATE TABLE t2(a PRIMARY KEY); + } +} {} + +foreach {n tdefn rc} { + 1 { + CREATE TRIGGER t2_trig AFTER INSERT ON t2 WHEN (new.a>0) BEGIN + INSERT INTO t2 VALUES(new.a - 1); + END; + } {0 {10 9 8 7 6 5 4 3 2 1 0}} + + 2 { + CREATE TRIGGER t2_trig AFTER INSERT ON t2 BEGIN + SELECT CASE WHEN new.a==2 THEN RAISE(IGNORE) ELSE NULL END; + INSERT INTO t2 VALUES(new.a - 1); + END; + } {0 {10 9 8 7 6 5 4 3 2}} + + 3 { + CREATE TRIGGER t2_trig BEFORE INSERT ON t2 WHEN (new.a>0) BEGIN + INSERT INTO t2 VALUES(new.a - 1); + END; + } {0 {0 1 2 3 4 5 6 7 8 9 10}} + + 4 { + CREATE TRIGGER t2_trig BEFORE INSERT ON t2 BEGIN + SELECT CASE WHEN new.a==2 THEN RAISE(IGNORE) ELSE NULL END; + INSERT INTO t2 VALUES(new.a - 1); + END; + } {0 {3 4 5 6 7 8 9 10}} + + 5 { + CREATE TRIGGER t2_trig BEFORE INSERT ON t2 BEGIN + INSERT INTO t2 VALUES(new.a - 1); + END; + } {1 {too many levels of trigger recursion}} + + 6 { + CREATE TRIGGER t2_trig AFTER INSERT ON t2 WHEN (new.a>0) BEGIN + INSERT OR IGNORE INTO t2 VALUES(new.a); + END; + } {0 10} + + 7 { + CREATE TRIGGER t2_trig BEFORE INSERT ON t2 WHEN (new.a>0) BEGIN + INSERT OR IGNORE INTO t2 VALUES(new.a); + END; + } {1 {too many levels of trigger recursion}} +} { + do_test triggerC-2.1.$n { + catchsql { DROP TRIGGER t2_trig } + execsql { DELETE FROM t2 } + execsql $tdefn + catchsql { + INSERT INTO t2 VALUES(10); + SELECT * FROM t2; + } + } $rc +} + +do_test triggerC-2.2 { + execsql { + CREATE TABLE t22(x); + + CREATE TRIGGER t22a AFTER INSERT ON t22 BEGIN + INSERT INTO t22 SELECT x + (SELECT max(x) FROM t22) FROM t22; + END; + CREATE TRIGGER t22b BEFORE INSERT ON t22 BEGIN + SELECT CASE WHEN (SELECT count(*) FROM t22) >= 100 + THEN RAISE(IGNORE) + ELSE NULL END; + END; + + INSERT INTO t22 VALUES(1); + SELECT count(*) FROM t22; + } +} {100} + +do_test triggerC-2.3 { + execsql { + CREATE TABLE t23(x PRIMARY KEY); + + CREATE TRIGGER t23a AFTER INSERT ON t23 BEGIN + INSERT INTO t23 VALUES(new.x + 1); + END; + + CREATE TRIGGER t23b BEFORE INSERT ON t23 BEGIN + SELECT CASE WHEN new.x>500 + THEN RAISE(IGNORE) + ELSE NULL END; + END; + + INSERT INTO t23 VALUES(1); + SELECT count(*) FROM t23; + } +} {500} + + +#----------------------------------------------------------------------- +# This block of tests, triggerC-3.*, test that SQLite throws an exception +# when it detects excessive recursion. +# +do_test triggerC-3.1.1 { + execsql { + CREATE TABLE t3(a, b); + CREATE TRIGGER t3i AFTER INSERT ON t3 BEGIN + DELETE FROM t3 WHERE rowid = new.rowid; + END; + CREATE TRIGGER t3d AFTER DELETE ON t3 BEGIN + INSERT INTO t3 VALUES(old.a, old.b); + END; + } +} {} +do_test triggerC-3.1.2 { + catchsql { INSERT INTO t3 VALUES(0,0) } +} {1 {too many levels of trigger recursion}} +do_test triggerC-3.1.3 { + execsql { SELECT * FROM t3 } +} {} + +#----------------------------------------------------------------------- +# This next block of tests, triggerC-4.*, checks that affinity +# transformations and constraint processing is performed at the correct +# times relative to BEFORE and AFTER triggers. +# +# For an INSERT statement, for each row to be inserted: +# +# 1. Apply affinities to non-rowid values to be inserted. +# 2. Fire BEFORE triggers. +# 3. Process constraints. +# 4. Insert new record. +# 5. Fire AFTER triggers. +# +# If the value of the rowid field is to be automatically assigned, it is +# set to -1 in the new.* record. Even if it is explicitly set to NULL +# by the INSERT statement. +# +# For an UPDATE statement, for each row to be deleted: +# +# 1. Apply affinities to non-rowid values to be inserted. +# 2. Fire BEFORE triggers. +# 3. Process constraints. +# 4. Insert new record. +# 5. Fire AFTER triggers. +# +# For a DELETE statement, for each row to be deleted: +# +# 1. Fire BEFORE triggers. +# 2. Remove database record. +# 3. Fire AFTER triggers. +# +# When a numeric value that as an exact integer representation is stored +# in a column with REAL affinity, it is actually stored as an integer. +# These tests check that the typeof() such values is always 'real', +# not 'integer'. +# +# triggerC-4.1.*: Check that affinity transformations are made before +# triggers are invoked. +# +do_test triggerC-4.1.1 { + catchsql { DROP TABLE log } + catchsql { DROP TABLE t4 } + execsql { + CREATE TABLE log(t); + CREATE TABLE t4(a TEXT,b INTEGER,c REAL); + CREATE TRIGGER t4bi BEFORE INSERT ON t4 BEGIN + INSERT INTO log VALUES(new.rowid || ' ' || typeof(new.rowid) || ' ' || + new.a || ' ' || typeof(new.a) || ' ' || + new.b || ' ' || typeof(new.b) || ' ' || + new.c || ' ' || typeof(new.c) + ); + END; + CREATE TRIGGER t4ai AFTER INSERT ON t4 BEGIN + INSERT INTO log VALUES(new.rowid || ' ' || typeof(new.rowid) || ' ' || + new.a || ' ' || typeof(new.a) || ' ' || + new.b || ' ' || typeof(new.b) || ' ' || + new.c || ' ' || typeof(new.c) + ); + END; + CREATE TRIGGER t4bd BEFORE DELETE ON t4 BEGIN + INSERT INTO log VALUES(old.rowid || ' ' || typeof(old.rowid) || ' ' || + old.a || ' ' || typeof(old.a) || ' ' || + old.b || ' ' || typeof(old.b) || ' ' || + old.c || ' ' || typeof(old.c) + ); + END; + CREATE TRIGGER t4ad AFTER DELETE ON t4 BEGIN + INSERT INTO log VALUES(old.rowid || ' ' || typeof(old.rowid) || ' ' || + old.a || ' ' || typeof(old.a) || ' ' || + old.b || ' ' || typeof(old.b) || ' ' || + old.c || ' ' || typeof(old.c) + ); + END; + CREATE TRIGGER t4bu BEFORE UPDATE ON t4 BEGIN + INSERT INTO log VALUES(old.rowid || ' ' || typeof(old.rowid) || ' ' || + old.a || ' ' || typeof(old.a) || ' ' || + old.b || ' ' || typeof(old.b) || ' ' || + old.c || ' ' || typeof(old.c) + ); + INSERT INTO log VALUES(new.rowid || ' ' || typeof(new.rowid) || ' ' || + new.a || ' ' || typeof(new.a) || ' ' || + new.b || ' ' || typeof(new.b) || ' ' || + new.c || ' ' || typeof(new.c) + ); + END; + CREATE TRIGGER t4au AFTER UPDATE ON t4 BEGIN + INSERT INTO log VALUES(old.rowid || ' ' || typeof(old.rowid) || ' ' || + old.a || ' ' || typeof(old.a) || ' ' || + old.b || ' ' || typeof(old.b) || ' ' || + old.c || ' ' || typeof(old.c) + ); + INSERT INTO log VALUES(new.rowid || ' ' || typeof(new.rowid) || ' ' || + new.a || ' ' || typeof(new.a) || ' ' || + new.b || ' ' || typeof(new.b) || ' ' || + new.c || ' ' || typeof(new.c) + ); + END; + } +} {} +foreach {n insert log} { + + 2 { + INSERT INTO t4 VALUES('1', '1', '1'); + DELETE FROM t4; + } { + -1 integer 1 text 1 integer 1.0 real + 1 integer 1 text 1 integer 1.0 real + 1 integer 1 text 1 integer 1.0 real + 1 integer 1 text 1 integer 1.0 real + } + + 3 { + INSERT INTO t4(rowid,a,b,c) VALUES(45, 45, 45, 45); + DELETE FROM t4; + } { + 45 integer 45 text 45 integer 45.0 real + 45 integer 45 text 45 integer 45.0 real + 45 integer 45 text 45 integer 45.0 real + 45 integer 45 text 45 integer 45.0 real + } + + 4 { + INSERT INTO t4(rowid,a,b,c) VALUES(-42.0, -42.0, -42.0, -42.0); + DELETE FROM t4; + } { + -42 integer -42.0 text -42 integer -42.0 real + -42 integer -42.0 text -42 integer -42.0 real + -42 integer -42.0 text -42 integer -42.0 real + -42 integer -42.0 text -42 integer -42.0 real + } + + 5 { + INSERT INTO t4(rowid,a,b,c) VALUES(NULL, -42.4, -42.4, -42.4); + DELETE FROM t4; + } { + -1 integer -42.4 text -42.4 real -42.4 real + 1 integer -42.4 text -42.4 real -42.4 real + 1 integer -42.4 text -42.4 real -42.4 real + 1 integer -42.4 text -42.4 real -42.4 real + } + + 6 { + INSERT INTO t4 VALUES(7, 7, 7); + UPDATE t4 SET a=8, b=8, c=8; + } { + -1 integer 7 text 7 integer 7.0 real + 1 integer 7 text 7 integer 7.0 real + 1 integer 7 text 7 integer 7.0 real + 1 integer 8 text 8 integer 8.0 real + 1 integer 7 text 7 integer 7.0 real + 1 integer 8 text 8 integer 8.0 real + } + + 7 { + UPDATE t4 SET rowid=2; + } { + 1 integer 8 text 8 integer 8.0 real + 2 integer 8 text 8 integer 8.0 real + 1 integer 8 text 8 integer 8.0 real + 2 integer 8 text 8 integer 8.0 real + } + + 8 { + UPDATE t4 SET a='9', b='9', c='9'; + } { + 2 integer 8 text 8 integer 8.0 real + 2 integer 9 text 9 integer 9.0 real + 2 integer 8 text 8 integer 8.0 real + 2 integer 9 text 9 integer 9.0 real + } + + 9 { + UPDATE t4 SET a='9.1', b='9.1', c='9.1'; + } { + 2 integer 9 text 9 integer 9.0 real + 2 integer 9.1 text 9.1 real 9.1 real + 2 integer 9 text 9 integer 9.0 real + 2 integer 9.1 text 9.1 real 9.1 real + } +} { + do_test triggerC-4.1.$n { + eval concat [execsql " + DELETE FROM log; + $insert ; + SELECT * FROM log; + "] + } [join $log " "] +} + +finish_test + + Index: tool/vdbe-compress.tcl ================================================================== --- tool/vdbe-compress.tcl +++ tool/vdbe-compress.tcl @@ -93,10 +93,11 @@ append afterUnion $line\n set vlist {} } elseif {[llength $vlist]>0} { append line " " foreach v $vlist { + regsub -all "(\[^a-zA-Z0-9>.\])${v}(\\W)" $line "\\1u.$sname.$v\\2" line regsub -all "(\[^a-zA-Z0-9>.\])${v}(\\W)" $line "\\1u.$sname.$v\\2" line } append afterUnion [string trimright $line]\n } elseif {$line=="" && [eof stdin]} { # no-op