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