LCOV - code coverage report
Current view: top level - src/backend/utils/adt - dbsize.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 199 353 56.4 %
Date: 2024-04-27 04:11:27 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");
     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), "pg_tblspc/%s/%s/%u",
     158           0 :                  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, "pg_tblspc/%u/%s", 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         490 : calculate_relation_size(RelFileLocator *rfn, ProcNumber backend, ForkNumber forknum)
     309             : {
     310         490 :     int64       totalsize = 0;
     311             :     char       *relationpath;
     312             :     char        pathname[MAXPGPATH];
     313         490 :     unsigned int segcount = 0;
     314             : 
     315         490 :     relationpath = relpathbackend(*rfn, backend, forknum);
     316             : 
     317         490 :     for (segcount = 0;; segcount++)
     318         256 :     {
     319             :         struct stat fst;
     320             : 
     321         746 :         CHECK_FOR_INTERRUPTS();
     322             : 
     323         746 :         if (segcount == 0)
     324         490 :             snprintf(pathname, MAXPGPATH, "%s",
     325             :                      relationpath);
     326             :         else
     327         256 :             snprintf(pathname, MAXPGPATH, "%s.%u",
     328             :                      relationpath, segcount);
     329             : 
     330         746 :         if (stat(pathname, &fst) < 0)
     331             :         {
     332         490 :             if (errno == ENOENT)
     333         490 :                 break;
     334             :             else
     335           0 :                 ereport(ERROR,
     336             :                         (errcode_for_file_access(),
     337             :                          errmsg("could not stat file \"%s\": %m", pathname)));
     338             :         }
     339         256 :         totalsize += fst.st_size;
     340             :     }
     341             : 
     342         490 :     return totalsize;
     343             : }
     344             : 
     345             : Datum
     346         204 : pg_relation_size(PG_FUNCTION_ARGS)
     347             : {
     348         204 :     Oid         relOid = PG_GETARG_OID(0);
     349         204 :     text       *forkName = PG_GETARG_TEXT_PP(1);
     350             :     Relation    rel;
     351             :     int64       size;
     352             : 
     353         204 :     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         204 :     if (rel == NULL)
     363           2 :         PG_RETURN_NULL();
     364             : 
     365         202 :     size = calculate_relation_size(&(rel->rd_locator), rel->rd_backend,
     366         202 :                                    forkname_to_number(text_to_cstring(forkName)));
     367             : 
     368         202 :     relation_close(rel, AccessShareLock);
     369             : 
     370         202 :     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         270 : pg_size_pretty(PG_FUNCTION_ARGS)
     570             : {
     571         270 :     int64       size = PG_GETARG_INT64(0);
     572             :     char        buf[64];
     573             :     const struct size_pretty_unit *unit;
     574             : 
     575         690 :     for (unit = size_pretty_units; unit->name != NULL; unit++)
     576             :     {
     577             :         uint8       bits;
     578             : 
     579             :         /* use this unit if there are no more units or we're below the limit */
     580         690 :         if (unit[1].name == NULL || i64abs(size) < unit->limit)
     581             :         {
     582         270 :             if (unit->round)
     583         156 :                 size = half_rounded(size);
     584             : 
     585         270 :             snprintf(buf, sizeof(buf), INT64_FORMAT " %s", size, unit->name);
     586         270 :             break;
     587             :         }
     588             : 
     589             :         /*
     590             :          * Determine the number of bits to use to build the divisor.  We may
     591             :          * need to use 1 bit less than the difference between this and the
     592             :          * next unit if the next unit uses half rounding.  Or we may need to
     593             :          * shift an extra bit if this unit uses half rounding and the next one
     594             :          * does not.  We use division rather than shifting right by this
     595             :          * number of bits to ensure positive and negative values are rounded
     596             :          * in the same way.
     597             :          */
     598         420 :         bits = (unit[1].unitbits - unit->unitbits - (unit[1].round == true)
     599         420 :                 + (unit->round == true));
     600         420 :         size /= ((int64) 1) << bits;
     601             :     }
     602             : 
     603         270 :     PG_RETURN_TEXT_P(cstring_to_text(buf));
     604             : }
     605             : 
     606             : static char *
     607         288 : numeric_to_cstring(Numeric n)
     608             : {
     609         288 :     Datum       d = NumericGetDatum(n);
     610             : 
     611         288 :     return DatumGetCString(DirectFunctionCall1(numeric_out, d));
     612             : }
     613             : 
     614             : static bool
     615         912 : numeric_is_less(Numeric a, Numeric b)
     616             : {
     617         912 :     Datum       da = NumericGetDatum(a);
     618         912 :     Datum       db = NumericGetDatum(b);
     619             : 
     620         912 :     return DatumGetBool(DirectFunctionCall2(numeric_lt, da, db));
     621             : }
     622             : 
     623             : static Numeric
     624         912 : numeric_absolute(Numeric n)
     625             : {
     626         912 :     Datum       d = NumericGetDatum(n);
     627             :     Datum       result;
     628             : 
     629         912 :     result = DirectFunctionCall1(numeric_abs, d);
     630         912 :     return DatumGetNumeric(result);
     631             : }
     632             : 
     633             : static Numeric
     634         228 : numeric_half_rounded(Numeric n)
     635             : {
     636         228 :     Datum       d = NumericGetDatum(n);
     637             :     Datum       zero;
     638             :     Datum       one;
     639             :     Datum       two;
     640             :     Datum       result;
     641             : 
     642         228 :     zero = NumericGetDatum(int64_to_numeric(0));
     643         228 :     one = NumericGetDatum(int64_to_numeric(1));
     644         228 :     two = NumericGetDatum(int64_to_numeric(2));
     645             : 
     646         228 :     if (DatumGetBool(DirectFunctionCall2(numeric_ge, d, zero)))
     647         114 :         d = DirectFunctionCall2(numeric_add, d, one);
     648             :     else
     649         114 :         d = DirectFunctionCall2(numeric_sub, d, one);
     650             : 
     651         228 :     result = DirectFunctionCall2(numeric_div_trunc, d, two);
     652         228 :     return DatumGetNumeric(result);
     653             : }
     654             : 
     655             : static Numeric
     656         660 : numeric_truncated_divide(Numeric n, int64 divisor)
     657             : {
     658         660 :     Datum       d = NumericGetDatum(n);
     659             :     Datum       divisor_numeric;
     660             :     Datum       result;
     661             : 
     662         660 :     divisor_numeric = NumericGetDatum(int64_to_numeric(divisor));
     663         660 :     result = DirectFunctionCall2(numeric_div_trunc, d, divisor_numeric);
     664         660 :     return DatumGetNumeric(result);
     665             : }
     666             : 
     667             : Datum
     668         288 : pg_size_pretty_numeric(PG_FUNCTION_ARGS)
     669             : {
     670         288 :     Numeric     size = PG_GETARG_NUMERIC(0);
     671         288 :     char       *result = NULL;
     672             :     const struct size_pretty_unit *unit;
     673             : 
     674         948 :     for (unit = size_pretty_units; unit->name != NULL; unit++)
     675             :     {
     676             :         unsigned int shiftby;
     677             : 
     678             :         /* use this unit if there are no more units or we're below the limit */
     679        1860 :         if (unit[1].name == NULL ||
     680         912 :             numeric_is_less(numeric_absolute(size),
     681         912 :                             int64_to_numeric(unit->limit)))
     682             :         {
     683         288 :             if (unit->round)
     684         228 :                 size = numeric_half_rounded(size);
     685             : 
     686         288 :             result = psprintf("%s %s", numeric_to_cstring(size), unit->name);
     687         288 :             break;
     688             :         }
     689             : 
     690             :         /*
     691             :          * Determine the number of bits to use to build the divisor.  We may
     692             :          * need to use 1 bit less than the difference between this and the
     693             :          * next unit if the next unit uses half rounding.  Or we may need to
     694             :          * shift an extra bit if this unit uses half rounding and the next one
     695             :          * does not.
     696             :          */
     697         660 :         shiftby = (unit[1].unitbits - unit->unitbits - (unit[1].round == true)
     698         660 :                    + (unit->round == true));
     699         660 :         size = numeric_truncated_divide(size, ((int64) 1) << shiftby);
     700             :     }
     701             : 
     702         288 :     PG_RETURN_TEXT_P(cstring_to_text(result));
     703             : }
     704             : 
     705             : /*
     706             :  * Convert a human-readable size to a size in bytes
     707             :  */
     708             : Datum
     709         360 : pg_size_bytes(PG_FUNCTION_ARGS)
     710             : {
     711         360 :     text       *arg = PG_GETARG_TEXT_PP(0);
     712             :     char       *str,
     713             :                *strptr,
     714             :                *endptr;
     715             :     char        saved_char;
     716             :     Numeric     num;
     717             :     int64       result;
     718         360 :     bool        have_digits = false;
     719             : 
     720         360 :     str = text_to_cstring(arg);
     721             : 
     722             :     /* Skip leading whitespace */
     723         360 :     strptr = str;
     724         378 :     while (isspace((unsigned char) *strptr))
     725          18 :         strptr++;
     726             : 
     727             :     /* Check that we have a valid number and determine where it ends */
     728         360 :     endptr = strptr;
     729             : 
     730             :     /* Part (1): sign */
     731         360 :     if (*endptr == '-' || *endptr == '+')
     732         138 :         endptr++;
     733             : 
     734             :     /* Part (2): main digit string */
     735         360 :     if (isdigit((unsigned char) *endptr))
     736             :     {
     737         288 :         have_digits = true;
     738             :         do
     739         594 :             endptr++;
     740         594 :         while (isdigit((unsigned char) *endptr));
     741             :     }
     742             : 
     743             :     /* Part (3): optional decimal point and fractional digits */
     744         360 :     if (*endptr == '.')
     745             :     {
     746         102 :         endptr++;
     747         102 :         if (isdigit((unsigned char) *endptr))
     748             :         {
     749          48 :             have_digits = true;
     750             :             do
     751          48 :                 endptr++;
     752          48 :             while (isdigit((unsigned char) *endptr));
     753             :         }
     754             :     }
     755             : 
     756             :     /* Complain if we don't have a valid number at this point */
     757         360 :     if (!have_digits)
     758          48 :         ereport(ERROR,
     759             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     760             :                  errmsg("invalid size: \"%s\"", str)));
     761             : 
     762             :     /* Part (4): optional exponent */
     763         312 :     if (*endptr == 'e' || *endptr == 'E')
     764             :     {
     765             :         long        exponent;
     766             :         char       *cp;
     767             : 
     768             :         /*
     769             :          * Note we might one day support EB units, so if what follows 'E'
     770             :          * isn't a number, just treat it all as a unit to be parsed.
     771             :          */
     772          30 :         exponent = strtol(endptr + 1, &cp, 10);
     773             :         (void) exponent;        /* Silence -Wunused-result warnings */
     774          30 :         if (cp > endptr + 1)
     775          30 :             endptr = cp;
     776             :     }
     777             : 
     778             :     /*
     779             :      * Parse the number, saving the next character, which may be the first
     780             :      * character of the unit string.
     781             :      */
     782         312 :     saved_char = *endptr;
     783         312 :     *endptr = '\0';
     784             : 
     785         312 :     num = DatumGetNumeric(DirectFunctionCall3(numeric_in,
     786             :                                               CStringGetDatum(strptr),
     787             :                                               ObjectIdGetDatum(InvalidOid),
     788             :                                               Int32GetDatum(-1)));
     789             : 
     790         306 :     *endptr = saved_char;
     791             : 
     792             :     /* Skip whitespace between number and unit */
     793         306 :     strptr = endptr;
     794         450 :     while (isspace((unsigned char) *strptr))
     795         144 :         strptr++;
     796             : 
     797             :     /* Handle possible unit */
     798         306 :     if (*strptr != '\0')
     799             :     {
     800             :         const struct size_pretty_unit *unit;
     801         264 :         int64       multiplier = 0;
     802             : 
     803             :         /* Trim any trailing whitespace */
     804         264 :         endptr = str + VARSIZE_ANY_EXHDR(arg) - 1;
     805             : 
     806         306 :         while (isspace((unsigned char) *endptr))
     807          42 :             endptr--;
     808             : 
     809         264 :         endptr++;
     810         264 :         *endptr = '\0';
     811             : 
     812        1002 :         for (unit = size_pretty_units; unit->name != NULL; unit++)
     813             :         {
     814             :             /* Parse the unit case-insensitively */
     815         966 :             if (pg_strcasecmp(strptr, unit->name) == 0)
     816         228 :                 break;
     817             :         }
     818             : 
     819             :         /* If not found, look in table of aliases */
     820         264 :         if (unit->name == NULL)
     821             :         {
     822          66 :             for (const struct size_bytes_unit_alias *a = size_bytes_aliases; a->alias != NULL; a++)
     823             :             {
     824          36 :                 if (pg_strcasecmp(strptr, a->alias) == 0)
     825             :                 {
     826           6 :                     unit = &size_pretty_units[a->unit_index];
     827           6 :                     break;
     828             :                 }
     829             :             }
     830             :         }
     831             : 
     832             :         /* Verify we found a valid unit in the loop above */
     833         264 :         if (unit->name == NULL)
     834          30 :             ereport(ERROR,
     835             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     836             :                      errmsg("invalid size: \"%s\"", text_to_cstring(arg)),
     837             :                      errdetail("Invalid size unit: \"%s\".", strptr),
     838             :                      errhint("Valid units are \"bytes\", \"B\", \"kB\", \"MB\", \"GB\", \"TB\", and \"PB\".")));
     839             : 
     840         234 :         multiplier = ((int64) 1) << unit->unitbits;
     841             : 
     842         234 :         if (multiplier > 1)
     843             :         {
     844             :             Numeric     mul_num;
     845             : 
     846         210 :             mul_num = int64_to_numeric(multiplier);
     847             : 
     848         210 :             num = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
     849             :                                                       NumericGetDatum(mul_num),
     850             :                                                       NumericGetDatum(num)));
     851             :         }
     852             :     }
     853             : 
     854         276 :     result = DatumGetInt64(DirectFunctionCall1(numeric_int8,
     855             :                                                NumericGetDatum(num)));
     856             : 
     857         264 :     PG_RETURN_INT64(result);
     858             : }
     859             : 
     860             : /*
     861             :  * Get the filenode of a relation
     862             :  *
     863             :  * This is expected to be used in queries like
     864             :  *      SELECT pg_relation_filenode(oid) FROM pg_class;
     865             :  * That leads to a couple of choices.  We work from the pg_class row alone
     866             :  * rather than actually opening each relation, for efficiency.  We don't
     867             :  * fail if we can't find the relation --- some rows might be visible in
     868             :  * the query's MVCC snapshot even though the relations have been dropped.
     869             :  * (Note: we could avoid using the catcache, but there's little point
     870             :  * because the relation mapper also works "in the now".)  We also don't
     871             :  * fail if the relation doesn't have storage.  In all these cases it
     872             :  * seems better to quietly return NULL.
     873             :  */
     874             : Datum
     875       16456 : pg_relation_filenode(PG_FUNCTION_ARGS)
     876             : {
     877       16456 :     Oid         relid = PG_GETARG_OID(0);
     878             :     RelFileNumber result;
     879             :     HeapTuple   tuple;
     880             :     Form_pg_class relform;
     881             : 
     882       16456 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
     883       16456 :     if (!HeapTupleIsValid(tuple))
     884           0 :         PG_RETURN_NULL();
     885       16456 :     relform = (Form_pg_class) GETSTRUCT(tuple);
     886             : 
     887       16456 :     if (RELKIND_HAS_STORAGE(relform->relkind))
     888             :     {
     889       13596 :         if (relform->relfilenode)
     890       11854 :             result = relform->relfilenode;
     891             :         else                    /* Consult the relation mapper */
     892        1742 :             result = RelationMapOidToFilenumber(relid,
     893        1742 :                                                 relform->relisshared);
     894             :     }
     895             :     else
     896             :     {
     897             :         /* no storage, return NULL */
     898        2860 :         result = InvalidRelFileNumber;
     899             :     }
     900             : 
     901       16456 :     ReleaseSysCache(tuple);
     902             : 
     903       16456 :     if (!RelFileNumberIsValid(result))
     904        2860 :         PG_RETURN_NULL();
     905             : 
     906       13596 :     PG_RETURN_OID(result);
     907             : }
     908             : 
     909             : /*
     910             :  * Get the relation via (reltablespace, relfilenumber)
     911             :  *
     912             :  * This is expected to be used when somebody wants to match an individual file
     913             :  * on the filesystem back to its table. That's not trivially possible via
     914             :  * pg_class, because that doesn't contain the relfilenumbers of shared and nailed
     915             :  * tables.
     916             :  *
     917             :  * We don't fail but return NULL if we cannot find a mapping.
     918             :  *
     919             :  * InvalidOid can be passed instead of the current database's default
     920             :  * tablespace.
     921             :  */
     922             : Datum
     923        8094 : pg_filenode_relation(PG_FUNCTION_ARGS)
     924             : {
     925        8094 :     Oid         reltablespace = PG_GETARG_OID(0);
     926        8094 :     RelFileNumber relfilenumber = PG_GETARG_OID(1);
     927             :     Oid         heaprel;
     928             : 
     929             :     /* test needed so RelidByRelfilenumber doesn't misbehave */
     930        8094 :     if (!RelFileNumberIsValid(relfilenumber))
     931           0 :         PG_RETURN_NULL();
     932             : 
     933        8094 :     heaprel = RelidByRelfilenumber(reltablespace, relfilenumber);
     934             : 
     935        8094 :     if (!OidIsValid(heaprel))
     936           0 :         PG_RETURN_NULL();
     937             :     else
     938        8094 :         PG_RETURN_OID(heaprel);
     939             : }
     940             : 
     941             : /*
     942             :  * Get the pathname (relative to $PGDATA) of a relation
     943             :  *
     944             :  * See comments for pg_relation_filenode.
     945             :  */
     946             : Datum
     947        3008 : pg_relation_filepath(PG_FUNCTION_ARGS)
     948             : {
     949        3008 :     Oid         relid = PG_GETARG_OID(0);
     950             :     HeapTuple   tuple;
     951             :     Form_pg_class relform;
     952             :     RelFileLocator rlocator;
     953             :     ProcNumber  backend;
     954             :     char       *path;
     955             : 
     956        3008 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
     957        3008 :     if (!HeapTupleIsValid(tuple))
     958           2 :         PG_RETURN_NULL();
     959        3006 :     relform = (Form_pg_class) GETSTRUCT(tuple);
     960             : 
     961        3006 :     if (RELKIND_HAS_STORAGE(relform->relkind))
     962             :     {
     963             :         /* This logic should match RelationInitPhysicalAddr */
     964        2434 :         if (relform->reltablespace)
     965         224 :             rlocator.spcOid = relform->reltablespace;
     966             :         else
     967        2210 :             rlocator.spcOid = MyDatabaseTableSpace;
     968        2434 :         if (rlocator.spcOid == GLOBALTABLESPACE_OID)
     969         200 :             rlocator.dbOid = InvalidOid;
     970             :         else
     971        2234 :             rlocator.dbOid = MyDatabaseId;
     972        2434 :         if (relform->relfilenode)
     973        2098 :             rlocator.relNumber = relform->relfilenode;
     974             :         else                    /* Consult the relation mapper */
     975         336 :             rlocator.relNumber = RelationMapOidToFilenumber(relid,
     976         336 :                                                             relform->relisshared);
     977             :     }
     978             :     else
     979             :     {
     980             :         /* no storage, return NULL */
     981         572 :         rlocator.relNumber = InvalidRelFileNumber;
     982             :         /* some compilers generate warnings without these next two lines */
     983         572 :         rlocator.dbOid = InvalidOid;
     984         572 :         rlocator.spcOid = InvalidOid;
     985             :     }
     986             : 
     987        3006 :     if (!RelFileNumberIsValid(rlocator.relNumber))
     988             :     {
     989         572 :         ReleaseSysCache(tuple);
     990         572 :         PG_RETURN_NULL();
     991             :     }
     992             : 
     993             :     /* Determine owning backend. */
     994        2434 :     switch (relform->relpersistence)
     995             :     {
     996        2434 :         case RELPERSISTENCE_UNLOGGED:
     997             :         case RELPERSISTENCE_PERMANENT:
     998        2434 :             backend = INVALID_PROC_NUMBER;
     999        2434 :             break;
    1000           0 :         case RELPERSISTENCE_TEMP:
    1001           0 :             if (isTempOrTempToastNamespace(relform->relnamespace))
    1002           0 :                 backend = ProcNumberForTempRelations();
    1003             :             else
    1004             :             {
    1005             :                 /* Do it the hard way. */
    1006           0 :                 backend = GetTempNamespaceProcNumber(relform->relnamespace);
    1007             :                 Assert(backend != INVALID_PROC_NUMBER);
    1008             :             }
    1009           0 :             break;
    1010           0 :         default:
    1011           0 :             elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
    1012             :             backend = INVALID_PROC_NUMBER;  /* placate compiler */
    1013             :             break;
    1014             :     }
    1015             : 
    1016        2434 :     ReleaseSysCache(tuple);
    1017             : 
    1018        2434 :     path = relpathbackend(rlocator, backend, MAIN_FORKNUM);
    1019             : 
    1020        2434 :     PG_RETURN_TEXT_P(cstring_to_text(path));
    1021             : }

Generated by: LCOV version 1.14