Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 7049219

Browse files
committed
Calculate extraUpdatedCols in query rewriter, not parser.
It's unsafe to do this at parse time because addition of generated columns to a table would not invalidate stored rules containing UPDATEs on the table ... but there might now be dependent generated columns that were not there when the rule was made. This also fixes an oversight that rewriteTargetView failed to update extraUpdatedCols when transforming an UPDATE on an updatable view. (Since the new calculation is downstream of that, rewriteTargetView doesn't actually need to do anything; but before, there was a demonstrable bug there.) In v13 and HEAD, this leads to easily-visible bugs because (since commit c6679e4) we won't recalculate generated columns that aren't listed in extraUpdatedCols. In v12 this bitmap is mostly just used for trigger-firing decisions, so you'd only notice a problem if a trigger cared whether a generated column had been updated. I'd complained about this back in May, but then forgot about it until bug #16671 from Michael Paul Killian revived the issue. Back-patch to v12 where this field was introduced. If existing stored rules contain any extraUpdatedCols values, they'll be ignored because the rewriter will overwrite them, so the bug will be fixed even for existing rules. (But note that if someone were to update to 13.1 or 12.5, store some rules with UPDATEs on tables having generated columns, and then downgrade to a prior minor version, they might observe issues similar to what this patch fixes. That seems unlikely enough to not be worth going to a lot of effort to fix.) Discussion: https://postgr.es/m/10206.1588964727@sss.pgh.pa.us Discussion: https://postgr.es/m/16671-2fa55851859fb166@postgresql.org
1 parent b4395cc commit 7049219

File tree

9 files changed

+115
-47
lines changed

9 files changed

+115
-47
lines changed

src/backend/optimizer/plan/setrefs.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,9 +411,9 @@ flatten_rtes_walker(Node *node, PlannerGlobal *glob)
411411
* In the flat rangetable, we zero out substructure pointers that are not
412412
* needed by the executor; this reduces the storage space and copying cost
413413
* for cached plans. We keep only the ctename, alias and eref Alias fields,
414-
* which are needed by EXPLAIN, and the selectedCols, insertedCols and
415-
* updatedCols bitmaps, which are needed for executor-startup permissions
416-
* checking and for trigger event checking.
414+
* which are needed by EXPLAIN, and the selectedCols, insertedCols,
415+
* updatedCols, and extraUpdatedCols bitmaps, which are needed for
416+
* executor-startup permissions checking and for trigger event checking.
417417
*/
418418
static void
419419
add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)

src/backend/parser/analyze.c

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,7 +2296,6 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
22962296
RangeTblEntry *target_rte;
22972297
ListCell *orig_tl;
22982298
ListCell *tl;
2299-
TupleDesc tupdesc = pstate->p_target_relation->rd_att;
23002299

23012300
tlist = transformTargetList(pstate, origTlist,
23022301
EXPR_KIND_UPDATE_SOURCE);
@@ -2355,41 +2354,9 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
23552354
if (orig_tl != NULL)
23562355
elog(ERROR, "UPDATE target count mismatch --- internal error");
23572356

2358-
fill_extraUpdatedCols(target_rte, tupdesc);
2359-
23602357
return tlist;
23612358
}
23622359

2363-
/*
2364-
* Record in extraUpdatedCols generated columns referencing updated base
2365-
* columns.
2366-
*/
2367-
void
2368-
fill_extraUpdatedCols(RangeTblEntry *target_rte, TupleDesc tupdesc)
2369-
{
2370-
if (tupdesc->constr &&
2371-
tupdesc->constr->has_generated_stored)
2372-
{
2373-
for (int i = 0; i < tupdesc->constr->num_defval; i++)
2374-
{
2375-
AttrDefault defval = tupdesc->constr->defval[i];
2376-
Node *expr;
2377-
Bitmapset *attrs_used = NULL;
2378-
2379-
/* skip if not generated column */
2380-
if (!TupleDescAttr(tupdesc, defval.adnum - 1)->attgenerated)
2381-
continue;
2382-
2383-
expr = stringToNode(defval.adbin);
2384-
pull_varattnos(expr, 1, &attrs_used);
2385-
2386-
if (bms_overlap(target_rte->updatedCols, attrs_used))
2387-
target_rte->extraUpdatedCols = bms_add_member(target_rte->extraUpdatedCols,
2388-
defval.adnum - FirstLowInvalidHeapAttributeNumber);
2389-
}
2390-
}
2391-
}
2392-
23932360
/*
23942361
* transformReturningList -
23952362
* handle a RETURNING clause in INSERT/UPDATE/DELETE

src/backend/replication/logical/worker.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@
4545
#include "miscadmin.h"
4646
#include "nodes/makefuncs.h"
4747
#include "optimizer/optimizer.h"
48-
#include "parser/analyze.h"
49-
#include "parser/parse_relation.h"
5048
#include "pgstat.h"
5149
#include "postmaster/bgworker.h"
5250
#include "postmaster/interrupt.h"
@@ -777,7 +775,8 @@ apply_handle_update(StringInfo s)
777775
}
778776
}
779777

780-
fill_extraUpdatedCols(target_rte, RelationGetDescr(rel->localrel));
778+
/* Also populate extraUpdatedCols, in case we have generated columns */
779+
fill_extraUpdatedCols(target_rte, rel->localrel);
781780

782781
PushActiveSnapshot(GetTransactionSnapshot());
783782

src/backend/rewrite/rewriteHandler.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "miscadmin.h"
3131
#include "nodes/makefuncs.h"
3232
#include "nodes/nodeFuncs.h"
33+
#include "optimizer/optimizer.h"
3334
#include "parser/analyze.h"
3435
#include "parser/parse_coerce.h"
3536
#include "parser/parse_relation.h"
@@ -1512,6 +1513,42 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
15121513
}
15131514
}
15141515

1516+
/*
1517+
* Record in target_rte->extraUpdatedCols the indexes of any generated columns
1518+
* that depend on any columns mentioned in target_rte->updatedCols.
1519+
*/
1520+
void
1521+
fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
1522+
{
1523+
TupleDesc tupdesc = RelationGetDescr(target_relation);
1524+
TupleConstr *constr = tupdesc->constr;
1525+
1526+
target_rte->extraUpdatedCols = NULL;
1527+
1528+
if (constr && constr->has_generated_stored)
1529+
{
1530+
for (int i = 0; i < constr->num_defval; i++)
1531+
{
1532+
AttrDefault *defval = &constr->defval[i];
1533+
Node *expr;
1534+
Bitmapset *attrs_used = NULL;
1535+
1536+
/* skip if not generated column */
1537+
if (!TupleDescAttr(tupdesc, defval->adnum - 1)->attgenerated)
1538+
continue;
1539+
1540+
/* identify columns this generated column depends on */
1541+
expr = stringToNode(defval->adbin);
1542+
pull_varattnos(expr, 1, &attrs_used);
1543+
1544+
if (bms_overlap(target_rte->updatedCols, attrs_used))
1545+
target_rte->extraUpdatedCols =
1546+
bms_add_member(target_rte->extraUpdatedCols,
1547+
defval->adnum - FirstLowInvalidHeapAttributeNumber);
1548+
}
1549+
}
1550+
}
1551+
15151552

15161553
/*
15171554
* matchLocks -
@@ -1643,6 +1680,7 @@ ApplyRetrieveRule(Query *parsetree,
16431680
rte->selectedCols = NULL;
16441681
rte->insertedCols = NULL;
16451682
rte->updatedCols = NULL;
1683+
rte->extraUpdatedCols = NULL;
16461684

16471685
/*
16481686
* For the most part, Vars referencing the view should remain as
@@ -3621,6 +3659,9 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
36213659
parsetree->override,
36223660
rt_entry_relation,
36233661
parsetree->resultRelation);
3662+
3663+
/* Also populate extraUpdatedCols (for generated columns) */
3664+
fill_extraUpdatedCols(rt_entry, rt_entry_relation);
36243665
}
36253666
else if (event == CMD_DELETE)
36263667
{

src/include/nodes/parsenodes.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -939,12 +939,16 @@ typedef struct PartitionCmd
939939
*
940940
* updatedCols is also used in some other places, for example, to determine
941941
* which triggers to fire and in FDWs to know which changed columns they
942-
* need to ship off. Generated columns that are caused to be updated by an
943-
* update to a base column are collected in extraUpdatedCols. This is not
944-
* considered for permission checking, but it is useful in those places
945-
* that want to know the full set of columns being updated as opposed to
946-
* only the ones the user explicitly mentioned in the query. (There is
947-
* currently no need for an extraInsertedCols, but it could exist.)
942+
* need to ship off.
943+
*
944+
* Generated columns that are caused to be updated by an update to a base
945+
* column are listed in extraUpdatedCols. This is not considered for
946+
* permission checking, but it is useful in those places that want to know
947+
* the full set of columns being updated as opposed to only the ones the
948+
* user explicitly mentioned in the query. (There is currently no need for
949+
* an extraInsertedCols, but it could exist.) Note that extraUpdatedCols
950+
* is populated during query rewrite, NOT in the parser, since generated
951+
* columns could be added after a rule has been parsed and stored.
948952
*
949953
* securityQuals is a list of security barrier quals (boolean expressions),
950954
* to be tested in the listed order before returning a row from the

src/include/parser/analyze.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,4 @@ extern void applyLockingClause(Query *qry, Index rtindex,
4646
extern List *BuildOnConflictExcludedTargetlist(Relation targetrel,
4747
Index exclRelIndex);
4848

49-
extern void fill_extraUpdatedCols(RangeTblEntry *target_rte, TupleDesc tupdesc);
50-
5149
#endif /* ANALYZE_H */

src/include/rewrite/rewriteHandler.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ extern Node *build_column_default(Relation rel, int attrno);
2626
extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
2727
Relation target_relation);
2828

29+
extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
30+
Relation target_relation);
31+
2932
extern Query *get_view_query(Relation view);
3033
extern const char *view_query_is_auto_updatable(Query *viewquery,
3134
bool check_cols);

src/test/regress/expected/updatable_views.out

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,41 @@ NOTICE: drop cascades to 3 other objects
14671467
DETAIL: drop cascades to view rw_view1
14681468
drop cascades to view rw_view2
14691469
drop cascades to view rw_view3
1470+
-- view on table with GENERATED columns
1471+
CREATE TABLE base_tbl (id int, idplus1 int GENERATED ALWAYS AS (id + 1) STORED);
1472+
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
1473+
INSERT INTO base_tbl (id) VALUES (1);
1474+
INSERT INTO rw_view1 (id) VALUES (2);
1475+
INSERT INTO base_tbl (id, idplus1) VALUES (3, DEFAULT);
1476+
INSERT INTO rw_view1 (id, idplus1) VALUES (4, DEFAULT);
1477+
INSERT INTO base_tbl (id, idplus1) VALUES (5, 6); -- error
1478+
ERROR: cannot insert into column "idplus1"
1479+
DETAIL: Column "idplus1" is a generated column.
1480+
INSERT INTO rw_view1 (id, idplus1) VALUES (6, 7); -- error
1481+
ERROR: cannot insert into column "idplus1"
1482+
DETAIL: Column "idplus1" is a generated column.
1483+
SELECT * FROM base_tbl;
1484+
id | idplus1
1485+
----+---------
1486+
1 | 2
1487+
2 | 3
1488+
3 | 4
1489+
4 | 5
1490+
(4 rows)
1491+
1492+
UPDATE base_tbl SET id = 2000 WHERE id = 2;
1493+
UPDATE rw_view1 SET id = 3000 WHERE id = 3;
1494+
SELECT * FROM base_tbl;
1495+
id | idplus1
1496+
------+---------
1497+
1 | 2
1498+
4 | 5
1499+
2000 | 2001
1500+
3000 | 3001
1501+
(4 rows)
1502+
1503+
DROP TABLE base_tbl CASCADE;
1504+
NOTICE: drop cascades to view rw_view1
14701505
-- inheritance tests
14711506
CREATE TABLE base_tbl_parent (a int);
14721507
CREATE TABLE base_tbl_child (CHECK (a > 0)) INHERITS (base_tbl_parent);

src/test/regress/sql/updatable_views.sql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,27 @@ SELECT events & 4 != 0 AS upd,
697697

698698
DROP TABLE base_tbl CASCADE;
699699

700+
-- view on table with GENERATED columns
701+
702+
CREATE TABLE base_tbl (id int, idplus1 int GENERATED ALWAYS AS (id + 1) STORED);
703+
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
704+
705+
INSERT INTO base_tbl (id) VALUES (1);
706+
INSERT INTO rw_view1 (id) VALUES (2);
707+
INSERT INTO base_tbl (id, idplus1) VALUES (3, DEFAULT);
708+
INSERT INTO rw_view1 (id, idplus1) VALUES (4, DEFAULT);
709+
INSERT INTO base_tbl (id, idplus1) VALUES (5, 6); -- error
710+
INSERT INTO rw_view1 (id, idplus1) VALUES (6, 7); -- error
711+
712+
SELECT * FROM base_tbl;
713+
714+
UPDATE base_tbl SET id = 2000 WHERE id = 2;
715+
UPDATE rw_view1 SET id = 3000 WHERE id = 3;
716+
717+
SELECT * FROM base_tbl;
718+
719+
DROP TABLE base_tbl CASCADE;
720+
700721
-- inheritance tests
701722

702723
CREATE TABLE base_tbl_parent (a int);

0 commit comments

Comments
 (0)