Skip to content

Commit 1ff1e4a

Browse files
committed
Really fix the ambiguity in REFRESH MATERIALIZED VIEW CONCURRENTLY.
Rather than trying to pick table aliases that won't conflict with any possible user-defined matview column name, adjust the queries' syntax so that the aliases are only used in places where they can't be mistaken for column names. Mostly this consists of writing "alias.*" not just "alias", which adds clarity for humans as well as machines. We do have the issue that "SELECT alias.*" acts differently from "SELECT alias", but we can use the same hack ruleutils.c uses for whole-row variables in SELECT lists: write "alias.*::compositetype". We might as well revert to the original aliases after doing this; they're a bit easier to read. Like 75d66d1, back-patch to all supported branches. Discussion: https://postgr.es/m/2488325.1628261320@sss.pgh.pa.us
1 parent cc4420f commit 1ff1e4a

File tree

3 files changed

+47
-24
lines changed

3 files changed

+47
-24
lines changed

src/backend/commands/matview.c

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -529,9 +529,12 @@ transientrel_destroy(DestReceiver *self)
529529
/*
530530
* Given a qualified temporary table name, append an underscore followed by
531531
* the given integer, to make a new table name based on the old one.
532+
* The result is a palloc'd string.
532533
*
533-
* This leaks memory through palloc(), which won't be cleaned up until the
534-
* current memory context is freed.
534+
* As coded, this would fail to make a valid SQL name if the given name were,
535+
* say, "FOO"."BAR". Currently, the table name portion of the input will
536+
* never be double-quoted because it's of the form "pg_temp_NNN", cf
537+
* make_new_heap(). But we might have to work harder someday.
535538
*/
536539
static char *
537540
make_temptable_name_n(char *tempname, int n)
@@ -619,16 +622,20 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
619622
* that in a way that allows showing the first duplicated row found. Even
620623
* after we pass this test, a unique index on the materialized view may
621624
* find a duplicate key problem.
625+
*
626+
* Note: here and below, we use "tablename.*::tablerowtype" as a hack to
627+
* keep ".*" from being expanded into multiple columns in a SELECT list.
628+
* Compare ruleutils.c's get_variable().
622629
*/
623630
resetStringInfo(&querybuf);
624631
appendStringInfo(&querybuf,
625-
"SELECT _$newdata FROM %s _$newdata "
626-
"WHERE _$newdata IS NOT NULL AND EXISTS "
627-
"(SELECT 1 FROM %s _$newdata2 WHERE _$newdata2 IS NOT NULL "
628-
"AND _$newdata2 OPERATOR(pg_catalog.*=) _$newdata "
629-
"AND _$newdata2.ctid OPERATOR(pg_catalog.<>) "
630-
"_$newdata.ctid)",
631-
tempname, tempname);
632+
"SELECT newdata.*::%s FROM %s newdata "
633+
"WHERE newdata.* IS NOT NULL AND EXISTS "
634+
"(SELECT 1 FROM %s newdata2 WHERE newdata2.* IS NOT NULL "
635+
"AND newdata2.* OPERATOR(pg_catalog.*=) newdata.* "
636+
"AND newdata2.ctid OPERATOR(pg_catalog.<>) "
637+
"newdata.ctid)",
638+
tempname, tempname, tempname);
632639
if (SPI_execute(querybuf.data, false, 1) != SPI_OK_SELECT)
633640
elog(ERROR, "SPI_exec failed: %s", querybuf.data);
634641
if (SPI_processed > 0)
@@ -655,9 +662,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
655662
resetStringInfo(&querybuf);
656663
appendStringInfo(&querybuf,
657664
"CREATE TEMP TABLE %s AS "
658-
"SELECT _$mv.ctid AS tid, _$newdata "
659-
"FROM %s _$mv FULL JOIN %s _$newdata ON (",
660-
diffname, matviewname, tempname);
665+
"SELECT mv.ctid AS tid, newdata.*::%s AS newdata "
666+
"FROM %s mv FULL JOIN %s newdata ON (",
667+
diffname, tempname, matviewname, tempname);
661668

662669
/*
663670
* Get the list of index OIDs for the table from the relcache, and look up
@@ -749,9 +756,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
749756
if (foundUniqueIndex)
750757
appendStringInfoString(&querybuf, " AND ");
751758

752-
leftop = quote_qualified_identifier("_$newdata",
759+
leftop = quote_qualified_identifier("newdata",
753760
NameStr(attr->attname));
754-
rightop = quote_qualified_identifier("_$mv",
761+
rightop = quote_qualified_identifier("mv",
755762
NameStr(attr->attname));
756763

757764
generate_operator_clause(&querybuf,
@@ -779,8 +786,8 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
779786
Assert(foundUniqueIndex);
780787

781788
appendStringInfoString(&querybuf,
782-
" AND _$newdata OPERATOR(pg_catalog.*=) _$mv) "
783-
"WHERE _$newdata IS NULL OR _$mv IS NULL "
789+
" AND newdata.* OPERATOR(pg_catalog.*=) mv.*) "
790+
"WHERE newdata.* IS NULL OR mv.* IS NULL "
784791
"ORDER BY tid");
785792

786793
/* Create the temporary "diff" table. */
@@ -806,19 +813,19 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
806813
/* Deletes must come before inserts; do them first. */
807814
resetStringInfo(&querybuf);
808815
appendStringInfo(&querybuf,
809-
"DELETE FROM %s _$mv WHERE ctid OPERATOR(pg_catalog.=) ANY "
810-
"(SELECT _$diff.tid FROM %s _$diff "
811-
"WHERE _$diff.tid IS NOT NULL "
812-
"AND _$diff._$newdata IS NULL)",
816+
"DELETE FROM %s mv WHERE ctid OPERATOR(pg_catalog.=) ANY "
817+
"(SELECT diff.tid FROM %s diff "
818+
"WHERE diff.tid IS NOT NULL "
819+
"AND diff.newdata IS NULL)",
813820
matviewname, diffname);
814821
if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
815822
elog(ERROR, "SPI_exec failed: %s", querybuf.data);
816823

817824
/* Inserts go last. */
818825
resetStringInfo(&querybuf);
819826
appendStringInfo(&querybuf,
820-
"INSERT INTO %s SELECT (_$diff._$newdata).* "
821-
"FROM %s _$diff WHERE tid IS NULL",
827+
"INSERT INTO %s SELECT (diff.newdata).* "
828+
"FROM %s diff WHERE tid IS NULL",
822829
matviewname, diffname);
823830
if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
824831
elog(ERROR, "SPI_exec failed: %s", querybuf.data);

src/test/regress/expected/matview.out

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,15 @@ NOTICE: drop cascades to materialized view mvtest_mv_v
551551
-- make sure running as superuser works when MV owned by another role (bug #11208)
552552
CREATE ROLE regress_user_mvtest;
553553
SET ROLE regress_user_mvtest;
554-
CREATE TABLE mvtest_foo_data AS SELECT i, md5(random()::text)
554+
-- this test case also checks for ambiguity in the queries issued by
555+
-- refresh_by_match_merge(), by choosing column names that intentionally
556+
-- duplicate all the aliases used in those queries
557+
CREATE TABLE mvtest_foo_data AS SELECT i,
558+
i+1 AS tid,
559+
md5(random()::text) AS mv,
560+
md5(random()::text) AS newdata,
561+
md5(random()::text) AS newdata2,
562+
md5(random()::text) AS diff
555563
FROM generate_series(1, 10) i;
556564
CREATE MATERIALIZED VIEW mvtest_mv_foo AS SELECT * FROM mvtest_foo_data;
557565
CREATE MATERIALIZED VIEW mvtest_mv_foo AS SELECT * FROM mvtest_foo_data;

src/test/regress/sql/matview.sql

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,15 @@ DROP TABLE mvtest_v CASCADE;
211211
-- make sure running as superuser works when MV owned by another role (bug #11208)
212212
CREATE ROLE regress_user_mvtest;
213213
SET ROLE regress_user_mvtest;
214-
CREATE TABLE mvtest_foo_data AS SELECT i, md5(random()::text)
214+
-- this test case also checks for ambiguity in the queries issued by
215+
-- refresh_by_match_merge(), by choosing column names that intentionally
216+
-- duplicate all the aliases used in those queries
217+
CREATE TABLE mvtest_foo_data AS SELECT i,
218+
i+1 AS tid,
219+
md5(random()::text) AS mv,
220+
md5(random()::text) AS newdata,
221+
md5(random()::text) AS newdata2,
222+
md5(random()::text) AS diff
215223
FROM generate_series(1, 10) i;
216224
CREATE MATERIALIZED VIEW mvtest_mv_foo AS SELECT * FROM mvtest_foo_data;
217225
CREATE MATERIALIZED VIEW mvtest_mv_foo AS SELECT * FROM mvtest_foo_data;

0 commit comments

Comments
 (0)