Commit 5bc45062 authored by Alexander Korotkov's avatar Alexander Korotkov

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
parent 66c74f8b
...@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS) ...@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
PG_RETURN_DATEADT(result); PG_RETURN_DATEADT(result);
} }
/* /*
* Internal routines for promoting date to timestamp and timestamp with * Promote date to timestamp.
* time zone *
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
* and zero is returned.
*/ */
Timestamp
static Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error)
date2timestamp(DateADT dateVal)
{ {
Timestamp result; Timestamp result;
...@@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal) ...@@ -572,9 +574,19 @@ date2timestamp(DateADT dateVal)
* boundary need be checked for overflow. * boundary need be checked for overflow.
*/ */
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE)) if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
ereport(ERROR, {
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), if (have_error)
errmsg("date out of range for timestamp"))); {
*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... */ /* date is days since 2000, timestamp is microseconds since same... */
result = dateVal * USECS_PER_DAY; result = dateVal * USECS_PER_DAY;
...@@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal) ...@@ -583,8 +595,23 @@ date2timestamp(DateADT dateVal)
return result; return result;
} }
/*
* Single-argument version of date2timestamp_opt_error().
*/
static TimestampTz 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; TimestampTz result;
struct pg_tm tt, struct pg_tm tt,
...@@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal) ...@@ -603,9 +630,19 @@ date2timestamptz(DateADT dateVal)
* boundary need be checked for overflow. * boundary need be checked for overflow.
*/ */
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE)) if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
ereport(ERROR, {
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), if (have_error)
errmsg("date out of range for timestamp"))); {
*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, j2date(dateVal + POSTGRES_EPOCH_JDATE,
&(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
...@@ -621,14 +658,33 @@ date2timestamptz(DateADT dateVal) ...@@ -621,14 +658,33 @@ date2timestamptz(DateADT dateVal)
* of time zone, check for allowed timestamp range after adding tz. * of time zone, check for allowed timestamp range after adding tz.
*/ */
if (!IS_VALID_TIMESTAMP(result)) if (!IS_VALID_TIMESTAMP(result))
ereport(ERROR, {
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), if (have_error)
errmsg("date out of range for timestamp"))); {
*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; return result;
} }
/*
* Single-argument version of date2timestamptz_opt_error().
*/
static TimestampTz
date2timestamptz(DateADT dateVal)
{
return date2timestamptz_opt_error(dateVal, NULL);
}
/* /*
* date2timestamp_no_overflow * date2timestamp_no_overflow
* *
......
...@@ -98,6 +98,43 @@ ...@@ -98,6 +98,43 @@
#include "utils/numeric.h" #include "utils/numeric.h"
#include "utils/pg_locale.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 * Routines flags
* ---------- * ----------
...@@ -1012,7 +1049,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw, ...@@ -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, static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid); TmToChar *in, char *out, Oid collid);
static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out, static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
bool std); bool std, bool *have_error);
#ifdef DEBUG_TO_FROM_CHAR #ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index); static void dump_index(const KeyWord *k, const int *index);
...@@ -1023,15 +1060,21 @@ static const char *get_th(char *num, int type); ...@@ -1023,15 +1060,21 @@ static const char *get_th(char *num, int type);
static char *str_numth(char *dest, 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 adjust_partial_year_to_2020(int year);
static int strspace_len(char *str); static int strspace_len(char *str);
static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode); 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); bool *have_error);
static int from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node); static void from_char_set_int(int *dest, const int value, const FormatNode *node,
static int from_char_parse_int(int *dest, char **src, 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 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, static void do_to_timestamp(text *date_txt, text *fmt, bool std,
struct pg_tm *tm, fsec_t *fsec, int *fprec, 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 char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree); static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
static char *int_to_roman(int number); static char *int_to_roman(int number);
...@@ -2233,21 +2276,26 @@ strspace_len(char *str) ...@@ -2233,21 +2276,26 @@ strspace_len(char *str)
* *
* Puke if the date mode has already been set, and the caller attempts to set * Puke if the date mode has already been set, and the caller attempts to set
* it to a conflicting mode. * it to a conflicting mode.
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
*/ */
static void 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 (mode != FROM_CHAR_DATE_NONE)
{ {
if (tmfc->mode == FROM_CHAR_DATE_NONE) if (tmfc->mode == FROM_CHAR_DATE_NONE)
tmfc->mode = mode; tmfc->mode = mode;
else if (tmfc->mode != mode) else if (tmfc->mode != mode)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid combination of date conventions"), errmsg("invalid combination of date conventions"),
errhint("Do not mix Gregorian and ISO week date " 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) ...@@ -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 * Puke if the destination integer has previously been set to some other
* non-zero value. * non-zero value.
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
*/ */
static void 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) if (*dest != 0 && *dest != value)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("conflicting values for \"%s\" field in formatting string", errmsg("conflicting values for \"%s\" field in "
node->key->name), "formatting string",
errdetail("This value contradicts a previous setting for " node->key->name),
"the same field type."))); errdetail("This value contradicts a previous setting "
"for the same field type."))));
*dest = value; *dest = value;
on_error:
return;
} }
/* /*
...@@ -2288,9 +2343,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node) ...@@ -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 * 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 * the length of the field is the same as the length of the format keyword (as
* with DD and MI). * with DD and MI).
*
* If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
* and -1 is returned.
*/ */
static int 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; long result;
char copy[DCH_MAX_ITEM_SIZ + 1]; 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) ...@@ -2323,51 +2382,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
char *last; char *last;
if (used < len) if (used < len)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("source string too short for \"%s\" formatting field", errmsg("source string too short for \"%s\" "
node->key->name), "formatting field",
errdetail("Field requires %d characters, but only %d " node->key->name),
"remain.", errdetail("Field requires %d characters, "
len, used), "but only %d remain.",
errhint("If your source string is not fixed-width, try " len, used),
"using the \"FM\" modifier."))); errhint("If your source string is not fixed-width, "
"try using the \"FM\" modifier."))));
errno = 0; errno = 0;
result = strtol(copy, &last, 10); result = strtol(copy, &last, 10);
used = last - copy; used = last - copy;
if (used > 0 && used < len) if (used > 0 && used < len)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"", errmsg("invalid value \"%s\" for \"%s\"",
copy, node->key->name), copy, node->key->name),
errdetail("Field requires %d characters, but only %d " errdetail("Field requires %d characters, "
"could be parsed.", len, used), "but only %d could be parsed.",
errhint("If your source string is not fixed-width, try " len, used),
"using the \"FM\" modifier."))); errhint("If your source string is not fixed-width, "
"try using the \"FM\" modifier."))));
*src += used; *src += used;
} }
if (*src == init) if (*src == init)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"", errmsg("invalid value \"%s\" for \"%s\"",
copy, node->key->name), 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) if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("value for \"%s\" in source string is out of range", errmsg("value for \"%s\" in source string is out of range",
node->key->name), node->key->name),
errdetail("Value must be in the range %d to %d.", errdetail("Value must be in the range %d to %d.",
INT_MIN, INT_MAX))); INT_MIN, INT_MAX))));
if (dest != NULL) 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; 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) ...@@ -2380,9 +2448,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
* required length explicitly. * required length explicitly.
*/ */
static int 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) ...@@ -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 * pointed to by 'dest', advance 'src' to the end of the part of the string
* which matched, and return the number of characters consumed. * 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 static int
from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, from_char_seq_search(int *dest, char **src, const char *const *array, int type,
FormatNode *node) int max, FormatNode *node, bool *have_error)
{ {
int len; int len;
...@@ -2481,15 +2550,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type, ...@@ -2481,15 +2550,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
Assert(max <= DCH_MAX_ITEM_SIZ); Assert(max <= DCH_MAX_ITEM_SIZ);
strlcpy(copy, *src, max + 1); strlcpy(copy, *src, max + 1);
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid value \"%s\" for \"%s\"", errmsg("invalid value \"%s\" for \"%s\"",
copy, node->key->name), copy, node->key->name),
errdetail("The given value did not match any of the allowed " errdetail("The given value did not match any of "
"values for this field."))); "the allowed values for this field."))));
} }
*src += len; *src += len;
return 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 ...@@ -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 * Note: we currently don't have any to_interval() function, so there
* is no need here for INVALID_FOR_INTERVAL checks. * 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 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; FormatNode *n;
char *s; char *s;
...@@ -3132,10 +3207,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std) ...@@ -3132,10 +3207,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
if (*s == n->character[0]) if (*s == n->character[0])
s++; s++;
else else
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("unmatched format separator \"%c\"", errmsg("unmatched format separator \"%c\"",
n->character[0]))); n->character[0]))));
} }
else if (!fx_mode) else if (!fx_mode)
{ {
...@@ -3191,7 +3266,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std) ...@@ -3191,7 +3266,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
continue; 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) switch (n->key->id)
{ {
...@@ -3203,8 +3279,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std) ...@@ -3203,8 +3279,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
case DCH_a_m: case DCH_a_m:
case DCH_p_m: case DCH_p_m:
from_char_seq_search(&value, &s, ampm_strings_long, from_char_seq_search(&value, &s, ampm_strings_long,
ALL_UPPER, n->key->len, n); ALL_UPPER, n->key->len, n, have_error);
from_char_set_int(&out->pm, value % 2, n); CHECK_ERROR;
from_char_set_int(&out->pm, value % 2, n, have_error);
CHECK_ERROR;
out->clock = CLOCK_12_HOUR; out->clock = CLOCK_12_HOUR;
break; break;
case DCH_AM: case DCH_AM:
...@@ -3212,30 +3290,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std) ...@@ -3212,30 +3290,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
case DCH_am: case DCH_am:
case DCH_pm: case DCH_pm:
from_char_seq_search(&value, &s, ampm_strings, from_char_seq_search(&value, &s, ampm_strings,
ALL_UPPER, n->key->len, n); ALL_UPPER, n->key->len, n, have_error);
from_char_set_int(&out->pm, value % 2, n); CHECK_ERROR;
from_char_set_int(&out->pm, value % 2, n, have_error);
CHECK_ERROR;
out->clock = CLOCK_12_HOUR; out->clock = CLOCK_12_HOUR;
break; break;
case DCH_HH: case DCH_HH:
case DCH_HH12: 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; out->clock = CLOCK_12_HOUR;
SKIP_THth(s, n->suffix); SKIP_THth(s, n->suffix);
break; break;
case DCH_HH24: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_MI: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_SS: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_MS: /* millisecond */ 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 * 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) ...@@ -3256,7 +3341,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
case DCH_US: /* microsecond */ case DCH_US: /* microsecond */
len = from_char_parse_int_len(&out->us, &s, len = from_char_parse_int_len(&out->us, &s,
n->key->id == DCH_US ? 6 : n->key->id == DCH_US ? 6 :
out->ff, n); out->ff, n, have_error);
CHECK_ERROR;
out->us *= len == 1 ? 100000 : out->us *= len == 1 ? 100000 :
len == 2 ? 10000 : len == 2 ? 10000 :
...@@ -3267,16 +3353,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std) ...@@ -3267,16 +3353,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
SKIP_THth(s, n->suffix); SKIP_THth(s, n->suffix);
break; break;
case DCH_SSSS: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_tz: case DCH_tz:
case DCH_TZ: case DCH_TZ:
case DCH_OF: case DCH_OF:
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("formatting field \"%s\" is only supported in to_char", errmsg("formatting field \"%s\" is only supported in to_char",
n->key->name))); n->key->name))));
CHECK_ERROR;
break; break;
case DCH_TZH: case DCH_TZH:
...@@ -3300,82 +3388,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std) ...@@ -3300,82 +3388,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
out->tzsign = +1; 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; break;
case DCH_TZM: case DCH_TZM:
/* assign positive timezone sign if TZH was not seen before */ /* assign positive timezone sign if TZH was not seen before */
if (!out->tzsign) if (!out->tzsign)
out->tzsign = +1; 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; break;
case DCH_A_D: case DCH_A_D:
case DCH_B_C: case DCH_B_C:
case DCH_a_d: case DCH_a_d:
case DCH_b_c: case DCH_b_c:
from_char_seq_search(&value, &s, adbc_strings_long, from_char_seq_search(&value, &s, adbc_strings_long,
ALL_UPPER, n->key->len, n); ALL_UPPER, n->key->len, n, have_error);
from_char_set_int(&out->bc, value % 2, n); CHECK_ERROR;
from_char_set_int(&out->bc, value % 2, n, have_error);
CHECK_ERROR;
break; break;
case DCH_AD: case DCH_AD:
case DCH_BC: case DCH_BC:
case DCH_ad: case DCH_ad:
case DCH_bc: case DCH_bc:
from_char_seq_search(&value, &s, adbc_strings, from_char_seq_search(&value, &s, adbc_strings,
ALL_UPPER, n->key->len, n); ALL_UPPER, n->key->len, n, have_error);
from_char_set_int(&out->bc, value % 2, n); CHECK_ERROR;
from_char_set_int(&out->bc, value % 2, n, have_error);
CHECK_ERROR;
break; break;
case DCH_MONTH: case DCH_MONTH:
case DCH_Month: case DCH_Month:
case DCH_month: case DCH_month:
from_char_seq_search(&value, &s, months_full, ONE_UPPER, from_char_seq_search(&value, &s, months_full, ONE_UPPER,
MAX_MONTH_LEN, n); MAX_MONTH_LEN, n, have_error);
from_char_set_int(&out->mm, value + 1, n); CHECK_ERROR;
from_char_set_int(&out->mm, value + 1, n, have_error);
CHECK_ERROR;
break; break;
case DCH_MON: case DCH_MON:
case DCH_Mon: case DCH_Mon:
case DCH_mon: case DCH_mon:
from_char_seq_search(&value, &s, months, ONE_UPPER, from_char_seq_search(&value, &s, months, ONE_UPPER,
MAX_MON_LEN, n); MAX_MON_LEN, n, have_error);
from_char_set_int(&out->mm, value + 1, n); CHECK_ERROR;
from_char_set_int(&out->mm, value + 1, n, have_error);
CHECK_ERROR;
break; break;
case DCH_MM: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_DAY: case DCH_DAY:
case DCH_Day: case DCH_Day:
case DCH_day: case DCH_day:
from_char_seq_search(&value, &s, days, ONE_UPPER, from_char_seq_search(&value, &s, days, ONE_UPPER,
MAX_DAY_LEN, n); MAX_DAY_LEN, n, have_error);
from_char_set_int(&out->d, value, n); CHECK_ERROR;
from_char_set_int(&out->d, value, n, have_error);
CHECK_ERROR;
out->d++; out->d++;
break; break;
case DCH_DY: case DCH_DY:
case DCH_Dy: case DCH_Dy:
case DCH_dy: case DCH_dy:
from_char_seq_search(&value, &s, days, ONE_UPPER, from_char_seq_search(&value, &s, days, ONE_UPPER,
MAX_DY_LEN, n); MAX_DY_LEN, n, have_error);
from_char_set_int(&out->d, value, n); CHECK_ERROR;
from_char_set_int(&out->d, value, n, have_error);
CHECK_ERROR;
out->d++; out->d++;
break; break;
case DCH_DDD: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_IDDD: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_DD: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_D: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_ID: 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 */ /* Shift numbering to match Gregorian where Sunday = 1 */
if (++out->d > 7) if (++out->d > 7)
out->d = 1; out->d = 1;
...@@ -3383,7 +3491,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std) ...@@ -3383,7 +3491,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
break; break;
case DCH_WW: case DCH_WW:
case DCH_IW: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_Q: case DCH_Q:
...@@ -3398,11 +3507,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std) ...@@ -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 * We still parse the source string for an integer, but it
* isn't stored anywhere in 'out'. * 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_CC: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_Y_YYY: case DCH_Y_YYY:
...@@ -3414,11 +3525,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std) ...@@ -3414,11 +3525,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch); matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
if (matched < 2) if (matched < 2)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input string for \"Y,YYY\""))); errmsg("invalid input string for \"Y,YYY\""))));
years += (millennia * 1000); 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; out->yysz = 4;
s += nch; s += nch;
SKIP_THth(s, n->suffix); SKIP_THth(s, n->suffix);
...@@ -3426,47 +3538,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std) ...@@ -3426,47 +3538,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
break; break;
case DCH_YYYY: case DCH_YYYY:
case DCH_IYYY: 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; out->yysz = 4;
SKIP_THth(s, n->suffix); SKIP_THth(s, n->suffix);
break; break;
case DCH_YYY: case DCH_YYY:
case DCH_IYY: 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->year = adjust_partial_year_to_2020(out->year);
out->yysz = 3; out->yysz = 3;
SKIP_THth(s, n->suffix); SKIP_THth(s, n->suffix);
break; break;
case DCH_YY: case DCH_YY:
case DCH_IY: 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->year = adjust_partial_year_to_2020(out->year);
out->yysz = 2; out->yysz = 2;
SKIP_THth(s, n->suffix); SKIP_THth(s, n->suffix);
break; break;
case DCH_Y: case DCH_Y:
case DCH_I: 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->year = adjust_partial_year_to_2020(out->year);
out->yysz = 1; out->yysz = 1;
SKIP_THth(s, n->suffix); SKIP_THth(s, n->suffix);
break; break;
case DCH_RM: case DCH_RM:
from_char_seq_search(&value, &s, rm_months_upper, from_char_seq_search(&value, &s, rm_months_upper,
ALL_UPPER, MAX_RM_LEN, n); ALL_UPPER, MAX_RM_LEN, n, have_error);
from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n); CHECK_ERROR;
from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
n, have_error);
CHECK_ERROR;
break; break;
case DCH_rm: case DCH_rm:
from_char_seq_search(&value, &s, rm_months_lower, from_char_seq_search(&value, &s, rm_months_lower,
ALL_LOWER, MAX_RM_LEN, n); ALL_LOWER, MAX_RM_LEN, n, have_error);
from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n); CHECK_ERROR;
from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
n, have_error);
CHECK_ERROR;
break; break;
case DCH_W: 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); SKIP_THth(s, n->suffix);
break; break;
case DCH_J: 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); SKIP_THth(s, n->suffix);
break; break;
} }
...@@ -3490,19 +3617,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std) ...@@ -3490,19 +3617,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
if (std) if (std)
{ {
if (n->type != NODE_TYPE_END) if (n->type != NODE_TYPE_END)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (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)) while (*s != '\0' && isspace((unsigned char) *s))
s++; s++;
if (*s != '\0') if (*s != '\0')
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("trailing characters remain in input string after " errmsg("trailing characters remain in input string "
"datetime format"))); "after datetime format"))));
} }
on_error:
return;
} }
/* /*
...@@ -3523,9 +3653,13 @@ DCH_prevent_counter_overflow(void) ...@@ -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 static int
DCH_datetime_type(FormatNode *node) DCH_datetime_type(FormatNode *node, bool *have_error)
{ {
FormatNode *n; FormatNode *n;
int flags = 0; int flags = 0;
...@@ -3566,10 +3700,10 @@ DCH_datetime_type(FormatNode *node) ...@@ -3566,10 +3700,10 @@ DCH_datetime_type(FormatNode *node)
case DCH_tz: case DCH_tz:
case DCH_TZ: case DCH_TZ:
case DCH_OF: case DCH_OF:
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("formatting field \"%s\" is only supported in to_char", errmsg("formatting field \"%s\" is only supported in to_char",
n->key->name))); n->key->name))));
flags |= DCH_ZONED; flags |= DCH_ZONED;
break; break;
case DCH_TZH: case DCH_TZH:
...@@ -3623,6 +3757,7 @@ DCH_datetime_type(FormatNode *node) ...@@ -3623,6 +3757,7 @@ DCH_datetime_type(FormatNode *node)
} }
} }
on_error:
return flags; return flags;
} }
...@@ -3917,7 +4052,7 @@ to_timestamp(PG_FUNCTION_ARGS) ...@@ -3917,7 +4052,7 @@ to_timestamp(PG_FUNCTION_ARGS)
fsec_t fsec; fsec_t fsec;
int fprec; 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. */ /* Use the specified time zone, if any. */
if (tm.tm_zone) if (tm.tm_zone)
...@@ -3956,7 +4091,7 @@ to_date(PG_FUNCTION_ARGS) ...@@ -3956,7 +4091,7 @@ to_date(PG_FUNCTION_ARGS)
struct pg_tm tm; struct pg_tm tm;
fsec_t fsec; 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 */ /* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
...@@ -3983,17 +4118,21 @@ to_date(PG_FUNCTION_ARGS) ...@@ -3983,17 +4118,21 @@ to_date(PG_FUNCTION_ARGS)
* the presence of date/time/zone components in the format string. * the presence of date/time/zone components in the format string.
* *
* When timezone component is present, the corresponding offset is set to '*tz'. * 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 Datum
parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, 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; struct pg_tm tm;
fsec_t fsec; fsec_t fsec;
int fprec = 0; int fprec = 0;
uint32 flags; 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 */ *typmod = fprec ? fprec : -1; /* fractional part precision */
...@@ -4021,15 +4160,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, ...@@ -4021,15 +4160,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
*/ */
Assert(!strict); Assert(!strict);
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (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) if (tm2timestamp(&tm, fsec, tz, &result) != 0)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamptz out of range"))); errmsg("timestamptz out of range"))));
AdjustTimestampForTypmod(&result, *typmod); AdjustTimestampForTypmod(&result, *typmod);
...@@ -4041,9 +4180,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, ...@@ -4041,9 +4180,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
Timestamp result; Timestamp result;
if (tm2timestamp(&tm, fsec, NULL, &result) != 0) if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range"))); errmsg("timestamp out of range"))));
AdjustTimestampForTypmod(&result, *typmod); AdjustTimestampForTypmod(&result, *typmod);
...@@ -4055,9 +4194,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, ...@@ -4055,9 +4194,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
{ {
if (flags & DCH_ZONED) if (flags & DCH_ZONED)
{ {
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("datetime format is zoned but not timed"))); errmsg("datetime format is zoned but not timed"))));
} }
else else
{ {
...@@ -4065,20 +4204,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, ...@@ -4065,20 +4204,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
/* Prevent overflow in Julian-day routines */ /* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) 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), (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"", 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) - result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
POSTGRES_EPOCH_JDATE; POSTGRES_EPOCH_JDATE;
/* Now check for just-out-of-range dates */ /* Now check for just-out-of-range dates */
if (!IS_VALID_DATE(result)) if (!IS_VALID_DATE(result))
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("date out of range: \"%s\"", errmsg("date out of range: \"%s\"",
text_to_cstring(date_txt)))); text_to_cstring(date_txt)))));
*typid = DATEOID; *typid = DATEOID;
return DateADTGetDatum(result); return DateADTGetDatum(result);
...@@ -4096,7 +4235,7 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, ...@@ -4096,7 +4235,7 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
int dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz); int dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
if (dterr) if (dterr)
DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"); RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
} }
else else
{ {
...@@ -4107,15 +4246,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, ...@@ -4107,15 +4246,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
*/ */
Assert(!strict); Assert(!strict);
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (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) if (tm2timetz(&tm, fsec, *tz, result) != 0)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timetz out of range"))); errmsg("timetz out of range"))));
AdjustTimeForTypmod(&result->time, *typmod); AdjustTimeForTypmod(&result->time, *typmod);
...@@ -4127,9 +4266,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, ...@@ -4127,9 +4266,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
TimeADT result; TimeADT result;
if (tm2time(&tm, fsec, &result) != 0) if (tm2time(&tm, fsec, &result) != 0)
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("time out of range"))); errmsg("time out of range"))));
AdjustTimeForTypmod(&result, *typmod); AdjustTimeForTypmod(&result, *typmod);
...@@ -4139,11 +4278,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, ...@@ -4139,11 +4278,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
} }
else else
{ {
ereport(ERROR, RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), (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; return (Datum) 0;
} }
...@@ -4159,17 +4299,24 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid, ...@@ -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 * The TmFromChar is then analysed and converted into the final results in
* struct 'tm' and 'fsec'. * 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 static void
do_to_timestamp(text *date_txt, text *fmt, bool std, do_to_timestamp(text *date_txt, text *fmt, bool std,
struct pg_tm *tm, fsec_t *fsec, int *fprec, struct pg_tm *tm, fsec_t *fsec, int *fprec,
uint32 *flags) uint32 *flags, bool *have_error)
{ {
FormatNode *format; FormatNode *format = NULL;
TmFromChar tmfc; TmFromChar tmfc;
int fmt_len; int fmt_len;
char *date_str; char *date_str;
int fmask; int fmask;
bool incache = false;
date_str = text_to_cstring(date_txt); date_str = text_to_cstring(date_txt);
...@@ -4183,7 +4330,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std, ...@@ -4183,7 +4330,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
if (fmt_len) if (fmt_len)
{ {
char *fmt_str; char *fmt_str;
bool incache;
fmt_str = text_to_cstring(fmt); fmt_str = text_to_cstring(fmt);
...@@ -4193,8 +4339,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std, ...@@ -4193,8 +4339,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
* Allocate new memory if format picture is bigger than static * Allocate new memory if format picture is bigger than static
* cache and do not use cache (call parser always) * cache and do not use cache (call parser always)
*/ */
incache = false;
format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode)); format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index, 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, ...@@ -4216,15 +4360,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
/* dump_index(DCH_keywords, DCH_index); */ /* dump_index(DCH_keywords, DCH_index); */
#endif #endif
DCH_from_char(format, date_str, &tmfc, std); DCH_from_char(format, date_str, &tmfc, std, have_error);
CHECK_ERROR;
pfree(fmt_str); pfree(fmt_str);
if (flags) if (flags)
*flags = DCH_datetime_type(format); *flags = DCH_datetime_type(format, have_error);
if (!incache) if (!incache)
{
pfree(format); pfree(format);
format = NULL;
}
CHECK_ERROR;
} }
DEBUG_TMFC(&tmfc); DEBUG_TMFC(&tmfc);
...@@ -4253,11 +4403,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool std, ...@@ -4253,11 +4403,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
if (tmfc.clock == CLOCK_12_HOUR) if (tmfc.clock == CLOCK_12_HOUR)
{ {
if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2) if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
ereport(ERROR, {
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), RETURN_ERROR(ereport(ERROR,
errmsg("hour \"%d\" is invalid for the 12-hour clock", (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
tm->tm_hour), errmsg("hour \"%d\" is invalid for the 12-hour clock",
errhint("Use the 24-hour clock, or give an hour between 1 and 12."))); tm->tm_hour),
errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
}
if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2) if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
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, ...@@ -4361,9 +4513,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
*/ */
if (!tm->tm_year && !tmfc.bc) if (!tm->tm_year && !tmfc.bc)
ereport(ERROR, {
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), RETURN_ERROR(ereport(ERROR,
errmsg("cannot calculate day of year without year information"))); (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("cannot calculate day of year without year information"))));
}
if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK) if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
{ {
...@@ -4420,7 +4574,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool std, ...@@ -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 * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
* irrelevant hint about datestyle. * 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, ...@@ -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_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE || tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
*fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC) *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 */ /* Save parsed time-zone into tm->tm_zone if it was specified */
if (tmfc.tzsign) if (tmfc.tzsign)
...@@ -4438,7 +4594,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std, ...@@ -4438,7 +4594,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR || if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_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", tz = psprintf("%c%02d:%02d",
tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm); tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
...@@ -4448,6 +4606,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std, ...@@ -4448,6 +4606,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
DEBUG_TM(tm); DEBUG_TM(tm);
on_error:
if (format && !incache)
pfree(format);
pfree(date_str); pfree(date_str);
} }
......
...@@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS) ...@@ -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. * Works for either timestamp or timestamptz.
*/ */
void bool
AdjustTimestampForTypmod(Timestamp *time, int32 typmod) AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
{ {
static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = { static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
INT64CONST(1000000), INT64CONST(1000000),
...@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod) ...@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
&& (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION)) && (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
{ {
if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION) if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
{
if (error)
{
*error = true;
return false;
}
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("timestamp(%d) precision must be between %d and %d", errmsg("timestamp(%d) precision must be between %d and %d",
typmod, 0, MAX_TIMESTAMP_PRECISION))); typmod, 0, MAX_TIMESTAMP_PRECISION)));
}
if (*time >= INT64CONST(0)) if (*time >= INT64CONST(0))
{ {
...@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod) ...@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
* TimestampScales[typmod]); * TimestampScales[typmod]);
} }
} }
return true;
} }
void
AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
{
(void) AdjustTimestampForTypmodError(time, typmod, NULL);
}
/* timestamptz_in() /* timestamptz_in()
* Convert a string to internal form. * Convert a string to internal form.
...@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS) ...@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp)); 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; TimestampTz result;
struct pg_tm tt, struct pg_tm tt,
...@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp) ...@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
int tz; int tz;
if (TIMESTAMP_NOT_FINITE(timestamp)) if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp; return 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")));
if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
{
tz = DetermineTimeZoneOffset(tm, session_timezone); tz = DetermineTimeZoneOffset(tm, session_timezone);
if (tm2timestamp(tm, fsec, &tz, &result) != 0) if (!tm2timestamp(tm, fsec, &tz, &result))
ereport(ERROR, return result;
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
} }
return result; if (have_error)
*have_error = true;
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
return 0;
}
/*
* Single-argument version of timestamp2timestamptz_opt_error().
*/
static TimestampTz
timestamp2timestamptz(Timestamp timestamp)
{
return timestamp2timestamptz_opt_error(timestamp, NULL);
} }
/* timestamptz_timestamp() /* timestamptz_timestamp()
......
...@@ -70,6 +70,8 @@ typedef struct ...@@ -70,6 +70,8 @@ typedef struct
/* date.c */ /* date.c */
extern int32 anytime_typmod_check(bool istz, int32 typmod); extern int32 anytime_typmod_check(bool istz, int32 typmod);
extern double date2timestamp_no_overflow(DateADT dateVal); 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 void EncodeSpecialDate(DateADT dt, char *str);
extern DateADT GetSQLCurrentDate(void); extern DateADT GetSQLCurrentDate(void);
extern TimeTzADT *GetSQLCurrentTime(int32 typmod); extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
......
...@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, ...@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl); extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod); extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
bool *error);
#endif /* DATETIME_H */ #endif /* DATETIME_H */
...@@ -27,6 +27,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes); ...@@ -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 char *asc_initcap(const char *buff, size_t nbytes);
extern Datum parse_datetime(text *date_txt, text *fmt, bool std, 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 #endif
...@@ -97,6 +97,9 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2); ...@@ -97,6 +97,9 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
/* timestamp comparison works for timestamptz also */ /* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2) #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 int isoweek2j(int year, int week);
extern void isoweek2date(int woy, int *year, int *mon, int *mday); extern void isoweek2date(int woy, int *year, int *mon, int *mday);
extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday); extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment