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: |
c8bedef0d61731c29ae34de1594222d1 |
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
Changes to src/delete.c.
︙ | ︙ | |||
110 111 112 113 114 115 116 | /* ** 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; */ | | | > > | 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 | /* If pTab is really a view, make sure it has been initialized. */ if( sqlite3ViewGetColumnNames(pParse, pTab) ){ goto delete_from_cleanup; } | | | 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 | */ if( sqlite3ViewGetColumnNames(pParse, pTab) ){ goto insert_cleanup; } /* Cannot insert into a read-only table. */ | | | 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 | 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. */ | | | 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 | 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*); | | | 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 | pLimit = 0; } #endif if( sqlite3ViewGetColumnNames(pParse, pTab) ){ goto update_cleanup; } | | | 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 | ** 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. */ | > > > | > | 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 |