]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/adt/pg_locale.c
Fix encoding issue when lc_monetary or lc_numeric are different encoding
[postgresql] / src / backend / utils / adt / pg_locale.c
index a46e1572840e2b6efc3235dcd004e5919d8d263a..171431fbd4453f4a482f482fefb1d143e12ae6e1 100644 (file)
@@ -2,18 +2,18 @@
  *
  * PostgreSQL locale utilities
  *
- * Portions Copyright (c) 2002-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 2002-2010, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/pg_locale.c,v 1.48 2009/01/27 12:45:09 mha Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/pg_locale.c,v 1.54 2010/04/22 01:55:52 itagaki Exp $
  *
  *-----------------------------------------------------------------------
  */
 
 /*----------
  * Here is how the locale stuff is handled: LC_COLLATE and LC_CTYPE
- * are fixed by initdb, stored in pg_control, and cannot be changed.
- * Thus, the effects of strcoll(), strxfrm(), isupper(), toupper(),
- * etc. are always in the same fixed locale.
+ * are fixed at CREATE DATABASE time, stored in pg_database, and cannot
+ * be changed. Thus, the effects of strcoll(), strxfrm(), isupper(),
+ * toupper(), etc. are always in the same fixed locale.
  *
  * LC_MESSAGES is settable at run time and will take effect
  * immediately.
@@ -93,7 +93,7 @@ static char lc_numeric_envbuf[LC_ENV_BUFSIZE];
 static char lc_time_envbuf[LC_ENV_BUFSIZE];
 
 #if defined(WIN32) && defined(LC_MESSAGES)
-static char *IsoLocaleName(const char *); /* MSVC specific */
+static char *IsoLocaleName(const char *);              /* MSVC specific */
 #endif
 
 
@@ -159,9 +159,9 @@ pg_perm_setlocale(int category, const char *locale)
                        result = IsoLocaleName(locale);
                        if (result == NULL)
                                result = (char *) locale;
-#endif /* WIN32 */
+#endif   /* WIN32 */
                        break;
-#endif /* LC_MESSAGES */
+#endif   /* LC_MESSAGES */
                case LC_MONETARY:
                        envvar = "LC_MONETARY";
                        envbuf = lc_monetary_envbuf;
@@ -387,6 +387,28 @@ free_struct_lconv(struct lconv * s)
 }
 
 
+/*
+ * Return a strdup'ed string converted from the specified encoding to the
+ * database encoding.
+ */
+static char *
+db_encoding_strdup(int encoding, const char *str)
+{
+       char   *pstr;
+       char   *mstr;
+
+       /* convert the string to the database encoding */
+       pstr = (char *) pg_do_encoding_conversion(
+                                               (unsigned char *) str, strlen(str),
+                                               encoding, GetDatabaseEncoding());
+       mstr = strdup(pstr);
+       if (pstr != str)
+               pfree(pstr);
+
+       return mstr;
+}
+
+
 /*
  * Return the POSIX lconv struct (contains number/money formatting
  * information) with locale information for all categories.
@@ -398,6 +420,14 @@ PGLC_localeconv(void)
        struct lconv *extlconv;
        char       *save_lc_monetary;
        char       *save_lc_numeric;
+       char       *decimal_point;
+       char       *grouping;
+       char       *thousands_sep;
+       int                     encoding;
+
+#ifdef WIN32
+       char       *save_lc_ctype;
+#endif
 
        /* Did we do it already? */
        if (CurrentLocaleConvValid)
@@ -413,28 +443,48 @@ PGLC_localeconv(void)
        if (save_lc_numeric)
                save_lc_numeric = pstrdup(save_lc_numeric);
 
-       setlocale(LC_MONETARY, locale_monetary);
+#ifdef WIN32
+       /* set user's value of ctype locale */
+       save_lc_ctype = setlocale(LC_CTYPE, NULL);
+       if (save_lc_ctype)
+               save_lc_ctype = pstrdup(save_lc_ctype);
+#endif
+
+       /* Get formatting information for numeric */
+#ifdef WIN32
+       setlocale(LC_CTYPE, locale_numeric);
+#endif
        setlocale(LC_NUMERIC, locale_numeric);
+       extlconv = localeconv();
+       encoding = pg_get_encoding_from_locale(locale_numeric);
+
+       decimal_point = db_encoding_strdup(encoding, extlconv->decimal_point);
+       thousands_sep = db_encoding_strdup(encoding, extlconv->thousands_sep);
+       grouping = strdup(extlconv->grouping);
 
-       /* Get formatting information */
+       /* Get formatting information for monetary */
+#ifdef WIN32
+       setlocale(LC_CTYPE, locale_monetary);
+#endif
+       setlocale(LC_MONETARY, locale_monetary);
        extlconv = localeconv();
+       encoding = pg_get_encoding_from_locale(locale_monetary);
 
        /*
         * Must copy all values since restoring internal settings may overwrite
         * localeconv()'s results.
         */
        CurrentLocaleConv = *extlconv;
-       CurrentLocaleConv.currency_symbol = strdup(extlconv->currency_symbol);
-       CurrentLocaleConv.decimal_point = strdup(extlconv->decimal_point);
-       CurrentLocaleConv.grouping = strdup(extlconv->grouping);
-       CurrentLocaleConv.thousands_sep = strdup(extlconv->thousands_sep);
-       CurrentLocaleConv.int_curr_symbol = strdup(extlconv->int_curr_symbol);
-       CurrentLocaleConv.mon_decimal_point = strdup(extlconv->mon_decimal_point);
+       CurrentLocaleConv.decimal_point = decimal_point;
+       CurrentLocaleConv.grouping = grouping;
+       CurrentLocaleConv.thousands_sep = thousands_sep;
+       CurrentLocaleConv.int_curr_symbol = db_encoding_strdup(encoding, extlconv->int_curr_symbol);
+       CurrentLocaleConv.currency_symbol = db_encoding_strdup(encoding, extlconv->currency_symbol);
+       CurrentLocaleConv.mon_decimal_point = db_encoding_strdup(encoding, extlconv->mon_decimal_point);
        CurrentLocaleConv.mon_grouping = strdup(extlconv->mon_grouping);
-       CurrentLocaleConv.mon_thousands_sep = strdup(extlconv->mon_thousands_sep);
-       CurrentLocaleConv.negative_sign = strdup(extlconv->negative_sign);
-       CurrentLocaleConv.positive_sign = strdup(extlconv->positive_sign);
-       CurrentLocaleConv.n_sign_posn = extlconv->n_sign_posn;
+       CurrentLocaleConv.mon_thousands_sep = db_encoding_strdup(encoding, extlconv->mon_thousands_sep);
+       CurrentLocaleConv.negative_sign = db_encoding_strdup(encoding, extlconv->negative_sign);
+       CurrentLocaleConv.positive_sign = db_encoding_strdup(encoding, extlconv->positive_sign);
 
        /* Try to restore internal settings */
        if (save_lc_monetary)
@@ -449,6 +499,15 @@ PGLC_localeconv(void)
                pfree(save_lc_numeric);
        }
 
+#ifdef WIN32
+       /* try to restore internal ctype settings */
+       if (save_lc_ctype)
+       {
+               setlocale(LC_CTYPE, save_lc_ctype);
+               pfree(save_lc_ctype);
+       }
+#endif
+
        CurrentLocaleConvValid = true;
        return &CurrentLocaleConv;
 }
@@ -468,28 +527,33 @@ PGLC_localeconv(void)
  * pg_strftime(), which isn't locale-aware and does not need to be replaced.
  */
 static size_t
-strftime_win32(char *dst, size_t dstlen, const wchar_t *format, const struct tm *tm)
+strftime_win32(char *dst, size_t dstlen, const wchar_t *format, const struct tm * tm)
 {
-       size_t  len;
-       wchar_t wbuf[MAX_L10N_DATA];
-       int             encoding;
+       size_t          len;
+       wchar_t         wbuf[MAX_L10N_DATA];
+       int                     encoding;
 
        encoding = GetDatabaseEncoding();
 
        len = wcsftime(wbuf, MAX_L10N_DATA, format, tm);
        if (len == 0)
-               /* strftime call failed - return 0 with the contents of dst unspecified */
+
+               /*
+                * strftime call failed - return 0 with the contents of dst
+                * unspecified
+                */
                return 0;
 
        len = WideCharToMultiByte(CP_UTF8, 0, wbuf, len, dst, dstlen, NULL, NULL);
        if (len == 0)
                elog(ERROR,
-                       "could not convert string to UTF-8:error %lu", GetLastError());
+                        "could not convert string to UTF-8:error %lu", GetLastError());
 
        dst[len] = '\0';
        if (encoding != PG_UTF8)
        {
-               char *convstr = pg_do_encoding_conversion(dst, len, PG_UTF8, encoding);
+               char       *convstr = pg_do_encoding_conversion(dst, len, PG_UTF8, encoding);
+
                if (dst != convstr)
                {
                        strlcpy(dst, convstr, dstlen);
@@ -501,8 +565,7 @@ strftime_win32(char *dst, size_t dstlen, const wchar_t *format, const struct tm
 }
 
 #define strftime(a,b,c,d) strftime_win32(a,b,L##c,d)
-
-#endif /* WIN32 */
+#endif   /* WIN32 */
 
 
 /*
@@ -511,12 +574,13 @@ strftime_win32(char *dst, size_t dstlen, const wchar_t *format, const struct tm
 void
 cache_locale_time(void)
 {
-       char            *save_lc_time;
+       char       *save_lc_time;
        time_t          timenow;
-       struct tm       *timeinfo;
+       struct tm  *timeinfo;
        char            buf[MAX_L10N_DATA];
        char       *ptr;
        int                     i;
+
 #ifdef WIN32
        char       *save_lc_ctype;
 #endif
@@ -611,10 +675,11 @@ cache_locale_time(void)
  *     contains the iso formatted locale name.
  */
 static
-char *IsoLocaleName(const char *winlocname)
+char *
+IsoLocaleName(const char *winlocname)
 {
-#if (_MSC_VER >= 1400) /* VC8.0 or later */
-       static char     iso_lc_messages[32];
+#if (_MSC_VER >= 1400)                 /* VC8.0 or later */
+       static char iso_lc_messages[32];
        _locale_t       loct = NULL;
 
        if (pg_strcasecmp("c", winlocname) == 0 ||
@@ -627,8 +692,9 @@ char *IsoLocaleName(const char *winlocname)
        loct = _create_locale(LC_CTYPE, winlocname);
        if (loct != NULL)
        {
-               char    isolang[32], isocrty[32];
-               LCID    lcid;
+               char            isolang[32],
+                                       isocrty[32];
+               LCID            lcid;
 
                lcid = loct->locinfo->lc_handle[LC_CTYPE];
                if (lcid == 0)
@@ -644,8 +710,8 @@ char *IsoLocaleName(const char *winlocname)
        }
        return NULL;
 #else
-       return NULL; /* Not supported on this version of msvc/mingw */
-#endif /* _MSC_VER >= 1400 */
+       return NULL;                            /* Not supported on this version of msvc/mingw */
+#endif   /* _MSC_VER >= 1400 */
 }
-#endif /* WIN32 && LC_MESSAGES */
 
+#endif   /* WIN32 && LC_MESSAGES */