openGauss-server/src/common/backend/utils/cache/partcache.cpp

2064 lines
73 KiB
C++

/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*
* partcache.cpp
*
* IDENTIFICATION
* src/common/backend/utils/cache/partcache.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include <sys/file.h>
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/schemapg.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "commands/vacuum.h"
#include "executor/nodeModifyTable.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "optimizer/var.h"
#include "rewrite/rewriteDefine.h"
#include "storage/lmgr.h"
#include "storage/smgr.h"
#include "catalog/storage.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/relmapper.h"
#include "utils/resowner.h"
#include "utils/syscache.h"
#include "access/cstore_am.h"
#include "utils/snapmgr.h"
#include "utils/partitionmap.h"
#include "utils/partitionmap_gs.h"
#include "catalog/pg_partition.h"
#include "postmaster/autovacuum.h"
#include "nodes/makefuncs.h"
/*
* part 1:macro definitions, global virables, and typedefs
*/
typedef struct partidcacheent {
Oid partoid;
Partition partdesc;
} PartIdCacheEnt;
#define PartitionCacheInsert(PARTITION) \
do { \
PartIdCacheEnt* idhentry; \
bool found = true; \
idhentry = (PartIdCacheEnt*)hash_search( \
u_sess->cache_cxt.PartitionIdCache, (void*)&((PARTITION)->pd_id), HASH_ENTER, &found); \
/* used to give notice if found -- now just keep quiet */ \
idhentry->partdesc = PARTITION; \
} while (0)
#define PartitionIdCacheLookup(ID, PARTITION) \
do { \
PartIdCacheEnt* hentry; \
hentry = (PartIdCacheEnt*)hash_search(u_sess->cache_cxt.PartitionIdCache, (void*)&(ID), HASH_FIND, NULL); \
if (hentry != NULL) \
(PARTITION) = hentry->partdesc; \
else \
(PARTITION) = NULL; \
} while (0)
#define PartitionCacheDelete(PARTITION) \
do { \
PartIdCacheEnt* idhentry; \
idhentry = (PartIdCacheEnt*)hash_search( \
u_sess->cache_cxt.PartitionIdCache, (void*)&((PARTITION)->pd_id), HASH_REMOVE, NULL); \
if (idhentry == NULL) \
ereport(WARNING, \
(errcode(ERRCODE_UNDEFINED_TABLE), errmsg("trying to delete a rd_id partdesc that does not exist"))); \
} while (0)
#define INITPARTCACHESIZE 100
/*
*part 2: static functions used only in this c source file
*
*non-export function prototypes
*/
static HeapTuple ScanPgPartition(Oid targetPartId, bool indexOK, Snapshot snapshot);
static Partition AllocatePartitionDesc(Form_pg_partition relp);
static Partition PartitionBuildDesc(Oid targetPartId, bool insertIt, bool isbucket);
static void PartitionInitPhysicalAddr(Partition partition);
static void PartitionDestroyPartition(Partition partition);
static void PartitionFlushPartition(Partition partition);
static void PartitionClearPartition(Partition partition, bool rebuild);
static void PartitionReloadIndexInfo(Partition part);
static void PartitionParseRelOptions(Partition partition, HeapTuple tuple);
static HeapTuple ScanPgPartition(Oid targetPartId, bool indexOK, Snapshot snapshot)
{
HeapTuple pg_partition_tuple;
Relation pg_partition_desc;
SysScanDesc pg_partition_scan;
ScanKeyData key[1];
/*
* If something goes wrong during backend startup, we might find ourselves
* trying to read pg_partition before we've selected a database. That ain't
* gonna work, so bail out with a useful error message. If this happens,
* it probably means a partcache entry that needs to be nailed isn't.
*/
if (!OidIsValid(u_sess->proc_cxt.MyDatabaseId)) {
ereport(FATAL,
(errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("cannot read pg_class without having selected a database")));
}
if (snapshot == NULL) {
snapshot = GetCatalogSnapshot();
}
/*
* form a scan key
*/
ScanKeyInit(&key[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(targetPartId));
/*
* Open pg_partition and fetch a tuple. Force heap scan if we haven't yet
* built the critical partcache entries (this includes initdb and startup
* without a pg_internal.init file). The caller can also force a heap
* scan by setting indexOK == false.
*/
/*u_sess->relcache_cxt.criticalRelcachesBuilt--->criticalPartcachesBuilt*/
pg_partition_desc = heap_open(PartitionRelationId, AccessShareLock);
pg_partition_scan = systable_beginscan(pg_partition_desc,
PartitionOidIndexId,
indexOK && u_sess->relcache_cxt.criticalRelcachesBuilt,
snapshot,
1,
key);
pg_partition_tuple = systable_getnext(pg_partition_scan);
/*
* Must copy tuple before releasing buffer.
*/
if (HeapTupleIsValid(pg_partition_tuple)) {
pg_partition_tuple = heap_copytuple(pg_partition_tuple);
}
/* all done */
systable_endscan(pg_partition_scan);
heap_close(pg_partition_desc, AccessShareLock);
return pg_partition_tuple;
}
static Partition AllocatePartitionDesc(Form_pg_partition partp)
{
Partition partition;
MemoryContext oldcxt;
Form_pg_partition partitionForm;
errno_t rc = 0;
/* Relcache entries must live in u_sess->cache_mem_cxt */
oldcxt = MemoryContextSwitchTo(u_sess->cache_mem_cxt);
/*
* allocate and zero space for new relation descriptor
*/
partition = (Partition)palloc0(sizeof(PartitionData));
/* make sure relation is marked as having no open file yet */
partition->pd_smgr = NULL;
/*
* Copy the partition tuple form
*
* We only allocate space for the fixed fields, ie, CLASS_TUPLE_SIZE. The
* variable-length fields (relacl, reloptions) are NOT stored in the
* partcache --- there'd be little point in it, since we don't copy the
* tuple's nulls bitmap and hence wouldn't know if the values are valid.
* Bottom line is that relacl *cannot* be retrieved from the partcache. Get
* it from the syscache if you need it. The same goes for the original
* form of reloptions (however, we do store the parsed form of reloptions
* in rd_options).
*/
partitionForm = (Form_pg_partition)palloc(PARTITION_TUPLE_SIZE);
rc = memcpy_s(partitionForm, PARTITION_TUPLE_SIZE, partp, PARTITION_TUPLE_SIZE);
securec_check(rc, "\0", "\0");
/* initialize relation tuple form */
partition->pd_part = partitionForm;
(void)MemoryContextSwitchTo(oldcxt);
return partition;
}
static Partition PartitionBuildDesc(Oid targetPartId, bool insertIt, bool isbukcet)
{
Partition partition;
Oid partid;
HeapTuple pg_partition_tuple;
Form_pg_partition partp;
/*
* find the tuple in pg_class corresponding to the given relation id
*/
pg_partition_tuple = ScanPgPartition(targetPartId, true, NULL);
/*
* if no such tuple exists, return NULL
*/
if (!HeapTupleIsValid(pg_partition_tuple)) {
return NULL;
}
/*
* get information from the pg_class_tuple
*/
partid = HeapTupleGetOid(pg_partition_tuple);
partp = (Form_pg_partition)GETSTRUCT(pg_partition_tuple);
Assert(partid == targetPartId);
/*
* allocate storage for the relation descriptor, and copy pg_partition_tuple
* to partition->pd_part.
*/
partition = AllocatePartitionDesc(partp);
/*
* initialize the partition's partition id (partition->pd_id)
*/
partition->pd_id = partid;
/*
* normal relations are not nailed into the cache; nor can a pre-existing
* relation be new. It could be temp though. (Actually, it could be new
* too, but it's okay to forget that fact if forced to flush the entry.)
*/
partition->pd_refcnt = 0;
partition->pd_createSubid = InvalidSubTransactionId;
partition->pd_newRelfilenodeSubid = InvalidSubTransactionId;
PartitionParseRelOptions(partition, pg_partition_tuple);
/*
* initialize the relation lock manager information
*/
PartitionInitLockInfo(partition); /* see lmgr.c */
/*
* initialize physical addressing information for the relation
*/
if (partition->pd_part->parentid != InvalidOid) {
PartitionInitPhysicalAddr(partition);
}
partition->pd_node.bucketNode = isbukcet ? DIR_BUCKET_ID : InvalidBktId;
/* make sure relation is marked as having no open file yet */
partition->pd_smgr = NULL;
/*
* now we can free the memory allocated for pg_class_tuple
*/
heap_freetuple_ext(pg_partition_tuple);
/*
* Insert newly created relation into partcache hash table, if requested.
*/
if (insertIt) {
PartitionCacheInsert(partition);
}
/* It's fully valid */
partition->pd_isvalid = true;
return partition;
}
static void PartitionInitPhysicalAddr(Partition partition)
{
partition->pd_node.spcNode = ConvertToRelfilenodeTblspcOid(partition->pd_part->reltablespace);
if (partition->pd_node.spcNode == GLOBALTABLESPACE_OID) {
partition->pd_node.dbNode = InvalidOid;
} else {
partition->pd_node.dbNode = u_sess->proc_cxt.MyDatabaseId;
}
if (partition->pd_part->relfilenode) {
partition->pd_node.relNode = partition->pd_part->relfilenode;
} else if (partition->pd_id) {
partition->pd_node.relNode = partition->pd_id;
} else {
/* Consult the relation mapper */
partition->pd_node.relNode = RelationMapOidToFilenode(partition->pd_id, false);
if (!OidIsValid(partition->pd_node.relNode)) {
ereport(ERROR,
(errcode(ERRCODE_NO_DATA_FOUND),
errmsg("could not find relation mapping for partition \"%s\", OID %u",
PartitionGetPartitionName(partition),
partition->pd_id)));
}
}
}
/*
*part 3: functions can be used by other modules
*
*
*/
Partition PartitionIdGetPartition(Oid partitionId, bool isbucket)
{
Partition pd;
/*
* first try to find reldesc in the cache
*/
PartitionIdCacheLookup(partitionId, pd);
if (PartitionIsValid(pd)) {
PartitionIncrementReferenceCount(pd);
/* revalidate cache entry if necessary */
if (!pd->pd_isvalid) {
/*
* Indexes only have a limited number of possible schema changes,
* and we don't want to use the full-blown procedure because it's
* a headache for indexes that reload itself depends on.
*/
if (pd->pd_part->parttype == PART_OBJ_TYPE_INDEX_PARTITION) {
PartitionReloadIndexInfo(pd);
} else {
PartitionClearPartition(pd, true);
}
}
return pd;
}
/*
* no partdesc in the cache, so have PartitionBuildDesc() build one and add
* it.
*/
pd = PartitionBuildDesc(partitionId, true, isbucket);
if (PartitionIsValid(pd)) {
PartitionIncrementReferenceCount(pd);
}
return pd;
}
char* PartitionOidGetName(Oid partOid)
{
HeapTuple tuple = ScanPgPartition(partOid, true, NULL);
if (!HeapTupleIsValid(tuple)) {
return NULL;
}
Form_pg_partition part = (Form_pg_partition)GETSTRUCT(tuple);
char* relName = (char*)palloc0(NAMEDATALEN);
error_t rc = strncpy_s(relName, NAMEDATALEN, part->relname.data, NAMEDATALEN - 1);
securec_check_ss(rc, "\0", "\0");
heap_freetuple_ext(tuple);
return relName;
}
Oid PartitionOidGetTablespace(Oid partOid)
{
HeapTuple tuple = ScanPgPartition(partOid, true, NULL);
if (!HeapTupleIsValid(tuple)) {
return InvalidOid;
}
Form_pg_partition part = (Form_pg_partition)GETSTRUCT(tuple);
Oid tablespaceOid = part->reltablespace;
heap_freetuple_ext(tuple);
return tablespaceOid;
}
void PartitionClose(Partition partition)
{
/* Note: no locking manipulations needed */
PartitionDecrementReferenceCount(partition);
#ifdef PARTCACHE_FORCE_RELEASE
if (PartitionHasReferenceCountZero(partition) && partition->pd_createSubid == InvalidSubTransactionId &&
partition->pd_newRelfilenodeSubid == InvalidSubTransactionId) {
PartitionClearPartition(partition, false);
}
#endif
}
Partition PartitionBuildLocalPartition(const char* relname, Oid partid, Oid partfilenode, Oid parttablespace)
{
Partition part;
MemoryContext oldcxt;
/*
* switch to the cache context to create the partcache entry.
*/
oldcxt = MemoryContextSwitchTo(u_sess->cache_mem_cxt);
/*
* allocate a new relation descriptor and fill in basic state fields.
*/
part = (Partition)palloc0(sizeof(PartitionData));
/* make sure relation is marked as having no open file yet */
part->pd_smgr = NULL;
part->pd_refcnt = 0;
/* it's being created in this transaction */
part->pd_createSubid = GetCurrentSubTransactionId();
part->pd_newRelfilenodeSubid = InvalidSubTransactionId;
/* must flag that we have rels created in this transaction */
u_sess->cache_cxt.part_cache_need_eoxact_work = true;
/*
* initialize partition tuple form (caller may add/override data later)
*/
part->pd_part = (Form_pg_partition)palloc0(PARTITION_TUPLE_SIZE);
(void)namestrcpy(&part->pd_part->relname, relname);
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
* RelationInitPhysicalAddr to consult the map.
*/
part->pd_id = partid;
part->pd_part->reltablespace = parttablespace;
part->pd_part->relfilenode = partfilenode;
/*belowing: cast out from Partition to Relation*/
PartitionInitLockInfo(part); /* see lmgr.c */
if (partfilenode != InvalidOid) {
PartitionInitPhysicalAddr(part);
}
part->pd_node.bucketNode = InvalidBktId;
/*
* Okay to insert into the partcache hash tables.
*/
PartitionCacheInsert(part);
/*
* done building partcache entry.
*/
(void)MemoryContextSwitchTo(oldcxt);
/* It's fully valid */
part->pd_isvalid = true;
/*
* Caller expects us to pin the returned entry.
*/
PartitionIncrementReferenceCount(part);
return part;
}
/*
* PartitionDestroyPartition
*
* Physically delete a partition cache entry and all subsidiary data.
* Caller must already have unhooked the entry from the hash table.
*/
static void PartitionDestroyPartition(Partition partition)
{
Assert(PartitionHasReferenceCountZero(partition));
/*
* Make sure smgr and lower levels close the partition's files, if they
* weren't closed already. (This was probably done by caller, but let's
* just be real sure.)
*/
PartitionCloseSmgr(partition);
/*
* Free all the subsidiary data structures of the partcache entry, then the
* entry itself.
*/
if (partition->pd_part) {
pfree_ext(partition->pd_part);
}
list_free_ext(partition->pd_indexlist);
if (partition->rd_options) {
pfree_ext(partition->rd_options);
}
pfree_ext(partition);
}
/*
* PartitionClearPartition
*
* Physically blow away a partition cache entry, or reset it and rebuild
* it from scratch (that is, from catalog entries). The latter path is
* used when we are notified of a change to an open relation (one with
* refcount > 0).
*
* NB: when rebuilding, we'd better hold some lock on the relation,
* else the catalog data we need to read could be changing under us.
* Also, a rel to be rebuilt had better have refcnt > 0. This is because
* an sinval reset could happen while we're accessing the catalogs, and
* the rel would get blown away underneath us by RelationCacheInvalidate
* if it has zero refcnt.
*
* The "rebuild" parameter is redundant in current usage because it has
* to match the relation's refcnt status, but we keep it as a crosscheck
* that we're doing what the caller expects.
*/
static void PartitionClearPartition(Partition partition, bool rebuild)
{
/*
* As per notes above, a rel to be rebuilt MUST have refcnt > 0; while of
* course it would be a bad idea to blow away one with nonzero refcnt.
*/
Assert(rebuild ? !PartitionHasReferenceCountZero(partition) : PartitionHasReferenceCountZero(partition));
/*
* Make sure smgr and lower levels close the partition's files, if they
* weren't closed already. If the partition is not getting deleted, the
* next smgr access should reopen the files automatically. This ensures
* that the low-level file access state is updated after, say, a vacuum
* truncation.
*/
PartitionCloseSmgr(partition);
/*
* Never, never ever blow away a nailed-in system relation, because we'd
* be unable to recover. However, we must redo RelationInitPhysicalAddr
* in case it is a mapped relation whose mapping changed.
*
* If it's a nailed index, then we need to re-read the pg_partition row to see
* if its relfilenode changed. We can't necessarily do that here, because
* we might be in a failed transaction. We assume it's okay to do it if
* there are open references to the partcache entry (cf notes for
* AtEOXact_RelationCache). Otherwise just mark the entry as possibly
* invalid, and it'll be fixed when next opened.
*/
/*
* Even non-system indexes should not be blown away if they are open and
* have valid index support information. This avoids problems with active
* use of the index support information. As with nailed indexes, we
* re-read the pg_class row to handle possible physical relocation of the
* index, and we check for pg_index updates too.
*/
if (partition->pd_part->parttype == PART_OBJ_TYPE_INDEX_PARTITION && partition->pd_refcnt > 0) {
partition->pd_isvalid = false; /* needs to be revalidated */
PartitionReloadIndexInfo(partition);
return;
}
/* Mark it invalid until we've finished rebuild */
partition->pd_isvalid = false;
/*
* If we're really done with the partcache entry, blow it away. But if
* someone is still using it, reconstruct the whole deal without moving
* the physical PartitionData record (so that the someone's pointer is
* still valid).
*/
if (!rebuild) {
/* Remove it from the hash table */
PartitionCacheDelete(partition);
/* And release storage */
PartitionDestroyPartition(partition);
} else {
/*
* Our strategy for rebuilding an open partcache entry is to build a
* new entry from scratch, swap its contents with the old entry, and
* finally delete the new entry (along with any infrastructure swapped
* over from the old entry). This is to avoid trouble in case an
* error causes us to lose control partway through. The old entry
* will still be marked !rd_isvalid, so we'll try to rebuild it again
* on next access. Meanwhile it's not any less valid than it was
* before, so any code that might expect to continue accessing it
* isn't hurt by the rebuild failure. (Consider for example a
* subtransaction that ALTERs a table and then gets canceled partway
* through the cache entry rebuild. The outer transaction should
* still see the not-modified cache entry as valid.) The worst
* consequence of an error is leaking the necessarily-unreferenced new
* entry, and this shouldn't happen often enough for that to be a big
* problem.
*
* When rebuilding an open partcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
* attempt to preserve the pg_class entry (rd_rel), tupledesc, and
* rewrite-rule substructures in place, because various places assume
* that these structures won't move while they are working with an
* open partcache entry. (Note: the refcount mechanism for tupledescs
* might someday allow us to remove this hack for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
* necessarily belong to that resource owner.
*/
Partition newpart;
Oid save_partid = PartitionGetPartid(partition);
errno_t rc = 0;
/* Build temporary entry, but don't link it into hashtable */
newpart = PartitionBuildDesc(save_partid, false, partition->pd_node.bucketNode != InvalidBktId);
if (NULL == newpart) {
/* Should only get here if partition was deleted */
PartitionCacheDelete(partition);
PartitionDestroyPartition(partition);
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE), errmsg("partition %u deleted while still in use", save_partid)));
}
/*
* Perform swapping of the partcache entry contents. Within this
* process the old entry is momentarily invalid, so there *must* be no
* possibility of CHECK_FOR_INTERRUPTS within this sequence. Do it in
* all-in-line code for safety.
*
* Since the vast majority of fields should be swapped, our method is
* to swap the whole structures and then re-swap those few fields we
* didn't want swapped.
*/
#define SWAPFIELD(fldtype, fldname) \
do { \
fldtype _tmp = newpart->fldname; \
newpart->fldname = partition->fldname; \
partition->fldname = _tmp; \
} while (0)
/* swap all Partition struct fields */
{
PartitionData tmpstruct;
rc = memcpy_s(&tmpstruct, sizeof(PartitionData), newpart, sizeof(PartitionData));
securec_check(rc, "\0", "\0");
rc = memcpy_s(newpart, sizeof(PartitionData), partition, sizeof(PartitionData));
securec_check(rc, "\0", "\0");
rc = memcpy_s(partition, sizeof(PartitionData), &tmpstruct, sizeof(PartitionData));
securec_check(rc, "\0", "\0");
}
/* rd_smgr must not be swapped, due to back-links from smgr level */
SWAPFIELD(SMgrRelation, pd_smgr);
/* rd_refcnt must be preserved */
SWAPFIELD(int, pd_refcnt);
/* creation sub-XIDs must be preserved */
SWAPFIELD(SubTransactionId, pd_createSubid);
SWAPFIELD(SubTransactionId, pd_newRelfilenodeSubid);
/* un-swap rd_rel pointers, swap contents instead */
SWAPFIELD(Form_pg_partition, pd_part);
/* ... but actually, we don't have to update newrel->rd_rel */
rc = memcpy_s(partition->pd_part, PARTITION_TUPLE_SIZE, newpart->pd_part, PARTITION_TUPLE_SIZE);
securec_check(rc, "\0", "\0");
/* toast OID override must be preserved */
SWAPFIELD(Oid, pd_toastoid);
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus*, pd_pgstat_info);
#undef SWAPFIELD
/* And now we can throw away the temporary entry */
PartitionDestroyPartition(newpart);
}
}
/*
* PartitionFlushPartition
*
* Rebuild the partition if it is open (refcount > 0), else blow it away.
*/
static void PartitionFlushPartition(Partition partition)
{
if (partition->pd_createSubid != InvalidSubTransactionId ||
partition->pd_newRelfilenodeSubid != InvalidSubTransactionId) {
/*
* New partcache entries are always rebuilt, not flushed; else we'd
* forget the "new" status of the partition, which is a useful
* optimization to have. Ditto for the new-relfilenode status.
*
* The rel could have zero refcnt here, so temporarily increment the
* refcnt to ensure it's safe to rebuild it. We can assume that the
* current transaction has some lock on the rel already.
*/
PartitionIncrementReferenceCount(partition);
PartitionClearPartition(partition, true);
PartitionDecrementReferenceCount(partition);
} else {
/*
* Pre-existing parts can be dropped from the partcache if not open.
*/
bool rebuild = !PartitionHasReferenceCountZero(partition);
PartitionClearPartition(partition, rebuild);
}
}
/*
* PartitionForgetPartition - unconditionally remove a partcache entry
*
* External interface for destroying a partcache entry when we
* drop the relation.
*/
void PartitionForgetPartition(Oid partid)
{
Partition partition;
PartitionIdCacheLookup(partid, partition);
if (!PointerIsValid(partition)) {
return; /* not in cache, nothing to do */
}
if (!PartitionHasReferenceCountZero(partition)) {
ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("partition %u is still open", partid)));
}
/* Unconditionally destroy the partcache entry */
PartitionClearPartition(partition, false);
}
/*
* RelationCacheInvalidateEntry
*
* This routine is invoked for SI cache flush messages.
*
* Any relcache entry matching the relid must be flushed. (Note: caller has
* already determined that the relid belongs to our database or is a shared
* relation.)
*
* We used to skip local relations, on the grounds that they could
* not be targets of cross-backend SI update messages; but it seems
* safer to process them, so that our *own* SI update messages will
* have the same effects during CommandCounterIncrement for both
* local and nonlocal relations.
*/
void PartitionCacheInvalidateEntry(Oid partitionId)
{
Partition partition;
PartitionIdCacheLookup(partitionId, partition);
if (PointerIsValid(partition)) {
PartitionFlushPartition(partition);
}
}
/*
* RelationCacheInvalidate
* Blow away cached relation descriptors that have zero reference counts,
* and rebuild those with positive reference counts. Also reset the smgr
* relation cache and re-read relation mapping data.
*
* This is currently used only to recover from SI message buffer overflow,
* so we do not touch new-in-transaction relations; they cannot be targets
* of cross-backend SI updates (and our own updates now go through a
* separate linked list that isn't limited by the SI message buffer size).
* Likewise, we need not discard new-relfilenode-in-transaction hints,
* since any invalidation of those would be a local event.
*
* We do this in two phases: the first pass deletes deletable items, and
* the second one rebuilds the rebuildable items. This is essential for
* safety, because hash_seq_search only copes with concurrent deletion of
* the element it is currently visiting. If a second SI overflow were to
* occur while we are walking the table, resulting in recursive entry to
* this routine, we could crash because the inner invocation blows away
* the entry next to be visited by the outer scan. But this way is OK,
* because (a) during the first pass we won't process any more SI messages,
* so hash_seq_search will complete safely; (b) during the second pass we
* only hold onto pointers to nondeletable entries.
*
* The two-phase approach also makes it easy to update relfilenodes for
* mapped relations before we do anything else, and to ensure that the
* second pass processes nailed-in-cache items before other nondeletable
* items. This should ensure that system catalogs are up to date before
* we attempt to use them to reload information about other open relations.
*/
void PartitionCacheInvalidate(void)
{
HASH_SEQ_STATUS status;
PartIdCacheEnt* idhentry = NULL;
Partition partition;
List* rebuildList = NIL;
ListCell* l = NULL;
/*
* Reload relation mapping data before starting to reconstruct cache.
*/
/* Phase 1 */
hash_seq_init(&status, u_sess->cache_cxt.PartitionIdCache);
while ((idhentry = (PartIdCacheEnt*)hash_seq_search(&status)) != NULL) {
partition = idhentry->partdesc;
/* Must close all smgr references to avoid leaving dangling ptrs */
PartitionCloseSmgr(partition);
/* Ignore new relations, since they are never cross-backend targets */
if (partition->pd_createSubid != InvalidSubTransactionId)
continue;
if (PartitionHasReferenceCountZero(partition)) {
/* Delete this entry immediately */
PartitionClearPartition(partition, false);
} else {
rebuildList = lappend(rebuildList, partition);
}
}
/*
* Now zap any remaining smgr cache entries. This must happen before we
* start to rebuild entries, since that may involve catalog fetches which
* will re-open catalog files.
*/
smgrcloseall();
/* Phase 2: rebuild the items found to need rebuild in phase 1 */
foreach (l, rebuildList) {
partition = (Partition)lfirst(l);
PartitionClearPartition(partition, true);
}
list_free_ext(rebuildList);
}
/*
* RelationCloseSmgrByOid - close a relcache entry's smgr link
*
* Needed in some cases where we are changing a relation's physical mapping.
* The link will be automatically reopened on next use.
*/
void PartitionCloseSmgrByOid(Oid partitionId)
{
Partition partition;
PartitionIdCacheLookup(partitionId, partition);
if (!PointerIsValid(partition)) {
return; /* not in cache, nothing to do */
}
PartitionCloseSmgr(partition);
}
/*
* AtEOXact_PartitionCache
*
* Clean up the partitonCache at main-transaction commit or abort.
*
* Note: this must be called *before* processing invalidation messages.
* In the case of abort, we don't want to try to rebuild any invalidated
* cache entries (since we can't safely do database accesses). Therefore
* we must reset refcnts before handling pending invalidations.
*
* We also need to do special cleanup when the current transaction
* created any partitions or made use of forced index lists.
*/
void AtEOXact_PartitionCache(bool isCommit)
{
HASH_SEQ_STATUS status;
PartIdCacheEnt* idhentry = NULL;
/*
* To speed up transaction exit, we want to avoid scanning the partitioncache
* unless there is actually something for this routine to do. Other than
* the debug-only Assert checks, most transactions don't create any work
* for us to do here, so we keep a static flag that gets set if there is
* anything to do. (Currently, this means either a partition is created in
* the current xact, or one is given a new relfilenode, or an index list
* is forced.) For simplicity, the flag remains set till end of top-level
* transaction, even though we could clear it at subtransaction end in
* some cases.
*/
if (!u_sess->cache_cxt.part_cache_need_eoxact_work
#ifdef USE_ASSERT_CHECKING
&& !assert_enabled
#endif
) {
return;
}
hash_seq_init(&status, u_sess->cache_cxt.PartitionIdCache);
while ((idhentry = (PartIdCacheEnt*)hash_seq_search(&status)) != NULL) {
Partition partition = idhentry->partdesc;
/*
* The relcache entry's ref count should be back to its normal
* not-in-a-transaction state: 0 unless it's nailed in cache.
*
* In bootstrap mode, this is NOT true, so don't check it --- the
* bootstrap code expects relations to stay open across start/commit
* transaction calls. (That seems bogus, but it's not worth fixing.)
*/
#ifdef USE_ASSERT_CHECKING
if (!IsBootstrapProcessingMode()) {
const int expected_refcnt = 0;
Assert(partition->pd_refcnt == expected_refcnt);
}
#endif
/*
* Is it a partition created in the current transaction?
*
* During commit, reset the flag to zero, since we are now out of the
* creating transaction. During abort, simply delete the relcache
* entry --- it isn't interesting any longer. (NOTE: if we have
* forgotten the new-ness of a new relation due to a forced cache
* flush, the entry will get deleted anyway by shared-cache-inval
* processing of the aborted pg_class insertion.)
*/
if (partition->pd_createSubid != InvalidSubTransactionId) {
if (isCommit) {
partition->pd_createSubid = InvalidSubTransactionId;
} else {
PartitionClearPartition(partition, false);
continue;
}
}
/*
* Likewise, reset the hint about the relfilenode being new.
*/
partition->pd_newRelfilenodeSubid = InvalidSubTransactionId;
}
/* Once done with the transaction, we can reset need_eoxact_work */
u_sess->cache_cxt.part_cache_need_eoxact_work = false;
}
/*
* AtEOSubXact_RelationCache
*
* Clean up the partitioncache at sub-transaction commit or abort.
*
* Note: this must be called *before* processing invalidation messages.
*/
void AtEOSubXact_PartitionCache(bool isCommit, SubTransactionId mySubid, SubTransactionId parentSubid)
{
HASH_SEQ_STATUS status;
PartIdCacheEnt* idhentry = NULL;
/*
* Skip the relcache scan if nothing to do --- see notes for
* AtEOXact_PartitionCache.
*/
if (!u_sess->cache_cxt.part_cache_need_eoxact_work)
return;
hash_seq_init(&status, u_sess->cache_cxt.PartitionIdCache);
while ((idhentry = (PartIdCacheEnt*)hash_seq_search(&status)) != NULL) {
Partition partition = idhentry->partdesc;
/*
* Is it a partition created in the current subtransaction?
*
* During subcommit, mark it as belonging to the parent, instead.
* During subabort, simply delete the partition entry.
*/
if (partition->pd_createSubid == mySubid) {
if (isCommit)
partition->pd_createSubid = parentSubid;
else {
PartitionClearPartition(partition, false);
continue;
}
}
/*
* Likewise, update or drop any new-relfilenode-in-subtransaction
* hint.
*/
if (partition->pd_newRelfilenodeSubid == mySubid) {
if (isCommit)
partition->pd_newRelfilenodeSubid = parentSubid;
else
partition->pd_newRelfilenodeSubid = InvalidSubTransactionId;
}
}
}
/*
* @@GaussDB@@
* Target : data partition
* Brief : This initializes the relation descriptor cache
* Description : At the time that this is invoked, we can't do database access yet (mainly
* : because the transaction subsystem is not up); all we are doing is making
* : an empty cache hashtable. This must be done before starting the initialization
* : transaction, because otherwise AtEOXact_RelationCache would crash if that
* : transaction aborts before we can get the relcache set up.
* Notes :
*/
void PartitionCacheInitialize(void)
{
HASHCTL ctl;
errno_t rc;
/*
* create hashtable that indexes the partcache
*/
rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl));
securec_check(rc, "", "");
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(PartIdCacheEnt);
ctl.hash = oid_hash;
ctl.hcxt = u_sess->cache_mem_cxt;
u_sess->cache_cxt.PartitionIdCache =
hash_create("Partcache by OID", INITPARTCACHESIZE, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
}
/* ----------------------------------------------------------------
* cache invalidation support routines
* ----------------------------------------------------------------
*/
/*
* @@GaussDB@@
* Target : data partition
* Brief : Increments partition reference count.
* Description :
* Notes : bootstrap mode has its own weird ideas about relation refcount
* : behavior; we ought to fix it someday, but for now, just disable
* : reference count ownership tracking in bootstrap mode.
*/
void PartitionIncrementReferenceCount(Partition part)
{
ResourceOwnerEnlargePartitionRefs(t_thrd.utils_cxt.CurrentResourceOwner);
part->pd_refcnt += 1;
if (!IsBootstrapProcessingMode()) {
ResourceOwnerRememberPartitionRef(t_thrd.utils_cxt.CurrentResourceOwner, part);
}
}
/*
* @@GaussDB@@
* Target : data partition
* Brief : Decrements relation reference count.
* Description :
* Notes :
*/
void PartitionDecrementReferenceCount(Partition part)
{
Assert(part->pd_refcnt > 0);
part->pd_refcnt -= 1;
if (!IsBootstrapProcessingMode()) {
ResourceOwnerForgetPartitionRef(t_thrd.utils_cxt.CurrentResourceOwner, part);
}
}
bytea* merge_rel_part_reloption(Oid rel_oid, Oid part_oid)
{
HeapTuple part_tuple = NULL, rel_tuple = NULL;
Datum rel_reloptions = (Datum)0;
Datum part_reloptions = (Datum)0;
Datum merged_reloptions = (Datum)0;
List* rel_reloptions_list = NIL;
List* part_reloptions_list = NIL;
List* merged_reloptions_list = NIL;
ListCell* lc = NULL;
bytea* merged_rd_options = NULL;
bool isnull = false;
/* get tuples */
part_tuple = SearchSysCache1WithLogLevel(PARTRELID, ObjectIdGetDatum(part_oid), LOG);
if (!HeapTupleIsValid(part_tuple))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", part_oid)));
part_reloptions = SysCacheGetAttr(PARTRELID, part_tuple, Anum_pg_partition_reloptions, &isnull);
rel_tuple = SearchSysCache1WithLogLevel(RELOID, ObjectIdGetDatum(rel_oid), LOG);
if (!HeapTupleIsValid(rel_tuple))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", rel_oid)));
rel_reloptions = SysCacheGetAttr(RELOID, rel_tuple, Anum_pg_class_reloptions, &isnull);
/* datum ==> list */
rel_reloptions_list = untransformRelOptions(rel_reloptions);
part_reloptions_list = untransformRelOptions(part_reloptions);
ReleaseSysCache(part_tuple);
ReleaseSysCache(rel_tuple);
if (part_reloptions_list == NIL || list_length(part_reloptions_list) == 0) {
merged_reloptions_list = rel_reloptions_list;
} else {
foreach (lc, rel_reloptions_list) {
DefElem* d = (DefElem*)lfirst(lc);
ListCell* cell = NULL;
int i = 0;
foreach (cell, part_reloptions_list) {
DefElem* d2 = (DefElem*)lfirst(cell);
if (pg_strncasecmp(d->defname, d2->defname, strlen(d->defname)) == 0) {
merged_reloptions_list = lappend(merged_reloptions_list, (void*)d2);
break;
}
i++;
}
if (i == list_length(part_reloptions_list)) {
merged_reloptions_list = lappend(merged_reloptions_list, (void*)d);
}
}
}
/* list ==> datum */
merged_reloptions = transformRelOptions((Datum)0, merged_reloptions_list, NULL, NULL, false, false);
/* datum ==> bytea * */
merged_rd_options = heap_reloptions(RELKIND_RELATION, merged_reloptions, true);
return merged_rd_options;
}
/*
* @@GaussDB@@
* Target : data partition
* Brief :
* Description :
* Notes : the invoker should release the EMS memory
*/
Relation partitionGetRelation(Relation rel, Partition part)
{
Relation relation;
MemoryContext oldcxt;
errno_t rc = 0;
bytea* merge_reloption = NULL;
bytea* des_reloption = NULL;
Assert(PointerIsValid(rel) && PointerIsValid(part));
/*
* Memory malloced in merge_rel_part_reloption cannot mount in CacheMemoryContext,
* the same is true for other memory in this function and these may be optimized later.
*/
if (RelationInClusterResizing(rel)) {
/* tuple.column(datum) ==> list ==> datum ==> bytea * */
merge_reloption = merge_rel_part_reloption(RelationGetRelid(rel), PartitionGetPartid(part));
}
oldcxt = MemoryContextSwitchTo(u_sess->cache_mem_cxt);
relation = (Relation)palloc0(sizeof(RelationData));
if (!IsBootstrapProcessingMode()) {
ResourceOwnerRememberFakerelRef(t_thrd.utils_cxt.CurrentResourceOwner, relation);
}
relation->rd_node = part->pd_node;
relation->rd_refcnt = part->pd_refcnt;
relation->rd_backend = InvalidBackendId;
relation->rd_isnailed = false;
relation->rd_isvalid = part->pd_isvalid;
relation->rd_indexvalid = part->pd_indexvalid;
relation->rd_createSubid = part->pd_createSubid;
relation->rd_newRelfilenodeSubid = part->pd_newRelfilenodeSubid;
relation->rd_rel = (Form_pg_class)palloc(sizeof(FormData_pg_class));
rc = memcpy_s(relation->rd_rel, sizeof(FormData_pg_class), rel->rd_rel, sizeof(FormData_pg_class));
securec_check(rc, "\0", "\0");
relation->rd_rel->reltoastrelid = part->pd_part->reltoastrelid;
relation->rd_rel->reltablespace = part->pd_part->reltablespace;
relation->rd_rel->parttype = PARTTYPE_NON_PARTITIONED_RELATION;
relation->rd_rel->relfilenode = part->pd_part->relfilenode;
relation->rd_rel->relpages = part->pd_part->relpages;
relation->rd_rel->reltuples = part->pd_part->reltuples;
relation->rd_rel->relallvisible = part->pd_part->relallvisible;
relation->rd_rel->relcudescrelid = part->pd_part->relcudescrelid;
relation->rd_rel->relcudescidx = part->pd_part->relcudescidx;
relation->rd_rel->reldeltarelid = part->pd_part->reldeltarelid;
relation->rd_rel->reldeltaidx = part->pd_part->reldeltaidx;
relation->rd_bucketoid = rel->rd_bucketoid;
if (REALTION_BUCKETKEY_INITED(rel))
relation->rd_bucketkey = rel->rd_bucketkey;
else
relation->rd_bucketkey = NULL;
relation->rd_att = rel->rd_att;
relation->rd_partHeapOid = part->pd_part->indextblid;
relation->rd_index = rel->rd_index;
relation->rd_indextuple = rel->rd_indextuple;
relation->rd_am = rel->rd_am;
relation->rd_indnkeyatts = rel->rd_indnkeyatts;
relation->rd_tam_type = rel->rd_tam_type;
if (!OidIsValid(rel->rd_rel->relam)) {
relation->rd_indexcxt = NULL;
} else {
Assert(rel->rd_indexcxt != NULL);
relation->rd_indexcxt = AllocSetContextCreate(rel->rd_indexcxt,
PartitionGetPartitionName(part),
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
}
relation->rd_aminfo = rel->rd_aminfo;
relation->rd_opfamily = rel->rd_opfamily;
relation->rd_opcintype = rel->rd_opcintype;
relation->rd_support = rel->rd_support;
relation->rd_supportinfo = rel->rd_supportinfo;
relation->rd_indoption = rel->rd_indoption;
relation->rd_indexprs = rel->rd_indexprs;
relation->rd_indpred = rel->rd_indpred;
relation->rd_exclops = rel->rd_exclops;
relation->rd_exclprocs = rel->rd_exclprocs;
relation->rd_exclstrats = rel->rd_exclstrats;
relation->rd_amcache = rel->rd_amcache;
relation->rd_indcollation = rel->rd_indcollation;
relation->rd_id = part->pd_id;
relation->rd_indexlist = part->pd_indexlist;
relation->rd_oidindex = part->pd_oidindex;
relation->rd_lockInfo = part->pd_lockInfo;
relation->rd_toastoid = part->pd_toastoid;
relation->partMap = NULL;
relation->pgstat_info = part->pd_pgstat_info;
relation->parentId = rel->rd_id;
relation->rd_smgr = part->pd_smgr;
/*detach the binding between partition and SmgrRelation*/
part->pd_smgr = NULL;
/*build the binding between dummy Relation and SmgrRelation*/
if (relation->rd_smgr) {
smgrsetowner(&((relation)->rd_smgr), relation->rd_smgr);
}
if (NULL != merge_reloption)
des_reloption = merge_reloption;
else
des_reloption = rel->rd_options;
if (NULL != des_reloption) {
int relOptSize = VARSIZE_ANY(des_reloption);
errno_t ret = EOK;
relation->rd_options = (bytea*)palloc(relOptSize);
ret = memcpy_s(relation->rd_options, relOptSize, des_reloption, relOptSize);
securec_check(ret, "\0", "\0");
}
(void)MemoryContextSwitchTo(oldcxt);
return relation;
}
/*
* NOTICE: caller MUST be sure relation parameter is a temprary RelationData, which is NOT in relcache.
*/
void releaseDummyRelation(Relation* relation)
{
if (relation == NULL || *relation == NULL) {
elog(LOG, "error parameter when release fake relation");
return;
}
if (!IsBootstrapProcessingMode()) {
ResourceOwnerForgetFakerelRef(t_thrd.utils_cxt.CurrentResourceOwner, *relation);
}
/*detach the binding between Relation and SmgrRelation*/
if ((*relation)->rd_smgr != NULL) {
/* put SmgrRelation object into unowned list */
smgrclearowner(&(*relation)->rd_smgr, (*relation)->rd_smgr);
}
if ((*relation)->rd_indexcxt != NULL) {
MemoryContextDelete((*relation)->rd_indexcxt);
(*relation)->rd_indexcxt = NULL;
}
/*free relation*/
/*if palloc(sizeof(FormData_pg_class)) throw exception, (*relation)->rd_rel will be null*/
if (PointerIsValid((*relation)->rd_rel)) {
pfree_ext((*relation)->rd_rel);
}
if (NULL != (*relation)->rd_options) {
pfree_ext((*relation)->rd_options);
}
pfree_ext(*relation);
*relation = NULL;
}
/*
* @@GaussDB@@
* Target : data partition
* Brief : reload minimal information for an open index partition
* Description :
*/
static void PartitionReloadIndexInfo(Partition part)
{
HeapTuple pg_partition_tuple;
Form_pg_partition partForm;
errno_t rc = 0;
/*
* Should be called only for invalidated indexe partition
*/
Assert(PART_OBJ_TYPE_INDEX_PARTITION == part->pd_part->parttype && !part->pd_isvalid);
/*
* Should be closed at smgr level
*/
Assert(NULL == part->pd_smgr);
pg_partition_tuple = ScanPgPartition(PartitionGetPartid(part), true, NULL);
if (!HeapTupleIsValid(pg_partition_tuple)) {
ereport(ERROR,
(errcode(ERRCODE_NO_DATA),
errmsg("could not find pg_partition tuple for index %u", PartitionGetPartid(part))));
}
if (part->rd_options) {
pfree_ext(part->rd_options);
}
PartitionParseRelOptions(part, pg_partition_tuple);
partForm = (Form_pg_partition)GETSTRUCT(pg_partition_tuple);
rc = memcpy_s(part->pd_part, PARTITION_TUPLE_SIZE, partForm, PARTITION_TUPLE_SIZE);
securec_check(rc, "\0", "\0");
heap_freetuple_ext(pg_partition_tuple);
/* We must recalculate physical address in case it changed
* bucketNode will not change anyway
*/
PartitionInitPhysicalAddr(part);
/*
* we can't read value from pg_index, we should read value from pg_partition
* or a other catalog for partition index but indisvalid indcheckxmin indisready
* is not been added.
*/
part->pd_isvalid = true;
}
/*
* @@GaussDB@@
* Target : data partition
* Brief : Assign a new relfilenode (physical file name) to the partition.
* Description :
* Input :
* Output :
* Notes :
*/
void PartitionSetNewRelfilenode(Relation parent, Partition part, TransactionId freezeXid)
{
Oid newrelfilenode;
RelFileNodeBackend newrnode;
Relation pg_partition;
HeapTuple tuple;
HeapTuple ntup;
Form_pg_partition partform;
Datum values[Natts_pg_partition];
bool nulls[Natts_pg_partition];
bool replaces[Natts_pg_partition];
errno_t rc;
Assert((parent->rd_rel->relkind == RELKIND_INDEX || parent->rd_rel->relkind == RELKIND_SEQUENCE)
? freezeXid == InvalidTransactionId
: TransactionIdIsNormal(freezeXid));
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(part->pd_part->reltablespace, NULL, parent->rd_rel->relpersistence);
/*
* Get a writable copy of the pg_partition tuple for the given relation.
*/
pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(PartitionGetPartid(part)));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("could not find tuple for partition %u", PartitionGetPartid(part))));
}
partform = (Form_pg_partition)GETSTRUCT(tuple);
// CStore Relation must deal with cudesc relation, delta relation
if (RelationIsColStore(parent)) {
// step 1: CUDesc relation must set new relfilenode
// step 2: CUDesc index must be set new relfilenode
//
DescTableSetNewRelfilenode(part->pd_part->relcudescrelid, freezeXid, true);
// Step 3: Deta relation must be set new relfilenode
//
DeltaTableSetNewRelfilenode(part->pd_part->reldeltarelid, freezeXid, true);
// Step 4: Create first data file for newrelfilenode
// Note that we need add xlog when create file
//
Relation partRel = partitionGetRelation(parent, part);
CStore::CreateStorage(partRel, newrelfilenode);
releaseDummyRelation(&partRel);
}
ereport(LOG,
(errmsg("Partition %s(%u) set newfilenode %u oldfilenode %u xid %lu",
PartitionGetPartitionName(part),
PartitionGetPartid(part),
newrelfilenode,
part->pd_node.relNode,
GetCurrentTransactionIdIfAny())));
/*
* Create storage for the main fork of the new relfilenode.
*
* NOTE: any conflict in relfilenode value will be caught here, if
* GetN
* ewRelFileNode messes up for any reason.
*/
newrnode.node = part->pd_node;
newrnode.node.relNode = newrelfilenode;
newrnode.backend = parent->rd_backend;
partition_create_new_storage(parent, part, newrnode);
Assert(!((part)->pd_part->relfilenode == InvalidOid));
partform->relfilenode = newrelfilenode;
Assert(parent->rd_rel->relkind != RELKIND_SEQUENCE);
partform->relpages = 0; /* it's empty until further notice */
partform->reltuples = 0;
partform->relallvisible = 0;
/* set relfrozenxid64 */
partform->relfrozenxid = (ShortTransactionId)InvalidTransactionId;
rc = memset_s(values, sizeof(values), 0, sizeof(values));
securec_check(rc, "\0", "\0");
rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls));
securec_check(rc, "\0", "\0");
rc = memset_s(replaces, sizeof(replaces), false, sizeof(replaces));
securec_check(rc, "\0", "\0");
replaces[Anum_pg_partition_relfrozenxid64 - 1] = true;
values[Anum_pg_partition_relfrozenxid64 - 1] = TransactionIdGetDatum(freezeXid);
ntup = heap_modify_tuple(tuple, RelationGetDescr(pg_partition), values, nulls, replaces);
simple_heap_update(pg_partition, &ntup->t_self, ntup);
CatalogUpdateIndexes(pg_partition, ntup);
heap_freetuple_ext(ntup);
heap_freetuple_ext(tuple);
heap_close(pg_partition, RowExclusiveLock);
CommandCounterIncrement();
/*
* Mark the part as having been given a new relfilenode in the current
* (sub) transaction. This is a hint that can be used to optimize later
* operations on the rel in the same transaction.
*/
part->pd_newRelfilenodeSubid = GetCurrentSubTransactionId();
/* ... and now we have eoxact cleanup work to do */
u_sess->cache_cxt.part_cache_need_eoxact_work = true;
}
static void PartitionParseRelOptions(Partition partition, HeapTuple tuple)
{
bytea* options = NULL;
bool isnull = false;
Datum datum;
Relation partitionRel;
errno_t rc;
partition->rd_options = NULL;
partitionRel = relation_open(PartitionRelationId, RowExclusiveLock);
/*
* Fetch reloptions from tuple; have to use a hardwired descriptor because
* we might not have any other for pg_class yet (consider executing this
* code for pg_class itself)
*/
datum = fastgetattr(tuple, Anum_pg_partition_reloptions, RelationGetDescr(partitionRel), &isnull);
/*close pg_partition catalog*/
relation_close(partitionRel, RowExclusiveLock);
if (isnull)
return;
options = heap_reloptions(RELKIND_RELATION, datum, false);
/*
* Copy parsed data into u_sess->cache_mem_cxt. To guard against the
* possibility of leaks in the reloptions code, we want to do the actual
* parsing in the caller's memory context and copy the results into
* u_sess->cache_mem_cxt after the fact.
*/
if (options != NULL) {
partition->rd_options = (bytea*)MemoryContextAlloc(u_sess->cache_mem_cxt, VARSIZE(options));
rc = memcpy_s(partition->rd_options, VARSIZE(options), options, VARSIZE(options));
securec_check(rc, "", "");
pfree_ext(options);
}
return;
}
/* Check one partition whether it is normal use, and save in Bitmapset liveParts */
static bool PartitionStatusIsLive(Oid partOid, Bitmapset** liveParts)
{
HeapTuple partTuple = NULL;
if (bms_is_member(partOid, *liveParts)) {
return true;
}
/* Get partition information from syscache */
partTuple = SearchSysCache1WithLogLevel(PARTRELID, ObjectIdGetDatum(partOid), LOG);
if (HeapTupleIsValid(partTuple)) {
ReleaseSysCache(partTuple);
*liveParts = bms_add_member(*liveParts, partOid);
return true;
}
return false;
}
/* Check one invisible partition whether enable clean */
static bool InvisblePartEnableClean(HeapTuple partTuple, TupleDesc tupleDesc)
{
Datum partOptions;
bool isNull = false;
partOptions = fastgetattr(partTuple, Anum_pg_partition_reloptions, tupleDesc, &isNull);
if (isNull || !PartitionInvisibleMetadataKeep(partOptions)) {
return true;
}
return false;
}
/* Just for lazy vacuum get one partition's status */
static PartStatus PartTupleStatusForVacuum(HeapTuple partTuple, Buffer buffer, TransactionId oldestXmin)
{
PartStatus partStatus = PART_METADATA_NOEXIST;
/*
* We could possibly get away with not locking the buffer here,
* since caller should hold ShareLock on the relation, but let's
* be conservative about it. (This remark is still correct even
* with HOT-pruning: our pin on the buffer prevents pruning.)
*/
LockBuffer(buffer, BUFFER_LOCK_SHARE);
switch (HeapTupleSatisfiesVacuum(partTuple, oldestXmin, buffer)) {
case HEAPTUPLE_INSERT_IN_PROGRESS:
case HEAPTUPLE_DELETE_IN_PROGRESS:
partStatus = PART_METADATA_CREATING;
break;
case HEAPTUPLE_LIVE:
partStatus = PART_METADATA_LIVE;
break;
case HEAPTUPLE_DEAD:
case HEAPTUPLE_RECENTLY_DEAD:
partStatus = PART_METADATA_INVISIBLE;
break;
default:
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("unexpected HeapTupleSatisfiesVacuum result")));
partStatus = PART_METADATA_NOEXIST; /* keep compiler quiet */
break;
}
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
return partStatus;
}
/*
* Check current partition status use HeapTupleSatisfiesVacuum
*
* Notes: return PART_METADATA_CEATING scenario occurs only in the process of automatically creating
* partitions when the interval partition insert statement is executed, Other partition
* change scenarios have AccessExclusiveLock locks, which are not executed concurrently
* with the vacuum process
*/
static PartStatus PartitionStatusForVacuum(Oid partOid)
{
Relation pgPartition = NULL;
SysScanDesc scan = NULL;
ScanKeyData key[1];
HeapTuple partTuple = NULL;
TransactionId oldestXmin;
PartStatus partStatus = PART_METADATA_NOEXIST;
pgPartition = heap_open(PartitionRelationId, RowExclusiveLock);
oldestXmin = u_sess->utils_cxt.RecentGlobalXmin;
ScanKeyInit(&key[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(partOid));
scan = systable_beginscan(pgPartition, InvalidOid, false, SnapshotAny, 1, key);
while (HeapTupleIsValid(partTuple = systable_getnext(scan))) {
partStatus = PartTupleStatusForVacuum(partTuple, scan->scan->rs_base.rs_cbuf, oldestXmin);
/* The status of a partition is creating or live, the partition status is the latest */
if (partStatus == PART_METADATA_CREATING || partStatus == PART_METADATA_LIVE) {
break;
}
}
systable_endscan(scan);
heap_close(pgPartition, NoLock);
return partStatus;
}
/*
* This function is used by global partition index to determine whether
* the partition corresponding to the partoid in index tuple should be ignored,
* The scenarios are as follows:
* a partition is created in a transaction and data is inserted into the partition,
* However, the transaction is aborted. Alternatively,
* a partition is created in a transaction, data is inserted into the partition,
* and the partition is deleted. The transaction is committed.
*
* Notes: In this case, lazy_vacuum of pg_partition must meet the following requirements:
* Before clearing a dead tuple, ensure that global partition index (if any) does not contain
* any indextuple containing partoid of the dead tuple.
*/
PartStatus PartitionGetMetadataStatus(Oid partOid, bool vacuumFlag)
{
HeapTuple partTuple;
/* Get partition information from syscache */
partTuple = SearchSysCache1WithLogLevel(PARTRELID, ObjectIdGetDatum(partOid), LOG);
if (HeapTupleIsValid(partTuple)) {
ReleaseSysCache(partTuple);
return PART_METADATA_LIVE;
}
/* When vacuum is performed, must checks whether the partition is being created */
if (vacuumFlag) {
return PartitionStatusForVacuum(partOid);
}
/*
* Find the tuple in pg_partition corresponding to the given partition oid
*
* Notes: use SnapshotAny to ensure that the tuple of pg_partition
* in the invisible state is obtained.
*/
partTuple = ScanPgPartition(partOid, false, SnapshotAny);
/* If get tuple exists, return status invisible */
if (HeapTupleIsValid(partTuple)) {
pfree_ext(partTuple);
return PART_METADATA_INVISIBLE;
}
return PART_METADATA_NOEXIST;
}
/* Set reloptions wait_clean_gpi, Just for pg_partition's tuple */
Datum SetWaitCleanGpiRelOptions(Datum oldOptions, bool enable)
{
Datum newOptions;
List* defList = NIL;
DefElem* def = NULL;
Value* defArg = enable ? makeString(OptEnabledWaitCleanGpi) : makeString(OptDisabledWaitCleanGpi);
def = makeDefElem(pstrdup("wait_clean_gpi"), (Node*)defArg);
defList = lappend(defList, def);
newOptions = transformRelOptions(oldOptions, defList, NULL, NULL, false, false);
pfree_ext(def->defname);
list_free_ext(defList);
return newOptions;
}
/* Update pg_partition's tuple attribute reloptions wait_clean_gpi */
static void UpdateWaitCleanGpiRelOptions(Relation pgPartition, HeapTuple partTuple, bool enable, bool inplace)
{
HeapTuple newTuple;
Datum partOptions;
Datum newOptions;
Datum replVal[Natts_pg_partition];
bool replNull[Natts_pg_partition];
bool replRepl[Natts_pg_partition];
errno_t rc;
bool isNull = false;
partOptions = fastgetattr(partTuple, Anum_pg_partition_reloptions, RelationGetDescr(pgPartition), &isNull);
/* If the caller use replacement to update reloptions, but the effect is the same as not set, just return */
if (inplace && enable == PartitionInvisibleMetadataKeep(partOptions)) {
return;
}
newOptions = SetWaitCleanGpiRelOptions(isNull ? (Datum)0 : partOptions, enable);
rc = memset_s(replVal, sizeof(replVal), 0, sizeof(replVal));
securec_check(rc, "\0", "\0");
rc = memset_s(replNull, sizeof(replNull), false, sizeof(replNull));
securec_check(rc, "\0", "\0");
rc = memset_s(replRepl, sizeof(replRepl), false, sizeof(replRepl));
securec_check(rc, "\0", "\0");
if (PointerIsValid(newOptions)) {
replVal[Anum_pg_partition_reloptions - 1] = newOptions;
replNull[Anum_pg_partition_reloptions - 1] = false;
} else {
replNull[Anum_pg_partition_reloptions - 1] = true;
}
replRepl[Anum_pg_partition_reloptions - 1] = true;
newTuple = heap_modify_tuple(partTuple, RelationGetDescr(pgPartition), replVal, replNull, replRepl);
if (inplace) {
heap_inplace_update(pgPartition, newTuple);
} else {
simple_heap_update(pgPartition, &newTuple->t_self, newTuple);
CatalogUpdateIndexes(pgPartition, newTuple);
}
ereport(LOG, (errmsg("partition %u set reloptions wait_clean_gpi success", HeapTupleGetOid(partTuple))));
heap_freetuple_ext(newTuple);
}
/* Set one partitioned relation's reloptions wait_clean_gpi */
void PartitionedSetWaitCleanGpi(const char* parentName, Oid parentPartOid, bool enable, bool inplace)
{
HeapTuple partTuple;
Relation pgPartition;
pgPartition = heap_open(PartitionRelationId, RowExclusiveLock);
partTuple = SearchSysCache3(PARTPARTOID,
PointerGetDatum(parentName),
CharGetDatum(PART_OBJ_TYPE_PARTED_TABLE),
ObjectIdGetDatum(parentPartOid));
if (!HeapTupleIsValid(partTuple)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for partition %u", parentPartOid)));
}
UpdateWaitCleanGpiRelOptions(pgPartition, partTuple, enable, inplace);
ReleaseSysCache(partTuple);
heap_close(pgPartition, NoLock);
/* Make changes visible */
CommandCounterIncrement();
ereport(LOG, (errmsg("partition relation %s set reloptions wait_clean_gpi success", parentName)));
}
/* Set one partition's reloptions wait_clean_gpi */
void PartitionSetWaitCleanGpi(Oid partOid, bool enable, bool inplace)
{
Relation pgPartition;
HeapTuple partTuple;
pgPartition = heap_open(PartitionRelationId, RowExclusiveLock);
partTuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(partOid));
if (!HeapTupleIsValid(partTuple)) {
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for partition %u", partOid)));
}
UpdateWaitCleanGpiRelOptions(pgPartition, partTuple, enable, inplace);
ReleaseSysCache(partTuple);
heap_close(pgPartition, NoLock);
/* Make changes visible */
CommandCounterIncrement();
ereport(LOG, (errmsg("partition %u set reloptions wait_clean_gpi success", partOid)));
}
/*
* Check one partition's invisible metadata tuple whether still keep
*
* Notes: if wait_clean_gpi=y is contained in reloptions, determine to keep
*/
bool PartitionInvisibleMetadataKeep(Datum datumRelOptions)
{
bool ret = false;
bytea* options = NULL;
char* waitCleanGpi;
if (!PointerIsValid(datumRelOptions)) {
return false;
}
options = heap_reloptions(RELKIND_RELATION, datumRelOptions, true);
if (options != NULL) {
waitCleanGpi = (char*)StdRdOptionsGetStringData(options, wait_clean_gpi, OptDisabledWaitCleanGpi);
if (pg_strcasecmp(OptEnabledWaitCleanGpi, waitCleanGpi) == 0) {
ret = true;
}
pfree_ext(options);
}
return ret;
}
/* Check whether a partition is properly used. */
bool PartitionParentOidIsLive(Datum parentDatum)
{
Oid parentid = InvalidOid;
HeapTuple partTuple = NULL;
if (!PointerIsValid(parentDatum)) {
return false;
}
parentid = DatumGetObjectId(parentDatum);
/* Get table information from syscache */
partTuple = SearchSysCache1WithLogLevel(RELOID, ObjectIdGetDatum(parentid), LOG);
if (HeapTupleIsValid(partTuple)) {
ReleaseSysCache(partTuple);
return true;
}
return false;
}
/*
* In pg_partition, search all tuples (visible and invisible) containing wait_clean_gpi=y
* in reloptios of one partitioed relation and set wait_clean_gpi=n
*
* Notes: This function is called only when a partition table is lazy vacuumed,
* and cannot be executed in parallel with PartitionSetWaitCleanGpi, Currently,
* the AccessShareLock lock of ADD_PARTITION_ACTION is used to ensure that no concurrent
* operations are performed.
*/
void PartitionedSetEnabledClean(Oid parentOid)
{
Relation pgPartition = NULL;
SysScanDesc scan = NULL;
ScanKeyData key[2];
HeapTuple tuple = NULL;
pgPartition = heap_open(PartitionRelationId, RowExclusiveLock);
ScanKeyInit(
&key[0], Anum_pg_partition_parttype, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(PART_OBJ_TYPE_PARTED_TABLE));
ScanKeyInit(&key[1], Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(parentOid));
scan = systable_beginscan(pgPartition, InvalidOid, false, SnapshotAny, 2, key);
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
UpdateWaitCleanGpiRelOptions(pgPartition, tuple, false, true);
}
systable_endscan(scan);
heap_close(pgPartition, NoLock);
ereport(LOG, (errmsg("partitioned %u set reloptions wait_clean_gpi=n success", parentOid)));
}
/*
* In pg_partition, search all tuples (visible and invisible) containing wait_clean_gpi=y
* in reloptios of one partition's all partitions and set wait_clean_gpi=n
*
* input cleanedParts means a collection of partoids that have been cleaned of all remaining invalid partitions
* input invisibleParts means the collection of partoids for invalid partitions that have been deleted
* input updatePartitioned means need check whether update partitioned's reloptions
*
* Notes: This function is called only when a partition table is lazy vacuumed,
* and cannot be executed in parallel with PartitionSetWaitCleanGpi, if updatePartitioned
*/
void PartitionSetEnabledClean(
Oid parentOid, const Bitmapset* cleanedParts, const Bitmapset* invisibleParts, bool updatePartitioned)
{
Relation pgPartition = NULL;
TupleDesc partTupdesc = NULL;
SysScanDesc scan = NULL;
ScanKeyData key[2];
HeapTuple tuple = NULL;
Oid partOid;
Bitmapset* liveParts = NULL;
bool needSetOpts = false;
bool needSetPartitioned = updatePartitioned;
pgPartition = heap_open(PartitionRelationId, RowExclusiveLock);
partTupdesc = RelationGetDescr(pgPartition);
ScanKeyInit(&key[0],
Anum_pg_partition_parttype,
BTEqualStrategyNumber,
F_CHAREQ,
CharGetDatum(PART_OBJ_TYPE_TABLE_PARTITION));
ScanKeyInit(&key[1], Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(parentOid));
scan = systable_beginscan(pgPartition, InvalidOid, false, SnapshotAny, 2, key);
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
needSetOpts = false;
partOid = HeapTupleGetOid(tuple);
if (bms_is_member(partOid, cleanedParts)) {
needSetOpts = true;
} else if (bms_is_member(partOid, invisibleParts)) {
needSetOpts = true;
} else if (PartitionStatusIsLive(partOid, &liveParts)) {
needSetOpts = true;
} else if (updatePartitioned && InvisblePartEnableClean(tuple, partTupdesc)) {
continue;
} else {
needSetPartitioned = false;
}
if (needSetOpts) {
UpdateWaitCleanGpiRelOptions(pgPartition, tuple, false, true);
}
}
systable_endscan(scan);
heap_close(pgPartition, NoLock);
bms_free(liveParts);
if (needSetPartitioned) {
PartitionedSetEnabledClean(parentOid);
}
}
/*
* In pg_partition, search all tuples containing wait_clean_gpi=y
* in reloptios of one relation's all partitions (visible and invisible)
* in a partition and set wait_clean_gpi=n
*
* Notes: This function is called only when a partitioned table is vacuum full,
* and cannot be executed in parallel with PartitionSetWaitCleanGpi.
*/
void PartitionSetAllEnabledClean(Oid parentOid)
{
Relation pgPartition = NULL;
SysScanDesc scan = NULL;
ScanKeyData key[1];
HeapTuple tuple = NULL;
pgPartition = heap_open(PartitionRelationId, RowExclusiveLock);
ScanKeyInit(&key[0], Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(parentOid));
scan = systable_beginscan(pgPartition, InvalidOid, false, SnapshotAny, 1, key);
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
UpdateWaitCleanGpiRelOptions(pgPartition, tuple, false, true);
}
systable_endscan(scan);
heap_close(pgPartition, NoLock);
ereport(LOG, (errmsg("relation %u set all partition's reloptions wait_clean_gpi=n success", parentOid)));
}
/*
* Get all invisible partition from pg_partition
*
* Notes: Before calling the function, you must ensure that a lock with parentOid
* is already held (to prevent parallelism with any ALTER table partition process)
* and AccessShareLock for ADD_PARTITION_ACTION (to prevent parallelism with the
* process of automatically creating partitions in any interval partition)
*/
void PartitionGetAllInvisibleParts(Oid parentOid, Bitmapset** invisibleParts)
{
Relation pgPartition = NULL;
SysScanDesc scan = NULL;
ScanKeyData key[2];
HeapTuple tuple = NULL;
Bitmapset* liveParts = NULL;
Oid partOid;
pgPartition = heap_open(PartitionRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_partition_parttype,
BTEqualStrategyNumber,
F_CHAREQ,
CharGetDatum(PART_OBJ_TYPE_TABLE_PARTITION));
ScanKeyInit(&key[1], Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(parentOid));
scan = systable_beginscan(pgPartition, InvalidOid, false, SnapshotAny, 2, key);
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
partOid = HeapTupleGetOid(tuple);
if (bms_is_member(partOid, *invisibleParts)) {
continue;
} else if (PartitionStatusIsLive(partOid, &liveParts)) {
continue;
} else {
*invisibleParts = bms_add_member(*invisibleParts, partOid);
}
}
systable_endscan(scan);
heap_close(pgPartition, NoLock);
bms_free(liveParts);
}
/*
* Check whether contain a tuple in pg_partition, which includes
* wait_clean_gpi=y in the reloptions of the tuple
*
* Notes: this function is called only when vacuum full pg_partition
*/
bool PartitionMetadataDisabledClean(Relation pgPartition)
{
bool result = false;
TupleDesc partTupdesc = NULL;
SysScanDesc scan = NULL;
HeapTuple tuple = NULL;
ScanKeyData key[1];
Form_pg_partition partform;
char* relName = NULL;
if (RelationGetRelid(pgPartition) != PartitionRelationId) {
return result;
}
ScanKeyInit(
&key[0], Anum_pg_partition_parttype, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(PART_OBJ_TYPE_PARTED_TABLE));
partTupdesc = RelationGetDescr(pgPartition);
scan = systable_beginscan(pgPartition, PartitionParentOidIndexId, true, NULL, 1, key);
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
bool isNull = false;
Datum partOptions = fastgetattr(tuple, Anum_pg_partition_reloptions, partTupdesc, &isNull);
if (isNull) {
continue;
}
if (PartitionInvisibleMetadataKeep(partOptions)) {
partform = (Form_pg_partition)GETSTRUCT(tuple);
relName = (char*)palloc0(NAMEDATALEN);
error_t rc = strncpy_s(relName, NAMEDATALEN, partform->relname.data, NAMEDATALEN - 1);
securec_check_ss(rc, "\0", "\0");
result = true;
break;
}
}
systable_endscan(scan);
if (result) {
ereport(WARNING,
(errmsg("system table pg_partition contain relation %s have reloptions wait_clean_gpi=y,"
"must run the vacuum (full) %s first",
relName,
relName)));
}
return result;
}