From ce0109193e49ef3cda84415ed5a117afd3c47521 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Wed, 24 Nov 2021 12:31:07 +0100 Subject: [PATCH v5 3/6] Move scanint8() to numutils.c Move scanint8() to numutils.c and rename to pg_strtoint64(). We already have a "16" and "32" version of that, and the code inside the functions was aligned, so this move makes all three versions consistent. The API is also changed to no longer provide the errorOK case. Instead, provide another function for such uses that just wraps the OS's strtoll() or similar. We already have such a function for the strtoull() variant, which is called pg_strtouint64(), so this version will be called pg_strtoint64(), except we already used that name above. FIXME --- src/backend/parser/parse_node.c | 12 ++- src/backend/replication/pgoutput/pgoutput.c | 9 +- src/backend/utils/adt/int8.c | 90 +------------------ src/backend/utils/adt/numutils.c | 97 +++++++++++++++++++++ src/bin/pgbench/pgbench.c | 4 +- src/include/utils/builtins.h | 2 + src/include/utils/int8.h | 25 ------ 7 files changed, 117 insertions(+), 122 deletions(-) delete mode 100644 src/include/utils/int8.h diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index 8cfe6f67c0..bb82439dc9 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -26,7 +26,6 @@ #include "parser/parse_relation.h" #include "parser/parsetree.h" #include "utils/builtins.h" -#include "utils/int8.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/varbit.h" @@ -353,7 +352,6 @@ make_const(ParseState *pstate, A_Const *aconst) { Const *con; Datum val; - int64 val64; Oid typeid; int typelen; bool typebyval; @@ -384,8 +382,15 @@ make_const(ParseState *pstate, A_Const *aconst) break; case T_Float: + { /* could be an oversize integer as well as a float ... */ - if (scanint8(aconst->val.fval.val, true, &val64)) + + int64 val64; + char *endptr; + + errno = 0; + val64 = pg_strtoint64xx(aconst->val.fval.val, &endptr, 10); + if (!errno && *endptr == '\0') { /* * It might actually fit in int32. Probably only INT_MIN can @@ -425,6 +430,7 @@ make_const(ParseState *pstate, A_Const *aconst) typebyval = false; } break; + } case T_String: diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index 6f6a203dea..2f0f40c75d 100644 --- a/src/backend/replication/pgoutput/pgoutput.c +++ b/src/backend/replication/pgoutput/pgoutput.c @@ -21,7 +21,6 @@ #include "replication/logicalproto.h" #include "replication/origin.h" #include "replication/pgoutput.h" -#include "utils/int8.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -205,7 +204,8 @@ parse_output_parameters(List *options, PGOutputData *data) /* Check each param, whether or not we recognize it */ if (strcmp(defel->defname, "proto_version") == 0) { - int64 parsed; + unsigned long parsed; + char *endptr; if (protocol_version_given) ereport(ERROR, @@ -213,12 +213,13 @@ parse_output_parameters(List *options, PGOutputData *data) errmsg("conflicting or redundant options"))); protocol_version_given = true; - if (!scanint8(strVal(defel->arg), true, &parsed)) + parsed = strtoul(strVal(defel->arg), &endptr, 10); + if (errno || *endptr != '\0') ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid proto_version"))); - if (parsed > PG_UINT32_MAX || parsed < 0) + if (parsed > PG_UINT32_MAX) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("proto_version \"%s\" out of range", diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 2168080dcc..f8f557526f 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -24,7 +24,6 @@ #include "nodes/supportnodes.h" #include "optimizer/optimizer.h" #include "utils/builtins.h" -#include "utils/int8.h" typedef struct @@ -45,99 +44,14 @@ typedef struct * Formatting and conversion routines. *---------------------------------------------------------*/ -/* - * scanint8 --- try to parse a string into an int8. - * - * If errorOK is false, ereport a useful error message if the string is bad. - * If errorOK is true, just return "false" for bad input. - */ -bool -scanint8(const char *str, bool errorOK, int64 *result) -{ - const char *ptr = str; - int64 tmp = 0; - bool neg = false; - - /* - * Do our own scan, rather than relying on sscanf which might be broken - * for long long. - * - * As INT64_MIN can't be stored as a positive 64 bit integer, accumulate - * value as a negative number. - */ - - /* skip leading spaces */ - while (*ptr && isspace((unsigned char) *ptr)) - ptr++; - - /* handle sign */ - if (*ptr == '-') - { - ptr++; - neg = true; - } - else if (*ptr == '+') - ptr++; - - /* require at least one digit */ - if (unlikely(!isdigit((unsigned char) *ptr))) - goto invalid_syntax; - - /* process digits */ - while (*ptr && isdigit((unsigned char) *ptr)) - { - int8 digit = (*ptr++ - '0'); - - if (unlikely(pg_mul_s64_overflow(tmp, 10, &tmp)) || - unlikely(pg_sub_s64_overflow(tmp, digit, &tmp))) - goto out_of_range; - } - - /* allow trailing whitespace, but not other trailing chars */ - while (*ptr != '\0' && isspace((unsigned char) *ptr)) - ptr++; - - if (unlikely(*ptr != '\0')) - goto invalid_syntax; - - if (!neg) - { - /* could fail if input is most negative number */ - if (unlikely(tmp == PG_INT64_MIN)) - goto out_of_range; - tmp = -tmp; - } - - *result = tmp; - return true; - -out_of_range: - if (!errorOK) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value \"%s\" is out of range for type %s", - str, "bigint"))); - return false; - -invalid_syntax: - if (!errorOK) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s: \"%s\"", - "bigint", str))); - return false; -} - /* int8in() */ Datum int8in(PG_FUNCTION_ARGS) { - char *str = PG_GETARG_CSTRING(0); - int64 result; + char *num = PG_GETARG_CSTRING(0); - (void) scanint8(str, false, &result); - PG_RETURN_INT64(result); + PG_RETURN_INT64(pg_strtoint64(num)); } diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c index b93096f288..ebc2d222a3 100644 --- a/src/backend/utils/adt/numutils.c +++ b/src/backend/utils/adt/numutils.c @@ -325,6 +325,90 @@ pg_strtoint32(const char *s) return 0; /* keep compiler quiet */ } +/* + * Convert input string to a signed 64 bit integer. + * + * Allows any number of leading or trailing whitespace characters. Will throw + * ereport() upon bad input format or overflow. + * + * NB: Accumulate input as a negative number, to deal with two's complement + * representation of the most negative number, which can't be represented as a + * positive number. + */ +int64 +pg_strtoint64(const char *s) +{ + const char *ptr = s; + int64 tmp = 0; + bool neg = false; + + /* + * Do our own scan, rather than relying on sscanf which might be broken + * for long long. + * + * As INT64_MIN can't be stored as a positive 64 bit integer, accumulate + * value as a negative number. + */ + + /* skip leading spaces */ + while (*ptr && isspace((unsigned char) *ptr)) + ptr++; + + /* handle sign */ + if (*ptr == '-') + { + ptr++; + neg = true; + } + else if (*ptr == '+') + ptr++; + + /* require at least one digit */ + if (unlikely(!isdigit((unsigned char) *ptr))) + goto invalid_syntax; + + /* process digits */ + while (*ptr && isdigit((unsigned char) *ptr)) + { + int8 digit = (*ptr++ - '0'); + + if (unlikely(pg_mul_s64_overflow(tmp, 10, &tmp)) || + unlikely(pg_sub_s64_overflow(tmp, digit, &tmp))) + goto out_of_range; + } + + /* allow trailing whitespace, but not other trailing chars */ + while (*ptr != '\0' && isspace((unsigned char) *ptr)) + ptr++; + + if (unlikely(*ptr != '\0')) + goto invalid_syntax; + + if (!neg) + { + /* could fail if input is most negative number */ + if (unlikely(tmp == PG_INT64_MIN)) + goto out_of_range; + tmp = -tmp; + } + + return tmp; + +out_of_range: + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", + s, "bigint"))); + +invalid_syntax: + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "bigint", s))); + + return 0; /* keep compiler quiet */ +} + /* * pg_itoa: converts a signed 16-bit integer to its string representation * and returns strlen(a). @@ -628,3 +712,16 @@ pg_strtouint64(const char *str, char **endptr, int base) return strtoul(str, endptr, base); #endif } + +// XXX unfortunate API naming conflict +int64 +pg_strtoint64xx(const char *str, char **endptr, int base) +{ +#ifdef _MSC_VER /* MSVC only */ + return _strtoi64(str, endptr, base); +#elif defined(HAVE_STRTOLL) && SIZEOF_LONG < 8 + return strtoll(str, endptr, base); +#else + return strtol(str, endptr, base); +#endif +} diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index c12b6f0615..026c5e3083 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -794,8 +794,8 @@ is_an_int(const char *str) /* * strtoint64 -- convert a string to 64-bit integer * - * This function is a slightly modified version of scanint8() from - * src/backend/utils/adt/int8.c. + * This function is a slightly modified version of pg_strtoint64() from + * src/backend/utils/adt/numutils.c. * * The function returns whether the conversion worked, and if so * "*result" is set to the result. diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 40fcb0ab6d..e8b2abace9 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -46,6 +46,7 @@ extern int namestrcmp(Name name, const char *str); extern int32 pg_atoi(const char *s, int size, int c); extern int16 pg_strtoint16(const char *s); extern int32 pg_strtoint32(const char *s); +extern int64 pg_strtoint64(const char *s); extern int pg_itoa(int16 i, char *a); extern int pg_ultoa_n(uint32 l, char *a); extern int pg_ulltoa_n(uint64 l, char *a); @@ -54,6 +55,7 @@ extern int pg_lltoa(int64 ll, char *a); extern char *pg_ultostr_zeropad(char *str, uint32 value, int32 minwidth); extern char *pg_ultostr(char *str, uint32 value); extern uint64 pg_strtouint64(const char *str, char **endptr, int base); +extern int64 pg_strtoint64xx(const char *str, char **endptr, int base); /* oid.c */ extern oidvector *buildoidvector(const Oid *oids, int n); diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h deleted file mode 100644 index 6571188f90..0000000000 --- a/src/include/utils/int8.h +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------- - * - * int8.h - * Declarations for operations on 64-bit integers. - * - * - * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * src/include/utils/int8.h - * - * NOTES - * These data types are supported on all 64-bit architectures, and may - * be supported through libraries on some 32-bit machines. If your machine - * is not currently supported, then please try to make it so, then post - * patches to the postgresql.org hackers mailing list. - * - *------------------------------------------------------------------------- - */ -#ifndef INT8_H -#define INT8_H - -extern bool scanint8(const char *str, bool errorOK, int64 *result); - -#endif /* INT8_H */ -- 2.33.1