Error suppression support for upcoming jsonpath .datetime() method
authorAlexander Korotkov <akorotkov@postgresql.org>
Wed, 25 Sep 2019 18:51:47 +0000 (21:51 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Wed, 25 Sep 2019 19:51:51 +0000 (22:51 +0300)
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
src/backend/utils/adt/date.c
src/backend/utils/adt/formatting.c
src/backend/utils/adt/timestamp.c
src/include/utils/date.h
src/include/utils/datetime.h
src/include/utils/formatting.h
src/include/utils/timestamp.h

index 9e291b5c7bc2c8b0769d476a8e3c0ab4e53602bb..fa50d79c05d59d41a86f9c1bd79f04ddb601f7b1 100644 (file)
@@ -550,13 +550,15 @@ date_mii(PG_FUNCTION_ARGS)
    PG_RETURN_DATEADT(result);
 }
 
+
 /*
- * Internal routines for promoting date to timestamp and timestamp with
- * time zone
+ * 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,14 +658,33 @@ 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
  *
index 462c333544cf026aa9b15d040cfa4cd22cd9a990..f7175df8da6ac502215c7acd651cf48a8f31e384 100644 (file)
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+/* ----------
+ * Convenience macros for error handling
+ * ----------
+ *
+ * Two macros below help to handle errors in functions that take
+ * 'bool *have_error' argument.  When this argument is not NULL, it's expected
+ * that function will suppress ereports when possible.  Instead it should
+ * return some default value and set *have_error flag.
+ *
+ * RETURN_ERROR() macro intended to wrap ereport() calls.  When have_error
+ * function argument is not NULL, then instead of ereport'ing we set
+ * *have_error flag and go to on_error label.  It's supposed that jump
+ * resources will be freed and some 'default' value returned.
+ *
+ * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set.
+ * It's supposed to be used for immediate exit from the function on error
+ * after call of another function with 'bool *have_error' argument.
+ */
+#define RETURN_ERROR(throw_error) \
+do { \
+   if (have_error) \
+   { \
+       *have_error = true; \
+       goto on_error; \
+   } \
+   else \
+   { \
+       throw_error; \
+   } \
+} while (0)
+
+#define CHECK_ERROR \
+do { \
+   if (have_error && *have_error) \
+       goto on_error; \
+} while (0)
+
 /* ----------
  * Routines flags
  * ----------
@@ -1012,7 +1049,7 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
 static void DCH_to_char(FormatNode *node, bool is_interval,
                        TmToChar *in, char *out, Oid collid);
 static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
-                         bool std);
+                         bool std, bool *have_error);
 
 #ifdef DEBUG_TO_FROM_CHAR
 static void dump_index(const KeyWord *k, const int *index);
@@ -1023,15 +1060,21 @@ static const char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
 static int adjust_partial_year_to_2020(int year);
 static int strspace_len(char *str);
-static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
-static void from_char_set_int(int *dest, const int value, const FormatNode *node);
-static int from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node);
-static int from_char_parse_int(int *dest, char **src, FormatNode *node);
+static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
+                              bool *have_error);
+static void from_char_set_int(int *dest, const int value, const FormatNode *node,
+                             bool *have_error);
+static int from_char_parse_int_len(int *dest, char **src, const int len,
+                                   FormatNode *node, bool *have_error);
+static int from_char_parse_int(int *dest, char **src, FormatNode *node,
+                               bool *have_error);
 static int seq_search(char *name, const char *const *array, int type, int max, int *len);
-static int from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node);
+static int from_char_seq_search(int *dest, char **src,
+                                const char *const *array, int type, int max,
+                                FormatNode *node, bool *have_error);
 static void do_to_timestamp(text *date_txt, text *fmt, bool std,
                            struct pg_tm *tm, fsec_t *fsec, int *fprec,
-                           uint32 *flags);
+                           uint32 *flags, bool *have_error);
 static char *fill_str(char *str, int c, int max);
 static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
 static char *int_to_roman(int number);
@@ -2233,21 +2276,26 @@ strspace_len(char *str)
  *
  * Puke if the date mode has already been set, and the caller attempts to set
  * it to a conflicting mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
+from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error)
 {
    if (mode != FROM_CHAR_DATE_NONE)
    {
        if (tmfc->mode == FROM_CHAR_DATE_NONE)
            tmfc->mode = mode;
        else if (tmfc->mode != mode)
-           ereport(ERROR,
-                   (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;
 }
 
 /*
@@ -2255,18 +2303,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode)
  *
  * Puke if the destination integer has previously been set to some other
  * non-zero value.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
-from_char_set_int(int *dest, const int value, const FormatNode *node)
+from_char_set_int(int *dest, const int value, const FormatNode *node,
+                 bool *have_error)
 {
    if (*dest != 0 && *dest != value)
-       ereport(ERROR,
-               (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;
 }
 
 /*
@@ -2288,9 +2343,13 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
  * Note that from_char_parse_int() provides a more convenient wrapper where
  * the length of the field is the same as the length of the format keyword (as
  * with DD and MI).
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and -1 is returned.
  */
 static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node,
+                       bool *have_error)
 {
    long        result;
    char        copy[DCH_MAX_ITEM_SIZ + 1];
@@ -2323,51 +2382,60 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
        char       *last;
 
        if (used < len)
-           ereport(ERROR,
-                   (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;
 }
 
 /*
@@ -2380,9 +2448,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
  * required length explicitly.
  */
 static int
-from_char_parse_int(int *dest, char **src, FormatNode *node)
+from_char_parse_int(int *dest, char **src, FormatNode *node, bool *have_error)
 {
-   return from_char_parse_int_len(dest, src, node->key->len, node);
+   return from_char_parse_int_len(dest, src, node->key->len, node, have_error);
 }
 
 /* ----------
@@ -2465,11 +2533,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len)
  * pointed to by 'dest', advance 'src' to the end of the part of the string
  * which matched, and return the number of characters consumed.
  *
- * If the string doesn't match, throw an error.
+ * If the string doesn't match, throw an error if 'have_error' is NULL,
+ * otherwise set '*have_error' and return -1.
  */
 static int
-from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max,
-                    FormatNode *node)
+from_char_seq_search(int *dest, char **src, const char *const *array, int type,
+                    int max, FormatNode *node, bool *have_error)
 {
    int         len;
 
@@ -2481,15 +2550,18 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type,
        Assert(max <= DCH_MAX_ITEM_SIZ);
        strlcpy(copy, *src, max + 1);
 
-       ereport(ERROR,
-               (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;
 }
 
 /* ----------
@@ -3089,10 +3161,13 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
  *
  * Note: we currently don't have any to_interval() function, so there
  * is no need here for INVALID_FOR_INTERVAL checks.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  * ----------
  */
 static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std,
+             bool *have_error)
 {
    FormatNode *n;
    char       *s;
@@ -3132,10 +3207,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
                if (*s == n->character[0])
                    s++;
                else
-                   ereport(ERROR,
-                           (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-                            errmsg("unmatched format separator \"%c\"",
-                                   n->character[0])));
+                   RETURN_ERROR(ereport(ERROR,
+                                        (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+                                         errmsg("unmatched format separator \"%c\"",
+                                                n->character[0]))));
            }
            else if (!fx_mode)
            {
@@ -3191,7 +3266,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
            continue;
        }
 
-       from_char_set_mode(out, n->key->date_mode);
+       from_char_set_mode(out, n->key->date_mode, have_error);
+       CHECK_ERROR;
 
        switch (n->key->id)
        {
@@ -3203,8 +3279,10 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
            case DCH_a_m:
            case DCH_p_m:
                from_char_seq_search(&value, &s, ampm_strings_long,
-                                    ALL_UPPER, n->key->len, n);
-               from_char_set_int(&out->pm, value % 2, n);
+                                    ALL_UPPER, n->key->len, n, have_error);
+               CHECK_ERROR;
+               from_char_set_int(&out->pm, value % 2, n, have_error);
+               CHECK_ERROR;
                out->clock = CLOCK_12_HOUR;
                break;
            case DCH_AM:
@@ -3212,30 +3290,37 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
            case DCH_am:
            case DCH_pm:
                from_char_seq_search(&value, &s, ampm_strings,
-                                    ALL_UPPER, n->key->len, n);
-               from_char_set_int(&out->pm, value % 2, n);
+                                    ALL_UPPER, n->key->len, n, have_error);
+               CHECK_ERROR;
+               from_char_set_int(&out->pm, value % 2, n, have_error);
+               CHECK_ERROR;
                out->clock = CLOCK_12_HOUR;
                break;
            case DCH_HH:
            case DCH_HH12:
-               from_char_parse_int_len(&out->hh, &s, 2, n);
+               from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+               CHECK_ERROR;
                out->clock = CLOCK_12_HOUR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_HH24:
-               from_char_parse_int_len(&out->hh, &s, 2, n);
+               from_char_parse_int_len(&out->hh, &s, 2, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_MI:
-               from_char_parse_int(&out->mi, &s, n);
+               from_char_parse_int(&out->mi, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_SS:
-               from_char_parse_int(&out->ss, &s, n);
+               from_char_parse_int(&out->ss, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_MS:        /* millisecond */
-               len = from_char_parse_int_len(&out->ms, &s, 3, n);
+               len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error);
+               CHECK_ERROR;
 
                /*
                 * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
@@ -3256,7 +3341,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
            case DCH_US:        /* microsecond */
                len = from_char_parse_int_len(&out->us, &s,
                                              n->key->id == DCH_US ? 6 :
-                                             out->ff, n);
+                                             out->ff, n, have_error);
+               CHECK_ERROR;
 
                out->us *= len == 1 ? 100000 :
                    len == 2 ? 10000 :
@@ -3267,16 +3353,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
                SKIP_THth(s, n->suffix);
                break;
            case DCH_SSSS:
-               from_char_parse_int(&out->ssss, &s, n);
+               from_char_parse_int(&out->ssss, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_tz:
            case DCH_TZ:
            case DCH_OF:
-               ereport(ERROR,
-                       (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:
 
@@ -3300,82 +3388,102 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
                        out->tzsign = +1;
                }
 
-               from_char_parse_int_len(&out->tzh, &s, 2, n);
+               from_char_parse_int_len(&out->tzh, &s, 2, n, have_error);
+               CHECK_ERROR;
                break;
            case DCH_TZM:
                /* assign positive timezone sign if TZH was not seen before */
                if (!out->tzsign)
                    out->tzsign = +1;
-               from_char_parse_int_len(&out->tzm, &s, 2, n);
+               from_char_parse_int_len(&out->tzm, &s, 2, n, have_error);
+               CHECK_ERROR;
                break;
            case DCH_A_D:
            case DCH_B_C:
            case DCH_a_d:
            case DCH_b_c:
                from_char_seq_search(&value, &s, adbc_strings_long,
-                                    ALL_UPPER, n->key->len, n);
-               from_char_set_int(&out->bc, value % 2, n);
+                                    ALL_UPPER, n->key->len, n, have_error);
+               CHECK_ERROR;
+               from_char_set_int(&out->bc, value % 2, n, have_error);
+               CHECK_ERROR;
                break;
            case DCH_AD:
            case DCH_BC:
            case DCH_ad:
            case DCH_bc:
                from_char_seq_search(&value, &s, adbc_strings,
-                                    ALL_UPPER, n->key->len, n);
-               from_char_set_int(&out->bc, value % 2, n);
+                                    ALL_UPPER, n->key->len, n, have_error);
+               CHECK_ERROR;
+               from_char_set_int(&out->bc, value % 2, n, have_error);
+               CHECK_ERROR;
                break;
            case DCH_MONTH:
            case DCH_Month:
            case DCH_month:
                from_char_seq_search(&value, &s, months_full, ONE_UPPER,
-                                    MAX_MONTH_LEN, n);
-               from_char_set_int(&out->mm, value + 1, n);
+                                    MAX_MONTH_LEN, n, have_error);
+               CHECK_ERROR;
+               from_char_set_int(&out->mm, value + 1, n, have_error);
+               CHECK_ERROR;
                break;
            case DCH_MON:
            case DCH_Mon:
            case DCH_mon:
                from_char_seq_search(&value, &s, months, ONE_UPPER,
-                                    MAX_MON_LEN, n);
-               from_char_set_int(&out->mm, value + 1, n);
+                                    MAX_MON_LEN, n, have_error);
+               CHECK_ERROR;
+               from_char_set_int(&out->mm, value + 1, n, have_error);
+               CHECK_ERROR;
                break;
            case DCH_MM:
-               from_char_parse_int(&out->mm, &s, n);
+               from_char_parse_int(&out->mm, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_DAY:
            case DCH_Day:
            case DCH_day:
                from_char_seq_search(&value, &s, days, ONE_UPPER,
-                                    MAX_DAY_LEN, n);
-               from_char_set_int(&out->d, value, n);
+                                    MAX_DAY_LEN, n, have_error);
+               CHECK_ERROR;
+               from_char_set_int(&out->d, value, n, have_error);
+               CHECK_ERROR;
                out->d++;
                break;
            case DCH_DY:
            case DCH_Dy:
            case DCH_dy:
                from_char_seq_search(&value, &s, days, ONE_UPPER,
-                                    MAX_DY_LEN, n);
-               from_char_set_int(&out->d, value, n);
+                                    MAX_DY_LEN, n, have_error);
+               CHECK_ERROR;
+               from_char_set_int(&out->d, value, n, have_error);
+               CHECK_ERROR;
                out->d++;
                break;
            case DCH_DDD:
-               from_char_parse_int(&out->ddd, &s, n);
+               from_char_parse_int(&out->ddd, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_IDDD:
-               from_char_parse_int_len(&out->ddd, &s, 3, n);
+               from_char_parse_int_len(&out->ddd, &s, 3, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_DD:
-               from_char_parse_int(&out->dd, &s, n);
+               from_char_parse_int(&out->dd, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_D:
-               from_char_parse_int(&out->d, &s, n);
+               from_char_parse_int(&out->d, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_ID:
-               from_char_parse_int_len(&out->d, &s, 1, n);
+               from_char_parse_int_len(&out->d, &s, 1, n, have_error);
+               CHECK_ERROR;
                /* Shift numbering to match Gregorian where Sunday = 1 */
                if (++out->d > 7)
                    out->d = 1;
@@ -3383,7 +3491,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
                break;
            case DCH_WW:
            case DCH_IW:
-               from_char_parse_int(&out->ww, &s, n);
+               from_char_parse_int(&out->ww, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_Q:
@@ -3398,11 +3507,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
                 * We still parse the source string for an integer, but it
                 * isn't stored anywhere in 'out'.
                 */
-               from_char_parse_int((int *) NULL, &s, n);
+               from_char_parse_int((int *) NULL, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_CC:
-               from_char_parse_int(&out->cc, &s, n);
+               from_char_parse_int(&out->cc, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_Y_YYY:
@@ -3414,11 +3525,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
 
                    matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
                    if (matched < 2)
-                       ereport(ERROR,
-                               (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);
@@ -3426,47 +3538,62 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
                break;
            case DCH_YYYY:
            case DCH_IYYY:
-               from_char_parse_int(&out->year, &s, n);
+               from_char_parse_int(&out->year, &s, n, have_error);
+               CHECK_ERROR;
                out->yysz = 4;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_YYY:
            case DCH_IYY:
-               if (from_char_parse_int(&out->year, &s, n) < 4)
+               len = from_char_parse_int(&out->year, &s, n, have_error);
+               CHECK_ERROR;
+               if (len < 4)
                    out->year = adjust_partial_year_to_2020(out->year);
                out->yysz = 3;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_YY:
            case DCH_IY:
-               if (from_char_parse_int(&out->year, &s, n) < 4)
+               len = from_char_parse_int(&out->year, &s, n, have_error);
+               CHECK_ERROR;
+               if (len < 4)
                    out->year = adjust_partial_year_to_2020(out->year);
                out->yysz = 2;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_Y:
            case DCH_I:
-               if (from_char_parse_int(&out->year, &s, n) < 4)
+               len = from_char_parse_int(&out->year, &s, n, have_error);
+               CHECK_ERROR;
+               if (len < 4)
                    out->year = adjust_partial_year_to_2020(out->year);
                out->yysz = 1;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_RM:
                from_char_seq_search(&value, &s, rm_months_upper,
-                                    ALL_UPPER, MAX_RM_LEN, n);
-               from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+                                    ALL_UPPER, MAX_RM_LEN, n, have_error);
+               CHECK_ERROR;
+               from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+                                 n, have_error);
+               CHECK_ERROR;
                break;
            case DCH_rm:
                from_char_seq_search(&value, &s, rm_months_lower,
-                                    ALL_LOWER, MAX_RM_LEN, n);
-               from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
+                                    ALL_LOWER, MAX_RM_LEN, n, have_error);
+               CHECK_ERROR;
+               from_char_set_int(&out->mm, MONTHS_PER_YEAR - value,
+                                 n, have_error);
+               CHECK_ERROR;
                break;
            case DCH_W:
-               from_char_parse_int(&out->w, &s, n);
+               from_char_parse_int(&out->w, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
            case DCH_J:
-               from_char_parse_int(&out->j, &s, n);
+               from_char_parse_int(&out->j, &s, n, have_error);
+               CHECK_ERROR;
                SKIP_THth(s, n->suffix);
                break;
        }
@@ -3490,19 +3617,22 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool std)
    if (std)
    {
        if (n->type != NODE_TYPE_END)
-           ereport(ERROR,
-                   (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;
 }
 
 /*
@@ -3523,9 +3653,13 @@ DCH_prevent_counter_overflow(void)
    }
 }
 
-/* Get mask of date/time/zone components present in format nodes. */
+/*
+ * Get mask of date/time/zone components present in format nodes.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
+ */
 static int
-DCH_datetime_type(FormatNode *node)
+DCH_datetime_type(FormatNode *node, bool *have_error)
 {
    FormatNode *n;
    int         flags = 0;
@@ -3566,10 +3700,10 @@ DCH_datetime_type(FormatNode *node)
            case DCH_tz:
            case DCH_TZ:
            case DCH_OF:
-               ereport(ERROR,
-                       (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:
@@ -3623,6 +3757,7 @@ DCH_datetime_type(FormatNode *node)
        }
    }
 
+on_error:
    return flags;
 }
 
@@ -3917,7 +4052,7 @@ to_timestamp(PG_FUNCTION_ARGS)
    fsec_t      fsec;
    int         fprec;
 
-   do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
+   do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL);
 
    /* Use the specified time zone, if any. */
    if (tm.tm_zone)
@@ -3956,7 +4091,7 @@ to_date(PG_FUNCTION_ARGS)
    struct pg_tm tm;
    fsec_t      fsec;
 
-   do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
+   do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL);
 
    /* Prevent overflow in Julian-day routines */
    if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3983,17 +4118,21 @@ to_date(PG_FUNCTION_ARGS)
  * the presence of date/time/zone components in the format string.
  *
  * When timezone component is present, the corresponding offset is set to '*tz'.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero value is returned.
  */
 Datum
 parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
-              int32 *typmod, int *tz)
+              int32 *typmod, int *tz, bool *have_error)
 {
    struct pg_tm tm;
    fsec_t      fsec;
    int         fprec = 0;
    uint32      flags;
 
-   do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+   do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, have_error);
+   CHECK_ERROR;
 
    *typmod = fprec ? fprec : -1;   /* fractional part precision */
 
@@ -4021,15 +4160,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
                     */
                    Assert(!strict);
 
-                   ereport(ERROR,
-                           (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-                            errmsg("missing time zone in input string for type timestamptz")));
+                   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);
 
@@ -4041,9 +4180,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
                Timestamp   result;
 
                if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
-                   ereport(ERROR,
-                           (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);
 
@@ -4055,9 +4194,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
            {
@@ -4065,20 +4204,20 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
 
                /* Prevent overflow in Julian-day routines */
                if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
-                   ereport(ERROR,
-                           (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);
@@ -4096,7 +4235,7 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
                int         dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), tz);
 
                if (dterr)
-                   DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz");
+                   RETURN_ERROR(DateTimeParseError(dterr, text_to_cstring(date_txt), "timetz"));
            }
            else
            {
@@ -4107,15 +4246,15 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
                 */
                Assert(!strict);
 
-               ereport(ERROR,
-                       (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-                        errmsg("missing time zone in input string for type timetz")));
+               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);
 
@@ -4127,9 +4266,9 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
            TimeADT     result;
 
            if (tm2time(&tm, fsec, &result) != 0)
-               ereport(ERROR,
-                       (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);
 
@@ -4139,11 +4278,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;
 }
 
@@ -4159,17 +4299,24 @@ parse_datetime(text *date_txt, text *fmt, bool strict, Oid *typid,
  *
  * The TmFromChar is then analysed and converted into the final results in
  * struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'std' specifies standard parsing mode.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set.
  */
 static void
 do_to_timestamp(text *date_txt, text *fmt, bool std,
                struct pg_tm *tm, fsec_t *fsec, int *fprec,
-               uint32 *flags)
+               uint32 *flags, bool *have_error)
 {
-   FormatNode *format;
+   FormatNode *format = NULL;
    TmFromChar  tmfc;
    int         fmt_len;
    char       *date_str;
    int         fmask;
+   bool        incache = false;
 
    date_str = text_to_cstring(date_txt);
 
@@ -4183,7 +4330,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
    if (fmt_len)
    {
        char       *fmt_str;
-       bool        incache;
 
        fmt_str = text_to_cstring(fmt);
 
@@ -4193,8 +4339,6 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
             * Allocate new memory if format picture is bigger than static
             * cache and do not use cache (call parser always)
             */
-           incache = false;
-
            format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
 
            parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
@@ -4216,15 +4360,21 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
        /* dump_index(DCH_keywords, DCH_index); */
 #endif
 
-       DCH_from_char(format, date_str, &tmfc, std);
+       DCH_from_char(format, date_str, &tmfc, std, have_error);
+       CHECK_ERROR;
 
        pfree(fmt_str);
 
        if (flags)
-           *flags = DCH_datetime_type(format);
+           *flags = DCH_datetime_type(format, have_error);
 
        if (!incache)
+       {
            pfree(format);
+           format = NULL;
+       }
+
+       CHECK_ERROR;
    }
 
    DEBUG_TMFC(&tmfc);
@@ -4253,11 +4403,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
    if (tmfc.clock == CLOCK_12_HOUR)
    {
        if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
-           ereport(ERROR,
-                   (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;
@@ -4361,9 +4513,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
         */
 
        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)
        {
@@ -4420,7 +4574,7 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
             * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
             * irrelevant hint about datestyle.
             */
-           DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+           RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
        }
    }
 
@@ -4429,7 +4583,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
        tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
        tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
        *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
-       DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp");
+   {
+       RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"));
+   }
 
    /* Save parsed time-zone into tm->tm_zone if it was specified */
    if (tmfc.tzsign)
@@ -4438,7 +4594,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
        if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
            tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
-           DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp");
+       {
+           RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"));
+       }
 
        tz = psprintf("%c%02d:%02d",
                      tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm);
@@ -4448,6 +4606,11 @@ do_to_timestamp(text *date_txt, text *fmt, bool std,
 
    DEBUG_TM(tm);
 
+on_error:
+
+   if (format && !incache)
+       pfree(format);
+
    pfree(date_str);
 }
 
index 2931bd58c0df85e410524a533645df775126296d..84bc97d40c37600ba32f5972ab1d564a8ea1a75a 100644 (file)
@@ -329,11 +329,11 @@ timestamp_scale(PG_FUNCTION_ARGS)
 }
 
 /*
- * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
+ * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod
  * Works for either timestamp or timestamptz.
  */
-void
-AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+bool
+AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error)
 {
    static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
        INT64CONST(1000000),
@@ -359,10 +359,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
        && (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION))
    {
        if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION)
+       {
+           if (error)
+           {
+               *error = true;
+               return false;
+           }
+
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                     errmsg("timestamp(%d) precision must be between %d and %d",
                            typmod, 0, MAX_TIMESTAMP_PRECISION)));
+       }
 
        if (*time >= INT64CONST(0))
        {
@@ -375,8 +383,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
                      * TimestampScales[typmod]);
        }
    }
+
+   return true;
 }
 
+void
+AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
+{
+   (void) AdjustTimestampForTypmodError(time, typmod, NULL);
+}
 
 /* timestamptz_in()
  * Convert a string to internal form.
@@ -5172,8 +5187,15 @@ timestamp_timestamptz(PG_FUNCTION_ARGS)
    PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp));
 }
 
-static TimestampTz
-timestamp2timestamptz(Timestamp timestamp)
+/*
+ * Convert timestamp to timestamp with time zone.
+ *
+ * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set
+ * and zero is returned.
+ */
+
+TimestampTz
+timestamp2timestamptz_opt_error(Timestamp timestamp, bool *have_error)
 {
    TimestampTz result;
    struct pg_tm tt,
@@ -5182,23 +5204,33 @@ timestamp2timestamptz(Timestamp timestamp)
    int         tz;
 
    if (TIMESTAMP_NOT_FINITE(timestamp))
-       result = timestamp;
-   else
-   {
-       if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
-           ereport(ERROR,
-                   (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                    errmsg("timestamp out of range")));
+       return timestamp;
 
+   if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL))
+   {
        tz = DetermineTimeZoneOffset(tm, session_timezone);
 
-       if (tm2timestamp(tm, fsec, &tz, &result) != 0)
-           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()
index bd15bfa5bb0f985f742b9cc97fcaa9d1e5831fb3..c29f13aaf042b91b671a2bde888fbab02c85978e 100644 (file)
@@ -70,6 +70,8 @@ typedef struct
 /* date.c */
 extern int32 anytime_typmod_check(bool istz, int32 typmod);
 extern double date2timestamp_no_overflow(DateADT dateVal);
+extern Timestamp date2timestamp_opt_error(DateADT dateVal, bool *have_error);
+extern TimestampTz date2timestamptz_opt_error(DateADT dateVal, bool *have_error);
 extern void EncodeSpecialDate(DateADT dt, char *str);
 extern DateADT GetSQLCurrentDate(void);
 extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
index 0cafdd26538e46f5b11d8bf64ed23b37dbe1bd00..5ebf336071bc9bc8895d977d31398d835a93dd5b 100644 (file)
@@ -337,5 +337,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
 extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod,
+                                         bool *error);
 
 #endif                         /* DATETIME_H */
index beeaf10c3321206084c4bb382fdf011b0f6b555b..165ca78ba5c9f30042c5bb1b3d7516b3fbcbb579 100644 (file)
@@ -27,6 +27,7 @@ extern char *asc_toupper(const char *buff, size_t nbytes);
 extern char *asc_initcap(const char *buff, size_t nbytes);
 
 extern Datum parse_datetime(text *date_txt, text *fmt, bool std,
-                           Oid *typid, int32 *typmod, int *tz);
+                           Oid *typid, int32 *typmod, int *tz,
+                           bool *have_error);
 
 #endif
index ea16190ec3de827225935a7e6e67df170098b9c7..e884d4405f52e39304d0d371e9e005d307f07bb0 100644 (file)
@@ -97,6 +97,9 @@ extern int    timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)  timestamp_cmp_internal(dt1, dt2)
 
+extern TimestampTz timestamp2timestamptz_opt_error(Timestamp timestamp,
+                                                  bool *have_error);
+
 extern int isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
 extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday);