summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/utils/adt/formatting.c174
-rw-r--r--src/test/regress/expected/horology.out2
2 files changed, 85 insertions, 91 deletions
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 4a35bf8c795..e222037013e 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -84,6 +84,7 @@
#include "catalog/pg_collation.h"
#include "mb/pg_wchar.h"
+#include "parser/scansup.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
@@ -280,18 +281,6 @@ static const char *const numth[] = {"st", "nd", "rd", "th", NULL};
* Flags & Options:
* ----------
*/
-#define ONE_UPPER 1 /* Name */
-#define ALL_UPPER 2 /* NAME */
-#define ALL_LOWER 3 /* name */
-
-#define FULL_SIZ 0
-
-#define MAX_MONTH_LEN 9
-#define MAX_MON_LEN 3
-#define MAX_DAY_LEN 9
-#define MAX_DY_LEN 3
-#define MAX_RM_LEN 4
-
#define TH_UPPER 1
#define TH_LOWER 2
@@ -953,7 +942,7 @@ static void parse_format(FormatNode *node, 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);
+static void DCH_from_char(FormatNode *node, const char *in, TmFromChar *out);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -963,13 +952,15 @@ static void dump_node(FormatNode *node, int max);
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 int strspace_len(const 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 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_parse_int_len(int *dest, const char **src, const int len, FormatNode *node);
+static int from_char_parse_int(int *dest, const char **src, FormatNode *node);
+static int seq_search(const char *name, const char *const *array, int *len);
+static int from_char_seq_search(int *dest, const char **src,
+ const char *const *array,
+ FormatNode *node);
static void do_to_timestamp(text *date_txt, text *fmt,
struct pg_tm * tm, fsec_t *fsec);
static char *fill_str(char *str, int c, int max);
@@ -2083,7 +2074,7 @@ adjust_partial_year_to_2020(int year)
static int
-strspace_len(char *str)
+strspace_len(const char *str)
{
int len = 0;
@@ -2157,11 +2148,11 @@ from_char_set_int(int *dest, const int value, const FormatNode *node)
* with DD and MI).
*/
static int
-from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
+from_char_parse_int_len(int *dest, const char **src, const int len, FormatNode *node)
{
long result;
char copy[DCH_MAX_ITEM_SIZ + 1];
- char *init = *src;
+ const char *init = *src;
int used;
/*
@@ -2178,8 +2169,11 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node)
* This node is in Fill Mode, or the next node is known to be a
* non-digit value, so we just slurp as many characters as we can get.
*/
+ char *endptr;
+
errno = 0;
- result = strtol(init, src, 10);
+ result = strtol(init, &endptr, 10);
+ *src = endptr;
}
else
{
@@ -2247,76 +2241,61 @@ 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, const char **src, FormatNode *node)
{
return from_char_parse_int_len(dest, src, node->key->len, node);
}
-/* ----------
- * Sequential search with to upper/lower conversion
- * ----------
+/*
+ * Sequentially search null-terminated "array" for a case-insensitive match
+ * to the initial character(s) of "name".
+ *
+ * Returns array index of match, or -1 for no match.
+ *
+ * *len is set to the length of the match, or 0 for no match.
+ *
+ * Case-insensitivity is defined per pg_tolower, so this is only
+ * suitable for comparisons to ASCII strings.
*/
static int
-seq_search(char *name, const char *const * array, int type, int max, int *len)
+seq_search(const char *name, const char *const *array, int *len)
{
- const char *p;
- const char *const * a;
- char *n;
- int last,
- i;
+ unsigned char firstc;
+ const char *const *a;
*len = 0;
+ /* empty string can't match anything */
if (!*name)
return -1;
- /* set first char */
- if (type == ONE_UPPER || type == ALL_UPPER)
- *name = pg_toupper((unsigned char) *name);
- else if (type == ALL_LOWER)
- *name = pg_tolower((unsigned char) *name);
+ /* we handle first char specially to gain some speed */
+ firstc = pg_tolower((unsigned char) *name);
- for (last = 0, a = array; *a != NULL; a++)
+ for (a = array; *a != NULL; a++)
{
+ const char *p;
+ const char *n;
+
/* compare first chars */
- if (*name != **a)
+ if (pg_tolower((unsigned char) **a) != firstc)
continue;
- for (i = 1, p = *a + 1, n = name + 1;; n++, p++, i++)
+ /* compare rest of string */
+ for (p = *a + 1, n = name + 1;; p++, n++)
{
- /* search fragment (max) only */
- if (max && i == max)
- {
- *len = i;
- return a - array;
- }
- /* full size */
+ /* return success if we matched whole array entry */
if (*p == '\0')
{
- *len = i;
+ *len = n - name;
return a - array;
}
- /* Not found in array 'a' */
+ /* else, must have another character in "name" ... */
if (*n == '\0')
break;
-
- /*
- * Convert (but convert new chars only)
- */
- if (i > last)
- {
- if (type == ONE_UPPER || type == ALL_LOWER)
- *n = pg_tolower((unsigned char) *n);
- else if (type == ALL_UPPER)
- *n = pg_toupper((unsigned char) *n);
- last = i;
- }
-
-#ifdef DEBUG_TO_FROM_CHAR
- elog(DEBUG_elog_output, "N: %c, P: %c, A: %s (%s)",
- *n, *p, *a, name);
-#endif
- if (*n != *p)
+ /* ... and it must match */
+ if (pg_tolower((unsigned char) *p) !=
+ pg_tolower((unsigned char) *n))
break;
}
}
@@ -2325,28 +2304,43 @@ seq_search(char *name, const char *const * array, int type, int max, int *len)
}
/*
- * Perform a sequential search in 'array' for text matching the first 'max'
- * characters of the source string.
+ * Perform a sequential search in 'array' for an entry matching the first
+ * character(s) of the 'src' string case-insensitively.
*
* 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.
*
* If the string doesn't match, throw an error.
+ *
+ * 'node' is used only for error reports: node->key->name identifies the
+ * field type we were searching for.
*/
static int
-from_char_seq_search(int *dest, char **src, const char *const * array, int type, int max,
+from_char_seq_search(int *dest, const char **src, const char *const *array,
FormatNode *node)
{
int len;
- *dest = seq_search(*src, array, type, max, &len);
+ *dest = seq_search(*src, array, &len);
+
if (len <= 0)
{
- char copy[DCH_MAX_ITEM_SIZ + 1];
+ /*
+ * In the error report, truncate the string at the next whitespace (if
+ * any) to avoid including irrelevant data.
+ */
+ char *copy = pstrdup(*src);
+ char *c;
- Assert(max <= DCH_MAX_ITEM_SIZ);
- strlcpy(copy, *src, max + 1);
+ for (c = copy; *c; c++)
+ {
+ if (scanner_isspace(*c))
+ {
+ *c = '\0';
+ break;
+ }
+ }
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
@@ -2935,10 +2929,10 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
* ----------
*/
static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, const char *in, TmFromChar *out)
{
FormatNode *n;
- char *s;
+ const char *s;
int len,
value;
bool fx_mode = false;
@@ -2975,7 +2969,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
case DCH_a_m:
case DCH_p_m:
from_char_seq_search(&value, &s, ampm_strings_long,
- ALL_UPPER, n->key->len, n);
+ n);
from_char_set_int(&out->pm, value % 2, n);
out->clock = CLOCK_12_HOUR;
break;
@@ -2984,7 +2978,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
case DCH_am:
case DCH_pm:
from_char_seq_search(&value, &s, ampm_strings,
- ALL_UPPER, n->key->len, n);
+ n);
from_char_set_int(&out->pm, value % 2, n);
out->clock = CLOCK_12_HOUR;
break;
@@ -3043,7 +3037,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
case DCH_a_d:
case DCH_b_c:
from_char_seq_search(&value, &s, adbc_strings_long,
- ALL_UPPER, n->key->len, n);
+ n);
from_char_set_int(&out->bc, value % 2, n);
break;
case DCH_AD:
@@ -3051,21 +3045,21 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
case DCH_ad:
case DCH_bc:
from_char_seq_search(&value, &s, adbc_strings,
- ALL_UPPER, n->key->len, n);
+ n);
from_char_set_int(&out->bc, value % 2, n);
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_seq_search(&value, &s, months_full,
+ n);
from_char_set_int(&out->mm, value + 1, n);
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_seq_search(&value, &s, months,
+ n);
from_char_set_int(&out->mm, value + 1, n);
break;
case DCH_MM:
@@ -3075,16 +3069,16 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
case DCH_DAY:
case DCH_Day:
case DCH_day:
- from_char_seq_search(&value, &s, days, ONE_UPPER,
- MAX_DAY_LEN, n);
+ from_char_seq_search(&value, &s, days,
+ n);
from_char_set_int(&out->d, value, n);
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_seq_search(&value, &s, days,
+ n);
from_char_set_int(&out->d, value, n);
out->d++;
break;
@@ -3183,12 +3177,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
break;
case DCH_RM:
from_char_seq_search(&value, &s, rm_months_upper,
- ALL_UPPER, MAX_RM_LEN, n);
+ n);
from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
break;
case DCH_rm:
from_char_seq_search(&value, &s, rm_months_lower,
- ALL_LOWER, MAX_RM_LEN, n);
+ n);
from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n);
break;
case DCH_W:
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index c4b806977dc..b691e307036 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2808,7 +2808,7 @@ SELECT to_timestamp('2000January09Sunday', 'YYYYFMMonthDDFMDay');
(1 row)
SELECT to_timestamp('97/Feb/16', 'YYMonDD');
-ERROR: invalid value "/Fe" for "Mon"
+ERROR: invalid value "/Feb/16" for "Mon"
DETAIL: The given value did not match any of the allowed values for this field.
SELECT to_timestamp('19971116', 'YYYYMMDD');
to_timestamp