Add pg_size_bytes() to parse human-readable size strings.
authorDean Rasheed <dean.a.rasheed@gmail.com>
Sat, 20 Feb 2016 09:57:27 +0000 (09:57 +0000)
committerDean Rasheed <dean.a.rasheed@gmail.com>
Sat, 20 Feb 2016 09:57:27 +0000 (09:57 +0000)
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

doc/src/sgml/func.sgml
src/backend/utils/adt/dbsize.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.h
src/include/utils/builtins.h
src/test/regress/expected/dbsize.out
src/test/regress/sql/dbsize.sql

index f9eea76fd5f1646dd64b1aa2ab17bd26b7f24e1d..60f117a33334991147bc9d3b56431da7f275b020 100644 (file)
@@ -17766,6 +17766,9 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
    <indexterm>
     <primary>pg_relation_size</primary>
    </indexterm>
+   <indexterm>
+    <primary>pg_size_bytes</primary>
+   </indexterm>
    <indexterm>
     <primary>pg_size_pretty</primary>
    </indexterm>
@@ -17836,6 +17839,15 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
         Shorthand for <literal>pg_relation_size(..., 'main')</literal>
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_size_bytes(<type>text</type>)</function></literal>
+        </entry>
+       <entry><type>bigint</type></entry>
+       <entry>
+         Converts a size in human-readable format with size units into bytes
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_size_pretty(<type>bigint</type>)</function></literal>
@@ -17968,10 +17980,26 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
 
    <para>
     <function>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.
    </para>
 
+   <para>
+    <function>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.
+   </para>
+
+   <note>
+    <para>
+     The units kB, MB, GB and TB used by the functions
+     <function>pg_size_pretty</> and <function>pg_size_bytes</> are defined
+     using powers of 2 rather than powers of 10, so 1kB is 1024 bytes, 1MB is
+     1024<superscript>2</> = 1048576 bytes, and so on.
+    </para>
+   </note>
+
    <para>
     The functions above that operate on tables or indexes accept a
     <type>regclass</> argument, which is simply the OID of the table or index
index 208469203d8ae76ded306ab5e26d4d252cc9e37d..d1072d9ccd6e85968eeef9531bb9e1a093bfb8de 100644 (file)
@@ -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
  *
index b4131f9eb45be28c41f522264b66a2841cf6aa85..8687abb97e70412c6150b5e8049f7fa8ddffdb9a 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 201602171
+#define CATALOG_VERSION_NO 201602201
 
 #endif
index 2222e8fdf13043d166da3b7b8f626a96ad58e8fe..59c50d93427f75aab2d538d315afd1b57d2216c9 100644 (file)
@@ -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_ ));
index a784de9d287cbf5c405387c0e58c3afb488ab524..94c188163a7435e156b59053063ce936840eaa05 100644 (file)
@@ -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);
index aa513e7eff05c1804d29543ea170a7fa1d07f282..20d8cb56893aac4b6dcfa471322159893e272fbe 100644 (file)
@@ -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"
index c118090cc6439f5d955cfd7d93ef955ef21163d4..d10a4d7f68a4f7d73058d92e666cd633ec3adcf0 100644 (file)
@@ -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');