diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c index b336185df7..a146b0a0bc 100644 --- a/src/backend/utils/adt/cash.c +++ b/src/backend/utils/adt/cash.c @@ -189,13 +189,30 @@ cash_in(PG_FUNCTION_ARGS) printf("cashin- string is '%s'\n", s); #endif + /* + * We accumulate the absolute amount in "value" and then apply the sign at + * the end. (The sign can appear before or after the digits, so it would + * be more complicated to do otherwise.) Because of the larger range of + * negative signed integers, we build "value" in the negative and then + * flip the sign at the end, catching most-negative-number overflow if + * necessary. + */ + for (; *s; s++) { /* we look for digits as long as we have found less */ /* than the required number of decimal places */ if (isdigit((unsigned char) *s) && (!seen_dot || dec < fpoint)) { - value = (value * 10) + (*s - '0'); + Cash newvalue = (value * 10) - (*s - '0'); + + if (newvalue / 10 != value) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type money", + str))); + + value = newvalue; if (seen_dot) dec++; @@ -214,11 +231,27 @@ cash_in(PG_FUNCTION_ARGS) /* round off if there's another digit */ if (isdigit((unsigned char) *s) && *s >= '5') - value++; + value--; /* remember we build the value in the negative */ + + if (value > 0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type money", + str))); /* adjust for less than required decimal places */ for (; dec < fpoint; dec++) - value *= 10; + { + Cash newvalue = value * 10; + + if (newvalue / 10 != value) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type money", + str))); + + value = newvalue; + } /* * should only be trailing digits followed by whitespace, right paren, @@ -247,7 +280,19 @@ cash_in(PG_FUNCTION_ARGS) str))); } - result = value * sgn; + /* If the value is supposed to be positive, flip the sign, but check for + * the most negative number. */ + if (sgn > 0) + { + result = -value; + if (result < 0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type money", + str))); + } + else + result = value; #ifdef CASHDEBUG printf("cashin- result is " INT64_FORMAT "\n", result); diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out index 538235c4cc..5695f87500 100644 --- a/src/test/regress/expected/money.out +++ b/src/test/regress/expected/money.out @@ -185,6 +185,96 @@ SELECT * FROM money_data; $123.46 (1 row) +-- input checks +SELECT '1234567890'::money; + money +------------------- + $1,234,567,890.00 +(1 row) + +SELECT '12345678901234567'::money; + money +---------------------------- + $12,345,678,901,234,567.00 +(1 row) + +SELECT '123456789012345678'::money; +ERROR: value "123456789012345678" is out of range for type money +LINE 1: SELECT '123456789012345678'::money; + ^ +SELECT '9223372036854775807'::money; +ERROR: value "9223372036854775807" is out of range for type money +LINE 1: SELECT '9223372036854775807'::money; + ^ +SELECT '-12345'::money; + money +------------- + -$12,345.00 +(1 row) + +SELECT '-1234567890'::money; + money +-------------------- + -$1,234,567,890.00 +(1 row) + +SELECT '-12345678901234567'::money; + money +----------------------------- + -$12,345,678,901,234,567.00 +(1 row) + +SELECT '-123456789012345678'::money; +ERROR: value "-123456789012345678" is out of range for type money +LINE 1: SELECT '-123456789012345678'::money; + ^ +SELECT '-9223372036854775808'::money; +ERROR: value "-9223372036854775808" is out of range for type money +LINE 1: SELECT '-9223372036854775808'::money; + ^ +-- special characters +SELECT '(1)'::money; + money +-------- + -$1.00 +(1 row) + +SELECT '($123,456.78)'::money; + money +-------------- + -$123,456.78 +(1 row) + +-- documented minimums and maximums +SELECT '-92233720368547758.08'::money; + money +----------------------------- + -$92,233,720,368,547,758.08 +(1 row) + +SELECT '92233720368547758.07'::money; + money +---------------------------- + $92,233,720,368,547,758.07 +(1 row) + +SELECT '-92233720368547758.09'::money; +ERROR: value "-92233720368547758.09" is out of range for type money +LINE 1: SELECT '-92233720368547758.09'::money; + ^ +SELECT '92233720368547758.08'::money; +ERROR: value "92233720368547758.08" is out of range for type money +LINE 1: SELECT '92233720368547758.08'::money; + ^ +-- rounding +SELECT '-92233720368547758.085'::money; +ERROR: value "-92233720368547758.085" is out of range for type money +LINE 1: SELECT '-92233720368547758.085'::money; + ^ +SELECT '92233720368547758.075'::money; +ERROR: value "92233720368547758.075" is out of range for type money +LINE 1: SELECT '92233720368547758.075'::money; + ^ -- Cast int4/int8 to money SELECT 1234567890::money; money @@ -198,10 +288,6 @@ SELECT 12345678901234567::money; $12,345,678,901,234,567.00 (1 row) -SELECT 123456789012345678::money; -ERROR: bigint out of range -SELECT 9223372036854775807::money; -ERROR: bigint out of range SELECT (-12345)::money; money ------------- @@ -220,10 +306,6 @@ SELECT (-12345678901234567)::money; -$12,345,678,901,234,567.00 (1 row) -SELECT (-123456789012345678)::money; -ERROR: bigint out of range -SELECT (-9223372036854775808)::money; -ERROR: bigint out of range SELECT 1234567890::int4::money; money ------------------- diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql index 09b9476b70..561ccb527f 100644 --- a/src/test/regress/sql/money.sql +++ b/src/test/regress/sql/money.sql @@ -57,16 +57,38 @@ DELETE FROM money_data; INSERT INTO money_data VALUES ('$123.459'); SELECT * FROM money_data; +-- input checks +SELECT '1234567890'::money; +SELECT '12345678901234567'::money; +SELECT '123456789012345678'::money; +SELECT '9223372036854775807'::money; +SELECT '-12345'::money; +SELECT '-1234567890'::money; +SELECT '-12345678901234567'::money; +SELECT '-123456789012345678'::money; +SELECT '-9223372036854775808'::money; + +-- special characters +SELECT '(1)'::money; +SELECT '($123,456.78)'::money; + +-- documented minimums and maximums +SELECT '-92233720368547758.08'::money; +SELECT '92233720368547758.07'::money; + +SELECT '-92233720368547758.09'::money; +SELECT '92233720368547758.08'::money; + +-- rounding +SELECT '-92233720368547758.085'::money; +SELECT '92233720368547758.075'::money; + -- Cast int4/int8 to money SELECT 1234567890::money; SELECT 12345678901234567::money; -SELECT 123456789012345678::money; -SELECT 9223372036854775807::money; SELECT (-12345)::money; SELECT (-1234567890)::money; SELECT (-12345678901234567)::money; -SELECT (-123456789012345678)::money; -SELECT (-9223372036854775808)::money; SELECT 1234567890::int4::money; SELECT 12345678901234567::int8::money; SELECT (-1234567890)::int4::money;