SQLite

Check-in [2c56b984]
Login

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

Overview
Comment:Do not use the one-pass optimization on an UPDATE if there is a subquery in the WHERE clause, since if the subquery is hidden behind a short-circuit operator, the subquery might not be evaluated until after one or more rows have been updated. Fix for the problem reported by forum post 0007d1fdb1. This is the same problem that was fixed by [73f0036f045bf371] only for UPDATE instead of DELETE.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 2c56b984a0bd3be5ec326a2109ea7b8f1d4ef63c8fc325caac9663cf2479eaff
User & Date: drh 2023-03-16 10:17:30
References
2023-08-01
00:34
Restore part of the UPDATE one-pass optimization that was removed by check-in [2c56b984a0bd3be5]: only disable one-pass if the WHERE clause contains a subquery. Allow subqueries in the SET expressions. Fix for performance problem reported by forum post 8ab195fd44e75ed0. (check-in: 42916af9 user: drh tags: trunk)
Context
2023-03-16
11:50
Update the tracing output for the query-invariant checker such that it shows the SQL that is run to verify that a found query-invariant discrepency is valid. Changes to testing logic only. (check-in: 8f45ad27 user: drh tags: trunk)
10:21
Do not use the one-pass optimization on an UPDATE if there is a subquery in the WHERE clause, since if the subquery is hidden behind a short-circuit operator, the subquery might not be evaluated until after one or more rows have been updated. (check-in: b5d8a9a6 user: drh tags: branch-3.41)
10:17
Do not use the one-pass optimization on an UPDATE if there is a subquery in the WHERE clause, since if the subquery is hidden behind a short-circuit operator, the subquery might not be evaluated until after one or more rows have been updated. Fix for the problem reported by forum post 0007d1fdb1. This is the same problem that was fixed by [73f0036f045bf371] only for UPDATE instead of DELETE. (check-in: 2c56b984 user: drh tags: trunk)
09:07
Remove a NEVER() from btreeNext() that dbsqlfuzz 460aa158f9a2c41145831cc924296cde1f312b3f found could sometimes be reached. I will find a way to test that branch later. (check-in: 1dffeffe user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/update.c.
723
724
725
726
727
728
729



730

731
732
733


734
735






736
737
738
739
740
741
742
      eOnePass = ONEPASS_SINGLE;
      sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL);
      bFinishSeek = 0;
    }else{
      /* 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;
      if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){






        flags |= WHERE_ONEPASS_MULTIROW;
      }
      pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere,0,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







>
>
>
|
>
|
<
<
>
>

|
>
>
>
>
>
>







723
724
725
726
727
728
729
730
731
732
733
734
735


736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
      eOnePass = ONEPASS_SINGLE;
      sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL);
      bFinishSeek = 0;
    }else{
      /* Begin the database scan. 
      **
      ** Do not consider a single-pass strategy for a multi-row update if
      ** there is anything that might disrupt the cursor being used to do
      ** the UPDATE:
      **   (1) This is a nested UPDATE
      **   (2) There are triggers
      **   (3) There are FOREIGN KEY constraints
      **   (4) There are REPLACE conflict handlers


      **   (5) There are subqueries in the WHERE clause
      */
      flags = WHERE_ONEPASS_DESIRED;
      if( !pParse->nested
       && !pTrigger
       && !hasFK
       && !chngKey
       && !bReplace
       && (sNC.ncFlags & NC_Subquery)==0
      ){
        flags |= WHERE_ONEPASS_MULTIROW;
      }
      pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere,0,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
Changes to test/corruptL.test.
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276



1277
1278
1279
1280
1281
1282
1283
|   4048: 37 20 31 0b 03 04 11 11 13 63 31 63 31 37 20 31   7 1......c1c17 1
|   4064: 0e 02 04 11 13 07 63 31 63 31 64 37 20 31 20 31   ......c1c1d7 1 1
|   4080: 0e 01 04 11 13 17 63 31 63 31 63 37 20 31 00 00   ......c1c1c7 1..
| end crash-3afa1ca9e9c1bd.db
}]} {}

extra_schema_checks 0
do_execsql_test 15.1 {
  PRAGMA cell_size_check = 0;
  UPDATE c1 SET c= NOT EXISTS(SELECT 1 FROM c1 ORDER BY (SELECT 1 FROM c1 ORDER BY a)) +10 WHERE d BETWEEN 4 AND 7;
} {}
extra_schema_checks 1




#-------------------------------------------------------------------------
reset_db
do_execsql_test 16.0 {
  CREATE TABLE t1(w, x, y, z, UNIQUE(w, x), UNIQUE(y, z));
  INSERT INTO t1 VALUES(1, 1, 1, 1);








|


|

>
>
>







1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
|   4048: 37 20 31 0b 03 04 11 11 13 63 31 63 31 37 20 31   7 1......c1c17 1
|   4064: 0e 02 04 11 13 07 63 31 63 31 64 37 20 31 20 31   ......c1c1d7 1 1
|   4080: 0e 01 04 11 13 17 63 31 63 31 63 37 20 31 00 00   ......c1c1c7 1..
| end crash-3afa1ca9e9c1bd.db
}]} {}

extra_schema_checks 0
do_catchsql_test 15.1 {
  PRAGMA cell_size_check = 0;
  UPDATE c1 SET c= NOT EXISTS(SELECT 1 FROM c1 ORDER BY (SELECT 1 FROM c1 ORDER BY a)) +10 WHERE d BETWEEN 4 AND 7;
} {1 {database disk image is malformed}}
extra_schema_checks 1
do_execsql_test 15.2 {
  PRAGMA integrity_check;
} {/in database main/}

#-------------------------------------------------------------------------
reset_db
do_execsql_test 16.0 {
  CREATE TABLE t1(w, x, y, z, UNIQUE(w, x), UNIQUE(y, z));
  INSERT INTO t1 VALUES(1, 1, 1, 1);

Changes to test/delete.test.
421
422
423
424
425
426
427



428
429
430
431
432
433
434
#
# When the WHERE clause of a DELETE statement contains a subquery
# which uses the table that is being deleted from and there is a
# short-circuit operator of some kind in the WHERE clause such that
# the subquery might not run right away, then the subquery might
# run after one or more rows have been deleted, which can change
# the result of the subquery, and result in the wrong answer.



#
reset_db
do_execsql_test delete-12.0 {
  CREATE TABLE t0(vkey INTEGER, pkey INTEGER,c1 INTEGER);
  INSERT INTO t0 VALUES(2,1,-20),(2,2,NULL),(2,3,0),(8,4,95);
  DELETE FROM t0 WHERE NOT (
    (t0.vkey <= t0.c1) AND







>
>
>







421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
#
# When the WHERE clause of a DELETE statement contains a subquery
# which uses the table that is being deleted from and there is a
# short-circuit operator of some kind in the WHERE clause such that
# the subquery might not run right away, then the subquery might
# run after one or more rows have been deleted, which can change
# the result of the subquery, and result in the wrong answer.
#
# Similar problem for UPDATE tested by update-21.4
# https://sqlite.org/forum/forumpost/0007d1fdb1
#
reset_db
do_execsql_test delete-12.0 {
  CREATE TABLE t0(vkey INTEGER, pkey INTEGER,c1 INTEGER);
  INSERT INTO t0 VALUES(2,1,-20),(2,2,NULL),(2,3,0),(8,4,95);
  DELETE FROM t0 WHERE NOT (
    (t0.vkey <= t0.c1) AND
Changes to test/update.test.
727
728
729
730
731
732
733

































734
735
} {ok}
do_catchsql_test update-20.20 {
  UPDATE t1 SET a=0;
} {1 {constraint failed}}
do_execsql_test update-20.30 {
  PRAGMA integrity_check;
} {ok}


































finish_test







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
} {ok}
do_catchsql_test update-20.20 {
  UPDATE t1 SET a=0;
} {1 {constraint failed}}
do_execsql_test update-20.30 {
  PRAGMA integrity_check;
} {ok}

# 2023-03-16 https://sqlite.org/forum/forumpost/0007d1fdb1
# A subquery in the WHERE clause of an UPDATE and behind a
# short-circuit evaluation caused problems because multi-row
# single-pass was selected.
#
# Similar problem for DELETE tested by delete-12.0.
# https://sqlite.org/src/info/73f0036f045bf371
#
reset_db
do_execsql_test update-21.1 {
  CREATE TABLE t1 (vkey INTEGER, c5 INTEGER);
  INSERT INTO t1 VALUES(3,NULL),(6,-54);
}
db null NULL
do_execsql_test update-21.2 {
  BEGIN;
  UPDATE t1 SET vkey = 100 WHERE c5 is null;
  SELECT * FROM t1 ORDER BY vkey, c5;
  ROLLBACK;
} {6 -54 100 NULL}
do_execsql_test update-21.3 {
  BEGIN;
  UPDATE t1 SET vkey = 100 WHERE NOT (-10*(select min(vkey) from t1) >= c5);
  SELECT * FROM t1 ORDER BY vkey, c5;
  ROLLBACK;
} {3 NULL 6 -54}
do_execsql_test update-21.4 {
  BEGIN;
  UPDATE t1 SET vkey = 100 WHERE c5 is null OR NOT (-10*(select min(vkey) from t1) >= c5);
  SELECT * FROM t1 ORDER BY vkey, c5;
  ROLLBACK;
} {6 -54 100 NULL}

finish_test