crash with sql language partition support function

Started by Amit Langotealmost 8 years ago18 messages
#1Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
2 attachment(s)

Hi.

I noticed that RelationBuildPartitionKey() is not doing the right thing
with respect to which context it passes to fmgr.c to set a the (cached)
partition support function's FmgrInfo's fn_mcxt.

I noticed a crash while trying to change partition pruning tests to use
manually created hash operator class per [1]/messages/by-id/CA+TgmoZ0D5kJbt8eKXtvVdvTcGGWn6ehWCRSZbWytD-uzH92mQ@mail.gmail.com.

CREATE OR REPLACE FUNCTION hashint4_noop(int4, int8) RETURNS int8 AS
$$SELECT coalesce($1)::int8$$ LANGUAGE sql IMMUTABLE STRICT;

CREATE OPERATOR CLASS test_int4_ops FOR TYPE int4 USING HASH AS
OPERATOR 1 = , FUNCTION 2 hashint4_noop(int4, int8);

CREATE OR REPLACE FUNCTION hashtext_length(text, int8) RETURNS int8 AS
$$SELECT length(coalesce($1))::int8$$ LANGUAGE sql IMMUTABLE STRICT;

CREATE OPERATOR CLASS test_text_ops FOR TYPE text USING HASH AS
OPERATOR 1 = , FUNCTION 2 hashtext_length(text, int8);

create table hp (a int, b text) partition by hash (a test_int4_ops, b
test_text_ops);
create table hp0 partition of hp for values with (modulus 4, remainder 0);
create table hp3 partition of hp for values with (modulus 4, remainder 3);
create table hp1 partition of hp for values with (modulus 4, remainder 1);
create table hp2 partition of hp for values with (modulus 4, remainder 2);

insert into hp values (1, 'abcde');
INSERT 0 1
Time: 13.604 ms
postgres=# insert into hp values (null, 'abcde');
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Attached fixes it. It teaches RelationBuildPartitionKey() to use
fmgr_info_cxt and pass rd_partkeycxt to it.

Since this bug also exists in the released PG 10 branch, I also created a
patch for that. It's slightly different than the one for PG 11dev,
because there were some changes recently to how the memory context is
manipulated in RelationBuildPartitionKey. That's
v1-PG10-0001-Fix-a-memory-context-bug-in-RelationBuildPartitio.patch.

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoZ0D5kJbt8eKXtvVdvTcGGWn6ehWCRSZbWytD-uzH92mQ@mail.gmail.com
/messages/by-id/CA+TgmoZ0D5kJbt8eKXtvVdvTcGGWn6ehWCRSZbWytD-uzH92mQ@mail.gmail.com

Attachments:

v1-PG10-0001-Fix-a-memory-context-bug-in-RelationBuildPartitio.patchtext/plain; charset=UTF-8; name=v1-PG10-0001-Fix-a-memory-context-bug-in-RelationBuildPartitio.patchDownload
From 81d8337760be175b37b5ffe84df06903173787c5 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 10 Apr 2018 15:38:19 +0900
Subject: [PATCH v1] Fix a memory context bug in RelationBuildPartitionKey

We should pass rd_partkeycxt to set fn_mcxt, not CurrentMemoryContex.
---
 src/backend/utils/cache/relcache.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a69b078f91..be7d5b7eef 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -849,6 +849,10 @@ RelationBuildPartitionKey(Relation relation)
 	if (!HeapTupleIsValid(tuple))
 		return;
 
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+
 	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
 
 	/* Fixed-length attributes */
@@ -951,7 +955,7 @@ RelationBuildPartitionKey(Relation relation)
 				 BTORDER_PROC, opclassform->opcintype, opclassform->opcintype,
 				 opclassform->opcfamily);
 
-		fmgr_info(funcid, &key->partsupfunc[i]);
+		fmgr_info_cxt(funcid, &key->partsupfunc[i], partkeycxt);
 
 		/* Collation */
 		key->partcollation[i] = collation->values[i];
@@ -985,9 +989,6 @@ RelationBuildPartitionKey(Relation relation)
 	ReleaseSysCache(tuple);
 
 	/* Success --- now copy to the cache memory */
-	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
-									   RelationGetRelationName(relation),
-									   ALLOCSET_SMALL_SIZES);
 	relation->rd_partkeycxt = partkeycxt;
 	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
 	relation->rd_partkey = copy_partition_key(key);
-- 
2.11.0

v1-PG11dev-0001-Fix-a-memory-context-bug-in-RelationBuildPartitio.patchtext/plain; charset=UTF-8; name=v1-PG11dev-0001-Fix-a-memory-context-bug-in-RelationBuildPartitio.patchDownload
From 57056b1ca511ef549ee3f28a8e17d2bdfbf757a5 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 10 Apr 2018 15:38:19 +0900
Subject: [PATCH v1] Fix a memory context bug in RelationBuildPartitionKey

We should pass rd_partkeycxt to set fn_mcxt, not CurrentMemoryContex.
---
 src/backend/utils/cache/relcache.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e81c4691ec..2a74601fc1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1034,7 +1034,7 @@ RelationBuildPartitionKey(Relation relation)
 							procnum,
 							format_type_be(opclassform->opcintype))));
 
-		fmgr_info(funcid, &key->partsupfunc[i]);
+		fmgr_info_cxt(funcid, &key->partsupfunc[i], partkeycxt);
 
 		/* Collation */
 		key->partcollation[i] = collation->values[i];
-- 
2.11.0

#2Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#1)
Re: crash with sql language partition support function

I have added this in the Older Bugs section of open items page.

https://wiki.postgresql.org/wiki/PostgreSQL_11_Open_Items#Older_Bugs

Thanks,
Amit

#3Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#1)
Re: crash with sql language partition support function

On Tue, Apr 10, 2018 at 1:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Attached fixes it. It teaches RelationBuildPartitionKey() to use
fmgr_info_cxt and pass rd_partkeycxt to it.

The patch is using partkeycxt and not rd_partkeycxt. Probably a typo
in the mail. But a wider question, why that context? I guess that
cache context will vanish when that cache entry is thrown away. That's
the reason we have to copy partition key information in
find_partition_scheme() instead of just pointing to it and also use
fmgr_info_copy() there. But if that's the case, buildfarm animal run
with -DCLOBBER_CACHE_ALWAYS should show failure. I am missing
something here.

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#4Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Ashutosh Bapat (#3)
Re: crash with sql language partition support function

Ashutosh Bapat wrote:

On Tue, Apr 10, 2018 at 1:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Attached fixes it. It teaches RelationBuildPartitionKey() to use
fmgr_info_cxt and pass rd_partkeycxt to it.

The patch is using partkeycxt and not rd_partkeycxt. Probably a typo
in the mail. But a wider question, why that context? I guess that
cache context will vanish when that cache entry is thrown away. That's
the reason we have to copy partition key information in
find_partition_scheme() instead of just pointing to it and also use
fmgr_info_copy() there. But if that's the case, buildfarm animal run
with -DCLOBBER_CACHE_ALWAYS should show failure. I am missing
something here.

Good point. Yeah, it looks like it should cause problems. I think it
would be better to have RelationGetPartitionKey() return a copy in the
caller's context of the data of the relcache data, rather than the
relcache data itself, no? Seems like this would not fail if there never
is a CCI between the RelationGetPartitionKey call and the last access of
the returned key struct ... but if this is the explanation, then it's a
pretty brittle setup and we should stop relying on that.

I wonder why do we RelationBuildPartitionDesc and
RelationBuildPartitionKey directly in RelationBuildDesc. Wouldn't it be
better to delay constructing those until the first access to them?

Finally: I don't quite understand why we need two memory contexts there,
one for the partkey and another for the partdesc. Surely one context
for both is sufficient. (And while at it, why not have the whole
RelationData inside the same memory context, so that it can be blown
away without so much retail pfree'ing?)

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#4)
Re: crash with sql language partition support function

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

Ashutosh Bapat wrote:

On Tue, Apr 10, 2018 at 1:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Attached fixes it. It teaches RelationBuildPartitionKey() to use
fmgr_info_cxt and pass rd_partkeycxt to it.

The patch is using partkeycxt and not rd_partkeycxt. Probably a typo
in the mail.

No, it's correct as written, because rd_partkeycxt hasn't been set yet.

Good point. Yeah, it looks like it should cause problems. I think it
would be better to have RelationGetPartitionKey() return a copy in the
caller's context of the data of the relcache data, rather than the
relcache data itself, no? Seems like this would not fail if there never
is a CCI between the RelationGetPartitionKey call and the last access of
the returned key struct ... but if this is the explanation, then it's a
pretty brittle setup and we should stop relying on that.

Yeah, all of the callers of RelationGetPartitionKey seem to assume that
the pointer they get is guaranteed valid and will stay so forever.
This is pretty dangerous, independently of the bug Amit mentions.

However, I'm not sure that copy-on-read is a good solution here, because
of exactly the point at stake that the FmgrInfos may have infrastructure.
We don't have a way to copy that, and even if we did, copying on every
reference would be really expensive.

We might try to make this work like the relcache infrastructure for
indexes, which also contains FmgrInfos. However, in the index case
we may safely assume that that stuff never changes for the life of the
index. I'm afraid that's not true for the partitioning data is it?

How much does it actually buy us to store FmgrInfos here rather than,
say, function OIDs? If we backed off to that, then the data structure
might be simple enough that copy-on-read would work.

Otherwise we might need some kind of refcount mechanism. We built one
for domain constraints in the typcache, and it's not horrid, but it is a
fair amount of code.

Finally: I don't quite understand why we need two memory contexts there,
one for the partkey and another for the partdesc. Surely one context
for both is sufficient.

It'd only matter if there were a reason to delete/rebuild one but not the
other within a particular relcache entry, which probably there isn't.
So using one context for both would likely be a bit simpler and more
efficient.

BTW, another thing in the same general area that I was planning to get
on the warpath about sooner or later is that the code managing
rd_partcheck is really cruddy. It spews a lot of random data structure
into the CacheMemoryContext with no way to release it at relcache inval,
resulting in a session-lifespan memory leak. (pfree'ing just the List
header, as RelationDestroyRelation does, is laughably inadequate.)
Perhaps that could be fixed by likewise storing it in a sub-context
used for partition information.

regards, tom lane

#6Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Tom Lane (#5)
1 attachment(s)
Re: crash with sql language partition support function

Thanks for the comments.

On 2018/04/11 0:27, Tom Lane wrote:

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

Ashutosh Bapat wrote:

On Tue, Apr 10, 2018 at 1:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Attached fixes it. It teaches RelationBuildPartitionKey() to use
fmgr_info_cxt and pass rd_partkeycxt to it.

The patch is using partkeycxt and not rd_partkeycxt. Probably a typo
in the mail.

No, it's correct as written, because rd_partkeycxt hasn't been set yet.

Yeah. partkeycxt will be assigned to rd_partkecxt later in the function,
but I guess Ashutosh seems to already be aware of that, given this discussion.

Good point. Yeah, it looks like it should cause problems. I think it
would be better to have RelationGetPartitionKey() return a copy in the
caller's context of the data of the relcache data, rather than the
relcache data itself, no? Seems like this would not fail if there never
is a CCI between the RelationGetPartitionKey call and the last access of
the returned key struct ... but if this is the explanation, then it's a
pretty brittle setup and we should stop relying on that.

Yeah, all of the callers of RelationGetPartitionKey seem to assume that
the pointer they get is guaranteed valid and will stay so forever.
This is pretty dangerous, independently of the bug Amit mentions.

However, I'm not sure that copy-on-read is a good solution here, because
of exactly the point at stake that the FmgrInfos may have infrastructure.
We don't have a way to copy that, and even if we did, copying on every
reference would be really expensive.

We might try to make this work like the relcache infrastructure for
indexes, which also contains FmgrInfos. However, in the index case
we may safely assume that that stuff never changes for the life of the
index. I'm afraid that's not true for the partitioning data is it?

How much does it actually buy us to store FmgrInfos here rather than,
say, function OIDs? If we backed off to that, then the data structure
might be simple enough that copy-on-read would work.

I'm thinking of rearranging this along the lines of rd_supportinfo
handling for indexes, which index AMs use index_procinfo to access.

Otherwise we might need some kind of refcount mechanism. We built one
for domain constraints in the typcache, and it's not horrid, but it is a
fair amount of code.

Finally: I don't quite understand why we need two memory contexts there,
one for the partkey and another for the partdesc. Surely one context
for both is sufficient.

It'd only matter if there were a reason to delete/rebuild one but not the
other within a particular relcache entry, which probably there isn't.
So using one context for both would likely be a bit simpler and more
efficient.

While the partdesc may change due to additiona/removal of partitions,
partkey never changes. So, while we always "keep" partkey, we may
delete/rebuild partdesc if it has changed.

BTW, another thing in the same general area that I was planning to get
on the warpath about sooner or later is that the code managing
rd_partcheck is really cruddy. It spews a lot of random data structure
into the CacheMemoryContext with no way to release it at relcache inval,
resulting in a session-lifespan memory leak. (pfree'ing just the List
header, as RelationDestroyRelation does, is laughably inadequate.)

Indeed it is. It should've been list_free_deep() I suppose.

Perhaps that could be fixed by likewise storing it in a sub-context
used for partition information.

We can do that, but note that the rd_partcheck is set for "partitions",
whereas other things we're talking about are set for partitioned table
parents. But I guess it doesn't matter much.

Anyway, after reading your replies, I thought of taking a stab at unifying
the partitioning information that's cached by relcache.c.

First thing toward that I did was to unify rd_partkeycxt and rd_pdcxt into
one rd_partcxt. Instead of having RelationBuildPartitionKey and
RelationBuildPartitionDesc that build rd_partkey and rd_partdesc, resp.,
now there is just RelationBuildPartitionInfo that builds both PartitionKey
and PartitionDesc, all under rd_partcxt. That means I moved a bunch of
code from catalog/partition.c to cache/relcache.c.

Second, I made all the places that want to hang on to information from
either the PartitionKey or PartitionDesc to make its copy first. Most
such places already do most of the copying, I just modified the way it's
done. Before doing that, I replaced the FmgrInfo array in PartitionKey
with one containing OIDs. FmgrInfo array is now cached outside in
rd_partsupfuncinfo, which is accessed using partition_getprocinfo() (which
functions like index_getprocinfo) by various callers.

Then, because we now have a policy of users making a copy of partitioning
information before it can be hanged on to, the keep_* logic in
RelationClearRelation() to preserve where rd_partkey and rd_partdesc point
to is no longer needed, so removed it.

Because different callers may need only certain information from the
relcache (or really a copy of it), there are different
RelationGetPartition* functions that return a copy of the requested
information. For example, if a certain caller wants just the number of
partitions, then use RelationGetPartitionCount which returns
rd_partdesc->nparts. If want OIDs, use RelationGetPartitionOids. Callers
like the planner, executor that want to copy *all* information use
RelationGetPartitionInfo which returns a PartitionInfo that contains
fields to contain copy of rd_partkey, rd_partdesc->oids, and
rd_partdesc->boundinfo.

Then finally, the expression tree contained in rd_partcheck now uses
memory allocated in the same rd_partcxt.

Attached find the patch. Since this started as a bugfix that will need to
be back-patched to PG10, I wonder if I need to write a separate patch to
adopt this (kind of big) cleanup/reshuffling for PG 10 code.

Thanks,
Amit

Attachments:

v1-0001-Overhaul-partition-information-caching.patchtext/plain; charset=UTF-8; name=v1-0001-Overhaul-partition-information-caching.patchDownload
From 4de902b93f14e391e4960c5ad0703a6e8cc14d59 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 10 Apr 2018 15:38:19 +0900
Subject: [PATCH v1] Overhaul partition information caching

This started out as a bugfix patch to set the partsupfunc FmgrInfo's
fn_mcxt correctly after discovering that not doing so would cause crash
when partsupfunc contained user-defined SQL functions.
---
 src/backend/catalog/heap.c             |    2 +-
 src/backend/catalog/partition.c        | 1248 +++---------------------------
 src/backend/catalog/pg_constraint.c    |    8 +-
 src/backend/commands/indexcmds.c       |    8 +-
 src/backend/commands/tablecmds.c       |   75 +-
 src/backend/commands/trigger.c         |   22 +-
 src/backend/executor/execPartition.c   |  155 +++-
 src/backend/optimizer/prep/prepunion.c |   13 +-
 src/backend/optimizer/util/plancat.c   |   75 +-
 src/backend/parser/parse_utilcmd.c     |    4 +-
 src/backend/partitioning/partprune.c   |   88 ++-
 src/backend/utils/cache/relcache.c     | 1331 +++++++++++++++++++++++++++++---
 src/include/catalog/partition.h        |   41 +-
 src/include/executor/execPartition.h   |   12 +-
 src/include/nodes/relation.h           |    2 +-
 src/include/partitioning/partbounds.h  |   16 +-
 src/include/partitioning/partprune.h   |    2 +-
 src/include/utils/rel.h                |   37 +-
 18 files changed, 1638 insertions(+), 1501 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8f329a6299..ddd0fa25fc 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -3472,7 +3472,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	 * relcache entry for that partition every time a partition is added or
 	 * removed.
 	 */
-	defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(parent));
+	defaultPartOid = RelationGetDefaultPartitionOid(parent);
 	if (OidIsValid(defaultPartOid))
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 0f5932feee..19d7519f20 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -55,15 +55,9 @@
 #include "utils/ruleutils.h"
 #include "utils/syscache.h"
 
-
 static Oid	get_partition_parent_worker(Relation inhRel, Oid relid);
 static void get_partition_ancestors_worker(Relation inhRel, Oid relid,
 							   List **ancestors);
-static int32 qsort_partition_hbound_cmp(const void *a, const void *b);
-static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
-							   void *arg);
-static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
-						   void *arg);
 
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
@@ -80,764 +74,6 @@ static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
 				   bool for_default);
 static List *get_range_nulltest(PartitionKey key);
-static List *generate_partition_qual(Relation rel);
-
-static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
-					 List *datums, bool lower);
-static int32 partition_hbound_cmp(int modulus1, int remainder1, int modulus2,
-					 int remainder2);
-static int32 partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
-					 Oid *partcollation, Datum *datums1,
-					 PartitionRangeDatumKind *kind1, bool lower1,
-					 PartitionRangeBound *b2);
-
-static int	get_partition_bound_num_indexes(PartitionBoundInfo b);
-
-
-/*
- * RelationBuildPartitionDesc
- *		Form rel's partition descriptor
- *
- * Not flushed from the cache by RelationClearRelation() unless changed because
- * of addition or removal of partition.
- */
-void
-RelationBuildPartitionDesc(Relation rel)
-{
-	List	   *inhoids,
-			   *partoids;
-	Oid		   *oids = NULL;
-	List	   *boundspecs = NIL;
-	ListCell   *cell;
-	int			i,
-				nparts;
-	PartitionKey key = RelationGetPartitionKey(rel);
-	PartitionDesc result;
-	MemoryContext oldcxt;
-
-	int			ndatums = 0;
-	int			default_index = -1;
-
-	/* Hash partitioning specific */
-	PartitionHashBound **hbounds = NULL;
-
-	/* List partitioning specific */
-	PartitionListValue **all_values = NULL;
-	int			null_index = -1;
-
-	/* Range partitioning specific */
-	PartitionRangeBound **rbounds = NULL;
-
-	/* Get partition oids from pg_inherits */
-	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
-
-	/* Collect bound spec nodes in a list */
-	i = 0;
-	partoids = NIL;
-	foreach(cell, inhoids)
-	{
-		Oid			inhrelid = lfirst_oid(cell);
-		HeapTuple	tuple;
-		Datum		datum;
-		bool		isnull;
-		Node	   *boundspec;
-
-		tuple = SearchSysCache1(RELOID, inhrelid);
-		if (!HeapTupleIsValid(tuple))
-			elog(ERROR, "cache lookup failed for relation %u", inhrelid);
-
-		/*
-		 * It is possible that the pg_class tuple of a partition has not been
-		 * updated yet to set its relpartbound field.  The only case where
-		 * this happens is when we open the parent relation to check using its
-		 * partition descriptor that a new partition's bound does not overlap
-		 * some existing partition.
-		 */
-		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
-		{
-			ReleaseSysCache(tuple);
-			continue;
-		}
-
-		datum = SysCacheGetAttr(RELOID, tuple,
-								Anum_pg_class_relpartbound,
-								&isnull);
-		Assert(!isnull);
-		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
-
-		/*
-		 * Sanity check: If the PartitionBoundSpec says this is the default
-		 * partition, its OID should correspond to whatever's stored in
-		 * pg_partitioned_table.partdefid; if not, the catalog is corrupt.
-		 */
-		if (castNode(PartitionBoundSpec, boundspec)->is_default)
-		{
-			Oid			partdefid;
-
-			partdefid = get_default_partition_oid(RelationGetRelid(rel));
-			if (partdefid != inhrelid)
-				elog(ERROR, "expected partdefid %u, but got %u",
-					 inhrelid, partdefid);
-		}
-
-		boundspecs = lappend(boundspecs, boundspec);
-		partoids = lappend_oid(partoids, inhrelid);
-		ReleaseSysCache(tuple);
-	}
-
-	nparts = list_length(partoids);
-
-	if (nparts > 0)
-	{
-		oids = (Oid *) palloc(nparts * sizeof(Oid));
-		i = 0;
-		foreach(cell, partoids)
-			oids[i++] = lfirst_oid(cell);
-
-		/* Convert from node to the internal representation */
-		if (key->strategy == PARTITION_STRATEGY_HASH)
-		{
-			ndatums = nparts;
-			hbounds = (PartitionHashBound **)
-				palloc(nparts * sizeof(PartitionHashBound *));
-
-			i = 0;
-			foreach(cell, boundspecs)
-			{
-				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
-													lfirst(cell));
-
-				if (spec->strategy != PARTITION_STRATEGY_HASH)
-					elog(ERROR, "invalid strategy in partition bound spec");
-
-				hbounds[i] = (PartitionHashBound *)
-					palloc(sizeof(PartitionHashBound));
-
-				hbounds[i]->modulus = spec->modulus;
-				hbounds[i]->remainder = spec->remainder;
-				hbounds[i]->index = i;
-				i++;
-			}
-
-			/* Sort all the bounds in ascending order */
-			qsort(hbounds, nparts, sizeof(PartitionHashBound *),
-				  qsort_partition_hbound_cmp);
-		}
-		else if (key->strategy == PARTITION_STRATEGY_LIST)
-		{
-			List	   *non_null_values = NIL;
-
-			/*
-			 * Create a unified list of non-null values across all partitions.
-			 */
-			i = 0;
-			null_index = -1;
-			foreach(cell, boundspecs)
-			{
-				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
-													lfirst(cell));
-				ListCell   *c;
-
-				if (spec->strategy != PARTITION_STRATEGY_LIST)
-					elog(ERROR, "invalid strategy in partition bound spec");
-
-				/*
-				 * Note the index of the partition bound spec for the default
-				 * partition. There's no datum to add to the list of non-null
-				 * datums for this partition.
-				 */
-				if (spec->is_default)
-				{
-					default_index = i;
-					i++;
-					continue;
-				}
-
-				foreach(c, spec->listdatums)
-				{
-					Const	   *val = castNode(Const, lfirst(c));
-					PartitionListValue *list_value = NULL;
-
-					if (!val->constisnull)
-					{
-						list_value = (PartitionListValue *)
-							palloc0(sizeof(PartitionListValue));
-						list_value->index = i;
-						list_value->value = val->constvalue;
-					}
-					else
-					{
-						/*
-						 * Never put a null into the values array, flag
-						 * instead for the code further down below where we
-						 * construct the actual relcache struct.
-						 */
-						if (null_index != -1)
-							elog(ERROR, "found null more than once");
-						null_index = i;
-					}
-
-					if (list_value)
-						non_null_values = lappend(non_null_values,
-												  list_value);
-				}
-
-				i++;
-			}
-
-			ndatums = list_length(non_null_values);
-
-			/*
-			 * Collect all list values in one array. Alongside the value, we
-			 * also save the index of partition the value comes from.
-			 */
-			all_values = (PartitionListValue **) palloc(ndatums *
-														sizeof(PartitionListValue *));
-			i = 0;
-			foreach(cell, non_null_values)
-			{
-				PartitionListValue *src = lfirst(cell);
-
-				all_values[i] = (PartitionListValue *)
-					palloc(sizeof(PartitionListValue));
-				all_values[i]->value = src->value;
-				all_values[i]->index = src->index;
-				i++;
-			}
-
-			qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
-					  qsort_partition_list_value_cmp, (void *) key);
-		}
-		else if (key->strategy == PARTITION_STRATEGY_RANGE)
-		{
-			int			k;
-			PartitionRangeBound **all_bounds,
-					   *prev;
-
-			all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
-														  sizeof(PartitionRangeBound *));
-
-			/*
-			 * Create a unified list of range bounds across all the
-			 * partitions.
-			 */
-			i = ndatums = 0;
-			foreach(cell, boundspecs)
-			{
-				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
-													lfirst(cell));
-				PartitionRangeBound *lower,
-						   *upper;
-
-				if (spec->strategy != PARTITION_STRATEGY_RANGE)
-					elog(ERROR, "invalid strategy in partition bound spec");
-
-				/*
-				 * Note the index of the partition bound spec for the default
-				 * partition. There's no datum to add to the allbounds array
-				 * for this partition.
-				 */
-				if (spec->is_default)
-				{
-					default_index = i++;
-					continue;
-				}
-
-				lower = make_one_range_bound(key, i, spec->lowerdatums,
-											 true);
-				upper = make_one_range_bound(key, i, spec->upperdatums,
-											 false);
-				all_bounds[ndatums++] = lower;
-				all_bounds[ndatums++] = upper;
-				i++;
-			}
-
-			Assert(ndatums == nparts * 2 ||
-				   (default_index != -1 && ndatums == (nparts - 1) * 2));
-
-			/* Sort all the bounds in ascending order */
-			qsort_arg(all_bounds, ndatums,
-					  sizeof(PartitionRangeBound *),
-					  qsort_partition_rbound_cmp,
-					  (void *) key);
-
-			/* Save distinct bounds from all_bounds into rbounds. */
-			rbounds = (PartitionRangeBound **)
-				palloc(ndatums * sizeof(PartitionRangeBound *));
-			k = 0;
-			prev = NULL;
-			for (i = 0; i < ndatums; i++)
-			{
-				PartitionRangeBound *cur = all_bounds[i];
-				bool		is_distinct = false;
-				int			j;
-
-				/* Is the current bound distinct from the previous one? */
-				for (j = 0; j < key->partnatts; j++)
-				{
-					Datum		cmpval;
-
-					if (prev == NULL || cur->kind[j] != prev->kind[j])
-					{
-						is_distinct = true;
-						break;
-					}
-
-					/*
-					 * If the bounds are both MINVALUE or MAXVALUE, stop now
-					 * and treat them as equal, since any values after this
-					 * point must be ignored.
-					 */
-					if (cur->kind[j] != PARTITION_RANGE_DATUM_VALUE)
-						break;
-
-					cmpval = FunctionCall2Coll(&key->partsupfunc[j],
-											   key->partcollation[j],
-											   cur->datums[j],
-											   prev->datums[j]);
-					if (DatumGetInt32(cmpval) != 0)
-					{
-						is_distinct = true;
-						break;
-					}
-				}
-
-				/*
-				 * Only if the bound is distinct save it into a temporary
-				 * array i.e. rbounds which is later copied into boundinfo
-				 * datums array.
-				 */
-				if (is_distinct)
-					rbounds[k++] = all_bounds[i];
-
-				prev = cur;
-			}
-
-			/* Update ndatums to hold the count of distinct datums. */
-			ndatums = k;
-		}
-		else
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) key->strategy);
-	}
-
-	/* Now build the actual relcache partition descriptor */
-	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
-										  "partition descriptor",
-										  ALLOCSET_DEFAULT_SIZES);
-	MemoryContextCopyAndSetIdentifier(rel->rd_pdcxt, RelationGetRelationName(rel));
-
-	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
-
-	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
-	result->nparts = nparts;
-	if (nparts > 0)
-	{
-		PartitionBoundInfo boundinfo;
-		int		   *mapping;
-		int			next_index = 0;
-
-		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
-
-		boundinfo = (PartitionBoundInfoData *)
-			palloc0(sizeof(PartitionBoundInfoData));
-		boundinfo->strategy = key->strategy;
-		boundinfo->default_index = -1;
-		boundinfo->ndatums = ndatums;
-		boundinfo->null_index = -1;
-		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
-
-		/* Initialize mapping array with invalid values */
-		mapping = (int *) palloc(sizeof(int) * nparts);
-		for (i = 0; i < nparts; i++)
-			mapping[i] = -1;
-
-		switch (key->strategy)
-		{
-			case PARTITION_STRATEGY_HASH:
-				{
-					/* Modulus are stored in ascending order */
-					int			greatest_modulus = hbounds[ndatums - 1]->modulus;
-
-					boundinfo->indexes = (int *) palloc(greatest_modulus *
-														sizeof(int));
-
-					for (i = 0; i < greatest_modulus; i++)
-						boundinfo->indexes[i] = -1;
-
-					for (i = 0; i < nparts; i++)
-					{
-						int			modulus = hbounds[i]->modulus;
-						int			remainder = hbounds[i]->remainder;
-
-						boundinfo->datums[i] = (Datum *) palloc(2 *
-																sizeof(Datum));
-						boundinfo->datums[i][0] = Int32GetDatum(modulus);
-						boundinfo->datums[i][1] = Int32GetDatum(remainder);
-
-						while (remainder < greatest_modulus)
-						{
-							/* overlap? */
-							Assert(boundinfo->indexes[remainder] == -1);
-							boundinfo->indexes[remainder] = i;
-							remainder += modulus;
-						}
-
-						mapping[hbounds[i]->index] = i;
-						pfree(hbounds[i]);
-					}
-					pfree(hbounds);
-					break;
-				}
-
-			case PARTITION_STRATEGY_LIST:
-				{
-					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
-
-					/*
-					 * Copy values.  Indexes of individual values are mapped
-					 * to canonical values so that they match for any two list
-					 * partitioned tables with same number of partitions and
-					 * same lists per partition.  One way to canonicalize is
-					 * to assign the index in all_values[] of the smallest
-					 * value of each partition, as the index of all of the
-					 * partition's values.
-					 */
-					for (i = 0; i < ndatums; i++)
-					{
-						boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
-						boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
-															key->parttypbyval[0],
-															key->parttyplen[0]);
-
-						/* If the old index has no mapping, assign one */
-						if (mapping[all_values[i]->index] == -1)
-							mapping[all_values[i]->index] = next_index++;
-
-						boundinfo->indexes[i] = mapping[all_values[i]->index];
-					}
-
-					/*
-					 * If null-accepting partition has no mapped index yet,
-					 * assign one.  This could happen if such partition
-					 * accepts only null and hence not covered in the above
-					 * loop which only handled non-null values.
-					 */
-					if (null_index != -1)
-					{
-						Assert(null_index >= 0);
-						if (mapping[null_index] == -1)
-							mapping[null_index] = next_index++;
-						boundinfo->null_index = mapping[null_index];
-					}
-
-					/* Assign mapped index for the default partition. */
-					if (default_index != -1)
-					{
-						/*
-						 * The default partition accepts any value not
-						 * specified in the lists of other partitions, hence
-						 * it should not get mapped index while assigning
-						 * those for non-null datums.
-						 */
-						Assert(default_index >= 0 &&
-							   mapping[default_index] == -1);
-						mapping[default_index] = next_index++;
-						boundinfo->default_index = mapping[default_index];
-					}
-
-					/* All partition must now have a valid mapping */
-					Assert(next_index == nparts);
-					break;
-				}
-
-			case PARTITION_STRATEGY_RANGE:
-				{
-					boundinfo->kind = (PartitionRangeDatumKind **)
-						palloc(ndatums *
-							   sizeof(PartitionRangeDatumKind *));
-					boundinfo->indexes = (int *) palloc((ndatums + 1) *
-														sizeof(int));
-
-					for (i = 0; i < ndatums; i++)
-					{
-						int			j;
-
-						boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
-																sizeof(Datum));
-						boundinfo->kind[i] = (PartitionRangeDatumKind *)
-							palloc(key->partnatts *
-								   sizeof(PartitionRangeDatumKind));
-						for (j = 0; j < key->partnatts; j++)
-						{
-							if (rbounds[i]->kind[j] == PARTITION_RANGE_DATUM_VALUE)
-								boundinfo->datums[i][j] =
-									datumCopy(rbounds[i]->datums[j],
-											  key->parttypbyval[j],
-											  key->parttyplen[j]);
-							boundinfo->kind[i][j] = rbounds[i]->kind[j];
-						}
-
-						/*
-						 * There is no mapping for invalid indexes.
-						 *
-						 * Any lower bounds in the rbounds array have invalid
-						 * indexes assigned, because the values between the
-						 * previous bound (if there is one) and this (lower)
-						 * bound are not part of the range of any existing
-						 * partition.
-						 */
-						if (rbounds[i]->lower)
-							boundinfo->indexes[i] = -1;
-						else
-						{
-							int			orig_index = rbounds[i]->index;
-
-							/* If the old index has no mapping, assign one */
-							if (mapping[orig_index] == -1)
-								mapping[orig_index] = next_index++;
-
-							boundinfo->indexes[i] = mapping[orig_index];
-						}
-					}
-
-					/* Assign mapped index for the default partition. */
-					if (default_index != -1)
-					{
-						Assert(default_index >= 0 && mapping[default_index] == -1);
-						mapping[default_index] = next_index++;
-						boundinfo->default_index = mapping[default_index];
-					}
-					boundinfo->indexes[i] = -1;
-					break;
-				}
-
-			default:
-				elog(ERROR, "unexpected partition strategy: %d",
-					 (int) key->strategy);
-		}
-
-		result->boundinfo = boundinfo;
-
-		/*
-		 * Now assign OIDs from the original array into mapped indexes of the
-		 * result array.  Order of OIDs in the former is defined by the
-		 * catalog scan that retrieved them, whereas that in the latter is
-		 * defined by canonicalized representation of the partition bounds.
-		 */
-		for (i = 0; i < nparts; i++)
-			result->oids[mapping[i]] = oids[i];
-		pfree(mapping);
-	}
-
-	MemoryContextSwitchTo(oldcxt);
-	rel->rd_partdesc = result;
-}
-
-/*
- * Are two partition bound collections logically equal?
- *
- * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
- * This is also useful when b1 and b2 are bound collections of two separate
- * relations, respectively, because PartitionBoundInfo is a canonical
- * representation of partition bounds.
- */
-bool
-partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
-					   PartitionBoundInfo b1, PartitionBoundInfo b2)
-{
-	int			i;
-
-	if (b1->strategy != b2->strategy)
-		return false;
-
-	if (b1->ndatums != b2->ndatums)
-		return false;
-
-	if (b1->null_index != b2->null_index)
-		return false;
-
-	if (b1->default_index != b2->default_index)
-		return false;
-
-	if (b1->strategy == PARTITION_STRATEGY_HASH)
-	{
-		int			greatest_modulus = get_hash_partition_greatest_modulus(b1);
-
-		/*
-		 * If two hash partitioned tables have different greatest moduli,
-		 * their partition schemes don't match.
-		 */
-		if (greatest_modulus != get_hash_partition_greatest_modulus(b2))
-			return false;
-
-		/*
-		 * We arrange the partitions in the ascending order of their modulus
-		 * and remainders.  Also every modulus is factor of next larger
-		 * modulus.  Therefore we can safely store index of a given partition
-		 * in indexes array at remainder of that partition.  Also entries at
-		 * (remainder + N * modulus) positions in indexes array are all same
-		 * for (modulus, remainder) specification for any partition.  Thus
-		 * datums array from both the given bounds are same, if and only if
-		 * their indexes array will be same.  So, it suffices to compare
-		 * indexes array.
-		 */
-		for (i = 0; i < greatest_modulus; i++)
-			if (b1->indexes[i] != b2->indexes[i])
-				return false;
-
-#ifdef USE_ASSERT_CHECKING
-
-		/*
-		 * Nonetheless make sure that the bounds are indeed same when the
-		 * indexes match.  Hash partition bound stores modulus and remainder
-		 * at b1->datums[i][0] and b1->datums[i][1] position respectively.
-		 */
-		for (i = 0; i < b1->ndatums; i++)
-			Assert((b1->datums[i][0] == b2->datums[i][0] &&
-					b1->datums[i][1] == b2->datums[i][1]));
-#endif
-	}
-	else
-	{
-		for (i = 0; i < b1->ndatums; i++)
-		{
-			int			j;
-
-			for (j = 0; j < partnatts; j++)
-			{
-				/* For range partitions, the bounds might not be finite. */
-				if (b1->kind != NULL)
-				{
-					/* The different kinds of bound all differ from each other */
-					if (b1->kind[i][j] != b2->kind[i][j])
-						return false;
-
-					/*
-					 * Non-finite bounds are equal without further
-					 * examination.
-					 */
-					if (b1->kind[i][j] != PARTITION_RANGE_DATUM_VALUE)
-						continue;
-				}
-
-				/*
-				 * Compare the actual values. Note that it would be both
-				 * incorrect and unsafe to invoke the comparison operator
-				 * derived from the partitioning specification here.  It would
-				 * be incorrect because we want the relcache entry to be
-				 * updated for ANY change to the partition bounds, not just
-				 * those that the partitioning operator thinks are
-				 * significant.  It would be unsafe because we might reach
-				 * this code in the context of an aborted transaction, and an
-				 * arbitrary partitioning operator might not be safe in that
-				 * 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;
-			}
-
-			if (b1->indexes[i] != b2->indexes[i])
-				return false;
-		}
-
-		/* There are ndatums+1 indexes in case of range partitions */
-		if (b1->strategy == PARTITION_STRATEGY_RANGE &&
-			b1->indexes[i] != b2->indexes[i])
-			return false;
-	}
-	return true;
-}
-
-/*
- * Return a copy of given PartitionBoundInfo structure. The data types of bounds
- * are described by given partition key specification.
- */
-PartitionBoundInfo
-partition_bounds_copy(PartitionBoundInfo src,
-					  PartitionKey key)
-{
-	PartitionBoundInfo dest;
-	int			i;
-	int			ndatums;
-	int			partnatts;
-	int			num_indexes;
-
-	dest = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
-
-	dest->strategy = src->strategy;
-	ndatums = dest->ndatums = src->ndatums;
-	partnatts = key->partnatts;
-
-	num_indexes = get_partition_bound_num_indexes(src);
-
-	/* List partitioned tables have only a single partition key. */
-	Assert(key->strategy != PARTITION_STRATEGY_LIST || partnatts == 1);
-
-	dest->datums = (Datum **) palloc(sizeof(Datum *) * ndatums);
-
-	if (src->kind != NULL)
-	{
-		dest->kind = (PartitionRangeDatumKind **) palloc(ndatums *
-														 sizeof(PartitionRangeDatumKind *));
-		for (i = 0; i < ndatums; i++)
-		{
-			dest->kind[i] = (PartitionRangeDatumKind *) palloc(partnatts *
-															   sizeof(PartitionRangeDatumKind));
-
-			memcpy(dest->kind[i], src->kind[i],
-				   sizeof(PartitionRangeDatumKind) * key->partnatts);
-		}
-	}
-	else
-		dest->kind = NULL;
-
-	for (i = 0; i < ndatums; i++)
-	{
-		int			j;
-
-		/*
-		 * For a corresponding to hash partition, datums array will have two
-		 * elements - modulus and remainder.
-		 */
-		bool		hash_part = (key->strategy == PARTITION_STRATEGY_HASH);
-		int			natts = hash_part ? 2 : partnatts;
-
-		dest->datums[i] = (Datum *) palloc(sizeof(Datum) * natts);
-
-		for (j = 0; j < natts; j++)
-		{
-			bool		byval;
-			int			typlen;
-
-			if (hash_part)
-			{
-				typlen = sizeof(int32); /* Always int4 */
-				byval = true;	/* int4 is pass-by-value */
-			}
-			else
-			{
-				byval = key->parttypbyval[j];
-				typlen = key->parttyplen[j];
-			}
-
-			if (dest->kind == NULL ||
-				dest->kind[i][j] == PARTITION_RANGE_DATUM_VALUE)
-				dest->datums[i][j] = datumCopy(src->datums[i][j],
-											   byval, typlen);
-		}
-	}
-
-	dest->indexes = (int *) palloc(sizeof(int) * num_indexes);
-	memcpy(dest->indexes, src->indexes, sizeof(int) * num_indexes);
-
-	dest->null_index = src->null_index;
-	dest->default_index = src->default_index;
-
-	return dest;
-}
 
 /*
  * check_new_partition_bound
@@ -849,9 +85,9 @@ void
 check_new_partition_bound(char *relname, Relation parent,
 						  PartitionBoundSpec *spec)
 {
-	PartitionKey key = RelationGetPartitionKey(parent);
-	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
-	PartitionBoundInfo boundinfo = partdesc->boundinfo;
+	PartitionInfo  *partinfo = RelationGetPartitionInfo(parent);
+	PartitionKey	key = partinfo->key;
+	PartitionBoundInfo boundinfo = partinfo->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -865,7 +101,7 @@ check_new_partition_bound(char *relname, Relation parent,
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
-						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
+						relname, get_rel_name(partinfo->oids[boundinfo->default_index])),
 				 parser_errposition(pstate, spec->location)));
 	}
 
@@ -876,9 +112,8 @@ check_new_partition_bound(char *relname, Relation parent,
 				Assert(spec->strategy == PARTITION_STRATEGY_HASH);
 				Assert(spec->remainder >= 0 && spec->remainder < spec->modulus);
 
-				if (partdesc->nparts > 0)
+				if (partinfo->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					Datum	  **datums = boundinfo->datums;
 					int			ndatums = boundinfo->ndatums;
 					int			greatest_modulus;
@@ -956,9 +191,16 @@ check_new_partition_bound(char *relname, Relation parent,
 
 		case PARTITION_STRATEGY_LIST:
 			{
+				FmgrInfo partsupfuncinfo;
+
 				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
 
-				if (partdesc->nparts > 0)
+				/* Get partsupfunc FmgrInfo for the only key. */
+				fmgr_info_copy(&partsupfuncinfo,
+							   partition_getprocinfo(parent, key, 0),
+							   CurrentMemoryContext);
+
+				if (partinfo->nparts > 0)
 				{
 					ListCell   *cell;
 
@@ -977,7 +219,7 @@ check_new_partition_bound(char *relname, Relation parent,
 							int			offset;
 							bool		equal;
 
-							offset = partition_list_bsearch(key->partsupfunc,
+							offset = partition_list_bsearch(&partsupfuncinfo,
 														key->partcollation,
 															boundinfo,
 															val->constvalue,
@@ -1005,6 +247,16 @@ check_new_partition_bound(char *relname, Relation parent,
 			{
 				PartitionRangeBound *lower,
 						   *upper;
+				FmgrInfo partsupfuncinfo[PARTITION_MAX_KEYS];
+				int		i;
+
+				/* Get partsupfunc FmgrInfo's. */
+				for (i = 0; i < key->partnatts; i++)
+				{
+					fmgr_info_copy(&partsupfuncinfo[i],
+								   partition_getprocinfo(parent, key, i),
+								   CurrentMemoryContext);
+				}
 
 				Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 				lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
@@ -1014,7 +266,7 @@ check_new_partition_bound(char *relname, Relation parent,
 				 * First check if the resulting range would be empty with
 				 * specified lower and upper bounds
 				 */
-				if (partition_rbound_cmp(key->partnatts, key->partsupfunc,
+				if (partition_rbound_cmp(key->partnatts, partsupfuncinfo,
 										 key->partcollation, lower->datums,
 										 lower->kind, true, upper) >= 0)
 				{
@@ -1028,9 +280,8 @@ check_new_partition_bound(char *relname, Relation parent,
 							 parser_errposition(pstate, spec->location)));
 				}
 
-				if (partdesc->nparts > 0)
+				if (partinfo->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					int			offset;
 					bool		equal;
 
@@ -1055,7 +306,7 @@ check_new_partition_bound(char *relname, Relation parent,
 					 * at the end.
 					 */
 					offset = partition_range_bsearch(key->partnatts,
-													 key->partsupfunc,
+													 partsupfuncinfo,
 													 key->partcollation,
 													 boundinfo, lower,
 													 &equal);
@@ -1080,7 +331,7 @@ check_new_partition_bound(char *relname, Relation parent,
 							is_lower = (boundinfo->indexes[offset + 1] == -1);
 
 							cmpval = partition_rbound_cmp(key->partnatts,
-														  key->partsupfunc,
+														  partsupfuncinfo,
 														  key->partcollation,
 														  datums, kind,
 														  is_lower, upper);
@@ -1121,7 +372,7 @@ check_new_partition_bound(char *relname, Relation parent,
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("partition \"%s\" would overlap partition \"%s\"",
-						relname, get_rel_name(partdesc->oids[with])),
+						relname, get_rel_name(partinfo->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
 }
@@ -1394,7 +645,7 @@ List *
 get_qual_from_partbound(Relation rel, Relation parent,
 						PartitionBoundSpec *spec)
 {
-	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionKey	key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
 
 	Assert(key != NULL);
@@ -1470,55 +721,6 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 }
 
 /*
- * RelationGetPartitionQual
- *
- * Returns a list of partition quals
- */
-List *
-RelationGetPartitionQual(Relation rel)
-{
-	/* Quick exit */
-	if (!rel->rd_rel->relispartition)
-		return NIL;
-
-	return generate_partition_qual(rel);
-}
-
-/*
- * get_partition_qual_relid
- *
- * Returns an expression tree describing the passed-in relation's partition
- * constraint. If there is no partition constraint returns NULL; this can
- * happen if the default partition is the only partition.
- */
-Expr *
-get_partition_qual_relid(Oid relid)
-{
-	Relation	rel = heap_open(relid, AccessShareLock);
-	Expr	   *result = NULL;
-	List	   *and_args;
-
-	/* Do the work only if this relation is a partition. */
-	if (rel->rd_rel->relispartition)
-	{
-		and_args = generate_partition_qual(rel);
-
-		if (and_args == NIL)
-			result = NULL;
-		else if (list_length(and_args) > 1)
-			result = makeBoolExpr(AND_EXPR, and_args, -1);
-		else
-			result = linitial(and_args);
-	}
-
-	/* Keep the lock. */
-	heap_close(rel, NoLock);
-
-	return result;
-}
-
-
-/*
  * get_partition_operator
  *
  * Return oid of the operator of given strategy for a given partition key
@@ -1690,7 +892,7 @@ make_partition_op_expr(PartitionKey key, int keynum,
 static List *
 get_qual_for_hash(Relation parent, PartitionBoundSpec *spec)
 {
-	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionKey	key = RelationGetPartitionKey(parent);
 	FuncExpr   *fexpr;
 	Node	   *relidConst;
 	Node	   *modulusConst;
@@ -1773,7 +975,8 @@ get_qual_for_hash(Relation parent, PartitionBoundSpec *spec)
 static List *
 get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
-	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionInfo  *partinfo = RelationGetPartitionInfo(parent);
+	PartitionKey	key = partinfo->key;
 	List	   *result;
 	Expr	   *keyCol;
 	Expr	   *opexpr;
@@ -1808,8 +1011,7 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 	{
 		int			i;
 		int			ndatums = 0;
-		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
-		PartitionBoundInfo boundinfo = pdesc->boundinfo;
+		PartitionBoundInfo boundinfo = partinfo->boundinfo;
 
 		if (boundinfo)
 		{
@@ -2092,7 +1294,7 @@ get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
 				j;
 	PartitionRangeDatum *ldatum,
 			   *udatum;
-	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionKey	key = RelationGetPartitionKey(parent);
 	Expr	   *keyCol;
 	Const	   *lower_val,
 			   *upper_val;
@@ -2108,11 +1310,11 @@ get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
 	if (spec->is_default)
 	{
 		List	   *or_expr_args = NIL;
-		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
-		Oid		   *inhoids = pdesc->oids;
-		int			nparts = pdesc->nparts,
+		Oid		   *inhoids = RelationGetPartitionOids(parent);
+		int			nparts = RelationGetPartitionCount(parent),
 					i;
 
+		Assert(inhoids != NULL || nparts == 0);
 		for (i = 0; i < nparts; i++)
 		{
 			Oid			inhrelid = inhoids[i];
@@ -2429,193 +1631,6 @@ get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
 }
 
 /*
- * generate_partition_qual
- *
- * Generate partition predicate from rel's partition bound expression. The
- * function returns a NIL list if there is no predicate.
- *
- * Result expression tree is stored CacheMemoryContext to ensure it survives
- * as long as the relcache entry. But we should be running in a less long-lived
- * working context. To avoid leaking cache memory if this routine fails partway
- * through, we build in working memory and then copy the completed structure
- * into cache memory.
- */
-static List *
-generate_partition_qual(Relation rel)
-{
-	HeapTuple	tuple;
-	MemoryContext oldcxt;
-	Datum		boundDatum;
-	bool		isnull;
-	PartitionBoundSpec *bound;
-	List	   *my_qual = NIL,
-			   *result = NIL;
-	Relation	parent;
-	bool		found_whole_row;
-
-	/* Guard against stack overflow due to overly deep partition tree */
-	check_stack_depth();
-
-	/* Quick copy */
-	if (rel->rd_partcheck != NIL)
-		return copyObject(rel->rd_partcheck);
-
-	/* Grab at least an AccessShareLock on the parent table */
-	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
-					   AccessShareLock);
-
-	/* Get pg_class.relpartbound */
-	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "cache lookup failed for relation %u",
-			 RelationGetRelid(rel));
-
-	boundDatum = SysCacheGetAttr(RELOID, tuple,
-								 Anum_pg_class_relpartbound,
-								 &isnull);
-	if (isnull)					/* should not happen */
-		elog(ERROR, "relation \"%s\" has relpartbound = null",
-			 RelationGetRelationName(rel));
-	bound = castNode(PartitionBoundSpec,
-					 stringToNode(TextDatumGetCString(boundDatum)));
-	ReleaseSysCache(tuple);
-
-	my_qual = get_qual_from_partbound(rel, parent, bound);
-
-	/* Add the parent's quals to the list (if any) */
-	if (parent->rd_rel->relispartition)
-		result = list_concat(generate_partition_qual(parent), my_qual);
-	else
-		result = my_qual;
-
-	/*
-	 * Change Vars to have partition's attnos instead of the parent's. We do
-	 * this after we concatenate the parent's quals, because we want every Var
-	 * in it to bear this relation's attnos. It's safe to assume varno = 1
-	 * here.
-	 */
-	result = map_partition_varattnos(result, 1, rel, parent,
-									 &found_whole_row);
-	/* There can never be a whole-row reference here */
-	if (found_whole_row)
-		elog(ERROR, "unexpected whole-row reference found in partition key");
-
-	/* Save a copy in the relcache */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
-	rel->rd_partcheck = copyObject(result);
-	MemoryContextSwitchTo(oldcxt);
-
-	/* Keep the parent locked until commit */
-	heap_close(parent, NoLock);
-
-	return result;
-}
-
-/*
- * get_partition_for_tuple
- *		Finds partition of relation which accepts the partition key specified
- *		in values and isnull
- *
- * Return value is index of the partition (>= 0 and < partdesc->nparts) if one
- * found or -1 if none found.
- */
-int
-get_partition_for_tuple(Relation relation, Datum *values, bool *isnull)
-{
-	int			bound_offset;
-	int			part_index = -1;
-	PartitionKey key = RelationGetPartitionKey(relation);
-	PartitionDesc partdesc = RelationGetPartitionDesc(relation);
-
-	/* Route as appropriate based on partitioning strategy. */
-	switch (key->strategy)
-	{
-		case PARTITION_STRATEGY_HASH:
-			{
-				PartitionBoundInfo boundinfo = partdesc->boundinfo;
-				int			greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
-				uint64		rowHash = compute_hash_value(key->partnatts,
-														 key->partsupfunc,
-														 values, isnull);
-
-				part_index = boundinfo->indexes[rowHash % greatest_modulus];
-			}
-			break;
-
-		case PARTITION_STRATEGY_LIST:
-			if (isnull[0])
-			{
-				if (partition_bound_accepts_nulls(partdesc->boundinfo))
-					part_index = partdesc->boundinfo->null_index;
-			}
-			else
-			{
-				bool		equal = false;
-
-				bound_offset = partition_list_bsearch(key->partsupfunc,
-													  key->partcollation,
-													  partdesc->boundinfo,
-													  values[0], &equal);
-				if (bound_offset >= 0 && equal)
-					part_index = partdesc->boundinfo->indexes[bound_offset];
-			}
-			break;
-
-		case PARTITION_STRATEGY_RANGE:
-			{
-				bool		equal = false,
-							range_partkey_has_null = false;
-				int			i;
-
-				/*
-				 * No range includes NULL, so this will be accepted by the
-				 * default partition if there is one, and otherwise rejected.
-				 */
-				for (i = 0; i < key->partnatts; i++)
-				{
-					if (isnull[i])
-					{
-						range_partkey_has_null = true;
-						break;
-					}
-				}
-
-				if (!range_partkey_has_null)
-				{
-					bound_offset = partition_range_datum_bsearch(key->partsupfunc,
-																 key->partcollation,
-																 partdesc->boundinfo,
-																 key->partnatts,
-																 values,
-																 &equal);
-
-					/*
-					 * The bound at bound_offset is less than or equal to the
-					 * tuple value, so the bound at offset+1 is the upper
-					 * bound of the partition we're looking for, if there
-					 * actually exists one.
-					 */
-					part_index = partdesc->boundinfo->indexes[bound_offset + 1];
-				}
-			}
-			break;
-
-		default:
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) key->strategy);
-	}
-
-	/*
-	 * part_index < 0 means we failed to find a partition of this parent. Use
-	 * the default partition, if there is one.
-	 */
-	if (part_index < 0)
-		part_index = partdesc->boundinfo->default_index;
-
-	return part_index;
-}
-
-/*
  * Checks if any of the 'attnums' is a partition key attribute for rel
  *
  * Sets *used_in_expr if any of the 'attnums' is found to be referenced in some
@@ -2629,7 +1644,7 @@ bool
 has_partition_attrs(Relation rel, Bitmapset *attnums,
 					bool *used_in_expr)
 {
-	PartitionKey key;
+	PartitionKey	key;
 	int			partnatts;
 	List	   *partexprs;
 	ListCell   *partexprs_item;
@@ -2679,27 +1694,13 @@ has_partition_attrs(Relation rel, Bitmapset *attnums,
 	return false;
 }
 
-/*
- * qsort_partition_hbound_cmp
- *
- * We sort hash bounds by modulus, then by remainder.
- */
-static int32
-qsort_partition_hbound_cmp(const void *a, const void *b)
-{
-	PartitionHashBound *h1 = (*(PartitionHashBound *const *) a);
-	PartitionHashBound *h2 = (*(PartitionHashBound *const *) b);
-
-	return partition_hbound_cmp(h1->modulus, h1->remainder,
-								h2->modulus, h2->remainder);
-}
 
 /*
  * partition_hbound_cmp
  *
  * Compares modulus first, then remainder if modulus are equal.
  */
-static int32
+int32
 partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2)
 {
 	if (modulus1 < modulus2)
@@ -2712,30 +1713,13 @@ partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2)
 }
 
 /*
- * qsort_partition_list_value_cmp
- *
- * Compare two list partition bound datums
- */
-static int32
-qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
-{
-	Datum		val1 = (*(const PartitionListValue **) a)->value,
-				val2 = (*(const PartitionListValue **) b)->value;
-	PartitionKey key = (PartitionKey) arg;
-
-	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
-										   key->partcollation[0],
-										   val1, val2));
-}
-
-/*
  * make_one_range_bound
  *
  * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
  * and a flag telling whether the bound is lower or not.  Made into a function
  * because there are multiple sites that want to use this facility.
  */
-static PartitionRangeBound *
+PartitionRangeBound *
 make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
 {
 	PartitionRangeBound *bound;
@@ -2774,19 +1758,6 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
 	return bound;
 }
 
-/* Used when sorting range bounds across all range partitions */
-static int32
-qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
-{
-	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
-	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
-	PartitionKey key = (PartitionKey) arg;
-
-	return partition_rbound_cmp(key->partnatts, key->partsupfunc,
-								key->partcollation, b1->datums, b1->kind,
-								b1->lower, b2);
-}
-
 /*
  * partition_rbound_cmp
  *
@@ -2804,8 +1775,9 @@ qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
  * structure, which only stores the upper bound of a common boundary between
  * two contiguous partitions.
  */
-static int32
-partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation,
+int32
+partition_rbound_cmp(int partnatts, FmgrInfo *partsupfuncinfo,
+					 Oid *partcollation,
 					 Datum *datums1, PartitionRangeDatumKind *kind1,
 					 bool lower1, PartitionRangeBound *b2)
 {
@@ -2836,7 +1808,7 @@ partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation,
 			 */
 			break;
 
-		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
+		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfuncinfo[i],
 												 partcollation[i],
 												 datums1[i],
 												 datums2[i]));
@@ -2868,7 +1840,7 @@ partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation,
  *
  */
 int32
-partition_rbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation,
+partition_rbound_datum_cmp(FmgrInfo *partsupfuncinfo, Oid *partcollation,
 						   Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
 						   Datum *tuple_datums, int n_tuple_datums)
 {
@@ -2882,7 +1854,7 @@ partition_rbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation,
 		else if (rb_kind[i] == PARTITION_RANGE_DATUM_MAXVALUE)
 			return 1;
 
-		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
+		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfuncinfo[i],
 												 partcollation[i],
 												 rb_datums[i],
 												 tuple_datums[i]));
@@ -2902,7 +1874,7 @@ partition_rbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation,
  * to the input value.
  */
 int
-partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
+partition_list_bsearch(FmgrInfo *partsupfuncinfo, Oid *partcollation,
 					   PartitionBoundInfo boundinfo,
 					   Datum value, bool *is_equal)
 {
@@ -2917,7 +1889,7 @@ partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
 		int32		cmpval;
 
 		mid = (lo + hi + 1) / 2;
-		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
+		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfuncinfo[0],
 												 partcollation[0],
 												 boundinfo->datums[mid][0],
 												 value));
@@ -2945,7 +1917,7 @@ partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
  * to the input range bound
  */
 int
-partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
+partition_range_bsearch(int partnatts, FmgrInfo *partsupfuncinfo,
 						Oid *partcollation,
 						PartitionBoundInfo boundinfo,
 						PartitionRangeBound *probe, bool *is_equal)
@@ -2961,7 +1933,8 @@ partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
 		int32		cmpval;
 
 		mid = (lo + hi + 1) / 2;
-		cmpval = partition_rbound_cmp(partnatts, partsupfunc, partcollation,
+		cmpval = partition_rbound_cmp(partnatts, partsupfuncinfo,
+									  partcollation,
 									  boundinfo->datums[mid],
 									  boundinfo->kind[mid],
 									  (boundinfo->indexes[mid] == -1),
@@ -2990,7 +1963,7 @@ partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
  * to the input tuple.
  */
 int
-partition_range_datum_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
+partition_range_datum_bsearch(FmgrInfo *partsupfuncinfo, Oid *partcollation,
 							  PartitionBoundInfo boundinfo,
 							  int nvalues, Datum *values, bool *is_equal)
 {
@@ -3005,7 +1978,7 @@ partition_range_datum_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
 		int32		cmpval;
 
 		mid = (lo + hi + 1) / 2;
-		cmpval = partition_rbound_datum_cmp(partsupfunc,
+		cmpval = partition_rbound_datum_cmp(partsupfuncinfo,
 											partcollation,
 											boundinfo->datums[mid],
 											boundinfo->kind[mid],
@@ -3068,27 +2041,10 @@ partition_hash_bsearch(PartitionBoundInfo boundinfo,
 }
 
 /*
- * get_default_oid_from_partdesc
- *
- * Given a partition descriptor, return the OID of the default partition, if
- * one exists; else, return InvalidOid.
- */
-Oid
-get_default_oid_from_partdesc(PartitionDesc partdesc)
-{
-	if (partdesc && partdesc->boundinfo &&
-		partition_bound_has_default(partdesc->boundinfo))
-		return partdesc->oids[partdesc->boundinfo->default_index];
-
-	return InvalidOid;
-}
-
-/*
  * get_default_partition_oid
  *
  * Given a relation OID, return the OID of the default partition, if one
- * exists.  Use get_default_oid_from_partdesc where possible, for
- * efficiency.
+ * exists.  Use get_default_partition_oid where possible, for efficiency.
  */
 Oid
 get_default_partition_oid(Oid parentId)
@@ -3171,46 +2127,6 @@ get_proposed_default_constraint(List *new_part_constraints)
 }
 
 /*
- * get_partition_bound_num_indexes
- *
- * Returns the number of the entries in the partition bound indexes array.
- */
-static int
-get_partition_bound_num_indexes(PartitionBoundInfo bound)
-{
-	int			num_indexes;
-
-	Assert(bound);
-
-	switch (bound->strategy)
-	{
-		case PARTITION_STRATEGY_HASH:
-
-			/*
-			 * The number of the entries in the indexes array is same as the
-			 * greatest modulus.
-			 */
-			num_indexes = get_hash_partition_greatest_modulus(bound);
-			break;
-
-		case PARTITION_STRATEGY_LIST:
-			num_indexes = bound->ndatums;
-			break;
-
-		case PARTITION_STRATEGY_RANGE:
-			/* Range partitioned table has an extra index. */
-			num_indexes = bound->ndatums + 1;
-			break;
-
-		default:
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) bound->strategy);
-	}
-
-	return num_indexes;
-}
-
-/*
  * get_hash_partition_greatest_modulus
  *
  * Returns the greatest modulus of the hash partition bound. The greatest
@@ -3233,7 +2149,7 @@ get_hash_partition_greatest_modulus(PartitionBoundInfo bound)
  * Compute the hash value for given not null partition key values.
  */
 uint64
-compute_hash_value(int partnatts, FmgrInfo *partsupfunc,
+compute_hash_value(int partnatts, FmgrInfo *partsupfuncinfo,
 				   Datum *values, bool *isnull)
 {
 	int			i;
@@ -3246,14 +2162,14 @@ compute_hash_value(int partnatts, FmgrInfo *partsupfunc,
 		{
 			Datum		hash;
 
-			Assert(OidIsValid(partsupfunc[i].fn_oid));
+			Assert(OidIsValid(partsupfuncinfo[i].fn_oid));
 
 			/*
 			 * Compute hash for each datum value by calling respective
 			 * datatype-specific hash functions of each partition key
 			 * attribute.
 			 */
-			hash = FunctionCall2(&partsupfunc[i], values[i], seed);
+			hash = FunctionCall2(&partsupfuncinfo[i], values[i], seed);
 
 			/* Form a single 64-bit hash value */
 			rowHash = hash_combine64(rowHash, DatumGetUInt64(hash));
@@ -3288,7 +2204,7 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 		int16		variadic_typlen;
 		bool		variadic_typbyval;
 		char		variadic_typalign;
-		FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
+		FmgrInfo	partsupfuncinfo[PARTITION_MAX_KEYS];
 	} ColumnsHashData;
 	Oid			parentId;
 	int			modulus;
@@ -3325,23 +2241,39 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 	if (my_extra == NULL || my_extra->relid != parentId)
 	{
 		Relation	parent;
-		PartitionKey key;
+		PartitionKey	key;
+		FmgrInfo	partsupfuncinfo[PARTITION_MAX_KEYS];
 		int			j;
 
 		/* Open parent relation and fetch partition keyinfo */
 		parent = try_relation_open(parentId, AccessShareLock);
 		if (parent == NULL)
 			PG_RETURN_NULL();
-		key = RelationGetPartitionKey(parent);
 
 		/* Reject parent table that is not hash-partitioned. */
-		if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE ||
-			key->strategy != PARTITION_STRATEGY_HASH)
+		if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("\"%s\" is not a hash partitioned table",
 							get_rel_name(parentId))));
 
+		key = RelationGetPartitionKey(parent);
+		Assert(key != NULL);
+
+		if (key->strategy != PARTITION_STRATEGY_HASH)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("\"%s\" is not a hash partitioned table",
+							get_rel_name(parentId))));
+
+		/* Get partsupfunc FmgrInfo's. */
+		for (j = 0; j < key->partnatts; j++)
+		{
+			fmgr_info_copy(&partsupfuncinfo[j],
+						   partition_getprocinfo(parent, key, j),
+						   CurrentMemoryContext);
+		}
+
 		if (!get_fn_expr_variadic(fcinfo->flinfo))
 		{
 			int			nargs = PG_NARGS() - 3;
@@ -3356,7 +2288,8 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 			/* allocate space for our cache */
 			fcinfo->flinfo->fn_extra =
 				MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
-									   offsetof(ColumnsHashData, partsupfunc) +
+									   offsetof(ColumnsHashData,
+												partsupfuncinfo) +
 									   sizeof(FmgrInfo) * nargs);
 			my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
 			my_extra->relid = parentId;
@@ -3373,8 +2306,8 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 							 errmsg("column %d of the partition key has type \"%s\", but supplied value is of type \"%s\"",
 									j + 1, format_type_be(key->parttypid[j]), format_type_be(argtype))));
 
-				fmgr_info_copy(&my_extra->partsupfunc[j],
-							   &key->partsupfunc[j],
+				fmgr_info_copy(&my_extra->partsupfuncinfo[j],
+							   &partsupfuncinfo[j],
 							   fcinfo->flinfo->fn_mcxt);
 			}
 
@@ -3386,7 +2319,8 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 			/* allocate space for our cache -- just one FmgrInfo in this case */
 			fcinfo->flinfo->fn_extra =
 				MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
-									   offsetof(ColumnsHashData, partsupfunc) +
+									   offsetof(ColumnsHashData,
+												partsupfuncinfo) +
 									   sizeof(FmgrInfo));
 			my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
 			my_extra->relid = parentId;
@@ -3407,8 +2341,8 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 									format_type_be(key->parttypid[j]),
 									format_type_be(my_extra->variadic_type))));
 
-			fmgr_info_copy(&my_extra->partsupfunc[0],
-						   &key->partsupfunc[0],
+			fmgr_info_copy(&my_extra->partsupfuncinfo[0],
+						   &partsupfuncinfo[0],
 						   fcinfo->flinfo->fn_mcxt);
 		}
 
@@ -3437,9 +2371,9 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 			if (PG_ARGISNULL(argno))
 				continue;
 
-			Assert(OidIsValid(my_extra->partsupfunc[i].fn_oid));
+			Assert(OidIsValid(my_extra->partsupfuncinfo[i].fn_oid));
 
-			hash = FunctionCall2(&my_extra->partsupfunc[i],
+			hash = FunctionCall2(&my_extra->partsupfuncinfo[i],
 								 PG_GETARG_DATUM(argno),
 								 seed);
 
@@ -3476,9 +2410,9 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 			if (isnull[i])
 				continue;
 
-			Assert(OidIsValid(my_extra->partsupfunc[0].fn_oid));
+			Assert(OidIsValid(my_extra->partsupfuncinfo[0].fn_oid));
 
-			hash = FunctionCall2(&my_extra->partsupfunc[0],
+			hash = FunctionCall2(&my_extra->partsupfuncinfo[0],
 								 datum[i],
 								 seed);
 
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8ba9890ca6..32386b246f 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -623,12 +623,14 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
 
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDesc	partdesc = RelationGetPartitionDesc(rel);
+		int			nparts = RelationGetPartitionCount(rel);
+		Oid		   *partoids = RelationGetPartitionOids(rel);
 		int			i;
 
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 			CloneForeignKeyConstraints(RelationGetRelid(rel),
-									   partdesc->oids[i],
+									   partoids[i],
 									   cloned);
 	}
 
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 860a60d109..6b159718bb 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -876,15 +876,12 @@ DefineIndex(Oid relationId,
 		 */
 		if (!stmt->relation || stmt->relation->inh)
 		{
-			PartitionDesc partdesc = RelationGetPartitionDesc(rel);
-			int			nparts = partdesc->nparts;
-			Oid		   *part_oids = palloc(sizeof(Oid) * nparts);
+			int			nparts = RelationGetPartitionCount(rel);
+			Oid		   *part_oids = RelationGetPartitionOids(rel);
 			bool		invalidate_parent = false;
 			TupleDesc	parentDesc;
 			Oid		   *opfamOids;
 
-			memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);
-
 			parentDesc = CreateTupleDescCopy(RelationGetDescr(rel));
 			opfamOids = palloc(sizeof(Oid) * numberOfAttributes);
 			for (i = 0; i < numberOfAttributes; i++)
@@ -900,6 +897,7 @@ DefineIndex(Oid relationId,
 			 * If none matches, build a new index by calling ourselves
 			 * recursively with the same options (except for the index name).
 			 */
+			Assert(part_oids != NULL || nparts == 0);
 			for (i = 0; i < nparts; i++)
 			{
 				Oid		childRelid = part_oids[i];
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f8108858ae..b9c9cb82c6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -825,8 +825,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 * the places such that lock parent, lock default partition and then
 		 * lock the partition so as to avoid a deadlock.
 		 */
-		defaultPartOid =
-			get_default_oid_from_partdesc(RelationGetPartitionDesc(parent));
+		defaultPartOid = RelationGetDefaultPartitionOid(parent);
 		if (OidIsValid(defaultPartOid))
 			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
@@ -5860,18 +5859,14 @@ ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
 	 * If the parent is a partitioned table, like check constraints, we do not
 	 * support removing the NOT NULL while partitions exist.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
-
-		Assert(partdesc != NULL);
-		if (partdesc->nparts > 0 && !recurse && !recursing)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot remove constraint from only the partitioned table when partitions exist"),
-					 errhint("Do not specify the ONLY keyword.")));
-	}
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		RelationGetPartitionCount(rel) > 0 && !recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot remove constraint from only the partitioned table when partitions exist"),
+				 errhint("Do not specify the ONLY keyword.")));
 }
+
 static ObjectAddress
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
@@ -6005,16 +6000,12 @@ ATPrepSetNotNull(Relation rel, bool recurse, bool recursing)
 	 * constraints must be added to the child tables.  Complain if requested
 	 * otherwise and partitions exist.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
-
-		if (partdesc && partdesc->nparts > 0 && !recurse && !recursing)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot add constraint to only the partitioned table when partitions exist"),
-					 errhint("Do not specify the ONLY keyword.")));
-	}
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		RelationGetPartitionCount(rel) > 0 && !recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add constraint to only the partitioned table when partitions exist"),
+				 errhint("Do not specify the ONLY keyword.")));
 }
 
 /*
@@ -7695,13 +7686,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 */
 	if (recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDesc partdesc;
+		int			nparts = RelationGetPartitionCount(rel);
+		Oid		   *partoids = RelationGetPartitionOids(rel);
 
-		partdesc = RelationGetPartitionDesc(rel);
-
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
-			Oid			partitionId = partdesc->oids[i];
+			Oid			partitionId = partoids[i];
 			Relation	partition = heap_open(partitionId, lockmode);
 			AlteredTableInfo *childtab;
 			ObjectAddress childAddr;
@@ -13985,10 +13976,12 @@ QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
 	}
 	else if (scanrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(scanrel);
+		int			nparts = RelationGetPartitionCount(scanrel);
+		Oid		   *partoids = RelationGetPartitionOids(scanrel);
 		int			i;
 
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
 			Relation	part_rel;
 			bool		found_whole_row;
@@ -13998,7 +13991,7 @@ QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
 			 * This is the minimum lock we need to prevent concurrent data
 			 * additions.
 			 */
-			part_rel = heap_open(partdesc->oids[i], ShareLock);
+			part_rel = heap_open(partoids[i], ShareLock);
 
 			/*
 			 * Adjust the constraint for scanrel so that it matches this
@@ -14048,8 +14041,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * We must lock the default partition if one exists, because attaching a
 	 * new partition will change its partition constraint.
 	 */
-	defaultPartOid =
-		get_default_oid_from_partdesc(RelationGetPartitionDesc(rel));
+	defaultPartOid = RelationGetDefaultPartitionOid(rel);
 	if (OidIsValid(defaultPartOid))
 		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
@@ -14623,8 +14615,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	 * We must lock the default partition, because detaching this partition
 	 * will change its partition constraint.
 	 */
-	defaultPartOid =
-		get_default_oid_from_partdesc(RelationGetPartitionDesc(rel));
+	defaultPartOid = RelationGetDefaultPartitionOid(rel);
 	if (OidIsValid(defaultPartOid))
 		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
@@ -14824,8 +14815,9 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		AttrNumber *attmap;
 		bool		found;
 		int			i;
-		PartitionDesc partDesc;
-		Oid			constraintOid,
+		int			nparts = RelationGetPartitionCount(parentTbl);
+		Oid		   *partoids = RelationGetPartitionOids(parentTbl),
+					constraintOid,
 					cldConstrId = InvalidOid;
 
 		/*
@@ -14843,11 +14835,11 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 							   RelationGetRelationName(partIdx))));
 
 		/* Make sure it indexes a partition of the other index's table */
-		partDesc = RelationGetPartitionDesc(parentTbl);
 		found = false;
-		for (i = 0; i < partDesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
-			if (partDesc->oids[i] == state.partitionOid)
+			if (partoids[i] == state.partitionOid)
 			{
 				found = true;
 				break;
@@ -14977,6 +14969,7 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
 	int				tuples = 0;
 	HeapTuple		inhTup;
 	bool			updated = false;
+	int				nparts = RelationGetPartitionCount(partedTbl);
 
 	Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
 
@@ -15016,7 +15009,7 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
 	 * If we found as many inherited indexes as the partitioned table has
 	 * partitions, we're good; update pg_index to set indisvalid.
 	 */
-	if (tuples == RelationGetPartitionDesc(partedTbl)->nparts)
+	if (tuples == nparts)
 	{
 		Relation	idxRel;
 		HeapTuple	newtup;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 0d57d46748..f225dc2cc3 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1093,7 +1093,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 */
 	if (partition_recurse)
 	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+		int			nparts = RelationGetPartitionCount(rel);
+		Oid		   *partoids = RelationGetPartitionOids(rel);
 		List	   *idxs = NIL;
 		List	   *childTbls = NIL;
 		ListCell   *l;
@@ -1125,7 +1126,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		oldcxt = MemoryContextSwitchTo(perChildCxt);
 
 		/* Iterate to create the trigger on each existing partition */
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
 			Oid			indexOnChild = InvalidOid;
 			ListCell   *l2;
@@ -1134,14 +1136,14 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 			Node	   *qual;
 			bool		found_whole_row;
 
-			childTbl = heap_open(partdesc->oids[i], ShareRowExclusiveLock);
+			childTbl = heap_open(partoids[i], ShareRowExclusiveLock);
 
 			/* Find which of the child indexes is the one on this partition */
 			if (OidIsValid(indexOid))
 			{
 				forboth(l, idxs, l2, childTbls)
 				{
-					if (lfirst_oid(l2) == partdesc->oids[i])
+					if (lfirst_oid(l2) == partoids[i])
 					{
 						indexOnChild = lfirst_oid(l);
 						break;
@@ -1150,7 +1152,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 				if (!OidIsValid(indexOnChild))
 					elog(ERROR, "failed to find index matching index \"%s\" in partition \"%s\"",
 						 get_rel_name(indexOid),
-						 get_rel_name(partdesc->oids[i]));
+						 get_rel_name(partoids[i]));
 			}
 
 			/*
@@ -1178,7 +1180,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 				elog(ERROR, "unexpected whole-row reference found in trigger WHEN clause");
 
 			CreateTrigger(childStmt, queryString,
-						  partdesc->oids[i], refRelOid,
+						  partoids[i], refRelOid,
 						  InvalidOid, indexOnChild,
 						  funcoid, trigoid, qual,
 						  isInternal, true);
@@ -1861,14 +1863,16 @@ EnableDisableTrigger(Relation rel, const char *tgname,
 			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
 				(TRIGGER_FOR_ROW(oldtrig->tgtype)))
 			{
-				PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+				int		nparts = RelationGetPartitionCount(rel);
+				Oid	   *partoids = RelationGetPartitionOids(rel);
 				int			i;
 
-				for (i = 0; i < partdesc->nparts; i++)
+				Assert(partoids != NULL || nparts == 0);
+				for (i = 0; i < nparts; i++)
 				{
 					Relation	part;
 
-					part = relation_open(partdesc->oids[i], lockmode);
+					part = relation_open(partoids[i], lockmode);
 					EnableDisableTrigger(part, NameStr(oldtrig->tgname),
 										 fires_when, skip_system, lockmode);
 					heap_close(part, NoLock);	/* keep lock till commit */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index d4d54e927a..8ba16ac56b 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -22,6 +22,7 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "partitioning/partbounds.h"
 #include "utils/lsyscache.h"
 #include "utils/rls.h"
 #include "utils/ruleutils.h"
@@ -35,6 +36,8 @@ static void FormPartitionKeyDatum(PartitionDispatch pd,
 					  EState *estate,
 					  Datum *values,
 					  bool *isnull);
+static int get_partition_for_tuple(PartitionDispatch pd, Datum *values,
+					  bool *isnull);
 static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 Datum *values,
 									 bool *isnull,
@@ -212,13 +215,11 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 	parent = pd[0];
 	while (true)
 	{
-		PartitionDesc partdesc;
 		TupleTableSlot *myslot = parent->tupslot;
 		TupleConversionMap *map = parent->tupmap;
 		int			cur_index = -1;
 
 		rel = parent->reldesc;
-		partdesc = RelationGetPartitionDesc(rel);
 
 		/*
 		 * Convert the tuple to this parent's layout so that we can do certain
@@ -249,13 +250,13 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 		 * Nothing for get_partition_for_tuple() to do if there are no
 		 * partitions to begin with.
 		 */
-		if (partdesc->nparts == 0)
+		if (parent->nparts == 0)
 		{
 			result = -1;
 			break;
 		}
 
-		cur_index = get_partition_for_tuple(rel, values, isnull);
+		cur_index = get_partition_for_tuple(parent, values, isnull);
 
 		/*
 		 * cur_index < 0 means we failed to find a partition of this parent.
@@ -995,8 +996,8 @@ get_partition_dispatch_recurse(Relation rel, Relation parent,
 							   List **pds, List **leaf_part_oids)
 {
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	PartitionDesc partdesc = RelationGetPartitionDesc(rel);
-	PartitionKey partkey = RelationGetPartitionKey(rel);
+	PartitionInfo *partinfo = RelationGetPartitionInfo(rel);
+	PartitionKey partkey = partinfo->key;
 	PartitionDispatch pd;
 	int			i;
 
@@ -1008,7 +1009,15 @@ get_partition_dispatch_recurse(Relation rel, Relation parent,
 	pd->reldesc = rel;
 	pd->key = partkey;
 	pd->keystate = NIL;
-	pd->partdesc = partdesc;
+	/* Get partsupfunc FmgrInfo's. */
+	for (i = 0; i < partkey->partnatts; i++)
+	{
+		fmgr_info_copy(&pd->partsupfuncinfo[i],
+					   partition_getprocinfo(rel, partkey, i),
+					   CurrentMemoryContext);
+	}
+	pd->nparts = partinfo->nparts;
+	pd->boundinfo = partinfo->boundinfo;
 	if (parent != NULL)
 	{
 		/*
@@ -1053,10 +1062,10 @@ get_partition_dispatch_recurse(Relation rel, Relation parent,
 	 * the tree.  This value is used to continue the search in the next level
 	 * of the partition tree.
 	 */
-	pd->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
-	for (i = 0; i < partdesc->nparts; i++)
+	pd->indexes = (int *) palloc(partinfo->nparts * sizeof(int));
+	for (i = 0; i < partinfo->nparts; i++)
 	{
-		Oid			partrelid = partdesc->oids[i];
+		Oid			partrelid = partinfo->oids[i];
 
 		if (get_rel_relkind(partrelid) != RELKIND_PARTITIONED_TABLE)
 		{
@@ -1144,6 +1153,110 @@ FormPartitionKeyDatum(PartitionDispatch pd,
 }
 
 /*
+ * get_partition_for_tuple
+ *		Finds partition of relation which accepts the partition key specified
+ *		in values and isnull
+ *
+ * Return value is index of the partition (>= 0 and < partinfo->nparts) if one
+ * found or -1 if none found.
+ */
+static int
+get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull)
+{
+	int			bound_offset;
+	int			part_index = -1;
+	PartitionKey key = pd->key;
+	FmgrInfo   *partsupfuncinfo = pd->partsupfuncinfo;
+	PartitionBoundInfo boundinfo = pd->boundinfo;
+
+	/* Route as appropriate based on partitioning strategy. */
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+			{
+				int			greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
+				uint64		rowHash = compute_hash_value(key->partnatts,
+														 partsupfuncinfo,
+														 values, isnull);
+
+				part_index = boundinfo->indexes[rowHash % greatest_modulus];
+			}
+			break;
+
+		case PARTITION_STRATEGY_LIST:
+			if (isnull[0])
+			{
+				if (partition_bound_accepts_nulls(boundinfo))
+					part_index = boundinfo->null_index;
+			}
+			else
+			{
+				bool		equal = false;
+
+				bound_offset = partition_list_bsearch(partsupfuncinfo,
+													  key->partcollation,
+													  boundinfo,
+													  values[0], &equal);
+				if (bound_offset >= 0 && equal)
+					part_index = boundinfo->indexes[bound_offset];
+			}
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			{
+				bool		equal = false,
+							range_partkey_has_null = false;
+				int			i;
+
+				/*
+				 * No range includes NULL, so this will be accepted by the
+				 * default partition if there is one, and otherwise rejected.
+				 */
+				for (i = 0; i < key->partnatts; i++)
+				{
+					if (isnull[i])
+					{
+						range_partkey_has_null = true;
+						break;
+					}
+				}
+
+				if (!range_partkey_has_null)
+				{
+					bound_offset = partition_range_datum_bsearch(partsupfuncinfo,
+																 key->partcollation,
+																 boundinfo,
+																 key->partnatts,
+																 values,
+																 &equal);
+
+					/*
+					 * The bound at bound_offset is less than or equal to the
+					 * tuple value, so the bound at offset+1 is the upper
+					 * bound of the partition we're looking for, if there
+					 * actually exists one.
+					 */
+					part_index = boundinfo->indexes[bound_offset + 1];
+				}
+			}
+			break;
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) key->strategy);
+	}
+
+	/*
+	 * part_index < 0 means we failed to find a partition of this parent. Use
+	 * the default partition, if there is one.
+	 */
+	if (part_index < 0)
+		part_index = boundinfo->default_index;
+
+	return part_index;
+}
+
+/*
  * ExecBuildSlotPartitionKeyDescription
  *
  * This works very much like BuildIndexValueDescription() and is currently
@@ -1157,7 +1270,8 @@ ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 int maxfieldlen)
 {
 	StringInfoData buf;
-	PartitionKey key = RelationGetPartitionKey(rel);
+	PartitionInfo  *partinfo = RelationGetPartitionInfo(rel);
+	PartitionKey	key = partinfo->key;
 	int			partnatts = get_partition_natts(key);
 	int			i;
 	Oid			relid = RelationGetRelid(rel);
@@ -1405,10 +1519,11 @@ ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 		PartitionPruneInfo *pinfo = (PartitionPruneInfo *) lfirst(lc);
 		PartitionPruningData *pprune = &prunedata[i];
 		PartitionPruneContext *context = &pprune->context;
-		PartitionDesc partdesc;
 		Relation	rel;
-		PartitionKey partkey;
+		PartitionInfo *partinfo;
+		PartitionKey	partkey;
 		int			partnatts;
+		int			j;
 
 		pprune->present_parts = bms_copy(pinfo->present_parts);
 		pprune->subnode_map = palloc(sizeof(int) * pinfo->nparts);
@@ -1429,17 +1544,23 @@ ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 		 */
 		rel = relation_open(pinfo->reloid, NoLock);
 
-		partkey = RelationGetPartitionKey(rel);
-		partdesc = RelationGetPartitionDesc(rel);
+		partinfo = RelationGetPartitionInfo(rel);
+		partkey = partinfo->key;
 
 		context->strategy = partkey->strategy;
 		context->partnatts = partnatts = partkey->partnatts;
 		context->partopfamily = partkey->partopfamily;
 		context->partopcintype = partkey->partopcintype;
 		context->partcollation = partkey->partcollation;
-		context->partsupfunc = partkey->partsupfunc;
+		/* Get partsupfunc FmgrInfo's. */
+		for (j = 0; j < partkey->partnatts; j++)
+		{
+			fmgr_info_copy(&context->partsupfuncinfo[j],
+						   partition_getprocinfo(rel, partkey, j),
+						   CurrentMemoryContext);
+		}
 		context->nparts = pinfo->nparts;
-		context->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey);
+		context->boundinfo = partinfo->boundinfo;
 		context->planstate = planstate;
 		context->safeparams = NULL; /* empty for now */
 
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 8d86e98adc..23ce22a27e 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1584,7 +1584,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	oldrelation = heap_open(parentOID, NoLock);
 
 	/* Scan the inheritance set and expand it */
-	if (RelationGetPartitionDesc(oldrelation) != NULL)
+	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		Assert(rte->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -1675,13 +1675,11 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte,
 	RangeTblEntry *childrte;
 	Index		childRTindex;
 	bool		has_child = false;
-	PartitionDesc partdesc = RelationGetPartitionDesc(parentrel);
+	int			nparts = RelationGetPartitionCount(parentrel);
+	Oid		   *partoids = RelationGetPartitionOids(parentrel);
 
 	check_stack_depth();
 
-	/* A partitioned table should always have a partition descriptor. */
-	Assert(partdesc);
-
 	Assert(parentrte->inh);
 
 	/*
@@ -1700,9 +1698,10 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte,
 									top_parentrc, parentrel,
 									appinfos, &childrte, &childRTindex);
 
-	for (i = 0; i < partdesc->nparts; i++)
+	Assert(partoids != NULL || nparts == 0);
+	for (i = 0; i < nparts; i++)
 	{
-		Oid			childOID = partdesc->oids[i];
+		Oid			childOID = partoids[i];
 		Relation	childrel;
 
 		/* Open rel; we already have required locks */
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 90bb0c2804..b77d8386da 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -70,8 +70,9 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
 static List *get_relation_statistics(RelOptInfo *rel, Relation relation);
 static void set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
 							Relation relation);
-static PartitionScheme find_partition_scheme(PlannerInfo *root, Relation rel);
-static void set_baserel_partition_key_exprs(Relation relation,
+static PartitionScheme find_partition_scheme(PlannerInfo *root, Relation rel,
+							PartitionKey partkey);
+static void set_baserel_partition_key_exprs(PartitionKey partkey,
 								RelOptInfo *rel);
 
 /*
@@ -1873,18 +1874,17 @@ static void
 set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
 							Relation relation)
 {
-	PartitionDesc partdesc;
-	PartitionKey partkey;
+	PartitionInfo *partinfo = RelationGetPartitionInfo(relation);
+	PartitionKey partkey = partinfo->key;
 
 	Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+	Assert(partinfo != NULL && partkey != NULL);
 
-	partdesc = RelationGetPartitionDesc(relation);
-	partkey = RelationGetPartitionKey(relation);
-	rel->part_scheme = find_partition_scheme(root, relation);
-	Assert(partdesc != NULL && rel->part_scheme != NULL);
-	rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey);
-	rel->nparts = partdesc->nparts;
-	set_baserel_partition_key_exprs(relation, rel);
+	rel->part_scheme = find_partition_scheme(root, relation, partkey);
+	Assert(rel->part_scheme != NULL);
+	rel->boundinfo = partinfo->boundinfo;
+	rel->nparts = partinfo->nparts;
+	set_baserel_partition_key_exprs(partkey, rel);
 	rel->partition_qual = RelationGetPartitionQual(relation);
 }
 
@@ -1894,9 +1894,9 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
  * Find or create a PartitionScheme for this Relation.
  */
 static PartitionScheme
-find_partition_scheme(PlannerInfo *root, Relation relation)
+find_partition_scheme(PlannerInfo *root, Relation rel,
+					  PartitionKey partkey)
 {
-	PartitionKey partkey = RelationGetPartitionKey(relation);
 	ListCell   *lc;
 	int			partnatts,
 				i;
@@ -1937,15 +1937,12 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
 
 		/*
 		 * If partopfamily and partopcintype matched, must have the same
-		 * partition comparison functions.  Note that we cannot reliably
-		 * Assert the equality of function structs themselves for they might
-		 * be different across PartitionKey's, so just Assert for the function
-		 * OIDs.
+		 * partition comparison functions.
 		 */
 #ifdef USE_ASSERT_CHECKING
 		for (i = 0; i < partkey->partnatts; i++)
-			Assert(partkey->partsupfunc[i].fn_oid ==
-				   part_scheme->partsupfunc[i].fn_oid);
+			Assert(partkey->partsupfunc[i] ==
+				   part_scheme->partsupfuncinfo[i].fn_oid);
 #endif
 
 		/* Found matching partition scheme. */
@@ -1953,39 +1950,22 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
 	}
 
 	/*
-	 * Did not find matching partition scheme. Create one copying relevant
-	 * information from the relcache. We need to copy the contents of the
-	 * array since the relcache entry may not survive after we have closed the
-	 * relation.
+	 * Did not find matching partition scheme. Create one usinng the
+	 * relevant information copied from the relcache.
 	 */
 	part_scheme = (PartitionScheme) palloc0(sizeof(PartitionSchemeData));
 	part_scheme->strategy = partkey->strategy;
 	part_scheme->partnatts = partkey->partnatts;
-
-	part_scheme->partopfamily = (Oid *) palloc(sizeof(Oid) * partnatts);
-	memcpy(part_scheme->partopfamily, partkey->partopfamily,
-		   sizeof(Oid) * partnatts);
-
-	part_scheme->partopcintype = (Oid *) palloc(sizeof(Oid) * partnatts);
-	memcpy(part_scheme->partopcintype, partkey->partopcintype,
-		   sizeof(Oid) * partnatts);
-
-	part_scheme->partcollation = (Oid *) palloc(sizeof(Oid) * partnatts);
-	memcpy(part_scheme->partcollation, partkey->partcollation,
-		   sizeof(Oid) * partnatts);
-
-	part_scheme->parttyplen = (int16 *) palloc(sizeof(int16) * partnatts);
-	memcpy(part_scheme->parttyplen, partkey->parttyplen,
-		   sizeof(int16) * partnatts);
-
-	part_scheme->parttypbyval = (bool *) palloc(sizeof(bool) * partnatts);
-	memcpy(part_scheme->parttypbyval, partkey->parttypbyval,
-		   sizeof(bool) * partnatts);
-
-	part_scheme->partsupfunc = (FmgrInfo *)
+	part_scheme->partopfamily = partkey->partopfamily;
+	part_scheme->partopcintype = partkey->partopcintype;
+	part_scheme->partcollation = partkey->partcollation;
+	part_scheme->parttyplen = partkey->parttyplen;
+	part_scheme->parttypbyval = partkey->parttypbyval;
+	part_scheme->partsupfuncinfo = (FmgrInfo *)
 		palloc(sizeof(FmgrInfo) * partnatts);
 	for (i = 0; i < partnatts; i++)
-		fmgr_info_copy(&part_scheme->partsupfunc[i], &partkey->partsupfunc[i],
+		fmgr_info_copy(&part_scheme->partsupfuncinfo[i],
+					   partition_getprocinfo(rel, partkey, i),
 					   CurrentMemoryContext);
 
 	/* Add the partitioning scheme to PlannerInfo. */
@@ -2002,10 +1982,9 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
  * nodes.  All Var nodes are restamped with the relid of given relation.
  */
 static void
-set_baserel_partition_key_exprs(Relation relation,
+set_baserel_partition_key_exprs(PartitionKey partkey,
 								RelOptInfo *rel)
 {
-	PartitionKey partkey = RelationGetPartitionKey(relation);
 	int			partnatts;
 	int			cnt;
 	List	  **partexprs;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index f9f9904bad..246e57e1c6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3559,7 +3559,7 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
 	{
 		case RELKIND_PARTITIONED_TABLE:
 			/* transform the partition bound, if any */
-			Assert(RelationGetPartitionKey(parentRel) != NULL);
+			Assert(parentRel->rd_partkey != NULL);
 			if (cmd->bound != NULL)
 				cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
 														 cmd->bound);
@@ -3600,7 +3600,7 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 						PartitionBoundSpec *spec)
 {
 	PartitionBoundSpec *result_spec;
-	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionKey	key = RelationGetPartitionKey(parent);
 	char		strategy = get_partition_strategy(key);
 	int			partnatts = get_partition_natts(key);
 	List	   *partexprs = get_partition_exprs(key);
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 7666c6c412..e1744a68e8 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -153,13 +153,13 @@ static List *get_steps_using_prefix_recurse(GeneratePruningStepsContext *context
 							   List *step_cmpfns);
 static PruneStepResult *get_matching_hash_bounds(PartitionPruneContext *context,
 						 StrategyNumber opstrategy, Datum *values, int nvalues,
-						 FmgrInfo *partsupfunc, Bitmapset *nullkeys);
+						 FmgrInfo *partsupfuncinfo, Bitmapset *nullkeys);
 static PruneStepResult *get_matching_list_bounds(PartitionPruneContext *context,
 						 StrategyNumber opstrategy, Datum value, int nvalues,
-						 FmgrInfo *partsupfunc, Bitmapset *nullkeys);
+						 FmgrInfo *partsupfuncinfo, Bitmapset *nullkeys);
 static PruneStepResult *get_matching_range_bounds(PartitionPruneContext *context,
 						  StrategyNumber opstrategy, Datum *values, int nvalues,
-						  FmgrInfo *partsupfunc, Bitmapset *nullkeys);
+						  FmgrInfo *partsupfuncinfo, Bitmapset *nullkeys);
 static bool pull_partkey_params(PartitionPruneInfo *pinfo, List *steps);
 static PruneStepResult *perform_pruning_base_step(PartitionPruneContext *context,
 						  PartitionPruneStepOp *opstep);
@@ -437,7 +437,10 @@ prune_append_rel_partitions(RelOptInfo *rel)
 	context.partopfamily = rel->part_scheme->partopfamily;
 	context.partopcintype = rel->part_scheme->partopcintype;
 	context.partcollation = rel->part_scheme->partcollation;
-	context.partsupfunc = rel->part_scheme->partsupfunc;
+	for (i = 0; i < context.partnatts; i++)
+		fmgr_info_copy(&context.partsupfuncinfo[i],
+					   &rel->part_scheme->partsupfuncinfo[i],
+					   CurrentMemoryContext);
 	context.nparts = rel->nparts;
 	context.boundinfo = rel->boundinfo;
 
@@ -1413,7 +1416,8 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		partclause->op_is_ne = false;
 		partclause->expr = expr;
 		/* We know that expr is of Boolean type. */
-		partclause->cmpfn = rel->part_scheme->partsupfunc[partkeyidx].fn_oid;
+		partclause->cmpfn =
+						rel->part_scheme->partsupfuncinfo[partkeyidx].fn_oid;
 		partclause->op_strategy = InvalidStrategy;
 
 		*pc = partclause;
@@ -1548,7 +1552,7 @@ match_clause_to_partition_key(RelOptInfo *rel,
 				return PARTCLAUSE_UNSUPPORTED;
 		}
 		else
-			cmpfn = part_scheme->partsupfunc[partkeyidx].fn_oid;
+			cmpfn = part_scheme->partsupfuncinfo[partkeyidx].fn_oid;
 
 		partclause = (PartClauseInfo *) palloc(sizeof(PartClauseInfo));
 		partclause->keyno = partkeyidx;
@@ -1962,15 +1966,15 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
  *
  * 'nvalues', the number of Datums in the 'values' array.
  *
- * 'partsupfunc' contains partition hashing functions that can produce correct
- * hash for the type of the values contained in 'values'.
+ * 'partsupfuncinfo' contains partition hashing functions that can produce
+ * correct hash for the type of the values contained in 'values'.
  *
  * 'nullkeys' is the set of partition keys that are null.
  */
 static PruneStepResult *
 get_matching_hash_bounds(PartitionPruneContext *context,
 						 StrategyNumber opstrategy, Datum *values, int nvalues,
-						 FmgrInfo *partsupfunc, Bitmapset *nullkeys)
+						 FmgrInfo *partsupfuncinfo, Bitmapset *nullkeys)
 {
 	PruneStepResult *result = (PruneStepResult *) palloc0(sizeof(PruneStepResult));
 	PartitionBoundInfo boundinfo = context->boundinfo;
@@ -2000,7 +2004,8 @@ get_matching_hash_bounds(PartitionPruneContext *context,
 			isnull[i] = bms_is_member(i, nullkeys);
 
 		greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
-		rowHash = compute_hash_value(partnatts, partsupfunc, values, isnull);
+		rowHash = compute_hash_value(partnatts, partsupfuncinfo, values,
+									 isnull);
 
 		if (partindices[rowHash % greatest_modulus] >= 0)
 			result->bound_offsets =
@@ -2029,15 +2034,15 @@ get_matching_hash_bounds(PartitionPruneContext *context,
  *
  * 'nvalues', if non-zero, should be exactly 1, because of list partitioning.
  *
- * 'partsupfunc' contains the list partitioning comparison function to be used
- * to perform partition_list_bsearch
+ * 'partsupfuncinfo' contains the list partitioning comparison function to be
+ * used to perform partition_list_bsearch
  *
  * 'nullkeys' is the set of partition keys that are null.
  */
 static PruneStepResult *
 get_matching_list_bounds(PartitionPruneContext *context,
 						 StrategyNumber opstrategy, Datum value, int nvalues,
-						 FmgrInfo *partsupfunc, Bitmapset *nullkeys)
+						 FmgrInfo *partsupfuncinfo, Bitmapset *nullkeys)
 {
 	PruneStepResult *result = (PruneStepResult *) palloc0(sizeof(PruneStepResult));
 	PartitionBoundInfo boundinfo = context->boundinfo;
@@ -2102,8 +2107,8 @@ get_matching_list_bounds(PartitionPruneContext *context,
 		result->bound_offsets = bms_add_range(NULL, 0,
 											  boundinfo->ndatums - 1);
 
-		off = partition_list_bsearch(partsupfunc, partcollation, boundinfo,
-									 value, &is_equal);
+		off = partition_list_bsearch(partsupfuncinfo, partcollation,
+									 boundinfo, value, &is_equal);
 		if (off >= 0 && is_equal)
 		{
 
@@ -2133,7 +2138,7 @@ get_matching_list_bounds(PartitionPruneContext *context,
 	switch (opstrategy)
 	{
 		case BTEqualStrategyNumber:
-			off = partition_list_bsearch(partsupfunc,
+			off = partition_list_bsearch(partsupfuncinfo,
 										 partcollation,
 										 boundinfo, value,
 										 &is_equal);
@@ -2150,7 +2155,7 @@ get_matching_list_bounds(PartitionPruneContext *context,
 			inclusive = true;
 			/* fall through */
 		case BTGreaterStrategyNumber:
-			off = partition_list_bsearch(partsupfunc,
+			off = partition_list_bsearch(partsupfuncinfo,
 										 partcollation,
 										 boundinfo, value,
 										 &is_equal);
@@ -2185,7 +2190,7 @@ get_matching_list_bounds(PartitionPruneContext *context,
 			inclusive = true;
 			/* fall through */
 		case BTLessStrategyNumber:
-			off = partition_list_bsearch(partsupfunc,
+			off = partition_list_bsearch(partsupfuncinfo,
 										 partcollation,
 										 boundinfo, value,
 										 &is_equal);
@@ -2231,16 +2236,16 @@ get_matching_list_bounds(PartitionPruneContext *context,
  *
  * 'nvalues', number of Datums in 'values' array. Must be <= context->partnatts.
  *
- * 'partsupfunc' contains the range partitioning comparison functions to be
- * used to perform partition_range_datum_bsearch or partition_rbound_datum_cmp
- * using.
+ * 'partsupfuncinfo' contains the range partitioning comparison functions to
+ * be used to perform partition_range_datum_bsearch or
+ * partition_rbound_datum_cmp using.
  *
  * 'nullkeys' is the set of partition keys that are null.
  */
 static PruneStepResult *
 get_matching_range_bounds(PartitionPruneContext *context,
 						  StrategyNumber opstrategy, Datum *values, int nvalues,
-						  FmgrInfo *partsupfunc, Bitmapset *nullkeys)
+						  FmgrInfo *partsupfuncinfo, Bitmapset *nullkeys)
 {
 	PruneStepResult *result = (PruneStepResult *) palloc0(sizeof(PruneStepResult));
 	PartitionBoundInfo boundinfo = context->boundinfo;
@@ -2302,7 +2307,7 @@ get_matching_range_bounds(PartitionPruneContext *context,
 	{
 		case BTEqualStrategyNumber:
 			/* Look for the smallest bound that is = lookup value. */
-			off = partition_range_datum_bsearch(partsupfunc,
+			off = partition_range_datum_bsearch(partsupfuncinfo,
 												partcollation,
 												boundinfo,
 												nvalues, values,
@@ -2341,7 +2346,7 @@ get_matching_range_bounds(PartitionPruneContext *context,
 						int32		cmpval;
 
 						cmpval =
-							partition_rbound_datum_cmp(partsupfunc,
+							partition_rbound_datum_cmp(partsupfuncinfo,
 													   partcollation,
 													   boundinfo->datums[off - 1],
 													   boundinfo->kind[off - 1],
@@ -2352,7 +2357,7 @@ get_matching_range_bounds(PartitionPruneContext *context,
 					}
 
 					Assert(0 ==
-						   partition_rbound_datum_cmp(partsupfunc,
+						   partition_rbound_datum_cmp(partsupfuncinfo,
 													  partcollation,
 													  boundinfo->datums[off],
 													  boundinfo->kind[off],
@@ -2381,7 +2386,7 @@ get_matching_range_bounds(PartitionPruneContext *context,
 					{
 						int32		cmpval;
 
-						cmpval = partition_rbound_datum_cmp(partsupfunc,
+						cmpval = partition_rbound_datum_cmp(partsupfuncinfo,
 															partcollation,
 															boundinfo->datums[off + 1],
 															boundinfo->kind[off + 1],
@@ -2392,7 +2397,7 @@ get_matching_range_bounds(PartitionPruneContext *context,
 					}
 
 					Assert(0 ==
-						   partition_rbound_datum_cmp(partsupfunc,
+						   partition_rbound_datum_cmp(partsupfuncinfo,
 													  partcollation,
 													  boundinfo->datums[off],
 													  boundinfo->kind[off],
@@ -2469,7 +2474,7 @@ get_matching_range_bounds(PartitionPruneContext *context,
 			 * Look for the smallest bound that is > or >= lookup value and
 			 * set minoff to its offset.
 			 */
-			off = partition_range_datum_bsearch(partsupfunc,
+			off = partition_range_datum_bsearch(partsupfuncinfo,
 												partcollation,
 												boundinfo,
 												nvalues, values,
@@ -2506,7 +2511,7 @@ get_matching_range_bounds(PartitionPruneContext *context,
 
 						nextoff = inclusive ? off - 1 : off + 1;
 						cmpval =
-							partition_rbound_datum_cmp(partsupfunc,
+							partition_rbound_datum_cmp(partsupfuncinfo,
 													   partcollation,
 													   boundinfo->datums[nextoff],
 													   boundinfo->kind[nextoff],
@@ -2518,7 +2523,7 @@ get_matching_range_bounds(PartitionPruneContext *context,
 					}
 
 					Assert(0 ==
-						   partition_rbound_datum_cmp(partsupfunc,
+						   partition_rbound_datum_cmp(partsupfuncinfo,
 													  partcollation,
 													  boundinfo->datums[off],
 													  boundinfo->kind[off],
@@ -2548,7 +2553,7 @@ get_matching_range_bounds(PartitionPruneContext *context,
 			 * Look for the greatest bound that is < or <= lookup value and
 			 * set minoff to its offset.
 			 */
-			off = partition_range_datum_bsearch(partsupfunc,
+			off = partition_range_datum_bsearch(partsupfuncinfo,
 												partcollation,
 												boundinfo,
 												nvalues, values,
@@ -2575,7 +2580,7 @@ get_matching_range_bounds(PartitionPruneContext *context,
 						int			nextoff;
 
 						nextoff = inclusive ? off + 1 : off - 1;
-						cmpval = partition_rbound_datum_cmp(partsupfunc,
+						cmpval = partition_rbound_datum_cmp(partsupfuncinfo,
 															partcollation,
 															boundinfo->datums[nextoff],
 															boundinfo->kind[nextoff],
@@ -2587,7 +2592,7 @@ get_matching_range_bounds(PartitionPruneContext *context,
 					}
 
 					Assert(0 ==
-						   partition_rbound_datum_cmp(partsupfunc,
+						   partition_rbound_datum_cmp(partsupfuncinfo,
 													  partcollation,
 													  boundinfo->datums[off],
 													  boundinfo->kind[off],
@@ -2747,7 +2752,7 @@ perform_pruning_base_step(PartitionPruneContext *context,
 	int			keyno,
 				nvalues;
 	Datum		values[PARTITION_MAX_KEYS];
-	FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
+	FmgrInfo	partsupfuncinfo[PARTITION_MAX_KEYS];
 
 	/*
 	 * There better be the same number of expressions and compare functions.
@@ -2796,11 +2801,12 @@ perform_pruning_base_step(PartitionPruneContext *context,
 				 */
 				cmpfn = lfirst_oid(lc2);
 				Assert(OidIsValid(cmpfn));
-				if (cmpfn != context->partsupfunc[keyno].fn_oid)
-					fmgr_info(cmpfn, &partsupfunc[keyno]);
+				if (cmpfn != context->partsupfuncinfo[keyno].fn_oid)
+					fmgr_info_cxt(cmpfn, &partsupfuncinfo[keyno],
+								  CurrentMemoryContext);
 				else
-					fmgr_info_copy(&partsupfunc[keyno],
-								   &context->partsupfunc[keyno],
+					fmgr_info_copy(&partsupfuncinfo[keyno],
+								   &context->partsupfuncinfo[keyno],
 								   CurrentMemoryContext);
 
 				values[keyno] = datum;
@@ -2818,21 +2824,21 @@ perform_pruning_base_step(PartitionPruneContext *context,
 			return get_matching_hash_bounds(context,
 											opstep->opstrategy,
 											values, nvalues,
-											partsupfunc,
+											partsupfuncinfo,
 											opstep->nullkeys);
 
 		case PARTITION_STRATEGY_LIST:
 			return get_matching_list_bounds(context,
 											opstep->opstrategy,
 											values[0], nvalues,
-											&partsupfunc[0],
+											&partsupfuncinfo[0],
 											opstep->nullkeys);
 
 		case PARTITION_STRATEGY_RANGE:
 			return get_matching_range_bounds(context,
 											 opstep->opstrategy,
 											 values, nvalues,
-											 partsupfunc,
+											 partsupfuncinfo,
 											 opstep->nullkeys);
 
 		default:
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e81c4691ec..d37e124dba 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -51,6 +51,7 @@
 #include "catalog/pg_auth_members.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
@@ -68,6 +69,7 @@
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
@@ -265,8 +267,6 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
-static void RelationBuildPartitionKey(Relation relation);
-static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
 static TupleDesc GetPgClassDescriptor(void);
@@ -286,9 +286,41 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
-static bool equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
-					PartitionDesc partdesc2);
 
+/*
+ * PartitionBoundInfo encapsulates a set of partition bounds.  It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure appears in partbounds.h.
+ */
+typedef struct PartitionBoundInfoData *PartitionBoundInfo;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+	int			nparts;			/* Number of partitions */
+	Oid		   *oids;			/* OIDs of partitions */
+	PartitionBoundInfo boundinfo;	/* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+typedef struct PartitionBoundSortInfo
+{
+	FmgrInfo	   *partsupfuncinfo;
+	PartitionKey	key;
+} PartitionBoundSortInfo;
+
+static int32 qsort_partition_hbound_cmp(const void *a, const void *b);
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
+							   void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
+						   void *arg);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
+static int	get_partition_bound_num_indexes(PartitionBoundInfo b);
+static List *generate_partition_qual(Relation rel);
 
 /*
  *		ScanPgRelation
@@ -874,10 +906,10 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
- * RelationBuildPartitionKey
- *		Build and attach to relcache partition key data of relation
+ * RelationBuildPartitionInfo
+ *		Build and attach to relcache partitionning related data of relation
  *
- * Partitioning key data is a complex structure; to avoid complicated logic to
+ * Partitioning data is of complex structure; to avoid complicated logic to
  * free individual elements whenever the relcache entry is flushed, we give it
  * its own memory context, child of CacheMemoryContext, which can easily be
  * deleted on its own.  To avoid leaking memory in that context in case of an
@@ -889,7 +921,7 @@ RelationBuildRuleLock(Relation relation)
  * permanently.
  */
 static void
-RelationBuildPartitionKey(Relation relation)
+RelationBuildPartitionInfo(Relation relation)
 {
 	Form_pg_partitioned_table form;
 	HeapTuple	tuple;
@@ -901,9 +933,29 @@ RelationBuildPartitionKey(Relation relation)
 	oidvector  *collation;
 	ListCell   *partexprs_item;
 	Datum		datum;
-	MemoryContext partkeycxt,
+	MemoryContext partcxt,
 				oldcxt;
 	int16		procnum;
+	int			nparts;
+	List	   *inhoids,
+			   *partoids;
+	Oid		   *oids = NULL;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	PartitionDesc partdesc;
+
+	int			ndatums = 0;
+	int			default_index = -1;
+
+	/* Hash partitioning specific */
+	PartitionHashBound **hbounds = NULL;
+
+	/* List partitioning specific */
+	PartitionListValue **all_values = NULL;
+	int			null_index = -1;
+
+	/* Range partitioning specific */
+	PartitionRangeBound **rbounds = NULL;
 
 	tuple = SearchSysCache1(PARTRELID,
 							ObjectIdGetDatum(RelationGetRelid(relation)));
@@ -915,13 +967,13 @@ RelationBuildPartitionKey(Relation relation)
 	if (!HeapTupleIsValid(tuple))
 		return;
 
-	partkeycxt = AllocSetContextCreate(CurTransactionContext,
-									   "partition key",
-									   ALLOCSET_SMALL_SIZES);
-	MemoryContextCopyAndSetIdentifier(partkeycxt,
-								   RelationGetRelationName(relation));
+	partcxt = AllocSetContextCreate(CurTransactionContext,
+									"partition info",
+									ALLOCSET_SMALL_SIZES);
+	MemoryContextCopyAndSetIdentifier(partcxt,
+									  RelationGetRelationName(relation));
 
-	key = (PartitionKey) MemoryContextAllocZero(partkeycxt,
+	key = (PartitionKey) MemoryContextAllocZero(partcxt,
 												sizeof(PartitionKeyData));
 
 	/* Fixed-length attributes */
@@ -973,16 +1025,16 @@ RelationBuildPartitionKey(Relation relation)
 		expr = eval_const_expressions(NULL, expr);
 		fix_opfuncids(expr);
 
-		oldcxt = MemoryContextSwitchTo(partkeycxt);
+		oldcxt = MemoryContextSwitchTo(partcxt);
 		key->partexprs = (List *) copyObject(expr);
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	oldcxt = MemoryContextSwitchTo(partkeycxt);
+	oldcxt = MemoryContextSwitchTo(partcxt);
 	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
 	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
 	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+	key->partsupfunc = (Oid *) palloc0(key->partnatts * sizeof(Oid));
 
 	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
 
@@ -993,6 +1045,13 @@ RelationBuildPartitionKey(Relation relation)
 	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
 	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
 	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/*
+	 * Also allocate space for partition support procedure FmgrInfo's, but
+	 * they won't be filled until somebody calls partition_get_procinfo.
+	 */
+	relation->rd_partsupfuncinfo = (FmgrInfo *)
+		MemoryContextAllocZero(partcxt, key->partnatts * sizeof(FmgrInfo));
 	MemoryContextSwitchTo(oldcxt);
 
 	/* determine support function number to search for */
@@ -1034,7 +1093,7 @@ RelationBuildPartitionKey(Relation relation)
 							procnum,
 							format_type_be(opclassform->opcintype))));
 
-		fmgr_info(funcid, &key->partsupfunc[i]);
+		key->partsupfunc[i] = funcid;
 
 		/* Collation */
 		key->partcollation[i] = collation->values[i];
@@ -1070,12 +1129,1127 @@ RelationBuildPartitionKey(Relation relation)
 	ReleaseSysCache(tuple);
 
 	/*
+	 * Now build the PartitionDesc that contains information about partitions.
+	 */
+
+	/* Get partition oids from pg_inherits */
+	inhoids = find_inheritance_children(RelationGetRelid(relation), NoLock);
+
+	/* Collect bound spec nodes in a list */
+	i = 0;
+	partoids = NIL;
+	foreach(cell, inhoids)
+	{
+		Oid			inhrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+
+		tuple = SearchSysCache1(RELOID, inhrelid);
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+		/*
+		 * It is possible that the pg_class tuple of a partition has not been
+		 * updated yet to set its relpartbound field.  The only case where
+		 * this happens is when we open the parent relation to check using its
+		 * partition descriptor that a new partition's bound does not overlap
+		 * some existing partition.
+		 */
+		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+
+		datum = SysCacheGetAttr(RELOID, tuple,
+								Anum_pg_class_relpartbound,
+								&isnull);
+		Assert(!isnull);
+		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+
+		/*
+		 * Sanity check: If the PartitionBoundSpec says this is the default
+		 * partition, its OID should correspond to whatever's stored in
+		 * pg_partitioned_table.partdefid; if not, the catalog is corrupt.
+		 */
+		if (castNode(PartitionBoundSpec, boundspec)->is_default)
+		{
+			Oid			partdefid;
+
+			partdefid = get_default_partition_oid(RelationGetRelid(relation));
+			if (partdefid != inhrelid)
+				elog(ERROR, "expected partdefid %u, but got %u",
+					 inhrelid, partdefid);
+		}
+
+		boundspecs = lappend(boundspecs, boundspec);
+		partoids = lappend_oid(partoids, inhrelid);
+		ReleaseSysCache(tuple);
+	}
+
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		FmgrInfo	partsupfuncinfo[PARTITION_MAX_KEYS];
+		PartitionBoundSortInfo sortinfo;
+
+		/* Get partsupfunc FmgrInfo's. */
+		for (i = 0; i < key->partnatts; i++)
+		{
+			fmgr_info_copy(&partsupfuncinfo[i],
+						   partition_getprocinfo(relation, key, i),
+						   CurrentMemoryContext);
+		}
+
+		sortinfo.partsupfuncinfo = partsupfuncinfo;
+		sortinfo.key = key;
+
+		oids = (Oid *) palloc(nparts * sizeof(Oid));
+
+		i = 0;
+		foreach(cell, partoids)
+			oids[i++] = lfirst_oid(cell);
+
+		/* Convert from node to the internal representation */
+		if (key->strategy == PARTITION_STRATEGY_HASH)
+		{
+			ndatums = nparts;
+			hbounds = (PartitionHashBound **)
+				palloc(nparts * sizeof(PartitionHashBound *));
+
+			i = 0;
+			foreach(cell, boundspecs)
+			{
+				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
+													lfirst(cell));
+
+				if (spec->strategy != PARTITION_STRATEGY_HASH)
+					elog(ERROR, "invalid strategy in partition bound spec");
+
+				hbounds[i] = (PartitionHashBound *)
+					palloc(sizeof(PartitionHashBound));
+
+				hbounds[i]->modulus = spec->modulus;
+				hbounds[i]->remainder = spec->remainder;
+				hbounds[i]->index = i;
+				i++;
+			}
+
+			/* Sort all the bounds in ascending order */
+			qsort(hbounds, nparts, sizeof(PartitionHashBound *),
+				  qsort_partition_hbound_cmp);
+		}
+		else if (key->strategy == PARTITION_STRATEGY_LIST)
+		{
+			List	   *non_null_values = NIL;
+
+			/*
+			 * Create a unified list of non-null values across all partitions.
+			 */
+			i = 0;
+			null_index = -1;
+			foreach(cell, boundspecs)
+			{
+				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
+													lfirst(cell));
+				ListCell   *c;
+
+				if (spec->strategy != PARTITION_STRATEGY_LIST)
+					elog(ERROR, "invalid strategy in partition bound spec");
+
+				/*
+				 * Note the index of the partition bound spec for the default
+				 * partition. There's no datum to add to the list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
+				foreach(c, spec->listdatums)
+				{
+					Const	   *val = castNode(Const, lfirst(c));
+					PartitionListValue *list_value = NULL;
+
+					if (!val->constisnull)
+					{
+						list_value = (PartitionListValue *)
+							palloc0(sizeof(PartitionListValue));
+						list_value->index = i;
+						list_value->value = val->constvalue;
+					}
+					else
+					{
+						/*
+						 * Never put a null into the values array, flag
+						 * instead for the code further down below where we
+						 * construct the actual relcache struct.
+						 */
+						if (null_index != -1)
+							elog(ERROR, "found null more than once");
+						null_index = i;
+					}
+
+					if (list_value)
+						non_null_values = lappend(non_null_values,
+												  list_value);
+				}
+
+				i++;
+			}
+
+			ndatums = list_length(non_null_values);
+
+			/*
+			 * Collect all list values in one array. Alongside the value, we
+			 * also save the index of partition the value comes from.
+			 */
+			all_values = (PartitionListValue **) palloc(ndatums *
+														sizeof(PartitionListValue *));
+			i = 0;
+			foreach(cell, non_null_values)
+			{
+				PartitionListValue *src = lfirst(cell);
+
+				all_values[i] = (PartitionListValue *)
+					palloc(sizeof(PartitionListValue));
+				all_values[i]->value = src->value;
+				all_values[i]->index = src->index;
+				i++;
+			}
+
+			qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
+					  qsort_partition_list_value_cmp, (void *) &sortinfo);
+		}
+		else if (key->strategy == PARTITION_STRATEGY_RANGE)
+		{
+			int			k;
+			PartitionRangeBound **all_bounds,
+					   *prev;
+
+			all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+														  sizeof(PartitionRangeBound *));
+
+			/*
+			 * Create a unified list of range bounds across all the
+			 * partitions.
+			 */
+			i = ndatums = 0;
+			foreach(cell, boundspecs)
+			{
+				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
+													lfirst(cell));
+				PartitionRangeBound *lower,
+						   *upper;
+
+				if (spec->strategy != PARTITION_STRATEGY_RANGE)
+					elog(ERROR, "invalid strategy in partition bound spec");
+
+				/*
+				 * Note the index of the partition bound spec for the default
+				 * partition. There's no datum to add to the allbounds array
+				 * for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i++;
+					continue;
+				}
+
+				lower = make_one_range_bound(key, i, spec->lowerdatums,
+											 true);
+				upper = make_one_range_bound(key, i, spec->upperdatums,
+											 false);
+				all_bounds[ndatums++] = lower;
+				all_bounds[ndatums++] = upper;
+				i++;
+			}
+
+			Assert(ndatums == nparts * 2 ||
+				   (default_index != -1 && ndatums == (nparts - 1) * 2));
+
+			/* Sort all the bounds in ascending order */
+			qsort_arg(all_bounds, ndatums,
+					  sizeof(PartitionRangeBound *),
+					  qsort_partition_rbound_cmp,
+					  (void *) &sortinfo);
+
+			/* Save distinct bounds from all_bounds into rbounds. */
+			rbounds = (PartitionRangeBound **)
+				palloc(ndatums * sizeof(PartitionRangeBound *));
+
+			k = 0;
+			prev = NULL;
+			for (i = 0; i < ndatums; i++)
+			{
+				PartitionRangeBound *cur = all_bounds[i];
+				bool		is_distinct = false;
+				int			j;
+
+				/* Is the current bound distinct from the previous one? */
+				for (j = 0; j < key->partnatts; j++)
+				{
+					Datum		cmpval;
+
+					if (prev == NULL || cur->kind[j] != prev->kind[j])
+					{
+						is_distinct = true;
+						break;
+					}
+
+					/*
+					 * If the bounds are both MINVALUE or MAXVALUE, stop now
+					 * and treat them as equal, since any values after this
+					 * point must be ignored.
+					 */
+					if (cur->kind[j] != PARTITION_RANGE_DATUM_VALUE)
+						break;
+
+					cmpval = FunctionCall2Coll(&partsupfuncinfo[j],
+											   key->partcollation[j],
+											   cur->datums[j],
+											   prev->datums[j]);
+					if (DatumGetInt32(cmpval) != 0)
+					{
+						is_distinct = true;
+						break;
+					}
+				}
+
+				/*
+				 * Only if the bound is distinct save it into a temporary
+				 * array i.e. rbounds which is later copied into boundinfo
+				 * datums array.
+				 */
+				if (is_distinct)
+					rbounds[k++] = all_bounds[i];
+
+				prev = cur;
+			}
+
+			/* Update ndatums to hold the count of distinct datums. */
+			ndatums = k;
+		}
+		else
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) key->strategy);
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	oldcxt = MemoryContextSwitchTo(partcxt);
+
+	partdesc = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	partdesc->nparts = nparts;
+	if (nparts > 0)
+	{
+		PartitionBoundInfo boundinfo;
+		int		   *mapping;
+		int			next_index = 0;
+
+		partdesc->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		boundinfo = (PartitionBoundInfoData *)
+			palloc0(sizeof(PartitionBoundInfoData));
+		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
+		boundinfo->ndatums = ndatums;
+		boundinfo->null_index = -1;
+		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
+
+		/* Initialize mapping array with invalid values */
+		mapping = (int *) palloc(sizeof(int) * nparts);
+		for (i = 0; i < nparts; i++)
+			mapping[i] = -1;
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_HASH:
+				{
+					/* Modulus are stored in ascending order */
+					int			greatest_modulus = hbounds[ndatums - 1]->modulus;
+
+					boundinfo->indexes = (int *) palloc(greatest_modulus *
+														sizeof(int));
+
+					for (i = 0; i < greatest_modulus; i++)
+						boundinfo->indexes[i] = -1;
+
+					for (i = 0; i < nparts; i++)
+					{
+						int			modulus = hbounds[i]->modulus;
+						int			remainder = hbounds[i]->remainder;
+
+						boundinfo->datums[i] = (Datum *) palloc(2 *
+																sizeof(Datum));
+						boundinfo->datums[i][0] = Int32GetDatum(modulus);
+						boundinfo->datums[i][1] = Int32GetDatum(remainder);
+
+						while (remainder < greatest_modulus)
+						{
+							/* overlap? */
+							Assert(boundinfo->indexes[remainder] == -1);
+							boundinfo->indexes[remainder] = i;
+							remainder += modulus;
+						}
+
+						mapping[hbounds[i]->index] = i;
+						pfree(hbounds[i]);
+					}
+					pfree(hbounds);
+					break;
+				}
+
+			case PARTITION_STRATEGY_LIST:
+				{
+					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
+
+					/*
+					 * Copy values.  Indexes of individual values are mapped
+					 * to canonical values so that they match for any two list
+					 * partitioned tables with same number of partitions and
+					 * same lists per partition.  One way to canonicalize is
+					 * to assign the index in all_values[] of the smallest
+					 * value of each partition, as the index of all of the
+					 * partition's values.
+					 */
+					for (i = 0; i < ndatums; i++)
+					{
+						boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
+						boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
+															key->parttypbyval[0],
+															key->parttyplen[0]);
+
+						/* If the old index has no mapping, assign one */
+						if (mapping[all_values[i]->index] == -1)
+							mapping[all_values[i]->index] = next_index++;
+
+						boundinfo->indexes[i] = mapping[all_values[i]->index];
+					}
+
+					/*
+					 * If null-accepting partition has no mapped index yet,
+					 * assign one.  This could happen if such partition
+					 * accepts only null and hence not covered in the above
+					 * loop which only handled non-null values.
+					 */
+					if (null_index != -1)
+					{
+						Assert(null_index >= 0);
+						if (mapping[null_index] == -1)
+							mapping[null_index] = next_index++;
+						boundinfo->null_index = mapping[null_index];
+					}
+
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any value not
+						 * specified in the lists of other partitions, hence
+						 * it should not get mapped index while assigning
+						 * those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
+					/* All partition must now have a valid mapping */
+					Assert(next_index == nparts);
+					break;
+				}
+
+			case PARTITION_STRATEGY_RANGE:
+				{
+					boundinfo->kind = (PartitionRangeDatumKind **)
+						palloc(ndatums *
+							   sizeof(PartitionRangeDatumKind *));
+					boundinfo->indexes = (int *) palloc((ndatums + 1) *
+														sizeof(int));
+
+					for (i = 0; i < ndatums; i++)
+					{
+						int			j;
+
+						boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
+																sizeof(Datum));
+						boundinfo->kind[i] = (PartitionRangeDatumKind *)
+							palloc(key->partnatts *
+								   sizeof(PartitionRangeDatumKind));
+						for (j = 0; j < key->partnatts; j++)
+						{
+							if (rbounds[i]->kind[j] == PARTITION_RANGE_DATUM_VALUE)
+								boundinfo->datums[i][j] =
+									datumCopy(rbounds[i]->datums[j],
+											  key->parttypbyval[j],
+											  key->parttyplen[j]);
+							boundinfo->kind[i][j] = rbounds[i]->kind[j];
+						}
+
+						/*
+						 * There is no mapping for invalid indexes.
+						 *
+						 * Any lower bounds in the rbounds array have invalid
+						 * indexes assigned, because the values between the
+						 * previous bound (if there is one) and this (lower)
+						 * bound are not part of the range of any existing
+						 * partition.
+						 */
+						if (rbounds[i]->lower)
+							boundinfo->indexes[i] = -1;
+						else
+						{
+							int			orig_index = rbounds[i]->index;
+
+							/* If the old index has no mapping, assign one */
+							if (mapping[orig_index] == -1)
+								mapping[orig_index] = next_index++;
+
+							boundinfo->indexes[i] = mapping[orig_index];
+						}
+					}
+
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						Assert(default_index >= 0 && mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+					boundinfo->indexes[i] = -1;
+					break;
+				}
+
+			default:
+				elog(ERROR, "unexpected partition strategy: %d",
+					 (int) key->strategy);
+		}
+
+		partdesc->boundinfo = boundinfo;
+
+		/*
+		 * Now assign OIDs from the original array into mapped indexes of the
+		 * result array.  Order of OIDs in the former is defined by the
+		 * catalog scan that retrieved them, whereas that in the latter is
+		 * defined by canonicalized representation of the partition bounds.
+		 */
+		for (i = 0; i < nparts; i++)
+			partdesc->oids[mapping[i]] = oids[i];
+		pfree(mapping);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+
+	/*
 	 * Success --- reparent our context and make the relcache point to the
 	 * newly constructed key
 	 */
-	MemoryContextSetParent(partkeycxt, CacheMemoryContext);
-	relation->rd_partkeycxt = partkeycxt;
+	MemoryContextSetParent(partcxt, CacheMemoryContext);
+	relation->rd_partcxt = partcxt;
 	relation->rd_partkey = key;
+	relation->rd_partdesc = partdesc;
+}
+
+/*
+ * RelationGetPartitionInfo
+ *		Returns a copy of the partitioning related data stored in relcache
+ *
+ * Note: Caller should be in a hopefully short-lived memory context
+ */
+PartitionInfo *
+RelationGetPartitionInfo(Relation relation)
+{
+	PartitionDesc partdesc = relation->rd_partdesc;
+	PartitionInfo *pinfo = palloc0(sizeof(PartitionInfo));
+
+	pinfo->key = copy_partition_key(relation->rd_partkey);
+	pinfo->nparts = partdesc->nparts;
+	if (pinfo->nparts > 0)
+	{
+		Assert(partdesc->oids != NULL);
+		pinfo->oids = palloc(partdesc->nparts * sizeof(Oid));
+		memcpy(pinfo->oids, partdesc->oids, partdesc->nparts * sizeof(Oid));
+		Assert(partdesc->boundinfo != NULL);
+		pinfo->boundinfo = partition_bounds_copy(partdesc->boundinfo,
+												 pinfo->key);
+	}
+
+	return pinfo;
+}
+
+/*
+ * Functions to call when calling RelationGetPartitionInfo might be too much.
+ */
+
+PartitionKey
+RelationGetPartitionKey(Relation relation)
+{
+	PartitionKey partkey = relation->rd_partkey;
+
+	return copy_partition_key(partkey);
+}
+
+int
+RelationGetPartitionCount(Relation relation)
+{
+	PartitionDesc partdesc = relation->rd_partdesc;
+
+	return partdesc->nparts;
+}
+
+Oid *
+RelationGetPartitionOids(Relation relation)
+{
+	PartitionDesc partdesc = relation->rd_partdesc;
+	Oid *result = NULL;
+
+	Assert(partdesc != NULL);
+	if (partdesc->nparts > 0)
+	{
+		result = palloc(partdesc->nparts * sizeof(Oid));
+		memcpy(result, partdesc->oids, partdesc->nparts * sizeof(Oid));
+	}
+
+	return result;
+}
+
+/*
+ * RelationGetDefaultPartitionOid
+ *
+ * Return the OID of the default partition, if one exists; else InvalidOid.
+ */
+Oid
+RelationGetDefaultPartitionOid(Relation rel)
+{
+	PartitionBoundInfo boundinfo;
+
+	/* Shouldn't be here otherwise! */
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+	boundinfo = rel->rd_partdesc->boundinfo;
+
+	if (boundinfo && partition_bound_has_default(boundinfo))
+		return rel->rd_partdesc->oids[boundinfo->default_index];
+
+	return InvalidOid;
+}
+
+/*
+ * partition_getprocinfo
+ *		Return fmgr lookup info of partition support procs from relcache
+ *
+ * If it's not been built yet by calling fmgr.c, do that and add it to
+ * relcache.
+ */
+FmgrInfo *
+partition_getprocinfo(Relation rel, PartitionKey key, int partattoff)
+{
+	FmgrInfo   *info;
+
+	info = rel->rd_partsupfuncinfo;
+
+	Assert(info != NULL);
+
+	info += partattoff;
+
+	/* Initialize the lookup info if first time through */
+	if (info->fn_oid == InvalidOid)
+	{
+		RegProcedure *func = key->partsupfunc;
+		RegProcedure procId;
+
+		Assert(func != NULL);
+
+		procId = func[partattoff];
+		Assert(RegProcedureIsValid(procId));
+		fmgr_info_cxt(procId, info, rel->rd_partcxt);
+	}
+
+	return info;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because PartitionBoundInfo is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
+					   PartitionBoundInfo b1, PartitionBoundInfo b2)
+{
+	int			i;
+
+	if (b1->strategy != b2->strategy)
+		return false;
+
+	if (b1->ndatums != b2->ndatums)
+		return false;
+
+	if (b1->null_index != b2->null_index)
+		return false;
+
+	if (b1->default_index != b2->default_index)
+		return false;
+
+	if (b1->strategy == PARTITION_STRATEGY_HASH)
+	{
+		int			greatest_modulus = get_hash_partition_greatest_modulus(b1);
+
+		/*
+		 * If two hash partitioned tables have different greatest moduli,
+		 * their partition schemes don't match.
+		 */
+		if (greatest_modulus != get_hash_partition_greatest_modulus(b2))
+			return false;
+
+		/*
+		 * We arrange the partitions in the ascending order of their modulus
+		 * and remainders.  Also every modulus is factor of next larger
+		 * modulus.  Therefore we can safely store index of a given partition
+		 * in indexes array at remainder of that partition.  Also entries at
+		 * (remainder + N * modulus) positions in indexes array are all same
+		 * for (modulus, remainder) specification for any partition.  Thus
+		 * datums array from both the given bounds are same, if and only if
+		 * their indexes array will be same.  So, it suffices to compare
+		 * indexes array.
+		 */
+		for (i = 0; i < greatest_modulus; i++)
+			if (b1->indexes[i] != b2->indexes[i])
+				return false;
+
+#ifdef USE_ASSERT_CHECKING
+
+		/*
+		 * Nonetheless make sure that the bounds are indeed same when the
+		 * indexes match.  Hash partition bound stores modulus and remainder
+		 * at b1->datums[i][0] and b1->datums[i][1] position respectively.
+		 */
+		for (i = 0; i < b1->ndatums; i++)
+			Assert((b1->datums[i][0] == b2->datums[i][0] &&
+					b1->datums[i][1] == b2->datums[i][1]));
+#endif
+	}
+	else
+	{
+		for (i = 0; i < b1->ndatums; i++)
+		{
+			int			j;
+
+			for (j = 0; j < partnatts; j++)
+			{
+				/* For range partitions, the bounds might not be finite. */
+				if (b1->kind != NULL)
+				{
+					/* The different kinds of bound all differ from each other */
+					if (b1->kind[i][j] != b2->kind[i][j])
+						return false;
+
+					/*
+					 * Non-finite bounds are equal without further
+					 * examination.
+					 */
+					if (b1->kind[i][j] != PARTITION_RANGE_DATUM_VALUE)
+						continue;
+				}
+
+				/*
+				 * Compare the actual values. Note that it would be both
+				 * incorrect and unsafe to invoke the comparison operator
+				 * derived from the partitioning specification here.  It would
+				 * be incorrect because we want the relcache entry to be
+				 * updated for ANY change to the partition bounds, not just
+				 * those that the partitioning operator thinks are
+				 * significant.  It would be unsafe because we might reach
+				 * this code in the context of an aborted transaction, and an
+				 * arbitrary partitioning operator might not be safe in that
+				 * 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;
+			}
+
+			if (b1->indexes[i] != b2->indexes[i])
+				return false;
+		}
+
+		/* There are ndatums+1 indexes in case of range partitions */
+		if (b1->strategy == PARTITION_STRATEGY_RANGE &&
+			b1->indexes[i] != b2->indexes[i])
+			return false;
+	}
+	return true;
+}
+
+/*
+ * Return a copy of given PartitionBoundInfo structure. The data types of bounds
+ * are described by given partition key specification.
+ */
+PartitionBoundInfo
+partition_bounds_copy(PartitionBoundInfo src,
+					  PartitionKey key)
+{
+	PartitionBoundInfo dest;
+	int			i;
+	int			ndatums;
+	int			partnatts;
+	int			num_indexes;
+
+	dest = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
+
+	dest->strategy = src->strategy;
+	ndatums = dest->ndatums = src->ndatums;
+	partnatts = key->partnatts;
+
+	num_indexes = get_partition_bound_num_indexes(src);
+
+	/* List partitioned tables have only a single partition key. */
+	Assert(key->strategy != PARTITION_STRATEGY_LIST || partnatts == 1);
+
+	dest->datums = (Datum **) palloc(sizeof(Datum *) * ndatums);
+
+	if (src->kind != NULL)
+	{
+		dest->kind = (PartitionRangeDatumKind **) palloc(ndatums *
+														 sizeof(PartitionRangeDatumKind *));
+		for (i = 0; i < ndatums; i++)
+		{
+			dest->kind[i] = (PartitionRangeDatumKind *) palloc(partnatts *
+															   sizeof(PartitionRangeDatumKind));
+
+			memcpy(dest->kind[i], src->kind[i],
+				   sizeof(PartitionRangeDatumKind) * key->partnatts);
+		}
+	}
+	else
+		dest->kind = NULL;
+
+	for (i = 0; i < ndatums; i++)
+	{
+		int			j;
+
+		/*
+		 * For a corresponding to hash partition, datums array will have two
+		 * elements - modulus and remainder.
+		 */
+		bool		hash_part = (key->strategy == PARTITION_STRATEGY_HASH);
+		int			natts = hash_part ? 2 : partnatts;
+
+		dest->datums[i] = (Datum *) palloc(sizeof(Datum) * natts);
+
+		for (j = 0; j < natts; j++)
+		{
+			bool		byval;
+			int			typlen;
+
+			if (hash_part)
+			{
+				typlen = sizeof(int32); /* Always int4 */
+				byval = true;	/* int4 is pass-by-value */
+			}
+			else
+			{
+				byval = key->parttypbyval[j];
+				typlen = key->parttyplen[j];
+			}
+
+			if (dest->kind == NULL ||
+				dest->kind[i][j] == PARTITION_RANGE_DATUM_VALUE)
+				dest->datums[i][j] = datumCopy(src->datums[i][j],
+											   byval, typlen);
+		}
+	}
+
+	dest->indexes = (int *) palloc(sizeof(int) * num_indexes);
+	memcpy(dest->indexes, src->indexes, sizeof(int) * num_indexes);
+
+	dest->null_index = src->null_index;
+	dest->default_index = src->default_index;
+
+	return dest;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel);
+}
+
+/*
+ * get_partition_qual_relid
+ *
+ * Returns an expression tree describing the passed-in relation's partition
+ * constraint. If there is no partition constraint returns NULL; this can
+ * happen if the default partition is the only partition.
+ */
+Expr *
+get_partition_qual_relid(Oid relid)
+{
+	Relation	rel = heap_open(relid, AccessShareLock);
+	Expr	   *result = NULL;
+	List	   *and_args;
+
+	/* Do the work only if this relation is a partition. */
+	if (rel->rd_rel->relispartition)
+	{
+		and_args = generate_partition_qual(rel);
+
+		if (and_args == NIL)
+			result = NULL;
+		else if (list_length(and_args) > 1)
+			result = makeBoolExpr(AND_EXPR, and_args, -1);
+		else
+			result = linitial(and_args);
+	}
+
+	/* Keep the lock. */
+	heap_close(rel, NoLock);
+
+	return result;
+}
+
+/*
+ * qsort_partition_hbound_cmp
+ *
+ * We sort hash bounds by modulus, then by remainder.
+ */
+static int32
+qsort_partition_hbound_cmp(const void *a, const void *b)
+{
+	PartitionHashBound *h1 = (*(PartitionHashBound *const *) a);
+	PartitionHashBound *h2 = (*(PartitionHashBound *const *) b);
+
+	return partition_hbound_cmp(h1->modulus, h1->remainder,
+								h2->modulus, h2->remainder);
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list partition bound datums
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+	Datum		val1 = (*(const PartitionListValue **) a)->value,
+				val2 = (*(const PartitionListValue **) b)->value;
+	PartitionBoundSortInfo *sortinfo = (PartitionBoundSortInfo *) arg;
+	PartitionKey key = sortinfo->key;
+
+	return DatumGetInt32(FunctionCall2Coll(&sortinfo->partsupfuncinfo[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+	PartitionBoundSortInfo *sortinfo = (PartitionBoundSortInfo *) arg;
+	PartitionKey key = sortinfo->key;
+
+	return partition_rbound_cmp(key->partnatts, sortinfo->partsupfuncinfo,
+								key->partcollation, b1->datums, b1->kind,
+								b1->lower, b2);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey newkey;
+	int			n;
+
+	Assert(fromkey != NULL);
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(Oid));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+/*
+ * get_partition_bound_num_indexes
+ *
+ * Returns the number of the entries in the partition bound indexes array.
+ */
+static int
+get_partition_bound_num_indexes(PartitionBoundInfo bound)
+{
+	int			num_indexes;
+
+	Assert(bound);
+
+	switch (bound->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+
+			/*
+			 * The number of the entries in the indexes array is same as the
+			 * greatest modulus.
+			 */
+			num_indexes = get_hash_partition_greatest_modulus(bound);
+			break;
+
+		case PARTITION_STRATEGY_LIST:
+			num_indexes = bound->ndatums;
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			/* Range partitioned table has an extra index. */
+			num_indexes = bound->ndatums + 1;
+			break;
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) bound->strategy);
+	}
+
+	return num_indexes;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression. The
+ * function returns a NIL list if there is no predicate.
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel)
+{
+	HeapTuple	tuple;
+	MemoryContext oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	PartitionBoundSpec *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+	bool		found_whole_row;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Quick copy */
+	if (rel->rd_partcheck != NIL)
+		return copyObject(rel->rd_partcheck);
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Get pg_class.relpartbound */
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(rel));
+
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	if (isnull)					/* should not happen */
+		elog(ERROR, "relation \"%s\" has relpartbound = null",
+			 RelationGetRelationName(rel));
+	bound = castNode(PartitionBoundSpec,
+					 stringToNode(TextDatumGetCString(boundDatum)));
+	ReleaseSysCache(tuple);
+
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* Add the parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition)
+		result = list_concat(generate_partition_qual(parent), my_qual);
+	else
+		result = my_qual;
+
+	/*
+	 * Change Vars to have partition's attnos instead of the parent's. We do
+	 * this after we concatenate the parent's quals, because we want every Var
+	 * in it to bear this relation's attnos. It's safe to assume varno = 1
+	 * here.
+	 */
+	result = map_partition_varattnos(result, 1, rel, parent,
+									 &found_whole_row);
+	/* There can never be a whole-row reference here */
+	if (found_whole_row)
+		elog(ERROR, "unexpected whole-row reference found in partition key");
+
+	/* Save a copy in the relcache */
+	oldcxt = MemoryContextSwitchTo(rel->rd_partcxt);
+	rel->rd_partcheck = copyObject(result);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Keep the parent locked until commit */
+	heap_close(parent, NoLock);
+
+	return result;
 }
 
 /*
@@ -1206,60 +2380,6 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
- * equalPartitionDescs
- *		Compare two partition descriptors for logical equality
- */
-static bool
-equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
-					PartitionDesc partdesc2)
-{
-	int			i;
-
-	if (partdesc1 != NULL)
-	{
-		if (partdesc2 == NULL)
-			return false;
-		if (partdesc1->nparts != partdesc2->nparts)
-			return false;
-
-		Assert(key != NULL || partdesc1->nparts == 0);
-
-		/*
-		 * Same oids? If the partitioning structure did not change, that is,
-		 * no partitions were added or removed to the relation, the oids array
-		 * should still match element-by-element.
-		 */
-		for (i = 0; i < partdesc1->nparts; i++)
-		{
-			if (partdesc1->oids[i] != partdesc2->oids[i])
-				return false;
-		}
-
-		/*
-		 * Now compare partition bound collections.  The logic to iterate over
-		 * the collections is private to partition.c.
-		 */
-		if (partdesc1->boundinfo != NULL)
-		{
-			if (partdesc2->boundinfo == NULL)
-				return false;
-
-			if (!partition_bounds_equal(key->partnatts, key->parttyplen,
-										key->parttypbyval,
-										partdesc1->boundinfo,
-										partdesc2->boundinfo))
-				return false;
-		}
-		else if (partdesc2->boundinfo != NULL)
-			return false;
-	}
-	else if (partdesc2 != NULL)
-		return false;
-
-	return true;
-}
-
-/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1389,16 +2509,26 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 
 	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionInfo(relation);
+	else if (relation->rd_rel->relispartition)
 	{
-		RelationBuildPartitionKey(relation);
-		RelationBuildPartitionDesc(relation);
+		/*
+		 * Create a cache memory context to allocate memory for partition
+		 * constraint expression tree.  Partition constraint itself is built
+		 * only when needed.
+		 */
+		relation->rd_partcxt = AllocSetContextCreate(CacheMemoryContext,
+													 "partition info",
+													 ALLOCSET_SMALL_SIZES);
+		MemoryContextCopyAndSetIdentifier(relation->rd_partcxt,
+										  RelationGetRelationName(relation));
 	}
 	else
 	{
-		relation->rd_partkeycxt = NULL;
+		relation->rd_partcxt = NULL;
 		relation->rd_partkey = NULL;
 		relation->rd_partdesc = NULL;
-		relation->rd_pdcxt = NULL;
+		relation->rd_partcheck = NIL;
 	}
 
 	/*
@@ -2401,12 +3531,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
-	if (relation->rd_partkeycxt)
-		MemoryContextDelete(relation->rd_partkeycxt);
-	if (relation->rd_pdcxt)
-		MemoryContextDelete(relation->rd_pdcxt);
-	if (relation->rd_partcheck)
-		pfree(relation->rd_partcheck);
+	if (relation->rd_partcxt)
+		MemoryContextDelete(relation->rd_partcxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2573,8 +3699,6 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 		bool		keep_policies;
-		bool		keep_partkey;
-		bool		keep_partdesc;
 
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
@@ -2605,10 +3729,6 @@ RelationClearRelation(Relation relation, bool rebuild)
 		keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
 		keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
 		keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
-		keep_partkey = (relation->rd_partkey != NULL);
-		keep_partdesc = equalPartitionDescs(relation->rd_partkey,
-											relation->rd_partdesc,
-											newrel->rd_partdesc);
 
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
@@ -2663,18 +3783,6 @@ RelationClearRelation(Relation relation, bool rebuild)
 		SWAPFIELD(Oid, rd_toastoid);
 		/* pgstat_info must be preserved */
 		SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
-		/* partition key must be preserved, if we have one */
-		if (keep_partkey)
-		{
-			SWAPFIELD(PartitionKey, rd_partkey);
-			SWAPFIELD(MemoryContext, rd_partkeycxt);
-		}
-		/* preserve old partdesc if no logical change */
-		if (keep_partdesc)
-		{
-			SWAPFIELD(PartitionDesc, rd_partdesc);
-			SWAPFIELD(MemoryContext, rd_pdcxt);
-		}
 
 #undef SWAPFIELD
 
@@ -3912,19 +5020,10 @@ RelationCacheInitializePhase3(void)
 		/*
 		 * Reload the partition key and descriptor for a partitioned table.
 		 */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
-			relation->rd_partkey == NULL)
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
-			RelationBuildPartitionKey(relation);
+			RelationBuildPartitionInfo(relation);
 			Assert(relation->rd_partkey != NULL);
-
-			restart = true;
-		}
-
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
-			relation->rd_partdesc == NULL)
-		{
-			RelationBuildPartitionDesc(relation);
 			Assert(relation->rd_partdesc != NULL);
 
 			restart = true;
@@ -5777,9 +6876,9 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
-		rel->rd_partkeycxt = NULL;
+		rel->rd_partcxt = NULL;
 		rel->rd_partkey = NULL;
-		rel->rd_pdcxt = NULL;
+		rel->rd_partsupfuncinfo = NULL;
 		rel->rd_partdesc = NULL;
 		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index b25e25bf9d..6683b34c42 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -22,27 +22,22 @@
 /* Seed for the extended hash function */
 #define HASH_PARTITION_SEED UINT64CONST(0x7A5B22367996DCFD)
 
-/*
- * PartitionBoundInfo encapsulates a set of partition bounds.  It is usually
- * associated with partitioned tables as part of its partition descriptor.
- *
- * The internal structure appears in partbounds.h.
- */
-typedef struct PartitionBoundInfoData *PartitionBoundInfo;
+extern PartitionInfo *RelationGetPartitionInfo(Relation relation);
+extern PartitionKey RelationGetPartitionKey(Relation relation);
+extern int RelationGetPartitionCount(Relation relation);
+extern Oid *RelationGetPartitionOids(Relation relation);
+extern Oid	RelationGetDefaultPartitionOid(Relation rel);
+extern List *RelationGetPartitionQual(Relation rel);
+extern Expr *get_partition_qual_relid(Oid relid);
 
-/*
- * Information about partitions of a partitioned table.
- */
-typedef struct PartitionDescData
-{
-	int			nparts;			/* Number of partitions */
-	Oid		   *oids;			/* OIDs of partitions */
-	PartitionBoundInfo boundinfo;	/* collection of partition bounds */
-} PartitionDescData;
-
-typedef struct PartitionDescData *PartitionDesc;
-
-extern void RelationBuildPartitionDesc(Relation relation);
+extern PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
+					 List *datums, bool lower);
+extern int32 partition_hbound_cmp(int modulus1, int remainder1, int modulus2,
+					 int remainder2);
+extern int32 partition_rbound_cmp(int partnatts, FmgrInfo *partsupfuncinfo,
+					 Oid *partcollation, Datum *datums1,
+					 PartitionRangeDatumKind *kind1, bool lower1,
+					 PartitionRangeBound *b2);
 extern bool partition_bounds_equal(int partnatts, int16 *parttyplen,
 					   bool *parttypbyval, PartitionBoundInfo b1,
 					   PartitionBoundInfo b2);
@@ -58,19 +53,13 @@ extern List *get_qual_from_partbound(Relation rel, Relation parent,
 extern List *map_partition_varattnos(List *expr, int fromrel_varno,
 						Relation to_rel, Relation from_rel,
 						bool *found_whole_row);
-extern List *RelationGetPartitionQual(Relation rel);
-extern Expr *get_partition_qual_relid(Oid relid);
 extern bool has_partition_attrs(Relation rel, Bitmapset *attnums,
 					bool *used_in_expr);
 
-extern Oid	get_default_oid_from_partdesc(PartitionDesc partdesc);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
 extern void check_default_allows_bound(Relation parent, Relation defaultRel,
 						   PartitionBoundSpec *new_spec);
 extern List *get_proposed_default_constraint(List *new_part_constaints);
 
-extern int get_partition_for_tuple(Relation relation, Datum *values,
-						bool *isnull);
-
 #endif							/* PARTITION_H */
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index b2daf24c41..e83e42577e 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -26,14 +26,16 @@
  *	reldesc		Relation descriptor of the table
  *	key			Partition key information of the table
  *	keystate	Execution state required for expressions in the partition key
- *	partdesc	Partition descriptor of the table
+ *	partsupfuncinfo		fmgr lookup info of partition support functions
+ *	nparts		Number of partitions of the table
+ *	boundinfo	PartitionBoundInfo of the table
  *	tupslot		A standalone TupleTableSlot initialized with this table's tuple
  *				descriptor
  *	tupmap		TupleConversionMap to convert from the parent's rowtype to
  *				this table's rowtype (when extracting the partition key of a
  *				tuple just before routing it through this table)
- *	indexes		Array with partdesc->nparts members (for details on what
- *				individual members represent, see how they are set in
+ *	indexes		Array with nparts members (for details on what individual
+ *				members represent, see how they are set in
  *				get_partition_dispatch_recurse())
  *-----------------------
  */
@@ -42,7 +44,9 @@ typedef struct PartitionDispatchData
 	Relation	reldesc;
 	PartitionKey key;
 	List	   *keystate;		/* list of ExprState */
-	PartitionDesc partdesc;
+	FmgrInfo	partsupfuncinfo[PARTITION_MAX_KEYS];
+	int			nparts;
+	PartitionBoundInfo boundinfo;
 	TupleTableSlot *tupslot;
 	TupleConversionMap *tupmap;
 	int		   *indexes;
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 73a41c5475..84f3e6a9c6 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -360,7 +360,7 @@ typedef struct PartitionSchemeData
 	bool	   *parttypbyval;
 
 	/* Cached information about partition comparison functions. */
-	FmgrInfo   *partsupfunc;
+	FmgrInfo   *partsupfuncinfo;
 }			PartitionSchemeData;
 
 typedef struct PartitionSchemeData *PartitionScheme;
diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h
index c76014d4a8..bd239c2250 100644
--- a/src/include/partitioning/partbounds.h
+++ b/src/include/partitioning/partbounds.h
@@ -11,9 +11,6 @@
 #ifndef PARTBOUNDS_H
 #define PARTBOUNDS_H
 
-#include "catalog/partition.h"
-
-
 /*
  * PartitionBoundInfoData encapsulates a set of partition bounds. It is
  * usually associated with partitioned tables as part of its partition
@@ -68,6 +65,8 @@ typedef struct PartitionBoundInfoData
 								 * isn't one */
 } PartitionBoundInfoData;
 
+typedef struct PartitionBoundInfoData *PartitionBoundInfo;
+
 #define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1)
 #define partition_bound_has_default(bi) ((bi)->default_index != -1)
 
@@ -101,22 +100,23 @@ typedef struct PartitionRangeBound
 } PartitionRangeBound;
 
 extern int	get_hash_partition_greatest_modulus(PartitionBoundInfo b);
-extern int partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
+extern int partition_list_bsearch(FmgrInfo *partsupfuncinfo,
+					   Oid *partcollation,
 					   PartitionBoundInfo boundinfo,
 					   Datum value, bool *is_equal);
-extern int partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
+extern int partition_range_bsearch(int partnatts, FmgrInfo *partsupfuncinfo,
 						Oid *partcollation,
 						PartitionBoundInfo boundinfo,
 						PartitionRangeBound *probe, bool *is_equal);
-extern int partition_range_datum_bsearch(FmgrInfo *partsupfunc,
+extern int partition_range_datum_bsearch(FmgrInfo *partsupfuncinfo,
 							  Oid *partcollation,
 							  PartitionBoundInfo boundinfo,
 							  int nvalues, Datum *values, bool *is_equal);
 extern int partition_hash_bsearch(PartitionBoundInfo boundinfo,
 					   int modulus, int remainder);
-extern uint64 compute_hash_value(int partnatts, FmgrInfo *partsupfunc,
+extern uint64 compute_hash_value(int partnatts, FmgrInfo *partsupfuncinfo,
 				   Datum *values, bool *isnull);
-extern int32 partition_rbound_datum_cmp(FmgrInfo *partsupfunc,
+extern int32 partition_rbound_datum_cmp(FmgrInfo *partsupfuncinfo,
 						   Oid *partcollation,
 						   Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
 						   Datum *tuple_datums, int n_tuple_datums);
diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h
index 2ae2fd16ed..f078dbdc82 100644
--- a/src/include/partitioning/partprune.h
+++ b/src/include/partitioning/partprune.h
@@ -30,7 +30,7 @@ typedef struct PartitionPruneContext
 	Oid		   *partopfamily;
 	Oid		   *partopcintype;
 	Oid		   *partcollation;
-	FmgrInfo   *partsupfunc;
+	FmgrInfo	partsupfuncinfo[PARTITION_MAX_KEYS];
 
 	/* Number of partitions */
 	int			nparts;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ffffde01da..6c81029c68 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -21,6 +21,7 @@
 #include "catalog/pg_publication.h"
 #include "fmgr.h"
 #include "nodes/bitmapset.h"
+#include "partitioning/partbounds.h"
 #include "rewrite/prs2lock.h"
 #include "storage/block.h"
 #include "storage/relfilenode.h"
@@ -60,7 +61,7 @@ typedef struct PartitionKeyData
 
 	Oid		   *partopfamily;	/* OIDs of operator families */
 	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
-	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+	Oid		   *partsupfunc;	/* partition support func OIDs */
 
 	/* Partitioning collation per attribute */
 	Oid		   *partcollation;
@@ -125,9 +126,11 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
-	MemoryContext rd_partkeycxt;	/* private memory cxt for the below */
+	MemoryContext rd_partcxt;	/* private memory cxt for the values contained
+								 * in the fields related to partitioning */
 	struct PartitionKeyData *rd_partkey;	/* partition key, or NULL */
-	MemoryContext rd_pdcxt;		/* private context for partdesc */
+	FmgrInfo   *rd_partsupfuncinfo;	/* lookup info for partition support
+									 * procs */
 	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
 	List	   *rd_partcheck;	/* partition CHECK quals */
 
@@ -248,6 +251,21 @@ typedef struct ForeignKeyCacheInfo
 } ForeignKeyCacheInfo;
 
 /*
+ * PartitionInfo
+ *		Information related to partitioning for a partitioned table
+ *
+ * This consists of the information from PartitionKey and PartitionDesc
+ * structs that are cached in the table's RelationData.
+ */
+typedef struct PartitionInfo
+{
+	PartitionKey	key;
+	int				nparts;
+	Oid			   *oids;
+	struct PartitionBoundInfoData *boundinfo;
+} PartitionInfo;
+
+/*
  * Options common for all all indexes
  */
 typedef struct GenericIndexOpts
@@ -612,11 +630,8 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
-/*
- * RelationGetPartitionKey
- *		Returns the PartitionKey of a relation
- */
-#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+extern FmgrInfo *partition_getprocinfo(Relation rel, PartitionKey key,
+									   int partattoff);
 
 /*
  * PartitionKey inquiry functions
@@ -660,12 +675,6 @@ get_partition_col_typmod(PartitionKey key, int col)
 	return key->parttypmod[col];
 }
 
-/*
- * RelationGetPartitionDesc
- *		Returns partition descriptor for a relation.
- */
-#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
-
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.11.0

#7Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Amit Langote (#6)
Re: crash with sql language partition support function

Amit Langote wrote:

Anyway, after reading your replies, I thought of taking a stab at unifying
the partitioning information that's cached by relcache.c.

Wow. Now that's one large patch. I'm going to run with this for HEAD,
but I think we should do a minimal fix for PG10. Did you detect any
further bugs, while doing all this rework, apart from the one that
started this thread? If not, I would prefer to do commit the minimal
fix at start of thread to both branches, then apply the larger
restructuring patch to HEAD only.

For the record, I don't like the amount of code that this is putting in
relcache.c. I am thinking that most of that code will go to
src/backend/partitioning/partbounds.c instead.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#8Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Amit Langote (#1)
Re: crash with sql language partition support function

Amit Langote wrote:

Since this bug also exists in the released PG 10 branch, I also created a
patch for that. It's slightly different than the one for PG 11dev,
because there were some changes recently to how the memory context is
manipulated in RelationBuildPartitionKey. That's
v1-PG10-0001-Fix-a-memory-context-bug-in-RelationBuildPartitio.patch.

Here's a repro for pg10, which doesn't have hash partitioning:

create function my_int4_sort(int4,int4) returns int language sql
as $$ select case when $1 = $2 then 0 when $1 > $2 then 1 else -1 end; $$;
create operator class test_int4_ops for type int4 using btree as
operator 1 < (int4,int4), operator 2 <= (int4,int4),
operator 3 = (int4,int4), operator 4 >= (int4,int4),
operator 5 > (int4,int4), function 1 my_int4_sort(int4,int4);
create table t (a int4) partition by range (a test_int4_ops);
create table t1 partition of t for values from (0) to (1000);
insert into t values (100);
insert into t values (200); -- *boom*

I'm dealing with this now -- will push shortly. The sane thing to do is
backpatch my previous memcxt fixes, since your patch introduces a
problem that we discussed with that other patch, namely that you would
leak the whole memory context if there is a problem while running the
function. I also noticed here that copy_partition_key is doing memcpy()
on the fmgr_info, which is bogus -- it should be using fmgr_info_copy.
Rather than patching that one piece it seems better to replace it
wholesale, since I bet this won't be the last time we'll hear about this
routine, and I would prefer not to deal with differently broken code in
the older branch.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#9Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#7)
Re: crash with sql language partition support function

On Thu, Apr 12, 2018 at 8:55 AM, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

Amit Langote wrote:

Anyway, after reading your replies, I thought of taking a stab at unifying
the partitioning information that's cached by relcache.c.

Wow. Now that's one large patch. I'm going to run with this for HEAD,
but I think we should do a minimal fix for PG10.

Is there really a compelling reason to do more than minimal fixes in
HEAD? We are (or should be) trying to stabilize this branch so we can
ship it and start the next one, and the chances that heavy hacking on
this will delay that process seem better than average.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#10Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Alvaro Herrera (#8)
Re: crash with sql language partition support function

Alvaro Herrera wrote:

I'm dealing with this now -- will push shortly. The sane thing to do is
backpatch my previous memcxt fixes, since your patch introduces a
problem that we discussed with that other patch, namely that you would
leak the whole memory context if there is a problem while running the
function. I also noticed here that copy_partition_key is doing memcpy()
on the fmgr_info, which is bogus -- it should be using fmgr_info_copy.
Rather than patching that one piece it seems better to replace it
wholesale, since I bet this won't be the last time we'll hear about this
routine, and I would prefer not to deal with differently broken code in
the older branch.

Pushed.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#11Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Robert Haas (#9)
Re: crash with sql language partition support function

Robert Haas wrote:

On Thu, Apr 12, 2018 at 8:55 AM, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

Amit Langote wrote:

Anyway, after reading your replies, I thought of taking a stab at unifying
the partitioning information that's cached by relcache.c.

Wow. Now that's one large patch. I'm going to run with this for HEAD,
but I think we should do a minimal fix for PG10.

Is there really a compelling reason to do more than minimal fixes in
HEAD?

IMO there is ample reason for restructuring the lot of code that currently
lives in catalog/partition.c. We're going to have to support this code
for a minimum of six years -- and that's only if we get around to
reorganizing it during pg12. I don't have a lot of faith that I'll be
motivated to reorganize it in pg12, but I do know that I am motivated to
reorganize it now. If we do happen to reorganize it in pg12, then any
bugs we find afterwards will cost double in terms of backpatching the
fixes than if we reorganize it now. I don't want my future self to
curse my current self for not doing it when it was appropriate -- i.e.
when it was fresh in my mind and freshly written.

We are (or should be) trying to stabilize this branch so we can
ship it and start the next one,

Yes, that is what I am trying to do -- I want us to have a sane base
upon which to do our work for years to come.

Do we need more than minimal fixes in the memory context, FmgrInfo, and
miscellaneous other fixes that Amit is proposing in this patch? That I
don't know. I hope to have an answer for this later, and I think this
is the reason for your final comment:

and the chances that heavy hacking on this will delay that process
seem better than average.

Maybe if there are no bugs and it's just ugly coding, it is best left
alone. But as I said, I don't know the answer yet.

I will make sure to propose any functional code changes separately from
code movement.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#12Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Amit Langote (#6)
Re: crash with sql language partition support function

I wonder what prompted people to #include "catalog/partition.h" in
executor.h.

Amit Langote wrote:

Anyway, after reading your replies, I thought of taking a stab at unifying
the partitioning information that's cached by relcache.c.

After going over your patch, I think you went slightly overboard here.
Or maybe not, but this patch is so large that it's hard to form an
opinion about it. Some of these cleanups we should probably adopt per
discussion upthread, but I think we're at a point where we have to work
in smaller steps to avoid destabilizing the code.

I'm not sure about the new PartitionInfo that you propose. I see you
had to add partitioning/partbounds.h to rel.h -- not a fan of that.
I was thinking of a simpler fix there, just remove one of the two
memcxts in RelationData and put both structs in the remaining one.
Maybe I'm not ambitious enough.

Here's what I would propose: create partitioning/partbounds.c to deal
with partition bounds (i.e. mostly PartitionBoundInfoData and
PartitionBoundSpec), and have utils/cache/partcache.c (with
corresponding utils/partcache.h) for RelationGetPartitionDesc and
RelationGetPartitionKey (not much else, it seems).

Maybe also move get_partition_for_tuple to execPartition.c?

Preliminarly, I would say that the following functions would be in
partbounds.c (in roughly this order):

Exported:

get_qual_from_partbound
partition_bounds_equal
partition_bounds_copy
check_new_partition_bound
check_default_allows_bound
get_hash_partition_greatest_modulus
make_one_range_bound

qsort_partition_list_value_cmp
partition_rbound_cmp
partition_rbound_datum_cmp
qsort_partition_hbound_cmp
partition_hbound_cmp

partition_list_bsearch
partition_range_bsearch
partition_range_datum_bsearch
partition_hash_bsearch

static:
get_partition_bound_num_indexes
make_partition_op_expr
get_partition_operator
get_qual_for_hash
get_qual_for_list
get_qual_for_range
get_range_key_properties
get_range_nulltest

Unsure yet about compute_hash_value and satisfies_hash_partition.

The rest would remain in catalog/partition.c, which should hopefully not
be a lot.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#13Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Alvaro Herrera (#12)
1 attachment(s)
Re: crash with sql language partition support function

On 2018/04/13 6:58, Alvaro Herrera wrote:

I wonder what prompted people to #include "catalog/partition.h" in
executor.h.

I regret having done it. Removing it from there is no one-line patch. :-(

Amit Langote wrote:

Anyway, after reading your replies, I thought of taking a stab at unifying
the partitioning information that's cached by relcache.c.

After going over your patch, I think you went slightly overboard here.
Or maybe not, but this patch is so large that it's hard to form an
opinion about it.

It's mostly code movement, but there are some other changes as well as
described below.

Some of these cleanups we should probably adopt per
discussion upthread, but I think we're at a point where we have to work
in smaller steps to avoid destabilizing the code.

OK, I agree. There are a few main things this patch does:

1. Code reorganization (across partition.c, relcache.c, partbounds.c, and
partcache.c)

2. Allocate all partitioning info in relcache, which include PartitionKey,
PartitionDesc, and partition constraint expression tree, in the same
context

3. Make copying of partitioning info in relcache by various callers a bit
more consistent. After doing that, the guards in RelationClearRelation
to preserve unchanged partition info are no longer necessary.

4. Cache FmgrInfo's outside PartitionKey, leaving just OIDs there, and add
a separate function to copy it to some caller-specified memory (and
context).

I'm not sure about the new PartitionInfo that you propose.

I too am no longer sure if there is any point in adding an extra
indirection if callers could instead directly ask for members like
partition key, nparts, oids, and boundinfo. So, I've gotten rid of it and
RelationGetPartitionInfo.

I see you
had to add partitioning/partbounds.h to rel.h -- not a fan of that.
I was thinking of a simpler fix there, just remove one of the two
memcxts in RelationData and put both structs in the remaining one.
Maybe I'm not ambitious enough.

Now, there's just rd_partcxt. Both RelationBuildPartitionKey and
RelationBuildPartitionDesc and partition constraint expression tree are
allocated in that context.

Here's what I would propose: create partitioning/partbounds.c to deal
with partition bounds (i.e. mostly PartitionBoundInfoData and
PartitionBoundSpec),

OK, I've done that. It exports:

get_qual_from_partbound
partition_bounds_equal
partition_bounds_copy
check_new_partition_bound
check_default_allows_bound
get_hash_partition_greatest_modulus
make_one_range_bound
partition_rbound_cmp
partition_rbound_datum_cmp
qsort_partition_hbound_cmp
partition_hbound_cmp

I have kept qsort_partition_* functions in partcache.c, because they're
only locally used.

and the following are static:

get_partition_bound_num_indexes
make_partition_op_expr
get_partition_operator
get_qual_for_hash
get_qual_for_list
get_qual_for_range
get_range_key_properties
get_range_nulltest

This means a bunch of code was migrated from catalog/partition.c to
partitioning/partbounds.c.

and have utils/cache/partcache.c (with
corresponding utils/partcache.h) for RelationGetPartitionDesc and
RelationGetPartitionKey (not much else, it seems).

OK, there is now a utils/cache/partcache.c as well.

Beside RelationGetPartitionKey and RelationGetPartitionDesc you mentioned,
it also has RelationgGetPartitionQual() and get_partition_qual_relid(),
because they touch the relcache (rd_partcheck) data. Also, there are
following locals:

qsort_partition_hbound_cmp
qsort_partition_list_value_cmp
qsort_partition_rbound_cmp
partition_key_copy
generate_partition_qual

Other than the above mentioned exported functions, I've introduced the
following functions which return copy of the information in relcache.

PartitionKey RelationGetPartitionKey(Relation relation);
FmgrInfo *partition_getprocinfo(Relation rel, PartitionKey key,
int partattoff);
int RelationGetPartitionCount(Relation relation);
Oid *RelationGetPartitionOids(Relation relation);
PartitionBoundInfo RelationGetPartitionBounds(Relation relation);
Oid RelationGetDefaultPartitionOid(Relation rel);

Maybe also move get_partition_for_tuple to execPartition.c?

Have done that already.

Unsure yet about compute_hash_value and satisfies_hash_partition.

I've left them in catalog/partition.c for now.

The rest would remain in catalog/partition.c, which should hopefully not
be a lot.

Not much after the latest version of the patch.

$ wc -l src/backend/catalog/partition.c
642 src/backend/catalog/partition.c

In master:

$ wc -l src/backend/catalog/partition.c
3497 src/backend/catalog/partition.c

Thanks,
Amit

Attachments:

v3-0001-Reorganize-partitioning-code-structure-and-partit.patchtext/plain; charset=UTF-8; name=v3-0001-Reorganize-partitioning-code-structure-and-partit.patchDownload
From 38242e4820da78dca8c8e4640028ee950628c759 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 10 Apr 2018 15:38:19 +0900
Subject: [PATCH v3] Reorganize partitioning code structure and partition info
 caching

---
 src/backend/catalog/heap.c             |    3 +-
 src/backend/catalog/partition.c        | 2913 +-------------------------------
 src/backend/catalog/pg_constraint.c    |    8 +-
 src/backend/commands/indexcmds.c       |    8 +-
 src/backend/commands/tablecmds.c       |   77 +-
 src/backend/commands/trigger.c         |   22 +-
 src/backend/executor/execMain.c        |    1 -
 src/backend/executor/execPartition.c   |  154 +-
 src/backend/optimizer/path/joinrels.c  |    2 +-
 src/backend/optimizer/prep/prepunion.c |   13 +-
 src/backend/optimizer/util/plancat.c   |   77 +-
 src/backend/optimizer/util/relnode.c   |    2 +-
 src/backend/parser/parse_utilcmd.c     |    4 +-
 src/backend/partitioning/Makefile      |    2 +-
 src/backend/partitioning/partbounds.c  | 2072 +++++++++++++++++++++++
 src/backend/partitioning/partprune.c   |   33 +-
 src/backend/utils/adt/ruleutils.c      |    1 -
 src/backend/utils/cache/Makefile       |    4 +-
 src/backend/utils/cache/partcache.c    | 1149 +++++++++++++
 src/backend/utils/cache/relcache.c     |  344 +---
 src/include/catalog/partition.h        |   39 -
 src/include/commands/tablecmds.h       |    2 +-
 src/include/executor/execPartition.h   |   13 +-
 src/include/partitioning/partbounds.h  |   41 +-
 src/include/partitioning/partprune.h   |    4 +-
 src/include/utils/partcache.h          |   66 +
 src/include/utils/rel.h                |   64 +-
 27 files changed, 3675 insertions(+), 3443 deletions(-)
 create mode 100644 src/backend/partitioning/partbounds.c
 create mode 100644 src/backend/utils/cache/partcache.c
 create mode 100644 src/include/utils/partcache.h

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8f329a6299..bdb47001c8 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -77,6 +77,7 @@
 #include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/partcache.h"
 #include "utils/rel.h"
 #include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
@@ -3472,7 +3473,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	 * relcache entry for that partition every time a partition is added or
 	 * removed.
 	 */
-	defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(parent));
+	defaultPartOid = RelationGetDefaultPartitionOid(parent);
 	if (OidIsValid(defaultPartOid))
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 55130e66e4..e7e18d37da 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,7 +29,6 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
-#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -45,1243 +44,18 @@
 #include "storage/lmgr.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
-#include "utils/datum.h"
 #include "utils/fmgroids.h"
 #include "utils/hashutils.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/partcache.h"
 #include "utils/rel.h"
-#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 
-
 static Oid	get_partition_parent_worker(Relation inhRel, Oid relid);
 static void get_partition_ancestors_worker(Relation inhRel, Oid relid,
 							   List **ancestors);
-static int32 qsort_partition_hbound_cmp(const void *a, const void *b);
-static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
-							   void *arg);
-static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
-						   void *arg);
-
-static Oid get_partition_operator(PartitionKey key, int col,
-					   StrategyNumber strategy, bool *need_relabel);
-static Expr *make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2);
-static void get_range_key_properties(PartitionKey key, int keynum,
-						 PartitionRangeDatum *ldatum,
-						 PartitionRangeDatum *udatum,
-						 ListCell **partexprs_item,
-						 Expr **keyCol,
-						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_hash(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
-				   bool for_default);
-static List *get_range_nulltest(PartitionKey key);
-static List *generate_partition_qual(Relation rel);
-
-static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
-					 List *datums, bool lower);
-static int32 partition_hbound_cmp(int modulus1, int remainder1, int modulus2,
-					 int remainder2);
-static int32 partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
-					 Oid *partcollation, Datum *datums1,
-					 PartitionRangeDatumKind *kind1, bool lower1,
-					 PartitionRangeBound *b2);
-
-static int	get_partition_bound_num_indexes(PartitionBoundInfo b);
-
-
-/*
- * RelationBuildPartitionDesc
- *		Form rel's partition descriptor
- *
- * Not flushed from the cache by RelationClearRelation() unless changed because
- * of addition or removal of partition.
- */
-void
-RelationBuildPartitionDesc(Relation rel)
-{
-	List	   *inhoids,
-			   *partoids;
-	Oid		   *oids = NULL;
-	List	   *boundspecs = NIL;
-	ListCell   *cell;
-	int			i,
-				nparts;
-	PartitionKey key = RelationGetPartitionKey(rel);
-	PartitionDesc result;
-	MemoryContext oldcxt;
-
-	int			ndatums = 0;
-	int			default_index = -1;
-
-	/* Hash partitioning specific */
-	PartitionHashBound **hbounds = NULL;
-
-	/* List partitioning specific */
-	PartitionListValue **all_values = NULL;
-	int			null_index = -1;
-
-	/* Range partitioning specific */
-	PartitionRangeBound **rbounds = NULL;
-
-	/* Get partition oids from pg_inherits */
-	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
-
-	/* Collect bound spec nodes in a list */
-	i = 0;
-	partoids = NIL;
-	foreach(cell, inhoids)
-	{
-		Oid			inhrelid = lfirst_oid(cell);
-		HeapTuple	tuple;
-		Datum		datum;
-		bool		isnull;
-		Node	   *boundspec;
-
-		tuple = SearchSysCache1(RELOID, inhrelid);
-		if (!HeapTupleIsValid(tuple))
-			elog(ERROR, "cache lookup failed for relation %u", inhrelid);
-
-		/*
-		 * It is possible that the pg_class tuple of a partition has not been
-		 * updated yet to set its relpartbound field.  The only case where
-		 * this happens is when we open the parent relation to check using its
-		 * partition descriptor that a new partition's bound does not overlap
-		 * some existing partition.
-		 */
-		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
-		{
-			ReleaseSysCache(tuple);
-			continue;
-		}
-
-		datum = SysCacheGetAttr(RELOID, tuple,
-								Anum_pg_class_relpartbound,
-								&isnull);
-		Assert(!isnull);
-		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
-
-		/*
-		 * Sanity check: If the PartitionBoundSpec says this is the default
-		 * partition, its OID should correspond to whatever's stored in
-		 * pg_partitioned_table.partdefid; if not, the catalog is corrupt.
-		 */
-		if (castNode(PartitionBoundSpec, boundspec)->is_default)
-		{
-			Oid			partdefid;
-
-			partdefid = get_default_partition_oid(RelationGetRelid(rel));
-			if (partdefid != inhrelid)
-				elog(ERROR, "expected partdefid %u, but got %u",
-					 inhrelid, partdefid);
-		}
-
-		boundspecs = lappend(boundspecs, boundspec);
-		partoids = lappend_oid(partoids, inhrelid);
-		ReleaseSysCache(tuple);
-	}
-
-	nparts = list_length(partoids);
-
-	if (nparts > 0)
-	{
-		oids = (Oid *) palloc(nparts * sizeof(Oid));
-		i = 0;
-		foreach(cell, partoids)
-			oids[i++] = lfirst_oid(cell);
-
-		/* Convert from node to the internal representation */
-		if (key->strategy == PARTITION_STRATEGY_HASH)
-		{
-			ndatums = nparts;
-			hbounds = (PartitionHashBound **)
-				palloc(nparts * sizeof(PartitionHashBound *));
-
-			i = 0;
-			foreach(cell, boundspecs)
-			{
-				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
-													lfirst(cell));
-
-				if (spec->strategy != PARTITION_STRATEGY_HASH)
-					elog(ERROR, "invalid strategy in partition bound spec");
-
-				hbounds[i] = (PartitionHashBound *)
-					palloc(sizeof(PartitionHashBound));
-
-				hbounds[i]->modulus = spec->modulus;
-				hbounds[i]->remainder = spec->remainder;
-				hbounds[i]->index = i;
-				i++;
-			}
-
-			/* Sort all the bounds in ascending order */
-			qsort(hbounds, nparts, sizeof(PartitionHashBound *),
-				  qsort_partition_hbound_cmp);
-		}
-		else if (key->strategy == PARTITION_STRATEGY_LIST)
-		{
-			List	   *non_null_values = NIL;
-
-			/*
-			 * Create a unified list of non-null values across all partitions.
-			 */
-			i = 0;
-			null_index = -1;
-			foreach(cell, boundspecs)
-			{
-				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
-													lfirst(cell));
-				ListCell   *c;
-
-				if (spec->strategy != PARTITION_STRATEGY_LIST)
-					elog(ERROR, "invalid strategy in partition bound spec");
-
-				/*
-				 * Note the index of the partition bound spec for the default
-				 * partition. There's no datum to add to the list of non-null
-				 * datums for this partition.
-				 */
-				if (spec->is_default)
-				{
-					default_index = i;
-					i++;
-					continue;
-				}
-
-				foreach(c, spec->listdatums)
-				{
-					Const	   *val = castNode(Const, lfirst(c));
-					PartitionListValue *list_value = NULL;
-
-					if (!val->constisnull)
-					{
-						list_value = (PartitionListValue *)
-							palloc0(sizeof(PartitionListValue));
-						list_value->index = i;
-						list_value->value = val->constvalue;
-					}
-					else
-					{
-						/*
-						 * Never put a null into the values array, flag
-						 * instead for the code further down below where we
-						 * construct the actual relcache struct.
-						 */
-						if (null_index != -1)
-							elog(ERROR, "found null more than once");
-						null_index = i;
-					}
-
-					if (list_value)
-						non_null_values = lappend(non_null_values,
-												  list_value);
-				}
-
-				i++;
-			}
-
-			ndatums = list_length(non_null_values);
-
-			/*
-			 * Collect all list values in one array. Alongside the value, we
-			 * also save the index of partition the value comes from.
-			 */
-			all_values = (PartitionListValue **) palloc(ndatums *
-														sizeof(PartitionListValue *));
-			i = 0;
-			foreach(cell, non_null_values)
-			{
-				PartitionListValue *src = lfirst(cell);
-
-				all_values[i] = (PartitionListValue *)
-					palloc(sizeof(PartitionListValue));
-				all_values[i]->value = src->value;
-				all_values[i]->index = src->index;
-				i++;
-			}
-
-			qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
-					  qsort_partition_list_value_cmp, (void *) key);
-		}
-		else if (key->strategy == PARTITION_STRATEGY_RANGE)
-		{
-			int			k;
-			PartitionRangeBound **all_bounds,
-					   *prev;
-
-			all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
-														  sizeof(PartitionRangeBound *));
-
-			/*
-			 * Create a unified list of range bounds across all the
-			 * partitions.
-			 */
-			i = ndatums = 0;
-			foreach(cell, boundspecs)
-			{
-				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
-													lfirst(cell));
-				PartitionRangeBound *lower,
-						   *upper;
-
-				if (spec->strategy != PARTITION_STRATEGY_RANGE)
-					elog(ERROR, "invalid strategy in partition bound spec");
-
-				/*
-				 * Note the index of the partition bound spec for the default
-				 * partition. There's no datum to add to the allbounds array
-				 * for this partition.
-				 */
-				if (spec->is_default)
-				{
-					default_index = i++;
-					continue;
-				}
-
-				lower = make_one_range_bound(key, i, spec->lowerdatums,
-											 true);
-				upper = make_one_range_bound(key, i, spec->upperdatums,
-											 false);
-				all_bounds[ndatums++] = lower;
-				all_bounds[ndatums++] = upper;
-				i++;
-			}
-
-			Assert(ndatums == nparts * 2 ||
-				   (default_index != -1 && ndatums == (nparts - 1) * 2));
-
-			/* Sort all the bounds in ascending order */
-			qsort_arg(all_bounds, ndatums,
-					  sizeof(PartitionRangeBound *),
-					  qsort_partition_rbound_cmp,
-					  (void *) key);
-
-			/* Save distinct bounds from all_bounds into rbounds. */
-			rbounds = (PartitionRangeBound **)
-				palloc(ndatums * sizeof(PartitionRangeBound *));
-			k = 0;
-			prev = NULL;
-			for (i = 0; i < ndatums; i++)
-			{
-				PartitionRangeBound *cur = all_bounds[i];
-				bool		is_distinct = false;
-				int			j;
-
-				/* Is the current bound distinct from the previous one? */
-				for (j = 0; j < key->partnatts; j++)
-				{
-					Datum		cmpval;
-
-					if (prev == NULL || cur->kind[j] != prev->kind[j])
-					{
-						is_distinct = true;
-						break;
-					}
-
-					/*
-					 * If the bounds are both MINVALUE or MAXVALUE, stop now
-					 * and treat them as equal, since any values after this
-					 * point must be ignored.
-					 */
-					if (cur->kind[j] != PARTITION_RANGE_DATUM_VALUE)
-						break;
-
-					cmpval = FunctionCall2Coll(&key->partsupfunc[j],
-											   key->partcollation[j],
-											   cur->datums[j],
-											   prev->datums[j]);
-					if (DatumGetInt32(cmpval) != 0)
-					{
-						is_distinct = true;
-						break;
-					}
-				}
-
-				/*
-				 * Only if the bound is distinct save it into a temporary
-				 * array i.e. rbounds which is later copied into boundinfo
-				 * datums array.
-				 */
-				if (is_distinct)
-					rbounds[k++] = all_bounds[i];
-
-				prev = cur;
-			}
-
-			/* Update ndatums to hold the count of distinct datums. */
-			ndatums = k;
-		}
-		else
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) key->strategy);
-	}
-
-	/* Now build the actual relcache partition descriptor */
-	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
-										  "partition descriptor",
-										  ALLOCSET_DEFAULT_SIZES);
-	MemoryContextCopyAndSetIdentifier(rel->rd_pdcxt, RelationGetRelationName(rel));
-
-	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
-
-	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
-	result->nparts = nparts;
-	if (nparts > 0)
-	{
-		PartitionBoundInfo boundinfo;
-		int		   *mapping;
-		int			next_index = 0;
-
-		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
-
-		boundinfo = (PartitionBoundInfoData *)
-			palloc0(sizeof(PartitionBoundInfoData));
-		boundinfo->strategy = key->strategy;
-		boundinfo->default_index = -1;
-		boundinfo->ndatums = ndatums;
-		boundinfo->null_index = -1;
-		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
-
-		/* Initialize mapping array with invalid values */
-		mapping = (int *) palloc(sizeof(int) * nparts);
-		for (i = 0; i < nparts; i++)
-			mapping[i] = -1;
-
-		switch (key->strategy)
-		{
-			case PARTITION_STRATEGY_HASH:
-				{
-					/* Modulus are stored in ascending order */
-					int			greatest_modulus = hbounds[ndatums - 1]->modulus;
-
-					boundinfo->indexes = (int *) palloc(greatest_modulus *
-														sizeof(int));
-
-					for (i = 0; i < greatest_modulus; i++)
-						boundinfo->indexes[i] = -1;
-
-					for (i = 0; i < nparts; i++)
-					{
-						int			modulus = hbounds[i]->modulus;
-						int			remainder = hbounds[i]->remainder;
-
-						boundinfo->datums[i] = (Datum *) palloc(2 *
-																sizeof(Datum));
-						boundinfo->datums[i][0] = Int32GetDatum(modulus);
-						boundinfo->datums[i][1] = Int32GetDatum(remainder);
-
-						while (remainder < greatest_modulus)
-						{
-							/* overlap? */
-							Assert(boundinfo->indexes[remainder] == -1);
-							boundinfo->indexes[remainder] = i;
-							remainder += modulus;
-						}
-
-						mapping[hbounds[i]->index] = i;
-						pfree(hbounds[i]);
-					}
-					pfree(hbounds);
-					break;
-				}
-
-			case PARTITION_STRATEGY_LIST:
-				{
-					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
-
-					/*
-					 * Copy values.  Indexes of individual values are mapped
-					 * to canonical values so that they match for any two list
-					 * partitioned tables with same number of partitions and
-					 * same lists per partition.  One way to canonicalize is
-					 * to assign the index in all_values[] of the smallest
-					 * value of each partition, as the index of all of the
-					 * partition's values.
-					 */
-					for (i = 0; i < ndatums; i++)
-					{
-						boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
-						boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
-															key->parttypbyval[0],
-															key->parttyplen[0]);
-
-						/* If the old index has no mapping, assign one */
-						if (mapping[all_values[i]->index] == -1)
-							mapping[all_values[i]->index] = next_index++;
-
-						boundinfo->indexes[i] = mapping[all_values[i]->index];
-					}
-
-					/*
-					 * If null-accepting partition has no mapped index yet,
-					 * assign one.  This could happen if such partition
-					 * accepts only null and hence not covered in the above
-					 * loop which only handled non-null values.
-					 */
-					if (null_index != -1)
-					{
-						Assert(null_index >= 0);
-						if (mapping[null_index] == -1)
-							mapping[null_index] = next_index++;
-						boundinfo->null_index = mapping[null_index];
-					}
-
-					/* Assign mapped index for the default partition. */
-					if (default_index != -1)
-					{
-						/*
-						 * The default partition accepts any value not
-						 * specified in the lists of other partitions, hence
-						 * it should not get mapped index while assigning
-						 * those for non-null datums.
-						 */
-						Assert(default_index >= 0 &&
-							   mapping[default_index] == -1);
-						mapping[default_index] = next_index++;
-						boundinfo->default_index = mapping[default_index];
-					}
-
-					/* All partition must now have a valid mapping */
-					Assert(next_index == nparts);
-					break;
-				}
-
-			case PARTITION_STRATEGY_RANGE:
-				{
-					boundinfo->kind = (PartitionRangeDatumKind **)
-						palloc(ndatums *
-							   sizeof(PartitionRangeDatumKind *));
-					boundinfo->indexes = (int *) palloc((ndatums + 1) *
-														sizeof(int));
-
-					for (i = 0; i < ndatums; i++)
-					{
-						int			j;
-
-						boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
-																sizeof(Datum));
-						boundinfo->kind[i] = (PartitionRangeDatumKind *)
-							palloc(key->partnatts *
-								   sizeof(PartitionRangeDatumKind));
-						for (j = 0; j < key->partnatts; j++)
-						{
-							if (rbounds[i]->kind[j] == PARTITION_RANGE_DATUM_VALUE)
-								boundinfo->datums[i][j] =
-									datumCopy(rbounds[i]->datums[j],
-											  key->parttypbyval[j],
-											  key->parttyplen[j]);
-							boundinfo->kind[i][j] = rbounds[i]->kind[j];
-						}
-
-						/*
-						 * There is no mapping for invalid indexes.
-						 *
-						 * Any lower bounds in the rbounds array have invalid
-						 * indexes assigned, because the values between the
-						 * previous bound (if there is one) and this (lower)
-						 * bound are not part of the range of any existing
-						 * partition.
-						 */
-						if (rbounds[i]->lower)
-							boundinfo->indexes[i] = -1;
-						else
-						{
-							int			orig_index = rbounds[i]->index;
-
-							/* If the old index has no mapping, assign one */
-							if (mapping[orig_index] == -1)
-								mapping[orig_index] = next_index++;
-
-							boundinfo->indexes[i] = mapping[orig_index];
-						}
-					}
-
-					/* Assign mapped index for the default partition. */
-					if (default_index != -1)
-					{
-						Assert(default_index >= 0 && mapping[default_index] == -1);
-						mapping[default_index] = next_index++;
-						boundinfo->default_index = mapping[default_index];
-					}
-					boundinfo->indexes[i] = -1;
-					break;
-				}
-
-			default:
-				elog(ERROR, "unexpected partition strategy: %d",
-					 (int) key->strategy);
-		}
-
-		result->boundinfo = boundinfo;
-
-		/*
-		 * Now assign OIDs from the original array into mapped indexes of the
-		 * result array.  Order of OIDs in the former is defined by the
-		 * catalog scan that retrieved them, whereas that in the latter is
-		 * defined by canonicalized representation of the partition bounds.
-		 */
-		for (i = 0; i < nparts; i++)
-			result->oids[mapping[i]] = oids[i];
-		pfree(mapping);
-	}
-
-	MemoryContextSwitchTo(oldcxt);
-	rel->rd_partdesc = result;
-}
-
-/*
- * Are two partition bound collections logically equal?
- *
- * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
- * This is also useful when b1 and b2 are bound collections of two separate
- * relations, respectively, because PartitionBoundInfo is a canonical
- * representation of partition bounds.
- */
-bool
-partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
-					   PartitionBoundInfo b1, PartitionBoundInfo b2)
-{
-	int			i;
-
-	if (b1->strategy != b2->strategy)
-		return false;
-
-	if (b1->ndatums != b2->ndatums)
-		return false;
-
-	if (b1->null_index != b2->null_index)
-		return false;
-
-	if (b1->default_index != b2->default_index)
-		return false;
-
-	if (b1->strategy == PARTITION_STRATEGY_HASH)
-	{
-		int			greatest_modulus = get_hash_partition_greatest_modulus(b1);
-
-		/*
-		 * If two hash partitioned tables have different greatest moduli,
-		 * their partition schemes don't match.
-		 */
-		if (greatest_modulus != get_hash_partition_greatest_modulus(b2))
-			return false;
-
-		/*
-		 * We arrange the partitions in the ascending order of their modulus
-		 * and remainders.  Also every modulus is factor of next larger
-		 * modulus.  Therefore we can safely store index of a given partition
-		 * in indexes array at remainder of that partition.  Also entries at
-		 * (remainder + N * modulus) positions in indexes array are all same
-		 * for (modulus, remainder) specification for any partition.  Thus
-		 * datums array from both the given bounds are same, if and only if
-		 * their indexes array will be same.  So, it suffices to compare
-		 * indexes array.
-		 */
-		for (i = 0; i < greatest_modulus; i++)
-			if (b1->indexes[i] != b2->indexes[i])
-				return false;
-
-#ifdef USE_ASSERT_CHECKING
-
-		/*
-		 * Nonetheless make sure that the bounds are indeed same when the
-		 * indexes match.  Hash partition bound stores modulus and remainder
-		 * at b1->datums[i][0] and b1->datums[i][1] position respectively.
-		 */
-		for (i = 0; i < b1->ndatums; i++)
-			Assert((b1->datums[i][0] == b2->datums[i][0] &&
-					b1->datums[i][1] == b2->datums[i][1]));
-#endif
-	}
-	else
-	{
-		for (i = 0; i < b1->ndatums; i++)
-		{
-			int			j;
-
-			for (j = 0; j < partnatts; j++)
-			{
-				/* For range partitions, the bounds might not be finite. */
-				if (b1->kind != NULL)
-				{
-					/* The different kinds of bound all differ from each other */
-					if (b1->kind[i][j] != b2->kind[i][j])
-						return false;
-
-					/*
-					 * Non-finite bounds are equal without further
-					 * examination.
-					 */
-					if (b1->kind[i][j] != PARTITION_RANGE_DATUM_VALUE)
-						continue;
-				}
-
-				/*
-				 * Compare the actual values. Note that it would be both
-				 * incorrect and unsafe to invoke the comparison operator
-				 * derived from the partitioning specification here.  It would
-				 * be incorrect because we want the relcache entry to be
-				 * updated for ANY change to the partition bounds, not just
-				 * those that the partitioning operator thinks are
-				 * significant.  It would be unsafe because we might reach
-				 * this code in the context of an aborted transaction, and an
-				 * arbitrary partitioning operator might not be safe in that
-				 * 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;
-			}
-
-			if (b1->indexes[i] != b2->indexes[i])
-				return false;
-		}
-
-		/* There are ndatums+1 indexes in case of range partitions */
-		if (b1->strategy == PARTITION_STRATEGY_RANGE &&
-			b1->indexes[i] != b2->indexes[i])
-			return false;
-	}
-	return true;
-}
-
-/*
- * Return a copy of given PartitionBoundInfo structure. The data types of bounds
- * are described by given partition key specification.
- */
-PartitionBoundInfo
-partition_bounds_copy(PartitionBoundInfo src,
-					  PartitionKey key)
-{
-	PartitionBoundInfo dest;
-	int			i;
-	int			ndatums;
-	int			partnatts;
-	int			num_indexes;
-
-	dest = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
-
-	dest->strategy = src->strategy;
-	ndatums = dest->ndatums = src->ndatums;
-	partnatts = key->partnatts;
-
-	num_indexes = get_partition_bound_num_indexes(src);
-
-	/* List partitioned tables have only a single partition key. */
-	Assert(key->strategy != PARTITION_STRATEGY_LIST || partnatts == 1);
-
-	dest->datums = (Datum **) palloc(sizeof(Datum *) * ndatums);
-
-	if (src->kind != NULL)
-	{
-		dest->kind = (PartitionRangeDatumKind **) palloc(ndatums *
-														 sizeof(PartitionRangeDatumKind *));
-		for (i = 0; i < ndatums; i++)
-		{
-			dest->kind[i] = (PartitionRangeDatumKind *) palloc(partnatts *
-															   sizeof(PartitionRangeDatumKind));
-
-			memcpy(dest->kind[i], src->kind[i],
-				   sizeof(PartitionRangeDatumKind) * key->partnatts);
-		}
-	}
-	else
-		dest->kind = NULL;
-
-	for (i = 0; i < ndatums; i++)
-	{
-		int			j;
-
-		/*
-		 * For a corresponding to hash partition, datums array will have two
-		 * elements - modulus and remainder.
-		 */
-		bool		hash_part = (key->strategy == PARTITION_STRATEGY_HASH);
-		int			natts = hash_part ? 2 : partnatts;
-
-		dest->datums[i] = (Datum *) palloc(sizeof(Datum) * natts);
-
-		for (j = 0; j < natts; j++)
-		{
-			bool		byval;
-			int			typlen;
-
-			if (hash_part)
-			{
-				typlen = sizeof(int32); /* Always int4 */
-				byval = true;	/* int4 is pass-by-value */
-			}
-			else
-			{
-				byval = key->parttypbyval[j];
-				typlen = key->parttyplen[j];
-			}
-
-			if (dest->kind == NULL ||
-				dest->kind[i][j] == PARTITION_RANGE_DATUM_VALUE)
-				dest->datums[i][j] = datumCopy(src->datums[i][j],
-											   byval, typlen);
-		}
-	}
-
-	dest->indexes = (int *) palloc(sizeof(int) * num_indexes);
-	memcpy(dest->indexes, src->indexes, sizeof(int) * num_indexes);
-
-	dest->null_index = src->null_index;
-	dest->default_index = src->default_index;
-
-	return dest;
-}
-
-/*
- * check_new_partition_bound
- *
- * Checks if the new partition's bound overlaps any of the existing partitions
- * of parent.  Also performs additional checks as necessary per strategy.
- */
-void
-check_new_partition_bound(char *relname, Relation parent,
-						  PartitionBoundSpec *spec)
-{
-	PartitionKey key = RelationGetPartitionKey(parent);
-	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
-	PartitionBoundInfo boundinfo = partdesc->boundinfo;
-	ParseState *pstate = make_parsestate(NULL);
-	int			with = -1;
-	bool		overlap = false;
-
-	if (spec->is_default)
-	{
-		/*
-		 * The default partition bound never conflicts with any other
-		 * partition's; if that's what we're attaching, the only possible
-		 * problem is that one already exists, so check for that and we're
-		 * done.
-		 */
-		if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
-			return;
-
-		/* Default partition already exists, error out. */
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
-						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
-				 parser_errposition(pstate, spec->location)));
-	}
-
-	switch (key->strategy)
-	{
-		case PARTITION_STRATEGY_HASH:
-			{
-				Assert(spec->strategy == PARTITION_STRATEGY_HASH);
-				Assert(spec->remainder >= 0 && spec->remainder < spec->modulus);
-
-				if (partdesc->nparts > 0)
-				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
-					Datum	  **datums = boundinfo->datums;
-					int			ndatums = boundinfo->ndatums;
-					int			greatest_modulus;
-					int			remainder;
-					int			offset;
-					bool		valid_modulus = true;
-					int			prev_modulus,	/* Previous largest modulus */
-								next_modulus;	/* Next largest modulus */
-
-					/*
-					 * Check rule that every modulus must be a factor of the
-					 * next larger modulus.  For example, if you have a bunch
-					 * of partitions that all have modulus 5, you can add a
-					 * new partition with modulus 10 or a new partition with
-					 * modulus 15, but you cannot add both a partition with
-					 * modulus 10 and a partition with modulus 15, because 10
-					 * is not a factor of 15.
-					 *
-					 * Get the greatest (modulus, remainder) pair contained in
-					 * boundinfo->datums that is less than or equal to the
-					 * (spec->modulus, spec->remainder) pair.
-					 */
-					offset = partition_hash_bsearch(boundinfo,
-													spec->modulus,
-													spec->remainder);
-					if (offset < 0)
-					{
-						next_modulus = DatumGetInt32(datums[0][0]);
-						valid_modulus = (next_modulus % spec->modulus) == 0;
-					}
-					else
-					{
-						prev_modulus = DatumGetInt32(datums[offset][0]);
-						valid_modulus = (spec->modulus % prev_modulus) == 0;
-
-						if (valid_modulus && (offset + 1) < ndatums)
-						{
-							next_modulus = DatumGetInt32(datums[offset + 1][0]);
-							valid_modulus = (next_modulus % spec->modulus) == 0;
-						}
-					}
-
-					if (!valid_modulus)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-								 errmsg("every hash partition modulus must be a factor of the next larger modulus")));
-
-					greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
-					remainder = spec->remainder;
-
-					/*
-					 * Normally, the lowest remainder that could conflict with
-					 * the new partition is equal to the remainder specified
-					 * for the new partition, but when the new partition has a
-					 * modulus higher than any used so far, we need to adjust.
-					 */
-					if (remainder >= greatest_modulus)
-						remainder = remainder % greatest_modulus;
-
-					/* Check every potentially-conflicting remainder. */
-					do
-					{
-						if (boundinfo->indexes[remainder] != -1)
-						{
-							overlap = true;
-							with = boundinfo->indexes[remainder];
-							break;
-						}
-						remainder += spec->modulus;
-					} while (remainder < greatest_modulus);
-				}
-
-				break;
-			}
-
-		case PARTITION_STRATEGY_LIST:
-			{
-				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-
-				if (partdesc->nparts > 0)
-				{
-					ListCell   *cell;
-
-					Assert(boundinfo &&
-						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
-						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo) ||
-							partition_bound_has_default(boundinfo)));
-
-					foreach(cell, spec->listdatums)
-					{
-						Const	   *val = castNode(Const, lfirst(cell));
-
-						if (!val->constisnull)
-						{
-							int			offset;
-							bool		equal;
-
-							offset = partition_list_bsearch(key->partsupfunc,
-														key->partcollation,
-															boundinfo,
-															val->constvalue,
-															&equal);
-							if (offset >= 0 && equal)
-							{
-								overlap = true;
-								with = boundinfo->indexes[offset];
-								break;
-							}
-						}
-						else if (partition_bound_accepts_nulls(boundinfo))
-						{
-							overlap = true;
-							with = boundinfo->null_index;
-							break;
-						}
-					}
-				}
-
-				break;
-			}
-
-		case PARTITION_STRATEGY_RANGE:
-			{
-				PartitionRangeBound *lower,
-						   *upper;
-
-				Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
-				lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
-				upper = make_one_range_bound(key, -1, spec->upperdatums, false);
-
-				/*
-				 * 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->kind, true, upper) >= 0)
-				{
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-							 errmsg("empty range bound specified for partition \"%s\"",
-									relname),
-							 errdetail("Specified lower bound %s is greater than or equal to upper bound %s.",
-									   get_range_partbound_string(spec->lowerdatums),
-									   get_range_partbound_string(spec->upperdatums)),
-							 parser_errposition(pstate, spec->location)));
-				}
-
-				if (partdesc->nparts > 0)
-				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
-					int			offset;
-					bool		equal;
-
-					Assert(boundinfo &&
-						   boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
-						   (boundinfo->ndatums > 0 ||
-							partition_bound_has_default(boundinfo)));
-
-					/*
-					 * Test whether the new lower bound (which is treated
-					 * inclusively as part of the new partition) lies inside
-					 * an existing partition, or in a gap.
-					 *
-					 * If it's inside an existing partition, the bound at
-					 * offset + 1 will be the upper bound of that partition,
-					 * and its index will be >= 0.
-					 *
-					 * If it's in a gap, the bound at offset + 1 will be the
-					 * lower bound of the next partition, and its index will
-					 * be -1. This is also true if there is no next partition,
-					 * since the index array is initialised with an extra -1
-					 * at the end.
-					 */
-					offset = partition_range_bsearch(key->partnatts,
-													 key->partsupfunc,
-													 key->partcollation,
-													 boundinfo, lower,
-													 &equal);
-
-					if (boundinfo->indexes[offset + 1] < 0)
-					{
-						/*
-						 * Check that the new partition will fit in the gap.
-						 * For it to fit, the new upper bound must be less
-						 * than or equal to the lower bound of the next
-						 * partition, if there is one.
-						 */
-						if (offset + 1 < boundinfo->ndatums)
-						{
-							int32		cmpval;
-							Datum 	   *datums;
-							PartitionRangeDatumKind *kind;
-							bool		is_lower;
-
-							datums = boundinfo->datums[offset + 1];
-							kind = boundinfo->kind[offset + 1];
-							is_lower = (boundinfo->indexes[offset + 1] == -1);
-
-							cmpval = partition_rbound_cmp(key->partnatts,
-														  key->partsupfunc,
-														  key->partcollation,
-														  datums, kind,
-														  is_lower, upper);
-							if (cmpval < 0)
-							{
-								/*
-								 * The new partition overlaps with the
-								 * existing partition between offset + 1 and
-								 * offset + 2.
-								 */
-								overlap = true;
-								with = boundinfo->indexes[offset + 2];
-							}
-						}
-					}
-					else
-					{
-						/*
-						 * The new partition overlaps with the existing
-						 * partition between offset and offset + 1.
-						 */
-						overlap = true;
-						with = boundinfo->indexes[offset + 1];
-					}
-				}
-
-				break;
-			}
-
-		default:
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) key->strategy);
-	}
-
-	if (overlap)
-	{
-		Assert(with >= 0);
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("partition \"%s\" would overlap partition \"%s\"",
-						relname, get_rel_name(partdesc->oids[with])),
-				 parser_errposition(pstate, spec->location)));
-	}
-}
-
-/*
- * check_default_allows_bound
- *
- * This function checks if there exists a row in the default partition that
- * would properly belong to the new partition being added.  If it finds one,
- * it throws an error.
- */
-void
-check_default_allows_bound(Relation parent, Relation default_rel,
-						   PartitionBoundSpec *new_spec)
-{
-	List	   *new_part_constraints;
-	List	   *def_part_constraints;
-	List	   *all_parts;
-	ListCell   *lc;
-
-	new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
-		? get_qual_for_list(parent, new_spec)
-		: get_qual_for_range(parent, new_spec, false);
-	def_part_constraints =
-		get_proposed_default_constraint(new_part_constraints);
-
-	/*
-	 * If the existing constraints on the default partition imply that it will
-	 * not contain any row that would belong to the new partition, we can
-	 * avoid scanning the default partition.
-	 */
-	if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
-	{
-		ereport(INFO,
-				(errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints",
-						RelationGetRelationName(default_rel))));
-		return;
-	}
-
-	/*
-	 * Scan the default partition and its subpartitions, and check for rows
-	 * that do not satisfy the revised partition constraints.
-	 */
-	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
-										AccessExclusiveLock, NULL);
-	else
-		all_parts = list_make1_oid(RelationGetRelid(default_rel));
-
-	foreach(lc, all_parts)
-	{
-		Oid			part_relid = lfirst_oid(lc);
-		Relation	part_rel;
-		Expr	   *constr;
-		Expr	   *partition_constraint;
-		EState	   *estate;
-		HeapTuple	tuple;
-		ExprState  *partqualstate = NULL;
-		Snapshot	snapshot;
-		TupleDesc	tupdesc;
-		ExprContext *econtext;
-		HeapScanDesc scan;
-		MemoryContext oldCxt;
-		TupleTableSlot *tupslot;
-
-		/* Lock already taken above. */
-		if (part_relid != RelationGetRelid(default_rel))
-		{
-			part_rel = heap_open(part_relid, NoLock);
-
-			/*
-			 * If the partition constraints on default partition child imply
-			 * that it will not contain any row that would belong to the new
-			 * partition, we can avoid scanning the child table.
-			 */
-			if (PartConstraintImpliedByRelConstraint(part_rel,
-													 def_part_constraints))
-			{
-				ereport(INFO,
-						(errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints",
-								RelationGetRelationName(part_rel))));
-
-				heap_close(part_rel, NoLock);
-				continue;
-			}
-		}
-		else
-			part_rel = default_rel;
-
-		/*
-		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
-		 * scanned.
-		 */
-		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
-		{
-			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
-				ereport(WARNING,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
-								RelationGetRelationName(part_rel),
-								RelationGetRelationName(default_rel))));
-
-			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
-				heap_close(part_rel, NoLock);
-
-			continue;
-		}
-
-		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
-		constr = linitial(def_part_constraints);
-		partition_constraint = (Expr *)
-			map_partition_varattnos((List *) constr,
-									1, part_rel, parent, NULL);
-		estate = CreateExecutorState();
-
-		/* Build expression execution states for partition check quals */
-		partqualstate = ExecPrepareExpr(partition_constraint, estate);
-
-		econtext = GetPerTupleExprContext(estate);
-		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
-		tupslot = MakeSingleTupleTableSlot(tupdesc);
-
-		/*
-		 * Switch to per-tuple memory context and reset it for each tuple
-		 * produced, so we don't leak memory.
-		 */
-		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
-		{
-			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
-			econtext->ecxt_scantuple = tupslot;
-
-			if (!ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
-								RelationGetRelationName(default_rel))));
-
-			ResetExprContext(econtext);
-			CHECK_FOR_INTERRUPTS();
-		}
-
-		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
-		UnregisterSnapshot(snapshot);
-		ExecDropSingleTupleTableSlot(tupslot);
-		FreeExecutorState(estate);
-
-		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
-			heap_close(part_rel, NoLock);	/* keep the lock until commit */
-	}
-}
 
 /*
  * get_partition_parent
@@ -1392,45 +166,6 @@ get_partition_ancestors_worker(Relation inhRel, Oid relid, List **ancestors)
 }
 
 /*
- * get_qual_from_partbound
- *		Given a parser node for partition bound, return the list of executable
- *		expressions as partition constraint
- */
-List *
-get_qual_from_partbound(Relation rel, Relation parent,
-						PartitionBoundSpec *spec)
-{
-	PartitionKey key = RelationGetPartitionKey(parent);
-	List	   *my_qual = NIL;
-
-	Assert(key != NULL);
-
-	switch (key->strategy)
-	{
-		case PARTITION_STRATEGY_HASH:
-			Assert(spec->strategy == PARTITION_STRATEGY_HASH);
-			my_qual = get_qual_for_hash(parent, spec);
-			break;
-
-		case PARTITION_STRATEGY_LIST:
-			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(parent, spec);
-			break;
-
-		case PARTITION_STRATEGY_RANGE:
-			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
-			my_qual = get_qual_for_range(parent, spec, false);
-			break;
-
-		default:
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) key->strategy);
-	}
-
-	return my_qual;
-}
-
-/*
  * map_partition_varattnos - maps varattno of any Vars in expr from the
  * attno's of 'from_rel' to the attno's of 'to_rel' partition, each of which
  * may be either a leaf partition or a partitioned table, but both of which
@@ -1476,1152 +211,6 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 }
 
 /*
- * RelationGetPartitionQual
- *
- * Returns a list of partition quals
- */
-List *
-RelationGetPartitionQual(Relation rel)
-{
-	/* Quick exit */
-	if (!rel->rd_rel->relispartition)
-		return NIL;
-
-	return generate_partition_qual(rel);
-}
-
-/*
- * get_partition_qual_relid
- *
- * Returns an expression tree describing the passed-in relation's partition
- * constraint. If there is no partition constraint returns NULL; this can
- * happen if the default partition is the only partition.
- */
-Expr *
-get_partition_qual_relid(Oid relid)
-{
-	Relation	rel = heap_open(relid, AccessShareLock);
-	Expr	   *result = NULL;
-	List	   *and_args;
-
-	/* Do the work only if this relation is a partition. */
-	if (rel->rd_rel->relispartition)
-	{
-		and_args = generate_partition_qual(rel);
-
-		if (and_args == NIL)
-			result = NULL;
-		else if (list_length(and_args) > 1)
-			result = makeBoolExpr(AND_EXPR, and_args, -1);
-		else
-			result = linitial(and_args);
-	}
-
-	/* Keep the lock. */
-	heap_close(rel, NoLock);
-
-	return result;
-}
-
-
-/*
- * get_partition_operator
- *
- * Return oid of the operator of given strategy for a given partition key
- * column.
- */
-static Oid
-get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
-					   bool *need_relabel)
-{
-	Oid			operoid;
-
-	/*
-	 * First check if there exists an operator of the given strategy, with
-	 * this column's type as both its lefttype and righttype, in the
-	 * partitioning operator family specified for the column.
-	 */
-	operoid = get_opfamily_member(key->partopfamily[col],
-								  key->parttypid[col],
-								  key->parttypid[col],
-								  strategy);
-
-	/*
-	 * If one doesn't exist, we must resort to using an operator in the same
-	 * operator family but with the operator class declared input type.  It is
-	 * OK to do so, because the column's type is known to be binary-coercible
-	 * with the operator class input type (otherwise, the operator class in
-	 * question would not have been accepted as the partitioning operator
-	 * class).  We must however inform the caller to wrap the non-Const
-	 * expression with a RelabelType node to denote the implicit coercion. It
-	 * ensures that the resulting expression structurally matches similarly
-	 * processed expressions within the optimizer.
-	 */
-	if (!OidIsValid(operoid))
-	{
-		operoid = get_opfamily_member(key->partopfamily[col],
-									  key->partopcintype[col],
-									  key->partopcintype[col],
-									  strategy);
-		if (!OidIsValid(operoid))
-			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
-				 strategy, key->partopcintype[col], key->partopcintype[col],
-				 key->partopfamily[col]);
-		*need_relabel = true;
-	}
-	else
-		*need_relabel = false;
-
-	return operoid;
-}
-
-/*
- * make_partition_op_expr
- *		Returns an Expr for the given partition key column with arg1 and
- *		arg2 as its leftop and rightop, respectively
- */
-static Expr *
-make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2)
-{
-	Oid			operoid;
-	bool		need_relabel = false;
-	Expr	   *result = NULL;
-
-	/* Get the correct btree operator for this partitioning column */
-	operoid = get_partition_operator(key, keynum, strategy, &need_relabel);
-
-	/*
-	 * Chosen operator may be such that the non-Const operand needs to be
-	 * coerced, so apply the same; see the comment in
-	 * get_partition_operator().
-	 */
-	if (!IsA(arg1, Const) &&
-		(need_relabel ||
-		 key->partcollation[keynum] != key->parttypcoll[keynum]))
-		arg1 = (Expr *) makeRelabelType(arg1,
-										key->partopcintype[keynum],
-										-1,
-										key->partcollation[keynum],
-										COERCE_EXPLICIT_CAST);
-
-	/* Generate the actual expression */
-	switch (key->strategy)
-	{
-		case PARTITION_STRATEGY_LIST:
-			{
-				List	   *elems = (List *) arg2;
-				int			nelems = list_length(elems);
-
-				Assert(nelems >= 1);
-				Assert(keynum == 0);
-
-				if (nelems > 1 &&
-					!type_is_array(key->parttypid[keynum]))
-				{
-					ArrayExpr  *arrexpr;
-					ScalarArrayOpExpr *saopexpr;
-
-					/* Construct an ArrayExpr for the right-hand inputs */
-					arrexpr = makeNode(ArrayExpr);
-					arrexpr->array_typeid =
-									get_array_type(key->parttypid[keynum]);
-					arrexpr->array_collid = key->parttypcoll[keynum];
-					arrexpr->element_typeid = key->parttypid[keynum];
-					arrexpr->elements = elems;
-					arrexpr->multidims = false;
-					arrexpr->location = -1;
-
-					/* Build leftop = ANY (rightop) */
-					saopexpr = makeNode(ScalarArrayOpExpr);
-					saopexpr->opno = operoid;
-					saopexpr->opfuncid = get_opcode(operoid);
-					saopexpr->useOr = true;
-					saopexpr->inputcollid = key->partcollation[keynum];
-					saopexpr->args = list_make2(arg1, arrexpr);
-					saopexpr->location = -1;
-
-					result = (Expr *) saopexpr;
-				}
-				else
-				{
-					List	   *elemops = NIL;
-					ListCell   *lc;
-
-					foreach (lc, elems)
-					{
-						Expr   *elem = lfirst(lc),
-							   *elemop;
-
-						elemop = make_opclause(operoid,
-											   BOOLOID,
-											   false,
-											   arg1, elem,
-											   InvalidOid,
-											   key->partcollation[keynum]);
-						elemops = lappend(elemops, elemop);
-					}
-
-					result = nelems > 1 ? makeBoolExpr(OR_EXPR, elemops, -1) : linitial(elemops);
-				}
-				break;
-			}
-
-		case PARTITION_STRATEGY_RANGE:
-			result = make_opclause(operoid,
-								   BOOLOID,
-								   false,
-								   arg1, arg2,
-								   InvalidOid,
-								   key->partcollation[keynum]);
-			break;
-
-		default:
-			elog(ERROR, "invalid partitioning strategy");
-			break;
-	}
-
-	return result;
-}
-
-/*
- * get_qual_for_hash
- *
- * Returns a CHECK constraint expression to use as a hash partition's
- * constraint, given the parent relation and partition bound structure.
- *
- * The partition constraint for a hash partition is always a call to the
- * built-in function satisfies_hash_partition().
- */
-static List *
-get_qual_for_hash(Relation parent, PartitionBoundSpec *spec)
-{
-	PartitionKey key = RelationGetPartitionKey(parent);
-	FuncExpr   *fexpr;
-	Node	   *relidConst;
-	Node	   *modulusConst;
-	Node	   *remainderConst;
-	List	   *args;
-	ListCell   *partexprs_item;
-	int			i;
-
-	/* Fixed arguments. */
-	relidConst = (Node *) makeConst(OIDOID,
-									-1,
-									InvalidOid,
-									sizeof(Oid),
-									ObjectIdGetDatum(RelationGetRelid(parent)),
-									false,
-									true);
-
-	modulusConst = (Node *) makeConst(INT4OID,
-									  -1,
-									  InvalidOid,
-									  sizeof(int32),
-									  Int32GetDatum(spec->modulus),
-									  false,
-									  true);
-
-	remainderConst = (Node *) makeConst(INT4OID,
-										-1,
-										InvalidOid,
-										sizeof(int32),
-										Int32GetDatum(spec->remainder),
-										false,
-										true);
-
-	args = list_make3(relidConst, modulusConst, remainderConst);
-	partexprs_item = list_head(key->partexprs);
-
-	/* Add an argument for each key column. */
-	for (i = 0; i < key->partnatts; i++)
-	{
-		Node	   *keyCol;
-
-		/* Left operand */
-		if (key->partattrs[i] != 0)
-		{
-			keyCol = (Node *) makeVar(1,
-									  key->partattrs[i],
-									  key->parttypid[i],
-									  key->parttypmod[i],
-									  key->parttypcoll[i],
-									  0);
-		}
-		else
-		{
-			keyCol = (Node *) copyObject(lfirst(partexprs_item));
-			partexprs_item = lnext(partexprs_item);
-		}
-
-		args = lappend(args, keyCol);
-	}
-
-	fexpr = makeFuncExpr(F_SATISFIES_HASH_PARTITION,
-						 BOOLOID,
-						 args,
-						 InvalidOid,
-						 InvalidOid,
-						 COERCE_EXPLICIT_CALL);
-
-	return list_make1(fexpr);
-}
-
-/*
- * get_qual_for_list
- *
- * Returns an implicit-AND list of expressions to use as a list partition's
- * constraint, given the parent relation and partition bound structure.
- *
- * The function returns NIL for a default partition when it's the only
- * partition since in that case there is no constraint.
- */
-static List *
-get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
-{
-	PartitionKey key = RelationGetPartitionKey(parent);
-	List	   *result;
-	Expr	   *keyCol;
-	Expr	   *opexpr;
-	NullTest   *nulltest;
-	ListCell   *cell;
-	List	   *elems = NIL;
-	bool		list_has_null = false;
-
-	/*
-	 * Only single-column list partitioning is supported, so we are worried
-	 * only about the partition key with index 0.
-	 */
-	Assert(key->partnatts == 1);
-
-	/* Construct Var or expression representing the partition column */
-	if (key->partattrs[0] != 0)
-		keyCol = (Expr *) makeVar(1,
-								  key->partattrs[0],
-								  key->parttypid[0],
-								  key->parttypmod[0],
-								  key->parttypcoll[0],
-								  0);
-	else
-		keyCol = (Expr *) copyObject(linitial(key->partexprs));
-
-	/*
-	 * For default list partition, collect datums for all the partitions. The
-	 * default partition constraint should check that the partition key is
-	 * equal to none of those.
-	 */
-	if (spec->is_default)
-	{
-		int			i;
-		int			ndatums = 0;
-		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
-		PartitionBoundInfo boundinfo = pdesc->boundinfo;
-
-		if (boundinfo)
-		{
-			ndatums = boundinfo->ndatums;
-
-			if (partition_bound_accepts_nulls(boundinfo))
-				list_has_null = true;
-		}
-
-		/*
-		 * If default is the only partition, there need not be any partition
-		 * constraint on it.
-		 */
-		if (ndatums == 0 && !list_has_null)
-			return NIL;
-
-		for (i = 0; i < ndatums; i++)
-		{
-			Const	   *val;
-
-			/*
-			 * Construct Const from known-not-null datum.  We must be careful
-			 * to copy the value, because our result has to be able to outlive
-			 * the relcache entry we're copying from.
-			 */
-			val = makeConst(key->parttypid[0],
-							key->parttypmod[0],
-							key->parttypcoll[0],
-							key->parttyplen[0],
-							datumCopy(*boundinfo->datums[i],
-									  key->parttypbyval[0],
-									  key->parttyplen[0]),
-							false,	/* isnull */
-							key->parttypbyval[0]);
-
-			elems = lappend(elems, val);
-		}
-	}
-	else
-	{
-		/*
-		 * Create list of Consts for the allowed values, excluding any nulls.
-		 */
-		foreach(cell, spec->listdatums)
-		{
-			Const	   *val = castNode(Const, lfirst(cell));
-
-			if (val->constisnull)
-				list_has_null = true;
-			else
-				elems = lappend(elems, copyObject(val));
-		}
-	}
-
-	if (elems)
-	{
-		/*
-		 * Generate the operator expression from the non-null partition
-		 * values.
-		 */
-		opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-										keyCol, (Expr *) elems);
-	}
-	else
-	{
-		/*
-		 * If there are no partition values, we don't need an operator
-		 * expression.
-		 */
-		opexpr = NULL;
-	}
-
-	if (!list_has_null)
-	{
-		/*
-		 * Gin up a "col IS NOT NULL" test that will be AND'd with the main
-		 * expression.  This might seem redundant, but the partition routing
-		 * machinery needs it.
-		 */
-		nulltest = makeNode(NullTest);
-		nulltest->arg = keyCol;
-		nulltest->nulltesttype = IS_NOT_NULL;
-		nulltest->argisrow = false;
-		nulltest->location = -1;
-
-		result = opexpr ? list_make2(nulltest, opexpr) : list_make1(nulltest);
-	}
-	else
-	{
-		/*
-		 * Gin up a "col IS NULL" test that will be OR'd with the main
-		 * expression.
-		 */
-		nulltest = makeNode(NullTest);
-		nulltest->arg = keyCol;
-		nulltest->nulltesttype = IS_NULL;
-		nulltest->argisrow = false;
-		nulltest->location = -1;
-
-		if (opexpr)
-		{
-			Expr	   *or;
-
-			or = makeBoolExpr(OR_EXPR, list_make2(nulltest, opexpr), -1);
-			result = list_make1(or);
-		}
-		else
-			result = list_make1(nulltest);
-	}
-
-	/*
-	 * Note that, in general, applying NOT to a constraint expression doesn't
-	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
-	 * NULL.  However, the partition constraints we construct here never
-	 * evaluate to NULL, so applying NOT works as intended.
-	 */
-	if (spec->is_default)
-	{
-		result = list_make1(make_ands_explicit(result));
-		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
-	}
-
-	return result;
-}
-
-/*
- * get_range_key_properties
- *		Returns range partition key information for a given column
- *
- * This is a subroutine for get_qual_for_range, and its API is pretty
- * specialized to that caller.
- *
- * Constructs an Expr for the key column (returned in *keyCol) and Consts
- * for the lower and upper range limits (returned in *lower_val and
- * *upper_val).  For MINVALUE/MAXVALUE limits, NULL is returned instead of
- * a Const.  All of these structures are freshly palloc'd.
- *
- * *partexprs_item points to the cell containing the next expression in
- * the key->partexprs list, or NULL.  It may be advanced upon return.
- */
-static void
-get_range_key_properties(PartitionKey key, int keynum,
-						 PartitionRangeDatum *ldatum,
-						 PartitionRangeDatum *udatum,
-						 ListCell **partexprs_item,
-						 Expr **keyCol,
-						 Const **lower_val, Const **upper_val)
-{
-	/* Get partition key expression for this column */
-	if (key->partattrs[keynum] != 0)
-	{
-		*keyCol = (Expr *) makeVar(1,
-								   key->partattrs[keynum],
-								   key->parttypid[keynum],
-								   key->parttypmod[keynum],
-								   key->parttypcoll[keynum],
-								   0);
-	}
-	else
-	{
-		if (*partexprs_item == NULL)
-			elog(ERROR, "wrong number of partition key expressions");
-		*keyCol = copyObject(lfirst(*partexprs_item));
-		*partexprs_item = lnext(*partexprs_item);
-	}
-
-	/* Get appropriate Const nodes for the bounds */
-	if (ldatum->kind == PARTITION_RANGE_DATUM_VALUE)
-		*lower_val = castNode(Const, copyObject(ldatum->value));
-	else
-		*lower_val = NULL;
-
-	if (udatum->kind == PARTITION_RANGE_DATUM_VALUE)
-		*upper_val = castNode(Const, copyObject(udatum->value));
-	else
-		*upper_val = NULL;
-}
-
- /*
-  * get_range_nulltest
-  *
-  * A non-default range partition table does not currently allow partition
-  * keys to be null, so emit an IS NOT NULL expression for each key column.
-  */
-static List *
-get_range_nulltest(PartitionKey key)
-{
-	List	   *result = NIL;
-	NullTest   *nulltest;
-	ListCell   *partexprs_item;
-	int			i;
-
-	partexprs_item = list_head(key->partexprs);
-	for (i = 0; i < key->partnatts; i++)
-	{
-		Expr	   *keyCol;
-
-		if (key->partattrs[i] != 0)
-		{
-			keyCol = (Expr *) makeVar(1,
-									  key->partattrs[i],
-									  key->parttypid[i],
-									  key->parttypmod[i],
-									  key->parttypcoll[i],
-									  0);
-		}
-		else
-		{
-			if (partexprs_item == NULL)
-				elog(ERROR, "wrong number of partition key expressions");
-			keyCol = copyObject(lfirst(partexprs_item));
-			partexprs_item = lnext(partexprs_item);
-		}
-
-		nulltest = makeNode(NullTest);
-		nulltest->arg = keyCol;
-		nulltest->nulltesttype = IS_NOT_NULL;
-		nulltest->argisrow = false;
-		nulltest->location = -1;
-		result = lappend(result, nulltest);
-	}
-
-	return result;
-}
-
-/*
- * get_qual_for_range
- *
- * Returns an implicit-AND list of expressions to use as a range partition's
- * constraint, given the parent relation and partition bound structure.
- *
- * For a multi-column range partition key, say (a, b, c), with (al, bl, cl)
- * as the lower bound tuple and (au, bu, cu) as the upper bound tuple, we
- * generate an expression tree of the following form:
- *
- *	(a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
- *		AND
- *	(a > al OR (a = al AND b > bl) OR (a = al AND b = bl AND c >= cl))
- *		AND
- *	(a < au OR (a = au AND b < bu) OR (a = au AND b = bu AND c < cu))
- *
- * It is often the case that a prefix of lower and upper bound tuples contains
- * the same values, for example, (al = au), in which case, we will emit an
- * expression tree of the following form:
- *
- *	(a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
- *		AND
- *	(a = al)
- *		AND
- *	(b > bl OR (b = bl AND c >= cl))
- *		AND
- *	(b < bu) OR (b = bu AND c < cu))
- *
- * If a bound datum is either MINVALUE or MAXVALUE, these expressions are
- * simplified using the fact that any value is greater than MINVALUE and less
- * than MAXVALUE. So, for example, if cu = MAXVALUE, c < cu is automatically
- * true, and we need not emit any expression for it, and the last line becomes
- *
- *	(b < bu) OR (b = bu), which is simplified to (b <= bu)
- *
- * In most common cases with only one partition column, say a, the following
- * expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
- *
- * For default partition, it returns the negation of the constraints of all
- * the other partitions.
- *
- * External callers should pass for_default as false; we set it to true only
- * when recursing.
- */
-static List *
-get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
-				   bool for_default)
-{
-	List	   *result = NIL;
-	ListCell   *cell1,
-			   *cell2,
-			   *partexprs_item,
-			   *partexprs_item_saved;
-	int			i,
-				j;
-	PartitionRangeDatum *ldatum,
-			   *udatum;
-	PartitionKey key = RelationGetPartitionKey(parent);
-	Expr	   *keyCol;
-	Const	   *lower_val,
-			   *upper_val;
-	List	   *lower_or_arms,
-			   *upper_or_arms;
-	int			num_or_arms,
-				current_or_arm;
-	ListCell   *lower_or_start_datum,
-			   *upper_or_start_datum;
-	bool		need_next_lower_arm,
-				need_next_upper_arm;
-
-	if (spec->is_default)
-	{
-		List	   *or_expr_args = NIL;
-		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
-		Oid		   *inhoids = pdesc->oids;
-		int			nparts = pdesc->nparts,
-					i;
-
-		for (i = 0; i < nparts; i++)
-		{
-			Oid			inhrelid = inhoids[i];
-			HeapTuple	tuple;
-			Datum		datum;
-			bool		isnull;
-			PartitionBoundSpec *bspec;
-
-			tuple = SearchSysCache1(RELOID, inhrelid);
-			if (!HeapTupleIsValid(tuple))
-				elog(ERROR, "cache lookup failed for relation %u", inhrelid);
-
-			datum = SysCacheGetAttr(RELOID, tuple,
-									Anum_pg_class_relpartbound,
-									&isnull);
-
-			Assert(!isnull);
-			bspec = (PartitionBoundSpec *)
-				stringToNode(TextDatumGetCString(datum));
-			if (!IsA(bspec, PartitionBoundSpec))
-				elog(ERROR, "expected PartitionBoundSpec");
-
-			if (!bspec->is_default)
-			{
-				List	   *part_qual;
-
-				part_qual = get_qual_for_range(parent, bspec, true);
-
-				/*
-				 * AND the constraints of the partition and add to
-				 * or_expr_args
-				 */
-				or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
-									   ? makeBoolExpr(AND_EXPR, part_qual, -1)
-									   : linitial(part_qual));
-			}
-			ReleaseSysCache(tuple);
-		}
-
-		if (or_expr_args != NIL)
-		{
-			Expr	   *other_parts_constr;
-
-			/*
-			 * Combine the constraints obtained for non-default partitions
-			 * using OR.  As requested, each of the OR's args doesn't include
-			 * the NOT NULL test for partition keys (which is to avoid its
-			 * useless repetition).  Add the same now.
-			 */
-			other_parts_constr =
-				makeBoolExpr(AND_EXPR,
-							 lappend(get_range_nulltest(key),
-									 list_length(or_expr_args) > 1
-									 ? makeBoolExpr(OR_EXPR, or_expr_args,
-													-1)
-									 : linitial(or_expr_args)),
-							 -1);
-
-			/*
-			 * Finally, the default partition contains everything *NOT*
-			 * contained in the non-default partitions.
-			 */
-			result = list_make1(makeBoolExpr(NOT_EXPR,
-											 list_make1(other_parts_constr), -1));
-		}
-
-		return result;
-	}
-
-	lower_or_start_datum = list_head(spec->lowerdatums);
-	upper_or_start_datum = list_head(spec->upperdatums);
-	num_or_arms = key->partnatts;
-
-	/*
-	 * If it is the recursive call for default, we skip the get_range_nulltest
-	 * to avoid accumulating the NullTest on the same keys for each partition.
-	 */
-	if (!for_default)
-		result = get_range_nulltest(key);
-
-	/*
-	 * Iterate over the key columns and check if the corresponding lower and
-	 * upper datums are equal using the btree equality operator for the
-	 * column's type.  If equal, we emit single keyCol = common_value
-	 * expression.  Starting from the first column for which the corresponding
-	 * lower and upper bound datums are not equal, we generate OR expressions
-	 * as shown in the function's header comment.
-	 */
-	i = 0;
-	partexprs_item = list_head(key->partexprs);
-	partexprs_item_saved = partexprs_item;	/* placate compiler */
-	forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums)
-	{
-		EState	   *estate;
-		MemoryContext oldcxt;
-		Expr	   *test_expr;
-		ExprState  *test_exprstate;
-		Datum		test_result;
-		bool		isNull;
-
-		ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
-		udatum = castNode(PartitionRangeDatum, lfirst(cell2));
-
-		/*
-		 * Since get_range_key_properties() modifies partexprs_item, and we
-		 * might need to start over from the previous expression in the later
-		 * part of this function, save away the current value.
-		 */
-		partexprs_item_saved = partexprs_item;
-
-		get_range_key_properties(key, i, ldatum, udatum,
-								 &partexprs_item,
-								 &keyCol,
-								 &lower_val, &upper_val);
-
-		/*
-		 * If either value is NULL, the corresponding partition bound is
-		 * either MINVALUE or MAXVALUE, and we treat them as unequal, because
-		 * even if they're the same, there is no common value to equate the
-		 * key column with.
-		 */
-		if (!lower_val || !upper_val)
-			break;
-
-		/* Create the test expression */
-		estate = CreateExecutorState();
-		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
-		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
-										   (Expr *) lower_val,
-										   (Expr *) upper_val);
-		fix_opfuncids((Node *) test_expr);
-		test_exprstate = ExecInitExpr(test_expr, NULL);
-		test_result = ExecEvalExprSwitchContext(test_exprstate,
-												GetPerTupleExprContext(estate),
-												&isNull);
-		MemoryContextSwitchTo(oldcxt);
-		FreeExecutorState(estate);
-
-		/* If not equal, go generate the OR expressions */
-		if (!DatumGetBool(test_result))
-			break;
-
-		/*
-		 * The bounds for the last key column can't be equal, because such a
-		 * range partition would never be allowed to be defined (it would have
-		 * an empty range otherwise).
-		 */
-		if (i == key->partnatts - 1)
-			elog(ERROR, "invalid range bound specification");
-
-		/* Equal, so generate keyCol = lower_val expression */
-		result = lappend(result,
-						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
-												keyCol, (Expr *) lower_val));
-
-		i++;
-	}
-
-	/* First pair of lower_val and upper_val that are not equal. */
-	lower_or_start_datum = cell1;
-	upper_or_start_datum = cell2;
-
-	/* OR will have as many arms as there are key columns left. */
-	num_or_arms = key->partnatts - i;
-	current_or_arm = 0;
-	lower_or_arms = upper_or_arms = NIL;
-	need_next_lower_arm = need_next_upper_arm = true;
-	while (current_or_arm < num_or_arms)
-	{
-		List	   *lower_or_arm_args = NIL,
-				   *upper_or_arm_args = NIL;
-
-		/* Restart scan of columns from the i'th one */
-		j = i;
-		partexprs_item = partexprs_item_saved;
-
-		for_both_cell(cell1, lower_or_start_datum, cell2, upper_or_start_datum)
-		{
-			PartitionRangeDatum *ldatum_next = NULL,
-					   *udatum_next = NULL;
-
-			ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
-			if (lnext(cell1))
-				ldatum_next = castNode(PartitionRangeDatum,
-									   lfirst(lnext(cell1)));
-			udatum = castNode(PartitionRangeDatum, lfirst(cell2));
-			if (lnext(cell2))
-				udatum_next = castNode(PartitionRangeDatum,
-									   lfirst(lnext(cell2)));
-			get_range_key_properties(key, j, ldatum, udatum,
-									 &partexprs_item,
-									 &keyCol,
-									 &lower_val, &upper_val);
-
-			if (need_next_lower_arm && lower_val)
-			{
-				uint16		strategy;
-
-				/*
-				 * For the non-last columns of this arm, use the EQ operator.
-				 * For the last column of this arm, use GT, unless this is the
-				 * last column of the whole bound check, or the next bound
-				 * datum is MINVALUE, in which case use GE.
-				 */
-				if (j - i < current_or_arm)
-					strategy = BTEqualStrategyNumber;
-				else if (j == key->partnatts - 1 ||
-						 (ldatum_next &&
-						  ldatum_next->kind == PARTITION_RANGE_DATUM_MINVALUE))
-					strategy = BTGreaterEqualStrategyNumber;
-				else
-					strategy = BTGreaterStrategyNumber;
-
-				lower_or_arm_args = lappend(lower_or_arm_args,
-											make_partition_op_expr(key, j,
-																   strategy,
-																   keyCol,
-																   (Expr *) lower_val));
-			}
-
-			if (need_next_upper_arm && upper_val)
-			{
-				uint16		strategy;
-
-				/*
-				 * For the non-last columns of this arm, use the EQ operator.
-				 * For the last column of this arm, use LT, unless the next
-				 * bound datum is MAXVALUE, in which case use LE.
-				 */
-				if (j - i < current_or_arm)
-					strategy = BTEqualStrategyNumber;
-				else if (udatum_next &&
-						 udatum_next->kind == PARTITION_RANGE_DATUM_MAXVALUE)
-					strategy = BTLessEqualStrategyNumber;
-				else
-					strategy = BTLessStrategyNumber;
-
-				upper_or_arm_args = lappend(upper_or_arm_args,
-											make_partition_op_expr(key, j,
-																   strategy,
-																   keyCol,
-																   (Expr *) upper_val));
-			}
-
-			/*
-			 * Did we generate enough of OR's arguments?  First arm considers
-			 * the first of the remaining columns, second arm considers first
-			 * two of the remaining columns, and so on.
-			 */
-			++j;
-			if (j - i > current_or_arm)
-			{
-				/*
-				 * We must not emit any more arms if the new column that will
-				 * be considered is unbounded, or this one was.
-				 */
-				if (!lower_val || !ldatum_next ||
-					ldatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
-					need_next_lower_arm = false;
-				if (!upper_val || !udatum_next ||
-					udatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
-					need_next_upper_arm = false;
-				break;
-			}
-		}
-
-		if (lower_or_arm_args != NIL)
-			lower_or_arms = lappend(lower_or_arms,
-									list_length(lower_or_arm_args) > 1
-									? makeBoolExpr(AND_EXPR, lower_or_arm_args, -1)
-									: linitial(lower_or_arm_args));
-
-		if (upper_or_arm_args != NIL)
-			upper_or_arms = lappend(upper_or_arms,
-									list_length(upper_or_arm_args) > 1
-									? makeBoolExpr(AND_EXPR, upper_or_arm_args, -1)
-									: linitial(upper_or_arm_args));
-
-		/* If no work to do in the next iteration, break away. */
-		if (!need_next_lower_arm && !need_next_upper_arm)
-			break;
-
-		++current_or_arm;
-	}
-
-	/*
-	 * Generate the OR expressions for each of lower and upper bounds (if
-	 * required), and append to the list of implicitly ANDed list of
-	 * expressions.
-	 */
-	if (lower_or_arms != NIL)
-		result = lappend(result,
-						 list_length(lower_or_arms) > 1
-						 ? makeBoolExpr(OR_EXPR, lower_or_arms, -1)
-						 : linitial(lower_or_arms));
-	if (upper_or_arms != NIL)
-		result = lappend(result,
-						 list_length(upper_or_arms) > 1
-						 ? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
-						 : linitial(upper_or_arms));
-
-	/*
-	 * As noted above, for non-default, we return list with constant TRUE. If
-	 * the result is NIL during the recursive call for default, it implies
-	 * this is the only other partition which can hold every value of the key
-	 * except NULL. Hence we return the NullTest result skipped earlier.
-	 */
-	if (result == NIL)
-		result = for_default
-			? get_range_nulltest(key)
-			: list_make1(makeBoolConst(true, false));
-
-	return result;
-}
-
-/*
- * generate_partition_qual
- *
- * Generate partition predicate from rel's partition bound expression. The
- * function returns a NIL list if there is no predicate.
- *
- * Result expression tree is stored CacheMemoryContext to ensure it survives
- * as long as the relcache entry. But we should be running in a less long-lived
- * working context. To avoid leaking cache memory if this routine fails partway
- * through, we build in working memory and then copy the completed structure
- * into cache memory.
- */
-static List *
-generate_partition_qual(Relation rel)
-{
-	HeapTuple	tuple;
-	MemoryContext oldcxt;
-	Datum		boundDatum;
-	bool		isnull;
-	PartitionBoundSpec *bound;
-	List	   *my_qual = NIL,
-			   *result = NIL;
-	Relation	parent;
-	bool		found_whole_row;
-
-	/* Guard against stack overflow due to overly deep partition tree */
-	check_stack_depth();
-
-	/* Quick copy */
-	if (rel->rd_partcheck != NIL)
-		return copyObject(rel->rd_partcheck);
-
-	/* Grab at least an AccessShareLock on the parent table */
-	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
-					   AccessShareLock);
-
-	/* Get pg_class.relpartbound */
-	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "cache lookup failed for relation %u",
-			 RelationGetRelid(rel));
-
-	boundDatum = SysCacheGetAttr(RELOID, tuple,
-								 Anum_pg_class_relpartbound,
-								 &isnull);
-	if (isnull)					/* should not happen */
-		elog(ERROR, "relation \"%s\" has relpartbound = null",
-			 RelationGetRelationName(rel));
-	bound = castNode(PartitionBoundSpec,
-					 stringToNode(TextDatumGetCString(boundDatum)));
-	ReleaseSysCache(tuple);
-
-	my_qual = get_qual_from_partbound(rel, parent, bound);
-
-	/* Add the parent's quals to the list (if any) */
-	if (parent->rd_rel->relispartition)
-		result = list_concat(generate_partition_qual(parent), my_qual);
-	else
-		result = my_qual;
-
-	/*
-	 * Change Vars to have partition's attnos instead of the parent's. We do
-	 * this after we concatenate the parent's quals, because we want every Var
-	 * in it to bear this relation's attnos. It's safe to assume varno = 1
-	 * here.
-	 */
-	result = map_partition_varattnos(result, 1, rel, parent,
-									 &found_whole_row);
-	/* There can never be a whole-row reference here */
-	if (found_whole_row)
-		elog(ERROR, "unexpected whole-row reference found in partition key");
-
-	/* Save a copy in the relcache */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
-	rel->rd_partcheck = copyObject(result);
-	MemoryContextSwitchTo(oldcxt);
-
-	/* Keep the parent locked until commit */
-	heap_close(parent, NoLock);
-
-	return result;
-}
-
-/*
- * get_partition_for_tuple
- *		Finds partition of relation which accepts the partition key specified
- *		in values and isnull
- *
- * Return value is index of the partition (>= 0 and < partdesc->nparts) if one
- * found or -1 if none found.
- */
-int
-get_partition_for_tuple(Relation relation, Datum *values, bool *isnull)
-{
-	int			bound_offset;
-	int			part_index = -1;
-	PartitionKey key = RelationGetPartitionKey(relation);
-	PartitionDesc partdesc = RelationGetPartitionDesc(relation);
-
-	/* Route as appropriate based on partitioning strategy. */
-	switch (key->strategy)
-	{
-		case PARTITION_STRATEGY_HASH:
-			{
-				PartitionBoundInfo boundinfo = partdesc->boundinfo;
-				int			greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
-				uint64		rowHash = compute_hash_value(key->partnatts,
-														 key->partsupfunc,
-														 values, isnull);
-
-				part_index = boundinfo->indexes[rowHash % greatest_modulus];
-			}
-			break;
-
-		case PARTITION_STRATEGY_LIST:
-			if (isnull[0])
-			{
-				if (partition_bound_accepts_nulls(partdesc->boundinfo))
-					part_index = partdesc->boundinfo->null_index;
-			}
-			else
-			{
-				bool		equal = false;
-
-				bound_offset = partition_list_bsearch(key->partsupfunc,
-													  key->partcollation,
-													  partdesc->boundinfo,
-													  values[0], &equal);
-				if (bound_offset >= 0 && equal)
-					part_index = partdesc->boundinfo->indexes[bound_offset];
-			}
-			break;
-
-		case PARTITION_STRATEGY_RANGE:
-			{
-				bool		equal = false,
-							range_partkey_has_null = false;
-				int			i;
-
-				/*
-				 * No range includes NULL, so this will be accepted by the
-				 * default partition if there is one, and otherwise rejected.
-				 */
-				for (i = 0; i < key->partnatts; i++)
-				{
-					if (isnull[i])
-					{
-						range_partkey_has_null = true;
-						break;
-					}
-				}
-
-				if (!range_partkey_has_null)
-				{
-					bound_offset = partition_range_datum_bsearch(key->partsupfunc,
-																 key->partcollation,
-																 partdesc->boundinfo,
-																 key->partnatts,
-																 values,
-																 &equal);
-
-					/*
-					 * The bound at bound_offset is less than or equal to the
-					 * tuple value, so the bound at offset+1 is the upper
-					 * bound of the partition we're looking for, if there
-					 * actually exists one.
-					 */
-					part_index = partdesc->boundinfo->indexes[bound_offset + 1];
-				}
-			}
-			break;
-
-		default:
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) key->strategy);
-	}
-
-	/*
-	 * part_index < 0 means we failed to find a partition of this parent. Use
-	 * the default partition, if there is one.
-	 */
-	if (part_index < 0)
-		part_index = partdesc->boundinfo->default_index;
-
-	return part_index;
-}
-
-/*
  * Checks if any of the 'attnums' is a partition key attribute for rel
  *
  * Sets *used_in_expr if any of the 'attnums' is found to be referenced in some
@@ -2635,7 +224,7 @@ bool
 has_partition_attrs(Relation rel, Bitmapset *attnums,
 					bool *used_in_expr)
 {
-	PartitionKey key;
+	PartitionKey	key;
 	int			partnatts;
 	List	   *partexprs;
 	ListCell   *partexprs_item;
@@ -2686,415 +275,10 @@ has_partition_attrs(Relation rel, Bitmapset *attnums,
 }
 
 /*
- * qsort_partition_hbound_cmp
- *
- * We sort hash bounds by modulus, then by remainder.
- */
-static int32
-qsort_partition_hbound_cmp(const void *a, const void *b)
-{
-	PartitionHashBound *h1 = (*(PartitionHashBound *const *) a);
-	PartitionHashBound *h2 = (*(PartitionHashBound *const *) b);
-
-	return partition_hbound_cmp(h1->modulus, h1->remainder,
-								h2->modulus, h2->remainder);
-}
-
-/*
- * partition_hbound_cmp
- *
- * Compares modulus first, then remainder if modulus are equal.
- */
-static int32
-partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2)
-{
-	if (modulus1 < modulus2)
-		return -1;
-	if (modulus1 > modulus2)
-		return 1;
-	if (modulus1 == modulus2 && remainder1 != remainder2)
-		return (remainder1 > remainder2) ? 1 : -1;
-	return 0;
-}
-
-/*
- * qsort_partition_list_value_cmp
- *
- * Compare two list partition bound datums
- */
-static int32
-qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
-{
-	Datum		val1 = (*(const PartitionListValue **) a)->value,
-				val2 = (*(const PartitionListValue **) b)->value;
-	PartitionKey key = (PartitionKey) arg;
-
-	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
-										   key->partcollation[0],
-										   val1, val2));
-}
-
-/*
- * make_one_range_bound
- *
- * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
- * and a flag telling whether the bound is lower or not.  Made into a function
- * because there are multiple sites that want to use this facility.
- */
-static PartitionRangeBound *
-make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
-{
-	PartitionRangeBound *bound;
-	ListCell   *lc;
-	int			i;
-
-	Assert(datums != NIL);
-
-	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
-	bound->index = index;
-	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
-	bound->kind = (PartitionRangeDatumKind *) palloc0(key->partnatts *
-													  sizeof(PartitionRangeDatumKind));
-	bound->lower = lower;
-
-	i = 0;
-	foreach(lc, datums)
-	{
-		PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
-
-		/* What's contained in this range datum? */
-		bound->kind[i] = datum->kind;
-
-		if (datum->kind == PARTITION_RANGE_DATUM_VALUE)
-		{
-			Const	   *val = castNode(Const, datum->value);
-
-			if (val->constisnull)
-				elog(ERROR, "invalid range bound datum");
-			bound->datums[i] = val->constvalue;
-		}
-
-		i++;
-	}
-
-	return bound;
-}
-
-/* Used when sorting range bounds across all range partitions */
-static int32
-qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
-{
-	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
-	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
-	PartitionKey key = (PartitionKey) arg;
-
-	return partition_rbound_cmp(key->partnatts, key->partsupfunc,
-								key->partcollation, b1->datums, b1->kind,
-								b1->lower, b2);
-}
-
-/*
- * partition_rbound_cmp
- *
- * Return for two range bounds whether the 1st one (specified in datums1,
- * kind1, and lower1) is <, =, or > the bound specified in *b2.
- *
- * partnatts, partsupfunc and partcollation give the number of attributes in the
- * bounds to be compared, comparison function to be used and the collations of
- * attributes, respectively.
- *
- * Note that if the values of the two range bounds compare equal, then we take
- * into account whether they are upper or lower bounds, and an upper bound is
- * considered to be smaller than a lower bound. This is important to the way
- * that RelationBuildPartitionDesc() builds the PartitionBoundInfoData
- * structure, which only stores the upper bound of a common boundary between
- * two contiguous partitions.
- */
-static int32
-partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation,
-					 Datum *datums1, PartitionRangeDatumKind *kind1,
-					 bool lower1, PartitionRangeBound *b2)
-{
-	int32		cmpval = 0;		/* placate compiler */
-	int			i;
-	Datum	   *datums2 = b2->datums;
-	PartitionRangeDatumKind *kind2 = b2->kind;
-	bool		lower2 = b2->lower;
-
-	for (i = 0; i < partnatts; i++)
-	{
-		/*
-		 * First, handle cases where the column is unbounded, which should not
-		 * invoke the comparison procedure, and should not consider any later
-		 * columns. Note that the PartitionRangeDatumKind enum elements
-		 * compare the same way as the values they represent.
-		 */
-		if (kind1[i] < kind2[i])
-			return -1;
-		else if (kind1[i] > kind2[i])
-			return 1;
-		else if (kind1[i] != PARTITION_RANGE_DATUM_VALUE)
-
-			/*
-			 * The column bounds are both MINVALUE or both MAXVALUE. No later
-			 * columns should be considered, but we still need to compare
-			 * whether they are upper or lower bounds.
-			 */
-			break;
-
-		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
-												 partcollation[i],
-												 datums1[i],
-												 datums2[i]));
-		if (cmpval != 0)
-			break;
-	}
-
-	/*
-	 * If the comparison is anything other than equal, we're done. If they
-	 * compare equal though, we still have to consider whether the boundaries
-	 * are inclusive or exclusive.  Exclusive one is considered smaller of the
-	 * two.
-	 */
-	if (cmpval == 0 && lower1 != lower2)
-		cmpval = lower1 ? 1 : -1;
-
-	return cmpval;
-}
-
-/*
- * partition_rbound_datum_cmp
- *
- * Return whether range bound (specified in rb_datums, rb_kind, and rb_lower)
- * is <, =, or > partition key of tuple (tuple_datums)
- *
- * n_tuple_datums, partsupfunc and partcollation give number of attributes in
- * the bounds to be compared, comparison function to be used and the collations
- * of attributes resp.
- *
- */
-int32
-partition_rbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation,
-						   Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
-						   Datum *tuple_datums, int n_tuple_datums)
-{
-	int			i;
-	int32		cmpval = -1;
-
-	for (i = 0; i < n_tuple_datums; i++)
-	{
-		if (rb_kind[i] == PARTITION_RANGE_DATUM_MINVALUE)
-			return -1;
-		else if (rb_kind[i] == PARTITION_RANGE_DATUM_MAXVALUE)
-			return 1;
-
-		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
-												 partcollation[i],
-												 rb_datums[i],
-												 tuple_datums[i]));
-		if (cmpval != 0)
-			break;
-	}
-
-	return cmpval;
-}
-
-/*
- * partition_list_bsearch
- *		Returns the index of the greatest bound datum that is less than equal
- * 		to the given value or -1 if all of the bound datums are greater
- *
- * *is_equal is set to true if the bound datum at the returned index is equal
- * to the input value.
- */
-int
-partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
-					   PartitionBoundInfo boundinfo,
-					   Datum value, bool *is_equal)
-{
-	int			lo,
-				hi,
-				mid;
-
-	lo = -1;
-	hi = boundinfo->ndatums - 1;
-	while (lo < hi)
-	{
-		int32		cmpval;
-
-		mid = (lo + hi + 1) / 2;
-		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
-												 partcollation[0],
-												 boundinfo->datums[mid][0],
-												 value));
-		if (cmpval <= 0)
-		{
-			lo = mid;
-			*is_equal = (cmpval == 0);
-			if (*is_equal)
-				break;
-		}
-		else
-			hi = mid - 1;
-	}
-
-	return lo;
-}
-
-/*
- * partition_range_bsearch
- *		Returns the index of the greatest range bound that is less than or
- *		equal to the given range bound or -1 if all of the range bounds are
- *		greater
- *
- * *is_equal is set to true if the range bound at the returned index is equal
- * to the input range bound
- */
-int
-partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
-						Oid *partcollation,
-						PartitionBoundInfo boundinfo,
-						PartitionRangeBound *probe, bool *is_equal)
-{
-	int			lo,
-				hi,
-				mid;
-
-	lo = -1;
-	hi = boundinfo->ndatums - 1;
-	while (lo < hi)
-	{
-		int32		cmpval;
-
-		mid = (lo + hi + 1) / 2;
-		cmpval = partition_rbound_cmp(partnatts, partsupfunc, partcollation,
-									  boundinfo->datums[mid],
-									  boundinfo->kind[mid],
-									  (boundinfo->indexes[mid] == -1),
-									  probe);
-		if (cmpval <= 0)
-		{
-			lo = mid;
-			*is_equal = (cmpval == 0);
-
-			if (*is_equal)
-				break;
-		}
-		else
-			hi = mid - 1;
-	}
-
-	return lo;
-}
-
-/*
- * partition_range_bsearch
- *		Returns the index of the greatest range bound that is less than or
- *		equal to the given tuple or -1 if all of the range bounds are greater
- *
- * *is_equal is set to true if the range bound at the returned index is equal
- * to the input tuple.
- */
-int
-partition_range_datum_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
-							  PartitionBoundInfo boundinfo,
-							  int nvalues, Datum *values, bool *is_equal)
-{
-	int			lo,
-				hi,
-				mid;
-
-	lo = -1;
-	hi = boundinfo->ndatums - 1;
-	while (lo < hi)
-	{
-		int32		cmpval;
-
-		mid = (lo + hi + 1) / 2;
-		cmpval = partition_rbound_datum_cmp(partsupfunc,
-											partcollation,
-											boundinfo->datums[mid],
-											boundinfo->kind[mid],
-											values,
-											nvalues);
-		if (cmpval <= 0)
-		{
-			lo = mid;
-			*is_equal = (cmpval == 0);
-
-			if (*is_equal)
-				break;
-		}
-		else
-			hi = mid - 1;
-	}
-
-	return lo;
-}
-
-/*
- * partition_hash_bsearch
- *		Returns the index of the greatest (modulus, remainder) pair that is
- *		less than or equal to the given (modulus, remainder) pair or -1 if
- *		all of them are greater
- */
-int
-partition_hash_bsearch(PartitionBoundInfo boundinfo,
-					   int modulus, int remainder)
-{
-	int			lo,
-				hi,
-				mid;
-
-	lo = -1;
-	hi = boundinfo->ndatums - 1;
-	while (lo < hi)
-	{
-		int32		cmpval,
-					bound_modulus,
-					bound_remainder;
-
-		mid = (lo + hi + 1) / 2;
-		bound_modulus = DatumGetInt32(boundinfo->datums[mid][0]);
-		bound_remainder = DatumGetInt32(boundinfo->datums[mid][1]);
-		cmpval = partition_hbound_cmp(bound_modulus, bound_remainder,
-									  modulus, remainder);
-		if (cmpval <= 0)
-		{
-			lo = mid;
-
-			if (cmpval == 0)
-				break;
-		}
-		else
-			hi = mid - 1;
-	}
-
-	return lo;
-}
-
-/*
- * get_default_oid_from_partdesc
- *
- * Given a partition descriptor, return the OID of the default partition, if
- * one exists; else, return InvalidOid.
- */
-Oid
-get_default_oid_from_partdesc(PartitionDesc partdesc)
-{
-	if (partdesc && partdesc->boundinfo &&
-		partition_bound_has_default(partdesc->boundinfo))
-		return partdesc->oids[partdesc->boundinfo->default_index];
-
-	return InvalidOid;
-}
-
-/*
  * get_default_partition_oid
  *
  * Given a relation OID, return the OID of the default partition, if one
- * exists.  Use get_default_oid_from_partdesc where possible, for
- * efficiency.
+ * exists.  Use get_default_partition_oid where possible, for efficiency.
  */
 Oid
 get_default_partition_oid(Oid parentId)
@@ -3177,63 +361,6 @@ get_proposed_default_constraint(List *new_part_constraints)
 }
 
 /*
- * get_partition_bound_num_indexes
- *
- * Returns the number of the entries in the partition bound indexes array.
- */
-static int
-get_partition_bound_num_indexes(PartitionBoundInfo bound)
-{
-	int			num_indexes;
-
-	Assert(bound);
-
-	switch (bound->strategy)
-	{
-		case PARTITION_STRATEGY_HASH:
-
-			/*
-			 * The number of the entries in the indexes array is same as the
-			 * greatest modulus.
-			 */
-			num_indexes = get_hash_partition_greatest_modulus(bound);
-			break;
-
-		case PARTITION_STRATEGY_LIST:
-			num_indexes = bound->ndatums;
-			break;
-
-		case PARTITION_STRATEGY_RANGE:
-			/* Range partitioned table has an extra index. */
-			num_indexes = bound->ndatums + 1;
-			break;
-
-		default:
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) bound->strategy);
-	}
-
-	return num_indexes;
-}
-
-/*
- * get_hash_partition_greatest_modulus
- *
- * Returns the greatest modulus of the hash partition bound. The greatest
- * modulus will be at the end of the datums array because hash partitions are
- * arranged in the ascending order of their modulus and remainders.
- */
-int
-get_hash_partition_greatest_modulus(PartitionBoundInfo bound)
-{
-	Assert(bound && bound->strategy == PARTITION_STRATEGY_HASH);
-	Assert(bound->datums && bound->ndatums > 0);
-	Assert(DatumGetInt32(bound->datums[bound->ndatums - 1][0]) > 0);
-
-	return DatumGetInt32(bound->datums[bound->ndatums - 1][0]);
-}
-
-/*
  * compute_hash_value
  *
  * Compute the hash value for given not null partition key values.
@@ -3331,23 +458,39 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 	if (my_extra == NULL || my_extra->relid != parentId)
 	{
 		Relation	parent;
-		PartitionKey key;
+		PartitionKey	key;
+		FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
 		int			j;
 
 		/* Open parent relation and fetch partition keyinfo */
 		parent = try_relation_open(parentId, AccessShareLock);
 		if (parent == NULL)
 			PG_RETURN_NULL();
-		key = RelationGetPartitionKey(parent);
 
 		/* Reject parent table that is not hash-partitioned. */
-		if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE ||
-			key->strategy != PARTITION_STRATEGY_HASH)
+		if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("\"%s\" is not a hash partitioned table",
 							get_rel_name(parentId))));
 
+		key = RelationGetPartitionKey(parent);
+		Assert(key != NULL);
+
+		if (key->strategy != PARTITION_STRATEGY_HASH)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("\"%s\" is not a hash partitioned table",
+							get_rel_name(parentId))));
+
+		/* Get partsupfunc FmgrInfo's. */
+		for (j = 0; j < key->partnatts; j++)
+		{
+			fmgr_info_copy(&partsupfunc[j],
+						   partition_getprocinfo(parent, key, j),
+						   CurrentMemoryContext);
+		}
+
 		if (!get_fn_expr_variadic(fcinfo->flinfo))
 		{
 			int			nargs = PG_NARGS() - 3;
@@ -3362,7 +505,8 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 			/* allocate space for our cache */
 			fcinfo->flinfo->fn_extra =
 				MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
-									   offsetof(ColumnsHashData, partsupfunc) +
+									   offsetof(ColumnsHashData,
+												partsupfunc) +
 									   sizeof(FmgrInfo) * nargs);
 			my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
 			my_extra->relid = parentId;
@@ -3380,7 +524,7 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 									j + 1, format_type_be(key->parttypid[j]), format_type_be(argtype))));
 
 				fmgr_info_copy(&my_extra->partsupfunc[j],
-							   &key->partsupfunc[j],
+							   &partsupfunc[j],
 							   fcinfo->flinfo->fn_mcxt);
 			}
 
@@ -3392,7 +536,8 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 			/* allocate space for our cache -- just one FmgrInfo in this case */
 			fcinfo->flinfo->fn_extra =
 				MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
-									   offsetof(ColumnsHashData, partsupfunc) +
+									   offsetof(ColumnsHashData,
+												partsupfunc) +
 									   sizeof(FmgrInfo));
 			my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
 			my_extra->relid = parentId;
@@ -3414,7 +559,7 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 									format_type_be(my_extra->variadic_type))));
 
 			fmgr_info_copy(&my_extra->partsupfunc[0],
-						   &key->partsupfunc[0],
+						   &partsupfunc[0],
 						   fcinfo->flinfo->fn_mcxt);
 		}
 
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8ba9890ca6..32386b246f 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -623,12 +623,14 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
 
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDesc	partdesc = RelationGetPartitionDesc(rel);
+		int			nparts = RelationGetPartitionCount(rel);
+		Oid		   *partoids = RelationGetPartitionOids(rel);
 		int			i;
 
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 			CloneForeignKeyConstraints(RelationGetRelid(rel),
-									   partdesc->oids[i],
+									   partoids[i],
 									   cloned);
 	}
 
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 78302544db..913faddb55 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -877,15 +877,12 @@ DefineIndex(Oid relationId,
 		 */
 		if (!stmt->relation || stmt->relation->inh)
 		{
-			PartitionDesc partdesc = RelationGetPartitionDesc(rel);
-			int			nparts = partdesc->nparts;
-			Oid		   *part_oids = palloc(sizeof(Oid) * nparts);
+			int			nparts = RelationGetPartitionCount(rel);
+			Oid		   *part_oids = RelationGetPartitionOids(rel);
 			bool		invalidate_parent = false;
 			TupleDesc	parentDesc;
 			Oid		   *opfamOids;
 
-			memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);
-
 			parentDesc = CreateTupleDescCopy(RelationGetDescr(rel));
 			opfamOids = palloc(sizeof(Oid) * numberOfKeyAttributes);
 			for (i = 0; i < numberOfKeyAttributes; i++)
@@ -901,6 +898,7 @@ DefineIndex(Oid relationId,
 			 * If none matches, build a new index by calling ourselves
 			 * recursively with the same options (except for the index name).
 			 */
+			Assert(part_oids != NULL || nparts == 0);
 			for (i = 0; i < nparts; i++)
 			{
 				Oid		childRelid = part_oids[i];
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f27684f96e..fe4265d4bb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -77,6 +77,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "partitioning/partbounds.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -92,6 +93,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/partcache.h"
 #include "utils/relcache.h"
 #include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
@@ -827,8 +829,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 * the places such that lock parent, lock default partition and then
 		 * lock the partition so as to avoid a deadlock.
 		 */
-		defaultPartOid =
-			get_default_oid_from_partdesc(RelationGetPartitionDesc(parent));
+		defaultPartOid = RelationGetDefaultPartitionOid(parent);
 		if (OidIsValid(defaultPartOid))
 			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
@@ -5862,18 +5863,14 @@ ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
 	 * If the parent is a partitioned table, like check constraints, we do not
 	 * support removing the NOT NULL while partitions exist.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
-
-		Assert(partdesc != NULL);
-		if (partdesc->nparts > 0 && !recurse && !recursing)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot remove constraint from only the partitioned table when partitions exist"),
-					 errhint("Do not specify the ONLY keyword.")));
-	}
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		RelationGetPartitionCount(rel) > 0 && !recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot remove constraint from only the partitioned table when partitions exist"),
+				 errhint("Do not specify the ONLY keyword.")));
 }
+
 static ObjectAddress
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
@@ -6007,16 +6004,12 @@ ATPrepSetNotNull(Relation rel, bool recurse, bool recursing)
 	 * constraints must be added to the child tables.  Complain if requested
 	 * otherwise and partitions exist.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
-
-		if (partdesc && partdesc->nparts > 0 && !recurse && !recursing)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot add constraint to only the partitioned table when partitions exist"),
-					 errhint("Do not specify the ONLY keyword.")));
-	}
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		RelationGetPartitionCount(rel) > 0 && !recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add constraint to only the partitioned table when partitions exist"),
+				 errhint("Do not specify the ONLY keyword.")));
 }
 
 /*
@@ -7697,13 +7690,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 */
 	if (recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDesc partdesc;
+		int			nparts = RelationGetPartitionCount(rel);
+		Oid		   *partoids = RelationGetPartitionOids(rel);
 
-		partdesc = RelationGetPartitionDesc(rel);
-
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
-			Oid			partitionId = partdesc->oids[i];
+			Oid			partitionId = partoids[i];
 			Relation	partition = heap_open(partitionId, lockmode);
 			AlteredTableInfo *childtab;
 			ObjectAddress childAddr;
@@ -13987,10 +13980,12 @@ QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
 	}
 	else if (scanrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(scanrel);
+		int			nparts = RelationGetPartitionCount(scanrel);
+		Oid		   *partoids = RelationGetPartitionOids(scanrel);
 		int			i;
 
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
 			Relation	part_rel;
 			bool		found_whole_row;
@@ -13999,7 +13994,7 @@ QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
 			/*
 			 * This is the minimum lock we need to prevent deadlocks.
 			 */
-			part_rel = heap_open(partdesc->oids[i], AccessExclusiveLock);
+			part_rel = heap_open(partoids[i], AccessExclusiveLock);
 
 			/*
 			 * Adjust the constraint for scanrel so that it matches this
@@ -14049,8 +14044,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * We must lock the default partition if one exists, because attaching a
 	 * new partition will change its partition constraint.
 	 */
-	defaultPartOid =
-		get_default_oid_from_partdesc(RelationGetPartitionDesc(rel));
+	defaultPartOid = RelationGetDefaultPartitionOid(rel);
 	if (OidIsValid(defaultPartOid))
 		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
@@ -14626,8 +14620,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	 * We must lock the default partition, because detaching this partition
 	 * will change its partition constraint.
 	 */
-	defaultPartOid =
-		get_default_oid_from_partdesc(RelationGetPartitionDesc(rel));
+	defaultPartOid = RelationGetDefaultPartitionOid(rel);
 	if (OidIsValid(defaultPartOid))
 		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
@@ -14828,8 +14821,9 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		AttrNumber *attmap;
 		bool		found;
 		int			i;
-		PartitionDesc partDesc;
-		Oid			constraintOid,
+		int			nparts = RelationGetPartitionCount(parentTbl);
+		Oid		   *partoids = RelationGetPartitionOids(parentTbl),
+					constraintOid,
 					cldConstrId = InvalidOid;
 
 		/*
@@ -14847,11 +14841,11 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 							   RelationGetRelationName(partIdx))));
 
 		/* Make sure it indexes a partition of the other index's table */
-		partDesc = RelationGetPartitionDesc(parentTbl);
 		found = false;
-		for (i = 0; i < partDesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
-			if (partDesc->oids[i] == state.partitionOid)
+			if (partoids[i] == state.partitionOid)
 			{
 				found = true;
 				break;
@@ -14982,6 +14976,7 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
 	int				tuples = 0;
 	HeapTuple		inhTup;
 	bool			updated = false;
+	int				nparts = RelationGetPartitionCount(partedTbl);
 
 	Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
 
@@ -15021,7 +15016,7 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
 	 * If we found as many inherited indexes as the partitioned table has
 	 * partitions, we're good; update pg_index to set indisvalid.
 	 */
-	if (tuples == RelationGetPartitionDesc(partedTbl)->nparts)
+	if (tuples == nparts)
 	{
 		Relation	idxRel;
 		HeapTuple	newtup;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index cc50691aa0..d4fdf1b556 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1086,7 +1086,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 */
 	if (partition_recurse)
 	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+		int			nparts = RelationGetPartitionCount(rel);
+		Oid		   *partoids = RelationGetPartitionOids(rel);
 		List	   *idxs = NIL;
 		List	   *childTbls = NIL;
 		ListCell   *l;
@@ -1118,7 +1119,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		oldcxt = MemoryContextSwitchTo(perChildCxt);
 
 		/* Iterate to create the trigger on each existing partition */
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
 			Oid			indexOnChild = InvalidOid;
 			ListCell   *l2;
@@ -1127,14 +1129,14 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 			Node	   *qual;
 			bool		found_whole_row;
 
-			childTbl = heap_open(partdesc->oids[i], ShareRowExclusiveLock);
+			childTbl = heap_open(partoids[i], ShareRowExclusiveLock);
 
 			/* Find which of the child indexes is the one on this partition */
 			if (OidIsValid(indexOid))
 			{
 				forboth(l, idxs, l2, childTbls)
 				{
-					if (lfirst_oid(l2) == partdesc->oids[i])
+					if (lfirst_oid(l2) == partoids[i])
 					{
 						indexOnChild = lfirst_oid(l);
 						break;
@@ -1143,7 +1145,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 				if (!OidIsValid(indexOnChild))
 					elog(ERROR, "failed to find index matching index \"%s\" in partition \"%s\"",
 						 get_rel_name(indexOid),
-						 get_rel_name(partdesc->oids[i]));
+						 get_rel_name(partoids[i]));
 			}
 
 			/*
@@ -1171,7 +1173,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 				elog(ERROR, "unexpected whole-row reference found in trigger WHEN clause");
 
 			CreateTrigger(childStmt, queryString,
-						  partdesc->oids[i], refRelOid,
+						  partoids[i], refRelOid,
 						  InvalidOid, indexOnChild,
 						  funcoid, trigoid, qual,
 						  isInternal, true);
@@ -1854,14 +1856,16 @@ EnableDisableTrigger(Relation rel, const char *tgname,
 			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
 				(TRIGGER_FOR_ROW(oldtrig->tgtype)))
 			{
-				PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+				int		nparts = RelationGetPartitionCount(rel);
+				Oid	   *partoids = RelationGetPartitionOids(rel);
 				int			i;
 
-				for (i = 0; i < partdesc->nparts; i++)
+				Assert(partoids != NULL || nparts == 0);
+				for (i = 0; i < nparts; i++)
 				{
 					Relation	part;
 
-					part = relation_open(partdesc->oids[i], lockmode);
+					part = relation_open(partoids[i], lockmode);
 					EnableDisableTrigger(part, NameStr(oldtrig->tgname),
 										 fires_when, skip_system, lockmode);
 					heap_close(part, NoLock);	/* keep lock till commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fa713bbe5..e885a16647 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,7 +42,6 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
-#include "catalog/partition.h"
 #include "catalog/pg_publication.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 11139f743d..e7652cbd25 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -22,6 +22,7 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "partitioning/partbounds.h"
 #include "utils/lsyscache.h"
 #include "utils/rls.h"
 #include "utils/ruleutils.h"
@@ -35,6 +36,8 @@ static void FormPartitionKeyDatum(PartitionDispatch pd,
 					  EState *estate,
 					  Datum *values,
 					  bool *isnull);
+static int get_partition_for_tuple(PartitionDispatch pd, Datum *values,
+					  bool *isnull);
 static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 Datum *values,
 									 bool *isnull,
@@ -201,13 +204,11 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 	parent = pd[0];
 	while (true)
 	{
-		PartitionDesc partdesc;
 		TupleTableSlot *myslot = parent->tupslot;
 		TupleConversionMap *map = parent->tupmap;
 		int			cur_index = -1;
 
 		rel = parent->reldesc;
-		partdesc = RelationGetPartitionDesc(rel);
 
 		/*
 		 * Convert the tuple to this parent's layout so that we can do certain
@@ -238,13 +239,13 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 		 * Nothing for get_partition_for_tuple() to do if there are no
 		 * partitions to begin with.
 		 */
-		if (partdesc->nparts == 0)
+		if (parent->nparts == 0)
 		{
 			result = -1;
 			break;
 		}
 
-		cur_index = get_partition_for_tuple(rel, values, isnull);
+		cur_index = get_partition_for_tuple(parent, values, isnull);
 
 		/*
 		 * cur_index < 0 means we failed to find a partition of this parent.
@@ -874,8 +875,10 @@ get_partition_dispatch_recurse(Relation rel, Relation parent,
 							   List **pds, List **leaf_part_oids)
 {
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	PartitionDesc partdesc = RelationGetPartitionDesc(rel);
 	PartitionKey partkey = RelationGetPartitionKey(rel);
+	int			nparts = RelationGetPartitionCount(rel);
+	Oid		   *partoids = RelationGetPartitionOids(rel);
+	PartitionBoundInfo boundinfo = RelationGetPartitionBounds(rel);
 	PartitionDispatch pd;
 	int			i;
 
@@ -887,7 +890,15 @@ get_partition_dispatch_recurse(Relation rel, Relation parent,
 	pd->reldesc = rel;
 	pd->key = partkey;
 	pd->keystate = NIL;
-	pd->partdesc = partdesc;
+	/* Get partsupfunc FmgrInfo's. */
+	for (i = 0; i < partkey->partnatts; i++)
+	{
+		fmgr_info_copy(&pd->partsupfunc[i],
+					   partition_getprocinfo(rel, partkey, i),
+					   CurrentMemoryContext);
+	}
+	pd->nparts = nparts;
+	pd->boundinfo = boundinfo;
 	if (parent != NULL)
 	{
 		/*
@@ -932,10 +943,10 @@ get_partition_dispatch_recurse(Relation rel, Relation parent,
 	 * the tree.  This value is used to continue the search in the next level
 	 * of the partition tree.
 	 */
-	pd->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
-	for (i = 0; i < partdesc->nparts; i++)
+	pd->indexes = (int *) palloc(nparts * sizeof(int));
+	for (i = 0; i < nparts; i++)
 	{
-		Oid			partrelid = partdesc->oids[i];
+		Oid			partrelid = partoids[i];
 
 		if (get_rel_relkind(partrelid) != RELKIND_PARTITIONED_TABLE)
 		{
@@ -1023,6 +1034,110 @@ FormPartitionKeyDatum(PartitionDispatch pd,
 }
 
 /*
+ * get_partition_for_tuple
+ *		Finds partition of relation which accepts the partition key specified
+ *		in values and isnull
+ *
+ * Return value is index of the partition (>= 0 and < nparts) if one found or
+ * -1 if none found.
+ */
+static int
+get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull)
+{
+	int			bound_offset;
+	int			part_index = -1;
+	PartitionKey key = pd->key;
+	FmgrInfo   *partsupfunc = pd->partsupfunc;
+	PartitionBoundInfo boundinfo = pd->boundinfo;
+
+	/* Route as appropriate based on partitioning strategy. */
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+			{
+				int			greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
+				uint64		rowHash = compute_hash_value(key->partnatts,
+														 partsupfunc,
+														 values, isnull);
+
+				part_index = boundinfo->indexes[rowHash % greatest_modulus];
+			}
+			break;
+
+		case PARTITION_STRATEGY_LIST:
+			if (isnull[0])
+			{
+				if (partition_bound_accepts_nulls(boundinfo))
+					part_index = boundinfo->null_index;
+			}
+			else
+			{
+				bool		equal = false;
+
+				bound_offset = partition_list_bsearch(partsupfunc,
+													  key->partcollation,
+													  boundinfo,
+													  values[0], &equal);
+				if (bound_offset >= 0 && equal)
+					part_index = boundinfo->indexes[bound_offset];
+			}
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			{
+				bool		equal = false,
+							range_partkey_has_null = false;
+				int			i;
+
+				/*
+				 * No range includes NULL, so this will be accepted by the
+				 * default partition if there is one, and otherwise rejected.
+				 */
+				for (i = 0; i < key->partnatts; i++)
+				{
+					if (isnull[i])
+					{
+						range_partkey_has_null = true;
+						break;
+					}
+				}
+
+				if (!range_partkey_has_null)
+				{
+					bound_offset = partition_range_datum_bsearch(partsupfunc,
+																 key->partcollation,
+																 boundinfo,
+																 key->partnatts,
+																 values,
+																 &equal);
+
+					/*
+					 * The bound at bound_offset is less than or equal to the
+					 * tuple value, so the bound at offset+1 is the upper
+					 * bound of the partition we're looking for, if there
+					 * actually exists one.
+					 */
+					part_index = boundinfo->indexes[bound_offset + 1];
+				}
+			}
+			break;
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) key->strategy);
+	}
+
+	/*
+	 * part_index < 0 means we failed to find a partition of this parent. Use
+	 * the default partition, if there is one.
+	 */
+	if (part_index < 0)
+		part_index = boundinfo->default_index;
+
+	return part_index;
+}
+
+/*
  * ExecBuildSlotPartitionKeyDescription
  *
  * This works very much like BuildIndexValueDescription() and is currently
@@ -1036,7 +1151,7 @@ ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 int maxfieldlen)
 {
 	StringInfoData buf;
-	PartitionKey key = RelationGetPartitionKey(rel);
+	PartitionKey	key = RelationGetPartitionKey(rel);
 	int			partnatts = get_partition_natts(key);
 	int			i;
 	Oid			relid = RelationGetRelid(rel);
@@ -1284,10 +1399,12 @@ ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 		PartitionPruneInfo *pinfo = (PartitionPruneInfo *) lfirst(lc);
 		PartitionPruningData *pprune = &prunedata[i];
 		PartitionPruneContext *context = &pprune->context;
-		PartitionDesc partdesc;
 		Relation	rel;
 		PartitionKey partkey;
+		int			nparts;
+		PartitionBoundInfo boundinfo;
 		int			partnatts;
+		int			j;
 
 		pprune->present_parts = bms_copy(pinfo->present_parts);
 		pprune->subnode_map = palloc(sizeof(int) * pinfo->nparts);
@@ -1309,16 +1426,23 @@ ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 		rel = relation_open(pinfo->reloid, NoLock);
 
 		partkey = RelationGetPartitionKey(rel);
-		partdesc = RelationGetPartitionDesc(rel);
+		nparts = RelationGetPartitionCount(rel);
+		boundinfo = RelationGetPartitionBounds(rel);
 
 		context->strategy = partkey->strategy;
 		context->partnatts = partnatts = partkey->partnatts;
 		context->partopfamily = partkey->partopfamily;
 		context->partopcintype = partkey->partopcintype;
 		context->partcollation = partkey->partcollation;
-		context->partsupfunc = partkey->partsupfunc;
-		context->nparts = pinfo->nparts;
-		context->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey);
+		/* Get partsupfunc FmgrInfo's. */
+		for (j = 0; j < partkey->partnatts; j++)
+		{
+			fmgr_info_copy(&context->partsupfunc[j],
+						   partition_getprocinfo(rel, partkey, j),
+						   CurrentMemoryContext);
+		}
+		context->nparts = nparts;
+		context->boundinfo = boundinfo;
 		context->planstate = planstate;
 		context->safeparams = NULL; /* empty for now */
 
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2e289d475e..5d66cfb87b 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -15,12 +15,12 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
-#include "catalog/partition.h"
 #include "optimizer/clauses.h"
 #include "optimizer/joininfo.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/prep.h"
+#include "partitioning/partbounds.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 8d86e98adc..23ce22a27e 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1584,7 +1584,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	oldrelation = heap_open(parentOID, NoLock);
 
 	/* Scan the inheritance set and expand it */
-	if (RelationGetPartitionDesc(oldrelation) != NULL)
+	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		Assert(rte->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -1675,13 +1675,11 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte,
 	RangeTblEntry *childrte;
 	Index		childRTindex;
 	bool		has_child = false;
-	PartitionDesc partdesc = RelationGetPartitionDesc(parentrel);
+	int			nparts = RelationGetPartitionCount(parentrel);
+	Oid		   *partoids = RelationGetPartitionOids(parentrel);
 
 	check_stack_depth();
 
-	/* A partitioned table should always have a partition descriptor. */
-	Assert(partdesc);
-
 	Assert(parentrte->inh);
 
 	/*
@@ -1700,9 +1698,10 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte,
 									top_parentrc, parentrel,
 									appinfos, &childrte, &childRTindex);
 
-	for (i = 0; i < partdesc->nparts; i++)
+	Assert(partoids != NULL || nparts == 0);
+	for (i = 0; i < nparts; i++)
 	{
-		Oid			childOID = partdesc->oids[i];
+		Oid			childOID = partoids[i];
 		Relation	childrel;
 
 		/* Open rel; we already have required locks */
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index cb9cfc3729..23fed6a93e 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,7 +27,6 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
-#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_statistic_ext.h"
 #include "foreign/fdwapi.h"
@@ -45,8 +44,9 @@
 #include "storage/bufmgr.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
-#include "utils/syscache.h"
+#include "utils/partcache.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 #include "utils/snapmgr.h"
 
 
@@ -70,8 +70,9 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
 static List *get_relation_statistics(RelOptInfo *rel, Relation relation);
 static void set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
 							Relation relation);
-static PartitionScheme find_partition_scheme(PlannerInfo *root, Relation rel);
-static void set_baserel_partition_key_exprs(Relation relation,
+static PartitionScheme find_partition_scheme(PlannerInfo *root, Relation rel,
+							PartitionKey partkey);
+static void set_baserel_partition_key_exprs(PartitionKey partkey,
 								RelOptInfo *rel);
 
 /*
@@ -1869,18 +1870,19 @@ static void
 set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
 							Relation relation)
 {
-	PartitionDesc partdesc;
-	PartitionKey partkey;
+	PartitionKey partkey = RelationGetPartitionKey(relation);
+	int		nparts = RelationGetPartitionCount(relation);
+	PartitionBoundInfo boundinfo = RelationGetPartitionBounds(relation);
 
 	Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+	Assert(partkey != NULL);
+	Assert(boundinfo != NULL || nparts == 0);
 
-	partdesc = RelationGetPartitionDesc(relation);
-	partkey = RelationGetPartitionKey(relation);
-	rel->part_scheme = find_partition_scheme(root, relation);
-	Assert(partdesc != NULL && rel->part_scheme != NULL);
-	rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey);
-	rel->nparts = partdesc->nparts;
-	set_baserel_partition_key_exprs(relation, rel);
+	rel->part_scheme = find_partition_scheme(root, relation, partkey);
+	Assert(rel->part_scheme != NULL);
+	rel->boundinfo = boundinfo;
+	rel->nparts = nparts;
+	set_baserel_partition_key_exprs(partkey, rel);
 	rel->partition_qual = RelationGetPartitionQual(relation);
 }
 
@@ -1890,9 +1892,9 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
  * Find or create a PartitionScheme for this Relation.
  */
 static PartitionScheme
-find_partition_scheme(PlannerInfo *root, Relation relation)
+find_partition_scheme(PlannerInfo *root, Relation rel,
+					  PartitionKey partkey)
 {
-	PartitionKey partkey = RelationGetPartitionKey(relation);
 	ListCell   *lc;
 	int			partnatts,
 				i;
@@ -1933,14 +1935,11 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
 
 		/*
 		 * If partopfamily and partopcintype matched, must have the same
-		 * partition comparison functions.  Note that we cannot reliably
-		 * Assert the equality of function structs themselves for they might
-		 * be different across PartitionKey's, so just Assert for the function
-		 * OIDs.
+		 * partition comparison functions.
 		 */
 #ifdef USE_ASSERT_CHECKING
 		for (i = 0; i < partkey->partnatts; i++)
-			Assert(partkey->partsupfunc[i].fn_oid ==
+			Assert(partkey->partsupfuncid[i] ==
 				   part_scheme->partsupfunc[i].fn_oid);
 #endif
 
@@ -1949,39 +1948,22 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
 	}
 
 	/*
-	 * Did not find matching partition scheme. Create one copying relevant
-	 * information from the relcache. We need to copy the contents of the
-	 * array since the relcache entry may not survive after we have closed the
-	 * relation.
+	 * Did not find matching partition scheme. Create one usinng the
+	 * relevant information copied from the relcache.
 	 */
 	part_scheme = (PartitionScheme) palloc0(sizeof(PartitionSchemeData));
 	part_scheme->strategy = partkey->strategy;
 	part_scheme->partnatts = partkey->partnatts;
-
-	part_scheme->partopfamily = (Oid *) palloc(sizeof(Oid) * partnatts);
-	memcpy(part_scheme->partopfamily, partkey->partopfamily,
-		   sizeof(Oid) * partnatts);
-
-	part_scheme->partopcintype = (Oid *) palloc(sizeof(Oid) * partnatts);
-	memcpy(part_scheme->partopcintype, partkey->partopcintype,
-		   sizeof(Oid) * partnatts);
-
-	part_scheme->partcollation = (Oid *) palloc(sizeof(Oid) * partnatts);
-	memcpy(part_scheme->partcollation, partkey->partcollation,
-		   sizeof(Oid) * partnatts);
-
-	part_scheme->parttyplen = (int16 *) palloc(sizeof(int16) * partnatts);
-	memcpy(part_scheme->parttyplen, partkey->parttyplen,
-		   sizeof(int16) * partnatts);
-
-	part_scheme->parttypbyval = (bool *) palloc(sizeof(bool) * partnatts);
-	memcpy(part_scheme->parttypbyval, partkey->parttypbyval,
-		   sizeof(bool) * partnatts);
-
+	part_scheme->partopfamily = partkey->partopfamily;
+	part_scheme->partopcintype = partkey->partopcintype;
+	part_scheme->partcollation = partkey->partcollation;
+	part_scheme->parttyplen = partkey->parttyplen;
+	part_scheme->parttypbyval = partkey->parttypbyval;
 	part_scheme->partsupfunc = (FmgrInfo *)
 		palloc(sizeof(FmgrInfo) * partnatts);
 	for (i = 0; i < partnatts; i++)
-		fmgr_info_copy(&part_scheme->partsupfunc[i], &partkey->partsupfunc[i],
+		fmgr_info_copy(&part_scheme->partsupfunc[i],
+					   partition_getprocinfo(rel, partkey, i),
 					   CurrentMemoryContext);
 
 	/* Add the partitioning scheme to PlannerInfo. */
@@ -1998,10 +1980,9 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
  * nodes.  All Var nodes are restamped with the relid of given relation.
  */
 static void
-set_baserel_partition_key_exprs(Relation relation,
+set_baserel_partition_key_exprs(PartitionKey partkey,
 								RelOptInfo *rel)
 {
-	PartitionKey partkey = RelationGetPartitionKey(relation);
 	int			partnatts;
 	int			cnt;
 	List	  **partexprs;
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index b9aa7486ba..fc19f892c5 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -17,7 +17,6 @@
 #include <limits.h>
 
 #include "miscadmin.h"
-#include "catalog/partition.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
@@ -27,6 +26,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "partitioning/partbounds.h"
 #include "utils/hsearch.h"
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9178139912..7889f8bfab 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3556,7 +3556,7 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
 	{
 		case RELKIND_PARTITIONED_TABLE:
 			/* transform the partition bound, if any */
-			Assert(RelationGetPartitionKey(parentRel) != NULL);
+			Assert(parentRel->rd_partkey != NULL);
 			if (cmd->bound != NULL)
 				cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
 														 cmd->bound);
@@ -3597,7 +3597,7 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 						PartitionBoundSpec *spec)
 {
 	PartitionBoundSpec *result_spec;
-	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionKey	key = RelationGetPartitionKey(parent);
 	char		strategy = get_partition_strategy(key);
 	int			partnatts = get_partition_natts(key);
 	List	   *partexprs = get_partition_exprs(key);
diff --git a/src/backend/partitioning/Makefile b/src/backend/partitioning/Makefile
index 429207c4eb..278fac3afa 100644
--- a/src/backend/partitioning/Makefile
+++ b/src/backend/partitioning/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/partitioning
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = partprune.o
+OBJS = partprune.o partbounds.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
new file mode 100644
index 0000000000..b25e74896b
--- /dev/null
+++ b/src/backend/partitioning/partbounds.c
@@ -0,0 +1,2072 @@
+/*-------------------------------------------------------------------------
+ *
+ * partbounds.c
+ *		Support routines for manipulating partition bounds
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		  src/backend/partitioning/partbounds.c
+ *
+ *-------------------------------------------------------------------------
+*/
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "access/nbtree.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
+#include "partitioning/partprune.h"
+#include "partitioning/partbounds.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+static int	get_partition_bound_num_indexes(PartitionBoundInfo b);
+static Expr *make_partition_op_expr(PartitionKey key, int keynum,
+					   uint16 strategy, Expr *arg1, Expr *arg2);
+static Oid get_partition_operator(PartitionKey key, int col,
+					   StrategyNumber strategy, bool *need_relabel);
+static List *get_qual_for_hash(Relation parent, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+				   bool for_default);
+static void get_range_key_properties(PartitionKey key, int keynum,
+						 PartitionRangeDatum *ldatum,
+						 PartitionRangeDatum *udatum,
+						 ListCell **partexprs_item,
+						 Expr **keyCol,
+						 Const **lower_val, Const **upper_val);
+static List *get_range_nulltest(PartitionKey key);
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent,
+						PartitionBoundSpec *spec)
+{
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	List	   *my_qual = NIL;
+
+	Assert(key != NULL);
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+			Assert(spec->strategy == PARTITION_STRATEGY_HASH);
+			my_qual = get_qual_for_hash(parent, spec);
+			break;
+
+		case PARTITION_STRATEGY_LIST:
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+			my_qual = get_qual_for_list(parent, spec);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			my_qual = get_qual_for_range(parent, spec, false);
+			break;
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) key->strategy);
+	}
+
+	return my_qual;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because PartitionBoundInfo is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
+					   PartitionBoundInfo b1, PartitionBoundInfo b2)
+{
+	int			i;
+
+	if (b1->strategy != b2->strategy)
+		return false;
+
+	if (b1->ndatums != b2->ndatums)
+		return false;
+
+	if (b1->null_index != b2->null_index)
+		return false;
+
+	if (b1->default_index != b2->default_index)
+		return false;
+
+	if (b1->strategy == PARTITION_STRATEGY_HASH)
+	{
+		int			greatest_modulus = get_hash_partition_greatest_modulus(b1);
+
+		/*
+		 * If two hash partitioned tables have different greatest moduli,
+		 * their partition schemes don't match.
+		 */
+		if (greatest_modulus != get_hash_partition_greatest_modulus(b2))
+			return false;
+
+		/*
+		 * We arrange the partitions in the ascending order of their modulus
+		 * and remainders.  Also every modulus is factor of next larger
+		 * modulus.  Therefore we can safely store index of a given partition
+		 * in indexes array at remainder of that partition.  Also entries at
+		 * (remainder + N * modulus) positions in indexes array are all same
+		 * for (modulus, remainder) specification for any partition.  Thus
+		 * datums array from both the given bounds are same, if and only if
+		 * their indexes array will be same.  So, it suffices to compare
+		 * indexes array.
+		 */
+		for (i = 0; i < greatest_modulus; i++)
+			if (b1->indexes[i] != b2->indexes[i])
+				return false;
+
+#ifdef USE_ASSERT_CHECKING
+
+		/*
+		 * Nonetheless make sure that the bounds are indeed same when the
+		 * indexes match.  Hash partition bound stores modulus and remainder
+		 * at b1->datums[i][0] and b1->datums[i][1] position respectively.
+		 */
+		for (i = 0; i < b1->ndatums; i++)
+			Assert((b1->datums[i][0] == b2->datums[i][0] &&
+					b1->datums[i][1] == b2->datums[i][1]));
+#endif
+	}
+	else
+	{
+		for (i = 0; i < b1->ndatums; i++)
+		{
+			int			j;
+
+			for (j = 0; j < partnatts; j++)
+			{
+				/* For range partitions, the bounds might not be finite. */
+				if (b1->kind != NULL)
+				{
+					/* The different kinds of bound all differ from each other */
+					if (b1->kind[i][j] != b2->kind[i][j])
+						return false;
+
+					/*
+					 * Non-finite bounds are equal without further
+					 * examination.
+					 */
+					if (b1->kind[i][j] != PARTITION_RANGE_DATUM_VALUE)
+						continue;
+				}
+
+				/*
+				 * Compare the actual values. Note that it would be both
+				 * incorrect and unsafe to invoke the comparison operator
+				 * derived from the partitioning specification here.  It would
+				 * be incorrect because we want the relcache entry to be
+				 * updated for ANY change to the partition bounds, not just
+				 * those that the partitioning operator thinks are
+				 * significant.  It would be unsafe because we might reach
+				 * this code in the context of an aborted transaction, and an
+				 * arbitrary partitioning operator might not be safe in that
+				 * 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;
+			}
+
+			if (b1->indexes[i] != b2->indexes[i])
+				return false;
+		}
+
+		/* There are ndatums+1 indexes in case of range partitions */
+		if (b1->strategy == PARTITION_STRATEGY_RANGE &&
+			b1->indexes[i] != b2->indexes[i])
+			return false;
+	}
+	return true;
+}
+
+/*
+ * Return a copy of given PartitionBoundInfo structure. The data types of bounds
+ * are described by given partition key specification.
+ */
+PartitionBoundInfo
+partition_bounds_copy(PartitionBoundInfo src,
+					  PartitionKey key)
+{
+	PartitionBoundInfo dest;
+	int			i;
+	int			ndatums;
+	int			partnatts;
+	int			num_indexes;
+
+	dest = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
+
+	dest->strategy = src->strategy;
+	ndatums = dest->ndatums = src->ndatums;
+	partnatts = key->partnatts;
+
+	num_indexes = get_partition_bound_num_indexes(src);
+
+	/* List partitioned tables have only a single partition key. */
+	Assert(key->strategy != PARTITION_STRATEGY_LIST || partnatts == 1);
+
+	dest->datums = (Datum **) palloc(sizeof(Datum *) * ndatums);
+
+	if (src->kind != NULL)
+	{
+		dest->kind = (PartitionRangeDatumKind **) palloc(ndatums *
+														 sizeof(PartitionRangeDatumKind *));
+		for (i = 0; i < ndatums; i++)
+		{
+			dest->kind[i] = (PartitionRangeDatumKind *) palloc(partnatts *
+															   sizeof(PartitionRangeDatumKind));
+
+			memcpy(dest->kind[i], src->kind[i],
+				   sizeof(PartitionRangeDatumKind) * key->partnatts);
+		}
+	}
+	else
+		dest->kind = NULL;
+
+	for (i = 0; i < ndatums; i++)
+	{
+		int			j;
+
+		/*
+		 * For a corresponding to hash partition, datums array will have two
+		 * elements - modulus and remainder.
+		 */
+		bool		hash_part = (key->strategy == PARTITION_STRATEGY_HASH);
+		int			natts = hash_part ? 2 : partnatts;
+
+		dest->datums[i] = (Datum *) palloc(sizeof(Datum) * natts);
+
+		for (j = 0; j < natts; j++)
+		{
+			bool		byval;
+			int			typlen;
+
+			if (hash_part)
+			{
+				typlen = sizeof(int32); /* Always int4 */
+				byval = true;	/* int4 is pass-by-value */
+			}
+			else
+			{
+				byval = key->parttypbyval[j];
+				typlen = key->parttyplen[j];
+			}
+
+			if (dest->kind == NULL ||
+				dest->kind[i][j] == PARTITION_RANGE_DATUM_VALUE)
+				dest->datums[i][j] = datumCopy(src->datums[i][j],
+											   byval, typlen);
+		}
+	}
+
+	dest->indexes = (int *) palloc(sizeof(int) * num_indexes);
+	memcpy(dest->indexes, src->indexes, sizeof(int) * num_indexes);
+
+	dest->null_index = src->null_index;
+	dest->default_index = src->default_index;
+
+	return dest;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent.  Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Relation parent,
+						  PartitionBoundSpec *spec)
+{
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	int				nparts = RelationGetPartitionCount(parent);
+	Oid			   *partoids = RelationGetPartitionOids(parent);
+	PartitionBoundInfo boundinfo = RelationGetPartitionBounds(parent);
+	ParseState *pstate = make_parsestate(NULL);
+	int			with = -1;
+	bool		overlap = false;
+
+	if (spec->is_default)
+	{
+		/*
+		 * The default partition bound never conflicts with any other
+		 * partition's; if that's what we're attaching, the only possible
+		 * problem is that one already exists, so check for that and we're
+		 * done.
+		 */
+		if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
+			return;
+
+		/* Default partition already exists, error out. */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+						relname, get_rel_name(partoids[boundinfo->default_index])),
+				 parser_errposition(pstate, spec->location)));
+	}
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+			{
+				Assert(spec->strategy == PARTITION_STRATEGY_HASH);
+				Assert(spec->remainder >= 0 && spec->remainder < spec->modulus);
+
+				if (nparts > 0)
+				{
+					Datum	  **datums = boundinfo->datums;
+					int			ndatums = boundinfo->ndatums;
+					int			greatest_modulus;
+					int			remainder;
+					int			offset;
+					bool		valid_modulus = true;
+					int			prev_modulus,	/* Previous largest modulus */
+								next_modulus;	/* Next largest modulus */
+
+					/*
+					 * Check rule that every modulus must be a factor of the
+					 * next larger modulus.  For example, if you have a bunch
+					 * of partitions that all have modulus 5, you can add a
+					 * new partition with modulus 10 or a new partition with
+					 * modulus 15, but you cannot add both a partition with
+					 * modulus 10 and a partition with modulus 15, because 10
+					 * is not a factor of 15.
+					 *
+					 * Get the greatest (modulus, remainder) pair contained in
+					 * boundinfo->datums that is less than or equal to the
+					 * (spec->modulus, spec->remainder) pair.
+					 */
+					offset = partition_hash_bsearch(boundinfo,
+													spec->modulus,
+													spec->remainder);
+					if (offset < 0)
+					{
+						next_modulus = DatumGetInt32(datums[0][0]);
+						valid_modulus = (next_modulus % spec->modulus) == 0;
+					}
+					else
+					{
+						prev_modulus = DatumGetInt32(datums[offset][0]);
+						valid_modulus = (spec->modulus % prev_modulus) == 0;
+
+						if (valid_modulus && (offset + 1) < ndatums)
+						{
+							next_modulus = DatumGetInt32(datums[offset + 1][0]);
+							valid_modulus = (next_modulus % spec->modulus) == 0;
+						}
+					}
+
+					if (!valid_modulus)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("every hash partition modulus must be a factor of the next larger modulus")));
+
+					greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
+					remainder = spec->remainder;
+
+					/*
+					 * Normally, the lowest remainder that could conflict with
+					 * the new partition is equal to the remainder specified
+					 * for the new partition, but when the new partition has a
+					 * modulus higher than any used so far, we need to adjust.
+					 */
+					if (remainder >= greatest_modulus)
+						remainder = remainder % greatest_modulus;
+
+					/* Check every potentially-conflicting remainder. */
+					do
+					{
+						if (boundinfo->indexes[remainder] != -1)
+						{
+							overlap = true;
+							with = boundinfo->indexes[remainder];
+							break;
+						}
+						remainder += spec->modulus;
+					} while (remainder < greatest_modulus);
+				}
+
+				break;
+			}
+
+		case PARTITION_STRATEGY_LIST:
+			{
+				FmgrInfo partsupfunc;
+
+				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+				/* Get partsupfunc FmgrInfo for the only key. */
+				fmgr_info_copy(&partsupfunc,
+							   partition_getprocinfo(parent, key, 0),
+							   CurrentMemoryContext);
+
+				if (nparts > 0)
+				{
+					ListCell   *cell;
+
+					Assert(boundinfo &&
+						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
+						   (boundinfo->ndatums > 0 ||
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
+
+					foreach(cell, spec->listdatums)
+					{
+						Const	   *val = castNode(Const, lfirst(cell));
+
+						if (!val->constisnull)
+						{
+							int			offset;
+							bool		equal;
+
+							offset = partition_list_bsearch(&partsupfunc,
+														key->partcollation,
+															boundinfo,
+															val->constvalue,
+															&equal);
+							if (offset >= 0 && equal)
+							{
+								overlap = true;
+								with = boundinfo->indexes[offset];
+								break;
+							}
+						}
+						else if (partition_bound_accepts_nulls(boundinfo))
+						{
+							overlap = true;
+							with = boundinfo->null_index;
+							break;
+						}
+					}
+				}
+
+				break;
+			}
+
+		case PARTITION_STRATEGY_RANGE:
+			{
+				PartitionRangeBound *lower,
+						   *upper;
+				FmgrInfo partsupfunc[PARTITION_MAX_KEYS];
+				int		i;
+
+				/* Get partsupfunc FmgrInfo's. */
+				for (i = 0; i < key->partnatts; i++)
+				{
+					fmgr_info_copy(&partsupfunc[i],
+								   partition_getprocinfo(parent, key, i),
+								   CurrentMemoryContext);
+				}
+
+				Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+				lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
+				upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+
+				/*
+				 * First check if the resulting range would be empty with
+				 * specified lower and upper bounds
+				 */
+				if (partition_rbound_cmp(key->partnatts, partsupfunc,
+										 key->partcollation, lower->datums,
+										 lower->kind, true, upper) >= 0)
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("empty range bound specified for partition \"%s\"",
+									relname),
+							 errdetail("Specified lower bound %s is greater than or equal to upper bound %s.",
+									   get_range_partbound_string(spec->lowerdatums),
+									   get_range_partbound_string(spec->upperdatums)),
+							 parser_errposition(pstate, spec->location)));
+				}
+
+				if (nparts > 0)
+				{
+					int			offset;
+					bool		equal;
+
+					Assert(boundinfo &&
+						   boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
+						   (boundinfo->ndatums > 0 ||
+							partition_bound_has_default(boundinfo)));
+
+					/*
+					 * Test whether the new lower bound (which is treated
+					 * inclusively as part of the new partition) lies inside
+					 * an existing partition, or in a gap.
+					 *
+					 * If it's inside an existing partition, the bound at
+					 * offset + 1 will be the upper bound of that partition,
+					 * and its index will be >= 0.
+					 *
+					 * If it's in a gap, the bound at offset + 1 will be the
+					 * lower bound of the next partition, and its index will
+					 * be -1. This is also true if there is no next partition,
+					 * since the index array is initialised with an extra -1
+					 * at the end.
+					 */
+					offset = partition_range_bsearch(key->partnatts,
+													 partsupfunc,
+													 key->partcollation,
+													 boundinfo, lower,
+													 &equal);
+
+					if (boundinfo->indexes[offset + 1] < 0)
+					{
+						/*
+						 * Check that the new partition will fit in the gap.
+						 * For it to fit, the new upper bound must be less
+						 * than or equal to the lower bound of the next
+						 * partition, if there is one.
+						 */
+						if (offset + 1 < boundinfo->ndatums)
+						{
+							int32		cmpval;
+							Datum 	   *datums;
+							PartitionRangeDatumKind *kind;
+							bool		is_lower;
+
+							datums = boundinfo->datums[offset + 1];
+							kind = boundinfo->kind[offset + 1];
+							is_lower = (boundinfo->indexes[offset + 1] == -1);
+
+							cmpval = partition_rbound_cmp(key->partnatts,
+														  partsupfunc,
+														  key->partcollation,
+														  datums, kind,
+														  is_lower, upper);
+							if (cmpval < 0)
+							{
+								/*
+								 * The new partition overlaps with the
+								 * existing partition between offset + 1 and
+								 * offset + 2.
+								 */
+								overlap = true;
+								with = boundinfo->indexes[offset + 2];
+							}
+						}
+					}
+					else
+					{
+						/*
+						 * The new partition overlaps with the existing
+						 * partition between offset and offset + 1.
+						 */
+						overlap = true;
+						with = boundinfo->indexes[offset + 1];
+					}
+				}
+
+				break;
+			}
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) key->strategy);
+	}
+
+	if (overlap)
+	{
+		Assert(with >= 0);
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" would overlap partition \"%s\"",
+						relname, get_rel_name(partoids[with])),
+				 parser_errposition(pstate, spec->location)));
+	}
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * would properly belong to the new partition being added.  If it finds one,
+ * it throws an error.
+ */
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
+{
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
+	List	   *all_parts;
+	ListCell   *lc;
+
+	new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+		? get_qual_for_list(parent, new_spec)
+		: get_qual_for_range(parent, new_spec, false);
+	def_part_constraints =
+		get_proposed_default_constraint(new_part_constraints);
+
+	/*
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
+	 */
+	if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
+	{
+		ereport(INFO,
+				(errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
+
+	/*
+	 * Scan the default partition and its subpartitions, and check for rows
+	 * that do not satisfy the revised partition constraints.
+	 */
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+		{
+			part_rel = heap_open(part_relid, NoLock);
+
+			/*
+			 * If the partition constraints on default partition child imply
+			 * that it will not contain any row that would belong to the new
+			 * partition, we can avoid scanning the child table.
+			 */
+			if (PartConstraintImpliedByRelConstraint(part_rel,
+													 def_part_constraints))
+			{
+				ereport(INFO,
+						(errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints",
+								RelationGetRelationName(part_rel))));
+
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+		}
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(def_part_constraints);
+		partition_constraint = (Expr *)
+			map_partition_varattnos((List *) constr,
+									1, part_rel, parent, NULL);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (!ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
+	}
+}
+
+/*
+ * get_hash_partition_greatest_modulus
+ *
+ * Returns the greatest modulus of the hash partition bound. The greatest
+ * modulus will be at the end of the datums array because hash partitions are
+ * arranged in the ascending order of their modulus and remainders.
+ */
+int
+get_hash_partition_greatest_modulus(PartitionBoundInfo bound)
+{
+	Assert(bound && bound->strategy == PARTITION_STRATEGY_HASH);
+	Assert(bound->datums && bound->ndatums > 0);
+	Assert(DatumGetInt32(bound->datums[bound->ndatums - 1][0]) > 0);
+
+	return DatumGetInt32(bound->datums[bound->ndatums - 1][0]);
+}
+
+/*
+ * make_one_range_bound
+ *
+ * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
+ * and a flag telling whether the bound is lower or not.  Made into a function
+ * because there are multiple sites that want to use this facility.
+ */
+PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell   *lc;
+	int			i;
+
+	Assert(datums != NIL);
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	bound->index = index;
+	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+	bound->kind = (PartitionRangeDatumKind *) palloc0(key->partnatts *
+													  sizeof(PartitionRangeDatumKind));
+	bound->lower = lower;
+
+	i = 0;
+	foreach(lc, datums)
+	{
+		PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
+
+		/* What's contained in this range datum? */
+		bound->kind[i] = datum->kind;
+
+		if (datum->kind == PARTITION_RANGE_DATUM_VALUE)
+		{
+			Const	   *val = castNode(Const, datum->value);
+
+			if (val->constisnull)
+				elog(ERROR, "invalid range bound datum");
+			bound->datums[i] = val->constvalue;
+		}
+
+		i++;
+	}
+
+	return bound;
+}
+
+/*
+ * partition_rbound_cmp
+ *
+ * Return for two range bounds whether the 1st one (specified in datums1,
+ * kind1, and lower1) is <, =, or > the bound specified in *b2.
+ *
+ * partnatts, partsupfunc and partcollation give the number of attributes in the
+ * bounds to be compared, comparison function to be used and the collations of
+ * attributes, respectively.
+ *
+ * Note that if the values of the two range bounds compare equal, then we take
+ * into account whether they are upper or lower bounds, and an upper bound is
+ * considered to be smaller than a lower bound. This is important to the way
+ * that RelationBuildPartitionDesc() builds the PartitionBoundInfoData
+ * structure, which only stores the upper bound of a common boundary between
+ * two contiguous partitions.
+ */
+int32
+partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
+					 Oid *partcollation,
+					 Datum *datums1, PartitionRangeDatumKind *kind1,
+					 bool lower1, PartitionRangeBound *b2)
+{
+	int32		cmpval = 0;		/* placate compiler */
+	int			i;
+	Datum	   *datums2 = b2->datums;
+	PartitionRangeDatumKind *kind2 = b2->kind;
+	bool		lower2 = b2->lower;
+
+	for (i = 0; i < partnatts; i++)
+	{
+		/*
+		 * First, handle cases where the column is unbounded, which should not
+		 * invoke the comparison procedure, and should not consider any later
+		 * columns. Note that the PartitionRangeDatumKind enum elements
+		 * compare the same way as the values they represent.
+		 */
+		if (kind1[i] < kind2[i])
+			return -1;
+		else if (kind1[i] > kind2[i])
+			return 1;
+		else if (kind1[i] != PARTITION_RANGE_DATUM_VALUE)
+
+			/*
+			 * The column bounds are both MINVALUE or both MAXVALUE. No later
+			 * columns should be considered, but we still need to compare
+			 * whether they are upper or lower bounds.
+			 */
+			break;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
+												 partcollation[i],
+												 datums1[i],
+												 datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/*
+	 * If the comparison is anything other than equal, we're done. If they
+	 * compare equal though, we still have to consider whether the boundaries
+	 * are inclusive or exclusive.  Exclusive one is considered smaller of the
+	 * two.
+	 */
+	if (cmpval == 0 && lower1 != lower2)
+		cmpval = lower1 ? 1 : -1;
+
+	return cmpval;
+}
+
+/*
+ * partition_rbound_datum_cmp
+ *
+ * Return whether range bound (specified in rb_datums, rb_kind, and rb_lower)
+ * is <, =, or > partition key of tuple (tuple_datums)
+ *
+ * n_tuple_datums, partsupfunc and partcollation give number of attributes in
+ * the bounds to be compared, comparison function to be used and the collations
+ * of attributes resp.
+ *
+ */
+int32
+partition_rbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation,
+						   Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
+						   Datum *tuple_datums, int n_tuple_datums)
+{
+	int			i;
+	int32		cmpval = -1;
+
+	for (i = 0; i < n_tuple_datums; i++)
+	{
+		if (rb_kind[i] == PARTITION_RANGE_DATUM_MINVALUE)
+			return -1;
+		else if (rb_kind[i] == PARTITION_RANGE_DATUM_MAXVALUE)
+			return 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
+												 partcollation[i],
+												 rb_datums[i],
+												 tuple_datums[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	return cmpval;
+}
+
+/*
+ * partition_hbound_cmp
+ *
+ * Compares modulus first, then remainder if modulus are equal.
+ */
+int32
+partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2)
+{
+	if (modulus1 < modulus2)
+		return -1;
+	if (modulus1 > modulus2)
+		return 1;
+	if (modulus1 == modulus2 && remainder1 != remainder2)
+		return (remainder1 > remainder2) ? 1 : -1;
+	return 0;
+}
+
+/*
+ * partition_list_bsearch
+ *		Returns the index of the greatest bound datum that is less than equal
+ * 		to the given value or -1 if all of the bound datums are greater
+ *
+ * *is_equal is set to true if the bound datum at the returned index is equal
+ * to the input value.
+ */
+int
+partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
+					   PartitionBoundInfo boundinfo,
+					   Datum value, bool *is_equal)
+{
+	int			lo,
+				hi,
+				mid;
+
+	lo = -1;
+	hi = boundinfo->ndatums - 1;
+	while (lo < hi)
+	{
+		int32		cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
+												 partcollation[0],
+												 boundinfo->datums[mid][0],
+												 value));
+		if (cmpval <= 0)
+		{
+			lo = mid;
+			*is_equal = (cmpval == 0);
+			if (*is_equal)
+				break;
+		}
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
+
+/*
+ * partition_range_bsearch
+ *		Returns the index of the greatest range bound that is less than or
+ *		equal to the given range bound or -1 if all of the range bounds are
+ *		greater
+ *
+ * *is_equal is set to true if the range bound at the returned index is equal
+ * to the input range bound
+ */
+int
+partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
+						Oid *partcollation,
+						PartitionBoundInfo boundinfo,
+						PartitionRangeBound *probe, bool *is_equal)
+{
+	int			lo,
+				hi,
+				mid;
+
+	lo = -1;
+	hi = boundinfo->ndatums - 1;
+	while (lo < hi)
+	{
+		int32		cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cmpval = partition_rbound_cmp(partnatts, partsupfunc,
+									  partcollation,
+									  boundinfo->datums[mid],
+									  boundinfo->kind[mid],
+									  (boundinfo->indexes[mid] == -1),
+									  probe);
+		if (cmpval <= 0)
+		{
+			lo = mid;
+			*is_equal = (cmpval == 0);
+
+			if (*is_equal)
+				break;
+		}
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
+
+/*
+ * partition_range_bsearch
+ *		Returns the index of the greatest range bound that is less than or
+ *		equal to the given tuple or -1 if all of the range bounds are greater
+ *
+ * *is_equal is set to true if the range bound at the returned index is equal
+ * to the input tuple.
+ */
+int
+partition_range_datum_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
+							  PartitionBoundInfo boundinfo,
+							  int nvalues, Datum *values, bool *is_equal)
+{
+	int			lo,
+				hi,
+				mid;
+
+	lo = -1;
+	hi = boundinfo->ndatums - 1;
+	while (lo < hi)
+	{
+		int32		cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cmpval = partition_rbound_datum_cmp(partsupfunc,
+											partcollation,
+											boundinfo->datums[mid],
+											boundinfo->kind[mid],
+											values,
+											nvalues);
+		if (cmpval <= 0)
+		{
+			lo = mid;
+			*is_equal = (cmpval == 0);
+
+			if (*is_equal)
+				break;
+		}
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
+
+/*
+ * partition_hash_bsearch
+ *		Returns the index of the greatest (modulus, remainder) pair that is
+ *		less than or equal to the given (modulus, remainder) pair or -1 if
+ *		all of them are greater
+ */
+int
+partition_hash_bsearch(PartitionBoundInfo boundinfo,
+					   int modulus, int remainder)
+{
+	int			lo,
+				hi,
+				mid;
+
+	lo = -1;
+	hi = boundinfo->ndatums - 1;
+	while (lo < hi)
+	{
+		int32		cmpval,
+					bound_modulus,
+					bound_remainder;
+
+		mid = (lo + hi + 1) / 2;
+		bound_modulus = DatumGetInt32(boundinfo->datums[mid][0]);
+		bound_remainder = DatumGetInt32(boundinfo->datums[mid][1]);
+		cmpval = partition_hbound_cmp(bound_modulus, bound_remainder,
+									  modulus, remainder);
+		if (cmpval <= 0)
+		{
+			lo = mid;
+
+			if (cmpval == 0)
+				break;
+		}
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
+
+/*
+ * get_partition_bound_num_indexes
+ *
+ * Returns the number of the entries in the partition bound indexes array.
+ */
+static int
+get_partition_bound_num_indexes(PartitionBoundInfo bound)
+{
+	int			num_indexes;
+
+	Assert(bound);
+
+	switch (bound->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+
+			/*
+			 * The number of the entries in the indexes array is same as the
+			 * greatest modulus.
+			 */
+			num_indexes = get_hash_partition_greatest_modulus(bound);
+			break;
+
+		case PARTITION_STRATEGY_LIST:
+			num_indexes = bound->ndatums;
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			/* Range partitioned table has an extra index. */
+			num_indexes = bound->ndatums + 1;
+			break;
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) bound->strategy);
+	}
+
+	return num_indexes;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel)
+{
+	Oid			operoid;
+
+	/*
+	 * First check if there exists an operator of the given strategy, with
+	 * this column's type as both its lefttype and righttype, in the
+	 * partitioning operator family specified for the column.
+	 */
+	operoid = get_opfamily_member(key->partopfamily[col],
+								  key->parttypid[col],
+								  key->parttypid[col],
+								  strategy);
+
+	/*
+	 * If one doesn't exist, we must resort to using an operator in the same
+	 * operator family but with the operator class declared input type.  It is
+	 * OK to do so, because the column's type is known to be binary-coercible
+	 * with the operator class input type (otherwise, the operator class in
+	 * question would not have been accepted as the partitioning operator
+	 * class).  We must however inform the caller to wrap the non-Const
+	 * expression with a RelabelType node to denote the implicit coercion. It
+	 * ensures that the resulting expression structurally matches similarly
+	 * processed expressions within the optimizer.
+	 */
+	if (!OidIsValid(operoid))
+	{
+		operoid = get_opfamily_member(key->partopfamily[col],
+									  key->partopcintype[col],
+									  key->partopcintype[col],
+									  strategy);
+		if (!OidIsValid(operoid))
+			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+				 strategy, key->partopcintype[col], key->partopcintype[col],
+				 key->partopfamily[col]);
+		*need_relabel = true;
+	}
+	else
+		*need_relabel = false;
+
+	return operoid;
+}
+
+/*
+ * make_partition_op_expr
+ *		Returns an Expr for the given partition key column with arg1 and
+ *		arg2 as its leftop and rightop, respectively
+ */
+static Expr *
+make_partition_op_expr(PartitionKey key, int keynum,
+					   uint16 strategy, Expr *arg1, Expr *arg2)
+{
+	Oid			operoid;
+	bool		need_relabel = false;
+	Expr	   *result = NULL;
+
+	/* Get the correct btree operator for this partitioning column */
+	operoid = get_partition_operator(key, keynum, strategy, &need_relabel);
+
+	/*
+	 * Chosen operator may be such that the non-Const operand needs to be
+	 * coerced, so apply the same; see the comment in
+	 * get_partition_operator().
+	 */
+	if (!IsA(arg1, Const) &&
+		(need_relabel ||
+		 key->partcollation[keynum] != key->parttypcoll[keynum]))
+		arg1 = (Expr *) makeRelabelType(arg1,
+										key->partopcintype[keynum],
+										-1,
+										key->partcollation[keynum],
+										COERCE_EXPLICIT_CAST);
+
+	/* Generate the actual expression */
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			{
+				List	   *elems = (List *) arg2;
+				int			nelems = list_length(elems);
+
+				Assert(nelems >= 1);
+				Assert(keynum == 0);
+
+				if (nelems > 1 &&
+					!type_is_array(key->parttypid[keynum]))
+				{
+					ArrayExpr  *arrexpr;
+					ScalarArrayOpExpr *saopexpr;
+
+					/* Construct an ArrayExpr for the right-hand inputs */
+					arrexpr = makeNode(ArrayExpr);
+					arrexpr->array_typeid =
+									get_array_type(key->parttypid[keynum]);
+					arrexpr->array_collid = key->parttypcoll[keynum];
+					arrexpr->element_typeid = key->parttypid[keynum];
+					arrexpr->elements = elems;
+					arrexpr->multidims = false;
+					arrexpr->location = -1;
+
+					/* Build leftop = ANY (rightop) */
+					saopexpr = makeNode(ScalarArrayOpExpr);
+					saopexpr->opno = operoid;
+					saopexpr->opfuncid = get_opcode(operoid);
+					saopexpr->useOr = true;
+					saopexpr->inputcollid = key->partcollation[keynum];
+					saopexpr->args = list_make2(arg1, arrexpr);
+					saopexpr->location = -1;
+
+					result = (Expr *) saopexpr;
+				}
+				else
+				{
+					List	   *elemops = NIL;
+					ListCell   *lc;
+
+					foreach (lc, elems)
+					{
+						Expr   *elem = lfirst(lc),
+							   *elemop;
+
+						elemop = make_opclause(operoid,
+											   BOOLOID,
+											   false,
+											   arg1, elem,
+											   InvalidOid,
+											   key->partcollation[keynum]);
+						elemops = lappend(elemops, elemop);
+					}
+
+					result = nelems > 1 ? makeBoolExpr(OR_EXPR, elemops, -1) : linitial(elemops);
+				}
+				break;
+			}
+
+		case PARTITION_STRATEGY_RANGE:
+			result = make_opclause(operoid,
+								   BOOLOID,
+								   false,
+								   arg1, arg2,
+								   InvalidOid,
+								   key->partcollation[keynum]);
+			break;
+
+		default:
+			elog(ERROR, "invalid partitioning strategy");
+			break;
+	}
+
+	return result;
+}
+
+/*
+ * get_qual_for_hash
+ *
+ * Returns a CHECK constraint expression to use as a hash partition's
+ * constraint, given the parent relation and partition bound structure.
+ *
+ * The partition constraint for a hash partition is always a call to the
+ * built-in function satisfies_hash_partition().
+ */
+static List *
+get_qual_for_hash(Relation parent, PartitionBoundSpec *spec)
+{
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	FuncExpr   *fexpr;
+	Node	   *relidConst;
+	Node	   *modulusConst;
+	Node	   *remainderConst;
+	List	   *args;
+	ListCell   *partexprs_item;
+	int			i;
+
+	/* Fixed arguments. */
+	relidConst = (Node *) makeConst(OIDOID,
+									-1,
+									InvalidOid,
+									sizeof(Oid),
+									ObjectIdGetDatum(RelationGetRelid(parent)),
+									false,
+									true);
+
+	modulusConst = (Node *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  Int32GetDatum(spec->modulus),
+									  false,
+									  true);
+
+	remainderConst = (Node *) makeConst(INT4OID,
+										-1,
+										InvalidOid,
+										sizeof(int32),
+										Int32GetDatum(spec->remainder),
+										false,
+										true);
+
+	args = list_make3(relidConst, modulusConst, remainderConst);
+	partexprs_item = list_head(key->partexprs);
+
+	/* Add an argument for each key column. */
+	for (i = 0; i < key->partnatts; i++)
+	{
+		Node	   *keyCol;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			keyCol = (Node *) makeVar(1,
+									  key->partattrs[i],
+									  key->parttypid[i],
+									  key->parttypmod[i],
+									  key->parttypcoll[i],
+									  0);
+		}
+		else
+		{
+			keyCol = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		args = lappend(args, keyCol);
+	}
+
+	fexpr = makeFuncExpr(F_SATISFIES_HASH_PARTITION,
+						 BOOLOID,
+						 args,
+						 InvalidOid,
+						 InvalidOid,
+						 COERCE_EXPLICIT_CALL);
+
+	return list_make1(fexpr);
+}
+
+/*
+ * get_qual_for_list
+ *
+ * Returns an implicit-AND list of expressions to use as a list partition's
+ * constraint, given the parent relation and partition bound structure.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since in that case there is no constraint.
+ */
+static List *
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
+{
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	List	   *result;
+	Expr	   *keyCol;
+	Expr	   *opexpr;
+	NullTest   *nulltest;
+	ListCell   *cell;
+	List	   *elems = NIL;
+	bool		list_has_null = false;
+
+	/*
+	 * Only single-column list partitioning is supported, so we are worried
+	 * only about the partition key with index 0.
+	 */
+	Assert(key->partnatts == 1);
+
+	/* Construct Var or expression representing the partition column */
+	if (key->partattrs[0] != 0)
+		keyCol = (Expr *) makeVar(1,
+								  key->partattrs[0],
+								  key->parttypid[0],
+								  key->parttypmod[0],
+								  key->parttypcoll[0],
+								  0);
+	else
+		keyCol = (Expr *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
+	{
+		int			i;
+		int			ndatums = 0;
+		PartitionBoundInfo boundinfo = RelationGetPartitionBounds(parent);
+
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/*
+			 * Construct Const from known-not-null datum.  We must be careful
+			 * to copy the value, because our result has to be able to outlive
+			 * the relcache entry we're copying from.
+			 */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							datumCopy(*boundinfo->datums[i],
+									  key->parttypbyval[0],
+									  key->parttyplen[0]),
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			elems = lappend(elems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				elems = lappend(elems, copyObject(val));
+		}
+	}
+
+	if (elems)
+	{
+		/*
+		 * Generate the operator expression from the non-null partition
+		 * values.
+		 */
+		opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
+										keyCol, (Expr *) elems);
+	}
+	else
+	{
+		/*
+		 * If there are no partition values, we don't need an operator
+		 * expression.
+		 */
+		opexpr = NULL;
+	}
+
+	if (!list_has_null)
+	{
+		/*
+		 * Gin up a "col IS NOT NULL" test that will be AND'd with the main
+		 * expression.  This might seem redundant, but the partition routing
+		 * machinery needs it.
+		 */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = keyCol;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+
+		result = opexpr ? list_make2(nulltest, opexpr) : list_make1(nulltest);
+	}
+	else
+	{
+		/*
+		 * Gin up a "col IS NULL" test that will be OR'd with the main
+		 * expression.
+		 */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = keyCol;
+		nulltest->nulltesttype = IS_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+
+		if (opexpr)
+		{
+			Expr	   *or;
+
+			or = makeBoolExpr(OR_EXPR, list_make2(nulltest, opexpr), -1);
+			result = list_make1(or);
+		}
+		else
+			result = list_make1(nulltest);
+	}
+
+	/*
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
+	return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Returns an implicit-AND list of expressions to use as a range partition's
+ * constraint, given the parent relation and partition bound structure.
+ *
+ * For a multi-column range partition key, say (a, b, c), with (al, bl, cl)
+ * as the lower bound tuple and (au, bu, cu) as the upper bound tuple, we
+ * generate an expression tree of the following form:
+ *
+ *	(a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
+ *		AND
+ *	(a > al OR (a = al AND b > bl) OR (a = al AND b = bl AND c >= cl))
+ *		AND
+ *	(a < au OR (a = au AND b < bu) OR (a = au AND b = bu AND c < cu))
+ *
+ * It is often the case that a prefix of lower and upper bound tuples contains
+ * the same values, for example, (al = au), in which case, we will emit an
+ * expression tree of the following form:
+ *
+ *	(a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
+ *		AND
+ *	(a = al)
+ *		AND
+ *	(b > bl OR (b = bl AND c >= cl))
+ *		AND
+ *	(b < bu) OR (b = bu AND c < cu))
+ *
+ * If a bound datum is either MINVALUE or MAXVALUE, these expressions are
+ * simplified using the fact that any value is greater than MINVALUE and less
+ * than MAXVALUE. So, for example, if cu = MAXVALUE, c < cu is automatically
+ * true, and we need not emit any expression for it, and the last line becomes
+ *
+ *	(b < bu) OR (b = bu), which is simplified to (b <= bu)
+ *
+ * In most common cases with only one partition column, say a, the following
+ * expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
+ *
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
+ * External callers should pass for_default as false; we set it to true only
+ * when recursing.
+ */
+static List *
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+				   bool for_default)
+{
+	List	   *result = NIL;
+	ListCell   *cell1,
+			   *cell2,
+			   *partexprs_item,
+			   *partexprs_item_saved;
+	int			i,
+				j;
+	PartitionRangeDatum *ldatum,
+			   *udatum;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	Expr	   *keyCol;
+	Const	   *lower_val,
+			   *upper_val;
+	List	   *lower_or_arms,
+			   *upper_or_arms;
+	int			num_or_arms,
+				current_or_arm;
+	ListCell   *lower_or_start_datum,
+			   *upper_or_start_datum;
+	bool		need_next_lower_arm,
+				need_next_upper_arm;
+
+	if (spec->is_default)
+	{
+		List	   *or_expr_args = NIL;
+		Oid		   *inhoids = RelationGetPartitionOids(parent);
+		int			nparts = RelationGetPartitionCount(parent),
+					i;
+
+		Assert(inhoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
+		{
+			Oid			inhrelid = inhoids[i];
+			HeapTuple	tuple;
+			Datum		datum;
+			bool		isnull;
+			PartitionBoundSpec *bspec;
+
+			tuple = SearchSysCache1(RELOID, inhrelid);
+			if (!HeapTupleIsValid(tuple))
+				elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+			datum = SysCacheGetAttr(RELOID, tuple,
+									Anum_pg_class_relpartbound,
+									&isnull);
+
+			Assert(!isnull);
+			bspec = (PartitionBoundSpec *)
+				stringToNode(TextDatumGetCString(datum));
+			if (!IsA(bspec, PartitionBoundSpec))
+				elog(ERROR, "expected PartitionBoundSpec");
+
+			if (!bspec->is_default)
+			{
+				List	   *part_qual;
+
+				part_qual = get_qual_for_range(parent, bspec, true);
+
+				/*
+				 * AND the constraints of the partition and add to
+				 * or_expr_args
+				 */
+				or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+									   ? makeBoolExpr(AND_EXPR, part_qual, -1)
+									   : linitial(part_qual));
+			}
+			ReleaseSysCache(tuple);
+		}
+
+		if (or_expr_args != NIL)
+		{
+			Expr	   *other_parts_constr;
+
+			/*
+			 * Combine the constraints obtained for non-default partitions
+			 * using OR.  As requested, each of the OR's args doesn't include
+			 * the NOT NULL test for partition keys (which is to avoid its
+			 * useless repetition).  Add the same now.
+			 */
+			other_parts_constr =
+				makeBoolExpr(AND_EXPR,
+							 lappend(get_range_nulltest(key),
+									 list_length(or_expr_args) > 1
+									 ? makeBoolExpr(OR_EXPR, or_expr_args,
+													-1)
+									 : linitial(or_expr_args)),
+							 -1);
+
+			/*
+			 * Finally, the default partition contains everything *NOT*
+			 * contained in the non-default partitions.
+			 */
+			result = list_make1(makeBoolExpr(NOT_EXPR,
+											 list_make1(other_parts_constr), -1));
+		}
+
+		return result;
+	}
+
+	lower_or_start_datum = list_head(spec->lowerdatums);
+	upper_or_start_datum = list_head(spec->upperdatums);
+	num_or_arms = key->partnatts;
+
+	/*
+	 * If it is the recursive call for default, we skip the get_range_nulltest
+	 * to avoid accumulating the NullTest on the same keys for each partition.
+	 */
+	if (!for_default)
+		result = get_range_nulltest(key);
+
+	/*
+	 * Iterate over the key columns and check if the corresponding lower and
+	 * upper datums are equal using the btree equality operator for the
+	 * column's type.  If equal, we emit single keyCol = common_value
+	 * expression.  Starting from the first column for which the corresponding
+	 * lower and upper bound datums are not equal, we generate OR expressions
+	 * as shown in the function's header comment.
+	 */
+	i = 0;
+	partexprs_item = list_head(key->partexprs);
+	partexprs_item_saved = partexprs_item;	/* placate compiler */
+	forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums)
+	{
+		EState	   *estate;
+		MemoryContext oldcxt;
+		Expr	   *test_expr;
+		ExprState  *test_exprstate;
+		Datum		test_result;
+		bool		isNull;
+
+		ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
+		udatum = castNode(PartitionRangeDatum, lfirst(cell2));
+
+		/*
+		 * Since get_range_key_properties() modifies partexprs_item, and we
+		 * might need to start over from the previous expression in the later
+		 * part of this function, save away the current value.
+		 */
+		partexprs_item_saved = partexprs_item;
+
+		get_range_key_properties(key, i, ldatum, udatum,
+								 &partexprs_item,
+								 &keyCol,
+								 &lower_val, &upper_val);
+
+		/*
+		 * If either value is NULL, the corresponding partition bound is
+		 * either MINVALUE or MAXVALUE, and we treat them as unequal, because
+		 * even if they're the same, there is no common value to equate the
+		 * key column with.
+		 */
+		if (!lower_val || !upper_val)
+			break;
+
+		/* Create the test expression */
+		estate = CreateExecutorState();
+		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
+										   (Expr *) lower_val,
+										   (Expr *) upper_val);
+		fix_opfuncids((Node *) test_expr);
+		test_exprstate = ExecInitExpr(test_expr, NULL);
+		test_result = ExecEvalExprSwitchContext(test_exprstate,
+												GetPerTupleExprContext(estate),
+												&isNull);
+		MemoryContextSwitchTo(oldcxt);
+		FreeExecutorState(estate);
+
+		/* If not equal, go generate the OR expressions */
+		if (!DatumGetBool(test_result))
+			break;
+
+		/*
+		 * The bounds for the last key column can't be equal, because such a
+		 * range partition would never be allowed to be defined (it would have
+		 * an empty range otherwise).
+		 */
+		if (i == key->partnatts - 1)
+			elog(ERROR, "invalid range bound specification");
+
+		/* Equal, so generate keyCol = lower_val expression */
+		result = lappend(result,
+						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
+												keyCol, (Expr *) lower_val));
+
+		i++;
+	}
+
+	/* First pair of lower_val and upper_val that are not equal. */
+	lower_or_start_datum = cell1;
+	upper_or_start_datum = cell2;
+
+	/* OR will have as many arms as there are key columns left. */
+	num_or_arms = key->partnatts - i;
+	current_or_arm = 0;
+	lower_or_arms = upper_or_arms = NIL;
+	need_next_lower_arm = need_next_upper_arm = true;
+	while (current_or_arm < num_or_arms)
+	{
+		List	   *lower_or_arm_args = NIL,
+				   *upper_or_arm_args = NIL;
+
+		/* Restart scan of columns from the i'th one */
+		j = i;
+		partexprs_item = partexprs_item_saved;
+
+		for_both_cell(cell1, lower_or_start_datum, cell2, upper_or_start_datum)
+		{
+			PartitionRangeDatum *ldatum_next = NULL,
+					   *udatum_next = NULL;
+
+			ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
+			if (lnext(cell1))
+				ldatum_next = castNode(PartitionRangeDatum,
+									   lfirst(lnext(cell1)));
+			udatum = castNode(PartitionRangeDatum, lfirst(cell2));
+			if (lnext(cell2))
+				udatum_next = castNode(PartitionRangeDatum,
+									   lfirst(lnext(cell2)));
+			get_range_key_properties(key, j, ldatum, udatum,
+									 &partexprs_item,
+									 &keyCol,
+									 &lower_val, &upper_val);
+
+			if (need_next_lower_arm && lower_val)
+			{
+				uint16		strategy;
+
+				/*
+				 * For the non-last columns of this arm, use the EQ operator.
+				 * For the last column of this arm, use GT, unless this is the
+				 * last column of the whole bound check, or the next bound
+				 * datum is MINVALUE, in which case use GE.
+				 */
+				if (j - i < current_or_arm)
+					strategy = BTEqualStrategyNumber;
+				else if (j == key->partnatts - 1 ||
+						 (ldatum_next &&
+						  ldatum_next->kind == PARTITION_RANGE_DATUM_MINVALUE))
+					strategy = BTGreaterEqualStrategyNumber;
+				else
+					strategy = BTGreaterStrategyNumber;
+
+				lower_or_arm_args = lappend(lower_or_arm_args,
+											make_partition_op_expr(key, j,
+																   strategy,
+																   keyCol,
+																   (Expr *) lower_val));
+			}
+
+			if (need_next_upper_arm && upper_val)
+			{
+				uint16		strategy;
+
+				/*
+				 * For the non-last columns of this arm, use the EQ operator.
+				 * For the last column of this arm, use LT, unless the next
+				 * bound datum is MAXVALUE, in which case use LE.
+				 */
+				if (j - i < current_or_arm)
+					strategy = BTEqualStrategyNumber;
+				else if (udatum_next &&
+						 udatum_next->kind == PARTITION_RANGE_DATUM_MAXVALUE)
+					strategy = BTLessEqualStrategyNumber;
+				else
+					strategy = BTLessStrategyNumber;
+
+				upper_or_arm_args = lappend(upper_or_arm_args,
+											make_partition_op_expr(key, j,
+																   strategy,
+																   keyCol,
+																   (Expr *) upper_val));
+			}
+
+			/*
+			 * Did we generate enough of OR's arguments?  First arm considers
+			 * the first of the remaining columns, second arm considers first
+			 * two of the remaining columns, and so on.
+			 */
+			++j;
+			if (j - i > current_or_arm)
+			{
+				/*
+				 * We must not emit any more arms if the new column that will
+				 * be considered is unbounded, or this one was.
+				 */
+				if (!lower_val || !ldatum_next ||
+					ldatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
+					need_next_lower_arm = false;
+				if (!upper_val || !udatum_next ||
+					udatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
+					need_next_upper_arm = false;
+				break;
+			}
+		}
+
+		if (lower_or_arm_args != NIL)
+			lower_or_arms = lappend(lower_or_arms,
+									list_length(lower_or_arm_args) > 1
+									? makeBoolExpr(AND_EXPR, lower_or_arm_args, -1)
+									: linitial(lower_or_arm_args));
+
+		if (upper_or_arm_args != NIL)
+			upper_or_arms = lappend(upper_or_arms,
+									list_length(upper_or_arm_args) > 1
+									? makeBoolExpr(AND_EXPR, upper_or_arm_args, -1)
+									: linitial(upper_or_arm_args));
+
+		/* If no work to do in the next iteration, break away. */
+		if (!need_next_lower_arm && !need_next_upper_arm)
+			break;
+
+		++current_or_arm;
+	}
+
+	/*
+	 * Generate the OR expressions for each of lower and upper bounds (if
+	 * required), and append to the list of implicitly ANDed list of
+	 * expressions.
+	 */
+	if (lower_or_arms != NIL)
+		result = lappend(result,
+						 list_length(lower_or_arms) > 1
+						 ? makeBoolExpr(OR_EXPR, lower_or_arms, -1)
+						 : linitial(lower_or_arms));
+	if (upper_or_arms != NIL)
+		result = lappend(result,
+						 list_length(upper_or_arms) > 1
+						 ? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
+						 : linitial(upper_or_arms));
+
+	/*
+	 * As noted above, for non-default, we return list with constant TRUE. If
+	 * the result is NIL during the recursive call for default, it implies
+	 * this is the only other partition which can hold every value of the key
+	 * except NULL. Hence we return the NullTest result skipped earlier.
+	 */
+	if (result == NIL)
+		result = for_default
+			? get_range_nulltest(key)
+			: list_make1(makeBoolConst(true, false));
+
+	return result;
+}
+
+/*
+ * get_range_key_properties
+ *		Returns range partition key information for a given column
+ *
+ * This is a subroutine for get_qual_for_range, and its API is pretty
+ * specialized to that caller.
+ *
+ * Constructs an Expr for the key column (returned in *keyCol) and Consts
+ * for the lower and upper range limits (returned in *lower_val and
+ * *upper_val).  For MINVALUE/MAXVALUE limits, NULL is returned instead of
+ * a Const.  All of these structures are freshly palloc'd.
+ *
+ * *partexprs_item points to the cell containing the next expression in
+ * the key->partexprs list, or NULL.  It may be advanced upon return.
+ */
+static void
+get_range_key_properties(PartitionKey key, int keynum,
+						 PartitionRangeDatum *ldatum,
+						 PartitionRangeDatum *udatum,
+						 ListCell **partexprs_item,
+						 Expr **keyCol,
+						 Const **lower_val, Const **upper_val)
+{
+	/* Get partition key expression for this column */
+	if (key->partattrs[keynum] != 0)
+	{
+		*keyCol = (Expr *) makeVar(1,
+								   key->partattrs[keynum],
+								   key->parttypid[keynum],
+								   key->parttypmod[keynum],
+								   key->parttypcoll[keynum],
+								   0);
+	}
+	else
+	{
+		if (*partexprs_item == NULL)
+			elog(ERROR, "wrong number of partition key expressions");
+		*keyCol = copyObject(lfirst(*partexprs_item));
+		*partexprs_item = lnext(*partexprs_item);
+	}
+
+	/* Get appropriate Const nodes for the bounds */
+	if (ldatum->kind == PARTITION_RANGE_DATUM_VALUE)
+		*lower_val = castNode(Const, copyObject(ldatum->value));
+	else
+		*lower_val = NULL;
+
+	if (udatum->kind == PARTITION_RANGE_DATUM_VALUE)
+		*upper_val = castNode(Const, copyObject(udatum->value));
+	else
+		*upper_val = NULL;
+}
+
+/*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+	List	   *result = NIL;
+	NullTest   *nulltest;
+	ListCell   *partexprs_item;
+	int			i;
+
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		Expr	   *keyCol;
+
+		if (key->partattrs[i] != 0)
+		{
+			keyCol = (Expr *) makeVar(1,
+									  key->partattrs[i],
+									  key->parttypid[i],
+									  key->parttypmod[i],
+									  key->parttypcoll[i],
+									  0);
+		}
+		else
+		{
+			if (partexprs_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			keyCol = copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		nulltest = makeNode(NullTest);
+		nulltest->arg = keyCol;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+	}
+
+	return result;
+}
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 7666c6c412..d506bc0264 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -54,7 +54,6 @@
 #include "optimizer/predtest.h"
 #include "optimizer/prep.h"
 #include "partitioning/partprune.h"
-#include "partitioning/partbounds.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
 
@@ -437,7 +436,10 @@ prune_append_rel_partitions(RelOptInfo *rel)
 	context.partopfamily = rel->part_scheme->partopfamily;
 	context.partopcintype = rel->part_scheme->partopcintype;
 	context.partcollation = rel->part_scheme->partcollation;
-	context.partsupfunc = rel->part_scheme->partsupfunc;
+	for (i = 0; i < context.partnatts; i++)
+		fmgr_info_copy(&context.partsupfunc[i],
+					   &rel->part_scheme->partsupfunc[i],
+					   CurrentMemoryContext);
 	context.nparts = rel->nparts;
 	context.boundinfo = rel->boundinfo;
 
@@ -1413,7 +1415,8 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		partclause->op_is_ne = false;
 		partclause->expr = expr;
 		/* We know that expr is of Boolean type. */
-		partclause->cmpfn = rel->part_scheme->partsupfunc[partkeyidx].fn_oid;
+		partclause->cmpfn =
+						rel->part_scheme->partsupfunc[partkeyidx].fn_oid;
 		partclause->op_strategy = InvalidStrategy;
 
 		*pc = partclause;
@@ -1962,8 +1965,8 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
  *
  * 'nvalues', the number of Datums in the 'values' array.
  *
- * 'partsupfunc' contains partition hashing functions that can produce correct
- * hash for the type of the values contained in 'values'.
+ * 'partsupfunc' contains partition hashing functions that can produce
+ * correct hash for the type of the values contained in 'values'.
  *
  * 'nullkeys' is the set of partition keys that are null.
  */
@@ -2000,7 +2003,8 @@ get_matching_hash_bounds(PartitionPruneContext *context,
 			isnull[i] = bms_is_member(i, nullkeys);
 
 		greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
-		rowHash = compute_hash_value(partnatts, partsupfunc, values, isnull);
+		rowHash = compute_hash_value(partnatts, partsupfunc, values,
+									 isnull);
 
 		if (partindices[rowHash % greatest_modulus] >= 0)
 			result->bound_offsets =
@@ -2029,8 +2033,8 @@ get_matching_hash_bounds(PartitionPruneContext *context,
  *
  * 'nvalues', if non-zero, should be exactly 1, because of list partitioning.
  *
- * 'partsupfunc' contains the list partitioning comparison function to be used
- * to perform partition_list_bsearch
+ * 'partsupfunc' contains the list partitioning comparison function to be
+ * used to perform partition_list_bsearch
  *
  * 'nullkeys' is the set of partition keys that are null.
  */
@@ -2102,8 +2106,8 @@ get_matching_list_bounds(PartitionPruneContext *context,
 		result->bound_offsets = bms_add_range(NULL, 0,
 											  boundinfo->ndatums - 1);
 
-		off = partition_list_bsearch(partsupfunc, partcollation, boundinfo,
-									 value, &is_equal);
+		off = partition_list_bsearch(partsupfunc, partcollation,
+									 boundinfo, value, &is_equal);
 		if (off >= 0 && is_equal)
 		{
 
@@ -2231,9 +2235,9 @@ get_matching_list_bounds(PartitionPruneContext *context,
  *
  * 'nvalues', number of Datums in 'values' array. Must be <= context->partnatts.
  *
- * 'partsupfunc' contains the range partitioning comparison functions to be
- * used to perform partition_range_datum_bsearch or partition_rbound_datum_cmp
- * using.
+ * 'partsupfunc' contains the range partitioning comparison functions to
+ * be used to perform partition_range_datum_bsearch or
+ * partition_rbound_datum_cmp using.
  *
  * 'nullkeys' is the set of partition keys that are null.
  */
@@ -2797,7 +2801,8 @@ perform_pruning_base_step(PartitionPruneContext *context,
 				cmpfn = lfirst_oid(lc2);
 				Assert(OidIsValid(cmpfn));
 				if (cmpfn != context->partsupfunc[keyno].fn_oid)
-					fmgr_info(cmpfn, &partsupfunc[keyno]);
+					fmgr_info_cxt(cmpfn, &partsupfunc[keyno],
+								  CurrentMemoryContext);
 				else
 					fmgr_info_copy(&partsupfunc[keyno],
 								   &context->partsupfunc[keyno],
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 99643e83b2..ebcfa3b152 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -24,7 +24,6 @@
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
-#include "catalog/partition.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_authid.h"
diff --git a/src/backend/utils/cache/Makefile b/src/backend/utils/cache/Makefile
index a943f8ea4b..d2b0f0b929 100644
--- a/src/backend/utils/cache/Makefile
+++ b/src/backend/utils/cache/Makefile
@@ -13,7 +13,7 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = attoptcache.o catcache.o evtcache.o inval.o plancache.o relcache.o \
-	relmapper.o relfilenodemap.o spccache.o syscache.o lsyscache.o \
-	typcache.o ts_cache.o
+	relmapper.o relfilenodemap.o partcache.o spccache.o syscache.o \
+    lsyscache.o typcache.o ts_cache.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c
new file mode 100644
index 0000000000..c0a8be76bc
--- /dev/null
+++ b/src/backend/utils/cache/partcache.c
@@ -0,0 +1,1149 @@
+/*-------------------------------------------------------------------------
+ *
+ * partcache.c
+ *		Support routines for manipulating partition information cached in
+ *		relcache
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		  src/backend/utils/cache/partcache.c
+ *
+ *-------------------------------------------------------------------------
+*/
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
+#include "partitioning/partbounds.h"
+#include "partitioning/partprune.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/partcache.h"
+#include "utils/syscache.h"
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+	int			nparts;			/* Number of partitions */
+	Oid		   *oids;			/* OIDs of partitions */
+	PartitionBoundInfo boundinfo;	/* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+typedef struct PartitionBoundSortInfo
+{
+	FmgrInfo	   *partsupfunc;
+	PartitionKey	key;
+} PartitionBoundSortInfo;
+
+static int32 qsort_partition_hbound_cmp(const void *a, const void *b);
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
+							   void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
+						   void *arg);
+static PartitionKey partition_key_copy(PartitionKey fromkey);
+static List *generate_partition_qual(Relation rel);
+
+/*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partition key data is of complex structure; to avoid complicated logic to
+ * free individual elements whenever the relcache entry is flushed, we give it
+ * its own memory context, child of CacheMemoryContext, which can easily be
+ * deleted on its own.  To avoid leaking memory in that context in case of an
+ * error partway through this function, the context is initially created as a
+ * child of CurTransactionContext and only re-parented to CacheMemoryContext
+ * at the end, when no further errors are possible.  Also, we don't make this
+ * context the current context except in very brief code sections, out of fear
+ * that some of our callees allocate memory on their own which would be leaked
+ * permanently.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table form;
+	HeapTuple	tuple;
+	bool		isnull;
+	int			i;
+	PartitionKey key;
+	AttrNumber *attrs;
+	oidvector  *opclass;
+	oidvector  *collation;
+	ListCell   *partexprs_item;
+	Datum		datum;
+	MemoryContext oldcxt;
+	int16		procnum;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) MemoryContextAllocZero(relation->rd_partcxt,
+												sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+
+	/*
+	 * We can rely on the first variable-length attribute being mapped to the
+	 * relevant field of the catalog's C struct, because all previous
+	 * attributes are non-nullable and fixed-length.
+	 */
+	attrs = form->partattrs.values;
+
+	/* But use the hard way to retrieve further variable-length attributes */
+	/* Operator class */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partexprs, &isnull);
+	if (!isnull)
+	{
+		char	   *exprString;
+		Node	   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step; fix
+		 * opfuncids while at it.  We don't need to bother with
+		 * canonicalize_qual() though, because partition expressions should be
+		 * in canonical form already (ie, no need for OR-merging or constant
+		 * elimination).
+		 */
+		expr = eval_const_expressions(NULL, expr);
+		fix_opfuncids(expr);
+
+		oldcxt = MemoryContextSwitchTo(relation->rd_partcxt);
+		key->partexprs = (List *) copyObject(expr);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	oldcxt = MemoryContextSwitchTo(relation->rd_partcxt);
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfuncid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/*
+	 * Also allocate space for partition support procedure FmgrInfo's, but
+	 * they won't be filled until somebody calls partition_get_procinfo.
+	 */
+	relation->rd_partsupfunc = (FmgrInfo *)
+						palloc0(key->partnatts * sizeof(FmgrInfo));
+	MemoryContextSwitchTo(oldcxt);
+
+	/* determine support function number to search for */
+	procnum = (key->strategy == PARTITION_STRATEGY_HASH) ?
+		HASHEXTENDED_PROC : BTORDER_PROC;
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber	attno = key->partattrs[i];
+		HeapTuple	opclasstup;
+		Form_pg_opclass opclassform;
+		Oid			funcid;
+
+		/* Collect opfamily information */
+		opclasstup = SearchSysCache1(CLAOID,
+									 ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(opclasstup))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
+		key->partopfamily[i] = opclassform->opcfamily;
+		key->partopcintype[i] = opclassform->opcintype;
+
+		/* Get a support function for the specified opfamily and datatypes */
+		funcid = get_opfamily_proc(opclassform->opcfamily,
+								   opclassform->opcintype,
+								   opclassform->opcintype,
+								   procnum);
+		if (!OidIsValid(funcid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("operator class \"%s\" of access method %s is missing support function %d for type %s",
+							NameStr(opclassform->opcname),
+							(key->strategy == PARTITION_STRATEGY_HASH) ?
+							"hash" : "btree",
+							procnum,
+							format_type_be(opclassform->opcintype))));
+
+		key->partsupfuncid[i] = funcid;
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			Form_pg_attribute att = TupleDescAttr(relation->rd_att, attno - 1);
+
+			key->parttypid[i] = att->atttypid;
+			key->parttypmod[i] = att->atttypmod;
+			key->parttypcoll[i] = att->attcollation;
+		}
+		else
+		{
+			if (partexprs_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+
+			partexprs_item = lnext(partexprs_item);
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(opclasstup);
+	}
+
+	ReleaseSysCache(tuple);
+	relation->rd_partkey = key;
+}
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *inhoids,
+			   *partoids;
+	Oid		   *oids = NULL;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey key = RelationGetPartitionKey(rel);
+	PartitionDesc result;
+	MemoryContext oldcxt;
+
+	int			ndatums = 0;
+	int			default_index = -1;
+
+	/* Hash partitioning specific */
+	PartitionHashBound **hbounds = NULL;
+
+	/* List partitioning specific */
+	PartitionListValue **all_values = NULL;
+	int			null_index = -1;
+
+	/* Range partitioning specific */
+	PartitionRangeBound **rbounds = NULL;
+
+	/* Get partition oids from pg_inherits */
+	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+	/* Collect bound spec nodes in a list */
+	i = 0;
+	partoids = NIL;
+	foreach(cell, inhoids)
+	{
+		Oid			inhrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+
+		tuple = SearchSysCache1(RELOID, inhrelid);
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+		/*
+		 * It is possible that the pg_class tuple of a partition has not been
+		 * updated yet to set its relpartbound field.  The only case where
+		 * this happens is when we open the parent relation to check using its
+		 * partition descriptor that a new partition's bound does not overlap
+		 * some existing partition.
+		 */
+		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+
+		datum = SysCacheGetAttr(RELOID, tuple,
+								Anum_pg_class_relpartbound,
+								&isnull);
+		Assert(!isnull);
+		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+
+		/*
+		 * Sanity check: If the PartitionBoundSpec says this is the default
+		 * partition, its OID should correspond to whatever's stored in
+		 * pg_partitioned_table.partdefid; if not, the catalog is corrupt.
+		 */
+		if (castNode(PartitionBoundSpec, boundspec)->is_default)
+		{
+			Oid			partdefid;
+
+			partdefid = get_default_partition_oid(RelationGetRelid(rel));
+			if (partdefid != inhrelid)
+				elog(ERROR, "expected partdefid %u, but got %u",
+					 inhrelid, partdefid);
+		}
+
+		boundspecs = lappend(boundspecs, boundspec);
+		partoids = lappend_oid(partoids, inhrelid);
+		ReleaseSysCache(tuple);
+	}
+
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
+		PartitionBoundSortInfo sortinfo;
+
+		/* Get partsupfunc FmgrInfo's. */
+		for (i = 0; i < key->partnatts; i++)
+		{
+			fmgr_info_copy(&partsupfunc[i],
+						   partition_getprocinfo(rel, key, i),
+						   CurrentMemoryContext);
+		}
+
+		sortinfo.partsupfunc = partsupfunc;
+		sortinfo.key = key;
+
+		oids = (Oid *) palloc(nparts * sizeof(Oid));
+		i = 0;
+		foreach(cell, partoids)
+			oids[i++] = lfirst_oid(cell);
+
+		/* Convert from node to the internal representation */
+		if (key->strategy == PARTITION_STRATEGY_HASH)
+		{
+			ndatums = nparts;
+			hbounds = (PartitionHashBound **)
+				palloc(nparts * sizeof(PartitionHashBound *));
+
+			i = 0;
+			foreach(cell, boundspecs)
+			{
+				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
+													lfirst(cell));
+
+				if (spec->strategy != PARTITION_STRATEGY_HASH)
+					elog(ERROR, "invalid strategy in partition bound spec");
+
+				hbounds[i] = (PartitionHashBound *)
+					palloc(sizeof(PartitionHashBound));
+
+				hbounds[i]->modulus = spec->modulus;
+				hbounds[i]->remainder = spec->remainder;
+				hbounds[i]->index = i;
+				i++;
+			}
+
+			/* Sort all the bounds in ascending order */
+			qsort(hbounds, nparts, sizeof(PartitionHashBound *),
+				  qsort_partition_hbound_cmp);
+		}
+		else if (key->strategy == PARTITION_STRATEGY_LIST)
+		{
+			List	   *non_null_values = NIL;
+
+			/*
+			 * Create a unified list of non-null values across all partitions.
+			 */
+			i = 0;
+			null_index = -1;
+			foreach(cell, boundspecs)
+			{
+				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
+													lfirst(cell));
+				ListCell   *c;
+
+				if (spec->strategy != PARTITION_STRATEGY_LIST)
+					elog(ERROR, "invalid strategy in partition bound spec");
+
+				/*
+				 * Note the index of the partition bound spec for the default
+				 * partition. There's no datum to add to the list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
+				foreach(c, spec->listdatums)
+				{
+					Const	   *val = castNode(Const, lfirst(c));
+					PartitionListValue *list_value = NULL;
+
+					if (!val->constisnull)
+					{
+						list_value = (PartitionListValue *)
+							palloc0(sizeof(PartitionListValue));
+						list_value->index = i;
+						list_value->value = val->constvalue;
+					}
+					else
+					{
+						/*
+						 * Never put a null into the values array, flag
+						 * instead for the code further down below where we
+						 * construct the actual relcache struct.
+						 */
+						if (null_index != -1)
+							elog(ERROR, "found null more than once");
+						null_index = i;
+					}
+
+					if (list_value)
+						non_null_values = lappend(non_null_values,
+												  list_value);
+				}
+
+				i++;
+			}
+
+			ndatums = list_length(non_null_values);
+
+			/*
+			 * Collect all list values in one array. Alongside the value, we
+			 * also save the index of partition the value comes from.
+			 */
+			all_values = (PartitionListValue **) palloc(ndatums *
+														sizeof(PartitionListValue *));
+			i = 0;
+			foreach(cell, non_null_values)
+			{
+				PartitionListValue *src = lfirst(cell);
+
+				all_values[i] = (PartitionListValue *)
+					palloc(sizeof(PartitionListValue));
+				all_values[i]->value = src->value;
+				all_values[i]->index = src->index;
+				i++;
+			}
+
+			qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
+					  qsort_partition_list_value_cmp, (void *) &sortinfo);
+		}
+		else if (key->strategy == PARTITION_STRATEGY_RANGE)
+		{
+			int			k;
+			PartitionRangeBound **all_bounds,
+					   *prev;
+
+			all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+														  sizeof(PartitionRangeBound *));
+
+			/*
+			 * Create a unified list of range bounds across all the
+			 * partitions.
+			 */
+			i = ndatums = 0;
+			foreach(cell, boundspecs)
+			{
+				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
+													lfirst(cell));
+				PartitionRangeBound *lower,
+						   *upper;
+
+				if (spec->strategy != PARTITION_STRATEGY_RANGE)
+					elog(ERROR, "invalid strategy in partition bound spec");
+
+				/*
+				 * Note the index of the partition bound spec for the default
+				 * partition. There's no datum to add to the allbounds array
+				 * for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i++;
+					continue;
+				}
+
+				lower = make_one_range_bound(key, i, spec->lowerdatums,
+											 true);
+				upper = make_one_range_bound(key, i, spec->upperdatums,
+											 false);
+				all_bounds[ndatums++] = lower;
+				all_bounds[ndatums++] = upper;
+				i++;
+			}
+
+			Assert(ndatums == nparts * 2 ||
+				   (default_index != -1 && ndatums == (nparts - 1) * 2));
+
+			/* Sort all the bounds in ascending order */
+			qsort_arg(all_bounds, ndatums,
+					  sizeof(PartitionRangeBound *),
+					  qsort_partition_rbound_cmp,
+					  (void *) &sortinfo);
+
+			/* Save distinct bounds from all_bounds into rbounds. */
+			rbounds = (PartitionRangeBound **)
+				palloc(ndatums * sizeof(PartitionRangeBound *));
+			k = 0;
+			prev = NULL;
+			for (i = 0; i < ndatums; i++)
+			{
+				PartitionRangeBound *cur = all_bounds[i];
+				bool		is_distinct = false;
+				int			j;
+
+				/* Is the current bound distinct from the previous one? */
+				for (j = 0; j < key->partnatts; j++)
+				{
+					Datum		cmpval;
+
+					if (prev == NULL || cur->kind[j] != prev->kind[j])
+					{
+						is_distinct = true;
+						break;
+					}
+
+					/*
+					 * If the bounds are both MINVALUE or MAXVALUE, stop now
+					 * and treat them as equal, since any values after this
+					 * point must be ignored.
+					 */
+					if (cur->kind[j] != PARTITION_RANGE_DATUM_VALUE)
+						break;
+
+					cmpval = FunctionCall2Coll(&partsupfunc[j],
+											   key->partcollation[j],
+											   cur->datums[j],
+											   prev->datums[j]);
+					if (DatumGetInt32(cmpval) != 0)
+					{
+						is_distinct = true;
+						break;
+					}
+				}
+
+				/*
+				 * Only if the bound is distinct save it into a temporary
+				 * array i.e. rbounds which is later copied into boundinfo
+				 * datums array.
+				 */
+				if (is_distinct)
+					rbounds[k++] = all_bounds[i];
+
+				prev = cur;
+			}
+
+			/* Update ndatums to hold the count of distinct datums. */
+			ndatums = k;
+		}
+		else
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) key->strategy);
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	oldcxt = MemoryContextSwitchTo(rel->rd_partcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		PartitionBoundInfo boundinfo;
+		int		   *mapping;
+		int			next_index = 0;
+
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		boundinfo = (PartitionBoundInfoData *)
+			palloc0(sizeof(PartitionBoundInfoData));
+		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
+		boundinfo->ndatums = ndatums;
+		boundinfo->null_index = -1;
+		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
+
+		/* Initialize mapping array with invalid values */
+		mapping = (int *) palloc(sizeof(int) * nparts);
+		for (i = 0; i < nparts; i++)
+			mapping[i] = -1;
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_HASH:
+				{
+					/* Modulus are stored in ascending order */
+					int			greatest_modulus = hbounds[ndatums - 1]->modulus;
+
+					boundinfo->indexes = (int *) palloc(greatest_modulus *
+														sizeof(int));
+
+					for (i = 0; i < greatest_modulus; i++)
+						boundinfo->indexes[i] = -1;
+
+					for (i = 0; i < nparts; i++)
+					{
+						int			modulus = hbounds[i]->modulus;
+						int			remainder = hbounds[i]->remainder;
+
+						boundinfo->datums[i] = (Datum *) palloc(2 *
+																sizeof(Datum));
+						boundinfo->datums[i][0] = Int32GetDatum(modulus);
+						boundinfo->datums[i][1] = Int32GetDatum(remainder);
+
+						while (remainder < greatest_modulus)
+						{
+							/* overlap? */
+							Assert(boundinfo->indexes[remainder] == -1);
+							boundinfo->indexes[remainder] = i;
+							remainder += modulus;
+						}
+
+						mapping[hbounds[i]->index] = i;
+						pfree(hbounds[i]);
+					}
+					pfree(hbounds);
+					break;
+				}
+
+			case PARTITION_STRATEGY_LIST:
+				{
+					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
+
+					/*
+					 * Copy values.  Indexes of individual values are mapped
+					 * to canonical values so that they match for any two list
+					 * partitioned tables with same number of partitions and
+					 * same lists per partition.  One way to canonicalize is
+					 * to assign the index in all_values[] of the smallest
+					 * value of each partition, as the index of all of the
+					 * partition's values.
+					 */
+					for (i = 0; i < ndatums; i++)
+					{
+						boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
+						boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
+															key->parttypbyval[0],
+															key->parttyplen[0]);
+
+						/* If the old index has no mapping, assign one */
+						if (mapping[all_values[i]->index] == -1)
+							mapping[all_values[i]->index] = next_index++;
+
+						boundinfo->indexes[i] = mapping[all_values[i]->index];
+					}
+
+					/*
+					 * If null-accepting partition has no mapped index yet,
+					 * assign one.  This could happen if such partition
+					 * accepts only null and hence not covered in the above
+					 * loop which only handled non-null values.
+					 */
+					if (null_index != -1)
+					{
+						Assert(null_index >= 0);
+						if (mapping[null_index] == -1)
+							mapping[null_index] = next_index++;
+						boundinfo->null_index = mapping[null_index];
+					}
+
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any value not
+						 * specified in the lists of other partitions, hence
+						 * it should not get mapped index while assigning
+						 * those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
+					/* All partition must now have a valid mapping */
+					Assert(next_index == nparts);
+					break;
+				}
+
+			case PARTITION_STRATEGY_RANGE:
+				{
+					boundinfo->kind = (PartitionRangeDatumKind **)
+						palloc(ndatums *
+							   sizeof(PartitionRangeDatumKind *));
+					boundinfo->indexes = (int *) palloc((ndatums + 1) *
+														sizeof(int));
+
+					for (i = 0; i < ndatums; i++)
+					{
+						int			j;
+
+						boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
+																sizeof(Datum));
+						boundinfo->kind[i] = (PartitionRangeDatumKind *)
+							palloc(key->partnatts *
+								   sizeof(PartitionRangeDatumKind));
+						for (j = 0; j < key->partnatts; j++)
+						{
+							if (rbounds[i]->kind[j] == PARTITION_RANGE_DATUM_VALUE)
+								boundinfo->datums[i][j] =
+									datumCopy(rbounds[i]->datums[j],
+											  key->parttypbyval[j],
+											  key->parttyplen[j]);
+							boundinfo->kind[i][j] = rbounds[i]->kind[j];
+						}
+
+						/*
+						 * There is no mapping for invalid indexes.
+						 *
+						 * Any lower bounds in the rbounds array have invalid
+						 * indexes assigned, because the values between the
+						 * previous bound (if there is one) and this (lower)
+						 * bound are not part of the range of any existing
+						 * partition.
+						 */
+						if (rbounds[i]->lower)
+							boundinfo->indexes[i] = -1;
+						else
+						{
+							int			orig_index = rbounds[i]->index;
+
+							/* If the old index has no mapping, assign one */
+							if (mapping[orig_index] == -1)
+								mapping[orig_index] = next_index++;
+
+							boundinfo->indexes[i] = mapping[orig_index];
+						}
+					}
+
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						Assert(default_index >= 0 && mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+					boundinfo->indexes[i] = -1;
+					break;
+				}
+
+			default:
+				elog(ERROR, "unexpected partition strategy: %d",
+					 (int) key->strategy);
+		}
+
+		result->boundinfo = boundinfo;
+
+		/*
+		 * Now assign OIDs from the original array into mapped indexes of the
+		 * result array.  Order of OIDs in the former is defined by the
+		 * catalog scan that retrieved them, whereas that in the latter is
+		 * defined by canonicalized representation of the partition bounds.
+		 */
+		for (i = 0; i < nparts; i++)
+			result->oids[mapping[i]] = oids[i];
+		pfree(mapping);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Functions to get a copy of partitioning infomation cached in RelationData.
+ */
+
+PartitionKey
+RelationGetPartitionKey(Relation relation)
+{
+	PartitionKey partkey = relation->rd_partkey;
+
+	return partkey ? partition_key_copy(partkey) : NULL;
+}
+
+int
+RelationGetPartitionCount(Relation relation)
+{
+	PartitionDesc partdesc = relation->rd_partdesc;
+
+	return partdesc->nparts;
+}
+
+Oid *
+RelationGetPartitionOids(Relation relation)
+{
+	PartitionDesc partdesc = relation->rd_partdesc;
+	Oid *result = NULL;
+
+	Assert(partdesc != NULL);
+	if (partdesc->nparts > 0)
+	{
+		result = palloc(partdesc->nparts * sizeof(Oid));
+		memcpy(result, partdesc->oids, partdesc->nparts * sizeof(Oid));
+	}
+
+	return result;
+}
+
+PartitionBoundInfo
+RelationGetPartitionBounds(Relation relation)
+{
+	PartitionDesc partdesc = relation->rd_partdesc;
+
+	return partdesc->boundinfo
+				? partition_bounds_copy(partdesc->boundinfo,
+										relation->rd_partkey)
+				: NULL;
+}
+
+/*
+ * RelationGetDefaultPartitionOid
+ *
+ * Return the OID of the default partition, if one exists; else InvalidOid.
+ */
+Oid
+RelationGetDefaultPartitionOid(Relation rel)
+{
+	PartitionBoundInfo boundinfo;
+
+	/* Shouldn't be here otherwise! */
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+	boundinfo = rel->rd_partdesc->boundinfo;
+
+	if (boundinfo && partition_bound_has_default(boundinfo))
+		return rel->rd_partdesc->oids[boundinfo->default_index];
+
+	return InvalidOid;
+}
+
+/*
+ * partition_getprocinfo
+ *		Return fmgr lookup info of partition support procs from relcache
+ *
+ * If it's not been built yet by calling fmgr.c, do that and add it to
+ * relcache.
+ */
+FmgrInfo *
+partition_getprocinfo(Relation rel, PartitionKey key, int partattoff)
+{
+	FmgrInfo   *info;
+
+	info = rel->rd_partsupfunc;
+
+	Assert(info != NULL);
+
+	info += partattoff;
+
+	/* Initialize the lookup info if first time through */
+	if (info->fn_oid == InvalidOid)
+	{
+		RegProcedure *func = key->partsupfuncid;
+		RegProcedure procId;
+
+		Assert(func != NULL);
+
+		procId = func[partattoff];
+		Assert(RegProcedureIsValid(procId));
+		fmgr_info_cxt(procId, info, rel->rd_partcxt);
+	}
+
+	return info;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel);
+}
+
+/*
+ * get_partition_qual_relid
+ *
+ * Returns an expression tree describing the passed-in relation's partition
+ * constraint. If there is no partition constraint returns NULL; this can
+ * happen if the default partition is the only partition.
+ */
+Expr *
+get_partition_qual_relid(Oid relid)
+{
+	Relation	rel = heap_open(relid, AccessShareLock);
+	Expr	   *result = NULL;
+	List	   *and_args;
+
+	/* Do the work only if this relation is a partition. */
+	if (rel->rd_rel->relispartition)
+	{
+		and_args = generate_partition_qual(rel);
+
+		if (and_args == NIL)
+			result = NULL;
+		else if (list_length(and_args) > 1)
+			result = makeBoolExpr(AND_EXPR, and_args, -1);
+		else
+			result = linitial(and_args);
+	}
+
+	/* Keep the lock. */
+	heap_close(rel, NoLock);
+
+	return result;
+}
+
+/*
+ * qsort_partition_hbound_cmp
+ *
+ * We sort hash bounds by modulus, then by remainder.
+ */
+static int32
+qsort_partition_hbound_cmp(const void *a, const void *b)
+{
+	PartitionHashBound *h1 = (*(PartitionHashBound *const *) a);
+	PartitionHashBound *h2 = (*(PartitionHashBound *const *) b);
+
+	return partition_hbound_cmp(h1->modulus, h1->remainder,
+								h2->modulus, h2->remainder);
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list partition bound datums
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+	Datum		val1 = (*(const PartitionListValue **) a)->value,
+				val2 = (*(const PartitionListValue **) b)->value;
+	PartitionBoundSortInfo *sortinfo = (PartitionBoundSortInfo *) arg;
+	PartitionKey key = sortinfo->key;
+
+	return DatumGetInt32(FunctionCall2Coll(&sortinfo->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+	PartitionBoundSortInfo *sortinfo = (PartitionBoundSortInfo *) arg;
+	PartitionKey key = sortinfo->key;
+
+	return partition_rbound_cmp(key->partnatts, sortinfo->partsupfunc,
+								key->partcollation, b1->datums, b1->kind,
+								b1->lower, b2);
+}
+
+/*
+ * partition_key_copy
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+partition_key_copy(PartitionKey fromkey)
+{
+	PartitionKey newkey;
+	int			n;
+
+	Assert(fromkey != NULL);
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfuncid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partsupfuncid, fromkey->partsupfuncid, n * sizeof(Oid));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression. The
+ * function returns a NIL list if there is no predicate.
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel)
+{
+	HeapTuple	tuple;
+	MemoryContext oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	PartitionBoundSpec *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+	bool		found_whole_row;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Quick copy */
+	if (rel->rd_partcheck != NIL)
+		return copyObject(rel->rd_partcheck);
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Get pg_class.relpartbound */
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(rel));
+
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	if (isnull)					/* should not happen */
+		elog(ERROR, "relation \"%s\" has relpartbound = null",
+			 RelationGetRelationName(rel));
+	bound = castNode(PartitionBoundSpec,
+					 stringToNode(TextDatumGetCString(boundDatum)));
+	ReleaseSysCache(tuple);
+
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* Add the parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition)
+		result = list_concat(generate_partition_qual(parent), my_qual);
+	else
+		result = my_qual;
+
+	/*
+	 * Change Vars to have partition's attnos instead of the parent's. We do
+	 * this after we concatenate the parent's quals, because we want every Var
+	 * in it to bear this relation's attnos. It's safe to assume varno = 1
+	 * here.
+	 */
+	result = map_partition_varattnos(result, 1, rel, parent,
+									 &found_whole_row);
+	/* There can never be a whole-row reference here */
+	if (found_whole_row)
+		elog(ERROR, "unexpected whole-row reference found in partition key");
+
+	/* Save a copy in the relcache */
+	oldcxt = MemoryContextSwitchTo(rel->rd_partcxt);
+	rel->rd_partcheck = copyObject(result);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Keep the parent locked until commit */
+	heap_close(parent, NoLock);
+
+	return result;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index dfa95fed30..e7b7511f07 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -43,7 +43,6 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
-#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -51,6 +50,7 @@
 #include "catalog/pg_auth_members.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
@@ -68,6 +68,7 @@
 #include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
@@ -85,6 +86,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/partcache.h"
 #include "utils/relmapper.h"
 #include "utils/resowner_private.h"
 #include "utils/snapmgr.h"
@@ -265,8 +267,6 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
-static void RelationBuildPartitionKey(Relation relation);
-static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
 static TupleDesc GetPgClassDescriptor(void);
@@ -286,9 +286,6 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
-static bool equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
-					PartitionDesc partdesc2);
-
 
 /*
  *		ScanPgRelation
@@ -874,211 +871,6 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
- * RelationBuildPartitionKey
- *		Build and attach to relcache partition key data of relation
- *
- * Partitioning key data is a complex structure; to avoid complicated logic to
- * free individual elements whenever the relcache entry is flushed, we give it
- * its own memory context, child of CacheMemoryContext, which can easily be
- * deleted on its own.  To avoid leaking memory in that context in case of an
- * error partway through this function, the context is initially created as a
- * child of CurTransactionContext and only re-parented to CacheMemoryContext
- * at the end, when no further errors are possible.  Also, we don't make this
- * context the current context except in very brief code sections, out of fear
- * that some of our callees allocate memory on their own which would be leaked
- * permanently.
- */
-static void
-RelationBuildPartitionKey(Relation relation)
-{
-	Form_pg_partitioned_table form;
-	HeapTuple	tuple;
-	bool		isnull;
-	int			i;
-	PartitionKey key;
-	AttrNumber *attrs;
-	oidvector  *opclass;
-	oidvector  *collation;
-	ListCell   *partexprs_item;
-	Datum		datum;
-	MemoryContext partkeycxt,
-				oldcxt;
-	int16		procnum;
-
-	tuple = SearchSysCache1(PARTRELID,
-							ObjectIdGetDatum(RelationGetRelid(relation)));
-
-	/*
-	 * The following happens when we have created our pg_class entry but not
-	 * the pg_partitioned_table entry yet.
-	 */
-	if (!HeapTupleIsValid(tuple))
-		return;
-
-	partkeycxt = AllocSetContextCreate(CurTransactionContext,
-									   "partition key",
-									   ALLOCSET_SMALL_SIZES);
-	MemoryContextCopyAndSetIdentifier(partkeycxt,
-								   RelationGetRelationName(relation));
-
-	key = (PartitionKey) MemoryContextAllocZero(partkeycxt,
-												sizeof(PartitionKeyData));
-
-	/* Fixed-length attributes */
-	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
-	key->strategy = form->partstrat;
-	key->partnatts = form->partnatts;
-
-	/*
-	 * We can rely on the first variable-length attribute being mapped to the
-	 * relevant field of the catalog's C struct, because all previous
-	 * attributes are non-nullable and fixed-length.
-	 */
-	attrs = form->partattrs.values;
-
-	/* But use the hard way to retrieve further variable-length attributes */
-	/* Operator class */
-	datum = SysCacheGetAttr(PARTRELID, tuple,
-							Anum_pg_partitioned_table_partclass, &isnull);
-	Assert(!isnull);
-	opclass = (oidvector *) DatumGetPointer(datum);
-
-	/* Collation */
-	datum = SysCacheGetAttr(PARTRELID, tuple,
-							Anum_pg_partitioned_table_partcollation, &isnull);
-	Assert(!isnull);
-	collation = (oidvector *) DatumGetPointer(datum);
-
-	/* Expressions */
-	datum = SysCacheGetAttr(PARTRELID, tuple,
-							Anum_pg_partitioned_table_partexprs, &isnull);
-	if (!isnull)
-	{
-		char	   *exprString;
-		Node	   *expr;
-
-		exprString = TextDatumGetCString(datum);
-		expr = stringToNode(exprString);
-		pfree(exprString);
-
-		/*
-		 * Run the expressions through const-simplification since the planner
-		 * will be comparing them to similarly-processed qual clause operands,
-		 * and may fail to detect valid matches without this step; fix
-		 * opfuncids while at it.  We don't need to bother with
-		 * canonicalize_qual() though, because partition expressions should be
-		 * in canonical form already (ie, no need for OR-merging or constant
-		 * elimination).
-		 */
-		expr = eval_const_expressions(NULL, expr);
-		fix_opfuncids(expr);
-
-		oldcxt = MemoryContextSwitchTo(partkeycxt);
-		key->partexprs = (List *) copyObject(expr);
-		MemoryContextSwitchTo(oldcxt);
-	}
-
-	oldcxt = MemoryContextSwitchTo(partkeycxt);
-	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
-	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
-
-	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-
-	/* Gather type and collation info as well */
-	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
-	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
-	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
-	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
-	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-	MemoryContextSwitchTo(oldcxt);
-
-	/* determine support function number to search for */
-	procnum = (key->strategy == PARTITION_STRATEGY_HASH) ?
-		HASHEXTENDED_PROC : BTORDER_PROC;
-
-	/* Copy partattrs and fill other per-attribute info */
-	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
-	partexprs_item = list_head(key->partexprs);
-	for (i = 0; i < key->partnatts; i++)
-	{
-		AttrNumber	attno = key->partattrs[i];
-		HeapTuple	opclasstup;
-		Form_pg_opclass opclassform;
-		Oid			funcid;
-
-		/* Collect opfamily information */
-		opclasstup = SearchSysCache1(CLAOID,
-									 ObjectIdGetDatum(opclass->values[i]));
-		if (!HeapTupleIsValid(opclasstup))
-			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
-
-		opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
-		key->partopfamily[i] = opclassform->opcfamily;
-		key->partopcintype[i] = opclassform->opcintype;
-
-		/* Get a support function for the specified opfamily and datatypes */
-		funcid = get_opfamily_proc(opclassform->opcfamily,
-								   opclassform->opcintype,
-								   opclassform->opcintype,
-								   procnum);
-		if (!OidIsValid(funcid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("operator class \"%s\" of access method %s is missing support function %d for type %s",
-							NameStr(opclassform->opcname),
-							(key->strategy == PARTITION_STRATEGY_HASH) ?
-							"hash" : "btree",
-							procnum,
-							format_type_be(opclassform->opcintype))));
-
-		fmgr_info_cxt(funcid, &key->partsupfunc[i], partkeycxt);
-
-		/* Collation */
-		key->partcollation[i] = collation->values[i];
-
-		/* Collect type information */
-		if (attno != 0)
-		{
-			Form_pg_attribute att = TupleDescAttr(relation->rd_att, attno - 1);
-
-			key->parttypid[i] = att->atttypid;
-			key->parttypmod[i] = att->atttypmod;
-			key->parttypcoll[i] = att->attcollation;
-		}
-		else
-		{
-			if (partexprs_item == NULL)
-				elog(ERROR, "wrong number of partition key expressions");
-
-			key->parttypid[i] = exprType(lfirst(partexprs_item));
-			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
-			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
-
-			partexprs_item = lnext(partexprs_item);
-		}
-		get_typlenbyvalalign(key->parttypid[i],
-							 &key->parttyplen[i],
-							 &key->parttypbyval[i],
-							 &key->parttypalign[i]);
-
-		ReleaseSysCache(opclasstup);
-	}
-
-	ReleaseSysCache(tuple);
-
-	/*
-	 * Success --- reparent our context and make the relcache point to the
-	 * newly constructed key
-	 */
-	MemoryContextSetParent(partkeycxt, CacheMemoryContext);
-	relation->rd_partkeycxt = partkeycxt;
-	relation->rd_partkey = key;
-}
-
-/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1206,60 +998,6 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
- * equalPartitionDescs
- *		Compare two partition descriptors for logical equality
- */
-static bool
-equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
-					PartitionDesc partdesc2)
-{
-	int			i;
-
-	if (partdesc1 != NULL)
-	{
-		if (partdesc2 == NULL)
-			return false;
-		if (partdesc1->nparts != partdesc2->nparts)
-			return false;
-
-		Assert(key != NULL || partdesc1->nparts == 0);
-
-		/*
-		 * Same oids? If the partitioning structure did not change, that is,
-		 * no partitions were added or removed to the relation, the oids array
-		 * should still match element-by-element.
-		 */
-		for (i = 0; i < partdesc1->nparts; i++)
-		{
-			if (partdesc1->oids[i] != partdesc2->oids[i])
-				return false;
-		}
-
-		/*
-		 * Now compare partition bound collections.  The logic to iterate over
-		 * the collections is private to partition.c.
-		 */
-		if (partdesc1->boundinfo != NULL)
-		{
-			if (partdesc2->boundinfo == NULL)
-				return false;
-
-			if (!partition_bounds_equal(key->partnatts, key->parttyplen,
-										key->parttypbyval,
-										partdesc1->boundinfo,
-										partdesc2->boundinfo))
-				return false;
-		}
-		else if (partdesc2->boundinfo != NULL)
-			return false;
-	}
-	else if (partdesc2 != NULL)
-		return false;
-
-	return true;
-}
-
-/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1387,18 +1125,40 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if a partitioned table, initialize key and partition descriptor info */
-	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	/*
+	 * If we need to initialize partitioning info, do that in a dedicated
+	 * context that's attached to the cache context.
+	 */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+		relation->rd_rel->relispartition)
 	{
-		RelationBuildPartitionKey(relation);
-		RelationBuildPartitionDesc(relation);
+		relation->rd_partcxt = AllocSetContextCreate(CacheMemoryContext,
+													 "partition info",
+													 ALLOCSET_SMALL_SIZES);
+		MemoryContextCopyAndSetIdentifier(relation->rd_partcxt,
+										  RelationGetRelationName(relation));
+
+		/*
+		 * For a partitioned table, initialize partition key and partition
+		 * descriptor.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			RelationBuildPartitionDesc(relation);
+		}
+
+		/*
+		 * Partition constraint of a partition itself is built only when
+		 * needed.  See RelationGetPartitionQual().
+		 */
 	}
 	else
 	{
-		relation->rd_partkeycxt = NULL;
+		relation->rd_partcxt = NULL;
 		relation->rd_partkey = NULL;
 		relation->rd_partdesc = NULL;
-		relation->rd_pdcxt = NULL;
+		relation->rd_partcheck = NIL;
 	}
 
 	/*
@@ -2401,12 +2161,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
-	if (relation->rd_partkeycxt)
-		MemoryContextDelete(relation->rd_partkeycxt);
-	if (relation->rd_pdcxt)
-		MemoryContextDelete(relation->rd_pdcxt);
-	if (relation->rd_partcheck)
-		pfree(relation->rd_partcheck);
+	if (relation->rd_partcxt)
+		MemoryContextDelete(relation->rd_partcxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2573,8 +2329,6 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 		bool		keep_policies;
-		bool		keep_partkey;
-		bool		keep_partdesc;
 
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
@@ -2605,10 +2359,6 @@ RelationClearRelation(Relation relation, bool rebuild)
 		keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
 		keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
 		keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
-		keep_partkey = (relation->rd_partkey != NULL);
-		keep_partdesc = equalPartitionDescs(relation->rd_partkey,
-											relation->rd_partdesc,
-											newrel->rd_partdesc);
 
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
@@ -2663,18 +2413,6 @@ RelationClearRelation(Relation relation, bool rebuild)
 		SWAPFIELD(Oid, rd_toastoid);
 		/* pgstat_info must be preserved */
 		SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
-		/* partition key must be preserved, if we have one */
-		if (keep_partkey)
-		{
-			SWAPFIELD(PartitionKey, rd_partkey);
-			SWAPFIELD(MemoryContext, rd_partkeycxt);
-		}
-		/* preserve old partdesc if no logical change */
-		if (keep_partdesc)
-		{
-			SWAPFIELD(PartitionDesc, rd_partdesc);
-			SWAPFIELD(MemoryContext, rd_pdcxt);
-		}
 
 #undef SWAPFIELD
 
@@ -3912,19 +3650,11 @@ RelationCacheInitializePhase3(void)
 		/*
 		 * Reload the partition key and descriptor for a partitioned table.
 		 */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
-			relation->rd_partkey == NULL)
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			RelationBuildPartitionKey(relation);
-			Assert(relation->rd_partkey != NULL);
-
-			restart = true;
-		}
-
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
-			relation->rd_partdesc == NULL)
-		{
 			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partkey != NULL);
 			Assert(relation->rd_partdesc != NULL);
 
 			restart = true;
@@ -5777,9 +5507,9 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
-		rel->rd_partkeycxt = NULL;
+		rel->rd_partcxt = NULL;
 		rel->rd_partkey = NULL;
-		rel->rd_pdcxt = NULL;
+		rel->rd_partsupfunc = NULL;
 		rel->rd_partdesc = NULL;
 		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index b25e25bf9d..5b3f90d2f5 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -22,55 +22,16 @@
 /* Seed for the extended hash function */
 #define HASH_PARTITION_SEED UINT64CONST(0x7A5B22367996DCFD)
 
-/*
- * PartitionBoundInfo encapsulates a set of partition bounds.  It is usually
- * associated with partitioned tables as part of its partition descriptor.
- *
- * The internal structure appears in partbounds.h.
- */
-typedef struct PartitionBoundInfoData *PartitionBoundInfo;
-
-/*
- * Information about partitions of a partitioned table.
- */
-typedef struct PartitionDescData
-{
-	int			nparts;			/* Number of partitions */
-	Oid		   *oids;			/* OIDs of partitions */
-	PartitionBoundInfo boundinfo;	/* collection of partition bounds */
-} PartitionDescData;
-
-typedef struct PartitionDescData *PartitionDesc;
-
-extern void RelationBuildPartitionDesc(Relation relation);
-extern bool partition_bounds_equal(int partnatts, int16 *parttyplen,
-					   bool *parttypbyval, PartitionBoundInfo b1,
-					   PartitionBoundInfo b2);
-extern PartitionBoundInfo partition_bounds_copy(PartitionBoundInfo src,
-					  PartitionKey key);
-
-extern void check_new_partition_bound(char *relname, Relation parent,
-						  PartitionBoundSpec *spec);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_partition_ancestors(Oid relid);
-extern List *get_qual_from_partbound(Relation rel, Relation parent,
-						PartitionBoundSpec *spec);
 extern List *map_partition_varattnos(List *expr, int fromrel_varno,
 						Relation to_rel, Relation from_rel,
 						bool *found_whole_row);
-extern List *RelationGetPartitionQual(Relation rel);
-extern Expr *get_partition_qual_relid(Oid relid);
 extern bool has_partition_attrs(Relation rel, Bitmapset *attnums,
 					bool *used_in_expr);
 
-extern Oid	get_default_oid_from_partdesc(PartitionDesc partdesc);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
-extern void check_default_allows_bound(Relation parent, Relation defaultRel,
-						   PartitionBoundSpec *new_spec);
 extern List *get_proposed_default_constraint(List *new_part_constaints);
 
-extern int get_partition_for_tuple(Relation relation, Datum *values,
-						bool *isnull);
-
 #endif							/* PARTITION_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 70ee3da76b..eb3fd8a206 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -17,8 +17,8 @@
 #include "access/htup.h"
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
 #include "catalog/partition.h"
+#include "nodes/parsenodes.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index 0c36c8be30..47eedb91bb 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -13,7 +13,6 @@
 #ifndef EXECPARTITION_H
 #define EXECPARTITION_H
 
-#include "catalog/partition.h"
 #include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/plannodes.h"
@@ -26,14 +25,16 @@
  *	reldesc		Relation descriptor of the table
  *	key			Partition key information of the table
  *	keystate	Execution state required for expressions in the partition key
- *	partdesc	Partition descriptor of the table
+ *	partsupfunc		fmgr lookup info of partition support functions
+ *	nparts		Number of partitions of the table
+ *	boundinfo	PartitionBoundInfo of the table
  *	tupslot		A standalone TupleTableSlot initialized with this table's tuple
  *				descriptor
  *	tupmap		TupleConversionMap to convert from the parent's rowtype to
  *				this table's rowtype (when extracting the partition key of a
  *				tuple just before routing it through this table)
- *	indexes		Array with partdesc->nparts members (for details on what
- *				individual members represent, see how they are set in
+ *	indexes		Array with nparts members (for details on what individual
+ *				members represent, see how they are set in
  *				get_partition_dispatch_recurse())
  *-----------------------
  */
@@ -42,7 +43,9 @@ typedef struct PartitionDispatchData
 	Relation	reldesc;
 	PartitionKey key;
 	List	   *keystate;		/* list of ExprState */
-	PartitionDesc partdesc;
+	FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
+	int			nparts;
+	PartitionBoundInfo boundinfo;
 	TupleTableSlot *tupslot;
 	TupleConversionMap *tupmap;
 	int		   *indexes;
diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h
index c76014d4a8..6516b29f55 100644
--- a/src/include/partitioning/partbounds.h
+++ b/src/include/partitioning/partbounds.h
@@ -11,8 +11,7 @@
 #ifndef PARTBOUNDS_H
 #define PARTBOUNDS_H
 
-#include "catalog/partition.h"
-
+#include "utils/partcache.h"
 
 /*
  * PartitionBoundInfoData encapsulates a set of partition bounds. It is
@@ -68,6 +67,8 @@ typedef struct PartitionBoundInfoData
 								 * isn't one */
 } PartitionBoundInfoData;
 
+typedef struct PartitionBoundInfoData *PartitionBoundInfo;
+
 #define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1)
 #define partition_bound_has_default(bi) ((bi)->default_index != -1)
 
@@ -101,7 +102,34 @@ typedef struct PartitionRangeBound
 } PartitionRangeBound;
 
 extern int	get_hash_partition_greatest_modulus(PartitionBoundInfo b);
-extern int partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
+extern uint64 compute_hash_value(int partnatts, FmgrInfo *partsupfunc,
+				   Datum *values, bool *isnull);
+extern List *get_qual_from_partbound(Relation rel, Relation parent,
+						PartitionBoundSpec *spec);
+extern bool partition_bounds_equal(int partnatts, int16 *parttyplen,
+					   bool *parttypbyval, PartitionBoundInfo b1,
+					   PartitionBoundInfo b2);
+extern PartitionBoundInfo partition_bounds_copy(PartitionBoundInfo src,
+					  PartitionKey key);
+extern void check_new_partition_bound(char *relname, Relation parent,
+						  PartitionBoundSpec *spec);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+
+extern PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
+					 List *datums, bool lower);
+extern int32 partition_hbound_cmp(int modulus1, int remainder1, int modulus2,
+					 int remainder2);
+extern int32 partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
+					 Oid *partcollation, Datum *datums1,
+					 PartitionRangeDatumKind *kind1, bool lower1,
+					 PartitionRangeBound *b2);
+extern int32 partition_rbound_datum_cmp(FmgrInfo *partsupfunc,
+						   Oid *partcollation,
+						   Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
+						   Datum *tuple_datums, int n_tuple_datums);
+extern int partition_list_bsearch(FmgrInfo *partsupfunc,
+					   Oid *partcollation,
 					   PartitionBoundInfo boundinfo,
 					   Datum value, bool *is_equal);
 extern int partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
@@ -114,11 +142,6 @@ extern int partition_range_datum_bsearch(FmgrInfo *partsupfunc,
 							  int nvalues, Datum *values, bool *is_equal);
 extern int partition_hash_bsearch(PartitionBoundInfo boundinfo,
 					   int modulus, int remainder);
-extern uint64 compute_hash_value(int partnatts, FmgrInfo *partsupfunc,
-				   Datum *values, bool *isnull);
-extern int32 partition_rbound_datum_cmp(FmgrInfo *partsupfunc,
-						   Oid *partcollation,
-						   Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
-						   Datum *tuple_datums, int n_tuple_datums);
+
 
 #endif							/* PARTBOUNDS_H */
diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h
index 2ae2fd16ed..5c44d9d212 100644
--- a/src/include/partitioning/partprune.h
+++ b/src/include/partitioning/partprune.h
@@ -14,7 +14,7 @@
 #ifndef PARTPRUNE_H
 #define PARTPRUNE_H
 
-#include "catalog/partition.h"
+#include "partitioning/partbounds.h"
 #include "nodes/relation.h"
 
 /*
@@ -30,7 +30,7 @@ typedef struct PartitionPruneContext
 	Oid		   *partopfamily;
 	Oid		   *partopcintype;
 	Oid		   *partcollation;
-	FmgrInfo   *partsupfunc;
+	FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
 
 	/* Number of partitions */
 	int			nparts;
diff --git a/src/include/utils/partcache.h b/src/include/utils/partcache.h
new file mode 100644
index 0000000000..2a6f1ad700
--- /dev/null
+++ b/src/include/utils/partcache.h
@@ -0,0 +1,66 @@
+/*-------------------------------------------------------------------------
+ *
+ * partcache.h
+ *
+ * Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partcache.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTCACHE_H
+#define PARTCACHE_H
+
+#include "utils/relcache.h"
+
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partition key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partition key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	Oid		   *partsupfuncid;	/* partition support func OIDs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+}			PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/*
+ * PartitionBoundInfo encapsulates a set of partition bounds.  It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure appears in partbounds.h.
+ */
+typedef struct PartitionBoundInfoData *PartitionBoundInfo;
+
+extern void RelationBuildPartitionKey(Relation relation);
+extern void RelationBuildPartitionDesc(Relation rel);
+extern PartitionKey RelationGetPartitionKey(Relation relation);
+extern FmgrInfo *partition_getprocinfo(Relation rel, PartitionKey key,
+									   int partattoff);
+extern int RelationGetPartitionCount(Relation relation);
+extern Oid *RelationGetPartitionOids(Relation relation);
+extern PartitionBoundInfo RelationGetPartitionBounds(Relation relation);
+extern Oid	RelationGetDefaultPartitionOid(Relation rel);
+extern List *RelationGetPartitionQual(Relation rel);
+extern Expr *get_partition_qual_relid(Oid relid);
+
+#endif							/* PARTCACHE_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ffffde01da..2c23704710 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -26,6 +26,7 @@
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/reltrigger.h"
+#include "utils/partcache.h"
 
 
 /*
@@ -47,36 +48,6 @@ typedef struct LockInfoData
 typedef LockInfoData *LockInfo;
 
 /*
- * Information about the partition key of a relation
- */
-typedef struct PartitionKeyData
-{
-	char		strategy;		/* partitioning strategy */
-	int16		partnatts;		/* number of columns in the partition key */
-	AttrNumber *partattrs;		/* attribute numbers of columns in the
-								 * partition key */
-	List	   *partexprs;		/* list of expressions in the partitioning
-								 * key, or NIL */
-
-	Oid		   *partopfamily;	/* OIDs of operator families */
-	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
-	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
-
-	/* Partitioning collation per attribute */
-	Oid		   *partcollation;
-
-	/* Type information per attribute */
-	Oid		   *parttypid;
-	int32	   *parttypmod;
-	int16	   *parttyplen;
-	bool	   *parttypbyval;
-	char	   *parttypalign;
-	Oid		   *parttypcoll;
-}			PartitionKeyData;
-
-typedef struct PartitionKeyData *PartitionKey;
-
-/*
  * Here are the contents of a relation cache entry.
  */
 
@@ -125,9 +96,11 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
-	MemoryContext rd_partkeycxt;	/* private memory cxt for the below */
+	MemoryContext rd_partcxt;	/* private memory cxt for the values contained
+								 * in the fields related to partitioning */
 	struct PartitionKeyData *rd_partkey;	/* partition key, or NULL */
-	MemoryContext rd_pdcxt;		/* private context for partdesc */
+	FmgrInfo   *rd_partsupfunc;	/* lookup info for partition support
+									 * procs */
 	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
 	List	   *rd_partcheck;	/* partition CHECK quals */
 
@@ -248,6 +221,21 @@ typedef struct ForeignKeyCacheInfo
 } ForeignKeyCacheInfo;
 
 /*
+ * PartitionInfo
+ *		Information related to partitioning for a partitioned table
+ *
+ * This consists of the information from PartitionKey and PartitionDesc
+ * structs that are cached in the table's RelationData.
+ */
+typedef struct PartitionInfo
+{
+	PartitionKey	key;
+	int				nparts;
+	Oid			   *oids;
+	struct PartitionBoundInfoData *boundinfo;
+} PartitionInfo;
+
+/*
  * Options common for all all indexes
  */
 typedef struct GenericIndexOpts
@@ -613,12 +601,6 @@ typedef struct ViewOptions
 	 !IsCatalogRelation(relation))
 
 /*
- * RelationGetPartitionKey
- *		Returns the PartitionKey of a relation
- */
-#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
-
-/*
  * PartitionKey inquiry functions
  */
 static inline int
@@ -660,12 +642,6 @@ get_partition_col_typmod(PartitionKey key, int col)
 	return key->parttypmod[col];
 }
 
-/*
- * RelationGetPartitionDesc
- *		Returns partition descriptor for a relation.
- */
-#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
-
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.11.0

#14Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Alvaro Herrera (#10)
Re: crash with sql language partition support function

On 2018/04/13 3:10, Alvaro Herrera wrote:

Alvaro Herrera wrote:

I'm dealing with this now -- will push shortly. The sane thing to do is
backpatch my previous memcxt fixes, since your patch introduces a
problem that we discussed with that other patch, namely that you would
leak the whole memory context if there is a problem while running the
function. I also noticed here that copy_partition_key is doing memcpy()
on the fmgr_info, which is bogus -- it should be using fmgr_info_copy.
Rather than patching that one piece it seems better to replace it
wholesale, since I bet this won't be the last time we'll hear about this
routine, and I would prefer not to deal with differently broken code in
the older branch.

Pushed.

Thanks for fixing that.

Regards,
Amit

#15Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Amit Langote (#13)
Re: crash with sql language partition support function

Amit Langote wrote:

On 2018/04/13 6:58, Alvaro Herrera wrote:

After going over your patch, I think you went slightly overboard here.
Or maybe not, but this patch is so large that it's hard to form an
opinion about it.

It's mostly code movement, but there are some other changes as well as
described below.

Hmm can you please split out the code changes into a separate patch? I
can commit the code movement quickly, but the other stuff requres more
creful review.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#16Amit Langote
amitlangote09@gmail.com
In reply to: Alvaro Herrera (#15)
2 attachment(s)
Re: crash with sql language partition support function

Sorry Alvaro for spamming you with multiple replies for the same
message. I'm failing to use Gmail properly.

On Fri, Apr 13, 2018 at 7:20 PM, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

Amit Langote wrote:

On 2018/04/13 6:58, Alvaro Herrera wrote:

After going over your patch, I think you went slightly overboard here.
Or maybe not, but this patch is so large that it's hard to form an
opinion about it.

It's mostly code movement, but there are some other changes as well as
described below.

Hmm can you please split out the code changes into a separate patch? I
can commit the code movement quickly, but the other stuff requres more
creful review.

OK, here are two patches.

First one reorganizes and second one implements the other things I
mentioned. If you think the 2nd one needs to be split further, do let
me know, although I may not be able to respond until Monday.

Thanks,
Amit

Attachments:

v4-0001-Reorganize-partitioning-code-structure.patchapplication/octet-stream; name=v4-0001-Reorganize-partitioning-code-structure.patchDownload
From 34b20f9e6220b72c17c5498e9265773354ef6816 Mon Sep 17 00:00:00 2001
From: Amit <amitlangote09@gmail.com>
Date: Fri, 13 Apr 2018 23:58:38 +0900
Subject: [PATCH v4 1/2] Reorganize partitioning code structure

---
 src/backend/catalog/heap.c            |    1 +
 src/backend/catalog/partition.c       | 2860 +--------------------------------
 src/backend/commands/tablecmds.c      |    2 +
 src/backend/executor/execPartition.c  |  106 ++
 src/backend/partitioning/Makefile     |    2 +-
 src/backend/partitioning/partbounds.c | 2055 +++++++++++++++++++++++
 src/backend/utils/cache/Makefile      |    4 +-
 src/backend/utils/cache/partcache.c   |  971 +++++++++++
 src/backend/utils/cache/relcache.c    |  207 +--
 src/include/catalog/partition.h       |   27 +-
 src/include/executor/execPartition.h  |    1 -
 src/include/partitioning/partbounds.h |   41 +-
 src/include/utils/partcache.h         |   51 +
 src/include/utils/rel.h               |   31 +-
 14 files changed, 3226 insertions(+), 3133 deletions(-)
 create mode 100644 src/backend/partitioning/partbounds.c
 create mode 100644 src/backend/utils/cache/partcache.c
 create mode 100644 src/include/utils/partcache.h

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8f329a6299..39813de991 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -77,6 +77,7 @@
 #include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/partcache.h"
 #include "utils/rel.h"
 #include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 55130e66e4..ad26adaf2f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,7 +29,6 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
-#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -45,1243 +44,18 @@
 #include "storage/lmgr.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
-#include "utils/datum.h"
 #include "utils/fmgroids.h"
 #include "utils/hashutils.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/partcache.h"
 #include "utils/rel.h"
-#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 
-
 static Oid	get_partition_parent_worker(Relation inhRel, Oid relid);
 static void get_partition_ancestors_worker(Relation inhRel, Oid relid,
 							   List **ancestors);
-static int32 qsort_partition_hbound_cmp(const void *a, const void *b);
-static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
-							   void *arg);
-static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
-						   void *arg);
-
-static Oid get_partition_operator(PartitionKey key, int col,
-					   StrategyNumber strategy, bool *need_relabel);
-static Expr *make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2);
-static void get_range_key_properties(PartitionKey key, int keynum,
-						 PartitionRangeDatum *ldatum,
-						 PartitionRangeDatum *udatum,
-						 ListCell **partexprs_item,
-						 Expr **keyCol,
-						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_hash(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
-				   bool for_default);
-static List *get_range_nulltest(PartitionKey key);
-static List *generate_partition_qual(Relation rel);
-
-static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
-					 List *datums, bool lower);
-static int32 partition_hbound_cmp(int modulus1, int remainder1, int modulus2,
-					 int remainder2);
-static int32 partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
-					 Oid *partcollation, Datum *datums1,
-					 PartitionRangeDatumKind *kind1, bool lower1,
-					 PartitionRangeBound *b2);
-
-static int	get_partition_bound_num_indexes(PartitionBoundInfo b);
-
-
-/*
- * RelationBuildPartitionDesc
- *		Form rel's partition descriptor
- *
- * Not flushed from the cache by RelationClearRelation() unless changed because
- * of addition or removal of partition.
- */
-void
-RelationBuildPartitionDesc(Relation rel)
-{
-	List	   *inhoids,
-			   *partoids;
-	Oid		   *oids = NULL;
-	List	   *boundspecs = NIL;
-	ListCell   *cell;
-	int			i,
-				nparts;
-	PartitionKey key = RelationGetPartitionKey(rel);
-	PartitionDesc result;
-	MemoryContext oldcxt;
-
-	int			ndatums = 0;
-	int			default_index = -1;
-
-	/* Hash partitioning specific */
-	PartitionHashBound **hbounds = NULL;
-
-	/* List partitioning specific */
-	PartitionListValue **all_values = NULL;
-	int			null_index = -1;
-
-	/* Range partitioning specific */
-	PartitionRangeBound **rbounds = NULL;
-
-	/* Get partition oids from pg_inherits */
-	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
-
-	/* Collect bound spec nodes in a list */
-	i = 0;
-	partoids = NIL;
-	foreach(cell, inhoids)
-	{
-		Oid			inhrelid = lfirst_oid(cell);
-		HeapTuple	tuple;
-		Datum		datum;
-		bool		isnull;
-		Node	   *boundspec;
-
-		tuple = SearchSysCache1(RELOID, inhrelid);
-		if (!HeapTupleIsValid(tuple))
-			elog(ERROR, "cache lookup failed for relation %u", inhrelid);
-
-		/*
-		 * It is possible that the pg_class tuple of a partition has not been
-		 * updated yet to set its relpartbound field.  The only case where
-		 * this happens is when we open the parent relation to check using its
-		 * partition descriptor that a new partition's bound does not overlap
-		 * some existing partition.
-		 */
-		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
-		{
-			ReleaseSysCache(tuple);
-			continue;
-		}
-
-		datum = SysCacheGetAttr(RELOID, tuple,
-								Anum_pg_class_relpartbound,
-								&isnull);
-		Assert(!isnull);
-		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
-
-		/*
-		 * Sanity check: If the PartitionBoundSpec says this is the default
-		 * partition, its OID should correspond to whatever's stored in
-		 * pg_partitioned_table.partdefid; if not, the catalog is corrupt.
-		 */
-		if (castNode(PartitionBoundSpec, boundspec)->is_default)
-		{
-			Oid			partdefid;
-
-			partdefid = get_default_partition_oid(RelationGetRelid(rel));
-			if (partdefid != inhrelid)
-				elog(ERROR, "expected partdefid %u, but got %u",
-					 inhrelid, partdefid);
-		}
-
-		boundspecs = lappend(boundspecs, boundspec);
-		partoids = lappend_oid(partoids, inhrelid);
-		ReleaseSysCache(tuple);
-	}
-
-	nparts = list_length(partoids);
-
-	if (nparts > 0)
-	{
-		oids = (Oid *) palloc(nparts * sizeof(Oid));
-		i = 0;
-		foreach(cell, partoids)
-			oids[i++] = lfirst_oid(cell);
-
-		/* Convert from node to the internal representation */
-		if (key->strategy == PARTITION_STRATEGY_HASH)
-		{
-			ndatums = nparts;
-			hbounds = (PartitionHashBound **)
-				palloc(nparts * sizeof(PartitionHashBound *));
-
-			i = 0;
-			foreach(cell, boundspecs)
-			{
-				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
-													lfirst(cell));
-
-				if (spec->strategy != PARTITION_STRATEGY_HASH)
-					elog(ERROR, "invalid strategy in partition bound spec");
-
-				hbounds[i] = (PartitionHashBound *)
-					palloc(sizeof(PartitionHashBound));
-
-				hbounds[i]->modulus = spec->modulus;
-				hbounds[i]->remainder = spec->remainder;
-				hbounds[i]->index = i;
-				i++;
-			}
-
-			/* Sort all the bounds in ascending order */
-			qsort(hbounds, nparts, sizeof(PartitionHashBound *),
-				  qsort_partition_hbound_cmp);
-		}
-		else if (key->strategy == PARTITION_STRATEGY_LIST)
-		{
-			List	   *non_null_values = NIL;
-
-			/*
-			 * Create a unified list of non-null values across all partitions.
-			 */
-			i = 0;
-			null_index = -1;
-			foreach(cell, boundspecs)
-			{
-				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
-													lfirst(cell));
-				ListCell   *c;
-
-				if (spec->strategy != PARTITION_STRATEGY_LIST)
-					elog(ERROR, "invalid strategy in partition bound spec");
-
-				/*
-				 * Note the index of the partition bound spec for the default
-				 * partition. There's no datum to add to the list of non-null
-				 * datums for this partition.
-				 */
-				if (spec->is_default)
-				{
-					default_index = i;
-					i++;
-					continue;
-				}
-
-				foreach(c, spec->listdatums)
-				{
-					Const	   *val = castNode(Const, lfirst(c));
-					PartitionListValue *list_value = NULL;
-
-					if (!val->constisnull)
-					{
-						list_value = (PartitionListValue *)
-							palloc0(sizeof(PartitionListValue));
-						list_value->index = i;
-						list_value->value = val->constvalue;
-					}
-					else
-					{
-						/*
-						 * Never put a null into the values array, flag
-						 * instead for the code further down below where we
-						 * construct the actual relcache struct.
-						 */
-						if (null_index != -1)
-							elog(ERROR, "found null more than once");
-						null_index = i;
-					}
-
-					if (list_value)
-						non_null_values = lappend(non_null_values,
-												  list_value);
-				}
-
-				i++;
-			}
-
-			ndatums = list_length(non_null_values);
-
-			/*
-			 * Collect all list values in one array. Alongside the value, we
-			 * also save the index of partition the value comes from.
-			 */
-			all_values = (PartitionListValue **) palloc(ndatums *
-														sizeof(PartitionListValue *));
-			i = 0;
-			foreach(cell, non_null_values)
-			{
-				PartitionListValue *src = lfirst(cell);
-
-				all_values[i] = (PartitionListValue *)
-					palloc(sizeof(PartitionListValue));
-				all_values[i]->value = src->value;
-				all_values[i]->index = src->index;
-				i++;
-			}
-
-			qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
-					  qsort_partition_list_value_cmp, (void *) key);
-		}
-		else if (key->strategy == PARTITION_STRATEGY_RANGE)
-		{
-			int			k;
-			PartitionRangeBound **all_bounds,
-					   *prev;
-
-			all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
-														  sizeof(PartitionRangeBound *));
-
-			/*
-			 * Create a unified list of range bounds across all the
-			 * partitions.
-			 */
-			i = ndatums = 0;
-			foreach(cell, boundspecs)
-			{
-				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
-													lfirst(cell));
-				PartitionRangeBound *lower,
-						   *upper;
-
-				if (spec->strategy != PARTITION_STRATEGY_RANGE)
-					elog(ERROR, "invalid strategy in partition bound spec");
-
-				/*
-				 * Note the index of the partition bound spec for the default
-				 * partition. There's no datum to add to the allbounds array
-				 * for this partition.
-				 */
-				if (spec->is_default)
-				{
-					default_index = i++;
-					continue;
-				}
-
-				lower = make_one_range_bound(key, i, spec->lowerdatums,
-											 true);
-				upper = make_one_range_bound(key, i, spec->upperdatums,
-											 false);
-				all_bounds[ndatums++] = lower;
-				all_bounds[ndatums++] = upper;
-				i++;
-			}
-
-			Assert(ndatums == nparts * 2 ||
-				   (default_index != -1 && ndatums == (nparts - 1) * 2));
-
-			/* Sort all the bounds in ascending order */
-			qsort_arg(all_bounds, ndatums,
-					  sizeof(PartitionRangeBound *),
-					  qsort_partition_rbound_cmp,
-					  (void *) key);
-
-			/* Save distinct bounds from all_bounds into rbounds. */
-			rbounds = (PartitionRangeBound **)
-				palloc(ndatums * sizeof(PartitionRangeBound *));
-			k = 0;
-			prev = NULL;
-			for (i = 0; i < ndatums; i++)
-			{
-				PartitionRangeBound *cur = all_bounds[i];
-				bool		is_distinct = false;
-				int			j;
-
-				/* Is the current bound distinct from the previous one? */
-				for (j = 0; j < key->partnatts; j++)
-				{
-					Datum		cmpval;
-
-					if (prev == NULL || cur->kind[j] != prev->kind[j])
-					{
-						is_distinct = true;
-						break;
-					}
-
-					/*
-					 * If the bounds are both MINVALUE or MAXVALUE, stop now
-					 * and treat them as equal, since any values after this
-					 * point must be ignored.
-					 */
-					if (cur->kind[j] != PARTITION_RANGE_DATUM_VALUE)
-						break;
-
-					cmpval = FunctionCall2Coll(&key->partsupfunc[j],
-											   key->partcollation[j],
-											   cur->datums[j],
-											   prev->datums[j]);
-					if (DatumGetInt32(cmpval) != 0)
-					{
-						is_distinct = true;
-						break;
-					}
-				}
-
-				/*
-				 * Only if the bound is distinct save it into a temporary
-				 * array i.e. rbounds which is later copied into boundinfo
-				 * datums array.
-				 */
-				if (is_distinct)
-					rbounds[k++] = all_bounds[i];
-
-				prev = cur;
-			}
-
-			/* Update ndatums to hold the count of distinct datums. */
-			ndatums = k;
-		}
-		else
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) key->strategy);
-	}
-
-	/* Now build the actual relcache partition descriptor */
-	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
-										  "partition descriptor",
-										  ALLOCSET_DEFAULT_SIZES);
-	MemoryContextCopyAndSetIdentifier(rel->rd_pdcxt, RelationGetRelationName(rel));
-
-	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
-
-	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
-	result->nparts = nparts;
-	if (nparts > 0)
-	{
-		PartitionBoundInfo boundinfo;
-		int		   *mapping;
-		int			next_index = 0;
-
-		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
-
-		boundinfo = (PartitionBoundInfoData *)
-			palloc0(sizeof(PartitionBoundInfoData));
-		boundinfo->strategy = key->strategy;
-		boundinfo->default_index = -1;
-		boundinfo->ndatums = ndatums;
-		boundinfo->null_index = -1;
-		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
-
-		/* Initialize mapping array with invalid values */
-		mapping = (int *) palloc(sizeof(int) * nparts);
-		for (i = 0; i < nparts; i++)
-			mapping[i] = -1;
-
-		switch (key->strategy)
-		{
-			case PARTITION_STRATEGY_HASH:
-				{
-					/* Modulus are stored in ascending order */
-					int			greatest_modulus = hbounds[ndatums - 1]->modulus;
-
-					boundinfo->indexes = (int *) palloc(greatest_modulus *
-														sizeof(int));
-
-					for (i = 0; i < greatest_modulus; i++)
-						boundinfo->indexes[i] = -1;
-
-					for (i = 0; i < nparts; i++)
-					{
-						int			modulus = hbounds[i]->modulus;
-						int			remainder = hbounds[i]->remainder;
-
-						boundinfo->datums[i] = (Datum *) palloc(2 *
-																sizeof(Datum));
-						boundinfo->datums[i][0] = Int32GetDatum(modulus);
-						boundinfo->datums[i][1] = Int32GetDatum(remainder);
-
-						while (remainder < greatest_modulus)
-						{
-							/* overlap? */
-							Assert(boundinfo->indexes[remainder] == -1);
-							boundinfo->indexes[remainder] = i;
-							remainder += modulus;
-						}
-
-						mapping[hbounds[i]->index] = i;
-						pfree(hbounds[i]);
-					}
-					pfree(hbounds);
-					break;
-				}
-
-			case PARTITION_STRATEGY_LIST:
-				{
-					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
-
-					/*
-					 * Copy values.  Indexes of individual values are mapped
-					 * to canonical values so that they match for any two list
-					 * partitioned tables with same number of partitions and
-					 * same lists per partition.  One way to canonicalize is
-					 * to assign the index in all_values[] of the smallest
-					 * value of each partition, as the index of all of the
-					 * partition's values.
-					 */
-					for (i = 0; i < ndatums; i++)
-					{
-						boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
-						boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
-															key->parttypbyval[0],
-															key->parttyplen[0]);
-
-						/* If the old index has no mapping, assign one */
-						if (mapping[all_values[i]->index] == -1)
-							mapping[all_values[i]->index] = next_index++;
-
-						boundinfo->indexes[i] = mapping[all_values[i]->index];
-					}
-
-					/*
-					 * If null-accepting partition has no mapped index yet,
-					 * assign one.  This could happen if such partition
-					 * accepts only null and hence not covered in the above
-					 * loop which only handled non-null values.
-					 */
-					if (null_index != -1)
-					{
-						Assert(null_index >= 0);
-						if (mapping[null_index] == -1)
-							mapping[null_index] = next_index++;
-						boundinfo->null_index = mapping[null_index];
-					}
-
-					/* Assign mapped index for the default partition. */
-					if (default_index != -1)
-					{
-						/*
-						 * The default partition accepts any value not
-						 * specified in the lists of other partitions, hence
-						 * it should not get mapped index while assigning
-						 * those for non-null datums.
-						 */
-						Assert(default_index >= 0 &&
-							   mapping[default_index] == -1);
-						mapping[default_index] = next_index++;
-						boundinfo->default_index = mapping[default_index];
-					}
-
-					/* All partition must now have a valid mapping */
-					Assert(next_index == nparts);
-					break;
-				}
-
-			case PARTITION_STRATEGY_RANGE:
-				{
-					boundinfo->kind = (PartitionRangeDatumKind **)
-						palloc(ndatums *
-							   sizeof(PartitionRangeDatumKind *));
-					boundinfo->indexes = (int *) palloc((ndatums + 1) *
-														sizeof(int));
-
-					for (i = 0; i < ndatums; i++)
-					{
-						int			j;
-
-						boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
-																sizeof(Datum));
-						boundinfo->kind[i] = (PartitionRangeDatumKind *)
-							palloc(key->partnatts *
-								   sizeof(PartitionRangeDatumKind));
-						for (j = 0; j < key->partnatts; j++)
-						{
-							if (rbounds[i]->kind[j] == PARTITION_RANGE_DATUM_VALUE)
-								boundinfo->datums[i][j] =
-									datumCopy(rbounds[i]->datums[j],
-											  key->parttypbyval[j],
-											  key->parttyplen[j]);
-							boundinfo->kind[i][j] = rbounds[i]->kind[j];
-						}
-
-						/*
-						 * There is no mapping for invalid indexes.
-						 *
-						 * Any lower bounds in the rbounds array have invalid
-						 * indexes assigned, because the values between the
-						 * previous bound (if there is one) and this (lower)
-						 * bound are not part of the range of any existing
-						 * partition.
-						 */
-						if (rbounds[i]->lower)
-							boundinfo->indexes[i] = -1;
-						else
-						{
-							int			orig_index = rbounds[i]->index;
-
-							/* If the old index has no mapping, assign one */
-							if (mapping[orig_index] == -1)
-								mapping[orig_index] = next_index++;
-
-							boundinfo->indexes[i] = mapping[orig_index];
-						}
-					}
-
-					/* Assign mapped index for the default partition. */
-					if (default_index != -1)
-					{
-						Assert(default_index >= 0 && mapping[default_index] == -1);
-						mapping[default_index] = next_index++;
-						boundinfo->default_index = mapping[default_index];
-					}
-					boundinfo->indexes[i] = -1;
-					break;
-				}
-
-			default:
-				elog(ERROR, "unexpected partition strategy: %d",
-					 (int) key->strategy);
-		}
-
-		result->boundinfo = boundinfo;
-
-		/*
-		 * Now assign OIDs from the original array into mapped indexes of the
-		 * result array.  Order of OIDs in the former is defined by the
-		 * catalog scan that retrieved them, whereas that in the latter is
-		 * defined by canonicalized representation of the partition bounds.
-		 */
-		for (i = 0; i < nparts; i++)
-			result->oids[mapping[i]] = oids[i];
-		pfree(mapping);
-	}
-
-	MemoryContextSwitchTo(oldcxt);
-	rel->rd_partdesc = result;
-}
-
-/*
- * Are two partition bound collections logically equal?
- *
- * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
- * This is also useful when b1 and b2 are bound collections of two separate
- * relations, respectively, because PartitionBoundInfo is a canonical
- * representation of partition bounds.
- */
-bool
-partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
-					   PartitionBoundInfo b1, PartitionBoundInfo b2)
-{
-	int			i;
-
-	if (b1->strategy != b2->strategy)
-		return false;
-
-	if (b1->ndatums != b2->ndatums)
-		return false;
-
-	if (b1->null_index != b2->null_index)
-		return false;
-
-	if (b1->default_index != b2->default_index)
-		return false;
-
-	if (b1->strategy == PARTITION_STRATEGY_HASH)
-	{
-		int			greatest_modulus = get_hash_partition_greatest_modulus(b1);
-
-		/*
-		 * If two hash partitioned tables have different greatest moduli,
-		 * their partition schemes don't match.
-		 */
-		if (greatest_modulus != get_hash_partition_greatest_modulus(b2))
-			return false;
-
-		/*
-		 * We arrange the partitions in the ascending order of their modulus
-		 * and remainders.  Also every modulus is factor of next larger
-		 * modulus.  Therefore we can safely store index of a given partition
-		 * in indexes array at remainder of that partition.  Also entries at
-		 * (remainder + N * modulus) positions in indexes array are all same
-		 * for (modulus, remainder) specification for any partition.  Thus
-		 * datums array from both the given bounds are same, if and only if
-		 * their indexes array will be same.  So, it suffices to compare
-		 * indexes array.
-		 */
-		for (i = 0; i < greatest_modulus; i++)
-			if (b1->indexes[i] != b2->indexes[i])
-				return false;
-
-#ifdef USE_ASSERT_CHECKING
-
-		/*
-		 * Nonetheless make sure that the bounds are indeed same when the
-		 * indexes match.  Hash partition bound stores modulus and remainder
-		 * at b1->datums[i][0] and b1->datums[i][1] position respectively.
-		 */
-		for (i = 0; i < b1->ndatums; i++)
-			Assert((b1->datums[i][0] == b2->datums[i][0] &&
-					b1->datums[i][1] == b2->datums[i][1]));
-#endif
-	}
-	else
-	{
-		for (i = 0; i < b1->ndatums; i++)
-		{
-			int			j;
-
-			for (j = 0; j < partnatts; j++)
-			{
-				/* For range partitions, the bounds might not be finite. */
-				if (b1->kind != NULL)
-				{
-					/* The different kinds of bound all differ from each other */
-					if (b1->kind[i][j] != b2->kind[i][j])
-						return false;
-
-					/*
-					 * Non-finite bounds are equal without further
-					 * examination.
-					 */
-					if (b1->kind[i][j] != PARTITION_RANGE_DATUM_VALUE)
-						continue;
-				}
-
-				/*
-				 * Compare the actual values. Note that it would be both
-				 * incorrect and unsafe to invoke the comparison operator
-				 * derived from the partitioning specification here.  It would
-				 * be incorrect because we want the relcache entry to be
-				 * updated for ANY change to the partition bounds, not just
-				 * those that the partitioning operator thinks are
-				 * significant.  It would be unsafe because we might reach
-				 * this code in the context of an aborted transaction, and an
-				 * arbitrary partitioning operator might not be safe in that
-				 * 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;
-			}
-
-			if (b1->indexes[i] != b2->indexes[i])
-				return false;
-		}
-
-		/* There are ndatums+1 indexes in case of range partitions */
-		if (b1->strategy == PARTITION_STRATEGY_RANGE &&
-			b1->indexes[i] != b2->indexes[i])
-			return false;
-	}
-	return true;
-}
-
-/*
- * Return a copy of given PartitionBoundInfo structure. The data types of bounds
- * are described by given partition key specification.
- */
-PartitionBoundInfo
-partition_bounds_copy(PartitionBoundInfo src,
-					  PartitionKey key)
-{
-	PartitionBoundInfo dest;
-	int			i;
-	int			ndatums;
-	int			partnatts;
-	int			num_indexes;
-
-	dest = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
-
-	dest->strategy = src->strategy;
-	ndatums = dest->ndatums = src->ndatums;
-	partnatts = key->partnatts;
-
-	num_indexes = get_partition_bound_num_indexes(src);
-
-	/* List partitioned tables have only a single partition key. */
-	Assert(key->strategy != PARTITION_STRATEGY_LIST || partnatts == 1);
-
-	dest->datums = (Datum **) palloc(sizeof(Datum *) * ndatums);
-
-	if (src->kind != NULL)
-	{
-		dest->kind = (PartitionRangeDatumKind **) palloc(ndatums *
-														 sizeof(PartitionRangeDatumKind *));
-		for (i = 0; i < ndatums; i++)
-		{
-			dest->kind[i] = (PartitionRangeDatumKind *) palloc(partnatts *
-															   sizeof(PartitionRangeDatumKind));
-
-			memcpy(dest->kind[i], src->kind[i],
-				   sizeof(PartitionRangeDatumKind) * key->partnatts);
-		}
-	}
-	else
-		dest->kind = NULL;
-
-	for (i = 0; i < ndatums; i++)
-	{
-		int			j;
-
-		/*
-		 * For a corresponding to hash partition, datums array will have two
-		 * elements - modulus and remainder.
-		 */
-		bool		hash_part = (key->strategy == PARTITION_STRATEGY_HASH);
-		int			natts = hash_part ? 2 : partnatts;
-
-		dest->datums[i] = (Datum *) palloc(sizeof(Datum) * natts);
-
-		for (j = 0; j < natts; j++)
-		{
-			bool		byval;
-			int			typlen;
-
-			if (hash_part)
-			{
-				typlen = sizeof(int32); /* Always int4 */
-				byval = true;	/* int4 is pass-by-value */
-			}
-			else
-			{
-				byval = key->parttypbyval[j];
-				typlen = key->parttyplen[j];
-			}
-
-			if (dest->kind == NULL ||
-				dest->kind[i][j] == PARTITION_RANGE_DATUM_VALUE)
-				dest->datums[i][j] = datumCopy(src->datums[i][j],
-											   byval, typlen);
-		}
-	}
-
-	dest->indexes = (int *) palloc(sizeof(int) * num_indexes);
-	memcpy(dest->indexes, src->indexes, sizeof(int) * num_indexes);
-
-	dest->null_index = src->null_index;
-	dest->default_index = src->default_index;
-
-	return dest;
-}
-
-/*
- * check_new_partition_bound
- *
- * Checks if the new partition's bound overlaps any of the existing partitions
- * of parent.  Also performs additional checks as necessary per strategy.
- */
-void
-check_new_partition_bound(char *relname, Relation parent,
-						  PartitionBoundSpec *spec)
-{
-	PartitionKey key = RelationGetPartitionKey(parent);
-	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
-	PartitionBoundInfo boundinfo = partdesc->boundinfo;
-	ParseState *pstate = make_parsestate(NULL);
-	int			with = -1;
-	bool		overlap = false;
-
-	if (spec->is_default)
-	{
-		/*
-		 * The default partition bound never conflicts with any other
-		 * partition's; if that's what we're attaching, the only possible
-		 * problem is that one already exists, so check for that and we're
-		 * done.
-		 */
-		if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
-			return;
-
-		/* Default partition already exists, error out. */
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
-						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
-				 parser_errposition(pstate, spec->location)));
-	}
-
-	switch (key->strategy)
-	{
-		case PARTITION_STRATEGY_HASH:
-			{
-				Assert(spec->strategy == PARTITION_STRATEGY_HASH);
-				Assert(spec->remainder >= 0 && spec->remainder < spec->modulus);
-
-				if (partdesc->nparts > 0)
-				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
-					Datum	  **datums = boundinfo->datums;
-					int			ndatums = boundinfo->ndatums;
-					int			greatest_modulus;
-					int			remainder;
-					int			offset;
-					bool		valid_modulus = true;
-					int			prev_modulus,	/* Previous largest modulus */
-								next_modulus;	/* Next largest modulus */
-
-					/*
-					 * Check rule that every modulus must be a factor of the
-					 * next larger modulus.  For example, if you have a bunch
-					 * of partitions that all have modulus 5, you can add a
-					 * new partition with modulus 10 or a new partition with
-					 * modulus 15, but you cannot add both a partition with
-					 * modulus 10 and a partition with modulus 15, because 10
-					 * is not a factor of 15.
-					 *
-					 * Get the greatest (modulus, remainder) pair contained in
-					 * boundinfo->datums that is less than or equal to the
-					 * (spec->modulus, spec->remainder) pair.
-					 */
-					offset = partition_hash_bsearch(boundinfo,
-													spec->modulus,
-													spec->remainder);
-					if (offset < 0)
-					{
-						next_modulus = DatumGetInt32(datums[0][0]);
-						valid_modulus = (next_modulus % spec->modulus) == 0;
-					}
-					else
-					{
-						prev_modulus = DatumGetInt32(datums[offset][0]);
-						valid_modulus = (spec->modulus % prev_modulus) == 0;
-
-						if (valid_modulus && (offset + 1) < ndatums)
-						{
-							next_modulus = DatumGetInt32(datums[offset + 1][0]);
-							valid_modulus = (next_modulus % spec->modulus) == 0;
-						}
-					}
-
-					if (!valid_modulus)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-								 errmsg("every hash partition modulus must be a factor of the next larger modulus")));
-
-					greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
-					remainder = spec->remainder;
-
-					/*
-					 * Normally, the lowest remainder that could conflict with
-					 * the new partition is equal to the remainder specified
-					 * for the new partition, but when the new partition has a
-					 * modulus higher than any used so far, we need to adjust.
-					 */
-					if (remainder >= greatest_modulus)
-						remainder = remainder % greatest_modulus;
-
-					/* Check every potentially-conflicting remainder. */
-					do
-					{
-						if (boundinfo->indexes[remainder] != -1)
-						{
-							overlap = true;
-							with = boundinfo->indexes[remainder];
-							break;
-						}
-						remainder += spec->modulus;
-					} while (remainder < greatest_modulus);
-				}
-
-				break;
-			}
-
-		case PARTITION_STRATEGY_LIST:
-			{
-				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-
-				if (partdesc->nparts > 0)
-				{
-					ListCell   *cell;
-
-					Assert(boundinfo &&
-						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
-						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo) ||
-							partition_bound_has_default(boundinfo)));
-
-					foreach(cell, spec->listdatums)
-					{
-						Const	   *val = castNode(Const, lfirst(cell));
-
-						if (!val->constisnull)
-						{
-							int			offset;
-							bool		equal;
-
-							offset = partition_list_bsearch(key->partsupfunc,
-														key->partcollation,
-															boundinfo,
-															val->constvalue,
-															&equal);
-							if (offset >= 0 && equal)
-							{
-								overlap = true;
-								with = boundinfo->indexes[offset];
-								break;
-							}
-						}
-						else if (partition_bound_accepts_nulls(boundinfo))
-						{
-							overlap = true;
-							with = boundinfo->null_index;
-							break;
-						}
-					}
-				}
-
-				break;
-			}
-
-		case PARTITION_STRATEGY_RANGE:
-			{
-				PartitionRangeBound *lower,
-						   *upper;
-
-				Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
-				lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
-				upper = make_one_range_bound(key, -1, spec->upperdatums, false);
-
-				/*
-				 * 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->kind, true, upper) >= 0)
-				{
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-							 errmsg("empty range bound specified for partition \"%s\"",
-									relname),
-							 errdetail("Specified lower bound %s is greater than or equal to upper bound %s.",
-									   get_range_partbound_string(spec->lowerdatums),
-									   get_range_partbound_string(spec->upperdatums)),
-							 parser_errposition(pstate, spec->location)));
-				}
-
-				if (partdesc->nparts > 0)
-				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
-					int			offset;
-					bool		equal;
-
-					Assert(boundinfo &&
-						   boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
-						   (boundinfo->ndatums > 0 ||
-							partition_bound_has_default(boundinfo)));
-
-					/*
-					 * Test whether the new lower bound (which is treated
-					 * inclusively as part of the new partition) lies inside
-					 * an existing partition, or in a gap.
-					 *
-					 * If it's inside an existing partition, the bound at
-					 * offset + 1 will be the upper bound of that partition,
-					 * and its index will be >= 0.
-					 *
-					 * If it's in a gap, the bound at offset + 1 will be the
-					 * lower bound of the next partition, and its index will
-					 * be -1. This is also true if there is no next partition,
-					 * since the index array is initialised with an extra -1
-					 * at the end.
-					 */
-					offset = partition_range_bsearch(key->partnatts,
-													 key->partsupfunc,
-													 key->partcollation,
-													 boundinfo, lower,
-													 &equal);
-
-					if (boundinfo->indexes[offset + 1] < 0)
-					{
-						/*
-						 * Check that the new partition will fit in the gap.
-						 * For it to fit, the new upper bound must be less
-						 * than or equal to the lower bound of the next
-						 * partition, if there is one.
-						 */
-						if (offset + 1 < boundinfo->ndatums)
-						{
-							int32		cmpval;
-							Datum 	   *datums;
-							PartitionRangeDatumKind *kind;
-							bool		is_lower;
-
-							datums = boundinfo->datums[offset + 1];
-							kind = boundinfo->kind[offset + 1];
-							is_lower = (boundinfo->indexes[offset + 1] == -1);
-
-							cmpval = partition_rbound_cmp(key->partnatts,
-														  key->partsupfunc,
-														  key->partcollation,
-														  datums, kind,
-														  is_lower, upper);
-							if (cmpval < 0)
-							{
-								/*
-								 * The new partition overlaps with the
-								 * existing partition between offset + 1 and
-								 * offset + 2.
-								 */
-								overlap = true;
-								with = boundinfo->indexes[offset + 2];
-							}
-						}
-					}
-					else
-					{
-						/*
-						 * The new partition overlaps with the existing
-						 * partition between offset and offset + 1.
-						 */
-						overlap = true;
-						with = boundinfo->indexes[offset + 1];
-					}
-				}
-
-				break;
-			}
-
-		default:
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) key->strategy);
-	}
-
-	if (overlap)
-	{
-		Assert(with >= 0);
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("partition \"%s\" would overlap partition \"%s\"",
-						relname, get_rel_name(partdesc->oids[with])),
-				 parser_errposition(pstate, spec->location)));
-	}
-}
-
-/*
- * check_default_allows_bound
- *
- * This function checks if there exists a row in the default partition that
- * would properly belong to the new partition being added.  If it finds one,
- * it throws an error.
- */
-void
-check_default_allows_bound(Relation parent, Relation default_rel,
-						   PartitionBoundSpec *new_spec)
-{
-	List	   *new_part_constraints;
-	List	   *def_part_constraints;
-	List	   *all_parts;
-	ListCell   *lc;
-
-	new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
-		? get_qual_for_list(parent, new_spec)
-		: get_qual_for_range(parent, new_spec, false);
-	def_part_constraints =
-		get_proposed_default_constraint(new_part_constraints);
-
-	/*
-	 * If the existing constraints on the default partition imply that it will
-	 * not contain any row that would belong to the new partition, we can
-	 * avoid scanning the default partition.
-	 */
-	if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
-	{
-		ereport(INFO,
-				(errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints",
-						RelationGetRelationName(default_rel))));
-		return;
-	}
-
-	/*
-	 * Scan the default partition and its subpartitions, and check for rows
-	 * that do not satisfy the revised partition constraints.
-	 */
-	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
-										AccessExclusiveLock, NULL);
-	else
-		all_parts = list_make1_oid(RelationGetRelid(default_rel));
-
-	foreach(lc, all_parts)
-	{
-		Oid			part_relid = lfirst_oid(lc);
-		Relation	part_rel;
-		Expr	   *constr;
-		Expr	   *partition_constraint;
-		EState	   *estate;
-		HeapTuple	tuple;
-		ExprState  *partqualstate = NULL;
-		Snapshot	snapshot;
-		TupleDesc	tupdesc;
-		ExprContext *econtext;
-		HeapScanDesc scan;
-		MemoryContext oldCxt;
-		TupleTableSlot *tupslot;
-
-		/* Lock already taken above. */
-		if (part_relid != RelationGetRelid(default_rel))
-		{
-			part_rel = heap_open(part_relid, NoLock);
-
-			/*
-			 * If the partition constraints on default partition child imply
-			 * that it will not contain any row that would belong to the new
-			 * partition, we can avoid scanning the child table.
-			 */
-			if (PartConstraintImpliedByRelConstraint(part_rel,
-													 def_part_constraints))
-			{
-				ereport(INFO,
-						(errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints",
-								RelationGetRelationName(part_rel))));
-
-				heap_close(part_rel, NoLock);
-				continue;
-			}
-		}
-		else
-			part_rel = default_rel;
-
-		/*
-		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
-		 * scanned.
-		 */
-		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
-		{
-			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
-				ereport(WARNING,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
-								RelationGetRelationName(part_rel),
-								RelationGetRelationName(default_rel))));
-
-			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
-				heap_close(part_rel, NoLock);
-
-			continue;
-		}
-
-		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
-		constr = linitial(def_part_constraints);
-		partition_constraint = (Expr *)
-			map_partition_varattnos((List *) constr,
-									1, part_rel, parent, NULL);
-		estate = CreateExecutorState();
-
-		/* Build expression execution states for partition check quals */
-		partqualstate = ExecPrepareExpr(partition_constraint, estate);
-
-		econtext = GetPerTupleExprContext(estate);
-		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
-		tupslot = MakeSingleTupleTableSlot(tupdesc);
-
-		/*
-		 * Switch to per-tuple memory context and reset it for each tuple
-		 * produced, so we don't leak memory.
-		 */
-		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
-		{
-			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
-			econtext->ecxt_scantuple = tupslot;
-
-			if (!ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
-								RelationGetRelationName(default_rel))));
-
-			ResetExprContext(econtext);
-			CHECK_FOR_INTERRUPTS();
-		}
-
-		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
-		UnregisterSnapshot(snapshot);
-		ExecDropSingleTupleTableSlot(tupslot);
-		FreeExecutorState(estate);
-
-		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
-			heap_close(part_rel, NoLock);	/* keep the lock until commit */
-	}
-}
 
 /*
  * get_partition_parent
@@ -1391,45 +165,6 @@ get_partition_ancestors_worker(Relation inhRel, Oid relid, List **ancestors)
 	get_partition_ancestors_worker(inhRel, parentOid, ancestors);
 }
 
-/*
- * get_qual_from_partbound
- *		Given a parser node for partition bound, return the list of executable
- *		expressions as partition constraint
- */
-List *
-get_qual_from_partbound(Relation rel, Relation parent,
-						PartitionBoundSpec *spec)
-{
-	PartitionKey key = RelationGetPartitionKey(parent);
-	List	   *my_qual = NIL;
-
-	Assert(key != NULL);
-
-	switch (key->strategy)
-	{
-		case PARTITION_STRATEGY_HASH:
-			Assert(spec->strategy == PARTITION_STRATEGY_HASH);
-			my_qual = get_qual_for_hash(parent, spec);
-			break;
-
-		case PARTITION_STRATEGY_LIST:
-			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(parent, spec);
-			break;
-
-		case PARTITION_STRATEGY_RANGE:
-			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
-			my_qual = get_qual_for_range(parent, spec, false);
-			break;
-
-		default:
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) key->strategy);
-	}
-
-	return my_qual;
-}
-
 /*
  * map_partition_varattnos - maps varattno of any Vars in expr from the
  * attno's of 'from_rel' to the attno's of 'to_rel' partition, each of which
@@ -1475,1152 +210,6 @@ map_partition_varattnos(List *expr, int fromrel_varno,
 	return expr;
 }
 
-/*
- * RelationGetPartitionQual
- *
- * Returns a list of partition quals
- */
-List *
-RelationGetPartitionQual(Relation rel)
-{
-	/* Quick exit */
-	if (!rel->rd_rel->relispartition)
-		return NIL;
-
-	return generate_partition_qual(rel);
-}
-
-/*
- * get_partition_qual_relid
- *
- * Returns an expression tree describing the passed-in relation's partition
- * constraint. If there is no partition constraint returns NULL; this can
- * happen if the default partition is the only partition.
- */
-Expr *
-get_partition_qual_relid(Oid relid)
-{
-	Relation	rel = heap_open(relid, AccessShareLock);
-	Expr	   *result = NULL;
-	List	   *and_args;
-
-	/* Do the work only if this relation is a partition. */
-	if (rel->rd_rel->relispartition)
-	{
-		and_args = generate_partition_qual(rel);
-
-		if (and_args == NIL)
-			result = NULL;
-		else if (list_length(and_args) > 1)
-			result = makeBoolExpr(AND_EXPR, and_args, -1);
-		else
-			result = linitial(and_args);
-	}
-
-	/* Keep the lock. */
-	heap_close(rel, NoLock);
-
-	return result;
-}
-
-
-/*
- * get_partition_operator
- *
- * Return oid of the operator of given strategy for a given partition key
- * column.
- */
-static Oid
-get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
-					   bool *need_relabel)
-{
-	Oid			operoid;
-
-	/*
-	 * First check if there exists an operator of the given strategy, with
-	 * this column's type as both its lefttype and righttype, in the
-	 * partitioning operator family specified for the column.
-	 */
-	operoid = get_opfamily_member(key->partopfamily[col],
-								  key->parttypid[col],
-								  key->parttypid[col],
-								  strategy);
-
-	/*
-	 * If one doesn't exist, we must resort to using an operator in the same
-	 * operator family but with the operator class declared input type.  It is
-	 * OK to do so, because the column's type is known to be binary-coercible
-	 * with the operator class input type (otherwise, the operator class in
-	 * question would not have been accepted as the partitioning operator
-	 * class).  We must however inform the caller to wrap the non-Const
-	 * expression with a RelabelType node to denote the implicit coercion. It
-	 * ensures that the resulting expression structurally matches similarly
-	 * processed expressions within the optimizer.
-	 */
-	if (!OidIsValid(operoid))
-	{
-		operoid = get_opfamily_member(key->partopfamily[col],
-									  key->partopcintype[col],
-									  key->partopcintype[col],
-									  strategy);
-		if (!OidIsValid(operoid))
-			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
-				 strategy, key->partopcintype[col], key->partopcintype[col],
-				 key->partopfamily[col]);
-		*need_relabel = true;
-	}
-	else
-		*need_relabel = false;
-
-	return operoid;
-}
-
-/*
- * make_partition_op_expr
- *		Returns an Expr for the given partition key column with arg1 and
- *		arg2 as its leftop and rightop, respectively
- */
-static Expr *
-make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2)
-{
-	Oid			operoid;
-	bool		need_relabel = false;
-	Expr	   *result = NULL;
-
-	/* Get the correct btree operator for this partitioning column */
-	operoid = get_partition_operator(key, keynum, strategy, &need_relabel);
-
-	/*
-	 * Chosen operator may be such that the non-Const operand needs to be
-	 * coerced, so apply the same; see the comment in
-	 * get_partition_operator().
-	 */
-	if (!IsA(arg1, Const) &&
-		(need_relabel ||
-		 key->partcollation[keynum] != key->parttypcoll[keynum]))
-		arg1 = (Expr *) makeRelabelType(arg1,
-										key->partopcintype[keynum],
-										-1,
-										key->partcollation[keynum],
-										COERCE_EXPLICIT_CAST);
-
-	/* Generate the actual expression */
-	switch (key->strategy)
-	{
-		case PARTITION_STRATEGY_LIST:
-			{
-				List	   *elems = (List *) arg2;
-				int			nelems = list_length(elems);
-
-				Assert(nelems >= 1);
-				Assert(keynum == 0);
-
-				if (nelems > 1 &&
-					!type_is_array(key->parttypid[keynum]))
-				{
-					ArrayExpr  *arrexpr;
-					ScalarArrayOpExpr *saopexpr;
-
-					/* Construct an ArrayExpr for the right-hand inputs */
-					arrexpr = makeNode(ArrayExpr);
-					arrexpr->array_typeid =
-									get_array_type(key->parttypid[keynum]);
-					arrexpr->array_collid = key->parttypcoll[keynum];
-					arrexpr->element_typeid = key->parttypid[keynum];
-					arrexpr->elements = elems;
-					arrexpr->multidims = false;
-					arrexpr->location = -1;
-
-					/* Build leftop = ANY (rightop) */
-					saopexpr = makeNode(ScalarArrayOpExpr);
-					saopexpr->opno = operoid;
-					saopexpr->opfuncid = get_opcode(operoid);
-					saopexpr->useOr = true;
-					saopexpr->inputcollid = key->partcollation[keynum];
-					saopexpr->args = list_make2(arg1, arrexpr);
-					saopexpr->location = -1;
-
-					result = (Expr *) saopexpr;
-				}
-				else
-				{
-					List	   *elemops = NIL;
-					ListCell   *lc;
-
-					foreach (lc, elems)
-					{
-						Expr   *elem = lfirst(lc),
-							   *elemop;
-
-						elemop = make_opclause(operoid,
-											   BOOLOID,
-											   false,
-											   arg1, elem,
-											   InvalidOid,
-											   key->partcollation[keynum]);
-						elemops = lappend(elemops, elemop);
-					}
-
-					result = nelems > 1 ? makeBoolExpr(OR_EXPR, elemops, -1) : linitial(elemops);
-				}
-				break;
-			}
-
-		case PARTITION_STRATEGY_RANGE:
-			result = make_opclause(operoid,
-								   BOOLOID,
-								   false,
-								   arg1, arg2,
-								   InvalidOid,
-								   key->partcollation[keynum]);
-			break;
-
-		default:
-			elog(ERROR, "invalid partitioning strategy");
-			break;
-	}
-
-	return result;
-}
-
-/*
- * get_qual_for_hash
- *
- * Returns a CHECK constraint expression to use as a hash partition's
- * constraint, given the parent relation and partition bound structure.
- *
- * The partition constraint for a hash partition is always a call to the
- * built-in function satisfies_hash_partition().
- */
-static List *
-get_qual_for_hash(Relation parent, PartitionBoundSpec *spec)
-{
-	PartitionKey key = RelationGetPartitionKey(parent);
-	FuncExpr   *fexpr;
-	Node	   *relidConst;
-	Node	   *modulusConst;
-	Node	   *remainderConst;
-	List	   *args;
-	ListCell   *partexprs_item;
-	int			i;
-
-	/* Fixed arguments. */
-	relidConst = (Node *) makeConst(OIDOID,
-									-1,
-									InvalidOid,
-									sizeof(Oid),
-									ObjectIdGetDatum(RelationGetRelid(parent)),
-									false,
-									true);
-
-	modulusConst = (Node *) makeConst(INT4OID,
-									  -1,
-									  InvalidOid,
-									  sizeof(int32),
-									  Int32GetDatum(spec->modulus),
-									  false,
-									  true);
-
-	remainderConst = (Node *) makeConst(INT4OID,
-										-1,
-										InvalidOid,
-										sizeof(int32),
-										Int32GetDatum(spec->remainder),
-										false,
-										true);
-
-	args = list_make3(relidConst, modulusConst, remainderConst);
-	partexprs_item = list_head(key->partexprs);
-
-	/* Add an argument for each key column. */
-	for (i = 0; i < key->partnatts; i++)
-	{
-		Node	   *keyCol;
-
-		/* Left operand */
-		if (key->partattrs[i] != 0)
-		{
-			keyCol = (Node *) makeVar(1,
-									  key->partattrs[i],
-									  key->parttypid[i],
-									  key->parttypmod[i],
-									  key->parttypcoll[i],
-									  0);
-		}
-		else
-		{
-			keyCol = (Node *) copyObject(lfirst(partexprs_item));
-			partexprs_item = lnext(partexprs_item);
-		}
-
-		args = lappend(args, keyCol);
-	}
-
-	fexpr = makeFuncExpr(F_SATISFIES_HASH_PARTITION,
-						 BOOLOID,
-						 args,
-						 InvalidOid,
-						 InvalidOid,
-						 COERCE_EXPLICIT_CALL);
-
-	return list_make1(fexpr);
-}
-
-/*
- * get_qual_for_list
- *
- * Returns an implicit-AND list of expressions to use as a list partition's
- * constraint, given the parent relation and partition bound structure.
- *
- * The function returns NIL for a default partition when it's the only
- * partition since in that case there is no constraint.
- */
-static List *
-get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
-{
-	PartitionKey key = RelationGetPartitionKey(parent);
-	List	   *result;
-	Expr	   *keyCol;
-	Expr	   *opexpr;
-	NullTest   *nulltest;
-	ListCell   *cell;
-	List	   *elems = NIL;
-	bool		list_has_null = false;
-
-	/*
-	 * Only single-column list partitioning is supported, so we are worried
-	 * only about the partition key with index 0.
-	 */
-	Assert(key->partnatts == 1);
-
-	/* Construct Var or expression representing the partition column */
-	if (key->partattrs[0] != 0)
-		keyCol = (Expr *) makeVar(1,
-								  key->partattrs[0],
-								  key->parttypid[0],
-								  key->parttypmod[0],
-								  key->parttypcoll[0],
-								  0);
-	else
-		keyCol = (Expr *) copyObject(linitial(key->partexprs));
-
-	/*
-	 * For default list partition, collect datums for all the partitions. The
-	 * default partition constraint should check that the partition key is
-	 * equal to none of those.
-	 */
-	if (spec->is_default)
-	{
-		int			i;
-		int			ndatums = 0;
-		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
-		PartitionBoundInfo boundinfo = pdesc->boundinfo;
-
-		if (boundinfo)
-		{
-			ndatums = boundinfo->ndatums;
-
-			if (partition_bound_accepts_nulls(boundinfo))
-				list_has_null = true;
-		}
-
-		/*
-		 * If default is the only partition, there need not be any partition
-		 * constraint on it.
-		 */
-		if (ndatums == 0 && !list_has_null)
-			return NIL;
-
-		for (i = 0; i < ndatums; i++)
-		{
-			Const	   *val;
-
-			/*
-			 * Construct Const from known-not-null datum.  We must be careful
-			 * to copy the value, because our result has to be able to outlive
-			 * the relcache entry we're copying from.
-			 */
-			val = makeConst(key->parttypid[0],
-							key->parttypmod[0],
-							key->parttypcoll[0],
-							key->parttyplen[0],
-							datumCopy(*boundinfo->datums[i],
-									  key->parttypbyval[0],
-									  key->parttyplen[0]),
-							false,	/* isnull */
-							key->parttypbyval[0]);
-
-			elems = lappend(elems, val);
-		}
-	}
-	else
-	{
-		/*
-		 * Create list of Consts for the allowed values, excluding any nulls.
-		 */
-		foreach(cell, spec->listdatums)
-		{
-			Const	   *val = castNode(Const, lfirst(cell));
-
-			if (val->constisnull)
-				list_has_null = true;
-			else
-				elems = lappend(elems, copyObject(val));
-		}
-	}
-
-	if (elems)
-	{
-		/*
-		 * Generate the operator expression from the non-null partition
-		 * values.
-		 */
-		opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-										keyCol, (Expr *) elems);
-	}
-	else
-	{
-		/*
-		 * If there are no partition values, we don't need an operator
-		 * expression.
-		 */
-		opexpr = NULL;
-	}
-
-	if (!list_has_null)
-	{
-		/*
-		 * Gin up a "col IS NOT NULL" test that will be AND'd with the main
-		 * expression.  This might seem redundant, but the partition routing
-		 * machinery needs it.
-		 */
-		nulltest = makeNode(NullTest);
-		nulltest->arg = keyCol;
-		nulltest->nulltesttype = IS_NOT_NULL;
-		nulltest->argisrow = false;
-		nulltest->location = -1;
-
-		result = opexpr ? list_make2(nulltest, opexpr) : list_make1(nulltest);
-	}
-	else
-	{
-		/*
-		 * Gin up a "col IS NULL" test that will be OR'd with the main
-		 * expression.
-		 */
-		nulltest = makeNode(NullTest);
-		nulltest->arg = keyCol;
-		nulltest->nulltesttype = IS_NULL;
-		nulltest->argisrow = false;
-		nulltest->location = -1;
-
-		if (opexpr)
-		{
-			Expr	   *or;
-
-			or = makeBoolExpr(OR_EXPR, list_make2(nulltest, opexpr), -1);
-			result = list_make1(or);
-		}
-		else
-			result = list_make1(nulltest);
-	}
-
-	/*
-	 * Note that, in general, applying NOT to a constraint expression doesn't
-	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
-	 * NULL.  However, the partition constraints we construct here never
-	 * evaluate to NULL, so applying NOT works as intended.
-	 */
-	if (spec->is_default)
-	{
-		result = list_make1(make_ands_explicit(result));
-		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
-	}
-
-	return result;
-}
-
-/*
- * get_range_key_properties
- *		Returns range partition key information for a given column
- *
- * This is a subroutine for get_qual_for_range, and its API is pretty
- * specialized to that caller.
- *
- * Constructs an Expr for the key column (returned in *keyCol) and Consts
- * for the lower and upper range limits (returned in *lower_val and
- * *upper_val).  For MINVALUE/MAXVALUE limits, NULL is returned instead of
- * a Const.  All of these structures are freshly palloc'd.
- *
- * *partexprs_item points to the cell containing the next expression in
- * the key->partexprs list, or NULL.  It may be advanced upon return.
- */
-static void
-get_range_key_properties(PartitionKey key, int keynum,
-						 PartitionRangeDatum *ldatum,
-						 PartitionRangeDatum *udatum,
-						 ListCell **partexprs_item,
-						 Expr **keyCol,
-						 Const **lower_val, Const **upper_val)
-{
-	/* Get partition key expression for this column */
-	if (key->partattrs[keynum] != 0)
-	{
-		*keyCol = (Expr *) makeVar(1,
-								   key->partattrs[keynum],
-								   key->parttypid[keynum],
-								   key->parttypmod[keynum],
-								   key->parttypcoll[keynum],
-								   0);
-	}
-	else
-	{
-		if (*partexprs_item == NULL)
-			elog(ERROR, "wrong number of partition key expressions");
-		*keyCol = copyObject(lfirst(*partexprs_item));
-		*partexprs_item = lnext(*partexprs_item);
-	}
-
-	/* Get appropriate Const nodes for the bounds */
-	if (ldatum->kind == PARTITION_RANGE_DATUM_VALUE)
-		*lower_val = castNode(Const, copyObject(ldatum->value));
-	else
-		*lower_val = NULL;
-
-	if (udatum->kind == PARTITION_RANGE_DATUM_VALUE)
-		*upper_val = castNode(Const, copyObject(udatum->value));
-	else
-		*upper_val = NULL;
-}
-
- /*
-  * get_range_nulltest
-  *
-  * A non-default range partition table does not currently allow partition
-  * keys to be null, so emit an IS NOT NULL expression for each key column.
-  */
-static List *
-get_range_nulltest(PartitionKey key)
-{
-	List	   *result = NIL;
-	NullTest   *nulltest;
-	ListCell   *partexprs_item;
-	int			i;
-
-	partexprs_item = list_head(key->partexprs);
-	for (i = 0; i < key->partnatts; i++)
-	{
-		Expr	   *keyCol;
-
-		if (key->partattrs[i] != 0)
-		{
-			keyCol = (Expr *) makeVar(1,
-									  key->partattrs[i],
-									  key->parttypid[i],
-									  key->parttypmod[i],
-									  key->parttypcoll[i],
-									  0);
-		}
-		else
-		{
-			if (partexprs_item == NULL)
-				elog(ERROR, "wrong number of partition key expressions");
-			keyCol = copyObject(lfirst(partexprs_item));
-			partexprs_item = lnext(partexprs_item);
-		}
-
-		nulltest = makeNode(NullTest);
-		nulltest->arg = keyCol;
-		nulltest->nulltesttype = IS_NOT_NULL;
-		nulltest->argisrow = false;
-		nulltest->location = -1;
-		result = lappend(result, nulltest);
-	}
-
-	return result;
-}
-
-/*
- * get_qual_for_range
- *
- * Returns an implicit-AND list of expressions to use as a range partition's
- * constraint, given the parent relation and partition bound structure.
- *
- * For a multi-column range partition key, say (a, b, c), with (al, bl, cl)
- * as the lower bound tuple and (au, bu, cu) as the upper bound tuple, we
- * generate an expression tree of the following form:
- *
- *	(a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
- *		AND
- *	(a > al OR (a = al AND b > bl) OR (a = al AND b = bl AND c >= cl))
- *		AND
- *	(a < au OR (a = au AND b < bu) OR (a = au AND b = bu AND c < cu))
- *
- * It is often the case that a prefix of lower and upper bound tuples contains
- * the same values, for example, (al = au), in which case, we will emit an
- * expression tree of the following form:
- *
- *	(a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
- *		AND
- *	(a = al)
- *		AND
- *	(b > bl OR (b = bl AND c >= cl))
- *		AND
- *	(b < bu) OR (b = bu AND c < cu))
- *
- * If a bound datum is either MINVALUE or MAXVALUE, these expressions are
- * simplified using the fact that any value is greater than MINVALUE and less
- * than MAXVALUE. So, for example, if cu = MAXVALUE, c < cu is automatically
- * true, and we need not emit any expression for it, and the last line becomes
- *
- *	(b < bu) OR (b = bu), which is simplified to (b <= bu)
- *
- * In most common cases with only one partition column, say a, the following
- * expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
- *
- * For default partition, it returns the negation of the constraints of all
- * the other partitions.
- *
- * External callers should pass for_default as false; we set it to true only
- * when recursing.
- */
-static List *
-get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
-				   bool for_default)
-{
-	List	   *result = NIL;
-	ListCell   *cell1,
-			   *cell2,
-			   *partexprs_item,
-			   *partexprs_item_saved;
-	int			i,
-				j;
-	PartitionRangeDatum *ldatum,
-			   *udatum;
-	PartitionKey key = RelationGetPartitionKey(parent);
-	Expr	   *keyCol;
-	Const	   *lower_val,
-			   *upper_val;
-	List	   *lower_or_arms,
-			   *upper_or_arms;
-	int			num_or_arms,
-				current_or_arm;
-	ListCell   *lower_or_start_datum,
-			   *upper_or_start_datum;
-	bool		need_next_lower_arm,
-				need_next_upper_arm;
-
-	if (spec->is_default)
-	{
-		List	   *or_expr_args = NIL;
-		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
-		Oid		   *inhoids = pdesc->oids;
-		int			nparts = pdesc->nparts,
-					i;
-
-		for (i = 0; i < nparts; i++)
-		{
-			Oid			inhrelid = inhoids[i];
-			HeapTuple	tuple;
-			Datum		datum;
-			bool		isnull;
-			PartitionBoundSpec *bspec;
-
-			tuple = SearchSysCache1(RELOID, inhrelid);
-			if (!HeapTupleIsValid(tuple))
-				elog(ERROR, "cache lookup failed for relation %u", inhrelid);
-
-			datum = SysCacheGetAttr(RELOID, tuple,
-									Anum_pg_class_relpartbound,
-									&isnull);
-
-			Assert(!isnull);
-			bspec = (PartitionBoundSpec *)
-				stringToNode(TextDatumGetCString(datum));
-			if (!IsA(bspec, PartitionBoundSpec))
-				elog(ERROR, "expected PartitionBoundSpec");
-
-			if (!bspec->is_default)
-			{
-				List	   *part_qual;
-
-				part_qual = get_qual_for_range(parent, bspec, true);
-
-				/*
-				 * AND the constraints of the partition and add to
-				 * or_expr_args
-				 */
-				or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
-									   ? makeBoolExpr(AND_EXPR, part_qual, -1)
-									   : linitial(part_qual));
-			}
-			ReleaseSysCache(tuple);
-		}
-
-		if (or_expr_args != NIL)
-		{
-			Expr	   *other_parts_constr;
-
-			/*
-			 * Combine the constraints obtained for non-default partitions
-			 * using OR.  As requested, each of the OR's args doesn't include
-			 * the NOT NULL test for partition keys (which is to avoid its
-			 * useless repetition).  Add the same now.
-			 */
-			other_parts_constr =
-				makeBoolExpr(AND_EXPR,
-							 lappend(get_range_nulltest(key),
-									 list_length(or_expr_args) > 1
-									 ? makeBoolExpr(OR_EXPR, or_expr_args,
-													-1)
-									 : linitial(or_expr_args)),
-							 -1);
-
-			/*
-			 * Finally, the default partition contains everything *NOT*
-			 * contained in the non-default partitions.
-			 */
-			result = list_make1(makeBoolExpr(NOT_EXPR,
-											 list_make1(other_parts_constr), -1));
-		}
-
-		return result;
-	}
-
-	lower_or_start_datum = list_head(spec->lowerdatums);
-	upper_or_start_datum = list_head(spec->upperdatums);
-	num_or_arms = key->partnatts;
-
-	/*
-	 * If it is the recursive call for default, we skip the get_range_nulltest
-	 * to avoid accumulating the NullTest on the same keys for each partition.
-	 */
-	if (!for_default)
-		result = get_range_nulltest(key);
-
-	/*
-	 * Iterate over the key columns and check if the corresponding lower and
-	 * upper datums are equal using the btree equality operator for the
-	 * column's type.  If equal, we emit single keyCol = common_value
-	 * expression.  Starting from the first column for which the corresponding
-	 * lower and upper bound datums are not equal, we generate OR expressions
-	 * as shown in the function's header comment.
-	 */
-	i = 0;
-	partexprs_item = list_head(key->partexprs);
-	partexprs_item_saved = partexprs_item;	/* placate compiler */
-	forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums)
-	{
-		EState	   *estate;
-		MemoryContext oldcxt;
-		Expr	   *test_expr;
-		ExprState  *test_exprstate;
-		Datum		test_result;
-		bool		isNull;
-
-		ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
-		udatum = castNode(PartitionRangeDatum, lfirst(cell2));
-
-		/*
-		 * Since get_range_key_properties() modifies partexprs_item, and we
-		 * might need to start over from the previous expression in the later
-		 * part of this function, save away the current value.
-		 */
-		partexprs_item_saved = partexprs_item;
-
-		get_range_key_properties(key, i, ldatum, udatum,
-								 &partexprs_item,
-								 &keyCol,
-								 &lower_val, &upper_val);
-
-		/*
-		 * If either value is NULL, the corresponding partition bound is
-		 * either MINVALUE or MAXVALUE, and we treat them as unequal, because
-		 * even if they're the same, there is no common value to equate the
-		 * key column with.
-		 */
-		if (!lower_val || !upper_val)
-			break;
-
-		/* Create the test expression */
-		estate = CreateExecutorState();
-		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
-		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
-										   (Expr *) lower_val,
-										   (Expr *) upper_val);
-		fix_opfuncids((Node *) test_expr);
-		test_exprstate = ExecInitExpr(test_expr, NULL);
-		test_result = ExecEvalExprSwitchContext(test_exprstate,
-												GetPerTupleExprContext(estate),
-												&isNull);
-		MemoryContextSwitchTo(oldcxt);
-		FreeExecutorState(estate);
-
-		/* If not equal, go generate the OR expressions */
-		if (!DatumGetBool(test_result))
-			break;
-
-		/*
-		 * The bounds for the last key column can't be equal, because such a
-		 * range partition would never be allowed to be defined (it would have
-		 * an empty range otherwise).
-		 */
-		if (i == key->partnatts - 1)
-			elog(ERROR, "invalid range bound specification");
-
-		/* Equal, so generate keyCol = lower_val expression */
-		result = lappend(result,
-						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
-												keyCol, (Expr *) lower_val));
-
-		i++;
-	}
-
-	/* First pair of lower_val and upper_val that are not equal. */
-	lower_or_start_datum = cell1;
-	upper_or_start_datum = cell2;
-
-	/* OR will have as many arms as there are key columns left. */
-	num_or_arms = key->partnatts - i;
-	current_or_arm = 0;
-	lower_or_arms = upper_or_arms = NIL;
-	need_next_lower_arm = need_next_upper_arm = true;
-	while (current_or_arm < num_or_arms)
-	{
-		List	   *lower_or_arm_args = NIL,
-				   *upper_or_arm_args = NIL;
-
-		/* Restart scan of columns from the i'th one */
-		j = i;
-		partexprs_item = partexprs_item_saved;
-
-		for_both_cell(cell1, lower_or_start_datum, cell2, upper_or_start_datum)
-		{
-			PartitionRangeDatum *ldatum_next = NULL,
-					   *udatum_next = NULL;
-
-			ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
-			if (lnext(cell1))
-				ldatum_next = castNode(PartitionRangeDatum,
-									   lfirst(lnext(cell1)));
-			udatum = castNode(PartitionRangeDatum, lfirst(cell2));
-			if (lnext(cell2))
-				udatum_next = castNode(PartitionRangeDatum,
-									   lfirst(lnext(cell2)));
-			get_range_key_properties(key, j, ldatum, udatum,
-									 &partexprs_item,
-									 &keyCol,
-									 &lower_val, &upper_val);
-
-			if (need_next_lower_arm && lower_val)
-			{
-				uint16		strategy;
-
-				/*
-				 * For the non-last columns of this arm, use the EQ operator.
-				 * For the last column of this arm, use GT, unless this is the
-				 * last column of the whole bound check, or the next bound
-				 * datum is MINVALUE, in which case use GE.
-				 */
-				if (j - i < current_or_arm)
-					strategy = BTEqualStrategyNumber;
-				else if (j == key->partnatts - 1 ||
-						 (ldatum_next &&
-						  ldatum_next->kind == PARTITION_RANGE_DATUM_MINVALUE))
-					strategy = BTGreaterEqualStrategyNumber;
-				else
-					strategy = BTGreaterStrategyNumber;
-
-				lower_or_arm_args = lappend(lower_or_arm_args,
-											make_partition_op_expr(key, j,
-																   strategy,
-																   keyCol,
-																   (Expr *) lower_val));
-			}
-
-			if (need_next_upper_arm && upper_val)
-			{
-				uint16		strategy;
-
-				/*
-				 * For the non-last columns of this arm, use the EQ operator.
-				 * For the last column of this arm, use LT, unless the next
-				 * bound datum is MAXVALUE, in which case use LE.
-				 */
-				if (j - i < current_or_arm)
-					strategy = BTEqualStrategyNumber;
-				else if (udatum_next &&
-						 udatum_next->kind == PARTITION_RANGE_DATUM_MAXVALUE)
-					strategy = BTLessEqualStrategyNumber;
-				else
-					strategy = BTLessStrategyNumber;
-
-				upper_or_arm_args = lappend(upper_or_arm_args,
-											make_partition_op_expr(key, j,
-																   strategy,
-																   keyCol,
-																   (Expr *) upper_val));
-			}
-
-			/*
-			 * Did we generate enough of OR's arguments?  First arm considers
-			 * the first of the remaining columns, second arm considers first
-			 * two of the remaining columns, and so on.
-			 */
-			++j;
-			if (j - i > current_or_arm)
-			{
-				/*
-				 * We must not emit any more arms if the new column that will
-				 * be considered is unbounded, or this one was.
-				 */
-				if (!lower_val || !ldatum_next ||
-					ldatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
-					need_next_lower_arm = false;
-				if (!upper_val || !udatum_next ||
-					udatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
-					need_next_upper_arm = false;
-				break;
-			}
-		}
-
-		if (lower_or_arm_args != NIL)
-			lower_or_arms = lappend(lower_or_arms,
-									list_length(lower_or_arm_args) > 1
-									? makeBoolExpr(AND_EXPR, lower_or_arm_args, -1)
-									: linitial(lower_or_arm_args));
-
-		if (upper_or_arm_args != NIL)
-			upper_or_arms = lappend(upper_or_arms,
-									list_length(upper_or_arm_args) > 1
-									? makeBoolExpr(AND_EXPR, upper_or_arm_args, -1)
-									: linitial(upper_or_arm_args));
-
-		/* If no work to do in the next iteration, break away. */
-		if (!need_next_lower_arm && !need_next_upper_arm)
-			break;
-
-		++current_or_arm;
-	}
-
-	/*
-	 * Generate the OR expressions for each of lower and upper bounds (if
-	 * required), and append to the list of implicitly ANDed list of
-	 * expressions.
-	 */
-	if (lower_or_arms != NIL)
-		result = lappend(result,
-						 list_length(lower_or_arms) > 1
-						 ? makeBoolExpr(OR_EXPR, lower_or_arms, -1)
-						 : linitial(lower_or_arms));
-	if (upper_or_arms != NIL)
-		result = lappend(result,
-						 list_length(upper_or_arms) > 1
-						 ? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
-						 : linitial(upper_or_arms));
-
-	/*
-	 * As noted above, for non-default, we return list with constant TRUE. If
-	 * the result is NIL during the recursive call for default, it implies
-	 * this is the only other partition which can hold every value of the key
-	 * except NULL. Hence we return the NullTest result skipped earlier.
-	 */
-	if (result == NIL)
-		result = for_default
-			? get_range_nulltest(key)
-			: list_make1(makeBoolConst(true, false));
-
-	return result;
-}
-
-/*
- * generate_partition_qual
- *
- * Generate partition predicate from rel's partition bound expression. The
- * function returns a NIL list if there is no predicate.
- *
- * Result expression tree is stored CacheMemoryContext to ensure it survives
- * as long as the relcache entry. But we should be running in a less long-lived
- * working context. To avoid leaking cache memory if this routine fails partway
- * through, we build in working memory and then copy the completed structure
- * into cache memory.
- */
-static List *
-generate_partition_qual(Relation rel)
-{
-	HeapTuple	tuple;
-	MemoryContext oldcxt;
-	Datum		boundDatum;
-	bool		isnull;
-	PartitionBoundSpec *bound;
-	List	   *my_qual = NIL,
-			   *result = NIL;
-	Relation	parent;
-	bool		found_whole_row;
-
-	/* Guard against stack overflow due to overly deep partition tree */
-	check_stack_depth();
-
-	/* Quick copy */
-	if (rel->rd_partcheck != NIL)
-		return copyObject(rel->rd_partcheck);
-
-	/* Grab at least an AccessShareLock on the parent table */
-	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
-					   AccessShareLock);
-
-	/* Get pg_class.relpartbound */
-	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
-	if (!HeapTupleIsValid(tuple))
-		elog(ERROR, "cache lookup failed for relation %u",
-			 RelationGetRelid(rel));
-
-	boundDatum = SysCacheGetAttr(RELOID, tuple,
-								 Anum_pg_class_relpartbound,
-								 &isnull);
-	if (isnull)					/* should not happen */
-		elog(ERROR, "relation \"%s\" has relpartbound = null",
-			 RelationGetRelationName(rel));
-	bound = castNode(PartitionBoundSpec,
-					 stringToNode(TextDatumGetCString(boundDatum)));
-	ReleaseSysCache(tuple);
-
-	my_qual = get_qual_from_partbound(rel, parent, bound);
-
-	/* Add the parent's quals to the list (if any) */
-	if (parent->rd_rel->relispartition)
-		result = list_concat(generate_partition_qual(parent), my_qual);
-	else
-		result = my_qual;
-
-	/*
-	 * Change Vars to have partition's attnos instead of the parent's. We do
-	 * this after we concatenate the parent's quals, because we want every Var
-	 * in it to bear this relation's attnos. It's safe to assume varno = 1
-	 * here.
-	 */
-	result = map_partition_varattnos(result, 1, rel, parent,
-									 &found_whole_row);
-	/* There can never be a whole-row reference here */
-	if (found_whole_row)
-		elog(ERROR, "unexpected whole-row reference found in partition key");
-
-	/* Save a copy in the relcache */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
-	rel->rd_partcheck = copyObject(result);
-	MemoryContextSwitchTo(oldcxt);
-
-	/* Keep the parent locked until commit */
-	heap_close(parent, NoLock);
-
-	return result;
-}
-
-/*
- * get_partition_for_tuple
- *		Finds partition of relation which accepts the partition key specified
- *		in values and isnull
- *
- * Return value is index of the partition (>= 0 and < partdesc->nparts) if one
- * found or -1 if none found.
- */
-int
-get_partition_for_tuple(Relation relation, Datum *values, bool *isnull)
-{
-	int			bound_offset;
-	int			part_index = -1;
-	PartitionKey key = RelationGetPartitionKey(relation);
-	PartitionDesc partdesc = RelationGetPartitionDesc(relation);
-
-	/* Route as appropriate based on partitioning strategy. */
-	switch (key->strategy)
-	{
-		case PARTITION_STRATEGY_HASH:
-			{
-				PartitionBoundInfo boundinfo = partdesc->boundinfo;
-				int			greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
-				uint64		rowHash = compute_hash_value(key->partnatts,
-														 key->partsupfunc,
-														 values, isnull);
-
-				part_index = boundinfo->indexes[rowHash % greatest_modulus];
-			}
-			break;
-
-		case PARTITION_STRATEGY_LIST:
-			if (isnull[0])
-			{
-				if (partition_bound_accepts_nulls(partdesc->boundinfo))
-					part_index = partdesc->boundinfo->null_index;
-			}
-			else
-			{
-				bool		equal = false;
-
-				bound_offset = partition_list_bsearch(key->partsupfunc,
-													  key->partcollation,
-													  partdesc->boundinfo,
-													  values[0], &equal);
-				if (bound_offset >= 0 && equal)
-					part_index = partdesc->boundinfo->indexes[bound_offset];
-			}
-			break;
-
-		case PARTITION_STRATEGY_RANGE:
-			{
-				bool		equal = false,
-							range_partkey_has_null = false;
-				int			i;
-
-				/*
-				 * No range includes NULL, so this will be accepted by the
-				 * default partition if there is one, and otherwise rejected.
-				 */
-				for (i = 0; i < key->partnatts; i++)
-				{
-					if (isnull[i])
-					{
-						range_partkey_has_null = true;
-						break;
-					}
-				}
-
-				if (!range_partkey_has_null)
-				{
-					bound_offset = partition_range_datum_bsearch(key->partsupfunc,
-																 key->partcollation,
-																 partdesc->boundinfo,
-																 key->partnatts,
-																 values,
-																 &equal);
-
-					/*
-					 * The bound at bound_offset is less than or equal to the
-					 * tuple value, so the bound at offset+1 is the upper
-					 * bound of the partition we're looking for, if there
-					 * actually exists one.
-					 */
-					part_index = partdesc->boundinfo->indexes[bound_offset + 1];
-				}
-			}
-			break;
-
-		default:
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) key->strategy);
-	}
-
-	/*
-	 * part_index < 0 means we failed to find a partition of this parent. Use
-	 * the default partition, if there is one.
-	 */
-	if (part_index < 0)
-		part_index = partdesc->boundinfo->default_index;
-
-	return part_index;
-}
-
 /*
  * Checks if any of the 'attnums' is a partition key attribute for rel
  *
@@ -2635,7 +224,7 @@ bool
 has_partition_attrs(Relation rel, Bitmapset *attnums,
 					bool *used_in_expr)
 {
-	PartitionKey key;
+	PartitionKey	key;
 	int			partnatts;
 	List	   *partexprs;
 	ListCell   *partexprs_item;
@@ -2685,394 +274,6 @@ has_partition_attrs(Relation rel, Bitmapset *attnums,
 	return false;
 }
 
-/*
- * qsort_partition_hbound_cmp
- *
- * We sort hash bounds by modulus, then by remainder.
- */
-static int32
-qsort_partition_hbound_cmp(const void *a, const void *b)
-{
-	PartitionHashBound *h1 = (*(PartitionHashBound *const *) a);
-	PartitionHashBound *h2 = (*(PartitionHashBound *const *) b);
-
-	return partition_hbound_cmp(h1->modulus, h1->remainder,
-								h2->modulus, h2->remainder);
-}
-
-/*
- * partition_hbound_cmp
- *
- * Compares modulus first, then remainder if modulus are equal.
- */
-static int32
-partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2)
-{
-	if (modulus1 < modulus2)
-		return -1;
-	if (modulus1 > modulus2)
-		return 1;
-	if (modulus1 == modulus2 && remainder1 != remainder2)
-		return (remainder1 > remainder2) ? 1 : -1;
-	return 0;
-}
-
-/*
- * qsort_partition_list_value_cmp
- *
- * Compare two list partition bound datums
- */
-static int32
-qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
-{
-	Datum		val1 = (*(const PartitionListValue **) a)->value,
-				val2 = (*(const PartitionListValue **) b)->value;
-	PartitionKey key = (PartitionKey) arg;
-
-	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
-										   key->partcollation[0],
-										   val1, val2));
-}
-
-/*
- * make_one_range_bound
- *
- * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
- * and a flag telling whether the bound is lower or not.  Made into a function
- * because there are multiple sites that want to use this facility.
- */
-static PartitionRangeBound *
-make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
-{
-	PartitionRangeBound *bound;
-	ListCell   *lc;
-	int			i;
-
-	Assert(datums != NIL);
-
-	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
-	bound->index = index;
-	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
-	bound->kind = (PartitionRangeDatumKind *) palloc0(key->partnatts *
-													  sizeof(PartitionRangeDatumKind));
-	bound->lower = lower;
-
-	i = 0;
-	foreach(lc, datums)
-	{
-		PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
-
-		/* What's contained in this range datum? */
-		bound->kind[i] = datum->kind;
-
-		if (datum->kind == PARTITION_RANGE_DATUM_VALUE)
-		{
-			Const	   *val = castNode(Const, datum->value);
-
-			if (val->constisnull)
-				elog(ERROR, "invalid range bound datum");
-			bound->datums[i] = val->constvalue;
-		}
-
-		i++;
-	}
-
-	return bound;
-}
-
-/* Used when sorting range bounds across all range partitions */
-static int32
-qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
-{
-	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
-	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
-	PartitionKey key = (PartitionKey) arg;
-
-	return partition_rbound_cmp(key->partnatts, key->partsupfunc,
-								key->partcollation, b1->datums, b1->kind,
-								b1->lower, b2);
-}
-
-/*
- * partition_rbound_cmp
- *
- * Return for two range bounds whether the 1st one (specified in datums1,
- * kind1, and lower1) is <, =, or > the bound specified in *b2.
- *
- * partnatts, partsupfunc and partcollation give the number of attributes in the
- * bounds to be compared, comparison function to be used and the collations of
- * attributes, respectively.
- *
- * Note that if the values of the two range bounds compare equal, then we take
- * into account whether they are upper or lower bounds, and an upper bound is
- * considered to be smaller than a lower bound. This is important to the way
- * that RelationBuildPartitionDesc() builds the PartitionBoundInfoData
- * structure, which only stores the upper bound of a common boundary between
- * two contiguous partitions.
- */
-static int32
-partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation,
-					 Datum *datums1, PartitionRangeDatumKind *kind1,
-					 bool lower1, PartitionRangeBound *b2)
-{
-	int32		cmpval = 0;		/* placate compiler */
-	int			i;
-	Datum	   *datums2 = b2->datums;
-	PartitionRangeDatumKind *kind2 = b2->kind;
-	bool		lower2 = b2->lower;
-
-	for (i = 0; i < partnatts; i++)
-	{
-		/*
-		 * First, handle cases where the column is unbounded, which should not
-		 * invoke the comparison procedure, and should not consider any later
-		 * columns. Note that the PartitionRangeDatumKind enum elements
-		 * compare the same way as the values they represent.
-		 */
-		if (kind1[i] < kind2[i])
-			return -1;
-		else if (kind1[i] > kind2[i])
-			return 1;
-		else if (kind1[i] != PARTITION_RANGE_DATUM_VALUE)
-
-			/*
-			 * The column bounds are both MINVALUE or both MAXVALUE. No later
-			 * columns should be considered, but we still need to compare
-			 * whether they are upper or lower bounds.
-			 */
-			break;
-
-		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
-												 partcollation[i],
-												 datums1[i],
-												 datums2[i]));
-		if (cmpval != 0)
-			break;
-	}
-
-	/*
-	 * If the comparison is anything other than equal, we're done. If they
-	 * compare equal though, we still have to consider whether the boundaries
-	 * are inclusive or exclusive.  Exclusive one is considered smaller of the
-	 * two.
-	 */
-	if (cmpval == 0 && lower1 != lower2)
-		cmpval = lower1 ? 1 : -1;
-
-	return cmpval;
-}
-
-/*
- * partition_rbound_datum_cmp
- *
- * Return whether range bound (specified in rb_datums, rb_kind, and rb_lower)
- * is <, =, or > partition key of tuple (tuple_datums)
- *
- * n_tuple_datums, partsupfunc and partcollation give number of attributes in
- * the bounds to be compared, comparison function to be used and the collations
- * of attributes resp.
- *
- */
-int32
-partition_rbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation,
-						   Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
-						   Datum *tuple_datums, int n_tuple_datums)
-{
-	int			i;
-	int32		cmpval = -1;
-
-	for (i = 0; i < n_tuple_datums; i++)
-	{
-		if (rb_kind[i] == PARTITION_RANGE_DATUM_MINVALUE)
-			return -1;
-		else if (rb_kind[i] == PARTITION_RANGE_DATUM_MAXVALUE)
-			return 1;
-
-		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
-												 partcollation[i],
-												 rb_datums[i],
-												 tuple_datums[i]));
-		if (cmpval != 0)
-			break;
-	}
-
-	return cmpval;
-}
-
-/*
- * partition_list_bsearch
- *		Returns the index of the greatest bound datum that is less than equal
- * 		to the given value or -1 if all of the bound datums are greater
- *
- * *is_equal is set to true if the bound datum at the returned index is equal
- * to the input value.
- */
-int
-partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
-					   PartitionBoundInfo boundinfo,
-					   Datum value, bool *is_equal)
-{
-	int			lo,
-				hi,
-				mid;
-
-	lo = -1;
-	hi = boundinfo->ndatums - 1;
-	while (lo < hi)
-	{
-		int32		cmpval;
-
-		mid = (lo + hi + 1) / 2;
-		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
-												 partcollation[0],
-												 boundinfo->datums[mid][0],
-												 value));
-		if (cmpval <= 0)
-		{
-			lo = mid;
-			*is_equal = (cmpval == 0);
-			if (*is_equal)
-				break;
-		}
-		else
-			hi = mid - 1;
-	}
-
-	return lo;
-}
-
-/*
- * partition_range_bsearch
- *		Returns the index of the greatest range bound that is less than or
- *		equal to the given range bound or -1 if all of the range bounds are
- *		greater
- *
- * *is_equal is set to true if the range bound at the returned index is equal
- * to the input range bound
- */
-int
-partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
-						Oid *partcollation,
-						PartitionBoundInfo boundinfo,
-						PartitionRangeBound *probe, bool *is_equal)
-{
-	int			lo,
-				hi,
-				mid;
-
-	lo = -1;
-	hi = boundinfo->ndatums - 1;
-	while (lo < hi)
-	{
-		int32		cmpval;
-
-		mid = (lo + hi + 1) / 2;
-		cmpval = partition_rbound_cmp(partnatts, partsupfunc, partcollation,
-									  boundinfo->datums[mid],
-									  boundinfo->kind[mid],
-									  (boundinfo->indexes[mid] == -1),
-									  probe);
-		if (cmpval <= 0)
-		{
-			lo = mid;
-			*is_equal = (cmpval == 0);
-
-			if (*is_equal)
-				break;
-		}
-		else
-			hi = mid - 1;
-	}
-
-	return lo;
-}
-
-/*
- * partition_range_bsearch
- *		Returns the index of the greatest range bound that is less than or
- *		equal to the given tuple or -1 if all of the range bounds are greater
- *
- * *is_equal is set to true if the range bound at the returned index is equal
- * to the input tuple.
- */
-int
-partition_range_datum_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
-							  PartitionBoundInfo boundinfo,
-							  int nvalues, Datum *values, bool *is_equal)
-{
-	int			lo,
-				hi,
-				mid;
-
-	lo = -1;
-	hi = boundinfo->ndatums - 1;
-	while (lo < hi)
-	{
-		int32		cmpval;
-
-		mid = (lo + hi + 1) / 2;
-		cmpval = partition_rbound_datum_cmp(partsupfunc,
-											partcollation,
-											boundinfo->datums[mid],
-											boundinfo->kind[mid],
-											values,
-											nvalues);
-		if (cmpval <= 0)
-		{
-			lo = mid;
-			*is_equal = (cmpval == 0);
-
-			if (*is_equal)
-				break;
-		}
-		else
-			hi = mid - 1;
-	}
-
-	return lo;
-}
-
-/*
- * partition_hash_bsearch
- *		Returns the index of the greatest (modulus, remainder) pair that is
- *		less than or equal to the given (modulus, remainder) pair or -1 if
- *		all of them are greater
- */
-int
-partition_hash_bsearch(PartitionBoundInfo boundinfo,
-					   int modulus, int remainder)
-{
-	int			lo,
-				hi,
-				mid;
-
-	lo = -1;
-	hi = boundinfo->ndatums - 1;
-	while (lo < hi)
-	{
-		int32		cmpval,
-					bound_modulus,
-					bound_remainder;
-
-		mid = (lo + hi + 1) / 2;
-		bound_modulus = DatumGetInt32(boundinfo->datums[mid][0]);
-		bound_remainder = DatumGetInt32(boundinfo->datums[mid][1]);
-		cmpval = partition_hbound_cmp(bound_modulus, bound_remainder,
-									  modulus, remainder);
-		if (cmpval <= 0)
-		{
-			lo = mid;
-
-			if (cmpval == 0)
-				break;
-		}
-		else
-			hi = mid - 1;
-	}
-
-	return lo;
-}
-
 /*
  * get_default_oid_from_partdesc
  *
@@ -3176,63 +377,6 @@ get_proposed_default_constraint(List *new_part_constraints)
 	return make_ands_implicit(defPartConstraint);
 }
 
-/*
- * get_partition_bound_num_indexes
- *
- * Returns the number of the entries in the partition bound indexes array.
- */
-static int
-get_partition_bound_num_indexes(PartitionBoundInfo bound)
-{
-	int			num_indexes;
-
-	Assert(bound);
-
-	switch (bound->strategy)
-	{
-		case PARTITION_STRATEGY_HASH:
-
-			/*
-			 * The number of the entries in the indexes array is same as the
-			 * greatest modulus.
-			 */
-			num_indexes = get_hash_partition_greatest_modulus(bound);
-			break;
-
-		case PARTITION_STRATEGY_LIST:
-			num_indexes = bound->ndatums;
-			break;
-
-		case PARTITION_STRATEGY_RANGE:
-			/* Range partitioned table has an extra index. */
-			num_indexes = bound->ndatums + 1;
-			break;
-
-		default:
-			elog(ERROR, "unexpected partition strategy: %d",
-				 (int) bound->strategy);
-	}
-
-	return num_indexes;
-}
-
-/*
- * get_hash_partition_greatest_modulus
- *
- * Returns the greatest modulus of the hash partition bound. The greatest
- * modulus will be at the end of the datums array because hash partitions are
- * arranged in the ascending order of their modulus and remainders.
- */
-int
-get_hash_partition_greatest_modulus(PartitionBoundInfo bound)
-{
-	Assert(bound && bound->strategy == PARTITION_STRATEGY_HASH);
-	Assert(bound->datums && bound->ndatums > 0);
-	Assert(DatumGetInt32(bound->datums[bound->ndatums - 1][0]) > 0);
-
-	return DatumGetInt32(bound->datums[bound->ndatums - 1][0]);
-}
-
 /*
  * compute_hash_value
  *
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f27684f96e..c1a9bda433 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -77,6 +77,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "partitioning/partbounds.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -92,6 +93,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/partcache.h"
 #include "utils/relcache.h"
 #include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 11139f743d..1bf4412be5 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -35,6 +35,8 @@ static void FormPartitionKeyDatum(PartitionDispatch pd,
 					  EState *estate,
 					  Datum *values,
 					  bool *isnull);
+static int get_partition_for_tuple(Relation relation, Datum *values,
+					  bool *isnull);
 static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 Datum *values,
 									 bool *isnull,
@@ -1022,6 +1024,110 @@ FormPartitionKeyDatum(PartitionDispatch pd,
 		elog(ERROR, "wrong number of partition key expressions");
 }
 
+/*
+ * get_partition_for_tuple
+ *		Finds partition of relation which accepts the partition key specified
+ *		in values and isnull
+ *
+ * Return value is index of the partition (>= 0 and < partdesc->nparts) if one
+ * found or -1 if none found.
+ */
+int
+get_partition_for_tuple(Relation relation, Datum *values, bool *isnull)
+{
+	int			bound_offset;
+	int			part_index = -1;
+	PartitionKey key = RelationGetPartitionKey(relation);
+	PartitionDesc partdesc = RelationGetPartitionDesc(relation);
+
+	/* Route as appropriate based on partitioning strategy. */
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+			{
+				PartitionBoundInfo boundinfo = partdesc->boundinfo;
+				int			greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
+				uint64		rowHash = compute_hash_value(key->partnatts,
+														 key->partsupfunc,
+														 values, isnull);
+
+				part_index = boundinfo->indexes[rowHash % greatest_modulus];
+			}
+			break;
+
+		case PARTITION_STRATEGY_LIST:
+			if (isnull[0])
+			{
+				if (partition_bound_accepts_nulls(partdesc->boundinfo))
+					part_index = partdesc->boundinfo->null_index;
+			}
+			else
+			{
+				bool		equal = false;
+
+				bound_offset = partition_list_bsearch(key->partsupfunc,
+													  key->partcollation,
+													  partdesc->boundinfo,
+													  values[0], &equal);
+				if (bound_offset >= 0 && equal)
+					part_index = partdesc->boundinfo->indexes[bound_offset];
+			}
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			{
+				bool		equal = false,
+							range_partkey_has_null = false;
+				int			i;
+
+				/*
+				 * No range includes NULL, so this will be accepted by the
+				 * default partition if there is one, and otherwise rejected.
+				 */
+				for (i = 0; i < key->partnatts; i++)
+				{
+					if (isnull[i])
+					{
+						range_partkey_has_null = true;
+						break;
+					}
+				}
+
+				if (!range_partkey_has_null)
+				{
+					bound_offset = partition_range_datum_bsearch(key->partsupfunc,
+																 key->partcollation,
+																 partdesc->boundinfo,
+																 key->partnatts,
+																 values,
+																 &equal);
+
+					/*
+					 * The bound at bound_offset is less than or equal to the
+					 * tuple value, so the bound at offset+1 is the upper
+					 * bound of the partition we're looking for, if there
+					 * actually exists one.
+					 */
+					part_index = partdesc->boundinfo->indexes[bound_offset + 1];
+				}
+			}
+			break;
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) key->strategy);
+	}
+
+	/*
+	 * part_index < 0 means we failed to find a partition of this parent. Use
+	 * the default partition, if there is one.
+	 */
+	if (part_index < 0)
+		part_index = partdesc->boundinfo->default_index;
+
+	return part_index;
+}
+
 /*
  * ExecBuildSlotPartitionKeyDescription
  *
diff --git a/src/backend/partitioning/Makefile b/src/backend/partitioning/Makefile
index 429207c4eb..278fac3afa 100644
--- a/src/backend/partitioning/Makefile
+++ b/src/backend/partitioning/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/partitioning
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = partprune.o
+OBJS = partprune.o partbounds.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
new file mode 100644
index 0000000000..5c4fc90341
--- /dev/null
+++ b/src/backend/partitioning/partbounds.c
@@ -0,0 +1,2055 @@
+/*-------------------------------------------------------------------------
+ *
+ * partbounds.c
+ *		Support routines for manipulating partition bounds
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		  src/backend/partitioning/partbounds.c
+ *
+ *-------------------------------------------------------------------------
+*/
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "access/nbtree.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
+#include "partitioning/partprune.h"
+#include "partitioning/partbounds.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+static int	get_partition_bound_num_indexes(PartitionBoundInfo b);
+static Expr *make_partition_op_expr(PartitionKey key, int keynum,
+					   uint16 strategy, Expr *arg1, Expr *arg2);
+static Oid get_partition_operator(PartitionKey key, int col,
+					   StrategyNumber strategy, bool *need_relabel);
+static List *get_qual_for_hash(Relation parent, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+				   bool for_default);
+static void get_range_key_properties(PartitionKey key, int keynum,
+						 PartitionRangeDatum *ldatum,
+						 PartitionRangeDatum *udatum,
+						 ListCell **partexprs_item,
+						 Expr **keyCol,
+						 Const **lower_val, Const **upper_val);
+static List *get_range_nulltest(PartitionKey key);
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent,
+						PartitionBoundSpec *spec)
+{
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	List	   *my_qual = NIL;
+
+	Assert(key != NULL);
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+			Assert(spec->strategy == PARTITION_STRATEGY_HASH);
+			my_qual = get_qual_for_hash(parent, spec);
+			break;
+
+		case PARTITION_STRATEGY_LIST:
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+			my_qual = get_qual_for_list(parent, spec);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			my_qual = get_qual_for_range(parent, spec, false);
+			break;
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) key->strategy);
+	}
+
+	return my_qual;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because PartitionBoundInfo is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
+					   PartitionBoundInfo b1, PartitionBoundInfo b2)
+{
+	int			i;
+
+	if (b1->strategy != b2->strategy)
+		return false;
+
+	if (b1->ndatums != b2->ndatums)
+		return false;
+
+	if (b1->null_index != b2->null_index)
+		return false;
+
+	if (b1->default_index != b2->default_index)
+		return false;
+
+	if (b1->strategy == PARTITION_STRATEGY_HASH)
+	{
+		int			greatest_modulus = get_hash_partition_greatest_modulus(b1);
+
+		/*
+		 * If two hash partitioned tables have different greatest moduli,
+		 * their partition schemes don't match.
+		 */
+		if (greatest_modulus != get_hash_partition_greatest_modulus(b2))
+			return false;
+
+		/*
+		 * We arrange the partitions in the ascending order of their modulus
+		 * and remainders.  Also every modulus is factor of next larger
+		 * modulus.  Therefore we can safely store index of a given partition
+		 * in indexes array at remainder of that partition.  Also entries at
+		 * (remainder + N * modulus) positions in indexes array are all same
+		 * for (modulus, remainder) specification for any partition.  Thus
+		 * datums array from both the given bounds are same, if and only if
+		 * their indexes array will be same.  So, it suffices to compare
+		 * indexes array.
+		 */
+		for (i = 0; i < greatest_modulus; i++)
+			if (b1->indexes[i] != b2->indexes[i])
+				return false;
+
+#ifdef USE_ASSERT_CHECKING
+
+		/*
+		 * Nonetheless make sure that the bounds are indeed same when the
+		 * indexes match.  Hash partition bound stores modulus and remainder
+		 * at b1->datums[i][0] and b1->datums[i][1] position respectively.
+		 */
+		for (i = 0; i < b1->ndatums; i++)
+			Assert((b1->datums[i][0] == b2->datums[i][0] &&
+					b1->datums[i][1] == b2->datums[i][1]));
+#endif
+	}
+	else
+	{
+		for (i = 0; i < b1->ndatums; i++)
+		{
+			int			j;
+
+			for (j = 0; j < partnatts; j++)
+			{
+				/* For range partitions, the bounds might not be finite. */
+				if (b1->kind != NULL)
+				{
+					/* The different kinds of bound all differ from each other */
+					if (b1->kind[i][j] != b2->kind[i][j])
+						return false;
+
+					/*
+					 * Non-finite bounds are equal without further
+					 * examination.
+					 */
+					if (b1->kind[i][j] != PARTITION_RANGE_DATUM_VALUE)
+						continue;
+				}
+
+				/*
+				 * Compare the actual values. Note that it would be both
+				 * incorrect and unsafe to invoke the comparison operator
+				 * derived from the partitioning specification here.  It would
+				 * be incorrect because we want the relcache entry to be
+				 * updated for ANY change to the partition bounds, not just
+				 * those that the partitioning operator thinks are
+				 * significant.  It would be unsafe because we might reach
+				 * this code in the context of an aborted transaction, and an
+				 * arbitrary partitioning operator might not be safe in that
+				 * 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;
+			}
+
+			if (b1->indexes[i] != b2->indexes[i])
+				return false;
+		}
+
+		/* There are ndatums+1 indexes in case of range partitions */
+		if (b1->strategy == PARTITION_STRATEGY_RANGE &&
+			b1->indexes[i] != b2->indexes[i])
+			return false;
+	}
+	return true;
+}
+
+/*
+ * Return a copy of given PartitionBoundInfo structure. The data types of bounds
+ * are described by given partition key specification.
+ */
+PartitionBoundInfo
+partition_bounds_copy(PartitionBoundInfo src,
+					  PartitionKey key)
+{
+	PartitionBoundInfo dest;
+	int			i;
+	int			ndatums;
+	int			partnatts;
+	int			num_indexes;
+
+	dest = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
+
+	dest->strategy = src->strategy;
+	ndatums = dest->ndatums = src->ndatums;
+	partnatts = key->partnatts;
+
+	num_indexes = get_partition_bound_num_indexes(src);
+
+	/* List partitioned tables have only a single partition key. */
+	Assert(key->strategy != PARTITION_STRATEGY_LIST || partnatts == 1);
+
+	dest->datums = (Datum **) palloc(sizeof(Datum *) * ndatums);
+
+	if (src->kind != NULL)
+	{
+		dest->kind = (PartitionRangeDatumKind **) palloc(ndatums *
+														 sizeof(PartitionRangeDatumKind *));
+		for (i = 0; i < ndatums; i++)
+		{
+			dest->kind[i] = (PartitionRangeDatumKind *) palloc(partnatts *
+															   sizeof(PartitionRangeDatumKind));
+
+			memcpy(dest->kind[i], src->kind[i],
+				   sizeof(PartitionRangeDatumKind) * key->partnatts);
+		}
+	}
+	else
+		dest->kind = NULL;
+
+	for (i = 0; i < ndatums; i++)
+	{
+		int			j;
+
+		/*
+		 * For a corresponding to hash partition, datums array will have two
+		 * elements - modulus and remainder.
+		 */
+		bool		hash_part = (key->strategy == PARTITION_STRATEGY_HASH);
+		int			natts = hash_part ? 2 : partnatts;
+
+		dest->datums[i] = (Datum *) palloc(sizeof(Datum) * natts);
+
+		for (j = 0; j < natts; j++)
+		{
+			bool		byval;
+			int			typlen;
+
+			if (hash_part)
+			{
+				typlen = sizeof(int32); /* Always int4 */
+				byval = true;	/* int4 is pass-by-value */
+			}
+			else
+			{
+				byval = key->parttypbyval[j];
+				typlen = key->parttyplen[j];
+			}
+
+			if (dest->kind == NULL ||
+				dest->kind[i][j] == PARTITION_RANGE_DATUM_VALUE)
+				dest->datums[i][j] = datumCopy(src->datums[i][j],
+											   byval, typlen);
+		}
+	}
+
+	dest->indexes = (int *) palloc(sizeof(int) * num_indexes);
+	memcpy(dest->indexes, src->indexes, sizeof(int) * num_indexes);
+
+	dest->null_index = src->null_index;
+	dest->default_index = src->default_index;
+
+	return dest;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent.  Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Relation parent,
+						  PartitionBoundSpec *spec)
+{
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
+	ParseState *pstate = make_parsestate(NULL);
+	int			with = -1;
+	bool		overlap = false;
+
+	if (spec->is_default)
+	{
+		/*
+		 * The default partition bound never conflicts with any other
+		 * partition's; if that's what we're attaching, the only possible
+		 * problem is that one already exists, so check for that and we're
+		 * done.
+		 */
+		if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
+			return;
+
+		/* Default partition already exists, error out. */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
+				 parser_errposition(pstate, spec->location)));
+	}
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+			{
+				Assert(spec->strategy == PARTITION_STRATEGY_HASH);
+				Assert(spec->remainder >= 0 && spec->remainder < spec->modulus);
+
+				if (partdesc->nparts > 0)
+				{
+					Datum	  **datums = boundinfo->datums;
+					int			ndatums = boundinfo->ndatums;
+					int			greatest_modulus;
+					int			remainder;
+					int			offset;
+					bool		valid_modulus = true;
+					int			prev_modulus,	/* Previous largest modulus */
+								next_modulus;	/* Next largest modulus */
+
+					/*
+					 * Check rule that every modulus must be a factor of the
+					 * next larger modulus.  For example, if you have a bunch
+					 * of partitions that all have modulus 5, you can add a
+					 * new partition with modulus 10 or a new partition with
+					 * modulus 15, but you cannot add both a partition with
+					 * modulus 10 and a partition with modulus 15, because 10
+					 * is not a factor of 15.
+					 *
+					 * Get the greatest (modulus, remainder) pair contained in
+					 * boundinfo->datums that is less than or equal to the
+					 * (spec->modulus, spec->remainder) pair.
+					 */
+					offset = partition_hash_bsearch(boundinfo,
+													spec->modulus,
+													spec->remainder);
+					if (offset < 0)
+					{
+						next_modulus = DatumGetInt32(datums[0][0]);
+						valid_modulus = (next_modulus % spec->modulus) == 0;
+					}
+					else
+					{
+						prev_modulus = DatumGetInt32(datums[offset][0]);
+						valid_modulus = (spec->modulus % prev_modulus) == 0;
+
+						if (valid_modulus && (offset + 1) < ndatums)
+						{
+							next_modulus = DatumGetInt32(datums[offset + 1][0]);
+							valid_modulus = (next_modulus % spec->modulus) == 0;
+						}
+					}
+
+					if (!valid_modulus)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("every hash partition modulus must be a factor of the next larger modulus")));
+
+					greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
+					remainder = spec->remainder;
+
+					/*
+					 * Normally, the lowest remainder that could conflict with
+					 * the new partition is equal to the remainder specified
+					 * for the new partition, but when the new partition has a
+					 * modulus higher than any used so far, we need to adjust.
+					 */
+					if (remainder >= greatest_modulus)
+						remainder = remainder % greatest_modulus;
+
+					/* Check every potentially-conflicting remainder. */
+					do
+					{
+						if (boundinfo->indexes[remainder] != -1)
+						{
+							overlap = true;
+							with = boundinfo->indexes[remainder];
+							break;
+						}
+						remainder += spec->modulus;
+					} while (remainder < greatest_modulus);
+				}
+
+				break;
+			}
+
+		case PARTITION_STRATEGY_LIST:
+			{
+				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+				if (partdesc->nparts > 0)
+				{
+					ListCell   *cell;
+
+					Assert(boundinfo &&
+						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
+						   (boundinfo->ndatums > 0 ||
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
+
+					foreach(cell, spec->listdatums)
+					{
+						Const	   *val = castNode(Const, lfirst(cell));
+
+						if (!val->constisnull)
+						{
+							int			offset;
+							bool		equal;
+
+							offset = partition_list_bsearch(&key->partsupfunc[0],
+														key->partcollation,
+															boundinfo,
+															val->constvalue,
+															&equal);
+							if (offset >= 0 && equal)
+							{
+								overlap = true;
+								with = boundinfo->indexes[offset];
+								break;
+							}
+						}
+						else if (partition_bound_accepts_nulls(boundinfo))
+						{
+							overlap = true;
+							with = boundinfo->null_index;
+							break;
+						}
+					}
+				}
+
+				break;
+			}
+
+		case PARTITION_STRATEGY_RANGE:
+			{
+				PartitionRangeBound *lower,
+						   *upper;
+
+				Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+				lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
+				upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+
+				/*
+				 * 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->kind, true, upper) >= 0)
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("empty range bound specified for partition \"%s\"",
+									relname),
+							 errdetail("Specified lower bound %s is greater than or equal to upper bound %s.",
+									   get_range_partbound_string(spec->lowerdatums),
+									   get_range_partbound_string(spec->upperdatums)),
+							 parser_errposition(pstate, spec->location)));
+				}
+
+				if (partdesc->nparts > 0)
+				{
+					int			offset;
+					bool		equal;
+
+					Assert(boundinfo &&
+						   boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
+						   (boundinfo->ndatums > 0 ||
+							partition_bound_has_default(boundinfo)));
+
+					/*
+					 * Test whether the new lower bound (which is treated
+					 * inclusively as part of the new partition) lies inside
+					 * an existing partition, or in a gap.
+					 *
+					 * If it's inside an existing partition, the bound at
+					 * offset + 1 will be the upper bound of that partition,
+					 * and its index will be >= 0.
+					 *
+					 * If it's in a gap, the bound at offset + 1 will be the
+					 * lower bound of the next partition, and its index will
+					 * be -1. This is also true if there is no next partition,
+					 * since the index array is initialised with an extra -1
+					 * at the end.
+					 */
+					offset = partition_range_bsearch(key->partnatts,
+													 key->partsupfunc,
+													 key->partcollation,
+													 boundinfo, lower,
+													 &equal);
+
+					if (boundinfo->indexes[offset + 1] < 0)
+					{
+						/*
+						 * Check that the new partition will fit in the gap.
+						 * For it to fit, the new upper bound must be less
+						 * than or equal to the lower bound of the next
+						 * partition, if there is one.
+						 */
+						if (offset + 1 < boundinfo->ndatums)
+						{
+							int32		cmpval;
+							Datum 	   *datums;
+							PartitionRangeDatumKind *kind;
+							bool		is_lower;
+
+							datums = boundinfo->datums[offset + 1];
+							kind = boundinfo->kind[offset + 1];
+							is_lower = (boundinfo->indexes[offset + 1] == -1);
+
+							cmpval = partition_rbound_cmp(key->partnatts,
+														  key->partsupfunc,
+														  key->partcollation,
+														  datums, kind,
+														  is_lower, upper);
+							if (cmpval < 0)
+							{
+								/*
+								 * The new partition overlaps with the
+								 * existing partition between offset + 1 and
+								 * offset + 2.
+								 */
+								overlap = true;
+								with = boundinfo->indexes[offset + 2];
+							}
+						}
+					}
+					else
+					{
+						/*
+						 * The new partition overlaps with the existing
+						 * partition between offset and offset + 1.
+						 */
+						overlap = true;
+						with = boundinfo->indexes[offset + 1];
+					}
+				}
+
+				break;
+			}
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) key->strategy);
+	}
+
+	if (overlap)
+	{
+		Assert(with >= 0);
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" would overlap partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[with])),
+				 parser_errposition(pstate, spec->location)));
+	}
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * would properly belong to the new partition being added.  If it finds one,
+ * it throws an error.
+ */
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
+{
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
+	List	   *all_parts;
+	ListCell   *lc;
+
+	new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+		? get_qual_for_list(parent, new_spec)
+		: get_qual_for_range(parent, new_spec, false);
+	def_part_constraints =
+		get_proposed_default_constraint(new_part_constraints);
+
+	/*
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
+	 */
+	if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
+	{
+		ereport(INFO,
+				(errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
+
+	/*
+	 * Scan the default partition and its subpartitions, and check for rows
+	 * that do not satisfy the revised partition constraints.
+	 */
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+		{
+			part_rel = heap_open(part_relid, NoLock);
+
+			/*
+			 * If the partition constraints on default partition child imply
+			 * that it will not contain any row that would belong to the new
+			 * partition, we can avoid scanning the child table.
+			 */
+			if (PartConstraintImpliedByRelConstraint(part_rel,
+													 def_part_constraints))
+			{
+				ereport(INFO,
+						(errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints",
+								RelationGetRelationName(part_rel))));
+
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+		}
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(def_part_constraints);
+		partition_constraint = (Expr *)
+			map_partition_varattnos((List *) constr,
+									1, part_rel, parent, NULL);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (!ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
+	}
+}
+
+/*
+ * get_hash_partition_greatest_modulus
+ *
+ * Returns the greatest modulus of the hash partition bound. The greatest
+ * modulus will be at the end of the datums array because hash partitions are
+ * arranged in the ascending order of their modulus and remainders.
+ */
+int
+get_hash_partition_greatest_modulus(PartitionBoundInfo bound)
+{
+	Assert(bound && bound->strategy == PARTITION_STRATEGY_HASH);
+	Assert(bound->datums && bound->ndatums > 0);
+	Assert(DatumGetInt32(bound->datums[bound->ndatums - 1][0]) > 0);
+
+	return DatumGetInt32(bound->datums[bound->ndatums - 1][0]);
+}
+
+/*
+ * make_one_range_bound
+ *
+ * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
+ * and a flag telling whether the bound is lower or not.  Made into a function
+ * because there are multiple sites that want to use this facility.
+ */
+PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell   *lc;
+	int			i;
+
+	Assert(datums != NIL);
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	bound->index = index;
+	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+	bound->kind = (PartitionRangeDatumKind *) palloc0(key->partnatts *
+													  sizeof(PartitionRangeDatumKind));
+	bound->lower = lower;
+
+	i = 0;
+	foreach(lc, datums)
+	{
+		PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
+
+		/* What's contained in this range datum? */
+		bound->kind[i] = datum->kind;
+
+		if (datum->kind == PARTITION_RANGE_DATUM_VALUE)
+		{
+			Const	   *val = castNode(Const, datum->value);
+
+			if (val->constisnull)
+				elog(ERROR, "invalid range bound datum");
+			bound->datums[i] = val->constvalue;
+		}
+
+		i++;
+	}
+
+	return bound;
+}
+
+/*
+ * partition_rbound_cmp
+ *
+ * Return for two range bounds whether the 1st one (specified in datums1,
+ * kind1, and lower1) is <, =, or > the bound specified in *b2.
+ *
+ * partnatts, partsupfunc and partcollation give the number of attributes in the
+ * bounds to be compared, comparison function to be used and the collations of
+ * attributes, respectively.
+ *
+ * Note that if the values of the two range bounds compare equal, then we take
+ * into account whether they are upper or lower bounds, and an upper bound is
+ * considered to be smaller than a lower bound. This is important to the way
+ * that RelationBuildPartitionDesc() builds the PartitionBoundInfoData
+ * structure, which only stores the upper bound of a common boundary between
+ * two contiguous partitions.
+ */
+int32
+partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
+					 Oid *partcollation,
+					 Datum *datums1, PartitionRangeDatumKind *kind1,
+					 bool lower1, PartitionRangeBound *b2)
+{
+	int32		cmpval = 0;		/* placate compiler */
+	int			i;
+	Datum	   *datums2 = b2->datums;
+	PartitionRangeDatumKind *kind2 = b2->kind;
+	bool		lower2 = b2->lower;
+
+	for (i = 0; i < partnatts; i++)
+	{
+		/*
+		 * First, handle cases where the column is unbounded, which should not
+		 * invoke the comparison procedure, and should not consider any later
+		 * columns. Note that the PartitionRangeDatumKind enum elements
+		 * compare the same way as the values they represent.
+		 */
+		if (kind1[i] < kind2[i])
+			return -1;
+		else if (kind1[i] > kind2[i])
+			return 1;
+		else if (kind1[i] != PARTITION_RANGE_DATUM_VALUE)
+
+			/*
+			 * The column bounds are both MINVALUE or both MAXVALUE. No later
+			 * columns should be considered, but we still need to compare
+			 * whether they are upper or lower bounds.
+			 */
+			break;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
+												 partcollation[i],
+												 datums1[i],
+												 datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/*
+	 * If the comparison is anything other than equal, we're done. If they
+	 * compare equal though, we still have to consider whether the boundaries
+	 * are inclusive or exclusive.  Exclusive one is considered smaller of the
+	 * two.
+	 */
+	if (cmpval == 0 && lower1 != lower2)
+		cmpval = lower1 ? 1 : -1;
+
+	return cmpval;
+}
+
+/*
+ * partition_rbound_datum_cmp
+ *
+ * Return whether range bound (specified in rb_datums, rb_kind, and rb_lower)
+ * is <, =, or > partition key of tuple (tuple_datums)
+ *
+ * n_tuple_datums, partsupfunc and partcollation give number of attributes in
+ * the bounds to be compared, comparison function to be used and the collations
+ * of attributes resp.
+ *
+ */
+int32
+partition_rbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation,
+						   Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
+						   Datum *tuple_datums, int n_tuple_datums)
+{
+	int			i;
+	int32		cmpval = -1;
+
+	for (i = 0; i < n_tuple_datums; i++)
+	{
+		if (rb_kind[i] == PARTITION_RANGE_DATUM_MINVALUE)
+			return -1;
+		else if (rb_kind[i] == PARTITION_RANGE_DATUM_MAXVALUE)
+			return 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
+												 partcollation[i],
+												 rb_datums[i],
+												 tuple_datums[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	return cmpval;
+}
+
+/*
+ * partition_hbound_cmp
+ *
+ * Compares modulus first, then remainder if modulus are equal.
+ */
+int32
+partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2)
+{
+	if (modulus1 < modulus2)
+		return -1;
+	if (modulus1 > modulus2)
+		return 1;
+	if (modulus1 == modulus2 && remainder1 != remainder2)
+		return (remainder1 > remainder2) ? 1 : -1;
+	return 0;
+}
+
+/*
+ * partition_list_bsearch
+ *		Returns the index of the greatest bound datum that is less than equal
+ * 		to the given value or -1 if all of the bound datums are greater
+ *
+ * *is_equal is set to true if the bound datum at the returned index is equal
+ * to the input value.
+ */
+int
+partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
+					   PartitionBoundInfo boundinfo,
+					   Datum value, bool *is_equal)
+{
+	int			lo,
+				hi,
+				mid;
+
+	lo = -1;
+	hi = boundinfo->ndatums - 1;
+	while (lo < hi)
+	{
+		int32		cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
+												 partcollation[0],
+												 boundinfo->datums[mid][0],
+												 value));
+		if (cmpval <= 0)
+		{
+			lo = mid;
+			*is_equal = (cmpval == 0);
+			if (*is_equal)
+				break;
+		}
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
+
+/*
+ * partition_range_bsearch
+ *		Returns the index of the greatest range bound that is less than or
+ *		equal to the given range bound or -1 if all of the range bounds are
+ *		greater
+ *
+ * *is_equal is set to true if the range bound at the returned index is equal
+ * to the input range bound
+ */
+int
+partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
+						Oid *partcollation,
+						PartitionBoundInfo boundinfo,
+						PartitionRangeBound *probe, bool *is_equal)
+{
+	int			lo,
+				hi,
+				mid;
+
+	lo = -1;
+	hi = boundinfo->ndatums - 1;
+	while (lo < hi)
+	{
+		int32		cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cmpval = partition_rbound_cmp(partnatts, partsupfunc,
+									  partcollation,
+									  boundinfo->datums[mid],
+									  boundinfo->kind[mid],
+									  (boundinfo->indexes[mid] == -1),
+									  probe);
+		if (cmpval <= 0)
+		{
+			lo = mid;
+			*is_equal = (cmpval == 0);
+
+			if (*is_equal)
+				break;
+		}
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
+
+/*
+ * partition_range_bsearch
+ *		Returns the index of the greatest range bound that is less than or
+ *		equal to the given tuple or -1 if all of the range bounds are greater
+ *
+ * *is_equal is set to true if the range bound at the returned index is equal
+ * to the input tuple.
+ */
+int
+partition_range_datum_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
+							  PartitionBoundInfo boundinfo,
+							  int nvalues, Datum *values, bool *is_equal)
+{
+	int			lo,
+				hi,
+				mid;
+
+	lo = -1;
+	hi = boundinfo->ndatums - 1;
+	while (lo < hi)
+	{
+		int32		cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cmpval = partition_rbound_datum_cmp(partsupfunc,
+											partcollation,
+											boundinfo->datums[mid],
+											boundinfo->kind[mid],
+											values,
+											nvalues);
+		if (cmpval <= 0)
+		{
+			lo = mid;
+			*is_equal = (cmpval == 0);
+
+			if (*is_equal)
+				break;
+		}
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
+
+/*
+ * partition_hash_bsearch
+ *		Returns the index of the greatest (modulus, remainder) pair that is
+ *		less than or equal to the given (modulus, remainder) pair or -1 if
+ *		all of them are greater
+ */
+int
+partition_hash_bsearch(PartitionBoundInfo boundinfo,
+					   int modulus, int remainder)
+{
+	int			lo,
+				hi,
+				mid;
+
+	lo = -1;
+	hi = boundinfo->ndatums - 1;
+	while (lo < hi)
+	{
+		int32		cmpval,
+					bound_modulus,
+					bound_remainder;
+
+		mid = (lo + hi + 1) / 2;
+		bound_modulus = DatumGetInt32(boundinfo->datums[mid][0]);
+		bound_remainder = DatumGetInt32(boundinfo->datums[mid][1]);
+		cmpval = partition_hbound_cmp(bound_modulus, bound_remainder,
+									  modulus, remainder);
+		if (cmpval <= 0)
+		{
+			lo = mid;
+
+			if (cmpval == 0)
+				break;
+		}
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
+
+/*
+ * get_partition_bound_num_indexes
+ *
+ * Returns the number of the entries in the partition bound indexes array.
+ */
+static int
+get_partition_bound_num_indexes(PartitionBoundInfo bound)
+{
+	int			num_indexes;
+
+	Assert(bound);
+
+	switch (bound->strategy)
+	{
+		case PARTITION_STRATEGY_HASH:
+
+			/*
+			 * The number of the entries in the indexes array is same as the
+			 * greatest modulus.
+			 */
+			num_indexes = get_hash_partition_greatest_modulus(bound);
+			break;
+
+		case PARTITION_STRATEGY_LIST:
+			num_indexes = bound->ndatums;
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			/* Range partitioned table has an extra index. */
+			num_indexes = bound->ndatums + 1;
+			break;
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) bound->strategy);
+	}
+
+	return num_indexes;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel)
+{
+	Oid			operoid;
+
+	/*
+	 * First check if there exists an operator of the given strategy, with
+	 * this column's type as both its lefttype and righttype, in the
+	 * partitioning operator family specified for the column.
+	 */
+	operoid = get_opfamily_member(key->partopfamily[col],
+								  key->parttypid[col],
+								  key->parttypid[col],
+								  strategy);
+
+	/*
+	 * If one doesn't exist, we must resort to using an operator in the same
+	 * operator family but with the operator class declared input type.  It is
+	 * OK to do so, because the column's type is known to be binary-coercible
+	 * with the operator class input type (otherwise, the operator class in
+	 * question would not have been accepted as the partitioning operator
+	 * class).  We must however inform the caller to wrap the non-Const
+	 * expression with a RelabelType node to denote the implicit coercion. It
+	 * ensures that the resulting expression structurally matches similarly
+	 * processed expressions within the optimizer.
+	 */
+	if (!OidIsValid(operoid))
+	{
+		operoid = get_opfamily_member(key->partopfamily[col],
+									  key->partopcintype[col],
+									  key->partopcintype[col],
+									  strategy);
+		if (!OidIsValid(operoid))
+			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+				 strategy, key->partopcintype[col], key->partopcintype[col],
+				 key->partopfamily[col]);
+		*need_relabel = true;
+	}
+	else
+		*need_relabel = false;
+
+	return operoid;
+}
+
+/*
+ * make_partition_op_expr
+ *		Returns an Expr for the given partition key column with arg1 and
+ *		arg2 as its leftop and rightop, respectively
+ */
+static Expr *
+make_partition_op_expr(PartitionKey key, int keynum,
+					   uint16 strategy, Expr *arg1, Expr *arg2)
+{
+	Oid			operoid;
+	bool		need_relabel = false;
+	Expr	   *result = NULL;
+
+	/* Get the correct btree operator for this partitioning column */
+	operoid = get_partition_operator(key, keynum, strategy, &need_relabel);
+
+	/*
+	 * Chosen operator may be such that the non-Const operand needs to be
+	 * coerced, so apply the same; see the comment in
+	 * get_partition_operator().
+	 */
+	if (!IsA(arg1, Const) &&
+		(need_relabel ||
+		 key->partcollation[keynum] != key->parttypcoll[keynum]))
+		arg1 = (Expr *) makeRelabelType(arg1,
+										key->partopcintype[keynum],
+										-1,
+										key->partcollation[keynum],
+										COERCE_EXPLICIT_CAST);
+
+	/* Generate the actual expression */
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			{
+				List	   *elems = (List *) arg2;
+				int			nelems = list_length(elems);
+
+				Assert(nelems >= 1);
+				Assert(keynum == 0);
+
+				if (nelems > 1 &&
+					!type_is_array(key->parttypid[keynum]))
+				{
+					ArrayExpr  *arrexpr;
+					ScalarArrayOpExpr *saopexpr;
+
+					/* Construct an ArrayExpr for the right-hand inputs */
+					arrexpr = makeNode(ArrayExpr);
+					arrexpr->array_typeid =
+									get_array_type(key->parttypid[keynum]);
+					arrexpr->array_collid = key->parttypcoll[keynum];
+					arrexpr->element_typeid = key->parttypid[keynum];
+					arrexpr->elements = elems;
+					arrexpr->multidims = false;
+					arrexpr->location = -1;
+
+					/* Build leftop = ANY (rightop) */
+					saopexpr = makeNode(ScalarArrayOpExpr);
+					saopexpr->opno = operoid;
+					saopexpr->opfuncid = get_opcode(operoid);
+					saopexpr->useOr = true;
+					saopexpr->inputcollid = key->partcollation[keynum];
+					saopexpr->args = list_make2(arg1, arrexpr);
+					saopexpr->location = -1;
+
+					result = (Expr *) saopexpr;
+				}
+				else
+				{
+					List	   *elemops = NIL;
+					ListCell   *lc;
+
+					foreach (lc, elems)
+					{
+						Expr   *elem = lfirst(lc),
+							   *elemop;
+
+						elemop = make_opclause(operoid,
+											   BOOLOID,
+											   false,
+											   arg1, elem,
+											   InvalidOid,
+											   key->partcollation[keynum]);
+						elemops = lappend(elemops, elemop);
+					}
+
+					result = nelems > 1 ? makeBoolExpr(OR_EXPR, elemops, -1) : linitial(elemops);
+				}
+				break;
+			}
+
+		case PARTITION_STRATEGY_RANGE:
+			result = make_opclause(operoid,
+								   BOOLOID,
+								   false,
+								   arg1, arg2,
+								   InvalidOid,
+								   key->partcollation[keynum]);
+			break;
+
+		default:
+			elog(ERROR, "invalid partitioning strategy");
+			break;
+	}
+
+	return result;
+}
+
+/*
+ * get_qual_for_hash
+ *
+ * Returns a CHECK constraint expression to use as a hash partition's
+ * constraint, given the parent relation and partition bound structure.
+ *
+ * The partition constraint for a hash partition is always a call to the
+ * built-in function satisfies_hash_partition().
+ */
+static List *
+get_qual_for_hash(Relation parent, PartitionBoundSpec *spec)
+{
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	FuncExpr   *fexpr;
+	Node	   *relidConst;
+	Node	   *modulusConst;
+	Node	   *remainderConst;
+	List	   *args;
+	ListCell   *partexprs_item;
+	int			i;
+
+	/* Fixed arguments. */
+	relidConst = (Node *) makeConst(OIDOID,
+									-1,
+									InvalidOid,
+									sizeof(Oid),
+									ObjectIdGetDatum(RelationGetRelid(parent)),
+									false,
+									true);
+
+	modulusConst = (Node *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  Int32GetDatum(spec->modulus),
+									  false,
+									  true);
+
+	remainderConst = (Node *) makeConst(INT4OID,
+										-1,
+										InvalidOid,
+										sizeof(int32),
+										Int32GetDatum(spec->remainder),
+										false,
+										true);
+
+	args = list_make3(relidConst, modulusConst, remainderConst);
+	partexprs_item = list_head(key->partexprs);
+
+	/* Add an argument for each key column. */
+	for (i = 0; i < key->partnatts; i++)
+	{
+		Node	   *keyCol;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			keyCol = (Node *) makeVar(1,
+									  key->partattrs[i],
+									  key->parttypid[i],
+									  key->parttypmod[i],
+									  key->parttypcoll[i],
+									  0);
+		}
+		else
+		{
+			keyCol = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		args = lappend(args, keyCol);
+	}
+
+	fexpr = makeFuncExpr(F_SATISFIES_HASH_PARTITION,
+						 BOOLOID,
+						 args,
+						 InvalidOid,
+						 InvalidOid,
+						 COERCE_EXPLICIT_CALL);
+
+	return list_make1(fexpr);
+}
+
+/*
+ * get_qual_for_list
+ *
+ * Returns an implicit-AND list of expressions to use as a list partition's
+ * constraint, given the parent relation and partition bound structure.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since in that case there is no constraint.
+ */
+static List *
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
+{
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	List	   *result;
+	Expr	   *keyCol;
+	Expr	   *opexpr;
+	NullTest   *nulltest;
+	ListCell   *cell;
+	List	   *elems = NIL;
+	bool		list_has_null = false;
+
+	/*
+	 * Only single-column list partitioning is supported, so we are worried
+	 * only about the partition key with index 0.
+	 */
+	Assert(key->partnatts == 1);
+
+	/* Construct Var or expression representing the partition column */
+	if (key->partattrs[0] != 0)
+		keyCol = (Expr *) makeVar(1,
+								  key->partattrs[0],
+								  key->parttypid[0],
+								  key->parttypmod[0],
+								  key->parttypcoll[0],
+								  0);
+	else
+		keyCol = (Expr *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
+	{
+		int			i;
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
+
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/*
+			 * Construct Const from known-not-null datum.  We must be careful
+			 * to copy the value, because our result has to be able to outlive
+			 * the relcache entry we're copying from.
+			 */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							datumCopy(*boundinfo->datums[i],
+									  key->parttypbyval[0],
+									  key->parttyplen[0]),
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			elems = lappend(elems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				elems = lappend(elems, copyObject(val));
+		}
+	}
+
+	if (elems)
+	{
+		/*
+		 * Generate the operator expression from the non-null partition
+		 * values.
+		 */
+		opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
+										keyCol, (Expr *) elems);
+	}
+	else
+	{
+		/*
+		 * If there are no partition values, we don't need an operator
+		 * expression.
+		 */
+		opexpr = NULL;
+	}
+
+	if (!list_has_null)
+	{
+		/*
+		 * Gin up a "col IS NOT NULL" test that will be AND'd with the main
+		 * expression.  This might seem redundant, but the partition routing
+		 * machinery needs it.
+		 */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = keyCol;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+
+		result = opexpr ? list_make2(nulltest, opexpr) : list_make1(nulltest);
+	}
+	else
+	{
+		/*
+		 * Gin up a "col IS NULL" test that will be OR'd with the main
+		 * expression.
+		 */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = keyCol;
+		nulltest->nulltesttype = IS_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+
+		if (opexpr)
+		{
+			Expr	   *or;
+
+			or = makeBoolExpr(OR_EXPR, list_make2(nulltest, opexpr), -1);
+			result = list_make1(or);
+		}
+		else
+			result = list_make1(nulltest);
+	}
+
+	/*
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
+	return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Returns an implicit-AND list of expressions to use as a range partition's
+ * constraint, given the parent relation and partition bound structure.
+ *
+ * For a multi-column range partition key, say (a, b, c), with (al, bl, cl)
+ * as the lower bound tuple and (au, bu, cu) as the upper bound tuple, we
+ * generate an expression tree of the following form:
+ *
+ *	(a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
+ *		AND
+ *	(a > al OR (a = al AND b > bl) OR (a = al AND b = bl AND c >= cl))
+ *		AND
+ *	(a < au OR (a = au AND b < bu) OR (a = au AND b = bu AND c < cu))
+ *
+ * It is often the case that a prefix of lower and upper bound tuples contains
+ * the same values, for example, (al = au), in which case, we will emit an
+ * expression tree of the following form:
+ *
+ *	(a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
+ *		AND
+ *	(a = al)
+ *		AND
+ *	(b > bl OR (b = bl AND c >= cl))
+ *		AND
+ *	(b < bu) OR (b = bu AND c < cu))
+ *
+ * If a bound datum is either MINVALUE or MAXVALUE, these expressions are
+ * simplified using the fact that any value is greater than MINVALUE and less
+ * than MAXVALUE. So, for example, if cu = MAXVALUE, c < cu is automatically
+ * true, and we need not emit any expression for it, and the last line becomes
+ *
+ *	(b < bu) OR (b = bu), which is simplified to (b <= bu)
+ *
+ * In most common cases with only one partition column, say a, the following
+ * expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
+ *
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
+ * External callers should pass for_default as false; we set it to true only
+ * when recursing.
+ */
+static List *
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+				   bool for_default)
+{
+	List	   *result = NIL;
+	ListCell   *cell1,
+			   *cell2,
+			   *partexprs_item,
+			   *partexprs_item_saved;
+	int			i,
+				j;
+	PartitionRangeDatum *ldatum,
+			   *udatum;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	Expr	   *keyCol;
+	Const	   *lower_val,
+			   *upper_val;
+	List	   *lower_or_arms,
+			   *upper_or_arms;
+	int			num_or_arms,
+				current_or_arm;
+	ListCell   *lower_or_start_datum,
+			   *upper_or_start_datum;
+	bool		need_next_lower_arm,
+				need_next_upper_arm;
+
+	if (spec->is_default)
+	{
+		List	   *or_expr_args = NIL;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		Oid		   *inhoids = pdesc->oids;
+		int			nparts = pdesc->nparts,
+					i;
+
+		for (i = 0; i < nparts; i++)
+		{
+			Oid			inhrelid = inhoids[i];
+			HeapTuple	tuple;
+			Datum		datum;
+			bool		isnull;
+			PartitionBoundSpec *bspec;
+
+			tuple = SearchSysCache1(RELOID, inhrelid);
+			if (!HeapTupleIsValid(tuple))
+				elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+			datum = SysCacheGetAttr(RELOID, tuple,
+									Anum_pg_class_relpartbound,
+									&isnull);
+
+			Assert(!isnull);
+			bspec = (PartitionBoundSpec *)
+				stringToNode(TextDatumGetCString(datum));
+			if (!IsA(bspec, PartitionBoundSpec))
+				elog(ERROR, "expected PartitionBoundSpec");
+
+			if (!bspec->is_default)
+			{
+				List	   *part_qual;
+
+				part_qual = get_qual_for_range(parent, bspec, true);
+
+				/*
+				 * AND the constraints of the partition and add to
+				 * or_expr_args
+				 */
+				or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+									   ? makeBoolExpr(AND_EXPR, part_qual, -1)
+									   : linitial(part_qual));
+			}
+			ReleaseSysCache(tuple);
+		}
+
+		if (or_expr_args != NIL)
+		{
+			Expr	   *other_parts_constr;
+
+			/*
+			 * Combine the constraints obtained for non-default partitions
+			 * using OR.  As requested, each of the OR's args doesn't include
+			 * the NOT NULL test for partition keys (which is to avoid its
+			 * useless repetition).  Add the same now.
+			 */
+			other_parts_constr =
+				makeBoolExpr(AND_EXPR,
+							 lappend(get_range_nulltest(key),
+									 list_length(or_expr_args) > 1
+									 ? makeBoolExpr(OR_EXPR, or_expr_args,
+													-1)
+									 : linitial(or_expr_args)),
+							 -1);
+
+			/*
+			 * Finally, the default partition contains everything *NOT*
+			 * contained in the non-default partitions.
+			 */
+			result = list_make1(makeBoolExpr(NOT_EXPR,
+											 list_make1(other_parts_constr), -1));
+		}
+
+		return result;
+	}
+
+	lower_or_start_datum = list_head(spec->lowerdatums);
+	upper_or_start_datum = list_head(spec->upperdatums);
+	num_or_arms = key->partnatts;
+
+	/*
+	 * If it is the recursive call for default, we skip the get_range_nulltest
+	 * to avoid accumulating the NullTest on the same keys for each partition.
+	 */
+	if (!for_default)
+		result = get_range_nulltest(key);
+
+	/*
+	 * Iterate over the key columns and check if the corresponding lower and
+	 * upper datums are equal using the btree equality operator for the
+	 * column's type.  If equal, we emit single keyCol = common_value
+	 * expression.  Starting from the first column for which the corresponding
+	 * lower and upper bound datums are not equal, we generate OR expressions
+	 * as shown in the function's header comment.
+	 */
+	i = 0;
+	partexprs_item = list_head(key->partexprs);
+	partexprs_item_saved = partexprs_item;	/* placate compiler */
+	forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums)
+	{
+		EState	   *estate;
+		MemoryContext oldcxt;
+		Expr	   *test_expr;
+		ExprState  *test_exprstate;
+		Datum		test_result;
+		bool		isNull;
+
+		ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
+		udatum = castNode(PartitionRangeDatum, lfirst(cell2));
+
+		/*
+		 * Since get_range_key_properties() modifies partexprs_item, and we
+		 * might need to start over from the previous expression in the later
+		 * part of this function, save away the current value.
+		 */
+		partexprs_item_saved = partexprs_item;
+
+		get_range_key_properties(key, i, ldatum, udatum,
+								 &partexprs_item,
+								 &keyCol,
+								 &lower_val, &upper_val);
+
+		/*
+		 * If either value is NULL, the corresponding partition bound is
+		 * either MINVALUE or MAXVALUE, and we treat them as unequal, because
+		 * even if they're the same, there is no common value to equate the
+		 * key column with.
+		 */
+		if (!lower_val || !upper_val)
+			break;
+
+		/* Create the test expression */
+		estate = CreateExecutorState();
+		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
+										   (Expr *) lower_val,
+										   (Expr *) upper_val);
+		fix_opfuncids((Node *) test_expr);
+		test_exprstate = ExecInitExpr(test_expr, NULL);
+		test_result = ExecEvalExprSwitchContext(test_exprstate,
+												GetPerTupleExprContext(estate),
+												&isNull);
+		MemoryContextSwitchTo(oldcxt);
+		FreeExecutorState(estate);
+
+		/* If not equal, go generate the OR expressions */
+		if (!DatumGetBool(test_result))
+			break;
+
+		/*
+		 * The bounds for the last key column can't be equal, because such a
+		 * range partition would never be allowed to be defined (it would have
+		 * an empty range otherwise).
+		 */
+		if (i == key->partnatts - 1)
+			elog(ERROR, "invalid range bound specification");
+
+		/* Equal, so generate keyCol = lower_val expression */
+		result = lappend(result,
+						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
+												keyCol, (Expr *) lower_val));
+
+		i++;
+	}
+
+	/* First pair of lower_val and upper_val that are not equal. */
+	lower_or_start_datum = cell1;
+	upper_or_start_datum = cell2;
+
+	/* OR will have as many arms as there are key columns left. */
+	num_or_arms = key->partnatts - i;
+	current_or_arm = 0;
+	lower_or_arms = upper_or_arms = NIL;
+	need_next_lower_arm = need_next_upper_arm = true;
+	while (current_or_arm < num_or_arms)
+	{
+		List	   *lower_or_arm_args = NIL,
+				   *upper_or_arm_args = NIL;
+
+		/* Restart scan of columns from the i'th one */
+		j = i;
+		partexprs_item = partexprs_item_saved;
+
+		for_both_cell(cell1, lower_or_start_datum, cell2, upper_or_start_datum)
+		{
+			PartitionRangeDatum *ldatum_next = NULL,
+					   *udatum_next = NULL;
+
+			ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
+			if (lnext(cell1))
+				ldatum_next = castNode(PartitionRangeDatum,
+									   lfirst(lnext(cell1)));
+			udatum = castNode(PartitionRangeDatum, lfirst(cell2));
+			if (lnext(cell2))
+				udatum_next = castNode(PartitionRangeDatum,
+									   lfirst(lnext(cell2)));
+			get_range_key_properties(key, j, ldatum, udatum,
+									 &partexprs_item,
+									 &keyCol,
+									 &lower_val, &upper_val);
+
+			if (need_next_lower_arm && lower_val)
+			{
+				uint16		strategy;
+
+				/*
+				 * For the non-last columns of this arm, use the EQ operator.
+				 * For the last column of this arm, use GT, unless this is the
+				 * last column of the whole bound check, or the next bound
+				 * datum is MINVALUE, in which case use GE.
+				 */
+				if (j - i < current_or_arm)
+					strategy = BTEqualStrategyNumber;
+				else if (j == key->partnatts - 1 ||
+						 (ldatum_next &&
+						  ldatum_next->kind == PARTITION_RANGE_DATUM_MINVALUE))
+					strategy = BTGreaterEqualStrategyNumber;
+				else
+					strategy = BTGreaterStrategyNumber;
+
+				lower_or_arm_args = lappend(lower_or_arm_args,
+											make_partition_op_expr(key, j,
+																   strategy,
+																   keyCol,
+																   (Expr *) lower_val));
+			}
+
+			if (need_next_upper_arm && upper_val)
+			{
+				uint16		strategy;
+
+				/*
+				 * For the non-last columns of this arm, use the EQ operator.
+				 * For the last column of this arm, use LT, unless the next
+				 * bound datum is MAXVALUE, in which case use LE.
+				 */
+				if (j - i < current_or_arm)
+					strategy = BTEqualStrategyNumber;
+				else if (udatum_next &&
+						 udatum_next->kind == PARTITION_RANGE_DATUM_MAXVALUE)
+					strategy = BTLessEqualStrategyNumber;
+				else
+					strategy = BTLessStrategyNumber;
+
+				upper_or_arm_args = lappend(upper_or_arm_args,
+											make_partition_op_expr(key, j,
+																   strategy,
+																   keyCol,
+																   (Expr *) upper_val));
+			}
+
+			/*
+			 * Did we generate enough of OR's arguments?  First arm considers
+			 * the first of the remaining columns, second arm considers first
+			 * two of the remaining columns, and so on.
+			 */
+			++j;
+			if (j - i > current_or_arm)
+			{
+				/*
+				 * We must not emit any more arms if the new column that will
+				 * be considered is unbounded, or this one was.
+				 */
+				if (!lower_val || !ldatum_next ||
+					ldatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
+					need_next_lower_arm = false;
+				if (!upper_val || !udatum_next ||
+					udatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
+					need_next_upper_arm = false;
+				break;
+			}
+		}
+
+		if (lower_or_arm_args != NIL)
+			lower_or_arms = lappend(lower_or_arms,
+									list_length(lower_or_arm_args) > 1
+									? makeBoolExpr(AND_EXPR, lower_or_arm_args, -1)
+									: linitial(lower_or_arm_args));
+
+		if (upper_or_arm_args != NIL)
+			upper_or_arms = lappend(upper_or_arms,
+									list_length(upper_or_arm_args) > 1
+									? makeBoolExpr(AND_EXPR, upper_or_arm_args, -1)
+									: linitial(upper_or_arm_args));
+
+		/* If no work to do in the next iteration, break away. */
+		if (!need_next_lower_arm && !need_next_upper_arm)
+			break;
+
+		++current_or_arm;
+	}
+
+	/*
+	 * Generate the OR expressions for each of lower and upper bounds (if
+	 * required), and append to the list of implicitly ANDed list of
+	 * expressions.
+	 */
+	if (lower_or_arms != NIL)
+		result = lappend(result,
+						 list_length(lower_or_arms) > 1
+						 ? makeBoolExpr(OR_EXPR, lower_or_arms, -1)
+						 : linitial(lower_or_arms));
+	if (upper_or_arms != NIL)
+		result = lappend(result,
+						 list_length(upper_or_arms) > 1
+						 ? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
+						 : linitial(upper_or_arms));
+
+	/*
+	 * As noted above, for non-default, we return list with constant TRUE. If
+	 * the result is NIL during the recursive call for default, it implies
+	 * this is the only other partition which can hold every value of the key
+	 * except NULL. Hence we return the NullTest result skipped earlier.
+	 */
+	if (result == NIL)
+		result = for_default
+			? get_range_nulltest(key)
+			: list_make1(makeBoolConst(true, false));
+
+	return result;
+}
+
+/*
+ * get_range_key_properties
+ *		Returns range partition key information for a given column
+ *
+ * This is a subroutine for get_qual_for_range, and its API is pretty
+ * specialized to that caller.
+ *
+ * Constructs an Expr for the key column (returned in *keyCol) and Consts
+ * for the lower and upper range limits (returned in *lower_val and
+ * *upper_val).  For MINVALUE/MAXVALUE limits, NULL is returned instead of
+ * a Const.  All of these structures are freshly palloc'd.
+ *
+ * *partexprs_item points to the cell containing the next expression in
+ * the key->partexprs list, or NULL.  It may be advanced upon return.
+ */
+static void
+get_range_key_properties(PartitionKey key, int keynum,
+						 PartitionRangeDatum *ldatum,
+						 PartitionRangeDatum *udatum,
+						 ListCell **partexprs_item,
+						 Expr **keyCol,
+						 Const **lower_val, Const **upper_val)
+{
+	/* Get partition key expression for this column */
+	if (key->partattrs[keynum] != 0)
+	{
+		*keyCol = (Expr *) makeVar(1,
+								   key->partattrs[keynum],
+								   key->parttypid[keynum],
+								   key->parttypmod[keynum],
+								   key->parttypcoll[keynum],
+								   0);
+	}
+	else
+	{
+		if (*partexprs_item == NULL)
+			elog(ERROR, "wrong number of partition key expressions");
+		*keyCol = copyObject(lfirst(*partexprs_item));
+		*partexprs_item = lnext(*partexprs_item);
+	}
+
+	/* Get appropriate Const nodes for the bounds */
+	if (ldatum->kind == PARTITION_RANGE_DATUM_VALUE)
+		*lower_val = castNode(Const, copyObject(ldatum->value));
+	else
+		*lower_val = NULL;
+
+	if (udatum->kind == PARTITION_RANGE_DATUM_VALUE)
+		*upper_val = castNode(Const, copyObject(udatum->value));
+	else
+		*upper_val = NULL;
+}
+
+/*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+	List	   *result = NIL;
+	NullTest   *nulltest;
+	ListCell   *partexprs_item;
+	int			i;
+
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		Expr	   *keyCol;
+
+		if (key->partattrs[i] != 0)
+		{
+			keyCol = (Expr *) makeVar(1,
+									  key->partattrs[i],
+									  key->parttypid[i],
+									  key->parttypmod[i],
+									  key->parttypcoll[i],
+									  0);
+		}
+		else
+		{
+			if (partexprs_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			keyCol = copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		nulltest = makeNode(NullTest);
+		nulltest->arg = keyCol;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+	}
+
+	return result;
+}
diff --git a/src/backend/utils/cache/Makefile b/src/backend/utils/cache/Makefile
index a943f8ea4b..9c24ce6ab4 100644
--- a/src/backend/utils/cache/Makefile
+++ b/src/backend/utils/cache/Makefile
@@ -13,7 +13,7 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = attoptcache.o catcache.o evtcache.o inval.o plancache.o relcache.o \
-	relmapper.o relfilenodemap.o spccache.o syscache.o lsyscache.o \
-	typcache.o ts_cache.o
+	relmapper.o relfilenodemap.o partcache.o spccache.o syscache.o \
+	lsyscache.o typcache.o ts_cache.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c
new file mode 100644
index 0000000000..db91cde40a
--- /dev/null
+++ b/src/backend/utils/cache/partcache.c
@@ -0,0 +1,971 @@
+/*-------------------------------------------------------------------------
+ *
+ * partcache.c
+ *		Support routines for manipulating partition information cached in
+ *		relcache
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		  src/backend/utils/cache/partcache.c
+ *
+ *-------------------------------------------------------------------------
+*/
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/partition.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
+#include "partitioning/partbounds.h"
+#include "partitioning/partprune.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/partcache.h"
+#include "utils/syscache.h"
+
+static int32 qsort_partition_hbound_cmp(const void *a, const void *b);
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
+							   void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
+						   void *arg);
+static List *generate_partition_qual(Relation rel);
+
+/*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is a complex structure; to avoid complicated logic to
+ * free individual elements whenever the relcache entry is flushed, we give it
+ * its own memory context, child of CacheMemoryContext, which can easily be
+ * deleted on its own.  To avoid leaking memory in that context in case of an
+ * error partway through this function, the context is initially created as a
+ * child of CurTransactionContext and only re-parented to CacheMemoryContext
+ * at the end, when no further errors are possible.  Also, we don't make this
+ * context the current context except in very brief code sections, out of fear
+ * that some of our callees allocate memory on their own which would be leaked
+ * permanently.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table form;
+	HeapTuple	tuple;
+	bool		isnull;
+	int			i;
+	PartitionKey key;
+	AttrNumber *attrs;
+	oidvector  *opclass;
+	oidvector  *collation;
+	ListCell   *partexprs_item;
+	Datum		datum;
+	MemoryContext partkeycxt,
+				oldcxt;
+	int16		procnum;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	partkeycxt = AllocSetContextCreate(CurTransactionContext,
+									   "partition key",
+									   ALLOCSET_SMALL_SIZES);
+	MemoryContextCopyAndSetIdentifier(partkeycxt,
+								   RelationGetRelationName(relation));
+
+	key = (PartitionKey) MemoryContextAllocZero(partkeycxt,
+												sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+
+	/*
+	 * We can rely on the first variable-length attribute being mapped to the
+	 * relevant field of the catalog's C struct, because all previous
+	 * attributes are non-nullable and fixed-length.
+	 */
+	attrs = form->partattrs.values;
+
+	/* But use the hard way to retrieve further variable-length attributes */
+	/* Operator class */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partexprs, &isnull);
+	if (!isnull)
+	{
+		char	   *exprString;
+		Node	   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step; fix
+		 * opfuncids while at it.  We don't need to bother with
+		 * canonicalize_qual() though, because partition expressions should be
+		 * in canonical form already (ie, no need for OR-merging or constant
+		 * elimination).
+		 */
+		expr = eval_const_expressions(NULL, expr);
+		fix_opfuncids(expr);
+
+		oldcxt = MemoryContextSwitchTo(partkeycxt);
+		key->partexprs = (List *) copyObject(expr);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	oldcxt = MemoryContextSwitchTo(partkeycxt);
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	MemoryContextSwitchTo(oldcxt);
+
+	/* determine support function number to search for */
+	procnum = (key->strategy == PARTITION_STRATEGY_HASH) ?
+		HASHEXTENDED_PROC : BTORDER_PROC;
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber	attno = key->partattrs[i];
+		HeapTuple	opclasstup;
+		Form_pg_opclass opclassform;
+		Oid			funcid;
+
+		/* Collect opfamily information */
+		opclasstup = SearchSysCache1(CLAOID,
+									 ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(opclasstup))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
+		key->partopfamily[i] = opclassform->opcfamily;
+		key->partopcintype[i] = opclassform->opcintype;
+
+		/* Get a support function for the specified opfamily and datatypes */
+		funcid = get_opfamily_proc(opclassform->opcfamily,
+								   opclassform->opcintype,
+								   opclassform->opcintype,
+								   procnum);
+		if (!OidIsValid(funcid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("operator class \"%s\" of access method %s is missing support function %d for type %s",
+							NameStr(opclassform->opcname),
+							(key->strategy == PARTITION_STRATEGY_HASH) ?
+							"hash" : "btree",
+							procnum,
+							format_type_be(opclassform->opcintype))));
+
+		fmgr_info_cxt(funcid, &key->partsupfunc[i], partkeycxt);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			Form_pg_attribute att = TupleDescAttr(relation->rd_att, attno - 1);
+
+			key->parttypid[i] = att->atttypid;
+			key->parttypmod[i] = att->atttypmod;
+			key->parttypcoll[i] = att->attcollation;
+		}
+		else
+		{
+			if (partexprs_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+
+			partexprs_item = lnext(partexprs_item);
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(opclasstup);
+	}
+
+	ReleaseSysCache(tuple);
+
+	/*
+	 * Success --- reparent our context and make the relcache point to the
+	 * newly constructed key
+	 */
+	MemoryContextSetParent(partkeycxt, CacheMemoryContext);
+	relation->rd_partkeycxt = partkeycxt;
+	relation->rd_partkey = key;
+}
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *inhoids,
+			   *partoids;
+	Oid		   *oids = NULL;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey key = RelationGetPartitionKey(rel);
+	PartitionDesc result;
+	MemoryContext oldcxt;
+
+	int			ndatums = 0;
+	int			default_index = -1;
+
+	/* Hash partitioning specific */
+	PartitionHashBound **hbounds = NULL;
+
+	/* List partitioning specific */
+	PartitionListValue **all_values = NULL;
+	int			null_index = -1;
+
+	/* Range partitioning specific */
+	PartitionRangeBound **rbounds = NULL;
+
+	/* Get partition oids from pg_inherits */
+	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+	/* Collect bound spec nodes in a list */
+	i = 0;
+	partoids = NIL;
+	foreach(cell, inhoids)
+	{
+		Oid			inhrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+
+		tuple = SearchSysCache1(RELOID, inhrelid);
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+		/*
+		 * It is possible that the pg_class tuple of a partition has not been
+		 * updated yet to set its relpartbound field.  The only case where
+		 * this happens is when we open the parent relation to check using its
+		 * partition descriptor that a new partition's bound does not overlap
+		 * some existing partition.
+		 */
+		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+
+		datum = SysCacheGetAttr(RELOID, tuple,
+								Anum_pg_class_relpartbound,
+								&isnull);
+		Assert(!isnull);
+		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+
+		/*
+		 * Sanity check: If the PartitionBoundSpec says this is the default
+		 * partition, its OID should correspond to whatever's stored in
+		 * pg_partitioned_table.partdefid; if not, the catalog is corrupt.
+		 */
+		if (castNode(PartitionBoundSpec, boundspec)->is_default)
+		{
+			Oid			partdefid;
+
+			partdefid = get_default_partition_oid(RelationGetRelid(rel));
+			if (partdefid != inhrelid)
+				elog(ERROR, "expected partdefid %u, but got %u",
+					 inhrelid, partdefid);
+		}
+
+		boundspecs = lappend(boundspecs, boundspec);
+		partoids = lappend_oid(partoids, inhrelid);
+		ReleaseSysCache(tuple);
+	}
+
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc(nparts * sizeof(Oid));
+		i = 0;
+		foreach(cell, partoids)
+			oids[i++] = lfirst_oid(cell);
+
+		/* Convert from node to the internal representation */
+		if (key->strategy == PARTITION_STRATEGY_HASH)
+		{
+			ndatums = nparts;
+			hbounds = (PartitionHashBound **)
+				palloc(nparts * sizeof(PartitionHashBound *));
+
+			i = 0;
+			foreach(cell, boundspecs)
+			{
+				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
+													lfirst(cell));
+
+				if (spec->strategy != PARTITION_STRATEGY_HASH)
+					elog(ERROR, "invalid strategy in partition bound spec");
+
+				hbounds[i] = (PartitionHashBound *)
+					palloc(sizeof(PartitionHashBound));
+
+				hbounds[i]->modulus = spec->modulus;
+				hbounds[i]->remainder = spec->remainder;
+				hbounds[i]->index = i;
+				i++;
+			}
+
+			/* Sort all the bounds in ascending order */
+			qsort(hbounds, nparts, sizeof(PartitionHashBound *),
+				  qsort_partition_hbound_cmp);
+		}
+		else if (key->strategy == PARTITION_STRATEGY_LIST)
+		{
+			List	   *non_null_values = NIL;
+
+			/*
+			 * Create a unified list of non-null values across all partitions.
+			 */
+			i = 0;
+			null_index = -1;
+			foreach(cell, boundspecs)
+			{
+				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
+													lfirst(cell));
+				ListCell   *c;
+
+				if (spec->strategy != PARTITION_STRATEGY_LIST)
+					elog(ERROR, "invalid strategy in partition bound spec");
+
+				/*
+				 * Note the index of the partition bound spec for the default
+				 * partition. There's no datum to add to the list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
+				foreach(c, spec->listdatums)
+				{
+					Const	   *val = castNode(Const, lfirst(c));
+					PartitionListValue *list_value = NULL;
+
+					if (!val->constisnull)
+					{
+						list_value = (PartitionListValue *)
+							palloc0(sizeof(PartitionListValue));
+						list_value->index = i;
+						list_value->value = val->constvalue;
+					}
+					else
+					{
+						/*
+						 * Never put a null into the values array, flag
+						 * instead for the code further down below where we
+						 * construct the actual relcache struct.
+						 */
+						if (null_index != -1)
+							elog(ERROR, "found null more than once");
+						null_index = i;
+					}
+
+					if (list_value)
+						non_null_values = lappend(non_null_values,
+												  list_value);
+				}
+
+				i++;
+			}
+
+			ndatums = list_length(non_null_values);
+
+			/*
+			 * Collect all list values in one array. Alongside the value, we
+			 * also save the index of partition the value comes from.
+			 */
+			all_values = (PartitionListValue **) palloc(ndatums *
+														sizeof(PartitionListValue *));
+			i = 0;
+			foreach(cell, non_null_values)
+			{
+				PartitionListValue *src = lfirst(cell);
+
+				all_values[i] = (PartitionListValue *)
+					palloc(sizeof(PartitionListValue));
+				all_values[i]->value = src->value;
+				all_values[i]->index = src->index;
+				i++;
+			}
+
+			qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
+					  qsort_partition_list_value_cmp, (void *) key);
+		}
+		else if (key->strategy == PARTITION_STRATEGY_RANGE)
+		{
+			int			k;
+			PartitionRangeBound **all_bounds,
+					   *prev;
+
+			all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+														  sizeof(PartitionRangeBound *));
+
+			/*
+			 * Create a unified list of range bounds across all the
+			 * partitions.
+			 */
+			i = ndatums = 0;
+			foreach(cell, boundspecs)
+			{
+				PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
+													lfirst(cell));
+				PartitionRangeBound *lower,
+						   *upper;
+
+				if (spec->strategy != PARTITION_STRATEGY_RANGE)
+					elog(ERROR, "invalid strategy in partition bound spec");
+
+				/*
+				 * Note the index of the partition bound spec for the default
+				 * partition. There's no datum to add to the allbounds array
+				 * for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i++;
+					continue;
+				}
+
+				lower = make_one_range_bound(key, i, spec->lowerdatums,
+											 true);
+				upper = make_one_range_bound(key, i, spec->upperdatums,
+											 false);
+				all_bounds[ndatums++] = lower;
+				all_bounds[ndatums++] = upper;
+				i++;
+			}
+
+			Assert(ndatums == nparts * 2 ||
+				   (default_index != -1 && ndatums == (nparts - 1) * 2));
+
+			/* Sort all the bounds in ascending order */
+			qsort_arg(all_bounds, ndatums,
+					  sizeof(PartitionRangeBound *),
+					  qsort_partition_rbound_cmp,
+					  (void *) key);
+
+			/* Save distinct bounds from all_bounds into rbounds. */
+			rbounds = (PartitionRangeBound **)
+				palloc(ndatums * sizeof(PartitionRangeBound *));
+			k = 0;
+			prev = NULL;
+			for (i = 0; i < ndatums; i++)
+			{
+				PartitionRangeBound *cur = all_bounds[i];
+				bool		is_distinct = false;
+				int			j;
+
+				/* Is the current bound distinct from the previous one? */
+				for (j = 0; j < key->partnatts; j++)
+				{
+					Datum		cmpval;
+
+					if (prev == NULL || cur->kind[j] != prev->kind[j])
+					{
+						is_distinct = true;
+						break;
+					}
+
+					/*
+					 * If the bounds are both MINVALUE or MAXVALUE, stop now
+					 * and treat them as equal, since any values after this
+					 * point must be ignored.
+					 */
+					if (cur->kind[j] != PARTITION_RANGE_DATUM_VALUE)
+						break;
+
+					cmpval = FunctionCall2Coll(&key->partsupfunc[j],
+											   key->partcollation[j],
+											   cur->datums[j],
+											   prev->datums[j]);
+					if (DatumGetInt32(cmpval) != 0)
+					{
+						is_distinct = true;
+						break;
+					}
+				}
+
+				/*
+				 * Only if the bound is distinct save it into a temporary
+				 * array i.e. rbounds which is later copied into boundinfo
+				 * datums array.
+				 */
+				if (is_distinct)
+					rbounds[k++] = all_bounds[i];
+
+				prev = cur;
+			}
+
+			/* Update ndatums to hold the count of distinct datums. */
+			ndatums = k;
+		}
+		else
+			elog(ERROR, "unexpected partition strategy: %d",
+				 (int) key->strategy);
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										  "partition descriptor",
+										  ALLOCSET_DEFAULT_SIZES);
+	MemoryContextCopyAndSetIdentifier(rel->rd_pdcxt, RelationGetRelationName(rel));
+
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		PartitionBoundInfo boundinfo;
+		int		   *mapping;
+		int			next_index = 0;
+
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		boundinfo = (PartitionBoundInfoData *)
+			palloc0(sizeof(PartitionBoundInfoData));
+		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
+		boundinfo->ndatums = ndatums;
+		boundinfo->null_index = -1;
+		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
+
+		/* Initialize mapping array with invalid values */
+		mapping = (int *) palloc(sizeof(int) * nparts);
+		for (i = 0; i < nparts; i++)
+			mapping[i] = -1;
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_HASH:
+				{
+					/* Modulus are stored in ascending order */
+					int			greatest_modulus = hbounds[ndatums - 1]->modulus;
+
+					boundinfo->indexes = (int *) palloc(greatest_modulus *
+														sizeof(int));
+
+					for (i = 0; i < greatest_modulus; i++)
+						boundinfo->indexes[i] = -1;
+
+					for (i = 0; i < nparts; i++)
+					{
+						int			modulus = hbounds[i]->modulus;
+						int			remainder = hbounds[i]->remainder;
+
+						boundinfo->datums[i] = (Datum *) palloc(2 *
+																sizeof(Datum));
+						boundinfo->datums[i][0] = Int32GetDatum(modulus);
+						boundinfo->datums[i][1] = Int32GetDatum(remainder);
+
+						while (remainder < greatest_modulus)
+						{
+							/* overlap? */
+							Assert(boundinfo->indexes[remainder] == -1);
+							boundinfo->indexes[remainder] = i;
+							remainder += modulus;
+						}
+
+						mapping[hbounds[i]->index] = i;
+						pfree(hbounds[i]);
+					}
+					pfree(hbounds);
+					break;
+				}
+
+			case PARTITION_STRATEGY_LIST:
+				{
+					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
+
+					/*
+					 * Copy values.  Indexes of individual values are mapped
+					 * to canonical values so that they match for any two list
+					 * partitioned tables with same number of partitions and
+					 * same lists per partition.  One way to canonicalize is
+					 * to assign the index in all_values[] of the smallest
+					 * value of each partition, as the index of all of the
+					 * partition's values.
+					 */
+					for (i = 0; i < ndatums; i++)
+					{
+						boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
+						boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
+															key->parttypbyval[0],
+															key->parttyplen[0]);
+
+						/* If the old index has no mapping, assign one */
+						if (mapping[all_values[i]->index] == -1)
+							mapping[all_values[i]->index] = next_index++;
+
+						boundinfo->indexes[i] = mapping[all_values[i]->index];
+					}
+
+					/*
+					 * If null-accepting partition has no mapped index yet,
+					 * assign one.  This could happen if such partition
+					 * accepts only null and hence not covered in the above
+					 * loop which only handled non-null values.
+					 */
+					if (null_index != -1)
+					{
+						Assert(null_index >= 0);
+						if (mapping[null_index] == -1)
+							mapping[null_index] = next_index++;
+						boundinfo->null_index = mapping[null_index];
+					}
+
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any value not
+						 * specified in the lists of other partitions, hence
+						 * it should not get mapped index while assigning
+						 * those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
+					/* All partition must now have a valid mapping */
+					Assert(next_index == nparts);
+					break;
+				}
+
+			case PARTITION_STRATEGY_RANGE:
+				{
+					boundinfo->kind = (PartitionRangeDatumKind **)
+						palloc(ndatums *
+							   sizeof(PartitionRangeDatumKind *));
+					boundinfo->indexes = (int *) palloc((ndatums + 1) *
+														sizeof(int));
+
+					for (i = 0; i < ndatums; i++)
+					{
+						int			j;
+
+						boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
+																sizeof(Datum));
+						boundinfo->kind[i] = (PartitionRangeDatumKind *)
+							palloc(key->partnatts *
+								   sizeof(PartitionRangeDatumKind));
+						for (j = 0; j < key->partnatts; j++)
+						{
+							if (rbounds[i]->kind[j] == PARTITION_RANGE_DATUM_VALUE)
+								boundinfo->datums[i][j] =
+									datumCopy(rbounds[i]->datums[j],
+											  key->parttypbyval[j],
+											  key->parttyplen[j]);
+							boundinfo->kind[i][j] = rbounds[i]->kind[j];
+						}
+
+						/*
+						 * There is no mapping for invalid indexes.
+						 *
+						 * Any lower bounds in the rbounds array have invalid
+						 * indexes assigned, because the values between the
+						 * previous bound (if there is one) and this (lower)
+						 * bound are not part of the range of any existing
+						 * partition.
+						 */
+						if (rbounds[i]->lower)
+							boundinfo->indexes[i] = -1;
+						else
+						{
+							int			orig_index = rbounds[i]->index;
+
+							/* If the old index has no mapping, assign one */
+							if (mapping[orig_index] == -1)
+								mapping[orig_index] = next_index++;
+
+							boundinfo->indexes[i] = mapping[orig_index];
+						}
+					}
+
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						Assert(default_index >= 0 && mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+					boundinfo->indexes[i] = -1;
+					break;
+				}
+
+			default:
+				elog(ERROR, "unexpected partition strategy: %d",
+					 (int) key->strategy);
+		}
+
+		result->boundinfo = boundinfo;
+
+		/*
+		 * Now assign OIDs from the original array into mapped indexes of the
+		 * result array.  Order of OIDs in the former is defined by the
+		 * catalog scan that retrieved them, whereas that in the latter is
+		 * defined by canonicalized representation of the partition bounds.
+		 */
+		for (i = 0; i < nparts; i++)
+			result->oids[mapping[i]] = oids[i];
+		pfree(mapping);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel);
+}
+
+/*
+ * get_partition_qual_relid
+ *
+ * Returns an expression tree describing the passed-in relation's partition
+ * constraint. If there is no partition constraint returns NULL; this can
+ * happen if the default partition is the only partition.
+ */
+Expr *
+get_partition_qual_relid(Oid relid)
+{
+	Relation	rel = heap_open(relid, AccessShareLock);
+	Expr	   *result = NULL;
+	List	   *and_args;
+
+	/* Do the work only if this relation is a partition. */
+	if (rel->rd_rel->relispartition)
+	{
+		and_args = generate_partition_qual(rel);
+
+		if (and_args == NIL)
+			result = NULL;
+		else if (list_length(and_args) > 1)
+			result = makeBoolExpr(AND_EXPR, and_args, -1);
+		else
+			result = linitial(and_args);
+	}
+
+	/* Keep the lock. */
+	heap_close(rel, NoLock);
+
+	return result;
+}
+
+/*
+ * qsort_partition_hbound_cmp
+ *
+ * We sort hash bounds by modulus, then by remainder.
+ */
+static int32
+qsort_partition_hbound_cmp(const void *a, const void *b)
+{
+	PartitionHashBound *h1 = (*(PartitionHashBound *const *) a);
+	PartitionHashBound *h2 = (*(PartitionHashBound *const *) b);
+
+	return partition_hbound_cmp(h1->modulus, h1->remainder,
+								h2->modulus, h2->remainder);
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list partition bound datums
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+	Datum		val1 = (*(const PartitionListValue **) a)->value,
+				val2 = (*(const PartitionListValue **) b)->value;
+	PartitionKey key = (PartitionKey) arg;
+
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+	PartitionKey key = (PartitionKey) arg;
+
+	return partition_rbound_cmp(key->partnatts, key->partsupfunc,
+								key->partcollation, b1->datums, b1->kind,
+								b1->lower, b2);
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression. The
+ * function returns a NIL list if there is no predicate.
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel)
+{
+	HeapTuple	tuple;
+	MemoryContext oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	PartitionBoundSpec *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+	bool		found_whole_row;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Quick copy */
+	if (rel->rd_partcheck != NIL)
+		return copyObject(rel->rd_partcheck);
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Get pg_class.relpartbound */
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u",
+			 RelationGetRelid(rel));
+
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	if (isnull)					/* should not happen */
+		elog(ERROR, "relation \"%s\" has relpartbound = null",
+			 RelationGetRelationName(rel));
+	bound = castNode(PartitionBoundSpec,
+					 stringToNode(TextDatumGetCString(boundDatum)));
+	ReleaseSysCache(tuple);
+
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* Add the parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition)
+		result = list_concat(generate_partition_qual(parent), my_qual);
+	else
+		result = my_qual;
+
+	/*
+	 * Change Vars to have partition's attnos instead of the parent's. We do
+	 * this after we concatenate the parent's quals, because we want every Var
+	 * in it to bear this relation's attnos. It's safe to assume varno = 1
+	 * here.
+	 */
+	result = map_partition_varattnos(result, 1, rel, parent,
+									 &found_whole_row);
+	/* There can never be a whole-row reference here */
+	if (found_whole_row)
+		elog(ERROR, "unexpected whole-row reference found in partition key");
+
+	/* Save a copy in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(result);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Keep the parent locked until commit */
+	heap_close(parent, NoLock);
+
+	return result;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index dfa95fed30..5c17fe1742 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -85,6 +85,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/partcache.h"
 #include "utils/relmapper.h"
 #include "utils/resowner_private.h"
 #include "utils/snapmgr.h"
@@ -265,7 +266,6 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
-static void RelationBuildPartitionKey(Relation relation);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -873,211 +873,6 @@ RelationBuildRuleLock(Relation relation)
 	relation->rd_rules = rulelock;
 }
 
-/*
- * RelationBuildPartitionKey
- *		Build and attach to relcache partition key data of relation
- *
- * Partitioning key data is a complex structure; to avoid complicated logic to
- * free individual elements whenever the relcache entry is flushed, we give it
- * its own memory context, child of CacheMemoryContext, which can easily be
- * deleted on its own.  To avoid leaking memory in that context in case of an
- * error partway through this function, the context is initially created as a
- * child of CurTransactionContext and only re-parented to CacheMemoryContext
- * at the end, when no further errors are possible.  Also, we don't make this
- * context the current context except in very brief code sections, out of fear
- * that some of our callees allocate memory on their own which would be leaked
- * permanently.
- */
-static void
-RelationBuildPartitionKey(Relation relation)
-{
-	Form_pg_partitioned_table form;
-	HeapTuple	tuple;
-	bool		isnull;
-	int			i;
-	PartitionKey key;
-	AttrNumber *attrs;
-	oidvector  *opclass;
-	oidvector  *collation;
-	ListCell   *partexprs_item;
-	Datum		datum;
-	MemoryContext partkeycxt,
-				oldcxt;
-	int16		procnum;
-
-	tuple = SearchSysCache1(PARTRELID,
-							ObjectIdGetDatum(RelationGetRelid(relation)));
-
-	/*
-	 * The following happens when we have created our pg_class entry but not
-	 * the pg_partitioned_table entry yet.
-	 */
-	if (!HeapTupleIsValid(tuple))
-		return;
-
-	partkeycxt = AllocSetContextCreate(CurTransactionContext,
-									   "partition key",
-									   ALLOCSET_SMALL_SIZES);
-	MemoryContextCopyAndSetIdentifier(partkeycxt,
-								   RelationGetRelationName(relation));
-
-	key = (PartitionKey) MemoryContextAllocZero(partkeycxt,
-												sizeof(PartitionKeyData));
-
-	/* Fixed-length attributes */
-	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
-	key->strategy = form->partstrat;
-	key->partnatts = form->partnatts;
-
-	/*
-	 * We can rely on the first variable-length attribute being mapped to the
-	 * relevant field of the catalog's C struct, because all previous
-	 * attributes are non-nullable and fixed-length.
-	 */
-	attrs = form->partattrs.values;
-
-	/* But use the hard way to retrieve further variable-length attributes */
-	/* Operator class */
-	datum = SysCacheGetAttr(PARTRELID, tuple,
-							Anum_pg_partitioned_table_partclass, &isnull);
-	Assert(!isnull);
-	opclass = (oidvector *) DatumGetPointer(datum);
-
-	/* Collation */
-	datum = SysCacheGetAttr(PARTRELID, tuple,
-							Anum_pg_partitioned_table_partcollation, &isnull);
-	Assert(!isnull);
-	collation = (oidvector *) DatumGetPointer(datum);
-
-	/* Expressions */
-	datum = SysCacheGetAttr(PARTRELID, tuple,
-							Anum_pg_partitioned_table_partexprs, &isnull);
-	if (!isnull)
-	{
-		char	   *exprString;
-		Node	   *expr;
-
-		exprString = TextDatumGetCString(datum);
-		expr = stringToNode(exprString);
-		pfree(exprString);
-
-		/*
-		 * Run the expressions through const-simplification since the planner
-		 * will be comparing them to similarly-processed qual clause operands,
-		 * and may fail to detect valid matches without this step; fix
-		 * opfuncids while at it.  We don't need to bother with
-		 * canonicalize_qual() though, because partition expressions should be
-		 * in canonical form already (ie, no need for OR-merging or constant
-		 * elimination).
-		 */
-		expr = eval_const_expressions(NULL, expr);
-		fix_opfuncids(expr);
-
-		oldcxt = MemoryContextSwitchTo(partkeycxt);
-		key->partexprs = (List *) copyObject(expr);
-		MemoryContextSwitchTo(oldcxt);
-	}
-
-	oldcxt = MemoryContextSwitchTo(partkeycxt);
-	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
-	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
-
-	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-
-	/* Gather type and collation info as well */
-	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
-	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
-	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
-	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
-	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-	MemoryContextSwitchTo(oldcxt);
-
-	/* determine support function number to search for */
-	procnum = (key->strategy == PARTITION_STRATEGY_HASH) ?
-		HASHEXTENDED_PROC : BTORDER_PROC;
-
-	/* Copy partattrs and fill other per-attribute info */
-	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
-	partexprs_item = list_head(key->partexprs);
-	for (i = 0; i < key->partnatts; i++)
-	{
-		AttrNumber	attno = key->partattrs[i];
-		HeapTuple	opclasstup;
-		Form_pg_opclass opclassform;
-		Oid			funcid;
-
-		/* Collect opfamily information */
-		opclasstup = SearchSysCache1(CLAOID,
-									 ObjectIdGetDatum(opclass->values[i]));
-		if (!HeapTupleIsValid(opclasstup))
-			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
-
-		opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
-		key->partopfamily[i] = opclassform->opcfamily;
-		key->partopcintype[i] = opclassform->opcintype;
-
-		/* Get a support function for the specified opfamily and datatypes */
-		funcid = get_opfamily_proc(opclassform->opcfamily,
-								   opclassform->opcintype,
-								   opclassform->opcintype,
-								   procnum);
-		if (!OidIsValid(funcid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("operator class \"%s\" of access method %s is missing support function %d for type %s",
-							NameStr(opclassform->opcname),
-							(key->strategy == PARTITION_STRATEGY_HASH) ?
-							"hash" : "btree",
-							procnum,
-							format_type_be(opclassform->opcintype))));
-
-		fmgr_info_cxt(funcid, &key->partsupfunc[i], partkeycxt);
-
-		/* Collation */
-		key->partcollation[i] = collation->values[i];
-
-		/* Collect type information */
-		if (attno != 0)
-		{
-			Form_pg_attribute att = TupleDescAttr(relation->rd_att, attno - 1);
-
-			key->parttypid[i] = att->atttypid;
-			key->parttypmod[i] = att->atttypmod;
-			key->parttypcoll[i] = att->attcollation;
-		}
-		else
-		{
-			if (partexprs_item == NULL)
-				elog(ERROR, "wrong number of partition key expressions");
-
-			key->parttypid[i] = exprType(lfirst(partexprs_item));
-			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
-			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
-
-			partexprs_item = lnext(partexprs_item);
-		}
-		get_typlenbyvalalign(key->parttypid[i],
-							 &key->parttyplen[i],
-							 &key->parttypbyval[i],
-							 &key->parttypalign[i]);
-
-		ReleaseSysCache(opclasstup);
-	}
-
-	ReleaseSysCache(tuple);
-
-	/*
-	 * Success --- reparent our context and make the relcache point to the
-	 * newly constructed key
-	 */
-	MemoryContextSetParent(partkeycxt, CacheMemoryContext);
-	relation->rd_partkeycxt = partkeycxt;
-	relation->rd_partkey = key;
-}
-
 /*
  *		equalRuleLocks
  *
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index b25e25bf9d..6ed013380b 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -16,20 +16,13 @@
 #include "fmgr.h"
 #include "executor/tuptable.h"
 #include "nodes/execnodes.h"
+#include "partitioning/partbounds.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
 /* Seed for the extended hash function */
 #define HASH_PARTITION_SEED UINT64CONST(0x7A5B22367996DCFD)
 
-/*
- * PartitionBoundInfo encapsulates a set of partition bounds.  It is usually
- * associated with partitioned tables as part of its partition descriptor.
- *
- * The internal structure appears in partbounds.h.
- */
-typedef struct PartitionBoundInfoData *PartitionBoundInfo;
-
 /*
  * Information about partitions of a partitioned table.
  */
@@ -42,35 +35,17 @@ typedef struct PartitionDescData
 
 typedef struct PartitionDescData *PartitionDesc;
 
-extern void RelationBuildPartitionDesc(Relation relation);
-extern bool partition_bounds_equal(int partnatts, int16 *parttyplen,
-					   bool *parttypbyval, PartitionBoundInfo b1,
-					   PartitionBoundInfo b2);
-extern PartitionBoundInfo partition_bounds_copy(PartitionBoundInfo src,
-					  PartitionKey key);
-
-extern void check_new_partition_bound(char *relname, Relation parent,
-						  PartitionBoundSpec *spec);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_partition_ancestors(Oid relid);
-extern List *get_qual_from_partbound(Relation rel, Relation parent,
-						PartitionBoundSpec *spec);
 extern List *map_partition_varattnos(List *expr, int fromrel_varno,
 						Relation to_rel, Relation from_rel,
 						bool *found_whole_row);
-extern List *RelationGetPartitionQual(Relation rel);
-extern Expr *get_partition_qual_relid(Oid relid);
 extern bool has_partition_attrs(Relation rel, Bitmapset *attnums,
 					bool *used_in_expr);
 
 extern Oid	get_default_oid_from_partdesc(PartitionDesc partdesc);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
-extern void check_default_allows_bound(Relation parent, Relation defaultRel,
-						   PartitionBoundSpec *new_spec);
 extern List *get_proposed_default_constraint(List *new_part_constaints);
 
-extern int get_partition_for_tuple(Relation relation, Datum *values,
-						bool *isnull);
-
 #endif							/* PARTITION_H */
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index 0c36c8be30..e81bdc4a0a 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -13,7 +13,6 @@
 #ifndef EXECPARTITION_H
 #define EXECPARTITION_H
 
-#include "catalog/partition.h"
 #include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/plannodes.h"
diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h
index c76014d4a8..8bae4b2642 100644
--- a/src/include/partitioning/partbounds.h
+++ b/src/include/partitioning/partbounds.h
@@ -11,8 +11,8 @@
 #ifndef PARTBOUNDS_H
 #define PARTBOUNDS_H
 
-#include "catalog/partition.h"
-
+#include "nodes/parsenodes.h"
+#include "utils/partcache.h"
 
 /*
  * PartitionBoundInfoData encapsulates a set of partition bounds. It is
@@ -68,6 +68,8 @@ typedef struct PartitionBoundInfoData
 								 * isn't one */
 } PartitionBoundInfoData;
 
+typedef struct PartitionBoundInfoData *PartitionBoundInfo;
+
 #define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1)
 #define partition_bound_has_default(bi) ((bi)->default_index != -1)
 
@@ -101,7 +103,34 @@ typedef struct PartitionRangeBound
 } PartitionRangeBound;
 
 extern int	get_hash_partition_greatest_modulus(PartitionBoundInfo b);
-extern int partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
+extern uint64 compute_hash_value(int partnatts, FmgrInfo *partsupfunc,
+				   Datum *values, bool *isnull);
+extern List *get_qual_from_partbound(Relation rel, Relation parent,
+						PartitionBoundSpec *spec);
+extern bool partition_bounds_equal(int partnatts, int16 *parttyplen,
+					   bool *parttypbyval, PartitionBoundInfo b1,
+					   PartitionBoundInfo b2);
+extern PartitionBoundInfo partition_bounds_copy(PartitionBoundInfo src,
+					  PartitionKey key);
+extern void check_new_partition_bound(char *relname, Relation parent,
+						  PartitionBoundSpec *spec);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+
+extern PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
+					 List *datums, bool lower);
+extern int32 partition_hbound_cmp(int modulus1, int remainder1, int modulus2,
+					 int remainder2);
+extern int32 partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
+					 Oid *partcollation, Datum *datums1,
+					 PartitionRangeDatumKind *kind1, bool lower1,
+					 PartitionRangeBound *b2);
+extern int32 partition_rbound_datum_cmp(FmgrInfo *partsupfunc,
+						   Oid *partcollation,
+						   Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
+						   Datum *tuple_datums, int n_tuple_datums);
+extern int partition_list_bsearch(FmgrInfo *partsupfunc,
+					   Oid *partcollation,
 					   PartitionBoundInfo boundinfo,
 					   Datum value, bool *is_equal);
 extern int partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
@@ -114,11 +143,5 @@ extern int partition_range_datum_bsearch(FmgrInfo *partsupfunc,
 							  int nvalues, Datum *values, bool *is_equal);
 extern int partition_hash_bsearch(PartitionBoundInfo boundinfo,
 					   int modulus, int remainder);
-extern uint64 compute_hash_value(int partnatts, FmgrInfo *partsupfunc,
-				   Datum *values, bool *isnull);
-extern int32 partition_rbound_datum_cmp(FmgrInfo *partsupfunc,
-						   Oid *partcollation,
-						   Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
-						   Datum *tuple_datums, int n_tuple_datums);
 
 #endif							/* PARTBOUNDS_H */
diff --git a/src/include/utils/partcache.h b/src/include/utils/partcache.h
new file mode 100644
index 0000000000..b9bde9fb7b
--- /dev/null
+++ b/src/include/utils/partcache.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * partcache.h
+ *
+ * Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partcache.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTCACHE_H
+#define PARTCACHE_H
+
+#include "utils/relcache.h"
+
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partition key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partition key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+}			PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+extern void RelationBuildPartitionKey(Relation relation);
+extern void RelationBuildPartitionDesc(Relation rel);
+extern List *RelationGetPartitionQual(Relation rel);
+extern Expr *get_partition_qual_relid(Oid relid);
+
+#endif							/* PARTCACHE_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ffffde01da..bc74d04ce5 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -26,6 +26,7 @@
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/reltrigger.h"
+#include "utils/partcache.h"
 
 
 /*
@@ -46,36 +47,6 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
-/*
- * Information about the partition key of a relation
- */
-typedef struct PartitionKeyData
-{
-	char		strategy;		/* partitioning strategy */
-	int16		partnatts;		/* number of columns in the partition key */
-	AttrNumber *partattrs;		/* attribute numbers of columns in the
-								 * partition key */
-	List	   *partexprs;		/* list of expressions in the partitioning
-								 * key, or NIL */
-
-	Oid		   *partopfamily;	/* OIDs of operator families */
-	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
-	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
-
-	/* Partitioning collation per attribute */
-	Oid		   *partcollation;
-
-	/* Type information per attribute */
-	Oid		   *parttypid;
-	int32	   *parttypmod;
-	int16	   *parttyplen;
-	bool	   *parttypbyval;
-	char	   *parttypalign;
-	Oid		   *parttypcoll;
-}			PartitionKeyData;
-
-typedef struct PartitionKeyData *PartitionKey;
-
 /*
  * Here are the contents of a relation cache entry.
  */
-- 
2.15.1 (Apple Git-101)

v4-0002-Reorganize-partitioning-cache-info.patchapplication/octet-stream; name=v4-0002-Reorganize-partitioning-cache-info.patchDownload
From 8966c8d2665e5a7955212472e5e0d7aa7fa48259 Mon Sep 17 00:00:00 2001
From: Amit <amitlangote09@gmail.com>
Date: Sat, 14 Apr 2018 00:22:55 +0900
Subject: [PATCH v4 2/2] Reorganize partitioning cache info

---
 src/backend/catalog/heap.c             |   2 +-
 src/backend/catalog/partition.c        |  53 +++----
 src/backend/catalog/pg_constraint.c    |   8 +-
 src/backend/commands/indexcmds.c       |   8 +-
 src/backend/commands/tablecmds.c       |  75 +++++-----
 src/backend/commands/trigger.c         |  22 +--
 src/backend/executor/execMain.c        |   1 -
 src/backend/executor/execPartition.c   |  84 ++++++-----
 src/backend/optimizer/path/joinrels.c  |   2 +-
 src/backend/optimizer/prep/prepunion.c |  13 +-
 src/backend/optimizer/util/plancat.c   |  77 ++++------
 src/backend/optimizer/util/relnode.c   |   2 +-
 src/backend/parser/parse_utilcmd.c     |   4 +-
 src/backend/partitioning/partbounds.c  |  49 ++++---
 src/backend/partitioning/partprune.c   |  33 +++--
 src/backend/utils/adt/ruleutils.c      |   1 -
 src/backend/utils/cache/partcache.c    | 252 ++++++++++++++++++++++++++++-----
 src/backend/utils/cache/relcache.c     | 134 +++++-------------
 src/include/catalog/partition.h        |  15 --
 src/include/commands/tablecmds.h       |   2 +-
 src/include/executor/execPartition.h   |  12 +-
 src/include/partitioning/partbounds.h  |   1 -
 src/include/partitioning/partprune.h   |   4 +-
 src/include/utils/partcache.h          |  17 ++-
 src/include/utils/rel.h                |  33 +++--
 25 files changed, 520 insertions(+), 384 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 39813de991..bdb47001c8 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -3473,7 +3473,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	 * relcache entry for that partition every time a partition is added or
 	 * removed.
 	 */
-	defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(parent));
+	defaultPartOid = RelationGetDefaultPartitionOid(parent);
 	if (OidIsValid(defaultPartOid))
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index ad26adaf2f..e7e18d37da 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -274,28 +274,11 @@ has_partition_attrs(Relation rel, Bitmapset *attnums,
 	return false;
 }
 
-/*
- * get_default_oid_from_partdesc
- *
- * Given a partition descriptor, return the OID of the default partition, if
- * one exists; else, return InvalidOid.
- */
-Oid
-get_default_oid_from_partdesc(PartitionDesc partdesc)
-{
-	if (partdesc && partdesc->boundinfo &&
-		partition_bound_has_default(partdesc->boundinfo))
-		return partdesc->oids[partdesc->boundinfo->default_index];
-
-	return InvalidOid;
-}
-
 /*
  * get_default_partition_oid
  *
  * Given a relation OID, return the OID of the default partition, if one
- * exists.  Use get_default_oid_from_partdesc where possible, for
- * efficiency.
+ * exists.  Use get_default_partition_oid where possible, for efficiency.
  */
 Oid
 get_default_partition_oid(Oid parentId)
@@ -475,23 +458,39 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 	if (my_extra == NULL || my_extra->relid != parentId)
 	{
 		Relation	parent;
-		PartitionKey key;
+		PartitionKey	key;
+		FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
 		int			j;
 
 		/* Open parent relation and fetch partition keyinfo */
 		parent = try_relation_open(parentId, AccessShareLock);
 		if (parent == NULL)
 			PG_RETURN_NULL();
-		key = RelationGetPartitionKey(parent);
 
 		/* Reject parent table that is not hash-partitioned. */
-		if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE ||
-			key->strategy != PARTITION_STRATEGY_HASH)
+		if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("\"%s\" is not a hash partitioned table",
 							get_rel_name(parentId))));
 
+		key = RelationGetPartitionKey(parent);
+		Assert(key != NULL);
+
+		if (key->strategy != PARTITION_STRATEGY_HASH)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("\"%s\" is not a hash partitioned table",
+							get_rel_name(parentId))));
+
+		/* Get partsupfunc FmgrInfo's. */
+		for (j = 0; j < key->partnatts; j++)
+		{
+			fmgr_info_copy(&partsupfunc[j],
+						   partition_getprocinfo(parent, key, j),
+						   CurrentMemoryContext);
+		}
+
 		if (!get_fn_expr_variadic(fcinfo->flinfo))
 		{
 			int			nargs = PG_NARGS() - 3;
@@ -506,7 +505,8 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 			/* allocate space for our cache */
 			fcinfo->flinfo->fn_extra =
 				MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
-									   offsetof(ColumnsHashData, partsupfunc) +
+									   offsetof(ColumnsHashData,
+												partsupfunc) +
 									   sizeof(FmgrInfo) * nargs);
 			my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
 			my_extra->relid = parentId;
@@ -524,7 +524,7 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 									j + 1, format_type_be(key->parttypid[j]), format_type_be(argtype))));
 
 				fmgr_info_copy(&my_extra->partsupfunc[j],
-							   &key->partsupfunc[j],
+							   &partsupfunc[j],
 							   fcinfo->flinfo->fn_mcxt);
 			}
 
@@ -536,7 +536,8 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 			/* allocate space for our cache -- just one FmgrInfo in this case */
 			fcinfo->flinfo->fn_extra =
 				MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
-									   offsetof(ColumnsHashData, partsupfunc) +
+									   offsetof(ColumnsHashData,
+												partsupfunc) +
 									   sizeof(FmgrInfo));
 			my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
 			my_extra->relid = parentId;
@@ -558,7 +559,7 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 									format_type_be(my_extra->variadic_type))));
 
 			fmgr_info_copy(&my_extra->partsupfunc[0],
-						   &key->partsupfunc[0],
+						   &partsupfunc[0],
 						   fcinfo->flinfo->fn_mcxt);
 		}
 
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8ba9890ca6..32386b246f 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -623,12 +623,14 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
 
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDesc	partdesc = RelationGetPartitionDesc(rel);
+		int			nparts = RelationGetPartitionCount(rel);
+		Oid		   *partoids = RelationGetPartitionOids(rel);
 		int			i;
 
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 			CloneForeignKeyConstraints(RelationGetRelid(rel),
-									   partdesc->oids[i],
+									   partoids[i],
 									   cloned);
 	}
 
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 78302544db..913faddb55 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -877,15 +877,12 @@ DefineIndex(Oid relationId,
 		 */
 		if (!stmt->relation || stmt->relation->inh)
 		{
-			PartitionDesc partdesc = RelationGetPartitionDesc(rel);
-			int			nparts = partdesc->nparts;
-			Oid		   *part_oids = palloc(sizeof(Oid) * nparts);
+			int			nparts = RelationGetPartitionCount(rel);
+			Oid		   *part_oids = RelationGetPartitionOids(rel);
 			bool		invalidate_parent = false;
 			TupleDesc	parentDesc;
 			Oid		   *opfamOids;
 
-			memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);
-
 			parentDesc = CreateTupleDescCopy(RelationGetDescr(rel));
 			opfamOids = palloc(sizeof(Oid) * numberOfKeyAttributes);
 			for (i = 0; i < numberOfKeyAttributes; i++)
@@ -901,6 +898,7 @@ DefineIndex(Oid relationId,
 			 * If none matches, build a new index by calling ourselves
 			 * recursively with the same options (except for the index name).
 			 */
+			Assert(part_oids != NULL || nparts == 0);
 			for (i = 0; i < nparts; i++)
 			{
 				Oid		childRelid = part_oids[i];
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c1a9bda433..fe4265d4bb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -829,8 +829,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 * the places such that lock parent, lock default partition and then
 		 * lock the partition so as to avoid a deadlock.
 		 */
-		defaultPartOid =
-			get_default_oid_from_partdesc(RelationGetPartitionDesc(parent));
+		defaultPartOid = RelationGetDefaultPartitionOid(parent);
 		if (OidIsValid(defaultPartOid))
 			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
@@ -5864,18 +5863,14 @@ ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
 	 * If the parent is a partitioned table, like check constraints, we do not
 	 * support removing the NOT NULL while partitions exist.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
-
-		Assert(partdesc != NULL);
-		if (partdesc->nparts > 0 && !recurse && !recursing)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot remove constraint from only the partitioned table when partitions exist"),
-					 errhint("Do not specify the ONLY keyword.")));
-	}
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		RelationGetPartitionCount(rel) > 0 && !recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot remove constraint from only the partitioned table when partitions exist"),
+				 errhint("Do not specify the ONLY keyword.")));
 }
+
 static ObjectAddress
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
@@ -6009,16 +6004,12 @@ ATPrepSetNotNull(Relation rel, bool recurse, bool recursing)
 	 * constraints must be added to the child tables.  Complain if requested
 	 * otherwise and partitions exist.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
-
-		if (partdesc && partdesc->nparts > 0 && !recurse && !recursing)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot add constraint to only the partitioned table when partitions exist"),
-					 errhint("Do not specify the ONLY keyword.")));
-	}
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		RelationGetPartitionCount(rel) > 0 && !recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add constraint to only the partitioned table when partitions exist"),
+				 errhint("Do not specify the ONLY keyword.")));
 }
 
 /*
@@ -7699,13 +7690,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 */
 	if (recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDesc partdesc;
+		int			nparts = RelationGetPartitionCount(rel);
+		Oid		   *partoids = RelationGetPartitionOids(rel);
 
-		partdesc = RelationGetPartitionDesc(rel);
-
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
-			Oid			partitionId = partdesc->oids[i];
+			Oid			partitionId = partoids[i];
 			Relation	partition = heap_open(partitionId, lockmode);
 			AlteredTableInfo *childtab;
 			ObjectAddress childAddr;
@@ -13989,10 +13980,12 @@ QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
 	}
 	else if (scanrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(scanrel);
+		int			nparts = RelationGetPartitionCount(scanrel);
+		Oid		   *partoids = RelationGetPartitionOids(scanrel);
 		int			i;
 
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
 			Relation	part_rel;
 			bool		found_whole_row;
@@ -14001,7 +13994,7 @@ QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
 			/*
 			 * This is the minimum lock we need to prevent deadlocks.
 			 */
-			part_rel = heap_open(partdesc->oids[i], AccessExclusiveLock);
+			part_rel = heap_open(partoids[i], AccessExclusiveLock);
 
 			/*
 			 * Adjust the constraint for scanrel so that it matches this
@@ -14051,8 +14044,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * We must lock the default partition if one exists, because attaching a
 	 * new partition will change its partition constraint.
 	 */
-	defaultPartOid =
-		get_default_oid_from_partdesc(RelationGetPartitionDesc(rel));
+	defaultPartOid = RelationGetDefaultPartitionOid(rel);
 	if (OidIsValid(defaultPartOid))
 		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
@@ -14628,8 +14620,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	 * We must lock the default partition, because detaching this partition
 	 * will change its partition constraint.
 	 */
-	defaultPartOid =
-		get_default_oid_from_partdesc(RelationGetPartitionDesc(rel));
+	defaultPartOid = RelationGetDefaultPartitionOid(rel);
 	if (OidIsValid(defaultPartOid))
 		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
@@ -14830,8 +14821,9 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 		AttrNumber *attmap;
 		bool		found;
 		int			i;
-		PartitionDesc partDesc;
-		Oid			constraintOid,
+		int			nparts = RelationGetPartitionCount(parentTbl);
+		Oid		   *partoids = RelationGetPartitionOids(parentTbl),
+					constraintOid,
 					cldConstrId = InvalidOid;
 
 		/*
@@ -14849,11 +14841,11 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 							   RelationGetRelationName(partIdx))));
 
 		/* Make sure it indexes a partition of the other index's table */
-		partDesc = RelationGetPartitionDesc(parentTbl);
 		found = false;
-		for (i = 0; i < partDesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
-			if (partDesc->oids[i] == state.partitionOid)
+			if (partoids[i] == state.partitionOid)
 			{
 				found = true;
 				break;
@@ -14984,6 +14976,7 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
 	int				tuples = 0;
 	HeapTuple		inhTup;
 	bool			updated = false;
+	int				nparts = RelationGetPartitionCount(partedTbl);
 
 	Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
 
@@ -15023,7 +15016,7 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
 	 * If we found as many inherited indexes as the partitioned table has
 	 * partitions, we're good; update pg_index to set indisvalid.
 	 */
-	if (tuples == RelationGetPartitionDesc(partedTbl)->nparts)
+	if (tuples == nparts)
 	{
 		Relation	idxRel;
 		HeapTuple	newtup;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index cc50691aa0..d4fdf1b556 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1086,7 +1086,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 */
 	if (partition_recurse)
 	{
-		PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+		int			nparts = RelationGetPartitionCount(rel);
+		Oid		   *partoids = RelationGetPartitionOids(rel);
 		List	   *idxs = NIL;
 		List	   *childTbls = NIL;
 		ListCell   *l;
@@ -1118,7 +1119,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		oldcxt = MemoryContextSwitchTo(perChildCxt);
 
 		/* Iterate to create the trigger on each existing partition */
-		for (i = 0; i < partdesc->nparts; i++)
+		Assert(partoids != NULL || nparts == 0);
+		for (i = 0; i < nparts; i++)
 		{
 			Oid			indexOnChild = InvalidOid;
 			ListCell   *l2;
@@ -1127,14 +1129,14 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 			Node	   *qual;
 			bool		found_whole_row;
 
-			childTbl = heap_open(partdesc->oids[i], ShareRowExclusiveLock);
+			childTbl = heap_open(partoids[i], ShareRowExclusiveLock);
 
 			/* Find which of the child indexes is the one on this partition */
 			if (OidIsValid(indexOid))
 			{
 				forboth(l, idxs, l2, childTbls)
 				{
-					if (lfirst_oid(l2) == partdesc->oids[i])
+					if (lfirst_oid(l2) == partoids[i])
 					{
 						indexOnChild = lfirst_oid(l);
 						break;
@@ -1143,7 +1145,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 				if (!OidIsValid(indexOnChild))
 					elog(ERROR, "failed to find index matching index \"%s\" in partition \"%s\"",
 						 get_rel_name(indexOid),
-						 get_rel_name(partdesc->oids[i]));
+						 get_rel_name(partoids[i]));
 			}
 
 			/*
@@ -1171,7 +1173,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 				elog(ERROR, "unexpected whole-row reference found in trigger WHEN clause");
 
 			CreateTrigger(childStmt, queryString,
-						  partdesc->oids[i], refRelOid,
+						  partoids[i], refRelOid,
 						  InvalidOid, indexOnChild,
 						  funcoid, trigoid, qual,
 						  isInternal, true);
@@ -1854,14 +1856,16 @@ EnableDisableTrigger(Relation rel, const char *tgname,
 			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
 				(TRIGGER_FOR_ROW(oldtrig->tgtype)))
 			{
-				PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+				int		nparts = RelationGetPartitionCount(rel);
+				Oid	   *partoids = RelationGetPartitionOids(rel);
 				int			i;
 
-				for (i = 0; i < partdesc->nparts; i++)
+				Assert(partoids != NULL || nparts == 0);
+				for (i = 0; i < nparts; i++)
 				{
 					Relation	part;
 
-					part = relation_open(partdesc->oids[i], lockmode);
+					part = relation_open(partoids[i], lockmode);
 					EnableDisableTrigger(part, NameStr(oldtrig->tgname),
 										 fires_when, skip_system, lockmode);
 					heap_close(part, NoLock);	/* keep lock till commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fa713bbe5..e885a16647 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,7 +42,6 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
-#include "catalog/partition.h"
 #include "catalog/pg_publication.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 1bf4412be5..e7652cbd25 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -22,6 +22,7 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "partitioning/partbounds.h"
 #include "utils/lsyscache.h"
 #include "utils/rls.h"
 #include "utils/ruleutils.h"
@@ -35,7 +36,7 @@ static void FormPartitionKeyDatum(PartitionDispatch pd,
 					  EState *estate,
 					  Datum *values,
 					  bool *isnull);
-static int get_partition_for_tuple(Relation relation, Datum *values,
+static int get_partition_for_tuple(PartitionDispatch pd, Datum *values,
 					  bool *isnull);
 static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 Datum *values,
@@ -203,13 +204,11 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 	parent = pd[0];
 	while (true)
 	{
-		PartitionDesc partdesc;
 		TupleTableSlot *myslot = parent->tupslot;
 		TupleConversionMap *map = parent->tupmap;
 		int			cur_index = -1;
 
 		rel = parent->reldesc;
-		partdesc = RelationGetPartitionDesc(rel);
 
 		/*
 		 * Convert the tuple to this parent's layout so that we can do certain
@@ -240,13 +239,13 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 		 * Nothing for get_partition_for_tuple() to do if there are no
 		 * partitions to begin with.
 		 */
-		if (partdesc->nparts == 0)
+		if (parent->nparts == 0)
 		{
 			result = -1;
 			break;
 		}
 
-		cur_index = get_partition_for_tuple(rel, values, isnull);
+		cur_index = get_partition_for_tuple(parent, values, isnull);
 
 		/*
 		 * cur_index < 0 means we failed to find a partition of this parent.
@@ -876,8 +875,10 @@ get_partition_dispatch_recurse(Relation rel, Relation parent,
 							   List **pds, List **leaf_part_oids)
 {
 	TupleDesc	tupdesc = RelationGetDescr(rel);
-	PartitionDesc partdesc = RelationGetPartitionDesc(rel);
 	PartitionKey partkey = RelationGetPartitionKey(rel);
+	int			nparts = RelationGetPartitionCount(rel);
+	Oid		   *partoids = RelationGetPartitionOids(rel);
+	PartitionBoundInfo boundinfo = RelationGetPartitionBounds(rel);
 	PartitionDispatch pd;
 	int			i;
 
@@ -889,7 +890,15 @@ get_partition_dispatch_recurse(Relation rel, Relation parent,
 	pd->reldesc = rel;
 	pd->key = partkey;
 	pd->keystate = NIL;
-	pd->partdesc = partdesc;
+	/* Get partsupfunc FmgrInfo's. */
+	for (i = 0; i < partkey->partnatts; i++)
+	{
+		fmgr_info_copy(&pd->partsupfunc[i],
+					   partition_getprocinfo(rel, partkey, i),
+					   CurrentMemoryContext);
+	}
+	pd->nparts = nparts;
+	pd->boundinfo = boundinfo;
 	if (parent != NULL)
 	{
 		/*
@@ -934,10 +943,10 @@ get_partition_dispatch_recurse(Relation rel, Relation parent,
 	 * the tree.  This value is used to continue the search in the next level
 	 * of the partition tree.
 	 */
-	pd->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
-	for (i = 0; i < partdesc->nparts; i++)
+	pd->indexes = (int *) palloc(nparts * sizeof(int));
+	for (i = 0; i < nparts; i++)
 	{
-		Oid			partrelid = partdesc->oids[i];
+		Oid			partrelid = partoids[i];
 
 		if (get_rel_relkind(partrelid) != RELKIND_PARTITIONED_TABLE)
 		{
@@ -1029,26 +1038,26 @@ FormPartitionKeyDatum(PartitionDispatch pd,
  *		Finds partition of relation which accepts the partition key specified
  *		in values and isnull
  *
- * Return value is index of the partition (>= 0 and < partdesc->nparts) if one
- * found or -1 if none found.
+ * Return value is index of the partition (>= 0 and < nparts) if one found or
+ * -1 if none found.
  */
-int
-get_partition_for_tuple(Relation relation, Datum *values, bool *isnull)
+static int
+get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull)
 {
 	int			bound_offset;
 	int			part_index = -1;
-	PartitionKey key = RelationGetPartitionKey(relation);
-	PartitionDesc partdesc = RelationGetPartitionDesc(relation);
+	PartitionKey key = pd->key;
+	FmgrInfo   *partsupfunc = pd->partsupfunc;
+	PartitionBoundInfo boundinfo = pd->boundinfo;
 
 	/* Route as appropriate based on partitioning strategy. */
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_HASH:
 			{
-				PartitionBoundInfo boundinfo = partdesc->boundinfo;
 				int			greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
 				uint64		rowHash = compute_hash_value(key->partnatts,
-														 key->partsupfunc,
+														 partsupfunc,
 														 values, isnull);
 
 				part_index = boundinfo->indexes[rowHash % greatest_modulus];
@@ -1058,19 +1067,19 @@ get_partition_for_tuple(Relation relation, Datum *values, bool *isnull)
 		case PARTITION_STRATEGY_LIST:
 			if (isnull[0])
 			{
-				if (partition_bound_accepts_nulls(partdesc->boundinfo))
-					part_index = partdesc->boundinfo->null_index;
+				if (partition_bound_accepts_nulls(boundinfo))
+					part_index = boundinfo->null_index;
 			}
 			else
 			{
 				bool		equal = false;
 
-				bound_offset = partition_list_bsearch(key->partsupfunc,
+				bound_offset = partition_list_bsearch(partsupfunc,
 													  key->partcollation,
-													  partdesc->boundinfo,
+													  boundinfo,
 													  values[0], &equal);
 				if (bound_offset >= 0 && equal)
-					part_index = partdesc->boundinfo->indexes[bound_offset];
+					part_index = boundinfo->indexes[bound_offset];
 			}
 			break;
 
@@ -1095,9 +1104,9 @@ get_partition_for_tuple(Relation relation, Datum *values, bool *isnull)
 
 				if (!range_partkey_has_null)
 				{
-					bound_offset = partition_range_datum_bsearch(key->partsupfunc,
+					bound_offset = partition_range_datum_bsearch(partsupfunc,
 																 key->partcollation,
-																 partdesc->boundinfo,
+																 boundinfo,
 																 key->partnatts,
 																 values,
 																 &equal);
@@ -1108,7 +1117,7 @@ get_partition_for_tuple(Relation relation, Datum *values, bool *isnull)
 					 * bound of the partition we're looking for, if there
 					 * actually exists one.
 					 */
-					part_index = partdesc->boundinfo->indexes[bound_offset + 1];
+					part_index = boundinfo->indexes[bound_offset + 1];
 				}
 			}
 			break;
@@ -1123,7 +1132,7 @@ get_partition_for_tuple(Relation relation, Datum *values, bool *isnull)
 	 * the default partition, if there is one.
 	 */
 	if (part_index < 0)
-		part_index = partdesc->boundinfo->default_index;
+		part_index = boundinfo->default_index;
 
 	return part_index;
 }
@@ -1142,7 +1151,7 @@ ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 int maxfieldlen)
 {
 	StringInfoData buf;
-	PartitionKey key = RelationGetPartitionKey(rel);
+	PartitionKey	key = RelationGetPartitionKey(rel);
 	int			partnatts = get_partition_natts(key);
 	int			i;
 	Oid			relid = RelationGetRelid(rel);
@@ -1390,10 +1399,12 @@ ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 		PartitionPruneInfo *pinfo = (PartitionPruneInfo *) lfirst(lc);
 		PartitionPruningData *pprune = &prunedata[i];
 		PartitionPruneContext *context = &pprune->context;
-		PartitionDesc partdesc;
 		Relation	rel;
 		PartitionKey partkey;
+		int			nparts;
+		PartitionBoundInfo boundinfo;
 		int			partnatts;
+		int			j;
 
 		pprune->present_parts = bms_copy(pinfo->present_parts);
 		pprune->subnode_map = palloc(sizeof(int) * pinfo->nparts);
@@ -1415,16 +1426,23 @@ ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 		rel = relation_open(pinfo->reloid, NoLock);
 
 		partkey = RelationGetPartitionKey(rel);
-		partdesc = RelationGetPartitionDesc(rel);
+		nparts = RelationGetPartitionCount(rel);
+		boundinfo = RelationGetPartitionBounds(rel);
 
 		context->strategy = partkey->strategy;
 		context->partnatts = partnatts = partkey->partnatts;
 		context->partopfamily = partkey->partopfamily;
 		context->partopcintype = partkey->partopcintype;
 		context->partcollation = partkey->partcollation;
-		context->partsupfunc = partkey->partsupfunc;
-		context->nparts = pinfo->nparts;
-		context->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey);
+		/* Get partsupfunc FmgrInfo's. */
+		for (j = 0; j < partkey->partnatts; j++)
+		{
+			fmgr_info_copy(&context->partsupfunc[j],
+						   partition_getprocinfo(rel, partkey, j),
+						   CurrentMemoryContext);
+		}
+		context->nparts = nparts;
+		context->boundinfo = boundinfo;
 		context->planstate = planstate;
 		context->safeparams = NULL; /* empty for now */
 
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2e289d475e..5d66cfb87b 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -15,12 +15,12 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
-#include "catalog/partition.h"
 #include "optimizer/clauses.h"
 #include "optimizer/joininfo.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/prep.h"
+#include "partitioning/partbounds.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 8d86e98adc..23ce22a27e 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1584,7 +1584,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	oldrelation = heap_open(parentOID, NoLock);
 
 	/* Scan the inheritance set and expand it */
-	if (RelationGetPartitionDesc(oldrelation) != NULL)
+	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		Assert(rte->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -1675,13 +1675,11 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte,
 	RangeTblEntry *childrte;
 	Index		childRTindex;
 	bool		has_child = false;
-	PartitionDesc partdesc = RelationGetPartitionDesc(parentrel);
+	int			nparts = RelationGetPartitionCount(parentrel);
+	Oid		   *partoids = RelationGetPartitionOids(parentrel);
 
 	check_stack_depth();
 
-	/* A partitioned table should always have a partition descriptor. */
-	Assert(partdesc);
-
 	Assert(parentrte->inh);
 
 	/*
@@ -1700,9 +1698,10 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte,
 									top_parentrc, parentrel,
 									appinfos, &childrte, &childRTindex);
 
-	for (i = 0; i < partdesc->nparts; i++)
+	Assert(partoids != NULL || nparts == 0);
+	for (i = 0; i < nparts; i++)
 	{
-		Oid			childOID = partdesc->oids[i];
+		Oid			childOID = partoids[i];
 		Relation	childrel;
 
 		/* Open rel; we already have required locks */
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index cb9cfc3729..23fed6a93e 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,7 +27,6 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
-#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_statistic_ext.h"
 #include "foreign/fdwapi.h"
@@ -45,8 +44,9 @@
 #include "storage/bufmgr.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
-#include "utils/syscache.h"
+#include "utils/partcache.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 #include "utils/snapmgr.h"
 
 
@@ -70,8 +70,9 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
 static List *get_relation_statistics(RelOptInfo *rel, Relation relation);
 static void set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
 							Relation relation);
-static PartitionScheme find_partition_scheme(PlannerInfo *root, Relation rel);
-static void set_baserel_partition_key_exprs(Relation relation,
+static PartitionScheme find_partition_scheme(PlannerInfo *root, Relation rel,
+							PartitionKey partkey);
+static void set_baserel_partition_key_exprs(PartitionKey partkey,
 								RelOptInfo *rel);
 
 /*
@@ -1869,18 +1870,19 @@ static void
 set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
 							Relation relation)
 {
-	PartitionDesc partdesc;
-	PartitionKey partkey;
+	PartitionKey partkey = RelationGetPartitionKey(relation);
+	int		nparts = RelationGetPartitionCount(relation);
+	PartitionBoundInfo boundinfo = RelationGetPartitionBounds(relation);
 
 	Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+	Assert(partkey != NULL);
+	Assert(boundinfo != NULL || nparts == 0);
 
-	partdesc = RelationGetPartitionDesc(relation);
-	partkey = RelationGetPartitionKey(relation);
-	rel->part_scheme = find_partition_scheme(root, relation);
-	Assert(partdesc != NULL && rel->part_scheme != NULL);
-	rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey);
-	rel->nparts = partdesc->nparts;
-	set_baserel_partition_key_exprs(relation, rel);
+	rel->part_scheme = find_partition_scheme(root, relation, partkey);
+	Assert(rel->part_scheme != NULL);
+	rel->boundinfo = boundinfo;
+	rel->nparts = nparts;
+	set_baserel_partition_key_exprs(partkey, rel);
 	rel->partition_qual = RelationGetPartitionQual(relation);
 }
 
@@ -1890,9 +1892,9 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
  * Find or create a PartitionScheme for this Relation.
  */
 static PartitionScheme
-find_partition_scheme(PlannerInfo *root, Relation relation)
+find_partition_scheme(PlannerInfo *root, Relation rel,
+					  PartitionKey partkey)
 {
-	PartitionKey partkey = RelationGetPartitionKey(relation);
 	ListCell   *lc;
 	int			partnatts,
 				i;
@@ -1933,14 +1935,11 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
 
 		/*
 		 * If partopfamily and partopcintype matched, must have the same
-		 * partition comparison functions.  Note that we cannot reliably
-		 * Assert the equality of function structs themselves for they might
-		 * be different across PartitionKey's, so just Assert for the function
-		 * OIDs.
+		 * partition comparison functions.
 		 */
 #ifdef USE_ASSERT_CHECKING
 		for (i = 0; i < partkey->partnatts; i++)
-			Assert(partkey->partsupfunc[i].fn_oid ==
+			Assert(partkey->partsupfuncid[i] ==
 				   part_scheme->partsupfunc[i].fn_oid);
 #endif
 
@@ -1949,39 +1948,22 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
 	}
 
 	/*
-	 * Did not find matching partition scheme. Create one copying relevant
-	 * information from the relcache. We need to copy the contents of the
-	 * array since the relcache entry may not survive after we have closed the
-	 * relation.
+	 * Did not find matching partition scheme. Create one usinng the
+	 * relevant information copied from the relcache.
 	 */
 	part_scheme = (PartitionScheme) palloc0(sizeof(PartitionSchemeData));
 	part_scheme->strategy = partkey->strategy;
 	part_scheme->partnatts = partkey->partnatts;
-
-	part_scheme->partopfamily = (Oid *) palloc(sizeof(Oid) * partnatts);
-	memcpy(part_scheme->partopfamily, partkey->partopfamily,
-		   sizeof(Oid) * partnatts);
-
-	part_scheme->partopcintype = (Oid *) palloc(sizeof(Oid) * partnatts);
-	memcpy(part_scheme->partopcintype, partkey->partopcintype,
-		   sizeof(Oid) * partnatts);
-
-	part_scheme->partcollation = (Oid *) palloc(sizeof(Oid) * partnatts);
-	memcpy(part_scheme->partcollation, partkey->partcollation,
-		   sizeof(Oid) * partnatts);
-
-	part_scheme->parttyplen = (int16 *) palloc(sizeof(int16) * partnatts);
-	memcpy(part_scheme->parttyplen, partkey->parttyplen,
-		   sizeof(int16) * partnatts);
-
-	part_scheme->parttypbyval = (bool *) palloc(sizeof(bool) * partnatts);
-	memcpy(part_scheme->parttypbyval, partkey->parttypbyval,
-		   sizeof(bool) * partnatts);
-
+	part_scheme->partopfamily = partkey->partopfamily;
+	part_scheme->partopcintype = partkey->partopcintype;
+	part_scheme->partcollation = partkey->partcollation;
+	part_scheme->parttyplen = partkey->parttyplen;
+	part_scheme->parttypbyval = partkey->parttypbyval;
 	part_scheme->partsupfunc = (FmgrInfo *)
 		palloc(sizeof(FmgrInfo) * partnatts);
 	for (i = 0; i < partnatts; i++)
-		fmgr_info_copy(&part_scheme->partsupfunc[i], &partkey->partsupfunc[i],
+		fmgr_info_copy(&part_scheme->partsupfunc[i],
+					   partition_getprocinfo(rel, partkey, i),
 					   CurrentMemoryContext);
 
 	/* Add the partitioning scheme to PlannerInfo. */
@@ -1998,10 +1980,9 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
  * nodes.  All Var nodes are restamped with the relid of given relation.
  */
 static void
-set_baserel_partition_key_exprs(Relation relation,
+set_baserel_partition_key_exprs(PartitionKey partkey,
 								RelOptInfo *rel)
 {
-	PartitionKey partkey = RelationGetPartitionKey(relation);
 	int			partnatts;
 	int			cnt;
 	List	  **partexprs;
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index b9aa7486ba..fc19f892c5 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -17,7 +17,6 @@
 #include <limits.h>
 
 #include "miscadmin.h"
-#include "catalog/partition.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
@@ -27,6 +26,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "partitioning/partbounds.h"
 #include "utils/hsearch.h"
 
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9178139912..7889f8bfab 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3556,7 +3556,7 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
 	{
 		case RELKIND_PARTITIONED_TABLE:
 			/* transform the partition bound, if any */
-			Assert(RelationGetPartitionKey(parentRel) != NULL);
+			Assert(parentRel->rd_partkey != NULL);
 			if (cmd->bound != NULL)
 				cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
 														 cmd->bound);
@@ -3597,7 +3597,7 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 						PartitionBoundSpec *spec)
 {
 	PartitionBoundSpec *result_spec;
-	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionKey	key = RelationGetPartitionKey(parent);
 	char		strategy = get_partition_strategy(key);
 	int			partnatts = get_partition_natts(key);
 	List	   *partexprs = get_partition_exprs(key);
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 5c4fc90341..b25e74896b 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -311,8 +311,9 @@ check_new_partition_bound(char *relname, Relation parent,
 						  PartitionBoundSpec *spec)
 {
 	PartitionKey	key = RelationGetPartitionKey(parent);
-	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
-	PartitionBoundInfo boundinfo = partdesc->boundinfo;
+	int				nparts = RelationGetPartitionCount(parent);
+	Oid			   *partoids = RelationGetPartitionOids(parent);
+	PartitionBoundInfo boundinfo = RelationGetPartitionBounds(parent);
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -332,7 +333,7 @@ check_new_partition_bound(char *relname, Relation parent,
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
-						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
+						relname, get_rel_name(partoids[boundinfo->default_index])),
 				 parser_errposition(pstate, spec->location)));
 	}
 
@@ -343,7 +344,7 @@ check_new_partition_bound(char *relname, Relation parent,
 				Assert(spec->strategy == PARTITION_STRATEGY_HASH);
 				Assert(spec->remainder >= 0 && spec->remainder < spec->modulus);
 
-				if (partdesc->nparts > 0)
+				if (nparts > 0)
 				{
 					Datum	  **datums = boundinfo->datums;
 					int			ndatums = boundinfo->ndatums;
@@ -422,9 +423,16 @@ check_new_partition_bound(char *relname, Relation parent,
 
 		case PARTITION_STRATEGY_LIST:
 			{
+				FmgrInfo partsupfunc;
+
 				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
 
-				if (partdesc->nparts > 0)
+				/* Get partsupfunc FmgrInfo for the only key. */
+				fmgr_info_copy(&partsupfunc,
+							   partition_getprocinfo(parent, key, 0),
+							   CurrentMemoryContext);
+
+				if (nparts > 0)
 				{
 					ListCell   *cell;
 
@@ -443,7 +451,7 @@ check_new_partition_bound(char *relname, Relation parent,
 							int			offset;
 							bool		equal;
 
-							offset = partition_list_bsearch(&key->partsupfunc[0],
+							offset = partition_list_bsearch(&partsupfunc,
 														key->partcollation,
 															boundinfo,
 															val->constvalue,
@@ -471,6 +479,16 @@ check_new_partition_bound(char *relname, Relation parent,
 			{
 				PartitionRangeBound *lower,
 						   *upper;
+				FmgrInfo partsupfunc[PARTITION_MAX_KEYS];
+				int		i;
+
+				/* Get partsupfunc FmgrInfo's. */
+				for (i = 0; i < key->partnatts; i++)
+				{
+					fmgr_info_copy(&partsupfunc[i],
+								   partition_getprocinfo(parent, key, i),
+								   CurrentMemoryContext);
+				}
 
 				Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 				lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
@@ -480,7 +498,7 @@ check_new_partition_bound(char *relname, Relation parent,
 				 * First check if the resulting range would be empty with
 				 * specified lower and upper bounds
 				 */
-				if (partition_rbound_cmp(key->partnatts, key->partsupfunc,
+				if (partition_rbound_cmp(key->partnatts, partsupfunc,
 										 key->partcollation, lower->datums,
 										 lower->kind, true, upper) >= 0)
 				{
@@ -494,7 +512,7 @@ check_new_partition_bound(char *relname, Relation parent,
 							 parser_errposition(pstate, spec->location)));
 				}
 
-				if (partdesc->nparts > 0)
+				if (nparts > 0)
 				{
 					int			offset;
 					bool		equal;
@@ -520,7 +538,7 @@ check_new_partition_bound(char *relname, Relation parent,
 					 * at the end.
 					 */
 					offset = partition_range_bsearch(key->partnatts,
-													 key->partsupfunc,
+													 partsupfunc,
 													 key->partcollation,
 													 boundinfo, lower,
 													 &equal);
@@ -545,7 +563,7 @@ check_new_partition_bound(char *relname, Relation parent,
 							is_lower = (boundinfo->indexes[offset + 1] == -1);
 
 							cmpval = partition_rbound_cmp(key->partnatts,
-														  key->partsupfunc,
+														  partsupfunc,
 														  key->partcollation,
 														  datums, kind,
 														  is_lower, upper);
@@ -586,7 +604,7 @@ check_new_partition_bound(char *relname, Relation parent,
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("partition \"%s\" would overlap partition \"%s\"",
-						relname, get_rel_name(partdesc->oids[with])),
+						relname, get_rel_name(partoids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
 }
@@ -1434,8 +1452,7 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 	{
 		int			i;
 		int			ndatums = 0;
-		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
-		PartitionBoundInfo boundinfo = pdesc->boundinfo;
+		PartitionBoundInfo boundinfo = RelationGetPartitionBounds(parent);
 
 		if (boundinfo)
 		{
@@ -1634,11 +1651,11 @@ get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
 	if (spec->is_default)
 	{
 		List	   *or_expr_args = NIL;
-		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
-		Oid		   *inhoids = pdesc->oids;
-		int			nparts = pdesc->nparts,
+		Oid		   *inhoids = RelationGetPartitionOids(parent);
+		int			nparts = RelationGetPartitionCount(parent),
 					i;
 
+		Assert(inhoids != NULL || nparts == 0);
 		for (i = 0; i < nparts; i++)
 		{
 			Oid			inhrelid = inhoids[i];
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 7666c6c412..d506bc0264 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -54,7 +54,6 @@
 #include "optimizer/predtest.h"
 #include "optimizer/prep.h"
 #include "partitioning/partprune.h"
-#include "partitioning/partbounds.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
 
@@ -437,7 +436,10 @@ prune_append_rel_partitions(RelOptInfo *rel)
 	context.partopfamily = rel->part_scheme->partopfamily;
 	context.partopcintype = rel->part_scheme->partopcintype;
 	context.partcollation = rel->part_scheme->partcollation;
-	context.partsupfunc = rel->part_scheme->partsupfunc;
+	for (i = 0; i < context.partnatts; i++)
+		fmgr_info_copy(&context.partsupfunc[i],
+					   &rel->part_scheme->partsupfunc[i],
+					   CurrentMemoryContext);
 	context.nparts = rel->nparts;
 	context.boundinfo = rel->boundinfo;
 
@@ -1413,7 +1415,8 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		partclause->op_is_ne = false;
 		partclause->expr = expr;
 		/* We know that expr is of Boolean type. */
-		partclause->cmpfn = rel->part_scheme->partsupfunc[partkeyidx].fn_oid;
+		partclause->cmpfn =
+						rel->part_scheme->partsupfunc[partkeyidx].fn_oid;
 		partclause->op_strategy = InvalidStrategy;
 
 		*pc = partclause;
@@ -1962,8 +1965,8 @@ get_steps_using_prefix_recurse(GeneratePruningStepsContext *context,
  *
  * 'nvalues', the number of Datums in the 'values' array.
  *
- * 'partsupfunc' contains partition hashing functions that can produce correct
- * hash for the type of the values contained in 'values'.
+ * 'partsupfunc' contains partition hashing functions that can produce
+ * correct hash for the type of the values contained in 'values'.
  *
  * 'nullkeys' is the set of partition keys that are null.
  */
@@ -2000,7 +2003,8 @@ get_matching_hash_bounds(PartitionPruneContext *context,
 			isnull[i] = bms_is_member(i, nullkeys);
 
 		greatest_modulus = get_hash_partition_greatest_modulus(boundinfo);
-		rowHash = compute_hash_value(partnatts, partsupfunc, values, isnull);
+		rowHash = compute_hash_value(partnatts, partsupfunc, values,
+									 isnull);
 
 		if (partindices[rowHash % greatest_modulus] >= 0)
 			result->bound_offsets =
@@ -2029,8 +2033,8 @@ get_matching_hash_bounds(PartitionPruneContext *context,
  *
  * 'nvalues', if non-zero, should be exactly 1, because of list partitioning.
  *
- * 'partsupfunc' contains the list partitioning comparison function to be used
- * to perform partition_list_bsearch
+ * 'partsupfunc' contains the list partitioning comparison function to be
+ * used to perform partition_list_bsearch
  *
  * 'nullkeys' is the set of partition keys that are null.
  */
@@ -2102,8 +2106,8 @@ get_matching_list_bounds(PartitionPruneContext *context,
 		result->bound_offsets = bms_add_range(NULL, 0,
 											  boundinfo->ndatums - 1);
 
-		off = partition_list_bsearch(partsupfunc, partcollation, boundinfo,
-									 value, &is_equal);
+		off = partition_list_bsearch(partsupfunc, partcollation,
+									 boundinfo, value, &is_equal);
 		if (off >= 0 && is_equal)
 		{
 
@@ -2231,9 +2235,9 @@ get_matching_list_bounds(PartitionPruneContext *context,
  *
  * 'nvalues', number of Datums in 'values' array. Must be <= context->partnatts.
  *
- * 'partsupfunc' contains the range partitioning comparison functions to be
- * used to perform partition_range_datum_bsearch or partition_rbound_datum_cmp
- * using.
+ * 'partsupfunc' contains the range partitioning comparison functions to
+ * be used to perform partition_range_datum_bsearch or
+ * partition_rbound_datum_cmp using.
  *
  * 'nullkeys' is the set of partition keys that are null.
  */
@@ -2797,7 +2801,8 @@ perform_pruning_base_step(PartitionPruneContext *context,
 				cmpfn = lfirst_oid(lc2);
 				Assert(OidIsValid(cmpfn));
 				if (cmpfn != context->partsupfunc[keyno].fn_oid)
-					fmgr_info(cmpfn, &partsupfunc[keyno]);
+					fmgr_info_cxt(cmpfn, &partsupfunc[keyno],
+								  CurrentMemoryContext);
 				else
 					fmgr_info_copy(&partsupfunc[keyno],
 								   &context->partsupfunc[keyno],
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 99643e83b2..ebcfa3b152 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -24,7 +24,6 @@
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
-#include "catalog/partition.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_authid.h"
diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c
index db91cde40a..c0a8be76bc 100644
--- a/src/backend/utils/cache/partcache.c
+++ b/src/backend/utils/cache/partcache.c
@@ -17,7 +17,6 @@
 #include "access/hash.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
-#include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
@@ -42,18 +41,37 @@
 #include "utils/partcache.h"
 #include "utils/syscache.h"
 
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+	int			nparts;			/* Number of partitions */
+	Oid		   *oids;			/* OIDs of partitions */
+	PartitionBoundInfo boundinfo;	/* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+typedef struct PartitionBoundSortInfo
+{
+	FmgrInfo	   *partsupfunc;
+	PartitionKey	key;
+} PartitionBoundSortInfo;
+
 static int32 qsort_partition_hbound_cmp(const void *a, const void *b);
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 							   void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 						   void *arg);
+static PartitionKey partition_key_copy(PartitionKey fromkey);
 static List *generate_partition_qual(Relation rel);
 
 /*
  * RelationBuildPartitionKey
  *		Build and attach to relcache partition key data of relation
  *
- * Partitioning key data is a complex structure; to avoid complicated logic to
+ * Partition key data is of complex structure; to avoid complicated logic to
  * free individual elements whenever the relcache entry is flushed, we give it
  * its own memory context, child of CacheMemoryContext, which can easily be
  * deleted on its own.  To avoid leaking memory in that context in case of an
@@ -77,8 +95,7 @@ RelationBuildPartitionKey(Relation relation)
 	oidvector  *collation;
 	ListCell   *partexprs_item;
 	Datum		datum;
-	MemoryContext partkeycxt,
-				oldcxt;
+	MemoryContext oldcxt;
 	int16		procnum;
 
 	tuple = SearchSysCache1(PARTRELID,
@@ -91,13 +108,7 @@ RelationBuildPartitionKey(Relation relation)
 	if (!HeapTupleIsValid(tuple))
 		return;
 
-	partkeycxt = AllocSetContextCreate(CurTransactionContext,
-									   "partition key",
-									   ALLOCSET_SMALL_SIZES);
-	MemoryContextCopyAndSetIdentifier(partkeycxt,
-								   RelationGetRelationName(relation));
-
-	key = (PartitionKey) MemoryContextAllocZero(partkeycxt,
+	key = (PartitionKey) MemoryContextAllocZero(relation->rd_partcxt,
 												sizeof(PartitionKeyData));
 
 	/* Fixed-length attributes */
@@ -149,17 +160,16 @@ RelationBuildPartitionKey(Relation relation)
 		expr = eval_const_expressions(NULL, expr);
 		fix_opfuncids(expr);
 
-		oldcxt = MemoryContextSwitchTo(partkeycxt);
+		oldcxt = MemoryContextSwitchTo(relation->rd_partcxt);
 		key->partexprs = (List *) copyObject(expr);
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	oldcxt = MemoryContextSwitchTo(partkeycxt);
+	oldcxt = MemoryContextSwitchTo(relation->rd_partcxt);
 	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
 	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
 	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
-	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
-
+	key->partsupfuncid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
 	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
 
 	/* Gather type and collation info as well */
@@ -169,6 +179,13 @@ RelationBuildPartitionKey(Relation relation)
 	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
 	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
 	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/*
+	 * Also allocate space for partition support procedure FmgrInfo's, but
+	 * they won't be filled until somebody calls partition_get_procinfo.
+	 */
+	relation->rd_partsupfunc = (FmgrInfo *)
+						palloc0(key->partnatts * sizeof(FmgrInfo));
 	MemoryContextSwitchTo(oldcxt);
 
 	/* determine support function number to search for */
@@ -210,7 +227,7 @@ RelationBuildPartitionKey(Relation relation)
 							procnum,
 							format_type_be(opclassform->opcintype))));
 
-		fmgr_info_cxt(funcid, &key->partsupfunc[i], partkeycxt);
+		key->partsupfuncid[i] = funcid;
 
 		/* Collation */
 		key->partcollation[i] = collation->values[i];
@@ -244,13 +261,6 @@ RelationBuildPartitionKey(Relation relation)
 	}
 
 	ReleaseSysCache(tuple);
-
-	/*
-	 * Success --- reparent our context and make the relcache point to the
-	 * newly constructed key
-	 */
-	MemoryContextSetParent(partkeycxt, CacheMemoryContext);
-	relation->rd_partkeycxt = partkeycxt;
 	relation->rd_partkey = key;
 }
 
@@ -349,6 +359,20 @@ RelationBuildPartitionDesc(Relation rel)
 
 	if (nparts > 0)
 	{
+		FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
+		PartitionBoundSortInfo sortinfo;
+
+		/* Get partsupfunc FmgrInfo's. */
+		for (i = 0; i < key->partnatts; i++)
+		{
+			fmgr_info_copy(&partsupfunc[i],
+						   partition_getprocinfo(rel, key, i),
+						   CurrentMemoryContext);
+		}
+
+		sortinfo.partsupfunc = partsupfunc;
+		sortinfo.key = key;
+
 		oids = (Oid *) palloc(nparts * sizeof(Oid));
 		i = 0;
 		foreach(cell, partoids)
@@ -466,7 +490,7 @@ RelationBuildPartitionDesc(Relation rel)
 			}
 
 			qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
-					  qsort_partition_list_value_cmp, (void *) key);
+					  qsort_partition_list_value_cmp, (void *) &sortinfo);
 		}
 		else if (key->strategy == PARTITION_STRATEGY_RANGE)
 		{
@@ -519,7 +543,7 @@ RelationBuildPartitionDesc(Relation rel)
 			qsort_arg(all_bounds, ndatums,
 					  sizeof(PartitionRangeBound *),
 					  qsort_partition_rbound_cmp,
-					  (void *) key);
+					  (void *) &sortinfo);
 
 			/* Save distinct bounds from all_bounds into rbounds. */
 			rbounds = (PartitionRangeBound **)
@@ -551,7 +575,7 @@ RelationBuildPartitionDesc(Relation rel)
 					if (cur->kind[j] != PARTITION_RANGE_DATUM_VALUE)
 						break;
 
-					cmpval = FunctionCall2Coll(&key->partsupfunc[j],
+					cmpval = FunctionCall2Coll(&partsupfunc[j],
 											   key->partcollation[j],
 											   cur->datums[j],
 											   prev->datums[j]);
@@ -582,12 +606,7 @@ RelationBuildPartitionDesc(Relation rel)
 	}
 
 	/* Now build the actual relcache partition descriptor */
-	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
-										  "partition descriptor",
-										  ALLOCSET_DEFAULT_SIZES);
-	MemoryContextCopyAndSetIdentifier(rel->rd_pdcxt, RelationGetRelationName(rel));
-
-	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+	oldcxt = MemoryContextSwitchTo(rel->rd_partcxt);
 
 	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
 	result->nparts = nparts;
@@ -794,6 +813,107 @@ RelationBuildPartitionDesc(Relation rel)
 	rel->rd_partdesc = result;
 }
 
+/*
+ * Functions to get a copy of partitioning infomation cached in RelationData.
+ */
+
+PartitionKey
+RelationGetPartitionKey(Relation relation)
+{
+	PartitionKey partkey = relation->rd_partkey;
+
+	return partkey ? partition_key_copy(partkey) : NULL;
+}
+
+int
+RelationGetPartitionCount(Relation relation)
+{
+	PartitionDesc partdesc = relation->rd_partdesc;
+
+	return partdesc->nparts;
+}
+
+Oid *
+RelationGetPartitionOids(Relation relation)
+{
+	PartitionDesc partdesc = relation->rd_partdesc;
+	Oid *result = NULL;
+
+	Assert(partdesc != NULL);
+	if (partdesc->nparts > 0)
+	{
+		result = palloc(partdesc->nparts * sizeof(Oid));
+		memcpy(result, partdesc->oids, partdesc->nparts * sizeof(Oid));
+	}
+
+	return result;
+}
+
+PartitionBoundInfo
+RelationGetPartitionBounds(Relation relation)
+{
+	PartitionDesc partdesc = relation->rd_partdesc;
+
+	return partdesc->boundinfo
+				? partition_bounds_copy(partdesc->boundinfo,
+										relation->rd_partkey)
+				: NULL;
+}
+
+/*
+ * RelationGetDefaultPartitionOid
+ *
+ * Return the OID of the default partition, if one exists; else InvalidOid.
+ */
+Oid
+RelationGetDefaultPartitionOid(Relation rel)
+{
+	PartitionBoundInfo boundinfo;
+
+	/* Shouldn't be here otherwise! */
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+	boundinfo = rel->rd_partdesc->boundinfo;
+
+	if (boundinfo && partition_bound_has_default(boundinfo))
+		return rel->rd_partdesc->oids[boundinfo->default_index];
+
+	return InvalidOid;
+}
+
+/*
+ * partition_getprocinfo
+ *		Return fmgr lookup info of partition support procs from relcache
+ *
+ * If it's not been built yet by calling fmgr.c, do that and add it to
+ * relcache.
+ */
+FmgrInfo *
+partition_getprocinfo(Relation rel, PartitionKey key, int partattoff)
+{
+	FmgrInfo   *info;
+
+	info = rel->rd_partsupfunc;
+
+	Assert(info != NULL);
+
+	info += partattoff;
+
+	/* Initialize the lookup info if first time through */
+	if (info->fn_oid == InvalidOid)
+	{
+		RegProcedure *func = key->partsupfuncid;
+		RegProcedure procId;
+
+		Assert(func != NULL);
+
+		procId = func[partattoff];
+		Assert(RegProcedureIsValid(procId));
+		fmgr_info_cxt(procId, info, rel->rd_partcxt);
+	}
+
+	return info;
+}
+
 /*
  * RelationGetPartitionQual
  *
@@ -867,9 +987,10 @@ qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
 {
 	Datum		val1 = (*(const PartitionListValue **) a)->value,
 				val2 = (*(const PartitionListValue **) b)->value;
-	PartitionKey key = (PartitionKey) arg;
+	PartitionBoundSortInfo *sortinfo = (PartitionBoundSortInfo *) arg;
+	PartitionKey key = sortinfo->key;
 
-	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+	return DatumGetInt32(FunctionCall2Coll(&sortinfo->partsupfunc[0],
 										   key->partcollation[0],
 										   val1, val2));
 }
@@ -880,13 +1001,70 @@ qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
 {
 	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
 	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
-	PartitionKey key = (PartitionKey) arg;
+	PartitionBoundSortInfo *sortinfo = (PartitionBoundSortInfo *) arg;
+	PartitionKey key = sortinfo->key;
 
-	return partition_rbound_cmp(key->partnatts, key->partsupfunc,
+	return partition_rbound_cmp(key->partnatts, sortinfo->partsupfunc,
 								key->partcollation, b1->datums, b1->kind,
 								b1->lower, b2);
 }
 
+/*
+ * partition_key_copy
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+partition_key_copy(PartitionKey fromkey)
+{
+	PartitionKey newkey;
+	int			n;
+
+	Assert(fromkey != NULL);
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfuncid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partsupfuncid, fromkey->partsupfuncid, n * sizeof(Oid));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
 /*
  * generate_partition_qual
  *
@@ -960,7 +1138,7 @@ generate_partition_qual(Relation rel)
 		elog(ERROR, "unexpected whole-row reference found in partition key");
 
 	/* Save a copy in the relcache */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(rel->rd_partcxt);
 	rel->rd_partcheck = copyObject(result);
 	MemoryContextSwitchTo(oldcxt);
 
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 5c17fe1742..aed32406f1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -43,7 +43,6 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
-#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -286,9 +285,6 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
-static bool equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
-					PartitionDesc partdesc2);
-
 
 /*
  *		ScanPgRelation
@@ -1000,60 +996,6 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 	return true;
 }
 
-/*
- * equalPartitionDescs
- *		Compare two partition descriptors for logical equality
- */
-static bool
-equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
-					PartitionDesc partdesc2)
-{
-	int			i;
-
-	if (partdesc1 != NULL)
-	{
-		if (partdesc2 == NULL)
-			return false;
-		if (partdesc1->nparts != partdesc2->nparts)
-			return false;
-
-		Assert(key != NULL || partdesc1->nparts == 0);
-
-		/*
-		 * Same oids? If the partitioning structure did not change, that is,
-		 * no partitions were added or removed to the relation, the oids array
-		 * should still match element-by-element.
-		 */
-		for (i = 0; i < partdesc1->nparts; i++)
-		{
-			if (partdesc1->oids[i] != partdesc2->oids[i])
-				return false;
-		}
-
-		/*
-		 * Now compare partition bound collections.  The logic to iterate over
-		 * the collections is private to partition.c.
-		 */
-		if (partdesc1->boundinfo != NULL)
-		{
-			if (partdesc2->boundinfo == NULL)
-				return false;
-
-			if (!partition_bounds_equal(key->partnatts, key->parttyplen,
-										key->parttypbyval,
-										partdesc1->boundinfo,
-										partdesc2->boundinfo))
-				return false;
-		}
-		else if (partdesc2->boundinfo != NULL)
-			return false;
-	}
-	else if (partdesc2 != NULL)
-		return false;
-
-	return true;
-}
-
 /*
  *		RelationBuildDesc
  *
@@ -1182,18 +1124,40 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if a partitioned table, initialize key and partition descriptor info */
-	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	/*
+	 * If we need to initialize partitioning info, do that in a dedicated
+	 * context that's attached to the cache context.
+	 */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+		relation->rd_rel->relispartition)
 	{
-		RelationBuildPartitionKey(relation);
-		RelationBuildPartitionDesc(relation);
+		relation->rd_partcxt = AllocSetContextCreate(CacheMemoryContext,
+													 "partition info",
+													 ALLOCSET_SMALL_SIZES);
+		MemoryContextCopyAndSetIdentifier(relation->rd_partcxt,
+										  RelationGetRelationName(relation));
+
+		/*
+		 * For a partitioned table, initialize partition key and partition
+		 * descriptor.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			RelationBuildPartitionDesc(relation);
+		}
+
+		/*
+		 * Partition constraint of a partition itself is built only when
+		 * needed.  See RelationGetPartitionQual().
+		 */
 	}
 	else
 	{
-		relation->rd_partkeycxt = NULL;
+		relation->rd_partcxt = NULL;
 		relation->rd_partkey = NULL;
 		relation->rd_partdesc = NULL;
-		relation->rd_pdcxt = NULL;
+		relation->rd_partcheck = NIL;
 	}
 
 	/*
@@ -2196,12 +2160,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
-	if (relation->rd_partkeycxt)
-		MemoryContextDelete(relation->rd_partkeycxt);
-	if (relation->rd_pdcxt)
-		MemoryContextDelete(relation->rd_pdcxt);
-	if (relation->rd_partcheck)
-		pfree(relation->rd_partcheck);
+	if (relation->rd_partcxt)
+		MemoryContextDelete(relation->rd_partcxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2368,8 +2328,6 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 		bool		keep_policies;
-		bool		keep_partkey;
-		bool		keep_partdesc;
 
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
@@ -2400,10 +2358,6 @@ RelationClearRelation(Relation relation, bool rebuild)
 		keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
 		keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
 		keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
-		keep_partkey = (relation->rd_partkey != NULL);
-		keep_partdesc = equalPartitionDescs(relation->rd_partkey,
-											relation->rd_partdesc,
-											newrel->rd_partdesc);
 
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
@@ -2458,18 +2412,6 @@ RelationClearRelation(Relation relation, bool rebuild)
 		SWAPFIELD(Oid, rd_toastoid);
 		/* pgstat_info must be preserved */
 		SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
-		/* partition key must be preserved, if we have one */
-		if (keep_partkey)
-		{
-			SWAPFIELD(PartitionKey, rd_partkey);
-			SWAPFIELD(MemoryContext, rd_partkeycxt);
-		}
-		/* preserve old partdesc if no logical change */
-		if (keep_partdesc)
-		{
-			SWAPFIELD(PartitionDesc, rd_partdesc);
-			SWAPFIELD(MemoryContext, rd_pdcxt);
-		}
 
 #undef SWAPFIELD
 
@@ -3707,19 +3649,11 @@ RelationCacheInitializePhase3(void)
 		/*
 		 * Reload the partition key and descriptor for a partitioned table.
 		 */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
-			relation->rd_partkey == NULL)
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			RelationBuildPartitionKey(relation);
-			Assert(relation->rd_partkey != NULL);
-
-			restart = true;
-		}
-
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
-			relation->rd_partdesc == NULL)
-		{
 			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partkey != NULL);
 			Assert(relation->rd_partdesc != NULL);
 
 			restart = true;
@@ -5572,9 +5506,9 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
-		rel->rd_partkeycxt = NULL;
+		rel->rd_partcxt = NULL;
 		rel->rd_partkey = NULL;
-		rel->rd_pdcxt = NULL;
+		rel->rd_partsupfunc = NULL;
 		rel->rd_partdesc = NULL;
 		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 6ed013380b..6e468630b5 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -16,25 +16,12 @@
 #include "fmgr.h"
 #include "executor/tuptable.h"
 #include "nodes/execnodes.h"
-#include "partitioning/partbounds.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
 /* Seed for the extended hash function */
 #define HASH_PARTITION_SEED UINT64CONST(0x7A5B22367996DCFD)
 
-/*
- * Information about partitions of a partitioned table.
- */
-typedef struct PartitionDescData
-{
-	int			nparts;			/* Number of partitions */
-	Oid		   *oids;			/* OIDs of partitions */
-	PartitionBoundInfo boundinfo;	/* collection of partition bounds */
-} PartitionDescData;
-
-typedef struct PartitionDescData *PartitionDesc;
-
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_partition_ancestors(Oid relid);
 extern List *map_partition_varattnos(List *expr, int fromrel_varno,
@@ -42,8 +29,6 @@ extern List *map_partition_varattnos(List *expr, int fromrel_varno,
 						bool *found_whole_row);
 extern bool has_partition_attrs(Relation rel, Bitmapset *attnums,
 					bool *used_in_expr);
-
-extern Oid	get_default_oid_from_partdesc(PartitionDesc partdesc);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
 extern List *get_proposed_default_constraint(List *new_part_constaints);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 70ee3da76b..eb3fd8a206 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -17,8 +17,8 @@
 #include "access/htup.h"
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
-#include "nodes/parsenodes.h"
 #include "catalog/partition.h"
+#include "nodes/parsenodes.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index e81bdc4a0a..47eedb91bb 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -25,14 +25,16 @@
  *	reldesc		Relation descriptor of the table
  *	key			Partition key information of the table
  *	keystate	Execution state required for expressions in the partition key
- *	partdesc	Partition descriptor of the table
+ *	partsupfunc		fmgr lookup info of partition support functions
+ *	nparts		Number of partitions of the table
+ *	boundinfo	PartitionBoundInfo of the table
  *	tupslot		A standalone TupleTableSlot initialized with this table's tuple
  *				descriptor
  *	tupmap		TupleConversionMap to convert from the parent's rowtype to
  *				this table's rowtype (when extracting the partition key of a
  *				tuple just before routing it through this table)
- *	indexes		Array with partdesc->nparts members (for details on what
- *				individual members represent, see how they are set in
+ *	indexes		Array with nparts members (for details on what individual
+ *				members represent, see how they are set in
  *				get_partition_dispatch_recurse())
  *-----------------------
  */
@@ -41,7 +43,9 @@ typedef struct PartitionDispatchData
 	Relation	reldesc;
 	PartitionKey key;
 	List	   *keystate;		/* list of ExprState */
-	PartitionDesc partdesc;
+	FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
+	int			nparts;
+	PartitionBoundInfo boundinfo;
 	TupleTableSlot *tupslot;
 	TupleConversionMap *tupmap;
 	int		   *indexes;
diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h
index 8bae4b2642..3e5f9dd920 100644
--- a/src/include/partitioning/partbounds.h
+++ b/src/include/partitioning/partbounds.h
@@ -11,7 +11,6 @@
 #ifndef PARTBOUNDS_H
 #define PARTBOUNDS_H
 
-#include "nodes/parsenodes.h"
 #include "utils/partcache.h"
 
 /*
diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h
index 2ae2fd16ed..5c44d9d212 100644
--- a/src/include/partitioning/partprune.h
+++ b/src/include/partitioning/partprune.h
@@ -14,7 +14,7 @@
 #ifndef PARTPRUNE_H
 #define PARTPRUNE_H
 
-#include "catalog/partition.h"
+#include "partitioning/partbounds.h"
 #include "nodes/relation.h"
 
 /*
@@ -30,7 +30,7 @@ typedef struct PartitionPruneContext
 	Oid		   *partopfamily;
 	Oid		   *partopcintype;
 	Oid		   *partcollation;
-	FmgrInfo   *partsupfunc;
+	FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
 
 	/* Number of partitions */
 	int			nparts;
diff --git a/src/include/utils/partcache.h b/src/include/utils/partcache.h
index b9bde9fb7b..2a6f1ad700 100644
--- a/src/include/utils/partcache.h
+++ b/src/include/utils/partcache.h
@@ -27,7 +27,7 @@ typedef struct PartitionKeyData
 
 	Oid		   *partopfamily;	/* OIDs of operator families */
 	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
-	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+	Oid		   *partsupfuncid;	/* partition support func OIDs */
 
 	/* Partitioning collation per attribute */
 	Oid		   *partcollation;
@@ -43,8 +43,23 @@ typedef struct PartitionKeyData
 
 typedef struct PartitionKeyData *PartitionKey;
 
+/*
+ * PartitionBoundInfo encapsulates a set of partition bounds.  It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure appears in partbounds.h.
+ */
+typedef struct PartitionBoundInfoData *PartitionBoundInfo;
+
 extern void RelationBuildPartitionKey(Relation relation);
 extern void RelationBuildPartitionDesc(Relation rel);
+extern PartitionKey RelationGetPartitionKey(Relation relation);
+extern FmgrInfo *partition_getprocinfo(Relation rel, PartitionKey key,
+									   int partattoff);
+extern int RelationGetPartitionCount(Relation relation);
+extern Oid *RelationGetPartitionOids(Relation relation);
+extern PartitionBoundInfo RelationGetPartitionBounds(Relation relation);
+extern Oid	RelationGetDefaultPartitionOid(Relation rel);
 extern List *RelationGetPartitionQual(Relation rel);
 extern Expr *get_partition_qual_relid(Oid relid);
 
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index bc74d04ce5..2c23704710 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -96,9 +96,11 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
-	MemoryContext rd_partkeycxt;	/* private memory cxt for the below */
+	MemoryContext rd_partcxt;	/* private memory cxt for the values contained
+								 * in the fields related to partitioning */
 	struct PartitionKeyData *rd_partkey;	/* partition key, or NULL */
-	MemoryContext rd_pdcxt;		/* private context for partdesc */
+	FmgrInfo   *rd_partsupfunc;	/* lookup info for partition support
+									 * procs */
 	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
 	List	   *rd_partcheck;	/* partition CHECK quals */
 
@@ -218,6 +220,21 @@ typedef struct ForeignKeyCacheInfo
 	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK operator OIDs */
 } ForeignKeyCacheInfo;
 
+/*
+ * PartitionInfo
+ *		Information related to partitioning for a partitioned table
+ *
+ * This consists of the information from PartitionKey and PartitionDesc
+ * structs that are cached in the table's RelationData.
+ */
+typedef struct PartitionInfo
+{
+	PartitionKey	key;
+	int				nparts;
+	Oid			   *oids;
+	struct PartitionBoundInfoData *boundinfo;
+} PartitionInfo;
+
 /*
  * Options common for all all indexes
  */
@@ -583,12 +600,6 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
-/*
- * RelationGetPartitionKey
- *		Returns the PartitionKey of a relation
- */
-#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
-
 /*
  * PartitionKey inquiry functions
  */
@@ -631,12 +642,6 @@ get_partition_col_typmod(PartitionKey key, int col)
 	return key->parttypmod[col];
 }
 
-/*
- * RelationGetPartitionDesc
- *		Returns partition descriptor for a relation.
- */
-#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
-
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
-- 
2.15.1 (Apple Git-101)

#17Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Amit Langote (#16)
Re: crash with sql language partition support function

I think this is a good improvement. On top of that, I propose a new
file partitioning/partdefs.h with the following approximate contents.
This reduces cross-inclusion of headers to the minimum. I'm dealing
with the fallout from this now, will post a complete patch shortly.

/*-------------------------------------------------------------------------
*
* partdefs.h
* Base definitions for partitioned table handling
*
* Copyright (c) 2007-2018, PostgreSQL Global Development Group
*
* src/include/partitioning/partdefs.h
*
*-------------------------------------------------------------------------
*/
#ifndef PARTDEFS_H
#define PARTDEFS_H

typedef enum PartitionRangeDatumKind
{
PARTITION_RANGE_DATUM_MINVALUE = -1, /* less than any other value */
PARTITION_RANGE_DATUM_VALUE = 0, /* a specific (bounded) value */
PARTITION_RANGE_DATUM_MAXVALUE = 1 /* greater than any other value */
} PartitionRangeDatumKind;

typedef struct PartitionBoundInfoData *PartitionBoundInfo;

typedef struct PartitionKeyData *PartitionKey;

typedef struct PartitionBoundSpec PartitionBoundSpec;

typedef struct PartitionDescData *PartitionDesc;

#endif /* PARTDEFS_H */

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#18Andres Freund
andres@anarazel.de
In reply to: Alvaro Herrera (#17)
Re: crash with sql language partition support function

Hi,

On 2018-04-13 15:08:30 -0300, Alvaro Herrera wrote:

I think this is a good improvement. On top of that, I propose a new
file partitioning/partdefs.h with the following approximate contents.
This reduces cross-inclusion of headers to the minimum. I'm dealing
with the fallout from this now, will post a complete patch shortly.

It'd be good to adjust the thread topic - this surely isn't about the
crash anymore. And it's good, especially given we're past the feature
freeze etc, if the subject conveyed what's happening?

- Andres