2005-08-02 14:07:27 +00:00
|
|
|
/*
|
|
|
|
* dbsize.c
|
2010-02-07 20:48:13 +00:00
|
|
|
* Database object size functions, and related inquiries
|
2005-08-02 14:07:27 +00:00
|
|
|
*
|
2017-01-03 13:48:53 -05:00
|
|
|
* Copyright (c) 2002-2017, PostgreSQL Global Development Group
|
2005-08-02 14:07:27 +00:00
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2010-09-20 22:08:53 +02:00
|
|
|
* src/backend/utils/adt/dbsize.c
|
2005-08-02 14:07:27 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
|
|
|
#include "access/heapam.h"
|
2012-08-30 16:15:44 -04:00
|
|
|
#include "access/htup_details.h"
|
2006-11-06 03:06:41 +00:00
|
|
|
#include "catalog/catalog.h"
|
2005-08-02 14:07:27 +00:00
|
|
|
#include "catalog/namespace.h"
|
2017-03-30 14:18:53 -04:00
|
|
|
#include "catalog/pg_authid.h"
|
2005-08-02 14:07:27 +00:00
|
|
|
#include "catalog/pg_tablespace.h"
|
|
|
|
#include "commands/dbcommands.h"
|
|
|
|
#include "commands/tablespace.h"
|
|
|
|
#include "miscadmin.h"
|
|
|
|
#include "storage/fd.h"
|
2007-08-27 01:19:14 +00:00
|
|
|
#include "utils/acl.h"
|
2005-08-02 14:07:27 +00:00
|
|
|
#include "utils/builtins.h"
|
2012-04-14 08:04:11 -04:00
|
|
|
#include "utils/numeric.h"
|
2008-06-19 00:46:06 +00:00
|
|
|
#include "utils/rel.h"
|
2013-07-22 10:34:34 -04:00
|
|
|
#include "utils/relfilenodemap.h"
|
2010-02-07 20:48:13 +00:00
|
|
|
#include "utils/relmapper.h"
|
2005-08-02 14:07:27 +00:00
|
|
|
#include "utils/syscache.h"
|
|
|
|
|
2015-11-06 11:03:02 -05:00
|
|
|
/* Divide by two and round towards positive infinity. */
|
|
|
|
#define half_rounded(x) (((x) + ((x) < 0 ? 0 : 1)) / 2)
|
2005-08-02 14:07:27 +00:00
|
|
|
|
|
|
|
/* Return physical size of directory contents, or 0 if dir doesn't exist */
|
|
|
|
static int64
|
|
|
|
db_dir_size(const char *path)
|
|
|
|
{
|
|
|
|
int64 dirsize = 0;
|
2005-10-15 02:49:52 +00:00
|
|
|
struct dirent *direntry;
|
|
|
|
DIR *dirdesc;
|
2017-04-11 14:13:31 -04:00
|
|
|
char filename[MAXPGPATH * 2];
|
2005-08-02 14:07:27 +00:00
|
|
|
|
|
|
|
dirdesc = AllocateDir(path);
|
|
|
|
|
|
|
|
if (!dirdesc)
|
2005-10-15 02:49:52 +00:00
|
|
|
return 0;
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2005-08-02 15:17:24 +00:00
|
|
|
while ((direntry = ReadDir(dirdesc, path)) != NULL)
|
2005-08-02 14:07:27 +00:00
|
|
|
{
|
2005-10-15 02:49:52 +00:00
|
|
|
struct stat fst;
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2010-01-23 21:29:00 +00:00
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
|
2005-10-15 02:49:52 +00:00
|
|
|
if (strcmp(direntry->d_name, ".") == 0 ||
|
2005-08-02 14:07:27 +00:00
|
|
|
strcmp(direntry->d_name, "..") == 0)
|
2005-10-15 02:49:52 +00:00
|
|
|
continue;
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2017-04-11 14:13:31 -04:00
|
|
|
snprintf(filename, sizeof(filename), "%s/%s", path, direntry->d_name);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
|
|
|
if (stat(filename, &fst) < 0)
|
2007-03-11 05:22:00 +00:00
|
|
|
{
|
|
|
|
if (errno == ENOENT)
|
|
|
|
continue;
|
|
|
|
else
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode_for_file_access(),
|
|
|
|
errmsg("could not stat file \"%s\": %m", filename)));
|
|
|
|
}
|
2005-10-15 02:49:52 +00:00
|
|
|
dirsize += fst.st_size;
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FreeDir(dirdesc);
|
|
|
|
return dirsize;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* calculate size of database in all tablespaces
|
|
|
|
*/
|
|
|
|
static int64
|
|
|
|
calculate_database_size(Oid dbOid)
|
|
|
|
{
|
2005-09-16 05:35:41 +00:00
|
|
|
int64 totalsize;
|
2005-10-15 02:49:52 +00:00
|
|
|
DIR *dirdesc;
|
|
|
|
struct dirent *direntry;
|
|
|
|
char dirpath[MAXPGPATH];
|
2017-04-11 14:13:31 -04:00
|
|
|
char pathname[MAXPGPATH + 12 + sizeof(TABLESPACE_VERSION_DIRECTORY)];
|
2007-08-29 17:24:29 +00:00
|
|
|
AclResult aclresult;
|
|
|
|
|
2017-03-30 14:18:53 -04:00
|
|
|
/*
|
|
|
|
* User must have connect privilege for target database
|
|
|
|
* or be a member of pg_read_all_stats
|
|
|
|
*/
|
2007-08-29 17:24:29 +00:00
|
|
|
aclresult = pg_database_aclcheck(dbOid, GetUserId(), ACL_CONNECT);
|
2017-03-30 14:18:53 -04:00
|
|
|
if (aclresult != ACLCHECK_OK &&
|
|
|
|
!is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS))
|
|
|
|
{
|
2007-08-29 17:24:29 +00:00
|
|
|
aclcheck_error(aclresult, ACL_KIND_DATABASE,
|
|
|
|
get_database_name(dbOid));
|
2017-03-30 14:18:53 -04:00
|
|
|
}
|
2005-08-02 14:07:27 +00:00
|
|
|
|
|
|
|
/* Shared storage in pg_global is not counted */
|
|
|
|
|
|
|
|
/* Include pg_default storage */
|
2017-04-11 14:13:31 -04:00
|
|
|
snprintf(pathname, sizeof(pathname), "base/%u", dbOid);
|
2005-09-16 05:35:41 +00:00
|
|
|
totalsize = db_dir_size(pathname);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
|
|
|
/* Scan the non-default tablespaces */
|
2006-11-06 03:06:41 +00:00
|
|
|
snprintf(dirpath, MAXPGPATH, "pg_tblspc");
|
2005-08-02 15:17:24 +00:00
|
|
|
dirdesc = AllocateDir(dirpath);
|
2005-08-02 14:07:27 +00:00
|
|
|
if (!dirdesc)
|
2005-10-15 02:49:52 +00:00
|
|
|
ereport(ERROR,
|
2005-08-02 14:07:27 +00:00
|
|
|
(errcode_for_file_access(),
|
|
|
|
errmsg("could not open tablespace directory \"%s\": %m",
|
2005-08-02 15:17:24 +00:00
|
|
|
dirpath)));
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2005-08-02 15:17:24 +00:00
|
|
|
while ((direntry = ReadDir(dirdesc, dirpath)) != NULL)
|
2005-08-02 14:07:27 +00:00
|
|
|
{
|
2010-01-23 21:29:00 +00:00
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
|
2005-10-15 02:49:52 +00:00
|
|
|
if (strcmp(direntry->d_name, ".") == 0 ||
|
2005-08-02 14:07:27 +00:00
|
|
|
strcmp(direntry->d_name, "..") == 0)
|
2005-10-15 02:49:52 +00:00
|
|
|
continue;
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2017-04-11 14:13:31 -04:00
|
|
|
snprintf(pathname, sizeof(pathname), "pg_tblspc/%s/%s/%u",
|
2010-01-12 02:42:52 +00:00
|
|
|
direntry->d_name, TABLESPACE_VERSION_DIRECTORY, dbOid);
|
2005-08-02 14:07:27 +00:00
|
|
|
totalsize += db_dir_size(pathname);
|
|
|
|
}
|
|
|
|
|
|
|
|
FreeDir(dirdesc);
|
|
|
|
|
|
|
|
return totalsize;
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
pg_database_size_oid(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2005-10-15 02:49:52 +00:00
|
|
|
Oid dbOid = PG_GETARG_OID(0);
|
2012-01-19 13:06:30 +02:00
|
|
|
int64 size;
|
|
|
|
|
|
|
|
size = calculate_database_size(dbOid);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2012-01-19 13:06:30 +02:00
|
|
|
if (size == 0)
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
|
|
|
|
PG_RETURN_INT64(size);
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
pg_database_size_name(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2005-10-15 02:49:52 +00:00
|
|
|
Name dbName = PG_GETARG_NAME(0);
|
2010-08-05 14:45:09 +00:00
|
|
|
Oid dbOid = get_database_oid(NameStr(*dbName), false);
|
2012-01-19 13:06:30 +02:00
|
|
|
int64 size;
|
|
|
|
|
|
|
|
size = calculate_database_size(dbOid);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2012-01-19 13:06:30 +02:00
|
|
|
if (size == 0)
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
|
|
|
|
PG_RETURN_INT64(size);
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2012-01-19 13:06:30 +02:00
|
|
|
* Calculate total size of tablespace. Returns -1 if the tablespace directory
|
|
|
|
* cannot be found.
|
2005-08-02 14:07:27 +00:00
|
|
|
*/
|
|
|
|
static int64
|
|
|
|
calculate_tablespace_size(Oid tblspcOid)
|
|
|
|
{
|
2005-10-15 02:49:52 +00:00
|
|
|
char tblspcPath[MAXPGPATH];
|
2017-04-11 14:13:31 -04:00
|
|
|
char pathname[MAXPGPATH * 2];
|
2005-10-15 02:49:52 +00:00
|
|
|
int64 totalsize = 0;
|
|
|
|
DIR *dirdesc;
|
|
|
|
struct dirent *direntry;
|
2007-08-29 17:24:29 +00:00
|
|
|
AclResult aclresult;
|
|
|
|
|
|
|
|
/*
|
2017-03-30 14:18:53 -04:00
|
|
|
* User must be a member of pg_read_all_stats or have CREATE privilege for
|
|
|
|
* target tablespace, either explicitly granted or implicitly because
|
|
|
|
* it is default for current database.
|
2007-08-29 17:24:29 +00:00
|
|
|
*/
|
2017-03-30 14:18:53 -04:00
|
|
|
if (tblspcOid != MyDatabaseTableSpace &&
|
|
|
|
!is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS))
|
2007-08-29 17:24:29 +00:00
|
|
|
{
|
|
|
|
aclresult = pg_tablespace_aclcheck(tblspcOid, GetUserId(), ACL_CREATE);
|
|
|
|
if (aclresult != ACLCHECK_OK)
|
|
|
|
aclcheck_error(aclresult, ACL_KIND_TABLESPACE,
|
|
|
|
get_tablespace_name(tblspcOid));
|
|
|
|
}
|
2005-08-02 14:07:27 +00:00
|
|
|
|
|
|
|
if (tblspcOid == DEFAULTTABLESPACE_OID)
|
2006-11-06 03:06:41 +00:00
|
|
|
snprintf(tblspcPath, MAXPGPATH, "base");
|
2005-08-02 14:07:27 +00:00
|
|
|
else if (tblspcOid == GLOBALTABLESPACE_OID)
|
2006-11-06 03:06:41 +00:00
|
|
|
snprintf(tblspcPath, MAXPGPATH, "global");
|
2005-08-02 14:07:27 +00:00
|
|
|
else
|
2010-01-12 02:42:52 +00:00
|
|
|
snprintf(tblspcPath, MAXPGPATH, "pg_tblspc/%u/%s", tblspcOid,
|
2010-02-26 02:01:40 +00:00
|
|
|
TABLESPACE_VERSION_DIRECTORY);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
|
|
|
dirdesc = AllocateDir(tblspcPath);
|
|
|
|
|
|
|
|
if (!dirdesc)
|
2012-01-19 13:06:30 +02:00
|
|
|
return -1;
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2005-08-02 15:17:24 +00:00
|
|
|
while ((direntry = ReadDir(dirdesc, tblspcPath)) != NULL)
|
2005-08-02 14:07:27 +00:00
|
|
|
{
|
2005-10-15 02:49:52 +00:00
|
|
|
struct stat fst;
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2010-01-23 21:29:00 +00:00
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
|
2005-10-15 02:49:52 +00:00
|
|
|
if (strcmp(direntry->d_name, ".") == 0 ||
|
2005-08-02 14:07:27 +00:00
|
|
|
strcmp(direntry->d_name, "..") == 0)
|
2005-10-15 02:49:52 +00:00
|
|
|
continue;
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2017-04-11 14:13:31 -04:00
|
|
|
snprintf(pathname, sizeof(pathname), "%s/%s", tblspcPath, direntry->d_name);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
|
|
|
if (stat(pathname, &fst) < 0)
|
2007-03-11 05:22:00 +00:00
|
|
|
{
|
|
|
|
if (errno == ENOENT)
|
|
|
|
continue;
|
|
|
|
else
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode_for_file_access(),
|
|
|
|
errmsg("could not stat file \"%s\": %m", pathname)));
|
|
|
|
}
|
2005-08-02 14:07:27 +00:00
|
|
|
|
Fix a number of places that were making file-type tests infelicitously.
The places that did, eg,
(statbuf.st_mode & S_IFMT) == S_IFDIR
were correct, but there is no good reason not to use S_ISDIR() instead,
especially when that's what the other 90% of our code does. The places
that did, eg,
(statbuf.st_mode & S_IFDIR)
were flat out *wrong* and would fail in various platform-specific ways,
eg a symlink could be mistaken for a regular file on most Unixen.
The actual impact of this is probably small, since the problem cases
seem to always involve symlinks or sockets, which are unlikely to be
found in the directories that PG code might be scanning. But it's
clearly trouble waiting to happen, so patch all the way back anyway.
(There seem to be no occurrences of the mistake in 7.4.)
2008-03-31 01:31:43 +00:00
|
|
|
if (S_ISDIR(fst.st_mode))
|
2005-10-15 02:49:52 +00:00
|
|
|
totalsize += db_dir_size(pathname);
|
|
|
|
|
|
|
|
totalsize += fst.st_size;
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FreeDir(dirdesc);
|
2005-10-15 02:49:52 +00:00
|
|
|
|
2005-08-02 14:07:27 +00:00
|
|
|
return totalsize;
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
pg_tablespace_size_oid(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2005-10-15 02:49:52 +00:00
|
|
|
Oid tblspcOid = PG_GETARG_OID(0);
|
2012-01-19 13:06:30 +02:00
|
|
|
int64 size;
|
|
|
|
|
|
|
|
size = calculate_tablespace_size(tblspcOid);
|
2005-10-15 02:49:52 +00:00
|
|
|
|
2012-01-19 13:06:30 +02:00
|
|
|
if (size < 0)
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
|
|
|
|
PG_RETURN_INT64(size);
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
pg_tablespace_size_name(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2005-10-15 02:49:52 +00:00
|
|
|
Name tblspcName = PG_GETARG_NAME(0);
|
2010-08-05 14:45:09 +00:00
|
|
|
Oid tblspcOid = get_tablespace_oid(NameStr(*tblspcName), false);
|
2012-01-19 13:06:30 +02:00
|
|
|
int64 size;
|
|
|
|
|
|
|
|
size = calculate_tablespace_size(tblspcOid);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2012-01-19 13:06:30 +02:00
|
|
|
if (size < 0)
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
|
|
|
|
PG_RETURN_INT64(size);
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2010-01-19 05:50:18 +00:00
|
|
|
* calculate size of (one fork of) a relation
|
2012-12-17 20:15:32 -05:00
|
|
|
*
|
|
|
|
* Note: we can safely apply this to temp tables of other sessions, so there
|
|
|
|
* is no check here or at the call sites for that.
|
2005-08-02 14:07:27 +00:00
|
|
|
*/
|
|
|
|
static int64
|
2010-08-13 20:10:54 +00:00
|
|
|
calculate_relation_size(RelFileNode *rfn, BackendId backend, ForkNumber forknum)
|
2005-08-02 14:07:27 +00:00
|
|
|
{
|
2005-09-29 22:04:36 +00:00
|
|
|
int64 totalsize = 0;
|
2006-11-06 03:06:41 +00:00
|
|
|
char *relationpath;
|
2005-09-29 22:04:36 +00:00
|
|
|
char pathname[MAXPGPATH];
|
|
|
|
unsigned int segcount = 0;
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2010-08-13 20:10:54 +00:00
|
|
|
relationpath = relpathbackend(*rfn, backend, forknum);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2005-10-15 02:49:52 +00:00
|
|
|
for (segcount = 0;; segcount++)
|
2005-08-02 14:07:27 +00:00
|
|
|
{
|
|
|
|
struct stat fst;
|
|
|
|
|
2010-01-23 21:29:00 +00:00
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
|
2005-08-02 14:07:27 +00:00
|
|
|
if (segcount == 0)
|
2006-11-06 03:06:41 +00:00
|
|
|
snprintf(pathname, MAXPGPATH, "%s",
|
|
|
|
relationpath);
|
2005-08-02 14:07:27 +00:00
|
|
|
else
|
2006-11-06 03:06:41 +00:00
|
|
|
snprintf(pathname, MAXPGPATH, "%s.%u",
|
|
|
|
relationpath, segcount);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
|
|
|
if (stat(pathname, &fst) < 0)
|
|
|
|
{
|
|
|
|
if (errno == ENOENT)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode_for_file_access(),
|
2005-10-29 00:31:52 +00:00
|
|
|
errmsg("could not stat file \"%s\": %m", pathname)));
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
|
|
|
totalsize += fst.st_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
return totalsize;
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
2008-10-03 07:33:10 +00:00
|
|
|
pg_relation_size(PG_FUNCTION_ARGS)
|
2005-08-02 14:07:27 +00:00
|
|
|
{
|
2008-10-03 07:33:10 +00:00
|
|
|
Oid relOid = PG_GETARG_OID(0);
|
2017-03-12 19:35:34 -04:00
|
|
|
text *forkName = PG_GETARG_TEXT_PP(1);
|
2005-09-29 22:04:36 +00:00
|
|
|
Relation rel;
|
|
|
|
int64 size;
|
2005-10-15 02:49:52 +00:00
|
|
|
|
2012-01-19 13:06:30 +02:00
|
|
|
rel = try_relation_open(relOid, AccessShareLock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Before 9.2, we used to throw an error if the relation didn't exist, but
|
|
|
|
* that makes queries like "SELECT pg_relation_size(oid) FROM pg_class"
|
|
|
|
* less robust, because while we scan pg_class with an MVCC snapshot,
|
|
|
|
* someone else might drop the table. It's better to return NULL for
|
2012-12-17 20:15:32 -05:00
|
|
|
* already-dropped tables than throw an error and abort the whole query.
|
2012-01-19 13:06:30 +02:00
|
|
|
*/
|
|
|
|
if (rel == NULL)
|
|
|
|
PG_RETURN_NULL();
|
2005-10-15 02:49:52 +00:00
|
|
|
|
2010-08-13 20:10:54 +00:00
|
|
|
size = calculate_relation_size(&(rel->rd_node), rel->rd_backend,
|
2009-06-11 14:49:15 +00:00
|
|
|
forkname_to_number(text_to_cstring(forkName)));
|
2005-10-15 02:49:52 +00:00
|
|
|
|
2005-09-29 22:04:36 +00:00
|
|
|
relation_close(rel, AccessShareLock);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2005-09-29 22:04:36 +00:00
|
|
|
PG_RETURN_INT64(size);
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
|
|
|
|
2010-01-19 05:50:18 +00:00
|
|
|
/*
|
2013-07-04 03:24:09 +09:00
|
|
|
* Calculate total on-disk size of a TOAST relation, including its indexes.
|
2010-01-19 05:50:18 +00:00
|
|
|
* Must not be applied to non-TOAST relations.
|
|
|
|
*/
|
|
|
|
static int64
|
|
|
|
calculate_toast_table_size(Oid toastrelid)
|
|
|
|
{
|
2010-02-26 02:01:40 +00:00
|
|
|
int64 size = 0;
|
|
|
|
Relation toastRel;
|
|
|
|
ForkNumber forkNum;
|
2013-07-04 03:24:09 +09:00
|
|
|
ListCell *lc;
|
|
|
|
List *indexlist;
|
2010-01-19 05:50:18 +00:00
|
|
|
|
|
|
|
toastRel = relation_open(toastrelid, AccessShareLock);
|
|
|
|
|
|
|
|
/* toast heap size, including FSM and VM size */
|
|
|
|
for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
|
2010-08-13 20:10:54 +00:00
|
|
|
size += calculate_relation_size(&(toastRel->rd_node),
|
|
|
|
toastRel->rd_backend, forkNum);
|
2010-01-19 05:50:18 +00:00
|
|
|
|
|
|
|
/* toast index size, including FSM and VM size */
|
2013-07-04 03:24:09 +09:00
|
|
|
indexlist = RelationGetIndexList(toastRel);
|
2010-01-19 05:50:18 +00:00
|
|
|
|
2013-07-04 03:24:09 +09:00
|
|
|
/* Size is calculated using all the indexes available */
|
|
|
|
foreach(lc, indexlist)
|
|
|
|
{
|
|
|
|
Relation toastIdxRel;
|
2014-05-06 12:12:18 -04:00
|
|
|
|
2013-07-04 03:24:09 +09:00
|
|
|
toastIdxRel = relation_open(lfirst_oid(lc),
|
|
|
|
AccessShareLock);
|
|
|
|
for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
|
|
|
|
size += calculate_relation_size(&(toastIdxRel->rd_node),
|
|
|
|
toastIdxRel->rd_backend, forkNum);
|
|
|
|
|
|
|
|
relation_close(toastIdxRel, AccessShareLock);
|
|
|
|
}
|
|
|
|
list_free(indexlist);
|
2010-01-19 05:50:18 +00:00
|
|
|
relation_close(toastRel, AccessShareLock);
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
2005-08-02 14:07:27 +00:00
|
|
|
|
|
|
|
/*
|
2010-01-19 05:50:18 +00:00
|
|
|
* Calculate total on-disk size of a given table,
|
|
|
|
* including FSM and VM, plus TOAST table if any.
|
|
|
|
* Indexes other than the TOAST table's index are not included.
|
|
|
|
*
|
|
|
|
* Note that this also behaves sanely if applied to an index or toast table;
|
|
|
|
* those won't have attached toast tables, but they can have multiple forks.
|
2005-08-02 14:07:27 +00:00
|
|
|
*/
|
|
|
|
static int64
|
2012-01-19 13:06:30 +02:00
|
|
|
calculate_table_size(Relation rel)
|
2005-08-02 14:07:27 +00:00
|
|
|
{
|
2010-02-26 02:01:40 +00:00
|
|
|
int64 size = 0;
|
|
|
|
ForkNumber forkNum;
|
2005-09-29 22:04:36 +00:00
|
|
|
|
2010-01-19 05:50:18 +00:00
|
|
|
/*
|
|
|
|
* heap size, including FSM and VM
|
|
|
|
*/
|
2008-10-03 07:33:10 +00:00
|
|
|
for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
|
2010-08-13 20:10:54 +00:00
|
|
|
size += calculate_relation_size(&(rel->rd_node), rel->rd_backend,
|
|
|
|
forkNum);
|
2010-01-19 05:50:18 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Size of toast relation
|
|
|
|
*/
|
|
|
|
if (OidIsValid(rel->rd_rel->reltoastrelid))
|
|
|
|
size += calculate_toast_table_size(rel->rd_rel->reltoastrelid);
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate total on-disk size of all indexes attached to the given table.
|
|
|
|
*
|
|
|
|
* Can be applied safely to an index, but you'll just get zero.
|
|
|
|
*/
|
|
|
|
static int64
|
2012-01-19 13:06:30 +02:00
|
|
|
calculate_indexes_size(Relation rel)
|
2010-01-19 05:50:18 +00:00
|
|
|
{
|
2010-02-26 02:01:40 +00:00
|
|
|
int64 size = 0;
|
2010-01-19 05:50:18 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Aggregate all indexes on the given relation
|
|
|
|
*/
|
|
|
|
if (rel->rd_rel->relhasindex)
|
2005-09-16 05:35:41 +00:00
|
|
|
{
|
2010-02-26 02:01:40 +00:00
|
|
|
List *index_oids = RelationGetIndexList(rel);
|
|
|
|
ListCell *cell;
|
2005-09-16 05:35:41 +00:00
|
|
|
|
2005-09-29 22:04:36 +00:00
|
|
|
foreach(cell, index_oids)
|
2005-09-16 05:35:41 +00:00
|
|
|
{
|
2005-09-29 22:04:36 +00:00
|
|
|
Oid idxOid = lfirst_oid(cell);
|
2010-01-19 05:50:18 +00:00
|
|
|
Relation idxRel;
|
2010-02-26 02:01:40 +00:00
|
|
|
ForkNumber forkNum;
|
2005-09-29 22:04:36 +00:00
|
|
|
|
2010-01-19 05:50:18 +00:00
|
|
|
idxRel = relation_open(idxOid, AccessShareLock);
|
2005-09-29 22:04:36 +00:00
|
|
|
|
2008-10-03 07:33:10 +00:00
|
|
|
for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)
|
2010-08-13 20:10:54 +00:00
|
|
|
size += calculate_relation_size(&(idxRel->rd_node),
|
|
|
|
idxRel->rd_backend,
|
|
|
|
forkNum);
|
2005-09-29 22:04:36 +00:00
|
|
|
|
2010-01-19 05:50:18 +00:00
|
|
|
relation_close(idxRel, AccessShareLock);
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
2005-09-29 22:04:36 +00:00
|
|
|
|
|
|
|
list_free(index_oids);
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
2005-09-16 05:35:41 +00:00
|
|
|
|
2010-01-19 05:50:18 +00:00
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
pg_table_size(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
Oid relOid = PG_GETARG_OID(0);
|
2012-01-19 13:06:30 +02:00
|
|
|
Relation rel;
|
|
|
|
int64 size;
|
|
|
|
|
|
|
|
rel = try_relation_open(relOid, AccessShareLock);
|
|
|
|
|
|
|
|
if (rel == NULL)
|
|
|
|
PG_RETURN_NULL();
|
2010-01-19 05:50:18 +00:00
|
|
|
|
2012-01-19 13:06:30 +02:00
|
|
|
size = calculate_table_size(rel);
|
|
|
|
|
|
|
|
relation_close(rel, AccessShareLock);
|
|
|
|
|
|
|
|
PG_RETURN_INT64(size);
|
2010-01-19 05:50:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
pg_indexes_size(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
Oid relOid = PG_GETARG_OID(0);
|
2012-01-19 13:06:30 +02:00
|
|
|
Relation rel;
|
|
|
|
int64 size;
|
|
|
|
|
|
|
|
rel = try_relation_open(relOid, AccessShareLock);
|
2010-01-19 05:50:18 +00:00
|
|
|
|
2012-01-19 13:06:30 +02:00
|
|
|
if (rel == NULL)
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
|
|
|
|
size = calculate_indexes_size(rel);
|
|
|
|
|
|
|
|
relation_close(rel, AccessShareLock);
|
|
|
|
|
|
|
|
PG_RETURN_INT64(size);
|
2010-01-19 05:50:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compute the on-disk size of all files for the relation,
|
|
|
|
* including heap data, index data, toast data, FSM, VM.
|
|
|
|
*/
|
|
|
|
static int64
|
2012-01-19 13:06:30 +02:00
|
|
|
calculate_total_relation_size(Relation rel)
|
2010-01-19 05:50:18 +00:00
|
|
|
{
|
|
|
|
int64 size;
|
|
|
|
|
|
|
|
/*
|
2010-02-26 02:01:40 +00:00
|
|
|
* Aggregate the table size, this includes size of the heap, toast and
|
|
|
|
* toast index with free space and visibility map
|
2010-01-19 05:50:18 +00:00
|
|
|
*/
|
2012-01-19 13:06:30 +02:00
|
|
|
size = calculate_table_size(rel);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2010-01-19 05:50:18 +00:00
|
|
|
/*
|
|
|
|
* Add size of all attached indexes as well
|
|
|
|
*/
|
2012-01-19 13:06:30 +02:00
|
|
|
size += calculate_indexes_size(rel);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
2008-10-03 07:33:10 +00:00
|
|
|
pg_total_relation_size(PG_FUNCTION_ARGS)
|
2005-08-02 14:07:27 +00:00
|
|
|
{
|
2012-01-19 13:06:30 +02:00
|
|
|
Oid relOid = PG_GETARG_OID(0);
|
|
|
|
Relation rel;
|
|
|
|
int64 size;
|
|
|
|
|
|
|
|
rel = try_relation_open(relOid, AccessShareLock);
|
|
|
|
|
|
|
|
if (rel == NULL)
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
|
|
|
|
size = calculate_total_relation_size(rel);
|
2005-08-02 14:07:27 +00:00
|
|
|
|
2012-01-19 13:06:30 +02:00
|
|
|
relation_close(rel, AccessShareLock);
|
|
|
|
|
|
|
|
PG_RETURN_INT64(size);
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* formatting with size units
|
|
|
|
*/
|
|
|
|
Datum
|
|
|
|
pg_size_pretty(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2005-10-15 02:49:52 +00:00
|
|
|
int64 size = PG_GETARG_INT64(0);
|
2008-03-25 22:42:46 +00:00
|
|
|
char buf[64];
|
2005-10-15 02:49:52 +00:00
|
|
|
int64 limit = 10 * 1024;
|
2011-04-29 01:44:50 -04:00
|
|
|
int64 limit2 = limit * 2 - 1;
|
2005-09-29 22:04:36 +00:00
|
|
|
|
2015-11-06 11:03:02 -05:00
|
|
|
if (Abs(size) < limit)
|
2008-03-25 22:42:46 +00:00
|
|
|
snprintf(buf, sizeof(buf), INT64_FORMAT " bytes", size);
|
2005-08-02 14:07:27 +00:00
|
|
|
else
|
|
|
|
{
|
2011-04-29 01:44:50 -04:00
|
|
|
size >>= 9; /* keep one extra bit for rounding */
|
2015-11-06 11:03:02 -05:00
|
|
|
if (Abs(size) < limit2)
|
2008-03-25 22:42:46 +00:00
|
|
|
snprintf(buf, sizeof(buf), INT64_FORMAT " kB",
|
2015-11-06 11:03:02 -05:00
|
|
|
half_rounded(size));
|
2005-08-02 14:07:27 +00:00
|
|
|
else
|
|
|
|
{
|
2011-04-29 01:44:50 -04:00
|
|
|
size >>= 10;
|
2015-11-06 11:03:02 -05:00
|
|
|
if (Abs(size) < limit2)
|
2008-03-25 22:42:46 +00:00
|
|
|
snprintf(buf, sizeof(buf), INT64_FORMAT " MB",
|
2015-11-06 11:03:02 -05:00
|
|
|
half_rounded(size));
|
2005-08-02 14:07:27 +00:00
|
|
|
else
|
|
|
|
{
|
2011-04-29 01:44:50 -04:00
|
|
|
size >>= 10;
|
2015-11-06 11:03:02 -05:00
|
|
|
if (Abs(size) < limit2)
|
2008-03-25 22:42:46 +00:00
|
|
|
snprintf(buf, sizeof(buf), INT64_FORMAT " GB",
|
2015-11-06 11:03:02 -05:00
|
|
|
half_rounded(size));
|
2005-08-02 14:07:27 +00:00
|
|
|
else
|
|
|
|
{
|
2011-04-29 01:44:50 -04:00
|
|
|
size >>= 10;
|
2008-03-25 22:42:46 +00:00
|
|
|
snprintf(buf, sizeof(buf), INT64_FORMAT " TB",
|
2015-11-06 11:03:02 -05:00
|
|
|
half_rounded(size));
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-03-25 22:42:46 +00:00
|
|
|
PG_RETURN_TEXT_P(cstring_to_text(buf));
|
2005-08-02 14:07:27 +00:00
|
|
|
}
|
2010-02-07 20:48:13 +00:00
|
|
|
|
2012-04-14 08:04:11 -04:00
|
|
|
static char *
|
|
|
|
numeric_to_cstring(Numeric n)
|
|
|
|
{
|
|
|
|
Datum d = NumericGetDatum(n);
|
2012-06-10 15:20:04 -04:00
|
|
|
|
2012-04-14 08:04:11 -04:00
|
|
|
return DatumGetCString(DirectFunctionCall1(numeric_out, d));
|
|
|
|
}
|
|
|
|
|
|
|
|
static Numeric
|
|
|
|
int64_to_numeric(int64 v)
|
|
|
|
{
|
|
|
|
Datum d = Int64GetDatum(v);
|
2012-06-10 15:20:04 -04:00
|
|
|
|
2012-04-14 08:04:11 -04:00
|
|
|
return DatumGetNumeric(DirectFunctionCall1(int8_numeric, d));
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
numeric_is_less(Numeric a, Numeric b)
|
|
|
|
{
|
|
|
|
Datum da = NumericGetDatum(a);
|
|
|
|
Datum db = NumericGetDatum(b);
|
|
|
|
|
|
|
|
return DatumGetBool(DirectFunctionCall2(numeric_lt, da, db));
|
|
|
|
}
|
|
|
|
|
|
|
|
static Numeric
|
2015-11-06 11:03:02 -05:00
|
|
|
numeric_absolute(Numeric n)
|
2012-04-14 08:04:11 -04:00
|
|
|
{
|
|
|
|
Datum d = NumericGetDatum(n);
|
2015-11-06 11:03:02 -05:00
|
|
|
Datum result;
|
|
|
|
|
|
|
|
result = DirectFunctionCall1(numeric_abs, d);
|
|
|
|
return DatumGetNumeric(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Numeric
|
|
|
|
numeric_half_rounded(Numeric n)
|
|
|
|
{
|
|
|
|
Datum d = NumericGetDatum(n);
|
|
|
|
Datum zero;
|
2012-04-14 08:04:11 -04:00
|
|
|
Datum one;
|
|
|
|
Datum two;
|
|
|
|
Datum result;
|
|
|
|
|
2015-11-06 11:03:02 -05:00
|
|
|
zero = DirectFunctionCall1(int8_numeric, Int64GetDatum(0));
|
2012-04-14 08:04:11 -04:00
|
|
|
one = DirectFunctionCall1(int8_numeric, Int64GetDatum(1));
|
|
|
|
two = DirectFunctionCall1(int8_numeric, Int64GetDatum(2));
|
2015-11-06 11:03:02 -05:00
|
|
|
|
|
|
|
if (DatumGetBool(DirectFunctionCall2(numeric_ge, d, zero)))
|
|
|
|
d = DirectFunctionCall2(numeric_add, d, one);
|
|
|
|
else
|
|
|
|
d = DirectFunctionCall2(numeric_sub, d, one);
|
|
|
|
|
|
|
|
result = DirectFunctionCall2(numeric_div_trunc, d, two);
|
2012-04-14 08:04:11 -04:00
|
|
|
return DatumGetNumeric(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Numeric
|
|
|
|
numeric_shift_right(Numeric n, unsigned count)
|
|
|
|
{
|
|
|
|
Datum d = NumericGetDatum(n);
|
|
|
|
Datum divisor_int64;
|
|
|
|
Datum divisor_numeric;
|
|
|
|
Datum result;
|
|
|
|
|
|
|
|
divisor_int64 = Int64GetDatum((int64) (1 << count));
|
|
|
|
divisor_numeric = DirectFunctionCall1(int8_numeric, divisor_int64);
|
|
|
|
result = DirectFunctionCall2(numeric_div_trunc, d, divisor_numeric);
|
|
|
|
return DatumGetNumeric(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
pg_size_pretty_numeric(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
Numeric size = PG_GETARG_NUMERIC(0);
|
|
|
|
Numeric limit,
|
|
|
|
limit2;
|
2013-10-13 00:09:18 -04:00
|
|
|
char *result;
|
2012-04-14 08:04:11 -04:00
|
|
|
|
|
|
|
limit = int64_to_numeric(10 * 1024);
|
|
|
|
limit2 = int64_to_numeric(10 * 1024 * 2 - 1);
|
|
|
|
|
2015-11-06 11:03:02 -05:00
|
|
|
if (numeric_is_less(numeric_absolute(size), limit))
|
2012-04-14 08:04:11 -04:00
|
|
|
{
|
2013-10-13 00:09:18 -04:00
|
|
|
result = psprintf("%s bytes", numeric_to_cstring(size));
|
2012-04-14 08:04:11 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* keep one extra bit for rounding */
|
|
|
|
/* size >>= 9 */
|
|
|
|
size = numeric_shift_right(size, 9);
|
|
|
|
|
2015-11-06 11:03:02 -05:00
|
|
|
if (numeric_is_less(numeric_absolute(size), limit2))
|
2012-04-14 08:04:11 -04:00
|
|
|
{
|
2015-11-06 11:03:02 -05:00
|
|
|
size = numeric_half_rounded(size);
|
2013-10-13 00:09:18 -04:00
|
|
|
result = psprintf("%s kB", numeric_to_cstring(size));
|
2012-04-14 08:04:11 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* size >>= 10 */
|
|
|
|
size = numeric_shift_right(size, 10);
|
2015-11-06 11:03:02 -05:00
|
|
|
if (numeric_is_less(numeric_absolute(size), limit2))
|
2012-04-14 08:04:11 -04:00
|
|
|
{
|
2015-11-06 11:03:02 -05:00
|
|
|
size = numeric_half_rounded(size);
|
2013-10-13 00:09:18 -04:00
|
|
|
result = psprintf("%s MB", numeric_to_cstring(size));
|
2012-04-14 08:04:11 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* size >>= 10 */
|
|
|
|
size = numeric_shift_right(size, 10);
|
|
|
|
|
2015-11-06 11:03:02 -05:00
|
|
|
if (numeric_is_less(numeric_absolute(size), limit2))
|
2012-04-14 08:04:11 -04:00
|
|
|
{
|
2015-11-06 11:03:02 -05:00
|
|
|
size = numeric_half_rounded(size);
|
2013-10-13 00:09:18 -04:00
|
|
|
result = psprintf("%s GB", numeric_to_cstring(size));
|
2012-04-14 08:04:11 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* size >>= 10 */
|
|
|
|
size = numeric_shift_right(size, 10);
|
2015-11-06 11:03:02 -05:00
|
|
|
size = numeric_half_rounded(size);
|
2013-10-13 00:09:18 -04:00
|
|
|
result = psprintf("%s TB", numeric_to_cstring(size));
|
2012-04-14 08:04:11 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PG_RETURN_TEXT_P(cstring_to_text(result));
|
|
|
|
}
|
|
|
|
|
2016-02-20 09:57:27 +00:00
|
|
|
/*
|
|
|
|
* Convert a human-readable size to a size in bytes
|
|
|
|
*/
|
|
|
|
Datum
|
|
|
|
pg_size_bytes(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
text *arg = PG_GETARG_TEXT_PP(0);
|
|
|
|
char *str,
|
|
|
|
*strptr,
|
|
|
|
*endptr;
|
|
|
|
char saved_char;
|
|
|
|
Numeric num;
|
|
|
|
int64 result;
|
|
|
|
bool have_digits = false;
|
|
|
|
|
|
|
|
str = text_to_cstring(arg);
|
|
|
|
|
|
|
|
/* Skip leading whitespace */
|
|
|
|
strptr = str;
|
|
|
|
while (isspace((unsigned char) *strptr))
|
|
|
|
strptr++;
|
|
|
|
|
|
|
|
/* Check that we have a valid number and determine where it ends */
|
|
|
|
endptr = strptr;
|
|
|
|
|
|
|
|
/* Part (1): sign */
|
|
|
|
if (*endptr == '-' || *endptr == '+')
|
|
|
|
endptr++;
|
|
|
|
|
|
|
|
/* Part (2): main digit string */
|
|
|
|
if (isdigit((unsigned char) *endptr))
|
|
|
|
{
|
|
|
|
have_digits = true;
|
|
|
|
do
|
|
|
|
endptr++;
|
|
|
|
while (isdigit((unsigned char) *endptr));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Part (3): optional decimal point and fractional digits */
|
|
|
|
if (*endptr == '.')
|
|
|
|
{
|
|
|
|
endptr++;
|
|
|
|
if (isdigit((unsigned char) *endptr))
|
|
|
|
{
|
|
|
|
have_digits = true;
|
|
|
|
do
|
|
|
|
endptr++;
|
|
|
|
while (isdigit((unsigned char) *endptr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Complain if we don't have a valid number at this point */
|
|
|
|
if (!have_digits)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
|
|
errmsg("invalid size: \"%s\"", str)));
|
|
|
|
|
|
|
|
/* Part (4): optional exponent */
|
|
|
|
if (*endptr == 'e' || *endptr == 'E')
|
|
|
|
{
|
2016-08-16 16:14:16 -04:00
|
|
|
long exponent;
|
2016-02-20 09:57:27 +00:00
|
|
|
char *cp;
|
|
|
|
|
|
|
|
/*
|
2016-08-14 15:06:01 -04:00
|
|
|
* Note we might one day support EB units, so if what follows 'E'
|
|
|
|
* isn't a number, just treat it all as a unit to be parsed.
|
2016-02-20 09:57:27 +00:00
|
|
|
*/
|
2016-08-16 16:14:16 -04:00
|
|
|
exponent = strtol(endptr + 1, &cp, 10);
|
|
|
|
(void) exponent; /* Silence -Wunused-result warnings */
|
2016-02-20 09:57:27 +00:00
|
|
|
if (cp > endptr + 1)
|
|
|
|
endptr = cp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parse the number, saving the next character, which may be the first
|
|
|
|
* character of the unit string.
|
|
|
|
*/
|
|
|
|
saved_char = *endptr;
|
|
|
|
*endptr = '\0';
|
|
|
|
|
|
|
|
num = DatumGetNumeric(DirectFunctionCall3(numeric_in,
|
|
|
|
CStringGetDatum(strptr),
|
|
|
|
ObjectIdGetDatum(InvalidOid),
|
|
|
|
Int32GetDatum(-1)));
|
|
|
|
|
|
|
|
*endptr = saved_char;
|
|
|
|
|
|
|
|
/* Skip whitespace between number and unit */
|
|
|
|
strptr = endptr;
|
|
|
|
while (isspace((unsigned char) *strptr))
|
|
|
|
strptr++;
|
|
|
|
|
|
|
|
/* Handle possible unit */
|
|
|
|
if (*strptr != '\0')
|
|
|
|
{
|
|
|
|
int64 multiplier = 0;
|
|
|
|
|
|
|
|
/* Trim any trailing whitespace */
|
|
|
|
endptr = str + VARSIZE_ANY_EXHDR(arg) - 1;
|
|
|
|
|
|
|
|
while (isspace((unsigned char) *endptr))
|
|
|
|
endptr--;
|
|
|
|
|
|
|
|
endptr++;
|
|
|
|
*endptr = '\0';
|
|
|
|
|
|
|
|
/* Parse the unit case-insensitively */
|
|
|
|
if (pg_strcasecmp(strptr, "bytes") == 0)
|
2016-02-20 15:49:26 +00:00
|
|
|
multiplier = (int64) 1;
|
2016-02-20 09:57:27 +00:00
|
|
|
else if (pg_strcasecmp(strptr, "kb") == 0)
|
2016-02-20 15:49:26 +00:00
|
|
|
multiplier = (int64) 1024;
|
2016-02-20 09:57:27 +00:00
|
|
|
else if (pg_strcasecmp(strptr, "mb") == 0)
|
2016-05-03 10:52:25 -04:00
|
|
|
multiplier = ((int64) 1024) * 1024;
|
|
|
|
|
2016-02-20 09:57:27 +00:00
|
|
|
else if (pg_strcasecmp(strptr, "gb") == 0)
|
2016-05-03 10:52:25 -04:00
|
|
|
multiplier = ((int64) 1024) * 1024 * 1024;
|
|
|
|
|
2016-02-20 09:57:27 +00:00
|
|
|
else if (pg_strcasecmp(strptr, "tb") == 0)
|
2016-05-03 10:52:25 -04:00
|
|
|
multiplier = ((int64) 1024) * 1024 * 1024 * 1024;
|
|
|
|
|
2016-02-20 09:57:27 +00:00
|
|
|
else
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
|
|
errmsg("invalid size: \"%s\"", text_to_cstring(arg)),
|
|
|
|
errdetail("Invalid size unit: \"%s\".", strptr),
|
|
|
|
errhint("Valid units are \"bytes\", \"kB\", \"MB\", \"GB\", and \"TB\".")));
|
|
|
|
|
|
|
|
if (multiplier > 1)
|
|
|
|
{
|
|
|
|
Numeric mul_num;
|
|
|
|
|
|
|
|
mul_num = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
|
|
|
|
Int64GetDatum(multiplier)));
|
|
|
|
|
|
|
|
num = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
|
|
|
|
NumericGetDatum(mul_num),
|
|
|
|
NumericGetDatum(num)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result = DatumGetInt64(DirectFunctionCall1(numeric_int8,
|
|
|
|
NumericGetDatum(num)));
|
|
|
|
|
|
|
|
PG_RETURN_INT64(result);
|
|
|
|
}
|
|
|
|
|
2010-02-07 20:48:13 +00:00
|
|
|
/*
|
|
|
|
* Get the filenode of a relation
|
|
|
|
*
|
|
|
|
* This is expected to be used in queries like
|
|
|
|
* SELECT pg_relation_filenode(oid) FROM pg_class;
|
|
|
|
* That leads to a couple of choices. We work from the pg_class row alone
|
2014-05-06 12:12:18 -04:00
|
|
|
* rather than actually opening each relation, for efficiency. We don't
|
2010-02-07 20:48:13 +00:00
|
|
|
* fail if we can't find the relation --- some rows might be visible in
|
Use an MVCC snapshot, rather than SnapshotNow, for catalog scans.
SnapshotNow scans have the undesirable property that, in the face of
concurrent updates, the scan can fail to see either the old or the new
versions of the row. In many cases, we work around this by requiring
DDL operations to hold AccessExclusiveLock on the object being
modified; in some cases, the existing locking is inadequate and random
failures occur as a result. This commit doesn't change anything
related to locking, but will hopefully pave the way to allowing lock
strength reductions in the future.
The major issue has held us back from making this change in the past
is that taking an MVCC snapshot is significantly more expensive than
using a static special snapshot such as SnapshotNow. However, testing
of various worst-case scenarios reveals that this problem is not
severe except under fairly extreme workloads. To mitigate those
problems, we avoid retaking the MVCC snapshot for each new scan;
instead, we take a new snapshot only when invalidation messages have
been processed. The catcache machinery already requires that
invalidation messages be sent before releasing the related heavyweight
lock; else other backends might rely on locally-cached data rather
than scanning the catalog at all. Thus, making snapshot reuse
dependent on the same guarantees shouldn't break anything that wasn't
already subtly broken.
Patch by me. Review by Michael Paquier and Andres Freund.
2013-07-02 09:47:01 -04:00
|
|
|
* the query's MVCC snapshot even though the relations have been dropped.
|
2010-02-07 20:48:13 +00:00
|
|
|
* (Note: we could avoid using the catcache, but there's little point
|
|
|
|
* because the relation mapper also works "in the now".) We also don't
|
|
|
|
* fail if the relation doesn't have storage. In all these cases it
|
|
|
|
* seems better to quietly return NULL.
|
|
|
|
*/
|
|
|
|
Datum
|
|
|
|
pg_relation_filenode(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
Oid relid = PG_GETARG_OID(0);
|
|
|
|
Oid result;
|
|
|
|
HeapTuple tuple;
|
|
|
|
Form_pg_class relform;
|
|
|
|
|
2010-02-14 18:42:19 +00:00
|
|
|
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
|
2010-02-07 20:48:13 +00:00
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
relform = (Form_pg_class) GETSTRUCT(tuple);
|
|
|
|
|
|
|
|
switch (relform->relkind)
|
|
|
|
{
|
|
|
|
case RELKIND_RELATION:
|
2013-03-03 18:23:31 -06:00
|
|
|
case RELKIND_MATVIEW:
|
2010-02-07 20:48:13 +00:00
|
|
|
case RELKIND_INDEX:
|
|
|
|
case RELKIND_SEQUENCE:
|
|
|
|
case RELKIND_TOASTVALUE:
|
|
|
|
/* okay, these have storage */
|
|
|
|
if (relform->relfilenode)
|
|
|
|
result = relform->relfilenode;
|
2010-02-26 02:01:40 +00:00
|
|
|
else /* Consult the relation mapper */
|
2010-02-07 20:48:13 +00:00
|
|
|
result = RelationMapOidToFilenode(relid,
|
|
|
|
relform->relisshared);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/* no storage, return NULL */
|
|
|
|
result = InvalidOid;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
|
|
|
|
if (!OidIsValid(result))
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
|
|
|
|
PG_RETURN_OID(result);
|
|
|
|
}
|
|
|
|
|
2013-07-22 10:34:34 -04:00
|
|
|
/*
|
|
|
|
* Get the relation via (reltablespace, relfilenode)
|
|
|
|
*
|
|
|
|
* This is expected to be used when somebody wants to match an individual file
|
2013-07-22 13:15:13 -04:00
|
|
|
* on the filesystem back to its table. That's not trivially possible via
|
|
|
|
* pg_class, because that doesn't contain the relfilenodes of shared and nailed
|
2013-07-22 10:34:34 -04:00
|
|
|
* tables.
|
|
|
|
*
|
|
|
|
* We don't fail but return NULL if we cannot find a mapping.
|
|
|
|
*
|
2013-07-22 13:15:13 -04:00
|
|
|
* InvalidOid can be passed instead of the current database's default
|
|
|
|
* tablespace.
|
2013-07-22 10:34:34 -04:00
|
|
|
*/
|
|
|
|
Datum
|
|
|
|
pg_filenode_relation(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
Oid reltablespace = PG_GETARG_OID(0);
|
|
|
|
Oid relfilenode = PG_GETARG_OID(1);
|
|
|
|
Oid heaprel = InvalidOid;
|
|
|
|
|
|
|
|
heaprel = RelidByRelfilenode(reltablespace, relfilenode);
|
|
|
|
|
|
|
|
if (!OidIsValid(heaprel))
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
else
|
|
|
|
PG_RETURN_OID(heaprel);
|
|
|
|
}
|
|
|
|
|
2010-02-07 20:48:13 +00:00
|
|
|
/*
|
|
|
|
* Get the pathname (relative to $PGDATA) of a relation
|
|
|
|
*
|
|
|
|
* See comments for pg_relation_filenode.
|
|
|
|
*/
|
|
|
|
Datum
|
|
|
|
pg_relation_filepath(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
Oid relid = PG_GETARG_OID(0);
|
|
|
|
HeapTuple tuple;
|
|
|
|
Form_pg_class relform;
|
|
|
|
RelFileNode rnode;
|
2010-08-13 20:10:54 +00:00
|
|
|
BackendId backend;
|
2010-02-07 20:48:13 +00:00
|
|
|
char *path;
|
|
|
|
|
2010-02-14 18:42:19 +00:00
|
|
|
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
|
2010-02-07 20:48:13 +00:00
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
|
|
PG_RETURN_NULL();
|
|
|
|
relform = (Form_pg_class) GETSTRUCT(tuple);
|
|
|
|
|
|
|
|
switch (relform->relkind)
|
|
|
|
{
|
|
|
|
case RELKIND_RELATION:
|
2013-03-03 18:23:31 -06:00
|
|
|
case RELKIND_MATVIEW:
|
2010-02-07 20:48:13 +00:00
|
|
|
case RELKIND_INDEX:
|
|
|
|
case RELKIND_SEQUENCE:
|
|
|
|
case RELKIND_TOASTVALUE:
|
|
|
|
/* okay, these have storage */
|
|
|
|
|
|
|
|
/* This logic should match RelationInitPhysicalAddr */
|
|
|
|
if (relform->reltablespace)
|
|
|
|
rnode.spcNode = relform->reltablespace;
|
|
|
|
else
|
|
|
|
rnode.spcNode = MyDatabaseTableSpace;
|
|
|
|
if (rnode.spcNode == GLOBALTABLESPACE_OID)
|
|
|
|
rnode.dbNode = InvalidOid;
|
|
|
|
else
|
|
|
|
rnode.dbNode = MyDatabaseId;
|
|
|
|
if (relform->relfilenode)
|
|
|
|
rnode.relNode = relform->relfilenode;
|
2010-02-26 02:01:40 +00:00
|
|
|
else /* Consult the relation mapper */
|
2010-02-07 20:48:13 +00:00
|
|
|
rnode.relNode = RelationMapOidToFilenode(relid,
|
2010-02-26 02:01:40 +00:00
|
|
|
relform->relisshared);
|
2010-02-07 20:48:13 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/* no storage, return NULL */
|
|
|
|
rnode.relNode = InvalidOid;
|
2011-02-17 16:00:23 -05:00
|
|
|
/* some compilers generate warnings without these next two lines */
|
|
|
|
rnode.dbNode = InvalidOid;
|
|
|
|
rnode.spcNode = InvalidOid;
|
2010-02-07 20:48:13 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!OidIsValid(rnode.relNode))
|
2010-08-13 20:10:54 +00:00
|
|
|
{
|
|
|
|
ReleaseSysCache(tuple);
|
2010-02-07 20:48:13 +00:00
|
|
|
PG_RETURN_NULL();
|
2010-08-13 20:10:54 +00:00
|
|
|
}
|
|
|
|
|
2010-12-13 12:34:26 -05:00
|
|
|
/* Determine owning backend. */
|
|
|
|
switch (relform->relpersistence)
|
2010-08-13 20:10:54 +00:00
|
|
|
{
|
2010-12-29 06:48:53 -05:00
|
|
|
case RELPERSISTENCE_UNLOGGED:
|
2010-12-13 12:34:26 -05:00
|
|
|
case RELPERSISTENCE_PERMANENT:
|
|
|
|
backend = InvalidBackendId;
|
|
|
|
break;
|
|
|
|
case RELPERSISTENCE_TEMP:
|
2014-08-25 21:28:19 -04:00
|
|
|
if (isTempOrTempToastNamespace(relform->relnamespace))
|
Improve the situation for parallel query versus temp relations.
Transmit the leader's temp-namespace state to workers. This is important
because without it, the workers do not really have the same search path
as the leader. For example, there is no good reason (and no extant code
either) to prevent a worker from executing a temp function that the
leader created previously; but as things stood it would fail to find the
temp function, and then either fail or execute the wrong function entirely.
We still prohibit a worker from creating a temp namespace on its own.
In effect, a worker can only see the session's temp namespace if the leader
had created it before starting the worker, which seems like the right
semantics.
Also, transmit the leader's BackendId to workers, and arrange for workers
to use that when determining the physical file path of a temp relation
belonging to their session. While the original intent was to prevent such
accesses entirely, there were a number of holes in that, notably in places
like dbsize.c which assume they can safely access temp rels of other
sessions anyway. We might as well get this right, as a small down payment
on someday allowing workers to access the leader's temp tables. (With
this change, directly using "MyBackendId" as a relation or buffer backend
ID is deprecated; you should use BackendIdForTempRelations() instead.
I left a couple of such uses alone though, as they're not going to be
reachable in parallel workers until we do something about localbuf.c.)
Move the thou-shalt-not-access-thy-leader's-temp-tables prohibition down
into localbuf.c, which is where it actually matters, instead of having it
in relation_open(). This amounts to recognizing that access to temp
tables' catalog entries is perfectly safe in a worker, it's only the data
in local buffers that is problematic.
Having done all that, we can get rid of the test in has_parallel_hazard()
that says that use of a temp table's rowtype is unsafe in parallel workers.
That test was unduly expensive, and if we really did need such a
prohibition, that was not even close to being a bulletproof guard for it.
(For example, any user-defined function executed in a parallel worker
might have attempted such access.)
2016-06-09 20:16:11 -04:00
|
|
|
backend = BackendIdForTempRelations();
|
2010-12-13 12:34:26 -05:00
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Do it the hard way. */
|
|
|
|
backend = GetTempNamespaceBackendId(relform->relnamespace);
|
|
|
|
Assert(backend != InvalidBackendId);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
|
2011-04-10 11:42:00 -04:00
|
|
|
backend = InvalidBackendId; /* placate compiler */
|
2010-12-13 12:34:26 -05:00
|
|
|
break;
|
2010-08-13 20:10:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ReleaseSysCache(tuple);
|
2010-02-07 20:48:13 +00:00
|
|
|
|
2010-08-13 20:10:54 +00:00
|
|
|
path = relpathbackend(rnode, backend, MAIN_FORKNUM);
|
2010-02-07 20:48:13 +00:00
|
|
|
|
|
|
|
PG_RETURN_TEXT_P(cstring_to_text(path));
|
|
|
|
}
|