Skip to content
Snippets Groups Projects
Commit c26ffb1e authored by Tom Lane's avatar Tom Lane
Browse files

Fix SetClientEncoding() to maintain a cache of previously selected encoding

conversion functions.  This allows transaction rollback to revert to a
previous client_encoding setting without doing fresh catalog lookups.
I believe that this explains and fixes the recent report of "failed to commit
client_encoding" failures.

This bug is present in 8.3.x, but it doesn't seem prudent to back-patch
the fix, at least not till it's had some time for field testing in HEAD.

In passing, remove SetDefaultClientEncoding(), which was used nowhere.
parent 33e7eac3
No related branches found
No related tags found
No related merge requests found
/* /*
* This file contains public functions for conversion between * This file contains public functions for conversion between
* client encoding and server internal encoding. * client encoding and server (database) encoding.
* (currently mule internal code (mic) is used) *
* Tatsuo Ishii * Tatsuo Ishii
* *
* $PostgreSQL: pgsql/src/backend/utils/mb/mbutils.c,v 1.82 2009/03/09 00:01:32 alvherre Exp $ * $PostgreSQL: pgsql/src/backend/utils/mb/mbutils.c,v 1.83 2009/04/02 17:30:53 tgl Exp $
*/ */
#include "postgres.h" #include "postgres.h"
...@@ -28,23 +28,37 @@ ...@@ -28,23 +28,37 @@
#define MAX_CONVERSION_GROWTH 4 #define MAX_CONVERSION_GROWTH 4
/* /*
* We handle for actual FE and BE encoding setting encoding-identificator * We maintain a simple linked list caching the fmgr lookup info for the
* and encoding-name too. It prevent searching and conversion from encoding * currently selected conversion functions, as well as any that have been
* to encoding name in getdatabaseencoding() and other routines. * selected previously in the current session. (We remember previous
* settings because we must be able to restore a previous setting during
* transaction rollback, without doing any fresh catalog accesses.)
*
* Since we'll never release this data, we just keep it in TopMemoryContext.
*/ */
static pg_enc2name *ClientEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; typedef struct ConvProcInfo
static pg_enc2name *DatabaseEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; {
int s_encoding; /* server and client encoding IDs */
int c_encoding;
FmgrInfo to_server_info; /* lookup info for conversion procs */
FmgrInfo to_client_info;
} ConvProcInfo;
static List *ConvProcList = NIL; /* List of ConvProcInfo */
/* /*
* Caches for conversion function info. These values are allocated in * These variables point to the currently active conversion functions,
* MbProcContext. That context is a child of TopMemoryContext, * or are NULL when no conversion is needed.
* which allows these values to survive across transactions. See
* SetClientEncoding() for more details.
*/ */
static MemoryContext MbProcContext = NULL;
static FmgrInfo *ToServerConvProc = NULL; static FmgrInfo *ToServerConvProc = NULL;
static FmgrInfo *ToClientConvProc = NULL; static FmgrInfo *ToClientConvProc = NULL;
/*
* These variables track the currently selected FE and BE encodings.
*/
static pg_enc2name *ClientEncoding = &pg_enc2name_tbl[PG_SQL_ASCII];
static pg_enc2name *DatabaseEncoding = &pg_enc2name_tbl[PG_SQL_ASCII];
/* /*
* During backend startup we can't set client encoding because we (a) * During backend startup we can't set client encoding because we (a)
* can't look up the conversion functions, and (b) may not know the database * can't look up the conversion functions, and (b) may not know the database
...@@ -70,11 +84,7 @@ int ...@@ -70,11 +84,7 @@ int
SetClientEncoding(int encoding, bool doit) SetClientEncoding(int encoding, bool doit)
{ {
int current_server_encoding; int current_server_encoding;
Oid to_server_proc, ListCell *lc;
to_client_proc;
FmgrInfo *to_server;
FmgrInfo *to_client;
MemoryContext oldcontext;
if (!PG_VALID_FE_ENCODING(encoding)) if (!PG_VALID_FE_ENCODING(encoding))
return -1; return -1;
...@@ -101,25 +111,23 @@ SetClientEncoding(int encoding, bool doit) ...@@ -101,25 +111,23 @@ SetClientEncoding(int encoding, bool doit)
ClientEncoding = &pg_enc2name_tbl[encoding]; ClientEncoding = &pg_enc2name_tbl[encoding];
ToServerConvProc = NULL; ToServerConvProc = NULL;
ToClientConvProc = NULL; ToClientConvProc = NULL;
if (MbProcContext)
MemoryContextReset(MbProcContext);
} }
return 0; return 0;
} }
if (IsTransactionState())
{
/* /*
* If we're not inside a transaction then we can't do catalog lookups, so * If we're in a live transaction, it's safe to access the catalogs,
* fail. After backend startup, this could only happen if we are * so look up the functions. We repeat the lookup even if the info
* re-reading postgresql.conf due to SIGHUP --- so basically this just * is already cached, so that we can react to changes in the contents
* constrains the ability to change client_encoding on the fly from * of pg_conversion.
* postgresql.conf. Which would probably be a stupid thing to do anyway.
*/ */
if (!IsTransactionState()) Oid to_server_proc,
return -1; to_client_proc;
ConvProcInfo *convinfo;
MemoryContext oldcontext;
/*
* Look up the conversion functions.
*/
to_server_proc = FindDefaultConversionProc(encoding, to_server_proc = FindDefaultConversionProc(encoding,
current_server_encoding); current_server_encoding);
if (!OidIsValid(to_server_proc)) if (!OidIsValid(to_server_proc))
...@@ -135,45 +143,85 @@ SetClientEncoding(int encoding, bool doit) ...@@ -135,45 +143,85 @@ SetClientEncoding(int encoding, bool doit)
if (!doit) if (!doit)
return 0; return 0;
/* Before loading the new fmgr info, remove the old info, if any */ /*
ToServerConvProc = NULL; * Load the fmgr info into TopMemoryContext (could still fail here)
ToClientConvProc = NULL; */
if (MbProcContext != NULL) convinfo = (ConvProcInfo *) MemoryContextAlloc(TopMemoryContext,
sizeof(ConvProcInfo));
convinfo->s_encoding = current_server_encoding;
convinfo->c_encoding = encoding;
fmgr_info_cxt(to_server_proc, &convinfo->to_server_info,
TopMemoryContext);
fmgr_info_cxt(to_client_proc, &convinfo->to_client_info,
TopMemoryContext);
/* Attach new info to head of list */
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
ConvProcList = lcons(convinfo, ConvProcList);
MemoryContextSwitchTo(oldcontext);
/*
* Everything is okay, so apply the setting.
*/
ClientEncoding = &pg_enc2name_tbl[encoding];
ToServerConvProc = &convinfo->to_server_info;
ToClientConvProc = &convinfo->to_client_info;
/*
* Remove any older entry for the same encoding pair (this is just
* to avoid memory leakage).
*/
foreach(lc, ConvProcList)
{
ConvProcInfo *oldinfo = (ConvProcInfo *) lfirst(lc);
if (oldinfo == convinfo)
continue;
if (oldinfo->s_encoding == convinfo->s_encoding &&
oldinfo->c_encoding == convinfo->c_encoding)
{ {
MemoryContextReset(MbProcContext); ConvProcList = list_delete_ptr(ConvProcList, oldinfo);
pfree(oldinfo);
break; /* need not look further */
}
}
return 0; /* success */
} }
else else
{ {
/* /*
* This is the first time through, so create the context. Make it a * If we're not in a live transaction, the only thing we can do
* child of TopMemoryContext so that these values survive across * is restore a previous setting using the cache. This covers all
* transactions. * transaction-rollback cases. The only case it might not work for
* is trying to change client_encoding on the fly by editing
* postgresql.conf and SIGHUP'ing. Which would probably be a stupid
* thing to do anyway.
*/ */
MbProcContext = AllocSetContextCreate(TopMemoryContext, foreach(lc, ConvProcList)
"MbProcContext", {
ALLOCSET_SMALL_MINSIZE, ConvProcInfo *oldinfo = (ConvProcInfo *) lfirst(lc);
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
}
/* Load the fmgr info into MbProcContext */
oldcontext = MemoryContextSwitchTo(MbProcContext);
to_server = palloc(sizeof(FmgrInfo));
to_client = palloc(sizeof(FmgrInfo));
fmgr_info(to_server_proc, to_server);
fmgr_info(to_client_proc, to_client);
MemoryContextSwitchTo(oldcontext);
if (oldinfo->s_encoding == current_server_encoding &&
oldinfo->c_encoding == encoding)
{
if (doit)
{
ClientEncoding = &pg_enc2name_tbl[encoding]; ClientEncoding = &pg_enc2name_tbl[encoding];
ToServerConvProc = to_server; ToServerConvProc = &oldinfo->to_server_info;
ToClientConvProc = to_client; ToClientConvProc = &oldinfo->to_client_info;
}
return 0; return 0;
} }
}
return -1; /* it's not cached, so fail */
}
}
/* /*
* Initialize client encoding if necessary. * Initialize client encoding if necessary.
* called from InitPostgres() once during backend starting up. * called from InitPostgres() once during backend startup.
*/ */
void void
InitializeClientEncoding(void) InitializeClientEncoding(void)
...@@ -196,7 +244,8 @@ InitializeClientEncoding(void) ...@@ -196,7 +244,8 @@ InitializeClientEncoding(void)
} }
/* /*
* returns the current client encoding */ * returns the current client encoding
*/
int int
pg_get_client_encoding(void) pg_get_client_encoding(void)
{ {
...@@ -511,9 +560,8 @@ pg_server_to_client(const char *s, int len) ...@@ -511,9 +560,8 @@ pg_server_to_client(const char *s, int len)
/* /*
* Perform default encoding conversion using cached FmgrInfo. Since * Perform default encoding conversion using cached FmgrInfo. Since
* this function does not access database at all, it is safe to call * this function does not access database at all, it is safe to call
* outside transactions. Explicit setting client encoding required * outside transactions. If the conversion has not been set up by
* before calling this function. Otherwise no conversion is * SetClientEncoding(), no conversion is performed.
* performed.
*/ */
static char * static char *
perform_default_encoding_conversion(const char *src, int len, bool is_client_to_server) perform_default_encoding_conversion(const char *src, int len, bool is_client_to_server)
...@@ -919,12 +967,6 @@ pg_bind_textdomain_codeset(const char *domainname, int encoding) ...@@ -919,12 +967,6 @@ pg_bind_textdomain_codeset(const char *domainname, int encoding)
#endif #endif
} }
void
SetDefaultClientEncoding(void)
{
ClientEncoding = &pg_enc2name_tbl[GetDatabaseEncoding()];
}
int int
GetDatabaseEncoding(void) GetDatabaseEncoding(void)
{ {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/mb/pg_wchar.h,v 1.87 2009/03/09 00:01:32 alvherre Exp $ * $PostgreSQL: pgsql/src/include/mb/pg_wchar.h,v 1.88 2009/04/02 17:30:53 tgl Exp $
* *
* NOTES * NOTES
* This is used both by the backend and by libpq, but should not be * This is used both by the backend and by libpq, but should not be
...@@ -383,7 +383,6 @@ extern size_t wchar2char(char *to, const wchar_t *from, size_t tolen); ...@@ -383,7 +383,6 @@ extern size_t wchar2char(char *to, const wchar_t *from, size_t tolen);
extern size_t char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen); extern size_t char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen);
#endif #endif
extern void SetDefaultClientEncoding(void);
extern int SetClientEncoding(int encoding, bool doit); extern int SetClientEncoding(int encoding, bool doit);
extern void InitializeClientEncoding(void); extern void InitializeClientEncoding(void);
extern int pg_get_client_encoding(void); extern int pg_get_client_encoding(void);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment