1 /*
2  * dbsize.c
3  *		Database object size functions, and related inquiries
4  *
5  * Copyright (c) 2002-2021, 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_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(), ROLE_PG_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(), ROLE_PG_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 bool
numeric_is_less(Numeric a,Numeric b)587 numeric_is_less(Numeric a, Numeric b)
588 {
589 	Datum		da = NumericGetDatum(a);
590 	Datum		db = NumericGetDatum(b);
591 
592 	return DatumGetBool(DirectFunctionCall2(numeric_lt, da, db));
593 }
594 
595 static Numeric
numeric_absolute(Numeric n)596 numeric_absolute(Numeric n)
597 {
598 	Datum		d = NumericGetDatum(n);
599 	Datum		result;
600 
601 	result = DirectFunctionCall1(numeric_abs, d);
602 	return DatumGetNumeric(result);
603 }
604 
605 static Numeric
numeric_half_rounded(Numeric n)606 numeric_half_rounded(Numeric n)
607 {
608 	Datum		d = NumericGetDatum(n);
609 	Datum		zero;
610 	Datum		one;
611 	Datum		two;
612 	Datum		result;
613 
614 	zero = NumericGetDatum(int64_to_numeric(0));
615 	one = NumericGetDatum(int64_to_numeric(1));
616 	two = NumericGetDatum(int64_to_numeric(2));
617 
618 	if (DatumGetBool(DirectFunctionCall2(numeric_ge, d, zero)))
619 		d = DirectFunctionCall2(numeric_add, d, one);
620 	else
621 		d = DirectFunctionCall2(numeric_sub, d, one);
622 
623 	result = DirectFunctionCall2(numeric_div_trunc, d, two);
624 	return DatumGetNumeric(result);
625 }
626 
627 static Numeric
numeric_truncated_divide(Numeric n,int64 divisor)628 numeric_truncated_divide(Numeric n, int64 divisor)
629 {
630 	Datum		d = NumericGetDatum(n);
631 	Datum		divisor_numeric;
632 	Datum		result;
633 
634 	divisor_numeric = NumericGetDatum(int64_to_numeric(divisor));
635 	result = DirectFunctionCall2(numeric_div_trunc, d, divisor_numeric);
636 	return DatumGetNumeric(result);
637 }
638 
639 Datum
pg_size_pretty_numeric(PG_FUNCTION_ARGS)640 pg_size_pretty_numeric(PG_FUNCTION_ARGS)
641 {
642 	Numeric		size = PG_GETARG_NUMERIC(0);
643 	Numeric		limit,
644 				limit2;
645 	char	   *result;
646 
647 	limit = int64_to_numeric(10 * 1024);
648 	limit2 = int64_to_numeric(10 * 1024 * 2 - 1);
649 
650 	if (numeric_is_less(numeric_absolute(size), limit))
651 	{
652 		result = psprintf("%s bytes", numeric_to_cstring(size));
653 	}
654 	else
655 	{
656 		/* keep one extra bit for rounding */
657 		/* size /= (1 << 9) */
658 		size = numeric_truncated_divide(size, 1 << 9);
659 
660 		if (numeric_is_less(numeric_absolute(size), limit2))
661 		{
662 			size = numeric_half_rounded(size);
663 			result = psprintf("%s kB", numeric_to_cstring(size));
664 		}
665 		else
666 		{
667 			/* size /= (1 << 10) */
668 			size = numeric_truncated_divide(size, 1 << 10);
669 
670 			if (numeric_is_less(numeric_absolute(size), limit2))
671 			{
672 				size = numeric_half_rounded(size);
673 				result = psprintf("%s MB", numeric_to_cstring(size));
674 			}
675 			else
676 			{
677 				/* size /= (1 << 10) */
678 				size = numeric_truncated_divide(size, 1 << 10);
679 
680 				if (numeric_is_less(numeric_absolute(size), limit2))
681 				{
682 					size = numeric_half_rounded(size);
683 					result = psprintf("%s GB", numeric_to_cstring(size));
684 				}
685 				else
686 				{
687 					/* size /= (1 << 10) */
688 					size = numeric_truncated_divide(size, 1 << 10);
689 					size = numeric_half_rounded(size);
690 					result = psprintf("%s TB", numeric_to_cstring(size));
691 				}
692 			}
693 		}
694 	}
695 
696 	PG_RETURN_TEXT_P(cstring_to_text(result));
697 }
698 
699 /*
700  * Convert a human-readable size to a size in bytes
701  */
702 Datum
pg_size_bytes(PG_FUNCTION_ARGS)703 pg_size_bytes(PG_FUNCTION_ARGS)
704 {
705 	text	   *arg = PG_GETARG_TEXT_PP(0);
706 	char	   *str,
707 			   *strptr,
708 			   *endptr;
709 	char		saved_char;
710 	Numeric		num;
711 	int64		result;
712 	bool		have_digits = false;
713 
714 	str = text_to_cstring(arg);
715 
716 	/* Skip leading whitespace */
717 	strptr = str;
718 	while (isspace((unsigned char) *strptr))
719 		strptr++;
720 
721 	/* Check that we have a valid number and determine where it ends */
722 	endptr = strptr;
723 
724 	/* Part (1): sign */
725 	if (*endptr == '-' || *endptr == '+')
726 		endptr++;
727 
728 	/* Part (2): main digit string */
729 	if (isdigit((unsigned char) *endptr))
730 	{
731 		have_digits = true;
732 		do
733 			endptr++;
734 		while (isdigit((unsigned char) *endptr));
735 	}
736 
737 	/* Part (3): optional decimal point and fractional digits */
738 	if (*endptr == '.')
739 	{
740 		endptr++;
741 		if (isdigit((unsigned char) *endptr))
742 		{
743 			have_digits = true;
744 			do
745 				endptr++;
746 			while (isdigit((unsigned char) *endptr));
747 		}
748 	}
749 
750 	/* Complain if we don't have a valid number at this point */
751 	if (!have_digits)
752 		ereport(ERROR,
753 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
754 				 errmsg("invalid size: \"%s\"", str)));
755 
756 	/* Part (4): optional exponent */
757 	if (*endptr == 'e' || *endptr == 'E')
758 	{
759 		long		exponent;
760 		char	   *cp;
761 
762 		/*
763 		 * Note we might one day support EB units, so if what follows 'E'
764 		 * isn't a number, just treat it all as a unit to be parsed.
765 		 */
766 		exponent = strtol(endptr + 1, &cp, 10);
767 		(void) exponent;		/* Silence -Wunused-result warnings */
768 		if (cp > endptr + 1)
769 			endptr = cp;
770 	}
771 
772 	/*
773 	 * Parse the number, saving the next character, which may be the first
774 	 * character of the unit string.
775 	 */
776 	saved_char = *endptr;
777 	*endptr = '\0';
778 
779 	num = DatumGetNumeric(DirectFunctionCall3(numeric_in,
780 											  CStringGetDatum(strptr),
781 											  ObjectIdGetDatum(InvalidOid),
782 											  Int32GetDatum(-1)));
783 
784 	*endptr = saved_char;
785 
786 	/* Skip whitespace between number and unit */
787 	strptr = endptr;
788 	while (isspace((unsigned char) *strptr))
789 		strptr++;
790 
791 	/* Handle possible unit */
792 	if (*strptr != '\0')
793 	{
794 		int64		multiplier = 0;
795 
796 		/* Trim any trailing whitespace */
797 		endptr = str + VARSIZE_ANY_EXHDR(arg) - 1;
798 
799 		while (isspace((unsigned char) *endptr))
800 			endptr--;
801 
802 		endptr++;
803 		*endptr = '\0';
804 
805 		/* Parse the unit case-insensitively */
806 		if (pg_strcasecmp(strptr, "bytes") == 0)
807 			multiplier = (int64) 1;
808 		else if (pg_strcasecmp(strptr, "kb") == 0)
809 			multiplier = (int64) 1024;
810 		else if (pg_strcasecmp(strptr, "mb") == 0)
811 			multiplier = ((int64) 1024) * 1024;
812 
813 		else if (pg_strcasecmp(strptr, "gb") == 0)
814 			multiplier = ((int64) 1024) * 1024 * 1024;
815 
816 		else if (pg_strcasecmp(strptr, "tb") == 0)
817 			multiplier = ((int64) 1024) * 1024 * 1024 * 1024;
818 
819 		else
820 			ereport(ERROR,
821 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
822 					 errmsg("invalid size: \"%s\"", text_to_cstring(arg)),
823 					 errdetail("Invalid size unit: \"%s\".", strptr),
824 					 errhint("Valid units are \"bytes\", \"kB\", \"MB\", \"GB\", and \"TB\".")));
825 
826 		if (multiplier > 1)
827 		{
828 			Numeric		mul_num;
829 
830 			mul_num = int64_to_numeric(multiplier);
831 
832 			num = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
833 													  NumericGetDatum(mul_num),
834 													  NumericGetDatum(num)));
835 		}
836 	}
837 
838 	result = DatumGetInt64(DirectFunctionCall1(numeric_int8,
839 											   NumericGetDatum(num)));
840 
841 	PG_RETURN_INT64(result);
842 }
843 
844 /*
845  * Get the filenode of a relation
846  *
847  * This is expected to be used in queries like
848  *		SELECT pg_relation_filenode(oid) FROM pg_class;
849  * That leads to a couple of choices.  We work from the pg_class row alone
850  * rather than actually opening each relation, for efficiency.  We don't
851  * fail if we can't find the relation --- some rows might be visible in
852  * the query's MVCC snapshot even though the relations have been dropped.
853  * (Note: we could avoid using the catcache, but there's little point
854  * because the relation mapper also works "in the now".)  We also don't
855  * fail if the relation doesn't have storage.  In all these cases it
856  * seems better to quietly return NULL.
857  */
858 Datum
pg_relation_filenode(PG_FUNCTION_ARGS)859 pg_relation_filenode(PG_FUNCTION_ARGS)
860 {
861 	Oid			relid = PG_GETARG_OID(0);
862 	Oid			result;
863 	HeapTuple	tuple;
864 	Form_pg_class relform;
865 
866 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
867 	if (!HeapTupleIsValid(tuple))
868 		PG_RETURN_NULL();
869 	relform = (Form_pg_class) GETSTRUCT(tuple);
870 
871 	if (RELKIND_HAS_STORAGE(relform->relkind))
872 	{
873 		if (relform->relfilenode)
874 			result = relform->relfilenode;
875 		else					/* Consult the relation mapper */
876 			result = RelationMapOidToFilenode(relid,
877 											  relform->relisshared);
878 	}
879 	else
880 	{
881 		/* no storage, return NULL */
882 		result = InvalidOid;
883 	}
884 
885 	ReleaseSysCache(tuple);
886 
887 	if (!OidIsValid(result))
888 		PG_RETURN_NULL();
889 
890 	PG_RETURN_OID(result);
891 }
892 
893 /*
894  * Get the relation via (reltablespace, relfilenode)
895  *
896  * This is expected to be used when somebody wants to match an individual file
897  * on the filesystem back to its table. That's not trivially possible via
898  * pg_class, because that doesn't contain the relfilenodes of shared and nailed
899  * tables.
900  *
901  * We don't fail but return NULL if we cannot find a mapping.
902  *
903  * InvalidOid can be passed instead of the current database's default
904  * tablespace.
905  */
906 Datum
pg_filenode_relation(PG_FUNCTION_ARGS)907 pg_filenode_relation(PG_FUNCTION_ARGS)
908 {
909 	Oid			reltablespace = PG_GETARG_OID(0);
910 	Oid			relfilenode = PG_GETARG_OID(1);
911 	Oid			heaprel;
912 
913 	/* test needed so RelidByRelfilenode doesn't misbehave */
914 	if (!OidIsValid(relfilenode))
915 		PG_RETURN_NULL();
916 
917 	heaprel = RelidByRelfilenode(reltablespace, relfilenode);
918 
919 	if (!OidIsValid(heaprel))
920 		PG_RETURN_NULL();
921 	else
922 		PG_RETURN_OID(heaprel);
923 }
924 
925 /*
926  * Get the pathname (relative to $PGDATA) of a relation
927  *
928  * See comments for pg_relation_filenode.
929  */
930 Datum
pg_relation_filepath(PG_FUNCTION_ARGS)931 pg_relation_filepath(PG_FUNCTION_ARGS)
932 {
933 	Oid			relid = PG_GETARG_OID(0);
934 	HeapTuple	tuple;
935 	Form_pg_class relform;
936 	RelFileNode rnode;
937 	BackendId	backend;
938 	char	   *path;
939 
940 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
941 	if (!HeapTupleIsValid(tuple))
942 		PG_RETURN_NULL();
943 	relform = (Form_pg_class) GETSTRUCT(tuple);
944 
945 	if (RELKIND_HAS_STORAGE(relform->relkind))
946 	{
947 		/* This logic should match RelationInitPhysicalAddr */
948 		if (relform->reltablespace)
949 			rnode.spcNode = relform->reltablespace;
950 		else
951 			rnode.spcNode = MyDatabaseTableSpace;
952 		if (rnode.spcNode == GLOBALTABLESPACE_OID)
953 			rnode.dbNode = InvalidOid;
954 		else
955 			rnode.dbNode = MyDatabaseId;
956 		if (relform->relfilenode)
957 			rnode.relNode = relform->relfilenode;
958 		else					/* Consult the relation mapper */
959 			rnode.relNode = RelationMapOidToFilenode(relid,
960 													 relform->relisshared);
961 	}
962 	else
963 	{
964 		/* no storage, return NULL */
965 		rnode.relNode = InvalidOid;
966 		/* some compilers generate warnings without these next two lines */
967 		rnode.dbNode = InvalidOid;
968 		rnode.spcNode = InvalidOid;
969 	}
970 
971 	if (!OidIsValid(rnode.relNode))
972 	{
973 		ReleaseSysCache(tuple);
974 		PG_RETURN_NULL();
975 	}
976 
977 	/* Determine owning backend. */
978 	switch (relform->relpersistence)
979 	{
980 		case RELPERSISTENCE_UNLOGGED:
981 		case RELPERSISTENCE_PERMANENT:
982 			backend = InvalidBackendId;
983 			break;
984 		case RELPERSISTENCE_TEMP:
985 			if (isTempOrTempToastNamespace(relform->relnamespace))
986 				backend = BackendIdForTempRelations();
987 			else
988 			{
989 				/* Do it the hard way. */
990 				backend = GetTempNamespaceBackendId(relform->relnamespace);
991 				Assert(backend != InvalidBackendId);
992 			}
993 			break;
994 		default:
995 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
996 			backend = InvalidBackendId; /* placate compiler */
997 			break;
998 	}
999 
1000 	ReleaseSysCache(tuple);
1001 
1002 	path = relpathbackend(rnode, backend, MAIN_FORKNUM);
1003 
1004 	PG_RETURN_TEXT_P(cstring_to_text(path));
1005 }
1006