Skip to content

Commit 5093944

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 0418bea commit 5093944

File tree

1 file changed

+109
-95
lines changed

1 file changed

+109
-95
lines changed

src/backend/utils/adt/cash.c

Lines changed: 109 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@
2828
#include "utils/cash.h"
2929
#include "utils/pg_locale.h"
3030

31-
#define CASH_BUFSZ 36
32-
33-
#define TERMINATOR (CASH_BUFSZ - 1)
34-
#define LAST_PAREN (TERMINATOR - 1)
35-
#define LAST_DIGIT (LAST_PAREN - 1)
36-
3731

3832
/*************************************************************************
3933
* Private routines
@@ -106,13 +100,13 @@ cash_in(PG_FUNCTION_ARGS)
106100
Cash value = 0;
107101
Cash dec = 0;
108102
Cash sgn = 1;
109-
int seen_dot = 0;
103+
bool seen_dot = false;
110104
const char *s = str;
111105
int fpoint;
112-
char dsymbol,
113-
ssymbol,
114-
psymbol;
115-
const char *nsymbol,
106+
char dsymbol;
107+
const char *ssymbol,
108+
*psymbol,
109+
*nsymbol,
116110
*csymbol;
117111

118112
struct lconv *lconvert = PGLC_localeconv();
@@ -131,18 +125,22 @@ cash_in(PG_FUNCTION_ARGS)
131125
if (fpoint < 0 || fpoint > 10)
132126
fpoint = 2; /* best guess in this case, I think */
133127

134-
dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
135-
if (*lconvert->mon_thousands_sep != '\0')
136-
ssymbol = *lconvert->mon_thousands_sep;
128+
/* we restrict dsymbol to be a single byte, but not the other symbols */
129+
if (*lconvert->mon_decimal_point != '\0' &&
130+
lconvert->mon_decimal_point[1] == '\0')
131+
dsymbol = *lconvert->mon_decimal_point;
137132
else
138-
/* ssymbol should not equal dsymbol */
139-
ssymbol = (dsymbol != ',') ? ',' : '.';
140-
csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
141-
psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
142-
nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
133+
dsymbol = '.';
134+
if (*lconvert->mon_thousands_sep != '\0')
135+
ssymbol = lconvert->mon_thousands_sep;
136+
else /* ssymbol should not equal dsymbol */
137+
ssymbol = (dsymbol != ',') ? "," : ".";
138+
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
139+
psymbol = (*lconvert->positive_sign != '\0') ? lconvert->positive_sign : "+";
140+
nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
143141

144142
#ifdef CASHDEBUG
145-
printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
143+
printf("cashin- precision '%d'; decimal '%c'; thousands '%s'; currency '%s'; positive '%s'; negative '%s'\n",
146144
fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
147145
#endif
148146

@@ -164,22 +162,20 @@ cash_in(PG_FUNCTION_ARGS)
164162
{
165163
sgn = -1;
166164
s += strlen(nsymbol);
167-
#ifdef CASHDEBUG
168-
printf("cashin- negative symbol; string is '%s'\n", s);
169-
#endif
170165
}
171166
else if (*s == '(')
172167
{
173168
sgn = -1;
174169
s++;
175170
}
176-
else if (*s == psymbol)
177-
s++;
171+
else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
172+
s += strlen(psymbol);
178173

179174
#ifdef CASHDEBUG
180175
printf("cashin- string is '%s'\n", s);
181176
#endif
182177

178+
/* allow whitespace and currency symbol after the sign, too */
183179
while (isspace((unsigned char) *s))
184180
s++;
185181
if (strncmp(s, csymbol, strlen(csymbol)) == 0)
@@ -189,7 +185,7 @@ cash_in(PG_FUNCTION_ARGS)
189185
printf("cashin- string is '%s'\n", s);
190186
#endif
191187

192-
for (;; s++)
188+
for (; *s; s++)
193189
{
194190
/* we look for digits as long as we have found less */
195191
/* than the required number of decimal places */
@@ -203,33 +199,44 @@ cash_in(PG_FUNCTION_ARGS)
203199
/* decimal point? then start counting fractions... */
204200
else if (*s == dsymbol && !seen_dot)
205201
{
206-
seen_dot = 1;
202+
seen_dot = true;
207203
}
208204
/* ignore if "thousands" separator, else we're done */
209-
else if (*s != ssymbol)
210-
{
211-
/* round off */
212-
if (isdigit((unsigned char) *s) && *s >= '5')
213-
value++;
214-
215-
/* adjust for less than required decimal places */
216-
for (; dec < fpoint; dec++)
217-
value *= 10;
218-
205+
else if (strncmp(s, ssymbol, strlen(ssymbol)) == 0)
206+
s += strlen(ssymbol) - 1;
207+
else
219208
break;
220-
}
221209
}
222210

223-
/* should only be trailing digits followed by whitespace or right paren */
211+
/* round off if there's another digit */
212+
if (isdigit((unsigned char) *s) && *s >= '5')
213+
value++;
214+
215+
/* adjust for less than required decimal places */
216+
for (; dec < fpoint; dec++)
217+
value *= 10;
218+
219+
/*
220+
* should only be trailing digits followed by whitespace, right paren,
221+
* or possibly a trailing minus sign
222+
*/
224223
while (isdigit((unsigned char) *s))
225224
s++;
226-
while (isspace((unsigned char) *s) || *s == ')')
227-
s++;
228-
229-
if (*s != '\0')
230-
ereport(ERROR,
231-
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
232-
errmsg("invalid input syntax for type money: \"%s\"", str)));
225+
while (*s)
226+
{
227+
if (isspace((unsigned char) *s) || *s == ')')
228+
s++;
229+
else if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
230+
{
231+
sgn = -1;
232+
s += strlen(nsymbol);
233+
}
234+
else
235+
ereport(ERROR,
236+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
237+
errmsg("invalid input syntax for type money: \"%s\"",
238+
str)));
239+
}
233240

234241
result = value * sgn;
235242

@@ -242,26 +249,24 @@ cash_in(PG_FUNCTION_ARGS)
242249

243250

244251
/* cash_out()
245-
* Function to convert cash to a dollars and cents representation.
246-
* XXX HACK This code appears to assume US conventions for
247-
* positive-valued amounts. - tgl 97/04/14
252+
* Function to convert cash to a dollars and cents representation, using
253+
* the lc_monetary locale's formatting.
248254
*/
249255
Datum
250256
cash_out(PG_FUNCTION_ARGS)
251257
{
252258
Cash value = PG_GETARG_CASH(0);
253259
char *result;
254-
char buf[CASH_BUFSZ];
255-
int minus = 0;
256-
int count = LAST_DIGIT;
257-
int point_pos;
258-
int ssymbol_position = 0;
260+
char buf[128];
261+
char *bufptr;
262+
bool minus = false;
263+
int digit_pos;
259264
int points,
260265
mon_group;
261-
char ssymbol;
262-
const char *csymbol,
263-
*nsymbol;
264266
char dsymbol;
267+
const char *ssymbol,
268+
*csymbol,
269+
*nsymbol;
265270
char convention;
266271

267272
struct lconv *lconvert = PGLC_localeconv();
@@ -280,69 +285,78 @@ cash_out(PG_FUNCTION_ARGS)
280285
mon_group = 3;
281286

282287
convention = lconvert->n_sign_posn;
283-
dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
284-
if (*lconvert->mon_thousands_sep != '\0')
285-
ssymbol = *lconvert->mon_thousands_sep;
286-
else
287-
/* ssymbol should not equal dsymbol */
288-
ssymbol = (dsymbol != ',') ? ',' : '.';
289-
csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
290-
nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
291-
292-
point_pos = LAST_DIGIT - points;
293288

294-
point_pos -= (points - 1) / mon_group;
295-
ssymbol_position = point_pos % (mon_group + 1);
289+
/* we restrict dsymbol to be a single byte, but not the other symbols */
290+
if (*lconvert->mon_decimal_point != '\0' &&
291+
lconvert->mon_decimal_point[1] == '\0')
292+
dsymbol = *lconvert->mon_decimal_point;
293+
else
294+
dsymbol = '.';
295+
if (*lconvert->mon_thousands_sep != '\0')
296+
ssymbol = lconvert->mon_thousands_sep;
297+
else /* ssymbol should not equal dsymbol */
298+
ssymbol = (dsymbol != ',') ? "," : ".";
299+
csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
300+
nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
296301

297302
/* we work with positive amounts and add the minus sign at the end */
298303
if (value < 0)
299304
{
300-
minus = 1;
305+
minus = true;
301306
value = -value;
302307
}
303308

304-
/* allow for trailing negative strings */
305-
MemSet(buf, ' ', CASH_BUFSZ);
306-
buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
309+
/* we build the result string right-to-left in buf[] */
310+
bufptr = buf + sizeof(buf) - 1;
311+
*bufptr = '\0';
307312

308-
while (value || count > (point_pos - 2))
313+
/*
314+
* Generate digits till there are no non-zero digits left and we emitted
315+
* at least one to the left of the decimal point. digit_pos is the
316+
* current digit position, with zero as the digit just left of the decimal
317+
* point, increasing to the right.
318+
*/
319+
digit_pos = points;
320+
do
309321
{
310-
if (points && count == point_pos)
311-
buf[count--] = dsymbol;
312-
else if (ssymbol && count % (mon_group + 1) == ssymbol_position)
313-
buf[count--] = ssymbol;
322+
if (points && digit_pos == 0)
323+
{
324+
/* insert decimal point */
325+
*(--bufptr) = dsymbol;
326+
}
327+
else if (digit_pos < points && (digit_pos % mon_group) == 0)
328+
{
329+
/* insert thousands sep */
330+
bufptr -= strlen(ssymbol);
331+
memcpy(bufptr, ssymbol, strlen(ssymbol));
332+
}
314333

315-
buf[count--] = ((uint64) value % 10) + '0';
334+
*(--bufptr) = ((uint64) value % 10) + '0';
316335
value = ((uint64) value) / 10;
317-
}
318-
319-
strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
320-
count -= strlen(csymbol) - 1;
336+
digit_pos--;
337+
} while (value || digit_pos >= 0);
321338

322-
/*
323-
* If points == 0 and the number of digits % mon_group == 0, the code
324-
* above adds a trailing ssymbol on the far right, so remove it.
325-
*/
326-
if (buf[LAST_DIGIT] == ssymbol)
327-
buf[LAST_DIGIT] = '\0';
339+
/* prepend csymbol */
340+
bufptr -= strlen(csymbol);
341+
memcpy(bufptr, csymbol, strlen(csymbol));
328342

329343
/* see if we need to signify negative amount */
330344
if (minus)
331345
{
332-
result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
346+
result = palloc(strlen(bufptr) + strlen(nsymbol) + 3);
333347

334348
/* Position code of 0 means use parens */
335349
if (convention == 0)
336-
sprintf(result, "(%s)", buf + count);
350+
sprintf(result, "(%s)", bufptr);
337351
else if (convention == 2)
338-
sprintf(result, "%s%s", buf + count, nsymbol);
352+
sprintf(result, "%s%s", bufptr, nsymbol);
339353
else
340-
sprintf(result, "%s%s", nsymbol, buf + count);
354+
sprintf(result, "%s%s", nsymbol, bufptr);
341355
}
342356
else
343357
{
344-
result = palloc(CASH_BUFSZ + 2 - count);
345-
strcpy(result, buf + count);
358+
/* just emit what we have */
359+
result = pstrdup(bufptr);
346360
}
347361

348362
PG_RETURN_CSTRING(result);

0 commit comments

Comments
 (0)