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