LCOV - code coverage report
Current view: top level - src/backend/utils/adt - dbsize.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 200 358 55.9 %
Date: 2025-01-18 04:15:08 Functions: 15 27 55.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * dbsize.c
       3             :  *      Database object size functions, and related inquiries
       4             :  *
       5             :  * Copyright (c) 2002-2025, PostgreSQL Global Development Group
       6             :  *
       7             :  * IDENTIFICATION
       8             :  *    src/backend/utils/adt/dbsize.c
       9             :  *
      10             :  */
      11             : 
      12             : #include "postgres.h"
      13             : 
      14             : #include <sys/stat.h>
      15             : 
      16             : #include "access/htup_details.h"
      17             : #include "access/relation.h"
      18             : #include "catalog/namespace.h"
      19             : #include "catalog/pg_authid.h"
      20             : #include "catalog/pg_database.h"
      21             : #include "catalog/pg_tablespace.h"
      22             : #include "commands/dbcommands.h"
      23             : #include "commands/tablespace.h"
      24             : #include "miscadmin.h"
      25             : #include "storage/fd.h"
      26             : #include "utils/acl.h"
      27             : #include "utils/builtins.h"
      28             : #include "utils/numeric.h"
      29             : #include "utils/rel.h"
      30             : #include "utils/relfilenumbermap.h"
      31             : #include "utils/relmapper.h"
      32             : #include "utils/syscache.h"
      33             : 
      34             : /* Divide by two and round away from zero */
      35             : #define half_rounded(x)   (((x) + ((x) < 0 ? -1 : 1)) / 2)
      36             : 
      37             : /* Units used in pg_size_pretty functions.  All units must be powers of 2 */
      38             : struct size_pretty_unit
      39             : {
      40             :     const char *name;           /* bytes, kB, MB, GB etc */
      41             :     uint32      limit;          /* upper limit, prior to half rounding after
      42             :                                  * converting to this unit. */
      43             :     bool        round;          /* do half rounding for this unit */
      44             :     uint8       unitbits;       /* (1 << unitbits) bytes to make 1 of this
      45             :                                  * unit */
      46             : };
      47             : 
      48             : /* When adding units here also update the docs and the error message in pg_size_bytes */
      49             : static const struct size_pretty_unit size_pretty_units[] = {
      50             :     {"bytes", 10 * 1024, false, 0},
      51             :     {"kB", 20 * 1024 - 1, true, 10},
      52             :     {"MB", 20 * 1024 - 1, true, 20},
      53             :     {"GB", 20 * 1024 - 1, true, 30},
      54             :     {"TB", 20 * 1024 - 1, true, 40},
      55             :     {"PB", 20 * 1024 - 1, true, 50},
      56             :     {NULL, 0, false, 0}
      57             : };
      58             : 
      59             : /* Additional unit aliases accepted by pg_size_bytes */
      60             : struct size_bytes_unit_alias
      61             : {
      62             :     const char *alias;
      63             :     int         unit_index;     /* corresponding size_pretty_units element */
      64             : };
      65             : 
      66             : /* When adding units here also update the docs and the error message in pg_size_bytes */
      67             : static const struct size_bytes_unit_alias size_bytes_aliases[] = {
      68             :     {"B", 0},
      69             :     {NULL}
      70             : };
      71             : 
      72             : /* Return physical size of directory contents, or 0 if dir doesn't exist */
      73             : static int64
      74           0 : db_dir_size(const char *path)
      75             : {
      76           0 :     int64       dirsize = 0;
      77             :     struct dirent *direntry;
      78             :     DIR        *dirdesc;
      79             :     char        filename[MAXPGPATH * 2];
      80             : 
      81           0 :     dirdesc = AllocateDir(path);
      82             : 
      83           0 :     if (!dirdesc)
      84           0 :         return 0;
      85             : 
      86           0 :     while ((direntry = ReadDir(dirdesc, path)) != NULL)
      87             :     {
      88             :         struct stat fst;
      89             : 
      90           0 :         CHECK_FOR_INTERRUPTS();
      91             : 
      92           0 :         if (strcmp(direntry->d_name, ".") == 0 ||
      93           0 :             strcmp(direntry->d_name, "..") == 0)
      94           0 :             continue;
      95             : 
      96           0 :         snprintf(filename, sizeof(filename), "%s/%s", path, direntry->d_name);
      97             : 
      98           0 :         if (stat(filename, &fst) < 0)
      99             :         {
     100           0 :             if (errno == ENOENT)
     101           0 :                 continue;
     102             :             else
     103           0 :                 ereport(ERROR,
     104             :                         (errcode_for_file_access(),
     105             :                          errmsg("could not stat file \"%s\": %m", filename)));
     106             :         }
     107           0 :         dirsize += fst.st_size;
     108             :     }
     109             : 
     110           0 :     FreeDir(dirdesc);
     111           0 :     return dirsize;
     112             : }
     113             : 
     114             : /*
     115             :  * calculate size of database in all tablespaces
     116             :  */
     117             : static int64
     118           0 : calculate_database_size(Oid dbOid)
     119             : {
     120             :     int64       totalsize;
     121             :     DIR        *dirdesc;
     122             :     struct dirent *direntry;
     123             :     char        dirpath[MAXPGPATH];
     124             :     char        pathname[MAXPGPATH + 21 + sizeof(TABLESPACE_VERSION_DIRECTORY)];
     125             :     AclResult   aclresult;
     126             : 
     127             :     /*
     128             :      * User must have connect privilege for target database or have privileges
     129             :      * of pg_read_all_stats
     130             :      */
     131           0 :     aclresult = object_aclcheck(DatabaseRelationId, dbOid, GetUserId(), ACL_CONNECT);
     132           0 :     if (aclresult != ACLCHECK_OK &&
     133           0 :         !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
     134             :     {
     135           0 :         aclcheck_error(aclresult, OBJECT_DATABASE,
     136           0 :                        get_database_name(dbOid));
     137             :     }
     138             : 
     139             :     /* Shared storage in pg_global is not counted */
     140             : 
     141             :     /* Include pg_default storage */
     142           0 :     snprintf(pathname, sizeof(pathname), "base/%u", dbOid);
     143           0 :     totalsize = db_dir_size(pathname);
     144             : 
     145             :     /* Scan the non-default tablespaces */
     146           0 :     snprintf(dirpath, MAXPGPATH, PG_TBLSPC_DIR);
     147           0 :     dirdesc = AllocateDir(dirpath);
     148             : 
     149           0 :     while ((direntry = ReadDir(dirdesc, dirpath)) != NULL)
     150             :     {
     151           0 :         CHECK_FOR_INTERRUPTS();
     152             : 
     153           0 :         if (strcmp(direntry->d_name, ".") == 0 ||
     154           0 :             strcmp(direntry->d_name, "..") == 0)
     155           0 :             continue;
     156             : 
     157           0 :         snprintf(pathname, sizeof(pathname), "%s/%s/%s/%u",
     158           0 :                  PG_TBLSPC_DIR, direntry->d_name, TABLESPACE_VERSION_DIRECTORY, dbOid);
     159           0 :         totalsize += db_dir_size(pathname);
     160             :     }
     161             : 
     162           0 :     FreeDir(dirdesc);
     163             : 
     164           0 :     return totalsize;
     165             : }
     166             : 
     167             : Datum
     168           0 : pg_database_size_oid(PG_FUNCTION_ARGS)
     169             : {
     170           0 :     Oid         dbOid = PG_GETARG_OID(0);
     171             :     int64       size;
     172             : 
     173             :     /*
     174             :      * Not needed for correctness, but avoid non-user-facing error message
     175             :      * later if the database doesn't exist.
     176             :      */
     177           0 :     if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(dbOid)))
     178           0 :         ereport(ERROR,
     179             :                 errcode(ERRCODE_UNDEFINED_OBJECT),
     180             :                 errmsg("database with OID %u does not exist", dbOid));
     181             : 
     182           0 :     size = calculate_database_size(dbOid);
     183             : 
     184           0 :     if (size == 0)
     185           0 :         PG_RETURN_NULL();
     186             : 
     187           0 :     PG_RETURN_INT64(size);
     188             : }
     189             : 
     190             : Datum
     191           0 : pg_database_size_name(PG_FUNCTION_ARGS)
     192             : {
     193           0 :     Name        dbName = PG_GETARG_NAME(0);
     194           0 :     Oid         dbOid = get_database_oid(NameStr(*dbName), false);
     195             :     int64       size;
     196             : 
     197           0 :     size = calculate_database_size(dbOid);
     198             : 
     199           0 :     if (size == 0)
     200           0 :         PG_RETURN_NULL();
     201             : 
     202           0 :     PG_RETURN_INT64(size);
     203             : }
     204             : 
     205             : 
     206             : /*
     207             :  * Calculate total size of tablespace. Returns -1 if the tablespace directory
     208             :  * cannot be found.
     209             :  */
     210             : static int64
     211           0 : calculate_tablespace_size(Oid tblspcOid)
     212             : {
     213             :     char        tblspcPath[MAXPGPATH];
     214             :     char        pathname[MAXPGPATH * 2];
     215           0 :     int64       totalsize = 0;
     216             :     DIR        *dirdesc;
     217             :     struct dirent *direntry;
     218             :     AclResult   aclresult;
     219             : 
     220             :     /*
     221             :      * User must have privileges of pg_read_all_stats or have CREATE privilege
     222             :      * for target tablespace, either explicitly granted or implicitly because
     223             :      * it is default for current database.
     224             :      */
     225           0 :     if (tblspcOid != MyDatabaseTableSpace &&
     226           0 :         !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
     227             :     {
     228           0 :         aclresult = object_aclcheck(TableSpaceRelationId, tblspcOid, GetUserId(), ACL_CREATE);
     229           0 :         if (aclresult != ACLCHECK_OK)
     230           0 :             aclcheck_error(aclresult, OBJECT_TABLESPACE,
     231           0 :                            get_tablespace_name(tblspcOid));
     232             :     }
     233             : 
     234           0 :     if (tblspcOid == DEFAULTTABLESPACE_OID)
     235           0 :         snprintf(tblspcPath, MAXPGPATH, "base");
     236           0 :     else if (tblspcOid == GLOBALTABLESPACE_OID)
     237           0 :         snprintf(tblspcPath, MAXPGPATH, "global");
     238             :     else
     239           0 :         snprintf(tblspcPath, MAXPGPATH, "%s/%u/%s", PG_TBLSPC_DIR, tblspcOid,
     240             :                  TABLESPACE_VERSION_DIRECTORY);
     241             : 
     242           0 :     dirdesc = AllocateDir(tblspcPath);
     243             : 
     244           0 :     if (!dirdesc)
     245           0 :         return -1;
     246             : 
     247           0 :     while ((direntry = ReadDir(dirdesc, tblspcPath)) != NULL)
     248             :     {
     249             :         struct stat fst;
     250             : 
     251           0 :         CHECK_FOR_INTERRUPTS();
     252             : 
     253           0 :         if (strcmp(direntry->d_name, ".") == 0 ||
     254           0 :             strcmp(direntry->d_name, "..") == 0)
     255           0 :             continue;
     256             : 
     257           0 :         snprintf(pathname, sizeof(pathname), "%s/%s", tblspcPath, direntry->d_name);
     258             : 
     259           0 :         if (stat(pathname, &fst) < 0)
     260             :         {
     261           0 :             if (errno == ENOENT)
     262           0 :                 continue;
     263             :             else
     264           0 :                 ereport(ERROR,
     265             :                         (errcode_for_file_access(),
     266             :                          errmsg("could not stat file \"%s\": %m", pathname)));
     267             :         }
     268             : 
     269           0 :         if (S_ISDIR(fst.st_mode))
     270           0 :             totalsize += db_dir_size(pathname);
     271             : 
     272           0 :         totalsize += fst.st_size;
     273             :     }
     274             : 
     275           0 :     FreeDir(dirdesc);
     276             : 
     277           0 :     return totalsize;
     278             : }
     279             : 
     280             : Datum
     281           0 : pg_tablespace_size_oid(PG_FUNCTION_ARGS)
     282             : {
     283           0 :     Oid         tblspcOid = PG_GETARG_OID(0);
     284             :     int64       size;
     285             : 
     286             :     /*
     287             :      * Not needed for correctness, but avoid non-user-facing error message
     288             :      * later if the tablespace doesn't exist.
     289             :      */
     290           0 :     if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tblspcOid)))
     291           0 :         ereport(ERROR,
     292             :                 errcode(ERRCODE_UNDEFINED_OBJECT),
     293             :                 errmsg("tablespace with OID %u does not exist", tblspcOid));
     294             : 
     295           0 :     size = calculate_tablespace_size(tblspcOid);
     296             : 
     297           0 :     if (size < 0)
     298           0 :         PG_RETURN_NULL();
     299             : 
     300           0 :     PG_RETURN_INT64(size);
     301             : }
     302             : 
     303             : Datum
     304           0 : pg_tablespace_size_name(PG_FUNCTION_ARGS)
     305             : {
     306           0 :     Name        tblspcName = PG_GETARG_NAME(0);
     307           0 :     Oid         tblspcOid = get_tablespace_oid(NameStr(*tblspcName), false);
     308             :     int64       size;
     309             : 
     310           0 :     size = calculate_tablespace_size(tblspcOid);
     311             : 
     312           0 :     if (size < 0)
     313           0 :         PG_RETURN_NULL();
     314             : 
     315           0 :     PG_RETURN_INT64(size);
     316             : }
     317             : 
     318             : 
     319             : /*
     320             :  * calculate size of (one fork of) a relation
     321             :  *
     322             :  * Note: we can safely apply this to temp tables of other sessions, so there
     323             :  * is no check here or at the call sites for that.
     324             :  */
     325             : static int64
     326         590 : calculate_relation_size(RelFileLocator *rfn, ProcNumber backend, ForkNumber forknum)
     327             : {
     328         590 :     int64       totalsize = 0;
     329             :     char       *relationpath;
     330             :     char        pathname[MAXPGPATH];
     331         590 :     unsigned int segcount = 0;
     332             : 
     333         590 :     relationpath = relpathbackend(*rfn, backend, forknum);
     334             : 
     335         590 :     for (segcount = 0;; segcount++)
     336         278 :     {
     337             :         struct stat fst;
     338             : 
     339         868 :         CHECK_FOR_INTERRUPTS();
     340             : 
     341         868 :         if (segcount == 0)
     342         590 :             snprintf(pathname, MAXPGPATH, "%s",
     343             :                      relationpath);
     344             :         else
     345         278 :             snprintf(pathname, MAXPGPATH, "%s.%u",
     346             :                      relationpath, segcount);
     347             : 
     348         868 :         if (stat(pathname, &fst) < 0)
     349             :         {
     350         590 :             if (errno == ENOENT)
     351         590 :                 break;
     352             :             else
     353           0 :                 ereport(ERROR,
     354             :                         (errcode_for_file_access(),
     355             :                          errmsg("could not stat file \"%s\": %m", pathname)));
     356             :         }
     357         278 :         totalsize += fst.st_size;
     358             :     }
     359             : 
     360         590 :     return totalsize;
     361             : }
     362             : 
     363             : Datum
     364         208 : pg_relation_size(PG_FUNCTION_ARGS)
     365             : {
     366         208 :     Oid         relOid = PG_GETARG_OID(0);
     367         208 :     text       *forkName = PG_GETARG_TEXT_PP(1);
     368             :     Relation    rel;
     369             :     int64       size;
     370             : 
     371         208 :     rel = try_relation_open(relOid, AccessShareLock);
     372             : 
     373             :     /*
     374             :      * Before 9.2, we used to throw an error if the relation didn't exist, but
     375             :      * that makes queries like "SELECT pg_relation_size(oid) FROM pg_class"
     376             :      * less robust, because while we scan pg_class with an MVCC snapshot,
     377             :      * someone else might drop the table. It's better to return NULL for
     378             :      * already-dropped tables than throw an error and abort the whole query.
     379             :      */
     380         208 :     if (rel == NULL)
     381           2 :         PG_RETURN_NULL();
     382             : 
     383         206 :     size = calculate_relation_size(&(rel->rd_locator), rel->rd_backend,
     384         206 :                                    forkname_to_number(text_to_cstring(forkName)));
     385             : 
     386         206 :     relation_close(rel, AccessShareLock);
     387             : 
     388         206 :     PG_RETURN_INT64(size);
     389             : }
     390             : 
     391             : /*
     392             :  * Calculate total on-disk size of a TOAST relation, including its indexes.
     393             :  * Must not be applied to non-TOAST relations.
     394             :  */
     395             : static int64
     396           0 : calculate_toast_table_size(Oid toastrelid)
     397             : {
     398           0 :     int64       size = 0;
     399             :     Relation    toastRel;
     400             :     ForkNumber  forkNum;
     401             :     ListCell   *lc;
     402             :     List       *indexlist;
     403             : 
     404           0 :     toastRel = relation_open(toastrelid, AccessShareLock);
     405             : 
     406             :     /* toast heap size, including FSM and VM size */
     407           0 :     for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
     408           0 :         size += calculate_relation_size(&(toastRel->rd_locator),
     409             :                                         toastRel->rd_backend, forkNum);
     410             : 
     411             :     /* toast index size, including FSM and VM size */
     412           0 :     indexlist = RelationGetIndexList(toastRel);
     413             : 
     414             :     /* Size is calculated using all the indexes available */
     415           0 :     foreach(lc, indexlist)
     416             :     {
     417             :         Relation    toastIdxRel;
     418             : 
     419           0 :         toastIdxRel = relation_open(lfirst_oid(lc),
     420             :                                     AccessShareLock);
     421           0 :         for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
     422           0 :             size += calculate_relation_size(&(toastIdxRel->rd_locator),
     423             :                                             toastIdxRel->rd_backend, forkNum);
     424             : 
     425           0 :         relation_close(toastIdxRel, AccessShareLock);
     426             :     }
     427           0 :     list_free(indexlist);
     428           0 :     relation_close(toastRel, AccessShareLock);
     429             : 
     430           0 :     return size;
     431             : }
     432             : 
     433             : /*
     434             :  * Calculate total on-disk size of a given table,
     435             :  * including FSM and VM, plus TOAST table if any.
     436             :  * Indexes other than the TOAST table's index are not included.
     437             :  *
     438             :  * Note that this also behaves sanely if applied to an index or toast table;
     439             :  * those won't have attached toast tables, but they can have multiple forks.
     440             :  */
     441             : static int64
     442          96 : calculate_table_size(Relation rel)
     443             : {
     444          96 :     int64       size = 0;
     445             :     ForkNumber  forkNum;
     446             : 
     447             :     /*
     448             :      * heap size, including FSM and VM
     449             :      */
     450         480 :     for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
     451         384 :         size += calculate_relation_size(&(rel->rd_locator), rel->rd_backend,
     452             :                                         forkNum);
     453             : 
     454             :     /*
     455             :      * Size of toast relation
     456             :      */
     457          96 :     if (OidIsValid(rel->rd_rel->reltoastrelid))
     458           0 :         size += calculate_toast_table_size(rel->rd_rel->reltoastrelid);
     459             : 
     460          96 :     return size;
     461             : }
     462             : 
     463             : /*
     464             :  * Calculate total on-disk size of all indexes attached to the given table.
     465             :  *
     466             :  * Can be applied safely to an index, but you'll just get zero.
     467             :  */
     468             : static int64
     469           0 : calculate_indexes_size(Relation rel)
     470             : {
     471           0 :     int64       size = 0;
     472             : 
     473             :     /*
     474             :      * Aggregate all indexes on the given relation
     475             :      */
     476           0 :     if (rel->rd_rel->relhasindex)
     477             :     {
     478           0 :         List       *index_oids = RelationGetIndexList(rel);
     479             :         ListCell   *cell;
     480             : 
     481           0 :         foreach(cell, index_oids)
     482             :         {
     483           0 :             Oid         idxOid = lfirst_oid(cell);
     484             :             Relation    idxRel;
     485             :             ForkNumber  forkNum;
     486             : 
     487           0 :             idxRel = relation_open(idxOid, AccessShareLock);
     488             : 
     489           0 :             for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
     490           0 :                 size += calculate_relation_size(&(idxRel->rd_locator),
     491             :                                                 idxRel->rd_backend,
     492             :                                                 forkNum);
     493             : 
     494           0 :             relation_close(idxRel, AccessShareLock);
     495             :         }
     496             : 
     497           0 :         list_free(index_oids);
     498             :     }
     499             : 
     500           0 :     return size;
     501             : }
     502             : 
     503             : Datum
     504          96 : pg_table_size(PG_FUNCTION_ARGS)
     505             : {
     506          96 :     Oid         relOid = PG_GETARG_OID(0);
     507             :     Relation    rel;
     508             :     int64       size;
     509             : 
     510          96 :     rel = try_relation_open(relOid, AccessShareLock);
     511             : 
     512          96 :     if (rel == NULL)
     513           0 :         PG_RETURN_NULL();
     514             : 
     515          96 :     size = calculate_table_size(rel);
     516             : 
     517          96 :     relation_close(rel, AccessShareLock);
     518             : 
     519          96 :     PG_RETURN_INT64(size);
     520             : }
     521             : 
     522             : Datum
     523           0 : pg_indexes_size(PG_FUNCTION_ARGS)
     524             : {
     525           0 :     Oid         relOid = PG_GETARG_OID(0);
     526             :     Relation    rel;
     527             :     int64       size;
     528             : 
     529           0 :     rel = try_relation_open(relOid, AccessShareLock);
     530             : 
     531           0 :     if (rel == NULL)
     532           0 :         PG_RETURN_NULL();
     533             : 
     534           0 :     size = calculate_indexes_size(rel);
     535             : 
     536           0 :     relation_close(rel, AccessShareLock);
     537             : 
     538           0 :     PG_RETURN_INT64(size);
     539             : }
     540             : 
     541             : /*
     542             :  *  Compute the on-disk size of all files for the relation,
     543             :  *  including heap data, index data, toast data, FSM, VM.
     544             :  */
     545             : static int64
     546           0 : calculate_total_relation_size(Relation rel)
     547             : {
     548             :     int64       size;
     549             : 
     550             :     /*
     551             :      * Aggregate the table size, this includes size of the heap, toast and
     552             :      * toast index with free space and visibility map
     553             :      */
     554           0 :     size = calculate_table_size(rel);
     555             : 
     556             :     /*
     557             :      * Add size of all attached indexes as well
     558             :      */
     559           0 :     size += calculate_indexes_size(rel);
     560             : 
     561           0 :     return size;
     562             : }
     563             : 
     564             : Datum
     565           0 : pg_total_relation_size(PG_FUNCTION_ARGS)
     566             : {
     567           0 :     Oid         relOid = PG_GETARG_OID(0);
     568             :     Relation    rel;
     569             :     int64       size;
     570             : 
     571           0 :     rel = try_relation_open(relOid, AccessShareLock);
     572             : 
     573           0 :     if (rel == NULL)
     574           0 :         PG_RETURN_NULL();
     575             : 
     576           0 :     size = calculate_total_relation_size(rel);
     577             : 
     578           0 :     relation_close(rel, AccessShareLock);
     579             : 
     580           0 :     PG_RETURN_INT64(size);
     581             : }
     582             : 
     583             : /*
     584             :  * formatting with size units
     585             :  */
     586             : Datum
     587         306 : pg_size_pretty(PG_FUNCTION_ARGS)
     588             : {
     589         306 :     int64       size = PG_GETARG_INT64(0);
     590             :     char        buf[64];
     591             :     const struct size_pretty_unit *unit;
     592             : 
     593         786 :     for (unit = size_pretty_units; unit->name != NULL; unit++)
     594             :     {
     595             :         uint8       bits;
     596         786 :         uint64      abs_size = size < 0 ? 0 - (uint64) size : (uint64) size;
     597             : 
     598             :         /*
     599             :          * Use this unit if there are no more units or the absolute size is
     600             :          * below the limit for the current unit.
     601             :          */
     602         786 :         if (unit[1].name == NULL || abs_size < unit->limit)
     603             :         {
     604         306 :             if (unit->round)
     605         168 :                 size = half_rounded(size);
     606             : 
     607         306 :             snprintf(buf, sizeof(buf), INT64_FORMAT " %s", size, unit->name);
     608         306 :             break;
     609             :         }
     610             : 
     611             :         /*
     612             :          * Determine the number of bits to use to build the divisor.  We may
     613             :          * need to use 1 bit less than the difference between this and the
     614             :          * next unit if the next unit uses half rounding.  Or we may need to
     615             :          * shift an extra bit if this unit uses half rounding and the next one
     616             :          * does not.  We use division rather than shifting right by this
     617             :          * number of bits to ensure positive and negative values are rounded
     618             :          * in the same way.
     619             :          */
     620         480 :         bits = (unit[1].unitbits - unit->unitbits - (unit[1].round == true)
     621         480 :                 + (unit->round == true));
     622         480 :         size /= ((int64) 1) << bits;
     623             :     }
     624             : 
     625         306 :     PG_RETURN_TEXT_P(cstring_to_text(buf));
     626             : }
     627             : 
     628             : static char *
     629         288 : numeric_to_cstring(Numeric n)
     630             : {
     631         288 :     Datum       d = NumericGetDatum(n);
     632             : 
     633         288 :     return DatumGetCString(DirectFunctionCall1(numeric_out, d));
     634             : }
     635             : 
     636             : static bool
     637         912 : numeric_is_less(Numeric a, Numeric b)
     638             : {
     639         912 :     Datum       da = NumericGetDatum(a);
     640         912 :     Datum       db = NumericGetDatum(b);
     641             : 
     642         912 :     return DatumGetBool(DirectFunctionCall2(numeric_lt, da, db));
     643             : }
     644             : 
     645             : static Numeric
     646         912 : numeric_absolute(Numeric n)
     647             : {
     648         912 :     Datum       d = NumericGetDatum(n);
     649             :     Datum       result;
     650             : 
     651         912 :     result = DirectFunctionCall1(numeric_abs, d);
     652         912 :     return DatumGetNumeric(result);
     653             : }
     654             : 
     655             : static Numeric
     656         228 : numeric_half_rounded(Numeric n)
     657             : {
     658         228 :     Datum       d = NumericGetDatum(n);
     659             :     Datum       zero;
     660             :     Datum       one;
     661             :     Datum       two;
     662             :     Datum       result;
     663             : 
     664         228 :     zero = NumericGetDatum(int64_to_numeric(0));
     665         228 :     one = NumericGetDatum(int64_to_numeric(1));
     666         228 :     two = NumericGetDatum(int64_to_numeric(2));
     667             : 
     668         228 :     if (DatumGetBool(DirectFunctionCall2(numeric_ge, d, zero)))
     669         114 :         d = DirectFunctionCall2(numeric_add, d, one);
     670             :     else
     671         114 :         d = DirectFunctionCall2(numeric_sub, d, one);
     672             : 
     673         228 :     result = DirectFunctionCall2(numeric_div_trunc, d, two);
     674         228 :     return DatumGetNumeric(result);
     675             : }
     676             : 
     677             : static Numeric
     678         660 : numeric_truncated_divide(Numeric n, int64 divisor)
     679             : {
     680         660 :     Datum       d = NumericGetDatum(n);
     681             :     Datum       divisor_numeric;
     682             :     Datum       result;
     683             : 
     684         660 :     divisor_numeric = NumericGetDatum(int64_to_numeric(divisor));
     685         660 :     result = DirectFunctionCall2(numeric_div_trunc, d, divisor_numeric);
     686         660 :     return DatumGetNumeric(result);
     687             : }
     688             : 
     689             : Datum
     690         288 : pg_size_pretty_numeric(PG_FUNCTION_ARGS)
     691             : {
     692         288 :     Numeric     size = PG_GETARG_NUMERIC(0);
     693         288 :     char       *result = NULL;
     694             :     const struct size_pretty_unit *unit;
     695             : 
     696         948 :     for (unit = size_pretty_units; unit->name != NULL; unit++)
     697             :     {
     698             :         unsigned int shiftby;
     699             : 
     700             :         /* use this unit if there are no more units or we're below the limit */
     701        1860 :         if (unit[1].name == NULL ||
     702         912 :             numeric_is_less(numeric_absolute(size),
     703         912 :                             int64_to_numeric(unit->limit)))
     704             :         {
     705         288 :             if (unit->round)
     706         228 :                 size = numeric_half_rounded(size);
     707             : 
     708         288 :             result = psprintf("%s %s", numeric_to_cstring(size), unit->name);
     709         288 :             break;
     710             :         }
     711             : 
     712             :         /*
     713             :          * Determine the number of bits to use to build the divisor.  We may
     714             :          * need to use 1 bit less than the difference between this and the
     715             :          * next unit if the next unit uses half rounding.  Or we may need to
     716             :          * shift an extra bit if this unit uses half rounding and the next one
     717             :          * does not.
     718             :          */
     719         660 :         shiftby = (unit[1].unitbits - unit->unitbits - (unit[1].round == true)
     720         660 :                    + (unit->round == true));
     721         660 :         size = numeric_truncated_divide(size, ((int64) 1) << shiftby);
     722             :     }
     723             : 
     724         288 :     PG_RETURN_TEXT_P(cstring_to_text(result));
     725             : }
     726             : 
     727             : /*
     728             :  * Convert a human-readable size to a size in bytes
     729             :  */
     730             : Datum
     731         360 : pg_size_bytes(PG_FUNCTION_ARGS)
     732             : {
     733         360 :     text       *arg = PG_GETARG_TEXT_PP(0);
     734             :     char       *str,
     735             :                *strptr,
     736             :                *endptr;
     737             :     char        saved_char;
     738             :     Numeric     num;
     739             :     int64       result;
     740         360 :     bool        have_digits = false;
     741             : 
     742         360 :     str = text_to_cstring(arg);
     743             : 
     744             :     /* Skip leading whitespace */
     745         360 :     strptr = str;
     746         378 :     while (isspace((unsigned char) *strptr))
     747          18 :         strptr++;
     748             : 
     749             :     /* Check that we have a valid number and determine where it ends */
     750         360 :     endptr = strptr;
     751             : 
     752             :     /* Part (1): sign */
     753         360 :     if (*endptr == '-' || *endptr == '+')
     754         138 :         endptr++;
     755             : 
     756             :     /* Part (2): main digit string */
     757         360 :     if (isdigit((unsigned char) *endptr))
     758             :     {
     759         288 :         have_digits = true;
     760             :         do
     761         594 :             endptr++;
     762         594 :         while (isdigit((unsigned char) *endptr));
     763             :     }
     764             : 
     765             :     /* Part (3): optional decimal point and fractional digits */
     766         360 :     if (*endptr == '.')
     767             :     {
     768         102 :         endptr++;
     769         102 :         if (isdigit((unsigned char) *endptr))
     770             :         {
     771          48 :             have_digits = true;
     772             :             do
     773          48 :                 endptr++;
     774          48 :             while (isdigit((unsigned char) *endptr));
     775             :         }
     776             :     }
     777             : 
     778             :     /* Complain if we don't have a valid number at this point */
     779         360 :     if (!have_digits)
     780          48 :         ereport(ERROR,
     781             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     782             :                  errmsg("invalid size: \"%s\"", str)));
     783             : 
     784             :     /* Part (4): optional exponent */
     785         312 :     if (*endptr == 'e' || *endptr == 'E')
     786             :     {
     787             :         long        exponent;
     788             :         char       *cp;
     789             : 
     790             :         /*
     791             :          * Note we might one day support EB units, so if what follows 'E'
     792             :          * isn't a number, just treat it all as a unit to be parsed.
     793             :          */
     794          30 :         exponent = strtol(endptr + 1, &cp, 10);
     795             :         (void) exponent;        /* Silence -Wunused-result warnings */
     796          30 :         if (cp > endptr + 1)
     797          30 :             endptr = cp;
     798             :     }
     799             : 
     800             :     /*
     801             :      * Parse the number, saving the next character, which may be the first
     802             :      * character of the unit string.
     803             :      */
     804         312 :     saved_char = *endptr;
     805         312 :     *endptr = '\0';
     806             : 
     807         312 :     num = DatumGetNumeric(DirectFunctionCall3(numeric_in,
     808             :                                               CStringGetDatum(strptr),
     809             :                                               ObjectIdGetDatum(InvalidOid),
     810             :                                               Int32GetDatum(-1)));
     811             : 
     812         306 :     *endptr = saved_char;
     813             : 
     814             :     /* Skip whitespace between number and unit */
     815         306 :     strptr = endptr;
     816         450 :     while (isspace((unsigned char) *strptr))
     817         144 :         strptr++;
     818             : 
     819             :     /* Handle possible unit */
     820         306 :     if (*strptr != '\0')
     821             :     {
     822             :         const struct size_pretty_unit *unit;
     823         264 :         int64       multiplier = 0;
     824             : 
     825             :         /* Trim any trailing whitespace */
     826         264 :         endptr = str + VARSIZE_ANY_EXHDR(arg) - 1;
     827             : 
     828         306 :         while (isspace((unsigned char) *endptr))
     829          42 :             endptr--;
     830             : 
     831         264 :         endptr++;
     832         264 :         *endptr = '\0';
     833             : 
     834        1002 :         for (unit = size_pretty_units; unit->name != NULL; unit++)
     835             :         {
     836             :             /* Parse the unit case-insensitively */
     837         966 :             if (pg_strcasecmp(strptr, unit->name) == 0)
     838         228 :                 break;
     839             :         }
     840             : 
     841             :         /* If not found, look in table of aliases */
     842         264 :         if (unit->name == NULL)
     843             :         {
     844          66 :             for (const struct size_bytes_unit_alias *a = size_bytes_aliases; a->alias != NULL; a++)
     845             :             {
     846          36 :                 if (pg_strcasecmp(strptr, a->alias) == 0)
     847             :                 {
     848           6 :                     unit = &size_pretty_units[a->unit_index];
     849           6 :                     break;
     850             :                 }
     851             :             }
     852             :         }
     853             : 
     854             :         /* Verify we found a valid unit in the loop above */
     855         264 :         if (unit->name == NULL)
     856          30 :             ereport(ERROR,
     857             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     858             :                      errmsg("invalid size: \"%s\"", text_to_cstring(arg)),
     859             :                      errdetail("Invalid size unit: \"%s\".", strptr),
     860             :                      errhint("Valid units are \"bytes\", \"B\", \"kB\", \"MB\", \"GB\", \"TB\", and \"PB\".")));
     861             : 
     862         234 :         multiplier = ((int64) 1) << unit->unitbits;
     863             : 
     864         234 :         if (multiplier > 1)
     865             :         {
     866             :             Numeric     mul_num;
     867             : 
     868         210 :             mul_num = int64_to_numeric(multiplier);
     869             : 
     870         210 :             num = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
     871             :                                                       NumericGetDatum(mul_num),
     872             :                                                       NumericGetDatum(num)));
     873             :         }
     874             :     }
     875             : 
     876         276 :     result = DatumGetInt64(DirectFunctionCall1(numeric_int8,
     877             :                                                NumericGetDatum(num)));
     878             : 
     879         264 :     PG_RETURN_INT64(result);
     880             : }
     881             : 
     882             : /*
     883             :  * Get the filenode of a relation
     884             :  *
     885             :  * This is expected to be used in queries like
     886             :  *      SELECT pg_relation_filenode(oid) FROM pg_class;
     887             :  * That leads to a couple of choices.  We work from the pg_class row alone
     888             :  * rather than actually opening each relation, for efficiency.  We don't
     889             :  * fail if we can't find the relation --- some rows might be visible in
     890             :  * the query's MVCC snapshot even though the relations have been dropped.
     891             :  * (Note: we could avoid using the catcache, but there's little point
     892             :  * because the relation mapper also works "in the now".)  We also don't
     893             :  * fail if the relation doesn't have storage.  In all these cases it
     894             :  * seems better to quietly return NULL.
     895             :  */
     896             : Datum
     897       16360 : pg_relation_filenode(PG_FUNCTION_ARGS)
     898             : {
     899       16360 :     Oid         relid = PG_GETARG_OID(0);
     900             :     RelFileNumber result;
     901             :     HeapTuple   tuple;
     902             :     Form_pg_class relform;
     903             : 
     904       16360 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
     905       16360 :     if (!HeapTupleIsValid(tuple))
     906           0 :         PG_RETURN_NULL();
     907       16360 :     relform = (Form_pg_class) GETSTRUCT(tuple);
     908             : 
     909       16360 :     if (RELKIND_HAS_STORAGE(relform->relkind))
     910             :     {
     911       13500 :         if (relform->relfilenode)
     912       11810 :             result = relform->relfilenode;
     913             :         else                    /* Consult the relation mapper */
     914        1690 :             result = RelationMapOidToFilenumber(relid,
     915        1690 :                                                 relform->relisshared);
     916             :     }
     917             :     else
     918             :     {
     919             :         /* no storage, return NULL */
     920        2860 :         result = InvalidRelFileNumber;
     921             :     }
     922             : 
     923       16360 :     ReleaseSysCache(tuple);
     924             : 
     925       16360 :     if (!RelFileNumberIsValid(result))
     926        2860 :         PG_RETURN_NULL();
     927             : 
     928       13500 :     PG_RETURN_OID(result);
     929             : }
     930             : 
     931             : /*
     932             :  * Get the relation via (reltablespace, relfilenumber)
     933             :  *
     934             :  * This is expected to be used when somebody wants to match an individual file
     935             :  * on the filesystem back to its table. That's not trivially possible via
     936             :  * pg_class, because that doesn't contain the relfilenumbers of shared and nailed
     937             :  * tables.
     938             :  *
     939             :  * We don't fail but return NULL if we cannot find a mapping.
     940             :  *
     941             :  * InvalidOid can be passed instead of the current database's default
     942             :  * tablespace.
     943             :  */
     944             : Datum
     945        7998 : pg_filenode_relation(PG_FUNCTION_ARGS)
     946             : {
     947        7998 :     Oid         reltablespace = PG_GETARG_OID(0);
     948        7998 :     RelFileNumber relfilenumber = PG_GETARG_OID(1);
     949             :     Oid         heaprel;
     950             : 
     951             :     /* test needed so RelidByRelfilenumber doesn't misbehave */
     952        7998 :     if (!RelFileNumberIsValid(relfilenumber))
     953           0 :         PG_RETURN_NULL();
     954             : 
     955        7998 :     heaprel = RelidByRelfilenumber(reltablespace, relfilenumber);
     956             : 
     957        7998 :     if (!OidIsValid(heaprel))
     958           0 :         PG_RETURN_NULL();
     959             :     else
     960        7998 :         PG_RETURN_OID(heaprel);
     961             : }
     962             : 
     963             : /*
     964             :  * Get the pathname (relative to $PGDATA) of a relation
     965             :  *
     966             :  * See comments for pg_relation_filenode.
     967             :  */
     968             : Datum
     969        3018 : pg_relation_filepath(PG_FUNCTION_ARGS)
     970             : {
     971        3018 :     Oid         relid = PG_GETARG_OID(0);
     972             :     HeapTuple   tuple;
     973             :     Form_pg_class relform;
     974             :     RelFileLocator rlocator;
     975             :     ProcNumber  backend;
     976             :     char       *path;
     977             : 
     978        3018 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
     979        3018 :     if (!HeapTupleIsValid(tuple))
     980           2 :         PG_RETURN_NULL();
     981        3016 :     relform = (Form_pg_class) GETSTRUCT(tuple);
     982             : 
     983        3016 :     if (RELKIND_HAS_STORAGE(relform->relkind))
     984             :     {
     985             :         /* This logic should match RelationInitPhysicalAddr */
     986        2444 :         if (relform->reltablespace)
     987         216 :             rlocator.spcOid = relform->reltablespace;
     988             :         else
     989        2228 :             rlocator.spcOid = MyDatabaseTableSpace;
     990        2444 :         if (rlocator.spcOid == GLOBALTABLESPACE_OID)
     991         192 :             rlocator.dbOid = InvalidOid;
     992             :         else
     993        2252 :             rlocator.dbOid = MyDatabaseId;
     994        2444 :         if (relform->relfilenode)
     995        2116 :             rlocator.relNumber = relform->relfilenode;
     996             :         else                    /* Consult the relation mapper */
     997         328 :             rlocator.relNumber = RelationMapOidToFilenumber(relid,
     998         328 :                                                             relform->relisshared);
     999             :     }
    1000             :     else
    1001             :     {
    1002             :         /* no storage, return NULL */
    1003         572 :         rlocator.relNumber = InvalidRelFileNumber;
    1004             :         /* some compilers generate warnings without these next two lines */
    1005         572 :         rlocator.dbOid = InvalidOid;
    1006         572 :         rlocator.spcOid = InvalidOid;
    1007             :     }
    1008             : 
    1009        3016 :     if (!RelFileNumberIsValid(rlocator.relNumber))
    1010             :     {
    1011         572 :         ReleaseSysCache(tuple);
    1012         572 :         PG_RETURN_NULL();
    1013             :     }
    1014             : 
    1015             :     /* Determine owning backend. */
    1016        2444 :     switch (relform->relpersistence)
    1017             :     {
    1018        2444 :         case RELPERSISTENCE_UNLOGGED:
    1019             :         case RELPERSISTENCE_PERMANENT:
    1020        2444 :             backend = INVALID_PROC_NUMBER;
    1021        2444 :             break;
    1022           0 :         case RELPERSISTENCE_TEMP:
    1023           0 :             if (isTempOrTempToastNamespace(relform->relnamespace))
    1024           0 :                 backend = ProcNumberForTempRelations();
    1025             :             else
    1026             :             {
    1027             :                 /* Do it the hard way. */
    1028           0 :                 backend = GetTempNamespaceProcNumber(relform->relnamespace);
    1029             :                 Assert(backend != INVALID_PROC_NUMBER);
    1030             :             }
    1031           0 :             break;
    1032           0 :         default:
    1033           0 :             elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
    1034             :             backend = INVALID_PROC_NUMBER;  /* placate compiler */
    1035             :             break;
    1036             :     }
    1037             : 
    1038        2444 :     ReleaseSysCache(tuple);
    1039             : 
    1040        2444 :     path = relpathbackend(rlocator, backend, MAIN_FORKNUM);
    1041             : 
    1042        2444 :     PG_RETURN_TEXT_P(cstring_to_text(path));
    1043             : }

Generated by: LCOV version 1.14