v1-0001-refactoring-transformForPortionOfClause.no-cfbot
application/octet-stream
Filename: v1-0001-refactoring-transformForPortionOfClause.no-cfbot
Type: application/octet-stream
Part: 0
From 6c8c386693a2568ea5b1f0dd6b7a854c067ca849 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 7 Apr 2026 12:03:07 +0800
Subject: [PATCH v1 1/1] refactoring transformForPortionOfClause
We should use coerce_to_target_type more, not can_coerce_type, coerce_type
individually. coerce_to_target_type also handles `UNKNOWN` constant, which
ensures the deparsing casts to the correct data type.
for example:
create view v1 as select 'a';
src9=# \sv v1
CREATE OR REPLACE VIEW public.v1 AS
SELECT 'a'::text AS "?column?"
context: https://git.postgresql.org/cgit/postgresql.git/commit/?id=8e72d914c52876525a90b28444453de8085c866f
---
src/backend/parser/analyze.c | 140 +++++++++++--------
src/test/regress/expected/for_portion_of.out | 11 +-
src/test/regress/sql/for_portion_of.sql | 3 +
3 files changed, 89 insertions(+), 65 deletions(-)
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 84deed9aaa6..6c897af43b5 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -24,6 +24,7 @@
#include "postgres.h"
+#include "access/htup_details.h"
#include "access/stratnum.h"
#include "access/sysattr.h"
#include "catalog/dependency.h"
@@ -1317,6 +1318,7 @@ transformForPortionOfClause(ParseState *pstate,
int range_attno = InvalidAttrNumber;
Form_pg_attribute attr;
Oid attbasetype;
+ int32 attbasetypmod = -1;
Oid opclass;
Oid opfamily;
Oid opcintype;
@@ -1346,7 +1348,7 @@ transformForPortionOfClause(ParseState *pstate,
parser_errposition(pstate, forPortionOf->location)));
attr = TupleDescAttr(targetrel->rd_att, range_attno - 1);
- attbasetype = getBaseType(attr->atttypid);
+ attbasetype = getBaseTypeAndTypmod(attr->atttypid, &attbasetypmod);
rangeVar = makeVar(rtindex,
range_attno,
@@ -1369,50 +1371,46 @@ transformForPortionOfClause(ParseState *pstate,
if (forPortionOf->target)
{
- Oid declared_target_type = attbasetype;
Oid actual_target_type;
+ /*
+ * XXX: For now we only support ranges and multiranges, so we fail on
+ * anything else.
+ */
+ if (!type_is_range(attbasetype) && !type_is_multirange(attbasetype))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("column \"%s\" of relation \"%s\" is not a range or multirange type",
+ forPortionOf->range_name,
+ RelationGetRelationName(targetrel)),
+ parser_errposition(pstate, forPortionOf->location));
+
/*
* We were already given an expression for the target, so we don't
* have to build anything. We still have to make sure we got the right
* type. NULL will be caught be the executor.
*/
-
result->targetRange = transformExpr(pstate,
forPortionOf->target,
EXPR_KIND_FOR_PORTION);
actual_target_type = exprType(result->targetRange);
- if (!can_coerce_type(1, &actual_target_type, &declared_target_type, COERCION_IMPLICIT))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("could not coerce FOR PORTION OF target from %s to %s",
- format_type_be(actual_target_type),
- format_type_be(declared_target_type)),
- parser_errposition(pstate, exprLocation(forPortionOf->target))));
-
- result->targetRange = coerce_type(pstate,
- result->targetRange,
- actual_target_type,
- declared_target_type,
- -1,
- COERCION_IMPLICIT,
- COERCE_IMPLICIT_CAST,
- exprLocation(forPortionOf->target));
+ result->targetRange =
+ coerce_to_target_type(pstate, result->targetRange, exprType(result->targetRange),
+ attbasetype,
+ attbasetypmod,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ exprLocation(forPortionOf->target));
- /*
- * XXX: For now we only support ranges and multiranges, so we fail on
- * anything else.
- */
- if (!type_is_range(attbasetype) && !type_is_multirange(attbasetype))
+ if (result->targetRange == NULL)
ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("column \"%s\" of relation \"%s\" is not a range or multirange type",
- forPortionOf->range_name,
- RelationGetRelationName(targetrel)),
- parser_errposition(pstate, forPortionOf->location)));
-
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not coerce FOR PORTION OF target from %s to %s",
+ format_type_be(actual_target_type),
+ format_type_be(attbasetype)),
+ parser_errposition(pstate, exprLocation(forPortionOf->target)));
}
else
{
@@ -1420,6 +1418,8 @@ transformForPortionOfClause(ParseState *pstate,
Oid declared_arg_types[2];
Oid actual_arg_types[2];
List *args;
+ HeapTuple tup;
+ int32 declared_arg_typTypeMod;
/*
* Make sure it's a range column. XXX: We could support this syntax on
@@ -1438,6 +1438,12 @@ transformForPortionOfClause(ParseState *pstate,
declared_arg_types[0] = rngsubtype;
declared_arg_types[1] = rngsubtype;
+ tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rngsubtype));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for type %u", rngsubtype);
+ declared_arg_typTypeMod = ((Form_pg_type) GETSTRUCT(tup))->typtypmod;
+ ReleaseSysCache(tup);
+
/*
* Build a range from the FROM ... TO ... bounds. This should give a
* constant result, so we accept functions like NOW() but not column
@@ -1451,31 +1457,42 @@ transformForPortionOfClause(ParseState *pstate,
EXPR_KIND_FOR_PORTION);
actual_arg_types[0] = exprType(result->targetFrom);
actual_arg_types[1] = exprType(result->targetTo);
+
+ result->targetFrom =
+ coerce_to_target_type(pstate, result->targetFrom, actual_arg_types[0],
+ declared_arg_types[0], declared_arg_typTypeMod,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ exprLocation(forPortionOf->target_start));
+
+ if (result->targetFrom == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not coerce FOR PORTION OF %s bound from %s to %s",
+ "FROM",
+ format_type_be(actual_arg_types[0]),
+ format_type_be(declared_arg_types[0])),
+ parser_errposition(pstate, exprLocation(forPortionOf->target_start)));
+
+ result->targetTo =
+ coerce_to_target_type(pstate, result->targetTo, actual_arg_types[1],
+ declared_arg_types[1], declared_arg_typTypeMod,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ exprLocation(forPortionOf->target_end));
+
+ if (result->targetTo == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not coerce FOR PORTION OF %s bound from %s to %s",
+ "TO",
+ format_type_be(actual_arg_types[1]),
+ format_type_be(declared_arg_types[1])),
+ parser_errposition(pstate, exprLocation(forPortionOf->target_end)));
+
args = list_make2(copyObject(result->targetFrom),
copyObject(result->targetTo));
- /*
- * Check the bound types separately, for better error message and
- * location
- */
- if (!can_coerce_type(1, actual_arg_types, declared_arg_types, COERCION_IMPLICIT))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("could not coerce FOR PORTION OF %s bound from %s to %s",
- "FROM",
- format_type_be(actual_arg_types[0]),
- format_type_be(declared_arg_types[0])),
- parser_errposition(pstate, exprLocation(forPortionOf->target_start))));
- if (!can_coerce_type(1, &actual_arg_types[1], &declared_arg_types[1], COERCION_IMPLICIT))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("could not coerce FOR PORTION OF %s bound from %s to %s",
- "TO",
- format_type_be(actual_arg_types[1]),
- format_type_be(declared_arg_types[1])),
- parser_errposition(pstate, exprLocation(forPortionOf->target_end))));
-
- make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
result->targetRange = (Node *) makeFuncExpr(get_range_constructor2(attbasetype),
attbasetype,
args,
@@ -1483,7 +1500,8 @@ transformForPortionOfClause(ParseState *pstate,
}
if (contain_volatile_functions_after_planning((Expr *) result->targetRange))
ereport(ERROR,
- (errmsg("FOR PORTION OF bounds cannot contain volatile functions")));
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("FOR PORTION OF bounds cannot contain volatile functions"));
/*
* Build overlapsExpr to use as an extra qual. This means we only hit rows
@@ -1575,14 +1593,14 @@ transformForPortionOfClause(ParseState *pstate,
* Coerce to domain if necessary. If we skip this, we will allow
* updating to forbidden values.
*/
- rangeTLEExpr = coerce_type(pstate,
- rangeTLEExpr,
- attbasetype,
- attr->atttypid,
- -1,
- COERCION_IMPLICIT,
- COERCE_IMPLICIT_CAST,
- exprLocation(forPortionOf->target));
+ rangeTLEExpr = coerce_to_target_type(pstate,
+ rangeTLEExpr,
+ attbasetype,
+ attr->atttypid,
+ attr->atttypmod,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST,
+ exprLocation(forPortionOf->target));
/* Make a TLE to set the range column */
result->rangeTargetList = NIL;
diff --git a/src/test/regress/expected/for_portion_of.out b/src/test/regress/expected/for_portion_of.out
index 31f772c723d..747fe3183cb 100644
--- a/src/test/regress/expected/for_portion_of.out
+++ b/src/test/regress/expected/for_portion_of.out
@@ -652,7 +652,7 @@ END;
RETURNS text
LANGUAGE sql
1 BEGIN ATOMIC
-2 UPDATE for_portion_of_test FOR PORTION OF valid_at FROM '2018-01-15' TO '2019-01-01' SET name = 'one^1'::text
+2 UPDATE for_portion_of_test FOR PORTION OF valid_at FROM '2018-01-15'::date TO '2019-01-01'::date SET name = 'one^1'::text
3 RETURNING for_portion_of_test.name;
4 END
CREATE OR REPLACE function fpo_update()
@@ -1010,6 +1010,8 @@ DELETE FROM for_portion_of_test WHERE id IN ('[10,11)');
CREATE FUNCTION fpo_delete()
RETURNS text
BEGIN ATOMIC
+ DELETE FROM for_portion_of_test
+ FOR PORTION OF valid_at ('[2018-01-15, 2019-01-01)');
DELETE FROM for_portion_of_test
FOR PORTION OF valid_at FROM '2018-01-15' TO '2019-01-01'
RETURNING name;
@@ -1019,9 +1021,10 @@ END;
RETURNS text
LANGUAGE sql
1 BEGIN ATOMIC
-2 DELETE FROM for_portion_of_test FOR PORTION OF valid_at FROM '2018-01-15' TO '2019-01-01'
-3 RETURNING for_portion_of_test.name;
-4 END
+2 DELETE FROM for_portion_of_test FOR PORTION OF valid_at ('[2018-01-15,2019-01-01)'::daterange);
+3 DELETE FROM for_portion_of_test FOR PORTION OF valid_at FROM '2018-01-15'::date TO '2019-01-01'::date
+4 RETURNING for_portion_of_test.name;
+5 END
CREATE OR REPLACE function fpo_delete()
RETURNS text
BEGIN ATOMIC
diff --git a/src/test/regress/sql/for_portion_of.sql b/src/test/regress/sql/for_portion_of.sql
index d4062acf1d1..938ae036634 100644
--- a/src/test/regress/sql/for_portion_of.sql
+++ b/src/test/regress/sql/for_portion_of.sql
@@ -651,6 +651,9 @@ DELETE FROM for_portion_of_test WHERE id IN ('[10,11)');
CREATE FUNCTION fpo_delete()
RETURNS text
BEGIN ATOMIC
+ DELETE FROM for_portion_of_test
+ FOR PORTION OF valid_at ('[2018-01-15, 2019-01-01)');
+
DELETE FROM for_portion_of_test
FOR PORTION OF valid_at FROM '2018-01-15' TO '2019-01-01'
RETURNING name;
--
2.34.1