Fix incorrect column name in error message for range partition bound check
Hi all,
Recently, while working with partitioned tables, I identified an issue where the error message references an incorrect column name when a range-partitioned table’s boundary values include MINVALUE or MAXVALUE.
For example:
postgres=# CREATE TABLE pt_test_colname(a int, b int, c int) PARTITION BY RANGE(a, b);
-- right case
postgres=# CREATE TABLE pt_test_colname_p1 PARTITION OF pt_test_colname FOR VALUES FROM (10, now()) TO (100, 100);
ERROR: specified value cannot be cast to type integer for column "b"
LINE 1: ...PARTITION OF pt_test_colname FOR VALUES FROM (10, now()) TO ...
^
-- wrong case
postgres=# CREATE TABLE pt_test_colname_p1 PARTITION OF pt_test_colname FOR VALUES FROM (minvalue, now()) TO (100, 100);
ERROR: specified value cannot be cast to type integer for column "a"
LINE 1: ...ION OF pt_test_colname FOR VALUES FROM (minvalue, now()) TO ...
^
The datetime value returned by now() cannot be cast to the partition key b (integer type), yet the error message incorrectly attributes this casting failure to column a when the preceding boundary value is the special MINVALUE or MAXVALUE.
Attachments:
v1-0001-Fix-incorrect-column-name-in-error-message-for-range.patchapplication/octet-stream; name=v1-0001-Fix-incorrect-column-name-in-error-message-for-range.patch; x-cm-securityLevel=0Download
From 11f9f048a40d15ad563e4ca405c7e27ca2ae6e36 Mon Sep 17 00:00:00 2001
From: myzhen <zhenmingyang@yeah.net>
Date: Tue, 6 Jan 2026 18:42:28 +0800
Subject: [PATCH] Fix incorrect column name in error message for range
partition bound check.
When range partition boundary value lists contain MINVALUE or MAXVALUE, and subsequent
boundary values cannot be implicitly cast to the corresponding partition key data type,
the error message references an incorrect column name.
---
src/backend/parser/parse_utilcmd.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 3f4393b52ea..d739ca96813 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4841,10 +4841,10 @@ transformPartitionRangeBounds(ParseState *pstate, List *blist,
prd = makeNode(PartitionRangeDatum);
prd->kind = PARTITION_RANGE_DATUM_VALUE;
prd->value = (Node *) value;
- ++i;
}
prd->location = exprLocation(expr);
+ ++i;
result = lappend(result, prd);
}
--
2.28.0.windows.1
Hi myzhen,
On Wed, Jan 7, 2026 at 1:48 PM myzhen <zhenmingyang@yeah.net> wrote:
postgres=# CREATE TABLE pt_test_colname_p1 PARTITION OF pt_test_colname
FOR VALUES FROM (minvalue, now()) TO (100, 100);
ERROR: specified value cannot be cast to type integer for column "a"
LINE 1: ...ION OF pt_test_colname FOR VALUES FROM (minvalue, now()) TO ...
^
The datetime value returned by now() cannot be cast to the partition key b
(integer type), yet the error message incorrectly attributes this casting
failure to column a when the preceding boundary value is the special
MINVALUE or MAXVALUE.
Yes, I think you are correct. It seems that this is an error prompt caused
by the infinite boundary values MINVALUE and MAXVALUE.
By the way, based on the context, once a column is set to MINVALUE or
MAXVALUE, the values of the remaining columns must be the same. However, I
noticed that 'validateInfiniteBounds' is only invoked after all boundary
value transformations are completed. Perhaps we can perform the validity
check for infinite boundary values earlier. In this way, we can not only
avoid this error but also achieve timely "short-circuit" — because any
non-MINVALUE transformation performed after MINVALUE is essentially
meaningless overhead. ideas?
Hi zhibin,
By the way, based on the context, once a column is set to MINVALUE or MAXVALUE, the values of the remaining columns must be the same. However, I noticed that 'validateInfiniteBounds' is only invoked after all boundary value transformations are completed. Perhaps we can perform the validity check for infinite boundary values earlier. In this way, we can not only avoid this error but also achieve timely "short-circuit" — because any non-MINVALUE transformation performed after MINVALUE is essentially meaningless overhead. ideas?
Thanks, I think it makes some sense, so I tried to make the second version.
Attachments:
v2-Fix-incorrect-column-name-in-error-message-for-range.patchapplication/octet-stream; name=v2-Fix-incorrect-column-name-in-error-message-for-range.patch; x-cm-securityLevel=0Download
From f8a18a1d8e129156123e1fc4b990d52a6f9c8539 Mon Sep 17 00:00:00 2001
From: myzhen <zhenmingyang@yeah.net>
Date: Tue, 6 Jan 2026 18:42:28 +0800
Subject: [PATCH] Fix incorrect column name in error message for range
partition bound check.
When range partition boundary value lists contain MINVALUE or MAXVALUE, and subsequent
boundary values cannot be implicitly cast to the corresponding partition key data type,
the error message references an incorrect column name.
---
src/backend/parser/parse_utilcmd.c | 85 ++++++++++++------------------
1 file changed, 35 insertions(+), 50 deletions(-)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 3f4393b52ea..6d909d407a7 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -140,7 +140,6 @@ static void setSchemaName(const char *context_schema, char **stmt_schema_name);
static void transformPartitionCmd(CreateStmtContext *cxt, PartitionBoundSpec *bound);
static List *transformPartitionRangeBounds(ParseState *pstate, List *blist,
Relation parent);
-static void validateInfiniteBounds(ParseState *pstate, List *blist);
static Const *transformPartitionBoundValue(ParseState *pstate, Node *val,
const char *colName, Oid colType, int32 colTypmod,
Oid partCollation);
@@ -4755,6 +4754,7 @@ transformPartitionRangeBounds(ParseState *pstate, List *blist,
PartitionKey key = RelationGetPartitionKey(parent);
List *partexprs = get_partition_exprs(key);
ListCell *lc;
+ PartitionRangeDatumKind kind = PARTITION_RANGE_DATUM_VALUE;
int i,
j;
@@ -4804,6 +4804,39 @@ transformPartitionRangeBounds(ParseState *pstate, List *blist,
}
}
+ /*
+ * Once we see MINVALUE or MAXVALUE for one column, the remaining columns
+ * must be the same.
+ */
+ if (kind == PARTITION_RANGE_DATUM_VALUE && prd)
+ kind = prd->kind;
+
+ if (kind != PARTITION_RANGE_DATUM_VALUE &&
+ (prd == NULL ||
+ kind != prd->kind))
+ {
+ switch (kind)
+ {
+ case PARTITION_RANGE_DATUM_VALUE:
+ /* shouldn't happen, but keep compiler quiet */
+ break;
+
+ case PARTITION_RANGE_DATUM_MAXVALUE:
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("every bound following MAXVALUE must also be MAXVALUE"),
+ parser_errposition(pstate, exprLocation(expr))));
+ break;
+
+ case PARTITION_RANGE_DATUM_MINVALUE:
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("every bound following MINVALUE must also be MINVALUE"),
+ parser_errposition(pstate, exprLocation(expr))));
+ break;
+ }
+ }
+
if (prd == NULL)
{
char *colname;
@@ -4841,65 +4874,17 @@ transformPartitionRangeBounds(ParseState *pstate, List *blist,
prd = makeNode(PartitionRangeDatum);
prd->kind = PARTITION_RANGE_DATUM_VALUE;
prd->value = (Node *) value;
- ++i;
}
prd->location = exprLocation(expr);
+ ++i;
result = lappend(result, prd);
}
- /*
- * Once we see MINVALUE or MAXVALUE for one column, the remaining columns
- * must be the same.
- */
- validateInfiniteBounds(pstate, result);
-
return result;
}
-/*
- * validateInfiniteBounds
- *
- * Check that a MAXVALUE or MINVALUE specification in a partition bound is
- * followed only by more of the same.
- */
-static void
-validateInfiniteBounds(ParseState *pstate, List *blist)
-{
- ListCell *lc;
- PartitionRangeDatumKind kind = PARTITION_RANGE_DATUM_VALUE;
-
- foreach(lc, blist)
- {
- PartitionRangeDatum *prd = lfirst_node(PartitionRangeDatum, lc);
-
- if (kind == prd->kind)
- continue;
-
- switch (kind)
- {
- case PARTITION_RANGE_DATUM_VALUE:
- kind = prd->kind;
- break;
-
- case PARTITION_RANGE_DATUM_MAXVALUE:
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("every bound following MAXVALUE must also be MAXVALUE"),
- parser_errposition(pstate, exprLocation((Node *) prd))));
- break;
-
- case PARTITION_RANGE_DATUM_MINVALUE:
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("every bound following MINVALUE must also be MINVALUE"),
- parser_errposition(pstate, exprLocation((Node *) prd))));
- break;
- }
- }
-}
-
/*
* Transform one entry in a partition bound spec, producing a constant.
*/
--
2.28.0.windows.1
Um, yes, as long as this problem can be solved, that's fine.
patch v2 may do less and reduce unnecessary transforms,
but the impact on DDL is minimal and may not need to be considered.
At 2026-01-08 08:36:06, "Eden Wang" <wanglintao_cn@163.com> wrote:
Hi myzhen,
Hmmm...personally, I prefer the first patch – it resolves this issue and is more concise.
Eden Wang
Import Notes
Reply to msg id not found: 202601080836046225584@163.com
On Wed, 7 Jan 2026 at 18:45, myzhen <zhenmingyang@yeah.net> wrote:
postgres=# CREATE TABLE pt_test_colname(a int, b int, c int) PARTITION BY RANGE(a, b);
-- wrong case
postgres=# CREATE TABLE pt_test_colname_p1 PARTITION OF pt_test_colname FOR VALUES FROM (minvalue, now()) TO (100, 100);
ERROR: specified value cannot be cast to type integer for column "a"
LINE 1: ...ION OF pt_test_colname FOR VALUES FROM (minvalue, now()) TO ...
(I thought I'd sent this email last week, but I don't see it in the
archives or my sent items)
I've pushed a fix similar to your v1 patch for this. I used
foreach_current_index rather than what you did in your v1. That seems
less prone to get broken again in the future if someone were to add
additional logic within the loop body.
I didn't see any need to do any more than that, so I didn't consider
anything your v2 patch did.
Thanks for the patch.
David