]> granicus.if.org Git - postgresql/commitdiff
Fix to_char() to use ASCII-only case-folding rules where appropriate.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 5 Mar 2013 18:02:35 +0000 (13:02 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 5 Mar 2013 18:02:35 +0000 (13:02 -0500)
formatting.c used locale-dependent case folding rules in some code paths
where the result isn't supposed to be locale-dependent, for example
to_char(timestamp, 'DAY').  Since the source data is always just ASCII
in these cases, that usually didn't matter ... but it does matter in
Turkish locales, which have unusual treatment of "i" and "I".  To confuse
matters even more, the misbehavior was only visible in UTF8 encoding,
because in single-byte encodings we used pg_toupper/pg_tolower which
don't have locale-specific behavior for ASCII characters.  Fix by providing
intentionally ASCII-only case-folding functions and using these where
appropriate.  Per bug #7913 from Adnan Dursun.  Back-patch to all active
branches, since it's been like this for a long time.

src/backend/utils/adt/formatting.c
src/include/utils/formatting.h

index 65bca38b2dd93907be105fa8c6243c14b30fa1fd..0db32066dd95254581972f36513cf2a2c26fb055 100644 (file)
@@ -1493,12 +1493,7 @@ str_tolower(const char *buff, size_t nbytes, Oid collid)
        /* C/POSIX collations use this path regardless of database encoding */
        if (lc_ctype_is_c(collid))
        {
-               char       *p;
-
-               result = pnstrdup(buff, nbytes);
-
-               for (p = result; *p; p++)
-                       *p = pg_ascii_tolower((unsigned char) *p);
+               result = asc_tolower(buff, nbytes);
        }
 #ifdef USE_WIDE_UPPER_LOWER
        else if (pg_database_encoding_max_length() > 1)
@@ -1618,12 +1613,7 @@ str_toupper(const char *buff, size_t nbytes, Oid collid)
        /* C/POSIX collations use this path regardless of database encoding */
        if (lc_ctype_is_c(collid))
        {
-               char       *p;
-
-               result = pnstrdup(buff, nbytes);
-
-               for (p = result; *p; p++)
-                       *p = pg_ascii_toupper((unsigned char) *p);
+               result = asc_toupper(buff, nbytes);
        }
 #ifdef USE_WIDE_UPPER_LOWER
        else if (pg_database_encoding_max_length() > 1)
@@ -1744,23 +1734,7 @@ str_initcap(const char *buff, size_t nbytes, Oid collid)
        /* C/POSIX collations use this path regardless of database encoding */
        if (lc_ctype_is_c(collid))
        {
-               char       *p;
-
-               result = pnstrdup(buff, nbytes);
-
-               for (p = result; *p; p++)
-               {
-                       char            c;
-
-                       if (wasalnum)
-                               *p = c = pg_ascii_tolower((unsigned char) *p);
-                       else
-                               *p = c = pg_ascii_toupper((unsigned char) *p);
-                       /* we don't trust isalnum() here */
-                       wasalnum = ((c >= 'A' && c <= 'Z') ||
-                                               (c >= 'a' && c <= 'z') ||
-                                               (c >= '0' && c <= '9'));
-               }
+               result = asc_initcap(buff, nbytes);
        }
 #ifdef USE_WIDE_UPPER_LOWER
        else if (pg_database_encoding_max_length() > 1)
@@ -1887,6 +1861,87 @@ str_initcap(const char *buff, size_t nbytes, Oid collid)
        return result;
 }
 
+/*
+ * ASCII-only lower function
+ *
+ * We pass the number of bytes so we can pass varlena and char*
+ * to this function.  The result is a palloc'd, null-terminated string.
+ */
+char *
+asc_tolower(const char *buff, size_t nbytes)
+{
+       char       *result;
+       char       *p;
+
+       if (!buff)
+               return NULL;
+
+       result = pnstrdup(buff, nbytes);
+
+       for (p = result; *p; p++)
+               *p = pg_ascii_tolower((unsigned char) *p);
+
+       return result;
+}
+
+/*
+ * ASCII-only upper function
+ *
+ * We pass the number of bytes so we can pass varlena and char*
+ * to this function.  The result is a palloc'd, null-terminated string.
+ */
+char *
+asc_toupper(const char *buff, size_t nbytes)
+{
+       char       *result;
+       char       *p;
+
+       if (!buff)
+               return NULL;
+
+       result = pnstrdup(buff, nbytes);
+
+       for (p = result; *p; p++)
+               *p = pg_ascii_toupper((unsigned char) *p);
+
+       return result;
+}
+
+/*
+ * ASCII-only initcap function
+ *
+ * We pass the number of bytes so we can pass varlena and char*
+ * to this function.  The result is a palloc'd, null-terminated string.
+ */
+char *
+asc_initcap(const char *buff, size_t nbytes)
+{
+       char       *result;
+       char       *p;
+       int                     wasalnum = false;
+
+       if (!buff)
+               return NULL;
+
+       result = pnstrdup(buff, nbytes);
+
+       for (p = result; *p; p++)
+       {
+               char            c;
+
+               if (wasalnum)
+                       *p = c = pg_ascii_tolower((unsigned char) *p);
+               else
+                       *p = c = pg_ascii_toupper((unsigned char) *p);
+               /* we don't trust isalnum() here */
+               wasalnum = ((c >= 'A' && c <= 'Z') ||
+                                       (c >= 'a' && c <= 'z') ||
+                                       (c >= '0' && c <= '9'));
+       }
+
+       return result;
+}
+
 /* convenience routines for when the input is null-terminated */
 
 static char *
@@ -1907,6 +1962,20 @@ str_initcap_z(const char *buff, Oid collid)
        return str_initcap(buff, strlen(buff), collid);
 }
 
+static char *
+asc_tolower_z(const char *buff)
+{
+       return asc_tolower(buff, strlen(buff));
+}
+
+static char *
+asc_toupper_z(const char *buff)
+{
+       return asc_toupper(buff, strlen(buff));
+}
+
+/* asc_initcap_z is not currently needed */
+
 
 /* ----------
  * Skip TM / th in FROM_CHAR
@@ -2419,7 +2488,8 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                                INVALID_FOR_INTERVAL;
                                if (tmtcTzn(in))
                                {
-                                       char       *p = str_tolower_z(tmtcTzn(in), collid);
+                                       /* We assume here that timezone names aren't localized */
+                                       char       *p = asc_tolower_z(tmtcTzn(in));
 
                                        strcpy(s, p);
                                        pfree(p);
@@ -2466,7 +2536,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                                        strcpy(s, str_toupper_z(localized_full_months[tm->tm_mon - 1], collid));
                                else
                                        sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
-                                                str_toupper_z(months_full[tm->tm_mon - 1], collid));
+                                                asc_toupper_z(months_full[tm->tm_mon - 1]));
                                s += strlen(s);
                                break;
                        case DCH_Month:
@@ -2476,7 +2546,8 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                                if (S_TM(n->suffix))
                                        strcpy(s, str_initcap_z(localized_full_months[tm->tm_mon - 1], collid));
                                else
-                                       sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, months_full[tm->tm_mon - 1]);
+                                       sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
+                                                       months_full[tm->tm_mon - 1]);
                                s += strlen(s);
                                break;
                        case DCH_month:
@@ -2486,10 +2557,8 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                                if (S_TM(n->suffix))
                                        strcpy(s, str_tolower_z(localized_full_months[tm->tm_mon - 1], collid));
                                else
-                               {
-                                       sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, months_full[tm->tm_mon - 1]);
-                                       *s = pg_tolower((unsigned char) *s);
-                               }
+                                       sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
+                                                       asc_tolower_z(months_full[tm->tm_mon - 1]));
                                s += strlen(s);
                                break;
                        case DCH_MON:
@@ -2499,7 +2568,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                                if (S_TM(n->suffix))
                                        strcpy(s, str_toupper_z(localized_abbrev_months[tm->tm_mon - 1], collid));
                                else
-                                       strcpy(s, str_toupper_z(months[tm->tm_mon - 1], collid));
+                                       strcpy(s, asc_toupper_z(months[tm->tm_mon - 1]));
                                s += strlen(s);
                                break;
                        case DCH_Mon:
@@ -2519,10 +2588,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                                if (S_TM(n->suffix))
                                        strcpy(s, str_tolower_z(localized_abbrev_months[tm->tm_mon - 1], collid));
                                else
-                               {
-                                       strcpy(s, months[tm->tm_mon - 1]);
-                                       *s = pg_tolower((unsigned char) *s);
-                               }
+                                       strcpy(s, asc_tolower_z(months[tm->tm_mon - 1]));
                                s += strlen(s);
                                break;
                        case DCH_MM:
@@ -2537,7 +2603,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                                        strcpy(s, str_toupper_z(localized_full_days[tm->tm_wday], collid));
                                else
                                        sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
-                                                       str_toupper_z(days[tm->tm_wday], collid));
+                                                       asc_toupper_z(days[tm->tm_wday]));
                                s += strlen(s);
                                break;
                        case DCH_Day:
@@ -2545,7 +2611,8 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                                if (S_TM(n->suffix))
                                        strcpy(s, str_initcap_z(localized_full_days[tm->tm_wday], collid));
                                else
-                                       sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, days[tm->tm_wday]);
+                                       sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
+                                                       days[tm->tm_wday]);
                                s += strlen(s);
                                break;
                        case DCH_day:
@@ -2553,10 +2620,8 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                                if (S_TM(n->suffix))
                                        strcpy(s, str_tolower_z(localized_full_days[tm->tm_wday], collid));
                                else
-                               {
-                                       sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, days[tm->tm_wday]);
-                                       *s = pg_tolower((unsigned char) *s);
-                               }
+                                       sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9,
+                                                       asc_tolower_z(days[tm->tm_wday]));
                                s += strlen(s);
                                break;
                        case DCH_DY:
@@ -2564,7 +2629,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                                if (S_TM(n->suffix))
                                        strcpy(s, str_toupper_z(localized_abbrev_days[tm->tm_wday], collid));
                                else
-                                       strcpy(s, str_toupper_z(days_short[tm->tm_wday], collid));
+                                       strcpy(s, asc_toupper_z(days_short[tm->tm_wday]));
                                s += strlen(s);
                                break;
                        case DCH_Dy:
@@ -2580,10 +2645,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                                if (S_TM(n->suffix))
                                        strcpy(s, str_tolower_z(localized_abbrev_days[tm->tm_wday], collid));
                                else
-                               {
-                                       strcpy(s, days_short[tm->tm_wday]);
-                                       *s = pg_tolower((unsigned char) *s);
-                               }
+                                       strcpy(s, asc_tolower_z(days_short[tm->tm_wday]));
                                s += strlen(s);
                                break;
                        case DCH_DDD:
@@ -4670,12 +4732,12 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number,
                                case NUM_rn:
                                        if (IS_FILLMODE(Np->Num))
                                        {
-                                               strcpy(Np->inout_p, str_tolower_z(Np->number_p, collid));
+                                               strcpy(Np->inout_p, asc_tolower_z(Np->number_p));
                                                Np->inout_p += strlen(Np->inout_p) - 1;
                                        }
                                        else
                                        {
-                                               sprintf(Np->inout_p, "%15s", str_tolower_z(Np->number_p, collid));
+                                               sprintf(Np->inout_p, "%15s", asc_tolower_z(Np->number_p));
                                                Np->inout_p += strlen(Np->inout_p) - 1;
                                        }
                                        break;
index ce571957fe61540419827c39cbfc362a82356700..b59469276d02c46443dc130a561445aa1249da7f 100644 (file)
@@ -24,6 +24,10 @@ extern char *str_tolower(const char *buff, size_t nbytes, Oid collid);
 extern char *str_toupper(const char *buff, size_t nbytes, Oid collid);
 extern char *str_initcap(const char *buff, size_t nbytes, Oid collid);
 
+extern char *asc_tolower(const char *buff, size_t nbytes);
+extern char *asc_toupper(const char *buff, size_t nbytes);
+extern char *asc_initcap(const char *buff, size_t nbytes);
+
 extern Datum timestamp_to_char(PG_FUNCTION_ARGS);
 extern Datum timestamptz_to_char(PG_FUNCTION_ARGS);
 extern Datum interval_to_char(PG_FUNCTION_ARGS);