Subject: Report error position in partition bound check

From: Alexandra Wang <[email-masked]>
Date: Wed, 8 Apr 2020 17:15:57 -0700

Hi,

I'm playing with partitioned tables and found a minor thing with the
error reporting of bounds checking when create partitions.

In function check_new_partition_bound(), there are three places where
we call ereport() with a parser_errposition(pstate, spec->location)
argument.  However, that pstate is a dummy ParseState made from NULL,
so the error message never reports the position of the error in the
source query line.

I have attached a patch to pass in a ParseState to
check_new_partition_bound() to enable the reporting of the error
position. Below is what the error message looks like before and after
applying the patch.

-- Create parent table
create table foo (a int, b date) partition by range (b);

-- Before:
create table foo_part_1 partition of foo for values from (date
'2007-01-01') to (date '2006-01-01');
ERROR:  empty range bound specified for partition "foo_part_1"
DETAIL:  Specified lower bound ('2007-01-01') is greater than or equal to
upper bound ('2006-01-01').

-- After:
create table foo_part_1 partition of foo for values from (date
'2007-01-01') to (date '2006-01-01');
ERROR:  empty range bound specified for partition "foo_part_1"
LINE 1: ...eate table foo_part_1 partition of foo for values from (date...
                                                             ^
DETAIL:  Specified lower bound ('2007-01-01') is greater than or equal to
upper bound ('2006-01-01').

Another option is to not pass the parser_errposition() argument at all
to ereport() in this function, since the query is relatively short and
the error message is already descriptive enough.

Alex and Ashwin
From b1aedddaf7ae0f609910ff82af136d93c0132c2e Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email-masked]>
Date: Wed, 8 Apr 2020 16:07:28 -0700
Subject: [PATCH] Report error position in partition bound check

We have been passing a dummy ParseState to ereport(). Without the source
text in the ParseState ereport does not report the error position even
if a error location is supplied. This patch passes a ParseState to
check_new_partition_bound() when it is available.

-- Create parent table
create table foo (a int, b date) partition by range (b);

-- Before:
create table foo_part_1 partition of foo for values from (date '2007-01-01') to (date '2006-01-01');
ERROR:  empty range bound specified for partition "foo_part_1"
DETAIL:  Specified lower bound ('2007-01-01') is greater than or equal to upper bound ('2006-01-01').

-- After:
create table foo_part_1 partition of foo for values from (date '2007-01-01') to (date '2006-01-01');
ERROR:  empty range bound specified for partition "foo_part_1"
LINE 1: ...eate table foo_part_1 partition of foo for values from (date...
                                                             ^
DETAIL:  Specified lower bound ('2007-01-01') is greater than or equal to upper bound ('2006-01-01').

Co-authored-by: Ashwin Agrawal<[email-masked]>
---
 src/backend/commands/tablecmds.c      | 4 ++--
 src/backend/partitioning/partbounds.c | 3 +--
 src/include/partitioning/partbounds.h | 4 +++-
 3 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6162fb018c..46df40bee8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1004,7 +1004,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 * Check first that the new partition's bound is valid and does not
 		 * overlap with any of existing partitions of the parent.
 		 */
-		check_new_partition_bound(relname, parent, bound);
+		check_new_partition_bound(relname, parent, bound, pstate);
 
 		/*
 		 * If the default partition exists, its partition constraints will
@@ -16268,7 +16268,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * error.
 	 */
 	check_new_partition_bound(RelationGetRelationName(attachrel), rel,
-							  cmd->bound);
+							  cmd->bound, NULL);
 
 	/* OK to create inheritance.  Rest of the checks performed there */
 	CreateInheritance(attachrel, rel);
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 7607501fe7..dd56832efc 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -2803,12 +2803,11 @@ partitions_are_ordered(PartitionBoundInfo boundinfo, int nparts)
  */
 void
 check_new_partition_bound(char *relname, Relation parent,
-						  PartitionBoundSpec *spec)
+						  PartitionBoundSpec *spec, ParseState *pstate)
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
 	PartitionBoundInfo boundinfo = partdesc->boundinfo;
-	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
 
diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h
index dfc720720b..c82f77d02f 100644
--- a/src/include/partitioning/partbounds.h
+++ b/src/include/partitioning/partbounds.h
@@ -14,6 +14,7 @@
 #include "fmgr.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
+#include "parser/parse_node.h"
 #include "partitioning/partdefs.h"
 #include "utils/relcache.h"
 struct RelOptInfo;				/* avoid including pathnodes.h here */
@@ -98,7 +99,8 @@ extern PartitionBoundInfo partition_bounds_merge(int partnatts,
 												 List **inner_parts);
 extern bool partitions_are_ordered(PartitionBoundInfo boundinfo, int nparts);
 extern void check_new_partition_bound(char *relname, Relation parent,
-									  PartitionBoundSpec *spec);
+									  PartitionBoundSpec *spec,
+									  ParseState *pstate);
 extern void check_default_partition_contents(Relation parent,
 											 Relation defaultRel,
 											 PartitionBoundSpec *new_spec);
-- 
2.26.0

---

From: Alexandra Wang <[email-masked]>
Date: Wed, 8 Apr 2020 18:05:58 -0700

Forgot to run make installcheck. Here's the new version of the patch that
updated the test answer file.
From 9071918648412383e41976a01106257bd6a2539e Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email-masked]>
Date: Wed, 8 Apr 2020 16:07:28 -0700
Subject: [PATCH v2] Report error position in partition bound check

-- Create parent table

-- Before:

-- After:

Co-authored-by: Ashwin Agrawal<[email-masked]>
---
 src/backend/commands/tablecmds.c           |  4 +--
 src/backend/partitioning/partbounds.c      |  3 +--
 src/include/partitioning/partbounds.h      |  4 ++-
 src/test/regress/expected/create_table.out | 30 ++++++++++++++++++++++
 4 files changed, 36 insertions(+), 5 deletions(-)

 		/*

diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h
index dfc720720b..c82f77d02f 100644
--- a/src/include/partitioning/partbounds.h
+++ b/src/include/partitioning/partbounds.h
@@ -14,6 +14,7 @@
 #include "fmgr.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
+#include "parser/parse_node.h"
 #include "partitioning/partdefs.h"
 #include "utils/relcache.h"
 struct RelOptInfo;				/* avoid including pathnodes.h here */
@@ -98,7 +99,8 @@ extern PartitionBoundInfo partition_bounds_merge(int partnatts,
 												 List **inner_parts);
 extern bool partitions_are_ordered(PartitionBoundInfo boundinfo, int nparts);
 extern void check_new_partition_bound(char *relname, Relation parent,
-									  PartitionBoundSpec *spec);
+									  PartitionBoundSpec *spec,
+									  ParseState *pstate);
 extern void check_default_partition_contents(Relation parent,
 											 Relation defaultRel,
 											 PartitionBoundSpec *new_spec);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1c72f23bc9..b8de012536 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -677,6 +677,8 @@ LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES WITH (MODU...
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
+LINE 1: ...TE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+                                                               ^
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -702,6 +704,8 @@ CREATE TABLE bigintp_10 PARTITION OF bigintp FOR VALUES IN (10);
 -- fails due to overlap:
 CREATE TABLE bigintp_10_2 PARTITION OF bigintp FOR VALUES IN ('10');
 ERROR:  partition "bigintp_10_2" would overlap partition "bigintp_10"
+LINE 1: ...ABLE bigintp_10_2 PARTITION OF bigintp FOR VALUES IN ('10');
+                                                             ^
 DROP TABLE bigintp;
 CREATE TABLE range_parted (
 	a date
@@ -823,8 +827,12 @@ CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
+LINE 1: ...LE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+                                                             ^
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+LINE 1: ...LE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', '...
+                                                             ^
 -- check default partition overlap
 INSERT INTO list_parted2 VALUES('X');
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
@@ -835,28 +843,42 @@ CREATE TABLE range_parted2 (
 -- trying to create range partition with empty range
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
 ERROR:  empty range bound specified for partition "fail_part"
+LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) T...
+                                                             ^
 DETAIL:  Specified lower bound (1) is greater than or equal to upper bound (0).
 -- note that the range '[1, 1)' has no elements
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
 ERROR:  empty range bound specified for partition "fail_part"
+LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) T...
+                                                             ^
 DETAIL:  Specified lower bound (1) is greater than or equal to upper bound (1).
 CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (minvalue) TO (1);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (minvalue) TO (2);
 ERROR:  partition "fail_part" would overlap partition "part0"
+LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (minv...
+                                                             ^
 CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part1"
+LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) T...
+                                                             ^
 CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30);
 CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
 ERROR:  partition "fail_part" would overlap partition "part2"
+LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) ...
+                                                             ^
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
 ERROR:  partition "fail_part" would overlap partition "part2"
+LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) ...
+                                                             ^
 -- Create a default partition for range partitioned table
 CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
 -- More than one default partition is not allowed, so this should give error
 CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
 ERROR:  partition "fail_default_part" conflicts with existing default partition "range2_default"
+LINE 1: ... TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+                                                               ^
 -- Check if the range for default partitions overlap
 INSERT INTO range_parted2 VALUES (85);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
@@ -870,17 +892,23 @@ CREATE TABLE range_parted3 (
 CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, minvalue) TO (0, maxvalue);
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, minvalue) TO (0, 1);
 ERROR:  partition "fail_part" would overlap partition "part00"
+LINE 1: ...E fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, m...
+                                                             ^
 CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, 1);
 CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
 CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
 ERROR:  partition "fail_part" would overlap partition "part12"
+LINE 1: ...E fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 1...
+                                                             ^
 CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- cannot create a partition that says column b is allowed to range
 -- from -infinity to +infinity, while there exist partitions that have
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
+LINE 1: ...E fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, m...
+                                                             ^
 -- check for partition bound overlap and other invalid specifications for the hash partition
 CREATE TABLE hash_parted2 (
 	a varchar
@@ -892,6 +920,8 @@ CREATE TABLE h2part_4 PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 8, REMA
 -- overlap with part_4
 CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
 ERROR:  partition "fail_part" would overlap partition "h2part_4"
+LINE 1: ...LE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (MODU...
+                                                             ^
 -- modulus must be greater than zero
 CREATE TABLE fail_part PARTITION OF hash_parted2 FOR VALUES WITH (MODULUS 0, REMAINDER 1);
 ERROR:  modulus for hash partition must be a positive integer
-- 
2.26.0

---

From: Michael Paquier <[email-masked]>
Date: Thu, 9 Apr 2020 10:11:03 +0900

> the error message is already descriptive enough.

It depends on the complexity of the relation definition, so adding a
position looks like a good idea to me.  Anyway, even if this looks
like an oversight to me, we are post feature freeze for 13 and that's
an improvement, so this looks like material for PG14 to me.  Are there
more opinions on the matter?

Please note that you forgot to update the regression test output.
--
Michael

---

From: Alexandra Wang <[email-masked]>
Date: Wed, 8 Apr 2020 20:17:55 -0700

> Please note that you forgot to update the regression test output.

Yep thanks! Please see my previous email for the updated patch.

---

From: Michael Paquier <[email-masked]>
Date: Thu, 9 Apr 2020 12:40:35 +0900

> Yep thanks! Please see my previous email for the updated patch.

Thanks, I saw the update.  It looks like my email was a couple of
minutes too late :)

Could you add this patch to the next commit fest [1]?

[1]: https://commitfest.postgresql.org/28/
--
Michael

---

From: Ashutosh Bapat <[email-masked]>
Date: Thu, 9 Apr 2020 19:21:15 +0530

Hi Alexandra,
As Michael said it will be considered for the next commitfest. But
from a quick glance, a suggestion.
Instead of passing NULL parsestate from ATExecAttachPartition, pass
make_parsestate(NULL). parse_errorposition() takes care of NULL parse
state input, but it might be safer this way. Better if we could cook
up a parse state with the query text available in
AlterTableUtilityContext available in ATExecCmd().

On Thu, Apr 9, 2020 at 6:36 AM Alexandra Wang <[email-masked]> wrote:
>
> Forgot to run make installcheck. Here's the new version of the patch that updated the test answer file.
>

-- 
Best Wishes,
Ashutosh Bapat

---

From: Tom Lane <[email-masked]>
Date: Thu, 09 Apr 2020 10:03:55 -0400

While I'm quite on board with providing useful error cursors,
the example cases in this patch don't seem all that useful:

As best I can tell from these examples, the cursor will always
point at the FROM keyword, making it pretty unhelpful.  It seems
like in addition to getting the query string passed down, you
need to do some work on the code that's actually reporting the
error position.  I'd expect at a minimum that the pointer allows
identifying which column of a multi-column partition key is
giving trouble.  The phrasing of this particular message, for
example, suggests that it ought to point at the "1" expression.

			regards, tom lane

---

From: Amit Langote <[email-masked]>
Date: Thu, 9 Apr 2020 23:08:36 +0900

> AlterTableUtilityContext available in ATExecCmd().

+1.  Maybe pass the *context* down to ATExecAttachPartition() from
ATExecCmd() rather than a ParseState.

-- 

Amit Langote
EnterpriseDB: http://www.enterprisedb.com

---

From: Amit Langote <[email-masked]>
Date: Fri, 10 Apr 2020 18:01:37 +0900

> example, suggests that it ought to point at the "1" expression.

I agree with that.  Tried that in the attached 0002, although trying
to get the cursor to point to exactly the offending column seems a bit
tough for partition overlap errors.  The patch does allow to single
out which one of the lower and upper bounds is causing the overlap
with an existing partition, which is better than now and seems helpful
enough.

Also, updated Alexandra's patch to incorporate Ashutosh's comment such
that we get the same output with ATTACH PARTITION commands too.

-- 

Amit Langote

---

From: Ashutosh Bapat <[email-masked]>
Date: Fri, 10 Apr 2020 21:07:25 +0530

> that we get the same output with ATTACH PARTITION commands too.

I looked at this briefly. It looks good, but I will review more in the next
CF. Do we have entry there yet? To nit-pick: for a multi-key value the ^
points to the first column and the reader may think that that's the
problematci column. Should it instead point to ( ?

-- 
Best Wishes,
Ashutosh

---

From: Alexandra Wang <[email-masked]>
Date: Fri, 10 Apr 2020 09:59:49 -0700

> that we get the same output with ATTACH PARTITION commands too.

Thank you Amit for updating the patches, the cursor looks much helpful now.
I
created the commitfest entry https://commitfest.postgresql.org/28/2533/

---

From: Alexandra Wang <[email-masked]>
Date: Fri, 10 Apr 2020 14:50:17 -0700

On Fri, Apr 10, 2020 at 8:37 AM Ashutosh Bapat <
[email-masked]> wrote:
> for a multi-key value the ^

I attached a v2 of Amit's 0002 patch to also report the exact column
for the partition overlap errors.
From f7cf86fcf7c1a26895e68f357717244f776fe5cd Mon Sep 17 00:00:00 2001
From: Alexandra Wang <[email-masked]>
Date: Fri, 10 Apr 2020 13:51:53 -0700
Subject: [PATCH v2] Improve check new partition bound error position report

---
 src/backend/parser/parse_utilcmd.c         |  3 ++
 src/backend/partitioning/partbounds.c      | 60 +++++++++++++++-------
 src/test/regress/expected/alter_table.out  |  2 +-
 src/test/regress/expected/create_table.out | 28 +++++-----
 4 files changed, 59 insertions(+), 34 deletions(-)

diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 75c122fe34..72113bb7ff 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4170,5 +4170,8 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
 	if (!IsA(value, Const))
 		elog(ERROR, "could not evaluate partition bound expression");
 
+	/* Preserve parser location information. */
+	((Const *) value)->location = exprLocation(val);
+
 	return (Const *) value;
 }
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index dd56832efc..0ef0f6ed07 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -223,7 +223,8 @@ static int32 partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
 static int	partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
 									Oid *partcollation,
 									PartitionBoundInfo boundinfo,
-									PartitionRangeBound *probe, bool *is_equal);
+									PartitionRangeBound *probe, bool *is_equal,
+									int32 *cmpval);
 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);
@@ -2810,6 +2811,7 @@ check_new_partition_bound(char *relname, Relation parent,
 	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	int			with = -1;
 	bool		overlap = false;
+	int			overlap_location = 0;
 
 	if (spec->is_default)
 	{
@@ -2904,6 +2906,7 @@ check_new_partition_bound(char *relname, Relation parent,
 						if (boundinfo->indexes[remainder] != -1)
 						{
 							overlap = true;
+							overlap_location = spec->location;
 							with = boundinfo->indexes[remainder];
 							break;
 						}
@@ -2932,6 +2935,7 @@ check_new_partition_bound(char *relname, Relation parent,
 					{
 						Const	   *val = castNode(Const, lfirst(cell));
 
+						overlap_location = val->location;
 						if (!val->constisnull)
 						{
 							int			offset;
@@ -2965,6 +2969,7 @@ check_new_partition_bound(char *relname, Relation parent,
 			{
 				PartitionRangeBound *lower,
 						   *upper;
+				int			cmpval;
 
 				Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 				lower = make_one_partition_rbound(key, -1, spec->lowerdatums, true);
@@ -2974,10 +2979,16 @@ 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,
-										 key->partcollation, lower->datums,
-										 lower->kind, true, upper) >= 0)
+				cmpval = partition_rbound_cmp(key->partnatts,
+											  key->partsupfunc,
+											  key->partcollation, lower->datums,
+											  lower->kind, true, upper);
+				if (cmpval >= 0)
 				{
+					/* Fetch the problem bound from lower datums list. */
+					PartitionRangeDatum *datum = list_nth(spec->lowerdatums,
+														  cmpval - 1);
+
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 							 errmsg("empty range bound specified for partition \"%s\"",
@@ -2985,7 +2996,7 @@ check_new_partition_bound(char *relname, Relation parent,
 							 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)));
+							 parser_errposition(pstate, datum->location)));
 				}
 
 				if (partdesc->nparts > 0)
@@ -3017,7 +3028,7 @@ check_new_partition_bound(char *relname, Relation parent,
 													 key->partsupfunc,
 													 key->partcollation,
 													 boundinfo, lower,
-													 &equal);
+													 &equal, &cmpval);
 
 					if (boundinfo->indexes[offset + 1] < 0)
 					{
@@ -3029,7 +3040,6 @@ check_new_partition_bound(char *relname, Relation parent,
 						 */
 						if (offset + 1 < boundinfo->ndatums)
 						{
-							int32		cmpval;
 							Datum	   *datums;
 							PartitionRangeDatumKind *kind;
 							bool		is_lower;
@@ -3051,6 +3061,8 @@ check_new_partition_bound(char *relname, Relation parent,
 								 * offset + 2.
 								 */
 								overlap = true;
+								overlap_location = ((PartitionRangeDatum *)
+									list_nth(spec->upperdatums, -cmpval - 1))->location;
 								with = boundinfo->indexes[offset + 2];
 							}
 						}
@@ -3061,7 +3073,13 @@ check_new_partition_bound(char *relname, Relation parent,
 						 * The new partition overlaps with the existing
 						 * partition between offset and offset + 1.
 						 */
+						Datum *datum;
+
 						overlap = true;
+						Assert(cmpval >= 0);
+						datum = cmpval == 0 ? linitial(spec->lowerdatums):
+							list_nth(spec->lowerdatums, cmpval - 1);
+						overlap_location = ((PartitionRangeDatum *)datum)->location;
 						with = boundinfo->indexes[offset + 1];
 					}
 				}
@@ -3077,11 +3095,12 @@ check_new_partition_bound(char *relname, Relation parent,
 	if (overlap)
 	{
 		Assert(with >= 0);
+		Assert(overlap_location > 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)));
+				 parser_errposition(pstate, overlap_location)));
 	}
 }
 
@@ -3315,7 +3334,9 @@ make_one_partition_rbound(PartitionKey key, int index, List *datums, bool lower)
  * 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.
+ * kind1, and lower1) is <, =, or > the bound specified in *b2. 0 is returned if
+ * equal and the 1-based index of the first mismatching bound if unequal;
+ * multiplied by -1 if the 1st bound is smaller.
  *
  * partnatts, partsupfunc and partcollation give the number of attributes in the
  * bounds to be compared, comparison function to be used and the collations of
@@ -3335,6 +3356,7 @@ partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
 					 bool lower1, PartitionRangeBound *b2)
 {
 	int32		cmpval = 0;		/* placate compiler */
+	int			result = 0;
 	int			i;
 	Datum	   *datums2 = b2->datums;
 	PartitionRangeDatumKind *kind2 = b2->kind;
@@ -3342,6 +3364,8 @@ partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
 
 	for (i = 0; i < partnatts; i++)
 	{
+		result++;
+
 		/*
 		 * First, handle cases where the column is unbounded, which should not
 		 * invoke the comparison procedure, and should not consider any later
@@ -3349,9 +3373,9 @@ partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
 		 * compare the same way as the values they represent.
 		 */
 		if (kind1[i] < kind2[i])
-			return -1;
+			return -result;
 		else if (kind1[i] > kind2[i])
-			return 1;
+			return result;
 		else if (kind1[i] != PARTITION_RANGE_DATUM_VALUE)
 
 			/*
@@ -3376,9 +3400,9 @@ partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
 	 * two.
 	 */
 	if (cmpval == 0 && lower1 != lower2)
-		cmpval = lower1 ? 1 : -1;
+		cmpval = lower1 ? result : -result;
 
-	return cmpval;
+	return cmpval == 0 ? 0 : (cmpval < 0 ? -result : result);;
 }
 
 /*
@@ -3490,7 +3514,7 @@ static int
 partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
 						Oid *partcollation,
 						PartitionBoundInfo boundinfo,
-						PartitionRangeBound *probe, bool *is_equal)
+						PartitionRangeBound *probe, bool *is_equal, int32 *cmpval)
 {
 	int			lo,
 				hi,
@@ -3500,19 +3524,17 @@ partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
 	hi = boundinfo->ndatums - 1;
 	while (lo < hi)
 	{
-		int32		cmpval;
-
 		mid = (lo + hi + 1) / 2;
-		cmpval = partition_rbound_cmp(partnatts, partsupfunc,
+		*cmpval = partition_rbound_cmp(partnatts, partsupfunc,
 									  partcollation,
 									  boundinfo->datums[mid],
 									  boundinfo->kind[mid],
 									  (boundinfo->indexes[mid] == -1),
 									  probe);
-		if (cmpval <= 0)
+		if (*cmpval <= 0)
 		{
 			lo = mid;
-			*is_equal = (cmpval == 0);
+			*is_equal = (*cmpval == 0);
 
 			if (*is_equal)
 				break;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 576f19b38e..b3174694e3 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3793,7 +3793,7 @@ CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
 LINE 1: ...LE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
-                                                                ^
+                                                                    ^
 DROP TABLE fail_part;
 -- check that an existing table can be attached as a default partition
 CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index b8de012536..41dce69cc4 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -705,7 +705,7 @@ CREATE TABLE bigintp_10 PARTITION OF bigintp FOR VALUES IN (10);
 CREATE TABLE bigintp_10_2 PARTITION OF bigintp FOR VALUES IN ('10');
 ERROR:  partition "bigintp_10_2" would overlap partition "bigintp_10"
 LINE 1: ...ABLE bigintp_10_2 PARTITION OF bigintp FOR VALUES IN ('10');
-                                                             ^
+                                                                 ^
 DROP TABLE bigintp;
 CREATE TABLE range_parted (
 	a date
@@ -828,10 +828,10 @@ CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 LINE 1: ...LE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
-                                                             ^
+                                                                 ^
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
-LINE 1: ...LE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', '...
+LINE 1: ...ail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
                                                              ^
 -- check default partition overlap
 INSERT INTO list_parted2 VALUES('X');
@@ -843,35 +843,35 @@ CREATE TABLE range_parted2 (
 -- trying to create range partition with empty range
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
 ERROR:  empty range bound specified for partition "fail_part"
-LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) T...
+LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
                                                              ^
 DETAIL:  Specified lower bound (1) is greater than or equal to upper bound (0).
 -- note that the range '[1, 1)' has no elements
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
 ERROR:  empty range bound specified for partition "fail_part"
-LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) T...
+LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
                                                              ^
 DETAIL:  Specified lower bound (1) is greater than or equal to upper bound (1).
 CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (minvalue) TO (1);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (minvalue) TO (2);
 ERROR:  partition "fail_part" would overlap partition "part0"
-LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (minv...
+LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (minvalue) ...
                                                              ^
 CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part1"
-LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) T...
+LINE 1: ..._part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (max...
                                                              ^
 CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30);
 CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
 ERROR:  partition "fail_part" would overlap partition "part2"
-LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) ...
-                                                             ^
+LINE 1: ...art PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
+                                                                   ^
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
 ERROR:  partition "fail_part" would overlap partition "part2"
-LINE 1: ...E fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) ...
-                                                             ^
+LINE 1: ...art PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+                                                                   ^
 -- Create a default partition for range partitioned table
 CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
 -- More than one default partition is not allowed, so this should give error
@@ -892,14 +892,14 @@ CREATE TABLE range_parted3 (
 CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, minvalue) TO (0, maxvalue);
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, minvalue) TO (0, 1);
 ERROR:  partition "fail_part" would overlap partition "part00"
-LINE 1: ...E fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, m...
+LINE 1: ..._part PARTITION OF range_parted3 FOR VALUES FROM (0, minvalu...
                                                              ^
 CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, 1);
 CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
 CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
 ERROR:  partition "fail_part" would overlap partition "part12"
-LINE 1: ...E fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 1...
+LINE 1: ...rt PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1,...
                                                              ^
 CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- cannot create a partition that says column b is allowed to range
@@ -907,7 +907,7 @@ CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
-LINE 1: ...E fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, m...
+LINE 1: ..._part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalu...
                                                              ^
 -- check for partition bound overlap and other invalid specifications for the hash partition
 CREATE TABLE hash_parted2 (
-- 
2.26.0