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
75 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)
85 0 : return 0;
86 :
87 0 : while ((direntry = ReadDir(dirdesc, path)) != NULL)
88 : {
89 : struct stat fst;
90 :
91 0 : CHECK_FOR_INTERRUPTS();
92 :
93 0 : if (strcmp(direntry->d_name, ".") == 0 ||
94 0 : strcmp(direntry->d_name, "..") == 0)
95 0 : continue;
96 :
97 0 : snprintf(filename, sizeof(filename), "%s/%s", path, direntry->d_name);
98 :
99 0 : if (stat(filename, &fst) < 0)
100 : {
101 0 : if (errno == ENOENT)
102 0 : continue;
103 : else
104 0 : ereport(ERROR,
105 : (errcode_for_file_access(),
106 : errmsg("could not stat file \"%s\": %m", filename)));
107 : }
108 0 : dirsize += fst.st_size;
109 : }
110 :
111 0 : FreeDir(dirdesc);
112 0 : return dirsize;
113 : }
114 :
115 : /*
116 : * calculate size of database in all tablespaces
117 : */
118 : static int64
119 0 : calculate_database_size(Oid dbOid)
120 : {
121 : int64 totalsize;
122 : DIR *dirdesc;
123 : struct dirent *direntry;
124 : char dirpath[MAXPGPATH];
125 : char pathname[MAXPGPATH + 21 + sizeof(TABLESPACE_VERSION_DIRECTORY)];
126 : AclResult aclresult;
127 :
128 : /*
129 : * User must have connect privilege for target database or have privileges
130 : * of pg_read_all_stats
131 : */
132 0 : aclresult = object_aclcheck(DatabaseRelationId, dbOid, GetUserId(), ACL_CONNECT);
133 0 : if (aclresult != ACLCHECK_OK &&
134 0 : !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
135 : {
136 0 : aclcheck_error(aclresult, OBJECT_DATABASE,
137 0 : get_database_name(dbOid));
138 : }
139 :
140 : /* Shared storage in pg_global is not counted */
141 :
142 : /* Include pg_default storage */
143 0 : snprintf(pathname, sizeof(pathname), "base/%u", dbOid);
144 0 : totalsize = db_dir_size(pathname);
145 :
146 : /* Scan the non-default tablespaces */
147 0 : snprintf(dirpath, MAXPGPATH, "pg_tblspc");
148 0 : dirdesc = AllocateDir(dirpath);
149 :
150 0 : while ((direntry = ReadDir(dirdesc, dirpath)) != NULL)
151 : {
152 0 : CHECK_FOR_INTERRUPTS();
153 :
154 0 : if (strcmp(direntry->d_name, ".") == 0 ||
155 0 : strcmp(direntry->d_name, "..") == 0)
156 0 : continue;
157 :
158 0 : snprintf(pathname, sizeof(pathname), "pg_tblspc/%s/%s/%u",
159 0 : direntry->d_name, TABLESPACE_VERSION_DIRECTORY, dbOid);
160 0 : totalsize += db_dir_size(pathname);
161 : }
162 :
163 0 : FreeDir(dirdesc);
164 :
165 0 : return totalsize;
166 : }
167 :
168 : Datum
169 0 : pg_database_size_oid(PG_FUNCTION_ARGS)
170 : {
171 0 : Oid dbOid = PG_GETARG_OID(0);
172 : int64 size;
173 :
174 0 : size = calculate_database_size(dbOid);
175 :
176 0 : if (size == 0)
177 0 : PG_RETURN_NULL();
178 :
179 0 : PG_RETURN_INT64(size);
180 : }
181 :
182 : Datum
183 0 : pg_database_size_name(PG_FUNCTION_ARGS)
184 : {
185 0 : Name dbName = PG_GETARG_NAME(0);
186 0 : Oid dbOid = get_database_oid(NameStr(*dbName), false);
187 : int64 size;
188 :
189 0 : size = calculate_database_size(dbOid);
190 :
191 0 : if (size == 0)
192 0 : PG_RETURN_NULL();
193 :
194 0 : PG_RETURN_INT64(size);
195 : }
196 :
197 :
198 : /*
199 : * Calculate total size of tablespace. Returns -1 if the tablespace directory
200 : * cannot be found.
201 : */
202 : static int64
203 0 : calculate_tablespace_size(Oid tblspcOid)
204 : {
205 : char tblspcPath[MAXPGPATH];
206 : char pathname[MAXPGPATH * 2];
207 0 : int64 totalsize = 0;
208 : 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 : */
217 0 : if (tblspcOid != MyDatabaseTableSpace &&
218 0 : !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
219 : {
220 0 : aclresult = object_aclcheck(TableSpaceRelationId, tblspcOid, GetUserId(), ACL_CREATE);
221 0 : if (aclresult != ACLCHECK_OK)
222 0 : aclcheck_error(aclresult, OBJECT_TABLESPACE,
223 0 : get_tablespace_name(tblspcOid));
224 : }
225 :
226 0 : if (tblspcOid == DEFAULTTABLESPACE_OID)
227 0 : snprintf(tblspcPath, MAXPGPATH, "base");
228 0 : else if (tblspcOid == GLOBALTABLESPACE_OID)
229 0 : snprintf(tblspcPath, MAXPGPATH, "global");
230 : else
231 0 : snprintf(tblspcPath, MAXPGPATH, "pg_tblspc/%u/%s", tblspcOid,
232 : TABLESPACE_VERSION_DIRECTORY);
233 :
234 0 : dirdesc = AllocateDir(tblspcPath);
235 :
236 0 : if (!dirdesc)
237 0 : return -1;
238 :
239 0 : while ((direntry = ReadDir(dirdesc, tblspcPath)) != NULL)
240 : {
241 : struct stat fst;
242 :
243 0 : CHECK_FOR_INTERRUPTS();
244 :
245 0 : if (strcmp(direntry->d_name, ".") == 0 ||
246 0 : strcmp(direntry->d_name, "..") == 0)
247 0 : continue;
248 :
249 0 : snprintf(pathname, sizeof(pathname), "%s/%s", tblspcPath, direntry->d_name);
250 :
251 0 : if (stat(pathname, &fst) < 0)
252 : {
253 0 : if (errno == ENOENT)
254 0 : continue;
255 : else
256 0 : ereport(ERROR,
257 : (errcode_for_file_access(),
258 : errmsg("could not stat file \"%s\": %m", pathname)));
259 : }
260 :
261 0 : if (S_ISDIR(fst.st_mode))
262 0 : totalsize += db_dir_size(pathname);
263 :
264 0 : totalsize += fst.st_size;
265 : }
266 :
267 0 : FreeDir(dirdesc);
268 :
269 0 : return totalsize;
270 : }
271 :
272 : Datum
273 0 : pg_tablespace_size_oid(PG_FUNCTION_ARGS)
274 : {
275 0 : Oid tblspcOid = PG_GETARG_OID(0);
276 : int64 size;
277 :
278 0 : size = calculate_tablespace_size(tblspcOid);
279 :
280 0 : if (size < 0)
281 0 : PG_RETURN_NULL();
282 :
283 0 : PG_RETURN_INT64(size);
284 : }
285 :
286 : Datum
287 0 : pg_tablespace_size_name(PG_FUNCTION_ARGS)
288 : {
289 0 : Name tblspcName = PG_GETARG_NAME(0);
290 0 : Oid tblspcOid = get_tablespace_oid(NameStr(*tblspcName), false);
291 : int64 size;
292 :
293 0 : size = calculate_tablespace_size(tblspcOid);
294 :
295 0 : if (size < 0)
296 0 : PG_RETURN_NULL();
297 :
298 0 : PG_RETURN_INT64(size);
299 : }
300 :
301 :
302 : /*
303 : * calculate size of (one fork of) a relation
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.
307 : */
308 : static int64
309 490 : calculate_relation_size(RelFileLocator *rfn, BackendId backend, ForkNumber forknum)
310 : {
311 490 : int64 totalsize = 0;
312 : char *relationpath;
313 : char pathname[MAXPGPATH];
314 490 : unsigned int segcount = 0;
315 :
316 490 : relationpath = relpathbackend(*rfn, backend, forknum);
317 :
318 490 : for (segcount = 0;; segcount++)
319 256 : {
320 : struct stat fst;
321 :
322 746 : CHECK_FOR_INTERRUPTS();
323 :
324 746 : if (segcount == 0)
325 490 : snprintf(pathname, MAXPGPATH, "%s",
326 : relationpath);
327 : else
328 256 : snprintf(pathname, MAXPGPATH, "%s.%u",
329 : relationpath, segcount);
330 :
331 746 : if (stat(pathname, &fst) < 0)
332 : {
333 490 : if (errno == ENOENT)
334 490 : break;
335 : else
336 0 : ereport(ERROR,
337 : (errcode_for_file_access(),
338 : errmsg("could not stat file \"%s\": %m", pathname)));
339 : }
340 256 : totalsize += fst.st_size;
341 : }
342 :
343 490 : return totalsize;
344 : }
345 :
346 : Datum
347 204 : pg_relation_size(PG_FUNCTION_ARGS)
348 : {
349 204 : Oid relOid = PG_GETARG_OID(0);
350 204 : text *forkName = PG_GETARG_TEXT_PP(1);
351 : Relation rel;
352 : int64 size;
353 :
354 204 : rel = try_relation_open(relOid, AccessShareLock);
355 :
356 : /*
357 : * 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
361 : * already-dropped tables than throw an error and abort the whole query.
362 : */
363 204 : if (rel == NULL)
364 2 : PG_RETURN_NULL();
365 :
366 202 : size = calculate_relation_size(&(rel->rd_locator), rel->rd_backend,
367 202 : forkname_to_number(text_to_cstring(forkName)));
368 :
369 202 : relation_close(rel, AccessShareLock);
370 :
371 202 : 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.
377 : */
378 : static int64
379 0 : calculate_toast_table_size(Oid toastrelid)
380 : {
381 0 : int64 size = 0;
382 : Relation toastRel;
383 : ForkNumber forkNum;
384 : ListCell *lc;
385 : List *indexlist;
386 :
387 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++)
391 0 : size += calculate_relation_size(&(toastRel->rd_locator),
392 : toastRel->rd_backend, forkNum);
393 :
394 : /* toast index size, including FSM and VM size */
395 0 : indexlist = RelationGetIndexList(toastRel);
396 :
397 : /* Size is calculated using all the indexes available */
398 0 : foreach(lc, indexlist)
399 : {
400 : Relation toastIdxRel;
401 :
402 0 : toastIdxRel = relation_open(lfirst_oid(lc),
403 : AccessShareLock);
404 0 : for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
405 0 : size += calculate_relation_size(&(toastIdxRel->rd_locator),
406 : toastIdxRel->rd_backend, forkNum);
407 :
408 0 : relation_close(toastIdxRel, AccessShareLock);
409 : }
410 0 : list_free(indexlist);
411 0 : relation_close(toastRel, AccessShareLock);
412 :
413 0 : return size;
414 : }
415 :
416 : /*
417 : * Calculate total on-disk size of a given table,
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 : */
424 : static int64
425 72 : calculate_table_size(Relation rel)
426 : {
427 72 : int64 size = 0;
428 : ForkNumber forkNum;
429 :
430 : /*
431 : * heap size, including FSM and VM
432 : */
433 360 : for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
434 288 : size += calculate_relation_size(&(rel->rd_locator), rel->rd_backend,
435 : forkNum);
436 :
437 : /*
438 : * Size of toast relation
439 : */
440 72 : if (OidIsValid(rel->rd_rel->reltoastrelid))
441 0 : size += calculate_toast_table_size(rel->rd_rel->reltoastrelid);
442 :
443 72 : return size;
444 : }
445 :
446 : /*
447 : * 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
452 0 : calculate_indexes_size(Relation rel)
453 : {
454 0 : int64 size = 0;
455 :
456 : /*
457 : * Aggregate all indexes on the given relation
458 : */
459 0 : if (rel->rd_rel->relhasindex)
460 : {
461 0 : List *index_oids = RelationGetIndexList(rel);
462 : ListCell *cell;
463 :
464 0 : foreach(cell, index_oids)
465 : {
466 0 : Oid idxOid = lfirst_oid(cell);
467 : Relation idxRel;
468 : ForkNumber forkNum;
469 :
470 0 : idxRel = relation_open(idxOid, AccessShareLock);
471 :
472 0 : for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
473 0 : size += calculate_relation_size(&(idxRel->rd_locator),
474 : idxRel->rd_backend,
475 : forkNum);
476 :
477 0 : relation_close(idxRel, AccessShareLock);
478 : }
479 :
480 0 : list_free(index_oids);
481 : }
482 :
483 0 : return size;
484 : }
485 :
486 : Datum
487 72 : pg_table_size(PG_FUNCTION_ARGS)
488 : {
489 72 : Oid relOid = PG_GETARG_OID(0);
490 : Relation rel;
491 : int64 size;
492 :
493 72 : rel = try_relation_open(relOid, AccessShareLock);
494 :
495 72 : if (rel == NULL)
496 0 : PG_RETURN_NULL();
497 :
498 72 : size = calculate_table_size(rel);
499 :
500 72 : relation_close(rel, AccessShareLock);
501 :
502 72 : PG_RETURN_INT64(size);
503 : }
504 :
505 : Datum
506 0 : pg_indexes_size(PG_FUNCTION_ARGS)
507 : {
508 0 : Oid relOid = PG_GETARG_OID(0);
509 : Relation rel;
510 : int64 size;
511 :
512 0 : rel = try_relation_open(relOid, AccessShareLock);
513 :
514 0 : if (rel == NULL)
515 0 : PG_RETURN_NULL();
516 :
517 0 : size = calculate_indexes_size(rel);
518 :
519 0 : relation_close(rel, AccessShareLock);
520 :
521 0 : PG_RETURN_INT64(size);
522 : }
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
529 0 : calculate_total_relation_size(Relation rel)
530 : {
531 : int64 size;
532 :
533 : /*
534 : * Aggregate the table size, this includes size of the heap, toast and
535 : * toast index with free space and visibility map
536 : */
537 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);
543 :
544 0 : return size;
545 : }
546 :
547 : Datum
548 0 : pg_total_relation_size(PG_FUNCTION_ARGS)
549 : {
550 0 : Oid relOid = PG_GETARG_OID(0);
551 : Relation rel;
552 : int64 size;
553 :
554 0 : rel = try_relation_open(relOid, AccessShareLock);
555 :
556 0 : if (rel == NULL)
557 0 : PG_RETURN_NULL();
558 :
559 0 : size = calculate_total_relation_size(rel);
560 :
561 0 : relation_close(rel, AccessShareLock);
562 :
563 0 : PG_RETURN_INT64(size);
564 : }
565 :
566 : /*
567 : * formatting with size units
568 : */
569 : Datum
570 270 : pg_size_pretty(PG_FUNCTION_ARGS)
571 : {
572 270 : int64 size = PG_GETARG_INT64(0);
573 : char buf[64];
574 : const struct size_pretty_unit *unit;
575 :
576 690 : for (unit = size_pretty_units; unit->name != NULL; unit++)
577 : {
578 : uint8 bits;
579 :
580 : /* use this unit if there are no more units or we're below the limit */
581 690 : if (unit[1].name == NULL || i64abs(size) < unit->limit)
582 : {
583 270 : if (unit->round)
584 156 : size = half_rounded(size);
585 :
586 270 : snprintf(buf, sizeof(buf), INT64_FORMAT " %s", size, unit->name);
587 270 : break;
588 : }
589 :
590 : /*
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 : */
599 420 : bits = (unit[1].unitbits - unit->unitbits - (unit[1].round == true)
600 420 : + (unit->round == true));
601 420 : size /= ((int64) 1) << bits;
602 : }
603 :
604 270 : PG_RETURN_TEXT_P(cstring_to_text(buf));
605 : }
606 :
607 : static char *
608 288 : numeric_to_cstring(Numeric n)
609 : {
610 288 : Datum d = NumericGetDatum(n);
611 :
612 288 : return DatumGetCString(DirectFunctionCall1(numeric_out, d));
613 : }
614 :
615 : static bool
616 912 : numeric_is_less(Numeric a, Numeric b)
617 : {
618 912 : Datum da = NumericGetDatum(a);
619 912 : Datum db = NumericGetDatum(b);
620 :
621 912 : return DatumGetBool(DirectFunctionCall2(numeric_lt, da, db));
622 : }
623 :
624 : static Numeric
625 912 : numeric_absolute(Numeric n)
626 : {
627 912 : Datum d = NumericGetDatum(n);
628 : Datum result;
629 :
630 912 : result = DirectFunctionCall1(numeric_abs, d);
631 912 : return DatumGetNumeric(result);
632 : }
633 :
634 : static Numeric
635 228 : numeric_half_rounded(Numeric n)
636 : {
637 228 : Datum d = NumericGetDatum(n);
638 : Datum zero;
639 : Datum one;
640 : Datum two;
641 : Datum result;
642 :
643 228 : zero = NumericGetDatum(int64_to_numeric(0));
644 228 : one = NumericGetDatum(int64_to_numeric(1));
645 228 : two = NumericGetDatum(int64_to_numeric(2));
646 :
647 228 : if (DatumGetBool(DirectFunctionCall2(numeric_ge, d, zero)))
648 114 : d = DirectFunctionCall2(numeric_add, d, one);
649 : else
650 114 : d = DirectFunctionCall2(numeric_sub, d, one);
651 :
652 228 : result = DirectFunctionCall2(numeric_div_trunc, d, two);
653 228 : return DatumGetNumeric(result);
654 : }
655 :
656 : static Numeric
657 660 : numeric_truncated_divide(Numeric n, int64 divisor)
658 : {
659 660 : Datum d = NumericGetDatum(n);
660 : Datum divisor_numeric;
661 : Datum result;
662 :
663 660 : divisor_numeric = NumericGetDatum(int64_to_numeric(divisor));
664 660 : result = DirectFunctionCall2(numeric_div_trunc, d, divisor_numeric);
665 660 : return DatumGetNumeric(result);
666 : }
667 :
668 : Datum
669 288 : pg_size_pretty_numeric(PG_FUNCTION_ARGS)
670 : {
671 288 : Numeric size = PG_GETARG_NUMERIC(0);
672 288 : char *result = NULL;
673 : const struct size_pretty_unit *unit;
674 :
675 948 : for (unit = size_pretty_units; unit->name != NULL; unit++)
676 : {
677 : unsigned int shiftby;
678 :
679 : /* use this unit if there are no more units or we're below the limit */
680 1860 : if (unit[1].name == NULL ||
681 912 : numeric_is_less(numeric_absolute(size),
682 912 : int64_to_numeric(unit->limit)))
683 : {
684 288 : if (unit->round)
685 228 : size = numeric_half_rounded(size);
686 :
687 288 : result = psprintf("%s %s", numeric_to_cstring(size), unit->name);
688 288 : break;
689 : }
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
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 : */
698 660 : shiftby = (unit[1].unitbits - unit->unitbits - (unit[1].round == true)
699 660 : + (unit->round == true));
700 660 : size = numeric_truncated_divide(size, ((int64) 1) << shiftby);
701 : }
702 :
703 288 : 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
710 360 : pg_size_bytes(PG_FUNCTION_ARGS)
711 : {
712 360 : text *arg = PG_GETARG_TEXT_PP(0);
713 : char *str,
714 : *strptr,
715 : *endptr;
716 : char saved_char;
717 : Numeric num;
718 : int64 result;
719 360 : bool have_digits = false;
720 :
721 360 : str = text_to_cstring(arg);
722 :
723 : /* Skip leading whitespace */
724 360 : strptr = str;
725 378 : while (isspace((unsigned char) *strptr))
726 18 : strptr++;
727 :
728 : /* Check that we have a valid number and determine where it ends */
729 360 : endptr = strptr;
730 :
731 : /* Part (1): sign */
732 360 : if (*endptr == '-' || *endptr == '+')
733 138 : endptr++;
734 :
735 : /* Part (2): main digit string */
736 360 : if (isdigit((unsigned char) *endptr))
737 : {
738 288 : have_digits = true;
739 : do
740 594 : endptr++;
741 594 : while (isdigit((unsigned char) *endptr));
742 : }
743 :
744 : /* Part (3): optional decimal point and fractional digits */
745 360 : if (*endptr == '.')
746 : {
747 102 : endptr++;
748 102 : if (isdigit((unsigned char) *endptr))
749 : {
750 48 : have_digits = true;
751 : do
752 48 : endptr++;
753 48 : while (isdigit((unsigned char) *endptr));
754 : }
755 : }
756 :
757 : /* Complain if we don't have a valid number at this point */
758 360 : if (!have_digits)
759 48 : ereport(ERROR,
760 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
761 : errmsg("invalid size: \"%s\"", str)));
762 :
763 : /* Part (4): optional exponent */
764 312 : if (*endptr == 'e' || *endptr == 'E')
765 : {
766 : long exponent;
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 : */
773 30 : exponent = strtol(endptr + 1, &cp, 10);
774 : (void) exponent; /* Silence -Wunused-result warnings */
775 30 : if (cp > endptr + 1)
776 30 : endptr = cp;
777 : }
778 :
779 : /*
780 : * Parse the number, saving the next character, which may be the first
781 : * character of the unit string.
782 : */
783 312 : saved_char = *endptr;
784 312 : *endptr = '\0';
785 :
786 312 : num = DatumGetNumeric(DirectFunctionCall3(numeric_in,
787 : CStringGetDatum(strptr),
788 : ObjectIdGetDatum(InvalidOid),
789 : Int32GetDatum(-1)));
790 :
791 306 : *endptr = saved_char;
792 :
793 : /* Skip whitespace between number and unit */
794 306 : strptr = endptr;
795 450 : while (isspace((unsigned char) *strptr))
796 144 : strptr++;
797 :
798 : /* Handle possible unit */
799 306 : if (*strptr != '\0')
800 : {
801 : const struct size_pretty_unit *unit;
802 264 : int64 multiplier = 0;
803 :
804 : /* Trim any trailing whitespace */
805 264 : endptr = str + VARSIZE_ANY_EXHDR(arg) - 1;
806 :
807 306 : while (isspace((unsigned char) *endptr))
808 42 : endptr--;
809 :
810 264 : endptr++;
811 264 : *endptr = '\0';
812 :
813 1002 : for (unit = size_pretty_units; unit->name != NULL; unit++)
814 : {
815 : /* Parse the unit case-insensitively */
816 966 : if (pg_strcasecmp(strptr, unit->name) == 0)
817 228 : break;
818 : }
819 :
820 : /* If not found, look in table of aliases */
821 264 : if (unit->name == NULL)
822 : {
823 66 : for (const struct size_bytes_unit_alias *a = size_bytes_aliases; a->alias != NULL; a++)
824 : {
825 36 : if (pg_strcasecmp(strptr, a->alias) == 0)
826 : {
827 6 : unit = &size_pretty_units[a->unit_index];
828 6 : break;
829 : }
830 : }
831 : }
832 :
833 : /* Verify we found a valid unit in the loop above */
834 264 : if (unit->name == NULL)
835 30 : ereport(ERROR,
836 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
837 : 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 :
841 234 : multiplier = ((int64) 1) << unit->unitbits;
842 :
843 234 : if (multiplier > 1)
844 : {
845 : Numeric mul_num;
846 :
847 210 : mul_num = int64_to_numeric(multiplier);
848 :
849 210 : num = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
850 : NumericGetDatum(mul_num),
851 : NumericGetDatum(num)));
852 : }
853 : }
854 :
855 276 : result = DatumGetInt64(DirectFunctionCall1(numeric_int8,
856 : NumericGetDatum(num)));
857 :
858 264 : PG_RETURN_INT64(result);
859 : }
860 :
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
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
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
873 : * seems better to quietly return NULL.
874 : */
875 : Datum
876 16006 : pg_relation_filenode(PG_FUNCTION_ARGS)
877 : {
878 16006 : Oid relid = PG_GETARG_OID(0);
879 : RelFileNumber result;
880 : HeapTuple tuple;
881 : Form_pg_class relform;
882 :
883 16006 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
884 16006 : if (!HeapTupleIsValid(tuple))
885 0 : PG_RETURN_NULL();
886 16006 : relform = (Form_pg_class) GETSTRUCT(tuple);
887 :
888 16006 : if (RELKIND_HAS_STORAGE(relform->relkind))
889 : {
890 13146 : if (relform->relfilenode)
891 11404 : result = relform->relfilenode;
892 : else /* Consult the relation mapper */
893 1742 : result = RelationMapOidToFilenumber(relid,
894 1742 : relform->relisshared);
895 : }
896 : else
897 : {
898 : /* no storage, return NULL */
899 2860 : result = InvalidRelFileNumber;
900 : }
901 :
902 16006 : ReleaseSysCache(tuple);
903 :
904 16006 : if (!RelFileNumberIsValid(result))
905 2860 : PG_RETURN_NULL();
906 :
907 13146 : PG_RETURN_OID(result);
908 : }
909 :
910 : /*
911 : * Get the relation via (reltablespace, relfilenumber)
912 : *
913 : * This is expected to be used when somebody wants to match an individual file
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
916 : * tables.
917 : *
918 : * We don't fail but return NULL if we cannot find a mapping.
919 : *
920 : * InvalidOid can be passed instead of the current database's default
921 : * tablespace.
922 : */
923 : Datum
924 7644 : pg_filenode_relation(PG_FUNCTION_ARGS)
925 : {
926 7644 : Oid reltablespace = PG_GETARG_OID(0);
927 7644 : RelFileNumber relfilenumber = PG_GETARG_OID(1);
928 : Oid heaprel;
929 :
930 : /* test needed so RelidByRelfilenumber doesn't misbehave */
931 7644 : if (!RelFileNumberIsValid(relfilenumber))
932 0 : PG_RETURN_NULL();
933 :
934 7644 : heaprel = RelidByRelfilenumber(reltablespace, relfilenumber);
935 :
936 7644 : if (!OidIsValid(heaprel))
937 0 : PG_RETURN_NULL();
938 : else
939 7644 : 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
948 3008 : pg_relation_filepath(PG_FUNCTION_ARGS)
949 : {
950 3008 : Oid relid = PG_GETARG_OID(0);
951 : HeapTuple tuple;
952 : Form_pg_class relform;
953 : RelFileLocator rlocator;
954 : BackendId backend;
955 : char *path;
956 :
957 3008 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
958 3008 : if (!HeapTupleIsValid(tuple))
959 2 : PG_RETURN_NULL();
960 3006 : relform = (Form_pg_class) GETSTRUCT(tuple);
961 :
962 3006 : if (RELKIND_HAS_STORAGE(relform->relkind))
963 : {
964 : /* This logic should match RelationInitPhysicalAddr */
965 2434 : if (relform->reltablespace)
966 224 : rlocator.spcOid = relform->reltablespace;
967 : else
968 2210 : rlocator.spcOid = MyDatabaseTableSpace;
969 2434 : if (rlocator.spcOid == GLOBALTABLESPACE_OID)
970 200 : rlocator.dbOid = InvalidOid;
971 : else
972 2234 : rlocator.dbOid = MyDatabaseId;
973 2434 : if (relform->relfilenode)
974 2098 : rlocator.relNumber = relform->relfilenode;
975 : else /* Consult the relation mapper */
976 336 : rlocator.relNumber = RelationMapOidToFilenumber(relid,
977 336 : relform->relisshared);
978 : }
979 : else
980 : {
981 : /* no storage, return NULL */
982 572 : rlocator.relNumber = InvalidRelFileNumber;
983 : /* some compilers generate warnings without these next two lines */
984 572 : rlocator.dbOid = InvalidOid;
985 572 : rlocator.spcOid = InvalidOid;
986 : }
987 :
988 3006 : if (!RelFileNumberIsValid(rlocator.relNumber))
989 : {
990 572 : ReleaseSysCache(tuple);
991 572 : PG_RETURN_NULL();
992 : }
993 :
994 : /* Determine owning backend. */
995 2434 : switch (relform->relpersistence)
996 : {
997 2434 : case RELPERSISTENCE_UNLOGGED:
998 : case RELPERSISTENCE_PERMANENT:
999 2434 : backend = InvalidBackendId;
1000 2434 : break;
1001 0 : case RELPERSISTENCE_TEMP:
1002 0 : if (isTempOrTempToastNamespace(relform->relnamespace))
1003 0 : backend = BackendIdForTempRelations();
1004 : else
1005 : {
1006 : /* Do it the hard way. */
1007 0 : backend = GetTempNamespaceBackendId(relform->relnamespace);
1008 : Assert(backend != InvalidBackendId);
1009 : }
1010 0 : break;
1011 0 : default:
1012 0 : elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
1013 : backend = InvalidBackendId; /* placate compiler */
1014 : break;
1015 : }
1016 :
1017 2434 : ReleaseSysCache(tuple);
1018 :
1019 2434 : path = relpathbackend(rlocator, backend, MAIN_FORKNUM);
1020 :
1021 2434 : PG_RETURN_TEXT_P(cstring_to_text(path));
1022 : }
|