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');