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

Generated by: LCOV version 1.14