agg_pushdown_partition_wise.diff
text/x-diff
Filename: agg_pushdown_partition_wise.diff
Type: text/x-diff
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/expected/postgres_fdw.out | 0 | 0 |
| contrib/postgres_fdw/sql/postgres_fdw.sql | 0 | 0 |
| doc/src/sgml/config.sgml | 0 | 0 |
| doc/src/sgml/fdwhandler.sgml | 0 | 0 |
| src/backend/catalog/partition.c | 0 | 0 |
| src/backend/executor/execExpr.c | 0 | 0 |
| src/backend/executor/nodeAgg.c | 0 | 0 |
| src/backend/nodes/copyfuncs.c | 0 | 0 |
| src/backend/nodes/equalfuncs.c | 0 | 0 |
| src/backend/nodes/nodeFuncs.c | 0 | 0 |
| src/backend/nodes/outfuncs.c | 0 | 0 |
| src/backend/nodes/readfuncs.c | 0 | 0 |
| src/backend/optimizer/geqo/geqo_eval.c | 0 | 0 |
| src/backend/optimizer/path/allpaths.c | 0 | 0 |
| src/backend/optimizer/path/costsize.c | 0 | 0 |
| src/backend/optimizer/path/equivclass.c | 0 | 0 |
| src/backend/optimizer/path/indxpath.c | 0 | 0 |
| src/backend/optimizer/path/joinpath.c | 0 | 0 |
| src/backend/optimizer/path/joinrels.c | 0 | 0 |
| src/backend/optimizer/path/tidpath.c | 0 | 0 |
| src/backend/optimizer/plan/createplan.c | 0 | 0 |
| src/backend/optimizer/plan/initsplan.c | 0 | 0 |
| src/backend/optimizer/plan/planagg.c | 0 | 0 |
| src/backend/optimizer/plan/planmain.c | 0 | 0 |
| src/backend/optimizer/plan/planner.c | 0 | 0 |
| src/backend/optimizer/plan/setrefs.c | 0 | 0 |
| src/backend/optimizer/prep/prepunion.c | 0 | 0 |
| src/backend/optimizer/README | 0 | 0 |
| src/backend/optimizer/util/pathnode.c | 0 | 0 |
| src/backend/optimizer/util/placeholder.c | 0 | 0 |
| src/backend/optimizer/util/plancat.c | 0 | 0 |
| src/backend/optimizer/util/relnode.c | 0 | 0 |
| src/backend/optimizer/util/tlist.c | 0 | 0 |
| src/backend/utils/adt/ruleutils.c | 0 | 0 |
| src/backend/utils/adt/selfuncs.c | 0 | 0 |
| src/backend/utils/cache/relcache.c | 0 | 0 |
| src/backend/utils/misc/guc.c | 0 | 0 |
| src/include/catalog/partition.h | 0 | 0 |
| src/include/foreign/fdwapi.h | 0 | 0 |
| src/include/nodes/extensible.h | 0 | 0 |
| src/include/nodes/nodes.h | 0 | 0 |
| src/include/nodes/relation.h | 0 | 0 |
| src/include/optimizer/cost.h | 0 | 0 |
| src/include/optimizer/pathnode.h | 0 | 0 |
| src/include/optimizer/paths.h | 0 | 0 |
| src/include/optimizer/placeholder.h | 0 | 0 |
| src/include/optimizer/planmain.h | 0 | 0 |
| src/include/optimizer/planner.h | 0 | 0 |
| src/include/optimizer/prep.h | 0 | 0 |
| src/include/optimizer/tlist.h | 0 | 0 |
| src/include/utils/selfuncs.h | 0 | 0 |
| src/test/regress/expected/inherit.out | 0 | 0 |
| src/test/regress/expected/sysviews.out | 0 | 0 |
| src/test/regress/parallel_schedule | 0 | 0 |
| src/test/regress/serial_schedule | 0 | 0 |
| src/test/regress/sql/inherit.sql | 0 | 0 |
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
new file mode 100644
index b29549a..418c59a
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
*************** AND ftoptions @> array['fetch_size=60000
*** 7219,7221 ****
--- 7219,7341 ----
(1 row)
ROLLBACK;
+ -- ===================================================================
+ -- test partition-wise-joins
+ -- ===================================================================
+ SET enable_partition_wise_join=on;
+ CREATE TABLE fprt1 (a int, b int, c varchar) PARTITION BY RANGE(a);
+ CREATE TABLE fprt1_p1 (LIKE fprt1);
+ CREATE TABLE fprt1_p2 (LIKE fprt1);
+ INSERT INTO fprt1_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 2) i;
+ INSERT INTO fprt1_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 2) i;
+ CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (0) TO (250)
+ SERVER loopback OPTIONS (table_name 'fprt1_p1', use_remote_estimate 'true');
+ CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (250) TO (500)
+ SERVER loopback OPTIONS (TABLE_NAME 'fprt1_p2');
+ ANALYZE fprt1;
+ ANALYZE fprt1_p1;
+ ANALYZE fprt1_p2;
+ CREATE TABLE fprt2 (a int, b int, c varchar) PARTITION BY RANGE(b);
+ CREATE TABLE fprt2_p1 (LIKE fprt2);
+ CREATE TABLE fprt2_p2 (LIKE fprt2);
+ INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i;
+ INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i;
+ CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (0) TO (250)
+ SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true');
+ CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500)
+ SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true');
+ ANALYZE fprt2;
+ ANALYZE fprt2_p1;
+ ANALYZE fprt2_p2;
+ -- inner join three tables
+ EXPLAIN (COSTS OFF)
+ SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3;
+ QUERY PLAN
+ --------------------------------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t3.c
+ -> Append
+ -> Foreign Scan
+ Relations: ((public.ftprt1_p1 t1) INNER JOIN (public.ftprt2_p1 t2)) INNER JOIN (public.ftprt1_p1 t3)
+ -> Foreign Scan
+ Relations: ((public.ftprt1_p2 t1) INNER JOIN (public.ftprt2_p2 t2)) INNER JOIN (public.ftprt1_p2 t3)
+ (7 rows)
+
+ SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3;
+ a | b | c
+ -----+-----+------
+ 0 | 0 | 0000
+ 150 | 150 | 0003
+ 250 | 250 | 0005
+ 400 | 400 | 0008
+ (4 rows)
+
+ -- left outer join + nullable clasue
+ EXPLAIN (COSTS OFF)
+ SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3;
+ QUERY PLAN
+ -----------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, ftprt2_p1.b, ftprt2_p1.c
+ -> Append
+ -> Foreign Scan
+ Relations: (public.ftprt1_p1 t1) LEFT JOIN (public.ftprt2_p1 fprt2)
+ (5 rows)
+
+ SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3;
+ a | b | c
+ ---+---+------
+ 0 | 0 | 0000
+ 2 | |
+ 4 | |
+ 6 | 6 | 0000
+ 8 | |
+ (5 rows)
+
+ -- with whole-row reference
+ EXPLAIN (COSTS OFF)
+ SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2;
+ QUERY PLAN
+ ---------------------------------------------------------------------------------
+ Sort
+ Sort Key: ((t1.*)::fprt1), ((t2.*)::fprt2)
+ -> Append
+ -> Foreign Scan
+ Relations: (public.ftprt1_p1 t1) INNER JOIN (public.ftprt2_p1 t2)
+ -> Foreign Scan
+ Relations: (public.ftprt1_p2 t1) INNER JOIN (public.ftprt2_p2 t2)
+ (7 rows)
+
+ SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2;
+ t1 | t2
+ ----------------+----------------
+ (0,0,0000) | (0,0,0000)
+ (150,150,0003) | (150,150,0003)
+ (250,250,0005) | (250,250,0005)
+ (400,400,0008) | (400,400,0008)
+ (4 rows)
+
+ -- join with lateral reference
+ EXPLAIN (COSTS OFF)
+ SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2;
+ QUERY PLAN
+ ---------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.a, t1.b
+ -> Append
+ -> Foreign Scan
+ Relations: (public.ftprt1_p1 t1) INNER JOIN (public.ftprt2_p1 t2)
+ -> Foreign Scan
+ Relations: (public.ftprt1_p2 t1) INNER JOIN (public.ftprt2_p2 t2)
+ (7 rows)
+
+ SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2;
+ a | b
+ -----+-----
+ 0 | 0
+ 150 | 150
+ 250 | 250
+ 400 | 400
+ (4 rows)
+
+ RESET enable_partition_wise_join;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
new file mode 100644
index 423eb02..a275f55
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
*************** WHERE ftrelid = 'table30000'::regclass
*** 1709,1711 ****
--- 1709,1764 ----
AND ftoptions @> array['fetch_size=60000'];
ROLLBACK;
+
+ -- ===================================================================
+ -- test partition-wise-joins
+ -- ===================================================================
+ SET enable_partition_wise_join=on;
+
+ CREATE TABLE fprt1 (a int, b int, c varchar) PARTITION BY RANGE(a);
+ CREATE TABLE fprt1_p1 (LIKE fprt1);
+ CREATE TABLE fprt1_p2 (LIKE fprt1);
+ INSERT INTO fprt1_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 2) i;
+ INSERT INTO fprt1_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 2) i;
+ CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (0) TO (250)
+ SERVER loopback OPTIONS (table_name 'fprt1_p1', use_remote_estimate 'true');
+ CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (250) TO (500)
+ SERVER loopback OPTIONS (TABLE_NAME 'fprt1_p2');
+ ANALYZE fprt1;
+ ANALYZE fprt1_p1;
+ ANALYZE fprt1_p2;
+
+ CREATE TABLE fprt2 (a int, b int, c varchar) PARTITION BY RANGE(b);
+ CREATE TABLE fprt2_p1 (LIKE fprt2);
+ CREATE TABLE fprt2_p2 (LIKE fprt2);
+ INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i;
+ INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i;
+ CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (0) TO (250)
+ SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true');
+ CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500)
+ SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true');
+ ANALYZE fprt2;
+ ANALYZE fprt2_p1;
+ ANALYZE fprt2_p2;
+
+ -- inner join three tables
+ EXPLAIN (COSTS OFF)
+ SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3;
+ SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3;
+
+ -- left outer join + nullable clasue
+ EXPLAIN (COSTS OFF)
+ SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3;
+ SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3;
+
+ -- with whole-row reference
+ EXPLAIN (COSTS OFF)
+ SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2;
+ SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2;
+
+ -- join with lateral reference
+ EXPLAIN (COSTS OFF)
+ SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2;
+ SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2;
+
+ RESET enable_partition_wise_join;
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
new file mode 100644
index e02b0c8..c4d9228
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** ANY <replaceable class="parameter">num_s
*** 3643,3648 ****
--- 3643,3667 ----
</listitem>
</varlistentry>
+ <varlistentry id="guc-enable-partition-wise-join" xreflabel="enable_partition_wise_join">
+ <term><varname>enable_partition_wise_join</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>enable_partition_wise_join</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables or disables the query planner's use of partition-wise join
+ plans. When enabled, it spends time in creating paths for joins between
+ partitions and consumes memory to construct expression nodes to be used
+ for those joins, even if partition-wise join does not result in the
+ cheapest path. The time and memory increase exponentially with the
+ number of partitioned tables being joined and they increase linearly
+ with the number of partitions. The default is <literal>off</>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-enable-seqscan" xreflabel="enable_seqscan">
<term><varname>enable_seqscan</varname> (<type>boolean</type>)
<indexterm>
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
new file mode 100644
index dbeaab5..ac8c2fa
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
*************** ShutdownForeignScan(ForeignScanState *no
*** 1270,1275 ****
--- 1270,1295 ----
</para>
</sect2>
+ <sect2 id="fdw-callbacks-reparameterize-paths">
+ <title>FDW Routines For reparameterization of paths</title>
+
+ <para>
+ <programlisting>
+ List *
+ ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
+ RelOptInfo *child_rel);
+ </programlisting>
+ This function is called while converting a path parameterized by the
+ top-most parent of the given child relation <literal>child_rel</> to be
+ parameterized by the child relation. The function is used to reparameterize
+ any paths or translate any expression nodes saved in the given
+ <literal>fdw_private</> member of a <structname>ForeignPath</>. The
+ callback may use <literal>reparameterize_path_by_child</>,
+ <literal>adjust_appendrel_attrs</> or
+ <literal>adjust_appendrel_attrs_multilevel</> as required.
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="fdw-helpers">
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index e0d2665..c44bb0e
*** a/src/backend/catalog/partition.c
--- b/src/backend/catalog/partition.c
*************** static List *generate_partition_qual(Rel
*** 126,140 ****
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
List *datums, bool lower);
! static int32 partition_rbound_cmp(PartitionKey key,
! Datum *datums1, RangeDatumContent *content1, bool lower1,
PartitionRangeBound *b2);
! static int32 partition_rbound_datum_cmp(PartitionKey key,
! Datum *rb_datums, RangeDatumContent *rb_content,
! Datum *tuple_datums);
! static int32 partition_bound_cmp(PartitionKey key,
! PartitionBoundInfo boundinfo,
int offset, void *probe, bool probe_is_bound);
static int partition_bound_bsearch(PartitionKey key,
PartitionBoundInfo boundinfo,
--- 126,141 ----
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
List *datums, bool lower);
! static int32 partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
! Oid *partcollation, Datum *datums1,
! RangeDatumContent *content1, bool lower1,
PartitionRangeBound *b2);
! static int32 partition_rbound_datum_cmp(int partnatts, FmgrInfo *partsupfunc,
! Oid *partcollation, Datum *rb_datums,
! RangeDatumContent *rb_content, Datum *tuple_datums);
! static int32 partition_bound_cmp(int partnatts, FmgrInfo *partsupfunc,
! Oid *partcollation, PartitionBoundInfo boundinfo,
int offset, void *probe, bool probe_is_bound);
static int partition_bound_bsearch(PartitionKey key,
PartitionBoundInfo boundinfo,
*************** RelationBuildPartitionDesc(Relation rel)
*** 592,598 ****
* representation of partition bounds.
*/
bool
! partition_bounds_equal(PartitionKey key,
PartitionBoundInfo b1, PartitionBoundInfo b2)
{
int i;
--- 593,599 ----
* representation of partition bounds.
*/
bool
! partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
PartitionBoundInfo b1, PartitionBoundInfo b2)
{
int i;
*************** partition_bounds_equal(PartitionKey key,
*** 613,619 ****
{
int j;
! for (j = 0; j < key->partnatts; j++)
{
/* For range partitions, the bounds might not be finite. */
if (b1->content != NULL)
--- 614,620 ----
{
int j;
! for (j = 0; j < partnatts; j++)
{
/* For range partitions, the bounds might not be finite. */
if (b1->content != NULL)
*************** partition_bounds_equal(PartitionKey key,
*** 642,649 ****
* context. datumIsEqual() should be simple enough to be safe.
*/
if (!datumIsEqual(b1->datums[i][j], b2->datums[i][j],
! key->parttypbyval[j],
! key->parttyplen[j]))
return false;
}
--- 643,649 ----
* context. datumIsEqual() should be simple enough to be safe.
*/
if (!datumIsEqual(b1->datums[i][j], b2->datums[i][j],
! parttypbyval[j], parttyplen[j]))
return false;
}
*************** partition_bounds_equal(PartitionKey key,
*** 652,658 ****
}
/* There are ndatums+1 indexes in case of range partitions */
! if (key->strategy == PARTITION_STRATEGY_RANGE &&
b1->indexes[i] != b2->indexes[i])
return false;
--- 652,658 ----
}
/* There are ndatums+1 indexes in case of range partitions */
! if (b1->strategy == PARTITION_STRATEGY_RANGE &&
b1->indexes[i] != b2->indexes[i])
return false;
*************** check_new_partition_bound(char *relname,
*** 734,741 ****
* First check if the resulting range would be empty with
* specified lower and upper bounds
*/
! if (partition_rbound_cmp(key, lower->datums, lower->content, true,
! upper) >= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create range partition with empty range"),
--- 734,742 ----
* First check if the resulting range would be empty with
* specified lower and upper bounds
*/
! if (partition_rbound_cmp(key->partnatts, key->partsupfunc,
! key->partcollation, lower->datums,
! lower->content, true, upper) >= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create range partition with empty range"),
*************** qsort_partition_rbound_cmp(const void *a
*** 1865,1871 ****
PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
PartitionKey key = (PartitionKey) arg;
! return partition_rbound_cmp(key, b1->datums, b1->content, b1->lower, b2);
}
/*
--- 1866,1874 ----
PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
PartitionKey key = (PartitionKey) arg;
! return partition_rbound_cmp(key->partnatts, key->partsupfunc,
! key->partcollation, b1->datums, b1->content,
! b1->lower, b2);
}
/*
*************** qsort_partition_rbound_cmp(const void *a
*** 1875,1881 ****
* content1, and lower1) is <=, =, >= the bound specified in *b2
*/
static int32
! partition_rbound_cmp(PartitionKey key,
Datum *datums1, RangeDatumContent *content1, bool lower1,
PartitionRangeBound *b2)
{
--- 1878,1884 ----
* content1, and lower1) is <=, =, >= the bound specified in *b2
*/
static int32
! partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation,
Datum *datums1, RangeDatumContent *content1, bool lower1,
PartitionRangeBound *b2)
{
*************** partition_rbound_cmp(PartitionKey key,
*** 1885,1891 ****
RangeDatumContent *content2 = b2->content;
bool lower2 = b2->lower;
! for (i = 0; i < key->partnatts; i++)
{
/*
* First, handle cases involving infinity, which don't require
--- 1888,1894 ----
RangeDatumContent *content2 = b2->content;
bool lower2 = b2->lower;
! for (i = 0; i < partnatts; i++)
{
/*
* First, handle cases involving infinity, which don't require
*************** partition_rbound_cmp(PartitionKey key,
*** 1905,1912 ****
else if (content2[i] != RANGE_DATUM_FINITE)
return content2[i] == RANGE_DATUM_NEG_INF ? 1 : -1;
! cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
! key->partcollation[i],
datums1[i],
datums2[i]));
if (cmpval != 0)
--- 1908,1915 ----
else if (content2[i] != RANGE_DATUM_FINITE)
return content2[i] == RANGE_DATUM_NEG_INF ? 1 : -1;
! cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
! partcollation[i],
datums1[i],
datums2[i]));
if (cmpval != 0)
*************** partition_rbound_cmp(PartitionKey key,
*** 1932,1951 ****
* rb_lower) <=, =, >= partition key of tuple (tuple_datums)
*/
static int32
! partition_rbound_datum_cmp(PartitionKey key,
! Datum *rb_datums, RangeDatumContent *rb_content,
! Datum *tuple_datums)
{
int i;
int32 cmpval = -1;
! for (i = 0; i < key->partnatts; i++)
{
if (rb_content[i] != RANGE_DATUM_FINITE)
return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
! cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
! key->partcollation[i],
rb_datums[i],
tuple_datums[i]));
if (cmpval != 0)
--- 1935,1954 ----
* rb_lower) <=, =, >= partition key of tuple (tuple_datums)
*/
static int32
! partition_rbound_datum_cmp(int partnatts, FmgrInfo *partsupfunc,
! Oid *partcollation, Datum *rb_datums,
! RangeDatumContent *rb_content, Datum *tuple_datums)
{
int i;
int32 cmpval = -1;
! for (i = 0; i < partnatts; i++)
{
if (rb_content[i] != RANGE_DATUM_FINITE)
return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
! cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
! partcollation[i],
rb_datums[i],
tuple_datums[i]));
if (cmpval != 0)
*************** partition_rbound_datum_cmp(PartitionKey
*** 1962,1978 ****
* specified in *probe.
*/
static int32
! partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
! int offset, void *probe, bool probe_is_bound)
{
Datum *bound_datums = boundinfo->datums[offset];
int32 cmpval = -1;
! switch (key->strategy)
{
case PARTITION_STRATEGY_LIST:
! cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
! key->partcollation[0],
bound_datums[0],
*(Datum *) probe));
break;
--- 1965,1982 ----
* specified in *probe.
*/
static int32
! partition_bound_cmp(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation,
! PartitionBoundInfo boundinfo, int offset, void *probe,
! bool probe_is_bound)
{
Datum *bound_datums = boundinfo->datums[offset];
int32 cmpval = -1;
! switch (boundinfo->strategy)
{
case PARTITION_STRATEGY_LIST:
! cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
! partcollation[0],
bound_datums[0],
*(Datum *) probe));
break;
*************** partition_bound_cmp(PartitionKey key, Pa
*** 1990,2001 ****
*/
bool lower = boundinfo->indexes[offset] < 0;
! cmpval = partition_rbound_cmp(key,
! bound_datums, content, lower,
! (PartitionRangeBound *) probe);
}
else
! cmpval = partition_rbound_datum_cmp(key,
bound_datums, content,
(Datum *) probe);
break;
--- 1994,2007 ----
*/
bool lower = boundinfo->indexes[offset] < 0;
! cmpval = partition_rbound_cmp(partnatts, partsupfunc,
! partcollation, bound_datums,
! content, lower,
! (PartitionRangeBound *) probe);
}
else
! cmpval = partition_rbound_datum_cmp(partnatts, partsupfunc,
! partcollation,
bound_datums, content,
(Datum *) probe);
break;
*************** partition_bound_cmp(PartitionKey key, Pa
*** 2003,2009 ****
default:
elog(ERROR, "unexpected partition strategy: %d",
! (int) key->strategy);
}
return cmpval;
--- 2009,2015 ----
default:
elog(ERROR, "unexpected partition strategy: %d",
! (int) boundinfo->strategy);
}
return cmpval;
*************** partition_bound_bsearch(PartitionKey key
*** 2037,2043 ****
int32 cmpval;
mid = (lo + hi + 1) / 2;
! cmpval = partition_bound_cmp(key, boundinfo, mid, probe,
probe_is_bound);
if (cmpval <= 0)
{
--- 2043,2050 ----
int32 cmpval;
mid = (lo + hi + 1) / 2;
! cmpval = partition_bound_cmp(key->partnatts, key->partsupfunc,
! key->partcollation, boundinfo, mid, probe,
probe_is_bound);
if (cmpval <= 0)
{
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
new file mode 100644
index 5a34a46..717763d
*** a/src/backend/executor/execExpr.c
--- b/src/backend/executor/execExpr.c
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 723,728 ****
--- 723,755 ----
break;
}
+ case T_GroupedVar:
+ /*
+ * GroupedVar is treated as an aggregate if it appears in the
+ * targetlist of Agg node, but as a normal variable elsewhere.
+ */
+ if (parent && (IsA(parent, AggState)))
+ {
+ GroupedVar *gvar = (GroupedVar *) node;
+
+ /*
+ * Currently GroupedVar can only represent partial aggregate.
+ */
+ Assert(gvar->agg_partial != NULL);
+
+ ExecInitExprRec((Expr *) gvar->agg_partial, parent, state,
+ resv, resnull);
+ break;
+ }
+ else
+ {
+ /*
+ * set_plan_refs should have replaced GroupedVar in the
+ * targetlist with an ordinary Var.
+ */
+ elog(ERROR, "parent of GroupedVar is not Agg node");
+ }
+
case T_GroupingFunc:
{
GroupingFunc *grp_node = (GroupingFunc *) node;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
new file mode 100644
index c2b8618..c4cb4c0
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** find_unaggregated_cols_walker(Node *node
*** 1829,1834 ****
--- 1829,1845 ----
/* do not descend into aggregate exprs */
return false;
}
+ if (IsA(node, GroupedVar))
+ {
+ GroupedVar *gvar = (GroupedVar *) node;
+
+ /*
+ * GroupedVar is currently used only for partial aggregation, so treat
+ * it like an Aggref above.
+ */
+ Assert(gvar->agg_partial != NULL);
+ return false;
+ }
return expression_tree_walker(node, find_unaggregated_cols_walker,
(void *) colnos);
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 00a0fed..7d188ea
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyPlaceHolderVar(const PlaceHolderVar
*** 2206,2211 ****
--- 2206,2226 ----
}
/*
+ * _copyGroupedVar
+ */
+ static GroupedVar *
+ _copyGroupedVar(const GroupedVar *from)
+ {
+ GroupedVar *newnode = makeNode(GroupedVar);
+
+ COPY_NODE_FIELD(gvexpr);
+ COPY_NODE_FIELD(agg_partial);
+ COPY_SCALAR_FIELD(gvid);
+
+ return newnode;
+ }
+
+ /*
* _copySpecialJoinInfo
*/
static SpecialJoinInfo *
*************** copyObjectImpl(const void *from)
*** 4984,4989 ****
--- 4999,5007 ----
case T_PlaceHolderVar:
retval = _copyPlaceHolderVar(from);
break;
+ case T_GroupedVar:
+ retval = _copyGroupedVar(from);
+ break;
case T_SpecialJoinInfo:
retval = _copySpecialJoinInfo(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 46573ae..f1dacd5
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalPlaceHolderVar(const PlaceHolderVa
*** 874,879 ****
--- 874,887 ----
}
static bool
+ _equalGroupedVar(const GroupedVar *a, const GroupedVar *b)
+ {
+ COMPARE_SCALAR_FIELD(gvid);
+
+ return true;
+ }
+
+ static bool
_equalSpecialJoinInfo(const SpecialJoinInfo *a, const SpecialJoinInfo *b)
{
COMPARE_BITMAPSET_FIELD(min_lefthand);
*************** equal(const void *a, const void *b)
*** 3148,3153 ****
--- 3156,3164 ----
case T_PlaceHolderVar:
retval = _equalPlaceHolderVar(a, b);
break;
+ case T_GroupedVar:
+ retval = _equalGroupedVar(a, b);
+ break;
case T_SpecialJoinInfo:
retval = _equalSpecialJoinInfo(a, b);
break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index 3e8189c..5c00e55
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** exprType(const Node *expr)
*** 259,264 ****
--- 259,267 ----
case T_PlaceHolderVar:
type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_GroupedVar:
+ type = exprType((Node *) ((const GroupedVar *) expr)->agg_partial);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
*************** exprCollation(const Node *expr)
*** 931,936 ****
--- 934,942 ----
case T_PlaceHolderVar:
coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_GroupedVar:
+ coll = exprCollation((Node *) ((const GroupedVar *) expr)->gvexpr);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
*************** expression_tree_walker(Node *node,
*** 2198,2203 ****
--- 2204,2211 ----
break;
case T_PlaceHolderVar:
return walker(((PlaceHolderVar *) node)->phexpr, context);
+ case T_GroupedVar:
+ return walker(((GroupedVar *) node)->gvexpr, context);
case T_InferenceElem:
return walker(((InferenceElem *) node)->expr, context);
case T_AppendRelInfo:
*************** expression_tree_mutator(Node *node,
*** 2989,2994 ****
--- 2997,3012 ----
return (Node *) newnode;
}
break;
+ case T_GroupedVar:
+ {
+ GroupedVar *gv = (GroupedVar *) node;
+ GroupedVar *newnode;
+
+ FLATCOPY(newnode, gv, GroupedVar);
+ MUTATE(newnode->gvexpr, gv->gvexpr, Expr *);
+ MUTATE(newnode->agg_partial, gv->agg_partial, Aggref *);
+ return (Node *) newnode;
+ }
case T_InferenceElem:
{
InferenceElem *inferenceelemdexpr = (InferenceElem *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 28cef85..4b6ee30
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outPlannerInfo(StringInfo str, const Pl
*** 2186,2191 ****
--- 2186,2192 ----
WRITE_NODE_FIELD(pcinfo_list);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(placeholder_list);
+ WRITE_NODE_FIELD(grouped_var_list);
WRITE_NODE_FIELD(fkey_list);
WRITE_NODE_FIELD(query_pathkeys);
WRITE_NODE_FIELD(group_pathkeys);
*************** _outParamPathInfo(StringInfo str, const
*** 2408,2413 ****
--- 2409,2424 ----
}
static void
+ _outGroupedPathInfo(StringInfo str, const GroupedPathInfo *node)
+ {
+ WRITE_NODE_TYPE("GROUPEDPATHINFO");
+
+ WRITE_NODE_FIELD(target);
+ WRITE_NODE_FIELD(pathlist);
+ WRITE_NODE_FIELD(partial_pathlist);
+ }
+
+ static void
_outRestrictInfo(StringInfo str, const RestrictInfo *node)
{
WRITE_NODE_TYPE("RESTRICTINFO");
*************** _outPlaceHolderVar(StringInfo str, const
*** 2451,2456 ****
--- 2462,2477 ----
}
static void
+ _outGroupedVar(StringInfo str, const GroupedVar *node)
+ {
+ WRITE_NODE_TYPE("GROUPEDVAR");
+
+ WRITE_NODE_FIELD(gvexpr);
+ WRITE_NODE_FIELD(agg_partial);
+ WRITE_UINT_FIELD(gvid);
+ }
+
+ static void
_outSpecialJoinInfo(StringInfo str, const SpecialJoinInfo *node)
{
WRITE_NODE_TYPE("SPECIALJOININFO");
*************** outNode(StringInfo str, const void *obj)
*** 3996,4007 ****
--- 4017,4034 ----
case T_ParamPathInfo:
_outParamPathInfo(str, obj);
break;
+ case T_GroupedPathInfo:
+ _outGroupedPathInfo(str, obj);
+ break;
case T_RestrictInfo:
_outRestrictInfo(str, obj);
break;
case T_PlaceHolderVar:
_outPlaceHolderVar(str, obj);
break;
+ case T_GroupedVar:
+ _outGroupedVar(str, obj);
+ break;
case T_SpecialJoinInfo:
_outSpecialJoinInfo(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index a883220..138f71c
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readVar(void)
*** 522,527 ****
--- 522,542 ----
}
/*
+ * _readGroupedVar
+ */
+ static GroupedVar *
+ _readGroupedVar(void)
+ {
+ READ_LOCALS(GroupedVar);
+
+ READ_NODE_FIELD(gvexpr);
+ READ_NODE_FIELD(agg_partial);
+ READ_UINT_FIELD(gvid);
+
+ READ_DONE();
+ }
+
+ /*
* _readConst
*/
static Const *
*************** parseNodeString(void)
*** 2440,2445 ****
--- 2455,2462 ----
return_value = _readTableFunc();
else if (MATCH("VAR", 3))
return_value = _readVar();
+ else if (MATCH("GROUPEDVAR", 10))
+ return_value = _readGroupedVar();
else if (MATCH("CONST", 5))
return_value = _readConst();
else if (MATCH("PARAM", 5))
diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README
new file mode 100644
index fc0fca4..eee093f
*** a/src/backend/optimizer/README
--- b/src/backend/optimizer/README
*************** be desirable to postpone the Gather stag
*** 1076,1078 ****
--- 1076,1105 ----
plan as possible. Expanding the range of cases in which more work can be
pushed below the Gather (and costing them accurately) is likely to keep us
busy for a long time to come.
+
+ Partition-wise joins
+ --------------------
+ A join between two similarly partitioned tables can be broken down into joins
+ between their matching partitions if there exists an equi-join condition
+ between the partition keys of the joining tables. The equi-join between
+ partition keys implies that all join partners for a given row in one
+ partitioned table must be in the corresponding partition of the other
+ partitioned table. The join partners can not be found in other partitions. This
+ condition allows the join between partitioned tables to be broken into joins
+ between the matching partitions. The resultant join is partitioned in the same
+ way as the joining relations, thus allowing an N-way join between similarly
+ partitioned tables having equi-join condition between their partition keys to
+ be broken down into N-way joins between their matching partitions. This
+ technique of breaking down a join between partition tables into join between
+ their partitions is called partition-wise join. We will use term "partitioned
+ relation" for both partitioned table as well as join between partitioned tables
+ which can use partition-wise join technique.
+
+ Partitioning properties of a partitioned table are stored in
+ PartitionSchemeData structure. Planner maintains a list of canonical partition
+ schemes (distinct PartitionSchemeData objects) so that any two partitioned
+ relations with same partitioning scheme share the same PartitionSchemeData
+ object. This reduces memory consumed by PartitionSchemeData objects and makes
+ it easy to compare the partition schemes of joining relations. RelOptInfos of
+ partitioned relations hold partition key expressions and the RelOptInfos of
+ the partition relations of that relation.
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
new file mode 100644
index b5cab0c..1ad910d
*** a/src/backend/optimizer/geqo/geqo_eval.c
--- b/src/backend/optimizer/geqo/geqo_eval.c
*************** merge_clump(PlannerInfo *root, List *clu
*** 264,271 ****
/* Keep searching if join order is not valid */
if (joinrel)
{
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, joinrel);
/* Find and save the cheapest paths for this joinrel */
set_cheapest(joinrel);
--- 264,279 ----
/* Keep searching if join order is not valid */
if (joinrel)
{
+
+ /*
+ * Create "append" paths for partitioned joins. Do this before
+ * creating GatherPaths so that partial "append" paths in
+ * partitioned joins will be considered.
+ */
+ generate_partition_wise_join_paths(root, joinrel);
+
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, joinrel, false);
/* Find and save the cheapest paths for this joinrel */
set_cheapest(joinrel);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index b93b4fc..83a2c37
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
***************
*** 24,29 ****
--- 24,30 ----
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "foreign/fdwapi.h"
+ #include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#ifdef OPTIMIZER_DEBUG
*************** set_rel_pathlist(PlannerInfo *root, RelO
*** 486,492 ****
* we'll consider gathering partial paths for the parent appendrel.)
*/
if (rel->reloptkind == RELOPT_BASEREL)
! generate_gather_paths(root, rel);
/*
* Allow a plugin to editorialize on the set of Paths for this base
--- 487,496 ----
* we'll consider gathering partial paths for the parent appendrel.)
*/
if (rel->reloptkind == RELOPT_BASEREL)
! {
! generate_gather_paths(root, rel, false);
! generate_gather_paths(root, rel, true);
! }
/*
* Allow a plugin to editorialize on the set of Paths for this base
*************** static void
*** 686,691 ****
--- 690,696 ----
set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
Relids required_outer;
+ Path *seq_path;
/*
* We don't support pushing join clauses into the quals of a seqscan, but
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 694,708 ****
*/
required_outer = rel->lateral_relids;
! /* Consider sequential scan */
! add_path(rel, create_seqscan_path(root, rel, required_outer, 0));
! /* If appropriate, consider parallel sequential scan */
if (rel->consider_parallel && required_outer == NULL)
create_plain_partial_paths(root, rel);
/* Consider index scans */
! create_index_paths(root, rel);
/* Consider TID scans */
create_tidscan_paths(root, rel);
--- 699,726 ----
*/
required_outer = rel->lateral_relids;
! /* Consider sequential scan, both plain and grouped. */
! seq_path = create_seqscan_path(root, rel, required_outer, 0);
! add_path(rel, seq_path, false);
! if (rel->gpi != NULL && required_outer == NULL)
! create_grouped_path(root, rel, seq_path, false, false, AGG_HASHED);
! /* If appropriate, consider parallel sequential scan (plain or grouped) */
if (rel->consider_parallel && required_outer == NULL)
create_plain_partial_paths(root, rel);
/* Consider index scans */
! create_index_paths(root, rel, false);
! if (rel->gpi != NULL)
! {
! /*
! * TODO Instead of calling the whole clause-matching machinery twice
! * (there should be no difference between plain and grouped paths from
! * this point of view), consider returning a separate list of paths
! * usable as grouped ones.
! */
! create_index_paths(root, rel, true);
! }
/* Consider TID scans */
create_tidscan_paths(root, rel);
*************** static void
*** 716,721 ****
--- 734,740 ----
create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel)
{
int parallel_workers;
+ Path *path;
parallel_workers = compute_parallel_worker(rel, rel->pages, -1);
*************** create_plain_partial_paths(PlannerInfo *
*** 724,730 ****
return;
/* Add an unordered partial path based on a parallel sequential scan. */
! add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers));
}
/*
--- 743,850 ----
return;
/* Add an unordered partial path based on a parallel sequential scan. */
! path = create_seqscan_path(root, rel, NULL, parallel_workers);
! add_partial_path(rel, path, false);
!
! /*
! * Do partial aggregation at base relation level if the relation is
! * eligible for it.
! */
! if (rel->gpi != NULL)
! create_grouped_path(root, rel, path, false, true, AGG_HASHED);
! }
!
! /*
! * Apply partial aggregation to a subpath and add the AggPath to the
! * appropriate pathlist.
! *
! * "precheck" tells whether the aggregation path should first be checked using
! * add_path_precheck().
! *
! * If "partial" is true, the resulting path is considered partial in terms of
! * parallel execution.
! *
! * The path we create here shouldn't be parameterized because of supposedly
! * high startup cost of aggregation (whether due to build of hash table for
! * AGG_HASHED strategy or due to explicit sort for AGG_SORTED).
! *
! * XXX IndexPath as an input for AGG_SORTED might seem to be an exception, but
! * aggregation of its output is only beneficial if it's performed by multiple
! * workers, i.e. the resulting path is partial (Besides parallel aggregation,
! * the other use case of aggregation push-down is aggregation performed on
! * remote database, but that has nothing to do with IndexScan). And partial
! * path cannot be parameterized because it's semantically wrong to use it on
! * the inner side of NL join.
! */
! void
! create_grouped_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
! bool precheck, bool partial, AggStrategy aggstrategy)
! {
! List *group_clauses = NIL;
! List *group_exprs = NIL;
! List *agg_exprs = NIL;
! Path *agg_path;
!
! /*
! * If the AggPath should be partial, the subpath must be too, and
! * therefore the subpath is essentially parallel_safe.
! */
! Assert(subpath->parallel_safe || !partial);
!
! /*
! * Grouped path should never be parameterized, so we're not supposed to
! * receive parameterized subpath.
! */
! Assert(subpath->param_info == NULL);
!
! /*
! * Note that "partial" in the following function names refers to 2-stage
! * aggregation, not to parallel processing.
! */
! if (aggstrategy == AGG_HASHED)
! agg_path = (Path *) create_partial_agg_hashed_path(root, subpath,
! true,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! subpath->rows);
! else if (aggstrategy == AGG_SORTED)
! agg_path = (Path *) create_partial_agg_sorted_path(root, subpath,
! true,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! subpath->rows);
! else
! elog(ERROR, "unexpected strategy %d", aggstrategy);
!
! /* Add the grouped path to the list of grouped base paths. */
! if (agg_path != NULL)
! {
! if (precheck)
! {
! List *pathkeys;
!
! /* AGG_HASH is not supposed to generate sorted output. */
! pathkeys = aggstrategy == AGG_SORTED ? subpath->pathkeys : NIL;
!
! if (!partial &&
! !add_path_precheck(rel, agg_path->startup_cost,
! agg_path->total_cost, pathkeys, NULL,
! true))
! return;
!
! if (partial &&
! !add_partial_path_precheck(rel, agg_path->total_cost, pathkeys,
! true))
! return;
! }
!
! if (!partial)
! add_path(rel, (Path *) agg_path, true);
! else
! add_partial_path(rel, (Path *) agg_path, true);
! }
}
/*
*************** set_tablesample_rel_pathlist(PlannerInfo
*** 810,816 ****
path = (Path *) create_material_path(rel, path);
}
! add_path(rel, path);
/* For the moment, at least, there are no other paths to consider */
}
--- 930,936 ----
path = (Path *) create_material_path(rel, path);
}
! add_path(rel, path, false);
/* For the moment, at least, there are no other paths to consider */
}
*************** set_append_rel_size(PlannerInfo *root, R
*** 915,926 ****
childrel = find_base_rel(root, childRTindex);
Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
/*
! * We have to copy the parent's targetlist and quals to the child,
! * with appropriate substitution of variables. However, only the
! * baserestrictinfo quals are needed before we can check for
! * constraint exclusion; so do that first and then check to see if we
! * can disregard this child.
*
* The child rel's targetlist might contain non-Var expressions, which
* means that substitution into the quals could produce opportunities
--- 1035,1100 ----
childrel = find_base_rel(root, childRTindex);
Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (rel->part_scheme)
+ {
+ AttrNumber attno;
+
+ /*
+ * For a partitioned tables, individual partitions can participate
+ * in the pair-wise joins. We need attr_needed data for building
+ * targetlists of joins between partitions.
+ */
+ for (attno = rel->min_attr; attno <= rel->max_attr; attno++)
+ {
+ int index = attno - rel->min_attr;
+ Relids attr_needed = bms_copy(rel->attr_needed[index]);
+
+ /* System attributes do not need translation. */
+ if (attno <= 0)
+ {
+ Assert(rel->min_attr == childrel->min_attr);
+ childrel->attr_needed[index] = attr_needed;
+ }
+ else
+ {
+ Var *var = list_nth(appinfo->translated_vars,
+ attno - 1);
+ int child_index;
+
+ /*
+ * Parent Var for a user defined attribute translates to
+ * child Var.
+ */
+ Assert(IsA(var, Var));
+
+ child_index = var->varattno - childrel->min_attr;
+ childrel->attr_needed[child_index] = attr_needed;
+ }
+ }
+ }
+
/*
! * Copy/Modify targetlist. Even if this child is deemed empty, we need
! * its targetlist in case it falls on nullable side in a child-join
! * because of partition-wise join.
! *
! * NB: the resulting childrel->reltarget->exprs may contain arbitrary
! * expressions, which otherwise would not occur in a rel's targetlist.
! * Code that might be looking at an appendrel child must cope with
! * such. (Normally, a rel's targetlist would only include Vars and
! * PlaceHolderVars.) XXX we do not bother to update the cost or width
! * fields of childrel->reltarget; not clear if that would be useful.
! */
! childrel->reltarget->exprs = (List *)
! adjust_appendrel_attrs(root,
! (Node *) rel->reltarget->exprs,
! 1, &appinfo);
!
! /*
! * We have to copy the parent's quals to the child, with appropriate
! * substitution of variables. However, only the baserestrictinfo quals
! * are needed before we can check for constraint exclusion; so do that
! * first and then check to see if we can disregard this child.
*
* The child rel's targetlist might contain non-Var expressions, which
* means that substitution into the quals could produce opportunities
*************** set_append_rel_size(PlannerInfo *root, R
*** 941,947 ****
Assert(IsA(rinfo, RestrictInfo));
childqual = adjust_appendrel_attrs(root,
(Node *) rinfo->clause,
! appinfo);
childqual = eval_const_expressions(root, childqual);
/* check for flat-out constant */
if (childqual && IsA(childqual, Const))
--- 1115,1121 ----
Assert(IsA(rinfo, RestrictInfo));
childqual = adjust_appendrel_attrs(root,
(Node *) rinfo->clause,
! 1, &appinfo);
childqual = eval_const_expressions(root, childqual);
/* check for flat-out constant */
if (childqual && IsA(childqual, Const))
*************** set_append_rel_size(PlannerInfo *root, R
*** 1047,1070 ****
continue;
}
! /*
! * CE failed, so finish copying/modifying targetlist and join quals.
! *
! * NB: the resulting childrel->reltarget->exprs may contain arbitrary
! * expressions, which otherwise would not occur in a rel's targetlist.
! * Code that might be looking at an appendrel child must cope with
! * such. (Normally, a rel's targetlist would only include Vars and
! * PlaceHolderVars.) XXX we do not bother to update the cost or width
! * fields of childrel->reltarget; not clear if that would be useful.
! */
childrel->joininfo = (List *)
adjust_appendrel_attrs(root,
(Node *) rel->joininfo,
! appinfo);
! childrel->reltarget->exprs = (List *)
! adjust_appendrel_attrs(root,
! (Node *) rel->reltarget->exprs,
! appinfo);
/*
* We have to make child entries in the EquivalenceClass data
--- 1221,1231 ----
continue;
}
! /* CE failed, so finish copying/modifying join quals. */
childrel->joininfo = (List *)
adjust_appendrel_attrs(root,
(Node *) rel->joininfo,
! 1, &appinfo);
/*
* We have to make child entries in the EquivalenceClass data
*************** set_append_rel_size(PlannerInfo *root, R
*** 1079,1092 ****
childrel->has_eclass_joins = rel->has_eclass_joins;
/*
- * Note: we could compute appropriate attr_needed data for the child's
- * variables, by transforming the parent's attr_needed through the
- * translated_vars mapping. However, currently there's no need
- * because attr_needed is only examined for base relations not
- * otherrels. So we just leave the child's attr_needed empty.
- */
-
- /*
* If parallelism is allowable for this query in general, see whether
* it's allowable for this childrel in particular. But if we've
* already decided the appendrel is not parallel-safe as a whole,
--- 1240,1245 ----
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1281,1299 ****
bool subpaths_valid = true;
List *partial_subpaths = NIL;
bool partial_subpaths_valid = true;
List *all_child_pathkeys = NIL;
List *all_child_outers = NIL;
ListCell *l;
List *partitioned_rels = NIL;
RangeTblEntry *rte;
! rte = planner_rt_fetch(rel->relid, root);
! if (rte->relkind == RELKIND_PARTITIONED_TABLE)
{
! partitioned_rels = get_partitioned_child_rels(root, rel->relid);
! /* The root partitioned table is included as a child rel */
! Assert(list_length(partitioned_rels) >= 1);
}
/*
* For every non-dummy child, remember the cheapest path. Also, identify
--- 1434,1460 ----
bool subpaths_valid = true;
List *partial_subpaths = NIL;
bool partial_subpaths_valid = true;
+ List *grouped_subpaths = NIL;
+ bool grouped_subpaths_valid = true;
List *all_child_pathkeys = NIL;
List *all_child_outers = NIL;
ListCell *l;
List *partitioned_rels = NIL;
RangeTblEntry *rte;
! if (rel->reloptkind == RELOPT_BASEREL)
{
! rte = planner_rt_fetch(rel->relid, root);
!
! if (rte->relkind == RELKIND_PARTITIONED_TABLE)
! {
! partitioned_rels = get_partitioned_child_rels(root, rel->relid);
! /* The root partitioned table is included as a child rel */
! Assert(list_length(partitioned_rels) >= 1);
! }
}
+ else if (rel->reloptkind == RELOPT_JOINREL && rel->part_scheme)
+ partitioned_rels = get_partitioned_child_rels_for_join(root, rel);
/*
* For every non-dummy child, remember the cheapest path. Also, identify
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1324,1329 ****
--- 1485,1521 ----
partial_subpaths_valid = false;
/*
+ * For grouped paths, use only the unparameterized subpaths.
+ *
+ * XXX Consider if the parameterized subpaths should be processed
+ * below. It's probably not useful for sequential scans (due to
+ * repeated aggregation), but might be worthwhile for other child
+ * nodes.
+ */
+ if (childrel->gpi != NULL && childrel->gpi->pathlist != NIL)
+ {
+ Path *path;
+
+ path = (Path *) linitial(childrel->gpi->pathlist);
+
+ /*
+ * PoC only: Simulate remote aggregation, which seems to be the
+ * typical use case for pushing the aggregation below Append node.
+ */
+ path->startup_cost = 0.0;
+ path->total_cost = 0.0;
+
+ if (path->param_info == NULL)
+ grouped_subpaths = accumulate_append_subpath(grouped_subpaths,
+ path);
+ else
+ grouped_subpaths_valid = false;
+ }
+ else
+ grouped_subpaths_valid = false;
+
+
+ /*
* Collect lists of all the available path orderings and
* parameterizations for all the children. We use these as a
* heuristic to indicate which sort orderings and parameterizations we
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1395,1401 ****
*/
if (subpaths_valid)
add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0,
! partitioned_rels));
/*
* Consider an append of partial unordered, unparameterized partial paths.
--- 1587,1594 ----
*/
if (subpaths_valid)
add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0,
! partitioned_rels),
! false);
/*
* Consider an append of partial unordered, unparameterized partial paths.
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1422,1429 ****
/* Generate a partial append path. */
appendpath = create_append_path(rel, partial_subpaths, NULL,
! parallel_workers, partitioned_rels);
! add_partial_path(rel, (Path *) appendpath);
}
/*
--- 1615,1635 ----
/* Generate a partial append path. */
appendpath = create_append_path(rel, partial_subpaths, NULL,
! parallel_workers,
! partitioned_rels);
! add_partial_path(rel, (Path *) appendpath, false);
! }
!
! /* TODO Also partial grouped paths? */
! if (grouped_subpaths_valid)
! {
! Path *path;
!
! path = (Path *) create_append_path(rel, grouped_subpaths, NULL, 0,
! partitioned_rels);
! /* pathtarget will produce the grouped relation.. */
! path->pathtarget = rel->gpi->target;
! add_path(rel, path, true);
}
/*
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1476,1482 ****
if (subpaths_valid)
add_path(rel, (Path *)
create_append_path(rel, subpaths, required_outer, 0,
! partitioned_rels));
}
}
--- 1682,1689 ----
if (subpaths_valid)
add_path(rel, (Path *)
create_append_path(rel, subpaths, required_outer, 0,
! partitioned_rels),
! false);
}
}
*************** generate_mergeappend_paths(PlannerInfo *
*** 1572,1585 ****
startup_subpaths,
pathkeys,
NULL,
! partitioned_rels));
if (startup_neq_total)
add_path(rel, (Path *) create_merge_append_path(root,
rel,
total_subpaths,
pathkeys,
NULL,
! partitioned_rels));
}
}
--- 1779,1794 ----
startup_subpaths,
pathkeys,
NULL,
! partitioned_rels),
! false);
if (startup_neq_total)
add_path(rel, (Path *) create_merge_append_path(root,
rel,
total_subpaths,
pathkeys,
NULL,
! partitioned_rels),
! false);
}
}
*************** set_dummy_rel_pathlist(RelOptInfo *rel)
*** 1712,1718 ****
rel->pathlist = NIL;
rel->partial_pathlist = NIL;
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL));
/*
* We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
--- 1921,1927 ----
rel->pathlist = NIL;
rel->partial_pathlist = NIL;
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL), false);
/*
* We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
*************** set_subquery_pathlist(PlannerInfo *root,
*** 1926,1932 ****
/* Generate outer path using this subpath */
add_path(rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
! pathkeys, required_outer));
}
}
--- 2135,2141 ----
/* Generate outer path using this subpath */
add_path(rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
! pathkeys, required_outer), false);
}
}
*************** set_function_pathlist(PlannerInfo *root,
*** 1995,2001 ****
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel,
! pathkeys, required_outer));
}
/*
--- 2204,2210 ----
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel,
! pathkeys, required_outer), false);
}
/*
*************** set_values_pathlist(PlannerInfo *root, R
*** 2015,2021 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_valuesscan_path(root, rel, required_outer));
}
/*
--- 2224,2230 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_valuesscan_path(root, rel, required_outer), false);
}
/*
*************** set_tablefunc_pathlist(PlannerInfo *root
*** 2036,2042 ****
/* Generate appropriate path */
add_path(rel, create_tablefuncscan_path(root, rel,
! required_outer));
}
/*
--- 2245,2251 ----
/* Generate appropriate path */
add_path(rel, create_tablefuncscan_path(root, rel,
! required_outer), false);
}
/*
*************** set_cte_pathlist(PlannerInfo *root, RelO
*** 2102,2108 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_ctescan_path(root, rel, required_outer));
}
/*
--- 2311,2317 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_ctescan_path(root, rel, required_outer), false);
}
/*
*************** set_namedtuplestore_pathlist(PlannerInfo
*** 2129,2135 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_namedtuplestorescan_path(root, rel, required_outer));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
--- 2338,2345 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_namedtuplestorescan_path(root, rel, required_outer),
! false);
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
*************** set_worktable_pathlist(PlannerInfo *root
*** 2182,2188 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_worktablescan_path(root, rel, required_outer));
}
/*
--- 2392,2399 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_worktablescan_path(root, rel, required_outer),
! false);
}
/*
*************** set_worktable_pathlist(PlannerInfo *root
*** 2195,2208 ****
* path that some GatherPath or GatherMergePath has a reference to.)
*/
void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
{
Path *cheapest_partial_path;
Path *simple_gather_path;
ListCell *lc;
/* If there are no partial paths, there's nothing to do here. */
! if (rel->partial_pathlist == NIL)
return;
/*
--- 2406,2426 ----
* path that some GatherPath or GatherMergePath has a reference to.)
*/
void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool grouped)
{
Path *cheapest_partial_path;
Path *simple_gather_path;
+ List *pathlist = NIL;
+ PathTarget *partial_target;
ListCell *lc;
+ if (!grouped)
+ pathlist = rel->partial_pathlist;
+ else if (rel->gpi != NULL)
+ pathlist = rel->gpi->partial_pathlist;
+
/* If there are no partial paths, there's nothing to do here. */
! if (pathlist == NIL)
return;
/*
*************** generate_gather_paths(PlannerInfo *root,
*** 2210,2226 ****
* path of interest: the cheapest one. That will be the one at the front
* of partial_pathlist because of the way add_partial_path works.
*/
! cheapest_partial_path = linitial(rel->partial_pathlist);
simple_gather_path = (Path *)
! create_gather_path(root, rel, cheapest_partial_path, rel->reltarget,
NULL, NULL);
! add_path(rel, simple_gather_path);
/*
* For each useful ordering, we can consider an order-preserving Gather
* Merge.
*/
! foreach (lc, rel->partial_pathlist)
{
Path *subpath = (Path *) lfirst(lc);
GatherMergePath *path;
--- 2428,2450 ----
* path of interest: the cheapest one. That will be the one at the front
* of partial_pathlist because of the way add_partial_path works.
*/
! cheapest_partial_path = linitial(pathlist);
!
! if (!grouped)
! partial_target = rel->reltarget;
! else if (rel->gpi != NULL)
! partial_target = rel->gpi->target;
!
simple_gather_path = (Path *)
! create_gather_path(root, rel, cheapest_partial_path, partial_target,
NULL, NULL);
! add_path(rel, simple_gather_path, grouped);
/*
* For each useful ordering, we can consider an order-preserving Gather
* Merge.
*/
! foreach (lc, pathlist)
{
Path *subpath = (Path *) lfirst(lc);
GatherMergePath *path;
*************** generate_gather_paths(PlannerInfo *root,
*** 2228,2236 ****
if (subpath->pathkeys == NIL)
continue;
! path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
subpath->pathkeys, NULL, NULL);
! add_path(rel, &path->path);
}
}
--- 2452,2460 ----
if (subpath->pathkeys == NIL)
continue;
! path = create_gather_merge_path(root, rel, subpath, partial_target,
subpath->pathkeys, NULL, NULL);
! add_path(rel, &path->path, grouped);
}
}
*************** standard_join_search(PlannerInfo *root,
*** 2388,2402 ****
* Run generate_gather_paths() for each just-processed joinrel. We
* could not do this earlier because both regular and partial paths
* can get added to a particular joinrel at multiple times within
! * join_search_one_level. After that, we're done creating paths for
! * the joinrel, so run set_cheapest().
*/
foreach(lc, root->join_rel_level[lev])
{
rel = (RelOptInfo *) lfirst(lc);
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, rel);
/* Find and save the cheapest paths for this rel */
set_cheapest(rel);
--- 2612,2641 ----
* Run generate_gather_paths() for each just-processed joinrel. We
* could not do this earlier because both regular and partial paths
* can get added to a particular joinrel at multiple times within
! * join_search_one_level.
! *
! * Similarly, create paths for joinrels which used partition-wise join
! * technique. We could not do this earlier because paths can get added
! * to a particular child-join at multiple times within
! * join_search_one_level.
! *
! * After that, we're done creating paths for the joinrel, so run
! * set_cheapest().
*/
foreach(lc, root->join_rel_level[lev])
{
rel = (RelOptInfo *) lfirst(lc);
+ /*
+ * Create paths for partition-wise joins. Do this before creating
+ * GatherPaths so that partial "append" paths in partitioned joins
+ * will be considered.
+ */
+ generate_partition_wise_join_paths(root, rel);
+
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, rel, false);
! generate_gather_paths(root, rel, true);
/* Find and save the cheapest paths for this rel */
set_cheapest(rel);
*************** create_partial_bitmap_paths(PlannerInfo
*** 3047,3053 ****
return;
add_partial_path(rel, (Path *) create_bitmap_heap_path(root, rel,
! bitmapqual, rel->lateral_relids, 1.0, parallel_workers));
}
/*
--- 3286,3292 ----
return;
add_partial_path(rel, (Path *) create_bitmap_heap_path(root, rel,
! bitmapqual, rel->lateral_relids, 1.0, parallel_workers), false);
}
/*
*************** compute_parallel_worker(RelOptInfo *rel,
*** 3142,3147 ****
--- 3381,3454 ----
return parallel_workers;
}
+ /*
+ * generate_partition_wise_join_paths
+ *
+ * Create paths representing partition-wise join for given partitioned
+ * join relation.
+ *
+ * This must not be called until after we are done adding paths for all
+ * child-joins. (Otherwise, add_path might delete a path that some "append"
+ * path has reference to.
+ */
+ void
+ generate_partition_wise_join_paths(PlannerInfo *root, RelOptInfo *rel)
+ {
+ List *live_children = NIL;
+ int cnt_parts;
+ int num_parts;
+ RelOptInfo **part_rels;
+
+ /* Handle only join relations. */
+ if (!IS_JOIN_REL(rel))
+ return;
+
+ /* If the relation is not partitioned or is proven dummy, nothing to do. */
+ if (!rel->part_scheme || !rel->boundinfo || IS_DUMMY_REL(rel))
+ return;
+
+ /* A partitioned join should have RelOptInfos of the child-joins. */
+ Assert(rel->part_rels && rel->nparts > 0);
+
+ /* Guard against stack overflow due to overly deep partition hierarchy. */
+ check_stack_depth();
+
+ num_parts = rel->nparts;
+ part_rels = rel->part_rels;
+
+ /* Collect non-dummy child-joins. */
+ for (cnt_parts = 0; cnt_parts < num_parts; cnt_parts++)
+ {
+ RelOptInfo *child_rel = part_rels[cnt_parts];
+
+ /* Add partition-wise join paths for partitioned child-joins. */
+ generate_partition_wise_join_paths(root, child_rel);
+
+ /* Dummy children will not be scanned, so ingore those. */
+ if (IS_DUMMY_REL(child_rel))
+ continue;
+
+ set_cheapest(child_rel);
+
+ #ifdef OPTIMIZER_DEBUG
+ debug_print_rel(root, rel);
+ #endif
+
+ live_children = lappend(live_children, child_rel);
+ }
+
+ /* If all child-joins are dummy, parent join is also dummy. */
+ if (!live_children)
+ {
+ mark_dummy_rel(rel);
+ return;
+ }
+
+ /* Add "append" paths containing paths from child-joins. */
+ add_paths_to_append_rel(root, rel, live_children);
+ list_free(live_children);
+ }
+
/*****************************************************************************
* DEBUG SUPPORT
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
new file mode 100644
index 52643d0..f278b77
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** bool enable_material = true;
*** 127,132 ****
--- 127,133 ----
bool enable_mergejoin = true;
bool enable_hashjoin = true;
bool enable_gathermerge = true;
+ bool enable_partition_wise_join = false;
typedef struct
{
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
new file mode 100644
index 67bd760..780ea04
*** a/src/backend/optimizer/path/equivclass.c
--- b/src/backend/optimizer/path/equivclass.c
*************** generate_join_implied_equalities_broken(
*** 1329,1335 ****
if (IS_OTHER_REL(inner_rel) && result != NIL)
result = (List *) adjust_appendrel_attrs_multilevel(root,
(Node *) result,
! inner_rel);
return result;
}
--- 1329,1336 ----
if (IS_OTHER_REL(inner_rel) && result != NIL)
result = (List *) adjust_appendrel_attrs_multilevel(root,
(Node *) result,
! inner_rel->relids,
! inner_rel->top_parent_relids);
return result;
}
*************** add_child_rel_equivalences(PlannerInfo *
*** 2112,2118 ****
child_expr = (Expr *)
adjust_appendrel_attrs(root,
(Node *) cur_em->em_expr,
! appinfo);
/*
* Transform em_relids to match. Note we do *not* do
--- 2113,2119 ----
child_expr = (Expr *)
adjust_appendrel_attrs(root,
(Node *) cur_em->em_expr,
! 1, &appinfo);
/*
* Transform em_relids to match. Note we do *not* do
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index 6e4bae8..a6fa713
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
***************
*** 32,37 ****
--- 32,38 ----
#include "optimizer/predtest.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+ #include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "utils/builtins.h"
#include "utils/bytea.h"
*************** static bool eclass_already_used(Equivale
*** 107,119 ****
static bool bms_equal_any(Relids relids, List *relids_list);
static void get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
! List **bitindexpaths);
static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop,
! bool *skip_lower_saop);
static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
List *clauses, List *other_clauses);
static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
--- 108,121 ----
static bool bms_equal_any(Relids relids, List *relids_list);
static void get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
! List **bitindexpaths, bool grouped);
static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop,
! bool *skip_lower_saop,
! bool grouped);
static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
List *clauses, List *other_clauses);
static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
*************** static Const *string_to_const(const char
*** 229,235 ****
* as meaning "unparameterized so far as the indexquals are concerned".
*/
void
! create_index_paths(PlannerInfo *root, RelOptInfo *rel)
{
List *indexpaths;
List *bitindexpaths;
--- 231,237 ----
* as meaning "unparameterized so far as the indexquals are concerned".
*/
void
! create_index_paths(PlannerInfo *root, RelOptInfo *rel, bool grouped)
{
List *indexpaths;
List *bitindexpaths;
*************** create_index_paths(PlannerInfo *root, Re
*** 274,281 ****
* non-parameterized paths. Plain paths go directly to add_path(),
* bitmap paths are added to bitindexpaths to be handled below.
*/
! get_index_paths(root, rel, index, &rclauseset,
! &bitindexpaths);
/*
* Identify the join clauses that can match the index. For the moment
--- 276,283 ----
* non-parameterized paths. Plain paths go directly to add_path(),
* bitmap paths are added to bitindexpaths to be handled below.
*/
! get_index_paths(root, rel, index, &rclauseset, &bitindexpaths,
! grouped);
/*
* Identify the join clauses that can match the index. For the moment
*************** create_index_paths(PlannerInfo *root, Re
*** 338,344 ****
bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
rel->lateral_relids, 1.0, 0);
! add_path(rel, (Path *) bpath);
/* create a partial bitmap heap path */
if (rel->consider_parallel && rel->lateral_relids == NULL)
--- 340,346 ----
bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
rel->lateral_relids, 1.0, 0);
! add_path(rel, (Path *) bpath, false);
/* create a partial bitmap heap path */
if (rel->consider_parallel && rel->lateral_relids == NULL)
*************** create_index_paths(PlannerInfo *root, Re
*** 415,421 ****
loop_count = get_loop_count(root, rel->relid, required_outer);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
required_outer, loop_count, 0);
! add_path(rel, (Path *) bpath);
}
}
}
--- 417,423 ----
loop_count = get_loop_count(root, rel->relid, required_outer);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
required_outer, loop_count, 0);
! add_path(rel, (Path *) bpath, false);
}
}
}
*************** get_join_index_paths(PlannerInfo *root,
*** 667,673 ****
Assert(clauseset.nonempty);
/* Build index path(s) using the collected set of clauses */
! get_index_paths(root, rel, index, &clauseset, bitindexpaths);
/*
* Remember we considered paths for this set of relids. We use lcons not
--- 669,675 ----
Assert(clauseset.nonempty);
/* Build index path(s) using the collected set of clauses */
! get_index_paths(root, rel, index, &clauseset, bitindexpaths, false);
/*
* Remember we considered paths for this set of relids. We use lcons not
*************** bms_equal_any(Relids relids, List *relid
*** 736,742 ****
static void
get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
! List **bitindexpaths)
{
List *indexpaths;
bool skip_nonnative_saop = false;
--- 738,744 ----
static void
get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
! List **bitindexpaths, bool grouped)
{
List *indexpaths;
bool skip_nonnative_saop = false;
*************** get_index_paths(PlannerInfo *root, RelOp
*** 754,760 ****
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! &skip_lower_saop);
/*
* If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
--- 756,762 ----
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! &skip_lower_saop, grouped);
/*
* If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
*************** get_index_paths(PlannerInfo *root, RelOp
*** 769,775 ****
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! NULL));
}
/*
--- 771,777 ----
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! NULL, grouped));
}
/*
*************** get_index_paths(PlannerInfo *root, RelOp
*** 789,797 ****
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
! add_path(rel, (Path *) ipath);
! if (index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
ipath->indexselectivity < 1.0))
*bitindexpaths = lappend(*bitindexpaths, ipath);
--- 791,799 ----
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
! add_path(rel, (Path *) ipath, grouped);
! if (!grouped && index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
ipath->indexselectivity < 1.0))
*bitindexpaths = lappend(*bitindexpaths, ipath);
*************** get_index_paths(PlannerInfo *root, RelOp
*** 802,815 ****
* natively, generate bitmap scan paths relying on executor-managed
* ScalarArrayOpExpr.
*/
! if (skip_nonnative_saop)
{
indexpaths = build_index_paths(root, rel,
index, clauses,
false,
ST_BITMAPSCAN,
NULL,
! NULL);
*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
}
}
--- 804,818 ----
* natively, generate bitmap scan paths relying on executor-managed
* ScalarArrayOpExpr.
*/
! if (!grouped && skip_nonnative_saop)
{
indexpaths = build_index_paths(root, rel,
index, clauses,
false,
ST_BITMAPSCAN,
NULL,
! NULL,
! false);
*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
}
}
*************** build_index_paths(PlannerInfo *root, Rel
*** 861,867 ****
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop,
! bool *skip_lower_saop)
{
List *result = NIL;
IndexPath *ipath;
--- 864,870 ----
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop,
! bool *skip_lower_saop, bool grouped)
{
List *result = NIL;
IndexPath *ipath;
*************** build_index_paths(PlannerInfo *root, Rel
*** 878,883 ****
--- 881,890 ----
bool index_is_ordered;
bool index_only_scan;
int indexcol;
+ bool can_agg_sorted;
+ List *group_clauses, *group_exprs, *agg_exprs;
+ AggPath *agg_path;
+ double agg_input_rows;
/*
* Check that index supports the desired scan type(s)
*************** build_index_paths(PlannerInfo *root, Rel
*** 891,896 ****
--- 898,906 ----
case ST_BITMAPSCAN:
if (!index->amhasgetbitmap)
return NIL;
+
+ if (grouped)
+ return NIL;
break;
case ST_ANYSCAN:
/* either or both are OK */
*************** build_index_paths(PlannerInfo *root, Rel
*** 1032,1037 ****
--- 1042,1051 ----
* later merging or final output ordering, OR the index has a useful
* predicate, OR an index-only scan is possible.
*/
+ can_agg_sorted = true;
+ group_clauses = NIL;
+ group_exprs = NIL;
+ agg_exprs = NIL;
if (index_clauses != NIL || useful_pathkeys != NIL || useful_predicate ||
index_only_scan)
{
*************** build_index_paths(PlannerInfo *root, Rel
*** 1048,1054 ****
outer_relids,
loop_count,
false);
! result = lappend(result, ipath);
/*
* If appropriate, consider parallel index scan. We don't allow
--- 1062,1086 ----
outer_relids,
loop_count,
false);
! if (!grouped)
! result = lappend(result, ipath);
! else
! {
! /* TODO Double-check if this is the correct input value. */
! agg_input_rows = rel->rows * ipath->indexselectivity;
!
! agg_path = create_partial_agg_sorted_path(root, (Path *) ipath,
! true,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! agg_input_rows);
!
! if (agg_path != NULL)
! result = lappend(result, agg_path);
! else
! can_agg_sorted = false;
! }
/*
* If appropriate, consider parallel index scan. We don't allow
*************** build_index_paths(PlannerInfo *root, Rel
*** 1077,1083 ****
* using parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
! add_partial_path(rel, (Path *) ipath);
else
pfree(ipath);
}
--- 1109,1139 ----
* using parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
! {
! if (!grouped)
! add_partial_path(rel, (Path *) ipath, grouped);
! else if (can_agg_sorted && outer_relids == NULL)
! {
! /* TODO Double-check if this is the correct input value. */
! agg_input_rows = rel->rows * ipath->indexselectivity;
!
! agg_path = create_partial_agg_sorted_path(root,
! (Path *) ipath,
! false,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! agg_input_rows);
!
! /*
! * If create_agg_sorted_path succeeded once, it should
! * always do.
! */
! Assert(agg_path != NULL);
!
! add_partial_path(rel, (Path *) agg_path, grouped);
! }
! }
else
pfree(ipath);
}
*************** build_index_paths(PlannerInfo *root, Rel
*** 1105,1111 ****
outer_relids,
loop_count,
false);
! result = lappend(result, ipath);
/* If appropriate, consider parallel index scan */
if (index->amcanparallel &&
--- 1161,1185 ----
outer_relids,
loop_count,
false);
!
! if (!grouped)
! result = lappend(result, ipath);
! else if (can_agg_sorted)
! {
! /* TODO Double-check if this is the correct input value. */
! agg_input_rows = rel->rows * ipath->indexselectivity;
!
! agg_path = create_partial_agg_sorted_path(root,
! (Path *) ipath,
! true,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! agg_input_rows);
!
! Assert(agg_path != NULL);
! result = lappend(result, agg_path);
! }
/* If appropriate, consider parallel index scan */
if (index->amcanparallel &&
*************** build_index_paths(PlannerInfo *root, Rel
*** 1129,1135 ****
* using parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
! add_partial_path(rel, (Path *) ipath);
else
pfree(ipath);
}
--- 1203,1227 ----
* using parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
! {
! if (!grouped)
! add_partial_path(rel, (Path *) ipath, grouped);
! else if (can_agg_sorted && outer_relids == NULL)
! {
! /* TODO Double-check if this is the correct input value. */
! agg_input_rows = rel->rows * ipath->indexselectivity;
!
! agg_path = create_partial_agg_sorted_path(root,
! (Path *) ipath,
! false,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! agg_input_rows);
! Assert(agg_path != NULL);
! add_partial_path(rel, (Path *) agg_path, grouped);
! }
! }
else
pfree(ipath);
}
*************** build_paths_for_OR(PlannerInfo *root, Re
*** 1244,1250 ****
useful_predicate,
ST_BITMAPSCAN,
NULL,
! NULL);
result = list_concat(result, indexpaths);
}
--- 1336,1343 ----
useful_predicate,
ST_BITMAPSCAN,
NULL,
! NULL,
! false);
result = list_concat(result, indexpaths);
}
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index 5aedcd1..f25719f
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
***************
*** 22,34 ****
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
/* Hook for plugins to get control in add_paths_to_joinrel() */
set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
! #define PATH_PARAM_BY_REL(path, rel) \
((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids))
static void try_partial_mergejoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
Path *outer_path,
--- 22,45 ----
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
+ #include "optimizer/tlist.h"
/* Hook for plugins to get control in add_paths_to_joinrel() */
set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
! /*
! * Paths parameterized by the parent can be considered to be parameterized by
! * any of its child.
! */
! #define PATH_PARAM_BY_PARENT(path, rel) \
! ((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), \
! (rel)->top_parent_relids))
! #define PATH_PARAM_BY_REL_SELF(path, rel) \
((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids))
+ #define PATH_PARAM_BY_REL(path, rel) \
+ (PATH_PARAM_BY_REL_SELF(path, rel) || PATH_PARAM_BY_PARENT(path, rel))
+
static void try_partial_mergejoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
Path *outer_path,
*************** static void try_partial_mergejoin_path(P
*** 38,66 ****
List *outersortkeys,
List *innersortkeys,
JoinType jointype,
! JoinPathExtraData *extra);
static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra);
static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra);
static void consider_parallel_nestloop(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra);
static void consider_parallel_mergejoin(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! Path *inner_cheapest_total);
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra);
static List *select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
--- 49,97 ----
List *outersortkeys,
List *innersortkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate);
static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra,
! bool grouped);
! static void sort_inner_and_outer_common(PlannerInfo *root,
! RelOptInfo *joinrel,
! RelOptInfo *outerrel,
! RelOptInfo *innerrel,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate);
static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra,
! bool grouped);
static void consider_parallel_nestloop(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped, bool do_aggregate);
static void consider_parallel_mergejoin(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! Path *inner_cheapest_total,
! bool grouped);
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra,
! bool grouped);
! static bool is_grouped_join_target_complete(PlannerInfo *root,
! PathTarget *jointarget,
! Path *outer_path,
! Path *inner_path);
static List *select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
*************** static void generate_mergejoin_paths(Pla
*** 77,83 ****
bool useallclauses,
Path *inner_cheapest_total,
List *merge_pathkeys,
! bool is_partial);
/*
--- 108,117 ----
bool useallclauses,
Path *inner_cheapest_total,
List *merge_pathkeys,
! bool is_partial,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate);
/*
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 115,120 ****
--- 149,167 ----
JoinPathExtraData extra;
bool mergejoin_allowed = true;
ListCell *lc;
+ Relids joinrelids;
+
+ /*
+ * PlannerInfo doesn't contain the SpecialJoinInfos created for joins
+ * between child relations, even if there is a SpecialJoinInfo node for
+ * the join between the topmost parents. Hence while calculating Relids
+ * set representing the restriction, consider relids of topmost parent
+ * of partitions.
+ */
+ if (joinrel->reloptkind == RELOPT_OTHER_JOINREL)
+ joinrelids = joinrel->top_parent_relids;
+ else
+ joinrelids = joinrel->relids;
extra.restrictlist = restrictlist;
extra.mergeclause_list = NIL;
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 197,212 ****
* join has already been proven legal.) If the SJ is relevant, it
* presents constraints for joining to anything not in its RHS.
*/
! if (bms_overlap(joinrel->relids, sjinfo2->min_righthand) &&
! !bms_overlap(joinrel->relids, sjinfo2->min_lefthand))
extra.param_source_rels = bms_join(extra.param_source_rels,
bms_difference(root->all_baserels,
sjinfo2->min_righthand));
/* full joins constrain both sides symmetrically */
if (sjinfo2->jointype == JOIN_FULL &&
! bms_overlap(joinrel->relids, sjinfo2->min_lefthand) &&
! !bms_overlap(joinrel->relids, sjinfo2->min_righthand))
extra.param_source_rels = bms_join(extra.param_source_rels,
bms_difference(root->all_baserels,
sjinfo2->min_lefthand));
--- 244,259 ----
* join has already been proven legal.) If the SJ is relevant, it
* presents constraints for joining to anything not in its RHS.
*/
! if (bms_overlap(joinrelids, sjinfo2->min_righthand) &&
! !bms_overlap(joinrelids, sjinfo2->min_lefthand))
extra.param_source_rels = bms_join(extra.param_source_rels,
bms_difference(root->all_baserels,
sjinfo2->min_righthand));
/* full joins constrain both sides symmetrically */
if (sjinfo2->jointype == JOIN_FULL &&
! bms_overlap(joinrelids, sjinfo2->min_lefthand) &&
! !bms_overlap(joinrelids, sjinfo2->min_righthand))
extra.param_source_rels = bms_join(extra.param_source_rels,
bms_difference(root->all_baserels,
sjinfo2->min_lefthand));
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 227,234 ****
* sorted. Skip this if we can't mergejoin.
*/
if (mergejoin_allowed)
sort_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra);
/*
* 2. Consider paths where the outer relation need not be explicitly
--- 274,285 ----
* sorted. Skip this if we can't mergejoin.
*/
if (mergejoin_allowed)
+ {
sort_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, false);
! sort_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, true);
! }
/*
* 2. Consider paths where the outer relation need not be explicitly
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 238,245 ****
* joins at all, so it wouldn't work in the prohibited cases either.)
*/
if (mergejoin_allowed)
match_unsorted_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra);
#ifdef NOT_USED
--- 289,300 ----
* joins at all, so it wouldn't work in the prohibited cases either.)
*/
if (mergejoin_allowed)
+ {
match_unsorted_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, false);
! match_unsorted_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, true);
! }
#ifdef NOT_USED
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 265,272 ****
* joins, because there may be no other alternative.
*/
if (enable_hashjoin || jointype == JOIN_FULL)
hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra);
/*
* 5. If inner and outer relations are foreign tables (or joins) belonging
--- 320,331 ----
* joins, because there may be no other alternative.
*/
if (enable_hashjoin || jointype == JOIN_FULL)
+ {
hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, false);
! hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, true);
! }
/*
* 5. If inner and outer relations are foreign tables (or joins) belonging
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 304,321 ****
*/
static inline bool
allow_star_schema_join(PlannerInfo *root,
! Path *outer_path,
! Path *inner_path)
{
- Relids innerparams = PATH_REQ_OUTER(inner_path);
- Relids outerrelids = outer_path->parent->relids;
-
/*
* It's a star-schema case if the outer rel provides some but not all of
* the inner rel's parameterization.
*/
! return (bms_overlap(innerparams, outerrelids) &&
! bms_nonempty_difference(innerparams, outerrelids));
}
/*
--- 363,377 ----
*/
static inline bool
allow_star_schema_join(PlannerInfo *root,
! Relids outerrelids,
! Relids inner_paramrels)
{
/*
* It's a star-schema case if the outer rel provides some but not all of
* the inner rel's parameterization.
*/
! return (bms_overlap(inner_paramrels, outerrelids) &&
! bms_nonempty_difference(inner_paramrels, outerrelids));
}
/*
*************** try_nestloop_path(PlannerInfo *root,
*** 330,339 ****
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra)
{
Relids required_outer;
JoinCostWorkspace workspace;
/*
* Check to see if proposed path is still parameterized, and reject if the
--- 386,427 ----
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
Relids required_outer;
JoinCostWorkspace workspace;
+ RelOptInfo *innerrel = inner_path->parent;
+ RelOptInfo *outerrel = outer_path->parent;
+ Relids innerrelids;
+ Relids outerrelids;
+ Relids inner_paramrels = PATH_REQ_OUTER(inner_path);
+ Relids outer_paramrels = PATH_REQ_OUTER(outer_path);
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* Caller should not request aggregation w/o grouped output. */
+ Assert(!do_aggregate || grouped);
+
+ /* GroupedPathInfo is necessary for us to produce a grouped set. */
+ Assert(joinrel->gpi != NULL || !grouped);
+
+ /*
+ * Parameterized paths in the child relations (base or join) are
+ * parameterized by top-level parent. Any paths we will create to be
+ * parameterized by the child child relations, are not added to the
+ * pathlist. Hence run parameterization tests on the parent relids.
+ */
+ if (innerrel->top_parent_relids)
+ innerrelids = innerrel->top_parent_relids;
+ else
+ innerrelids = innerrel->relids;
+
+ if (outerrel->top_parent_relids)
+ outerrelids = outerrel->top_parent_relids;
+ else
+ outerrelids = outerrel->relids;
/*
* Check to see if proposed path is still parameterized, and reject if the
*************** try_nestloop_path(PlannerInfo *root,
*** 341,359 ****
* says to allow it anyway. Also, we must reject if have_dangerous_phv
* doesn't like the look of it, which could only happen if the nestloop is
* still parameterized.
*/
! required_outer = calc_nestloop_required_outer(outer_path,
! inner_path);
! if (required_outer &&
! ((!bms_overlap(required_outer, extra->param_source_rels) &&
! !allow_star_schema_join(root, outer_path, inner_path)) ||
! have_dangerous_phv(root,
! outer_path->parent->relids,
! PATH_REQ_OUTER(inner_path))))
{
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
}
/*
--- 429,452 ----
* says to allow it anyway. Also, we must reject if have_dangerous_phv
* doesn't like the look of it, which could only happen if the nestloop is
* still parameterized.
+ *
+ * Grouped path should never be parameterized.
*/
! required_outer = calc_nestloop_required_outer(outerrelids, outer_paramrels,
! innerrelids, inner_paramrels);
! if (required_outer)
{
! if (grouped ||
! (!bms_overlap(required_outer, extra->param_source_rels) &&
! !allow_star_schema_join(root, outerrelids, inner_paramrels)) ||
! have_dangerous_phv(root,
! outer_path->parent->relids,
! PATH_REQ_OUTER(inner_path)))
! {
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
! }
}
/*
*************** try_nestloop_path(PlannerInfo *root,
*** 368,388 ****
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path, extra);
! if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer))
{
! add_path(joinrel, (Path *)
! create_nestloop_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! required_outer));
}
else
{
--- 461,522 ----
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path, extra);
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! join_target = joinrel->gpi->target;
!
! join_path = (Path *) create_nestloop_path(root, joinrel, jointype,
! &workspace, extra,
! outer_path, inner_path,
! extra->restrictlist, pathkeys,
! required_outer, join_target);
!
! /* Do partial aggregation if needed. */
! if (do_aggregate && required_outer == NULL)
! {
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_HASHED);
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_SORTED);
! }
! else if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer, grouped))
{
! /*
! * Since result produced by a child is part of the result produced by
! * its topmost parent and has same properties, the parameters
! * representing that parent may be substituted by values from a child.
! * Hence expressions and hence paths using those expressions,
! * parameterized by a parent can be said to be parameterized by any of
! * its child. For a join between child relations, if the inner path is
! * parameterized by the parent of the outer relation, create a
! * nestloop join path with inner relation parameterized by the outer
! * relation by translating the inner path to be parameterized by the
! * outer child relation. The translated path should have the same costs
! * as the original path, so cost check above should still hold.
! */
! if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent))
! {
! inner_path = reparameterize_path_by_child(root, inner_path,
! outer_path->parent);
!
! /*
! * If we could not translate the path, we can't create nest loop
! * path.
! */
! if (!inner_path)
! return;
! }
!
! add_path(joinrel, join_path, grouped);
}
else
{
*************** try_partial_nestloop_path(PlannerInfo *r
*** 403,411 ****
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
/*
* If the inner path is parameterized, the parameterization must be fully
--- 537,553 ----
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
JoinCostWorkspace workspace;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* The same checks we do in try_nestloop_path. */
+ Assert(!do_aggregate || grouped);
+ Assert(joinrel->gpi != NULL || !grouped);
/*
* If the inner path is parameterized, the parameterization must be fully
*************** try_partial_nestloop_path(PlannerInfo *r
*** 428,448 ****
*/
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path, extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
return;
! /* Might be good enough to be worth trying, so let's try it. */
! add_partial_path(joinrel, (Path *)
! create_nestloop_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! NULL));
}
/*
--- 570,650 ----
*/
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path, extra);
!
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! {
! Assert(joinrel->gpi != NULL);
! join_target = joinrel->gpi->target;
! }
!
! join_path = (Path *) create_nestloop_path(root, joinrel, jointype,
! &workspace, extra,
! outer_path, inner_path,
! extra->restrictlist, pathkeys,
! NULL, join_target);
!
! if (do_aggregate)
! {
! create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED);
! create_grouped_path(root, joinrel, join_path, true, true, AGG_SORTED);
! }
! else if (add_partial_path_precheck(joinrel, workspace.total_cost,
! pathkeys, grouped))
! {
! /* Might be good enough to be worth trying, so let's try it. */
! add_partial_path(joinrel, (Path *) join_path, grouped);
! }
! }
!
! static void
! try_grouped_nestloop_path(PlannerInfo *root,
! RelOptInfo *joinrel,
! Path *outer_path,
! Path *inner_path,
! List *pathkeys,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool do_aggregate,
! bool partial)
! {
! /*
! * Missing GroupedPathInfo indicates that we should not try to create a
! * grouped join.
! */
! if (joinrel->gpi == NULL)
return;
! /*
! * Reject the path if we're supposed to combine grouped and plain relation
! * but the grouped one does not evaluate all the relevant aggregates.
! */
! if (!do_aggregate &&
! !is_grouped_join_target_complete(root, joinrel->gpi->target,
! outer_path, inner_path))
! return;
!
! /*
! * As repeated aggregation doesn't seem to be attractive, make sure that
! * the resulting grouped relation is not parameterized.
! */
! if (outer_path->param_info != NULL || inner_path->param_info != NULL)
! return;
!
! if (!partial)
! try_nestloop_path(root, joinrel, outer_path, inner_path, pathkeys,
! jointype, extra, true, do_aggregate);
! else
! try_partial_nestloop_path(root, joinrel, outer_path, inner_path,
! pathkeys, jointype, extra, true,
! do_aggregate);
}
/*
*************** try_mergejoin_path(PlannerInfo *root,
*** 461,470 ****
List *innersortkeys,
JoinType jointype,
JoinPathExtraData *extra,
! bool is_partial)
{
Relids required_outer;
JoinCostWorkspace workspace;
if (is_partial)
{
--- 663,682 ----
List *innersortkeys,
JoinType jointype,
JoinPathExtraData *extra,
! bool is_partial,
! bool grouped,
! bool do_aggregate)
{
Relids required_outer;
JoinCostWorkspace workspace;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* Caller should not request aggregation w/o grouped output. */
+ Assert(!do_aggregate || grouped);
+
+ /* GroupedPathInfo is necessary for us to produce a grouped set. */
+ Assert(joinrel->gpi != NULL || !grouped);
if (is_partial)
{
*************** try_mergejoin_path(PlannerInfo *root,
*** 477,498 ****
outersortkeys,
innersortkeys,
jointype,
! extra);
return;
}
/*
! * Check to see if proposed path is still parameterized, and reject if the
! * parameterization wouldn't be sensible.
*/
! required_outer = calc_non_nestloop_required_outer(outer_path,
! inner_path);
! if (required_outer &&
! !bms_overlap(required_outer, extra->param_source_rels))
{
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
}
/*
--- 689,713 ----
outersortkeys,
innersortkeys,
jointype,
! extra,
! grouped,
! do_aggregate);
return;
}
/*
! * Check to see if proposed path is still parameterized, and reject if
! * it's grouped or if the parameterization wouldn't be sensible.
*/
! required_outer = calc_non_nestloop_required_outer(outer_path, inner_path);
! if (required_outer)
{
! if (grouped || !bms_overlap(required_outer, extra->param_source_rels))
! {
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
! }
}
/*
*************** try_mergejoin_path(PlannerInfo *root,
*** 511,537 ****
*/
initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
outer_path, inner_path,
! outersortkeys, innersortkeys,
! extra);
! if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer))
{
! add_path(joinrel, (Path *)
! create_mergejoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! required_outer,
! mergeclauses,
! outersortkeys,
! innersortkeys));
}
else
{
--- 726,773 ----
*/
initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
outer_path, inner_path,
! outersortkeys, innersortkeys, extra);
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! join_target = joinrel->gpi->target;
!
!
! join_path = (Path *) create_mergejoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! required_outer,
! mergeclauses,
! outersortkeys,
! innersortkeys,
! join_target);
!
! /* Do partial aggregation if needed. */
! if (do_aggregate)
! {
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_HASHED);
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_SORTED);
! }
! else if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer, grouped))
{
! add_path(joinrel, (Path *) join_path, grouped);
}
else
{
*************** try_partial_mergejoin_path(PlannerInfo *
*** 555,563 ****
List *outersortkeys,
List *innersortkeys,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
/*
* See comments in try_partial_hashjoin_path().
--- 791,807 ----
List *outersortkeys,
List *innersortkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
JoinCostWorkspace workspace;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* The same checks we do in try_mergejoin_path. */
+ Assert(!do_aggregate || grouped);
+ Assert(joinrel->gpi != NULL || !grouped);
/*
* See comments in try_partial_hashjoin_path().
*************** try_partial_mergejoin_path(PlannerInfo *
*** 587,613 ****
*/
initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
outer_path, inner_path,
! outersortkeys, innersortkeys,
! extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
return;
! /* Might be good enough to be worth trying, so let's try it. */
! add_partial_path(joinrel, (Path *)
! create_mergejoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! NULL,
! mergeclauses,
! outersortkeys,
! innersortkeys));
}
/*
--- 831,1003 ----
*/
initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
outer_path, inner_path,
! outersortkeys, innersortkeys, extra);
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! {
! Assert(joinrel->gpi != NULL);
! join_target = joinrel->gpi->target;
! }
!
! join_path = (Path *) create_mergejoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! NULL,
! mergeclauses,
! outersortkeys,
! innersortkeys,
! join_target);
!
! if (do_aggregate)
! {
! create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED);
! create_grouped_path(root, joinrel, join_path, true, true, AGG_SORTED);
! }
! else if (add_partial_path_precheck(joinrel, workspace.total_cost,
! pathkeys, grouped))
! {
! /* Might be good enough to be worth trying, so let's try it. */
! add_partial_path(joinrel, (Path *) join_path, grouped);
! }
! }
!
! static void
! try_grouped_mergejoin_path(PlannerInfo *root,
! RelOptInfo *joinrel,
! Path *outer_path,
! Path *inner_path,
! List *pathkeys,
! List *mergeclauses,
! List *outersortkeys,
! List *innersortkeys,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool partial,
! bool do_aggregate)
! {
! /*
! * Missing GroupedPathInfo indicates that we should not try to create a
! * grouped join.
! */
! if (joinrel->gpi == NULL)
return;
! /*
! * Reject the path if we're supposed to combine grouped and plain relation
! * but the grouped one does not evaluate all the relevant aggregates.
! */
! if (!do_aggregate &&
! !is_grouped_join_target_complete(root, joinrel->gpi->target,
! outer_path, inner_path))
! return;
!
! /*
! * As repeated aggregation doesn't seem to be attractive, make sure that
! * the resulting grouped relation is not parameterized.
! */
! if (outer_path->param_info != NULL || inner_path->param_info != NULL)
! return;
!
! if (!partial)
! try_mergejoin_path(root, joinrel, outer_path, inner_path, pathkeys,
! mergeclauses, outersortkeys, innersortkeys,
! jointype, extra, false, true, do_aggregate);
! else
! try_partial_mergejoin_path(root, joinrel, outer_path, inner_path,
! pathkeys,
! mergeclauses, outersortkeys, innersortkeys,
! jointype, extra, true, do_aggregate);
! }
!
! static void
! try_mergejoin_path_common(PlannerInfo *root,
! RelOptInfo *joinrel,
! Path *outer_path,
! Path *inner_path,
! List *pathkeys,
! List *mergeclauses,
! List *outersortkeys,
! List *innersortkeys,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool partial,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate)
! {
! bool grouped_join;
!
! grouped_join = grouped_outer || grouped_inner || do_aggregate;
!
! /* Join of two grouped paths is not supported. */
! Assert(!(grouped_outer && grouped_inner));
!
! if (!grouped_join)
! {
! /* Only join plain paths. */
! try_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! pathkeys,
! mergeclauses,
! outersortkeys,
! innersortkeys,
! jointype,
! extra,
! partial,
! false, false);
! }
! else if (grouped_outer || grouped_inner)
! {
! Assert(!do_aggregate);
!
! /*
! * Exactly one of the input paths is grouped, so create a grouped join
! * path.
! */
! try_grouped_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! pathkeys,
! mergeclauses,
! outersortkeys,
! innersortkeys,
! jointype,
! extra,
! partial,
! false);
! }
! /* Preform explicit aggregation only if suitable target exists. */
! else if (joinrel->gpi != NULL)
! {
! try_grouped_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! pathkeys,
! mergeclauses,
! outersortkeys,
! innersortkeys,
! jointype,
! extra,
! partial, true);
! }
}
/*
*************** try_hashjoin_path(PlannerInfo *root,
*** 622,668 ****
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra)
{
Relids required_outer;
JoinCostWorkspace workspace;
/*
! * Check to see if proposed path is still parameterized, and reject if the
! * parameterization wouldn't be sensible.
*/
! required_outer = calc_non_nestloop_required_outer(outer_path,
! inner_path);
! if (required_outer &&
! !bms_overlap(required_outer, extra->param_source_rels))
{
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
}
/*
* See comments in try_nestloop_path(). Also note that hashjoin paths
* never have any output pathkeys, per comments in create_hashjoin_path.
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra);
! if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! NIL, required_outer))
{
! add_path(joinrel, (Path *)
! create_hashjoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! required_outer,
! hashclauses));
}
else
{
--- 1012,1086 ----
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
Relids required_outer;
JoinCostWorkspace workspace;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* Caller should not request aggregation w/o grouped output. */
+ Assert(!do_aggregate || grouped);
+
+ /* GroupedPathInfo is necessary for us to produce a grouped set. */
+ Assert(joinrel->gpi != NULL || !grouped);
/*
! * Check to see if proposed path is still parameterized, and reject if
! * it's grouped or if the parameterization wouldn't be sensible.
*/
! required_outer = calc_non_nestloop_required_outer(outer_path, inner_path);
! if (required_outer)
{
! if (grouped || !bms_overlap(required_outer, extra->param_source_rels))
! {
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
! }
}
/*
* See comments in try_nestloop_path(). Also note that hashjoin paths
* never have any output pathkeys, per comments in create_hashjoin_path.
+ *
+ * TODO Need to consider aggregation here?
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra);
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! join_target = joinrel->gpi->target;
!
! join_path = (Path *) create_hashjoin_path(root, joinrel, jointype,
! &workspace,
! extra,
! outer_path, inner_path,
! extra->restrictlist,
! required_outer, hashclauses,
! join_target);
!
! /* Do partial aggregation if needed. */
! if (do_aggregate)
! {
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_HASHED);
! }
! else if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! NIL, required_outer, grouped))
{
! add_path(joinrel, (Path *) join_path, grouped);
}
else
{
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 683,691 ****
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
/*
* If the inner path is parameterized, the parameterization must be fully
--- 1101,1117 ----
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
JoinCostWorkspace workspace;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* The same checks we do in try_hashjoin_path. */
+ Assert(!do_aggregate || grouped);
+ Assert(joinrel->gpi != NULL || !grouped);
/*
* If the inner path is parameterized, the parameterization must be fully
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 708,728 ****
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL))
return;
! /* Might be good enough to be worth trying, so let's try it. */
! add_partial_path(joinrel, (Path *)
! create_hashjoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! NULL,
! hashclauses));
}
/*
--- 1134,1229 ----
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra);
!
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! {
! Assert(joinrel->gpi != NULL);
! join_target = joinrel->gpi->target;
! }
!
! join_path = (Path *) create_hashjoin_path(root, joinrel, jointype,
! &workspace,
! extra,
! outer_path, inner_path,
! extra->restrictlist, NULL,
! hashclauses, join_target);
!
! /* Do partial aggregation if needed. */
! if (do_aggregate)
! {
! create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED);
! }
! else if (add_partial_path_precheck(joinrel, workspace.total_cost,
! NIL, grouped))
! {
! add_partial_path(joinrel, (Path *) join_path , grouped);
! }
! }
!
! /*
! * Create a new grouped hash join path by joining a grouped path to plain
! * (non-grouped) one, or by joining 2 plain relations and applying grouping on
! * the result.
! *
! * Joining of 2 grouped paths is not supported. If a grouped relation A was
! * joined to grouped relation B, then the grouping of B reduces the number of
! * times each group of A is appears in the join output. This makes difference
! * for some aggregates, e.g. sum().
! *
! * If do_aggregate is true, neither input rel is grouped so we need to
! * aggregate the join result explicitly.
! *
! * partial argument tells whether the join path should be considered partial.
! */
! static void
! try_grouped_hashjoin_path(PlannerInfo *root,
! RelOptInfo *joinrel,
! Path *outer_path,
! Path *inner_path,
! List *hashclauses,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool do_aggregate,
! bool partial)
! {
! /*
! * Missing GroupedPathInfo indicates that we should not try to create a
! * grouped join.
! */
! if (joinrel->gpi == NULL)
return;
! /*
! * Reject the path if we're supposed to combine grouped and plain relation
! * but the grouped one does not evaluate all the relevant aggregates.
! */
! if (!do_aggregate &&
! !is_grouped_join_target_complete(root, joinrel->gpi->target,
! outer_path, inner_path))
! return;
!
! /*
! * As repeated aggregation doesn't seem to be attractive, make sure that
! * the resulting grouped relation is not parameterized.
! */
! if (outer_path->param_info != NULL || inner_path->param_info != NULL)
! return;
!
! if (!partial)
! try_hashjoin_path(root, joinrel, outer_path, inner_path, hashclauses,
! jointype, extra, true, do_aggregate);
! else
! try_partial_hashjoin_path(root, joinrel, outer_path, inner_path,
! hashclauses, jointype, extra, true,
! do_aggregate);
}
/*
*************** sort_inner_and_outer(PlannerInfo *root,
*** 773,779 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
Path *outer_path;
--- 1274,1313 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
! {
! if (!grouped)
! {
! sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, false, false, false);
! }
! else
! {
! /* Use all the supported strategies to generate grouped join. */
! sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, true, false, false);
! sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, false, true, false);
! sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, false, false, true);
! }
! }
!
! /*
! * TODO As merge_pathkeys shouldn't differ across execution, use a separate
! * function to derive them and pass them here in a list.
! */
! static void
! sort_inner_and_outer_common(PlannerInfo *root,
! RelOptInfo *joinrel,
! RelOptInfo *outerrel,
! RelOptInfo *innerrel,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate)
{
JoinType save_jointype = jointype;
Path *outer_path;
*************** sort_inner_and_outer(PlannerInfo *root,
*** 782,787 ****
--- 1316,1322 ----
Path *cheapest_safe_inner = NULL;
List *all_pathkeys;
ListCell *l;
+ bool grouped_result;
/*
* We only consider the cheapest-total-cost input paths, since we are
*************** sort_inner_and_outer(PlannerInfo *root,
*** 796,803 ****
* against mergejoins with parameterized inputs; see comments in
* src/backend/optimizer/README.
*/
! outer_path = outerrel->cheapest_total_path;
! inner_path = innerrel->cheapest_total_path;
/*
* If either cheapest-total path is parameterized by the other rel, we
--- 1331,1357 ----
* against mergejoins with parameterized inputs; see comments in
* src/backend/optimizer/README.
*/
! if (grouped_outer)
! {
! if (outerrel->gpi != NULL && outerrel->gpi->pathlist != NIL)
! outer_path = linitial(outerrel->gpi->pathlist);
! else
! return;
! }
! else
! outer_path = outerrel->cheapest_total_path;
!
! if (grouped_inner)
! {
! if (innerrel->gpi != NULL && innerrel->gpi->pathlist != NIL)
! inner_path = linitial(innerrel->gpi->pathlist);
! else
! return;
! }
! else
! inner_path = innerrel->cheapest_total_path;
!
! grouped_result = grouped_outer || grouped_inner || do_aggregate;
/*
* If either cheapest-total path is parameterized by the other rel, we
*************** sort_inner_and_outer(PlannerInfo *root,
*** 843,855 ****
outerrel->partial_pathlist != NIL &&
bms_is_empty(joinrel->lateral_relids))
{
! cheapest_partial_outer = (Path *) linitial(outerrel->partial_pathlist);
if (inner_path->parallel_safe)
cheapest_safe_inner = inner_path;
else if (save_jointype != JOIN_UNIQUE_INNER)
cheapest_safe_inner =
! get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
}
/*
--- 1397,1446 ----
outerrel->partial_pathlist != NIL &&
bms_is_empty(joinrel->lateral_relids))
{
! if (grouped_outer)
! {
! if (outerrel->gpi != NULL && outerrel->gpi->partial_pathlist != NIL)
! cheapest_partial_outer = (Path *)
! linitial(outerrel->gpi->partial_pathlist);
! else
! return;
! }
! else
! cheapest_partial_outer = (Path *)
! linitial(outerrel->partial_pathlist);
!
! if (grouped_inner)
! {
! if (innerrel->gpi != NULL && innerrel->gpi->pathlist != NIL)
! inner_path = linitial(innerrel->gpi->pathlist);
! else
! return;
! }
! else
! inner_path = innerrel->cheapest_total_path;
if (inner_path->parallel_safe)
cheapest_safe_inner = inner_path;
else if (save_jointype != JOIN_UNIQUE_INNER)
+ {
+ List *inner_pathlist;
+
+ if (!grouped_inner)
+ inner_pathlist = innerrel->pathlist;
+ else
+ {
+ Assert(innerrel->gpi != NULL);
+ inner_pathlist = innerrel->gpi->pathlist;
+ }
+
+ /*
+ * All the grouped paths should be unparameterized, so the
+ * function is overly stringent in the grouped_inner case, but
+ * still useful.
+ */
cheapest_safe_inner =
! get_cheapest_parallel_safe_total_inner(inner_pathlist);
! }
}
/*
*************** sort_inner_and_outer(PlannerInfo *root,
*** 925,957 ****
* properly. try_mergejoin_path will detect that case and suppress an
* explicit sort step, so we needn't do so here.
*/
! try_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! merge_pathkeys,
! cur_mergeclauses,
! outerkeys,
! innerkeys,
! jointype,
! extra,
! false);
/*
* If we have partial outer and parallel safe inner path then try
* partial mergejoin path.
*/
if (cheapest_partial_outer && cheapest_safe_inner)
! try_partial_mergejoin_path(root,
! joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! merge_pathkeys,
! cur_mergeclauses,
! outerkeys,
! innerkeys,
! jointype,
! extra);
}
}
--- 1516,1574 ----
* properly. try_mergejoin_path will detect that case and suppress an
* explicit sort step, so we needn't do so here.
*/
! if (!grouped_result)
! try_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! merge_pathkeys,
! cur_mergeclauses,
! outerkeys,
! innerkeys,
! jointype,
! extra,
! false, false, false);
! else
! {
! try_mergejoin_path_common(root, joinrel, outer_path, inner_path,
! merge_pathkeys, cur_mergeclauses,
! outerkeys, innerkeys, jointype, extra,
! false,
! grouped_outer, grouped_inner,
! do_aggregate);
! }
/*
* If we have partial outer and parallel safe inner path then try
* partial mergejoin path.
*/
if (cheapest_partial_outer && cheapest_safe_inner)
! {
! if (!grouped_result)
! {
! try_partial_mergejoin_path(root,
! joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! merge_pathkeys,
! cur_mergeclauses,
! outerkeys,
! innerkeys,
! jointype,
! extra, false, false);
! }
! else
! {
! try_mergejoin_path_common(root, joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! merge_pathkeys, cur_mergeclauses,
! outerkeys, innerkeys, jointype, extra,
! true,
! grouped_outer, grouped_inner,
! do_aggregate);
! }
! }
}
}
*************** sort_inner_and_outer(PlannerInfo *root,
*** 968,973 ****
--- 1585,1598 ----
* some sort key requirements). So, we consider truncations of the
* mergeclause list as well as the full list. (Ideally we'd consider all
* subsets of the mergeclause list, but that seems way too expensive.)
+ *
+ * grouped_outer - is outerpath grouped?
+ * grouped_inner - use grouped paths of innerrel?
+ * do_aggregate - apply (partial) aggregation to the output?
+ *
+ * TODO If subsequent calls often differ only by the 3 arguments above,
+ * consider a workspace structure to share useful info (eg merge clauses)
+ * across calls.
*/
static void
generate_mergejoin_paths(PlannerInfo *root,
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 979,985 ****
bool useallclauses,
Path *inner_cheapest_total,
List *merge_pathkeys,
! bool is_partial)
{
List *mergeclauses;
List *innersortkeys;
--- 1604,1613 ----
bool useallclauses,
Path *inner_cheapest_total,
List *merge_pathkeys,
! bool is_partial,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate)
{
List *mergeclauses;
List *innersortkeys;
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1030,1046 ****
* try_mergejoin_path will do the right thing if inner_cheapest_total is
* already correctly sorted.)
*/
! try_mergejoin_path(root,
! joinrel,
! outerpath,
! inner_cheapest_total,
! merge_pathkeys,
! mergeclauses,
! NIL,
! innersortkeys,
! jointype,
! extra,
! is_partial);
/* Can't do anything else if inner path needs to be unique'd */
if (save_jointype == JOIN_UNIQUE_INNER)
--- 1658,1675 ----
* try_mergejoin_path will do the right thing if inner_cheapest_total is
* already correctly sorted.)
*/
! try_mergejoin_path_common(root,
! joinrel,
! outerpath,
! inner_cheapest_total,
! merge_pathkeys,
! mergeclauses,
! NIL,
! innersortkeys,
! jointype,
! extra,
! is_partial,
! grouped_outer, grouped_inner, do_aggregate);
/* Can't do anything else if inner path needs to be unique'd */
if (save_jointype == JOIN_UNIQUE_INNER)
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1096,1111 ****
for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
{
Path *innerpath;
List *newclauses = NIL;
/*
* Look for an inner path ordered well enough for the first
* 'sortkeycnt' innersortkeys. NB: trialsortkeys list is modified
* destructively, which is why we made a copy...
*/
trialsortkeys = list_truncate(trialsortkeys, sortkeycnt);
! innerpath = get_cheapest_path_for_pathkeys(innerrel->pathlist,
trialsortkeys,
NULL,
TOTAL_COST,
--- 1725,1746 ----
for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
{
+ List *inner_pathlist = NIL;
Path *innerpath;
List *newclauses = NIL;
+ if (!grouped_inner)
+ inner_pathlist = innerrel->pathlist;
+ else if (innerrel->gpi != NULL)
+ inner_pathlist = innerrel->gpi->pathlist;
+
/*
* Look for an inner path ordered well enough for the first
* 'sortkeycnt' innersortkeys. NB: trialsortkeys list is modified
* destructively, which is why we made a copy...
*/
trialsortkeys = list_truncate(trialsortkeys, sortkeycnt);
! innerpath = get_cheapest_path_for_pathkeys(inner_pathlist,
trialsortkeys,
NULL,
TOTAL_COST,
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1128,1148 ****
}
else
newclauses = mergeclauses;
! try_mergejoin_path(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! newclauses,
! NIL,
! NIL,
! jointype,
! extra,
! is_partial);
cheapest_total_inner = innerpath;
}
/* Same on the basis of cheapest startup cost ... */
! innerpath = get_cheapest_path_for_pathkeys(innerrel->pathlist,
trialsortkeys,
NULL,
STARTUP_COST,
--- 1763,1787 ----
}
else
newclauses = mergeclauses;
!
! try_mergejoin_path_common(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! newclauses,
! NIL,
! NIL,
! jointype,
! extra,
! is_partial,
! grouped_outer, grouped_inner,
! do_aggregate);
!
cheapest_total_inner = innerpath;
}
/* Same on the basis of cheapest startup cost ... */
! innerpath = get_cheapest_path_for_pathkeys(inner_pathlist,
trialsortkeys,
NULL,
STARTUP_COST,
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1173,1189 ****
else
newclauses = mergeclauses;
}
! try_mergejoin_path(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! newclauses,
! NIL,
! NIL,
! jointype,
! extra,
! is_partial);
}
cheapest_startup_inner = innerpath;
}
--- 1812,1830 ----
else
newclauses = mergeclauses;
}
! try_mergejoin_path_common(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! newclauses,
! NIL,
! NIL,
! jointype,
! extra,
! is_partial,
! grouped_outer, grouped_inner,
! do_aggregate);
}
cheapest_startup_inner = innerpath;
}
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1218,1223 ****
--- 1859,1866 ----
* 'innerrel' is the inner join relation
* 'jointype' is the type of join to do
* 'extra' contains additional input values
+ * 'grouped' indicates that the at least one relation in the join has been
+ * aggregated.
*/
static void
match_unsorted_outer(PlannerInfo *root,
*************** match_unsorted_outer(PlannerInfo *root,
*** 1225,1231 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
bool nestjoinOK;
--- 1868,1875 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinType save_jointype = jointype;
bool nestjoinOK;
*************** match_unsorted_outer(PlannerInfo *root,
*** 1235,1240 ****
--- 1879,1906 ----
ListCell *lc1;
/*
+ * If grouped join path is requested, we ignore cases where either input
+ * path needs to be unique. For each side we should expect either grouped
+ * or plain relation, which differ quite a bit.
+ *
+ * XXX Although unique-ification of grouped path might result in too
+ * expensive input path (note that grouped input relation is not
+ * necessarily unique, regardless the grouping keys --- one or more plain
+ * relation could already have been joined to it), we might want to
+ * unique-ify the input relation in the future at least in the case it's a
+ * plain relation.
+ *
+ * (Materialization is not involved in grouped paths for similar reasons.)
+ */
+ if (grouped &&
+ (jointype == JOIN_UNIQUE_OUTER || jointype == JOIN_UNIQUE_INNER))
+ return;
+
+ /* No grouped join w/o grouped target. */
+ if (grouped && joinrel->gpi == NULL)
+ return;
+
+ /*
* Nestloop only supports inner, left, semi, and anti joins. Also, if we
* are doing a right or full mergejoin, we must use *all* the mergeclauses
* as join clauses, else we will not have a valid plan. (Although these
*************** match_unsorted_outer(PlannerInfo *root,
*** 1290,1296 ****
create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
Assert(inner_cheapest_total);
}
! else if (nestjoinOK)
{
/*
* Consider materializing the cheapest inner path, unless
--- 1956,1962 ----
create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
Assert(inner_cheapest_total);
}
! else if (nestjoinOK && !grouped)
{
/*
* Consider materializing the cheapest inner path, unless
*************** match_unsorted_outer(PlannerInfo *root,
*** 1321,1326 ****
--- 1987,1994 ----
*/
if (save_jointype == JOIN_UNIQUE_OUTER)
{
+ Assert(!grouped);
+
if (outerpath != outerrel->cheapest_total_path)
continue;
outerpath = (Path *) create_unique_path(root, outerrel,
*************** match_unsorted_outer(PlannerInfo *root,
*** 1348,1354 ****
inner_cheapest_total,
merge_pathkeys,
jointype,
! extra);
}
else if (nestjoinOK)
{
--- 2016,2023 ----
inner_cheapest_total,
merge_pathkeys,
jointype,
! extra,
! false, false);
}
else if (nestjoinOK)
{
*************** match_unsorted_outer(PlannerInfo *root,
*** 1364,1387 ****
{
Path *innerpath = (Path *) lfirst(lc2);
! try_nestloop_path(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! jointype,
! extra);
}
! /* Also consider materialized form of the cheapest inner path */
! if (matpath != NULL)
try_nestloop_path(root,
joinrel,
outerpath,
matpath,
merge_pathkeys,
jointype,
! extra);
}
/* Can't do anything else if outer path needs to be unique'd */
--- 2033,2078 ----
{
Path *innerpath = (Path *) lfirst(lc2);
! if (!grouped)
! try_nestloop_path(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! jointype,
! extra, false, false);
! else
! {
! /*
! * Since both input paths are plain, request explicit
! * aggregation.
! */
! try_grouped_nestloop_path(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! jointype,
! extra,
! true,
! false);
! }
}
! /*
! * Also consider materialized form of the cheapest inner path.
! *
! * (There's no matpath for grouped join.)
! */
! if (matpath != NULL && !grouped)
try_nestloop_path(root,
joinrel,
outerpath,
matpath,
merge_pathkeys,
jointype,
! extra,
! false, false);
}
/* Can't do anything else if outer path needs to be unique'd */
*************** match_unsorted_outer(PlannerInfo *root,
*** 1396,1402 ****
generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
save_jointype, extra, useallclauses,
inner_cheapest_total, merge_pathkeys,
! false);
}
/*
--- 2087,2163 ----
generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
save_jointype, extra, useallclauses,
inner_cheapest_total, merge_pathkeys,
! false, false, false, grouped);
!
! /* Try to join the plain outer relation to grouped inner. */
! if (grouped && nestjoinOK &&
! save_jointype != JOIN_UNIQUE_OUTER &&
! save_jointype != JOIN_UNIQUE_INNER &&
! innerrel->gpi != NULL && outerrel->gpi == NULL)
! {
! Path *inner_cheapest_grouped = (Path *) linitial(innerrel->gpi->pathlist);
!
! if (PATH_PARAM_BY_REL(inner_cheapest_grouped, outerrel))
! continue;
!
! /* XXX Shouldn't Assert() be used here instead? */
! if (PATH_PARAM_BY_REL(outerpath, innerrel))
! continue;
!
! /*
! * Only outer grouped path is interesting in this case: grouped
! * path on the inner side of NL join would imply repeated
! * aggregation somewhere in the inner path.
! */
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! save_jointype, extra, useallclauses,
! inner_cheapest_grouped, merge_pathkeys,
! false, false, true, false);
! }
! }
!
! /*
! * Combine grouped outer and plain inner paths.
! */
! if (grouped && nestjoinOK &&
! save_jointype != JOIN_UNIQUE_OUTER &&
! save_jointype != JOIN_UNIQUE_INNER)
! {
! /*
! * If the inner rel had a grouped target, its plain paths should be
! * ignored. Otherwise we could create grouped paths with different
! * targets.
! */
! if (outerrel->gpi != NULL && innerrel->gpi == NULL &&
! inner_cheapest_total != NULL)
! {
! /* Nested loop paths. */
! foreach(lc1, outerrel->gpi->pathlist)
! {
! Path *outerpath = (Path *) lfirst(lc1);
! List *merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
! outerpath->pathkeys);
!
! if (PATH_PARAM_BY_REL(outerpath, innerrel))
! continue;
!
! try_grouped_nestloop_path(root,
! joinrel,
! outerpath,
! inner_cheapest_total,
! merge_pathkeys,
! jointype,
! extra,
! false,
! false);
!
! /* Merge join paths. */
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! save_jointype, extra, useallclauses,
! inner_cheapest_total, merge_pathkeys,
! false, true, false, false);
! }
! }
}
/*
*************** match_unsorted_outer(PlannerInfo *root,
*** 1416,1423 ****
bms_is_empty(joinrel->lateral_relids))
{
if (nestjoinOK)
! consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra);
/*
* If inner_cheapest_total is NULL or non parallel-safe then find the
--- 2177,2197 ----
bms_is_empty(joinrel->lateral_relids))
{
if (nestjoinOK)
! {
! if (!grouped)
! /* Plain partial paths. */
! consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra, false, false);
! else
! {
! /* Grouped partial paths with explicit aggregation. */
! consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra, true, true);
! /* Grouped partial paths w/o explicit aggregation. */
! consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra, true, false);
! }
! }
/*
* If inner_cheapest_total is NULL or non parallel-safe then find the
*************** match_unsorted_outer(PlannerInfo *root,
*** 1437,1443 ****
if (inner_cheapest_total)
consider_parallel_mergejoin(root, joinrel, outerrel, innerrel,
save_jointype, extra,
! inner_cheapest_total);
}
}
--- 2211,2217 ----
if (inner_cheapest_total)
consider_parallel_mergejoin(root, joinrel, outerrel, innerrel,
save_jointype, extra,
! inner_cheapest_total, grouped);
}
}
*************** consider_parallel_mergejoin(PlannerInfo
*** 1460,1469 ****
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! Path *inner_cheapest_total)
{
ListCell *lc1;
/* generate merge join path for each partial outer path */
foreach(lc1, outerrel->partial_pathlist)
{
--- 2234,2252 ----
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! Path *inner_cheapest_total,
! bool grouped)
{
ListCell *lc1;
+ if (grouped)
+ {
+ /* TODO Consider if these types should be supported. */
+ if (jointype == JOIN_UNIQUE_OUTER ||
+ jointype == JOIN_UNIQUE_INNER)
+ return;
+ }
+
/* generate merge join path for each partial outer path */
foreach(lc1, outerrel->partial_pathlist)
{
*************** consider_parallel_mergejoin(PlannerInfo
*** 1476,1484 ****
merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
outerpath->pathkeys);
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath, jointype,
! extra, false, inner_cheapest_total,
! merge_pathkeys, true);
}
}
--- 2259,2314 ----
merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
outerpath->pathkeys);
! if (!grouped)
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! jointype, extra, false,
! inner_cheapest_total, merge_pathkeys,
! true,
! false, false, false);
! else
! {
! /*
! * Create grouped join by joining plain rels and aggregating the
! * result.
! */
! Assert(joinrel->gpi != NULL);
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! jointype, extra, false,
! inner_cheapest_total, merge_pathkeys,
! true, false, false, true);
!
! /* Combine the plain outer with grouped inner one(s). */
! if (outerrel->gpi == NULL && innerrel->gpi != NULL)
! {
! Path *inner_cheapest_grouped = (Path *)
! linitial(innerrel->gpi->pathlist);
!
! if (inner_cheapest_grouped != NULL &&
! inner_cheapest_grouped->parallel_safe)
! generate_mergejoin_paths(root, joinrel, innerrel,
! outerpath, jointype, extra,
! false, inner_cheapest_grouped,
! merge_pathkeys,
! true, false, true, false);
! }
! }
! }
!
! /* In addition, try to join grouped outer to plain inner one(s). */
! if (grouped && outerrel->gpi != NULL && innerrel->gpi == NULL)
! {
! foreach(lc1, outerrel->gpi->partial_pathlist)
! {
! Path *outerpath = (Path *) lfirst(lc1);
! List *merge_pathkeys;
!
! merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
! outerpath->pathkeys);
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! jointype, extra, false,
! inner_cheapest_total, merge_pathkeys,
! true, true, false, false);
! }
}
}
*************** consider_parallel_nestloop(PlannerInfo *
*** 1499,1513 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
ListCell *lc1;
if (jointype == JOIN_UNIQUE_INNER)
jointype = JOIN_INNER;
! foreach(lc1, outerrel->partial_pathlist)
{
Path *outerpath = (Path *) lfirst(lc1);
List *pathkeys;
--- 2329,2373 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped, bool do_aggregate)
{
JoinType save_jointype = jointype;
+ List *outer_pathlist;
ListCell *lc1;
+ if (grouped)
+ {
+ /* TODO Consider if these types should be supported. */
+ if (save_jointype == JOIN_UNIQUE_OUTER ||
+ save_jointype == JOIN_UNIQUE_INNER)
+ return;
+ }
+
if (jointype == JOIN_UNIQUE_INNER)
jointype = JOIN_INNER;
! if (!grouped || do_aggregate)
! {
! /*
! * If creating grouped paths by explicit aggregation, the input paths
! * must be plain.
! */
! outer_pathlist = outerrel->partial_pathlist;
! }
! else if (outerrel->gpi != NULL)
! {
! /*
! * Only the outer paths are accepted as grouped when we try to combine
! * grouped and plain ones. Grouped inner path implies repeated
! * aggregation, which doesn't sound as a good idea.
! */
! outer_pathlist = outerrel->gpi->partial_pathlist;
! }
! else
! return;
!
! foreach(lc1, outer_pathlist)
{
Path *outerpath = (Path *) lfirst(lc1);
List *pathkeys;
*************** consider_parallel_nestloop(PlannerInfo *
*** 1538,1544 ****
* inner paths, but right now create_unique_path is not on board
* with that.)
*/
! if (save_jointype == JOIN_UNIQUE_INNER)
{
if (innerpath != innerrel->cheapest_total_path)
continue;
--- 2398,2404 ----
* inner paths, but right now create_unique_path is not on board
* with that.)
*/
! if (save_jointype == JOIN_UNIQUE_INNER && !grouped)
{
if (innerpath != innerrel->cheapest_total_path)
continue;
*************** consider_parallel_nestloop(PlannerInfo *
*** 1548,1555 ****
Assert(innerpath);
}
! try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra);
}
}
}
--- 2408,2433 ----
Assert(innerpath);
}
! if (!grouped)
! try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra,
! false, false);
! else if (do_aggregate)
! {
! /* Request aggregation as both input rels are plain. */
! try_grouped_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra,
! true, true);
! }
! /*
! * Only combine the grouped outer path with the plain inner if the
! * inner relation cannot produce grouped paths. Otherwise we could
! * generate grouped paths with different targets.
! */
! else if (innerrel->gpi == NULL)
! try_grouped_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra,
! false, true);
}
}
}
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1571,1583 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
bool isouterjoin = IS_OUTER_JOIN(jointype);
List *hashclauses;
ListCell *l;
/*
* We need to build only one hashclauses list for any given pair of outer
* and inner relations; all of the hashable clauses will be used as keys.
--- 2449,2466 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinType save_jointype = jointype;
bool isouterjoin = IS_OUTER_JOIN(jointype);
List *hashclauses;
ListCell *l;
+ /* No grouped join w/o grouped target. */
+ if (grouped && joinrel->gpi == NULL)
+ return;
+
/*
* We need to build only one hashclauses list for any given pair of outer
* and inner relations; all of the hashable clauses will be used as keys.
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1627,1632 ****
--- 2510,2518 ----
* can't use a hashjoin. (There's no use looking for alternative
* input paths, since these should already be the least-parameterized
* available paths.)
+ *
+ * (The same check should work for grouped paths, as these don't
+ * differ in parameterization.)
*/
if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1646,1652 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
/* no possibility of cheap startup here */
}
else if (jointype == JOIN_UNIQUE_INNER)
--- 2532,2539 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false, false);
/* no possibility of cheap startup here */
}
else if (jointype == JOIN_UNIQUE_INNER)
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1662,1668 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
if (cheapest_startup_outer != NULL &&
cheapest_startup_outer != cheapest_total_outer)
try_hashjoin_path(root,
--- 2549,2556 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false, false);
if (cheapest_startup_outer != NULL &&
cheapest_startup_outer != cheapest_total_outer)
try_hashjoin_path(root,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1671,1733 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
}
else
{
! /*
! * For other jointypes, we consider the cheapest startup outer
! * together with the cheapest total inner, and then consider
! * pairings of cheapest-total paths including parameterized ones.
! * There is no use in generating parameterized paths on the basis
! * of possibly cheap startup cost, so this is sufficient.
! */
! ListCell *lc1;
! ListCell *lc2;
!
! if (cheapest_startup_outer != NULL)
! try_hashjoin_path(root,
! joinrel,
! cheapest_startup_outer,
! cheapest_total_inner,
! hashclauses,
! jointype,
! extra);
!
! foreach(lc1, outerrel->cheapest_parameterized_paths)
{
- Path *outerpath = (Path *) lfirst(lc1);
-
/*
! * We cannot use an outer path that is parameterized by the
! * inner rel.
*/
! if (PATH_PARAM_BY_REL(outerpath, innerrel))
! continue;
! foreach(lc2, innerrel->cheapest_parameterized_paths)
{
! Path *innerpath = (Path *) lfirst(lc2);
/*
! * We cannot use an inner path that is parameterized by
! * the outer rel, either.
*/
! if (PATH_PARAM_BY_REL(innerpath, outerrel))
continue;
! if (outerpath == cheapest_startup_outer &&
! innerpath == cheapest_total_inner)
! continue; /* already tried it */
! try_hashjoin_path(root,
! joinrel,
! outerpath,
! innerpath,
! hashclauses,
! jointype,
! extra);
}
}
}
--- 2559,2712 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false, false);
}
else
{
! if (!grouped)
{
/*
! * For other jointypes, we consider the cheapest startup outer
! * together with the cheapest total inner, and then consider
! * pairings of cheapest-total paths including parameterized
! * ones. There is no use in generating parameterized paths on
! * the basis of possibly cheap startup cost, so this is
! * sufficient.
*/
! ListCell *lc1;
! if (cheapest_startup_outer != NULL)
! try_hashjoin_path(root,
! joinrel,
! cheapest_startup_outer,
! cheapest_total_inner,
! hashclauses,
! jointype,
! extra,
! false, false);
!
! foreach(lc1, outerrel->cheapest_parameterized_paths)
{
! Path *outerpath = (Path *) lfirst(lc1);
! ListCell *lc2;
/*
! * We cannot use an outer path that is parameterized by the
! * inner rel.
*/
! if (PATH_PARAM_BY_REL(outerpath, innerrel))
continue;
! foreach(lc2, innerrel->cheapest_parameterized_paths)
! {
! Path *innerpath = (Path *) lfirst(lc2);
! /*
! * We cannot use an inner path that is parameterized by
! * the outer rel, either.
! */
! if (PATH_PARAM_BY_REL(innerpath, outerrel))
! continue;
!
! if (outerpath == cheapest_startup_outer &&
! innerpath == cheapest_total_inner)
! continue; /* already tried it */
!
! try_hashjoin_path(root,
! joinrel,
! outerpath,
! innerpath,
! hashclauses,
! jointype,
! extra,
! false, false);
! }
! }
! }
! else
! {
! /* Create grouped paths if possible. */
! /*
! * TODO
! *
! * Consider processing JOIN_UNIQUE_INNER and JOIN_UNIQUE_OUTER
! * join types, ie perform grouping of the inner / outer rel if
! * it's not unique yet and if the grouping is legal.
! */
! if (jointype == JOIN_UNIQUE_OUTER ||
! jointype == JOIN_UNIQUE_INNER)
! return;
!
! /*
! * Join grouped relation to non-grouped one.
! *
! * Do not use plain path of the input rel whose target does
! * have GroupedPahtInfo. For example (assuming that join of
! * two grouped rels is not supported), the only way to
! * evaluate SELECT sum(a.x), sum(b.y) ... is to join "a" and
! * "b" and aggregate the result. Otherwise the path target
! * wouldn't match joinrel->gpi->target. TODO Move this comment
! * elsewhere as it seems common to all join kinds.
! */
! /*
! * TODO Allow outer join if the grouped rel is on the
! * non-nullable side.
! */
! if (jointype == JOIN_INNER)
! {
! Path *grouped_path, *plain_path;
!
! if (outerrel->gpi != NULL &&
! outerrel->gpi->pathlist != NIL &&
! innerrel->gpi == NULL)
! {
! grouped_path = (Path *)
! linitial(outerrel->gpi->pathlist);
! plain_path = cheapest_total_inner;
! try_grouped_hashjoin_path(root, joinrel,
! grouped_path, plain_path,
! hashclauses, jointype,
! extra, false, false);
! }
! else if (innerrel->gpi != NULL &&
! innerrel->gpi->pathlist != NIL &&
! outerrel->gpi == NULL)
! {
! grouped_path = (Path *)
! linitial(innerrel->gpi->pathlist);
! plain_path = cheapest_total_outer;
! try_grouped_hashjoin_path(root, joinrel, plain_path,
! grouped_path, hashclauses,
! jointype, extra,
! false, false);
!
! if (cheapest_startup_outer != NULL &&
! cheapest_startup_outer != cheapest_total_outer)
! {
! plain_path = cheapest_startup_outer;
! try_grouped_hashjoin_path(root, joinrel,
! plain_path,
! grouped_path,
! hashclauses,
! jointype, extra,
! false, false);
! }
! }
}
+
+ /*
+ * Try to join plain relations and make a grouped rel out of
+ * the join.
+ *
+ * Since aggregation needs the whole relation, we are only
+ * interested in total costs.
+ */
+ try_grouped_hashjoin_path(root, joinrel,
+ cheapest_total_outer,
+ cheapest_total_inner,
+ hashclauses,
+ jointype, extra, true, false);
}
}
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1765,1777 ****
cheapest_safe_inner =
get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
! if (cheapest_safe_inner != NULL)
! try_partial_hashjoin_path(root, joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! hashclauses, jointype, extra);
}
}
}
/*
--- 2744,2967 ----
cheapest_safe_inner =
get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
! if (!grouped)
! {
! if (cheapest_safe_inner != NULL)
! try_partial_hashjoin_path(root, joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! hashclauses, jointype, extra,
! false, false);
! }
! else if (joinrel->gpi != NULL)
! {
! /*
! * Grouped partial path.
! *
! * 1. Apply aggregation to the plain partial join path.
! */
! if (cheapest_safe_inner != NULL)
! try_grouped_hashjoin_path(root, joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! hashclauses,
! jointype, extra, true, true);
!
! /*
! * 2. Join the cheapest partial grouped outer path (if one
! * exists) to cheapest_safe_inner (there's no reason to look
! * for another inner path than what we used for non-grouped
! * partial join path).
! */
! if (outerrel->gpi != NULL &&
! outerrel->gpi->partial_pathlist != NIL &&
! innerrel->gpi == NULL &&
! cheapest_safe_inner != NULL)
! {
! Path *outer_path;
!
! outer_path = (Path *)
! linitial(outerrel->gpi->partial_pathlist);
!
! try_grouped_hashjoin_path(root, joinrel, outer_path,
! cheapest_safe_inner,
! hashclauses,
! jointype, extra, false, true);
! }
!
! /*
! * 3. Join the cheapest_partial_outer path (again, no reason
! * to use different outer path than the one we used for plain
! * partial join) to the cheapest grouped inner path if the
! * latter exists and is parallel-safe.
! */
! if (innerrel->gpi != NULL &&
! innerrel->gpi->pathlist != NIL &&
! outerrel->gpi == NULL)
! {
! Path *inner_path;
!
! inner_path = (Path *) linitial(innerrel->gpi->pathlist);
!
! if (inner_path->parallel_safe)
! try_grouped_hashjoin_path(root, joinrel,
! cheapest_partial_outer,
! inner_path,
! hashclauses,
! jointype, extra,
! false, true);
! }
!
! /*
! * Other combinations seem impossible because: 1. At most 1
! * input relation of the join can be grouped, 2. the inner
! * path must not be partial.
! */
! }
! }
! }
! }
!
! /*
! * Do the input paths emit all the aggregates contained in the grouped target
! * of the join?
! *
! * The point is that one input relation might be unable to evaluate some
! * aggregate(s), so it'll only generate plain paths. It's wrong to combine
! * such plain paths with grouped ones that the other input rel might be able
! * to generate because the result would miss the aggregate(s) the first
! * relation failed to evaluate.
! *
! * TODO For better efficiency, consider storing Bitmapset of
! * GroupedVarInfo.gvid in GroupedPathInfo.
! */
! static bool
! is_grouped_join_target_complete(PlannerInfo *root, PathTarget *jointarget,
! Path *outer_path, Path *inner_path)
! {
! RelOptInfo *outer_rel = outer_path->parent;
! RelOptInfo *inner_rel = inner_path->parent;
! ListCell *l1;
!
! /*
! * Join of two grouped relations is not supported.
! *
! * This actually isn't check of target completeness --- can it be located
! * elsewhere?
! */
! if (outer_rel->gpi != NULL && inner_rel->gpi != NULL)
! return false;
!
! foreach(l1, jointarget->exprs)
! {
! Expr *expr = (Expr *) lfirst(l1);
! GroupedVar *gvar;
! GroupedVarInfo *gvi = NULL;
! ListCell *l2;
! bool found = false;
!
! /* Only interested in aggregates. */
! if (!IsA(expr, GroupedVar))
! continue;
!
! gvar = castNode(GroupedVar, expr);
!
! /* Find the corresponding GroupedVarInfo. */
! foreach(l2, root->grouped_var_list)
! {
! GroupedVarInfo *gvi_tmp = castNode(GroupedVarInfo, lfirst(l2));
!
! if (gvi_tmp->gvid == gvar->gvid)
! {
! gvi = gvi_tmp;
! break;
! }
! }
! Assert(gvi != NULL);
!
! /*
! * If any aggregate references both input relations, something went
! * wrong during construction of one of the input targets: one input
! * rel is grouped, but no grouping target should have been created for
! * it if some aggregate required more than that input rel.
! */
! Assert(gvi->gv_eval_at == NULL ||
! !(bms_overlap(gvi->gv_eval_at, outer_rel->relids) &&
! bms_overlap(gvi->gv_eval_at, inner_rel->relids)));
!
! /*
! * If the aggregate belongs to the plain relation, it probably
! * means that non-grouping expression made aggregation of that
! * input relation impossible. Since that expression is not
! * necessarily emitted by the current join, aggregation might be
! * possible here. On the other hand, aggregation of a join which
! * already contains a grouped relation does not seem too
! * beneficial.
! *
! * XXX The condition below is also met if the query contains both
! * "star aggregate" and a normal one. Since the earlier can be
! * added to any base relation, and since we don't support join of
! * 2 grouped relations, join of arbitrary 2 relations will always
! * result in a plain relation.
! *
! * XXX If we conclude that aggregation is worth, only consider
! * this test failed if target usable for aggregation cannot be
! * created (i.e. the non-grouping expression is in the output of
! * the current join).
! */
! if ((outer_rel->gpi == NULL &&
! bms_overlap(gvi->gv_eval_at, outer_rel->relids))
! || (inner_rel->gpi == NULL &&
! bms_overlap(gvi->gv_eval_at, inner_rel->relids)))
! return false;
!
! /* Look for the aggregate in the input targets. */
! if (outer_rel->gpi != NULL)
! {
! /* No more than one input path should be grouped. */
! Assert(inner_rel->gpi == NULL);
!
! foreach(l2, outer_path->pathtarget->exprs)
! {
! expr = (Expr *) lfirst(l2);
!
! if (!IsA(expr, GroupedVar))
! continue;
!
! gvar = castNode(GroupedVar, expr);
! if (gvar->gvid == gvi->gvid)
! {
! found = true;
! break;
! }
! }
}
+ else if (!found && inner_rel->gpi != NULL)
+ {
+ Assert(outer_rel->gpi == NULL);
+
+ foreach(l2, inner_path->pathtarget->exprs)
+ {
+ expr = (Expr *) lfirst(l2);
+
+ if (!IsA(expr, GroupedVar))
+ continue;
+
+ gvar = castNode(GroupedVar, expr);
+ if (gvar->gvid == gvi->gvid)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ /* Even a single missing aggregate causes the whole test to fail. */
+ if (!found)
+ return false;
}
+
+ return true;
}
/*
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
new file mode 100644
index 5a68de3..ea24ed9
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
***************
*** 14,23 ****
--- 14,29 ----
*/
#include "postgres.h"
+ #include "miscadmin.h"
+ #include "nodes/relation.h"
+ #include "optimizer/clauses.h"
#include "optimizer/joininfo.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
+ #include "optimizer/prep.h"
+ #include "optimizer/cost.h"
#include "utils/memutils.h"
+ #include "utils/lsyscache.h"
static void make_rels_by_clause_joins(PlannerInfo *root,
*************** static void make_rels_by_clauseless_join
*** 29,40 ****
static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel);
static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel);
static bool is_dummy_rel(RelOptInfo *rel);
- static void mark_dummy_rel(RelOptInfo *rel);
static bool restriction_is_constant_false(List *restrictlist,
bool only_pushed_down);
static void populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
RelOptInfo *rel2, RelOptInfo *joinrel,
SpecialJoinInfo *sjinfo, List *restrictlist);
/*
--- 35,53 ----
static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel);
static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel);
static bool is_dummy_rel(RelOptInfo *rel);
static bool restriction_is_constant_false(List *restrictlist,
bool only_pushed_down);
static void populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
RelOptInfo *rel2, RelOptInfo *joinrel,
SpecialJoinInfo *sjinfo, List *restrictlist);
+ static void try_partition_wise_join(PlannerInfo *root, RelOptInfo *rel1,
+ RelOptInfo *rel2, RelOptInfo *joinrel,
+ SpecialJoinInfo *parent_sjinfo,
+ List *parent_restrictlist);
+ static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel);
+ static void build_joinrel_partition_bounds(RelOptInfo *rel1, RelOptInfo *rel2,
+ RelOptInfo *joinrel, JoinType jointype,
+ List **rel1_parts, List **rel2_parts);
/*
*************** make_join_rel(PlannerInfo *root, RelOptI
*** 731,736 ****
--- 744,752 ----
populate_joinrel_with_paths(root, rel1, rel2, joinrel, sjinfo,
restrictlist);
+ /* Apply partition-wise join technique, if possible. */
+ try_partition_wise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
bms_free(joinrelids);
return joinrel;
*************** is_dummy_rel(RelOptInfo *rel)
*** 1197,1203 ****
* is that the best solution is to explicitly make the dummy path in the same
* context the given RelOptInfo is in.
*/
! static void
mark_dummy_rel(RelOptInfo *rel)
{
MemoryContext oldcontext;
--- 1213,1219 ----
* is that the best solution is to explicitly make the dummy path in the same
* context the given RelOptInfo is in.
*/
! void
mark_dummy_rel(RelOptInfo *rel)
{
MemoryContext oldcontext;
*************** mark_dummy_rel(RelOptInfo *rel)
*** 1217,1223 ****
rel->partial_pathlist = NIL;
/* Set up the dummy path */
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL));
/* Set or update cheapest_total_path and related fields */
set_cheapest(rel);
--- 1233,1239 ----
rel->partial_pathlist = NIL;
/* Set up the dummy path */
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL), false);
/* Set or update cheapest_total_path and related fields */
set_cheapest(rel);
*************** restriction_is_constant_false(List *rest
*** 1268,1270 ****
--- 1284,1712 ----
}
return false;
}
+
+ /*
+ * Assess whether join between given two partitioned relations can be broken
+ * down into joins between matching partitions; a technique called
+ * "partition-wise join"
+ *
+ * Partition-wise join is possible when a. Joining relations have same
+ * partitioning scheme b. There exists an equi-join between the partition keys
+ * of the two relations.
+ *
+ * Partition-wise join is planned as follows (details: optimizer/README.)
+ *
+ * 1. Create the RelOptInfos for joins between matching partitions i.e
+ * child-joins and add paths those.
+ *
+ * 2. Add "append" paths to join between parent relations. The second phase is
+ * implemented by generate_partition_wise_join_paths().
+ *
+ * The RelOptInfo, SpecialJoinInfo and restrictlist for each child join are
+ * obtained by translating the respective parent join structures.
+ */
+ static void
+ try_partition_wise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
+ RelOptInfo *joinrel, SpecialJoinInfo *parent_sjinfo,
+ List *parent_restrictlist)
+ {
+ int nparts;
+ int cnt_parts;
+ ListCell *lc1;
+ ListCell *lc2;
+ List *rel1_parts;
+ List *rel2_parts;
+ bool is_strict;
+
+ /* Guard against stack overflow due to overly deep partition hierarchy. */
+ check_stack_depth();
+
+ /* Nothing to do, if the join relation is not partitioned. */
+ if (!joinrel->part_scheme)
+ return;
+
+ /*
+ * set_append_rel_pathlist() may not create paths in children of an empty
+ * partitioned table and so we can not add paths to a child-joins when one
+ * of the joining relations is empty. So, deem such a join as
+ * unpartitioned.
+ */
+ if (IS_DUMMY_REL(rel1) || IS_DUMMY_REL(rel2))
+ return;
+
+ /*
+ * Since this join relation is partitioned, all the base relations
+ * participating in this join must be partitioned and so are all the
+ * intermediate join relations.
+ */
+ Assert(rel1->part_scheme && rel2->part_scheme);
+
+ /*
+ * Every pair of joining relations we see here should have an equi-join
+ * between partition keys if this join has been deemed as a partitioned
+ * join. See build_joinrel_partition_info() for reasons.
+ */
+ Assert(have_partkey_equi_join(rel1, rel2, parent_sjinfo->jointype,
+ parent_restrictlist, &is_strict));
+
+ /*
+ * The partition scheme of the join relation should match that of the
+ * joining relations.
+ */
+ Assert(joinrel->part_scheme == rel1->part_scheme &&
+ joinrel->part_scheme == rel2->part_scheme);
+
+ /* We should have RelOptInfos of the partitions available. */
+ Assert(rel1->part_rels && rel2->part_rels);
+
+ /*
+ * Calculate bounds for the join relation. If we can not come up with joint
+ * bounds, we can not use partition-wise join.
+ */
+ build_joinrel_partition_bounds(rel1, rel2, joinrel,
+ parent_sjinfo->jointype, &rel1_parts,
+ &rel2_parts);
+ if (!joinrel->boundinfo)
+ return;
+
+ Assert(list_length(rel1_parts) == list_length(rel2_parts));
+ Assert(joinrel->nparts == list_length(rel1_parts));
+ Assert(joinrel->nparts > 0);
+
+ nparts = joinrel->nparts;
+
+ elog(DEBUG3, "join between relations %s and %s is considered for partition-wise join.",
+ bmsToString(rel1->relids), bmsToString(rel2->relids));
+
+ /* Allocate space for hold child-joins RelOptInfos, if not already done. */
+ if (!joinrel->part_rels)
+ joinrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * nparts);
+
+ /*
+ * Create child join relations for this partitioned join, if those don't
+ * exist. Add paths to child-joins for a pair of child relations
+ * corresponding corresponding to the given pair of parent relations.
+ */
+ cnt_parts = 0;
+ forboth (lc1, rel1_parts, lc2, rel2_parts)
+ {
+ RelOptInfo *child_rel1 = lfirst(lc1);
+ RelOptInfo *child_rel2 = lfirst(lc2);
+ SpecialJoinInfo *child_sjinfo;
+ List *child_restrictlist;
+ RelOptInfo *child_joinrel;
+ Relids child_joinrelids;
+ AppendRelInfo **appinfos;
+ int nappinfos;
+
+ /* We should never try to join two overlapping sets of rels. */
+ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids));
+ child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids);
+ appinfos = find_appinfos_by_relids(root, child_joinrelids, &nappinfos);
+
+ /*
+ * Construct SpecialJoinInfo from parent join relations's
+ * SpecialJoinInfo.
+ */
+ child_sjinfo = build_child_join_sjinfo(root, parent_sjinfo,
+ child_rel1->relids,
+ child_rel2->relids);
+
+ /*
+ * Construct restrictions applicable to the child join from
+ * those applicable to the parent join.
+ */
+ child_restrictlist = (List *) adjust_appendrel_attrs(root,
+ (Node *) parent_restrictlist,
+ nappinfos, appinfos);
+
+ child_joinrel = joinrel->part_rels[cnt_parts];
+ if (!child_joinrel)
+ {
+ child_joinrel = build_child_join_rel(root, child_rel1, child_rel2,
+ joinrel, child_restrictlist,
+ child_sjinfo,
+ child_sjinfo->jointype);
+ joinrel->part_rels[cnt_parts] = child_joinrel;
+ }
+
+ Assert(bms_equal(child_joinrel->relids, child_joinrelids));
+
+ /* Also translate expressions that AggPath will use in its target. */
+ if (child_joinrel->gpi != NULL)
+ {
+ Assert(child_joinrel->gpi->target != NULL);
+
+ child_joinrel->gpi->target->exprs =
+ (List *) adjust_appendrel_attrs(root,
+ (Node *) child_joinrel->gpi->target->exprs,
+ nappinfos, appinfos);
+ }
+
+ populate_joinrel_with_paths(root, child_rel1, child_rel2,
+ child_joinrel, child_sjinfo,
+ child_restrictlist);
+
+ pfree(appinfos);
+
+ /*
+ * If the child relations themselves are partitioned, try partition-wise join
+ * recursively.
+ */
+ try_partition_wise_join(root, child_rel1, child_rel2, child_joinrel,
+ child_sjinfo, child_restrictlist);
+ cnt_parts++;
+ }
+ }
+
+ /*
+ * Returns true if there exists an equi-join condition for each pair of
+ * partition key from given relations being joined.
+ */
+ bool
+ have_partkey_equi_join(RelOptInfo *rel1, RelOptInfo *rel2, JoinType jointype,
+ List *restrictlist, bool *is_strict)
+ {
+ PartitionScheme part_scheme = rel1->part_scheme;
+ ListCell *lc;
+ int cnt_pks;
+ int num_pks;
+ bool *pk_has_clause;
+
+ *is_strict = false;
+
+ /*
+ * This function should be called when the joining relations have same
+ * partitioning scheme.
+ */
+ Assert(rel1->part_scheme == rel2->part_scheme);
+ Assert(part_scheme);
+
+ num_pks = part_scheme->partnatts;
+
+ pk_has_clause = (bool *) palloc0(sizeof(bool) * num_pks);
+
+ foreach (lc, restrictlist)
+ {
+ RestrictInfo *rinfo = lfirst(lc);
+ OpExpr *opexpr;
+ Expr *expr1;
+ Expr *expr2;
+ int ipk1;
+ int ipk2;
+
+ /* If processing an outer join, only use its own join clauses. */
+ if (IS_OUTER_JOIN(jointype) && rinfo->is_pushed_down)
+ continue;
+
+ /* Skip clauses which can not be used for a join. */
+ if (!rinfo->can_join)
+ continue;
+
+ /* Skip clauses which are not equality conditions. */
+ if (!rinfo->mergeopfamilies)
+ continue;
+
+ opexpr = (OpExpr *) rinfo->clause;
+ Assert(is_opclause(opexpr));
+
+ /*
+ * The equi-join between partition keys is strict if equi-join between
+ * at least one partition key is using a strict operator. See
+ * explanation about outer join reordering identity 3 in
+ * optimizer/README
+ */
+ *is_strict = *is_strict || op_strict(opexpr->opno);
+
+ /* Match the operands to the relation. */
+ if (bms_is_subset(rinfo->left_relids, rel1->relids) &&
+ bms_is_subset(rinfo->right_relids, rel2->relids))
+ {
+ expr1 = linitial(opexpr->args);
+ expr2 = lsecond(opexpr->args);
+ }
+ else if (bms_is_subset(rinfo->left_relids, rel2->relids) &&
+ bms_is_subset(rinfo->right_relids, rel1->relids))
+ {
+ expr1 = lsecond(opexpr->args);
+ expr2 = linitial(opexpr->args);
+ }
+ else
+ continue;
+
+ /*
+ * Only clauses referencing the partition keys are useful for
+ * partition-wise join.
+ */
+ ipk1 = match_expr_to_partition_keys(expr1, rel1);
+ if (ipk1 < 0)
+ continue;
+ ipk2 = match_expr_to_partition_keys(expr2, rel2);
+ if (ipk2 < 0)
+ continue;
+
+ /*
+ * If the clause refers to keys at different cardinal positions in the
+ * partition keys of joining relations, it can not be used for
+ * partition-wise join.
+ */
+ if (ipk1 != ipk2)
+ continue;
+
+ /*
+ * The clause allows partition-wise join if only it uses the same
+ * operator family as that specified by the partition key.
+ */
+ if (!list_member_oid(rinfo->mergeopfamilies,
+ part_scheme->partopfamily[ipk1]))
+ continue;
+
+ /* Mark the partition key as having an equi-join clause. */
+ pk_has_clause[ipk1] = true;
+ }
+
+ /* Check whether every partition key has an equi-join condition. */
+ for (cnt_pks = 0; cnt_pks < num_pks; cnt_pks++)
+ {
+ if (!pk_has_clause[cnt_pks])
+ {
+ pfree(pk_has_clause);
+ return false;
+ }
+ }
+
+ pfree(pk_has_clause);
+ return true;
+ }
+
+ /*
+ * Find the partition key from the given relation matching the given
+ * expression. If found, return the index of the partition key, else return -1.
+ */
+ static int
+ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel)
+ {
+ int cnt_pks;
+ int num_pks;
+
+ /* This function should be called only for partitioned relations. */
+ Assert(rel->part_scheme);
+
+ num_pks = rel->part_scheme->partnatts;
+
+ /* Remove the relabel decoration. */
+ while (IsA(expr, RelabelType))
+ expr = (Expr *) (castNode(RelabelType, expr))->arg;
+
+ for (cnt_pks = 0; cnt_pks < num_pks; cnt_pks++)
+ {
+ List *pkexprs = rel->partexprs[cnt_pks];
+ ListCell *lc;
+
+ foreach(lc, pkexprs)
+ {
+ Expr *pkexpr = lfirst(lc);
+ if (equal(pkexpr, expr))
+ return cnt_pks;
+ }
+ }
+
+ return -1;
+ }
+
+ /*
+ * Calculate the bounds/lists of the join relation based on partition bounds of the
+ * joining relations. Also returns the matching partitions from the joining
+ * relations.
+ *
+ * As of now, it simply checks whether the bounds/lists of the joining
+ * relations match and returns bounds/lists of the first relation. In future
+ * this function will be expanded to merge the bounds/lists from the joining
+ * relations to produce the bounds/lists of the join relation. If the function
+ * fails to merge the bounds/lists, it returns NULL and the lists are also NIL.
+ *
+ * The function also returns two lists of RelOptInfos, one for each joining
+ * relation. The RelOptInfos at the same position in each of the lists give the
+ * partitions with matching bounds which can be joined to produce join relation
+ * corresponding to the merged partition bounds corresponding to that position.
+ * When there doesn't exist a matching partition on either side, corresponding
+ * RelOptInfo will be NULL.
+ */
+ static void
+ build_joinrel_partition_bounds(RelOptInfo *rel1, RelOptInfo *rel2,
+ RelOptInfo *joinrel, JoinType jointype,
+ List **rel1_parts, List **rel2_parts)
+ {
+ PartitionScheme part_scheme;
+ int cnt;
+ int nparts;
+ int16 *parttyplen;
+ bool *parttypbyval;
+
+ Assert(rel1->part_scheme == rel2->part_scheme);
+ Assert(rel1->nparts == rel2->nparts);
+ *rel1_parts = NIL;
+ *rel2_parts = NIL;
+
+ part_scheme = rel1->part_scheme;
+
+ /*
+ * Ideally, we should be able to join two relations which have different
+ * number of partitions as long as the bounds of partitions available on
+ * both the sides match. But for now, we need exact same number of
+ * partitions on both the sides.
+ */
+ if (rel1->nparts != rel2->nparts)
+ {
+ /*
+ * If this pair of joining relations did not have same number of
+ * partitions no other pair can have same number of partitions.
+ */
+ Assert(!joinrel->boundinfo && joinrel->nparts == 0);
+ return;
+ }
+
+
+ parttyplen = (int16 *) palloc(sizeof(int16) * part_scheme->partnatts);
+ parttypbyval = (bool *) palloc(sizeof(bool) * part_scheme->partnatts);
+ for (cnt = 0; cnt < part_scheme->partnatts; cnt++)
+ get_typlenbyval(part_scheme->partopcintype[cnt], &parttyplen[cnt],
+ &parttypbyval[cnt]);
+
+ if (!partition_bounds_equal(part_scheme->partnatts, parttyplen,
+ parttypbyval, rel1->boundinfo,
+ rel2->boundinfo))
+ {
+ /*
+ * If this pair of joining relations did not have same partition bounds
+ * no other pair can have same partition bounds.
+ */
+ Assert(!joinrel->boundinfo && joinrel->nparts == 0);
+ return;
+ }
+
+ nparts = rel1->nparts;
+ for (cnt = 0; cnt < nparts; cnt++)
+ {
+ *rel1_parts = lappend(*rel1_parts, rel1->part_rels[cnt]);
+ *rel2_parts = lappend(*rel2_parts, rel2->part_rels[cnt]);
+ }
+
+ /* Set the partition bounds if not already set. */
+ if (!joinrel->boundinfo)
+ {
+ joinrel->boundinfo = rel1->boundinfo;
+ joinrel->nparts = rel1->nparts;
+ }
+ else
+ {
+ /* Verify existing bounds. */
+ Assert(partition_bounds_equal(part_scheme->partnatts, parttyplen,
+ parttypbyval, joinrel->boundinfo,
+ rel1->boundinfo));
+ Assert(joinrel->nparts == rel1->nparts);
+ }
+
+ pfree(parttyplen);
+ pfree(parttypbyval);
+ }
diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c
new file mode 100644
index a2fe661..91d855c
*** a/src/backend/optimizer/path/tidpath.c
--- b/src/backend/optimizer/path/tidpath.c
*************** create_tidscan_paths(PlannerInfo *root,
*** 266,270 ****
if (tidquals)
add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
! required_outer));
}
--- 266,270 ----
if (tidquals)
add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
! required_outer), false);
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
new file mode 100644
index 95e6eb7..3f1389f
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** static Plan *prepare_sort_from_pathkeys(
*** 252,258 ****
static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec,
TargetEntry *tle,
Relids relids);
! static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys);
static Sort *make_sort_from_groupcols(List *groupcls,
AttrNumber *grpColIdx,
Plan *lefttree);
--- 252,259 ----
static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec,
TargetEntry *tle,
Relids relids);
! static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
! Relids relids);
static Sort *make_sort_from_groupcols(List *groupcls,
AttrNumber *grpColIdx,
Plan *lefttree);
*************** create_sort_plan(PlannerInfo *root, Sort
*** 1650,1656 ****
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_SMALL_TLIST);
! plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys);
copy_generic_path_info(&plan->plan, (Path *) best_path);
--- 1651,1657 ----
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_SMALL_TLIST);
! plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys, NULL);
copy_generic_path_info(&plan->plan, (Path *) best_path);
*************** create_mergejoin_plan(PlannerInfo *root,
*** 3767,3772 ****
--- 3768,3775 ----
ListCell *lc;
ListCell *lop;
ListCell *lip;
+ Path *outer_path = best_path->jpath.outerjoinpath;
+ Path *inner_path = best_path->jpath.innerjoinpath;
/*
* MergeJoin can project, so we don't have to demand exact tlists from the
*************** create_mergejoin_plan(PlannerInfo *root,
*** 3830,3837 ****
*/
if (best_path->outersortkeys)
{
Sort *sort = make_sort_from_pathkeys(outer_plan,
! best_path->outersortkeys);
label_sort_with_costsize(root, sort, -1.0);
outer_plan = (Plan *) sort;
--- 3833,3842 ----
*/
if (best_path->outersortkeys)
{
+ Relids outer_relids = outer_path->parent->relids;
Sort *sort = make_sort_from_pathkeys(outer_plan,
! best_path->outersortkeys,
! outer_relids);
label_sort_with_costsize(root, sort, -1.0);
outer_plan = (Plan *) sort;
*************** create_mergejoin_plan(PlannerInfo *root,
*** 3842,3849 ****
if (best_path->innersortkeys)
{
Sort *sort = make_sort_from_pathkeys(inner_plan,
! best_path->innersortkeys);
label_sort_with_costsize(root, sort, -1.0);
inner_plan = (Plan *) sort;
--- 3847,3856 ----
if (best_path->innersortkeys)
{
+ Relids inner_relids = inner_path->parent->relids;
Sort *sort = make_sort_from_pathkeys(inner_plan,
! best_path->innersortkeys,
! inner_relids);
label_sort_with_costsize(root, sort, -1.0);
inner_plan = (Plan *) sort;
*************** prepare_sort_from_pathkeys(Plan *lefttre
*** 5687,5697 ****
continue;
/*
! * Ignore child members unless they match the rel being
* sorted.
*/
if (em->em_is_child &&
! !bms_equal(em->em_relids, relids))
continue;
sortexpr = em->em_expr;
--- 5694,5704 ----
continue;
/*
! * Ignore child members unless they belong to the rel being
* sorted.
*/
if (em->em_is_child &&
! !bms_is_subset(em->em_relids, relids))
continue;
sortexpr = em->em_expr;
*************** find_ec_member_for_tle(EquivalenceClass
*** 5803,5812 ****
continue;
/*
! * Ignore child members unless they match the rel being sorted.
*/
if (em->em_is_child &&
! !bms_equal(em->em_relids, relids))
continue;
/* Match if same expression (after stripping relabel) */
--- 5810,5819 ----
continue;
/*
! * Ignore child members unless they belong to the rel being sorted.
*/
if (em->em_is_child &&
! !bms_is_subset(em->em_relids, relids))
continue;
/* Match if same expression (after stripping relabel) */
*************** find_ec_member_for_tle(EquivalenceClass
*** 5827,5835 ****
*
* 'lefttree' is the node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted
*/
static Sort *
! make_sort_from_pathkeys(Plan *lefttree, List *pathkeys)
{
int numsortkeys;
AttrNumber *sortColIdx;
--- 5834,5843 ----
*
* 'lefttree' is the node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted
+ * 'relids' is the set of relations required by prepare_sort_from_pathkeys()
*/
static Sort *
! make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
{
int numsortkeys;
AttrNumber *sortColIdx;
*************** make_sort_from_pathkeys(Plan *lefttree,
*** 5839,5845 ****
/* Compute sort column info, and adjust lefttree as needed */
lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
! NULL,
NULL,
false,
&numsortkeys,
--- 5847,5853 ----
/* Compute sort column info, and adjust lefttree as needed */
lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
! relids,
NULL,
false,
&numsortkeys,
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
new file mode 100644
index ebd442a..0313c71
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 14,20 ****
--- 14,22 ----
*/
#include "postgres.h"
+ #include "access/sysattr.h"
#include "catalog/pg_type.h"
+ #include "catalog/pg_class.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
***************
*** 26,31 ****
--- 28,34 ----
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+ #include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
#include "rewrite/rewriteManip.h"
*************** typedef struct PostponedQual
*** 45,50 ****
--- 48,54 ----
} PostponedQual;
+ static void create_grouped_var_infos(PlannerInfo *root);
static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
Index rtindex);
static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
*************** add_vars_to_targetlist(PlannerInfo *root
*** 240,245 ****
--- 244,533 ----
}
}
+ /*
+ * Add GroupedVarInfo to grouped_var_list for each aggregate and setup
+ * GroupedPathInfo for each base relation that can product grouped paths.
+ *
+ * XXX In the future we might want to create GroupedVarInfo for grouping
+ * expressions too, so that grouping key is not limited to plain Var if the
+ * grouping takes place below the top-level join.
+ *
+ * root->group_pathkeys must be setup before this function is called.
+ */
+ extern void
+ add_grouping_info_to_base_rels(PlannerInfo *root)
+ {
+ int i;
+
+ /* No grouping in the query? */
+ if (!root->parse->groupClause || root->group_pathkeys == NIL)
+ return;
+
+ /* TODO This is just for PoC. Relax the limitation later. */
+ if (root->parse->havingQual)
+ return;
+
+ /* Create GroupedVarInfo per (distinct) aggregate. */
+ create_grouped_var_infos(root);
+
+ /* Is no grouping is possible below the top-level join? */
+ if (root->grouped_var_list == NIL)
+ return;
+
+ /* Process the individual base relations. */
+ for (i = 1; i < root->simple_rel_array_size; i++)
+ {
+ RelOptInfo *rel = root->simple_rel_array[i];
+
+ /*
+ * "other rels" will have their targets built later, by translation of
+ * the target of the parent rel - see set_append_rel_size. If we
+ * wanted to prepare the child rels here, we'd need another iteration
+ * of simple_rel_array_size.
+ */
+ if (rel != NULL && rel->reloptkind == RELOPT_BASEREL)
+ prepare_rel_for_grouping(root, rel);
+ }
+ }
+
+ /*
+ * Create GroupedVarInfo for each distinct aggregate.
+ *
+ * If any aggregate is not suitable, set root->grouped_var_list to NIL and
+ * return.
+ *
+ * TODO Include aggregates from HAVING clause.
+ */
+ static void
+ create_grouped_var_infos(PlannerInfo *root)
+ {
+ List *tlist_exprs;
+ ListCell *lc;
+
+ Assert(root->grouped_var_list == NIL);
+
+ /*
+ * TODO Check if processed_tlist contains the HAVING aggregates. If not,
+ * get them elsewhere.
+ */
+ tlist_exprs = pull_var_clause((Node *) root->processed_tlist,
+ PVC_INCLUDE_AGGREGATES);
+ if (tlist_exprs == NIL)
+ return;
+
+ /* tlist_exprs may also contain Vars, but we only need Aggrefs. */
+ foreach(lc, tlist_exprs)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ Aggref *aggref;
+ ListCell *lc2;
+ GroupedVarInfo *gvi;
+ bool exists;
+
+ if (IsA(expr, Var))
+ continue;
+
+ aggref = castNode(Aggref, expr);
+
+ /* TODO Think if (some of) these can be handled. */
+ if (aggref->aggvariadic ||
+ aggref->aggdirectargs || aggref->aggorder ||
+ aggref->aggdistinct || aggref->aggfilter)
+ {
+ /*
+ * Partial aggregation is not useful if at least one aggregate
+ * cannot be evaluated below the top-level join.
+ *
+ * XXX Is it worth freeing the GroupedVarInfos and their subtrees?
+ */
+ root->grouped_var_list = NIL;
+ break;
+ }
+
+ /* Does GroupedVarInfo for this aggregate already exist? */
+ exists = false;
+ foreach(lc2, root->grouped_var_list)
+ {
+ Expr *expr = (Expr *) lfirst(lc2);
+
+ gvi = castNode(GroupedVarInfo, expr);
+
+ if (equal(expr, gvi->gvexpr))
+ {
+ exists = true;
+ break;
+ }
+ }
+
+ /* Construct a new GroupedVarInfo if does not exist yet. */
+ if (!exists)
+ {
+ Relids relids;
+
+ /* TODO Initialize gv_width. */
+ gvi = makeNode(GroupedVarInfo);
+
+ gvi->gvid = list_length(root->grouped_var_list);
+ gvi->gvexpr = (Expr *) copyObject(aggref);
+ gvi->agg_partial = copyObject(aggref);
+ mark_partial_aggref(gvi->agg_partial, AGGSPLIT_INITIAL_SERIAL);
+
+ /* Find out where the aggregate should be evaluated. */
+ relids = pull_varnos((Node *) aggref);
+ if (!bms_is_empty(relids))
+ gvi->gv_eval_at = relids;
+ else
+ {
+ Assert(aggref->aggstar);
+ gvi->gv_eval_at = NULL;
+ }
+
+ root->grouped_var_list = lappend(root->grouped_var_list, gvi);
+ }
+ }
+
+ list_free(tlist_exprs);
+ }
+
+ /*
+ * Check if all the expressions of rel->reltarget can be used as grouping
+ * expressions and create target for grouped paths.
+ *
+ * If we succeed to create the grouping target, also replace rel->reltarget
+ * with a new one that has sortgrouprefs initialized -- this is necessary for
+ * create_agg_plan to match the grouping clauses against the input target
+ * expressions.
+ *
+ * rel_agg_attrs is a set attributes of the relation referenced by aggregate
+ * arguments. These can exist in the (plain) target without being grouping
+ * expressions.
+ *
+ * rel_agg_vars should be passed instead if rel is a join.
+ *
+ * TODO How about PHVs?
+ *
+ * TODO Make sure cost / width of both "result" and "plain" are correct.
+ */
+ PathTarget *
+ create_grouped_target(PlannerInfo *root, RelOptInfo *rel,
+ Relids rel_agg_attrs, List *rel_agg_vars)
+ {
+ PathTarget *result, *plain;
+ ListCell *lc;
+
+ /* The plan to be returned. */
+ result = create_empty_pathtarget();
+ /* The one to replace rel->reltarget. */
+ plain = create_empty_pathtarget();
+
+ foreach(lc, rel->reltarget->exprs)
+ {
+ Expr *texpr;
+ Index sortgroupref;
+ bool agg_arg_only = false;
+
+ texpr = (Expr *) lfirst(lc);
+
+ sortgroupref = get_expr_sortgroupref(root, texpr);
+ if (sortgroupref > 0)
+ {
+ /* It's o.k. to use the target expression for grouping. */
+ add_column_to_pathtarget(result, texpr, sortgroupref);
+
+ /*
+ * As for the plain target, add the original expression but set
+ * sortgroupref in addition.
+ */
+ add_column_to_pathtarget(plain, texpr, sortgroupref);
+
+ /* Process the next expression. */
+ continue;
+ }
+
+ /*
+ * It may still be o.k. if the expression is only contained in Aggref
+ * - then it's not expected in the grouped output.
+ *
+ * TODO Try to handle generic expression, not only Var. That might
+ * require us to create rel->reltarget of the grouping rel in
+ * parallel to that of the plain rel, and adding whole expressions
+ * instead of individual vars.
+ */
+ if (IsA(texpr, Var))
+ {
+ Var *arg_var = castNode(Var, texpr);
+
+ if (rel->relid > 0)
+ {
+ AttrNumber varattno;
+
+ /*
+ * For a single relation we only need to check attribute
+ * number.
+ *
+ * Apply the same offset that pull_varattnos() did.
+ */
+ varattno = arg_var->varattno - FirstLowInvalidHeapAttributeNumber;
+
+ if (bms_is_member(varattno, rel_agg_attrs))
+ agg_arg_only = true;
+ }
+ else
+ {
+ ListCell *lc2;
+
+ /* Join case. */
+ foreach(lc2, rel_agg_vars)
+ {
+ Var *var = castNode(Var, lfirst(lc2));
+
+ if (var->varno == arg_var->varno &&
+ var->varattno == arg_var->varattno)
+ {
+ agg_arg_only = true;
+ break;
+ }
+ }
+ }
+
+ if (agg_arg_only)
+ {
+ /*
+ * This expression is not suitable for grouping, but the
+ * aggregation input target ought to stay complete.
+ */
+ add_column_to_pathtarget(plain, texpr, 0);
+ }
+ }
+
+ /*
+ * A single mismatched expression makes the whole relation useless
+ * for grouping.
+ */
+ if (!agg_arg_only)
+ {
+ /*
+ * TODO This seems possible to happen multiple times per relation,
+ * so result might be worth freeing. Implement free_pathtarget()?
+ * Or mark the relation as inappropriate for grouping?
+ */
+ /* TODO Free both result and plain. */
+ return NULL;
+ }
+ }
+
+ if (list_length(result->exprs) == 0)
+ {
+ /* TODO free_pathtarget(result); free_pathtarget(plain) */
+ result = NULL;
+ }
+
+ /* Apply the adjusted input target as the replacement is complete now.q */
+ rel->reltarget = plain;
+
+ return result;
+ }
+
/*****************************************************************************
*
*************** create_lateral_join_info(PlannerInfo *ro
*** 629,639 ****
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RelOptInfo *brel = root->simple_rel_array[rti];
! if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
continue;
! if (root->simple_rte_array[rti]->inh)
{
foreach(lc, root->append_rel_list)
{
--- 917,941 ----
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RelOptInfo *brel = root->simple_rel_array[rti];
+ RangeTblEntry *brte = root->simple_rte_array[rti];
! if (brel == NULL)
continue;
! /*
! * If an "other rel" RTE is a "partitioned table", we must propagate
! * the lateral info inherited all the way from the root parent to its
! * children. That's because the children are not linked directly with
! * the root parent via AppendRelInfo's unlike in case of a regular
! * inheritance set (see expand_inherited_rtentry()). Failing to
! * do this would result in those children not getting marked with the
! * appropriate lateral info.
! */
! if (brel->reloptkind != RELOPT_BASEREL &&
! brte->relkind != RELKIND_PARTITIONED_TABLE)
! continue;
!
! if (brte->inh)
{
foreach(lc, root->append_rel_list)
{
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
new file mode 100644
index 5565736..058af2c
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
*************** preprocess_minmax_aggregates(PlannerInfo
*** 223,229 ****
create_minmaxagg_path(root, grouped_rel,
create_pathtarget(root, tlist),
aggs_list,
! (List *) parse->havingQual));
}
/*
--- 223,229 ----
create_minmaxagg_path(root, grouped_rel,
create_pathtarget(root, tlist),
aggs_list,
! (List *) parse->havingQual), false);
}
/*
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
new file mode 100644
index ef0de3f..f70b445
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 83,89 ****
add_path(final_rel, (Path *)
create_result_path(root, final_rel,
final_rel->reltarget,
! (List *) parse->jointree->quals));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(final_rel);
--- 83,89 ----
add_path(final_rel, (Path *)
create_result_path(root, final_rel,
final_rel->reltarget,
! (List *) parse->jointree->quals), false);
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(final_rel);
*************** query_planner(PlannerInfo *root, List *t
*** 114,119 ****
--- 114,120 ----
root->full_join_clauses = NIL;
root->join_info_list = NIL;
root->placeholder_list = NIL;
+ root->grouped_var_list = NIL;
root->fkey_list = NIL;
root->initial_rels = NIL;
*************** query_planner(PlannerInfo *root, List *t
*** 177,182 ****
--- 178,191 ----
(*qp_callback) (root, qp_extra);
/*
+ * If the query result can be grouped, check if any grouping can be
+ * performed below the top-level join. If so, Initialize GroupedPathInfo
+ * of base relations capable to do the grouping and setup
+ * root->grouped_var_list.
+ */
+ add_grouping_info_to_base_rels(root);
+
+ /*
* Examine any "placeholder" expressions generated during subquery pullup.
* Make sure that the Vars they need are marked as needed at the relevant
* join level. This must be done before join removal because it might
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 649a233..d47f635
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** typedef struct
*** 108,117 ****
--- 108,135 ----
int *tleref_to_colnum_map;
} grouping_sets_data;
+ /* Result of a given invocation of inheritance_planner_guts() */
+ typedef struct
+ {
+ Index nominalRelation;
+ List *partitioned_rels;
+ List *resultRelations;
+ List *subpaths;
+ List *subroots;
+ List *withCheckOptionLists;
+ List *returningLists;
+ List *final_rtable;
+ List *init_plans;
+ int save_rel_array_size;
+ RelOptInfo **save_rel_array;
+ } inheritance_planner_result;
+
/* Local functions */
static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
static void inheritance_planner(PlannerInfo *root);
+ static void inheritance_planner_guts(PlannerInfo *root,
+ inheritance_planner_result *inhpres);
static void grouping_planner(PlannerInfo *root, bool inheritance_update,
double tuple_fraction);
static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root);
*************** static void standard_qp_callback(Planner
*** 130,138 ****
static double get_number_of_groups(PlannerInfo *root,
double path_rows,
grouping_sets_data *gd);
- static Size estimate_hashagg_tablesize(Path *path,
- const AggClauseCosts *agg_costs,
- double dNumGroups);
static RelOptInfo *create_grouping_paths(PlannerInfo *root,
RelOptInfo *input_rel,
PathTarget *target,
--- 148,153 ----
*************** preprocess_phv_expression(PlannerInfo *r
*** 1020,1044 ****
static void
inheritance_planner(PlannerInfo *root)
{
Query *parse = root->parse;
int parentRTindex = parse->resultRelation;
Bitmapset *subqueryRTindexes;
Bitmapset *modifiableARIindexes;
! int nominalRelation = -1;
! List *final_rtable = NIL;
! int save_rel_array_size = 0;
! RelOptInfo **save_rel_array = NULL;
! List *subpaths = NIL;
! List *subroots = NIL;
! List *resultRelations = NIL;
! List *withCheckOptionLists = NIL;
! List *returningLists = NIL;
! List *rowMarks;
! RelOptInfo *final_rel;
ListCell *lc;
Index rti;
RangeTblEntry *parent_rte;
- List *partitioned_rels = NIL;
Assert(parse->commandType != CMD_INSERT);
--- 1035,1139 ----
static void
inheritance_planner(PlannerInfo *root)
{
+ inheritance_planner_result inhpres;
+ Query *parse = root->parse;
+ RelOptInfo *final_rel;
+ Index rti;
+ int final_rtable_len;
+ ListCell *lc;
+ List *rowMarks;
+
+ /*
+ * Away we go... Although the inheritance hierarchy to be processed might
+ * be represented in a non-flat manner, some of the elements needed to
+ * create the final ModifyTable path are always returned in a flat list
+ * structure.
+ */
+ memset(&inhpres, 0, sizeof(inhpres));
+ inheritance_planner_guts(root, &inhpres);
+
+ /* Result path must go into outer query's FINAL upperrel */
+ final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
+
+ /*
+ * We don't currently worry about setting final_rel's consider_parallel
+ * flag in this case, nor about allowing FDWs or create_upper_paths_hook
+ * to get control here.
+ */
+
+ /*
+ * If we managed to exclude every child rel, return a dummy plan; it
+ * doesn't even need a ModifyTable node.
+ */
+ if (inhpres.subpaths == NIL)
+ {
+ set_dummy_rel_pathlist(final_rel);
+ return;
+ }
+
+ /*
+ * Put back the final adjusted rtable into the master copy of the Query.
+ * (We mustn't do this if we found no non-excluded children.)
+ */
+ parse->rtable = inhpres.final_rtable;
+ root->simple_rel_array_size = inhpres.save_rel_array_size;
+ root->simple_rel_array = inhpres.save_rel_array;
+ /* Must reconstruct master's simple_rte_array, too */
+ final_rtable_len = list_length(inhpres.final_rtable);
+ root->simple_rte_array = (RangeTblEntry **)
+ palloc0((final_rtable_len + 1) *
+ sizeof(RangeTblEntry *));
+ rti = 1;
+ foreach(lc, inhpres.final_rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+ root->simple_rte_array[rti++] = rte;
+ }
+
+ /*
+ * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
+ * have dealt with fetching non-locked marked rows, else we need to have
+ * ModifyTable do that.
+ */
+ if (parse->rowMarks)
+ rowMarks = NIL;
+ else
+ rowMarks = root->rowMarks;
+
+ /* Create Path representing a ModifyTable to do the UPDATE/DELETE work */
+ add_path(final_rel, (Path *)
+ create_modifytable_path(root, final_rel,
+ parse->commandType,
+ parse->canSetTag,
+ inhpres.nominalRelation,
+ inhpres.partitioned_rels,
+ inhpres.resultRelations,
+ inhpres.subpaths,
+ inhpres.subroots,
+ inhpres.withCheckOptionLists,
+ inhpres.returningLists,
+ rowMarks,
+ NULL,
+ SS_assign_special_param(root)), false);
+ }
+
+ /*
+ * inheritance_planner_guts
+ * Recursive guts of inheritance_planner
+ */
+ static void
+ inheritance_planner_guts(PlannerInfo *root,
+ inheritance_planner_result *inhpres)
+ {
Query *parse = root->parse;
int parentRTindex = parse->resultRelation;
Bitmapset *subqueryRTindexes;
Bitmapset *modifiableARIindexes;
! bool nominalRelationSet = false;
ListCell *lc;
Index rti;
RangeTblEntry *parent_rte;
Assert(parse->commandType != CMD_INSERT);
*************** inheritance_planner(PlannerInfo *root)
*** 1106,1112 ****
*/
parent_rte = rt_fetch(parentRTindex, root->parse->rtable);
if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
! nominalRelation = parentRTindex;
/*
* And now we can get on with generating a plan for each child table.
--- 1201,1210 ----
*/
parent_rte = rt_fetch(parentRTindex, root->parse->rtable);
if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
! {
! inhpres->nominalRelation = parentRTindex;
! nominalRelationSet = true;
! }
/*
* And now we can get on with generating a plan for each child table.
*************** inheritance_planner(PlannerInfo *root)
*** 1115,1120 ****
--- 1213,1219 ----
{
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
PlannerInfo *subroot;
+ Index childRTindex = appinfo->child_relid;
RangeTblEntry *child_rte;
RelOptInfo *sub_final_rel;
Path *subpath;
*************** inheritance_planner(PlannerInfo *root)
*** 1136,1152 ****
* references to the parent RTE to refer to the current child RTE,
* then fool around with subquery RTEs.
*/
! subroot->parse = (Query *)
! adjust_appendrel_attrs(root,
! (Node *) parse,
! appinfo);
/*
* If there are securityQuals attached to the parent, move them to the
* child rel (they've already been transformed properly for that).
*/
parent_rte = rt_fetch(parentRTindex, subroot->parse->rtable);
! child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable);
child_rte->securityQuals = parent_rte->securityQuals;
parent_rte->securityQuals = NIL;
--- 1235,1249 ----
* references to the parent RTE to refer to the current child RTE,
* then fool around with subquery RTEs.
*/
! subroot->parse = (Query *) adjust_appendrel_attrs(root, (Node *) parse,
! 1, &appinfo);
/*
* If there are securityQuals attached to the parent, move them to the
* child rel (they've already been transformed properly for that).
*/
parent_rte = rt_fetch(parentRTindex, subroot->parse->rtable);
! child_rte = rt_fetch(childRTindex, subroot->parse->rtable);
child_rte->securityQuals = parent_rte->securityQuals;
parent_rte->securityQuals = NIL;
*************** inheritance_planner(PlannerInfo *root)
*** 1191,1197 ****
* These won't be referenced, so there's no need to make them very
* valid-looking.
*/
! while (list_length(subroot->parse->rtable) < list_length(final_rtable))
subroot->parse->rtable = lappend(subroot->parse->rtable,
makeNode(RangeTblEntry));
--- 1288,1295 ----
* These won't be referenced, so there's no need to make them very
* valid-looking.
*/
! while (list_length(subroot->parse->rtable) <
! list_length(inhpres->final_rtable))
subroot->parse->rtable = lappend(subroot->parse->rtable,
makeNode(RangeTblEntry));
*************** inheritance_planner(PlannerInfo *root)
*** 1203,1209 ****
* since subquery RTEs couldn't contain any references to the target
* rel.
*/
! if (final_rtable != NIL && subqueryRTindexes != NULL)
{
ListCell *lr;
--- 1301,1307 ----
* since subquery RTEs couldn't contain any references to the target
* rel.
*/
! if (inhpres->final_rtable != NIL && subqueryRTindexes != NULL)
{
ListCell *lr;
*************** inheritance_planner(PlannerInfo *root)
*** 1248,1253 ****
--- 1346,1392 ----
}
}
+ /*
+ * Recurse for a partitioned child table. We shouldn't be planning
+ * a partitioned RTE as a child member, which is what the code after
+ * this block does.
+ */
+ if (child_rte->inh)
+ {
+ inheritance_planner_result child_inhpres;
+
+ Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
+
+ /* During the recursive invocation, this child is the parent. */
+ subroot->parse->resultRelation = childRTindex;
+ memset(&child_inhpres, 0, sizeof(child_inhpres));
+ inheritance_planner_guts(subroot, &child_inhpres);
+
+ inhpres->partitioned_rels = list_concat(inhpres->partitioned_rels,
+ child_inhpres.partitioned_rels);
+ inhpres->resultRelations = list_concat(inhpres->resultRelations,
+ child_inhpres.resultRelations);
+ inhpres->subpaths = list_concat(inhpres->subpaths,
+ child_inhpres.subpaths);
+ inhpres->subroots = list_concat(inhpres->subroots,
+ child_inhpres.subroots);
+ inhpres->withCheckOptionLists =
+ list_concat(inhpres->withCheckOptionLists,
+ child_inhpres.withCheckOptionLists);
+ inhpres->returningLists = list_concat(inhpres->returningLists,
+ child_inhpres.returningLists);
+ if (child_inhpres.final_rtable != NIL)
+ inhpres->final_rtable = child_inhpres.final_rtable;
+ if (child_inhpres.init_plans != NIL)
+ inhpres->init_plans = child_inhpres.init_plans;
+ if (child_inhpres.save_rel_array_size != 0)
+ {
+ inhpres->save_rel_array_size = child_inhpres.save_rel_array_size;
+ inhpres->save_rel_array = child_inhpres.save_rel_array;
+ }
+ continue;
+ }
+
/* There shouldn't be any OJ info to translate, as yet */
Assert(subroot->join_info_list == NIL);
/* and we haven't created PlaceHolderInfos, either */
*************** inheritance_planner(PlannerInfo *root)
*** 1279,1286 ****
* the duplicate child RTE added for the parent does not appear
* anywhere else in the plan tree.
*/
! if (nominalRelation < 0)
! nominalRelation = appinfo->child_relid;
/*
* Select cheapest path in case there's more than one. We always run
--- 1418,1428 ----
* the duplicate child RTE added for the parent does not appear
* anywhere else in the plan tree.
*/
! if (!nominalRelationSet)
! {
! inhpres->nominalRelation = childRTindex;
! nominalRelationSet = true;
! }
/*
* Select cheapest path in case there's more than one. We always run
*************** inheritance_planner(PlannerInfo *root)
*** 1303,1314 ****
* becomes the initial contents of final_rtable; otherwise, append
* just its modified subquery RTEs to final_rtable.
*/
! if (final_rtable == NIL)
! final_rtable = subroot->parse->rtable;
else
! final_rtable = list_concat(final_rtable,
! list_copy_tail(subroot->parse->rtable,
! list_length(final_rtable)));
/*
* We need to collect all the RelOptInfos from all child plans into
--- 1445,1456 ----
* becomes the initial contents of final_rtable; otherwise, append
* just its modified subquery RTEs to final_rtable.
*/
! if (inhpres->final_rtable == NIL)
! inhpres->final_rtable = subroot->parse->rtable;
else
! inhpres->final_rtable = list_concat(inhpres->final_rtable,
! list_copy_tail(subroot->parse->rtable,
! list_length(inhpres->final_rtable)));
/*
* We need to collect all the RelOptInfos from all child plans into
*************** inheritance_planner(PlannerInfo *root)
*** 1317,1425 ****
* have to propagate forward the RelOptInfos that were already built
* in previous children.
*/
! Assert(subroot->simple_rel_array_size >= save_rel_array_size);
! for (rti = 1; rti < save_rel_array_size; rti++)
{
! RelOptInfo *brel = save_rel_array[rti];
if (brel)
subroot->simple_rel_array[rti] = brel;
}
! save_rel_array_size = subroot->simple_rel_array_size;
! save_rel_array = subroot->simple_rel_array;
/* Make sure any initplans from this rel get into the outer list */
! root->init_plans = subroot->init_plans;
/* Build list of sub-paths */
! subpaths = lappend(subpaths, subpath);
/* Build list of modified subroots, too */
! subroots = lappend(subroots, subroot);
/* Build list of target-relation RT indexes */
! resultRelations = lappend_int(resultRelations, appinfo->child_relid);
/* Build lists of per-relation WCO and RETURNING targetlists */
if (parse->withCheckOptions)
! withCheckOptionLists = lappend(withCheckOptionLists,
! subroot->parse->withCheckOptions);
if (parse->returningList)
! returningLists = lappend(returningLists,
! subroot->parse->returningList);
!
Assert(!parse->onConflict);
}
if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
{
! partitioned_rels = get_partitioned_child_rels(root, parentRTindex);
/* The root partitioned table is included as a child rel */
! Assert(list_length(partitioned_rels) >= 1);
! }
!
! /* Result path must go into outer query's FINAL upperrel */
! final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
!
! /*
! * We don't currently worry about setting final_rel's consider_parallel
! * flag in this case, nor about allowing FDWs or create_upper_paths_hook
! * to get control here.
! */
!
! /*
! * If we managed to exclude every child rel, return a dummy plan; it
! * doesn't even need a ModifyTable node.
! */
! if (subpaths == NIL)
! {
! set_dummy_rel_pathlist(final_rel);
! return;
! }
!
! /*
! * Put back the final adjusted rtable into the master copy of the Query.
! * (We mustn't do this if we found no non-excluded children.)
! */
! parse->rtable = final_rtable;
! root->simple_rel_array_size = save_rel_array_size;
! root->simple_rel_array = save_rel_array;
! /* Must reconstruct master's simple_rte_array, too */
! root->simple_rte_array = (RangeTblEntry **)
! palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *));
! rti = 1;
! foreach(lc, final_rtable)
! {
! RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
!
! root->simple_rte_array[rti++] = rte;
}
-
- /*
- * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
- * have dealt with fetching non-locked marked rows, else we need to have
- * ModifyTable do that.
- */
- if (parse->rowMarks)
- rowMarks = NIL;
- else
- rowMarks = root->rowMarks;
-
- /* Create Path representing a ModifyTable to do the UPDATE/DELETE work */
- add_path(final_rel, (Path *)
- create_modifytable_path(root, final_rel,
- parse->commandType,
- parse->canSetTag,
- nominalRelation,
- partitioned_rels,
- resultRelations,
- subpaths,
- subroots,
- withCheckOptionLists,
- returningLists,
- rowMarks,
- NULL,
- SS_assign_special_param(root)));
}
/*--------------------
--- 1459,1506 ----
* have to propagate forward the RelOptInfos that were already built
* in previous children.
*/
! Assert(subroot->simple_rel_array_size >= inhpres->save_rel_array_size);
! for (rti = 1; rti < inhpres->save_rel_array_size; rti++)
{
! RelOptInfo *brel = inhpres->save_rel_array[rti];
if (brel)
subroot->simple_rel_array[rti] = brel;
}
! inhpres->save_rel_array_size = subroot->simple_rel_array_size;
! inhpres->save_rel_array = subroot->simple_rel_array;
/* Make sure any initplans from this rel get into the outer list */
! inhpres->init_plans = subroot->init_plans;
/* Build list of sub-paths */
! inhpres->subpaths = lappend(inhpres->subpaths, subpath);
/* Build list of modified subroots, too */
! inhpres->subroots = lappend(inhpres->subroots, subroot);
/* Build list of target-relation RT indexes */
! inhpres->resultRelations = lappend_int(inhpres->resultRelations,
! childRTindex);
/* Build lists of per-relation WCO and RETURNING targetlists */
if (parse->withCheckOptions)
! inhpres->withCheckOptionLists =
! lappend(inhpres->withCheckOptionLists,
! subroot->parse->withCheckOptions);
if (parse->returningList)
! inhpres->returningLists = lappend(inhpres->returningLists,
! subroot->parse->returningList);
Assert(!parse->onConflict);
}
if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
{
! inhpres->partitioned_rels = get_partitioned_child_rels(root,
! parentRTindex);
/* The root partitioned table is included as a child rel */
! Assert(list_length(inhpres->partitioned_rels) >= 1);
}
}
/*--------------------
*************** grouping_planner(PlannerInfo *root, bool
*** 2040,2046 ****
}
/* And shove it into final_rel */
! add_path(final_rel, path);
}
/*
--- 2121,2127 ----
}
/* And shove it into final_rel */
! add_path(final_rel, path, false);
}
/*
*************** get_number_of_groups(PlannerInfo *root,
*** 3446,3485 ****
}
/*
- * estimate_hashagg_tablesize
- * estimate the number of bytes that a hash aggregate hashtable will
- * require based on the agg_costs, path width and dNumGroups.
- *
- * XXX this may be over-estimating the size now that hashagg knows to omit
- * unneeded columns from the hashtable. Also for mixed-mode grouping sets,
- * grouping columns not in the hashed set are counted here even though hashagg
- * won't store them. Is this a problem?
- */
- static Size
- estimate_hashagg_tablesize(Path *path, const AggClauseCosts *agg_costs,
- double dNumGroups)
- {
- Size hashentrysize;
-
- /* Estimate per-hash-entry space at tuple width... */
- hashentrysize = MAXALIGN(path->pathtarget->width) +
- MAXALIGN(SizeofMinimalTupleHeader);
-
- /* plus space for pass-by-ref transition values... */
- hashentrysize += agg_costs->transitionSpace;
- /* plus the per-hash-entry overhead */
- hashentrysize += hash_agg_entry_size(agg_costs->numAggs);
-
- /*
- * Note that this disregards the effect of fill-factor and growth policy
- * of the hash-table. That's probably ok, given default the default
- * fill-factor is relatively high. It'd be hard to meaningfully factor in
- * "double-in-size" growth policies here.
- */
- return hashentrysize * dNumGroups;
- }
-
- /*
* create_grouping_paths
*
* Build a new upperrel containing Paths for grouping and/or aggregation.
--- 3527,3532 ----
*************** create_grouping_paths(PlannerInfo *root,
*** 3600,3606 ****
(List *) parse->havingQual);
}
! add_path(grouped_rel, path);
/* No need to consider any other alternatives. */
set_cheapest(grouped_rel);
--- 3647,3653 ----
(List *) parse->havingQual);
}
! add_path(grouped_rel, path, false);
/* No need to consider any other alternatives. */
set_cheapest(grouped_rel);
*************** create_grouping_paths(PlannerInfo *root,
*** 3777,3783 ****
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups));
else
add_partial_path(grouped_rel, (Path *)
create_group_path(root,
--- 3824,3831 ----
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups),
! false);
else
add_partial_path(grouped_rel, (Path *)
create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3786,3792 ****
partial_grouping_target,
parse->groupClause,
NIL,
! dNumPartialGroups));
}
}
}
--- 3834,3841 ----
partial_grouping_target,
parse->groupClause,
NIL,
! dNumPartialGroups),
! false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3817,3823 ****
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups));
}
}
}
--- 3866,3873 ----
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups),
! false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3869,3875 ****
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups));
}
else if (parse->groupClause)
{
--- 3919,3925 ----
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups), false);
}
else if (parse->groupClause)
{
*************** create_grouping_paths(PlannerInfo *root,
*** 3884,3890 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
}
else
{
--- 3934,3940 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
}
else
{
*************** create_grouping_paths(PlannerInfo *root,
*** 3933,3939 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
else
add_path(grouped_rel, (Path *)
create_group_path(root,
--- 3983,3989 ----
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups), false);
else
add_path(grouped_rel, (Path *)
create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3942,3948 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
/*
* The point of using Gather Merge rather than Gather is that it
--- 3992,3998 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
/*
* The point of using Gather Merge rather than Gather is that it
*************** create_grouping_paths(PlannerInfo *root,
*** 3995,4001 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
else
add_path(grouped_rel, (Path *)
create_group_path(root,
--- 4045,4051 ----
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups), false);
else
add_path(grouped_rel, (Path *)
create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 4004,4010 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
}
}
}
--- 4054,4060 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 4049,4055 ****
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups));
}
}
--- 4099,4105 ----
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups), false);
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 4087,4095 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
}
}
}
/* Give a helpful error if we failed to find any implementation */
--- 4137,4212 ----
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups), false);
}
}
+
+ /*
+ * If input_rel has partially aggregated partial paths, gather them
+ * and perform the final aggregation.
+ *
+ * TODO Allow havingQual - currently not supported at base relation
+ * level.
+ */
+ if (input_rel->gpi != NULL &&
+ input_rel->gpi->partial_pathlist != NIL &&
+ !parse->havingQual)
+ {
+ Path *path = (Path *) linitial(input_rel->gpi->partial_pathlist);
+ double total_groups = path->rows * path->parallel_workers;
+
+ path = (Path *) create_gather_path(root,
+ input_rel,
+ path,
+ path->pathtarget,
+ NULL,
+ &total_groups);
+
+ /*
+ * The input path is partially aggregated and the final
+ * aggregation - if the path wins - will be done below. So we're
+ * done with it for now.
+ *
+ * The top-level grouped_rel needs to receive the path into
+ * regular pathlist, as opposed grouped_rel->gpi->pathlist.
+ */
+ add_path(input_rel, path, false);
+ }
+
+ /*
+ * If input_rel has partially aggregated paths, perform the final
+ * aggregation.
+ *
+ * TODO Allow havingQual - currently not supported at base relation
+ * level.
+ */
+ if (input_rel->gpi != NULL && input_rel->gpi->pathlist != NIL &&
+ !parse->havingQual)
+ {
+ Path *pre_agg = (Path *) linitial(input_rel->gpi->pathlist);
+
+ dNumGroups = get_number_of_groups(root, pre_agg->rows, gd);
+
+ MemSet(&agg_final_costs, 0, sizeof(AggClauseCosts));
+ get_agg_clause_costs(root, (Node *) target->exprs,
+ AGGSPLIT_FINAL_DESERIAL,
+ &agg_final_costs);
+ get_agg_clause_costs(root, parse->havingQual,
+ AGGSPLIT_FINAL_DESERIAL,
+ &agg_final_costs);
+
+ add_path(grouped_rel,
+ (Path *) create_agg_path(root, grouped_rel,
+ pre_agg,
+ target,
+ AGG_HASHED,
+ AGGSPLIT_FINAL_DESERIAL,
+ parse->groupClause,
+ (List *) parse->havingQual,
+ &agg_final_costs,
+ dNumGroups),
+ false);
+ }
}
/* Give a helpful error if we failed to find any implementation */
*************** consider_groupingsets_paths(PlannerInfo
*** 4289,4295 ****
strat,
new_rollups,
agg_costs,
! dNumGroups));
return;
}
--- 4406,4412 ----
strat,
new_rollups,
agg_costs,
! dNumGroups), false);
return;
}
*************** consider_groupingsets_paths(PlannerInfo
*** 4447,4453 ****
AGG_MIXED,
rollups,
agg_costs,
! dNumGroups));
}
}
--- 4564,4570 ----
AGG_MIXED,
rollups,
agg_costs,
! dNumGroups), false);
}
}
*************** consider_groupingsets_paths(PlannerInfo
*** 4464,4470 ****
AGG_SORTED,
gd->rollups,
agg_costs,
! dNumGroups));
}
/*
--- 4581,4587 ----
AGG_SORTED,
gd->rollups,
agg_costs,
! dNumGroups), false);
}
/*
*************** create_one_window_path(PlannerInfo *root
*** 4649,4655 ****
window_pathkeys);
}
! add_path(window_rel, path);
}
/*
--- 4766,4772 ----
window_pathkeys);
}
! add_path(window_rel, path, false);
}
/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4755,4761 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows));
}
}
--- 4872,4878 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
}
*************** create_distinct_paths(PlannerInfo *root,
*** 4782,4788 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows));
}
/*
--- 4899,4905 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4829,4835 ****
parse->distinctClause,
NIL,
NULL,
! numDistinctRows));
}
/* Give a helpful error if we failed to find any implementation */
--- 4946,4952 ----
parse->distinctClause,
NIL,
NULL,
! numDistinctRows), false);
}
/* Give a helpful error if we failed to find any implementation */
*************** create_ordered_paths(PlannerInfo *root,
*** 4927,4933 ****
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path);
}
}
--- 5044,5050 ----
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path, false);
}
}
*************** create_ordered_paths(PlannerInfo *root,
*** 4977,4983 ****
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path);
}
}
--- 5094,5100 ----
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path, false);
}
}
*************** get_partitioned_child_rels(PlannerInfo *
*** 6083,6085 ****
--- 6200,6230 ----
return result;
}
+
+ /*
+ * get_partitioned_child_rels_for_join
+ * Build and return a list containing the RTI of every partitioned
+ * relation which is a child of some rel included in the join.
+ *
+ * Note: Only call this function on joins between partitioned tables.
+ */
+ List *
+ get_partitioned_child_rels_for_join(PlannerInfo *root,
+ RelOptInfo *joinrel)
+ {
+ List *result = NIL;
+ ListCell *l;
+
+ foreach(l, root->pcinfo_list)
+ {
+ PartitionedChildRelInfo *pc = lfirst(l);
+
+ if (bms_is_member(pc->parent_relid, joinrel->relids))
+ result = list_concat(result, list_copy(pc->child_rels));
+ }
+
+ /* The root partitioned table is included as a child rel */
+ Assert(list_length(result) >= bms_num_members(joinrel->relids));
+
+ return result;
+ }
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index 1278371..44c3919
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** typedef struct
*** 40,46 ****
--- 40,50 ----
List *tlist; /* underlying target list */
int num_vars; /* number of plain Var tlist entries */
bool has_ph_vars; /* are there PlaceHolderVar entries? */
+ bool has_grp_vars; /* are there GroupedVar entries? */
bool has_non_vars; /* are there other entries? */
+ bool has_conv_whole_rows; /* are there ConvertRowtypeExpr entries
+ * encapsulating a whole-row Var?
+ */
tlist_vinfo vars[FLEXIBLE_ARRAY_MEMBER]; /* has num_vars entries */
} indexed_tlist;
*************** static List *set_returning_clause_refere
*** 139,144 ****
--- 143,149 ----
int rtoffset);
static bool extract_query_dependencies_walker(Node *node,
PlannerInfo *context);
+ static Var *get_wholerow_ref_from_convert_row_type(Node *node);
/*****************************************************************************
*
*************** set_upper_references(PlannerInfo *root,
*** 1725,1733 ****
--- 1730,1781 ----
indexed_tlist *subplan_itlist;
List *output_targetlist;
ListCell *l;
+ List *sub_tlist_save = NIL;
+
+ if (root->grouped_var_list != NIL)
+ {
+ if (IsA(plan, Agg))
+ {
+ Agg *agg = (Agg *) plan;
+
+ if (agg->aggsplit == AGGSPLIT_FINAL_DESERIAL)
+ {
+ /*
+ * convert_combining_aggrefs could have replaced some vars
+ * with Aggref expressions representing the partial
+ * aggregation. We need to restore the same Aggrefs in the
+ * subplan targetlist, but this would break the subplan if
+ * it's something else than the partial aggregation (i.e. the
+ * partial aggregation takes place lower in the plan tree). So
+ * we'll eventually need to restore the original list.
+ */
+ if (!IsA(subplan, Agg))
+ sub_tlist_save = subplan->targetlist;
+ #ifdef USE_ASSERT_CHECKING
+ else
+ Assert(((Agg *) subplan)->aggsplit == AGGSPLIT_INITIAL_SERIAL);
+ #endif /* USE_ASSERT_CHECKING */
+
+ /*
+ * Restore the aggregate expressions that we might have
+ * removed when planning for aggregation at base relation
+ * level.
+ */
+ subplan->targetlist =
+ restore_grouping_expressions(root, subplan->targetlist);
+ }
+ }
+ }
subplan_itlist = build_tlist_index(subplan->targetlist);
+ /*
+ * The replacement of GroupVars by Aggrefs was only needed for the index
+ * build.
+ */
+ if (sub_tlist_save != NIL)
+ subplan->targetlist = sub_tlist_save;
+
output_targetlist = NIL;
foreach(l, plan->targetlist)
{
*************** build_tlist_index(List *tlist)
*** 1937,1943 ****
--- 1985,1993 ----
itlist->tlist = tlist;
itlist->has_ph_vars = false;
+ itlist->has_grp_vars = false;
itlist->has_non_vars = false;
+ itlist->has_conv_whole_rows = false;
/* Find the Vars and fill in the index array */
vinfo = itlist->vars;
*************** build_tlist_index(List *tlist)
*** 1956,1961 ****
--- 2006,2015 ----
}
else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
itlist->has_ph_vars = true;
+ else if (tle->expr && IsA(tle->expr, GroupedVar))
+ itlist->has_grp_vars = true;
+ else if (get_wholerow_ref_from_convert_row_type((Node *) tle->expr))
+ itlist->has_conv_whole_rows = true;
else
itlist->has_non_vars = true;
}
*************** build_tlist_index(List *tlist)
*** 1971,1977 ****
* This is like build_tlist_index, but we only index tlist entries that
* are Vars belonging to some rel other than the one specified. We will set
* has_ph_vars (allowing PlaceHolderVars to be matched), but not has_non_vars
! * (so nothing other than Vars and PlaceHolderVars can be matched).
*/
static indexed_tlist *
build_tlist_index_other_vars(List *tlist, Index ignore_rel)
--- 2025,2034 ----
* This is like build_tlist_index, but we only index tlist entries that
* are Vars belonging to some rel other than the one specified. We will set
* has_ph_vars (allowing PlaceHolderVars to be matched), but not has_non_vars
! * (so nothing other than Vars and PlaceHolderVars can be matched). In case of
! * DML, where this function will be used, returning lists from child relations
! * will be appended similar to a simple append relation. That does not require
! * fixing ConvertRowtypeExpr references. So, those are not considered here.
*/
static indexed_tlist *
build_tlist_index_other_vars(List *tlist, Index ignore_rel)
*************** build_tlist_index_other_vars(List *tlist
*** 1988,1993 ****
--- 2045,2051 ----
itlist->tlist = tlist;
itlist->has_ph_vars = false;
itlist->has_non_vars = false;
+ itlist->has_conv_whole_rows = false;
/* Find the desired Vars and fill in the index array */
vinfo = itlist->vars;
*************** fix_join_expr_mutator(Node *node, fix_jo
*** 2233,2238 ****
--- 2291,2321 ----
/* No referent found for Var */
elog(ERROR, "variable not found in subplan target lists");
}
+ if (IsA(node, GroupedVar))
+ {
+ GroupedVar *gvar = (GroupedVar *) node;
+
+ /* See if the GroupedVar has bubbled up from a lower plan node */
+ if (context->outer_itlist && context->outer_itlist->has_grp_vars)
+ {
+ newvar = search_indexed_tlist_for_non_var((Expr *) gvar,
+ context->outer_itlist,
+ OUTER_VAR);
+ if (newvar)
+ return (Node *) newvar;
+ }
+ if (context->inner_itlist && context->inner_itlist->has_grp_vars)
+ {
+ newvar = search_indexed_tlist_for_non_var((Expr *) gvar,
+ context->inner_itlist,
+ INNER_VAR);
+ if (newvar)
+ return (Node *) newvar;
+ }
+
+ /* No referent found for GroupedVar */
+ elog(ERROR, "grouped variable not found in subplan target lists");
+ }
if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
*************** fix_join_expr_mutator(Node *node, fix_jo
*** 2258,2263 ****
--- 2341,2369 ----
/* If not supplied by input plans, evaluate the contained expr */
return fix_join_expr_mutator((Node *) phv->phexpr, context);
}
+ if (get_wholerow_ref_from_convert_row_type(node))
+ {
+ if (context->outer_itlist &&
+ context->outer_itlist->has_conv_whole_rows)
+ {
+ newvar = search_indexed_tlist_for_non_var((Expr *) node,
+ context->outer_itlist,
+ OUTER_VAR);
+
+ if (newvar)
+ return (Node *) newvar;
+ }
+ if (context->inner_itlist &&
+ context->inner_itlist->has_conv_whole_rows)
+ {
+ newvar = search_indexed_tlist_for_non_var((Expr *) node,
+ context->inner_itlist,
+ INNER_VAR);
+
+ if (newvar)
+ return (Node *) newvar;
+ }
+ }
if (IsA(node, Param))
return fix_param_node(context->root, (Param *) node);
/* Try matching more complex expressions too, if tlists have any */
*************** fix_upper_expr_mutator(Node *node, fix_u
*** 2364,2369 ****
--- 2470,2486 ----
/* If not supplied by input plan, evaluate the contained expr */
return fix_upper_expr_mutator((Node *) phv->phexpr, context);
}
+ if (get_wholerow_ref_from_convert_row_type(node))
+ {
+ if (context->subplan_itlist->has_conv_whole_rows)
+ {
+ newvar = search_indexed_tlist_for_non_var((Expr *) node,
+ context->subplan_itlist,
+ context->newvarno);
+ if (newvar)
+ return (Node *) newvar;
+ }
+ }
if (IsA(node, Param))
return fix_param_node(context->root, (Param *) node);
if (IsA(node, Aggref))
*************** fix_upper_expr_mutator(Node *node, fix_u
*** 2389,2395 ****
/* If no match, just fall through to process it normally */
}
/* Try matching more complex expressions too, if tlist has any */
! if (context->subplan_itlist->has_non_vars)
{
newvar = search_indexed_tlist_for_non_var((Expr *) node,
context->subplan_itlist,
--- 2506,2513 ----
/* If no match, just fall through to process it normally */
}
/* Try matching more complex expressions too, if tlist has any */
! if (context->subplan_itlist->has_grp_vars ||
! context->subplan_itlist->has_non_vars)
{
newvar = search_indexed_tlist_for_non_var((Expr *) node,
context->subplan_itlist,
*************** extract_query_dependencies_walker(Node *
*** 2596,2598 ****
--- 2714,2748 ----
return expression_tree_walker(node, extract_query_dependencies_walker,
(void *) context);
}
+
+ /*
+ * get_wholerow_ref_from_convert_row_type
+ * Given a node, check if it's a ConvertRowtypeExpr encapsulating a
+ * whole-row reference as implicit cast and return the whole-row
+ * reference Var if so. Otherwise return NULL. In case of multi-level
+ * partitioning, we will have as many nested ConvertRowtypeExpr as there
+ * are levels in partition hierarchy.
+ */
+ static Var *
+ get_wholerow_ref_from_convert_row_type(Node *node)
+ {
+ Var *var = NULL;
+ ConvertRowtypeExpr *convexpr;
+
+ if (!node || !IsA(node, ConvertRowtypeExpr))
+ return NULL;
+
+ /* Traverse nested ConvertRowtypeExpr's. */
+ convexpr = castNode(ConvertRowtypeExpr, node);
+ while (convexpr->convertformat == COERCE_IMPLICIT_CAST &&
+ IsA(convexpr->arg, ConvertRowtypeExpr))
+ convexpr = (ConvertRowtypeExpr *) convexpr->arg;
+
+ if (IsA(convexpr->arg, Var))
+ var = castNode(Var, convexpr->arg);
+
+ if (var && var->varattno == 0)
+ return var;
+
+ return NULL;
+ }
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index a1be858..8bdaa44
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 55,61 ****
typedef struct
{
PlannerInfo *root;
! AppendRelInfo *appinfo;
} adjust_appendrel_attrs_context;
static Path *recurse_set_operations(Node *setOp, PlannerInfo *root,
--- 55,62 ----
typedef struct
{
PlannerInfo *root;
! int nappinfos;
! AppendRelInfo **appinfos;
} adjust_appendrel_attrs_context;
static Path *recurse_set_operations(Node *setOp, PlannerInfo *root,
*************** static List *generate_append_tlist(List
*** 97,103 ****
List *input_tlists,
List *refnames_tlist);
static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist);
! static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte,
Index rti);
static void make_inh_translation_list(Relation oldrelation,
Relation newrelation,
--- 98,104 ----
List *input_tlists,
List *refnames_tlist);
static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist);
! static List *expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte,
Index rti);
static void make_inh_translation_list(Relation oldrelation,
Relation newrelation,
*************** static Bitmapset *translate_col_privs(co
*** 107,113 ****
List *translated_vars);
static Node *adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_context *context);
- static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
--- 108,113 ----
*************** plan_set_operations(PlannerInfo *root)
*** 207,213 ****
root->processed_tlist = top_tlist;
/* Add only the final path to the SETOP upperrel. */
! add_path(setop_rel, path);
/* Let extensions possibly add some more paths */
if (create_upper_paths_hook)
--- 207,213 ----
root->processed_tlist = top_tlist;
/* Add only the final path to the SETOP upperrel. */
! add_path(setop_rel, path, false);
/* Let extensions possibly add some more paths */
if (create_upper_paths_hook)
*************** expand_inherited_tables(PlannerInfo *roo
*** 1330,1348 ****
Index nrtes;
Index rti;
ListCell *rl;
/*
* expand_inherited_rtentry may add RTEs to parse->rtable; there is no
* need to scan them since they can't have inh=true. So just scan as far
* as the original end of the rtable list.
*/
! nrtes = list_length(root->parse->rtable);
! rl = list_head(root->parse->rtable);
for (rti = 1; rti <= nrtes; rti++)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl);
! expand_inherited_rtentry(root, rte, rti);
rl = lnext(rl);
}
}
--- 1330,1351 ----
Index nrtes;
Index rti;
ListCell *rl;
+ Query *parse = root->parse;
/*
* expand_inherited_rtentry may add RTEs to parse->rtable; there is no
* need to scan them since they can't have inh=true. So just scan as far
* as the original end of the rtable list.
*/
! nrtes = list_length(parse->rtable);
! rl = list_head(parse->rtable);
for (rti = 1; rti <= nrtes; rti++)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl);
+ List *appinfos;
! appinfos = expand_inherited_rtentry(root, rte, rti);
! root->append_rel_list = list_concat(root->append_rel_list, appinfos);
rl = lnext(rl);
}
}
*************** expand_inherited_tables(PlannerInfo *roo
*** 1362,1369 ****
*
* A childless table is never considered to be an inheritance set; therefore
* a parent RTE must always have at least two associated AppendRelInfos.
*/
! static void
expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
{
Query *parse = root->parse;
--- 1365,1374 ----
*
* A childless table is never considered to be an inheritance set; therefore
* a parent RTE must always have at least two associated AppendRelInfos.
+ *
+ * Returns a list of AppendRelInfos, or NIL.
*/
! static List*
expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
{
Query *parse = root->parse;
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1380,1391 ****
/* Does RT entry allow inheritance? */
if (!rte->inh)
! return;
/* Ignore any already-expanded UNION ALL nodes */
if (rte->rtekind != RTE_RELATION)
{
Assert(rte->rtekind == RTE_SUBQUERY);
! return;
}
/* Fast path for common case of childless table */
parentOID = rte->relid;
--- 1385,1396 ----
/* Does RT entry allow inheritance? */
if (!rte->inh)
! return NIL;
/* Ignore any already-expanded UNION ALL nodes */
if (rte->rtekind != RTE_RELATION)
{
Assert(rte->rtekind == RTE_SUBQUERY);
! return NIL;
}
/* Fast path for common case of childless table */
parentOID = rte->relid;
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1393,1399 ****
{
/* Clear flag before returning */
rte->inh = false;
! return;
}
/*
--- 1398,1404 ----
{
/* Clear flag before returning */
rte->inh = false;
! return NIL;
}
/*
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1417,1424 ****
else
lockmode = AccessShareLock;
! /* Scan for all members of inheritance set, acquire needed locks */
! inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
/*
* Check that there's at least one descendant, else treat as no-child
--- 1422,1440 ----
else
lockmode = AccessShareLock;
! /*
! * Expand partitioned table level-wise to help optimizations like
! * partition-wise join which match partitions at every level. Otherwise,
! * scan for all members of inheritance set. Acquire needed locks
! */
! if (rte->relkind == RELKIND_PARTITIONED_TABLE)
! {
! inhOIDs = list_make1_oid(parentOID);
! inhOIDs = list_concat(inhOIDs,
! find_inheritance_children(parentOID, lockmode));
! }
! else
! inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
/*
* Check that there's at least one descendant, else treat as no-child
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1429,1435 ****
{
/* Clear flag before returning */
rte->inh = false;
! return;
}
/*
--- 1445,1451 ----
{
/* Clear flag before returning */
rte->inh = false;
! return NIL;
}
/*
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1457,1462 ****
--- 1473,1484 ----
Index childRTindex;
AppendRelInfo *appinfo;
+ /*
+ * If this child is a partitioned table, this contains AppendRelInfos
+ * for its own children.
+ */
+ List *myappinfos;
+
/* Open rel if needed; we already have required locks */
if (childOID != parentOID)
newrelation = heap_open(childOID, NoLock);
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1490,1496 ****
childrte = copyObject(rte);
childrte->relid = childOID;
childrte->relkind = newrelation->rd_rel->relkind;
! childrte->inh = false;
childrte->requiredPerms = 0;
childrte->securityQuals = NIL;
parse->rtable = lappend(parse->rtable, childrte);
--- 1512,1523 ----
childrte = copyObject(rte);
childrte->relid = childOID;
childrte->relkind = newrelation->rd_rel->relkind;
! /* A partitioned child will need to be expanded further. */
! if (childOID != parentOID &&
! childrte->relkind == RELKIND_PARTITIONED_TABLE)
! childrte->inh = true;
! else
! childrte->inh = false;
childrte->requiredPerms = 0;
childrte->securityQuals = NIL;
parse->rtable = lappend(parse->rtable, childrte);
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1498,1506 ****
/*
* Build an AppendRelInfo for this parent and child, unless the child
! * is a partitioned table.
*/
! if (childrte->relkind != RELKIND_PARTITIONED_TABLE)
{
need_append = true;
appinfo = makeNode(AppendRelInfo);
--- 1525,1533 ----
/*
* Build an AppendRelInfo for this parent and child, unless the child
! * RTE simply duplicates the parent *partitioned* table.
*/
! if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh)
{
need_append = true;
appinfo = makeNode(AppendRelInfo);
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1570,1575 ****
--- 1597,1610 ----
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
+
+ /* Expand partitioned children recursively. */
+ if (childrte->inh)
+ {
+ myappinfos = expand_inherited_rtentry(root, childrte,
+ childRTindex);
+ appinfos = list_concat(appinfos, myappinfos);
+ }
}
heap_close(oldrelation, NoLock);
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1585,1591 ****
{
/* Clear flag before returning */
rte->inh = false;
! return;
}
/*
--- 1620,1626 ----
{
/* Clear flag before returning */
rte->inh = false;
! return NIL;
}
/*
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1606,1613 ****
root->pcinfo_list = lappend(root->pcinfo_list, pcinfo);
}
! /* Otherwise, OK to add to root->append_rel_list */
! root->append_rel_list = list_concat(root->append_rel_list, appinfos);
}
/*
--- 1641,1648 ----
root->pcinfo_list = lappend(root->pcinfo_list, pcinfo);
}
! /* The following will be concatenated to root->append_rel_list. */
! return appinfos;
}
/*
*************** translate_col_privs(const Bitmapset *par
*** 1767,1776 ****
/*
* adjust_appendrel_attrs
! * Copy the specified query or expression and translate Vars referring
! * to the parent rel of the specified AppendRelInfo to refer to the
! * child rel instead. We also update rtindexes appearing outside Vars,
! * such as resultRelation and jointree relids.
*
* Note: this is only applied after conversion of sublinks to subplans,
* so we don't need to cope with recursion into sub-queries.
--- 1802,1812 ----
/*
* adjust_appendrel_attrs
! * Copy the specified query or expression and translate Vars referring to
! * the parent rels of the child rels specified in the given list of
! * AppendRelInfos to refer to the corresponding child rel instead. We also
! * update rtindexes appearing outside Vars, such as resultRelation and
! * jointree relids.
*
* Note: this is only applied after conversion of sublinks to subplans,
* so we don't need to cope with recursion into sub-queries.
*************** translate_col_privs(const Bitmapset *par
*** 1779,1791 ****
* maybe we should try to fold the two routines together.
*/
Node *
! adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
{
Node *result;
adjust_appendrel_attrs_context context;
context.root = root;
! context.appinfo = appinfo;
/*
* Must be prepared to start with a Query or a bare expression tree.
--- 1815,1835 ----
* maybe we should try to fold the two routines together.
*/
Node *
! adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos,
! AppendRelInfo **appinfos)
{
Node *result;
adjust_appendrel_attrs_context context;
context.root = root;
! context.nappinfos = nappinfos;
! context.appinfos = appinfos;
!
! /*
! * Catch a caller who wants to adjust expressions, but doesn't pass any
! * AppendRelInfo.
! */
! Assert(appinfos && nappinfos >= 1);
/*
* Must be prepared to start with a Query or a bare expression tree.
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1793,1812 ****
if (node && IsA(node, Query))
{
Query *newnode;
newnode = query_tree_mutator((Query *) node,
adjust_appendrel_attrs_mutator,
(void *) &context,
QTW_IGNORE_RC_SUBQUERIES);
! if (newnode->resultRelation == appinfo->parent_relid)
{
! newnode->resultRelation = appinfo->child_relid;
! /* Fix tlist resnos too, if it's inherited UPDATE */
! if (newnode->commandType == CMD_UPDATE)
! newnode->targetList =
! adjust_inherited_tlist(newnode->targetList,
! appinfo);
}
result = (Node *) newnode;
}
else
--- 1837,1864 ----
if (node && IsA(node, Query))
{
Query *newnode;
+ int cnt;
newnode = query_tree_mutator((Query *) node,
adjust_appendrel_attrs_mutator,
(void *) &context,
QTW_IGNORE_RC_SUBQUERIES);
! for (cnt = 0; cnt < nappinfos; cnt++)
{
! AppendRelInfo *appinfo = appinfos[cnt];
!
! if (newnode->resultRelation == appinfo->parent_relid)
! {
! newnode->resultRelation = appinfo->child_relid;
! /* Fix tlist resnos too, if it's inherited UPDATE */
! if (newnode->commandType == CMD_UPDATE)
! newnode->targetList =
! adjust_inherited_tlist(newnode->targetList,
! appinfo);
! break;
! }
}
+
result = (Node *) newnode;
}
else
*************** static Node *
*** 1819,1831 ****
adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_context *context)
{
! AppendRelInfo *appinfo = context->appinfo;
if (node == NULL)
return NULL;
if (IsA(node, Var))
{
Var *var = (Var *) copyObject(node);
if (var->varlevelsup == 0 &&
var->varno == appinfo->parent_relid)
--- 1871,1900 ----
adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_context *context)
{
! AppendRelInfo **appinfos = context->appinfos;
! int nappinfos = context->nappinfos;
! int cnt;
!
! /*
! * Catch a caller who wants to adjust expressions, but doesn't pass any
! * AppendRelInfo.
! */
! Assert(appinfos && nappinfos >= 1);
if (node == NULL)
return NULL;
if (IsA(node, Var))
{
Var *var = (Var *) copyObject(node);
+ AppendRelInfo *appinfo;
+
+ for (cnt = 0; cnt < nappinfos; cnt++)
+ {
+ appinfo = appinfos[cnt];
+
+ if (var->varno == appinfo->parent_relid)
+ break;
+ }
if (var->varlevelsup == 0 &&
var->varno == appinfo->parent_relid)
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1908,1936 ****
{
CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
! if (cexpr->cvarno == appinfo->parent_relid)
! cexpr->cvarno = appinfo->child_relid;
return (Node *) cexpr;
}
if (IsA(node, RangeTblRef))
{
RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
! if (rtr->rtindex == appinfo->parent_relid)
! rtr->rtindex = appinfo->child_relid;
return (Node *) rtr;
}
if (IsA(node, JoinExpr))
{
/* Copy the JoinExpr node with correct mutation of subnodes */
JoinExpr *j;
j = (JoinExpr *) expression_tree_mutator(node,
adjust_appendrel_attrs_mutator,
(void *) context);
/* now fix JoinExpr's rtindex (probably never happens) */
! if (j->rtindex == appinfo->parent_relid)
! j->rtindex = appinfo->child_relid;
return (Node *) j;
}
if (IsA(node, PlaceHolderVar))
--- 1977,2030 ----
{
CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
! for (cnt = 0; cnt < nappinfos; cnt++)
! {
! AppendRelInfo *appinfo = appinfos[cnt];
!
! if (cexpr->cvarno == appinfo->parent_relid)
! {
! cexpr->cvarno = appinfo->child_relid;
! break;
! }
! }
return (Node *) cexpr;
}
if (IsA(node, RangeTblRef))
{
RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
! for (cnt = 0; cnt < nappinfos; cnt++)
! {
! AppendRelInfo *appinfo = appinfos[cnt];
!
! if (rtr->rtindex == appinfo->parent_relid)
! {
! rtr->rtindex = appinfo->child_relid;
! break;
! }
! }
return (Node *) rtr;
}
if (IsA(node, JoinExpr))
{
/* Copy the JoinExpr node with correct mutation of subnodes */
JoinExpr *j;
+ AppendRelInfo *appinfo;
j = (JoinExpr *) expression_tree_mutator(node,
adjust_appendrel_attrs_mutator,
(void *) context);
/* now fix JoinExpr's rtindex (probably never happens) */
! for (cnt = 0; cnt < nappinfos; cnt++)
! {
! appinfo = appinfos[cnt];
!
! if (j->rtindex == appinfo->parent_relid)
! {
! j->rtindex = appinfo->child_relid;
! break;
! }
! }
return (Node *) j;
}
if (IsA(node, PlaceHolderVar))
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1943,1951 ****
(void *) context);
/* now fix PlaceHolderVar's relid sets */
if (phv->phlevelsup == 0)
! phv->phrels = adjust_relid_set(phv->phrels,
! appinfo->parent_relid,
! appinfo->child_relid);
return (Node *) phv;
}
/* Shouldn't need to handle planner auxiliary nodes here */
--- 2037,2044 ----
(void *) context);
/* now fix PlaceHolderVar's relid sets */
if (phv->phlevelsup == 0)
! phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos,
! context->appinfos);
return (Node *) phv;
}
/* Shouldn't need to handle planner auxiliary nodes here */
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1976,1999 ****
adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context);
/* adjust relid sets too */
! newinfo->clause_relids = adjust_relid_set(oldinfo->clause_relids,
! appinfo->parent_relid,
! appinfo->child_relid);
! newinfo->required_relids = adjust_relid_set(oldinfo->required_relids,
! appinfo->parent_relid,
! appinfo->child_relid);
! newinfo->outer_relids = adjust_relid_set(oldinfo->outer_relids,
! appinfo->parent_relid,
! appinfo->child_relid);
! newinfo->nullable_relids = adjust_relid_set(oldinfo->nullable_relids,
! appinfo->parent_relid,
! appinfo->child_relid);
! newinfo->left_relids = adjust_relid_set(oldinfo->left_relids,
! appinfo->parent_relid,
! appinfo->child_relid);
! newinfo->right_relids = adjust_relid_set(oldinfo->right_relids,
! appinfo->parent_relid,
! appinfo->child_relid);
/*
* Reset cached derivative fields, since these might need to have
--- 2069,2092 ----
adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context);
/* adjust relid sets too */
! newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids,
! context->nappinfos,
! context->appinfos);
! newinfo->required_relids = adjust_child_relids(oldinfo->required_relids,
! context->nappinfos,
! context->appinfos);
! newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids,
! context->nappinfos,
! context->appinfos);
! newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids,
! context->nappinfos,
! context->appinfos);
! newinfo->left_relids = adjust_child_relids(oldinfo->left_relids,
! context->nappinfos,
! context->appinfos);
! newinfo->right_relids = adjust_child_relids(oldinfo->right_relids,
! context->nappinfos,
! context->appinfos);
/*
* Reset cached derivative fields, since these might need to have
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 2025,2047 ****
}
/*
! * Substitute newrelid for oldrelid in a Relid set
*/
! static Relids
! adjust_relid_set(Relids relids, Index oldrelid, Index newrelid)
{
! if (bms_is_member(oldrelid, relids))
{
! /* Ensure we have a modifiable copy */
! relids = bms_copy(relids);
! /* Remove old, add new */
! relids = bms_del_member(relids, oldrelid);
! relids = bms_add_member(relids, newrelid);
}
return relids;
}
/*
* Adjust the targetlist entries of an inherited UPDATE operation
*
* The expressions have already been fixed, but we have to make sure that
--- 2118,2212 ----
}
/*
! * Replace parent relids by child relids in the copy of given relid set
! * according to the given list of AppendRelInfos. The given relid set is
! * returned as is if it contains no parent in the given list, otherwise, the
! * given relid set is not changed.
*/
! Relids
! adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos)
{
! Bitmapset *result = NULL;
! int cnt;
!
! for (cnt = 0; cnt < nappinfos; cnt++)
{
! AppendRelInfo *appinfo = appinfos[cnt];
!
! /* Remove parent, add child */
! if (bms_is_member(appinfo->parent_relid, relids))
! {
! /* Make a copy if we are changing the set. */
! if (!result)
! result = bms_copy(relids);
!
! result = bms_del_member(result, appinfo->parent_relid);
! result = bms_add_member(result, appinfo->child_relid);
! }
}
+
+ /* Return new set if we modified the given set. */
+ if (result)
+ return result;
+
+ /* Else return the given relids set as is. */
return relids;
}
/*
+ * Replace any relid present in top_parent_relids with its child in
+ * child_relids. Members of child_relids can be multiple levels below top
+ * parent in the partition hierarchy.
+ */
+ Relids
+ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
+ Relids child_relids, Relids top_parent_relids)
+ {
+ AppendRelInfo **appinfos;
+ int nappinfos;
+ Relids parent_relids = NULL;
+ Relids result;
+ Relids tmp_result = NULL;
+ int cnt;
+
+ /*
+ * If the given relids set doesn't contain any of the top parent relids,
+ * it will remain unchanged.
+ */
+ if (!bms_overlap(relids, top_parent_relids))
+ return relids;
+
+ appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos);
+
+ /* Construct relids set for the immediate parent of the given child. */
+ for (cnt = 0; cnt < nappinfos; cnt++)
+ {
+ AppendRelInfo *appinfo = appinfos[cnt];
+
+ parent_relids = bms_add_member(parent_relids, appinfo->parent_relid);
+ }
+
+ /* Recurse if immediate parent is not the top parent. */
+ if (!bms_equal(parent_relids, top_parent_relids))
+ {
+ tmp_result = adjust_child_relids_multilevel(root, relids,
+ parent_relids,
+ top_parent_relids);
+ relids = tmp_result;
+ }
+
+ result = adjust_child_relids(relids, nappinfos, appinfos);
+
+ /* Free memory consumed by any intermediate result. */
+ if (tmp_result)
+ bms_free(tmp_result);
+ bms_free(parent_relids);
+ pfree(appinfos);
+
+ return result;
+ }
+
+ /*
* Adjust the targetlist entries of an inherited UPDATE operation
*
* The expressions have already been fixed, but we have to make sure that
*************** adjust_inherited_tlist(List *tlist, Appe
*** 2142,2162 ****
* adjust_appendrel_attrs_multilevel
* Apply Var translations from a toplevel appendrel parent down to a child.
*
! * In some cases we need to translate expressions referencing a baserel
* to reference an appendrel child that's multiple levels removed from it.
*/
Node *
adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
! RelOptInfo *child_rel)
{
! AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, child_rel);
! RelOptInfo *parent_rel = find_base_rel(root, appinfo->parent_relid);
- /* If parent is also a child, first recurse to apply its translations */
- if (IS_OTHER_REL(parent_rel))
- node = adjust_appendrel_attrs_multilevel(root, node, parent_rel);
- else
- Assert(parent_rel->reloptkind == RELOPT_BASEREL);
/* Now translate for this child */
! return adjust_appendrel_attrs(root, node, appinfo);
}
--- 2307,2432 ----
* adjust_appendrel_attrs_multilevel
* Apply Var translations from a toplevel appendrel parent down to a child.
*
! * In some cases we need to translate expressions referencing a parent relation
* to reference an appendrel child that's multiple levels removed from it.
*/
Node *
adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
! Relids child_relids,
! Relids top_parent_relids)
{
! AppendRelInfo **appinfos;
! Bitmapset *parent_relids = NULL;
! int nappinfos;
! int cnt;
!
! Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids));
!
! appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos);
!
! /* Construct relids set for the immediate parent of given child. */
! for (cnt = 0; cnt < nappinfos; cnt++)
! {
! AppendRelInfo *appinfo = appinfos[cnt];
!
! parent_relids = bms_add_member(parent_relids, appinfo->parent_relid);
! }
!
! /* Recurse if immediate parent is not the top parent. */
! if (!bms_equal(parent_relids, top_parent_relids))
! node = adjust_appendrel_attrs_multilevel(root, node, parent_relids,
! top_parent_relids);
/* Now translate for this child */
! node = adjust_appendrel_attrs(root, node, nappinfos, appinfos);
!
! pfree(appinfos);
!
! return node;
! }
!
! /*
! * Construct the SpecialJoinInfo for a child-join by translating
! * SpecialJoinInfo for the join between parents. left_relids and right_relids
! * are the relids of left and right side of the join respectively.
! */
! SpecialJoinInfo *
! build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo,
! Relids left_relids, Relids right_relids)
! {
! SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo);
! AppendRelInfo **left_appinfos;
! int left_nappinfos;
! AppendRelInfo **right_appinfos;
! int right_nappinfos;
!
! memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo));
! left_appinfos = find_appinfos_by_relids(root, left_relids,
! &left_nappinfos);
! right_appinfos = find_appinfos_by_relids(root, right_relids,
! &right_nappinfos);
!
! sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand,
! left_nappinfos, left_appinfos);
! sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand,
! right_nappinfos,
! right_appinfos);
! sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand,
! left_nappinfos, left_appinfos);
! sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand,
! right_nappinfos,
! right_appinfos);
!
! /*
! * Replace the Var nodes of parent with those of children in expressions.
! * This function may be called within a temporary context, but the
! * expressions will be shallow-copied into the plan. Hence copy those in
! * the planner's context.
! */
! sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root,
! (Node *) sjinfo->semi_rhs_exprs,
! right_nappinfos,
! right_appinfos);
!
! pfree(left_appinfos);
! pfree(right_appinfos);
!
! return sjinfo;
! }
!
! /*
! * find_appinfos_by_relids
! * Find AppendRelInfo structures for all relations specified by relids.
! *
! * The AppendRelInfos are returned in an array, which can be pfree'd by the
! * caller.
! */
! AppendRelInfo **
! find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
! {
! ListCell *lc;
! AppendRelInfo **appinfos;
! int cnt = 0;
!
! *nappinfos = bms_num_members(relids);
! appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos);
!
! foreach (lc, root->append_rel_list)
! {
! AppendRelInfo *appinfo = lfirst(lc);
!
! if (bms_is_member(appinfo->child_relid, relids))
! {
! appinfos[cnt] = appinfo;
! cnt++;
!
! /* Stop when we have gathered all the AppendRelInfos. */
! if (cnt == *nappinfos)
! return appinfos;
! }
! }
!
! /* Should have found the entries ... */
! elog(ERROR, "Did not find one or more of requested child rels in append_rel_list");
! return NULL; /* not reached */
}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
new file mode 100644
index 2d5caae..2bacec9
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 18,32 ****
--- 18,39 ----
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+ #include "nodes/extensible.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
+ #include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+ #include "optimizer/tlist.h"
+ /* TODO Remove this if get_grouping_expressions ends up in another module. */
+ #include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parsetree.h"
+ #include "foreign/fdwapi.h"
#include "utils/lsyscache.h"
+ #include "utils/memutils.h"
#include "utils/selfuncs.h"
*************** set_cheapest(RelOptInfo *parent_rel)
*** 409,416 ****
* Returns nothing, but modifies parent_rel->pathlist.
*/
void
! add_path(RelOptInfo *parent_rel, Path *new_path)
{
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
List *new_path_pathkeys;
--- 416,424 ----
* Returns nothing, but modifies parent_rel->pathlist.
*/
void
! add_path(RelOptInfo *parent_rel, Path *new_path, bool grouped)
{
+ List *pathlist;
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
List *new_path_pathkeys;
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 427,432 ****
--- 435,448 ----
/* Pretend parameterized paths have no pathkeys, per comment above */
new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys;
+ if (!grouped)
+ pathlist = parent_rel->pathlist;
+ else
+ {
+ Assert(parent_rel->gpi != NULL);
+ pathlist = parent_rel->gpi->pathlist;
+ }
+
/*
* Loop to check proposed new path against old paths. Note it is possible
* for more than one old path to be tossed out because new_path dominates
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 436,442 ****
* list cell.
*/
p1_prev = NULL;
! for (p1 = list_head(parent_rel->pathlist); p1 != NULL; p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
bool remove_old = false; /* unless new proves superior */
--- 452,458 ----
* list cell.
*/
p1_prev = NULL;
! for (p1 = list_head(pathlist); p1 != NULL; p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
bool remove_old = false; /* unless new proves superior */
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 582,589 ****
*/
if (remove_old)
{
! parent_rel->pathlist = list_delete_cell(parent_rel->pathlist,
! p1, p1_prev);
/*
* Delete the data pointed-to by the deleted cell, if possible
--- 598,604 ----
*/
if (remove_old)
{
! pathlist = list_delete_cell(pathlist, p1, p1_prev);
/*
* Delete the data pointed-to by the deleted cell, if possible
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 614,622 ****
{
/* Accept the new path: insert it at proper place in pathlist */
if (insert_after)
! lappend_cell(parent_rel->pathlist, insert_after, new_path);
else
! parent_rel->pathlist = lcons(new_path, parent_rel->pathlist);
}
else
{
--- 629,642 ----
{
/* Accept the new path: insert it at proper place in pathlist */
if (insert_after)
! lappend_cell(pathlist, insert_after, new_path);
else
! pathlist = lcons(new_path, pathlist);
!
! if (!grouped)
! parent_rel->pathlist = pathlist;
! else
! parent_rel->gpi->pathlist = pathlist;
}
else
{
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 646,653 ****
bool
add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer)
{
List *new_path_pathkeys;
bool consider_startup;
ListCell *p1;
--- 666,674 ----
bool
add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer, bool grouped)
{
+ List *pathlist;
List *new_path_pathkeys;
bool consider_startup;
ListCell *p1;
*************** add_path_precheck(RelOptInfo *parent_rel
*** 656,664 ****
new_path_pathkeys = required_outer ? NIL : pathkeys;
/* Decide whether new path's startup cost is interesting */
! consider_startup = required_outer ? parent_rel->consider_param_startup : parent_rel->consider_startup;
! foreach(p1, parent_rel->pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
--- 677,694 ----
new_path_pathkeys = required_outer ? NIL : pathkeys;
/* Decide whether new path's startup cost is interesting */
! consider_startup = required_outer ? parent_rel->consider_param_startup :
! parent_rel->consider_startup;
! if (!grouped)
! pathlist = parent_rel->pathlist;
! else
! {
! Assert(parent_rel->gpi != NULL);
! pathlist = parent_rel->gpi->pathlist;
! }
!
! foreach(p1, pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
*************** add_path_precheck(RelOptInfo *parent_rel
*** 749,771 ****
* referenced by partial BitmapHeapPaths.
*/
void
! add_partial_path(RelOptInfo *parent_rel, Path *new_path)
{
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
ListCell *p1;
ListCell *p1_prev;
ListCell *p1_next;
/* Check for query cancel. */
CHECK_FOR_INTERRUPTS();
/*
* As in add_path, throw out any paths which are dominated by the new
* path, but throw out the new path if some existing path dominates it.
*/
p1_prev = NULL;
! for (p1 = list_head(parent_rel->partial_pathlist); p1 != NULL;
p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
--- 779,810 ----
* referenced by partial BitmapHeapPaths.
*/
void
! add_partial_path(RelOptInfo *parent_rel, Path *new_path, bool grouped)
{
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
ListCell *p1;
ListCell *p1_prev;
ListCell *p1_next;
+ List *pathlist;
/* Check for query cancel. */
CHECK_FOR_INTERRUPTS();
+ if (!grouped)
+ pathlist = parent_rel->partial_pathlist;
+ else
+ {
+ Assert(parent_rel->gpi != NULL);
+ pathlist = parent_rel->gpi->partial_pathlist;
+ }
+
/*
* As in add_path, throw out any paths which are dominated by the new
* path, but throw out the new path if some existing path dominates it.
*/
p1_prev = NULL;
! for (p1 = list_head(pathlist); p1 != NULL;
p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
*************** add_partial_path(RelOptInfo *parent_rel,
*** 819,830 ****
}
/*
! * Remove current element from partial_pathlist if dominated by new.
*/
if (remove_old)
{
! parent_rel->partial_pathlist =
! list_delete_cell(parent_rel->partial_pathlist, p1, p1_prev);
pfree(old_path);
/* p1_prev does not advance */
}
--- 858,868 ----
}
/*
! * Remove current element from pathlist if dominated by new.
*/
if (remove_old)
{
! pathlist = list_delete_cell(pathlist, p1, p1_prev);
pfree(old_path);
/* p1_prev does not advance */
}
*************** add_partial_path(RelOptInfo *parent_rel,
*** 839,845 ****
/*
* If we found an old path that dominates new_path, we can quit
! * scanning the partial_pathlist; we will not add new_path, and we
* assume new_path cannot dominate any later path.
*/
if (!accept_new)
--- 877,883 ----
/*
* If we found an old path that dominates new_path, we can quit
! * scanning the pathlist; we will not add new_path, and we
* assume new_path cannot dominate any later path.
*/
if (!accept_new)
*************** add_partial_path(RelOptInfo *parent_rel,
*** 850,859 ****
{
/* Accept the new path: insert it at proper place */
if (insert_after)
! lappend_cell(parent_rel->partial_pathlist, insert_after, new_path);
else
! parent_rel->partial_pathlist =
! lcons(new_path, parent_rel->partial_pathlist);
}
else
{
--- 888,901 ----
{
/* Accept the new path: insert it at proper place */
if (insert_after)
! lappend_cell(pathlist, insert_after, new_path);
else
! pathlist = lcons(new_path, pathlist);
!
! if (!grouped)
! parent_rel->partial_pathlist = pathlist;
! else
! parent_rel->gpi->partial_pathlist = pathlist;
}
else
{
*************** add_partial_path(RelOptInfo *parent_rel,
*** 874,882 ****
*/
bool
add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost,
! List *pathkeys)
{
ListCell *p1;
/*
* Our goal here is twofold. First, we want to find out whether this path
--- 916,933 ----
*/
bool
add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost,
! List *pathkeys, bool grouped)
{
ListCell *p1;
+ List *pathlist;
+
+ if (!grouped)
+ pathlist = parent_rel->partial_pathlist;
+ else
+ {
+ Assert(parent_rel->gpi != NULL);
+ pathlist = parent_rel->gpi->partial_pathlist;
+ }
/*
* Our goal here is twofold. First, we want to find out whether this path
*************** add_partial_path_precheck(RelOptInfo *pa
*** 886,895 ****
* final cost computations. If so, we definitely want to consider it.
*
* Unlike add_path(), we always compare pathkeys here. This is because we
! * expect partial_pathlist to be very short, and getting a definitive
! * answer at this stage avoids the need to call add_path_precheck.
*/
! foreach(p1, parent_rel->partial_pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
--- 937,947 ----
* final cost computations. If so, we definitely want to consider it.
*
* Unlike add_path(), we always compare pathkeys here. This is because we
! * expect partial_pathlist / grouped_pathlist to be very short, and
! * getting a definitive answer at this stage avoids the need to call
! * add_path_precheck.
*/
! foreach(p1, pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
*************** add_partial_path_precheck(RelOptInfo *pa
*** 918,924 ****
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL))
return false;
return true;
--- 970,976 ----
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL, grouped))
return false;
return true;
*************** create_foreignscan_path(PlannerInfo *roo
*** 1994,2007 ****
* Note: result must not share storage with either input
*/
Relids
! calc_nestloop_required_outer(Path *outer_path, Path *inner_path)
{
- Relids outer_paramrels = PATH_REQ_OUTER(outer_path);
- Relids inner_paramrels = PATH_REQ_OUTER(inner_path);
Relids required_outer;
/* inner_path can require rels from outer path, but not vice versa */
! Assert(!bms_overlap(outer_paramrels, inner_path->parent->relids));
/* easy case if inner path is not parameterized */
if (!inner_paramrels)
return bms_copy(outer_paramrels);
--- 2046,2060 ----
* Note: result must not share storage with either input
*/
Relids
! calc_nestloop_required_outer(Relids outerrelids,
! Relids outer_paramrels,
! Relids innerrelids,
! Relids inner_paramrels)
{
Relids required_outer;
/* inner_path can require rels from outer path, but not vice versa */
! Assert(!bms_overlap(outer_paramrels, innerrelids));
/* easy case if inner path is not parameterized */
if (!inner_paramrels)
return bms_copy(outer_paramrels);
*************** calc_nestloop_required_outer(Path *outer
*** 2009,2015 ****
required_outer = bms_union(outer_paramrels, inner_paramrels);
/* ... and remove any mention of now-satisfied outer rels */
required_outer = bms_del_members(required_outer,
! outer_path->parent->relids);
/* maintain invariant that required_outer is exactly NULL if empty */
if (bms_is_empty(required_outer))
{
--- 2062,2068 ----
required_outer = bms_union(outer_paramrels, inner_paramrels);
/* ... and remove any mention of now-satisfied outer rels */
required_outer = bms_del_members(required_outer,
! outerrelids);
/* maintain invariant that required_outer is exactly NULL if empty */
if (bms_is_empty(required_outer))
{
*************** calc_non_nestloop_required_outer(Path *o
*** 2055,2060 ****
--- 2108,2114 ----
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
* 'pathkeys' are the path keys of the new join path
* 'required_outer' is the set of required outer rels
+ * 'target' can be passed to override that of joinrel.
*
* Returns the resulting path node.
*/
*************** create_nestloop_path(PlannerInfo *root,
*** 2068,2074 ****
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
! Relids required_outer)
{
NestPath *pathnode = makeNode(NestPath);
Relids inner_req_outer = PATH_REQ_OUTER(inner_path);
--- 2122,2129 ----
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
! Relids required_outer,
! PathTarget *target)
{
NestPath *pathnode = makeNode(NestPath);
Relids inner_req_outer = PATH_REQ_OUTER(inner_path);
*************** create_nestloop_path(PlannerInfo *root,
*** 2101,2107 ****
pathnode->path.pathtype = T_NestLoop;
pathnode->path.parent = joinrel;
! pathnode->path.pathtarget = joinrel->reltarget;
pathnode->path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
--- 2156,2162 ----
pathnode->path.pathtype = T_NestLoop;
pathnode->path.parent = joinrel;
! pathnode->path.pathtarget = target == NULL ? joinrel->reltarget : target;
pathnode->path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
*************** create_mergejoin_path(PlannerInfo *root,
*** 2159,2171 ****
Relids required_outer,
List *mergeclauses,
List *outersortkeys,
! List *innersortkeys)
{
MergePath *pathnode = makeNode(MergePath);
pathnode->jpath.path.pathtype = T_MergeJoin;
pathnode->jpath.path.parent = joinrel;
! pathnode->jpath.path.pathtarget = joinrel->reltarget;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
--- 2214,2228 ----
Relids required_outer,
List *mergeclauses,
List *outersortkeys,
! List *innersortkeys,
! PathTarget *target)
{
MergePath *pathnode = makeNode(MergePath);
pathnode->jpath.path.pathtype = T_MergeJoin;
pathnode->jpath.path.parent = joinrel;
! pathnode->jpath.path.pathtarget = target == NULL ? joinrel->reltarget :
! target;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
*************** create_mergejoin_path(PlannerInfo *root,
*** 2210,2215 ****
--- 2267,2273 ----
* 'required_outer' is the set of required outer rels
* 'hashclauses' are the RestrictInfo nodes to use as hash clauses
* (this should be a subset of the restrict_clauses list)
+ * 'target' can be passed to override that of joinrel.
*/
HashPath *
create_hashjoin_path(PlannerInfo *root,
*************** create_hashjoin_path(PlannerInfo *root,
*** 2221,2233 ****
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses)
{
HashPath *pathnode = makeNode(HashPath);
pathnode->jpath.path.pathtype = T_HashJoin;
pathnode->jpath.path.parent = joinrel;
! pathnode->jpath.path.pathtarget = joinrel->reltarget;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
--- 2279,2293 ----
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses,
! PathTarget *target)
{
HashPath *pathnode = makeNode(HashPath);
pathnode->jpath.path.pathtype = T_HashJoin;
pathnode->jpath.path.parent = joinrel;
! pathnode->jpath.path.pathtarget = target == NULL ? joinrel->reltarget :
! target;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
*************** create_agg_path(PlannerInfo *root,
*** 2682,2688 ****
pathnode->path.pathtarget = target;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
! pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_workers = subpath->parallel_workers;
--- 2742,2748 ----
pathnode->path.pathtarget = target;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
! pathnode->path.parallel_aware = true;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_workers = subpath->parallel_workers;
*************** create_agg_path(PlannerInfo *root,
*** 2713,2718 ****
--- 2773,2948 ----
}
/*
+ * Apply partial AGG_SORTED aggregation path to subpath if it's suitably
+ * sorted.
+ *
+ * first_call indicates whether the function is being called first time for
+ * given index --- since the target should not change, we can skip the check
+ * of sorting during subsequent calls.
+ *
+ * group_clauses, group_exprs and agg_exprs are pointers to lists we populate
+ * when called first time for particular index, and that user passes for
+ * subsequent calls.
+ *
+ * NULL is returned if sorting of subpath output is not suitable.
+ */
+ AggPath *
+ create_partial_agg_sorted_path(PlannerInfo *root, Path *subpath,
+ bool first_call,
+ List **group_clauses, List **group_exprs,
+ List **agg_exprs, double input_rows)
+ {
+ RelOptInfo *rel;
+ AggClauseCosts agg_costs;
+ double dNumGroups;
+ AggPath *result = NULL;
+
+ rel = subpath->parent;
+ Assert(rel->gpi != NULL);
+
+ if (subpath->pathkeys == NIL)
+ return NULL;
+
+ if (!grouping_is_sortable(root->parse->groupClause))
+ return NULL;
+
+ if (first_call)
+ {
+ ListCell *lc1;
+ List *key_subset = NIL;
+
+ /*
+ * Find all query pathkeys that our relation does affect.
+ */
+ foreach(lc1, root->group_pathkeys)
+ {
+ PathKey *gkey = castNode(PathKey, lfirst(lc1));
+ ListCell *lc2;
+
+ foreach(lc2, subpath->pathkeys)
+ {
+ PathKey *skey = castNode(PathKey, lfirst(lc2));
+
+ if (skey == gkey)
+ {
+ key_subset = lappend(key_subset, gkey);
+ break;
+ }
+ }
+ }
+
+ if (key_subset == NIL)
+ return NULL;
+
+ /* Check if AGG_SORTED is useful for the whole query. */
+ if (!pathkeys_contained_in(key_subset, subpath->pathkeys))
+ return NULL;
+ }
+
+ if (first_call)
+ get_grouping_expressions(root, rel->gpi->target, group_clauses,
+ group_exprs, agg_exprs);
+
+ MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
+ Assert(*agg_exprs != NIL);
+ get_agg_clause_costs(root, (Node *) *agg_exprs, AGGSPLIT_INITIAL_SERIAL,
+ &agg_costs);
+
+ Assert(*group_exprs != NIL);
+ dNumGroups = estimate_num_groups(root, *group_exprs, input_rows, NULL);
+
+ /* TODO HAVING qual. */
+ Assert(*group_clauses != NIL);
+ result = create_agg_path(root, rel, subpath, rel->gpi->target, AGG_SORTED,
+ AGGSPLIT_INITIAL_SERIAL, *group_clauses, NIL,
+ &agg_costs, dNumGroups);
+
+ return result;
+ }
+
+ /*
+ * Appy partial AGG_HASHED aggregation to subpath.
+ *
+ * Arguments have the same meaning as those of create_agg_sorted_path.
+ *
+ */
+ AggPath *
+ create_partial_agg_hashed_path(PlannerInfo *root, Path *subpath,
+ bool first_call,
+ List **group_clauses, List **group_exprs,
+ List **agg_exprs, double input_rows)
+ {
+ RelOptInfo *rel;
+ bool can_hash;
+ AggClauseCosts agg_costs;
+ double dNumGroups;
+ Size hashaggtablesize;
+ Query *parse = root->parse;
+ AggPath *result = NULL;
+
+ rel = subpath->parent;
+ Assert(rel->gpi != NULL);
+
+ if (first_call)
+ {
+ /*
+ * Find one grouping clause per grouping column.
+ *
+ * All that create_agg_plan eventually needs of the clause is
+ * tleSortGroupRef, so we don't have to care that the clause
+ * expression might differ from texpr, in case texpr was derived from
+ * EC.
+ */
+ get_grouping_expressions(root, rel->gpi->target, group_clauses,
+ group_exprs, agg_exprs);
+ }
+
+ MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
+ Assert(*agg_exprs != NIL);
+ get_agg_clause_costs(root, (Node *) *agg_exprs, AGGSPLIT_INITIAL_SERIAL,
+ &agg_costs);
+
+ can_hash = (parse->groupClause != NIL &&
+ parse->groupingSets == NIL &&
+ agg_costs.numOrderedAggs == 0 &&
+ grouping_is_hashable(parse->groupClause));
+
+ if (can_hash)
+ {
+ Assert(*group_exprs != NIL);
+ dNumGroups = estimate_num_groups(root, *group_exprs, input_rows,
+ NULL);
+
+ hashaggtablesize = estimate_hashagg_tablesize(subpath, &agg_costs,
+ dNumGroups);
+
+ if (hashaggtablesize < work_mem * 1024L)
+ {
+ /*
+ * Create the partial aggregation path.
+ */
+ Assert(*group_clauses != NIL);
+
+ result = create_agg_path(root, rel, subpath,
+ rel->gpi->target,
+ AGG_HASHED,
+ AGGSPLIT_INITIAL_SERIAL,
+ *group_clauses, NIL,
+ &agg_costs,
+ dNumGroups);
+
+ /*
+ * The agg path should require no fewer parameters than the plain
+ * one.
+ */
+ result->path.param_info = subpath->param_info;
+ }
+ }
+
+ return result;
+ }
+
+ /*
* create_groupingsets_path
* Creates a pathnode that represents performing GROUPING SETS aggregation
*
*************** reparameterize_path(PlannerInfo *root, P
*** 3426,3428 ****
--- 3656,4081 ----
}
return NULL;
}
+
+ /*
+ * reparameterize_path_by_child
+ * Given a path parameterized by the parent of the given relation,
+ * translate the path to be parameterized by the given child relation.
+ *
+ * The function creates a new path of the same type as the given path, but
+ * parameterized by the given child relation. If it can not reparameterize the
+ * path as required, it returns NULL.
+ *
+ * The cost, number of rows, width and parallel path properties depend upon
+ * path->parent, which does not change during the translation. Hence those
+ * members are copied as they are.
+ */
+
+ Path *
+ reparameterize_path_by_child(PlannerInfo *root, Path *path,
+ RelOptInfo *child_rel)
+ {
+
+ #define FLAT_COPY_PATH(newnode, node, nodetype) \
+ ( (newnode) = makeNode(nodetype), \
+ memcpy((newnode), (node), sizeof(nodetype)) )
+
+ Path *new_path;
+ ParamPathInfo *new_ppi;
+ ParamPathInfo *old_ppi;
+ Relids required_outer;
+
+ /*
+ * If the path is not parameterized by parent of the given relation or it it
+ * doesn't need reparameterization.
+ */
+ if (!path->param_info ||
+ !bms_overlap(PATH_REQ_OUTER(path), child_rel->top_parent_relids))
+ return path;
+
+ /*
+ * Make a copy of the given path and reparameterize or translate the
+ * path specific members.
+ */
+ switch (nodeTag(path))
+ {
+ case T_Path:
+ FLAT_COPY_PATH(new_path, path, Path);
+ break;
+
+ case T_IndexPath:
+ {
+ IndexPath *ipath;
+
+ FLAT_COPY_PATH(ipath, path, IndexPath);
+ ipath->indexclauses = (List *) adjust_appendrel_attrs_multilevel(root,
+ (Node *) ipath->indexclauses,
+ child_rel->relids,
+ child_rel->top_parent_relids);
+ ipath->indexquals = (List *) adjust_appendrel_attrs_multilevel(root,
+ (Node *) ipath->indexquals,
+ child_rel->relids,
+ child_rel->top_parent_relids);
+ new_path = (Path *) ipath;
+ }
+ break;
+
+ case T_BitmapHeapPath:
+ {
+ BitmapHeapPath *bhpath;
+
+ FLAT_COPY_PATH(bhpath, path, BitmapHeapPath);
+ bhpath->bitmapqual = reparameterize_path_by_child(root,
+ bhpath->bitmapqual,
+ child_rel);
+ new_path = (Path *) bhpath;
+ }
+ break;
+
+ case T_BitmapAndPath:
+ {
+ BitmapAndPath *bapath;
+ ListCell *lc;
+ List *bitmapquals = NIL;
+
+ FLAT_COPY_PATH(bapath, path, BitmapAndPath);
+ foreach (lc, bapath->bitmapquals)
+ {
+ Path *bmqpath = lfirst(lc);
+
+ bitmapquals = lappend(bitmapquals,
+ reparameterize_path_by_child(root,
+ bmqpath,
+ child_rel));
+ }
+ bapath->bitmapquals = bitmapquals;
+ new_path = (Path *) bapath;
+ }
+ break;
+
+ case T_BitmapOrPath:
+ {
+ BitmapOrPath *bopath;
+ ListCell *lc;
+ List *bitmapquals = NIL;
+
+ FLAT_COPY_PATH(bopath, path, BitmapOrPath);
+ foreach (lc, bopath->bitmapquals)
+ {
+ Path *bmqpath = lfirst(lc);
+
+ bitmapquals = lappend(bitmapquals,
+ reparameterize_path_by_child(root,
+ bmqpath,
+ child_rel));
+ }
+ bopath->bitmapquals = bitmapquals;
+ new_path = (Path *) bopath;
+ }
+ break;
+
+ case T_TidPath:
+ {
+ TidPath *tpath;
+
+ /*
+ * TidPath contains tidquals, which do not contain any external
+ * parameters per create_tidscan_path(). So don't bother to
+ * translate those.
+ */
+ FLAT_COPY_PATH(tpath, path, TidPath);
+ new_path = (Path *) tpath;
+ }
+ break;
+
+ case T_ForeignPath:
+ {
+ ForeignPath *fpath;
+ ReparameterizeForeignPathByChild_function rfpc_func;
+
+ FLAT_COPY_PATH(fpath, path, ForeignPath);
+ if (fpath->fdw_outerpath)
+ fpath->fdw_outerpath = reparameterize_path_by_child(root,
+ fpath->fdw_outerpath,
+ child_rel);
+ rfpc_func = path->parent->fdwroutine->ReparameterizeForeignPathByChild;
+
+ /* Hand over to FDW if supported. */
+ if (rfpc_func)
+ fpath->fdw_private = rfpc_func(root, fpath->fdw_private,
+ child_rel);
+ new_path = (Path *) fpath;
+ }
+ break;
+
+ case T_CustomPath:
+ {
+ CustomPath *cpath;
+ ListCell *lc;
+ List *custompaths = NIL;
+
+ FLAT_COPY_PATH(cpath, path, CustomPath);
+
+ foreach (lc, cpath->custom_paths)
+ {
+ Path *subpath = lfirst(lc);
+
+ custompaths = lappend(custompaths,
+ reparameterize_path_by_child(root,
+ subpath,
+ child_rel));
+ }
+ cpath->custom_paths = custompaths;
+
+ if (cpath->methods &&
+ cpath->methods->ReparameterizeCustomPathByChild)
+ cpath->custom_private = cpath->methods->ReparameterizeCustomPathByChild(root,
+ cpath->custom_private,
+ child_rel);
+
+ new_path = (Path *) cpath;
+ }
+ break;
+
+ case T_NestPath:
+ {
+ JoinPath *jpath;
+
+ FLAT_COPY_PATH(jpath, path, NestPath);
+
+ jpath->outerjoinpath = reparameterize_path_by_child(root,
+ jpath->outerjoinpath,
+ child_rel);
+ jpath->innerjoinpath = reparameterize_path_by_child(root,
+ jpath->innerjoinpath,
+ child_rel);
+ jpath->joinrestrictinfo = (List *) adjust_appendrel_attrs_multilevel(root,
+ (Node *) jpath->joinrestrictinfo,
+ child_rel->relids,
+ child_rel->top_parent_relids);
+ new_path = (Path *) jpath;
+ }
+ break;
+
+ case T_MergePath:
+ {
+ JoinPath *jpath;
+ MergePath *mpath;
+
+ FLAT_COPY_PATH(mpath, path, MergePath);
+
+ jpath = (JoinPath *) mpath;
+ jpath->outerjoinpath = reparameterize_path_by_child(root,
+ jpath->outerjoinpath,
+ child_rel);
+ jpath->innerjoinpath = reparameterize_path_by_child(root,
+ jpath->innerjoinpath,
+ child_rel);
+ jpath->joinrestrictinfo = (List *) adjust_appendrel_attrs_multilevel(root,
+ (Node *) jpath->joinrestrictinfo,
+ child_rel->relids,
+ child_rel->top_parent_relids);
+ mpath->path_mergeclauses = (List *) adjust_appendrel_attrs_multilevel(root,
+ (Node *) mpath->path_mergeclauses,
+ child_rel->relids,
+ child_rel->top_parent_relids);
+ new_path = (Path *) mpath;
+ }
+ break;
+
+ case T_HashPath:
+ {
+ JoinPath *jpath;
+ HashPath *hpath;
+ FLAT_COPY_PATH(hpath, path, HashPath);
+
+ jpath = (JoinPath *) hpath;
+ jpath->outerjoinpath = reparameterize_path_by_child(root,
+ jpath->outerjoinpath,
+ child_rel);
+ jpath->innerjoinpath = reparameterize_path_by_child(root,
+ jpath->innerjoinpath,
+ child_rel);
+ jpath->joinrestrictinfo = (List *) adjust_appendrel_attrs_multilevel(root,
+ (Node *) jpath->joinrestrictinfo,
+ child_rel->relids,
+ child_rel->top_parent_relids);
+ hpath->path_hashclauses = (List *) adjust_appendrel_attrs_multilevel(root,
+ (Node *) hpath->path_hashclauses,
+ child_rel->relids,
+ child_rel->top_parent_relids);
+ new_path = (Path *) hpath;
+ }
+ break;
+
+ case T_AppendPath:
+ {
+ AppendPath *apath;
+ List *subpaths = NIL;
+ ListCell *lc;
+
+ FLAT_COPY_PATH(apath, path, AppendPath);
+ foreach (lc, apath->subpaths)
+ subpaths = lappend(subpaths,
+ reparameterize_path_by_child(root,
+ lfirst(lc),
+ child_rel));
+ apath->subpaths = subpaths;
+ new_path = (Path *) apath;
+ }
+ break;
+
+ case T_MergeAppend:
+ {
+ MergeAppendPath *mapath;
+ List *subpaths = NIL;
+ ListCell *lc;
+
+ FLAT_COPY_PATH(mapath, path, MergeAppendPath);
+ foreach (lc, mapath->subpaths)
+ subpaths = lappend(subpaths,
+ reparameterize_path_by_child(root,
+ lfirst(lc),
+ child_rel));
+ mapath->subpaths = subpaths;
+ new_path = (Path *) mapath;
+ }
+ break;
+
+ case T_MaterialPath:
+ {
+ MaterialPath *mpath;
+
+ FLAT_COPY_PATH(mpath, path, MaterialPath);
+ mpath->subpath = reparameterize_path_by_child(root,
+ mpath->subpath,
+ child_rel);
+ new_path = (Path *) mpath;
+ }
+ break;
+
+ case T_UniquePath:
+ {
+ UniquePath *upath;
+
+ FLAT_COPY_PATH(upath, path, UniquePath);
+ upath->subpath = reparameterize_path_by_child(root,
+ upath->subpath,
+ child_rel);
+ upath->uniq_exprs = (List *) adjust_appendrel_attrs_multilevel(root,
+ (Node *) upath->uniq_exprs,
+ child_rel->relids,
+ child_rel->top_parent_relids);
+ new_path = (Path *) upath;
+ }
+ break;
+
+ case T_GatherPath:
+ {
+ GatherPath *gpath;
+
+ FLAT_COPY_PATH(gpath, path, GatherPath);
+ gpath->subpath = reparameterize_path_by_child(root,
+ gpath->subpath,
+ child_rel);
+ new_path = (Path *) gpath;
+ }
+ break;
+
+ case T_GatherMergePath:
+ {
+ GatherMergePath *gmpath;
+
+ FLAT_COPY_PATH(gmpath, path, GatherMergePath);
+ gmpath->subpath = reparameterize_path_by_child(root,
+ gmpath->subpath,
+ child_rel);
+ new_path = (Path *) gmpath;
+ }
+ break;
+
+ case T_SubqueryScanPath:
+ /*
+ * Subqueries can't be partitioned right now, so a subquery can not
+ * participate in a partition-wise join and hence can not be seen
+ * here.
+ */
+ case T_ResultPath:
+ /*
+ * A result path can not have any parameterization, so we
+ * should never see it here.
+ */
+ default:
+ /* Other kinds of paths can not appear in a join tree. */
+ elog(ERROR, "unrecognized path node type %d", (int) nodeTag(path));
+
+ /* Keep compiler quite about unassigned new_path */
+ return NULL;
+ }
+
+ /*
+ * Adjust the parameterization information, which refers to the topmost
+ * parent. The topmost parent can be multiple levels away from the given
+ * child, hence use multi-level expression adjustment routines.
+ */
+ old_ppi = new_path->param_info;
+ required_outer = adjust_child_relids_multilevel(root,
+ old_ppi->ppi_req_outer,
+ child_rel->relids,
+ child_rel->top_parent_relids);
+
+ /* If we already have a PPI for this parameterization, just return it */
+ new_ppi = find_param_path_info(new_path->parent, required_outer);
+
+ /*
+ * If not, build a new one and link it to the list of PPIs. When called
+ * during GEQO join planning, we are in a short-lived memory context. We
+ * must make sure that the new PPI and its contents attached to a baserel
+ * survives the GEQO cycle, else the baserel is trashed for future GEQO
+ * cycles. On the other hand, when we are adding new PPI to a joinrel
+ * during GEQO, we don't want that to clutter the main planning context.
+ * Upshot is that the best solution is to explicitly allocate new PPI in
+ * the same context the given RelOptInfo is in.
+ */
+ if (!new_ppi)
+ {
+ MemoryContext oldcontext;
+ RelOptInfo *rel = path->parent;
+
+ oldcontext = MemoryContextSwitchTo(GetMemoryChunkContext(rel));
+
+ new_ppi = makeNode(ParamPathInfo);
+ new_ppi->ppi_req_outer = bms_copy(required_outer);
+ new_ppi->ppi_rows = old_ppi->ppi_rows;
+ new_ppi->ppi_clauses = (List *) adjust_appendrel_attrs_multilevel(root,
+ (Node *) old_ppi->ppi_clauses,
+ child_rel->relids,
+ child_rel->top_parent_relids);
+ rel->ppilist = lappend(rel->ppilist, new_ppi);
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+ bms_free(required_outer);
+
+ new_path->param_info = new_ppi;
+
+ /*
+ * Adjust the path target if the parent of the outer relation is referenced
+ * in the targetlist. This can happen when only the parent of outer relation is
+ * laterally referenced in this relation.
+ */
+ if (bms_overlap(path->parent->lateral_relids, child_rel->top_parent_relids))
+ {
+ List *exprs;
+
+ new_path->pathtarget = copy_pathtarget(new_path->pathtarget);
+ exprs = new_path->pathtarget->exprs;
+ exprs = (List *) adjust_appendrel_attrs_multilevel(root,
+ (Node *) exprs,
+ child_rel->relids,
+ child_rel->top_parent_relids);
+ new_path->pathtarget->exprs = exprs;
+ }
+
+ return new_path;
+ }
diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c
new file mode 100644
index 698a387..6714288
*** a/src/backend/optimizer/util/placeholder.c
--- b/src/backend/optimizer/util/placeholder.c
***************
*** 20,25 ****
--- 20,26 ----
#include "optimizer/pathnode.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
+ #include "optimizer/prep.h"
#include "optimizer/var.h"
#include "utils/lsyscache.h"
*************** add_placeholders_to_joinrel(PlannerInfo
*** 414,419 ****
--- 415,424 ----
Relids relids = joinrel->relids;
ListCell *lc;
+ /* This function is called only on the parent relations. */
+ Assert(!IS_OTHER_REL(joinrel) && !IS_OTHER_REL(outer_rel) &&
+ !IS_OTHER_REL(inner_rel));
+
foreach(lc, root->placeholder_list)
{
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
*************** add_placeholders_to_joinrel(PlannerInfo
*** 459,461 ****
--- 464,518 ----
}
}
}
+
+ /*
+ * add_placeholders_to_child_joinrel
+ * Translate the PHVs in parent's targetlist and add them to the child's
+ * targetlist. Also adjust the cost
+ */
+ void
+ add_placeholders_to_child_joinrel(PlannerInfo *root, RelOptInfo *childrel,
+ RelOptInfo *parentrel)
+ {
+ ListCell *lc;
+ AppendRelInfo **appinfos;
+ int nappinfos;
+
+
+ Assert(IS_JOIN_REL(childrel) && IS_JOIN_REL(parentrel));
+
+ /* Ensure child relations is really what it claims to be. */
+ Assert(IS_OTHER_REL(childrel));
+
+ appinfos = find_appinfos_by_relids(root, childrel->relids, &nappinfos);
+ foreach (lc, parentrel->reltarget->exprs)
+ {
+ PlaceHolderVar *phv = lfirst(lc);
+
+ if (IsA(phv, PlaceHolderVar))
+ {
+ /*
+ * In case the placeholder Var refers to any of the parent
+ * relations, translate it to refer to the corresponding child.
+ */
+ if (bms_overlap(phv->phrels, parentrel->relids) &&
+ childrel->reloptkind == RELOPT_OTHER_JOINREL)
+ {
+ phv = (PlaceHolderVar *) adjust_appendrel_attrs(root,
+ (Node *) phv,
+ nappinfos,
+ appinfos);
+ }
+
+ childrel->reltarget->exprs = lappend(childrel->reltarget->exprs,
+ phv);
+ }
+ }
+
+ /* Adjust the cost and width of child targetlist. */
+ childrel->reltarget->cost.startup = parentrel->reltarget->cost.startup;
+ childrel->reltarget->cost.per_tuple = parentrel->reltarget->cost.per_tuple;
+ childrel->reltarget->width = parentrel->reltarget->width;
+
+ pfree(appinfos);
+ }
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
new file mode 100644
index 9207c8d..7e846e1
*** a/src/backend/optimizer/util/plancat.c
--- b/src/backend/optimizer/util/plancat.c
***************
*** 27,32 ****
--- 27,33 ----
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+ #include "catalog/pg_inherits_fn.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_statistic_ext.h"
*************** static List *get_relation_constraints(Pl
*** 68,73 ****
--- 69,80 ----
static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
Relation heapRelation);
static List *get_relation_statistics(RelOptInfo *rel, Relation relation);
+ static List **build_baserel_partition_key_exprs(Relation relation,
+ Index varno);
+ static PartitionScheme find_partition_scheme(struct PlannerInfo *root,
+ Relation rel);
+ static void get_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
+ Relation relation);
/*
* get_relation_info -
*************** get_relation_info(PlannerInfo *root, Oid
*** 420,425 ****
--- 427,436 ----
/* Collect info about relation's foreign keys, if relevant */
get_relation_foreign_keys(root, rel, relation, inhparent);
+ /* Collect info about relation's partitioning scheme, if any. */
+ if (inhparent)
+ get_relation_partition_info(root, rel, relation);
+
heap_close(relation, NoLock);
/*
*************** has_row_triggers(PlannerInfo *root, Inde
*** 1801,1803 ****
--- 1812,1975 ----
heap_close(relation, NoLock);
return result;
}
+
+ /*
+ * get_relation_partition_info
+ *
+ * Retrieves partitioning information for a given relation.
+ *
+ * Partitioning scheme, partition key expressions and OIDs of partitions are
+ * added to the given RelOptInfo. A partitioned table can participate in the
+ * query as a simple relation or an inheritance parent. Only the later can have
+ * child relations, and hence partitions. From the point of view of the query
+ * optimizer only such relations are considered to be partitioned. Hence
+ * partitioning information is set only for an inheritance parent.
+ */
+ static void
+ get_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
+ Relation relation)
+ {
+ PartitionDesc part_desc = RelationGetPartitionDesc(relation);
+
+ /* No partitioning information for an unpartitioned relation. */
+ if (relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE ||
+ !(rel->part_scheme = find_partition_scheme(root, relation)))
+ return;
+
+ Assert(part_desc);
+ rel->nparts = part_desc->nparts;
+ rel->boundinfo = part_desc->boundinfo;
+ rel->partexprs = build_baserel_partition_key_exprs(relation, rel->relid);
+ rel->part_oids = part_desc->oids;
+
+ Assert(rel->nparts > 0 && rel->boundinfo && rel->part_oids);
+ return;
+ }
+
+ /*
+ * find_partition_scheme
+ *
+ * The function returns a canonical partition scheme which exactly matches the
+ * partitioning properties of the given relation if one exists in the of
+ * canonical partitioning schemes maintained in PlannerInfo. If none of the
+ * existing partitioning schemes match, the function creates a canonical
+ * partition scheme and adds it to the list.
+ *
+ * For an unpartitioned table or for a multi-level partitioned table it returns
+ * NULL. See comments in the function for more details.
+ */
+ static PartitionScheme
+ find_partition_scheme(PlannerInfo *root, Relation relation)
+ {
+ PartitionKey part_key = RelationGetPartitionKey(relation);
+ ListCell *lc;
+ int partnatts;
+ PartitionScheme part_scheme = NULL;
+
+ /* No partition scheme for an unpartitioned relation. */
+ if (!part_key)
+ return NULL;
+
+ partnatts = part_key->partnatts;
+
+ /* Search for a matching partition scheme and return if found one. */
+ foreach (lc, root->part_schemes)
+ {
+ part_scheme = lfirst(lc);
+
+ /* Match partitioning strategy and number of keys. */
+ if (part_key->strategy != part_scheme->strategy ||
+ partnatts != part_scheme->partnatts)
+ continue;
+
+ /* Match the partition key types. */
+ if (memcmp(part_key->partopfamily, part_scheme->partopfamily,
+ sizeof(Oid) * partnatts) != 0 ||
+ memcmp(part_key->partopcintype, part_scheme->partopcintype,
+ sizeof(Oid) * partnatts) != 0 ||
+ memcmp(part_key->parttypcoll, part_scheme->parttypcoll,
+ sizeof(Oid) * partnatts) != 0)
+ continue;
+
+ /* Found matching partition scheme. */
+ return part_scheme;
+ }
+
+ /* Did not find matching partition scheme. Create one. */
+ part_scheme = (PartitionScheme) palloc0(sizeof(PartitionSchemeData));
+
+ part_scheme->strategy = part_key->strategy;
+ /* Store partition key information. */
+ part_scheme->partnatts = part_key->partnatts;
+ part_scheme->partopfamily = part_key->partopfamily;
+ part_scheme->partopcintype = part_key->partopcintype;
+ part_scheme->parttypcoll = part_key->parttypcoll;
+ part_scheme->partsupfunc = part_key->partsupfunc;
+
+ /* Add the partitioning scheme to PlannerInfo. */
+ root->part_schemes = lappend(root->part_schemes, part_scheme);
+
+ return part_scheme;
+ }
+
+ /*
+ * build_baserel_partition_key_exprs
+ *
+ * Collect partition key expressions for a given base relation. The function
+ * converts any single column partition keys into corresponding Var nodes. It
+ * restamps Var nodes in partition key expressions by given varno. The
+ * partition key expressions are returned as an array of single element lists
+ * to be stored in RelOptInfo of the base relation.
+ */
+ static List **
+ build_baserel_partition_key_exprs(Relation relation, Index varno)
+ {
+ PartitionKey part_key = RelationGetPartitionKey(relation);
+ int num_pkexprs;
+ int cnt_pke;
+ List **partexprs;
+ ListCell *lc;
+
+ if (!part_key || part_key->partnatts <= 0)
+ return NULL;
+
+ num_pkexprs = part_key->partnatts;
+ partexprs = (List **) palloc(sizeof(List *) * num_pkexprs);
+ lc = list_head(part_key->partexprs);
+
+ for (cnt_pke = 0; cnt_pke < num_pkexprs; cnt_pke++)
+ {
+ AttrNumber attno = part_key->partattrs[cnt_pke];
+ Expr *pkexpr;
+
+ if (attno != InvalidAttrNumber)
+ {
+ /* Single column partition key is stored as a Var node. */
+ Form_pg_attribute att_tup;
+
+ if (attno < 0)
+ att_tup = SystemAttributeDefinition(attno,
+ relation->rd_rel->relhasoids);
+ else
+ att_tup = relation->rd_att->attrs[attno - 1];
+
+ pkexpr = (Expr *) makeVar(varno, attno, att_tup->atttypid,
+ att_tup->atttypmod,
+ att_tup->attcollation, 0);
+ }
+ else
+ {
+ if (lc == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+
+ /* Re-stamp the expression with given varno. */
+ pkexpr = (Expr *) copyObject(lfirst(lc));
+ ChangeVarNodes((Node *) pkexpr, 1, varno, 0);
+ lc = lnext(lc);
+ }
+
+ partexprs[cnt_pke] = list_make1(pkexpr);
+ }
+
+ return partexprs;
+ }
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index 342d884..308bdec
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 23,30 ****
--- 23,32 ----
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/plancat.h"
+ #include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
+ #include "optimizer/var.h"
#include "utils/hsearch.h"
*************** typedef struct JoinHashEntry
*** 35,41 ****
} JoinHashEntry;
static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel);
static List *build_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outer_rel,
--- 37,43 ----
} JoinHashEntry;
static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel, bool grouped);
static List *build_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outer_rel,
*************** static List *subbuild_joinrel_joinlist(R
*** 52,57 ****
--- 54,64 ----
static void set_foreign_rel_properties(RelOptInfo *joinrel,
RelOptInfo *outer_rel, RelOptInfo *inner_rel);
static void add_join_rel(PlannerInfo *root, RelOptInfo *joinrel);
+ extern ParamPathInfo *find_param_path_info(RelOptInfo *rel,
+ Relids required_outer);
+ static void build_joinrel_partition_info(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel, RelOptInfo *inner_rel,
+ List *restrictlist, JoinType jointype);
/*
*************** build_simple_rel(PlannerInfo *root, int
*** 120,125 ****
--- 127,133 ----
rel->cheapest_parameterized_paths = NIL;
rel->direct_lateral_relids = NULL;
rel->lateral_relids = NULL;
+ rel->gpi = NULL;
rel->relid = relid;
rel->rtekind = rte->rtekind;
/* min_attr, max_attr, attr_needed, attr_widths are set below */
*************** build_simple_rel(PlannerInfo *root, int
*** 146,151 ****
--- 154,164 ----
rel->baserestrict_min_security = UINT_MAX;
rel->joininfo = NIL;
rel->has_eclass_joins = false;
+ rel->part_scheme = NULL;
+ rel->nparts = 0;
+ rel->boundinfo = NULL;
+ rel->partexprs = NULL;
+ rel->part_rels = NULL;
/*
* Pass top parent's relids down the inheritance hierarchy. If the parent
*************** build_simple_rel(PlannerInfo *root, int
*** 218,237 ****
if (rte->inh)
{
ListCell *l;
foreach(l, root->append_rel_list)
{
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);
/* append_rel_list contains all append rels; ignore others */
if (appinfo->parent_relid != relid)
continue;
! (void) build_simple_rel(root, appinfo->child_relid,
! rel);
}
}
return rel;
}
--- 231,293 ----
if (rte->inh)
{
ListCell *l;
+ int nparts = rel->nparts;
+
+ if (nparts > 0)
+ rel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * nparts);
foreach(l, root->append_rel_list)
{
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);
+ RelOptInfo *childrel;
+ int cnt_parts;
+ RangeTblEntry *childRTE;
/* append_rel_list contains all append rels; ignore others */
if (appinfo->parent_relid != relid)
continue;
! childrel = build_simple_rel(root, appinfo->child_relid,
! rel);
!
! /* Nothing more to do for an unpartitioned table. */
! if (!rel->part_scheme)
! continue;
!
! childRTE = root->simple_rte_array[appinfo->child_relid];
! /*
! * Two partitioned tables with the same partitioning scheme, have
! * their partition bounds arranged in the same order. The order of
! * partition OIDs in RelOptInfo corresponds to the partition bound
! * order. Thus the OIDs of matching partitions from both the tables
! * are placed at the same position in the array of partition OIDs
! * in the respective RelOptInfos. Arranging RelOptInfos of
! * partitions in the same order as their OIDs makes it easy to find
! * the RelOptInfos of matching partitions for partition-wise join.
! */
! for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++)
! {
! if (rel->part_oids[cnt_parts] == childRTE->relid)
! {
! Assert(!rel->part_rels[cnt_parts]);
! rel->part_rels[cnt_parts] = childrel;
! break;
! }
! }
}
}
+ /* Should have found all the childrels of a partitioned relation. */
+ if (rel->part_scheme)
+ {
+ int cnt_parts;
+
+ for (cnt_parts = 0; cnt_parts < rel->nparts; cnt_parts++)
+ if (!rel->part_rels[cnt_parts])
+ elog(ERROR, "could not find the RelOptInfo of a partition with oid %u",
+ rel->part_oids[cnt_parts]);
+ }
+
return rel;
}
*************** build_join_rel(PlannerInfo *root,
*** 453,458 ****
--- 509,517 ----
RelOptInfo *joinrel;
List *restrictlist;
+ /* This function should be used only for join between parents. */
+ Assert(!IS_OTHER_REL(outer_rel) && !IS_OTHER_REL(inner_rel));
+
/*
* See if we already have a joinrel for this set of base rels.
*/
*************** build_join_rel(PlannerInfo *root,
*** 497,502 ****
--- 556,562 ----
inner_rel->direct_lateral_relids);
joinrel->lateral_relids = min_join_parameterization(root, joinrel->relids,
outer_rel, inner_rel);
+ joinrel->gpi = NULL;
joinrel->relid = 0; /* indicates not a baserel */
joinrel->rtekind = RTE_JOIN;
joinrel->min_attr = 0;
*************** build_join_rel(PlannerInfo *root,
*** 527,532 ****
--- 587,597 ----
joinrel->joininfo = NIL;
joinrel->has_eclass_joins = false;
joinrel->top_parent_relids = NULL;
+ joinrel->part_scheme = NULL;
+ joinrel->nparts = 0;
+ joinrel->boundinfo = NULL;
+ joinrel->partexprs = NULL;
+ joinrel->part_rels = NULL;
/* Compute information relevant to the foreign relations. */
set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
*************** build_join_rel(PlannerInfo *root,
*** 539,548 ****
* and inner rels we first try to build it from. But the contents should
* be the same regardless.
*/
! build_joinrel_tlist(root, joinrel, outer_rel);
! build_joinrel_tlist(root, joinrel, inner_rel);
add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
/*
* add_placeholders_to_joinrel also took care of adding the ph_lateral
* sets of any PlaceHolderVars computed here to direct_lateral_relids, so
--- 604,620 ----
* and inner rels we first try to build it from. But the contents should
* be the same regardless.
*/
! build_joinrel_tlist(root, joinrel, outer_rel, false);
! build_joinrel_tlist(root, joinrel, inner_rel, false);
add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
+ /* Try to build grouped target. */
+ /*
+ * TODO Consider if placeholders make sense here. If not, also make the
+ * related code below conditional.
+ */
+ prepare_rel_for_grouping(root, joinrel);
+
/*
* add_placeholders_to_joinrel also took care of adding the ph_lateral
* sets of any PlaceHolderVars computed here to direct_lateral_relids, so
*************** build_join_rel(PlannerInfo *root,
*** 572,577 ****
--- 644,653 ----
*/
joinrel->has_eclass_joins = has_relevant_eclass_joinclause(root, joinrel);
+ /* Store the partition information. */
+ build_joinrel_partition_info(joinrel, outer_rel, inner_rel, restrictlist,
+ sjinfo->jointype);
+
/*
* Set estimates of the joinrel's size.
*/
*************** build_join_rel(PlannerInfo *root,
*** 617,622 ****
--- 693,845 ----
return joinrel;
}
+ /*
+ * build_child_join_rel
+ * Builds RelOptInfo for joining given two child relations from RelOptInfo
+ * representing the join between their parents.
+ *
+ * 'outer_rel' and 'inner_rel' are the RelOptInfos of child relations being
+ * joined.
+ * 'parent_joinrel' is the RelOptInfo representing the join between parent
+ * relations. Most of the members of new RelOptInfo are produced by
+ * translating corresponding members of this RelOptInfo.
+ * 'sjinfo': context info for child join
+ * 'restrictlist': list of RestrictInfo nodes that apply to this particular
+ * pair of joinable relations.
+ * 'join_appinfos': list of AppendRelInfo nodes for base child relations involved
+ * in this join.
+ */
+ RelOptInfo *
+ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel, RelOptInfo *parent_joinrel,
+ List *restrictlist, SpecialJoinInfo *sjinfo,
+ JoinType jointype)
+ {
+ RelOptInfo *joinrel = makeNode(RelOptInfo);
+ AppendRelInfo **appinfos;
+ int nappinfos;
+
+ /* Only joins between other relations land here. */
+ Assert(IS_OTHER_REL(outer_rel) && IS_OTHER_REL(inner_rel));
+
+ joinrel->reloptkind = RELOPT_OTHER_JOINREL;
+ joinrel->relids = bms_union(outer_rel->relids, inner_rel->relids);
+ joinrel->rows = 0;
+ /* cheap startup cost is interesting iff not all tuples to be retrieved */
+ joinrel->consider_startup = (root->tuple_fraction > 0);
+ joinrel->consider_param_startup = false;
+ joinrel->consider_parallel = false;
+ joinrel->reltarget = create_empty_pathtarget();
+ joinrel->pathlist = NIL;
+ joinrel->ppilist = NIL;
+ joinrel->partial_pathlist = NIL;
+ joinrel->cheapest_startup_path = NULL;
+ joinrel->cheapest_total_path = NULL;
+ joinrel->cheapest_unique_path = NULL;
+ joinrel->cheapest_parameterized_paths = NIL;
+ joinrel->direct_lateral_relids = NULL;
+ joinrel->lateral_relids = NULL;
+ joinrel->gpi = makeNode(GroupedPathInfo);
+ if (parent_joinrel->gpi)
+ /*
+ * Translation into child varnos will take place along with other
+ * translations, see try_partition_wise_join.
+ */
+ joinrel->gpi->target = copy_pathtarget(parent_joinrel->gpi->target);
+ joinrel->relid = 0; /* indicates not a baserel */
+ joinrel->rtekind = RTE_JOIN;
+ joinrel->min_attr = 0;
+ joinrel->max_attr = 0;
+ joinrel->attr_needed = NULL;
+ joinrel->attr_widths = NULL;
+ joinrel->lateral_vars = NIL;
+ joinrel->lateral_referencers = NULL;
+ joinrel->indexlist = NIL;
+ joinrel->pages = 0;
+ joinrel->tuples = 0;
+ joinrel->allvisfrac = 0;
+ joinrel->subroot = NULL;
+ joinrel->subplan_params = NIL;
+ joinrel->serverid = InvalidOid;
+ joinrel->userid = InvalidOid;
+ joinrel->useridiscurrent = false;
+ joinrel->fdwroutine = NULL;
+ joinrel->fdw_private = NULL;
+ joinrel->baserestrictinfo = NIL;
+ joinrel->baserestrictcost.startup = 0;
+ joinrel->baserestrictcost.per_tuple = 0;
+ joinrel->joininfo = NIL;
+ joinrel->has_eclass_joins = false;
+ joinrel->top_parent_relids = NULL;
+ joinrel->part_scheme = NULL;
+ joinrel->part_rels = NULL;
+ joinrel->partexprs = NULL;
+
+ joinrel->top_parent_relids = bms_union(outer_rel->top_parent_relids,
+ inner_rel->top_parent_relids);
+
+ /* Compute information relevant to foreign relations. */
+ set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
+
+ /* Build targetlist */
+ build_joinrel_tlist(root, joinrel, outer_rel, false);
+ build_joinrel_tlist(root, joinrel, inner_rel, false);
+ /* Add placeholder variables. */
+ add_placeholders_to_child_joinrel(root, joinrel, parent_joinrel);
+
+ /* Try to build grouped target. */
+ /*
+ * TODO Consider if placeholders make sense here. If not, also make the
+ * related code below conditional.
+ */
+ prepare_rel_for_grouping(root, joinrel);
+
+
+ /* Construct joininfo list. */
+ appinfos = find_appinfos_by_relids(root, joinrel->relids, &nappinfos);
+ joinrel->joininfo = (List *) adjust_appendrel_attrs(root,
+ (Node *) parent_joinrel->joininfo,
+ nappinfos,
+ appinfos);
+ pfree(appinfos);
+
+ /*
+ * Lateral relids referred in child join will be same as that referred in
+ * the parent relation. Throw any partial result computed while building
+ * the targetlist.
+ */
+ bms_free(joinrel->direct_lateral_relids);
+ bms_free(joinrel->lateral_relids);
+ joinrel->direct_lateral_relids = (Relids) bms_copy(parent_joinrel->direct_lateral_relids);
+ joinrel->lateral_relids = (Relids) bms_copy(parent_joinrel->lateral_relids);
+
+ /*
+ * If the parent joinrel has pending equivalence classes, so does the
+ * child.
+ */
+ joinrel->has_eclass_joins = parent_joinrel->has_eclass_joins;
+
+ /* Is the join between partitions itself partitioned? */
+ build_joinrel_partition_info(joinrel, outer_rel, inner_rel, restrictlist,
+ jointype);
+
+ /* Child joinrel is parallel safe if parent is parallel safe. */
+ joinrel->consider_parallel = parent_joinrel->consider_parallel;
+
+
+ /* Set estimates of the child-joinrel's size. */
+ set_joinrel_size_estimates(root, joinrel, outer_rel, inner_rel,
+ sjinfo, restrictlist);
+
+ /* We build the join only once. */
+ Assert(!find_join_rel(root, joinrel->relids));
+
+ /* Add the relation to the PlannerInfo. */
+ add_join_rel(root, joinrel);
+
+ return joinrel;
+ }
+
/*
* min_join_parameterization
*
*************** min_join_parameterization(PlannerInfo *r
*** 670,679 ****
*/
static void
build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel)
{
! Relids relids = joinrel->relids;
ListCell *vars;
foreach(vars, input_rel->reltarget->exprs)
{
--- 893,932 ----
*/
static void
build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel, bool grouped)
{
! Relids relids;
! PathTarget *input_target, *result;
ListCell *vars;
+ int i = -1;
+
+ /* attrs_needed refers to parent relids and not those of a child. */
+ if (joinrel->top_parent_relids)
+ relids = joinrel->top_parent_relids;
+ else
+ relids = joinrel->relids;
+
+ if (!grouped)
+ {
+ input_target = input_rel->reltarget;
+ result = joinrel->reltarget;
+ }
+ else
+ {
+ if (input_rel->gpi != NULL)
+ {
+ input_target = input_rel->gpi->target;
+ Assert(input_target != NULL);
+ }
+ else
+ input_target = input_rel->reltarget;
+
+ /* Caller should have initialized this. */
+ Assert(joinrel->gpi != NULL);
+
+ /* Default to the plain target. */
+ result = joinrel->gpi->target;
+ }
foreach(vars, input_rel->reltarget->exprs)
{
*************** build_joinrel_tlist(PlannerInfo *root, R
*** 690,713 ****
/*
* Otherwise, anything in a baserel or joinrel targetlist ought to be
! * a Var. (More general cases can only appear in appendrel child
! * rels, which will never be seen here.)
*/
! if (!IsA(var, Var))
elog(ERROR, "unexpected node type in rel targetlist: %d",
(int) nodeTag(var));
- /* Get the Var's original base rel */
- baserel = find_base_rel(root, var->varno);
-
- /* Is it still needed above this joinrel? */
- ndx = var->varattno - baserel->min_attr;
if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
{
/* Yup, add it to the output */
! joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var);
! /* Vars have cost zero, so no need to adjust reltarget->cost */
! joinrel->reltarget->width += baserel->attr_widths[ndx];
}
}
}
--- 943,1009 ----
/*
* Otherwise, anything in a baserel or joinrel targetlist ought to be
! * a Var or ConvertRowtypeExpr introduced while translating parent
! * targetlist to that of the child.
*/
! if (IsA(var, Var))
! {
! /* Get the Var's original base rel */
! baserel = find_base_rel(root, var->varno);
!
! /* Is it still needed above this joinrel? */
! ndx = var->varattno - baserel->min_attr;
! }
! else if (IsA(var, ConvertRowtypeExpr))
! {
! ConvertRowtypeExpr *child_expr = (ConvertRowtypeExpr *) var;
! Var *childvar = (Var *) child_expr->arg;
!
! /*
! * Child's whole-row references are converted to that of parent
! * using ConvertRowtypeExpr. There can be as many
! * ConvertRowtypeExpr decorations as the depth of partition tree.
! * The argument to deepest ConvertRowtypeExpr is expected to be a
! * whole-row reference of the child.
! */
! while (IsA(childvar, ConvertRowtypeExpr))
! {
! child_expr = (ConvertRowtypeExpr *) childvar;
! childvar = (Var *) child_expr->arg;
! }
! Assert(IsA(childvar, Var) && childvar->varattno == 0);
!
! baserel = find_base_rel(root, childvar->varno);
! ndx = 0 - baserel->min_attr;
! }
! else
elog(ERROR, "unexpected node type in rel targetlist: %d",
(int) nodeTag(var));
if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
{
+ Index sortgroupref = 0;
+
/* Yup, add it to the output */
! if (input_target->sortgrouprefs)
! sortgroupref = input_target->sortgrouprefs[i];
!
! /*
! * Even if not used for grouping in the input path (the input path
! * is not necessarily grouped), it might be useful for grouping
! * higher in the join tree.
! */
! if (sortgroupref == 0)
! sortgroupref = get_expr_sortgroupref(root, (Expr *) var);
!
! add_column_to_pathtarget(result, (Expr *) var, sortgroupref);
!
! /*
! * Vars have cost zero, so no need to adjust reltarget->cost. Even
! * if, it's a ConvertRowtypeExpr, it will be computed only for the
! * base relation, costing nothing for a join.
! */
! result->width += baserel->attr_widths[ndx];
}
}
}
*************** subbuild_joinrel_joinlist(RelOptInfo *jo
*** 843,848 ****
--- 1139,1147 ----
{
ListCell *l;
+ /* Expected to be called only for join between parent relations. */
+ Assert(joinrel->reloptkind == RELOPT_JOINREL);
+
foreach(l, joininfo_list)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
*************** get_baserel_parampathinfo(PlannerInfo *r
*** 1048,1059 ****
Assert(!bms_overlap(baserel->relids, required_outer));
/* If we already have a PPI for this parameterization, just return it */
! foreach(lc, baserel->ppilist)
! {
! ppi = (ParamPathInfo *) lfirst(lc);
! if (bms_equal(ppi->ppi_req_outer, required_outer))
! return ppi;
! }
/*
* Identify all joinclauses that are movable to this base rel given this
--- 1347,1354 ----
Assert(!bms_overlap(baserel->relids, required_outer));
/* If we already have a PPI for this parameterization, just return it */
! if ((ppi = find_param_path_info(baserel, required_outer)))
! return ppi;
/*
* Identify all joinclauses that are movable to this base rel given this
*************** get_baserel_parampathinfo(PlannerInfo *r
*** 1095,1100 ****
--- 1390,1545 ----
}
/*
+ * If the relation can produce grouped paths, create GroupedPathInfo for it
+ * and create target for the grouped paths.
+ */
+ void
+ prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel)
+ {
+ List *rel_aggregates;
+ Relids rel_agg_attrs = NULL;
+ List *rel_agg_vars = NIL;
+ bool found_higher;
+ ListCell *lc;
+ PathTarget *target_grouped;
+
+ if (rel->relid > 0)
+ {
+ RangeTblEntry *rte = root->simple_rte_array[rel->relid];;
+
+ /*
+ * rtekind != RTE_RELATION case is not supported yet.
+ */
+ if (rte->rtekind != RTE_RELATION)
+ return;
+ }
+
+ /* Caller should only pass base relations or joins. */
+ Assert(rel->reloptkind == RELOPT_BASEREL ||
+ rel->reloptkind == RELOPT_JOINREL ||
+ rel->reloptkind == RELOPT_OTHER_JOINREL);
+
+ /*
+ * If any outer join can set the attribute value to NULL, the aggregate
+ * would receive different input at the base rel level.
+ *
+ * TODO For RELOPT_JOINREL, do not return if all the joins that can set
+ * any entry of the grouped target (do we need to postpone this check
+ * until the grouped target is available, and should create_grouped_target
+ * take care?) of this rel to NULL are provably below rel. (It's ok if rel
+ * is one of these joins.)
+ */
+ if (bms_overlap(rel->relids, root->nullable_baserels))
+ return;
+
+ /*
+ * Check if some aggregates can be evaluated in this relation's target,
+ * and collect all vars referenced by these aggregates.
+ */
+ rel_aggregates = NIL;
+ found_higher = false;
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = castNode(GroupedVarInfo, lfirst(lc));
+
+ /*
+ * The subset includes gv_eval_at uninitialized, which typically means
+ * Aggref.aggstar.
+ */
+ if (bms_is_subset(gvi->gv_eval_at, rel->relids))
+ {
+ Aggref *aggref = castNode(Aggref, gvi->gvexpr);
+
+ /*
+ * Accept the aggregate.
+ *
+ * GroupedVarInfo is more convenient for the next processing than
+ * Aggref, see add_aggregates_to_grouped_target.
+ */
+ rel_aggregates = lappend(rel_aggregates, gvi);
+
+ if (rel->relid > 0)
+ {
+ /*
+ * Simple relation. Collect attributes referenced by the
+ * aggregate arguments.
+ */
+ pull_varattnos((Node *) aggref, rel->relid, &rel_agg_attrs);
+ }
+ else
+ {
+ List *agg_vars;
+
+ /*
+ * Join. Collect vars referenced by the aggregate
+ * arguments.
+ */
+ /*
+ * TODO Can any argument contain PHVs? And if so, does it matter?
+ * Consider PVC_INCLUDE_PLACEHOLDERS | PVC_RECURSE_PLACEHOLDERS.
+ */
+ agg_vars = pull_var_clause((Node *) aggref,
+ PVC_RECURSE_AGGREGATES);
+ rel_agg_vars = list_concat(rel_agg_vars, agg_vars);
+ }
+ }
+ else if (bms_overlap(gvi->gv_eval_at, rel->relids))
+ {
+ /*
+ * Remember that there is at least one aggregate that needs more
+ * than this rel.
+ */
+ found_higher = true;
+ }
+ }
+
+ /*
+ * Grouping makes little sense w/o aggregate function.
+ */
+ if (rel_aggregates == NIL)
+ {
+ bms_free(rel_agg_attrs);
+ return;
+ }
+
+ if (found_higher)
+ {
+ /*
+ * If some aggregate(s) need only this rel but some other need
+ * multiple relations including the the current one, grouping of the
+ * current rel could steal some input variables from the "higher
+ * aggregate" (besides decreasing the number of input rows).
+ */
+ list_free(rel_aggregates);
+ bms_free(rel_agg_attrs);
+ return;
+ }
+
+ /*
+ * If rel->reltarget can be used for aggregation, mark the relation as
+ * capable of grouping.
+ */
+ Assert(rel->gpi == NULL);
+ target_grouped = create_grouped_target(root, rel, rel_agg_attrs,
+ rel_agg_vars);
+ if (target_grouped != NULL)
+ {
+ GroupedPathInfo *gpi;
+
+ gpi = makeNode(GroupedPathInfo);
+ gpi->target = copy_pathtarget(target_grouped);
+ gpi->pathlist = NIL;
+ gpi->partial_pathlist = NIL;
+ rel->gpi = gpi;
+
+ /*
+ * Add aggregates (in the form of GroupedVar) to the target.
+ */
+ add_aggregates_to_target(root, gpi->target, rel_aggregates, rel);
+ }
+ }
+
+ /*
* get_joinrel_parampathinfo
* Get the ParamPathInfo for a parameterized path for a join relation,
* constructing one if we don't have one already.
*************** get_joinrel_parampathinfo(PlannerInfo *r
*** 1290,1301 ****
*restrict_clauses = list_concat(pclauses, *restrict_clauses);
/* If we already have a PPI for this parameterization, just return it */
! foreach(lc, joinrel->ppilist)
! {
! ppi = (ParamPathInfo *) lfirst(lc);
! if (bms_equal(ppi->ppi_req_outer, required_outer))
! return ppi;
! }
/* Estimate the number of rows returned by the parameterized join */
rows = get_parameterized_joinrel_size(root, joinrel,
--- 1735,1742 ----
*restrict_clauses = list_concat(pclauses, *restrict_clauses);
/* If we already have a PPI for this parameterization, just return it */
! if ((ppi = find_param_path_info(joinrel, required_outer)))
! return ppi;
/* Estimate the number of rows returned by the parameterized join */
rows = get_parameterized_joinrel_size(root, joinrel,
*************** ParamPathInfo *
*** 1334,1340 ****
get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer)
{
ParamPathInfo *ppi;
- ListCell *lc;
/* Unparameterized paths have no ParamPathInfo */
if (bms_is_empty(required_outer))
--- 1775,1780 ----
*************** get_appendrel_parampathinfo(RelOptInfo *
*** 1343,1354 ****
Assert(!bms_overlap(appendrel->relids, required_outer));
/* If we already have a PPI for this parameterization, just return it */
! foreach(lc, appendrel->ppilist)
! {
! ppi = (ParamPathInfo *) lfirst(lc);
! if (bms_equal(ppi->ppi_req_outer, required_outer))
! return ppi;
! }
/* Else build the ParamPathInfo */
ppi = makeNode(ParamPathInfo);
--- 1783,1790 ----
Assert(!bms_overlap(appendrel->relids, required_outer));
/* If we already have a PPI for this parameterization, just return it */
! if ((ppi = find_param_path_info(appendrel, required_outer)))
! return ppi;
/* Else build the ParamPathInfo */
ppi = makeNode(ParamPathInfo);
*************** get_appendrel_parampathinfo(RelOptInfo *
*** 1359,1361 ****
--- 1795,1917 ----
return ppi;
}
+
+ /*
+ * Returns a ParamPathInfo for outer relations specified by required_outer, if
+ * already available in the given rel. Returns NULL otherwise.
+ */
+ ParamPathInfo *
+ find_param_path_info(RelOptInfo *rel, Relids required_outer)
+ {
+ ListCell *lc;
+
+ foreach(lc, rel->ppilist)
+ {
+ ParamPathInfo *ppi = (ParamPathInfo *) lfirst(lc);
+ if (bms_equal(ppi->ppi_req_outer, required_outer))
+ return ppi;
+ }
+
+ return NULL;
+ }
+
+ /*
+ * build_joinrel_partition_info
+ * If the join between given partitioned relations is possibly partitioned
+ * set the partitioning scheme and partition keys expressions for the
+ * join.
+ *
+ * If the two relations have same partitioning scheme, their join may be
+ * partitioned and will follow the same partitioning scheme as the joining
+ * relations.
+ */
+ static void
+ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel, List *restrictlist,
+ JoinType jointype)
+ {
+ int num_pks;
+ int cnt;
+ bool is_strict;
+
+ /* Nothing to do if partition-wise join technique is disabled. */
+ if (!enable_partition_wise_join)
+ {
+ joinrel->part_scheme = NULL;
+ return;
+ }
+
+ /*
+ * The join is not partitioned, if any of the relations being joined are
+ * not partitioned or they do not have same partitioning scheme or if there
+ * is no equi-join between partition keys.
+ *
+ * For an N-way inner join, where every syntactic inner join has equi-join
+ * between partition keys and a matching partitioning scheme, partition
+ * keys of N relations form an equivalence class, thus inducing an
+ * equi-join between any pair of joining relations.
+ *
+ * For an N-way join with outer joins, where every syntactic join has an
+ * equi-join between partition keys and a matching partitioning scheme,
+ * outer join reordering identities in optimizer/README imply that only
+ * those pairs of join are legal which have an equi-join between partition
+ * keys. Thus every pair of joining relations we see here should have an
+ * equi-join if this join has been deemed as a partitioned join.
+ */
+ if (!outer_rel->part_scheme || !inner_rel->part_scheme ||
+ outer_rel->part_scheme != inner_rel->part_scheme ||
+ !have_partkey_equi_join(outer_rel, inner_rel, jointype, restrictlist,
+ &is_strict))
+ {
+ joinrel->part_scheme = NULL;
+ return;
+ }
+
+ /*
+ * This function will be called only once for each joinrel, hence it should
+ * not have partition scheme, partition key expressions and array for
+ * storing child relations set.
+ */
+ Assert(!joinrel->part_scheme && !joinrel->partexprs &&
+ !joinrel->part_rels);
+
+ /*
+ * Join relation is partitioned using same partitioning scheme as the
+ * joining relations.
+ */
+ joinrel->part_scheme = outer_rel->part_scheme;
+ num_pks = joinrel->part_scheme->partnatts;
+
+ /*
+ * Construct partition keys for the join.
+ *
+ * An INNER join between two partitioned relations is partition by key
+ * expressions from both the relations. For tables A and B partitioned by a
+ * and b respectively, (A INNER JOIN B ON A.a = B.b) is partitioned by both
+ * A.a and B.b.
+ *
+ * An OUTER join like (A LEFT JOIN B ON A.a = B.b) may produce rows with
+ * B.b NULL. These rows may not fit the partitioning conditions imposed on
+ * B.b. Hence, strictly speaking, the join is not partitioned by B.b.
+ * Strictly speaking, partition keys of an OUTER join should include
+ * partition key expressions from the OUTER side only. Consider a join like
+ * (A LEFT JOIN B on (A.a = B.b) LEFT JOIN C ON B.b = C.c. If we do not
+ * include B.b as partition key expression for (AB), it prohibits us from
+ * using partition-wise join when joining (AB) with C as there is no
+ * equi-join between partition keys of joining relations. If the equality
+ * operator is strict, two NULL values are never equal and no two rows from
+ * mis-matching partitions can join. Hence if the equality operator is
+ * strict it's safe to include B.b as partition key expression for (AB),
+ * even though rows in (AB) are not strictly partitioned by B.b.
+ */
+ joinrel->partexprs = (List **) palloc0(sizeof(List *) * num_pks);
+ for (cnt = 0; cnt < num_pks; cnt++)
+ {
+ List *pkexpr = list_copy(outer_rel->partexprs[cnt]);
+
+ if (jointype == JOIN_INNER || is_strict)
+ pkexpr = list_concat(pkexpr,
+ list_copy(inner_rel->partexprs[cnt]));
+ joinrel->partexprs[cnt] = pkexpr;
+ }
+ }
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index 0952385..dd962b7
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
*************** get_sortgrouplist_exprs(List *sgClauses,
*** 408,413 ****
--- 408,487 ----
return result;
}
+ /*
+ * get_sortgrouplist_clauses
+ *
+ * Given a "grouped target" (i.e. target where each non-GroupedVar
+ * element must have sortgroupref set), build a list of the referencing
+ * SortGroupClauses, a list of the corresponding grouping expressions and
+ * a list of aggregate expressions.
+ */
+ /* Refine the function name. */
+ void
+ get_grouping_expressions(PlannerInfo *root, PathTarget *target,
+ List **grouping_clauses, List **grouping_exprs,
+ List **agg_exprs)
+ {
+ ListCell *l;
+ int i = 0;
+
+ foreach(l, target->exprs)
+ {
+ Index sortgroupref = 0;
+ SortGroupClause *cl;
+ Expr *texpr;
+
+ texpr = (Expr *) lfirst(l);
+
+ /* The target should contain at least one grouping column. */
+ Assert(target->sortgrouprefs != NULL);
+
+ if (IsA(texpr, GroupedVar))
+ {
+ /*
+ * texpr should represent the first aggregate in the targetlist.
+ */
+ break;
+ }
+
+ /*
+ * Find the clause by sortgroupref.
+ */
+ sortgroupref = target->sortgrouprefs[i++];
+
+ /*
+ * Besides aggregates, the target should contain no expressions w/o
+ * sortgroupref. Plain relation being joined to grouped can have
+ * sortgroupref equal to zero for expressions contained neither in
+ * grouping expression nor in aggregate arguments, but if the target
+ * contains such an expression, it shouldn't be used for aggregation
+ * --- see can_aggregate field of GroupedPathInfo.
+ */
+ Assert(sortgroupref > 0);
+
+ cl = get_sortgroupref_clause(sortgroupref, root->parse->groupClause);
+ *grouping_clauses = list_append_unique(*grouping_clauses, cl);
+
+ /*
+ * Add only unique clauses because of joins (both sides of a join can
+ * point at the same grouping clause). XXX Is it worth adding a bool
+ * argument indicating that we're dealing with join right now?
+ */
+ *grouping_exprs = list_append_unique(*grouping_exprs, texpr);
+ }
+
+ /* Now collect the aggregates. */
+ while (l != NULL)
+ {
+ GroupedVar *gvar = castNode(GroupedVar, lfirst(l));
+
+ /* Currently, GroupedVarInfo can only represent aggregate. */
+ Assert(gvar->agg_partial != NULL);
+ *agg_exprs = lappend(*agg_exprs, gvar->agg_partial);
+ l = lnext(l);
+ }
+ }
+
/*****************************************************************************
* Functions to extract data from a list of SortGroupClauses
*************** apply_pathtarget_labeling_to_tlist(List
*** 783,788 ****
--- 857,1081 ----
}
/*
+ * Replace each "grouped var" in the source targetlist with the original
+ * expression.
+ *
+ * TODO Think of more suitable name. Although "grouped var" may substitute for
+ * grouping expressions in the future, currently Aggref is the only outcome of
+ * the replacement. undo_grouped_var_substitutions?
+ */
+ List *
+ restore_grouping_expressions(PlannerInfo *root, List *src)
+ {
+ List *result = NIL;
+ ListCell *l;
+
+ foreach(l, src)
+ {
+ TargetEntry *te, *te_new;
+ Aggref *expr_new = NULL;
+
+ te = castNode(TargetEntry, lfirst(l));
+
+ if (IsA(te->expr, GroupedVar))
+ {
+ GroupedVar *gvar;
+
+ gvar = castNode(GroupedVar, te->expr);
+ expr_new = gvar->agg_partial;
+ }
+
+ if (expr_new != NULL)
+ {
+ te_new = flatCopyTargetEntry(te);
+ te_new->expr = (Expr *) expr_new;
+ }
+ else
+ te_new = te;
+ result = lappend(result, te_new);
+ }
+
+ return result;
+ }
+
+ /*
+ * For each aggregate add GroupedVar to target if "vars" is true, or the
+ * Aggref (marked as partial) if "vars" is false.
+ *
+ * If caller passes the aggregates, he must do so in the form of
+ * GroupedVarInfos so that we don't have to look for gvid. If NULL is passed,
+ * the function retrieves the suitable aggregates itself.
+ *
+ * List of the aggregates added is returned. This is only useful if the
+ * function had to retrieve the aggregates itself (i.e. NIL was passed for
+ * aggregates) -- caller is expected to do extra checks in that case (and to
+ * also free the list).
+ */
+ List *
+ add_aggregates_to_target(PlannerInfo *root, PathTarget *target,
+ List *aggregates, RelOptInfo *rel)
+ {
+ ListCell *lc;
+ GroupedVarInfo *gvi;
+
+ if (aggregates == NIL)
+ {
+ /* Caller should pass the aggregates for base relation. */
+ Assert(rel->reloptkind != RELOPT_BASEREL);
+
+ /* Collect all aggregates that this rel can evaluate. */
+ foreach(lc, root->grouped_var_list)
+ {
+ gvi = castNode(GroupedVarInfo, lfirst(lc));
+
+ /*
+ * Overlap is not guarantee of correctness alone, but caller needs
+ * to do additional checks, so we're optimistic here.
+ *
+ * If gv_eval_at is NULL, the underlying Aggref should have
+ * aggstar set.
+ */
+ if (bms_overlap(gvi->gv_eval_at, rel->relids) ||
+ gvi->gv_eval_at == NULL)
+ aggregates = lappend(aggregates, gvi);
+ }
+
+ if (aggregates == NIL)
+ return NIL;
+ }
+
+ /* Create the vars and add them to the target. */
+ foreach(lc, aggregates)
+ {
+ GroupedVar *gvar;
+
+ gvi = castNode(GroupedVarInfo, lfirst(lc));
+ gvar = makeNode(GroupedVar);
+ gvar->gvid = gvi->gvid;
+ gvar->gvexpr = gvi->gvexpr;
+ gvar->agg_partial = gvi->agg_partial;
+ add_new_column_to_pathtarget(target, (Expr *) gvar);
+ }
+
+ return aggregates;
+ }
+
+ /*
+ * Return ressortgroupref of the target entry that is either equal to the
+ * expression or exists in the same equivalence class.
+ */
+ Index
+ get_expr_sortgroupref(PlannerInfo *root, Expr *expr)
+ {
+ ListCell *lc;
+ Index sortgroupref;
+
+ /*
+ * First, check if the query group clause contains exactly this
+ * expression.
+ */
+ foreach(lc, root->processed_tlist)
+ {
+ TargetEntry *te = castNode(TargetEntry, lfirst(lc));
+
+ if (equal(expr, te->expr) && te->ressortgroupref > 0)
+ return te->ressortgroupref;
+ }
+
+ /*
+ * If exactly this expression is not there, check if a grouping clause
+ * exists that belongs to the same equivalence class as the expression.
+ */
+ foreach(lc, root->group_pathkeys)
+ {
+ PathKey *pk = castNode(PathKey, lfirst(lc));
+ EquivalenceClass *ec = pk->pk_eclass;
+ ListCell *lm;
+ EquivalenceMember *em;
+ Expr *em_expr = NULL;
+ Query *query = root->parse;
+
+ /*
+ * Single-member EC cannot provide us with additional expression.
+ */
+ if (list_length(ec->ec_members) < 2)
+ continue;
+
+ /* We need equality anywhere in the join tree. */
+ if (ec->ec_below_outer_join)
+ continue;
+
+ /*
+ * TODO Reconsider this restriction. As the grouping expression is
+ * only evaluated at the relation level (and only the result will be
+ * propagated to the final targetlist), volatile function might be
+ * o.k. Need to think what volatile EC exactly means.
+ */
+ if (ec->ec_has_volatile)
+ continue;
+
+ foreach(lm, ec->ec_members)
+ {
+ em = (EquivalenceMember *) lfirst(lm);
+
+ /* The EC has !ec_below_outer_join. */
+ Assert(!em->em_nullable_relids);
+ if (equal(em->em_expr, expr))
+ {
+ em_expr = (Expr *) em->em_expr;
+ break;
+ }
+ }
+
+ if (em_expr == NULL)
+ /* Go for the next EC. */
+ continue;
+
+ /*
+ * Find the corresponding SortGroupClause, which provides us with
+ * sortgroupref. (It can belong to any EC member.)
+ */
+ sortgroupref = 0;
+ foreach(lm, ec->ec_members)
+ {
+ ListCell *lsg;
+
+ em = (EquivalenceMember *) lfirst(lm);
+ foreach(lsg, query->groupClause)
+ {
+ SortGroupClause *sgc;
+ Expr *expr;
+
+ sgc = (SortGroupClause *) lfirst(lsg);
+ expr = (Expr *) get_sortgroupclause_expr(sgc,
+ query->targetList);
+ if (equal(em->em_expr, expr))
+ {
+ Assert(sgc->tleSortGroupRef > 0);
+ sortgroupref = sgc->tleSortGroupRef;
+ break;
+ }
+ }
+
+ if (sortgroupref > 0)
+ break;
+ }
+
+ /*
+ * Since we searched in group_pathkeys, at least one EM of this EC
+ * should correspond to a SortGroupClause, otherwise the EC could
+ * not exist at all.
+ */
+ Assert(sortgroupref > 0);
+
+ return sortgroupref;
+ }
+
+ /* No EC found in group_pathkeys. */
+ return 0;
+ }
+
+ /*
* split_pathtarget_at_srfs
* Split given PathTarget into multiple levels to position SRFs safely
*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
new file mode 100644
index 184e5da..5e3c3b4
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** get_rule_expr(Node *node, deparse_contex
*** 7559,7564 ****
--- 7559,7572 ----
get_agg_expr((Aggref *) node, context, (Aggref *) node);
break;
+ case T_GroupedVar:
+ {
+ GroupedVar *gvar = castNode(GroupedVar, node);
+
+ get_agg_expr(gvar->agg_partial, context, (Aggref *) gvar->gvexpr);
+ break;
+ }
+
case T_GroupingFunc:
{
GroupingFunc *gexpr = (GroupingFunc *) node;
*************** get_agg_combine_expr(Node *node, deparse
*** 8993,9002 ****
Aggref *aggref;
Aggref *original_aggref = private;
! if (!IsA(node, Aggref))
elog(ERROR, "combining Aggref does not point to an Aggref");
- aggref = (Aggref *) node;
get_agg_expr(aggref, context, original_aggref);
}
--- 9001,9018 ----
Aggref *aggref;
Aggref *original_aggref = private;
! if (IsA(node, Aggref))
! aggref = (Aggref *) node;
! else if (IsA(node, GroupedVar))
! {
! GroupedVar *gvar = castNode(GroupedVar, node);
!
! aggref = gvar->agg_partial;
! original_aggref = castNode(Aggref, gvar->gvexpr);
! }
! else
elog(ERROR, "combining Aggref does not point to an Aggref");
get_agg_expr(aggref, context, original_aggref);
}
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
new file mode 100644
index a35b93b..78e24ea
*** a/src/backend/utils/adt/selfuncs.c
--- b/src/backend/utils/adt/selfuncs.c
***************
*** 114,119 ****
--- 114,120 ----
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
+ #include "executor/nodeAgg.h"
#include "mb/pg_wchar.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
*************** estimate_hash_bucketsize(PlannerInfo *ro
*** 3705,3710 ****
--- 3706,3744 ----
return (Selectivity) estfract;
}
+ /*
+ * estimate_hashagg_tablesize
+ * estimate the number of bytes that a hash aggregate hashtable will
+ * require based on the agg_costs, path width and dNumGroups.
+ *
+ * XXX this may be over-estimating the size now that hashagg knows to omit
+ * unneeded columns from the hashtable. Also for mixed-mode grouping sets,
+ * grouping columns not in the hashed set are counted here even though hashagg
+ * won't store them. Is this a problem?
+ */
+ Size
+ estimate_hashagg_tablesize(Path *path, const AggClauseCosts *agg_costs,
+ double dNumGroups)
+ {
+ Size hashentrysize;
+
+ /* Estimate per-hash-entry space at tuple width... */
+ hashentrysize = MAXALIGN(path->pathtarget->width) +
+ MAXALIGN(SizeofMinimalTupleHeader);
+
+ /* plus space for pass-by-ref transition values... */
+ hashentrysize += agg_costs->transitionSpace;
+ /* plus the per-hash-entry overhead */
+ hashentrysize += hash_agg_entry_size(agg_costs->numAggs);
+
+ /*
+ * Note that this disregards the effect of fill-factor and growth policy
+ * of the hash-table. That's probably ok, given default the default
+ * fill-factor is relatively high. It'd be hard to meaningfully factor in
+ * "double-in-size" growth policies here.
+ */
+ return hashentrysize * dNumGroups;
+ }
/*-------------------------------------------------------------------------
*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
new file mode 100644
index 85c6b61..cf94ccc
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
*************** equalPartitionDescs(PartitionKey key, Pa
*** 1204,1210 ****
if (partdesc2->boundinfo == NULL)
return false;
! if (!partition_bounds_equal(key, partdesc1->boundinfo,
partdesc2->boundinfo))
return false;
}
--- 1204,1212 ----
if (partdesc2->boundinfo == NULL)
return false;
! if (!partition_bounds_equal(key->partnatts, key->parttyplen,
! key->parttypbyval,
! partdesc1->boundinfo,
partdesc2->boundinfo))
return false;
}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
new file mode 100644
index a414fb2..343986d
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 914,919 ****
--- 914,928 ----
true,
NULL, NULL, NULL
},
+ {
+ {"enable_partition_wise_join", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enables partition-wise join."),
+ NULL
+ },
+ &enable_partition_wise_join,
+ false,
+ NULL, NULL, NULL
+ },
{
{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 421644c..e51bca1
*** a/src/include/catalog/partition.h
--- b/src/include/catalog/partition.h
*************** typedef struct PartitionDispatchData
*** 71,78 ****
typedef struct PartitionDispatchData *PartitionDispatch;
extern void RelationBuildPartitionDesc(Relation relation);
! extern bool partition_bounds_equal(PartitionKey key,
! PartitionBoundInfo p1, PartitionBoundInfo p2);
extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
extern Oid get_partition_parent(Oid relid);
--- 71,79 ----
typedef struct PartitionDispatchData *PartitionDispatch;
extern void RelationBuildPartitionDesc(Relation relation);
! extern bool partition_bounds_equal(int partnatts, int16 *parttyplen,
! bool *parttypbyval, PartitionBoundInfo b1,
! PartitionBoundInfo b2);
extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
extern Oid get_partition_parent(Oid relid);
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
new file mode 100644
index 6ca44f7..c57ff7b
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
*************** typedef void (*ShutdownForeignScan_funct
*** 155,160 ****
--- 155,163 ----
typedef bool (*IsForeignScanParallelSafe_function) (PlannerInfo *root,
RelOptInfo *rel,
RangeTblEntry *rte);
+ typedef List *(*ReparameterizeForeignPathByChild_function) (PlannerInfo *root,
+ List *fdw_private,
+ RelOptInfo *child_rel);
/*
* FdwRoutine is the struct returned by a foreign-data wrapper's handler
*************** typedef struct FdwRoutine
*** 226,231 ****
--- 229,237 ----
InitializeDSMForeignScan_function InitializeDSMForeignScan;
InitializeWorkerForeignScan_function InitializeWorkerForeignScan;
ShutdownForeignScan_function ShutdownForeignScan;
+
+ /* Support functions for path reparameterization. */
+ ReparameterizeForeignPathByChild_function ReparameterizeForeignPathByChild;
} FdwRoutine;
diff --git a/src/include/nodes/extensible.h b/src/include/nodes/extensible.h
new file mode 100644
index 0b02cc1..1c802ad
*** a/src/include/nodes/extensible.h
--- b/src/include/nodes/extensible.h
*************** typedef struct CustomPathMethods
*** 96,101 ****
--- 96,104 ----
List *tlist,
List *clauses,
List *custom_plans);
+ struct List *(*ReparameterizeCustomPathByChild) (PlannerInfo *root,
+ List *custom_private,
+ RelOptInfo *child_rel);
} CustomPathMethods;
/*
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index f59d719..ba1eac8
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 218,223 ****
--- 218,224 ----
T_IndexOptInfo,
T_ForeignKeyOptInfo,
T_ParamPathInfo,
+ T_GroupedPathInfo,
T_Path,
T_IndexPath,
T_BitmapHeapPath,
*************** typedef enum NodeTag
*** 258,267 ****
--- 259,270 ----
T_PathTarget,
T_RestrictInfo,
T_PlaceHolderVar,
+ T_GroupedVar,
T_SpecialJoinInfo,
T_AppendRelInfo,
T_PartitionedChildRelInfo,
T_PlaceHolderInfo,
+ T_GroupedVarInfo,
T_MinMaxAggInfo,
T_PlannerParamItem,
T_RollupData,
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index 7a8e2fd..b576dd5
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 15,20 ****
--- 15,21 ----
#define RELATION_H
#include "access/sdir.h"
+ #include "catalog/partition.h"
#include "lib/stringinfo.h"
#include "nodes/params.h"
#include "nodes/parsenodes.h"
*************** typedef struct PlannerInfo
*** 256,261 ****
--- 257,264 ----
List *placeholder_list; /* list of PlaceHolderInfos */
+ List *grouped_var_list; /* List of GroupedVarInfos. */
+
List *fkey_list; /* list of ForeignKeyOptInfos */
List *query_pathkeys; /* desired pathkeys for query_planner() */
*************** typedef struct PlannerInfo
*** 265,270 ****
--- 268,276 ----
List *distinct_pathkeys; /* distinctClause pathkeys, if any */
List *sort_pathkeys; /* sortClause pathkeys, if any */
+ List *part_schemes; /* Canonicalised partition schemes
+ * used in the query. */
+
List *initial_rels; /* RelOptInfos we are now trying to join */
/* Use fetch_upper_rel() to get any particular upper rel */
*************** typedef struct PlannerInfo
*** 325,330 ****
--- 331,362 ----
((root)->simple_rte_array ? (root)->simple_rte_array[rti] : \
rt_fetch(rti, (root)->parse->rtable))
+ /*
+ * Partitioning scheme
+ * Structure to hold partitioning scheme for a given relation.
+ *
+ * Multiple relations may be partitioned in the same way. The relations
+ * resulting from joining such relations may be partitioned in the same way as
+ * the joining relations. Similarly, relations derived from such relations by
+ * grouping, sorting may be partitioned in the same way as the underlying
+ * scan relations. All such relations partitioned in the same way share the
+ * partitioning scheme.
+ *
+ * PlannerInfo stores a list of distinct "canonical" partitioning schemes.
+ * RelOptInfo of a partitioned relation holds the pointer to "canonical"
+ * partitioning scheme.
+ */
+ typedef struct PartitionSchemeData
+ {
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ Oid *parttypcoll; /* OIDs of collations of partition keys. */
+ } PartitionSchemeData;
+
+ typedef struct PartitionSchemeData *PartitionScheme;
/*----------
* RelOptInfo
*************** typedef struct PlannerInfo
*** 359,364 ****
--- 391,401 ----
* handling join alias Vars. Currently this is not needed because all join
* alias Vars are expanded to non-aliased form during preprocess_expression.
*
+ * We also have relations representing joins between child relations of
+ * different partitioned tables. These relations are not added to
+ * join_rel_level lists as they are not joined directly by the dynamic
+ * programming algorithm.
+ *
* There is also a RelOptKind for "upper" relations, which are RelOptInfos
* that describe post-scan/join processing steps, such as aggregation.
* Many of the fields in these RelOptInfos are meaningless, but their Path
*************** typedef struct PlannerInfo
*** 401,406 ****
--- 438,445 ----
* direct_lateral_relids - rels this rel has direct LATERAL references to
* lateral_relids - required outer rels for LATERAL, as a Relids set
* (includes both direct and indirect lateral references)
+ * gpi - GroupedPathInfo if the relation can produce grouped paths, NULL
+ * otherwise.
*
* If the relation is a base relation it will have these fields set:
*
*************** typedef struct PlannerInfo
*** 486,491 ****
--- 525,543 ----
* We store baserestrictcost in the RelOptInfo (for base relations) because
* we know we will need it at least once (to price the sequential scan)
* and may need it multiple times to price index scans.
+ *
+ * If the relation is partitioned these fields will be set
+ * part_scheme - Partitioning scheme of the relation
+ * nparts - Number of partitions
+ * boundinfo - Partition bounds/lists
+ * part_rels - RelOptInfos of the partition relations
+ * partexprs - Partition key expressions
+ *
+ * Note: A base relation will always have only one set of partition keys. But a
+ * join relation is partitioned by the partition keys of joining relations.
+ * Partition keys are stored as an array of partition key expressions, with
+ * each array element containing a list of one (for a base relation) or more
+ * (as many as the number of joining relations) expressions.
*----------
*/
typedef enum RelOptKind
*************** typedef enum RelOptKind
*** 493,498 ****
--- 545,551 ----
RELOPT_BASEREL,
RELOPT_JOINREL,
RELOPT_OTHER_MEMBER_REL,
+ RELOPT_OTHER_JOINREL,
RELOPT_UPPER_REL,
RELOPT_DEADREL
} RelOptKind;
*************** typedef enum RelOptKind
*** 506,518 ****
(rel)->reloptkind == RELOPT_OTHER_MEMBER_REL)
/* Is the given relation a join relation? */
! #define IS_JOIN_REL(rel) ((rel)->reloptkind == RELOPT_JOINREL)
/* Is the given relation an upper relation? */
#define IS_UPPER_REL(rel) ((rel)->reloptkind == RELOPT_UPPER_REL)
/* Is the given relation an "other" relation? */
! #define IS_OTHER_REL(rel) ((rel)->reloptkind == RELOPT_OTHER_MEMBER_REL)
typedef struct RelOptInfo
{
--- 559,575 ----
(rel)->reloptkind == RELOPT_OTHER_MEMBER_REL)
/* Is the given relation a join relation? */
! #define IS_JOIN_REL(rel) \
! ((rel)->reloptkind == RELOPT_JOINREL || \
! (rel)->reloptkind == RELOPT_OTHER_JOINREL)
/* Is the given relation an upper relation? */
#define IS_UPPER_REL(rel) ((rel)->reloptkind == RELOPT_UPPER_REL)
/* Is the given relation an "other" relation? */
! #define IS_OTHER_REL(rel) \
! ((rel)->reloptkind == RELOPT_OTHER_MEMBER_REL || \
! (rel)->reloptkind == RELOPT_OTHER_JOINREL)
typedef struct RelOptInfo
{
*************** typedef struct RelOptInfo
*** 548,553 ****
--- 605,613 ----
Relids direct_lateral_relids; /* rels directly laterally referenced */
Relids lateral_relids; /* minimum parameterization of rel */
+ /* Information needed to produce grouped paths. */
+ struct GroupedPathInfo *gpi;
+
/* information about a base rel (not set for join rels!) */
Index relid;
Oid reltablespace; /* containing tablespace */
*************** typedef struct RelOptInfo
*** 566,571 ****
--- 626,632 ----
PlannerInfo *subroot; /* if subquery */
List *subplan_params; /* if subquery */
int rel_parallel_workers; /* wanted number of parallel workers */
+ Oid *part_oids; /* OIDs of partitions */
/* Information about foreign tables and foreign joins */
Oid serverid; /* identifies server for the table or join */
*************** typedef struct RelOptInfo
*** 591,596 ****
--- 652,673 ----
/* used by "other" relations */
Relids top_parent_relids; /* Relids of topmost parents */
+
+ /* For all the partitioned relations. */
+ PartitionScheme part_scheme; /* Partitioning scheme. */
+ int nparts; /* number of partitions */
+ PartitionBoundInfo boundinfo; /* Partition bounds/lists */
+ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions,
+ * stored in the same order as bounds
+ * or lists in PartitionScheme.
+ */
+ List **partexprs; /* Array of list of partition key
+ * expressions. For base relations
+ * these are one element lists. For
+ * join there may be as many elements
+ * as the number of joining
+ * relations.
+ */
} RelOptInfo;
/*
*************** typedef struct ParamPathInfo
*** 913,918 ****
--- 990,1017 ----
List *ppi_clauses; /* join clauses available from outer rels */
} ParamPathInfo;
+ /*
+ * GroupedPathInfo
+ *
+ * If RelOptInfo points to this structure, grouped paths can be created for
+ * it.
+ *
+ * "target" will be used as pathtarget of grouped paths produced by this
+ * relation. Grouped path is either a result of aggregation of the relation
+ * that owns this structure or, if the owning relation is a join, a join path
+ * whose one side is a grouped path and the other is a plain (i.e. not
+ * grouped) one. (Two grouped paths cannot be joined in general because
+ * grouping of one side of the join essentially reduces occurrence of groups
+ * of the other side in the input of the final aggregation.)
+ */
+ typedef struct GroupedPathInfo
+ {
+ NodeTag type;
+
+ PathTarget *target; /* output of grouped paths. */
+ List *pathlist; /* List of grouped paths. */
+ List *partial_pathlist; /* List of partial grouped paths. */
+ } GroupedPathInfo;
/*
* Type "Path" is used as-is for sequential-scan paths, as well as some other
*************** typedef struct PlaceHolderVar
*** 1852,1857 ****
--- 1951,1989 ----
Index phlevelsup; /* > 0 if PHV belongs to outer query */
} PlaceHolderVar;
+
+ /*
+ * Similar to the concept of PlaceHolderVar, we treat aggregates and grouping
+ * columns as special variables if grouping is possible below the top-level
+ * join. The reason is that aggregates having start as the argument can be
+ * evaluated at various places in the join tree (i.e. cannot be assigned to
+ * target list of exactly one relation). Also this concept seems to be less
+ * invasive than adding the grouped vars to reltarget (in which case
+ * attr_needed and attr_widths arrays of RelOptInfo) would also need
+ * additional changes.
+ *
+ * gvexpr is a pointer to gvexpr field of the corresponding instance
+ * GroupedVarInfo. It's there for the sake of exprType(), exprCollation(),
+ * etc.
+ *
+ * agg_partial also points to the corresponding field of GroupedVarInfo if the
+ * GroupedVar is in the target of a parent relation (RELOPT_BASEREL). However
+ * within a child relation's (RELOPT_OTHER_MEMBER_REL) target it points to a
+ * copy which has argument expressions translated, so they no longer reference
+ * the parent.
+ *
+ * XXX Currently we only create GroupedVar for aggregates, but sometime we can
+ * do it for grouping keys as well. That would allow grouping below the
+ * top-level join by keys other than plain Var.
+ */
+ typedef struct GroupedVar
+ {
+ Expr xpr;
+ Expr *gvexpr; /* the represented expression */
+ Aggref *agg_partial; /* partial aggregate if gvexpr is aggregate */
+ Index gvid; /* GroupedVarInfo */
+ } GroupedVar;
+
/*
* "Special join" info.
*
*************** typedef struct PlaceHolderInfo
*** 2067,2072 ****
--- 2199,2220 ----
} PlaceHolderInfo;
/*
+ * Likewise, GroupedVarInfo exists for each distinct GroupedVar.
+ */
+ typedef struct GroupedVarInfo
+ {
+ NodeTag type;
+
+ Index gvid; /* GroupedVar.gvid */
+ Expr *gvexpr; /* the represented expression. */
+ Aggref *agg_partial; /* if gvexpr is aggregate, agg_partial is
+ * the corresponding partial aggregate */
+ Relids gv_eval_at; /* lowest level we can evaluate the expression
+ * at or NULL if it can happen anywhere. */
+ int32 gv_width; /* estimated width of the expression */
+ } GroupedVarInfo;
+
+ /*
* This struct describes one potentially index-optimizable MIN/MAX aggregate
* function. MinMaxAggPath contains a list of these, and if we accept that
* path, the list is stored into root->minmax_aggs for use during setrefs.c.
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
new file mode 100644
index ed70def..ca06455
*** a/src/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
*************** extern bool enable_material;
*** 67,72 ****
--- 67,73 ----
extern bool enable_mergejoin;
extern bool enable_hashjoin;
extern bool enable_gathermerge;
+ extern bool enable_partition_wise_join;
extern int constraint_exclusion;
extern double clamp_row_est(double nrows);
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index 77bc770..4a0d845
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern int compare_path_costs(Path *path
*** 25,37 ****
extern int compare_fractional_path_costs(Path *path1, Path *path2,
double fraction);
extern void set_cheapest(RelOptInfo *parent_rel);
! extern void add_path(RelOptInfo *parent_rel, Path *new_path);
extern bool add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer);
! extern void add_partial_path(RelOptInfo *parent_rel, Path *new_path);
extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
! Cost total_cost, List *pathkeys);
extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer, int parallel_workers);
--- 25,39 ----
extern int compare_fractional_path_costs(Path *path1, Path *path2,
double fraction);
extern void set_cheapest(RelOptInfo *parent_rel);
! extern void add_path(RelOptInfo *parent_rel, Path *new_path, bool grouped);
extern bool add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer, bool grouped);
! extern void add_partial_path(RelOptInfo *parent_rel, Path *new_path,
! bool grouped);
extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
! Cost total_cost, List *pathkeys,
! bool grouped);
extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer, int parallel_workers);
*************** extern ForeignPath *create_foreignscan_p
*** 112,118 ****
Path *fdw_outerpath,
List *fdw_private);
! extern Relids calc_nestloop_required_outer(Path *outer_path, Path *inner_path);
extern Relids calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path);
extern NestPath *create_nestloop_path(PlannerInfo *root,
--- 114,123 ----
Path *fdw_outerpath,
List *fdw_private);
! extern Relids calc_nestloop_required_outer(Relids outerrelids,
! Relids outer_paramrels,
! Relids innerrelids,
! Relids inner_paramrels);
extern Relids calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path);
extern NestPath *create_nestloop_path(PlannerInfo *root,
*************** extern NestPath *create_nestloop_path(Pl
*** 124,130 ****
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
! Relids required_outer);
extern MergePath *create_mergejoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
--- 129,136 ----
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
! Relids required_outer,
! PathTarget *target);
extern MergePath *create_mergejoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
*************** extern MergePath *create_mergejoin_path(
*** 138,144 ****
Relids required_outer,
List *mergeclauses,
List *outersortkeys,
! List *innersortkeys);
extern HashPath *create_hashjoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
--- 144,151 ----
Relids required_outer,
List *mergeclauses,
List *outersortkeys,
! List *innersortkeys,
! PathTarget *target);
extern HashPath *create_hashjoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
*************** extern HashPath *create_hashjoin_path(Pl
*** 149,155 ****
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses);
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
--- 156,163 ----
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses,
! PathTarget *target);
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
*************** extern AggPath *create_agg_path(PlannerI
*** 190,195 ****
--- 198,217 ----
List *qual,
const AggClauseCosts *aggcosts,
double numGroups);
+ extern AggPath *create_partial_agg_sorted_path(PlannerInfo *root,
+ Path *subpath,
+ bool first_call,
+ List **group_clauses,
+ List **group_exprs,
+ List **agg_exprs,
+ double input_rows);
+ extern AggPath *create_partial_agg_hashed_path(PlannerInfo *root,
+ Path *subpath,
+ bool first_call,
+ List **group_clauses,
+ List **group_exprs,
+ List **agg_exprs,
+ double input_rows);
extern GroupingSetsPath *create_groupingsets_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
*************** extern LimitPath *create_limit_path(Plan
*** 248,253 ****
--- 270,277 ----
extern Path *reparameterize_path(PlannerInfo *root, Path *path,
Relids required_outer,
double loop_count);
+ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path,
+ RelOptInfo *child_rel);
/*
* prototypes for relnode.c
*************** extern ParamPathInfo *get_joinrel_paramp
*** 285,289 ****
--- 309,320 ----
List **restrict_clauses);
extern ParamPathInfo *get_appendrel_parampathinfo(RelOptInfo *appendrel,
Relids required_outer);
+ extern ParamPathInfo *find_param_path_info(RelOptInfo *rel,
+ Relids required_outer);
+ extern void prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel);
+ extern RelOptInfo *build_child_join_rel(PlannerInfo *root,
+ RelOptInfo *outer_rel, RelOptInfo *inner_rel,
+ RelOptInfo *parent_joinrel, List *restrictlist,
+ SpecialJoinInfo *sjinfo, JoinType jointype);
#endif /* PATHNODE_H */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index 25fe78c..8dd4efd
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern void set_dummy_rel_pathlist(RelOp
*** 53,63 ****
extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
List *initial_rels);
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel);
extern int compute_parallel_worker(RelOptInfo *rel, double heap_pages,
double index_pages);
extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel,
Path *bitmapqual);
#ifdef OPTIMIZER_DEBUG
extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
--- 53,69 ----
extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
List *initial_rels);
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel,
! bool grouped);
! extern void create_grouped_path(PlannerInfo *root, RelOptInfo *rel,
! Path *subpath, bool precheck, bool partial,
! AggStrategy aggstrategy);
extern int compute_parallel_worker(RelOptInfo *rel, double heap_pages,
double index_pages);
extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel,
Path *bitmapqual);
+ extern void generate_partition_wise_join_paths(PlannerInfo *root,
+ RelOptInfo *rel);
#ifdef OPTIMIZER_DEBUG
extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
*************** extern void debug_print_rel(PlannerInfo
*** 67,73 ****
* indxpath.c
* routines to generate index paths
*/
! extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel);
extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
List *restrictlist,
List *exprlist, List *oprlist);
--- 73,80 ----
* indxpath.c
* routines to generate index paths
*/
! extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel,
! bool grouped);
extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
List *restrictlist,
List *exprlist, List *oprlist);
*************** extern bool have_join_order_restriction(
*** 111,116 ****
--- 118,126 ----
RelOptInfo *rel1, RelOptInfo *rel2);
extern bool have_dangerous_phv(PlannerInfo *root,
Relids outer_relids, Relids inner_params);
+ extern void mark_dummy_rel(RelOptInfo *rel);
+ extern bool have_partkey_equi_join(RelOptInfo *rel1, RelOptInfo *rel2,
+ JoinType jointype, List *restrictlist, bool *is_strict);
/*
* equivclass.c
diff --git a/src/include/optimizer/placeholder.h b/src/include/optimizer/placeholder.h
new file mode 100644
index 11e6403..8598268
*** a/src/include/optimizer/placeholder.h
--- b/src/include/optimizer/placeholder.h
*************** extern void fix_placeholder_input_needed
*** 28,32 ****
--- 28,34 ----
extern void add_placeholders_to_base_rels(PlannerInfo *root);
extern void add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outer_rel, RelOptInfo *inner_rel);
+ extern void add_placeholders_to_child_joinrel(PlannerInfo *root,
+ RelOptInfo *childrel, RelOptInfo *parentrel);
#endif /* PLACEHOLDER_H */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index 5df68a2..07bc4c0
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern int join_collapse_limit;
*** 74,80 ****
extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
! Relids where_needed, bool create_new_ph);
extern void find_lateral_references(PlannerInfo *root);
extern void create_lateral_join_info(PlannerInfo *root);
extern List *deconstruct_jointree(PlannerInfo *root);
--- 74,82 ----
extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
! Relids where_needed, bool create_new_ph);
! extern void add_grouping_info_to_base_rels(PlannerInfo *root);
! extern void add_grouped_vars_to_rels(PlannerInfo *root);
extern void find_lateral_references(PlannerInfo *root);
extern void create_lateral_join_info(PlannerInfo *root);
extern List *deconstruct_jointree(PlannerInfo *root);
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
new file mode 100644
index f3aaa23..4a550bb
*** a/src/include/optimizer/planner.h
--- b/src/include/optimizer/planner.h
*************** extern Expr *preprocess_phv_expression(P
*** 58,62 ****
--- 58,64 ----
extern bool plan_cluster_use_sort(Oid tableOid, Oid indexOid);
extern List *get_partitioned_child_rels(PlannerInfo *root, Index rti);
+ extern List *get_partitioned_child_rels_for_join(PlannerInfo *root,
+ RelOptInfo *joinrel);
#endif /* PLANNER_H */
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
new file mode 100644
index 2b20b36..95802c9
*** a/src/include/optimizer/prep.h
--- b/src/include/optimizer/prep.h
*************** extern RelOptInfo *plan_set_operations(P
*** 53,61 ****
extern void expand_inherited_tables(PlannerInfo *root);
extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
! AppendRelInfo *appinfo);
extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
! RelOptInfo *child_rel);
#endif /* PREP_H */
--- 53,74 ----
extern void expand_inherited_tables(PlannerInfo *root);
extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
! int nappinfos, AppendRelInfo **appinfos);
extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
! Relids child_relids,
! Relids top_parent_relids);
!
! extern Relids adjust_child_relids(Relids relids, int nappinfos,
! AppendRelInfo **appinfos);
!
! extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
! Relids relids, int *nappinfos);
!
! extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root,
! SpecialJoinInfo *parent_sjinfo,
! Relids left_relids, Relids right_relids);
! extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
! Relids child_relids, Relids top_parent_relids);
#endif /* PREP_H */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
new file mode 100644
index ccb93d8..ddea03c
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern Node *get_sortgroupclause_expr(So
*** 41,46 ****
--- 41,49 ----
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
List *targetList);
+ extern void get_grouping_expressions(PlannerInfo *root, PathTarget *target,
+ List **grouping_clauses,
+ List **grouping_exprs, List **agg_exprs);
extern SortGroupClause *get_sortgroupref_clause(Index sortref,
List *clauses);
*************** extern void split_pathtarget_at_srfs(Pla
*** 65,70 ****
--- 68,84 ----
PathTarget *target, PathTarget *input_target,
List **targets, List **targets_contain_srfs);
+ /* TODO Find the best location (position and in some cases even file) for the
+ * following ones. */
+ extern List *restore_grouping_expressions(PlannerInfo *root, List *src);
+ extern List *add_aggregates_to_target(PlannerInfo *root, PathTarget *target,
+ List *aggregates, RelOptInfo *rel);
+ extern Index get_expr_sortgroupref(PlannerInfo *root, Expr *expr);
+ /* TODO Move definition from initsplan.c to tlist.c. */
+ extern PathTarget *create_grouped_target(PlannerInfo *root, RelOptInfo *rel,
+ Relids rel_agg_attrs,
+ List *rel_agg_vars);
+
/* Convenience macro to get a PathTarget with valid cost/width fields */
#define create_pathtarget(root, tlist) \
set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
new file mode 100644
index 9f9d2dc..e05e6f6
*** a/src/include/utils/selfuncs.h
--- b/src/include/utils/selfuncs.h
*************** extern double estimate_num_groups(Planne
*** 206,211 ****
--- 206,214 ----
extern Selectivity estimate_hash_bucketsize(PlannerInfo *root, Node *hashkey,
double nbuckets);
+ extern Size estimate_hashagg_tablesize(Path *path,
+ const AggClauseCosts *agg_costs,
+ double dNumGroups);
extern List *deconstruct_indexquals(IndexPath *path);
extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
new file mode 100644
index 6163ed8..7a969f2
*** a/src/test/regress/expected/inherit.out
--- b/src/test/regress/expected/inherit.out
*************** select tableoid::regclass::text as relna
*** 625,630 ****
--- 625,652 ----
(3 rows)
drop table parted_tab;
+ -- Check UPDATE with *multi-level partitioned* inherited target
+ create table mlparted_tab (a int, b char, c text) partition by list (a);
+ create table mlparted_tab_part1 partition of mlparted_tab for values in (1);
+ create table mlparted_tab_part2 partition of mlparted_tab for values in (2) partition by list (b);
+ create table mlparted_tab_part3 partition of mlparted_tab for values in (3);
+ create table mlparted_tab_part2a partition of mlparted_tab_part2 for values in ('a');
+ create table mlparted_tab_part2b partition of mlparted_tab_part2 for values in ('b');
+ insert into mlparted_tab values (1, 'a'), (2, 'a'), (2, 'b'), (3, 'a');
+ update mlparted_tab mlp set c = 'xxx'
+ from
+ (select a from some_tab union all select a+1 from some_tab) ss (a)
+ where (mlp.a = ss.a and mlp.b = 'b') or mlp.a = 3;
+ select tableoid::regclass::text as relname, mlparted_tab.* from mlparted_tab order by 1,2;
+ relname | a | b | c
+ ---------------------+---+---+-----
+ mlparted_tab_part1 | 1 | a |
+ mlparted_tab_part2a | 2 | a |
+ mlparted_tab_part2b | 2 | b | xxx
+ mlparted_tab_part3 | 3 | a | xxx
+ (4 rows)
+
+ drop table mlparted_tab;
drop table some_tab cascade;
NOTICE: drop cascades to table some_tab_child
/* Test multiple inheritance of column defaults */
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
new file mode 100644
index 568b783..cd1f7f3
*** a/src/test/regress/expected/sysviews.out
--- b/src/test/regress/expected/sysviews.out
*************** select count(*) >= 0 as ok from pg_prepa
*** 70,90 ****
-- This is to record the prevailing planner enable_foo settings during
-- a regression test run.
select name, setting from pg_settings where name like 'enable%';
! name | setting
! ----------------------+---------
! enable_bitmapscan | on
! enable_gathermerge | on
! enable_hashagg | on
! enable_hashjoin | on
! enable_indexonlyscan | on
! enable_indexscan | on
! enable_material | on
! enable_mergejoin | on
! enable_nestloop | on
! enable_seqscan | on
! enable_sort | on
! enable_tidscan | on
! (12 rows)
-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
-- more-or-less working. We can't test their contents in any great detail
--- 70,91 ----
-- This is to record the prevailing planner enable_foo settings during
-- a regression test run.
select name, setting from pg_settings where name like 'enable%';
! name | setting
! ----------------------------+---------
! enable_bitmapscan | on
! enable_gathermerge | on
! enable_hashagg | on
! enable_hashjoin | on
! enable_indexonlyscan | on
! enable_indexscan | on
! enable_material | on
! enable_mergejoin | on
! enable_nestloop | on
! enable_partition_wise_join | off
! enable_seqscan | on
! enable_sort | on
! enable_tidscan | on
! (13 rows)
-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
-- more-or-less working. We can't test their contents in any great detail
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
new file mode 100644
index 1f8f098..2d14885
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
*************** test: publication subscription
*** 103,109 ****
# ----------
# Another group of parallel tests
# ----------
! test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
--- 103,109 ----
# ----------
# Another group of parallel tests
# ----------
! test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass partition_join multi_level_partition_join
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
new file mode 100644
index 04206c3..9ac24dd
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
*************** test: with
*** 179,181 ****
--- 179,183 ----
test: xml
test: event_trigger
test: stats
+ test: partition_join
+ test: multi_level_partition_join
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
new file mode 100644
index d43b75c..b814a4c
*** a/src/test/regress/sql/inherit.sql
--- b/src/test/regress/sql/inherit.sql
*************** where parted_tab.a = ss.a;
*** 154,159 ****
--- 154,176 ----
select tableoid::regclass::text as relname, parted_tab.* from parted_tab order by 1,2;
drop table parted_tab;
+
+ -- Check UPDATE with *multi-level partitioned* inherited target
+ create table mlparted_tab (a int, b char, c text) partition by list (a);
+ create table mlparted_tab_part1 partition of mlparted_tab for values in (1);
+ create table mlparted_tab_part2 partition of mlparted_tab for values in (2) partition by list (b);
+ create table mlparted_tab_part3 partition of mlparted_tab for values in (3);
+ create table mlparted_tab_part2a partition of mlparted_tab_part2 for values in ('a');
+ create table mlparted_tab_part2b partition of mlparted_tab_part2 for values in ('b');
+ insert into mlparted_tab values (1, 'a'), (2, 'a'), (2, 'b'), (3, 'a');
+
+ update mlparted_tab mlp set c = 'xxx'
+ from
+ (select a from some_tab union all select a+1 from some_tab) ss (a)
+ where (mlp.a = ss.a and mlp.b = 'b') or mlp.a = 3;
+ select tableoid::regclass::text as relname, mlparted_tab.* from mlparted_tab order by 1,2;
+
+ drop table mlparted_tab;
drop table some_tab cascade;
/* Test multiple inheritance of column defaults */