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

Generated by: LCOV version 1.14