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

Lines changed: 112 additions & 89 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy