diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index f9eea76fd5..60f117a333 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17766,6 +17766,9 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup()); pg_relation_size + + pg_size_bytes + pg_size_pretty @@ -17836,6 +17839,15 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup()); Shorthand for pg_relation_size(..., 'main') + + + pg_size_bytes(text) + + bigint + + Converts a size in human-readable format with size units into bytes + + pg_size_pretty(bigint) @@ -17968,10 +17980,26 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup()); pg_size_pretty can be used to format the result of one of - the other functions in a human-readable way, using kB, MB, GB or TB as - appropriate. + the other functions in a human-readable way, using bytes, kB, MB, GB or TB + as appropriate. + + pg_size_bytes can be used to get the size in bytes from a + string in human-readable format. The input may have units of bytes, kB, + MB, GB or TB, and is parsed case-insensitively. If no units are specified, + bytes are assumed. + + + + + The units kB, MB, GB and TB used by the functions + pg_size_pretty and pg_size_bytes are defined + using powers of 2 rather than powers of 10, so 1kB is 1024 bytes, 1MB is + 10242 = 1048576 bytes, and so on. + + + The functions above that operate on tables or indexes accept a regclass argument, which is simply the OID of the table or index diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c index 208469203d..d1072d9ccd 100644 --- a/src/backend/utils/adt/dbsize.c +++ b/src/backend/utils/adt/dbsize.c @@ -699,6 +699,155 @@ pg_size_pretty_numeric(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text(result)); } +/* + * 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 * diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index b4131f9eb4..8687abb97e 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201602171 +#define CATALOG_VERSION_NO 201602201 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 2222e8fdf1..59c50d9342 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -3601,6 +3601,8 @@ DATA(insert OID = 2288 ( pg_size_pretty PGNSP PGUID 12 1 0 0 0 f f f f t f i s DESCR("convert a long int to a human readable text using size units"); DATA(insert OID = 3166 ( pg_size_pretty PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 25 "1700" _null_ _null_ _null_ _null_ _null_ pg_size_pretty_numeric _null_ _null_ _null_ )); DESCR("convert a numeric to a human readable text using size units"); +DATA(insert OID = 3334 ( pg_size_bytes PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 20 "25" _null_ _null_ _null_ _null_ _null_ pg_size_bytes _null_ _null_ _null_ )); +DESCR("convert a size in human-readable format with size units into bytes"); DATA(insert OID = 2997 ( pg_table_size PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 20 "2205" _null_ _null_ _null_ _null_ _null_ pg_table_size _null_ _null_ _null_ )); DESCR("disk space usage for the specified table, including TOAST, free space and visibility map"); DATA(insert OID = 2998 ( pg_indexes_size PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 20 "2205" _null_ _null_ _null_ _null_ _null_ pg_indexes_size _null_ _null_ _null_ )); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index a784de9d28..94c188163a 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -473,6 +473,7 @@ extern Datum pg_relation_size(PG_FUNCTION_ARGS); extern Datum pg_total_relation_size(PG_FUNCTION_ARGS); extern Datum pg_size_pretty(PG_FUNCTION_ARGS); extern Datum pg_size_pretty_numeric(PG_FUNCTION_ARGS); +extern Datum pg_size_bytes(PG_FUNCTION_ARGS); extern Datum pg_table_size(PG_FUNCTION_ARGS); extern Datum pg_indexes_size(PG_FUNCTION_ARGS); extern Datum pg_relation_filenode(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/dbsize.out b/src/test/regress/expected/dbsize.out index aa513e7eff..20d8cb5689 100644 --- a/src/test/regress/expected/dbsize.out +++ b/src/test/regress/expected/dbsize.out @@ -35,3 +35,112 @@ SELECT size, pg_size_pretty(size), pg_size_pretty(-1 * size) FROM 1000000000000000.5 | 909 TB | -909 TB (12 rows) +SELECT size, pg_size_bytes(size) FROM + (VALUES ('1'), ('123bytes'), ('1kB'), ('1MB'), (' 1 GB'), ('1.5 GB '), + ('1TB'), ('3000 TB'), ('1e6 MB')) x(size); + size | pg_size_bytes +----------+------------------ + 1 | 1 + 123bytes | 123 + 1kB | 1024 + 1MB | 1048576 + 1 GB | 1073741824 + 1.5 GB | 1610612736 + 1TB | 1099511627776 + 3000 TB | 3298534883328000 + 1e6 MB | 1048576000000 +(9 rows) + +-- case-insensitive units are supported +SELECT size, pg_size_bytes(size) FROM + (VALUES ('1'), ('123bYteS'), ('1kb'), ('1mb'), (' 1 Gb'), ('1.5 gB '), + ('1tb'), ('3000 tb'), ('1e6 mb')) x(size); + size | pg_size_bytes +----------+------------------ + 1 | 1 + 123bYteS | 123 + 1kb | 1024 + 1mb | 1048576 + 1 Gb | 1073741824 + 1.5 gB | 1610612736 + 1tb | 1099511627776 + 3000 tb | 3298534883328000 + 1e6 mb | 1048576000000 +(9 rows) + +-- negative numbers are supported +SELECT size, pg_size_bytes(size) FROM + (VALUES ('-1'), ('-123bytes'), ('-1kb'), ('-1mb'), (' -1 Gb'), ('-1.5 gB '), + ('-1tb'), ('-3000 TB'), ('-10e-1 MB')) x(size); + size | pg_size_bytes +-----------+------------------- + -1 | -1 + -123bytes | -123 + -1kb | -1024 + -1mb | -1048576 + -1 Gb | -1073741824 + -1.5 gB | -1610612736 + -1tb | -1099511627776 + -3000 TB | -3298534883328000 + -10e-1 MB | -1048576 +(9 rows) + +-- different cases with allowed points +SELECT size, pg_size_bytes(size) FROM + (VALUES ('-1.'), ('-1.kb'), ('-1. kb'), ('-0. gb'), + ('-.1'), ('-.1kb'), ('-.1 kb'), ('-.0 gb')) x(size); + size | pg_size_bytes +--------+--------------- + -1. | -1 + -1.kb | -1024 + -1. kb | -1024 + -0. gb | 0 + -.1 | 0 + -.1kb | -102 + -.1 kb | -102 + -.0 gb | 0 +(8 rows) + +-- invalid inputs +SELECT pg_size_bytes('1 AB'); +ERROR: invalid size: "1 AB" +DETAIL: Invalid size unit: "AB". +HINT: Valid units are "bytes", "kB", "MB", "GB", and "TB". +SELECT pg_size_bytes('1 AB A'); +ERROR: invalid size: "1 AB A" +DETAIL: Invalid size unit: "AB A". +HINT: Valid units are "bytes", "kB", "MB", "GB", and "TB". +SELECT pg_size_bytes('1 AB A '); +ERROR: invalid size: "1 AB A " +DETAIL: Invalid size unit: "AB A". +HINT: Valid units are "bytes", "kB", "MB", "GB", and "TB". +SELECT pg_size_bytes('9223372036854775807.9'); +ERROR: bigint out of range +SELECT pg_size_bytes('1e100'); +ERROR: bigint out of range +SELECT pg_size_bytes('1e1000000000000000000'); +ERROR: invalid size: "1e1000000000000000000" +SELECT pg_size_bytes('1 byte'); -- the singular "byte" is not supported +ERROR: invalid size: "1 byte" +DETAIL: Invalid size unit: "byte". +HINT: Valid units are "bytes", "kB", "MB", "GB", and "TB". +SELECT pg_size_bytes(''); +ERROR: invalid size: "" +SELECT pg_size_bytes('kb'); +ERROR: invalid size: "kb" +SELECT pg_size_bytes('..'); +ERROR: invalid size: ".." +SELECT pg_size_bytes('-.'); +ERROR: invalid size: "-." +SELECT pg_size_bytes('-.kb'); +ERROR: invalid size: "-.kb" +SELECT pg_size_bytes('-. kb'); +ERROR: invalid size: "-. kb" +SELECT pg_size_bytes('.+912'); +ERROR: invalid size: ".+912" +SELECT pg_size_bytes('+912+ kB'); +ERROR: invalid size: "+912+ kB" +DETAIL: Invalid size unit: "+ kB". +HINT: Valid units are "bytes", "kB", "MB", "GB", and "TB". +SELECT pg_size_bytes('++123 kB'); +ERROR: invalid size: "++123 kB" diff --git a/src/test/regress/sql/dbsize.sql b/src/test/regress/sql/dbsize.sql index c118090cc6..d10a4d7f68 100644 --- a/src/test/regress/sql/dbsize.sql +++ b/src/test/regress/sql/dbsize.sql @@ -10,3 +10,42 @@ SELECT size, pg_size_pretty(size), pg_size_pretty(-1 * size) FROM (10.5::numeric), (1000.5::numeric), (1000000.5::numeric), (1000000000.5::numeric), (1000000000000.5::numeric), (1000000000000000.5::numeric)) x(size); + +SELECT size, pg_size_bytes(size) FROM + (VALUES ('1'), ('123bytes'), ('1kB'), ('1MB'), (' 1 GB'), ('1.5 GB '), + ('1TB'), ('3000 TB'), ('1e6 MB')) x(size); + +-- case-insensitive units are supported +SELECT size, pg_size_bytes(size) FROM + (VALUES ('1'), ('123bYteS'), ('1kb'), ('1mb'), (' 1 Gb'), ('1.5 gB '), + ('1tb'), ('3000 tb'), ('1e6 mb')) x(size); + +-- negative numbers are supported +SELECT size, pg_size_bytes(size) FROM + (VALUES ('-1'), ('-123bytes'), ('-1kb'), ('-1mb'), (' -1 Gb'), ('-1.5 gB '), + ('-1tb'), ('-3000 TB'), ('-10e-1 MB')) x(size); + +-- different cases with allowed points +SELECT size, pg_size_bytes(size) FROM + (VALUES ('-1.'), ('-1.kb'), ('-1. kb'), ('-0. gb'), + ('-.1'), ('-.1kb'), ('-.1 kb'), ('-.0 gb')) x(size); + +-- invalid inputs +SELECT pg_size_bytes('1 AB'); +SELECT pg_size_bytes('1 AB A'); +SELECT pg_size_bytes('1 AB A '); +SELECT pg_size_bytes('9223372036854775807.9'); +SELECT pg_size_bytes('1e100'); +SELECT pg_size_bytes('1e1000000000000000000'); +SELECT pg_size_bytes('1 byte'); -- the singular "byte" is not supported +SELECT pg_size_bytes(''); + +SELECT pg_size_bytes('kb'); +SELECT pg_size_bytes('..'); +SELECT pg_size_bytes('-.'); +SELECT pg_size_bytes('-.kb'); +SELECT pg_size_bytes('-. kb'); + +SELECT pg_size_bytes('.+912'); +SELECT pg_size_bytes('+912+ kB'); +SELECT pg_size_bytes('++123 kB');