Replace static bufs with a StringInfo in cash_words()

For clarity. The code was correct, and the buffer was large enough,
but string manipulation with no bounds checking is scary.

This incurs an extra palloc+pfree to every call, but in quick
performance testing, it doesn't seem to be significant.

Reviewed-by: Robert Haas
Discussion: https://www.postgresql.org/message-id/7f86e06a-98c5-4ce3-8ec9-3885c8de0358@iki.fi
This commit is contained in:
Heikki Linnakangas 2024-07-30 22:02:58 +03:00
parent 47c98035c6
commit 5bf948d564

View File

@ -35,10 +35,9 @@
* Private routines * Private routines
************************************************************************/ ************************************************************************/
static const char * static void
num_word(Cash value) append_num_word(StringInfo buf, Cash value)
{ {
static char buf[128];
static const char *const small[] = { static const char *const small[] = {
"zero", "one", "two", "three", "four", "five", "six", "seven", "zero", "one", "two", "three", "four", "five", "six", "seven",
"eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
@ -50,13 +49,16 @@ num_word(Cash value)
/* deal with the simple cases first */ /* deal with the simple cases first */
if (value <= 20) if (value <= 20)
return small[value]; {
appendStringInfoString(buf, small[value]);
return;
}
/* is it an even multiple of 100? */ /* is it an even multiple of 100? */
if (!tu) if (!tu)
{ {
sprintf(buf, "%s hundred", small[value / 100]); appendStringInfo(buf, "%s hundred", small[value / 100]);
return buf; return;
} }
/* more than 99? */ /* more than 99? */
@ -64,27 +66,25 @@ num_word(Cash value)
{ {
/* is it an even multiple of 10 other than 10? */ /* is it an even multiple of 10 other than 10? */
if (value % 10 == 0 && tu > 10) if (value % 10 == 0 && tu > 10)
sprintf(buf, "%s hundred %s", appendStringInfo(buf, "%s hundred %s",
small[value / 100], big[tu / 10]); small[value / 100], big[tu / 10]);
else if (tu < 20) else if (tu < 20)
sprintf(buf, "%s hundred and %s", appendStringInfo(buf, "%s hundred and %s",
small[value / 100], small[tu]); small[value / 100], small[tu]);
else else
sprintf(buf, "%s hundred %s %s", appendStringInfo(buf, "%s hundred %s %s",
small[value / 100], big[tu / 10], small[tu % 10]); small[value / 100], big[tu / 10], small[tu % 10]);
} }
else else
{ {
/* is it an even multiple of 10 other than 10? */ /* is it an even multiple of 10 other than 10? */
if (value % 10 == 0 && tu > 10) if (value % 10 == 0 && tu > 10)
sprintf(buf, "%s", big[tu / 10]); appendStringInfoString(buf, big[tu / 10]);
else if (tu < 20) else if (tu < 20)
sprintf(buf, "%s", small[tu]); appendStringInfoString(buf, small[tu]);
else else
sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]); appendStringInfo(buf, "%s %s", big[tu / 10], small[tu % 10]);
} }
return buf;
} /* num_word() */ } /* num_word() */
static inline Cash static inline Cash
@ -960,8 +960,9 @@ cash_words(PG_FUNCTION_ARGS)
{ {
Cash value = PG_GETARG_CASH(0); Cash value = PG_GETARG_CASH(0);
uint64 val; uint64 val;
char buf[256]; StringInfoData buf;
char *p = buf; text *res;
Cash dollars;
Cash m0; Cash m0;
Cash m1; Cash m1;
Cash m2; Cash m2;
@ -970,19 +971,19 @@ cash_words(PG_FUNCTION_ARGS)
Cash m5; Cash m5;
Cash m6; Cash m6;
initStringInfo(&buf);
/* work with positive numbers */ /* work with positive numbers */
if (value < 0) if (value < 0)
{ {
value = -value; value = -value;
strcpy(buf, "minus "); appendStringInfoString(&buf, "minus ");
p += 6;
} }
else
buf[0] = '\0';
/* Now treat as unsigned, to avoid trouble at INT_MIN */ /* Now treat as unsigned, to avoid trouble at INT_MIN */
val = (uint64) value; val = (uint64) value;
dollars = val / INT64CONST(100);
m0 = val % INT64CONST(100); /* cents */ m0 = val % INT64CONST(100); /* cents */
m1 = (val / INT64CONST(100)) % 1000; /* hundreds */ m1 = (val / INT64CONST(100)) % 1000; /* hundreds */
m2 = (val / INT64CONST(100000)) % 1000; /* thousands */ m2 = (val / INT64CONST(100000)) % 1000; /* thousands */
@ -993,49 +994,51 @@ cash_words(PG_FUNCTION_ARGS)
if (m6) if (m6)
{ {
strcat(buf, num_word(m6)); append_num_word(&buf, m6);
strcat(buf, " quadrillion "); appendStringInfoString(&buf, " quadrillion ");
} }
if (m5) if (m5)
{ {
strcat(buf, num_word(m5)); append_num_word(&buf, m5);
strcat(buf, " trillion "); appendStringInfoString(&buf, " trillion ");
} }
if (m4) if (m4)
{ {
strcat(buf, num_word(m4)); append_num_word(&buf, m4);
strcat(buf, " billion "); appendStringInfoString(&buf, " billion ");
} }
if (m3) if (m3)
{ {
strcat(buf, num_word(m3)); append_num_word(&buf, m3);
strcat(buf, " million "); appendStringInfoString(&buf, " million ");
} }
if (m2) if (m2)
{ {
strcat(buf, num_word(m2)); append_num_word(&buf, m2);
strcat(buf, " thousand "); appendStringInfoString(&buf, " thousand ");
} }
if (m1) if (m1)
strcat(buf, num_word(m1)); append_num_word(&buf, m1);
if (!*p) if (dollars == 0)
strcat(buf, "zero"); appendStringInfoString(&buf, "zero");
strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and "); appendStringInfoString(&buf, dollars == 1 ? " dollar and " : " dollars and ");
strcat(buf, num_word(m0)); append_num_word(&buf, m0);
strcat(buf, m0 == 1 ? " cent" : " cents"); appendStringInfoString(&buf, m0 == 1 ? " cent" : " cents");
/* capitalize output */ /* capitalize output */
buf[0] = pg_toupper((unsigned char) buf[0]); buf.data[0] = pg_toupper((unsigned char) buf.data[0]);
/* return as text datum */ /* return as text datum */
PG_RETURN_TEXT_P(cstring_to_text(buf)); res = cstring_to_text_with_len(buf.data, buf.len);
pfree(buf.data);
PG_RETURN_TEXT_P(res);
} }