diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 2d15e78fd08441d7e9113ba4d594dba0135389c6..49edc51dbaca20e5f5eacd77450cade4309e3b2e 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -259,6 +259,21 @@ PGconn *PQconnectdbParams(const char **keywords, const char **values, int expand </listitem> </varlistentry> + <varlistentry id="libpq-connect-client-encoding" xreflabel="client_encoding"> + <term><literal>client_encoding</literal></term> + <listitem> + <para> + This sets the <varname>client_encoding</varname> + configuration parameter for this connection. In addition to + the values accepted by the corresponding server option, you + can use <literal>auto</literal> to determine the right + encoding from the current locale in the client + (<envar>LC_CTYPE</envar> environment variable on Unix + systems). + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-options" xreflabel="options"> <term><literal>options</literal></term> <listitem> @@ -6345,6 +6360,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) linkend="libpq-connect-connect-timeout"> connection parameter. </para> </listitem> + + <listitem> + <para> + <indexterm> + <primary><envar>PGCLIENTENCODING</envar></primary> + </indexterm> + <envar>PGCLIENTENCODING</envar> behaves the same as the <xref + linkend="libpq-connect-client-encoding"> connection parameter. + </para> + </listitem> </itemizedlist> </para> @@ -6378,17 +6403,6 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) </para> </listitem> - <listitem> - <para> - <indexterm> - <primary><envar>PGCLIENTENCODING</envar></primary> - </indexterm> - <envar>PGCLIENTENCODING</envar> sets the default client character - set encoding. (Equivalent to <literal>SET client_encoding TO - ...</literal>.) - </para> - </listitem> - <listitem> <para> <indexterm> diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index ff60a72059ee17ea3c9050b076498837a559cce8..baefb578826c1ea0698c033ad082c9bd5ade7513 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -593,6 +593,17 @@ $ <userinput>psql "service=myservice sslmode=require"</userinput> privileges, server is not running on the targeted host, etc.), <application>psql</application> will return an error and terminate. </para> + + <para> + If at least one of standard input or standard output are a + terminal, then <application>psql</application> sets the client + encoding to <quote>auto</quote>, which will detect the + appropriate client encoding from the locale settings + (<envar>LC_CTYPE</envar> environment variable on Unix systems). + If this doesn't work out as expected, the client encoding can be + overridden using the environment + variable <envar>PGCLIENTENCODING</envar>. + </para> </refsect2> <refsect2 id="R2-APP-PSQL-4"> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index d1268848d5b8bb550e860bdb2b209ed127eac0c1..d7cdcf64344ef70129588b39946f9069c06c371b 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -1487,7 +1487,7 @@ do_connect(char *dbname, char *user, char *host, char *port) while (true) { -#define PARAMS_ARRAY_SIZE 7 +#define PARAMS_ARRAY_SIZE 8 const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords)); const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values)); @@ -1503,8 +1503,10 @@ do_connect(char *dbname, char *user, char *host, char *port) values[4] = dbname; keywords[5] = "fallback_application_name"; values[5] = pset.progname; - keywords[6] = NULL; - values[6] = NULL; + keywords[6] = "client_encoding"; + values[6] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto"; + keywords[7] = NULL; + values[7] = NULL; n_conn = PQconnectdbParams(keywords, values, true); diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 10713e9b15d6d5757410f75647caeaa363e51c1c..7b8078c21e4b0d22152f1d6d0fc3ae17897adbae 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -171,7 +171,7 @@ main(int argc, char *argv[]) /* loop until we have a password if requested by backend */ do { -#define PARAMS_ARRAY_SIZE 7 +#define PARAMS_ARRAY_SIZE 8 const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords)); const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values)); @@ -189,8 +189,10 @@ main(int argc, char *argv[]) "postgres" : options.dbname; keywords[5] = "fallback_application_name"; values[5] = pset.progname; - keywords[6] = NULL; - values[6] = NULL; + keywords[6] = "client_encoding"; + values[6] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto"; + keywords[7] = NULL; + values[7] = NULL; new_pass = false; pset.db = PQconnectdbParams(keywords, values, true); diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore index 366adeb9080a49564b32f6a9d2dfcb0cd6dadf37..29024ae67ce1cec97f38746b641eb4d0b750fc89 100644 --- a/src/interfaces/libpq/.gitignore +++ b/src/interfaces/libpq/.gitignore @@ -1,4 +1,5 @@ /exports.list +/chklocale.c /crypt.c /getaddrinfo.c /inet_aton.c diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index f4111c4b766c629bdfb1c2ec8686d5d9257f36d0..18795446d5b93c1fb8f2f9085aaa297d8770f08b 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -35,7 +35,7 @@ OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \ fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \ libpq-events.o # libpgport C files we always use -OBJS += inet_net_ntop.o noblock.o pgstrcasecmp.o thread.o +OBJS += chklocale.o inet_net_ntop.o noblock.o pgstrcasecmp.o thread.o # libpgport C files that are needed if identified by configure OBJS += $(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS)) # backend/libpq @@ -88,7 +88,7 @@ backend_src = $(top_srcdir)/src/backend # For some libpgport modules, this only happens if configure decides # the module is needed (see filter hack in OBJS, above). -crypt.c getaddrinfo.c inet_aton.c inet_net_ntop.c noblock.c open.c pgsleep.c pgstrcasecmp.c snprintf.c strerror.c strlcpy.c thread.c win32error.c: % : $(top_srcdir)/src/port/% +chklocale.c crypt.c getaddrinfo.c inet_aton.c inet_net_ntop.c noblock.c open.c pgsleep.c pgstrcasecmp.c snprintf.c strerror.c strlcpy.c thread.c win32error.c: % : $(top_srcdir)/src/port/% rm -f $@ && $(LN_S) $< . ip.c md5.c: % : $(backend_src)/libpq/% @@ -135,7 +135,7 @@ clean distclean: clean-lib # Might be left over from a Win32 client-only build rm -f pg_config_paths.h rm -f inet_net_ntop.c noblock.c pgstrcasecmp.c thread.c - rm -f crypt.c getaddrinfo.c inet_aton.c open.c snprintf.c strerror.c strlcpy.c win32error.c + rm -f chklocale.c crypt.c getaddrinfo.c inet_aton.c open.c snprintf.c strerror.c strlcpy.c win32error.c rm -f pgsleep.c rm -f md5.c ip.c rm -f encnames.c wchar.c diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index b8013ed42fcda4e5961d5ab77868e7c424c17b00..7766c7eabae425aabf9669ccae0542550e01fa1c 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -175,6 +175,9 @@ static const PQconninfoOption PQconninfoOptions[] = { {"port", "PGPORT", DEF_PGPORT_STR, NULL, "Database-Port", "", 6}, + {"client_encoding", "PGCLIENTENCODING", NULL, NULL, + "Client-Encoding", "", 10}, + /* * "tty" is no longer used either, but keep it present for backwards * compatibility. @@ -270,9 +273,6 @@ static const PQEnvironmentOption EnvironmentOptions[] = { "PGTZ", "timezone" }, - { - "PGCLIENTENCODING", "client_encoding" - }, /* internal performance-related settings */ { "PGGEQO", "geqo" @@ -612,6 +612,8 @@ fillPGconn(PGconn *conn, PQconninfoOption *connOptions) conn->pgpass = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "connect_timeout"); conn->connect_timeout = tmp ? strdup(tmp) : NULL; + tmp = conninfo_getval(connOptions, "client_encoding"); + conn->client_encoding_initial = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "keepalives"); conn->keepalives = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "keepalives_idle"); @@ -786,6 +788,16 @@ connectOptions2(PGconn *conn) else conn->sslmode = strdup(DefaultSSLMode); + /* + * Resolve special "auto" client_encoding from the locale + */ + if (conn->client_encoding_initial && + strcmp(conn->client_encoding_initial, "auto") == 0) + { + free(conn->client_encoding_initial); + conn->client_encoding_initial = strdup(pg_encoding_to_char(pg_get_encoding_from_locale(NULL, true))); + } + /* * Only if we get this far is it appropriate to try to connect. (We need a * state flag, rather than just the boolean result of this function, in @@ -2508,7 +2520,7 @@ keep_going: /* We will come back to here until there is if (PG_PROTOCOL_MAJOR(conn->pversion) < 3) { conn->status = CONNECTION_SETENV; - conn->setenv_state = SETENV_STATE_OPTION_SEND; + conn->setenv_state = SETENV_STATE_CLIENT_ENCODING_SEND; conn->next_eo = EnvironmentOptions; return PGRES_POLLING_WRITING; } @@ -4661,6 +4673,10 @@ PQsetClientEncoding(PGconn *conn, const char *encoding) if (!encoding) return -1; + /* Resolve special "auto" value from the locale */ + if (strcmp(encoding, "auto") == 0) + encoding = pg_encoding_to_char(pg_get_encoding_from_locale(NULL, true)); + /* check query buffer overflow */ if (sizeof(qbuf) < (sizeof(query) + strlen(encoding))) return -1; diff --git a/src/interfaces/libpq/fe-protocol2.c b/src/interfaces/libpq/fe-protocol2.c index 058a25b8033ebf656e433b651d86a989599e3a99..05357de1c74f81d96e507e71a78274e9f4cc3ef7 100644 --- a/src/interfaces/libpq/fe-protocol2.c +++ b/src/interfaces/libpq/fe-protocol2.c @@ -58,6 +58,7 @@ pqSetenvPoll(PGconn *conn) switch (conn->setenv_state) { /* These are reading states */ + case SETENV_STATE_CLIENT_ENCODING_WAIT: case SETENV_STATE_OPTION_WAIT: case SETENV_STATE_QUERY1_WAIT: case SETENV_STATE_QUERY2_WAIT: @@ -74,6 +75,7 @@ pqSetenvPoll(PGconn *conn) } /* These are writing states, so we just proceed. */ + case SETENV_STATE_CLIENT_ENCODING_SEND: case SETENV_STATE_OPTION_SEND: case SETENV_STATE_QUERY1_SEND: case SETENV_STATE_QUERY2_SEND: @@ -98,6 +100,39 @@ pqSetenvPoll(PGconn *conn) { switch (conn->setenv_state) { + /* + * The _CLIENT_ENCODING_SEND code is slightly different + * from _OPTION_SEND below (e.g., no getenv() call), which + * is why a different state is used. + */ + case SETENV_STATE_CLIENT_ENCODING_SEND: + { + char setQuery[100]; /* note length limit in + * sprintf below */ + const char *val = conn->client_encoding_initial; + + if (val) + { + if (pg_strcasecmp(val, "default") == 0) + sprintf(setQuery, "SET client_encoding = DEFAULT"); + else + sprintf(setQuery, "SET client_encoding = '%.60s'", + val); +#ifdef CONNECTDEBUG + fprintf(stderr, + "Sending client_encoding with %s\n", + setQuery); +#endif + if (!PQsendQuery(conn, setQuery)) + goto error_return; + + conn->setenv_state = SETENV_STATE_CLIENT_ENCODING_WAIT; + } + else + conn->setenv_state = SETENV_STATE_OPTION_SEND; + break; + } + case SETENV_STATE_OPTION_SEND: { /* @@ -142,6 +177,31 @@ pqSetenvPoll(PGconn *conn) break; } + case SETENV_STATE_CLIENT_ENCODING_WAIT: + { + if (PQisBusy(conn)) + return PGRES_POLLING_READING; + + res = PQgetResult(conn); + + if (res) + { + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + goto error_return; + } + PQclear(res); + /* Keep reading until PQgetResult returns NULL */ + } + else + { + /* Query finished, so send the next option */ + conn->setenv_state = SETENV_STATE_OPTION_SEND; + } + break; + } + case SETENV_STATE_OPTION_WAIT: { if (PQisBusy(conn)) diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 2a8dbdfd3aa5edfc960db19dd0aa3c4c83b9deef..cf0b91a9bc1b2d5cbc84def4526e92958fe26a33 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -1933,6 +1933,9 @@ build_startup_packet(const PGconn *conn, char *packet, ADD_STARTUP_OPTION("application_name", val); } + if (conn->client_encoding_initial && conn->client_encoding_initial[0]) + ADD_STARTUP_OPTION("client_encoding", conn->client_encoding_initial); + /* Add any environment-driven GUC settings needed */ for (next_eo = options; next_eo->envName; next_eo++) { diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index e9a2b718d33d762691e95f099aca4af9308a46f0..25c779acd7ef2c0a29f8e55885f3f478dcaafe37 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -235,6 +235,8 @@ typedef enum /* (this is used only for 2.0-protocol connections) */ typedef enum { + SETENV_STATE_CLIENT_ENCODING_SEND, /* About to send an Environment Option */ + SETENV_STATE_CLIENT_ENCODING_WAIT, /* Waiting for above send to complete */ SETENV_STATE_OPTION_SEND, /* About to send an Environment Option */ SETENV_STATE_OPTION_WAIT, /* Waiting for above send to complete */ SETENV_STATE_QUERY1_SEND, /* About to send a status query */ @@ -293,6 +295,7 @@ struct pg_conn char *pgtty; /* tty on which the backend messages is * displayed (OBSOLETE, NOT USED) */ char *connect_timeout; /* connection timeout (numeric string) */ + char *client_encoding_initial; /* encoding to use */ char *pgoptions; /* options to start the backend with */ char *appname; /* application name */ char *fbappname; /* fallback application name */