blob: e159c838d8ac815ba2a6e38db00781dd02d1c2ea [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*-------------------------------------------------------------------------
*
* heap.c
* code to create and destroy POSTGRES heap relations
*
* Portions Copyright (c) 2005-2010, Greenplum inc
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.314 2006/11/05 22:42:08 tgl Exp $
*
*
* 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 "miscadmin.h"
#include "port.h"
#include "access/fileam.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/sysattr.h"
#include "access/transam.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/catquery.h"
#include "catalog/dependency.h"
#include "catalog/gp_policy.h"
#include "catalog/gp_persistent.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_appendonly.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_attribute_encoding.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_exttable.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_rule.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "cdb/cdbanalyze.h"
#include "cdb/cdbappendonlyam.h"
#include "cdb/cdbmirroredfilesysobj.h"
#include "cdb/cdbparquetfooterprocessor.h"
#include "cdb/cdbparquetstoragewrite.h"
#include "cdb/cdbpartition.h"
#include "cdb/cdbpersistentfilesysobj.h"
#include "cdb/cdbsharedstorageop.h"
#include "cdb/cdbvars.h"
#include "commands/dbcommands.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "nodes/makefuncs.h"
#include "nodes/pg_list.h"
#include "nodes/value.h"
#include "optimizer/clauses.h"
#include "optimizer/var.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "pg_config_manual.h"
#include "storage/fd.h"
#include "storage/smgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h" /* CDB: GetMemoryChunkContext */
#include "utils/palloc.h"
#include "utils/relcache.h"
#include "utils/syscache.h"
#include "utils/uri.h"
typedef struct pg_result PGresult;
extern void PQclear(PGresult *res);
static void MetaTrackAddUpdInternal(cqContext *pcqCtx,
Oid classid,
Oid objoid,
Oid relowner,
char* actionname,
char* subtype,
Relation rel,
HeapTuple old_tuple);
static void AddNewRelationTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid, Oid new_type_oid,
Oid relowner,
char relkind,
char relstorage,
Datum reloptions);
static Oid AddNewRelationType(const char *typeName,
Oid typeNamespace,
Oid new_rel_oid,
char new_rel_kind,
Oid ownerid);
static void RelationRemoveInheritance(Oid relid);
static Oid StoreRelCheck(Relation rel, char *ccname, char *ccbin, Oid conoid);
static Node* cookConstraint (ParseState *pstate,
Node *raw_constraint,
char *relname);
static void StoreConstraints(Relation rel, TupleDesc tupdesc);
static List *insert_ordered_unique_oid(List *list, Oid datum);
/* ----------------------------------------------------------------
* 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.
*/
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,
true, 'p', 'i', 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,
true, 'p', 'i', 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
};
/*CDB*/
static FormData_pg_attribute a8 = {
0, {"gp_segment_id"}, INT4OID, 0, sizeof(gpsegmentId),
GpSegmentIdAttributeNumber, 0, -1, -1,
true, 'p', 'i', true, false, false, true, 0
};
static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8};
/*
* 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)
{
if (attno >= 0 || attno < -(int) lengthof(SysAtt))
elog(ERROR, "invalid system attribute number %d", attno);
if (attno == ObjectIdAttributeNumber && !relhasoids)
elog(ERROR, "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;
}
/* ----------------------------------------------------------------
* 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.
*
* 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,
TupleDesc tupDesc,
Oid relam,
char relkind,
char relstorage,
bool shared_relation,
bool allow_system_table_mods,
bool bufferPoolBulkLoad)
{
bool create_storage;
Relation rel;
bool isAppendOnly = (relstorage == RELSTORAGE_AOROWS || relstorage == RELSTORAGE_PARQUET);
bool skipCreatingSharedTable = false;
/* The caller must have provided an OID for the relation. */
Assert(OidIsValid(relid));
/*
* sanity checks
*/
if (!allow_system_table_mods &&
(IsSystemNamespace(relnamespace) || IsToastNamespace(relnamespace) ||
IsAoSegmentNamespace(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."),
errOmitLocation(true)));
/*
* Decide if we need storage or not, and handle a couple other special
* cases for particular relkinds.
*/
switch (relkind)
{
case RELKIND_VIEW:
case RELKIND_COMPOSITE_TYPE:
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:
create_storage = true;
/*
* Force reltablespace to zero for sequences, since we don't
* support moving them around into different tablespaces.
*/
reltablespace = InvalidOid;
break;
default:
if(relstorage_is_external(relstorage))
create_storage = false;
else
create_storage = true;
break;
}
/*
* 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.
*/
if (reltablespace == MyDatabaseTableSpace || reltablespace == get_database_dts(MyDatabaseId))
reltablespace = InvalidOid;
/*
* build the relcache entry.
*/
rel = RelationBuildLocalRelation(relname,
relnamespace,
tupDesc,
relid,
reltablespace,
relkind, /*CDB*/
relstorage,
shared_relation);
if (!create_storage)
return rel;
/*
* We save the persistent TID and serial number in pg_class so we
* can supply them to the Storage Manager if the object is subsequently
* dropped.
*
* For shared table (that we created during upgrade), we create it once in every
* database, but they will all point to the same file. So, the file might have already
* been created.
* So, if we're in upgrade and we're creating shared table, we scan for existences.
* (We don't need to scan when we're creating shared table during initdb.)
* If it exists, we reuse the Tid and SerialNum and mark the isPresent to true.
*
* Note that we have not tried creating shared AO table.
*
* For non-shared table, we should always need to create a file.
*/
// WARNING: Do not use the rel structure -- it doesn't have relstorage set...
if (shared_relation && gp_upgrade_mode)
{
if(GpPersistent_IsPersistentRelation(rel->rd_node.relNode))
{
// persistent systable is created with gp_before_persistence_work set to true,
// which means all persistent systables aren't initiated at this time, so we
// can't all API related to persistent table, here directly check file exists on disk
char *rpath;
int fd;
rpath = relpath(rel->rd_node);
fd = PathNameOpenFile(rpath, O_RDONLY | PG_BINARY, 0);
if (fd >= 0)
{
FileClose(fd);
skipCreatingSharedTable = true;
}
pfree(rpath);
}
else
{
skipCreatingSharedTable = PersistentFileSysObj_ScanForRelation(
&rel->rd_node,
/* segmentFileNum */ 0,
&rel->rd_relationnodeinfo.persistentTid,
&rel->rd_relationnodeinfo.persistentSerialNum);
}
if (Debug_persistent_print && skipCreatingSharedTable)
elog(Persistent_DebugPrintLevel(),
"heap_create: file for shared relation '%s' already exists", relname);
}
if (skipCreatingSharedTable)
return rel;
if (!isAppendOnly)
{
PersistentFileSysRelStorageMgr localRelStorageMgr;
PersistentFileSysRelBufpoolKind relBufpoolKind;
GpPersistentRelfileNode_GetRelfileInfo(
relkind,
relstorage,
relam,
&localRelStorageMgr,
&relBufpoolKind);
Assert(localRelStorageMgr == PersistentFileSysRelStorageMgr_BufferPool);
Assert(rel->rd_smgr == NULL);
RelationOpenSmgr(rel);
MirroredFileSysObj_TransactionCreateBufferPoolFile(
rel->rd_smgr,
relBufpoolKind,
rel->rd_isLocalBuf,
rel->rd_rel->relname.data,
/* doJustInTimeDirCreate */true, bufferPoolBulkLoad,
&rel->rd_relationnodeinfo.persistentTid,
&rel->rd_relationnodeinfo.persistentSerialNum);
rel->rd_relationnodeinfo.isPresent = true;
}
else
{
Assert(Gp_role == GP_ROLE_DISPATCH);
TransactionCreateDatabaseDir(rel->rd_node.dbNode, rel->rd_node.spcNode);
MirroredFileSysObj_TransactionCreateRelationDir(
&rel->rd_node,
false,
&rel->rd_relationnodeinfo.persistentTid,
&rel->rd_relationnodeinfo.persistentSerialNum);
rel->rd_relationnodeinfo.isPresent = true;
#if 0
SharedStorageOpTasks *tasks = CreateSharedStorageOpTasks();
int i;
for (i = 0 ; i < rel->rd_segfile0_count; ++i)
{
int32 id = i - 1;
SharedStorageOpPreAddTask(&rel->rd_node, 0, id, relname,
&rel->rd_segfile0_relationnodeinfos[i].persistentTid,
&rel->rd_segfile0_relationnodeinfos[i].persistentSerialNum);
rel->rd_segfile0_relationnodeinfos[i].isPresent = TRUE;
SharedStorageOpAddTask(relname, &rel->rd_node, 0, id,
&rel->rd_segfile0_relationnodeinfos[i].persistentTid,
rel->rd_segfile0_relationnodeinfos[i].persistentSerialNum,
tasks);
}
PerformSharedStorageOpTasks(tasks, Op_CreateSegFile);
PostPerformSharedStorageOpTasks(tasks);
DropSharedStorageOpTasks(tasks);
int i;
Relation gp_relation_node;
//rel->rd_segfile0_count;
for (i = 0 ; i < 1; ++i)
{
int error;
smgrcreatepending(&rel->rd_node,
0,
PersistentFileSysRelStorageMgr_AppendOnly,
PersistentFileSysRelBufpoolKind_None,
(char *) relname,
&rel->rd_segfile0_relationnodeinfo.persistentTid,
&rel->rd_segfile0_relationnodeinfo.persistentSerialNum,
/*isLocalBuf*/ false,
/*bufferPoolBulkLoad*/ false,
/*flushToXLog*/ true);
rel->rd_segfile0_relationnodeinfo.isPresent = true;
MirroredAppendOnly_Create(&rel->rd_node, 0,
(char *) relname, &error);
if (error != 0)
{
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create relation file '%s', relation name '%s', contentid: %d: %s",
relpath(rel->rd_node),
relname, i - 1,
strerror(error))));
}
gp_relation_node = heap_open(GpRelfileNodeRelationId, RowExclusiveLock);
InsertGpRelationNodeTuple(
gp_relation_node,
/* relationId */ 0,
(char *) relname,
rel->rd_node.relNode,
0,
/* updateIndex */ true,
&rel->rd_segfile0_relationnodeinfo.persistentTid,
rel->rd_segfile0_relationnodeinfo.persistentSerialNum);
heap_close(gp_relation_node, RowExclusiveLock);
}
#endif
}
if (Debug_check_for_invalid_persistent_tid &&
!Persistent_BeforePersistenceWork() &&
PersistentStore_IsZeroTid(&rel->rd_relationnodeinfo.persistentTid))
{
elog(ERROR,
"setNewRelfilenodeCommon has invalid TID (0,0) into relation %u/%u/%u '%s', serial number " INT64_FORMAT,
rel->rd_node.spcNode,
rel->rd_node.dbNode,
rel->rd_node.relNode,
NameStr(rel->rd_rel->relname),
rel->rd_relationnodeinfo.persistentSerialNum);
}
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"heap_create: '%s', Append-Only '%s', persistent TID %s and serial number " INT64_FORMAT " for CREATE",
relpath(rel->rd_node),
(isAppendOnly ? "true" : "false"),
ItemPointerToString(&rel->rd_relationnodeinfo.persistentTid),
rel->rd_relationnodeinfo.persistentSerialNum);
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)
{
int i;
int j;
int natts = tupdesc->natts;
/* Sanity check on column count */
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)
{
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\" is duplicated",
NameStr(tupdesc->attrs[j]->attname)),
errOmitLocation(true)));
}
}
/*
* next check the attribute types
*/
for (i = 0; i < natts; i++)
{
CheckAttributeType(NameStr(tupdesc->attrs[i]->attname),
tupdesc->attrs[i]->atttypid);
}
}
/* --------------------------------
* CheckAttributeType
*
* Verify that the proposed datatype of an attribute is legal.
* This is needed because there are types (and pseudo-types)
* in the catalogs that we do not support as elements of real tuples.
* --------------------------------
*/
void
CheckAttributeType(const char *attname, Oid atttypid)
{
char att_typtype = get_typtype(atttypid);
/*
* Warn user, but don't fail, if column to be created has UNKNOWN type
* (usually as a result of a 'retrieve into' - jolly)
*
* Refuse any attempt to create a pseudo-type column.
*/
if (Gp_role != GP_ROLE_EXECUTE)
{
if (atttypid == UNKNOWNOID)
ereport(WARNING,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" has type \"unknown\"", attname),
errdetail("Proceeding with relation creation anyway."),
errOmitLocation(true)));
else if (att_typtype == 'p')
{
/* Special hack for pg_statistic: allow ANYARRAY during initdb */
if (atttypid != ANYARRAYOID || IsUnderPostmaster)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" has pseudo-type %s",
attname, format_type_be(atttypid)),
errOmitLocation(true)));
}
}
}
/* --------------------------------
* CheckAttributeArray
*
* Verify that whether the attribute is of type array,
* parquet table doesn't support array type
* --------------------------------
*/
void CheckAttributeForParquet(TupleDesc tupdesc, char relstorage){
if(relstorage != RELSTORAGE_PARQUET){
return;
}
int natts = tupdesc->natts;
for(int i = 0; i < natts; i++){
switch(tupdesc->attrs[i]->atttypid){
case HAWQ_TYPE_BOOL:
case HAWQ_TYPE_CHAR:
case HAWQ_TYPE_NAME:
case HAWQ_TYPE_INT8:
case HAWQ_TYPE_INT2:
case HAWQ_TYPE_INT4:
case HAWQ_TYPE_FLOAT4:
case HAWQ_TYPE_FLOAT8:
case HAWQ_TYPE_MONEY:
case HAWQ_TYPE_NUMERIC:
case HAWQ_TYPE_BYTE:
case HAWQ_TYPE_TEXT:
case HAWQ_TYPE_XML:
case HAWQ_TYPE_MACADDR:
case HAWQ_TYPE_INET:
case HAWQ_TYPE_CIDR:
case HAWQ_TYPE_BPCHAR:
case HAWQ_TYPE_VARCHAR:
case HAWQ_TYPE_DATE:
case HAWQ_TYPE_TIME:
case HAWQ_TYPE_TIMESTAMP:
case HAWQ_TYPE_TIMETZ:
case HAWQ_TYPE_TIMESTAMPTZ:
case HAWQ_TYPE_INTERVAL:
case HAWQ_TYPE_BIT:
case HAWQ_TYPE_VARBIT:
case HAWQ_TYPE_POINT:
case HAWQ_TYPE_LSEG:
case HAWQ_TYPE_PATH:
case HAWQ_TYPE_BOX:
case HAWQ_TYPE_POLYGON:
case HAWQ_TYPE_CIRCLE:
break;
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
errmsg("data type for column %s is not supported for parquet table yet",
NameStr(tupdesc->attrs[i]->attname)),
errOmitLocation(true)));
}
if(tupdesc->attrs[i]->attndims != 0){
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("array type of column \"%s\" is not supported for parquet table yet",
NameStr(tupdesc->attrs[i]->attname))));
}
}
}
/* MPP-6929: metadata tracking */
/* --------------------------------
* MetaTrackAddObject
*
* Track creation of object in pg_stat_last_operation. The
* arguments are:
*
* classid - the oid of the table containing the object, eg
* "pg_class" for a relation
* objoid - the oid of the object itself in the specified table
* relowner - role ? user ?
* actionname - generally CREATE for this case
* subtype - some generic descriptive, eg TABLE for a "CREATE TABLE"
*
*
* --------------------------------
*/
static void MetaTrackAddUpdInternal(cqContext *pcqCtx,
Oid classid,
Oid objoid,
Oid relowner,
char* actionname,
char* subtype,
Relation rel,
HeapTuple old_tuple)
{
HeapTuple new_tuple;
Datum values[Natts_pg_statlastop];
bool isnull[Natts_pg_statlastop];
bool new_record_repl[Natts_pg_statlastop];
NameData uname;
NameData aname;
MemSet(isnull, 0, sizeof(bool) * Natts_pg_statlastop);
MemSet(new_record_repl, 0, sizeof(bool) * Natts_pg_statlastop);
values[Anum_pg_statlastop_classid - 1] = ObjectIdGetDatum(classid);
values[Anum_pg_statlastop_objid - 1] = ObjectIdGetDatum(objoid);
aname.data[0] = '\0';
namestrcpy(&aname, actionname);
values[Anum_pg_statlastop_staactionname - 1] = NameGetDatum(&aname);
values[Anum_pg_statlastop_stasysid - 1] = ObjectIdGetDatum(relowner);
/* set this column to update */
new_record_repl[Anum_pg_statlastop_stasysid - 1] = true;
uname.data[0] = '\0';
{
char *rolnamestr;
int fetchCount;
rolnamestr = caql_getcstring_plus(
NULL,
&fetchCount,
NULL,
cql("SELECT rolname FROM pg_authid "
" WHERE oid = :1 ",
ObjectIdGetDatum(relowner)));
if (fetchCount)
{
namestrcpy(&uname, rolnamestr);
pfree(rolnamestr);
}
else
{
/* Generate numeric OID if we don't find an entry */
sprintf(NameStr(uname), "%u", relowner);
}
}
values[Anum_pg_statlastop_stausename - 1] = NameGetDatum(&uname);
/* set this column to update */
new_record_repl[Anum_pg_statlastop_stausename - 1] = true;
values[Anum_pg_statlastop_stasubtype - 1] = CStringGetTextDatum(subtype);
/* set this column to update */
new_record_repl[Anum_pg_statlastop_stasubtype - 1] = true;
values[Anum_pg_statlastop_statime - 1] = GetCurrentTimestamp();
/* set this column to update */
new_record_repl[Anum_pg_statlastop_statime - 1] = true;
if (HeapTupleIsValid(old_tuple))
{
new_tuple = caql_modify_current(pcqCtx,
values,
isnull, new_record_repl);
caql_update_current(pcqCtx, new_tuple);
/* and Update indexes (implicit) */
}
else
{
new_tuple = caql_form_tuple(pcqCtx, values, isnull);
(void) caql_insert(pcqCtx, new_tuple);
/* and Update indexes (implicit) */
}
if (HeapTupleIsValid(old_tuple))
heap_freetuple(new_tuple);
} /* end MetaTrackAddUpdInternal */
void MetaTrackAddObject(Oid classid,
Oid objoid,
Oid relowner,
char* actionname,
char* subtype)
{
Relation rel;
cqContext cqc;
cqContext *pcqCtx;
if (IsBootstrapProcessingMode())
return;
if (IsSharedRelation(classid))
{
rel = heap_open(StatLastShOpRelationId, RowExclusiveLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), rel),
cql("INSERT INTO pg_stat_last_shoperation ",
NULL));
}
else
{
rel = heap_open(StatLastOpRelationId, RowExclusiveLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), rel),
cql("INSERT INTO pg_stat_last_operation ",
NULL));
}
MetaTrackAddUpdInternal(pcqCtx,
classid, objoid, relowner,
actionname, subtype,
rel, (HeapTuple) InvalidOid);
caql_endscan(pcqCtx);
heap_close(rel, RowExclusiveLock);
/* CommandCounterIncrement(); */
} /* end MetaTrackAddObject */
void MetaTrackUpdObject(Oid classid,
Oid objoid,
Oid relowner,
char* actionname,
char* subtype)
{
HeapTuple tuple;
cqContext *pcqCtx;
cqContext cqc;
Relation rel;
int ii = 0;
if (IsBootstrapProcessingMode())
return;
if (IsSharedRelation(classid))
rel = heap_open(StatLastShOpRelationId, RowExclusiveLock);
else
rel = heap_open(StatLastOpRelationId, RowExclusiveLock);
if (IsSharedRelation(classid))
{
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), rel),
cql("SELECT * FROM pg_stat_last_shoperation "
" WHERE classid = :1 "
" AND objid = :2 "
" AND staactionname = :3 "
" FOR UPDATE ",
ObjectIdGetDatum(classid),
ObjectIdGetDatum(objoid),
CStringGetDatum(actionname)));
}
else
{
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), rel),
cql("SELECT * FROM pg_stat_last_operation "
" WHERE classid = :1 "
" AND objid = :2 "
" AND staactionname = :3 "
" FOR UPDATE ",
ObjectIdGetDatum(classid),
ObjectIdGetDatum(objoid),
CStringGetDatum(actionname)));
}
/* should be a unique index - only 1 answer... */
while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx)))
{
if (HeapTupleIsValid(tuple))
{
MetaTrackAddUpdInternal(pcqCtx,
classid, objoid, relowner,
actionname, subtype,
rel, tuple);
/* CommandCounterIncrement(); */
ii++;
}
}
caql_endscan(pcqCtx);
heap_close(rel, RowExclusiveLock);
/* add it if it didn't already exist */
if (!ii)
MetaTrackAddObject(classid,
objoid,
relowner,
actionname,
subtype);
} /* end MetaTrackUpdObject */
void MetaTrackDropObject(Oid classid,
Oid objoid)
{
int ii = 0;
if (IsSharedRelation(classid))
{
ii = caql_getcount(
NULL,
cql("DELETE FROM pg_stat_last_shoperation "
" WHERE classid = :1 "
" AND objid = :2 ",
ObjectIdGetDatum(classid),
ObjectIdGetDatum(objoid)));
}
else
{
ii = caql_getcount(
NULL,
cql("DELETE FROM pg_stat_last_operation "
" WHERE classid = :1 "
" AND objid = :2 ",
ObjectIdGetDatum(classid),
ObjectIdGetDatum(objoid)));
}
} /* end MetaTrackDropObject */
/* --------------------------------
* 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)
{
const Form_pg_attribute *dpp;
int i;
HeapTuple tup;
int natts = tupdesc->natts;
ObjectAddress myself,
referenced;
cqContext *pcqCtx;
/*
* open pg_attribute and its indexes.
*/
pcqCtx = caql_beginscan(
NULL,
cql("INSERT INTO pg_attribute ",
NULL));
/*
* First we add the user attributes. This is also a convenient place to
* add dependencies on their datatypes.
*/
dpp = tupdesc->attrs;
for (i = 0; i < natts; i++)
{
/* Fill in the correct relation OID */
(*dpp)->attrelid = new_rel_oid;
/* Make sure these are OK, too */
(*dpp)->attstattarget = -1;
(*dpp)->attcacheoff = -1;
tup = heap_addheader(Natts_pg_attribute,
false,
ATTRIBUTE_TUPLE_SIZE,
(void *) *dpp);
caql_insert(pcqCtx, tup);
/* and Update indexes (implicit) */
heap_freetuple(tup);
myself.classId = RelationRelationId;
myself.objectId = new_rel_oid;
myself.objectSubId = i + 1;
referenced.classId = TypeRelationId;
referenced.objectId = (*dpp)->atttypid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
dpp++;
}
/*
* 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)
{
dpp = SysAtt;
for (i = 0; i < -1 - FirstLowInvalidHeapAttributeNumber; i++)
{
if (tupdesc->tdhasoid ||
(*dpp)->attnum != ObjectIdAttributeNumber)
{
Form_pg_attribute attStruct;
tup = heap_addheader(Natts_pg_attribute,
false,
ATTRIBUTE_TUPLE_SIZE,
(void *) *dpp);
attStruct = (Form_pg_attribute) GETSTRUCT(tup);
/* Fill in the correct relation OID in the copied tuple */
attStruct->attrelid = new_rel_oid;
/* Fill in correct inheritance info for the OID column */
if (attStruct->attnum == ObjectIdAttributeNumber)
{
attStruct->attislocal = oidislocal;
attStruct->attinhcount = oidinhcount;
}
/*
* Unneeded since they should be OK in the constant data
* anyway
*/
/* attStruct->attstattarget = 0; */
/* attStruct->attcacheoff = -1; */
caql_insert(pcqCtx, tup);
/* and Update indexes (implicit) */
heap_freetuple(tup);
}
dpp++;
}
}
/*
* clean up
*/
caql_endscan(pcqCtx); /* close rel, indexes */
}
/* --------------------------------
* 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.
* We always initialize relacl to NULL (i.e., default permissions),
* and reloptions is set to the passed-in text array (if any).
* --------------------------------
*/
void
InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum reloptions)
{
Form_pg_class rd_rel = new_rel_desc->rd_rel;
Datum values[Natts_pg_class];
bool nulls[Natts_pg_class];
HeapTuple tup;
cqContext cqc;
cqContext *pcqCtx;
/* This is a tad tedious, but way cleaner than what we used to do... */
memset(values, 0, sizeof(values));
memset(nulls, false, sizeof(nulls));
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_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] = Int32GetDatum(rd_rel->relpages);
values[Anum_pg_class_reltuples - 1] = Float4GetDatum(rd_rel->reltuples);
values[Anum_pg_class_reltoastrelid - 1] = ObjectIdGetDatum(rd_rel->reltoastrelid);
values[Anum_pg_class_reltoastidxid - 1] = ObjectIdGetDatum(rd_rel->reltoastidxid);
values[Anum_pg_class_relaosegrelid - 1] = ObjectIdGetDatum(rd_rel->relaosegrelid);
values[Anum_pg_class_relaosegidxid - 1] = ObjectIdGetDatum(rd_rel->relaosegidxid);
values[Anum_pg_class_relhasindex - 1] = BoolGetDatum(rd_rel->relhasindex);
values[Anum_pg_class_relisshared - 1] = BoolGetDatum(rd_rel->relisshared);
values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
values[Anum_pg_class_relstorage - 1] = CharGetDatum(rd_rel->relstorage);
values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
values[Anum_pg_class_reltriggers - 1] = Int16GetDatum(rd_rel->reltriggers);
values[Anum_pg_class_relukeys - 1] = Int16GetDatum(rd_rel->relukeys);
values[Anum_pg_class_relfkeys - 1] = Int16GetDatum(rd_rel->relfkeys);
values[Anum_pg_class_relrefs - 1] = Int16GetDatum(rd_rel->relrefs);
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_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
/* start out with empty permissions */
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;
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), pg_class_desc),
cql("INSERT INTO pg_class ",
NULL));
tup = caql_form_tuple(pcqCtx, 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);
/* finally insert the new tuple, update the indexes, and clean up */
caql_insert(pcqCtx, tup);
/* and Update indexes (implicit) */
caql_endscan(pcqCtx);
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 relowner,
char relkind,
char relstorage,
Datum reloptions)
{
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_INDEX:
case RELKIND_TOASTVALUE:
case RELKIND_AOSEGMENTS:
case RELKIND_AOBLOCKDIR:
/* The relation is real, but as yet empty */
new_rel_reltup->relpages = 0;
new_rel_reltup->reltuples = 0;
/* estimated stats for external tables */
/* NOTE: look at cdb_estimate_rel_size() if changing these values */
if(relstorage_is_external(relstorage))
{
new_rel_reltup->relpages = gp_external_table_default_number_of_pages;
new_rel_reltup->reltuples = gp_external_table_default_number_of_tuples;
}
break;
case RELKIND_SEQUENCE:
/* Sequences always have a known size */
new_rel_reltup->relpages = 1;
new_rel_reltup->reltuples = 1;
break;
default:
/* Views, etc, have no disk storage */
new_rel_reltup->relpages = 0;
new_rel_reltup->reltuples = 0;
break;
}
/* Initialize relfrozenxid */
if (relkind == RELKIND_RELATION ||
relkind == RELKIND_TOASTVALUE ||
relkind == RELKIND_AOSEGMENTS ||
relkind == RELKIND_AOBLOCKDIR)
{
/*
* 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.
*/
if (!IsBootstrapProcessingMode())
new_rel_reltup->relfrozenxid = RecentXmin;
else
new_rel_reltup->relfrozenxid = FirstNormalTransactionId;
}
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 = InvalidTransactionId;
}
new_rel_reltup->relowner = relowner;
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->relkind = relkind;
new_rel_reltup->relstorage = relstorage;
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid, reloptions);
}
/* --------------------------------
* AddNewRelationType -
*
* define a composite type corresponding to the new relation
* --------------------------------
*/
static Oid
AddNewRelationType(const char *typeName,
Oid typeNamespace,
Oid new_rel_oid,
char new_rel_kind,
Oid ownerid)
{
return
TypeCreate(typeName, /* type name */
typeNamespace, /* type namespace */
new_rel_oid, /* relation oid */
new_rel_kind, /* relation kind */
ownerid, /* owner's ID */
-1, /* internal size (varlena) */
'c', /* type-type (complex) */
',', /* default array delimiter */
F_RECORD_IN, /* input procedure */
F_RECORD_OUT, /* output procedure */
F_RECORD_RECV, /* receive procedure */
F_RECORD_SEND, /* send procedure */
InvalidOid, /* analyze procedure - default */
InvalidOid, /* array element type - irrelevant */
InvalidOid, /* domain base type - irrelevant */
NULL, /* default value - none */
NULL, /* default binary representation */
false, /* passed by reference */
'd', /* alignment - must be the largest! */
'x', /* fully TOASTable */
-1, /* typmod */
0, /* array dimensions for typBaseType */
false); /* Type NOT NULL */
}
void
InsertGpRelfileNodeTuple(
Relation gp_relfile_node,
Oid relationId,
char *relname,
Oid relfilenode,
int32 segmentFileNum,
bool updateIndex,
ItemPointer persistentTid,
int64 persistentSerialNum)
{
Datum values[Natts_gp_relfile_node];
bool nulls[Natts_gp_relfile_node];
HeapTuple tuple;
// cqContext cqc;
// cqContext *pcqCtx;
if (Debug_check_for_invalid_persistent_tid &&
!Persistent_BeforePersistenceWork() &&
PersistentStore_IsZeroTid(persistentTid))
{
elog(ERROR,
"Inserting with invalid TID (0,0) into relation id %u '%s', relfilenode %u, segment file #%d, serial number " INT64_FORMAT,
relationId,
relname,
relfilenode,
segmentFileNum,
persistentSerialNum);
}
memset(values, 0, sizeof(values));
memset(nulls, false, sizeof(nulls));
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"InsertGpRelationNodeTuple: Inserting into relation id %u '%s', relfilenode %u, segment file #%d, serial number " INT64_FORMAT ", TID %s",
relationId,
relname,
relfilenode,
segmentFileNum,
persistentSerialNum,
ItemPointerToString(persistentTid));
// pcqCtx = caql_beginscan(
// caql_addrel(cqclr(&cqc), pg_class_desc),
// cqlXXX("INSERT INTO gp_relation_node ",
// NULL));
GpRelfileNode_SetDatumValues(
values,
relfilenode,
segmentFileNum,
persistentTid,
persistentSerialNum);
// tuple = caql_form_tuple(pcqCtx, values, nulls);
/* XXX XXX: note optional index update */
tuple = heap_form_tuple(RelationGetDescr(gp_relfile_node), values, nulls);
/* finally insert the new tuple, update the indexes, and clean up */
simple_heap_insert(gp_relfile_node, tuple);
if (updateIndex)
{
CatalogUpdateIndexes(gp_relfile_node, tuple);
}
// caql_endscan(pcqCtx);
heap_freetuple(tuple);
}
void
UpdateGpRelfileNodeTuple(
Relation gp_relfile_node,
HeapTuple tuple,
Oid relfilenode,
int32 segmentFileNum,
ItemPointer persistentTid,
int64 persistentSerialNum)
{
Datum repl_val[Natts_gp_relfile_node];
bool repl_null[Natts_gp_relfile_node];
bool repl_repl[Natts_gp_relfile_node];
HeapTuple newtuple;
if (Debug_check_for_invalid_persistent_tid &&
!Persistent_BeforePersistenceWork() &&
PersistentStore_IsZeroTid(persistentTid))
{
elog(ERROR,
"Updating with invalid TID (0,0) in relfilenode %u, segment file #%d, serial number " INT64_FORMAT,
relfilenode,
segmentFileNum,
persistentSerialNum);
}
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"UpdateGpRelationNodeTuple: Updating relfilenode %u, segment file #%d, serial number " INT64_FORMAT " at TID %s",
relfilenode,
segmentFileNum,
persistentSerialNum,
ItemPointerToString(persistentTid));
memset(repl_val, 0, sizeof(repl_val));
memset(repl_null, false, sizeof(repl_null));
memset(repl_repl, false, sizeof(repl_null));
repl_repl[Anum_gp_relfile_node_relfilenode_oid - 1] = true;
repl_val[Anum_gp_relfile_node_relfilenode_oid - 1] = ObjectIdGetDatum(relfilenode);
repl_repl[Anum_gp_relfile_node_segment_file_num - 1] = true;
repl_val[Anum_gp_relfile_node_segment_file_num - 1] = Int32GetDatum(segmentFileNum);
// UNDONE: createMirrorDataLossTrackingSessionNum
repl_repl[Anum_gp_relfile_node_persistent_tid- 1] = true;
repl_val[Anum_gp_relfile_node_persistent_tid- 1] = PointerGetDatum(persistentTid);
repl_repl[Anum_gp_relfile_node_persistent_serial_num - 1] = true;
repl_val[Anum_gp_relfile_node_persistent_serial_num - 1] = Int64GetDatum(persistentSerialNum);
newtuple = heap_modify_tuple(tuple, RelationGetDescr(gp_relfile_node), repl_val, repl_null, repl_repl);
simple_heap_update(gp_relfile_node, &newtuple->t_self, newtuple);
CatalogUpdateIndexes(gp_relfile_node, newtuple);
heap_freetuple(newtuple);
}
static void
AddNewRelfileNodeTuple(
Relation gp_relfile_node,
Relation new_rel)
{
if (new_rel->rd_relationnodeinfo.isPresent)
{
InsertGpRelfileNodeTuple(
gp_relfile_node,
new_rel->rd_id,
new_rel->rd_rel->relname.data,
new_rel->rd_rel->relfilenode,
/* segmentFileNum */ 0,
/* updateIndex */ true,
&new_rel->rd_relationnodeinfo.persistentTid,
new_rel->rd_relationnodeinfo.persistentSerialNum);
}
}
/* --------------------------------
* heap_create_with_catalog
*
* creates a new cataloged relation. see comments above.
* --------------------------------
*/
Oid
heap_create_with_catalog(const char *relname,
Oid relnamespace,
Oid reltablespace,
Oid relid,
Oid ownerid,
TupleDesc tupdesc,
Oid relam,
char relkind,
char relstorage,
bool shared_relation,
bool oidislocal,
bool bufferPoolBulkLoad,
int oidinhcount,
OnCommitAction oncommit,
const struct GpPolicy *policy,
Datum reloptions,
bool allow_system_table_mods,
Oid *comptypeOid,
ItemPointer persistentTid,
int64 *persistentSerialNum)
{
Relation pg_class_desc;
Relation gp_relfile_node_desc;
Relation new_rel_desc;
Oid new_type_oid;
bool appendOnlyRel;
StdRdOptions *stdRdOptions;
int safefswritesize = gp_safefswritesize;
bool override = false;
pg_class_desc = heap_open(RelationRelationId, RowExclusiveLock);
// When creating gp_persistent_relfile_node, we can't directly insert meta info into gp_relfile_node
// for this table is renamed from gp_relation_node, also it's schema changed.
if (IsBootstrapProcessingMode()|| (gp_upgrade_mode && GpPersistent_IsPersistentRelation(relid)))
gp_relfile_node_desc = NULL;
else
gp_relfile_node_desc = heap_open(GpRelfileNodeRelationId, RowExclusiveLock);
/*
* sanity checks
*/
Assert(IsNormalProcessingMode() || IsBootstrapProcessingMode());
/*
* Was "appendonly" specified in the relopts? If yes, fix our relstorage.
* Also, check for override (debug) GUCs.
* During upgrade, do not validate because we accept tidycat options as well.
*/
stdRdOptions = (StdRdOptions*) heap_reloptions(relkind, reloptions, !gp_upgrade_mode);
heap_test_override_reloptions(relkind, stdRdOptions, &safefswritesize);
appendOnlyRel = stdRdOptions->appendonly;
if(appendOnlyRel)
{
if(stdRdOptions->columnstore == RELSTORAGE_PARQUET){
DetectHostEndian();
}
relstorage = stdRdOptions->columnstore;
}
reltablespace = GetSuitableTablespace(relkind, relstorage,
reltablespace, &override);
if (override)
{
if (stdRdOptions->forceHeap)
ereport(ERROR,
(errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED),
errmsg("tablespace \"%s\" does not support heap relation",
get_tablespace_name(reltablespace)),
errOmitLocation(true)));
appendOnlyRel = stdRdOptions->appendonly = true;
relstorage = stdRdOptions->columnstore;
reloptions = transformRelOptions(reloptions,
list_make1(makeDefElem("appendonly", (Node *) makeString("true"))),
true,
false);
}
validateAppendOnlyRelOptions(appendOnlyRel,
stdRdOptions->blocksize,
stdRdOptions->pagesize,
stdRdOptions->rowgroupsize,
safefswritesize,
stdRdOptions->compresslevel,
stdRdOptions->compresstype,
stdRdOptions->checksum,
relkind,
stdRdOptions->columnstore);
/* MPP-8058: disallow OIDS on column-oriented tables */
if (tupdesc->tdhasoid &&
IsNormalProcessingMode() &&
(Gp_role == GP_ROLE_DISPATCH))
{
if (relstorage == RELSTORAGE_PARQUET)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(
"OIDS=TRUE is not allowed on tables that "
"use column-oriented storage. Use OIDS=FALSE"
),
errOmitLocation(true)));
else
ereport(NOTICE,
(errmsg(
"OIDS=TRUE is not recommended for user-created "
"tables. Use OIDS=FALSE to prevent wrap-around "
"of the OID counter"
),
errOmitLocation(true)));
}
CheckAttributeNamesTypes(tupdesc, relkind);
CheckAttributeForParquet(tupdesc, relstorage);
if (get_relname_relid(relname, relnamespace))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists", relname),
errOmitLocation(true)));
/*
* 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))
relid = GetNewRelFileNode(reltablespace, shared_relation,
pg_class_desc,
relstorage_is_ao(pg_class_desc->rd_rel->relstorage));
else
if (IsUnderPostmaster)
{
CheckNewRelFileNodeIsOk(relid, reltablespace, shared_relation,
pg_class_desc,
relstorage_is_ao(pg_class_desc->rd_rel->relstorage));
}
/*
* 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,
tupdesc,
relam,
relkind,
relstorage,
shared_relation,
allow_system_table_mods,
bufferPoolBulkLoad);
Assert(relid == RelationGetRelid(new_rel_desc));
if (persistentTid != NULL)
{
*persistentTid =
new_rel_desc->rd_relationnodeinfo.persistentTid;
*persistentSerialNum =
new_rel_desc->rd_relationnodeinfo.persistentSerialNum;
}
/*
* since defining a relation also defines a complex type, we add a new
* system type corresponding to the new relation.
*
* NOTE: we could get a unique-index failure here, in case the same name
* has already been used for a type.
*
* Don't create the shell type if the bootstrapper tells us it already
* knows what it is. Importing for upgrading.
*/
if (IsBootstrapProcessingMode() &&
(PointerIsValid(comptypeOid) && OidIsValid(*comptypeOid)))
{
new_type_oid = *comptypeOid;
}
else
{
if (comptypeOid == NULL || *comptypeOid == InvalidOid)
new_type_oid = AddNewRelationType(relname,
relnamespace,
relid,
relkind,
ownerid);
else
{
new_type_oid = TypeCreateWithOid(relname, /* type name */
relnamespace, /* type namespace */
relid, /* relation oid */
relkind, /* relation kind */
ownerid,
-1, /* internal size (varlena) */
'c', /* type-type (complex) */
',', /* default array delimiter */
F_RECORD_IN, /* input procedure */
F_RECORD_OUT, /* output procedure */
F_RECORD_RECV, /* receive procedure */
F_RECORD_SEND, /* send procedure */
InvalidOid, /* analyze procedure - default */
InvalidOid, /* array element type - irrelevant */
InvalidOid, /* domain base type - irrelevant */
NULL, /* default value - none */
NULL, /* default binary representation */
false, /* passed by reference */
'd', /* alignment - must be the largest! */
'x', /* fully TOASTable */
-1, /* typmod */
0, /* array dimensions for typBaseType */
false, /* Type NOT NULL */
*comptypeOid,
0);
}
}
if (comptypeOid)
*comptypeOid = new_type_oid;
/*
* 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,
ownerid,
relkind,
relstorage,
reloptions);
if (gp_relfile_node_desc != NULL)
{
if (!appendOnlyRel)
{
/*
* for heap table and index, use master's contentid always
*/
AddNewRelfileNodeTuple(
gp_relfile_node_desc,
new_rel_desc);
}
heap_close(gp_relfile_node_desc, RowExclusiveLock);
}
/* Add an entry in pg_appendonly. */
if(appendOnlyRel)
{
if(relstorage == RELSTORAGE_PARQUET)
{
InsertAppendOnlyEntry(relid,
stdRdOptions->rowgroupsize,
stdRdOptions->pagesize,
0,
safefswritesize,
stdRdOptions->compresslevel,
1,
0,
stdRdOptions->checksum,
(stdRdOptions->columnstore == RELSTORAGE_AOROWS) ? false : true,
stdRdOptions->compresstype,
InvalidOid,
InvalidOid,
InvalidOid,
InvalidOid);
}
else if (relstorage == RELSTORAGE_AOROWS)
{
InsertAppendOnlyEntry(relid,
stdRdOptions->blocksize,
0,
DEFAULT_SPLIT_WRITE_SIZE,
safefswritesize,
stdRdOptions->compresslevel,
2,
0,
stdRdOptions->checksum,
(stdRdOptions->columnstore == RELSTORAGE_AOROWS) ? false : true,
stdRdOptions->compresstype,
InvalidOid,
InvalidOid,
InvalidOid,
InvalidOid);
}
}
/*
* now add tuples to pg_attribute for the attributes in our new relation.
*/
AddNewAttributeTuples(relid, new_rel_desc->rd_att, relkind,
oidislocal, oidinhcount);
/*
* Make a dependency link to force the relation to be deleted if its
* namespace is. Also make a dependency link to its owner.
*
* 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).
* Also, skip this in bootstrap mode, since we don't make dependencies
* while bootstrapping.
*/
if (relkind != RELKIND_COMPOSITE_TYPE &&
relkind != RELKIND_TOASTVALUE &&
!IsBootstrapProcessingMode())
{
ObjectAddress myself,
referenced;
myself.classId = RelationRelationId;
myself.objectId = relid;
myself.objectSubId = 0;
referenced.classId = NamespaceRelationId;
referenced.objectId = relnamespace;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
recordDependencyOnOwner(RelationRelationId, relid, ownerid);
}
/*
* 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, tupdesc);
/*
* If there's a special on-commit action, remember it
*/
if (oncommit != ONCOMMIT_NOOP)
register_on_commit_action(relid, oncommit);
/*
* CDB: If caller gave us a distribution policy, store the distribution
* key column list in the gp_distribution_policy catalog and attach a
* copy to the relcache entry.
*/
if (!gp_upgrade_mode && policy &&
Gp_role == GP_ROLE_DISPATCH)
{
Assert(relkind == RELKIND_RELATION);
new_rel_desc->rd_cdbpolicy = GpPolicyCopy(GetMemoryChunkContext(new_rel_desc), policy);
GpPolicyStore(relid, policy);
}
if (Gp_role == GP_ROLE_DISPATCH) /* MPP-11313: */
{
bool doIt = true;
char *subtyp = "TABLE";
switch (relkind)
{
case RELKIND_RELATION:
break;
case RELKIND_INDEX:
subtyp = "INDEX";
break;
case RELKIND_SEQUENCE:
subtyp = "SEQUENCE";
break;
case RELKIND_VIEW:
subtyp = "VIEW";
break;
default:
doIt = false;
}
/* MPP-7576: don't track internal namespace tables */
switch (relnamespace)
{
case PG_CATALOG_NAMESPACE:
/* MPP-7773: don't track objects in system namespace
* if modifying system tables (eg during upgrade)
*/
if (allowSystemTableModsDDL)
doIt = false;
break;
case PG_TOAST_NAMESPACE:
case PG_BITMAPINDEX_NAMESPACE:
case PG_AOSEGMENT_NAMESPACE:
doIt = false;
break;
default:
break;
}
/* MPP-7572: not valid if in any temporary namespace */
if (doIt)
doIt = (!(isAnyTempNamespace(relnamespace)));
/* MPP-6929: metadata tracking */
if (doIt)
MetaTrackAddObject(RelationRelationId,
relid, GetUserId(), /* not ownerid */
"CREATE", subtyp
);
}
/*
* 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); /* do not unlock till end of xact */
heap_close(pg_class_desc, RowExclusiveLock);
return relid;
} /* end heap_create_with_catalog */
/*
* 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)
{
int numDel = 0;
numDel = caql_getcount(
NULL,
cql("DELETE FROM pg_inherits "
" WHERE inhrelid = :1 ",
ObjectIdGetDatum(relid)));
}
/* del_part_entry_by_key is superfluous - removed */
static void
RemovePartitioning(Oid relid)
{
Relation rel;
HeapTuple tuple;
Relation pgrule;
cqContext *pcqCtx;
cqContext cqc, cqcrul;
int numDel = 0;
if (Gp_role == GP_ROLE_EXECUTE)
return;
RemovePartitionEncodingByRelid(relid);
/* loop through all matches in pg_partition */
rel = heap_open(PartitionRelationId, RowExclusiveLock);
pgrule = heap_open(PartitionRuleRelationId,
RowExclusiveLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), rel),
cql("SELECT * FROM pg_partition "
" WHERE parrelid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(relid)));
while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx)))
{
Oid paroid = HeapTupleGetOid(tuple);
numDel = caql_getcount(
caql_addrel(cqclr(&cqcrul), pgrule),
cql("DELETE FROM pg_partition_rule "
" WHERE paroid = :1 ",
ObjectIdGetDatum(paroid)));
/* remove ourself */
caql_delete_current(pcqCtx);
}
caql_endscan(pcqCtx);
heap_close(rel, NoLock);
/* we might be a leaf partition: delete any records */
numDel = caql_getcount(
caql_addrel(cqclr(&cqcrul), pgrule),
cql("DELETE FROM pg_partition_rule "
" WHERE parchildrelid = :1 ",
ObjectIdGetDatum(relid)));
heap_close(pgrule, NoLock);
CommandCounterIncrement();
}
/*
* 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)
{
HeapTuple tup;
cqContext *pcqCtx;
/* Grab an appropriate lock on the pg_class relation */
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(relid)));
tup = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for relation %u", relid);
/* delete the relation tuple from pg_class, and finish up */
caql_delete_current(pcqCtx);
caql_endscan(pcqCtx);
}
/*
* 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)
{
int numDel = 0;
numDel = caql_getcount(
NULL,
cql("DELETE FROM pg_attribute "
" WHERE attrelid = :1 ",
ObjectIdGetDatum(relid)));
}
void
DeleteGpRelfileNodeTuple(
Relation relation,
int32 segmentFileNum)
{
Relation gp_relfile_node;
HeapTuple tuple;
/*
Assert(NULL != relation->rd_segfile0_relationnodeinfos
&& contentid + 1 < relation->rd_segfile0_count);
*/
Assert(relation->rd_relationnodeinfo.isPresent);
/* Grab an appropriate lock on the pg_class relation */
gp_relfile_node = heap_open(GpRelfileNodeRelationId, RowExclusiveLock);
tuple = ScanGpRelfileNodeTuple(
gp_relfile_node,
relation->rd_rel->relfilenode,
segmentFileNum);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find node tuple for relation %u, relation file node %u, segment file #%d",
RelationGetRelid(relation),
relation->rd_rel->relfilenode,
segmentFileNum);
/* delete the relation tuple from pg_class, and finish up */
simple_heap_delete(gp_relfile_node, &tuple->t_self);
heap_close(gp_relfile_node, 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.)
*/
void
RemoveAttributeById(Oid relid, AttrNumber attnum)
{
Relation rel;
Relation attr_rel;
HeapTuple tuple;
Form_pg_attribute attStruct;
char newattname[NAMEDATALEN];
cqContext cqc;
cqContext *pcqCtx;
/*
* 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);
pcqCtx = caql_addrel(cqclr(&cqc), attr_rel);
tuple = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_attribute "
" WHERE attrelid = :1 "
" AND attnum = :2 "
" FOR UPDATE ",
ObjectIdGetDatum(relid),
Int16GetDatum(attnum)));
if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
attnum, relid);
attStruct = (Form_pg_attribute) GETSTRUCT(tuple);
if (attnum < 0)
{
/* System attribute (probably OID) ... just delete the row */
caql_delete_current(pcqCtx);
}
else
{
/* Dropping user attributes is lots harder */
/* Mark the attribute as dropped */
attStruct->attisdropped = 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.
*/
attStruct->atttypid = InvalidOid;
/* Remove any NOT NULL constraint the column may have */
attStruct->attnotnull = false;
/* We don't want to keep stats for it anymore */
attStruct->attstattarget = 0;
/*
* Change the column name to something that isn't likely to conflict
*/
snprintf(newattname, sizeof(newattname),
"........pg.dropped.%d........", attnum);
namestrcpy(&(attStruct->attname), newattname);
caql_update_current(pcqCtx, tuple);
}
/*
* 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)
RemoveStatistics(relid, attnum);
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)
{
Relation attrdef_rel;
cqContext *pcqCtx;
cqContext cqc;
HeapTuple tuple;
bool found = false;
attrdef_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), attrdef_rel),
cql("SELECT * FROM pg_attrdef "
" WHERE adrelid = :1 "
" AND adnum = :2 "
" FOR UPDATE ",
ObjectIdGetDatum(relid),
Int16GetDatum(attnum)));
/* XXX XXX: SELECT oid ... due to unique index ? */
/* There should be at most one matching tuple, but we loop anyway */
while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx)))
{
ObjectAddress object;
object.classId = AttrDefaultRelationId;
object.objectId = HeapTupleGetOid(tuple);
object.objectSubId = 0;
performDeletion(&object, behavior);
found = true;
}
caql_endscan(pcqCtx);
heap_close(attrdef_rel, RowExclusiveLock);
if (complain && !found)
elog(ERROR, "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;
cqContext *adrcqCtx;
cqContext *attcqCtx;
cqContext cqc;
cqContext cqc2;
HeapTuple tuple;
Oid myrelid;
AttrNumber myattnum;
/* Grab an appropriate lock on the pg_attrdef relation */
attrdef_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
/* Find the pg_attrdef tuple */
adrcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), attrdef_rel),
cql("SELECT * FROM pg_attrdef "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(attrdefId)));
tuple = caql_getnext(adrcqCtx);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for attrdef %u", attrdefId);
myrelid = ((Form_pg_attrdef) GETSTRUCT(tuple))->adrelid;
myattnum = ((Form_pg_attrdef) GETSTRUCT(tuple))->adnum;
/* Get an exclusive lock on the relation owning the attribute */
myrel = relation_open(myrelid, AccessExclusiveLock);
/* Now we can delete the pg_attrdef row */
caql_delete_current(adrcqCtx);
caql_endscan(adrcqCtx);
heap_close(attrdef_rel, RowExclusiveLock);
/* Fix the pg_attribute row */
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
attcqCtx = caql_addrel(cqclr(&cqc2), attr_rel);
tuple = caql_getfirst(
attcqCtx,
cql("SELECT * FROM pg_attribute "
" WHERE attrelid = :1 "
" AND attnum = :2 "
" FOR UPDATE ",
ObjectIdGetDatum(myrelid),
Int16GetDatum(myattnum)));
if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
myattnum, myrelid);
((Form_pg_attribute) GETSTRUCT(tuple))->atthasdef = false;
caql_update_current(attcqCtx, tuple);
/* and Update indexes (implicit) */
/*
* 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);
/* Keep lock on attribute's rel until end of xact */
relation_close(myrel, NoLock);
}
static void
remove_gp_relation_node_and_schedule_drop(
Relation rel)
{
PersistentFileSysRelStorageMgr relStorageMgr;
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"remove_gp_relation_node_and_schedule_drop: dropping relation '%s', relation id %u '%s', relfilenode %u",
rel->rd_rel->relname.data,
rel->rd_id,
relpath(rel->rd_node),
rel->rd_rel->relfilenode);
relStorageMgr = ((RelationIsAoRows(rel) || RelationIsParquet(rel)) ?
PersistentFileSysRelStorageMgr_AppendOnly:
PersistentFileSysRelStorageMgr_BufferPool);
if (relStorageMgr == PersistentFileSysRelStorageMgr_BufferPool)
{
MirroredFileSysObj_ScheduleDropBufferPoolRel(rel);
DeleteGpRelfileNodeTuple(
rel,
/* segmentFileNum */ 0);
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"remove_gp_relation_node_and_schedule_drop: For Buffer Pool managed relation '%s' persistent TID %s and serial number " INT64_FORMAT " for DROP",
relpath(rel->rd_node),
ItemPointerToString(&rel->rd_relationnodeinfo.persistentTid),
rel->rd_relationnodeinfo.persistentSerialNum);
}
else
{
Relation relNodeRelation;
GpRelfileNodeScan gpRelfileNodeScan;
HeapTuple tuple;
int32 segmentFileNum;
ItemPointerData persistentTid;
int64 persistentSerialNum;
relNodeRelation = heap_open(GpRelfileNodeRelationId, RowExclusiveLock);
GpRelfileNodeBeginScan(
relNodeRelation,
rel->rd_id,
rel->rd_rel->relfilenode,
&gpRelfileNodeScan);
while ((tuple = GpRelfileNodeGetNext(
&gpRelfileNodeScan,
&segmentFileNum,
&persistentTid,
&persistentSerialNum)))
{
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"remove_gp_relation_node_and_schedule_drop: For Append-Only relation %u relfilenode %u scanned segment file #%d, serial number " INT64_FORMAT " at TID %s for DROP",
rel->rd_id,
rel->rd_rel->relfilenode,
segmentFileNum,
persistentSerialNum,
ItemPointerToString(&persistentTid));
simple_heap_delete(relNodeRelation, &tuple->t_self);
MirroredFileSysObj_ScheduleDropAppendOnlyFile(
&rel->rd_node,
segmentFileNum,
rel->rd_rel->relname.data,
&persistentTid,
persistentSerialNum);
}
GpRelfileNodeEndScan(&gpRelfileNodeScan);
heap_close(relNodeRelation, RowExclusiveLock);
/*
* Now schedule the relation directory removal.
*
* TODO: Add some logic to search the TID and serial number
* of the tuple associated with the relation in the persistent
* table.
*/
MirroredFileSysObj_ScheduleDropRelationDir(&rel->rd_node,
is_tablespace_shared(rel->rd_node.spcNode));
}
}
/*
* 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;
const struct GpPolicy *policy;
bool removePolicy = false;
bool is_part_child = false;
bool is_appendonly_rel;
bool is_external_rel;
bool is_foreign_rel;
char relkind;
/*
* Open and lock the relation.
*/
rel = relation_open(relid, AccessExclusiveLock);
relkind = rel->rd_rel->relkind;
is_appendonly_rel = (RelationIsAoRows(rel) || RelationIsParquet(rel));
is_external_rel = RelationIsExternal(rel);
is_foreign_rel = RelationIsForeign(rel);
/*
* Get the distribution policy and figure out if it is to be removed.
*/
policy = rel->rd_cdbpolicy;
if (policy &&
policy->ptype == POLICYTYPE_PARTITIONED &&
Gp_role == GP_ROLE_DISPATCH &&
relkind == RELKIND_RELATION)
removePolicy = true;
/*
* Schedule unlinking of the relation's physical file at commit.
*/
if (relkind != RELKIND_VIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
!RelationIsExternal(rel))
{
remove_gp_relation_node_and_schedule_drop(rel);
}
/*
* Close relcache entry, but *keep* AccessExclusiveLock (unless this is
* a child partition) on the relation until transaction commit. This
* ensures no one else will try to do something with the doomed relation.
*/
is_part_child = !rel_needs_long_lock(RelationGetRelid(rel));
if (is_part_child)
relation_close(rel, AccessExclusiveLock);
else
relation_close(rel, NoLock);
/*
* 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);
/*
* remove partitioning configuration
*/
RemovePartitioning(relid);
/*
* delete statistics
*/
RemoveStatistics(relid, 0);
/*
* delete attribute tuples
*/
DeleteAttributeTuples(relid);
/*
* delete relation tuple
*/
DeleteRelationTuple(relid);
/*
* append-only table? delete the corresponding pg_appendonly tuple
*/
if(is_appendonly_rel)
RemoveAppendonlyEntry(relid);
/*
* External table? If so, delete the pg_exttable tuple.
*/
if (is_external_rel)
{
/* Step 1. remove uri on file system */
rel = relation_open(relid, AccessExclusiveLock);
ExtTableEntry *exttbl = GetExtTableEntry(rel->rd_id);
char *path = (char *) strVal(linitial(exttbl->locations));
char *searchKey = (char *) palloc0 (MAXPGPATH);
char *fileSpacePath = NULL;
GetFilespacePathForTablespace(get_database_dts(MyDatabaseId),
&fileSpacePath);
sprintf(searchKey, "%s/ExtErrTbl/",fileSpacePath);
char *match = strstr(path,searchKey);
if (match)
{
RemovePath(path, 1);
}
/* Get category for the external table */
List *entry_locations = exttbl->locations;
Assert(entry_locations);
ListCell *entry_location = list_head(entry_locations);
char *url = ((Value*)lfirst(entry_location))->val.str;
char *category = getExtTblCategoryInFmtOptsStr(exttbl->fmtopts);
/* Remove data for internal table */
if (category != NULL &&
pg_strncasecmp(category, "internal", strlen("internal")) == 0)
{
if (IS_HDFS_URI(url)) /* ORC, TEXT, CSV */
{
// orc, text, csv only support one location.
Assert(list_length(entry_locations) == 1);
RemovePath(url, 1);
}
}
if (category)
{
pfree(category);
}
relation_close(rel, AccessExclusiveLock);
/* Step 2. remove pg_exttable entry */
RemoveExtTableEntry(relid);
}
if (is_foreign_rel)
RemoveForeignTableEntry(relid);
/*
* delete distribution policy if present
*/
if (removePolicy)
GpPolicyRemove(relid);
/*
* Attribute encoding
*/
if (relkind == RELKIND_RELATION)
RemoveAttributeEncodingsByRelid(relid);
/* MPP-6929: metadata tracking */
MetaTrackDropObject(RelationRelationId,
relid);
}
/*
* Store a default expression for column attnum of relation rel.
* The expression must be presented as a nodeToString() string.
*/
void
StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin)
{
Node *expr;
char *adsrc;
Relation adrel;
HeapTuple tuple;
Datum values[4];
static bool nulls[4] = {false, false, false, false};
Relation attrrel;
HeapTuple atttup;
Form_pg_attribute attStruct;
Oid attrdefOid;
ObjectAddress colobject,
defobject;
cqContext *adrcqCtx;
cqContext *attcqCtx;
cqContext cqc;
cqContext cqc2;
/*
* Need to construct source equivalent of given node-string.
*/
expr = stringToNode(adbin);
/*
* 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;
values[Anum_pg_attrdef_adbin - 1] = CStringGetTextDatum(adbin);
values[Anum_pg_attrdef_adsrc - 1] = CStringGetTextDatum(adsrc);
adrel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
adrcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), adrel),
cql("INSERT INTO pg_attrdef ",
NULL));
if (Debug_check_for_invalid_persistent_tid)
{
elog(LOG,
"StoreAttrDefault[1] relation %u/%u/%u '%s', isPresent %s, serial number " INT64_FORMAT ", TID %s",
adrel->rd_node.spcNode,
adrel->rd_node.dbNode,
adrel->rd_node.relNode,
NameStr(adrel->rd_rel->relname),
(adrel->rd_relationnodeinfo.isPresent ? "true" : "false"),
adrel->rd_relationnodeinfo.persistentSerialNum,
ItemPointerToString(&adrel->rd_relationnodeinfo.persistentTid));
}
// Fetch gp_persistent_relation_node information that will be added to XLOG record.
RelationFetchGpRelationNodeForXLog(adrel);
if (Debug_check_for_invalid_persistent_tid)
{
elog(LOG,
"StoreAttrDefault[2] relation %u/%u/%u '%s', isPresent %s, serial number " INT64_FORMAT ", TID %s",
adrel->rd_node.spcNode,
adrel->rd_node.dbNode,
adrel->rd_node.relNode,
NameStr(adrel->rd_rel->relname),
(adrel->rd_relationnodeinfo.isPresent ? "true" : "false"),
adrel->rd_relationnodeinfo.persistentSerialNum,
ItemPointerToString(&adrel->rd_relationnodeinfo.persistentTid));
}
tuple = caql_form_tuple(adrcqCtx, values, nulls);
attrdefOid = caql_insert(adrcqCtx, tuple);
/* and Update indexes (implicit) */
defobject.classId = AttrDefaultRelationId;
defobject.objectId = attrdefOid;
defobject.objectSubId = 0;
caql_endscan(adrcqCtx);
heap_close(adrel, RowExclusiveLock);
/* now can free some of the stuff allocated above */
pfree(DatumGetPointer(values[Anum_pg_attrdef_adbin - 1]));
pfree(DatumGetPointer(values[Anum_pg_attrdef_adsrc - 1]));
heap_freetuple(tuple);
pfree(adsrc);
/*
* Update the pg_attribute entry for the column to show that a default
* exists.
*/
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
attcqCtx = caql_addrel(cqclr(&cqc2), attrrel);
atttup = caql_getfirst(
attcqCtx,
cql("SELECT * FROM pg_attribute "
" WHERE attrelid = :1 "
" AND attnum = :2 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationGetRelid(rel)),
Int16GetDatum(attnum)));
if (!HeapTupleIsValid(atttup))
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
attnum, RelationGetRelid(rel));
attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
if (!attStruct->atthasdef)
{
attStruct->atthasdef = true;
caql_update_current(attcqCtx, atttup);
/* and Update indexes (implicit) */
}
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);
/*
* Record dependencies on objects used in the expression, too.
*/
recordDependencyOnExpr(&defobject, expr, NIL, DEPENDENCY_NORMAL);
}
/*
* Store a check-constraint expression for the given relation.
* The expression must be presented as a nodeToString() string.
*
* Caller is responsible for updating the count of constraints
* in the pg_class entry for the relation.
*
* Return OID of the newly created constraint entry.
*/
static Oid
StoreRelCheck(Relation rel, char *ccname, char *ccbin, Oid conOid)
{
Node *expr;
char *ccsrc;
List *varList;
int keycount;
int16 *attNos;
/*
* Convert condition to an expression tree.
*/
expr = stringToNode(ccbin);
/*
* deparse it
*/
ccsrc = deparse_expression(expr,
deparse_context_for(RelationGetRelationName(rel),
RelationGetRelid(rel)),
false, false);
/*
* Find columns of rel that are used in ccbin
*
* 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, false);
keycount = list_length(varList);
if (keycount > 0)
{
ListCell *vl;
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
*/
conOid = CreateConstraintEntry(ccname, /* Constraint Name */
conOid, /* Constraint Oid */
RelationGetNamespace(rel), /* namespace */
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
false, /* Is Deferred */
RelationGetRelid(rel), /* relation */
attNos, /* attrs in the constraint */
keycount, /* # attrs in the constraint */
InvalidOid, /* not a domain constraint */
InvalidOid, /* Foreign key fields */
NULL,
0,
' ',
' ',
' ',
InvalidOid, /* no associated index */
expr, /* Tree form check constraint */
ccbin, /* Binary form check constraint */
ccsrc); /* Source form check constraint */
pfree(ccsrc);
return conOid;
}
/*
* Store defaults and constraints passed in via the tuple constraint struct.
*
* 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 AddRelationRawConstraints()).
*/
static void
StoreConstraints(Relation rel, TupleDesc tupdesc)
{
TupleConstr *constr = tupdesc->constr;
int i;
if (!constr)
return; /* nothing to do */
/*
* 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();
for (i = 0; i < constr->num_defval; i++)
StoreAttrDefault(rel, constr->defval[i].adnum,
constr->defval[i].adbin);
}
/*
* AddRelationConstraints
*
* Add both raw (not-yet-transformed) and cooked 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
* rawColDefaults: list of RawColumnDefault structures
* constraints: list of Constraint nodes
*
* All entries in rawColDefaults will be processed. Entries in rawConstraints
* will be processed only if they are CONSTR_CHECK type and contain a "raw"
* expression.
*
* 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 *
AddRelationConstraints(Relation rel,
List *rawColDefaults,
List *constraints)
{
List *cookedConstraints = NIL;
TupleDesc tupleDesc;
TupleConstr *oldconstr;
int numoldchecks;
ParseState *pstate;
RangeTblEntry *rte;
int numchecks;
List *checknames;
ListCell *cell;
Node *expr;
CookedConstraint *cooked;
/*
* Get info about existing constraints.
*/
tupleDesc = RelationGetDescr(rel);
oldconstr = tupleDesc->constr;
if (oldconstr)
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);
/*
* Process column default expressions.
*/
foreach(cell, rawColDefaults)
{
RawColumnDefault *colDef = (RawColumnDefault *) lfirst(cell);
Form_pg_attribute atp = rel->rd_att->attrs[colDef->attnum - 1];
expr = cookDefault(pstate, colDef->raw_default,
atp->atttypid, atp->atttypmod,
NameStr(atp->attname));
StoreAttrDefault(rel, colDef->attnum, nodeToString(expr));
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
cooked->contype = CONSTR_DEFAULT;
cooked->name = NULL;
cooked->attnum = colDef->attnum;
cooked->expr = expr;
cookedConstraints = lappend(cookedConstraints, cooked);
}
/*
* Process constraint expressions.
*/
numchecks = numoldchecks;
checknames = NIL;
foreach(cell, constraints)
{
Constraint *cdef = (Constraint *) lfirst(cell);
char *ccname;
if (cdef->contype != CONSTR_CHECK)
continue;
/*
* Transform raw parsetree to executable expression, and verify
* it's valid as a CHECK constraint
*/
if (cdef->raw_expr != NULL)
{
Insist(cdef->cooked_expr == NULL);
expr = cookConstraint(pstate, cdef->raw_expr,
RelationGetRelationName(rel));
}
/*
* Here, we assume the parser will only pass us valid CHECK
* expressions, so we do no particular checking.
*/
else
{
Insist(cdef->cooked_expr != NULL);
expr = stringToNode(cdef->cooked_expr);
}
/*
* Check name uniqueness, or generate a name if none was given.
*/
if (cdef->name != NULL)
{
ListCell *cell2;
ccname = cdef->name;
/* Check against pre-existing constraints */
if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
RelationGetRelid(rel),
RelationGetNamespace(rel),
ccname))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("constraint \"%s\" for relation \"%s\" already exists",
ccname, RelationGetRelationName(rel)),
errOmitLocation(true)));
/* Check against other new constraints */
/* Needed because we don't do CommandCounterIncrement in loop */
foreach(cell2, checknames)
{
if (strcmp((char *) lfirst(cell2), ccname) == 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("check constraint \"%s\" already exists",
ccname),
errOmitLocation(true)));
}
}
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;
char *colname;
vars = pull_var_clause(expr, false);
/* eliminate duplicates */
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);
}
/* save name for future checks */
checknames = lappend(checknames, ccname);
/*
* OK, store it.
*/
cdef->conoid = StoreRelCheck(rel, ccname, nodeToString(expr), cdef->conoid);
numchecks++;
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
cooked->contype = CONSTR_CHECK;
cooked->name = ccname;
cooked->attnum = 0;
cooked->expr = expr;
cookedConstraints = lappend(cookedConstraints, cooked);
}
/* Cleanup the parse state */
free_parsestate(&pstate);
/*
* 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);
return cookedConstraints;
}
/*
* Transform raw parsetree to executable expression.
*/
static Node*
cookConstraint (ParseState *pstate,
Node *raw_constraint,
char *relname)
{
Node *expr;
/* Transform raw parsetree to executable expression. */
expr = transformExpr(pstate, raw_constraint);
/* Make sure it yields a boolean result. */
expr = coerce_to_boolean(pstate, expr, "CHECK");
/* 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), errOmitLocation(true)));
/*
* No subplans or aggregates, either...
*/
if (pstate->p_hasSubLinks)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot use subquery in check constraint"),
errOmitLocation(true)));
if (pstate->p_hasAggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in check constraint"),
errOmitLocation(true)));
if (pstate->p_hasWindFuncs)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use window function in check constraint"),
errOmitLocation(true)));
return expr;
}
/*
* 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.
*/
void
SetRelationNumChecks(Relation rel, int numchecks)
{
Relation relrel;
HeapTuple reltup;
Form_pg_class relStruct;
cqContext cqc;
cqContext *pcqCtx;
relrel = heap_open(RelationRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), relrel);
reltup = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationGetRelid(rel))));
if (!HeapTupleIsValid(reltup))
elog(ERROR, "cache lookup failed for relation %u",
RelationGetRelid(rel));
relStruct = (Form_pg_class) GETSTRUCT(reltup);
if (relStruct->relchecks != numchecks)
{
relStruct->relchecks = numchecks;
caql_update_current(pcqCtx, reltup);
/* and Update indexes (implicit) */
}
else
{
/* Skip the disk update, but force relcache inval anyway */
CacheInvalidateRelcache(rel);
}
heap_freetuple(reltup);
heap_close(relrel, RowExclusiveLock);
}
/*
* 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,
char *attname)
{
Node *expr;
Assert(raw_default != NULL);
/*
* Transform raw parsetree to executable expression.
*/
expr = transformExpr(pstate, raw_default);
/*
* Make sure default expr does not refer to any vars.
*/
if (contain_var_clause(expr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("cannot use column references in default expression")));
/*
* It can't return a set either.
*/
if (expression_returns_set(expr))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("default expression must not return a set"),
errOmitLocation(true)));
/*
* No subplans or aggregates, either...
*/
if (pstate->p_hasSubLinks)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot use subquery in default expression"),
errOmitLocation(true)));
if (pstate->p_hasAggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in default expression"),
errOmitLocation(true)));
if (pstate->p_hasWindFuncs)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use window function in default expression"),
errOmitLocation(true)));
/*
* 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);
expr = coerce_to_target_type(pstate, expr, type_id,
atttypid, atttypmod,
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
if (expr == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" is of type %s"
" but default expression is of type %s",
attname,
format_type_be(atttypid),
format_type_be(type_id)),
errhint("You will need to rewrite or cast the expression."),
errOmitLocation(true)));
}
return expr;
}
/*
* Removes all constraints on a relation that match the given name.
*
* It is the responsibility of the calling function to acquire a suitable
* lock on the relation.
*
* Returns: The number of constraints removed.
*/
int
RemoveRelConstraints(Relation rel, const char *constrName,
DropBehavior behavior)
{
int ndeleted = 0;
cqContext *pcqCtx;
HeapTuple contup;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_constraint "
" WHERE conrelid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationGetRelid(rel))));
/*
* Scan over the result set, removing any matching entries.
*/
while (HeapTupleIsValid(contup = caql_getnext(pcqCtx)))
{
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(contup);
if (strcmp(NameStr(con->conname), constrName) == 0)
{
ObjectAddress conobj;
conobj.classId = ConstraintRelationId;
conobj.objectId = HeapTupleGetOid(contup);
conobj.objectSubId = 0;
performDeletion(&conobj, behavior);
ndeleted++;
}
}
/* Clean up after the scan */
caql_endscan(pcqCtx);
return ndeleted;
}
/*
* 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
* for that column.
*/
void
RemoveStatistics(Oid relid, AttrNumber attnum)
{
int numDel = 0;
if (attnum == 0)
{
numDel = caql_getcount(
NULL,
cql("DELETE FROM pg_statistic "
" WHERE starelid = :1 ",
ObjectIdGetDatum(relid)));
}
else
{
numDel = caql_getcount(
NULL,
cql("DELETE FROM pg_statistic "
" WHERE starelid = :1 "
" AND staattnum = :2 ",
ObjectIdGetDatum(relid),
Int16GetDatum(attnum)));
}
}
/*
* 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)
{
ListCell *indlist;
/* Ask the relcache to produce a list of the indexes of the rel */
foreach(indlist, RelationGetIndexList(heapRelation))
{
Oid indexId = lfirst_oid(indlist);
Relation currentIndex;
IndexInfo *indexInfo;
/* Open the index relation; use exclusive lock, just to be sure */
currentIndex = index_open(indexId, AccessExclusiveLock);
/* Fetch info needed for index_build */
indexInfo = BuildIndexInfo(currentIndex);
/* Now truncate the actual file (and discard buffers) */
RelationTruncate(
currentIndex,
0,
/* markPersistentAsPhysicallyTruncated */ true);
/* Initialize the index and rebuild */
/* Note: we do not need to re-establish pkey setting */
index_build(heapRelation, currentIndex, indexInfo, false);
/* We're done with this index */
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;
/* Open relations for processing, and grab exclusive access on each */
foreach(cell, relids)
{
Oid rid = lfirst_oid(cell);
Relation rel, trel;
Oid toastrelid;
Oid aosegrelid;
Oid aoblkdirrelid;
AppendOnlyEntry *aoEntry = NULL;
rel = heap_open(rid, AccessExclusiveLock);
relations = lappend(relations, rel);
/* If there is a toast table, add it to the list too */
toastrelid = rel->rd_rel->reltoastrelid;
if (OidIsValid(toastrelid))
{
trel = heap_open(toastrelid, AccessExclusiveLock);
relations = lappend(relations, trel);
}
/*
* CONCERN: Not clear this EVER makes sense for Append-Only.
*/
if (RelationIsAoRows(rel) || RelationIsParquet(rel))
{
aoEntry = GetAppendOnlyEntry(rid, SnapshotNow);
/* If there is an aoseg table, add it to the list too */
aosegrelid = aoEntry->segrelid;
if (OidIsValid(aosegrelid))
{
rel = heap_open(aosegrelid, AccessExclusiveLock);
relations = lappend(relations, rel);
}
/* If there is an aoblkdir table, add it to the list too */
aoblkdirrelid = aoEntry->blkdirrelid;
if (OidIsValid(aoblkdirrelid))
{
rel = heap_open(aoblkdirrelid, AccessExclusiveLock);
relations = lappend(relations, rel);
}
pfree(aoEntry);
}
}
/* Don't allow truncate on tables that are referenced by foreign keys */
heap_truncate_check_FKs(relations, true);
/* OK to do it */
foreach(cell, relations)
{
Relation rel = lfirst(cell);
/* Truncate the actual file (and discard buffers) */
RelationTruncate(
rel,
0,
/* markPersistentAsPhysicallyTruncated */ false);
/* If this relation has indexes, truncate the indexes too */
RelationTruncateIndexes(rel);
/*
* Close the relation, but keep exclusive lock on it until commit.
*/
heap_close(rel, NoLock);
}
}
/*
* 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;
ListCell *cell;
/*
* 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 = lfirst(cell);
if (rel->rd_rel->reltriggers != 0)
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;
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),
errOmitLocation(true)));
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),
errOmitLocation(true)));
}
}
}
}
/*
* 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,
SnapshotNow, 0, NULL);
while (HeapTupleIsValid(tuple = systable_getnext(fkeyScan)))
{
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
/* Not a foreign key */
if (con->contype != CONSTRAINT_FOREIGN)
continue;
/* Not referencing one of our list of tables */
if (!list_member_oid(relationIds, con->confrelid))
continue;
/* Add referencer unless already in input or result list */
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;
/* Does the datum belong at the front? */
if (list == NIL || datum < linitial_oid(list))
return lcons_oid(datum, list);
/* Does it match the first entry? */
if (datum == linitial_oid(list))
return list; /* duplicate, so don't insert */
/* No, so find the entry it belongs after */
prev = list_head(list);
for (;;)
{
ListCell *curr = lnext(prev);
if (curr == NULL || datum < lfirst_oid(curr))
break; /* it belongs after 'prev', before 'curr' */
if (datum == lfirst_oid(curr))
return list; /* duplicate, so don't insert */
prev = curr;
}
/* Insert datum into list after 'prev' */
lappend_cell_oid(list, prev, datum);
return list;
}
/*
* setNewRelfilenodeCommon
*
* Replaces relfilenode and updates pg_class / gp_relation_node.
* If the updating relation is gp_relation_node's index, the caller
* should rebuild the index by index_build().
*/
static void
setNewRelfilenodeCommon(Relation relation, Oid newrelfilenode)
{
RelFileNode newrnode;
Relation pg_class;
HeapTuple tuple;
Form_pg_class rd_rel;
bool isAppendOnly;
Relation gp_relation_node;
bool is_gp_relation_node_index;
cqContext cqc;
cqContext *pcqCtx;
char * relname;
/*
* Find the pg_class tuple for the given relation. This is not used
* during bootstrap, so okay to use heap_update always.
*/
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
gp_relation_node = heap_open(GpRelfileNodeRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), pg_class);
tuple = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationGetRelid(relation))));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for relation %u",
RelationGetRelid(relation));
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
/* schedule unlinking old relfilenode */
remove_gp_relation_node_and_schedule_drop(relation);
/* create another storage file. Is it a little ugly ? */
/* NOTE: any conflict in relfilenode value will be caught here */
newrnode = relation->rd_node;
newrnode.relNode = newrelfilenode;
isAppendOnly = (relation->rd_rel->relstorage == RELSTORAGE_AOROWS ||
relation->rd_rel->relstorage == RELSTORAGE_PARQUET);
relname = RelationGetRelationName(relation);
if (!isAppendOnly)
{
SMgrRelation srel;
PersistentFileSysRelStorageMgr localRelStorageMgr;
PersistentFileSysRelBufpoolKind relBufpoolKind;
GpPersistentRelfileNode_GetRelfileInfo(
relation->rd_rel->relkind,
relation->rd_rel->relstorage,
relation->rd_rel->relam,
&localRelStorageMgr,
&relBufpoolKind);
Assert(localRelStorageMgr == PersistentFileSysRelStorageMgr_BufferPool);
srel = smgropen(newrnode);
MirroredFileSysObj_TransactionCreateBufferPoolFile(
srel,
relBufpoolKind,
relation->rd_isLocalBuf,
relname,
/* doJustInTimeDirCreate */ true,
/* bufferPoolBulkLoad */ false,
&relation->rd_relationnodeinfo.persistentTid,
&relation->rd_relationnodeinfo.persistentSerialNum);
smgrclose(srel);
}
else
{
MirroredFileSysObj_TransactionCreateRelationDir(
&newrnode,
false,
&relation->rd_relationnodeinfo.persistentTid,
&relation->rd_relationnodeinfo.persistentSerialNum);
}
if (Debug_check_for_invalid_persistent_tid &&
!Persistent_BeforePersistenceWork() &&
PersistentStore_IsZeroTid(&relation->rd_relationnodeinfo.persistentTid))
{
elog(ERROR,
"setNewRelfilenodeCommon has invalid TID (0,0) for relation %u/%u/%u '%s', serial number " INT64_FORMAT,
newrnode.spcNode,
newrnode.dbNode,
newrnode.relNode,
NameStr(relation->rd_rel->relname),
relation->rd_relationnodeinfo.persistentSerialNum);
}
relation->rd_relationnodeinfo.isPresent = true;
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"setNewRelfilenodeCommon: NEW '%s', Append-Only '%s', persistent TID %s and serial number " INT64_FORMAT,
relpath(newrnode),
(isAppendOnly ? "true" : "false"),
ItemPointerToString(&relation->rd_relationnodeinfo.persistentTid),
relation->rd_relationnodeinfo.persistentSerialNum);
/* Update GETSTRUCT fields of the pg_class row */
rd_rel->relfilenode = newrelfilenode;
rd_rel->relpages = 0; /* it's empty until further notice */
rd_rel->reltuples = 0;
/*
* If the swapping relation is an index of gp_relation_node,
* updating itself is bogus; if gp_relation_node has old indexlist,
* CatalogUpdateIndexes updates old index file, and is crash-unsafe.
* Hence, here we skip it and count on later index_build.
* (Or should we add index_build() call after CCI beflow in this case?)
*/
is_gp_relation_node_index = relation->rd_index &&
relation->rd_index->indrelid == GpRelfileNodeRelationId;
if (!isAppendOnly)
{
InsertGpRelfileNodeTuple(
gp_relation_node,
relation->rd_id,
NameStr(relation->rd_rel->relname),
newrelfilenode,
/* segmentFileNum */ 0,
/* updateIndex */ !is_gp_relation_node_index,
&relation->rd_relationnodeinfo.persistentTid,
relation->rd_relationnodeinfo.persistentSerialNum);
}
caql_update_current(pcqCtx, tuple);
/* and Update indexes (implicit) */
heap_freetuple(tuple);
heap_close(pg_class, RowExclusiveLock);
heap_close(gp_relation_node, RowExclusiveLock);
/* Make sure the relfilenode change is visible */
CommandCounterIncrement();
}
/*
* setNewRelfilenode - assign a new relfilenode value to the relation
*
* Caller must already hold exclusive lock on the relation.
*/
Oid
setNewRelfilenode(Relation relation)
{
Oid newrelfilenode;
/* Can't change relfilenode for nailed tables (indexes ok though) */
Assert(!relation->rd_isnailed ||
relation->rd_rel->relkind == RELKIND_INDEX);
/* Can't change for shared tables or indexes */
Assert(!relation->rd_rel->relisshared);
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace,
relation->rd_rel->relisshared,
NULL,
relstorage_is_ao(relation->rd_rel->relstorage));
elog(DEBUG1, "setNewRelfilenode called in EXECUTE mode, "
"newrelfilenode=%d", newrelfilenode);
setNewRelfilenodeCommon(relation, newrelfilenode);
return newrelfilenode;
}
/*
* Greenplum specific routine.
*
* setNewRelfilenodeToOid - assign a new relfilenode value to the relation
*
* Caller must already hold exclusive lock on the relation.
*/
Oid
setNewRelfilenodeToOid(Relation relation, Oid newrelfilenode)
{
/* Can't change relfilenode for nailed tables (indexes ok though) */
Assert(!relation->rd_isnailed ||
relation->rd_rel->relkind == RELKIND_INDEX);
/* Can't change for shared tables or indexes */
Assert(!relation->rd_rel->relisshared);
elog(DEBUG3, "setNewRelfilenodeToOid called. newrelfilenode = %d",
newrelfilenode);
CheckNewRelFileNodeIsOk(newrelfilenode, relation->rd_rel->reltablespace,
relation->rd_rel->relisshared, NULL, relstorage_is_ao(relation->rd_rel->relstorage));
setNewRelfilenodeCommon(relation, newrelfilenode);
return newrelfilenode;
}