0003-error-suppression-for-datetime-5.patch
text/x-patch
Filename: 0003-error-suppression-for-datetime-5.patch
Type: text/x-patch
Part: 1
Patch
Format: unified
Series: patch 0003
| File | + | − |
|---|---|---|
| src/backend/utils/adt/date.c | 71 | 15 |
| src/backend/utils/adt/formatting.c | 358 | 186 |
| src/backend/utils/adt/timestamp.c | 49 | 17 |
| src/include/utils/date.h | 2 | 0 |
| src/include/utils/datetime.h | 2 | 0 |
| src/include/utils/formatting.h | 2 | 1 |
| src/include/utils/timestamp.h | 3 | 0 |
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9e291b5..fa50d79 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -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
+ * Promote date to timestamp.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
*/
-
-static Timestamp
-date2timestamp(DateADT dateVal)
+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))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
+ {
+ 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))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
+ {
+ 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,15 +658,34 @@ date2timestamptz(DateADT dateVal)
* of time zone, check for allowed timestamp range after adding tz.
*/
if (!IS_VALID_TIMESTAMP(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range for timestamp")));
+ {
+ 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
*
* This is chartered to produce a double value that is numerically
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 9031432..a3b15b5 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -99,6 +99,43 @@
#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 type
* ----------
*/
@@ -1008,7 +1045,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 strict);
+ bool strict, bool *have_error);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -1019,14 +1056,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 strict,
- struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
+ struct pg_tm *tm, fsec_t *fsec, int *fprec,
+ int *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);
@@ -2202,21 +2246,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,
- (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.")));
+ 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."))));
}
+
+on_error:
+ return;
}
/*
@@ -2224,18 +2273,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,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("conflicting values for \"%s\" field in formatting string",
- node->key->name),
- errdetail("This value contradicts a previous setting for "
- "the same field type.")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("conflicting values for \"%s\" field in "
+ "formatting string",
+ node->key->name),
+ errdetail("This value contradicts a previous setting "
+ "for the same field type."))));
*dest = value;
+
+on_error:
+ return;
}
/*
@@ -2257,9 +2313,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];
@@ -2292,51 +2352,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
char *last;
if (used < len)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("source string too short for \"%s\" formatting field",
- node->key->name),
- 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.")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("source string too short for \"%s\" "
+ "formatting field",
+ node->key->name),
+ 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."))));
errno = 0;
result = strtol(copy, &last, 10);
used = last - copy;
if (used > 0 && used < len)
- 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.")));
+ 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."))));
*src += used;
}
if (*src == init)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("invalid value \"%s\" for \"%s\"",
- copy, node->key->name),
- errdetail("Value must be an integer.")));
+ 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."))));
if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
- 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)));
+ 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))));
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;
}
/*
@@ -2349,9 +2418,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);
}
/* ----------
@@ -2434,11 +2503,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;
@@ -2450,15 +2520,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,
- (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.")));
+ 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."))));
}
*src += len;
return len;
+
+on_error:
+ return -1;
}
/* ----------
@@ -3060,10 +3133,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 strict)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict,
+ bool *have_error)
{
FormatNode *n;
char *s;
@@ -3146,7 +3222,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
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)
{
@@ -3158,8 +3235,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
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:
@@ -3167,30 +3246,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
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
@@ -3211,7 +3297,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
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 :
@@ -3222,16 +3309,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
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,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("formatting field \"%s\" is only supported in to_char",
- n->key->name)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name))));
+ CHECK_ERROR;
break;
case DCH_TZH:
@@ -3255,82 +3344,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
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;
@@ -3338,7 +3447,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
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:
@@ -3353,11 +3463,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
* 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:
@@ -3369,11 +3481,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
if (matched < 2)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("invalid input string for \"Y,YYY\"")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ 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);
@@ -3381,47 +3494,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
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;
}
@@ -3441,19 +3569,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
if (strict)
{
if (n->type != NODE_TYPE_END)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("input string is too short for datetime format")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format"))));
while (*s != '\0' && isspace((unsigned char) *s))
s++;
if (*s != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("trailing characters remain in input string after "
- "datetime format")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string "
+ "after datetime format"))));
}
+
+on_error:
+ return;
}
/*
@@ -3474,9 +3605,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;
@@ -3517,10 +3652,10 @@ DCH_datetime_type(FormatNode *node)
case DCH_tz:
case DCH_TZ:
case DCH_OF:
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("formatting field \"%s\" is only supported in to_char",
- n->key->name)));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name))));
flags |= DCH_ZONED;
break;
case DCH_TZH:
@@ -3574,6 +3709,7 @@ DCH_datetime_type(FormatNode *node)
}
}
+on_error:
return flags;
}
@@ -3867,7 +4003,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)
@@ -3906,7 +4042,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))
@@ -3933,17 +4069,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;
int 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 */
@@ -3964,16 +4104,22 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
}
else
{
- if (*tz == PG_INT32_MIN)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("missing time zone in input string for type timestamptz")));
+ /*
+ * Time zone is present in format string, but not in input
+ * string. Assuming do_to_timestamp() triggers no error
+ * this should be possible only in non-strict case.
+ */
+ Assert(!strict);
+
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time zone in input string for type timestamptz"))));
}
if (tm2timestamp(&tm, fsec, tz, &result) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamptz out of range")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamptz out of range"))));
AdjustTimestampForTypmod(&result, *typmod);
@@ -3985,9 +4131,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
Timestamp result;
if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range"))));
AdjustTimestampForTypmod(&result, *typmod);
@@ -3999,9 +4145,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
{
if (flags & DCH_ZONED)
{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("datetime format is zoned but not timed")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is zoned but not timed"))));
}
else
{
@@ -4009,20 +4155,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,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range: \"%s\"",
- text_to_cstring(date_txt))));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ 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,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("date out of range: \"%s\"",
- text_to_cstring(date_txt))));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt)))));
*typid = DATEOID;
return DateADTGetDatum(result);
@@ -4040,20 +4186,26 @@ 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
{
- if (*tz == PG_INT32_MIN)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("missing time zone in input string for type timetz")));
+ /*
+ * Time zone is present in format string, but not in input
+ * string. Assuming do_to_timestamp() triggers no error this
+ * should be possible only in non-strict case.
+ */
+ Assert(!strict);
+
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time zone in input string for type timetz"))));
}
if (tm2timetz(&tm, fsec, *tz, result) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timetz out of range")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timetz out of range"))));
AdjustTimeForTypmod(&result->time, *typmod);
@@ -4065,9 +4217,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
TimeADT result;
if (tm2time(&tm, fsec, &result) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("time out of range")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range"))));
AdjustTimeForTypmod(&result, *typmod);
@@ -4077,11 +4229,12 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
}
else
{
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("datetime format is not dated and not timed")));
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is not dated and not timed"))));
}
+on_error:
return (Datum) 0;
}
@@ -4102,16 +4255,19 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
*
* 'strict' enables error reporting on unmatched trailing characters in input
* or format string patterns.
+ *
+ * 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 strict,
- struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
+do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm,
+ fsec_t *fsec, int *fprec, int *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);
@@ -4125,7 +4281,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
if (fmt_len)
{
char *fmt_str;
- bool incache;
fmt_str = text_to_cstring(fmt);
@@ -4135,8 +4290,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
* 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,
@@ -4158,15 +4311,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
/* dump_index(DCH_keywords, DCH_index); */
#endif
- DCH_from_char(format, date_str, &tmfc, strict);
+ DCH_from_char(format, date_str, &tmfc, strict, 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);
@@ -4195,11 +4354,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
if (tmfc.clock == CLOCK_12_HOUR)
{
if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
- 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.")));
+ {
+ 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."))));
+ }
if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
tm->tm_hour += HOURS_PER_DAY / 2;
@@ -4303,9 +4464,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
*/
if (!tm->tm_year && !tmfc.bc)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
- errmsg("cannot calculate day of year without year information")));
+ {
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("cannot calculate day of year without year information"))));
+ }
if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
{
@@ -4362,7 +4525,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
* 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"));
}
}
@@ -4371,7 +4534,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
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)
@@ -4380,7 +4545,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
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);
@@ -4390,6 +4557,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict,
DEBUG_TM(tm);
+on_error:
+
+ if (format && !incache)
+ pfree(format);
+
pfree(date_str);
}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2931bd5..84bc97d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -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)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ if (!tm2timestamp(tm, fsec, &tz, &result))
+ return 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 0;
+}
+
+/*
+ * Single-argument version of timestamp2timestamptz_opt_error().
+ */
+static TimestampTz
+timestamp2timestamptz(Timestamp timestamp)
+{
+ return timestamp2timestamptz_opt_error(timestamp, NULL);
}
/* timestamptz_timestamp()
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index bd15bfa..c29f13a 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -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);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0cafdd2..5ebf336 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -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 */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index a390302..1a2e2a9 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -29,6 +29,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 strict,
- Oid *typid, int32 *typmod, int *tz);
+ Oid *typid, int32 *typmod, int *tz,
+ bool *have_error);
#endif
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index ea16190..e884d44 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -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);