forked from huawei/openGauss-server
1116 lines
42 KiB
C++
Executable File
1116 lines
42 KiB
C++
Executable File
/* -------------------------------------------------------------------------
|
|
*
|
|
* catalog.cpp
|
|
* routines concerned with catalog naming conventions and other
|
|
* bits of hard-wired knowledge
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/common/backend/catalog/catalog.cpp
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include "access/genam.h"
|
|
#include "access/sysattr.h"
|
|
#include "access/transam.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/gs_obsscaninfo.h"
|
|
#include "catalog/indexing.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/pg_auth_members.h"
|
|
#include "catalog/pg_authid.h"
|
|
#include "catalog/pg_database.h"
|
|
#include "catalog/pg_directory.h"
|
|
#include "catalog/pg_job.h"
|
|
#include "catalog/pg_job_proc.h"
|
|
#include "catalog/pg_namespace.h"
|
|
#include "catalog/pg_obsscaninfo.h"
|
|
#include "catalog/pg_pltemplate.h"
|
|
#include "catalog/pg_db_role_setting.h"
|
|
#include "catalog/pg_shdepend.h"
|
|
#include "catalog/pg_shdescription.h"
|
|
#include "catalog/pg_shseclabel.h"
|
|
#include "catalog/pg_tablespace.h"
|
|
#include "catalog/pg_auth_history.h"
|
|
#include "catalog/pg_user_status.h"
|
|
#include "catalog/toasting.h"
|
|
#include "catalog/pgxc_node.h"
|
|
#include "catalog/pgxc_group.h"
|
|
#include "catalog/pg_extension_data_source.h"
|
|
#include "catalog/pg_proc.h"
|
|
#include "commands/tablespace.h"
|
|
#include "commands/directory.h"
|
|
#include "cstore.h"
|
|
#include "storage/custorage.h"
|
|
#include "catalog/pg_resource_pool.h"
|
|
#include "catalog/pg_workload_group.h"
|
|
#include "catalog/pg_app_workloadgroup_mapping.h"
|
|
#include "miscadmin.h"
|
|
#include "storage/fd.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/rel_gs.h"
|
|
#include "utils/tqual.h"
|
|
#ifdef PGXC
|
|
#include "pgxc/pgxc.h"
|
|
#endif
|
|
|
|
#define atooid(x) ((Oid)strtoul((x), NULL, 10))
|
|
|
|
/*
|
|
* Lookup table of fork name by fork number.
|
|
*
|
|
* If you add a new entry, remember to update the errhint below, and the
|
|
* documentation for pg_relation_size(). Also keep FORKNAMECHARS above
|
|
* up-to-date.
|
|
*/
|
|
const char* forkNames[] = {
|
|
"main", /* MAIN_FORKNUM */
|
|
"fsm", /* FSM_FORKNUM */
|
|
"vm", /* VISIBILITYMAP_FORKNUM */
|
|
"bcm", /* BCM_FORKNUM */
|
|
"init" /* INIT_FORKNUM */
|
|
};
|
|
|
|
/*
|
|
* forkname_to_number - look up fork number by name
|
|
*/
|
|
ForkNumber forkname_to_number(char* forkName, BlockNumber* segno)
|
|
{
|
|
ForkNumber forkNum;
|
|
int iforkNum;
|
|
|
|
if (forkName == NULL || *forkName == '\0')
|
|
return InvalidForkNumber;
|
|
|
|
for (iforkNum = 0; iforkNum <= MAX_FORKNUM; iforkNum++) {
|
|
forkNum = (ForkNumber)iforkNum;
|
|
if (strcmp(forkNames[forkNum], forkName) == 0)
|
|
return forkNum;
|
|
}
|
|
|
|
/*
|
|
* if the iforkNum more than MAX_FORKNUM , the table is a column data file
|
|
* or a column data bcm file. example C1_bcm or C1_bcm.1. C1.0 is not in the
|
|
* database and the caller function has checked the file name in *_bcm format
|
|
*/
|
|
char* parsepath = forkName;
|
|
char* token = NULL;
|
|
char* tmptoken = NULL;
|
|
size_t parselen = strlen(parsepath);
|
|
|
|
token = strtok_r(parsepath, "_", &tmptoken);
|
|
Assert(token != NULL);
|
|
|
|
if (strlen(token) == parselen) {
|
|
/* it is a column data file. C1.0 */
|
|
char* subtoken = NULL;
|
|
char* tmpsubtoken = NULL;
|
|
|
|
subtoken = strtok_r(token, ".", &tmpsubtoken);
|
|
Assert(subtoken != NULL);
|
|
if (strlen(subtoken) == parselen) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid fork name"),
|
|
errhint("Valid fork names are \"main\", \"fsm\", \"bcm\", and \"vm\".")));
|
|
} else {
|
|
/* skip 'C' */
|
|
subtoken = subtoken + 1;
|
|
forkNum = ColumnId2ColForkNum(atooid(subtoken));
|
|
if (segno != NULL)
|
|
*segno = (BlockNumber)atooid(tmpsubtoken);
|
|
|
|
return forkNum;
|
|
}
|
|
} else {
|
|
/* it is a column data bcm file. C1_bcm */
|
|
/* skip 'C' */
|
|
token = token + 1;
|
|
forkNum = ColumnId2ColForkNum(atooid(token));
|
|
|
|
/* bcm.1 */
|
|
token = strtok_r(NULL, ".", &tmptoken);
|
|
|
|
if (segno != NULL) {
|
|
if (token != NULL)
|
|
*segno = (BlockNumber)atooid(tmptoken);
|
|
else
|
|
*segno = 0;
|
|
}
|
|
|
|
return forkNum;
|
|
}
|
|
|
|
return InvalidForkNumber; /* keep compiler quiet */
|
|
}
|
|
|
|
/*
|
|
* forkname_chars
|
|
* We use this to figure out whether a filename could be a relation
|
|
* fork (as opposed to an oddly named stray file that somehow ended
|
|
* up in the database directory). If the passed string begins with
|
|
* a fork name (other than the main fork name), we return its length,
|
|
* and set *fork (if not NULL) to the fork number. If not, we return 0.
|
|
*
|
|
* Note that the present coding assumes that there are no fork names which
|
|
* are prefixes of other fork names.
|
|
*/
|
|
int forkname_chars(const char* str, ForkNumber* fork)
|
|
{
|
|
ForkNumber forkNum;
|
|
int iforkNum;
|
|
for (iforkNum = 1; iforkNum <= MAX_FORKNUM; iforkNum++) {
|
|
forkNum = (ForkNumber)iforkNum;
|
|
int len = strlen(forkNames[forkNum]);
|
|
if (strncmp(forkNames[forkNum], str, len) == 0) {
|
|
if (fork != NULL)
|
|
*fork = forkNum;
|
|
return len;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* relpathbackend - construct path to a relation's file
|
|
*
|
|
* Result is a palloc'd string.
|
|
*/
|
|
char* relpathbackend(RelFileNode rnode, BackendId backend, ForkNumber forknum)
|
|
{
|
|
int pathlen;
|
|
char* path = NULL;
|
|
|
|
// Column store
|
|
// Future: fix this function.
|
|
if (IsValidColForkNum(forknum)) {
|
|
path = (char*)palloc0(MAXPGPATH * sizeof(char));
|
|
|
|
CFileNode cFileNode(rnode, ColForkNum2ColumnId(forknum), MAIN_FORKNUM);
|
|
CUStorage cuStorage(cFileNode);
|
|
cuStorage.GetBcmFileName(path, 0);
|
|
cuStorage.Destroy();
|
|
} else {
|
|
errno_t rc = EOK;
|
|
|
|
if (rnode.spcNode == GLOBALTABLESPACE_OID) {
|
|
/* Shared system relations live in {datadir}/global */
|
|
Assert(rnode.dbNode == 0);
|
|
Assert(rnode.bucketNode == InvalidBktId);
|
|
Assert(backend == InvalidBackendId);
|
|
pathlen = 7 + OIDCHARS + 1 + FORKNAMECHARS + 1;
|
|
path = (char*)palloc(pathlen);
|
|
if (forknum != MAIN_FORKNUM)
|
|
rc = snprintf_s(path, pathlen, pathlen - 1, "global/%u_%s", rnode.relNode, forkNames[forknum]);
|
|
else
|
|
rc = snprintf_s(path, pathlen, pathlen - 1, "global/%u", rnode.relNode);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
} else if (rnode.spcNode == DEFAULTTABLESPACE_OID) {
|
|
/* The default tablespace is {datadir}/base */
|
|
if (backend == InvalidBackendId) {
|
|
pathlen = 5 + OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS + 2;
|
|
path = (char*)palloc(pathlen);
|
|
if (forknum != MAIN_FORKNUM) {
|
|
if (rnode.bucketNode == InvalidBktId)
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"base/%u/%u_%s",
|
|
rnode.dbNode,
|
|
rnode.relNode,
|
|
forkNames[forknum]);
|
|
else
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"base/%u/%u_b%d_%s",
|
|
rnode.dbNode,
|
|
rnode.relNode,
|
|
rnode.bucketNode,
|
|
forkNames[forknum]);
|
|
} else {
|
|
if (rnode.bucketNode == InvalidBktId)
|
|
rc = snprintf_s(path, pathlen, pathlen - 1, "base/%u/%u", rnode.dbNode, rnode.relNode);
|
|
else
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"base/%u/%u_b%d",
|
|
rnode.dbNode,
|
|
rnode.relNode,
|
|
rnode.bucketNode);
|
|
}
|
|
securec_check_ss(rc, "\0", "\0");
|
|
} else {
|
|
/* OIDCHARS will suffice for an integer, too */
|
|
Assert(rnode.bucketNode == InvalidBktId);
|
|
pathlen = 5 + OIDCHARS + 2 + OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1;
|
|
path = (char*)palloc(pathlen);
|
|
if (forknum != MAIN_FORKNUM)
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"base/%u/t%d_%u_%s",
|
|
rnode.dbNode,
|
|
backend,
|
|
rnode.relNode,
|
|
forkNames[forknum]);
|
|
else
|
|
rc = snprintf_s(path, pathlen, pathlen - 1, "base/%u/t%d_%u", rnode.dbNode, backend, rnode.relNode);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
}
|
|
} else {
|
|
/* All other tablespaces are accessed via symlinks */
|
|
if (backend == InvalidBackendId) {
|
|
pathlen = 9 + 1 + OIDCHARS + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 + OIDCHARS +
|
|
1
|
|
#ifdef PGXC
|
|
/* Postgres-XC tablespaces include node name */
|
|
+ strlen(g_instance.attr.attr_common.PGXCNodeName) + 1
|
|
#endif
|
|
+ OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS + 2;
|
|
path = (char*)palloc(pathlen);
|
|
#ifdef PGXC
|
|
if (forknum != MAIN_FORKNUM) {
|
|
if (rnode.bucketNode == InvalidBktId)
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"pg_tblspc/%u/%s_%s/%u/%u_%s",
|
|
rnode.spcNode,
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
g_instance.attr.attr_common.PGXCNodeName,
|
|
rnode.dbNode,
|
|
rnode.relNode,
|
|
forkNames[forknum]);
|
|
else
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"pg_tblspc/%u/%s_%s/%u/%u_b%d_%s",
|
|
rnode.spcNode,
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
g_instance.attr.attr_common.PGXCNodeName,
|
|
rnode.dbNode,
|
|
rnode.relNode,
|
|
rnode.bucketNode,
|
|
forkNames[forknum]);
|
|
} else {
|
|
if (rnode.bucketNode == InvalidBktId)
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"pg_tblspc/%u/%s_%s/%u/%u",
|
|
rnode.spcNode,
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
g_instance.attr.attr_common.PGXCNodeName,
|
|
rnode.dbNode,
|
|
rnode.relNode);
|
|
else
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"pg_tblspc/%u/%s_%s/%u/%u_b%d",
|
|
rnode.spcNode,
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
g_instance.attr.attr_common.PGXCNodeName,
|
|
rnode.dbNode,
|
|
rnode.relNode,
|
|
rnode.bucketNode);
|
|
}
|
|
securec_check_ss(rc, "\0", "\0");
|
|
#else
|
|
if (forknum != MAIN_FORKNUM)
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"pg_tblspc/%u/%s/%u/%u_%s",
|
|
rnode.spcNode,
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
rnode.dbNode,
|
|
rnode.relNode,
|
|
forkNames[forknum]);
|
|
else
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"pg_tblspc/%u/%s/%u/%u",
|
|
rnode.spcNode,
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
rnode.dbNode,
|
|
rnode.relNode);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
#endif
|
|
} else {
|
|
/* OIDCHARS will suffice for an integer, too */
|
|
pathlen = 9 + 1 + OIDCHARS + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 + OIDCHARS + 2
|
|
#ifdef PGXC
|
|
+ strlen(g_instance.attr.attr_common.PGXCNodeName) + 1
|
|
#endif
|
|
+ OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1;
|
|
path = (char*)palloc(pathlen);
|
|
#ifdef PGXC
|
|
if (forknum != MAIN_FORKNUM)
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"pg_tblspc/%u/%s_%s/%u/t%d_%u_%s",
|
|
rnode.spcNode,
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
g_instance.attr.attr_common.PGXCNodeName,
|
|
rnode.dbNode,
|
|
backend,
|
|
rnode.relNode,
|
|
forkNames[forknum]);
|
|
else
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"pg_tblspc/%u/%s_%s/%u/t%d_%u",
|
|
rnode.spcNode,
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
g_instance.attr.attr_common.PGXCNodeName,
|
|
rnode.dbNode,
|
|
backend,
|
|
rnode.relNode);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
#else
|
|
if (forknum != MAIN_FORKNUM)
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"pg_tblspc/%u/%s/%u/t%d_%u_%s",
|
|
rnode.spcNode,
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
rnode.dbNode,
|
|
backend,
|
|
rnode.relNode,
|
|
forkNames[forknum]);
|
|
else
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"pg_tblspc/%u/%s/%u/t%d_%u",
|
|
rnode.spcNode,
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
rnode.dbNode,
|
|
backend,
|
|
rnode.relNode);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
/* parse relation path to relfilenode */
|
|
RelFileNodeForkNum relpath_to_filenode(char* path)
|
|
{
|
|
RelFileNodeForkNum filenode;
|
|
char* parsepath = NULL;
|
|
char* token = NULL;
|
|
char* tmptoken = NULL;
|
|
|
|
parsepath = pstrdup(path);
|
|
token = strtok_r(parsepath, "/", &tmptoken);
|
|
|
|
if (tmptoken == NULL || *tmptoken == '\0') {
|
|
pfree(parsepath);
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid relation file path %s.", path)));
|
|
}
|
|
|
|
errno_t rc = memset_s(&filenode, sizeof(RelFileNodeForkNum), 0, sizeof(RelFileNodeForkNum));
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
if (0 == strncmp(token, "global", 7)) {
|
|
filenode.rnode.node.spcNode = GLOBALTABLESPACE_OID;
|
|
filenode.rnode.node.dbNode = 0;
|
|
filenode.rnode.node.bucketNode = InvalidBktId;
|
|
filenode.rnode.backend = InvalidBackendId;
|
|
|
|
token = strtok_r(NULL, "_", &tmptoken);
|
|
if (*tmptoken == '\0') {
|
|
/* global/relNode */
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.forknumber = MAIN_FORKNUM;
|
|
} else {
|
|
/* global/relNode_forkName */
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.forknumber = forkname_to_number(tmptoken, &filenode.segno);
|
|
}
|
|
} else if (0 == strncmp(token, "base", 5)) {
|
|
filenode.rnode.node.spcNode = DEFAULTTABLESPACE_OID;
|
|
|
|
token = strtok_r(NULL, "/", &tmptoken);
|
|
Assert(token != NULL);
|
|
if (unlikely(*tmptoken == '\0')) {
|
|
pfree(parsepath);
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid relation file path %s.", path)));
|
|
}
|
|
filenode.rnode.node.dbNode = atooid(token);
|
|
|
|
if (*tmptoken != 't') {
|
|
token = strtok_r(NULL, "_", &tmptoken);
|
|
if (*tmptoken == '\0') {
|
|
/* base/dbNode/relNode */
|
|
filenode.rnode.node.bucketNode = InvalidBktId;
|
|
filenode.forknumber = MAIN_FORKNUM;
|
|
filenode.rnode.backend = InvalidBackendId;
|
|
token = strtok_r(token, ".", &tmptoken);
|
|
if (*tmptoken == '\0') {
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.segno = 0;
|
|
} else {
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.segno = (BlockNumber)atooid(tmptoken);
|
|
}
|
|
} else {
|
|
/* base/dbNode/relNode_forkName */
|
|
filenode.rnode.backend = InvalidBackendId;
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.rnode.node.bucketNode = InvalidBktId;
|
|
/* avoid _bcm */
|
|
if (*tmptoken == 'b' && *(tmptoken + 1) != 'c') {
|
|
tmptoken = tmptoken + 1;
|
|
filenode.rnode.node.bucketNode = (int2)atooid(tmptoken);
|
|
token = strtok_r(NULL, "_", &tmptoken);
|
|
if (*tmptoken == '\0') {
|
|
filenode.forknumber = MAIN_FORKNUM;
|
|
token = strtok_r(token, ".", &tmptoken);
|
|
if (*tmptoken != '\0') {
|
|
filenode.segno = (BlockNumber)atooid(tmptoken);
|
|
}
|
|
} else {
|
|
filenode.forknumber = forkname_to_number(tmptoken, NULL);
|
|
}
|
|
} else {
|
|
filenode.forknumber = forkname_to_number(tmptoken, &filenode.segno);
|
|
}
|
|
}
|
|
} else {
|
|
tmptoken = tmptoken + 1; /* skip 't' */
|
|
|
|
token = strtok_r(NULL, "_", &tmptoken);
|
|
Assert(token != NULL);
|
|
filenode.rnode.backend = atoi(token);
|
|
token = strtok_r(NULL, "_", &tmptoken);
|
|
if (*tmptoken == '\0') {
|
|
/* base/dbNode/tbackendId_relNode */
|
|
filenode.forknumber = MAIN_FORKNUM;
|
|
token = strtok_r(token, ".", &tmptoken);
|
|
if (*tmptoken == '\0') {
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.segno = 0;
|
|
} else {
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.segno = (BlockNumber)atooid(tmptoken);
|
|
}
|
|
} else {
|
|
/* base/dbNode/tbackendId_relNode_forkName */
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.forknumber = forkname_to_number(tmptoken, &filenode.segno);
|
|
}
|
|
}
|
|
} else if (0 == strncmp(token, "pg_tblspc", 10)) {
|
|
char tblspcversiondir[MAXPGPATH];
|
|
int errorno = 0;
|
|
|
|
errorno = snprintf_s(tblspcversiondir,
|
|
MAXPGPATH,
|
|
MAXPGPATH - 1,
|
|
"%s_%s",
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
g_instance.attr.attr_common.PGXCNodeName);
|
|
securec_check_ss(errorno, "\0", "\0");
|
|
|
|
token = strtok_r(NULL, "/", &tmptoken);
|
|
filenode.rnode.node.spcNode = atooid(token);
|
|
|
|
/* check tablespace version directory */
|
|
token = strtok_r(NULL, "/", &tmptoken);
|
|
Assert(token != NULL);
|
|
|
|
/* skip tablespaces which not belong to us. */
|
|
if (0 != strncmp(token, tblspcversiondir, strlen(tblspcversiondir) + 1)) {
|
|
pfree(parsepath);
|
|
return filenode;
|
|
}
|
|
|
|
token = strtok_r(NULL, "/", &tmptoken);
|
|
Assert(token != NULL);
|
|
|
|
filenode.rnode.node.dbNode = atooid(token);
|
|
|
|
if (*tmptoken != 't') {
|
|
token = strtok_r(NULL, "_", &tmptoken);
|
|
|
|
if (*tmptoken == '\0') {
|
|
/* pg_tblspc/spcNode/dbNode/relNode */
|
|
filenode.forknumber = MAIN_FORKNUM;
|
|
filenode.rnode.backend = InvalidBackendId;
|
|
token = strtok_r(token, ".", &tmptoken);
|
|
|
|
if (*tmptoken == '\0') {
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.segno = 0;
|
|
} else {
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.segno = (BlockNumber)atooid(tmptoken);
|
|
}
|
|
} else {
|
|
/* pg_tblspc/spcNode/dbNode/relNode_forkName */
|
|
filenode.rnode.backend = InvalidBackendId;
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.rnode.node.bucketNode = InvalidBktId;
|
|
if (*tmptoken == 'b') {
|
|
tmptoken = tmptoken + 1;
|
|
filenode.rnode.node.bucketNode = (int2)atooid(tmptoken);
|
|
token = strtok_r(NULL, "_", &tmptoken);
|
|
if (*tmptoken == '\0') {
|
|
filenode.forknumber = MAIN_FORKNUM;
|
|
token = strtok_r(token, ".", &tmptoken);
|
|
if (*tmptoken != '\0') {
|
|
filenode.segno = (BlockNumber)atooid(tmptoken);
|
|
}
|
|
} else {
|
|
filenode.forknumber = forkname_to_number(tmptoken, NULL);
|
|
}
|
|
} else {
|
|
filenode.forknumber = forkname_to_number(tmptoken, &filenode.segno);
|
|
}
|
|
}
|
|
} else {
|
|
tmptoken = tmptoken + 1; /* skip 't' */
|
|
|
|
token = strtok_r(NULL, "_", &tmptoken);
|
|
Assert(token != NULL);
|
|
filenode.rnode.backend = atoi(token);
|
|
token = strtok_r(NULL, "_", &tmptoken);
|
|
if (*tmptoken == '\0') {
|
|
/* pg_tblspc/spcNode/dbNode/tbackendId_relNode */
|
|
filenode.forknumber = MAIN_FORKNUM;
|
|
token = strtok_r(token, ".", &tmptoken);
|
|
if (*tmptoken == '\0') {
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.segno = 0;
|
|
} else {
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.segno = (BlockNumber)atooid(tmptoken);
|
|
}
|
|
} else {
|
|
/* pg_tblspc/spcNode/dbNode/tbackendId_relNode_forkName */
|
|
filenode.rnode.node.relNode = atooid(token);
|
|
filenode.forknumber = forkname_to_number(tmptoken, &filenode.segno);
|
|
}
|
|
}
|
|
} else {
|
|
pfree(parsepath);
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid relation file path %s: %m", path)));
|
|
}
|
|
|
|
pfree(parsepath);
|
|
return filenode;
|
|
}
|
|
|
|
/*
|
|
* GetDatabasePath - construct path to a database dir
|
|
*
|
|
* Result is a palloc'd string.
|
|
*
|
|
* XXX this must agree with relpath()!
|
|
*/
|
|
char* GetDatabasePath(Oid dbNode, Oid spcNode)
|
|
{
|
|
int pathlen;
|
|
char* path = NULL;
|
|
errno_t rc = EOK;
|
|
|
|
if (spcNode == GLOBALTABLESPACE_OID) {
|
|
/* Shared system relations live in {datadir}/global */
|
|
Assert(dbNode == 0);
|
|
pathlen = 6 + 1;
|
|
path = (char*)palloc(pathlen);
|
|
rc = snprintf_s(path, pathlen, pathlen - 1, "global");
|
|
securec_check_ss(rc, "\0", "\0");
|
|
} else if (spcNode == DEFAULTTABLESPACE_OID) {
|
|
/* The default tablespace is {datadir}/base */
|
|
pathlen = 5 + OIDCHARS + 1;
|
|
path = (char*)palloc(pathlen);
|
|
rc = snprintf_s(path, pathlen, pathlen - 1, "base/%u", dbNode);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
} else {
|
|
/* All other tablespaces are accessed via symlinks */
|
|
pathlen = 9 + 1 + OIDCHARS + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) +
|
|
#ifdef PGXC
|
|
/* Postgres-XC tablespaces include node name in path */
|
|
strlen(g_instance.attr.attr_common.PGXCNodeName) + 1 +
|
|
#endif
|
|
1 + OIDCHARS + 1;
|
|
path = (char*)palloc(pathlen);
|
|
#ifdef PGXC
|
|
rc = snprintf_s(path,
|
|
pathlen,
|
|
pathlen - 1,
|
|
"pg_tblspc/%u/%s_%s/%u",
|
|
spcNode,
|
|
TABLESPACE_VERSION_DIRECTORY,
|
|
g_instance.attr.attr_common.PGXCNodeName,
|
|
dbNode);
|
|
#else
|
|
rc =
|
|
snprintf_s(path, pathlen, pathlen - 1, "pg_tblspc/%u/%s/%u", spcNode, TABLESPACE_VERSION_DIRECTORY, dbNode);
|
|
#endif
|
|
securec_check_ss(rc, "\0", "\0");
|
|
}
|
|
return path;
|
|
}
|
|
|
|
/*
|
|
* IsSystemRelation
|
|
* True iff the relation is a system catalog relation.
|
|
*
|
|
* NB: TOAST relations are considered system relations by this test
|
|
* for compatibility with the old IsSystemRelationName function.
|
|
* This is appropriate in many places but not all. Where it's not,
|
|
* also check IsToastRelation.
|
|
*
|
|
* We now just test if the relation is in the system catalog namespace;
|
|
* so it's no longer necessary to forbid user relations from having
|
|
* names starting with pg_.
|
|
*/
|
|
bool IsSystemRelation(Relation relation)
|
|
{
|
|
return IsSystemNamespace(RelationGetNamespace(relation)) || IsToastNamespace(RelationGetNamespace(relation));
|
|
}
|
|
|
|
/*
|
|
* IsSystemClass
|
|
* Like the above, but takes a Form_pg_class as argument.
|
|
* Used when we do not want to open the relation and have to
|
|
* search pg_class directly.
|
|
*/
|
|
bool IsSystemClass(Form_pg_class reltuple)
|
|
{
|
|
Oid relnamespace = reltuple->relnamespace;
|
|
|
|
return IsSystemNamespace(relnamespace) || IsToastNamespace(relnamespace);
|
|
}
|
|
|
|
/*
|
|
* IsCatalogRelation
|
|
* True iff the relation is a system catalog, or the toast table for
|
|
* a system catalog. By a system catalog, we mean one that created
|
|
* in the pg_catalog schema during initdb. As with IsSystemRelation(),
|
|
* user-created relations in pg_catalog don't count as system catalogs.
|
|
*
|
|
* Note that IsSystemRelation() returns true for ALL toast relations,
|
|
* but this function returns true only for toast relations of system
|
|
* catalogs.
|
|
*/
|
|
bool IsCatalogRelation(Relation relation)
|
|
{
|
|
return IsCatalogClass(RelationGetRelid(relation), relation->rd_rel);
|
|
}
|
|
|
|
/*
|
|
* IsCatalogClass
|
|
* True iff the relation is a system catalog relation.
|
|
*
|
|
* Check IsCatalogRelation() for details.
|
|
*/
|
|
bool IsCatalogClass(Oid relid, Form_pg_class reltuple)
|
|
{
|
|
Oid relnamespace = reltuple->relnamespace;
|
|
|
|
/*
|
|
* Never consider relations outside pg_catalog/pg_toast to be catalog
|
|
* relations.
|
|
*/
|
|
if (!IsSystemNamespace(relnamespace) && !IsToastNamespace(relnamespace))
|
|
return false;
|
|
|
|
/* ----
|
|
* Check whether the oid was assigned during initdb, when creating the
|
|
* initial template database. Minus the relations in information_schema
|
|
* excluded above, these are integral part of the system.
|
|
* We could instead check whether the relation is pinned in pg_depend, but
|
|
* this is noticeably cheaper and doesn't require catalog access.
|
|
*
|
|
* This test is safe since even an oid wraparound will preserve this
|
|
* property (c.f. GetNewObjectId()) and it has the advantage that it works
|
|
* correctly even if a user decides to create a relation in the pg_catalog
|
|
* namespace.
|
|
* ----
|
|
*/
|
|
return relid < FirstNormalObjectId;
|
|
}
|
|
|
|
/*
|
|
* IsToastRelation
|
|
* True iff relation is a TOAST support relation (or index).
|
|
*/
|
|
bool IsToastRelation(Relation relation)
|
|
{
|
|
return IsToastNamespace(RelationGetNamespace(relation));
|
|
}
|
|
|
|
/*
|
|
* IsToastClass
|
|
* Like the above, but takes a Form_pg_class as argument.
|
|
* Used when we do not want to open the relation and have to
|
|
* search pg_class directly.
|
|
*/
|
|
bool IsToastClass(Form_pg_class reltuple)
|
|
{
|
|
Oid relnamespace = reltuple->relnamespace;
|
|
|
|
return IsToastNamespace(relnamespace);
|
|
}
|
|
|
|
/*
|
|
* IsSystemNamespace
|
|
* True iff namespace is pg_catalog.
|
|
*
|
|
* NOTE: the reason this isn't a macro is to avoid having to include
|
|
* catalog/pg_namespace.h in a lot of places.
|
|
*/
|
|
bool IsSystemNamespace(Oid namespaceId)
|
|
{
|
|
return namespaceId == PG_CATALOG_NAMESPACE;
|
|
}
|
|
|
|
/*
|
|
* IsToastNamespace
|
|
* True iff namespace is pg_toast or my temporary-toast-table namespace.
|
|
*
|
|
* Note: this will return false for temporary-toast-table namespaces belonging
|
|
* to other backends. Those are treated the same as other backends' regular
|
|
* temp table namespaces, and access is prevented where appropriate.
|
|
*/
|
|
bool IsToastNamespace(Oid namespaceId)
|
|
{
|
|
return (namespaceId == PG_TOAST_NAMESPACE) || isTempToastNamespace(namespaceId);
|
|
}
|
|
|
|
bool IsCStoreNamespace(Oid namespaceId)
|
|
{
|
|
return (namespaceId == CSTORE_NAMESPACE);
|
|
}
|
|
|
|
/*
|
|
* IsPerformanceNamespace
|
|
* True iff namespace is dbe_perf.
|
|
*
|
|
* NOTE: the reason this isn't a macro is to avoid having to include
|
|
* catalog/pg_namespace.h in a lot of places.
|
|
*/
|
|
bool IsPerformanceNamespace(Oid namespaceId)
|
|
{
|
|
return namespaceId == PG_DBMSPERF_NAMESPACE;
|
|
}
|
|
|
|
/*
|
|
* IsSnapshotNamespace
|
|
* True iff namespace is snapshot.
|
|
*
|
|
* NOTE: the reason this isn't a macro is to avoid having to include
|
|
* catalog/pg_namespace.h in a lot of places.
|
|
*/
|
|
bool IsSnapshotNamespace(Oid namespaceId)
|
|
{
|
|
return namespaceId == PG_SNAPSHOT_NAMESPACE;
|
|
}
|
|
|
|
/*
|
|
* IsReservedName
|
|
* True iff name starts with the pg_ prefix.
|
|
*
|
|
* For some classes of objects, the prefix pg_ is reserved for
|
|
* system objects only. As of 8.0, this is only true for
|
|
* schema and tablespace names.
|
|
*/
|
|
bool IsReservedName(const char* name)
|
|
{
|
|
/* ugly coding for speed */
|
|
return (name[0] == 'p' && name[1] == 'g' && name[2] == '_');
|
|
}
|
|
|
|
/*
|
|
* IsSharedRelation
|
|
* Given the OID of a relation, determine whether it's supposed to be
|
|
* shared across an entire database cluster.
|
|
*
|
|
* Hard-wiring this list is pretty grotty, but we really need it so that
|
|
* we can compute the locktag for a relation (and then lock it) without
|
|
* having already read its pg_class entry. If we try to retrieve relisshared
|
|
* from pg_class with no pre-existing lock, there is a race condition against
|
|
* anyone who is concurrently committing a change to the pg_class entry:
|
|
* since we read system catalog entries under SnapshotNow, it's possible
|
|
* that both the old and new versions of the row are invalid at the instants
|
|
* we scan them. We fix this by insisting that updaters of a pg_class
|
|
* row must hold exclusive lock on the corresponding rel, and that users
|
|
* of a relation must hold at least AccessShareLock on the rel *before*
|
|
* trying to open its relcache entry. But to lock a rel, you have to
|
|
* know if it's shared. Fortunately, the set of shared relations is
|
|
* fairly static, so a hand-maintained list of their OIDs isn't completely
|
|
* impractical.
|
|
*/
|
|
bool IsSharedRelation(Oid relationId)
|
|
{
|
|
/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
|
|
if (relationId == AuthIdRelationId || relationId == AuthMemRelationId || relationId == DatabaseRelationId ||
|
|
relationId == PLTemplateRelationId || relationId == SharedDescriptionRelationId ||
|
|
relationId == SharedDependRelationId || relationId == SharedSecLabelRelationId ||
|
|
relationId == TableSpaceRelationId ||
|
|
/* Database Security: Support password complexity */
|
|
/* add AuthHistoryRelationId in shared catalogs */
|
|
relationId == AuthHistoryRelationId ||
|
|
/* add UserStatusRelationId in shared catalogs */
|
|
relationId == UserStatusRelationId ||
|
|
#ifdef PGXC
|
|
relationId == PgxcGroupRelationId || relationId == PgxcNodeRelationId || relationId == ResourcePoolRelationId ||
|
|
relationId == WorkloadGroupRelationId || relationId == AppWorkloadGroupMappingRelationId ||
|
|
#endif
|
|
relationId == DbRoleSettingRelationId || relationId == PgJobRelationId || relationId == PgJobProcRelationId ||
|
|
relationId == DataSourceRelationId || relationId == GSObsScanInfoRelationId)
|
|
return true;
|
|
/* These are their indexes (see indexing.h) */
|
|
if (relationId == AuthIdRolnameIndexId || relationId == AuthIdOidIndexId || relationId == AuthMemRoleMemIndexId ||
|
|
relationId == AuthMemMemRoleIndexId || relationId == DatabaseNameIndexId || relationId == DatabaseOidIndexId ||
|
|
relationId == PLTemplateNameIndexId || relationId == SharedDescriptionObjIndexId ||
|
|
relationId == SharedDependDependerIndexId || relationId == SharedDependReferenceIndexId ||
|
|
relationId == SharedSecLabelObjectIndexId || relationId == TablespaceOidIndexId ||
|
|
relationId == TablespaceNameIndexId ||
|
|
/* Database Security: Support password complexity */
|
|
relationId == AuthHistoryIndexId || relationId == AuthHistoryOidIndexId ||
|
|
relationId == UserStatusRoleidIndexId || relationId == UserStatusOidIndexId ||
|
|
#ifdef PGXC
|
|
relationId == PgxcNodeNodeNameIndexId || relationId == PgxcNodeNodeNameIndexIdOld ||
|
|
relationId == PgxcNodeNodeIdIndexId || relationId == PgxcNodeOidIndexId ||
|
|
relationId == PgxcGroupGroupNameIndexId || relationId == PgxcGroupOidIndexId ||
|
|
relationId == PgxcGroupToastTable || relationId == PgxcGroupToastIndex ||
|
|
relationId == ResourcePoolPoolNameIndexId || relationId == ResourcePoolOidIndexId ||
|
|
relationId == WorkloadGroupGroupNameIndexId || relationId == WorkloadGroupOidIndexId ||
|
|
relationId == AppWorkloadGroupMappingNameIndexId || relationId == AppWorkloadGroupMappingOidIndexId ||
|
|
#endif
|
|
relationId == DbRoleSettingDatidRolidIndexId ||
|
|
/* Add job system table indexs */
|
|
relationId == PgJobOidIndexId || relationId == PgJobIdIndexId || relationId == PgJobProcOidIndexId ||
|
|
relationId == PgJobProcIdIndexId || relationId == DataSourceOidIndexId || relationId == DataSourceNameIndexId)
|
|
return true;
|
|
/* These are their toast tables and toast indexes (see toasting.h) */
|
|
if (relationId == PgShdescriptionToastTable || relationId == PgShdescriptionToastIndex ||
|
|
relationId == PgDbRoleSettingToastTable || relationId == PgDbRoleSettingToastIndex)
|
|
return true;
|
|
|
|
ListCell* cell = NULL;
|
|
foreach (cell, u_sess->upg_cxt.new_shared_catalog_list) {
|
|
Oid relOid = lfirst_oid(cell);
|
|
if (relOid == relationId)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* GetNewOid
|
|
* Generate a new OID that is unique within the given relation.
|
|
*
|
|
* Caller must have a suitable lock on the relation.
|
|
*
|
|
* Uniqueness is promised only if the relation has a unique index on OID.
|
|
* This is true for all system catalogs that have OIDs, but might not be
|
|
* true for user tables. Note that we are effectively assuming that the
|
|
* table has a relatively small number of entries (much less than 2^32)
|
|
* and there aren't very long runs of consecutive existing OIDs. Again,
|
|
* this is reasonable for system catalogs but less so for user tables.
|
|
*
|
|
* Since the OID is not immediately inserted into the table, there is a
|
|
* race condition here; but a problem could occur only if someone else
|
|
* managed to cycle through 2^32 OIDs and generate the same OID before we
|
|
* finish inserting our row. This seems unlikely to be a problem. Note
|
|
* that if we had to *commit* the row to end the race condition, the risk
|
|
* would be rather higher; therefore we use SnapshotAny in the test, so that
|
|
* so that we will see uncommitted rows. (We used to use SnapshotDirty, but that has
|
|
* the disadvantage that it ignores recently-deleted rows, creating a risk
|
|
* of transient conflicts for as long as our own MVCC snapshots think a
|
|
* recently-deleted row is live. The risk is far higher when selecting TOAST
|
|
* OIDs, because SnapshotToast considers dead rows as active indefinitely.)
|
|
*/
|
|
Oid GetNewOid(Relation relation)
|
|
{
|
|
Oid oidIndex;
|
|
|
|
/* If relation doesn't have OIDs at all, caller is confused */
|
|
Assert(relation->rd_rel->relhasoids);
|
|
|
|
/* In bootstrap mode, we don't have any indexes to use */
|
|
if (IsBootstrapProcessingMode())
|
|
return GetNewObjectId();
|
|
|
|
/* The relcache will cache the identity of the OID index for us */
|
|
oidIndex = RelationGetOidIndex(relation);
|
|
|
|
/* If no OID index, just hand back the next OID counter value */
|
|
if (!OidIsValid(oidIndex)) {
|
|
/*
|
|
* System catalogs that have OIDs should *always* have a unique OID
|
|
* index; we should only take this path for user tables. Give a
|
|
* warning if it looks like somebody forgot an index.
|
|
*/
|
|
if (IsSystemRelation(relation))
|
|
ereport(
|
|
WARNING, (errmsg("generating possibly-non-unique OID for \"%s\"", RelationGetRelationName(relation))));
|
|
|
|
return GetNewObjectId();
|
|
}
|
|
|
|
/* Otherwise, use the index to find a nonconflicting OID */
|
|
return GetNewOidWithIndex(relation, oidIndex, ObjectIdAttributeNumber);
|
|
}
|
|
|
|
/*
|
|
* GetNewOidWithIndex
|
|
* Guts of GetNewOid: use the supplied index
|
|
*
|
|
* This is exported separately because there are cases where we want to use
|
|
* an index that will not be recognized by RelationGetOidIndex: TOAST tables
|
|
* and pg_largeobject have indexes that are usable, but have multiple columns
|
|
* and are on ordinary columns rather than a true OID column. This code
|
|
* will work anyway, so long as the OID is the index's first column. The
|
|
* caller must pass in the actual heap attnum of the OID column, however.
|
|
*
|
|
* Caller must have a suitable lock on the relation.
|
|
*/
|
|
Oid GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn)
|
|
{
|
|
Oid newOid;
|
|
SysScanDesc scan;
|
|
ScanKeyData key;
|
|
bool collides = false;
|
|
|
|
/* Generate new OIDs until we find one not in the table */
|
|
do {
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
/*
|
|
* See comments in GetNewObjectId.
|
|
* In the future, we might turn to SnapshotToast when getting new
|
|
* chunk_id for toast datum to prevent wrap around.
|
|
*/
|
|
newOid = GetNewObjectId(IsToastNamespace(RelationGetNamespace(relation)));
|
|
|
|
ScanKeyInit(&key, oidcolumn, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(newOid));
|
|
|
|
/* see notes above about using SnapshotAny */
|
|
scan = systable_beginscan(relation, indexId, true, SnapshotAny, 1, &key);
|
|
|
|
collides = HeapTupleIsValid(systable_getnext(scan));
|
|
|
|
systable_endscan(scan);
|
|
} while (collides);
|
|
|
|
return newOid;
|
|
}
|
|
|
|
/*
|
|
* GetNewRelFileNode
|
|
* Generate a new relfilenode number that is unique within the given
|
|
* tablespace.
|
|
*
|
|
* If the relfilenode will also be used as the relation's OID, pass the
|
|
* opened pg_class catalog, and this routine will guarantee that the result
|
|
* is also an unused OID within pg_class. If the result is to be used only
|
|
* as a relfilenode for an existing relation, pass NULL for pg_class.
|
|
*
|
|
* As with GetNewOid, there is some theoretical risk of a race condition,
|
|
* but it doesn't seem worth worrying about.
|
|
*
|
|
* Note: we don't support using this in bootstrap mode. All relations
|
|
* created by bootstrap have preassigned OIDs, so there's no need.
|
|
*/
|
|
Oid GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
|
|
{
|
|
RelFileNodeBackend rnode;
|
|
char* rpath = NULL;
|
|
int fd;
|
|
bool collides = false;
|
|
BackendId backend;
|
|
|
|
switch (relpersistence) {
|
|
case RELPERSISTENCE_GLOBAL_TEMP:
|
|
backend = t_thrd.proc_cxt.MyBackendId;
|
|
break;
|
|
case RELPERSISTENCE_TEMP:
|
|
case RELPERSISTENCE_UNLOGGED:
|
|
case RELPERSISTENCE_PERMANENT:
|
|
backend = InvalidBackendId;
|
|
break;
|
|
default:
|
|
elog(ERROR, "invalid relpersistence: %c", relpersistence);
|
|
return InvalidOid; /* placate compiler */
|
|
}
|
|
|
|
/* This logic should match relation_init_physical_addr */
|
|
rnode.node.spcNode = ConvertToRelfilenodeTblspcOid(reltablespace);
|
|
rnode.node.dbNode = (rnode.node.spcNode == GLOBALTABLESPACE_OID) ? InvalidOid : u_sess->proc_cxt.MyDatabaseId;
|
|
rnode.node.bucketNode = InvalidBktId;
|
|
/*
|
|
* The relpath will vary based on the backend ID, so we must initialize
|
|
* that properly here to make sure that any collisions based on filename
|
|
* are properly detected.
|
|
*/
|
|
rnode.backend = backend;
|
|
|
|
do {
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
/* Generate the OID */
|
|
if (pg_class)
|
|
rnode.node.relNode = GetNewOid(pg_class);
|
|
else
|
|
rnode.node.relNode = GetNewObjectId();
|
|
|
|
/* Check for existing file of same name */
|
|
rpath = relpath(rnode, MAIN_FORKNUM);
|
|
fd = BasicOpenFile(rpath, O_RDONLY | PG_BINARY, 0);
|
|
if (fd >= 0) {
|
|
/* definite collision */
|
|
close(fd);
|
|
collides = true;
|
|
} else {
|
|
/*
|
|
* Here we have a little bit of a dilemma: if errno is something
|
|
* other than ENOENT, should we declare a collision and loop? In
|
|
* particular one might think this advisable for, say, EPERM.
|
|
* However there really shouldn't be any unreadable files in a
|
|
* tablespace directory, and if the EPERM is actually complaining
|
|
* that we can't read the directory itself, we'd be in an infinite
|
|
* loop. In practice it seems best to go ahead regardless of the
|
|
* errno. If there is a colliding file we will get an smgr
|
|
* failure when we attempt to create the new relation file.
|
|
*/
|
|
collides = false;
|
|
}
|
|
|
|
pfree(rpath);
|
|
rpath = NULL;
|
|
} while (collides);
|
|
|
|
return rnode.node.relNode;
|
|
}
|