/ Check-in [25560145]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix the OP_Once opcode so that it works correctly for recursive triggers. Ticket [06796225f59c057cd120f1].
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 2556014514f36808e6d18b25722eae0daeeb8fbb5d18af13a9698ea6c6db1679
User & Date: drh 2017-03-24 17:59:56
Context
2017-03-24
18:38
Previous check-in was not correct. This is a better fix for the OP_Once problem of ticket [06796225f59c057cd120f1]. check-in: 8194dd28 user: drh tags: trunk
17:59
Fix the OP_Once opcode so that it works correctly for recursive triggers. Ticket [06796225f59c057cd120f1]. check-in: 25560145 user: drh tags: trunk
13:31
Add the RFC-7396 Appendix A test cases for json_patch(). check-in: c5441d2d user: drh tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/vdbe.c.

  2328   2328       pOut->u.i = ~sqlite3VdbeIntValue(pIn1);
  2329   2329     }
  2330   2330     break;
  2331   2331   }
  2332   2332   
  2333   2333   /* Opcode: Once P1 P2 * * *
  2334   2334   **
  2335         -** If the P1 value is equal to the P1 value on the OP_Init opcode at
  2336         -** instruction 0, then jump to P2.  If the two P1 values differ, then
  2337         -** set the P1 value on this opcode to equal the P1 value on the OP_Init
  2338         -** and fall through.
         2335  +** Fall through to the next instruction the first time this opcode is
         2336  +** encountered on each invocation of the byte-code program.  Jump to P2
         2337  +** on the second and all subsequent encounters during the same invocation.
         2338  +**
         2339  +** Top-level programs determine first invocation by comparing the P1
         2340  +** operand against the P1 operand on the OP_Init opcode at the beginning
         2341  +** of the program.  If the P1 values differ, then fall through and make
         2342  +** the P1 of this opcode equal to the P1 of OP_Init.  If P1 values are
         2343  +** the same then take the jump.
         2344  +**
         2345  +** For subprograms, there is a bitmask in the VdbeFrame that determines
         2346  +** whether or not the jump should be taken.  The bitmask is necessary
         2347  +** because the self-altering code trick does not work for recursive
         2348  +** triggers.
  2339   2349   */
  2340   2350   case OP_Once: {             /* jump */
         2351  +  u32 iAddr;                /* Address of this instruction */
  2341   2352     assert( p->aOp[0].opcode==OP_Init );
  2342         -  VdbeBranchTaken(p->aOp[0].p1==pOp->p1, 2);
  2343         -  if( p->aOp[0].p1==pOp->p1 ){
  2344         -    goto jump_to_p2;
         2353  +  if( p->pFrame ){
         2354  +    iAddr = (int)(pOp - p->aOp);
         2355  +    if( (p->pFrame->aOnce[iAddr/8] & (1<<(iAddr & 7)))!=0 ){
         2356  +      VdbeBranchTaken(1, 2);
         2357  +      p->pFrame->aOnce[iAddr/8] |= 1<<(iAddr & 7);
         2358  +      goto jump_to_p2;
         2359  +    }
  2345   2360     }else{
  2346         -    pOp->p1 = p->aOp[0].p1;
         2361  +    if( p->aOp[0].p1==pOp->p1 ){
         2362  +      VdbeBranchTaken(1, 2);
         2363  +      goto jump_to_p2;
         2364  +    }
  2347   2365     }
         2366  +  VdbeBranchTaken(0, 2);
         2367  +  pOp->p1 = p->aOp[0].p1;
  2348   2368     break;
  2349   2369   }
  2350   2370   
  2351   2371   /* Opcode: If P1 P2 P3 * *
  2352   2372   **
  2353   2373   ** Jump to P2 if the value in register P1 is true.  The value
  2354   2374   ** is considered true if it is numeric and non-zero.  If the value
................................................................................
  5866   5886       ** variable nMem (and later, VdbeFrame.nChildMem) to this value.
  5867   5887       */
  5868   5888       nMem = pProgram->nMem + pProgram->nCsr;
  5869   5889       assert( nMem>0 );
  5870   5890       if( pProgram->nCsr==0 ) nMem++;
  5871   5891       nByte = ROUND8(sizeof(VdbeFrame))
  5872   5892                 + nMem * sizeof(Mem)
  5873         -              + pProgram->nCsr * sizeof(VdbeCursor *);
         5893  +              + pProgram->nCsr * sizeof(VdbeCursor*)
         5894  +              + (pProgram->nOp + 7)/8;
  5874   5895       pFrame = sqlite3DbMallocZero(db, nByte);
  5875   5896       if( !pFrame ){
  5876   5897         goto no_mem;
  5877   5898       }
  5878   5899       sqlite3VdbeMemRelease(pRt);
  5879   5900       pRt->flags = MEM_Frame;
  5880   5901       pRt->u.pFrame = pFrame;
................................................................................
  5917   5938     p->pAuxData = 0;
  5918   5939     p->nChange = 0;
  5919   5940     p->pFrame = pFrame;
  5920   5941     p->aMem = aMem = VdbeFrameMem(pFrame);
  5921   5942     p->nMem = pFrame->nChildMem;
  5922   5943     p->nCursor = (u16)pFrame->nChildCsr;
  5923   5944     p->apCsr = (VdbeCursor **)&aMem[p->nMem];
         5945  +  pFrame->aOnce = (u8*)&p->apCsr[pProgram->nCsr];
  5924   5946     p->aOp = aOp = pProgram->aOp;
  5925   5947     p->nOp = pProgram->nOp;
  5926   5948   #ifdef SQLITE_ENABLE_STMT_SCANSTATUS
  5927   5949     p->anExec = 0;
  5928   5950   #endif
  5929   5951     pOp = &aOp[-1];
  5930   5952   

Changes to src/vdbe.h.

    83     83   ** A sub-routine used to implement a trigger program.
    84     84   */
    85     85   struct SubProgram {
    86     86     VdbeOp *aOp;                  /* Array of opcodes for sub-program */
    87     87     int nOp;                      /* Elements in aOp[] */
    88     88     int nMem;                     /* Number of memory cells required */
    89     89     int nCsr;                     /* Number of cursors required */
           90  +  u8 *aOnce;                    /* Array of OP_Once flags */
    90     91     void *token;                  /* id that may be used to recursive triggers */
    91     92     SubProgram *pNext;            /* Next sub-program already visited */
    92     93   };
    93     94   
    94     95   /*
    95     96   ** A smaller version of VdbeOp used for the VdbeAddOpList() function because
    96     97   ** it takes up less space.

Changes to src/vdbeInt.h.

   160    160   struct VdbeFrame {
   161    161     Vdbe *v;                /* VM this frame belongs to */
   162    162     VdbeFrame *pParent;     /* Parent of this frame, or NULL if parent is main */
   163    163     Op *aOp;                /* Program instructions for parent frame */
   164    164     i64 *anExec;            /* Event counters from parent frame */
   165    165     Mem *aMem;              /* Array of memory cells for parent frame */
   166    166     VdbeCursor **apCsr;     /* Array of Vdbe cursors for parent frame */
          167  +  u8 *aOnce;              /* Bitmask used by OP_Once */
   167    168     void *token;            /* Copy of SubProgram.token */
   168    169     i64 lastRowid;          /* Last insert rowid (sqlite3.lastRowid) */
   169    170     AuxData *pAuxData;      /* Linked list of auxdata allocations */
   170    171     int nCursor;            /* Number of entries in apCsr */
   171    172     int pc;                 /* Program Counter in parent (calling) frame */
   172    173     int nOp;                /* Size of aOp array */
   173    174     int nMem;               /* Number of entries in aMem */

Added test/triggerG.test.

            1  +# 2017-03-24
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice', here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +#
           12  +
           13  +set testdir [file dirname $argv0]
           14  +source $testdir/tester.tcl
           15  +set testprefix triggerG
           16  +ifcapable {!trigger} {
           17  +  finish_test
           18  +  return
           19  +}
           20  +
           21  +# Test cases for ticket 
           22  +# https://www.sqlite.org/src/tktview/06796225f59c057cd120f
           23  +#
           24  +# The OP_Once opcode was not working correctly for recursive triggers.
           25  +#
           26  +do_execsql_test 100 {
           27  +  PRAGMA recursive_triggers = 1;
           28  +  
           29  +  CREATE TABLE t1(a);
           30  +  CREATE INDEX i1 ON t1(a);
           31  +  INSERT INTO t1(a) VALUES(0),(2),(3),(8),(9);
           32  +  CREATE TABLE t2(b);
           33  +  CREATE TABLE t3(c);
           34  +  
           35  +  CREATE TRIGGER tr AFTER INSERT ON t3 BEGIN
           36  +    INSERT INTO t3 SELECT new.c+1 WHERE new.c<5;
           37  +    INSERT INTO t2 SELECT new.c*100+a FROM t1 WHERE a IN (1, 2, 3, 4);
           38  +  END;
           39  +  
           40  +  INSERT INTO t3 VALUES(2);
           41  +  SELECT c FROM t3 ORDER BY c;;
           42  +} {2 3 4 5}
           43  +do_execsql_test 110 {
           44  +  SELECT b FROM t2 ORDER BY b;
           45  +} {202 203 302 303 402 403 502 503}
           46  +
           47  +finish_test