21
21
#include "catalog/catalog.h"
22
22
#include "catalog/indexing.h"
23
23
#include "catalog/namespace.h"
24
+ #include "catalog/pg_am.h"
25
+ #include "catalog/pg_opclass.h"
24
26
#include "catalog/pg_operator.h"
25
27
#include "commands/cluster.h"
26
28
#include "commands/matview.h"
39
41
#include "utils/rel.h"
40
42
#include "utils/snapmgr.h"
41
43
#include "utils/syscache.h"
42
- #include "utils/typcache.h"
43
44
44
45
45
46
typedef struct
@@ -61,14 +62,11 @@ static void transientrel_shutdown(DestReceiver *self);
61
62
static void transientrel_destroy (DestReceiver * self );
62
63
static void refresh_matview_datafill (DestReceiver * dest , Query * query ,
63
64
const char * queryString );
64
-
65
65
static char * make_temptable_name_n (char * tempname , int n );
66
- static void mv_GenerateOper (StringInfo buf , Oid opoid );
67
-
68
66
static void refresh_by_match_merge (Oid matviewOid , Oid tempOid , Oid relowner ,
69
67
int save_sec_context );
70
68
static void refresh_by_heap_swap (Oid matviewOid , Oid OIDNewHeap , char relpersistence );
71
-
69
+ static bool is_usable_unique_index ( Relation indexRel );
72
70
static void OpenMatViewIncrementalMaintenance (void );
73
71
static void CloseMatViewIncrementalMaintenance (void );
74
72
@@ -230,23 +228,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
230
228
{
231
229
Oid indexoid = lfirst_oid (indexoidscan );
232
230
Relation indexRel ;
233
- Form_pg_index indexStruct ;
234
231
235
232
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 );
249
234
index_close (indexRel , AccessShareLock );
235
+ if (hasUniqueIndex )
236
+ break ;
250
237
}
251
238
252
239
list_free (indexoidlist );
@@ -536,25 +523,6 @@ make_temptable_name_n(char *tempname, int n)
536
523
return namebuf .data ;
537
524
}
538
525
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
-
558
526
/*
559
527
* refresh_by_match_merge
560
528
*
@@ -602,7 +570,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
602
570
List * indexoidlist ;
603
571
ListCell * indexoidscan ;
604
572
int16 relnatts ;
605
- bool * usedForQual ;
573
+ Oid * opUsedForQual ;
606
574
607
575
initStringInfo (& querybuf );
608
576
matviewRel = heap_open (matviewOid , NoLock );
@@ -614,7 +582,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
614
582
diffname = make_temptable_name_n (tempname , 2 );
615
583
616
584
relnatts = matviewRel -> rd_rel -> relnatts ;
617
- usedForQual = (bool * ) palloc0 (sizeof (bool ) * relnatts );
618
585
619
586
/* Open SPI context. */
620
587
if (SPI_connect () != SPI_OK_CONNECT )
@@ -678,58 +645,98 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
678
645
* include all rows.
679
646
*/
680
647
tupdesc = matviewRel -> rd_att ;
648
+ opUsedForQual = (Oid * ) palloc0 (sizeof (Oid ) * relnatts );
681
649
foundUniqueIndex = false;
650
+
682
651
indexoidlist = RelationGetIndexList (matviewRel );
683
652
684
653
foreach (indexoidscan , indexoidlist )
685
654
{
686
655
Oid indexoid = lfirst_oid (indexoidscan );
687
656
Relation indexRel ;
688
- Form_pg_index indexStruct ;
689
657
690
658
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 ))
701
660
{
661
+ Form_pg_index indexStruct = indexRel -> rd_index ;
702
662
int numatts = indexStruct -> indnatts ;
663
+ oidvector * indclass ;
664
+ Datum indclassDatum ;
665
+ bool isnull ;
703
666
int i ;
704
667
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
+
705
676
/* Add quals for all columns from this index. */
706
677
for (i = 0 ; i < numatts ; i ++ )
707
678
{
708
679
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 ;
710
687
Oid op ;
711
- const char * colname ;
688
+ const char * leftop ;
689
+ const char * rightop ;
712
690
713
691
/*
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 .
716
694
*/
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 )
718
722
continue ;
719
- usedForQual [attnum - 1 ] = true ;
723
+ opUsedForQual [attnum - 1 ] = op ;
720
724
721
725
/*
722
726
* Actually add the qual, ANDed with any others.
723
727
*/
724
728
if (foundUniqueIndex )
725
729
appendStringInfoString (& querybuf , " AND " );
726
730
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 );
733
740
734
741
foundUniqueIndex = true;
735
742
}
@@ -742,11 +749,11 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
742
749
list_free (indexoidlist );
743
750
744
751
/*
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.
746
753
*
747
754
* ExecRefreshMatView() checks that after taking the exclusive lock on the
748
755
* 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 .
750
757
*/
751
758
Assert (foundUniqueIndex );
752
759
@@ -823,6 +830,51 @@ refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence)
823
830
RecentXmin , ReadNextMultiXactId (), relpersistence );
824
831
}
825
832
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
+
826
878
827
879
/*
828
880
* This should be used to test whether the backend is in a context where it is
0 commit comments