diff options
Diffstat (limited to 'src/backend/utils/adt/numeric.c')
-rw-r--r-- | src/backend/utils/adt/numeric.c | 360 |
1 files changed, 299 insertions, 61 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 67edb70ab82..898c52099bd 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -500,6 +500,11 @@ static void zero_var(NumericVar *var); static bool set_var_from_str(const char *str, const char *cp, NumericVar *dest, const char **endptr, Node *escontext); +static bool set_var_from_non_decimal_integer_str(const char *str, + const char *cp, int sign, + int base, NumericVar *dest, + const char **endptr, + Node *escontext); static void set_var_from_num(Numeric num, NumericVar *dest); static void init_var_from_num(Numeric num, NumericVar *dest); static void set_var_from_var(const NumericVar *value, NumericVar *dest); @@ -629,6 +634,8 @@ numeric_in(PG_FUNCTION_ARGS) Node *escontext = fcinfo->context; Numeric res; const char *cp; + const char *numstart; + int sign; /* Skip leading spaces */ cp = str; @@ -640,70 +647,130 @@ numeric_in(PG_FUNCTION_ARGS) } /* - * Check for NaN and infinities. We recognize the same strings allowed by - * float8in(). + * Process the number's sign. This duplicates logic in set_var_from_str(), + * but it's worth doing here, since it simplifies the handling of + * infinities and non-decimal integers. */ - if (pg_strncasecmp(cp, "NaN", 3) == 0) - { - res = make_result(&const_nan); - cp += 3; - } - else if (pg_strncasecmp(cp, "Infinity", 8) == 0) - { - res = make_result(&const_pinf); - cp += 8; - } - else if (pg_strncasecmp(cp, "+Infinity", 9) == 0) - { - res = make_result(&const_pinf); - cp += 9; - } - else if (pg_strncasecmp(cp, "-Infinity", 9) == 0) - { - res = make_result(&const_ninf); - cp += 9; - } - else if (pg_strncasecmp(cp, "inf", 3) == 0) - { - res = make_result(&const_pinf); - cp += 3; - } - else if (pg_strncasecmp(cp, "+inf", 4) == 0) + numstart = cp; + sign = NUMERIC_POS; + + if (*cp == '+') + cp++; + else if (*cp == '-') { - res = make_result(&const_pinf); - cp += 4; + sign = NUMERIC_NEG; + cp++; } - else if (pg_strncasecmp(cp, "-inf", 4) == 0) + + /* + * Check for NaN and infinities. We recognize the same strings allowed by + * float8in(). + * + * Since all other legal inputs have a digit or a decimal point after the + * sign, we need only check for NaN/infinity if that's not the case. + */ + if (!isdigit((unsigned char) *cp) && *cp != '.') { - res = make_result(&const_ninf); - cp += 4; + /* + * The number must be NaN or infinity; anything else can only be a + * syntax error. Note that NaN mustn't have a sign. + */ + if (pg_strncasecmp(numstart, "NaN", 3) == 0) + { + res = make_result(&const_nan); + cp = numstart + 3; + } + else if (pg_strncasecmp(cp, "Infinity", 8) == 0) + { + res = make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf); + cp += 8; + } + else if (pg_strncasecmp(cp, "inf", 3) == 0) + { + res = make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf); + cp += 3; + } + else + goto invalid_syntax; + + /* + * Check for trailing junk; there should be nothing left but spaces. + * + * We intentionally do this check before applying the typmod because + * we would like to throw any trailing-junk syntax error before any + * semantic error resulting from apply_typmod_special(). + */ + while (*cp) + { + if (!isspace((unsigned char) *cp)) + goto invalid_syntax; + cp++; + } + + if (!apply_typmod_special(res, typmod, escontext)) + PG_RETURN_NULL(); } else { /* - * Use set_var_from_str() to parse a normal numeric value + * We have a normal numeric value, which may be a non-decimal integer + * or a regular decimal number. */ NumericVar value; + int base; bool have_error; init_var(&value); - if (!set_var_from_str(str, cp, &value, &cp, escontext)) - PG_RETURN_NULL(); + /* + * Determine the number's base by looking for a non-decimal prefix + * indicator ("0x", "0o", or "0b"). + */ + if (cp[0] == '0') + { + switch (cp[1]) + { + case 'x': + case 'X': + base = 16; + break; + case 'o': + case 'O': + base = 8; + break; + case 'b': + case 'B': + base = 2; + break; + default: + base = 10; + } + } + else + base = 10; + + /* Parse the rest of the number and apply the sign */ + if (base == 10) + { + if (!set_var_from_str(str, cp, &value, &cp, escontext)) + PG_RETURN_NULL(); + value.sign = sign; + } + else + { + if (!set_var_from_non_decimal_integer_str(str, cp + 2, sign, base, + &value, &cp, escontext)) + PG_RETURN_NULL(); + } /* - * We duplicate a few lines of code here because we would like to - * throw any trailing-junk syntax error before any semantic error - * resulting from apply_typmod. We can't easily fold the two cases - * together because we mustn't apply apply_typmod to a NaN/Inf. + * Should be nothing left but spaces. As above, throw any typmod error + * after finishing syntax check. */ while (*cp) { if (!isspace((unsigned char) *cp)) - ereturn(escontext, (Datum) 0, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s: \"%s\"", - "numeric", str))); + goto invalid_syntax; cp++; } @@ -718,26 +785,15 @@ numeric_in(PG_FUNCTION_ARGS) errmsg("value overflows numeric format"))); free_var(&value); - - PG_RETURN_NUMERIC(res); - } - - /* Should be nothing left but spaces */ - while (*cp) - { - if (!isspace((unsigned char) *cp)) - ereturn(escontext, (Datum) 0, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s: \"%s\"", - "numeric", str))); - cp++; } - /* As above, throw any typmod error after finishing syntax check */ - if (!apply_typmod_special(res, typmod, escontext)) - PG_RETURN_NULL(); - PG_RETURN_NUMERIC(res); + +invalid_syntax: + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "numeric", str))); } @@ -6993,6 +7049,188 @@ set_var_from_str(const char *str, const char *cp, /* + * Return the numeric value of a single hex digit. + */ +static inline int +xdigit_value(char dig) +{ + return dig >= '0' && dig <= '9' ? dig - '0' : + dig >= 'a' && dig <= 'f' ? dig - 'a' + 10 : + dig >= 'A' && dig <= 'F' ? dig - 'A' + 10 : -1; +} + +/* + * set_var_from_non_decimal_integer_str() + * + * Parse a string containing a non-decimal integer + * + * This function does not handle leading or trailing spaces. It returns + * the end+1 position parsed into *endptr, so that caller can check for + * trailing spaces/garbage if deemed necessary. + * + * cp is the place to actually start parsing; str is what to use in error + * reports. The number's sign and base prefix indicator (e.g., "0x") are + * assumed to have already been parsed, so cp should point to the number's + * first digit in the base specified. + * + * base is expected to be 2, 8 or 16. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). + */ +static bool +set_var_from_non_decimal_integer_str(const char *str, const char *cp, int sign, + int base, NumericVar *dest, + const char **endptr, Node *escontext) +{ + const char *firstdigit = cp; + int64 tmp; + int64 mul; + NumericVar tmp_var; + + init_var(&tmp_var); + + zero_var(dest); + + /* + * Process input digits in groups that fit in int64. Here "tmp" is the + * value of the digits in the group, and "mul" is base^n, where n is the + * number of digits in the group. Thus tmp < mul, and we must start a new + * group when mul * base threatens to overflow PG_INT64_MAX. + */ + tmp = 0; + mul = 1; + + if (base == 16) + { + while (*cp) + { + if (isxdigit((unsigned char) *cp)) + { + if (mul > PG_INT64_MAX / 16) + { + /* Add the contribution from this group of digits */ + int64_to_numericvar(mul, &tmp_var); + mul_var(dest, &tmp_var, dest, 0); + int64_to_numericvar(tmp, &tmp_var); + add_var(dest, &tmp_var, dest); + + /* Result will overflow if weight overflows int16 */ + if (dest->weight > SHRT_MAX) + goto out_of_range; + + /* Begin a new group */ + tmp = 0; + mul = 1; + } + + tmp = tmp * 16 + xdigit_value(*cp++); + mul = mul * 16; + } + else + break; + } + } + else if (base == 8) + { + while (*cp) + { + if (*cp >= '0' && *cp <= '7') + { + if (mul > PG_INT64_MAX / 8) + { + /* Add the contribution from this group of digits */ + int64_to_numericvar(mul, &tmp_var); + mul_var(dest, &tmp_var, dest, 0); + int64_to_numericvar(tmp, &tmp_var); + add_var(dest, &tmp_var, dest); + + /* Result will overflow if weight overflows int16 */ + if (dest->weight > SHRT_MAX) + goto out_of_range; + + /* Begin a new group */ + tmp = 0; + mul = 1; + } + + tmp = tmp * 8 + (*cp++ - '0'); + mul = mul * 8; + } + else + break; + } + } + else if (base == 2) + { + while (*cp) + { + if (*cp >= '0' && *cp <= '1') + { + if (mul > PG_INT64_MAX / 2) + { + /* Add the contribution from this group of digits */ + int64_to_numericvar(mul, &tmp_var); + mul_var(dest, &tmp_var, dest, 0); + int64_to_numericvar(tmp, &tmp_var); + add_var(dest, &tmp_var, dest); + + /* Result will overflow if weight overflows int16 */ + if (dest->weight > SHRT_MAX) + goto out_of_range; + + /* Begin a new group */ + tmp = 0; + mul = 1; + } + + tmp = tmp * 2 + (*cp++ - '0'); + mul = mul * 2; + } + else + break; + } + } + else + /* Should never happen; treat as invalid input */ + goto invalid_syntax; + + /* Check that we got at least one digit */ + if (unlikely(cp == firstdigit)) + goto invalid_syntax; + + /* Add the contribution from the final group of digits */ + int64_to_numericvar(mul, &tmp_var); + mul_var(dest, &tmp_var, dest, 0); + int64_to_numericvar(tmp, &tmp_var); + add_var(dest, &tmp_var, dest); + + if (dest->weight > SHRT_MAX) + goto out_of_range; + + dest->sign = sign; + + free_var(&tmp_var); + + /* Return end+1 position for caller */ + *endptr = cp; + + return true; + +out_of_range: + ereturn(escontext, false, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + +invalid_syntax: + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "numeric", str))); +} + + +/* * set_var_from_num() - * * Convert the packed db format into a variable |