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:
Tom Lane 2011-10-29 14:31:03 -04:00
parent 0418bea295
commit 5093944311

View File

@ -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);