Skip to content

Commit dc5f4b0

Browse files
author
D. Richard Hipp
committed
If a RETURNING clause contains a subquery that references the table that is
being updated in the statement that includes the RETURNING clause, then mark the subquery as correlated sot hat it is recomputed for each result and not just computed once and reused. See [forum:/forumpost/2c83569ce8945d39|forum post 2c83569ce8945d39].
1 parent c660e9c commit dc5f4b0

File tree

2 files changed

+127
-0
lines changed

2 files changed

+127
-0
lines changed

src/trigger.c

+67
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,72 @@ static ExprList *sqlite3ExpandReturning(
951951
return pNew;
952952
}
953953

954+
/* If the Expr node is a subquery or an EXISTS operator or an IN operator that
955+
** uses a subquery, and if the subquery is SF_Correlated, then mark the
956+
** expression as EP_VarSelect.
957+
*/
958+
static int sqlite3ReturningSubqueryVarSelect(Walker *NotUsed, Expr *pExpr){
959+
UNUSED_PARAMETER(NotUsed);
960+
if( ExprUseXSelect(pExpr)
961+
&& (pExpr->x.pSelect->selFlags & SF_Correlated)!=0
962+
){
963+
testcase( ExprHasProperty(pExpr, EP_VarSelect) );
964+
ExprSetProperty(pExpr, EP_VarSelect);
965+
}
966+
return WRC_Continue;
967+
}
968+
969+
970+
/*
971+
** If the SELECT references the table pWalker->u.pTab, then do two things:
972+
**
973+
** (1) Mark the SELECT as as SF_Correlated.
974+
** (2) Set pWalker->eCode to non-zero so that the caller will know
975+
** that (1) has happened.
976+
*/
977+
static int sqlite3ReturningSubqueryCorrelated(Walker *pWalker, Select *pSelect){
978+
int i;
979+
SrcList *pSrc;
980+
assert( pSelect!=0 );
981+
pSrc = pSelect->pSrc;
982+
assert( pSrc!=0 );
983+
for(i=0; i<pSrc->nSrc; i++){
984+
if( pSrc->a[i].pTab==pWalker->u.pTab ){
985+
testcase( pSelect->selFlags & SF_Correlated );
986+
pSelect->selFlags |= SF_Correlated;
987+
pWalker->eCode = 1;
988+
break;
989+
}
990+
}
991+
return WRC_Continue;
992+
}
993+
994+
/*
995+
** Scan the expression list that is the argument to RETURNING looking
996+
** for subqueries that depend on the table which is being modified in the
997+
** statement that is hosting the RETURNING clause (pTab). Mark all such
998+
** subqueries as SF_Correlated. If the subqueries are part of an
999+
** expression, mark the expression as EP_VarSelect.
1000+
**
1001+
** https://sqlite.org/forum/forumpost/2c83569ce8945d39
1002+
*/
1003+
static void sqlite3ProcessReturningSubqueries(
1004+
ExprList *pEList,
1005+
Table *pTab
1006+
){
1007+
Walker w;
1008+
memset(&w, 0, sizeof(w));
1009+
w.xExprCallback = sqlite3ExprWalkNoop;
1010+
w.xSelectCallback = sqlite3ReturningSubqueryCorrelated;
1011+
w.u.pTab = pTab;
1012+
sqlite3WalkExprList(&w, pEList);
1013+
if( w.eCode ){
1014+
w.xExprCallback = sqlite3ReturningSubqueryVarSelect;
1015+
w.xSelectCallback = sqlite3SelectWalkNoop;
1016+
sqlite3WalkExprList(&w, pEList);
1017+
}
1018+
}
1019+
9541020
/*
9551021
** Generate code for the RETURNING trigger. Unlike other triggers
9561022
** that invoke a subprogram in the bytecode, the code for RETURNING
@@ -1014,6 +1080,7 @@ static void codeReturningTrigger(
10141080
int i;
10151081
int nCol = pNew->nExpr;
10161082
int reg = pParse->nMem+1;
1083+
sqlite3ProcessReturningSubqueries(pNew, pTab);
10171084
pParse->nMem += nCol+2;
10181085
pReturning->iRetReg = reg;
10191086
for(i=0; i<nCol; i++){

test/returning1.test

+60
Original file line numberDiff line numberDiff line change
@@ -460,4 +460,64 @@ do_catchsql_test 19.1 {
460460
END;
461461
} {0 {}}
462462

463+
# 2024-04-24
464+
# https://sqlite.org/forum/forumpost/2c83569ce8945d39
465+
#
466+
# If the RETURNING clause includes subqueries that reference the
467+
# table being modified, make sure that the subqueries are identified
468+
# as correlated so that the results are recomputed after each step
469+
# instead of being computed once and reused.
470+
#
471+
reset_db
472+
db null N
473+
do_execsql_test 20.1 {
474+
CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT);
475+
INSERT INTO t1 VALUES(1,10),(2,20),(3,30),(4,40),(6,60),(8,80);
476+
BEGIN;
477+
DELETE FROM t1 WHERE a<>3
478+
RETURNING a,
479+
(SELECT min(a) FROM t1),
480+
(SELECT max(a) FROM t1),
481+
(SELECT round(avg(a),2) FROM t1);
482+
ROLLBACK;
483+
} {
484+
1 2 8 4.6
485+
2 3 8 5.25
486+
4 3 8 5.67
487+
6 3 8 5.5
488+
8 3 3 3.0
489+
}
490+
do_execsql_test 20.2 {
491+
BEGIN;
492+
DELETE FROM t1
493+
RETURNING a,
494+
(SELECT min(a) FROM t1),
495+
(SELECT max(a) FROM t1),
496+
(SELECT round(avg(a),2) FROM t1);
497+
ROLLBACK;
498+
} {
499+
1 2 8 4.6
500+
2 3 8 5.25
501+
3 4 8 6.0
502+
4 6 8 7.0
503+
6 8 8 8.0
504+
8 N N N
505+
}
506+
do_execsql_test 20.3 {
507+
BEGIN;
508+
DELETE FROM t1
509+
RETURNING a,
510+
(SELECT min(t2.a)+t1.a*100 FROM t1 AS t2),
511+
(SELECT max(t2.a)+t1.a*100 FROM t1 AS t2),
512+
(SELECT round(avg(t2.a),2)+t1.a*100 FROM t1 AS t2);
513+
ROLLBACK;
514+
} {
515+
1 102 108 104.6
516+
2 203 208 205.25
517+
3 304 308 306.0
518+
4 406 408 407.0
519+
6 608 608 608.0
520+
8 N N N
521+
}
522+
463523
finish_test

0 commit comments

Comments
 (0)