SQLite

Check-in [c8bedef0]
Login

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

Overview
Comment:Fix multiple problems with RETURNING on a DML statement against a view, all inspired by forum post dc3b92cfa0. (1) Do not allow a RETURNING clause to trick the code generator into thinking that the view being updated has an INSTEAD OF trigger. (2) Generate all result columns for a view in a DML statement. (3) The automatic covering index for a view should cover all result columns of the view.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: c8bedef0d61731c29ae34de1594222d15b578f9e2cddbbd5b74fb3059644fe0f
User & Date: drh 2023-03-28 11:18:04
Context
2023-03-28
16:02
Fix a weird corner case in aggregate function processing that results from the recent addition of support for index expressions on aggregate queries. Forum post bad532820c. (check-in: c34fd9fe user: drh tags: trunk)
11:21
Fix multiple problems with RETURNING on a DML statement against a view. (1) Do not allow a RETURNING clause to trick the code generator into thinking that the view being updated has an INSTEAD OF trigger. (2) Generate all result columns for a view in a DML statement. (3) The automatic covering index for a view should cover all result columns of the view. (check-in: b49816fc user: drh tags: branch-3.41)
11:18
Fix multiple problems with RETURNING on a DML statement against a view, all inspired by forum post dc3b92cfa0. (1) Do not allow a RETURNING clause to trick the code generator into thinking that the view being updated has an INSTEAD OF trigger. (2) Generate all result columns for a view in a DML statement. (3) The automatic covering index for a view should cover all result columns of the view. (check-in: c8bedef0 user: drh tags: trunk)
2023-03-27
13:57
Remove a meaningless JS test. Add a timer to the OPFS async-side worker loader in an attempt to catch a browser-specific quirk in which the worker loading silently fails, per discussion in/around [forum post a708c98dcb3ef|forum:a708c98dcb3ef]. (check-in: 4fc1904b user: stephan tags: trunk)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/delete.c.

110
111
112
113
114
115
116
117
118
119
120
121
122
123


124
125
126
127
128
129
130
/*
** Check to make sure the given table is writable. 
**
** If pTab is not writable  ->  generate an error message and return 1.
** If pTab is writable but other errors have occurred -> return 1.
** If pTab is writable and no prior errors -> return 0;
*/
int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){
  if( tabIsReadOnly(pParse, pTab) ){
    sqlite3ErrorMsg(pParse, "table %s may not be modified", pTab->zName);
    return 1;
  }
#ifndef SQLITE_OMIT_VIEW
  if( !viewOk && IsView(pTab) ){


    sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName);
    return 1;
  }
#endif
  return 0;
}








|





|
>
>







110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*
** Check to make sure the given table is writable. 
**
** If pTab is not writable  ->  generate an error message and return 1.
** If pTab is writable but other errors have occurred -> return 1.
** If pTab is writable and no prior errors -> return 0;
*/
int sqlite3IsReadOnly(Parse *pParse, Table *pTab, Trigger *pTrigger){
  if( tabIsReadOnly(pParse, pTab) ){
    sqlite3ErrorMsg(pParse, "table %s may not be modified", pTab->zName);
    return 1;
  }
#ifndef SQLITE_OMIT_VIEW
  if( IsView(pTab) 
   && (pTrigger==0 || (pTrigger->bReturning && pTrigger->pNext==0))
  ){
    sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName);
    return 1;
  }
#endif
  return 0;
}

370
371
372
373
374
375
376
377
378
379
380
381
382
383
384

  /* If pTab is really a view, make sure it has been initialized.
  */
  if( sqlite3ViewGetColumnNames(pParse, pTab) ){
    goto delete_from_cleanup;
  }

  if( sqlite3IsReadOnly(pParse, pTab, (pTrigger?1:0)) ){
    goto delete_from_cleanup;
  }
  iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
  assert( iDb<db->nDb );
  rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, 
                            db->aDb[iDb].zDbSName);
  assert( rcauth==SQLITE_OK || rcauth==SQLITE_DENY || rcauth==SQLITE_IGNORE );







|







372
373
374
375
376
377
378
379
380
381
382
383
384
385
386

  /* If pTab is really a view, make sure it has been initialized.
  */
  if( sqlite3ViewGetColumnNames(pParse, pTab) ){
    goto delete_from_cleanup;
  }

  if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){
    goto delete_from_cleanup;
  }
  iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
  assert( iDb<db->nDb );
  rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, 
                            db->aDb[iDb].zDbSName);
  assert( rcauth==SQLITE_OK || rcauth==SQLITE_DENY || rcauth==SQLITE_IGNORE );

Changes to src/insert.c.

792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
  */
  if( sqlite3ViewGetColumnNames(pParse, pTab) ){
    goto insert_cleanup;
  }

  /* Cannot insert into a read-only table.
  */
  if( sqlite3IsReadOnly(pParse, pTab, tmask) ){
    goto insert_cleanup;
  }

  /* Allocate a VDBE
  */
  v = sqlite3GetVdbe(pParse);
  if( v==0 ) goto insert_cleanup;







|







792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
  */
  if( sqlite3ViewGetColumnNames(pParse, pTab) ){
    goto insert_cleanup;
  }

  /* Cannot insert into a read-only table.
  */
  if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){
    goto insert_cleanup;
  }

  /* Allocate a VDBE
  */
  v = sqlite3GetVdbe(pParse);
  if( v==0 ) goto insert_cleanup;
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
      addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, regCols); VdbeCoverage(v);
      sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols);
      sqlite3VdbeJumpHere(v, addr1);
      sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); VdbeCoverage(v);
    }

    /* Copy the new data already generated. */
    assert( pTab->nNVCol>0 );
    sqlite3VdbeAddOp3(v, OP_Copy, regRowid+1, regCols+1, pTab->nNVCol-1);

#ifndef SQLITE_OMIT_GENERATED_COLUMNS
    /* Compute the new value for generated columns after all other
    ** columns have already been computed.  This must be done after
    ** computing the ROWID in case one of the generated columns
    ** refers to the ROWID. */







|







1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
      addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, regCols); VdbeCoverage(v);
      sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols);
      sqlite3VdbeJumpHere(v, addr1);
      sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); VdbeCoverage(v);
    }

    /* Copy the new data already generated. */
    assert( pTab->nNVCol>0 || pParse->nErr>0 );
    sqlite3VdbeAddOp3(v, OP_Copy, regRowid+1, regCols+1, pTab->nNVCol-1);

#ifndef SQLITE_OMIT_GENERATED_COLUMNS
    /* Compute the new value for generated columns after all other
    ** columns have already been computed.  This must be done after
    ** computing the ROWID in case one of the generated columns
    ** refers to the ROWID. */

Changes to src/sqliteInt.h.

4797
4798
4799
4800
4801
4802
4803
4804
4805
4806
4807
4808
4809
4810
4811
                          Expr*, int, int, u8);
void sqlite3DropIndex(Parse*, SrcList*, int);
int sqlite3Select(Parse*, Select*, SelectDest*);
Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*,
                         Expr*,ExprList*,u32,Expr*);
void sqlite3SelectDelete(sqlite3*, Select*);
Table *sqlite3SrcListLookup(Parse*, SrcList*);
int sqlite3IsReadOnly(Parse*, Table*, int);
void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int);
#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY)
Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,char*);
#endif
void sqlite3CodeChangeCount(Vdbe*,int,const char*);
void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*);
void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*,







|







4797
4798
4799
4800
4801
4802
4803
4804
4805
4806
4807
4808
4809
4810
4811
                          Expr*, int, int, u8);
void sqlite3DropIndex(Parse*, SrcList*, int);
int sqlite3Select(Parse*, Select*, SelectDest*);
Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*,
                         Expr*,ExprList*,u32,Expr*);
void sqlite3SelectDelete(sqlite3*, Select*);
Table *sqlite3SrcListLookup(Parse*, SrcList*);
int sqlite3IsReadOnly(Parse*, Table*, Trigger*);
void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int);
#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY)
Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,char*);
#endif
void sqlite3CodeChangeCount(Vdbe*,int,const char*);
void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*);
void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*,

Changes to src/trigger.c.

1449
1450
1451
1452
1453
1454
1455



1456
1457
1458
1459
1460
1461
1462
  int orconf           /* Default ON CONFLICT policy for trigger steps */
){
  const int op = pChanges ? TK_UPDATE : TK_DELETE;
  u32 mask = 0;
  Trigger *p;

  assert( isNew==1 || isNew==0 );



  for(p=pTrigger; p; p=p->pNext){
    if( p->op==op
     && (tr_tm&p->tr_tm)
     && checkColumnOverlap(p->pColumns,pChanges)
    ){
      if( p->bReturning ){
        mask = 0xffffffff;







>
>
>







1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
  int orconf           /* Default ON CONFLICT policy for trigger steps */
){
  const int op = pChanges ? TK_UPDATE : TK_DELETE;
  u32 mask = 0;
  Trigger *p;

  assert( isNew==1 || isNew==0 );
  if( IsView(pTab) ){
    return 0xffffffff;
  }
  for(p=pTrigger; p; p=p->pNext){
    if( p->op==op
     && (tr_tm&p->tr_tm)
     && checkColumnOverlap(p->pColumns,pChanges)
    ){
      if( p->bReturning ){
        mask = 0xffffffff;

Changes to src/update.c.

404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
    pLimit = 0;
  }
#endif

  if( sqlite3ViewGetColumnNames(pParse, pTab) ){
    goto update_cleanup;
  }
  if( sqlite3IsReadOnly(pParse, pTab, tmask) ){
    goto update_cleanup;
  }

  /* 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.







|







404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
    pLimit = 0;
  }
#endif

  if( sqlite3ViewGetColumnNames(pParse, pTab) ){
    goto update_cleanup;
  }
  if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){
    goto update_cleanup;
  }

  /* 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.

Changes to src/where.c.

959
960
961
962
963
964
965



966

967
968
969
970
971
972
973
  ** covering index.  A "covering index" is an index that contains all
  ** columns that are needed by the query.  With a covering index, the
  ** original table never needs to be accessed.  Automatic indices must
  ** be a covering index because the index will not be updated if the
  ** original table changes and the index and table cannot both be used
  ** if they go out of sync.
  */



  extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1));

  mxBitCol = MIN(BMS-1,pTable->nCol);
  testcase( pTable->nCol==BMS-1 );
  testcase( pTable->nCol==BMS-2 );
  for(i=0; i<mxBitCol; i++){
    if( extraCols & MASKBIT(i) ) nKeyCol++;
  }
  if( pSrc->colUsed & MASKBIT(BMS-1) ){







>
>
>
|
>







959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
  ** covering index.  A "covering index" is an index that contains all
  ** columns that are needed by the query.  With a covering index, the
  ** original table never needs to be accessed.  Automatic indices must
  ** be a covering index because the index will not be updated if the
  ** original table changes and the index and table cannot both be used
  ** if they go out of sync.
  */
  if( IsView(pTable) ){
    extraCols = ALLBITS;
  }else{
    extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1));
  }
  mxBitCol = MIN(BMS-1,pTable->nCol);
  testcase( pTable->nCol==BMS-1 );
  testcase( pTable->nCol==BMS-2 );
  for(i=0; i<mxBitCol; i++){
    if( extraCols & MASKBIT(i) ) nKeyCol++;
  }
  if( pSrc->colUsed & MASKBIT(BMS-1) ){

Changes to test/returning1.test.

410
411
412
413
414
415
416

417
418
419
420
421
422
423

# 2023-03-08 https://sqlite.org/forum/forumpost/f5a2b1db87
# NULL pointer dereference following an error.
#
do_execsql_test 18.0 {
  CREATE TABLE v0(c1 INT);
  CREATE VIEW view_2(c1) AS SELECT CASE WHEN c1 COLLATE TRUE THEN TRUE ELSE TRUE END FROM v0;

}
do_catchsql_test 18.1 {
  INSERT INTO view_2 DEFAULT VALUES RETURNING *;
} {1 {no such collation sequence: TRUE}}

# 2023-03-16 
# https://sqlite.org/forum/forumpost/c99d6e0329







>







410
411
412
413
414
415
416
417
418
419
420
421
422
423
424

# 2023-03-08 https://sqlite.org/forum/forumpost/f5a2b1db87
# NULL pointer dereference following an error.
#
do_execsql_test 18.0 {
  CREATE TABLE v0(c1 INT);
  CREATE VIEW view_2(c1) AS SELECT CASE WHEN c1 COLLATE TRUE THEN TRUE ELSE TRUE END FROM v0;
  CREATE TRIGGER x1 INSTEAD OF INSERT ON view_2 BEGIN SELECT true; END;
}
do_catchsql_test 18.1 {
  INSERT INTO view_2 DEFAULT VALUES RETURNING *;
} {1 {no such collation sequence: TRUE}}

# 2023-03-16 
# https://sqlite.org/forum/forumpost/c99d6e0329

Changes to test/window1.test.

2220
2221
2222
2223
2224
2225
2226



















































2227
2228
2229
}
do_execsql_test 72.1 {
  SELECT COUNT(*) FROM (
      SELECT TOTAL(0) OVER (PARTITION BY t0.c0) FROM t0
      )
  WHERE ('1' IS NOT ('abcde' NOTNULL)); 
} {1}





















































finish_test







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



2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
}
do_execsql_test 72.1 {
  SELECT COUNT(*) FROM (
      SELECT TOTAL(0) OVER (PARTITION BY t0.c0) FROM t0
      )
  WHERE ('1' IS NOT ('abcde' NOTNULL)); 
} {1}

# 2023-03-28 https://sqlite.org/forum/forumpost/dc3b92cfa0 (Song Liu)
#
reset_db
do_execsql_test 73.0 {
  CREATE TABLE t1(a INT);
  INSERT INTO t1(a) VALUES(1),(2),(4);
  CREATE VIEW t2(b,c) AS SELECT * FROM t1 JOIN t1 A ORDER BY sum(0) OVER(PARTITION BY 0);
  CREATE TRIGGER x1 INSTEAD OF UPDATE ON t2 BEGIN SELECT true; END;
}
do_execsql_test 73.1 {
  SELECT * FROM t2;
} {1 1 1 2 1 4 2 1 2 2 2 4 4 1 4 2 4 4}
do_execsql_test 73.2 {
  UPDATE t2 SET c=99 WHERE b=4 RETURNING *;
} {4 99 4 99 4 99}
do_execsql_test 73.3 {
  SELECT *, nth_value(15,2) OVER() FROM t2, t1 WHERE b=4;
} {
  4 1 1 15
  4 2 1 15
  4 4 1 15
  4 1 2 15
  4 2 2 15
  4 4 2 15
  4 1 4 15
  4 2 4 15
  4 4 4 15
}
do_execsql_test 73.4 {
  UPDATE t2 SET c=nth_value(15,2) OVER() FROM (SELECT * FROM t1) WHERE b=4 RETURNING *;
} {
  4 15
  4 15
  4 15
  4 15
  4 15
  4 15
  4 15
  4 15
  4 15
}
do_execsql_test 73.5 {
  DROP TRIGGER x1;
}
do_catchsql_test 73.6 {
  UPDATE t2 SET c=99 WHERE b=4 RETURNING *;
} {1 {cannot modify t2 because it is a view}}
do_catchsql_test 73.7 {
  UPDATE t2 SET c=nth_value(15,2) OVER() FROM (SELECT * FROM t1) WHERE b=4 RETURNING *;
} {1 {cannot modify t2 because it is a view}}


finish_test