From c60488b4748b4316f1c92d62457671046e5c8994 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 9 Dec 2022 16:07:49 -0500 Subject: [PATCH] Convert datetime input functions to use "soft" error reporting. This patch converts the input functions for date, time, timetz, timestamp, timestamptz, and interval to the new soft-error style. There's some related stuff in formatting.c that remains to be cleaned up, but that seems like a separable project. Discussion: https://postgr.es/m/3bbbb0df-7382-bf87-9737-340ba096e034@postgrespro.ru --- src/backend/utils/adt/date.c | 31 +++++--- src/backend/utils/adt/datetime.c | 22 +++--- src/backend/utils/adt/formatting.c | 21 ++--- src/backend/utils/adt/timestamp.c | 94 ++++++++++++----------- src/include/utils/datetime.h | 12 ++- src/test/regress/expected/date.out | 31 ++++++++ src/test/regress/expected/interval.out | 31 ++++++++ src/test/regress/expected/time.out | 31 ++++++++ src/test/regress/expected/timestamp.out | 31 ++++++++ src/test/regress/expected/timestamptz.out | 31 ++++++++ src/test/regress/expected/timetz.out | 31 ++++++++ src/test/regress/sql/date.sql | 7 ++ src/test/regress/sql/interval.sql | 7 ++ src/test/regress/sql/time.sql | 7 ++ src/test/regress/sql/timestamp.sql | 7 ++ src/test/regress/sql/timestamptz.sql | 7 ++ src/test/regress/sql/timetz.sql | 7 ++ 17 files changed, 328 insertions(+), 80 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 3c5a8a6985..1cf7c7652d 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -111,6 +111,7 @@ Datum date_in(PG_FUNCTION_ARGS) { char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; DateADT date; fsec_t fsec; struct pg_tm tt, @@ -130,7 +131,10 @@ date_in(PG_FUNCTION_ARGS) dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp, &extra); if (dterr != 0) - DateTimeParseError(dterr, &extra, str, "date"); + { + DateTimeParseError(dterr, &extra, str, "date", escontext); + PG_RETURN_NULL(); + } switch (dtype) { @@ -150,13 +154,13 @@ date_in(PG_FUNCTION_ARGS) PG_RETURN_DATEADT(date); default: - DateTimeParseError(DTERR_BAD_FORMAT, &extra, str, "date"); - break; + DateTimeParseError(DTERR_BAD_FORMAT, &extra, str, "date", escontext); + PG_RETURN_NULL(); } /* Prevent overflow in Julian-day routines */ if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday)) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("date out of range: \"%s\"", str))); @@ -164,7 +168,7 @@ date_in(PG_FUNCTION_ARGS) /* Now check for just-out-of-range dates */ if (!IS_VALID_DATE(date)) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("date out of range: \"%s\"", str))); @@ -1384,11 +1388,11 @@ Datum time_in(PG_FUNCTION_ARGS) { char *str = PG_GETARG_CSTRING(0); - #ifdef NOT_USED Oid typelem = PG_GETARG_OID(1); #endif int32 typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; TimeADT result; fsec_t fsec; struct pg_tm tt, @@ -1408,7 +1412,10 @@ time_in(PG_FUNCTION_ARGS) dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz, &extra); if (dterr != 0) - DateTimeParseError(dterr, &extra, str, "time"); + { + DateTimeParseError(dterr, &extra, str, "time", escontext); + PG_RETURN_NULL(); + } tm2time(tm, fsec, &result); AdjustTimeForTypmod(&result, typmod); @@ -2272,11 +2279,11 @@ Datum timetz_in(PG_FUNCTION_ARGS) { char *str = PG_GETARG_CSTRING(0); - #ifdef NOT_USED Oid typelem = PG_GETARG_OID(1); #endif int32 typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; TimeTzADT *result; fsec_t fsec; struct pg_tm tt, @@ -2296,7 +2303,11 @@ timetz_in(PG_FUNCTION_ARGS) dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz, &extra); if (dterr != 0) - DateTimeParseError(dterr, &extra, str, "time with time zone"); + { + DateTimeParseError(dterr, &extra, str, "time with time zone", + escontext); + PG_RETURN_NULL(); + } result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); tm2timetz(tm, fsec, tz, result); @@ -3071,7 +3082,7 @@ timetz_zone(PG_FUNCTION_ARGS) dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra); if (dterr) - DateTimeParseError(dterr, &extra, NULL, NULL); + DateTimeParseError(dterr, &extra, NULL, NULL, NULL); if (type == TZ || type == DTZ) { diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 84bba97abc..b5b117a8ca 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -4031,50 +4031,54 @@ DecodeUnits(int field, const char *lowtoken, int *val) * we were trying to accept. (For some DTERR codes, these are not used and * can be NULL.) * + * If escontext points to an ErrorSaveContext node, that is filled instead + * of throwing an error. + * * Note: it might seem useless to distinguish DTERR_INTERVAL_OVERFLOW and * DTERR_TZDISP_OVERFLOW from DTERR_FIELD_OVERFLOW, but SQL99 mandates three * separate SQLSTATE codes, so ... */ void DateTimeParseError(int dterr, DateTimeErrorExtra *extra, - const char *str, const char *datatype) + const char *str, const char *datatype, + Node *escontext) { switch (dterr) { case DTERR_FIELD_OVERFLOW: - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW), errmsg("date/time field value out of range: \"%s\"", str))); break; case DTERR_MD_FIELD_OVERFLOW: /* same as above, but add hint about DateStyle */ - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW), errmsg("date/time field value out of range: \"%s\"", str), errhint("Perhaps you need a different \"datestyle\" setting."))); break; case DTERR_INTERVAL_OVERFLOW: - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW), errmsg("interval field value out of range: \"%s\"", str))); break; case DTERR_TZDISP_OVERFLOW: - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE), errmsg("time zone displacement out of range: \"%s\"", str))); break; case DTERR_BAD_TIMEZONE: - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("time zone \"%s\" not recognized", extra->dtee_timezone))); break; case DTERR_BAD_ZONE_ABBREV: - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("time zone \"%s\" not recognized", extra->dtee_timezone), @@ -4083,7 +4087,7 @@ DateTimeParseError(int dterr, DateTimeErrorExtra *extra, break; case DTERR_BAD_FORMAT: default: - ereport(ERROR, + errsave(escontext, (errcode(ERRCODE_INVALID_DATETIME_FORMAT), errmsg("invalid input syntax for type %s: \"%s\"", datatype, str))); @@ -5026,7 +5030,7 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS) tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp, &extra); if (tzp == NULL) DateTimeParseError(DTERR_BAD_ZONE_ABBREV, &extra, - NULL, NULL); + NULL, NULL, NULL); now = GetCurrentTransactionStartTimestamp(); gmtoffset = -DetermineTimeZoneAbbrevOffsetTS(now, tp->token, diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 0d065d8e41..eba981abd8 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -4251,7 +4251,7 @@ to_timestamp(PG_FUNCTION_ARGS) if (dterr) DateTimeParseError(dterr, &extra, text_to_cstring(date_txt), - "timestamptz"); + "timestamptz", NULL); } else tz = DetermineTimeZoneOffset(&tm, session_timezone); @@ -4263,7 +4263,7 @@ to_timestamp(PG_FUNCTION_ARGS) /* Use the specified fractional precision, if any. */ if (fprec) - AdjustTimestampForTypmod(&result, fprec); + AdjustTimestampForTypmod(&result, fprec, NULL); PG_RETURN_TIMESTAMP(result); } @@ -4351,7 +4351,7 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, if (dterr) DateTimeParseError(dterr, &extra, text_to_cstring(date_txt), - "timestamptz"); + "timestamptz", NULL); } else { @@ -4372,7 +4372,7 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamptz out of range")))); - AdjustTimestampForTypmod(&result, *typmod); + AdjustTimestampForTypmod(&result, *typmod, NULL); /* XXX */ *typid = TIMESTAMPTZOID; return TimestampTzGetDatum(result); @@ -4386,7 +4386,7 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range")))); - AdjustTimestampForTypmod(&result, *typmod); + AdjustTimestampForTypmod(&result, *typmod, NULL); /* XXX */ *typid = TIMESTAMPOID; return TimestampGetDatum(result); @@ -4440,7 +4440,7 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, if (dterr) RETURN_ERROR(DateTimeParseError(dterr, &extra, text_to_cstring(date_txt), - "timetz")); + "timetz", NULL)); } else { @@ -4789,7 +4789,8 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an * irrelevant hint about datestyle. */ - RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, date_str, "timestamp")); + RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + date_str, "timestamp", NULL)); } } @@ -4799,7 +4800,8 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE || *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC) { - RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, date_str, "timestamp")); + RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + date_str, "timestamp", NULL)); } /* Save parsed time-zone into tm->tm_zone if it was specified */ @@ -4810,7 +4812,8 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR || tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR) { - RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, NULL, date_str, "timestamp")); + RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, NULL, + date_str, "timestamp", NULL)); } tz = psprintf("%c%02d:%02d", diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 5a98ca1dec..3f2508c0c4 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -74,7 +74,8 @@ typedef struct static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec); static Timestamp dt2local(Timestamp dt, int timezone); -static void AdjustIntervalForTypmod(Interval *interval, int32 typmod); +static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod, + Node *escontext); static TimestampTz timestamp2timestamptz(Timestamp timestamp); static Timestamp timestamptz2timestamp(TimestampTz timestamp); @@ -145,11 +146,11 @@ Datum timestamp_in(PG_FUNCTION_ARGS) { char *str = PG_GETARG_CSTRING(0); - #ifdef NOT_USED Oid typelem = PG_GETARG_OID(1); #endif int32 typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; Timestamp result; fsec_t fsec; struct pg_tm tt, @@ -169,13 +170,16 @@ timestamp_in(PG_FUNCTION_ARGS) dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz, &extra); if (dterr != 0) - DateTimeParseError(dterr, &extra, str, "timestamp"); + { + DateTimeParseError(dterr, &extra, str, "timestamp", escontext); + PG_RETURN_NULL(); + } switch (dtype) { case DTK_DATE: if (tm2timestamp(tm, fsec, NULL, &result) != 0) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range: \"%s\"", str))); break; @@ -198,7 +202,7 @@ timestamp_in(PG_FUNCTION_ARGS) TIMESTAMP_NOEND(result); } - AdjustTimestampForTypmod(&result, typmod); + AdjustTimestampForTypmod(&result, typmod, escontext); PG_RETURN_TIMESTAMP(result); } @@ -257,7 +261,7 @@ timestamp_recv(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); - AdjustTimestampForTypmod(×tamp, typmod); + AdjustTimestampForTypmod(×tamp, typmod, NULL); PG_RETURN_TIMESTAMP(timestamp); } @@ -328,17 +332,20 @@ timestamp_scale(PG_FUNCTION_ARGS) result = timestamp; - AdjustTimestampForTypmod(&result, typmod); + AdjustTimestampForTypmod(&result, typmod, NULL); PG_RETURN_TIMESTAMP(result); } /* - * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod + * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod * Works for either timestamp or timestamptz. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). */ bool -AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error) +AdjustTimestampForTypmod(Timestamp *time, int32 typmod, Node *escontext) { static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = { INT64CONST(1000000), @@ -364,18 +371,10 @@ AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error) && (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION)) { if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION) - { - if (error) - { - *error = true; - return false; - } - - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("timestamp(%d) precision must be between %d and %d", typmod, 0, MAX_TIMESTAMP_PRECISION))); - } if (*time >= INT64CONST(0)) { @@ -392,12 +391,6 @@ AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error) return true; } -void -AdjustTimestampForTypmod(Timestamp *time, int32 typmod) -{ - (void) AdjustTimestampForTypmodError(time, typmod, NULL); -} - /* timestamptz_in() * Convert a string to internal form. */ @@ -405,11 +398,11 @@ Datum timestamptz_in(PG_FUNCTION_ARGS) { char *str = PG_GETARG_CSTRING(0); - #ifdef NOT_USED Oid typelem = PG_GETARG_OID(1); #endif int32 typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; TimestampTz result; fsec_t fsec; struct pg_tm tt, @@ -429,13 +422,17 @@ timestamptz_in(PG_FUNCTION_ARGS) dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz, &extra); if (dterr != 0) - DateTimeParseError(dterr, &extra, str, "timestamp with time zone"); + { + DateTimeParseError(dterr, &extra, str, "timestamp with time zone", + escontext); + PG_RETURN_NULL(); + } switch (dtype) { case DTK_DATE: if (tm2timestamp(tm, fsec, &tz, &result) != 0) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range: \"%s\"", str))); break; @@ -458,7 +455,7 @@ timestamptz_in(PG_FUNCTION_ARGS) TIMESTAMP_NOEND(result); } - AdjustTimestampForTypmod(&result, typmod); + AdjustTimestampForTypmod(&result, typmod, escontext); PG_RETURN_TIMESTAMPTZ(result); } @@ -525,7 +522,7 @@ parse_sane_timezone(struct pg_tm *tm, text *zone) false); dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra); if (dterr) - DateTimeParseError(dterr, &extra, NULL, NULL); + DateTimeParseError(dterr, &extra, NULL, NULL, NULL); if (type == TZ || type == DTZ) { @@ -824,7 +821,7 @@ timestamptz_recv(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); - AdjustTimestampForTypmod(×tamp, typmod); + AdjustTimestampForTypmod(×tamp, typmod, NULL); PG_RETURN_TIMESTAMPTZ(timestamp); } @@ -873,7 +870,7 @@ timestamptz_scale(PG_FUNCTION_ARGS) result = timestamp; - AdjustTimestampForTypmod(&result, typmod); + AdjustTimestampForTypmod(&result, typmod, NULL); PG_RETURN_TIMESTAMPTZ(result); } @@ -889,11 +886,11 @@ Datum interval_in(PG_FUNCTION_ARGS) { char *str = PG_GETARG_CSTRING(0); - #ifdef NOT_USED Oid typelem = PG_GETARG_OID(1); #endif int32 typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; Interval *result; struct pg_itm_in tt, *itm_in = &tt; @@ -931,7 +928,8 @@ interval_in(PG_FUNCTION_ARGS) { if (dterr == DTERR_FIELD_OVERFLOW) dterr = DTERR_INTERVAL_OVERFLOW; - DateTimeParseError(dterr, &extra, str, "interval"); + DateTimeParseError(dterr, &extra, str, "interval", escontext); + PG_RETURN_NULL(); } result = (Interval *) palloc(sizeof(Interval)); @@ -940,7 +938,7 @@ interval_in(PG_FUNCTION_ARGS) { case DTK_DELTA: if (itmin2interval(itm_in, result) != 0) - ereport(ERROR, + ereturn(escontext, (Datum) 0, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("interval out of range"))); break; @@ -950,7 +948,7 @@ interval_in(PG_FUNCTION_ARGS) dtype, str); } - AdjustIntervalForTypmod(result, typmod); + AdjustIntervalForTypmod(result, typmod, escontext); PG_RETURN_INTERVAL_P(result); } @@ -994,7 +992,7 @@ interval_recv(PG_FUNCTION_ARGS) interval->day = pq_getmsgint(buf, sizeof(interval->day)); interval->month = pq_getmsgint(buf, sizeof(interval->month)); - AdjustIntervalForTypmod(interval, typmod); + AdjustIntervalForTypmod(interval, typmod, NULL); PG_RETURN_INTERVAL_P(interval); } @@ -1318,7 +1316,7 @@ interval_scale(PG_FUNCTION_ARGS) result = palloc(sizeof(Interval)); *result = *interval; - AdjustIntervalForTypmod(result, typmod); + AdjustIntervalForTypmod(result, typmod, NULL); PG_RETURN_INTERVAL_P(result); } @@ -1326,9 +1324,13 @@ interval_scale(PG_FUNCTION_ARGS) /* * Adjust interval for specified precision, in both YEAR to SECOND * range and sub-second precision. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). */ -static void -AdjustIntervalForTypmod(Interval *interval, int32 typmod) +static bool +AdjustIntervalForTypmod(Interval *interval, int32 typmod, + Node *escontext) { static const int64 IntervalScales[MAX_INTERVAL_PRECISION + 1] = { INT64CONST(1000000), @@ -1468,7 +1470,7 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod) if (precision != INTERVAL_FULL_PRECISION) { if (precision < 0 || precision > MAX_INTERVAL_PRECISION) - ereport(ERROR, + ereturn(escontext, false, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("interval(%d) precision must be between %d and %d", precision, 0, MAX_INTERVAL_PRECISION))); @@ -1489,6 +1491,8 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod) } } } + + return true; } /* @@ -1609,7 +1613,7 @@ current_timestamp(PG_FUNCTION_ARGS) ts = GetCurrentTransactionStartTimestamp(); if (typmod >= 0) - AdjustTimestampForTypmod(&ts, typmod); + AdjustTimestampForTypmod(&ts, typmod, NULL); return TimestampTzGetDatum(ts); } @@ -1630,7 +1634,7 @@ sql_localtimestamp(PG_FUNCTION_ARGS) ts = timestamptz2timestamp(GetCurrentTransactionStartTimestamp()); if (typmod >= 0) - AdjustTimestampForTypmod(&ts, typmod); + AdjustTimestampForTypmod(&ts, typmod, NULL); return TimestampGetDatum(ts); } @@ -4324,7 +4328,7 @@ timestamptz_trunc_zone(PG_FUNCTION_ARGS) dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra); if (dterr) - DateTimeParseError(dterr, &extra, NULL, NULL); + DateTimeParseError(dterr, &extra, NULL, NULL, NULL); if (type == TZ || type == DTZ) { @@ -5452,7 +5456,7 @@ timestamp_zone(PG_FUNCTION_ARGS) dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra); if (dterr) - DateTimeParseError(dterr, &extra, NULL, NULL); + DateTimeParseError(dterr, &extra, NULL, NULL, NULL); if (type == TZ || type == DTZ) { @@ -5708,7 +5712,7 @@ timestamptz_zone(PG_FUNCTION_ARGS) dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra); if (dterr) - DateTimeParseError(dterr, &extra, NULL, NULL); + DateTimeParseError(dterr, &extra, NULL, NULL, NULL); if (type == TZ || type == DTZ) { diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index bb70b75449..bdb7c06bec 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -16,7 +16,6 @@ #ifndef DATETIME_H #define DATETIME_H -#include "nodes/nodes.h" #include "utils/timestamp.h" /* this struct is declared in utils/tzparser.h: */ @@ -318,8 +317,8 @@ extern int DecodeISO8601Interval(char *str, int *dtype, struct pg_itm_in *itm_in); extern void DateTimeParseError(int dterr, DateTimeErrorExtra *extra, - const char *str, - const char *datatype) pg_attribute_noreturn(); + const char *str, const char *datatype, + struct Node *escontext); extern int DetermineTimeZoneOffset(struct pg_tm *tm, pg_tz *tzp); extern int DetermineTimeZoneAbbrevOffset(struct pg_tm *tm, const char *abbr, pg_tz *tzp); @@ -343,7 +342,7 @@ extern int DecodeUnits(int field, const char *lowtoken, int *val); extern int j2day(int date); -extern Node *TemporalSimplify(int32 max_precis, Node *node); +extern struct Node *TemporalSimplify(int32 max_precis, struct Node *node); extern bool CheckDateTokenTables(void); @@ -351,8 +350,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, int n); extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl); -extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod); -extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, - bool *error); +extern bool AdjustTimestampForTypmod(Timestamp *time, int32 typmod, + struct Node *escontext); #endif /* DATETIME_H */ diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out index 75ff659378..f8f83e40e9 100644 --- a/src/test/regress/expected/date.out +++ b/src/test/regress/expected/date.out @@ -840,6 +840,37 @@ SELECT date '5874898-01-01'; -- out of range ERROR: date out of range: "5874898-01-01" LINE 1: SELECT date '5874898-01-01'; ^ +-- Test non-error-throwing API +SELECT pg_input_is_valid('now', 'date'); + pg_input_is_valid +------------------- + t +(1 row) + +SELECT pg_input_is_valid('garbage', 'date'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_is_valid('6874898-01-01', 'date'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_error_message('garbage', 'date'); + pg_input_error_message +----------------------------------------------- + invalid input syntax for type date: "garbage" +(1 row) + +SELECT pg_input_error_message('6874898-01-01', 'date'); + pg_input_error_message +------------------------------------ + date out of range: "6874898-01-01" +(1 row) + RESET datestyle; -- -- Simple math diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index 00885acd1d..579e92e7b3 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -72,6 +72,37 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 30 eons ago'); ERROR: invalid input syntax for type interval: "@ 30 eons ago" LINE 1: INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 30 eons ago'); ^ +-- Test non-error-throwing API +SELECT pg_input_is_valid('1.5 weeks', 'interval'); + pg_input_is_valid +------------------- + t +(1 row) + +SELECT pg_input_is_valid('garbage', 'interval'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_is_valid('@ 30 eons ago', 'interval'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_error_message('garbage', 'interval'); + pg_input_error_message +--------------------------------------------------- + invalid input syntax for type interval: "garbage" +(1 row) + +SELECT pg_input_error_message('@ 30 eons ago', 'interval'); + pg_input_error_message +--------------------------------------------------------- + invalid input syntax for type interval: "@ 30 eons ago" +(1 row) + -- test interval operators SELECT * FROM INTERVAL_TBL; f1 diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out index f3a71503c2..a44caededd 100644 --- a/src/test/regress/expected/time.out +++ b/src/test/regress/expected/time.out @@ -114,6 +114,37 @@ SELECT '25:00:00'::time; -- not allowed ERROR: date/time field value out of range: "25:00:00" LINE 1: SELECT '25:00:00'::time; ^ +-- Test non-error-throwing API +SELECT pg_input_is_valid('12:00:00', 'time'); + pg_input_is_valid +------------------- + t +(1 row) + +SELECT pg_input_is_valid('25:00:00', 'time'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_is_valid('15:36:39 America/New_York', 'time'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_error_message('25:00:00', 'time'); + pg_input_error_message +------------------------------------------------ + date/time field value out of range: "25:00:00" +(1 row) + +SELECT pg_input_error_message('15:36:39 America/New_York', 'time'); + pg_input_error_message +----------------------------------------------------------------- + invalid input syntax for type time: "15:36:39 America/New_York" +(1 row) + -- -- TIME simple math -- diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out index 79f8180955..be66274738 100644 --- a/src/test/regress/expected/timestamp.out +++ b/src/test/regress/expected/timestamp.out @@ -119,6 +119,37 @@ INSERT INTO TIMESTAMP_TBL VALUES ('19970710 173201 America/Does_not_exist'); ERROR: time zone "america/does_not_exist" not recognized LINE 1: INSERT INTO TIMESTAMP_TBL VALUES ('19970710 173201 America/D... ^ +-- Test non-error-throwing API +SELECT pg_input_is_valid('now', 'timestamp'); + pg_input_is_valid +------------------- + t +(1 row) + +SELECT pg_input_is_valid('garbage', 'timestamp'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_error_message('garbage', 'timestamp'); + pg_input_error_message +---------------------------------------------------- + invalid input syntax for type timestamp: "garbage" +(1 row) + +SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp'); + pg_input_error_message +-------------------------------------------- + time zone "nehwon/lankhmar" not recognized +(1 row) + -- Check date conversion and date arithmetic INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 18:32:01 PDT'); INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997'); diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index eba84191d3..fb06acbccc 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -170,6 +170,37 @@ SELECT '205000-01-10 17:32:01 Europe/Helsinki'::timestamptz; -- non-DST Fri Jan 10 07:32:01 205000 PST (1 row) +-- Test non-error-throwing API +SELECT pg_input_is_valid('now', 'timestamptz'); + pg_input_is_valid +------------------- + t +(1 row) + +SELECT pg_input_is_valid('garbage', 'timestamptz'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_error_message('garbage', 'timestamptz'); + pg_input_error_message +------------------------------------------------------------------- + invalid input syntax for type timestamp with time zone: "garbage" +(1 row) + +SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz'); + pg_input_error_message +-------------------------------------------- + time zone "nehwon/lankhmar" not recognized +(1 row) + -- Check date conversion and date arithmetic INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-06-10 18:32:01 PDT'); INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 17:32:01 1997'); diff --git a/src/test/regress/expected/timetz.out b/src/test/regress/expected/timetz.out index 8942a9b95b..984285663b 100644 --- a/src/test/regress/expected/timetz.out +++ b/src/test/regress/expected/timetz.out @@ -131,6 +131,37 @@ SELECT '25:00:00 PDT'::timetz; -- not allowed ERROR: date/time field value out of range: "25:00:00 PDT" LINE 1: SELECT '25:00:00 PDT'::timetz; ^ +-- Test non-error-throwing API +SELECT pg_input_is_valid('12:00:00 PDT', 'timetz'); + pg_input_is_valid +------------------- + t +(1 row) + +SELECT pg_input_is_valid('25:00:00 PDT', 'timetz'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_is_valid('15:36:39 America/New_York', 'timetz'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_error_message('25:00:00 PDT', 'timetz'); + pg_input_error_message +---------------------------------------------------- + date/time field value out of range: "25:00:00 PDT" +(1 row) + +SELECT pg_input_error_message('15:36:39 America/New_York', 'timetz'); + pg_input_error_message +-------------------------------------------------------------------------------- + invalid input syntax for type time with time zone: "15:36:39 America/New_York" +(1 row) + -- -- TIME simple math -- diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql index 8f7435b767..9fd15be5f9 100644 --- a/src/test/regress/sql/date.sql +++ b/src/test/regress/sql/date.sql @@ -193,6 +193,13 @@ SELECT date '4714-11-23 BC'; -- out of range SELECT date '5874897-12-31'; SELECT date '5874898-01-01'; -- out of range +-- Test non-error-throwing API +SELECT pg_input_is_valid('now', 'date'); +SELECT pg_input_is_valid('garbage', 'date'); +SELECT pg_input_is_valid('6874898-01-01', 'date'); +SELECT pg_input_error_message('garbage', 'date'); +SELECT pg_input_error_message('6874898-01-01', 'date'); + RESET datestyle; -- diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index 97d33a1323..0517b5b82b 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -32,6 +32,13 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours'); INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval'); INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 30 eons ago'); +-- Test non-error-throwing API +SELECT pg_input_is_valid('1.5 weeks', 'interval'); +SELECT pg_input_is_valid('garbage', 'interval'); +SELECT pg_input_is_valid('@ 30 eons ago', 'interval'); +SELECT pg_input_error_message('garbage', 'interval'); +SELECT pg_input_error_message('@ 30 eons ago', 'interval'); + -- test interval operators SELECT * FROM INTERVAL_TBL; diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql index 3637f28798..b439cd6b41 100644 --- a/src/test/regress/sql/time.sql +++ b/src/test/regress/sql/time.sql @@ -40,6 +40,13 @@ SELECT '23:59:60.01'::time; -- not allowed SELECT '24:01:00'::time; -- not allowed SELECT '25:00:00'::time; -- not allowed +-- Test non-error-throwing API +SELECT pg_input_is_valid('12:00:00', 'time'); +SELECT pg_input_is_valid('25:00:00', 'time'); +SELECT pg_input_is_valid('15:36:39 America/New_York', 'time'); +SELECT pg_input_error_message('25:00:00', 'time'); +SELECT pg_input_error_message('15:36:39 America/New_York', 'time'); + -- -- TIME simple math -- diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql index ebc969f36c..e1175b12ce 100644 --- a/src/test/regress/sql/timestamp.sql +++ b/src/test/regress/sql/timestamp.sql @@ -94,6 +94,13 @@ INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 America/New_York'); -- this fails (even though TZ is a no-op, we still look it up) INSERT INTO TIMESTAMP_TBL VALUES ('19970710 173201 America/Does_not_exist'); +-- Test non-error-throwing API +SELECT pg_input_is_valid('now', 'timestamp'); +SELECT pg_input_is_valid('garbage', 'timestamp'); +SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp'); +SELECT pg_input_error_message('garbage', 'timestamp'); +SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp'); + -- Check date conversion and date arithmetic INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 18:32:01 PDT'); diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql index a107abc5a4..27263b3e0b 100644 --- a/src/test/regress/sql/timestamptz.sql +++ b/src/test/regress/sql/timestamptz.sql @@ -107,6 +107,13 @@ SELECT '20500110 173201 Europe/Helsinki'::timestamptz; -- non-DST SELECT '205000-07-10 17:32:01 Europe/Helsinki'::timestamptz; -- DST SELECT '205000-01-10 17:32:01 Europe/Helsinki'::timestamptz; -- non-DST +-- Test non-error-throwing API +SELECT pg_input_is_valid('now', 'timestamptz'); +SELECT pg_input_is_valid('garbage', 'timestamptz'); +SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz'); +SELECT pg_input_error_message('garbage', 'timestamptz'); +SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz'); + -- Check date conversion and date arithmetic INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-06-10 18:32:01 PDT'); diff --git a/src/test/regress/sql/timetz.sql b/src/test/regress/sql/timetz.sql index 7b70f4656c..b62aa3fe05 100644 --- a/src/test/regress/sql/timetz.sql +++ b/src/test/regress/sql/timetz.sql @@ -45,6 +45,13 @@ SELECT '23:59:60.01 PDT'::timetz; -- not allowed SELECT '24:01:00 PDT'::timetz; -- not allowed SELECT '25:00:00 PDT'::timetz; -- not allowed +-- Test non-error-throwing API +SELECT pg_input_is_valid('12:00:00 PDT', 'timetz'); +SELECT pg_input_is_valid('25:00:00 PDT', 'timetz'); +SELECT pg_input_is_valid('15:36:39 America/New_York', 'timetz'); +SELECT pg_input_error_message('25:00:00 PDT', 'timetz'); +SELECT pg_input_error_message('15:36:39 America/New_York', 'timetz'); + -- -- TIME simple math --