mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-15 08:20:16 +08:00
Fix assorted bogosities in cash_in() and cash_out().
cash_out failed to handle multiple-byte thousands separators, as per bug #6277 from Alexander Law. In addition, cash_in didn't handle that either, nor could it handle multiple-byte positive_sign. Both routines failed to support multiple-byte mon_decimal_point, which I did not think was worth changing, but at least now they check for the possibility and fall back to using '.' rather than emitting invalid output. Also, make cash_in handle trailing negative signs, which formerly it would reject. Since cash_out generates trailing negative signs whenever the locale tells it to, this last omission represents a fail-to-reload-dumped-data bug. IMO that justifies patching this all the way back.
This commit is contained in:
parent
0418bea295
commit
5093944311
@ -28,12 +28,6 @@
|
|||||||
#include "utils/cash.h"
|
#include "utils/cash.h"
|
||||||
#include "utils/pg_locale.h"
|
#include "utils/pg_locale.h"
|
||||||
|
|
||||||
#define CASH_BUFSZ 36
|
|
||||||
|
|
||||||
#define TERMINATOR (CASH_BUFSZ - 1)
|
|
||||||
#define LAST_PAREN (TERMINATOR - 1)
|
|
||||||
#define LAST_DIGIT (LAST_PAREN - 1)
|
|
||||||
|
|
||||||
|
|
||||||
/*************************************************************************
|
/*************************************************************************
|
||||||
* Private routines
|
* Private routines
|
||||||
@ -106,13 +100,13 @@ cash_in(PG_FUNCTION_ARGS)
|
|||||||
Cash value = 0;
|
Cash value = 0;
|
||||||
Cash dec = 0;
|
Cash dec = 0;
|
||||||
Cash sgn = 1;
|
Cash sgn = 1;
|
||||||
int seen_dot = 0;
|
bool seen_dot = false;
|
||||||
const char *s = str;
|
const char *s = str;
|
||||||
int fpoint;
|
int fpoint;
|
||||||
char dsymbol,
|
char dsymbol;
|
||||||
ssymbol,
|
const char *ssymbol,
|
||||||
psymbol;
|
*psymbol,
|
||||||
const char *nsymbol,
|
*nsymbol,
|
||||||
*csymbol;
|
*csymbol;
|
||||||
|
|
||||||
struct lconv *lconvert = PGLC_localeconv();
|
struct lconv *lconvert = PGLC_localeconv();
|
||||||
@ -131,18 +125,22 @@ cash_in(PG_FUNCTION_ARGS)
|
|||||||
if (fpoint < 0 || fpoint > 10)
|
if (fpoint < 0 || fpoint > 10)
|
||||||
fpoint = 2; /* best guess in this case, I think */
|
fpoint = 2; /* best guess in this case, I think */
|
||||||
|
|
||||||
dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
|
/* we restrict dsymbol to be a single byte, but not the other symbols */
|
||||||
if (*lconvert->mon_thousands_sep != '\0')
|
if (*lconvert->mon_decimal_point != '\0' &&
|
||||||
ssymbol = *lconvert->mon_thousands_sep;
|
lconvert->mon_decimal_point[1] == '\0')
|
||||||
|
dsymbol = *lconvert->mon_decimal_point;
|
||||||
else
|
else
|
||||||
/* ssymbol should not equal dsymbol */
|
dsymbol = '.';
|
||||||
ssymbol = (dsymbol != ',') ? ',' : '.';
|
if (*lconvert->mon_thousands_sep != '\0')
|
||||||
csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
|
ssymbol = lconvert->mon_thousands_sep;
|
||||||
psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
|
else /* ssymbol should not equal dsymbol */
|
||||||
nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
|
ssymbol = (dsymbol != ',') ? "," : ".";
|
||||||
|
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
|
||||||
|
psymbol = (*lconvert->positive_sign != '\0') ? lconvert->positive_sign : "+";
|
||||||
|
nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
|
||||||
|
|
||||||
#ifdef CASHDEBUG
|
#ifdef CASHDEBUG
|
||||||
printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
|
printf("cashin- precision '%d'; decimal '%c'; thousands '%s'; currency '%s'; positive '%s'; negative '%s'\n",
|
||||||
fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
|
fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -164,22 +162,20 @@ cash_in(PG_FUNCTION_ARGS)
|
|||||||
{
|
{
|
||||||
sgn = -1;
|
sgn = -1;
|
||||||
s += strlen(nsymbol);
|
s += strlen(nsymbol);
|
||||||
#ifdef CASHDEBUG
|
|
||||||
printf("cashin- negative symbol; string is '%s'\n", s);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
else if (*s == '(')
|
else if (*s == '(')
|
||||||
{
|
{
|
||||||
sgn = -1;
|
sgn = -1;
|
||||||
s++;
|
s++;
|
||||||
}
|
}
|
||||||
else if (*s == psymbol)
|
else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
|
||||||
s++;
|
s += strlen(psymbol);
|
||||||
|
|
||||||
#ifdef CASHDEBUG
|
#ifdef CASHDEBUG
|
||||||
printf("cashin- string is '%s'\n", s);
|
printf("cashin- string is '%s'\n", s);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* allow whitespace and currency symbol after the sign, too */
|
||||||
while (isspace((unsigned char) *s))
|
while (isspace((unsigned char) *s))
|
||||||
s++;
|
s++;
|
||||||
if (strncmp(s, csymbol, strlen(csymbol)) == 0)
|
if (strncmp(s, csymbol, strlen(csymbol)) == 0)
|
||||||
@ -189,7 +185,7 @@ cash_in(PG_FUNCTION_ARGS)
|
|||||||
printf("cashin- string is '%s'\n", s);
|
printf("cashin- string is '%s'\n", s);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (;; s++)
|
for (; *s; s++)
|
||||||
{
|
{
|
||||||
/* we look for digits as long as we have found less */
|
/* we look for digits as long as we have found less */
|
||||||
/* than the required number of decimal places */
|
/* than the required number of decimal places */
|
||||||
@ -203,33 +199,44 @@ cash_in(PG_FUNCTION_ARGS)
|
|||||||
/* decimal point? then start counting fractions... */
|
/* decimal point? then start counting fractions... */
|
||||||
else if (*s == dsymbol && !seen_dot)
|
else if (*s == dsymbol && !seen_dot)
|
||||||
{
|
{
|
||||||
seen_dot = 1;
|
seen_dot = true;
|
||||||
}
|
}
|
||||||
/* ignore if "thousands" separator, else we're done */
|
/* ignore if "thousands" separator, else we're done */
|
||||||
else if (*s != ssymbol)
|
else if (strncmp(s, ssymbol, strlen(ssymbol)) == 0)
|
||||||
{
|
s += strlen(ssymbol) - 1;
|
||||||
/* round off */
|
else
|
||||||
if (isdigit((unsigned char) *s) && *s >= '5')
|
|
||||||
value++;
|
|
||||||
|
|
||||||
/* adjust for less than required decimal places */
|
|
||||||
for (; dec < fpoint; dec++)
|
|
||||||
value *= 10;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* should only be trailing digits followed by whitespace or right paren */
|
/* round off if there's another digit */
|
||||||
|
if (isdigit((unsigned char) *s) && *s >= '5')
|
||||||
|
value++;
|
||||||
|
|
||||||
|
/* adjust for less than required decimal places */
|
||||||
|
for (; dec < fpoint; dec++)
|
||||||
|
value *= 10;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* should only be trailing digits followed by whitespace, right paren,
|
||||||
|
* or possibly a trailing minus sign
|
||||||
|
*/
|
||||||
while (isdigit((unsigned char) *s))
|
while (isdigit((unsigned char) *s))
|
||||||
s++;
|
s++;
|
||||||
while (isspace((unsigned char) *s) || *s == ')')
|
while (*s)
|
||||||
s++;
|
{
|
||||||
|
if (isspace((unsigned char) *s) || *s == ')')
|
||||||
if (*s != '\0')
|
s++;
|
||||||
ereport(ERROR,
|
else if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
|
||||||
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
{
|
||||||
errmsg("invalid input syntax for type money: \"%s\"", str)));
|
sgn = -1;
|
||||||
|
s += strlen(nsymbol);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||||
|
errmsg("invalid input syntax for type money: \"%s\"",
|
||||||
|
str)));
|
||||||
|
}
|
||||||
|
|
||||||
result = value * sgn;
|
result = value * sgn;
|
||||||
|
|
||||||
@ -242,26 +249,24 @@ cash_in(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
|
|
||||||
/* cash_out()
|
/* cash_out()
|
||||||
* Function to convert cash to a dollars and cents representation.
|
* Function to convert cash to a dollars and cents representation, using
|
||||||
* XXX HACK This code appears to assume US conventions for
|
* the lc_monetary locale's formatting.
|
||||||
* positive-valued amounts. - tgl 97/04/14
|
|
||||||
*/
|
*/
|
||||||
Datum
|
Datum
|
||||||
cash_out(PG_FUNCTION_ARGS)
|
cash_out(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Cash value = PG_GETARG_CASH(0);
|
Cash value = PG_GETARG_CASH(0);
|
||||||
char *result;
|
char *result;
|
||||||
char buf[CASH_BUFSZ];
|
char buf[128];
|
||||||
int minus = 0;
|
char *bufptr;
|
||||||
int count = LAST_DIGIT;
|
bool minus = false;
|
||||||
int point_pos;
|
int digit_pos;
|
||||||
int ssymbol_position = 0;
|
|
||||||
int points,
|
int points,
|
||||||
mon_group;
|
mon_group;
|
||||||
char ssymbol;
|
|
||||||
const char *csymbol,
|
|
||||||
*nsymbol;
|
|
||||||
char dsymbol;
|
char dsymbol;
|
||||||
|
const char *ssymbol,
|
||||||
|
*csymbol,
|
||||||
|
*nsymbol;
|
||||||
char convention;
|
char convention;
|
||||||
|
|
||||||
struct lconv *lconvert = PGLC_localeconv();
|
struct lconv *lconvert = PGLC_localeconv();
|
||||||
@ -280,69 +285,78 @@ cash_out(PG_FUNCTION_ARGS)
|
|||||||
mon_group = 3;
|
mon_group = 3;
|
||||||
|
|
||||||
convention = lconvert->n_sign_posn;
|
convention = lconvert->n_sign_posn;
|
||||||
dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
|
|
||||||
if (*lconvert->mon_thousands_sep != '\0')
|
/* we restrict dsymbol to be a single byte, but not the other symbols */
|
||||||
ssymbol = *lconvert->mon_thousands_sep;
|
if (*lconvert->mon_decimal_point != '\0' &&
|
||||||
|
lconvert->mon_decimal_point[1] == '\0')
|
||||||
|
dsymbol = *lconvert->mon_decimal_point;
|
||||||
else
|
else
|
||||||
/* ssymbol should not equal dsymbol */
|
dsymbol = '.';
|
||||||
ssymbol = (dsymbol != ',') ? ',' : '.';
|
if (*lconvert->mon_thousands_sep != '\0')
|
||||||
csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
|
ssymbol = lconvert->mon_thousands_sep;
|
||||||
nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
|
else /* ssymbol should not equal dsymbol */
|
||||||
|
ssymbol = (dsymbol != ',') ? "," : ".";
|
||||||
point_pos = LAST_DIGIT - points;
|
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
|
||||||
|
nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
|
||||||
point_pos -= (points - 1) / mon_group;
|
|
||||||
ssymbol_position = point_pos % (mon_group + 1);
|
|
||||||
|
|
||||||
/* we work with positive amounts and add the minus sign at the end */
|
/* we work with positive amounts and add the minus sign at the end */
|
||||||
if (value < 0)
|
if (value < 0)
|
||||||
{
|
{
|
||||||
minus = 1;
|
minus = true;
|
||||||
value = -value;
|
value = -value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* allow for trailing negative strings */
|
/* we build the result string right-to-left in buf[] */
|
||||||
MemSet(buf, ' ', CASH_BUFSZ);
|
bufptr = buf + sizeof(buf) - 1;
|
||||||
buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
|
*bufptr = '\0';
|
||||||
|
|
||||||
while (value || count > (point_pos - 2))
|
|
||||||
{
|
|
||||||
if (points && count == point_pos)
|
|
||||||
buf[count--] = dsymbol;
|
|
||||||
else if (ssymbol && count % (mon_group + 1) == ssymbol_position)
|
|
||||||
buf[count--] = ssymbol;
|
|
||||||
|
|
||||||
buf[count--] = ((uint64) value % 10) + '0';
|
|
||||||
value = ((uint64) value) / 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
|
|
||||||
count -= strlen(csymbol) - 1;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If points == 0 and the number of digits % mon_group == 0, the code
|
* Generate digits till there are no non-zero digits left and we emitted
|
||||||
* above adds a trailing ssymbol on the far right, so remove it.
|
* at least one to the left of the decimal point. digit_pos is the
|
||||||
|
* current digit position, with zero as the digit just left of the decimal
|
||||||
|
* point, increasing to the right.
|
||||||
*/
|
*/
|
||||||
if (buf[LAST_DIGIT] == ssymbol)
|
digit_pos = points;
|
||||||
buf[LAST_DIGIT] = '\0';
|
do
|
||||||
|
{
|
||||||
|
if (points && digit_pos == 0)
|
||||||
|
{
|
||||||
|
/* insert decimal point */
|
||||||
|
*(--bufptr) = dsymbol;
|
||||||
|
}
|
||||||
|
else if (digit_pos < points && (digit_pos % mon_group) == 0)
|
||||||
|
{
|
||||||
|
/* insert thousands sep */
|
||||||
|
bufptr -= strlen(ssymbol);
|
||||||
|
memcpy(bufptr, ssymbol, strlen(ssymbol));
|
||||||
|
}
|
||||||
|
|
||||||
|
*(--bufptr) = ((uint64) value % 10) + '0';
|
||||||
|
value = ((uint64) value) / 10;
|
||||||
|
digit_pos--;
|
||||||
|
} while (value || digit_pos >= 0);
|
||||||
|
|
||||||
|
/* prepend csymbol */
|
||||||
|
bufptr -= strlen(csymbol);
|
||||||
|
memcpy(bufptr, csymbol, strlen(csymbol));
|
||||||
|
|
||||||
/* see if we need to signify negative amount */
|
/* see if we need to signify negative amount */
|
||||||
if (minus)
|
if (minus)
|
||||||
{
|
{
|
||||||
result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
|
result = palloc(strlen(bufptr) + strlen(nsymbol) + 3);
|
||||||
|
|
||||||
/* Position code of 0 means use parens */
|
/* Position code of 0 means use parens */
|
||||||
if (convention == 0)
|
if (convention == 0)
|
||||||
sprintf(result, "(%s)", buf + count);
|
sprintf(result, "(%s)", bufptr);
|
||||||
else if (convention == 2)
|
else if (convention == 2)
|
||||||
sprintf(result, "%s%s", buf + count, nsymbol);
|
sprintf(result, "%s%s", bufptr, nsymbol);
|
||||||
else
|
else
|
||||||
sprintf(result, "%s%s", nsymbol, buf + count);
|
sprintf(result, "%s%s", nsymbol, bufptr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = palloc(CASH_BUFSZ + 2 - count);
|
/* just emit what we have */
|
||||||
strcpy(result, buf + count);
|
result = pstrdup(bufptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
PG_RETURN_CSTRING(result);
|
PG_RETURN_CSTRING(result);
|
||||||
|
Loading…
Reference in New Issue
Block a user