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