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

Generated by: LCOV version 1.14