Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 2661469

Browse files
committed
Allow DateTimeParseError to handle bad-timezone error messages.
Pay down some ancient technical debt (dating to commit 022fd99): fix a couple of places in datetime parsing that were throwing ereport's immediately instead of returning a DTERR code that could be interpreted by DateTimeParseError. The reason for that was that there was no mechanism for passing any auxiliary data (such as a zone name) to DateTimeParseError, and these errors seemed to really need it. Up to now it didn't matter that much just where the error got thrown, but now we'd like to have a hard policy that datetime parse errors get thrown from just the one place. Hence, invent a "DateTimeErrorExtra" struct that can be used to carry any extra values needed for specific DTERR codes. Perhaps in the future somebody will be motivated to use this to improve the specificity of other DateTimeParseError messages, but for now just deal with the timezone-error cases. This is on the way to making the datetime input functions report parse errors softly; but it's really an independent change, so commit separately. Discussion: https://postgr.es/m/3bbbb0df-7382-bf87-9737-340ba096e034@postgrespro.ru
1 parent fc7852c commit 2661469

File tree

7 files changed

+176
-90
lines changed

7 files changed

+176
-90
lines changed

contrib/adminpack/adminpack.c

+3-1
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,7 @@ pg_logdir_ls_internal(FunctionCallInfo fcinfo)
553553
fsec_t fsec;
554554
int tz = 0;
555555
struct pg_tm date;
556+
DateTimeErrorExtra extra;
556557

557558
/*
558559
* Default format: postgresql-YYYY-MM-DD_HHMMSS.log
@@ -571,7 +572,8 @@ pg_logdir_ls_internal(FunctionCallInfo fcinfo)
571572
if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
572573
continue;
573574

574-
if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
575+
if (DecodeDateTime(field, ftype, nf,
576+
&dtype, &date, &fsec, &tz, &extra))
575577
continue;
576578

577579
/* Seems the timestamp is OK; prepare and return tuple */

src/backend/access/transam/xlogrecovery.c

+3-1
Original file line numberDiff line numberDiff line change
@@ -4790,12 +4790,14 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
47904790
char *field[MAXDATEFIELDS];
47914791
int ftype[MAXDATEFIELDS];
47924792
char workbuf[MAXDATELEN + MAXDATEFIELDS];
4793+
DateTimeErrorExtra dtextra;
47934794
TimestampTz timestamp;
47944795

47954796
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
47964797
field, ftype, MAXDATEFIELDS, &nf);
47974798
if (dterr == 0)
4798-
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
4799+
dterr = DecodeDateTime(field, ftype, nf,
4800+
&dtype, tm, &fsec, &tz, &dtextra);
47994801
if (dterr != 0)
48004802
return false;
48014803
if (dtype != DTK_DATE)

src/backend/utils/adt/date.c

+19-9
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,15 @@ date_in(PG_FUNCTION_ARGS)
122122
char *field[MAXDATEFIELDS];
123123
int ftype[MAXDATEFIELDS];
124124
char workbuf[MAXDATELEN + 1];
125+
DateTimeErrorExtra extra;
125126

126127
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
127128
field, ftype, MAXDATEFIELDS, &nf);
128129
if (dterr == 0)
129-
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp);
130+
dterr = DecodeDateTime(field, ftype, nf,
131+
&dtype, tm, &fsec, &tzp, &extra);
130132
if (dterr != 0)
131-
DateTimeParseError(dterr, str, "date");
133+
DateTimeParseError(dterr, &extra, str, "date");
132134

133135
switch (dtype)
134136
{
@@ -148,7 +150,7 @@ date_in(PG_FUNCTION_ARGS)
148150
PG_RETURN_DATEADT(date);
149151

150152
default:
151-
DateTimeParseError(DTERR_BAD_FORMAT, str, "date");
153+
DateTimeParseError(DTERR_BAD_FORMAT, &extra, str, "date");
152154
break;
153155
}
154156

@@ -1398,13 +1400,15 @@ time_in(PG_FUNCTION_ARGS)
13981400
char *field[MAXDATEFIELDS];
13991401
int dtype;
14001402
int ftype[MAXDATEFIELDS];
1403+
DateTimeErrorExtra extra;
14011404

14021405
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
14031406
field, ftype, MAXDATEFIELDS, &nf);
14041407
if (dterr == 0)
1405-
dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
1408+
dterr = DecodeTimeOnly(field, ftype, nf,
1409+
&dtype, tm, &fsec, &tz, &extra);
14061410
if (dterr != 0)
1407-
DateTimeParseError(dterr, str, "time");
1411+
DateTimeParseError(dterr, &extra, str, "time");
14081412

14091413
tm2time(tm, fsec, &result);
14101414
AdjustTimeForTypmod(&result, typmod);
@@ -2284,13 +2288,15 @@ timetz_in(PG_FUNCTION_ARGS)
22842288
char *field[MAXDATEFIELDS];
22852289
int dtype;
22862290
int ftype[MAXDATEFIELDS];
2291+
DateTimeErrorExtra extra;
22872292

22882293
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
22892294
field, ftype, MAXDATEFIELDS, &nf);
22902295
if (dterr == 0)
2291-
dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
2296+
dterr = DecodeTimeOnly(field, ftype, nf,
2297+
&dtype, tm, &fsec, &tz, &extra);
22922298
if (dterr != 0)
2293-
DateTimeParseError(dterr, str, "time with time zone");
2299+
DateTimeParseError(dterr, &extra, str, "time with time zone");
22942300

22952301
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
22962302
tm2timetz(tm, fsec, tz, result);
@@ -3042,9 +3048,11 @@ timetz_zone(PG_FUNCTION_ARGS)
30423048
int tz;
30433049
char tzname[TZ_STRLEN_MAX + 1];
30443050
char *lowzone;
3045-
int type,
3051+
int dterr,
3052+
type,
30463053
val;
30473054
pg_tz *tzp;
3055+
DateTimeErrorExtra extra;
30483056

30493057
/*
30503058
* Look up the requested timezone. First we look in the timezone
@@ -3061,7 +3069,9 @@ timetz_zone(PG_FUNCTION_ARGS)
30613069
strlen(tzname),
30623070
false);
30633071

3064-
type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);
3072+
dterr = DecodeTimezoneAbbrev(0, lowzone, &type, &val, &tzp, &extra);
3073+
if (dterr)
3074+
DateTimeParseError(dterr, &extra, NULL, NULL);
30653075

30663076
if (type == TZ || type == DTZ)
30673077
{

src/backend/utils/adt/datetime.c

+77-47
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ static int DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
6969
static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
7070
const char *abbr, pg_tz *tzp,
7171
int *offset, int *isdst);
72-
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp);
72+
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
73+
DateTimeErrorExtra *extra);
7374

7475

7576
const int day_tab[2][13] =
@@ -951,6 +952,9 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
951952
* Return 0 if full date, 1 if only time, and negative DTERR code if problems.
952953
* (Currently, all callers treat 1 as an error return too.)
953954
*
955+
* Inputs are field[] and ftype[] arrays, of length nf.
956+
* Other arguments are outputs.
957+
*
954958
* External format(s):
955959
* "<weekday> <month>-<day>-<year> <hour>:<minute>:<second>"
956960
* "Fri Feb-7-1997 15:23:27"
@@ -972,7 +976,8 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
972976
*/
973977
int
974978
DecodeDateTime(char **field, int *ftype, int nf,
975-
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
979+
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
980+
DateTimeErrorExtra *extra)
976981
{
977982
int fmask = 0,
978983
tmask,
@@ -1112,15 +1117,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
11121117
namedTz = pg_tzset(field[i]);
11131118
if (!namedTz)
11141119
{
1115-
/*
1116-
* We should return an error code instead of
1117-
* ereport'ing directly, but then there is no way
1118-
* to report the bad time zone name.
1119-
*/
1120-
ereport(ERROR,
1121-
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1122-
errmsg("time zone \"%s\" not recognized",
1123-
field[i])));
1120+
extra->dtee_timezone = field[i];
1121+
return DTERR_BAD_TIMEZONE;
11241122
}
11251123
/* we'll apply the zone setting below */
11261124
tmask = DTK_M(TZ);
@@ -1376,7 +1374,10 @@ DecodeDateTime(char **field, int *ftype, int nf,
13761374
case DTK_STRING:
13771375
case DTK_SPECIAL:
13781376
/* timezone abbrevs take precedence over built-in tokens */
1379-
type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz);
1377+
dterr = DecodeTimezoneAbbrev(i, field[i],
1378+
&type, &val, &valtz, extra);
1379+
if (dterr)
1380+
return dterr;
13801381
if (type == UNKNOWN_FIELD)
13811382
type = DecodeSpecial(i, field[i], &val);
13821383
if (type == IGNORE_DTF)
@@ -1912,6 +1913,9 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
19121913
* Interpret parsed string as time fields only.
19131914
* Returns 0 if successful, DTERR code if bogus input detected.
19141915
*
1916+
* Inputs are field[] and ftype[] arrays, of length nf.
1917+
* Other arguments are outputs.
1918+
*
19151919
* Note that support for time zone is here for
19161920
* SQL TIME WITH TIME ZONE, but it reveals
19171921
* bogosity with SQL date/time standards, since
@@ -1922,7 +1926,8 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
19221926
*/
19231927
int
19241928
DecodeTimeOnly(char **field, int *ftype, int nf,
1925-
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
1929+
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
1930+
DateTimeErrorExtra *extra)
19261931
{
19271932
int fmask = 0,
19281933
tmask,
@@ -2018,15 +2023,8 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
20182023
namedTz = pg_tzset(field[i]);
20192024
if (!namedTz)
20202025
{
2021-
/*
2022-
* We should return an error code instead of
2023-
* ereport'ing directly, but then there is no way
2024-
* to report the bad time zone name.
2025-
*/
2026-
ereport(ERROR,
2027-
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2028-
errmsg("time zone \"%s\" not recognized",
2029-
field[i])));
2026+
extra->dtee_timezone = field[i];
2027+
return DTERR_BAD_TIMEZONE;
20302028
}
20312029
/* we'll apply the zone setting below */
20322030
ftype[i] = DTK_TZ;
@@ -2278,7 +2276,10 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
22782276
case DTK_STRING:
22792277
case DTK_SPECIAL:
22802278
/* timezone abbrevs take precedence over built-in tokens */
2281-
type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz);
2279+
dterr = DecodeTimezoneAbbrev(i, field[i],
2280+
&type, &val, &valtz, extra);
2281+
if (dterr)
2282+
return dterr;
22822283
if (type == UNKNOWN_FIELD)
22832284
type = DecodeSpecial(i, field[i], &val);
22842285
if (type == IGNORE_DTF)
@@ -3211,22 +3212,28 @@ DecodeTimezone(const char *str, int *tzp)
32113212
/* DecodeTimezoneAbbrev()
32123213
* Interpret string as a timezone abbreviation, if possible.
32133214
*
3214-
* Returns an abbreviation type (TZ, DTZ, or DYNTZ), or UNKNOWN_FIELD if
3215+
* Sets *ftype to an abbreviation type (TZ, DTZ, or DYNTZ), or UNKNOWN_FIELD if
32153216
* string is not any known abbreviation. On success, set *offset and *tz to
32163217
* represent the UTC offset (for TZ or DTZ) or underlying zone (for DYNTZ).
32173218
* Note that full timezone names (such as America/New_York) are not handled
32183219
* here, mostly for historical reasons.
32193220
*
3221+
* The function result is 0 or a DTERR code; in the latter case, *extra
3222+
* is filled as needed. Note that unknown-abbreviation is not considered
3223+
* an error case. Also note that many callers assume that the DTERR code
3224+
* is one that DateTimeParseError does not require "str" or "datatype"
3225+
* strings for.
3226+
*
32203227
* Given string must be lowercased already.
32213228
*
32223229
* Implement a cache lookup since it is likely that dates
32233230
* will be related in format.
32243231
*/
32253232
int
32263233
DecodeTimezoneAbbrev(int field, const char *lowtoken,
3227-
int *offset, pg_tz **tz)
3234+
int *ftype, int *offset, pg_tz **tz,
3235+
DateTimeErrorExtra *extra)
32283236
{
3229-
int type;
32303237
const datetkn *tp;
32313238

32323239
tp = abbrevcache[field];
@@ -3241,18 +3248,20 @@ DecodeTimezoneAbbrev(int field, const char *lowtoken,
32413248
}
32423249
if (tp == NULL)
32433250
{
3244-
type = UNKNOWN_FIELD;
3251+
*ftype = UNKNOWN_FIELD;
32453252
*offset = 0;
32463253
*tz = NULL;
32473254
}
32483255
else
32493256
{
32503257
abbrevcache[field] = tp;
3251-
type = tp->type;
3252-
if (type == DYNTZ)
3258+
*ftype = tp->type;
3259+
if (tp->type == DYNTZ)
32533260
{
32543261
*offset = 0;
3255-
*tz = FetchDynamicTimeZone(zoneabbrevtbl, tp);
3262+
*tz = FetchDynamicTimeZone(zoneabbrevtbl, tp, extra);
3263+
if (*tz == NULL)
3264+
return DTERR_BAD_ZONE_ABBREV;
32563265
}
32573266
else
32583267
{
@@ -3261,7 +3270,7 @@ DecodeTimezoneAbbrev(int field, const char *lowtoken,
32613270
}
32623271
}
32633272

3264-
return type;
3273+
return 0;
32653274
}
32663275

32673276

@@ -4014,15 +4023,21 @@ DecodeUnits(int field, const char *lowtoken, int *val)
40144023
/*
40154024
* Report an error detected by one of the datetime input processing routines.
40164025
*
4017-
* dterr is the error code, str is the original input string, datatype is
4018-
* the name of the datatype we were trying to accept.
4026+
* dterr is the error code, and *extra contains any auxiliary info we need
4027+
* for the error report. extra can be NULL if not needed for the particular
4028+
* dterr value.
4029+
*
4030+
* str is the original input string, and datatype is the name of the datatype
4031+
* we were trying to accept. (For some DTERR codes, these are not used and
4032+
* can be NULL.)
40194033
*
40204034
* Note: it might seem useless to distinguish DTERR_INTERVAL_OVERFLOW and
40214035
* DTERR_TZDISP_OVERFLOW from DTERR_FIELD_OVERFLOW, but SQL99 mandates three
40224036
* separate SQLSTATE codes, so ...
40234037
*/
40244038
void
4025-
DateTimeParseError(int dterr, const char *str, const char *datatype)
4039+
DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
4040+
const char *str, const char *datatype)
40264041
{
40274042
switch (dterr)
40284043
{
@@ -4052,6 +4067,20 @@ DateTimeParseError(int dterr, const char *str, const char *datatype)
40524067
errmsg("time zone displacement out of range: \"%s\"",
40534068
str)));
40544069
break;
4070+
case DTERR_BAD_TIMEZONE:
4071+
ereport(ERROR,
4072+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4073+
errmsg("time zone \"%s\" not recognized",
4074+
extra->dtee_timezone)));
4075+
break;
4076+
case DTERR_BAD_ZONE_ABBREV:
4077+
ereport(ERROR,
4078+
(errcode(ERRCODE_CONFIG_FILE_ERROR),
4079+
errmsg("time zone \"%s\" not recognized",
4080+
extra->dtee_timezone),
4081+
errdetail("This time zone name appears in the configuration file for time zone abbreviation \"%s\".",
4082+
extra->dtee_abbrev)));
4083+
break;
40554084
case DTERR_BAD_FORMAT:
40564085
default:
40574086
ereport(ERROR,
@@ -4880,9 +4909,12 @@ InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
48804909

48814910
/*
48824911
* Helper subroutine to locate pg_tz timezone for a dynamic abbreviation.
4912+
*
4913+
* On failure, returns NULL and fills *extra for a DTERR_BAD_ZONE_ABBREV error.
48834914
*/
48844915
static pg_tz *
4885-
FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp)
4916+
FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
4917+
DateTimeErrorExtra *extra)
48864918
{
48874919
DynamicZoneAbbrev *dtza;
48884920

@@ -4896,18 +4928,12 @@ FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp)
48964928
if (dtza->tz == NULL)
48974929
{
48984930
dtza->tz = pg_tzset(dtza->zone);
4899-
4900-
/*
4901-
* Ideally we'd let the caller ereport instead of doing it here, but
4902-
* then there is no way to report the bad time zone name.
4903-
*/
49044931
if (dtza->tz == NULL)
4905-
ereport(ERROR,
4906-
(errcode(ERRCODE_CONFIG_FILE_ERROR),
4907-
errmsg("time zone \"%s\" not recognized",
4908-
dtza->zone),
4909-
errdetail("This time zone name appears in the configuration file for time zone abbreviation \"%s\".",
4910-
tp->token)));
4932+
{
4933+
/* Ooops, bogus zone name in config file entry */
4934+
extra->dtee_timezone = dtza->zone;
4935+
extra->dtee_abbrev = tp->token;
4936+
}
49114937
}
49124938
return dtza->tz;
49134939
}
@@ -4993,10 +5019,14 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
49935019
{
49945020
/* Determine the current meaning of the abbrev */
49955021
pg_tz *tzp;
5022+
DateTimeErrorExtra extra;
49965023
TimestampTz now;
49975024
int isdst;
49985025

4999-
tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp);
5026+
tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp, &extra);
5027+
if (tzp == NULL)
5028+
DateTimeParseError(DTERR_BAD_ZONE_ABBREV, &extra,
5029+
NULL, NULL);
50005030
now = GetCurrentTransactionStartTimestamp();
50015031
gmtoffset = -DetermineTimeZoneAbbrevOffsetTS(now,
50025032
tp->token,

0 commit comments

Comments
 (0)