SQLite

Check-in [0a2b8e1b9d]
Login

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

Overview
Comment:Fix a problem with single-pass multi-row UPDATE statements that invoke REPLACE conflict handling.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | onepass-update
Files: files | file ages | folders
SHA1: 0a2b8e1b9dc600b5a93622e8eea6218649df5e0f
User & Date: dan 2017-01-11 19:03:08.308
Context
2017-01-11
20:10
Fix a problem causing the pre-update hook to be passed an incorrect rowid value in some single-pass multi-row updates. (Closed-Leaf check-in: 62257eb53c user: dan tags: onepass-update)
19:03
Fix a problem with single-pass multi-row UPDATE statements that invoke REPLACE conflict handling. (check-in: 0a2b8e1b9d user: dan tags: onepass-update)
15:42
Fix a problem preventing UPDATE statements that use a range-scan on the PK index of a WITHOUT ROWID table from using a one-pass strategy. (check-in: cab86c9094 user: dan tags: onepass-update)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/update.c.
131
132
133
134
135
136
137

138
139
140
141
142
143
144
  int newmask;           /* Mask of NEW.* columns accessed by BEFORE triggers */
  int iEph = 0;          /* Ephemeral table holding all primary key values */
  int nKey = 0;          /* Number of elements in regKey for WITHOUT ROWID */
  int aiCurOnePass[2];   /* The write cursors opened by WHERE_ONEPASS */
  int addrOpen;          /* Address of OP_OpenEphemeral */
  int iPk;               /* First of nPk cells holding PRIMARY KEY value */
  i16 nPk;               /* Number of components of the PRIMARY KEY */


  /* Register Allocations */
  int regRowCount = 0;   /* A count of rows changed */
  int regOldRowid = 0;   /* The old rowid */
  int regNewRowid = 0;   /* The new rowid */
  int regNew = 0;        /* Content of the NEW.* table in triggers */
  int regOld = 0;        /* Content of OLD.* table in triggers */







>







131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
  int newmask;           /* Mask of NEW.* columns accessed by BEFORE triggers */
  int iEph = 0;          /* Ephemeral table holding all primary key values */
  int nKey = 0;          /* Number of elements in regKey for WITHOUT ROWID */
  int aiCurOnePass[2];   /* The write cursors opened by WHERE_ONEPASS */
  int addrOpen;          /* Address of OP_OpenEphemeral */
  int iPk;               /* First of nPk cells holding PRIMARY KEY value */
  i16 nPk;               /* Number of components of the PRIMARY KEY */
  int bReplace = 0;      /* True if REPLACE conflict resolution might happen */

  /* Register Allocations */
  int regRowCount = 0;   /* A count of rows changed */
  int regOldRowid = 0;   /* The old rowid */
  int regNewRowid = 0;   /* The new rowid */
  int regNew = 0;        /* Content of the NEW.* table in triggers */
  int regOld = 0;        /* Content of OLD.* table in triggers */
290
291
292
293
294
295
296





297
298
299
300
301
302
303





304
305
306
307
308
309
310
    }else{
      reg = 0;
      for(i=0; i<pIdx->nKeyCol; i++){
        i16 iIdxCol = pIdx->aiColumn[i];
        if( iIdxCol<0 || aXRef[iIdxCol]>=0 ){
          reg = ++pParse->nMem;
          pParse->nMem += pIdx->nColumn;





          break;
        }
      }
    }
    if( reg==0 ) aToOpen[j+1] = 0;
    aRegIdx[j] = reg;
  }






  /* Begin generating code. */
  v = sqlite3GetVdbe(pParse);
  if( v==0 ) goto update_cleanup;
  if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
  sqlite3BeginWriteOperation(pParse, 1, iDb);








>
>
>
>
>







>
>
>
>
>







291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
    }else{
      reg = 0;
      for(i=0; i<pIdx->nKeyCol; i++){
        i16 iIdxCol = pIdx->aiColumn[i];
        if( iIdxCol<0 || aXRef[iIdxCol]>=0 ){
          reg = ++pParse->nMem;
          pParse->nMem += pIdx->nColumn;
          if( (onError==OE_Replace)
           || (onError==OE_Default && pIdx->onError==OE_Replace) 
          ){
            bReplace = 1;
          }
          break;
        }
      }
    }
    if( reg==0 ) aToOpen[j+1] = 0;
    aRegIdx[j] = reg;
  }
  if( bReplace ){
    /* If REPLACE conflict resolution might be invoked, open cursors on all 
    ** indexes in case they are needed to delete records.  */
    memset(aToOpen, 1, nIdx+1);
  }

  /* Begin generating code. */
  v = sqlite3GetVdbe(pParse);
  if( v==0 ) goto update_cleanup;
  if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
  sqlite3BeginWriteOperation(pParse, 1, iDb);

370
371
372
373
374
375
376
377






378
379
380
381
382
383
384
385
386
    iEph = pParse->nTab++;

    sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
    addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk);
    sqlite3VdbeSetP4KeyInfo(pParse, pPk);
  }

  /* Begin the database scan. */






  flags = WHERE_ONEPASS_DESIRED | WHERE_SEEK_TABLE;
  if( pParse->nested==0 && pTrigger==0 && hasFK==0 && chngKey==0 ){
    flags |= WHERE_ONEPASS_MULTIROW;
  }
  pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur);
  if( pWInfo==0 ) goto update_cleanup;

  /* A one-pass strategy that might update more than one row may not
  ** be used if any column of the index used for the scan is being







|
>
>
>
>
>
>

|







381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
    iEph = pParse->nTab++;

    sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
    addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk);
    sqlite3VdbeSetP4KeyInfo(pParse, pPk);
  }

  /* Begin the database scan. 
  **
  ** Do not consider a single-pass strategy for a multi-row update if
  ** there are any triggers or foreign keys to process, or rows may
  ** be deleted as a result of REPLACE conflict handling. Any of these
  ** things might disturb a cursor being used to scan through the table
  ** or index, causing a single-pass approach to malfunction.  */
  flags = WHERE_ONEPASS_DESIRED | WHERE_SEEK_TABLE;
  if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
    flags |= WHERE_ONEPASS_MULTIROW;
  }
  pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur);
  if( pWInfo==0 ) goto update_cleanup;

  /* A one-pass strategy that might update more than one row may not
  ** be used if any column of the index used for the scan is being
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473

  if( eOnePass!=ONEPASS_MULTI ){
    sqlite3WhereEnd(pWInfo);
  }

  labelBreak = sqlite3VdbeMakeLabel(v);
  if( !isView ){
    int iAddrOnce = 0;
    /* 
    ** Open every index that needs updating.  Note that if any
    ** index could potentially invoke a REPLACE conflict resolution 
    ** action, then we need to open all indices because we might need
    ** to be deleting some records.
    */
    if( onError==OE_Replace ){
      memset(aToOpen, 1, nIdx+1);
    }else{
      for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
        if( pIdx->onError==OE_Replace ){
          memset(aToOpen, 1, nIdx+1);
          break;
        }
      }
    }
    if( eOnePass!=ONEPASS_OFF ){
      if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0;
      if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0;
    }

    if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){
      iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
    }
    sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, aToOpen,
                               0, 0);
    if( iAddrOnce ) sqlite3VdbeJumpHere(v, iAddrOnce);
  }

  /* Top of the update loop */
  if( eOnePass!=ONEPASS_OFF ){
    if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){
      assert( pPk );
      sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey, nKey);







|
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<






|



|







449
450
451
452
453
454
455
456
457
458














459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476

  if( eOnePass!=ONEPASS_MULTI ){
    sqlite3WhereEnd(pWInfo);
  }

  labelBreak = sqlite3VdbeMakeLabel(v);
  if( !isView ){
    int addrOnce = 0;

    /* Open every index that needs updating. */














    if( eOnePass!=ONEPASS_OFF ){
      if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0;
      if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0;
    }

    if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){
      addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
    }
    sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, aToOpen,
                               0, 0);
    if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce);
  }

  /* Top of the update loop */
  if( eOnePass!=ONEPASS_OFF ){
    if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){
      assert( pPk );
      sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey, nKey);
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
        sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regNew+i);
      }
    }
  }

  if( !isView ){
    int addr1 = 0;        /* Address of jump instruction */
    int bReplace = 0;     /* True if REPLACE conflict resolution might happen */

    /* Do constraint checks. */
    assert( regOldRowid>0 );
    sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
        regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace,
        aXRef);








<







601
602
603
604
605
606
607

608
609
610
611
612
613
614
        sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regNew+i);
      }
    }
  }

  if( !isView ){
    int addr1 = 0;        /* Address of jump instruction */


    /* Do constraint checks. */
    assert( regOldRowid>0 );
    sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
        regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace,
        aXRef);

Changes to test/update2.test.
80
81
82
83
84
85
86

87



88


89
90
91

92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115



116







117













































118

  UPDATE t4 SET c=c+2 WHERE c>2;
  SELECT a, c FROM t4 ORDER BY a;
} {1 5 2 6}

#-------------------------------------------------------------------------
#
foreach {tn sql} {

  1 { CREATE TABLE b1(a INTEGER PRIMARY KEY, b, c) }



  2 { CREATE TABLE b1(a INT PRIMARY KEY, b, c) WITHOUT ROWID }


} {
  execsql { DROP TABLE IF EXISTS b1 }
  execsql $sql

  do_execsql_test 4.$tn.0 {
    CREATE UNIQUE INDEX b1c ON b1(c);

    INSERT INTO b1 VALUES(1, 'a', 1);
    INSERT INTO b1 VALUES(2, 'b', 15);
    INSERT INTO b1 VALUES(3, 'c', 3);
    INSERT INTO b1 VALUES(4, 'd', 4);
    INSERT INTO b1 VALUES(5, 'e', 5);
    INSERT INTO b1 VALUES(6, 'f', 6);
    INSERT INTO b1 VALUES(7, 'g', 7);
  }

  do_execsql_test 4.$tn.1 {
    UPDATE OR REPLACE b1 SET c=c+10 WHERE a BETWEEN 4 AND 7;
    SELECT * FROM b1 ORDER BY a;
  } {
    1 a 1
    3 c 3
    4 d 14
    5 e 15
    6 f 16
    7 g 17
  }
}

























































finish_test








>
|
>
>
>
|
>
>

|

>


<




















|
>
>
>

>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
  UPDATE t4 SET c=c+2 WHERE c>2;
  SELECT a, c FROM t4 ORDER BY a;
} {1 5 2 6}

#-------------------------------------------------------------------------
#
foreach {tn sql} {
  1 { 
    CREATE TABLE b1(a INTEGER PRIMARY KEY, b, c);
    CREATE TABLE c1(a INTEGER PRIMARY KEY, b, c, d)
  }
  2 { 
    CREATE TABLE b1(a INT PRIMARY KEY, b, c) WITHOUT ROWID;
    CREATE TABLE c1(a INT PRIMARY KEY, b, c, d) WITHOUT ROWID;
  }
} {
  execsql { DROP TABLE IF EXISTS b1; DROP TABLE IF EXISTS c1; }
  execsql $sql

  do_execsql_test 4.$tn.0 {
    CREATE UNIQUE INDEX b1c ON b1(c);

    INSERT INTO b1 VALUES(1, 'a', 1);
    INSERT INTO b1 VALUES(2, 'b', 15);
    INSERT INTO b1 VALUES(3, 'c', 3);
    INSERT INTO b1 VALUES(4, 'd', 4);
    INSERT INTO b1 VALUES(5, 'e', 5);
    INSERT INTO b1 VALUES(6, 'f', 6);
    INSERT INTO b1 VALUES(7, 'g', 7);
  }

  do_execsql_test 4.$tn.1 {
    UPDATE OR REPLACE b1 SET c=c+10 WHERE a BETWEEN 4 AND 7;
    SELECT * FROM b1 ORDER BY a;
  } {
    1 a 1
    3 c 3
    4 d 14
    5 e 15
    6 f 16
    7 g 17
  }

  do_execsql_test 4.$tn.2 {
    CREATE INDEX c1d ON c1(d, b);
    CREATE UNIQUE INDEX c1c ON c1(c, b);

    INSERT INTO c1 VALUES(1, 'a', 1,  1);
    INSERT INTO c1 VALUES(2, 'a', 15, 2);
    INSERT INTO c1 VALUES(3, 'a', 3,  3);
    INSERT INTO c1 VALUES(4, 'a', 4,  4);
    INSERT INTO c1 VALUES(5, 'a', 5,  5);
    INSERT INTO c1 VALUES(6, 'a', 6,  6);
    INSERT INTO c1 VALUES(7, 'a', 7,  7);
  }

  do_execsql_test 4.$tn.3 {
    UPDATE OR REPLACE c1 SET c=c+10 WHERE d BETWEEN 4 AND 7;
    SELECT * FROM c1 ORDER BY a;
  } {
    1 a 1 1
    3 a 3 3
    4 a 14 4
    5 a 15 5
    6 a 16 6
    7 a 17 7
  }

  do_execsql_test 4.$tn.4 { PRAGMA integrity_check } ok

  do_execsql_test 4.$tn.5 {
    DROP INDEX c1d;
    DROP INDEX c1c;
    DELETE FROM c1;

    INSERT INTO c1 VALUES(1, 'a', 1,  1);
    INSERT INTO c1 VALUES(2, 'a', 15, 2);
    INSERT INTO c1 VALUES(3, 'a', 3,  3);
    INSERT INTO c1 VALUES(4, 'a', 4,  4);
    INSERT INTO c1 VALUES(5, 'a', 5,  5);
    INSERT INTO c1 VALUES(6, 'a', 6,  6);
    INSERT INTO c1 VALUES(7, 'a', 7,  7);

    CREATE INDEX c1d ON c1(d);
    CREATE UNIQUE INDEX c1c ON c1(c);
  }

  do_execsql_test 4.$tn.6 {
    UPDATE OR REPLACE c1 SET c=c+10 WHERE d BETWEEN 4 AND 7;
    SELECT * FROM c1 ORDER BY a;
  } {
    1 a 1 1
    3 a 3 3
    4 a 14 4
    5 a 15 5
    6 a 16 6
    7 a 17 7
  }
}

finish_test