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
@@ -490,25 +488,6 @@ make_temptable_name_n(char *tempname, int n)
490
488
return namebuf .data ;
491
489
}
492
490
493
- static void
494
- mv_GenerateOper (StringInfo buf , Oid opoid )
495
- {
496
- HeapTuple opertup ;
497
- Form_pg_operator operform ;
498
-
499
- opertup = SearchSysCache1 (OPEROID , ObjectIdGetDatum (opoid ));
500
- if (!HeapTupleIsValid (opertup ))
501
- elog (ERROR , "cache lookup failed for operator %u" , opoid );
502
- operform = (Form_pg_operator ) GETSTRUCT (opertup );
503
- Assert (operform -> oprkind == 'b' );
504
-
505
- appendStringInfo (buf , "OPERATOR(%s.%s)" ,
506
- quote_identifier (get_namespace_name (operform -> oprnamespace )),
507
- NameStr (operform -> oprname ));
508
-
509
- ReleaseSysCache (opertup );
510
- }
511
-
512
491
/*
513
492
* refresh_by_match_merge
514
493
*
@@ -556,7 +535,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
556
535
List * indexoidlist ;
557
536
ListCell * indexoidscan ;
558
537
int16 relnatts ;
559
- bool * usedForQual ;
538
+ Oid * opUsedForQual ;
560
539
561
540
initStringInfo (& querybuf );
562
541
matviewRel = heap_open (matviewOid , NoLock );
@@ -568,7 +547,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
568
547
diffname = make_temptable_name_n (tempname , 2 );
569
548
570
549
relnatts = matviewRel -> rd_rel -> relnatts ;
571
- usedForQual = (bool * ) palloc0 (sizeof (bool ) * relnatts );
572
550
573
551
/* Open SPI context. */
574
552
if (SPI_connect () != SPI_OK_CONNECT )
@@ -632,58 +610,98 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
632
610
* include all rows.
633
611
*/
634
612
tupdesc = matviewRel -> rd_att ;
613
+ opUsedForQual = (Oid * ) palloc0 (sizeof (Oid ) * relnatts );
635
614
foundUniqueIndex = false;
615
+
636
616
indexoidlist = RelationGetIndexList (matviewRel );
637
617
638
618
foreach (indexoidscan , indexoidlist )
639
619
{
640
620
Oid indexoid = lfirst_oid (indexoidscan );
641
621
Relation indexRel ;
642
- Form_pg_index indexStruct ;
643
622
644
623
indexRel = index_open (indexoid , RowExclusiveLock );
645
- indexStruct = indexRel -> rd_index ;
646
-
647
- /*
648
- * We're only interested if it is unique, valid, contains no
649
- * expressions, and is not partial.
650
- */
651
- if (indexStruct -> indisunique &&
652
- IndexIsValid (indexStruct ) &&
653
- RelationGetIndexExpressions (indexRel ) == NIL &&
654
- RelationGetIndexPredicate (indexRel ) == NIL )
624
+ if (is_usable_unique_index (indexRel ))
655
625
{
626
+ Form_pg_index indexStruct = indexRel -> rd_index ;
656
627
int numatts = indexStruct -> indnatts ;
628
+ oidvector * indclass ;
629
+ Datum indclassDatum ;
630
+ bool isnull ;
657
631
int i ;
658
632
633
+ /* Must get indclass the hard way. */
634
+ indclassDatum = SysCacheGetAttr (INDEXRELID ,
635
+ indexRel -> rd_indextuple ,
636
+ Anum_pg_index_indclass ,
637
+ & isnull );
638
+ Assert (!isnull );
639
+ indclass = (oidvector * ) DatumGetPointer (indclassDatum );
640
+
659
641
/* Add quals for all columns from this index. */
660
642
for (i = 0 ; i < numatts ; i ++ )
661
643
{
662
644
int attnum = indexStruct -> indkey .values [i ];
663
- Oid type ;
645
+ Oid opclass = indclass -> values [i ];
646
+ Form_pg_attribute attr = TupleDescAttr (tupdesc , attnum - 1 );
647
+ Oid attrtype = attr -> atttypid ;
648
+ HeapTuple cla_ht ;
649
+ Form_pg_opclass cla_tup ;
650
+ Oid opfamily ;
651
+ Oid opcintype ;
664
652
Oid op ;
665
- const char * colname ;
653
+ const char * leftop ;
654
+ const char * rightop ;
655
+
656
+ /*
657
+ * Identify the equality operator associated with this index
658
+ * column. First we need to look up the column's opclass.
659
+ */
660
+ cla_ht = SearchSysCache1 (CLAOID , ObjectIdGetDatum (opclass ));
661
+ if (!HeapTupleIsValid (cla_ht ))
662
+ elog (ERROR , "cache lookup failed for opclass %u" , opclass );
663
+ cla_tup = (Form_pg_opclass ) GETSTRUCT (cla_ht );
664
+ Assert (cla_tup -> opcmethod == BTREE_AM_OID );
665
+ opfamily = cla_tup -> opcfamily ;
666
+ opcintype = cla_tup -> opcintype ;
667
+ ReleaseSysCache (cla_ht );
668
+
669
+ op = get_opfamily_member (opfamily , opcintype , opcintype ,
670
+ BTEqualStrategyNumber );
671
+ if (!OidIsValid (op ))
672
+ elog (ERROR , "missing operator %d(%u,%u) in opfamily %u" ,
673
+ BTEqualStrategyNumber , opcintype , opcintype , opfamily );
666
674
667
675
/*
668
- * Only include the column once regardless of how many times
669
- * it shows up in how many indexes.
676
+ * If we find the same column with the same equality semantics
677
+ * in more than one index, we only need to emit the equality
678
+ * clause once.
679
+ *
680
+ * Since we only remember the last equality operator, this
681
+ * code could be fooled into emitting duplicate clauses given
682
+ * multiple indexes with several different opclasses ... but
683
+ * that's so unlikely it doesn't seem worth spending extra
684
+ * code to avoid.
670
685
*/
671
- if (usedForQual [attnum - 1 ])
686
+ if (opUsedForQual [attnum - 1 ] == op )
672
687
continue ;
673
- usedForQual [attnum - 1 ] = true ;
688
+ opUsedForQual [attnum - 1 ] = op ;
674
689
675
690
/*
676
691
* Actually add the qual, ANDed with any others.
677
692
*/
678
693
if (foundUniqueIndex )
679
694
appendStringInfoString (& querybuf , " AND " );
680
695
681
- colname = quote_identifier (NameStr ((tupdesc -> attrs [attnum - 1 ])-> attname ));
682
- appendStringInfo (& querybuf , "newdata.%s " , colname );
683
- type = attnumTypeId (matviewRel , attnum );
684
- op = lookup_type_cache (type , TYPECACHE_EQ_OPR )-> eq_opr ;
685
- mv_GenerateOper (& querybuf , op );
686
- appendStringInfo (& querybuf , " mv.%s" , colname );
696
+ leftop = quote_qualified_identifier ("newdata" ,
697
+ NameStr (attr -> attname ));
698
+ rightop = quote_qualified_identifier ("mv" ,
699
+ NameStr (attr -> attname ));
700
+
701
+ generate_operator_clause (& querybuf ,
702
+ leftop , attrtype ,
703
+ op ,
704
+ rightop , attrtype );
687
705
688
706
foundUniqueIndex = true;
689
707
}
@@ -775,6 +793,51 @@ refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence)
775
793
RecentXmin , ReadNextMultiXactId (), relpersistence );
776
794
}
777
795
796
+ /*
797
+ * Check whether specified index is usable for match merge.
798
+ */
799
+ static bool
800
+ is_usable_unique_index (Relation indexRel )
801
+ {
802
+ Form_pg_index indexStruct = indexRel -> rd_index ;
803
+
804
+ /*
805
+ * Must be unique, valid, immediate, non-partial, and be defined over
806
+ * plain user columns (not expressions). We also require it to be a
807
+ * btree. Even if we had any other unique index kinds, we'd not know how
808
+ * to identify the corresponding equality operator, nor could we be sure
809
+ * that the planner could implement the required FULL JOIN with non-btree
810
+ * operators.
811
+ */
812
+ if (indexStruct -> indisunique &&
813
+ indexStruct -> indimmediate &&
814
+ indexRel -> rd_rel -> relam == BTREE_AM_OID &&
815
+ IndexIsValid (indexStruct ) &&
816
+ RelationGetIndexPredicate (indexRel ) == NIL &&
817
+ indexStruct -> indnatts > 0 )
818
+ {
819
+ /*
820
+ * The point of groveling through the index columns individually is to
821
+ * reject both index expressions and system columns. Currently,
822
+ * matviews couldn't have OID columns so there's no way to create an
823
+ * index on a system column; but maybe someday that wouldn't be true,
824
+ * so let's be safe.
825
+ */
826
+ int numatts = indexStruct -> indnatts ;
827
+ int i ;
828
+
829
+ for (i = 0 ; i < numatts ; i ++ )
830
+ {
831
+ int attnum = indexStruct -> indkey .values [i ];
832
+
833
+ if (attnum <= 0 )
834
+ return false;
835
+ }
836
+ return true;
837
+ }
838
+ return false;
839
+ }
840
+
778
841
779
842
/*
780
843
* This should be used to test whether the backend is in a context where it is
0 commit comments