diff options
Diffstat (limited to 'src/backend/utils/adt/formatting.c')
-rw-r--r-- | src/backend/utils/adt/formatting.c | 537 |
1 files changed, 350 insertions, 187 deletions
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 462c333544c..f7175df8da6 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -99,6 +99,43 @@ #include "utils/pg_locale.h" /* ---------- + * Convenience macros for error handling + * ---------- + * + * Two macros below help to handle errors in functions that take + * 'bool *have_error' argument. When this argument is not NULL, it's expected + * that function will suppress ereports when possible. Instead it should + * return some default value and set *have_error flag. + * + * RETURN_ERROR() macro intended to wrap ereport() calls. When have_error + * function argument is not NULL, then instead of ereport'ing we set + * *have_error flag and go to on_error label. It's supposed that jump + * resources will be freed and some 'default' value returned. + * + * CHECK_ERROR() jumps on_error label when *have_error flag is defined and set. + * It's supposed to be used for immediate exit from the function on error + * after call of another function with 'bool *have_error' argument. + */ +#define RETURN_ERROR(throw_error) \ +do { \ + if (have_error) \ + { \ + *have_error = true; \ + goto on_error; \ + } \ + else \ + { \ + throw_error; \ + } \ +} while (0) + +#define CHECK_ERROR \ +do { \ + if (have_error && *have_error) \ + goto on_error; \ +} while (0) + +/* ---------- * Routines 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); } |