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