mirror of
https://github.com/postgres/postgres.git
synced 2025-10-22 14:32:25 +03:00
Tidy up locale thread safety in ECPG library.
Remove setlocale() and _configthreadlocal() as fallback strategy on systems that don't have uselocale(), where ECPG tries to control LC_NUMERIC formatting on input and output of floating point numbers. It was probably broken on some systems (NetBSD), and the code was also quite messy and complicated, with obsolete configure tests (Windows). It was also arguably broken, or at least had unstated environmental requirements, if pgtypeslib code was called directly. Instead, introduce PG_C_LOCALE to refer to the "C" locale as a locale_t value. It maps to the special constant LC_C_LOCALE when defined by libc (macOS, NetBSD), or otherwise uses a process-lifetime locale_t that is allocated on first use, just as ECPG previously did itself. The new replacement might be more widely useful. Then change the float parsing and printing code to pass that to _l() functions where appropriate. Unfortunately the portability of those functions is a bit complicated. First, many obvious and useful _l() functions are missing from POSIX, though most standard libraries define some of them anyway. Second, although the thread-safe save/restore technique can be used to replace the missing ones, Windows and NetBSD refused to implement standard uselocale(). They might have a point: "wide scope" uselocale() is hard to combine with other code and error-prone, especially in library code. Luckily they have the _l() functions we want so far anyway. So we have to be prepared for both ways of doing things: 1. In ECPG, use strtod_l() for parsing, and supply a port.h replacement using uselocale() over a limited scope if missing. 2. Inside our own snprintf.c, use three different approaches to format floats. For frontend code, call libc's snprintf_l(), or wrap libc's snprintf() in uselocale() if it's missing. For backend code, snprintf.c can keep assuming that the global locale's LC_NUMERIC is "C" and call libc's snprintf() without change, for now. (It might eventually be possible to call our in-tree Ryū routines to display floats in snprintf.c, given the C-locale-always remit of our in-tree snprintf(), but this patch doesn't risk changing anything that complicated.) Author: Thomas Munro <thomas.munro@gmail.com> Reviewed-by: Peter Eisentraut <peter@eisentraut.org> Reviewed-by: Tristan Partin <tristan@partin.io> Reviewed-by: Heikki Linnakangas <hlinnaka@iki.fi> Discussion: https://postgr.es/m/CWZBBRR6YA8D.8EHMDRGLCKCD%40neon.tech
This commit is contained in:
@@ -10,10 +10,6 @@
|
||||
#include "ecpgtype.h"
|
||||
#include "sqlca.h"
|
||||
|
||||
#ifdef HAVE_USELOCALE
|
||||
locale_t ecpg_clocale = (locale_t) 0;
|
||||
#endif
|
||||
|
||||
static pthread_mutex_t connections_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_key_t actual_connection_key;
|
||||
static pthread_once_t actual_connection_key_once = PTHREAD_ONCE_INIT;
|
||||
@@ -268,7 +264,7 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p
|
||||
const char **conn_keywords;
|
||||
const char **conn_values;
|
||||
|
||||
if (sqlca == NULL)
|
||||
if (sqlca == NULL || !pg_ensure_c_locale())
|
||||
{
|
||||
ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
|
||||
ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
|
||||
@@ -483,39 +479,6 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p
|
||||
/* add connection to our list */
|
||||
pthread_mutex_lock(&connections_mutex);
|
||||
|
||||
/*
|
||||
* ... but first, make certain we have created ecpg_clocale. Rely on
|
||||
* holding connections_mutex to ensure this is done by only one thread.
|
||||
*/
|
||||
#ifdef HAVE_USELOCALE
|
||||
if (!ecpg_clocale)
|
||||
{
|
||||
ecpg_clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
|
||||
if (!ecpg_clocale)
|
||||
{
|
||||
pthread_mutex_unlock(&connections_mutex);
|
||||
ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
|
||||
ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
|
||||
if (host)
|
||||
ecpg_free(host);
|
||||
if (port)
|
||||
ecpg_free(port);
|
||||
if (options)
|
||||
ecpg_free(options);
|
||||
if (realname)
|
||||
ecpg_free(realname);
|
||||
if (dbname)
|
||||
ecpg_free(dbname);
|
||||
if (conn_keywords)
|
||||
ecpg_free(conn_keywords);
|
||||
if (conn_values)
|
||||
ecpg_free(conn_values);
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (connection_name != NULL)
|
||||
this->name = ecpg_strdup(connection_name, lineno);
|
||||
else
|
||||
|
@@ -466,7 +466,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
|
||||
pval++;
|
||||
|
||||
if (!check_special_value(pval, &dres, &scan_length))
|
||||
dres = strtod(pval, &scan_length);
|
||||
dres = strtod_l(pval, &scan_length, PG_C_LOCALE);
|
||||
|
||||
if (isarray && *scan_length == '"')
|
||||
scan_length++;
|
||||
|
@@ -475,46 +475,9 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...)
|
||||
memset(&stmt, 0, sizeof stmt);
|
||||
stmt.lineno = lineno;
|
||||
|
||||
/* Make sure we do NOT honor the locale for numeric input */
|
||||
/* since the database gives the standard decimal point */
|
||||
/* (see comments in execute.c) */
|
||||
#ifdef HAVE_USELOCALE
|
||||
|
||||
/*
|
||||
* To get here, the above PQnfields() test must have found nonzero
|
||||
* fields. One needs a connection to create such a descriptor. (EXEC
|
||||
* SQL SET DESCRIPTOR can populate the descriptor's "items", but it
|
||||
* can't change the descriptor's PQnfields().) Any successful
|
||||
* connection initializes ecpg_clocale.
|
||||
*/
|
||||
Assert(ecpg_clocale);
|
||||
stmt.oldlocale = uselocale(ecpg_clocale);
|
||||
#else
|
||||
#ifdef WIN32
|
||||
stmt.oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
|
||||
#endif
|
||||
stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
#endif
|
||||
|
||||
/* desperate try to guess something sensible */
|
||||
stmt.connection = ecpg_get_connection(NULL);
|
||||
ecpg_store_result(ECPGresult, index, &stmt, &data_var);
|
||||
|
||||
#ifdef HAVE_USELOCALE
|
||||
if (stmt.oldlocale != (locale_t) 0)
|
||||
uselocale(stmt.oldlocale);
|
||||
#else
|
||||
if (stmt.oldlocale)
|
||||
{
|
||||
setlocale(LC_NUMERIC, stmt.oldlocale);
|
||||
ecpg_free(stmt.oldlocale);
|
||||
}
|
||||
#ifdef WIN32
|
||||
if (stmt.oldthreadlocale != -1)
|
||||
_configthreadlocale(stmt.oldthreadlocale);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL)
|
||||
|
||||
|
@@ -56,10 +56,6 @@ struct ECPGtype_information_cache
|
||||
enum ARRAY_TYPE isarray;
|
||||
};
|
||||
|
||||
#ifdef HAVE_USELOCALE
|
||||
extern locale_t ecpg_clocale; /* LC_NUMERIC=C */
|
||||
#endif
|
||||
|
||||
/* structure to store one statement */
|
||||
struct statement
|
||||
{
|
||||
@@ -73,14 +69,6 @@ struct statement
|
||||
bool questionmarks;
|
||||
struct variable *inlist;
|
||||
struct variable *outlist;
|
||||
#ifdef HAVE_USELOCALE
|
||||
locale_t oldlocale;
|
||||
#else
|
||||
char *oldlocale;
|
||||
#ifdef WIN32
|
||||
int oldthreadlocale;
|
||||
#endif
|
||||
#endif
|
||||
int nparams;
|
||||
char **paramvalues;
|
||||
int *paramlengths;
|
||||
|
@@ -101,9 +101,6 @@ free_statement(struct statement *stmt)
|
||||
free_variable(stmt->outlist);
|
||||
ecpg_free(stmt->command);
|
||||
ecpg_free(stmt->name);
|
||||
#ifndef HAVE_USELOCALE
|
||||
ecpg_free(stmt->oldlocale);
|
||||
#endif
|
||||
ecpg_free(stmt);
|
||||
}
|
||||
|
||||
@@ -1973,43 +1970,6 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator,
|
||||
if (stmt == NULL)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Make sure we do NOT honor the locale for numeric input/output since the
|
||||
* database wants the standard decimal point. If available, use
|
||||
* uselocale() for this because it's thread-safe. Windows doesn't have
|
||||
* that, but it does have _configthreadlocale().
|
||||
*/
|
||||
#ifdef HAVE_USELOCALE
|
||||
|
||||
/*
|
||||
* Since ecpg_init() succeeded, we have a connection. Any successful
|
||||
* connection initializes ecpg_clocale.
|
||||
*/
|
||||
Assert(ecpg_clocale);
|
||||
stmt->oldlocale = uselocale(ecpg_clocale);
|
||||
if (stmt->oldlocale == (locale_t) 0)
|
||||
{
|
||||
ecpg_do_epilogue(stmt);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
#ifdef WIN32
|
||||
stmt->oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
|
||||
if (stmt->oldthreadlocale == -1)
|
||||
{
|
||||
ecpg_do_epilogue(stmt);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
|
||||
if (stmt->oldlocale == NULL)
|
||||
{
|
||||
ecpg_do_epilogue(stmt);
|
||||
return false;
|
||||
}
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If statement type is ECPGst_prepnormal we are supposed to prepare the
|
||||
* statement before executing them
|
||||
@@ -2216,19 +2176,6 @@ ecpg_do_epilogue(struct statement *stmt)
|
||||
if (stmt == NULL)
|
||||
return;
|
||||
|
||||
#ifdef HAVE_USELOCALE
|
||||
if (stmt->oldlocale != (locale_t) 0)
|
||||
uselocale(stmt->oldlocale);
|
||||
#else
|
||||
if (stmt->oldlocale)
|
||||
{
|
||||
setlocale(LC_NUMERIC, stmt->oldlocale);
|
||||
#ifdef WIN32
|
||||
_configthreadlocale(stmt->oldthreadlocale);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
free_statement(stmt);
|
||||
}
|
||||
|
||||
|
@@ -1218,7 +1218,7 @@ DecodeNumber(int flen, char *str, int fmask,
|
||||
return DecodeNumberField(flen, str, (fmask | DTK_DATE_M),
|
||||
tmask, tm, fsec, is2digits);
|
||||
|
||||
*fsec = strtod(cp, &cp);
|
||||
*fsec = strtod_l(cp, &cp, PG_C_LOCALE);
|
||||
if (*cp != '\0')
|
||||
return -1;
|
||||
}
|
||||
@@ -2030,7 +2030,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
|
||||
{
|
||||
double frac;
|
||||
|
||||
frac = strtod(cp, &cp);
|
||||
frac = strtod_l(cp, &cp, PG_C_LOCALE);
|
||||
if (*cp != '\0')
|
||||
return -1;
|
||||
*fsec = frac * 1000000;
|
||||
@@ -2054,7 +2054,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
|
||||
{
|
||||
double time;
|
||||
|
||||
time = strtod(cp, &cp);
|
||||
time = strtod_l(cp, &cp, PG_C_LOCALE);
|
||||
if (*cp != '\0')
|
||||
return -1;
|
||||
|
||||
|
@@ -60,7 +60,7 @@ ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart)
|
||||
if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
|
||||
return DTERR_BAD_FORMAT;
|
||||
errno = 0;
|
||||
val = strtod(str, endptr);
|
||||
val = strtod_l(str, endptr, PG_C_LOCALE);
|
||||
/* did we not see anything that looks like a double? */
|
||||
if (*endptr == str || errno != 0)
|
||||
return DTERR_BAD_FORMAT;
|
||||
@@ -455,7 +455,7 @@ DecodeInterval(char **field, int *ftype, int nf, /* int range, */
|
||||
else if (*cp == '.')
|
||||
{
|
||||
errno = 0;
|
||||
fval = strtod(cp, &cp);
|
||||
fval = strtod_l(cp, &cp, PG_C_LOCALE);
|
||||
if (*cp != '\0' || errno != 0)
|
||||
return DTERR_BAD_FORMAT;
|
||||
|
||||
|
@@ -1455,7 +1455,7 @@ numericvar_to_double(numeric *var, double *dp)
|
||||
* strtod does not reset errno to 0 in case of success.
|
||||
*/
|
||||
errno = 0;
|
||||
val = strtod(tmp, &endptr);
|
||||
val = strtod_l(tmp, &endptr, PG_C_LOCALE);
|
||||
if (errno == ERANGE)
|
||||
{
|
||||
free(tmp);
|
||||
|
Reference in New Issue
Block a user