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