Thank you!
Sadly I didn't manage how to reproduce that locally. check-world doesn't seem to fail at my end. That being said, attached patch should fix the issue reported below. I'll have another look at the log later this week. Regards Arne ________________________________ From: Andres Freund <and...@anarazel.de> Sent: Tuesday, November 22, 2022 2:36:59 AM To: Arne Roland Cc: David Rowley; Amit Langote; pgsql-hackers; Zhihong Yu; Alvaro Herrera; Julien Rouhaud Subject: Re: missing indexes in indexlist with partitioned tables Hi, On 2022-11-02 01:50:38 +0000, Arne Roland wrote: > I mainly changed the comments, the Assert and some casing. The tests have been failing for a while https://cirrus-ci.com/github/postgresql-cfbot/postgresql/commitfest/40/3452 Cirrus CI<https://cirrus-ci.com/github/postgresql-cfbot/postgresql/commitfest/40/3452> cirrus-ci.com Cirrus CI makes your development cycle fast, efficient, and secure by leveraging modern cloud technologies. https://api.cirrus-ci.com/v1/task/6190372803051520/logs/cores.log #2 0x00005645dff192f6 in ExceptionalCondition (conditionName=conditionName@entry=0x5645e014b167 "false", fileName=fileName@entry=0x5645e0196b08 "../src/backend/storage/buffer/bufmgr.c", lineNumber=lineNumber@entry=2971) at ../src/backend/utils/error/assert.c:66 No locals. #3 0x00005645dfc13823 in RelationGetNumberOfBlocksInFork (relation=relation@entry=0x7fb54d54e470, forkNum=forkNum@entry=MAIN_FORKNUM) at ../src/backend/storage/buffer/bufmgr.c:2971 No locals. #4 0x00005645dfa9ac5e in get_relation_info (root=root@entry=0x5645e1ed9840, relationObjectId=16660, inhparent=<optimized out>, rel=rel@entry=0x5645e2086b38) at ../src/backend/optimizer/util/plancat.c:442 indexoid = <optimized out> info = 0x5645e2083b28 i = <optimized out> indexRelation = 0x7fb54d54e470 index = 0x7fb54d548c48 amroutine = <optimized out> ncolumns = 1 nkeycolumns = 1 l__state = {l = <optimized out>, i = <optimized out>} indexoidlist = 0x5645e2088a98 lmode = 1 l = <optimized out> varno = 1 relation = 0x7fb54d54e680 hasindex = <optimized out> indexinfos = 0x0 __func__ = "get_relation_info" #5 0x00005645dfaa5e25 in build_simple_rel (root=0x5645e1ed9840, relid=1, parent=parent@entry=0x0) at ../src/backend/optimizer/util/relnode.c:293 rel = 0x5645e2086b38 rte = 0x5645e1ed8fc8 __func__ = "build_simple_rel" ... Greetings, Andres Freund
From e2c0667eb3ad86db2f13369e9692065d57aa7b1c Mon Sep 17 00:00:00 2001 From: Arne Roland <arne.rol...@index.de> Date: Tue, 6 Dec 2022 01:40:21 +0100 Subject: [PATCH v10] v10 --- src/backend/optimizer/util/plancat.c | 289 ++++++++++--------- src/backend/utils/adt/selfuncs.c | 4 + src/include/nodes/pathnodes.h | 11 +- src/test/regress/expected/inherit.out | 2 + src/test/regress/expected/join.out | 52 +++- src/test/regress/expected/partition_join.out | 6 +- src/test/regress/sql/inherit.sql | 4 + src/test/regress/sql/join.sql | 34 ++- src/test/regress/sql/partition_join.sql | 6 +- 9 files changed, 264 insertions(+), 144 deletions(-) diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 9defe37836..008bd38785 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,10 +106,12 @@ static void set_baserel_partition_constraint(Relation relation, * cases these are left as zeroes, but sometimes we need to compute attr * widths here, and we may as well cache the results for costsize.c. * - * If inhparent is true, all we need to do is set up the attr arrays: - * the RelOptInfo actually represents the appendrel formed by an inheritance - * tree, and so the parent rel's physical size and index information isn't - * important for it. + * If inhparent is true, we don't care about physical size, index am + * and estimates. + * We still need index uniqueness for join removal. + * When it comes to the path, the RelOptInfo actually represents + * the appendrel formed by an inheritance tree, and so the parent + * rel's physical size isn't important for it. */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, @@ -175,10 +177,12 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, /* * Make list of indexes. Ignore indexes on system catalogs if told to. - * Don't bother with indexes for an inheritance parent, either. + * Don't bother with indexes from traditional inheritance parents. + * We care about partitioned indexes for further optimisations, like join + * removal, even if we don't have any am for them. */ - if (inhparent || - (IgnoreSystemIndexes && IsSystemRelation(relation))) + if ((inhparent && relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + || (IgnoreSystemIndexes && IsSystemRelation(relation))) hasindex = false; else hasindex = relation->rd_rel->relhasindex; @@ -209,8 +213,8 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, IndexAmRoutine *amroutine; IndexOptInfo *info; int ncolumns, - nkeycolumns; - int i; + nkeycolumns, + i; /* * Extract info from the relation descriptor for the index. @@ -231,16 +235,6 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, continue; } - /* - * Ignore partitioned indexes, since they are not usable for - * queries. - */ - if (indexRelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) - { - index_close(indexRelation, NoLock); - continue; - } - /* * If the index is valid, but cannot yet be used, ignore it; but * mark the plan we are generating as transient. See @@ -285,108 +279,133 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, info->relam = indexRelation->rd_rel->relam; - /* We copy just the fields we need, not all of rd_indam */ - amroutine = indexRelation->rd_indam; - info->amcanorderbyop = amroutine->amcanorderbyop; - info->amoptionalkey = amroutine->amoptionalkey; - info->amsearcharray = amroutine->amsearcharray; - info->amsearchnulls = amroutine->amsearchnulls; - info->amcanparallel = amroutine->amcanparallel; - info->amhasgettuple = (amroutine->amgettuple != NULL); - info->amhasgetbitmap = amroutine->amgetbitmap != NULL && - relation->rd_tableam->scan_bitmap_next_block != NULL; - info->amcanmarkpos = (amroutine->ammarkpos != NULL && - amroutine->amrestrpos != NULL); - info->amcostestimate = amroutine->amcostestimate; - Assert(info->amcostestimate != NULL); - - /* Fetch index opclass options */ - info->opclassoptions = RelationGetIndexAttOptions(indexRelation, true); - /* - * Fetch the ordering information for the index, if any. + * We don't have am for partitioned indexes, therefore we skip + * gathering the info. This is ok, because we don't use + * partitioned indexes in paths. We resolve inheritance before + * generating paths. */ - if (info->relam == BTREE_AM_OID) + if (indexRelation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX) { + /* We copy just the fields we need, not all of rd_indam */ + amroutine = indexRelation->rd_indam; + info->amcanorderbyop = amroutine->amcanorderbyop; + info->amoptionalkey = amroutine->amoptionalkey; + info->amsearcharray = amroutine->amsearcharray; + info->amsearchnulls = amroutine->amsearchnulls; + info->amcanparallel = amroutine->amcanparallel; + info->amhasgettuple = (amroutine->amgettuple != NULL); + info->amhasgetbitmap = amroutine->amgetbitmap != NULL && + relation->rd_tableam->scan_bitmap_next_block != NULL; + info->amcanmarkpos = (amroutine->ammarkpos != NULL && + amroutine->amrestrpos != NULL); + info->amcostestimate = amroutine->amcostestimate; + Assert(info->amcostestimate != NULL); + + /* Fetch index opclass options */ + info->opclassoptions = RelationGetIndexAttOptions(indexRelation, true); + /* - * If it's a btree index, we can use its opfamily OIDs - * directly as the sort ordering opfamily OIDs. + * Fetch the ordering information for the index, if any. */ - Assert(amroutine->amcanorder); - - info->sortopfamily = info->opfamily; - info->reverse_sort = (bool *) palloc(sizeof(bool) * nkeycolumns); - info->nulls_first = (bool *) palloc(sizeof(bool) * nkeycolumns); - - for (i = 0; i < nkeycolumns; i++) + if (info->relam == BTREE_AM_OID) { - int16 opt = indexRelation->rd_indoption[i]; + /* + * If it's a btree index, we can use its opfamily OIDs + * directly as the sort ordering opfamily OIDs. + */ + Assert(amroutine->amcanorder); - info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0; - info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0; - } - } - else if (amroutine->amcanorder) - { - /* - * Otherwise, identify the corresponding btree opfamilies by - * trying to map this index's "<" operators into btree. Since - * "<" uniquely defines the behavior of a sort order, this is - * a sufficient test. - * - * XXX This method is rather slow and also requires the - * undesirable assumption that the other index AM numbers its - * strategies the same as btree. It'd be better to have a way - * to explicitly declare the corresponding btree opfamily for - * each opfamily of the other index type. But given the lack - * of current or foreseeable amcanorder index types, it's not - * worth expending more effort on now. - */ - info->sortopfamily = (Oid *) palloc(sizeof(Oid) * nkeycolumns); - info->reverse_sort = (bool *) palloc(sizeof(bool) * nkeycolumns); - info->nulls_first = (bool *) palloc(sizeof(bool) * nkeycolumns); + info->sortopfamily = info->opfamily; + info->reverse_sort = (bool *)palloc(sizeof(bool) * nkeycolumns); + info->nulls_first = (bool *)palloc(sizeof(bool) * nkeycolumns); - for (i = 0; i < nkeycolumns; i++) - { - int16 opt = indexRelation->rd_indoption[i]; - Oid ltopr; - Oid btopfamily; - Oid btopcintype; - int16 btstrategy; - - info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0; - info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0; - - ltopr = get_opfamily_member(info->opfamily[i], - info->opcintype[i], - info->opcintype[i], - BTLessStrategyNumber); - if (OidIsValid(ltopr) && - get_ordering_op_properties(ltopr, - &btopfamily, - &btopcintype, - &btstrategy) && - btopcintype == info->opcintype[i] && - btstrategy == BTLessStrategyNumber) + for (i = 0; i < nkeycolumns; i++) { - /* Successful mapping */ - info->sortopfamily[i] = btopfamily; + int16 opt = indexRelation->rd_indoption[i]; + + info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0; + info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0; } - else + } + else if (amroutine->amcanorder) + { + /* + * Otherwise, identify the corresponding btree opfamilies by + * trying to map this index's "<" operators into btree. Since + * "<" uniquely defines the behavior of a sort order, this is + * a sufficient test. + * + * XXX This method is rather slow and also requires the + * undesirable assumption that the other index AM numbers its + * strategies the same as btree. It'd be better to have a way + * to explicitly declare the corresponding btree opfamily for + * each opfamily of the other index type. But given the lack + * of current or foreseeable amcanorder index types, it's not + * worth expending more effort on now. + */ + info->sortopfamily = (Oid *)palloc(sizeof(Oid) * nkeycolumns); + info->reverse_sort = (bool *)palloc(sizeof(bool) * nkeycolumns); + info->nulls_first = (bool *)palloc(sizeof(bool) * nkeycolumns); + + for (i = 0; i < nkeycolumns; i++) { - /* Fail ... quietly treat index as unordered */ - info->sortopfamily = NULL; - info->reverse_sort = NULL; - info->nulls_first = NULL; - break; + int16 opt = indexRelation->rd_indoption[i]; + Oid ltopr; + Oid btopfamily; + Oid btopcintype; + int16 btstrategy; + + info->reverse_sort[i] = (opt & INDOPTION_DESC) != 0; + info->nulls_first[i] = (opt & INDOPTION_NULLS_FIRST) != 0; + + ltopr = get_opfamily_member(info->opfamily[i], + info->opcintype[i], + info->opcintype[i], + BTLessStrategyNumber); + if (OidIsValid(ltopr) && + get_ordering_op_properties(ltopr, + &btopfamily, + &btopcintype, + &btstrategy) && + btopcintype == info->opcintype[i] && + btstrategy == BTLessStrategyNumber) + { + /* Successful mapping */ + info->sortopfamily[i] = btopfamily; + } + else + { + /* Fail ... quietly treat index as unordered */ + info->sortopfamily = NULL; + info->reverse_sort = NULL; + info->nulls_first = NULL; + break; + } } } + else + { + info->sortopfamily = NULL; + info->reverse_sort = NULL; + info->nulls_first = NULL; + } } else { info->sortopfamily = NULL; info->reverse_sort = NULL; info->nulls_first = NULL; + + info->amcanorderbyop = false; + info->amoptionalkey = false; + info->amsearcharray = false; + info->amsearchnulls = false; + info->amcanparallel = false; + info->amhasgettuple = false; + info->amhasgetbitmap = false; + info->amcanmarkpos = false; + info->amcostestimate = NULL; } /* @@ -411,38 +430,42 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, info->immediate = index->indimmediate; info->hypothetical = false; - /* - * Estimate the index size. If it's not a partial index, we lock - * the number-of-tuples estimate to equal the parent table; if it - * is partial then we have to use the same methods as we would for - * a table, except we can be sure that the index is not larger - * than the table. - */ - if (info->indpred == NIL) - { - info->pages = RelationGetNumberOfBlocks(indexRelation); - info->tuples = rel->tuples; - } - else - { - double allvisfrac; /* dummy */ - - estimate_rel_size(indexRelation, NULL, - &info->pages, &info->tuples, &allvisfrac); - if (info->tuples > rel->tuples) - info->tuples = rel->tuples; - } - - if (info->relam == BTREE_AM_OID) + /* Skip size and btree calculation if there isn't really a btree. */ + if (indexRelation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX) { - /* For btrees, get tree height while we have the index open */ - info->tree_height = _bt_getrootheight(indexRelation); - } - else - { - /* For other index types, just set it to "unknown" for now */ - info->tree_height = -1; - } + /* + * Estimate the index size. If it's not a partial index, we lock + * the number-of-tuples estimate to equal the parent table; if it + * is partial then we have to use the same methods as we would for + * a table, except we can be sure that the index is not larger + * than the table. + */ + if (info->indpred == NIL) + { + info->pages = RelationGetNumberOfBlocks(indexRelation); + info->tuples = rel->tuples; + } + else + { + double allvisfrac; /* dummy */ + + estimate_rel_size(indexRelation, NULL, + &info->pages, &info->tuples, &allvisfrac); + if (info->tuples > rel->tuples) + info->tuples = rel->tuples; + } + + if (info->relam == BTREE_AM_OID) + { + /* For btrees, get tree height while we have the index open */ + info->tree_height = _bt_getrootheight(indexRelation); + } + else + { + /* For other index types, just set it to "unknown" for now */ + info->tree_height = -1; + } + } index_close(indexRelation, NoLock); diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 48858a871a..878e6e2f2b 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -5993,6 +5993,10 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, rte = root->simple_rte_array[rel->relid]; Assert(rte->rtekind == RTE_RELATION); + /* ignore partitioned tables. Any indexes here are not real indexes */ + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + return false; + /* Search through the indexes to see if any match our problem */ foreach(lc, rel->indexlist) { diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 12624e6adb..b3ccff1017 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -634,7 +634,7 @@ typedef struct PartitionSchemeData *PartitionScheme; * lateral_relids - required outer rels for LATERAL, as a Relids set * (includes both direct and indirect lateral references) * - * If the relation is a base relation it will have these fields set: + * If the relation is a base relation, it will have these fields set: * * relid - RTE index (this is redundant with the relids field, but * is provided for convenience of access) @@ -649,8 +649,6 @@ typedef struct PartitionSchemeData *PartitionScheme; * Vars and PlaceHolderVars) * lateral_referencers - relids of rels that reference this one laterally * (includes both direct and indirect lateral references) - * indexlist - list of IndexOptInfo nodes for relation's indexes - * (always NIL if it's not a table) * pages - number of disk pages in relation (zero if not a table) * tuples - number of tuples in relation (not considering restrictions) * allvisfrac - fraction of disk pages that are marked all-visible @@ -666,6 +664,13 @@ typedef struct PartitionSchemeData *PartitionScheme; * For otherrels that are appendrel members, these fields are filled * in just as for a baserel, except we don't bother with lateral_vars. * + * If the relation is either a base relation or a partitioned table, it will have a set: + * indexlist - list of IndexOptInfo nodes for relation's indexes + * (for patitioned indexes not all of the + * IndexOptInfo fields are set, since they + * can't be accessed) + * (always NIL if it's not a table) + * * If the relation is either a foreign table or a join of foreign tables that * all belong to the same foreign server and are assigned to the same user to * check access permissions as (cf checkAsUser), these fields will be set: diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 2d49e765de..80fc76ed32 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -1,6 +1,8 @@ -- -- Test inheritance features -- +-- XXX These tables are not dropped. Why? +-- XXX Are they needed for later tests? For dump-restore? Dropping them doesn't fail any test from check-world. CREATE TABLE a (aa TEXT); CREATE TABLE b (bb TEXT) INHERITS (a); CREATE TABLE c (cc TEXT) INHERITS (a); diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index b8d43e4c14..822230da3a 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -4699,7 +4699,8 @@ select a.unique1, b.unique2 (1 row) -- --- test join removal +-- test join removal for plain tables +-- we have almost the smare tests for partitioned tables below -- begin; CREATE TEMP TABLE a (id int PRIMARY KEY, b_id int); @@ -4831,6 +4832,55 @@ select 1 from (select a.id FROM a left join b on a.b_id = b.id) q, Filter: (a.id = i) (4 rows) +rollback; +-- +-- test join removal for partitioned tables +-- this is analog to the non partitioned case above +-- +begin; +CREATE TABLE b_p (id int PRIMARY KEY, c_id int) partition by range(id); +CREATE TABLE b_m partition of b_p for values from (0) to (10) partition by range(id); +CREATE TABLE b_c partition of b_m for values from (0) to (10); +CREATE TABLE c_p (id int PRIMARY KEY) partition by range(id); +CREATE TABLE c_m partition of c_p for values from (0) to (10) partition by range(id); +CREATE TABLE c_c partition of c_m for values from (0) to (10); +INSERT INTO b_p VALUES (0, 0), (1, NULL); +INSERT INTO c_p VALUES (0), (1); +-- all three cases should be optimizable into a simple seqscan +EXPLAIN (COSTS OFF) SELECT b0.* FROM b_p b0 LEFT JOIN b_p b ON b0.c_id = b.id; + QUERY PLAN +-------------------- + Seq Scan on b_c b0 +(1 row) + +EXPLAIN (COSTS OFF) SELECT b.* FROM b_p b LEFT JOIN c_p c ON b.c_id = c.id; + QUERY PLAN +------------------- + Seq Scan on b_c b +(1 row) + +EXPLAIN (COSTS OFF) + SELECT b0.* FROM b_p b0 LEFT JOIN (b_p b LEFT JOIN c_p c on b.c_id = c.id) + ON (b0.c_id = b.id); + QUERY PLAN +-------------------- + Seq Scan on b_c b0 +(1 row) + +-- check optimization of outer join within another special join +EXPLAIN (COSTS OFF) +select id from b_p b0 WHERE id in ( + SELECT b.id FROM b_p b LEFT JOIN c_p c on b.id = c.id +); + QUERY PLAN +------------------------------- + Hash Join + Hash Cond: (b0.id = b.id) + -> Seq Scan on b_c b0 + -> Hash + -> Seq Scan on b_c b +(5 rows) + rollback; create temp table parent (k int primary key, pd int); create temp table child (k int unique, cd int); diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index b20facc19f..dd8ad1c71a 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -4867,13 +4867,13 @@ CREATE TABLE fract_t (id BIGINT, PRIMARY KEY (id)) PARTITION BY RANGE (id); CREATE TABLE fract_t0 PARTITION OF fract_t FOR VALUES FROM ('0') TO ('1000'); CREATE TABLE fract_t1 PARTITION OF fract_t FOR VALUES FROM ('1000') TO ('2000'); -- insert data -INSERT INTO fract_t (id) (SELECT generate_series(0, 1999)); +INSERT INTO fract_t (id) SELECT i FROM generate_series(0, 1999) i; ANALYZE fract_t; -- verify plan; nested index only scans SET max_parallel_workers_per_gather = 0; SET enable_partitionwise_join = on; EXPLAIN (COSTS OFF) -SELECT * FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY id ASC LIMIT 10; +SELECT x.id, y.id FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY x.id ASC LIMIT 10; QUERY PLAN ----------------------------------------------------------------------- Limit @@ -4890,7 +4890,7 @@ SELECT * FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY id ASC LIMIT 10; (11 rows) EXPLAIN (COSTS OFF) -SELECT * FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY id DESC LIMIT 10; +SELECT x.id, y.id FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY x.id DESC LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------- Limit diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index 195aedb5ff..69600bb068 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -1,6 +1,10 @@ -- -- Test inheritance features -- + +-- XXX These tables are not dropped. Why? +-- XXX Are they needed for later tests? For dump-restore? Dropping them doesn't fail any test from check-world. + CREATE TABLE a (aa TEXT); CREATE TABLE b (bb TEXT) INHERITS (a); CREATE TABLE c (cc TEXT) INHERITS (a); diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 65aab85c35..8424473630 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -1627,7 +1627,8 @@ select a.unique1, b.unique2 where b.unique2 = any (select q1 from int8_tbl c where c.q1 < b.unique1); -- --- test join removal +-- test join removal for plain tables +-- we have almost the smare tests for partitioned tables below -- begin; @@ -1696,6 +1697,37 @@ select 1 from (select a.id FROM a left join b on a.b_id = b.id) q, rollback; +-- +-- test join removal for partitioned tables +-- this is analog to the non partitioned case above +-- + +begin; + +CREATE TABLE b_p (id int PRIMARY KEY, c_id int) partition by range(id); +CREATE TABLE b_m partition of b_p for values from (0) to (10) partition by range(id); +CREATE TABLE b_c partition of b_m for values from (0) to (10); +CREATE TABLE c_p (id int PRIMARY KEY) partition by range(id); +CREATE TABLE c_m partition of c_p for values from (0) to (10) partition by range(id); +CREATE TABLE c_c partition of c_m for values from (0) to (10); +INSERT INTO b_p VALUES (0, 0), (1, NULL); +INSERT INTO c_p VALUES (0), (1); + +-- all three cases should be optimizable into a simple seqscan +EXPLAIN (COSTS OFF) SELECT b0.* FROM b_p b0 LEFT JOIN b_p b ON b0.c_id = b.id; +EXPLAIN (COSTS OFF) SELECT b.* FROM b_p b LEFT JOIN c_p c ON b.c_id = c.id; +EXPLAIN (COSTS OFF) + SELECT b0.* FROM b_p b0 LEFT JOIN (b_p b LEFT JOIN c_p c on b.c_id = c.id) + ON (b0.c_id = b.id); + +-- check optimization of outer join within another special join +EXPLAIN (COSTS OFF) +select id from b_p b0 WHERE id in ( + SELECT b.id FROM b_p b LEFT JOIN c_p c on b.id = c.id +); + +rollback; + create temp table parent (k int primary key, pd int); create temp table child (k int unique, cd int); insert into parent values (1, 10), (2, 20), (3, 30); diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql index 67f506361f..94f48a7c51 100644 --- a/src/test/regress/sql/partition_join.sql +++ b/src/test/regress/sql/partition_join.sql @@ -1149,7 +1149,7 @@ CREATE TABLE fract_t0 PARTITION OF fract_t FOR VALUES FROM ('0') TO ('1000'); CREATE TABLE fract_t1 PARTITION OF fract_t FOR VALUES FROM ('1000') TO ('2000'); -- insert data -INSERT INTO fract_t (id) (SELECT generate_series(0, 1999)); +INSERT INTO fract_t (id) SELECT i FROM generate_series(0, 1999) i; ANALYZE fract_t; -- verify plan; nested index only scans @@ -1157,10 +1157,10 @@ SET max_parallel_workers_per_gather = 0; SET enable_partitionwise_join = on; EXPLAIN (COSTS OFF) -SELECT * FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY id ASC LIMIT 10; +SELECT x.id, y.id FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY x.id ASC LIMIT 10; EXPLAIN (COSTS OFF) -SELECT * FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY id DESC LIMIT 10; +SELECT x.id, y.id FROM fract_t x LEFT JOIN fract_t y USING (id) ORDER BY x.id DESC LIMIT 10; -- cleanup DROP TABLE fract_t; -- 2.35.3