| /* |
| * 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); |
| } |