Line data Source code
1 : /*
2 : * dbsize.c
3 : * Database object size functions, and related inquiries
4 : *
5 : * Copyright (c) 2002-2025, PostgreSQL Global Development Group
6 : *
7 : * IDENTIFICATION
8 : * src/backend/utils/adt/dbsize.c
9 : *
10 : */
11 :
12 : #include "postgres.h"
13 :
14 : #include <sys/stat.h>
15 :
16 : #include "access/htup_details.h"
17 : #include "access/relation.h"
18 : #include "catalog/namespace.h"
19 : #include "catalog/pg_authid.h"
20 : #include "catalog/pg_database.h"
21 : #include "catalog/pg_tablespace.h"
22 : #include "commands/dbcommands.h"
23 : #include "commands/tablespace.h"
24 : #include "miscadmin.h"
25 : #include "storage/fd.h"
26 : #include "utils/acl.h"
27 : #include "utils/builtins.h"
28 : #include "utils/numeric.h"
29 : #include "utils/rel.h"
30 : #include "utils/relfilenumbermap.h"
31 : #include "utils/relmapper.h"
32 : #include "utils/syscache.h"
33 :
34 : /* Divide by two and round away from zero */
35 : #define half_rounded(x) (((x) + ((x) < 0 ? -1 : 1)) / 2)
36 :
37 : /* Units used in pg_size_pretty functions. All units must be powers of 2 */
38 : struct size_pretty_unit
39 : {
40 : const char *name; /* bytes, kB, MB, GB etc */
41 : uint32 limit; /* upper limit, prior to half rounding after
42 : * converting to this unit. */
43 : bool round; /* do half rounding for this unit */
44 : uint8 unitbits; /* (1 << unitbits) bytes to make 1 of this
45 : * unit */
46 : };
47 :
48 : /* When adding units here also update the docs and the error message in pg_size_bytes */
49 : static const struct size_pretty_unit size_pretty_units[] = {
50 : {"bytes", 10 * 1024, false, 0},
51 : {"kB", 20 * 1024 - 1, true, 10},
52 : {"MB", 20 * 1024 - 1, true, 20},
53 : {"GB", 20 * 1024 - 1, true, 30},
54 : {"TB", 20 * 1024 - 1, true, 40},
55 : {"PB", 20 * 1024 - 1, true, 50},
56 : {NULL, 0, false, 0}
57 : };
58 :
59 : /* Additional unit aliases accepted by pg_size_bytes */
60 : struct size_bytes_unit_alias
61 : {
62 : const char *alias;
63 : int unit_index; /* corresponding size_pretty_units element */
64 : };
65 :
66 : /* When adding units here also update the docs and the error message in pg_size_bytes */
67 : static const struct size_bytes_unit_alias size_bytes_aliases[] = {
68 : {"B", 0},
69 : {NULL}
70 : };
71 :
72 : /* Return physical size of directory contents, or 0 if dir doesn't exist */
73 : static int64
74 0 : db_dir_size(const char *path)
75 : {
76 0 : int64 dirsize = 0;
77 : struct dirent *direntry;
78 : DIR *dirdesc;
79 : char filename[MAXPGPATH * 2];
80 :
81 0 : dirdesc = AllocateDir(path);
82 :
83 0 : if (!dirdesc)
84 0 : return 0;
85 :
86 0 : while ((direntry = ReadDir(dirdesc, path)) != NULL)
87 : {
88 : struct stat fst;
89 :
90 0 : CHECK_FOR_INTERRUPTS();
91 :
92 0 : if (strcmp(direntry->d_name, ".") == 0 ||
93 0 : strcmp(direntry->d_name, "..") == 0)
94 0 : continue;
95 :
96 0 : snprintf(filename, sizeof(filename), "%s/%s", path, direntry->d_name);
97 :
98 0 : if (stat(filename, &fst) < 0)
99 : {
100 0 : if (errno == ENOENT)
101 0 : continue;
102 : else
103 0 : ereport(ERROR,
104 : (errcode_for_file_access(),
105 : errmsg("could not stat file \"%s\": %m", filename)));
106 : }
107 0 : dirsize += fst.st_size;
108 : }
109 :
110 0 : FreeDir(dirdesc);
111 0 : return dirsize;
112 : }
113 :
114 : /*
115 : * calculate size of database in all tablespaces
116 : */
117 : static int64
118 0 : calculate_database_size(Oid dbOid)
119 : {
120 : int64 totalsize;
121 : DIR *dirdesc;
122 : struct dirent *direntry;
123 : char dirpath[MAXPGPATH];
124 : char pathname[MAXPGPATH + 21 + sizeof(TABLESPACE_VERSION_DIRECTORY)];
125 : AclResult aclresult;
126 :
127 : /*
128 : * User must have connect privilege for target database or have privileges
129 : * of pg_read_all_stats
130 : */
131 0 : aclresult = object_aclcheck(DatabaseRelationId, dbOid, GetUserId(), ACL_CONNECT);
132 0 : if (aclresult != ACLCHECK_OK &&
133 0 : !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
134 : {
135 0 : aclcheck_error(aclresult, OBJECT_DATABASE,
136 0 : get_database_name(dbOid));
137 : }
138 :
139 : /* Shared storage in pg_global is not counted */
140 :
141 : /* Include pg_default storage */
142 0 : snprintf(pathname, sizeof(pathname), "base/%u", dbOid);
143 0 : totalsize = db_dir_size(pathname);
144 :
145 : /* Scan the non-default tablespaces */
146 0 : snprintf(dirpath, MAXPGPATH, PG_TBLSPC_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 590 : calculate_relation_size(RelFileLocator *rfn, ProcNumber backend, ForkNumber forknum)
327 : {
328 590 : int64 totalsize = 0;
329 : char *relationpath;
330 : char pathname[MAXPGPATH];
331 590 : unsigned int segcount = 0;
332 :
333 590 : relationpath = relpathbackend(*rfn, backend, forknum);
334 :
335 590 : for (segcount = 0;; segcount++)
336 278 : {
337 : struct stat fst;
338 :
339 868 : CHECK_FOR_INTERRUPTS();
340 :
341 868 : if (segcount == 0)
342 590 : snprintf(pathname, MAXPGPATH, "%s",
343 : relationpath);
344 : else
345 278 : snprintf(pathname, MAXPGPATH, "%s.%u",
346 : relationpath, segcount);
347 :
348 868 : if (stat(pathname, &fst) < 0)
349 : {
350 590 : if (errno == ENOENT)
351 590 : break;
352 : else
353 0 : ereport(ERROR,
354 : (errcode_for_file_access(),
355 : errmsg("could not stat file \"%s\": %m", pathname)));
356 : }
357 278 : totalsize += fst.st_size;
358 : }
359 :
360 590 : return totalsize;
361 : }
362 :
363 : Datum
364 208 : pg_relation_size(PG_FUNCTION_ARGS)
365 : {
366 208 : Oid relOid = PG_GETARG_OID(0);
367 208 : text *forkName = PG_GETARG_TEXT_PP(1);
368 : Relation rel;
369 : int64 size;
370 :
371 208 : 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 208 : if (rel == NULL)
381 2 : PG_RETURN_NULL();
382 :
383 206 : size = calculate_relation_size(&(rel->rd_locator), rel->rd_backend,
384 206 : forkname_to_number(text_to_cstring(forkName)));
385 :
386 206 : relation_close(rel, AccessShareLock);
387 :
388 206 : 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 96 : calculate_table_size(Relation rel)
443 : {
444 96 : int64 size = 0;
445 : ForkNumber forkNum;
446 :
447 : /*
448 : * heap size, including FSM and VM
449 : */
450 480 : for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
451 384 : size += calculate_relation_size(&(rel->rd_locator), rel->rd_backend,
452 : forkNum);
453 :
454 : /*
455 : * Size of toast relation
456 : */
457 96 : if (OidIsValid(rel->rd_rel->reltoastrelid))
458 0 : size += calculate_toast_table_size(rel->rd_rel->reltoastrelid);
459 :
460 96 : 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 96 : pg_table_size(PG_FUNCTION_ARGS)
505 : {
506 96 : Oid relOid = PG_GETARG_OID(0);
507 : Relation rel;
508 : int64 size;
509 :
510 96 : rel = try_relation_open(relOid, AccessShareLock);
511 :
512 96 : if (rel == NULL)
513 0 : PG_RETURN_NULL();
514 :
515 96 : size = calculate_table_size(rel);
516 :
517 96 : relation_close(rel, AccessShareLock);
518 :
519 96 : 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 306 : pg_size_pretty(PG_FUNCTION_ARGS)
588 : {
589 306 : int64 size = PG_GETARG_INT64(0);
590 : char buf[64];
591 : const struct size_pretty_unit *unit;
592 :
593 786 : for (unit = size_pretty_units; unit->name != NULL; unit++)
594 : {
595 : uint8 bits;
596 786 : 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 786 : if (unit[1].name == NULL || abs_size < unit->limit)
603 : {
604 306 : if (unit->round)
605 168 : size = half_rounded(size);
606 :
607 306 : snprintf(buf, sizeof(buf), INT64_FORMAT " %s", size, unit->name);
608 306 : 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 480 : bits = (unit[1].unitbits - unit->unitbits - (unit[1].round == true)
621 480 : + (unit->round == true));
622 480 : size /= ((int64) 1) << bits;
623 : }
624 :
625 306 : PG_RETURN_TEXT_P(cstring_to_text(buf));
626 : }
627 :
628 : static char *
629 288 : numeric_to_cstring(Numeric n)
630 : {
631 288 : Datum d = NumericGetDatum(n);
632 :
633 288 : return DatumGetCString(DirectFunctionCall1(numeric_out, d));
634 : }
635 :
636 : static bool
637 912 : numeric_is_less(Numeric a, Numeric b)
638 : {
639 912 : Datum da = NumericGetDatum(a);
640 912 : Datum db = NumericGetDatum(b);
641 :
642 912 : return DatumGetBool(DirectFunctionCall2(numeric_lt, da, db));
643 : }
644 :
645 : static Numeric
646 912 : numeric_absolute(Numeric n)
647 : {
648 912 : Datum d = NumericGetDatum(n);
649 : Datum result;
650 :
651 912 : result = DirectFunctionCall1(numeric_abs, d);
652 912 : return DatumGetNumeric(result);
653 : }
654 :
655 : static Numeric
656 228 : numeric_half_rounded(Numeric n)
657 : {
658 228 : Datum d = NumericGetDatum(n);
659 : Datum zero;
660 : Datum one;
661 : Datum two;
662 : Datum result;
663 :
664 228 : zero = NumericGetDatum(int64_to_numeric(0));
665 228 : one = NumericGetDatum(int64_to_numeric(1));
666 228 : two = NumericGetDatum(int64_to_numeric(2));
667 :
668 228 : if (DatumGetBool(DirectFunctionCall2(numeric_ge, d, zero)))
669 114 : d = DirectFunctionCall2(numeric_add, d, one);
670 : else
671 114 : d = DirectFunctionCall2(numeric_sub, d, one);
672 :
673 228 : result = DirectFunctionCall2(numeric_div_trunc, d, two);
674 228 : return DatumGetNumeric(result);
675 : }
676 :
677 : static Numeric
678 660 : numeric_truncated_divide(Numeric n, int64 divisor)
679 : {
680 660 : Datum d = NumericGetDatum(n);
681 : Datum divisor_numeric;
682 : Datum result;
683 :
684 660 : divisor_numeric = NumericGetDatum(int64_to_numeric(divisor));
685 660 : result = DirectFunctionCall2(numeric_div_trunc, d, divisor_numeric);
686 660 : return DatumGetNumeric(result);
687 : }
688 :
689 : Datum
690 288 : pg_size_pretty_numeric(PG_FUNCTION_ARGS)
691 : {
692 288 : Numeric size = PG_GETARG_NUMERIC(0);
693 288 : char *result = NULL;
694 : const struct size_pretty_unit *unit;
695 :
696 948 : 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 1860 : if (unit[1].name == NULL ||
702 912 : numeric_is_less(numeric_absolute(size),
703 912 : int64_to_numeric(unit->limit)))
704 : {
705 288 : if (unit->round)
706 228 : size = numeric_half_rounded(size);
707 :
708 288 : result = psprintf("%s %s", numeric_to_cstring(size), unit->name);
709 288 : 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 660 : shiftby = (unit[1].unitbits - unit->unitbits - (unit[1].round == true)
720 660 : + (unit->round == true));
721 660 : size = numeric_truncated_divide(size, ((int64) 1) << shiftby);
722 : }
723 :
724 288 : 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 360 : pg_size_bytes(PG_FUNCTION_ARGS)
732 : {
733 360 : 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 360 : bool have_digits = false;
741 :
742 360 : str = text_to_cstring(arg);
743 :
744 : /* Skip leading whitespace */
745 360 : strptr = str;
746 378 : while (isspace((unsigned char) *strptr))
747 18 : strptr++;
748 :
749 : /* Check that we have a valid number and determine where it ends */
750 360 : endptr = strptr;
751 :
752 : /* Part (1): sign */
753 360 : if (*endptr == '-' || *endptr == '+')
754 138 : endptr++;
755 :
756 : /* Part (2): main digit string */
757 360 : if (isdigit((unsigned char) *endptr))
758 : {
759 288 : have_digits = true;
760 : do
761 594 : endptr++;
762 594 : while (isdigit((unsigned char) *endptr));
763 : }
764 :
765 : /* Part (3): optional decimal point and fractional digits */
766 360 : if (*endptr == '.')
767 : {
768 102 : endptr++;
769 102 : if (isdigit((unsigned char) *endptr))
770 : {
771 48 : have_digits = true;
772 : do
773 48 : endptr++;
774 48 : while (isdigit((unsigned char) *endptr));
775 : }
776 : }
777 :
778 : /* Complain if we don't have a valid number at this point */
779 360 : if (!have_digits)
780 48 : ereport(ERROR,
781 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
782 : errmsg("invalid size: \"%s\"", str)));
783 :
784 : /* Part (4): optional exponent */
785 312 : 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 30 : exponent = strtol(endptr + 1, &cp, 10);
795 : (void) exponent; /* Silence -Wunused-result warnings */
796 30 : if (cp > endptr + 1)
797 30 : 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 312 : saved_char = *endptr;
805 312 : *endptr = '\0';
806 :
807 312 : num = DatumGetNumeric(DirectFunctionCall3(numeric_in,
808 : CStringGetDatum(strptr),
809 : ObjectIdGetDatum(InvalidOid),
810 : Int32GetDatum(-1)));
811 :
812 306 : *endptr = saved_char;
813 :
814 : /* Skip whitespace between number and unit */
815 306 : strptr = endptr;
816 450 : while (isspace((unsigned char) *strptr))
817 144 : strptr++;
818 :
819 : /* Handle possible unit */
820 306 : if (*strptr != '\0')
821 : {
822 : const struct size_pretty_unit *unit;
823 264 : int64 multiplier = 0;
824 :
825 : /* Trim any trailing whitespace */
826 264 : endptr = str + VARSIZE_ANY_EXHDR(arg) - 1;
827 :
828 306 : while (isspace((unsigned char) *endptr))
829 42 : endptr--;
830 :
831 264 : endptr++;
832 264 : *endptr = '\0';
833 :
834 1002 : for (unit = size_pretty_units; unit->name != NULL; unit++)
835 : {
836 : /* Parse the unit case-insensitively */
837 966 : if (pg_strcasecmp(strptr, unit->name) == 0)
838 228 : break;
839 : }
840 :
841 : /* If not found, look in table of aliases */
842 264 : if (unit->name == NULL)
843 : {
844 66 : for (const struct size_bytes_unit_alias *a = size_bytes_aliases; a->alias != NULL; a++)
845 : {
846 36 : if (pg_strcasecmp(strptr, a->alias) == 0)
847 : {
848 6 : unit = &size_pretty_units[a->unit_index];
849 6 : break;
850 : }
851 : }
852 : }
853 :
854 : /* Verify we found a valid unit in the loop above */
855 264 : if (unit->name == NULL)
856 30 : 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 234 : multiplier = ((int64) 1) << unit->unitbits;
863 :
864 234 : if (multiplier > 1)
865 : {
866 : Numeric mul_num;
867 :
868 210 : mul_num = int64_to_numeric(multiplier);
869 :
870 210 : num = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
871 : NumericGetDatum(mul_num),
872 : NumericGetDatum(num)));
873 : }
874 : }
875 :
876 276 : result = DatumGetInt64(DirectFunctionCall1(numeric_int8,
877 : NumericGetDatum(num)));
878 :
879 264 : 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 16360 : pg_relation_filenode(PG_FUNCTION_ARGS)
898 : {
899 16360 : Oid relid = PG_GETARG_OID(0);
900 : RelFileNumber result;
901 : HeapTuple tuple;
902 : Form_pg_class relform;
903 :
904 16360 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
905 16360 : if (!HeapTupleIsValid(tuple))
906 0 : PG_RETURN_NULL();
907 16360 : relform = (Form_pg_class) GETSTRUCT(tuple);
908 :
909 16360 : if (RELKIND_HAS_STORAGE(relform->relkind))
910 : {
911 13500 : if (relform->relfilenode)
912 11810 : result = relform->relfilenode;
913 : else /* Consult the relation mapper */
914 1690 : result = RelationMapOidToFilenumber(relid,
915 1690 : relform->relisshared);
916 : }
917 : else
918 : {
919 : /* no storage, return NULL */
920 2860 : result = InvalidRelFileNumber;
921 : }
922 :
923 16360 : ReleaseSysCache(tuple);
924 :
925 16360 : if (!RelFileNumberIsValid(result))
926 2860 : PG_RETURN_NULL();
927 :
928 13500 : 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 : * InvalidOid can be passed instead of the current database's default
942 : * tablespace.
943 : */
944 : Datum
945 7998 : pg_filenode_relation(PG_FUNCTION_ARGS)
946 : {
947 7998 : Oid reltablespace = PG_GETARG_OID(0);
948 7998 : RelFileNumber relfilenumber = PG_GETARG_OID(1);
949 : Oid heaprel;
950 :
951 : /* test needed so RelidByRelfilenumber doesn't misbehave */
952 7998 : if (!RelFileNumberIsValid(relfilenumber))
953 0 : PG_RETURN_NULL();
954 :
955 7998 : heaprel = RelidByRelfilenumber(reltablespace, relfilenumber);
956 :
957 7998 : if (!OidIsValid(heaprel))
958 0 : PG_RETURN_NULL();
959 : else
960 7998 : PG_RETURN_OID(heaprel);
961 : }
962 :
963 : /*
964 : * Get the pathname (relative to $PGDATA) of a relation
965 : *
966 : * See comments for pg_relation_filenode.
967 : */
968 : Datum
969 3018 : pg_relation_filepath(PG_FUNCTION_ARGS)
970 : {
971 3018 : Oid relid = PG_GETARG_OID(0);
972 : HeapTuple tuple;
973 : Form_pg_class relform;
974 : RelFileLocator rlocator;
975 : ProcNumber backend;
976 : char *path;
977 :
978 3018 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
979 3018 : if (!HeapTupleIsValid(tuple))
980 2 : PG_RETURN_NULL();
981 3016 : relform = (Form_pg_class) GETSTRUCT(tuple);
982 :
983 3016 : if (RELKIND_HAS_STORAGE(relform->relkind))
984 : {
985 : /* This logic should match RelationInitPhysicalAddr */
986 2444 : if (relform->reltablespace)
987 216 : rlocator.spcOid = relform->reltablespace;
988 : else
989 2228 : rlocator.spcOid = MyDatabaseTableSpace;
990 2444 : if (rlocator.spcOid == GLOBALTABLESPACE_OID)
991 192 : rlocator.dbOid = InvalidOid;
992 : else
993 2252 : rlocator.dbOid = MyDatabaseId;
994 2444 : if (relform->relfilenode)
995 2116 : rlocator.relNumber = relform->relfilenode;
996 : else /* Consult the relation mapper */
997 328 : rlocator.relNumber = RelationMapOidToFilenumber(relid,
998 328 : relform->relisshared);
999 : }
1000 : else
1001 : {
1002 : /* no storage, return NULL */
1003 572 : rlocator.relNumber = InvalidRelFileNumber;
1004 : /* some compilers generate warnings without these next two lines */
1005 572 : rlocator.dbOid = InvalidOid;
1006 572 : rlocator.spcOid = InvalidOid;
1007 : }
1008 :
1009 3016 : if (!RelFileNumberIsValid(rlocator.relNumber))
1010 : {
1011 572 : ReleaseSysCache(tuple);
1012 572 : PG_RETURN_NULL();
1013 : }
1014 :
1015 : /* Determine owning backend. */
1016 2444 : switch (relform->relpersistence)
1017 : {
1018 2444 : case RELPERSISTENCE_UNLOGGED:
1019 : case RELPERSISTENCE_PERMANENT:
1020 2444 : backend = INVALID_PROC_NUMBER;
1021 2444 : break;
1022 0 : case RELPERSISTENCE_TEMP:
1023 0 : if (isTempOrTempToastNamespace(relform->relnamespace))
1024 0 : backend = ProcNumberForTempRelations();
1025 : else
1026 : {
1027 : /* Do it the hard way. */
1028 0 : backend = GetTempNamespaceProcNumber(relform->relnamespace);
1029 : Assert(backend != INVALID_PROC_NUMBER);
1030 : }
1031 0 : break;
1032 0 : default:
1033 0 : elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
1034 : backend = INVALID_PROC_NUMBER; /* placate compiler */
1035 : break;
1036 : }
1037 :
1038 2444 : ReleaseSysCache(tuple);
1039 :
1040 2444 : path = relpathbackend(rlocator, backend, MAIN_FORKNUM);
1041 :
1042 2444 : PG_RETURN_TEXT_P(cstring_to_text(path));
1043 : }
|