Error suppression support for upcoming jsonpath .datetime() method

Add support of error suppression in some date and time manipulation functions
as it's required for jsonpath .datetime() method support.  This commit doesn't
use PG_TRY()/PG_CATCH() in order to implement that.  Instead, it provides
internal versions of date and time functions used, which support error
suppression.

Discussion: https://postgr.es/m/CAPpHfdsZgYEra_PeCLGNoXOWYx6iU-S3wF8aX0ObQUcZU%2B4XTw%40mail.gmail.com
Author: Alexander Korotkov, Nikita Glukhov
Reviewed-by: Anastasia Lubennikova, Peter Eisentraut
This commit is contained in:
Alexander Korotkov 2019-09-25 21:51:47 +03:00
parent 66c74f8b6e
commit 5bc450629b
7 changed files with 480 additions and 221 deletions

View File

@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
PG_RETURN_DATEADT(result);
}
/*
* Internal routines for promoting date to timestamp and timestamp with
* time zone
*/
static Timestamp
date2timestamp(DateADT dateVal)
/*
* Promote date to timestamp.
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
* and zero is returned.
*/
Timestamp
date2timestamp_opt_error(DateADT dateVal, bool *have_error)
{
Timestamp result;
@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal)
* boundary need be checked for overflow.
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
{
if (have_error)
{
*have_error = true;
return (Timestamp) 0;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range for timestamp")));
}
}
/* date is days since 2000, timestamp is microseconds since same... */
result = dateVal * USECS_PER_DAY;
@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal)
return result;
}
/*
* Single-argument version of date2timestamp_opt_error().
*/
static TimestampTz
date2timestamptz(DateADT dateVal)
date2timestamp(DateADT dateVal)
{
return date2timestamp_opt_error(dateVal, NULL);
}
/*
* Promote date to timestamp with time zone.
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
* and zero is returned.
*/
TimestampTz
date2timestamptz_opt_error(DateADT dateVal, bool *have_error)
{
TimestampTz result;
struct pg_tm tt,
@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal)
* boundary need be checked for overflow.
*/
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
{
if (have_error)
{
*have_error = true;
return (TimestampTz) 0;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range for timestamp")));
}
}
j2date(dateVal + POSTGRES_EPOCH_JDATE,
&(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
@ -621,14 +658,33 @@ date2timestamptz(DateADT dateVal)
* of time zone, check for allowed timestamp range after adding tz.
*/
if (!IS_VALID_TIMESTAMP(result))
{
if (have_error)
{
*have_error = true;
return (TimestampTz) 0;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range for timestamp")));
}
}
}
return result;
}
/*
* Single-argument version of date2timestamptz_opt_error().
*/
static TimestampTz
date2timestamptz(DateADT dateVal)
{
return date2timestamptz_opt_error(dateVal, NULL);
}
/*
* date2timestamp_no_overflow
*

View File

@ -98,6 +98,43 @@
#include "utils/numeric.h"
#include "utils/pg_locale.h"
/* ----------
* Convenience macros for error handling
* ----------
*
* Two macros below help to handle errors in functions that take
* 'bool *have_error' argument. When this argument is not NULL, it's expected
* that function will suppress ereports when possible. Instead it should
* return some default value and set *have_error flag.
*
* RETURN_ERROR() macro intended to wrap ereport() calls. When have_error
* function argument is not NULL, then instead of ereport'ing we set
* *have_error flag and go to on_error label. It's supposed that jump
* resources will be freed and some 'default' value returned.
*
* CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
* It's supposed to be used for immediate exit from the function on error
* after call of another function with 'bool *have_error' argument.
*/
#define RETURN_ERROR(throw_error) \
do { \
if (have_error) \
{ \
*have_error = true; \
goto on_error; \
} \
else \
{ \
throw_error; \
} \
} while (0)
#define CHECK_ERROR \
do { \
if (have_error && *have_error) \
goto on_error; \
} while (0)
/* ----------
* Routines flags
* ----------
@ -1012,7 +1049,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid);
static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
bool std);
bool std, bool *have_error);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@ -1023,15 +1060,21 @@ static const char *get_th(char *num, int type);
static char *str_numth(char *dest, char *num, int type);
static int adjust_partial_year_to_2020(int year);
static int strspace_len(char *str);
static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
static void from_char_set_int(int *dest, const int value, const FormatNode *node);
static int from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
static int from_char_parse_int(int *dest, char **src, FormatNode *node);
static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
bool *have_error);
static void from_char_set_int(int *dest, const int value, const FormatNode *node,
bool *have_error);
static int from_char_parse_int_len(int *dest, char **src, const int len,
FormatNode *node, bool *have_error);
static int from_char_parse_int(int *dest, char **src, FormatNode *node,
bool *have_error);
static int seq_search(char *name, const char *const *array, int type, int max, int *len);
static int from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
static int from_char_seq_search(int *dest, char **src,
const char *const *array, int type, int max,
FormatNode *node, bool *have_error);
static void do_to_timestamp(text *date_txt, text *fmt, bool std,
struct pg_tm *tm, fsec_t *fsec, int *fprec,
uint32 *flags);
uint32 *flags, bool *have_error);
static char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
static char *int_to_roman(int number);
@ -2233,21 +2276,26 @@ strspace_len(char *str)
*
* Puke if the date mode has already been set, and the caller attempts to set
* it to a conflicting mode.
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
*/
static void
from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
{
if (mode != FROM_CHAR_DATE_NONE)
{
if (tmfc->mode == FROM_CHAR_DATE_NONE)
tmfc->mode = mode;
else if (tmfc->mode != mode)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid combination of date conventions"),
errhint("Do not mix Gregorian and ISO week date "
"conventions in a formatting template.")));
"conventions in a formatting template."))));
}
on_error:
return;
}
/*
@ -2255,18 +2303,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
*
* Puke if the destination integer has previously been set to some other
* non-zero value.
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
*/
static void
from_char_set_int(int *dest, const int value, const FormatNode *node)
from_char_set_int(int *dest, const int value, const FormatNode *node,
bool *have_error)
{
if (*dest != 0 && *dest != value)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("conflicting values for \"%s\" field in formatting string",
errmsg("conflicting values for \"%s\" field in "
"formatting string",
node->key->name),
errdetail("This value contradicts a previous setting for "
"the same field type.")));
errdetail("This value contradicts a previous setting "
"for the same field type."))));
*dest = value;
on_error:
return;
}
/*
@ -2288,9 +2343,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
* Note that from_char_parse_int() provides a more convenient wrapper where
* the length of the field is the same as the length of the format keyword (as
* with DD and MI).
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
* and -1 is returned.
*/
static int
from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
bool *have_error)
{
long result;
char copy[DCH_MAX_ITEM_SIZ + 1];
@ -2323,51 +2382,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
char *last;
if (used < len)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("source string too short for \"%s\" formatting field",
errmsg("source string too short for \"%s\" "
"formatting field",
node->key->name),
errdetail("Field requires %d characters, but only %d "
"remain.",
errdetail("Field requires %d characters, "
"but only %d remain.",
len, used),
errhint("If your source string is not fixed-width, try "
"using the \"FM\" modifier.")));
errhint("If your source string is not fixed-width, "
"try using the \"FM\" modifier."))));
errno = 0;
result = strtol(copy, &last, 10);
used = last - copy;
if (used > 0 && used < len)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"",
copy, node->key->name),
errdetail("Field requires %d characters, but only %d "
"could be parsed.", len, used),
errhint("If your source string is not fixed-width, try "
"using the \"FM\" modifier.")));
errdetail("Field requires %d characters, "
"but only %d could be parsed.",
len, used),
errhint("If your source string is not fixed-width, "
"try using the \"FM\" modifier."))));
*src += used;
}
if (*src == init)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"",
copy, node->key->name),
errdetail("Value must be an integer.")));
errdetail("Value must be an integer."))));
if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("value for \"%s\" in source string is out of range",
node->key->name),
errdetail("Value must be in the range %d to %d.",
INT_MIN, INT_MAX)));
INT_MIN, INT_MAX))));
if (dest != NULL)
from_char_set_int(dest, (int) result, node);
{
from_char_set_int(dest, (int) result, node, have_error);
CHECK_ERROR;
}
return *src - init;
on_error:
return -1;
}
/*
@ -2380,9 +2448,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
* required length explicitly.
*/
static int
from_char_parse_int(int *dest, char **src, FormatNode *node)
from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
{
return from_char_parse_int_len(dest, src, node->key->len, node);
return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
}
/* ----------
@ -2465,11 +2533,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
* pointed to by 'dest', advance 'src' to the end of the part of the string
* which matched, and return the number of characters consumed.
*
* If the string doesn't match, throw an error.
* If the string doesn't match, throw an error if 'have_error' is NULL,
* otherwise set '*have_error' and return -1.
*/
static int
from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
FormatNode *node)
from_char_seq_search(int *dest, char **src, const char *const *array, int type,
int max, FormatNode *node, bool *have_error)
{
int len;
@ -2481,15 +2550,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
Assert(max <= DCH_MAX_ITEM_SIZ);
strlcpy(copy, *src, max + 1);
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"",
copy, node->key->name),
errdetail("The given value did not match any of the allowed "
"values for this field.")));
errdetail("The given value did not match any of "
"the allowed values for this field."))));
}
*src += len;
return len;
on_error:
return -1;
}
/* ----------
@ -3089,10 +3161,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
*
* Note: we currently don't have any to_interval() function, so there
* is no need here for INVALID_FOR_INTERVAL checks.
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
* ----------
*/
static void
DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
bool *have_error)
{
FormatNode *n;
char *s;
@ -3132,10 +3207,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
if (*s == n->character[0])
s++;
else
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("unmatched format separator \"%c\"",
n->character[0])));
n->character[0]))));
}
else if (!fx_mode)
{
@ -3191,7 +3266,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
continue;
}
from_char_set_mode(out, n->key->date_mode);
from_char_set_mode(out, n->key->date_mode, have_error);
CHECK_ERROR;
switch (n->key->id)
{
@ -3203,8 +3279,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
case DCH_a_m:
case DCH_p_m:
from_char_seq_search(&value, &s, ampm_strings_long,
ALL_UPPER, n->key->len, n);
from_char_set_int(&out->pm, value % 2, n);
ALL_UPPER, n->key->len, n, have_error);
CHECK_ERROR;
from_char_set_int(&out->pm, value % 2, n, have_error);
CHECK_ERROR;
out->clock = CLOCK_12_HOUR;
break;
case DCH_AM:
@ -3212,30 +3290,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
case DCH_am:
case DCH_pm:
from_char_seq_search(&value, &s, ampm_strings,
ALL_UPPER, n->key->len, n);
from_char_set_int(&out->pm, value % 2, n);
ALL_UPPER, n->key->len, n, have_error);
CHECK_ERROR;
from_char_set_int(&out->pm, value % 2, n, have_error);
CHECK_ERROR;
out->clock = CLOCK_12_HOUR;
break;
case DCH_HH:
case DCH_HH12:
from_char_parse_int_len(&out->hh, &s, 2, n);
from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
CHECK_ERROR;
out->clock = CLOCK_12_HOUR;
SKIP_THth(s, n->suffix);
break;
case DCH_HH24:
from_char_parse_int_len(&out->hh, &s, 2, n);
from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_MI:
from_char_parse_int(&out->mi, &s, n);
from_char_parse_int(&out->mi, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_SS:
from_char_parse_int(&out->ss, &s, n);
from_char_parse_int(&out->ss, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_MS: /* millisecond */
len = from_char_parse_int_len(&out->ms, &s, 3, n);
len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
CHECK_ERROR;
/*
* 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@ -3256,7 +3341,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
case DCH_US: /* microsecond */
len = from_char_parse_int_len(&out->us, &s,
n->key->id == DCH_US ? 6 :
out->ff, n);
out->ff, n, have_error);
CHECK_ERROR;
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
@ -3267,16 +3353,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
SKIP_THth(s, n->suffix);
break;
case DCH_SSSS:
from_char_parse_int(&out->ssss, &s, n);
from_char_parse_int(&out->ssss, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_tz:
case DCH_TZ:
case DCH_OF:
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("formatting field \"%s\" is only supported in to_char",
n->key->name)));
n->key->name))));
CHECK_ERROR;
break;
case DCH_TZH:
@ -3300,82 +3388,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
out->tzsign = +1;
}
from_char_parse_int_len(&out->tzh, &s, 2, n);
from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
CHECK_ERROR;
break;
case DCH_TZM:
/* assign positive timezone sign if TZH was not seen before */
if (!out->tzsign)
out->tzsign = +1;
from_char_parse_int_len(&out->tzm, &s, 2, n);
from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
CHECK_ERROR;
break;
case DCH_A_D:
case DCH_B_C:
case DCH_a_d:
case DCH_b_c:
from_char_seq_search(&value, &s, adbc_strings_long,
ALL_UPPER, n->key->len, n);
from_char_set_int(&out->bc, value % 2, n);
ALL_UPPER, n->key->len, n, have_error);
CHECK_ERROR;
from_char_set_int(&out->bc, value % 2, n, have_error);
CHECK_ERROR;
break;
case DCH_AD:
case DCH_BC:
case DCH_ad:
case DCH_bc:
from_char_seq_search(&value, &s, adbc_strings,
ALL_UPPER, n->key->len, n);
from_char_set_int(&out->bc, value % 2, n);
ALL_UPPER, n->key->len, n, have_error);
CHECK_ERROR;
from_char_set_int(&out->bc, value % 2, n, have_error);
CHECK_ERROR;
break;
case DCH_MONTH:
case DCH_Month:
case DCH_month:
from_char_seq_search(&value, &s, months_full, ONE_UPPER,
MAX_MONTH_LEN, n);
from_char_set_int(&out->mm, value + 1, n);
MAX_MONTH_LEN, n, have_error);
CHECK_ERROR;
from_char_set_int(&out->mm, value + 1, n, have_error);
CHECK_ERROR;
break;
case DCH_MON:
case DCH_Mon:
case DCH_mon:
from_char_seq_search(&value, &s, months, ONE_UPPER,
MAX_MON_LEN, n);
from_char_set_int(&out->mm, value + 1, n);
MAX_MON_LEN, n, have_error);
CHECK_ERROR;
from_char_set_int(&out->mm, value + 1, n, have_error);
CHECK_ERROR;
break;
case DCH_MM:
from_char_parse_int(&out->mm, &s, n);
from_char_parse_int(&out->mm, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_DAY:
case DCH_Day:
case DCH_day:
from_char_seq_search(&value, &s, days, ONE_UPPER,
MAX_DAY_LEN, n);
from_char_set_int(&out->d, value, n);
MAX_DAY_LEN, n, have_error);
CHECK_ERROR;
from_char_set_int(&out->d, value, n, have_error);
CHECK_ERROR;
out->d++;
break;
case DCH_DY:
case DCH_Dy:
case DCH_dy:
from_char_seq_search(&value, &s, days, ONE_UPPER,
MAX_DY_LEN, n);
from_char_set_int(&out->d, value, n);
MAX_DY_LEN, n, have_error);
CHECK_ERROR;
from_char_set_int(&out->d, value, n, have_error);
CHECK_ERROR;
out->d++;
break;
case DCH_DDD:
from_char_parse_int(&out->ddd, &s, n);
from_char_parse_int(&out->ddd, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_IDDD:
from_char_parse_int_len(&out->ddd, &s, 3, n);
from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_DD:
from_char_parse_int(&out->dd, &s, n);
from_char_parse_int(&out->dd, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_D:
from_char_parse_int(&out->d, &s, n);
from_char_parse_int(&out->d, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_ID:
from_char_parse_int_len(&out->d, &s, 1, n);
from_char_parse_int_len(&out->d, &s, 1, n, have_error);
CHECK_ERROR;
/* Shift numbering to match Gregorian where Sunday = 1 */
if (++out->d > 7)
out->d = 1;
@ -3383,7 +3491,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
break;
case DCH_WW:
case DCH_IW:
from_char_parse_int(&out->ww, &s, n);
from_char_parse_int(&out->ww, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_Q:
@ -3398,11 +3507,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
* We still parse the source string for an integer, but it
* isn't stored anywhere in 'out'.
*/
from_char_parse_int((int *) NULL, &s, n);
from_char_parse_int((int *) NULL, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_CC:
from_char_parse_int(&out->cc, &s, n);
from_char_parse_int(&out->cc, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_Y_YYY:
@ -3414,11 +3525,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
if (matched < 2)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input string for \"Y,YYY\"")));
errmsg("invalid input string for \"Y,YYY\""))));
years += (millennia * 1000);
from_char_set_int(&out->year, years, n);
from_char_set_int(&out->year, years, n, have_error);
CHECK_ERROR;
out->yysz = 4;
s += nch;
SKIP_THth(s, n->suffix);
@ -3426,47 +3538,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
break;
case DCH_YYYY:
case DCH_IYYY:
from_char_parse_int(&out->year, &s, n);
from_char_parse_int(&out->year, &s, n, have_error);
CHECK_ERROR;
out->yysz = 4;
SKIP_THth(s, n->suffix);
break;
case DCH_YYY:
case DCH_IYY:
if (from_char_parse_int(&out->year, &s, n) < 4)
len = from_char_parse_int(&out->year, &s, n, have_error);
CHECK_ERROR;
if (len < 4)
out->year = adjust_partial_year_to_2020(out->year);
out->yysz = 3;
SKIP_THth(s, n->suffix);
break;
case DCH_YY:
case DCH_IY:
if (from_char_parse_int(&out->year, &s, n) < 4)
len = from_char_parse_int(&out->year, &s, n, have_error);
CHECK_ERROR;
if (len < 4)
out->year = adjust_partial_year_to_2020(out->year);
out->yysz = 2;
SKIP_THth(s, n->suffix);
break;
case DCH_Y:
case DCH_I:
if (from_char_parse_int(&out->year, &s, n) < 4)
len = from_char_parse_int(&out->year, &s, n, have_error);
CHECK_ERROR;
if (len < 4)
out->year = adjust_partial_year_to_2020(out->year);
out->yysz = 1;
SKIP_THth(s, n->suffix);
break;
case DCH_RM:
from_char_seq_search(&value, &s, rm_months_upper,
ALL_UPPER, MAX_RM_LEN, n);
from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
ALL_UPPER, MAX_RM_LEN, n, have_error);
CHECK_ERROR;
from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
n, have_error);
CHECK_ERROR;
break;
case DCH_rm:
from_char_seq_search(&value, &s, rm_months_lower,
ALL_LOWER, MAX_RM_LEN, n);
from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
ALL_LOWER, MAX_RM_LEN, n, have_error);
CHECK_ERROR;
from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
n, have_error);
CHECK_ERROR;
break;
case DCH_W:
from_char_parse_int(&out->w, &s, n);
from_char_parse_int(&out->w, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
case DCH_J:
from_char_parse_int(&out->j, &s, n);
from_char_parse_int(&out->j, &s, n, have_error);
CHECK_ERROR;
SKIP_THth(s, n->suffix);
break;
}
@ -3490,19 +3617,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
if (std)
{
if (n->type != NODE_TYPE_END)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("input string is too short for datetime format")));
errmsg("input string is too short for datetime format"))));
while (*s != '\0' && isspace((unsigned char) *s))
s++;
if (*s != '\0')
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("trailing characters remain in input string after "
"datetime format")));
errmsg("trailing characters remain in input string "
"after datetime format"))));
}
on_error:
return;
}
/*
@ -3523,9 +3653,13 @@ DCH_prevent_counter_overflow(void)
}
}
/* Get mask of date/time/zone components present in format nodes. */
/*
* Get mask of date/time/zone components present in format nodes.
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
*/
static int
DCH_datetime_type(FormatNode *node)
DCH_datetime_type(FormatNode *node, bool *have_error)
{
FormatNode *n;
int flags = 0;
@ -3566,10 +3700,10 @@ DCH_datetime_type(FormatNode *node)
case DCH_tz:
case DCH_TZ:
case DCH_OF:
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("formatting field \"%s\" is only supported in to_char",
n->key->name)));
n->key->name))));
flags |= DCH_ZONED;
break;
case DCH_TZH:
@ -3623,6 +3757,7 @@ DCH_datetime_type(FormatNode *node)
}
}
on_error:
return flags;
}
@ -3917,7 +4052,7 @@ to_timestamp(PG_FUNCTION_ARGS)
fsec_t fsec;
int fprec;
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@ -3956,7 +4091,7 @@ to_date(PG_FUNCTION_ARGS)
struct pg_tm tm;
fsec_t fsec;
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@ -3983,17 +4118,21 @@ to_date(PG_FUNCTION_ARGS)
* the presence of date/time/zone components in the format string.
*
* When timezone component is present, the corresponding offset is set to '*tz'.
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
* and zero value is returned.
*/
Datum
parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
int32 *typmod, int *tz)
int32 *typmod, int *tz, bool *have_error)
{
struct pg_tm tm;
fsec_t fsec;
int fprec = 0;
uint32 flags;
do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
CHECK_ERROR;
*typmod = fprec ? fprec : -1; /* fractional part precision */
@ -4021,15 +4160,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
*/
Assert(!strict);
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("missing time zone in input string for type timestamptz")));
errmsg("missing time zone in input string for type timestamptz"))));
}
if (tm2timestamp(&tm, fsec, tz, &result) != 0)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamptz out of range")));
errmsg("timestamptz out of range"))));
AdjustTimestampForTypmod(&result, *typmod);
@ -4041,9 +4180,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
Timestamp result;
if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
errmsg("timestamp out of range"))));
AdjustTimestampForTypmod(&result, *typmod);
@ -4055,9 +4194,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
{
if (flags & DCH_ZONED)
{
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("datetime format is zoned but not timed")));
errmsg("datetime format is zoned but not timed"))));
}
else
{
@ -4065,20 +4204,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"",
text_to_cstring(date_txt))));
text_to_cstring(date_txt)))));
result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
POSTGRES_EPOCH_JDATE;
/* Now check for just-out-of-range dates */
if (!IS_VALID_DATE(result))
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"",
text_to_cstring(date_txt))));
text_to_cstring(date_txt)))));
*typid = DATEOID;
return DateADTGetDatum(result);
@ -4096,7 +4235,7 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
int dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
if (dterr)
DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
}
else
{
@ -4107,15 +4246,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
*/
Assert(!strict);
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("missing time zone in input string for type timetz")));
errmsg("missing time zone in input string for type timetz"))));
}
if (tm2timetz(&tm, fsec, *tz, result) != 0)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timetz out of range")));
errmsg("timetz out of range"))));
AdjustTimeForTypmod(&result->time, *typmod);
@ -4127,9 +4266,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
TimeADT result;
if (tm2time(&tm, fsec, &result) != 0)
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("time out of range")));
errmsg("time out of range"))));
AdjustTimeForTypmod(&result, *typmod);
@ -4139,11 +4278,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
}
else
{
ereport(ERROR,
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("datetime format is not dated and not timed")));
errmsg("datetime format is not dated and not timed"))));
}
on_error:
return (Datum) 0;
}
@ -4159,17 +4299,24 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
*
* The TmFromChar is then analysed and converted into the final results in
* struct 'tm' and 'fsec'.
*
* Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
*
* 'std' specifies standard parsing mode.
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
*/
static void
do_to_timestamp(text *date_txt, text *fmt, bool std,
struct pg_tm *tm, fsec_t *fsec, int *fprec,
uint32 *flags)
uint32 *flags, bool *have_error)
{
FormatNode *format;
FormatNode *format = NULL;
TmFromChar tmfc;
int fmt_len;
char *date_str;
int fmask;
bool incache = false;
date_str = text_to_cstring(date_txt);
@ -4183,7 +4330,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
if (fmt_len)
{
char *fmt_str;
bool incache;
fmt_str = text_to_cstring(fmt);
@ -4193,8 +4339,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
* Allocate new memory if format picture is bigger than static
* cache and do not use cache (call parser always)
*/
incache = false;
format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
@ -4216,15 +4360,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
/* dump_index(DCH_keywords, DCH_index); */
#endif
DCH_from_char(format, date_str, &tmfc, std);
DCH_from_char(format, date_str, &tmfc, std, have_error);
CHECK_ERROR;
pfree(fmt_str);
if (flags)
*flags = DCH_datetime_type(format);
*flags = DCH_datetime_type(format, have_error);
if (!incache)
{
pfree(format);
format = NULL;
}
CHECK_ERROR;
}
DEBUG_TMFC(&tmfc);
@ -4253,11 +4403,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
if (tmfc.clock == CLOCK_12_HOUR)
{
if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
ereport(ERROR,
{
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("hour \"%d\" is invalid for the 12-hour clock",
tm->tm_hour),
errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
}
if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
tm->tm_hour += HOURS_PER_DAY / 2;
@ -4361,9 +4513,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
*/
if (!tm->tm_year && !tmfc.bc)
ereport(ERROR,
{
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("cannot calculate day of year without year information")));
errmsg("cannot calculate day of year without year information"))));
}
if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
{
@ -4420,7 +4574,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
* said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
* irrelevant hint about datestyle.
*/
DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
}
}
@ -4429,7 +4583,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
{
RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
}
/* Save parsed time-zone into tm->tm_zone if it was specified */
if (tmfc.tzsign)
@ -4438,7 +4594,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
{
RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
}
tz = psprintf("%c%02d:%02d",
tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@ -4448,6 +4606,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
DEBUG_TM(tm);
on_error:
if (format && !incache)
pfree(format);
pfree(date_str);
}

View File

@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
}
/*
* AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
* AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
* Works for either timestamp or timestamptz.
*/
void
AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
bool
AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
{
static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
INT64CONST(1000000),
@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
{
if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
{
if (error)
{
*error = true;
return false;
}
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("timestamp(%d) precision must be between %d and %d",
typmod, 0, MAX_TIMESTAMP_PRECISION)));
}
if (*time >= INT64CONST(0))
{
@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
* TimestampScales[typmod]);
}
}
return true;
}
void
AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
{
(void) AdjustTimestampForTypmodError(time, typmod, NULL);
}
/* timestamptz_in()
* Convert a string to internal form.
@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
}
static TimestampTz
timestamp2timestamptz(Timestamp timestamp)
/*
* Convert timestamp to timestamp with time zone.
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
* and zero is returned.
*/
TimestampTz
timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
{
TimestampTz result;
struct pg_tm tt,
@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
int tz;
if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
return timestamp;
if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
{
tz = DetermineTimeZoneOffset(tm, session_timezone);
if (tm2timestamp(tm, fsec, &tz, &result) != 0)
if (!tm2timestamp(tm, fsec, &tz, &result))
return result;
}
if (have_error)
*have_error = true;
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
return result;
return 0;
}
/*
* Single-argument version of timestamp2timestamptz_opt_error().
*/
static TimestampTz
timestamp2timestamptz(Timestamp timestamp)
{
return timestamp2timestamptz_opt_error(timestamp, NULL);
}
/* timestamptz_timestamp()

View File

@ -70,6 +70,8 @@ typedef struct
/* date.c */
extern int32 anytime_typmod_check(bool istz, int32 typmod);
extern double date2timestamp_no_overflow(DateADT dateVal);
extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
extern void EncodeSpecialDate(DateADT dt, char *str);
extern DateADT GetSQLCurrentDate(void);
extern TimeTzADT *GetSQLCurrentTime(int32 typmod);

View File

@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
bool *error);
#endif /* DATETIME_H */

View File

@ -27,6 +27,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
extern char *asc_initcap(const char *buff, size_t nbytes);
extern Datum parse_datetime(text *date_txt, text *fmt, bool std,
Oid *typid, int32 *typmod, int *tz);
Oid *typid, int32 *typmod, int *tz,
bool *have_error);
#endif

View File

@ -97,6 +97,9 @@ 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_error(Timestamp timestamp,
bool *have_error);
extern int isoweek2j(int year, int week);
extern void isoweek2date(int woy, int *year, int *mon, int *mday);
extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);