Refactoring: Use soft error reporting for *_opt_overflow functions of date/timestamp
Hi,
The attached patch proposes to use soft error reporting infrastructure
for the date/timestamp conversion function, which currently depends on
integer variables to control error throwing.
This continues the previous refactoring commit [1] where we adopted
soft error reporting for some numeric functions. This patch applies
the same pattern to the date/timestamp function. The change ensures
consistency by utilizing the existing soft error reporting
infrastructure.
Note that in the patch, I renamed the function by replacing the
"no_overflow" extension in the function name with "overflow_safe".
Alternatively, we could just use "safe" alone. Suggestions are
welcome.
--
Regards,
Amul Sul
EDB: http://www.enterprisedb.com
Attachments:
0001-Switch-some-date-related-functions-to-use-soft-error.patchapplication/x-patch; name=0001-Switch-some-date-related-functions-to-use-soft-error.patchDownload
From 9d55abe251fe3085069c539a9888643b38ac6b86 Mon Sep 17 00:00:00 2001
From: Amul Sul <sulamul@gmail.com>
Date: Wed, 26 Nov 2025 14:54:03 +0530
Subject: [PATCH] Switch some date-related functions to use soft error
reporting.
Like 4246a977bad6e76, changing some functions related to the data type
date/timestamp to use the soft error reporting rather than a custom
boolean flag (called "overflow") that callers of these functions could
rely on to bypass the generation of ERROR reports, letting the callers
do their own error handling
XXX: Replacing the "no_overflow" extension in the function name with
"overflow_safe". We could, alternatively, just use "safe" alone.
---
contrib/btree_gin/btree_gin.c | 17 +--
src/backend/utils/adt/date.c | 200 +++++++++++-----------------------
src/include/utils/date.h | 12 +-
3 files changed, 78 insertions(+), 151 deletions(-)
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index 8c477d17e22..c07586bf213 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -7,6 +7,7 @@
#include "access/stratnum.h"
#include "mb/pg_wchar.h"
+#include "nodes/miscnodes.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/float.h"
@@ -496,9 +497,9 @@ cvt_date_timestamp(Datum input)
{
DateADT val = DatumGetDateADT(input);
Timestamp result;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- result = date2timestamp_opt_overflow(val, &overflow);
+ result = date2timestamp_overflow_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return TimestampGetDatum(result);
}
@@ -530,10 +531,10 @@ static Datum
cvt_date_timestamptz(Datum input)
{
DateADT val = DatumGetDateADT(input);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
TimestampTz result;
- int overflow;
- result = date2timestamptz_opt_overflow(val, &overflow);
+ result = date2timestamptz_overflow_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return TimestampTzGetDatum(result);
}
@@ -604,10 +605,10 @@ static Datum
cvt_timestamp_date(Datum input)
{
Timestamp val = DatumGetTimestamp(input);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
DateADT result;
- int overflow;
- result = timestamp2date_opt_overflow(val, &overflow);
+ result = timestamp2date_overflow_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return DateADTGetDatum(result);
}
@@ -616,10 +617,10 @@ static Datum
cvt_timestamptz_date(Datum input)
{
TimestampTz val = DatumGetTimestampTz(input);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
DateADT result;
- int overflow;
- result = timestamptz2date_opt_overflow(val, &overflow);
+ result = timestamptz2date_overflow_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return DateADTGetDatum(result);
}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..d7477a1fead 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -27,6 +27,7 @@
#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
+#include "nodes/miscnodes.h"
#include "nodes/supportnodes.h"
#include "parser/scansup.h"
#include "utils/array.h"
@@ -613,26 +614,16 @@ date_mii(PG_FUNCTION_ARGS)
/*
- * Promote date to timestamp.
+ * Promotes date to timestamp, including soft error reporting capabilities.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
- *
- * If the date is finite but out of the valid range for timestamp, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamp infinity.
- *
- * Note: *overflow = -1 is actually not possible currently, since both
+ * Note: Lower bound overflow is actually not possible currently, since both
* datatypes have the same lower bound, Julian day zero.
*/
Timestamp
-date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
+date2timestamp_overflow_safe(DateADT dateVal, Node *escontext)
{
Timestamp result;
- if (overflow)
- *overflow = 0;
-
if (DATE_IS_NOBEGIN(dateVal))
TIMESTAMP_NOBEGIN(result);
else if (DATE_IS_NOEND(dateVal))
@@ -645,18 +636,10 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
{
- if (overflow)
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- return result;
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
/* date is days since 2000, timestamp is microseconds since same... */
@@ -672,30 +655,21 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
static TimestampTz
date2timestamp(DateADT dateVal)
{
- return date2timestamp_opt_overflow(dateVal, NULL);
+ return date2timestamp_overflow_safe(dateVal, NULL);
}
/*
- * Promote date to timestamp with time zone.
- *
- * On successful conversion, *overflow is set to zero if it's not NULL.
- *
- * If the date is finite but out of the valid range for timestamptz, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamptz infinity.
+ * Promotes date to timestamp with time zone, including soft error reporting
+ * capabilities.
*/
TimestampTz
-date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
+date2timestamptz_overflow_safe(DateADT dateVal, Node *escontext)
{
TimestampTz result;
struct pg_tm tt,
*tm = &tt;
int tz;
- if (overflow)
- *overflow = 0;
-
if (DATE_IS_NOBEGIN(dateVal))
TIMESTAMP_NOBEGIN(result);
else if (DATE_IS_NOEND(dateVal))
@@ -708,18 +682,10 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
{
- if (overflow)
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- return result;
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
j2date(dateVal + POSTGRES_EPOCH_JDATE,
@@ -737,25 +703,14 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
*/
if (!IS_VALID_TIMESTAMP(result))
{
- if (overflow)
- {
- if (result < MIN_TIMESTAMP)
- {
- *overflow = -1;
- TIMESTAMP_NOBEGIN(result);
- }
- else
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- }
- }
+ if (result < MIN_TIMESTAMP)
+ TIMESTAMP_NOBEGIN(result);
else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
}
@@ -768,7 +723,7 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
static TimestampTz
date2timestamptz(DateADT dateVal)
{
- return date2timestamptz_opt_overflow(dateVal, NULL);
+ return date2timestamptz_overflow_safe(dateVal, NULL);
}
/*
@@ -808,15 +763,16 @@ int32
date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2)
{
Timestamp dt1;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- dt1 = date2timestamp_opt_overflow(dateVal, &overflow);
- if (overflow > 0)
+ dt1 = date2timestamp_overflow_safe(dateVal, (Node *) &escontext);
+ if (escontext.error_occurred)
{
+ Assert(TIMESTAMP_IS_NOEND(dt1)); /* NOBEGIN case cannot occur */
+
/* dt1 is larger than any finite timestamp, but less than infinity */
return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
}
- Assert(overflow == 0); /* -1 case cannot occur */
return timestamp_cmp_internal(dt1, dt2);
}
@@ -888,18 +844,22 @@ int32
date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2)
{
TimestampTz dt1;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- dt1 = date2timestamptz_opt_overflow(dateVal, &overflow);
- if (overflow > 0)
- {
- /* dt1 is larger than any finite timestamp, but less than infinity */
- return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
- }
- if (overflow < 0)
+ dt1 = date2timestamptz_overflow_safe(dateVal, (Node *) &escontext);
+
+ if (escontext.error_occurred)
{
- /* dt1 is less than any finite timestamp, but more than -infinity */
- return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ /* dt1 is larger than any finite timestamp, but less than infinity */
+ return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
+ }
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ /* dt1 is less than any finite timestamp, but more than -infinity */
+ return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ }
}
return timestamptz_cmp_internal(dt1, dt2);
@@ -1364,34 +1324,24 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_opt_overflow(timestamp, NULL);
+ result = timestamp2date_overflow_safe(timestamp, NULL);
PG_RETURN_DATEADT(result);
}
/*
- * Convert timestamp to date.
- *
- * On successful conversion, *overflow is set to zero if it's not NULL.
- *
- * If the timestamp is finite but out of the valid range for date, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate date infinity.
+ * Convert timestamp to date, including soft error reporting capabilities.
*
* Note: given the ranges of the types, overflow is only possible at
* the minimum end of the range, but we don't assume that in this code.
*/
DateADT
-timestamp2date_opt_overflow(Timestamp timestamp, int *overflow)
+timestamp2date_overflow_safe(Timestamp timestamp, Node *escontext)
{
DateADT result;
struct pg_tm tt,
*tm = &tt;
fsec_t fsec;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_IS_NOBEGIN(timestamp))
DATE_NOBEGIN(result);
else if (TIMESTAMP_IS_NOEND(timestamp))
@@ -1400,21 +1350,12 @@ timestamp2date_opt_overflow(Timestamp timestamp, int *overflow)
{
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- DATE_NOBEGIN(result);
- }
- else
- {
- *overflow = 1; /* not actually reachable */
- DATE_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ DATE_NOBEGIN(result);
+ else
+ DATE_NOEND(result); /* not actually reachable */
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
@@ -1450,25 +1391,18 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_opt_overflow(timestamp, NULL);
+ result = timestamptz2date_overflow_safe(timestamp, NULL);
PG_RETURN_DATEADT(result);
}
/*
- * Convert timestamptz to date.
- *
- * On successful conversion, *overflow is set to zero if it's not NULL.
- *
- * If the timestamptz is finite but out of the valid range for date, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate date infinity.
+ * Convert timestamptz to date, including soft error reporting capabilities.
*
* Note: given the ranges of the types, overflow is only possible at
* the minimum end of the range, but we don't assume that in this code.
*/
DateADT
-timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
+timestamptz2date_overflow_safe(TimestampTz timestamp, Node *escontext)
{
DateADT result;
struct pg_tm tt,
@@ -1476,9 +1410,6 @@ timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
fsec_t fsec;
int tz;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_IS_NOBEGIN(timestamp))
DATE_NOBEGIN(result);
else if (TIMESTAMP_IS_NOEND(timestamp))
@@ -1487,21 +1418,12 @@ timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- DATE_NOBEGIN(result);
- }
- else
- {
- *overflow = 1; /* not actually reachable */
- DATE_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ DATE_NOBEGIN(result);
+ else
+ DATE_NOEND(result); /* not actually reachable */
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index abfda0b1ae9..a4064c3a551 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -98,10 +98,14 @@ TimeTzADTPGetDatum(const TimeTzADT *X)
/* date.c */
extern int32 anytime_typmod_check(bool istz, int32 typmod);
extern double date2timestamp_no_overflow(DateADT dateVal);
-extern Timestamp date2timestamp_opt_overflow(DateADT dateVal, int *overflow);
-extern TimestampTz date2timestamptz_opt_overflow(DateADT dateVal, int *overflow);
-extern DateADT timestamp2date_opt_overflow(Timestamp timestamp, int *overflow);
-extern DateADT timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow);
+extern Timestamp date2timestamp_overflow_safe(DateADT dateVal,
+ Node *escontext);
+extern TimestampTz date2timestamptz_overflow_safe(DateADT dateVal,
+ Node *escontext);
+extern DateADT timestamp2date_overflow_safe(Timestamp timestamp,
+ Node *escontext);
+extern DateADT timestamptz2date_overflow_safe(TimestampTz timestamp,
+ Node *escontext);
extern int32 date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2);
extern int32 date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2);
--
2.47.1
On Wed, Nov 26, 2025 at 03:09:25PM +0530, Amul Sul wrote:
This continues the previous refactoring commit [1] where we adopted
soft error reporting for some numeric functions. This patch applies
the same pattern to the date/timestamp function. The change ensures
consistency by utilizing the existing soft error reporting
infrastructure.
Thanks for continuing this work.
Note that in the patch, I renamed the function by replacing the
"no_overflow" extension in the function name with "overflow_safe".
Alternatively, we could just use "safe" alone. Suggestions are
welcome.
Hmm. Following the previous example you have quoted, I am wondering
if we'd tweak the names a bit differently. Rather than the
popo_overflow_safe() pattern from your patch, I would choose a simpler
popo_safe() as naming convention. That would be also more consistent
with the names applied to the refactored routines of 4246a977bad6.
- result = date2timestamp_opt_overflow(val, &overflow);
+ result = date2timestamp_overflow_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
In these cases, why don't you just pass NULL to the routines for the
error context? (Sorry, I don't have my eyes on the code now, but I
recall that NULL should work as well, meaning the same as "ignore
me".)
--
Michael
Hi,
On Wed, Nov 26, 2025 at 8:43 PM Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Nov 26, 2025 at 03:09:25PM +0530, Amul Sul wrote:
This continues the previous refactoring commit [1] where we adopted
soft error reporting for some numeric functions. This patch applies
the same pattern to the date/timestamp function. The change ensures
consistency by utilizing the existing soft error reporting
infrastructure.Thanks for continuing this work.
+1
I see that Michael has now noticed this, I was looking at this earlier
today and thought of a couple of nitpicky things to share:
* The rename from *_opt_overflow to *_overflow_safe could be made a
separate patch (say 0002), so it can be discussed separately. For
example, whether to keep the old *_opt_overflow variants for backward
compatibility since they’re exported and possibly used by extensions.
* Maybe it's just me, but several function comments (for example
around date2timestamptz_overflow_safe()) lost detailed explanations of
overflow behavior. It’d be better to preserve those specifics and only
adjust the wording to describe how errors are reported via escontext:
/*
- * Promote date to timestamp with time zone.
- *
- * On successful conversion, *overflow is set to zero if it's not NULL.
- *
- * If the date is finite but out of the valid range for timestamptz, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamptz infinity.
+ * Promotes date to timestamp with time zone, including soft error reporting
+ * capabilities.
/*
- * Convert timestamp to date.
- *
- * On successful conversion, *overflow is set to zero if it's not NULL.
- *
- * If the timestamp is finite but out of the valid range for date, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate date infinity.
+ * Convert timestamp to date, including soft error reporting capabilities.
/*
- * Convert timestamptz to date.
- *
- * On successful conversion, *overflow is set to zero if it's not NULL.
- *
- * If the timestamptz is finite but out of the valid range for date, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate date infinity.
+ * Convert timestamptz to date, including soft error reporting capabilities.
--
Thanks, Amit Langote
On Wed, Nov 26, 2025 at 5:13 PM Michael Paquier <michael@paquier.xyz> wrote:
Hmm. Following the previous example you have quoted, I am wondering
if we'd tweak the names a bit differently. Rather than the
popo_overflow_safe() pattern from your patch, I would choose a simpler
popo_safe() as naming convention. That would be also more consistent
with the names applied to the refactored routines of 4246a977bad6.
The reason for this naming was to maintain consistency with the
function date2timestamp_no_overflow() in date.h. I am now uncertain
whether we should rename date2timestamp_no_overflow() as well to align
with the current change. I also lean towards popo_safe() as a naming
convention.
- result = date2timestamp_opt_overflow(val, &overflow); + result = date2timestamp_overflow_safe(val, (Node *) &escontext); /* We can ignore the overflow result, since result is useful as-is */In these cases, why don't you just pass NULL to the routines for the
error context? (Sorry, I don't have my eyes on the code now, but I
recall that NULL should work as well, meaning the same as "ignore
me".)
Won't that result in an error that we are trying to avoid?
Regards,
Amul
On Wed, Nov 26, 2025 at 5:41 PM Amit Langote <amitlangote09@gmail.com> wrote:
Hi,
On Wed, Nov 26, 2025 at 8:43 PM Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Nov 26, 2025 at 03:09:25PM +0530, Amul Sul wrote:
This continues the previous refactoring commit [1] where we adopted
soft error reporting for some numeric functions. This patch applies
the same pattern to the date/timestamp function. The change ensures
consistency by utilizing the existing soft error reporting
infrastructure.Thanks for continuing this work.
+1
Thank you both for taking a look at the patch.
I see that Michael has now noticed this, I was looking at this earlier
today and thought of a couple of nitpicky things to share:* The rename from *_opt_overflow to *_overflow_safe could be made a
separate patch (say 0002), so it can be discussed separately. For
example, whether to keep the old *_opt_overflow variants for backward
compatibility since they’re exported and possibly used by extensions.
I am probably okay with this, but it is up to the committer. In the
previous commit, however, we performed a rename within the same
commit. IIUC, the extensions are expected to be updated with respect
to the major release
* Maybe it's just me, but several function comments (for example
around date2timestamptz_overflow_safe()) lost detailed explanations of
overflow behavior. It’d be better to preserve those specifics and only
adjust the wording to describe how errors are reported via escontext:
The previous comments are no longer relevant now that we are utilizing
the established error-safe infrastructure, but, I would think more on
this later since I am out of time today.
Regards,
Amul
On Wed, Nov 26, 2025 at 06:40:38PM +0530, Amul Sul wrote:
On Wed, Nov 26, 2025 at 5:41 PM Amit Langote <amitlangote09@gmail.com> wrote:
* The rename from *_opt_overflow to *_overflow_safe could be made a
separate patch (say 0002), so it can be discussed separately. For
example, whether to keep the old *_opt_overflow variants for backward
compatibility since they’re exported and possibly used by extensions.I am probably okay with this, but it is up to the committer. In the
previous commit, however, we performed a rename within the same
commit. IIUC, the extensions are expected to be updated with respect
to the major release
I am not sure to see a point in doing a rename of the routines
separately. We are changing one of the argument type of the
functions, replacing the "overflow" integer with an error context
Node. If we were to do a rename in one patch, then a redesign of the
arguments, this leads to more code churn at the same end as the same
code paths would get rewritten twice instead of once. This move could
have made more sense if the existing popo_opt_overflow() used NULL for
the overflow value like in btree_gin.c in the past, but that makes the
change less appealing with the soft reporting APIs available in the
core backend.
* Maybe it's just me, but several function comments (for example
around date2timestamptz_overflow_safe()) lost detailed explanations of
overflow behavior. It’d be better to preserve those specifics and only
adjust the wording to describe how errors are reported via escontext:The previous comments are no longer relevant now that we are utilizing
the established error-safe infrastructure, but, I would think more on
this later since I am out of time today.
It seems to me that it is important to keep documented the overflow
check in some way rather than removing it as the patch is doing,
particularly regarding the finite vs infinite value behaviors. We do
not need anymore the documentation about how "overflow" is set in this
routines, of course, but keeping these expectations documented would
be better.
--
Michael
On Thu, Nov 27, 2025 at 7:59 AM Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Nov 26, 2025 at 06:40:38PM +0530, Amul Sul wrote:
On Wed, Nov 26, 2025 at 5:41 PM Amit Langote <amitlangote09@gmail.com> wrote:
* The rename from *_opt_overflow to *_overflow_safe could be made a
separate patch (say 0002), so it can be discussed separately. For
example, whether to keep the old *_opt_overflow variants for backward
compatibility since they’re exported and possibly used by extensions.I am probably okay with this, but it is up to the committer. In the
previous commit, however, we performed a rename within the same
commit. IIUC, the extensions are expected to be updated with respect
to the major releaseI am not sure to see a point in doing a rename of the routines
separately. We are changing one of the argument type of the
functions, replacing the "overflow" integer with an error context
Node. If we were to do a rename in one patch, then a redesign of the
arguments, this leads to more code churn at the same end as the same
code paths would get rewritten twice instead of once.
Ok, let's drop the patch breakdown part of my comment then.
This move could
have made more sense if the existing popo_opt_overflow() used NULL for
the overflow value like in btree_gin.c in the past, but that makes the
change less appealing with the soft reporting APIs available in the
core backend.
I’m fine with updating all core callers to use the new *_safe(... Node
*escontext) APIs all in one patch. However, we could consider keeping
the existing *_opt_overflow() functions as thin wrappers over the new
ones, to avoid breaking third-party extensions immediately for what is
primarily a refactoring change.
* Maybe it's just me, but several function comments (for example
around date2timestamptz_overflow_safe()) lost detailed explanations of
overflow behavior. It’d be better to preserve those specifics and only
adjust the wording to describe how errors are reported via escontext:The previous comments are no longer relevant now that we are utilizing
the established error-safe infrastructure, but, I would think more on
this later since I am out of time today.It seems to me that it is important to keep documented the overflow
check in some way rather than removing it as the patch is doing,
particularly regarding the finite vs infinite value behaviors. We do
not need anymore the documentation about how "overflow" is set in this
routines, of course, but keeping these expectations documented would
be better.
Yeah, I meant we should expand "including soft error reporting
capabilities" somehow, something like this:
- * On successful conversion, *overflow is set to zero if it's not NULL.
- *
- * If the date is finite but out of the valid range for timestamp, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamp infinity.
+ * If the date is finite but out of the valid range for timestamp, an
+ * out-of-range error is reported. When escontext is NULL this is a
+ * normal ERROR; when escontext points to an ErrorSaveContext, the error
+ * is reported softly and TIMESTAMP_NOEND is returned.
--
Thanks, Amit Langote
On Thu, Nov 27, 2025 at 10:18:38AM +0900, Amit Langote wrote:
I’m fine with updating all core callers to use the new *_safe(... Node
*escontext) APIs all in one patch. However, we could consider keeping
the existing *_opt_overflow() functions as thin wrappers over the new
ones, to avoid breaking third-party extensions immediately for what is
primarily a refactoring change.
Like 4246a977bad6, one point is to be able to make the internals of
these functions leaner, by relying on the fact that the econtext node
includes an error to let the callers of the functions decide what to
do. If we keep some function wrappers as the ones we have now, this
partially defeats the purpose of the change, which is to simplify
in-core code at the end. This move also encourages users to switch to
the new facility to be fed error reports generated by the in-core
routines, meaning less duplication with error strings outside of the
core code, hence less maintenance for out-of-core code.
--
Michael
On Thu, Nov 27, 2025 at 10:28 AM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Nov 27, 2025 at 10:18:38AM +0900, Amit Langote wrote:
I’m fine with updating all core callers to use the new *_safe(... Node
*escontext) APIs all in one patch. However, we could consider keeping
the existing *_opt_overflow() functions as thin wrappers over the new
ones, to avoid breaking third-party extensions immediately for what is
primarily a refactoring change.Like 4246a977bad6, one point is to be able to make the internals of
these functions leaner, by relying on the fact that the econtext node
includes an error to let the callers of the functions decide what to
do. If we keep some function wrappers as the ones we have now, this
partially defeats the purpose of the change, which is to simplify
in-core code at the end. This move also encourages users to switch to
the new facility to be fed error reports generated by the in-core
routines, meaning less duplication with error strings outside of the
core code, hence less maintenance for out-of-core code.
Ok, I see, the idea is to encourage out-of-core code to move to the
new ErrorSaveContext-based handling, rather than keep wrappers around
just to ease the transition. IOW, we shouldn’t carry redundant code in
core once the new, simpler APIs exist. Sounds ok to me. Some
extension authors might grumble when their code stops compiling with
v19 though,only to find that the change was mostly stylistic ;-).
--
Thanks, Amit Langote
On Thu, Nov 27, 2025 at 6:48 AM Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Nov 27, 2025 at 7:59 AM Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Nov 26, 2025 at 06:40:38PM +0530, Amul Sul wrote:
On Wed, Nov 26, 2025 at 5:41 PM Amit Langote <amitlangote09@gmail.com> wrote:
[....]
It seems to me that it is important to keep documented the overflow
check in some way rather than removing it as the patch is doing,
particularly regarding the finite vs infinite value behaviors. We do
not need anymore the documentation about how "overflow" is set in this
routines, of course, but keeping these expectations documented would
be better.Yeah, I meant we should expand "including soft error reporting
capabilities" somehow, something like this:- * On successful conversion, *overflow is set to zero if it's not NULL. - * - * If the date is finite but out of the valid range for timestamp, then: - * if overflow is NULL, we throw an out-of-range error. - * if overflow is not NULL, we store +1 or -1 there to indicate the sign - * of the overflow, and return the appropriate timestamp infinity. + * If the date is finite but out of the valid range for timestamp, an + * out-of-range error is reported. When escontext is NULL this is a + * normal ERROR; when escontext points to an ErrorSaveContext, the error + * is reported softly and TIMESTAMP_NOEND is returned.
Okay, I have attached an updated version -- added the necessary
comments and renamed the function, replacing "opt_overflow" with the
"_safe".
One question: Regarding date2timestamp_no_overflow(), should we rename
it to date2double? We can't use date2timestamp_safe because we already
have that function. The renaming is relevant because this function
converts a date to the double data type, which allows us to remove the
"_no_overflow" extension.
Regards,
Amul
Attachments:
v2-0001-Switch-some-date-related-functions-to-use-soft-er.patchapplication/octet-stream; name=v2-0001-Switch-some-date-related-functions-to-use-soft-er.patchDownload
From bbbf42614fa7aa77b274719cda078ffff78c141c Mon Sep 17 00:00:00 2001
From: Amul Sul <sulamul@gmail.com>
Date: Thu, 27 Nov 2025 13:09:01 +0530
Subject: [PATCH v2] Switch some date-related functions to use soft error
reporting.
Like 4246a977bad6e76, changing some functions related to the data type
date/timestamp to use the soft error reporting rather than a custom
boolean flag (called "overflow") that callers of these functions could
rely on to bypass the generation of ERROR reports, letting the callers
do their own error handling
---
contrib/btree_gin/btree_gin.c | 17 +--
src/backend/utils/adt/date.c | 219 +++++++++++++---------------------
src/include/utils/date.h | 8 +-
3 files changed, 97 insertions(+), 147 deletions(-)
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index 8c477d17e22..c922211b150 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -7,6 +7,7 @@
#include "access/stratnum.h"
#include "mb/pg_wchar.h"
+#include "nodes/miscnodes.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/float.h"
@@ -496,9 +497,9 @@ cvt_date_timestamp(Datum input)
{
DateADT val = DatumGetDateADT(input);
Timestamp result;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- result = date2timestamp_opt_overflow(val, &overflow);
+ result = date2timestamp_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return TimestampGetDatum(result);
}
@@ -530,10 +531,10 @@ static Datum
cvt_date_timestamptz(Datum input)
{
DateADT val = DatumGetDateADT(input);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
TimestampTz result;
- int overflow;
- result = date2timestamptz_opt_overflow(val, &overflow);
+ result = date2timestamptz_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return TimestampTzGetDatum(result);
}
@@ -604,10 +605,10 @@ static Datum
cvt_timestamp_date(Datum input)
{
Timestamp val = DatumGetTimestamp(input);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
DateADT result;
- int overflow;
- result = timestamp2date_opt_overflow(val, &overflow);
+ result = timestamp2date_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return DateADTGetDatum(result);
}
@@ -616,10 +617,10 @@ static Datum
cvt_timestamptz_date(Datum input)
{
TimestampTz val = DatumGetTimestampTz(input);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
DateADT result;
- int overflow;
- result = timestamptz2date_opt_overflow(val, &overflow);
+ result = timestamptz2date_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return DateADTGetDatum(result);
}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..a9a4ca90ded 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -27,6 +27,7 @@
#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
+#include "nodes/miscnodes.h"
#include "nodes/supportnodes.h"
#include "parser/scansup.h"
#include "utils/array.h"
@@ -611,28 +612,24 @@ date_mii(PG_FUNCTION_ARGS)
PG_RETURN_DATEADT(result);
}
-
/*
- * Promote date to timestamp.
+ * Promotes date to timestamp, including soft error reporting capabilities.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the date falls out of the valid range for the timestamp type, error
+ * handling proceeds based on the error save context:
*
- * If the date is finite but out of the valid range for timestamp, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamp infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*
- * Note: *overflow = -1 is actually not possible currently, since both
- * datatypes have the same lower bound, Julian day zero.
+ * Note: Lower bound overflow is currently not possible, as both date and
+ * timestamp datatypes share the same lower boundary: Julian day zero.
*/
Timestamp
-date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
+date2timestamp_safe(DateADT dateVal, Node *escontext)
{
Timestamp result;
- if (overflow)
- *overflow = 0;
-
if (DATE_IS_NOBEGIN(dateVal))
TIMESTAMP_NOBEGIN(result);
else if (DATE_IS_NOEND(dateVal))
@@ -645,18 +642,10 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
{
- if (overflow)
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- return result;
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
/* date is days since 2000, timestamp is microseconds since same... */
@@ -672,30 +661,28 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
static TimestampTz
date2timestamp(DateADT dateVal)
{
- return date2timestamp_opt_overflow(dateVal, NULL);
+ return date2timestamp_safe(dateVal, NULL);
}
/*
- * Promote date to timestamp with time zone.
+ * Promotes date to timestamp with time zone, including soft error reporting
+ * capabilities.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the date falls out of the valid range for the timestamp type, error
+ * handling proceeds based on the error save context:
*
- * If the date is finite but out of the valid range for timestamptz, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamptz infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*/
TimestampTz
-date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
+date2timestamptz_safe(DateADT dateVal, Node *escontext)
{
TimestampTz result;
struct pg_tm tt,
*tm = &tt;
int tz;
- if (overflow)
- *overflow = 0;
-
if (DATE_IS_NOBEGIN(dateVal))
TIMESTAMP_NOBEGIN(result);
else if (DATE_IS_NOEND(dateVal))
@@ -708,18 +695,10 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
{
- if (overflow)
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- return result;
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
j2date(dateVal + POSTGRES_EPOCH_JDATE,
@@ -737,25 +716,14 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
*/
if (!IS_VALID_TIMESTAMP(result))
{
- if (overflow)
- {
- if (result < MIN_TIMESTAMP)
- {
- *overflow = -1;
- TIMESTAMP_NOBEGIN(result);
- }
- else
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- }
- }
+ if (result < MIN_TIMESTAMP)
+ TIMESTAMP_NOBEGIN(result);
else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
}
@@ -768,7 +736,7 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
static TimestampTz
date2timestamptz(DateADT dateVal)
{
- return date2timestamptz_opt_overflow(dateVal, NULL);
+ return date2timestamptz_safe(dateVal, NULL);
}
/*
@@ -808,15 +776,16 @@ int32
date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2)
{
Timestamp dt1;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- dt1 = date2timestamp_opt_overflow(dateVal, &overflow);
- if (overflow > 0)
+ dt1 = date2timestamp_safe(dateVal, (Node *) &escontext);
+ if (escontext.error_occurred)
{
+ Assert(TIMESTAMP_IS_NOEND(dt1)); /* NOBEGIN case cannot occur */
+
/* dt1 is larger than any finite timestamp, but less than infinity */
return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
}
- Assert(overflow == 0); /* -1 case cannot occur */
return timestamp_cmp_internal(dt1, dt2);
}
@@ -888,18 +857,22 @@ int32
date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2)
{
TimestampTz dt1;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- dt1 = date2timestamptz_opt_overflow(dateVal, &overflow);
- if (overflow > 0)
- {
- /* dt1 is larger than any finite timestamp, but less than infinity */
- return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
- }
- if (overflow < 0)
+ dt1 = date2timestamptz_safe(dateVal, (Node *) &escontext);
+
+ if (escontext.error_occurred)
{
- /* dt1 is less than any finite timestamp, but more than -infinity */
- return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ /* dt1 is larger than any finite timestamp, but less than infinity */
+ return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
+ }
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ /* dt1 is less than any finite timestamp, but more than -infinity */
+ return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ }
}
return timestamptz_cmp_internal(dt1, dt2);
@@ -1364,34 +1337,31 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_opt_overflow(timestamp, NULL);
+ result = timestamp2date_safe(timestamp, NULL);
PG_RETURN_DATEADT(result);
}
/*
- * Convert timestamp to date.
+ * Convert timestamp to date, including soft error reporting capabilities.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the timestamp falls out of the valid range for the date type, error
+ * handling proceeds based on the error save context:
*
- * If the timestamp is finite but out of the valid range for date, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate date infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*
* Note: given the ranges of the types, overflow is only possible at
- * the minimum end of the range, but we don't assume that in this code.
+ * the lower bound of the range, but we don't assume that in this code.
*/
DateADT
-timestamp2date_opt_overflow(Timestamp timestamp, int *overflow)
+timestamp2date_safe(Timestamp timestamp, Node *escontext)
{
DateADT result;
struct pg_tm tt,
*tm = &tt;
fsec_t fsec;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_IS_NOBEGIN(timestamp))
DATE_NOBEGIN(result);
else if (TIMESTAMP_IS_NOEND(timestamp))
@@ -1400,21 +1370,12 @@ timestamp2date_opt_overflow(Timestamp timestamp, int *overflow)
{
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- DATE_NOBEGIN(result);
- }
- else
- {
- *overflow = 1; /* not actually reachable */
- DATE_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ DATE_NOBEGIN(result);
+ else
+ DATE_NOEND(result); /* not actually reachable */
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
@@ -1450,25 +1411,25 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_opt_overflow(timestamp, NULL);
+ result = timestamptz2date_safe(timestamp, NULL);
PG_RETURN_DATEADT(result);
}
/*
- * Convert timestamptz to date.
+ * Convert timestamptz to date, including soft error reporting capabilities.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the timestamp falls out of the valid range for the date type, error
+ * handling proceeds based on the error save context:
*
- * If the timestamptz is finite but out of the valid range for date, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate date infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*
* Note: given the ranges of the types, overflow is only possible at
- * the minimum end of the range, but we don't assume that in this code.
+ * the lower bound of the range, but we don't assume that in this code.
*/
DateADT
-timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
+timestamptz2date_safe(TimestampTz timestamp, Node *escontext)
{
DateADT result;
struct pg_tm tt,
@@ -1476,9 +1437,6 @@ timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
fsec_t fsec;
int tz;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_IS_NOBEGIN(timestamp))
DATE_NOBEGIN(result);
else if (TIMESTAMP_IS_NOEND(timestamp))
@@ -1487,21 +1445,12 @@ timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- DATE_NOBEGIN(result);
- }
- else
- {
- *overflow = 1; /* not actually reachable */
- DATE_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ DATE_NOBEGIN(result);
+ else
+ DATE_NOEND(result); /* not actually reachable */
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index abfda0b1ae9..7316ac0ff17 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -98,10 +98,10 @@ TimeTzADTPGetDatum(const TimeTzADT *X)
/* date.c */
extern int32 anytime_typmod_check(bool istz, int32 typmod);
extern double date2timestamp_no_overflow(DateADT dateVal);
-extern Timestamp date2timestamp_opt_overflow(DateADT dateVal, int *overflow);
-extern TimestampTz date2timestamptz_opt_overflow(DateADT dateVal, int *overflow);
-extern DateADT timestamp2date_opt_overflow(Timestamp timestamp, int *overflow);
-extern DateADT timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow);
+extern Timestamp date2timestamp_safe(DateADT dateVal, Node *escontext);
+extern TimestampTz date2timestamptz_safe(DateADT dateVal, Node *escontext);
+extern DateADT timestamp2date_safe(Timestamp timestamp, Node *escontext);
+extern DateADT timestamptz2date_safe(TimestampTz timestamp, Node *escontext);
extern int32 date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2);
extern int32 date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2);
--
2.47.1
On Thu, Nov 27, 2025 at 4:58 PM Amul Sul <sulamul@gmail.com> wrote:
On Thu, Nov 27, 2025 at 6:48 AM Amit Langote <amitlangote09@gmail.com> wrote:
On Thu, Nov 27, 2025 at 7:59 AM Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Nov 26, 2025 at 06:40:38PM +0530, Amul Sul wrote:
It seems to me that it is important to keep documented the overflow
check in some way rather than removing it as the patch is doing,
particularly regarding the finite vs infinite value behaviors. We do
not need anymore the documentation about how "overflow" is set in this
routines, of course, but keeping these expectations documented would
be better.Yeah, I meant we should expand "including soft error reporting
capabilities" somehow, something like this:- * On successful conversion, *overflow is set to zero if it's not NULL. - * - * If the date is finite but out of the valid range for timestamp, then: - * if overflow is NULL, we throw an out-of-range error. - * if overflow is not NULL, we store +1 or -1 there to indicate the sign - * of the overflow, and return the appropriate timestamp infinity. + * If the date is finite but out of the valid range for timestamp, an + * out-of-range error is reported. When escontext is NULL this is a + * normal ERROR; when escontext points to an ErrorSaveContext, the error + * is reported softly and TIMESTAMP_NOEND is returned.Okay, I have attached an updated version -- added the necessary
comments and renamed the function, replacing "opt_overflow" with the
"_safe".
Thanks.
LGTM beside minor nits:
+ * Promotes date to timestamp, including soft error reporting capabilities.
The part after the comma looks unnecessary IMO. It's clear from the
rest of the description that the function has soft error reporting
capabilities.
+ * handling proceeds based on the error save context:
I don't see "error save context" anywhere else in the code. Why not
just use 'escontext' instead or just say "proceeds as follows:"?
One question: Regarding date2timestamp_no_overflow(), should we rename
it to date2double? We can't use date2timestamp_safe because we already
have that function. The renaming is relevant because this function
converts a date to the double data type, which allows us to remove the
"_no_overflow" extension.
Makes sense to me.
--
Thanks, Amit Langote
On Thu, Nov 27, 2025 at 06:18:32PM +0900, Amit Langote wrote:
On Thu, Nov 27, 2025 at 4:58 PM Amul Sul <sulamul@gmail.com> wrote:
One question: Regarding date2timestamp_no_overflow(), should we rename
it to date2double? We can't use date2timestamp_safe because we already
have that function. The renaming is relevant because this function
converts a date to the double data type, which allows us to remove the
"_no_overflow" extension.Makes sense to me.
Yes, it would be nice to change all this area at once to remain
consistent across the board. date2timestamp_no_overflow() is the last
"no_overflow" routine in date.h.
--
Michael
On Fri, Nov 28, 2025 at 6:16 AM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Nov 27, 2025 at 06:18:32PM +0900, Amit Langote wrote:
On Thu, Nov 27, 2025 at 4:58 PM Amul Sul <sulamul@gmail.com> wrote:
One question: Regarding date2timestamp_no_overflow(), should we rename
it to date2double? We can't use date2timestamp_safe because we already
have that function. The renaming is relevant because this function
converts a date to the double data type, which allows us to remove the
"_no_overflow" extension.Makes sense to me.
Yes, it would be nice to change all this area at once to remain
consistent across the board. date2timestamp_no_overflow() is the last
"no_overflow" routine in date.h.
I have attached patch 0002 that renames it. I also updated patch 0001
to accommodate Amit's comment suggestions.
Regards,
Amul
Attachments:
v2-0001-Switch-some-date-related-functions-to-use-soft-er.patchapplication/octet-stream; name=v2-0001-Switch-some-date-related-functions-to-use-soft-er.patchDownload
From 784e0997935d98a64229edd21fce91345e543726 Mon Sep 17 00:00:00 2001
From: Amul Sul <sulamul@gmail.com>
Date: Fri, 28 Nov 2025 09:18:54 +0530
Subject: [PATCH v2 1/2] Switch some date-related functions to use soft error
reporting.
Like 4246a977bad6e76, changing some functions related to the data type
date/timestamp to use the soft error reporting rather than a custom
boolean flag (called "overflow") that callers of these functions could
rely on to bypass the generation of ERROR reports, letting the callers
do their own error handling
---
contrib/btree_gin/btree_gin.c | 17 +--
src/backend/utils/adt/date.c | 209 +++++++++++++---------------------
src/include/utils/date.h | 8 +-
3 files changed, 92 insertions(+), 142 deletions(-)
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index 8c477d17e22..c922211b150 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -7,6 +7,7 @@
#include "access/stratnum.h"
#include "mb/pg_wchar.h"
+#include "nodes/miscnodes.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/float.h"
@@ -496,9 +497,9 @@ cvt_date_timestamp(Datum input)
{
DateADT val = DatumGetDateADT(input);
Timestamp result;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- result = date2timestamp_opt_overflow(val, &overflow);
+ result = date2timestamp_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return TimestampGetDatum(result);
}
@@ -530,10 +531,10 @@ static Datum
cvt_date_timestamptz(Datum input)
{
DateADT val = DatumGetDateADT(input);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
TimestampTz result;
- int overflow;
- result = date2timestamptz_opt_overflow(val, &overflow);
+ result = date2timestamptz_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return TimestampTzGetDatum(result);
}
@@ -604,10 +605,10 @@ static Datum
cvt_timestamp_date(Datum input)
{
Timestamp val = DatumGetTimestamp(input);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
DateADT result;
- int overflow;
- result = timestamp2date_opt_overflow(val, &overflow);
+ result = timestamp2date_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return DateADTGetDatum(result);
}
@@ -616,10 +617,10 @@ static Datum
cvt_timestamptz_date(Datum input)
{
TimestampTz val = DatumGetTimestampTz(input);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
DateADT result;
- int overflow;
- result = timestamptz2date_opt_overflow(val, &overflow);
+ result = timestamptz2date_safe(val, (Node *) &escontext);
/* We can ignore the overflow result, since result is useful as-is */
return DateADTGetDatum(result);
}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 344f58b92f7..f5da1a24439 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -27,6 +27,7 @@
#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
+#include "nodes/miscnodes.h"
#include "nodes/supportnodes.h"
#include "parser/scansup.h"
#include "utils/array.h"
@@ -615,24 +616,21 @@ date_mii(PG_FUNCTION_ARGS)
/*
* Promote date to timestamp.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the date falls out of the valid range for the timestamp type, error
+ * handling proceeds based on the escontext:
*
- * If the date is finite but out of the valid range for timestamp, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamp infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*
- * Note: *overflow = -1 is actually not possible currently, since both
- * datatypes have the same lower bound, Julian day zero.
+ * Note: Lower bound overflow is currently not possible, as both date and
+ * timestamp datatypes share the same lower boundary: Julian day zero.
*/
Timestamp
-date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
+date2timestamp_safe(DateADT dateVal, Node *escontext)
{
Timestamp result;
- if (overflow)
- *overflow = 0;
-
if (DATE_IS_NOBEGIN(dateVal))
TIMESTAMP_NOBEGIN(result);
else if (DATE_IS_NOEND(dateVal))
@@ -645,18 +643,10 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
{
- if (overflow)
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- return result;
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
/* date is days since 2000, timestamp is microseconds since same... */
@@ -672,30 +662,27 @@ date2timestamp_opt_overflow(DateADT dateVal, int *overflow)
static TimestampTz
date2timestamp(DateADT dateVal)
{
- return date2timestamp_opt_overflow(dateVal, NULL);
+ return date2timestamp_safe(dateVal, NULL);
}
/*
* Promote date to timestamp with time zone.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the date falls out of the valid range for the timestamp type, error
+ * handling proceeds based on the escontext:
*
- * If the date is finite but out of the valid range for timestamptz, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamptz infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*/
TimestampTz
-date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
+date2timestamptz_safe(DateADT dateVal, Node *escontext)
{
TimestampTz result;
struct pg_tm tt,
*tm = &tt;
int tz;
- if (overflow)
- *overflow = 0;
-
if (DATE_IS_NOBEGIN(dateVal))
TIMESTAMP_NOBEGIN(result);
else if (DATE_IS_NOEND(dateVal))
@@ -708,18 +695,10 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
{
- if (overflow)
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- return result;
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
j2date(dateVal + POSTGRES_EPOCH_JDATE,
@@ -737,25 +716,14 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
*/
if (!IS_VALID_TIMESTAMP(result))
{
- if (overflow)
- {
- if (result < MIN_TIMESTAMP)
- {
- *overflow = -1;
- TIMESTAMP_NOBEGIN(result);
- }
- else
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- }
- }
+ if (result < MIN_TIMESTAMP)
+ TIMESTAMP_NOBEGIN(result);
else
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
- }
+ TIMESTAMP_NOEND(result);
+
+ ereturn(escontext, result,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range for timestamp")));
}
}
@@ -768,7 +736,7 @@ date2timestamptz_opt_overflow(DateADT dateVal, int *overflow)
static TimestampTz
date2timestamptz(DateADT dateVal)
{
- return date2timestamptz_opt_overflow(dateVal, NULL);
+ return date2timestamptz_safe(dateVal, NULL);
}
/*
@@ -808,15 +776,16 @@ int32
date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2)
{
Timestamp dt1;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- dt1 = date2timestamp_opt_overflow(dateVal, &overflow);
- if (overflow > 0)
+ dt1 = date2timestamp_safe(dateVal, (Node *) &escontext);
+ if (escontext.error_occurred)
{
+ Assert(TIMESTAMP_IS_NOEND(dt1)); /* NOBEGIN case cannot occur */
+
/* dt1 is larger than any finite timestamp, but less than infinity */
return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
}
- Assert(overflow == 0); /* -1 case cannot occur */
return timestamp_cmp_internal(dt1, dt2);
}
@@ -888,18 +857,22 @@ int32
date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2)
{
TimestampTz dt1;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- dt1 = date2timestamptz_opt_overflow(dateVal, &overflow);
- if (overflow > 0)
- {
- /* dt1 is larger than any finite timestamp, but less than infinity */
- return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
- }
- if (overflow < 0)
+ dt1 = date2timestamptz_safe(dateVal, (Node *) &escontext);
+
+ if (escontext.error_occurred)
{
- /* dt1 is less than any finite timestamp, but more than -infinity */
- return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ /* dt1 is larger than any finite timestamp, but less than infinity */
+ return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
+ }
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ /* dt1 is less than any finite timestamp, but more than -infinity */
+ return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ }
}
return timestamptz_cmp_internal(dt1, dt2);
@@ -1364,34 +1337,31 @@ timestamp_date(PG_FUNCTION_ARGS)
Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamp2date_opt_overflow(timestamp, NULL);
+ result = timestamp2date_safe(timestamp, NULL);
PG_RETURN_DATEADT(result);
}
/*
* Convert timestamp to date.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the timestamp falls out of the valid range for the date type, error
+ * handling proceeds based on the escontext:
*
- * If the timestamp is finite but out of the valid range for date, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate date infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*
* Note: given the ranges of the types, overflow is only possible at
- * the minimum end of the range, but we don't assume that in this code.
+ * the lower bound of the range, but we don't assume that in this code.
*/
DateADT
-timestamp2date_opt_overflow(Timestamp timestamp, int *overflow)
+timestamp2date_safe(Timestamp timestamp, Node *escontext)
{
DateADT result;
struct pg_tm tt,
*tm = &tt;
fsec_t fsec;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_IS_NOBEGIN(timestamp))
DATE_NOBEGIN(result);
else if (TIMESTAMP_IS_NOEND(timestamp))
@@ -1400,21 +1370,12 @@ timestamp2date_opt_overflow(Timestamp timestamp, int *overflow)
{
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- DATE_NOBEGIN(result);
- }
- else
- {
- *overflow = 1; /* not actually reachable */
- DATE_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ DATE_NOBEGIN(result);
+ else
+ DATE_NOEND(result); /* not actually reachable */
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
@@ -1450,25 +1411,25 @@ timestamptz_date(PG_FUNCTION_ARGS)
TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
DateADT result;
- result = timestamptz2date_opt_overflow(timestamp, NULL);
+ result = timestamptz2date_safe(timestamp, NULL);
PG_RETURN_DATEADT(result);
}
/*
* Convert timestamptz to date.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the timestamp falls out of the valid range for the date type, error
+ * handling proceeds based on the escontext:
*
- * If the timestamptz is finite but out of the valid range for date, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate date infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*
* Note: given the ranges of the types, overflow is only possible at
- * the minimum end of the range, but we don't assume that in this code.
+ * the lower bound of the range, but we don't assume that in this code.
*/
DateADT
-timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
+timestamptz2date_safe(TimestampTz timestamp, Node *escontext)
{
DateADT result;
struct pg_tm tt,
@@ -1476,9 +1437,6 @@ timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
fsec_t fsec;
int tz;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_IS_NOBEGIN(timestamp))
DATE_NOBEGIN(result);
else if (TIMESTAMP_IS_NOEND(timestamp))
@@ -1487,21 +1445,12 @@ timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow)
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- DATE_NOBEGIN(result);
- }
- else
- {
- *overflow = 1; /* not actually reachable */
- DATE_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ DATE_NOBEGIN(result);
+ else
+ DATE_NOEND(result); /* not actually reachable */
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index abfda0b1ae9..7316ac0ff17 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -98,10 +98,10 @@ TimeTzADTPGetDatum(const TimeTzADT *X)
/* date.c */
extern int32 anytime_typmod_check(bool istz, int32 typmod);
extern double date2timestamp_no_overflow(DateADT dateVal);
-extern Timestamp date2timestamp_opt_overflow(DateADT dateVal, int *overflow);
-extern TimestampTz date2timestamptz_opt_overflow(DateADT dateVal, int *overflow);
-extern DateADT timestamp2date_opt_overflow(Timestamp timestamp, int *overflow);
-extern DateADT timestamptz2date_opt_overflow(TimestampTz timestamp, int *overflow);
+extern Timestamp date2timestamp_safe(DateADT dateVal, Node *escontext);
+extern TimestampTz date2timestamptz_safe(DateADT dateVal, Node *escontext);
+extern DateADT timestamp2date_safe(Timestamp timestamp, Node *escontext);
+extern DateADT timestamptz2date_safe(TimestampTz timestamp, Node *escontext);
extern int32 date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2);
extern int32 date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2);
--
2.47.1
v2-0002-Rename-date2timestamp_no_overflow-to-date2double.patchapplication/octet-stream; name=v2-0002-Rename-date2timestamp_no_overflow-to-date2double.patchDownload
From 184f60a3f3aba949cb530bf365c9aa086d08aaa4 Mon Sep 17 00:00:00 2001
From: Amul Sul <sulamul@gmail.com>
Date: Fri, 28 Nov 2025 09:22:52 +0530
Subject: [PATCH v2 2/2] Rename date2timestamp_no_overflow to date2double.
In the previous commit, we renamed all date/timestamp-related
functions that ended with the opt_overflow keyword. This function is
the only version containing the overflow keyword that still needs to
be renamed for consistency. Furthermore, we cannot simply remove the
no_overflow suffix because the date2timestamp() function already
exists. Therefore, it is better to rename it to date2double(), which
would be more appropriate since the remaining date/timestamp
conversion functions include both the source and target type names.
---
src/backend/utils/adt/date.c | 4 ++--
src/backend/utils/adt/selfuncs.c | 2 +-
src/include/utils/date.h | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index f5da1a24439..5b68cd4374c 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -740,7 +740,7 @@ date2timestamptz(DateADT dateVal)
}
/*
- * date2timestamp_no_overflow
+ * date2double
*
* This is chartered to produce a double value that is numerically
* equivalent to the corresponding Timestamp value, if the date is in the
@@ -750,7 +750,7 @@ date2timestamptz(DateADT dateVal)
* used for statistical estimation purposes.
*/
double
-date2timestamp_no_overflow(DateADT dateVal)
+date2double(DateADT dateVal)
{
double result;
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 540aa9628d7..8277072ab7b 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5432,7 +5432,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case TIMESTAMPTZOID:
return DatumGetTimestampTz(value);
case DATEOID:
- return date2timestamp_no_overflow(DatumGetDateADT(value));
+ return date2double(DatumGetDateADT(value));
case INTERVALOID:
{
Interval *interval = DatumGetIntervalP(value);
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 7316ac0ff17..ef6d1b47390 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -97,7 +97,7 @@ TimeTzADTPGetDatum(const TimeTzADT *X)
/* date.c */
extern int32 anytime_typmod_check(bool istz, int32 typmod);
-extern double date2timestamp_no_overflow(DateADT dateVal);
+extern double date2double(DateADT dateVal);
extern Timestamp date2timestamp_safe(DateADT dateVal, Node *escontext);
extern TimestampTz date2timestamptz_safe(DateADT dateVal, Node *escontext);
extern DateADT timestamp2date_safe(Timestamp timestamp, Node *escontext);
--
2.47.1
On Fri, Nov 28, 2025 at 09:46:43AM +0530, Amul Sul wrote:
I have attached patch 0002 that renames it. I also updated patch 0001
to accommodate Amit's comment suggestions.
Thanks, applied this one after more tweaks. Regarding 0002, just
doing a renaming makes me a bit uncomfortable after a second look.
Another way to look at the problem while being consistent would be to
convert date2timestamp_no_overflow() to use soft error reports,
requiring its caller in selfuncs.c to use an error context node. I
cannot get really excited at the end just for the sake of the planner
stats.
There were two more functions that btree_gin.c is pointing at that
could to the switch: timestamp->timestamptz and its opposite. This
also shaves some code, which is nice. Please see the attached.
--
Michael
Attachments:
0001-Update-timestamp-tz-functions-to-use-soft-error-repo.patchtext/x-diff; charset=us-asciiDownload
From b662223bac8a50a426f1d42c2f3226362b682675 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 1 Dec 2025 16:00:04 +0900
Subject: [PATCH] Update timestamp[tz] functions to use soft-error reporting
---
src/include/utils/timestamp.h | 8 +-
src/backend/utils/adt/timestamp.c | 121 +++++++++++-------------------
contrib/btree_gin/btree_gin.c | 12 +--
3 files changed, 54 insertions(+), 87 deletions(-)
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 93531732b085..f1a85c7d9eba 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -142,10 +142,10 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
-extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp,
- int *overflow);
-extern Timestamp timestamptz2timestamp_opt_overflow(TimestampTz timestamp,
- int *overflow);
+extern TimestampTz timestamp2timestamptz_safe(Timestamp timestamp,
+ Node *escontext);
+extern Timestamp timestamptz2timestamp_safe(TimestampTz timestamp,
+ Node *escontext);
extern int32 timestamp_cmp_timestamptz_internal(Timestamp timestampVal,
TimestampTz dt2);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 156a4830ffda..af48527d436f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2363,18 +2363,21 @@ int32
timestamp_cmp_timestamptz_internal(Timestamp timestampVal, TimestampTz dt2)
{
TimestampTz dt1;
- int overflow;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
- dt1 = timestamp2timestamptz_opt_overflow(timestampVal, &overflow);
- if (overflow > 0)
+ dt1 = timestamp2timestamptz_safe(timestampVal, (Node *) &escontext);
+ if (escontext.error_occurred)
{
- /* dt1 is larger than any finite timestamp, but less than infinity */
- return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
- }
- if (overflow < 0)
- {
- /* dt1 is less than any finite timestamp, but more than -infinity */
- return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ /* dt1 is larger than any finite timestamp, but less than infinity */
+ return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1;
+ }
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ /* dt1 is less than any finite timestamp, but more than -infinity */
+ return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1;
+ }
}
return timestamptz_cmp_internal(dt1, dt2);
@@ -6434,15 +6437,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
/*
* Convert timestamp to timestamp with time zone.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the timestamp is finite but out of the valid range for timestamptz,
+ * error handling proceeds based on escontext.
*
- * If the timestamp is finite but out of the valid range for timestamptz, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamptz infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*/
TimestampTz
-timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
+timestamp2timestamptz_safe(Timestamp timestamp, Node *escontext)
{
TimestampTz result;
struct pg_tm tt,
@@ -6450,9 +6453,6 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
fsec_t fsec;
int tz;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_NOT_FINITE(timestamp))
return timestamp;
@@ -6467,26 +6467,14 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
return result;
}
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- TIMESTAMP_NOBEGIN(result);
- }
- else
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- }
- return result;
- }
+ if (timestamp < 0)
+ TIMESTAMP_NOBEGIN(result);
+ else
+ TIMESTAMP_NOEND(result);
- ereport(ERROR,
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
-
- return 0;
}
/*
@@ -6495,7 +6483,7 @@ timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow)
static TimestampTz
timestamp2timestamptz(Timestamp timestamp)
{
- return timestamp2timestamptz_opt_overflow(timestamp, NULL);
+ return timestamp2timestamptz_safe(timestamp, NULL);
}
/* timestamptz_timestamp()
@@ -6515,21 +6503,21 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
static Timestamp
timestamptz2timestamp(TimestampTz timestamp)
{
- return timestamptz2timestamp_opt_overflow(timestamp, NULL);
+ return timestamptz2timestamp_safe(timestamp, NULL);
}
/*
* Convert timestamp with time zone to timestamp.
*
- * On successful conversion, *overflow is set to zero if it's not NULL.
+ * If the timestamptz is finite but out of the valid range for timestamp,
+ * error handling proceeds based on escontext.
*
- * If the timestamptz is finite but out of the valid range for timestamp, then:
- * if overflow is NULL, we throw an out-of-range error.
- * if overflow is not NULL, we store +1 or -1 there to indicate the sign
- * of the overflow, and return the appropriate timestamp infinity.
+ * If escontext is NULL, we throw an out-of-range error (hard error).
+ * If escontext is not NULL, we return NOBEGIN or NOEND for lower bound or
+ * upper bound overflow, respectively, and record a soft error.
*/
Timestamp
-timestamptz2timestamp_opt_overflow(TimestampTz timestamp, int *overflow)
+timestamptz2timestamp_safe(TimestampTz timestamp, Node *escontext)
{
Timestamp result;
struct pg_tm tt,
@@ -6537,50 +6525,29 @@ timestamptz2timestamp_opt_overflow(TimestampTz timestamp, int *overflow)
fsec_t fsec;
int tz;
- if (overflow)
- *overflow = 0;
-
if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- TIMESTAMP_NOBEGIN(result);
- }
- else
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ TIMESTAMP_NOBEGIN(result);
+ else
+ TIMESTAMP_NOEND(result);
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
if (tm2timestamp(tm, fsec, NULL, &result) != 0)
{
- if (overflow)
- {
- if (timestamp < 0)
- {
- *overflow = -1;
- TIMESTAMP_NOBEGIN(result);
- }
- else
- {
- *overflow = 1;
- TIMESTAMP_NOEND(result);
- }
- return result;
- }
- ereport(ERROR,
+ if (timestamp < 0)
+ TIMESTAMP_NOBEGIN(result);
+ else
+ TIMESTAMP_NOEND(result);
+
+ ereturn(escontext, result,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index 1a2339a70c81..966b76e591b7 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -508,11 +508,11 @@ static Datum
cvt_timestamptz_timestamp(Datum input)
{
TimestampTz val = DatumGetTimestampTz(input);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
Timestamp result;
- int overflow;
- result = timestamptz2timestamp_opt_overflow(val, &overflow);
- /* We can ignore the overflow result, since result is useful as-is */
+ result = timestamptz2timestamp_safe(val, (Node *) &escontext);
+ /* We can ignore errors, since result is useful as-is */
return TimestampGetDatum(result);
}
@@ -543,11 +543,11 @@ static Datum
cvt_timestamp_timestamptz(Datum input)
{
Timestamp val = DatumGetTimestamp(input);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
TimestampTz result;
- int overflow;
- result = timestamp2timestamptz_opt_overflow(val, &overflow);
- /* We can ignore the overflow result, since result is useful as-is */
+ result = timestamp2timestamptz_safe(val, (Node *) &escontext);
+ /* We can ignore errors, since result is useful as-is */
return TimestampTzGetDatum(result);
}
--
2.51.0
On Mon, Dec 1, 2025 at 12:32 PM Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Nov 28, 2025 at 09:46:43AM +0530, Amul Sul wrote:
I have attached patch 0002 that renames it. I also updated patch 0001
to accommodate Amit's comment suggestions.Thanks, applied this one after more tweaks. Regarding 0002, just
doing a renaming makes me a bit uncomfortable after a second look.
Another way to look at the problem while being consistent would be to
convert date2timestamp_no_overflow() to use soft error reports,
requiring its caller in selfuncs.c to use an error context node. I
cannot get really excited at the end just for the sake of the planner
stats.
Understood. Thanks for committing the patch.
There were two more functions that btree_gin.c is pointing at that
could to the switch: timestamp->timestamptz and its opposite. This
also shaves some code, which is nice. Please see the attached.
Yes, the patch looks good to me.
Regards,
Amul
On Mon, Dec 01, 2025 at 05:31:43PM +0530, Amul Sul wrote:
On Mon, Dec 1, 2025 at 12:32 PM Michael Paquier <michael@paquier.xyz> wrote:
There were two more functions that btree_gin.c is pointing at that
could to the switch: timestamp->timestamptz and its opposite. This
also shaves some code, which is nice. Please see the attached.Yes, the patch looks good to me.
Thanks for double-checking. Applied this one as well.
--
Michael
On Mon, Dec 1, 2025 at 9:02 PM Amul Sul <sulamul@gmail.com> wrote:
On Mon, Dec 1, 2025 at 12:32 PM Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Nov 28, 2025 at 09:46:43AM +0530, Amul Sul wrote:
I have attached patch 0002 that renames it. I also updated patch 0001
to accommodate Amit's comment suggestions.Thanks, applied this one after more tweaks. Regarding 0002, just
doing a renaming makes me a bit uncomfortable after a second look.
Another way to look at the problem while being consistent would be to
convert date2timestamp_no_overflow() to use soft error reports,
requiring its caller in selfuncs.c to use an error context node. I
cannot get really excited at the end just for the sake of the planner
stats.Understood. Thanks for committing the patch.
+1, thanks Michael for taking care of this and Amul too.
--
Thanks, Amit Langote