*
* heap.cpp
* code to create and destroy openGauss heap relations
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
* Portions Copyright (c) 2010-2012 Postgres-XC Development Group
*
*
* IDENTIFICATION
* src/common/backend/catalog/heap.cpp
*
*
* INTERFACE ROUTINES
* heap_create() - Create an uncataloged heap relation
* heap_create_with_catalog() - Create a cataloged relation
* heap_drop_with_catalog() - Removes named relation from catalogs
*
* NOTES
* this code taken from access/heap/create.c, which contains
* the old heap_create_with_catalog, amcreate, and amdestroy.
* those routines will soon call these routines using the function
* manager,
* just like the poorly named "NewXXX" routines do. The
* "New" routines are all going to die soon, once and for all!
* -cim 1/13/91
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "fmgr.h"
#include "knl/knl_variable.h"
#include "access/cstore_delta.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "access/multixact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/gs_matview.h"
#include "catalog/gs_obsscaninfo.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_object.h"
#include "catalog/pg_obsscaninfo.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_fn.h"
#include "catalog/pg_hashbucket.h"
#include "catalog/pg_hashbucket_fn.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_statistic_history.h"
#include "catalog/pg_statistic_lock.h"
#include "catalog/pg_synonym.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_fn.h"
#include "catalog/pg_uid_fn.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/storage_gtt.h"
#include "catalog/pg_subscription_rel.h"
#include "commands/matview.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/typecmds.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/params.h"
#include "optimizer/var.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parse_utilcmd.h"
#include "parser/parsetree.h"
#include "pgxc/groupmgr.h"
#include "storage/buf/buf.h"
#include "storage/predicate.h"
#include "storage/page_compression.h"
#include "storage/buf/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/smgr/smgr.h"
#include "storage/smgr/segment.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/extended_statistics.h"
#include "utils/fmgroids.h"
#include "utils/hotkey.h"
#include "utils/int8.h"
#include "utils/int16.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/partitionmap.h"
#include "utils/partitionmap_gs.h"
#include "utils/partitionkey.h"
#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/rel_gs.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "access/heapam.h"
#include "foreign/fdwapi.h"
#include "instruments/generate_report.h"
#include "catalog/gs_encrypted_columns.h"
#include "catalog/gs_dependencies_fn.h"
#include "catalog/gs_sql_limit.h"
#include "utils/plpgsql.h"
#include "tcop/autonomoustransaction.h"
#ifdef PGXC
#include "catalog/pgxc_class.h"
#include "catalog/pgxc_node.h"
#include "catalog/pgxc_slice.h"
#include "commands/comment.h"
#include "pgxc/locator.h"
#include "pgxc/groupmgr.h"
#include "pgxc/nodemgr.h"
#include "pgxc/pgxc.h"
#include "pgxc/pgxcnode.h"
#include "replication/bcm.h"
#endif
#include "client_logic/client_logic.h"
#ifndef ENABLE_MULTIPLE_NODES
#include "utils/pl_package.h"
#endif
static void AddNewRelationTuple(Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, Oid new_type_oid,
Oid reloftype, Oid relowner, char relkind, char relpersistence, Datum relacl, Datum reloptions,
int2vector* bucketcol, bool ispartrel);
static oidvector* BuildIntervalTablespace(const IntervalPartitionDefState* intervalPartDef);
static void deletePartitionTuple(Oid part_id);
static void addNewPartitionTuplesForPartition(Relation pg_partition_rel,
Oid relid, Oid reltablespace,
Oid bucketOid, PartitionState* partTableState, Oid ownerid,
Datum reloptions, const TupleDesc tupledesc, char strategy, StorageType storage_type, LOCKMODE partLockMode,
PartitionExprKeyInfo *partExprKeyInfo = NULL);
static void addNewPartitionTupleForTable(Relation pg_partition_rel, const char* relname, const Oid reloid,
const Oid reltablespaceid, const TupleDesc reltupledesc, const PartitionState* partTableState, Oid ownerid,
Datum reloptions, PartitionExprKeyInfo *partExprKeyInfo = NULL);
static void addNewPartitionTupleForValuePartitionedTable(Relation pg_partition_rel, const char* relname,
const Oid reloid, const Oid reltablespaceid, const TupleDesc reltupledesc, const PartitionState* partTableState,
Datum reloptions);
static void heapDropPartitionTable(Relation relation);
static ObjectAddress AddNewRelationType(const char* typeName, Oid typeNamespace, Oid new_rel_oid, char new_rel_kind, Oid ownerid,
Oid new_row_type, Oid new_array_type, Oid typbasetype = InvalidOid);
static void RelationRemoveInheritance(Oid relid);
static Oid StoreRelCheck(
Relation rel, const char* ccname, Node* expr, bool is_validated, bool is_local, int inhcount, bool is_no_inherit,
bool is_deferrable = false, bool is_deferred = false, bool is_disable = false);
static void StoreConstraints(Relation rel, List* cooked_constraints);
static bool MergeWithExistingConstraint(
Relation rel, char* ccname, Node* expr, bool allow_merge, bool is_local, bool is_no_inherit);
static void SetRelationNumChecks(Relation rel, int numchecks);
static Node* cookConstraint(ParseState* pstate, Node* raw_constraint, char* relname);
static List* insert_ordered_unique_oid(List* list, Oid datum);
static void InitPartitionDef(Partition newPartition, Oid partOid, char strategy);
static void InitSubPartitionDef(Partition newPartition, Oid partOid, char strategy);
static bool binary_upgrade_is_next_part_pg_partition_oid_valid();
static Oid binary_upgrade_get_next_part_pg_partition_oid();
bool binary_upgrade_is_next_part_toast_pg_class_oid_valid();
static Oid binary_upgrade_get_next_part_toast_pg_class_oid();
static Oid binary_upgrade_get_next_part_toast_pg_class_rfoid();
static Oid binary_upgrade_get_next_part_pg_partition_rfoid();
static void LockSeqConstraints(Relation rel, List* Constraints);
static bool ReferenceGenerated(const List *rawdefaultlist, AttrNumber attnum);
static bool CheckNestedGeneratedWalker(Node *node, ParseState *context);
static bool CheckNestedGenerated(ParseState *pstate, Node *node);
extern void getErrorTableFilePath(char* buf, int len, Oid databaseid, Oid reid);
extern void make_tmptable_cache_key(Oid relNode);
#define RELKIND_IN_RTM (relkind == RELKIND_RELATION || relkind == RELKIND_TOASTVALUE || relkind == RELKIND_MATVIEW)
static RangePartitionDefState *MakeRangeDefaultSubpartition(PartitionState *partitionState, char *partitionName,
char *tablespacename);
static ListPartitionDefState *MakeListDefaultSubpartition(PartitionState *partitionState, char *partitionName,
char *tablespacename);
static HashPartitionDefState *MakeHashDefaultSubpartition(PartitionState *partitionState, char *partitionName,
char *tablespacename);
static void MakeDefaultSubpartitionName(PartitionState *partitionState, char **subPartitionName,
const char *partitionName);
* XXX UGLY HARD CODED BADNESS FOLLOWS XXX
*
* these should all be moved to someplace in the lib/catalog
* module, if not obliterated first.
* ----------------------------------------------------------------
*/
* Note:
* Should the system special case these attributes in the future?
* Advantage: consume much less space in the ATTRIBUTE relation.
* Disadvantage: special cases will be all over the place.
*/
* The initializers below do not include trailing variable length fields,
* but that's OK - we're never going to reference anything beyond the
* fixed-size portion of the structure anyway.
*/
static FormData_pg_attribute a1 = {0,
{"ctid"},
TIDOID,
0,
sizeof(ItemPointerData),
SelfItemPointerAttributeNumber,
0,
-1,
-1,
false,
'p',
's',
true,
false,
false,
true,
0};
static FormData_pg_attribute a2 = {0,
{"oid"},
OIDOID,
0,
sizeof(Oid),
ObjectIdAttributeNumber,
0,
-1,
-1,
true,
'p',
'i',
true,
false,
false,
true,
0};
static FormData_pg_attribute a3 = {0,
{"xmin"},
XIDOID,
0,
sizeof(TransactionId),
MinTransactionIdAttributeNumber,
0,
-1,
-1,
FLOAT8PASSBYVAL,
'p',
'd',
true,
false,
false,
true,
0};
static FormData_pg_attribute a4 = {0,
{"cmin"},
CIDOID,
0,
sizeof(CommandId),
MinCommandIdAttributeNumber,
0,
-1,
-1,
true,
'p',
'i',
true,
false,
false,
true,
0};
static FormData_pg_attribute a5 = {0,
{"xmax"},
XIDOID,
0,
sizeof(TransactionId),
MaxTransactionIdAttributeNumber,
0,
-1,
-1,
FLOAT8PASSBYVAL,
'p',
'd',
true,
false,
false,
true,
0};
static FormData_pg_attribute a6 = {0,
{"cmax"},
CIDOID,
0,
sizeof(CommandId),
MaxCommandIdAttributeNumber,
0,
-1,
-1,
true,
'p',
'i',
true,
false,
false,
true,
0};
* We decided to call this attribute "tableoid" rather than say
* "classoid" on the basis that in the future there may be more than one
* table of a particular class/type. In any case table is still the word
* used in SQL.
*/
static FormData_pg_attribute a7 = {0,
{"tableoid"},
OIDOID,
0,
sizeof(Oid),
TableOidAttributeNumber,
0,
-1,
-1,
true,
'p',
'i',
true,
false,
false,
true,
0};
#ifdef PGXC
* In XC we need some sort of node identification for each tuple
* We are adding another system column that would serve as node identifier.
* This is not only required by WHERE CURRENT OF but it can be used any
* where we want to know the originating Datanode of a tuple received
* at the Coordinator
*/
static FormData_pg_attribute a8 = {0,
{"xc_node_id"},
INT4OID,
0,
sizeof(int4),
XC_NodeIdAttributeNumber,
0,
-1,
-1,
true,
'p',
'i',
true,
false,
false,
true,
0};
static FormData_pg_attribute a9 = {0,
{"tablebucketid"},
INT2OID,
0,
sizeof(int2),
BucketIdAttributeNumber,
0,
-1,
-1,
true,
'p',
'i',
true,
false,
false,
true,
0};
static FormData_pg_attribute a10 = {0,
{"gs_tuple_uid"},
INT8OID,
0,
sizeof(int64),
UidAttributeNumber,
0,
-1,
-1,
true,
'p',
'd',
true,
false,
false,
true,
0};
#ifdef USE_SPQ
static FormData_pg_attribute a11 = {0,
{"_root_ctid"},
TIDOID,
0,
sizeof(ItemPointerData),
RootSelfItemPointerAttributeNumber,
0,
-1,
-1,
false,
'p',
's',
true,
false,
false,
true,
0};
static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10, &a11};
#else
static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10};
#endif
#else
static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7};
#endif
* This function returns a Form_pg_attribute pointer for a system attribute.
* Note that we elog if the presented attno is invalid, which would only
* happen if there's a problem upstream.
*/
Form_pg_attribute SystemAttributeDefinition(AttrNumber attno, bool relhasoids, bool relhasbucket, bool relhasuids)
{
if (attno >= 0 || attno < -(int)lengthof(SysAtt))
ereport(
ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), errmsg("invalid system attribute number %d", attno)));
if (attno == ObjectIdAttributeNumber && !relhasoids)
ereport(
ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), errmsg("invalid system attribute number %d", attno)));
if (attno == BucketIdAttributeNumber && !relhasbucket)
ereport(
ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), errmsg("invalid system attribute number %d", attno)));
if (attno == UidAttributeNumber && !relhasuids) {
ereport(
ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), errmsg("invalid system attribute number %d", attno)));
}
return SysAtt[-attno - 1];
}
* If the given name is a system attribute name, return a Form_pg_attribute
* pointer for a prototype definition. If not, return NULL.
*/
Form_pg_attribute SystemAttributeByName(const char* attname, bool relhasoids)
{
int j;
for (j = 0; j < (int)lengthof(SysAtt); j++) {
Form_pg_attribute att = SysAtt[j];
if (relhasoids || att->attnum != ObjectIdAttributeNumber) {
if (strcmp(NameStr(att->attname), attname) == 0)
return att;
}
}
return NULL;
}
int GetSysAttLength(bool hasBucketAttr)
{
if (hasBucketAttr) {
return lengthof(SysAtt);
} else {
return lengthof(SysAtt) - 1;
}
}
static void InitPartitionDef(Partition newPartition, Oid partOid, char strategy)
{
newPartition->pd_part->parttype = PART_OBJ_TYPE_TABLE_PARTITION;
newPartition->pd_part->parentid = partOid;
newPartition->pd_part->rangenum = 0;
newPartition->pd_part->intervalnum = 0;
newPartition->pd_part->partstrategy = strategy;
newPartition->pd_part->reltoastrelid = InvalidOid;
newPartition->pd_part->reltoastidxid = InvalidOid;
newPartition->pd_part->indextblid = InvalidOid;
newPartition->pd_part->reldeltarelid = InvalidOid;
newPartition->pd_part->reldeltaidx = InvalidOid;
newPartition->pd_part->relcudescrelid = InvalidOid;
newPartition->pd_part->relcudescidx = InvalidOid;
newPartition->pd_part->indisusable = true;
}
static void InitSubPartitionDef(Partition newPartition, Oid partOid, char strategy)
{
newPartition->pd_part->parttype = PART_OBJ_TYPE_TABLE_SUB_PARTITION;
newPartition->pd_part->parentid = partOid;
newPartition->pd_part->rangenum = 0;
newPartition->pd_part->intervalnum = 0;
newPartition->pd_part->partstrategy = strategy;
newPartition->pd_part->reltoastrelid = InvalidOid;
newPartition->pd_part->reltoastidxid = InvalidOid;
newPartition->pd_part->indextblid = InvalidOid;
newPartition->pd_part->reldeltarelid = InvalidOid;
newPartition->pd_part->reldeltaidx = InvalidOid;
newPartition->pd_part->relcudescrelid = InvalidOid;
newPartition->pd_part->relcudescidx = InvalidOid;
newPartition->pd_part->indisusable = true;
}
* XXX END OF UGLY HARD CODED BADNESS XXX
* ---------------------------------------------------------------- */
* heap_create - Create an uncataloged heap relation
*
* Note API change: the caller must now always provide the OID
* to use for the relation. The relfilenode may (and, normally,
* should) be left unspecified.
*
* rel->rd_rel is initialized by RelationBuildLocalRelation,
* and is mostly zeroes at return.
* ----------------------------------------------------------------
*/
Relation heap_create(const char* relname, Oid relnamespace, Oid reltablespace, Oid relid, Oid relfilenode,
Oid bucketOid, TupleDesc tupDesc, char relkind, char relpersistence, bool partitioned_relation, bool rowMovement,
bool shared_relation, bool mapped_relation, bool allow_system_table_mods, int8 row_compress, Datum reloptions,
Oid ownerid, bool skip_create_storage, TableAmType tam_type, int8 relindexsplit, StorageType storage_type,
bool newcbi, Oid accessMethodObjectId)
{
bool create_storage = false;
Relation rel;
bool isbucket = false;
if ((IsInitdb && EnableInitDBSegment) || (u_sess->attr.attr_common.IsInplaceUpgrade && ENABLE_DMS)) {
if (relpersistence == RELPERSISTENCE_PERMANENT) {
storage_type = SEGMENT_PAGE;
}
}
Assert(OidIsValid(relid));
* sanity checks
*/
if (!allow_system_table_mods &&
(IsSystemNamespace(relnamespace) || IsToastNamespace(relnamespace) || IsCStoreNamespace(relnamespace) ||
IsPackageSchemaOid(relnamespace) || IsPldeveloper(relnamespace)) && IsNormalProcessingMode()) {
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create \"%s.%s\"", get_namespace_name(relnamespace), relname),
errdetail("System catalog modifications are currently disallowed.")));
}
* Decide if we need storage or not, and handle a couple other special
* cases for particular relkinds.
*/
switch (relkind) {
case RELKIND_VIEW:
case RELKIND_CONTQUERY:
case RELKIND_COMPOSITE_TYPE:
case RELKIND_STREAM:
case RELKIND_FOREIGN_TABLE:
create_storage = false;
* Force reltablespace to zero if the relation has no physical
* storage. This is mainly just for cleanliness' sake.
*/
reltablespace = InvalidOid;
break;
case RELKIND_SEQUENCE:
case RELKIND_LARGE_SEQUENCE:
create_storage = true;
* Force reltablespace to zero for sequences, since we don't
* support moving them around into different tablespaces.
*/
reltablespace = InvalidOid;
break;
case RELKIND_GLOBAL_INDEX:
create_storage = true;
break;
default:
if (!partitioned_relation) {
create_storage = true;
} else {
create_storage = false;
}
break;
}
if (ENABLE_DSS && !partitioned_relation) {
* when we store systable to segment, we should allocate segment header page
* for all objects, to avoid some issues, like: pg_table_size for view.
* if the view has no segment header page, the seg_totalblocks' call will crash,
* because of read_head_buffer's magic number check fail.
*/
create_storage = true;
}
* Never allow a pg_class entry to explicitly specify the database's
* default tablespace in reltablespace; force it to zero instead. This
* ensures that if the database is cloned with a different default
* tablespace, the pg_class entry will still match where CREATE DATABASE
* will put the physically copied relation.
*
* Yes, this is a bit of a hack.
*/
reltablespace = ConvertToPgclassRelTablespaceOid(reltablespace);
* Unless otherwise requested, the physical ID (relfilenode) is initially
* the same as the logical ID (OID). When the caller did specify a
* relfilenode, it already exists; do not attempt to create it.
*/
if (OidIsValid(relfilenode)) {
if (u_sess->proc_cxt.IsBinaryUpgrade) {
if (!partitioned_relation && storage_type == SEGMENT_PAGE) {
isbucket = BUCKET_OID_IS_VALID(bucketOid) && !newcbi;
Oid database_id = (ConvertToRelfilenodeTblspcOid(reltablespace) == GLOBALTABLESPACE_OID) ?
InvalidOid : u_sess->proc_cxt.MyDatabaseId;
relfilenode = seg_alloc_segment(ConvertToRelfilenodeTblspcOid(reltablespace),
database_id, isbucket, relfilenode);
}
} else {
create_storage = false;
}
} else if ((storage_type == SEGMENT_PAGE && !partitioned_relation) ||
(storage_type == SEGMENT_PAGE && ENABLE_DSS && create_storage)) {
isbucket = BUCKET_OID_IS_VALID(bucketOid) && !newcbi;
Oid database_id = (ConvertToRelfilenodeTblspcOid(reltablespace) == GLOBALTABLESPACE_OID) ?
InvalidOid : u_sess->proc_cxt.MyDatabaseId;
relfilenode = (Oid)seg_alloc_segment(ConvertToRelfilenodeTblspcOid(reltablespace),
database_id, isbucket, InvalidBlockNumber);
ereport(LOG, (errmsg("Segment Relation %s(%u) set relfilenode %u xid %lu", relname, relid, relfilenode,
GetCurrentTransactionIdIfAny())));
} else {
relfilenode = relid;
}
* build the relcache entry.
*/
rel = RelationBuildLocalRelation(relname,
relnamespace,
tupDesc,
relid,
relfilenode,
reltablespace,
shared_relation,
mapped_relation,
relpersistence,
relkind,
row_compress,
reloptions,
tam_type,
relindexsplit,
storage_type,
accessMethodObjectId);
if (partitioned_relation) {
rel->rd_rel->parttype = PARTTYPE_PARTITIONED_RELATION;
}
rel->rd_rel->relrowmovement = rowMovement;
* Save newcbi as a context indicator to
* avoid missing information in later index building process.
*/
rel->newcbi = newcbi;
if (u_sess->attr.attr_common.IsInplaceUpgrade && !u_sess->upg_cxt.new_catalog_need_storage)
create_storage = false;
if (skip_create_storage) {
create_storage = false;
}
* Have the storage manager create the relation's disk file, if needed.
*
* We only create the main fork here, other forks will be created on
* demand.
*/
if (create_storage) {
rel->rd_bucketoid = bucketOid;
RelationOpenSmgr(rel);
RelationCreateStorage(rel->rd_node, relpersistence, ownerid, bucketOid, rel);
}
if (RelationUsesSpaceType(rel->rd_rel->relpersistence) == SP_TEMP) {
make_tmptable_cache_key(rel->rd_rel->relfilenode);
}
return rel;
}
* heap_create_with_catalog - Create a cataloged relation
*
* this is done in multiple steps:
*
* 1) CheckAttributeNamesTypes() is used to make certain the tuple
* descriptor contains a valid set of attribute names and types
*
* 2) pg_class is opened and get_relname_relid()
* performs a scan to ensure that no relation with the
* same name already exists.
*
* 3) heap_create() is called to create the new relation on disk.
*
* 4) TypeCreate() is called to define a new type corresponding
* to the new relation.
*
* 5) AddNewRelationTuple() is called to register the
* relation in pg_class.
*
* 6) AddNewAttributeTuples() is called to register the
* new relation's schema in pg_attribute.
*
* 7) StoreConstraints is called () - vadim 08/22/97
*
* 8) the relations are closed and the new relation's oid
* is returned.
*
* ----------------------------------------------------------------
*/
* CheckAttributeNamesTypes
*
* this is used to make certain the tuple descriptor contains a
* valid set of attribute names and datatypes. a problem simply
* generates ereport(ERROR) which aborts the current transaction.
* --------------------------------
*/
void CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind, bool allow_system_table_mods)
{
int i;
int j;
int natts = tupdesc->natts;
if (natts < 0 || natts > MaxHeapAttributeNumber)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS), errmsg("tables can have at most %d columns", MaxHeapAttributeNumber)));
* first check for collision with system attribute names
*
* Skip this for a view or type relation, since those don't have system
* attributes.
*/
if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_CONTQUERY) {
for (i = 0; i < natts; i++) {
if (SystemAttributeByName(NameStr(tupdesc->attrs[i].attname), tupdesc->tdhasoid) != NULL)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column name \"%s\" conflicts with a system column name",
NameStr(tupdesc->attrs[i].attname))));
}
}
* next check for repeated attribute names
*/
for (i = 1; i < natts; i++) {
for (j = 0; j < i; j++) {
if (strcmp(NameStr(tupdesc->attrs[j].attname), NameStr(tupdesc->attrs[i].attname)) == 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column name \"%s\" specified more than once", NameStr(tupdesc->attrs[j].attname))));
}
}
* next check the attribute types
*/
for (i = 0; i < natts; i++) {
CheckAttributeType(NameStr(tupdesc->attrs[i].attname),
tupdesc->attrs[i].atttypid,
tupdesc->attrs[i].attcollation,
NIL,
allow_system_table_mods);
}
}
* CheckAttributeType
*
* Verify that the proposed datatype of an attribute is legal.
* This is needed mainly because there are types (and pseudo-types)
* in the catalogs that we do not support as elements of real tuples.
* We also check some other properties required of a table column.
*
* If the attribute is being proposed for addition to an existing table or
* composite type, pass a one-element list of the rowtype OID as
* containing_rowtypes. When checking a to-be-created rowtype, it's
* sufficient to pass NIL, because there could not be any recursive reference
* to a not-yet-existing rowtype.
* --------------------------------
*/
void CheckAttributeType(
const char* attname, Oid atttypid, Oid attcollation, List* containing_rowtypes, bool allow_system_table_mods)
{
char att_typtype = get_typtype(atttypid);
Oid att_typelem;
if (atttypid == UNKNOWNOID) {
* Warn user, but don't fail, if column to be created has UNKNOWN type
* (usually as a result of a 'retrieve into' - jolly)
*/
ereport(WARNING,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" has type \"unknown\"", attname),
errdetail("Proceeding with relation creation anyway.")));
} else if (att_typtype == TYPTYPE_PSEUDO) {
* Refuse any attempt to create a pseudo-type column, except for a
* special hack for pg_statistic: allow ANYARRAY when modifying system
* catalogs (this allows creating pg_statistic and cloning it during
* VACUUM FULL)
*/
if (atttypid != ANYARRAYOID || !allow_system_table_mods) {
#ifndef ENABLE_MULTIPLE_NODES
if (u_sess->attr.attr_common.plsql_show_all_error) {
StringInfoData message;
initStringInfo(&message);
appendStringInfo(&message, "column \"%s\" has pseudo-type %s", attname, format_type_be(atttypid));
InsertErrorMessage(message.data, 0, true);
}
#endif
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" has pseudo-type %s", attname, format_type_be(atttypid))));
}
} else if (att_typtype == TYPTYPE_DOMAIN) {
* If it's a domain, recurse to check its base type.
*/
CheckAttributeType(attname, getBaseType(atttypid), attcollation, containing_rowtypes, allow_system_table_mods);
} else if (att_typtype == TYPTYPE_COMPOSITE || att_typtype == TYPTYPE_ABSTRACT_OBJECT) {
* For a composite type, recurse into its attributes.
*/
Relation relation;
TupleDesc tupdesc;
int i;
* Check for self-containment. Eventually we might be able to allow
* this (just return without complaint, if so) but it's not clear how
* many other places would require anti-recursion defenses before it
* would be safe to allow tables to contain their own rowtype.
*/
if (list_member_oid(containing_rowtypes, atttypid)) {
if (strcmp(attname, "pljson_list_data") == 0) {
return;
}
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("composite type %s cannot be made a member of itself", format_type_be(atttypid))));
}
containing_rowtypes = lcons_oid(atttypid, containing_rowtypes);
relation = relation_open(get_typ_typrelid(atttypid), AccessShareLock);
tupdesc = RelationGetDescr(relation);
for (i = 0; i < tupdesc->natts; i++) {
Form_pg_attribute attr = &tupdesc->attrs[i];
if (attr->attisdropped)
continue;
CheckAttributeType(NameStr(attr->attname),
attr->atttypid,
attr->attcollation,
containing_rowtypes,
allow_system_table_mods);
}
relation_close(relation, AccessShareLock);
containing_rowtypes = list_delete_first(containing_rowtypes);
} else if (att_typtype == TYPTYPE_TABLEOF) {
* For a table of type, find its base elemid and collation
*/
att_typelem = get_element_type(atttypid);
Oid att_collation = get_typcollation(att_typelem);
CheckAttributeType(attname, att_typelem, att_collation, containing_rowtypes, allow_system_table_mods);
} else if (OidIsValid((att_typelem = get_element_type(atttypid)))) {
* Must recurse into array types, too, in case they are composite.
*/
CheckAttributeType(attname, att_typelem, attcollation, containing_rowtypes, allow_system_table_mods);
}
* This might not be strictly invalid per SQL standard, but it is pretty
* useless, and it cannot be dumped, so we must disallow it.
*/
if (!OidIsValid(attcollation) && type_is_collatable(atttypid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("no collation was derived for column \"%s\" with collatable type %s",
attname,
format_type_be(atttypid)),
errhint("Use the COLLATE clause to set the collation explicitly.")));
}
void InsertTablebucketidAttribute(Oid newRelOid)
{
CatalogIndexState indstate;
Relation rel;
int tablebucketidIndex = -BucketIdAttributeNumber - 1;
* open pg_attribute and its indexes.
*/
rel = heap_open(AttributeRelationId, RowExclusiveLock);
indstate = CatalogOpenIndexes(rel);
FormData_pg_attribute attStruct;
errno_t rc = memcpy_s(&attStruct, sizeof(FormData_pg_attribute), (char*)SysAtt[tablebucketidIndex],
sizeof(FormData_pg_attribute));
securec_check(rc, "\0", "\0");
attStruct.attrelid = newRelOid;
InsertPgAttributeTuple(rel, &attStruct, indstate);
CatalogCloseIndexes(indstate);
heap_close(rel, RowExclusiveLock);
}
* InsertPgAttributeTuple
* Construct and insert a new tuple in pg_attribute.
*
* Caller has already opened and locked pg_attribute. new_attribute is the
* attribute to insert (but we ignore attacl and attoptions, which are always
* initialized to NULL).
*
* indstate is the index state for CatalogIndexInsert. It can be passed as
* NULL, in which case we'll fetch the necessary info. (Don't do this when
* inserting multiple attributes, because it's a tad more expensive.)
*/
void InsertPgAttributeTuple(Relation pg_attribute_rel, Form_pg_attribute new_attribute, CatalogIndexState indstate)
{
Datum values[Natts_pg_attribute];
bool nulls[Natts_pg_attribute];
HeapTuple tup;
errno_t rc;
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");
values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(new_attribute->attcacheoff);
values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attribute->attbyval);
values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attribute->attstorage);
values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
values[Anum_pg_attribute_attcmprmode - 1] = Int8GetDatum(new_attribute->attcmprmode);
values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
values[Anum_pg_attribute_attkvtype - 1] = Int8GetDatum(new_attribute->attkvtype);
nulls[Anum_pg_attribute_attacl - 1] = true;
nulls[Anum_pg_attribute_attoptions - 1] = true;
nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
nulls[Anum_pg_attribute_attinitdefval - 1] = true;
nulls[Anum_pg_attribute_attdroppedname - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
(void)simple_heap_insert(pg_attribute_rel, tup);
if (indstate != NULL)
CatalogIndexInsert(indstate, tup);
else
CatalogUpdateIndexes(pg_attribute_rel, tup);
heap_freetuple(tup);
}
static bool make_gs_depend_param_body(GsDependParamBody* gs_depend_param_body, const char* typ_name,
const char relkind, const Oid namespace_oid)
{
bool need_build_depend = false;
int cw = CompileWhich();
need_build_depend = (relkind == RELKIND_RELATION || relkind == RELKIND_COMPOSITE_TYPE) &&
(cw == PLPGSQL_COMPILE_PACKAGE_PROC || cw == PLPGSQL_COMPILE_PACKAGE || cw == PLPGSQL_COMPILE_PROC);
if (!need_build_depend) {
return false;
}
gs_depend_param_body->dependNamespaceOid = namespace_oid;
if (NULL != u_sess->plsql_cxt.curr_compile_context &&
NULL != u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package) {
PLpgSQL_package* pkg = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package;
gs_depend_param_body->dependPkgOid = pkg->pkg_oid;
gs_depend_param_body->dependPkgName = pkg->pkg_signature;
}
char* real_typ_name = ParseTypeName((char*)typ_name, gs_depend_param_body->dependPkgOid);
if (real_typ_name == NULL) {
gs_depend_param_body->dependName = pstrdup(typ_name);
} else {
gs_depend_param_body->dependName = real_typ_name;
}
gs_depend_param_body->refPosType = GSDEPEND_REFOBJ_POS_IN_TYPE;
gs_depend_param_body->type = GSDEPEND_OBJECT_TYPE_TYPE;
return true;
}
* AddNewAttributeTuples
*
* this registers the new relation's schema by adding
* tuples to pg_attribute.
* --------------------------------
*/
static void AddNewAttributeTuples(Oid new_rel_oid, TupleDesc tupdesc, char relkind,
bool oidislocal, int oidinhcount, bool hasbucket, bool hasuids, List* depend_extend, const char* typ_name, Oid namespace_oid)
{
Form_pg_attribute attr;
int i;
Relation rel;
CatalogIndexState indstate;
int natts = tupdesc->natts;
ObjectAddress myself, referenced;
* open pg_attribute and its indexes.
*/
rel = heap_open(AttributeRelationId, RowExclusiveLock);
indstate = CatalogOpenIndexes(rel);
GsDependParamBody gs_depend_param_body;
gsplsql_init_gs_depend_param_body(&gs_depend_param_body);
bool need_build_depend = false;
if (enable_plpgsql_gsdependency()) {
need_build_depend = make_gs_depend_param_body(&gs_depend_param_body, typ_name, relkind, namespace_oid);
}
ListCell* depend_extend_cell = list_head(depend_extend);
* First we add the user attributes. This is also a convenient place to
* add dependencies on their datatypes and collations.
*/
for (i = 0; i < natts; i++) {
attr = &tupdesc->attrs[i];
attr->attrelid = new_rel_oid;
attr->attstattarget = -1;
attr->attcacheoff = -1;
InsertPgAttributeTuple(rel, attr, indstate);
* During inplace/grey upgrade, we don't record dependencies
* for new catalog attributes. Also if the column is dropped
* no need to record dependency anymore
*/
if (!u_sess->attr.attr_common.IsInplaceUpgrade && !attr->attisdropped) {
myself.classId = RelationRelationId;
myself.objectId = new_rel_oid;
myself.objectSubId = i + 1;
referenced.classId = TypeRelationId;
referenced.objectId = attr->atttypid;
referenced.objectSubId = 0;
if (need_build_depend) {
if (NULL != depend_extend_cell) {
gs_depend_param_body.dependExtend = (TypeDependExtend*)lfirst(depend_extend_cell);
} else {
gs_depend_param_body.dependExtend = NULL;
}
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL, &gs_depend_param_body);
} else {
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
if (OidIsValid(attr->attcollation) && attr->attcollation != DEFAULT_COLLATION_OID) {
referenced.classId = CollationRelationId;
referenced.objectId = attr->attcollation;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
}
if (need_build_depend && NULL != depend_extend_cell) {
depend_extend_cell = lnext(depend_extend_cell);
}
}
if (need_build_depend) {
pfree_ext(gs_depend_param_body.dependName);
}
* Next we add the system attributes. Skip OID if rel has no OIDs. Skip
* all for a view or type relation. We don't bother with making datatype
* dependencies here, since presumably all these types are pinned.
*/
if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_CONTQUERY) {
for (i = 0; i < (int)lengthof(SysAtt); i++) {
FormData_pg_attribute attStruct;
errno_t rc;
if (!tupdesc->tdhasoid && SysAtt[i]->attnum == ObjectIdAttributeNumber)
continue;
#ifdef USE_SPQ
if (SysAtt[i]->attnum == RootSelfItemPointerAttributeNumber)
continue;
#endif
if (!hasbucket && SysAtt[i]->attnum == BucketIdAttributeNumber)
continue;
if (!hasuids && SysAtt[i]->attnum == UidAttributeNumber)
continue;
rc = memcpy_s(&attStruct, sizeof(FormData_pg_attribute), (char*)SysAtt[i], sizeof(FormData_pg_attribute));
securec_check(rc, "\0", "\0");
attStruct.attrelid = new_rel_oid;
if (attStruct.attnum == ObjectIdAttributeNumber) {
attStruct.attislocal = oidislocal;
attStruct.attinhcount = oidinhcount;
}
InsertPgAttributeTuple(rel, &attStruct, indstate);
}
}
* clean up
*/
CatalogCloseIndexes(indstate);
heap_close(rel, RowExclusiveLock);
}
static void AddNewGsSecEncryptedColumnsTuples(const Oid new_rel_oid, List *ceLst)
{
Relation rel;
CatalogIndexState indstate;
* open gs_encrypted_columns and its indexes.
*/
rel = heap_open(ClientLogicCachedColumnsId, RowExclusiveLock);
indstate = CatalogOpenIndexes(rel);
ListCell *li = NULL;
foreach (li, ceLst) {
CeHeapInfo *ceHeapInfo = (CeHeapInfo *)lfirst(li);
insert_gs_sec_encrypted_column_tuple(ceHeapInfo, rel, new_rel_oid, indstate);
}
* clean up
*/
CatalogCloseIndexes(indstate);
heap_close(rel, RowExclusiveLock);
}
* InsertPgClassTuple
*
* Construct and insert a new tuple in pg_class.
*
* Caller has already opened and locked pg_class.
* Tuple data is taken from new_rel_desc->rd_rel, except for the
* variable-width fields which are not present in a cached reldesc.
* relacl and reloptions are passed in Datum form (to avoid having
* to reference the data types in heap.h). Pass (Datum) 0 to set them
* to NULL.
* --------------------------------
*/
void InsertPgClassTuple(
Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, Datum relacl, Datum reloptions, char relkind, int2vector* bucketcol)
{
Form_pg_class rd_rel = new_rel_desc->rd_rel;
Datum values[Natts_pg_class];
bool nulls[Natts_pg_class];
HeapTuple tup;
errno_t rc;
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");
values[Anum_pg_class_relname - 1] = NameGetDatum(&rd_rel->relname);
values[Anum_pg_class_relnamespace - 1] = ObjectIdGetDatum(rd_rel->relnamespace);
values[Anum_pg_class_reltype - 1] = ObjectIdGetDatum(rd_rel->reltype);
values[Anum_pg_class_reloftype - 1] = ObjectIdGetDatum(rd_rel->reloftype);
values[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(rd_rel->relowner);
values[Anum_pg_class_relam - 1] = ObjectIdGetDatum(rd_rel->relam);
values[Anum_pg_class_relfilenode - 1] = ObjectIdGetDatum(rd_rel->relfilenode);
values[Anum_pg_class_reltablespace - 1] = ObjectIdGetDatum(rd_rel->reltablespace);
values[Anum_pg_class_relpages - 1] = Float8GetDatum(rd_rel->relpages);
values[Anum_pg_class_reltuples - 1] = Float8GetDatum(rd_rel->reltuples);
values[Anum_pg_class_relallvisible - 1] = Int32GetDatum(rd_rel->relallvisible);
values[Anum_pg_class_reltoastrelid - 1] = ObjectIdGetDatum(rd_rel->reltoastrelid);
values[Anum_pg_class_reltoastidxid - 1] = ObjectIdGetDatum(rd_rel->reltoastidxid);
values[Anum_pg_class_reldeltarelid - 1] = ObjectIdGetDatum(rd_rel->reldeltarelid);
values[Anum_pg_class_reldeltaidx - 1] = ObjectIdGetDatum(rd_rel->reldeltaidx);
values[Anum_pg_class_relcudescrelid - 1] = ObjectIdGetDatum(rd_rel->relcudescrelid);
values[Anum_pg_class_relcudescidx - 1] = ObjectIdGetDatum(rd_rel->relcudescidx);
values[Anum_pg_class_relhasindex - 1] = BoolGetDatum(rd_rel->relhasindex);
values[Anum_pg_class_relisshared - 1] = BoolGetDatum(rd_rel->relisshared);
values[Anum_pg_class_relpersistence - 1] = CharGetDatum(rd_rel->relpersistence);
values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[ANUM_PG_CLASS_RELCMPRS - 1] = CharGetDatum(rd_rel->relcmprs);
values[Anum_pg_class_relhasclusterkey - 1] = BoolGetDatum(rd_rel->relhasclusterkey);
values[Anum_pg_clsss_relrowmovement - 1] = BoolGetDatum(rd_rel->relrowmovement);
values[Anum_pg_class_parttype - 1] = CharGetDatum(rd_rel->parttype);
values[Anum_pg_class_relfrozenxid - 1] = ShortTransactionIdGetDatum(rd_rel->relfrozenxid);
if (relacl != (Datum)0)
values[Anum_pg_class_relacl - 1] = relacl;
else
nulls[Anum_pg_class_relacl - 1] = true;
if (reloptions != (Datum)0)
values[Anum_pg_class_reloptions - 1] = reloptions;
else
nulls[Anum_pg_class_reloptions - 1] = true;
if (RELKIND_IN_RTM)
values[Anum_pg_class_relfrozenxid64 - 1] = u_sess->utils_cxt.RecentXmin;
else
values[Anum_pg_class_relfrozenxid64 - 1] = InvalidTransactionId;
if (!IsSystemNamespace(rd_rel->relnamespace) && rd_rel->relkind == RELKIND_RELATION)
values[Anum_pg_class_relreplident - 1] = REPLICA_IDENTITY_DEFAULT;
else
values[Anum_pg_class_relreplident - 1] = REPLICA_IDENTITY_NOTHING;
if (new_rel_desc->relreplident && new_rel_desc->relreplident != REPLICA_IDENTITY_NOTHING) {
values[Anum_pg_class_relreplident - 1] = new_rel_desc->relreplident;
}
if (OidIsValid(new_rel_desc->rd_bucketoid)) {
Assert(new_rel_desc->storage_type == SEGMENT_PAGE);
values[Anum_pg_class_relbucket - 1] = ObjectIdGetDatum(new_rel_desc->rd_bucketoid);
} else if (new_rel_desc->storage_type == SEGMENT_PAGE) {
values[Anum_pg_class_relbucket - 1] = ObjectIdGetDatum(VirtualSegmentOid);
} else {
nulls[Anum_pg_class_relbucket - 1] = true;
}
#ifndef ENABLE_MULTIPLE_NODES
if (RELKIND_IN_RTM && !is_cstore_option(relkind, reloptions)) {
values[Anum_pg_class_relminmxid - 1] = GetOldestMultiXactId();
} else {
values[Anum_pg_class_relminmxid - 1] = InvalidMultiXactId;
}
#endif
if (bucketcol != NULL)
values[Anum_pg_class_relbucketkey - 1] = PointerGetDatum(bucketcol);
else
nulls[Anum_pg_class_relbucketkey - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
* The new tuple must have the oid already chosen for the rel. Sure would
* be embarrassing to do this sort of thing in polite company.
*/
HeapTupleSetOid(tup, new_rel_oid);
(void)simple_heap_insert(pg_class_desc, tup);
CatalogUpdateIndexes(pg_class_desc, tup);
heap_freetuple(tup);
}
* AddNewRelationTuple
*
* this registers the new relation in the catalogs by
* adding a tuple to pg_class.
* --------------------------------
*/
static void AddNewRelationTuple(Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, Oid new_type_oid,
Oid reloftype, Oid relowner, char relkind, char relpersistence, Datum relacl, Datum reloptions,
int2vector* bucketcol, bool ispartrel)
{
Form_pg_class new_rel_reltup;
* first we update some of the information in our uncataloged relation's
* relation descriptor.
*/
new_rel_reltup = new_rel_desc->rd_rel;
switch (relkind) {
case RELKIND_RELATION:
case RELKIND_MATVIEW:
case RELKIND_INDEX:
case RELKIND_TOASTVALUE:
case RELKIND_GLOBAL_INDEX:
new_rel_reltup->relpages = 0;
new_rel_reltup->reltuples = 0;
new_rel_reltup->relallvisible = 0;
break;
case RELKIND_SEQUENCE:
case RELKIND_LARGE_SEQUENCE:
new_rel_reltup->relpages = 1;
new_rel_reltup->reltuples = 1;
new_rel_reltup->relallvisible = 0;
break;
default:
new_rel_reltup->relpages = 0;
new_rel_reltup->reltuples = 0;
new_rel_reltup->relallvisible = 0;
break;
}
if (RELKIND_IN_RTM) {
* Initialize to the minimum XID that could put tuples in the table.
* We know that no xacts older than RecentXmin are still running, so
* that will do.
*/
new_rel_reltup->relfrozenxid = (ShortTransactionId)u_sess->utils_cxt.RecentXmin;
} else {
* Other relation types will not contain XIDs, so set relfrozenxid to
* InvalidTransactionId. (Note: a sequence does contain a tuple, but
* we force its xmin to be FrozenTransactionId always; see
* commands/sequence.c.)
*/
new_rel_reltup->relfrozenxid = (ShortTransactionId)InvalidTransactionId;
}
if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) {
new_rel_reltup->relfrozenxid = (ShortTransactionId)InvalidTransactionId;
}
new_rel_reltup->relowner = relowner;
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
new_rel_desc->rd_att->tdtypeid = new_type_oid;
InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid, relacl, reloptions, relkind, bucketcol);
}
#ifdef PGXC
* cmp_nodes
*
* Compare the Oids of two XC nodes
* to sort them in ascending order by their names
* --------------------------------
*/
static int cmp_nodes(const void* p1, const void* p2)
{
NameData node1 = {{0}};
NameData node2 = {{0}};
return strcmp(get_pgxc_nodename(*(Oid*)p1, &node1), get_pgxc_nodename(*(Oid*)p2, &node2));
}
static void CheckDistColsUnique(DistributeBy *distribBy, int distribKeyNum, int2 *attnum)
{
if (distribBy == NULL) {
return;
}
for (int i = 0; i < distribKeyNum - 1; i++) {
for (int j = i + 1; j < distribKeyNum; j++) {
if (attnum[i] == attnum[j]) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Include identical distribution column \"%s\"",
strVal(list_nth(distribBy->colname, i)))));
}
}
}
}
static List* GetDistColsPos(DistributeBy* distributeBy, TupleDesc desc)
{
int i;
List* pos = NULL;
ListCell* cell = NULL;
char* colname = NULL;
FormData_pg_attribute *attrs = desc->attrs;
foreach (cell, distributeBy->colname) {
colname = strVal((Value*)lfirst(cell));
for (i = 0; i < desc->natts; i++) {
if (strcmp(colname, attrs[i].attname.data) == 0) {
break;
}
}
Assert(i < desc->natts);
pos = lappend_int(pos, i);
}
return pos;
}
static void CheckListDistEntry(List* entry, int distkeynum, bool isDefault)
{
int boundaryLength = list_length(entry);
if (boundaryLength != distkeynum && !isDefault) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Distribution boundary length (%d) does not equal to the number of distribute keys (%d).",
boundaryLength, distkeynum)));
}
}
* Get the List Distribution entry's items in string format,
* the return value should be freed by caller.
*/
static char* DistEntryToString(List* entry)
{
Oid typoutput;
bool typIsVarlena = false;
char* outputstr = NULL;
ListCell* cell = NULL;
StringInfoData string;
initStringInfo(&string);
appendStringInfoChar(&string, '\'');
foreach (cell, entry) {
Const* element = (Const *)lfirst(cell);
if (cell != list_head(entry)) {
appendStringInfoChar(&string, ',');
}
if (element->constisnull) {
appendStringInfoString(&string, "NULL");
} else if (element->ismaxvalue) {
appendStringInfoString(&string, "MAXVALUE");
} else {
getTypeOutputInfo(element->consttype, &typoutput, &typIsVarlena);
outputstr = OidOutputFunctionCall(typoutput, element->constvalue);
appendStringInfo(&string, "%s", outputstr);
}
}
appendStringInfoChar(&string, '\'');
return string.data;
}
static bool PreCheckListSlice(ListSliceDefState* slicedef)
{
List* boundary = NULL;
List* boundaries = NULL;
ListCell* cell = NULL;
ListCell* next = NULL;
Const* firstConst = NULL;
boundaries = slicedef->boundaries;
foreach (cell, boundaries) {
next = cell;
boundary = (List*)lfirst(cell);
firstConst = linitial_node(Const, boundary);
if (firstConst->ismaxvalue) {
return true;
}
}
return false;
}
static int ConstCmp(const void* a, const void* b)
{
const SliceConstInfo* rea = (const SliceConstInfo*)a;
const SliceConstInfo* reb = (const SliceConstInfo*)b;
return partitonKeyCompare((Const**)rea->sliceBoundaryValue, (Const**)reb->sliceBoundaryValue, rea->sliceNum);
}
static void IsDuplicateBoundariesExist(SliceConstInfo* sliceConstInfo, int totalBoundariesNum, int distkeynum)
{
qsort(sliceConstInfo, totalBoundariesNum, sizeof(SliceConstInfo), ConstCmp);
for (int i = 0; i < totalBoundariesNum - 1; i++) {
int cmp = partitonKeyCompare(sliceConstInfo[i].sliceBoundaryValue,
sliceConstInfo[i + 1].sliceBoundaryValue, distkeynum);
if (cmp == 0) {
if (strcmp(sliceConstInfo[i].sliceName, sliceConstInfo[i + 1].sliceName) == 0) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("List value %s specified multiple times in slice %s.",
DistEntryToString(sliceConstInfo[i].sliceBoundary),
sliceConstInfo[i].sliceName)));
} else {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("List value %s specified multiple times in slices %s and %s.",
DistEntryToString(sliceConstInfo[i].sliceBoundary),
sliceConstInfo[i].sliceName,
sliceConstInfo[i + 1].sliceName)));
}
}
}
}
static int GetTotalBoundariesNum(List* sliceList)
{
int result = 0;
ListSliceDefState* slicedef = NULL;
List* boundaryList = NULL;
foreach_cell(sliceCell, sliceList) {
slicedef = (ListSliceDefState*)lfirst(sliceCell);
boundaryList = slicedef->boundaries;
result += boundaryList->length;
}
return result;
}
static void CheckDuplicateListSlices(List* pos, FormData_pg_attribute* attrs, DistributeBy *distby)
{
List* boundary = NULL;
List* sliceList = NULL;
ListSliceDefState* slicedef = NULL;
List* boundaryList = NULL;
int sliceCount = 0;
int totalBoundariesNum = 0;
bool is_intreval = false;
SliceConstInfo* sliceConstInfo = NULL;
int distkeynum = list_length(distby->colname);
List* constsList = NULL;
if (pos == NULL || attrs == NULL) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid list distribution table definition")));
}
sliceList = distby->distState->sliceList;
totalBoundariesNum = GetTotalBoundariesNum(sliceList);
sliceConstInfo = (SliceConstInfo*)palloc0(totalBoundariesNum * sizeof(SliceConstInfo));
foreach_cell(sliceCell, sliceList) {
slicedef = (ListSliceDefState*)lfirst(sliceCell);
boundaryList = slicedef->boundaries;
bool isDefault = PreCheckListSlice(slicedef);
if (isDefault) {
if (lnext(sliceCell) != NULL) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("DEFAULT slice must be last slice specified")));
}
continue;
}
foreach_cell(boundaryCell, boundaryList) {
boundary = (List*)lfirst(boundaryCell);
CheckListDistEntry(boundary, distkeynum, isDefault);
Const* curConstValue = GetPartitionValue(pos, attrs, boundary, is_intreval, false);
constsList = lappend(constsList, curConstValue);
sliceConstInfo[sliceCount].sliceName = slicedef->name;
sliceConstInfo[sliceCount].sliceNum = distkeynum;
sliceConstInfo[sliceCount].sliceBoundary = boundary;
for (int counter = 0; counter < pos->length; counter++) {
sliceConstInfo[sliceCount].sliceBoundaryValue[counter] = curConstValue + counter;
}
sliceCount++;
}
}
IsDuplicateBoundariesExist(sliceConstInfo, totalBoundariesNum, distkeynum);
if (constsList != NULL) {
list_free_deep(constsList);
}
pfree_ext(sliceConstInfo);
return;
}
static void CheckOneBoundaryValue(List* boundary, List* posList, TupleDesc desc)
{
int pos;
Const* srcConst = NULL;
Const* targetConst = NULL;
ListCell* boundaryCell = NULL;
ListCell* posCell = NULL;
FormData_pg_attribute* attrs = desc->attrs;
forboth(boundaryCell, boundary, posCell, posList) {
srcConst = (Const*)lfirst(boundaryCell);
if (srcConst->ismaxvalue || srcConst->constisnull) {
continue;
}
pos = lfirst_int(posCell);
targetConst = (Const*)GetTargetValue(&attrs[pos], srcConst, false);
if (!PointerIsValid(targetConst)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_OPERATION),
errmsg("slice boundary value can't be converted into distribution key data type")));
}
}
}
* check whether every boundary value can be converted to distribute key type successfully
*/
static void CheckBoundaryValues(DistributeBy *distributeby, List* posList, TupleDesc desc)
{
ListCell* cell = NULL;
ListCell* boundaryCell = NULL;
List* boundary = NULL;
ListSliceDefState* listDef = NULL;
RangePartitionDefState* rangeDef = NULL;
if (distributeby->disttype == DISTTYPE_RANGE) {
foreach(cell, distributeby->distState->sliceList) {
rangeDef = (RangePartitionDefState*)lfirst(cell);
CheckOneBoundaryValue(rangeDef->boundary, posList, desc);
}
} else {
foreach(cell, distributeby->distState->sliceList) {
listDef = (ListSliceDefState*)lfirst(cell);
foreach(boundaryCell, listDef->boundaries) {
boundary = (List*)lfirst(boundaryCell);
CheckOneBoundaryValue(boundary, posList, desc);
}
}
}
return;
}
static void CheckDistStateBoundaryValue(DistributeBy *distributeby, List* posList, TupleDesc desc)
{
int oldFormat = u_sess->attr.attr_sql.sql_compatibility;
PG_TRY();
{
* some scenario should report error,
* like: distribute by (int) (slice s1 values less than ('a')),
* 'a' can't be convert to integer, references to int4in
*/
u_sess->attr.attr_sql.sql_compatibility = A_FORMAT;
CheckBoundaryValues(distributeby, posList, desc);
}
PG_CATCH();
{
u_sess->attr.attr_sql.sql_compatibility = oldFormat;
PG_RE_THROW();
}
PG_END_TRY();
u_sess->attr.attr_sql.sql_compatibility = oldFormat;
return;
}
static void CheckDistBoundaries(DistributeBy *distributeby, TupleDesc desc)
{
List* posList = NULL;
if (distributeby == NULL || distributeby->distState == NULL) {
return;
}
posList = GetDistColsPos(distributeby, desc);
if (distributeby->disttype == DISTTYPE_RANGE) {
* 1. check length of every slice boundaries
* 2. check whether range slice boundaries ascending
*/
ComparePartitionValue(posList, desc->attrs, distributeby->distState->sliceList, false);
} else {
* DISTTYPE_LIST
* 1. check length of every slice boundaries
* 2. check whether list slice boundaries unique
*/
CheckDuplicateListSlices(posList, desc->attrs, distributeby);
}
CheckDistStateBoundaryValue(distributeby, posList, desc);
list_free_ext(posList);
return;
}
static void CheckNodeInNodeGroup(char** dnName, Oid dnOid, char* sliceName, Oid* nodeoids, int numnodes)
{
bool isExist = false;
for (int i = 0; i < numnodes; i++) {
if (PGXCNodeGetNodeId(dnOid, PGXC_NODE_DATANODE) == PGXCNodeGetNodeId(nodeoids[i], PGXC_NODE_DATANODE)) {
isExist = true;
break;
}
}
if (!isExist) {
if (u_sess->attr.attr_sql.enable_cluster_resize) {
* we should clear non-existed datanode name when cluster resizing,
* e.g. create table (like distribution)
* because the specified dn_name maybe doesn't exist after shrinking.
*/
*dnName = NULL;
ereport(WARNING,
(errmsg("the specified datanode %s in slice %s is not in node group, ignore it when cluster resizing",
*dnName, sliceName)));
} else {
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("datanode %s specified in slice %s is not contained in the specified node group",
*dnName, sliceName)));
}
}
}
static void CheckDistDatanodeValidity(DistributeBy* distributeby, Oid* nodeoids, int numnodes)
{
ListCell* cell = NULL;
Node* node = NULL;
DistState* distState = NULL;
Oid dnOid;
if (distributeby == NULL || distributeby->distState == NULL) {
return;
}
* Checks: 1. specified dn exists; 2. specified dn is contained in specifed node group.
*/
distState = distributeby->distState;
foreach (cell, distState->sliceList) {
node = (Node*)lfirst(cell);
switch (nodeTag(node)) {
case T_RangePartitionDefState: {
RangePartitionDefState *def = (RangePartitionDefState*)node;
if (def->tablespacename != NULL) {
dnOid = get_pgxc_datanodeoid(def->tablespacename, false);
CheckNodeInNodeGroup(&def->tablespacename, dnOid, def->partitionName, nodeoids, numnodes);
}
break;
}
case T_ListSliceDefState: {
ListSliceDefState* def = (ListSliceDefState*)node;
if (def->datanode_name != NULL) {
dnOid = get_pgxc_datanodeoid(def->datanode_name, false);
CheckNodeInNodeGroup(&def->datanode_name, dnOid, def->name, nodeoids, numnodes);
}
break;
}
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized node type: %d", (int)nodeTag(node))));
}
}
return;
}
static void CheckSliceRefNodeGroup(DistributeBy *distributeby, char* groupName)
{
Oid groupOid, baseGroupOid;
* If groupname is NULL, it means we didn't specify GROUP in table creation,
* so just use the default installation as target group
*/
if (groupName == NULL) {
groupName = PgxcGroupGetInstallationGroup();
}
groupOid = get_pgxc_groupoid(groupName, true);
baseGroupOid = get_pgxc_class_groupoid(distributeby->referenceoid);
if (baseGroupOid != groupOid) {
char *baseGroupName = get_pgxc_groupname(baseGroupOid);
ereport(ERROR,
(errcode(ERRCODE_INVALID_ATTRIBUTE),
errmsg("table's node group (%s) is not the same with referenced table's node group(%s)",
groupName, baseGroupName)));
}
return;
}
* Check whether base table can references the slice of another table
*/
static void CheckSliceReferenceValidity(Oid relid, DistributeBy *distributeby, TupleDesc descriptor, char* groupName)
{
char baseType;
Oid keyType;
Oid baseKeyType;
AttrNumber keyIdx;
AttrNumber baseKeyIdx;
DistributionType reftype;
char *colname = NULL;
RelationLocInfo* baseLocInfo = NULL;
ListCell* colAttrCell = NULL;
ListCell* colNameCell = NULL;
baseLocInfo = GetRelationLocInfo(distributeby->referenceoid);
if (baseLocInfo == NULL) {
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("The referenced table is not distributed")));
}
baseType = baseLocInfo->locatorType;
reftype = distributeby->disttype;
if (!((baseType == LOCATOR_TYPE_RANGE && reftype == DISTTYPE_RANGE) ||
(baseType == LOCATOR_TYPE_LIST && reftype == DISTTYPE_LIST))) {
FreeRelationLocInfo(baseLocInfo);
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("Assigned distribution strategy is not the same as that of base table")));
}
if (list_length(distributeby->colname) != list_length(baseLocInfo->partAttrNum)) {
FreeRelationLocInfo(baseLocInfo);
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("number of table's distribution keys is not the same with referenced table.")));
}
forboth(colAttrCell, baseLocInfo->partAttrNum, colNameCell, distributeby->colname) {
baseKeyIdx = lfirst_int(colAttrCell);
colname = strVal(lfirst(colNameCell));
baseKeyType = get_atttype(distributeby->referenceoid, baseKeyIdx);
keyIdx = get_attnum(relid, colname);
keyType = descriptor->attrs[keyIdx - 1].atttypid;
if (baseKeyType != keyType) {
FreeRelationLocInfo(baseLocInfo);
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("Column %s is not of the same datatype as base table", colname)));
}
}
CheckSliceRefNodeGroup(distributeby, groupName);
FreeRelationLocInfo(baseLocInfo);
return;
}
static char* GetRelationDistributionGroupName(PGXCSubCluster* subcluster, Oid* nodeoids, int numnodes, bool first)
{
char *group_name = NULL;
if (subcluster != NULL && subcluster->clustertype == SUBCLUSTER_GROUP) {
ListCell* lc = NULL;
Assert(list_length(subcluster->members) == 1);
foreach (lc, subcluster->members) {
group_name = strVal(lfirst(lc));
break;
}
} else if (subcluster != NULL && subcluster->clustertype == SUBCLUSTER_NODE) {
* If we use the grammar "create table to node", when we support
* multi-nodegroup, it must be converted to "create table to group",
* but we are not sure to find the group correct through "to node".
*
* In non-logic cluster mode, we use "to node" for create table to installatation
* group or redistribution group in restore mode, so we do this conversion here.
*
* In logic cluster mode, we use "to node" for create table to logic cluster
* node group in restore mode.
*/
group_name = DeduceGroupByNodeList(nodeoids, numnodes);
} else if (first) {
* Neither TO NODE nor TO GROUP is not specified in CREATE TABLE,
* the foreign table and hdfs table is created in installation group.
*/
group_name = PgxcGroupGetInstallationGroup();
}
* If groupname is NULL, it means we didn't specify GROUP in table creation,
* so just use the default installation as target group
*/
if (group_name == NULL) {
if (isRestoreMode || strcmp(u_sess->attr.attr_sql.default_storage_nodegroup, INSTALLATION_MODE) == 0) {
group_name = PgxcGroupGetInstallationGroup();
} else {
group_name = u_sess->attr.attr_sql.default_storage_nodegroup;
}
}
return group_name;
}
* AddRelationDistribution
*
* Add to pgxc_class table
* --------------------------------
*/
void AddRelationDistribution(const char *relname, Oid relid, DistributeBy* distributeby, PGXCSubCluster* subcluster,
List* parentOids, TupleDesc descriptor, bool isinstallationgroup, bool isbucket, int bucketmaplen)
{
char locatortype = '\0';
int hashalgorithm = 0;
int hashbuckets = 0;
int2* attnum = NULL;
ObjectAddress myself, referenced;
int numnodes;
Oid* nodeoids = NULL;
int distributeKeyNum = 0;
char* group_name = NULL;
Oid group_oid = InvalidOid;
int bucketCnt;
if (distributeby != NULL && distributeby->colname) {
distributeKeyNum = list_length(distributeby->colname);
attnum = (int2*)palloc(distributeKeyNum * sizeof(int2));
} else {
distributeKeyNum = 1;
attnum = (int2*)palloc(1 * sizeof(int2));
}
GetRelationDistributionItems(relid, distributeby, descriptor, &locatortype, &hashalgorithm, &hashbuckets, attnum);
CheckDistColsUnique(distributeby, distributeKeyNum, attnum);
CheckDistBoundaries(distributeby, descriptor);
nodeoids = GetRelationDistributionNodes(subcluster, &numnodes);
CheckDistDatanodeValidity(distributeby, nodeoids, numnodes);
group_name = GetRelationDistributionGroupName(subcluster, nodeoids, numnodes, isinstallationgroup);
(void)BucketMapCacheGetBucketmap(group_name, &bucketCnt);
if (bucketmaplen != 0 && bucketmaplen != bucketCnt) {
bucketCnt = bucketmaplen;
if (!is_pgxc_group_bucketcnt_exists(get_pgxc_groupoid(group_name, false), bucketmaplen, &group_name,
&group_oid)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_OPERATION),
errmsg("there is no group can match the bucket map length %d", bucketmaplen)));
}
Assert(OidIsValid(group_oid));
t_thrd.xact_cxt.PGXCGroupOid = group_oid;
}
PgxcClassCreate(
relid, locatortype, attnum, hashalgorithm, hashbuckets, numnodes, nodeoids, distributeKeyNum, group_name);
if (IsLocatorDistributedBySlice(locatortype) && distributeby != NULL) {
if (!OidIsValid(distributeby->referenceoid) && list_length(distributeby->distState->sliceList) < numnodes) {
ereport(NOTICE, (errmsg("table's slice count is less than datanode count.")));
}
if (OidIsValid(distributeby->referenceoid)) {
CheckSliceReferenceValidity(relid, distributeby, descriptor, group_name);
}
* generate a random startpos to reduce slice skewness.
* we should guarantee startpos the same in all CNs when creating a list/range distribution table.
* implement it later using ((uint32)random()) % ((uint32)numnodes),
* and route the startpos to other CNs.
*/
PgxcSliceCreate(relname, relid, distributeby, descriptor, nodeoids, (uint32)numnodes, 0);
}
pfree(attnum);
myself.classId = PgxcClassRelationId;
myself.objectId = relid;
myself.objectSubId = 0;
referenced.classId = RelationRelationId;
referenced.objectId = relid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
}
static const char* GetDistributeTypeName(DistributionType disttype)
{
const char *str = "";
switch (disttype) {
case DISTTYPE_HASH:
str = "hash";
break;
case DISTTYPE_REPLICATION:
str = "replication";
break;
case DISTTYPE_MODULO:
str = "modulo";
break;
case DISTTYPE_ROUNDROBIN:
str = "roundrobin";
break;
case DISTTYPE_HIDETAG:
str = "hidetag";
break;
case DISTTYPE_LIST:
str = "list";
break;
case DISTTYPE_RANGE:
str = "range";
break;
default:
break;
}
return str;
}
static void CheckDistributeKeyAndType(Oid relid, DistributeBy *distributeby,
TupleDesc descriptor, AttrNumber *attnum)
{
int i = 0;
AttrNumber localAttrNum = 0;
char *colname = NULL;
ListCell *cell = NULL;
foreach (cell, distributeby->colname) {
colname = strVal(lfirst(cell));
localAttrNum = get_attnum(relid, colname);
* Validate user-specified distribute column.
* System columns cannot be used.
*/
if (localAttrNum <= 0 && localAttrNum >= -(int)lengthof(SysAtt)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Invalid distribution column specified")));
}
if (distributeby->disttype == DISTTYPE_LIST || distributeby->disttype == DISTTYPE_RANGE) {
if (!IsTypeDistributableForSlice(descriptor->attrs[localAttrNum - 1].atttypid)) {
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("Column %s is not a %s distributable data type", colname,
GetDistributeTypeName(distributeby->disttype))));
}
} else {
if (!IsTypeDistributable(descriptor->attrs[localAttrNum - 1].atttypid)) {
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("Column %s is not a %s distributable data type", colname,
GetDistributeTypeName(distributeby->disttype))));
}
}
attnum[i++] = localAttrNum;
}
}
* GetRelationDistributionItems
* Obtain distribution type and related items based on deparsed information
* of clause DISTRIBUTE BY.
* Depending on the column types given a fallback to a safe distribution can be done.
*/
void GetRelationDistributionItems(Oid relid, DistributeBy* distributeby, TupleDesc descriptor, char* locatortype,
int* hashalgorithm, int* hashbuckets, AttrNumber* attnum)
{
int local_hashalgorithm = 0;
int local_hashbuckets = 0;
char local_locatortype = '\0';
AttrNumber local_attnum = 0;
if (distributeby == NULL) {
* If no distribution was specified, and we have not chosen
* one based on primary key or foreign key, use first column with
* a supported data type.
*/
FormData_pg_attribute attr;
int i;
local_locatortype = LOCATOR_TYPE_HASH;
for (i = 0; i < descriptor->natts; i++) {
attr = descriptor->attrs[i];
if (IsTypeDistributable(attr.atttypid)) {
local_attnum = i + 1;
attnum[0] = local_attnum;
Oid namespaceid = get_rel_namespace(relid);
* As create hdfs table will inner create a delta table. For preventing
* duplicate print notice, we check the relnamespace here to forbid
* print notice when create a delta table.
*/
if (!IsCStoreNamespace(namespaceid))
ereport(NOTICE,
(errcode(ERRCODE_SUCCESSFUL_COMPLETION),
errmsg("The 'DISTRIBUTE BY' clause is not specified. Using '%s' as the distribution column "
"by default.",
attr.attname.data),
errhint(
"Please use 'DISTRIBUTE BY' clause to specify suitable data distribution column.")));
break;
}
}
if (local_attnum == 0) {
FEATURE_NOT_PUBLIC_ERROR("There is no hash distributable column");
}
if (local_attnum == 0) {
attnum[0] = local_attnum;
local_locatortype = LOCATOR_TYPE_RROBIN;
}
} else {
* User specified distribution type
*/
switch (distributeby->disttype) {
case DISTTYPE_HASH:
CheckDistributeKeyAndType(relid, distributeby, descriptor, attnum);
local_locatortype = LOCATOR_TYPE_HASH;
break;
case DISTTYPE_MODULO:
CheckDistributeKeyAndType(relid, distributeby, descriptor, attnum);
local_locatortype = LOCATOR_TYPE_MODULO;
break;
case DISTTYPE_REPLICATION:
local_locatortype = LOCATOR_TYPE_REPLICATED;
attnum[0] = local_attnum;
break;
case DISTTYPE_ROUNDROBIN:
local_locatortype = LOCATOR_TYPE_RROBIN;
attnum[0] = local_attnum;
break;
case DISTTYPE_LIST:
CheckDistributeKeyAndType(relid, distributeby, descriptor, attnum);
local_locatortype = LOCATOR_TYPE_LIST;
break;
case DISTTYPE_RANGE:
CheckDistributeKeyAndType(relid, distributeby, descriptor, attnum);
local_locatortype = LOCATOR_TYPE_RANGE;
break;
default:
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Invalid distribution type")));
}
}
if (local_locatortype == LOCATOR_TYPE_HASH) {
local_hashalgorithm = 1;
local_hashbuckets = HASH_SIZE;
}
if (hashalgorithm != NULL)
*hashalgorithm = local_hashalgorithm;
if (hashbuckets != NULL)
*hashbuckets = local_hashbuckets;
if (locatortype != NULL)
*locatortype = local_locatortype;
}
HashBucketInfo* GetRelationBucketInfo(DistributeBy* distributeby,
TupleDesc tupledsc,
bool* createbucket,
bool hashbucket)
{
Form_pg_attribute attr = NULL;
int2vector* bucketkey = NULL;
HashBucketInfo* bucketinfo = NULL;
int nattr = tupledsc->natts;
Oid buckettmp[BUCKETDATALEN];
int bucketcnt = 0;
bool needbucket = *createbucket;
int i = 0;
if (!isRestoreMode && needbucket == true && t_thrd.xact_cxt.PGXCNodeId == -1) {
if (distributeby == NULL || distributeby->disttype != DISTTYPE_HASH) {
if (hashbucket) {
Assert(0);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Create hash table directly on DN is not allowed.")));
} else {
ereport(LOG, (errmsg("Create table without bucket when directly created on dn.")));
return NULL;
}
} else {
Assert(0);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Create hash table but there is no valid PGXCBucketMap.")));
}
}
bucketinfo = (HashBucketInfo*)palloc0(sizeof(HashBucketInfo));
bucketinfo->bucketOid = InvalidOid;
if (t_thrd.xact_cxt.PGXCNodeId == MAX_DN_NODE_NUM) {
Assert(u_sess->attr.attr_sql.enable_cluster_resize);
if (u_sess->attr.attr_sql.enable_cluster_resize && !isRestoreMode && needbucket == true) {
*createbucket = false;
ereport(LOG, (errmsg("Create table without bucket when PGXCNodeId is MAX_DN_NODE_NUM.")));
} else {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Invalid BucketMap or wrong NodeId(%d)", MAX_DN_NODE_NUM)));
}
}
if (distributeby == NULL) {
bool found = false;
bucketkey = buildint2vector(NULL, 1);
for (i = 0; i < nattr; i++) {
attr = &tupledsc->attrs[i];
if (IsTypeDistributable(attr->atttypid)) {
bucketkey->values[0] = attr->attnum;
bucketinfo->bucketcol = bucketkey;
found = true;
break;
}
}
if (!found) {
if (hashbucket) {
ereport(
ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("There is no hash distributable column")));
} else {
pfree(bucketinfo);
return NULL;
}
}
} else if (distributeby->disttype == DISTTYPE_HASH) {
* User specified distribution type
*/
AttrNumber local_attnum = 0;
ListCell* cell = NULL;
char* colname = NULL;
bool* is_exist = NULL;
int j;
* Validate user-specified hash column.
* System columns cannot be used.
*/
bucketkey = buildint2vector(NULL, distributeby->colname->length);
is_exist = (bool*)palloc0(nattr * sizeof(bool));
foreach (cell, distributeby->colname) {
colname = strVal(lfirst(cell));
for (j = 0; j < nattr; j++) {
attr = &tupledsc->attrs[j];
if (strcmp(colname, attr->attname.data) == 0) {
local_attnum = attr->attnum;
break;
}
}
if (j == nattr) {
pfree(bucketkey);
pfree(is_exist);
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Invalid distribution column specified")));
}
if (!IsTypeDistributable(attr->atttypid)) {
pfree(bucketkey);
pfree(is_exist);
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("Column %s is not a hash distributable data type", colname)));
}
if (is_exist[local_attnum - 1] == true) {
pfree(bucketkey);
pfree(is_exist);
ereport(ERROR, (errcode(ERRCODE_DUPLICATE_COLUMN), errmsg("duplicate partition key: %s", colname)));
}
bucketkey->values[i++] = local_attnum;
is_exist[local_attnum - 1] = true;
}
pfree(is_exist);
bucketinfo->bucketcol = bucketkey;
} else {
if (hashbucket) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("distributeby no hash is invalid with hashbucket")));
} else {
pfree(bucketinfo);
return NULL;
}
}
if (*createbucket == true) {
if (isRestoreMode) {
if (u_sess->storage_cxt.dumpHashbucketIds != NULL && u_sess->storage_cxt.dumpHashbucketIdNum != 0) {
for (i = 0; i < u_sess->storage_cxt.dumpHashbucketIdNum; i++) {
buckettmp[bucketcnt++] = u_sess->storage_cxt.dumpHashbucketIds[i];
}
}
} else {
for (int i = 0; i < t_thrd.xact_cxt.PGXCBucketCnt; i++) {
* if bucket seqno equals to PGXCNodeId which get from CN then
* then we will add a bucket for current relation later
*/
if (t_thrd.xact_cxt.PGXCBucketMap[i] == t_thrd.xact_cxt.PGXCNodeId) {
buckettmp[bucketcnt++] = i;
}
}
}
if (bucketcnt == 0) {
Assert(0);
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Invalid BucketMap or wrong NodeId(%d)", t_thrd.xact_cxt.PGXCNodeId)));
}
qsort(buckettmp, bucketcnt, sizeof(Oid), bid_cmp);
bucketinfo->bucketlist = buildoidvector(buckettmp, bucketcnt);
}
return bucketinfo;
}
* BuildRelationDistributionNodes
* Build an unsorted node Oid array based on a node name list.
*/
Oid* BuildRelationDistributionNodes(List* nodes, int* numnodes)
{
Oid* nodeoids = NULL;
ListCell* item = NULL;
*numnodes = 0;
nodeoids = (Oid*)palloc0(u_sess->pgxc_cxt.NumDataNodes * sizeof(Oid));
foreach (item, nodes) {
char* node_name = strVal(lfirst(item));
Oid noid = get_pgxc_nodeoid(node_name);
if (!OidIsValid(noid))
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("PGXC Node %s: object not defined", node_name)));
if (get_pgxc_nodetype(noid) != PGXC_NODE_DATANODE)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("PGXC node %s: not a Datanode", node_name)));
if (*numnodes != 0) {
bool is_listed = false;
int i;
for (i = 0; i < *numnodes; i++) {
if (nodeoids[i] == noid) {
is_listed = true;
break;
}
}
if (!is_listed) {
(*numnodes)++;
nodeoids[*numnodes - 1] = noid;
}
} else {
(*numnodes)++;
nodeoids[*numnodes - 1] = noid;
}
}
return nodeoids;
}
* GetRelationDistributionNodes
* Transform subcluster information generated by query deparsing of TO NODE or
* TO GROUP clause into a sorted array of nodes OIDs.
*/
Oid* GetRelationDistributionNodes(PGXCSubCluster* subcluster, int* numnodes)
{
ListCell* lc = NULL;
Oid* nodes = NULL;
*numnodes = 0;
if (subcluster == NULL) {
int i;
if (!isRestoreMode) {
* If no subcluster is defined, all the Datanodes are associated to the
* table. So obtain list of node Oids currenly known to the session.
* There could be a difference between the content of pgxc_node catalog
* table and current session, because someone may change nodes and not
* yet update session data.
*/
*numnodes = u_sess->pgxc_cxt.NumDataNodes;
if (*numnodes == 0)
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("No Datanode defined in cluster")));
nodes = (Oid*)palloc(u_sess->pgxc_cxt.NumDataNodes * sizeof(Oid));
for (i = 0; i < u_sess->pgxc_cxt.NumDataNodes; i++)
nodes[i] = PGXCNodeGetNodeOid(i, PGXC_NODE_DATANODE);
} else {
PgxcNodeGetOids(NULL, &nodes, NULL, numnodes, false);
if (*numnodes == 0)
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("No Datanode defined in cluster")));
}
}
if (nodes == NULL && subcluster->clustertype == SUBCLUSTER_GROUP) {
Assert(list_length(subcluster->members) == 1);
foreach (lc, subcluster->members) {
const char* group_name = strVal(lfirst(lc));
Oid group_oid = get_pgxc_groupoid(group_name);
if (!OidIsValid(group_oid))
ereport(
ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("PGXC Group %s: group not defined", group_name)));
*numnodes = get_pgxc_groupmembers(group_oid, &nodes);
}
} else if (nodes == NULL) {
* This is the case of a list of nodes names.
* Here the result is a sorted array of node Oids
*/
nodes = BuildRelationDistributionNodes(subcluster->members, numnodes);
}
return SortRelationDistributionNodes(nodes, *numnodes);
}
* SortRelationDistributionNodes
* Sort elements in a node array.
*/
Oid* SortRelationDistributionNodes(Oid* nodeoids, int numnodes)
{
qsort(nodeoids, numnodes, sizeof(Oid), cmp_nodes);
return nodeoids;
}
#endif
* AddNewRelationType -
*
* define a composite type corresponding to the new relation
* --------------------------------
*/
static ObjectAddress AddNewRelationType(const char* typname, Oid typeNamespace, Oid new_rel_oid, char new_rel_kind, Oid ownerid,
Oid new_row_type, Oid new_array_type, Oid typbasetype)
{
return TypeCreate(new_row_type,
typname,
typeNamespace,
new_rel_oid,
new_rel_kind,
ownerid,
-1,
TYPTYPE_COMPOSITE,
TYPCATEGORY_COMPOSITE,
false,
DEFAULT_TYPDELIM,
F_RECORD_IN,
F_RECORD_OUT,
F_RECORD_RECV,
F_RECORD_SEND,
InvalidOid,
InvalidOid,
InvalidOid,
InvalidOid,
false,
new_array_type,
typbasetype,
NULL,
NULL,
false,
'd',
'x',
-1,
0,
false,
InvalidOid);
}
static Datum AddSegmentOption(Datum relOptions)
{
DefElem *def = makeDefElem(pstrdup("segment"), (Node *)makeString((char *)"on"));
List* optsList = untransformRelOptions(relOptions);
optsList = lappend(optsList, def);
return transformRelOptions((Datum)0, optsList, NULL, NULL, false, false);
}
static Datum AddRelrewriteOption(Datum relOptions, Oid relrewrite)
{
DefElem *def = makeDefElem(pstrdup("relrewrite"), (Node *)makeInteger(relrewrite));
List* optsList = untransformRelOptions(relOptions);
optsList = lappend(optsList, def);
return transformRelOptions((Datum)0, optsList, NULL, NULL, false, false);
}
Node* GetColumnRef(Node* key, bool* isExpr, bool* isFunc)
{
Node* result = NULL;
A_Expr* aexpr = NULL;
Node* col = NULL;
FuncCall *funcexpr = NULL;
ListCell* cell = NULL;
switch (key->type) {
case T_ColumnRef:
result = key;
break;
case T_A_Expr:
*isExpr = true;
aexpr = (A_Expr*)key;
col = GetColumnRef(aexpr->lexpr, isExpr, isFunc);
if(col)
result = col;
else
result = GetColumnRef(aexpr->rexpr, isExpr, isFunc);
break;
case T_FuncCall:
*isExpr = true;
*isFunc = true;
funcexpr = (FuncCall*)key;
foreach(cell, funcexpr->args) {
result = GetColumnRef((Node*)(lfirst(cell)), isExpr, isFunc);
if (result)
break;
}
break;
default:
break;
}
return result;
}
static bool CheckPartitionExprKey(List* keys, char partStrategy, bool* isFunc)
{
bool isExpr = false;
ListCell* cell = NULL;
foreach (cell, keys) {
Node* col = GetColumnRef((Node*)lfirst(cell), &isExpr, isFunc);
if (!col)
ereport(ERROR,(errcode(ERRCODE_UNDEFINED_COLUMN),(errmsg("The partition key doesn't have any column."))));
if (isExpr && keys->length > 1)
ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),(errmsg("The multi partition expr keys are not supported."))));
}
if (isExpr && (partStrategy == PART_STRATEGY_VALUE || partStrategy == PART_STRATEGY_INTERVAL))
ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),(errmsg("The partition expr key doesn't support value or interval partition"))));
return !isExpr;
}
* heap_create_with_catalog
*
* creates a new cataloged relation. see comments above.
*
* Arguments:
* relname: name to give to new rel
* relnamespace: OID of namespace it goes in
* reltablespace: OID of tablespace it goes in
* relid: OID to assign to new rel, or InvalidOid to select a new OID
* reltypeid: OID to assign to rel's rowtype, or InvalidOid to select one
* reloftypeid: if a typed table, OID of underlying type; else InvalidOid
* ownerid: OID of new rel's owner
* tupdesc: tuple descriptor (source of column definitions)
* cooked_constraints: list of precooked check constraints and defaults
* relkind: relkind for new rel
* relpersistence: rel's persistence status (permanent, temp, or unlogged)
* shared_relation: TRUE if it's to be a shared relation
* mapped_relation: TRUE if the relation will use the relfilenode map
* oidislocal: TRUE if oid column (if any) should be marked attislocal
* oidinhcount: attinhcount to assign to oid column (if any)
* oncommit: ON COMMIT marking (only relevant if it's a temp table)
* reloptions: reloptions in Datum form, or (Datum) 0 if none
* use_user_acl: TRUE if should look for user-defined default permissions;
* if FALSE, relacl is always set NULL
* allow_system_table_mods: TRUE to allow creation in system namespaces
* Output parameters:
* typaddress: if not null, gets the object address of the new pg_type entry
*
* Returns the OID of the new relation
* --------------------------------
*/
Oid heap_create_with_catalog(const char *relname, Oid relnamespace, Oid reltablespace, Oid relid, Oid reltypeid,
Oid reloftypeid, Oid ownerid, TupleDesc tupdesc, List *cooked_constraints, char relkind,
char relpersistence, bool shared_relation, bool mapped_relation, bool oidislocal,
int oidinhcount, OnCommitAction oncommit, Datum reloptions, bool use_user_acl,
bool allow_system_table_mods, PartitionState *partTableState, int8 row_compress,
HashBucketInfo *bucketinfo, bool record_dependce, List *ceLst, StorageType storage_type,
LOCKMODE partLockMode, ObjectAddress *typaddress, List* depend_extend, Oid relrewrite,
Oid typbasetype)
{
Relation pg_class_desc;
Relation new_rel_desc;
Relation pg_partition_desc = NULL;
Acl* relacl = NULL;
Oid existing_relid;
Oid old_type_oid;
Oid new_type_oid = InvalidOid;
Oid new_array_oid = InvalidOid;
MemoryContext old_context;
Oid relfileid = InvalidOid;
Oid relbucketOid = InvalidOid;
int2vector* bucketcol = NULL;
bool relhasbucket = false;
ObjectAddress new_type_addr;
bool relhasuids = false;
if ((IsInitdb && EnableInitDBSegment) || (u_sess->attr.attr_common.IsInplaceUpgrade && ENABLE_DMS)) {
if (relpersistence == RELPERSISTENCE_UNLOGGED) {
relpersistence = RELPERSISTENCE_PERMANENT;
ereport(WARNING,
(errmsg("Store unlogged table in segment when enable system table segment")));
}
if (relpersistence == RELPERSISTENCE_PERMANENT && (relkind == RELKIND_RELATION ||
relkind == RELKIND_INDEX || relkind == RELKIND_GLOBAL_INDEX)) {
storage_type = SEGMENT_PAGE;
reloptions = AddSegmentOption(reloptions);
}
}
pg_class_desc = heap_open(RelationRelationId, RowExclusiveLock);
* sanity checks
*/
Assert(IsNormalProcessingMode() || IsBootstrapProcessingMode());
CheckAttributeNamesTypes(tupdesc, relkind, allow_system_table_mods);
* Check relation name to ensure that it doesn't conflict with existing synonym.
*/
if (!IsInitdb && GetSynonymOid(relname, relnamespace, true) != InvalidOid) {
ereport(ERROR,
(errmsg("relation name is already used by an existing synonym in schema \"%s\"",
get_namespace_name(relnamespace))));
}
* This would fail later on anyway, if the relation already exists. But
* by catching it here we can emit a nicer error message.
*/
existing_relid = get_relname_relid(relname, relnamespace);
if (existing_relid != InvalidOid) {
Oid namespaceid_of_existing_rel = get_rel_namespace(existing_relid);
char* namespace_of_existing_rel = get_namespace_name(namespaceid_of_existing_rel);
ereport(ERROR, (errmodule(MOD_COMMAND), errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists in schema \"%s\"", relname, namespace_of_existing_rel),
errdetail("creating new table with existing name in the same schema"),
errcause("the name of the table being created already exists"),
erraction("change a new name")));
}
* Since we are going to create a rowtype as well, also check for
* collision with an existing type name. If there is one and it's an
* autogenerated array, we can rename it out of the way; otherwise we can
* at least give a good error message.
*/
old_type_oid = GetSysCacheOid2(TYPENAMENSP, CStringGetDatum(relname), ObjectIdGetDatum(relnamespace));
if (OidIsValid(old_type_oid)) {
if (!moveArrayTypeName(old_type_oid, relname, relnamespace))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("type \"%s\" already exists", relname),
errhint("A relation has an associated type of the same name, "
"so you must use a name that doesn't conflict "
"with any existing type.")));
}
* Shared relations must be in pg_global (last-ditch check)
*/
if (shared_relation && reltablespace != GLOBALTABLESPACE_OID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("shared relations must be placed in pg_global tablespace")));
* Allocate an OID for the relation, unless we were told what to use.
*
* The OID will be the relfilenode as well, so make sure it doesn't
* collide with either pg_class OIDs or existing physical files.
*/
if (!OidIsValid(relid)) {
bool is_partition_toast_tbl = strncmp(relname, "pg_toast_part", strlen("pg_toast_part")) == 0;
* Use binary-upgrade override for pg_class.oid/relfilenode, if
* supplied.
*/
if (u_sess->proc_cxt.IsBinaryUpgrade && OidIsValid(u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_oid) &&
(relkind == RELKIND_RELATION || RELKIND_IS_SEQUENCE(relkind) || relkind == RELKIND_VIEW ||
relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE || relkind == RELKIND_MATVIEW
|| relkind == RELKIND_STREAM || relkind == RELKIND_CONTQUERY)) {
relid = u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_oid;
u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_oid = InvalidOid;
relfileid = u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_rfoid;
u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_rfoid = InvalidOid;
} else if (u_sess->proc_cxt.IsBinaryUpgrade && !is_partition_toast_tbl &&
OidIsValid(u_sess->upg_cxt.binary_upgrade_next_toast_pg_class_oid) &&
relkind == RELKIND_TOASTVALUE) {
relid = u_sess->upg_cxt.binary_upgrade_next_toast_pg_class_oid;
u_sess->upg_cxt.binary_upgrade_next_toast_pg_class_oid = InvalidOid;
relfileid = u_sess->upg_cxt.binary_upgrade_next_toast_pg_class_rfoid;
u_sess->upg_cxt.binary_upgrade_next_toast_pg_class_rfoid = InvalidOid;
} else if (u_sess->proc_cxt.IsBinaryUpgrade &&
(is_partition_toast_tbl && binary_upgrade_is_next_part_toast_pg_class_oid_valid()) &&
relkind == RELKIND_TOASTVALUE) {
relid = binary_upgrade_get_next_part_toast_pg_class_oid();
relfileid = binary_upgrade_get_next_part_toast_pg_class_rfoid();
} else if (u_sess->attr.attr_common.IsInplaceUpgrade &&
OidIsValid(u_sess->upg_cxt.Inplace_upgrade_next_heap_pg_class_oid) &&
(relkind == RELKIND_RELATION || RELKIND_IS_SEQUENCE(relkind) || relkind == RELKIND_VIEW ||
relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE
|| relkind == RELKIND_STREAM || relkind == RELKIND_CONTQUERY)) {
relid = u_sess->upg_cxt.Inplace_upgrade_next_heap_pg_class_oid;
u_sess->upg_cxt.Inplace_upgrade_next_heap_pg_class_oid = InvalidOid;
} else if (u_sess->attr.attr_common.IsInplaceUpgrade &&
OidIsValid(u_sess->upg_cxt.Inplace_upgrade_next_toast_pg_class_oid) &&
relkind == RELKIND_TOASTVALUE) {
Assert(!is_partition_toast_tbl);
relid = u_sess->upg_cxt.Inplace_upgrade_next_toast_pg_class_oid;
u_sess->upg_cxt.Inplace_upgrade_next_toast_pg_class_oid = InvalidOid;
} else
relid = GetNewRelFileNode(reltablespace, pg_class_desc, relpersistence);
}
* Determine the relation's initial permissions.
*/
if (use_user_acl) {
switch (relkind) {
case RELKIND_RELATION:
case RELKIND_VIEW:
case RELKIND_CONTQUERY:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
case RELKIND_STREAM:
relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid, relnamespace);
break;
case RELKIND_SEQUENCE:
case RELKIND_LARGE_SEQUENCE:
relacl = get_user_default_acl(ACL_OBJECT_SEQUENCE, ownerid, relnamespace);
break;
default:
relacl = NULL;
break;
}
} else
relacl = NULL;
* Determine the relation's bucket info.
*/
if (bucketinfo != NULL && !OidIsValid(bucketinfo->bucketOid)) {
Oid bucketid;
bucketcol = bucketinfo->bucketcol;
Assert(bucketcol != NULL);
if (bucketinfo->bucketlist != NULL) {
bucketid =
hash_any((unsigned char*)bucketinfo->bucketlist->values, bucketinfo->bucketlist->dim1 * sizeof(Oid));
relbucketOid = searchHashBucketByBucketid(bucketinfo->bucketlist, bucketid);
if (!OidIsValid(relbucketOid)) {
relbucketOid = insertHashBucketEntry(bucketinfo->bucketlist, bucketid);
}
} else {
relbucketOid = VirtualBktOid;
}
relhasbucket = true;
} else if (bucketinfo != NULL) {
relbucketOid = bucketinfo->bucketOid;
Assert(OidIsValid(relbucketOid));
relhasbucket = true;
}
if (OidIsValid(relrewrite)) {
reloptions = AddRelrewriteOption(reloptions, relrewrite);
}
bytea* hreloptions = heap_reloptions(relkind, reloptions, false);
TableAmType tam = get_tableam_from_reloptions(hreloptions, relkind, InvalidOid);
int8 indexsplit = get_indexsplit_from_reloptions(hreloptions, InvalidOid);
relhasuids = StdRdOptionsHasUids(hreloptions, relkind);
PartitionExprKeyInfo partExprKeyInfo = PartitionExprKeyInfo();
bool subpartkeyexprIsNull = true;
bool subpartkeyIsFunc = false;
if (partTableState) {
partExprKeyInfo.partkeyexprIsNull = CheckPartitionExprKey(partTableState->partitionKey, partTableState->partitionStrategy, &(partExprKeyInfo.partkeyIsFunc));
if (partTableState->subPartitionState)
subpartkeyexprIsNull = CheckPartitionExprKey(partTableState->subPartitionState->partitionKey,
partTableState->subPartitionState->partitionStrategy, &subpartkeyIsFunc);
}
* Create the relcache entry (mostly dummy at this point) and the physical
* disk file. (If we fail further down, it's the smgr's responsibility to
* remove the disk file again.)
*/
new_rel_desc = heap_create(relname,
relnamespace,
reltablespace,
relid,
relfileid,
(partTableState == NULL) ? relbucketOid : InvalidOid,
tupdesc,
relkind,
relpersistence,
partTableState ? (partTableState->partitionStrategy == PART_STRATEGY_VALUE ? false : true) : false,
partTableState ? (partTableState->rowMovement == ROWMOVEMENT_ENABLE ? true : false) : false,
shared_relation,
mapped_relation,
allow_system_table_mods,
row_compress,
reloptions,
ownerid,
false,
tam,
indexsplit,
storage_type
);
if (OidIsValid(relrewrite)) {
Relation OldHeap = heap_open(relrewrite, AccessExclusiveLock);
new_rel_desc->relreplident = OldHeap->relreplident;
heap_close(OldHeap, AccessExclusiveLock);
}
PgObjectType objectType = GetPgObjectTypePgClass(relkind);
if (objectType != OBJECT_TYPE_INVALID) {
PgObjectOption objectOpt = {true, true, true, true};
CreatePgObject(relid, objectType, ownerid, objectOpt);
}
Assert(relid == RelationGetRelid(new_rel_desc));
new_rel_desc->rd_bucketoid = relbucketOid;
* Decide whether to create an array type over the relation's rowtype. We
* do not create any array types for system catalogs (ie, those made
* during initdb). We create array types for regular relations, views,
* composite types and foreign tables ... but not, eg, for toast tables or
* sequences.
*/
if ((IsUnderPostmaster && !u_sess->attr.attr_common.IsInplaceUpgrade &&
(relkind == RELKIND_RELATION || relkind == RELKIND_VIEW || relkind == RELKIND_FOREIGN_TABLE ||
relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_MATVIEW || relkind == RELKIND_STREAM ||
relkind == RELKIND_CONTQUERY)) ||
(strcmp(relname, "bulk_exception") == 0) || (strcmp(relname, "desc_rec") == 0))
new_array_oid = AssignTypeArrayOid();
* Since defining a relation also defines a complex type, we add a new
* system type corresponding to the new relation. The OID of the type can
* be preselected by the caller, but if reltypeid is InvalidOid, we'll
* generate a new OID for it.
*/
old_context = CurrentMemoryContext;
* try to report a nicer error message rather than 'duplicate key'
*/
PG_TRY();
{
* NOTE: we could get a unique-index failure here, in case someone else is
* creating the same type name in parallel but hadn't committed yet when
* we checked for a duplicate name above. in such case we try to emit a
* nicer error message using try...catch
*/
new_type_addr = AddNewRelationType(
relname,
relnamespace,
relid,
relkind,
ownerid,
reltypeid,
new_array_oid,
typbasetype);
new_type_oid = new_type_addr.objectId;
if (typaddress)
*typaddress = new_type_addr;
}
PG_CATCH();
{
ErrorData* edata = NULL;
* if we are in here we are most likely in ErrorContext
* we have to switch to the old_context to allowing for CopyErrorData()
* because FlushErrorState will free everything in ErrorContext
*/
(void*)MemoryContextSwitchTo(old_context);
edata = CopyErrorData();
if (edata->sqlerrcode == ERRCODE_UNIQUE_VIOLATION) {
FlushErrorState();
FreeErrorData(edata);
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists", relname),
errdetail("because of creating the same table in parallel")));
} else {
ReThrowError(edata);
}
}
PG_END_TRY();
* Now make the array type if wanted.
*/
if (OidIsValid(new_array_oid)) {
char* relarrayname = NULL;
relarrayname = makeArrayTypeName(relname, relnamespace);
(void)TypeCreate(new_array_oid,
relarrayname,
relnamespace,
InvalidOid,
0,
ownerid,
-1,
TYPTYPE_BASE,
TYPCATEGORY_ARRAY,
false,
DEFAULT_TYPDELIM,
F_ARRAY_IN,
F_ARRAY_OUT,
F_ARRAY_RECV,
F_ARRAY_SEND,
InvalidOid,
InvalidOid,
F_ARRAY_TYPANALYZE,
new_type_oid,
true,
InvalidOid,
InvalidOid,
NULL,
NULL,
false,
'd',
'x',
-1,
0,
false,
InvalidOid);
pfree(relarrayname);
}
* for value-partitioned relation, we need set rd_rel->parttype here before
* insert pg_class enrty
*/
if (partTableState != NULL && partTableState->partitionStrategy == PART_STRATEGY_VALUE) {
new_rel_desc->rd_rel->parttype = PARTTYPE_VALUE_PARTITIONED_RELATION;
}
if (partTableState != NULL && partTableState->subPartitionState != NULL) {
new_rel_desc->rd_rel->parttype = PARTTYPE_SUBPARTITIONED_RELATION;
}
* now create an entry in pg_class for the relation.
*
* NOTE: we could get a unique-index failure here, in case someone else is
* creating the same relation name in parallel but hadn't committed yet
* when we checked for a duplicate name above.
*/
AddNewRelationTuple(pg_class_desc,
new_rel_desc,
relid,
new_type_oid,
reloftypeid,
ownerid,
relkind,
relpersistence,
PointerGetDatum(relacl),
reloptions,
bucketcol,
(partTableState != NULL));
* now add tuples to pg_attribute for the attributes in our new relation.
*/
AddNewAttributeTuples(
relid, new_rel_desc->rd_att, relkind, oidislocal, oidinhcount, relhasbucket, relhasuids, depend_extend, relname, relnamespace);
if (ceLst != NULL) {
AddNewGsSecEncryptedColumnsTuples(relid, ceLst);
}
if (partTableState && (RELKIND_TOASTVALUE != relkind)) {
pg_partition_desc = heap_open(PartitionRelationId, RowExclusiveLock);
if (partTableState->partitionStrategy == PART_STRATEGY_VALUE) {
addNewPartitionTupleForValuePartitionedTable(
pg_partition_desc, relname, relid, reltablespace, tupdesc, partTableState, reloptions);
} else {
addNewPartitionTupleForTable(pg_partition_desc,
relname,
relid,
reltablespace,
tupdesc,
partTableState,
ownerid,
reloptions,
&partExprKeyInfo);
partExprKeyInfo.partkeyexprIsNull = subpartkeyexprIsNull;
partExprKeyInfo.partkeyIsFunc = subpartkeyIsFunc;
addNewPartitionTuplesForPartition(pg_partition_desc,
relid,
reltablespace,
relbucketOid,
partTableState,
ownerid,
reloptions,
tupdesc,
partTableState->partitionStrategy,
storage_type,
partLockMode,
&partExprKeyInfo);
}
}
* Make a dependency link to force the relation to be deleted if its
* namespace is. Also make a dependency link to its owner, as well as
* dependencies for any roles mentioned in the default ACL.
*
* For composite types, these dependencies are tracked for the pg_type
* entry, so we needn't record them here. Likewise, TOAST tables don't
* need a namespace dependency (they live in a pinned namespace) nor an
* owner dependency (they depend indirectly through the parent table), nor
* should they have any ACL entries. The same applies for extension
* dependencies.
*
* If it's a temp table, we do not make it an extension member; this
* prevents the unintuitive result that deletion of the temp table at
* session end would make the whole extension go away.
*
* Also, skip this in bootstrap mode, since we don't make dependencies
* while bootstrapping.
*/
if (record_dependce == true && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_TOASTVALUE &&
!IsBootstrapProcessingMode()) {
ObjectAddress myself;
myself.classId = RelationRelationId;
myself.objectId = relid;
myself.objectSubId = 0;
if (u_sess->attr.attr_common.IsInplaceUpgrade && myself.objectId < FirstBootstrapObjectId)
recordPinnedDependency(&myself);
else {
ObjectAddress referenced;
referenced.classId = NamespaceRelationId;
referenced.objectId = relnamespace;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
recordDependencyOnOwner(RelationRelationId, relid, ownerid);
if (relpersistence != RELPERSISTENCE_TEMP)
recordDependencyOnCurrentExtension(&myself, false);
if (reloftypeid) {
referenced.classId = TypeRelationId;
referenced.objectId = reloftypeid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
if (relacl != NULL) {
int nnewmembers;
Oid* newmembers = NULL;
nnewmembers = aclmembers(relacl, &newmembers);
updateAclDependencies(RelationRelationId, relid, 0, ownerid, 0, NULL, nnewmembers, newmembers);
}
}
}
InvokeObjectAccessHook(OAT_POST_CREATE, RelationRelationId, relid, 0, NULL);
* Store any supplied constraints and defaults.
*
* NB: this may do a CommandCounterIncrement and rebuild the relcache
* entry, so the relation must be valid and self-consistent at this point.
* In particular, there are not yet constraints and defaults anywhere.
*/
StoreConstraints(new_rel_desc, cooked_constraints);
* If there's a special on-commit action, remember it
*/
if (oncommit != ONCOMMIT_NOOP)
register_on_commit_action(relid, oncommit);
if (relpersistence == RELPERSISTENCE_UNLOGGED) {
Assert(RELKIND_IN_RTM);
heap_create_init_fork(new_rel_desc);
}
* ok, the relation has been cataloged, so close our relations and return
* the OID of the newly created relation.
*/
heap_close(new_rel_desc, NoLock);
heap_close(pg_class_desc, RowExclusiveLock);
if (pg_partition_desc) {
heap_close(pg_partition_desc, RowExclusiveLock);
}
return relid;
}
* Set up an init fork for an unlogged table so that it can be correctly
* reinitialized on restart. An immediate sync is required even if the
* page has been logged, because the write did not go through
* shared_buffers and therefore a concurrent checkpoint may have moved
* the redo pointer past our xlog record. Recovery may as well remove it
* while replaying, for example, XLOG_DBASE_CREATE or XLOG_TBLSPC_CREATE
* record. Therefore, logging is necessary even if wal_level=minimal.
*/
void heap_create_init_fork(Relation rel)
{
RelationOpenSmgr(rel);
smgrcreate(rel->rd_smgr, INIT_FORKNUM, false);
log_smgrcreate(&rel->rd_smgr->smgr_rnode.node, INIT_FORKNUM);
smgrimmedsync(rel->rd_smgr, INIT_FORKNUM);
}
* RelationRemoveInheritance
*
* Formerly, this routine checked for child relations and aborted the
* deletion if any were found. Now we rely on the dependency mechanism
* to check for or delete child relations. By the time we get here,
* there are no children and we need only remove any pg_inherits rows
* linking this relation to its parent(s).
*/
static void RelationRemoveInheritance(Oid relid)
{
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key;
HeapTuple tuple;
catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
ScanKeyInit(&key, Anum_pg_inherits_inhrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true, NULL, 1, &key);
while (HeapTupleIsValid(tuple = systable_getnext(scan)))
simple_heap_delete(catalogRelation, &tuple->t_self);
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
}
* DeleteRelationTuple
*
* Remove pg_class row for the given relid.
*
* Note: this is shared by relation deletion and index deletion. It's
* not intended for use anyplace else.
*/
void DeleteRelationTuple(Oid relid)
{
Relation pg_class_desc;
HeapTuple tup;
pg_class_desc = heap_open(RelationRelationId, RowExclusiveLock);
tup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tup))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid)));
simple_heap_delete(pg_class_desc, &tup->t_self);
ReleaseSysCache(tup);
heap_close(pg_class_desc, RowExclusiveLock);
}
* DeleteAttributeTuples
*
* Remove pg_attribute rows for the given relid.
*
* Note: this is shared by relation deletion and index deletion. It's
* not intended for use anyplace else.
*/
void DeleteAttributeTuples(Oid relid)
{
Relation attrel;
SysScanDesc scan;
ScanKeyData key[1];
HeapTuple atttup;
attrel = heap_open(AttributeRelationId, RowExclusiveLock);
ScanKeyInit(&key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
scan = systable_beginscan(attrel, AttributeRelidNumIndexId, true, NULL, 1, key);
while ((atttup = systable_getnext(scan)) != NULL)
simple_heap_delete(attrel, &atttup->t_self);
systable_endscan(scan);
heap_close(attrel, RowExclusiveLock);
}
* DeleteSystemAttributeTuples
*
* Remove pg_attribute rows for system columns of the given relid.
*
* Note: this is only used when converting a table to a view. Views don't
* have system columns, so we should remove them from pg_attribute.
*/
void DeleteSystemAttributeTuples(Oid relid)
{
Relation attrel;
SysScanDesc scan;
ScanKeyData key[2];
HeapTuple atttup;
attrel = heap_open(AttributeRelationId, RowExclusiveLock);
ScanKeyInit(&key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
ScanKeyInit(&key[1], Anum_pg_attribute_attnum, BTLessEqualStrategyNumber, F_INT2LE, Int16GetDatum(0));
scan = systable_beginscan(attrel, AttributeRelidNumIndexId, true, NULL, 2, key);
while ((atttup = systable_getnext(scan)) != NULL)
simple_heap_delete(attrel, &atttup->t_self);
systable_endscan(scan);
heap_close(attrel, RowExclusiveLock);
}
* RemoveAttributeById
*
* This is the guts of ALTER TABLE DROP COLUMN: actually mark the attribute
* deleted in pg_attribute. We also remove pg_statistic entries for it.
* (Everything else needed, such as getting rid of any pg_attrdef entry,
* is handled by dependency.c.)
*
* During inplace or online upgrade, we may need to delete newly added columns
* to perform upgrade rollback. In that case, we remove the attribute's
* pg_attribute entry and decrease the relation's relnatts in pg_class.
* We don't need to rewrite the whole catalog, since there should be no entry
* of new schema version or, even if there was, they should have been removed
* during proceeding rollback procedure.
*/
void RemoveAttributeById(Oid relid, AttrNumber attnum)
{
Relation rel = NULL;
Relation attr_rel = NULL;
Relation pgclass = NULL;
HeapTuple reltuple;
HeapTuple atttuple;
Form_pg_attribute attStruct;
char newattname[NAMEDATALEN];
bool isRedisDropColumn = false;
Datum values[Natts_pg_attribute] = { 0 };
bool nulls[Natts_pg_attribute] = { 0 };
bool replaces[Natts_pg_attribute] = { 0 };
const int keyNum = 2;
ScanKeyData key[keyNum];
SysScanDesc scan;
HeapTuple newatttuple;
* Grab an exclusive lock on the target table, which we will NOT release
* until end of transaction. (In the simple case where we are directly
* dropping this column, AlterTableDropColumn already did this ... but
* when cascading from a drop of some other object, we may not have any
* lock.)
*/
rel = relation_open(relid, AccessExclusiveLock);
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
ScanKeyInit(&key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
ScanKeyInit(&key[1], Anum_pg_attribute_attnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(attnum));
scan = systable_beginscan(attr_rel, AttributeRelidNumIndexId, true, SnapshotSelf, keyNum, key);
atttuple = systable_getnext(scan);
if (!HeapTupleIsValid(atttuple))
{
Assert(0);
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for attribute %d of relation %u", attnum, relid)));
}
if (RelationIsRedistributeDest(rel) && (attnum > 0 && attnum == rel->rd_att->natts))
isRedisDropColumn = true;
if (attnum < 0 || isRedisDropColumn) {
simple_heap_delete(attr_rel, &atttuple->t_self);
} else {
errno_t rc;
attStruct = (Form_pg_attribute)GETSTRUCT(atttuple);
values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(true);
* Set the type OID to invalid. A dropped attribute's type link
* cannot be relied on (once the attribute is dropped, the type might
* be too). Fortunately we do not need the type row --- the only
* really essential information is the type's typlen and typalign,
* which are preserved in the attribute's attlen and attalign. We set
* atttypid to zero here as a means of catching code that incorrectly
* expects it to be valid.
*/
values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(false);
values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(0);
values[Anum_pg_attribute_attdroppedname - 1] = NameGetDatum(&(attStruct->attname));
* Change the column name to something that isn't likely to conflict
*/
rc =
snprintf_s(newattname, sizeof(newattname), sizeof(newattname) - 1, "........pg.dropped.%d........", attnum);
securec_check_ss(rc, "\0", "\0");
values[Anum_pg_attribute_attname - 1] = NameGetDatum(newattname);
replaces[Anum_pg_attribute_attisdropped - 1] = true;
replaces[Anum_pg_attribute_atttypid - 1] = true;
replaces[Anum_pg_attribute_attnotnull - 1] = true;
replaces[Anum_pg_attribute_attstattarget - 1] = true;
replaces[Anum_pg_attribute_attdroppedname - 1] = true;
replaces[Anum_pg_attribute_attname - 1] = true;
newatttuple = heap_modify_tuple(atttuple, RelationGetDescr(attr_rel), values, nulls, replaces);
simple_heap_update(attr_rel, &newatttuple->t_self, newatttuple);
CatalogUpdateIndexes(attr_rel, newatttuple);
heap_freetuple_ext(newatttuple);
}
systable_endscan(scan);
* Because updating the pg_attribute row will trigger a relcache flush for
* the target relation, we need not do anything else to notify other
* backends of the change.
*/
heap_close(attr_rel, RowExclusiveLock);
if (attnum > 0) {
if (RELATION_IS_GLOBAL_TEMP(rel)) {
remove_gtt_att_statistic(relid, attnum);
} else {
RemoveStatistics<'c'>(relid, attnum);
}
}
if (isRedisDropColumn) {
pgclass = heap_open(RelationRelationId, RowExclusiveLock);
reltuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(reltuple))
ereport(
ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid)));
((Form_pg_class)GETSTRUCT(reltuple))->relnatts -= 1;
simple_heap_update(pgclass, &reltuple->t_self, reltuple);
CatalogUpdateIndexes(pgclass, reltuple);
heap_freetuple(reltuple);
heap_close(pgclass, RowExclusiveLock);
}
relation_close(rel, NoLock);
}
* RemoveAttrDefault
*
* If the specified relation/attribute has a default, remove it.
* (If no default, raise error if complain is true, else return quietly.)
*/
void RemoveAttrDefault(Oid relid, AttrNumber attnum, DropBehavior behavior, bool complain, bool internal)
{
Relation attrdef_rel;
ScanKeyData scankeys[2];
SysScanDesc scan;
HeapTuple tuple;
bool found = false;
attrdef_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
ScanKeyInit(&scankeys[0], Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
ScanKeyInit(&scankeys[1], Anum_pg_attrdef_adnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(attnum));
scan = systable_beginscan(attrdef_rel, AttrDefaultIndexId, true, NULL, 2, scankeys);
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
ObjectAddress object;
object.classId = AttrDefaultRelationId;
object.objectId = HeapTupleGetOid(tuple);
object.objectSubId = 0;
performDeletion(&object, behavior, internal ? PERFORM_DELETION_INTERNAL : 0);
found = true;
}
systable_endscan(scan);
heap_close(attrdef_rel, RowExclusiveLock);
if (complain && !found)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("could not find attrdef tuple for relation %u attnum %d", relid, attnum)));
}
* RemoveAttrDefaultById
*
* Remove a pg_attrdef entry specified by OID. This is the guts of
* attribute-default removal. Note it should be called via performDeletion,
* not directly.
*/
void RemoveAttrDefaultById(Oid attrdefId)
{
Relation attrdef_rel;
Relation attr_rel;
Relation myrel;
ScanKeyData scankeys[1];
SysScanDesc scan;
HeapTuple tuple;
Oid myrelid;
AttrNumber myattnum;
attrdef_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
ScanKeyInit(&scankeys[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(attrdefId));
scan = systable_beginscan(attrdef_rel, AttrDefaultOidIndexId, true, NULL, 1, scankeys);
tuple = systable_getnext(scan);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("could not find tuple for attrdef %u", attrdefId)));
myrelid = ((Form_pg_attrdef)GETSTRUCT(tuple))->adrelid;
myattnum = ((Form_pg_attrdef)GETSTRUCT(tuple))->adnum;
myrel = relation_open(myrelid, AccessExclusiveLock);
simple_heap_delete(attrdef_rel, &tuple->t_self);
systable_endscan(scan);
heap_close(attrdef_rel, RowExclusiveLock);
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
tuple = SearchSysCacheCopy2(ATTNUM, ObjectIdGetDatum(myrelid), Int16GetDatum(myattnum));
if (!HeapTupleIsValid(tuple))
{
Assert(0);
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for attribute %d of relation %u", myattnum, myrelid)));
}
((Form_pg_attribute)GETSTRUCT(tuple))->atthasdef = false;
simple_heap_update(attr_rel, &tuple->t_self, tuple);
CatalogUpdateIndexes(attr_rel, tuple);
* Our update of the pg_attribute row will force a relcache rebuild, so
* there's nothing else to do here.
*/
heap_close(attr_rel, RowExclusiveLock);
relation_close(myrel, NoLock);
}
* Remove error info table file for bulkload
* there are problems in a rollback transaction
*/
static void RemoveBulkLoadErrorTableFile(Oid databaseid, Oid relid)
{
char filepath[PATH_MAX + 1];
getErrorTableFilePath(filepath, PATH_MAX + 1, databaseid, relid);
struct stat st;
if (0 == stat(filepath, &st)) {
if (unlink(filepath)) {
ereport(ERROR, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", filepath)));
}
}
}
#ifdef ENABLE_MOT
static void MotFdwDropForeignRelation(Relation rel, FdwRoutine* fdwRoutine)
{
if (fdwRoutine->GetFdwType != NULL && fdwRoutine->GetFdwType() == MOT_ORC && fdwRoutine->ValidateTableDef != NULL) {
DropForeignStmt stmt;
stmt.type = T_DropForeignStmt;
stmt.relkind = RELKIND_RELATION;
stmt.reloid = RelationGetRelid(rel);
stmt.indexoid = 0;
stmt.name = rel->rd_rel->relname.data;
fdwRoutine->ValidateTableDef((Node*)&stmt);
}
}
#endif
* heap_drop_with_catalog - removes specified relation from catalogs
*
* Note that this routine is not responsible for dropping objects that are
* linked to the pg_class entry via dependencies (for example, indexes and
* constraints). Those are deleted by the dependency-tracing logic in
* dependency.c before control gets here. In general, therefore, this routine
* should never be called directly; go through performDeletion() instead.
*/
void heap_drop_with_catalog(Oid relid)
{
Relation rel;
* Open and lock the relation.
*/
rel = relation_open(relid, AccessExclusiveLock);
if (RelationUsesSpaceType(rel->rd_rel->relpersistence) == SP_TEMP) {
make_tmptable_cache_key(rel->rd_rel->relfilenode);
}
* There can no longer be anyone *else* touching the relation, but we
* might still have open queries or cursors, or pending trigger events, in
* our own session.
*/
CheckTableNotInUse(rel, "DROP TABLE");
* This effectively deletes all rows in the table, and may be done in a
* serializable transaction. In that case we must record a rw-conflict in
* to this transaction from each transaction holding a predicate lock on
* the table.
*/
CheckTableForSerializableConflictIn(rel);
* Delete pg_foreign_table tuple first.
*/
bool flag = rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE || rel->rd_rel->relkind == RELKIND_STREAM;
if (flag) {
Relation foreignRel;
HeapTuple tuple;
foreignRel = heap_open(ForeignTableRelationId, RowExclusiveLock);
tuple = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for foreign table %u", relid)));
RemoveBulkLoadErrorTableFile(foreignRel->rd_node.dbNode, relid);
simple_heap_delete(foreignRel, &tuple->t_self);
ReleaseSysCache(tuple);
heap_close(foreignRel, RowExclusiveLock);
* When we drop a foreign partition table, we need to delete the corresponding
* tuple in pg_partition.
*/
FdwRoutine* fdwRoutine = GetFdwRoutineByRelId(relid, true);
if (fdwRoutine != NULL) {
if (NULL != fdwRoutine->PartitionTblProcess) {
fdwRoutine->PartitionTblProcess(NULL, relid, HDFS_DROP_PARTITIONED_FOREIGNTBL);
}
#ifdef ENABLE_MOT
MotFdwDropForeignRelation(rel, fdwRoutine);
#endif
}
}
if (RelationIsValuePartitioned(rel)) {
HeapTuple partTuple = searchPgPartitionByParentIdCopy(PART_OBJ_TYPE_PARTED_TABLE, relid);
if (partTuple) {
Relation pg_partitionRel = heap_open(PartitionRelationId, RowExclusiveLock);
simple_heap_delete(pg_partitionRel, &partTuple->t_self);
heap_freetuple(partTuple);
heap_close(pg_partitionRel, RowExclusiveLock);
} else
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg(
"Catalog table pg_partition may get trashed on table %s as it is not consitant with pg_class",
RelationGetRelationName(rel))));
}
if (RELATION_IS_PARTITIONED(rel)) {
heapDropPartitionTable(rel);
}
CheckGttTableInUse(rel);
* When dropping temp objects, we need further check whether current datanode is
* suffered from an unclean shutdown without gsql reconnect, as in this case the
* context of temp backend thread is inconsistant with other datanodes (timelineID),
* in order to prevent user further operator temp object, we error-out the DROP
* statement to tell user to have to re-connect server.
*/
if (STMT_RETRY_ENABLED) {
} else if (RelationIsLocalTemp(rel)) {
if (!validateTempNamespace(u_sess->catalog_cxt.myTempNamespace))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEMP_OBJECTS),
errmsg("Temp tables are invalid because datanode %s restart. "
"Quit your session to clean invalid temp tables.",
g_instance.attr.attr_common.PGXCNodeName)));
}
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW && rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && rel->rd_rel->relkind != RELKIND_STREAM &&
rel->rd_rel->relkind != RELKIND_CONTQUERY) {
RelationDropStorage(rel);
}
PgObjectType drop_type = GetPgObjectTypePgClass(rel->rd_rel->relkind);
if (drop_type != OBJECT_TYPE_INVALID) {
DeletePgObject(rel->rd_id, drop_type);
}
* Close relcache entry, but *keep* AccessExclusiveLock on the relation
* until transaction commit. This ensures no one else will try to do
* something with the doomed relation.
*/
if (ISMLOG(RelationGetForm(rel)->relname.data)) {
Oid base_relid = get_matview_mlog_baserelid(relid);
if (OidIsValid(base_relid)) {
CacheInvalidateRelcacheByRelid(base_relid);
}
}
relation_close(rel, NoLock);
* Remove any associated relation synchronization states.
*/
RemoveSubscriptionRel(InvalidOid, relid);
* Forget any ON COMMIT action for the rel
*/
remove_on_commit_action(relid);
* Flush the relation from the relcache. We want to do this before
* starting to remove catalog entries, just to be certain that no relcache
* entry rebuild will happen partway through. (That should not really
* matter, since we don't do CommandCounterIncrement here, but let's be
* safe.)
*/
RelationForgetRelation(relid);
* remove inheritance information
*/
RelationRemoveInheritance(relid);
* delete statistics
*/
RemoveStatistics<'c'>(relid, 0);
* delete hotkeys
*/
pgstat_send_cleanup_hotkeys(InvalidOid, relid);
* delete attribute tuples
*/
DeleteAttributeTuples(relid);
* delete relation tuple
*/
DeleteRelationTuple(relid);
}
* Store a default expression for column attnum of relation rel.
*/
Oid StoreAttrDefault(Relation rel, AttrNumber attnum, Node* expr, char generatedCol, Node* update_expr, bool skip_dep)
{
char* adbin = NULL;
char* adbin_on_update = NULL;
char* adsrc = NULL;
char* update_src = NULL;
Relation adrel;
HeapTuple tuple;
Datum values[Natts_pg_attrdef];
Relation attrrel;
HeapTuple atttup;
Form_pg_attribute attStruct;
Oid attrdefOid;
ObjectAddress colobject, defobject;
ScanKeyData skey[2];
HeapTuple htup = NULL;
bool isnull = false;
bool on_update_exist = false;
bool adin_on_update_exist = false;
* Flatten expression to string form for storage.
*/
if (update_expr != NULL) {
adbin_on_update = nodeToString(update_expr);
update_src = deparse_expression(
update_expr, deparse_context_for(RelationGetRelationName(rel), RelationGetRelid(rel)), false, false);
} else {
Relation adrel_temp = heap_open(AttrDefaultRelationId, RowExclusiveLock);
ScanKeyInit(&skey[0], Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
ScanKeyInit(&skey[1], Anum_pg_attrdef_adnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(attnum));
SysScanDesc adscan = systable_beginscan(adrel_temp, AttrDefaultIndexId, true, NULL, 2, skey);
if (HeapTupleIsValid(htup = systable_getnext(adscan))) {
Datum val = heap_getattr(htup, Anum_pg_attrdef_adbin_on_update, adrel_temp->rd_att, &isnull);
if (val && pg_strcasecmp(TextDatumGetCString(val), "") != 0) {
adbin_on_update = TextDatumGetCString(val);
on_update_exist = true;
}
val = heap_getattr(htup, Anum_pg_attrdef_adsrc_on_update, adrel_temp->rd_att, &isnull);
if (val && pg_strcasecmp(TextDatumGetCString(val), "") != 0) {
update_src = TextDatumGetCString(val);
adin_on_update_exist = true;
}
}
systable_endscan(adscan);
heap_close(adrel_temp, RowExclusiveLock);
}
* Also deparse it to form the mostly-obsolete adsrc field.
*/
adsrc = deparse_expression(
expr, deparse_context_for(RelationGetRelationName(rel), RelationGetRelid(rel)), false, false);
* Make the pg_attrdef entry.
*/
values[Anum_pg_attrdef_adrelid - 1] = RelationGetRelid(rel);
values[Anum_pg_attrdef_adnum - 1] = attnum;
if (expr != NULL) {
adbin = nodeToString(expr);
}
values[Anum_pg_attrdef_adbin - 1] = adbin ? CStringGetTextDatum(adbin) : CStringGetTextDatum("");
values[Anum_pg_attrdef_adsrc - 1] = adsrc ? CStringGetTextDatum(adsrc) : CStringGetTextDatum("");
values[Anum_pg_attrdef_adbin_on_update - 1] = (update_expr != NULL || (on_update_exist && adin_on_update_exist)) ?
CStringGetTextDatum(adbin_on_update) : CStringGetTextDatum("");
values[Anum_pg_attrdef_adsrc_on_update - 1] = (update_expr != NULL || (on_update_exist && adin_on_update_exist)) ?
CStringGetTextDatum(update_src) : CStringGetTextDatum("");
if (t_thrd.proc->workingVersionNum >= GENERATED_COL_VERSION_NUM) {
values[Anum_pg_attrdef_adgencol - 1] = CharGetDatum(generatedCol);
} else {
values[Anum_pg_attrdef_adgencol - 1] = CharGetDatum(0);
}
adrel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
tuple = heap_form_tuple(adrel->rd_att, values, u_sess->catalog_cxt.nulls);
if (on_update_exist) {
RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, true);
}
attrdefOid = simple_heap_insert(adrel, tuple);
CatalogUpdateIndexes(adrel, tuple);
defobject.classId = AttrDefaultRelationId;
defobject.objectId = attrdefOid;
defobject.objectSubId = 0;
heap_close(adrel, RowExclusiveLock);
pfree(DatumGetPointer(values[Anum_pg_attrdef_adbin - 1]));
pfree(DatumGetPointer(values[Anum_pg_attrdef_adsrc - 1]));
pfree(DatumGetPointer(values[Anum_pg_attrdef_adbin_on_update - 1]));
pfree(DatumGetPointer(values[Anum_pg_attrdef_adsrc_on_update - 1]));
heap_freetuple(tuple);
pfree(adsrc);
if (adbin) {
pfree(adbin);
}
if (adbin_on_update) {
pfree(adbin_on_update);
}
if (update_src) {
pfree(update_src);
}
* Update the pg_attribute entry for the column to show that a default
* exists.
*/
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
atttup = SearchSysCacheCopy2(ATTNUM, ObjectIdGetDatum(RelationGetRelid(rel)), Int16GetDatum(attnum));
if (!HeapTupleIsValid(atttup)) {
Assert(0);
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for attribute %d of relation %u", attnum, RelationGetRelid(rel))));
}
attStruct = (Form_pg_attribute)GETSTRUCT(atttup);
if (!attStruct->atthasdef) {
attStruct->atthasdef = true;
simple_heap_update(attrrel, &atttup->t_self, atttup);
CatalogUpdateIndexes(attrrel, atttup);
}
heap_close(attrrel, RowExclusiveLock);
heap_freetuple(atttup);
* Make a dependency so that the pg_attrdef entry goes away if the column
* (or whole table) is deleted.
*/
colobject.classId = RelationRelationId;
colobject.objectId = RelationGetRelid(rel);
colobject.objectSubId = attnum;
recordDependencyOn(&defobject, &colobject, DEPENDENCY_AUTO);
if (skip_dep) {
return InvalidOid;
}
* Record dependencies on objects used in the expression, too.
*/
if (generatedCol == ATTRIBUTE_GENERATED_STORED || generatedCol == ATTRIBUTE_GENERATED_PERSISTED) {
* Generated column: Dropping anything that the generation expression
* refers to automatically drops the generated column.
*/
recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel),
DEPENDENCY_AUTO,
DEPENDENCY_AUTO);
} else {
* Normal default: Dropping anything that the default refers to
* requires CASCADE and drops the default only.
*/
recordDependencyOnExpr(&defobject, expr, NIL, DEPENDENCY_NORMAL);
}
return attrdefOid;
}
* Store a check-constraint expression for the given relation.
*
* Caller is responsible for updating the count of constraints
* in the pg_class entry for the relation.
* The OID of the new constraint is returned.
*/
static Oid StoreRelCheck(
Relation rel, const char* ccname, Node* expr, bool is_validated, bool is_local, int inhcount, bool is_no_inherit,
bool is_deferrable, bool is_deferred, bool is_disable)
{
char* ccbin = NULL;
char* ccsrc = NULL;
List* varList = NIL;
int keycount;
int16* attNos = NULL;
Oid constrOid;
* Flatten expression to string form for storage.
*/
ccbin = nodeToString(expr);
* Also deparse it to form the mostly-obsolete consrc field.
*/
ccsrc = deparse_expression(
expr, deparse_context_for(RelationGetRelationName(rel), RelationGetRelid(rel)), false, false);
* Find columns of rel that are used in expr
*
* NB: pull_var_clause is okay here only because we don't allow subselects
* in check constraints; it would fail to examine the contents of
* subselects.
*/
varList = pull_var_clause(expr, PVC_REJECT_AGGREGATES, PVC_REJECT_PLACEHOLDERS);
keycount = list_length(varList);
if (keycount > 0) {
ListCell* vl = NULL;
int i = 0;
attNos = (int16*)palloc(keycount * sizeof(int16));
foreach (vl, varList) {
Var* var = (Var*)lfirst(vl);
int j;
for (j = 0; j < i; j++)
if (attNos[j] == var->varattno)
break;
if (j == i)
attNos[i++] = var->varattno;
}
keycount = i;
} else
attNos = NULL;
* Create the Check Constraint
*/
constrOid = CreateConstraintEntry(ccname,
RelationGetNamespace(rel),
CONSTRAINT_CHECK,
is_deferrable,
is_deferred,
is_validated,
RelationGetRelid(rel),
attNos,
keycount,
keycount,
InvalidOid,
InvalidOid,
InvalidOid,
NULL,
NULL,
NULL,
NULL,
0,
' ',
' ',
' ',
NULL,
expr,
ccbin,
ccsrc,
is_local,
inhcount,
is_no_inherit,
NULL,
is_disable);
pfree(ccbin);
pfree(ccsrc);
return constrOid;
}
* Store defaults and constraints (passed as a list of CookedConstraint).
*
* NOTE: only pre-cooked expressions will be passed this way, which is to
* say expressions inherited from an existing relation. Newly parsed
* expressions can be added later, by direct calls to StoreAttrDefault
* and StoreRelCheck (see AddRelationNewConstraints()).
*/
static void StoreConstraints(Relation rel, List* cooked_constraints)
{
int numchecks = 0;
ListCell* lc = NULL;
if (cooked_constraints == NIL)
return;
* Deparsing of constraint expressions will fail unless the just-created
* pg_attribute tuples for this relation are made visible. So, bump the
* command counter. CAUTION: this will cause a relcache entry rebuild.
*/
CommandCounterIncrement();
foreach (lc, cooked_constraints) {
CookedConstraint* con = (CookedConstraint*)lfirst(lc);
switch (con->contype) {
case CONSTR_DEFAULT:
con->conoid = StoreAttrDefault(rel, con->attnum, con->expr, 0,con->update_expr);
break;
case CONSTR_GENERATED:
con->conoid = StoreAttrDefault(rel, con->attnum, con->expr, ATTRIBUTE_GENERATED_STORED, NULL);
break;
case CONSTR_CHECK:
con->conoid = StoreRelCheck(
rel, con->name, con->expr, !con->skip_validation, con->is_local, con->inhcount, con->is_no_inherit, false, false, con->isdisable);
numchecks++;
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PARAMETER),
errmsg("unrecognized constraint type: %d", (int)con->contype)));
}
}
if (numchecks > 0)
SetRelationNumChecks(rel, numchecks);
}
static Node* CookAutoIncDefault(ParseState* pstate, Relation rel, RawColumnDefault* colDef, Form_pg_attribute atp)
{
AutoIncrement *autoinc = NULL;
CheckAutoIncrementDatatype(atp->atttypid, NameStr(atp->attname));
if (RelHasAutoInc(rel)) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
(errmsg("Incorrect column definition, there can be only one auto_increment column"))));
}
autoinc = makeNode(AutoIncrement);
(void)find_coercion_pathway(INT16OID, atp->atttypid, COERCION_ASSIGNMENT, &autoinc->autoincin_funcid);
(void)find_coercion_pathway(atp->atttypid, INT16OID, COERCION_ASSIGNMENT, &autoinc->autoincout_funcid);
autoinc->expr = cookDefault(pstate, ((AutoIncrement*)colDef->raw_default)->expr, atp->atttypid,
atp->atttypmod, atp->attcollation, NameStr(atp->attname), colDef->generatedCol);
if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) {
Const* con = (Const*)((AutoIncrement*)colDef->raw_default)->expr;
tmptable_autoinc_reset(rel->rd_rel->relfilenode, DatumGetInt128(con->constvalue));
}
return (Node*)autoinc;
}
static void CheckAutoIncrementCheckExpr(Node* expr, AttrNumber attrnum)
{
if (attrnum == 0) {
return;
}
Bitmapset *checkattrs = NULL;
pull_varattnos(expr, 1, &checkattrs);
if (bms_is_member(attrnum - FirstLowInvalidHeapAttributeNumber, checkattrs)) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
(errmsg("check constraint cannot refer to an auto_increment column"))));
}
}
* AddRelationNewConstraints
*
* Add new column default expressions and/or constraint check expressions
* to an existing relation. This is defined to do both for efficiency in
* DefineRelation, but of course you can do just one or the other by passing
* empty lists.
*
* rel: relation to be modified
* newColDefaults: list of RawColumnDefault structures
* newConstraints: list of Constraint nodes
* allow_merge: TRUE if check constraints may be merged with existing ones
* is_local: TRUE if definition is local, FALSE if it's inherited
*
* All entries in newColDefaults will be processed. Entries in newConstraints
* will be processed only if they are CONSTR_CHECK type.
*
* Returns a list of CookedConstraint nodes that shows the cooked form of
* the default and constraint expressions added to the relation.
*
* NB: caller should have opened rel with AccessExclusiveLock, and should
* hold that lock till end of transaction. Also, we assume the caller has
* done a CommandCounterIncrement if necessary to make the relation's catalog
* tuples visible.
*/
List* AddRelationNewConstraints(
Relation rel, List* newColDefaults, List* newConstraints, bool allow_merge, bool is_local)
{
List* cookedConstraints = NIL;
TupleDesc tupleDesc;
TupleConstr* oldconstr = NULL;
int numoldchecks;
ParseState* pstate = NULL;
RangeTblEntry* rte = NULL;
int numchecks;
List* checknames = NIL;
ListCell* cell = NULL;
Node* expr = NULL;
CookedConstraint* cooked = NULL;
Oid defOid;
AttrNumber autoinc_attnum = RelAutoIncAttrNum(rel);
Node* update_expr = NULL;
Bitmapset* generated_by_attrs = NULL;
* Get info about existing constraints.
*/
tupleDesc = RelationGetDescr(rel);
oldconstr = tupleDesc->constr;
if (oldconstr != NULL)
numoldchecks = oldconstr->num_check;
else
numoldchecks = 0;
* Create a dummy ParseState and insert the target relation as its sole
* rangetable entry. We need a ParseState for transformExpr.
*/
pstate = make_parsestate(NULL);
rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
addRTEtoQuery(pstate, rte, true, true, true);
pstate->p_rawdefaultlist = newColDefaults;
if (u_sess->hook_cxt.invokePreAddConstraintsHook) {
((InvokePreAddConstraintsHookType)(u_sess->hook_cxt.invokePreAddConstraintsHook))(rel, pstate,
newColDefaults);
}
* Process column default expressions.
*/
foreach (cell, newColDefaults) {
RawColumnDefault* colDef = (RawColumnDefault*)lfirst(cell);
Form_pg_attribute atp = &rel->rd_att->attrs[colDef->attnum - 1];
if (colDef->raw_default != NULL) {
if (IsA(colDef->raw_default, AutoIncrement)) {
autoinc_attnum = colDef->attnum;
expr = CookAutoIncDefault(pstate, rel, colDef, atp);
} else {
expr = cookDefault(pstate, colDef->raw_default, atp->atttypid, atp->atttypmod,
atp->attcollation, NameStr(atp->attname), colDef->generatedCol);
if (colDef->generatedCol == ATTRIBUTE_GENERATED_STORED ||
colDef->generatedCol == ATTRIBUTE_GENERATED_PERSISTED) {
pull_varattnos(expr, 1, &generated_by_attrs);
}
}
}
if (colDef->update_expr != NULL) {
update_expr = cookDefault(pstate, colDef->update_expr, atp->atttypid, atp->atttypmod,
atp->attcollation, NameStr(atp->attname), colDef->generatedCol);
}
* If the expression is just a NULL constant, we do not bother to make
* an explicit pg_attrdef entry, since the default behavior is
* equivalent. This applies to column defaults, but not for
* generation expressions.
*
* Note a nonobvious property of this test: if the column is of a
* domain type, what we'll get is not a bare null Const but a
* CoerceToDomain expr, so we will not discard the default. This is
* critical because the column default needs to be retained to
* override any default that the domain might have.
*/
if (expr != NULL && !colDef->generatedCol && IsA(expr, Const) && ((Const *)expr)->constisnull)
continue;
defOid = StoreAttrDefault(rel, colDef->attnum, expr, colDef->generatedCol, update_expr);
cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint));
cooked->contype = CONSTR_DEFAULT;
cooked->conoid = defOid;
cooked->name = NULL;
cooked->attnum = colDef->attnum;
cooked->expr = expr;
cooked->skip_validation = false;
cooked->is_local = is_local;
cooked->inhcount = is_local ? 0 : 1;
cooked->is_no_inherit = false;
cooked->isdisable = false;
cookedConstraints = lappend(cookedConstraints, cooked);
expr = NULL;
update_expr = NULL;
}
if (autoinc_attnum > 0 &&
bms_is_member(autoinc_attnum - FirstLowInvalidHeapAttributeNumber, generated_by_attrs)) {
bms_free_ext(generated_by_attrs);
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
(errmsg("generated column cannot refer to auto_increment column"))));
}
bms_free_ext(generated_by_attrs);
pstate->p_rawdefaultlist = NIL;
* Process constraint expressions.
*/
numchecks = numoldchecks;
checknames = NIL;
foreach (cell, newConstraints) {
Constraint* cdef = (Constraint*)lfirst(cell);
char* ccname = NULL;
Oid constrOid;
if (cdef->contype != CONSTR_CHECK)
continue;
if (cdef->raw_expr != NULL) {
Assert(cdef->cooked_expr == NULL);
* Transform raw parsetree to executable expression, and verify
* it's valid as a CHECK constraint.
*/
expr = cookConstraint(pstate, cdef->raw_expr, RelationGetRelationName(rel));
} else {
Assert(cdef->cooked_expr != NULL);
* Here, we assume the parser will only pass us valid CHECK
* expressions, so we do no particular checking.
*/
expr = (Node*)stringToNode(cdef->cooked_expr);
}
* Check constraint expressions cannot contain auto_increment column.
*/
CheckAutoIncrementCheckExpr(expr, autoinc_attnum);
* Check name uniqueness, or generate a name if none was given.
*/
if (cdef->conname != NULL) {
ListCell* cell2 = NULL;
ccname = cdef->conname;
foreach (cell2, checknames) {
if (strcmp((char*)lfirst(cell2), ccname) == 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("check constraint \"%s\" already exists", ccname)));
}
checknames = lappend(checknames, ccname);
* Check against pre-existing constraints. If we are allowed to
* merge with an existing constraint, there's no more to do here.
* (We omit the duplicate constraint from the result, which is
* what ATAddCheckConstraint wants.)
*/
if (MergeWithExistingConstraint(rel, ccname, expr, allow_merge, is_local, cdef->is_no_inherit))
continue;
} else {
* When generating a name, we want to create "tab_col_check" for a
* column constraint and "tab_check" for a table constraint. We
* no longer have any info about the syntactic positioning of the
* constraint phrase, so we approximate this by seeing whether the
* expression references more than one column. (If the user
* played by the rules, the result is the same...)
*
* Note: pull_var_clause() doesn't descend into sublinks, but we
* eliminated those above; and anyway this only needs to be an
* approximate answer.
*/
List* vars = NIL;
char* colname = NULL;
vars = pull_var_clause(expr, PVC_REJECT_AGGREGATES, PVC_REJECT_PLACEHOLDERS);
vars = list_union(NIL, vars);
if (list_length(vars) == 1)
colname = get_attname(RelationGetRelid(rel), ((Var*)linitial(vars))->varattno);
else
colname = NULL;
ccname = ChooseConstraintName(
RelationGetRelationName(rel), colname, "check", RelationGetNamespace(rel), checknames);
checknames = lappend(checknames, ccname);
}
* OK, store it.
*/
constrOid = StoreRelCheck(rel, ccname, expr, !cdef->skip_validation,
is_local, is_local ? 0 : 1, cdef->is_no_inherit,
cdef->deferrable, cdef->initdeferred, cdef->isdisable);
ListCell *cell = NULL;
foreach (cell, cdef->constraintOptions) {
void *pointer = lfirst(cell);
if (IsA(pointer, CommentStmt)) {
CommentStmt *commentStmt = (CommentStmt *)pointer;
CreateComments(constrOid, ConstraintRelationId, 0, commentStmt->comment);
break;
}
}
numchecks++;
cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint));
cooked->contype = CONSTR_CHECK;
cooked->conoid = constrOid;
cooked->name = ccname;
cooked->attnum = 0;
cooked->expr = expr;
cooked->skip_validation = cdef->skip_validation;
cooked->is_local = is_local;
cooked->inhcount = is_local ? 0 : 1;
cooked->is_no_inherit = cdef->is_no_inherit;
cooked->isdisable = cdef->isdisable;
cookedConstraints = lappend(cookedConstraints, cooked);
}
* Update the count of constraints in the relation's pg_class tuple. We do
* this even if there was no change, in order to ensure that an SI update
* message is sent out for the pg_class tuple, which will force other
* backends to rebuild their relcache entries for the rel. (This is
* critical if we added defaults but not constraints.)
*/
SetRelationNumChecks(rel, numchecks);
* Lock the sequence that the constranits depend, we may lock other object
* in the farther.
*/
LockSeqConstraints(rel, cookedConstraints);
return cookedConstraints;
}
* Check for a pre-existing check constraint that conflicts with a proposed
* new one, and either adjust its conislocal/coninhcount settings or throw
* error as needed.
*
* Returns TRUE if merged (constraint is a duplicate), or FALSE if it's
* got a so-far-unique name, or throws error if conflict.
*
* XXX See MergeConstraintsIntoExisting too if you change this code.
*/
static bool MergeWithExistingConstraint(
Relation rel, char* ccname, Node* expr, bool allow_merge, bool is_local, bool is_no_inherit)
{
bool found = false;
Relation conDesc = NULL;
SysScanDesc conscan = NULL;
ScanKeyData skey[2];
HeapTuple tup = NULL;
conDesc = heap_open(ConstraintRelationId, ShareUpdateExclusiveLock);
ScanKeyInit(&skey[0], Anum_pg_constraint_conname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(ccname));
ScanKeyInit(&skey[1],
Anum_pg_constraint_connamespace,
BTEqualStrategyNumber,
F_OIDEQ,
ObjectIdGetDatum(RelationGetNamespace(rel)));
conscan = systable_beginscan(conDesc, ConstraintNameNspIndexId, true, NULL, 2, skey);
while (HeapTupleIsValid(tup = systable_getnext(conscan))) {
Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup);
if (con->conrelid == RelationGetRelid(rel)) {
if (con->contype == CONSTRAINT_CHECK) {
Datum val;
bool isnull = false;
val = fastgetattr(tup, Anum_pg_constraint_conbin, conDesc->rd_att, &isnull);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("null conbin for rel %s", RelationGetRelationName(rel))));
if (equal(expr, stringToNode(TextDatumGetCString(val))))
found = true;
}
if (!found || !allow_merge)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("constraint \"%s\" for relation \"%s\" already exists",
ccname,
RelationGetRelationName(rel))));
tup = heap_copytuple(tup);
con = (Form_pg_constraint)GETSTRUCT(tup);
if (con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("constraint \"%s\" conflicts with non-inherited constraint on relation \"%s\"",
ccname,
RelationGetRelationName(rel))));
if (is_local)
con->conislocal = true;
else
con->coninhcount++;
if (is_no_inherit) {
Assert(is_local);
con->connoinherit = true;
}
ereport(NOTICE, (errmsg("merging constraint \"%s\" with inherited definition", ccname)));
simple_heap_update(conDesc, &tup->t_self, tup);
CatalogUpdateIndexes(conDesc, tup);
break;
}
}
systable_endscan(conscan);
heap_close(conDesc, ShareUpdateExclusiveLock);
return found;
}
* Update the count of constraints in the relation's pg_class tuple.
*
* Caller had better hold exclusive lock on the relation.
*
* An important side effect is that a SI update message will be sent out for
* the pg_class tuple, which will force other backends to rebuild their
* relcache entries for the rel. Also, this backend will rebuild its
* own relcache entry at the next CommandCounterIncrement.
*/
static void SetRelationNumChecks(Relation rel, int numchecks)
{
Relation relrel;
HeapTuple reltup;
Form_pg_class relStruct;
relrel = heap_open(RelationRelationId, RowExclusiveLock);
reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(RelationGetRelid(rel)));
if (!HeapTupleIsValid(reltup))
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for relation %u", RelationGetRelid(rel))));
relStruct = (Form_pg_class)GETSTRUCT(reltup);
if (relStruct->relchecks != numchecks) {
relStruct->relchecks = numchecks;
simple_heap_update(relrel, &reltup->t_self, reltup);
CatalogUpdateIndexes(relrel, reltup);
} else {
CacheInvalidateRelcache(rel);
}
heap_freetuple(reltup);
heap_close(relrel, RowExclusiveLock);
}
static bool ReferenceGenerated(const List *rawdefaultlist, AttrNumber attnum)
{
ListCell *cell = NULL;
foreach (cell, rawdefaultlist) {
RawColumnDefault *colDef = (RawColumnDefault *)lfirst(cell);
if (colDef->attnum == attnum && colDef->generatedCol) {
return true;
}
}
return false;
}
* Check for references to generated columns
*/
static bool CheckNestedGeneratedWalker(Node *node, ParseState *context)
{
ParseState *pstate = context;
if (node == NULL) {
return false;
} else if (IsA(node, Var)) {
Var *var = (Var *)node;
RangeTblEntry *rte = rt_fetch(var->varno, pstate->p_rtable);
Oid relid = rte->relid;
AttrNumber attnum = var->varattno;
if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
(ReferenceGenerated(pstate->p_rawdefaultlist, attnum) || GetGenerated(relid, attnum))) {
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use generated column \"%s\" in column generation expression",
get_attname(relid, attnum)),
errdetail("A generated column cannot reference another generated column."),
parser_errposition(pstate, var->location)));
}
return false;
} else {
return expression_tree_walker(node, (bool (*)())CheckNestedGeneratedWalker, context);
}
}
static bool CheckNestedGenerated(ParseState *pstate, Node *node)
{
return CheckNestedGeneratedWalker(node, pstate);
}
Node* parseParamRef(ParseState* pstate, ParamRef* pref)
{
Node* param_expr = NULL;
if (likely(u_sess->parser_cxt.param_info != NULL)) {
ParamListInfo params_info = (ParamListInfo)u_sess->parser_cxt.param_info;
int i = pref->number - 1;
char* str_expr = Datum_to_string(params_info->params[i].value, params_info->params[i].ptype, params_info->params[i].isnull);
Value* val = (Value*)palloc0(sizeof(Value));
val->type = T_String;
val->val.str = str_expr;
if (params_info->params[i].isnull)
val->type = T_Null;
param_expr = (Node*)make_const(pstate, val, 0);
pfree_ext(val);
if (pref->number == params_info->numParams && u_sess->parser_cxt.ddl_pbe_context != NULL) {
MemoryContextDelete(u_sess->parser_cxt.ddl_pbe_context);
if (((IS_PGXC_COORDINATOR || IS_PGXC_DATANODE) && IsConnFromCoord())) {
u_sess->parser_cxt.param_info = NULL;
}
u_sess->parser_cxt.ddl_pbe_context = NULL;
}
} else {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("u_sess->parser_cxt.param_info should not be NULL")));
}
return param_expr;
}
* Take a raw default and convert it to a cooked format ready for
* storage.
*
* Parse state should be set up to recognize any vars that might appear
* in the expression. (Even though we plan to reject vars, it's more
* user-friendly to give the correct error message than "unknown var".)
*
* If atttypid is not InvalidOid, coerce the expression to the specified
* type (and typmod atttypmod). attname is only needed in this case:
* it is used in the error message, if any.
*/
Node *cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, int32 atttypmod, Oid attcollation,
char *attname, char generatedCol)
{
Node* expr = NULL;
Assert(raw_default != NULL);
if (t_thrd.proc->workingVersionNum >= DDL_PBE_VERSION_NUM)
pstate->p_paramref_hook = parseParamRef;
* Transform raw parsetree to executable expression.
*/
pstate->p_expr_kind = generatedCol ? EXPR_KIND_GENERATED_COLUMN : EXPR_KIND_COLUMN_DEFAULT;
expr = transformExpr(pstate, raw_default, pstate->p_expr_kind);
pstate->p_expr_kind = EXPR_KIND_NONE;
if (generatedCol == ATTRIBUTE_GENERATED_STORED || generatedCol == ATTRIBUTE_GENERATED_PERSISTED)
{
(void)CheckNestedGenerated(pstate, expr);
if (contain_mutable_functions(expr))
ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("generation expression is not immutable")));
}
if (t_thrd.proc->workingVersionNum >= DDL_PBE_VERSION_NUM)
pstate->p_paramref_hook = NULL;
* Make sure default expr does not refer to any vars.
*/
if (contain_var_clause(expr) && !generatedCol)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("cannot use column references in default expression")));
* Make sure default expr does not refer to rownum.
*/
#ifndef ENABLE_MULTIPLE_NODES
ExcludeRownumExpr(pstate, expr);
#endif
* transformExpr() should have already rejected subqueries, aggregates,
* window functions, and SRFs, based on the EXPR_KIND_ for a default
* expression.
*/
if (!pstate->p_is_flt_frame) {
if (expression_returns_set(expr))
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("%s expression must not return a set", generatedCol ? "generated column" : "default")));
}
* No subplans or aggregates, either...
*/
if (pstate->p_hasSubLinks)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot use subquery in %s expression", generatedCol ? "generated column" : "default")));
if (pstate->p_hasAggs)
ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in %s expression", generatedCol ? "generated column" : "default")));
if (pstate->p_hasWindowFuncs)
ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR),
errmsg("cannot use window function in %s expression", generatedCol ? "generated column" : "default")));
* Coerce the expression to the correct type and typmod, if given. This
* should match the parser's processing of non-defaulted expressions ---
* see transformAssignedExpr().
*/
if (OidIsValid(atttypid)) {
Oid type_id = exprType(expr);
if (type_is_set(atttypid)) {
expr = coerce_to_settype(
pstate, expr, type_id, atttypid, atttypmod, COERCION_ASSIGNMENT, COERCE_IMPLICIT_CAST, -1, attcollation);
} else {
expr = coerce_to_target_type(pstate, expr, type_id, atttypid, atttypmod, COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST, NULL, NULL, -1);
if (expr == NULL)
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" is of type %s but %s expression is of type %s", attname, format_type_be(atttypid),
generatedCol ? "generated column" : "default", format_type_be(type_id)),
errhint("You will need to rewrite or cast the expression.")));
}
}
* Finally, take care of collations in the finished expression.
*/
assign_expr_collations(pstate, expr);
if (DB_IS_CMPT_BD && OidIsValid(attcollation)) {
int attcharset = get_valid_charset_by_collation(attcollation);
expr = coerce_to_target_charset(expr, attcharset, atttypid, atttypmod, attcollation);
}
return expr;
}
* Take a raw CHECK constraint expression and convert it to a cooked format
* ready for storage.
*
* Parse state must be set up to recognize any vars that might appear
* in the expression.
*/
static Node* cookConstraint(ParseState* pstate, Node* raw_constraint, char* relname)
{
Node* expr = NULL;
* Transform raw parsetree to executable expression.
*/
expr = transformExpr(pstate, raw_constraint, EXPR_KIND_CHECK_CONSTRAINT);
* Make sure it yields a boolean result.
*/
expr = coerce_to_boolean(pstate, expr, "CHECK");
* Take care of collations.
*/
assign_expr_collations(pstate, expr);
* Make sure no outside relations are referred to.
*/
if (list_length(pstate->p_rtable) != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("only table \"%s\" can be referenced in check constraint", relname)));
* No subplans or aggregates, either...
*/
if (pstate->p_hasSubLinks)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot use subquery in check constraint")));
if (pstate->p_hasAggs)
ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), errmsg("cannot use aggregate function in check constraint")));
if (pstate->p_hasWindowFuncs)
ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), errmsg("cannot use window function in check constraint")));
return expr;
}
* CopyStatistics --- copy entries in pg_statistic from one rel to another
*
* This function was enhanced by multi-column statistics.
*/
void CopyStatistics(Oid fromrelid, Oid torelid, char starelkind)
{
Relation pgstatistic;
SysScanDesc scan;
ScanKeyData key[2];
int nkeys = 2;
HeapTuple tuple;
ScanKeyInit(&key[0], Anum_pg_statistic_starelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(fromrelid));
if (starelkind == STARELKIND_CLASS) {
ScanKeyInit(
&key[1], Anum_pg_statistic_starelkind, BTEqualStrategyNumber, F_CHAREQ, ObjectIdGetDatum(STARELKIND_CLASS));
}
else if (starelkind == STARELKIND_PARTITION) {
ScanKeyInit(&key[1],
Anum_pg_statistic_starelkind,
BTEqualStrategyNumber,
F_CHAREQ,
ObjectIdGetDatum(STARELKIND_PARTITION));
}
else {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("invalid starelkind for pg_statistic")));
}
pgstatistic = heap_open(StatisticRelationId, RowExclusiveLock);
scan = systable_beginscan(pgstatistic, StatisticRelidKindAttnumInhIndexId, true, NULL, nkeys, key);
while (HeapTupleIsValid((tuple = systable_getnext(scan))))
{
Form_pg_statistic statform;
tuple = heap_copytuple(tuple);
statform = (Form_pg_statistic) GETSTRUCT(tuple);
statform->starelid = torelid;
(void)simple_heap_insert(pgstatistic, tuple);
CatalogUpdateIndexes(pgstatistic, tuple);
heap_freetuple(tuple);
}
systable_endscan(scan);
heap_close(pgstatistic, RowExclusiveLock);
* NOTE: scan key is reused. Since the two keys have the same attrnum
* in pg_statistic and pg_statistic_ext, we don't need to change it
*/
pgstatistic = heap_open(StatisticExtRelationId, RowExclusiveLock);
scan = systable_beginscan(pgstatistic, StatisticExtRelidKindInhKeyIndexId, true, NULL, nkeys, key);
while (HeapTupleIsValid((tuple = systable_getnext(scan))))
{
Form_pg_statistic_ext statform;
tuple = heap_copytuple(tuple);
statform = (Form_pg_statistic_ext) GETSTRUCT(tuple);
statform->starelid = torelid;
(void)simple_heap_insert(pgstatistic, tuple);
CatalogUpdateIndexes(pgstatistic, tuple);
heap_freetuple(tuple);
}
systable_endscan(scan);
heap_close(pgstatistic, RowExclusiveLock);
}
* RemoveStatistics --- remove entries in pg_statistic for a rel or column
*
* If attnum is zero, remove all entries for rel;
* else remove only the one(s) for that column.
*
* This function was enhanced by multi-column statistics.
*/
template <char starelkind>
void RemoveStatistics(Oid relid, AttrNumber attnum)
{
Relation pgstatistic;
SysScanDesc scan;
ScanKeyData key[3];
int nkeys;
HeapTuple tuple;
ScanKeyInit(&key[0], Anum_pg_statistic_starelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
if (starelkind == STARELKIND_CLASS) {
ScanKeyInit(
&key[1], Anum_pg_statistic_starelkind, BTEqualStrategyNumber, F_CHAREQ, ObjectIdGetDatum(STARELKIND_CLASS));
}
else if (starelkind == STARELKIND_PARTITION) {
ScanKeyInit(&key[1],
Anum_pg_statistic_starelkind,
BTEqualStrategyNumber,
F_CHAREQ,
ObjectIdGetDatum(STARELKIND_PARTITION));
}
else {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("invalid starelkind for pg_statistic")));
}
if (0 == attnum) {
nkeys = 2;
} else {
ScanKeyInit(&key[2], Anum_pg_statistic_staattnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(attnum));
nkeys = 3;
}
pgstatistic = heap_open(StatisticRelationId, RowExclusiveLock);
scan = systable_beginscan(pgstatistic, StatisticRelidKindAttnumInhIndexId, true, NULL, nkeys, key);
while (HeapTupleIsValid(tuple = systable_getnext(scan)))
simple_heap_delete(pgstatistic, &tuple->t_self);
systable_endscan(scan);
heap_close(pgstatistic, RowExclusiveLock);
* NOTE: scan key is reused with the first two keys. Since the two keys have
* the same attrnum in pg_statistic and pg_statistic_ext, we don't need to change it
*/
nkeys = 2;
pgstatistic = heap_open(StatisticExtRelationId, RowExclusiveLock);
scan = systable_beginscan(pgstatistic, StatisticExtRelidKindInhKeyIndexId, true, NULL, nkeys, key);
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
if (0 == attnum)
simple_heap_delete(pgstatistic, &tuple->t_self);
else {
bool isnull = false;
Datum attnums = heap_getattr(tuple, Anum_pg_statistic_ext_stakey, RelationGetDescr(pgstatistic), &isnull);
int2vector* keys = (int2vector*)DatumGetPointer(attnums);
int key_nums = keys->dim1;
int i;
for (i = 0; i < key_nums; i++) {
if (attnum == keys->values[i])
break;
}
if (i < key_nums)
simple_heap_delete(pgstatistic, &tuple->t_self);
}
}
systable_endscan(scan);
heap_close(pgstatistic, RowExclusiveLock);
if (t_thrd.proc->workingVersionNum >= STATISTIC_HISTORY_VERSION_NUMBER) {
RemoveStatisticHistory(relid, attnum);
if (attnum == 0) {
Oid namespaceid = get_rel_namespace(relid);
RemoveStatisticLockTab(namespaceid, relid);
}
}
}
* RelationTruncateIndexes - truncate all indexes associated
* with the heap relation to zero tuples.
*
* The routine will truncate and then reconstruct the indexes on
* the specified relation. Caller must hold exclusive lock on rel.
*/
static void RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
{
ListCell* indlist = NULL;
foreach (indlist, RelationGetIndexList(heapRelation)) {
Oid indexId = lfirst_oid(indlist);
Relation currentIndex;
IndexInfo* indexInfo = NULL;
currentIndex = index_open(indexId, lockmode);
indexInfo = BuildIndexInfo(currentIndex);
* Now truncate the actual file (and discard buffers).
*/
RelationTruncate(currentIndex, 0);
if (unlikely(currentIndex->rd_rel->relam == PSORT_AM_OID)) {
Relation psort_rel = heap_open(currentIndex->rd_rel->relcudescrelid, lockmode);
heap_truncate_one_rel(psort_rel);
heap_close(psort_rel, NoLock);
}
index_build(heapRelation, NULL, currentIndex, NULL, indexInfo, false, true, INDEX_CREATE_NONE_PARTITION);
index_close(currentIndex, NoLock);
}
}
* heap_truncate
*
* This routine deletes all data within all the specified relations.
*
* This is not transaction-safe! There is another, transaction-safe
* implementation in commands/tablecmds.c. We now use this only for
* ON COMMIT truncation of temporary tables, where it doesn't matter.
*/
void heap_truncate(List* relids)
{
List* relations = NIL;
ListCell* cell = NULL;
foreach (cell, relids) {
Oid rid = lfirst_oid(cell);
Relation rel;
LOCKMODE lockmode = AccessExclusiveLock;
if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP) {
lockmode = RowExclusiveLock;
}
rel = heap_open(rid, lockmode);
relations = lappend(relations, rel);
}
heap_truncate_check_FKs(relations, true);
foreach (cell, relations) {
Relation rel = (RelationData*)lfirst(cell);
heap_truncate_one_rel(rel);
pgstat_count_truncate(rel);
heap_close(rel, NoLock);
}
}
static void TruncateColCStorePartitionInSameXact(Relation rel, Partition p)
{
Relation cudesc_rel = heap_open(p->pd_part->relcudescrelid, AccessExclusiveLock);
heap_truncate_one_rel(cudesc_rel);
heap_close(cudesc_rel, NoLock);
Relation delta_rel = heap_open(p->pd_part->reldeltarelid, AccessExclusiveLock);
heap_truncate_one_rel(delta_rel);
heap_close(delta_rel, NoLock);
Relation partRel = partitionGetRelation(rel, p);
CStore::TruncateStorageInSameXact(partRel);
releaseDummyRelation(&partRel);
}
* heap_truncate_one_rel
*
* This routine deletes all data within the specified relation.
*
* This is not transaction-safe, because the truncation is done immediately
* and cannot be rolled back later. Caller is responsible for having
* checked permissions etc, and must have obtained AccessExclusiveLock.
*/
void heap_truncate_one_rel(Relation rel)
{
Oid toastrelid;
LOCKMODE lockmode = AccessExclusiveLock;
if (RELATION_IS_GLOBAL_TEMP(rel)) {
if (!gtt_storage_attached(RelationGetRelid(rel)))
return;
* Truncate global temp table only need RowExclusiveLock
*/
lockmode = RowExclusiveLock;
}
#ifdef ENABLE_MOT
if (RelationIsForeignTable(rel) && isMOTFromTblOid(RelationGetRelid(rel))) {
FdwRoutine* fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(rel));
if (fdwroutine->TruncateForeignTable != NULL) {
fdwroutine->TruncateForeignTable(NULL, rel);
}
} else if (!RELATION_IS_PARTITIONED(rel)) {
#else
if (!RELATION_IS_PARTITIONED(rel)) {
#endif
RelationTruncate(rel, 0);
if (RelationIsColStore(rel)) {
Relation cudesc_rel = heap_open(rel->rd_rel->relcudescrelid, lockmode);
heap_truncate_one_rel(cudesc_rel);
heap_close(cudesc_rel, NoLock);
Relation delta_rel = heap_open(rel->rd_rel->reldeltarelid, lockmode);
heap_truncate_one_rel(delta_rel);
heap_close(delta_rel, NoLock);
CStore::TruncateStorageInSameXact(rel);
}
RelationTruncateIndexes(rel, lockmode);
toastrelid = rel->rd_rel->reltoastrelid;
if (OidIsValid(toastrelid)) {
Relation toastrel = heap_open(toastrelid, lockmode);
RelationTruncate(toastrel, 0);
RelationTruncateIndexes(toastrel, lockmode);
heap_close(toastrel, NoLock);
}
} else {
List* partOidList = NIL;
List* indexList = NIL;
ListCell* indCell = NULL;
Relation currentIndex = NULL;
List* currentParttiionIndexList = NIL;
ListCell* partCell = NULL;
partOidList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_PARTITION, rel->rd_id);
foreach (partCell, partOidList) {
Partition p = partitionOpen(rel, HeapTupleGetOid((HeapTuple)lfirst(partCell)), lockmode);
PartitionTruncate(rel, p, 0);
if (RelationIsColStore(rel)) {
TruncateColCStorePartitionInSameXact(rel, p);
}
partitionClose(rel, p, NoLock);
}
indexList = RelationGetIndexList(rel);
foreach (indCell, indexList) {
ListCell* cell1 = NULL;
IndexInfo* indexInfo = NULL;
Oid indexId = lfirst_oid(indCell);
currentIndex = index_open(indexId, lockmode);
indexInfo = BuildIndexInfo(currentIndex);
currentParttiionIndexList = searchPgPartitionByParentId(PART_OBJ_TYPE_INDEX_PARTITION, indexId);
foreach (cell1, currentParttiionIndexList) {
Partition indexPart =
partitionOpen(currentIndex, HeapTupleGetOid((HeapTuple)lfirst(cell1)), lockmode);
Partition p;
PartitionTruncate(currentIndex, indexPart, 0);
if (unlikely(currentIndex->rd_rel->relam == PSORT_AM_OID)) {
Relation psort_rel = heap_open(currentIndex->rd_rel->relcudescrelid, lockmode);
heap_truncate_one_rel(psort_rel);
heap_close(psort_rel, NoLock);
}
p = partitionOpen(rel, indexPart->pd_part->indextblid, NoLock);
index_build(rel, p, currentIndex, indexPart, indexInfo, false, true, INDEX_CREATE_LOCAL_PARTITION);
partitionClose(rel, p, NoLock);
partitionClose(currentIndex, indexPart, NoLock);
}
freePartList(currentParttiionIndexList);
index_close(currentIndex, NoLock);
}
foreach (partCell, partOidList) {
HeapTuple tup = (HeapTuple)lfirst(partCell);
Form_pg_partition partForm = (Form_pg_partition)GETSTRUCT(tup);
if (partForm->reltoastrelid != InvalidOid) {
Relation toastrel = heap_open(partForm->reltoastrelid, lockmode);
RelationTruncate(toastrel, 0);
RelationTruncateIndexes(toastrel, lockmode);
heap_close(toastrel, NoLock);
}
}
freePartList(partOidList);
}
if (RELATION_IS_GLOBAL_TEMP(rel)) {
up_gtt_relstats(rel, 0, 0, 0, u_sess->utils_cxt.RecentXmin);
}
}
* heap_truncate_check_FKs
* Check for foreign keys referencing a list of relations that
* are to be truncated, and raise error if there are any
*
* We disallow such FKs (except self-referential ones) since the whole point
* of TRUNCATE is to not scan the individual rows to be thrown away.
*
* This is split out so it can be shared by both implementations of truncate.
* Caller should already hold a suitable lock on the relations.
*
* tempTables is only used to select an appropriate error message.
*/
void heap_truncate_check_FKs(List* relations, bool tempTables)
{
List* oids = NIL;
List* dependents = NIL;
ListCell* cell = NULL;
* Build a list of OIDs of the interesting relations.
*
* If a relation has no triggers, then it can neither have FKs nor be
* referenced by a FK from another table, so we can ignore it.
*/
foreach (cell, relations) {
Relation rel = (Relation)lfirst(cell);
if (rel->rd_rel->relhastriggers)
oids = lappend_oid(oids, RelationGetRelid(rel));
}
* Fast path: if no relation has triggers, none has FKs either.
*/
if (oids == NIL)
return;
* Otherwise, must scan pg_constraint. We make one pass with all the
* relations considered; if this finds nothing, then all is well.
*/
dependents = heap_truncate_find_FKs(oids);
if (dependents == NIL)
return;
* Otherwise we repeat the scan once per relation to identify a particular
* pair of relations to complain about. This is pretty slow, but
* performance shouldn't matter much in a failure path. The reason for
* doing things this way is to ensure that the message produced is not
* dependent on chance row locations within pg_constraint.
*/
foreach (cell, oids) {
Oid relid = lfirst_oid(cell);
ListCell* cell2 = NULL;
dependents = heap_truncate_find_FKs(list_make1_oid(relid));
foreach (cell2, dependents) {
Oid relid2 = lfirst_oid(cell2);
if (!list_member_oid(oids, relid2)) {
char* relname = get_rel_name(relid);
char* relname2 = get_rel_name(relid2);
if (tempTables)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported ON COMMIT and foreign key combination"),
errdetail(
"Table \"%s\" references \"%s\", but they do not have the same ON COMMIT setting.",
relname2,
relname)));
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot truncate a table referenced in a foreign key constraint"),
errdetail("Table \"%s\" references \"%s\".", relname2, relname),
errhint("Truncate table \"%s\" at the same time, "
"or use TRUNCATE ... CASCADE.",
relname2)));
}
}
}
}
* heap_truncate_find_FKs
* Find relations having foreign keys referencing any of the given rels
*
* Input and result are both lists of relation OIDs. The result contains
* no duplicates, does *not* include any rels that were already in the input
* list, and is sorted in OID order. (The last property is enforced mainly
* to guarantee consistent behavior in the regression tests; we don't want
* behavior to change depending on chance locations of rows in pg_constraint.)
*
* Note: caller should already have appropriate lock on all rels mentioned
* in relationIds. Since adding or dropping an FK requires exclusive lock
* on both rels, this ensures that the answer will be stable.
*/
List* heap_truncate_find_FKs(List* relationIds)
{
List* result = NIL;
Relation fkeyRel;
SysScanDesc fkeyScan;
HeapTuple tuple;
* Must scan pg_constraint. Right now, it is a seqscan because there is
* no available index on confrelid.
*/
fkeyRel = heap_open(ConstraintRelationId, AccessShareLock);
fkeyScan = systable_beginscan(fkeyRel, InvalidOid, false, NULL, 0, NULL);
while (HeapTupleIsValid(tuple = systable_getnext(fkeyScan))) {
Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tuple);
if (con->contype != CONSTRAINT_FOREIGN)
continue;
if (!list_member_oid(relationIds, con->confrelid))
continue;
if (!list_member_oid(relationIds, con->conrelid))
result = insert_ordered_unique_oid(result, con->conrelid);
}
systable_endscan(fkeyScan);
heap_close(fkeyRel, AccessShareLock);
return result;
}
* insert_ordered_unique_oid
* Insert a new Oid into a sorted list of Oids, preserving ordering,
* and eliminating duplicates
*
* Building the ordered list this way is O(N^2), but with a pretty small
* constant, so for the number of entries we expect it will probably be
* faster than trying to apply qsort(). It seems unlikely someone would be
* trying to truncate a table with thousands of dependent tables ...
*/
static List* insert_ordered_unique_oid(List* list, Oid datum)
{
ListCell* prev = NULL;
if (list == NIL || datum < linitial_oid(list))
return lcons_oid(datum, list);
if (datum == linitial_oid(list))
return list;
prev = list_head(list);
for (;;) {
ListCell* curr = lnext(prev);
if (curr == NULL || datum < lfirst_oid(curr))
break;
if (datum == lfirst_oid(curr))
return list;
prev = curr;
}
(void)lappend_cell_oid(list, prev, datum);
return list;
}
* @@GaussDB@@
* Brief : buildPartitionKey
* Description : build partitioin key attribute NO. array based on table's TupleDesc
* Notes :
*/
int2vector* buildPartitionKey(List* keys, TupleDesc tupledsc)
{
ListCell* cell = NULL;
ColumnRef* col = NULL;
int i = 0;
int j = 0;
int attnum = tupledsc->natts;
int partkeyNum = keys->length;
char* columName = NULL;
bool finded = false;
FormData_pg_attribute* attrs = tupledsc->attrs;
int2vector* partkey = NULL;
partkey = buildint2vector(NULL, partkeyNum);
bool isExpr = false;
bool isFunc = false;
foreach (cell, keys) {
col = (ColumnRef*)GetColumnRef((Node*)lfirst(cell), &isExpr, &isFunc);
if (!col)
ereport(ERROR,(errcode(ERRCODE_UNDEFINED_COLUMN),(errmsg("The partition key doesn't have any column."))));
if (isExpr && keys->length > 1)
ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),(errmsg("The multi partition expr keys are not supported."))));
columName = ((Value*)linitial(col->fields))->val.str;
finded = false;
for (j = 0; j < attnum; j++) {
if (strcmp(columName, attrs[j].attname.data) == 0) {
partkey->values[i] = attrs[j].attnum;
finded = true;
break;
}
}
if (!finded) {
ereport(ERROR,
(errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW),
(errmsg("buildPartitionKey(): partKeys specified NONE IS found'"))));
}
i++;
}
return partkey;
}
static oidvector* BuildIntervalTablespace(const IntervalPartitionDefState* intervalPartDef)
{
if (intervalPartDef->intervalTablespaces == NULL || intervalPartDef->intervalTablespaces->length == 0) {
return NULL;
}
oidvector* tablespaceVec = buildoidvector(NULL, intervalPartDef->intervalTablespaces->length);
ListCell* cell = NULL;
int i = 0;
const char* tablespaceName;
Oid tableSpaceOid;
AclResult aclresult;
foreach (cell, intervalPartDef->intervalTablespaces) {
tablespaceName = ((Value*)lfirst(cell))->val.str;
tableSpaceOid = get_tablespace_oid(tablespaceName, false);
if (tableSpaceOid != u_sess->proc_cxt.MyDatabaseTableSpace) {
aclresult = pg_tablespace_aclcheck(tableSpaceOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK) {
aclcheck_error(aclresult, ACL_KIND_TABLESPACE, tablespaceName);
}
}
tablespaceVec->values[i] = tableSpaceOid;
i++;
}
return tablespaceVec;
}
static Datum BuildInterval(Node* partInterval)
{
Assert(IsA(partInterval, A_Const));
A_Const* constNode = (A_Const*)partInterval;
Assert(IsA(&constNode->val, String));
ArrayBuildState* astate = NULL;
astate = accumArrayResult(
astate, PointerGetDatum(cstring_to_text(constNode->val.val.str)), false, TEXTOID, CurrentMemoryContext);
return makeArrayResult(astate, CurrentMemoryContext);
}
* @@GaussDB@@
* Brief : AddNewPartitionTuple
* Description : Insert a entry to pg_partition. The entry is for partitioned-table or partition.
* Notes :
*/
void addNewPartitionTuple(Relation pg_part_desc, Partition new_part_desc, PartitionTupleInfo *partTupleInfo)
{
Form_pg_partition new_part_tup = new_part_desc->pd_part;
* first we update some of the information in our uncataloged relation's
* relation descriptor.
*/
new_part_tup->relpages = 0;
new_part_tup->reltuples = 0;
new_part_tup->relallvisible = 0;
* Initialize to the minimum XID that could put tuples in the table.
* We know that no xacts older than RecentXmin are still running, so
* that will do.
*/
new_part_tup->relfrozenxid = (ShortTransactionId)InvalidTransactionId;
insertPartitionEntry(pg_part_desc, new_part_desc, new_part_desc->pd_id, partTupleInfo);
}
static void deletePartitionTuple(Oid part_id)
{
Relation pg_partition_desc;
HeapTuple tup;
pg_partition_desc = heap_open(PartitionRelationId, RowExclusiveLock);
tup = SearchSysCache1(PARTRELID, ObjectIdGetDatum(part_id));
if (!HeapTupleIsValid(tup)) {
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for partition %u", part_id)));
}
else {
simple_heap_delete(pg_partition_desc, &tup->t_self);
}
ReleaseSysCache(tup);
heap_close(pg_partition_desc, RowExclusiveLock);
}
void dropToastTableOnPartition(Oid partId)
{
ObjectAddresses* objects = NULL;
ObjectAddress obj;
Oid partToastRelid = InvalidOid;
Form_pg_partition partitionTuple = NULL;
HeapTuple partitionTupleRaw = NULL;
partitionTupleRaw = SearchSysCache1((int)PARTRELID, ObjectIdGetDatum(partId));
if (!PointerIsValid(partitionTupleRaw)) {
return;
}
partitionTuple = (Form_pg_partition)GETSTRUCT(partitionTupleRaw);
partToastRelid = partitionTuple->reltoastrelid;
if (!OidIsValid(partToastRelid)) {
ReleaseSysCache(partitionTupleRaw);
return;
}
objects = new_object_addresses();
obj.classId = RelationRelationId;
obj.objectId = partToastRelid;
obj.objectSubId = 0;
add_exact_object_address(&obj, objects);
* a, pg_toast_oid type object in pg_type
* b, pg_toast_oid_index index object in pg_class
* c, pg_toast_oid toast relation in pg_class.
*/
performMultipleDeletions(objects, DROP_CASCADE, 0);
free_object_addresses(objects);
ReleaseSysCache(partitionTupleRaw);
}
void dropCuDescTableOnPartition(Oid partId)
{
ObjectAddresses* objects = NULL;
ObjectAddress obj;
Oid partCuDescRelid = InvalidOid;
Form_pg_partition partitionTuple = NULL;
HeapTuple partitionTupleRaw = NULL;
partitionTupleRaw = SearchSysCache1((int)PARTRELID, ObjectIdGetDatum(partId));
if (!PointerIsValid(partitionTupleRaw)) {
return;
}
partitionTuple = (Form_pg_partition)GETSTRUCT(partitionTupleRaw);
partCuDescRelid = partitionTuple->relcudescrelid;
if (!OidIsValid(partCuDescRelid)) {
ReleaseSysCache(partitionTupleRaw);
return;
}
objects = new_object_addresses();
obj.classId = RelationRelationId;
obj.objectId = partCuDescRelid;
obj.objectSubId = 0;
add_exact_object_address(&obj, objects);
* a, pg_toast_oid type object in pg_type
* b, pg_toast_oid_index index object in pg_class
* c, pg_toast_oid toast relation in pg_class.
*/
performMultipleDeletions(objects, DROP_CASCADE, 0);
free_object_addresses(objects);
ReleaseSysCache(partitionTupleRaw);
}
void dropDeltaTableOnPartition(Oid partId)
{
ObjectAddresses* objects = NULL;
ObjectAddress obj;
Oid partDeltaTableRelid = InvalidOid;
Form_pg_partition partitionTuple = NULL;
HeapTuple partitionTupleRaw = NULL;
partitionTupleRaw = SearchSysCache1((int)PARTRELID, ObjectIdGetDatum(partId));
if (!PointerIsValid(partitionTupleRaw)) {
return;
}
partitionTuple = (Form_pg_partition)GETSTRUCT(partitionTupleRaw);
partDeltaTableRelid = partitionTuple->reldeltarelid;
if (!OidIsValid(partDeltaTableRelid)) {
ReleaseSysCache(partitionTupleRaw);
return;
}
objects = new_object_addresses();
obj.classId = RelationRelationId;
obj.objectId = partDeltaTableRelid;
obj.objectSubId = 0;
add_exact_object_address(&obj, objects);
* a, pg_toast_oid type object in pg_type
* b, pg_toast_oid_index index object in pg_class
* c, pg_toast_oid toast relation in pg_class.
*/
performMultipleDeletions(objects, DROP_CASCADE, 0);
free_object_addresses(objects);
ReleaseSysCache(partitionTupleRaw);
}
* @@GaussDB@@
* Brief :
* Description : Build local PartitionData instance, which is not in partition hashtable
* Notes :
* Parameters : part_name: partition name
* for_partitioned_table: true for partitioned table, false for partition
* part_tablespace: partition's tablespace.
* part_id: partition's oid. This parameter cannot be InvalidOid.
* relbufferpool: partition's bufferpool. All partitions of one partitioned table have same bufferpool.
* partFileNode: whether an existing index definition is
compatible with a prospective index definition,
such that the existing index storage
could become the storage of the new index,
avoiding a rebuild.
*
*/
Partition heapCreatePartition(const char* part_name, bool for_partitioned_table, Oid part_tablespace, Oid part_id,
Oid partFileNode, Oid bucketOid, Oid ownerid, StorageType storage_type, bool newcbi, Datum reloptions)
{
Partition new_part_desc = NULL;
bool createStorage = false;
bool isbucket = false;
Assert(OidIsValid(part_id));
* Never allow a pg_partition entry to explicitly specify the database's
* default tablespace in reltablespace; force it to zero instead. This
* ensures that if the database is cloned with a different default
* tablespace, the pg_class entry will still match where CREATE DATABASE
* will put the physically copied relation.
*
* Yes, this is a bit of a hack.
*/
part_tablespace = ConvertToPgclassRelTablespaceOid(part_tablespace);
if (!for_partitioned_table) {
if (!OidIsValid(partFileNode)) {
createStorage = true;
if (storage_type == SEGMENT_PAGE) {
Assert(part_tablespace != GLOBALTABLESPACE_OID);
isbucket = BUCKET_OID_IS_VALID(bucketOid) && !newcbi;
partFileNode = (Oid)seg_alloc_segment(ConvertToRelfilenodeTblspcOid(part_tablespace),
u_sess->proc_cxt.MyDatabaseId, isbucket, InvalidBlockNumber);
ereport(LOG, (errmsg("Segment Partition %s(%u) set relfilenode %u xid %lu", part_name, part_id,
partFileNode, GetCurrentTransactionIdIfAny())));
} else {
partFileNode = part_id;
}
}
if (u_sess->proc_cxt.IsBinaryUpgrade) {
createStorage = true;
if (storage_type == SEGMENT_PAGE) {
Assert(part_tablespace != GLOBALTABLESPACE_OID);
BlockNumber preassignedBlock = OidIsValid(partFileNode) ? partFileNode : InvalidBlockNumber;
isbucket = BUCKET_OID_IS_VALID(bucketOid) && !newcbi;
partFileNode = (Oid)seg_alloc_segment(ConvertToRelfilenodeTblspcOid(part_tablespace),
u_sess->proc_cxt.MyDatabaseId, isbucket, preassignedBlock);
}
}
}
* build the partcache entry.
*/
new_part_desc = PartitionBuildLocalPartition(part_name,
part_id,
partFileNode,
part_tablespace,
for_partitioned_table ? HEAP_DISK : storage_type,
reloptions);
* Save newcbi as a context indicator to
* avoid missing information in later index building process.
*/
new_part_desc->newcbi = newcbi;
* if this pg_partition entry is for partitioned table, then part_filenode is invalid.
* if this pg_partition entry is for partition, then part_filenode must be specified.
*/
if (!for_partitioned_table && createStorage) {
PartitionOpenSmgr(new_part_desc);
if (newcbi) {
RelationData dummyrel;
dummyrel.rd_id = InvalidOid;
dummyrel.newcbi = true;
RelationCreateStorage(new_part_desc->pd_node,
RELPERSISTENCE_PERMANENT,
ownerid,
bucketOid,
&dummyrel);
} else {
RelationCreateStorage(new_part_desc->pd_node,
RELPERSISTENCE_PERMANENT,
ownerid,
bucketOid);
}
}
return new_part_desc;
}
* @@GaussDB@@
* Target : data partition
* Brief :
* Description :
* Notes :
*/
void heapDropPartitionToastList(List* toastList)
{
ObjectAddresses* objects = NULL;
ObjectAddress obj;
Oid toastRelid = InvalidOid;
ListCell* cell = NULL;
objects = new_object_addresses();
foreach (cell, toastList) {
toastRelid = lfirst_oid(cell);
if (OidIsValid(toastRelid)) {
obj.classId = RelationRelationId;
obj.objectId = toastRelid;
obj.objectSubId = 0;
add_exact_object_address(&obj, objects);
}
}
* a, pg_toast_oid type object in pg_type
* b, pg_toast_oid_index index object in pg_class
* c, pg_toast_oid toast relation in pg_class.
*/
performMultipleDeletions(objects, DROP_CASCADE, 0);
free_object_addresses(objects);
}
void heapDropSubPartitionList(Relation rel, Oid partId)
{
ListCell* cell = NULL;
List *partCacheList = NIL;
HeapTuple partTuple = NULL;
Oid subPartOid = InvalidOid;
Form_pg_partition partForm = NULL;
Oid toastOid = InvalidOid;
Oid cuDesc = InvalidOid;
Oid delta = InvalidOid;
List *toastOidList = NIL;
List *cuDescOidList = NIL;
List *deltaOidList = NIL;
List *subPartOidList = NIL;
partCacheList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partId);
foreach (cell, partCacheList) {
partTuple = (HeapTuple)lfirst(cell);
if (PointerIsValid(partTuple)) {
subPartOid = HeapTupleGetOid(partTuple);
subPartOidList = lappend_oid(subPartOidList, subPartOid);
partForm = (Form_pg_partition)GETSTRUCT(partTuple);
toastOid = partForm->reltoastrelid;
cuDesc = partForm->relcudescrelid;
delta = partForm->reldeltarelid;
if (OidIsValid(toastOid)) {
toastOidList = lappend_oid(toastOidList, toastOid);
}
if (OidIsValid(cuDesc)) {
cuDescOidList = lappend_oid(cuDescOidList, cuDesc);
}
if (OidIsValid(delta)) {
deltaOidList = lappend_oid(deltaOidList, delta);
}
}
}
* drop table partition's toast table
*/
if (PointerIsValid(toastOidList)) {
heapDropPartitionToastList(toastOidList);
list_free(toastOidList);
}
if (PointerIsValid(cuDescOidList)) {
heapDropPartitionToastList(cuDescOidList);
list_free(cuDescOidList);
}
if (PointerIsValid(deltaOidList)) {
heapDropPartitionToastList(deltaOidList);
list_free(deltaOidList);
}
* drop table partition
*/
if (PointerIsValid(subPartOidList)) {
heapDropPartitionList(rel, subPartOidList);
list_free(subPartOidList);
}
freePartList(partCacheList);
}
* @@GaussDB@@
* Target : data partition
* Brief :
* Description :
* Notes :
*/
void heapDropPartitionList(Relation rel, List* partitionList)
{
List* newPartList = NIL;
ListCell* cell = NULL;
Oid partId = InvalidOid;
Partition part = NULL;
Relation partRel = NULL;
Assert(PointerIsValid(rel));
Assert(RELATION_IS_PARTITIONED(rel));
Assert(PointerIsValid(partitionList));
* step 1: delete depending objs on partition list
*/
foreach (cell, partitionList) {
partId = lfirst_oid(cell);
if (OidIsValid(partId)) {
part = partitionOpen(rel, partId, AccessExclusiveLock);
if (RelationIsSubPartitioned(rel)) {
partRel = partitionGetRelation(rel, part);
heapDropSubPartitionList(partRel, partId);
releaseDummyRelation(&partRel);
}
newPartList = lappend(newPartList, part);
}
}
* step 2: iterate partList, delete one by one
*/
foreach (cell, newPartList) {
part = (Partition)lfirst(cell);
heapDropPartition(rel, part);
}
* clean up
*/
list_free(newPartList);
}
* @@GaussDB@@
* Target : data partition
* Brief :
* Description :
* Notes : the caller MUST have AccessExclusiveLock lock on partition
*/
void heapDropPartition(Relation relation, Partition part)
{
Oid partid = InvalidOid;
Relation partRel = NULL;
* Open and lock the relation.
*/
Assert(PointerIsValid(part));
partid = part->pd_id;
checkPartNotInUse(part, "DROP PARTITION");
if (!(RelationIsSubPartitioned(relation) && part->pd_part->parttype == PART_OBJ_TYPE_TABLE_PARTITION)) {
partRel = partitionGetRelation(relation, part);
TransferPredicateLocksToHeapRelation(partRel);
* Schedule unlinking of the relation's physical files at commit.
*/
RelationDropStorage(partRel);
releaseDummyRelation(&partRel);
}
partitionClose(relation, part, NoLock);
* Flush the partition from the partcache. We want to do this before
* starting to remove catalog entries, just to be certain that no partcache
* entry rebuild will happen partway through. (That should not really
* matter, since we don't do CommandCounterIncrement here, but let's be
* safe.)
*/
PartitionForgetPartition(partid);
* delete statistics
*/
RemoveStatistics<'p'>(partid, 0);
* delete partition tuple
*/
deletePartitionTuple(partid);
}
static void heapDropPartitionTable(Relation relation)
{
List* partCacheList = NIL;
List* partitionOidList = NIL;
List* toastOidList = NIL;
List* cuDescOidList = NIL;
List* deltaOidList = NIL;
Oid partOid = InvalidOid;
Oid toastOid = InvalidOid;
Oid cuDesc = InvalidOid;
Oid delta = InvalidOid;
Partition partTable = NULL;
HeapTuple partTuple = NULL;
HeapTuple tableTuple = NULL;
Form_pg_partition partForm = NULL;
ListCell* cell = NULL;
partCacheList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_PARTITION, RelationGetRelid(relation));
tableTuple = searchPgPartitionByParentIdCopy(PART_OBJ_TYPE_PARTED_TABLE, RelationGetRelid(relation));
*the opened partition must already LOCKED before realy dropped.
*/
foreach (cell, partCacheList) {
partTuple = (HeapTuple)lfirst(cell);
if (PointerIsValid(partTuple)) {
partOid = HeapTupleGetOid(partTuple);
partitionOidList = lappend_oid(partitionOidList, partOid);
partForm = (Form_pg_partition)GETSTRUCT(partTuple);
toastOid = partForm->reltoastrelid;
cuDesc = partForm->relcudescrelid;
delta = partForm->reldeltarelid;
if (OidIsValid(toastOid)) {
toastOidList = lappend_oid(toastOidList, toastOid);
}
if (OidIsValid(cuDesc)) {
cuDescOidList = lappend_oid(cuDescOidList, cuDesc);
}
if (OidIsValid(delta)) {
deltaOidList = lappend_oid(deltaOidList, delta);
}
}
}
* drop table partition's toast table
*/
if (PointerIsValid(toastOidList)) {
heapDropPartitionToastList(toastOidList);
list_free(toastOidList);
}
if (PointerIsValid(cuDescOidList)) {
heapDropPartitionToastList(cuDescOidList);
list_free(cuDescOidList);
}
if (PointerIsValid(deltaOidList)) {
heapDropPartitionToastList(deltaOidList);
list_free(deltaOidList);
}
* drop table partition
*/
if (PointerIsValid(partitionOidList)) {
heapDropPartitionList(relation, partitionOidList);
list_free(partitionOidList);
}
* drop partitioned table
*/
if (PointerIsValid(tableTuple)) {
partOid = HeapTupleGetOid(tableTuple);
partTable = partitionOpen(relation, partOid, AccessExclusiveLock);
heapDropPartition(relation, partTable);
heap_freetuple(tableTuple);
} else {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for partitioned table %u in pg_partition", RelationGetRelid(relation))));
}
freePartList(partCacheList);
}
* @@GaussDB@@
* Target : data partition
* Brief :
* Description :
* Notes :
*/
void heapDropPartitionIndex(Relation parentIndex, Oid partIndexId)
{
Partition partIndex = NULL;
Relation partRel = NULL;
ObjectAddress obj;
partIndex = partitionOpen(parentIndex, partIndexId, AccessExclusiveLock);
partRel = partitionGetRelation(parentIndex, partIndex);
if (partRel->rd_rel->relcudescrelid != InvalidOid) {
obj.classId = RelationRelationId;
obj.objectId = partRel->rd_rel->relcudescrelid;
obj.objectSubId = 0;
performDeletion(&obj, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
}
#ifndef ENABLE_MULTIPLE_NODES
if (RelationIsCUFormat(parentIndex) && partRel->rd_index != NULL && partRel->rd_index->indisunique) {
obj.classId = RelationRelationId;
obj.objectId = GetDeltaIdxFromCUIdx(RelationGetRelid(partRel), true);
obj.objectSubId = 0;
performDeletion(&obj, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
}
#endif
* we should process the predicate lock on the partition
*/
TransferPredicateLocksToHeapRelation(partRel);
RelationDropStorage(partRel);
partitionClose(parentIndex, partIndex, NoLock);
PartitionForgetPartition(partIndexId);
deletePartitionTuple(partIndexId);
releaseDummyRelation(&partRel);
}
void GetNewPartitionOidAndNewPartrelfileOid(List *subPartitionDefState, Oid *newPartitionOid, Oid *newPartrelfileOid,
Relation pgPartRel, Oid newPartitionTableSpaceOid)
{
if (subPartitionDefState != NULL) {
if (u_sess->proc_cxt.IsBinaryUpgrade &&
OidIsValid(u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid)) {
*newPartitionOid = u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid;
u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid = InvalidOid;
*newPartrelfileOid = u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_rfoid;
u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_rfoid = InvalidOid;
} else {
*newPartitionOid = GetNewOid(pgPartRel);
}
} else {
if (u_sess->proc_cxt.IsBinaryUpgrade && binary_upgrade_is_next_part_pg_partition_oid_valid()) {
*newPartitionOid = binary_upgrade_get_next_part_pg_partition_oid();
*newPartrelfileOid = binary_upgrade_get_next_part_pg_partition_rfoid();
} else {
*newPartitionOid = GetNewRelFileNode(newPartitionTableSpaceOid, pgPartRel,
RELPERSISTENCE_PERMANENT);
can be 'p'(permanent table) */
}
}
}
* @@GaussDB@@
* Brief :
* Description : add new partition, USED by addNewPartitionTuplesForPartition() iteration
* and ADD PARTITION
* Notes :
* Parameters : pg_partition_rel: RelationData pointer for pg_partition,
* the caller MUST get RowExclusiveLock on pg_partition
* partTableOid: partitioned table's oid
* partTablespace: partitioned table's tablespace.
* If partition's tablespace is not provided, it will inherit from parent partitioned
* table. partBufferPool: partitioned table's BufferPoolId. All partitions of one partitioned table have same
* BufferPoolId. newPartDef: partition description.
*
*/
Oid heapAddRangePartition(Relation pgPartRel, Oid partTableOid, Oid partTablespace, Oid bucketOid,
RangePartitionDefState *newPartDef, Oid ownerid, Datum reloptions, const bool *isTimestamptz,
StorageType storage_type, LOCKMODE partLockMode, int2vector* subpartition_key, bool isSubpartition,
PartitionExprKeyInfo *partExprKeyInfo, char partStrategy)
{
Datum boundaryValue = (Datum)0;
Oid newPartitionOid = InvalidOid;
Oid newPartitionTableSpaceOid = InvalidOid;
Relation relation;
Partition newPartition;
Oid newPartrelfileOid = InvalidOid;
if (!PointerIsValid(newPartDef)) {
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("missing definition for new partition")));
}
if (!PointerIsValid(newPartDef->boundary)) {
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("boundary not defined for new partition")));
}
if (newPartDef->boundary->length > MAX_PARTKEY_NUMS) {
ereport(ERROR,
(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
errmsg("too many partition keys, allowed is %d", MAX_PARTKEY_NUMS)));
}
if (!PointerIsValid(newPartDef->partitionName)) {
ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("partition name is invalid")));
}
boundaryValue = transformPartitionBoundary(newPartDef->boundary, isTimestamptz);
if (newPartDef->tablespacename) {
newPartitionTableSpaceOid = get_tablespace_oid(newPartDef->tablespacename, false);
}
if (!OidIsValid(newPartitionTableSpaceOid)) {
newPartitionTableSpaceOid = partTablespace;
}
if (OidIsValid(newPartitionTableSpaceOid) && newPartitionTableSpaceOid != u_sess->proc_cxt.MyDatabaseTableSpace) {
AclResult aclresult;
aclresult = pg_tablespace_aclcheck(newPartitionTableSpaceOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK) {
aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(newPartitionTableSpaceOid));
}
}
GetNewPartitionOidAndNewPartrelfileOid(newPartDef->subPartitionDefState, &newPartitionOid, &newPartrelfileOid,
pgPartRel, newPartitionTableSpaceOid);
if (partLockMode != NoLock) {
LockPartitionOid(partTableOid, (uint32)newPartitionOid, partLockMode);
}
bool forPartitionTable = false;
if (isSubpartition) {
forPartitionTable = false;
} else if (newPartDef->subPartitionDefState != NULL) {
forPartitionTable = true;
} else {
forPartitionTable = false;
}
newPartition = heapCreatePartition(newPartDef->partitionName,
forPartitionTable,
newPartitionTableSpaceOid,
newPartitionOid,
newPartrelfileOid,
bucketOid,
ownerid,
storage_type,
false,
reloptions);
Assert(newPartitionOid == PartitionGetPartid(newPartition));
PartitionTupleInfo partTupleInfo = PartitionTupleInfo();
if (isSubpartition) {
InitSubPartitionDef(newPartition, partTableOid, partStrategy);
partTupleInfo.partitionno = INVALID_PARTITION_NO;
partTupleInfo.subpartitionno = newPartDef->partitionno;
} else {
InitPartitionDef(newPartition, partTableOid, partStrategy);
partTupleInfo.partitionno = newPartDef->partitionno;
partTupleInfo.subpartitionno = -list_length(newPartDef->subPartitionDefState);
}
partTupleInfo.pkey = subpartition_key;
partTupleInfo.boundaries = boundaryValue;
partTupleInfo.reloptions = reloptions;
if (partExprKeyInfo) {
partTupleInfo.partexprkeyinfo.partkeyexprIsNull = partExprKeyInfo->partkeyexprIsNull;
partTupleInfo.partexprkeyinfo.partkeyIsFunc = partExprKeyInfo->partkeyIsFunc;
partTupleInfo.partexprkeyinfo.partExprKeyStr = partExprKeyInfo->partExprKeyStr;
}
addNewPartitionTuple(pgPartRel, newPartition, &partTupleInfo);
if (isSubpartition) {
PartitionCloseSmgr(newPartition);
Partition part = newPartition;
if (PartitionIsBucket(newPartition)) {
part = newPartition->parent;
bucketClosePartition(newPartition);
}
PartitionClose(part);
} else {
relation = relation_open(partTableOid, NoLock);
PartitionCloseSmgr(newPartition);
partitionClose(relation, newPartition, NoLock);
relation_close(relation, NoLock);
}
return newPartitionOid;
}
#define IsDigital(_ch) (((_ch) >= '0') && ((_ch) <= '9'))
static unsigned ExtractIntervalPartNameSuffix(const char* partName)
{
if (partName == NULL) {
return 0;
}
size_t len = strlen(partName);
size_t constPartLen = strlen(INTERVAL_PARTITION_NAME_PREFIX);
if (len <= constPartLen || len > constPartLen + INTERVAL_PARTITION_NAME_SUFFIX_LEN) {
return 0;
}
if (strncmp(partName, INTERVAL_PARTITION_NAME_PREFIX, constPartLen) != 0) {
return 0;
}
for (size_t i = constPartLen; i < len; ++i) {
if (!IsDigital(partName[i])) {
return 0;
}
}
return (unsigned)atoi(partName + constPartLen);
}
int RangeElementOidCmp(const void* a, const void* b)
{
const RangeElement* rea = (const RangeElement*)a;
const RangeElement* reb = (const RangeElement*)b;
if (rea->partitionOid < reb->partitionOid) {
return 1;
}
if (rea->partitionOid == reb->partitionOid) {
return 0;
}
return -1;
}
char* GenIntervalPartitionName(Relation rel)
{
unsigned suffix = 0;
Oid existingPartOid;
RangePartitionMap* partMap = (RangePartitionMap*)rel->partMap;
RangeElement* eles = CopyRangeElementsWithoutBoundary(partMap->rangeElements, partMap->rangeElementsNum);
qsort(eles, partMap->rangeElementsNum, sizeof(RangeElement), RangeElementOidCmp);
for (int i = 0; i < partMap->rangeElementsNum; ++i) {
if (!eles[i].isInterval) {
continue;
}
char* name = PartitionOidGetName(eles[i].partitionOid);
if ((suffix = ExtractIntervalPartNameSuffix(name)) != 0) {
pfree(name);
break;
}
}
pfree(eles);
char* partName = (char*)palloc0(NAMEDATALEN);
error_t rc;
while (true) {
++suffix;
suffix = (suffix % MAX_PARTITION_NUM == 0 ? MAX_PARTITION_NUM : suffix % MAX_PARTITION_NUM);
rc = snprintf_s(partName, NAMEDATALEN, NAMEDATALEN - 1, INTERVAL_PARTITION_NAME_PREFIX_FMT, suffix);
securec_check_ss(rc, "\0", "\0");
existingPartOid = PartitionNameGetPartitionOid(
rel->rd_id, partName, PART_OBJ_TYPE_TABLE_PARTITION, AccessShareLock, true, false, NULL, NULL, NoLock);
if (!OidIsValid(existingPartOid)) {
return partName;
}
}
}
Oid GetRecentUsedTablespace(Relation rel)
{
RangePartitionMap* partMap = (RangePartitionMap*)rel->partMap;
Assert(partMap->rangeElementsNum >= 1);
RangeElement* maxOidEle = &partMap->rangeElements[0];
for (int i = 1; i < partMap->rangeElementsNum; ++i) {
if (partMap->rangeElements[i].partitionOid > maxOidEle->partitionOid) {
maxOidEle = &partMap->rangeElements[i];
}
}
if (!maxOidEle->isInterval) {
return InvalidOid;
}
return PartitionOidGetTablespace(maxOidEle->partitionOid);
}
Oid ChooseIntervalTablespace(Relation rel)
{
const oidvector* tablespaceVec = ((RangePartitionMap*)rel->partMap)->intervalTablespace;
Assert(tablespaceVec->dim1 >= 1);
if (tablespaceVec->dim1 == 1) {
return tablespaceVec->values[0];
}
const Oid recentUsed = GetRecentUsedTablespace(rel);
if (!OidIsValid(recentUsed)) {
return tablespaceVec->values[0];
}
int i = 0;
for (; i < tablespaceVec->dim1; ++i) {
if (tablespaceVec->values[i] == recentUsed) {
break;
}
}
return tablespaceVec->values[(i + 1) % tablespaceVec->dim1];
}
Oid HeapAddIntervalPartition(Relation pgPartRel, Relation rel, Oid partTableOid, Oid partrelfileOid, Oid partTablespace,
Oid bucketOid, Datum boundaryValue, Oid ownerid, Datum reloptions, StorageType storage_type)
{
Oid newPartitionOid = InvalidOid;
Oid newPartitionTableSpaceOid = InvalidOid;
Relation relation;
Partition newPartition;
if (((RangePartitionMap*)rel->partMap)->intervalTablespace != NULL) {
newPartitionTableSpaceOid = ChooseIntervalTablespace(rel);
}
if (!OidIsValid(newPartitionTableSpaceOid)) {
newPartitionTableSpaceOid = partTablespace;
}
if (OidIsValid(newPartitionTableSpaceOid) && newPartitionTableSpaceOid != u_sess->proc_cxt.MyDatabaseTableSpace) {
AclResult aclresult = pg_tablespace_aclcheck(newPartitionTableSpaceOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK) {
aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(newPartitionTableSpaceOid));
}
}
if (!OidIsValid(partrelfileOid) && u_sess->proc_cxt.IsBinaryUpgrade &&
binary_upgrade_is_next_part_pg_partition_oid_valid()) {
newPartitionOid = binary_upgrade_get_next_part_pg_partition_oid();
partrelfileOid = binary_upgrade_get_next_part_pg_partition_rfoid();
} else if (!OidIsValid(partrelfileOid)) {
newPartitionOid = GetNewRelFileNode(newPartitionTableSpaceOid,
pgPartRel,
RELPERSISTENCE_PERMANENT);
} else {
Assert(t_thrd.xact_cxt.inheritFileNode);
ereport(NOTICE, (errmsg("Define inheritFileNode %u for new interval partition", partrelfileOid)));
}
LockPartitionOid(partTableOid, (uint32)newPartitionOid, AccessExclusiveLock);
char* partName = GenIntervalPartitionName(rel);
newPartition = heapCreatePartition(partName,
false,
newPartitionTableSpaceOid,
newPartitionOid,
partrelfileOid,
bucketOid,
ownerid,
storage_type,
false,
reloptions);
pfree(partName);
Assert(newPartitionOid == PartitionGetPartid(newPartition));
int partitionno = -GetCurrentPartitionNo(RelOidGetPartitionTupleid(partTableOid));
if (!PARTITIONNO_IS_VALID(partitionno)) {
RelationResetPartitionno(partTableOid, RowExclusiveLock);
partitionno = -GetCurrentPartitionNo(RelOidGetPartitionTupleid(partTableOid));
Assert(PARTITIONNO_IS_VALID(partitionno));
}
InitPartitionDef(newPartition, partTableOid, PART_STRATEGY_INTERVAL);
PartitionTupleInfo partTupleInfo = PartitionTupleInfo();
partTupleInfo.boundaries = boundaryValue;
partTupleInfo.reloptions = reloptions;
partTupleInfo.partitionno = ++partitionno;
partTupleInfo.subpartitionno = INVALID_PARTITION_NO;
addNewPartitionTuple(pgPartRel, newPartition, &partTupleInfo);
UpdateCurrentPartitionNo(RelOidGetPartitionTupleid(partTableOid), -partitionno, true);
relation = relation_open(partTableOid, NoLock);
PartitionCloseSmgr(newPartition);
partitionClose(relation, newPartition, NoLock);
relation_close(relation, NoLock);
return newPartitionOid;
}
Oid HeapAddListPartition(Relation pgPartRel, Oid partTableOid, Oid partTablespace, Oid bucketOid,
ListPartitionDefState* newListPartDef, Oid ownerid, Datum reloptions, const bool* isTimestamptz,
StorageType storage_type, int2vector* subpartition_key, bool isSubpartition,
PartitionExprKeyInfo *partExprKeyInfo)
{
Datum boundaryValue = (Datum)0;
Oid newListPartitionOid = InvalidOid;
Oid newPartitionTableSpaceOid = InvalidOid;
Oid partrelfileOid = InvalidOid;
Relation relation;
Partition newListPartition;
if (!PointerIsValid(newListPartDef)) {
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("missing definition for new partition")));
}
if (!PointerIsValid(newListPartDef->boundary)) {
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("boundary not defined for new partition")));
}
if (newListPartDef->boundary->length > PARTKEY_VALUE_MAXNUM) {
ereport(ERROR,
(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
errmsg("too many partition keys, allowed is %d", PARTKEY_VALUE_MAXNUM)));
}
if (!PointerIsValid(newListPartDef->partitionName)) {
ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("partition name is invalid")));
}
boundaryValue = transformListBoundary(newListPartDef->boundary, isTimestamptz);
if (newListPartDef->tablespacename) {
newPartitionTableSpaceOid = get_tablespace_oid(newListPartDef->tablespacename, false);
}
if (!OidIsValid(newPartitionTableSpaceOid)) {
newPartitionTableSpaceOid = partTablespace;
}
if (OidIsValid(newPartitionTableSpaceOid) && newPartitionTableSpaceOid != u_sess->proc_cxt.MyDatabaseTableSpace) {
AclResult aclresult = pg_tablespace_aclcheck(newPartitionTableSpaceOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK) {
aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(newPartitionTableSpaceOid));
}
}
GetNewPartitionOidAndNewPartrelfileOid(newListPartDef->subPartitionDefState, &newListPartitionOid, &partrelfileOid,
pgPartRel, newPartitionTableSpaceOid);
LockPartitionOid(partTableOid, (uint32)newListPartitionOid, AccessExclusiveLock);
bool forPartitionTable = false;
if (isSubpartition) {
forPartitionTable = false;
} else if (newListPartDef->subPartitionDefState != NULL) {
forPartitionTable = true;
} else {
forPartitionTable = false;
}
newListPartition = heapCreatePartition(newListPartDef->partitionName, forPartitionTable, newPartitionTableSpaceOid,
newListPartitionOid, partrelfileOid, bucketOid, ownerid, storage_type,
false, reloptions);
Assert(newListPartitionOid == PartitionGetPartid(newListPartition));
PartitionTupleInfo partTupleInfo = PartitionTupleInfo();
if (isSubpartition) {
InitSubPartitionDef(newListPartition, partTableOid, PART_STRATEGY_LIST);
partTupleInfo.partitionno = INVALID_PARTITION_NO;
partTupleInfo.subpartitionno = newListPartDef->partitionno;
} else {
InitPartitionDef(newListPartition, partTableOid, PART_STRATEGY_LIST);
partTupleInfo.partitionno = newListPartDef->partitionno;
partTupleInfo.subpartitionno = -list_length(newListPartDef->subPartitionDefState);
}
partTupleInfo.pkey = subpartition_key;
partTupleInfo.boundaries = boundaryValue;
partTupleInfo.reloptions = reloptions;
if (partExprKeyInfo) {
partTupleInfo.partexprkeyinfo.partkeyexprIsNull = partExprKeyInfo->partkeyexprIsNull;
partTupleInfo.partexprkeyinfo.partkeyIsFunc = partExprKeyInfo->partkeyIsFunc;
partTupleInfo.partexprkeyinfo.partExprKeyStr = partExprKeyInfo->partExprKeyStr;
}
addNewPartitionTuple(pgPartRel, newListPartition, &partTupleInfo);
if (isSubpartition) {
PartitionCloseSmgr(newListPartition);
Partition part = newListPartition;
if (PartitionIsBucket(newListPartition)) {
part = newListPartition->parent;
bucketClosePartition(newListPartition);
}
PartitionClose(part);
} else {
relation = relation_open(partTableOid, NoLock);
PartitionCloseSmgr(newListPartition);
partitionClose(relation, newListPartition, NoLock);
relation_close(relation, NoLock);
}
return newListPartitionOid;
}
Timestamp Align2UpBoundary(Timestamp value, Interval* intervalValue, Timestamp boundary)
{
Timestamp nearbyBoundary = boundary;
Interval* diff = DatumGetIntervalP(timestamp_mi(value, boundary));
int multiple = (int)(INTERVAL_TO_USEC(diff) / INTERVAL_TO_USEC(intervalValue));
pfree(diff);
if (multiple != 0) {
Interval* integerInterval = DatumGetIntervalP(interval_mul(intervalValue, (float8)multiple));
nearbyBoundary = DatumGetTimestamp(timestamp_pl_interval(boundary, integerInterval));
pfree(integerInterval);
}
if (nearbyBoundary <= value) {
while (true) {
nearbyBoundary = DatumGetTimestamp(timestamp_pl_interval(nearbyBoundary, intervalValue));
if (nearbyBoundary > value) {
return nearbyBoundary;
}
}
} else {
while (true) {
Timestamp res = DatumGetTimestamp(timestamp_mi_interval(nearbyBoundary, intervalValue));
if (res <= value) {
return nearbyBoundary;
}
nearbyBoundary = res;
}
}
}
Datum Timestamp2Boundarys(Relation rel, Timestamp ts)
{
Const consts;
RangePartitionMap* partMap = (RangePartitionMap*)rel->partMap;
Const* lastPartBoundary = partMap->rangeElements[partMap->rangeElementsNum - 1].boundary[0];
bool isTimestamptz = lastPartBoundary->consttype == TIMESTAMPTZOID;
Datum columnRaw;
if (lastPartBoundary->consttype == DATEOID) {
columnRaw = timestamp2date(ts);
} else {
columnRaw = TimestampGetDatum(ts);
}
int2vector* partKeyColumn = partMap->base.partitionKey;
Assert(partKeyColumn->dim1 == 1);
(void)transformDatum2Const(rel->rd_att, partKeyColumn->values[0], columnRaw, false, &consts);
List* bondary = list_make1(&consts);
Datum res = transformPartitionBoundary(bondary, &isTimestamptz);
list_free(bondary);
return res;
}
* calculate date type partititon boundary by insertedValue and lastBoundary. DestBoundary should match the equation:
* abs(destBoundary - lastBoundary) = n * interval, (n > 0)
*/
static Datum DateAlign2UpBoundary(Datum insertedValueDatum, Interval *intervalValue, Const *lastBoundary)
{
Timestamp insertedTs;
Timestamp boundaryTs;
if (lastBoundary->consttype == DATEOID) {
insertedTs = date2timestamp(DatumGetDateADT(insertedValueDatum));
boundaryTs = date2timestamp(DatumGetDateADT(lastBoundary->constvalue));
} else {
insertedTs = DatumGetTimestamp(insertedValueDatum);
boundaryTs = DatumGetTimestamp(lastBoundary->constvalue);
}
Timestamp nearbyValue = boundaryTs;
Interval *diff = DatumGetIntervalP(timestamp_mi(insertedTs, boundaryTs));
int multiple = (int)(INTERVAL_TO_USEC(diff) / INTERVAL_TO_USEC(intervalValue));
pfree_ext(diff);
if (multiple != 0) {
Interval *integerInterval = DatumGetIntervalP(interval_mul(intervalValue, (float8)multiple));
nearbyValue = DatumGetTimestamp(timestamp_pl_interval(boundaryTs, integerInterval));
pfree_ext(integerInterval);
}
if (nearbyValue <= insertedTs) {
while (true) {
nearbyValue = DatumGetTimestamp(timestamp_pl_interval(nearbyValue, intervalValue));
if (nearbyValue > insertedTs) {
break;
}
CHECK_FOR_INTERRUPTS();
}
} else {
while (true) {
Timestamp res = DatumGetTimestamp(timestamp_mi_interval(nearbyValue, intervalValue));
if (res <= insertedTs) {
break;
}
nearbyValue = res;
CHECK_FOR_INTERRUPTS();
}
}
if (lastBoundary->consttype == DATEOID) {
return timestamp2date(nearbyValue);
} else {
return TimestampGetDatum(nearbyValue);
}
}
static Node *GetIntervalBoundaryByTuple(Relation rel, Tuple tuple)
{
Assert(PartitionMapIsInterval(rel->partMap));
RangePartitionMap *partMap = (RangePartitionMap*)rel->partMap;
int2vector *partKeyColumn = partMap->base.partitionKey;
Assert(partKeyColumn->dim1 == 1);
Assert(partMap->base.type == PART_TYPE_INTERVAL);
Assert(partMap->rangeElementsNum >= 1);
Const *lastPartBoundary = partMap->rangeElements[partMap->rangeElementsNum - 1].boundary[0];
bool isNull = false;
Datum constValue = tableam_tops_tuple_fast_getattr(tuple, partKeyColumn->values[0], rel->rd_att, &isNull);
Datum destValue;
if (lastPartBoundary->consttype == DATEOID || lastPartBoundary->consttype == TIMESTAMPOID
|| lastPartBoundary->consttype == TIMESTAMPTZOID) {
destValue = DateAlign2UpBoundary(constValue, partMap->intervalValue, lastPartBoundary);
} else {
ereport(ERROR, (errmsg("Interval partition key only support type date, timestamp or timestamptz")));
}
return (Node *)makeConst(lastPartBoundary->consttype, lastPartBoundary->consttypmod,
lastPartBoundary->constcollid, lastPartBoundary->constlen, destValue, isNull, lastPartBoundary->constbyval);
}
Datum GetPartBoundaryByTuple(Relation rel, Tuple tuple)
{
RangePartitionMap* partMap = (RangePartitionMap*)rel->partMap;
int2vector* partKeyColumn = partMap->base.partitionKey;
Assert(partKeyColumn->dim1 == 1);
Assert(partMap->base.type == PART_TYPE_INTERVAL);
Assert(partMap->rangeElementsNum >= 1);
Const* lastPartBoundary = partMap->rangeElements[partMap->rangeElementsNum - 1].boundary[0];
Assert(lastPartBoundary->consttype == TIMESTAMPOID || lastPartBoundary->consttype == TIMESTAMPTZOID ||
lastPartBoundary->consttype == DATEOID);
bool isNull = false;
Datum columnRaw;
Timestamp value;
Timestamp boundaryTs;
columnRaw = tableam_tops_tuple_fast_getattr(tuple, partKeyColumn->values[0], rel->rd_att, &isNull);
if (lastPartBoundary->consttype == DATEOID) {
value = date2timestamp(DatumGetDateADT(columnRaw));
boundaryTs = date2timestamp(DatumGetDateADT(lastPartBoundary->constvalue));
} else {
value = DatumGetTimestamp(columnRaw);
boundaryTs = DatumGetTimestamp(lastPartBoundary->constvalue);
}
return Timestamp2Boundarys(rel, Align2UpBoundary(value, partMap->intervalValue, boundaryTs));
}
static char *HeapGetIntervalBoundaryCstring(Relation rel, Tuple tuple)
{
Node *boundary = GetIntervalBoundaryByTuple(rel, tuple);
bool *isTimestamptz = CheckPartkeyHasTimestampwithzone(rel);
char *boundaryStr = ConstBondaryGetString((Const *)boundary, *isTimestamptz);
StringInfo boundaries = makeStringInfo();
appendStringInfo(boundaries, "'%s'", boundaryStr);
char *boundariesStr = boundaries->data;
pfree_ext(isTimestamptz);
pfree_ext(boundary);
pfree_ext(boundaries);
return boundariesStr;
}
static char *HeapGetIntervalNextTablespace(Relation rel)
{
RangePartitionMap *partMap = (RangePartitionMap *)rel->partMap;
Oid newTbsOid = InvalidOid;
if (partMap->intervalTablespace != NULL) {
newTbsOid = ChooseIntervalTablespace(rel);
}
if (!OidIsValid(newTbsOid)) {
return NULL;
}
return get_tablespace_name(newTbsOid);
}
static bool HeapAddIntervalPartitionByAutonomousSession(Relation rel, Tuple insertTuple)
{
char *relname = RelationGetRelationName(rel);
ereport(LOG, (errmsg("add new interval partition on heap %s by autonomous session.", relname)));
bool result = true;
PG_TRY();
{
char *spcname = get_namespace_name(RelationGetNamespace(rel));
char *destname = GenIntervalPartitionName(rel);
char *boundary = HeapGetIntervalBoundaryCstring(rel, insertTuple);
char *partspcname = HeapGetIntervalNextTablespace(rel);
u_sess->is_partition_autonomous_query = true;
CreateAutonomousSession();
u_sess->SPI_cxt.autonomous_session->ExecSimpleQuery("START TRANSACTION;", NULL, 0);
ATResult res = u_sess->SPI_cxt.autonomous_session->ExecSimpleQuery("select txid_current();", NULL, 0);
int64 currentXid = DatumGetInt64(res.ResTup);
StringInfo sql = makeStringInfo();
appendStringInfo(sql, "ALTER TABLE %s.%s ADD PARTITION %s VALUES LESS THAN (%s)", quote_identifier(spcname),
quote_identifier(relname), quote_identifier(destname), boundary);
if (PointerIsValid(partspcname)) {
appendStringInfo(sql, " TABLESPACE %s", quote_identifier(partspcname));
}
appendStringInfo(sql, ";");
appendStringInfo(sql, "COMMIT;");
u_sess->SPI_cxt.autonomous_session->ExecSimpleQuery(sql->data, NULL, currentXid, true);
DestroyStringInfo(sql);
u_sess->is_partition_autonomous_query = false;
DestoryAutonomousSession(false);
pfree_ext(spcname);
pfree_ext(destname);
pfree_ext(boundary);
pfree_ext(partspcname);
}
PG_CATCH();
{
u_sess->is_partition_autonomous_query = false;
DestoryAutonomousSession(true);
result = false;
FlushErrorState();
ereport(LOG,
(errmsg("add new interval partition on heap %s by autonomous session failed.", relname)));
}
PG_END_TRY();
return result;
}
Oid AddNewIntervalPartition(Relation rel, void* insertTuple, int *partitionno, bool isDDL)
{
Relation pgPartRel = NULL;
Oid newPartOid = InvalidOid;
Datum newRelOptions;
Datum relOptions;
HeapTuple tuple;
bool isNull = false;
List* oldRelOptions = NIL;
Oid bucketOid;
if (rel->partMap->isDirty) {
CacheInvalidateRelcache(rel);
}
LockPartitionObject(rel->rd_id, INTERVAL_PARTITION_LOCK_SDEQUENCE, PARTITION_EXCLUSIVE_LOCK);
partitionRoutingForTuple(rel, insertTuple, u_sess->catalog_cxt.route, false, true);
if (u_sess->catalog_cxt.route->fileExist) {
Assert(OidIsValid(u_sess->catalog_cxt.route->partitionId));
UnlockPartitionObject(rel->rd_id, INTERVAL_PARTITION_LOCK_SDEQUENCE, PARTITION_EXCLUSIVE_LOCK);
if (PointerIsValid(partitionno)) {
*partitionno = GetPartitionnoFromSequence(rel->partMap, u_sess->catalog_cxt.route->partSeq);
}
return u_sess->catalog_cxt.route->partitionId;
}
if ((getNumberOfPartitions(rel) + 1) > MAX_PARTITION_NUM) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("too many partitions for partitioned table"),
errhint("Number of partitions can not be more than %d", MAX_PARTITION_NUM)));
}
if (!checkRelationLocalIndexesUsable(rel)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("can't add partition bacause the relation %s has unusable local index",
RelationGetRelationName(rel)),
errhint("please reindex the unusable index first.")));
}
if (RELATION_SUPPORT_AUTONOMOUS_EXTEND_PARTITION
&& HeapAddIntervalPartitionByAutonomousSession(rel, insertTuple)) {
UnlockPartitionObject(rel->rd_id, INTERVAL_PARTITION_LOCK_SDEQUENCE, PARTITION_EXCLUSIVE_LOCK);
AcceptInvalidationMessages();
partitionRoutingForTuple(rel, insertTuple, u_sess->catalog_cxt.route, false, true);
if (u_sess->catalog_cxt.route->fileExist) {
Assert(OidIsValid(u_sess->catalog_cxt.route->partitionId));
newPartOid = u_sess->catalog_cxt.route->partitionId;
if (PointerIsValid(partitionno)) {
*partitionno = GetPartitionnoFromSequence(rel->partMap, u_sess->catalog_cxt.route->partSeq);
}
} else {
ereport(ERROR, (errmsg("search failed for new added interval partition.")));
}
return newPartOid;
}
ereport(LOG,
(errmsg("add new interval partition on heap %s by current session.", RelationGetRelationName(rel))));
pgPartRel = relation_open(PartitionRelationId, RowExclusiveLock);
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rel->rd_id));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for relation %u", rel->rd_id)));
}
relOptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isNull);
oldRelOptions = untransformRelOptions(relOptions);
newRelOptions = transformRelOptions((Datum)0, oldRelOptions, NULL, NULL, false, false);
ReleaseSysCache(tuple);
if (oldRelOptions != NIL) {
list_free_ext(oldRelOptions);
}
bucketOid = RelationGetBucketOid(rel);
newPartOid = HeapAddIntervalPartition(pgPartRel,
rel,
rel->rd_id,
InvalidOid,
rel->rd_rel->reltablespace,
bucketOid,
GetPartBoundaryByTuple(rel, (HeapTuple)insertTuple),
rel->rd_rel->relowner,
(Datum)newRelOptions,
RelationGetStorageType(rel));
CommandCounterIncrement();
addIndexForPartition(rel, newPartOid);
addToastTableForNewPartition(rel, newPartOid);
CacheInvalidateRelcache(rel);
relation_close(pgPartRel, NoLock);
* We must bump the command counter to make the newly-created
* table partition visible for using.
*/
CommandCounterIncrement();
* If add interval partition in the DDL, do not need to change the csn
* because the scn has been changed in the DDL.
*/
if (!isDDL) {
UpdatePgObjectChangecsn(RelationGetRelid(rel), rel->rd_rel->relkind);
}
* be locked in CommitTransaction. */
#ifndef ENABLE_MULTIPLE_NODES
AddPartitionDDLInfo(RelationGetRelid(rel));
#endif
if (PointerIsValid(partitionno)) {
*partitionno = GetCurrentPartitionNo(newPartOid);
PARTITIONNO_VALID_ASSERT(*partitionno);
}
return newPartOid;
}
Oid HeapAddHashPartition(Relation pgPartRel, Oid partTableOid, Oid partTablespace, Oid bucketOid,
HashPartitionDefState* newHashPartDef, Oid ownerid, Datum reloptions, const bool* isTimestamptz,
StorageType storage_type, int2vector* subpartition_key, bool isSubpartition,
PartitionExprKeyInfo *partExprKeyInfo)
{
Datum boundaryValue = (Datum)0;
Oid newHashPartitionOid = InvalidOid;
Oid newPartitionTableSpaceOid = InvalidOid;
Oid partrelfileOid = InvalidOid;
Relation relation;
Partition newHashPartition;
if (!PointerIsValid(newHashPartDef)) {
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("missing definition for new partition")));
}
if (!PointerIsValid(newHashPartDef->boundary)) {
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("boundary not defined for new partition")));
}
if (newHashPartDef->boundary->length != 1) {
ereport(ERROR,
(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
errmsg("too many partition keys, allowed is 1")));
}
if (!PointerIsValid(newHashPartDef->partitionName)) {
ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("partition name is invalid")));
}
bool isTime = false;
boundaryValue = transformPartitionBoundary(newHashPartDef->boundary, &isTime);
if (newHashPartDef->tablespacename) {
newPartitionTableSpaceOid = get_tablespace_oid(newHashPartDef->tablespacename, false);
}
if (!OidIsValid(newPartitionTableSpaceOid)) {
newPartitionTableSpaceOid = partTablespace;
}
if (OidIsValid(newPartitionTableSpaceOid) && newPartitionTableSpaceOid != u_sess->proc_cxt.MyDatabaseTableSpace) {
AclResult aclresult;
aclresult = pg_tablespace_aclcheck(newPartitionTableSpaceOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK) {
aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(newPartitionTableSpaceOid));
}
}
GetNewPartitionOidAndNewPartrelfileOid(newHashPartDef->subPartitionDefState, &newHashPartitionOid, &partrelfileOid,
pgPartRel, newPartitionTableSpaceOid);
LockPartitionOid(partTableOid, (uint32)newHashPartitionOid, AccessExclusiveLock);
bool forPartitionTable = false;
if (isSubpartition) {
forPartitionTable = false;
} else if (newHashPartDef->subPartitionDefState != NULL) {
forPartitionTable = true;
} else {
forPartitionTable = false;
}
newHashPartition = heapCreatePartition(newHashPartDef->partitionName,
forPartitionTable,
newPartitionTableSpaceOid,
newHashPartitionOid,
partrelfileOid,
bucketOid,
ownerid,
storage_type,
false,
reloptions);
Assert(newHashPartitionOid == PartitionGetPartid(newHashPartition));
PartitionTupleInfo partTupleInfo = PartitionTupleInfo();
if (isSubpartition) {
InitSubPartitionDef(newHashPartition, partTableOid, PART_STRATEGY_HASH);
partTupleInfo.partitionno = INVALID_PARTITION_NO;
partTupleInfo.subpartitionno = newHashPartDef->partitionno;
} else {
InitPartitionDef(newHashPartition, partTableOid, PART_STRATEGY_HASH);
partTupleInfo.partitionno = newHashPartDef->partitionno;
partTupleInfo.subpartitionno = -list_length(newHashPartDef->subPartitionDefState);
}
partTupleInfo.pkey = subpartition_key;
partTupleInfo.boundaries = boundaryValue;
partTupleInfo.reloptions = reloptions;
if (partExprKeyInfo) {
partTupleInfo.partexprkeyinfo.partkeyexprIsNull = partExprKeyInfo->partkeyexprIsNull;
partTupleInfo.partexprkeyinfo.partkeyIsFunc = partExprKeyInfo->partkeyIsFunc;
partTupleInfo.partexprkeyinfo.partExprKeyStr = partExprKeyInfo->partExprKeyStr;
}
addNewPartitionTuple(pgPartRel, newHashPartition, &partTupleInfo);
if (isSubpartition) {
PartitionCloseSmgr(newHashPartition);
Partition part = newHashPartition;
if (PartitionIsBucket(newHashPartition)) {
part = newHashPartition->parent;
bucketClosePartition(newHashPartition);
}
PartitionClose(part);
} else {
relation = relation_open(partTableOid, NoLock);
PartitionCloseSmgr(newHashPartition);
partitionClose(relation, newHashPartition, NoLock);
relation_close(relation, NoLock);
}
return newHashPartitionOid;
}
static void addNewPartitionTupleForValuePartitionedTable(Relation pg_partition_rel, const char* relname,
const Oid reloid, const Oid reltablespaceid, const TupleDesc reltupledesc, const PartitionState* partTableState,
Datum reloptions)
{
Datum values[Natts_pg_partition];
bool nulls[Natts_pg_partition];
HeapTuple tup;
int2vector* partition_key_attr_no = buildPartitionKey(partTableState->partitionKey, reltupledesc);
errno_t errorno = EOK;
errorno = memset_s(values, sizeof(values), 0, sizeof(values));
securec_check_c(errorno, "\0", "\0");
errorno = memset_s(nulls, sizeof(nulls), 0, sizeof(nulls));
securec_check_c(errorno, "\0", "\0");
values[Anum_pg_partition_relname - 1] = NameGetDatum(relname);
values[Anum_pg_partition_parttype - 1] = CharGetDatum(PART_OBJ_TYPE_PARTED_TABLE);
values[Anum_pg_partition_parentid - 1] = ObjectIdGetDatum(reloid);
values[Anum_pg_partition_rangenum - 1] = UInt32GetDatum(0);
values[Anum_pg_partition_intervalnum - 1] = UInt32GetDatum(0);
values[Anum_pg_partition_partstrategy - 1] = CharGetDatum(partTableState->partitionStrategy);
values[Anum_pg_partition_relfilenode - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_partition_reltablespace - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_partition_relpages - 1] = Float8GetDatum(0);
values[Anum_pg_partition_reltuples - 1] = Float8GetDatum(0);
values[Anum_pg_partition_relallvisible - 1] = UInt32GetDatum(0);
values[Anum_pg_partition_reltoastrelid - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_partition_reltoastidxid - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_partition_indextblid - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_partition_deltarelid - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_partition_reldeltaidx - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_partition_relcudescrelid - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_partition_relcudescidx - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_partition_indisusable - 1] = BoolGetDatum(true);
values[Anum_pg_partition_relfrozenxid - 1] = ShortTransactionIdGetDatum(InvalidTransactionId);
values[Anum_pg_partition_partkey - 1] = PointerGetDatum(partition_key_attr_no);
nulls[Anum_pg_partition_intablespace - 1] = true;
nulls[Anum_pg_partition_intspnum - 1] = true;
nulls[Anum_pg_partition_interval - 1] = true;
nulls[Anum_pg_partition_boundaries - 1] = true;
nulls[Anum_pg_partition_transit - 1] = true;
if (reloptions != (Datum)0) {
values[Anum_pg_partition_reloptions - 1] = reloptions;
nulls[Anum_pg_partition_reloptions - 1] = false;
} else {
nulls[Anum_pg_partition_reloptions - 1] = true;
}
values[Anum_pg_partition_relfrozenxid64 - 1] = TransactionIdGetDatum(InvalidTransactionId);
#ifndef ENABLE_MULTIPLE_NODES
values[Anum_pg_partition_relminmxid - 1] = TransactionIdGetDatum(InvalidMultiXactId);
#endif
tup = heap_form_tuple(RelationGetDescr(pg_partition_rel), values, nulls);
HeapTupleSetOid(tup, InvalidOid);
(void)simple_heap_insert(pg_partition_rel, tup);
CatalogUpdateIndexes(pg_partition_rel, tup);
heap_freetuple(tup);
if (partition_key_attr_no != NULL) {
pfree(partition_key_attr_no);
}
}
* @@GaussDB@@
* Brief :
* Description : Add new partition tuple into pg_partition for partitioned-table
* Notes :
* Parameters : pg_partition_rel: RelationData pointer for pg_partition,
* the caller MUST get RowExclusiveLock on pg_partition
* relname: partitioned table's name
* reloid : partitioned table's oid in pg_class
* reltupledesc : TupleDesc for partitioned tuple, it is used to get partition key attribute number.
* partTableState: partition definition of partitioned table.
*/
static void addNewPartitionTupleForTable(Relation pg_partition_rel, const char* relname, const Oid reloid,
const Oid reltablespaceid, const TupleDesc reltupledesc, const PartitionState* partTableState, Oid ownerid,
Datum reloptions, PartitionExprKeyInfo *partExprKeyInfo)
{
Datum interval = (Datum)0;
Datum transition_point = (Datum)0;
Oid new_partition_oid = InvalidOid;
int2vector* partition_key_attr_no = NULL;
oidvector* interval_talespace = NULL;
Relation relation = NULL;
Partition new_partition = NULL;
Datum newOptions;
Oid new_partition_rfoid = InvalidOid;
Assert(pg_partition_rel);
if (partTableState->partitionStrategy != 'l' && partTableState->partitionStrategy != 'h') {
RangePartitionDefState* lastPartition = NULL;
lastPartition = (RangePartitionDefState*)lfirst(partTableState->partitionList->tail);
if (lastPartition->boundary->length > MAX_PARTKEY_NUMS) {
ereport(ERROR,
(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
errmsg("number of partition key columns MUST less or equal than %d", MAX_PARTKEY_NUMS)));
}
}
partition_key_attr_no = buildPartitionKey(partTableState->partitionKey, reltupledesc);
if (partTableState->intervalPartDef != NULL) {
interval_talespace = BuildIntervalTablespace(partTableState->intervalPartDef);
interval = BuildInterval(partTableState->intervalPartDef->partInterval);
}
if (u_sess->proc_cxt.IsBinaryUpgrade && OidIsValid(u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid)) {
new_partition_oid = u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid;
u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid = InvalidOid;
new_partition_rfoid = u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_rfoid;
u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_rfoid = InvalidOid;
} else {
new_partition_oid = GetNewOid(pg_partition_rel);
}
new_partition = heapCreatePartition(relname,
true,
reltablespaceid,
new_partition_oid,
new_partition_rfoid,
InvalidOid,
ownerid,
HEAP_DISK,
false,
reloptions);
Assert(new_partition_oid == PartitionGetPartid(new_partition));
new_partition->pd_part->parttype = PART_OBJ_TYPE_PARTED_TABLE;
new_partition->pd_part->parentid = reloid;
new_partition->pd_part->rangenum = 0;
new_partition->pd_part->intervalnum = 0;
new_partition->pd_part->partstrategy = partTableState->partitionStrategy;
new_partition->pd_part->reltoastrelid = InvalidOid;
new_partition->pd_part->reltoastidxid = InvalidOid;
new_partition->pd_part->indextblid = InvalidOid;
new_partition->pd_part->reldeltarelid = InvalidOid;
new_partition->pd_part->reldeltaidx = InvalidOid;
new_partition->pd_part->relcudescrelid = InvalidOid;
new_partition->pd_part->relcudescidx = InvalidOid;
new_partition->pd_part->indisusable = true;
newOptions = SetWaitCleanGpiRelOptions(reloptions, false);
PartitionTupleInfo partTupleInfo = PartitionTupleInfo();
partTupleInfo.pkey = partition_key_attr_no;
partTupleInfo.intablespace = interval_talespace;
partTupleInfo.interval = interval;
partTupleInfo.boundaries = (Datum)0;
partTupleInfo.transitionPoint = transition_point;
partTupleInfo.reloptions = newOptions;
partTupleInfo.partitionno = -list_length(partTableState->partitionList);
partTupleInfo.subpartitionno = INVALID_PARTITION_NO;
if (partExprKeyInfo) {
partTupleInfo.partexprkeyinfo.partkeyexprIsNull = partExprKeyInfo->partkeyexprIsNull;
partTupleInfo.partexprkeyinfo.partkeyIsFunc = partExprKeyInfo->partkeyIsFunc;
partTupleInfo.partexprkeyinfo.partExprKeyStr = partExprKeyInfo->partExprKeyStr;
}
addNewPartitionTuple(pg_partition_rel, new_partition, &partTupleInfo);
relation = relation_open(reloid, NoLock);
partitionClose(relation, new_partition, NoLock);
relation_close(relation, NoLock);
pfree_ext(partition_key_attr_no);
pfree_ext(interval_talespace);
if (interval != 0) {
pfree(DatumGetPointer(interval));
}
}
bool IsExistDefaultSubpartitionName(List *partitionNameList, char *defaultPartitionName)
{
ListCell *cell = NULL;
foreach (cell, partitionNameList) {
char *partitionName = (char *)lfirst(cell);
if (!strcmp(partitionName, defaultPartitionName)) {
return true;
}
}
return false;
}
static void MakeDefaultSubpartitionName(PartitionState *partitionState, char **subPartitionName,
const char *partitionName)
{
int numLen = 0;
int subPartitionNameLen = 0;
List *partitionNameList = NIL;
if (PointerIsValid(partitionState->partitionList)) {
partitionNameList = GetPartitionNameList(partitionState->partitionList);
} else if (PointerIsValid(partitionState->partitionNameList)) {
partitionNameList = partitionState->partitionNameList;
}
for (int i = 1; i < INT32_MAX_VALUE; i++) {
numLen = (int)log10(i) + 1;
subPartitionNameLen = strlen(partitionName) + strlen("_subpartdefault") + numLen + 1;
if (subPartitionNameLen > NAMEDATALEN) {
subPartitionNameLen = strlen("sys_subpartdefault") + numLen + 1;
*subPartitionName = (char *)palloc0(subPartitionNameLen);
errno_t rc = snprintf_s(*subPartitionName, subPartitionNameLen, subPartitionNameLen - 1,
"sys_subpartdefault%d", i);
securec_check_ss(rc, "\0", "\0");
} else {
*subPartitionName = (char *)palloc0(subPartitionNameLen);
errno_t rc = snprintf_s(*subPartitionName, subPartitionNameLen, subPartitionNameLen - 1,
"%s_subpartdefault%d", partitionName, i);
securec_check_ss(rc, "\0", "\0");
}
bool isExist = IsExistDefaultSubpartitionName(partitionNameList, *subPartitionName);
if (isExist) {
pfree_ext(*subPartitionName);
} else {
break;
}
}
if (PointerIsValid(partitionState->partitionList)) {
list_free_ext(partitionNameList);
}
}
static ListPartitionDefState *MakeListDefaultSubpartition(PartitionState *partitionState, char *partitionName,
char *tablespacename)
{
ListPartitionDefState *subPartitionDefState = makeNode(ListPartitionDefState);
MakeDefaultSubpartitionName(partitionState, &subPartitionDefState->partitionName, partitionName);
Const *boundaryDefault = makeNode(Const);
boundaryDefault->ismaxvalue = true;
boundaryDefault->location = -1;
subPartitionDefState->boundary = list_make1(boundaryDefault);
subPartitionDefState->tablespacename = pstrdup(tablespacename);
return subPartitionDefState;
}
static HashPartitionDefState *MakeHashDefaultSubpartition(PartitionState *partitionState, char *partitionName,
char *tablespacename)
{
HashPartitionDefState *subPartitionDefState = makeNode(HashPartitionDefState);
MakeDefaultSubpartitionName(partitionState, &subPartitionDefState->partitionName, partitionName);
Const *boundaryDefault = makeConst(INT4OID, -1, InvalidOid, sizeof(int32), Int32GetDatum(0), false, true);
subPartitionDefState->boundary = list_make1(boundaryDefault);
subPartitionDefState->tablespacename = pstrdup(tablespacename);
return subPartitionDefState;
}
static RangePartitionDefState *MakeRangeDefaultSubpartition(PartitionState *partitionState, char *partitionName,
char *tablespacename)
{
RangePartitionDefState *subPartitionDefState = makeNode(RangePartitionDefState);
MakeDefaultSubpartitionName(partitionState, &subPartitionDefState->partitionName, partitionName);
Const *boundaryDefault = makeNode(Const);
boundaryDefault->ismaxvalue = true;
subPartitionDefState->boundary = list_make1(boundaryDefault);
subPartitionDefState->tablespacename = pstrdup(tablespacename);
return subPartitionDefState;
}
Node *MakeDefaultSubpartition(PartitionState *partitionState, PartitionDefState *partitionDefState)
{
PartitionState *subPartitionState = partitionState->subPartitionState;
char subPartitionStrategy = subPartitionState->partitionStrategy;
char *partitionName = partitionDefState->partitionName;
char *tablespacename = partitionDefState->tablespacename;
if (subPartitionStrategy == PART_STRATEGY_LIST) {
ListPartitionDefState *subPartitionDefState =
MakeListDefaultSubpartition(partitionState, partitionName, tablespacename);
subPartitionDefState->partitionno = 1;
return (Node *)subPartitionDefState;
} else if (subPartitionStrategy == PART_STRATEGY_HASH) {
HashPartitionDefState *subPartitionDefState =
MakeHashDefaultSubpartition(partitionState, partitionName, tablespacename);
subPartitionDefState->partitionno = 1;
return (Node *)subPartitionDefState;
} else {
RangePartitionDefState *subPartitionDefState =
MakeRangeDefaultSubpartition(partitionState, partitionName, tablespacename);
subPartitionDefState->partitionno = 1;
return (Node *)subPartitionDefState;
}
}
List *addNewSubPartitionTuplesForPartition(Relation pgPartRel, Oid partTableOid, Oid partTablespace,
Oid bucketOid, Oid ownerid, Datum reloptions,
const bool *isTimestamptz, StorageType storage_type,
PartitionState *partitionState, Node *partitionDefState,
LOCKMODE partLockMode)
{
List *subpartOidList = NIL;
if (partitionState->subPartitionState == NULL) {
return NIL;
}
PartitionState *subPartitionState = partitionState->subPartitionState;
ListCell *lc = NULL;
Oid subpartOid = InvalidOid;
char subPartitionStrategy = subPartitionState->partitionStrategy;
List *subPartitionDefStateList = ((PartitionDefState *)partitionDefState)->subPartitionDefState;
foreach (lc, subPartitionDefStateList) {
if (subPartitionStrategy == PART_STRATEGY_LIST) {
ListPartitionDefState *subPartitionDefState = (ListPartitionDefState *)lfirst(lc);
subpartOid = HeapAddListPartition(pgPartRel, partTableOid, partTablespace, bucketOid,
subPartitionDefState, ownerid, reloptions, isTimestamptz, storage_type, NULL, true);
} else if (subPartitionStrategy == PART_STRATEGY_HASH) {
HashPartitionDefState *subPartitionDefState = (HashPartitionDefState *)lfirst(lc);
subpartOid = HeapAddHashPartition(pgPartRel, partTableOid, partTablespace, bucketOid,
subPartitionDefState, ownerid, reloptions, isTimestamptz, storage_type, NULL, true);
} else {
RangePartitionDefState *subPartitionDefState = (RangePartitionDefState *)lfirst(lc);
subpartOid = heapAddRangePartition(pgPartRel, partTableOid, partTablespace, bucketOid,
subPartitionDefState, ownerid, reloptions, isTimestamptz, storage_type, partLockMode, NULL, true);
}
if (OidIsValid(subpartOid)) {
subpartOidList = lappend_oid(subpartOidList, subpartOid);
}
}
return subpartOidList;
}
* check whether the partion key has timestampwithzone type.
*/
static void IsPartitionKeyContainTimestampwithzoneType(const PartitionState *partTableState, const TupleDesc tupledesc,
bool *isTimestamptz, int partKeyNum)
{
ListCell *partKeyCell = NULL;
ColumnRef *col = NULL;
char *columName = NULL;
int partKeyIdx = 0;
int attnum = tupledesc->natts;
FormData_pg_attribute *attrs = tupledesc->attrs;
foreach (partKeyCell, partTableState->partitionKey) {
col = (ColumnRef *)lfirst(partKeyCell);
columName = ((Value *)linitial(col->fields))->val.str;
isTimestamptz[partKeyIdx] = false;
for (int i = 0; i < attnum; i++) {
if (TIMESTAMPTZOID == attrs[i].atttypid && 0 == strcmp(columName, attrs[i].attname.data)) {
isTimestamptz[partKeyIdx] = true;
break;
}
}
partKeyIdx++;
}
Assert(partKeyIdx == partKeyNum);
}
* so for a subpartition, the tablespace itself is used, if not exist, inherit from the partition, and if
* still not exist, inherit from the table */
Oid GetPartTablespaceOidForSubpartition(Oid reltablespace, const char* partTablespacename)
{
Oid partTablespaceOid = InvalidOid;
if (PointerIsValid(partTablespacename)) {
partTablespaceOid = get_tablespace_oid(partTablespacename, false);
}
if (!OidIsValid(partTablespaceOid)) {
partTablespaceOid = reltablespace;
}
return partTablespaceOid;
}
* @@GaussDB@@
* Brief :
* Description : add new partition tuples of partitioned table into pg_partition
* Notes :
* Parameters : pg_partition_rel: RelationData pointer for pg_partition,
* the caller MUST get RowExclusiveLock on pg_partition
* relid: partitioned table's oid
* reltablespace: partitioned table's tablespace.
* If partition's tablespace is not provided, it will inherit from parent partitioned
* table. BufferPoolId: partitioned table's BufferPoolId. All partitions of one partitioned table have same
* BufferPoolId. partTableState: partition schema of partitioned table which is being created right now.
*
*/
static void addNewPartitionTuplesForPartition(Relation pg_partition_rel, Oid relid,
Oid reltablespace, Oid bucketOid, PartitionState* partTableState, Oid ownerid, Datum reloptions,
const TupleDesc tupledesc, char strategy, StorageType storage_type, LOCKMODE partLockMode, PartitionExprKeyInfo *partExprKeyInfo)
{
int partKeyNum = list_length(partTableState->partitionKey);
bool isTimestamptzForPartKey[partKeyNum];
memset_s(isTimestamptzForPartKey, sizeof(isTimestamptzForPartKey), 0, sizeof(isTimestamptzForPartKey));
IsPartitionKeyContainTimestampwithzoneType(partTableState, tupledesc, isTimestamptzForPartKey, partKeyNum);
bool *isTimestamptzForSubPartKey = NULL;
if (partTableState->subPartitionState != NULL) {
int subPartKeyNum = list_length(partTableState->subPartitionState->partitionKey);
isTimestamptzForSubPartKey = (bool*)palloc0(sizeof(bool) * subPartKeyNum);
IsPartitionKeyContainTimestampwithzoneType(partTableState->subPartitionState, tupledesc,
isTimestamptzForSubPartKey, subPartKeyNum);
}
ListCell* cell = NULL;
Assert(pg_partition_rel);
List *subPartitionKey = NIL;
int2vector* subpartition_key_attr_no = NULL;
if (partTableState->subPartitionState != NULL) {
subPartitionKey = partTableState->subPartitionState->partitionKey;
subpartition_key_attr_no = buildPartitionKey(subPartitionKey, tupledesc);
}
foreach (cell, partTableState->partitionList) {
if (strategy == PART_STRATEGY_LIST) {
ListPartitionDefState* partitionDefState = (ListPartitionDefState*)lfirst(cell);
if (partTableState->subPartitionState != NULL && partitionDefState->subPartitionDefState == NULL) {
Node *subPartitionDefState =
MakeDefaultSubpartition(partTableState, (PartitionDefState *)partitionDefState);
partitionDefState->subPartitionDefState =
lappend(partitionDefState->subPartitionDefState, subPartitionDefState);
}
Oid partitionOid = HeapAddListPartition(pg_partition_rel,
relid,
reltablespace,
bucketOid,
partitionDefState,
ownerid,
reloptions,
isTimestamptzForPartKey,
storage_type,
subpartition_key_attr_no,
false,
partExprKeyInfo);
Oid partTablespaceOid =
GetPartTablespaceOidForSubpartition(reltablespace, partitionDefState->tablespacename);
List *subpartitionOidList = addNewSubPartitionTuplesForPartition(pg_partition_rel, partitionOid,
partTablespaceOid, bucketOid, ownerid, reloptions, isTimestamptzForSubPartKey, storage_type,
partTableState, (Node *)partitionDefState, partLockMode);
if (subpartitionOidList != NIL) {
list_free_ext(subpartitionOidList);
}
} else if (strategy == PART_STRATEGY_HASH) {
HashPartitionDefState* partitionDefState = (HashPartitionDefState*)lfirst(cell);
if (partTableState->subPartitionState != NULL && partitionDefState->subPartitionDefState == NULL) {
Node *subPartitionDefState =
MakeDefaultSubpartition(partTableState, (PartitionDefState *)partitionDefState);
partitionDefState->subPartitionDefState =
lappend(partitionDefState->subPartitionDefState, subPartitionDefState);
}
Oid partitionOid = HeapAddHashPartition(pg_partition_rel,
relid,
reltablespace,
bucketOid,
partitionDefState,
ownerid,
reloptions,
isTimestamptzForPartKey,
storage_type,
subpartition_key_attr_no,
false,
partExprKeyInfo);
Oid partTablespaceOid =
GetPartTablespaceOidForSubpartition(reltablespace, partitionDefState->tablespacename);
List *subpartitionOidList = addNewSubPartitionTuplesForPartition(pg_partition_rel, partitionOid,
partTablespaceOid, bucketOid, ownerid, reloptions, isTimestamptzForSubPartKey, storage_type,
partTableState, (Node *)partitionDefState, partLockMode);
if (subpartitionOidList != NIL) {
list_free_ext(subpartitionOidList);
}
} else {
RangePartitionDefState* partitionDefState = (RangePartitionDefState*)lfirst(cell);
if (partTableState->subPartitionState != NULL && partitionDefState->subPartitionDefState == NULL) {
Node *subPartitionDefState =
MakeDefaultSubpartition(partTableState, (PartitionDefState *)partitionDefState);;
partitionDefState->subPartitionDefState =
lappend(partitionDefState->subPartitionDefState, subPartitionDefState);
}
Oid partitionOid = heapAddRangePartition(pg_partition_rel,
relid,
reltablespace,
bucketOid,
(RangePartitionDefState*)lfirst(cell),
ownerid,
reloptions,
isTimestamptzForPartKey,
storage_type,
partLockMode,
subpartition_key_attr_no,
false,
partExprKeyInfo);
Oid partTablespaceOid =
GetPartTablespaceOidForSubpartition(reltablespace, partitionDefState->tablespacename);
List *subpartitionOidList = addNewSubPartitionTuplesForPartition(pg_partition_rel, partitionOid,
partTablespaceOid, bucketOid, ownerid, reloptions, isTimestamptzForSubPartKey, storage_type,
partTableState, (Node *)partitionDefState, partLockMode);
if (subpartitionOidList != NIL) {
list_free_ext(subpartitionOidList);
}
}
}
if (isTimestamptzForSubPartKey != NULL) {
pfree_ext(isTimestamptzForSubPartKey);
}
}
void heap_truncate_one_part(Relation rel, Oid partOid)
{
SubTransactionId mySubid;
Partition p = partitionOpen(rel, partOid, AccessExclusiveLock);
Oid toastOid = p->pd_part->reltoastrelid;
List* partIndexlist = NULL;
Relation parentIndex = NULL;
mySubid = GetCurrentSubTransactionId();
MultiXactId multiXid = GetOldestMultiXactId();;
partIndexlist = searchPartitionIndexesByblid(partOid);
* Truncate a partition need a new relfilenode.
* Otherwise, recovery may fail in the following scenarios:
*
* start transaction;
* create table p1 *** partition by ***;
* copy p1 from ***;
* alter table p1 truncate partition ***;
* commit;
* copy p1 from ***;
* update p1 set ***;
*/
CheckTableForSerializableConflictIn(rel);
PartitionSetNewRelfilenode(rel, p, u_sess->utils_cxt.RecentXmin,
RelationIsColStore(rel) ? InvalidMultiXactId : multiXid);
if (toastOid != InvalidOid) {
Relation toastRel = heap_open(toastOid, AccessExclusiveLock);
RelationSetNewRelfilenode(toastRel, u_sess->utils_cxt.RecentXmin, multiXid);
heap_close(toastRel, NoLock);
}
if (PointerIsValid(partIndexlist)) {
ListCell* lc = NULL;
Oid partIndId = InvalidOid;
Oid parentIndId = InvalidOid;
HeapTuple partIndexTuple = NULL;
Partition indexPart = NULL;
foreach (lc, partIndexlist) {
partIndexTuple = (HeapTuple)lfirst(lc);
parentIndId = (((Form_pg_partition)GETSTRUCT(partIndexTuple)))->parentid;
partIndId = HeapTupleGetOid(partIndexTuple);
parentIndex = index_open(parentIndId, AccessShareLock);
indexPart = partitionOpen(parentIndex, partIndId, AccessExclusiveLock);
reindex_partIndex(rel, p, parentIndex, indexPart);
partitionClose(parentIndex, indexPart, NoLock);
index_close(parentIndex, NoLock);
}
}
if (toastOid != InvalidOid) {
(void)ReindexRelation(toastOid, 0, REINDEX_BTREE_INDEX, NULL);
}
freePartList(partIndexlist);
partitionClose(rel, p, NoLock);
}
* Compute the hash value of a hash-bucket tuple
* */
int2 computeTupleBucketId(Relation rel, HeapTuple tuple)
{
TupleDesc tup_desc = rel->rd_att;
int2vector* col_ids = rel->rd_bucketkey->bucketKey;
MultiHashKey mkeys;
Assert(REALTION_BUCKETKEY_VALID(rel));
mkeys.keyNum = (uint32)col_ids->dim1;
mkeys.keyTypes = rel->rd_bucketkey->bucketKeyType;
mkeys.locatorType = LOCATOR_TYPE_HASH;
mkeys.keyValues = (Datum*)palloc(mkeys.keyNum * sizeof(Datum));
mkeys.isNulls = (bool*)palloc(mkeys.keyNum * sizeof(bool));
for (int i = 0; i < col_ids->dim1; i++) {
mkeys.isNulls[i] = false;
mkeys.keyValues[i] =
fastgetattr((tuple), col_ids->values[i], tup_desc, &mkeys.isNulls[i]);
}
uint32 hashValue = hash_multikey(&mkeys);
pfree(mkeys.keyValues);
pfree(mkeys.isNulls);
return GetBucketID(hashValue, rel->rd_bucketmapsize);
}
int lookupHBucketid(oidvector *buckets, int low, int2 bktId)
{
int high = buckets->dim1 - 1;
Assert(low <= high);
while (low <= high) {
int mid = (high + low) / 2;
int2 bid = buckets->values[mid];
if (bktId == bid) {
return mid;
} else if (bktId > bid) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
extern PartKeyExprResult
ComputePartKeyExprTuple(Relation rel, EState *estate, TupleTableSlot *slot, Relation partRel, char *partExprKeyStr);
Oid getPartitionIdFromTuple(Relation rel, void *tuple, EState* estate, TupleTableSlot* slot, int *partitionno, bool isDDL, bool canIgnore)
{
char* partExprKeyStr = NULL;
Oid targetOid = InvalidOid;
bool partExprKeyIsNull = PartExprKeyIsNull(rel, &partExprKeyStr);
if (partExprKeyIsNull) {
targetOid = heapTupleGetPartitionOid(rel, tuple, partitionno, isDDL, canIgnore);
} else {
PartKeyExprResult partKeyExprResult = ComputePartKeyExprTuple(rel, estate, slot, NULL, partExprKeyStr);
targetOid = heapTupleGetPartitionOid(rel, (void*)(&partKeyExprResult), partitionno, isDDL, canIgnore, false);
}
pfree_ext(partExprKeyStr);
return targetOid;
}
static bool binary_upgrade_is_next_part_pg_partition_oid_valid()
{
if (NULL == u_sess->upg_cxt.binary_upgrade_next_part_pg_partition_oid) {
return false;
}
if (u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_oid >=
u_sess->upg_cxt.binary_upgrade_max_part_pg_partition_oid) {
return false;
}
if (!OidIsValid(
u_sess->upg_cxt
.binary_upgrade_next_part_pg_partition_oid[u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_oid])) {
return false;
}
return true;
}
static Oid binary_upgrade_get_next_part_pg_partition_oid()
{
Oid old_part_pg_partition_oid = InvalidOid;
if (false == binary_upgrade_is_next_part_pg_partition_oid_valid()) {
return InvalidOid;
}
old_part_pg_partition_oid =
u_sess->upg_cxt
.binary_upgrade_next_part_pg_partition_oid[u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_oid];
u_sess->upg_cxt
.binary_upgrade_next_part_pg_partition_oid[u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_oid] =
InvalidOid;
u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_oid++;
return old_part_pg_partition_oid;
}
static Oid binary_upgrade_get_next_part_pg_partition_rfoid()
{
Oid old_part_pg_partition_rfoid = InvalidOid;
if (NULL == u_sess->upg_cxt.binary_upgrade_next_part_pg_partition_rfoid) {
return InvalidOid;
}
if (u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_rfoid >=
u_sess->upg_cxt.binary_upgrade_max_part_pg_partition_rfoid) {
return InvalidOid;
}
old_part_pg_partition_rfoid =
u_sess->upg_cxt
.binary_upgrade_next_part_pg_partition_rfoid[u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_rfoid];
u_sess->upg_cxt
.binary_upgrade_next_part_pg_partition_rfoid[u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_rfoid] =
InvalidOid;
u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_rfoid++;
return old_part_pg_partition_rfoid;
}
bool binary_upgrade_is_next_part_toast_pg_class_oid_valid()
{
if (NULL == u_sess->upg_cxt.binary_upgrade_next_part_toast_pg_class_oid) {
return false;
}
if (u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_oid >=
u_sess->upg_cxt.binary_upgrade_max_part_toast_pg_class_oid) {
return false;
}
if (!OidIsValid(u_sess->upg_cxt.binary_upgrade_next_part_toast_pg_class_oid
[u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_oid])) {
return false;
}
return true;
}
static Oid binary_upgrade_get_next_part_toast_pg_class_oid()
{
Oid old_part_toast_pg_class_oid = InvalidOid;
if (false == binary_upgrade_is_next_part_toast_pg_class_oid_valid()) {
return InvalidOid;
}
old_part_toast_pg_class_oid =
u_sess->upg_cxt
.binary_upgrade_next_part_toast_pg_class_oid[u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_oid];
u_sess->upg_cxt
.binary_upgrade_next_part_toast_pg_class_oid[u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_oid] =
InvalidOid;
u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_oid++;
return old_part_toast_pg_class_oid;
}
static Oid binary_upgrade_get_next_part_toast_pg_class_rfoid()
{
Oid old_part_toast_pg_class_rfoid = InvalidOid;
if (NULL == u_sess->upg_cxt.binary_upgrade_next_part_toast_pg_class_rfoid) {
return InvalidOid;
}
if (u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_rfoid >=
u_sess->upg_cxt.binary_upgrade_max_part_toast_pg_class_rfoid) {
return InvalidOid;
}
old_part_toast_pg_class_rfoid = u_sess->upg_cxt.binary_upgrade_next_part_toast_pg_class_rfoid
[u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_rfoid];
u_sess->upg_cxt
.binary_upgrade_next_part_toast_pg_class_rfoid[u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_rfoid] =
InvalidOid;
u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_rfoid++;
return old_part_toast_pg_class_rfoid;
}
static int TransformClusterColNameList(Oid relId, List* colList, int16* attnums)
{
ListCell* l = NULL;
int attnum;
attnum = 0;
foreach (l, colList) {
char* attname = strVal(lfirst(l));
HeapTuple atttuple;
atttuple = SearchSysCacheAttName(relId, attname);
if (!HeapTupleIsValid(atttuple))
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" does not exist", attname)));
if (attnum >= INDEX_MAX_KEYS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("cannot have more than %d keys in a cluster key", INDEX_MAX_KEYS)));
if ((((Form_pg_attribute)GETSTRUCT(atttuple))->atttypid) == HLL_OID)
ereport(
ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("partial cluster key don't support HLL type")));
attnums[attnum] = ((Form_pg_attribute)GETSTRUCT(atttuple))->attnum;
ReleaseSysCache(atttuple);
attnum++;
}
return attnum;
}
void SetRelHasClusterKey(Relation rel, bool has)
{
Relation relrel;
HeapTuple reltup;
Form_pg_class relStruct;
relrel = heap_open(RelationRelationId, RowExclusiveLock);
reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(RelationGetRelid(rel)));
if (!HeapTupleIsValid(reltup))
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for relation %u", RelationGetRelid(rel))));
relStruct = (Form_pg_class)GETSTRUCT(reltup);
if (relStruct->relhasclusterkey != has) {
relStruct->relhasclusterkey = has;
simple_heap_update(relrel, &reltup->t_self, reltup);
CatalogUpdateIndexes(relrel, reltup);
} else {
CacheInvalidateRelcache(rel);
}
heap_freetuple(reltup);
heap_close(relrel, RowExclusiveLock);
}
* Add cluster key constraint for relation.
* return address List of constraint
*/
List* AddRelClusterConstraints(Relation rel, List* clusterKeys)
{
List *result = NIL;
if (!RelationIsColStore(rel)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("partial cluster key constraint does not support row/timeseries store")));
}
ListCell* cell = NULL;
int nKeys = list_length(clusterKeys);
foreach (cell, clusterKeys) {
Constraint* cdef = (Constraint*)lfirst(cell);
ObjectAddress * address = NULL;
Assert(cdef->contype == CONSTR_CLUSTER);
List* colNameList = cdef->keys;
int colNum = list_length(colNameList);
int16* attNums = (int16*)palloc(sizeof(int16) * colNum);
TransformClusterColNameList(rel->rd_id, colNameList, attNums);
char* conname = cdef->conname;
if (conname != NULL) {
if (ConstraintNameIsUsed(CONSTRAINT_RELATION, RelationGetRelid(rel), RelationGetNamespace(rel), conname))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("constraint \"%s\" for relation \"%s\" already exists",
conname,
RelationGetRelationName(rel))));
} else {
conname =
ChooseConstraintName(RelationGetRelationName(rel), NULL, "cluster", RelationGetNamespace(rel), NIL);
}
address = (ObjectAddress *)palloc0(sizeof(ObjectAddress));
address->classId = ConstraintRelationId;
* Create the Check Constraint
*/
address->objectId = CreateConstraintEntry(conname,
RelationGetNamespace(rel),
CONSTRAINT_CLUSTER,
false,
false,
true,
RelationGetRelid(rel),
attNums,
colNum,
colNum,
InvalidOid,
InvalidOid,
InvalidOid,
NULL,
NULL,
NULL,
NULL,
0,
' ',
' ',
' ',
NULL,
NULL,
NULL,
NULL,
true,
0,
true,
cdef->inforConstraint);
CreateNonColumnComment(address->objectId, cdef->constraintOptions, ConstraintRelationId);
pfree(attNums);
result = lappend(result, address);
}
if (nKeys > 0)
SetRelHasClusterKey(rel, true);
return result;
}
* @hdfs
* Brief : Check the constraint from pg_constraint.
* Description : Check the constraint from pg_constraint. According to ccname, judge whether
existence of the same constraint in pg_constraint for this rel or not.
* Input : ccname, constraint name to be found.
rel, target relation. Check if the rel has the same constraint named as ccname.
* Output : none.
* Return Value : If we find the same constraint in rel return true, otherwise return false.
* Notes : none.
*/
bool FindExistingConstraint(const char* ccname, Relation rel)
{
bool found = false;
Relation conDesc;
SysScanDesc conscan;
ScanKeyData skey[2];
HeapTuple tup;
conDesc = heap_open(ConstraintRelationId, ShareUpdateExclusiveLock);
ScanKeyInit(&skey[0], Anum_pg_constraint_conname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(ccname));
ScanKeyInit(&skey[1],
Anum_pg_constraint_connamespace,
BTEqualStrategyNumber,
F_OIDEQ,
ObjectIdGetDatum(RelationGetNamespace(rel)));
conscan = systable_beginscan(conDesc, ConstraintNameNspIndexId, true, NULL, 2, skey);
while (HeapTupleIsValid(tup = systable_getnext(conscan))) {
Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup);
if (0 == pg_strcasecmp(NameStr(con->conname), ccname)) {
found = true;
break;
}
}
systable_endscan(conscan);
heap_close(conDesc, ShareUpdateExclusiveLock);
return found;
}
* @Description: Build the column map. Store the column number using
* bitmap method.
* @in tuple_desc, A tuple descriptor.
* @return reutrn the column map.
*/
char* make_column_map(TupleDesc tuple_desc)
{
#define COLS_IN_BYTE 8
FormData_pg_attribute* attrs = tuple_desc->attrs;
char* col_map = (char*)palloc0((MaxHeapAttributeNumber + COLS_IN_BYTE) / COLS_IN_BYTE);
int col_cnt;
Assert(tuple_desc->natts > 0);
for (col_cnt = 0; col_cnt < tuple_desc->natts; col_cnt++) {
if (!attrs[col_cnt].attisdropped && attrs[col_cnt].attnum > 0) {
col_map[attrs[col_cnt].attnum >> 3] |= (1 << (attrs[col_cnt].attnum % COLS_IN_BYTE));
}
}
return col_map;
}
* @Description: check whether the partition keys has timestampwithzone type.
* @input: partTableRel, the partition table relation.
* @return: a bool array to indicate the result. The length of array is equal to the number of partition keys.
* @Notes: remember to pfree the array.
*/
bool* CheckPartkeyHasTimestampwithzone(Relation partTableRel, bool isForSubPartition)
{
Relation pgPartRel = NULL;
HeapTuple partitionTableTuple = NULL;
Datum partkey_raw = (Datum)0;
bool isNull = false;
int n_key_column = 0;
ArrayType* partkey_columns = NULL;
int16* attnums = NULL;
int relationAttNumber = 0;
TupleDesc relationTupleDesc = NULL;
FormData_pg_attribute* relationAtts = NULL;
pgPartRel = relation_open(PartitionRelationId, AccessShareLock);
if (isForSubPartition || RelationIsPartitionOfSubPartitionTable(partTableRel)) {
partitionTableTuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(partTableRel->rd_id));
} else {
partitionTableTuple =
searchPgPartitionByParentIdCopy(PART_OBJ_TYPE_PARTED_TABLE, ObjectIdGetDatum(partTableRel->rd_id));
}
if (NULL == partitionTableTuple) {
relation_close(pgPartRel, AccessShareLock);
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("null partition key value for relation \"%s\" in check partkey type.",
RelationGetRelationName(partTableRel))));
}
partkey_raw = heap_getattr(partitionTableTuple, Anum_pg_partition_partkey, RelationGetDescr(pgPartRel), &isNull);
if (isNull) {
relation_close(pgPartRel, AccessShareLock);
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("null partition key value for relation \"%s\" in check partkey type.",
RelationGetRelationName(partTableRel))));
}
partkey_columns = DatumGetArrayTypeP(partkey_raw);
n_key_column = ARR_DIMS(partkey_columns)[0];
if (ARR_NDIM(partkey_columns) != 1 || n_key_column < 0 || n_key_column > MAX_RANGE_PARTKEY_NUMS ||
ARR_HASNULL(partkey_columns) || ARR_ELEMTYPE(partkey_columns) != INT2OID) {
relation_close(pgPartRel, AccessShareLock);
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("partition key column's number of relation \"%s\" is not a 1-D smallint array in check partkey "
"type.",
RelationGetRelationName(partTableRel))));
}
Assert(n_key_column <= MAX_RANGE_PARTKEY_NUMS);
attnums = (int16*)ARR_DATA_PTR(partkey_columns);
relationTupleDesc = partTableRel->rd_att;
relationAttNumber = relationTupleDesc->natts;
relationAtts = relationTupleDesc->attrs;
bool* isTimestamptz = (bool*)palloc0(sizeof(bool) * n_key_column);
for (int i = 0; i < n_key_column; i++) {
int attnum = (int)(attnums[i]);
if (attnum >= 1 && attnum <= relationAttNumber) {
if (relationAtts[attnum - 1].atttypid == TIMESTAMPTZOID) {
isTimestamptz[i] = true;
}
} else {
relation_close(pgPartRel, AccessShareLock);
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("partition key column's number of %s not in the range of all its columns",
RelationGetRelationName(partTableRel))));
}
}
if (isForSubPartition || RelationIsPartitionOfSubPartitionTable(partTableRel))
ReleaseSysCache(partitionTableTuple);
else
heap_freetuple_ext(partitionTableTuple);
relation_close(pgPartRel, AccessShareLock);
return isTimestamptz;
}
bool *CheckSubPartkeyHasTimestampwithzone(Relation partTableRel, List *subpartKeyPosList)
{
Assert(RelationIsSubPartitioned(partTableRel));
int subPartKeyNum = list_length(subpartKeyPosList);
bool *isTimestamptzForSubPartKey = (bool *)palloc0(sizeof(bool) * subPartKeyNum);
ListCell *subpartKeyCell = NULL;
int partKeyIdx = 0;
foreach (subpartKeyCell, subpartKeyPosList) {
int pos = lfirst_int(subpartKeyCell);
if ((RelationGetDescr(partTableRel))->attrs[pos].atttypid == TIMESTAMPTZOID) {
isTimestamptzForSubPartKey[partKeyIdx] = true;
}
partKeyIdx++;
}
return isTimestamptzForSubPartKey;
}
* LockSeqConstraints
*
* Lock the sequence that the Constraint depend
*/
static void LockSeqConstraints(Relation rel, List* Constraints)
{
if (rel == NULL || Constraints == NULL) {
return;
}
contain_func_context context =
init_contain_func_context(list_make4_oid(NEXTVALFUNCOID, CURRVALFUNCOID, SETVAL1FUNCOID, SETVAL3FUNCOID), true);
ListCell* lc = NULL;
ListCell* fitem = NULL;
foreach (lc, Constraints) {
CookedConstraint* constr = (CookedConstraint*)lfirst(lc);
Assert(constr->contype == CONSTR_DEFAULT || constr->contype == CONSTR_CHECK);
(void)contains_specified_func(constr->expr, &context);
foreach (fitem, context.func_exprs) {
FuncExpr* func = (FuncExpr*)lfirst(fitem);
Node* node = (Node*)linitial(func->args);
if (IsA(node, Const)) {
Oid seqid = ((Const*)node)->constvalue;
LockRelationOid(seqid, AccessShareLock);
Relation seq = RelationIdGetRelation(seqid);
if (!RelationIsValid(seq) || !RelationIsSequnce(seq)) {
if (constr->contype == CONSTR_DEFAULT) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PARAMETER),
errmsg("Object in DEFAULT constraints of column \"%s\" doesn't exists",
quote_identifier((char*)attnumAttName(rel, constr->attnum)))));
} else {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PARAMETER),
errmsg("Object in CHECK constraints doesn't exists")));
}
}
RelationClose(seq);
}
}
if (context.func_exprs) {
list_free(context.func_exprs);
context.func_exprs = NIL;
}
}
list_free(context.funcids);
context.funcids = NIL;
return;
}
* Check whether a materialized view is in an initial, unloaded state.
*
* The check here must match what is set up in heap_create_init_fork().
* Currently the init fork is an empty file. A missing heap is also
* considered to be unloaded.
*/
bool
heap_is_matview_init_state(Relation rel)
{
Assert(rel->rd_rel->relkind == RELKIND_MATVIEW);
if (IS_PGXC_COORDINATOR)
return false;
RelationOpenSmgr(rel);
if (!smgrexists(rel->rd_smgr, MAIN_FORKNUM)) {
return true;
}
return (smgrnblocks(rel->rd_smgr, MAIN_FORKNUM) < 1);
}
int GetIndexKeyAttsByTuple(Relation relation, HeapTuple indexTuple)
{
bool isnull = false;
Datum indkeyDatum;
TupleDesc tupleDesc = RelationIsValid(relation) ? RelationGetDescr(relation) : GetDefaultPgIndexDesc();
Form_pg_index index = (Form_pg_index)GETSTRUCT(indexTuple);
* This scenario will only occur after the upgrade, This scenario means that
* the indnatts and Indnkeyatts of all current indexes are equal
*/
if (heap_attisnull(indexTuple, Anum_pg_index_indnkeyatts, NULL)) {
return index->indnatts;
}
indkeyDatum = fastgetattr(indexTuple, Anum_pg_index_indnkeyatts, tupleDesc, &isnull);
Assert(!isnull);
return DatumGetInt16(indkeyDatum);
}
bool GetIndexVisibleStateByTuple(HeapTuple indexTuple)
{
bool isnull = false;
Datum visibleDatum = heap_getattr(indexTuple, Anum_pg_index_indisvisible, GetDefaultPgIndexDesc(), &isnull);
return isnull || DatumGetBool(visibleDatum);
}
void AddOrDropUidsAttr(Oid relOid, bool oldRelHasUids, bool newRelHasUids)
{
if (oldRelHasUids == newRelHasUids) {
return;
}
if (newRelHasUids) {
Relation rel = heap_open(AttributeRelationId, RowExclusiveLock);
CatalogIndexState indstate = CatalogOpenIndexes(rel);
FormData_pg_attribute attStruct;
errno_t rc;
int uidAttrNum = -UidAttributeNumber;
rc = memcpy_s(&attStruct, sizeof(FormData_pg_attribute),
(char*)SysAtt[uidAttrNum - 1], sizeof(FormData_pg_attribute));
securec_check(rc, "\0", "\0");
attStruct.attrelid = relOid;
InsertPgAttributeTuple(rel, &attStruct, indstate);
CatalogCloseIndexes(indstate);
heap_close(rel, RowExclusiveLock);
InsertUidEntry(relOid);
} else {
ObjectAddress object;
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = UidAttributeNumber;
performDeletion(&object, DROP_RESTRICT, 0);
}
}
static void heap_serialize_rel_attribute(Relation att_rel, Oid rel_oid,
int att_idx, StringInfoData* concat_name, bool* depend_undefined)
{
ScanKeyData skey[2];
SysScanDesc scan;
HeapTuple tuple;
bool is_null = false;
int key_num = 0;
ScanKeyInit(&skey[key_num++], Anum_pg_attribute_attrelid, BTEqualStrategyNumber,
F_OIDEQ, ObjectIdGetDatum(rel_oid));
ScanKeyInit(&skey[key_num++], Anum_pg_attribute_attnum, BTEqualStrategyNumber,
F_INT4EQ, Int32GetDatum(att_idx));
scan = systable_beginscan(att_rel, AttributeRelidNumIndexId, true, SnapshotSelf, key_num, skey);
tuple = systable_getnext(scan);
if (!HeapTupleIsValid(tuple)) {
systable_endscan(scan);
return;
}
Datum id_dropped_datum = heap_getattr(tuple, Anum_pg_attribute_attisdropped,
RelationGetDescr(att_rel), &is_null);
if (is_null || DatumGetBool(id_dropped_datum)) {
systable_endscan(scan);
return;
}
Datum att_name_datum = heap_getattr(tuple, Anum_pg_attribute_attname,
RelationGetDescr(att_rel), &is_null);
if (!is_null) {
appendStringInfoString(concat_name, DatumGetName(att_name_datum)->data);
}
Datum typ_oid_datum = heap_getattr(tuple, Anum_pg_attribute_atttypid,
RelationGetDescr(att_rel), &is_null);
appendStringInfoString(concat_name, ":");
Oid typ_oid = DatumGetObjectId(typ_oid_datum);
if (!is_null && OidIsValid(typ_oid) && typ_oid != UNDEFINEDOID) {
(void)MakeTypeNamesStrForTypeOid(DatumGetObjectId(typ_oid_datum), depend_undefined, concat_name);
} else if (NULL != depend_undefined) {
*depend_undefined = true;
}
appendStringInfoString(concat_name, ",");
systable_endscan(scan);
}
char* heap_serialize_row_attr(Oid rel_oid, bool* depend_undefined)
{
Relation rel;
StringInfoData concat_name;
char rel_kind = get_rel_relkind(rel_oid);
if (rel_kind != RELKIND_COMPOSITE_TYPE && rel_kind != RELKIND_RELATION) {
return NULL;
}
int att_num = get_relnatts(rel_oid);
rel = heap_open(AttributeRelationId, AccessShareLock);
initStringInfo(&concat_name);
for (int i = 1; i <= att_num; i++) {
heap_serialize_rel_attribute(rel, rel_oid, i, &concat_name, depend_undefined);
}
heap_close(rel, AccessShareLock);
char* ret = pstrdup(concat_name.data);
FreeStringInfo(&concat_name);
return ret;
}
#ifdef USE_SPQ
HeapTuple heaptuple_from_pg_attribute(Relation pg_attribute_rel,
Form_pg_attribute new_attribute)
{
Datum values[Natts_pg_attribute] = { 0 };
bool nulls[Natts_pg_attribute] = { false };
values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(new_attribute->attcacheoff);
values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attribute->attbyval);
values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attribute->attstorage);
values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
values[Anum_pg_attribute_attcmprmode - 1] = Int8GetDatum(new_attribute->attcmprmode);
values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
values[Anum_pg_attribute_attkvtype - 1] = Int8GetDatum(new_attribute->attkvtype);
nulls[Anum_pg_attribute_attacl - 1] = true;
nulls[Anum_pg_attribute_attoptions - 1] = true;
nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
nulls[Anum_pg_attribute_attinitdefval - 1] = true;
nulls[Anum_pg_attribute_attdroppedname - 1] = true;
return heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
}
#endif
bool GetIndexEnableStateByTuple(HeapTuple indexTuple)
{
bool isnull = false;
Datum enableDatum = heap_getattr(indexTuple, Anum_pg_index_indisenable, GetDefaultPgIndexDesc(), &isnull);
return isnull || DatumGetBool(enableDatum);
}
bool relationHasDisableIndex(Relation relation) {
auto indexoidlist = RelationGetIndexList(relation);
foreach_cell(l, indexoidlist) {
auto indexOid = lfirst_oid(l);
auto indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid));
if (!HeapTupleIsValid(indexTuple))
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("could not find tuple for amop entry %u", indexOid)));
auto enableState = GetIndexEnableStateByTuple(indexTuple);
ReleaseSysCache(indexTuple);
if (!enableState) {
list_free(indexoidlist);
return true;
}
}
list_free(indexoidlist);
return false;
}
bool resultRelationsHasDisableIndex(PlannedStmt *plannedstmt) {
if (nullptr == plannedstmt->resultRelations) {
return false;
}
ListCell *lc = NULL;
foreach (lc, (List*)linitial(plannedstmt->resultRelations)) {
Index idx = lfirst_int(lc);
auto rid = getrelid(idx, plannedstmt->rtable);
if (OidIsValid(rid) == false || rid < FirstNormalObjectId) {
continue;
}
auto rel = heap_open(rid, AccessShareLock);
if (relationHasDisableIndex(rel)) {
heap_close(rel, NoLock);
return true;
}
heap_close(rel, NoLock);
}
return false;
}
void CheckWriteCommandWithDisableIndex(PlannedStmt *plannedstmt) {
if (!CommandIsReadOnly(plannedstmt) && resultRelationsHasDisableIndex(plannedstmt)) {
ereport(ERROR, (errcode(ERRCODE_OPERATOR_INTERVENTION),
errmsg("The relation has no permit to write because it has index in disable state")));
}
}