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"
4042#include "utils/rel.h"
4143#include "utils/snapmgr.h"
4244#include "utils/syscache.h"
43- #include "utils/typcache.h"
4445
4546
4647typedef struct
@@ -62,14 +63,11 @@ static void transientrel_shutdown(DestReceiver *self);
6263static void transientrel_destroy (DestReceiver * self );
6364static uint64 refresh_matview_datafill (DestReceiver * dest , Query * query ,
6465 const char * queryString );
65-
6666static char * make_temptable_name_n (char * tempname , int n );
67- static void mv_GenerateOper (StringInfo buf , Oid opoid );
68-
6967static void refresh_by_match_merge (Oid matviewOid , Oid tempOid , Oid relowner ,
7068 int save_sec_context );
7169static void refresh_by_heap_swap (Oid matviewOid , Oid OIDNewHeap , char relpersistence );
72-
70+ static bool is_usable_unique_index ( Relation indexRel );
7371static void OpenMatViewIncrementalMaintenance (void );
7472static void CloseMatViewIncrementalMaintenance (void );
7573
@@ -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 );
@@ -557,25 +544,6 @@ make_temptable_name_n(char *tempname, int n)
557544 return namebuf .data ;
558545}
559546
560- static void
561- mv_GenerateOper (StringInfo buf , Oid opoid )
562- {
563- HeapTuple opertup ;
564- Form_pg_operator operform ;
565-
566- opertup = SearchSysCache1 (OPEROID , ObjectIdGetDatum (opoid ));
567- if (!HeapTupleIsValid (opertup ))
568- elog (ERROR , "cache lookup failed for operator %u" , opoid );
569- operform = (Form_pg_operator ) GETSTRUCT (opertup );
570- Assert (operform -> oprkind == 'b' );
571-
572- appendStringInfo (buf , "OPERATOR(%s.%s)" ,
573- quote_identifier (get_namespace_name (operform -> oprnamespace )),
574- NameStr (operform -> oprname ));
575-
576- ReleaseSysCache (opertup );
577- }
578-
579547/*
580548 * refresh_by_match_merge
581549 *
@@ -623,7 +591,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
623591 List * indexoidlist ;
624592 ListCell * indexoidscan ;
625593 int16 relnatts ;
626- bool * usedForQual ;
594+ Oid * opUsedForQual ;
627595
628596 initStringInfo (& querybuf );
629597 matviewRel = heap_open (matviewOid , NoLock );
@@ -635,7 +603,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
635603 diffname = make_temptable_name_n (tempname , 2 );
636604
637605 relnatts = matviewRel -> rd_rel -> relnatts ;
638- usedForQual = (bool * ) palloc0 (sizeof (bool ) * relnatts );
639606
640607 /* Open SPI context. */
641608 if (SPI_connect () != SPI_OK_CONNECT )
@@ -699,59 +666,98 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
699666 * include all rows.
700667 */
701668 tupdesc = matviewRel -> rd_att ;
669+ opUsedForQual = (Oid * ) palloc0 (sizeof (Oid ) * relnatts );
702670 foundUniqueIndex = false;
671+
703672 indexoidlist = RelationGetIndexList (matviewRel );
704673
705674 foreach (indexoidscan , indexoidlist )
706675 {
707676 Oid indexoid = lfirst_oid (indexoidscan );
708677 Relation indexRel ;
709- Form_pg_index indexStruct ;
710678
711679 indexRel = index_open (indexoid , RowExclusiveLock );
712- indexStruct = indexRel -> rd_index ;
713-
714- /*
715- * We're only interested if it is unique, valid, contains no
716- * expressions, and is not partial.
717- */
718- if (indexStruct -> indisunique &&
719- IndexIsValid (indexStruct ) &&
720- RelationGetIndexExpressions (indexRel ) == NIL &&
721- RelationGetIndexPredicate (indexRel ) == NIL )
680+ if (is_usable_unique_index (indexRel ))
722681 {
682+ Form_pg_index indexStruct = indexRel -> rd_index ;
723683 int numatts = indexStruct -> indnatts ;
684+ oidvector * indclass ;
685+ Datum indclassDatum ;
686+ bool isnull ;
724687 int i ;
725688
689+ /* Must get indclass the hard way. */
690+ indclassDatum = SysCacheGetAttr (INDEXRELID ,
691+ indexRel -> rd_indextuple ,
692+ Anum_pg_index_indclass ,
693+ & isnull );
694+ Assert (!isnull );
695+ indclass = (oidvector * ) DatumGetPointer (indclassDatum );
696+
726697 /* Add quals for all columns from this index. */
727698 for (i = 0 ; i < numatts ; i ++ )
728699 {
729700 int attnum = indexStruct -> indkey .values [i ];
701+ Oid opclass = indclass -> values [i ];
730702 Form_pg_attribute attr = TupleDescAttr (tupdesc , attnum - 1 );
731- Oid type ;
703+ Oid attrtype = attr -> atttypid ;
704+ HeapTuple cla_ht ;
705+ Form_pg_opclass cla_tup ;
706+ Oid opfamily ;
707+ Oid opcintype ;
732708 Oid op ;
733- const char * colname ;
709+ const char * leftop ;
710+ const char * rightop ;
734711
735712 /*
736- * Only include the column once regardless of how many times
737- * it shows up in how many indexes .
713+ * Identify the equality operator associated with this index
714+ * column. First we need to look up the column's opclass .
738715 */
739- if (usedForQual [attnum - 1 ])
716+ cla_ht = SearchSysCache1 (CLAOID , ObjectIdGetDatum (opclass ));
717+ if (!HeapTupleIsValid (cla_ht ))
718+ elog (ERROR , "cache lookup failed for opclass %u" , opclass );
719+ cla_tup = (Form_pg_opclass ) GETSTRUCT (cla_ht );
720+ Assert (cla_tup -> opcmethod == BTREE_AM_OID );
721+ opfamily = cla_tup -> opcfamily ;
722+ opcintype = cla_tup -> opcintype ;
723+ ReleaseSysCache (cla_ht );
724+
725+ op = get_opfamily_member (opfamily , opcintype , opcintype ,
726+ BTEqualStrategyNumber );
727+ if (!OidIsValid (op ))
728+ elog (ERROR , "missing operator %d(%u,%u) in opfamily %u" ,
729+ BTEqualStrategyNumber , opcintype , opcintype , opfamily );
730+
731+ /*
732+ * If we find the same column with the same equality semantics
733+ * in more than one index, we only need to emit the equality
734+ * clause once.
735+ *
736+ * Since we only remember the last equality operator, this
737+ * code could be fooled into emitting duplicate clauses given
738+ * multiple indexes with several different opclasses ... but
739+ * that's so unlikely it doesn't seem worth spending extra
740+ * code to avoid.
741+ */
742+ if (opUsedForQual [attnum - 1 ] == op )
740743 continue ;
741- usedForQual [attnum - 1 ] = true ;
744+ opUsedForQual [attnum - 1 ] = op ;
742745
743746 /*
744747 * Actually add the qual, ANDed with any others.
745748 */
746749 if (foundUniqueIndex )
747750 appendStringInfoString (& querybuf , " AND " );
748751
749- colname = quote_identifier (NameStr (attr -> attname ));
750- appendStringInfo (& querybuf , "newdata.%s " , colname );
751- type = attnumTypeId (matviewRel , attnum );
752- op = lookup_type_cache (type , TYPECACHE_EQ_OPR )-> eq_opr ;
753- mv_GenerateOper (& querybuf , op );
754- appendStringInfo (& querybuf , " mv.%s" , colname );
752+ leftop = quote_qualified_identifier ("newdata" ,
753+ NameStr (attr -> attname ));
754+ rightop = quote_qualified_identifier ("mv" ,
755+ NameStr (attr -> attname ));
756+
757+ generate_operator_clause (& querybuf ,
758+ leftop , attrtype ,
759+ op ,
760+ rightop , attrtype );
755761
756762 foundUniqueIndex = true;
757763 }
@@ -764,11 +770,11 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
764770 list_free (indexoidlist );
765771
766772 /*
767- * There must be at least one unique index on the matview.
773+ * There must be at least one usable unique index on the matview.
768774 *
769775 * ExecRefreshMatView() checks that after taking the exclusive lock on the
770776 * matview. So at least one unique index is guaranteed to exist here
771- * because the lock is still being held.
777+ * because the lock is still being held; so an Assert seems sufficient .
772778 */
773779 Assert (foundUniqueIndex );
774780
@@ -845,6 +851,51 @@ refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence)
845851 RecentXmin , ReadNextMultiXactId (), relpersistence );
846852}
847853
854+ /*
855+ * Check whether specified index is usable for match merge.
856+ */
857+ static bool
858+ is_usable_unique_index (Relation indexRel )
859+ {
860+ Form_pg_index indexStruct = indexRel -> rd_index ;
861+
862+ /*
863+ * Must be unique, valid, immediate, non-partial, and be defined over
864+ * plain user columns (not expressions). We also require it to be a
865+ * btree. Even if we had any other unique index kinds, we'd not know how
866+ * to identify the corresponding equality operator, nor could we be sure
867+ * that the planner could implement the required FULL JOIN with non-btree
868+ * operators.
869+ */
870+ if (indexStruct -> indisunique &&
871+ indexStruct -> indimmediate &&
872+ indexRel -> rd_rel -> relam == BTREE_AM_OID &&
873+ IndexIsValid (indexStruct ) &&
874+ RelationGetIndexPredicate (indexRel ) == NIL &&
875+ indexStruct -> indnatts > 0 )
876+ {
877+ /*
878+ * The point of groveling through the index columns individually is to
879+ * reject both index expressions and system columns. Currently,
880+ * matviews couldn't have OID columns so there's no way to create an
881+ * index on a system column; but maybe someday that wouldn't be true,
882+ * so let's be safe.
883+ */
884+ int numatts = indexStruct -> indnatts ;
885+ int i ;
886+
887+ for (i = 0 ; i < numatts ; i ++ )
888+ {
889+ int attnum = indexStruct -> indkey .values [i ];
890+
891+ if (attnum <= 0 )
892+ return false;
893+ }
894+ return true;
895+ }
896+ return false;
897+ }
898+
848899
849900/*
850901 * This should be used to test whether the backend is in a context where it is
0 commit comments