20
20
#include "catalog/catalog.h"
21
21
#include "catalog/indexing.h"
22
22
#include "catalog/namespace.h"
23
+ #include "catalog/pg_am.h"
24
+ #include "catalog/pg_opclass.h"
23
25
#include "catalog/pg_operator.h"
24
26
#include "commands/cluster.h"
25
27
#include "commands/matview.h"
38
40
#include "utils/rel.h"
39
41
#include "utils/snapmgr.h"
40
42
#include "utils/syscache.h"
41
- #include "utils/typcache.h"
42
43
43
44
44
45
typedef struct
@@ -60,14 +61,11 @@ static void transientrel_shutdown(DestReceiver *self);
60
61
static void transientrel_destroy (DestReceiver * self );
61
62
static void refresh_matview_datafill (DestReceiver * dest , Query * query ,
62
63
const char * queryString );
63
-
64
64
static char * make_temptable_name_n (char * tempname , int n );
65
- static void mv_GenerateOper (StringInfo buf , Oid opoid );
66
-
67
65
static void refresh_by_match_merge (Oid matviewOid , Oid tempOid , Oid relowner ,
68
66
int save_sec_context );
69
67
static void refresh_by_heap_swap (Oid matviewOid , Oid OIDNewHeap );
70
-
68
+ static bool is_usable_unique_index ( Relation indexRel );
71
69
static void OpenMatViewIncrementalMaintenance (void );
72
70
static void CloseMatViewIncrementalMaintenance (void );
73
71
@@ -477,25 +475,6 @@ make_temptable_name_n(char *tempname, int n)
477
475
return namebuf .data ;
478
476
}
479
477
480
- static void
481
- mv_GenerateOper (StringInfo buf , Oid opoid )
482
- {
483
- HeapTuple opertup ;
484
- Form_pg_operator operform ;
485
-
486
- opertup = SearchSysCache1 (OPEROID , ObjectIdGetDatum (opoid ));
487
- if (!HeapTupleIsValid (opertup ))
488
- elog (ERROR , "cache lookup failed for operator %u" , opoid );
489
- operform = (Form_pg_operator ) GETSTRUCT (opertup );
490
- Assert (operform -> oprkind == 'b' );
491
-
492
- appendStringInfo (buf , "OPERATOR(%s.%s)" ,
493
- quote_identifier (get_namespace_name (operform -> oprnamespace )),
494
- NameStr (operform -> oprname ));
495
-
496
- ReleaseSysCache (opertup );
497
- }
498
-
499
478
/*
500
479
* refresh_by_match_merge
501
480
*
@@ -543,7 +522,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
543
522
List * indexoidlist ;
544
523
ListCell * indexoidscan ;
545
524
int16 relnatts ;
546
- bool * usedForQual ;
525
+ Oid * opUsedForQual ;
547
526
548
527
initStringInfo (& querybuf );
549
528
matviewRel = heap_open (matviewOid , NoLock );
@@ -555,7 +534,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
555
534
diffname = make_temptable_name_n (tempname , 2 );
556
535
557
536
relnatts = matviewRel -> rd_rel -> relnatts ;
558
- usedForQual = (bool * ) palloc0 (sizeof (bool ) * relnatts );
559
537
560
538
/* Open SPI context. */
561
539
if (SPI_connect () != SPI_OK_CONNECT )
@@ -619,58 +597,98 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
619
597
* include all rows.
620
598
*/
621
599
tupdesc = matviewRel -> rd_att ;
600
+ opUsedForQual = (Oid * ) palloc0 (sizeof (Oid ) * relnatts );
622
601
foundUniqueIndex = false;
602
+
623
603
indexoidlist = RelationGetIndexList (matviewRel );
624
604
625
605
foreach (indexoidscan , indexoidlist )
626
606
{
627
607
Oid indexoid = lfirst_oid (indexoidscan );
628
608
Relation indexRel ;
629
- Form_pg_index indexStruct ;
630
609
631
610
indexRel = index_open (indexoid , RowExclusiveLock );
632
- indexStruct = indexRel -> rd_index ;
633
-
634
- /*
635
- * We're only interested if it is unique, valid, contains no
636
- * expressions, and is not partial.
637
- */
638
- if (indexStruct -> indisunique &&
639
- IndexIsValid (indexStruct ) &&
640
- RelationGetIndexExpressions (indexRel ) == NIL &&
641
- RelationGetIndexPredicate (indexRel ) == NIL )
611
+ if (is_usable_unique_index (indexRel ))
642
612
{
613
+ Form_pg_index indexStruct = indexRel -> rd_index ;
643
614
int numatts = indexStruct -> indnatts ;
615
+ oidvector * indclass ;
616
+ Datum indclassDatum ;
617
+ bool isnull ;
644
618
int i ;
645
619
620
+ /* Must get indclass the hard way. */
621
+ indclassDatum = SysCacheGetAttr (INDEXRELID ,
622
+ indexRel -> rd_indextuple ,
623
+ Anum_pg_index_indclass ,
624
+ & isnull );
625
+ Assert (!isnull );
626
+ indclass = (oidvector * ) DatumGetPointer (indclassDatum );
627
+
646
628
/* Add quals for all columns from this index. */
647
629
for (i = 0 ; i < numatts ; i ++ )
648
630
{
649
631
int attnum = indexStruct -> indkey .values [i ];
650
- Oid type ;
632
+ Oid opclass = indclass -> values [i ];
633
+ Form_pg_attribute attr = TupleDescAttr (tupdesc , attnum - 1 );
634
+ Oid attrtype = attr -> atttypid ;
635
+ HeapTuple cla_ht ;
636
+ Form_pg_opclass cla_tup ;
637
+ Oid opfamily ;
638
+ Oid opcintype ;
651
639
Oid op ;
652
- const char * colname ;
640
+ const char * leftop ;
641
+ const char * rightop ;
642
+
643
+ /*
644
+ * Identify the equality operator associated with this index
645
+ * column. First we need to look up the column's opclass.
646
+ */
647
+ cla_ht = SearchSysCache1 (CLAOID , ObjectIdGetDatum (opclass ));
648
+ if (!HeapTupleIsValid (cla_ht ))
649
+ elog (ERROR , "cache lookup failed for opclass %u" , opclass );
650
+ cla_tup = (Form_pg_opclass ) GETSTRUCT (cla_ht );
651
+ Assert (cla_tup -> opcmethod == BTREE_AM_OID );
652
+ opfamily = cla_tup -> opcfamily ;
653
+ opcintype = cla_tup -> opcintype ;
654
+ ReleaseSysCache (cla_ht );
655
+
656
+ op = get_opfamily_member (opfamily , opcintype , opcintype ,
657
+ BTEqualStrategyNumber );
658
+ if (!OidIsValid (op ))
659
+ elog (ERROR , "missing operator %d(%u,%u) in opfamily %u" ,
660
+ BTEqualStrategyNumber , opcintype , opcintype , opfamily );
653
661
654
662
/*
655
- * Only include the column once regardless of how many times
656
- * it shows up in how many indexes.
663
+ * If we find the same column with the same equality semantics
664
+ * in more than one index, we only need to emit the equality
665
+ * clause once.
666
+ *
667
+ * Since we only remember the last equality operator, this
668
+ * code could be fooled into emitting duplicate clauses given
669
+ * multiple indexes with several different opclasses ... but
670
+ * that's so unlikely it doesn't seem worth spending extra
671
+ * code to avoid.
657
672
*/
658
- if (usedForQual [attnum - 1 ])
673
+ if (opUsedForQual [attnum - 1 ] == op )
659
674
continue ;
660
- usedForQual [attnum - 1 ] = true ;
675
+ opUsedForQual [attnum - 1 ] = op ;
661
676
662
677
/*
663
678
* Actually add the qual, ANDed with any others.
664
679
*/
665
680
if (foundUniqueIndex )
666
681
appendStringInfoString (& querybuf , " AND " );
667
682
668
- colname = quote_identifier (NameStr ((tupdesc -> attrs [attnum - 1 ])-> attname ));
669
- appendStringInfo (& querybuf , "newdata.%s " , colname );
670
- type = attnumTypeId (matviewRel , attnum );
671
- op = lookup_type_cache (type , TYPECACHE_EQ_OPR )-> eq_opr ;
672
- mv_GenerateOper (& querybuf , op );
673
- appendStringInfo (& querybuf , " mv.%s" , colname );
683
+ leftop = quote_qualified_identifier ("newdata" ,
684
+ NameStr (attr -> attname ));
685
+ rightop = quote_qualified_identifier ("mv" ,
686
+ NameStr (attr -> attname ));
687
+
688
+ generate_operator_clause (& querybuf ,
689
+ leftop , attrtype ,
690
+ op ,
691
+ rightop , attrtype );
674
692
675
693
foundUniqueIndex = true;
676
694
}
@@ -762,6 +780,51 @@ refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap)
762
780
RecentXmin , ReadNextMultiXactId ());
763
781
}
764
782
783
+ /*
784
+ * Check whether specified index is usable for match merge.
785
+ */
786
+ static bool
787
+ is_usable_unique_index (Relation indexRel )
788
+ {
789
+ Form_pg_index indexStruct = indexRel -> rd_index ;
790
+
791
+ /*
792
+ * Must be unique, valid, immediate, non-partial, and be defined over
793
+ * plain user columns (not expressions). We also require it to be a
794
+ * btree. Even if we had any other unique index kinds, we'd not know how
795
+ * to identify the corresponding equality operator, nor could we be sure
796
+ * that the planner could implement the required FULL JOIN with non-btree
797
+ * operators.
798
+ */
799
+ if (indexStruct -> indisunique &&
800
+ indexStruct -> indimmediate &&
801
+ indexRel -> rd_rel -> relam == BTREE_AM_OID &&
802
+ IndexIsValid (indexStruct ) &&
803
+ RelationGetIndexPredicate (indexRel ) == NIL &&
804
+ indexStruct -> indnatts > 0 )
805
+ {
806
+ /*
807
+ * The point of groveling through the index columns individually is to
808
+ * reject both index expressions and system columns. Currently,
809
+ * matviews couldn't have OID columns so there's no way to create an
810
+ * index on a system column; but maybe someday that wouldn't be true,
811
+ * so let's be safe.
812
+ */
813
+ int numatts = indexStruct -> indnatts ;
814
+ int i ;
815
+
816
+ for (i = 0 ; i < numatts ; i ++ )
817
+ {
818
+ int attnum = indexStruct -> indkey .values [i ];
819
+
820
+ if (attnum <= 0 )
821
+ return false;
822
+ }
823
+ return true;
824
+ }
825
+ return false;
826
+ }
827
+
765
828
766
829
/*
767
830
* This should be used to test whether the backend is in a context where it is
0 commit comments