Change initdb and CREATE DATABASE to actively reject attempts to create
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 28 Sep 2007 22:25:49 +0000 (22:25 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 28 Sep 2007 22:25:49 +0000 (22:25 +0000)
databases with encodings that are incompatible with the server's LC_CTYPE
locale, when we can determine that (which we can on most modern platforms,
I believe).  C/POSIX locale is compatible with all encodings, of course,
so there is still some usefulness to CREATE DATABASE's ENCODING option,
but this will insulate us against all sorts of recurring complaints
caused by mismatched settings.

I moved initdb's existing LC_CTYPE-to-encoding mapping knowledge into
a new src/port/ file so it could be shared by CREATE DATABASE.

doc/src/sgml/charset.sgml
doc/src/sgml/ref/create_database.sgml
src/Makefile.global.in
src/backend/commands/dbcommands.c
src/bin/initdb/initdb.c
src/include/port.h
src/port/Makefile
src/port/chklocale.c [new file with mode: 0644]

index 433dab79517e4b4651813089da01670fca3f14e5..40d172d5d9f5bb74042180d60e6df19951fafdfa 100644 (file)
@@ -249,7 +249,7 @@ initdb --locale=sv_SE
    <title>Problems</>
 
    <para>
-    If locale support doesn't work in spite of the explanation above,
+    If locale support doesn't work according to the explanation above,
     check that the locale support in your operating system is
     correctly configured.  To check what locales are installed on your
     system, you can use the command <literal>locale -a</literal> if
@@ -301,7 +301,8 @@ initdb --locale=sv_SE
 
   <para>
    The character set support in <productname>PostgreSQL</productname>
-   allows you to store text in a variety of character sets, including
+   allows you to store text in a variety of character sets (also called
+   encodings), including
    single-byte character sets such as the ISO 8859 series and
    multiple-byte character sets such as <acronym>EUC</> (Extended Unix
    Code), UTF-8, and Mule internal code.  All supported character sets
@@ -314,6 +315,20 @@ initdb --locale=sv_SE
    databases each with a different character set.
   </para>
 
+  <para>
+   An important restriction, however, is that each database character set
+   must be compatible with the server's <envar>LC_CTYPE</> setting.
+   When <envar>LC_CTYPE</> is <literal>C</> or <literal>POSIX</>, any
+   character set is allowed, but for other settings of <envar>LC_CTYPE</>
+   there is only one character set that will work correctly.
+   Since the <envar>LC_CTYPE</> setting is frozen by <command>initdb</>, the
+   apparent flexibility to use different encodings in different databases
+   of a cluster is more theoretical than real, except when you select
+   <literal>C</> or <literal>POSIX</> locale (thus disabling any real locale
+   awareness).  It is likely that these mechanisms will be revisited in future
+   versions of <productname>PostgreSQL</productname>.
+  </para>
+
    <sect2 id="multibyte-charset-supported">
     <title>Supported Character Sets</title>
 
@@ -716,7 +731,8 @@ initdb -E EUC_JP
     </para>
 
     <para>
-     You can create a database with a different character set:
+     If you have selected <literal>C</> or <literal>POSIX</> locale,
+     you can create a database with a different character set:
 
 <screen>
 createdb -E EUC_KR korean
@@ -731,7 +747,7 @@ CREATE DATABASE korean WITH ENCODING 'EUC_KR';
 </programlisting>
 
      The encoding for a database is stored in the system catalog
-     <literal>pg_database</literal>.  You can see that by using the
+     <literal>pg_database</literal>.  You can see it by using the
      <option>-l</option> option or the <command>\l</command> command
      of <command>psql</command>.
 
@@ -756,26 +772,23 @@ $ <userinput>psql -l</userinput>
 
     <important>
      <para>
-      Although you can specify any encoding you want for a database, it is
-      unwise to choose an encoding that is not what is expected by the locale
-      you have selected.  The <literal>LC_COLLATE</literal> and
-      <literal>LC_CTYPE</literal> settings imply a particular encoding,
-      and locale-dependent operations (such as sorting) are likely to
-      misinterpret data that is in an incompatible encoding.
-     </para>
-
-     <para>
-      Since these locale settings are frozen by <command>initdb</>, the
-      apparent flexibility to use different encodings in different databases
-      of a cluster is more theoretical than real.  It is likely that these
-      mechanisms will be revisited in future versions of
-      <productname>PostgreSQL</productname>.
+      On most modern operating systems, <productname>PostgreSQL</productname>
+      can determine which character set is implied by an <envar>LC_CTYPE</>
+      setting, and it will enforce that only the correct database encoding is
+      used.  On older systems it is your responsibility to ensure that you use
+      the encoding expected by the locale you have selected.  A mistake in
+      this area is likely to lead to strange misbehavior of locale-dependent
+      operations such as sorting.
      </para>
 
      <para>
-      One way to use multiple encodings safely is to set the locale to
-      <literal>C</> or <literal>POSIX</> during <command>initdb</>, thus
-      disabling any real locale awareness.
+      <productname>PostgreSQL</productname> will allow superusers to create
+      databases with <literal>SQL_ASCII</> encoding even when
+      <envar>LC_CTYPE</> is not <literal>C</> or <literal>POSIX</>.  As noted
+      above, <literal>SQL_ASCII</> does not enforce that the data stored in
+      the database has any particular encoding, and so this choice poses risks
+      of locale-dependent misbehavior.  Using this combination of settings is
+      deprecated and may someday be forbidden altogether.
      </para>
     </important>
    </sect2>
index f3edac98f75591b8a89554f0241b34602c3de031..95350c4a1b956dfbf58d16e41f852117173748ed 100644 (file)
@@ -107,7 +107,8 @@ CREATE DATABASE <replaceable class="PARAMETER">name</replaceable>
         to use the default encoding (namely, the encoding of the
         template database). The character sets supported by the
         <productname>PostgreSQL</productname> server are described in
-        <xref linkend="multibyte-charset-supported">.
+        <xref linkend="multibyte-charset-supported">. See below for
+        additional restrictions.
        </para>
       </listitem>
      </varlistentry>
@@ -178,6 +179,21 @@ CREATE DATABASE <replaceable class="PARAMETER">name</replaceable>
    See <xref linkend="manage-ag-templatedbs"> for more information.
   </para>
 
+  <para>
+   Any character set encoding specified for the new database must be
+   compatible with the server's <envar>LC_CTYPE</> locale setting.
+   If <envar>LC_CTYPE</> is <literal>C</> (or equivalently
+   <literal>POSIX</>), then all encodings are allowed, but for other
+   locale settings there is only one encoding that will work properly,
+   and so the apparent freedom to specify an encoding is illusory if
+   you didn't initialize the database cluster in <literal>C</> locale.
+   <command>CREATE DATABASE</> will allow superusers to specify
+   <literal>SQL_ASCII</> encoding regardless of the locale setting,
+   but this choice is deprecated and may result in misbehavior of
+   character-string functions if data that is not encoding-compatible
+   with the locale is stored in the database.
+  </para>
+
   <para>
    The <literal>CONNECTION LIMIT</> option is only enforced approximately;
    if two new sessions start at about the same time when just one
index 9a535f00f3d379a4c7480489581c2d2db3ed6c2d..4733d5882d69cb85863abb29961db0f5a91585f2 100644 (file)
@@ -423,7 +423,7 @@ endif
 #
 # substitute implementations of C library routines (see src/port/)
 
-LIBOBJS = @LIBOBJS@ copydir.o dirmod.o exec.o noblock.o path.o pipe.o pgsleep.o pgstrcasecmp.o qsort.o qsort_arg.o sprompt.o thread.o
+LIBOBJS = @LIBOBJS@
 
 LIBS := -lpgport $(LIBS)
 # add location of libpgport.a to LDFLAGS
index 58d1009dada831da073d4369aaf180f72ce046ed..5b88bc2ab2f96cc01612330f06e71273ac4d7548 100644 (file)
@@ -20,6 +20,7 @@
 #include "postgres.h"
 
 #include <fcntl.h>
+#include <locale.h>
 #include <unistd.h>
 #include <sys/stat.h>
 
@@ -96,6 +97,7 @@ createdb(const CreatedbStmt *stmt)
        const char *dbtemplate = NULL;
        int                     encoding = -1;
        int                     dbconnlimit = -1;
+       int                     ctype_encoding;
 
        /* Extract options from the statement node tree */
        foreach(option, stmt->options)
@@ -254,6 +256,32 @@ createdb(const CreatedbStmt *stmt)
                                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                                 errmsg("invalid server encoding %d", encoding)));
 
+       /*
+        * Check whether encoding matches server locale settings.  We allow
+        * mismatch in two cases:
+        *
+        * 1. ctype_encoding = SQL_ASCII, which means either that the locale
+        * is C/POSIX which works with any encoding, or that we couldn't determine
+        * the locale's encoding and have to trust the user to get it right.
+        *
+        * 2. selected encoding is SQL_ASCII, but only if you're a superuser.
+        * This is risky but we have historically allowed it --- notably, the
+        * regression tests require it.
+        *
+        * Note: if you change this policy, fix initdb to match.
+        */
+       ctype_encoding = pg_get_encoding_from_locale(NULL);
+
+       if (!(ctype_encoding == encoding ||
+                 ctype_encoding == PG_SQL_ASCII ||
+                 (encoding == PG_SQL_ASCII && superuser())))
+               ereport(ERROR,
+                               (errmsg("encoding %s does not match server's locale %s",
+                                               pg_encoding_to_char(encoding),
+                                               setlocale(LC_CTYPE, NULL)),
+                                errdetail("The server's LC_CTYPE setting requires encoding %s.",
+                                                  pg_encoding_to_char(ctype_encoding))));
+
        /* Resolve default tablespace for new database */
        if (dtablespacename && dtablespacename->arg)
        {
index 3fb9bb60f33d4baa0bed27851e7aae3d15bd457d..745fe9e8487289b334bfec8ca0cd758d92035b42 100644 (file)
@@ -54,9 +54,6 @@
 #include <unistd.h>
 #include <locale.h>
 #include <signal.h>
-#ifdef HAVE_LANGINFO_H
-#include <langinfo.h>
-#endif
 #include <time.h>
 
 #include "libpq/pqsignal.h"
@@ -720,197 +717,6 @@ get_encoding_id(char *encoding_name)
        exit(1);
 }
 
-#if defined(HAVE_LANGINFO_H) && defined(CODESET)
-/*
- * Checks whether the encoding selected for PostgreSQL and the
- * encoding used by the system locale match.
- */
-
-struct encoding_match
-{
-       enum pg_enc pg_enc_code;
-       const char *system_enc_name;
-};
-
-static const struct encoding_match encoding_match_list[] = {
-       {PG_EUC_JP, "EUC-JP"},
-       {PG_EUC_JP, "eucJP"},
-       {PG_EUC_JP, "IBM-eucJP"},
-       {PG_EUC_JP, "sdeckanji"},
-
-       {PG_EUC_CN, "EUC-CN"},
-       {PG_EUC_CN, "eucCN"},
-       {PG_EUC_CN, "IBM-eucCN"},
-       {PG_EUC_CN, "GB2312"},
-       {PG_EUC_CN, "dechanzi"},
-
-       {PG_EUC_KR, "EUC-KR"},
-       {PG_EUC_KR, "eucKR"},
-       {PG_EUC_KR, "IBM-eucKR"},
-       {PG_EUC_KR, "deckorean"},
-       {PG_EUC_KR, "5601"},
-
-       {PG_EUC_TW, "EUC-TW"},
-       {PG_EUC_TW, "eucTW"},
-       {PG_EUC_TW, "IBM-eucTW"},
-       {PG_EUC_TW, "cns11643"},
-
-#ifdef NOT_VERIFIED
-       {PG_JOHAB, "???"},
-#endif
-
-       {PG_UTF8, "UTF-8"},
-       {PG_UTF8, "utf8"},
-
-       {PG_LATIN1, "ISO-8859-1"},
-       {PG_LATIN1, "ISO8859-1"},
-       {PG_LATIN1, "iso88591"},
-
-       {PG_LATIN2, "ISO-8859-2"},
-       {PG_LATIN2, "ISO8859-2"},
-       {PG_LATIN2, "iso88592"},
-
-       {PG_LATIN3, "ISO-8859-3"},
-       {PG_LATIN3, "ISO8859-3"},
-       {PG_LATIN3, "iso88593"},
-
-       {PG_LATIN4, "ISO-8859-4"},
-       {PG_LATIN4, "ISO8859-4"},
-       {PG_LATIN4, "iso88594"},
-
-       {PG_LATIN5, "ISO-8859-9"},
-       {PG_LATIN5, "ISO8859-9"},
-       {PG_LATIN5, "iso88599"},
-
-       {PG_LATIN6, "ISO-8859-10"},
-       {PG_LATIN6, "ISO8859-10"},
-       {PG_LATIN6, "iso885910"},
-
-       {PG_LATIN7, "ISO-8859-13"},
-       {PG_LATIN7, "ISO8859-13"},
-       {PG_LATIN7, "iso885913"},
-
-       {PG_LATIN8, "ISO-8859-14"},
-       {PG_LATIN8, "ISO8859-14"},
-       {PG_LATIN8, "iso885914"},
-
-       {PG_LATIN9, "ISO-8859-15"},
-       {PG_LATIN9, "ISO8859-15"},
-       {PG_LATIN9, "iso885915"},
-
-       {PG_LATIN10, "ISO-8859-16"},
-       {PG_LATIN10, "ISO8859-16"},
-       {PG_LATIN10, "iso885916"},
-
-       {PG_WIN1252, "CP1252"},
-       {PG_WIN1253, "CP1253"},
-       {PG_WIN1254, "CP1254"},
-       {PG_WIN1255, "CP1255"},
-       {PG_WIN1256, "CP1256"},
-       {PG_WIN1257, "CP1257"},
-       {PG_WIN1258, "CP1258"},
-#ifdef NOT_VERIFIED
-       {PG_WIN874, "???"},
-#endif
-       {PG_KOI8R, "KOI8-R"},
-       {PG_WIN1251, "CP1251"},
-       {PG_WIN866, "CP866"},
-
-       {PG_ISO_8859_5, "ISO-8859-5"},
-       {PG_ISO_8859_5, "ISO8859-5"},
-       {PG_ISO_8859_5, "iso88595"},
-
-       {PG_ISO_8859_6, "ISO-8859-6"},
-       {PG_ISO_8859_6, "ISO8859-6"},
-       {PG_ISO_8859_6, "iso88596"},
-
-       {PG_ISO_8859_7, "ISO-8859-7"},
-       {PG_ISO_8859_7, "ISO8859-7"},
-       {PG_ISO_8859_7, "iso88597"},
-
-       {PG_ISO_8859_8, "ISO-8859-8"},
-       {PG_ISO_8859_8, "ISO8859-8"},
-       {PG_ISO_8859_8, "iso88598"},
-
-       {PG_SQL_ASCII, NULL}            /* end marker */
-};
-
-static char *
-get_encoding_from_locale(const char *ctype)
-{
-       char       *save;
-       char       *sys;
-
-       save = setlocale(LC_CTYPE, NULL);
-       if (!save)
-               return NULL;
-       save = xstrdup(save);
-
-       setlocale(LC_CTYPE, ctype);
-       sys = nl_langinfo(CODESET);
-       sys = xstrdup(sys);
-
-       setlocale(LC_CTYPE, save);
-       free(save);
-
-       return sys;
-}
-
-static void
-check_encodings_match(int pg_enc, const char *ctype)
-{
-       char       *sys;
-       int                     i;
-
-       sys = get_encoding_from_locale(ctype);
-
-       for (i = 0; encoding_match_list[i].system_enc_name; i++)
-       {
-               if (pg_enc == encoding_match_list[i].pg_enc_code
-                 && pg_strcasecmp(sys, encoding_match_list[i].system_enc_name) == 0)
-               {
-                       free(sys);
-                       return;
-               }
-       }
-
-       fprintf(stderr,
-                       _("%s: warning: encoding mismatch\n"), progname);
-       fprintf(stderr,
-         _("The encoding you selected (%s) and the encoding that the selected\n"
-               "locale uses (%s) are not known to match.  This might lead to\n"
-       "misbehavior in various character string processing functions.  To fix\n"
-               "this situation, rerun %s and either do not specify an encoding\n"
-               "explicitly, or choose a matching combination.\n"),
-                       pg_encoding_to_char(pg_enc), sys, progname);
-
-       free(sys);
-       return;
-}
-
-static int
-find_matching_encoding(const char *ctype)
-{
-       char       *sys;
-       int                     i;
-
-       sys = get_encoding_from_locale(ctype);
-
-       for (i = 0; encoding_match_list[i].system_enc_name; i++)
-       {
-               if (pg_strcasecmp(sys, encoding_match_list[i].system_enc_name) == 0)
-               {
-                       free(sys);
-                       return encoding_match_list[i].pg_enc_code;
-               }
-       }
-
-       free(sys);
-       return -1;
-}
-#endif   /* HAVE_LANGINFO_H && CODESET */
-
-
 /*
  * Support for determining the best default text search configuration.
  * We key this off LC_CTYPE, after stripping its encoding indicator if any.
@@ -2909,10 +2715,6 @@ main(int argc, char *argv[])
        if (strlen(username) == 0)
                username = effective_user;
 
-
-       if (strlen(encoding))
-               encodingid = get_encoding_id(encoding);
-
        set_input(&bki_file, "postgres.bki");
        set_input(&desc_file, "postgres.description");
        set_input(&shdesc_file, "postgres.shdescription");
@@ -2988,32 +2790,58 @@ main(int argc, char *argv[])
                           lc_time);
        }
 
-#if defined(HAVE_LANGINFO_H) && defined(CODESET)
-       if (strcmp(lc_ctype, "C") != 0 && strcmp(lc_ctype, "POSIX") != 0)
+       if (strlen(encoding) == 0)
        {
-               if (strlen(encoding) == 0)
-               {
-                       int                     tmp;
+               int             ctype_enc;
 
-                       tmp = find_matching_encoding(lc_ctype);
-                       if (tmp == -1)
-                       {
-                               fprintf(stderr, _("%s: could not find suitable encoding for locale \"%s\"\n"), progname, lc_ctype);
-                               fprintf(stderr, _("Rerun %s with the -E option.\n"), progname);
-                               fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
-                               exit(1);
-                       }
-                       else
-                       {
-                               encodingid = encodingid_to_string(tmp);
-                               printf(_("The default database encoding has accordingly been set to %s.\n"),
-                                          pg_encoding_to_char(tmp));
-                       }
+               ctype_enc = pg_get_encoding_from_locale(lc_ctype);
+
+               if (ctype_enc == PG_SQL_ASCII &&
+                       !(pg_strcasecmp(lc_ctype, "C") == 0 ||
+                         pg_strcasecmp(lc_ctype, "POSIX") == 0))
+               {
+                       fprintf(stderr, _("%s: could not find suitable encoding for locale \"%s\"\n"),
+                                       progname, lc_ctype);
+                       fprintf(stderr, _("Rerun %s with the -E option.\n"), progname);
+                       fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+                                       progname);
+                       exit(1);
                }
                else
-                       check_encodings_match(atoi(encodingid), lc_ctype);
+               {
+                       encodingid = encodingid_to_string(ctype_enc);
+                       printf(_("The default database encoding has accordingly been set to %s.\n"),
+                                  pg_encoding_to_char(ctype_enc));
+               }
+       }
+       else
+       {
+               int             user_enc;
+               int             ctype_enc;
+
+               encodingid = get_encoding_id(encoding);
+               user_enc = atoi(encodingid);
+
+               ctype_enc = pg_get_encoding_from_locale(lc_ctype);
+
+               /* We allow selection of SQL_ASCII --- see notes in createdb() */
+               if (!(ctype_enc == user_enc ||
+                         ctype_enc == PG_SQL_ASCII ||
+                         user_enc == PG_SQL_ASCII))
+               {
+                       fprintf(stderr, _("%s: encoding mismatch\n"), progname);
+                       fprintf(stderr,
+                       _("The encoding you selected (%s) and the encoding that the\n"
+                         "selected locale uses (%s) do not match.  This would lead to\n"
+                         "misbehavior in various character string processing functions.\n"
+                         "Rerun %s and either do not specify an encoding explicitly,\n"
+                         "or choose a matching combination.\n"),
+                                       pg_encoding_to_char(user_enc),
+                                       pg_encoding_to_char(ctype_enc),
+                                       progname);
+                       exit(1);
+               }
        }
-#endif   /* HAVE_LANGINFO_H && CODESET */
 
        if (strlen(default_text_search_config) == 0)
        {
index 419b14f2e0c87db7c97ebacabaeb94c2bf98fcc4..d7e3613ea83d7ff62b892eb6b82a3cac9cb05eca 100644 (file)
@@ -391,4 +391,7 @@ typedef int (*qsort_arg_comparator) (const void *a, const void *b, void *arg);
 extern void qsort_arg(void *base, size_t nel, size_t elsize,
                  qsort_arg_comparator cmp, void *arg);
 
+/* port/chklocale.c */
+extern int     pg_get_encoding_from_locale(const char *ctype);
+
 #endif   /* PG_PORT_H */
index d7315a2516fe77e90f07f48d0c7f59fe322b09f2..4f12b40ac8ecb757411606e0634f6d20052d1bd7 100644 (file)
 #      libpgport_srv.a - contains object files without FRONTEND defined,
 #              for use only by the backend binaries
 #
+# LIBOBJS is set by configure (via Makefile.global) to be the list of
+# object files that are conditionally needed depending on platform.
+# OBJS adds additional object files that are always compiled.
+#
 # IDENTIFICATION
 #    $PostgreSQL$
 #
@@ -26,8 +30,10 @@ include $(top_builddir)/src/Makefile.global
 override CPPFLAGS := -I$(top_builddir)/src/port -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
-# Replace all object files so they use FRONTEND define
-LIBOBJS_SRV = $(LIBOBJS:%.o=%_srv.o)
+OBJS = $(LIBOBJS) chklocale.o copydir.o dirmod.o exec.o noblock.o path.o pipe.o pgsleep.o pgstrcasecmp.o qsort.o qsort_arg.o sprompt.o thread.o
+
+# foo_srv.o and foo.o are both built from foo.c, but only foo.o has -DFRONTEND
+OBJS_SRV = $(OBJS:%.o=%_srv.o)
 
 all: libpgport.a libpgport_srv.a
 
@@ -41,32 +47,29 @@ installdirs:
 uninstall:
        rm -f '$(DESTDIR)$(libdir)/libpgport.a'
 
-libpgport.a: $(LIBOBJS)
+libpgport.a: $(OBJS)
        $(AR) $(AROPT) $@ $^
 
+# thread.o needs PTHREAD_CFLAGS (but thread_srv.o does not)
 thread.o: thread.c
        $(CC) $(CFLAGS) $(CPPFLAGS) $(PTHREAD_CFLAGS) -c $<
 
-path.o: path.c pg_config_paths.h
-
-path_srv.o: path.c pg_config_paths.h
-
 #
 # Server versions of object files
 #
 
-libpgport_srv.a: $(LIBOBJS_SRV)
+libpgport_srv.a: $(OBJS_SRV)
        $(AR) $(AROPT) $@ $^
 
 %_srv.o: %.c
        $(CC) $(CFLAGS) $(subst -DFRONTEND,, $(CPPFLAGS)) -c $< -o $@
 
-# No thread flags for server version
-thread_srv.o: thread.c
-       $(CC) $(CFLAGS) $(subst -DFRONTEND,, $(CPPFLAGS)) -c $< -o $@
-
 # Dependency is to ensure that path changes propagate
-#
+
+path.o: path.c pg_config_paths.h
+
+path_srv.o: path.c pg_config_paths.h
+
 # We create a separate file rather than put these in pg_config.h
 # because many of these values come from makefiles and are not
 # available to configure.
@@ -84,4 +87,4 @@ pg_config_paths.h: $(top_builddir)/src/Makefile.global
        echo "#define MANDIR \"$(mandir)\"" >>$@
 
 clean distclean maintainer-clean:
-       rm -f libpgport.a libpgport_srv.a $(LIBOBJS) $(LIBOBJS_SRV) pg_config_paths.h
+       rm -f libpgport.a libpgport_srv.a $(OBJS) $(OBJS_SRV) pg_config_paths.h
diff --git a/src/port/chklocale.c b/src/port/chklocale.c
new file mode 100644 (file)
index 0000000..92bf4db
--- /dev/null
@@ -0,0 +1,246 @@
+/*-------------------------------------------------------------------------
+ *
+ * chklocale.c
+ *             Functions for handling locale-related info
+ *
+ *
+ * Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *       $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <locale.h>
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif
+
+#include "mb/pg_wchar.h"
+
+
+#if defined(HAVE_LANGINFO_H) && defined(CODESET)
+
+/*
+ * This table needs to recognize all the CODESET spellings for supported
+ * backend encodings.  We don't need to handle frontend-only encodings.
+ * Note that we search the table with pg_strcasecmp(), so variant
+ * capitalizations don't need their own entries.
+ */
+struct encoding_match
+{
+       enum pg_enc pg_enc_code;
+       const char *system_enc_name;
+};
+
+static const struct encoding_match encoding_match_list[] = {
+       {PG_EUC_JP, "EUC-JP"},
+       {PG_EUC_JP, "eucJP"},
+       {PG_EUC_JP, "IBM-eucJP"},
+       {PG_EUC_JP, "sdeckanji"},
+
+       {PG_EUC_CN, "EUC-CN"},
+       {PG_EUC_CN, "eucCN"},
+       {PG_EUC_CN, "IBM-eucCN"},
+       {PG_EUC_CN, "GB2312"},
+       {PG_EUC_CN, "dechanzi"},
+
+       {PG_EUC_KR, "EUC-KR"},
+       {PG_EUC_KR, "eucKR"},
+       {PG_EUC_KR, "IBM-eucKR"},
+       {PG_EUC_KR, "deckorean"},
+       {PG_EUC_KR, "5601"},
+
+       {PG_EUC_TW, "EUC-TW"},
+       {PG_EUC_TW, "eucTW"},
+       {PG_EUC_TW, "IBM-eucTW"},
+       {PG_EUC_TW, "cns11643"},
+
+       {PG_UTF8, "UTF-8"},
+       {PG_UTF8, "utf8"},
+
+       {PG_LATIN1, "ISO-8859-1"},
+       {PG_LATIN1, "ISO8859-1"},
+       {PG_LATIN1, "iso88591"},
+
+       {PG_LATIN2, "ISO-8859-2"},
+       {PG_LATIN2, "ISO8859-2"},
+       {PG_LATIN2, "iso88592"},
+
+       {PG_LATIN3, "ISO-8859-3"},
+       {PG_LATIN3, "ISO8859-3"},
+       {PG_LATIN3, "iso88593"},
+
+       {PG_LATIN4, "ISO-8859-4"},
+       {PG_LATIN4, "ISO8859-4"},
+       {PG_LATIN4, "iso88594"},
+
+       {PG_LATIN5, "ISO-8859-9"},
+       {PG_LATIN5, "ISO8859-9"},
+       {PG_LATIN5, "iso88599"},
+
+       {PG_LATIN6, "ISO-8859-10"},
+       {PG_LATIN6, "ISO8859-10"},
+       {PG_LATIN6, "iso885910"},
+
+       {PG_LATIN7, "ISO-8859-13"},
+       {PG_LATIN7, "ISO8859-13"},
+       {PG_LATIN7, "iso885913"},
+
+       {PG_LATIN8, "ISO-8859-14"},
+       {PG_LATIN8, "ISO8859-14"},
+       {PG_LATIN8, "iso885914"},
+
+       {PG_LATIN9, "ISO-8859-15"},
+       {PG_LATIN9, "ISO8859-15"},
+       {PG_LATIN9, "iso885915"},
+
+       {PG_LATIN10, "ISO-8859-16"},
+       {PG_LATIN10, "ISO8859-16"},
+       {PG_LATIN10, "iso885916"},
+
+       {PG_KOI8R, "KOI8-R"},
+
+       {PG_WIN1252, "CP1252"},
+       {PG_WIN1253, "CP1253"},
+       {PG_WIN1254, "CP1254"},
+       {PG_WIN1255, "CP1255"},
+       {PG_WIN1256, "CP1256"},
+       {PG_WIN1257, "CP1257"},
+       {PG_WIN1258, "CP1258"},
+#ifdef NOT_VERIFIED
+       {PG_WIN874, "???"},
+#endif
+       {PG_WIN1251, "CP1251"},
+       {PG_WIN866, "CP866"},
+
+       {PG_ISO_8859_5, "ISO-8859-5"},
+       {PG_ISO_8859_5, "ISO8859-5"},
+       {PG_ISO_8859_5, "iso88595"},
+
+       {PG_ISO_8859_6, "ISO-8859-6"},
+       {PG_ISO_8859_6, "ISO8859-6"},
+       {PG_ISO_8859_6, "iso88596"},
+
+       {PG_ISO_8859_7, "ISO-8859-7"},
+       {PG_ISO_8859_7, "ISO8859-7"},
+       {PG_ISO_8859_7, "iso88597"},
+
+       {PG_ISO_8859_8, "ISO-8859-8"},
+       {PG_ISO_8859_8, "ISO8859-8"},
+       {PG_ISO_8859_8, "iso88598"},
+
+       {PG_SQL_ASCII, NULL}            /* end marker */
+};
+
+
+/*
+ * Given a setting for LC_CTYPE, return the Postgres ID of the associated
+ * encoding, if we can determine it.
+ *
+ * Pass in NULL to get the encoding for the current locale setting.
+ *
+ * If the result is PG_SQL_ASCII, callers should treat it as being compatible
+ * with any desired encoding.  We return this if the locale is C/POSIX or we
+ * can't determine the encoding.
+ */
+int
+pg_get_encoding_from_locale(const char *ctype)
+{
+       char       *sys;
+       int                     i;
+
+       if (ctype)
+       {
+               char       *save;
+
+               save = setlocale(LC_CTYPE, NULL);
+               if (!save)
+                       return PG_SQL_ASCII;            /* setlocale() broken? */
+               /* must copy result, or it might change after setlocale */
+               save = strdup(save);
+               if (!save)
+                       return PG_SQL_ASCII;            /* out of memory; unlikely */
+
+               if (!setlocale(LC_CTYPE, ctype))
+               {
+                       free(save);
+                       return PG_SQL_ASCII;            /* bogus ctype passed in? */
+               }
+
+               sys = nl_langinfo(CODESET);
+               if (sys)
+                       sys = strdup(sys);
+
+               setlocale(LC_CTYPE, save);
+               free(save);
+       }
+       else
+       {
+               /* much easier... */
+               ctype = setlocale(LC_CTYPE, NULL);
+               if (!ctype)
+                       return PG_SQL_ASCII;            /* setlocale() broken? */
+               sys = nl_langinfo(CODESET);
+               if (sys)
+                       sys = strdup(sys);
+       }
+
+       if (!sys)
+               return PG_SQL_ASCII;            /* out of memory; unlikely */
+
+       if (pg_strcasecmp(ctype, "C") == 0 || pg_strcasecmp(ctype, "POSIX") == 0)
+       {
+               free(sys);
+               return PG_SQL_ASCII;
+       }
+
+       for (i = 0; encoding_match_list[i].system_enc_name; i++)
+       {
+               if (pg_strcasecmp(sys, encoding_match_list[i].system_enc_name) == 0)
+               {
+                       free(sys);
+                       return encoding_match_list[i].pg_enc_code;
+               }
+       }
+
+       /*
+        * We print a warning if we got a CODESET string but couldn't recognize
+        * it.  This means we need another entry in the table.
+        */
+#ifdef FRONTEND
+       fprintf(stderr, _("could not determine encoding for locale \"%s\": codeset is \"%s\""),
+                       ctype, sys);
+       /* keep newline separate so there's only one translatable string */
+       fputc('\n', stderr);
+#else
+       ereport(WARNING,
+                       (errmsg("could not determine encoding for locale \"%s\": codeset is \"%s\"",
+                                       ctype, sys),
+                        errdetail("Please report this to <pgsql-bugs@postgresql.org>.")));
+#endif
+
+       free(sys);
+       return PG_SQL_ASCII;
+}
+
+#else /* !(HAVE_LANGINFO_H && CODESET) */
+
+/*
+ * stub if no platform support
+ */
+int
+pg_get_encoding_from_locale(const char *ctype)
+{
+       return PG_SQL_ASCII;
+}
+
+#endif /* HAVE_LANGINFO_H && CODESET */