blob: 156e04ce876667303e8930058fa5b6b3929e1016 [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.
*/
/*-------------------------------------------------------------------------
*
* tablecmds.c
* Commands for creating and altering table structures and settings
*
* Portions Copyright (c) 2005-2010, Greenplum inc
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.206.2.5 2008/05/09 22:37:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "fmgr.h"
#include <math.h>
#include <fcntl.h>
#include <locale.h>
#include <unistd.h>
#include "access/appendonlywriter.h"
#include "access/bitmap.h"
#include "access/genam.h"
#include "access/hash.h"
#include "access/heapam.h"
#include "access/extprotocol.h"
#include "access/filesplit.h"
#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/xact.h"
#include "access/transam.h"
#include "access/plugstorage.h"
#include "catalog/catalog.h"
#include "catalog/catquery.h"
#include "catalog/dependency.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_attribute_encoding.h"
#include "catalog/pg_compression.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_exttable.h"
#include "catalog/pg_extprotocol.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_resqueue.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_rule.h"
#include "catalog/toasting.h"
#include "cdb/cdbappendonlyam.h"
#include "cdb/cdbparquetam.h"
#include "cdb/cdbpartition.h"
#include "cdb/cdbsharedstorageop.h"
#include "commands/cluster.h"
#include "commands/copy.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "executor/executor.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/print.h"
#include "nodes/relation.h"
#include "nodes/parsenodes.h"
#include "nodes/value.h"
#include "optimizer/clauses.h"
#include "optimizer/plancat.h"
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "parser/analyze.h"
#include "parser/gramparse.h"
#include "parser/parse_agg.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parser.h"
#include "postmaster/identity.h"
#include "rewrite/rewriteHandler.h"
#include "storage/backendid.h"
#include "storage/smgr.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/numeric.h"
#include "utils/relcache.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
#include "utils/uri.h"
#include "utils/formatting.h"
#include "mb/pg_wchar.h"
#include "cdb/cdbdisp.h"
#include "cdb/cdbsrlz.h"
#include "cdb/cdbvars.h"
#include "cdb/cdbcat.h"
#include "cdb/cdbrelsize.h"
#include "cdb/cdboidsync.h"
#include "cdb/cdbsreh.h"
#include "cdb/cdbmirroredfilesysobj.h"
#include "cdb/cdbmirroredbufferpool.h"
#include "cdb/cdbmirroredappendonly.h"
#include "cdb/cdbpersistentfilesysobj.h"
#include "cdb/cdbquerycontextdispatching.h"
#include "cdb/dispatcher.h"
#include "cdb/cdbdispatchresult.h"
#include "optimizer/planmain.h"
/*
* ON COMMIT action list
*/
typedef struct OnCommitItem
{
Oid relid; /* relid of relation */
OnCommitAction oncommit; /* what to do at end of xact */
/*
* If this entry was created during the current transaction,
* creating_subid is the ID of the creating subxact; if created in a prior
* transaction, creating_subid is zero. If deleted during the current
* transaction, deleting_subid is the ID of the deleting subxact; if no
* deletion request is pending, deleting_subid is zero.
*/
SubTransactionId creating_subid;
SubTransactionId deleting_subid;
} OnCommitItem;
static List *on_commits = NIL;
/*
* State information for ALTER TABLE
*
* The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
* structs, one for each table modified by the operation (the named table
* plus any child tables that are affected). We save lists of subcommands
* to apply to this table (possibly modified by parse transformation steps);
* these lists will be executed in Phase 2. If a Phase 3 step is needed,
* necessary information is stored in the constraints and newvals lists.
*
* Phase 2 is divided into multiple passes; subcommands are executed in
* a pass determined by subcommand type.
*/
#define AT_PASS_DROP 0 /* DROP (all flavors) */
#define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */
#define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */
#define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */
#define AT_PASS_COL_ATTRS 4 /* set other column attributes */
/* We could support a RENAME COLUMN pass here, but not currently used */
#define AT_PASS_ADD_COL 5 /* ADD COLUMN */
#define AT_PASS_ADD_INDEX 6 /* ADD indexes */
#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */
#define AT_PASS_MISC 8 /* other stuff */
#define AT_NUM_PASSES 9
typedef struct AlteredTableInfo
{
/* Information saved before any work commences: */
Oid relid; /* Relation to work on */
char relkind; /* Its relkind */
TupleDesc oldDesc; /* Pre-modification tuple descriptor */
/* Information saved by Phase 1 for Phase 2: */
List *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
/* Information saved by Phases 1/2 for Phase 3: */
List *constraints; /* List of NewConstraint */
List *newvals; /* List of NewColumnValue */
bool new_notnull; /* T if we added new NOT NULL constraints */
bool new_dropoids; /* T if we dropped the OID column */
Oid newTableSpace; /* new tablespace; 0 means no change */
Oid exchange_relid; /* for EXCHANGE, the exchanged in rel */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
List *changedIndexOids; /* OIDs of indexes to rebuild */
List *changedIndexDefs; /* string definitions of same */
List *scantable_splits; /* split information for scan */
List *ao_segnos; /* segment file number for the new relation */
} AlteredTableInfo;
/*
* Struct describing one new column value that needs to be computed during
* Phase 3 copy (this could be either a new column with a non-null default, or
* a column that we're changing the type of). Columns without such an entry
* are just copied from the old table during ATRewriteTable. Note that the
* expr is an expression over *old* table values.
*/
typedef struct NewColumnValue
{
AttrNumber attnum; /* which column */
Expr *expr; /* expression to compute */
ExprState *exprstate; /* execution state */
} NewColumnValue;
static void truncate_check_rel(Relation rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, List *inhAttrNameList,
bool is_partition);
static Datum AddDefaultPageRowGroupSize(Datum relOptions, List *options);
static bool add_nonduplicate_cooked_constraint(Constraint *cdef, List *stmtConstraints);
static bool change_varattnos_varno_walker(Node *node, const AttrMapContext *attrMapCxt);
static void StoreCatalogInheritance(Oid relationId, List *supers);
static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
int16 seqNumber, Relation inhRelation,
bool is_partition);
static int findAttrByName(const char *attributeName, List *schema);
static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass);
static void AlterIndexNamespaces(Relation classRel, Relation rel,
Oid oldNspOid, Oid newNspOid);
static void AlterSeqNamespaces(Relation classRel, Relation rel,
Oid oldNspOid, Oid newNspOid,
const char *newNspName);
static int transformColumnNameList(Oid relId, List *colList,
int16 *attnums, Oid *atttypids);
static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
List **attnamelist,
int16 *attnums, Oid *atttypids,
Oid *opclasses);
static void validateForeignKeyConstraint(FkConstraint *fkconstraint,
Relation rel, Relation pkrel);
static void createForeignKeyTriggers(Relation rel, Oid refRelOid,
FkConstraint *fkconstraint, Oid constraintOid);
static char *fkMatchTypeToString(char match_type);
static void ATController(Relation rel, List *cmds, bool recurse,
int * oidInfoCount, TableOidInfo ** oidInfo, List **poidmap);
static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing);
static void ATRewriteCatalogs(List **wqueue);
static void ATAddToastIfNeeded(List **wqueue,
int oidInfoCount,TableOidInfo * oidInfo, List **poidmap);
static void ATExecCmd(List **wqueue,
AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd);
static void ATRewriteTables(List **wqueue,
int oidInfoCount, TableOidInfo * oidInfo, List **poidmap);
static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap);
static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
static void ATSimplePermissions(Relation rel, bool allowView);
static void ATSimplePermissionsRelationOrIndex(Relation rel);
static void ATSimpleRecursion(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse);
/* static void ATOneLevelRecursion(List **wqueue, Relation rel,
AlterTableCmd *cmd); */
static void ATPrepAddColumn(Relation rel, bool recurse, AlterTableCmd *cmd);
static void ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
ColumnDef *colDef);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void ATExecDropNotNull(Relation rel, const char *colName);
static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName);
static void ATExecColumnDefault(Relation rel, const char *colName,
Node *newDefault);
static void ATPrepSetStatistics(Relation rel, const char *colName,
Node *flagValue);
static void ATExecSetStatistics(Relation rel, const char *colName,
Node *newValue);
static void ATExecSetStorage(Relation rel, const char *colName,
Node *newValue);
static void ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
DropBehavior behavior,
bool recurse, bool recursing);
static void ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, bool is_rebuild, bool part_expanded);
static void ATExecAddConstraint(AlteredTableInfo *tab, Relation rel, Node *newConstraint, bool recurse);
static void ATAddCheckConstraint(AlteredTableInfo *tab, Relation rel, Constraint *constr, bool recurse);
static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
FkConstraint *fkconstraint);
static void ATPrepDropConstraint(List **wqueue, Relation rel,
bool recurse, AlterTableCmd *cmd);
static void ATExecDropConstraint(Relation rel, const char *constrName,
DropBehavior behavior, bool quiet);
static void ATPrepAlterColumnType(List **wqueue,
AlteredTableInfo *tab, Relation rel,
bool recurse, bool recursing,
AlterTableCmd *cmd);
static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
const char *colName, TypeName *typname);
static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab);
static void ATPostAlterTypeParse(Oid oldRelId, Oid refRelId, char *cmd,
List **wqueue, Oid constrOid);
static void change_owner_recurse_to_sequences(Oid relationOid,
Oid newOwnerId);
static void ATExecClusterOn(Relation rel, const char *indexName);
static void ATExecDropCluster(Relation rel);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename);
static void ATPartsPrepSetTableSpace(List **wqueue, Relation rel, AlterTableCmd *cmd,
List *oids);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace,
TableOidInfo *oidInfo);
static void ATExecSetRelOptions(Relation rel, List *defList, bool isReset);
static void ATExecEnableDisableTrigger(Relation rel, char *trigname,
bool enable, bool skip_system);
static void ATExecAddInherit(Relation rel, Node *node);
static void ATExecDropInherit(Relation rel, RangeVar *parent, bool is_partition);
static void ATExecSetDistributedBy(Relation rel, Node *node,
AlterTableCmd *cmd);
static void ATPrepExchange(Relation rel, AlterPartitionCmd *pc);
const char* synthetic_sql = "(internally generated SQL command)";
/* ALTER TABLE ... PARTITION */
static void ATPExecPartAdd(AlteredTableInfo *tab,
Relation rel,
AlterPartitionCmd *pc,
AlterTableType att); /* Add */
static void ATPExecPartAlter(List **wqueue, AlteredTableInfo *tab,
Relation rel,
AlterPartitionCmd *pc); /* Alter */
static void ATPExecPartCoalesce(Relation rel,
AlterPartitionCmd *pc); /* Coalesce */
static void ATPExecPartDrop(Relation rel,
AlterPartitionCmd *pc); /* Drop */
static void ATPExecPartExchange(AlteredTableInfo *tab,
Relation rel,
AlterPartitionCmd *pc); /* Exchange */
static void ATPExecPartMerge(Relation rel,
AlterPartitionCmd *pc); /* Merge */
static void ATPExecPartModify(Relation rel,
AlterPartitionCmd *pc); /* Modify */
static void ATPExecPartRename(Relation rel,
AlterPartitionCmd *pc); /* Rename */
static void ATPExecPartSetTemplate(AlteredTableInfo *tab, /* Set */
Relation rel, /* Subpartition */
AlterPartitionCmd *pc); /* Template */
static List *
atpxTruncateList(Relation rel, PartitionNode *pNode);
static void ATPExecPartTruncate(Relation rel,
AlterPartitionCmd *pc); /* Truncate */
static void
copy_buffer_pool_data(
Relation rel,
SMgrRelation dst,
ItemPointer persistentTid,
int64 persistentSerialNum,
bool useWal);
static bool TypeTupleExists(Oid typeId);
static void update_ri_trigger_args(Oid relid,
const char *oldname,
const char *newname,
bool fk_scan,
bool update_relname);
static Datum transformLocationUris(List *locs, List* fmtopts, bool isweb,
bool iswritable, bool forceCreateDir, bool* isCustom);
static Datum transformExecOnClause(List *on_clause, int *preferred_segment_num, bool iswritable);
static char transformFormatType(char *formatname);
static void checkCustomFormatOptString(char **opt,
const char *key,
const char *val,
const bool needopt,
const int nvalidvals,
const char **validvals);
static void checkCustomFormatEncoding(const char *formatterName,
const char *encodingName);
static char *checkCustomFormatOptions(const char *formatterName,
List *formatOpts,
const bool isWritable,
const char *delimiter);
static void checkCustomFormatDateTypes(const char *formatterName,
TupleDesc tupleDesc);
static Datum transformFormatOpts(char formattype,
char *formatname,
char *formattername,
List *formatOpts,
int numcols,
bool iswritable,
GpPolicy *policy);
Oid DefineRelation(CreateStmt *stmt, char relkind, char relstorage, const char *formattername);
static Oid DefineRelation_int(CreateStmt *stmt, char relkind,
char relstorage, const char *formattername);
static void ATExecPartAddInternal(Relation rel, Node *def);
static void
AlterRelationNamespaceInternalTwo(Relation rel,
Oid relid,
Oid oldNspOid, Oid newNspOid,
bool hasDependEntry,
const char *newschema);
static RangeVar *make_temp_table_name(Relation rel, BackendId id);
static bool prebuild_temp_table(Relation rel, RangeVar *tmpname, List *distro,
List *opts, List **hidden_types, bool isTmpTableAo);
static void ATPartitionCheck(AlterTableType subtype, Relation rel, bool rejectroot, bool recursing);
static void InvokeProtocolValidation(Oid procOid, char *procName, bool iswritable, bool forceCreateDir,
List *locs, List* fmtopts);
static char *alterTableCmdString(AlterTableType subtype);
static bool isPgDefaultTablespace(const char *tablespacename);
static Oid LookupCustomProtocolValidatorFunc(char *protoname);
bool
isPgDefaultTablespace(const char *tablespacename){
if(tablespacename == NULL)
return false;
else if(strcmp(tablespacename, "pg_default") == 0)
return true;
else
return false;
}
/* ----------------------------------------------------------------
* DefineRelation
* Creates a new relation.
*
* If successful, returns the OID of the new relation.
* ----------------------------------------------------------------
*/
Oid
DefineRelation(CreateStmt *stmt, char relkind, char relstorage, const char *formattername)
{
Oid reloid = 0;
Assert(stmt->base.relation->schemaname == NULL || strlen(stmt->base.relation->schemaname)>0);
/* forbid create non-system table on tablespace pg_default */
if((!IsBootstrapProcessingMode()) && (isPgDefaultTablespace(stmt->base.tablespacename)))
{
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("Creating table on tablespace 'pg_default' is not allowed")));
}
reloid = DefineRelation_int(stmt, relkind, relstorage, formattername);
return reloid;
}
Oid
DefineRelation_int(CreateStmt *stmt,
char relkind,
char relstorage,
const char *formattername)
{
char relname[NAMEDATALEN];
Oid namespaceId;
List *schema = stmt->base.tableElts;
Oid relationId = InvalidOid;
Oid tablespaceId;
Relation rel;
TupleDesc descriptor;
List *inheritOids;
List *old_constraints;
bool localHasOids;
int parentOidCount;
List *rawDefaults;
Datum reloptions;
ListCell *listptr;
AttrNumber attnum;
bool isPartitioned;
ItemPointerData persistentTid;
int64 persistentSerialNum;
TidycatOptions *tidycatOptions = NULL;
/*
* Truncate relname to appropriate length (probably a waste of time, as
* parser should have done this already).
*/
StrNCpy(relname, stmt->base.relation->relname, NAMEDATALEN);
/*
* Check consistency of arguments
*/
if (stmt->base.oncommit != ONCOMMIT_NOOP && !stmt->base.relation->istemp)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables"),
errOmitLocation(true)));
/*
* Look up the namespace in which we are supposed to create the relation.
* Check we have permission to create there. Skip check if bootstrapping,
* since permissions machinery may not be working yet.
*/
namespaceId = RangeVarGetCreationNamespace(stmt->base.relation);
if (!IsBootstrapProcessingMode())
{
AclResult aclresult;
aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(),
ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
get_namespace_name(namespaceId));
}
/*
* Select tablespace to use. If not specified, use default_tablespace
* (which may in turn default to database's default).
*
* Note: This code duplicates code in indexcmds.c
*/
if (relkind == RELKIND_SEQUENCE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_COMPOSITE_TYPE ||
(relkind == RELKIND_RELATION && (
relstorage == RELSTORAGE_EXTERNAL ||
relstorage == RELSTORAGE_FOREIGN ||
relstorage == RELSTORAGE_VIRTUAL)))
{
/*
* MPP-8262: unable to create sequence with default_tablespace
*
* These relkinds have no storage, and thus do not support tablespaces.
* We shouldn't go through the regular default case for these because we
* don't want to pick up the value from the default_tablespace guc.
*/
Assert(!stmt->base.tablespacename);
tablespaceId = InvalidOid;
}
else if (stmt->base.tablespacename)
{
/*
* Tablespace specified on the command line, or was passed down by
* dispatch.
*/
tablespaceId = get_tablespace_oid(stmt->base.tablespacename);
if (!OidIsValid(tablespaceId))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("tablespace \"%s\" does not exist",
stmt->base.tablespacename),
errOmitLocation(true)));
}
else
{
/*
* Get the default tablespace specified via default_tablespace, or fall
* back on the database tablespace.
*/
tablespaceId = (gp_upgrade_mode) ? DEFAULTTABLESPACE_OID : GetDefaultTablespace();
/* Need the real tablespace id for dispatch */
if (!OidIsValid(tablespaceId))
tablespaceId = get_database_dts(MyDatabaseId);
}
/* Goh tablespace check. */
/*
* temporaty disable this check for dispatching matedata test.
* TODO
CheckCrossAccessTablespace(tablespaceId);
*/
/*
* Parse and validate reloptions, if any.
*/
reloptions = transformRelOptions((Datum) 0, stmt->base.options, true, false);
/*
* Accept and only accept tidycat option during upgrade.
*
* All other storage option will be discarded during upgrade.
* During bootstrap, we don't have any storage option. So, during
* upgrade, we don't need it as well because we're just creating
* catalog objects. Further, we overload the WITH clause to pass-in
* the index oid. So, if we don't strip it out, it'll appear in
* the pg_class.reloptions, and we don't want that.
*
*/
if (gp_upgrade_mode)
{
tidycatOptions = tidycat_reloptions(reloptions);
reloptions = 0;
}
/* Check permissions except when using database's default */
if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace &&
tablespaceId != get_database_dts(MyDatabaseId))
{
AclResult aclresult;
aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(),
ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_TABLESPACE,
get_tablespace_name(tablespaceId));
}
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes. Update the offsets of the distribution attributes
* in GpPolicy if necessary.
*/
isPartitioned = stmt->base.partitionBy ? true : false;
schema = MergeAttributes(schema, stmt->base.inhRelations,
stmt->base.relation->istemp, isPartitioned,
&inheritOids, &old_constraints, &parentOidCount, stmt->policy);
/*
* If a partition table, and user not specify pagesize and rowgroupsize, specify the default
* pagesize to 1MB, rowgroupsize to 8MB.
*/
if((stmt->base.partitionBy) || (stmt->base.is_part_child)){
reloptions = AddDefaultPageRowGroupSize(reloptions, stmt->base.options);
}
/*
* Create a relation descriptor from the relation schema and create the
* relation. Note that in this stage only inherited (pre-cooked) defaults
* and constraints will be included into the new relation.
* (BuildDescForRelation takes care of the inherited defaults, but we have
* to copy inherited constraints here.)
*/
descriptor = BuildDescForRelation(schema);
localHasOids = interpretOidsOption(stmt->base.options);
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
* Check supported data types for pluggable format, i.e., orc
* Need to remove this check if all data types are supported for orc format.
*/
if ((relkind == RELKIND_RELATION) && (relstorage == RELSTORAGE_EXTERNAL) &&
formattername && pg_strncasecmp(formattername, "text", strlen("text")) &&
pg_strncasecmp(formattername, "csv", strlen("csv")))
{
checkCustomFormatDateTypes(formattername, descriptor);
}
/*
* old_constraints: pre-cooked constraints from CREATE TABLE ... INHERIT ...
* stmt->constraints: might have some pre-cooked constraints passed by analyze.c,
* due to LIKE tab INCLUDING CONSTRAINTS
*
* We no longer add these cooked constraints during heap_create_with_catalog.
* Instead, we add them along with raw constraints so that we can specify
* pg_constraint oid to use on segments.
*/
if (old_constraints)
{
/* deal with constraints from MergeAttributes */
foreach(listptr, old_constraints)
{
Constraint *cdef = (Constraint *) lfirst(listptr);
if (cdef->contype == CONSTR_CHECK &&
add_nonduplicate_cooked_constraint(cdef, stmt->base.constraints))
stmt->base.constraints = lappend(stmt->base.constraints, cdef);
}
}
stmt->oidInfo.toastOid = InvalidOid;
stmt->oidInfo.toastIndexOid = InvalidOid;
stmt->oidInfo.aosegOid = InvalidOid;
stmt->oidInfo.aosegIndexOid = InvalidOid;
stmt->oidInfo.aoblkdirOid = InvalidOid;
stmt->oidInfo.aoblkdirIndexOid = InvalidOid;
stmt->ownerid = GetUserId();
if(gp_upgrade_mode && tidycatOptions && (tidycatOptions->relid != InvalidOid))
{
stmt->oidInfo.relOid = tidycatOptions->relid;
stmt->oidInfo.comptypeOid = tidycatOptions->reltype_oid;
stmt->oidInfo.toastOid = tidycatOptions->toast_oid;
stmt->oidInfo.toastIndexOid = tidycatOptions->toast_index;
stmt->oidInfo.toastComptypeOid = tidycatOptions->toast_reltype;
}
/* MPP-8405: disallow OIDS on partitioned tables */
if ((stmt->base.partitionBy ||
stmt->base.is_part_child) &&
descriptor->tdhasoid &&
IsNormalProcessingMode() &&
(Gp_role == GP_ROLE_DISPATCH))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(
"OIDS=TRUE is not allowed on partitioned tables. "
"Use OIDS=FALSE"
),
errOmitLocation(true)));
if (stmt->oidInfo.relOid)
elog(DEBUG4, "DefineRelation relOid=%d schemaname=%s ",
stmt->oidInfo.relOid,
stmt->base.relation->schemaname ? stmt->base.relation->schemaname : "");
relationId = heap_create_with_catalog(relname,
namespaceId,
tablespaceId,
stmt->oidInfo.relOid,
stmt->ownerid,
descriptor,
/* relam */ InvalidOid,
relkind,
relstorage,
tablespaceId==GLOBALTABLESPACE_OID,
localHasOids,
/* bufferPoolBulkLoad */ false,
parentOidCount,
stmt->base.oncommit,
stmt->policy, /*CDB*/
reloptions,
allowSystemTableModsDDL,
&stmt->oidInfo.comptypeOid,
&persistentTid,
&persistentSerialNum);
StoreCatalogInheritance(relationId, inheritOids);
/*
* We must bump the command counter to make the newly-created relation
* tuple visible for opening.
*/
CommandCounterIncrement();
/*
* Open the new relation and acquire exclusive lock on it, unless we're
* doing partition.
*
* This isn't even necessary in the case of normal relations since other
* backends can't see the new rel anyway until we commit), but it keeps
* the lock manager from complaining about deadlock risks.
*/
if (stmt->base.is_part_child)
rel = relation_open(relationId, NoLock);
else
rel = relation_open(relationId, AccessExclusiveLock);
/*
* For some reason, even though we have bumped the command counter above,
* occasionally we are not able to see the persistent info just stored in gp_relation_node
* at the XLOG level. So, we save the values here for debugging purposes.
*/
rel->rd_haveCreateDebugInfo = true;
rel->rd_createDebugIsZeroTid = PersistentStore_IsZeroTid(&persistentTid);
rel->rd_createDebugPersistentTid = persistentTid;
rel->rd_createDebugPersistentSerialNum = persistentSerialNum;
/*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
* parsetrees; we need to transform them to executable expression trees
* before they can be added. The most convenient way to do that is to
* apply the parser's transformExpr routine, but transformExpr doesn't
* work unless we have a pre-existing relation. So, the transformation has
* to be postponed to this final step of CREATE TABLE.
*
* First, scan schema to find new column defaults.
*/
rawDefaults = NIL;
attnum = 0;
foreach(listptr, schema)
{
ColumnDef *colDef = lfirst(listptr);
attnum++;
if (colDef->raw_default != NULL)
{
RawColumnDefault *rawEnt;
Assert(colDef->cooked_default == NULL);
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
rawEnt->attnum = attnum;
rawEnt->raw_default = colDef->raw_default;
rawDefaults = lappend(rawDefaults, rawEnt);
}
}
/*
* Parse and add the defaults/constraints, if any.
*/
if (rawDefaults || stmt->base.constraints)
AddRelationConstraints(rel, rawDefaults, stmt->base.constraints);
if (stmt->attr_encodings)
AddRelationAttributeEncodings(rel, stmt->attr_encodings);
/*
* Clean up. We keep lock on new relation (although it shouldn't be
* visible to anyone else anyway, until commit).
*/
relation_close(rel, NoLock);
return relationId;
}
/*
* Add Default page size and rowgroup size to relation options
*/
static Datum AddDefaultPageRowGroupSize(Datum relOptions, List *defList){
Datum result = 0;
ListCell *cell = NULL;
bool pageSizeSet = false;
bool rowgroupSizeSet = false;
bool parquetTable = false;
bool need_free_arg = false;
if(defList == NIL)
return relOptions;
foreach(cell, defList)
{
DefElem *def = lfirst(cell);
if(pg_strcasecmp("pagesize", def->defname) == 0)
{
pageSizeSet = true;
}
if(pg_strcasecmp("rowgroupsize", def->defname) == 0)
{
rowgroupSizeSet = true;
}
if(pg_strcasecmp("orientation", def->defname) == 0)
{
if(def->arg == NULL)
{
insist_log(false, "syntax not correct, orientation should has corresponding value");
}
if(pg_strcasecmp("parquet", defGetString(def, &need_free_arg)) == 0)
{
parquetTable = true;
}
}
}
if (!parquetTable)
return relOptions;
if ((pageSizeSet == true) && (rowgroupSizeSet == true)){
return relOptions;
}
else
{
/*set default page size*/
ArrayBuildState *astate = NULL;
if(DatumGetPointer(relOptions) != 0){
ArrayType *array = DatumGetArrayTypeP(relOptions);
Datum *options;
int noptions;
int i;
Assert(ARR_ELEMTYPE(array) == TEXTOID);
deconstruct_array(array, TEXTOID, -1, false, 'i',
&options, NULL, &noptions);
/* copy the existing rel options*/
for (i = 0; i < noptions; i++)
{
astate = accumArrayResult(astate, options[i],
false, TEXTOID,
CurrentMemoryContext);
}
}
/* append page size*/
if (pageSizeSet == false){
text *t;
const char *name = "pagesize";
Size len;
char value[INT32_CHAR_SIZE];
pg_ltoa(DEFAULT_PARQUET_PAGE_SIZE_PARTITION, value);
len = VARHDRSZ + strlen(name) + 1 + strlen(value);
t = (text *) palloc(len + 1);
SET_VARSIZE(t, len);
sprintf(VARDATA(t), "%s=%s", name, value);
astate = accumArrayResult(astate, PointerGetDatum(t),
false, TEXTOID,
CurrentMemoryContext);
}
if(rowgroupSizeSet == false){
text *t;
const char *name = "rowgroupsize";
char value[INT32_CHAR_SIZE];
Size len;
pg_ltoa(DEFAULT_PARQUET_ROWGROUP_SIZE_PARTITION, value);
len = VARHDRSZ + strlen(name) + 1 + strlen(value);
t = (text *) palloc(len + 1);
SET_VARSIZE(t, len);
sprintf(VARDATA(t), "%s=%s", name, value);
astate = accumArrayResult(astate, PointerGetDatum(t),
false, TEXTOID,
CurrentMemoryContext);
}
if (astate)
result = makeArrayResult(astate, CurrentMemoryContext);
return result;
}
}
/* ----------------------------------------------------------------
* DefineExternalRelation
* Creates a new external relation.
*
* In here we first dispatch a normal DefineRelation() (with relstorage
* external) in order to create the external relation entries in pg_class
* pg_type etc. Then once this is done we dispatch ourselves (DefineExternalRelation)
* in order to create the pg_exttable entry across the gp array and we
* also record a dependency with the error table, if one exists.
*
* Why don't we just do all of this in one dispatch run? because that
* involves duplicating the DefineRelation() code or severely modifying it
* to have special cases for external tables. IMHO it's better and cleaner
* to leave it intact and do another dispatch.
* ----------------------------------------------------------------
*/
extern void
DefineExternalRelation(CreateExternalStmt *createExtStmt)
{
/*volatile struct CdbDispatcherState ds = {NULL, NULL};*/
CreateStmt *createStmt = makeNode(CreateStmt);
ExtTableTypeDesc *exttypeDesc = (ExtTableTypeDesc *)createExtStmt->exttypedesc;
SingleRowErrorDesc *singlerowerrorDesc = NULL;
DefElem *dencoding = NULL;
ListCell *option;
Oid reloid = 0;
Oid fmtErrTblOid = InvalidOid;
Datum formatOptStr;
Datum locationUris = 0;
Datum locationExec = 0;
char* commandString = NULL;
char rejectlimittype = '\0';
char formattype;
char* formattername = NULL;
int rejectlimit = -1;
int encoding = -1;
int preferred_segment_num = -1;
bool issreh = false; /* is single row error handling requested? */
bool isexternal = createExtStmt->isexternal;
bool iswritable = createExtStmt->iswritable;
bool isweb = createExtStmt->isweb;
bool forceCreateDir = createExtStmt->forceCreateDir;
bool isExternalHdfs = false;
char* location = NULL;
int location_len = NULL;
/*
* now set the parameters for keys/inheritance etc. Most of these are
* uninteresting for external relations...
*/
createStmt->base = createExtStmt->base;
// external table options is not compatible with internal table
// set NIL here
createStmt->base.options = NIL;
createStmt->policy = createExtStmt->policy; /* policy was set in transform */
/*
* Recognize formatter option if there are some tokens found in parser.
* This design is to give CREATE EXTERNAL TABLE DDL the flexiblity to
* support user defined external formatter options.
*/
recognizeExternalRelationFormatterOptions(createExtStmt);
/*
* Get tablespace, database, schema for the relation
*/
RangeVar *rel = createExtStmt->base.relation;
// get tablespace name for the relation
Oid tablespace_id = (gp_upgrade_mode) ? DEFAULTTABLESPACE_OID : GetDefaultTablespace();
if (!OidIsValid(tablespace_id))
{
tablespace_id = get_database_dts(MyDatabaseId);
}
char *tablespace_name = get_tablespace_name(tablespace_id);
// get database name for the relation
char *database_name = rel->catalogname ? rel->catalogname : get_database_name(MyDatabaseId);
// get schema name for the relation
char *schema_name = get_namespace_name(RangeVarGetCreationNamespace(rel));
// get table name for the relation
char *table_name = rel->relname;
/*
* Do some special logic when we use custom
*/
if (exttypeDesc->exttabletype == EXTTBL_TYPE_LOCATION)
{
if (exttypeDesc->location_list == NIL)
{
if (dfs_url == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Cannot create table on HDFS when the service is not available"),
errhint("Check HDFS service and hawq_dfs_url configuration"),
errOmitLocation(true)));
}
location_len = strlen(PROTOCOL_HDFS) + /* hdfs:// */
strlen(dfs_url) + /* hawq_dfs_url */
// 1 + strlen(filespace_name) + /* '/' + filespace name */
1 + strlen(tablespace_name) + /* '/' + tablespace name */
1 + strlen(database_name) + /* '/' + database name */
1 + strlen(schema_name) + /* '/' + schema name */
1 + strlen(table_name) + 1; /* '/' + table name + '\0' */
char *path;
if (createExtStmt->parentPath == NULL) {
path = (char *)palloc(sizeof(char) * location_len);
sprintf(path, "%s%s/%s/%s/%s/%s",
PROTOCOL_HDFS,
dfs_url, /* filespace_name, */ tablespace_name,
database_name, schema_name, table_name);
}
else {
path = (char *)palloc(sizeof(char) *
(location_len + strlen(createExtStmt->parentPath)+1));
sprintf(path, "%s%s/%s/%s/%s/%s/%s",
PROTOCOL_HDFS,
dfs_url, /* filespace_name, */ tablespace_name,
database_name, schema_name,
createExtStmt->parentPath, table_name);
}
exttypeDesc->location_list = list_make1((Node *) makeString(path));
}
/* Check the location to extract protocol */
ListCell *first_uri = list_head(exttypeDesc->location_list);
Value *v = lfirst(first_uri);
char *uri_str = pstrdup(v->val.str);
Uri *uri = ParseExternalTableUri(uri_str);
bool isHdfs = is_hdfs_protocol(uri);
pfree(uri_str);
FreeExternalTableUri(uri);
if (isHdfs)
{
/* We have an HDFS external protocol */
isExternalHdfs = true;
}
}
if (isExternalHdfs)
{
if (list_length(exttypeDesc->location_list)!= 1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Only support 1 external HDFS location. "
"Now input %d location(s)",
list_length(exttypeDesc->location_list))));
}
if (isExternalHdfs)
{
/*
* We force any specified formatter to be custom.
*/
if (strcmp(createExtStmt->format, "custom") != 0)
{
/* There should be no "formatter" option which we will add now */
ListCell *cell = NULL;
foreach(cell, createExtStmt->base.options)
{
DefElem *de = (DefElem *)lfirst(cell);
if (strcmp(de->defname, "formatter") == 0)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Invalid FORMATTER option. "
"Should not specify formatter option")));
}
}
formattername = str_tolower(createExtStmt->format,
strlen(createExtStmt->format));
createExtStmt->base.options =
lappend(createExtStmt->base.options,
makeDefElem("formatter",
(Node *)makeString(pstrdup(formattername))));
pfree(createExtStmt->format);
createExtStmt->format = pstrdup("custom");
}
/*
* Add table category (EXTERNAL or INTERNAL) in format options
*/
const char *kind = createExtStmt->isexternal ? "external" : "internal";
createExtStmt->base.options =
lappend(createExtStmt->base.options,
makeDefElem("category", (Node *)makeString(kind)));
}
bool isCustom = false;
switch(exttypeDesc->exttabletype)
{
case EXTTBL_TYPE_LOCATION:
/* Parse and validate URI strings (LOCATION clause) */
locationUris = transformLocationUris(exttypeDesc->location_list,
createExtStmt->base.options,
isweb, iswritable, forceCreateDir, &isCustom);
if(!isCustom){
int locLength = list_length(exttypeDesc->location_list);
if (createStmt->policy && locLength >= 0 && locLength > createStmt->policy->bucketnum)
{
createStmt->policy->bucketnum = locLength;
}
}
break;
case EXTTBL_TYPE_EXECUTE:
locationExec = transformExecOnClause(exttypeDesc->on_clause, &preferred_segment_num, iswritable);
if (createStmt->policy)
{
createStmt->policy->bucketnum = preferred_segment_num;
}
commandString = exttypeDesc->command_string;
if(strlen(commandString) == 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Invalid EXECUTE clause. Found an empty command string"),
errOmitLocation(true)));
break;
default:
ereport(ERROR,
(errcode(ERRCODE_GP_INTERNAL_ERROR),
errmsg("Internal error: unknown external table type"),
errOmitLocation(true)));
}
/*
* check permissions to create this external table.
*
* - Always allow if superuser.
* - Never allow EXECUTE or 'file' exttables if not superuser.
* - Allow http, gpfdist or gpfdists tables if pg_auth has the right permissions
* for this role and for this type of table, or if gp_external_grant_privileges
* is on (gp_external_grant_privileges should be deprecated at some point).
*/
if(!superuser() && Gp_role == GP_ROLE_DISPATCH)
{
if(exttypeDesc->exttabletype == EXTTBL_TYPE_EXECUTE)
{
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create an EXECUTE external web table"),
errOmitLocation(true)));
}
else
{
ListCell *first_uri;
Value *v;
char *uri_str;
Uri *uri;
if(exttypeDesc->exttabletype == EXTTBL_TYPE_LOCATION)
{
first_uri = list_head(exttypeDesc->location_list);
v = lfirst(first_uri);
uri_str = pstrdup(v->val.str);
uri = ParseExternalTableUri(uri_str);
}
if(uri->protocol == URI_FILE)
{
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create an external table with a file protocol"),
errOmitLocation(true)));
}
else if(!gp_external_grant_privileges)
{
/*
* Check if this role has the proper 'gpfdist', 'gpfdists' or 'http'
* permissions in pg_auth for creating this table.
*/
bool isnull;
Oid userid = GetUserId();
cqContext *pcqCtx;
HeapTuple tuple;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_authid "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(userid)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist (in DefineExternalRelation)",
GetUserNameFromId(userid))));
if ( (uri->protocol == URI_GPFDIST || uri->protocol == URI_GPFDISTS) && iswritable)
{
Datum d_wextgpfd = caql_getattr(pcqCtx, Anum_pg_authid_rolcreatewextgpfd,
&isnull);
bool createwextgpfd = (isnull ? false : DatumGetBool(d_wextgpfd));
if (!createwextgpfd)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: no privilege to create a writable gpfdist(s) external table"),
errOmitLocation(true)));
}
else if ( (uri->protocol == URI_GPFDIST || uri->protocol == URI_GPFDISTS) && !iswritable)
{
Datum d_rextgpfd = caql_getattr(pcqCtx, Anum_pg_authid_rolcreaterextgpfd,
&isnull);
bool createrextgpfd = (isnull ? false : DatumGetBool(d_rextgpfd));
if (!createrextgpfd)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: no privilege to create a readable gpfdist(s) external table"),
errOmitLocation(true)));
}
else if (uri->protocol == URI_HTTP && !iswritable)
{
Datum d_exthttp = caql_getattr(pcqCtx, Anum_pg_authid_rolcreaterexthttp,
&isnull);
bool createrexthttp = (isnull ? false : DatumGetBool(d_exthttp));
if (!createrexthttp)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: no privilege to create an http external table"),
errOmitLocation(true)));
}
else if (uri->protocol == URI_CUSTOM)
{
Oid ownerId = GetUserId();
char* protname = uri->customprotocol;
Oid ptcId = LookupExtProtocolOid(protname, false);
AclResult aclresult;
/* Check we have the right permissions on this protocol */
if (!pg_extprotocol_ownercheck(ptcId, ownerId))
{
AclMode mode = (iswritable ? ACL_INSERT : ACL_SELECT);
aclresult = pg_extprotocol_aclcheck(ptcId, ownerId, mode);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_EXTPROTOCOL, protname);
}
}
/* magma follow the same ack check as HDFS */
else if (uri->protocol == URI_HDFS )
{
Datum d_wexthdfs = caql_getattr(pcqCtx, Anum_pg_authid_rolcreatewexthdfs, &isnull);
bool createwexthdfs = (isnull ? false : DatumGetBool(d_wexthdfs));
if (iswritable) {
if (!createwexthdfs)
{
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: no privilege to create a writable hdfs external table"),
errOmitLocation(true)));
}
}
else
{
/* A role that can create writable external hdfs table can always
* create readable external hdfs table.*/
Datum d_rexthdfs = caql_getattr(pcqCtx, Anum_pg_authid_rolcreaterexthdfs, &isnull);
bool createrexthdfs = (isnull ? false : DatumGetBool(d_rexthdfs));
if (!createrexthdfs && !createwexthdfs)
{
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: no privilege to create a readable hdfs external table"),
errOmitLocation(true)));
}
}
}
else
{
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("internal error in DefineExternalRelation. "
"protocol is %d, writable is %d",
uri->protocol, iswritable)));
}
caql_endscan(pcqCtx);
}
FreeExternalTableUri(uri);
pfree(uri_str);
}
}
/*
* Parse single row error handling info if available
*/
singlerowerrorDesc = (SingleRowErrorDesc *)createExtStmt->sreh;
if(singlerowerrorDesc)
{
issreh = true;
/* get reject limit, and reject limit type */
rejectlimit = singlerowerrorDesc->rejectlimit;
rejectlimittype = (singlerowerrorDesc->is_limit_in_rows ? 'r' : 'p');
VerifyRejectLimit(rejectlimittype, rejectlimit);
/* KEEP only allowed for COPY */
if(singlerowerrorDesc->is_keep)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("KEEP is not supported in the external table definition. "
"Error table will be dropped when its external table is "
"dropped."),
errOmitLocation(true)));
if(singlerowerrorDesc->errtable)
fmtErrTblOid = RangeVarGetRelid(singlerowerrorDesc->errtable, true, false /*allowHcatalog*/);
else
fmtErrTblOid = InvalidOid; /* no err tbl was requested */
}
/*
* Parse and validate FORMAT clause.
*
* We force formatter as 'custom' if it is external hdfs protocol
*/
formattype = (isExternalHdfs) ?
'b' : transformFormatType(createExtStmt->format);
if ((formattype == 'b') )
{
Oid procOid = InvalidOid;
procOid = LookupPlugStorageValidatorFunc(formattername, "validate_interfaces");
if (OidIsValid(procOid))
{
InvokePlugStorageValidationFormatInterfaces(procOid, formattername);
}
else
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unsupported format '%s' in pluggable storage", formattername),
errhint("make sure format is installed"),
errOmitLocation(true)));
}
}
formatOptStr = transformFormatOpts(formattype,
createExtStmt->format,
formattername,
createExtStmt->base.options,
list_length(createExtStmt->base.tableElts),
iswritable,
createExtStmt->policy);
/*
* Parse external table data encoding
*/
foreach(option, createExtStmt->encoding)
{
DefElem *defel = (DefElem *) lfirst(option);
Assert(strcmp(defel->defname, "encoding") == 0);
if (dencoding)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant ENCODING specification"),
errOmitLocation(true)));
dencoding = defel;
}
if (dencoding && dencoding->arg)
{
const char *encoding_name;
if (IsA(dencoding->arg, Integer))
{
encoding = intVal(dencoding->arg);
encoding_name = pg_encoding_to_char(encoding);
/* custom format */
if (formattername)
{
checkCustomFormatEncoding(formattername, encoding_name);
}
if (strcmp(encoding_name, "") == 0 ||
pg_valid_client_encoding(encoding_name) < 0)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("%d is not a valid encoding code",
encoding),
errOmitLocation(true)));
}
else if (IsA(dencoding->arg, String))
{
encoding_name = strVal(dencoding->arg);
if (formattername)
{
checkCustomFormatEncoding(formattername, encoding_name);
}
/* custom format */
if (pg_valid_client_encoding(encoding_name) < 0)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("%s is not a valid encoding name",
encoding_name),
errOmitLocation(true)));
encoding = pg_char_to_encoding(encoding_name);
}
else
elog(ERROR, "unrecognized node type: %d",
nodeTag(dencoding->arg));
}
/* If encoding is defaulted, use database encoding */
if (encoding < 0)
encoding = pg_get_client_encoding();
/*
* First, create the pg_class and other regular relation catalog entries.
* Under the covers this will dispatch a CREATE TABLE statement to all the
* QEs.
*/
Assert(Gp_role == GP_ROLE_DISPATCH || Gp_role == GP_ROLE_UTILITY);
reloid = DefineRelation(createStmt, RELKIND_RELATION,
RELSTORAGE_EXTERNAL, formattername);
/*
* Now we take care of pg_exttable and dependency with error table (if any).
*/
/*
* get our pg_class external rel OID. If we're the QD we just created
* it above. If we're a QE DefineRelation() was already dispatched to
* us and therefore we have a local entry in pg_class. get the OID
* from cache.
*/
if (Gp_role == GP_ROLE_DISPATCH || Gp_role == GP_ROLE_UTILITY)
Assert(reloid != InvalidOid);
else
reloid = RangeVarGetRelid(createExtStmt->base.relation, true, false /*allowHcatalog*/);
/*
* create a pg_exttable entry for this external table.
*/
InsertExtTableEntry(reloid,
iswritable,
isweb,
issreh,
formattype,
rejectlimittype,
commandString,
rejectlimit,
fmtErrTblOid,
encoding,
formatOptStr,
locationExec,
locationUris);
/*
* record a dependency between the external table and its error table (if one exists)
*/
if(singlerowerrorDesc && singlerowerrorDesc->errtable)
{
ObjectAddress myself,
referenced;
Oid fmtErrTblOid = RangeVarGetRelid(((SingleRowErrorDesc *)createExtStmt->sreh)->errtable, true, false /*allowHcatalog*/);
Assert(fmtErrTblOid != InvalidOid);
myself.classId = RelationRelationId;
myself.objectId = reloid;
myself.objectSubId = 0;
referenced.classId = RelationRelationId;
referenced.objectId = fmtErrTblOid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
/*
* Add primary key constraint in pg_constraint for custom external table.
* We don't add primary key index in pg_index for now.
*/
if (formattername)
{
pfree(formattername);
}
}
extern void
DefineForeignRelation(CreateForeignStmt *createForeignStmt)
{
CreateStmt *createStmt = makeNode(CreateStmt);
Oid reloid = 0;
bool shouldDispatch = (Gp_role == GP_ROLE_DISPATCH &&
IsNormalProcessingMode());
/*
* now set the parameters for keys/inheritance etc. Most of these are
* uninteresting for external relations...
*/
createStmt->base.relation = createForeignStmt->relation;
createStmt->base.tableElts = createForeignStmt->tableElts;
createStmt->base.inhRelations = NIL;
createStmt->base.constraints = NIL;
createStmt->base.options = NIL;
createStmt->base.oncommit = ONCOMMIT_NOOP;
createStmt->base.tablespacename = NULL;
createStmt->policy = NULL; /* for now, we use "master only" type of distribution */
/* (permissions are checked in foreigncmd.c:InsertForeignTableEntry() ) */
/*
* First, create the pg_class and other regular relation catalog entries.
* Under the covers this will dispatch a CREATE TABLE statement to all the
* QEs.
*/
if(Gp_role == GP_ROLE_DISPATCH || Gp_role == GP_ROLE_UTILITY)
reloid = DefineRelation(createStmt, RELKIND_RELATION, RELSTORAGE_FOREIGN, NonCustomFormatType);
/*
* Now we take care of pg_foreign_table
*/
ObjectAddress myself;
ObjectAddress referenced;
Oid ownerId = GetUserId();
ForeignServer *srv = GetForeignServerByName(createForeignStmt->srvname, false);
/*
* get our pg_class foreign rel OID. If we're the QD we just created
* it above. If we're a QE DefineRelation() was already dispatched to
* us and therefore we have a local entry in pg_class. get the OID
* from cache.
*/
if(Gp_role == GP_ROLE_DISPATCH || Gp_role == GP_ROLE_UTILITY)
Assert(reloid != InvalidOid);
else
reloid = RangeVarGetRelid(createForeignStmt->relation, true, false);
/* Add dependency on SERVER and owner */
myself.classId = RelationRelationId;
myself.objectId = reloid;
myself.objectSubId = 0;
referenced.classId = ForeignServerRelationId;
referenced.objectId = srv->serverid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
recordDependencyOnOwner(RelationRelationId, reloid, ownerId);
/* create a pg_exttable entry for this foreign table.*/
InsertForeignTableEntry(reloid,
createForeignStmt->srvname,
createForeignStmt->options);
if (shouldDispatch)
{
/* Dispatch the statement tree to all primary and mirror segdbs.
* Doesn't wait for the QEs to finish execution.
*/
dispatch_statement_node((Node *) createForeignStmt, NULL, NULL, NULL);
}
}
/* ----------------------------------------------------------------
* DefinePartitionedRelation
* Create the rewrite rule for a partitioned table
*
* parse/analyze.c/transformPartitionBy does the bulk of the work for
* partitioned tables, converting a single CREATE TABLE into a series
* of statements to create the child tables for each partition. Each
* child table has a check constraint and a rewrite rule to ensure that
* INSERTs to the parent end up in the correct child table (partition).
* However, we cannot add a RuleStmt for a non-existent table to the
* a(fter)list for the Create statement (believe me, I tried, really
* hard). Thus, we create the "parsed" RuleStmt in analyze, and
* finally parse_analyze it here *after* the relation is created, the
* use process_utility to dispatch.
* ----------------------------------------------------------------
*/
void
DefinePartitionedRelation(CreateStmt *stmt, Oid relOid)
{
if (stmt->base.postCreate)
{
List *pQry;
Node *pUtl;
DestReceiver *dest = None_Receiver;
List *pL1 = (List *)stmt->base.postCreate;
pQry = parse_analyze(linitial(pL1), NULL, NULL, 0);
if (pQry)
{
/* just grab the first guy - "There Can Be Only One"(TM) */
pUtl = linitial(pQry);
if (pUtl)
{
Assert(IsA(pUtl, Query));
Assert(((Query *)pUtl)->commandType == CMD_UTILITY);
ProcessUtility((Node *)(((Query *)pUtl)->utilityStmt),
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
}
}
}
}
/*
* Run deferred statements generated by internal operations around
* partition addition/split.
*/
void
EvaluateDeferredStatements(List *deferredStmts)
{
ListCell *lc;
/***
*** XXX: Fix MPP-13750, however this fails to address the similar bug
*** with ordinary inheritance and partial indexes. When that bug,
*** is fixed, this section should become unnecessary!
***/
foreach( lc, deferredStmts )
{
ListCell *ulc;
List *analyzedStmts = NIL;
DestReceiver *dest = None_Receiver;
Node *dstmt = lfirst(lc);
analyzedStmts = parse_analyze(dstmt, NULL, NULL, 0);
foreach (ulc, analyzedStmts)
{
Query *uquery = NULL;
Node *utilityStmt = lfirst(ulc);
Insist(IsA(utilityStmt, Query));
uquery = (Query*)utilityStmt;
Insist(uquery->commandType == CMD_UTILITY);
ereport(DEBUG1,
(errmsg("processing deferred utility statement")));
ProcessUtility((Node*)uquery->utilityStmt,
synthetic_sql,
NULL,
false,
dest,
NULL);
}
}
}
/* Don't track internal namespaces for toast, bitmap, aoseg */
#define METATRACK_VALIDNAMESPACE(namespaceId) \
(namespaceId != PG_TOAST_NAMESPACE && \
namespaceId != PG_BITMAPINDEX_NAMESPACE && \
namespaceId != PG_AOSEGMENT_NAMESPACE )
/* check for valid namespace and valid relkind */
static
bool
MetaTrackValidKindNsp(Form_pg_class rd_rel)
{
Oid nsp = rd_rel->relnamespace;
if (PG_CATALOG_NAMESPACE == nsp)
{
/* MPP-7773: don't track objects in system namespace
* if modifying system tables (eg during upgrade)
*/
if (allowSystemTableModsDDL)
return (false);
}
/* MPP-7599: watch out for toast indexes */
return (METATRACK_VALIDNAMESPACE(nsp)
&& MetaTrackValidRelkind(rd_rel->relkind)
/* MPP-7572: not valid if in any temporary namespace */
&& (!(isAnyTempNamespace(nsp))));
}
/*
* RemoveRelation
* Deletes a relation.
*/
void
RemoveRelation(const RangeVar *relation, DropBehavior behavior,
DropStmt *stmt, char relkind)
{
Oid relOid;
ObjectAddress object;
HeapTuple tuple;
cqContext *pcqCtx;
AcceptInvalidationMessages();
relOid = RangeVarGetRelid(relation, true, false /*allowHcatalog*/);
/* Not there? */
if (!OidIsValid(relOid))
{
DropErrorMsgNonExistent(relation, relkind, stmt->missing_ok);
return;
}
if (Gp_role == GP_ROLE_DISPATCH)
{
LockRelationOid(RelationRelationId, RowExclusiveLock);
LockRelationOid(TypeRelationId, RowExclusiveLock);
LockRelationOid(DependRelationId, RowExclusiveLock);
/* Get the lock before trying to fetch the pg_class entry */
LockRelationOid(relOid, AccessExclusiveLock);
}
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(relOid)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple))
{
if (Gp_role == GP_ROLE_DISPATCH)
{
Oid again = RangeVarGetRelid(relation, true, false /*allowHcatalog*/);
/* Not there? */
if (!OidIsValid(again))
{
DropErrorMsgNonExistent(relation, relkind, stmt->missing_ok);
UnlockRelationOid(DependRelationId, RowExclusiveLock);
UnlockRelationOid(TypeRelationId, RowExclusiveLock);
UnlockRelationOid(RelationRelationId, RowExclusiveLock);
UnlockRelationOid(relOid, AccessExclusiveLock);
caql_endscan(pcqCtx);
return;
}
}
elog(ERROR, "relation \"%s\" does not exist", relation->relname);
}
/* MPP-3260: disallow direct DROP TABLE of a partition */
if (stmt && rel_is_child_partition(relOid) && !stmt->bAllowPartn)
{
Oid master = rel_partition_get_master(relOid);
char *pretty = rel_get_part_path_pretty(relOid,
" ALTER PARTITION ",
" DROP PARTITION ");
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot drop partition \"%s\" directly",
get_rel_name(relOid)),
errhint("Table \"%s\" is a child partition of table "
"\"%s\". To drop it, use ALTER TABLE \"%s\"%s...",
get_rel_name(relOid), get_rel_name(master),
get_rel_name(master), pretty ? pretty : "" ),
errOmitLocation(true)));
}
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = 0;
caql_endscan(pcqCtx);
/* if we got here then we should proceed. */
performDeletion(&object, behavior);
}
/*
* RelationToRemoveIsTemp
* Checks if an object being targeted for drop is a temporary table.
*/
bool
RelationToRemoveIsTemp(const RangeVar *relation, DropBehavior behavior)
{
Oid relOid;
HeapTuple relTup;
Form_pg_class relForm;
char *nspname;
char *relname;
bool isTemp;
cqContext *pcqCtx;
elog(DEBUG5, "Relation to remove catalogname %s, schemaname %s, relname %s",
(relation->catalogname == NULL ? "<empty>" : relation->catalogname),
(relation->schemaname == NULL ? "<empty>" : relation->schemaname),
(relation->relname == NULL ? "<empty>" : relation->relname));
// UNDONE: Not sure how to interpret 'behavior'...
relOid = RangeVarGetRelid(relation, false, false /*allowHcatalog*/);
/*
* Lock down the object to stablize it before we examine its
* charactistics.
*/
if (Gp_role == GP_ROLE_DISPATCH)
{
LockRelationOid(RelationRelationId, RowExclusiveLock);
LockRelationOid(TypeRelationId, RowExclusiveLock);
LockRelationOid(DependRelationId, RowExclusiveLock);
}
/* Lock the relation to be dropped */
LockRelationOid(relOid, AccessExclusiveLock);
/*
* When we got the relOid lock, it is possible that the relation has gone away.
* this will throw Error if the relation is already deleted.
*/
RangeVarGetRelid(relation, false, false /*allowHcatalog*/);
/* if we got here then we should proceed. */
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 ",
ObjectIdGetDatum(relOid)));
relTup = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(relTup))
elog(ERROR, "cache lookup failed for relation %u", relOid);
relForm = (Form_pg_class) GETSTRUCT(relTup);
/* Qualify the name if not visible in search path */
if (RelationIsVisible(relOid))
nspname = NULL;
else
nspname = get_namespace_name(relForm->relnamespace);
/* XXX XXX: is this all just for debugging? could just be simplified to:
SELECT relnamespace from pg_class
*/
relname = quote_qualified_identifier(nspname, NameStr(relForm->relname));
isTemp = isTempNamespace(relForm->relnamespace);
elog(DEBUG5, "Relation name is %s, namespace %s, isTemp = %s",
relname,
(nspname == NULL ? "<null>" : nspname),
(isTemp ? "true" : "false"));
caql_endscan(pcqCtx);
return isTemp;
}
/*
* ExecuteTruncate
* Executes a TRUNCATE command.
*
* This is a multi-relation truncate. We first open and grab exclusive
* lock on all relations involved, checking permissions and otherwise
* verifying that the relation is OK for truncation. In CASCADE mode,
* relations having FK references to the targeted relations are automatically
* added to the group; in RESTRICT mode, we check that all FK references are
* internal to the group that's being truncated. Finally all the relations
* are truncated and reindexed.
*/
void
ExecuteTruncate(TruncateStmt *stmt)
{
List *rels = NIL;
List *relids = NIL;
List *meta_relids = NIL;
ListCell *cell;
int partcheck = 2;
List *partList = NIL;
/*
* Open, exclusive-lock, and check all the explicitly-specified relations
*
* Check if table has partitions and add them too
*/
while (partcheck)
{
foreach(cell, stmt->relations)
{
RangeVar *rv = lfirst(cell);
Relation rel;
PartitionNode *pNode;
PG_TRY();
{
rel = heap_openrv(rv, AccessExclusiveLock);
}
PG_CATCH();
{
/*
* In the case of the table being dropped concurrently,
* throw a friendlier error than:
*
* "could not open relation with relid 1234"
*/
if (rv->schemaname)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
rv->schemaname, rv->relname),
errOmitLocation(true)));
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
rv->relname),
errOmitLocation(true)));
PG_RE_THROW();
}
PG_END_TRY();
truncate_check_rel(rel);
if (partcheck == 2)
{
pNode = RelationBuildPartitionDesc(rel, false);
if (pNode)
{
List *plist = atpxTruncateList(rel, pNode);
if (plist)
{
if (partList)
partList = list_concat(partList, plist);
else
partList = plist;
}
}
}
heap_close(rel, NoLock);
}
partcheck--;
if (partList)
{
/* add the partitions to the relation list and try again */
if (partcheck == 1)
stmt->relations = list_concat(partList, stmt->relations);
}
else
/* no partitions - no need to try again */
partcheck = 0;
} /* end while partcheck */
/*
* Open, exclusive-lock, and check all the explicitly-specified relations
*/
foreach(cell, stmt->relations)
{
RangeVar *rv = lfirst(cell);
Relation rel;
rel = heap_openrv(rv, AccessExclusiveLock);
truncate_check_rel(rel);
rels = lappend(rels, rel);
relids = lappend_oid(relids, RelationGetRelid(rel));
if (MetaTrackValidKindNsp(rel->rd_rel))
meta_relids = lappend_oid(meta_relids, RelationGetRelid(rel));
}
/*
* In CASCADE mode, suck in all referencing relations as well. This
* requires multiple iterations to find indirectly-dependent relations. At
* each phase, we need to exclusive-lock new rels before looking for their
* dependencies, else we might miss something. Also, we check each rel as
* soon as we open it, to avoid a faux pas such as holding lock for a long
* time on a rel we have no permissions for.
*/
if (stmt->behavior == DROP_CASCADE)
{
for (;;)
{
List *newrelids;
newrelids = heap_truncate_find_FKs(relids);
if (newrelids == NIL)
break; /* nothing else to add */
foreach(cell, newrelids)
{
Oid relid = lfirst_oid(cell);
Relation rel;
rel = heap_open(relid, AccessExclusiveLock);
ereport(NOTICE,
(errmsg("truncate cascades to table \"%s\"",
RelationGetRelationName(rel)),
errOmitLocation(true)));
truncate_check_rel(rel);
rels = lappend(rels, rel);
relids = lappend_oid(relids, relid);
if (MetaTrackValidKindNsp(rel->rd_rel))
meta_relids = lappend_oid(meta_relids,
RelationGetRelid(rel));
}
}
}
/*
* Check foreign key references. In CASCADE mode, this should be
* unnecessary since we just pulled in all the references; but as a
* cross-check, do it anyway if in an Assert-enabled build.
*/
#ifdef USE_ASSERT_CHECKING
heap_truncate_check_FKs(rels, false);
#else
if (stmt->behavior == DROP_RESTRICT)
heap_truncate_check_FKs(rels, false);
#endif
/*
* OK, truncate each table.
*/
foreach(cell, rels)
{
Relation rel = (Relation) lfirst(cell);
Oid heap_relid;
Oid toast_relid;
Oid aoseg_relid = InvalidOid;
Oid aoblkdir_relid = InvalidOid;
List *indoids = NIL;
/*
* Create a new empty storage file for the relation, and assign it
* as the relfilenode value. The old storage file is scheduled for
* deletion at commit.
*/
setNewRelfilenode(rel);
heap_relid = RelationGetRelid(rel);
toast_relid = rel->rd_rel->reltoastrelid;
heap_close(rel, NoLock);
if (RelationIsAoRows(rel) || RelationIsParquet(rel)){
GetAppendOnlyEntryAuxOids(heap_relid, SnapshotNow,
&aoseg_relid, NULL,
&aoblkdir_relid, NULL);
AORelRemoveHashEntryOnCommit(RelationGetRelid(rel));
}
else{
ereport(ERROR,
( errcode(ERRCODE_GP_COMMAND_ERROR),
errmsg("TRUNCATE on heap table is not supported.")));
}
/*
* The same for the toast table, if any.
*/
if (OidIsValid(toast_relid))
{
rel = relation_open(toast_relid, AccessExclusiveLock);
setNewRelfilenode(rel);
heap_close(rel, NoLock);
}
/*
* The same for the aoseg table, if any.
*/
if (OidIsValid(aoseg_relid))
{
rel = relation_open(aoseg_relid, AccessExclusiveLock);
setNewRelfilenode(rel);
heap_close(rel, NoLock);
}
if (OidIsValid(aoblkdir_relid))
{
rel = relation_open(aoblkdir_relid, AccessExclusiveLock);
setNewRelfilenode(rel);
heap_close(rel, NoLock);
}
/*
* Reconstruct the indexes to match, and we're done.
*/
reindex_relation(heap_relid, true, true, true, &indoids, true);
}
}
/*
* Check that a given rel is safe to truncate. Subroutine for ExecuteTruncate
*/
static void
truncate_check_rel(Relation rel)
{
/* Only allow truncate on regular or append-only tables */
if (rel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
RelationGetRelationName(rel)),
errOmitLocation(true)));
if (RelationIsExternal(rel))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an external relation and can't be truncated",
RelationGetRelationName(rel)),
errOmitLocation(true)));
/* Permissions checks */
if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(rel));
if (!allowSystemTableModsDDL && IsSystemRelation(rel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel)),
errOmitLocation(true)));
/*
* We can never allow truncation of shared or nailed-in-cache relations,
* because we can't support changing their relfilenode values.
*/
if (rel->rd_rel->relisshared || rel->rd_isnailed)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot truncate system relation \"%s\"",
RelationGetRelationName(rel)),
errOmitLocation(true)));
/*
* Don't allow truncate on temp tables of other backends ... their local
* buffer manager is not going to cope.
*/
if (isOtherTempNamespace(RelationGetNamespace(rel)))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot truncate temporary tables of other sessions"),
errOmitLocation(true)));
/*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
*/
CheckTableNotInUse(rel, "TRUNCATE");
}
/*----------
* MergeAttributes
* Returns new schema given initial schema and superclasses.
*
* Input arguments:
* 'schema' is the column/attribute definition for the table. (It's a list
* of ColumnDef's.) It is destructively changed.
* 'supers' is a list of names (as RangeVar nodes) of parent relations.
* 'istemp' is TRUE if we are creating a temp relation.
* 'GpPolicy *' is NULL if the distribution policy is not to be updated
*
* Output arguments:
* 'supOids' receives a list of the OIDs of the parent relations.
* 'supconstr' receives a list of constraints belonging to the parents,
* updated as necessary to be valid for the child.
* 'supOidCount' is set to the number of parents that have OID columns.
* 'GpPolicy' is updated with the offsets of the distribution
* attributes in the new schema
*
* Return value:
* Completed schema list.
*
* Notes:
* The order in which the attributes are inherited is very important.
* Intuitively, the inherited attributes should come first. If a table
* inherits from multiple parents, the order of those attributes are
* according to the order of the parents specified in CREATE TABLE.
*
* Here's an example:
*
* create table person (name text, age int4, location point);
* create table emp (salary int4, manager text) inherits(person);
* create table student (gpa float8) inherits (person);
* create table stud_emp (percent int4) inherits (emp, student);
*
* The order of the attributes of stud_emp is:
*
* person {1:name, 2:age, 3:location}
* / \
* {6:gpa} student emp {4:salary, 5:manager}
* \ /
* stud_emp {7:percent}
*
* If the same attribute name appears multiple times, then it appears
* in the result table in the proper location for its first appearance.
*
* Constraints (including NOT NULL constraints) for the child table
* are the union of all relevant constraints, from both the child schema
* and parent tables.
*
* The default value for a child column is defined as:
* (1) If the child schema specifies a default, that value is used.
* (2) If neither the child nor any parent specifies a default, then
* the column will not have a default.
* (3) If conflicting defaults are inherited from different parents
* (and not overridden by the child), an error is raised.
* (4) Otherwise the inherited default is used.
* Rule (3) is new in Postgres 7.1; in earlier releases you got a
* rather arbitrary choice of which parent default to use.
*----------
*/
List *
MergeAttributes(List *schema, List *supers, bool istemp, bool isPartitioned,
List **supOids, List **supconstr, int *supOidCount, GpPolicy *policy)
{
ListCell *entry;
List *inhSchema = NIL;
List *parentOids = NIL;
List *constraints = NIL;
int parentsWithOids = 0;
bool have_bogus_defaults = false;
char *bogus_marker = "Bogus!"; /* marks conflicting defaults */
int child_attno;
/*
* Check for and reject tables with too many columns. We perform this
* check relatively early for two reasons: (a) we don't run the risk of
* overflowing an AttrNumber in subsequent code (b) an O(n^2) algorithm is
* okay if we're processing <= 1600 columns, but could take minutes to
* execute if the user attempts to create a table with hundreds of
* thousands of columns.
*
* Note that we also need to check that any we do not exceed this figure
* after including columns from inherited relations.
*/
if (list_length(schema) > MaxHeapAttributeNumber)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("tables can have at most %d columns",
MaxHeapAttributeNumber)));
/*
* Check for duplicate names in the explicit list of attributes.
*
* Although we might consider merging such entries in the same way that we
* handle name conflicts for inherited attributes, it seems to make more
* sense to assume such conflicts are errors.
*/
foreach(entry, schema)
{
ColumnDef *coldef = lfirst(entry);
ListCell *rest;
for_each_cell(rest, lnext(entry))
{
ColumnDef *restdef = lfirst(rest);
if (strcmp(coldef->colname, restdef->colname) == 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" duplicated",
coldef->colname),
errOmitLocation(true)));
}
}
/*
* Scan the parents left-to-right, and merge their attributes to form a
* list of inherited attributes (inhSchema). Also check to see if we need
* to inherit an OID column.
*/
child_attno = 0;
foreach(entry, supers)
{
RangeVar *parent = (RangeVar *) lfirst(entry);
Relation relation;
TupleDesc tupleDesc;
TupleConstr *constr;
AttrNumber *newattno;
AttrNumber parent_attno;
relation = heap_openrv(parent, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table",
parent->relname),
errOmitLocation(true)));
/* Permanent rels cannot inherit from temporary ones */
if (!istemp && isTempNamespace(RelationGetNamespace(relation)))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from temporary relation \"%s\"",
parent->relname),
errOmitLocation(true)));
/*
* We should have an UNDER permission flag for this, but for now,
* demand that creator of a child table own the parent.
*/
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
/*
* Reject duplications in the list of parents.
*/
if (list_member_oid(parentOids, RelationGetRelid(relation)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("inherited relation \"%s\" duplicated",
parent->relname),
errOmitLocation(true)));
parentOids = lappend_oid(parentOids, RelationGetRelid(relation));
if (relation->rd_rel->relhasoids)
parentsWithOids++;
tupleDesc = RelationGetDescr(relation);
constr = tupleDesc->constr;
/*
* newattno[] will contain the child-table attribute numbers for the
* attributes of this parent table. (They are not the same for
* parents after the first one, nor if we have dropped columns.)
*/
newattno = (AttrNumber *)
palloc(tupleDesc->natts * sizeof(AttrNumber));
for (parent_attno = 1; parent_attno <= tupleDesc->natts;
parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
char *attributeName = NameStr(attribute->attname);
int exist_attno;
ColumnDef *def;
/*
* Ignore dropped columns in the parent.
*/
if (attribute->attisdropped)
{
/*
* change_varattnos_of_a_node asserts that this is greater
* than zero, so if anything tries to use it, we should find
* out.
*/
newattno[parent_attno - 1] = 0;
continue;
}
/*
* Does it conflict with some previously inherited column?
*/
exist_attno = findAttrByName(attributeName, inhSchema);
if (exist_attno > 0)
{
/*
* Yes, try to merge the two column definitions. They must
* have the same type and typmod.
*/
if (Gp_role == GP_ROLE_EXECUTE)
{
ereport(DEBUG1,
(errmsg("merging multiple inherited definitions of column \"%s\"",
attributeName)));
}
else
ereport(NOTICE,
(errmsg("merging multiple inherited definitions of column \"%s\"",
attributeName)));
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
if (typenameTypeId(NULL, def->typname) != attribute->atttypid ||
def->typname->typmod != attribute->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("inherited column \"%s\" has a type conflict",
attributeName),
errdetail("%s versus %s",
TypeNameToString(def->typname),
format_type_be(attribute->atttypid)),
errOmitLocation(true)));
def->inhcount++;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= attribute->attnotnull;
/* Default and other constraints are handled below */
newattno[parent_attno - 1] = exist_attno;
/*
* Update GpPolicy
*/
if (policy != NULL)
{
int attr_ofst = 0;
Assert(policy->nattrs >= 0 && "the number of distribution attributes is not negative");
/* Iterate over all distribution attribute offsets */
for (attr_ofst = 0; attr_ofst < policy->nattrs; attr_ofst++)
{
/* Check if any distribution attribute has higher offset than the current */
if (policy->attrs[attr_ofst] > child_attno)
{
Assert(policy->attrs[attr_ofst] > 0 && "index should not become negative");
policy->attrs[attr_ofst]--;
}
}
}
}
else
{
/*
* No, create a new inherited column
*/
def = makeNode(ColumnDef);
def->colname = pstrdup(attributeName);
def->typname = makeTypeNameFromOid(attribute->atttypid,
attribute->atttypmod);
def->inhcount = 1;
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->raw_default = NULL;
def->cooked_default = NULL;
def->constraints = NIL;
inhSchema = lappend(inhSchema, def);
newattno[parent_attno - 1] = ++child_attno;
}
/*
* Copy default if any
*/
if (attribute->atthasdef)
{
char *this_default = NULL;
AttrDefault *attrdef;
int i;
/* Find default in constraint structure */
Assert(constr != NULL);
attrdef = constr->defval;
for (i = 0; i < constr->num_defval; i++)
{
if (attrdef[i].adnum == parent_attno)
{
this_default = attrdef[i].adbin;
break;
}
}
Assert(this_default != NULL);
/*
* If default expr could contain any vars, we'd need to fix
* 'em, but it can't; so default is ready to apply to child.
*
* If we already had a default from some prior parent, check
* to see if they are the same. If so, no problem; if not,
* mark the column as having a bogus default. Below, we will
* complain if the bogus default isn't overridden by the child
* schema.
*/
Assert(def->raw_default == NULL);
if (def->cooked_default == NULL)
def->cooked_default = pstrdup(this_default);
else if (strcmp(def->cooked_default, this_default) != 0)
{
def->cooked_default = bogus_marker;
have_bogus_defaults = true;
}
}
}
/*
* Now copy the constraints of this parent, adjusting attnos using the
* completed newattno[] map
*/
if (constr && constr->num_check > 0)
{
ConstrCheck *check = constr->check;
int i;
for (i = 0; i < constr->num_check; i++)
{
Constraint *cdef = makeNode(Constraint);
Node *expr;
cdef->contype = CONSTR_CHECK;
cdef->name = pstrdup(check[i].ccname);
cdef->raw_expr = NULL;
/* adjust varattnos of ccbin here */
expr = stringToNode(check[i].ccbin);
change_varattnos_of_a_node(expr, newattno);
cdef->cooked_expr = nodeToString(expr);
constraints = lappend(constraints, cdef);
}
}
pfree(newattno);
/*
* Close the parent rel, but keep our AccessShareLock on it until xact
* commit. That will prevent someone else from deleting or ALTERing
* the parent before the child is committed.
*/
heap_close(relation, NoLock);
}
/*
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
*/
if (inhSchema != NIL)
{
foreach(entry, schema)
{
ColumnDef *newdef = lfirst(entry);
char *attributeName = newdef->colname;
int exist_attno;
/*
* Does it conflict with some previously inherited column?
*/
exist_attno = findAttrByName(attributeName, inhSchema);
if (exist_attno > 0)
{
ColumnDef *def;
/*
* Yes, try to merge the two column definitions. They must
* have the same type and typmod.
*/
if (Gp_role == GP_ROLE_EXECUTE)
{
ereport(DEBUG1,
(errmsg("merging column \"%s\" with inherited definition",
attributeName)));
}
else
ereport(NOTICE,
(errmsg("merging column \"%s\" with inherited definition",
attributeName)));
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
if (typenameTypeId(NULL, def->typname) != typenameTypeId(NULL, newdef->typname) ||
def->typname->typmod != newdef->typname->typmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" has a type conflict",
attributeName),
errdetail("%s versus %s",
TypeNameToString(def->typname),
TypeNameToString(newdef->typname))));
/* Mark the column as locally defined */
def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
if (newdef->raw_default != NULL)
{
def->raw_default = newdef->raw_default;
def->cooked_default = newdef->cooked_default;
}
}
else
{
/*
* No, attach new column to result schema
*/
inhSchema = lappend(inhSchema, newdef);
}
}
schema = inhSchema;
/*
* Check that we haven't exceeded the legal # of columns after merging
* in inherited columns.
*/
if (list_length(schema) > MaxHeapAttributeNumber)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("tables can have at most %d columns",
MaxHeapAttributeNumber)));
}
/*
* If we found any conflicting parent default values, check to make sure
* they were overridden by the child.
*/
if (have_bogus_defaults)
{
foreach(entry, schema)
{
ColumnDef *def = lfirst(entry);
if (def->cooked_default == bogus_marker)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
errmsg("column \"%s\" inherits conflicting default values",
def->colname),
errhint("To resolve the conflict, specify a default explicitly.")));
}
}
*supOids = parentOids;
*supconstr = constraints;
*supOidCount = parentsWithOids;
return schema;
}
/*
* In multiple-inheritance situations, it's possible to inherit
* the same grandparent constraint through multiple parents.
* Hence, we want to discard inherited constraints that match as to
* both name and expression. Otherwise, gripe if there are conflicting
* names. Nonconflicting constraints are added back to the CreateStmt->constraints
*/
static bool
add_nonduplicate_cooked_constraint(Constraint *cdef, List *stmtConstraints)
{
/* Should only see precooked constraints here */
Assert(cdef->contype == CONSTR_CHECK);
Assert(cdef->name != NULL);
Assert(cdef->raw_expr == NULL && cdef->cooked_expr != NULL);
ListCell *listptr;
foreach(listptr, stmtConstraints)
{
Constraint *stmtCdef = (Constraint *) lfirst(listptr);
/* Constraint in CreateStmt may be raw and maynot be a check constraint */
if (stmtCdef->cooked_expr == NULL || stmtCdef->contype != CONSTR_CHECK)
continue;
if (strcmp(stmtCdef->name, cdef->name) != 0)
continue;
if (strcmp(stmtCdef->cooked_expr, cdef->cooked_expr) == 0)
return false;
}
return true;
}
/*
* Replace varattno values in an expression tree according to the given
* map array, that is, varattno N is replaced by newattno[N-1]. It is
* caller's responsibility to ensure that the array is long enough to
* define values for all user varattnos present in the tree. System column
* attnos remain unchanged. For historical reason, we only map varattno of the first
* range table entry from this method. So, we call the more general
* change_varattnos_of_a_varno() with varno set to 1
*
* Note that the passed node tree is modified in-place!
*/
void
change_varattnos_of_a_node(Node *node, const AttrNumber *newattno)
{
/* Only attempt re-mapping if re-mapping is necessary (i.e., non-null newattno map) */
if (newattno)
{
change_varattnos_of_a_varno(node, newattno, 1 /* varno is hard-coded to 1 (i.e., only first RTE) */);
}
}
/*
* Replace varattno values for a given varno RTE index in an expression
* tree according to the given map array, that is, varattno N is replaced
* by newattno[N-1]. It is caller's responsibility to ensure that the array
* is long enough to define values for all user varattnos present in the tree.
* System column attnos remain unchanged.
*
* Note that the passed node tree is modified in-place!
*/
void
change_varattnos_of_a_varno(Node *node, const AttrNumber *newattno, Index varno)
{
AttrMapContext attrMapCxt;
attrMapCxt.newattno = newattno;
attrMapCxt.varno = varno;
(void) change_varattnos_varno_walker(node, &attrMapCxt);
}
/*
* Remaps the varattno of a varattno in a Var node using an attribute map.
*/
static bool
change_varattnos_varno_walker(Node *node, const AttrMapContext *attrMapCxt)
{
if (node == NULL)
return false;
if (IsA(node, Var))
{
Var *var = (Var *) node;
if (var->varlevelsup == 0 && (var->varno == attrMapCxt->varno) &&
var->varattno > 0)
{
/*
* ??? the following may be a problem when the node is multiply
* referenced though stringToNode() doesn't create such a node
* currently.
*/
Assert(attrMapCxt->newattno[var->varattno - 1] > 0);
var->varattno = var->varoattno = attrMapCxt->newattno[var->varattno - 1];
}
return false;
}
return expression_tree_walker(node, change_varattnos_varno_walker,
(void *) attrMapCxt);
}
/*
* Generate a map for change_varattnos_of_a_node from old and new TupleDesc's,
* matching according to column name. This function returns a NULL pointer (i.e.
* null map) if no mapping is necessary (i.e., old and new TupleDesc are already
* aligned).
*/
AttrNumber *
varattnos_map(TupleDesc old, TupleDesc new)
{
AttrNumber *attmap;
int i,
j;
bool mapRequired = false;
attmap = (AttrNumber *) palloc0(sizeof(AttrNumber) * old->natts);
for (i = 1; i <= old->natts; i++)
{
if (old->attrs[i - 1]->attisdropped)
continue; /* leave the entry as zero */
for (j = 1; j <= new->natts; j++)
{
if (strcmp(NameStr(old->attrs[i - 1]->attname),
NameStr(new->attrs[j - 1]->attname)) == 0)
{
attmap[i - 1] = j;
if (i != j)
{
mapRequired = true;
}
break;
}
}
}
if (!mapRequired)
{
pfree(attmap);
/* No mapping required, so return NULL */
attmap = NULL;
}
return attmap;
}
/*
* Generate a map for change_varattnos_of_a_node from a TupleDesc and a list
* of ColumnDefs
*/
AttrNumber *
varattnos_map_schema(TupleDesc old, List *schema)
{
AttrNumber *attmap;
int i;
attmap = (AttrNumber *) palloc0(sizeof(AttrNumber) * old->natts);
for (i = 1; i <= old->natts; i++)
{
if (old->attrs[i - 1]->attisdropped)
continue; /* leave the entry as zero */
attmap[i - 1] = findAttrByName(NameStr(old->attrs[i - 1]->attname),
schema);
}
return attmap;
}
/*
* StoreCatalogInheritance
* Updates the system catalogs with proper inheritance information.
*
* supers is a list of the OIDs of the new relation's direct ancestors.
*/
static void
StoreCatalogInheritance(Oid relationId, List *supers)
{
Relation relation;
int16 seqNumber;
ListCell *entry;
/*
* sanity checks
*/
AssertArg(OidIsValid(relationId));
if (supers == NIL)
return;
/*
* Store INHERITS information in pg_inherits using direct ancestors only.
* Also enter dependencies on the direct ancestors, and make sure they are
* marked with relhassubclass = true.
*
* (Once upon a time, both direct and indirect ancestors were found here
* and then entered into pg_ipl. Since that catalog doesn't exist
* anymore, there's no need to look for indirect ancestors.)
*/
relation = heap_open(InheritsRelationId, RowExclusiveLock);
seqNumber = 1;
foreach(entry, supers)
{
Oid parentOid = lfirst_oid(entry);
StoreCatalogInheritance1(relationId, parentOid, seqNumber, relation,
false);
seqNumber++;
}
heap_close(relation, RowExclusiveLock);
}
/*
* Make catalog entries showing relationId as being an inheritance child
* of parentOid. inhRelation is the already-opened pg_inherits catalog.
*/
static void
StoreCatalogInheritance1(Oid relationId, Oid parentOid,
int16 seqNumber, Relation inhRelation,
bool is_partition)
{
Datum datum[Natts_pg_inherits];
bool nullarr[Natts_pg_inherits];
ObjectAddress childobject,
parentobject;
HeapTuple tuple;
cqContext cqc;
cqContext *pcqCtx;
Assert(RelationGetRelid(inhRelation) == InheritsRelationId);
/*
* Make the pg_inherits entry
*/
datum[0] = ObjectIdGetDatum(relationId); /* inhrelid */
datum[1] = ObjectIdGetDatum(parentOid); /* inhparent */
datum[2] = Int16GetDatum(seqNumber); /* inhseqno */
nullarr[0] = false;
nullarr[1] = false;
nullarr[2] = false;
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), inhRelation),
cql("INSERT INTO pg_inherits",
NULL));
tuple = caql_form_tuple(pcqCtx, datum, nullarr);
caql_insert(pcqCtx, tuple); /* implicit update of index as well */
heap_freetuple(tuple);
caql_endscan(pcqCtx);
/*
* Store a dependency too
*/
parentobject.classId = RelationRelationId;
parentobject.objectId = parentOid;
parentobject.objectSubId = 0;
childobject.classId = RelationRelationId;
childobject.objectId = relationId;
childobject.objectSubId = 0;
recordDependencyOn(&childobject, &parentobject,
is_partition ? DEPENDENCY_AUTO : DEPENDENCY_NORMAL);
/*
* Mark the parent as having subclasses.
*/
setRelhassubclassInRelation(parentOid, true);
}
/*
* Look for an existing schema entry with the given name.
*
* Returns the index (starting with 1) if attribute already exists in schema,
* 0 if it doesn't.
*/
static int
findAttrByName(const char *attributeName, List *schema)
{
ListCell *s;
int i = 1;
foreach(s, schema)
{
ColumnDef *def = lfirst(s);
if (strcmp(attributeName, def->colname) == 0)
return i;
i++;
}
return 0;
}
/*
* Update a relation's pg_class.relhassubclass entry to the given value
*/
static void
setRelhassubclassInRelation(Oid relationId, bool relhassubclass)
{
Relation relationRelation;
HeapTuple tuple;
Form_pg_class classtuple;
cqContext cqc;
cqContext *pcqCtx;
/*
* Fetch a modifiable copy of the tuple, modify it, update pg_class.
*
* If the tuple already has the right relhassubclass setting, we don't
* need to update it, but we still need to issue an SI inval message.
*/
relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), relationRelation);
tuple = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(relationId)));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relationId);
classtuple = (Form_pg_class) GETSTRUCT(tuple);
if (classtuple->relhassubclass != relhassubclass)
{
classtuple->relhassubclass = relhassubclass;
caql_update_current(pcqCtx, tuple);
/* and Update indexes (implicit) */
}
else
{
/* no need to change tuple, but force relcache rebuild anyway */
CacheInvalidateRelcacheByTuple(tuple);
}
heap_freetuple(tuple);
heap_close(relationRelation, NoLock);
}
/*
* renameatt - changes the name of a attribute in a relation
*
* Attname attribute is changed in attribute catalog.
* No record of the previous attname is kept (correct?).
*
* get proper relrelation from relation catalog (if not arg)
* scan attribute catalog
* for name conflict (within rel)
* for original attribute (if not arg)
* modify attname in attribute tuple
* insert modified attribute in attribute catalog
* delete original attribute from attribute catalog
*/
void
renameatt(Oid myrelid,
const char *oldattname,
const char *newattname,
bool recurse,
bool recursing)
{
Relation targetrelation;
Relation attrelation;
HeapTuple atttup;
Form_pg_attribute attform;
int attnum;
List *indexoidlist;
ListCell *indexoidscan;
cqContext cqc;
cqContext cqc2;
cqContext *pcqCtx;
/*
* Grab an exclusive lock on the target table, which we will NOT release
* until end of transaction.
*/
targetrelation = relation_open(myrelid, AccessExclusiveLock);
/*
* permissions checking. this would normally be done in utility.c, but
* this particular routine is recursive.
*
* normally, only the owner of a class can change its schema.
*/
if (!pg_class_ownercheck(myrelid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(targetrelation));
if (!allowSystemTableModsDDL && IsSystemRelation(targetrelation))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(targetrelation))));
if (RelationIsParquet(targetrelation))
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_SUPPORTED),
errmsg("Unsupported Rename column command for table type parquet"),
errOmitLocation(true)));
/*
* if the 'recurse' flag is set then we are supposed to rename this
* attribute in all classes that inherit from 'relname' (as well as in
* 'relname').
*
* any permissions or problems with duplicate attributes will cause the
* whole transaction to abort, which is what we want -- all or nothing.
*/
if (recurse)
{
ListCell *child;
List *children;
/* this routine is actually in the planner */
children = find_all_inheritors(myrelid);
/*
* find_all_inheritors does the recursive search of the inheritance
* hierarchy, so all we have to do is process all of the relids in the
* list that it returns.
*/
foreach(child, children)
{
Oid childrelid = lfirst_oid(child);
if (childrelid == myrelid)
continue;
/* note we need not recurse again */
renameatt(childrelid, oldattname, newattname, false, true);
}
}
else
{
/*
* If we are told not to recurse, there had better not be any child
* tables; else the rename would put them out of step.
*/
if (!recursing &&
find_inheritance_children(myrelid) != NIL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("inherited column \"%s\" must be renamed in child tables too",
oldattname)));
}
attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), attrelation);
atttup = caql_getattname(pcqCtx, myrelid, oldattname);
if (!HeapTupleIsValid(atttup))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" does not exist",
oldattname),
errOmitLocation(true)));
attform = (Form_pg_attribute) GETSTRUCT(atttup);
attnum = attform->attnum;
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rename system column \"%s\"",
oldattname)));
/*
* if the attribute is inherited, forbid the renaming, unless we are
* already inside a recursive rename.
*/
if (attform->attinhcount > 0 && !recursing)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot rename inherited column \"%s\"",
oldattname)));
/* should not already exist */
/* this test is deliberately not attisdropped-aware */
if (caql_getcount(
caql_addrel(cqclr(&cqc2), attrelation),
cql("SELECT COUNT(*) FROM pg_attribute "
" WHERE attrelid = :1 "
" AND attname = :2 ",
ObjectIdGetDatum(myrelid),
PointerGetDatum((char *) newattname))))
{
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" of relation \"%s\" already exists",
newattname, RelationGetRelationName(targetrelation)),
errOmitLocation(true)));
}
namestrcpy(&(attform->attname), newattname);
caql_update_current(pcqCtx, atttup); /* implicit update of index as well */
heap_freetuple(atttup);
/*
* Update column names of indexes that refer to the column being renamed.
*/
indexoidlist = RelationGetIndexList(targetrelation);
foreach(indexoidscan, indexoidlist)
{
Oid indexoid = lfirst_oid(indexoidscan);
HeapTuple indextup;
Form_pg_index indexform;
int i;
cqContext *pidxCtx;
/*
* Scan through index columns to see if there's any simple index
* entries for this attribute. We ignore expressional entries.
*/
pidxCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_index "
" WHERE indexrelid = :1 ",
ObjectIdGetDatum(indexoid)));
indextup = caql_getnext(pidxCtx);
if (!HeapTupleIsValid(indextup))
elog(ERROR, "cache lookup failed for index %u", indexoid);
indexform = (Form_pg_index) GETSTRUCT(indextup);
for (i = 0; i < indexform->indnatts; i++)
{
if (attnum != indexform->indkey.values[i])
continue;
/*
* Found one, rename it.
*/
pcqCtx = caql_addrel(cqclr(&cqc), attrelation);
atttup = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_attribute "
" WHERE attrelid = :1 "
" AND attnum = :2 "
" FOR UPDATE ",
ObjectIdGetDatum(indexoid),
Int16GetDatum(i + 1)));
if (!HeapTupleIsValid(atttup))
continue; /* should we raise an error? */
/*
* Update the (copied) attribute tuple.
*/
namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname),
newattname);
caql_update_current(pcqCtx, atttup);
/* and Update indexes (implicit) */
heap_freetuple(atttup);
}
caql_endscan(pidxCtx);
}
list_free(indexoidlist);
heap_close(attrelation, RowExclusiveLock);
/*
* Update att name in any RI triggers associated with the relation.
*/
if (targetrelation->rd_rel->reltriggers > 0)
{
/* update tgargs column reference where att is primary key */
update_ri_trigger_args(RelationGetRelid(targetrelation),
oldattname, newattname,
false, false);
/* update tgargs column reference where att is foreign key */
update_ri_trigger_args(RelationGetRelid(targetrelation),
oldattname, newattname,
true, false);
}
/* MPP-6929, MPP-7600: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(targetrelation->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(targetrelation),
GetUserId(),
"ALTER", "RENAME COLUMN"
);
relation_close(targetrelation, NoLock); /* close rel but keep lock */
}
/*
* renamerel - change the name of a relation
*
* XXX - When renaming sequences, we don't bother to modify the
* sequence name that is stored within the sequence itself
* (this would cause problems with MVCC). In the future,
* the sequence name should probably be removed from the
* sequence, AFAIK there's no need for it to be there.
*/
void
renamerel(Oid myrelid, const char *newrelname, RenameStmt *stmt)
{
Relation pg_class_desc;
HeapTuple tuple;
Oid typeId;
Oid namespaceId;
char relkind;
char oldrelname[NAMEDATALEN];
bool relhastriggers;
Form_pg_class form;
bool __MAYBE_UNUSED isSystemRelation;
cqContext cqc;
cqContext *pcqCtx;
/* if this is a child table of a partitioning configuration, complain */
if (stmt && rel_is_child_partition(myrelid) && !stmt->bAllowPartn)
{
Oid master = rel_partition_get_master(myrelid);
char *pretty = rel_get_part_path_pretty(myrelid,
" ALTER PARTITION ",
" RENAME PARTITION ");
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot rename partition \"%s\" directly",
get_rel_name(myrelid)),
errhint("Table \"%s\" is a child partition of table "
"\"%s\". To rename it, use ALTER TABLE \"%s\"%s...",
get_rel_name(myrelid), get_rel_name(master),
get_rel_name(master), pretty ? pretty : "" ),
errOmitLocation(true)));
}
pg_class_desc = heap_open(RelationRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), pg_class_desc);
tuple = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(myrelid)));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("relation \"%d\" does not exist", myrelid)));
form = (Form_pg_class) GETSTRUCT(tuple);
relkind = form->relkind;
namespaceId = form->relnamespace;
relhastriggers = form->reltriggers;
typeId = form->reltype;
strncpy(&oldrelname[0], (form->relname).data, NAMEDATALEN);
isSystemRelation = IsSystemNamespace(namespaceId) ||
IsToastNamespace(namespaceId) ||
IsAoSegmentNamespace(namespaceId);
Assert (allowSystemTableModsDDL || !isSystemRelation);
/*
* Find relation's pg_class tuple, and make sure newrelname isn't in use.
*/
if (get_relname_relid(newrelname, namespaceId) != InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists",
newrelname),
errOmitLocation(true)));
/*
* Update pg_class tuple with new relname. (Scribbling on tuple is OK
* because it's a copy...)
*/
namestrcpy(&(form->relname), newrelname);
caql_update_current(pcqCtx, tuple); /* implicit update of index as well */
heap_freetuple(tuple);
heap_close(pg_class_desc, NoLock);
/*
* Also rename the associated type, if any.
*/
if (relkind != RELKIND_INDEX)
{
if (!TypeTupleExists(typeId))
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("type \"%s\" does not exist", oldrelname)));
else
TypeRename(typeId, newrelname);
}
/*
* Update rel name in any RI triggers associated with the relation.
*/
if (relhastriggers)
{
/* update tgargs where relname is primary key */
update_ri_trigger_args(myrelid,
oldrelname,
newrelname,
false, true);
/* update tgargs where relname is foreign key */
update_ri_trigger_args(myrelid,
oldrelname,
newrelname,
true, true);
}
/* MPP-3059: recursive rename of partitioned table */
/* Note: the top-level RENAME has bAllowPartn=FALSE, while the
* generated statements from this block set it to TRUE. That way,
* the rename of each partition is allowed, but this block doesn't
* get invoked recursively.
*/
if (stmt && !rel_is_child_partition(myrelid) && !stmt->bAllowPartn &&
(Gp_role == GP_ROLE_DISPATCH))
{
PartitionNode *pNode;
pNode = RelationBuildPartitionDescByOid(myrelid, false);
if (pNode)
{
RenameStmt *renStmt = makeNode(RenameStmt);
DestReceiver *dest = None_Receiver;
List *renList = NIL;
int skipped = 0;
int renamed = 0;
renStmt->renameType = OBJECT_TABLE;
renStmt->subname = NULL;
renStmt->bAllowPartn = true; /* allow rename of partitions */
/* rename the children as well */
renList = atpxRenameList(pNode, oldrelname, newrelname, &skipped);
/* process children if there are any */
if (renList)
{
ListCell *lc;
foreach(lc, renList)
{
ListCell *lc2;
List *lpair = lfirst(lc);
lc2 = list_head(lpair);
renStmt->relation = (RangeVar *)lfirst(lc2);
lc2 = lnext(lc2);
renStmt->newname = (char *)lfirst(lc2);
ProcessUtility((Node *) renStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
renamed++;
}
/* MPP-3542: warn when skip child partitions */
if (skipped)
{
ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("renamed %d partitions, "
"skipped %d child partitions "
"due to name truncation",
renamed, skipped),
errOmitLocation(true)));
}
}
}
}
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&&
/* MPP-7773: don't track objects in system namespace
* if modifying system tables (eg during upgrade)
*/
( ! ( (PG_CATALOG_NAMESPACE == namespaceId) && (allowSystemTableModsDDL)))
&& ( MetaTrackValidRelkind(relkind)
&& METATRACK_VALIDNAMESPACE(namespaceId)
&& (!(isAnyTempNamespace(namespaceId)))
))
MetaTrackUpdObject(RelationRelationId,
myrelid,
GetUserId(),
"ALTER", "RENAME"
);
}
static bool
TypeTupleExists(Oid typeId)
{
Relation pg_type_desc;
cqContext cqc;
bool bExists;
pg_type_desc = heap_open(TypeRelationId, AccessShareLock);
bExists = (0 <
caql_getcount(
caql_addrel(cqclr(&cqc), pg_type_desc),
cql("SELECT COUNT(*) FROM pg_type "
" WHERE oid = :1 ",
ObjectIdGetDatum(typeId))));
heap_close(pg_type_desc, AccessShareLock);
return (bExists);
}
/*
* Scan pg_trigger for RI triggers that are on the specified relation
* (if fk_scan is false) or have it as the tgconstrrel (if fk_scan
* is true). Update RI trigger args fields matching oldname to contain
* newname instead. If update_relname is true, examine the relname
* fields; otherwise examine the attname fields.
*/
static void
update_ri_trigger_args(Oid relid,
const char *oldname,
const char *newname,
bool fk_scan,
bool update_relname)
{
cqContext *pcqCtx;
HeapTuple tuple;
Datum values[Natts_pg_trigger];
bool nulls[Natts_pg_trigger];
bool replaces[Natts_pg_trigger];
if (fk_scan)
{
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_trigger "
" WHERE tgconstrrelid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(relid)));
}
else
{
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_trigger "
" WHERE tgrelid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(relid)));
}
while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx)))
{
Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
bytea *val;
bytea *newtgargs;
bool isnull;
int tg_type;
bool examine_pk;
bool changed;
int tgnargs;
int i;
int newlen;
const char *arga[RI_MAX_ARGUMENTS];
const char *argp;
tg_type = RI_FKey_trigger_type(pg_trigger->tgfoid);
if (tg_type == RI_TRIGGER_NONE)
{
/* Not an RI trigger, forget it */
continue;
}
/*
* It is an RI trigger, so parse the tgargs bytea.
*
* NB: we assume the field will never be compressed or moved out of
* line; so does trigger.c ...
*/
tgnargs = pg_trigger->tgnargs;
val = DatumGetByteaP(caql_getattr(pcqCtx,
Anum_pg_trigger_tgargs,
&isnull));
if (isnull || tgnargs < RI_FIRST_ATTNAME_ARGNO ||
tgnargs > RI_MAX_ARGUMENTS)
{
/* This probably shouldn't happen, but ignore busted triggers */
continue;
}
argp = (const char *) VARDATA(val);
for (i = 0; i < tgnargs; i++)
{
arga[i] = argp;
argp += strlen(argp) + 1;
}
/*
* Figure out which item(s) to look at. If the trigger is primary-key
* type and attached to my rel, I should look at the PK fields; if it
* is foreign-key type and attached to my rel, I should look at the FK
* fields. But the opposite rule holds when examining triggers found
* by tgconstrrel search.
*/
examine_pk = (tg_type == RI_TRIGGER_PK) == (!fk_scan);
changed = false;
if (update_relname)
{
/* Change the relname if needed */
i = examine_pk ? RI_PK_RELNAME_ARGNO : RI_FK_RELNAME_ARGNO;
if (strcmp(arga[i], oldname) == 0)
{
arga[i] = newname;
changed = true;
}
}
else
{
/* Change attname(s) if needed */
i = examine_pk ? RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX :
RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_FK_IDX;
for (; i < tgnargs; i += 2)
{
if (strcmp(arga[i], oldname) == 0)
{
arga[i] = newname;
changed = true;
}
}
}
if (!changed)
{
/* Don't need to update this tuple */
continue;
}
/*
* Construct modified tgargs bytea.
*/
newlen = VARHDRSZ;
for (i = 0; i < tgnargs; i++)
newlen += strlen(arga[i]) + 1;
newtgargs = (bytea *) palloc(newlen);
SET_VARSIZE(newtgargs, newlen);
newlen = VARHDRSZ;
for (i = 0; i < tgnargs; i++)
{
strcpy(((char *) newtgargs) + newlen, arga[i]);
newlen += strlen(arga[i]) + 1;
}
/*
* Build modified tuple.
*/
for (i = 0; i < Natts_pg_trigger; i++)
{
values[i] = (Datum) 0;
replaces[i] = false;
nulls[i] = false;
}
values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(newtgargs);
replaces[Anum_pg_trigger_tgargs - 1] = true;
tuple = caql_modify_current(pcqCtx, values, nulls, replaces);
/*
* Update pg_trigger and its indexes
*/
caql_update_current(pcqCtx, tuple);
/*
* Invalidate trigger's relation's relcache entry so that other
* backends (and this one too!) are sent SI message to make them
* rebuild relcache entries. (Ideally this should happen
* automatically...)
*
* We can skip this for triggers on relid itself, since that relcache
* flush will happen anyway due to the table or column rename. We
* just need to catch the far ends of RI relationships.
*/
pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
if (pg_trigger->tgrelid != relid)
CacheInvalidateRelcacheByRelid(pg_trigger->tgrelid);
/* free up our scratch memory */
pfree(newtgargs);
heap_freetuple(tuple);
}
caql_endscan(pcqCtx);
/*
* Increment cmd counter to make updates visible; this is needed in case
* the same tuple has to be updated again by next pass (can happen in case
* of a self-referential FK relationship).
*/
CommandCounterIncrement();
}
/*
* Disallow ALTER TABLE (and similar commands) when the current backend has
* any open reference to the target table besides the one just acquired by
* the calling command; this implies there's an open cursor or active plan.
* We need this check because our AccessExclusiveLock doesn't protect us
* against stomping on our own foot, only other people's feet!
*
* For ALTER TABLE, the only case known to cause serious trouble is ALTER
* COLUMN TYPE, and some changes are obviously pretty benign, so this could
* possibly be relaxed to only error out for certain types of alterations.
* But the use-case for allowing any of these things is not obvious, so we
* won't work hard at it for now.
*
* We also reject these commands if there are any pending AFTER trigger events
* for the rel. This is certainly necessary for the rewriting variants of
* ALTER TABLE, because they don't preserve tuple TIDs and so the pending
* events would try to fetch the wrong tuples. It might be overly cautious
* in other cases, but again it seems better to err on the side of paranoia.
*
* REINDEX calls this with "rel" referencing the index to be rebuilt; here
* we are worried about active indexscans on the index. The trigger-event
* check can be skipped, since we are doing no damage to the parent table.
*
* The statement name (eg, "ALTER TABLE") is passed for use in error messages.
*/
void
CheckTableNotInUse(Relation rel, const char *stmt)
{
int expected_refcnt;
expected_refcnt = rel->rd_isnailed ? 2 : 1;
/*
* XXX For a bitmap index, since vacuum (or vacuum full) is currently done through
* reindex_index, the reference count could be 2 (or 3). We set it
* here until vacuum is done properly.
*/
if (expected_refcnt == 1 &&
RelationIsBitmapIndex(rel) &&
(rel->rd_refcnt == 2 || rel->rd_refcnt == 3))
expected_refcnt = rel->rd_refcnt;
if (rel->rd_refcnt != expected_refcnt)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
/* translator: first %s is a SQL command, eg ALTER TABLE */
errmsg("cannot %s \"%s\" because "
"it is being used by active queries in this session",
stmt, RelationGetRelationName(rel))));
if (rel->rd_rel->relkind != RELKIND_INDEX &&
AfterTriggerPendingOnRel(RelationGetRelid(rel)))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
/* translator: first %s is a SQL command, eg ALTER TABLE */
errmsg("cannot %s \"%s\" because "
"it has pending trigger events",
stmt, RelationGetRelationName(rel))));
}
static
void ATVerifyObject(AlterTableStmt *stmt, Relation rel)
{
/* Nothing to check for ALTER INDEX */
if(stmt->relkind == OBJECT_INDEX)
return;
/*
* Verify the object specified against relstorage in the catalog.
* Enforce correct syntax usage.
*/
if (RelationIsForeign(rel) && stmt->relkind != OBJECT_FOREIGNTABLE)
{
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a foreign table", RelationGetRelationName(rel)),
errhint("Use ALTER FOREIGN TABLE instead"),
errOmitLocation(true)));
}
else if (RelationIsExternal(rel) && stmt->relkind != OBJECT_EXTTABLE)
{
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an external table", RelationGetRelationName(rel)),
errhint("Use ALTER EXTERNAL TABLE instead"),
errOmitLocation(true)));
}
else if (!RelationIsExternal(rel) && !RelationIsForeign(rel) && stmt->relkind != OBJECT_TABLE)
{
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a base table", RelationGetRelationName(rel)),
errhint("Use ALTER TABLE instead"),
errOmitLocation(true)));
}
/*
* Check the ALTER command type is supported for this object
*/
if (RelationIsForeign(rel) || RelationIsExternal(rel))
{
ListCell *lcmd;
foreach(lcmd, stmt->cmds)
{
AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
switch(cmd->subtype)
{
/* FOREIGN and EXTERNAL tables doesn't support the following AT */
case AT_ColumnDefault:
case AT_DropNotNull:
case AT_SetNotNull:
case AT_SetStatistics:
case AT_SetStorage:
case AT_AddIndex:
case AT_ReAddIndex:
case AT_AddConstraint:
case AT_AddConstraintRecurse:
case AT_ProcessedConstraint:
case AT_DropConstraint:
case AT_DropConstraintQuietly:
case AT_ClusterOn:
case AT_DropCluster:
case AT_DropOids:
case AT_SetTableSpace:
case AT_SetRelOptions:
case AT_ResetRelOptions:
case AT_EnableTrig:
case AT_DisableTrig:
case AT_EnableTrigAll:
case AT_DisableTrigAll:
case AT_EnableTrigUser:
case AT_DisableTrigUser:
case AT_AddInherit:
case AT_DropInherit:
case AT_SetDistributedBy:
case AT_PartAdd:
case AT_PartAlter:
case AT_PartCoalesce:
case AT_PartDrop:
case AT_PartExchange:
case AT_PartMerge:
case AT_PartModify:
case AT_PartRename:
case AT_PartSetTemplate:
case AT_PartSplit:
case AT_PartTruncate:
case AT_PartAddInternal:
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
errmsg("Unsupported ALTER command for table type %s",
(RelationIsExternal(rel) ? "external" : "foreign")),
errOmitLocation(true)));
break;
case AT_AddColumn: /* check no constraint is added too */
if(((ColumnDef *) cmd->def)->constraints != NIL ||
((ColumnDef *) cmd->def)->is_not_null ||
((ColumnDef *) cmd->def)->raw_default)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
errmsg("Unsupported ALTER command for table type %s. No constraints allowed.",
(RelationIsExternal(rel) ? "external" : "foreign")),
errOmitLocation(true)));
break;
default:
/* ALTER type supported */
break;
}
}
}
/*
* Check the ALTER command type is supported for this object
*/
/*
* Check whether the relation or its sub partition table is parquet table, if yes,
* check whether the command is supported
*/
List *children = find_all_inheritors(RelationGetRelid(rel));
ListCell *lc;
foreach(lc, children)
{
Oid relid = lfirst_oid(lc);
Relation crel;
if (relid == RelationGetRelid(rel))
crel = rel;
else
crel = heap_open(relid, AccessShareLock);
if (RelationIsParquet(crel)) {
ListCell *lcmd;
foreach(lcmd, stmt->cmds)
{
AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
switch (cmd->subtype) {
/* FOREIGN and EXTERNAL tables doesn't support the following AT */
case AT_AddColumn:
case AT_AddColumnRecurse:
case AT_ColumnDefault:
case AT_DropNotNull:
case AT_SetNotNull:
case AT_SetStorage:
case AT_DropColumn:
case AT_DropColumnRecurse:
case AT_AddIndex:
case AT_ReAddIndex:
case AT_AlterColumnType:
case AT_ClusterOn:
case AT_DropCluster:
case AT_DropOids:
case AT_EnableTrig:
case AT_DisableTrig:
case AT_EnableTrigAll:
case AT_DisableTrigAll:
case AT_EnableTrigUser:
case AT_DisableTrigUser:
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
errmsg("Unsupported ALTER command for parquet table \"%s\"",
NameStr(crel->rd_rel->relname)),
errOmitLocation(true)));
break;
default:
/* ALTER type supported */
break;
}
}
}
/*close sub partition table*/
if (relid != RelationGetRelid(rel))
heap_close(crel, AccessShareLock);
}
list_free(children);
}
/*
* Copy the AlteredTableInfo to a serializeable AlterRewriteTableInfo, to dispatch
* ATRewriteTable work to segments.
*/
static AlterRewriteTableInfo *
prepareAlteredTableInfo(AlteredTableInfo *tab)
{
AlterRewriteTableInfo *ar_tab = makeNode(AlterRewriteTableInfo);
ListCell *l;
ar_tab->relid = tab->relid;
ar_tab->relkind = tab->relkind;
ar_tab->oldDesc = tab->oldDesc;
ar_tab->new_notnull = tab->new_notnull;
ar_tab->new_dropoids = tab->new_dropoids;
ar_tab->newTableSpace = tab->newTableSpace;
ar_tab->exchange_relid = tab->exchange_relid;
/* Copy constraints */
foreach (l, tab->constraints)
{
NewConstraint *con = lfirst(l);
AlterRewriteNewConstraint *ar_con =
makeNode(AlterRewriteNewConstraint);
ar_con->name = pstrdup(con->name);
ar_con->contype = con->contype;
ar_con->refrelid = con->refrelid;
ar_con->qual = copyObject(con->qual);
ar_tab->constraints = lappend(ar_tab->constraints, ar_con);
}
/* Copy newvals */
foreach (l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
AlterRewriteNewColumnValue *ar_ex =
makeNode(AlterRewriteNewColumnValue);
ar_ex->attnum = ex->attnum;
ar_ex->expr = copyObject(ex->expr);
ar_tab->newvals = lappend(ar_tab->newvals, ar_ex);
}
return ar_tab;
}
/*
* Copy back the AlteredTableInfo from a serializeable AlterRewriteTableInfo.
*/
static AlteredTableInfo *
rebuildAlteredTableInfo(AlterRewriteTableInfo *ar_tab)
{
AlteredTableInfo *tab =
(AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo));
ListCell *l;
tab->relid = ar_tab->relid;
tab->relkind = ar_tab->relkind;
tab->oldDesc = ar_tab->oldDesc;
tab->new_notnull = ar_tab->new_notnull;
tab->new_dropoids = ar_tab->new_dropoids;
tab->newTableSpace = ar_tab->newTableSpace;
tab->exchange_relid = ar_tab->exchange_relid;
tab->scantable_splits = ar_tab->scantable_splits;
tab->ao_segnos = ar_tab->ao_segnos;
/* Copy constraints */
foreach (l, ar_tab->constraints)
{
AlterRewriteNewConstraint *ar_con = lfirst(l);
NewConstraint *con =
(NewConstraint *) palloc0(sizeof(NewConstraint));
con->name = pstrdup(ar_con->name);
con->contype = ar_con->contype;
con->refrelid = ar_con->refrelid;
con->qual = copyObject(ar_con->qual);
tab->constraints = lappend(tab->constraints, con);
}
/* Copy newvals */
foreach (l, ar_tab->newvals)
{
AlterRewriteNewColumnValue *ar_ex = lfirst(l);
NewColumnValue *ex =
(NewColumnValue *) palloc0(sizeof(NewColumnValue));
ex->attnum = ar_ex->attnum;
ex->expr = copyObject(ar_ex->expr);
tab->newvals = lappend(tab->newvals, ex);
}
return tab;
}
/*
* AlterTable
* Execute ALTER TABLE, which can be a list of subcommands
*
* ALTER TABLE is performed in three phases:
* 1. Examine subcommands and perform pre-transformation checking.
* 2. Update system catalogs.
* 3. Scan table(s) to check new constraints, and optionally recopy
* the data into new table(s).
* Phase 3 is not performed unless one or more of the subcommands requires
* it. The intention of this design is to allow multiple independent
* updates of the table schema to be performed with only one pass over the
* data.
*
* ATPrepCmd performs phase 1. A "work queue" entry is created for
* each table to be affected (there may be multiple affected tables if the
* commands traverse a table inheritance hierarchy). Also we do preliminary
* validation of the subcommands, including parse transformation of those
* expressions that need to be evaluated with respect to the old table
* schema.
*
* ATRewriteCatalogs performs phase 2 for each affected table (note that
* phases 2 and 3 do no explicit recursion, since phase 1 already did it).
* Certain subcommands need to be performed before others to avoid
* unnecessary conflicts; for example, DROP COLUMN should come before
* ADD COLUMN. Therefore phase 1 divides the subcommands into multiple
* lists, one for each logical "pass" of phase 2.
*
* ATRewriteTables performs phase 3 for those tables that need it.
*
* Thanks to the magic of MVCC, an error anywhere along the way rolls back
* the whole operation; we don't have to do anything special to clean up.
*/
void
AlterTable(Oid relid, AlterTableStmt *stmt)
{
GpRoleValue oldrole = GP_ROLE_UNDEFINED;
if(gp_upgrade_mode)
{
oldrole = Gp_role;
Gp_role = GP_ROLE_UTILITY;
}
if (stmt->relkind == OBJECT_INDEX)
{
if (!gp_called_by_pgdump)
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET), errmsg("Cannot support alter index statement yet") ));
}
else if (stmt->relkind == OBJECT_FOREIGNTABLE)
{
if (!gp_called_by_pgdump)
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET), errmsg("Cannot support alter foreign table statement yet") ));
}
else if (stmt->relkind == OBJECT_EXTTABLE)
{
if (!gp_called_by_pgdump)
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET), errmsg("Cannot support alter external table statement yet") ));
}
/* check if this is an hcatalog table */
#ifdef USE_ASSERT_CHECKING
Oid oidRel =
#endif //USE_ASSERT_CHECKING
RangeVarGetRelid(stmt->relation, false /*failOk*/, false /*allowHcatalog*/);
Assert(OidIsValid(oidRel));
Assert(OidIsValid(relid));
Relation rel;
/* Caller is required to provide an adequate lock. */
rel = relation_open(relid, NoLock);
int expected_refcnt;
ATVerifyObject(stmt, rel);
/*
* Disallow ALTER TABLE when the current backend has any open reference
* to it besides the one we just got (such as an open cursor or active
* plan); our AccessExclusiveLock doesn't protect us against stomping on
* our own foot, only other people's feet!
*
* Note: the only case known to cause serious trouble is ALTER COLUMN TYPE,
* and some changes are obviously pretty benign, so this could possibly
* be relaxed to only error out for certain types of alterations. But
* the use-case for allowing any of these things is not obvious, so we
* won't work hard at it for now.
*/
expected_refcnt = rel->rd_isnailed ? 2 : 1;
if (rel->rd_refcnt != expected_refcnt)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("relation \"%s\" is being used by active queries in this session",
RelationGetRelationName(rel))));
if (Gp_role == GP_ROLE_DISPATCH)
{
LockRelationOid(RelationRelationId, RowExclusiveLock);
LockRelationOid(TypeRelationId, RowExclusiveLock);
LockRelationOid(DependRelationId, RowExclusiveLock);
}
ATController(rel,
stmt->cmds,
interpretInhOption(stmt->relation->inhOpt),
&stmt->oidInfoCount,
&stmt->oidInfo,
&stmt->oidmap);
if(gp_upgrade_mode)
Gp_role = oldrole;
}
/*
* AlterTableInternal
*
* ALTER TABLE with target specified by OID
*
* We do not reject if the relation is already open, because it's quite likely
* that one or more layers of caller have it open. That means it is unsafe to
* use this entry point for alterations that could break existing query plans.
* On the assumption it's not used for such, we don't have to reject pending
* AFTER triggers, either.
*
* It is also unsafe to use this function for any Alter Table subcommand that
* requires rewriting the table or creating toast tables, because that requires
* creating relfilenodes outside of a context that understands dispatch.
* Commands that rewrite the table include: adding or altering columns, changing
* the tablespace, etc.
*/
void
AlterTableInternal(Oid relid, List *cmds, bool recurse)
{
ATController(relation_open(relid, AccessExclusiveLock),
cmds,
recurse,
0,
NULL,
NULL);
}
static void
ATController(Relation rel, List *cmds, bool recurse,
int * oidInfoCount, TableOidInfo ** oidInfo, List **poidmap)
{
List *wqueue = NIL;
ListCell *lcmd;
int ocount = 0;
TableOidInfo * oids = NULL;
bool is_partition = false;
bool is_data_remote = (RelationIsExternal(rel) || RelationIsForeign(rel));
#ifdef USE_ASSERT_CHECKING
Oid relid = RelationGetRelid(rel);
#endif
/*
* In HAWQ, only the master has the catalog information.
* So, no need to sync oid to segments.
*/
/* cdb_sync_oid_to_segments(); */
/* Phase 1: preliminary examination of commands, create work queue */
foreach(lcmd, cmds)
{
AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
if (cmd->subtype == AT_PartAddInternal)
is_partition = true;
ATPrepCmd(&wqueue, rel, cmd, recurse, false);
}
if (is_partition)
relation_close(rel, AccessExclusiveLock);
else
/* Close the relation, but keep lock until commit */
relation_close(rel, NoLock);
/* Phase 2: update system catalogs */
ATRewriteCatalogs(&wqueue);
if (Gp_role == GP_ROLE_DISPATCH && oidInfoCount != NULL)
{
*oidInfoCount = list_length(wqueue);
*oidInfo = palloc0(sizeof(TableOidInfo)*(*oidInfoCount));
}
if (oidInfoCount != NULL)
ocount = *oidInfoCount;
if (oidInfo != NULL)
oids = *oidInfo;
/*
* Build OID map info.
*
* If we've recursed (i.e., expanded) an ALTER TABLE SET DISTRIBUTED
* command from a master table to its children, then we need to extract
* the OID map (used to ensure that all segments map old index OIDs to
* new index OIDs) for each of the sub commands and put them in the
* master command. This is because we only dispatch the command on the
* master table to the segments, not each expanded command. We expect
* the segments to do the same expansion.
*/
if (Gp_role == GP_ROLE_DISPATCH)
{
List *umap = NIL; /* the full OID map for all relations */
ListCell *lc;
AlterTableCmd *mastercmd = NULL; /* the command which was expanded */
/*
* Iterate over the work queue looking for SET WITH DISTRIBUTED
* statements.
*/
foreach(lc, wqueue)
{
ListCell *lc2;
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(lc);
/* AT_PASS_MISC is used for SET DISTRIBUTED BY */
List *subcmds = (List *)tab->subcmds[AT_PASS_MISC];
foreach(lc2, subcmds)
{
AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lc2);
if (cmd->subtype == AT_SetDistributedBy)
{
if (!mastercmd)
{
Assert(tab->relid == relid);
mastercmd = cmd;
}
/*
* cmd->def stores the data we're sending to the QEs.
* The second element is where we stash QE data to drive
* the command. The second element of qe_data is our
* OID map. See ATExecSetDistributedBy().
*
* Yes, this is begging to be improved.
*/
List *data = (List *)cmd->def;
List *qe_data = lsecond(data);
if (qe_data && list_length(qe_data) >= 2)
{
List *oidmap = lsecond(qe_data);
umap = list_concat(umap, oidmap);
}
}
}
}
/* Finally, push it all back into the master command */
if (mastercmd && umap)
{
List *qe_data = lsecond((List *)mastercmd->def);
if (list_length(qe_data) >= 2)
lsecond(qe_data) = umap;
else if (list_length(qe_data) == 1)
{
qe_data = lappend(qe_data, umap);
/*
* We've changed the pointer, save it back to the master
* definition.
*/
lsecond((List *)mastercmd->def) = qe_data;
}
}
}
/*
* Phase 3: scan/rewrite tables as needed. If the data is in an external
* table, no need to rewrite it or to add toast.
*/
if (!is_data_remote)
{
ATRewriteTables(&wqueue, ocount, oids, poidmap);
ATAddToastIfNeeded(&wqueue, ocount, oids, poidmap);
}
}
/*
* This is the execution work of the phase 3 of ALTER TABLE. The piece of
* information should be dispatched from master.
*/
void
AlterRewriteTable(AlterRewriteTableInfo *ar_tab)
{
AlteredTableInfo *tab;
Assert(Gp_role == GP_ROLE_EXECUTE);
tab = rebuildAlteredTableInfo(ar_tab);
ATRewriteTable(tab, ar_tab->newheap_oid);
}
/*
* ATPrepCmd
*
* Traffic cop for ALTER TABLE Phase 1 operations, including simple
* recursion and permission checks.
*
* Caller must have acquired AccessExclusiveLock on relation already.
* This lock should be held until commit.
*/
static void
ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing)
{
AlteredTableInfo *tab;
int pass = 0;
/* Find or create work queue entry for this table */
tab = ATGetQueueEntry(wqueue, rel);
/*
* Copy the original subcommand for each table. This avoids conflicts
* when different child tables need to make different parse
* transformations (for example, the same column may have different column
* numbers in different children).
*/
if (recursing)
cmd = copyObject(cmd);
/*
* Do permissions checking, recursion to child tables if needed, and any
* additional phase-1 processing needed.
*/
switch (cmd->subtype)
{
case AT_AddColumn: /* ADD COLUMN */
ATSimplePermissions(rel, false);
/*
* test that this is allowed for partitioning, but only if we aren't
* recursing.
*/
ATPartitionCheck(cmd->subtype, rel, false, recursing);
/* Performs own recursion */
ATPrepAddColumn(rel, recurse, cmd);
pass = AT_PASS_ADD_COL;
break;
case AT_AddColumnRecurse: /* ADD COLUMN internal */
ATSimplePermissions(rel, false);
/* No need to do ATPartitionCheck */
/* Performs own recursion */
ATPrepAddColumn(rel, recurse, cmd);
pass = AT_PASS_ADD_COL;
break;
case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
/*
* We allow defaults on views so that INSERT into a view can have
* default-ish behavior. This works because the rewriter
* substitutes default values into INSERTs before it expands
* rules.
*/
ATSimplePermissions(rel, true);
ATPartitionCheck(cmd->subtype, rel, false, recursing);
ATSimpleRecursion(wqueue, rel, cmd, recurse);
/* No command-specific prep needed */
pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, false);
ATPartitionCheck(cmd->subtype, rel, false, recursing);
ATSimpleRecursion(wqueue, rel, cmd, recurse);
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATSimplePermissions(rel, false);
ATSimpleRecursion(wqueue, rel, cmd, recurse);
if (!cmd->part_expanded)
ATPartitionCheck(cmd->subtype, rel, false, recursing);
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
break;
case AT_SetStatistics: /* ALTER COLUMN STATISTICS */
ATSimpleRecursion(wqueue, rel, cmd, recurse);
/* Performs own permission checks */
ATPrepSetStatistics(rel, cmd->name, cmd->def);
pass = AT_PASS_COL_ATTRS;
break;
case AT_SetStorage: /* ALTER COLUMN STORAGE */
ATSimplePermissions(rel, false);
ATSimpleRecursion(wqueue, rel, cmd, recurse);
/* No command-specific prep needed */
pass = AT_PASS_COL_ATTRS;
break;
case AT_DropColumn: /* DROP COLUMN */
case AT_DropColumnRecurse:
ATSimplePermissions(rel, false);
ATPartitionCheck(cmd->subtype, rel, false, recursing);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
cmd->subtype = AT_DropColumnRecurse;
pass = AT_PASS_DROP;
break;
case AT_AddIndex: /* ADD INDEX */
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("ALTER TABLE ... ADD INDEX is not supported")));
ATSimplePermissions(rel, false);
/* Any recursion for partitioning is done in ATExecAddIndex() itself */
/* However, if the index supports a PK or UNIQUE constraint and the
* relation is partitioned, we need to assure the constraint is named
* in a reasonable way. */
{
ConstrType contype = CONSTR_NULL; /* name picker will reject this */
IndexStmt *topindexstmt = (IndexStmt*)cmd->def;
Insist(IsA(topindexstmt, IndexStmt));
if ( topindexstmt->isconstraint )
{
if ( !topindexstmt->altconname )
{
if ( topindexstmt->primary )
contype = CONSTR_PRIMARY;
else if ( topindexstmt->unique )
contype = CONSTR_UNIQUE;
/* Pick and install a constraint name. */
topindexstmt->altconname =
ChooseConstraintNameForPartitionEarly(rel,
contype,
(Node*)topindexstmt->indexParams);
}
switch ( rel_part_status(RelationGetRelid(rel)) )
{
case PART_STATUS_ROOT:
break;
case PART_STATUS_INTERIOR:
case PART_STATUS_LEAF:
/* Don't do this check for child parts (= internal requests). */
if ( ! topindexstmt->is_part_child )
{
char *what = "UNIQUE";
if ( contype == CONSTR_PRIMARY )
what = "PRIMARY KEY";
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("can't place a %s constraint on just part of "
"partitioned table \"%s\"",
what,RelationGetRelationName(rel)),
errhint("Constrain the whole table or create a "
"part-wise UNIQUE index instead.")));
}
break;
default:
break;
}
}
}
pass = AT_PASS_ADD_INDEX;
break;
case AT_AddConstraint: /* ADD CONSTRAINT */
ATSimplePermissions(rel, false);
/* (!recurse && !recursing) is supposed to detect the ONLY clause.
* We allow operations on the root of a partitioning hierarchy, but
* not ONLY the root.
*/
ATPartitionCheck(cmd->subtype, rel, (!recurse && !recursing), recursing);
/*
* Currently we recurse only for CHECK constraints, never for
* foreign-key constraints. UNIQUE/PKEY constraints won't be seen
* here.
*/
/* Any recursion for partitioning/inheritance is done in ATExecAddConstraint() itself */
if (recurse)
cmd->subtype = AT_AddConstraintRecurse;
pass = AT_PASS_ADD_CONSTR;
break;
case AT_AddConstraintRecurse: /* ADD check CONSTRAINT internal */
/* Parent/Base CHECK constraints apply to child/part tables here.
* No need for ATPartitionCheck
*/
ATSimplePermissions(rel, false);
pass = AT_PASS_ADD_CONSTR;
break;
case AT_DropConstraint: /* DROP CONSTRAINT */
ATSimplePermissions(rel, false);
/* (!recurse && !recursing) is supposed to detect the ONLY clause.
* We allow operations on the root of a partitioning hierarchy, but
* not ONLY the root.
*/
ATPartitionCheck(cmd->subtype, rel, (!recurse && !recursing), recursing);
/* Performs own recursion */
ATPrepDropConstraint(wqueue, rel, recurse, cmd);
pass = AT_PASS_DROP;
break;
case AT_DropConstraintQuietly: /* DROP CONSTRAINT for child */
ATSimplePermissions(rel, false);
ATSimpleRecursion(wqueue, rel, cmd, recurse);
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
case AT_AlterColumnType: /* ALTER COLUMN TYPE */
ATSimplePermissions(rel, false);
ATPartitionCheck(cmd->subtype, rel, false, recursing);
/* Performs own recursion */
ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd);
pass = AT_PASS_ALTER_TYPE;
break;
case AT_ChangeOwner: /* ALTER OWNER */
{
bool do_recurse = false;
if (Gp_role == GP_ROLE_DISPATCH)
{
ATPartitionCheck(cmd->subtype, rel, false, recursing);
if (rel_is_partitioned(RelationGetRelid(rel)))
{
do_recurse = true;
/* tell QE to recurse */
cmd->def = (Node *)makeInteger(1);
}
}
else if (Gp_role == GP_ROLE_EXECUTE)
{
if (cmd->def && intVal(cmd->def))
do_recurse = true;
}
if (do_recurse)
ATSimpleRecursion(wqueue, rel, cmd, recurse);
pass = AT_PASS_MISC;
}
break;
case AT_ClusterOn: /* CLUSTER ON */
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("ALTER TABLE ... CLUSTER is not supported")));
case AT_DropCluster: /* SET WITHOUT CLUSTER */
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("ALTER TABLE ... SET WITHOUT CLUSTER is not supported")));
ATSimplePermissions(rel, false);
/* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
case AT_DropOids: /* SET WITHOUT OIDS */
ATSimplePermissions(rel, false);
ATPartitionCheck(cmd->subtype, rel, false, recursing);
/* Performs own recursion */
if (rel->rd_rel->relhasoids)
{
AlterTableCmd *dropCmd = makeNode(AlterTableCmd);
dropCmd->subtype = AT_DropColumn;
dropCmd->name = pstrdup("oid");
dropCmd->behavior = cmd->behavior;
ATPrepCmd(wqueue, rel, dropCmd, recurse, false);
}
pass = AT_PASS_DROP;
break;
case AT_SetTableSpace: /* SET TABLESPACE */
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("ALTER TABLE ... SET TABLESPACE is not supported")));
ATSimplePermissionsRelationOrIndex(rel);
/* This command never recurses, but the offered relation may be partitioned,
* in which case, we need to act as if the command specified the top-level
* list of parts.
*/
if (Gp_role == GP_ROLE_DISPATCH && rel_is_partitioned(RelationGetRelid(rel)) )
{
PartitionNode *pnode = NULL;
List *all_oids = NIL;
pnode = get_parts(RelationGetRelid(rel),
0, /**/
InvalidOid, /**/
false, /**/
CurrentMemoryContext,
true /*includesubparts*/);
all_oids = lcons_oid(RelationGetRelid(rel), all_partition_relids(pnode));
Assert( cmd->partoids == NIL );
cmd->partoids = all_oids;
ATPartsPrepSetTableSpace(wqueue, rel, cmd, all_oids);
}
else if (Gp_role == GP_ROLE_EXECUTE && cmd->partoids)
{
ATPartsPrepSetTableSpace(wqueue, rel, cmd, cmd->partoids);
}
else if (Gp_role == GP_ROLE_UTILITY)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SET TABLESPACE is not supported in utility mode")));
}
else
ATPrepSetTableSpace(tab, rel, cmd->name);
pass = AT_PASS_MISC; /* doesn't actually matter */
break;
case AT_SetRelOptions: /* SET (...) */
case AT_ResetRelOptions: /* RESET (...) */
ATSimplePermissionsRelationOrIndex(rel);
/* This command never recurses */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
case AT_SetDistributedBy: /* SET DISTRIBUTED BY */
if ( !recursing ) /* MPP-5772, MPP-5784 */
{
Oid relid = RelationGetRelid(rel);
PartStatus ps = rel_part_status(relid);
if ( recurse ) /* Normal ALTER TABLE */
{
switch (ps)
{
case PART_STATUS_NONE:
case PART_STATUS_ROOT:
case PART_STATUS_LEAF:
break;
case PART_STATUS_INTERIOR:
/*Reject interior branches of partitioned tables.*/
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("can't set the distribution"
" policy of \"%s\"",
RelationGetRelationName(rel)),
errhint("Distribution policy may be set"
" for an entire partitioned table"
" or one of its leaf parts;"
" not for an interior branch."
),
errOmitLocation(true)));
break; /* tidy */
}
}
else /* ALTER TABLE ONLY */
{
switch (ps)
{
case PART_STATUS_NONE:
case PART_STATUS_LEAF:
break;
case PART_STATUS_ROOT:
case PART_STATUS_INTERIOR:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("can't set the distribution policy "
"of ONLY \"%s\"",
RelationGetRelationName(rel)),
errhint("Distribution policy may be set "
"for an entire partitioned table"
" or one of its leaf parts."
),
errOmitLocation(true)));
break; /* tidy */
}
}
if ( ps == PART_STATUS_LEAF )
{
Oid ptrelid = rel_partition_get_master(relid);
List *dist_cnames = lsecond((List*)cmd->def);
/* might be null if no policy set, e.g. just
* a change of storage options...
*/
if (dist_cnames)
{
Relation ptrel = heap_open(ptrelid,
AccessShareLock);
Assert(IsA(dist_cnames, List));
if (! can_implement_dist_on_part(ptrel,
dist_cnames) )
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot SET DISTRIBUTED BY for %s",
RelationGetRelationName(rel)),
errhint("Leaf distribution policy must be"
" random or match that of the"
" entire partitioned table."),
errOmitLocation(true)
));
heap_close(ptrel, AccessShareLock);
}
}
}
ATSimplePermissions(rel, false);
ATSimpleRecursion(wqueue, rel, cmd, recurse);
pass = AT_PASS_MISC;
break;
case AT_EnableTrig: /* ENABLE TRIGGER variants */
case AT_EnableTrigAll:
case AT_EnableTrigUser:
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("ALTER TABLE ... ENABLE TRIGGER is not supported")));
case AT_DisableTrig: /* DISABLE TRIGGER variants */
case AT_DisableTrigAll:
case AT_DisableTrigUser:
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("ALTER TABLE ... DISABLE TRIGGER is not supported")));
ATSimplePermissions(rel, false);
ATPartitionCheck(cmd->subtype, rel, false, recursing);
/* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
case AT_AddInherit: /* INHERIT / NO INHERIT */
case AT_DropInherit:
ATSimplePermissions(rel, false);
ATPartitionCheck(cmd->subtype, rel, true, recursing);
/* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
/* CDB: Partitioned Table commands */
case AT_PartExchange: /* Exchange */
ATPartitionCheck(cmd->subtype, rel, false, recursing);
if (Gp_role == GP_ROLE_UTILITY)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("EXCHANGE is not supported in utility mode")));
pass = AT_PASS_MISC;
ATPrepExchange(rel, (AlterPartitionCmd *)cmd->def);
break;
case AT_PartMerge:
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("ALTER TABLE ... MERGE PARTITION is not supported")));
{
AlterPartitionCmd *pc = (AlterPartitionCmd *) cmd->def;
PgPartRule *prule1 = NULL;
PgPartRule *prule2 = NULL;
PgPartRule *prule3 = NULL;
ListCell *lc;
List *source_rels = NIL;
Relation target = NULL;
ATPartitionCheck(cmd->subtype, rel, false, recursing);
if (Gp_role == GP_ROLE_UTILITY)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MERGE is not supported in utility mode")));
else if (Gp_role == GP_ROLE_DISPATCH)
{
Relation srel;
AlterPartitionId *pid = (AlterPartitionId *) pc->partid;
prule1 = get_part_rule(rel, pid, true, true,
CurrentMemoryContext, NULL, false);
prule2 = get_part_rule(rel, (AlterPartitionId *)pc->arg1,
true, true,
CurrentMemoryContext, NULL, false);
if (pc->arg2)
{
AlterPartitionCmd *pc2 = (AlterPartitionCmd *)pc->arg2;
prule3 = get_part_rule(rel,
(AlterPartitionId *)pc2->partid,
true, true,
CurrentMemoryContext, NULL, false);
}
if (prule1)
{
/* cannot exchange a hash partition */
if ('h' == prule1->pNode->part->parkind)
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot merge HASH partition%s "
"of relation \"%s\"",
prule1->partIdStr,
RelationGetRelationName(rel))));
}
srel = heap_open(prule1->topRule->parchildrelid,
AccessExclusiveLock);
source_rels = lappend(source_rels, srel);
srel = heap_open(prule2->topRule->parchildrelid,
AccessExclusiveLock);
if (prule3)
{
source_rels = lappend(source_rels, srel);
target = heap_open(prule3->topRule->parchildrelid,
AccessExclusiveLock);
}
else
target = srel;
}
else
{
Relation srel;
srel = heap_openrv((RangeVar *)pc->partid,
AccessExclusiveLock);
source_rels = lappend(source_rels, srel);
srel = heap_openrv((RangeVar *)pc->arg1, AccessExclusiveLock);
if (pc->arg2)
{
source_rels = lappend(source_rels, srel);
target = heap_openrv((RangeVar *)pc->arg2,
AccessExclusiveLock);
}
else
target = heap_openrv((RangeVar *)pc->arg1,
AccessExclusiveLock);
}
ATSimplePermissions(rel, false);
foreach(lc, source_rels)
ATSimplePermissions(lfirst(lc), false);
ATSimplePermissions(target, false);
foreach(lc, source_rels)
heap_close(lfirst(lc), NoLock);
heap_close(target, NoLock);
pass = AT_PASS_MISC;
break;
}
case AT_PartSplit: /* Split */
{
AlterPartitionCmd *pc = (AlterPartitionCmd *) cmd->def;
ATPartitionCheck(cmd->subtype, rel, false, recursing);
if (Gp_role == GP_ROLE_UTILITY)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SPLIT is not supported in utility mode")));
else if (Gp_role == GP_ROLE_DISPATCH)
{
PgPartRule *prule1 = NULL;
PgPartRule *prule2 = NULL;
PgPartRule *prule3 = NULL;
PgPartRule *prule_specified = NULL;
Relation target = NULL;
AlterPartitionId *pid = (AlterPartitionId *) pc->partid;
AlterPartitionCmd *cmd2;
Node *at;
bool is_at = true;
List *todo;
ListCell *l;
bool rollup_vals = false;
/*
* Need to do a bunch of validation:
* 0) Target exists
* 0.5) Shouldn't have children
* 1) The usual permissions checks
* 2) Not called on HASH
* 3) AT () parameter falls into constraint specified
* 4) INTO partitions don't exist except for the one being split
*/
/* We'll error out if it doesn't exist */
prule1 = get_part_rule(rel, pid, true, true,
CurrentMemoryContext, NULL, false);
if (prule1->topRule->children)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot split partition with child "
"partitions"),
errhint("Try splitting the child partitions."),
errOmitLocation(true)));
target = heap_open(prule1->topRule->parchildrelid,
AccessExclusiveLock);
if (linitial((List *)pc->arg1))
is_at = false;
if (prule1->topRule->parisdefault &&
prule1->pNode->part->parkind == 'r' &&
is_at)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("AT clause cannot be used when splitting "
"a default RANGE partition"),
errOmitLocation(true)));
}
else if (prule1->pNode->part->parkind == 'l' && !is_at)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot SPLIT DEFAULT PARTITION "
"with LIST"),
errhint("Use SPLIT with the AT clause instead."),
errOmitLocation(true)));
}
if (prule1->pNode->part->parkind == 'h')
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("SPLIT is not supported for "
"HASH partitions"),
errOmitLocation(true)));
if (linitial((List *)pc->arg1))
is_at = false;
todo = (List *)pc->arg1;
pc->arg1 = (Node *)NIL;
foreach(l, todo)
{
List *l1 = NIL;
ListCell *lc;
Node *t = lfirst(l);
if (!t)
{
pc->arg1 = (Node *)lappend((List *)pc->arg1, NULL);
continue;
}
else if (is_at)
l1 = (List *)t;
else if (!is_at)
{
Node *n = t;
PartitionRangeItem *ri = (PartitionRangeItem *)n;
Assert(IsA(n, PartitionRangeItem));
l1 = ri->partRangeVal;
}
if (IsA(linitial(l1), A_Const) ||
IsA(linitial(l1), TypeCast) ||
IsA(linitial(l1), FuncExpr) ||
IsA(linitial(l1), OpExpr))
{
List *new = NIL;
ListCell *lc;
foreach(lc, l1)
new = lappend(new, list_make1(lfirst(lc)));
l1 = new;
rollup_vals = true;
}
else
Insist(IsA(linitial(l1), List));
foreach(lc, l1)
{
List *vals = lfirst(lc);
ListCell *lc2;
int i = 0;
ParseState *pstate = make_parsestate(NULL);
if (prule1->pNode->part->parnatts != list_length(vals))
{
AttrNumber parnatts = prule1->pNode->part->parnatts;
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("partition being split has "
"%i column%s but parameter "
"has %i column%s",
parnatts,
parnatts > 1 ? "s" : "",
list_length(vals),
list_length(vals) > 1 ? "s" : ""),
errOmitLocation(true)));
}
vals = (List *)transformExpressionList(pstate, vals);
free_parsestate(&pstate);
foreach(lc2, vals)
{
Node *n = lfirst(lc2);
AttrNumber attnum =
prule1->pNode->part->paratts[i];
Oid typid =
rel->rd_att->attrs[attnum - 1]->atttypid;
int32 typmod =
rel->rd_att->attrs[attnum - 1]->atttypmod;
char parkind = prule1->pNode->part->parkind;
PartitionByType t = (parkind == 'r') ?
PARTTYP_RANGE : PARTTYP_LIST;
n = coerce_partition_value(n, typid, typmod, t);
lfirst(lc2) = n;
i++;
}
lfirst(lc) = vals;
}
if (rollup_vals)
{
/* now, unroll */
if (prule1->pNode->part->parkind == 'r')
{
List *new = NIL;
ListCell *lc;
Node *n;
foreach(lc, l1)
new = lappend(new, linitial(lfirst(lc)));
if (is_at)
n = (Node *)new;
else
{
PartitionRangeItem *ri = lfirst(l);
ri->partRangeVal = new;
n = (Node *)ri;
}
pc->arg1 = (Node *)lappend((List *)pc->arg1, n);
}
else
{
/* for LIST, leave it as is */
pc->arg1 = (Node *)lappend((List *)pc->arg1,
l1);
}
}
else
pc->arg1 = (Node *)lappend((List *)pc->arg1, l1);
}
at = lsecond((List *)pc->arg1);
if (prule1->pNode->part->parkind == 'l')
{
if (prule1->topRule->parisdefault)
{
/*
* Check that AT() value doesn't exist in any existing
* definition.
*/
}
else
{
/* all elements in AT() must match */
ListCell *lcat;
List *lv = (List *)prule1->topRule->parlistvalues;
int16 nkeys = prule1->pNode->part->parnatts;
List *atlist = (List *)at;
int nmatches = 0;
foreach(lcat, atlist)
{
List *cols = lfirst(lcat);
ListCell *parvals = list_head(lv);
bool found = false;
Assert(list_length(cols) == nkeys);
while (parvals)
{
List *vals = lfirst(parvals);
ListCell *lcv = list_head(vals);
ListCell *lcc = list_head(cols);
int parcol;
bool matched = true;
for (parcol = 0; parcol < nkeys; parcol++)
{
Oid opclass =
prule1->pNode->part->parclass[parcol];
Oid funcid = get_opclass_proc(opclass, 0,
BTORDER_PROC);
Const *v = lfirst(lcv);
Const *c = lfirst(lcc);
Datum d;
if (v->constisnull && c->constisnull)
continue;
else if (v->constisnull || c->constisnull)
{
matched = false;
break;
}
d = OidFunctionCall2(funcid, c->constvalue,
v->constvalue);
if (DatumGetInt32(d) != 0)
{
matched = false;
break;
}
lcv = lnext(lcv);
lcc = lnext(lcc);
}
if (!matched)
{
parvals = lnext(parvals);
continue;
}
else
{
found = true;
nmatches++;
break;
}
}
if (!found)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("AT clause parameter is not "
"a member of the target "
"partition specification"),
errOmitLocation(true)));
}
if (nmatches >= list_length(lv))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("AT clause cannot contain all "
"values in partition%s",
prule1->partIdStr),
errOmitLocation(true)));
}
}
else
{
/* XXX: handle split of default */
if (prule1->topRule->parisdefault)
{
}
else
{
/* at must be in range */
Const *start = NULL;
Const *end = NULL;
Const *atval = NULL;
Oid opclass = prule1->pNode->part->parclass[0];
Oid funcid = get_opclass_proc(opclass, 0,
BTORDER_PROC);
Node *n;
Datum res;
int32 ret;
bool in_range = true;
n = (Node *)linitial((List *)at);
Assert(IsA(n, Const));
atval = (Const *)n;
/* XXX: ignore composite key range for now */
if (prule1->topRule->parrangestart)
start = linitial((List *)prule1->topRule->parrangestart);
/* MPP-6589: allow "open" start or end */
if (start)
{
Insist(exprType((Node *)n) == exprType((Node *)start));
res = OidFunctionCall2(funcid, start->constvalue,
atval->constvalue);
ret = DatumGetInt32(res);
}
else
{
/* MPP-6589: no start - check end */
Assert(prule1->topRule->parrangeend);
ret = -1;
}
if (ret > 0)
in_range = false;
else if (ret == 0)
{
if (!prule1->topRule->parrangestartincl)
in_range = false;
}
else
{
/* in the range, test end */
if (prule1->topRule->parrangeend)
{
end = linitial((List *)prule1->topRule->parrangeend);
res = OidFunctionCall2(funcid,
atval->constvalue,
end->constvalue);
ret = DatumGetInt32(res);
if (ret > 0)
in_range = false;
else if (ret == 0)
if (!prule1->topRule->parrangeendincl)
in_range = false;
}
/* if no end, remains "in range" */
}
if (!in_range)
{
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("AT clause parameter is not "
"a member of the target "
"partition specification"),
errOmitLocation(true)));
}
}
}
/*
* pc->partid == target
* pc->arg1 == AT
* pc->arg2 == AlterPartitionCmd (partid == 1, arg1 = 2)
*/
if (PointerIsValid(pc->arg2))
{
cmd2 = (AlterPartitionCmd *)pc->arg2;
prule2 = get_part_rule(rel,
(AlterPartitionId *)cmd2->partid,
false, false, CurrentMemoryContext,
NULL, false);
prule3 = get_part_rule(rel, (AlterPartitionId *)cmd2->arg1,
false, false, CurrentMemoryContext,
NULL, false);
/* both can't exist */
if (prule2 && prule3)
{
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("both INTO partitions "
"already exist"),
errOmitLocation(true)));
}
else if (prule1->topRule->parisdefault &&
!prule2 && !prule3)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("default partition name missing "
"from INTO clause"),
errOmitLocation(true)));
}
}
/* MPP-18395 begs for a last minute check that we aren't about to
* split into an existing partition. The case that prompts the check
* has to do with specifying an existing partition by name, but it
* may (someday) be possible to specify one by rank or constraint,
* so lets just refuse to use an existing partition that isn't the
* one we're splitting. (We're going to drop this one, so it's okay
* recycle it.)
*
* Examples:
* t is target part to split,
* n, m are new parts,
* x, y are existing parts != t
*
* t -> t, n -- ok
* t -> n, t -- ok
* t -> n, m -- ok
* t -> n, x -- no
* t -> x, n -- no
* t -> x, y -- no (unexpected)
*
* By the way, at this point
* prule1 refers to the part to split
* prule2 refers to the first INTO part
* prule3 refers to the seconde INTO part
*/
Insist( prule2 == NULL || prule3 == NULL );
if ( prule2 != NULL )
prule_specified = prule2;
else if ( prule3 != NULL )
prule_specified = prule3;
else
prule_specified = NULL;
if ( prule_specified &&
prule_specified->topRule->parruleid != prule1->topRule->parruleid
)
{
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("cannot split into an existing partition"),
errOmitLocation(true)));
}
ATSimplePermissions(target, false);
heap_close(target, NoLock);
}
else
{
Insist(IsA(pc->partid, Integer));
Insist(IsA(pc->arg1, RangeVar));
Insist(IsA(pc->arg2, RangeVar));
}
pass = AT_PASS_MISC;
break;
}
case AT_PartAlter: /* Alter */
if ( Gp_role == GP_ROLE_DISPATCH && recurse && ! recursing )
{
AlterTableCmd *basecmd = basic_AT_cmd(cmd);
switch ( basecmd->subtype )
{
case AT_SetTableSpace:
cmd->partoids = basic_AT_oids(rel,cmd);
ATPartsPrepSetTableSpace(wqueue, rel, basecmd, cmd->partoids);
break;
default:
/* Not a for-each-subsumed-part-type command. */
break;
}
}
else if (Gp_role == GP_ROLE_EXECUTE && cmd->partoids)
{
AlterTableCmd *basecmd = basic_AT_cmd(cmd);
switch ( basecmd->subtype )
{
case AT_SetTableSpace:
ATPartsPrepSetTableSpace(wqueue, rel, basecmd, cmd->partoids);
break;
default:
/* Not a for-each-subsumed-part-type command. */
break;
}
}
else if (Gp_role == GP_ROLE_UTILITY)
{
if (basic_AT_cmd(cmd)->subtype == AT_SetTableSpace)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SET TABLESPACE is not supported in utility mode")));
}
pass = AT_PASS_MISC;
break;
case AT_PartSetTemplate: /* Set Subpartition Template */
if (!gp_allow_non_uniform_partitioning_ddl)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Cannot modify subpartition template for partitioned table")));
}
case AT_PartAdd: /* Add */
case AT_PartCoalesce: /* Coalesce */
case AT_PartDrop: /* Drop */
case AT_PartRename: /* Rename */
case AT_PartTruncate: /* Truncate */
case AT_PartAddInternal: /* internal partition creation */
ATSimplePermissions(rel, false);
ATPartitionCheck(cmd->subtype, rel, false, recursing);
/* XXX XXX XXX */
/* This command never recurses */
/* No command-specific prep needed */
/* XXX: check permissions */
pass = AT_PASS_MISC;
break;
case AT_PartModify: /* Modify */
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("ALTER TABLE ... MODIFY PARTITION is not supported")));
break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
pass = 0; /* keep compiler quiet */
break;
}
/* Add the subcommand to the appropriate list for phase 2 */
tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd);
}
/*
* ATRewriteCatalogs
*
* Traffic cop for ALTER TABLE Phase 2 operations. Subcommands are
* dispatched in a "safe" execution order (designed to avoid unnecessary
* conflicts).
*/
static void
ATRewriteCatalogs(List **wqueue)
{
int pass;
ListCell *ltab;
/*
* We process all the tables "in parallel", one pass at a time. This is
* needed because we may have to propagate work from one table to another
* (specifically, ALTER TYPE on a foreign key's PK has to dispatch the
* re-adding of the foreign key constraint to the other table). Work can
* only be propagated into later passes, however.
*/
for (pass = 0; pass < AT_NUM_PASSES; pass++)
{
/* Go through each table that needs to be processed */
foreach(ltab, *wqueue)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
List *subcmds = tab->subcmds[pass];
Relation rel;
ListCell *lcmd;
if (subcmds == NIL)
continue;
/*
* Exclusive lock was obtained by phase 1, needn't get it again
*/
rel = relation_open(tab->relid, NoLock);
foreach(lcmd, subcmds)
{
AlterTableCmd *atc = lfirst(lcmd);
ATExecCmd(wqueue, tab, rel, atc);
/*
* SET DISTRIBUTED BY() calls RelationForgetRelation(),
* which will scribble on rel, so we must re-open it.
*/
if (atc->subtype == AT_SetDistributedBy)
rel = relation_open(tab->relid, NoLock);
}
/*
* After the ALTER TYPE pass, do cleanup work (this is not done in
* ATExecAlterColumnType since it should be done only once if
* multiple columns of a table are altered).
*/
if (pass == AT_PASS_ALTER_TYPE)
ATPostAlterTypeCleanup(wqueue, tab);
relation_close(rel, NoLock);
}
}
}
static void
ATAddToastIfNeeded(List **wqueue,
int oidInfoCount, TableOidInfo * oidInfo, List **poidmap)
{
ListCell *ltab;
int m = 0;
/*
* Check to see if a toast table must be added, if we executed any
* subcommands that might have added a column or changed column storage.
*/
foreach(ltab, *wqueue)
{
Oid tOid = InvalidOid;
Oid tiOid = InvalidOid;
Oid *toastComptypeOid = NULL;
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
bool is_part = !rel_needs_long_lock(tab->relid);
if (tab->relkind == RELKIND_RELATION &&
(tab->subcmds[AT_PASS_ADD_COL] ||
tab->subcmds[AT_PASS_ALTER_TYPE] ||
tab->subcmds[AT_PASS_COL_ATTRS]))
{
Relation rel;
Oid reltablespace;
/*
* For upgrade, don't create a toast table.
*/
if (gp_upgrade_mode)
continue;
/*
* Determine if we need to create a toast table.
*/
rel = heap_open(tab->relid, NoLock);
if (rel->rd_rel->reltoastrelid != InvalidOid)
{ /* Already have one */
heap_close(rel, NoLock);
continue;
}
if (!RelationNeedsToastTable(rel))
{ /* Don't need one */
heap_close(rel, NoLock);
continue;
}
reltablespace = rel->rd_rel->reltablespace;
heap_close(rel, NoLock);
/*
* We do not currently have a toast table, but we need one. If we
* have not already allocated oids in the oidinfo then we need to do
* so now.
*/
if (Gp_role == GP_ROLE_DISPATCH)
{
Relation pg_class_desc = NULL;
Relation pg_type_desc = NULL;
Assert(oidInfo);
Assert(m < oidInfoCount);
if (oidInfo[m].toastOid == InvalidOid)
{
pg_class_desc = heap_open(RelationRelationId, RowExclusiveLock);
oidInfo[m].toastOid = GetNewRelFileNode(reltablespace, false, pg_class_desc, false);
}
if (oidInfo[m].toastIndexOid == InvalidOid)
{
if (!pg_class_desc)
pg_class_desc = heap_open(RelationRelationId, RowExclusiveLock);
oidInfo[m].toastIndexOid = GetNewRelFileNode(reltablespace, false, pg_class_desc, false);
}
if (oidInfo[m].toastComptypeOid == InvalidOid)
{
pg_type_desc = heap_open(RelationRelationId, RowExclusiveLock);
oidInfo[m].toastIndexOid = GetNewRelFileNode(reltablespace, false, pg_type_desc, false);
}
if (pg_class_desc)
heap_close(pg_class_desc, NoLock);
if (pg_type_desc)
heap_close(pg_type_desc, NoLock);
}
/*
* Normal dispatch/execute mode will always have an oidInfo, but if
* we are in utility mode or bootstrap then we might not. In this
* event we fall back to the InvalidOid as initialized above.
*/
if (oidInfo != NULL)
{
Assert(m < oidInfoCount);
tOid = oidInfo[m].toastOid;
tiOid = oidInfo[m].toastIndexOid;
toastComptypeOid = &oidInfo[m].toastComptypeOid;
}
/* Finally create the toast table */
elog(DEBUG2,"Create Toast %d with Oid %u", m, tOid);
AlterTableCreateToastTableWithOid(tab->relid, tOid, tiOid,
toastComptypeOid, is_part);
m++;
}
}
}
/*
* ATExecCmd: dispatch a subcommand to appropriate execution routine
*/
static void
ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd)
{
switch (cmd->subtype)
{
case AT_AddColumn: /* ADD COLUMN */
case AT_AddColumnRecurse:
ATExecAddColumn(tab, rel, (ColumnDef *) cmd->def);
break;
case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
ATExecColumnDefault(rel, cmd->name, cmd->def);
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATExecDropNotNull(rel, cmd->name);
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATExecSetNotNull(tab, rel, cmd->name);
break;
case AT_SetStatistics: /* ALTER COLUMN STATISTICS */
ATExecSetStatistics(rel, cmd->name, cmd->def);
break;
case AT_SetStorage: /* ALTER COLUMN STORAGE */
ATExecSetStorage(rel, cmd->name, cmd->def);
break;
case AT_DropColumn: /* DROP COLUMN */
ATExecDropColumn(wqueue, rel, cmd->name, cmd->behavior, false, false);
break;
case AT_DropColumnRecurse: /* DROP COLUMN with recursion */
ATExecDropColumn(wqueue, rel, cmd->name, cmd->behavior, true, false);
break;
case AT_AddIndex: /* ADD INDEX */
ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
cmd->part_expanded);
break;
case AT_ReAddIndex: /* ADD INDEX */
ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
cmd->part_expanded);
break;
case AT_AddConstraint: /* ADD CONSTRAINT */
ATExecAddConstraint(tab, rel, cmd->def, false);
break;
case AT_AddConstraintRecurse: /* ADD CONSTRAINT with recursion: internal */
ATExecAddConstraint(tab, rel, cmd->def, true);
break;
case AT_DropConstraint: /* DROP CONSTRAINT */
ATExecDropConstraint(rel, cmd->name, cmd->behavior, false);
break;
case AT_DropConstraintQuietly: /* DROP CONSTRAINT for child */
ATExecDropConstraint(rel, cmd->name, cmd->behavior, true);
break;
case AT_AlterColumnType: /* ALTER COLUMN TYPE */
ATExecAlterColumnType(tab, rel, cmd->name, (TypeName *) cmd->def);
break;
case AT_ChangeOwner: /* ALTER OWNER */
ATExecChangeOwner(RelationGetRelid(rel),
get_roleid_checked(cmd->name),
false);
break;
case AT_ClusterOn: /* CLUSTER ON */
ATExecClusterOn(rel, cmd->name);
break;
case AT_DropCluster: /* SET WITHOUT CLUSTER */
ATExecDropCluster(rel);
break;
case AT_DropOids: /* SET WITHOUT OIDS */
/*
* Nothing to do here; we'll have generated a DropColumn
* subcommand to do the real work
*/
break;
case AT_SetTableSpace: /* SET TABLESPACE */
/*
* Nothing to do here; Phase 3 does the work
*/
break;
case AT_SetRelOptions: /* SET (...) */
ATExecSetRelOptions(rel, (List *) cmd->def, false);
break;
case AT_ResetRelOptions: /* RESET (...) */
ATExecSetRelOptions(rel, (List *) cmd->def, true);
break;
case AT_EnableTrig: /* ENABLE TRIGGER name */
ATExecEnableDisableTrigger(rel, cmd->name, true, false);
break;
case AT_DisableTrig: /* DISABLE TRIGGER name */
ATExecEnableDisableTrigger(rel, cmd->name, false, false);
break;
case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */
ATExecEnableDisableTrigger(rel, NULL, true, false);
break;
case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */
ATExecEnableDisableTrigger(rel, NULL, false, false);
break;
case AT_EnableTrigUser: /* ENABLE TRIGGER USER */
ATExecEnableDisableTrigger(rel, NULL, true, true);
break;
case AT_DisableTrigUser: /* DISABLE TRIGGER USER */
ATExecEnableDisableTrigger(rel, NULL, false, true);
break;
case AT_AddInherit:
ATExecAddInherit(rel, (Node *) cmd->def);
break;
case AT_DropInherit:
ATExecDropInherit(rel, (RangeVar *) cmd->def, false);
break;
case AT_SetDistributedBy: /* SET DISTRIBUTED BY */
ATExecSetDistributedBy(rel, (Node *) cmd->def, cmd);
break;
/* CDB: Partitioned Table commands */
case AT_PartAdd: /* Add */
ATPExecPartAdd(tab, rel, (AlterPartitionCmd *) cmd->def,
cmd->subtype);
break;
case AT_PartAlter: /* Alter */
ATPExecPartAlter(wqueue, tab, rel, (AlterPartitionCmd *) cmd->def);
break;
case AT_PartCoalesce: /* Coalesce */
ATPExecPartCoalesce(rel, (AlterPartitionCmd *) cmd->def);
break;
case AT_PartDrop: /* Drop */
ATPExecPartDrop(rel, (AlterPartitionCmd *) cmd->def);
break;
case AT_PartExchange: /* Exchange */
ATPExecPartExchange(tab, rel, (AlterPartitionCmd *) cmd->def);
break;
case AT_PartMerge: /* Merge */
ATPExecPartMerge(rel, (AlterPartitionCmd *) cmd->def);
break;
case AT_PartModify: /* Modify */
ATPExecPartModify(rel, (AlterPartitionCmd *) cmd->def);
break;
case AT_PartRename: /* Rename */
ATPExecPartRename(rel, (AlterPartitionCmd *) cmd->def);
break;
case AT_PartSetTemplate: /* Set Subpartition Template */
ATPExecPartSetTemplate(tab, rel, (AlterPartitionCmd *) cmd->def);
break;
case AT_PartSplit: /* Split */
ATPExecPartSplit(rel, (AlterPartitionCmd *) cmd->def);
break;
case AT_PartTruncate: /* Truncate */
ATPExecPartTruncate(rel, (AlterPartitionCmd *) cmd->def);
break;
case AT_PartAddInternal:
ATExecPartAddInternal(rel, cmd->def);
break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
break;
}
/*
* Bump the command counter to ensure the next subcommand in the sequence
* can see the changes so far
*/
CommandCounterIncrement();
}
/*
* ATRewriteTables: ALTER TABLE phase 3
*/
static void
ATRewriteTables(List **wqueue,
int oidInfoCount, TableOidInfo * oidInfo, List **poidmap)
{
ListCell *ltab;
int m = 0;
/* Go through each table that needs to be checked or rewritten */
foreach(ltab, *wqueue)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
Relation rel;
bool relisshared;
bool relisnailed;
Oid newTableSpace;
Oid oldTableSpace;
Oid relNamespace;
/* We will lock the table iff we decide to actually rewrite it */
rel = relation_open(tab->relid, NoLock);
relisshared = rel->rd_rel->relisshared;
relisnailed = rel->rd_isnailed;
relNamespace = RelationGetNamespace(rel);
oldTableSpace = rel->rd_rel->reltablespace;
newTableSpace = tab->newTableSpace ? tab->newTableSpace : oldTableSpace;
if (tab->newTableSpace) {
RejectAccessTablespace(oldTableSpace, "cannot move from shared tablespace %s");
RejectAccessTablespace(newTableSpace, "cannot move to shared tablespace %s");
/* If this is an index and we are in a shared storage database, reject change index tablespace */
if (rel->rd_rel->relkind == RELKIND_INDEX && is_tablespace_shared_master(get_database_dts(MyDatabaseId)))
RejectAccessTablespace(get_database_dts(MyDatabaseId), "cannot change tablespace on shared storage");
}
/*
* There are two cases where we will rewrite the table, for these cases
* run the necessary sanity checks.
*/
if (tab->newvals != NIL || tab->newTableSpace)
{
/*
* AlterTableInternal is not allowed to execute alter table
* subcommands that require allocating new relfilenodes since it is
* executed in a context that doesn't dispatch the Alter Table.
*
* This should never occur.
*/
if (!oidInfo)
elog(ERROR,
"internal alter table cannot allocate new relfilenodes");
Assert(m < oidInfoCount);
/*
* We can never allow rewriting of shared or nailed-in-cache
* relations, because we can't support changing their relfilenode
* values.
*/
if (relisshared || relisnailed)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite system relation \"%s\"",
RelationGetRelationName(rel))));
/*
* Don't allow rewrite on temp tables of other backends ... their
* local buffer manager is not going to cope.
*/
if (isOtherTempNamespace(relNamespace))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite temporary tables of other sessions")));
}
heap_close(rel, NoLock);
/*
* We only need to rewrite the table if at least one column needs to be
* recomputed, or we are removing the OID column.
*/
if (tab->newvals != NIL || tab->new_dropoids)
{
/* Sanity check of oidInfoCount */
Insist(oidInfoCount==list_length(*wqueue));
/* Build a temporary relation and copy data */
char NewHeapName[NAMEDATALEN];
Oid OIDNewHeap;
ObjectAddress object;
/*
* Create the new heap, using a temporary name in the same
* namespace as the existing table. NOTE: there is some risk of
* collision with user relnames. Working around this seems more
* trouble than it's worth; in particular, we can't create the new
* heap in a different namespace from the old, or we will have
* problems with the TEMP status of temp tables.
*/
snprintf(NewHeapName, sizeof(NewHeapName),
"pg_temp_%u", tab->relid);
List *indexIds = RelationGetIndexList(rel);
OIDNewHeap = make_new_heap(tab->relid, NewHeapName, newTableSpace,
&oidInfo[m], list_length(indexIds) > 0);
list_free(indexIds);
/*
* Copy the heap data into the new table with the desired
* modifications, and test the current data within the table
* against new constraints generated by ALTER TABLE commands.
*/
ATRewriteTable(tab, OIDNewHeap);
/*
* Swap the physical files of the old and new heaps.
*
* MPP-17516 - The third argument to swap_relation_files is
* "swap_stats", and it dictates whether the relpages and
* reltuples of the fake relfile should be copied over to our
* original pg_class tuple. We do not want to do this in the case
* of ALTER TABLE rewrites as the temp relfile will not have correct
* stats.
*/
swap_relation_files(tab->relid, OIDNewHeap, false);
CommandCounterIncrement();
/* Destroy new heap with old filenode */
object.classId = RelationRelationId;
object.objectId = OIDNewHeap;
object.objectSubId = 0;
/*
* The new relation is local to our transaction and we know
* nothing depends on it, so DROP_RESTRICT should be OK.
*/
performDeletion(&object, DROP_RESTRICT);
/* performDeletion does CommandCounterIncrement at end */
/*
* Rebuild each index on the relation (but not the toast table,
* which is all-new anyway). We do not need
* CommandCounterIncrement() because reindex_relation does it.
* reindex_relation also checks for non-NULL poidmap, so we don't
* need to.
*
* It is not legal to reindex without dispatch of the associated
* oids!
*/
reindex_relation(tab->relid, false, false, false,
poidmap, poidmap && Gp_role == GP_ROLE_DISPATCH);
}
else
{
/*
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
if (tab->constraints != NIL || tab->new_notnull)
ATRewriteTable(tab, InvalidOid);
/*
* If we had SET TABLESPACE but no reason to reconstruct tuples,
* just do a block-by-block copy.
*/
if (tab->newTableSpace)
{
populate_oidInfo(&oidInfo[m], newTableSpace, relisshared,
false);
ATExecSetTableSpace(tab->relid, tab->newTableSpace,
&oidInfo[m]);
}
}
m++;
}
/*
* Foreign key constraints are checked in a final pass, since (a) it's
* generally best to examine each one separately, and (b) it's at least
* theoretically possible that we have changed both relations of the
* foreign key, and we'd better have finished both rewrites before we try
* to read the tables.
*/
foreach(ltab, *wqueue)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
Relation rel = NULL;
ListCell *lcon;
foreach(lcon, tab->constraints)
{
NewConstraint *con = lfirst(lcon);
if (con->contype == CONSTR_FOREIGN)
{
FkConstraint *fkconstraint = (FkConstraint *) con->qual;
Relation refrel;
if (rel == NULL)
{
/* Long since locked, no need for another */
rel = heap_open(tab->relid, NoLock);
}
refrel = heap_open(con->refrelid, RowShareLock);
validateForeignKeyConstraint(fkconstraint, rel, refrel);
heap_close(refrel, NoLock);
}
}
if (rel)
heap_close(rel, NoLock);
}
}
/*
* ATRewriteTable: scan or rewrite one table
*
* OIDNewHeap is InvalidOid if we don't need to rewrite
*/
static void
ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
{
Relation oldrel;
Relation newrel;
TupleDesc oldTupDesc;
TupleDesc newTupDesc;
bool needscan = false;
List *notnull_attrs;
int i;
ListCell *l;
EState *estate;
/*
* Open the relation(s). We have surely already locked the existing table.
* In EXCHANGE of partition case, we only need to validate the content
* based on new constraints. oldTupDesc should point to the oldrel's tuple
* descriptor since tab->oldDesc comes from the parent partition.
*/
if (OidIsValid(tab->exchange_relid))
{
oldrel = heap_open(tab->exchange_relid, NoLock);
oldTupDesc = RelationGetDescr(oldrel);
}
else
{
oldrel = heap_open(tab->relid, NoLock);
oldTupDesc = tab->oldDesc;
}
newTupDesc = RelationGetDescr(oldrel); /* includes all mods */
if (OidIsValid(OIDNewHeap))
newrel = heap_open(OIDNewHeap, AccessExclusiveLock);
else
newrel = NULL;
/*
* If we need to rewrite the table, the operation has to be propagated to
* tables that use this table's rowtype as a column type.
*
* (Eventually this will probably become true for scans as well, but at
* the moment a composite type does not enforce any constraints, so it's
* not necessary/appropriate to enforce them just during ALTER.)
*/
if (newrel)
find_composite_type_dependencies(oldrel->rd_rel->reltype,
RelationGetRelationName(oldrel),
NULL);
/*
* Generate the constraint and default execution states
*/
estate = CreateExecutorState();
/* Build the needed expression execution states */
foreach(l, tab->constraints)
{
NewConstraint *con = lfirst(l);
switch (con->contype)
{
case CONSTR_CHECK:
needscan = true;
con->qualstate = (List *)
ExecPrepareExpr((Expr *) con->qual, estate);
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
break;
default:
elog(ERROR, "unrecognized constraint type: %d",
(int) con->contype);
}
}
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
needscan = true;
ex->exprstate = ExecPrepareExpr((Expr *) ex->expr, estate);
}
notnull_attrs = NIL;
if (newrel || tab->new_notnull)
{
/*
* If we are rebuilding the tuples OR if we added any new NOT NULL
* constraints, check all not-null constraints. This is a bit of
* overkill but it minimizes risk of bugs, and heap_attisnull is a
* pretty cheap test anyway.
*/
for (i = 0; i < newTupDesc->natts; i++)
{
if (newTupDesc->attrs[i]->attnotnull &&
!newTupDesc->attrs[i]->attisdropped)
notnull_attrs = lappend_int(notnull_attrs, i);
}
if (notnull_attrs)
needscan = true;
}
if (newrel || needscan)
{
ExprContext *econtext;
Datum *values;
bool *isnull;
TupleTableSlot *oldslot;
TupleTableSlot *newslot;
HeapScanDesc heapscan = NULL;
AppendOnlyScanDesc aoscan = NULL;
HeapTuple htuple = NULL;
MemTuple mtuple = NULL;
MemoryContext oldCxt;
List *dropped_attrs = NIL;
ListCell *lc;
char relstorage = oldrel->rd_rel->relstorage;
econtext = GetPerTupleExprContext(estate);
/*
* Make tuple slots for old and new tuples. Note that even when the
* tuples are the same, the tupDescs might not be (consider ADD COLUMN
* without a default).
*/
oldslot = MakeSingleTupleTableSlot(oldTupDesc);
newslot = MakeSingleTupleTableSlot(newTupDesc);
/* Preallocate values/isnull arrays */
i = Max(newTupDesc->natts, oldTupDesc->natts);
values = (Datum *) palloc0(i * sizeof(Datum));
isnull = (bool *) palloc(i * sizeof(bool));
memset(isnull, true, i * sizeof(bool));
/*
* Any attributes that are dropped according to the new tuple
* descriptor can be set to NULL. We precompute the list of dropped
* attributes to avoid needing to do so in the per-tuple loop.
*/
for (i = 0; i < newTupDesc->natts; i++)
{
if (newTupDesc->attrs[i]->attisdropped)
dropped_attrs = lappend_int(dropped_attrs, i);
}
/*
* Scan through the rows, generating a new row if needed and then
* checking all the constraints.
*/
if(relstorage == RELSTORAGE_HEAP)
{
heapscan = heap_beginscan(oldrel, SnapshotNow, 0, NULL);
/*
* Switch to per-tuple memory context and reset it for each tuple
* produced, so we don't leak memory.
*/
oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
while ((htuple = heap_getnext(heapscan, ForwardScanDirection)) != NULL)
{
if (newrel)
{
Oid tupOid = InvalidOid;
/* Extract data from old tuple */
heap_deform_tuple(htuple, oldTupDesc, values, isnull);
if (oldTupDesc->tdhasoid)
tupOid = HeapTupleGetOid(htuple);
/* Set dropped attributes to null in new tuple */
foreach(lc, dropped_attrs)
isnull[lfirst_int(lc)] = true;
/*
* Process supplied expressions to replace selected columns.
* Expression inputs come from the old tuple.
*/
ExecStoreHeapTuple(htuple, oldslot, InvalidBuffer, false);
econtext->ecxt_scantuple = oldslot;
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate,
econtext,
&isnull[ex->attnum - 1],
NULL);
}
/*
* Form the new tuple. Note that we don't explicitly pfree it,
* since the per-tuple memory context will be reset shortly.
*/
htuple = heap_form_tuple(newTupDesc, values, isnull);
/* Preserve OID, if any */
if (newTupDesc->tdhasoid)
HeapTupleSetOid(htuple, tupOid);
}
/* Now check any constraints on the possibly-changed tuple */
ExecStoreHeapTuple(htuple, newslot, InvalidBuffer, false);
econtext->ecxt_scantuple = newslot;
foreach(l, notnull_attrs)
{
int attn = lfirst_int(l);
if (heap_attisnull(htuple, attn + 1))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" contains null values",
NameStr(newTupDesc->attrs[attn]->attname)),
errOmitLocation(true)));
}
foreach(l, tab->constraints)
{
NewConstraint *con = lfirst(l);
switch (con->contype)
{
case CONSTR_CHECK:
if (!ExecQual(con->qualstate, econtext, true))
{
if (OidIsValid(tab->exchange_relid))
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("exchange table contains "
"a row which violates the "
"partitioning "
"specification of \"%s\"",
get_rel_name(tab->relid)),
errOmitLocation(true)));
else
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
con->name),
errOmitLocation(true)));
}
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
break;
default:
elog(ERROR, "unrecognized constraint type: %d",
(int) con->contype);
}
}
/* Write the tuple out to the new relation */
if (newrel)
simple_heap_insert(newrel, htuple);
ResetExprContext(econtext);
CHECK_FOR_INTERRUPTS();
}
MemoryContextSwitchTo(oldCxt);
heap_endscan(heapscan);
}
else if(relstorage == RELSTORAGE_AOROWS && Gp_role != GP_ROLE_DISPATCH)
{
/*
* When rewriting an appendonly table we choose to always insert
* into the segfile reserved for special purposes (segno #0).
*/
int segno = list_nth_int(tab->ao_segnos, GetQEIndex());;
AppendOnlyInsertDesc aoInsertDesc = NULL;
MemTupleBinding* mt_bind;
if(newrel)
{
ResultRelSegFileInfo *segfileinfo = InitResultRelSegFileInfo(segno, RELSTORAGE_AOROWS, 1);
aoInsertDesc = appendonly_insert_init(newrel, segfileinfo);
}
mt_bind = (newrel ? aoInsertDesc->mt_bind : create_memtuple_binding(newTupDesc));
aoscan = appendonly_beginscan(oldrel, SnapshotNow, 0, NULL);
aoscan->splits = GetFileSplitsOfSegment(tab->scantable_splits,
oldrel->rd_id, GetQEIndex());
/*
* Switch to per-tuple memory context and reset it for each tuple
* produced, so we don't leak memory.
*/
oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
while ((mtuple = appendonly_getnext(aoscan, ForwardScanDirection, oldslot)) != NULL)
{
if (newrel)
{
Oid tupOid = InvalidOid;
TupClearShouldFree(oldslot);
econtext->ecxt_scantuple = oldslot;
/* Extract data from old tuple */
for(i = 0; i < oldslot->tts_tupleDescriptor->natts ; i++)
values[i] = slot_getattr(oldslot, i+1, &isnull[i]);
if (oldTupDesc->tdhasoid)
tupOid = MemTupleGetOid(mtuple, mt_bind);
/* Set dropped attributes to null in new tuple */
foreach(lc, dropped_attrs)
isnull[lfirst_int(lc)] = true;
/*
* Process supplied expressions to replace selected columns.
* Expression inputs come from the old tuple.
*/
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate,
econtext,
&isnull[ex->attnum - 1],
NULL);
}
/*
* Form the new tuple. Note that we don't explicitly pfree it,
* since the per-tuple memory context will be reset shortly.
*/
mtuple = memtuple_form_to(mt_bind,
values, isnull,
NULL, NULL, false);
/* Preserve OID, if any */
if (newTupDesc->tdhasoid)
MemTupleSetOid(mtuple, mt_bind, tupOid);
}
/* Now check any constraints on the possibly-changed tuple */
ExecStoreMemTuple(mtuple, newslot, false);
econtext->ecxt_scantuple = newslot;
foreach(l, notnull_attrs)
{
int attn = lfirst_int(l);
if (memtuple_attisnull(mtuple, mt_bind, attn + 1))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" contains null values",
NameStr(newTupDesc->attrs[attn]->attname)),
errOmitLocation(true)));
}
foreach(l, tab->constraints)
{
NewConstraint *con = lfirst(l);
switch (con->contype)
{
case CONSTR_CHECK:
if (!ExecQual(con->qualstate, econtext, true))
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
con->name),
errOmitLocation(true)));
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
break;
default:
elog(ERROR, "unrecognized constraint type: %d",
(int) con->contype);
}
}
/* Write the tuple out to the new relation */
if (newrel)
{
Oid tupleOid;
AOTupleId aoTupleId;
appendonly_insert(aoInsertDesc, mtuple, &tupleOid, &aoTupleId);
}
ResetExprContext(econtext);
CHECK_FOR_INTERRUPTS();
}
MemoryContextSwitchTo(oldCxt);
appendonly_endscan(aoscan);
if(newrel)
{
StringInfo buf = NULL;
QueryContextDispatchingSendBack sendback = NULL;
if (Gp_role == GP_ROLE_EXECUTE)
buf = PreSendbackChangedCatalog(1);
sendback = CreateQueryContextDispatchingSendBack(1);
aoInsertDesc->sendback = sendback;
sendback->relid = RelationGetRelid(aoInsertDesc->aoi_rel);
appendonly_insert_finish(aoInsertDesc);
if (Gp_role == GP_ROLE_EXECUTE)
AddSendbackChangedCatalogContent(buf, sendback);
DropQueryContextDispatchingSendBack(sendback);
if (Gp_role == GP_ROLE_EXECUTE)
FinishSendbackChangedCatalog(buf);
}
}
else if(relstorage == RELSTORAGE_PARQUET && Gp_role != GP_ROLE_DISPATCH)
{
int segno = list_nth_int(tab->ao_segnos, GetQEIndex());
ParquetInsertDesc idesc = NULL;
ParquetScanDesc sdesc = NULL;
int nvp = oldrel->rd_att->natts;
bool *proj = palloc0(sizeof(bool) * nvp);
/*
* We use the old tuple descriptor instead of oldrel's tuple descriptor,
* which may already contain altered column.
*/
if (oldTupDesc)
{
Assert(oldTupDesc->natts <= nvp);
memset(proj, true, oldTupDesc->natts);
}
else
memset(proj, true, nvp);
if(newrel)
{
ResultRelSegFileInfo *segfileinfo = InitResultRelSegFileInfo(segno, RELSTORAGE_PARQUET, 1);
idesc = parquet_insert_init(newrel, segfileinfo);
}
sdesc = parquet_beginscan(oldrel, SnapshotNow, oldTupDesc, proj);
sdesc->splits = GetFileSplitsOfSegment(tab->scantable_splits,
oldrel->rd_id, GetQEIndex());
parquet_getnext(sdesc, ForwardScanDirection, oldslot);
while(!TupIsNull(oldslot))
{
oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
econtext->ecxt_scantuple = oldslot;
if(newrel)
{
Datum *slotvalues = slot_get_values(oldslot);
bool *slotnulls = slot_get_isnull(oldslot);
Datum *newslotvalues = slot_get_values(newslot);
bool *newslotnulls = slot_get_isnull(newslot);
int nv = Min(oldslot->tts_tupleDescriptor->natts, newslot->tts_tupleDescriptor->natts);
/* aocs does not do oid yet */
Assert(!oldTupDesc->tdhasoid);
Assert(!newTupDesc->tdhasoid);
/* Dropped att should be set correctly by aocs_getnext */
/* transfer values from old to new slot */
memcpy(newslotvalues, slotvalues, sizeof(Datum) * nv);
memcpy(newslotnulls, slotnulls, sizeof(bool) * nv);
TupSetVirtualTupleNValid(newslot, nv);
/* Process supplied expressions */
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
newslotvalues[ex->attnum-1] =
ExecEvalExpr(ex->exprstate,
econtext,
&newslotnulls[ex->attnum-1],
NULL
);
if (ex->attnum > nv)
TupSetVirtualTupleNValid(newslot, ex->attnum);
}
/* Use the modified tuple to check constraints below */
econtext->ecxt_scantuple = newslot;
}
/* Check constraints */
foreach (l, tab->constraints)
{
NewConstraint *con = lfirst(l);
switch(con->contype)
{
case CONSTR_CHECK:
if(!ExecQual(con->qualstate, econtext, true))
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated",
con->name
),
errOmitLocation(true)));
break;
case CONSTR_FOREIGN:
/* Nothing to do */
break;
default:
elog(ERROR, "Unrecognized constraint type: %d",
(int) con->contype);
}
}
if(newrel)
{
MemoryContextSwitchTo(oldCxt);
parquet_insert(idesc, newslot);
}
ResetExprContext(econtext);
CHECK_FOR_INTERRUPTS();
MemoryContextSwitchTo(oldCxt);
parquet_getnext(sdesc, ForwardScanDirection, oldslot);
}
parquet_endscan(sdesc);
if(newrel)
{
StringInfo buf = NULL;
QueryContextDispatchingSendBack sendback = NULL;
if (Gp_role == GP_ROLE_EXECUTE)
buf = PreSendbackChangedCatalog(1);
sendback = CreateQueryContextDispatchingSendBack(
idesc->parquet_rel->rd_att->natts);
idesc->sendback = sendback;
sendback->relid = RelationGetRelid(idesc->parquet_rel);
parquet_insert_finish(idesc);
if (Gp_role == GP_ROLE_EXECUTE)
AddSendbackChangedCatalogContent(buf, sendback);
DropQueryContextDispatchingSendBack(sendback);
if (Gp_role == GP_ROLE_EXECUTE)
FinishSendbackChangedCatalog(buf);
}
pfree(proj);
}
else if(relstorage_is_ao(relstorage) && Gp_role == GP_ROLE_DISPATCH)
{
/*
* All we want to do on the QD during an AO table rewrite
* is to drop the shared memory hash table entry for this
* table if it exists. We must do so since before the
* rewrite we probably have few non-zero segfile entries
* for this table while after the rewrite only segno zero
* will be full and the others will be empty. By dropping
* the hash entry we force refreshing the entry from the
* catalog the next time a write into this AO table comes
* along.
*
* Note that ALTER already took an exclusive lock on the
* old relation so we are guaranteed to not drop the hash
* entry from under any concurrent operation.
*/
LWLockAcquire(AOSegFileLock, LW_EXCLUSIVE);
AORelRemoveHashEntry(RelationGetRelid(oldrel), false);
LWLockRelease(AOSegFileLock);
}
else
{
Assert(!"Invalid relstorage type");
}
ExecDropSingleTupleTableSlot(oldslot);
ExecDropSingleTupleTableSlot(newslot);
}
FreeExecutorState(estate);
/*
* In hawq, here we dispatch the process to segments as opposed to what
* we did in GPDB which is to dispatch the whole statement from the top-level.
* The reason is we need a new relation created, but not yet swapped, so
* the catalog status is somewhat transient. This is the only timing we
* can construct metadata dispatch and delegate to segments. We don't
* need to dispatch the process if ATRewriteTable has no work to do.
*/
if (Gp_role == GP_ROLE_DISPATCH && (newrel || needscan))
{
AlterRewriteTableInfo *ar_tab;
QueryContextInfo *contextdisp;
DispatchDataResult result;
QueryResource *resource = NULL;
int target_segment_num = -1;
List *segment_segnos = NIL;
QueryResource *savedResource = NULL;
{
/*
* calculate the target segment number based on
* the target relation.
*/
GpPolicy *targetPolicy = NULL;
targetPolicy = GpPolicyFetch(CurrentMemoryContext, tab->relid);
Assert(targetPolicy);
target_segment_num = targetPolicy->bucketnum;
pfree(targetPolicy);
if (newrel)
{
targetPolicy = GpPolicyFetch(CurrentMemoryContext, newrel->rd_id);
Assert(targetPolicy);
if (target_segment_num != targetPolicy->bucketnum)
{
pfree(targetPolicy);
elog(ERROR, "target segment num %d IS NOT equal to the bucket number of this relation %d", target_segment_num, targetPolicy->bucketnum);
}
pfree(targetPolicy);
}
}
resource = AllocateResource(QRL_ONCE, 1, 1, target_segment_num, target_segment_num,NULL,0);
savedResource = GetActiveQueryResource();
SetActiveQueryResource(resource);
segment_segnos = SetSegnoForWrite(NIL, 0, target_segment_num, true, true, false);
/*
* We create seg files for the new relation here.
*/
if (newrel)
{
CreateAppendOnlyParquetSegFileForRelationOnMaster(newrel, segment_segnos);
}
/* prepare for the metadata dispatch */
contextdisp = CreateQueryContextInfo();
ar_tab = prepareAlteredTableInfo(tab);
ar_tab->scantable_splits = NIL;
ar_tab->ao_segnos = segment_segnos;
/*
* For the original table, there could be a case to scan
* partition table, while the new rel is always a new single
* relation.
*/
prepareDispatchedCatalogRelation(contextdisp,
tab->relid,
false,
NULL);
ar_tab->scantable_splits = AssignAOSegFileSplitToSegment(tab->relid, NIL,
target_segment_num, ar_tab->scantable_splits);
/*
* Specify the segno directly as we don't have segno mapping here.
*/
if (OidIsValid(OIDNewHeap))
prepareDispatchedCatalogSingleRelation(contextdisp,
OIDNewHeap,
true,
segment_segnos);
FinalizeQueryContextInfo(contextdisp);
ar_tab->newheap_oid = OIDNewHeap;
dispatch_statement_node((Node *) ar_tab, contextdisp, resource, &result);
cdbdisp_iterate_results_sendback(result.result, result.numresults,
UpdateCatalogModifiedOnSegments);
dispatch_free_result(&result);
DropQueryContextInfo(contextdisp);
FreeResource(resource);
SetActiveQueryResource(savedResource);
}
heap_close(oldrel, NoLock);
if (newrel)
heap_close(newrel, NoLock);
}
/*
* ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue
*/
static AlteredTableInfo *
ATGetQueueEntry(List **wqueue, Relation rel)
{
Oid relid = RelationGetRelid(rel);
AlteredTableInfo *tab;
ListCell *ltab;
foreach(ltab, *wqueue)
{
tab = (AlteredTableInfo *) lfirst(ltab);
if (tab->relid == relid)
return tab;
}
/*
* Not there, so add it. Note that we make a copy of the relation's
* existing descriptor before anything interesting can happen to it.
*/
tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo));
tab->relid = relid;
tab->relkind = rel->rd_rel->relkind;
tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel));
*wqueue = lappend(*wqueue, tab);
return tab;
}
/*
* Issue an error, if this is a part of a partitioned table or, in case
* rejectroot is true, if this is a partitioned table itself.
*
* Usually called from ATPrepCmd to validate a subcommand of an ALTER
* TABLE command. Many commands may be invoked on an entire partitioned
* table, but not on just a part. These specify rejectroot as false.
*/
static void
ATPartitionCheck(AlterTableType subtype, Relation rel, bool rejectroot, bool recursing)
{
if (recursing)
return;
if (rejectroot &&
(rel_is_child_partition(RelationGetRelid(rel)) ||
rel_is_partitioned(RelationGetRelid(rel))))
{
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("can't %s \"%s\"; it is a partitioned table or part thereof",
alterTableCmdString(subtype),
RelationGetRelationName(rel)),
errOmitLocation(true)));
}
else if (rel_is_child_partition(RelationGetRelid(rel)))
{
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("can't %s \"%s\"; it is part of a partitioned table",
alterTableCmdString(subtype),
RelationGetRelationName(rel)),
errhint("You may be able to perform the operation on the partitioned table as a whole."),
errOmitLocation(true)));
}
}
/*
* ATSimplePermissions
*
* - Ensure that it is a relation (or possibly a view)
* - Ensure this user is the owner
* - Ensure that it is not a system table
*/
static void
ATSimplePermissions(Relation rel, bool allowView)
{
if (rel->rd_rel->relkind != RELKIND_RELATION)
{
if (allowView)
{
if (rel->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or view",
RelationGetRelationName(rel)),
errOmitLocation(true)));
}
else if (!IsUnderPostmaster &&
(rel->rd_rel->relkind == RELKIND_AOSEGMENTS ||
rel->rd_rel->relkind == RELKIND_AOBLOCKDIR))
{
/*
* Allow ALTER TABLE operations in standard alone mode on
* AO segment tables.
*/
}
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
RelationGetRelationName(rel)),
errOmitLocation(true)));
}
/* Permissions checks */
if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(rel));
if (!allowSystemTableModsDDL && IsSystemRelation(rel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel)),
errOmitLocation(true)));
}
/*
* ATSimplePermissionsRelationOrIndex
*
* - Ensure that it is a relation or an index
* - Ensure this user is the owner
* - Ensure that it is not a system table
*/
static void
ATSimplePermissionsRelationOrIndex(Relation rel)
{
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or index",
RelationGetRelationName(rel)),
errOmitLocation(true)));
/* Permissions checks */
if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(rel));
if (!allowSystemTableModsDDL && IsSystemRelation(rel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel))));
}
/*
* ATSimpleRecursion
*
* Simple table recursion sufficient for most ALTER TABLE operations.
* All direct and indirect children are processed in an unspecified order.
* Note that if a child inherits from the original table via multiple
* inheritance paths, it will be visited just once.
*/
static void
ATSimpleRecursion(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse)
{
/*
* Propagate to children if desired. Non-table relations never have
* children, so no need to search in that case.
*/
if (recurse && rel->rd_rel->relkind == RELKIND_RELATION)
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
List *children;
/* this routine is actually in the planner */
children = find_all_inheritors(relid);
/*
* find_all_inheritors does the recursive search of the inheritance
* hierarchy, so all we have to do is process all of the relids in the
* list that it returns.
*/
foreach(child, children)
{
Oid childrelid = lfirst_oid(child);
Relation childrel;
if (childrelid == relid)
continue;
childrel = relation_open(childrelid, AccessExclusiveLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
ATPrepCmd(wqueue, childrel, cmd, false, true);
relation_close(childrel, NoLock);
}
}
}
/*
* ATOneLevelRecursion
*
* Here, we visit only direct inheritance children. It is expected that
* the command's prep routine will recurse again to find indirect children.
* When using this technique, a multiply-inheriting child will be visited
* multiple times.
*/
/*
static void
ATOneLevelRecursion(List **wqueue, Relation rel,
AlterTableCmd *cmd)
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
List *children;
* this routine is actually in the planner *
children = find_inheritance_children(relid);
foreach(child, children)
{
Oid childrelid = lfirst_oid(child);
Relation childrel;
childrel = relation_open(childrelid, AccessExclusiveLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
ATPrepCmd(wqueue, childrel, cmd, true, true);
relation_close(childrel, NoLock);
}
}
*/
/*
* find_composite_type_dependencies
*
* Check to see if a composite type is being used as a column in some
* other table (possibly nested several levels deep in composite types!).
* Eventually, we'd like to propagate the check or rewrite operation
* into other such tables, but for now, just error out if we find any.
*
* Caller should provide either a table name or a type name (not both) to
* report in the error message, if any.
*
* We assume that functions and views depending on the type are not reasons
* to reject the ALTER. (How safe is this really?)
*/
void
find_composite_type_dependencies(Oid typeOid,
const char *origTblName,
const char *origTypeName)
{
cqContext *pcqCtx;
HeapTuple depTup;
/*
* We scan pg_depend to find those things that depend on the rowtype. (We
* assume we can ignore refobjsubid for a rowtype.)
*/
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_depend "
" WHERE refclassid = :1 "
" AND refobjid = :2 ",
ObjectIdGetDatum(TypeRelationId),
ObjectIdGetDatum(typeOid)));
while (HeapTupleIsValid(depTup = caql_getnext(pcqCtx)))
{
Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
Relation rel;
Form_pg_attribute att;
/* Ignore dependees that aren't user columns of relations */
/* (we assume system columns are never of rowtypes) */
if (pg_depend->classid != RelationRelationId ||
pg_depend->objsubid <= 0)
continue;
rel = relation_open(pg_depend->objid, AccessShareLock);
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION)
{
if (origTblName)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter table \"%s\" because column \"%s\".\"%s\" uses its rowtype",
origTblName,
RelationGetRelationName(rel),
NameStr(att->attname)),
errOmitLocation(true)));
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type \"%s\" because column \"%s\".\"%s\" uses it",
origTypeName,
RelationGetRelationName(rel),
NameStr(att->attname)),
errOmitLocation(true)));
}
else if (OidIsValid(rel->rd_rel->reltype))
{
/*
* A view or composite type itself isn't a problem, but we must
* recursively check for indirect dependencies via its rowtype.
*/
find_composite_type_dependencies(rel->rd_rel->reltype,
origTblName, origTypeName);
}
relation_close(rel, AccessShareLock);
}
caql_endscan(pcqCtx);
}
/*
* ALTER TABLE ADD COLUMN
*
* Adds an additional attribute to a relation making the assumption that
* CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the
* AT_AddColumn AlterTableCmd by analyze.c and added as independent
* AlterTableCmd's.
*/
static void
ATPrepAddColumn(Relation rel, bool recurse, AlterTableCmd *cmd)
{
/*
* If there's an encoding clause, this better be an append only
* column oriented table.
*/
ColumnDef *def = (ColumnDef *)cmd->def;
if (def->encoding)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ENCODING clause supplied for table that is not supported")));
if (def->encoding)
def->encoding = transformStorageEncodingClause(def->encoding);
/*
* If we are told not to recurse, there had better not be any child
* tables; else the addition would put them out of step.
*/
if (!recurse && find_inheritance_children(RelationGetRelid(rel)) != NIL)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column must be added to child tables too")));
}
/*
* We are the master and the table has child(ren):
* internally create and execute new AlterTableStmt(s) on child(ren)
* before dispatching the original AlterTableStmt
* This is to ensure that pg_constraint oid is consistent across segments for
* ALTER TABLE ... ADD COLUMN ... CHECK ...
*/
else if (Gp_role == GP_ROLE_DISPATCH)
{
/*
* Recurse to add the column to child classes.
*
* We must recurse one level at a time, so that multiply-inheriting
* children are visited the right number of times and end up with the
* right attinhcount.
*/
List *children;
ListCell *lchild;
children = find_inheritance_children(RelationGetRelid(rel));
DestReceiver *dest = None_Receiver;
foreach(lchild, children)
{
Oid childrelid = lfirst_oid(lchild);
Relation childrel;
RangeVar *rv;
AlterTableCmd *atc;
AlterTableStmt *ats;
if (childrelid == RelationGetRelid(rel))
continue;
childrel = heap_open(childrelid, AccessShareLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
/* Recurse to child */
atc = copyObject(cmd);
atc->subtype = AT_AddColumnRecurse;
/* Child should see column as singly inherited */
((ColumnDef *) atc->def)->inhcount = 1;
((ColumnDef *) atc->def)->is_local = false;
rv = makeRangeVar(NULL /*catalogname*/, get_namespace_name(RelationGetNamespace(childrel)),
get_rel_name(childrelid), -1);
ats = makeNode(AlterTableStmt);
ats->relation = rv;
ats->cmds = list_make1(atc);
ats->relkind = OBJECT_TABLE;
heap_close(childrel, AccessShareLock);
ProcessUtility((Node *)ats,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
}
}
}
void
ATAddColumn(Relation rel, ColumnDef *colDef)
{
ATExecAddColumn((AlteredTableInfo*)NULL, rel, colDef);
}
static void
ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
ColumnDef *colDef)
{
Oid myrelid = RelationGetRelid(rel);
Relation pgclass,
attrdesc;
HeapTuple reltup;
HeapTuple attributeTuple;
Form_pg_attribute attribute;
FormData_pg_attribute attributeD;
int i;
int minattnum,
maxatts;
HeapTuple typeTuple;
Oid typeOid;
Form_pg_type tform;
Expr *defval;
cqContext cqc;
cqContext cqc2;
cqContext *pclaCtx;
cqContext *patCtx;
/*
* Append Only tables don't support ADD COLUMN when no default value is
* specified. This is because this scenario makes a schema change that
* memtuples can't deal with yet. If a default value is specified than it
* it ok because the whole table will get re-written in phase 3. Let's
* catch this now before doing any real work.
*
* coldDef->raw_default is null in 2 cases - when there is no DEFAULT
* specified and when DEFAULT NULL is specified. We only want to block
* the case when there is no DEFAULT specified. Hence we also check
* colDef->default_is_null as it records the fact that DEFAULT NULL was
* specified.
*/
if ((RelationIsAoRows(rel) || RelationIsParquet(rel)) &&
(!colDef->default_is_null && !colDef->raw_default))
{
ereport(ERROR,
(errcode(ERRCODE_GP_FEATURE_NOT_YET),
errmsg("ADD COLUMN with no default value in append-only tables"
" is not yet supported."),
errOmitLocation(true)
));
}
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
patCtx = caql_beginscan(
caql_addrel(cqclr(&cqc2), attrdesc),
cql("INSERT INTO pg_attribute ",
NULL));
/*
* Are we adding the column to a recursion child? If so, check whether to
* merge with an existing definition for the column.
*/
if (colDef->inhcount > 0)
{
HeapTuple tuple;
cqContext cqc3;
cqContext *patCtx2;
patCtx2 = caql_addrel(cqclr(&cqc3), attrdesc);
/* Does child already have a column by this name? */
tuple = caql_getattname(patCtx2, myrelid, colDef->colname);
if (HeapTupleIsValid(tuple))
{
Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
/* Okay if child matches by type */
if (typenameTypeId(NULL, colDef->typname) != childatt->atttypid ||
colDef->typname->typmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(rel), colDef->colname),
errOmitLocation(true)));
/* Bump the existing child att's inhcount */
childatt->attinhcount++;
caql_update_current(patCtx2, tuple);
/* and Update indexes (implicit) */
heap_freetuple(tuple);
/* Inform the user about the merge */
if (Gp_role == GP_ROLE_EXECUTE)
{
ereport(DEBUG1,
(errmsg("merging definition of column \"%s\" for child \"%s\"",
colDef->colname, RelationGetRelationName(rel)),
errOmitLocation(true)));
}
else
ereport(NOTICE,
(errmsg("merging definition of column \"%s\" for child \"%s\"",
colDef->colname, RelationGetRelationName(rel)),
errOmitLocation(true)));
heap_close(attrdesc, RowExclusiveLock);
return;
}
}
pgclass = heap_open(RelationRelationId, RowExclusiveLock);
pclaCtx = caql_addrel(cqclr(&cqc), pgclass);
reltup = caql_getfirst(
pclaCtx,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(myrelid)));
if (!HeapTupleIsValid(reltup))
elog(ERROR, "cache lookup failed for relation %u", myrelid);
/*
* this test is deliberately not attisdropped-aware, since if one tries to
* add a column matching a dropped column name, it's gonna fail anyway.
*
* For GPDB upgrade, we know that some columns don't exist, so don't go to
* the waste of looking them up. This could be a major cost if we're adding
* 20,000 columns for AO, for example.
*/
if (!(!IsUnderPostmaster && (rel->rd_rel->relkind == RELKIND_AOSEGMENTS ||
rel->rd_rel->relkind == RELKIND_AOBLOCKDIR)))
{
cqContext cqc3;
if (caql_getcount(
caql_addrel(cqclr(&cqc3), attrdesc),
cql("SELECT COUNT(*) FROM pg_attribute "
" WHERE attrelid = :1 "
" AND attname = :2 ",
ObjectIdGetDatum(myrelid),
PointerGetDatum(colDef->colname))))
{
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" of relation \"%s\" already exists",
colDef->colname, RelationGetRelationName(rel)),
errOmitLocation(true)));
}
}
minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts;
maxatts = minattnum + 1;
if (maxatts > MaxHeapAttributeNumber)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("tables can have at most %d columns",
MaxHeapAttributeNumber)));
i = minattnum + 1;
typeTuple = typenameType(NULL, colDef->typname);
tform = (Form_pg_type) GETSTRUCT(typeTuple);
typeOid = HeapTupleGetOid(typeTuple);
/* make sure datatype is legal for a column */
CheckAttributeType(colDef->colname, typeOid);
attributeTuple = heap_addheader(Natts_pg_attribute,
false,
ATTRIBUTE_TUPLE_SIZE,
(void *) &attributeD);
attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple);
attribute->attrelid = myrelid;
namestrcpy(&(attribute->attname), colDef->colname);
attribute->atttypid = typeOid;
attribute->attstattarget = -1;
attribute->attlen = tform->typlen;
attribute->attcacheoff = -1;
attribute->atttypmod = colDef->typname->typmod;
attribute->attnum = i;
attribute->attbyval = tform->typbyval;
attribute->attndims = list_length(colDef->typname->arrayBounds);
attribute->attstorage = tform->typstorage;
attribute->attalign = tform->typalign;
attribute->attnotnull = colDef->is_not_null;
attribute->atthasdef = false;
attribute->attisdropped = false;
attribute->attislocal = colDef->is_local;
attribute->attinhcount = colDef->inhcount;
ReleaseType(typeTuple);
caql_insert(patCtx, attributeTuple); /* implicit update of index as well */
caql_endscan(patCtx);
heap_close(attrdesc, RowExclusiveLock);
/*
* Update number of attributes in pg_class tuple
*/
((Form_pg_class) GETSTRUCT(reltup))->relnatts = maxatts;
caql_update_current(pclaCtx, reltup);/* implicit update of index as well */
heap_freetuple(reltup);
heap_close(pgclass, RowExclusiveLock);
/* Make the attribute's catalog entry visible */
CommandCounterIncrement();
/*
* Store the DEFAULT, if any, in the catalogs
*/
if (colDef->raw_default)
{
RawColumnDefault *rawEnt;
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
rawEnt->attnum = attribute->attnum;
rawEnt->raw_default = copyObject(colDef->raw_default);
/*
* This function is intended for CREATE TABLE, so it processes a
* _list_ of defaults, but we just do one.
*/
AddRelationConstraints(rel, list_make1(rawEnt), NIL);
/* Make the additional catalog changes visible */
CommandCounterIncrement();
}
/*
* Tell Phase 3 to fill in the default expression, if there is one.
*
* If there is no default, Phase 3 doesn't have to do anything, because
* that effectively means that the default is NULL. The heap tuple access
* routines always check for attnum > # of attributes in tuple, and return
* NULL if so, so without any modification of the tuple data we will get
* the effect of NULL values in the new column.
*
* An exception occurs when the new column is of a domain type: the domain
* might have a NOT NULL constraint, or a check constraint that indirectly
* rejects nulls. If there are any domain constraints then we construct
* an explicit NULL default value that will be passed through
* CoerceToDomain processing. (This is a tad inefficient, since it causes
* rewriting the table which we really don't have to do, but the present
* design of domain processing doesn't offer any simple way of checking
* the constraints more directly.)
*
* Note: we use build_column_default, and not just the cooked default
* returned by AddRelationConstraints, so that the right thing happens
* when a datatype's default applies.
*/
defval = (Expr *) build_column_default(rel, attribute->attnum);
if (!defval && GetDomainConstraints(typeOid) != NIL)
{
Oid basetype = getBaseType(typeOid);
defval = (Expr *) makeNullConst(basetype, -1);
defval = (Expr *) coerce_to_target_type(NULL,
(Node *) defval,
basetype,
typeOid,
colDef->typname->typmod,
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
if (defval == NULL) /* should not happen */
elog(ERROR, "failed to coerce base type to domain");
}
/*
* MPP-14367/MPP-19664 - Handling of default NULL for AO/CO tables. Currently
* memtuples cannot deal with the scenario where the number of
* attributes in the tuple data don't match the attnum. We will generate
* an explicit NULL default value and force a rewrite of the table below.
* Note: This is inefficient and there is already another JIRA MPP-5419 open
* to track adding columns without default values to AO tables efficiently.
* When that JIRA is fixed, the following piece of code may be removed
*/
if (!defval && (RelationIsAoRows(rel))
&& colDef->default_is_null)
{
defval = (Expr *) makeNullConst(typeOid, -1);
}
if (defval)
{
NewColumnValue *newval;
newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
newval->attnum = attribute->attnum;
newval->expr = defval;
/* tab is null if this is called by "create or replace view"
* which can't have any default value.
*/
Assert(tab);
tab->newvals = lappend(tab->newvals, newval);
}
/*
* If the new column is NOT NULL, tell Phase 3 it needs to test that.
* Also, "create or replace view" won't have constraint on the column.
*/
Assert(!colDef->is_not_null || tab);
if (tab)
tab->new_notnull |= colDef->is_not_null;
/*
* Add needed dependency entries for the new column.
*/
add_column_datatype_dependency(myrelid, i, attribute->atttypid);
if (colDef->encoding)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ENCODING clause not supported")));
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "ADD COLUMN"
);
}
/*
* Install a column's dependency on its datatype.
*/
static void
add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid)
{
ObjectAddress myself,
referenced;
myself.classId = RelationRelationId;
myself.objectId = relid;
myself.objectSubId = attnum;
referenced.classId = TypeRelationId;
referenced.objectId = typid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
/*
* ALTER TABLE ALTER COLUMN DROP NOT NULL
*/
static void
ATExecDropNotNull(Relation rel, const char *colName)
{
HeapTuple tuple;
AttrNumber attnum;
Relation attr_rel;
List *indexoidlist;
ListCell *indexoidscan;
cqContext cqc;
cqContext *pcqCtx;
/*
* lookup the attribute
*/
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), attr_rel);
tuple = caql_getattname(pcqCtx, RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel)),
errOmitLocation(true)));
attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
/* Prevent them from altering a system attribute */
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
/*
* Check that the attribute is not in a primary key
*/
/* Loop over all indexes on the relation */
indexoidlist = RelationGetIndexList(rel);
foreach(indexoidscan, indexoidlist)
{
Oid indexoid = lfirst_oid(indexoidscan);
HeapTuple indexTuple;
Form_pg_index indexStruct;
int i;
cqContext *pidxCtx;
pidxCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_index "
" WHERE indexrelid = :1 ",
ObjectIdGetDatum(indexoid)));
indexTuple = caql_getnext(pidxCtx);
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexoid);
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
/* If the index is not a primary key, skip the check */
if (indexStruct->indisprimary)
{
/*
* Loop over each attribute in the primary key and see if it
* matches the to-be-altered attribute
*/
for (i = 0; i < indexStruct->indnatts; i++)
{
if (indexStruct->indkey.values[i] == attnum)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" is in a primary key",
colName),
errOmitLocation(true)));
}
}
caql_endscan(pidxCtx);
}
list_free(indexoidlist);
/*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
{
((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE;
caql_update_current(pcqCtx, tuple);
/* and Update indexes (implicit) */
}
heap_close(attr_rel, RowExclusiveLock);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "ALTER COLUMN DROP NOT NULL"
);
}
/*
* ALTER TABLE ALTER COLUMN SET NOT NULL
*/
static void
ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName)
{
HeapTuple tuple;
AttrNumber attnum;
Relation attr_rel;
cqContext cqc;
cqContext *pcqCtx;
/*
* lookup the attribute
*/
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), attr_rel);
tuple = caql_getattname(pcqCtx, RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel)),
errOmitLocation(true)));
attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
/* Prevent them from altering a system attribute */
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
/*
* Okay, actually perform the catalog change ... if needed
*/
if (!((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
{
((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE;
caql_update_current(pcqCtx, tuple);
/* and Update indexes (implicit) */
/* Tell Phase 3 it needs to test the constraint */
tab->new_notnull = true;
}
heap_close(attr_rel, RowExclusiveLock);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "ALTER COLUMN SET NOT NULL"
);
}
/*
* ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
*/
static void
ATExecColumnDefault(Relation rel, const char *colName,
Node *newDefault)
{
AttrNumber attnum;
/*
* get the number of the attribute
*/
attnum = get_attnum(RelationGetRelid(rel), colName);
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel)),
errOmitLocation(true)));
/* Prevent them from altering a system attribute */
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
/*
* Remove any old default for the column. We use RESTRICT here for
* safety, but at present we do not expect anything to depend on the
* default.
*/
RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false);
if (newDefault)
{
/* SET DEFAULT */
RawColumnDefault *rawEnt;
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
rawEnt->attnum = attnum;
rawEnt->raw_default = newDefault;
/*
* This function is intended for CREATE TABLE, so it processes a
* _list_ of defaults, but we just do one.
*/
AddRelationConstraints(rel, list_make1(rawEnt), NIL);
}
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "ALTER COLUMN DEFAULT"
);
}
/*
* ALTER TABLE ALTER COLUMN SET STATISTICS
*/
static void
ATPrepSetStatistics(Relation rel, const char *colName, Node *flagValue)
{
/*
* We do our own permission checking because (a) we want to allow SET
* STATISTICS on indexes (for expressional index columns), and (b) we want
* to allow SET STATISTICS on system catalogs without requiring
* allowSystemTableModsDDL to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or index",
RelationGetRelationName(rel)),
errOmitLocation(true)));
/* Permissions checks */
if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(rel));
}
static void
ATExecSetStatistics(Relation rel, const char *colName, Node *newValue)
{
int newtarget;
Relation attrelation;
HeapTuple tuple;
Form_pg_attribute attrtuple;
cqContext cqc;
cqContext *pcqCtx;
Assert(IsA(newValue, Integer));
newtarget = intVal(newValue);
/*
* Limit target to a sane range
*/
if (newtarget < -1)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("statistics target %d is too low",
newtarget)));
}
else if (newtarget > 1000)
{
newtarget = 1000;
ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("lowering statistics target to %d",
newtarget)));
}
attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), attrelation);
tuple = caql_getattname(pcqCtx, RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
if (attrtuple->attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
attrtuple->attstattarget = newtarget;
caql_update_current(pcqCtx, tuple); /* implicit update of index as well */
heap_freetuple(tuple);
heap_close(attrelation, RowExclusiveLock);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "ALTER COLUMN SET STATISTICS"
);
}
/*
* ALTER TABLE ALTER COLUMN SET STORAGE
*/
static void
ATExecSetStorage(Relation rel, const char *colName, Node *newValue)
{
char *storagemode;
char newstorage;
Relation attrelation;
HeapTuple tuple;
Form_pg_attribute attrtuple;
cqContext cqc;
cqContext *pcqCtx;
Assert(IsA(newValue, String));
storagemode = strVal(newValue);
if (pg_strcasecmp(storagemode, "plain") == 0)
newstorage = 'p';
else if (pg_strcasecmp(storagemode, "external") == 0)
newstorage = 'e';
else if (pg_strcasecmp(storagemode, "extended") == 0)
newstorage = 'x';
else if (pg_strcasecmp(storagemode, "main") == 0)
newstorage = 'm';
else
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid storage type \"%s\"",
storagemode)));
newstorage = 0; /* keep compiler quiet */
}
attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), attrelation);
tuple = caql_getattname(pcqCtx, RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel)),
errOmitLocation(true)));
attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
if (attrtuple->attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
/*
* safety check: do not allow toasted storage modes unless column datatype
* is TOAST-aware.
*/
if (newstorage == 'p' || TypeIsToastable(attrtuple->atttypid))
attrtuple->attstorage = newstorage;
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("column data type %s can only have storage PLAIN",
format_type_be(attrtuple->atttypid))));
caql_update_current(pcqCtx, tuple); /* implicit update of index as well */
heap_freetuple(tuple);
heap_close(attrelation, RowExclusiveLock);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "ALTER COLUMN SET STORAGE"
);
}
/*
* ALTER TABLE DROP COLUMN
*
* DROP COLUMN cannot use the normal ALTER TABLE recursion mechanism,
* because we have to decide at runtime whether to recurse or not depending
* on whether attinhcount goes to zero or not. (We can't check this in a
* static pre-pass because it won't handle multiple inheritance situations
* correctly.)
*/
static void
ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
DropBehavior behavior,
bool recurse, bool recursing)
{
HeapTuple tuple;
Form_pg_attribute targetatt;
AttrNumber attnum;
List *children;
ObjectAddress object;
GpPolicy *policy;
PartitionNode *pn;
cqContext *pcqCtx;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
ATSimplePermissions(rel, false);
/*
* get the number of the attribute
*/
pcqCtx = caql_getattname_scan(NULL, RelationGetRelid(rel), colName);
tuple = caql_get_current(pcqCtx);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
targetatt = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = targetatt->attnum;
/* Can't drop a system attribute, except OID */
if (attnum <= 0 && attnum != ObjectIdAttributeNumber)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot drop system column \"%s\"",
colName),
errOmitLocation(true)));
/* Don't drop inherited columns */
if (targetatt->attinhcount > 0 && !recursing)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot drop inherited column \"%s\"",
colName),
errOmitLocation(true)));
/* better not be a column we partition on */
pn = RelationBuildPartitionDesc(rel, false);
if (pn)
{
List *patts = get_partition_attrs(pn);
if (list_member_int(patts, attnum))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot drop partitioning column \"%s\"",
colName),
errOmitLocation(true)));
/*
* Remove any partition encoding entry
*/
RemovePartitionEncodingByRelidAttribute(RelationGetRelid(rel), attnum);
}
caql_endscan(pcqCtx);
policy = rel->rd_cdbpolicy;
if (policy != NULL && policy->ptype == POLICYTYPE_PARTITIONED)
{
int ia = 0;
for (ia = 0; ia < policy->nattrs; ia++)
{
if (attnum == policy->attrs[ia])
{
policy->nattrs = 0;
policy->bucketnum = 0;
rel->rd_cdbpolicy = GpPolicyCopy(GetMemoryChunkContext(rel), policy);
GpPolicyReplace(RelationGetRelid(rel), policy);
if (Gp_role != GP_ROLE_EXECUTE)
ereport(NOTICE,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Dropping a column that is part of the "
"distribution policy forces a "
"NULL distribution policy"),
errOmitLocation(true)));
}
}
}
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
* use find_all_inheritors to do it in one pass.
*/
children = find_inheritance_children(RelationGetRelid(rel));
if (children)
{
Relation attr_rel;
ListCell *child;
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
foreach(child, children)
{
Oid childrelid = lfirst_oid(child);
Relation childrel;
Form_pg_attribute childatt;
cqContext cqc;
cqContext *attcqCtx;
childrel = heap_open(childrelid, AccessExclusiveLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
attcqCtx = caql_addrel(cqclr(&cqc), attr_rel);
tuple = caql_getattname(attcqCtx, childrelid, colName);
if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
colName, childrelid);
childatt = (Form_pg_attribute) GETSTRUCT(tuple);
if (childatt->attinhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited attribute \"%s\"",
childrelid, colName);
if (recurse)
{
/*
* If the child column has other definition sources,
* just decrement its inheritance count;
* if not or if this is part of a partition
* configuration, recurse to delete it.
*/
if ((childatt->attinhcount == 1 && !childatt->attislocal) ||
pn)
{
/* Time to delete this child column, too */
ATExecDropColumn(wqueue, childrel, colName, behavior, true, true);
}
else
{
/* Child column must survive my deletion */
childatt->attinhcount--;
caql_update_current(attcqCtx, tuple);
/* and Update indexes (implicit) */
/* Make update visible */
CommandCounterIncrement();
}
}
else
{
/*
* If we were told to drop ONLY in this table (no recursion),
* we need to mark the inheritors' attribute as locally
* defined rather than inherited.
*/
childatt->attinhcount--;
childatt->attislocal = true;
caql_update_current(attcqCtx, tuple);
/* and Update indexes (implicit) */
/* Make update visible */
CommandCounterIncrement();
}
heap_freetuple(tuple);
heap_close(childrel, NoLock);
}
heap_close(attr_rel, RowExclusiveLock);
}
/*
* Perform the actual column deletion
*/
object.classId = RelationRelationId;
object.objectId = RelationGetRelid(rel);
object.objectSubId = attnum;
performDeletion(&object, behavior);
/*
* If we dropped the OID column, must adjust pg_class.relhasoids and
* tell Phase 3 to physically get rid of the column.
*/
if (attnum == ObjectIdAttributeNumber)
{
Form_pg_class tuple_class;
AlteredTableInfo *tab;
cqContext *relcqCtx;
relcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationGetRelid(rel))));
tuple = caql_getnext(relcqCtx);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u",
RelationGetRelid(rel));
/* make a copy to update */
tuple = heap_copytuple(tuple);
tuple_class = (Form_pg_class) GETSTRUCT(tuple);
tuple_class->relhasoids = false;
caql_update_current(relcqCtx, tuple);
/* and Update indexes (implicit) */
caql_endscan(relcqCtx);
/* Find or create work queue entry for this table */
tab = ATGetQueueEntry(wqueue, rel);
/* Tell Phase 3 to physically remove the OID column */
tab->new_dropoids = true;
}
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "DROP COLUMN"
);
}
/*
* ALTER TABLE ADD INDEX
*
* There is no such command in the grammar, but the parser converts UNIQUE
* and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets us
* schedule creation of the index at the appropriate time during ALTER.
*/
static void
ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, bool is_rebuild, bool part_expanded)
{
bool check_rights;
bool skip_build;
bool quiet;
Assert(IsA(stmt, IndexStmt));
/* The index should already be built if we are a QE */
if (Gp_role == GP_ROLE_EXECUTE)
return;
/* suppress schema rights check when rebuilding existing index */
check_rights = !is_rebuild;
/* skip index build if phase 3 will have to rewrite table anyway */
skip_build = (tab->newvals != NIL);
/* suppress notices when rebuilding existing index */
quiet = is_rebuild;
DefineIndex(RelationGetRelid(rel), /* relation */
stmt->idxname, /* index name */
InvalidOid, /* no predefined OID */
stmt->accessMethod, /* am name */
stmt->tableSpace,
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
stmt->rangetable,
stmt->options,
stmt->unique,
stmt->primary,
stmt->isconstraint,
true, /* is_alter_table */
check_rights,
skip_build,
quiet,
false,
part_expanded,
stmt);
/*
* If we're the master and this is a partitioned table, cascade index
* creation for all children.
*/
if (Gp_role == GP_ROLE_DISPATCH &&
rel_is_partitioned(RelationGetRelid(rel)))
{
List *children = find_all_inheritors(RelationGetRelid(rel));
ListCell *lc;
bool prefix_match = false;
char *pname = RelationGetRelationName(rel);
char *iname = stmt->idxname ? stmt->idxname : "idx";
DestReceiver *dest = None_Receiver;
/* is the parent relation name a prefix of the index name? */
if (strlen(iname) > strlen(pname) &&
strncmp(pname, iname, strlen(pname)) == 0)
prefix_match = true;
foreach(lc, children)
{
Oid relid = lfirst_oid(lc);
Relation crel;
IndexStmt *istmt;
AlterTableStmt *ats;
AlterTableCmd *atc;
RangeVar *rv;
char *idxname;
if (relid == RelationGetRelid(rel))
continue;
crel = heap_open(relid, AccessShareLock);
istmt = copyObject(stmt);
istmt->idxOids = NIL;
istmt->is_part_child = true;
istmt->constrOid = InvalidOid;
ats = makeNode(AlterTableStmt);
atc = makeNode(AlterTableCmd);
rv = makeRangeVar(NULL /*catalogname*/, get_namespace_name(RelationGetNamespace(rel)),
get_rel_name(relid), -1);
istmt->relation = rv;
if (prefix_match)
{
/*
* If the next character in the index name is '_', absorb
* it, as ChooseRelationName() will add another.
*/
int off = 0;
if (iname[strlen(pname)] == '_')
off = 1;
idxname = ChooseRelationName(RelationGetRelationName(crel),
NULL,
(iname + strlen(pname) + off),
RelationGetNamespace(crel),
NULL);
}
else
idxname = ChooseRelationName(RelationGetRelationName(crel),
NULL,
iname,
RelationGetNamespace(crel),
NULL);
istmt->idxname = idxname;
atc->subtype = AT_AddIndex;
atc->def = (Node *)istmt;
atc->part_expanded = true;
ats->relation = copyObject(istmt->relation);
ats->cmds = list_make1(atc);
ats->relkind = OBJECT_TABLE;
heap_close(crel, AccessShareLock); /* already locked master */
ProcessUtility((Node *)ats,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
}
}
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "ADD INDEX"
);
}
static void
ATExecAddConstraint(AlteredTableInfo *tab, Relation rel, Node *newConstraint, bool recurse)
{
switch (nodeTag(newConstraint))
{
case T_Constraint:
{
Constraint *constr = (Constraint *) newConstraint;
/*
* Currently, we only expect to see CONSTR_CHECK nodes
* arriving here (see the preprocessing done in
* parser/analyze.c). Use a switch anyway to make it easier
* to add more code later.
*/
switch (constr->contype)
{
case CONSTR_CHECK:
{
ATAddCheckConstraint(tab, rel, constr, recurse);
break;
}
default:
elog(ERROR, "unrecognized constraint type: %d",
(int) constr->contype);
}
break;
}
case T_FkConstraint:
{
FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
/*
* Assign or validate constraint name
*/
if (fkconstraint->constr_name)
{
if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
RelationGetRelid(rel),
RelationGetNamespace(rel),
fkconstraint->constr_name))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("constraint \"%s\" for relation \"%s\" already exists",
fkconstraint->constr_name,
RelationGetRelationName(rel)),
errOmitLocation(true)));
}
else
fkconstraint->constr_name =
ChooseConstraintName(RelationGetRelationName(rel),
strVal(linitial(fkconstraint->fk_attrs)),
"fkey",
RelationGetNamespace(rel),
NIL);
ATAddForeignKeyConstraint(tab, rel, fkconstraint);
break;
}
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(newConstraint));
}
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "ADD CONSTRAINT"
);
}
static void
ATAddCheckConstraint(AlteredTableInfo *tab, Relation rel, Constraint *constr, bool recurse)
{
List *newcons;
ListCell *lcon;
List *children;
ListCell *lchild;
/*
* Call AddRelationNewConstraints to do the work.
* It returns a list of cooked constraints.
*/
newcons = AddRelationConstraints(rel, NIL, list_make1(constr));
/* Add each constraint to Phase 3's queue */
foreach(lcon, newcons)
{
CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);
NewConstraint *newcon;
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
newcon->name = ccon->name;
newcon->contype = ccon->contype;
/* ExecQual wants implicit-AND format */
newcon->qual = (Node *) make_ands_implicit((Expr *) ccon->expr);
tab->constraints = lappend(tab->constraints, newcon);
/* Save the actually assigned name if it was defaulted on partitioned table */
if (rel_is_partitioned(RelationGetRelid(rel)) && constr->name == NULL)
constr->name = ccon->name;
}
/*
* If this constraint needs to be recursed, this is a base/parent table,
* and we are the master, then cascade check constraint creation for all children.
* We do it here to synchronize pg_constraint oid.
*/
if (Gp_role == GP_ROLE_DISPATCH && recurse)
{
/* Propagate to children as appropriate. */
children = find_inheritance_children(RelationGetRelid(rel));
if (children == NIL)
return;
DestReceiver *dest = None_Receiver;
foreach(lchild, children)
{
Oid childrelid = lfirst_oid(lchild);
Relation childrel;
Constraint *childconstr;
RangeVar *rv;
AlterTableCmd *atc;
AlterTableStmt *ats;
if (childrelid == RelationGetRelid(rel))
continue;
childrel = heap_open(childrelid, AccessShareLock);
CheckTableNotInUse(childrel, "ALTER TABLE");
/* Recurse to child */
childconstr = copyObject(constr);
childconstr->conoid = InvalidOid;
//Insist(childconstr->name != NULL);
rv = makeRangeVar(NULL /*catalogname*/, get_namespace_name(RelationGetNamespace(childrel)),
get_rel_name(childrelid), -1);
atc = makeNode(AlterTableCmd);
atc->subtype = AT_AddConstraintRecurse;
atc->def = (Node *) childconstr;
ats = makeNode(AlterTableStmt);
ats->relation = rv;
ats->cmds = list_make1(atc);
ats->relkind = OBJECT_TABLE;
heap_close(childrel, AccessShareLock);
ProcessUtility((Node *)ats,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
}
}
}
/*
* Add a foreign-key constraint to a single table
*
* Subroutine for ATExecAddConstraint. Must already hold exclusive
* lock on the rel, and have done appropriate validity/permissions checks
* for it.
*/
static void
ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
FkConstraint *fkconstraint)
{
Relation pkrel;
AclResult aclresult;
int16 pkattnum[INDEX_MAX_KEYS];
int16 fkattnum[INDEX_MAX_KEYS];
Oid pktypoid[INDEX_MAX_KEYS];
Oid fktypoid[INDEX_MAX_KEYS];
Oid opclasses[INDEX_MAX_KEYS];
int i;
int numfks,
numpks;
Oid indexOid;
Oid constrOid;
/*
* Grab an exclusive lock on the pk table, so that someone doesn't delete
* rows out from under us. (Although a lesser lock would do for that
* purpose, we'll need exclusive lock anyway to add triggers to the pk
* table; trying to start with a lesser lock will just create a risk of
* deadlock.)
*/
if (OidIsValid(fkconstraint->old_pktable_oid))
{
pkrel = heap_open(fkconstraint->old_pktable_oid, AccessExclusiveLock);
}
else
{
pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
}
/*
* Validity and permissions checks
*
* Note: REFERENCES permissions checks are redundant with CREATE TRIGGER,
* but we may as well error out sooner instead of later.
*/
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("referenced relation \"%s\" is not a table",
RelationGetRelationName(pkrel)),
errOmitLocation(true)));
aclresult = pg_class_aclcheck(RelationGetRelid(pkrel), GetUserId(),
ACL_REFERENCES);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_CLASS,
RelationGetRelationName(pkrel));
if (!allowSystemTableModsDDL && IsSystemRelation(pkrel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(pkrel)),
errOmitLocation(true)));
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_REFERENCES);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_CLASS,
RelationGetRelationName(rel));
/*
* Disallow reference from permanent table to temp table or vice versa.
* (The ban on perm->temp is for fairly obvious reasons. The ban on
* temp->perm is because other backends might need to run the RI triggers
* on the perm table, but they can't reliably see tuples the owning
* backend has created in the temp table, because non-shared buffers are
* used for temp tables.)
*/
if (isTempNamespace(RelationGetNamespace(pkrel)))
{
if (!isTempNamespace(RelationGetNamespace(rel)))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot reference temporary table from permanent table constraint"),
errOmitLocation(true)));
}
else
{
if (isTempNamespace(RelationGetNamespace(rel)))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot reference permanent table from temporary table constraint"),
errOmitLocation(true)));
}
/*
* Disallow reference to a part of a partitioned table. A foreign key
* must reference the whole partitioned table or none of it.
*/
if ( rel_is_child_partition(RelationGetRelid(pkrel)) )
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot reference just part of a partitioned table"),
errOmitLocation(true)));
}
/*
* Look up the referencing attributes to make sure they exist, and record
* their attnums and type OIDs.
*/
MemSet(pkattnum, 0, sizeof(pkattnum));
MemSet(fkattnum, 0, sizeof(fkattnum));
MemSet(pktypoid, 0, sizeof(pktypoid));
MemSet(fktypoid, 0, sizeof(fktypoid));
MemSet(opclasses, 0, sizeof(opclasses));
numfks = transformColumnNameList(RelationGetRelid(rel),
fkconstraint->fk_attrs,
fkattnum, fktypoid);
/*
* If the attribute list for the referenced table was omitted, lookup the
* definition of the primary key and use it. Otherwise, validate the
* supplied attribute list. In either case, discover the index OID and
* index opclasses, and the attnums and type OIDs of the attributes.
*/
if (fkconstraint->pk_attrs == NIL)
{
numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
&fkconstraint->pk_attrs,
pkattnum, pktypoid,
opclasses);
}
else
{
numpks = transformColumnNameList(RelationGetRelid(pkrel),
fkconstraint->pk_attrs,
pkattnum, pktypoid);
/* Look for an index matching the column list */
indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
opclasses);
}
/* Be sure referencing and referenced column types are comparable */
if (numfks != numpks)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FOREIGN_KEY),
errmsg("number of referencing and referenced columns for foreign key disagree"),
errOmitLocation(true)));
for (i = 0; i < numpks; i++)
{
/*
* pktypoid[i] is the primary key table's i'th key's type fktypoid[i]
* is the foreign key table's i'th key's type
*
* Note that we look for an operator with the PK type on the left;
* when the types are different this is critical because the PK index
* will need operators with the indexkey on the left. (Ordinarily both
* commutator operators will exist if either does, but we won't get
* the right answer from the test below on opclass membership unless
* we select the proper operator.)
*/
Operator o = oper(NULL, list_make1(makeString("=")),
pktypoid[i], fktypoid[i],
true, -1);
if (o == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("foreign key constraint \"%s\" "
"cannot be implemented",
fkconstraint->constr_name),
errdetail("Key columns \"%s\" and \"%s\" "
"are of incompatible types: %s and %s.",
strVal(list_nth(fkconstraint->fk_attrs, i)),
strVal(list_nth(fkconstraint->pk_attrs, i)),
format_type_be(fktypoid[i]),
format_type_be(pktypoid[i])),
errOmitLocation(true)));
/*
* Check that the found operator is compatible with the PK index, and
* generate a warning if not, since otherwise costly seqscans will be
* incurred to check FK validity.
*/
if (Gp_role != GP_ROLE_EXECUTE && !op_in_opclass(oprid(o), opclasses[i]))
ereport(WARNING,
(errmsg("foreign key constraint \"%s\" "
"will require costly sequential scans",
fkconstraint->constr_name),
errdetail("Key columns \"%s\" and \"%s\" "
"are of different types: %s and %s.",
strVal(list_nth(fkconstraint->fk_attrs, i)),
strVal(list_nth(fkconstraint->pk_attrs, i)),
format_type_be(fktypoid[i]),
format_type_be(pktypoid[i])),
errOmitLocation(true)));
ReleaseOperator(o);
}
/*
* Tell Phase 3 to check that the constraint is satisfied by existing rows
* (we can skip this during table creation).
*/
if (!fkconstraint->skip_validation)
{
NewConstraint *newcon;
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
newcon->name = fkconstraint->constr_name;
newcon->contype = CONSTR_FOREIGN;
newcon->refrelid = RelationGetRelid(pkrel);
newcon->qual = (Node *) fkconstraint;
tab->constraints = lappend(tab->constraints, newcon);
}
/*
* Record the FK constraint in pg_constraint.
*/
constrOid = CreateConstraintEntry(fkconstraint->constr_name,
fkconstraint->constrOid,
RelationGetNamespace(rel),
CONSTRAINT_FOREIGN,
fkconstraint->deferrable,
fkconstraint->initdeferred,
RelationGetRelid(rel),
fkattnum,
numfks,
InvalidOid, /* not a domain
* constraint */
RelationGetRelid(pkrel),
pkattnum,
numpks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
fkconstraint->fk_matchtype,
indexOid,
NULL, /* no check constraint */
NULL,
NULL);
fkconstraint->constrOid = constrOid;
/*
* Create the triggers that will enforce the constraint.
*/
createForeignKeyTriggers(rel, RelationGetRelid(pkrel), fkconstraint,
constrOid);
/*
* Close pk table, but keep lock until we've committed.
*/
heap_close(pkrel, NoLock);
}
/*
* transformColumnNameList - transform list of column names
*
* Lookup each name and return its attnum and type OID
*/
static int
transformColumnNameList(Oid relId, List *colList,
int16 *attnums, Oid *atttypids)
{
ListCell *l;
int attnum;
attnum = 0;
foreach(l, colList)
{
char *attname = strVal(lfirst(l));
HeapTuple atttuple;
cqContext *pcqCtx;
pcqCtx = caql_getattname_scan(NULL, relId, attname);
atttuple = caql_get_current(pcqCtx);
if (!HeapTupleIsValid(atttuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" referenced in foreign key constraint does not exist",
attname),
errOmitLocation(true)));
if (attnum >= INDEX_MAX_KEYS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("cannot have more than %d keys in a foreign key",
INDEX_MAX_KEYS)));
attnums[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->attnum;
atttypids[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->atttypid;
caql_endscan(pcqCtx);
attnum++;
}
return attnum;
}
/*
* transformFkeyGetPrimaryKey -
*
* Look up the names, attnums, and types of the primary key attributes
* for the pkrel. Also return the index OID and index opclasses of the
* index supporting the primary key.
*
* All parameters except pkrel are output parameters. Also, the function
* return value is the number of attributes in the primary key.
*
* Used when the column list in the REFERENCES specification is omitted.
*/
static int
transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
List **attnamelist,
int16 *attnums, Oid *atttypids,
Oid *opclasses)
{
List *indexoidlist;
ListCell *indexoidscan;
HeapTuple indexTuple = NULL;
Form_pg_index indexStruct = NULL;
Datum indclassDatum;
bool isnull;
oidvector *indclass;
int i;
cqContext *pcqCtx;
/*
* Get the list of index OIDs for the table from the relcache, and look up
* each one in the pg_index syscache until we find one marked primary key
* (hopefully there isn't more than one such).
*/
*indexOid = InvalidOid;
indexoidlist = RelationGetIndexList(pkrel);
foreach(indexoidscan, indexoidlist)
{
Oid indexoid = lfirst_oid(indexoidscan);
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_index "
" WHERE indexrelid = :1 ",
ObjectIdGetDatum(indexoid)));
indexTuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexoid);
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
if (indexStruct->indisprimary)
{
*indexOid = indexoid;
break;
}
caql_endscan(pcqCtx);
}
list_free(indexoidlist);
/*
* Check that we found it
*/
if (!OidIsValid(*indexOid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("there is no primary key for referenced table \"%s\"",
RelationGetRelationName(pkrel)),
errOmitLocation(true)));
/* Must get indclass the hard way */
indclassDatum = caql_getattr(pcqCtx,
Anum_pg_index_indclass, &isnull);
Assert(!isnull);
indclass = (oidvector *) DatumGetPointer(indclassDatum);
/*
* Now build the list of PK attributes from the indkey definition (we
* assume a primary key cannot have expressional elements)
*/
*attnamelist = NIL;
for (i = 0; i < indexStruct->indnatts; i++)
{
int pkattno = indexStruct->indkey.values[i];
attnums[i] = pkattno;
atttypids[i] = attnumTypeId(pkrel, pkattno);
opclasses[i] = indclass->values[i];
*attnamelist = lappend(*attnamelist,
makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
}
caql_endscan(pcqCtx);
return i;
}
/*
* transformFkeyCheckAttrs -
*
* Make sure that the attributes of a referenced table belong to a unique
* (or primary key) constraint. Return the OID of the index supporting
* the constraint, as well as the opclasses associated with the index
* columns.
*/
Oid
transformFkeyCheckAttrs(Relation pkrel,
int numattrs, int16 *attnums,
Oid *opclasses) /* output parameter */
{
Oid indexoid = InvalidOid;
bool found = false;
List *indexoidlist;
ListCell *indexoidscan;
/*
* Get the list of index OIDs for the table from the relcache, and look up
* each one in the pg_index syscache, and match unique indexes to the list
* of attnums we are given.
*/
indexoidlist = RelationGetIndexList(pkrel);
foreach(indexoidscan, indexoidlist)
{
HeapTuple indexTuple;
cqContext *pcqCtx;
Form_pg_index indexStruct;
int i,
j;
indexoid = lfirst_oid(indexoidscan);
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_index "
" WHERE indexrelid = :1 ",
ObjectIdGetDatum(indexoid)));
indexTuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexoid);
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
/*
* Must have the right number of columns; must be unique and not a
* partial index; forget it if there are any expressions, too
*/
if (indexStruct->indnatts == numattrs &&
indexStruct->indisunique &&
heap_attisnull(indexTuple, Anum_pg_index_indpred) &&
heap_attisnull(indexTuple, Anum_pg_index_indexprs))
{
/* Must get indclass the hard way */
Datum indclassDatum;
bool isnull;
oidvector *indclass;
indclassDatum = caql_getattr(pcqCtx,
Anum_pg_index_indclass, &isnull);
Assert(!isnull);
indclass = (oidvector *) DatumGetPointer(indclassDatum);
/*
* The given attnum list may match the index columns in any order.
* Check that each list is a subset of the other.
*/
for (i = 0; i < numattrs; i++)
{
found = false;
for (j = 0; j < numattrs; j++)
{
if (attnums[i] == indexStruct->indkey.values[j])
{
found = true;
break;
}
}
if (!found)
break;
}
if (found)
{
for (i = 0; i < numattrs; i++)
{
found = false;
for (j = 0; j < numattrs; j++)
{
if (attnums[j] == indexStruct->indkey.values[i])
{
opclasses[j] = indclass->values[i];
found = true;
break;
}
}
if (!found)
break;
}
}
}
caql_endscan(pcqCtx);
if (found)
break;
} /* end foreach(indexoidscan, indexoidlist) */
if (!found)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FOREIGN_KEY),
errmsg("there is no unique constraint matching given keys for referenced table \"%s\"",
RelationGetRelationName(pkrel)),
errOmitLocation(true)));
list_free(indexoidlist);
return indexoid;
}
/*
* Scan the existing rows in a table to verify they meet a proposed FK
* constraint.
*
* Caller must have opened and locked both relations.
*/
static void
validateForeignKeyConstraint(FkConstraint *fkconstraint,
Relation rel,
Relation pkrel)
{
HeapScanDesc scan;
HeapTuple tuple;
Trigger trig;
ListCell *list;
int count;
/*
* See if we can do it with a single LEFT JOIN query. A FALSE result
* indicates we must proceed with the fire-the-trigger method.
*/
if (RI_Initial_Check(fkconstraint, rel, pkrel))
return;
/*
* Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
* if that tuple had just been inserted. If any of those fail, it should
* ereport(ERROR) and that's that.
*/
MemSet(&trig, 0, sizeof(trig));
trig.tgoid = InvalidOid;
trig.tgname = fkconstraint->constr_name;
trig.tgenabled = TRUE;
trig.tgisconstraint = TRUE;
trig.tgconstrrelid = RelationGetRelid(pkrel);
trig.tgdeferrable = FALSE;
trig.tginitdeferred = FALSE;
trig.tgargs = (char **) palloc(sizeof(char *) *
(4 + list_length(fkconstraint->fk_attrs)
+ list_length(fkconstraint->pk_attrs)));
trig.tgargs[0] = trig.tgname;
trig.tgargs[1] = RelationGetRelationName(rel);
trig.tgargs[2] = RelationGetRelationName(pkrel);
trig.tgargs[3] = fkMatchTypeToString(fkconstraint->fk_matchtype);
count = 4;
foreach(list, fkconstraint->fk_attrs)
{
char *fk_at = strVal(lfirst(list));
trig.tgargs[count] = fk_at;
count += 2;
}
count = 5;
foreach(list, fkconstraint->pk_attrs)
{
char *pk_at = strVal(lfirst(list));
trig.tgargs[count] = pk_at;
count += 2;
}
trig.tgnargs = count - 1;
scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
FunctionCallInfoData fcinfo;
TriggerData trigdata;
/*
* Make a call to the trigger function
*
* No parameters are passed, but we do set a context
*/
MemSet(&fcinfo, 0, sizeof(fcinfo));
/*
* We assume RI_FKey_check_ins won't look at flinfo...
*/
trigdata.type = T_TriggerData;
trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
trigdata.tg_relation = rel;
trigdata.tg_trigtuple = tuple;
trigdata.tg_newtuple = NULL;
trigdata.tg_trigger = &trig;
trigdata.tg_trigtuplebuf = scan->rs_cbuf;
trigdata.tg_newtuplebuf = InvalidBuffer;
fcinfo.context = (Node *) &trigdata;
RI_FKey_check_ins(&fcinfo);
}
heap_endscan(scan);
pfree(trig.tgargs);
}
static void
CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid,
FkConstraint *fkconstraint,
ObjectAddress *constrobj, ObjectAddress *trigobj,
bool on_insert)
{
CreateTrigStmt *fk_trigger;
ListCell *fk_attr;
ListCell *pk_attr;
fk_trigger = makeNode(CreateTrigStmt);
fk_trigger->trigname = fkconstraint->constr_name;
fk_trigger->relation = NULL;
fk_trigger->before = false;
fk_trigger->row = true;
/* Either ON INSERT or ON UPDATE */
if (on_insert)
{
fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
fk_trigger->actions[0] = 'i';
fk_trigger->trigOid = fkconstraint->trig1Oid;
}
else
{
fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
fk_trigger->actions[0] = 'u';
fk_trigger->trigOid = fkconstraint->trig2Oid;
}
fk_trigger->actions[1] = '\0';
fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
fk_trigger->constrrel = NULL;
fk_trigger->args = NIL;
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->constr_name));
fk_trigger->args = lappend(fk_trigger->args,
makeString(get_rel_name(myRelOid)));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->pktable->relname));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
if (list_length(fkconstraint->fk_attrs) != list_length(fkconstraint->pk_attrs))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FOREIGN_KEY),
errmsg("number of referencing and referenced columns for foreign key disagree"),
errOmitLocation(true)));
forboth(fk_attr, fkconstraint->fk_attrs,
pk_attr, fkconstraint->pk_attrs)
{
fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
}
trigobj->objectId = CreateTrigger(fk_trigger, myRelOid, refRelOid, true);
if (on_insert)
fkconstraint->trig1Oid = trigobj->objectId;
else
fkconstraint->trig2Oid = trigobj->objectId;
/* Register dependency from trigger to constraint */
recordDependencyOn(trigobj, constrobj, DEPENDENCY_INTERNAL);
/* Make changes-so-far visible */
CommandCounterIncrement();
}
/*
* Create the triggers that implement an FK constraint.
*/
static void
createForeignKeyTriggers(Relation rel, Oid refRelOid,
FkConstraint *fkconstraint, Oid constrOid)
{
Oid myRelOid;
CreateTrigStmt *fk_trigger;
ListCell *fk_attr;
ListCell *pk_attr;
ObjectAddress trigobj,
constrobj;
/*
* Special for Greenplum Database: Ignore foreign keys for now, with warning
*/
if (Gp_role == GP_ROLE_DISPATCH || Gp_role == GP_ROLE_EXECUTE)
{
if (Gp_role == GP_ROLE_DISPATCH)
ereport(WARNING,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Referential integrity (FOREIGN KEY) constraints are not supported in Greenplum Database, will not be enforced."),
errOmitLocation(true)));
}
myRelOid = RelationGetRelid(rel);
/*
* Preset objectAddress fields
*/
constrobj.classId = ConstraintRelationId;
constrobj.objectId = constrOid;
constrobj.objectSubId = 0;
trigobj.classId = TriggerRelationId;
trigobj.objectSubId = 0;
/* Make changes-so-far visible */
CommandCounterIncrement();
/*
* Build and execute a CREATE CONSTRAINT TRIGGER statement for the CHECK
* action for both INSERTs and UPDATEs on the referencing table.
*/
CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
&constrobj, &trigobj, true);
CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
&constrobj, &trigobj, false);
/*
* Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
* DELETE action on the referenced table.
*/
fk_trigger = makeNode(CreateTrigStmt);
fk_trigger->trigname = fkconstraint->constr_name;
fk_trigger->relation = NULL;
fk_trigger->before = false;
fk_trigger->row = true;
fk_trigger->actions[0] = 'd';
fk_trigger->actions[1] = '\0';
fk_trigger->isconstraint = true;
fk_trigger->constrrel = NULL;
switch (fkconstraint->fk_del_action)
{
case FKCONSTR_ACTION_NOACTION:
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
break;
case FKCONSTR_ACTION_RESTRICT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
break;
case FKCONSTR_ACTION_CASCADE:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
break;
case FKCONSTR_ACTION_SETNULL:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
break;
case FKCONSTR_ACTION_SETDEFAULT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
break;
default:
elog(ERROR, "unrecognized FK action type: %d",
(int) fkconstraint->fk_del_action);
break;
}
fk_trigger->args = NIL;
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->constr_name));
fk_trigger->args = lappend(fk_trigger->args,
makeString(get_rel_name(myRelOid)));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->pktable->relname));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
forboth(fk_attr, fkconstraint->fk_attrs,
pk_attr, fkconstraint->pk_attrs)
{
fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
}
fk_trigger->trigOid = fkconstraint->trig3Oid;
trigobj.objectId = CreateTrigger(fk_trigger, refRelOid, myRelOid, true);
fkconstraint->trig3Oid = trigobj.objectId;
/* Register dependency from trigger to constraint */
recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
/* Make changes-so-far visible */
CommandCounterIncrement();
/*
* Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
* UPDATE action on the referenced table.
*/
fk_trigger = makeNode(CreateTrigStmt);
fk_trigger->trigname = fkconstraint->constr_name;
fk_trigger->relation = NULL;
fk_trigger->before = false;
fk_trigger->row = true;
fk_trigger->actions[0] = 'u';
fk_trigger->actions[1] = '\0';
fk_trigger->isconstraint = true;
fk_trigger->constrrel = NULL;
switch (fkconstraint->fk_upd_action)
{
case FKCONSTR_ACTION_NOACTION:
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
break;
case FKCONSTR_ACTION_RESTRICT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
break;
case FKCONSTR_ACTION_CASCADE:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
break;
case FKCONSTR_ACTION_SETNULL:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
break;
case FKCONSTR_ACTION_SETDEFAULT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
break;
default:
elog(ERROR, "unrecognized FK action type: %d",
(int) fkconstraint->fk_upd_action);
break;
}
fk_trigger->args = NIL;
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->constr_name));
fk_trigger->args = lappend(fk_trigger->args,
makeString(get_rel_name(myRelOid)));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->pktable->relname));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
forboth(fk_attr, fkconstraint->fk_attrs,
pk_attr, fkconstraint->pk_attrs)
{
fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
}
fk_trigger->trigOid = fkconstraint->trig4Oid;
trigobj.objectId = CreateTrigger(fk_trigger, refRelOid, myRelOid, true);
fkconstraint->trig4Oid = trigobj.objectId;
/* Register dependency from trigger to constraint */
recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
}
/*
* fkMatchTypeToString -
* convert FKCONSTR_MATCH_xxx code to string to use in trigger args
*/
static char *
fkMatchTypeToString(char match_type)
{
switch (match_type)
{
case FKCONSTR_MATCH_FULL:
return pstrdup("FULL");
case FKCONSTR_MATCH_PARTIAL:
return pstrdup("PARTIAL");
case FKCONSTR_MATCH_UNSPECIFIED:
return pstrdup("UNSPECIFIED");
default:
elog(ERROR, "unrecognized match type: %d",
(int) match_type);
}
return NULL; /* can't get here */
}
/*
* ALTER TABLE DROP CONSTRAINT
*/
static void
ATPrepDropConstraint(List **wqueue, Relation rel,
bool recurse, AlterTableCmd *cmd)
{
/*
* We don't want errors or noise from child tables, so we have to pass
* down a modified command.
*
* This can't result in dropping the partitioning rule because the
* partitioning rule exists only on the part, and ATPrepCmd guards
* against dropping directly from parts.
*/
if (recurse)
{
AlterTableCmd *childCmd = copyObject(cmd);
childCmd->subtype = AT_DropConstraintQuietly;
ATSimpleRecursion(wqueue, rel, childCmd, recurse);
}
}
static void
ATExecDropConstraint(Relation rel, const char *constrName,
DropBehavior behavior, bool quiet)
{
int deleted;
deleted = RemoveRelConstraints(rel, constrName, behavior);
if (!quiet)
{
/* If zero constraints deleted, complain */
if (deleted == 0)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("constraint \"%s\" does not exist",
constrName),
errOmitLocation(true)));
/* Otherwise if more than one constraint deleted, notify */
else if (deleted > 1 && Gp_role != GP_ROLE_EXECUTE)
ereport(NOTICE,
(errmsg("multiple constraints named \"%s\" were dropped",
constrName),
errOmitLocation(true)));
}
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "DROP CONSTRAINT"
);
}
/*
* ALTER COLUMN TYPE
*/
static void
ATPrepAlterColumnType(List **wqueue,
AlteredTableInfo *tab, Relation rel,
bool recurse, bool recursing,
AlterTableCmd *cmd)
{
char *colName = cmd->name;
TypeName *typname = (TypeName *) cmd->def;
HeapTuple tuple;
Form_pg_attribute attTup;
AttrNumber attnum;
Oid targettype;
Node *transform;
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
cqContext *pcqCtx;
/* lookup the attribute so we can check inheritance status */
pcqCtx = caql_getattname_scan(NULL, RelationGetRelid(rel), colName);
tuple = caql_get_current(pcqCtx);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel)),
errOmitLocation(true)));
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = attTup->attnum;
/* Can't alter a system attribute */
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName),
errOmitLocation(true)));
/* Don't alter inherited columns */
if (attTup->attinhcount > 0 && !recursing)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot alter inherited column \"%s\"",
colName),
errOmitLocation(true)));
/* Look up the target type */
targettype = typenameTypeId(NULL, typname);
/* make sure datatype is legal for a column */
CheckAttributeType(colName, targettype);
/*
* Set up an expression to transform the old data value to the new type.
* If a USING option was given, transform and use that expression, else
* just take the old value and try to coerce it. We do this first so that
* type incompatibility can be detected before we waste effort, and
* because we need the expression to be parsed against the original table
* rowtype.
*/
/* GPDB: we always need the RTE */
{
RangeTblEntry *rte;
/* Expression must be able to access vars of old table */
rte = addRangeTableEntryForRelation(pstate,
rel,
NULL,
false,
true);
addRTEtoQuery(pstate, rte, false, true, true);
}
if (cmd->transform)
{
transform = transformExpr(pstate, cmd->transform);
/* It can't return a set */
if (expression_returns_set(transform))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("transform 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 transform expression"),
errOmitLocation(true)));
if (pstate->p_hasAggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in transform expression"),
errOmitLocation(true)));
if (pstate->p_hasWindFuncs)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use window function in transform expression"),
errOmitLocation(true)));
}
else
{
transform = (Node *) makeVar(1, attnum,
attTup->atttypid, attTup->atttypmod,
0);
}
transform = coerce_to_target_type(pstate,
transform, exprType(transform),
targettype, typname->typmod,
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
if (transform == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" cannot be cast to type \"%s\"",
colName, TypeNameToString(typname)),
errOmitLocation(true)));
free_parsestate(&pstate);
/*
* Add a work queue item to make ATRewriteTable update the column
* contents.
*/
newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
newval->attnum = attnum;
newval->expr = (Expr *) transform;
tab->newvals = lappend(tab->newvals, newval);
caql_endscan(pcqCtx);
/*
* The recursion case is handled by ATSimpleRecursion. However, if we are
* told not to recurse, there had better not be any child tables; else the
* alter would put them out of step.
*/
if (recurse)
ATSimpleRecursion(wqueue, rel, cmd, recurse);
else if (!recursing &&
find_inheritance_children(RelationGetRelid(rel)) != NIL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("type of inherited column \"%s\" must be changed in child tables too",
colName),
errOmitLocation(true)));
}
static void
ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
const char *colName, TypeName *typname)
{
HeapTuple heapTup;
Form_pg_attribute attTup;
AttrNumber attnum;
HeapTuple typeTuple;
Form_pg_type tform;
Oid targettype;
Node *defaultexpr;
Relation attrelation;
Relation depRel;
HeapTuple depTup;
GpPolicy * policy = NULL;
bool sourceIsInt = false;
bool targetIsInt = false;
bool sourceIsVarlenA = false;
bool targetIsVarlenA = false;
bool hashCompatible = false;
cqContext cqc;
cqContext cqc2;
cqContext *pcqCtx;
cqContext *patCtx;
attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
patCtx = caql_addrel(cqclr(&cqc2), attrelation);
/* Look up the target column */
heapTup = caql_getattname(patCtx, RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel)),
errOmitLocation(true)));
attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
attnum = attTup->attnum;
/* Check for multiple ALTER TYPE on same column --- can't cope */
if (attTup->atttypid != tab->oldDesc->attrs[attnum - 1]->atttypid ||
attTup->atttypmod != tab->oldDesc->attrs[attnum - 1]->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of column \"%s\" twice",
colName),
errOmitLocation(true)));
/* Look up the target type (should not fail, since prep found it) */
typeTuple = typenameType(NULL, typname);
tform = (Form_pg_type) GETSTRUCT(typeTuple);
targettype = HeapTupleGetOid(typeTuple);
if (targettype == INT4OID ||
targettype == INT2OID ||
targettype == INT8OID)
sourceIsInt = true;
if (attTup->atttypid == INT4OID ||
attTup->atttypid == INT2OID ||
attTup->atttypid == INT8OID)
targetIsInt = true;
if (targettype == VARCHAROID ||
targettype == CHAROID ||
targettype == TEXTOID)
targetIsVarlenA = true;
if (attTup->atttypid == VARCHAROID ||
attTup->atttypid == CHAROID ||
attTup->atttypid == TEXTOID)
sourceIsVarlenA = true;
if (sourceIsInt && targetIsInt)
hashCompatible = true;
else if (sourceIsVarlenA && targetIsVarlenA)
hashCompatible = true;
/*
* If there is a default expression for the column, get it and ensure we
* can coerce it to the new datatype. (We must do this before changing
* the column type, because build_column_default itself will try to
* coerce, and will not issue the error message we want if it fails.)
*
* We remove any implicit coercion steps at the top level of the old
* default expression; this has been agreed to satisfy the principle of
* least surprise. (The conversion to the new column type should act like
* it started from what the user sees as the stored expression, and the
* implicit coercions aren't going to be shown.)
*/
if (attTup->atthasdef)
{
defaultexpr = build_column_default(rel, attnum);
Assert(defaultexpr);
defaultexpr = strip_implicit_coercions(defaultexpr);
defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */
defaultexpr, exprType(defaultexpr),
targettype, typname->typmod,
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
if (defaultexpr == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("default for column \"%s\" cannot be cast to type \"%s\"",
colName, TypeNameToString(typname)),
errOmitLocation(true)));
}
else
defaultexpr = NULL;
/*
* Find everything that depends on the column (constraints, indexes, etc),
* and record enough information to let us recreate the objects.
*
* The actual recreation does not happen here, but only after we have
* performed all the individual ALTER TYPE operations. We have to save
* the info before executing ALTER TYPE, though, else the deparser will
* get confused.
*
* There could be multiple entries for the same object, so we must check
* to ensure we process each one only once. Note: we assume that an index
* that implements a constraint will not show a direct dependency on the
* column.
*/
depRel = heap_open(DependRelationId, RowExclusiveLock);
/* FOR UPDATE due to DELETE later... */
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), depRel),
cql("SELECT * FROM pg_depend "
" WHERE refclassid = :1 "
" AND refobjid = :2 "
" AND refobjsubid = :3 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationRelationId),
ObjectIdGetDatum(RelationGetRelid(rel)),
Int32GetDatum((int32) attnum)));
while (HeapTupleIsValid(depTup = caql_getnext(pcqCtx)))
{
Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
ObjectAddress foundObject;
/* We don't expect any PIN dependencies on columns */
if (foundDep->deptype == DEPENDENCY_PIN)
elog(ERROR, "cannot alter type of a pinned column");
foundObject.classId = foundDep->classid;
foundObject.objectId = foundDep->objid;
foundObject.objectSubId = foundDep->objsubid;
switch (getObjectClass(&foundObject))
{
case OCLASS_CLASS:
{
char relKind = get_rel_relkind(foundObject.objectId);
if (relKind == RELKIND_INDEX)
{
Assert(foundObject.objectSubId == 0);
if (!list_member_oid(tab->changedIndexOids, foundObject.objectId))
{
char * indexdefstring = pg_get_indexdef_string(foundObject.objectId);
tab->changedIndexOids = lappend_oid(tab->changedIndexOids,
foundObject.objectId);
tab->changedIndexDefs = lappend(tab->changedIndexDefs,indexdefstring);
if (Gp_role == GP_ROLE_DISPATCH && indexdefstring &&strstr(indexdefstring," UNIQUE ")!=0 && !hashCompatible)
{
policy = rel->rd_cdbpolicy;
if (policy != NULL && policy->ptype == POLICYTYPE_PARTITIONED)
{
int ia = 0;
if (cdbRelSize(rel) != 0)
for (ia = 0; ia < policy->nattrs; ia++)
{
if (attnum == policy->attrs[ia])
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Changing the type of a column that is part of the distribution policy and used in a unique index is not allowed"),
errOmitLocation(true)));
}
}
}
}
}
}
else if (relKind == RELKIND_SEQUENCE)
{
/*
* This must be a SERIAL column's sequence. We need
* not do anything to it.
*/
Assert(foundObject.objectSubId == 0);
}
else
{
/* Not expecting any other direct dependencies... */
elog(ERROR, "unexpected object depending on column: %s",
getObjectDescription(&foundObject));
}
break;
}
case OCLASS_CONSTRAINT:
Assert(foundObject.objectSubId == 0);
if (!list_member_oid(tab->changedConstraintOids,
foundObject.objectId))
{
char *defstring = pg_get_constraintdef_string(foundObject.objectId);
if (Gp_role == GP_ROLE_DISPATCH && !hashCompatible)
if (strstr(defstring," UNIQUE")!=0 ||
strstr(defstring,"PRIMARY KEY")!=0 )
{
policy = rel->rd_cdbpolicy;
if (policy != NULL && policy->ptype == POLICYTYPE_PARTITIONED)
{
int ia = 0;
if (cdbRelSize(rel) != 0)
for (ia = 0; ia < policy->nattrs; ia++)
{
if (attnum == policy->attrs[ia])
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Changing the type of a column that is used in a UNIQUE or PRIMARY KEY constraint is not allowed"),
errOmitLocation(true)));
}
}
}
}
/*
* Put NORMAL dependencies at the front of the list and
* AUTO dependencies at the back. This makes sure that
* foreign-key constraints depending on this column will
* be dropped before unique or primary-key constraints of
* the column; which we must have because the FK
* constraints depend on the indexes belonging to the
* unique constraints.
*/
if (foundDep->deptype == DEPENDENCY_NORMAL)
{
tab->changedConstraintOids =
lcons_oid(foundObject.objectId,
tab->changedConstraintOids);
tab->changedConstraintDefs =
lcons(defstring,
tab->changedConstraintDefs);
}
else
{
tab->changedConstraintOids =
lappend_oid(tab->changedConstraintOids,
foundObject.objectId);
tab->changedConstraintDefs =
lappend(tab->changedConstraintDefs,
defstring);
}
}
break;
case OCLASS_REWRITE:
/* XXX someday see if we can cope with revising views */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject),
colName),
errOmitLocation(true)));
break;
case OCLASS_DEFAULT:
/*
* Ignore the column's default expression, since we will fix
* it below.
*/
Assert(defaultexpr);
break;
case OCLASS_PROC:
case OCLASS_TYPE:
case OCLASS_CAST:
case OCLASS_CONVERSION:
case OCLASS_LANGUAGE:
case OCLASS_OPERATOR:
case OCLASS_OPCLASS:
case OCLASS_TRIGGER:
case OCLASS_SCHEMA:
/*
* We don't expect any of these sorts of objects to depend on
* a column.
*/
elog(ERROR, "unexpected object depending on column: %s",
getObjectDescription(&foundObject));
break;
default:
elog(ERROR, "unrecognized object class: %u",
foundObject.classId);
}
}
caql_endscan(pcqCtx);
/*
* Now scan for dependencies of this column on other things. The only
* thing we should find is the dependency on the column datatype, which we
* want to remove.
*/
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), depRel),
cql("SELECT * FROM pg_depend "
" WHERE classid = :1 "
" AND objid = :2 "
" AND objsubid = :3 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationRelationId),
ObjectIdGetDatum(RelationGetRelid(rel)),
Int32GetDatum((int32) attnum)));
while (HeapTupleIsValid(depTup = caql_getnext(pcqCtx)))
{
Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
if (foundDep->deptype != DEPENDENCY_NORMAL)
elog(ERROR, "found unexpected dependency type '%c'",
foundDep->deptype);
if (foundDep->refclassid != TypeRelationId ||
foundDep->refobjid != attTup->atttypid)
elog(ERROR, "found unexpected dependency for column");
caql_delete_current(pcqCtx);
}
caql_endscan(pcqCtx);
heap_close(depRel, RowExclusiveLock);
policy = rel->rd_cdbpolicy;
if (policy != NULL && policy->ptype == POLICYTYPE_PARTITIONED &&
!hashCompatible)
{
if (Gp_role != GP_ROLE_EXECUTE)
{
int ia = 0;
ListCell *lc;
List *partkeys;
partkeys = rel_partition_key_attrs(rel->rd_id);
foreach (lc, partkeys)
{
if ( attnum == lfirst_int(lc) )
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used in "
"a partitioning key"),
errOmitLocation(true)));
}
if (cdbRelSize(rel) != 0)
{
for (ia = 0; ia < policy->nattrs; ia++)
{
if (attnum == policy->attrs[ia])
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used in "
"a distribution policy"),
errOmitLocation(true)));
}
}
}
}
/*
* Here we go --- change the recorded column type. (Note heapTup is a
* copy of the syscache entry, so okay to scribble on.)
*/
attTup->atttypid = targettype;
attTup->atttypmod = typname->typmod;
attTup->attndims = list_length(typname->arrayBounds);
attTup->attlen = tform->typlen;
attTup->attbyval = tform->typbyval;
attTup->attalign = tform->typalign;
attTup->attstorage = tform->typstorage;
ReleaseType(typeTuple);
caql_update_current(patCtx, heapTup);/* implicit update of index as well */
heap_close(attrelation, RowExclusiveLock);
/* Install dependency on new datatype */
add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype);
/*
* Drop any pg_statistic entry for the column, since it's now wrong type
*/
RemoveStatistics(RelationGetRelid(rel), attnum);
/*
* Update the default, if present, by brute force --- remove and re-add
* the default. Probably unsafe to take shortcuts, since the new version
* may well have additional dependencies. (It's okay to do this now,
* rather than after other ALTER TYPE commands, since the default won't
* depend on other column types.)
*/
if (defaultexpr)
{
/* Must make new row visible since it will be updated again */
CommandCounterIncrement();
/*
* We use RESTRICT here for safety, but at present we do not expect
* anything to depend on the default.
*/
RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true);
StoreAttrDefault(rel, attnum, nodeToString(defaultexpr));
}
/* Cleanup */
heap_freetuple(heapTup);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "ALTER COLUMN TYPE"
);
}
/*
* Cleanup after we've finished all the ALTER TYPE operations for a
* particular relation. We have to drop and recreate all the indexes
* and constraints that depend on the altered columns.
*/
static void
ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab)
{
ObjectAddress obj;
ListCell *def_item;
ListCell *oid_item;
/*
* Re-parse the index and constraint definitions, and attach them to the
* appropriate work queue entries. We do this before dropping because in
* the case of a FOREIGN KEY constraint, we might not yet have exclusive
* lock on the table the constraint is attached to, and we need to get
* that before dropping. It's safe because the parser won't actually look
* at the catalogs to detect the existing entry.
*
* We can't rely on the output of deparsing to tell us which relation
* to operate on, because concurrent activity might have made the name
* resolve differently. Instead, we've got to use the OID of the
* constraint or index we're processing to figure out which relation
* to operate on.
*/
forboth(oid_item, tab->changedIndexOids,
def_item, tab->changedIndexDefs)
{
/*
* Temporary workaround for MPP-1318. INDEX CREATE is dispatched
* immediately, which unfortunately breaks the ALTER work queue.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter indexed column"),
errhint("DROP the index first, and recreate it after the ALTER"),
errOmitLocation(true)));
/*Oid oldId = lfirst_oid(oid_item);
Oid relid;
relid = IndexGetRelation(oldId);
ATPostAlterTypeParse(relid, InvalidOid,
(char *) lfirst(def_item),
wqueue, oldOid);*/
}
/* Reuse old constraint OID for new constraint */
forboth(oid_item, tab->changedConstraintOids,
def_item, tab->changedConstraintDefs)
{
Oid oldId = lfirst_oid(oid_item);
Oid relid;
Oid confrelid;
get_constraint_relation_oids(oldId, &relid, &confrelid);
ATPostAlterTypeParse(relid, confrelid,
(char *) lfirst(def_item),
wqueue, oldId);
}
/*
* Now we can drop the existing constraints and indexes --- constraints
* first, since some of them might depend on the indexes. In fact, we
* have to delete FOREIGN KEY constraints before UNIQUE constraints, but
* we already ordered the constraint list to ensure that would happen. It
* should be okay to use DROP_RESTRICT here, since nothing else should be
* depending on these objects.
*/
foreach(oid_item, tab->changedConstraintOids)
{
obj.classId = ConstraintRelationId;
obj.objectId = lfirst_oid(oid_item);
obj.objectSubId = 0;
performDeletion(&obj, DROP_RESTRICT);
}
foreach(oid_item, tab->changedIndexOids)
{
obj.classId = RelationRelationId;
obj.objectId = lfirst_oid(oid_item);
obj.objectSubId = 0;
performDeletion(&obj, DROP_RESTRICT);
}
/*
* The objects will get recreated during subsequent passes over the work
* queue.
*/
}
static void
ATPostAlterTypeParse(Oid oldRelId, Oid refRelId, char *cmd, List **wqueue, Oid constrOid)
{
List *raw_parsetree_list;
List *querytree_list;
ListCell *list_item;
Relation rel;
/*
* We expect that we only have to do raw parsing and parse analysis, not
* any rule rewriting, since these will all be utility statements.
*/
raw_parsetree_list = raw_parser(cmd);
querytree_list = NIL;
foreach(list_item, raw_parsetree_list)
{
Node *parsetree = (Node *) lfirst(list_item);
querytree_list = list_concat(querytree_list,
parse_analyze(parsetree, cmd, NULL, 0));
}
/* Caller should already have acquired whatever lock we need. */
rel = relation_open(oldRelId, NoLock);
/*
* Attach each generated command to the proper place in the work queue.
* Note this could result in creation of entirely new work-queue entries.
*/
foreach(list_item, querytree_list)
{
Query *query = (Query *) lfirst(list_item);
AlteredTableInfo *tab;
Assert(IsA(query, Query));
Assert(query->commandType == CMD_UTILITY);
switch (nodeTag(query->utilityStmt))
{
case T_IndexStmt:
{
IndexStmt *stmt = (IndexStmt *) query->utilityStmt;
AlterTableCmd *newcmd;
tab = ATGetQueueEntry(wqueue, rel);
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_ReAddIndex;
newcmd->def = (Node *) stmt;
tab->subcmds[AT_PASS_OLD_INDEX] =
lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd);
break;
}
case T_AlterTableStmt:
{
AlterTableStmt *stmt = (AlterTableStmt *) query->utilityStmt;
ListCell *lcmd;
tab = ATGetQueueEntry(wqueue, rel);
foreach(lcmd, stmt->cmds)
{
AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
switch (cmd->subtype)
{
case AT_AddIndex:
cmd->subtype = AT_ReAddIndex;
tab->subcmds[AT_PASS_OLD_INDEX] =
lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
break;
case AT_AddConstraint:
/* Reuse old constraint OID for new constraint */
if (nodeTag(cmd->def) == T_Constraint)
((Constraint *)cmd->def)->conoid = constrOid;
else if (nodeTag(cmd->def) == T_FkConstraint)
((FkConstraint *)cmd->def)->constrOid = constrOid;
tab->subcmds[AT_PASS_OLD_CONSTR] =
lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
break;
default:
elog(ERROR, "unexpected statement type: %d",
(int) cmd->subtype);
}
}
break;
}
default:
elog(ERROR, "unexpected statement type: %d",
(int) nodeTag(query->utilityStmt));
}
}
relation_close(rel, NoLock);
}
/*
* ALTER TABLE OWNER
*
* recursing is true if we are recursing from a table to its indexes,
* sequences, or toast table. We don't allow the ownership of those things to
* be changed separately from the parent table. Also, we can skip permission
* checks (this is necessary not just an optimization, else we'd fail to
* handle toast tables properly).
*
* recursing is also true if ALTER TYPE OWNER is calling us to fix up a
* free-standing composite type.
*/
void
ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing)
{
Relation target_rel;
HeapTuple tuple;
Form_pg_class tuple_class;
cqContext *pcqCtx;
/*
* Get exclusive lock till end of transaction on the target table. Use
* relation_open so that we can work on indexes and sequences.
*/
target_rel = relation_open(relationOid, AccessExclusiveLock);
/* Get its pg_class tuple, too */
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(relationOid)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relationOid);
tuple_class = (Form_pg_class) GETSTRUCT(tuple);
/* Can we change the ownership of this tuple? */
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
case RELKIND_VIEW:
/* ok to change owner */
break;
case RELKIND_INDEX:
if (!recursing)
{
/*
* Because ALTER INDEX OWNER used to be allowed, and in fact
* is generated by old versions of pg_dump, we give a warning
* and do nothing rather than erroring out. Also, to avoid
* unnecessary chatter while restoring those old dumps, say
* nothing at all if the command would be a no-op anyway.
*/
if (tuple_class->relowner != newOwnerId)
ereport(WARNING,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change owner of index \"%s\"",
NameStr(tuple_class->relname)),
errhint("Change the ownership of the index's table, instead."),
errOmitLocation(true)));
/* quick hack to exit via the no-op path */
newOwnerId = tuple_class->relowner;
}
break;
case RELKIND_SEQUENCE:
if (!recursing &&
tuple_class->relowner != newOwnerId)
{
/* if it's an owned sequence, disallow changing it by itself */
Oid tableId;
int32 colId;
if (sequenceIsOwned(relationOid, &tableId, &colId))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot change owner of sequence \"%s\"",
NameStr(tuple_class->relname)),
errdetail("Sequence \"%s\" is linked to table \"%s\".",
NameStr(tuple_class->relname),
get_rel_name(tableId)),
errOmitLocation(true)));
}
break;
case RELKIND_TOASTVALUE:
case RELKIND_AOSEGMENTS:
case RELKIND_AOBLOCKDIR:
case RELKIND_COMPOSITE_TYPE:
if (recursing)
break;
/* FALL THRU */
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or sequence",
NameStr(tuple_class->relname)),
errOmitLocation(true)));
}
/*
* If the new owner is the same as the existing owner, consider the
* command to have succeeded. This is for dump restoration purposes.
*/
if (tuple_class->relowner != newOwnerId)
{
Datum repl_val[Natts_pg_class];
bool repl_null[Natts_pg_class];
bool repl_repl[Natts_pg_class];
Acl *newAcl;
Datum aclDatum;
bool isNull;
HeapTuple newtuple;
/* skip permission checks when recursing to index or toast table */
if (!recursing)
{
/* Superusers can always do it */
if (!superuser())
{
Oid namespaceOid = tuple_class->relnamespace;
AclResult aclresult;
/* Otherwise, must be owner of the existing object */
if (!pg_class_ownercheck(relationOid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(target_rel));
/* Must be able to become new owner */
check_is_member_of_role(GetUserId(), newOwnerId);
/* New owner must have CREATE privilege on namespace */
aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId,
ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
get_namespace_name(namespaceOid));
}
}
memset(repl_null, false, sizeof(repl_null));
memset(repl_repl, false, sizeof(repl_repl));
repl_repl[Anum_pg_class_relowner - 1] = true;
repl_val[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(newOwnerId);
/*
* Determine the modified ACL for the new owner. This is only
* necessary when the ACL is non-null.
*/
aclDatum = caql_getattr(pcqCtx,
Anum_pg_class_relacl,
&isNull);
if (!isNull)
{
newAcl = aclnewowner(DatumGetAclP(aclDatum),
tuple_class->relowner, newOwnerId);
repl_repl[Anum_pg_class_relacl - 1] = true;
repl_val[Anum_pg_class_relacl - 1] = PointerGetDatum(newAcl);
}
newtuple = caql_modify_current(pcqCtx, repl_val, repl_null, repl_repl);
caql_update_current(pcqCtx, newtuple);
/* and Update indexes (implicit) */
heap_freetuple(newtuple);
/*
* Update owner dependency reference, if any. A composite type has
* none, because it's tracked for the pg_type entry instead of here;
* indexes don't have their own entries either.
*/
if (tuple_class->relkind != RELKIND_COMPOSITE_TYPE &&
tuple_class->relkind != RELKIND_INDEX &&
tuple_class->relkind != RELKIND_TOASTVALUE)
changeDependencyOnOwner(RelationRelationId, relationOid,
newOwnerId);
/*
* Also change the ownership of the table's rowtype, if it has one
*/
if (tuple_class->relkind != RELKIND_INDEX)
AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId,
tuple_class->relkind == RELKIND_COMPOSITE_TYPE);
/*
* If we are operating on a table, also change the ownership of any
* indexes and sequences that belong to the table, as well as the
* table's toast table (if it has one)
*/
if (tuple_class->relkind == RELKIND_RELATION ||
tuple_class->relkind == RELKIND_TOASTVALUE ||
tuple_class->relkind == RELKIND_AOSEGMENTS ||
tuple_class->relkind == RELKIND_AOBLOCKDIR)
{
List *index_oid_list;
ListCell *i;
/* Find all the indexes belonging to this relation */
index_oid_list = RelationGetIndexList(target_rel);
/* For each index, recursively change its ownership */
foreach(i, index_oid_list)
ATExecChangeOwner(lfirst_oid(i), newOwnerId, true);
list_free(index_oid_list);
}
if (tuple_class->relkind == RELKIND_RELATION)
{
/* If it has a toast table, recurse to change its ownership */
if (tuple_class->reltoastrelid != InvalidOid)
ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerId,
true);
if(RelationIsAoRows(target_rel) || RelationIsParquet(target_rel))
{
Oid segrelid, blkdirrelid;
GetAppendOnlyEntryAuxOids(relationOid, SnapshotNow,
&segrelid, NULL,
&blkdirrelid, NULL);
/* If it has an AO segment table, recurse to change its
* ownership */
if (segrelid != InvalidOid)
ATExecChangeOwner(segrelid, newOwnerId, true);
/* If it has an AO block directory table, recurse to change its
* ownership */
if (blkdirrelid != InvalidOid)
ATExecChangeOwner(blkdirrelid, newOwnerId, true);
}
/* If it has dependent sequences, recurse to change them too */
change_owner_recurse_to_sequences(relationOid, newOwnerId);
}
}
caql_endscan(pcqCtx);
relation_close(target_rel, NoLock);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(tuple_class)
)
MetaTrackUpdObject(RelationRelationId,
relationOid,
GetUserId(),
"ALTER", "OWNER"
);
}
/*
* change_owner_recurse_to_sequences
*
* Helper function for ATExecChangeOwner. Examines pg_depend searching
* for sequences that are dependent on serial columns, and changes their
* ownership.
*/
static void
change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId)
{
cqContext *pcqCtx;
HeapTuple tup;
/*
* SERIAL sequences are those having an auto dependency on one of the
* table's columns (we don't care *which* column, exactly).
*/
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_depend "
" WHERE refclassid = :1 "
" AND refobjid = :2 ",
ObjectIdGetDatum(RelationRelationId),
ObjectIdGetDatum(relationOid)));
/* we leave refobjsubid unspecified */
while (HeapTupleIsValid(tup = caql_getnext(pcqCtx)))
{
Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup);
Relation seqRel;
/* skip dependencies other than auto dependencies on columns */
if (depForm->refobjsubid == 0 ||
depForm->classid != RelationRelationId ||
depForm->objsubid != 0 ||
depForm->deptype != DEPENDENCY_AUTO)
continue;
/* Use relation_open just in case it's an index */
seqRel = relation_open(depForm->objid, AccessExclusiveLock);
/* skip non-sequence relations */
if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE)
{
/* No need to keep the lock */
relation_close(seqRel, AccessExclusiveLock);
continue;
}
/* We don't need to close the sequence while we alter it. */
ATExecChangeOwner(depForm->objid, newOwnerId, true);
/* Now we can close it. Keep the lock till end of transaction. */
relation_close(seqRel, NoLock);
}
caql_endscan(pcqCtx);
}
/*
* ALTER TABLE CLUSTER ON
*
* The only thing we have to do is to change the indisclustered bits.
*/
static void
ATExecClusterOn(Relation rel, const char *indexName)
{
Oid indexOid;
indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace);
if (!OidIsValid(indexOid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("index \"%s\" for table \"%s\" does not exist",
indexName, RelationGetRelationName(rel)),
errOmitLocation(true)));
/* Check index is valid to cluster on */
check_index_is_clusterable(rel, indexOid, false);
/* And do the work */
mark_index_clustered(rel, indexOid);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "CLUSTER ON"
);
}
/*
* ALTER TABLE SET WITHOUT CLUSTER
*
* We have to find any indexes on the table that have indisclustered bit
* set and turn it off.
*/
static void
ATExecDropCluster(Relation rel)
{
mark_index_clustered(rel, InvalidOid);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "SET WITHOUT CLUSTER"
);
}
/*
* ALTER TABLE SET TABLESPACE
*/
/*
* Convert tablespace name to pg_tablespace Oid. Error out if not valid and
* settable by the current user.
*/
Oid
get_settable_tablespace_oid(char *tablespacename)
{
Oid tablespaceId;
AclResult aclresult;
/* Check that the tablespace exists */
tablespaceId = get_tablespace_oid(tablespacename);
if (!OidIsValid(tablespaceId))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("tablespace \"%s\" does not exist", tablespacename)));
/* Check its permissions */
aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_TABLESPACE, tablespacename);
return tablespaceId;
}
static void
ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, char *tablespacename)
{
Oid tablespaceId;
tablespaceId = get_settable_tablespace_oid(tablespacename);
/* Save info for Phase 3 to do the real work */
if (OidIsValid(tab->newTableSpace))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot have multiple SET TABLESPACE subcommands"),
errOmitLocation(true)));
tab->newTableSpace = tablespaceId;
}
/*
* ATPartsPrepSetTableSpace is like ATPrepSetTableSpace except that it generates work queue
* entries for the command (an ALTER TABLE ... SET TABLESPACE ...) for each part within the
* sub-hierarchy indicated by the oid list.
*
* Designed to be called from the AT_PartAlter case of ATPrepCmd.
*/
static void
ATPartsPrepSetTableSpace(List **wqueue, Relation rel, AlterTableCmd *cmd, List *oids)
{
ListCell *lc;
Oid tablespaceId;
int pass = AT_PASS_MISC;
Assert( cmd && cmd->subtype == AT_SetTableSpace );
Assert( oids );
tablespaceId = get_settable_tablespace_oid(cmd->name);
Assert(tablespaceId);
ereport(DEBUG1,
(errmsg("Expanding ALTER TABLE %s SET TABLESPACE...",
RelationGetRelationName(rel))
));
foreach(lc, oids)
{
Oid partrelid = lfirst_oid(lc);
Relation partrel = relation_open(partrelid, NoLock);
/* NoLock because we should be holding AccessExclusiveLock on parent */
AlterTableCmd *partcmd = copyObject(cmd);
AlteredTableInfo *parttab = ATGetQueueEntry(wqueue, partrel);
if( OidIsValid(parttab->newTableSpace) )
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting SET TABLESPACE subcommands for \"%s\"",
RelationGetRelationName(partrel)),
errOmitLocation(true)));
parttab->newTableSpace = tablespaceId;
parttab->subcmds[pass] = lappend(parttab->subcmds[pass], partcmd);
ereport(DEBUG1,
(errmsg("Will SET TABLESPACE on \"%s\"",
RelationGetRelationName(partrel))));
relation_close(partrel, NoLock);
}
}
/*
* ALTER TABLE/INDEX SET (...) or RESET (...)
*/
static void
ATExecSetRelOptions(Relation rel, List *defList, bool isReset)
{
Oid relid;
HeapTuple tuple;
HeapTuple newtuple;
Datum datum;
bool isnull;
Datum newOptions;
Datum repl_val[Natts_pg_class];
bool repl_null[Natts_pg_class];
bool repl_repl[Natts_pg_class];
cqContext *pcqCtx;
if (defList == NIL)
return; /* nothing to do */
/* Get the old reloptions */
relid = RelationGetRelid(rel);
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(relid)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
datum = caql_getattr(pcqCtx, Anum_pg_class_reloptions, &isnull);
/* MPP-5777: disallow all options except fillfactor.
* Future work: could convert from SET to SET WITH codepath which
* can support additional reloption types
*/
if ((defList != NIL)
/* && ((rel->rd_rel->relkind == RELKIND_RELATION)
|| (rel->rd_rel->relkind == RELKIND_TOASTVALUE)) */
)
{
ListCell *cell;
foreach(cell, defList)
{
DefElem *def = lfirst(cell);
int kw_len = strlen(def->defname);
char *text_str = "fillfactor";
int text_len = strlen(text_str);
if ((text_len != kw_len) ||
(pg_strncasecmp(text_str, def->defname, kw_len) != 0))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot SET reloption \"%s\"",
def->defname)));
}
}
/* Generate new proposed reloptions (text array) */
newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
defList, false, isReset);
/* Validate */
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_AOSEGMENTS:
case RELKIND_AOBLOCKDIR:
if(RelationIsAoRows(rel) || RelationIsParquet(rel))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("altering reloptions for append only tables"
" is not permitted"),
errOmitLocation(true)));
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
break;
case RELKIND_INDEX:
(void) index_reloptions(rel->rd_am->amoptions, newOptions, true);
break;
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, index, or TOAST table",
RelationGetRelationName(rel)),
errOmitLocation(true)));
break;
}
/*
* All we need do here is update the pg_class row; the new options will be
* propagated into relcaches during post-commit cache inval.
*/
memset(repl_val, 0, sizeof(repl_val));
memset(repl_null, false, sizeof(repl_null));
memset(repl_repl, false, sizeof(repl_repl));
if (newOptions != (Datum) 0)
repl_val[Anum_pg_class_reloptions - 1] = newOptions;
else
repl_null[Anum_pg_class_reloptions - 1] = true;
repl_repl[Anum_pg_class_reloptions - 1] = true;
newtuple = caql_modify_current(pcqCtx,
repl_val, repl_null, repl_repl);
caql_update_current(pcqCtx, newtuple);
/* and Update indexes (implicit) */
heap_freetuple(newtuple);
caql_endscan(pcqCtx);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", isReset ? "RESET" : "SET"
);
}
static void
copy_append_only_data(
RelFileNode *oldRelFileNode,
RelFileNode *newRelFileNode,
int32 segmentFileNum,
char *relationName,
int64 eof,
ItemPointer persistentTid,
int64 persistentSerialNum,
char *buffer)
{
char srcFileName[MAXPGPATH];
char dstFileName[MAXPGPATH];
char extension[12];
File srcFile;
MirroredAppendOnlyOpen mirroredDstOpen;
int64 endOffset;
int64 readOffset;
int32 bufferLen;
int retval;
int primaryError;
/*bool mirrorDataLossOccurred;*/
/*bool mirrorCatchupRequired;
MirrorDataLossTrackingState originalMirrorDataLossTrackingState;
int64 originalMirrorDataLossTrackingSessionNum;*/
if (segmentFileNum > 0)
{
sprintf(extension, ".%u", segmentFileNum);
}
else
extension[0] = '\0';
CopyRelPath(srcFileName, MAXPGPATH, *oldRelFileNode);
if (segmentFileNum > 0)
{
strcat(srcFileName, extension);
}
/*
* Open the files
*/
srcFile = PathNameOpenFile(srcFileName, O_RDONLY | PG_BINARY, 0);
if (srcFile < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\": %m", srcFileName),
errdetail("%s", HdfsGetLastError())));
CopyRelPath(dstFileName, MAXPGPATH, *newRelFileNode);
if (segmentFileNum > 0)
{
strcat(dstFileName, extension);
}
MirroredAppendOnly_OpenReadWrite(
&mirroredDstOpen,
newRelFileNode,
segmentFileNum,
relationName,
/* logicalEof */ 0, // NOTE: This is the START EOF. Since we are copying, we start at 0.
true,
&primaryError);
if (primaryError != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\" for relation '%s': %s", dstFileName, relationName, strerror(primaryError)),
errdetail("%s", HdfsGetLastError())));
/*
* Do the data copying.
*/
endOffset = eof;
readOffset = 0;
bufferLen = (Size) Min(2*BLCKSZ, endOffset);
while (readOffset < endOffset)
{
CHECK_FOR_INTERRUPTS();
retval = FileRead(srcFile, buffer, bufferLen);
if (retval != bufferLen)
{
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not read from position: " INT64_FORMAT " in file '%s' : %m", readOffset, srcFileName),
errdetail("%s", HdfsGetLastError())));
break;
}
MirroredAppendOnly_Append(
&mirroredDstOpen,
buffer,
bufferLen,
&primaryError/*,
&mirrorDataLossOccurred*/);
if (primaryError != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not write file \"%s\": %s", dstFileName, strerror(primaryError)),
errdetail("%s", HdfsGetLastError())));
readOffset += bufferLen;
bufferLen = (Size) Min(2*BLCKSZ, endOffset - readOffset);
}
MirroredAppendOnly_FlushAndClose(
&mirroredDstOpen,
&primaryError
/*&mirrorDataLossOccurred,
&mirrorCatchupRequired,
&originalMirrorDataLossTrackingState,
&originalMirrorDataLossTrackingSessionNum*/);
if (primaryError != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not flush (fsync) file \"%s\" for relation '%s': %s"
, dstFileName, relationName, strerror(primaryError)),
errdetail("%s", HdfsGetLastError())));
FileClose(srcFile);
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"ALTER TABLE SET TABLESPACE: Copied Append-Only segment file #%d EOF " INT64_FORMAT,
segmentFileNum,
eof);
}
static void
ATExecSetTableSpace_AppendOnly(
Oid tableOid,
Relation rel,
Relation gp_relfile_node,
RelFileNode *newRelFileNode)
{
char *buffer;
GpRelfileNodeScan gpRelfileNodeScan;
HeapTuple tuple;
int32 segmentFileNum = 0;
ItemPointerData oldPersistentTid;
int64 oldPersistentSerialNum = 0;
ItemPointerData newPersistentTid;
int64 newPersistentSerialNum;
HeapTuple tupleCopy;
AppendOnlyEntry *aoEntry;
Snapshot appendOnlyMetaDataSnapshot = SnapshotNow;
/*
* We can use SnapshotNow since we have an exclusive lock on the source.
*/
int segmentCount;
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"ALTER TABLE SET TABLESPACE: Append-Only "
"relation id %u, old path %u/%u/%u, new path %u/%u/%u",
RelationGetRelid(rel),
rel->rd_node.spcNode,
rel->rd_node.dbNode,
rel->rd_node.relNode,
newRelFileNode->spcNode,
newRelFileNode->dbNode,
newRelFileNode->relNode);
/* Use palloc to ensure we get a maxaligned buffer */
buffer = palloc(2*BLCKSZ);
aoEntry = GetAppendOnlyEntry(RelationGetRelid(rel), appendOnlyMetaDataSnapshot);
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"ALTER TABLE SET TABLESPACE: pg_appendonly entry for Append-Only %u/%u/%u, segment file #%d "
"segrelid %u, segidxid %u, "
"persistent TID %s and serial number " INT64_FORMAT,
rel->rd_node.spcNode,
rel->rd_node.dbNode,
rel->rd_node.relNode,
segmentFileNum,
aoEntry->segrelid,
aoEntry->segidxid,
ItemPointerToString(&oldPersistentTid),
oldPersistentSerialNum);
/*
* Loop through segment files
* Create segment file in new tablespace under transaction,
* Copy Append-Only segment file data and fsync,
* Update old gp_relation_node with new relfilenode and persistent information,
* Schedule old segment file for drop under transaction,
*/
GpRelfileNodeBeginScan(
gp_relfile_node,
tableOid,
rel->rd_rel->relfilenode,
&gpRelfileNodeScan);
segmentCount = 0;
while ((tuple = GpRelfileNodeGetNext(
&gpRelfileNodeScan,
&segmentFileNum,
&oldPersistentTid,
&oldPersistentSerialNum)))
{
int64 eof = 0;
/*
* Create segment file in new tablespace under transaction.
*/
MirroredFileSysObj_TransactionCreateAppendOnlyFile(
newRelFileNode,
segmentFileNum,
rel->rd_rel->relname.data,
/* doJustInTimeDirCreate */ true,
&newPersistentTid,
&newPersistentSerialNum);
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"ALTER TABLE SET TABLESPACE: Create for Append-Only %u/%u/%u, segment file #%d "
"persistent TID %s and serial number " INT64_FORMAT,
newRelFileNode->spcNode,
newRelFileNode->dbNode,
newRelFileNode->relNode,
segmentFileNum,
ItemPointerToString(&newPersistentTid),
newPersistentSerialNum);
/*
* Update gp_relation_node with new persistent information.
*/
tupleCopy = heap_copytuple(tuple);
UpdateGpRelfileNodeTuple(
gp_relfile_node,
tupleCopy,
newRelFileNode->relNode,
segmentFileNum,
&newPersistentTid,
newPersistentSerialNum);
/*
* Schedule old segment file for drop under transaction.
*/
MirroredFileSysObj_ScheduleDropAppendOnlyFile(
&rel->rd_node,
segmentFileNum,
rel->rd_rel->relname.data,
&oldPersistentTid,
oldPersistentSerialNum);
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"ALTER TABLE SET TABLESPACE: Drop for Append-Only %u/%u/%u, segment file #%d "
"persistent TID %s and serial number " INT64_FORMAT,
rel->rd_node.spcNode,
rel->rd_node.dbNode,
rel->rd_node.relNode,
segmentFileNum,
ItemPointerToString(&oldPersistentTid),
oldPersistentSerialNum);
/*
* Copy Append-Only segment file data and fsync.
*/
if (RelationIsAoRows(rel))
{
FileSegInfo *fileSegInfo;
fileSegInfo = GetFileSegInfo(rel, aoEntry, appendOnlyMetaDataSnapshot, segmentFileNum);
if (fileSegInfo == NULL)
{
/*
* Segment file #0 is special and can exist without an entry.
*/
if (segmentFileNum == 0)
eof = 0;
else
ereport(ERROR,
(errmsg("Append-only %u/%u/%u row segment file #%d information missing",
rel->rd_node.spcNode,
rel->rd_node.dbNode,
rel->rd_node.relNode,
segmentFileNum)));
}
else
{
eof = fileSegInfo->eof;
pfree(fileSegInfo);
}
}
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"ALTER TABLE SET TABLESPACE: Going to copy Append-Only %u/%u/%u to %u/%u/%u, segment file #%d, EOF " INT64_FORMAT ", "
"persistent TID %s and serial number " INT64_FORMAT,
rel->rd_node.spcNode,
rel->rd_node.dbNode,
rel->rd_node.relNode,
newRelFileNode->spcNode,
newRelFileNode->dbNode,
newRelFileNode->relNode,
segmentFileNum,
eof,
ItemPointerToString(&newPersistentTid),
newPersistentSerialNum);
copy_append_only_data(
&rel->rd_node,
newRelFileNode,
segmentFileNum,
rel->rd_rel->relname.data,
eof,
&newPersistentTid,
newPersistentSerialNum,
buffer);
segmentCount++;
}
GpRelfileNodeEndScan(&gpRelfileNodeScan);
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"ALTER TABLE SET TABLESPACE: Copied %d Append-Only segments from %u/%u/%u to %u/%u/%u, "
"persistent TID %s and serial number " INT64_FORMAT,
segmentCount,
rel->rd_node.spcNode,
rel->rd_node.dbNode,
rel->rd_node.relNode,
newRelFileNode->spcNode,
newRelFileNode->dbNode,
newRelFileNode->relNode,
ItemPointerToString(&newPersistentTid),
newPersistentSerialNum);
pfree(aoEntry);
pfree(buffer);
}
static void
ATExecSetTableSpace_BufferPool(
Oid tableOid,
Relation rel,
Relation gp_relfile_node,
RelFileNode *newRelFileNode)
{
Oid oldTablespace;
HeapTuple nodeTuple;
ItemPointerData newPersistentTid;
int64 newPersistentSerialNum;
SMgrRelation dstrel;
bool useWal;
PersistentFileSysRelStorageMgr localRelStorageMgr;
PersistentFileSysRelBufpoolKind relBufpoolKind;
oldTablespace = rel->rd_rel->reltablespace ? rel->rd_rel->reltablespace : MyDatabaseTableSpace;
/*
* We need to log the copied data in WAL enabled AND
* it's not a temp rel.
*/
useWal = !XLog_CanBypassWal() && !rel->rd_istemp;
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"ALTER TABLE SET TABLESPACE: Buffer Pool managed "
"relation id %u, old path '%s', old relfilenode %u, old reltablespace %u, "
"new path '%s', new relfilenode %u, new reltablespace %u, "
"bulk load %s",
RelationGetRelid(rel),
relpath(rel->rd_node),
rel->rd_rel->relfilenode,
oldTablespace,
relpath(*newRelFileNode),
newRelFileNode->relNode,
newRelFileNode->spcNode,
(!useWal ? "true" : "false"));
/* Fetch relation's gp_relfile_node row */
/* TODO in hawq */
Assert(!"need contentid");
nodeTuple = ScanGpRelfileNodeTuple(
gp_relfile_node,
rel->rd_rel->relfilenode,
/* segmentFileNum */ 0);
if (!HeapTupleIsValid(nodeTuple))
elog(ERROR, "cache lookup failed for relation %u, tablespace %u, relation file node %u",
tableOid,
oldTablespace,
rel->rd_rel->relfilenode);
GpPersistentRelfileNode_GetRelfileInfo(
rel->rd_rel->relkind,
rel->rd_rel->relstorage,
rel->rd_rel->relam,
&localRelStorageMgr,
&relBufpoolKind);
Assert(localRelStorageMgr == PersistentFileSysRelStorageMgr_BufferPool);
dstrel = smgropen(*newRelFileNode);
MirroredFileSysObj_TransactionCreateBufferPoolFile(
dstrel,
relBufpoolKind,
rel->rd_isLocalBuf,
rel->rd_rel->relname.data,
/* doJustInTimeDirCreate */ true,
/* bufferPoolBulkLoad */ !useWal,
&newPersistentTid,
&newPersistentSerialNum);
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"ALTER TABLE SET TABLESPACE: Create for Buffer Pool managed '%s' "
"persistent TID %s and serial number " INT64_FORMAT,
relpath(*newRelFileNode),
ItemPointerToString(&newPersistentTid),
newPersistentSerialNum);
/* copy relation data to the new physical file */
copy_buffer_pool_data(
rel,
dstrel,
&newPersistentTid,
newPersistentSerialNum,
useWal);
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"ALTER TABLE SET TABLESPACE: Scheduling drop for '%s' "
"persistent TID %s and serial number " INT64_FORMAT,
relpath(*newRelFileNode),
ItemPointerToString(&rel->rd_relationnodeinfo.persistentTid),
rel->rd_relationnodeinfo.persistentSerialNum);
/* schedule unlinking old physical file */
MirroredFileSysObj_ScheduleDropBufferPoolRel(rel);
/*
* Now drop smgr references. The source was already dropped by
* smgrscheduleunlink.
*/
smgrclose(dstrel);
/* Update gp_relfile_node row. */
UpdateGpRelfileNodeTuple(
gp_relfile_node,
nodeTuple,
newRelFileNode->relNode,
/* segmentFileNum */ 0,
&newPersistentTid,
newPersistentSerialNum);
}
static bool
ATExecSetTableSpace_Relation(Oid tableOid, Oid newTableSpace, Oid newrelfilenode)
{
Relation rel;
Relation pg_class;
Oid oldTableSpace;
HeapTuple tuple;
Form_pg_class rd_rel;
Relation gp_relfile_node;
RelFileNode newrnode;
cqContext cqc;
cqContext *pcqCtx;
rel = relation_open(tableOid, AccessExclusiveLock);
/*
* We can never allow moving of shared or nailed-in-cache relations,
* because we can't support changing their reltablespace values.
*/
if (rel->rd_rel->relisshared || rel->rd_isnailed)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move system relation \"%s\"",
RelationGetRelationName(rel)),
errOmitLocation(true)));
/*
* Don't allow moving temp tables of other backends ... their local buffer
* manager is not going to cope.
*/
if (isOtherTempNamespace(RelationGetNamespace(rel)))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move temporary tables of other sessions"),
errOmitLocation(true)));
/*
* No work if no change in tablespace.
*/
oldTableSpace = rel->rd_rel->reltablespace;
if (newTableSpace == oldTableSpace ||
(newTableSpace == MyDatabaseTableSpace && oldTableSpace == 0))
{
/* XXX - Why do we hold the lock if we aren't changing anything? */
relation_close(rel, NoLock);
return false;
}
/* Fetch relation's pg_class row */
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), pg_class);
tuple = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(tableOid)));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", tableOid);
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
gp_relfile_node = heap_open(GpRelfileNodeRelationId, RowExclusiveLock);
/* create another storage file. Is it a little ugly ? */
/* NOTE: any conflict in relfilenode value will be caught here */
newrnode = rel->rd_node;
newrnode.relNode = newrelfilenode;
newrnode.spcNode = newTableSpace;
/* update the pg_class row */
rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace) ? InvalidOid : newTableSpace;
rd_rel->relfilenode = newrelfilenode;
if (Debug_persistent_print)
elog(Persistent_DebugPrintLevel(),
"ALTER TABLE SET TABLESPACE: Update pg_class for relation id %u --> relfilenode to %u, reltablespace to %u",
tableOid,
rd_rel->relfilenode,
rd_rel->reltablespace);
caql_update_current(pcqCtx, tuple); /* implicit update of index as well */
heap_freetuple(tuple);
if (RelationIsAoRows(rel) || RelationIsParquet(rel))
ATExecSetTableSpace_AppendOnly(
tableOid,
rel,
gp_relfile_node,
&newrnode);
else
ATExecSetTableSpace_BufferPool(
tableOid,
rel,
gp_relfile_node,
&newrnode);
heap_close(pg_class, RowExclusiveLock);
heap_close(gp_relfile_node, RowExclusiveLock);
/* Make sure the reltablespace change is visible */
CommandCounterIncrement();
/* Hold the lock until commit */
relation_close(rel, NoLock);
return true;
}
/*
* Execute ALTER TABLE SET TABLESPACE for cases where there is no tuple
* rewriting to be done, so we just want to copy the data as fast as possible.
*/
static void
ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, TableOidInfo *oidInfo)
{
Relation rel;
Oid newrelfilenode;
Oid reltoastrelid = InvalidOid;
Oid reltoastidxid = InvalidOid;
Oid relaosegrelid = InvalidOid;
Oid relaosegidxid = InvalidOid;
Oid relaoblkdirrelid = InvalidOid;
Oid relaoblkdiridxid = InvalidOid;
Oid relbmrelid = InvalidOid;
Oid relbmidxid = InvalidOid;
/* Ensure valid input */
Assert(OidIsValid(tableOid));
Assert(OidIsValid(newTableSpace));
Assert(oidInfo);
newrelfilenode = oidInfo->relOid;
/*
* Need lock here in case we are recursing to toast table or index
*/
rel = relation_open(tableOid, AccessExclusiveLock);
reltoastrelid = rel->rd_rel->reltoastrelid;
/* Find the toast relation index */
if (reltoastrelid)
{
Relation toastrel;
toastrel = relation_open(reltoastrelid, NoLock);
reltoastidxid = toastrel->rd_rel->reltoastidxid;
relation_close(toastrel, NoLock);
}
/* Get the ao sub objects */
if (RelationIsAoRows(rel))
GetAppendOnlyEntryAuxOids(tableOid, SnapshotNow,
&relaosegrelid, &relaosegidxid,
&relaoblkdirrelid, &relaoblkdiridxid);
/* Get the bitmap sub objects */
if (RelationIsBitmapIndex(rel))
GetBitmapIndexAuxOids(rel, &relbmrelid, &relbmidxid);
/* Move the main table */
if (!ATExecSetTableSpace_Relation(tableOid, newTableSpace, newrelfilenode))
{
/* XXX - Why do we hold the lock if we aren't changing anything? */
relation_close(rel, NoLock);
return;
}
/* Move associated toast/toast_index/ao subobjects */
if (OidIsValid(reltoastrelid))
ATExecSetTableSpace_Relation(reltoastrelid, newTableSpace,
oidInfo->toastOid);
if (OidIsValid(reltoastidxid))
ATExecSetTableSpace_Relation(reltoastidxid, newTableSpace,
oidInfo->toastIndexOid);
if (OidIsValid(relaosegrelid))
ATExecSetTableSpace_Relation(relaosegrelid, newTableSpace,
oidInfo->aosegOid);
if (OidIsValid(relaosegidxid))
ATExecSetTableSpace_Relation(relaosegidxid, newTableSpace,
oidInfo->aosegIndexOid);
if (OidIsValid(relaoblkdirrelid))
ATExecSetTableSpace_Relation(relaoblkdirrelid, newTableSpace,
oidInfo->aoblkdirOid);
if (OidIsValid(relaoblkdiridxid))
ATExecSetTableSpace_Relation(relaoblkdiridxid, newTableSpace,
oidInfo->aoblkdirIndexOid);
/*
* MPP-7996 - bitmap index subobjects w/Alter Table Set tablespace
*
* Minor hack: Bitmap indexes are never AO tables. Rather than extending
* OidInfo with yet more oids we simply overload aosegOid/aosegIndexOid
* to serve double purpose as oids for the bitmap subobjects.
*/
if (OidIsValid(relbmrelid))
{
Assert(!relaosegrelid);
ATExecSetTableSpace_Relation(relbmrelid, newTableSpace,
oidInfo->aosegOid);
}
if (OidIsValid(relbmidxid))
{
Assert(!relaosegidxid);
ATExecSetTableSpace_Relation(relbmidxid, newTableSpace,
oidInfo->aosegIndexOid);
}
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH) && MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "SET TABLESPACE"
);
/* Hold the lock until commit */
relation_close(rel, NoLock);
}
/*
* Copy data, block by block
*/
static void
copy_buffer_pool_data(
Relation rel,
SMgrRelation dst,
ItemPointer persistentTid,
int64 persistentSerialNum,
bool useWal)
{
SMgrRelation src;
BlockNumber nblocks;
BlockNumber blkno;
char buf[BLCKSZ];
Page page = (Page) buf;
/*
* Since we copy the file directly without looking at the shared buffers,
* we'd better first flush out any pages of the source relation that are
* in shared buffers. We assume no new changes will be made while we are
* holding exclusive lock on the rel.
*/
FlushRelationBuffers(rel);
nblocks = RelationGetNumberOfBlocks(rel);
/* RelationGetNumberOfBlocks will certainly have opened rd_smgr */
src = rel->rd_smgr;
if (useWal)
{
if (Debug_persistent_print)
{
elog(Persistent_DebugPrintLevel(),
"copy_buffer_pool_data %u/%u/%u: not bypassing the WAL -- not using bulk load, persistent serial num " INT64_FORMAT ", TID %s",
rel->rd_node.spcNode,
rel->rd_node.dbNode,
rel->rd_node.relNode,
persistentSerialNum,
ItemPointerToString(persistentTid));
}
}
for (blkno = 0; blkno < nblocks; blkno++)
{
smgrread(src, blkno, buf);
/* XLOG stuff */
if (useWal)
{
xl_heap_newpage xlrec;
XLogRecPtr recptr;
XLogRecData rdata[2];
/* NO ELOG(ERROR) from here till newpage op is logged */
START_CRIT_SECTION();
xlrec.heapnode.node = dst->smgr_rnode;
xlrec.heapnode.persistentTid = *persistentTid;
xlrec.heapnode.persistentSerialNum = persistentSerialNum;
xlrec.blkno = blkno;
rdata[0].data = (char *) &xlrec;
rdata[0].len = SizeOfHeapNewpage;
rdata[0].buffer = InvalidBuffer;
rdata[0].next = &(rdata[1]);
rdata[1].data = (char *) page;
rdata[1].len = BLCKSZ;
rdata[1].buffer = InvalidBuffer;
rdata[1].next = NULL;
recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_NEWPAGE, rdata);
PageSetLSN(page, recptr);
PageSetTLI(page, ThisTimeLineID);
END_CRIT_SECTION();
}
// -------- MirroredLock ----------
LWLockAcquire(MirroredLock, LW_SHARED);
/*
* Now write the page. We say isTemp = true even if it's not a temp
* rel, because there's no need for smgr to schedule an fsync for this
* write; we'll do it ourselves below.
*/
smgrwrite(dst, blkno, buf, true);
LWLockRelease(MirroredLock);
// -------- MirroredLock ----------
}
/*
* If the rel isn't temp, we must fsync it down to disk before it's safe
* to commit the transaction. (For a temp rel we don't care since the rel
* will be uninteresting after a crash anyway.)
*
* It's obvious that we must do this when not WAL-logging the copy. It's
* less obvious that we have to do it even if we did WAL-log the copied
* pages. The reason is that since we're copying outside shared buffers, a
* CHECKPOINT occurring during the copy has no way to flush the previously
* written data to disk (indeed it won't know the new rel even exists). A
* crash later on would replay WAL from the checkpoint, therefore it
* wouldn't replay our earlier WAL entries. If we do not fsync those pages
* here, they might still not be on disk when the crash occurs.
*/
// -------- MirroredLock ----------
LWLockAcquire(MirroredLock, LW_SHARED);
if (!rel->rd_istemp)
smgrimmedsync(dst);
LWLockRelease(MirroredLock);
// -------- MirroredLock ----------
if (useWal)
{
if (Debug_persistent_print)
{
elog(Persistent_DebugPrintLevel(),
"copy_buffer_pool_data %u/%u/%u: did not bypass the WAL -- did not use bulk load, persistent serial num " INT64_FORMAT ", TID %s",
rel->rd_node.spcNode,
rel->rd_node.dbNode,
rel->rd_node.relNode,
persistentSerialNum,
ItemPointerToString(persistentTid));
}
}
}
/*
* ALTER TABLE ENABLE/DISABLE TRIGGER
*
* We just pass this off to trigger.c.
*/
static void
ATExecEnableDisableTrigger(Relation rel, char *trigname,
bool enable, bool skip_system)
{
EnableDisableTrigger(rel, trigname, enable, skip_system);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER",
enable ? "ENABLE TRIGGER" :
"DISABLE TRIGGER"
);
}
static void
inherit_parent(Relation parent_rel, Relation child_rel, bool is_partition, List *inhAttrNameList)
{
cqContext *pcqCtx;
cqContext cqc;
HeapTuple inheritsTuple;
int32 inhseqno;
List *children;
Relation catalogRelation;
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
* (Note: get RowExclusiveLock because we will write pg_inherits below.)
*
* Note: we do not reject the case where the child already inherits from
* the parent indirectly; CREATE TABLE doesn't reject comparable cases.
*/
catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), catalogRelation),
cql("SELECT * FROM pg_inherits "
" WHERE inhrelid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationGetRelid(child_rel))));
/* inhseqno sequences start at 1 */
inhseqno = 0;
while (HeapTupleIsValid(inheritsTuple = caql_getnext(pcqCtx)))
{
Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
if (inh->inhparent == RelationGetRelid(parent_rel))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("inherited relation \"%s\" duplicated",
RelationGetRelationName(parent_rel)),
errOmitLocation(true)));
if (inh->inhseqno > inhseqno)
inhseqno = inh->inhseqno;
}
caql_endscan(pcqCtx);
/*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
*
* This is not completely bulletproof because of race conditions: in
* multi-level inheritance trees, someone else could concurrently
* be making another inheritance link that closes the loop but does
* not join either of the rels we have locked. Preventing that seems
* to require exclusive locks on the entire inheritance tree, which is
* a cure worse than the disease. find_all_inheritors() will cope with
* circularity anyway, so don't sweat it too much.
*/
children = find_all_inheritors(RelationGetRelid(child_rel));
if (list_member_oid(children, RelationGetRelid(parent_rel)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("circular inheritance not allowed"),
errdetail("\"%s\" is already a child of \"%s\".",
RelationGetRelationName(parent_rel),
RelationGetRelationName(child_rel)),
errOmitLocation(true)));
/* If parent has OIDs then child must have OIDs */
if (parent_rel->rd_rel->relhasoids && !child_rel->rd_rel->relhasoids)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("table \"%s\" without OIDs cannot inherit from table \"%s\" with OIDs",
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel)),
errOmitLocation(true)));
/* Match up the columns and bump attinhcount and attislocal */
MergeAttributesIntoExisting(child_rel, parent_rel, inhAttrNameList, is_partition);
/* Match up the constraints and make sure they're present in child */
MergeConstraintsIntoExisting(child_rel, parent_rel);
/*
* OK, it looks valid. Make the catalog entries that show inheritance.
*/
StoreCatalogInheritance1(RelationGetRelid(child_rel),
RelationGetRelid(parent_rel),
inhseqno + 1,
catalogRelation, is_partition);
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
}
/*
* ALTER TABLE INHERIT
*
* Add a parent to the child's parents. This verifies that all the columns and
* check constraints of the parent appear in the child and that they have the
* same data types and expressions.
*/
static void
ATExecAddInherit(Relation child_rel, Node *node)
{
Relation parent_rel;
bool is_partition;
RangeVar *parent;
List *inhAttrNameList = NIL;
Assert(PointerIsValid(node));
if (IsA(node, InheritPartitionCmd))
{
parent = ((InheritPartitionCmd *)node)->parent;
is_partition = true;
}
else
{
List *inhParms;
Assert(IsA(node, List));
inhParms = (List *)node;
Insist(list_length(inhParms) == 2);
Assert(IsA(linitial(inhParms), RangeVar));
Assert(lsecond(inhParms) == NIL || IsA(lsecond(inhParms), List));
parent = (RangeVar *)linitial(inhParms);
inhAttrNameList = (List *)lsecond(inhParms);
is_partition = false;
}
/*
* AccessShareLock on the parent is what's obtained during normal CREATE
* TABLE ... INHERITS ..., so should be enough here.
*/
parent_rel = heap_openrv(parent, AccessShareLock);
/*
* Must be owner of both parent and child -- child was checked by
* ATSimplePermissions call in ATPrepCmd
*/
ATSimplePermissions(parent_rel, false);
/* Permanent rels cannot inherit from temporary ones */
if (!isTempNamespace(RelationGetNamespace(child_rel)) &&
isTempNamespace(RelationGetNamespace(parent_rel)))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from temporary relation \"%s\"",
RelationGetRelationName(parent_rel)),
errOmitLocation(true)));
if (is_partition)
{
/* lookup all attrs */
int attno;
for (attno = 0; attno < parent_rel->rd_att->natts; attno++)
{
Form_pg_attribute attribute = parent_rel->rd_att->attrs[attno];
char *attributeName = NameStr(attribute->attname);
/* MPP-5397: ignore dropped cols */
if (!attribute->attisdropped)
inhAttrNameList =
lappend(inhAttrNameList,
makeString(attributeName));
}
}
inherit_parent(parent_rel, child_rel, is_partition, inhAttrNameList);
/*
* Keep our lock on the parent relation until commit, unless we're
* doing partitioning, in which case the parent is sufficiently locked.
* We want to unlock here in case we're doing deep sub partitioning. We do
* not want to acquire too many locks since we're overflow the lock buffer.
* An exclusive lock on the parent table is sufficient to guard against
* concurrency issues.
*/
if (is_partition)
heap_close(parent_rel, AccessShareLock);
else
heap_close(parent_rel, NoLock);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(child_rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(child_rel),
GetUserId(),
"ALTER", "INHERIT"
);
}
/*
* Obtain the source-text form of the constraint expression for a check
* constraint, given its pg_constraint tuple
*/
static char *
decompile_conbin(HeapTuple contup, TupleDesc tupdesc)
{
Form_pg_constraint con;
bool isnull;
Datum attr;
Datum expr;
con = (Form_pg_constraint) GETSTRUCT(contup);
attr = heap_getattr(contup, Anum_pg_constraint_conbin, tupdesc, &isnull);
if (isnull)
elog(ERROR, "null conbin for constraint %u", HeapTupleGetOid(contup));
expr = DirectFunctionCall2(pg_get_expr, attr,
ObjectIdGetDatum(con->conrelid));
return DatumGetCString(DirectFunctionCall1(textout, expr));
}
/*
toast_idxid = index_create(toast_relid, toast_idxname, newIndexOid,
indexInfo,
BTREE_AM_OID,
rel->rd_rel->reltablespace,
classObjectId,
true, false, true, false);*/
/*
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
* Called by ATExecAddInherit
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
* does. However, that is widely unpopular --- in the common use case of
* partitioned tables it's a foot-gun.
*
* The data type must match exactly. If the parent column is NOT NULL then
* the child must be as well. Defaults are not compared, however.
*/
static void
MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, List *inhAttrNameList,
bool is_partition)
{
Relation attrrel;
AttrNumber parent_attno;
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
ListCell *attNameCell;
cqContext cqc;
cqContext *pcqCtx;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), attrrel);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
/*
* If we have an inherited column list, ensure all named columns exist in parent
* and that the list excludes system columns.
*/
foreach( attNameCell, inhAttrNameList )
{
bool columnDefined = false;
char *inhAttrName = strVal((Value *)lfirst(attNameCell));
for (parent_attno = 1; parent_attno <= parent_natts && !columnDefined; parent_attno++)
{
char *attributeName;
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
if (attribute->attisdropped)
continue;
attributeName = NameStr(attribute->attname);
columnDefined = (strcmp(inhAttrName, attributeName) == 0);
}
if (!columnDefined)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" does not exist in parent table \"%s\"",
inhAttrName,
RelationGetRelationName(parent_rel)),
errOmitLocation(true)));
}
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
char *attributeName = NameStr(attribute->attname);
/* Ignore dropped columns in the parent. */
if (attribute->attisdropped)
continue;
/* Find same column in child (matching on column name). */
tuple = caql_getattname(pcqCtx, RelationGetRelid(child_rel),
attributeName);
if (HeapTupleIsValid(tuple))
{
/* Check they are same type and typmod */
Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
if (attribute->atttypid != childatt->atttypid ||
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName),
errOmitLocation(true)));
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" in child table must be marked NOT NULL",
attributeName),
errOmitLocation(true)));
/*
* OK, bump the child column's inheritance count. (If we fail
* later on, this change will just roll back.)
*/
childatt->attinhcount++;
/*
* For locally-defined attributes, check to see if the attribute is named
* in the "fully-inherited" list. If so, mark the child attribute as
* not locally defined. (Default/standard behaviour is to leave the
* attribute locally defined.)
*/
if (childatt->attislocal)
{
/* never local when we're doing partitioning */
if (is_partition)
childatt->attislocal = false;
else
{
foreach( attNameCell, inhAttrNameList )
{
char *inhAttrName = strVal((Value *)lfirst(attNameCell));
if (strcmp(attributeName, inhAttrName) == 0)
{
childatt->attislocal = false;
break;
}
}
}
}
caql_update_current(pcqCtx, tuple);
/* and Update indexes (implicit) */
heap_freetuple(tuple);
}
else
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("child table is missing column \"%s\"",
attributeName),
errOmitLocation(true)));
}
}
heap_close(attrrel, RowExclusiveLock);
}
/*
* Check constraints in child table match up with constraints in parent
*
* Called by ATExecAddInherit and exchange_part_inheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does. We may also want
* to allow an optional flag on parent table constraints indicating they are
* intended to ONLY apply to the master table, not to the children. That would
* make it possible to ensure no records are mistakenly inserted into the
* master in partitioned tables rather than the appropriate child.
*
* XXX This is O(N^2) which may be an issue with tables with hundreds of
* constraints. As long as tables have more like 10 constraints it shouldn't be
* a problem though. Even 100 constraints ought not be the end of the world.
*/
static void
MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
{
Relation catalogRelation;
TupleDesc tupleDesc;
cqContext *pcqCtx;
cqContext cqc;
HeapTuple constraintTuple;
ListCell *elem;
List *constraints;
/* First gather up the child's constraint definitions */
catalogRelation = heap_open(ConstraintRelationId, AccessShareLock);
tupleDesc = RelationGetDescr(catalogRelation);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), catalogRelation),
cql("SELECT * FROM pg_constraint "
" WHERE conrelid = :1 ",
ObjectIdGetDatum(RelationGetRelid(child_rel))));
constraints = NIL;
while (HeapTupleIsValid(constraintTuple = caql_getnext(pcqCtx)))
{
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
if (con->contype != CONSTRAINT_CHECK)
continue;
constraints = lappend(constraints, heap_copytuple(constraintTuple));
}
caql_endscan(pcqCtx);
/* Then scan through the parent's constraints looking for matches */
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), catalogRelation),
cql("SELECT * FROM pg_constraint "
" WHERE conrelid = :1 ",
ObjectIdGetDatum(RelationGetRelid(parent_rel))));
while (HeapTupleIsValid(constraintTuple = caql_getnext(pcqCtx)))
{
Form_pg_constraint parent_con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
bool found = false;
Form_pg_constraint child_con = NULL;
HeapTuple child_contuple = NULL;
if (parent_con->contype != CONSTRAINT_CHECK)
continue;
foreach(elem, constraints)
{
child_contuple = (HeapTuple) lfirst(elem);
child_con = (Form_pg_constraint) GETSTRUCT(child_contuple);
if (strcmp(NameStr(parent_con->conname),
NameStr(child_con->conname)) == 0)
{
found = true;
break;
}
}
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("child table is missing constraint \"%s\"",
NameStr(parent_con->conname)),
errOmitLocation(true)));
if (parent_con->condeferrable != child_con->condeferrable ||
parent_con->condeferred != child_con->condeferred ||
strcmp(decompile_conbin(constraintTuple, tupleDesc),
decompile_conbin(child_contuple, tupleDesc)) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("constraint definition for check constraint \"%s\" does not match",
NameStr(parent_con->conname)),
errOmitLocation(true)));
/*
* TODO: add conislocal,coninhcount to constraints. This is where we
* would have to bump them just like attributes
*/
}
caql_endscan(pcqCtx);
heap_close(catalogRelation, AccessShareLock);
}
/*
* ALTER TABLE NO INHERIT
*
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
*
* If attinhcount goes to 0 then attislocal gets set to true. If it goes back
* up attislocal stays true, which means if a child is ever removed from a
* parent then its columns will never be automatically dropped which may
* surprise. But at least we'll never surprise by dropping columns someone
* isn't expecting to be dropped which would actually mean data loss.
*/
static void
ATExecDropInherit(Relation rel, RangeVar *parent, bool is_partition)
{
Relation parent_rel;
Relation catalogRelation;
cqContext *pcqCtx;
cqContext cqc;
HeapTuple inheritsTuple,
attributeTuple,
depTuple;
bool found = false;
/*
* AccessShareLock on the parent is probably enough, seeing that DROP TABLE
* doesn't lock parent tables at all. We need some lock since we'll be
* inspecting the parent's schema.
*/
parent_rel = heap_openrv(parent, AccessShareLock);
/*
* We don't bother to check ownership of the parent table --- ownership
* of the child is presumed enough rights.
*/
/*
* Find and destroy the pg_inherits entry linking the two, or error out
* if there is none.
*/
/* XXX XXX: could do DELETE FROM ... WHERE ... AND inhparent = :2,
ObjectIdGetDatum(RelationGetRelid(parent_rel))
*/
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_inherits "
" WHERE inhrelid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationGetRelid(rel))));
while (HeapTupleIsValid(inheritsTuple = caql_getnext(pcqCtx)))
{
Oid inhparent;
inhparent = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhparent;
if (inhparent == RelationGetRelid(parent_rel))
{
caql_delete_current(pcqCtx);
found = true;
break;
}
}
caql_endscan(pcqCtx);
if (!found)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" is not a parent of relation \"%s\"",
RelationGetRelationName(parent_rel),
RelationGetRelationName(rel)),
errOmitLocation(true)));
/*
* Search through child columns looking for ones matching parent rel
*/
catalogRelation = heap_open(AttributeRelationId, RowExclusiveLock);
/* XXX XXX: AND attisdrop = FALSE, etc */
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), catalogRelation),
cql("SELECT * FROM pg_attribute "
" WHERE attrelid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationGetRelid(rel))));
while (HeapTupleIsValid(attributeTuple = caql_getnext(pcqCtx)))
{
Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
HeapTuple attnameTuple;
/* Ignore if dropped or not inherited */
if (att->attisdropped)
continue;
if (att->attinhcount <= 0)
continue;
/* XXX: would have been attname_exists() */
attnameTuple = caql_getattname(NULL, RelationGetRelid(parent_rel),
NameStr(att->attname));
if (HeapTupleIsValid(attnameTuple))
{
/* Decrement inhcount and possibly set islocal to true */
HeapTuple copyTuple = heap_copytuple(attributeTuple);
Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple);
copy_att->attinhcount--;
if (copy_att->attinhcount == 0)
copy_att->attislocal = true;
caql_update_current(pcqCtx, copyTuple);
heap_freetuple(copyTuple);
heap_freetuple(attnameTuple);
}
}
caql_endscan(pcqCtx);
heap_close(catalogRelation, RowExclusiveLock);
/*
* Drop the dependency
*
* There's no convenient way to do this, so go trawling through pg_depend
*/
/* XXX XXX: AND refclassid, refobjid, etc */
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_depend "
" WHERE classid = :1 "
" AND objid = :2 "
" AND objsubid = :3 "
" FOR UPDATE ",
ObjectIdGetDatum(RelationRelationId),
ObjectIdGetDatum(RelationGetRelid(rel)),
Int32GetDatum(0)));
while (HeapTupleIsValid(depTuple = caql_getnext(pcqCtx)))
{
Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(depTuple);
if (dep->refclassid == RelationRelationId &&
dep->refobjid == RelationGetRelid(parent_rel) &&
dep->refobjsubid == 0 &&
((dep->deptype == DEPENDENCY_NORMAL && !is_partition) ||
(dep->deptype == DEPENDENCY_AUTO && is_partition)))
caql_delete_current(pcqCtx);
}
caql_endscan(pcqCtx);
/* keep our lock on the parent relation until commit */
heap_close(parent_rel, NoLock);
/* MPP-6929: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "NO INHERIT"
);
}
/*
* deparse pg_class.reloptions into a list.
*/
static List *
reloptions_list(Oid relid)
{
Datum reloptions;
HeapTuple tuple;
bool isNull = true;
List *opts = NIL;
cqContext *pcqCtx;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 ",
ObjectIdGetDatum(relid)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
reloptions = caql_getattr(pcqCtx,
Anum_pg_class_reloptions,
&isNull);
if (!isNull)
opts = untransformRelOptions(reloptions);
caql_endscan(pcqCtx);
return opts;
}
/*
* Build:
*
* CREATE TABLE pg_temp_<NNNN> AS SELECT * FROM rel
* DISTRIBUTED BY dist_clause
*/
static QueryDesc *
build_ctas_with_dist(Relation rel, List *dist_clause,
List *storage_opts, RangeVar **tmprv, List **hidden_types)
{
Query *q;
SelectStmt *s = makeNode(SelectStmt);
Node *n;
List *parsetrees;
RangeVar *from_tbl;
List *rewritten;
PlannedStmt *stmt;
DestReceiver *dest;
QueryDesc *queryDesc;
RangeVar *tmprel = make_temp_table_name(rel, MyBackendId);
bool pre_built;
{
ResTarget *t = makeNode(ResTarget);
ColumnRef *c = makeNode(ColumnRef);
c->fields = list_make1(makeString("*"));
c->location = -1;
t->val = (Node *)c;
t->location = -1;
s->targetList = list_make1(t);
}
from_tbl = makeRangeVar(NULL /*catalogname*/, get_namespace_name(RelationGetNamespace(rel)),
pstrdup(RelationGetRelationName(rel)), -1);
from_tbl->inhOpt = INH_NO; /* MPP-5300: turn off inheritance -
* Otherwise, the data from the child
* tables is added to the parent!
*/
s->fromClause = list_make1(from_tbl);
/* Handle tables with OIDS */
if (rel->rd_rel->relhasoids)
storage_opts = lappend(storage_opts, defWithOids(true));
pre_built = prebuild_temp_table(rel, tmprel, dist_clause,
storage_opts, hidden_types,
(RelationIsAoRows(rel) ||
RelationIsParquet(rel)));
if (pre_built)
{
InsertStmt *i = makeNode(InsertStmt);
i->relation = tmprel;
i->selectStmt = (Node *)s;
n = (Node *)i;
}
else
{
Oid tblspc = rel->rd_rel->reltablespace;
s->intoClause = makeNode(IntoClause);
s->intoClause->rel = tmprel;
s->intoClause->options = storage_opts;
s->intoClause->tableSpaceName = get_tablespace_name(tblspc);
s->distributedBy = dist_clause;
n = (Node *)s;
}
*tmprv = tmprel;
parsetrees = parse_analyze((Node *)n, NULL, NULL, 0);
Assert(list_length(parsetrees) == 1);
q = (Query *)linitial(parsetrees);
AcquireRewriteLocks(q);
/* Rewrite through rule system */
rewritten = QueryRewrite(q);
/* We don't expect more or less than one result query */
Assert(list_length(rewritten) == 1);
q = (Query *) linitial(rewritten);
Assert(q->commandType == CMD_SELECT || q->commandType == CMD_INSERT);
/* plan the query */
stmt = planner(q, 0, NULL, QRL_ONCE);
/*
* Update snapshot command ID to ensure this query sees results of any
* previously executed queries.
*/
//ActiveSnapshot->curcid = GetCurrentCommandId();
/* Create dest receiver for COPY OUT */
dest = CreateDestReceiver(DestIntoRel, NULL);
/* Create a QueryDesc requesting no output */
queryDesc = CreateQueryDesc(stmt, pstrdup("(internal SELECT INTO query)"),
ActiveSnapshot, InvalidSnapshot,
dest, NULL, false);
return queryDesc;
}
static Datum
new_rel_opts(Relation rel, List *lwith)
{
Datum newOptions = PointerGetDatum(NULL);
bool make_heap = false;
bool need_free_value = false;
if (lwith && list_length(lwith))
{
ListCell *lc;
/*
* See if user has specified appendonly = false. If so, we
* have no use for the existing reloptions
*/
foreach(lc, lwith)
{
DefElem *e = lfirst(lc);
if (pg_strcasecmp(e->defname, "appendonly") == 0 &&
pg_strcasecmp(defGetString(e, &need_free_value), "false") == 0)
{
make_heap = true;
break;
}
}
}
if (!make_heap)
{
/* Get the old reloptions */
bool isnull;
Oid relid = RelationGetRelid(rel);
cqContext *pcqCtx;
HeapTuple optsTuple;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 ",
ObjectIdGetDatum(relid)));
optsTuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(optsTuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
newOptions = caql_getattr(pcqCtx,
Anum_pg_class_reloptions, &isnull);
/* take a copy since we're using it after ReleaseSysCache() */
if (!isnull)
newOptions = datumCopy(newOptions, false, -1);
caql_endscan(pcqCtx);
}
/* Generate new proposed reloptions (text array) */
newOptions = transformRelOptions(newOptions, lwith, false, false);
return newOptions;
}
static RangeVar *
make_temp_table_name(Relation rel, BackendId id)
{
char *nspname,
tmpname[NAMEDATALEN];
/* temporary enough */
snprintf(tmpname, NAMEDATALEN, "pg_temp_%u_%i", RelationGetRelid(rel), id);
nspname = get_namespace_name(RelationGetNamespace(rel));
return makeRangeVar(NULL /*catalogname*/, nspname, pstrdup(tmpname), -1);
}
/* Fill-in the names field of the given TypeName node with the name
* of an existing type that matches the given internal length, and alignment.
*
* If none can be found, return NULL, else return the given TypeName
* pointer.
*
* It may seem as if we should include by-value in the test, but built-in
* types cover the allowable internal lengths for these and we don't want
* to second guess.
*
* Specifically for use by make_typename.
*/
static TypeName *
pick_name_of_similar_type(TypeName *tname, int2 typlen, char typalign)
{
HeapTuple tuple;
/* XXX XXX: not sure why this is RowExclusiveLock */
tuple = caql_getfirst(
NULL,
cql("SELECT * FROM pg_type "
" WHERE typlen = :1 "
" AND typalign = :2 "
" FOR UPDATE ",
Int16GetDatum(typlen),
CharGetDatum(typalign)));
if (HeapTupleIsValid(tuple))
{
Form_pg_type typtuple = (Form_pg_type)GETSTRUCT(tuple);
tname->names =
list_make2(makeString(get_namespace_name(typtuple->typnamespace)),
makeString(pstrdup(NameStr(typtuple->typname))));
}
else
tname = NULL;
return tname;
}
/*
* Build the basic CreateFunctionStmt statement for dispatch as part of
* build_hidden_type().
*/
static CreateFunctionStmt *
build_iofunc(char *newname, char *iotype, char *baseio, char *returns)
{
CreateFunctionStmt *iofunc;
char ioname[NAMEDATALEN];
FunctionParameter *param;
List *args;
if (strcmp("in", iotype) == 0)
args = list_make2(makeString("pg_catalog"), makeString("cstring"));
else
args = list_make1(makeString(newname));
/*
* CREATE FUNCTION F(cstring) RETURNS shelltype AS 'int4(cstring)' language
* internal;
*/
iofunc = makeNode(CreateFunctionStmt);
snprintf(ioname, NAMEDATALEN, "%s_%s", newname, iotype);
iofunc->funcname = list_make1(makeString(pstrdup(ioname)));
param = makeNode(FunctionParameter);
param->argType = makeTypeNameFromNameList(args);
param->mode = FUNC_PARAM_IN;
iofunc->parameters = list_make1(param);
iofunc->returnType = makeTypeName(returns);
iofunc->options = list_make2(makeDefElem("as", (Node *)list_make1(makeString(baseio))),
makeDefElem("language", (Node *)makeString("internal")));
return iofunc;
}
/*
* Build a temporary hidden type for dropped columns for which there is no
* suitable pg_type entry. We find these "ghost types" when a user has created a
* type, dropped a column using it and THEN dropped the type. There is a kind of
* "homeopathic echo" here, where we have the structural details of the type in
* pg_attribute but no way of pin pointing the type configuration by name.
*
* Procedure:
* a) Build a shell type
* b) Create IO functions
* c) Fill out the shell type with IO functions and correct length and alignment
* data
*
* The IO functions do not have to do anything because all values in the column
* will be NULL, the functions will never be called.
*/
static TypeName *
build_hidden_type(Form_pg_attribute att)
{
DefineStmt *newtype;
CreateFunctionStmt *iofunc;
Value *inputname;
Value *outputname;
List *parsetrees;
DestReceiver *dest = None_Receiver;
Query *q;
char *alignname = NULL;
TypeName *typname;
char newname[NAMEDATALEN];
Assert(Gp_role == GP_ROLE_DISPATCH);
snprintf(newname, NAMEDATALEN, "pg_atsdb_%u_%i_%u", att->attrelid,
att->attnum, MyBackendId);
/* shell type */
newtype = makeNode(DefineStmt);
newtype->kind = OBJECT_TYPE;
newtype->defnames = list_make1(makeString(newname));
/* that's all that's needed for the shell! */
parsetrees = parse_analyze((Node *)newtype, NULL, NULL, 0);
Assert(list_length(parsetrees) == 1);
q = (Query *)linitial(parsetrees);
ProcessUtility((Node *)q->utilityStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
CommandCounterIncrement();
/*
* Now for the input function.
*/
iofunc = build_iofunc(newname, "in", "int4in", newname);
inputname = copyObject(linitial(iofunc->funcname));
parsetrees = parse_analyze((Node *)iofunc, NULL, NULL, 0);
q = (Query *)linitial(parsetrees);
ProcessUtility((Node *)q->utilityStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
CommandCounterIncrement();
/* and then output */
/* output function accepts shellname as its argument */
iofunc = build_iofunc(newname, "out", "int4out", "cstring");
outputname = copyObject(linitial(iofunc->funcname));
parsetrees = parse_analyze((Node *)iofunc, NULL, NULL, 0);
q = (Query *)linitial(parsetrees);
ProcessUtility((Node *)q->utilityStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
CommandCounterIncrement();
/* now build full type */
newtype = makeNode(DefineStmt);
newtype->kind = OBJECT_TYPE;
newtype->defnames = list_make1(makeString(newname));
newtype->definition = lappend(newtype->definition, makeDefElem("input", (Node *)inputname));
newtype->definition = lappend(newtype->definition, makeDefElem("output", (Node *)outputname));
if (att->attbyval)
newtype->definition = lappend(newtype->definition, makeDefElem("passedbyvalue",
(Node *)makeInteger(1)));
if (att->attlen == -1)
newtype->definition = lappend(newtype->definition, makeDefElem("internallength",
(Node *)makeString("variable")));
else
newtype->definition = lappend(newtype->definition, makeDefElem("internallength",
(Node *)makeInteger(att->attlen)));
if (att->attalign == 'd')
alignname = "double";
else if (att->attalign == 'i')
alignname = "int4";
else if (att->attalign == 's')
alignname = "int2";
else if (att->attalign == 'c')
alignname = "char";
else
Assert(false); /* shouldn't get here */
newtype->definition = lappend(newtype->definition, makeDefElem("alignment",
(Node *)makeString(alignname)));
parsetrees = parse_analyze((Node *)newtype, NULL, NULL, 0);
q = (Query *)linitial(parsetrees);
ProcessUtility((Node *)q->utilityStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
CommandCounterIncrement();
typname = makeNode(TypeName);
typname->names = list_make1(makeString(pstrdup(newname)));
return typname;
}
/* Allocate and return a pointer to a TypeName node specifying the
* qualified name of an existing type that matches the internal length
* and alignment of the given pg_attribute tuple.
*
* First we try a few hard-coded builtin types. Internal types exist for
* select distinct typlen, typalign
* from pg_type where typlen > 0;
* on a new catalog. Names are not unique.
*
* MPP-5483: If that fails, we look in pg_type for a match. If even that
* fails, we issue an error.
*
* Specifically for use by prebuilt_temp_table.
*/
static TypeName *
make_typname(Form_pg_attribute att, bool *built)
{
TypeName *tname = makeNode(TypeName);
int2 attlen = att->attlen;
Value *typname = NULL;
bool dig_in_catalog = false;
Assert(att->attisdropped); /* better not be here unless */
tname->typmod = att->atttypmod;
if (attlen == -1)
{
if (att->attalign == 'i')
{
if (att->atttypmod < MaxAttrSize)
typname = makeString(pstrdup("varchar"));
else
typname = makeString(pstrdup("numeric"));
/*
* We're building dropped columns. Dropped columns should never
* create a TOAST table because they only ever contain NULL. So,
* even if the original typmod was -1 (variable length), set it to a
* real value so that we avoid TOAST creation. To do this, we must
* be one byte longer than the maximum variable length header size.
* See needs_toast_table().
*
* XXX: there may be occassions where tables with dropped TOASTable
* columns do still have TOAST tables. Need to explore those.
*/
tname->typmod = 1 + Max(NUMERIC_HDRSZ, VARHDRSZ);
}
else if (att->attalign == 'd')
typname = makeString(pstrdup("path"));
else
dig_in_catalog = true;
}
else if (attlen == 1 && att->attalign == 'c')
{
typname = makeString(pstrdup("char"));
}
else if (attlen == 2 && att->attalign == 's')
{
typname = makeString(pstrdup("int2"));
}
else if (attlen == 4 && att->attalign == 'i')
{
typname = makeString(pstrdup("int4"));
}
else if (attlen == 6 && att->attalign == 's')
{
typname = makeString(pstrdup("tid"));
}
else if (attlen == 6 && att->attalign == 'i')
{
typname = makeString(pstrdup("macaddr"));
}
else if (attlen == 8 && att->attalign == 'd')
{
typname = makeString(pstrdup("int8"));
}
else if (attlen == 12 && att->attalign == 'd')
{
typname = makeString(pstrdup("timetz"));
}
else if (attlen == 12 && att->attalign == 'i')
{
typname = makeString(pstrdup("tinterval"));
}
else if (attlen == 16 && att->attalign == 'd')
{
typname = makeString(pstrdup("interval"));
}
else if (attlen == 24 && att->attalign == 'd')
{
typname = makeString(pstrdup("circle"));
}
else if (attlen == 32 && att->attalign == 'd')
{
typname = makeString(pstrdup("box"));
}
else if (attlen == 64 && att->attalign == 'i')
{
typname = makeString(pstrdup("name"));
}
else
{
dig_in_catalog = true;
}
*built = false; /* assume we found an existing type */
if ( dig_in_catalog )
{
tname = pick_name_of_similar_type(tname, attlen, att->attalign);
if (!tname)
{
*built = true;
tname = build_hidden_type(att);
}
}
else
{
tname->names = list_make2(makeString(pstrdup("pg_catalog")), typname);
}
tname->location = -1;
return tname;
}
/*
* If the table has dropped columns, we must create the table and
* drop the columns before we can dispatch the select statement.
* Return true if we do it, false if we do not. If we return false,
* there are no dropped columns and we can do a SELECT INTO later.
* If we need to do it, but fail, issue an error. (See make_type.)
*
* Specifically for build_ctas_with_dist.
*
* Note that the caller should guarantee that isTmpTableAo has
* a value that matches 'opts'.
*/
static bool
prebuild_temp_table(Relation rel, RangeVar *tmpname, List *distro, List *opts,
List **hidden_types, bool isTmpTableAo)
{
bool need_rebuild = false;
int attno = 0;
TupleDesc tupdesc = RelationGetDescr(rel);
List *drop_atts = NIL; /* attnums where we want an attribute to drop */
if (!need_rebuild)
{
for (attno = 0; attno < tupdesc->natts; attno++)
{
if (tupdesc->attrs[attno]->attisdropped)
{
need_rebuild = true;
break;
}
}
}
/*
* If the new table is an AO table with indexes, always use
* Create Table + Insert Into. During Create Table phase,
* we determine whether to create the block directory
* depending on whether the original table has indexes. It is
* important to create the block directory to support the reindex
* later. See MPP-9545 for more info.
*/
if (isTmpTableAo &&
rel->rd_rel->relhasindex)
need_rebuild = true;
if (need_rebuild)
{
CreateStmt *cs = makeNode(CreateStmt);
List *parsetrees;
Query *q;
char *dstr = "__gp_atsdb_droppedcol";
DestReceiver *dest = None_Receiver;
cs->base.relKind = RELKIND_RELATION;
cs->base.distributedBy = distro;
cs->base.relation = tmpname;
cs->ownerid = rel->rd_rel->relowner;
cs->base.tablespacename = get_tablespace_name(rel->rd_rel->reltablespace);
cs->buildAoBlkdir = false;
if (isTmpTableAo &&
rel->rd_rel->relhasindex)
cs->buildAoBlkdir = true;
cs->base.options = opts;
for (attno = 0; attno < tupdesc->natts; attno++)
{
ColumnDef *cd = makeNode(ColumnDef);
TypeName *tname = NULL;
Form_pg_attribute att = tupdesc->attrs[attno];
cd->is_local = true;
if (att->attisdropped)
{
NameData alias;
bool built;
tname = make_typname(att, &built);
snprintf(NameStr(alias), sizeof(alias), "%s%d", dstr, attno);
cd->colname = pstrdup(NameStr(alias));
drop_atts = lappend_int(drop_atts, attno);
if (built)
*hidden_types = lappend(*hidden_types, tname->names);
}
else
{
Type typ = typeidType(att->atttypid);
Oid typnamespace = ((Form_pg_type) GETSTRUCT(typ))->typnamespace;
char *nspname = get_namespace_name(typnamespace);
int arno;
char *typstr;
int4 ndims = att->attndims;
tname = makeNode(TypeName);
if (!PointerIsValid(nspname))
elog(ERROR, "could not lookup namespace %d", typnamespace);
cd->colname = pstrdup(NameStr(att->attname));
typstr = typeTypeName(typ);
tname->names = list_make2(makeString(nspname),
makeString(typstr));
ReleaseType(typ);
tname->typmod = att->atttypmod;
/*
* If this is a built in array type, like _int4, then reduce
* the array dimensions by 1. This is an annoying postgres
* hack which I wish would go away.
*/
if (typstr && typstr[0] == '_' && ndims > 0)
ndims--;
for (arno = 0; arno < ndims; arno++)
/* bound of -1 are fine because this has no effect on data */
tname->arrayBounds = lappend(tname->arrayBounds,
makeInteger(-1));
}
tname->location = -1;
cd->typname = tname;
cs->base.tableElts = lappend(cs->base.tableElts, cd);
}
parsetrees = parse_analyze((Node *)cs, NULL, NULL, 0);
Assert(list_length(parsetrees) == 1);
q = (Query *)linitial(parsetrees);
ProcessUtility((Node *)q->utilityStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
CommandCounterIncrement();
/* should always be true */
if (drop_atts)
{
/* Build an ALTER TABLE DROP COLUMN statement */
AlterTableStmt *ats = makeNode(AlterTableStmt);
ListCell *lc;
ats->relkind = OBJECT_TABLE;
ats->relation = tmpname;
foreach(lc, drop_atts)
{
AlterTableCmd *atc = makeNode(AlterTableCmd);
NameData colname;
attno = lfirst_int(lc);
atc->subtype = AT_DropColumn;
snprintf(NameStr(colname), NAMEDATALEN, "%s%d", dstr, attno);
atc->name = pstrdup(NameStr(colname));
ats->cmds = lappend(ats->cmds, atc);
}
#ifdef NOT_USED
elog_node_display(NOTICE, "DROP STATEMENT", ats, true);
#endif
parsetrees = parse_analyze((Node *)ats, NULL, NULL, 0);
Assert(list_length(parsetrees) == 1);
q = (Query *)linitial(parsetrees);
ProcessUtility((Node *)q->utilityStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
CommandCounterIncrement();
}
}
return need_rebuild;
}
/* Build a human readable tag for what we're doing */
static char *
make_distro_str(List *lwith, List *ldistro)
{
char *distro_str = "SET WITH DISTRIBUTED BY";
if (lwith && ldistro)
distro_str = "SET WITH DISTRIBUTED BY";
else
{
if (lwith)
distro_str = "SET WITH";
else if (ldistro)
distro_str = "SET DISTRIBUTED BY";
}
return pstrdup(distro_str); /* don't return a stack address */
}
/*
* ALTER TABLE SET DISTRIBUTED BY
*
* set distribution policy for rel
*
* GPSQL: Despite the GP_ROLE_DISPATCH references, note that this entire
* function executes only on the master.
*/
static void
ATExecSetDistributedBy(Relation rel, Node *node, AlterTableCmd *cmd)
{
Assert(Gp_role != GP_ROLE_EXECUTE); /* GPSQL */
List *lprime;
List *lwith, *ldistro;
List *cols = NIL;
ListCell *lc;
GpPolicy *policy = NULL;
QueryDesc *queryDesc;
RangeVar *tmprv = NULL;
Oid tmprelid;
Oid tarrelid = RelationGetRelid(rel);
List *oid_map = NIL;
List *qe_data = NIL; /* data to pass to QEs */
bool rand_pol = false;
bool force_reorg = false;
Datum newOptions = PointerGetDatum(NULL);
bool change_policy = false;
bool is_ao = false;
bool is_aocs = false;
List *hidden_types = NIL; /* types we need to build for dropped columns */
/* Permissions checks */
if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(rel));
/* Can't ALTER TABLE SET system catalogs */
if (IsSystemRelation(rel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel)),
errOmitLocation(true)));
Assert(PointerIsValid(node));
Assert(IsA(node, List));
lprime = (List *)node;
/*
* First element is the WITH clause, second element is the actual
* distribution clause.
*/
lwith = (List *)linitial(lprime);
ldistro = (List *)lsecond(lprime);
if (Gp_role == GP_ROLE_UTILITY)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SET DISTRIBUTED BY not supported in utility mode")));
/* we only support fully distributed tables */
if (Gp_role == GP_ROLE_DISPATCH)
{
if (rel->rd_cdbpolicy->ptype != POLICYTYPE_PARTITIONED)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("%s not supported on non-distributed tables",
ldistro ? "SET DISTRIBUTED BY" : "SET WITH"),
errOmitLocation(true)));
}
if (Gp_role == GP_ROLE_DISPATCH)
{
if (lwith)
{
bool seen_reorg = false;
ListCell *lc;
char *reorg_str = "reorganize";
List *nlist = NIL;
/* remove the "REORGANIZE=true/false" from the WITH clause */
foreach(lc, lwith)
{
DefElem *def = lfirst(lc);
if (pg_strcasecmp(reorg_str, def->defname) != 0)
{
/* MPP-7770: disable changing storage options for now */
if (!gp_setwith_alter_storage)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("option \"%s\" not supported", def->defname),
errOmitLocation(true)));
if (pg_strcasecmp(def->defname, "appendonly") == 0)
{
if (IsA(def->arg, String)
&& pg_strcasecmp(strVal(def->arg), "true") == 0)
{
is_ao = true;
}
else
{
is_ao = false;
}
}
if (pg_strcasecmp(def->defname, "orientation") == 0)
{
if (IsA(def->arg, String)
&& pg_strcasecmp(strVal(def->arg), "column") == 0)
{
ereport(ERROR,
(errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED),
errmsg("orientation option \"column\" is deprecated. Not support it any more."),
errhint("valid orientation option is \"row\" or \"parquet\""),
errOmitLocation(true)));
}
else if (IsA(def->arg, String)
&& pg_strcasecmp(strVal(def->arg), "parquet") == 0)
{
is_aocs = true;
}
else
{
if (!IsA(def->arg, String)
|| pg_strcasecmp(strVal(def->arg), "row") != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid orientation option"),
errhint("valid orientation option is \"row\" or \"parquet\""),
errOmitLocation(true)));
}
}
nlist = lappend(nlist, def);
}
else
{
/* have we been here before ? */
if (seen_reorg)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"%s\" specified more than once", reorg_str),
errOmitLocation(true)));
seen_reorg = true;
if (!def->arg)
{
force_reorg = true;
}
else
{
if (IsA(def->arg, String)
&& pg_strcasecmp("TRUE", strVal(def->arg)) == 0)
{
force_reorg = true;
}
else if (IsA(def->arg, String)
&& pg_strcasecmp("FALSE", strVal(def->arg)) == 0)
{
force_reorg = false;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Invalid REORGANIZE option"),
errhint("valid REORGANIZE option is \"true\" or \"false\""),
errOmitLocation(true)));
}
}
}
}
if (is_aocs && !is_ao)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("specify orientation requires appendonly"),
errOmitLocation(true)));
}
lwith = nlist;
/*
* If there are other storage options, but REORGANIZE is not
* specified, then the storage must be re-org'd. But if
* REORGANIZE was specified use that setting.
*
* If the user specified we not force a reorg but there are other
* WITH clause items, then we cannot honour what the user has
* requested.
*/
if (!seen_reorg && list_length(lwith))
force_reorg = true;
else if (seen_reorg && force_reorg == false && list_length(lwith))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("%s must be set to true when changing storage "
"type", reorg_str),
errOmitLocation(true)));
newOptions = new_rel_opts(rel, lwith);
/* ensure that the options parse */
if (newOptions)
(void)heap_reloptions(rel->rd_rel->relkind, newOptions, true);
}
else
newOptions = new_rel_opts(rel, NIL);
if (ldistro)
change_policy = true;
if (ldistro && linitial(ldistro) == NULL)
{
Insist(list_length(ldistro) == 1);
rand_pol = true;
if (!force_reorg)
{
if (rel->rd_cdbpolicy->nattrs == 0)
ereport(WARNING,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("distribution policy of relation \"%s\" "
"already set to DISTRIBUTED RANDOMLY",
RelationGetRelationName(rel)),
errhint("Use ALTER TABLE \"%s\" "
"SET WITH (REORGANIZE=TRUE) "
"DISTRIBUTED RANDOMLY "
"to force a random redistribution",
RelationGetRelationName(rel)),
errOmitLocation(true)));
}
policy = (GpPolicy *) palloc(sizeof(GpPolicy));
policy->ptype = POLICYTYPE_PARTITIONED;
policy->nattrs = 0;
/**
* consider user can modify default_hash_table_bucket_number in session,
* should set bucketnum to the current hash_table_bucket_number during reorganize table
*/
policy->bucketnum = GetHashDistPartitionNum();
rel->rd_cdbpolicy = GpPolicyCopy(GetMemoryChunkContext(rel),
policy);
GpPolicyReplace(RelationGetRelid(rel), policy);
/* only need to rebuild if have new storage options */
if (!(DatumGetPointer(newOptions) || force_reorg))
{
/*
* caller expects ATExecSetDistributedBy() to close rel
* (see the non-random distribution case below for why.
*/
heap_close(rel, NoLock);
goto l_distro_fini;
}
}
}
/*
* Changing a table from random distribution to a specific distribution
* policy is the hard bit. For that, we must do the following:
*
* a) Ensure that the proposed policy is sensible
* b) Create a temporary table and reorganise data according to
* our desired distribution policy. To do this, we build a Query
* node which expresses the query CREATE TABLE tmp_table_name AS
* SELECT * FROM cur_table DISTRIBUTED BY (policy)
* c) Execute the query across all nodes
* d) Update our parse tree to include the details of the newly created
* table
* e) Update the ownership of the temporary table
* f) Swap the relfilenodes of the existing table and the temporary table
* g) Update the policy on the QD to reflect the underlying data
* h) Drop the temporary table -- and with it, the old copy of the data
*/
if (Gp_role == GP_ROLE_DISPATCH)
{
volatile Snapshot saveSnapshot = NULL;
if (change_policy)
{
policy = palloc(sizeof(GpPolicy) +
sizeof(policy->attrs[0]) * list_length(ldistro));
policy->ptype = POLICYTYPE_PARTITIONED;
policy->nattrs = 0;
/**
* consider user can modify default_hash_table_bucket_number in session,
* should set bucketnum to the current hash_table_bucket_number during reorganize table
*/
policy->bucketnum = GetHashDistPartitionNum();
/* Step (a) */
if (!rand_pol)
{
foreach(lc, ldistro)
{
char *colName = strVal((Value *)lfirst(lc));
cqContext *attcqCtx;
HeapTuple tuple;
attcqCtx = caql_getattname_scan(NULL,
RelationGetRelid(rel),
colName);
tuple = caql_get_current(attcqCtx);
AttrNumber attnum;
if (list_member(cols, lfirst(lc)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("distribution policy must be a "
"unique set of columns"),
errhint("Column \"%s\" appears "
"more than once", colName),
errOmitLocation(true)));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of "
"relation \"%s\" does not exist",
colName,
RelationGetRelationName(rel)),
errOmitLocation(true)));
attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
/* Prevent them from altering a system attribute */
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot distribute by system column \"%s\"",
colName),
errOmitLocation(true)));
policy->attrs[policy->nattrs++] = attnum;
caql_endscan(attcqCtx);
cols = lappend(cols, lfirst(lc));
} /* end foreach */
Assert(policy->nattrs > 0);
/*
* See if the the old policy is the same as the new one but
* remember, we still might have to rebuild if there are new
* storage options.
*/
if (!DatumGetPointer(newOptions) && !force_reorg &&
(policy->nattrs == rel->rd_cdbpolicy->nattrs))
{
int i;
bool diff = false;
for (i = 0; i < policy->nattrs; i++)
{
if (policy->attrs[i] != rel->rd_cdbpolicy->attrs[i])
{
diff = true;
break;
}
}
if (!diff)
{
char *dist =
palloc(list_length(ldistro) * (NAMEDATALEN + 1));
dist[0] = '\0';
foreach(lc, ldistro)
{
if (lc != list_head(ldistro))
strcat(dist, ",");
strcat(dist, strVal(lfirst(lc)));
}
ereport(WARNING,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("distribution policy of relation \"%s\" "
"already set to (%s)",
RelationGetRelationName(rel),
dist),
errhint("Use ALTER TABLE \"%s\" "
"SET WITH (REORGANIZE=TRUE) "
"DISTRIBUTED BY (%s) "
"to force redistribution",
RelationGetRelationName(rel),
dist),
errOmitLocation(true)));
heap_close(rel, NoLock);
/* Tell QEs to do nothing */
linitial(lprime) = NULL;
lsecond(lprime) = list_make1(NULL);
return;
/* don't goto l_distro_fini -- didn't do anything! */
}
}
}
}
if (!ldistro)
ldistro = make_dist_clause(rel);
/* force the use of legacy query optimizer, since PQO will not redistribute the tuples if the current and required
distributions are both RANDOM even when reorganize is set to "true"*/
bool saveOptimizerGucValue = optimizer;
optimizer = false;
if (saveOptimizerGucValue)
{
elog(LOG, "ALTER SET DISTRIBUTED BY: falling back to legacy query optimizer to ensure re-distribution of tuples.");
}
PG_TRY();
{
/*
* We need to update our snapshot here to make sure we see all
* committed work. We have an exclusive lock on the table so no one
* will be able to access the table now.
*/
saveSnapshot = ActiveSnapshot;
ActiveSnapshot = CopySnapshot(GetLatestSnapshot());
/* Step (b) - build CTAS */
queryDesc = build_ctas_with_dist(rel, ldistro,
untransformRelOptions(newOptions),
&tmprv,
&hidden_types);
/* Step (c) - run on all nodes */
ExecutorStart(queryDesc, 0);
ExecutorRun(queryDesc, ForwardScanDirection, 0L);
ExecutorEnd(queryDesc);
FreeQueryDesc(queryDesc);
/* Restore the old snapshot */
ActiveSnapshot = saveSnapshot;
optimizer = saveOptimizerGucValue;
}
PG_CATCH();
{
ActiveSnapshot = saveSnapshot;
optimizer = saveOptimizerGucValue;
PG_RE_THROW();
}
PG_END_TRY();
CommandCounterIncrement(); /* see the effects of the command */
/*
* Step (d) - tell the seg nodes about the temporary relation. This
* involves stomping on the node we've been given
*/
qe_data = lappend(qe_data, makeInteger(MyBackendId));
}
/*
GPSQL: ALTER SET DISTRIBUTED BY doesn't need to propagate the tmprv and relopts to
the QEs, because the QEs won't be needing to revise their own catalogs.
else
{
int backend_id;
bool reorg = true;
Assert(list_length(lprime) >= 2);
lwith = linitial(lprime);
qe_data = lsecond(lprime);
if (lwith)
{
if (list_length(lwith))
{
DefElem *e = linitial(lwith);
if (pg_strcasecmp(e->defname, "reorganize") == 0 &&
pg_strcasecmp(strVal(e->arg), "false") == 0)
reorg = false;
}
}
else if (!lwith)
reorg = false;
if (!reorg && list_length(qe_data) == 1 && linitial(qe_data) == NULL)
{
heap_close(rel, NoLock);
goto l_distro_fini;
}
backend_id = intVal(linitial(qe_data));
tmprv = make_temp_table_name(rel, backend_id);
oid_map = lsecond(qe_data);
if (list_length(qe_data) == 3)
hidden_types = lthird(qe_data);
if (list_length(lprime) == 3)
{
Value *v = lthird(lprime);
if (intVal(v) == 1)
{
is_ao = true;
relstorage = RELSTORAGE_AOROWS;
}
else
{
is_ao = false;
relstorage = RELSTORAGE_HEAP;
}
}
newOptions = new_rel_opts(rel, lwith);
}
*/
/*
* Step (e) - Correct ownership on temporary table:
* necessary so that the toast tables/indices have the correct
* owner after we swap them.
*
* Note: ATExecChangeOwner does NOT dispatch, so this does not
* belong in the dispatch block above (MPP-9663).
*/
ATExecChangeOwner(RangeVarGetRelid(tmprv, false, false /*allowHcatalog*/),
rel->rd_rel->relowner, true);
CommandCounterIncrement(); /* see the effects of the command */
/*
* Step (f) - swap relfilenodes and MORE !!!
*
* Just lookup the Oid and pass it to swap_relation_files(). To do
* this we must close the rel, since it needs to be forgotten by
* the cache, we keep the lock though. ATRewriteCatalogs() knows
* that we've closed the relation here.
*/
heap_close(rel, NoLock);
tmprelid = RangeVarGetRelid(tmprv, false, false /*allowHcatalog*/);
swap_relation_files(tarrelid, tmprelid, false);
if (DatumGetPointer(newOptions))
{
Datum repl_val[Natts_pg_class];
bool repl_null[Natts_pg_class];
bool repl_repl[Natts_pg_class];
HeapTuple newOptsTuple;
HeapTuple tuple;
cqContext *relcqCtx;
/*
* All we need do here is update the pg_class row; the new
* options will be propagated into relcaches during
* post-commit cache inval.
*/
MemSet(repl_val, 0, sizeof(repl_val));
MemSet(repl_null, false, sizeof(repl_null));
MemSet(repl_repl, false, sizeof(repl_repl));
if (newOptions != (Datum) 0)
repl_val[Anum_pg_class_reloptions - 1] = newOptions;
else
repl_null[Anum_pg_class_reloptions - 1] = true;
repl_repl[Anum_pg_class_reloptions - 1] = true;
relcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(tarrelid)));
tuple = caql_getnext(relcqCtx);
Insist(HeapTupleIsValid(tuple));
newOptsTuple = caql_modify_current(relcqCtx,
repl_val, repl_null, repl_repl);
caql_update_current(relcqCtx, newOptsTuple);
/* and Update indexes (implicit) */
heap_freetuple(newOptsTuple);
caql_endscan(relcqCtx);
/*
* Increment cmd counter to make updates visible; this is
* needed because the same tuple has to be updated again
*/
CommandCounterIncrement();
}
/* now, reindex */
/* GPSQL: This will run only on the master, and that's ok. At this
* stage of GPSQL, the only indexes we're concerned with are those
* that reside on the master, which index master metadata. */
reindex_relation(tarrelid, false, false /* ao_segs ? */, false,
&oid_map, Gp_role == GP_ROLE_DISPATCH);
/* Step (g) */
if (Gp_role == GP_ROLE_DISPATCH)
{
if (change_policy)
GpPolicyReplace(tarrelid, policy);
qe_data = lappend(qe_data, oid_map);
if (hidden_types)
qe_data = lappend(qe_data, hidden_types);
linitial(lprime) = lwith;
lsecond(lprime) = qe_data;
lprime = lappend(lprime, makeInteger(is_ao ? (is_aocs ? 2 : 1) : 0));
}
/* Step (h) Drop the table */
{
ObjectAddress object;
object.classId = RelationRelationId;
object.objectId = tmprelid;
object.objectSubId = 0;
performDeletion(&object, DROP_RESTRICT);
if (hidden_types)
{
object.classId = TypeRelationId;
foreach(lc, hidden_types)
{
TypeName *tname = makeTypeNameFromNameList(lfirst(lc));
Oid typid = typenameTypeId(NULL, tname);
object.objectId = typid;
/* cascade to get rid of functions */
performDeletion(&object, DROP_CASCADE);
}
}
}
AORelRemoveHashEntryOnCommit(tarrelid);
l_distro_fini:
/* MPP-6929: metadata tracking */
if (Gp_role == GP_ROLE_DISPATCH)
{
char *distro_str = make_distro_str(lwith, ldistro);
/* don't check relkind - must be a table */
MetaTrackUpdObject(RelationRelationId,
tarrelid,
GetUserId(),
"ALTER", distro_str
);
}
}
/*
* rel could be a toast table, toast table index or index on a
* table. Get that table's OID.
*/
static Oid
rel_get_table_oid(Relation rel)
{
Oid toid = RelationGetRelid(rel);
Relation thisrel = NULL;
if (rel->rd_rel->relkind == RELKIND_INDEX)
{
Oid indrelid;
int fetchCount;
indrelid = caql_getoid_plus(
NULL,
&fetchCount,
NULL,
cql("SELECT indrelid FROM pg_index "
" WHERE indexrelid = :1 ",
ObjectIdGetDatum(toid)));
if (!fetchCount)
elog(ERROR, "cache lookup failure: cannot find pg_index entry for OID %u",
toid);
toid = indrelid;
thisrel = relation_open(toid, NoLock);
toid = rel_get_table_oid(thisrel); /* **RECURSIVE** */
relation_close(thisrel, NoLock);
return toid;
}
else if (rel->rd_rel->relkind == RELKIND_AOSEGMENTS ||
rel->rd_rel->relkind == RELKIND_AOBLOCKDIR ||
rel->rd_rel->relkind == RELKIND_TOASTVALUE)
{
/* use pg_depend to find parent */
cqContext *pcqCtx;
cqContext cqc;
HeapTuple tup;
/* XXX XXX: SnapShotAny */
pcqCtx = caql_beginscan(
caql_snapshot(cqclr(&cqc), SnapshotAny),
cql("SELECT * FROM pg_depend "
" WHERE classid = :1 "
" AND objid = :2 ",
ObjectIdGetDatum(RelationRelationId),
ObjectIdGetDatum(toid)));
/*
* We use SnapshotAny because the ordering of the dependency code means
* that some times we've already deleted the pg_depend tuple. So, we do
* an extra test below to see that, if this tuple is deleted, it was
* done so by our xid, otherwise we overlook it.
*/
while (HeapTupleIsValid(tup = caql_getnext(pcqCtx)))
{
Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
HeapTupleHeader htup = tup->t_data;
if (!TransactionIdIsNormal(HeapTupleHeaderGetXmax(htup)) ||
TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(htup)))
{
if (foundDep->deptype == DEPENDENCY_INTERNAL)
{
toid = foundDep->refobjid;
break;
}
}
}
caql_endscan(pcqCtx);
}
return toid;
}
/*
* partition children, toast tables and indexes, and indexes on partition
* children do not long lived locks because the lock on the partition master
* protects us.
*/
bool
rel_needs_long_lock(Oid relid)
{
bool needs_lock = true;
Relation rel = relation_open(relid, NoLock);
relid = rel_get_table_oid(rel);
relation_close(rel, NoLock);
if (Gp_role == GP_ROLE_DISPATCH)
needs_lock = !rel_is_child_partition(relid);
else
{
int fetchCount;
fetchCount = caql_getcount(
NULL,
cql("SELECT COUNT(*) FROM pg_inherits "
" WHERE inhrelid = :1 "
" AND inhseqno = :2 ",
ObjectIdGetDatum(relid),
Int32GetDatum(1)));
if (fetchCount)
needs_lock = false;
}
return needs_lock;
}
/*
* ALTER TABLE ... ADD PARTITION
*
*/
static AlterPartitionId *
wack_pid_relname(AlterPartitionId *pid,
PartitionNode **ppNode,
Relation rel,
PgPartRule **ppar_prule,
char **plrelname,
char *lRelNameBuf)
{
AlterPartitionId *locPid = pid; /* local pid if IDRule */
if (!pid)
return NULL;
if (AT_AP_IDRule == locPid->idtype)
{
List *l1 = (List *)pid->partiddef;
ListCell *lc;
PgPartRule *par_prule = NULL;
lc = list_head(l1);
*ppar_prule = (PgPartRule*) lfirst(lc);
par_prule = *ppar_prule;
*plrelname = par_prule->relname;
if (par_prule && par_prule->topRule && par_prule->topRule->children)
*ppNode = par_prule->topRule->children;
lc = lnext(lc);
locPid = (AlterPartitionId *)lfirst(lc);
Assert(locPid);
}
else
{
*ppNode = RelationBuildPartitionDesc(rel, false);
snprintf(lRelNameBuf, (NAMEDATALEN*2),
"relation \"%s\"",
RelationGetRelationName(rel));
*plrelname = lRelNameBuf;
}
return locPid;
}
static void
ATPExecPartAdd(AlteredTableInfo *tab,
Relation rel,
AlterPartitionCmd *pc,
AlterTableType att)
{
AlterPartitionId *pid = (AlterPartitionId *)pc->partid;
PgPartRule *prule = NULL;
PartitionNode *pNode = NULL;
char *parTypName = NULL;
char namBuf[NAMEDATALEN];
AlterPartitionId *locPid = NULL; /* local pid if IDRule */
PgPartRule* par_prule = NULL; /* prule for parent if IDRule */
char lRelNameBuf[(NAMEDATALEN*2)];
char *lrelname = NULL;
AlterPartitionCmd *pc2 = NULL;
bool is_split = false;
bool bSetTemplate = (att == AT_PartSetTemplate);
/* This whole function is QD only. */
if (Gp_role != GP_ROLE_DISPATCH)
return;
pc2 = (AlterPartitionCmd *)pc->arg2;
if (pc2->partid)
is_split = true;
locPid =
wack_pid_relname(pid,
&pNode,
rel,
&par_prule,
&lrelname,
lRelNameBuf);
if (!pNode)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("%s is not partitioned", lrelname),
errOmitLocation(true)));
switch (pNode->part->parkind)
{
case 'h': /* hash */
parTypName = "HASH";
break;
case 'r': /* range */
parTypName = "RANGE";
break;
case 'l': /* list */
parTypName = "LIST";
break;
default:
elog(ERROR, "unrecognized partitioning kind '%c'",
pNode->part->parkind);
Assert(false);
break;
} /* end switch */
if (locPid->idtype == AT_AP_IDName)
snprintf(namBuf, sizeof(namBuf), " \"%s\"",
strVal(locPid->partiddef));
else
namBuf[0] = '\0';
if ('h' == pNode->part->parkind)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add HASH partition%s to %s",
namBuf,
lrelname),
errhint("recreate the table to increase the "
"number of partitions"),
errOmitLocation(true)));
/* partition must have a valid name */
if ((locPid->idtype != AT_AP_IDName)
&& (locPid->idtype != AT_AP_IDNone))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot ADD partition%s to %s by rank or value",
namBuf,
lrelname),
errhint("use a named partition"),
errOmitLocation(true)));
PartitionElem *pElem = (PartitionElem *) pc2->arg1;
Node *pStoreAttr = pElem->storeAttr;
if (pStoreAttr && ((AlterPartitionCmd *)pStoreAttr)->arg1)
{
List *pWithList = (List *)(((AlterPartitionCmd *)pStoreAttr)->arg1);
GpPolicy *parentPolicy = GpPolicyFetch(CurrentMemoryContext, RelationGetRelid(rel));
int bucketnum = parentPolicy->bucketnum;
int child_bucketnum = GetRelOpt_bucket_num_fromOptions(pWithList, bucketnum);
if (child_bucketnum != bucketnum)
ereport(ERROR,
(errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED),
errmsg("distribution policy for partition%s "
"must be the same as that for %s",
namBuf,
lrelname)));
}
/* don't check if splitting or setting a subpartition template */
if (!is_split && !bSetTemplate)
/* We complain if partition already exists, so prule should be NULL */
prule = get_part_rule(rel, pid, true, false,
CurrentMemoryContext, NULL,
false);
if (!prule)
{
bool isDefault = intVal(pc->arg1);
/* DEFAULT checks */
if (!isDefault && (pNode->default_part) &&
!is_split && !bSetTemplate) /* MPP-6093: ok to reset template */
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add %s partition%s to "
"%s with DEFAULT partition \"%s\"",
parTypName,
namBuf,
lrelname,
pNode->default_part->parname),
errhint("need to SPLIT partition \"%s\"",
pNode->default_part->parname),
errOmitLocation(true)));
if (isDefault && !is_split)
{
if ('h' == pNode->part->parkind)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add a DEFAULT partition%s "
"to %s of type HASH",
namBuf,
lrelname),
errOmitLocation(true)));
/* MPP-6093: ok to reset template */
if (pNode->default_part && !bSetTemplate)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("DEFAULT partition \"%s\" for "
"%s already exists",
pNode->default_part->parname,
lrelname),
errOmitLocation(true)));
/* XXX XXX: move this check to gram.y ? */
if (pElem->boundSpec)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid use of boundary specification "
"for DEFAULT partition%s of %s",
namBuf,
lrelname),
errOmitLocation(true)));
}
/* Do the real work for add ... */
if ('r' == pNode->part->parkind)
{
atpxPartAddList(rel, pc, pNode,
pc2->arg2, /* utl statement */
(locPid->idtype == AT_AP_IDName) ?
locPid->partiddef : NULL, /* partition name */
isDefault, (PartitionElem *) pc2->arg1,
PARTTYP_RANGE,
par_prule,
lrelname,
bSetTemplate,
rel->rd_rel->relowner);
}
else if ('l' == pNode->part->parkind)
{
atpxPartAddList(rel, pc, pNode,
pc2->arg2, /* utl statement */
(locPid->idtype == AT_AP_IDName) ?
locPid->partiddef : NULL, /* partition name */
isDefault, (PartitionElem *) pc2->arg1,
PARTTYP_LIST,
par_prule,
lrelname,
bSetTemplate,
rel->rd_rel->relowner);
}
}
/* MPP-6929: metadata tracking */
if (!is_split && !bSetTemplate)
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"PARTITION", "ADD"
);
} /* end ATPExecPartAdd */
/*
* Add partition hierarchy to catalogs.
*
* parts is a list, with a partitioning rule and potentially sub-partitions.
*/
static void
ATExecPartAddInternal(Relation rel, Node *def)
{
PartitionBy *part = (PartitionBy *)def;
add_part_to_catalog(RelationGetRelid(rel), part, false);
}
/* ALTER TABLE ... ALTER PARTITION */
static void
ATPExecPartAlter(List **wqueue, AlteredTableInfo *tab, Relation rel,
AlterPartitionCmd *pc)
{
AlterPartitionId *pid = (AlterPartitionId *)pc->partid;
AlterTableCmd *atc = (AlterTableCmd *)pc->arg1;
PgPartRule *prule = NULL;
List *pidlst = NIL;
AlterPartitionId *pid2 = makeNode(AlterPartitionId);
AlterPartitionCmd *pc2 = NULL;
bool bPartitionCmd = true; /* true if a "partition" cmd */
Relation rel2 = rel;
while (1)
{
pidlst = lappend(pidlst, pid);
if (atc->subtype != AT_PartAlter)
break;
pc2 = (AlterPartitionCmd *)atc->def;
pid = (AlterPartitionId *)pc2->partid;
atc = (AlterTableCmd *)pc2->arg1;
}
/* let split, exchange through */
if (!(atc->subtype == AT_PartExchange ||
atc->subtype == AT_PartSplit ||
atc->subtype == AT_SetDistributedBy) &&
Gp_role != GP_ROLE_DISPATCH)
return;
switch (atc->subtype)
{
case AT_PartAdd: /* Add */
case AT_PartCoalesce: /* Coalesce */
case AT_PartDrop: /* Drop */
case AT_PartSplit: /* Split */
case AT_PartMerge: /* Merge */
case AT_PartModify: /* Modify */
case AT_PartSetTemplate: /* Set Subpartition Template */
if (!gp_allow_non_uniform_partitioning_ddl)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Cannot modify multi-level partitioned table to have non-uniform partitioning hierarchy.")));
}
break;
/* XXX XXX: treat set subpartition template special:
need to pass the pNode to ATPExecPartSetTemplate and bypass
ATExecCmd ...
*/
case AT_PartRename: /* Rename */
case AT_PartExchange: /* Exchange */
case AT_PartTruncate: /* Truncate */
break;
/* Next, list of ALTER TABLE commands applicable to a child table */
case AT_SetTableSpace: /* Set Tablespace */
case AT_SetDistributedBy: /* SET DISTRIBUTED BY */
bPartitionCmd = false;
break;
default:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot ALTER PARTITION for relation \"%s\"",
RelationGetRelationName(rel)),
errOmitLocation(true)));
}
if (Gp_role == GP_ROLE_DISPATCH)
{
pid2->idtype = AT_AP_IDList;
pid2->partiddef = (Node *)pidlst;
pid2->location = -1;
prule = get_part_rule(rel, pid2, true, true,
CurrentMemoryContext, NULL,
false);
if (!prule)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot ALTER PARTITION for relation \"%s\"",
RelationGetRelationName(rel)),
errOmitLocation(true)));
if (bPartitionCmd)
{
/* build the IDRule for the nested ALTER PARTITION cmd ... */
Assert(IsA(atc->def, AlterPartitionCmd));
pc2 = (AlterPartitionCmd *)atc->def;
pid = (AlterPartitionId *)pc2->partid;
pid2->idtype = AT_AP_IDRule;
pid2->partiddef = (Node *)list_make2((Node *)prule, pid);
pid2->location = -1;
pc2->partid = (Node *)pid2;
}
else /* treat as a table */
{
/* Update PID for use on QEs */
pid2->idtype = AT_AP_IDRule;
pid2->partiddef = (Node *)list_make2((Node *)prule, pid);
pid2->location = -1;
pc->partid = (Node *)pid2;
/* get the child table relid */
rel2 = heap_open(prule->topRule->parchildrelid,
AccessExclusiveLock);
/* MPP-5524: check if can change distribution policy */
if (atc->subtype == AT_SetDistributedBy)
{
List *dist_cnames = NIL;
Assert(IsA(atc->def, List));
dist_cnames = lsecond((List*)atc->def);
/* might be null if no policy set, e.g. just a change
* of storage options...
*/
if (dist_cnames)
{
Assert(IsA(dist_cnames, List));
if (! can_implement_dist_on_part(rel, dist_cnames) )
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot ALTER PARTITION ... SET "
"DISTRIBUTED BY for %s",
prule->relname),
errhint("distribution policy of "
"partition must match parent"),
errOmitLocation(true)
));
}
}
/*
* Give the notice first, because it looks weird if it
* comes after a failure message
*/
ereport(NOTICE,
(errmsg("altering table \"%s\" "
"(%s)",
RelationGetRelationName(rel2),
prule->relname)));
}
}
else if (Gp_role == GP_ROLE_EXECUTE && atc->subtype == AT_SetDistributedBy)
{
pid = (AlterPartitionId *)pc->partid;
Assert(IsA(pid->partiddef, List));
prule = (PgPartRule *)linitial((List *)pid->partiddef);
/* get the child table relid */
rel2 = heap_open(prule->topRule->parchildrelid,
AccessExclusiveLock);
bPartitionCmd = false;
}
/* execute the command */
ATExecCmd(wqueue, tab, rel2, atc);
if (!bPartitionCmd)
{
/* NOTE: for the case of Set Distro,
* ATExecSetDistributedBy rebuilds the relation, so rel2
* is already gone!
*/
if (atc->subtype != AT_SetDistributedBy)
heap_close(rel2, NoLock);
}
/* MPP-6929: metadata tracking - don't track this! */
} /* end ATPExecPartAlter */
/* ALTER TABLE ... COALESCE PARTITION */
static void
ATPExecPartCoalesce(Relation rel,
AlterPartitionCmd *pc)
{
AlterPartitionId *pid = (AlterPartitionId *)pc->partid;
PgPartRule *prule = NULL;
if (Gp_role != GP_ROLE_DISPATCH)
return;
prule = get_part_rule(rel, pid, true, true, CurrentMemoryContext, NULL,
false);
if (0)
{
parruleord_open_gap(
prule->pNode->part->partid,
prule->pNode->part->parlevel,
prule->topRule->parparentoid,
prule->topRule->parruleord,
0,
CurrentMemoryContext);
}
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot COALESCE PARTITION for relation \"%s\"",
RelationGetRelationName(rel)),
errOmitLocation(true)));
}
/* ALTER TABLE ... DROP PARTITION */
static void
ATPExecPartDrop(Relation rel,
AlterPartitionCmd *pc)
{
AlterPartitionId *pid = (AlterPartitionId *)pc->partid;
PgPartRule *prule = NULL;
PartitionNode *pNode = NULL;
DropStmt *ds = (DropStmt *)pc->arg1;
bool bCheckMaybe = !(ds->missing_ok);
AlterPartitionId *locPid = pid; /* local pid if IDRule */
PgPartRule* par_prule = NULL; /* prule for parent if IDRule */
char lRelNameBuf[(NAMEDATALEN*2)];
char *lrelname = NULL;
bool bForceDrop = false;
if (Gp_role != GP_ROLE_DISPATCH)
return;
if (pc->arg2 &&
IsA(pc->arg2, AlterPartitionCmd))
{
/* NOTE: Ugh, I hate this hack. Normally, PartDrop has a null
* pc->arg2 (the DropStmt is on arg1). However, for SPLIT, we
* have the case where we may need to DROP that last partition
* of a table, which in only ok because we will re-ADD two
* partitions to replace it. So allow bForceDrop only for
* this case. We need a better way to decorate the ALTER cmd
* structs to annotate these special cases.
*/
bForceDrop = true;
}
/* missing partition id only ok for range partitions -- just get
* first one */
locPid =
wack_pid_relname(pid,
&pNode,
rel,
&par_prule,
&lrelname,
lRelNameBuf);
if (AT_AP_IDNone == locPid->idtype)
{
if (pNode && pNode->part && (pNode->part->parkind != 'r'))
{
if (strlen(lrelname))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("missing name or value for DROP for %s",
lrelname),
errOmitLocation(true)));
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("missing name or value for DROP"),
errOmitLocation(true)));
}
/* if a range partition, and not specified, just get the first one */
locPid->idtype = AT_AP_IDRank;
locPid->partiddef = (Node *)makeInteger(1);
}
prule = get_part_rule(rel, pid, bCheckMaybe, true,
CurrentMemoryContext, NULL, false);
/* MPP-3722: complain if for(value) matches the default partition */
if ((locPid->idtype == AT_AP_IDValue)
&& prule &&
(prule->topRule == prule->pNode->default_part))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("FOR expression matches "
"DEFAULT partition%s of %s",
prule->partIdStr,
prule->relname),
errhint("FOR expression may only specify "
"a non-default partition in this context."),
errOmitLocation(true)));
if (!prule)
{
Assert(ds->missing_ok);
switch (locPid->idtype)
{
case AT_AP_IDNone: /* no ID */
/* should never happen */
Assert(false);
break;
case AT_AP_IDName: /* IDentify by Name */
ereport(NOTICE,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("partition \"%s\" of %s does not "
"exist, skipping",
strVal(locPid->partiddef),
lrelname
),
errOmitLocation(true)));
break;
case AT_AP_IDValue: /* IDentifier FOR Value */
ereport(NOTICE,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("partition for specified value of "
"%s does not exist, skipping",
lrelname
),
errOmitLocation(true)));
break;
case AT_AP_IDRank: /* IDentifier FOR Rank */
ereport(NOTICE,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("partition for specified rank of "
"%s does not exist, skipping",
lrelname
),
errOmitLocation(true)));
break;
case AT_AP_ID_oid: /* IDentifier by oid */
ereport(NOTICE,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("partition for specified oid of "
"%s does not exist, skipping",
lrelname
),
errOmitLocation(true)));
break;
case AT_AP_IDDefault: /* IDentify DEFAULT partition */
ereport(NOTICE,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("DEFAULT partition for "
"%s does not exist, skipping",
lrelname
),
errOmitLocation(true)));
break;
default: /* XXX XXX */
Assert(false);
}
return;
}
else
{
char* prelname;
int numParts = list_length(prule->pNode->rules);
DestReceiver *dest = None_Receiver;
Relation rel2;
RangeVar *relation;
char *namespace_name;
/* add the default partition to the count of partitions */
if (prule->pNode->default_part)
numParts++;
/* maybe ERRCODE_INVALID_TABLE_DEFINITION ? */
/* cannot drop a hash partition */
if ('h' == prule->pNode->part->parkind)
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot drop HASH partition%s of %s",
prule->partIdStr,
prule->relname),
errhint("recreate the table to reduce the "
"number of partitions"),
errOmitLocation(true)));
/* cannot drop last partition of table */
if (!bForceDrop && (numParts <= 1))
{
if (AT_AP_IDRule != pid->idtype)
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot drop partition%s of "
"%s -- only one remains",
prule->partIdStr,
prule->relname),
errhint("Use DROP TABLE \"%s\" to remove the "
"table and the final partition ",
RelationGetRelationName(rel)),
errOmitLocation(true)));
else
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot drop partition%s of "
"%s -- only one remains",
prule->partIdStr,
prule->relname),
errhint("DROP the parent partition to remove the "
"final partition "),
errOmitLocation(true)));
}
rel2 = heap_open(prule->topRule->parchildrelid, NoLock);
elog(DEBUG5, "dropping partition oid %u", prule->topRule->parchildrelid);
prelname = pstrdup(RelationGetRelationName(rel2));
namespace_name = get_namespace_name(rel2->rd_rel->relnamespace);
/* XXX XXX : don't need "relation" unless fix to use removerelation */
relation = makeRangeVar(NULL /*catalogname*/, namespace_name, prelname, -1);
relation->location = pc->location;
heap_close(rel2, NoLock);
ds->removeType = OBJECT_TABLE;
ds->bAllowPartn = true; /* allow drop of partitions */
if (prule->topRule->children)
{
List *l1 = atpxDropList(rel2, prule->topRule->children);
ds->objects = lappend(l1,
list_make2(makeString(namespace_name),
makeString(prelname)));
}
else
ds->objects = list_make1(list_make2(makeString(namespace_name),
makeString(prelname)));
ProcessUtility((Node *) ds,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
/* Notify of name if did not use name for partition id spec */
if (prule && prule->topRule && prule->topRule->children
&& (ds->behavior != DROP_CASCADE ))
{
ereport(NOTICE,
(errmsg("dropped partition%s for %s and its children",
prule->partIdStr,
prule->relname)));
}
else if ((pid->idtype != AT_AP_IDName)
&& prule->isName)
ereport(NOTICE,
(errmsg("dropped partition%s for %s",
prule->partIdStr,
prule->relname)));
/* MPP-6929: metadata tracking */
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"PARTITION", "DROP"
);
}
} /* end ATPExecPartDrop */
static void
exchange_part_inheritance(Oid oldrelid, Oid newrelid)
{
int fetchCount;
Oid parentid;
Relation oldrel;
Relation newrel;
Relation parent;
oldrel = heap_open(oldrelid, AccessExclusiveLock);
newrel = heap_open(newrelid, AccessExclusiveLock);
parentid = caql_getoid_plus(
NULL,
&fetchCount,
NULL,
cql("SELECT inhparent FROM pg_inherits "
" WHERE inhrelid = :1 ",
ObjectIdGetDatum(oldrelid)));
/* should be one and only one parent when it comes to inheritance */
Assert(1 == fetchCount);
parent = heap_open(parentid, AccessShareLock); /* should be enough */
ATExecDropInherit(oldrel,
makeRangeVar(NULL /*catalogname*/, get_namespace_name(parent->rd_rel->relnamespace),
RelationGetRelationName(parent), -1),
true);
inherit_parent(parent, newrel, true /* it's a partition */, NIL);
heap_close(parent, NoLock);
heap_close(oldrel, NoLock);
heap_close(newrel, NoLock);
}
/* ALTER TABLE ... EXCHANGE PARTITION
*
* Do the exchange that was validated earlier (in ATPrepExchange).
*/
static void
ATPExecPartExchange(AlteredTableInfo *tab, Relation rel, AlterPartitionCmd *pc)
{
Oid oldrelid = InvalidOid;
Oid newrelid = InvalidOid;
PgPartRule *orig_prule = NULL;
AlterPartitionCmd *pc2 = NULL;
bool is_split = false;
List *pcols = NIL; /* partitioned attributes of rel */
AlterPartitionIdType orig_pid_type = AT_AP_IDNone; /* save for NOTICE msg at end... */
if (Gp_role == GP_ROLE_UTILITY)
return;
/* Exchange for SPLIT is different from user-requested EXCHANGE. The special
* coding to indicate SPLIT is obscure. */
is_split = ((AlterPartitionCmd *)pc->arg2)->arg2 != NULL;
if (Gp_role == GP_ROLE_DISPATCH)
{
AlterPartitionId *pid = (AlterPartitionId *)pc->partid;
PgPartRule *prule = NULL;
RangeVar *newrelrv = (RangeVar *)pc->arg1;
RangeVar *oldrelrv;
PartitionNode *pn;
Relation oldrel;
pn = RelationBuildPartitionDesc(rel, false);
pcols = get_partition_attrs(pn);
prule = get_part_rule(rel, pid, true, true,
CurrentMemoryContext, NULL, false);
if (!prule)
return;
if (prule && prule->topRule && prule->topRule->children)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot EXCHANGE PARTITION for "
"%s -- partition has children",
prule->relname
),
errOmitLocation(true)));
orig_pid_type = pid->idtype;
orig_prule = prule;
oldrelid = prule->topRule->parchildrelid;
newrelid = RangeVarGetRelid(newrelrv, false, false /*allowHcatalog*/);
pfree(pc->partid);
oldrel = heap_open(oldrelid, NoLock);
oldrelrv =
makeRangeVar(NULL /*catalogname*/, get_namespace_name(RelationGetNamespace(oldrel)),
get_rel_name(oldrelid), -1);
heap_close(oldrel, NoLock);
pc->partid = (Node *)oldrelrv;
pc2 = (AlterPartitionCmd *)pc->arg2;
pc2->arg2 = (Node *)pcols; /* for execute nodes */
/* MPP-6929: metadata tracking */
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"PARTITION", "EXCHANGE"
);
}
else if (Gp_role == GP_ROLE_EXECUTE)
{
RangeVar *oldrelrv;
RangeVar *newrelrv;
Assert(IsA(pc->partid, RangeVar));
Assert(IsA(pc->arg1, RangeVar));
oldrelrv = (RangeVar *)pc->partid;
newrelrv = (RangeVar *)pc->arg1;
oldrelid = RangeVarGetRelid(oldrelrv, false, false /*allowHcatalog*/);
newrelid = RangeVarGetRelid(newrelrv, false, false /*allowHcatalog*/);
pc2 = (AlterPartitionCmd *)pc->arg2;
pcols = (List *)pc2->arg2;
}
Assert(OidIsValid(oldrelid));
Assert(OidIsValid(newrelid));
#if IF_ONLY_IT_WAS_THAT_SIMPLE
swap_relation_files(oldrelid, newrelid, false);
CommandCounterIncrement();
#else
/*
* It would be nice to just swap the relfilenodes. In fact, we could
* do that in most cases, the exceptions being tables with dropped
* columns and append only tables.
*
* So instead, we swap the names of the tables, the type names, the
* constraints, inheritance. We do not swap indexes, ao information
* or statistics.
*
* Note that the state, whether QD or QE, at this point is
* - rel -- relid of partitioned table
* - oldrelid -- relid of part currently in place
* - newrelid -- relid of candidate part to exchange in
* - orig_pid_type -- what kind of AlterTablePartitionId id'd the part
* - orig_prule -- Used in issuing notice in occasional case.
* - pc2->arg1 -- integer Value node: 1 validate, else 0.
* - pcols -- integer list of master partitioning columns
*/
{
char tmpname1[NAMEDATALEN];
char tmpname2[NAMEDATALEN];
char *newname;
char *oldname;
Relation newrel;
Relation oldrel;
AttrMap *newmap;
AttrMap *oldmap;
List *newcons;
bool __MAYBE_UNUSED ok;
bool validate = intVal(pc2->arg1) ? true : false;
Oid oldnspid = InvalidOid;
Oid newnspid = InvalidOid;
char *newNspName = NULL;
char *oldNspName = NULL;
newrel = heap_open(newrelid, AccessExclusiveLock);
oldrel = heap_open(oldrelid, AccessExclusiveLock);
oldnspid = RelationGetNamespace(oldrel);
newnspid = RelationGetNamespace(newrel);
if (oldnspid != newnspid)
{
newNspName = pstrdup(get_namespace_name(newnspid));
oldNspName = pstrdup(get_namespace_name(oldnspid));
}
newname = pstrdup(RelationGetRelationName(newrel));
oldname = pstrdup(RelationGetRelationName(oldrel));
ok = map_part_attrs(rel, newrel, &newmap, FALSE);
Assert(ok);
ok = map_part_attrs(rel, oldrel, &oldmap, FALSE);
Assert(ok);
newcons = cdb_exchange_part_constraints(rel, oldrel, newrel, validate, is_split);
tab->constraints = list_concat(tab->constraints, newcons);
CommandCounterIncrement();
exchange_part_inheritance(RelationGetRelid(oldrel), RelationGetRelid(newrel));
CommandCounterIncrement();
snprintf(tmpname1, sizeof(tmpname1), "pg_temp_%u", oldrelid);
snprintf(tmpname2, sizeof(tmpname2), "pg_temp_%u", newrelid);
exchange_permissions(RelationGetRelid(oldrel),
RelationGetRelid(newrel));
CommandCounterIncrement();
heap_close(newrel, NoLock);
heap_close(oldrel, NoLock);
/* rename rel renames the type too */
renamerel(oldrelid, tmpname1, NULL);
CommandCounterIncrement();
RelationForgetRelation(oldrelid);
/* MPP-6979: if the namespaces are different, switch them */
if (newNspName)
{
/* move the old partition (which has a temporary name) to
* the new namespace
*/
oldrel = heap_open(oldrelid, AccessExclusiveLock);
AlterRelationNamespaceInternalTwo(oldrel,
oldrelid,
oldnspid, newnspid,
true,
newNspName);
heap_close(oldrel, NoLock);
CommandCounterIncrement();
RelationForgetRelation(oldrelid);
/* before we move the new table to the old namespace,
* rename it to a temporary name to avoid a name
* collision. It would be nice to have an atomic
* operation to rename and renamespace a relation...
*/
renamerel(newrelid, tmpname2, NULL);
CommandCounterIncrement();
RelationForgetRelation(newrelid);
newrel = heap_open(newrelid, AccessExclusiveLock);
AlterRelationNamespaceInternalTwo(newrel,
newrelid,
newnspid, oldnspid,
true,
oldNspName);
heap_close(newrel, NoLock);
CommandCounterIncrement();
RelationForgetRelation(newrelid);
}
renamerel(newrelid, oldname, NULL);
CommandCounterIncrement();
RelationForgetRelation(newrelid);
renamerel(oldrelid, newname, NULL);
CommandCounterIncrement();
RelationForgetRelation(oldrelid);
CommandCounterIncrement();
/* fix up partitioning rule if we're on the QD*/
if (Gp_role == GP_ROLE_DISPATCH)
{
exchange_part_rule(oldrelid, newrelid);
CommandCounterIncrement();
/* Notify of name if did not use name for partition id spec */
if ( orig_pid_type != AT_AP_IDName && orig_prule->isName )
ereport(NOTICE,
(errmsg("exchanged partition%s of "
"%s with relation \"%s\"",
orig_prule->partIdStr,
orig_prule->relname,
newname),
errOmitLocation(true)));
}
}
tab->exchange_relid = newrelid;
#endif
}
/* ALTER TABLE ... MERGE PARTITION */
static void
ATPExecPartMerge(Relation rel,
AlterPartitionCmd *pc)
{
Relation source = NULL;
Relation target = NULL;
if (Gp_role == GP_ROLE_UTILITY)
return;
else if (Gp_role == GP_ROLE_DISPATCH)
{
AlterPartitionId *pid = (AlterPartitionId *)pc->partid;
PgPartRule *prule1 = NULL;
PgPartRule *prule2 = NULL;
PgPartRule *prule3 = NULL;
RangeVar *relrv1, *relrv2;
Relation r1, r2;
prule1 = get_part_rule(rel, pid, true, true,
CurrentMemoryContext, NULL, false);
/*
* XXX: get_namespace_name(prule1->topRule->parchildrelid) is wrong
* need the relnamespace not relid
*/
relrv1 = makeRangeVar(
NULL /*catalogname*/,
get_namespace_name(prule1->topRule->parchildrelid),
get_rel_name(prule1->topRule->parchildrelid), -1);
r1 = heap_openrv(relrv1, AccessExclusiveLock);
prule2 = get_part_rule(rel, (AlterPartitionId *)pc->arg1,
true, true,
CurrentMemoryContext, NULL, false);
relrv2 = makeRangeVar(
NULL /*catalogname*/,
get_namespace_name(prule2->topRule->parchildrelid),
get_rel_name(prule2->topRule->parchildrelid), -1);
r2 = heap_openrv(relrv2, AccessExclusiveLock);
if (pc->arg2)
{
AlterPartitionCmd *pc2 = (AlterPartitionCmd *)pc->arg2;
prule3 = get_part_rule(rel, (AlterPartitionId *)pc2->partid,
true, true, CurrentMemoryContext,
NULL, false);
Assert(PointerIsValid(prule3));
if (prule3->topRule->parchildrelid == prule1->topRule->parchildrelid)
{
source = r1;
pc->partid = (Node *)relrv1;
target = r2;
pc->arg1 = (Node *)relrv2;
}
else if (prule3->topRule->parchildrelid == prule2->topRule->parchildrelid)
{
source = r2;
pc->partid = (Node *)relrv2;
target = r1;
pc->arg1 = (Node *)relrv1;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("target of MERGE must be either \"%s\" or "
"\"%s\"",
get_rel_name(prule1->topRule->parchildrelid),
get_rel_name(prule2->topRule->parchildrelid)),
errOmitLocation(true)));
}
}
else
{
source = r1;
target = r2;
pc->partid = (Node *)relrv1;
pc->arg1 = (Node *)relrv2;
}
/* MPP-6929: metadata tracking */
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"PARTITION", "MERGE"
);
}
else if (Gp_role == GP_ROLE_EXECUTE)
{
RangeVar *relrv;
Assert(IsA(pc->partid, RangeVar));
Assert(IsA(pc->arg1, RangeVar));
relrv = (RangeVar *)pc->partid;
source = heap_openrv(relrv, AccessExclusiveLock);
relrv = (RangeVar *)pc->arg1;
target = heap_openrv(relrv, AccessExclusiveLock);
}
elog(NOTICE, "merging");
elog(NOTICE, "%s", RelationGetRelationName(source));
elog(NOTICE, "with %s", RelationGetRelationName(target));
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot MERGE PARTITION for relation \"%s\"",
RelationGetRelationName(rel)),
errOmitLocation(true)));
}
/* ALTER TABLE ... MODIFY PARTITION */
static void
ATPExecPartModify(Relation rel,
AlterPartitionCmd *pc)
{
AlterPartitionId *pid = (AlterPartitionId *)pc->partid;
PgPartRule *prule = NULL;
if (Gp_role != GP_ROLE_DISPATCH)
return;
prule = get_part_rule(rel, pid, true, true, CurrentMemoryContext, NULL,
false);
if (prule)
{
AlterPartitionCmd *pc2 = (AlterPartitionCmd *)pc->arg1;
AlterPartitionCmd *pc3 = (AlterPartitionCmd *)pc2;
DefElem *def = (DefElem *)pc3->arg1;
bool bStart = false;
bool bEnd = false;
bool bAdd = false;
bool bDrop = false;
char *parTypName = NULL;
if (0 == strcmp(def->defname, "START"))
bStart = true;
else if (0 == strcmp(def->defname, "END"))
bEnd = true;
else if (0 == strcmp(def->defname, "ADD"))
bAdd = true;
else if (0 == strcmp(def->defname, "DROP"))
bDrop = true;
else Assert(false);
switch (prule->pNode->part->parkind)
{
case 'h': /* hash */
parTypName = "HASH";
break;
case 'r': /* range */
parTypName = "RANGE";
break;
case 'l': /* list */
parTypName = "LIST";
break;
default:
elog(ERROR, "unrecognized partitioning kind '%c'",
prule->pNode->part->parkind);
Assert(false);
break;
} /* end switch */
/* cannot modify default partitions */
if (prule->pNode->default_part == prule->topRule)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot MODIFY DEFAULT %s partition%s "
"for relation \"%s\"",
parTypName,
((prule && prule->isName) ? prule->partIdStr : ""),
RelationGetRelationName(rel)),
errOmitLocation(true)));
if ((bStart || bEnd) && ('r' != prule->pNode->part->parkind))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid use of %s boundary specification "
"in partition%s of type %s",
"RANGE",
((prule && prule->isName) ? prule->partIdStr : ""),
parTypName
),
errOmitLocation(true)));
if ((bAdd || bDrop) && ('l' != prule->pNode->part->parkind))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid use of %s boundary specification "
"in partition%s of type %s",
"LIST",
((prule && prule->isName) ? prule->partIdStr : ""),
parTypName
),
errOmitLocation(true)));
if (bAdd || bDrop)
{
atpxModifyListOverlap(rel, pid, prule,
(PartitionElem *)pc3->arg2,
bAdd);
}
if (bStart || bEnd)
{
atpxModifyRangeOverlap(rel, pid, prule,
(PartitionElem *)pc3->arg2);
}
if (0)
parruleord_reset_rank(
prule->pNode->part->partid,
prule->pNode->part->parlevel,
prule->topRule->parparentoid,
prule->topRule->parruleord,
CurrentMemoryContext);
/* MPP-6929: metadata tracking */
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"PARTITION", "MODIFY"
);
}
if (0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot MODIFY PARTITION for relation \"%s\"",
RelationGetRelationName(rel)),
errOmitLocation(true)));
}
/* ALTER TABLE ... RENAME PARTITION */
static void
ATPExecPartRename(Relation rel,
AlterPartitionCmd *pc)
{
AlterPartitionId *pid = (AlterPartitionId *)pc->partid;
PgPartRule *prule = NULL;
PartitionNode *pNode = NULL;
PgPartRule* par_prule = NULL; /* prule for parent if IDRule */
char lRelNameBuf[(NAMEDATALEN*2)];
char *lrelname=NULL;
if (Gp_role != GP_ROLE_DISPATCH)
return;
wack_pid_relname(pid, &pNode, rel, &par_prule,
&lrelname, lRelNameBuf);
prule = get_part_rule(rel, pid, true, true, CurrentMemoryContext, NULL,
false);
if (prule)
{
AlterPartitionId newpid;
Relation targetrelation;
char targetrelname[NAMEDATALEN];
Relation parentrelation;
Oid namespaceId;
char *newpartname = strVal(pc->arg1);
char *relname;
char parentname[NAMEDATALEN];
int partDepth = prule->pNode->part->parlevel;
RenameStmt *renStmt = makeNode(RenameStmt);
DestReceiver *dest = None_Receiver;
Relation part_rel;
HeapTuple tuple;
Form_pg_partition_rule pgrule;
List *renList = NIL;
int skipped = 0;
int renamed = 0;
cqContext cqc;
cqContext *pcqCtx;
newpid.idtype = AT_AP_IDName;
newpid.partiddef = pc->arg1;
newpid.location = -1;
/* ERROR if exists */
get_part_rule1(rel, &newpid, true, false,
CurrentMemoryContext, NULL,
pNode,
lrelname,
NULL);
targetrelation = relation_open(prule->topRule->parchildrelid,
AccessExclusiveLock);
StrNCpy(targetrelname, RelationGetRelationName(targetrelation),
NAMEDATALEN);
namespaceId = RelationGetNamespace(targetrelation);
relation_close(targetrelation, AccessExclusiveLock);
if (0 == prule->topRule->parparentoid)
{
StrNCpy(parentname,
RelationGetRelationName(rel), NAMEDATALEN);
}
else
{
Assert(par_prule);
if (par_prule)
{
/* look in the parent prule */
parentrelation =
RelationIdGetRelation(par_prule->topRule->parchildrelid);
StrNCpy(parentname,
RelationGetRelationName(parentrelation), NAMEDATALEN);
RelationClose(parentrelation);
}
}
/* MPP-3523: the "label" portion of the new relation is
* prt_`newpartname', and makeObjectName won't truncate this
* portion of the partition name -- it will assert instead.
*/
if (strlen(newpartname) > (NAMEDATALEN - 8))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("name \"%s\" for child partition "
"is too long",
newpartname)));
relname = ChoosePartitionName(parentname,
partDepth,
newpartname,
namespaceId);
/* does CommandCounterIncrement */
renStmt->renameType = OBJECT_TABLE;
renStmt->relation = makeRangeVar(NULL /*catalogname*/, get_namespace_name(namespaceId),
pstrdup(targetrelname), -1);
renStmt->subname = NULL;
renStmt->newname = relname;
renStmt->bAllowPartn = true; /* allow rename of partitions */
if (prule && prule->topRule && prule->topRule->children)
pNode = prule->topRule->children;
else
pNode = NULL;
/* rename the children as well */
renList = atpxRenameList(pNode, targetrelname, relname, &skipped);
part_rel = heap_open(PartitionRuleRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), part_rel);
tuple = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_partition_rule "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(prule->topRule->parruleid)));
Insist(HeapTupleIsValid(tuple));
pgrule = (Form_pg_partition_rule)GETSTRUCT(tuple);
namestrcpy(&(pgrule->parname), newpartname);
caql_update_current(pcqCtx, tuple);
/* and Update indexes (implicit) */
heap_freetuple(tuple);
heap_close(part_rel, NoLock);
CommandCounterIncrement();
ProcessUtility((Node *) renStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
/* process children if there are any */
if (renList)
{
ListCell *lc;
foreach(lc, renList)
{
ListCell *lc2;
List *lpair = lfirst(lc);
lc2 = list_head(lpair);
renStmt->relation = (RangeVar *)lfirst(lc2);
lc2 = lnext(lc2);
renStmt->newname = (char *)lfirst(lc2);
ProcessUtility((Node *) renStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
renamed++;
}
}
/* MPP-6929: metadata tracking */
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"PARTITION", "RENAME"
);
/* Notify of name if did not use name for partition id spec */
if ((pid->idtype != AT_AP_IDName)
&& prule->isName)
ereport(NOTICE,
(errmsg("renamed partition%s to \"%s\" for %s",
prule->partIdStr,
newpartname,
prule->relname)));
/* MPP-3542: warn when skip child partitions */
if (skipped)
{
ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("renamed %d partitions, skipped %d child partitions due to name truncation",
renamed, skipped),
errOmitLocation(true)));
}
}
} /* end ATPExecPartRename */
/* ALTER TABLE ... SET SUBPARTITION TEMPLATE */
static void
ATPExecPartSetTemplate(AlteredTableInfo *tab,
Relation rel,
AlterPartitionCmd *pc)
{
AlterPartitionId *pid = (AlterPartitionId *)pc->partid;
PgPartRule *prule = NULL;
int lvl = 1;
if (Gp_role != GP_ROLE_DISPATCH)
return;
/* set template for top level table */
if (pid && (pid->idtype != AT_AP_IDName))
{
Assert((pid->idtype == AT_AP_IDRule) && IsA(pid->partiddef, List));
/* MPP-5941: work correctly with many levels of templates */
/* wah! the idrule is invalid, so can't use get_part_rule.
* So pull the pgpartrule directly from the idrule (yuck!)
*/
prule = (PgPartRule *)linitial((List*)pid->partiddef);
Assert(prule);
/* current pnode level is one below current (our parent), and
* we want the one above us (our subpartition), so add 2
*/
lvl = prule->pNode->part->parlevel + 2;
Assert (lvl > 1);
}
{
/* relid, level, no parent */
switch (del_part_template(RelationGetRelid(rel), lvl, 0))
{
case 0:
if (pc->arg1)
{
/* no prior template - just add new one */
ereport(NOTICE,
(errmsg("%s level %d "
"subpartition template specification "
"for relation \"%s\"",
"adding",
lvl,
RelationGetRelationName(rel))));
}
else
{
/* tried to drop non-existent template */
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("relation \"%s\" does not have a "
"level %d "
"subpartition template specification",
RelationGetRelationName(rel),
lvl),
errOmitLocation(true)));
}
break;
case 1:
/* if have new spec,
* note old spec is being replaced,
* else just note it is dropped
*/
ereport(NOTICE,
(errmsg("%s level %d "
"subpartition template specification "
"for relation \"%s\"",
(pc->arg1) ? "replacing" : "dropped",
lvl,
RelationGetRelationName(rel))));
break;
default:
elog(ERROR,
"could not drop "
"level %d "
"subpartition template specification "
"for relation \"%s\"",
lvl,
RelationGetRelationName(rel));
break;
}
}
if (pc->arg1)
ATPExecPartAdd(tab, rel, pc, AT_PartSetTemplate);
/* MPP-6929: metadata tracking */
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "SET SUBPARTITION TEMPLATE"
);
} /* end ATPExecPartSetTemplate */
static bool
partrule_walker(Node *node, void *context)
{
part_rule_cxt *p = (part_rule_cxt *)context;
if (node == NULL)
return false;
if (IsA(node, PgPartRule))
{
PgPartRule *pg = (PgPartRule *)node;
partrule_walker((Node *)pg->topRule, p);
partrule_walker((Node *)pg->pNode, p);
return false;
}
else if (IsA(node, Partition))
{
return false;
}
else if (IsA(node, PartitionRule))
{
PartitionRule *pr = (PartitionRule *)node;
if (pr->parchildrelid == p->old_oid)
pr->parchildrelid = p->new_oid;
return partrule_walker((Node *)pr->children, p);
}
else if (IsA(node, PartitionNode))
{
PartitionNode *pn = (PartitionNode *)node;
ListCell *lc;
partrule_walker((Node *)pn->default_part, p);
foreach(lc, pn->rules)
{
PartitionRule *r = lfirst(lc);
partrule_walker((Node *)r, p);
}
return false;
}
return expression_tree_walker(node, partrule_walker, p);
}
/*
* Build a basic ResultRelInfo for executing split. We only need
* the relation descriptor and index information.
*/
static ResultRelInfo *
make_split_resultrel(Relation rel)
{
ResultRelInfo *rri;
rri = palloc0(sizeof(ResultRelInfo));
rri->type = T_ResultRelInfo;
rri->ri_RelationDesc = rel;
rri->ri_NumIndices = 0;
ExecOpenIndices(rri);
return rri;
}
/*
* Close indexes and free memory
*/
static void
destroy_split_resultrel(ResultRelInfo *rri)
{
ExecCloseIndices(rri);
/*
* Don't do anything with the relation descriptor, that's our caller's job
*/
pfree(rri);
}
/*
* Scan tuples from the temprel (origin, T) and route them to split parts (A, B)
* based on the constraints. It is important that the origin may have dropped
* columns while the new relations will not.
*
* This also covers index tuple population. Note this doesn't handle row OID
* as it's not allowed in partition.
*/
static void
split_rows(Relation intoa, Relation intob, Relation temprel, List *splits, int segno)
{
ResultRelInfo *rria = make_split_resultrel(intoa);
ResultRelInfo *rrib = make_split_resultrel(intob);
EState *estate = CreateExecutorState();
TupleDesc tupdescT = temprel->rd_att;
TupleTableSlot *slotT = MakeSingleTupleTableSlot(tupdescT);
HeapScanDesc heapscan = NULL;
AppendOnlyScanDesc aoscan = NULL;
ParquetScanDesc parquetscan = NULL;
bool *aocsproj = NULL;
MemoryContext oldCxt;
AppendOnlyInsertDesc aoinsertdesc_a = NULL;
AppendOnlyInsertDesc aoinsertdesc_b = NULL;
ParquetInsertDesc parquetinsertdesc_a = NULL;
ParquetInsertDesc parquetinsertdesc_b = NULL;
ExprState *achk = NULL;
ExprState *bchk = NULL;
/*
* Set up for reconstructMatchingTupleSlot. In split operation,
* slot/tupdesc should look same between A and B, but here we don't
* assume so just in case, to be safe.
*/
rria->ri_partSlot = MakeSingleTupleTableSlot(RelationGetDescr(intoa));
rrib->ri_partSlot = MakeSingleTupleTableSlot(RelationGetDescr(intob));
map_part_attrs(temprel, intoa, &rria->ri_partInsertMap, true);
map_part_attrs(temprel, intob, &rrib->ri_partInsertMap, true);
Assert(NULL != rria->ri_RelationDesc);
rria->ri_resultSlot = MakeSingleTupleTableSlot(rria->ri_RelationDesc->rd_att);
Assert(NULL != rrib->ri_RelationDesc);
rrib->ri_resultSlot = MakeSingleTupleTableSlot(rrib->ri_RelationDesc->rd_att);
/* constr might not be defined if this is a default partition */
if (intoa->rd_att->constr && intoa->rd_att->constr->num_check)
{
List * all_part_constraints = NIL;
for (int i = 0; i < intoa->rd_att->constr->num_check; i++)
all_part_constraints = lappend(all_part_constraints,
stringToNode(intoa->rd_att->constr->check[i].ccbin));
achk = ExecPrepareExpr((Expr *)all_part_constraints, estate);
}
if (intob->rd_att->constr && intob->rd_att->constr->num_check)
{
List * all_part_constraints = NIL;
for (int i = 0; i < intob->rd_att->constr->num_check; i++)
all_part_constraints = lappend(all_part_constraints,
stringToNode(intob->rd_att->constr->check[i].ccbin));
bchk = ExecPrepareExpr((Expr *)all_part_constraints, estate);
}
/* be careful about AO vs. normal heap tables */
if (RelationIsHeap(temprel))
heapscan = heap_beginscan(temprel, SnapshotNow, 0, NULL);
else if (RelationIsAoRows(temprel))
{
aoscan = appendonly_beginscan(temprel, SnapshotNow, 0, NULL);
aoscan->splits = splits;
}
else if (RelationIsParquet(temprel))
{
int nvp = temprel->rd_att->natts;
int i;
aocsproj = (bool *) palloc(sizeof(bool) * nvp);
for(i=0; i<nvp; ++i)
aocsproj[i] = true;
parquetscan = parquet_beginscan(temprel, SnapshotNow, NULL /* relationTupleDesc */, aocsproj);
parquetscan->splits = splits;
}
else
{
Assert(false);
}
oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
while (true)
{
ExprContext *econtext = GetPerTupleExprContext(estate);
bool targetIsA;
Relation targetRelation;
AppendOnlyInsertDesc *targetAODescPtr;
ParquetInsertDesc *targetParquetDescPtr;
TupleTableSlot *targetSlot;
ItemPointer tid;
ResultRelInfo *targetRelInfo = NULL;
AOTupleId aoTupleId;
/* read next tuple from temprel */
if (RelationIsHeap(temprel))
{
HeapTuple tuple;
tuple = heap_getnext(heapscan, ForwardScanDirection);
if (!HeapTupleIsValid(tuple))
break;
tuple = heap_copytuple(tuple);
ExecStoreHeapTuple(tuple, slotT, InvalidBuffer, false);
}
else if (RelationIsAoRows(temprel))
{
MemTuple mtuple;
mtuple = appendonly_getnext(aoscan, ForwardScanDirection, slotT);
if (!PointerIsValid(mtuple))
break;
TupClearShouldFree(slotT);
}
else if (RelationIsParquet(temprel)){
parquet_getnext(parquetscan, ForwardScanDirection, slotT);
if (TupIsNull(slotT))
break;
}
/* prepare for ExecQual */
econtext->ecxt_scantuple = slotT;
/* determine if we are inserting into a or b */
if (achk)
{
targetIsA = ExecQual((List *)achk, econtext, false);
}
else
{
Assert(PointerIsValid(bchk));
targetIsA = !ExecQual((List *)bchk, econtext, false);
}
/* load variables for the specific target */
if (targetIsA)
{
targetRelation = intoa;
targetAODescPtr = &aoinsertdesc_a;
targetParquetDescPtr = &parquetinsertdesc_a;
targetRelInfo = rria;
}
else
{
targetRelation = intob;
targetAODescPtr = &aoinsertdesc_b;
targetParquetDescPtr = &parquetinsertdesc_b;
targetRelInfo = rrib;
}
/*
* Map attributes from origin to target. We should consider dropped
* columns in the origin.
*/
targetSlot = reconstructMatchingTupleSlot(slotT, targetRelInfo);
/* insert into the target table */
if (RelationIsHeap(targetRelation))
{
HeapTuple tuple;
tuple = ExecFetchSlotHeapTuple(targetSlot);
simple_heap_insert(targetRelation, tuple);
/* cache TID for later updating of indexes */
tid = &(((HeapTuple) tuple)->t_self);
}
else if (RelationIsAoRows(targetRelation))
{
MemTuple mtuple;
Oid tupleOid;
if (!(*targetAODescPtr))
{
ResultRelSegFileInfo *segfileinfo = NULL;
MemoryContextSwitchTo(oldCxt);
segfileinfo = InitResultRelSegFileInfo(segno, RELSTORAGE_AOROWS, 1);
*targetAODescPtr = appendonly_insert_init(targetRelation,
segfileinfo);
MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
}
mtuple = ExecFetchSlotMemTuple(targetSlot, false);
appendonly_insert(*targetAODescPtr, mtuple, &tupleOid, &aoTupleId);
/* cache TID for later updating of indexes */
tid = (ItemPointer) &aoTupleId;
}
else if (RelationIsParquet(targetRelation)){
if (!*targetParquetDescPtr)
{
ResultRelSegFileInfo *segfileinfo = NULL;
MemoryContextSwitchTo(oldCxt);
segfileinfo = InitResultRelSegFileInfo(segno, RELSTORAGE_PARQUET, 1);
*targetParquetDescPtr = parquet_insert_init(targetRelation,
segfileinfo);
MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
}
parquet_insert(*targetParquetDescPtr, targetSlot);
/* cache TID for later updating of indexes */
tid = slot_get_ctid(targetSlot);
}
else
{
Assert(false);
}
/*
* Insert index for this tuple.
* TODO: for performance reason, should we call index_build() instead?
*/
if (targetRelInfo->ri_NumIndices > 0)
{
estate->es_result_relation_info = targetRelInfo;
ExecInsertIndexTuples(targetSlot, tid, estate, false);
estate->es_result_relation_info = NULL;
}
/* done, clean up context for this pass */
ResetExprContext(econtext);
}
StringInfo buf = NULL;
QueryContextDispatchingSendBack sendback = NULL;
int aocount = 0;
if (aoinsertdesc_a)
++aocount;
if (aoinsertdesc_b)
++aocount;
if (parquetinsertdesc_a)
++aocount;
if (parquetinsertdesc_b)
++aocount;
if (Gp_role == GP_ROLE_EXECUTE && aocount > 0)
buf = PreSendbackChangedCatalog(aocount);
if (aoinsertdesc_a)
{
sendback = CreateQueryContextDispatchingSendBack(1);
aoinsertdesc_a->sendback = sendback;
sendback->relid = RelationGetRelid(aoinsertdesc_a->aoi_rel);
appendonly_insert_finish(aoinsertdesc_a);
if (Gp_role == GP_ROLE_EXECUTE)
AddSendbackChangedCatalogContent(buf, sendback);
DropQueryContextDispatchingSendBack(sendback);
}
if (aoinsertdesc_b)
{
sendback = CreateQueryContextDispatchingSendBack(1);
aoinsertdesc_b->sendback = sendback;
sendback->relid = RelationGetRelid(aoinsertdesc_b->aoi_rel);
appendonly_insert_finish(aoinsertdesc_b);
if (Gp_role == GP_ROLE_EXECUTE)
AddSendbackChangedCatalogContent(buf, sendback);
DropQueryContextDispatchingSendBack(sendback);
}
if (parquetinsertdesc_a)
{
sendback = CreateQueryContextDispatchingSendBack(1);
parquetinsertdesc_a->sendback = sendback;
sendback->relid = RelationGetRelid(parquetinsertdesc_a->parquet_rel);
parquet_insert_finish(parquetinsertdesc_a);
if (Gp_role == GP_ROLE_EXECUTE)
AddSendbackChangedCatalogContent(buf, sendback);
DropQueryContextDispatchingSendBack(sendback);
}
if (parquetinsertdesc_b)
{
sendback = CreateQueryContextDispatchingSendBack(1);
parquetinsertdesc_b->sendback = sendback;
sendback->relid = RelationGetRelid(parquetinsertdesc_b->parquet_rel);
parquet_insert_finish(parquetinsertdesc_b);
if (Gp_role == GP_ROLE_EXECUTE)
AddSendbackChangedCatalogContent(buf, sendback);
DropQueryContextDispatchingSendBack(sendback);
}
if (Gp_role == GP_ROLE_EXECUTE && aocount > 0)
FinishSendbackChangedCatalog(buf);
MemoryContextSwitchTo(oldCxt);
ExecDropSingleTupleTableSlot(slotT);
ExecDropSingleTupleTableSlot(rria->ri_partSlot);
ExecDropSingleTupleTableSlot(rrib->ri_partSlot);
/*
* We created our target result tuple table slots upfront.
* We can drop them now.
*/
Assert(NULL != rria->ri_resultSlot);
Assert(NULL != rria->ri_resultSlot->tts_tupleDescriptor);
ExecDropSingleTupleTableSlot(rria->ri_resultSlot);
rria->ri_resultSlot = NULL;
Assert(NULL != rrib->ri_resultSlot);
Assert(NULL != rrib->ri_resultSlot->tts_tupleDescriptor);
ExecDropSingleTupleTableSlot(rrib->ri_resultSlot);
rrib->ri_resultSlot = NULL;
if (rria->ri_partInsertMap)
pfree(rria->ri_partInsertMap);
if (rrib->ri_partInsertMap)
pfree(rrib->ri_partInsertMap);
if (RelationIsHeap(temprel))
heap_endscan(heapscan);
else if (RelationIsAoRows(temprel))
appendonly_endscan(aoscan);
else if(RelationIsParquet(temprel)){
pfree(aocsproj);
parquet_endscan(parquetscan);
}
destroy_split_resultrel(rria);
destroy_split_resultrel(rrib);
}
/* ALTER TABLE ... SPLIT PARTITION */
/*
atpxSplitDropRule: walk the partition rules, eliminating rules that
match parruleid and paroid. If topRule is supplied, walk his
subtree as well (it may differ from pnode if the PgPartRule was
copied).
*/
static void
atpxSplitDropRule(PartitionNode *pNode, PartitionRule *topRule,
Oid parruleid, Oid paroid)
{
ListCell *lc;
ListCell *lcprev = NULL, *lcdel = NULL;
if (!pNode)
return;
foreach(lc, pNode->rules)
{
PartitionRule *rule = lfirst(lc);
if (rule->children)
atpxSplitDropRule(rule->children, NULL,
parruleid, paroid);
if ((parruleid == rule->parruleid) &&
(paroid == rule->paroid))
{
lcdel = lc; /* should only be one match */
break;
}
lcprev = lc;
}
if (lcdel)
pNode->rules = list_delete_cell(pNode->rules, lcdel, lcprev);
/* and the default partition */
if (pNode->default_part)
{
PartitionRule *rule = pNode->default_part;
if (rule->children)
atpxSplitDropRule(rule->children, NULL,
parruleid, paroid);
if ((parruleid == rule->parruleid) &&
(paroid == rule->paroid))
pNode->default_part = NULL;
}
/* check optional topRule */
if (topRule && topRule->children)
atpxSplitDropRule(topRule->children, NULL,
parruleid, paroid);
} /* end atpxSplitDropRule */
/* Given a Relation, make a distributed by () clause for parser consumption. */
List *
make_dist_clause(Relation rel)
{
int i;
List *distro = NIL;
for (i = 0; i < rel->rd_cdbpolicy->nattrs; i++)
{
AttrNumber attno = rel->rd_cdbpolicy->attrs[i];
TupleDesc tupdesc = RelationGetDescr(rel);
Value *attstr;
NameData attname;
attname = tupdesc->attrs[attno - 1]->attname;
attstr = makeString(pstrdup(NameStr(attname)));
distro = lappend(distro, attstr);
}
if (!distro)
{
/* must be random distribution */
distro = list_make1(NULL);
}
return distro;
}
/*
* Given a relation, get all column encodings for that relation as a list of
* ColumnReferenceStorageDirective structures.
*/
static List *
rel_get_column_encodings(Relation rel)
{
List **colencs = RelationGetUntransformedAttributeOptions(rel);
List *out = NIL;
if (colencs)
{
AttrNumber attno;
for (attno = 0; attno < RelationGetNumberOfAttributes(rel); attno++)
{
if (colencs[attno] && !rel->rd_att->attrs[attno]->attisdropped)
{
ColumnReferenceStorageDirective *d =
makeNode(ColumnReferenceStorageDirective);
char *colname = pstrdup(NameStr(rel->rd_att->attrs[attno]->attname));
d->column = makeString(colname);
d->encoding = colencs[attno];
out = lappend(out, d);
}
}
}
return out;
}
/*
* Depending on whether a table is heap, append only or append only column
* oriented, return NIL, (appendonly=true) or (appendonly=true,
* orientation=parquet) respectively.
*/
static List *
make_orientation_options(Relation rel)
{
List *l = NIL;
if (RelationIsAoRows(rel) ||
RelationIsParquet(rel) )
{
l = lappend(l, makeDefElem("appendonly", (Node *)makeString("true")));
if (RelationIsParquet(rel))
{
l = lappend(l, makeDefElem("orientation",
(Node *)makeString("parquet")));
}
}
return l;
}
void
ATPExecPartSplit(Relation rel,
AlterPartitionCmd *pc)
{
Relation temprel = NULL;
Relation intoa = NULL;
Relation intob = NULL;
Oid temprelid;
if (Gp_role == GP_ROLE_DISPATCH)
{
CreateStmt *ct = makeNode(CreateStmt);
char tmpname[NAMEDATALEN];
InhRelation *inh = makeNode(InhRelation);
DestReceiver *dest = None_Receiver;
Node *at = lsecond((List *)pc->arg1);
AlterPartitionCmd *pc2 = (AlterPartitionCmd *)pc->arg2;
AlterPartitionId *pid = (AlterPartitionId *)pc->partid;
AlterTableStmt *ats = makeNode(AlterTableStmt);
RangeVar *rv;
AlterTableCmd *cmd = makeNode(AlterTableCmd);
AlterPartitionCmd *mypc = makeNode(AlterPartitionCmd);
AlterPartitionCmd *mypc2 = makeNode(AlterPartitionCmd);
AlterPartitionId *idpid;
RangeVar *tmprv;
PgPartRule *prule;
DropStmt *ds = makeNode(DropStmt);
List *parsetrees;
Query *q;
char *nspname = get_namespace_name(RelationGetNamespace(rel));
char *relname = get_rel_name(RelationGetRelid(rel));
Oid relid = RelationGetRelid(rel);
RangeVar *rva = NULL;
RangeVar *rvb = NULL;
int into_exists = 0; /* which into partition exists? */
int i;
AlterPartitionId *intopid1 = NULL;
AlterPartitionId *intopid2 = NULL;
Oid rel_to_drop = InvalidOid;
AlterPartitionId *aapid = NULL; /* just for alter partition pids */
Relation existrel;
List *existstorage_opts;
ListCell *lc;
char *defparname = NULL; /* name of default partition (if specified) */
List *distro = NIL;
List *colencs = NIL;
List *orient = NIL;
/* Get target meta data */
prule = get_part_rule(rel, pid, true, true,
CurrentMemoryContext, NULL, false);
/*
* In order to implement SPLIT, we do the following:
*
* 1) Build a temporary table T on all nodes.
* 2) Exchange that table with the target partition P
* Now, T has all the data or P
* 3) Drop partition P
* 4) Create two new partitions in the place of the old one
*/
/* look up INTO clause info, if the user supplied it */
if (!pc2) /* no INTO */
{
if (prule->topRule->parisdefault)
{
defparname = pstrdup(prule->topRule->parname);
into_exists = 2;
}
}
else /* has INTO clause */
{
bool isdef = false;
bool exists = false;
char *parname = NULL;
/*
* If we're working on a subpartition, the INTO partition is
* actually a child partition of the parent identified by
* prule. So, we cannot just use get_part_rule() to determine
* if one of them is a default.
*/
/* first item */
if (pid->idtype == AT_AP_IDRule)
{
if (prule->pNode->default_part &&
((AlterPartitionId *)pc2->partid)->idtype == AT_AP_IDDefault)
{
isdef = true;
exists = true;
parname = prule->pNode->default_part->parname;
if (!defparname && isdef)
defparname = pstrdup(parname);
}
else
{
ListCell *rc;
AlterPartitionId *id = (AlterPartitionId *)pc2->partid;
if (id->idtype == AT_AP_IDDefault)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("relation \"%s\" does not have a "
"default partition",
RelationGetRelationName(rel)),
errOmitLocation(true)));
foreach(rc, prule->pNode->rules)
{
PartitionRule *r = lfirst(rc);
if (strcmp(r->parname, strVal((Value *)id->partiddef)) == 0)
{
isdef = false;
exists = true;
parname = r->parname;
}
}
if (prule->pNode->default_part &&
strcmp(prule->pNode->default_part->parname,
strVal((Value *)id->partiddef)) == 0)
{
isdef = true;
exists = true;
parname = prule->pNode->default_part->parname;
if (!defparname && isdef)
defparname = pstrdup(parname);
}
}
}
else /* not a AT_AP_IDRule */
{
PgPartRule *tmprule;
tmprule = get_part_rule(rel, (AlterPartitionId *)pc2->partid,
false, false, CurrentMemoryContext,
NULL, false);
if (tmprule)
{
isdef = tmprule->topRule->parisdefault;
exists = true;
parname = tmprule->topRule->parname;
if (!defparname && isdef)
defparname = pstrdup(parname);
}
}
if (exists && isdef)
{
intopid2 = (AlterPartitionId *)pc2->partid;
intopid1 = (AlterPartitionId *)pc2->arg1;
into_exists = 2;
if (intopid2->idtype == AT_AP_IDDefault)
intopid2->partiddef = (Node *)makeString(pstrdup(parname));
}
else
{
if (exists)
into_exists = 1;
intopid1 = (AlterPartitionId *)pc2->partid;
intopid2 = (AlterPartitionId *)pc2->arg1;
}
/* second item */
exists = false;
isdef = false;
parname = NULL;
if (pid->idtype == AT_AP_IDRule)
{
if (prule->pNode->default_part &&
((AlterPartitionId *)pc2->arg1)->idtype == AT_AP_IDDefault)
{
isdef = true;
exists = true;
parname = prule->pNode->default_part->parname;
if (!defparname && isdef)
defparname = pstrdup(parname);
}
else
{
ListCell *rc;
AlterPartitionId *id = (AlterPartitionId *)pc2->arg1;
if (id->idtype == AT_AP_IDDefault)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("relation \"%s\" does not have a "
"default partition",
RelationGetRelationName(rel)),
errOmitLocation(true)));
foreach(rc, prule->pNode->rules)
{
PartitionRule *r = lfirst(rc);
if (strcmp(r->parname, strVal((Value *)id->partiddef)) == 0)
{
isdef = false;
exists = true;
parname = r->parname;
}
}
if (prule->pNode->default_part &&
strcmp(prule->pNode->default_part->parname,
strVal((Value *)id->partiddef)) == 0)
{
isdef = true;
exists = true;
parname = prule->pNode->default_part->parname;
if (!defparname && isdef)
defparname = pstrdup(parname);
}
}
}
else
{
PgPartRule *tmprule;
tmprule = get_part_rule(rel, (AlterPartitionId *)pc2->arg1,
false, false, CurrentMemoryContext,
NULL, false);
if (tmprule)
{
isdef = tmprule->topRule->parisdefault;
exists = true;
parname = tmprule->topRule->parname;
if (!defparname && isdef)
defparname = pstrdup(parname);
}
}
if (exists)
{
if (into_exists != 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("both INTO partitions "
"already exist"),
errOmitLocation(true)));
into_exists = 2;
intopid1 = (AlterPartitionId *)pc2->partid;
intopid2 = (AlterPartitionId *)pc2->arg1;
if (isdef)
{
if (intopid2->idtype == AT_AP_IDDefault)
intopid2->partiddef = (Node *)makeString(parname);
}
}
}
existrel = heap_open(prule->topRule->parchildrelid, NoLock);
existstorage_opts = reloptions_list(RelationGetRelid(existrel));
distro = make_dist_clause(existrel);
colencs = rel_get_column_encodings(existrel);
orient = make_orientation_options(existrel);
heap_close(existrel, NoLock);
/* 1) Create temp table */
rv = makeRangeVar(NULL /*catalogname*/, nspname, relname, -1);
inh->relation = copyObject(rv);
inh->options = list_make3_int(CREATE_TABLE_LIKE_INCLUDING_DEFAULTS,
CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS,
CREATE_TABLE_LIKE_INCLUDING_INDEXES);
ct->base.tableElts = list_make1(inh);
ct->base.distributedBy = list_copy(distro); /* must preserve the list for later */
/* should be unique enough */
snprintf(tmpname, NAMEDATALEN, "pg_temp_%u", relid);
tmprv = makeRangeVar(NULL /*catalogname*/, nspname, tmpname, -1);
ct->base.relation = tmprv;
ct->base.relKind = RELKIND_RELATION;
ct->ownerid = rel->rd_rel->relowner;
ct->is_split_part = true;
parsetrees = parse_analyze((Node *)ct, NULL, NULL, 0);
q = (Query *)linitial(parsetrees);
ProcessUtility((Node *)q->utilityStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
CommandCounterIncrement();
/* get the oid of the temporary table */
temprelid = get_relname_relid(tmpname,
RelationGetNamespace(rel));
if (pid->idtype == AT_AP_IDRule)
{
idpid = copyObject(pid);
}
else
{
idpid = makeNode(AlterPartitionId);
idpid->idtype = AT_AP_IDRule;
idpid->partiddef = (Node *)list_make2((Node *)prule, pid);
idpid->location = -1;
}
/* 2) EXCHANGE temp with target */
rel_to_drop = prule->topRule->parchildrelid;
ats->relation = copyObject(rv);
ats->relkind = OBJECT_TABLE;
cmd->subtype = AT_PartExchange;
mypc->partid = (Node *)idpid;
mypc->arg1 = (Node *)tmprv;
mypc->arg2 = (Node *)mypc2;
mypc2->arg1 = (Node *)makeInteger(0);
mypc2->arg2 = (Node *)makeInteger(1); /* tell them we're SPLIT */
cmd->def = (Node *)mypc;
ats->cmds = list_make1(cmd);
parsetrees = parse_analyze((Node *)ats, NULL, NULL, 0);
Assert(list_length(parsetrees) == 1);
q = (Query *)linitial(parsetrees);
heap_close(rel, NoLock);
ProcessUtility((Node *)q->utilityStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
rel = heap_open(relid, AccessExclusiveLock);
CommandCounterIncrement();
/* 3) drop the old partition */
if (pid->idtype == AT_AP_IDRule)
{
PgPartRule *pr;
part_rule_cxt cxt;
idpid = copyObject(pid);
/* need to update the OID reference */
pr = linitial((List *)idpid->partiddef);
cxt.old_oid = rel_to_drop;
cxt.new_oid = temprelid;
partrule_walker((Node *)pr, (void *)&cxt);
elog(DEBUG5, "dropping OID %u", temprelid);
}
else
{
/* refresh prule, out of date due to EXCHANGE */
prule = get_part_rule(rel, pid, true, true,
CurrentMemoryContext, NULL, false);
idpid = makeNode(AlterPartitionId);
idpid->idtype = AT_AP_IDRule;
idpid->partiddef = (Node *)list_make2((Node *)prule, pid);
idpid->location = -1;
}
cmd->subtype = AT_PartDrop;
ds->missing_ok = false;
ds->behavior = DROP_RESTRICT;
ds->removeType = OBJECT_TABLE;
mypc->partid = (Node *)idpid;
mypc->arg1 = (Node *)ds;
/* MPP-6589: make DROP work, even if last one
* NOTE: hateful hackery. Normally, the arg2 for the PartDrop
* cmd is null. But since SPLIT may need to DROP the last
* partition before it re-ADDs two new ones, we pass a non-null
* arg2 as a flag to enable ForceDrop in ATPExecPartDrop().
*/
mypc->arg2 = (Node *)makeNode(AlterPartitionCmd);
cmd->def = (Node *)mypc;
parsetrees = parse_analyze((Node *)ats, NULL, NULL, 0);
heap_close(rel, NoLock);
/*
* Might have expanded to multiple statements if, for example, the
* master table has indexes on it.
*/
foreach(lc, parsetrees)
{
q = (Query *)lfirst(lc);
ProcessUtility((Node *)q->utilityStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
}
rel = heap_open(relid, AccessExclusiveLock);
CommandCounterIncrement();
/*
* Now that we've dropped the partition, we need to handle updating
* the PgPartRule pid for the case where it is a pid representing
* ALTER PARTITION ... ALTER PARTITION ...
*
* For the normal case, we'll just run get_part_rule() again deeper
* in the code.
*/
if (pid->idtype == AT_AP_IDRule)
{
AlterPartitionId *tmppid = copyObject(pid);
/* MPP-10223: pid contains a "stale" pNode with a
* partition rule for the partition we just dropped.
* Delve deep into pNode to eliminate the topRule.
*/
if (1)
{
AlterPartitionId *newpid = tmppid;
PgPartRule *tmprule = NULL;
while (tmppid->idtype == AT_AP_IDRule)
{
List *l = (List *)tmppid->partiddef;
/* wipe out the topRule because the partition was
* dropped, or PartAdd will find it and complain!!
* Note that because the pid was copied,
* tmprule->topRule has a separate copy of the
* prule subtree, so we need to fix this one, too.
*/
tmprule = (PgPartRule *)linitial(l);
atpxSplitDropRule(tmprule->pNode,
tmprule->topRule,
prule->topRule->parruleid,
prule->topRule->paroid);
tmppid = lsecond(l);
}
tmppid = newpid;
}
aapid = tmppid;
}
/* Add two new partitions */
elog(DEBUG5, "Split partition: adding two new partitions");
/* 4) add two new partitions, via a loop to reduce code duplication */
for (i = 1; i <= 2; i++)
{
/* build up commands for adding two. */
AlterPartitionId *mypid = makeNode(AlterPartitionId);
CreateStmt *mycs = makeNode(CreateStmt);
char *parname = NULL;
AlterPartitionId *intopid = NULL;
PartitionElem *pelem = makeNode(PartitionElem);
Oid newchildrelid = InvalidOid;
AlterPartitionCmd *storenode = NULL;
/* use storage options for existing rel */
if (existstorage_opts)
{
storenode = makeNode(AlterPartitionCmd);
storenode->arg1 = (Node *)existstorage_opts;
storenode->location = -1;
}
pelem->storeAttr = (Node *)storenode;
if (pc2)
{
if (i == 1)
intopid = intopid1;
else
intopid = intopid2;
}
else
{
if (into_exists == i && prule->topRule->parname)
parname = pstrdup(prule->topRule->parname);
}
mycs->base.relation = makeRangeVar(NULL /*catalogname*/, NULL, "fake_partition_name", -1);
mycs->base.relKind = RELKIND_RELATION;
/*
* If the new partition is column oriented, initialize the column
* entity list to the set of column encodings. Latter in parse
* analysis of this statement, we will append to this list a LIKE
* clause to clone the other column attributes.
*
* Be careful to copy the list since it is modified by parse
* analysis.
*/
if (colencs)
mycs->base.tableElts = list_copy(colencs);
mycs->base.options = orient;
mypid->idtype = AT_AP_IDNone;
mypid->location = -1;
mypid->partiddef = NULL;
mypc->partid = (Node *)mypid;
if (intopid)
parname = strVal(intopid->partiddef);
if (prule->topRule->parisdefault && i == into_exists)
{
/* nothing to do */
}
else
{
if (prule->pNode->part->parkind == 'r')
{
PartitionBoundSpec *a = makeNode(PartitionBoundSpec);
if (prule->topRule->parisdefault)
{
a->partStart = linitial((List *)pc->arg1);
a->partEnd = lsecond((List *)pc->arg1);
}
else if (i == 1)
{
PartitionRangeItem *ri;
/* MPP-6589: if the partition has an "open"
* START, pass a NULL partStart
*/
if (prule->topRule &&
prule->topRule->parrangestart)
{
ri = makeNode(PartitionRangeItem);
ri->location = -1;
ri->everycount = 0;
ri->partRangeVal =
copyObject(prule->topRule->parrangestart);
ri->partedge =
prule->topRule->parrangestartincl ?
PART_EDGE_INCLUSIVE : PART_EDGE_EXCLUSIVE;
a->partStart = (Node *)ri;
}
else
a->partStart = NULL;
ri = makeNode(PartitionRangeItem);
ri->location = -1;
ri->everycount = 0;
ri->partRangeVal = copyObject(at);
ri->partedge = PART_EDGE_EXCLUSIVE;
a->partEnd = (Node *)ri;
}
else if (i == 2)
{
PartitionRangeItem *ri;
ri = makeNode(PartitionRangeItem);
ri->location = -1;
ri->everycount = 0;
ri->partRangeVal = copyObject(at);
ri->partedge = PART_EDGE_INCLUSIVE;
a->partStart = (Node *)ri;
/* MPP-6589: if the partition has an "open"
* END, pass a NULL partEnd
*/
if (prule->topRule &&
prule->topRule->parrangeend)
{
ri = makeNode(PartitionRangeItem);
ri->location = -1;
ri->everycount = 0;
ri->partRangeVal =
copyObject(prule->topRule->parrangeend);
ri->partedge = prule->topRule->parrangeendincl ?
PART_EDGE_INCLUSIVE : PART_EDGE_EXCLUSIVE;
a->partEnd = (Node *)ri;
}
else
a->partEnd = NULL;
}
pelem->boundSpec = (Node *)a;
}
else
{
if ((into_exists && into_exists != i &&
prule->topRule->parisdefault) ||
(i == 2 && !prule->topRule->parisdefault))
{
PartitionValuesSpec *valuesspec =
makeNode(PartitionValuesSpec);
valuesspec->partValues = copyObject(at);
valuesspec->location = -1;
pelem->boundSpec = (Node *)valuesspec;
}
else
{
PartitionValuesSpec *valuesspec =
makeNode(PartitionValuesSpec);
List *lv = prule->topRule->parlistvalues;
List *newvals = NIL;
ListCell *lc;
List *atlist = (List *)at;
/* must be LIST */
Assert(prule->pNode->part->parkind == 'l');
/*
* Iterate through list of existing constraints and
* pick out those not in AT() clause
*/
foreach(lc, lv)
{
List *cols = lfirst(lc);
ListCell *parvals = list_head(atlist);
int16 nkeys = prule->pNode->part->parnatts;
bool found = false;
while (parvals)
{
List *vals = lfirst(parvals);
ListCell *lcv = list_head(vals);
ListCell *lcc = list_head(cols);
int parcol;
bool matched = true;
for (parcol = 0; parcol < nkeys; parcol++)
{
Oid opclass =
prule->pNode->part->parclass[parcol];
Oid funcid = get_opclass_proc(opclass, 0,
BTORDER_PROC);
Const *v = lfirst(lcv);
Const *c = lfirst(lcc);
Datum d;
if (v->constisnull && c->constisnull)
continue;
else if (v->constisnull || c->constisnull)
{
matched = false;
break;
}
d = OidFunctionCall2(funcid, c->constvalue,
v->constvalue);
if (DatumGetInt32(d) != 0)
{
matched = false;
break;
}
lcv = lnext(lcv);
lcc = lnext(lcc);
}
if (matched)
{
found = true;
break;
}
parvals = lnext(parvals);
}
if (!found)
{
/* Not in AT() clause, so keep it */
newvals = lappend(newvals,
copyObject((Node *)cols));
}
}
valuesspec->partValues = newvals;
valuesspec->location = -1;
pelem->boundSpec = (Node *)valuesspec;
}
}
}
/*
* We always want to create a partition name because we
* need to recall the partition details below.
*/
if (!parname)
{
char n[NAMEDATALEN];
snprintf(n, NAMEDATALEN, "r%lu", random());
parname = pstrdup(n);
}
pelem->partName = (Node *)makeString(parname);
mypid->partiddef = pelem->partName;
mypid->idtype = AT_AP_IDName;
pelem->location = -1;
/* MPP-10421: determine if new partition, re-use of old
* partition, and/or is default partition
*/
pelem->isDefault = (defparname &&
parname &&
(0 == strcmp(parname, defparname)));
/* tell ADD PARTITION we're doing a SPLIT */
mypc2->partid = (Node *)makeInteger(1);
mypc2->arg1 = (Node *)pelem;
mypc2->arg2 = (Node *)list_make1(mycs);
mypc2->location = -1;
mypc->arg1 = (Node *)makeInteger(pelem->isDefault ? 1 : 0);
mypc->arg2 = (Node *)mypc2;
mypc->location = -1;
cmd->subtype = AT_PartAdd;
cmd->def = (Node *)mypc;
/* turn this into ALTER PARTITION if need be */
if (pid->idtype == AT_AP_IDRule)
{
AlterTableCmd *ac = makeNode(AlterTableCmd);
AlterPartitionCmd *ap = makeNode(AlterPartitionCmd);
ac->subtype = AT_PartAlter;
ac->def = (Node *)ap;
ap->partid = (Node *)aapid;
ap->arg1 = (Node *)cmd; /* embed the real command */
ap->location = -1;
cmd = ac;
}
ats->cmds = list_make1(cmd);
parsetrees = parse_analyze((Node *)ats, NULL, NULL, 0);
Assert(list_length(parsetrees) == 1);
q = (Query *)linitial(parsetrees);
heap_close(rel, NoLock);
ProcessUtility((Node *)q->utilityStmt,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
rel = heap_open(relid, AccessExclusiveLock);
/* make our change visible */
CommandCounterIncrement();
/* get the rule back which ADD PARTITION just created */
if (pid->idtype == AT_AP_IDRule)
{
cqContext cqc;
int fetchCount;
Relation rulerel = heap_open(PartitionRuleRelationId,
AccessShareLock);
Datum d = DirectFunctionCall1(namein,
CStringGetDatum(strVal(pelem->partName)));
/* XXX XXX: SnapshotSelf - but we just did a
* CommandCounterIncrement()
*/
newchildrelid = caql_getoid_plus(
caql_snapshot(caql_addrel(cqclr(&cqc), rulerel),
SnapshotSelf),
&fetchCount,
NULL,
cql("SELECT parchildrelid FROM pg_partition_rule "
" WHERE paroid = :1 "
" AND parparentrule = :2 "
" AND parname = :3 ",
ObjectIdGetDatum(prule->topRule->paroid),
ObjectIdGetDatum(prule->topRule->parparentoid),
d));
Insist(fetchCount);
heap_close(rulerel, NoLock);
}
else
{
PgPartRule *tmprule;
tmprule = get_part_rule(rel, mypid, true, true,
CurrentMemoryContext, NULL, false);
newchildrelid = tmprule->topRule->parchildrelid;
}
Assert(OidIsValid(newchildrelid));
if (i == 1)
{
intoa = heap_open(newchildrelid,
AccessExclusiveLock);
rva = makeRangeVar(
NULL /*catalogname*/,
get_namespace_name(RelationGetNamespace(intoa)),
get_rel_name(RelationGetRelid(intoa)), -1);
}
else
{
intob = heap_open(newchildrelid,
AccessExclusiveLock);
rvb = makeRangeVar(
NULL /*catalogname*/,
get_namespace_name(RelationGetNamespace(intob)),
get_rel_name(RelationGetRelid(intob)), -1);
}
}
temprel = heap_open(rel_to_drop, AccessExclusiveLock);
/* update parse tree with info for the QEs */
Assert(PointerIsValid(rva));
Assert(PointerIsValid(rvb));
/* update for consumption by QEs */
pc->partid = (Node *)makeInteger(RelationGetRelid(temprel));
pc->arg1 = (Node *)copyObject(rva);
pc->arg2 = (Node *)copyObject(rvb);
/* MPP-6929: metadata tracking */
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"PARTITION", "SPLIT"
);
} /* end if dispatch */
else if (Gp_role == GP_ROLE_EXECUTE)
{
Oid temprelid;
RangeVar *rva;
RangeVar *rvb;
temprelid = (Oid)intVal((Value *)pc->partid);
temprel = heap_open(temprelid, AccessExclusiveLock);
rva = (RangeVar *)pc->arg1;
intoa = heap_openrv(rva, AccessExclusiveLock);
rvb = (RangeVar *)pc->arg2;
intob = heap_openrv(rvb, AccessExclusiveLock);
}
else
return;
/*
* Now, on every node, scan the exchanged out table, splitting data
* between two new partitions.
*
* To do this, we use the CHECK constraints we just created on the
* tables via ADD PARTITION.
*/
if (Gp_role == GP_ROLE_EXECUTE)
{
List *split = NIL;
int segno = 0;
if (RelationIsAoRows(temprel) || RelationIsParquet(temprel))
{
split = GetFileSplitsOfSegment(pc->scantable_splits,
temprel->rd_id, GetQEIndex());
segno = list_nth_int(pc->newpart_aosegnos, GetQEIndex());
}
split_rows(intoa, intob, temprel, split, segno);
}
/*
* In HAWQ, we need to dispatch the splitting of rows to segments. Most other
* ALTER operations, dispatch the ALTER phase 3 work to segments. ALTER .. SPLIT PARTITION
* is different as the actual working of splitting happens in phase 2. Hence we dispatch
* at this point
*/
if (Gp_role == GP_ROLE_DISPATCH)
{
QueryContextInfo *contextdisp;
QueryResource *resource = NULL;
DispatchDataResult result;
List *segment_segnos;
List *scantable_splits;
QueryResource *savedResource = NULL;
int target_segment_num = -1;
/*
* make sure all three relations have the same
* bucket number.
*/
{
GpPolicy *targetPolicy = NULL;
targetPolicy = GpPolicyFetch(CurrentMemoryContext, (Oid)intVal((Value *)pc->partid));
Assert(targetPolicy);
target_segment_num = targetPolicy->bucketnum;
pfree(targetPolicy);
targetPolicy = GpPolicyFetch(CurrentMemoryContext, intoa->rd_id);
Assert(targetPolicy);
if (target_segment_num != targetPolicy->bucketnum)
{
pfree(targetPolicy);
elog(ERROR, "target segment num %d IS NOT equal to the bucket number of this relation %d", target_segment_num, targetPolicy->bucketnum);
}
pfree(targetPolicy);
targetPolicy = GpPolicyFetch(CurrentMemoryContext, intob->rd_id);
Assert(targetPolicy);
if (target_segment_num != targetPolicy->bucketnum)
{
pfree(targetPolicy);
elog(ERROR, "target segment num %d IS NOT equal to the bucket number of this relation %d", target_segment_num, targetPolicy->bucketnum);
}
pfree(targetPolicy);
}
resource = AllocateResource(QRL_ONCE, 1, 1, target_segment_num, target_segment_num, NULL, 0);
savedResource = GetActiveQueryResource();
SetActiveQueryResource(resource);
segment_segnos = SetSegnoForWrite(NIL, 0, target_segment_num, true, true, false);
scantable_splits = NIL;
/* create the segfiles for the new relations here */
CreateAppendOnlyParquetSegFileForRelationOnMaster(intoa, segment_segnos);
CreateAppendOnlyParquetSegFileForRelationOnMaster(intob, segment_segnos);
/* prepare for the metadata dispatch */
contextdisp = CreateQueryContextInfo();
prepareDispatchedCatalogSingleRelation(contextdisp,
intoa->rd_id,
true,
segment_segnos);
prepareDispatchedCatalogSingleRelation(contextdisp,
intob->rd_id,
true,
segment_segnos);
prepareDispatchedCatalogRelation(contextdisp,
(Oid)intVal((Value *)pc->partid),
false,
NULL);
/*
* Dispatch split-related metadata.
*/
scantable_splits = AssignAOSegFileSplitToSegment((Oid)intVal((Value *)pc->partid),
NIL, target_segment_num, scantable_splits);
pc->scantable_splits = scantable_splits;
pc->newpart_aosegnos = segment_segnos;
FinalizeQueryContextInfo(contextdisp);
dispatch_statement_node((Node *) pc, contextdisp, resource, &result);
cdbdisp_iterate_results_sendback(result.result, result.numresults,
UpdateCatalogModifiedOnSegments);
dispatch_free_result(&result);
DropQueryContextInfo(contextdisp);
FreeResource(resource);
SetActiveQueryResource(savedResource);
}
elog(DEBUG5, "dropping temp rel %s", RelationGetRelationName(temprel));
temprelid = RelationGetRelid(temprel);
heap_close(temprel, NoLock);
/*
* In HAWQ, we only need to drop the temp relation on the master which will ensure
* the relation is dropped on segments as well.
*/
if (Gp_role != GP_ROLE_EXECUTE)
{
ObjectAddress addr;
addr.classId = RelationRelationId;
addr.objectId = temprelid;
addr.objectSubId = 0;
performDeletion(&addr, DROP_RESTRICT);
}
heap_close(intoa, NoLock);
heap_close(intob, NoLock);
}
/* ALTER TABLE ... TRUNCATE PARTITION */
static List *
atpxTruncateList(Relation rel, PartitionNode *pNode)
{
List *l1 = NIL;
ListCell *lc;
if (!pNode)
return l1;
/* add the child lists first */
foreach(lc, pNode->rules)
{
PartitionRule *rule = lfirst(lc);
List *l2 = NIL;
if (rule->children)
l2 = atpxTruncateList(rel, rule->children);
else
l2 = NIL;
if (l2)
{
if (l1)
l1 = list_concat(l1, l2);
else
l1 = l2;
}
}
/* and the default partition */
if (pNode->default_part)
{
PartitionRule *rule = pNode->default_part;
List *l2 = NIL;
if (rule->children)
l2 = atpxTruncateList(rel, rule->children);
else
l2 = NIL;
if (l2)
{
if (l1)
l1 = list_concat(l1, l2);
else
l1 = l2;
}
}
/* add entries for rules at current level */
foreach(lc, pNode->rules)
{
PartitionRule *rule = lfirst(lc);
RangeVar *rv;
Relation rel;
rel = heap_open(rule->parchildrelid, AccessShareLock);
rv = makeRangeVar(NULL /*catalogname*/, get_namespace_name(RelationGetNamespace(rel)),
pstrdup(RelationGetRelationName(rel)), -1);
heap_close(rel, NoLock);
if (l1)
l1 = lappend(l1, rv);
else
l1 = list_make1(rv);
}
/* and the default partition */
if (pNode->default_part)
{
PartitionRule *rule = pNode->default_part;
RangeVar *rv;
Relation rel;
rel = heap_open(rule->parchildrelid, AccessShareLock);
rv = makeRangeVar(NULL /*catalogname*/, get_namespace_name(RelationGetNamespace(rel)),
pstrdup(RelationGetRelationName(rel)), -1);
heap_close(rel, NoLock);
if (l1)
l1 = lappend(l1, rv);
else
l1 = list_make1(rv);
}
return l1;
} /* end atpxTruncateList */
static void
ATPExecPartTruncate(Relation rel,
AlterPartitionCmd *pc)
{
AlterPartitionId *pid = (AlterPartitionId *)pc->partid;
PgPartRule *prule = NULL;
if (Gp_role != GP_ROLE_DISPATCH)
return;
prule = get_part_rule(rel, pid, true, true, CurrentMemoryContext, NULL,
false);
if (prule)
{
RangeVar *rv;
TruncateStmt *ts = (TruncateStmt *)pc->arg1;
DestReceiver *dest = None_Receiver;
Relation rel2;
rel2 = heap_open(prule->topRule->parchildrelid, AccessShareLock);
rv = makeRangeVar(NULL /*catalogname*/, get_namespace_name(RelationGetNamespace(rel2)),
pstrdup(RelationGetRelationName(rel2)), -1);
rv->location = pc->location;
if (prule->topRule->children)
{
List *l1 = atpxTruncateList(rel2, prule->topRule->children);
ts->relations = lappend(l1, rv);
}
else
ts->relations = list_make1(rv);
heap_close(rel2, NoLock);
ProcessUtility( (Node *) ts,
synthetic_sql,
NULL,
false, /* not top level */
dest,
NULL);
/* Notify of name if did not use name for partition id spec */
if (prule && prule->topRule && prule->topRule->children
&& (ts->behavior != DROP_CASCADE ))
{
ereport(NOTICE,
(errmsg("truncated partition%s for %s and its children",
prule->partIdStr,
prule->relname),
errOmitLocation(true)));
}
else if ((pid->idtype != AT_AP_IDName)
&& prule->isName)
ereport(NOTICE,
(errmsg("truncated partition%s for %s",
prule->partIdStr,
prule->relname),
errOmitLocation(true)));
}
} /* end ATPExecPartTruncate */
/*
* Execute ALTER TABLE SET SCHEMA
*
* WARNING WARNING WARNING: In previous *minor* releases the caller was
* responsible for checking ownership of the relation, but now we do it here.
*/
void
AlterTableNamespace(RangeVar *relation, const char *newschema)
{
Relation rel;
Oid relid;
Oid oldNspOid;
Oid nspOid;
/* make sure this is not an hcatalog table */
relid = RangeVarGetRelid(relation, false /*failOk*/, false /*allowHcatalog*/);
rel = heap_openrv(relation, AccessExclusiveLock);
CheckRelationOwnership(relid, true);
oldNspOid = RelationGetNamespace(rel);
/* heap_openrv allows TOAST, but we don't want to */
if (rel->rd_rel->relkind == RELKIND_TOASTVALUE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a TOAST relation",
RelationGetRelationName(rel)),
errOmitLocation(true)));
if (rel->rd_rel->relkind == RELKIND_AOSEGMENTS)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an Append Only segment listing relation",
RelationGetRelationName(rel)),
errOmitLocation(true)));
if (rel->rd_rel->relkind == RELKIND_AOBLOCKDIR)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an Append Only block directory relation",
RelationGetRelationName(rel)),
errOmitLocation(true)));
/* if it's an owned sequence, disallow moving it by itself */
if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
{
Oid tableId;
int32 colId;
if (sequenceIsOwned(relid, &tableId, &colId))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move an owned sequence into another schema"),
errdetail("Sequence \"%s\" is linked to table \"%s\".",
RelationGetRelationName(rel),
get_rel_name(tableId)),
errOmitLocation(true)));
}
/* get schema OID and check its permissions */
nspOid = LookupCreationNamespace(newschema);
if (oldNspOid == nspOid)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" is already in schema \"%s\"",
RelationGetRelationName(rel),
newschema),
errOmitLocation(true)));
/* disallow renaming into or out of temp schemas */
if (isAnyTempNamespace(nspOid) || isAnyTempNamespace(oldNspOid))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move objects into or out of temporary schemas"),
errOmitLocation(true)));
/* same for TOAST schema */
if (nspOid == PG_TOAST_NAMESPACE || oldNspOid == PG_TOAST_NAMESPACE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move objects into or out of TOAST schema"),
errOmitLocation(true)));
/* same for AO SEGMENT schema */
if (nspOid == PG_AOSEGMENT_NAMESPACE || oldNspOid == PG_AOSEGMENT_NAMESPACE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move objects into or out of AO SEGMENT schema"),
errOmitLocation(true)));
/* OK, modify the pg_class row and pg_depend entry */
AlterRelationNamespaceInternalTwo(rel, relid,
oldNspOid, nspOid,
true, newschema);
/* MPP-7825, MPP-6929, MPP-7600: metadata tracking */
if ((Gp_role == GP_ROLE_DISPATCH)
&& MetaTrackValidKindNsp(rel->rd_rel))
MetaTrackUpdObject(RelationRelationId,
RelationGetRelid(rel),
GetUserId(),
"ALTER", "SET SCHEMA"
);
/* close rel, but keep lock until commit */
relation_close(rel, NoLock);
}
static void
AlterRelationNamespaceInternalTwo(Relation rel,
Oid relid,
Oid oldNspOid, Oid newNspOid,
bool hasDependEntry,
const char *newschema)
{
Relation classRel;
/* OK, modify the pg_class row and pg_depend entry */
classRel = heap_open(RelationRelationId, RowExclusiveLock);
AlterRelationNamespaceInternal(classRel, relid, oldNspOid, newNspOid,
hasDependEntry);
/* Fix the table's rowtype too */
AlterTypeNamespaceInternal(rel->rd_rel->reltype, newNspOid, false);
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, newNspOid);
AlterSeqNamespaces(classRel, rel, oldNspOid, newNspOid, newschema);
AlterConstraintNamespaces(relid, oldNspOid, newNspOid, false);
}
heap_close(classRel, RowExclusiveLock);
}
/*
* The guts of relocating a relation to another namespace: fix the pg_class
* entry, and the pg_depend entry if any. Caller must already have
* opened and write-locked pg_class.
*/
void
AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
Oid oldNspOid, Oid newNspOid,
bool hasDependEntry)
{
HeapTuple classTup;
Form_pg_class classForm;
cqContext cqc;
cqContext *pcqCtx;
Assert(RelationGetRelid(classRel) == RelationRelationId);
pcqCtx = caql_addrel(cqclr(&cqc), classRel);
classTup = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(relOid)));
if (!HeapTupleIsValid(classTup))
elog(ERROR, "cache lookup failed for relation %u", relOid);
classForm = (Form_pg_class) GETSTRUCT(classTup);
Assert(classForm->relnamespace == oldNspOid);
/* check for duplicate name (more friendly than unique-index failure) */
if (get_relname_relid(NameStr(classForm->relname),
newNspOid) != InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_TABLE),
errmsg("relation \"%s\" already exists in schema \"%s\"",
NameStr(classForm->relname),
get_namespace_name(newNspOid)),
errOmitLocation(true)));
/* classTup is a copy, so OK to scribble on */
classForm->relnamespace = newNspOid;
caql_update_current(pcqCtx, classTup);
/* and Update indexes (implicit) */
/* Update dependency on schema if caller said so */
if (hasDependEntry &&
changeDependencyFor(RelationRelationId, relOid,
NamespaceRelationId, oldNspOid, newNspOid) != 1)
elog(ERROR, "failed to change schema dependency for relation \"%s\"",
NameStr(classForm->relname));
heap_freetuple(classTup);
}
/*
* Move all indexes for the specified relation to another namespace.
*
* Note: we assume adequate permission checking was done by the caller,
* and that the caller has a suitable lock on the owning relation.
*/
static void
AlterIndexNamespaces(Relation classRel, Relation rel,
Oid oldNspOid, Oid newNspOid)
{
List *indexList;
ListCell *l;
indexList = RelationGetIndexList(rel);
foreach(l, indexList)
{
Oid indexOid = lfirst_oid(l);
/*
* Note: currently, the index will not have its own dependency on the
* namespace, so we don't need to do changeDependencyFor(). There's no
* rowtype in pg_type, either.
*/
AlterRelationNamespaceInternal(classRel, indexOid,
oldNspOid, newNspOid,
false);
}
list_free(indexList);
}
/*
* Move all SERIAL-column sequences of the specified relation to another
* namespace.
*
* Note: we assume adequate permission checking was done by the caller,
* and that the caller has a suitable lock on the owning relation.
*/
static void
AlterSeqNamespaces(Relation classRel, Relation rel,
Oid oldNspOid, Oid newNspOid, const char *newNspName)
{
cqContext *pcqCtx;
HeapTuple tup;
/*
* SERIAL sequences are those having an auto dependency on one of the
* table's columns (we don't care *which* column, exactly).
*/
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_depend "
" WHERE refclassid = :1 "
" AND refobjid = :2 ",
ObjectIdGetDatum(RelationRelationId),
ObjectIdGetDatum(RelationGetRelid(rel))));
while (HeapTupleIsValid(tup = caql_getnext(pcqCtx)))
{
Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup);
Relation seqRel;
/* skip dependencies other than auto dependencies on columns */
if (depForm->refobjsubid == 0 ||
depForm->classid != RelationRelationId ||
depForm->objsubid != 0 ||
depForm->deptype != DEPENDENCY_AUTO)
continue;
/* Use relation_open just in case it's an index */
seqRel = relation_open(depForm->objid, AccessExclusiveLock);
/* skip non-sequence relations */
if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE)
{
/* No need to keep the lock */
relation_close(seqRel, AccessExclusiveLock);
continue;
}
/* Fix the pg_class and pg_depend entries */
AlterRelationNamespaceInternal(classRel, depForm->objid,
oldNspOid, newNspOid,
true);
/*
* Sequences have entries in pg_type. We need to be careful to move
* them to the new namespace, too.
*/
AlterTypeNamespaceInternal(RelationGetForm(seqRel)->reltype,
newNspOid, false);
/* Now we can close it. Keep the lock till end of transaction. */
relation_close(seqRel, NoLock);
}
caql_endscan(pcqCtx);
}
/*
* This code supports
* CREATE TEMP TABLE ... ON COMMIT { DROP | PRESERVE ROWS | DELETE ROWS }
*
* Because we only support this for TEMP tables, it's sufficient to remember
* the state in a backend-local data structure.
*/
/*
* Register a newly-created relation's ON COMMIT action.
*/
void
register_on_commit_action(Oid relid, OnCommitAction action)
{
OnCommitItem *oc;
MemoryContext oldcxt;
/*
* We needn't bother registering the relation unless there is an ON COMMIT
* action we need to take.
*/
if (action == ONCOMMIT_NOOP || action == ONCOMMIT_PRESERVE_ROWS)
return;
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
oc = (OnCommitItem *) palloc(sizeof(OnCommitItem));
oc->relid = relid;
oc->oncommit = action;
oc->creating_subid = GetCurrentSubTransactionId();
oc->deleting_subid = InvalidSubTransactionId;
on_commits = lcons(oc, on_commits);
MemoryContextSwitchTo(oldcxt);
}
/*
* Unregister any ON COMMIT action when a relation is deleted.
*
* Actually, we only mark the OnCommitItem entry as to be deleted after commit.
*/
void
remove_on_commit_action(Oid relid)
{
ListCell *l;
foreach(l, on_commits)
{
OnCommitItem *oc = (OnCommitItem *) lfirst(l);
if (oc->relid == relid)
{
oc->deleting_subid = GetCurrentSubTransactionId();
break;
}
}
}
/*
* Perform ON COMMIT actions.
*
* This is invoked just before actually committing, since it's possible
* to encounter errors.
*/
void
PreCommit_on_commit_actions(void)
{
ListCell *l;
List *oids_to_truncate = NIL;
foreach(l, on_commits)
{
OnCommitItem *oc = (OnCommitItem *) lfirst(l);
/* Ignore entry if already dropped in this xact */
if (oc->deleting_subid != InvalidSubTransactionId)
continue;
switch (oc->oncommit)
{
case ONCOMMIT_NOOP:
case ONCOMMIT_PRESERVE_ROWS:
/* Do nothing (there shouldn't be such entries, actually) */
break;
case ONCOMMIT_DELETE_ROWS:
oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
break;
case ONCOMMIT_DROP:
{
ObjectAddress object;
object.classId = RelationRelationId;
object.objectId = oc->relid;
object.objectSubId = 0;
performDeletion(&object, DROP_CASCADE);
/*
* Note that table deletion will call
* remove_on_commit_action, so the entry should get marked
* as deleted.
*/
Assert(oc->deleting_subid != InvalidSubTransactionId);
break;
}
}
}
if (oids_to_truncate != NIL)
{
heap_truncate(oids_to_truncate);
CommandCounterIncrement(); /* XXX needed? */
}
}
/*
* Post-commit or post-abort cleanup for ON COMMIT management.
*
* All we do here is remove no-longer-needed OnCommitItem entries.
*
* During commit, remove entries that were deleted during this transaction;
* during abort, remove those created during this transaction.
*/
void
AtEOXact_on_commit_actions(bool isCommit)
{
ListCell *cur_item;
ListCell *prev_item;
prev_item = NULL;
cur_item = list_head(on_commits);
while (cur_item != NULL)
{
OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
if (isCommit ? oc->deleting_subid != InvalidSubTransactionId :
oc->creating_subid != InvalidSubTransactionId)
{
/* cur_item must be removed */
on_commits = list_delete_cell(on_commits, cur_item, prev_item);
pfree(oc);
if (prev_item)
cur_item = lnext(prev_item);
else
cur_item = list_head(on_commits);
}
else
{
/* cur_item must be preserved */
oc->creating_subid = InvalidSubTransactionId;
oc->deleting_subid = InvalidSubTransactionId;
prev_item = cur_item;
cur_item = lnext(prev_item);
}
}
}
/*
* Post-subcommit or post-subabort cleanup for ON COMMIT management.
*
* During subabort, we can immediately remove entries created during this
* subtransaction. During subcommit, just relabel entries marked during
* this subtransaction as being the parent's responsibility.
*/
void
AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
SubTransactionId parentSubid)
{
ListCell *cur_item;
ListCell *prev_item;
prev_item = NULL;
cur_item = list_head(on_commits);
while (cur_item != NULL)
{
OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
if (!isCommit && oc->creating_subid == mySubid)
{
/* cur_item must be removed */
on_commits = list_delete_cell(on_commits, cur_item, prev_item);
pfree(oc);
if (prev_item)
cur_item = lnext(prev_item);
else
cur_item = list_head(on_commits);
}
else
{
/* cur_item must be preserved */
if (oc->creating_subid == mySubid)
oc->creating_subid = parentSubid;
if (oc->deleting_subid == mySubid)
oc->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
prev_item = cur_item;
cur_item = lnext(prev_item);
}
}
}
/*
* Transform the URI string list into a text array (the form that is
* used in the catalog table pg_exttable). While at it we validate
* the URI strings.
*
* The result is a text array but we declare it as Datum to avoid
* including array.h in analyze.h.
*/
static Datum transformLocationUris(List *locs, List* fmtopts, bool isweb, bool iswritable,
bool forceCreateDir, bool* isCustom)
{
ListCell *cell;
ArrayBuildState *astate;
Datum result;
UriProtocol first_protocol = URI_FILE; /* initialize to keep gcc quiet */
bool first_uri = true;
char* first_customprotocal;
#define FDIST_DEF_PORT 8080
Uri *uri;
text *t;
char *uri_str_orig;
char *uri_str_final;
Size len;
Value *v;
/* We build new array using accumArrayResult */
astate = NULL;
/* first, check for duplicate URI entries */
foreach(cell, locs)
{
Value *v1 = lfirst(cell);
const char *uri1 = v1->val.str;
ListCell *rest;
for_each_cell(rest, lnext(cell))
{
Value *v2 = lfirst(rest);
const char *uri2 = v2->val.str;
if (strcmp(uri1, uri2) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("location uri \"%s\" appears more than once",
uri1),
errOmitLocation(true)));
}
}
/* iterate through the user supplied URI list from LOCATION clause. */
foreach(cell, locs)
{
v = lfirst(cell);
/* get the current URI string from the command */
uri_str_orig = pstrdup(v->val.str);
/* parse it to its components */
uri = ParseExternalTableUri(uri_str_orig);
/* allocate memory for a modified URI string (if needs modification) */
uri_str_final = (char *) palloc(strlen(uri_str_orig) *
sizeof(char) +
1 + 4 + 1 /* default port if added */);
/*
* in here edit the uri string if needed
*/
/* in HAWQ 2.0, the file protocol is not supported any more. */
if (uri->protocol == URI_FILE)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("the file protocol for external tables is deprecated"),
errhint("use the gpfdist protocol or COPY FROM instead"),
errOmitLocation(true)));
}
/* no port was specified for gpfdist, gpfdists or hdfs. add the default */
if ((uri->protocol == URI_GPFDIST || uri->protocol == URI_GPFDISTS) && uri->port == -1)
{
char *at_hostname = (char *) uri_str_orig
+ strlen(uri->protocol == URI_GPFDIST ? "gpfdist://" : "gpfdists://");
char *after_hostname = strchr(at_hostname, '/');
int len = after_hostname - at_hostname;
char *hostname = pstrdup(at_hostname);
hostname[len] = '\0';
/* add the default port number to the uri string */
sprintf(uri_str_final, "%s%s:%d%s",
(uri->protocol == URI_GPFDIST ? PROTOCOL_GPFDIST : PROTOCOL_GPFDISTS),
hostname,
FDIST_DEF_PORT, after_hostname);
pfree(hostname);
}
else
{
/* no changes to original uri string */
uri_str_final = (char *) uri_str_orig;
}
/*
* If a custom protocol is used, validate its existence.
* If it exists, and a custom protocol url validator exists
* as well, invoke it now.
*/
if (first_uri && uri->protocol == URI_CUSTOM)
{
Oid procOid = InvalidOid;
procOid = LookupExtProtocolFunction(uri->customprotocol,
EXTPTC_FUNC_VALIDATOR,
false);
if (OidIsValid(procOid) && Gp_role == GP_ROLE_DISPATCH)
InvokeProtocolValidation(procOid,
uri->customprotocol,
iswritable, forceCreateDir,
locs, fmtopts);
first_customprotocal = uri->customprotocol;
}
if (first_uri && uri->protocol == URI_HDFS)
{
Oid procOid = InvalidOid;
procOid = LookupCustomProtocolValidatorFunc("hdfs");
if (OidIsValid(procOid) && Gp_role == GP_ROLE_DISPATCH)
{
InvokeProtocolValidation(procOid,
uri->customprotocol,
iswritable, forceCreateDir,
locs, fmtopts);
}
}
if(first_uri)
{
first_protocol = uri->protocol;
first_uri = false;
if(uri->protocol == URI_CUSTOM){
*isCustom = true;
}
}
if(uri->protocol != first_protocol || (first_protocol == URI_CUSTOM && strcmp(first_customprotocal, uri->customprotocol) != 0))
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("URI protocols must be the same for all data sources"),
errhint("Available protocols are 'http', 'file', 'pxf', 'gpfdist' and 'gpfdists'"),
errOmitLocation(true)));
}
if(uri->protocol != URI_HTTP && isweb)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("an EXTERNAL WEB TABLE may only use http URI\'s, problem in: \'%s\'", uri_str_final),
errhint("Use CREATE EXTERNAL TABLE instead."),
errOmitLocation(true)));
if(uri->protocol == URI_HTTP && !isweb)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("http URI\'s can only be used in an external web table"),
errhint("Use CREATE EXTERNAL WEB TABLE instead."),
errOmitLocation(true)));
if(iswritable && (uri->protocol == URI_HTTP || uri->protocol == URI_FILE))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported URI protocol \'%s\' for writable external table",
(uri->protocol == URI_HTTP ? "http" : "file")),
errhint("Writable external tables may use \'gpfdist(s)\' URIs only."),
errOmitLocation(true)));
if(uri->protocol != URI_CUSTOM && iswritable && strchr(uri->path, '*'))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Unsupported use of wildcard in a writable external web table definition: "
"\'%s\'", uri_str_final),
errhint("Specify the explicit path and file name to write into.")));
if ((uri->protocol == URI_GPFDIST || uri->protocol == URI_GPFDISTS) &&
iswritable &&
uri->path[strlen(uri->path) - 1] == '/')
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Unsupported use of a directory name in a writable gpfdist(s) external table : "
"\'%s\'", uri_str_final),
errhint("Specify the explicit path and file name to write into."),
errOmitLocation(true)));
len = VARHDRSZ + strlen(uri_str_final);
/* +1 leaves room for sprintf's trailing null */
t = (text *) palloc(len + 1);
SET_VARSIZE(t, len);
sprintf((char *) VARDATA(t), "%s", uri_str_final);
astate = accumArrayResult(astate, PointerGetDatum(t),
false, TEXTOID,
CurrentMemoryContext);
FreeExternalTableUri(uri);
pfree(uri_str_final);
}
if (astate)
result = makeArrayResult(astate, CurrentMemoryContext);
else
result = (Datum) 0;
return result;
}
static Oid
LookupCustomProtocolValidatorFunc(char *protoname)
{
List* funcname = NIL;
Oid procOid = InvalidOid;
Oid argList[1];
Oid returnOid;
elog(LOG, "find validator func for %s", protoname);
char* new_func_name = (char *)palloc0(strlen(protoname) + 16);
sprintf(new_func_name, "%s_validate", protoname);
funcname = lappend(funcname, makeString(new_func_name));
returnOid = VOIDOID;
procOid = LookupFuncName(funcname, 0, argList, true);
if (!OidIsValid(procOid))
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("protocol function %s was not found.",
new_func_name),
errhint("Create it with CREATE FUNCTION."),
errOmitLocation(true)));
/* check return type matches */
if (get_func_rettype(procOid) != returnOid)
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("protocol function %s has an incorrect return type",
new_func_name),
errOmitLocation(true)));
/* check allowed volatility */
if (func_volatile(procOid) != PROVOLATILE_STABLE)
ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("protocol function %s is not declared STABLE.",
new_func_name),
errOmitLocation(true)));
pfree(new_func_name);
return procOid;
}
static Datum transformExecOnClause(List *on_clause, int *preferred_segment_num, bool iswritable)
{
ArrayBuildState *astate;
Datum result;
ListCell *exec_location_opt;
char *exec_location_str = NULL;
int value_int;
Size len;
text *t;
/*
* Extract options from the statement node tree
* NOTE: as of now we only support one option in the ON clause
* and therefore more than one is an error (check here in case
* the sql parser isn't strict enough).
*/
foreach(exec_location_opt, on_clause)
{
DefElem *defel = (DefElem *) lfirst(exec_location_opt);
/* only one element is allowed! */
if(exec_location_str)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("ON clause must not have more than one element."),
errOmitLocation(true)));
if (strcmp(defel->defname, "all") == 0)
{
/* in HAWQ 2.0, we don't support ON ALL any more. */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("the ON ALL syntax for external tables is deprecated"),
errOmitLocation(true)));
/* result: "ALL_SEGMENTS" */
/*
exec_location_str = (char *) palloc(12 + 1);
exec_location_str = "ALL_SEGMENTS";
*preferred_segment_num = GetAllWorkerHostNum();
*/
}
else if (strcmp(defel->defname, "hostname") == 0)
{
/* in HAWQ 2.0, we don't support ON HOST any more. */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("the ON HOST syntax for external tables is deprecated"),
errOmitLocation(true)));
/* result: "HOST:<hostname>" */
/*
value_str = strVal(defel->arg);
exec_location_str = (char *) palloc(5 + 1 + strlen(value_str) + 1);
sprintf((char *) exec_location_str, "HOST:%s", value_str);
*preferred_segment_num = GetAllWorkerHostNum();
*/
}
else if (strcmp(defel->defname, "eachhost") == 0)
{
/* in HAWQ 2.0, we don't support EACHHOST any more. */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("the ON EACHHOST syntax for external tables is deprecated"),
errOmitLocation(true)));
/* result: "PER_HOST" */
/*
exec_location_str = (char *) palloc(8 + 1);
exec_location_str = "PER_HOST";
*preferred_segment_num = GetAllWorkerHostNum();
*/
}
else if (strcmp(defel->defname, "master") == 0)
{
if(iswritable){
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("the ON master syntax for writable external tables is deprecated"),
errOmitLocation(true)));
}
/* result: "MASTER_ONLY" */
exec_location_str = (char *) palloc(11 + 1);
exec_location_str = "MASTER_ONLY";
*preferred_segment_num = -1;
}
else if (strcmp(defel->defname, "segment") == 0)
{
if(iswritable){
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("the ON segment syntax for writable external tables is deprecated"),
errOmitLocation(true)));
}
/* result: "SEGMENT_ID:<segid>" */
value_int = intVal(defel->arg);
exec_location_str = (char *) palloc(10 + 1 + 8 + 1);
sprintf((char *) exec_location_str, "SEGMENT_ID:%d", value_int);
*preferred_segment_num = value_int + 1;
}
else if (strcmp(defel->defname, "random") == 0)
{
/* result: "TOTAL_SEGS:<number>" */
value_int = intVal(defel->arg);
exec_location_str = (char *) palloc(10 + 1 + 8 + 1);
sprintf((char *) exec_location_str, "TOTAL_SEGS:%d", value_int);
*preferred_segment_num = value_int;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_GP_INTERNAL_ERROR),
errmsg("Unknown location code for EXECUTE in tablecmds."),
errOmitLocation(true)));
}
}
/* convert to text[] */
astate = NULL;
len = VARHDRSZ + strlen(exec_location_str);
t = (text *) palloc(len + 1);
SET_VARSIZE(t, len);
sprintf((char *) VARDATA(t), "%s", exec_location_str);
astate = accumArrayResult(astate, PointerGetDatum(t),
false, TEXTOID,
CurrentMemoryContext);
if (astate)
result = makeArrayResult(astate, CurrentMemoryContext);
else
result = (Datum) 0;
return result;
}
/*
* transform format name to format code and validate that
* the format is supported. Currently the only supported formats
* are "text" (type 't') ,"csv" (type 'c') and "custom" (type 'b')
*/
static char transformFormatType(char *formatname)
{
char result = '\0';
if(pg_strcasecmp(formatname, "text") == 0)
result = 't';
else if(pg_strcasecmp(formatname, "csv") == 0)
result = 'c';
else if(pg_strcasecmp(formatname, "custom") == 0)
result = 'b';
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unsupported format '%s'", formatname),
errhint("available formats are \"text\", \"csv\", or \"custom\""),
errOmitLocation(true)));
return result;
}
/*
* Check if values for options of custom external table are valid
*/
static void checkCustomFormatOptString(char **opt,
const char *key,
const char *val,
const bool needopt,
const int nvalidvals,
const char **validvals)
{
Assert(opt);
/* check if need to check option */
if (!needopt)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("redundant option %s", key),
errOmitLocation(true)));
}
/* check if option is redundant */
if (*opt)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options \"%s\"", key),
errOmitLocation(true)));
}
*opt = val;
/* check if value for option is valid */
bool valid = false;
for (int i = 0; i < nvalidvals; i++)
{
if (strncasecmp(*opt, validvals[i], strlen(validvals[i])) == 0)
{
valid = true;
}
}
if (!valid)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid value for option %s: \"%s\"", key, val),
errOmitLocation(true)));
}
}
/*
* Check if given encoding for custom external table is supported.
*
* For custom external table, the valid format type kinds are:
* 1) 't' for TEXT
* 2) 'c' for CSV
* 3) 'b' for pluggable format, i.e., ORC which currently support UTF8 encoding.
*/
static void checkCustomFormatEncoding(const char *formatterName,
const char *encodingName)
{
Oid procOid = InvalidOid;
procOid = LookupPlugStorageValidatorFunc(formatterName, "validate_encodings");
if (OidIsValid(procOid))
{
InvokePlugStorageValidationFormatEncodings(procOid, encodingName);
}
}
/*
* Check if options for custom external table are valid
*/
static char *checkCustomFormatOptions(const char *formattername,
List *formatOpts,
const bool isWritable,
const char *delimiter)
{
/* General options for custom external table */
const int maxlen = 8 * 1024 - 1;
char *format_str = NULL;
format_str = (char *) palloc0(maxlen + 1);
/* built-in TEXT, CSV format */
if (pg_strncasecmp(formattername, "text", strlen("text")) == 0 ||
pg_strncasecmp(formattername, "csv", strlen("csv")) == 0)
{
char *formatter = NULL;
ListCell *option;
int len = 0;
StringInfoData key_modified;
initStringInfo(&key_modified);
foreach(option, formatOpts)
{
DefElem *defel = (DefElem *) lfirst(option);
char *key = defel->defname;
bool need_free_value = false;
char *val = defGetString(defel, &need_free_value);
/* check formatter */
if (strncasecmp(key, "formatter", strlen("formatter")) == 0)
{
char *formatterValues[] = {"text", "csv"};
checkCustomFormatOptString(&formatter, key, val, true, 2, formatterValues);
}
/* check option for text/csv format */
/* MPP-14467 - replace any space chars with meta char */
resetStringInfo(&key_modified);
appendStringInfoString(&key_modified, key);
replaceStringInfoString(&key_modified, " ", "<gpx20>");
sprintf((char *) format_str + len, "%s '%s' ", key_modified.data, val);
len += strlen(key_modified.data) + strlen(val) + 4;
if (need_free_value)
{
pfree(val);
val = NULL;
}
AssertImply(need_free_value, NULL == val);
if (len > maxlen)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("format options must be less than %d bytes in size", maxlen),
errOmitLocation(true)));
}
}
if(!formatter)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("no formatter function specified"),
errOmitLocation(true)));
}
else if (strncasecmp(formatter, "text", strlen("text")) == 0)
{
if (delimiter &&
strchr("\\.abcdefghijklmnopqrstuvwxyz0123456789", delimiter[0]) != NULL)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("delimiter cannot be \"%s\"", delimiter)));
}
}
}
/* pluggable format */
else
{
Oid procOid = InvalidOid;
procOid = LookupPlugStorageValidatorFunc(formattername, "validate_options");
if (OidIsValid(procOid))
{
InvokePlugStorageValidationFormatOptions(procOid, formatOpts,
format_str, isWritable);
}
}
return format_str;
}
/*
* Check if the data types for custom external table are valid
*
* Currently supported data types for ORC external tables are:
* SMALLINT, INT, BIGINT, REAL, FLOAT, DOUBLE PRECISION, BOOL, VARCHAR, or TEXT
*
* Need to revise this if more data types are supported for ORC external table.
*/
static void checkCustomFormatDateTypes(const char *formatterName,
TupleDesc tupleDesc)
{
if (formatterName == NULL)
return;
Oid procOid = InvalidOid;
procOid = LookupPlugStorageValidatorFunc(formatterName, "validate_datatypes");
if (OidIsValid(procOid))
{
InvokePlugStorageValidationFormatDataTypes(procOid, tupleDesc);
}
}
/*
* Transform the FORMAT options into a text field. Parse the
* options and validate them for their respective format type.
*
* The result is a text field that includes the format string.
*/
static Datum transformFormatOpts(char formattype, char *formatname, char *formattername, List *formatOpts, int numcols, bool iswritable, GpPolicy *policy)
{
ListCell *option;
Datum result;
char *format_str = NULL;
char *delim = NULL;
char *null_print = NULL;
char *quote = NULL;
char *escape = NULL;
char *eol_str = NULL;
char *formatter = NULL;
char *compresstype = NULL;
char *rlecoder = NULL;
bool header_line = false;
bool fill_missing = false;
List *force_notnull = NIL;
List *force_quote = NIL;
Size len;
StringInfoData fnn, fq, nl;
Assert(fmttype_is_custom(formattype) ||
fmttype_is_text(formattype) ||
fmttype_is_csv(formattype));
/* Extract options from the statement node tree */
if (fmttype_is_text(formattype) || fmttype_is_csv(formattype))
{
foreach(option, formatOpts)
{
DefElem *defel = (DefElem *)lfirst(option);
if (strcmp(defel->defname, "delimiter") == 0)
{
if (delim)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
delim = strVal(defel->arg);
}
else if (strcmp(defel->defname, "null") == 0)
{
if (null_print)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
null_print = strVal(defel->arg);
}
else if (strcmp(defel->defname, "header") == 0)
{
if (header_line)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
header_line = intVal(defel->arg);
}
else if (strcmp(defel->defname, "quote") == 0)
{
if (quote)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
quote = strVal(defel->arg);
}
else if (strcmp(defel->defname, "escape") == 0)
{
if (escape)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
escape = strVal(defel->arg);
}
else if (strcmp(defel->defname, "force_notnull") == 0)
{
if (force_notnull)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
force_notnull = (List *) defel->arg;
}
else if (strcmp(defel->defname, "force_quote") == 0)
{
if (force_quote)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
force_quote = (List *) defel->arg;
}
else if (strcmp(defel->defname, "fill_missing_fields") == 0)
{
if (fill_missing)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
fill_missing = intVal(defel->arg);
}
else if (strcmp(defel->defname, "newline") == 0)
{
if (eol_str)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
eol_str = strVal(defel->arg);
}
else if (strcmp(defel->defname, "formatter") == 0)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("formatter option only valid for custom formatters"),
errOmitLocation(true)));
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
/* Set defaults */
if (!delim)
delim = fmttype_is_csv(formattype) ? "," : "\t";
if (!null_print)
null_print = fmttype_is_csv(formattype) ? "" : "\\N";
if (fmttype_is_csv(formattype))
{
if (!quote)
quote = "\"";
if (!escape)
escape = quote;
}
if (!fmttype_is_csv(formattype) && !escape)
escape = "\\"; /* default escape for text mode */
/*
* re-construct the FORCE NOT NULL list string.
* TODO: is there no existing util function that does this? can't find.
*/
if(force_notnull)
{
ListCell *l;
bool is_first_col = true;
initStringInfo(&fnn);
appendStringInfo(&fnn, " force not null");
foreach(l, force_notnull)
{
const char *col_name = strVal(lfirst(l));
appendStringInfo(&fnn, (is_first_col ? " %s" : ",%s"),
quote_identifier(col_name));
is_first_col = false;
}
}
/* re-construct the FORCE QUOTE list string. */
if(force_quote)
{
ListCell *l;
bool is_first_col = true;
initStringInfo(&fq);
appendStringInfo(&fq, " force quote");
foreach(l, force_quote)
{
const char *col_name = strVal(lfirst(l));
appendStringInfo(&fq, (is_first_col ? " %s" : ",%s"),
quote_identifier(col_name));
is_first_col = false;
}
}
if(eol_str)
{
initStringInfo(&nl);
appendStringInfo(&nl, " newline '%s'", eol_str);
}
/* verify all user supplied control char combinations are legal */
ValidateControlChars(false,
!iswritable,
fmttype_is_csv(formattype),
delim,
null_print,
quote,
escape,
force_quote,
force_notnull,
header_line,
fill_missing,
eol_str,
numcols);
/*
* build the format option string that will get stored in the catalog.
*/
len = 9 + 1 + 1 + strlen(delim) + 1 + /* "delimiter 'x' of 'off'" */
1 + /* space */
4 + 1 + 1 + strlen(null_print) + 1 + /* "null 'str'" */
1 + /* space */
6 + 1 + 1 + strlen(escape) + 1; /* "escape 'c' or 'off' */
if (fmttype_is_csv(formattype))
len += 1 + /* space */
5 + 1 + 3; /* "quote 'x'" */
len += header_line ? strlen(" header") : 0;
len += fill_missing ? strlen(" fill missing fields") : 0;
len += force_notnull ? strlen(fnn.data) : 0;
len += force_quote ? strlen(fq.data) : 0;
len += (eol_str ? (1 + 7 + 1 + 1 + strlen(eol_str) + 1) : 0); /* space x 2, newline 'xx/xxxx' */
/* +1 leaves room for sprintf's trailing null */
format_str = (char *) palloc(len + 1);
if(fmttype_is_text(formattype))
{
sprintf((char *) format_str, "delimiter '%s' null '%s' escape '%s'%s%s%s",
delim, null_print, escape, (header_line ? " header":""),
(fill_missing ? " fill missing fields":""), (eol_str ? nl.data : ""));
}
else if (fmttype_is_csv(formattype))
{
sprintf((char *) format_str, "delimiter '%s' null '%s' escape '%s' quote '%s'%s%s%s%s%s",
delim, null_print, escape, quote, (header_line ? " header" : ""),
(fill_missing ? " fill missing fields":""),
(force_notnull ? fnn.data :""), (force_quote ? fq.data :""),
(eol_str ? nl.data : ""));
}
else
{
/* should never happen */
Assert(false);
}
}
else
{
/* custom/parquet/orc format */
format_str = checkCustomFormatOptions(formattername, formatOpts, iswritable, delim);
/* set default hash key */
ListCell *opt;
foreach(opt, formatOpts)
{
DefElem *defel = (DefElem *) lfirst(opt);
char *key = defel->defname;
bool need_free_value = false;
char *val = (char *) defGetString(defel, &need_free_value);
if (strncasecmp(key, "bucketnum", strlen("bucketnum")) == 0)
{
policy->bucketnum = atoi(val);
}
if (need_free_value)
{
pfree(val);
val = NULL;
}
}
}
/* convert c string to text datum */
result = DirectFunctionCall1(textin, CStringGetDatum(format_str));
/* clean up */
if (format_str) pfree(format_str);
if (force_notnull) pfree(fnn.data);
if (force_quote) pfree(fq.data);
if (eol_str) pfree(nl.data);
return result;
}
/* ALTER TABLE EXCHANGE
*
* NB different signature from non-partitioned table Prep functions.
*/
static void
ATPrepExchange(Relation rel, AlterPartitionCmd *pc)
{
PgPartRule *prule = NULL;
Relation oldrel = NULL;
Relation newrel = NULL;
if (Gp_role == GP_ROLE_DISPATCH)
{
AlterPartitionId *pid = (AlterPartitionId *) pc->partid;
bool is_split;
is_split = ((AlterPartitionCmd *)pc->arg2)->arg2 != NULL;
if (is_split)
return;
prule = get_part_rule(rel, pid, true, true,
CurrentMemoryContext, NULL, false);
if (prule)
{
/* cannot exchange a hash partition */
if ('h' == prule->pNode->part->parkind)
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot exchange HASH partition%s "
"of relation \"%s\"", prule->partIdStr,
RelationGetRelationName(rel))));
}
oldrel = heap_open(prule->topRule->parchildrelid,
AccessExclusiveLock);
newrel = heap_openrv((RangeVar *)pc->arg1,
AccessExclusiveLock);
}
else
{
oldrel = heap_openrv((RangeVar *)pc->partid,
AccessExclusiveLock);
newrel = heap_openrv((RangeVar *)pc->arg1,
AccessExclusiveLock);
}
ATSimplePermissions(rel, false);
ATSimplePermissions(oldrel, false);
ATSimplePermissions(newrel, false);
/* Check that old and new look the same, error if not. */
is_exchangeable(rel, oldrel, newrel, true);
heap_close(oldrel, NoLock);
heap_close(newrel, NoLock);
}
/*
* Return a palloc'd string representing an AlterTableCmd type for use
* in a message pattern like "can't %s a partitioned table". The default
* return string is "alter".
*
* This may pose a challenge for localization.
*/
static
char *alterTableCmdString(AlterTableType subtype)
{
char *cmdstring = NULL;
switch (subtype)
{
case AT_AddColumn: /* add column */
case AT_AddColumnRecurse: /* internal to command/tablecmds.c*/
cmdstring = pstrdup("add a column to");
break;
case AT_ColumnDefault: /* alter column default */
cmdstring = pstrdup("alter a column default of");
break;
case AT_DropNotNull: /* alter column drop not null */
case AT_SetNotNull: /* alter column set not null */
cmdstring = pstrdup("alter a column null setting of");
break;
case AT_SetStatistics: /* alter column statistics */
case AT_SetStorage: /* alter column storage */
break;
case AT_DropColumn: /* drop column */
case AT_DropColumnRecurse: /* internal to commands/tablecmds.c */
cmdstring = pstrdup("drop a column from");
break;
case AT_AddIndex: /* add index */
case AT_ReAddIndex: /* internal to commands/tablecmds.c */
break;
case AT_AddConstraint: /* add constraint */
case AT_AddConstraintRecurse: /* internal to commands/tablecmds.c */
cmdstring = pstrdup("add a constraint to");
break;
case AT_ProcessedConstraint: /* pre-processed add constraint (local in parser/analyze.c) */
break;
case AT_DropConstraint: /* drop constraint */
cmdstring = pstrdup("drop a constraint from");
break;
case AT_DropConstraintQuietly: /* drop constraint, no error/warning (local in commands/tablecmds.c) */
break;
case AT_AlterColumnType: /* alter column type */
cmdstring = pstrdup("alter a column datatype of");
break;
case AT_ChangeOwner: /* change owner */
cmdstring = pstrdup("alter the owner of");
break;
case AT_ClusterOn: /* CLUSTER ON */
case AT_DropCluster: /* SET WITHOUT CLUSTER */
break;
case AT_DropOids: /* SET WITHOUT OIDS */
cmdstring = pstrdup("alter the oid setting of");
break;
case AT_SetTableSpace: /* SET TABLESPACE */
case AT_SetRelOptions: /* SET (...) -- AM specific parameters */
case AT_ResetRelOptions: /* RESET (...) -- AM specific parameters */
break;
case AT_EnableTrig: /* ENABLE TRIGGER name */
case AT_DisableTrig: /* DISABLE TRIGGER name */
case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */
case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */
case AT_EnableTrigUser: /* ENABLE TRIGGER USER */
case AT_DisableTrigUser: /* DISABLE TRIGGER USER */
cmdstring = pstrdup("enable or disable triggers on");
break;
case AT_AddInherit: /* INHERIT parent */
case AT_DropInherit: /* NO INHERIT parent */
cmdstring = pstrdup("alter inheritance on");
break;
case AT_SetDistributedBy: /* SET DISTRIBUTED BY */
break;
case AT_PartAdd: /* Add */
case AT_PartAlter: /* Alter */
case AT_PartCoalesce: /* Coalesce */
case AT_PartDrop: /* Drop */
break;
case AT_PartExchange: /* Exchange */
cmdstring = pstrdup("exchange a part into");
break;
case AT_PartMerge: /* Merge */
cmdstring = pstrdup("merge parts of");
break;
case AT_PartModify: /* Modify */
case AT_PartRename: /* Rename */
case AT_PartSetTemplate: /* Set Subpartition Template */
break;
case AT_PartSplit: /* Split */
cmdstring = pstrdup("split parts of");
break;
case AT_PartTruncate: /* Truncate */
case AT_PartAddInternal: /* CREATE TABLE time partition addition */
break;
}
if ( cmdstring == NULL )
{
cmdstring = pstrdup("alter");
}
return cmdstring;
}
static void
InvokeProtocolValidation(Oid procOid, char *procName, bool iswritable, bool forceCreateDir,
List *locs, List* fmtopts)
{
ExtProtocolValidatorData *validator_data;
FmgrInfo *validator_udf;
FunctionCallInfoData fcinfo;
validator_data = (ExtProtocolValidatorData *) palloc0 (sizeof(ExtProtocolValidatorData));
validator_udf = palloc(sizeof(FmgrInfo));
fmgr_info(procOid, validator_udf);
validator_data->type = T_ExtProtocolValidatorData;
validator_data->url_list = locs;
validator_data->format_opts = fmtopts;
validator_data->forceCreateDir = forceCreateDir;
validator_data->errmsg = NULL;
validator_data->direction = (iswritable ? EXT_VALIDATE_WRITE :
EXT_VALIDATE_READ);
validator_data->action = EXT_VALID_ACT_ARGUMENTS;
InitFunctionCallInfoData(/* FunctionCallInfoData */ fcinfo,
/* FmgrInfo */ validator_udf,
/* nArgs */ 0,
/* Call Context */ (Node *) validator_data,
/* ResultSetInfo */ NULL);
/* invoke validator. if this function returns - validation passed */
FunctionCallInvoke(&fcinfo);
/* We do not expect a null result */
if (fcinfo.isnull)
elog(ERROR, "validator function %u returned NULL",
fcinfo.flinfo->fn_oid);
pfree(validator_data);
pfree(validator_udf);
}