Skip to content

Commit 841c29c

Browse files
committed
Various cleanups for REFRESH MATERIALIZED VIEW CONCURRENTLY.
Open and lock each index before checking definition in RMVC. The ExclusiveLock on the related table is not viewed as sufficient to ensure that no changes are made to the index definition, and invalidation messages from other backends might have been missed. Additionally, use RelationGetIndexExpressions() and check for NIL rather than doing our own loop. Protect against redefinition of tid and rowvar operators in RMVC. While working on this, noticed that the fixes for bugs found during the CF made the UPDATE statement useless, since no rows could qualify for that treatment any more. Ripping out code to support the UPDATE statement simplified the operator cleanups. Change slightly confusing local field name. Use meaningful alias names on queries in refresh_by_match_merge(). Per concerns of raised by Andres Freund and comments and suggestions from Noah Misch. Some additional issues remain, which will be addressed separately.
1 parent 221e92f commit 841c29c

File tree

1 file changed

+34
-79
lines changed

1 file changed

+34
-79
lines changed

src/backend/commands/matview.c

+34-79
Original file line numberDiff line numberDiff line change
@@ -496,19 +496,14 @@ mv_GenerateOper(StringInfo buf, Oid opoid)
496496
* columns equal. The behavior of NULLs on equality tests and on UNIQUE
497497
* indexes turns out to be quite convenient here; the tests we need to make
498498
* are consistent with default behavior. If there is at least one UNIQUE
499-
* index on the materialized view, we have exactly the guarantee we need. By
500-
* joining based on equality on all columns which are part of any unique
501-
* index, we identify the rows on which we can use UPDATE without any problem.
502-
* If any column is NULL in either the old or new version of a row (or both),
503-
* we must use DELETE and INSERT, since there could be multiple rows which are
504-
* NOT DISTINCT FROM each other, and we could otherwise end up with the wrong
505-
* number of occurrences in the updated relation. The temporary table used to
506-
* hold the diff results contains just the TID of the old record (if matched)
507-
* and the ROW from the new table as a single column of complex record type
508-
* (if matched).
499+
* index on the materialized view, we have exactly the guarantee we need.
509500
*
510-
* Once we have the diff table, we perform set-based DELETE, UPDATE, and
511-
* INSERT operations against the materialized view, and discard both temporary
501+
* The temporary table used to hold the diff results contains just the TID of
502+
* the old record (if matched) and the ROW from the new table as a single
503+
* column of complex record type (if matched).
504+
*
505+
* Once we have the diff table, we perform set-based DELETE and INSERT
506+
* operations against the materialized view, and discard both temporary
512507
* tables.
513508
*
514509
* Everything from the generation of the new data to applying the differences
@@ -567,9 +562,12 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
567562
*/
568563
resetStringInfo(&querybuf);
569564
appendStringInfo(&querybuf,
570-
"SELECT x FROM %s x WHERE x IS NOT NULL AND EXISTS "
571-
"(SELECT * FROM %s y WHERE y IS NOT NULL "
572-
"AND (y.*) = (x.*) AND y.ctid <> x.ctid) LIMIT 1",
565+
"SELECT newdata FROM %s newdata "
566+
"WHERE newdata IS NOT NULL AND EXISTS "
567+
"(SELECT * FROM %s newdata2 WHERE newdata2 IS NOT NULL "
568+
"AND newdata2 OPERATOR(pg_catalog.=) newdata "
569+
"AND newdata2.ctid OPERATOR(pg_catalog.<>) "
570+
"newdata.ctid) LIMIT 1",
573571
tempname, tempname);
574572
if (SPI_execute(querybuf.data, false, 1) != SPI_OK_SELECT)
575573
elog(ERROR, "SPI_exec failed: %s", querybuf.data);
@@ -587,7 +585,8 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
587585
resetStringInfo(&querybuf);
588586
appendStringInfo(&querybuf,
589587
"CREATE TEMP TABLE %s AS "
590-
"SELECT x.ctid AS tid, y FROM %s x FULL JOIN %s y ON (",
588+
"SELECT mv.ctid AS tid, newdata "
589+
"FROM %s mv FULL JOIN %s newdata ON (",
591590
diffname, matviewname, tempname);
592591

593592
/*
@@ -603,52 +602,45 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
603602
foreach(indexoidscan, indexoidlist)
604603
{
605604
Oid indexoid = lfirst_oid(indexoidscan);
605+
Relation indexRel;
606606
HeapTuple indexTuple;
607-
Form_pg_index index;
607+
Form_pg_index indexStruct;
608608

609+
indexRel = index_open(indexoid, RowExclusiveLock);
609610
indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
610611
if (!HeapTupleIsValid(indexTuple)) /* should not happen */
611612
elog(ERROR, "cache lookup failed for index %u", indexoid);
612-
index = (Form_pg_index) GETSTRUCT(indexTuple);
613+
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
613614

614615
/* We're only interested if it is unique and valid. */
615-
if (index->indisunique && IndexIsValid(index))
616+
if (indexStruct->indisunique && IndexIsValid(indexStruct))
616617
{
617-
int numatts = index->indnatts;
618+
int numatts = indexStruct->indnatts;
618619
int i;
619-
bool expr = false;
620-
Relation indexRel;
621620

622621
/* Skip any index on an expression. */
623-
for (i = 0; i < numatts; i++)
624-
{
625-
if (index->indkey.values[i] == 0)
626-
{
627-
expr = true;
628-
break;
629-
}
630-
}
631-
if (expr)
622+
if (RelationGetIndexExpressions(indexRel) != NIL)
632623
{
624+
index_close(indexRel, NoLock);
633625
ReleaseSysCache(indexTuple);
634626
continue;
635627
}
636628

637629
/* Skip partial indexes. */
638-
indexRel = index_open(index->indexrelid, RowExclusiveLock);
639630
if (RelationGetIndexPredicate(indexRel) != NIL)
640631
{
641632
index_close(indexRel, NoLock);
642633
ReleaseSysCache(indexTuple);
643634
continue;
644635
}
636+
645637
/* Hold the locks, since we're about to run DML which needs them. */
646638
index_close(indexRel, NoLock);
647639

648640
/* Add quals for all columns from this index. */
649641
for (i = 0; i < numatts; i++)
650642
{
651-
int attnum = index->indkey.values[i];
643+
int attnum = indexStruct->indkey.values[i];
652644
Oid type;
653645
Oid op;
654646
const char *colname;
@@ -671,11 +663,11 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
671663
appendStringInfoString(&querybuf, " AND ");
672664

673665
colname = quote_identifier(NameStr((tupdesc->attrs[attnum - 1])->attname));
674-
appendStringInfo(&querybuf, "y.%s ", colname);
666+
appendStringInfo(&querybuf, "newdata.%s ", colname);
675667
type = attnumTypeId(matviewRel, attnum);
676668
op = lookup_type_cache(type, TYPECACHE_EQ_OPR)->eq_opr;
677669
mv_GenerateOper(&querybuf, op);
678-
appendStringInfo(&querybuf, " x.%s", colname);
670+
appendStringInfo(&querybuf, " mv.%s", colname);
679671

680672
foundUniqueIndex = true;
681673
}
@@ -693,7 +685,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
693685
errhint("Create a UNIQUE index with no WHERE clause on one or more columns of the materialized view.")));
694686

695687
appendStringInfoString(&querybuf,
696-
" AND y = x) WHERE (y.*) IS DISTINCT FROM (x.*)"
688+
" AND newdata = mv) WHERE newdata IS NULL OR mv IS NULL"
697689
" ORDER BY tid");
698690

699691
/* Create the temporary "diff" table. */
@@ -726,56 +718,19 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
726718
/* Deletes must come before inserts; do them first. */
727719
resetStringInfo(&querybuf);
728720
appendStringInfo(&querybuf,
729-
"DELETE FROM %s WHERE ctid IN "
730-
"(SELECT d.tid FROM %s d "
731-
"WHERE d.tid IS NOT NULL "
732-
"AND (d.y) IS NOT DISTINCT FROM NULL)",
721+
"DELETE FROM %s mv WHERE ctid OPERATOR(pg_catalog.=) ANY "
722+
"(SELECT diff.tid FROM %s diff "
723+
"WHERE diff.tid IS NOT NULL "
724+
"AND diff.newdata IS NULL)",
733725
matviewname, diffname);
734726
if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
735727
elog(ERROR, "SPI_exec failed: %s", querybuf.data);
736728

737-
/* Updates before inserts gives a better chance at HOT updates. */
738-
resetStringInfo(&querybuf);
739-
appendStringInfo(&querybuf, "UPDATE %s x SET ", matviewname);
740-
741-
{
742-
int i;
743-
bool targetColFound = false;
744-
745-
for (i = 0; i < tupdesc->natts; i++)
746-
{
747-
const char *colname;
748-
749-
if (tupdesc->attrs[i]->attisdropped)
750-
continue;
751-
752-
if (usedForQual[i])
753-
continue;
754-
755-
if (targetColFound)
756-
appendStringInfoString(&querybuf, ", ");
757-
targetColFound = true;
758-
759-
colname = quote_identifier(NameStr((tupdesc->attrs[i])->attname));
760-
appendStringInfo(&querybuf, "%s = (d.y).%s", colname, colname);
761-
}
762-
763-
if (targetColFound)
764-
{
765-
appendStringInfo(&querybuf,
766-
" FROM %s d "
767-
"WHERE d.tid IS NOT NULL AND x.ctid = d.tid",
768-
diffname);
769-
770-
if (SPI_exec(querybuf.data, 0) != SPI_OK_UPDATE)
771-
elog(ERROR, "SPI_exec failed: %s", querybuf.data);
772-
}
773-
}
774-
775729
/* Inserts go last. */
776730
resetStringInfo(&querybuf);
777731
appendStringInfo(&querybuf,
778-
"INSERT INTO %s SELECT (y).* FROM %s WHERE tid IS NULL",
732+
"INSERT INTO %s SELECT (diff.newdata).* "
733+
"FROM %s diff WHERE tid IS NULL",
779734
matviewname, diffname);
780735
if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
781736
elog(ERROR, "SPI_exec failed: %s", querybuf.data);

0 commit comments

Comments
 (0)