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

Generated by: LCOV version 2.0-1