Show index search count in EXPLAIN ANALYZE.
authorPeter Geoghegan <pg@bowt.ie>
Wed, 5 Mar 2025 14:36:48 +0000 (09:36 -0500)
committerPeter Geoghegan <pg@bowt.ie>
Wed, 5 Mar 2025 14:36:48 +0000 (09:36 -0500)
Expose the count of index searches/index descents in EXPLAIN ANALYZE's
output for index scan nodes.  This information is particularly useful
with scans that use ScalarArrayOp quals, where the number of index scans
isn't predictable in advance (at least not with optimizations like the
one added to nbtree by Postgres 17 commit 5bf748b8).  It will also be
useful when EXPLAIN ANALYZE shows details of an nbtree index scan that
uses skip scan optimizations set to be introduced by an upcoming patch.

The instrumentation works by teaching index AMs to increment a new
nsearches counter whenever a new index search begins.  The counter is
incremented at exactly the same point that index AMs must already
increment the index's pg_stat_*_indexes.idx_scan counter (we're counting
the same event, but at the scan level rather than the relation level).
The new counter is stored in the scan descriptor (IndexScanDescData),
which explain.c reaches by going through the scan node's PlanState.

This approach doesn't match the approach used when tracking other index
scan specific costs (e.g., "Rows Removed by Filter:").  It is similar to
the approach used in other cases where we must track costs that are only
readily accessible inside an access method, and not from the executor
(e.g., "Heap Blocks:" output for a Bitmap Heap Scan).  It is inherently
necessary to maintain a counter that can be incremented multiple times
during a single amgettuple call (or amgetbitmap call), and directly
exposing PlanState.instrument to index access methods seems unappealing.

Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-By: Tomas Vondra <tomas@vondra.me>
Reviewed-By: Robert Haas <robertmhaas@gmail.com>
Reviewed-By: Masahiro Ikeda <ikedamsh@oss.nttdata.com>
Reviewed-By: Matthias van de Meent <boekewurm+postgres@gmail.com>
Discussion: https://postgr.es/m/CAH2-Wz=PKR6rB7qbx+Vnd7eqeB5VTcrW=iJvAsTsKbdG+kW_UA@mail.gmail.com
Discussion: https://postgr.es/m/CAH2-WzkRqvaqR2CTNqTZP0z6FuL4-3ED6eQB0yx38XBNj1v-4Q@mail.gmail.com

22 files changed:
contrib/bloom/blscan.c
doc/src/sgml/bloom.sgml
doc/src/sgml/monitoring.sgml
doc/src/sgml/perform.sgml
doc/src/sgml/ref/explain.sgml
doc/src/sgml/rules.sgml
src/backend/access/brin/brin.c
src/backend/access/gin/ginscan.c
src/backend/access/gist/gistget.c
src/backend/access/hash/hashsearch.c
src/backend/access/index/genam.c
src/backend/access/nbtree/nbtree.c
src/backend/access/nbtree/nbtsearch.c
src/backend/access/spgist/spgscan.c
src/backend/commands/explain.c
src/include/access/relscan.h
src/test/regress/expected/brin_multi.out
src/test/regress/expected/memoize.out
src/test/regress/expected/partition_prune.out
src/test/regress/expected/select.out
src/test/regress/sql/memoize.sql
src/test/regress/sql/partition_prune.sql

index bf801fe78ff38ddd8f356bd0c96ee1a3ce979b78..472169d6145a18e6c261e3f1812e0b04fde71a4d 100644 (file)
@@ -116,6 +116,7 @@ blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
    bas = GetAccessStrategy(BAS_BULKREAD);
    npages = RelationGetNumberOfBlocks(scan->indexRelation);
    pgstat_count_index_scan(scan->indexRelation);
+   scan->nsearches++;
 
    for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++)
    {
index 663a0a4a6810c100e200b5630f24d84416025096..ec5d077679b14935f08a94e3c74574721e9293d5 100644 (file)
@@ -173,10 +173,11 @@ CREATE INDEX
    Buffers: shared hit=21864
    -&gt;  Bitmap Index Scan on bloomidx  (cost=0.00..178436.00 rows=1 width=0) (actual time=20.005..20.005 rows=2300.00 loops=1)
          Index Cond: ((i2 = 898732) AND (i5 = 123451))
+         Index Searches: 1
          Buffers: shared hit=19608
  Planning Time: 0.099 ms
  Execution Time: 22.632 ms
-(10 rows)
+(11 rows)
 </programlisting>
   </para>
 
@@ -208,13 +209,15 @@ CREATE INDEX
          Buffers: shared hit=6
          -&gt;  Bitmap Index Scan on btreeidx5  (cost=0.00..4.52 rows=11 width=0) (actual time=0.026..0.026 rows=7.00 loops=1)
                Index Cond: (i5 = 123451)
+               Index Searches: 1
                Buffers: shared hit=3
          -&gt;  Bitmap Index Scan on btreeidx2  (cost=0.00..4.52 rows=11 width=0) (actual time=0.007..0.007 rows=8.00 loops=1)
                Index Cond: (i2 = 898732)
+               Index Searches: 1
                Buffers: shared hit=3
  Planning Time: 0.264 ms
  Execution Time: 0.047 ms
-(13 rows)
+(15 rows)
 </programlisting>
    Although this query runs much faster than with either of the single
    indexes, we pay a penalty in index size.  Each of the single-column
index 16646f560e8dce69a7f06faf781ea128e25fff54..fd9bdd884720388f38e5ad5bb2660953acd0ceaa 100644 (file)
@@ -4234,16 +4234,32 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
   <note>
    <para>
-    Queries that use certain <acronym>SQL</acronym> constructs to search for
-    rows matching any value out of a list or array of multiple scalar values
-    (see <xref linkend="functions-comparisons"/>) perform multiple
-    <quote>primitive</quote> index scans (up to one primitive scan per scalar
-    value) during query execution.  Each internal primitive index scan
-    increments <structname>pg_stat_all_indexes</structname>.<structfield>idx_scan</structfield>,
+    Index scans may sometimes perform multiple index searches per execution.
+    Each index search increments <structname>pg_stat_all_indexes</structname>.<structfield>idx_scan</structfield>,
     so it's possible for the count of index scans to significantly exceed the
     total number of index scan executor node executions.
    </para>
+   <para>
+    This can happen with queries that use certain <acronym>SQL</acronym>
+    constructs to search for rows matching any value out of a list or array of
+    multiple scalar values (see <xref linkend="functions-comparisons"/>).  It
+    can also happen to queries with a
+    <literal><replaceable>column_name</replaceable> =
+     <replaceable>value1</replaceable> OR
+     <replaceable>column_name</replaceable> =
+     <replaceable>value2</replaceable> ...</literal> construct, though only
+    when the optimizer transforms the construct into an equivalent
+    multi-valued array representation.
+   </para>
   </note>
+  <tip>
+   <para>
+    <command>EXPLAIN ANALYZE</command> outputs the total number of index
+    searches performed by each index scan node.  See
+    <xref linkend="using-explain-analyze"/> for an example demonstrating how
+    this works.
+   </para>
+  </tip>
 
  </sect2>
 
index be4b49f62b5fd903c93b136581d9edc3a9394825..684eccc097bfaca15ae205965dc82f7bb9c5dcaa 100644 (file)
@@ -729,9 +729,11 @@ WHERE t1.unique1 &lt; 10 AND t1.unique2 = t2.unique2;
          Buffers: shared hit=3 read=5 written=4
          -&gt;  Bitmap Index Scan on tenk1_unique1  (cost=0.00..4.36 rows=10 width=0) (actual time=0.004..0.004 rows=10.00 loops=1)
                Index Cond: (unique1 &lt; 10)
+               Index Searches: 1
                Buffers: shared hit=2
    -&gt;  Index Scan using tenk2_unique2 on tenk2 t2  (cost=0.29..7.90 rows=1 width=244) (actual time=0.003..0.003 rows=1 loops=10)
          Index Cond: (unique2 = t1.unique2)
+         Index Searches: 10
          Buffers: shared hit=24 read=6
  Planning:
    Buffers: shared hit=15 dirtied=9
@@ -790,6 +792,7 @@ WHERE t1.unique1 &lt; 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous;
                      Buffers: shared hit=92
                      -&gt;  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=100 width=0) (actual time=0.013..0.013 rows=100.00 loops=1)
                            Index Cond: (unique1 &lt; 100)
+                           Index Searches: 1
                            Buffers: shared hit=2
  Planning:
    Buffers: shared hit=12
@@ -805,6 +808,58 @@ WHERE t1.unique1 &lt; 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous;
     shown.)
    </para>
 
+   <para>
+    Index Scan nodes (as well as Bitmap Index Scan and Index-Only Scan nodes)
+    show an <quote>Index Searches</quote> line that reports the total number
+    of searches across <emphasis>all</emphasis> node
+    executions/<literal>loops</literal>:
+
+<screen>
+EXPLAIN ANALYZE SELECT * FROM tenk1 WHERE thousand IN (1, 500, 700, 999);
+                                                            QUERY PLAN
+-------------------------------------------------------------------&zwsp;---------------------------------------------------------
+ Bitmap Heap Scan on tenk1  (cost=9.45..73.44 rows=40 width=244) (actual time=0.012..0.028 rows=40.00 loops=1)
+   Recheck Cond: (thousand = ANY ('{1,500,700,999}'::integer[]))
+   Heap Blocks: exact=39
+   Buffers: shared hit=47
+   ->  Bitmap Index Scan on tenk1_thous_tenthous  (cost=0.00..9.44 rows=40 width=0) (actual time=0.009..0.009 rows=40.00 loops=1)
+         Index Cond: (thousand = ANY ('{1,500,700,999}'::integer[]))
+         Index Searches: 4
+         Buffers: shared hit=8
+ Planning Time: 0.037 ms
+ Execution Time: 0.034 ms
+</screen>
+
+    Here we see a Bitmap Index Scan node that needed 4 separate index
+    searches.  The scan had to search the index from the
+    <structname>tenk1_thous_tenthous</structname> index root page once per
+    <type>integer</type> value from the predicate's <literal>IN</literal>
+    construct.  However, the number of index searches often won't have such a
+    simple correspondence to the query predicate:
+
+<screen>
+EXPLAIN ANALYZE SELECT * FROM tenk1 WHERE thousand IN (1, 2, 3, 4);
+                                                            QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on tenk1  (cost=9.45..73.44 rows=40 width=244) (actual time=0.009..0.019 rows=40.00 loops=1)
+   Recheck Cond: (thousand = ANY ('{1,2,3,4}'::integer[]))
+   Heap Blocks: exact=38
+   Buffers: shared hit=40
+   ->  Bitmap Index Scan on tenk1_thous_tenthous  (cost=0.00..9.44 rows=40 width=0) (actual time=0.005..0.005 rows=40.00 loops=1)
+         Index Cond: (thousand = ANY ('{1,2,3,4}'::integer[]))
+         Index Searches: 1
+         Buffers: shared hit=2
+ Planning Time: 0.029 ms
+ Execution Time: 0.026 ms
+</screen>
+
+    This variant of our <literal>IN</literal> query performed only 1 index
+    search.  It spent less time traversing the index (compared to the original
+    query) because its <literal>IN</literal> construct uses values matching
+    index tuples stored next to each other, on the same
+    <structname>tenk1_thous_tenthous</structname> index leaf page.
+   </para>
+
    <para>
     Another type of extra information is the number of rows removed by a
     filter condition:
@@ -861,6 +916,7 @@ EXPLAIN ANALYZE SELECT * FROM polygon_tbl WHERE f1 @&gt; polygon '(0.5,2.0)';
  Index Scan using gpolygonind on polygon_tbl  (cost=0.13..8.15 rows=1 width=85) (actual time=0.074..0.074 rows=0.00 loops=1)
    Index Cond: (f1 @&gt; '((0.5,2))'::polygon)
    Rows Removed by Index Recheck: 1
+   Index Searches: 1
    Buffers: shared hit=1
  Planning Time: 0.039 ms
  Execution Time: 0.098 ms
@@ -894,8 +950,10 @@ EXPLAIN (ANALYZE, BUFFERS OFF) SELECT * FROM tenk1 WHERE unique1 &lt; 100 AND un
    -&gt;  BitmapAnd  (cost=25.07..25.07 rows=10 width=0) (actual time=0.100..0.101 rows=0.00 loops=1)
          -&gt;  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=100 width=0) (actual time=0.027..0.027 rows=100.00 loops=1)
                Index Cond: (unique1 &lt; 100)
+               Index Searches: 1
          -&gt;  Bitmap Index Scan on tenk1_unique2  (cost=0.00..19.78 rows=999 width=0) (actual time=0.070..0.070 rows=999.00 loops=1)
                Index Cond: (unique2 &gt; 9000)
+               Index Searches: 1
  Planning Time: 0.162 ms
  Execution Time: 0.143 ms
 </screen>
@@ -923,6 +981,7 @@ EXPLAIN ANALYZE UPDATE tenk1 SET hundred = hundred + 1 WHERE unique1 &lt; 100;
          Buffers: shared hit=4 read=2
          -&gt;  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=100 width=0) (actual time=0.031..0.031 rows=100.00 loops=1)
                Index Cond: (unique1 &lt; 100)
+               Index Searches: 1
                Buffers: shared read=2
  Planning Time: 0.151 ms
  Execution Time: 1.856 ms
@@ -1061,6 +1120,7 @@ EXPLAIN ANALYZE SELECT * FROM tenk1 WHERE unique1 &lt; 100 AND unique2 &gt; 9000
          Index Cond: (unique2 &gt; 9000)
          Filter: (unique1 &lt; 100)
          Rows Removed by Filter: 287
+         Index Searches: 1
          Buffers: shared hit=16
  Planning Time: 0.077 ms
  Execution Time: 0.086 ms
index 7daddf03ef0b3c1022a9c622280bdbec79f86624..9ed1061b7ff206371ddabc3cf28ce8c04bf7ad31 100644 (file)
@@ -506,10 +506,11 @@ EXPLAIN ANALYZE EXECUTE query(100, 200);
    Buffers: shared hit=4
    -&gt;  Index Scan using test_pkey on test  (cost=0.29..10.27 rows=99 width=8) (actual time=0.009..0.025 rows=99.00 loops=1)
          Index Cond: ((id &gt; 100) AND (id &lt; 200))
+         Index Searches: 1
          Buffers: shared hit=4
  Planning Time: 0.244 ms
  Execution Time: 0.073 ms
-(9 rows)
+(10 rows)
 </programlisting>
   </para>
 
index 1d9924a2a3c714247aa5515662793c43449a287c..8467d961fd0a05d367ac0565ab8376a0b8c2d30a 100644 (file)
@@ -1046,6 +1046,7 @@ SELECT count(*) FROM words WHERE word = 'caterpiler';
    -&gt;  Index Only Scan using wrd_word on wrd  (cost=0.42..4.44 rows=1 width=0) (actual time=0.039..0.039 rows=0.00 loops=1)
          Index Cond: (word = 'caterpiler'::text)
          Heap Fetches: 0
+         Index Searches: 1
  Planning time: 0.164 ms
  Execution time: 0.117 ms
 </programlisting>
@@ -1090,6 +1091,7 @@ SELECT word FROM words ORDER BY word &lt;-&gt; 'caterpiler' LIMIT 10;
  Limit  (cost=0.29..1.06 rows=10 width=10) (actual time=187.222..188.257 rows=10.00 loops=1)
    -&gt;  Index Scan using wrd_trgm on wrd  (cost=0.29..37020.87 rows=479829 width=10) (actual time=187.219..188.252 rows=10.00 loops=1)
          Order By: (word &lt;-&gt; 'caterpiler'::text)
+         Index Searches: 1
  Planning time: 0.196 ms
  Execution time: 198.640 ms
 </programlisting>
index 75a65ec9c75a949d8a7fa90304fdabe069334587..9f146c12a85a2ee63112547daa351cc4008345fe 100644 (file)
@@ -591,6 +591,7 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
    opaque = (BrinOpaque *) scan->opaque;
    bdesc = opaque->bo_bdesc;
    pgstat_count_index_scan(idxRel);
+   scan->nsearches++;
 
    /*
     * We need to know the size of the table so that we know how long to
index 63ded6301e25f2cec6908ab056cd6df0e3501a8c..8c1bbf36644fc04bffdbbb328298d099ba3aa4d3 100644 (file)
@@ -437,6 +437,7 @@ ginNewScanKey(IndexScanDesc scan)
    MemoryContextSwitchTo(oldCtx);
 
    pgstat_count_index_scan(scan->indexRelation);
+   scan->nsearches++;
 }
 
 void
index cc40e928e0a1224433b2be589c93cfd46b23c3c7..609e85fdae1510b799a682ad2d87efc03941c947 100644 (file)
@@ -625,6 +625,7 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir)
        GISTSearchItem fakeItem;
 
        pgstat_count_index_scan(scan->indexRelation);
+       scan->nsearches++;
 
        so->firstCall = false;
        so->curPageData = so->nPageData = 0;
@@ -750,6 +751,7 @@ gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
        return 0;
 
    pgstat_count_index_scan(scan->indexRelation);
+   scan->nsearches++;
 
    /* Begin the scan by processing the root page */
    so->curPageData = so->nPageData = 0;
index a3a1fccf33b479ea1960aff0d281de9239c48c86..c4f73043772f9a9afd868e57e1302b70e1028024 100644 (file)
@@ -298,6 +298,7 @@ _hash_first(IndexScanDesc scan, ScanDirection dir)
    HashScanPosItem *currItem;
 
    pgstat_count_index_scan(rel);
+   scan->nsearches++;
 
    /*
     * We do not support hash scans with no index qualification, because we
index 07bae342e25f01ff02533899b32522b7e1a6de40..0cabfa5de5d3c6d40e632b8fc9b08ffbd74e5923 100644 (file)
@@ -119,6 +119,7 @@ RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys)
    scan->ignore_killed_tuples = !scan->xactStartedInRecovery;
 
    scan->opaque = NULL;
+   scan->nsearches = 0;
 
    scan->xs_itup = NULL;
    scan->xs_itupdesc = NULL;
index 45ea6afba1db57e845c8de561cb24b25f7b10798..eabd54b8c3d3607803b4ac8dad5c575c6d8891af 100644 (file)
@@ -70,6 +70,7 @@ typedef struct BTParallelScanDescData
    BTPS_State  btps_pageStatus;    /* indicates whether next page is
                                     * available for scan. see above for
                                     * possible states of parallel scan. */
+   uint64      btps_nsearches; /* tracked for IndexScanDescData.nsearches */
    slock_t     btps_mutex;     /* protects above variables, btps_arrElems */
    ConditionVariable btps_cv;  /* used to synchronize parallel scan */
 
@@ -557,6 +558,7 @@ btinitparallelscan(void *target)
    bt_target->btps_nextScanPage = InvalidBlockNumber;
    bt_target->btps_lastCurrPage = InvalidBlockNumber;
    bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
+   bt_target->btps_nsearches = 0;
    ConditionVariableInit(&bt_target->btps_cv);
 }
 
@@ -583,6 +585,7 @@ btparallelrescan(IndexScanDesc scan)
    btscan->btps_nextScanPage = InvalidBlockNumber;
    btscan->btps_lastCurrPage = InvalidBlockNumber;
    btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
+   /* deliberately don't reset btps_nsearches (matches index_rescan) */
    SpinLockRelease(&btscan->btps_mutex);
 }
 
@@ -676,6 +679,7 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *next_scan_page,
            {
                /* Can start scheduled primitive scan right away, so do so */
                btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
+               btscan->btps_nsearches++;
                for (int i = 0; i < so->numArrayKeys; i++)
                {
                    BTArrayKeyInfo *array = &so->arrayKeys[i];
@@ -712,6 +716,11 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *next_scan_page,
             */
            btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
            Assert(btscan->btps_nextScanPage != P_NONE);
+           if (btscan->btps_nextScanPage == InvalidBlockNumber)
+           {
+               Assert(first);
+               btscan->btps_nsearches++;
+           }
            *next_scan_page = btscan->btps_nextScanPage;
            *last_curr_page = btscan->btps_lastCurrPage;
            exit_loop = true;
@@ -810,6 +819,12 @@ _bt_parallel_done(IndexScanDesc scan)
        btscan->btps_pageStatus = BTPARALLEL_DONE;
        status_changed = true;
    }
+
+   /*
+    * Don't use local nsearches counter -- overwrite it with the nsearches
+    * counter that we've been maintaining in shared memory
+    */
+   scan->nsearches = btscan->btps_nsearches;
    SpinLockRelease(&btscan->btps_mutex);
 
    /* wake up all the workers associated with this parallel scan */
index 472ce06f1908ee0c8dec4e38470ba971d27d4943..941b4eaaf18dc66550c60f5c740295bba2277f9b 100644 (file)
@@ -950,6 +950,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
     * _bt_search/_bt_endpoint below
     */
    pgstat_count_index_scan(rel);
+   scan->nsearches++;
 
    /*----------
     * Examine the scan keys to discover where we need to start the scan.
index 53f910e9d8987bc7319deff47a4a99ab0470dce6..8554b453555abb478774ded8cd4cb8f740761800 100644 (file)
@@ -421,6 +421,7 @@ spgrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
    /* count an indexscan for stats */
    pgstat_count_index_scan(scan->indexRelation);
+   scan->nsearches++;
 }
 
 void
index d8a7232cedbf2eb0786332548e0e3cc81daed059..78d5d0b67c4a2b5532bc2d2e68c953411da2ad30 100644 (file)
@@ -13,6 +13,7 @@
  */
 #include "postgres.h"
 
+#include "access/relscan.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
 #include "commands/createas.h"
@@ -125,6 +126,7 @@ static void show_recursive_union_info(RecursiveUnionState *rstate,
 static void show_memoize_info(MemoizeState *mstate, List *ancestors,
                              ExplainState *es);
 static void show_hashagg_info(AggState *aggstate, ExplainState *es);
+static void show_indexsearches_info(PlanState *planstate, ExplainState *es);
 static void show_tidbitmap_info(BitmapHeapScanState *planstate,
                                ExplainState *es);
 static void show_instrumentation_count(const char *qlabel, int which,
@@ -2096,6 +2098,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
            if (plan->qual)
                show_instrumentation_count("Rows Removed by Filter", 1,
                                           planstate, es);
+           show_indexsearches_info(planstate, es);
            break;
        case T_IndexOnlyScan:
            show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
@@ -2112,10 +2115,12 @@ ExplainNode(PlanState *planstate, List *ancestors,
            if (es->analyze)
                ExplainPropertyFloat("Heap Fetches", NULL,
                                     planstate->instrument->ntuples2, 0, es);
+           show_indexsearches_info(planstate, es);
            break;
        case T_BitmapIndexScan:
            show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
                           "Index Cond", planstate, ancestors, es);
+           show_indexsearches_info(planstate, es);
            break;
        case T_BitmapHeapScan:
            show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
@@ -3855,6 +3860,41 @@ show_hashagg_info(AggState *aggstate, ExplainState *es)
    }
 }
 
+/*
+ * Show the total number of index searches performed by a
+ * IndexScan/IndexOnlyScan/BitmapIndexScan node
+ */
+static void
+show_indexsearches_info(PlanState *planstate, ExplainState *es)
+{
+   Plan       *plan = planstate->plan;
+   struct IndexScanDescData *scanDesc = NULL;
+   uint64      nsearches = 0;
+
+   if (!es->analyze)
+       return;
+
+   switch (nodeTag(plan))
+   {
+       case T_IndexScan:
+           scanDesc = ((IndexScanState *) planstate)->iss_ScanDesc;
+           break;
+       case T_IndexOnlyScan:
+           scanDesc = ((IndexOnlyScanState *) planstate)->ioss_ScanDesc;
+           break;
+       case T_BitmapIndexScan:
+           scanDesc = ((BitmapIndexScanState *) planstate)->biss_ScanDesc;
+           break;
+       default:
+           break;
+   }
+
+   if (scanDesc)
+       nsearches = scanDesc->nsearches;
+
+   ExplainPropertyUInteger("Index Searches", NULL, nsearches, es);
+}
+
 /*
  * Show exact/lossy pages for a BitmapHeapScan node
  */
index dc6e01842845e799ecf7fdb43e96d5cc13039876..8e0b08b0d944347f137b491c02b8d2701822f8a5 100644 (file)
@@ -150,6 +150,13 @@ typedef struct IndexScanDescData
    /* index access method's private state */
    void       *opaque;         /* access-method-specific info */
 
+   /*
+    * Instrumentation counters that are maintained by every index access
+    * method, for all scan types.  These go here because there is no standard
+    * way to access PlanState.instrument during amgettuple calls.
+    */
+   uint64      nsearches;      /* total # of index searches */
+
    /*
     * In an index-only scan, a successful amgettuple call must fill either
     * xs_itup (and xs_itupdesc) or xs_hitup (and xs_hitupdesc) to provide the
index 991b7eacada21beabfb1a65e7202547cac05dc08..cb5b5e53e867c23a3dffa1d3c732e732f273935a 100644 (file)
@@ -853,7 +853,8 @@ SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date;
    Recheck Cond: (a = '2023-01-01'::date)
    ->  Bitmap Index Scan on brin_date_test_a_idx (actual rows=0.00 loops=1)
          Index Cond: (a = '2023-01-01'::date)
-(4 rows)
+         Index Searches: 1
+(5 rows)
 
 DROP TABLE brin_date_test;
 RESET enable_seqscan;
@@ -872,7 +873,8 @@ SELECT * FROM brin_timestamp_test WHERE a = '2023-01-01'::timestamp;
    Recheck Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone)
    ->  Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0.00 loops=1)
          Index Cond: (a = '2023-01-01 00:00:00'::timestamp without time zone)
-(4 rows)
+         Index Searches: 1
+(5 rows)
 
 EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF, BUFFERS OFF)
 SELECT * FROM brin_timestamp_test WHERE a = '1900-01-01'::timestamp;
@@ -882,7 +884,8 @@ SELECT * FROM brin_timestamp_test WHERE a = '1900-01-01'::timestamp;
    Recheck Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone)
    ->  Bitmap Index Scan on brin_timestamp_test_a_idx (actual rows=0.00 loops=1)
          Index Cond: (a = '1900-01-01 00:00:00'::timestamp without time zone)
-(4 rows)
+         Index Searches: 1
+(5 rows)
 
 DROP TABLE brin_timestamp_test;
 RESET enable_seqscan;
@@ -900,7 +903,8 @@ SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date;
    Recheck Cond: (a = '2023-01-01'::date)
    ->  Bitmap Index Scan on brin_date_test_a_idx (actual rows=0.00 loops=1)
          Index Cond: (a = '2023-01-01'::date)
-(4 rows)
+         Index Searches: 1
+(5 rows)
 
 EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF, BUFFERS OFF)
 SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date;
@@ -910,7 +914,8 @@ SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date;
    Recheck Cond: (a = '1900-01-01'::date)
    ->  Bitmap Index Scan on brin_date_test_a_idx (actual rows=0.00 loops=1)
          Index Cond: (a = '1900-01-01'::date)
-(4 rows)
+         Index Searches: 1
+(5 rows)
 
 DROP TABLE brin_date_test;
 RESET enable_seqscan;
@@ -929,7 +934,8 @@ SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
    Recheck Cond: (a = '@ 30 years ago'::interval)
    ->  Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0.00 loops=1)
          Index Cond: (a = '@ 30 years ago'::interval)
-(4 rows)
+         Index Searches: 1
+(5 rows)
 
 EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF, BUFFERS OFF)
 SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
@@ -939,7 +945,8 @@ SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
    Recheck Cond: (a = '@ 30 years'::interval)
    ->  Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0.00 loops=1)
          Index Cond: (a = '@ 30 years'::interval)
-(4 rows)
+         Index Searches: 1
+(5 rows)
 
 DROP TABLE brin_interval_test;
 RESET enable_seqscan;
@@ -957,7 +964,8 @@ SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
    Recheck Cond: (a = '@ 30 years ago'::interval)
    ->  Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0.00 loops=1)
          Index Cond: (a = '@ 30 years ago'::interval)
-(4 rows)
+         Index Searches: 1
+(5 rows)
 
 EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF, BUFFERS OFF)
 SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
@@ -967,7 +975,8 @@ SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
    Recheck Cond: (a = '@ 30 years'::interval)
    ->  Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0.00 loops=1)
          Index Cond: (a = '@ 30 years'::interval)
-(4 rows)
+         Index Searches: 1
+(5 rows)
 
 DROP TABLE brin_interval_test;
 RESET enable_seqscan;
index 22f2d32845e3a406b602e41881689db526da9190..38dfaf021c91d27b69fa9f33b15c77c660ea5bb5 100644 (file)
@@ -22,8 +22,9 @@ begin
         ln := regexp_replace(ln, 'Evictions: 0', 'Evictions: Zero');
         ln := regexp_replace(ln, 'Evictions: \d+', 'Evictions: N');
         ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N');
-   ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N');
-   ln := regexp_replace(ln, 'loops=\d+', 'loops=N');
+        ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N');
+        ln := regexp_replace(ln, 'loops=\d+', 'loops=N');
+        ln := regexp_replace(ln, 'Index Searches: \d+', 'Index Searches: N');
         return next ln;
     end loop;
 end;
@@ -49,7 +50,8 @@ WHERE t2.unique1 < 1000;', false);
                ->  Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1.00 loops=N)
                      Index Cond: (unique1 = t2.twenty)
                      Heap Fetches: N
-(12 rows)
+                     Index Searches: N
+(13 rows)
 
 -- And check we get the expected results.
 SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
@@ -80,7 +82,8 @@ WHERE t1.unique1 < 1000;', false);
                ->  Index Only Scan using tenk1_unique1 on tenk1 t2 (actual rows=1.00 loops=N)
                      Index Cond: (unique1 = t1.twenty)
                      Heap Fetches: N
-(12 rows)
+                     Index Searches: N
+(13 rows)
 
 -- And check we get the expected results.
 SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
@@ -106,6 +109,7 @@ WHERE t1.unique1 < 10;', false);
    ->  Nested Loop Left Join (actual rows=20.00 loops=N)
          ->  Index Scan using tenk1_unique1 on tenk1 t1 (actual rows=10.00 loops=N)
                Index Cond: (unique1 < 10)
+               Index Searches: N
          ->  Memoize (actual rows=2.00 loops=N)
                Cache Key: t1.two
                Cache Mode: binary
@@ -115,7 +119,8 @@ WHERE t1.unique1 < 10;', false);
                      Rows Removed by Filter: 2
                      ->  Index Scan using tenk1_unique1 on tenk1 t2_1 (actual rows=4.00 loops=N)
                            Index Cond: (unique1 < 4)
-(13 rows)
+                           Index Searches: N
+(15 rows)
 
 -- And check we get the expected results.
 SELECT COUNT(*),AVG(t2.t1two) FROM tenk1 t1 LEFT JOIN
@@ -149,7 +154,8 @@ WHERE s.c1 = s.c2 AND t1.unique1 < 1000;', false);
                      Filter: ((t1.two + 1) = unique1)
                      Rows Removed by Filter: 9999
                      Heap Fetches: N
-(13 rows)
+                     Index Searches: N
+(14 rows)
 
 -- And check we get the expected results.
 SELECT COUNT(*), AVG(t1.twenty) FROM tenk1 t1 LEFT JOIN
@@ -219,7 +225,8 @@ ON t1.x = t2.t::numeric AND t1.t::numeric = t2.x;', false);
                Index Cond: (x = (t1.t)::numeric)
                Filter: (t1.x = (t)::numeric)
                Heap Fetches: N
-(10 rows)
+               Index Searches: N
+(11 rows)
 
 DROP TABLE expr_key;
 -- Reduce work_mem and hash_mem_multiplier so that we see some cache evictions
@@ -246,7 +253,8 @@ WHERE t2.unique1 < 1200;', true);
                ->  Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1.00 loops=N)
                      Index Cond: (unique1 = t2.thousand)
                      Heap Fetches: N
-(12 rows)
+                     Index Searches: N
+(13 rows)
 
 CREATE TABLE flt (f float);
 CREATE INDEX flt_f_idx ON flt (f);
@@ -261,6 +269,7 @@ SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f = f2.f;', false);
  Nested Loop (actual rows=4.00 loops=N)
    ->  Index Only Scan using flt_f_idx on flt f1 (actual rows=2.00 loops=N)
          Heap Fetches: N
+         Index Searches: N
    ->  Memoize (actual rows=2.00 loops=N)
          Cache Key: f1.f
          Cache Mode: logical
@@ -268,7 +277,8 @@ SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f = f2.f;', false);
          ->  Index Only Scan using flt_f_idx on flt f2 (actual rows=2.00 loops=N)
                Index Cond: (f = f1.f)
                Heap Fetches: N
-(10 rows)
+               Index Searches: N
+(12 rows)
 
 -- Ensure memoize operates in binary mode
 SELECT explain_memoize('
@@ -278,6 +288,7 @@ SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f >= f2.f;', false);
  Nested Loop (actual rows=4.00 loops=N)
    ->  Index Only Scan using flt_f_idx on flt f1 (actual rows=2.00 loops=N)
          Heap Fetches: N
+         Index Searches: N
    ->  Memoize (actual rows=2.00 loops=N)
          Cache Key: f1.f
          Cache Mode: binary
@@ -285,7 +296,8 @@ SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f >= f2.f;', false);
          ->  Index Only Scan using flt_f_idx on flt f2 (actual rows=2.00 loops=N)
                Index Cond: (f <= f1.f)
                Heap Fetches: N
-(10 rows)
+               Index Searches: N
+(12 rows)
 
 DROP TABLE flt;
 -- Exercise Memoize in binary mode with a large fixed width type and a
@@ -311,7 +323,8 @@ SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.n >= s2.n;', false);
          Hits: 3  Misses: 3  Evictions: Zero  Overflows: 0  Memory Usage: NkB
          ->  Index Scan using strtest_n_idx on strtest s2 (actual rows=4.00 loops=N)
                Index Cond: (n <= s1.n)
-(9 rows)
+               Index Searches: N
+(10 rows)
 
 -- Ensure we get 3 hits and 3 misses
 SELECT explain_memoize('
@@ -327,7 +340,8 @@ SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.t >= s2.t;', false);
          Hits: 3  Misses: 3  Evictions: Zero  Overflows: 0  Memory Usage: NkB
          ->  Index Scan using strtest_t_idx on strtest s2 (actual rows=4.00 loops=N)
                Index Cond: (t <= s1.t)
-(9 rows)
+               Index Searches: N
+(10 rows)
 
 DROP TABLE strtest;
 -- Ensure memoize works with partitionwise join
@@ -348,6 +362,7 @@ SELECT * FROM prt t1 INNER JOIN prt t2 ON t1.a = t2.a;', false);
    ->  Nested Loop (actual rows=16.00 loops=N)
          ->  Index Only Scan using iprt_p1_a on prt_p1 t1_1 (actual rows=4.00 loops=N)
                Heap Fetches: N
+               Index Searches: N
          ->  Memoize (actual rows=4.00 loops=N)
                Cache Key: t1_1.a
                Cache Mode: logical
@@ -355,9 +370,11 @@ SELECT * FROM prt t1 INNER JOIN prt t2 ON t1.a = t2.a;', false);
                ->  Index Only Scan using iprt_p1_a on prt_p1 t2_1 (actual rows=4.00 loops=N)
                      Index Cond: (a = t1_1.a)
                      Heap Fetches: N
+                     Index Searches: N
    ->  Nested Loop (actual rows=16.00 loops=N)
          ->  Index Only Scan using iprt_p2_a on prt_p2 t1_2 (actual rows=4.00 loops=N)
                Heap Fetches: N
+               Index Searches: N
          ->  Memoize (actual rows=4.00 loops=N)
                Cache Key: t1_2.a
                Cache Mode: logical
@@ -365,7 +382,8 @@ SELECT * FROM prt t1 INNER JOIN prt t2 ON t1.a = t2.a;', false);
                ->  Index Only Scan using iprt_p2_a on prt_p2 t2_2 (actual rows=4.00 loops=N)
                      Index Cond: (a = t1_2.a)
                      Heap Fetches: N
-(21 rows)
+                     Index Searches: N
+(25 rows)
 
 -- Ensure memoize works with parameterized union-all Append path
 SET enable_partitionwise_join TO off;
@@ -378,6 +396,7 @@ ON t1.a = t2.a;', false);
  Nested Loop (actual rows=16.00 loops=N)
    ->  Index Only Scan using iprt_p1_a on prt_p1 t1 (actual rows=4.00 loops=N)
          Heap Fetches: N
+         Index Searches: N
    ->  Memoize (actual rows=4.00 loops=N)
          Cache Key: t1.a
          Cache Mode: logical
@@ -386,10 +405,12 @@ ON t1.a = t2.a;', false);
                ->  Index Only Scan using iprt_p1_a on prt_p1 (actual rows=4.00 loops=N)
                      Index Cond: (a = t1.a)
                      Heap Fetches: N
+                     Index Searches: N
                ->  Index Only Scan using iprt_p2_a on prt_p2 (actual rows=0.00 loops=N)
                      Index Cond: (a = t1.a)
                      Heap Fetches: N
-(14 rows)
+                     Index Searches: N
+(17 rows)
 
 DROP TABLE prt;
 RESET enable_partitionwise_join;
index d95d2395d48fac005167f661c98d6926def1a3ff..34f2b0b8dbdaf5f3edcf7ccb4bb434eb5aafa5c2 100644 (file)
@@ -2369,6 +2369,10 @@ begin
         ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N');
         ln := regexp_replace(ln, 'actual rows=\d+(?:\.\d+)? loops=\d+', 'actual rows=N loops=N');
         ln := regexp_replace(ln, 'Rows Removed by Filter: \d+', 'Rows Removed by Filter: N');
+        perform regexp_matches(ln, 'Index Searches: \d+');
+        if found then
+          continue;
+        end if;
         return next ln;
     end loop;
 end;
@@ -2686,47 +2690,56 @@ select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1
          Filter: (b = (InitPlan 2).col1)
          ->  Bitmap Index Scan on ab_a1_b1_a_idx (never executed)
                Index Cond: (a = (InitPlan 1).col1)
+               Index Searches: 0
    ->  Bitmap Heap Scan on ab_a1_b2 ab_2 (never executed)
          Recheck Cond: (a = (InitPlan 1).col1)
          Filter: (b = (InitPlan 2).col1)
          ->  Bitmap Index Scan on ab_a1_b2_a_idx (never executed)
                Index Cond: (a = (InitPlan 1).col1)
+               Index Searches: 0
    ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (never executed)
          Recheck Cond: (a = (InitPlan 1).col1)
          Filter: (b = (InitPlan 2).col1)
          ->  Bitmap Index Scan on ab_a1_b3_a_idx (never executed)
                Index Cond: (a = (InitPlan 1).col1)
+               Index Searches: 0
    ->  Bitmap Heap Scan on ab_a2_b1 ab_4 (never executed)
          Recheck Cond: (a = (InitPlan 1).col1)
          Filter: (b = (InitPlan 2).col1)
          ->  Bitmap Index Scan on ab_a2_b1_a_idx (never executed)
                Index Cond: (a = (InitPlan 1).col1)
+               Index Searches: 0
    ->  Bitmap Heap Scan on ab_a2_b2 ab_5 (never executed)
          Recheck Cond: (a = (InitPlan 1).col1)
          Filter: (b = (InitPlan 2).col1)
          ->  Bitmap Index Scan on ab_a2_b2_a_idx (never executed)
                Index Cond: (a = (InitPlan 1).col1)
+               Index Searches: 0
    ->  Bitmap Heap Scan on ab_a2_b3 ab_6 (never executed)
          Recheck Cond: (a = (InitPlan 1).col1)
          Filter: (b = (InitPlan 2).col1)
          ->  Bitmap Index Scan on ab_a2_b3_a_idx (never executed)
                Index Cond: (a = (InitPlan 1).col1)
+               Index Searches: 0
    ->  Bitmap Heap Scan on ab_a3_b1 ab_7 (never executed)
          Recheck Cond: (a = (InitPlan 1).col1)
          Filter: (b = (InitPlan 2).col1)
          ->  Bitmap Index Scan on ab_a3_b1_a_idx (never executed)
                Index Cond: (a = (InitPlan 1).col1)
+               Index Searches: 0
    ->  Bitmap Heap Scan on ab_a3_b2 ab_8 (actual rows=0.00 loops=1)
          Recheck Cond: (a = (InitPlan 1).col1)
          Filter: (b = (InitPlan 2).col1)
          ->  Bitmap Index Scan on ab_a3_b2_a_idx (actual rows=0.00 loops=1)
                Index Cond: (a = (InitPlan 1).col1)
+               Index Searches: 1
    ->  Bitmap Heap Scan on ab_a3_b3 ab_9 (never executed)
          Recheck Cond: (a = (InitPlan 1).col1)
          Filter: (b = (InitPlan 2).col1)
          ->  Bitmap Index Scan on ab_a3_b3_a_idx (never executed)
                Index Cond: (a = (InitPlan 1).col1)
-(52 rows)
+               Index Searches: 0
+(61 rows)
 
 -- Test run-time partition pruning with UNION ALL parents
 explain (analyze, costs off, summary off, timing off, buffers off)
@@ -2742,16 +2755,19 @@ select * from (select * from ab where a = 1 union all select * from ab) ab where
                Filter: (b = (InitPlan 1).col1)
                ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0.00 loops=1)
                      Index Cond: (a = 1)
+                     Index Searches: 1
          ->  Bitmap Heap Scan on ab_a1_b2 ab_12 (never executed)
                Recheck Cond: (a = 1)
                Filter: (b = (InitPlan 1).col1)
                ->  Bitmap Index Scan on ab_a1_b2_a_idx (never executed)
                      Index Cond: (a = 1)
+                     Index Searches: 0
          ->  Bitmap Heap Scan on ab_a1_b3 ab_13 (never executed)
                Recheck Cond: (a = 1)
                Filter: (b = (InitPlan 1).col1)
                ->  Bitmap Index Scan on ab_a1_b3_a_idx (never executed)
                      Index Cond: (a = 1)
+                     Index Searches: 0
    ->  Seq Scan on ab_a1_b1 ab_1 (actual rows=0.00 loops=1)
          Filter: (b = (InitPlan 1).col1)
    ->  Seq Scan on ab_a1_b2 ab_2 (never executed)
@@ -2770,7 +2786,7 @@ select * from (select * from ab where a = 1 union all select * from ab) ab where
          Filter: (b = (InitPlan 1).col1)
    ->  Seq Scan on ab_a3_b3 ab_9 (never executed)
          Filter: (b = (InitPlan 1).col1)
-(37 rows)
+(40 rows)
 
 -- A case containing a UNION ALL with a non-partitioned child.
 explain (analyze, costs off, summary off, timing off, buffers off)
@@ -2786,16 +2802,19 @@ select * from (select * from ab where a = 1 union all (values(10,5)) union all s
                Filter: (b = (InitPlan 1).col1)
                ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0.00 loops=1)
                      Index Cond: (a = 1)
+                     Index Searches: 1
          ->  Bitmap Heap Scan on ab_a1_b2 ab_12 (never executed)
                Recheck Cond: (a = 1)
                Filter: (b = (InitPlan 1).col1)
                ->  Bitmap Index Scan on ab_a1_b2_a_idx (never executed)
                      Index Cond: (a = 1)
+                     Index Searches: 0
          ->  Bitmap Heap Scan on ab_a1_b3 ab_13 (never executed)
                Recheck Cond: (a = 1)
                Filter: (b = (InitPlan 1).col1)
                ->  Bitmap Index Scan on ab_a1_b3_a_idx (never executed)
                      Index Cond: (a = 1)
+                     Index Searches: 0
    ->  Result (actual rows=0.00 loops=1)
          One-Time Filter: (5 = (InitPlan 1).col1)
    ->  Seq Scan on ab_a1_b1 ab_1 (actual rows=0.00 loops=1)
@@ -2816,7 +2835,7 @@ select * from (select * from ab where a = 1 union all (values(10,5)) union all s
          Filter: (b = (InitPlan 1).col1)
    ->  Seq Scan on ab_a3_b3 ab_9 (never executed)
          Filter: (b = (InitPlan 1).col1)
-(39 rows)
+(42 rows)
 
 -- Another UNION ALL test, but containing a mix of exec init and exec run-time pruning.
 create table xy_1 (x int, y int);
@@ -2887,16 +2906,19 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;');
                      Recheck Cond: (a = 1)
                      ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0.00 loops=1)
                            Index Cond: (a = 1)
+                           Index Searches: 1
                ->  Bitmap Heap Scan on ab_a1_b2 ab_a1_2 (actual rows=1.00 loops=1)
                      Recheck Cond: (a = 1)
                      Heap Blocks: exact=1
                      ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1.00 loops=1)
                            Index Cond: (a = 1)
+                           Index Searches: 1
                ->  Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0.00 loops=1)
                      Recheck Cond: (a = 1)
                      Heap Blocks: exact=1
                      ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1.00 loops=1)
                            Index Cond: (a = 1)
+                           Index Searches: 1
          ->  Materialize (actual rows=1.00 loops=1)
                Storage: Memory  Maximum Storage: NkB
                ->  Append (actual rows=1.00 loops=1)
@@ -2904,17 +2926,20 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;');
                            Recheck Cond: (a = 1)
                            ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0.00 loops=1)
                                  Index Cond: (a = 1)
+                                 Index Searches: 1
                      ->  Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1.00 loops=1)
                            Recheck Cond: (a = 1)
                            Heap Blocks: exact=1
                            ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1.00 loops=1)
                                  Index Cond: (a = 1)
+                                 Index Searches: 1
                      ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0.00 loops=1)
                            Recheck Cond: (a = 1)
                            Heap Blocks: exact=1
                            ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1.00 loops=1)
                                  Index Cond: (a = 1)
-(37 rows)
+                                 Index Searches: 1
+(43 rows)
 
 table ab;
  a | b 
@@ -2990,17 +3015,23 @@ select * from tbl1 join tprt on tbl1.col1 > tprt.col1;
    ->  Append (actual rows=3.00 loops=2)
          ->  Index Scan using tprt1_idx on tprt_1 (actual rows=2.00 loops=2)
                Index Cond: (col1 < tbl1.col1)
+               Index Searches: 2
          ->  Index Scan using tprt2_idx on tprt_2 (actual rows=2.00 loops=1)
                Index Cond: (col1 < tbl1.col1)
+               Index Searches: 1
          ->  Index Scan using tprt3_idx on tprt_3 (never executed)
                Index Cond: (col1 < tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt4_idx on tprt_4 (never executed)
                Index Cond: (col1 < tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt5_idx on tprt_5 (never executed)
                Index Cond: (col1 < tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt6_idx on tprt_6 (never executed)
                Index Cond: (col1 < tbl1.col1)
-(15 rows)
+               Index Searches: 0
+(21 rows)
 
 explain (analyze, costs off, summary off, timing off, buffers off)
 select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
@@ -3011,17 +3042,23 @@ select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
    ->  Append (actual rows=1.00 loops=2)
          ->  Index Scan using tprt1_idx on tprt_1 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt2_idx on tprt_2 (actual rows=1.00 loops=2)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 2
          ->  Index Scan using tprt3_idx on tprt_3 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt4_idx on tprt_4 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt5_idx on tprt_5 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt6_idx on tprt_6 (never executed)
                Index Cond: (col1 = tbl1.col1)
-(15 rows)
+               Index Searches: 0
+(21 rows)
 
 select tbl1.col1, tprt.col1 from tbl1
 inner join tprt on tbl1.col1 > tprt.col1
@@ -3056,17 +3093,23 @@ select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1;
    ->  Append (actual rows=4.60 loops=5)
          ->  Index Scan using tprt1_idx on tprt_1 (actual rows=2.00 loops=5)
                Index Cond: (col1 < tbl1.col1)
+               Index Searches: 5
          ->  Index Scan using tprt2_idx on tprt_2 (actual rows=2.75 loops=4)
                Index Cond: (col1 < tbl1.col1)
+               Index Searches: 4
          ->  Index Scan using tprt3_idx on tprt_3 (actual rows=1.00 loops=2)
                Index Cond: (col1 < tbl1.col1)
+               Index Searches: 2
          ->  Index Scan using tprt4_idx on tprt_4 (never executed)
                Index Cond: (col1 < tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt5_idx on tprt_5 (never executed)
                Index Cond: (col1 < tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt6_idx on tprt_6 (never executed)
                Index Cond: (col1 < tbl1.col1)
-(15 rows)
+               Index Searches: 0
+(21 rows)
 
 explain (analyze, costs off, summary off, timing off, buffers off)
 select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1;
@@ -3077,17 +3120,23 @@ select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1;
    ->  Append (actual rows=0.60 loops=5)
          ->  Index Scan using tprt1_idx on tprt_1 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt2_idx on tprt_2 (actual rows=1.00 loops=2)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 2
          ->  Index Scan using tprt3_idx on tprt_3 (actual rows=0.33 loops=3)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 3
          ->  Index Scan using tprt4_idx on tprt_4 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt5_idx on tprt_5 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt6_idx on tprt_6 (never executed)
                Index Cond: (col1 = tbl1.col1)
-(15 rows)
+               Index Searches: 0
+(21 rows)
 
 select tbl1.col1, tprt.col1 from tbl1
 inner join tprt on tbl1.col1 > tprt.col1
@@ -3141,17 +3190,23 @@ select * from tbl1 join tprt on tbl1.col1 < tprt.col1;
    ->  Append (actual rows=1.00 loops=1)
          ->  Index Scan using tprt1_idx on tprt_1 (never executed)
                Index Cond: (col1 > tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt2_idx on tprt_2 (never executed)
                Index Cond: (col1 > tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt3_idx on tprt_3 (never executed)
                Index Cond: (col1 > tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt4_idx on tprt_4 (never executed)
                Index Cond: (col1 > tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt5_idx on tprt_5 (never executed)
                Index Cond: (col1 > tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt6_idx on tprt_6 (actual rows=1.00 loops=1)
                Index Cond: (col1 > tbl1.col1)
-(15 rows)
+               Index Searches: 1
+(21 rows)
 
 select tbl1.col1, tprt.col1 from tbl1
 inner join tprt on tbl1.col1 < tprt.col1
@@ -3173,17 +3228,23 @@ select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
    ->  Append (actual rows=0.00 loops=1)
          ->  Index Scan using tprt1_idx on tprt_1 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt2_idx on tprt_2 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt3_idx on tprt_3 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt4_idx on tprt_4 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt5_idx on tprt_5 (never executed)
                Index Cond: (col1 = tbl1.col1)
+               Index Searches: 0
          ->  Index Scan using tprt6_idx on tprt_6 (never executed)
                Index Cond: (col1 = tbl1.col1)
-(15 rows)
+               Index Searches: 0
+(21 rows)
 
 select tbl1.col1, tprt.col1 from tbl1
 inner join tprt on tbl1.col1 = tprt.col1
@@ -3513,10 +3574,12 @@ explain (analyze, costs off, summary off, timing off, buffers off) execute mt_q1
    ->  Index Scan using ma_test_p2_b_idx on ma_test_p2 ma_test_1 (actual rows=1.00 loops=1)
          Filter: ((a >= $1) AND ((a % 10) = 5))
          Rows Removed by Filter: 9
+         Index Searches: 1
    ->  Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_2 (actual rows=1.00 loops=1)
          Filter: ((a >= $1) AND ((a % 10) = 5))
          Rows Removed by Filter: 9
-(9 rows)
+         Index Searches: 1
+(11 rows)
 
 execute mt_q1(15);
  a  
@@ -3534,7 +3597,8 @@ explain (analyze, costs off, summary off, timing off, buffers off) execute mt_q1
    ->  Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_1 (actual rows=1.00 loops=1)
          Filter: ((a >= $1) AND ((a % 10) = 5))
          Rows Removed by Filter: 9
-(6 rows)
+         Index Searches: 1
+(7 rows)
 
 execute mt_q1(25);
  a  
@@ -3582,13 +3646,17 @@ explain (analyze, costs off, summary off, timing off, buffers off) select * from
              ->  Limit (actual rows=1.00 loops=1)
                    ->  Index Scan using ma_test_p2_b_idx on ma_test_p2 (actual rows=1.00 loops=1)
                          Index Cond: (b IS NOT NULL)
+                         Index Searches: 1
    ->  Index Scan using ma_test_p1_b_idx on ma_test_p1 ma_test_1 (never executed)
          Filter: (a >= (InitPlan 2).col1)
+         Index Searches: 0
    ->  Index Scan using ma_test_p2_b_idx on ma_test_p2 ma_test_2 (actual rows=10.00 loops=1)
          Filter: (a >= (InitPlan 2).col1)
+         Index Searches: 1
    ->  Index Scan using ma_test_p3_b_idx on ma_test_p3 ma_test_3 (actual rows=10.00 loops=1)
          Filter: (a >= (InitPlan 2).col1)
-(14 rows)
+         Index Searches: 1
+(18 rows)
 
 reset enable_seqscan;
 reset enable_sort;
@@ -4159,13 +4227,17 @@ select * from rangep where b IN((select 1),(select 2)) order by a;
          Sort Key: rangep_2.a
          ->  Index Scan using rangep_0_to_100_1_a_idx on rangep_0_to_100_1 rangep_2 (actual rows=0.00 loops=1)
                Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1]))
+               Index Searches: 1
          ->  Index Scan using rangep_0_to_100_2_a_idx on rangep_0_to_100_2 rangep_3 (actual rows=0.00 loops=1)
                Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1]))
+               Index Searches: 1
          ->  Index Scan using rangep_0_to_100_3_a_idx on rangep_0_to_100_3 rangep_4 (never executed)
                Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1]))
+               Index Searches: 0
    ->  Index Scan using rangep_100_to_200_a_idx on rangep_100_to_200 rangep_5 (actual rows=0.00 loops=1)
          Filter: (b = ANY (ARRAY[(InitPlan 1).col1, (InitPlan 2).col1]))
-(15 rows)
+         Index Searches: 1
+(19 rows)
 
 reset enable_sort;
 drop table rangep;
index cd79abc35db02729d43f025472ec1ac0a87bc2ce..bab0cc93ff5eced4c2f55c0c66c6a6fd6ff9ebc8 100644 (file)
@@ -764,7 +764,8 @@ select * from onek2 where unique2 = 11 and stringu1 = 'ATAAAA';
  Index Scan using onek2_u2_prtl on onek2 (actual rows=1.00 loops=1)
    Index Cond: (unique2 = 11)
    Filter: (stringu1 = 'ATAAAA'::name)
-(3 rows)
+   Index Searches: 1
+(4 rows)
 
 explain (costs off)
 select unique2 from onek2 where unique2 = 11 and stringu1 = 'ATAAAA';
index d5aab4e56665bb968762e8bbdfd4c8a91c697371..c0d47fa875ad96ccec47ef8d518b2682c0cc2ded 100644 (file)
@@ -23,8 +23,9 @@ begin
         ln := regexp_replace(ln, 'Evictions: 0', 'Evictions: Zero');
         ln := regexp_replace(ln, 'Evictions: \d+', 'Evictions: N');
         ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N');
-   ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N');
-   ln := regexp_replace(ln, 'loops=\d+', 'loops=N');
+        ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N');
+        ln := regexp_replace(ln, 'loops=\d+', 'loops=N');
+        ln := regexp_replace(ln, 'Index Searches: \d+', 'Index Searches: N');
         return next ln;
     end loop;
 end;
index 5f36d589b6b5656ad22cf74e2d42e1f55855658f..4a2c74b089990780e9cf1d57e90a4ad165ab0949 100644 (file)
@@ -588,6 +588,10 @@ begin
         ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N');
         ln := regexp_replace(ln, 'actual rows=\d+(?:\.\d+)? loops=\d+', 'actual rows=N loops=N');
         ln := regexp_replace(ln, 'Rows Removed by Filter: \d+', 'Rows Removed by Filter: N');
+        perform regexp_matches(ln, 'Index Searches: \d+');
+        if found then
+          continue;
+        end if;
         return next ln;
     end loop;
 end;