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