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