Skip to content

Commit 5b297de

Browse files
committed
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.
1 parent 7208e0b commit 5b297de

File tree

1 file changed

+112
-89
lines changed

1 file changed

+112
-89
lines changed

src/backend/utils/adt/cash.c

+112-89
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,6 @@
2626

2727
static const char *num_word(Cash value);
2828

29-
/* when we go to 64 bit values we will have to modify this */
30-
#define CASH_BUFSZ 24
31-
32-
#define TERMINATOR (CASH_BUFSZ - 1)
33-
#define LAST_PAREN (TERMINATOR - 1)
34-
#define LAST_DIGIT (LAST_PAREN - 1)
35-
3629

3730
/*
3831
* Cash is a pass-by-ref SQL type, so we must pass and return pointers.
@@ -71,14 +64,14 @@ cash_in(PG_FUNCTION_ARGS)
7164
Cash value = 0;
7265
Cash dec = 0;
7366
Cash sgn = 1;
74-
int seen_dot = 0;
67+
bool seen_dot = false;
7568
const char *s = str;
7669
int fpoint;
77-
char *csymbol;
78-
char dsymbol,
79-
ssymbol,
80-
psymbol,
81-
*nsymbol;
70+
char dsymbol;
71+
const char *ssymbol,
72+
*psymbol,
73+
*nsymbol,
74+
*csymbol;
8275

8376
struct lconv *lconvert = PGLC_localeconv();
8477

@@ -96,14 +89,22 @@ cash_in(PG_FUNCTION_ARGS)
9689
if (fpoint < 0 || fpoint > 10)
9790
fpoint = 2; /* best guess in this case, I think */
9891

99-
dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
100-
ssymbol = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
101-
csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
102-
psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
103-
nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
92+
/* we restrict dsymbol to be a single byte, but not the other symbols */
93+
if (*lconvert->mon_decimal_point != '\0' &&
94+
lconvert->mon_decimal_point[1] == '\0')
95+
dsymbol = *lconvert->mon_decimal_point;
96+
else
97+
dsymbol = '.';
98+
if (*lconvert->mon_thousands_sep != '\0')
99+
ssymbol = lconvert->mon_thousands_sep;
100+
else /* ssymbol should not equal dsymbol */
101+
ssymbol = (dsymbol != ',') ? "," : ".";
102+
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
103+
psymbol = (*lconvert->positive_sign != '\0') ? lconvert->positive_sign : "+";
104+
nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
104105

105106
#ifdef CASHDEBUG
106-
printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
107+
printf("cashin- precision '%d'; decimal '%c'; thousands '%s'; currency '%s'; positive '%s'; negative '%s'\n",
107108
fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
108109
#endif
109110

@@ -124,23 +125,20 @@ cash_in(PG_FUNCTION_ARGS)
124125
{
125126
sgn = -1;
126127
s += strlen(nsymbol);
127-
#ifdef CASHDEBUG
128-
printf("cashin- negative symbol; string is '%s'\n", s);
129-
#endif
130128
}
131129
else if (*s == '(')
132130
{
133131
sgn = -1;
134132
s++;
135-
136133
}
137-
else if (*s == psymbol)
138-
s++;
134+
else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
135+
s += strlen(psymbol);
139136

140137
#ifdef CASHDEBUG
141138
printf("cashin- string is '%s'\n", s);
142139
#endif
143140

141+
/* allow whitespace and currency symbol after the sign, too */
144142
while (isspace((unsigned char) *s))
145143
s++;
146144
if (strncmp(s, csymbol, strlen(csymbol)) == 0)
@@ -150,7 +148,7 @@ cash_in(PG_FUNCTION_ARGS)
150148
printf("cashin- string is '%s'\n", s);
151149
#endif
152150

153-
for (;; s++)
151+
for (; *s; s++)
154152
{
155153
/* we look for digits as long as we have found less */
156154
/* than the required number of decimal places */
@@ -164,30 +162,44 @@ cash_in(PG_FUNCTION_ARGS)
164162
/* decimal point? then start counting fractions... */
165163
else if (*s == dsymbol && !seen_dot)
166164
{
167-
seen_dot = 1;
165+
seen_dot = true;
168166
}
169167
/* ignore if "thousands" separator, else we're done */
170-
else if (*s != ssymbol)
171-
{
172-
/* round off */
173-
if (isdigit((unsigned char) *s) && *s >= '5')
174-
value++;
175-
176-
/* adjust for less than required decimal places */
177-
for (; dec < fpoint; dec++)
178-
value *= 10;
179-
168+
else if (strncmp(s, ssymbol, strlen(ssymbol)) == 0)
169+
s += strlen(ssymbol) - 1;
170+
else
180171
break;
181-
}
182172
}
183173

184-
while (isspace((unsigned char) *s) || *s == '0' || *s == ')')
185-
s++;
174+
/* round off if there's another digit */
175+
if (isdigit((unsigned char) *s) && *s >= '5')
176+
value++;
186177

187-
if (*s != '\0')
188-
ereport(ERROR,
189-
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
190-
errmsg("invalid input syntax for type money: \"%s\"", str)));
178+
/* adjust for less than required decimal places */
179+
for (; dec < fpoint; dec++)
180+
value *= 10;
181+
182+
/*
183+
* should only be trailing digits followed by whitespace, right paren,
184+
* or possibly a trailing minus sign
185+
*/
186+
while (isdigit((unsigned char) *s))
187+
s++;
188+
while (*s)
189+
{
190+
if (isspace((unsigned char) *s) || *s == ')')
191+
s++;
192+
else if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
193+
{
194+
sgn = -1;
195+
s += strlen(nsymbol);
196+
}
197+
else
198+
ereport(ERROR,
199+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
200+
errmsg("invalid input syntax for type money: \"%s\"",
201+
str)));
202+
}
191203

192204
result = value * sgn;
193205

@@ -200,25 +212,23 @@ cash_in(PG_FUNCTION_ARGS)
200212

201213

202214
/* cash_out()
203-
* Function to convert cash to a dollars and cents representation.
204-
* XXX HACK This code appears to assume US conventions for
205-
* positive-valued amounts. - tgl 97/04/14
215+
* Function to convert cash to a dollars and cents representation, using
216+
* the lc_monetary locale's formatting.
206217
*/
207218
Datum
208219
cash_out(PG_FUNCTION_ARGS)
209220
{
210221
Cash value = PG_GETARG_CASH(0);
211222
char *result;
212-
char buf[CASH_BUFSZ];
213-
int minus = 0;
214-
int count = LAST_DIGIT;
215-
int point_pos;
216-
int comma_position = 0;
223+
char buf[128];
224+
char *bufptr;
225+
bool minus = false;
226+
int digit_pos;
217227
int points,
218228
mon_group;
219-
char comma;
220-
char *csymbol,
221-
dsymbol,
229+
char dsymbol;
230+
const char *ssymbol,
231+
*csymbol,
222232
*nsymbol;
223233
char convention;
224234

@@ -237,66 +247,79 @@ cash_out(PG_FUNCTION_ARGS)
237247
if (mon_group <= 0 || mon_group > 6)
238248
mon_group = 3;
239249

240-
comma = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
241250
convention = lconvert->n_sign_posn;
242-
dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
243-
csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
244-
nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
245-
246-
point_pos = LAST_DIGIT - points;
247251

248-
/* allow more than three decimal points and separate them */
249-
if (comma)
250-
{
251-
point_pos -= (points - 1) / mon_group;
252-
comma_position = point_pos % (mon_group + 1);
253-
}
252+
/* we restrict dsymbol to be a single byte, but not the other symbols */
253+
if (*lconvert->mon_decimal_point != '\0' &&
254+
lconvert->mon_decimal_point[1] == '\0')
255+
dsymbol = *lconvert->mon_decimal_point;
256+
else
257+
dsymbol = '.';
258+
if (*lconvert->mon_thousands_sep != '\0')
259+
ssymbol = lconvert->mon_thousands_sep;
260+
else /* ssymbol should not equal dsymbol */
261+
ssymbol = (dsymbol != ',') ? "," : ".";
262+
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
263+
nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
254264

255265
/* we work with positive amounts and add the minus sign at the end */
256266
if (value < 0)
257267
{
258-
minus = 1;
268+
minus = true;
259269
value = -value;
260270
}
261271

262-
/* allow for trailing negative strings */
263-
MemSet(buf, ' ', CASH_BUFSZ);
264-
buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
272+
/* we build the result string right-to-left in buf[] */
273+
bufptr = buf + sizeof(buf) - 1;
274+
*bufptr = '\0';
265275

266-
while (value || count > (point_pos - 2))
276+
/*
277+
* Generate digits till there are no non-zero digits left and we emitted
278+
* at least one to the left of the decimal point. digit_pos is the
279+
* current digit position, with zero as the digit just left of the decimal
280+
* point, increasing to the right.
281+
*/
282+
digit_pos = points;
283+
do
267284
{
268-
if (points && count == point_pos)
269-
buf[count--] = dsymbol;
270-
else if (comma && count % (mon_group + 1) == comma_position)
271-
buf[count--] = comma;
285+
if (points && digit_pos == 0)
286+
{
287+
/* insert decimal point */
288+
*(--bufptr) = dsymbol;
289+
}
290+
else if (digit_pos < points && (digit_pos % mon_group) == 0)
291+
{
292+
/* insert thousands sep */
293+
bufptr -= strlen(ssymbol);
294+
memcpy(bufptr, ssymbol, strlen(ssymbol));
295+
}
272296

273-
buf[count--] = ((unsigned int) value % 10) + '0';
297+
*(--bufptr) = ((unsigned int) value % 10) + '0';
274298
value = ((unsigned int) value) / 10;
275-
}
276-
277-
strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
278-
count -= strlen(csymbol) - 1;
299+
digit_pos--;
300+
} while (value || digit_pos >= 0);
279301

280-
if (buf[LAST_DIGIT] == ',')
281-
buf[LAST_DIGIT] = buf[LAST_PAREN];
302+
/* prepend csymbol */
303+
bufptr -= strlen(csymbol);
304+
memcpy(bufptr, csymbol, strlen(csymbol));
282305

283306
/* see if we need to signify negative amount */
284307
if (minus)
285308
{
286-
result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
309+
result = palloc(strlen(bufptr) + strlen(nsymbol) + 3);
287310

288311
/* Position code of 0 means use parens */
289312
if (convention == 0)
290-
sprintf(result, "(%s)", buf + count);
313+
sprintf(result, "(%s)", bufptr);
291314
else if (convention == 2)
292-
sprintf(result, "%s%s", buf + count, nsymbol);
315+
sprintf(result, "%s%s", bufptr, nsymbol);
293316
else
294-
sprintf(result, "%s%s", nsymbol, buf + count);
317+
sprintf(result, "%s%s", nsymbol, bufptr);
295318
}
296319
else
297320
{
298-
result = palloc(CASH_BUFSZ + 2 - count);
299-
strcpy(result, buf + count);
321+
/* just emit what we have */
322+
result = pstrdup(bufptr);
300323
}
301324

302325
PG_RETURN_CSTRING(result);

0 commit comments

Comments
 (0)