2121#include "catalog/catalog.h"
2222#include "catalog/indexing.h"
2323#include "catalog/namespace.h"
24+ #include "catalog/pg_am.h"
25+ #include "catalog/pg_opclass.h"
2426#include "catalog/pg_operator.h"
2527#include "commands/cluster.h"
2628#include "commands/matview.h"
3941#include "utils/rel.h"
4042#include "utils/snapmgr.h"
4143#include "utils/syscache.h"
42- #include "utils/typcache.h"
4344
4445
4546typedef struct
@@ -61,14 +62,11 @@ static void transientrel_shutdown(DestReceiver *self);
6162static void transientrel_destroy (DestReceiver * self );
6263static void refresh_matview_datafill (DestReceiver * dest , Query * query ,
6364 const char * queryString );
64-
6565static char * make_temptable_name_n (char * tempname , int n );
66- static void mv_GenerateOper (StringInfo buf , Oid opoid );
67-
6866static void refresh_by_match_merge (Oid matviewOid , Oid tempOid , Oid relowner ,
6967 int save_sec_context );
7068static void refresh_by_heap_swap (Oid matviewOid , Oid OIDNewHeap , char relpersistence );
71-
69+ static bool is_usable_unique_index ( Relation indexRel );
7270static void OpenMatViewIncrementalMaintenance (void );
7371static void CloseMatViewIncrementalMaintenance (void );
7472
@@ -230,23 +228,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
230228 {
231229 Oid indexoid = lfirst_oid (indexoidscan );
232230 Relation indexRel ;
233- Form_pg_index indexStruct ;
234231
235232 indexRel = index_open (indexoid , AccessShareLock );
236- indexStruct = indexRel -> rd_index ;
237-
238- if (indexStruct -> indisunique &&
239- IndexIsValid (indexStruct ) &&
240- RelationGetIndexExpressions (indexRel ) == NIL &&
241- RelationGetIndexPredicate (indexRel ) == NIL &&
242- indexStruct -> indnatts > 0 )
243- {
244- hasUniqueIndex = true;
245- index_close (indexRel , AccessShareLock );
246- break ;
247- }
248-
233+ hasUniqueIndex = is_usable_unique_index (indexRel );
249234 index_close (indexRel , AccessShareLock );
235+ if (hasUniqueIndex )
236+ break ;
250237 }
251238
252239 list_free (indexoidlist );
@@ -536,25 +523,6 @@ make_temptable_name_n(char *tempname, int n)
536523 return namebuf .data ;
537524}
538525
539- static void
540- mv_GenerateOper (StringInfo buf , Oid opoid )
541- {
542- HeapTuple opertup ;
543- Form_pg_operator operform ;
544-
545- opertup = SearchSysCache1 (OPEROID , ObjectIdGetDatum (opoid ));
546- if (!HeapTupleIsValid (opertup ))
547- elog (ERROR , "cache lookup failed for operator %u" , opoid );
548- operform = (Form_pg_operator ) GETSTRUCT (opertup );
549- Assert (operform -> oprkind == 'b' );
550-
551- appendStringInfo (buf , "OPERATOR(%s.%s)" ,
552- quote_identifier (get_namespace_name (operform -> oprnamespace )),
553- NameStr (operform -> oprname ));
554-
555- ReleaseSysCache (opertup );
556- }
557-
558526/*
559527 * refresh_by_match_merge
560528 *
@@ -602,7 +570,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
602570 List * indexoidlist ;
603571 ListCell * indexoidscan ;
604572 int16 relnatts ;
605- bool * usedForQual ;
573+ Oid * opUsedForQual ;
606574
607575 initStringInfo (& querybuf );
608576 matviewRel = heap_open (matviewOid , NoLock );
@@ -614,7 +582,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
614582 diffname = make_temptable_name_n (tempname , 2 );
615583
616584 relnatts = matviewRel -> rd_rel -> relnatts ;
617- usedForQual = (bool * ) palloc0 (sizeof (bool ) * relnatts );
618585
619586 /* Open SPI context. */
620587 if (SPI_connect () != SPI_OK_CONNECT )
@@ -678,58 +645,98 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
678645 * include all rows.
679646 */
680647 tupdesc = matviewRel -> rd_att ;
648+ opUsedForQual = (Oid * ) palloc0 (sizeof (Oid ) * relnatts );
681649 foundUniqueIndex = false;
650+
682651 indexoidlist = RelationGetIndexList (matviewRel );
683652
684653 foreach (indexoidscan , indexoidlist )
685654 {
686655 Oid indexoid = lfirst_oid (indexoidscan );
687656 Relation indexRel ;
688- Form_pg_index indexStruct ;
689657
690658 indexRel = index_open (indexoid , RowExclusiveLock );
691- indexStruct = indexRel -> rd_index ;
692-
693- /*
694- * We're only interested if it is unique, valid, contains no
695- * expressions, and is not partial.
696- */
697- if (indexStruct -> indisunique &&
698- IndexIsValid (indexStruct ) &&
699- RelationGetIndexExpressions (indexRel ) == NIL &&
700- RelationGetIndexPredicate (indexRel ) == NIL )
659+ if (is_usable_unique_index (indexRel ))
701660 {
661+ Form_pg_index indexStruct = indexRel -> rd_index ;
702662 int numatts = indexStruct -> indnatts ;
663+ oidvector * indclass ;
664+ Datum indclassDatum ;
665+ bool isnull ;
703666 int i ;
704667
668+ /* Must get indclass the hard way. */
669+ indclassDatum = SysCacheGetAttr (INDEXRELID ,
670+ indexRel -> rd_indextuple ,
671+ Anum_pg_index_indclass ,
672+ & isnull );
673+ Assert (!isnull );
674+ indclass = (oidvector * ) DatumGetPointer (indclassDatum );
675+
705676 /* Add quals for all columns from this index. */
706677 for (i = 0 ; i < numatts ; i ++ )
707678 {
708679 int attnum = indexStruct -> indkey .values [i ];
709- Oid type ;
680+ Oid opclass = indclass -> values [i ];
681+ Form_pg_attribute attr = TupleDescAttr (tupdesc , attnum - 1 );
682+ Oid attrtype = attr -> atttypid ;
683+ HeapTuple cla_ht ;
684+ Form_pg_opclass cla_tup ;
685+ Oid opfamily ;
686+ Oid opcintype ;
710687 Oid op ;
711- const char * colname ;
688+ const char * leftop ;
689+ const char * rightop ;
712690
713691 /*
714- * Only include the column once regardless of how many times
715- * it shows up in how many indexes .
692+ * Identify the equality operator associated with this index
693+ * column. First we need to look up the column's opclass .
716694 */
717- if (usedForQual [attnum - 1 ])
695+ cla_ht = SearchSysCache1 (CLAOID , ObjectIdGetDatum (opclass ));
696+ if (!HeapTupleIsValid (cla_ht ))
697+ elog (ERROR , "cache lookup failed for opclass %u" , opclass );
698+ cla_tup = (Form_pg_opclass ) GETSTRUCT (cla_ht );
699+ Assert (cla_tup -> opcmethod == BTREE_AM_OID );
700+ opfamily = cla_tup -> opcfamily ;
701+ opcintype = cla_tup -> opcintype ;
702+ ReleaseSysCache (cla_ht );
703+
704+ op = get_opfamily_member (opfamily , opcintype , opcintype ,
705+ BTEqualStrategyNumber );
706+ if (!OidIsValid (op ))
707+ elog (ERROR , "missing operator %d(%u,%u) in opfamily %u" ,
708+ BTEqualStrategyNumber , opcintype , opcintype , opfamily );
709+
710+ /*
711+ * If we find the same column with the same equality semantics
712+ * in more than one index, we only need to emit the equality
713+ * clause once.
714+ *
715+ * Since we only remember the last equality operator, this
716+ * code could be fooled into emitting duplicate clauses given
717+ * multiple indexes with several different opclasses ... but
718+ * that's so unlikely it doesn't seem worth spending extra
719+ * code to avoid.
720+ */
721+ if (opUsedForQual [attnum - 1 ] == op )
718722 continue ;
719- usedForQual [attnum - 1 ] = true ;
723+ opUsedForQual [attnum - 1 ] = op ;
720724
721725 /*
722726 * Actually add the qual, ANDed with any others.
723727 */
724728 if (foundUniqueIndex )
725729 appendStringInfoString (& querybuf , " AND " );
726730
727- colname = quote_identifier (NameStr ((tupdesc -> attrs [attnum - 1 ])-> attname ));
728- appendStringInfo (& querybuf , "newdata.%s " , colname );
729- type = attnumTypeId (matviewRel , attnum );
730- op = lookup_type_cache (type , TYPECACHE_EQ_OPR )-> eq_opr ;
731- mv_GenerateOper (& querybuf , op );
732- appendStringInfo (& querybuf , " mv.%s" , colname );
731+ leftop = quote_qualified_identifier ("newdata" ,
732+ NameStr (attr -> attname ));
733+ rightop = quote_qualified_identifier ("mv" ,
734+ NameStr (attr -> attname ));
735+
736+ generate_operator_clause (& querybuf ,
737+ leftop , attrtype ,
738+ op ,
739+ rightop , attrtype );
733740
734741 foundUniqueIndex = true;
735742 }
@@ -742,11 +749,11 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
742749 list_free (indexoidlist );
743750
744751 /*
745- * There must be at least one unique index on the matview.
752+ * There must be at least one usable unique index on the matview.
746753 *
747754 * ExecRefreshMatView() checks that after taking the exclusive lock on the
748755 * matview. So at least one unique index is guaranteed to exist here
749- * because the lock is still being held.
756+ * because the lock is still being held; so an Assert seems sufficient .
750757 */
751758 Assert (foundUniqueIndex );
752759
@@ -823,6 +830,51 @@ refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence)
823830 RecentXmin , ReadNextMultiXactId (), relpersistence );
824831}
825832
833+ /*
834+ * Check whether specified index is usable for match merge.
835+ */
836+ static bool
837+ is_usable_unique_index (Relation indexRel )
838+ {
839+ Form_pg_index indexStruct = indexRel -> rd_index ;
840+
841+ /*
842+ * Must be unique, valid, immediate, non-partial, and be defined over
843+ * plain user columns (not expressions). We also require it to be a
844+ * btree. Even if we had any other unique index kinds, we'd not know how
845+ * to identify the corresponding equality operator, nor could we be sure
846+ * that the planner could implement the required FULL JOIN with non-btree
847+ * operators.
848+ */
849+ if (indexStruct -> indisunique &&
850+ indexStruct -> indimmediate &&
851+ indexRel -> rd_rel -> relam == BTREE_AM_OID &&
852+ IndexIsValid (indexStruct ) &&
853+ RelationGetIndexPredicate (indexRel ) == NIL &&
854+ indexStruct -> indnatts > 0 )
855+ {
856+ /*
857+ * The point of groveling through the index columns individually is to
858+ * reject both index expressions and system columns. Currently,
859+ * matviews couldn't have OID columns so there's no way to create an
860+ * index on a system column; but maybe someday that wouldn't be true,
861+ * so let's be safe.
862+ */
863+ int numatts = indexStruct -> indnatts ;
864+ int i ;
865+
866+ for (i = 0 ; i < numatts ; i ++ )
867+ {
868+ int attnum = indexStruct -> indkey .values [i ];
869+
870+ if (attnum <= 0 )
871+ return false;
872+ }
873+ return true;
874+ }
875+ return false;
876+ }
877+
826878
827879/*
828880 * This should be used to test whether the backend is in a context where it is
0 commit comments