From edd4e4c951166ce26eff88f1d9f8e9d9a2b19624 Mon Sep 17 00:00:00 2001 From: amit Date: Tue, 22 Aug 2017 17:31:42 +0900 Subject: [PATCH 6/6] WIP: planner-side changes for partition-pruning This implements the planner-side logic to extract bounding scan keys to be passed to get_partitions_for_keys. That is it will go through rel->baserestrictinfo and match individual clauses to partition keys and construct lower bound and upper bound tuples, which may cover only a prefix of a multi-column partition key. A bunch of smarts are still missing when mapping the clause operands with keys. For example, code to match a clause is specifed as (constant op var) doesn't exist. Also, redundant keys are not eliminated, for example, a combination of clauses a = 10 and a > 1 will cause the later clause a > 1 taking over and resulting in needless scanning of partitions containing values a > 1 and a < 10. ...constraint exclusion is no longer used... --- src/backend/catalog/partition.c | 57 +++++ src/backend/nodes/outfuncs.c | 2 + src/backend/optimizer/path/allpaths.c | 157 ++++++++++++- src/backend/optimizer/prep/prepunion.c | 9 + src/backend/optimizer/util/plancat.c | 4 + src/include/catalog/partition.h | 2 + src/include/nodes/relation.h | 2 + src/test/regress/expected/partition.out | 375 ++++++++++++++++++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + src/test/regress/sql/partition.sql | 65 ++++++ 11 files changed, 668 insertions(+), 8 deletions(-) create mode 100644 src/test/regress/expected/partition.out create mode 100644 src/test/regress/sql/partition.sql diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index afb85cbc37..8e57d36449 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -1177,6 +1177,63 @@ RelationGetPartitionDispatchInfo(Relation rel, } /* + * get_partition_keys + * Returns a list of expressions matching the partition key columns + */ +List * +get_partition_keys(PartitionDispatch pd, int varno) +{ + int i; + PartitionKey key = pd->key; + List *result = NIL; + ListCell *lc; + + lc = list_head(key->partexprs); + for (i = 0; i < key->partnatts; i++) + { + Expr *keyCol; + + if (key->partattrs[i] != 0) + { + keyCol = (Expr *) makeVar(varno, + key->partattrs[i], + key->parttypid[i], + key->parttypmod[i], + key->parttypcoll[i], + 0); + } + else + { + if (lc == NULL) + elog(ERROR, "wrong number of partition key expressions"); + keyCol = (Expr *) copyObject(lfirst(lc)); + lc = lnext(lc); + } + + result = lappend(result, keyCol); + } + + return result; +} + +/* + * get_partition_opfamilies + * Get partitioning operator family OIDs for all keys + */ +List * +get_partition_opfamilies(PartitionDispatch pd) +{ + List *result = NIL; + PartitionKey key = pd->key; + int i; + + for (i = 0; i < key->partnatts; i++) + result = lappend_oid(result, key->partopfamily[i]); + + return result; +} + +/* * get_partitions_for_keys * Returns the list of indexes (from pd->indexes) of the partitions that * will need to be scanned for the given scan keys. diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 2480fd6429..4d092489ba 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2524,6 +2524,8 @@ _outPartitionInfo(StringInfo str, const PartitionInfo *node) WRITE_BOOL_FIELD(is_other_temp); WRITE_UINT_FIELD(relid); + WRITE_NODE_FIELD(keys); + WRITE_NODE_FIELD(keyopfamilies); /* Don't bother writing out the PartitionDispatch object */ } diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index a5e217674b..a55ede2faa 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -868,10 +868,24 @@ get_rel_partitions_recurse(RelOptInfo *rootrel, List *indexes; List *result = NIL, *my_live_partitions = NIL; - ListCell *l; + ListCell *lc1, + *lc2, + *keyopfamilies_item; + int keyPos; + List *matchedclauses[PARTITION_MAX_KEYS]; bool keyisnull[PARTITION_MAX_KEYS]; Datum minkeys[PARTITION_MAX_KEYS], maxkeys[PARTITION_MAX_KEYS]; + bool need_next_min, + need_next_max, + minkey_set[PARTITION_MAX_KEYS], + maxkey_set[PARTITION_MAX_KEYS], + min_incl, + max_incl; + int n_partkeys = list_length(partinfo->keys), + n_minkeys = 0, + n_maxkeys = 0, + i; /* * Create a PartitionAppendInfo to map this table to the child tables @@ -885,22 +899,151 @@ get_rel_partitions_recurse(RelOptInfo *rootrel, painfo); /* - * TODO: collect the keys by looking at the clauses in - * rootrel->baserestrictinfo considering this table's partition keys. + * Match individual OpExprs in the query's restriction with individual + * partition key columns. There is one list per key. */ + memset(matchedclauses, 0, sizeof(matchedclauses)); memset(keyisnull, false, sizeof(keyisnull)); + keyPos = 0; + foreach(lc1, partinfo->keys) + { + Node *partkey = lfirst(lc1); + + foreach(lc2, rootrel->baserestrictinfo) + { + RestrictInfo *rinfo = lfirst(lc2); + Expr *clause = rinfo->clause; + + if (is_opclause(clause)) + { + Node *leftop = get_leftop(clause); + + if (IsA(leftop, RelabelType)) + leftop = (Node *) ((RelabelType *) leftop)->arg; + + if (equal(leftop, partkey)) + matchedclauses[keyPos] = lappend(matchedclauses[keyPos], + clause); + } + else if (IsA(clause, NullTest)) + { + NullTest *nulltest = (NullTest *) clause; + Node *arg = (Node *) nulltest->arg; + + if (equal(arg, partkey) && nulltest->nulltesttype == IS_NULL) + keyisnull[keyPos] = true; + } + } + + /* Onto finding clauses matching the next partition key. */ + keyPos++; + } + + /* + * Determine the min keys and the max keys using btree semantics-based + * interpretation of the clauses' operators. + */ + + /* + * XXX - There should be a step similar to _bt_preprocess_keys() here, + * to eliminate any redundant scan keys for a given partition column. For + * example, among a <= 4 and a <= 5, we can only keep a <= 4 for being + * more restrictive and discard a <= 5. While doing that, we can also + * check to see if there exists a contradictory combination of scan keys + * that makes the query trivially false for all records in the table. + */ + memset(minkeys, 0, sizeof(minkeys)); memset(maxkeys, 0, sizeof(maxkeys)); + memset(minkey_set, false, sizeof(minkey_set)); + memset(maxkey_set, false, sizeof(maxkey_set)); + need_next_min = true; + need_next_max = true; + keyopfamilies_item = list_head(partinfo->keyopfamilies); + for (i = 0; i < n_partkeys; i++) + { + /* + * If no scan key existed for the previous column, we are done. + */ + if (i > n_minkeys) + need_next_min = false; + + if (i > n_maxkeys) + need_next_max = false; + + foreach(lc1, matchedclauses[i]) + { + Expr *clause = lfirst(lc1); + Const *rightop = (Const *) get_rightop(clause); + Oid opno = ((OpExpr *) clause)->opno, + opfamily = lfirst_oid(keyopfamilies_item); + StrategyNumber strategy; + + strategy = get_op_opfamily_strategy(opno, opfamily); + switch (strategy) + { + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + if (need_next_max) + { + maxkeys[i] = rightop->constvalue; + if (!maxkey_set[i]) + n_maxkeys++; + maxkey_set[i] = true; + max_incl = (strategy == BTLessEqualStrategyNumber); + } + if (strategy == BTLessStrategyNumber) + need_next_max = false; + break; + + case BTGreaterStrategyNumber: + case BTGreaterEqualStrategyNumber: + if (need_next_min) + { + minkeys[i] = rightop->constvalue; + if (!minkey_set[i]) + n_minkeys++; + minkey_set[i] = true; + min_incl = (strategy == BTGreaterEqualStrategyNumber); + } + if (strategy == BTGreaterStrategyNumber) + need_next_min = false; + break; + + case BTEqualStrategyNumber: + if (need_next_min) + { + minkeys[i] = rightop->constvalue; + if (!minkey_set[i]) + n_minkeys++; + } + minkey_set[i] = true; + min_incl = true; + + if (need_next_max) + { + maxkeys[i] = rightop->constvalue; + if (!maxkey_set[i]) + n_maxkeys++; + } + maxkey_set[i] = true; + max_incl = true; + break; + } + } + + keyopfamilies_item = lnext(keyopfamilies_item); + } /* Ask partition.c which partitions it thinks match the keys. */ indexes = get_partitions_for_keys(partinfo->pd, keyisnull, - minkeys, 0, false, - maxkeys, 0, false); + minkeys, n_minkeys, min_incl, + maxkeys, n_maxkeys, max_incl); /* Collect leaf partitions in the result list and recurse for others. */ - foreach(l, indexes) + foreach(lc1, indexes) { - int index = lfirst_int(l); + int index = lfirst_int(lc1); if (index >= 0) { diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 01de2d778d..e5c60020b7 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1509,6 +1509,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) pinfo = makeNode(PartitionInfo); pinfo->relid = rti; pinfo->pd = pds[0]; + pinfo->keys = get_partition_keys(pinfo->pd, rti); + pinfo->keyopfamilies = get_partition_opfamilies(pinfo->pd); partition_infos = list_make1(pinfo); partitioned_child_rels = list_make1_int(rti); for (i = 1; i < num_parted; i++) @@ -1617,6 +1619,13 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) pinfo->is_other_temp = is_other_temp; pinfo->relid = childRTindex; pinfo->pd = pds[i++]; + + /* Convert so that expression contains oldrelation's attnos. */ + pinfo->keys = + map_partition_varattnos(get_partition_keys(pinfo->pd, rti), + rti, oldrelation, pinfo->pd->reldesc, + NULL); + pinfo->keyopfamilies = get_partition_opfamilies(pinfo->pd); partition_infos = lappend(partition_infos, pinfo); partitioned_child_rels = lappend_int(partitioned_child_rels, diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 3781a91b76..fdcb77b16f 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1149,7 +1149,9 @@ get_relation_constraints(PlannerInfo *root, Index varno = rel->relid; Relation relation; TupleConstr *constr; +#ifdef USE_PARTITION_CONSTRAINT_FOR_PRUNING List *pcqual; +#endif /* * We assume the relation has already been safely locked. @@ -1235,6 +1237,7 @@ get_relation_constraints(PlannerInfo *root, } } +#ifdef USE_PARTITION_CONSTRAINT_FOR_PRUNING /* Append partition predicates, if any */ pcqual = RelationGetPartitionQual(relation); if (pcqual) @@ -1252,6 +1255,7 @@ get_relation_constraints(PlannerInfo *root, result = list_concat(result, pcqual); } +#endif heap_close(relation, NoLock); diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h index fb15498f92..d9ca8d8371 100644 --- a/src/include/catalog/partition.h +++ b/src/include/catalog/partition.h @@ -96,6 +96,8 @@ extern int get_partition_for_tuple(PartitionTupleRoutingInfo **ptrinfos, TupleTableSlot **failed_slot); /* Planner support stuff. */ +extern List *get_partition_keys(PartitionDispatch pd, int varno); +extern List *get_partition_opfamilies(PartitionDispatch pd); extern List *get_partitions_for_keys(PartitionDispatch pd, bool *key_is_null, Datum *minkeys, int n_minkeys, bool min_inclusive, diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index a67a43b069..0f9bcd81ed 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -2045,6 +2045,8 @@ typedef struct PartitionInfo bool is_other_temp; /* If true, ignore the following fields */ Index relid; /* Ordinal position in the rangetable */ + List *keys; /* Expressions for partition keys */ + List *keyopfamilies; /* Operator family OID per key */ PartitionDispatch pd; /* Information about partitions */ } PartitionInfo; diff --git a/src/test/regress/expected/partition.out b/src/test/regress/expected/partition.out new file mode 100644 index 0000000000..0839923fca --- /dev/null +++ b/src/test/regress/expected/partition.out @@ -0,0 +1,375 @@ +-- +-- Test partitioning planner code +-- +create table rlpt (a int, b varchar) partition by range (a); +create table rlpt1 partition of rlpt for values from (minvalue) to (1); +create table rlpt2 partition of rlpt for values from (1) to (10); +create table rlpt3 (b varchar, a int) partition by list (b varchar_ops); +create table rlpt3abcd partition of rlpt3 for values in ('ab', 'cd'); +create table rlpt3efgh partition of rlpt3 for values in ('ef', 'gh'); +create table rlpt3nullxy partition of rlpt3 for values in (null, 'xy'); +alter table rlpt attach partition rlpt3 for values from (15) to (20); +create table rlpt4 partition of rlpt for values from (20) to (30); +create table rlpt5 partition of rlpt for values from (31) to (maxvalue); +explain (costs off) select * from rlpt where a < 1; + QUERY PLAN +------------------------- + Append + -> Seq Scan on rlpt1 + Filter: (a < 1) +(3 rows) + +explain (costs off) select * from rlpt where a <= 1; + QUERY PLAN +-------------------------- + Append + -> Seq Scan on rlpt1 + Filter: (a <= 1) + -> Seq Scan on rlpt2 + Filter: (a <= 1) +(5 rows) + +explain (costs off) select * from rlpt where a = 1; + QUERY PLAN +------------------------- + Append + -> Seq Scan on rlpt2 + Filter: (a = 1) +(3 rows) + +explain (costs off) select * from rlpt where a = 1::bigint; /* same as above */ + QUERY PLAN +----------------------------------- + Append + -> Seq Scan on rlpt2 + Filter: (a = '1'::bigint) +(3 rows) + +explain (costs off) select * from rlpt where a = 1::numeric; /* no pruning */ + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on rlpt1 + Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlpt2 + Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlpt3abcd + Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlpt3efgh + Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlpt3nullxy + Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlpt4 + Filter: ((a)::numeric = '1'::numeric) + -> Seq Scan on rlpt5 + Filter: ((a)::numeric = '1'::numeric) +(15 rows) + +explain (costs off) select * from rlpt where a <= 10; + QUERY PLAN +--------------------------- + Append + -> Seq Scan on rlpt1 + Filter: (a <= 10) + -> Seq Scan on rlpt2 + Filter: (a <= 10) +(5 rows) + +explain (costs off) select * from rlpt where a > 10; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on rlpt3abcd + Filter: (a > 10) + -> Seq Scan on rlpt3efgh + Filter: (a > 10) + -> Seq Scan on rlpt3nullxy + Filter: (a > 10) + -> Seq Scan on rlpt4 + Filter: (a > 10) + -> Seq Scan on rlpt5 + Filter: (a > 10) +(11 rows) + +explain (costs off) select * from rlpt where a < 15; + QUERY PLAN +-------------------------- + Append + -> Seq Scan on rlpt1 + Filter: (a < 15) + -> Seq Scan on rlpt2 + Filter: (a < 15) +(5 rows) + +explain (costs off) select * from rlpt where a <= 15; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on rlpt1 + Filter: (a <= 15) + -> Seq Scan on rlpt2 + Filter: (a <= 15) + -> Seq Scan on rlpt3abcd + Filter: (a <= 15) + -> Seq Scan on rlpt3efgh + Filter: (a <= 15) + -> Seq Scan on rlpt3nullxy + Filter: (a <= 15) +(11 rows) + +explain (costs off) select * from rlpt where a > 15 and b = 'ab'; + QUERY PLAN +--------------------------------------------------------- + Append + -> Seq Scan on rlpt3abcd + Filter: ((a > 15) AND ((b)::text = 'ab'::text)) + -> Seq Scan on rlpt4 + Filter: ((a > 15) AND ((b)::text = 'ab'::text)) + -> Seq Scan on rlpt5 + Filter: ((a > 15) AND ((b)::text = 'ab'::text)) +(7 rows) + +explain (costs off) select * from rlpt where a = 16 and b is null; + QUERY PLAN +-------------------------------------------- + Append + -> Seq Scan on rlpt3nullxy + Filter: ((b IS NULL) AND (a = 16)) +(3 rows) + +explain (costs off) select * from rlpt where a = 16 and b is not null; + QUERY PLAN +------------------------------------------------ + Append + -> Seq Scan on rlpt3abcd + Filter: ((b IS NOT NULL) AND (a = 16)) + -> Seq Scan on rlpt3efgh + Filter: ((b IS NOT NULL) AND (a = 16)) + -> Seq Scan on rlpt3nullxy + Filter: ((b IS NOT NULL) AND (a = 16)) +(7 rows) + +explain (costs off) select * from rlpt where a is null; /* while we're on nulls */ + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +explain (costs off) select * from rlpt where a > 30; + QUERY PLAN +-------------------------- + Append + -> Seq Scan on rlpt5 + Filter: (a > 30) +(3 rows) + +explain (costs off) select * from rlpt where a = 30; /* empty */ + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +explain (costs off) select * from rlpt where a <= 31; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on rlpt1 + Filter: (a <= 31) + -> Seq Scan on rlpt2 + Filter: (a <= 31) + -> Seq Scan on rlpt3abcd + Filter: (a <= 31) + -> Seq Scan on rlpt3efgh + Filter: (a <= 31) + -> Seq Scan on rlpt3nullxy + Filter: (a <= 31) + -> Seq Scan on rlpt4 + Filter: (a <= 31) + -> Seq Scan on rlpt5 + Filter: (a <= 31) +(15 rows) + +-- multi-column keys +create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +create table mc3p0 partition of mc3p for values from (minvalue, 0, 0) to (1, 1, 1); +create table mc3p1 partition of mc3p for values from (1, 1, 1) to (10, 5, 10); +create table mc3p2 partition of mc3p for values from (10, 5, 10) to (10, 10, 10); +create table mc3p3 partition of mc3p for values from (10, 10, 10) to (10, 10, 20); +create table mc3p4 partition of mc3p for values from (10, 10, 20) to (10, maxvalue, maxvalue); +create table mc3p5 partition of mc3p for values from (11, 1, 1) to (20, 10, 10); +create table mc3p6 partition of mc3p for values from (20, 10, 10) to (20, 20, 20); +create table mc3p7 partition of mc3p for values from (20, 20, 20) to (maxvalue, 0, 0); +explain (costs off) select * from mc3p where a = 1; + QUERY PLAN +------------------------- + Append + -> Seq Scan on mc3p0 + Filter: (a = 1) + -> Seq Scan on mc3p1 + Filter: (a = 1) +(5 rows) + +explain (costs off) select * from mc3p where a = 1 and abs(b) < 1; + QUERY PLAN +-------------------------------------------- + Append + -> Seq Scan on mc3p0 + Filter: ((a = 1) AND (abs(b) < 1)) +(3 rows) + +explain (costs off) select * from mc3p where a = 1 and abs(b) = 1; + QUERY PLAN +-------------------------------------------- + Append + -> Seq Scan on mc3p0 + Filter: ((a = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p1 + Filter: ((a = 1) AND (abs(b) = 1)) +(5 rows) + +explain (costs off) select * from mc3p where a = 1 and abs(b) = 1 and c < 8; + QUERY PLAN +-------------------------------------------------------- + Append + -> Seq Scan on mc3p0 + Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p1 + Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1)) +(5 rows) + +explain (costs off) select * from mc3p where a = 10 and abs(b) between 5 and 35; + QUERY PLAN +----------------------------------------------------------------- + Append + -> Seq Scan on mc3p1 + Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) + -> Seq Scan on mc3p2 + Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) + -> Seq Scan on mc3p3 + Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) + -> Seq Scan on mc3p4 + Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35)) +(9 rows) + +explain (costs off) select * from mc3p where a > 10; + QUERY PLAN +-------------------------- + Append + -> Seq Scan on mc3p5 + Filter: (a > 10) + -> Seq Scan on mc3p6 + Filter: (a > 10) + -> Seq Scan on mc3p7 + Filter: (a > 10) +(7 rows) + +explain (costs off) select * from mc3p where a >= 10; + QUERY PLAN +--------------------------- + Append + -> Seq Scan on mc3p1 + Filter: (a >= 10) + -> Seq Scan on mc3p2 + Filter: (a >= 10) + -> Seq Scan on mc3p3 + Filter: (a >= 10) + -> Seq Scan on mc3p4 + Filter: (a >= 10) + -> Seq Scan on mc3p5 + Filter: (a >= 10) + -> Seq Scan on mc3p6 + Filter: (a >= 10) + -> Seq Scan on mc3p7 + Filter: (a >= 10) +(15 rows) + +explain (costs off) select * from mc3p where a < 10; + QUERY PLAN +-------------------------- + Append + -> Seq Scan on mc3p0 + Filter: (a < 10) + -> Seq Scan on mc3p1 + Filter: (a < 10) +(5 rows) + +explain (costs off) select * from mc3p where a <= 10 and abs(b) < 10; + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on mc3p0 + Filter: ((a <= 10) AND (abs(b) < 10)) + -> Seq Scan on mc3p1 + Filter: ((a <= 10) AND (abs(b) < 10)) + -> Seq Scan on mc3p2 + Filter: ((a <= 10) AND (abs(b) < 10)) +(7 rows) + +explain (costs off) select * from mc3p where a = 11 and abs(b) = 0; /* empty */ + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +explain (costs off) select * from mc3p where a = 20 and abs(b) = 10 and c = 100; + QUERY PLAN +------------------------------------------------------------ + Append + -> Seq Scan on mc3p6 + Filter: ((a = 20) AND (c = 100) AND (abs(b) = 10)) +(3 rows) + +explain (costs off) select * from mc3p where a > 20; + QUERY PLAN +-------------------------- + Append + -> Seq Scan on mc3p7 + Filter: (a > 20) +(3 rows) + +explain (costs off) select * from mc3p where a >= 20; + QUERY PLAN +--------------------------- + Append + -> Seq Scan on mc3p5 + Filter: (a >= 20) + -> Seq Scan on mc3p6 + Filter: (a >= 20) + -> Seq Scan on mc3p7 + Filter: (a >= 20) +(7 rows) + +-- XXX - redundant clause elimination does not happen yet +explain (costs off) select * from mc3p where a = 10 and a > 1; + QUERY PLAN +---------------------------------------- + Append + -> Seq Scan on mc3p0 + Filter: ((a > 1) AND (a = 10)) + -> Seq Scan on mc3p1 + Filter: ((a > 1) AND (a = 10)) + -> Seq Scan on mc3p2 + Filter: ((a > 1) AND (a = 10)) + -> Seq Scan on mc3p3 + Filter: ((a > 1) AND (a = 10)) + -> Seq Scan on mc3p4 + Filter: ((a > 1) AND (a = 10)) +(11 rows) + +-- XXX - the OR clauses don't contribute to partition-pruning yet +explain (costs off) select * from rlpt3 where b = 'ab' or b = 'ef'; + QUERY PLAN +------------------------------------------------------------------------ + Append + -> Seq Scan on rlpt3abcd + Filter: (((b)::text = 'ab'::text) OR ((b)::text = 'ef'::text)) + -> Seq Scan on rlpt3efgh + Filter: (((b)::text = 'ab'::text) OR ((b)::text = 'ef'::text)) + -> Seq Scan on rlpt3nullxy + Filter: (((b)::text = 'ab'::text) OR ((b)::text = 'ef'::text)) +(7 rows) + +drop table rlpt, mc3p; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index eefdeeacae..e5089a7cee 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -60,7 +60,7 @@ test: create_index create_view # ---------- # Another group of parallel tests # ---------- -test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views rolenames roleattributes create_am +test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views rolenames roleattributes create_am partition # ---------- # sanity_check does a vacuum, affecting the sort order of SELECT * diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 76b0de30a7..6611662149 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -71,6 +71,7 @@ test: create_cast test: constraints test: triggers test: inherit +test: partition test: create_table_like test: typed_table test: vacuum diff --git a/src/test/regress/sql/partition.sql b/src/test/regress/sql/partition.sql new file mode 100644 index 0000000000..8ffe91b08f --- /dev/null +++ b/src/test/regress/sql/partition.sql @@ -0,0 +1,65 @@ +-- +-- Test partitioning planner code +-- +create table rlpt (a int, b varchar) partition by range (a); +create table rlpt1 partition of rlpt for values from (minvalue) to (1); +create table rlpt2 partition of rlpt for values from (1) to (10); + +create table rlpt3 (b varchar, a int) partition by list (b varchar_ops); +create table rlpt3abcd partition of rlpt3 for values in ('ab', 'cd'); +create table rlpt3efgh partition of rlpt3 for values in ('ef', 'gh'); +create table rlpt3nullxy partition of rlpt3 for values in (null, 'xy'); +alter table rlpt attach partition rlpt3 for values from (15) to (20); + +create table rlpt4 partition of rlpt for values from (20) to (30); +create table rlpt5 partition of rlpt for values from (31) to (maxvalue); + +explain (costs off) select * from rlpt where a < 1; +explain (costs off) select * from rlpt where a <= 1; +explain (costs off) select * from rlpt where a = 1; +explain (costs off) select * from rlpt where a = 1::bigint; /* same as above */ +explain (costs off) select * from rlpt where a = 1::numeric; /* no pruning */ +explain (costs off) select * from rlpt where a <= 10; +explain (costs off) select * from rlpt where a > 10; +explain (costs off) select * from rlpt where a < 15; +explain (costs off) select * from rlpt where a <= 15; +explain (costs off) select * from rlpt where a > 15 and b = 'ab'; +explain (costs off) select * from rlpt where a = 16 and b is null; +explain (costs off) select * from rlpt where a = 16 and b is not null; +explain (costs off) select * from rlpt where a is null; /* while we're on nulls */ +explain (costs off) select * from rlpt where a > 30; +explain (costs off) select * from rlpt where a = 30; /* empty */ +explain (costs off) select * from rlpt where a <= 31; + +-- multi-column keys +create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); +create table mc3p0 partition of mc3p for values from (minvalue, 0, 0) to (1, 1, 1); +create table mc3p1 partition of mc3p for values from (1, 1, 1) to (10, 5, 10); +create table mc3p2 partition of mc3p for values from (10, 5, 10) to (10, 10, 10); +create table mc3p3 partition of mc3p for values from (10, 10, 10) to (10, 10, 20); +create table mc3p4 partition of mc3p for values from (10, 10, 20) to (10, maxvalue, maxvalue); +create table mc3p5 partition of mc3p for values from (11, 1, 1) to (20, 10, 10); +create table mc3p6 partition of mc3p for values from (20, 10, 10) to (20, 20, 20); +create table mc3p7 partition of mc3p for values from (20, 20, 20) to (maxvalue, 0, 0); + +explain (costs off) select * from mc3p where a = 1; +explain (costs off) select * from mc3p where a = 1 and abs(b) < 1; +explain (costs off) select * from mc3p where a = 1 and abs(b) = 1; +explain (costs off) select * from mc3p where a = 1 and abs(b) = 1 and c < 8; +explain (costs off) select * from mc3p where a = 10 and abs(b) between 5 and 35; +explain (costs off) select * from mc3p where a > 10; +explain (costs off) select * from mc3p where a >= 10; +explain (costs off) select * from mc3p where a < 10; +explain (costs off) select * from mc3p where a <= 10 and abs(b) < 10; +explain (costs off) select * from mc3p where a = 11 and abs(b) = 0; /* empty */ +explain (costs off) select * from mc3p where a = 20 and abs(b) = 10 and c = 100; +explain (costs off) select * from mc3p where a > 20; +explain (costs off) select * from mc3p where a >= 20; + +-- XXX - redundant clause elimination does not happen yet +explain (costs off) select * from mc3p where a = 10 and a > 1; + +-- XXX - the OR clauses don't contribute to partition-pruning yet +explain (costs off) select * from rlpt3 where b = 'ab' or b = 'ef'; + +drop table rlpt, mc3p; -- 2.11.0