From: Itagaki Takahiro <itagaki.takahiro@gmail.com>
Date: Thu, 22 Apr 2010 01:55:52 +0000 (+0000)
Subject: Fix encoding issue when lc_monetary or lc_numeric are different encoding
X-Git-Tag: REL9_0_BETA1~39
X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=95a777c612c6dcebe2f17c57c35f7e7c93714484;p=postgresql

Fix encoding issue when lc_monetary or lc_numeric are different encoding
from lc_ctype, that could happen on Windows. We need to change lc_ctype
together with lc_monetary or lc_numeric, and convert strings in lconv
from lc_ctype encoding to the database encoding.

The bug reported by Mikko, original patch by Hiroshi Inoue,
with changes by Bruce and me.
---

diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 882356ddbd..171431fbd4 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -4,7 +4,7 @@
  *
  * Portions Copyright (c) 2002-2010, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/pg_locale.c,v 1.53 2010/02/27 20:20:44 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/pg_locale.c,v 1.54 2010/04/22 01:55:52 itagaki Exp $
  *
  *-----------------------------------------------------------------------
  */
@@ -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;
 }