From 4dd687502d9eb0b2984c36579c2fcf5283adfa7c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 9 Dec 2022 20:15:56 -0500 Subject: [PATCH] Restructure soft-error handling in formatting.c. Replace the error trapping scheme introduced in 5bc450629 with our shiny new errsave/ereturn mechanism. This doesn't have any real functional impact (although I think that the new coding is able to report a few more errors softly than v15 did). And I doubt there's any measurable performance difference either. But this gets rid of an ad-hoc, one-of-a-kind design in favor of a mechanism that will be widely used going forward, so it should be a net win for code readability. Discussion: https://postgr.es/m/3bbbb0df-7382-bf87-9737-340ba096e034@postgrespro.ru --- src/backend/utils/adt/formatting.c | 668 ++++++++++++-------------- src/backend/utils/adt/jsonpath_exec.c | 12 +- src/include/utils/formatting.h | 2 +- 3 files changed, 323 insertions(+), 359 deletions(-) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index eba981abd8..65746c48d2 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -78,6 +78,7 @@ #include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "mb/pg_wchar.h" +#include "nodes/miscnodes.h" #include "parser/scansup.h" #include "utils/builtins.h" #include "utils/date.h" @@ -88,42 +89,6 @@ #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 @@ -1065,7 +1030,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, const char *in, TmFromChar *out, - Oid collid, bool std, bool *have_error); + Oid collid, bool std, Node *escontext); #ifdef DEBUG_TO_FROM_CHAR static void dump_index(const KeyWord *k, const int *index); @@ -1076,24 +1041,24 @@ 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(const char *str); -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 bool from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, + Node *escontext); +static bool from_char_set_int(int *dest, const int value, const FormatNode *node, + Node *escontext); static int from_char_parse_int_len(int *dest, const char **src, const int len, - FormatNode *node, bool *have_error); + FormatNode *node, Node *escontext); static int from_char_parse_int(int *dest, const char **src, FormatNode *node, - bool *have_error); + Node *escontext); static int seq_search_ascii(const char *name, const char *const *array, int *len); static int seq_search_localized(const char *name, char **array, int *len, Oid collid); -static int from_char_seq_search(int *dest, const char **src, +static bool from_char_seq_search(int *dest, const char **src, const char *const *array, char **localized_array, Oid collid, - FormatNode *node, bool *have_error); -static void do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, + FormatNode *node, Node *escontext); +static bool do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, struct pg_tm *tm, fsec_t *fsec, int *fprec, - uint32 *flags, bool *have_error); + uint32 *flags, Node *escontext); 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); @@ -2291,25 +2256,25 @@ strspace_len(const 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. + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). */ -static void -from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *have_error) +static bool +from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, + Node *escontext) { if (mode != FROM_CHAR_DATE_NONE) { if (tmfc->mode == FROM_CHAR_DATE_NONE) tmfc->mode = mode; else if (tmfc->mode != mode) - 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.")))); + ereturn(escontext, false, + (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; + return true; } /* @@ -2318,24 +2283,22 @@ on_error: * 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. + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). */ -static void +static bool from_char_set_int(int *dest, const int value, const FormatNode *node, - bool *have_error) + Node *escontext) { if (*dest != 0 && *dest != value) - 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.")))); + ereturn(escontext, false, + (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; + return true; } /* @@ -2352,18 +2315,16 @@ on_error: * point at the character immediately following the last character used in the * conversion. * - * Return the number of characters consumed. + * Returns the number of characters consumed, or -1 on error (if escontext + * points to an ErrorSaveContext; otherwise errors are thrown). * * 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, const char **src, const int len, FormatNode *node, - bool *have_error) + Node *escontext) { long result; char copy[DCH_MAX_ITEM_SIZ + 1]; @@ -2399,60 +2360,54 @@ from_char_parse_int_len(int *dest, const char **src, const int len, FormatNode * char *last; if (used < len) - 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.")))); + ereturn(escontext, -1, + (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) - 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.")))); + ereturn(escontext, -1, + (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) - 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.")))); + ereturn(escontext, -1, + (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) - 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)))); + ereturn(escontext, -1, + (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, have_error); - CHECK_ERROR; + if (!from_char_set_int(dest, (int) result, node, escontext)) + return -1; } return *src - init; - -on_error: - return -1; } /* @@ -2465,9 +2420,10 @@ on_error: * required length explicitly. */ static int -from_char_parse_int(int *dest, const char **src, FormatNode *node, bool *have_error) +from_char_parse_int(int *dest, const char **src, FormatNode *node, + Node *escontext) { - return from_char_parse_int_len(dest, src, node->key->len, node, have_error); + return from_char_parse_int_len(dest, src, node->key->len, node, escontext); } /* @@ -2613,19 +2569,19 @@ seq_search_localized(const char *name, char **array, int *len, Oid collid) * (which will follow the rules of the collation 'collid'). * * If a match is found, copy the array index of the match into the integer - * pointed to by 'dest', advance 'src' to the end of the part of the string - * which matched, and return the number of characters consumed. + * pointed to by 'dest' and advance 'src' to the end of the part of the string + * which matched. * - * If the string doesn't match, throw an error if 'have_error' is NULL, - * otherwise set '*have_error' and return -1. + * Returns true on match, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). * * 'node' is used only for error reports: node->key->name identifies the * field type we were searching for. */ -static int +static bool from_char_seq_search(int *dest, const char **src, const char *const *array, char **localized_array, Oid collid, - FormatNode *node, bool *have_error) + FormatNode *node, Node *escontext) { int len; @@ -2652,18 +2608,15 @@ from_char_seq_search(int *dest, const char **src, const char *const *array, } } - 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.")))); + ereturn(escontext, false, + (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; + return true; } /* ---------- @@ -3310,14 +3263,17 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col * * 'collid' identifies the collation to use, if needed. * 'std' specifies standard parsing mode. - * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set. + * + * If escontext points to an ErrorSaveContext, data errors will be reported + * by filling that struct; the caller must test SOFT_ERROR_OCCURRED() to see + * whether an error occurred. Otherwise, errors are thrown. * * Note: we currently don't have any to_interval() function, so there * is no need here for INVALID_FOR_INTERVAL checks. */ static void DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, - Oid collid, bool std, bool *have_error) + Oid collid, bool std, Node *escontext) { FormatNode *n; const char *s; @@ -3360,10 +3316,10 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, if (*s == n->character[0]) s++; else - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("unmatched format separator \"%c\"", - n->character[0])))); + ereturn(escontext,, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("unmatched format separator \"%c\"", + n->character[0]))); } else if (!fx_mode) { @@ -3421,18 +3377,18 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, */ if (std && n->type == NODE_TYPE_CHAR && strncmp(s, n->character, chlen) != 0) - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("unmatched format character \"%s\"", - n->character)))); + ereturn(escontext,, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("unmatched format character \"%s\"", + n->character))); s += chlen; } continue; } - from_char_set_mode(out, n->key->date_mode, have_error); - CHECK_ERROR; + if (!from_char_set_mode(out, n->key->date_mode, escontext)) + return; switch (n->key->id) { @@ -3443,51 +3399,52 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, case DCH_P_M: case DCH_a_m: case DCH_p_m: - from_char_seq_search(&value, &s, ampm_strings_long, - NULL, InvalidOid, - n, have_error); - CHECK_ERROR; - from_char_set_int(&out->pm, value % 2, n, have_error); - CHECK_ERROR; + if (!from_char_seq_search(&value, &s, ampm_strings_long, + NULL, InvalidOid, + n, escontext)) + return; + if (!from_char_set_int(&out->pm, value % 2, n, escontext)) + return; out->clock = CLOCK_12_HOUR; break; case DCH_AM: case DCH_PM: case DCH_am: case DCH_pm: - from_char_seq_search(&value, &s, ampm_strings, - NULL, InvalidOid, - n, have_error); - CHECK_ERROR; - from_char_set_int(&out->pm, value % 2, n, have_error); - CHECK_ERROR; + if (!from_char_seq_search(&value, &s, ampm_strings, + NULL, InvalidOid, + n, escontext)) + return; + if (!from_char_set_int(&out->pm, value % 2, n, escontext)) + return; out->clock = CLOCK_12_HOUR; break; case DCH_HH: case DCH_HH12: - from_char_parse_int_len(&out->hh, &s, 2, n, have_error); - CHECK_ERROR; + if (from_char_parse_int_len(&out->hh, &s, 2, n, escontext) < 0) + return; out->clock = CLOCK_12_HOUR; SKIP_THth(s, n->suffix); break; case DCH_HH24: - from_char_parse_int_len(&out->hh, &s, 2, n, have_error); - CHECK_ERROR; + if (from_char_parse_int_len(&out->hh, &s, 2, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_MI: - from_char_parse_int(&out->mi, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->mi, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_SS: - from_char_parse_int(&out->ss, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->ss, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_MS: /* millisecond */ - len = from_char_parse_int_len(&out->ms, &s, 3, n, have_error); - CHECK_ERROR; + len = from_char_parse_int_len(&out->ms, &s, 3, n, escontext); + if (len < 0) + return; /* * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25 @@ -3508,8 +3465,9 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, case DCH_US: /* microsecond */ len = from_char_parse_int_len(&out->us, &s, n->key->id == DCH_US ? 6 : - out->ff, n, have_error); - CHECK_ERROR; + out->ff, n, escontext); + if (len < 0) + return; out->us *= len == 1 ? 100000 : len == 2 ? 10000 : @@ -3520,18 +3478,17 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, SKIP_THth(s, n->suffix); break; case DCH_SSSS: - from_char_parse_int(&out->ssss, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->ssss, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_tz: case DCH_TZ: case DCH_OF: - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("formatting field \"%s\" is only supported in to_char", - n->key->name)))); - CHECK_ERROR; + ereturn(escontext,, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("formatting field \"%s\" is only supported in to_char", + n->key->name))); break; case DCH_TZH: @@ -3555,112 +3512,112 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, out->tzsign = +1; } - from_char_parse_int_len(&out->tzh, &s, 2, n, have_error); - CHECK_ERROR; + if (from_char_parse_int_len(&out->tzh, &s, 2, n, escontext) < 0) + return; 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, have_error); - CHECK_ERROR; + if (from_char_parse_int_len(&out->tzm, &s, 2, n, escontext) < 0) + return; 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, - NULL, InvalidOid, - n, have_error); - CHECK_ERROR; - from_char_set_int(&out->bc, value % 2, n, have_error); - CHECK_ERROR; + if (!from_char_seq_search(&value, &s, adbc_strings_long, + NULL, InvalidOid, + n, escontext)) + return; + if (!from_char_set_int(&out->bc, value % 2, n, escontext)) + return; break; case DCH_AD: case DCH_BC: case DCH_ad: case DCH_bc: - from_char_seq_search(&value, &s, adbc_strings, - NULL, InvalidOid, - n, have_error); - CHECK_ERROR; - from_char_set_int(&out->bc, value % 2, n, have_error); - CHECK_ERROR; + if (!from_char_seq_search(&value, &s, adbc_strings, + NULL, InvalidOid, + n, escontext)) + return; + if (!from_char_set_int(&out->bc, value % 2, n, escontext)) + return; break; case DCH_MONTH: case DCH_Month: case DCH_month: - from_char_seq_search(&value, &s, months_full, - S_TM(n->suffix) ? localized_full_months : NULL, - collid, - n, have_error); - CHECK_ERROR; - from_char_set_int(&out->mm, value + 1, n, have_error); - CHECK_ERROR; + if (!from_char_seq_search(&value, &s, months_full, + S_TM(n->suffix) ? localized_full_months : NULL, + collid, + n, escontext)) + return; + if (!from_char_set_int(&out->mm, value + 1, n, escontext)) + return; break; case DCH_MON: case DCH_Mon: case DCH_mon: - from_char_seq_search(&value, &s, months, - S_TM(n->suffix) ? localized_abbrev_months : NULL, - collid, - n, have_error); - CHECK_ERROR; - from_char_set_int(&out->mm, value + 1, n, have_error); - CHECK_ERROR; + if (!from_char_seq_search(&value, &s, months, + S_TM(n->suffix) ? localized_abbrev_months : NULL, + collid, + n, escontext)) + return; + if (!from_char_set_int(&out->mm, value + 1, n, escontext)) + return; break; case DCH_MM: - from_char_parse_int(&out->mm, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->mm, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_DAY: case DCH_Day: case DCH_day: - from_char_seq_search(&value, &s, days, - S_TM(n->suffix) ? localized_full_days : NULL, - collid, - n, have_error); - CHECK_ERROR; - from_char_set_int(&out->d, value, n, have_error); - CHECK_ERROR; + if (!from_char_seq_search(&value, &s, days, + S_TM(n->suffix) ? localized_full_days : NULL, + collid, + n, escontext)) + return; + if (!from_char_set_int(&out->d, value, n, escontext)) + return; out->d++; break; case DCH_DY: case DCH_Dy: case DCH_dy: - from_char_seq_search(&value, &s, days_short, - S_TM(n->suffix) ? localized_abbrev_days : NULL, - collid, - n, have_error); - CHECK_ERROR; - from_char_set_int(&out->d, value, n, have_error); - CHECK_ERROR; + if (!from_char_seq_search(&value, &s, days_short, + S_TM(n->suffix) ? localized_abbrev_days : NULL, + collid, + n, escontext)) + return; + if (!from_char_set_int(&out->d, value, n, escontext)) + return; out->d++; break; case DCH_DDD: - from_char_parse_int(&out->ddd, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->ddd, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_IDDD: - from_char_parse_int_len(&out->ddd, &s, 3, n, have_error); - CHECK_ERROR; + if (from_char_parse_int_len(&out->ddd, &s, 3, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_DD: - from_char_parse_int(&out->dd, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->dd, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_D: - from_char_parse_int(&out->d, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->d, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_ID: - from_char_parse_int_len(&out->d, &s, 1, n, have_error); - CHECK_ERROR; + if (from_char_parse_int_len(&out->d, &s, 1, n, escontext) < 0) + return; /* Shift numbering to match Gregorian where Sunday = 1 */ if (++out->d > 7) out->d = 1; @@ -3668,8 +3625,8 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, break; case DCH_WW: case DCH_IW: - from_char_parse_int(&out->ww, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->ww, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_Q: @@ -3684,13 +3641,13 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, * 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, have_error); - CHECK_ERROR; + if (from_char_parse_int((int *) NULL, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_CC: - from_char_parse_int(&out->cc, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->cc, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_Y_YYY: @@ -3702,12 +3659,12 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch); if (matched < 2) - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("invalid input string for \"Y,YYY\"")))); + ereturn(escontext,, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid input string for \"Y,YYY\""))); years += (millennia * 1000); - from_char_set_int(&out->year, years, n, have_error); - CHECK_ERROR; + if (!from_char_set_int(&out->year, years, n, escontext)) + return; out->yysz = 4; s += nch; SKIP_THth(s, n->suffix); @@ -3715,15 +3672,16 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, break; case DCH_YYYY: case DCH_IYYY: - from_char_parse_int(&out->year, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->year, &s, n, escontext) < 0) + return; out->yysz = 4; SKIP_THth(s, n->suffix); break; case DCH_YYY: case DCH_IYY: - len = from_char_parse_int(&out->year, &s, n, have_error); - CHECK_ERROR; + len = from_char_parse_int(&out->year, &s, n, escontext); + if (len < 0) + return; if (len < 4) out->year = adjust_partial_year_to_2020(out->year); out->yysz = 3; @@ -3731,8 +3689,9 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, break; case DCH_YY: case DCH_IY: - len = from_char_parse_int(&out->year, &s, n, have_error); - CHECK_ERROR; + len = from_char_parse_int(&out->year, &s, n, escontext); + if (len < 0) + return; if (len < 4) out->year = adjust_partial_year_to_2020(out->year); out->yysz = 2; @@ -3740,8 +3699,9 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, break; case DCH_Y: case DCH_I: - len = from_char_parse_int(&out->year, &s, n, have_error); - CHECK_ERROR; + len = from_char_parse_int(&out->year, &s, n, escontext); + if (len < 0) + return; if (len < 4) out->year = adjust_partial_year_to_2020(out->year); out->yysz = 1; @@ -3749,22 +3709,22 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, break; case DCH_RM: case DCH_rm: - from_char_seq_search(&value, &s, rm_months_lower, - NULL, InvalidOid, - n, have_error); - CHECK_ERROR; - from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, - n, have_error); - CHECK_ERROR; + if (!from_char_seq_search(&value, &s, rm_months_lower, + NULL, InvalidOid, + n, escontext)) + return; + if (!from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n, + escontext)) + return; break; case DCH_W: - from_char_parse_int(&out->w, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->w, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; case DCH_J: - from_char_parse_int(&out->j, &s, n, have_error); - CHECK_ERROR; + if (from_char_parse_int(&out->j, &s, n, escontext) < 0) + return; SKIP_THth(s, n->suffix); break; } @@ -3788,22 +3748,18 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, if (std) { if (n->type != NODE_TYPE_END) - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("input string is too short for datetime format")))); + ereturn(escontext,, + (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') - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("trailing characters remain in input string " - "after datetime format")))); + ereturn(escontext,, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("trailing characters remain in input string after datetime format"))); } - -on_error: - return; } /* @@ -3826,11 +3782,9 @@ DCH_prevent_counter_overflow(void) /* * 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, bool *have_error) +DCH_datetime_type(FormatNode *node) { FormatNode *n; int flags = 0; @@ -3871,12 +3825,6 @@ DCH_datetime_type(FormatNode *node, bool *have_error) case DCH_tz: case DCH_TZ: case DCH_OF: - 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: case DCH_TZM: flags |= DCH_ZONED; @@ -3928,7 +3876,6 @@ DCH_datetime_type(FormatNode *node, bool *have_error) } } -on_error: return flags; } @@ -4313,25 +4260,26 @@ to_date(PG_FUNCTION_ARGS) * The actual data type (returned in 'typid', 'typmod') is determined by * the presence of date/time/zone components in the format string. * - * When timezone component is present, the corresponding offset is + * When a timezone component is present, the corresponding offset is * returned in '*tz'. * - * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set - * and zero value is returned. + * If escontext points to an ErrorSaveContext, data errors will be reported + * by filling that struct; the caller must test SOFT_ERROR_OCCURRED() to see + * whether an error occurred. Otherwise, errors are thrown. */ Datum parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, Oid *typid, int32 *typmod, int *tz, - bool *have_error) + Node *escontext) { struct pg_tm tm; fsec_t fsec; int fprec; uint32 flags; - do_to_timestamp(date_txt, fmt, collid, strict, - &tm, &fsec, &fprec, &flags, have_error); - CHECK_ERROR; + if (!do_to_timestamp(date_txt, fmt, collid, strict, + &tm, &fsec, &fprec, &flags, escontext)) + return (Datum) 0; *typmod = fprec ? fprec : -1; /* fractional part precision */ @@ -4349,9 +4297,12 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, int dterr = DecodeTimezone(tm.tm_zone, tz); if (dterr) + { DateTimeParseError(dterr, &extra, text_to_cstring(date_txt), - "timestamptz", NULL); + "timestamptz", escontext); + return (Datum) 0; + } } else { @@ -4362,17 +4313,17 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, */ Assert(!strict); - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("missing time zone in input string for type timestamptz")))); + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("missing time zone in input string for type timestamptz"))); } if (tm2timestamp(&tm, fsec, tz, &result) != 0) - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamptz out of range")))); + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamptz out of range"))); - AdjustTimestampForTypmod(&result, *typmod, NULL); /* XXX */ + AdjustTimestampForTypmod(&result, *typmod, escontext); *typid = TIMESTAMPTZOID; return TimestampTzGetDatum(result); @@ -4382,11 +4333,11 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, Timestamp result; if (tm2timestamp(&tm, fsec, NULL, &result) != 0) - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range")))); + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); - AdjustTimestampForTypmod(&result, *typmod, NULL); /* XXX */ + AdjustTimestampForTypmod(&result, *typmod, escontext); *typid = TIMESTAMPOID; return TimestampGetDatum(result); @@ -4396,9 +4347,9 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, { if (flags & DCH_ZONED) { - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("datetime format is zoned but not timed")))); + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("datetime format is zoned but not timed"))); } else { @@ -4406,20 +4357,20 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, /* Prevent overflow in Julian-day routines */ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range: \"%s\"", - text_to_cstring(date_txt))))); + ereturn(escontext, (Datum) 0, + (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)) - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range: \"%s\"", - text_to_cstring(date_txt))))); + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", + text_to_cstring(date_txt)))); *typid = DATEOID; return DateADTGetDatum(result); @@ -4438,9 +4389,12 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, int dterr = DecodeTimezone(tm.tm_zone, tz); if (dterr) - RETURN_ERROR(DateTimeParseError(dterr, &extra, - text_to_cstring(date_txt), - "timetz", NULL)); + { + DateTimeParseError(dterr, &extra, + text_to_cstring(date_txt), + "timetz", escontext); + return (Datum) 0; + } } else { @@ -4451,15 +4405,15 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, */ Assert(!strict); - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("missing time zone in input string for type timetz")))); + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("missing time zone in input string for type timetz"))); } if (tm2timetz(&tm, fsec, *tz, result) != 0) - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timetz out of range")))); + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timetz out of range"))); AdjustTimeForTypmod(&result->time, *typmod); @@ -4471,9 +4425,9 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, TimeADT result; if (tm2time(&tm, fsec, &result) != 0) - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("time out of range")))); + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("time out of range"))); AdjustTimeForTypmod(&result, *typmod); @@ -4483,13 +4437,10 @@ parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, } else { - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("datetime format is not dated and not timed")))); + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("datetime format is not dated and not timed"))); } - -on_error: - return (Datum) 0; } /* @@ -4500,9 +4451,13 @@ on_error: * * 'collid' identifies the collation to use, if needed. * 'std' specifies standard parsing mode. + * * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags', * if that is not NULL. - * If 'have_error' is NULL, then errors are thrown, else '*have_error' is set. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). Note that currently, + * soft-error behavior is provided for bad data but not bad format. * * We parse 'fmt' into a list of FormatNodes, which is then passed to * DCH_from_char to populate a TmFromChar with the parsed contents of @@ -4511,10 +4466,10 @@ on_error: * The TmFromChar is then analysed and converted into the final results in * struct 'tm', 'fsec', and 'fprec'. */ -static void +static bool do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, struct pg_tm *tm, fsec_t *fsec, int *fprec, - uint32 *flags, bool *have_error) + uint32 *flags, Node *escontext) { FormatNode *format = NULL; TmFromChar tmfc; @@ -4572,21 +4527,19 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, /* dump_index(DCH_keywords, DCH_index); */ #endif - DCH_from_char(format, date_str, &tmfc, collid, std, have_error); - CHECK_ERROR; - + DCH_from_char(format, date_str, &tmfc, collid, std, escontext); pfree(fmt_str); + if (SOFT_ERROR_OCCURRED(escontext)) + goto fail; if (flags) - *flags = DCH_datetime_type(format, have_error); + *flags = DCH_datetime_type(format); if (!incache) { pfree(format); format = NULL; } - - CHECK_ERROR; } DEBUG_TMFC(&tmfc); @@ -4616,11 +4569,12 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, { if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2) { - 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.")))); + errsave(escontext, + (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."))); + goto fail; } if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2) @@ -4729,9 +4683,10 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, if (!tm->tm_year && !tmfc.bc) { - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("cannot calculate day of year without year information")))); + errsave(escontext, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("cannot calculate day of year without year information"))); + goto fail; } if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK) @@ -4789,8 +4744,9 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an * irrelevant hint about datestyle. */ - RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, - date_str, "timestamp", NULL)); + DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + date_str, "timestamp", escontext); + goto fail; } } @@ -4800,8 +4756,9 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE || *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC) { - RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, - date_str, "timestamp", NULL)); + DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + date_str, "timestamp", escontext); + goto fail; } /* Save parsed time-zone into tm->tm_zone if it was specified */ @@ -4812,8 +4769,9 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR || tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR) { - RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, NULL, - date_str, "timestamp", NULL)); + DateTimeParseError(DTERR_TZDISP_OVERFLOW, NULL, + date_str, "timestamp", escontext); + goto fail; } tz = psprintf("%c%02d:%02d", @@ -4824,12 +4782,18 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, DEBUG_TM(tm); -on_error: - if (format && !incache) pfree(format); - pfree(date_str); + + return true; + +fail: + if (format && !incache) + pfree(format); + pfree(date_str); + + return false; } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 930bd26584..e758616eb8 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1808,7 +1808,7 @@ executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, text *template; char *template_str; int template_len; - bool have_error = false; + ErrorSaveContext escontext = {T_ErrorSaveContext}; jspGetArg(jsp, &elem); @@ -1822,9 +1822,9 @@ executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, value = parse_datetime(datetime, template, collid, true, &typid, &typmod, &tz, - jspThrowErrors(cxt) ? NULL : &have_error); + jspThrowErrors(cxt) ? NULL : (Node *) &escontext); - if (have_error) + if (escontext.error_occurred) res = jperError; else res = jperOk; @@ -1859,7 +1859,7 @@ executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, /* loop until datetime format fits */ for (i = 0; i < lengthof(fmt_str); i++) { - bool have_error = false; + ErrorSaveContext escontext = {T_ErrorSaveContext}; if (!fmt_txt[i]) { @@ -1872,9 +1872,9 @@ executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, value = parse_datetime(datetime, fmt_txt[i], collid, true, &typid, &typmod, &tz, - &have_error); + (Node *) &escontext); - if (!have_error) + if (!escontext.error_occurred) { res = jperOk; break; diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h index 851e787bfd..887e22d6f6 100644 --- a/src/include/utils/formatting.h +++ b/src/include/utils/formatting.h @@ -28,6 +28,6 @@ extern char *asc_initcap(const char *buff, size_t nbytes); extern Datum parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, Oid *typid, int32 *typmod, int *tz, - bool *have_error); + struct Node *escontext); #endif