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