diff options
author | Dean Rasheed <dean.a.rasheed@gmail.com> | 2016-02-20 09:57:27 +0000 |
---|---|---|
committer | Dean Rasheed <dean.a.rasheed@gmail.com> | 2016-02-20 09:57:27 +0000 |
commit | 53874c5228fe16589a4d01b3e1fab3678e0fd8e3 (patch) | |
tree | 7ca2581b3e8731254b62b31fbb2f95a1301b3ef1 /src/backend/utils/adt/dbsize.c | |
parent | 091b6055e3e52338850370f17835e833ca66ac55 (diff) | |
download | postgresql-53874c5228fe16589a4d01b3e1fab3678e0fd8e3.tar.gz postgresql-53874c5228fe16589a4d01b3e1fab3678e0fd8e3.zip |
Add pg_size_bytes() to parse human-readable size strings.
This will parse strings in the format produced by pg_size_pretty() and
return sizes in bytes. This allows queries to be written with clauses
like "pg_total_relation_size(oid) > pg_size_bytes('10 GB')".
Author: Pavel Stehule with various improvements by Vitaly Burovoy
Discussion: http://www.postgresql.org/message-id/CAFj8pRD-tGoDKnxdYgECzA4On01_uRqPrwF-8LdkSE-6bDHp0w@mail.gmail.com
Reviewed-by: Vitaly Burovoy, Oleksandr Shulgin, Kyotaro Horiguchi,
Michael Paquier and Robert Haas
Diffstat (limited to 'src/backend/utils/adt/dbsize.c')
-rw-r--r-- | src/backend/utils/adt/dbsize.c | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c index 208469203d8..d1072d9ccd6 100644 --- a/src/backend/utils/adt/dbsize.c +++ b/src/backend/utils/adt/dbsize.c @@ -700,6 +700,155 @@ pg_size_pretty_numeric(PG_FUNCTION_ARGS) } /* + * Convert a human-readable size to a size in bytes + */ +Datum +pg_size_bytes(PG_FUNCTION_ARGS) +{ + text *arg = PG_GETARG_TEXT_PP(0); + char *str, + *strptr, + *endptr; + char saved_char; + Numeric num; + int64 result; + bool have_digits = false; + + str = text_to_cstring(arg); + + /* Skip leading whitespace */ + strptr = str; + while (isspace((unsigned char) *strptr)) + strptr++; + + /* Check that we have a valid number and determine where it ends */ + endptr = strptr; + + /* Part (1): sign */ + if (*endptr == '-' || *endptr == '+') + endptr++; + + /* Part (2): main digit string */ + if (isdigit((unsigned char) *endptr)) + { + have_digits = true; + do + endptr++; + while (isdigit((unsigned char) *endptr)); + } + + /* Part (3): optional decimal point and fractional digits */ + if (*endptr == '.') + { + endptr++; + if (isdigit((unsigned char) *endptr)) + { + have_digits = true; + do + endptr++; + while (isdigit((unsigned char) *endptr)); + } + } + + /* Complain if we don't have a valid number at this point */ + if (!have_digits) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid size: \"%s\"", str))); + + /* Part (4): optional exponent */ + if (*endptr == 'e' || *endptr == 'E') + { + long exponent; + char *cp; + + /* + * Note we might one day support EB units, so if what follows isn't a + * number, just treat it all as a unit to be parsed. + */ + exponent = strtol(endptr + 1, &cp, 10); + if (cp > endptr + 1) + { + if (exponent > NUMERIC_MAX_PRECISION || + exponent < -NUMERIC_MAX_PRECISION) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid size: \"%s\"", str))); + endptr = cp; + } + } + + /* + * Parse the number, saving the next character, which may be the first + * character of the unit string. + */ + saved_char = *endptr; + *endptr = '\0'; + + num = DatumGetNumeric(DirectFunctionCall3(numeric_in, + CStringGetDatum(strptr), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1))); + + *endptr = saved_char; + + /* Skip whitespace between number and unit */ + strptr = endptr; + while (isspace((unsigned char) *strptr)) + strptr++; + + /* Handle possible unit */ + if (*strptr != '\0') + { + int64 multiplier = 0; + + /* Trim any trailing whitespace */ + endptr = str + VARSIZE_ANY_EXHDR(arg) - 1; + + while (isspace((unsigned char) *endptr)) + endptr--; + + endptr++; + *endptr = '\0'; + + /* Parse the unit case-insensitively */ + if (pg_strcasecmp(strptr, "bytes") == 0) + multiplier = 1; + else if (pg_strcasecmp(strptr, "kb") == 0) + multiplier = 1024; + else if (pg_strcasecmp(strptr, "mb") == 0) + multiplier = 1024 * 1024; + else if (pg_strcasecmp(strptr, "gb") == 0) + multiplier = 1024 * 1024 * 1024; + else if (pg_strcasecmp(strptr, "tb") == 0) + multiplier = 1024 * 1024 * 1024 * 1024L; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid size: \"%s\"", text_to_cstring(arg)), + errdetail("Invalid size unit: \"%s\".", strptr), + errhint("Valid units are \"bytes\", \"kB\", \"MB\", \"GB\", and \"TB\"."))); + + if (multiplier > 1) + { + Numeric mul_num; + + mul_num = DatumGetNumeric(DirectFunctionCall1(int8_numeric, + Int64GetDatum(multiplier))); + + num = DatumGetNumeric(DirectFunctionCall2(numeric_mul, + NumericGetDatum(mul_num), + NumericGetDatum(num))); + } + } + + result = DatumGetInt64(DirectFunctionCall1(numeric_int8, + NumericGetDatum(num))); + + PG_RETURN_INT64(result); +} + +/* * Get the filenode of a relation * * This is expected to be used in queries like |