]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/pg_locale.c
Update copyright to 2004.
[postgresql] / src / backend / utils / adt / pg_locale.c
1 /*-----------------------------------------------------------------------
2  *
3  * PostgreSQL locale utilities
4  *
5  * Portions Copyright (c) 2002-2004, PostgreSQL Global Development Group
6  *
7  * $PostgreSQL: pgsql/src/backend/utils/adt/pg_locale.c,v 1.27 2004/08/29 04:12:52 momjian Exp $
8  *
9  *-----------------------------------------------------------------------
10  */
11
12 /*----------
13  * Here is how the locale stuff is handled: LC_COLLATE and LC_CTYPE
14  * are fixed by initdb, stored in pg_control, and cannot be changed.
15  * Thus, the effects of strcoll(), strxfrm(), isupper(), toupper(),
16  * etc. are always in the same fixed locale.
17  *
18  * LC_MESSAGES is settable at run time and will take effect
19  * immediately.
20  *
21  * The other categories, LC_MONETARY, LC_NUMERIC, and LC_TIME are also
22  * settable at run-time.  However, we don't actually set those locale
23  * categories permanently.      This would have bizarre effects like no
24  * longer accepting standard floating-point literals in some locales.
25  * Instead, we only set the locales briefly when needed, cache the
26  * required information obtained from localeconv(), and set them back.
27  * The cached information is only used by the formatting functions
28  * (to_char, etc.) and the money type.  For the user, this should all be
29  * transparent.  (Actually, LC_TIME doesn't do anything at all right
30  * now.)
31  *
32  * !!! NOW HEAR THIS !!!
33  *
34  * We've been bitten repeatedly by this bug, so let's try to keep it in
35  * mind in future: on some platforms, the locale functions return pointers
36  * to static data that will be overwritten by any later locale function.
37  * Thus, for example, the obvious-looking sequence
38  *                      save = setlocale(category, NULL);
39  *                      if (!setlocale(category, value))
40  *                              fail = true;
41  *                      setlocale(category, save);
42  * DOES NOT WORK RELIABLY: on some platforms the second setlocale() call
43  * will change the memory save is pointing at.  To do this sort of thing
44  * safely, you *must* pstrdup what setlocale returns the first time.
45  *----------
46  */
47
48
49 #include "postgres.h"
50
51 #include <locale.h>
52
53 #include "utils/pg_locale.h"
54
55
56 /* indicated whether locale information cache is valid */
57 static bool CurrentLocaleConvValid = false;
58
59
60 /* GUC storage area */
61
62 char       *locale_messages;
63 char       *locale_monetary;
64 char       *locale_numeric;
65 char       *locale_time;
66
67
68 /* GUC assign hooks */
69
70 /*
71  * This is common code for several locale categories.  This doesn't
72  * actually set the locale permanently, it only tests if the locale is
73  * valid.  (See explanation at the top of this file.)
74  */
75 static const char *
76 locale_xxx_assign(int category, const char *value, bool doit, GucSource source)
77 {
78         char       *save;
79
80         save = setlocale(category, NULL);
81         if (!save)
82                 return NULL;                    /* won't happen, we hope */
83
84         /* save may be pointing at a modifiable scratch variable, see above */
85         save = pstrdup(save);
86
87         if (!setlocale(category, value))
88                 value = NULL;                   /* set failure return marker */
89
90         setlocale(category, save);      /* assume this won't fail */
91         pfree(save);
92
93         /* need to reload cache next time? */
94         if (doit && value != NULL)
95                 CurrentLocaleConvValid = false;
96
97         return value;
98 }
99
100
101 const char *
102 locale_monetary_assign(const char *value, bool doit, GucSource source)
103 {
104         return locale_xxx_assign(LC_MONETARY, value, doit, source);
105 }
106
107 const char *
108 locale_numeric_assign(const char *value, bool doit, GucSource source)
109 {
110         return locale_xxx_assign(LC_NUMERIC, value, doit, source);
111 }
112
113 const char *
114 locale_time_assign(const char *value, bool doit, GucSource source)
115 {
116         return locale_xxx_assign(LC_TIME, value, doit, source);
117 }
118
119
120 /*
121  * We allow LC_MESSAGES to actually be set globally.
122  */
123 const char *
124 locale_messages_assign(const char *value, bool doit, GucSource source)
125 {
126         /*
127          * LC_MESSAGES category does not exist everywhere, but accept it
128          * anyway
129          */
130 #ifdef LC_MESSAGES
131         if (doit)
132         {
133                 if (!setlocale(LC_MESSAGES, value))
134                 {
135 #ifdef WIN32
136                         /*
137                          * Win32 returns NULL when you set LC_MESSAGES to "".  So don't
138                          * complain unless we're trying to set it to something else.
139                          */
140                         if (value[0])
141                                 return NULL;
142 #else
143                         return NULL;
144 #endif
145                 }
146         }
147         else
148                 value = locale_xxx_assign(LC_MESSAGES, value, false, source);
149 #endif /* LC_MESSAGES */
150         return value;
151 }
152
153
154 /*
155  * We'd like to cache whether LC_COLLATE is C (or POSIX), so we can
156  * optimize a few code paths in various places.
157  */
158 bool
159 lc_collate_is_c(void)
160 {
161         /* Cache result so we only have to compute it once */
162         static int      result = -1;
163         char       *localeptr;
164
165         if (result >= 0)
166                 return (bool) result;
167         localeptr = setlocale(LC_COLLATE, NULL);
168         if (!localeptr)
169                 elog(ERROR, "invalid LC_COLLATE setting");
170
171         if (strcmp(localeptr, "C") == 0)
172                 result = true;
173         else if (strcmp(localeptr, "POSIX") == 0)
174                 result = true;
175         else
176                 result = false;
177         return (bool) result;
178 }
179
180
181 /*
182  * Frees the malloced content of a struct lconv.  (But not the struct
183  * itself.)
184  */
185 static void
186 free_struct_lconv(struct lconv * s)
187 {
188         if (s == NULL)
189                 return;
190
191         if (s->currency_symbol)
192                 free(s->currency_symbol);
193         if (s->decimal_point)
194                 free(s->decimal_point);
195         if (s->grouping)
196                 free(s->grouping);
197         if (s->thousands_sep)
198                 free(s->thousands_sep);
199         if (s->int_curr_symbol)
200                 free(s->int_curr_symbol);
201         if (s->mon_decimal_point)
202                 free(s->mon_decimal_point);
203         if (s->mon_grouping)
204                 free(s->mon_grouping);
205         if (s->mon_thousands_sep)
206                 free(s->mon_thousands_sep);
207         if (s->negative_sign)
208                 free(s->negative_sign);
209         if (s->positive_sign)
210                 free(s->positive_sign);
211 }
212
213
214 /*
215  * Return the POSIX lconv struct (contains number/money formatting
216  * information) with locale information for all categories.
217  */
218 struct lconv *
219 PGLC_localeconv(void)
220 {
221         static struct lconv CurrentLocaleConv;
222         struct lconv *extlconv;
223         char       *save_lc_monetary;
224         char       *save_lc_numeric;
225
226         /* Did we do it already? */
227         if (CurrentLocaleConvValid)
228                 return &CurrentLocaleConv;
229
230         free_struct_lconv(&CurrentLocaleConv);
231
232         /* Set user's values of monetary and numeric locales */
233         save_lc_monetary = setlocale(LC_MONETARY, NULL);
234         if (save_lc_monetary)
235                 save_lc_monetary = pstrdup(save_lc_monetary);
236         save_lc_numeric = setlocale(LC_NUMERIC, NULL);
237         if (save_lc_numeric)
238                 save_lc_numeric = pstrdup(save_lc_numeric);
239
240         setlocale(LC_MONETARY, locale_monetary);
241         setlocale(LC_NUMERIC, locale_numeric);
242
243         /* Get formatting information */
244         extlconv = localeconv();
245
246         /*
247          * Must copy all values since restoring internal settings may
248          * overwrite localeconv()'s results.
249          */
250         CurrentLocaleConv = *extlconv;
251         CurrentLocaleConv.currency_symbol = strdup(extlconv->currency_symbol);
252         CurrentLocaleConv.decimal_point = strdup(extlconv->decimal_point);
253         CurrentLocaleConv.grouping = strdup(extlconv->grouping);
254         CurrentLocaleConv.thousands_sep = strdup(extlconv->thousands_sep);
255         CurrentLocaleConv.int_curr_symbol = strdup(extlconv->int_curr_symbol);
256         CurrentLocaleConv.mon_decimal_point = strdup(extlconv->mon_decimal_point);
257         CurrentLocaleConv.mon_grouping = strdup(extlconv->mon_grouping);
258         CurrentLocaleConv.mon_thousands_sep = strdup(extlconv->mon_thousands_sep);
259         CurrentLocaleConv.negative_sign = strdup(extlconv->negative_sign);
260         CurrentLocaleConv.positive_sign = strdup(extlconv->positive_sign);
261         CurrentLocaleConv.n_sign_posn = extlconv->n_sign_posn;
262
263         /* Try to restore internal settings */
264         if (save_lc_monetary)
265         {
266                 setlocale(LC_MONETARY, save_lc_monetary);
267                 pfree(save_lc_monetary);
268         }
269
270         if (save_lc_numeric)
271         {
272                 setlocale(LC_NUMERIC, save_lc_numeric);
273                 pfree(save_lc_numeric);
274         }
275
276         CurrentLocaleConvValid = true;
277         return &CurrentLocaleConv;
278 }