Files
openGauss-server/src/gausskernel/process/tcop/utility.cpp
2020-08-15 21:01:23 +08:00

11214 lines
426 KiB
C++

/* -------------------------------------------------------------------------
*
* utility.cpp
* Contains functions which control the execution of the POSTGRES utility
* commands. At one time acted as an interface between the Lisp and C
* systems.
*
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
* Portions Copyright (c) 2010-2012 Postgres-XC Development Group
*
* IDENTIFICATION
* src/gausskernel/process/tcop/utility.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "nodes/print.h"
#include "access/reloptions.h"
#include "access/transam.h"
#include "access/twophase.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_synonym.h"
#include "catalog/pgxc_group.h"
#include "catalog/toasting.h"
#include "catalog/cstore_ctlg.h"
#include "commands/alter.h"
#include "commands/async.h"
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/collationcmds.h"
#include "commands/conversioncmds.h"
#include "commands/copy.h"
#include "commands/createas.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/discard.h"
#include "commands/explain.h"
#include "commands/extension.h"
#include "commands/lockcmds.h"
#include "commands/portalcmds.h"
#include "commands/prepare.h"
#include "commands/proclang.h"
#include "commands/schemacmds.h"
#include "commands/seclabel.h"
#include "commands/sec_rls_cmds.h"
#include "commands/sequence.h"
#include "commands/shutdown.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/directory.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "commands/user.h"
#include "commands/vacuum.h"
#include "commands/verify.h"
#include "commands/view.h"
#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "instruments/instr_unique_sql.h"
#include "gaussdb_version.h"
#include "miscadmin.h"
#include "optimizer/cost.h"
#include "optimizer/plancat.h"
#include "nodes/makefuncs.h"
#include "parser/parse_utilcmd.h"
#include "parser/analyze.h"
#include "parser/parse_func.h"
#include "postmaster/autovacuum.h"
#include "postmaster/bgwriter.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteRemove.h"
#include "storage/fd.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
#include "tsearch/ts_locale.h"
#include "utils/acl.h"
#include "utils/extended_statistics.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/tqual.h"
#include "utils/syscache.h"
#include "tsdb/meta_utils.h"
#ifdef PGXC
#include "pgxc/barrier.h"
#include "pgxc/execRemote.h"
#include "pgxc/locator.h"
#include "pgxc/pgxc.h"
#include "optimizer/pgxcplan.h"
#include "pgxc/poolutils.h"
#include "nodes/nodes.h"
#include "pgxc/poolmgr.h"
#include "pgxc/nodemgr.h"
#include "pgxc/groupmgr.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/rel_gs.h"
#include "utils/snapmgr.h"
#include "utils/builtins.h"
#include "utils/inval.h"
#include "catalog/pgxc_node.h"
#include "workload/workload.h"
#include "tsdb/time_bucket.h"
static RemoteQueryExecType ExecUtilityFindNodes(ObjectType object_type, Oid rel_id, bool* is_temp);
static RemoteQueryExecType exec_utility_find_nodes_relkind(Oid rel_id, bool* is_temp);
static RemoteQueryExecType get_nodes_4_comment_utility(CommentStmt* stmt, bool* is_temp, ExecNodes** exec_nodes);
static RemoteQueryExecType get_nodes_4_rules_utility(RangeVar* relation, bool* is_temp);
static void drop_stmt_pre_treatment(
DropStmt* stmt, const char* query_string, bool sent_to_remote, bool* is_temp, RemoteQueryExecType* exec_type);
static void ExecUtilityWithMessage(const char* query_string, bool sent_to_remote, bool is_temp);
static void exec_utility_with_message_parallel_ddl_mode(
const char* query_string, bool sent_to_remote, bool is_temp, const char* first_exec_node, RemoteQueryExecType exec_type);
static bool is_stmt_allowed_in_locked_mode(Node* parse_tree, const char* query_string);
static ExecNodes* assign_utility_stmt_exec_nodes(Node* parse_tree);
#endif
static void add_remote_query_4_alter_stmt(bool is_first_node, AlterTableStmt* atstmt, const char* query_string, List** stmts,
char** drop_seq_string, ExecNodes** exec_nodes);
bool IsVariableinBlackList(const char* name);
/* @hdfs DoVacuumMppTable process MPP local table for command Vacuum/Analyze */
void DoVacuumMppTable(VacuumStmt* stmt, const char* query_string, bool is_top_level, bool sent_to_remote);
/* Hook for plugins to get control in ProcessUtility() */
THR_LOCAL ProcessUtility_hook_type ProcessUtility_hook = NULL;
#ifdef ENABLE_MULTIPLE_NODES
static void analyze_tmptbl_debug_cn(Oid rel_id, Oid main_relid, VacuumStmt* stmt, bool iscommit);
#endif
/* @hdfs Get sheduling message for analyze command */
extern List* CNSchedulingForAnalyze(
unsigned int* totalFilesNum, unsigned int* num_of_dns, Oid foreign_table_id, bool isglbstats);
extern void begin_delta_merge(VacuumStmt* stmt);
#ifdef ENABLE_MULTIPLE_NODES
static void set_dndistinct_coors(VacuumStmt* stmt, int attnum);
#endif
static void attatch_global_info(char** query_string_with_info, VacuumStmt* stmt, const char* query_string, bool has_var,
AnalyzeMode e_analyze_mode, Oid rel_id, char* foreign_tbl_schedul_message = NULL);
static char* get_hybrid_message(ForeignTableDesc* table_desc, VacuumStmt* stmt, char* foreign_tbl_schedul_message);
static bool need_full_dn_execution(const char* group_name);
int SetGTMVacuumFlag(GTM_TransactionKey txn_key, bool is_vacuum);
#ifdef ENABLE_MULTIPLE_NODES
extern void BumpPGXCGlobalVersion();
#endif
extern void check_log_ft_definition(CreateForeignTableStmt* stmt);
extern void ts_check_feature_disable();
/* the hash value of extension script */
#define POSTGIS_VERSION_NUM 2
/* the size of page, unit is kB */
#define PAGE_SIZE 8
#define HALF_AMOUNT 0.5
#define BIG_MEM_RATIO 1.2
#define SMALL_MEM_RATIO 1.1
/* ----------------------------------------------------------------
* report_utility_time
*
* send the finish time of copy and exchange/truncate/drop partition operations to pgstat collector.
* ----------------------------------------------------------------
*/
static void report_utility_time(void* parse_tree)
{
ListCell* lc = NULL;
RangeVar* relation = NULL;
if (u_sess->attr.attr_sql.enable_save_datachanged_timestamp == false)
return;
if (!IS_SINGLE_NODE && !IS_PGXC_COORDINATOR)
return;
if (nodeTag(parse_tree) != T_CopyStmt && nodeTag(parse_tree) != T_AlterTableStmt)
return;
if (nodeTag(parse_tree) == T_CopyStmt) {
CopyStmt* cs = (CopyStmt*)parse_tree;
if (cs->is_from == false) {
ereport(DEBUG1, (errmsg("\"copy to\" found")));
return;
}
relation = cs->relation;
}
if (nodeTag(parse_tree) == T_AlterTableStmt) {
AlterTableStmt* ats = (AlterTableStmt*)parse_tree;
bool found = false;
AlterTableCmd* cmd = NULL;
foreach (lc, ats->cmds) {
cmd = (AlterTableCmd*)lfirst(lc);
if (cmd->subtype == AT_TruncatePartition || cmd->subtype == AT_ExchangePartition ||
cmd->subtype == AT_DropPartition) {
found = true;
}
}
if (found == false) {
ereport(DEBUG1, (errmsg("truncate/exchange/drop partition not found")));
return;
}
relation = ats->relation;
}
if (relation == NULL) {
ereport(DEBUG1, (errmsg("relation is NULL in CopyStmt/AlterTableStmt")));
return;
}
MemoryContext current_ctx = CurrentMemoryContext;
Relation rel = NULL;
PG_TRY();
{
rel = heap_openrv(relation, AccessShareLock);
Oid rid = rel->rd_id;
if (rel->rd_rel->relkind == RELKIND_RELATION && rid >= FirstNormalObjectId) {
if (rel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT ||
rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED) {
pgstat_report_data_changed(rid, STATFLG_RELATION, rel->rd_rel->relisshared);
}
}
heap_close(rel, AccessShareLock);
}
PG_CATCH();
{
(void)MemoryContextSwitchTo(current_ctx);
ErrorData* edata = CopyErrorData();
ereport(DEBUG1, (errmsg("Failed to send data changed time, cause: %s", edata->message)));
FlushErrorState();
FreeErrorData(edata);
if (rel != NULL)
heap_close(rel, AccessShareLock);
}
PG_END_TRY();
}
/*
* CommandIsReadOnly: is an executable query read-only?
*
* This is a much stricter test than we apply for XactReadOnly mode;
* the query must be *in truth* read-only, because the caller wishes
* not to do CommandCounterIncrement for it.
*
* Note: currently no need to support Query nodes here
*/
bool CommandIsReadOnly(Node* parse_tree)
{
if (IsA(parse_tree, PlannedStmt)) {
PlannedStmt* stmt = (PlannedStmt*)parse_tree;
switch (stmt->commandType) {
case CMD_SELECT:
if (stmt->rowMarks != NIL)
return false; /* SELECT FOR UPDATE/SHARE */
else if (stmt->hasModifyingCTE)
return false; /* data-modifying CTE */
else
return true;
case CMD_UPDATE:
case CMD_INSERT:
case CMD_DELETE:
case CMD_MERGE:
return false;
default:
elog(WARNING, "unrecognized commandType: %d", (int)stmt->commandType);
break;
}
}
/* For now, treat all utility commands as read/write */
return false;
}
/*
* check_xact_readonly: is a utility command read-only?
*
* Here we use the loose rules of XactReadOnly mode: no permanent effects
* on the database are allowed.
*/
static void check_xact_readonly(Node* parse_tree)
{
if (!u_sess->attr.attr_common.XactReadOnly)
return;
/*
* Note:Disk space usage reach the threshold causing database open read-only.
* In this case,if xc_maintenance_mode=on T_DropStmt and T_TruncateStmt is allowed
* otherwise,just allow read-only stmt
*/
if ((IS_PGXC_COORDINATOR || IS_PGXC_DATANODE) && IsConnFromCoord())
return;
if (u_sess->attr.attr_common.xc_maintenance_mode) {
switch (nodeTag(parse_tree)) {
case T_DropStmt:
case T_TruncateStmt:
return;
default:
break;
}
}
/*
* Note: Commands that need to do more complicated checking are handled
* elsewhere, in particular COPY and plannable statements do their own
* checking. However they should all call PreventCommandIfReadOnly to
* actually throw the error.
*/
switch (nodeTag(parse_tree)) {
case T_AlterDatabaseStmt:
case T_AlterDatabaseSetStmt:
case T_AlterDomainStmt:
case T_AlterFunctionStmt:
case T_AlterRoleSetStmt:
case T_AlterObjectSchemaStmt:
case T_AlterOwnerStmt:
case T_AlterSeqStmt:
case T_AlterTableStmt:
case T_RenameStmt:
case T_CommentStmt:
case T_DefineStmt:
case T_CreateCastStmt:
case T_CreateConversionStmt:
case T_CreatedbStmt:
case T_CreateDomainStmt:
case T_CreateFunctionStmt:
case T_CreateRoleStmt:
case T_IndexStmt:
case T_CreatePLangStmt:
case T_CreateOpClassStmt:
case T_CreateOpFamilyStmt:
case T_AlterOpFamilyStmt:
case T_RuleStmt:
case T_CreateSchemaStmt:
case T_CreateSeqStmt:
case T_CreateStmt:
case T_CreateTableAsStmt:
case T_CreateTableSpaceStmt:
case T_CreateTrigStmt:
case T_CompositeTypeStmt:
case T_CreateEnumStmt:
case T_CreateRangeStmt:
case T_AlterEnumStmt:
case T_ViewStmt:
case T_DropStmt:
case T_DropdbStmt:
case T_DropTableSpaceStmt:
case T_DropRoleStmt:
case T_GrantStmt:
case T_GrantRoleStmt:
case T_AlterDefaultPrivilegesStmt:
case T_TruncateStmt:
case T_DropOwnedStmt:
case T_ReassignOwnedStmt:
case T_AlterTSDictionaryStmt:
case T_AlterTSConfigurationStmt:
case T_CreateExtensionStmt:
case T_AlterExtensionStmt:
case T_AlterExtensionContentsStmt:
case T_CreateFdwStmt:
case T_AlterFdwStmt:
case T_CreateForeignServerStmt:
case T_AlterForeignServerStmt:
case T_CreateUserMappingStmt:
case T_AlterUserMappingStmt:
case T_DropUserMappingStmt:
case T_AlterTableSpaceOptionsStmt:
case T_CreateForeignTableStmt:
case T_SecLabelStmt:
case T_CreateResourcePoolStmt:
case T_AlterResourcePoolStmt:
case T_DropResourcePoolStmt:
case T_ClusterStmt:
case T_ReindexStmt:
case T_CreateDataSourceStmt:
case T_AlterDataSourceStmt:
case T_CreateDirectoryStmt:
case T_DropDirectoryStmt:
case T_CreateRlsPolicyStmt:
case T_AlterRlsPolicyStmt:
case T_CreateSynonymStmt:
case T_DropSynonymStmt:
PreventCommandIfReadOnly(CreateCommandTag(parse_tree));
break;
case T_VacuumStmt: {
VacuumStmt* stmt = (VacuumStmt*)parse_tree;
/* on verify mode, do nothing */
if (!(stmt->options & VACOPT_VERIFY)) {
PreventCommandIfReadOnly(CreateCommandTag(parse_tree));
}
break;
}
case T_AlterRoleStmt: {
AlterRoleStmt* stmt = (AlterRoleStmt*)parse_tree;
if (!(DO_NOTHING != stmt->lockstatus && t_thrd.postmaster_cxt.HaShmData->current_mode == STANDBY_MODE)) {
PreventCommandIfReadOnly(CreateCommandTag(parse_tree));
}
break;
}
default:
/* do nothing */
break;
}
}
/*
* PreventCommandIfReadOnly: throw error if XactReadOnly
*
* This is useful mainly to ensure consistency of the error message wording;
* most callers have checked XactReadOnly for themselves.
*/
void PreventCommandIfReadOnly(const char* cmd_name)
{
if (u_sess->attr.attr_common.XactReadOnly)
ereport(ERROR,
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
/* translator: %s is name of a SQL command, eg CREATE */
errmsg("cannot execute %s in a read-only transaction", cmd_name)));
}
/*
* PreventCommandDuringRecovery: throw error if RecoveryInProgress
*
* The majority of operations that are unsafe in a Hot Standby slave
* will be rejected by XactReadOnly tests. However there are a few
* commands that are allowed in "read-only" xacts but cannot be allowed
* in Hot Standby mode. Those commands should call this function.
*/
void PreventCommandDuringRecovery(const char* cmd_name)
{
if (RecoveryInProgress())
ereport(ERROR,
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
/* translator: %s is name of a SQL command, eg CREATE */
errmsg("cannot execute %s during recovery", cmd_name)));
}
/*
* CheckRestrictedOperation: throw error for hazardous command if we're
* inside a security restriction context.
*
* This is needed to protect session-local state for which there is not any
* better-defined protection mechanism, such as ownership.
*/
static void CheckRestrictedOperation(const char* cmd_name)
{
if (InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
/* translator: %s is name of a SQL command, eg PREPARE */
errmsg("cannot execute %s within security-restricted operation", cmd_name)));
}
/*
* @NodeGroup Support
* @Description: Determine the list objects is in same node group
*
* @Return: 'true' in same group, 'false' in different group
*/
static bool DropObjectsInSameNodeGroup(DropStmt* stmt)
{
ListCell* cell = NULL;
ListCell* cell2 = NULL;
Oid group_oid = InvalidOid;
Oid goid = InvalidOid;
bool find_first = false;
ObjectType object_type = stmt->removeType;
Oid ins_group_oid;
if (stmt->objects == NIL || list_length(stmt->objects) == 1) {
return true;
}
ins_group_oid = ng_get_installation_group_oid();
/* Scanning dropping list to error-out un-allowed cases */
foreach (cell, stmt->objects) {
ObjectAddress address;
List* objname = (List*)lfirst(cell);
List* objargs = NIL;
Relation relation = NULL;
if (stmt->arguments) {
cell2 = (!cell2 ? list_head(stmt->arguments) : lnext(cell2));
objargs = (List*)lfirst(cell2);
}
/* Get an ObjectAddress for the object. */
address = get_object_address(stmt->removeType, objname, objargs, &relation, AccessShareLock, stmt->missing_ok);
/* Issue NOTICE if supplied object was not found. */
if (!OidIsValid(address.objectId)) {
continue;
}
/* Fetching group_oid for given relation */
if (object_type == OBJECT_FUNCTION) {
goid = GetFunctionNodeGroupByFuncid(address.objectId);
if (!OidIsValid(goid))
goid = ins_group_oid;
} else if (object_type == OBJECT_SEQUENCE) {
Oid tableId = InvalidOid;
int32 colId;
if (!sequenceIsOwned(address.objectId, &tableId, &colId))
goid = ins_group_oid;
else
goid = ng_get_baserel_groupoid(tableId, RELKIND_RELATION);
}
/* Close relation if necessary */
if (relation)
relation_close(relation, AccessShareLock);
if (!find_first) {
group_oid = goid;
find_first = true;
continue;
}
if (goid != group_oid) {
return false;
}
}
return true;
}
/*
* @NodeGroup Support
* @Description: Get same node group for grant objects,
* Only for tables, foreign tables, sequences and functions,
* Caller should not call GrantStmtGetNodeGroup for other object type.
*
* @Return: InvalidOid means different group, or same group oid.
*/
static Oid GrantStmtGetNodeGroup(GrantStmt* stmt)
{
Oid goid = InvalidOid;
Oid ins_group_oid = InvalidOid;
List* oids = NIL;
bool find_first = false;
ListCell* cell = NULL;
Oid group_oid = InvalidOid;
/* Collect the OIDs of the target objects */
oids = GrantStmtGetObjectOids(stmt);
ins_group_oid = ng_get_installation_group_oid();
/* Scanning dropping list to error-out un-allowed cases */
foreach (cell, oids) {
Oid object_oid = lfirst_oid(cell);
char relkind;
/* Issue NOTICE if supplied object was not found. */
if (!OidIsValid(object_oid)) {
continue;
}
if (stmt->objtype == ACL_OBJECT_FUNCTION) {
goid = GetFunctionNodeGroupByFuncid(object_oid);
if (!OidIsValid(goid))
goid = ins_group_oid;
} else if (stmt->objtype == ACL_OBJECT_RELATION || stmt->objtype == ACL_OBJECT_SEQUENCE) {
relkind = get_rel_relkind(object_oid);
/* Fetching group_oid for given relation */
if (relkind == RELKIND_RELATION || relkind == RELKIND_FOREIGN_TABLE) {
goid = ng_get_baserel_groupoid(object_oid, RELKIND_RELATION);
} else {
/* maybe views, sequences */
continue;
}
} else {
/* Should not enter here */
Assert(false);
}
if (!find_first) {
group_oid = goid;
find_first = true;
continue;
}
if (goid != group_oid) {
list_free(oids);
return InvalidOid;
}
}
list_free(oids);
return find_first ? group_oid : ins_group_oid;
}
/*
* @NodeGroup Support
* @Description: Create ExecNodes according node group oid.
*
* @Return: ExecNodes.
*/
static ExecNodes* GetNodeGroupExecNodes(Oid group_oid)
{
ExecNodes* exec_nodes = NULL;
Oid* members = NULL;
int nmembers;
bool need_full_dn = false;
exec_nodes = makeNode(ExecNodes);
if (!in_logic_cluster()) {
char* group_name = get_pgxc_groupname(group_oid);
if (group_name == NULL) {
ereport(ERROR,
(errmodule(MOD_EXECUTOR),
errcode(ERRCODE_DATA_EXCEPTION),
errmsg("computing nodegroup is not a valid group.")));
}
need_full_dn = need_full_dn_execution(group_name);
pfree_ext(group_name);
}
if (need_full_dn) {
exec_nodes->nodeList = GetAllDataNodes();
return exec_nodes;
}
nmembers = get_pgxc_groupmembers_redist(group_oid, &members);
exec_nodes->nodeList = GetNodeGroupNodeList(members, nmembers);
pfree_ext(members);
return exec_nodes;
}
/*
* @NodeGroup Support
* @Description: Get exec nodes according table that own the sequence.
*
* @Return: exec nodes of the sequence.
*/
static ExecNodes* GetOwnedByNodes(Node* seq_stmt)
{
Oid goid;
Oid table_id;
List* owned_by = NIL;
List* rel_name = NIL;
ListCell* option = NULL;
int nnames;
RangeVar* rel = NULL;
List* options = NULL;
Assert(seq_stmt != NULL);
if (IsA(seq_stmt, CreateSeqStmt))
options = ((CreateSeqStmt*)seq_stmt)->options;
else if (IsA(seq_stmt, AlterSeqStmt))
options = ((AlterSeqStmt*)seq_stmt)->options;
else
return NULL;
foreach (option, options) {
DefElem* defel = (DefElem*)lfirst(option);
if (strcmp(defel->defname, "owned_by") == 0) {
owned_by = defGetQualifiedName(defel);
break;
}
}
if (owned_by == NULL)
return NULL;
nnames = list_length(owned_by);
Assert(nnames > 0);
if (nnames == 1) {
/* Must be OWNED BY NONE */
return NULL;
}
/* Separate rel_name */
rel_name = list_truncate(list_copy(owned_by), nnames - 1);
rel = makeRangeVarFromNameList(rel_name);
if (rel->schemaname != NULL)
table_id = get_valid_relname_relid(rel->schemaname, rel->relname);
else
table_id = RelnameGetRelid(rel->relname);
Assert(OidIsValid(table_id));
/* Fetching group_oid for given relation */
goid = get_pgxc_class_groupoid(table_id);
if (!OidIsValid(goid))
return NULL;
return GetNodeGroupExecNodes(goid);
}
#ifdef ENABLE_MULTIPLE_NODES
/*
* @NodeGroup Support
* @Description: Get exec nodes according table that own the sequence.
*
* @Return: exec nodes of the sequence.
*/
static ExecNodes* GetSequenceNodes(RangeVar* sequence, bool missing_ok)
{
Oid goid;
Oid seq_id;
Oid table_id = InvalidOid;
int32 col_id = 0;
seq_id = RangeVarGetRelid(sequence, NoLock, missing_ok);
if (!OidIsValid(seq_id))
return NULL;
if (!sequenceIsOwned(seq_id, &table_id, &col_id))
return NULL;
/* Fetching group_oid for given relation */
goid = get_pgxc_class_groupoid(table_id);
if (!OidIsValid(goid))
return NULL;
return GetNodeGroupExecNodes(goid);
}
#endif
/*
* @NodeGroup Support
* @Description: Get create function query string in datanodes.
*
* @Return: new query string for sql language in logic cluster, or old query string.
*/
static const char* GetCreateFuncStringInDN(CreateFunctionStmt* stmt, const char* query_string)
{
errno_t rc;
ListCell* option = NULL;
bool is_sql_lang = false;
size_t str_len;
char* tmp = NULL;
const char* query_str = query_string;
if (!in_logic_cluster() || !u_sess->attr.attr_sql.check_function_bodies)
return query_string;
foreach (option, stmt->options) {
DefElem* defel = (DefElem*)lfirst(option);
if (strcmp(defel->defname, "language") == 0) {
if (strcmp(strVal(defel->arg), "sql") == 0) {
is_sql_lang = true;
break;
}
}
}
if (!is_sql_lang)
return query_string;
str_len = strlen(query_string) + 128;
tmp = (char*)palloc(str_len);
rc = snprintf_s(
tmp, str_len, str_len - 1, "SET check_function_bodies = off;%s;SET check_function_bodies = on;", query_string);
securec_check_ss(rc, "\0", "\0");
query_str = tmp;
return query_str;
}
/*
* @NodeGroup Support
* @Description: Determine the list objects is in same node group
*
* @Return: 'true' in same group, 'false' in different group
*/
static ExecNodes* GetFunctionNodes(Oid func_id)
{
Oid goid;
/* Fetching group_oid for given relation */
goid = GetFunctionNodeGroupByFuncid(func_id);
if (!OidIsValid(goid))
return NULL;
return GetNodeGroupExecNodes(goid);
}
/*
* @NodeGroup Support
* @Description: Determine the list objects is in same node group
*
* @Return: 'true' in same group, 'false' in different group
*/
static ExecNodes* GetDropFunctionNodes(DropStmt* stmt)
{
ListCell* cell = NULL;
ListCell* cell2 = NULL;
if (stmt->objects == NIL) {
return NULL;
}
/* Scanning dropping list to error-out un-allowed cases */
foreach (cell, stmt->objects) {
ObjectAddress address;
List* obj_name = (List*)lfirst(cell);
List* obj_args = NIL;
Relation relation = NULL;
if (stmt->arguments) {
cell2 = (!cell2 ? list_head(stmt->arguments) : lnext(cell2));
obj_args = (List*)lfirst(cell2);
}
/* Get an ObjectAddress for the object. */
address = get_object_address(stmt->removeType, obj_name, obj_args, &relation, AccessShareLock, stmt->missing_ok);
if (relation)
heap_close(relation, AccessShareLock);
/* Issue NOTICE if supplied object was not found. */
if (!OidIsValid(address.objectId)) {
continue;
}
return GetFunctionNodes(address.objectId);
}
return NULL;
}
/*
* Check whether current user can create table in logic cluster.
*/
static void check_logic_cluster_create_priv(Oid group_oid, const char* group_name)
{
Oid redist_oid;
Oid current_group_oid;
const char* redist_group_name = NULL;
char group_kind = get_pgxc_groupkind(group_oid);
if (group_kind == 'i' || group_kind == 'e') {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unable to create table on installation group and elastic group "
"in logic cluster.")));
}
if (superuser())
return;
/* check whether current user attach to logic cluster,if not return ERROR */
current_group_oid = get_current_lcgroup_oid();
if (group_oid == current_group_oid) {
return;
}
if (!OidIsValid(current_group_oid)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("User \"%s\" need to attach to logic cluster \"%s\" to create table.",
GetUserNameFromId(GetUserId()),
group_name)));
return;
}
redist_group_name = PgxcGroupGetInRedistributionGroup();
redist_oid = get_pgxc_groupoid(redist_group_name);
if (redist_oid == current_group_oid) {
if (group_oid == PgxcGroupGetRedistDestGroupOid()) {
return;
}
}
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("User \"%s\" have no privilege to create table on logic cluster \"%s\".",
GetUserNameFromId(GetUserId()),
group_name)));
}
/*
* append_internal_data_to_query
* append internalData to query_string
*
* internalData: the string needed to append the the end of query_string
* query_string: original source text of command
*
* If there are some semicolons in the end of query_string, change these semicolons to space
*/
static char* append_internal_data_to_query(char* internal_data, const char* query_string)
{
int i;
StringInfoData str;
initStringInfo(&str);
Assert(query_string != NULL);
Assert(internal_data != NULL);
appendStringInfoString(&str, query_string);
/* remove the last semicolon if exist. */
for (i = str.len - 1; i >= 0; i--) {
if (str.data[i] == ';') {
str.data[i] = ' ';
continue;
}
if (!isspace(str.data[i]))
break;
}
appendStringInfo(&str, " INTERNAL DATA %s ;", internal_data);
return str.data;
}
/*
* assemble_create_sequence_msg
* assemble CREATE SEQUENCE query string for create sequences.
*
* stmts: CreateSeqStmt list
* uuids: uuids of sequences
*
* return string including multiple CREATE SEQUENCE statements.
*/
static char* assemble_create_sequence_msg(List* stmts, List* uuids)
{
char* msg = NULL;
ListCell* l = NULL;
StringInfoData str;
initStringInfo(&str);
foreach (l, stmts) {
Node* stmt = (Node*)lfirst(l);
if (IsA(stmt, CreateSeqStmt)) {
char query[256];
errno_t rc = EOK;
CreateSeqStmt* create_seq_stmt = (CreateSeqStmt*)stmt;
const char* schema_name = NULL;
const char* seq_name = quote_identifier(create_seq_stmt->sequence->relname);
if (create_seq_stmt->sequence->schemaname && create_seq_stmt->sequence->schemaname[0])
schema_name = quote_identifier(create_seq_stmt->sequence->schemaname);
if (schema_name == NULL) {
rc = snprintf_s(query, sizeof(query), sizeof(query) - 1, "CREATE SEQUENCE %s;", seq_name);
} else {
rc = snprintf_s(query, sizeof(query), sizeof(query) - 1, "CREATE SEQUENCE %s.%s;", schema_name, seq_name);
}
securec_check_ss(rc, "\0", "\0");
appendStringInfoString(&str, query);
if (OidIsValid(create_seq_stmt->ownerId)) {
char* rol_name = GetUserNameFromId(create_seq_stmt->ownerId);
/* Don't need to check rol_name by quote_identifier. */
if (schema_name == NULL) {
rc = snprintf_s(
query, sizeof(query), sizeof(query) - 1, "ALTER SEQUENCE %s OWNER TO %s;", seq_name, rol_name);
} else {
rc = snprintf_s(query,
sizeof(query),
sizeof(query) - 1,
"ALTER SEQUENCE %s.%s OWNER TO %s;",
schema_name,
seq_name,
rol_name);
}
securec_check_ss(rc, "\0", "\0");
appendStringInfoString(&str, query);
pfree_ext(rol_name);
}
if (schema_name != NULL && schema_name != create_seq_stmt->sequence->schemaname)
pfree_ext(schema_name);
if (seq_name != create_seq_stmt->sequence->relname)
pfree_ext(seq_name);
}
}
char* uuid_info = nodeToString(uuids);
AssembleHybridMessage(&msg, str.data, uuid_info);
pfree_ext(uuid_info);
pfree_ext(str.data);
return msg;
}
/*
* assemble_drop_sequence_msg
* assemble DROP SEQUENCE query string.
*
* seqs: sequence oid list.
* str: out parameter, return drop sequence string.
*
*/
static void assemble_drop_sequence_msg(List* seqs, StringInfo str)
{
Oid seq_id;
ListCell* s = NULL;
const char* rel_name = NULL;
const char* nsp_name = NULL;
const char* schema_name = NULL;
const char* seq_name = NULL;
char query[256];
errno_t rc = EOK;
foreach (s, seqs) {
seq_id = lfirst_oid(s);
rel_name = get_rel_name(seq_id);
nsp_name = get_namespace_name(get_rel_namespace(seq_id), true);
schema_name = quote_identifier(nsp_name);
seq_name = quote_identifier(rel_name);
Assert(seq_name != NULL && schema_name != NULL);
rc = snprintf_s(
query, sizeof(query), sizeof(query) - 1, "DROP SEQUENCE IF EXISTS %s.%s CASCADE;", schema_name, seq_name);
securec_check_ss(rc, "\0", "\0");
if (str->data == NULL) {
initStringInfo(str);
}
appendStringInfoString(str, query);
if (schema_name != nsp_name)
pfree_ext(schema_name);
if (seq_name != rel_name)
pfree_ext(seq_name);
pfree_ext(rel_name);
pfree_ext(nsp_name);
}
}
#ifdef ENABLE_MULTIPLE_NODES
/*
* get_drop_sequence_msg
* Get SQL including multiple DROP SEQUENCE;
*
* objects: the normal table oid list.
*
* If all tables in objects have not owned sequences, return null;
* or the return string include DROP SEQUENCE for all sequences
* owned by these tables.
*/
static char* get_drop_sequence_msg(List* objects)
{
ListCell* l = NULL;
StringInfoData str;
List* seqs = NULL;
Oid rel_id;
str.data = NULL;
foreach (l, objects) {
rel_id = lfirst_oid(l);
seqs = getOwnedSequences(rel_id);
if (seqs == NULL)
continue;
assemble_drop_sequence_msg(seqs, &str);
}
return str.data;
}
#endif
/*
* make_remote_query_for_seq
* return RemoteQuery for executing query_string in nodes without excluded_nodes.
*
* excluded_nodes: the node list that don't need to execute query.
* query_string: SQL command to execute.
*
*/
static RemoteQuery* make_remote_query_for_seq(ExecNodes* excluded_nodes, char* query_string)
{
List* all_nodes = GetAllDataNodes();
List* exec_nodes = list_difference_int(all_nodes, excluded_nodes->nodeList);
RemoteQuery* step = makeNode(RemoteQuery);
step->combine_type = COMBINE_TYPE_SAME;
step->sql_statement = (char*)query_string;
step->exec_type = EXEC_ON_DATANODES;
step->is_temp = false;
step->exec_nodes = makeNode(ExecNodes);
step->exec_nodes->nodeList = exec_nodes;
list_free(all_nodes);
return step;
}
#ifdef ENABLE_MULTIPLE_NODES
/*
* exec_remote_query_4_seq
* Execute query_string in nodes without excluded_nodes.
*
* excluded_nodes: the node list that don't need to execute query.
* query_string: SQL command to execute.
* uuid: Sequence uuid, if uuid is zero, ignore the parameter.
*
*/
static void exec_remote_query_4_seq(ExecNodes* excluded_nodes, char* query_string, int64 uuid)
{
char* query_string_with_uuid = query_string;
if (uuid != INVALIDSEQUUID) {
Const* n = makeConst(INT8OID, -1, InvalidOid, sizeof(int64), Int64GetDatum(uuid), false, true);
char* uuid_info = nodeToString(n);
AssembleHybridMessage(&query_string_with_uuid, query_string, uuid_info);
pfree_ext(n);
pfree_ext(uuid_info);
}
RemoteQuery* step = make_remote_query_for_seq(excluded_nodes, query_string_with_uuid);
ExecRemoteUtility(step);
list_free(step->exec_nodes->nodeList);
pfree_ext(step->exec_nodes);
pfree_ext(step);
}
/*
* drop_sequence_4_node_group
* Drop sequences not in datanodes (in parameter excluded_nodes).
*
* stmt: DropStmt struct, we get all dropped tables from stmt parameter.
* excluded_nodes: the node list that don't need to execute query.
*
* The function is used to DROP TABLE in nodegroup.
* DROP TABLE don't drop sequences in datanodes that don't belong to the NodeGroup,
* so we need this function to drop these sequences in the execluded nodes.
*/
static void drop_sequence_4_node_group(DropStmt* stmt, ExecNodes* excluded_nodes)
{
char* query_string = NULL;
List* objects = NIL;
ListCell* cell = NULL;
Oid table_oid;
if (excluded_nodes == NULL)
return;
if (stmt->removeType != OBJECT_TABLE)
return;
if (excluded_nodes->nodeList->length == u_sess->pgxc_cxt.NumDataNodes)
return;
foreach (cell, stmt->objects) {
RangeVar* rel = NULL;
rel = makeRangeVarFromNameList((List*)lfirst(cell));
table_oid = RangeVarGetRelid(rel, NoLock, true);
if (!OidIsValid(table_oid)) {
continue;
}
if (get_rel_relkind(table_oid) != RELKIND_RELATION) {
continue;
}
objects = lappend_oid(objects, table_oid);
}
query_string = get_drop_sequence_msg(objects);
if (query_string == NULL)
return;
exec_remote_query_4_seq(excluded_nodes, query_string, INVALIDSEQUUID);
pfree_ext(query_string);
}
/*
* alter_sequence_all_nodes
* ALTER sequences not in datanodes (in parameter excluded_nodes).
*
* seq_stmt: AlterSeqStmt struct, we get all altered sequences from this parameter.
* excluded_nodes: the node list that don't need to execute query.
*
* The function is used to ALTER SEQUENCE in nodegroup.
*
* Consider the scenario:
* Sequence s1 is owned by t1 and increment is 1;
* table t1 belong to nodegroup1 (datanode1,datanode2);
* table t2 belong to nodegroup2 (datanode3,datanode4).
* If execute ALTER SEQUENCE s1 increment by 2 owned by t2, how we do ?
*
* We have to call alter_sequence_all_nodes in nodes excluding nodegroup2, modify
* increment and set owned by none first. Then we ALTER SEQUENCE in nodegroup2,
* modify increment and set owned by t2. The final result is: increment in all nodes is 2,
* owned by is none excluding nodegroup2, owned by t2 in nodegroup2.
*/
static void alter_sequence_all_nodes(AlterSeqStmt* seq_stmt, ExecNodes* exclude_nodes)
{
if (exclude_nodes == NULL)
return;
if (exclude_nodes->nodeList->length == u_sess->pgxc_cxt.NumDataNodes)
return;
RangeVar* sequence = seq_stmt->sequence;
/* Optimization for ALTER SEQUENCE OWNED BY. */
if (seq_stmt->options->length == 1) {
DefElem* def_elem = (DefElem*)linitial(seq_stmt->options);
if (strcmp(def_elem->defname, "owned_by") == 0) {
ExecNodes* exec_nodes = GetSequenceNodes(sequence, true);
if (exec_nodes == NULL)
return;
/* Judge whether current sequence nodegroup is same as new sequence nodegroup */
List* diff = list_difference_int(exec_nodes->nodeList, exclude_nodes->nodeList);
if (diff == NULL) {
FreeExecNodes(&exec_nodes);
return;
}
list_free(diff);
}
}
/* Get ALTER SEQUENCE statement with OWNED BY NONE */
char* msg = deparse_alter_sequence((Node*)seq_stmt, true);
exec_remote_query_4_seq(exclude_nodes, msg, INVALIDSEQUUID);
pfree_ext(msg);
}
#endif
/*
* get_drop_seq_query_string
* Get DROP SEQUENCE string list (used in ALTER TABLE ... DROP COLUMN).
*
* stmt: AlterTableStmt struct, we get all dropped sequences from this parameter.
* rel_id: the normal table oid. We can get it from AlterTableStmt, but we expect
* caller can give the oid for optimization.
*
* The function is used to ALTER TABLE ... DROP COLUMN in nodegroup.
* When drop column with owned sequence for nodegroup table, DROP TABLE
* is executed only in nodes in the nodegroup, sequence in other datanodes won't
* be dropped. We have to call get_drop_seq_query_string to assemble DROP SEQUENCE
* statements, execute these sqls by exec_remote_query_4_seq in other datanodes.
*/
static char* get_drop_seq_query_string(AlterTableStmt* stmt, Oid rel_id)
{
HeapTuple tuple;
char* col_name = NULL;
ListCell* cell = NULL;
AlterTableCmd* cmd = NULL;
List* seq_list = NIL;
List* attr_list = NIL;
foreach (cell, stmt->cmds) {
cmd = (AlterTableCmd*)lfirst(cell);
if (cmd->subtype == AT_DropColumn || cmd->subtype == AT_DropColumnRecurse) {
Form_pg_attribute target_att;
col_name = cmd->name;
/*
* get the number of the attribute
*/
tuple = SearchSysCacheAttName(rel_id, col_name);
if (!HeapTupleIsValid(tuple))
continue;
target_att = (Form_pg_attribute)GETSTRUCT(tuple);
attr_list = lappend_int(attr_list, target_att->attnum);
ReleaseSysCache(tuple);
}
}
if (attr_list != NIL) {
StringInfoData str;
seq_list = getOwnedSequences(rel_id, attr_list);
list_free(attr_list);
if (seq_list != NULL) {
str.data = NULL;
assemble_drop_sequence_msg(seq_list, &str);
return str.data;
}
}
return NULL;
}
/*
* ProcessUtility
* general utility function invoker
*
* parse_tree: the parse tree for the utility statement
* query_string: original source text of command
* params: parameters to use during execution
* is_top_level: true if executing a "top level" (interactively issued) command
* dest: where to send results
* completion_tag: points to a buffer of size COMPLETION_TAG_BUFSIZE
* in which to store a command completion status string.
*
* Notes: as of PG 8.4, caller MUST supply a query_string; it is not
* allowed anymore to pass NULL. (If you really don't have source text,
* you can pass a constant string, perhaps "(query not available)".)
*
* completion_tag is only set nonempty if we want to return a nondefault status.
*
* completion_tag may be NULL if caller doesn't want a status string.
*/
void ProcessUtility(Node* parse_tree, const char* query_string, ParamListInfo params, bool is_top_level, DestReceiver* dest,
#ifdef PGXC
bool sent_to_remote,
#endif /* PGXC */
char* completion_tag)
{
/* required as of 8.4 */
AssertEreport(query_string != NULL, MOD_EXECUTOR, "query string is NULL");
/*
* We provide a function hook variable that lets loadable plugins get
* control when ProcessUtility is called. Such a plugin would normally
* call standard_ProcessUtility().
*/
if (ProcessUtility_hook)
(*ProcessUtility_hook)(parse_tree,
query_string,
params,
is_top_level,
dest,
#ifdef PGXC
sent_to_remote,
#endif /* PGXC */
completion_tag);
else
standard_ProcessUtility(parse_tree,
query_string,
params,
is_top_level,
dest,
#ifdef PGXC
sent_to_remote,
#endif /* PGXC */
completion_tag);
}
// @Temp Table.
// Check if a SQL stmt contain temp tables, to decide if lock other coordinators
bool isAllTempObjects(Node* parse_tree, const char* query_string, bool sent_to_remote)
{
switch (nodeTag(parse_tree)) {
case T_CreateStmt: {
CreateStmt* stmt = (CreateStmt*)parse_tree;
char* temp_namespace_name = NULL;
if (stmt->relation) {
if (OidIsValid(u_sess->catalog_cxt.myTempNamespace) && stmt->relation->schemaname != NULL) {
temp_namespace_name = get_namespace_name(u_sess->catalog_cxt.myTempNamespace);
if (temp_namespace_name != NULL &&
strcmp(stmt->relation->schemaname, temp_namespace_name) == 0)
return true;
}
if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
return true;
}
break;
}
case T_ViewStmt: {
return IsViewTemp((ViewStmt*)parse_tree, query_string);
break;
}
case T_AlterFunctionStmt: // @Temp Table. alter function's lock check is moved in AlterFunction
{
return IsFunctionTemp((AlterFunctionStmt*)parse_tree);
break;
}
case T_DropStmt:
switch (((DropStmt*)parse_tree)->removeType) {
case OBJECT_INDEX:
case OBJECT_TABLE:
case OBJECT_VIEW:
case OBJECT_SEQUENCE: {
bool is_all_temp = false;
RemoteQueryExecType exec_type = EXEC_ON_ALL_NODES;
DropStmt* new_stmt = (DropStmt*)copyObject(parse_tree);
new_stmt->missing_ok = true;
/* Check restrictions on objects dropped */
drop_stmt_pre_treatment(new_stmt, query_string, sent_to_remote, &is_all_temp, &exec_type);
pfree_ext(new_stmt);
return is_all_temp;
}
case OBJECT_SCHEMA: {
ListCell* cell = NULL;
foreach (cell, ((DropStmt*)parse_tree)->objects) {
List* obj_name = (List*)lfirst(cell);
char* name = NameListToString(obj_name);
if (isTempNamespaceName(name) || isToastTempNamespaceName(name))
return true;
}
break;
}
default: {
bool is_all_temp = false;
RemoteQueryExecType exec_type = EXEC_ON_ALL_NODES;
DropStmt* new_stmt = (DropStmt*)copyObject(parse_tree);
new_stmt->missing_ok = true;
/* Check restrictions on objects dropped */
drop_stmt_pre_treatment(new_stmt, query_string, sent_to_remote, &is_all_temp, &exec_type);
pfree_ext(new_stmt);
return is_all_temp;
}
}
break;
case T_CreateSchemaStmt: {
char* nsp_name = ((CreateSchemaStmt*)parse_tree)->schemaname;
if (isTempNamespaceName(nsp_name) || isToastTempNamespaceName(nsp_name))
return true;
break;
}
case T_AlterTableStmt: {
AlterTableStmt* alter_stmt = (AlterTableStmt*)parse_tree;
Oid rel_id;
LOCKMODE lock_mode;
/*
* Figure out lock mode, and acquire lock. This also does
* basic permissions checks, so that we won't wait for a lock
* on (for example) a relation on which we have no
* permissions.
*/
lock_mode = AlterTableGetLockLevel(alter_stmt->cmds);
rel_id = AlterTableLookupRelation(alter_stmt, lock_mode, true);
if (OidIsValid(rel_id) && IsTempTable(rel_id)) {
return true;
}
if (OidIsValid(rel_id) && u_sess->attr.attr_sql.enable_parallel_ddl)
UnlockRelationOid(rel_id, lock_mode);
break;
}
case T_RenameStmt: {
RenameStmt* stmt = (RenameStmt*)parse_tree;
bool is_all_temp = false;
if (stmt->relation) {
/*
* When a relation is defined, it is possible that this object does
* not exist but an IF EXISTS clause might be used. So we do not do
* any error check here but block the access to remote nodes to
* this object as it does not exisy
*/
Oid rel_id = RangeVarGetRelid(stmt->relation, AccessShareLock, true);
if (OidIsValid(rel_id)) {
(void)ExecUtilityFindNodes(stmt->renameType, rel_id, &is_all_temp);
UnlockRelationOid(rel_id, AccessShareLock);
}
return is_all_temp;
}
break;
}
case T_AlterObjectSchemaStmt: {
AlterObjectSchemaStmt* stmt = (AlterObjectSchemaStmt*)parse_tree;
bool is_all_temp = false;
/* Try to use the object relation if possible */
if (stmt->relation) {
/*
* When a relation is defined, it is possible that this object does
* not exist but an IF EXISTS clause might be used. So we do not do
* any error check here but block the access to remote nodes to
* this object as it does not exisy
*/
Oid rel_id = RangeVarGetRelid(stmt->relation, AccessShareLock, true);
if (OidIsValid(rel_id)) {
(void)ExecUtilityFindNodes(stmt->objectType, rel_id, &is_all_temp);
UnlockRelationOid(rel_id, AccessShareLock);
}
return is_all_temp;
}
break;
}
case T_CommentStmt: {
bool is_all_temp = false;
CommentStmt* stmt = (CommentStmt*)parse_tree;
(void)get_nodes_4_comment_utility(stmt, &is_all_temp, NULL);
return is_all_temp;
}
case T_GrantStmt: {
GrantStmt* stmt = (GrantStmt*)parse_tree;
bool is_temp = false;
bool has_temp = false;
bool has_nontemp = false;
/* Launch GRANT on Coordinator if object is a sequence */
if ((stmt->objtype == ACL_OBJECT_RELATION && stmt->targtype == ACL_TARGET_OBJECT)) {
/*
* In case object is a relation, differenciate the case
* of a sequence, a view and a table
*/
ListCell* cell = NULL;
/* Check the list of objects */
foreach (cell, stmt->objects) {
RangeVar* rel_var = (RangeVar*)lfirst(cell);
Oid rel_id = RangeVarGetRelid(rel_var, NoLock, true);
/* Skip if object does not exist */
if (!OidIsValid(rel_id)) {
continue;
}
(void)exec_utility_find_nodes_relkind(rel_id, &is_temp);
if (is_temp) {
has_temp = true;
} else {
has_nontemp = true;
}
if (has_temp && has_nontemp) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Grant not supported for TEMP and non-TEMP objects together"),
errdetail("You should separate TEMP and non-TEMP objects")));
}
}
return has_temp;
}
break;
}
case T_IndexStmt: {
IndexStmt* stmt = (IndexStmt*)parse_tree;
Oid rel_id;
bool is_temp = false;
/* INDEX on a temporary table cannot use 2PC at commit */
rel_id = RangeVarGetRelidExtended(stmt->relation, AccessShareLock, true, false, false, true, NULL, NULL);
if (OidIsValid(rel_id)) {
(void)ExecUtilityFindNodes(OBJECT_INDEX, rel_id, &is_temp);
UnlockRelationOid(rel_id, AccessShareLock);
}
return is_temp;
}
case T_RuleStmt: {
bool is_temp = false;
(void)get_nodes_4_rules_utility(((RuleStmt*)parse_tree)->relation, &is_temp);
return is_temp;
}
case T_CreateSeqStmt: {
CreateSeqStmt* stmt = (CreateSeqStmt*)parse_tree;
bool is_temp = false;
/* In case this query is related to a SERIAL execution, just bypass */
if (!stmt->is_serial)
is_temp = stmt->sequence->relpersistence == RELPERSISTENCE_TEMP;
return is_temp;
}
case T_CreateTrigStmt: {
CreateTrigStmt* stmt = (CreateTrigStmt*)parse_tree;
bool is_temp = false;
Oid rel_id = RangeVarGetRelidExtended(stmt->relation, NoLock, false, false, false, true, NULL, NULL);
if (OidIsValid(rel_id))
(void)ExecUtilityFindNodes(OBJECT_TABLE, rel_id, &is_temp);
return is_temp;
}
case T_AlterSeqStmt:
case T_AlterOwnerStmt:
default:
break;
}
return false;
}
/* Find the first execute CN on parallel_ddl_mode. */
char* find_first_exec_cn()
{
char* result = NULL;
result = u_sess->pgxc_cxt.co_handles[0].remoteNodeName;
for (int i = 1; i < u_sess->pgxc_cxt.NumCoords; i++) {
result = (strcmp(u_sess->pgxc_cxt.co_handles[i].remoteNodeName, result) < 0) ?
u_sess->pgxc_cxt.co_handles[i].remoteNodeName :
result;
}
return result;
}
/* Find with(hashbucket) options in all stmts */
bool find_hashbucket_options(List* stmts)
{
ListCell* l = NULL;
foreach (l, stmts) {
Node* stmt = (Node*)lfirst(l);
ListCell* opt = NULL;
List* user_options = NULL;
if (!IsA(stmt, CreateStmt)) {
continue;
}
user_options = ((CreateStmt*)stmt)->options;
foreach (opt, user_options) {
DefElem* def = (DefElem*)lfirst(opt);
char* lower_string = lowerstr(def->defname);
if (strstr(lower_string, "hashbucket")) {
return true;
}
}
}
return false;
}
/*
* Notice: parsetree could be from cached plan, do not modify it under other memory context
*/
#ifdef PGXC
void CreateCommand(CreateStmt *parse_tree, const char *query_string, ParamListInfo params,
bool is_top_level, bool sent_to_remote)
#else
void CreateCommand(CreateStmt* parse_tree, const char* query_string, ParamListInfo params, bool is_top_level)
#endif
{
List* stmts = NIL;
ListCell* l = NULL;
Oid rel_oid;
#ifdef PGXC
bool is_temp = false;
bool is_object_temp = false;
PGXCSubCluster* sub_cluster = NULL;
char* tablespace_name = NULL;
char relpersistence = RELPERSISTENCE_PERMANENT;
bool table_is_exist = false;
char* internal_data = NULL;
List* uuids = (List*)copyObject(parse_tree->uuids);
char* first_exec_node = NULL;
bool is_first_node = false;
char* query_string_with_info = (char*)query_string;
char* query_string_with_data = (char*)query_string;
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
first_exec_node = find_first_exec_cn();
is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
}
#endif
/*
* DefineRelation() needs to know "is_top_level"
* by "DfsDDLIsTopLevelXact" to prevent "create hdfs table" running
* inside a transaction block.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord())
u_sess->exec_cxt.DfsDDLIsTopLevelXact = is_top_level;
/* Run parse analysis ... */
if (u_sess->attr.attr_sql.enable_parallel_ddl)
stmts = transformCreateStmt((CreateStmt*)parse_tree, query_string, NIL, true, is_first_node);
else
stmts = transformCreateStmt((CreateStmt*)parse_tree, query_string, NIL, false);
/*
* If stmts is NULL, then the table is exists.
* we need record that for searching the group of table.
*/
if (stmts == NIL) {
table_is_exist = true;
/*
* Just return here, if we continue
* to send if not exists stmt, may
* cause the inconsistency of metadata.
* If we under xc_maintenance_mode, we can do
* this to slove some problem of inconsistency.
*/
if (u_sess->attr.attr_common.xc_maintenance_mode == false)
return;
}
#ifdef PGXC
if (IS_MAIN_COORDINATOR) {
/*
* Scan the list of objects.
* Temporary tables are created on Datanodes only.
* Non-temporary objects are created on all nodes.
* In case temporary and non-temporary objects are mized return an error.
*/
bool is_first = true;
foreach (l, stmts) {
Node* stmt = (Node*)lfirst(l);
if (IsA(stmt, CreateStmt)) {
CreateStmt* stmt_loc = (CreateStmt*)stmt;
sub_cluster = stmt_loc->subcluster;
tablespace_name = stmt_loc->tablespacename;
relpersistence = stmt_loc->relation->relpersistence;
is_object_temp = stmt_loc->relation->relpersistence == RELPERSISTENCE_TEMP;
internal_data = stmt_loc->internalData;
if (is_object_temp)
u_sess->exec_cxt.hasTempObject = true;
if (is_first) {
is_first = false;
if (is_object_temp)
is_temp = true;
} else {
if (is_object_temp != is_temp)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CREATE not supported for TEMP and non-TEMP objects"),
errdetail("You should separate TEMP and non-TEMP objects")));
}
} else if (IsA(stmt, CreateForeignTableStmt)) {
if (in_logic_cluster()) {
CreateStmt* stmt_loc = (CreateStmt*)stmt;
sub_cluster = stmt_loc->subcluster;
}
/* There are no temporary foreign tables */
if (is_first) {
is_first = false;
} else {
if (!is_temp)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CREATE not supported for TEMP and non-TEMP objects"),
errdetail("You should separate TEMP and non-TEMP objects")));
}
} else if (IsA(stmt, CreateSeqStmt)) {
CreateSeqStmt* sstmt = (CreateSeqStmt*)stmt;
Const* n = makeConst(INT8OID, -1, InvalidOid, sizeof(int64), Int64GetDatum(sstmt->uuid), false, true);
uuids = lappend(uuids, n);
}
}
/* Package the internalData after the query_string */
if (internal_data != NULL) {
query_string_with_data = append_internal_data_to_query(internal_data, query_string);
}
/*
* Now package the uuids message that create table on RemoteNode need.
*/
if (uuids != NIL) {
char* uuid_info = nodeToString(uuids);
AssembleHybridMessage(&query_string_with_info, query_string_with_data, uuid_info);
} else
query_string_with_info = query_string_with_data;
}
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (!sent_to_remote) {
RemoteQuery* step = makeNode(RemoteQuery);
step->combine_type = COMBINE_TYPE_SAME;
step->sql_statement = (char*)query_string_with_info;
if (is_object_temp)
step->exec_type = EXEC_ON_NONE;
else
step->exec_type = EXEC_ON_COORDS;
step->exec_nodes = NULL;
step->is_temp = is_temp;
ExecRemoteUtility_ParallelDDLMode(step, first_exec_node);
pfree_ext(step);
}
}
}
if (u_sess->attr.attr_sql.enable_parallel_ddl) {
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && !is_first_node)
stmts = transformCreateStmt((CreateStmt*)parse_tree, query_string, uuids, false);
}
#endif
#ifdef PGXC
/*
* Add a RemoteQuery node for a query at top level on a remote
* Coordinator, if not already done so
*/
if (!sent_to_remote) {
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node)
stmts = AddRemoteQueryNode(stmts, query_string_with_info, EXEC_ON_DATANODES, is_temp);
else
stmts = AddRemoteQueryNode(stmts, query_string_with_info, CHOOSE_EXEC_NODES(is_object_temp), is_temp);
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() &&
(sub_cluster == NULL || sub_cluster->clustertype == SUBCLUSTER_GROUP)) {
const char* group_name = NULL;
Oid group_oid = InvalidOid;
/*
* If TO-GROUP clause is specified when creating table, we
* only have to add required datanode in remote DDL execution
*/
if (sub_cluster != NULL) {
ListCell* lc = NULL;
foreach (lc, sub_cluster->members) {
group_name = strVal(lfirst(lc));
}
} else if (in_logic_cluster() && !table_is_exist) {
/*
* for CreateForeignTableStmt ,
* CreateTableStmt with user not attached to logic cluster
*/
group_name = PgxcGroupGetCurrentLogicCluster();
if (group_name == NULL) {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("Cannot find logic cluster.")));
}
} else {
Oid tablespace_id = InvalidOid;
bool dfs_tablespace = false;
if (tablespace_name != NULL) {
tablespace_id = get_tablespace_oid(tablespace_name, false);
} else {
tablespace_id = GetDefaultTablespace(relpersistence);
}
/* Determine if we are working on a HDFS table. */
dfs_tablespace = IsSpecifiedTblspc(tablespace_id, FILESYSTEM_HDFS);
/*
* If TO-GROUP clause is not specified we are using the installation group to
* distribute table.
*
* For HDFS table/Foreign Table we don't refer default_storage_nodegroup
* to make table creation.
*/
if (table_is_exist) {
Oid rel_id = RangeVarGetRelid(((CreateStmt*)parse_tree)->relation, NoLock, true);
if (OidIsValid(rel_id)) {
Oid table_groupoid = get_pgxc_class_groupoid(rel_id);
if (OidIsValid(table_groupoid)) {
group_name = get_pgxc_groupname(table_groupoid);
}
}
if (group_name == NULL) {
group_name = PgxcGroupGetInstallationGroup();
}
} else if (dfs_tablespace || IsA(parse_tree, CreateForeignTableStmt)) {
group_name = PgxcGroupGetInstallationGroup();
} else if (strcmp(u_sess->attr.attr_sql.default_storage_nodegroup, INSTALLATION_MODE) == 0 ||
u_sess->attr.attr_common.IsInplaceUpgrade) {
group_name = PgxcGroupGetInstallationGroup();
} else {
group_name = u_sess->attr.attr_sql.default_storage_nodegroup;
}
/* If we didn't identify an installation node group error it out out */
if (group_name == NULL) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("Installation node group is not defined in current cluster")));
}
}
/* Fetch group name */
group_oid = get_pgxc_groupoid(group_name);
if (!OidIsValid(group_oid)) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("Target node group \"%s\" doesn't exist", group_name)));
}
if (in_logic_cluster()) {
check_logic_cluster_create_priv(group_oid, group_name);
} else {
/* No limit in logic cluster mode */
/* check to block non-redistribution process creating table to old group */
if (!u_sess->attr.attr_sql.enable_cluster_resize) {
char in_redistribution = get_pgxc_group_redistributionstatus(group_oid);
if (in_redistribution == 'y') {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unable to create table on old installation group \"%s\" while in cluster "
"resizing.",
group_name)));
}
}
}
/* Build exec_nodes to table creation */
const int total_len = list_length(stmts);
Node* node = (Node*)list_nth(stmts, (total_len - 1));
// *node* should be a RemoteQuery Node
AssertEreport(query_string != NULL, MOD_EXECUTOR, "Node type is not remote type");
RemoteQuery* rquery = (RemoteQuery*)node;
// *exec_nodes* should be a NULL pointer
AssertEreport(!rquery->exec_nodes, MOD_EXECUTOR, "remote query is not DN");
rquery->exec_nodes = makeNode(ExecNodes);
/* Set group oid here for sending bucket map to dn */
rquery->exec_nodes->distribution.group_oid = group_oid;
if (find_hashbucket_options(stmts)) {
rquery->is_send_bucket_map = true;
}
/*
* Check node group permissions, we only do such kind of ACL check
* for user-defined nodegroup(none-installation)
*/
AclResult acl_result = pg_nodegroup_aclcheck(group_oid, GetUserId(), ACL_CREATE);
if (acl_result != ACLCHECK_OK) {
aclcheck_error(acl_result, ACL_KIND_NODEGROUP, group_name);
}
/*
* Notice!!
* In cluster resizing stage we need special processing logics in table creation as:
* [1]. create table delete_delta ... to group old_group on all DN
* [2]. display pgxc_group.group_members
* [3]. drop table delete_delta ==> drop delete_delta on all DN
*
* So, as normal, when target node group's status is marked as 'installation' or
* 'redistribution', we have to issue a full-DN create table request, remeber
* pgxc_class.group_members still reflects table's logic distribution to tell pgxc
* planner to build Scan operator in multi_nodegroup way. The reason we have to so is
* to be compatible with current gs_switch_relfilenode() invokation in cluster expand
* and shrunk mechanism.
*/
if (need_full_dn_execution(group_name)) {
/* Sepcial path, issue full-DN create table request */
rquery->exec_nodes->nodeList = GetAllDataNodes();
} else {
/* Normal path, issue only needs DNs in create table request */
Oid* members = NULL;
int nmembers = 0;
nmembers = get_pgxc_groupmembers(group_oid, &members);
/* Append nodeId to exec_nodes */
rquery->exec_nodes->nodeList = GetNodeGroupNodeList(members, nmembers);
pfree_ext(members);
if (uuids && nmembers < u_sess->pgxc_cxt.NumDataNodes) {
char* create_seqs;
RemoteQuery* step;
/* Create table in NodeGroup with sequence. */
create_seqs = assemble_create_sequence_msg(stmts, uuids);
step = make_remote_query_for_seq(rquery->exec_nodes, create_seqs);
stmts = lappend(stmts, step);
}
}
}
}
#endif
if (uuids != NIL) {
list_free_deep(uuids);
uuids = NIL;
}
/* ... and do it */
foreach (l, stmts) {
Node* stmt = (Node*)lfirst(l);
if (IsA(stmt, CreateStmt)) {
Datum toast_options;
static const char* const validnsps[] = HEAP_RELOPT_NAMESPACES;
/* forbid user to set or change inner options */
ForbidOutUsersToSetInnerOptions(((CreateStmt*)stmt)->options);
/* Create the table itself */
rel_oid = DefineRelation((CreateStmt*)stmt, RELKIND_RELATION, InvalidOid);
/*
* Let AlterTableCreateToastTable decide if this one
* needs a secondary relation too.
*/
CommandCounterIncrement();
/* parse and validate reloptions for the toast table */
toast_options =
transformRelOptions((Datum)0, ((CreateStmt*)stmt)->options, "toast", validnsps, true, false);
(void)heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
AlterTableCreateToastTable(rel_oid, toast_options, ((CreateStmt *)stmt)->oldToastNode);
AlterCStoreCreateTables(rel_oid, toast_options, (CreateStmt*)stmt);
#ifdef ENABLE_MULTIPLE_NODES
MetaUtils::create_ts_store_tables(rel_oid, toast_options, (CreateStmt *)stmt);
#endif
AlterDfsCreateTables(rel_oid, toast_options, (CreateStmt*)stmt);
/* create partition policy if ttl or period defined */
if (IS_MAIN_COORDINATOR)
create_part_policy_if_needed((CreateStmt*)stmt, RELKIND_RELATION);
} else if (IsA(stmt, CreateForeignTableStmt)) {
/* forbid user to set or change inner options */
ForbidOutUsersToSetInnerOptions(((CreateStmt*)stmt)->options);
/* if this is a log ft, check its definition */
check_log_ft_definition((CreateForeignTableStmt*)stmt);
/* Create the table itself */
rel_oid = DefineRelation((CreateStmt*)stmt, RELKIND_FOREIGN_TABLE, InvalidOid);
CreateForeignTable((CreateForeignTableStmt*)stmt, rel_oid);
} else {
if (IsA(stmt, AlterTableStmt))
((AlterTableStmt*)stmt)->fromCreate = true;
/* Recurse for anything else */
ProcessUtility(stmt,
query_string_with_info,
params,
false,
None_Receiver,
#ifdef PGXC
true,
#endif /* PGXC */
NULL);
}
/* Need CCI between commands */
if (lnext(l) != NULL)
CommandCounterIncrement();
}
/* reset */
parse_tree->uuids = NIL;
}
void ReindexCommand(ReindexStmt* stmt, bool is_top_level)
{
/* we choose to allow this during "read only" transactions */
PreventCommandDuringRecovery("REINDEX");
switch (stmt->kind) {
case OBJECT_INDEX:
case OBJECT_INDEX_PARTITION:
ReindexIndex(stmt->relation, (const char*)stmt->name, &stmt->memUsage);
break;
case OBJECT_TABLE:
case OBJECT_TABLE_PARTITION:
ReindexTable(stmt->relation, (const char*)stmt->name, &stmt->memUsage);
break;
case OBJECT_INTERNAL:
case OBJECT_INTERNAL_PARTITION:
ReindexInternal(stmt->relation, (const char*)stmt->name);
break;
case OBJECT_DATABASE:
/*
* This cannot run inside a user transaction block; if
* we were inside a transaction, then its commit- and
* start-transaction-command calls would not have the
* intended effect!
*/
PreventTransactionChain(is_top_level, "REINDEX DATABASE");
ReindexDatabase(stmt->name, stmt->do_system, stmt->do_user, &stmt->memUsage);
break;
default: {
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized object type: %d", (int)stmt->kind)));
} break;
}
}
/* Called by standard_ProcessUtility() for the cases TRANS_STMT_BEGIN and TRANS_STMT_START */
static void set_item_arg_according_to_def_name(DefElem* item)
{
if (strcmp(item->defname, "transaction_isolation") == 0)
SetPGVariable("transaction_isolation", list_make1(item->arg), true);
else if (strcmp(item->defname, "transaction_read_only") == 0)
SetPGVariable("transaction_read_only", list_make1(item->arg), true);
else if (strcmp(item->defname, "transaction_deferrable") == 0)
SetPGVariable("transaction_deferrable", list_make1(item->arg), true);
}
/* Add a RemoteQuery node for a query at top level on a remote Coordinator */
static void add_remote_query_4_alter_stmt(bool is_first_node, AlterTableStmt* atstmt, const char* query_string, List** stmts,
char** drop_seq_string, ExecNodes** exec_nodes)
{
if (!PointerIsValid(atstmt) || !PointerIsValid(stmts) || !PointerIsValid(drop_seq_string) ||
!PointerIsValid(exec_nodes)) {
return;
}
bool is_temp = false;
RemoteQueryExecType exec_type;
Oid rel_id = RangeVarGetRelid(atstmt->relation, NoLock, true);
if (!OidIsValid(rel_id)) {
return;
}
exec_type = ExecUtilityFindNodes(atstmt->relkind, rel_id, &is_temp);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_DATANODES) {
*stmts = AddRemoteQueryNode(*stmts, query_string, EXEC_ON_DATANODES, is_temp);
}
} else {
*stmts = AddRemoteQueryNode(*stmts, query_string, exec_type, is_temp);
}
/* nodegroup attch execnodes */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
Node* node = (Node*)lfirst(list_tail(*stmts));
if (IsA(node, RemoteQuery)) {
RemoteQuery* rquery = (RemoteQuery*)node;
rquery->exec_nodes = RelidGetExecNodes(rel_id);
if (rquery->exec_nodes->nodeList != NULL &&
rquery->exec_nodes->nodeList->length < u_sess->pgxc_cxt.NumDataNodes) {
*drop_seq_string = get_drop_seq_query_string(atstmt, rel_id);
*exec_nodes = rquery->exec_nodes;
}
}
}
}
void standard_ProcessUtility(Node* parse_tree, const char* query_string, ParamListInfo params, bool is_top_level,
DestReceiver* dest,
#ifdef PGXC
bool sent_to_remote,
#endif /* PGXC */
char* completion_tag)
{
/* This can recurse, so check for excessive recursion */
check_stack_depth();
/* Check the statement during online expansion. */
BlockUnsupportedDDL(parse_tree);
/* reset t_thrd.vacuum_cxt.vac_context in case that
invalid t_thrd.vacuum_cxt.vac_context would be used */
t_thrd.vacuum_cxt.vac_context = NULL;
#ifdef PGXC
/*
* For more detail see comments in function pgxc_lock_for_backup.
*
* Cosider the following scenario:
* Imagine a two cordinator cluster CO1, CO2
* Suppose a client connected to CO1 issues select pgxc_lock_for_backup()
* Now assume that a client connected to CO2 issues a create table
* select pgxc_lock_for_backup() would try to acquire the advisory lock
* in exclusive mode, whereas create table would try to acquire the same
* lock in shared mode. Both these requests will always try acquire the
* lock in the same order i.e. they would both direct the request first to
* CO1 and then to CO2. One of the two requests would therefore pass
* and the other would fail.
*
* Consider another scenario:
* Suppose we have a two cooridnator cluster CO1 and CO2
* Assume one client connected to each coordinator
* Further assume one client starts a transaction
* and issues a DDL. This is an unfinished transaction.
* Now assume the second client issues
* select pgxc_lock_for_backup()
* This request would fail because the unfinished transaction
* would already hold the advisory lock.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && IsNormalProcessingMode()) {
/* Is the statement a prohibited one? */
if (!is_stmt_allowed_in_locked_mode(parse_tree, query_string))
pgxc_lock_for_utility_stmt(parse_tree, isAllTempObjects(parse_tree, query_string, sent_to_remote));
}
#endif
if (t_thrd.proc->workingVersionNum >= 91275) {
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && !IsAbortedTransactionBlockState()) {
Oid node_oid = get_pgxc_nodeoid(g_instance.attr.attr_common.PGXCNodeName);
bool nodeis_active = true;
nodeis_active = is_pgxc_nodeactive(node_oid);
if (OidIsValid(node_oid) && nodeis_active == false)
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Current Node is not active")));
}
}
check_xact_readonly(parse_tree);
if (completion_tag != NULL)
completion_tag[0] = '\0';
errno_t errorno = EOK;
switch (nodeTag(parse_tree)) {
/*
* ******************** transactions ********************
*/
case T_TransactionStmt: {
TransactionStmt* stmt = (TransactionStmt*)parse_tree;
switch (stmt->kind) {
/*
* START TRANSACTION, as defined by SQL99: Identical
* to BEGIN. Same code for both.
*/
case TRANS_STMT_BEGIN:
case TRANS_STMT_START: {
ListCell* lc = NULL;
BeginTransactionBlock();
foreach (lc, stmt->options) {
DefElem* item = (DefElem*)lfirst(lc);
set_item_arg_according_to_def_name(item);
}
u_sess->need_report_top_xid = true;
} break;
case TRANS_STMT_COMMIT:
/* Only generate one time when u_sess->debug_query_id = 0 in CN */
if ((IS_SINGLE_NODE || IS_PGXC_COORDINATOR) && u_sess->debug_query_id == 0) {
u_sess->debug_query_id = generate_unique_id64(&gt_queryId);
pgstat_report_queryid(u_sess->debug_query_id);
}
/* check the commit cmd */
if (GTM_MODE && (IS_PGXC_DATANODE || IsConnFromCoord())) {
TransactionId CurrentTopXid = GetTopTransactionIdIfAny();
if (TransactionIdIsValid(CurrentTopXid) &&
TransactionIdIsValid(t_thrd.xact_cxt.reserved_nextxid_check) &&
t_thrd.xact_cxt.reserved_nextxid_check != CurrentTopXid) {
ereport(PANIC,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
(errmsg("commit xid %lu is not equal to the excute one %lu.",
CurrentTopXid,
t_thrd.xact_cxt.reserved_nextxid_check))));
}
}
if (!EndTransactionBlock()) {
/* report unsuccessful commit in completion_tag */
if (completion_tag != NULL) {
errorno = strcpy_s(completion_tag, COMPLETION_TAG_BUFSIZE, "ROLLBACK");
securec_check(errorno, "\0", "\0");
}
}
break;
case TRANS_STMT_PREPARE:
PreventCommandDuringRecovery("PREPARE TRANSACTION");
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && !u_sess->attr.attr_common.xc_maintenance_mode) {
/* Explicit prepare transaction is not support */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
(errmsg("Explicit prepare transaction is not supported."))));
/* Add check if xid is valid */
if (IsXidImplicit((const char*)stmt->gid)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
(errmsg("Invalid transaciton_id to prepare."))));
break;
}
}
#endif
if (!PrepareTransactionBlock(stmt->gid)) {
/* report unsuccessful commit in completion_tag */
if (completion_tag != NULL) {
errorno = strcpy_s(completion_tag, COMPLETION_TAG_BUFSIZE, "ROLLBACK");
securec_check(errorno, "\0", "\0");
}
}
break;
case TRANS_STMT_COMMIT_PREPARED:
PreventTransactionChain(is_top_level, "COMMIT PREPARED");
PreventCommandDuringRecovery("COMMIT PREPARED");
/* for commit in progress, extract the latest local csn for set */
if (COMMITSEQNO_IS_COMMITTING(stmt->csn)) {
stmt->csn = GET_COMMITSEQNO(stmt->csn);
}
#ifdef PGXC
/*
* Commit a transaction which was explicitely prepared
* before
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
if (!u_sess->attr.attr_common.xc_maintenance_mode) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
(errmsg("Explicit commit prepared transaction is not supported."))));
} else {
if (!(useLocalXid || !IsPostmasterEnvironment || GTM_FREE_MODE) &&
COMMITSEQNO_IS_COMMITTED(stmt->csn)) {
setCommitCsn(stmt->csn);
}
FinishPreparedTransaction(stmt->gid, true);
}
} else {
#endif
#ifdef ENABLE_DISTRIBUTE_TEST
if (IS_PGXC_DATANODE && TEST_STUB(DN_COMMIT_PREPARED_SLEEP, twophase_default_error_emit)) {
ereport(g_instance.distribute_test_param_instance->elevel,
(errmsg("GTM_TEST %s: DN commit prepare sleep",
g_instance.attr.attr_common.PGXCNodeName)));
/* sleep 30s or more */
pg_usleep(g_instance.distribute_test_param_instance->sleep_time * 1000000);
}
/* white box test start */
if (IS_PGXC_DATANODE)
execute_whitebox(WHITEBOX_LOC, stmt->gid, WHITEBOX_WAIT, 0.0001);
/* white box test end */
#endif
if (!(useLocalXid || !IsPostmasterEnvironment || GTM_FREE_MODE) &&
COMMITSEQNO_IS_COMMITTED(stmt->csn))
setCommitCsn(stmt->csn);
FinishPreparedTransaction(stmt->gid, true);
#ifdef PGXC
}
#endif
break;
case TRANS_STMT_ROLLBACK_PREPARED:
PreventTransactionChain(is_top_level, "ROLLBACK PREPARED");
PreventCommandDuringRecovery("ROLLBACK PREPARED");
#ifdef PGXC
/*
* Abort a transaction which was explicitely prepared
* before
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
if (!u_sess->attr.attr_common.xc_maintenance_mode) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
(errmsg("Explicit rollback prepared transaction is not supported."))));
} else
FinishPreparedTransaction(stmt->gid, false);
} else
#endif
FinishPreparedTransaction(stmt->gid, false);
break;
case TRANS_STMT_ROLLBACK:
/* check the abort cmd */
if (GTM_MODE && (IS_PGXC_DATANODE || IsConnFromCoord())) {
TransactionId CurrentTopXid = GetTopTransactionIdIfAny();
if (TransactionIdIsValid(CurrentTopXid) &&
TransactionIdIsValid(t_thrd.xact_cxt.reserved_nextxid_check) &&
t_thrd.xact_cxt.reserved_nextxid_check != CurrentTopXid) {
/* level ERROR will cause ERRDATA stack overflow */
ereport(PANIC,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
(errmsg("abort xid %lu is not equal to the former one %lu.",
CurrentTopXid,
t_thrd.xact_cxt.reserved_nextxid_check))));
}
}
UserAbortTransactionBlock();
break;
case TRANS_STMT_SAVEPOINT: {
ListCell* cell = NULL;
char* name = NULL;
RequireTransactionChain(is_top_level, "SAVEPOINT");
foreach (cell, stmt->options) {
DefElem* elem = (DefElem*)lfirst(cell);
if (strcmp(elem->defname, "savepoint_name") == 0)
name = strVal(elem->arg);
}
AssertEreport(PointerIsValid(name), MOD_EXECUTOR, "name pointer is Invalid");
/*
* CN send the following info to DNs and other CNs before itself DefineSavepoint
* 1)parent xid
* 2)start transaction command if need
* 3)SAVEPOINT command
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
pgxc_node_remote_savepoint(query_string, EXEC_ON_ALL_NODES, true, true);
#ifdef ENABLE_DISTRIBUTE_TEST
if (TEST_STUB(CN_SAVEPOINT_BEFORE_DEFINE_LOCAL_FAILED, twophase_default_error_emit)) {
ereport(g_instance.distribute_test_param_instance->elevel,
(errmsg("SUBXACT_TEST %s: cn local define savepoint \"%s\" failed",
g_instance.attr.attr_common.PGXCNodeName,
name)));
}
/* white box test start */
if (execute_whitebox(WHITEBOX_LOC, NULL, WHITEBOX_DEFAULT, 0.0001)) {
ereport(g_instance.distribute_test_param_instance->elevel,
(errmsg("WHITE_BOX TEST %s: cn local define savepoint failed",
g_instance.attr.attr_common.PGXCNodeName)));
}
/* white box test end */
#endif
} else {
/* Dn or other cn should assign next_xid from cn to local, set parent xid for
* CurrentTransactionState */
GetCurrentTransactionId();
#ifdef ENABLE_DISTRIBUTE_TEST
if (TEST_STUB(DN_SAVEPOINT_BEFORE_DEFINE_LOCAL_FAILED, twophase_default_error_emit)) {
ereport(g_instance.distribute_test_param_instance->elevel,
(errmsg("SUBXACT_TEST %s: dn local define savepoint \"%s\" failed",
g_instance.attr.attr_common.PGXCNodeName,
name)));
}
/* white box test start */
if (execute_whitebox(WHITEBOX_LOC, NULL, WHITEBOX_DEFAULT, 0.0001)) {
ereport(g_instance.distribute_test_param_instance->elevel,
(errmsg("WHITE_BOX TEST %s: dn local define savepoint failed",
g_instance.attr.attr_common.PGXCNodeName)));
}
/* white box test end */
#endif
}
DefineSavepoint(name);
} break;
case TRANS_STMT_RELEASE:
RequireTransactionChain(is_top_level, "RELEASE SAVEPOINT");
ReleaseSavepoint(stmt->options);
#ifdef ENABLE_DISTRIBUTE_TEST
/* CN send xid for remote nodes for it assignning parentxid when need. */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
if (TEST_STUB(CN_RELEASESAVEPOINT_BEFORE_SEND_FAILED, twophase_default_error_emit)) {
ereport(g_instance.distribute_test_param_instance->elevel,
(errmsg("SUBXACT_TEST %s: cn release savepoint before send to remote nodes failed.",
g_instance.attr.attr_common.PGXCNodeName)));
}
} else if (TEST_STUB(DN_RELEASESAVEPOINT_AFTER_LOCAL_DEAL_FAILED, twophase_default_error_emit)) {
ereport(g_instance.distribute_test_param_instance->elevel,
(errmsg("SUBXACT_TEST %s: dn release savepoint after loal deal failed",
g_instance.attr.attr_common.PGXCNodeName)));
}
/* white box test start */
if (execute_whitebox(WHITEBOX_LOC, NULL, WHITEBOX_DEFAULT, 0.0001)) {
ereport(g_instance.distribute_test_param_instance->elevel,
(errmsg("WHITE_BOX TEST %s: release savepoint failed",
g_instance.attr.attr_common.PGXCNodeName)));
}
/* white box test end */
#endif
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
pgxc_node_remote_savepoint(query_string, EXEC_ON_ALL_NODES, true, false);
}
break;
case TRANS_STMT_ROLLBACK_TO:
RequireTransactionChain(is_top_level, "ROLLBACK TO SAVEPOINT");
RollbackToSavepoint(stmt->options);
#ifdef ENABLE_DISTRIBUTE_TEST
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
if (TEST_STUB(CN_ROLLBACKTOSAVEPOINT_BEFORE_SEND_FAILED, twophase_default_error_emit)) {
ereport(g_instance.distribute_test_param_instance->elevel,
(errmsg("SUBXACT_TEST %s: cn rollback to savepoint failed before send to remote nodes.",
g_instance.attr.attr_common.PGXCNodeName)));
}
} else if (TEST_STUB(DN_ROLLBACKTOSAVEPOINT_AFTER_LOCAL_DEAL_FAILED, twophase_default_error_emit)) {
ereport(g_instance.distribute_test_param_instance->elevel,
(errmsg("SUBXACT_TEST %s: dn rollback to savepoint failed after local deal failed",
g_instance.attr.attr_common.PGXCNodeName)));
}
/* white box test start */
if (execute_whitebox(WHITEBOX_LOC, NULL, WHITEBOX_DEFAULT, 0.0001)) {
ereport(LOG,
(errmsg("WHITE_BOX TEST %s: rollback to savepoint failed",
g_instance.attr.attr_common.PGXCNodeName)));
}
/* white box test end */
#endif
/*
* CN needn't send xid, as savepoint must be sent and executed before.
* And the parent xid must be in transaction state pushed and remained.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
pgxc_node_remote_savepoint(query_string, EXEC_ON_ALL_NODES, false, false);
}
/*
* CommitTransactionCommand is in charge of
* re-defining the savepoint again
*/
break;
default:
break;
}
} break;
/*
* Portal (cursor) manipulation
*
* Note: DECLARE CURSOR is processed mostly as a SELECT, and
* therefore what we will get here is a PlannedStmt not a bare
* DeclareCursorStmt.
*/
case T_PlannedStmt: {
PlannedStmt* stmt = (PlannedStmt*)parse_tree;
if (stmt->utilityStmt == NULL || !IsA(stmt->utilityStmt, DeclareCursorStmt)) {
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("non-DECLARE CURSOR PlannedStmt passed to ProcessUtility")));
}
PerformCursorOpen(stmt, params, query_string, is_top_level);
} break;
case T_ClosePortalStmt: {
ClosePortalStmt* stmt = (ClosePortalStmt*)parse_tree;
CheckRestrictedOperation("CLOSE");
stop_query(); // stop datanode query asap.
PerformPortalClose(stmt->portalname);
} break;
case T_FetchStmt:
PerformPortalFetch((FetchStmt*)parse_tree, dest, completion_tag);
break;
/*
* relation and attribute manipulation
*/
case T_CreateSchemaStmt:
#ifdef PGXC
CreateSchemaCommand((CreateSchemaStmt*)parse_tree, query_string, sent_to_remote);
#else
CreateSchemaCommand((CreateSchemaStmt*)parse_tree, query_string);
#endif
break;
case T_CreateForeignTableStmt:
case T_CreateStmt: {
#ifdef PGXC
CreateCommand((CreateStmt*)parse_tree, query_string, params, is_top_level, sent_to_remote);
#else
CreateCommand((CreateStmt*)parse_tree, query_string, params, is_top_level);
#endif
} break;
case T_CreateTableSpaceStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
PreventTransactionChain(is_top_level, "CREATE TABLESPACE");
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
exec_utility_with_message_parallel_ddl_mode(
query_string, sent_to_remote, false, first_exec_node, EXEC_ON_COORDS);
CreateTableSpace((CreateTableSpaceStmt*)parse_tree);
exec_utility_with_message_parallel_ddl_mode(
query_string, sent_to_remote, false, first_exec_node, EXEC_ON_DATANODES);
} else {
CreateTableSpace((CreateTableSpaceStmt*)parse_tree);
ExecUtilityWithMessage(query_string, sent_to_remote, false);
}
} else {
/* Don't allow this to be run inside transaction block on single node */
if (IS_SINGLE_NODE)
PreventTransactionChain(is_top_level, "CREATE TABLESPACE");
CreateTableSpace((CreateTableSpaceStmt*)parse_tree);
}
#else
PreventTransactionChain(is_top_level, "CREATE TABLESPACE");
CreateTableSpace((CreateTableSpaceStmt*)parse_tree);
#endif
break;
case T_DropTableSpaceStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord())
PreventTransactionChain(is_top_level, "DROP TABLESPACE");
/* Allow this to be run inside transaction block on remote nodes */
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
DropTableSpace((DropTableSpaceStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
DropTableSpace((DropTableSpaceStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
DropTableSpace((DropTableSpaceStmt*)parse_tree);
}
#else
PreventTransactionChain(is_top_level, "DROP TABLESPACE");
DropTableSpace((DropTableSpaceStmt*)parse_tree);
#endif
break;
case T_AlterTableSpaceOptionsStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
AlterTableSpaceOptions((AlterTableSpaceOptionsStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
AlterTableSpaceOptions((AlterTableSpaceOptionsStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
AlterTableSpaceOptions((AlterTableSpaceOptionsStmt*)parse_tree);
}
#else
AlterTableSpaceOptions((AlterTableSpaceOptionsStmt*)parse_tree);
#endif
break;
case T_CreateExtensionStmt:
CreateExtension((CreateExtensionStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_AlterExtensionStmt:
#ifdef PGXC
FEATURE_NOT_PUBLIC_ERROR("EXTENSION is not yet supported.");
#endif /* PGXC */
ExecAlterExtensionStmt((AlterExtensionStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_AlterExtensionContentsStmt:
#ifdef PGXC
FEATURE_NOT_PUBLIC_ERROR("EXTENSION is not yet supported.");
#endif /* PGXC */
ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_CreateFdwStmt:
CreateForeignDataWrapper((CreateFdwStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && !IsInitdb)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_AlterFdwStmt:
AlterForeignDataWrapper((AlterFdwStmt*)parse_tree);
break;
case T_CreateForeignServerStmt:
CreateForeignServer((CreateForeignServerStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && !IsInitdb)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_AlterForeignServerStmt:
AlterForeignServer((AlterForeignServerStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord())
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_CreateUserMappingStmt:
CreateUserMapping((CreateUserMappingStmt*)parse_tree);
break;
case T_AlterUserMappingStmt:
AlterUserMapping((AlterUserMappingStmt*)parse_tree);
break;
case T_DropUserMappingStmt:
RemoveUserMapping((DropUserMappingStmt*)parse_tree);
break;
case T_CreateDataSourceStmt:
CreateDataSource((CreateDataSourceStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && !IsInitdb)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_AlterDataSourceStmt:
AlterDataSource((AlterDataSourceStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord())
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_CreateRlsPolicyStmt: /* CREATE ROW LEVEL SECURITY POLICY */
CreateRlsPolicy((CreateRlsPolicyStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && !IsInitdb)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false);
#endif
break;
case T_AlterRlsPolicyStmt: /* ALTER ROW LEVEL SECURITY POLICY */
AlterRlsPolicy((AlterRlsPolicyStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && !IsInitdb)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false);
#endif
break;
case T_DropStmt:
CheckObjectInBlackList(((DropStmt*)parse_tree)->removeType, query_string);
/*
* performMultipleDeletions() needs to know is_top_level by
* "DfsDDLIsTopLevelXact" to prevent "drop hdfs table"
* running inside a transaction block.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord())
u_sess->exec_cxt.DfsDDLIsTopLevelXact = is_top_level;
switch (((DropStmt*)parse_tree)->removeType) {
case OBJECT_INDEX:
#ifdef ENABLE_MULTIPLE_NODES
if (((DropStmt*)parse_tree)->concurrent) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PGXC does not support concurrent INDEX yet"),
errdetail("The feature is not currently supported")));
}
#endif
if (((DropStmt*)parse_tree)->concurrent)
PreventTransactionChain(is_top_level, "DROP INDEX CONCURRENTLY");
/* fall through */
case OBJECT_FOREIGN_TABLE:
case OBJECT_TABLE: {
#ifdef PGXC
/*
* For table batch-dropping, we we only support to drop tables
* belonging same nodegroup.
*
* Note: we only have to such kind of check at CN node
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
DropStmt* ds = (DropStmt*)parse_tree;
if (!ObjectsInSameNodeGroup(ds->objects, T_DropStmt)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("NOT-SUPPORT: Not support DROP multiple objects different nodegroup")));
}
}
/*
* Need to let ProcSleep know if we could cancel redistribution transaction which
* locks the table we want to drop. ProcSleep will make sure we only cancel the
* transaction doing redistribution.
*
* need to refactor this part into a common function where all supported cancel-redistribution
* DDL statements sets it
*/
if (IS_PGXC_COORDINATOR && ((DropStmt*)parse_tree)->removeType == OBJECT_TABLE) {
u_sess->exec_cxt.could_cancel_redistribution = true;
}
#endif
}
/* fall through */
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
#ifdef PGXC
{
if (((DropStmt*)parse_tree)->removeType == OBJECT_FOREIGN_TABLE) {
/*
* In the security mode, the useft privilege of a user must be
* checked before the user creates a foreign table.
*/
if (isSecurityMode && !have_useft_privilege()) {
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to drop foreign table in security mode")));
}
}
bool is_temp = false;
RemoteQueryExecType exec_type = EXEC_ON_ALL_NODES;
ObjectAddresses* new_objects = NULL;
/*
* For DROP TABLE/INDEX/VIEW/... IF EXISTS query, only notice is emitted
* if the referred objects are not found. In such case, the atomicity and consistency
* of the query or transaction among local CN and remote nodes can not be guaranteed
* against concurrent CREATE TABLE/INDEX/VIEW/... query.
*
* To ensure such atomicity and consistency, we only refer to local CN about
* the visibility of the objects to be deleted and rewrite the query into tmp_queryString
* without the inivisible objects. Later, if the objects in tmp_queryString are not
* found on remote nodes, which should not happen, just ERROR.
*/
StringInfo tmp_queryString = makeStringInfo();
/* Check restrictions on objects dropped */
drop_stmt_pre_treatment((DropStmt*)parse_tree, query_string, sent_to_remote, &is_temp, &exec_type);
char* first_exec_node = NULL;
bool is_first_node = false;
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
first_exec_node = find_first_exec_cn();
is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
}
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && u_sess->attr.attr_sql.enable_parallel_ddl) {
if (!is_first_node) {
new_objects = PreCheckforRemoveRelation((DropStmt*)parse_tree, tmp_queryString, &exec_type);
}
}
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() &&
(exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_COORDS)) {
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
RemoteQuery* step = makeNode(RemoteQuery);
step->combine_type = COMBINE_TYPE_SAME;
step->sql_statement = tmp_queryString->data[0] ? tmp_queryString->data : (char*)query_string;
step->exec_type = EXEC_ON_COORDS;
step->exec_nodes = NULL;
step->is_temp = false;
ExecRemoteUtility_ParallelDDLMode(step, first_exec_node);
pfree_ext(step);
}
}
/*
* @NodeGroup Support
*
* Scan for first object from drop-list in DropStmt to find target DNs,
* here for TO-GROUP aware objects, we need pass DropStmt handler into
* ExecUtilityStmtOnNodes() to further evaluate which DNs wend utility.
*/
ExecNodes* exec_nodes = NULL;
Node* reparse = NULL;
ObjectType object_type = ((DropStmt*)parse_tree)->removeType;
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() &&
(object_type == OBJECT_TABLE || object_type == OBJECT_INDEX)) {
reparse = (Node*)parse_tree;
ListCell* lc = list_head(((DropStmt*)parse_tree)->objects);
RangeVar* rel = makeRangeVarFromNameList((List*)lfirst(lc));
Oid rel_id;
LOCKMODE lockmode = NoLock;
if (object_type == OBJECT_TABLE)
lockmode = AccessExclusiveLock;
rel_id = RangeVarGetRelid(rel, lockmode, ((DropStmt*)parse_tree)->missing_ok);
if (OidIsValid(rel_id)) {
Oid check_id = rel_id;
if (get_rel_relkind(rel_id) == RELKIND_INDEX) {
check_id = IndexGetRelation(rel_id, false);
}
Oid group_oid = get_pgxc_class_groupoid(check_id);
char* group_name = get_pgxc_groupname(group_oid);
/*
* Reminding, when supported user-defined node group expansion,
* we need create ExecNodes from target node group.
*
* Notice!!
* In cluster resizing stage we need special processing logics in dropping table as:
* [1]. create table delete_delta ... to group old_group on all DN
* [2]. display pgxc_group.group_members
* [3]. drop table delete_delta ==> drop delete_delta on all DN
*
* So, as normal, when target node group's status is marked as 'installation' or
* 'redistribution', we have to issue a full-DN drop table request, remeber
* pgxc_class.group_members still reflects table's logic distribution to tell pgxc
* planner to build Scan operator in multi_nodegroup way. The reason we have to so is
* to be compatible with current gs_switch_relfilenode() invokation in cluster expand
* and shrunk mechanism.
*/
if (need_full_dn_execution(group_name)) {
exec_nodes = makeNode(ExecNodes);
exec_nodes->nodeList = GetAllDataNodes();
} else {
exec_nodes = RelidGetExecNodes(rel_id);
}
} else {
exec_nodes = RelidGetExecNodes(rel_id);
}
} else if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && object_type == OBJECT_FOREIGN_TABLE &&
in_logic_cluster()) {
ListCell* lc = list_head(((DropStmt*)parse_tree)->objects);
RangeVar* relvar = makeRangeVarFromNameList((List*)lfirst(lc));
Oid rel_id = RangeVarGetRelid(relvar, NoLock, true);
if (OidIsValid(rel_id))
exec_nodes = RelidGetExecNodes(rel_id);
else if (!((DropStmt*)parse_tree)->missing_ok) {
if (relvar->schemaname)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("foreign table \"%s.%s\" does not exist",
relvar->schemaname,
relvar->relname)));
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("foreign table \"%s\" does not exist", relvar->relname)));
}
}
#ifdef ENABLE_MULTIPLE_NODES
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
drop_sequence_4_node_group((DropStmt*)parse_tree, exec_nodes);
}
#endif
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && u_sess->attr.attr_sql.enable_parallel_ddl) {
if (!is_first_node)
RemoveRelationsonMainExecCN((DropStmt*)parse_tree, new_objects);
else
RemoveRelations((DropStmt*)parse_tree, tmp_queryString, &exec_type);
} else
RemoveRelations((DropStmt*)parse_tree, tmp_queryString, &exec_type);
/* DROP is done depending on the object type and its temporary type */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_DATANODES)
ExecUtilityStmtOnNodes_ParallelDDLMode(
tmp_queryString->data[0] ? tmp_queryString->data : query_string,
exec_nodes,
sent_to_remote,
false,
EXEC_ON_DATANODES,
is_temp,
first_exec_node,
reparse);
} else {
ExecUtilityStmtOnNodes(tmp_queryString->data[0] ? tmp_queryString->data : query_string,
exec_nodes,
sent_to_remote,
false,
exec_type,
is_temp,
reparse);
}
}
pfree_ext(tmp_queryString->data);
pfree_ext(tmp_queryString);
FreeExecNodes(&exec_nodes);
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && new_objects != NULL)
free_object_addresses(new_objects);
#endif
} break;
case OBJECT_SCHEMA:
case OBJECT_FUNCTION: {
#ifdef PGXC
bool is_temp = false;
RemoteQueryExecType exec_type = EXEC_ON_ALL_NODES;
ObjectAddresses* new_objects = NULL;
StringInfo tmp_queryString = makeStringInfo();
/* Check restrictions on objects dropped */
drop_stmt_pre_treatment((DropStmt*)parse_tree, query_string, sent_to_remote, &is_temp, &exec_type);
char* first_exec_node = NULL;
bool is_first_node = false;
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
first_exec_node = find_first_exec_cn();
is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
}
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && u_sess->attr.attr_sql.enable_parallel_ddl) {
new_objects =
PreCheckforRemoveObjects((DropStmt*)parse_tree, tmp_queryString, &exec_type, is_first_node);
}
/*
* @NodeGroup Support
*
* Scan for first object from drop-list in DropStmt to find target DNs.
*/
ExecNodes* exec_nodes = NULL;
ObjectType object_type = ((DropStmt*)parse_tree)->removeType;
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && object_type == OBJECT_FUNCTION &&
in_logic_cluster()) {
if (!DropObjectsInSameNodeGroup((DropStmt*)parse_tree)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("NOT-SUPPORT: Not support DROP multiple functions in different nodegroup")));
}
exec_nodes = GetDropFunctionNodes((DropStmt*)parse_tree);
}
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() &&
(exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_COORDS)) {
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
RemoteQuery* step = makeNode(RemoteQuery);
step->combine_type = COMBINE_TYPE_SAME;
step->sql_statement = tmp_queryString->data[0] ? tmp_queryString->data : (char*)query_string;
step->exec_type = EXEC_ON_COORDS;
step->exec_nodes = NULL;
step->is_temp = false;
ExecRemoteUtility_ParallelDDLMode(step, first_exec_node);
pfree_ext(step);
}
}
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && u_sess->attr.attr_sql.enable_parallel_ddl) {
RemoveObjectsonMainExecCN((DropStmt*)parse_tree, new_objects, is_first_node);
} else {
if (IS_SINGLE_NODE) {
RemoveObjects((DropStmt*)parse_tree, true);
} else {
if (u_sess->attr.attr_sql.enable_parallel_ddl)
RemoveObjects((DropStmt*)parse_tree, false);
else
RemoveObjects((DropStmt*)parse_tree, true);
}
}
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_DATANODES)
ExecUtilityStmtOnNodes_ParallelDDLMode(
tmp_queryString->data[0] ? tmp_queryString->data : query_string,
exec_nodes,
sent_to_remote,
false,
EXEC_ON_DATANODES,
is_temp,
first_exec_node);
} else {
ExecUtilityStmtOnNodes(tmp_queryString->data[0] ? tmp_queryString->data : query_string,
exec_nodes,
sent_to_remote,
false,
exec_type,
is_temp);
}
}
pfree_ext(tmp_queryString->data);
pfree_ext(tmp_queryString);
FreeExecNodes(&exec_nodes);
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && new_objects != NULL)
free_object_addresses(new_objects);
#endif
} break;
default: {
#ifdef PGXC
bool is_temp = false;
RemoteQueryExecType exec_type = EXEC_ON_ALL_NODES;
/* Check restrictions on objects dropped */
drop_stmt_pre_treatment((DropStmt*)parse_tree, query_string, sent_to_remote, &is_temp, &exec_type);
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
char* first_exec_node = NULL;
bool is_first_node = false;
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
first_exec_node = find_first_exec_cn();
is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
}
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() &&
(exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_COORDS)) {
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
RemoteQuery* step = makeNode(RemoteQuery);
step->combine_type = COMBINE_TYPE_SAME;
step->sql_statement = (char*)query_string;
step->exec_type = EXEC_ON_COORDS;
step->exec_nodes = NULL;
step->is_temp = false;
ExecRemoteUtility_ParallelDDLMode(step, first_exec_node);
pfree_ext(step);
}
}
#endif
RemoveObjects((DropStmt*)parse_tree, true);
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_DATANODES)
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, is_temp, first_exec_node);
} else {
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, exec_type, is_temp);
}
}
#endif
} break;
}
break;
case T_TruncateStmt:
#ifdef PGXC
/*
* In Postgres-XC, TRUNCATE needs to be launched to remote nodes
* before AFTER triggers. As this needs an internal control it is
* managed by this function internally.
*/
ExecuteTruncate((TruncateStmt*)parse_tree, query_string);
#else
ExecuteTruncate((TruncateStmt*)parse_tree);
#endif
break;
case T_CommentStmt:
CommentObject((CommentStmt*)parse_tree);
#ifdef PGXC
/* Comment objects depending on their object and temporary types */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
bool is_temp = false;
ExecNodes* exec_nodes = NULL;
CommentStmt* stmt = (CommentStmt*)parse_tree;
RemoteQueryExecType exec_type = get_nodes_4_comment_utility(stmt, &is_temp, &exec_nodes);
ExecUtilityStmtOnNodes(query_string, exec_nodes, sent_to_remote, false, exec_type, is_temp);
FreeExecNodes(&exec_nodes);
}
#endif
break;
case T_SecLabelStmt:
#ifdef PGXC
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SECURITY LABEL is not yet supported.")));
#endif /* PGXC */
ExecSecLabelStmt((SecLabelStmt*)parse_tree);
break;
case T_CopyStmt: {
if (((CopyStmt*)parse_tree)->filename != NULL && isSecurityMode && !IsInitdb) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_OPERATION),
errmsg("operation copy with file is forbidden in security mode.")));
}
uint64 processed;
processed = DoCopy((CopyStmt*)parse_tree, query_string);
if (completion_tag != NULL) {
errorno = snprintf_s(completion_tag,
COMPLETION_TAG_BUFSIZE,
COMPLETION_TAG_BUFSIZE - 1,
"COPY " UINT64_FORMAT,
processed);
securec_check_ss(errorno, "\0", "\0");
}
report_utility_time(parse_tree);
} break;
case T_PrepareStmt:
CheckRestrictedOperation("PREPARE");
PrepareQuery((PrepareStmt*)parse_tree, query_string);
break;
case T_ExecuteStmt:
/*
* Check the prepared stmt is ok for executing directly, otherwise
* RePrepareQuery proc should be called to re-generated a new prepared stmt.
*/
if (needRecompileQuery((ExecuteStmt*)parse_tree))
RePrepareQuery((ExecuteStmt*)parse_tree);
ExecuteQuery((ExecuteStmt*)parse_tree, NULL, query_string, params, dest, completion_tag);
break;
case T_DeallocateStmt:
CheckRestrictedOperation("DEALLOCATE");
DeallocateQuery((DeallocateStmt*)parse_tree);
break;
/*
* schema
*/
case T_RenameStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
RenameStmt* stmt = (RenameStmt*)parse_tree;
RemoteQueryExecType exec_type;
bool is_temp = false;
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
CheckObjectInBlackList(stmt->renameType, query_string);
/* Try to use the object relation if possible */
if (stmt->relation) {
/*
* When a relation is defined, it is possible that this object does
* not exist but an IF EXISTS clause might be used. So we do not do
* any error check here but block the access to remote nodes to
* this object as it does not exisy
*/
Oid rel_id = RangeVarGetRelid(stmt->relation, AccessShareLock, true);
if (OidIsValid(rel_id)) {
// Check relations's internal mask
Relation rel = relation_open(rel_id, NoLock);
if ((RelationGetInternalMask(rel) & INTERNAL_MASK_DALTER))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("internal relation doesn't allow ALTER")));
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("target table is a foreign table")));
if (RelationIsPAXFormat(rel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("RENAME operation is not supported for DFS table.")));
}
relation_close(rel, NoLock);
UnlockRelationOid(rel_id, AccessShareLock);
exec_type = ExecUtilityFindNodes(stmt->renameType, rel_id, &is_temp);
} else
exec_type = EXEC_ON_NONE;
} else {
exec_type = ExecUtilityFindNodes(stmt->renameType, InvalidOid, &is_temp);
}
/* Clean also remote Coordinators */
if (stmt->renameType == OBJECT_DATABASE) {
/* clean all connections with dbname on all CNs before db operations */
PreCleanAndCheckConns(stmt->subname, stmt->missing_ok);
} else if (stmt->renameType == OBJECT_USER || stmt->renameType == OBJECT_ROLE) {
/* clean all connections with username on all CNs before user operations */
PreCleanAndCheckUserConns(stmt->subname, stmt->missing_ok);
}
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node &&
(exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_COORDS)) {
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
NULL,
sent_to_remote,
false,
EXEC_ON_COORDS,
is_temp,
first_exec_node,
(Node*)parse_tree);
}
ExecRenameStmt((RenameStmt*)parse_tree);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_DATANODES)
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
NULL,
sent_to_remote,
false,
EXEC_ON_DATANODES,
is_temp,
first_exec_node,
(Node*)parse_tree);
} else {
ExecUtilityStmtOnNodes(
query_string, NULL, sent_to_remote, false, exec_type, is_temp, (Node*)parse_tree);
}
} else {
if (IS_SINGLE_NODE) {
CheckObjectInBlackList(((RenameStmt*)parse_tree)->renameType, query_string);
}
ExecRenameStmt((RenameStmt*)parse_tree);
}
#else
ExecRenameStmt((RenameStmt*)parse_tree);
#endif
break;
case T_AlterObjectSchemaStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
AlterObjectSchemaStmt* stmt = (AlterObjectSchemaStmt*)parse_tree;
RemoteQueryExecType exec_type;
bool is_temp = false;
CheckObjectInBlackList(stmt->objectType, query_string);
/* Try to use the object relation if possible */
if (stmt->relation) {
/*
* When a relation is defined, it is possible that this object does
* not exist but an IF EXISTS clause might be used. So we do not do
* any error check here but block the access to remote nodes to
* this object as it does not exisy
*/
Oid rel_id = RangeVarGetRelid(stmt->relation, AccessShareLock, true);
if (OidIsValid(rel_id)) {
Relation rel = relation_open(rel_id, NoLock);
if ((RelationGetInternalMask(rel) & INTERNAL_MASK_DALTER))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("internal relation doesn't allow ALTER")));
if (rel->rd_rel->relkind == RELKIND_RELATION && RelationIsPAXFormat(rel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature"),
errdetail("DFS table doesn't allow ALTER TABLE SET SCHEMA")));
}
relation_close(rel, NoLock);
UnlockRelationOid(rel_id, AccessShareLock);
exec_type = ExecUtilityFindNodes(stmt->objectType, rel_id, &is_temp);
} else
exec_type = EXEC_ON_NONE;
} else {
exec_type = ExecUtilityFindNodes(stmt->objectType, InvalidOid, &is_temp);
}
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_COORDS) {
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
NULL,
sent_to_remote,
false,
EXEC_ON_COORDS,
is_temp,
first_exec_node,
(Node*)parse_tree);
}
}
ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt*)parse_tree);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_DATANODES) {
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
NULL,
sent_to_remote,
false,
EXEC_ON_DATANODES,
is_temp,
first_exec_node,
(Node*)parse_tree);
}
} else {
ExecUtilityStmtOnNodes(
query_string, NULL, sent_to_remote, false, exec_type, is_temp, (Node*)parse_tree);
}
} else {
if (IS_SINGLE_NODE) {
CheckObjectInBlackList(((AlterObjectSchemaStmt*)parse_tree)->objectType, query_string);
}
ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt*)parse_tree);
}
#else
ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt*)parse_tree);
#endif
break;
case T_AlterOwnerStmt:
CheckObjectInBlackList(((AlterOwnerStmt*)parse_tree)->objectType, query_string);
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
ExecNodes* exec_nodes = NULL;
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
AlterOwnerStmt* OwnerStmt = (AlterOwnerStmt*)parse_tree;
if (OwnerStmt->objectType == OBJECT_FUNCTION) {
Oid funcid = LookupFuncNameTypeNames(OwnerStmt->object, OwnerStmt->objarg, false);
exec_nodes = GetFunctionNodes(funcid);
}
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
ExecAlterOwnerStmt((AlterOwnerStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, exec_nodes, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
ExecAlterOwnerStmt((AlterOwnerStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, exec_nodes, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
ExecAlterOwnerStmt((AlterOwnerStmt*)parse_tree);
}
#else
ExecAlterOwnerStmt((AlterOwnerStmt*)parse_tree);
#endif
break;
case T_AlterTableStmt: {
AlterTableStmt* atstmt = (AlterTableStmt*)parse_tree;
LOCKMODE lockmode;
char* first_exec_node = NULL;
bool is_first_node = false;
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
first_exec_node = find_first_exec_cn();
is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
}
/*
* Figure out lock mode, and acquire lock. This also does
* basic permissions checks, so that we won't wait for a lock
* on (for example) a relation on which we have no
* permissions.
*/
lockmode = AlterTableGetLockLevel(atstmt->cmds);
#ifdef PGXC
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (!sent_to_remote) {
bool isTemp = false;
RemoteQueryExecType exec_type;
RemoteQuery* step = makeNode(RemoteQuery);
Oid rel_id = RangeVarGetRelid(atstmt->relation, lockmode, true);
if (OidIsValid(rel_id)) {
exec_type = ExecUtilityFindNodes(atstmt->relkind, rel_id, &isTemp);
if (exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_COORDS)
step->exec_type = EXEC_ON_COORDS;
else
step->exec_type = EXEC_ON_NONE;
step->combine_type = COMBINE_TYPE_SAME;
step->sql_statement = (char*)query_string;
step->is_temp = isTemp;
step->exec_nodes = NULL;
UnlockRelationOid(rel_id, lockmode);
ExecRemoteUtility_ParallelDDLMode(step, first_exec_node);
pfree_ext(step);
}
}
}
}
#endif
Oid rel_id;
List* stmts = NIL;
ListCell* l = NULL;
char* drop_seq_string = NULL;
ExecNodes* exec_nodes = NULL;
rel_id = AlterTableLookupRelation(atstmt, lockmode);
elog(DEBUG1,
"[GET LOCK] Get the lock %d successfully on relation %s for altering operator.",
lockmode,
atstmt->relation->relname);
if (OidIsValid(rel_id)) {
/* Run parse analysis ... */
stmts = transformAlterTableStmt(rel_id, atstmt, query_string);
#ifdef PGXC
/*
* Add a RemoteQuery node for a query at top level on a remote
* Coordinator, if not already done so
*/
if (!sent_to_remote) {
/* nodegroup attch execnodes */
add_remote_query_4_alter_stmt(is_first_node, atstmt, query_string, &stmts, &drop_seq_string, &exec_nodes);
}
#endif
/* ... and do it */
foreach (l, stmts) {
Node* stmt = (Node*)lfirst(l);
if (IsA(stmt, AlterTableStmt)) {
/* Do the table alteration proper */
AlterTable(rel_id, lockmode, (AlterTableStmt*)stmt);
} else {
/* Recurse for anything else */
ProcessUtility(stmt,
query_string,
params,
false,
None_Receiver,
#ifdef PGXC
true,
#endif /* PGXC */
NULL);
}
/* Need CCI between commands */
if (lnext(l) != NULL)
CommandCounterIncrement();
}
#ifdef ENABLE_MULTIPLE_NODES
if (drop_seq_string != NULL) {
Assert(exec_nodes != NULL);
exec_remote_query_4_seq(exec_nodes, drop_seq_string, INVALIDSEQUUID);
}
#endif
} else {
ereport(NOTICE, (errmsg("relation \"%s\" does not exist, skipping", atstmt->relation->relname)));
}
report_utility_time(parse_tree);
pfree_ext(drop_seq_string);
} break;
case T_AlterDomainStmt:
#ifdef ENABLE_MULTIPLE_NODES
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("domain is not yet supported.")));
#endif /* PGXC */
{
AlterDomainStmt* stmt = (AlterDomainStmt*)parse_tree;
/*
* Some or all of these functions are recursive to cover
* inherited things, so permission checks are done there.
*/
switch (stmt->subtype) {
case 'T': /* ALTER DOMAIN DEFAULT */
/*
* Recursively alter column default for table and, if
* requested, for descendants
*/
AlterDomainDefault(stmt->typname, stmt->def);
break;
case 'N': /* ALTER DOMAIN DROP NOT NULL */
AlterDomainNotNull(stmt->typname, false);
break;
case 'O': /* ALTER DOMAIN SET NOT NULL */
AlterDomainNotNull(stmt->typname, true);
break;
case 'C': /* ADD CONSTRAINT */
AlterDomainAddConstraint(stmt->typname, stmt->def);
break;
case 'X': /* DROP CONSTRAINT */
AlterDomainDropConstraint(stmt->typname, stmt->name, stmt->behavior, stmt->missing_ok);
break;
case 'V': /* VALIDATE CONSTRAINT */
AlterDomainValidateConstraint(stmt->typname, stmt->name);
break;
default: /* oops */
{
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized alter domain type: %d", (int)stmt->subtype)));
} break;
}
}
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_GrantStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
RemoteQueryExecType remoteExecType = EXEC_ON_ALL_NODES;
GrantStmt* stmt = (GrantStmt*)parse_tree;
bool is_temp = false;
ExecNodes* exec_nodes = NULL;
/* Launch GRANT on Coordinator if object is a sequence */
if ((stmt->objtype == ACL_OBJECT_RELATION && stmt->targtype == ACL_TARGET_OBJECT)) {
/*
* In case object is a relation, differenciate the case
* of a sequence, a view and a table
*/
ListCell* cell = NULL;
/* Check the list of objects */
bool first = true;
RemoteQueryExecType type_local = remoteExecType;
foreach (cell, stmt->objects) {
RangeVar* relvar = (RangeVar*)lfirst(cell);
Oid rel_id = RangeVarGetRelid(relvar, NoLock, true);
/* Skip if object does not exist */
if (!OidIsValid(rel_id))
continue;
remoteExecType = exec_utility_find_nodes_relkind(rel_id, &is_temp);
/* Check if object node type corresponds to the first one */
if (first) {
type_local = remoteExecType;
first = false;
} else {
if (type_local != remoteExecType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PGXC does not support GRANT on multiple object types"),
errdetail("Grant VIEW/TABLE with separate queries")));
}
}
} else if (stmt->objtype == ACL_OBJECT_NODEGROUP && stmt->targtype == ACL_TARGET_OBJECT) {
/* For NodeGroup's grant/revoke operation we only issue comments on CN nodes */
remoteExecType = EXEC_ON_COORDS;
}
if (remoteExecType != EXEC_ON_COORDS &&
(stmt->objtype == ACL_OBJECT_RELATION || stmt->objtype == ACL_OBJECT_SEQUENCE ||
stmt->objtype == ACL_OBJECT_FUNCTION)) {
/* Only for tables, foreign tables, sequences and functions, not views */
Oid group_oid = GrantStmtGetNodeGroup(stmt);
if (!OidIsValid(group_oid))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("NOT-SUPPORT: Not support Grant/Revoke privileges"
" to objects in different nodegroup")));
exec_nodes = GetNodeGroupExecNodes(group_oid);
}
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (remoteExecType == EXEC_ON_ALL_NODES || remoteExecType == EXEC_ON_COORDS) {
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
NULL,
sent_to_remote,
false,
EXEC_ON_COORDS,
is_temp,
first_exec_node,
(Node*)stmt);
}
}
ExecuteGrantStmt((GrantStmt*)parse_tree);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (remoteExecType == EXEC_ON_ALL_NODES || remoteExecType == EXEC_ON_DATANODES) {
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
exec_nodes,
sent_to_remote,
false,
EXEC_ON_DATANODES,
is_temp,
first_exec_node,
(Node*)stmt);
}
} else {
ExecUtilityStmtOnNodes(
query_string, exec_nodes, sent_to_remote, false, remoteExecType, is_temp, (Node*)stmt);
}
} else {
ExecuteGrantStmt((GrantStmt*)parse_tree);
}
#else
ExecuteGrantStmt((GrantStmt*)parse_tree);
#endif
break;
case T_GrantRoleStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
GrantRole((GrantRoleStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
GrantRole((GrantRoleStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
GrantRole((GrantRoleStmt*)parse_tree);
}
#else
GrantRole((GrantRoleStmt*)parse_tree);
#endif
break;
case T_AlterDefaultPrivilegesStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt*)parse_tree);
}
#else
ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt*)parse_tree);
#endif
break;
/*
* **************** object creation / destruction *****************
*/
case T_DefineStmt: {
DefineStmt* stmt = (DefineStmt*)parse_tree;
switch (stmt->kind) {
case OBJECT_AGGREGATE:
#ifdef ENABLE_MULTIPLE_NODES
if (!u_sess->attr.attr_common.IsInplaceUpgrade && !u_sess->exec_cxt.extension_is_valid)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("user defined aggregate is not yet supported.")));
#endif /* ENABLE_MULTIPLE_NODES */
DefineAggregate(stmt->defnames, stmt->args, stmt->oldstyle, stmt->definition);
break;
case OBJECT_OPERATOR:
#ifdef PGXC
if (!u_sess->attr.attr_common.IsInplaceUpgrade && !u_sess->exec_cxt.extension_is_valid)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("user defined operator is not yet supported.")));
#endif /* PGXC */
AssertEreport(stmt->args == NIL, MOD_EXECUTOR, "stmt args is NULL");
DefineOperator(stmt->defnames, stmt->definition);
break;
case OBJECT_TYPE:
AssertEreport(stmt->args == NIL, MOD_EXECUTOR, "stmt args is NULL");
DefineType(stmt->defnames, stmt->definition);
break;
case OBJECT_TSPARSER:
#ifdef PGXC
if (!IsInitdb) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("user-defined text search parser is not yet supported.")));
}
#endif /* PGXC */
AssertEreport(stmt->args == NIL, MOD_EXECUTOR, "stmt args is NULL");
DefineTSParser(stmt->defnames, stmt->definition);
break;
case OBJECT_TSDICTIONARY:
/* not support with 300 */
ts_check_feature_disable();
AssertEreport(stmt->args == NIL, MOD_EXECUTOR, "stmt args is NULL");
DefineTSDictionary(stmt->defnames, stmt->definition);
break;
case OBJECT_TSTEMPLATE:
#ifdef PGXC
/*
* An erroneous text search template definition could confuse or
* even crash the server, so we just forbid user to create a user
* defined text search template definition
*/
if (!IsInitdb) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("user-defined text search template is not yet supported.")));
}
#endif /* PGXC */
AssertEreport(stmt->args == NIL, MOD_EXECUTOR, "stmt args is NULL");
DefineTSTemplate(stmt->defnames, stmt->definition);
break;
case OBJECT_TSCONFIGURATION:
ts_check_feature_disable();
/* use 'args' filed to record configuration options */
DefineTSConfiguration(stmt->defnames, stmt->definition, stmt->args);
break;
case OBJECT_COLLATION:
#ifdef PGXC
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("user defined collation is not yet supported.")));
#endif /* PGXC */
AssertEreport(stmt->args == NIL, MOD_EXECUTOR, "stmt args is NULL");
DefineCollation((const List*)stmt->defnames, (const List*)stmt->definition);
break;
default: {
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized define stmt type: %d", (int)stmt->kind)));
} break;
}
}
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_CompositeTypeStmt: /* CREATE TYPE (composite) */
{
CompositeTypeStmt* stmt = (CompositeTypeStmt*)parse_tree;
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
DefineCompositeType(stmt->typevar, stmt->coldeflist);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
DefineCompositeType(stmt->typevar, stmt->coldeflist);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else
#endif
{
DefineCompositeType(stmt->typevar, stmt->coldeflist);
}
} break;
case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */
{
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
DefineEnum((CreateEnumStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
DefineEnum((CreateEnumStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else
#endif
{
DefineEnum((CreateEnumStmt*)parse_tree);
}
} break;
case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */
#ifdef ENABLE_MULTIPLE_NODES
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("user defined range type is not yet supported.")));
#endif /* ENABLE_MULTIPLE_NODES */
DefineRange((CreateRangeStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_AlterEnumStmt: /* ALTER TYPE (enum) */
{
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord())
#endif
{
/*
* We disallow this in transaction blocks, because we can't cope
* with enum OID values getting into indexes and then having
* their defining pg_enum entries go away.
*/
PreventTransactionChain(is_top_level, "ALTER TYPE ... ADD");
}
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
AlterEnum((AlterEnumStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
AlterEnum((AlterEnumStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else
#endif
{
AlterEnum((AlterEnumStmt*)parse_tree);
}
} break;
case T_ViewStmt: /* CREATE VIEW */
{
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
/*
* Run parse analysis to convert the raw parse tree to a Query. Note this
* also acquires sufficient locks on the source table(s).
*
* Since parse analysis scribbles on its input, copy the raw parse tree;
* this ensures we don't corrupt a prepared statement, for example.
*/
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
bool is_temp = IsViewTemp((ViewStmt*)parse_tree, query_string);
if (!is_temp) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
}
DefineView((ViewStmt*)parse_tree, query_string, is_first_node);
if (!is_temp) {
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
NULL,
sent_to_remote,
false,
(u_sess->attr.attr_common.IsInplaceUpgrade ? EXEC_ON_DATANODES : EXEC_ON_NONE),
false,
first_exec_node);
}
} else {
DefineView((ViewStmt*)parse_tree, query_string, is_first_node);
char* schema_name = ((ViewStmt*)parse_tree)->view->schemaname;
if (schema_name == NULL)
schema_name = DatumGetCString(DirectFunctionCall1(current_schema, PointerGetDatum(NULL)));
bool temp_schema = false;
if (schema_name != NULL)
temp_schema = strncasecmp(schema_name, "pg_temp", 7) == 0 ? true : false;
if (!ExecIsTempObjectIncluded() && !temp_schema)
ExecUtilityStmtOnNodes(query_string,
NULL,
sent_to_remote,
false,
(u_sess->attr.attr_common.IsInplaceUpgrade ? EXEC_ON_ALL_NODES : EXEC_ON_COORDS),
false);
}
} else {
DefineView((ViewStmt*)parse_tree, query_string);
}
#else
DefineView((ViewStmt*)parse_tree, query_string);
#endif
} break;
case T_CreateFunctionStmt: /* CREATE FUNCTION */
{
CreateFunction((CreateFunctionStmt*)parse_tree, query_string);
#ifdef PGXC
Oid group_oid;
bool multi_group = false;
ExecNodes* exec_nodes = NULL;
const char* query_str = NULL;
if (IS_PGXC_COORDINATOR) {
group_oid = GetFunctionNodeGroup((CreateFunctionStmt*)parse_tree, &multi_group);
if (multi_group) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Does not support FUNCTION with multiple nodegroup table type in logic cluster."),
errdetail("The feature is not currently supported")));
}
query_str = GetCreateFuncStringInDN((CreateFunctionStmt*)parse_tree, query_string);
if (OidIsValid(group_oid)) {
exec_nodes = GetNodeGroupExecNodes(group_oid);
}
ExecUtilityStmtOnNodes(
query_str, exec_nodes, sent_to_remote, false, CHOOSE_EXEC_NODES(ExecIsTempObjectIncluded()), false);
FreeExecNodes(&exec_nodes);
if (query_str != query_string)
pfree_ext(query_str);
}
#endif
} break;
case T_AlterFunctionStmt: /* ALTER FUNCTION */
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
ExecNodes* exec_nodes = NULL;
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
bool is_temp = IsFunctionTemp((AlterFunctionStmt*)parse_tree);
if (!is_temp) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
}
AlterFunction((AlterFunctionStmt*)parse_tree);
Oid group_oid = GetFunctionNodeGroup((AlterFunctionStmt*)parse_tree);
if (OidIsValid(group_oid)) {
exec_nodes = GetNodeGroupExecNodes(group_oid);
}
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, exec_nodes, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
AlterFunction((AlterFunctionStmt*)parse_tree);
Oid group_oid = GetFunctionNodeGroup((AlterFunctionStmt*)parse_tree);
if (OidIsValid(group_oid)) {
exec_nodes = GetNodeGroupExecNodes(group_oid);
}
ExecUtilityStmtOnNodes(query_string,
exec_nodes,
sent_to_remote,
false,
CHOOSE_EXEC_NODES(ExecIsTempObjectIncluded()),
false);
}
FreeExecNodes(&exec_nodes);
} else {
AlterFunction((AlterFunctionStmt*)parse_tree);
}
#else
AlterFunction((AlterFunctionStmt*)parse_tree);
#endif
break;
case T_IndexStmt: /* CREATE INDEX */
{
IndexStmt* stmt = (IndexStmt*)parse_tree;
Oid rel_id;
LOCKMODE lockmode;
Oid indexRelOid;
if (stmt->concurrent) {
PreventTransactionChain(is_top_level, "CREATE INDEX CONCURRENTLY");
}
/* forbid user to set or change inner options */
ForbidOutUsersToSetInnerOptions(stmt->options);
#ifdef PGXC
bool is_temp = false;
ExecNodes* exec_nodes = NULL;
RemoteQueryExecType exec_type = EXEC_ON_ALL_NODES;
char* first_exec_node = NULL;
bool is_first_node = false;
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
first_exec_node = find_first_exec_cn();
is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
}
#ifdef ENABLE_MULTIPLE_NODES
if (stmt->concurrent) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PGXC does not support concurrent INDEX yet"),
errdetail("The feature is not currently supported")));
}
#endif
/* INDEX on a temporary table cannot use 2PC at commit */
rel_id = RangeVarGetRelidExtended(stmt->relation, AccessShareLock, true, false, false, true, NULL, NULL);
if (OidIsValid(rel_id)) {
exec_type = ExecUtilityFindNodes(OBJECT_INDEX, rel_id, &is_temp);
UnlockRelationOid(rel_id, AccessShareLock);
} else {
exec_type = EXEC_ON_NONE;
}
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && !stmt->isconstraint) {
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node &&
(exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_COORDS)) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, stmt->concurrent, EXEC_ON_COORDS, is_temp, first_exec_node);
}
}
#endif
/*
* Look up the relation OID just once, right here at the
* beginning, so that we don't end up repeating the name
* lookup later and latching onto a different relation
* partway through. To avoid lock upgrade hazards, it's
* important that we take the strongest lock that will
* eventually be needed here, so the lockmode calculation
* needs to match what DefineIndex() does.
*/
lockmode = stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock;
rel_id = RangeVarGetRelidExtended(
stmt->relation, lockmode, false, false, false, true, RangeVarCallbackOwnsRelation, NULL);
/* Run parse analysis ... */
stmt = transformIndexStmt(rel_id, stmt, query_string);
#ifdef PGXC
/* Find target datanode list that we need send CREATE-INDEX on.
* If it's a view, skip it. */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && get_rel_relkind(rel_id) == RELKIND_RELATION) {
int nmembers = 0;
Oid group_oid = InvalidOid;
Oid* members = NULL;
exec_nodes = makeNode(ExecNodes);
char in_redistribution = 'n';
AssertEreport(rel_id != InvalidOid, MOD_EXECUTOR, "relation OID is invalid");
/* Get nodegroup Oid from the index's base table */
group_oid = get_pgxc_class_groupoid(rel_id);
AssertEreport(group_oid != InvalidOid, MOD_EXECUTOR, "group OID is invalid");
in_redistribution = get_pgxc_group_redistributionstatus(group_oid);
char* group_name = get_pgxc_groupname(group_oid);
/* Get node list and appending to exec_nodes */
if (need_full_dn_execution(group_name)) {
/* Sepcial path, issue full-DN create index request */
exec_nodes->nodeList = GetAllDataNodes();
} else {
nmembers = get_pgxc_groupmembers(group_oid, &members);
exec_nodes->nodeList = GetNodeGroupNodeList(members, nmembers);
pfree_ext(members);
}
}
#endif
pgstat_set_io_state(IOSTATE_WRITE);
/* ... and do it */
WaitState oldStatus = pgstat_report_waitstatus(STATE_CREATE_INDEX);
indexRelOid = DefineIndex(rel_id,
stmt,
InvalidOid, /* no predefined OID */
false, /* is_alter_table */
true, /* check_rights */
!u_sess->upg_cxt.new_catalog_need_storage, /* skip_build */
false); /* quiet */
CreateForeignIndex(stmt, indexRelOid);
(void)pgstat_report_waitstatus(oldStatus);
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !stmt->isconstraint && !IsConnFromCoord()) {
query_string = ConstructMesageWithMemInfo(query_string, stmt->memUsage);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node &&
(exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_DATANODES)) {
ExecUtilityStmtOnNodes(
query_string, exec_nodes, sent_to_remote, stmt->concurrent, EXEC_ON_DATANODES, is_temp);
} else {
ExecUtilityStmtOnNodes(query_string, exec_nodes, sent_to_remote, stmt->concurrent, exec_type, is_temp);
}
}
FreeExecNodes(&exec_nodes);
#endif
} break;
case T_RuleStmt: /* CREATE RULE */
#ifdef ENABLE_MULTIPLE_NODES
if (!IsInitdb && !u_sess->attr.attr_sql.enable_cluster_resize && !u_sess->exec_cxt.extension_is_valid)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("RULE is not yet supported.")));
#endif /* ENABLE_MULTIPLE_NODES */
DefineRule((RuleStmt*)parse_tree, query_string);
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
RemoteQueryExecType exec_type;
bool is_temp = false;
exec_type = get_nodes_4_rules_utility(((RuleStmt*)parse_tree)->relation, &is_temp);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, exec_type, is_temp);
}
#endif
break;
case T_CreateSeqStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
CreateSeqStmt* stmt = (CreateSeqStmt*)parse_tree;
ExecNodes* exec_nodes = NULL;
char* queryStringWithUUID = gen_hybirdmsg_for_CreateSeqStmt(stmt, query_string);
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
/* In case this query is related to a SERIAL execution, just bypass */
if (!stmt->is_serial) {
bool is_temp = stmt->sequence->relpersistence == RELPERSISTENCE_TEMP;
if (!is_temp) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
queryStringWithUUID, NULL, sent_to_remote, false, EXEC_ON_COORDS, is_temp, first_exec_node);
}
}
}
DefineSequence((CreateSeqStmt*)parse_tree);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
/* In case this query is related to a SERIAL execution, just bypass */
if (!stmt->is_serial) {
bool is_temp = stmt->sequence->relpersistence == RELPERSISTENCE_TEMP;
exec_nodes = GetOwnedByNodes((Node*)stmt);
ExecUtilityStmtOnNodes_ParallelDDLMode(queryStringWithUUID,
exec_nodes,
sent_to_remote,
false,
EXEC_ON_DATANODES,
is_temp,
first_exec_node);
}
} else {
/* In case this query is related to a SERIAL execution, just bypass */
if (!stmt->is_serial) {
bool is_temp = stmt->sequence->relpersistence == RELPERSISTENCE_TEMP;
exec_nodes = GetOwnedByNodes((Node*)stmt);
/* Set temporary object flag in pooler */
ExecUtilityStmtOnNodes(
queryStringWithUUID, exec_nodes, sent_to_remote, false, CHOOSE_EXEC_NODES(is_temp), is_temp);
}
}
#ifdef ENABLE_MULTIPLE_NODES
if (IS_MAIN_COORDINATOR && exec_nodes != NULL &&
exec_nodes->nodeList->length < u_sess->pgxc_cxt.NumDataNodes) {
/* NodeGroup: Create sequence in other datanodes without owned by */
char* msg = deparse_create_sequence((Node*)parse_tree, true);
exec_remote_query_4_seq(exec_nodes, msg, stmt->uuid);
pfree_ext(msg);
}
#endif
pfree_ext(queryStringWithUUID);
FreeExecNodes(&exec_nodes);
} else {
DefineSequence((CreateSeqStmt*)parse_tree);
}
#else
DefineSequence((CreateSeqStmt*)parse_tree);
#endif
ClearCreateSeqStmtUUID((CreateSeqStmt*)parse_tree);
break;
case T_AlterSeqStmt:
#ifdef PGXC
if (IS_MAIN_COORDINATOR || IS_SINGLE_NODE) {
PreventAlterSeqInTransaction(is_top_level, (AlterSeqStmt*)parse_tree);
}
if (IS_PGXC_COORDINATOR) {
AlterSeqStmt* stmt = (AlterSeqStmt*)parse_tree;
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
ExecNodes* exec_nodes = NULL;
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
/* In case this query is related to a SERIAL execution, just bypass */
if (!stmt->is_serial) {
bool is_temp = false;
RemoteQueryExecType exec_type;
Oid rel_id = RangeVarGetRelid(stmt->sequence, NoLock, stmt->missing_ok);
if (!OidIsValid(rel_id))
break;
exec_type = ExecUtilityFindNodes(OBJECT_SEQUENCE, rel_id, &is_temp);
if (exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_COORDS) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, is_temp, first_exec_node);
}
}
AlterSequence((AlterSeqStmt*)parse_tree);
#ifdef ENABLE_MULTIPLE_NODES
/* In case this query is related to a SERIAL execution, just bypass */
if (IS_MAIN_COORDINATOR && !stmt->is_serial) {
bool is_temp = false;
RemoteQueryExecType exec_type;
Oid rel_id = RangeVarGetRelid(stmt->sequence, NoLock, true);
if (!OidIsValid(rel_id))
break;
exec_type = ExecUtilityFindNodes(OBJECT_SEQUENCE, rel_id, &is_temp);
if (exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_DATANODES) {
exec_nodes = GetOwnedByNodes((Node*)stmt);
alter_sequence_all_nodes(stmt, exec_nodes);
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
exec_nodes,
sent_to_remote,
false,
EXEC_ON_DATANODES,
is_temp,
first_exec_node);
}
}
#endif
} else {
AlterSequence((AlterSeqStmt*)parse_tree);
#ifdef ENABLE_MULTIPLE_NODES
/* In case this query is related to a SERIAL execution, just bypass */
if (IS_MAIN_COORDINATOR && !stmt->is_serial) {
bool is_temp = false;
RemoteQueryExecType exec_type;
Oid rel_id = RangeVarGetRelid(stmt->sequence, NoLock, true);
if (!OidIsValid(rel_id))
break;
exec_type = ExecUtilityFindNodes(OBJECT_SEQUENCE, rel_id, &is_temp);
exec_nodes = GetOwnedByNodes((Node*)stmt);
alter_sequence_all_nodes(stmt, exec_nodes);
ExecUtilityStmtOnNodes(query_string, exec_nodes, sent_to_remote, false, exec_type, is_temp);
}
#endif
}
FreeExecNodes(&exec_nodes);
} else {
AlterSequence((AlterSeqStmt*)parse_tree);
}
#else
PreventAlterSeqInTransaction(is_top_level, (AlterSeqStmt*)parse_tree);
AlterSequence((AlterSeqStmt*)parse_tree);
#endif
break;
case T_DoStmt:
ExecuteDoStmt((DoStmt*)parse_tree);
break;
case T_CreatedbStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
PreventTransactionChain(is_top_level, "CREATE DATABASE");
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
exec_utility_with_message_parallel_ddl_mode(
query_string, sent_to_remote, false, first_exec_node, EXEC_ON_COORDS);
createdb((CreatedbStmt*)parse_tree);
exec_utility_with_message_parallel_ddl_mode(
query_string, sent_to_remote, false, first_exec_node, EXEC_ON_DATANODES);
} else {
createdb((CreatedbStmt*)parse_tree);
ExecUtilityWithMessage(query_string, sent_to_remote, false);
}
} else {
if (IS_SINGLE_NODE)
PreventTransactionChain(is_top_level, "CREATE DATABASE");
createdb((CreatedbStmt*)parse_tree);
}
#else
PreventTransactionChain(is_top_level, "CREATE DATABASE");
createdb((CreatedbStmt*)parse_tree);
#endif
break;
case T_AlterDatabaseStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
/*
* If this is not a SET TABLESPACE statement, just propogate the
* cmd as usual.
*/
if (!IsSetTableSpace((AlterDatabaseStmt*)parse_tree))
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
else
exec_utility_with_message_parallel_ddl_mode(
query_string, sent_to_remote, false, first_exec_node, EXEC_ON_COORDS);
AlterDatabase((AlterDatabaseStmt*)parse_tree, is_top_level);
if (!IsSetTableSpace((AlterDatabaseStmt*)parse_tree))
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
else
exec_utility_with_message_parallel_ddl_mode(
query_string, sent_to_remote, false, first_exec_node, EXEC_ON_DATANODES);
} else {
AlterDatabase((AlterDatabaseStmt*)parse_tree, is_top_level);
/*
* If this is not a SET TABLESPACE statement, just propogate the
* cmd as usual.
*/
if (!IsSetTableSpace((AlterDatabaseStmt*)parse_tree))
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
else
ExecUtilityWithMessage(query_string, sent_to_remote, false);
}
} else {
AlterDatabase((AlterDatabaseStmt*)parse_tree, is_top_level);
}
#else
AlterDatabase((AlterDatabaseStmt*)parse_tree, is_top_level);
#endif
break;
case T_AlterDatabaseSetStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
AlterDatabaseSet((AlterDatabaseSetStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
AlterDatabaseSet((AlterDatabaseSetStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
AlterDatabaseSet((AlterDatabaseSetStmt*)parse_tree);
}
#else
AlterDatabaseSet((AlterDatabaseSetStmt*)parse_tree);
#endif
break;
case T_DropdbStmt: {
DropdbStmt* stmt = (DropdbStmt*)parse_tree;
#ifdef PGXC
#ifdef ENABLE_MULTIPLE_NODES
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
/* clean all connections with dbname on all CNs before db operations */
PreCleanAndCheckConns(stmt->dbname, stmt->missing_ok);
/* Allow this to be run inside transaction block on remote nodes */
PreventTransactionChain(is_top_level, "DROP DATABASE");
}
#else
/* Disallow dropping db to be run inside transaction block on single node */
PreventTransactionChain(is_top_level, "DROP DATABASE");
#endif
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
dropdb(stmt->dbname, stmt->missing_ok);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
dropdb(stmt->dbname, stmt->missing_ok);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
dropdb(stmt->dbname, stmt->missing_ok);
}
#else
PreventTransactionChain(is_top_level, "DROP DATABASE");
dropdb(stmt->dbname, stmt->missing_ok);
#endif
} break;
/* Query-level asynchronous notification */
case T_NotifyStmt:
#ifdef PGXC
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("NOFITY statement is not yet supported.")));
#endif /* PGXC */
{
NotifyStmt* stmt = (NotifyStmt*)parse_tree;
PreventCommandDuringRecovery("NOTIFY");
Async_Notify(stmt->conditionname, stmt->payload);
}
break;
case T_ListenStmt:
#ifdef PGXC
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("LISTEN statement is not yet supported.")));
#endif /* PGXC */
{
ListenStmt* stmt = (ListenStmt*)parse_tree;
PreventCommandDuringRecovery("LISTEN");
CheckRestrictedOperation("LISTEN");
Async_Listen(stmt->conditionname);
}
break;
case T_UnlistenStmt:
#ifdef PGXC
ereport(
ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("UNLISTEN statement is not yet supported.")));
#endif /* PGXC */
{
UnlistenStmt* stmt = (UnlistenStmt*)parse_tree;
PreventCommandDuringRecovery("UNLISTEN");
CheckRestrictedOperation("UNLISTEN");
if (stmt->conditionname)
Async_Unlisten(stmt->conditionname);
else
Async_UnlistenAll();
}
break;
case T_LoadStmt:
#ifdef PGXC
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("LOAD statement is not yet supported.")));
#endif /* PGXC */
{
LoadStmt* stmt = (LoadStmt*)parse_tree;
closeAllVfds(); /* probably not necessary... */
/* Allowed names are restricted if you're not superuser */
load_file(stmt->filename, !superuser());
}
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false);
#endif
break;
case T_ClusterStmt:
/* we choose to allow this during "read only" transactions */
PreventCommandDuringRecovery("CLUSTER");
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
ExecNodes* exec_nodes = NULL;
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
bool is_temp = false;
RemoteQueryExecType exec_type = EXEC_ON_ALL_NODES;
ClusterStmt* cstmt = (ClusterStmt*)parse_tree;
if (cstmt->relation) {
Oid rel_id = RangeVarGetRelid(cstmt->relation, NoLock, true);
(void)ExecUtilityFindNodes(OBJECT_TABLE, rel_id, &is_temp);
exec_type = CHOOSE_EXEC_NODES(is_temp);
} else if (in_logic_cluster()) {
/*
* In logic cluster mode, superuser and system DBA can execute CLUSTER
* on all nodes; logic cluster users can execute CLUSTER on its node group;
* other users can't execute CLUSTER in DNs, only CLUSTER one table.
*/
Oid group_oid = get_current_lcgroup_oid();
if (OidIsValid(group_oid)) {
exec_nodes = GetNodeGroupExecNodes(group_oid);
} else if (!superuser()) {
exec_type = EXEC_ON_NONE;
ereport(NOTICE,
(errmsg("CLUSTER do not run in DNs because User \"%s\" don't "
"attach to any logic cluster.",
GetUserNameFromId(GetUserId()))));
}
}
query_string = ConstructMesageWithMemInfo(query_string, cstmt->memUsage);
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (!is_temp) {
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
NULL,
sent_to_remote,
true,
EXEC_ON_COORDS,
false,
first_exec_node,
(Node*)parse_tree);
}
}
cluster((ClusterStmt*)parse_tree, is_top_level);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
exec_nodes,
sent_to_remote,
true,
EXEC_ON_DATANODES,
false,
first_exec_node,
(Node*)parse_tree);
} else {
ExecUtilityStmtOnNodes(
query_string, exec_nodes, sent_to_remote, true, exec_type, false, (Node*)parse_tree);
}
FreeExecNodes(&exec_nodes);
} else {
cluster((ClusterStmt*)parse_tree, is_top_level);
}
#else
cluster((ClusterStmt*)parse_tree, is_top_level);
#endif
break;
case T_VacuumStmt: {
bool tmp_enable_autoanalyze = u_sess->attr.attr_sql.enable_autoanalyze;
VacuumStmt* stmt = (VacuumStmt*)parse_tree;
stmt->dest = dest;
// for vacuum lazy, no need do IO collection and IO scheduler
if (ENABLE_WORKLOAD_CONTROL && !(stmt->options & VACOPT_FULL) && u_sess->wlm_cxt->wlm_params.iotrack == 0)
WLMCleanIOHashTable();
/*
* "vacuum full compact" means "vacuum full" in fact
* for dfs table if VACOPT_COMPACT is enabled
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
if (stmt->options & VACOPT_COMPACT)
stmt->options |= VACOPT_FULL;
}
/* @hdfs
* isForeignTableAnalyze will be set to true when we need to
* analyze a foreign table/foreign tables
*/
bool isForeignTableAnalyze = IsHDFSForeignTableAnalyzable(stmt);
if (stmt->isMOTForeignTable && (stmt->options & VACOPT_VACUUM)) {
stmt->options |= VACOPT_FULL;
}
/*
* @hdfs
* Log in data node and run analyze foreign table/tables command is illegal.
* We need to do scheduling to run analyze foreign table/tables command which
* can only be done on coordinator node.
*/
if (IS_PGXC_DATANODE && isForeignTableAnalyze && !IsConnFromCoord()) {
ereport(WARNING,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Running analyze on table/tables reside in HDFS directly from data node is not "
"supported.")));
break;
}
/*
* @hdfs
* On data node, we got hybridmesage includeing data node No.
* We judge if analyze work belongs to us by PGXCNodeId. If not, we break out.
*/
if (IS_PGXC_DATANODE && IsConnFromCoord() && isForeignTableAnalyze &&
(u_sess->pgxc_cxt.PGXCNodeId != (int)stmt->nodeNo)) {
break;
}
if (stmt->isPgFdwForeignTables && stmt->va_cols) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE), errmsg("This relation doesn't support analyze with column.")));
}
/* @hdfs Process MPP Local table "vacuum" "analyze" command. */
/* forbid auto-analyze inside vacuum/analyze */
u_sess->attr.attr_sql.enable_autoanalyze = false;
pgstat_set_io_state(IOSTATE_VACUUM);
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
/* init the special nodeId identify which receive analyze command from client */
stmt->orgCnNodeNo = ~0;
}
DoVacuumMppTable(stmt, query_string, is_top_level, sent_to_remote);
u_sess->attr.attr_sql.enable_autoanalyze = tmp_enable_autoanalyze;
} break;
case T_ExplainStmt:
/*
* To pass all the llt, do not generate parallel plan
* if we need to explain the query plan.
*/
if (u_sess->opt_cxt.parallel_debug_mode == LLT_MODE) {
u_sess->opt_cxt.query_dop = 1;
u_sess->opt_cxt.skew_strategy_opt = SKEW_OPT_OFF;
}
/* Set PTFastQueryShippingStore value. */
PTFastQueryShippingStore = u_sess->attr.attr_sql.enable_fast_query_shipping;
ExplainQuery((ExplainStmt*)parse_tree, query_string, params, dest, completion_tag);
/* Reset u_sess->opt_cxt.query_dop. */
if (u_sess->opt_cxt.parallel_debug_mode == LLT_MODE) {
u_sess->opt_cxt.query_dop = u_sess->opt_cxt.query_dop_store;
u_sess->opt_cxt.skew_strategy_opt = u_sess->attr.attr_sql.skew_strategy_store;
}
/* Rest PTFastQueryShippingStore. */
PTFastQueryShippingStore = true;
break;
case T_CreateTableAsStmt:
ExecCreateTableAs((CreateTableAsStmt*)parse_tree, query_string, params, completion_tag);
break;
case T_AlterSystemStmt:
PreventTransactionChain(is_top_level, "ALTER SYSTEM SET");
AlterSystemSetConfigFile((AlterSystemStmt*)parse_tree);
break;
case T_VariableSetStmt:
ExecSetVariableStmt((VariableSetStmt*)parse_tree);
#ifdef PGXC
/* Let the pooler manage the statement */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
VariableSetStmt* stmt = (VariableSetStmt*)parse_tree;
char* mask_string = NULL;
mask_string = maskPassword(query_string);
if (mask_string == NULL)
mask_string = (char*)query_string;
// do not send the variable which in blacklist to other nodes
if (IsVariableinBlackList(stmt->name)) {
break;
}
if (u_sess->catalog_cxt.overrideStack && (!pg_strcasecmp(stmt->name, SEARCH_PATH_GUC_NAME) ||
!pg_strcasecmp(stmt->name, CURRENT_SCHEMA_GUC_NAME))) {
/*
* set search_path or current_schema inside stored procedure is invalid,
* do not send to other nodes.
*/
OverrideStackEntry* entry = NULL;
entry = (OverrideStackEntry*)linitial(u_sess->catalog_cxt.overrideStack);
if (entry->inProcedure)
break;
}
/*
* If command is local and we are not in a transaction block do NOT
* send this query to backend nodes, it is just bypassed by the backend.
*/
if (stmt->is_local) {
if (IsTransactionBlock()) {
if (PoolManagerSetCommand(POOL_CMD_LOCAL_SET, mask_string) < 0) {
/* Add retry query error code ERRCODE_SET_QUERY for error "ERROR SET query". */
ereport(ERROR, (errcode(ERRCODE_SET_QUERY), errmsg("Postgres-XC: ERROR SET query")));
}
}
} else {
/* when set command in function, treat it as in transaction. */
if (!IsTransactionBlock() && !(dest->mydest == DestSPI)) {
/* register the set message into pooler session params */
int ret = register_pooler_session_param(stmt->name, mask_string);
if (PoolManagerSetCommand(POOL_CMD_GLOBAL_SET, mask_string) < 0) {
/* unregister the set message */
if (ret == 0)
unregister_pooler_session_param(stmt->name);
/* Add retry query error code ERRCODE_SET_QUERY for error "ERROR SET query". */
ereport(ERROR, (errcode(ERRCODE_SET_QUERY), errmsg("Postgres-XC: ERROR SET query")));
}
} else {
ExecUtilityStmtOnNodes(mask_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
// Append set command to input_set_message, this will be sent to pool when this Transaction
// commit.
int ret = check_set_message_to_send(stmt, query_string);
if (ret != -1) {
if (ret == 0)
append_set_message(query_string);
else
make_set_message(); /* make new message string */
}
}
}
if (mask_string != query_string)
pfree(mask_string);
}
#endif
break;
case T_VariableShowStmt: {
VariableShowStmt* n = (VariableShowStmt*)parse_tree;
GetPGVariable(n->name, dest);
} break;
case T_ShutdownStmt: {
ShutdownStmt* n = (ShutdownStmt*)parse_tree;
DoShutdown(n);
} break;
case T_DiscardStmt:
#ifdef PGXC
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DISCARD statement is not yet supported.")));
#endif /* PGXC */
/* should we allow DISCARD PLANS? */
CheckRestrictedOperation("DISCARD");
DiscardCommand((DiscardStmt*)parse_tree, is_top_level);
#ifdef PGXC
/*
* Discard objects for all the sessions possible.
* For example, temporary tables are created on all Datanodes
* and Coordinators.
*/
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string,
NULL,
sent_to_remote,
true,
CHOOSE_EXEC_NODES(((DiscardStmt*)parse_tree)->target == DISCARD_TEMP),
false);
#endif
break;
case T_CreateTrigStmt:
(void)CreateTrigger(
(CreateTrigStmt*)parse_tree, query_string, InvalidOid, InvalidOid, InvalidOid, InvalidOid, false);
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
CreateTrigStmt* stmt = (CreateTrigStmt*)parse_tree;
RemoteQueryExecType exec_type;
bool is_temp = false;
Oid rel_id = RangeVarGetRelidExtended(stmt->relation, NoLock, false, false, false, true, NULL, NULL);
exec_type = ExecUtilityFindNodes(OBJECT_TABLE, rel_id, &is_temp);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, exec_type, is_temp, (Node*)stmt);
}
#endif
break;
case T_CreatePLangStmt:
#ifdef PGXC
if (!IsInitdb)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("new language is not yet supported.")));
#endif /* PGXC */
CreateProceduralLanguage((CreatePLangStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
/*
* ******************************** DOMAIN statements ****
*/
case T_CreateDomainStmt:
#ifdef ENABLE_MULTIPLE_NODES
if (!IsInitdb && !u_sess->attr.attr_common.IsInplaceUpgrade)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("domain is not yet supported.")));
#endif /* PGXC */
DefineDomain((CreateDomainStmt*)parse_tree);
#ifdef ENABLE_MULTIPLE_NODES
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
/*
* ******************************** ROLE statements ****
*/
case T_CreateRoleStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
CreateRole((CreateRoleStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
CreateRole((CreateRoleStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
CreateRole((CreateRoleStmt*)parse_tree);
}
#else
CreateRole((CreateRoleStmt*)parse_tree);
#endif
break;
case T_AlterRoleStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
AlterRole((AlterRoleStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
AlterRole((AlterRoleStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
AlterRole((AlterRoleStmt*)parse_tree);
}
#else
AlterRole((AlterRoleStmt*)parse_tree);
#endif
break;
case T_AlterRoleSetStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
AlterRoleSet((AlterRoleSetStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
AlterRoleSet((AlterRoleSetStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
AlterRoleSet((AlterRoleSetStmt*)parse_tree);
}
#else
AlterRoleSet((AlterRoleSetStmt*)parse_tree);
#endif
break;
case T_DropRoleStmt:
#ifdef PGXC
/* Clean connections before dropping a user on local node */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
ListCell* item = NULL;
foreach (item, ((DropRoleStmt*)parse_tree)->roles) {
const char* role = strVal(lfirst(item));
DropRoleStmt* stmt = (DropRoleStmt*)parse_tree;
/* clean all connections with role on all CNs */
PreCleanAndCheckUserConns(role, stmt->missing_ok);
}
}
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
DropRole((DropRoleStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
DropRole((DropRoleStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
DropRole((DropRoleStmt*)parse_tree);
}
#else
DropRole((DropRoleStmt*)parse_tree);
#endif
break;
case T_DropOwnedStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
DropOwnedObjects((DropOwnedStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
DropOwnedObjects((DropOwnedStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
DropOwnedObjects((DropOwnedStmt*)parse_tree);
}
#else
DropOwnedObjects((DropOwnedStmt*)parse_tree);
#endif
break;
case T_ReassignOwnedStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
ReassignOwnedObjects((ReassignOwnedStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
ReassignOwnedObjects((ReassignOwnedStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
ReassignOwnedObjects((ReassignOwnedStmt*)parse_tree);
}
#else
ReassignOwnedObjects((ReassignOwnedStmt*)parse_tree);
#endif
break;
case T_LockStmt:
/*
* Since the lock would just get dropped immediately, LOCK TABLE
* outside a transaction block is presumed to be user error.
*/
RequireTransactionChain(is_top_level, "LOCK TABLE");
#ifdef PGXC
/* only lock local table if cm_agent do Lock Stmt */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() &&
!(u_sess->attr.attr_common.xc_maintenance_mode &&
(strcmp(u_sess->attr.attr_common.application_name, "cm_agent") == 0))) {
ListCell* cell = NULL;
bool has_nontemp = false;
bool has_temp = false;
bool is_temp = false;
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
foreach (cell, ((LockStmt*)parse_tree)->relations) {
RangeVar* r = (RangeVar*)lfirst(cell);
Oid rel_id = RangeVarGetRelid(r, NoLock, true);
(void)ExecUtilityFindNodes(OBJECT_TABLE, rel_id, &is_temp);
has_temp |= is_temp;
has_nontemp |= !is_temp;
if (has_temp && has_nontemp)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("LOCK not supported for TEMP and non-TEMP objects together"),
errdetail("You should separate TEMP and non-TEMP objects")));
}
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
RemoteQuery* step = makeNode(RemoteQuery);
step->combine_type = COMBINE_TYPE_SAME;
step->sql_statement = (char*)query_string;
step->exec_type = has_temp ? EXEC_ON_NONE : EXEC_ON_COORDS;
step->exec_nodes = NULL;
step->is_temp = has_temp;
ExecRemoteUtility_ParallelDDLMode(step, first_exec_node);
pfree_ext(step);
}
LockTableCommand((LockStmt*)parse_tree);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
NULL,
sent_to_remote,
false,
EXEC_ON_DATANODES,
false,
first_exec_node,
(Node*)parse_tree);
} else {
ExecUtilityStmtOnNodes(
query_string, NULL, sent_to_remote, false, CHOOSE_EXEC_NODES(has_temp), false, (Node*)parse_tree);
}
} else {
LockTableCommand((LockStmt*)parse_tree);
}
#else
LockTableCommand((LockStmt*)parse_tree);
#endif
break;
case T_ConstraintsSetStmt:
AfterTriggerSetState((ConstraintsSetStmt*)parse_tree);
#ifdef PGXC
/*
* Let the pooler manage the statement, SET CONSTRAINT can just be used
* inside a transaction block, hence it has no effect outside that, so use
* it as a local one.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && IsTransactionBlock()) {
if (PoolManagerSetCommand(POOL_CMD_LOCAL_SET, query_string) < 0) {
/* Add retry query error code ERRCODE_SET_QUERY for error "ERROR SET query". */
ereport(ERROR, (errcode(ERRCODE_SET_QUERY), errmsg("Postgres-XC: ERROR SET query")));
}
}
#endif
break;
case T_CheckPointStmt:
if (!superuser())
ereport(
ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to do CHECKPOINT")));
/*
* You might think we should have a PreventCommandDuringRecovery()
* here, but we interpret a CHECKPOINT command during recovery as
* a request for a restartpoint instead. We allow this since it
* can be a useful way of reducing switchover time when using
* various forms of replication.
*/
RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT | (RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, true, EXEC_ON_DATANODES, false);
#endif
break;
#ifdef PGXC
case T_BarrierStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
RequestBarrier(((BarrierStmt*)parse_tree)->id, completion_tag);
break;
/*
* Node DDL is an operation local to Coordinator.
* In case of a new node being created in the cluster,
* it is necessary to create this node on all the Coordinators independently.
*/
case T_AlterNodeStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
PgxcNodeAlter((AlterNodeStmt*)parse_tree);
break;
case T_CreateNodeStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
PgxcNodeCreate((CreateNodeStmt*)parse_tree);
break;
case T_AlterCoordinatorStmt: {
AlterCoordinatorStmt* stmt = (AlterCoordinatorStmt*)parse_tree;
if (IS_PGXC_COORDINATOR && IsConnFromCoord())
PgxcCoordinatorAlter((AlterCoordinatorStmt*)parse_tree);
const char* coor_name = stmt->node_name;
Oid noid = get_pgxc_nodeoid(coor_name);
char node_type = get_pgxc_nodetype(noid);
List* cn_list = NIL;
int cn_num = 0;
if (node_type != 'C')
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PGXC Node %s is not a valid coordinator", coor_name)));
/* alter coordinator node should run on CN nodes only */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && node_type == PGXC_NODE_COORDINATOR) {
/* generate cn list */
ListCell* lc = NULL;
foreach (lc, stmt->coor_nodes) {
char* lc_name = strVal(lfirst(lc));
int lc_idx = PgxcGetNodeIndex(lc_name);
/* only support cn in with clause */
if (lc_idx == -1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), errmsg("Invalid value \"%s\" in WITH clause", lc_name)));
/* except myself */
if (u_sess->pgxc_cxt.PGXCNodeId - 1 != lc_idx)
cn_list = lappend_int(cn_list, lc_idx);
else
PgxcCoordinatorAlter((AlterCoordinatorStmt*)parse_tree);
cn_num++;
}
int numCoords = 0;
int numDns = 0;
PgxcNodeCount(&numCoords, &numDns);
if (cn_num * 2 < numCoords)
ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Number of coordinators in with clause can not be less than half of numCoords(%d)",
numCoords)));
/* need to alter on remote CN(s) */
if (cn_list != NIL) {
ExecNodes* nodes = makeNode(ExecNodes);
nodes->nodeList = cn_list;
ExecUtilityStmtOnNodes(query_string, nodes, sent_to_remote, false, EXEC_ON_COORDS, false);
FreeExecNodes(&nodes);
}
}
} break;
case T_DropNodeStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
{
DropNodeStmt* stmt = (DropNodeStmt*)parse_tree;
char node_type = PgxcNodeRemove(stmt);
/* drop node should run on CN nodes only */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && node_type != PGXC_NODE_NONE &&
(!g_instance.attr.attr_storage.IsRoachStandbyCluster)) {
List* cn_list = NIL;
int cn_num = 0;
int cn_delete_idx = -2;
/* for drop node cn */
if (node_type == PGXC_NODE_COORDINATOR) {
/* get the node index of the dropped cn */
const char* node_name = stmt->node_name;
cn_delete_idx = PgxcGetNodeIndex(node_name);
/* special case: node is created just in this session */
if (cn_delete_idx == -1) {
elog(DEBUG2, "Drop Node %s: get node index -1", node_name);
}
}
/* generate cn list */
if (stmt->remote_nodes) {
ListCell* lc = NULL;
foreach (lc, stmt->remote_nodes) {
char* lc_name = strVal(lfirst(lc));
int lc_idx = PgxcGetNodeIndex(lc_name);
/* only support cn in with clause */
if (lc_idx == -1 || lc_idx == cn_delete_idx)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Invalid value \"%s\" in WITH clause", lc_name)));
/* except myself */
if (u_sess->pgxc_cxt.PGXCNodeId - 1 != lc_idx)
cn_list = lappend_int(cn_list, lc_idx);
}
} else {
cn_list = GetAllCoordNodes();
cn_num = list_length(cn_list);
/* remove the dropped cn from nodelist */
if (cn_num > 0 && cn_delete_idx > -1) {
cn_list = list_delete_int(cn_list, cn_delete_idx);
Assert(cn_num - 1 == list_length(cn_list));
}
}
cn_num = list_length(cn_list);
/* need to drop on remote CN(s) */
if (cn_num > 0) {
ExecNodes* nodes = makeNode(ExecNodes);
nodes->nodeList = cn_list;
ExecUtilityStmtOnNodes(query_string, nodes, sent_to_remote, false, EXEC_ON_COORDS, false);
FreeExecNodes(&nodes);
} else if (cn_list != NULL) {
list_free_ext(cn_list);
}
}
}
break;
case T_CreateGroupStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (IS_SINGLE_NODE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Don't support node group in single_node mode.")));
PgxcGroupCreate((CreateGroupStmt*)parse_tree);
/* create node group should run on CN nodes only */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false);
}
break;
case T_AlterGroupStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (IS_SINGLE_NODE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Don't support node group in single_node mode.")));
#ifdef ENABLE_MULTIPLE_NODES
PgxcGroupAlter((AlterGroupStmt*)parse_tree);
/* alter node group should run on CN nodes only */
{
AlterGroupType alter_type = ((AlterGroupStmt*)parse_tree)->alter_type;
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() &&
(alter_type != AG_SET_DEFAULT && alter_type != AG_SET_SEQ_ALLNODES &&
alter_type != AG_SET_SEQ_SELFNODES)) {
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false);
}
}
#endif
break;
case T_DropGroupStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (IS_SINGLE_NODE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Don't support node group in single_node mode.")));
PgxcGroupRemove((DropGroupStmt*)parse_tree);
/* drop node group should run on CN nodes only */
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false);
}
break;
case T_CreateSynonymStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
CreateSynonym((CreateSynonymStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
CreateSynonym((CreateSynonymStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
CreateSynonym((CreateSynonymStmt*)parse_tree);
}
#else
CreateSynonym((CreateSynonymStmt*)parse_tree);
#endif
break;
case T_DropSynonymStmt:
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
DropSynonym((DropSynonymStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
DropSynonym((DropSynonymStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
DropSynonym((DropSynonymStmt*)parse_tree);
}
#else
DropSynonym((DropSynonymStmt*)parse_tree);
#endif
break;
case T_CreateResourcePoolStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
if (in_logic_cluster() && get_current_lcgroup_name()) {
char* create_respool_stmt = NULL;
create_respool_stmt = GenerateResourcePoolStmt((CreateResourcePoolStmt*)parse_tree, query_string);
Assert(create_respool_stmt != NULL);
ExecUtilityStmtOnNodes_ParallelDDLMode(
create_respool_stmt, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
pfree_ext(create_respool_stmt);
} else {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
}
CreateResourcePool((CreateResourcePoolStmt*)parse_tree);
if (in_logic_cluster() && get_current_lcgroup_name()) {
char* create_respool_stmt = NULL;
create_respool_stmt = GenerateResourcePoolStmt((CreateResourcePoolStmt*)parse_tree, query_string);
Assert(create_respool_stmt != NULL);
ExecUtilityStmtOnNodes_ParallelDDLMode(
create_respool_stmt, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
pfree_ext(create_respool_stmt);
} else {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
}
} else {
CreateResourcePool((CreateResourcePoolStmt*)parse_tree);
if (in_logic_cluster() && get_current_lcgroup_name()) {
char* create_respool_stmt = NULL;
create_respool_stmt = GenerateResourcePoolStmt((CreateResourcePoolStmt*)parse_tree, query_string);
Assert(create_respool_stmt != NULL);
ExecUtilityStmtOnNodes(
create_respool_stmt, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
pfree_ext(create_respool_stmt);
} else {
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
}
} else {
CreateResourcePool((CreateResourcePoolStmt*)parse_tree);
}
break;
case T_AlterResourcePoolStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
AlterResourcePool((AlterResourcePoolStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
AlterResourcePool((AlterResourcePoolStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
AlterResourcePool((AlterResourcePoolStmt*)parse_tree);
}
break;
case T_DropResourcePoolStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
DropResourcePool((DropResourcePoolStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
DropResourcePool((DropResourcePoolStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
DropResourcePool((DropResourcePoolStmt*)parse_tree);
}
break;
case T_CreateWorkloadGroupStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (!IsConnFromCoord())
PreventTransactionChain(is_top_level, "CREATE WORKLOAD GROUP");
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
CreateWorkloadGroup((CreateWorkloadGroupStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
CreateWorkloadGroup((CreateWorkloadGroupStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
CreateWorkloadGroup((CreateWorkloadGroupStmt*)parse_tree);
}
break;
case T_AlterWorkloadGroupStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (!IsConnFromCoord())
PreventTransactionChain(is_top_level, "ALTER WORKLOAD GROUP");
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
AlterWorkloadGroup((AlterWorkloadGroupStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
AlterWorkloadGroup((AlterWorkloadGroupStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
AlterWorkloadGroup((AlterWorkloadGroupStmt*)parse_tree);
}
break;
case T_DropWorkloadGroupStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (!IsConnFromCoord())
PreventTransactionChain(is_top_level, "DROP WORKLOAD GROUP");
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
DropWorkloadGroup((DropWorkloadGroupStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
DropWorkloadGroup((DropWorkloadGroupStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
DropWorkloadGroup((DropWorkloadGroupStmt*)parse_tree);
}
break;
case T_CreateAppWorkloadGroupMappingStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (!IsConnFromCoord())
PreventTransactionChain(is_top_level, "CREATE APP WORKLOAD GROUP MAPPING");
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
CreateAppWorkloadGroupMapping((CreateAppWorkloadGroupMappingStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
CreateAppWorkloadGroupMapping((CreateAppWorkloadGroupMappingStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
CreateAppWorkloadGroupMapping((CreateAppWorkloadGroupMappingStmt*)parse_tree);
}
break;
case T_AlterAppWorkloadGroupMappingStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (!IsConnFromCoord())
PreventTransactionChain(is_top_level, "ALTER APP WORKLOAD GROUP MAPPING");
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
AlterAppWorkloadGroupMapping((AlterAppWorkloadGroupMappingStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
AlterAppWorkloadGroupMapping((AlterAppWorkloadGroupMappingStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
AlterAppWorkloadGroupMapping((AlterAppWorkloadGroupMappingStmt*)parse_tree);
}
break;
case T_DropAppWorkloadGroupMappingStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (!IsConnFromCoord())
PreventTransactionChain(is_top_level, "DROP APP WORKLOAD GROUP MAPPING");
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
DropAppWorkloadGroupMapping((DropAppWorkloadGroupMappingStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
DropAppWorkloadGroupMapping((DropAppWorkloadGroupMappingStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else {
DropAppWorkloadGroupMapping((DropAppWorkloadGroupMappingStmt*)parse_tree);
}
break;
#endif
case T_ReindexStmt: {
ReindexStmt* stmt = (ReindexStmt*)parse_tree;
RemoteQueryExecType exec_type;
bool is_temp = false;
pgstat_set_io_state(IOSTATE_WRITE);
#ifdef PGXC
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (stmt->relation) {
Oid rel_id = InvalidOid;
rel_id = RangeVarGetRelid(stmt->relation, AccessShareLock, false);
if (OidIsValid(rel_id)) {
exec_type = ExecUtilityFindNodes(stmt->kind, rel_id, &is_temp);
UnlockRelationOid(rel_id, AccessShareLock);
} else
exec_type = EXEC_ON_NONE;
} else
exec_type = EXEC_ON_ALL_NODES;
query_string = ConstructMesageWithMemInfo(query_string, stmt->memUsage);
/*
* If I am the main execute CN but not CCN,
* Notify the CCN to create firstly, and then notify other CNs except me.
*/
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node &&
(exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_COORDS)) {
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
NULL,
sent_to_remote,
stmt->kind == OBJECT_DATABASE,
EXEC_ON_COORDS,
false,
first_exec_node,
(Node*)stmt);
}
ReindexCommand(stmt, is_top_level);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node &&
(exec_type == EXEC_ON_ALL_NODES || exec_type == EXEC_ON_DATANODES)) {
ExecUtilityStmtOnNodes_ParallelDDLMode(query_string,
NULL,
sent_to_remote,
stmt->kind == OBJECT_DATABASE,
EXEC_ON_DATANODES,
false,
first_exec_node,
(Node*)stmt);
} else {
ExecUtilityStmtOnNodes(
query_string, NULL, sent_to_remote, stmt->kind == OBJECT_DATABASE, exec_type, false, (Node*)stmt);
}
} else {
ReindexCommand(stmt, is_top_level);
}
#else
ReindexCommand(stmt, is_top_level);
#endif
} break;
case T_CreateConversionStmt:
#ifdef PGXC
if (!IsInitdb)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("user defined conversion is not yet supported.")));
#endif /* PGXC */
CreateConversionCommand((CreateConversionStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_CreateCastStmt:
#ifdef PGXC
if (!IsInitdb && !u_sess->exec_cxt.extension_is_valid && !u_sess->attr.attr_common.IsInplaceUpgrade)
ereport(
ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("user defined cast is not yet supported.")));
#endif /* PGXC */
CreateCast((CreateCastStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_CreateOpClassStmt:
#ifdef PGXC
if (!u_sess->attr.attr_common.IsInplaceUpgrade && !u_sess->exec_cxt.extension_is_valid)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("user defined operator is not yet supported.")));
#endif /* PGXC */
DefineOpClass((CreateOpClassStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_CreateOpFamilyStmt:
#ifdef PGXC
if (!u_sess->attr.attr_common.IsInplaceUpgrade)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("user defined operator is not yet supported.")));
#endif /* PGXC */
DefineOpFamily((CreateOpFamilyStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_AlterOpFamilyStmt:
#ifdef PGXC
if (!u_sess->attr.attr_common.IsInplaceUpgrade)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("user defined operator is not yet supported.")));
#endif /* PGXC */
AlterOpFamily((AlterOpFamilyStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_AlterTSDictionaryStmt:
ts_check_feature_disable();
AlterTSDictionary((AlterTSDictionaryStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
case T_AlterTSConfigurationStmt:
ts_check_feature_disable();
AlterTSConfiguration((AlterTSConfigurationStmt*)parse_tree);
#ifdef PGXC
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
#endif
break;
#ifdef PGXC
case T_RemoteQuery:
AssertEreport(IS_PGXC_COORDINATOR, MOD_EXECUTOR, "not a coordinator node");
/*
* Do not launch query on Other Datanodes if remote connection is a Coordinator one
* it will cause a deadlock in the cluster at Datanode levels.
*/
#ifdef ENABLE_MULTIPLE_NODES
if (!IsConnFromCoord())
ExecRemoteUtility((RemoteQuery*)parse_tree);
#endif
break;
case T_CleanConnStmt:
#ifndef ENABLE_MULTIPLE_NODES
DISTRIBUTED_FEATURE_NOT_SUPPORTED();
#endif
if (IS_PGXC_DATANODE) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("\"CLEAN CONNECTION ...\" can NOT run at DN!")));
break; // never run here
}
CleanConnection((CleanConnStmt*)parse_tree);
if (IS_PGXC_COORDINATOR)
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, true, EXEC_ON_COORDS, false);
break;
#endif
case T_CreateDirectoryStmt:
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
CreatePgDirectory((CreateDirectoryStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
CreatePgDirectory((CreateDirectoryStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else
CreatePgDirectory((CreateDirectoryStmt*)parse_tree);
break;
case T_DropDirectoryStmt:
if (IS_PGXC_COORDINATOR) {
char* first_exec_node = find_first_exec_cn();
bool is_first_node = (strcmp(first_exec_node, g_instance.attr.attr_common.PGXCNodeName) == 0);
if (u_sess->attr.attr_sql.enable_parallel_ddl && !is_first_node) {
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_COORDS, false, first_exec_node);
DropPgDirectory((DropDirectoryStmt*)parse_tree);
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, EXEC_ON_DATANODES, false, first_exec_node);
} else {
DropPgDirectory((DropDirectoryStmt*)parse_tree);
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, false);
}
} else
DropPgDirectory((DropDirectoryStmt*)parse_tree);
break;
default: {
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized node type: %d", (int)nodeTag(parse_tree))));
} break;
}
}
#ifdef PGXC
/*
* is_stmt_allowed_in_locked_mode
*
* Allow/Disallow a utility command while cluster is locked
* A statement will be disallowed if it makes such changes
* in catalog that are backed up by pg_dump except
* CREATE NODE that has to be allowed because
* a new node has to be created while the cluster is still
* locked for backup
*/
static bool is_stmt_allowed_in_locked_mode(Node* parse_tree, const char* query_string)
{
#define ALLOW 1
#define DISALLOW 0
switch (nodeTag(parse_tree)) {
/* To allow creation of temp tables */
case T_CreateStmt: /* CREATE TABLE */
{
CreateStmt* stmt = (CreateStmt*)parse_tree;
/* Don't allow temp table in online scenes for metadata sync problem. */
if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP &&
!u_sess->attr.attr_sql.enable_online_ddl_waitlock)
return ALLOW;
return DISALLOW;
} break;
case T_CreateGroupStmt: {
CreateGroupStmt* stmt = (CreateGroupStmt*)parse_tree;
/* Enable create node group in logic and physical cluster online expansion. */
if ((in_logic_cluster() && stmt->src_group_name) ||
(u_sess->attr.attr_sql.enable_online_ddl_waitlock && superuser()))
return ALLOW;
return DISALLOW;
} break;
case T_ExecuteStmt:
/*
* Prepared statememts can only have
* SELECT, INSERT, UPDATE, DELETE,
* or VALUES statement, there is no
* point stopping EXECUTE.
*/
case T_CreateNodeStmt:
case T_AlterNodeStmt:
/*
* This has to be allowed so that the new node
* can be created, while the cluster is still
* locked for backup
*/
/* two cases: when a cluster is first deployed; */
/* when new nodes are added into cluster */
/* for latter, OM has lock DDL and backup */
case T_AlterGroupStmt:
case T_AlterCoordinatorStmt:
case T_DropNodeStmt:
/*
* This has to be allowed so that DROP NODE
* can be issued to drop a node that has crashed.
* Otherwise system would try to acquire a shared
* advisory lock on the crashed node.
*/
case T_TransactionStmt:
case T_PlannedStmt:
case T_ClosePortalStmt:
case T_FetchStmt:
case T_CopyStmt:
case T_PrepareStmt:
/*
* Prepared statememts can only have
* SELECT, INSERT, UPDATE, DELETE,
* or VALUES statement, there is no
* point stopping PREPARE.
*/
case T_DeallocateStmt:
/*
* If prepare is allowed the deallocate should
* be allowed also
*/
case T_DoStmt:
case T_NotifyStmt:
case T_ListenStmt:
case T_UnlistenStmt:
case T_LoadStmt:
case T_ClusterStmt:
case T_ExplainStmt:
case T_VariableSetStmt:
case T_VariableShowStmt:
case T_DiscardStmt:
case T_LockStmt:
case T_ConstraintsSetStmt:
case T_CheckPointStmt:
case T_BarrierStmt:
case T_ReindexStmt:
case T_RemoteQuery:
case T_CleanConnStmt:
case T_CreateFunctionStmt: // @Temp Table. create function's lock check is moved in CreateFunction
return ALLOW;
default:
return DISALLOW;
}
return DISALLOW;
}
/*
* ExecUtilityWithMessage:
* Execute the query on remote nodes in a transaction block.
* If this fails on one of the nodes :
* Add a context message containing the failed node names.
* Rethrow the error with the message about the failed nodes.
* If all are successful, just return.
*/
static void ExecUtilityWithMessage(const char* query_string, bool sent_to_remote, bool is_temp)
{
PG_TRY();
{
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, false, EXEC_ON_ALL_NODES, is_temp);
}
PG_CATCH();
{
/*
* Some nodes failed. Add context about what all nodes the query
* failed
*/
ExecNodes* coord_success_nodes = NULL;
ExecNodes* data_success_nodes = NULL;
char* msg_failed_nodes = NULL;
pgxc_all_success_nodes(&data_success_nodes, &coord_success_nodes, &msg_failed_nodes);
if (msg_failed_nodes != NULL)
errcontext("%s", msg_failed_nodes);
PG_RE_THROW();
}
PG_END_TRY();
}
static void exec_utility_with_message_parallel_ddl_mode(
const char* query_string, bool sent_to_remote, bool is_temp, const char* first_exec_node, RemoteQueryExecType exec_type)
{
PG_TRY();
{
ExecUtilityStmtOnNodes_ParallelDDLMode(
query_string, NULL, sent_to_remote, false, exec_type, is_temp, first_exec_node);
}
PG_CATCH();
{
/*
* Some nodes failed. Add context about what all nodes the query
* failed
*/
ExecNodes* coord_success_nodes = NULL;
ExecNodes* data_success_nodes = NULL;
char* msg_failed_nodes = NULL;
pgxc_all_success_nodes(&data_success_nodes, &coord_success_nodes, &msg_failed_nodes);
if (msg_failed_nodes != NULL)
errcontext("%s", msg_failed_nodes);
PG_RE_THROW();
}
PG_END_TRY();
}
/*
* Execute a Utility statement on nodes, including Coordinators
* If the DDL is received from a remote Coordinator,
* it is not possible to push down DDL to Datanodes
* as it is taken in charge by the remote Coordinator.
*/
void ExecUtilityStmtOnNodes(const char* query_string, ExecNodes* nodes, bool sent_to_remote, bool force_auto_commit,
RemoteQueryExecType exec_type, bool is_temp, Node* parse_tree)
{
bool need_free_nodes = false;
/*
* @NodeGroup Support
*
* Binding necessary datanodes under exec_nodes to run utility, we put the logic here to
* re-calculate the exec_nodes for target relation in some statements like VACUUM, GRANT,
* as we don't want to put the similar logic in each swith-case branches in standard_ProcessUtility()
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && parse_tree && nodes == NULL && exec_type != EXEC_ON_COORDS) {
nodes = assign_utility_stmt_exec_nodes(parse_tree);
need_free_nodes = true;
}
/* single-user mode */
if (!IsPostmasterEnvironment)
return;
/* gs_dump cn do not connect datanode */
if (u_sess->attr.attr_common.application_name &&
!strncmp(u_sess->attr.attr_common.application_name, "gs_dump", strlen("gs_dump")))
return;
/* Return if query is launched on no nodes */
if (exec_type == EXEC_ON_NONE)
return;
/* Nothing to be done if this statement has been sent to the nodes */
if (sent_to_remote)
return;
/*
* If no Datanodes defined and the remote utility sent to DN, the query cannot
* be launched
*/
if ((exec_type == EXEC_ON_DATANODES || exec_type == EXEC_ON_ALL_NODES) && u_sess->pgxc_cxt.NumDataNodes == 0)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("No Datanode defined in cluster"),
errhint("You need to define at least 1 Datanode with "
"CREATE NODE.")));
#ifdef ENABLE_MULTIPLE_NODES
if (!IsConnFromCoord()) {
RemoteQuery* step = makeNode(RemoteQuery);
step->combine_type = COMBINE_TYPE_SAME;
step->exec_nodes = nodes;
step->sql_statement = pstrdup(query_string);
step->force_autocommit = force_auto_commit;
step->exec_type = exec_type;
step->is_temp = is_temp;
ExecRemoteUtility(step);
pfree_ext(step->sql_statement);
pfree_ext(step);
if (need_free_nodes)
FreeExecNodes(&nodes);
}
#endif
}
/*
* Execute a Utility statement on nodes, including Coordinators
* If the DDL is received from a remote Coordinator,
* it is not possible to push down DDL to Datanodes
* as it is taken in charge by the remote Coordinator.
*/
void ExecUtilityStmtOnNodes_ParallelDDLMode(const char* query_string, ExecNodes* nodes, bool sent_to_remote,
bool force_auto_commit, RemoteQueryExecType exec_type, bool is_temp, const char* first_exec_node, Node* parse_tree)
{
/*
* @NodeGroup Support
*
* Binding necessary datanodes under exec_nodes to run utility, we put the logic here to
* re-calculate the exec_nodes for target relation in some statements like VACUUM, GRANT,
* as we don't want to put the similar logic in each swith-case branches in standard_ProcessUtility()
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && parse_tree && nodes == NULL && exec_type != EXEC_ON_COORDS) {
nodes = assign_utility_stmt_exec_nodes(parse_tree);
}
// single-user mode
//
if (!IsPostmasterEnvironment)
return;
/* gs_dump cn do not connect datanode */
if (u_sess->attr.attr_common.application_name &&
!strncmp(u_sess->attr.attr_common.application_name, "gs_dump", strlen("gs_dump")))
return;
/* Return if query is launched on no nodes */
if (exec_type == EXEC_ON_NONE)
return;
/* Nothing to be done if this statement has been sent to the nodes */
if (sent_to_remote)
return;
/*
* If no Datanodes defined and the remote utility sent to DN, the query cannot
* be launched
*/
if ((exec_type == EXEC_ON_DATANODES || exec_type == EXEC_ON_ALL_NODES) && u_sess->pgxc_cxt.NumDataNodes == 0)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("No Datanode defined in cluster"),
errhint("You need to define at least 1 Datanode with "
"CREATE NODE.")));
if (!IsConnFromCoord()) {
RemoteQuery* step = makeNode(RemoteQuery);
step->combine_type = COMBINE_TYPE_SAME;
step->exec_nodes = nodes;
step->sql_statement = pstrdup(query_string);
step->force_autocommit = force_auto_commit;
step->exec_type = exec_type;
step->is_temp = is_temp;
ExecRemoteUtility_ParallelDDLMode(step, first_exec_node);
pfree_ext(step->sql_statement);
pfree_ext(step);
}
}
/*
* ExecUtilityFindNodes
*
* Determine the list of nodes to launch query on.
* This depends on temporary nature of object and object type.
* Return also a flag indicating if relation is temporary.
*
* If object is a RULE, the object id sent is that of the object to which the
* rule is applicable.
*/
static RemoteQueryExecType ExecUtilityFindNodes(ObjectType object_type, Oid object_id, bool* is_temp)
{
RemoteQueryExecType exec_type;
switch (object_type) {
case OBJECT_SEQUENCE:
if ((*is_temp = IsTempTable(object_id)))
exec_type = EXEC_ON_DATANODES;
else
exec_type = EXEC_ON_ALL_NODES;
break;
/* Triggers are evaluated based on the relation they are defined on */
case OBJECT_TABLE:
case OBJECT_TRIGGER:
/* Do the check on relation kind */
exec_type = exec_utility_find_nodes_relkind(object_id, is_temp);
break;
/*
* Views and rules, both permanent or temporary are created
* on Coordinators only.
*/
case OBJECT_RULE:
case OBJECT_VIEW:
/* Check if object is a temporary view */
if ((*is_temp = IsTempTable(object_id)))
exec_type = EXEC_ON_NONE;
else
exec_type = u_sess->attr.attr_common.IsInplaceUpgrade ? EXEC_ON_ALL_NODES : EXEC_ON_COORDS;
break;
case OBJECT_INDEX:
case OBJECT_CONSTRAINT:
// Temp table. For index or constraint, we should check if it's parent is temp object.
// NOTICE: the argument object_id should be it's parent's oid, not itself's oid.
if ((*is_temp = IsTempTable(object_id)))
exec_type = EXEC_ON_DATANODES;
else
exec_type = EXEC_ON_ALL_NODES;
break;
case OBJECT_COLUMN:
case OBJECT_INTERNAL:
/*
* 1. temp view column, exec on current CN and exec_type is EXEC_ON_NONE;
* 2. temp table column, exec on current CN and DNs, so exec_type is EXEC_ON_DATANODES;
* 3. normal view column, exec on all CNs, so exec_type is EXEC_ON_COORDS;
* 4. normal table column, exec on all CNs and DNs, so exec_type is EXEC_ON_ALL_NODES;
*/
if ((*is_temp = IsTempTable(object_id))) {
if (IsRelaionView(object_id))
exec_type = EXEC_ON_NONE;
else
exec_type = EXEC_ON_DATANODES;
} else {
if (IsRelaionView(object_id))
exec_type = u_sess->attr.attr_common.IsInplaceUpgrade ? EXEC_ON_ALL_NODES : EXEC_ON_COORDS;
else
exec_type = EXEC_ON_ALL_NODES;
}
break;
default:
*is_temp = false;
exec_type = EXEC_ON_ALL_NODES;
break;
}
return exec_type;
}
/*
* exec_utility_find_nodes_relkind
*
* Get node execution and temporary type
* for given relation depending on its relkind
*/
static RemoteQueryExecType exec_utility_find_nodes_relkind(Oid rel_id, bool* is_temp)
{
char relkind_str = get_rel_relkind(rel_id);
RemoteQueryExecType exec_type;
switch (relkind_str) {
case RELKIND_SEQUENCE:
*is_temp = IsTempTable(rel_id);
exec_type = CHOOSE_EXEC_NODES(*is_temp);
break;
case RELKIND_RELATION:
*is_temp = IsTempTable(rel_id);
exec_type = CHOOSE_EXEC_NODES(*is_temp);
break;
case RELKIND_VIEW:
if ((*is_temp = IsTempTable(rel_id)))
exec_type = EXEC_ON_NONE;
else
exec_type = u_sess->attr.attr_common.IsInplaceUpgrade ? EXEC_ON_ALL_NODES : EXEC_ON_COORDS;
break;
default:
*is_temp = false;
exec_type = EXEC_ON_ALL_NODES;
break;
}
return exec_type;
}
#endif
/* Execute a Utility statement on nodes, including Coordinators
* If the DDL is received from a remote Coordinator,
* it is not possible to push down DDL to Datanodes
* as it is taken in charge by the remote Coordinator.
*/
HeapTuple* ExecRemoteVacuumStmt(VacuumStmt* stmt, const char* query_string, bool sent_to_remote, ANALYZE_RQTYPE arq_type,
AnalyzeMode e_analyze_mode, Oid rel_id)
{
ExecNodes* nodes = NULL;
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
if (stmt->relation) {
rel_id = RangeVarGetRelid(stmt->relation, NoLock, false);
}
nodes = RelidGetExecNodes(rel_id);
}
HeapTuple* result = NULL;
// single-user mode
//
if (!IsPostmasterEnvironment) {
FreeExecNodes(&nodes);
return NULL;
}
/* Nothing to be done if this statement has been sent to the nodes */
if (sent_to_remote) {
FreeExecNodes(&nodes);
return NULL;
}
/* If no Datanodes defined, the query cannot be launched */
if (u_sess->pgxc_cxt.NumDataNodes == 0)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("No Datanode defined in cluster"),
errhint("You need to define at least 1 Datanode with "
"CREATE NODE.")));
if (!IsConnFromCoord()) {
RemoteQuery* step = makeNode(RemoteQuery);
step->combine_type = COMBINE_TYPE_SAME;
step->exec_nodes = nodes;
step->sql_statement = pstrdup(query_string);
step->force_autocommit = true;
step->exec_type = EXEC_ON_DATANODES;
step->is_temp = false;
result = RecvRemoteSampleMessage(stmt, step, arq_type, e_analyze_mode);
pfree_ext(step->sql_statement);
pfree_ext(step);
}
FreeExecNodes(&nodes);
return result;
}
/*
* UtilityReturnsTuples
* Return "true" if this utility statement will send output to the
* destination.
*
* Generally, there should be a case here for each case in ProcessUtility
* where "dest" is passed on.
*/
bool UtilityReturnsTuples(Node* parse_tree)
{
switch (nodeTag(parse_tree)) {
case T_FetchStmt: {
FetchStmt* stmt = (FetchStmt*)parse_tree;
Portal portal;
if (stmt->ismove)
return false;
portal = GetPortalByName(stmt->portalname);
if (!PortalIsValid(portal))
return false; /* not our business to raise error */
return portal->tupDesc ? true : false;
}
case T_ExecuteStmt: {
ExecuteStmt* stmt = (ExecuteStmt*)parse_tree;
PreparedStatement* entry = NULL;
entry = FetchPreparedStatement(stmt->name, false);
if (entry == NULL)
return false; /* not our business to raise error */
if (entry->plansource->resultDesc)
return true;
return false;
}
case T_ExplainStmt:
return true;
case T_VariableShowStmt:
return true;
default:
return false;
}
}
/*
* UtilityTupleDescriptor
* Fetch the actual output tuple descriptor for a utility statement
* for which UtilityReturnsTuples() previously returned "true".
*
* The returned descriptor is created in (or copied into) the current memory
* context.
*/
TupleDesc UtilityTupleDescriptor(Node* parse_tree)
{
switch (nodeTag(parse_tree)) {
case T_FetchStmt: {
FetchStmt* stmt = (FetchStmt*)parse_tree;
Portal portal;
if (stmt->ismove)
return NULL;
portal = GetPortalByName(stmt->portalname);
if (!PortalIsValid(portal))
return NULL; /* not our business to raise error */
return CreateTupleDescCopy(portal->tupDesc);
}
case T_ExecuteStmt: {
ExecuteStmt* stmt = (ExecuteStmt*)parse_tree;
PreparedStatement* entry = NULL;
entry = FetchPreparedStatement(stmt->name, false);
if (entry == NULL)
return NULL; /* not our business to raise error */
return FetchPreparedStatementResultDesc(entry);
}
case T_ExplainStmt:
return ExplainResultDesc((ExplainStmt*)parse_tree);
case T_VariableShowStmt: {
VariableShowStmt* n = (VariableShowStmt*)parse_tree;
return GetPGVariableResultDesc(n->name);
}
default:
return NULL;
}
}
/*
* QueryReturnsTuples
* Return "true" if this Query will send output to the destination.
*/
#ifdef NOT_USED
bool QueryReturnsTuples(Query* parse_tree)
{
switch (parse_tree->commandType) {
case CMD_SELECT:
/* returns tuples ... unless it's DECLARE CURSOR */
if (parse_tree->utilityStmt == NULL)
return true;
break;
case CMD_MERGE:
return false;
case CMD_INSERT:
case CMD_UPDATE:
case CMD_DELETE:
/* the forms with RETURNING return tuples */
if (parse_tree->returningList)
return true;
break;
case CMD_UTILITY:
return UtilityReturnsTuples(parse_tree->utilityStmt);
case CMD_UNKNOWN:
case CMD_NOTHING:
/* probably shouldn't get here */
break;
default:
break;
}
return false; /* default */
}
#endif
/*
* UtilityContainsQuery
* Return the contained Query, or NULL if there is none
*
* Certain utility statements, such as EXPLAIN, contain a plannable Query.
* This function encapsulates knowledge of exactly which ones do.
* We assume it is invoked only on already-parse-analyzed statements
* (else the contained parse_tree isn't a Query yet).
*
* In some cases (currently, only EXPLAIN of CREATE TABLE AS/SELECT INTO),
* potentially Query-containing utility statements can be nested. This
* function will drill down to a non-utility Query, or return NULL if none.
*/
Query* UtilityContainsQuery(Node* parse_tree)
{
Query* qry = NULL;
switch (nodeTag(parse_tree)) {
case T_ExplainStmt:
qry = (Query*)((ExplainStmt*)parse_tree)->query;
AssertEreport(IsA(qry, Query), MOD_EXECUTOR, "node type is not query");
if (qry->commandType == CMD_UTILITY)
return UtilityContainsQuery(qry->utilityStmt);
return qry;
case T_CreateTableAsStmt:
qry = (Query*)((CreateTableAsStmt*)parse_tree)->query;
AssertEreport(IsA(qry, Query), MOD_EXECUTOR, "node type is not query");
if (qry->commandType == CMD_UTILITY)
return UtilityContainsQuery(qry->utilityStmt);
return qry;
default:
return NULL;
}
}
/*
* AlterObjectTypeCommandTag
* helper function for CreateCommandTag
*
* This covers most cases where ALTER is used with an ObjectType enum.
*/
static const char* AlterObjectTypeCommandTag(ObjectType obj_type)
{
const char* tag = NULL;
switch (obj_type) {
case OBJECT_AGGREGATE:
tag = "ALTER AGGREGATE";
break;
case OBJECT_ATTRIBUTE:
tag = "ALTER TYPE";
break;
case OBJECT_CAST:
tag = "ALTER CAST";
break;
case OBJECT_COLLATION:
tag = "ALTER COLLATION";
break;
case OBJECT_COLUMN:
tag = "ALTER TABLE";
break;
case OBJECT_CONSTRAINT:
tag = "ALTER TABLE";
break;
case OBJECT_CONVERSION:
tag = "ALTER CONVERSION";
break;
case OBJECT_DATABASE:
tag = "ALTER DATABASE";
break;
case OBJECT_DOMAIN:
tag = "ALTER DOMAIN";
break;
case OBJECT_EXTENSION:
tag = "ALTER EXTENSION";
break;
case OBJECT_FDW:
tag = "ALTER FOREIGN DATA WRAPPER";
break;
case OBJECT_FOREIGN_SERVER:
tag = "ALTER SERVER";
break;
case OBJECT_FOREIGN_TABLE:
tag = "ALTER FOREIGN TABLE";
break;
case OBJECT_FUNCTION:
tag = "ALTER FUNCTION";
break;
case OBJECT_INDEX:
tag = "ALTER INDEX";
break;
case OBJECT_LANGUAGE:
tag = "ALTER LANGUAGE";
break;
case OBJECT_LARGEOBJECT:
tag = "ALTER LARGE OBJECT";
break;
case OBJECT_OPCLASS:
tag = "ALTER OPERATOR CLASS";
break;
case OBJECT_OPERATOR:
tag = "ALTER OPERATOR";
break;
case OBJECT_OPFAMILY:
tag = "ALTER OPERATOR FAMILY";
break;
case OBJECT_RLSPOLICY:
tag = "ALTER ROW LEVEL SECURITY POLICY";
break;
case OBJECT_PARTITION:
tag = "ALTER TABLE";
break;
case OBJECT_PARTITION_INDEX:
tag = "ALTER INDEX";
break;
case OBJECT_ROLE:
case OBJECT_USER:
tag = "ALTER ROLE";
break;
case OBJECT_RULE:
tag = "ALTER RULE";
break;
case OBJECT_SCHEMA:
tag = "ALTER SCHEMA";
break;
case OBJECT_SEQUENCE:
tag = "ALTER SEQUENCE";
break;
case OBJECT_TABLE:
tag = "ALTER TABLE";
break;
case OBJECT_TABLESPACE:
tag = "ALTER TABLESPACE";
break;
case OBJECT_TRIGGER:
tag = "ALTER TRIGGER";
break;
case OBJECT_TSCONFIGURATION:
tag = "ALTER TEXT SEARCH CONFIGURATION";
break;
case OBJECT_TSDICTIONARY:
tag = "ALTER TEXT SEARCH DICTIONARY";
break;
case OBJECT_TSPARSER:
tag = "ALTER TEXT SEARCH PARSER";
break;
case OBJECT_TSTEMPLATE:
tag = "ALTER TEXT SEARCH TEMPLATE";
break;
case OBJECT_TYPE:
tag = "ALTER TYPE";
break;
case OBJECT_VIEW:
tag = "ALTER VIEW";
break;
case OBJECT_DATA_SOURCE:
tag = "ALTER DATA SOURCE";
break;
case OBJECT_DIRECTORY:
tag = "ALTER DIRECTORY";
break;
case OBJECT_SYNONYM:
tag = "ALTER SYNONYM";
break;
default:
tag = "?\?\?";
break;
}
return tag;
}
/*
* CreateCommandTag
* utility to get a string representation of the command operation,
* given either a raw (un-analyzed) parse_tree or a planned query.
*
* This must handle all command types, but since the vast majority
* of 'em are utility commands, it seems sensible to keep it here.
*
* NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE.
* Also, the result must point at a true constant (permanent storage).
*/
const char* CreateCommandTag(Node* parse_tree)
{
const char* tag = NULL;
switch (nodeTag(parse_tree)) {
/* raw plannable queries */
case T_InsertStmt:
tag = "INSERT";
break;
case T_DeleteStmt:
tag = "DELETE";
break;
case T_UpdateStmt:
tag = "UPDATE";
break;
case T_MergeStmt:
tag = "MERGE";
break;
case T_SelectStmt:
tag = "SELECT";
break;
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt: {
TransactionStmt* stmt = (TransactionStmt*)parse_tree;
switch (stmt->kind) {
case TRANS_STMT_BEGIN:
tag = "BEGIN";
break;
case TRANS_STMT_START:
tag = "START TRANSACTION";
break;
case TRANS_STMT_COMMIT:
tag = "COMMIT";
break;
case TRANS_STMT_ROLLBACK:
case TRANS_STMT_ROLLBACK_TO:
tag = "ROLLBACK";
break;
case TRANS_STMT_SAVEPOINT:
tag = "SAVEPOINT";
break;
case TRANS_STMT_RELEASE:
tag = "RELEASE";
break;
case TRANS_STMT_PREPARE:
tag = "PREPARE TRANSACTION";
break;
case TRANS_STMT_COMMIT_PREPARED:
tag = "COMMIT PREPARED";
break;
case TRANS_STMT_ROLLBACK_PREPARED:
tag = "ROLLBACK PREPARED";
break;
default:
tag = "?\?\?";
break;
}
} break;
case T_DeclareCursorStmt:
tag = "DECLARE CURSOR";
break;
case T_ClosePortalStmt: {
ClosePortalStmt* stmt = (ClosePortalStmt*)parse_tree;
if (stmt->portalname == NULL)
tag = "CLOSE CURSOR ALL";
else
tag = "CLOSE CURSOR";
} break;
case T_FetchStmt: {
FetchStmt* stmt = (FetchStmt*)parse_tree;
tag = (stmt->ismove) ? "MOVE" : "FETCH";
} break;
case T_CreateDomainStmt:
tag = "CREATE DOMAIN";
break;
case T_CreateSchemaStmt:
tag = "CREATE SCHEMA";
break;
case T_CreateStmt:
tag = "CREATE TABLE";
break;
case T_CreateTableSpaceStmt:
tag = "CREATE TABLESPACE";
break;
case T_DropTableSpaceStmt:
tag = "DROP TABLESPACE";
break;
case T_AlterTableSpaceOptionsStmt:
tag = "ALTER TABLESPACE";
break;
case T_CreateExtensionStmt:
tag = "CREATE EXTENSION";
break;
case T_AlterExtensionStmt:
tag = "ALTER EXTENSION";
break;
case T_AlterExtensionContentsStmt:
tag = "ALTER EXTENSION";
break;
case T_CreateFdwStmt:
tag = "CREATE FOREIGN DATA WRAPPER";
break;
case T_AlterFdwStmt:
tag = "ALTER FOREIGN DATA WRAPPER";
break;
case T_CreateForeignServerStmt:
tag = "CREATE SERVER";
break;
case T_AlterForeignServerStmt:
tag = "ALTER SERVER";
break;
case T_CreateUserMappingStmt:
tag = "CREATE USER MAPPING";
break;
case T_AlterUserMappingStmt:
tag = "ALTER USER MAPPING";
break;
case T_DropUserMappingStmt:
tag = "DROP USER MAPPING";
break;
case T_CreateSynonymStmt:
tag = "CREATE SYNONYM";
break;
case T_DropSynonymStmt:
tag = "DROP SYNONYM";
break;
case T_CreateDataSourceStmt:
tag = "CREATE DATA SOURCE";
break;
case T_AlterDataSourceStmt:
tag = "ALTER DATA SOURCE";
break;
case T_CreateRlsPolicyStmt:
tag = "CREATE ROW LEVEL SECURITY POLICY";
break;
case T_AlterRlsPolicyStmt:
tag = "ALTER ROW LEVEL SECURITY POLICY";
break;
case T_CreateForeignTableStmt:
tag = "CREATE FOREIGN TABLE";
break;
case T_DropStmt:
switch (((DropStmt*)parse_tree)->removeType) {
case OBJECT_TABLE:
tag = "DROP TABLE";
break;
case OBJECT_SEQUENCE:
tag = "DROP SEQUENCE";
break;
case OBJECT_VIEW:
tag = "DROP VIEW";
break;
case OBJECT_INDEX:
tag = "DROP INDEX";
break;
case OBJECT_TYPE:
tag = "DROP TYPE";
break;
case OBJECT_DOMAIN:
tag = "DROP DOMAIN";
break;
case OBJECT_COLLATION:
tag = "DROP COLLATION";
break;
case OBJECT_CONVERSION:
tag = "DROP CONVERSION";
break;
case OBJECT_SCHEMA:
tag = "DROP SCHEMA";
break;
case OBJECT_TSPARSER:
tag = "DROP TEXT SEARCH PARSER";
break;
case OBJECT_TSDICTIONARY:
tag = "DROP TEXT SEARCH DICTIONARY";
break;
case OBJECT_TSTEMPLATE:
tag = "DROP TEXT SEARCH TEMPLATE";
break;
case OBJECT_TSCONFIGURATION:
tag = "DROP TEXT SEARCH CONFIGURATION";
break;
case OBJECT_FOREIGN_TABLE:
tag = "DROP FOREIGN TABLE";
break;
case OBJECT_EXTENSION:
tag = "DROP EXTENSION";
break;
case OBJECT_FUNCTION: {
DropStmt* dpStmt = (DropStmt*)parse_tree;
tag = (dpStmt->isProcedure) ? "DROP PROCEDURE" : "DROP FUNCTION";
break;
}
case OBJECT_AGGREGATE:
tag = "DROP AGGREGATE";
break;
case OBJECT_OPERATOR:
tag = "DROP OPERATOR";
break;
case OBJECT_LANGUAGE:
tag = "DROP LANGUAGE";
break;
case OBJECT_CAST:
tag = "DROP CAST";
break;
case OBJECT_TRIGGER:
tag = "DROP TRIGGER";
break;
case OBJECT_RULE:
tag = "DROP RULE";
break;
case OBJECT_FDW:
tag = "DROP FOREIGN DATA WRAPPER";
break;
case OBJECT_FOREIGN_SERVER:
tag = "DROP SERVER";
break;
case OBJECT_OPCLASS:
tag = "DROP OPERATOR CLASS";
break;
case OBJECT_OPFAMILY:
tag = "DROP OPERATOR FAMILY";
break;
case OBJECT_RLSPOLICY:
tag = "DROP ROW LEVEL SECURITY POLICY";
break;
case OBJECT_DATA_SOURCE:
tag = "DROP DATA SOURCE";
break;
default:
tag = "?\?\?";
break;
}
break;
case T_TruncateStmt:
tag = "TRUNCATE TABLE";
break;
case T_CommentStmt:
tag = "COMMENT";
break;
case T_SecLabelStmt:
tag = "SECURITY LABEL";
break;
case T_CopyStmt:
tag = "COPY";
break;
case T_RenameStmt:
tag = AlterObjectTypeCommandTag(((RenameStmt*)parse_tree)->renameType);
break;
case T_AlterObjectSchemaStmt:
tag = AlterObjectTypeCommandTag(((AlterObjectSchemaStmt*)parse_tree)->objectType);
break;
case T_AlterOwnerStmt:
tag = AlterObjectTypeCommandTag(((AlterOwnerStmt*)parse_tree)->objectType);
break;
case T_AlterTableStmt:
tag = AlterObjectTypeCommandTag(((AlterTableStmt*)parse_tree)->relkind);
break;
case T_AlterDomainStmt:
tag = "ALTER DOMAIN";
break;
case T_AlterFunctionStmt:
tag = "ALTER FUNCTION";
break;
case T_GrantStmt: {
GrantStmt* stmt = (GrantStmt*)parse_tree;
tag = (stmt->is_grant) ? "GRANT" : "REVOKE";
} break;
case T_GrantRoleStmt: {
GrantRoleStmt* stmt = (GrantRoleStmt*)parse_tree;
tag = (stmt->is_grant) ? "GRANT ROLE" : "REVOKE ROLE";
} break;
case T_AlterDefaultPrivilegesStmt:
tag = "ALTER DEFAULT PRIVILEGES";
break;
case T_DefineStmt:
switch (((DefineStmt*)parse_tree)->kind) {
case OBJECT_AGGREGATE:
tag = "CREATE AGGREGATE";
break;
case OBJECT_OPERATOR:
tag = "CREATE OPERATOR";
break;
case OBJECT_TYPE:
tag = "CREATE TYPE";
break;
case OBJECT_TSPARSER:
tag = "CREATE TEXT SEARCH PARSER";
break;
case OBJECT_TSDICTIONARY:
tag = "CREATE TEXT SEARCH DICTIONARY";
break;
case OBJECT_TSTEMPLATE:
tag = "CREATE TEXT SEARCH TEMPLATE";
break;
case OBJECT_TSCONFIGURATION:
tag = "CREATE TEXT SEARCH CONFIGURATION";
break;
case OBJECT_COLLATION:
tag = "CREATE COLLATION";
break;
default:
tag = "?\?\?";
break;
}
break;
case T_CompositeTypeStmt:
tag = "CREATE TYPE";
break;
case T_CreateEnumStmt:
tag = "CREATE TYPE";
break;
case T_CreateRangeStmt:
tag = "CREATE TYPE";
break;
case T_AlterEnumStmt:
tag = "ALTER TYPE";
break;
case T_ViewStmt:
tag = "CREATE VIEW";
break;
case T_CreateFunctionStmt: {
CreateFunctionStmt* cfstmt = (CreateFunctionStmt*)parse_tree;
tag = (cfstmt->isProcedure) ? "CREATE PROCEDURE" : "CREATE FUNCTION";
break;
}
case T_IndexStmt:
tag = "CREATE INDEX";
break;
case T_RuleStmt:
tag = "CREATE RULE";
break;
case T_CreateSeqStmt:
tag = "CREATE SEQUENCE";
break;
case T_AlterSeqStmt:
tag = "ALTER SEQUENCE";
break;
case T_DoStmt:
tag = "ANONYMOUS BLOCK EXECUTE";
break;
case T_CreatedbStmt:
tag = "CREATE DATABASE";
break;
case T_AlterDatabaseStmt:
tag = "ALTER DATABASE";
break;
case T_AlterDatabaseSetStmt:
tag = "ALTER DATABASE";
break;
case T_DropdbStmt:
tag = "DROP DATABASE";
break;
case T_NotifyStmt:
tag = "NOTIFY";
break;
case T_ListenStmt:
tag = "LISTEN";
break;
case T_UnlistenStmt:
tag = "UNLISTEN";
break;
case T_LoadStmt:
tag = "LOAD";
break;
case T_ClusterStmt:
tag = "CLUSTER";
break;
case T_VacuumStmt:
if (((VacuumStmt*)parse_tree)->options & VACOPT_VACUUM) {
tag = "VACUUM";
} else if (((VacuumStmt*)parse_tree)->options & VACOPT_MERGE) {
tag = "DELTA MERGE";
} else if (((VacuumStmt*)parse_tree)->options & VACOPT_VERIFY) {
tag = "ANALYZE VERIFY";
} else {
tag = "ANALYZE";
}
break;
case T_ExplainStmt:
tag = "EXPLAIN";
break;
case T_CreateTableAsStmt:
if (((CreateTableAsStmt*)parse_tree)->is_select_into)
tag = "SELECT INTO";
else
tag = "CREATE TABLE AS";
break;
case T_AlterSystemStmt:
tag = "ALTER SYSTEM SET";
break;
case T_VariableSetStmt:
switch (((VariableSetStmt*)parse_tree)->kind) {
case VAR_SET_VALUE:
case VAR_SET_CURRENT:
case VAR_SET_DEFAULT:
case VAR_SET_MULTI:
case VAR_SET_ROLEPWD:
tag = "SET";
break;
case VAR_RESET:
case VAR_RESET_ALL:
tag = "RESET";
break;
default:
tag = "?\?\?";
break;
}
break;
case T_VariableShowStmt:
tag = "SHOW";
break;
case T_DiscardStmt:
switch (((DiscardStmt*)parse_tree)->target) {
case DISCARD_ALL:
tag = "DISCARD ALL";
break;
case DISCARD_PLANS:
tag = "DISCARD PLANS";
break;
case DISCARD_TEMP:
tag = "DISCARD TEMP";
break;
default:
tag = "?\?\?";
break;
}
break;
case T_CreateTrigStmt:
tag = "CREATE TRIGGER";
break;
case T_CreatePLangStmt:
tag = "CREATE LANGUAGE";
break;
case T_CreateRoleStmt:
tag = "CREATE ROLE";
break;
case T_AlterRoleStmt:
tag = "ALTER ROLE";
break;
case T_AlterRoleSetStmt:
tag = "ALTER ROLE";
break;
case T_DropRoleStmt:
tag = "DROP ROLE";
break;
case T_DropOwnedStmt:
tag = "DROP OWNED";
break;
case T_ReassignOwnedStmt:
tag = "REASSIGN OWNED";
break;
case T_LockStmt:
tag = "LOCK TABLE";
break;
case T_ConstraintsSetStmt:
tag = "SET CONSTRAINTS";
break;
case T_CheckPointStmt:
tag = "CHECKPOINT";
break;
#ifdef PGXC
case T_BarrierStmt:
tag = "BARRIER";
break;
case T_AlterNodeStmt:
case T_AlterCoordinatorStmt:
tag = "ALTER NODE";
break;
case T_CreateNodeStmt:
tag = "CREATE NODE";
break;
case T_DropNodeStmt:
tag = "DROP NODE";
break;
case T_CreateGroupStmt:
tag = "CREATE NODE GROUP";
break;
case T_AlterGroupStmt:
tag = "ALTER NODE GROUP";
break;
case T_DropGroupStmt:
tag = "DROP NODE GROUP";
break;
case T_CreateResourcePoolStmt:
tag = "CREATE RESOURCE POOL";
break;
case T_AlterResourcePoolStmt:
tag = "ALTER RESOURCE POOL";
break;
case T_DropResourcePoolStmt:
tag = "DROP RESOURCE POOL";
break;
case T_CreateWorkloadGroupStmt:
tag = "CREATE WORKLOAD GROUP";
break;
case T_AlterWorkloadGroupStmt:
tag = "ALTER WORKLOAD GROUP";
break;
case T_DropWorkloadGroupStmt:
tag = "DROP WORKLOAD GROUP";
break;
case T_CreateAppWorkloadGroupMappingStmt:
tag = "CREATE APP WORKLOAD GROUP MAPPING";
break;
case T_AlterAppWorkloadGroupMappingStmt:
tag = "ALTER APP WORKLOAD GROUP MAPPING";
break;
case T_DropAppWorkloadGroupMappingStmt:
tag = "DROP APP WORKLOAD GROUP MAPPING";
break;
#endif
case T_ReindexStmt:
tag = "REINDEX";
break;
case T_CreateConversionStmt:
tag = "CREATE CONVERSION";
break;
case T_CreateCastStmt:
tag = "CREATE CAST";
break;
case T_CreateOpClassStmt:
tag = "CREATE OPERATOR CLASS";
break;
case T_CreateOpFamilyStmt:
tag = "CREATE OPERATOR FAMILY";
break;
case T_AlterOpFamilyStmt:
tag = "ALTER OPERATOR FAMILY";
break;
case T_AlterTSDictionaryStmt:
tag = "ALTER TEXT SEARCH DICTIONARY";
break;
case T_AlterTSConfigurationStmt:
tag = "ALTER TEXT SEARCH CONFIGURATION";
break;
case T_PrepareStmt:
tag = "PREPARE";
break;
case T_ExecuteStmt:
tag = "EXECUTE";
break;
case T_DeallocateStmt: {
DeallocateStmt* stmt = (DeallocateStmt*)parse_tree;
if (stmt->name == NULL)
tag = "DEALLOCATE ALL";
else
tag = "DEALLOCATE";
} break;
/* already-planned queries */
case T_PlannedStmt: {
PlannedStmt* stmt = (PlannedStmt*)parse_tree;
switch (stmt->commandType) {
case CMD_SELECT:
/*
* We take a little extra care here so that the result
* will be useful for complaints about read-only
* statements
*/
if (stmt->utilityStmt != NULL) {
AssertEreport(
IsA(stmt->utilityStmt, DeclareCursorStmt), MOD_EXECUTOR, "not a cursor declare stmt");
tag = "DECLARE CURSOR";
} else if (stmt->rowMarks != NIL) {
/* not 100% but probably close enough */
if (((PlanRowMark*)linitial(stmt->rowMarks))->markType == ROW_MARK_EXCLUSIVE)
tag = "SELECT FOR UPDATE";
else
tag = "SELECT FOR SHARE";
} else
tag = "SELECT";
break;
case CMD_UPDATE:
tag = "UPDATE";
break;
case CMD_INSERT:
tag = "INSERT";
break;
case CMD_DELETE:
tag = "DELETE";
break;
case CMD_MERGE:
tag = "MERGE";
break;
default:
elog(WARNING, "unrecognized commandType: %d", (int)stmt->commandType);
tag = "?\?\?";
break;
}
} break;
/* parsed-and-rewritten-but-not-planned queries */
case T_Query: {
Query* stmt = (Query*)parse_tree;
switch (stmt->commandType) {
case CMD_SELECT:
/*
* We take a little extra care here so that the result
* will be useful for complaints about read-only
* statements
*/
if (stmt->utilityStmt != NULL) {
AssertEreport(
IsA(stmt->utilityStmt, DeclareCursorStmt), MOD_EXECUTOR, "not a cursor declare stmt");
tag = "DECLARE CURSOR";
} else if (stmt->rowMarks != NIL) {
/* not 100% but probably close enough */
if (((RowMarkClause*)linitial(stmt->rowMarks))->forUpdate)
tag = "SELECT FOR UPDATE";
else
tag = "SELECT FOR SHARE";
} else
tag = "SELECT";
break;
case CMD_UPDATE:
tag = "UPDATE";
break;
case CMD_INSERT:
tag = "INSERT";
break;
case CMD_DELETE:
tag = "DELETE";
break;
case CMD_MERGE:
tag = "MERGE";
break;
case CMD_UTILITY:
tag = CreateCommandTag(stmt->utilityStmt);
break;
default:
elog(WARNING, "unrecognized commandType: %d", (int)stmt->commandType);
tag = "?\?\?";
break;
}
} break;
case T_ExecDirectStmt:
tag = "EXECUTE DIRECT";
break;
case T_CleanConnStmt:
tag = "CLEAN CONNECTION";
break;
case T_CreateDirectoryStmt:
tag = "CREATE DIRECTORY";
break;
case T_DropDirectoryStmt:
tag = "DROP DIRECTORY";
break;
case T_ShutdownStmt:
tag = "SHUTDOWN";
break;
default:
elog(WARNING, "unrecognized node type: %d", (int)nodeTag(parse_tree));
tag = "?\?\?";
break;
}
return tag;
}
/*
* GetAlterTableCommandTag
* utility to get a string representation of the alter table
* command operation.
*/
const char* CreateAlterTableCommandTag(const AlterTableType subtype)
{
const char* tag = NULL;
switch (subtype) {
case AT_AddColumn:
tag = "ADD COLUMN";
break;
case AT_AddColumnRecurse:
tag = "ADD COLUMN RECURSE";
break;
case AT_AddColumnToView:
tag = "ADD COLUMN TO VIEW";
break;
case AT_AddPartition:
tag = "ADD PARTITION";
break;
case AT_ColumnDefault:
tag = "COLUMN DEFAULT";
break;
case AT_DropNotNull:
tag = "DROP NOT NULL";
break;
case AT_SetNotNull:
tag = "SET NOT NULL";
break;
case AT_SetStatistics:
tag = "SET STATISTICS";
break;
case AT_SetOptions:
tag = "SET OPTIONS";
break;
case AT_ResetOptions:
tag = "RESET OPTIONS";
break;
case AT_SetStorage:
tag = "SET STORAGE";
break;
case AT_DropColumn:
tag = "DROP COLUMN";
break;
case AT_DropColumnRecurse:
tag = "DROP COLUMN RECURSE";
break;
case AT_DropPartition:
tag = "DROP PARTITION";
break;
case AT_AddIndex:
tag = "ADD INDEX";
break;
case AT_ReAddIndex:
tag = "RE ADD INDEX";
break;
case AT_AddConstraint:
tag = "ADD CONSTRAINT";
break;
case AT_AddConstraintRecurse:
tag = "ADD CONSTRAINT RECURSE";
break;
case AT_ValidateConstraint:
tag = "VALIDATE CONSTRAINT";
break;
case AT_ValidateConstraintRecurse:
tag = "VALIDATE CONSTRAINT RECURSE";
break;
case AT_ProcessedConstraint:
tag = "PROCESSED CONSTRAINT";
break;
case AT_AddIndexConstraint:
tag = "ADD INDEX CONSTRAINT";
break;
case AT_DropConstraint:
tag = "DROP CONSTRAINT";
break;
case AT_DropConstraintRecurse:
tag = "DROP CONSTRAINT RECURSE";
break;
case AT_AlterColumnType:
tag = "ALTER COLUMN TYPE";
break;
case AT_AlterColumnGenericOptions:
tag = "ALTER COLUMN GENERIC OPTIONS";
break;
case AT_ChangeOwner:
tag = "CHANGE OWNER";
break;
case AT_ClusterOn:
tag = "CLUSTER ON";
break;
case AT_DropCluster:
tag = "DROP CLUSTER";
break;
case AT_AddOids:
tag = "ADD OIDS";
break;
case AT_AddOidsRecurse:
tag = "ADD OIDS RECURSE";
break;
case AT_DropOids:
tag = "DROP OIDS";
break;
case AT_SetTableSpace:
tag = "SET TABLE SPACE";
break;
case AT_SetPartitionTableSpace:
tag = "SET PARTITION TABLE SPACE";
break;
case AT_SetRelOptions:
tag = "SET REL OPTIONS";
break;
case AT_ResetRelOptions:
tag = "RESET REL OPTIONS";
break;
case AT_ReplaceRelOptions:
tag = "REPLACE REL OPTIONS";
break;
case AT_UnusableIndex:
tag = "UNUSABLE INDEX";
break;
case AT_UnusableIndexPartition:
tag = "UNUSABLE INDEX PARTITION";
break;
case AT_UnusableAllIndexOnPartition:
tag = "UNUSABLE ALL INDEX ON PARTITION";
break;
case AT_RebuildIndex:
tag = "REBUILD INDEX";
break;
case AT_RebuildIndexPartition:
tag = "REBUILD INDEX PARTITION";
break;
case AT_RebuildAllIndexOnPartition:
tag = "REBUILD ALL INDEX ON PARTITION";
break;
case AT_EnableTrig:
tag = "ENABLE TRIGGER";
break;
case AT_EnableAlwaysTrig:
tag = "ENABLE ALWAYS TRIGGER";
break;
case AT_EnableReplicaTrig:
tag = "ENABLE REPLICA TRIGGER";
break;
case AT_DisableTrig:
tag = "DISABLE TRIGGER";
break;
case AT_EnableTrigAll:
tag = "ENABLE TRIGGER ALL";
break;
case AT_DisableTrigAll:
tag = "DISABLE TRIGGER ALL";
break;
case AT_EnableTrigUser:
tag = "ENABLE TRIGGER USER";
break;
case AT_DisableTrigUser:
tag = "DISABLE TRIGGER USER";
break;
case AT_EnableRule:
tag = "ENABLE RULE";
break;
case AT_EnableAlwaysRule:
tag = "ENABLE ALWAYS RULE";
break;
case AT_EnableReplicaRule:
tag = "ENABLE REPLICA RULE";
break;
case AT_DisableRule:
tag = "DISABLE RULE";
break;
case AT_EnableRls:
tag = "ENABLE ROW LEVEL SECURITY";
break;
case AT_DisableRls:
tag = "DISABLE ROW LEVEL SECURITY";
break;
case AT_ForceRls:
tag = "FORCE ROW LEVEL SECURITY";
break;
case AT_NoForceRls:
tag = "NO FORCE ROW LEVEL SECURITY";
break;
case AT_AddInherit:
tag = "ADD INHERIT";
break;
case AT_DropInherit:
tag = "DROP INHERIT";
break;
case AT_AddOf:
tag = "ADD OF";
break;
case AT_DropOf:
tag = "DROP OF";
break;
case AT_SET_COMPRESS:
tag = "SET COMPRESS";
break;
#ifdef PGXC
case AT_DistributeBy:
tag = "DISTRIBUTE BY";
break;
case AT_SubCluster:
tag = "SUB CLUSTER";
break;
case AT_AddNodeList:
tag = "ADD NODE LIST";
break;
case AT_DeleteNodeList:
tag = "DELETE NODE LIST";
break;
#endif
case AT_GenericOptions:
tag = "GENERIC OPTIONS";
break;
case AT_EnableRowMoveMent:
tag = "ENABLE ROW MOVE MENT";
break;
case AT_DisableRowMoveMent:
tag = "DISABLE ROW MOVE MENT";
break;
case AT_TruncatePartition:
tag = "TRUNCATE PARTITION";
break;
case AT_ExchangePartition:
tag = "EXCHANGE PARTITION";
break;
case AT_MergePartition:
tag = "MERGE PARTITION";
break;
case AT_SplitPartition:
tag = "SPLIT PARTITION";
break;
case AT_ReAddConstraint:
tag = "RE ADD CONSTRAINT";
break;
default:
tag = "?\?\?";
break;
}
return tag;
}
/*
* GetCommandLogLevel
* utility to get the minimum log_statement level for a command,
* given either a raw (un-analyzed) parse_tree or a planned query.
*
* This must handle all command types, but since the vast majority
* of 'em are utility commands, it seems sensible to keep it here.
*/
LogStmtLevel GetCommandLogLevel(Node* parse_tree)
{
LogStmtLevel lev;
switch (nodeTag(parse_tree)) {
/* raw plannable queries */
case T_InsertStmt:
case T_DeleteStmt:
case T_UpdateStmt:
case T_MergeStmt:
lev = LOGSTMT_MOD;
break;
case T_SelectStmt:
if (((SelectStmt*)parse_tree)->intoClause)
lev = LOGSTMT_DDL; /* SELECT INTO */
else
lev = LOGSTMT_ALL;
break;
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt:
lev = LOGSTMT_ALL;
break;
case T_DeclareCursorStmt:
lev = LOGSTMT_ALL;
break;
case T_ClosePortalStmt:
lev = LOGSTMT_ALL;
break;
case T_FetchStmt:
lev = LOGSTMT_ALL;
break;
case T_CreateSchemaStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateStmt:
case T_CreateForeignTableStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateTableSpaceStmt:
case T_DropTableSpaceStmt:
case T_AlterTableSpaceOptionsStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateExtensionStmt:
case T_AlterExtensionStmt:
case T_AlterExtensionContentsStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateFdwStmt:
case T_AlterFdwStmt:
case T_CreateForeignServerStmt:
case T_AlterForeignServerStmt:
case T_CreateUserMappingStmt:
case T_AlterUserMappingStmt:
case T_DropUserMappingStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateDataSourceStmt:
case T_AlterDataSourceStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateRlsPolicyStmt:
case T_AlterRlsPolicyStmt:
lev = LOGSTMT_DDL;
break;
case T_DropStmt:
lev = LOGSTMT_DDL;
break;
case T_TruncateStmt:
lev = LOGSTMT_MOD;
break;
case T_CommentStmt:
lev = LOGSTMT_DDL;
break;
case T_SecLabelStmt:
lev = LOGSTMT_DDL;
break;
case T_CopyStmt:
if (((CopyStmt*)parse_tree)->is_from)
lev = LOGSTMT_MOD;
else
lev = LOGSTMT_ALL;
break;
case T_PrepareStmt: {
PrepareStmt* stmt = (PrepareStmt*)parse_tree;
/* Look through a PREPARE to the contained stmt */
lev = GetCommandLogLevel(stmt->query);
} break;
case T_ExecuteStmt: {
ExecuteStmt* stmt = (ExecuteStmt*)parse_tree;
PreparedStatement* ps = NULL;
/* Look through an EXECUTE to the referenced stmt */
ps = FetchPreparedStatement(stmt->name, false);
if (ps != NULL)
lev = GetCommandLogLevel(ps->plansource->raw_parse_tree);
else
lev = LOGSTMT_ALL;
} break;
case T_DeallocateStmt:
lev = LOGSTMT_ALL;
break;
case T_RenameStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterObjectSchemaStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterOwnerStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterTableStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterDomainStmt:
lev = LOGSTMT_DDL;
break;
case T_GrantStmt:
lev = LOGSTMT_DDL;
break;
case T_GrantRoleStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterDefaultPrivilegesStmt:
lev = LOGSTMT_DDL;
break;
case T_DefineStmt:
lev = LOGSTMT_DDL;
break;
case T_CompositeTypeStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateEnumStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateRangeStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterEnumStmt:
lev = LOGSTMT_DDL;
break;
case T_ViewStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateFunctionStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterFunctionStmt:
lev = LOGSTMT_DDL;
break;
case T_IndexStmt:
lev = LOGSTMT_DDL;
break;
case T_RuleStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateSeqStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterSeqStmt:
lev = LOGSTMT_DDL;
break;
case T_DoStmt:
lev = LOGSTMT_ALL;
break;
case T_CreatedbStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterDatabaseStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterDatabaseSetStmt:
lev = LOGSTMT_DDL;
break;
case T_DropdbStmt:
lev = LOGSTMT_DDL;
break;
case T_NotifyStmt:
lev = LOGSTMT_ALL;
break;
case T_ListenStmt:
lev = LOGSTMT_ALL;
break;
case T_UnlistenStmt:
lev = LOGSTMT_ALL;
break;
case T_LoadStmt:
lev = LOGSTMT_ALL;
break;
case T_ClusterStmt:
lev = LOGSTMT_DDL;
break;
case T_VacuumStmt:
lev = LOGSTMT_ALL;
break;
case T_ExplainStmt: {
ExplainStmt* stmt = (ExplainStmt*)parse_tree;
bool analyze = false;
ListCell* lc = NULL;
/* Look through an EXPLAIN ANALYZE to the contained stmt */
foreach (lc, stmt->options) {
DefElem* opt = (DefElem*)lfirst(lc);
if (strcmp(opt->defname, "analyze") == 0)
analyze = defGetBoolean(opt);
/* don't "break", as explain.c will use the last value */
}
if (analyze)
return GetCommandLogLevel(stmt->query);
/* Plain EXPLAIN isn't so interesting */
lev = LOGSTMT_ALL;
} break;
case T_CreateTableAsStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterSystemStmt:
lev = LOGSTMT_DDL;
break;
case T_VariableSetStmt:
lev = LOGSTMT_ALL;
break;
case T_VariableShowStmt:
lev = LOGSTMT_ALL;
break;
case T_DiscardStmt:
lev = LOGSTMT_ALL;
break;
case T_CreateTrigStmt:
lev = LOGSTMT_DDL;
break;
case T_CreatePLangStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateDomainStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateRoleStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterRoleStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterRoleSetStmt:
lev = LOGSTMT_DDL;
break;
case T_DropRoleStmt:
lev = LOGSTMT_DDL;
break;
case T_DropOwnedStmt:
lev = LOGSTMT_DDL;
break;
case T_ReassignOwnedStmt:
lev = LOGSTMT_DDL;
break;
case T_LockStmt:
lev = LOGSTMT_ALL;
break;
case T_ConstraintsSetStmt:
lev = LOGSTMT_ALL;
break;
case T_CheckPointStmt:
lev = LOGSTMT_ALL;
break;
case T_ReindexStmt:
lev = LOGSTMT_ALL; /* should this be DDL? */
break;
case T_CreateConversionStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateCastStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateOpClassStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateOpFamilyStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterOpFamilyStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterTSDictionaryStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterTSConfigurationStmt:
lev = LOGSTMT_DDL;
break;
/* already-planned queries */
case T_PlannedStmt: {
PlannedStmt* stmt = (PlannedStmt*)parse_tree;
switch (stmt->commandType) {
case CMD_SELECT:
lev = LOGSTMT_ALL;
break;
case CMD_UPDATE:
case CMD_INSERT:
case CMD_DELETE:
case CMD_MERGE:
lev = LOGSTMT_MOD;
break;
default:
elog(WARNING, "unrecognized commandType: %d", (int)stmt->commandType);
lev = LOGSTMT_ALL;
break;
}
} break;
/* parsed-and-rewritten-but-not-planned queries */
case T_Query: {
Query* stmt = (Query*)parse_tree;
switch (stmt->commandType) {
case CMD_SELECT:
lev = LOGSTMT_ALL;
break;
case CMD_UPDATE:
case CMD_INSERT:
case CMD_DELETE:
case CMD_MERGE:
lev = LOGSTMT_MOD;
break;
case CMD_UTILITY:
lev = GetCommandLogLevel(stmt->utilityStmt);
break;
default:
elog(WARNING, "unrecognized commandType: %d", (int)stmt->commandType);
lev = LOGSTMT_ALL;
break;
}
} break;
case T_BarrierStmt:
case T_CreateNodeStmt:
case T_AlterNodeStmt:
case T_DropNodeStmt:
case T_CreateGroupStmt:
case T_AlterGroupStmt:
case T_DropGroupStmt:
case T_CreateResourcePoolStmt:
case T_AlterResourcePoolStmt:
case T_DropResourcePoolStmt:
case T_CreateWorkloadGroupStmt:
case T_AlterWorkloadGroupStmt:
case T_DropWorkloadGroupStmt:
case T_CreateAppWorkloadGroupMappingStmt:
case T_AlterAppWorkloadGroupMappingStmt:
case T_DropAppWorkloadGroupMappingStmt:
lev = LOGSTMT_DDL;
break;
case T_ExecDirectStmt:
lev = LOGSTMT_ALL;
break;
#ifdef PGXC
case T_CleanConnStmt:
lev = LOGSTMT_DDL;
break;
#endif
case T_CreateDirectoryStmt:
case T_DropDirectoryStmt:
lev = LOGSTMT_DDL;
break;
case T_CreateSynonymStmt:
case T_DropSynonymStmt:
lev = LOGSTMT_DDL;
break;
case T_ShutdownStmt:
lev = LOGSTMT_ALL;
break;
default:
elog(WARNING, "unrecognized node type: %d", (int)nodeTag(parse_tree));
lev = LOGSTMT_ALL;
break;
}
return lev;
}
#ifdef PGXC
/*
* GetCommentObjectId
* Change to return the nodes to execute the utility on in future.
*
* Return Object ID of object commented
* Note: This function uses portions of the code of CommentObject,
* even if this code is duplicated this is done like this to facilitate
* merges with PostgreSQL head.
*/
static RemoteQueryExecType get_nodes_4_comment_utility(CommentStmt* stmt, bool* is_temp, ExecNodes** exec_nodes)
{
ObjectAddress address;
Relation relation;
RemoteQueryExecType exec_type = EXEC_ON_ALL_NODES; /* By default execute on all nodes */
Oid object_id;
if (exec_nodes != NULL)
*exec_nodes = NULL;
if (stmt->objtype == OBJECT_DATABASE && list_length(stmt->objname) == 1) {
char* database = strVal(linitial(stmt->objname));
if (!OidIsValid(get_database_oid(database, true)))
ereport(WARNING, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", database)));
/* No clue, return the default one */
return exec_type;
}
address =
get_object_address(stmt->objtype, stmt->objname, stmt->objargs, &relation, ShareUpdateExclusiveLock, false);
object_id = address.objectId;
/*
* If the object being commented is a rule, the nodes are decided by the
* object to which rule is applicable, so get the that object's oid
*/
if (stmt->objtype == OBJECT_RULE) {
if (!relation || !OidIsValid(relation->rd_id)) {
/* This should not happen, but prepare for the worst */
char* rulename = strVal(llast(stmt->objname));
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("can not find relation for rule \"%s\" does not exist", rulename)));
object_id = InvalidOid;
} else {
object_id = RelationGetRelid(relation);
if (exec_nodes != NULL) {
*exec_nodes = RelidGetExecNodes(object_id, false);
}
}
}
if (stmt->objtype == OBJECT_CONSTRAINT) {
if (!relation || !OidIsValid(relation->rd_id)) {
/* This should not happen, but prepare for the worst */
char* constraintname = strVal(llast(stmt->objname));
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("can not find relation for constraint \"%s\" does not exist", constraintname)));
object_id = InvalidOid;
} else {
object_id = RelationGetRelid(relation);
if (exec_nodes != NULL) {
*exec_nodes = RelidGetExecNodes(object_id, false);
}
}
}
if (relation != NULL)
relation_close(relation, NoLock);
/* Commented object may not have a valid object ID, so move to default */
if (OidIsValid(object_id)) {
exec_type = ExecUtilityFindNodes(stmt->objtype, object_id, is_temp);
if (exec_nodes != NULL && (exec_type == EXEC_ON_DATANODES || exec_type == EXEC_ON_ALL_NODES)) {
switch (stmt->objtype) {
case OBJECT_TABLE:
case OBJECT_FOREIGN_TABLE:
case OBJECT_INDEX:
case OBJECT_COLUMN:
*exec_nodes = RelidGetExecNodes(object_id, false);
break;
case OBJECT_FUNCTION:
*exec_nodes = GetFunctionNodes(object_id);
break;
default:
break;
}
}
}
return exec_type;
}
/*
* get_nodes_4_rules_utility
* Get the nodes to execute this RULE related utility statement.
* A rule is expanded on Coordinator itself, and does not need any
* existence on Datanode. In fact, if it were to exist on Datanode,
* there is a possibility that it would expand again
*/
static RemoteQueryExecType get_nodes_4_rules_utility(RangeVar* relation, bool* is_temp)
{
Oid rel_id = RangeVarGetRelid(relation, NoLock, true);
RemoteQueryExecType exec_type;
/* Skip if this Oid does not exist */
if (!OidIsValid(rel_id))
return EXEC_ON_NONE;
/*
* See if it's a temporary object, do we really need
* to care about temporary objects here? What about the
* temporary objects defined inside the rule?
*/
exec_type = ExecUtilityFindNodes(OBJECT_RULE, rel_id, is_temp);
return exec_type;
}
/*
* TreatDropStmtOnCoord
* Do a pre-treatment of Drop statement on a remote Coordinator
*/
static void drop_stmt_pre_treatment(
DropStmt* stmt, const char* query_string, bool sent_to_remote, bool* is_temp, RemoteQueryExecType* exec_type)
{
bool res_is_temp = false;
RemoteQueryExecType res_exec_type = EXEC_ON_ALL_NODES;
/* Nothing to do if not local Coordinator */
if (IS_PGXC_DATANODE || IsConnFromCoord())
return;
/*
* Check for shared-cache-inval messages before trying to access the
* relation. This is needed to cover the case where the name
* identifies a rel that has been dropped and recreated since the
* start of our transaction: if we don't flush the old syscache entry,
* then we'll latch onto that entry and suffer an error later.
*/
AcceptInvalidationMessages();
switch (stmt->removeType) {
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
case OBJECT_INDEX: {
/*
* Check the list of objects going to be dropped.
* XC does not allow yet to mix drop of temporary and
* non-temporary objects because this involves to rewrite
* query to process for tables.
*/
ListCell* cell = NULL;
bool is_first = true;
foreach (cell, stmt->objects) {
RangeVar* rel = makeRangeVarFromNameList((List*)lfirst(cell));
Oid rel_id;
/*
* Do not print result at all, error is thrown
* after if necessary
* Notice : need get lock here to forbid parallel drop
*/
rel_id = RangeVarGetRelid(rel, AccessShareLock, true);
/*
* In case this relation ID is incorrect throw
* a correct DROP error.
*/
if (!OidIsValid(rel_id) && !stmt->missing_ok)
DropTableThrowErrorExternal(rel, stmt->removeType, stmt->missing_ok);
/* In case of DROP ... IF EXISTS bypass */
if (!OidIsValid(rel_id) && stmt->missing_ok)
continue;
if (is_first) {
res_exec_type = ExecUtilityFindNodes(stmt->removeType, rel_id, &res_is_temp);
is_first = false;
} else {
RemoteQueryExecType exec_type_loc;
bool is_temp_loc = false;
exec_type_loc = ExecUtilityFindNodes(stmt->removeType, rel_id, &is_temp_loc);
if (exec_type_loc != res_exec_type || is_temp_loc != res_is_temp)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DROP not supported for TEMP and non-TEMP objects"),
errdetail("You should separate TEMP and non-TEMP objects")));
}
UnlockRelationOid(rel_id, AccessShareLock);
}
} break;
case OBJECT_SCHEMA: {
ListCell* cell = NULL;
bool has_temp = false;
bool has_nontemp = false;
foreach (cell, stmt->objects) {
List* objname = (List*)lfirst(cell);
char* name = NameListToString(objname);
if (isTempNamespaceName(name) || isToastTempNamespaceName(name)) {
has_temp = true;
res_exec_type = EXEC_ON_DATANODES;
} else {
has_nontemp = true;
}
if (has_temp && has_nontemp)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DROP not supported for TEMP and non-TEMP objects"),
errdetail("You should separate TEMP and non-TEMP objects")));
}
res_is_temp = has_temp;
if (has_temp)
res_exec_type = EXEC_ON_DATANODES;
else
res_exec_type = EXEC_ON_ALL_NODES;
break;
}
/*
* Those objects are dropped depending on the nature of the relationss
* they are defined on. This evaluation uses the temporary behavior
* and the relkind of the relation used.
*/
case OBJECT_RULE:
case OBJECT_TRIGGER: {
List* objname = (List*)linitial(stmt->objects);
Relation relation = NULL;
get_object_address(stmt->removeType, objname, NIL, &relation, AccessExclusiveLock, stmt->missing_ok);
/* Do nothing if no relation */
if (relation && OidIsValid(relation->rd_id))
res_exec_type = ExecUtilityFindNodes(stmt->removeType, relation->rd_id, &res_is_temp);
else
res_exec_type = EXEC_ON_NONE;
/* Close relation if necessary */
if (relation)
relation_close(relation, NoLock);
} break;
/* Datanode do not have rls information */
case OBJECT_RLSPOLICY:
res_is_temp = false;
res_exec_type = EXEC_ON_COORDS;
break;
default:
res_is_temp = false;
res_exec_type = EXEC_ON_ALL_NODES;
break;
}
/* Save results */
*is_temp = res_is_temp;
*exec_type = res_exec_type;
}
#endif
char* VariableBlackList[] = {"client_encoding"};
bool IsVariableinBlackList(const char* name)
{
if (name == NULL) {
return false;
}
bool isInBlackList = false;
int blacklistLen = sizeof(VariableBlackList) / sizeof(char*);
for (int i = 0; i < blacklistLen; i++) {
char* blackname = VariableBlackList[i];
AssertEreport(blackname, MOD_EXECUTOR, "the character string is NULL");
if (0 == pg_strncasecmp(blackname, name, strlen(blackname))) {
isInBlackList = true;
break;
}
}
return isInBlackList;
}
/*
* return true if the extension can be uninstall
*/
bool DropExtensionIsSupported(const char* query_string)
{
char* lower_string = lowerstr(query_string);
if (strstr(lower_string, "drop") && (strstr(lower_string, "postgis") || strstr(lower_string, "packages") ||
strstr(lower_string, "mysql_fdw") || strstr(lower_string, "oracle_fdw") ||
strstr(lower_string, "postgres_fdw"))) {
pfree_ext(lower_string);
return true;
} else {
pfree_ext(lower_string);
return false;
}
}
/*
* Check if the object is in blacklist, if true, ALTER/DROP operation of the object is disabled.
* Note that only prescribed extensions are able droppable.
*/
void CheckObjectInBlackList(ObjectType obj_type, const char* query_string)
{
const char* tag = NULL;
bool is_not_extended_feature = false;
switch (obj_type) {
case OBJECT_EXTENSION:
tag = "EXTENSION";
/* Check if the extension is provided officially */
if (DropExtensionIsSupported(query_string))
return;
else
break;
#ifdef ENABLE_MULTIPLE_NODES
case OBJECT_AGGREGATE:
tag = "AGGREGATE";
break;
#endif
case OBJECT_OPERATOR:
tag = "OPERATOR";
break;
case OBJECT_OPCLASS:
tag = "OPERATOR CLASS";
break;
case OBJECT_OPFAMILY:
tag = "OPERATOR FAMILY";
break;
case OBJECT_COLLATION:
tag = "COLLATION";
break;
#ifdef ENABLE_MULTIPLE_NODES
case OBJECT_RULE:
tag = "RULE";
break;
#endif
case OBJECT_TSDICTIONARY:
case OBJECT_TSCONFIGURATION:
ts_check_feature_disable();
return;
case OBJECT_TSPARSER:
is_not_extended_feature = true;
tag = "TEXT SEARCH PARSER";
break;
case OBJECT_TSTEMPLATE:
is_not_extended_feature = true;
tag = "TEXT SEARCH TEMPLATE";
break;
case OBJECT_LANGUAGE:
tag = "LANGUAGE";
break;
/*Single node support domain feature.*/
/* case OBJECT_DOMAIN:
tag = "DOMAIN";
break;*/
case OBJECT_CONVERSION:
tag = "CONVERSION";
break;
case OBJECT_FDW:
tag = "FOREIGN DATA WRAPPER";
break;
default:
return;
}
/* Enable DROP/ALTER operation of the above objects during inplace upgrade. */
if (!u_sess->attr.attr_common.IsInplaceUpgrade) {
if (is_not_extended_feature && !IsInitdb) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("%s is not yet supported.", tag)));
} else if ((!g_instance.attr.attr_common.support_extended_features) &&
(!(obj_type == OBJECT_RULE && u_sess->attr.attr_sql.enable_cluster_resize))) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("%s is not yet supported.", tag)));
}
}
}
/*
* CheckObjectInBlackList
*
* Check if the given extension is supported. If hashCheck is true,
* the hash value of the sql script will be checked.
*/
bool CheckExtensionInWhiteList(const char* extension_name, uint32 hash_value, bool hash_check)
{
#ifdef ENABLE_MULTIPLE_NODES
/* 2902411162 hash for fastcheck, the sql file is much shorter than published version. */
uint32 postgisHashHistory[POSTGIS_VERSION_NUM] = {2902411162, 2959454932};
/* check for extension name */
if (pg_strcasecmp(extension_name, "postgis") == 0) {
/* PostGIS is disabled under 300 deployment */
if (is_feature_disabled(POSTGIS_DOCKING)) {
return false;
}
if (hash_check && !isSecurityMode) {
/* check for postgis hashvalue */
for (int i = 0; i < POSTGIS_VERSION_NUM; i++) {
if (hash_value == postgisHashHistory[i]) {
return true;
}
}
} else {
return true;
}
}
#endif
return true;
}
#ifdef ENABLE_MULTIPLE_NODES
/*
* @hdfs
* notify_other_cn_get_statistics
*
* We send hybridMessage to each other coordinator node. These nodes get statistics from
* predefined data node represented in hybridMessage.
*/
static void notify_other_cn_get_statistics(const char* hybrid_message, bool sent_to_remote)
{
ExecNodes* exec_nodes = NULL;
int node_index;
exec_nodes = makeNode(ExecNodes);
for (node_index = 0; node_index < u_sess->pgxc_cxt.NumCoords; node_index++) {
if (node_index != PGXCNodeGetNodeIdFromName(g_instance.attr.attr_common.PGXCNodeName, PGXC_NODE_COORDINATOR))
exec_nodes->nodeList = lappend_int(exec_nodes->nodeList, node_index);
}
ExecUtilityStmtOnNodes(hybrid_message, exec_nodes, sent_to_remote, true, EXEC_ON_COORDS, false);
FreeExecNodes(&exec_nodes);
/*
* for VACUUM ANALYZE table where options=3, vacuum has set use_own_xacts
* true and already poped ActiveSnapshot, so we don't want to pop again.
*/
if (ActiveSnapshotSet()) {
PopActiveSnapshot();
}
CommitTransactionCommand();
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
}
#endif
/*
* @hdfs
* AssembleHybridMessage
*
* Assembel hybridmessage. Copy query_string and scheduling_message into query_string_with_info.
*/
void AssembleHybridMessage(char** query_string_with_info, const char* query_string, const char* scheduling_message)
{
unsigned int query_string_len = strlen(query_string);
unsigned int scheduling_message_len = strlen(scheduling_message);
unsigned int pos = 0;
unsigned int alloc_len = 0;
errno_t errorno = EOK;
/* make Const node and set query_string_len identify how long does the query string command. */
Const* n = makeNode(Const);
n->constvalue = UInt32GetDatum(query_string_len);
n->constisnull = false;
n->constlen = sizeof(uint32);
n->constbyval = true;
char* query_len_const = nodeToString(n);
/* the query string with info include 'h'+querylenConst+query_string+scheduling_message+'\0'. */
alloc_len = 2 + strlen(query_len_const) + strlen(query_string) + strlen(scheduling_message);
*query_string_with_info = (char*)MemoryContextAlloc(t_thrd.mem_cxt.msg_mem_cxt, alloc_len);
const size_t head_len = 1;
/* write hybirdmessage head into query_string_with_info */
errorno = memcpy_s(*query_string_with_info + pos, head_len, "h", head_len);
securec_check(errorno, "\0", "\0");
pos += head_len;
/* write serialized Const identify how long does the query_string into query_string_with_info */
errorno = memcpy_s(*query_string_with_info + pos, strlen(query_len_const), query_len_const, strlen(query_len_const));
securec_check(errorno, "\0", "\0");
pos += strlen(query_len_const);
/* write query_string into query_string_with_info. query_string is a executive sql sentence. */
errorno = memcpy_s(*query_string_with_info + pos, query_string_len, query_string, query_string_len);
securec_check(errorno, "\0", "\0");
pos += query_string_len;
/* write scheduling_message into query_string_with_info. */
errorno = memcpy_s(*query_string_with_info + pos, scheduling_message_len, scheduling_message, scheduling_message_len);
securec_check(errorno, "\0", "\0");
pos += scheduling_message_len;
const size_t end_len = 1;
/* write terminator at the end of query_string_with_info */
errorno = memcpy_s(*query_string_with_info + pos, end_len, "\0", end_len);
securec_check(errorno, "\0", "\0");
pfree_ext(n);
pfree_ext(query_len_const);
}
#ifdef ENABLE_MULTIPLE_NODES
/*
* @hdfs
* get_scheduling_message
*
* In this function we call CNSchedulingForAnalyze to get scheduling information. The exact count of file which
* will be analyzed is stored in totalFileCnt. At the same time, CNSchedulingForAnalyze function selects a data
* node to execute analyze operation. The selected datanode number is stored in nodeNo.
*/
static char* get_scheduling_message(const Oid foreign_table_id, VacuumStmt* stmt)
{
HDFSTableAnalyze* hdfs_table_analyze = makeNode(HDFSTableAnalyze);
List* dn_task = NIL; /* Get scheduling information */
/* get right scheduling messages for global stats. */
dn_task = CNSchedulingForAnalyze(&stmt->totalFileCnt, &stmt->DnCnt, foreign_table_id, true);
/* set default values. */
hdfs_table_analyze->DnCnt = 0;
stmt->nodeNo = 0;
stmt->hdfsforeignMapDnList = NIL;
/*
* There is a risk that dn_task can be null. We mush process this situation
* It means that we call CNSchedulingForAnalyze failed.
*/
if (dn_task != NIL) {
bool first = true;
ListCell* taskCell = NULL;
if (!IS_OBS_CSV_TXT_FOREIGN_TABLE(foreign_table_id)) {
SplitMap* task_map = NULL;
foreach (taskCell, dn_task) {
task_map = (SplitMap*)lfirst(taskCell);
if (task_map->splits != NIL) {
/* we need to find the first dn which have filelist to get dndistinct for global stats. */
if (first) {
hdfs_table_analyze->DnCnt = stmt->DnCnt;
stmt->nodeNo = task_map->nodeId;
first = false;
}
/* get all nodeid which have filelist in order to get total reltuples from them later. */
stmt->hdfsforeignMapDnList = lappend_int(stmt->hdfsforeignMapDnList, task_map->nodeId);
}
}
} else {
DistFdwDataNodeTask* task_map = NULL;
first = true;
foreach (taskCell, dn_task) {
task_map = (DistFdwDataNodeTask*)lfirst(taskCell);
if (NIL != task_map->task) {
/* we need to find the first dn which have filelist to get dndistinct for global stats. */
if (first) {
hdfs_table_analyze->DnCnt = stmt->DnCnt;
Oid nodeOid = get_pgxc_nodeoid(task_map->dnName);
stmt->nodeNo = PGXCNodeGetNodeId(nodeOid, PGXC_NODE_DATANODE);
first = false;
}
/* get all nodeid which have filelist in order to get total reltuples from them later. */
stmt->hdfsforeignMapDnList = lappend_int(stmt->hdfsforeignMapDnList, stmt->nodeNo);
}
}
}
}
/* dn_task can be null when we call CNSchedulingForAnalyze failed */
hdfs_table_analyze->DnWorkFlow = dn_task;
return nodeToString(hdfs_table_analyze);
}
#endif
/**
* @Description: attatch sample rate to the querystring for global stats
* @out query_string_with_info - the query string with analyze command and schedulingMessag
* @in stmt - the statment for analyze or vacuum command
* @in query_string - the query string for analyze or vacuum command
* @in rel_id - the relation oid for analyze or vacuum
* @in has_var - the flag identify do analyze for one relation or all database
* @in e_analyze_mode - identify hdfs table or normal table
* @in rel_id - the hdfs foreign table oid. others, it is invalid for other tables
* @in foreign_tbl_schedul_message - identify the schedul info for hdfs foreign table
*
* @return: void
*/
static void attatch_global_info(char** query_string_with_info, VacuumStmt* stmt, const char* query_string, bool has_var,
AnalyzeMode e_analyze_mode, Oid rel_id, char* foreign_tbl_schedul_message)
{
SplitMap* task_map = makeNode(SplitMap);
HDFSTableAnalyze* hdfs_table_analyze = NULL;
char* global_info = NULL;
if (!stmt->isForeignTables || stmt->isPgFdwForeignTables) {
hdfs_table_analyze = makeNode(HDFSTableAnalyze);
// stmt->sampleRate, DNs get the sample rate for sending sample rows to CN.
if (e_analyze_mode == ANALYZENORMAL) {
hdfs_table_analyze->isHdfsStore = false;
hdfs_table_analyze->sampleRate[0] = stmt->pstGlobalStatEx[0].sampleRate;
} else {
hdfs_table_analyze->isHdfsStore = true;
for (int i = 0; i < ANALYZE_MODE_MAX_NUM - 1; i++)
hdfs_table_analyze->sampleRate[i] = stmt->pstGlobalStatEx[i].sampleRate;
}
task_map->locatorType = LOCATOR_TYPE_NONE;
task_map->splits = NIL;
hdfs_table_analyze->DnCnt = 0;
hdfs_table_analyze->DnWorkFlow = lappend(hdfs_table_analyze->DnWorkFlow, task_map);
hdfs_table_analyze->isHdfsForeignTbl = false;
} else {
/*
* Call scheduler to get datanode which will execute analyze operation
* and file list.
*/
if (foreign_tbl_schedul_message != NULL) {
hdfs_table_analyze = (HDFSTableAnalyze*)stringToNode(foreign_tbl_schedul_message);
} else {
/* make a default dn task for do analyze foreign tables. */
hdfs_table_analyze = makeNode(HDFSTableAnalyze);
task_map->locatorType = LOCATOR_TYPE_NONE;
task_map->splits = NIL;
hdfs_table_analyze->DnCnt = 0;
hdfs_table_analyze->DnWorkFlow = lappend(hdfs_table_analyze->DnWorkFlow, task_map);
}
hdfs_table_analyze->isHdfsStore = false;
hdfs_table_analyze->sampleRate[0] = stmt->pstGlobalStatEx[0].sampleRate;
hdfs_table_analyze->isHdfsForeignTbl = true;
}
/* orgCnNodeNo is the ID of CN which do global analyze, the other CNs fetch stats from it */
hdfs_table_analyze->orgCnNodeNo =
PGXCNodeGetNodeIdFromName(g_instance.attr.attr_common.PGXCNodeName, PGXC_NODE_COORDINATOR);
hdfs_table_analyze->sampleTableRequired = stmt->sampleTableRequired;
hdfs_table_analyze->tmpSampleTblNameList = stmt->tmpSampleTblNameList;
hdfs_table_analyze->disttype = stmt->disttype;
hdfs_table_analyze->memUsage.work_mem = stmt->memUsage.work_mem;
hdfs_table_analyze->memUsage.max_mem = stmt->memUsage.max_mem;
global_info = nodeToString(hdfs_table_analyze);
if (has_var) {
AssembleHybridMessage(query_string_with_info, query_string, global_info);
} else {
AssertEreport(query_string != NULL, MOD_EXECUTOR, "Invalid NULL value");
if (!stmt->isForeignTables || stmt->isPgFdwForeignTables) {
StringInfo stringptr = makeStringInfo();
/*
* If the query comes from JDBC connecion, there is without ';'
* at the end of query_string.
*/
const char* position = NULL;
position = strrchr(query_string, ';');
if (position == NULL) {
/* We don't find ';' at the end of query_string. copy query_string to stringptr. */
appendBinaryStringInfo(stringptr, query_string, strlen(query_string));
} else {
bool hasSemicolon = true;
position += 1;
/* Find the char ';' is the end of query_string or not. */
while (*position != '\0') {
if (!((*position == ' ') || (*position == '\n') || (*position == '\t') || (*position == '\r') ||
(*position == '\f') || (*position == '\v'))) {
/* We have find ';', but it is not the end of query_string. copy query_string to stringptr. */
appendBinaryStringInfo(stringptr, query_string, strlen(query_string));
hasSemicolon = false;
break;
}
++position;
}
if (hasSemicolon) {
/* Find ';' at the end of query_string. copy query_string to stringptr except ';'. */
appendBinaryStringInfo(stringptr, query_string, strlen(query_string) - 1);
}
}
appendStringInfoSpaces(stringptr, 1);
appendStringInfoString(stringptr, quote_identifier(stmt->relation->schemaname));
appendStringInfoChar(stringptr, '.');
appendStringInfoString(stringptr, quote_identifier(stmt->relation->relname));
appendStringInfoChar(stringptr, ';');
AssembleHybridMessage(query_string_with_info, stringptr->data, global_info);
pfree_ext(stringptr->data);
pfree_ext(stringptr);
} else {
/*
* Assemble ForeignTableDesc and put it into workList.
* We will use items in workList to apply analzye operation
*/
ForeignTableDesc* foreignTableDesc = makeNode(ForeignTableDesc);
foreignTableDesc->tableName = (char*)quote_identifier(stmt->relation->relname);
foreignTableDesc->tableOid = rel_id;
foreignTableDesc->schemaName = (char*)quote_identifier(stmt->relation->schemaname);
/* Get hybridmessage and send it to data node */
*query_string_with_info = get_hybrid_message(foreignTableDesc, stmt, global_info);
pfree_ext(foreignTableDesc);
}
}
pfree_ext(task_map);
pfree_ext(hdfs_table_analyze);
pfree_ext(global_info);
}
/*
* @hdfs
* get_hybrid_message
*
* This function uses parameter table_desc generating hybridmessage which will be sent
* to data node.
*/
static char* get_hybrid_message(ForeignTableDesc* table_desc, VacuumStmt* stmt, char* foreign_tbl_schedul_message)
{
char* table_name = table_desc->tableName;
char* schema_name = table_desc->schemaName;
size_t table_len = strlen(table_name);
size_t schema_len = strlen(schema_name);
const char* analyze_key_words = "analyze \0";
const char* verbose_key_words = "verbose \0";
unsigned int analyze_key_words_len = strlen(analyze_key_words);
unsigned int verbose_key_words_len = strlen(verbose_key_words);
char* query_string = NULL;
int pos = 0;
char* scheduling_message = NULL;
errno_t errorno = EOK;
/* get right scheduling messages. */
scheduling_message = foreign_tbl_schedul_message;
/*
* If SQL sentence includes "verbose" command, we write verbose keyword into query_string.
*/
if (stmt->options & VACOPT_VERBOSE) {
query_string =
(char*)palloc(analyze_key_words_len + verbose_key_words_len + table_len + schema_len + 3);
errorno = memcpy_s(query_string, analyze_key_words_len, analyze_key_words, analyze_key_words_len);
securec_check(errorno, "\0", "\0");
pos += analyze_key_words_len;
errorno = memcpy_s(query_string + pos, verbose_key_words_len, verbose_key_words, verbose_key_words_len);
securec_check(errorno, "\0", "\0");
pos += verbose_key_words_len;
errorno = memcpy_s(query_string + pos, schema_len, schema_name, schema_len);
securec_check(errorno, "\0", "\0");
const size_t spilt_len = 1;
pos += schema_len;
errorno = memcpy_s(query_string + pos, spilt_len, ".", spilt_len);
securec_check(errorno, "\0", "\0");
pos += spilt_len;
errorno = memcpy_s(query_string + pos, table_len, table_name, table_len);
securec_check(errorno, "\0", "\0");
pos += table_len;
errorno = memcpy_s(query_string + pos, spilt_len, ";", spilt_len);
securec_check(errorno, "\0", "\0");
pos += spilt_len;
query_string[pos] = '\0';
} else {
query_string = (char*)palloc(analyze_key_words_len + table_len + schema_len + 3);
errorno = memcpy_s(query_string + pos, analyze_key_words_len, analyze_key_words, analyze_key_words_len);
securec_check(errorno, "\0", "\0");
pos += analyze_key_words_len;
errorno = memcpy_s(query_string + pos, schema_len, schema_name, schema_len);
securec_check(errorno, "\0", "\0");
const size_t spilt_len = 1;
pos += schema_len;
errorno = memcpy_s(query_string + pos, spilt_len, ".", spilt_len);
securec_check(errorno, "\0", "\0");
pos += spilt_len;
errorno = memcpy_s(query_string + pos, table_len, table_name, table_len);
securec_check(errorno, "\0", "\0");
pos += table_len;
errorno = memcpy_s(query_string + pos, spilt_len, ";", spilt_len);
securec_check(errorno, "\0", "\0");
pos += spilt_len;
query_string[pos] = '\0';
}
/* Write query_string and schedulingMesage into query_string_with_info. */
char* query_string_with_info = NULL;
AssembleHybridMessage(&query_string_with_info, query_string, scheduling_message);
/* Free memory */
pfree_ext(query_string);
return query_string_with_info;
}
/*
* @hdfs
* IsForeignTableAnalyze
*
* A table is foreign table or not. If it is a foreign tabel, does it support analyze operation?
*/
bool IsHDFSForeignTableAnalyzable(VacuumStmt* stmt)
{
Oid foreign_table_id = 0;
bool ret_value = false;
stmt->isPgFdwForeignTables = false;
stmt->isMOTForeignTable = false;
if (stmt->isForeignTables == true) {
/* "analyze foreign tables;" command. Analyze all foreign tables */
ret_value = true;
} else if (!stmt->isForeignTables && !stmt->relation) {
/* "analyze [verbose]" analyze MPP local table/tables */
ret_value = false;
} else {
foreign_table_id = RangeVarGetRelidExtended(stmt->relation, NoLock, false, false, false, true, NULL, NULL);
/*
* A MPP local table or foreign table? If it is a foreign table, support analyze or not?
* False for MPP Local table and foreign table which does not support analyze operation.
*/
ret_value = IsHDFSTableAnalyze(foreign_table_id);
stmt->isForeignTables = ret_value;
if (IsPGFDWTableAnalyze(foreign_table_id)) {
stmt->isPgFdwForeignTables = true;
}
if (IsMOTForeignTable(foreign_table_id)) {
stmt->isMOTForeignTable = true;
}
}
return ret_value;
}
#ifdef ENABLE_MULTIPLE_NODES
// we degrade the lock level for VACUUM FULL statement.
// we should wait until all the current transactions finish,
// so make sure the order of executing VACUUM FULL is right.
static void cn_do_vacuum_full_mpp_table(VacuumStmt* stmt, const char* query_string, bool is_top_level, bool sent_to_remote)
{
Oid relation_id = InvalidOid;
ExecNodes* exec_nodes = NULL;
RemoteQueryExecType exec_type = EXEC_ON_DATANODES;
// step 1: the other CN nodes work;
if (stmt->relation) {
relation_id = RangeVarGetRelid(stmt->relation, NoLock, false);
if (!IsTempTable(relation_id))
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, true, EXEC_ON_COORDS, false);
}
// step 2: myself CN node works;
// this function will commit the previous transaction:
// 1. PopActiveSnapshot()
// 2. CommitTransactionCommand()
vacuum(stmt, InvalidOid, true, NULL, is_top_level);
// 3. StartTransactionCommand()
PushActiveSnapshot(GetTransactionSnapshot());
// Though we have held lockForBackup in share mode before, but CommitTransaction has release lock
// So We must hold lockForBackup in share mode in order to prevent global backup
// during vacuum full. Because 'select pgxc_lock_for_backup' will run on all coordinator,
// so we can only hold lockForBackup in local coordinator.
//
pgxc_lock_for_utility_stmt(NULL, false);
if (!superuser() && in_logic_cluster()) {
/*
* In logic cluster mode, superuser and system DBA can execute Vacuum Full
* on all nodes; logic cluster users can execute Vacuum Full on its node group;
* other users can't execute Vacuum Full in DN nodes.
*/
Oid curr_group_oid = get_current_lcgroup_oid();
if (OidIsValid(curr_group_oid)) {
exec_nodes = GetNodeGroupExecNodes(curr_group_oid);
} else {
exec_type = EXEC_ON_NONE;
ereport(NOTICE,
(errmsg("Vacuum Full do not run in DNs because User \"%s\" don't "
"attach to any logic cluster.",
GetUserNameFromId(GetUserId()))));
}
}
// step 3: all the DN nodes work;
query_string = ConstructMesageWithMemInfo(query_string, stmt->memUsage);
ExecUtilityStmtOnNodes(query_string, exec_nodes, sent_to_remote, true, exec_type, false, (Node*)stmt);
FreeExecNodes(&exec_nodes);
// it's important that fetching statistic info from one datanode
// must be with a new transaction. because if the followings,
// step 1: CN xid1 <1000> : we notify all dn to do VACUUM FULL.
// step 2: DN xid2 <1001> : start transaction;
// Do VACUUM FULL;
// commit transaction;
// step 3: CN xid1 <1000> : query from one datanode, but xid2=1001 is the futhre event.
// so CN get the previous statisttic info instead of the newest.
// we cannot accept this sutiation, because info is out of date.
// so that start a new transaction to do fetching from one datanode.
PopActiveSnapshot();
CommitTransactionCommand();
StartTransactionCommand();
}
/*
* Code below is from ExecUtilityStmtOnNodes(), just difference
* is step->combine_type, means that don't care any result from DN.
*/
static void send_delta_merge_req(const char* query_string)
{
RemoteQuery* step = makeNode(RemoteQuery);
step->combine_type = COMBINE_TYPE_NONE;
step->exec_nodes = NULL;
step->sql_statement = pstrdup(query_string);
step->force_autocommit = true;
step->exec_type = EXEC_ON_DATANODES;
step->is_temp = false;
ExecRemoteUtility(step);
pfree_ext(step->sql_statement);
pfree_ext(step);
}
/*
* if enable_upgrade_merge_lock_mode is true, block all insert/delete/
* update on all CNs when deltamerge the same table under ExclusiveLock.
*/
static void delta_merge_with_exclusive_lock(VacuumStmt* stmt)
{
/* t_thrd.vacuum_cxt.vac_context is needed by get_rel_oids() */
int i = 0;
const int retry_times = 3;
t_thrd.vacuum_cxt.vac_context = AllocSetContextCreate(t_thrd.mem_cxt.portal_mem_cxt,
"Vacuum Deltamerge",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
List* dfsRels = get_rel_oids(InvalidOid, stmt);
PopActiveSnapshot();
CommitTransactionCommand();
ListCell* lc = NULL;
foreach (lc, dfsRels) {
vacuum_object* vo = NULL;
Relation rel = NULL;
StringInfo query = NULL;
char* schema_name = NULL;
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
vo = (vacuum_object*)lfirst(lc);
/*
* try to lock table on local CN with no long wait for three times,
* if the lock is unavailable, just skip the current relation.
*/
for (i = 0; i < retry_times; i++) {
if (ConditionalLockRelationOid(vo->tab_oid, AccessExclusiveLock)) {
rel = try_relation_open(vo->tab_oid, NoLock);
break;
}
/*
* except the last time, we sleep for a while(30s, 60s) and then try to
* lock the relation again.
*/
if (i + 1 < retry_times)
sleep((i + 1) * 30);
}
/* for deltamerge, if the relation is moved before or is locked, just skip without error report. */
if (rel == NULL) {
elog(LOG, "delta merge skip relation %d, because the relation can not be open.", vo->tab_oid);
PopActiveSnapshot();
CommitTransactionCommand();
continue;
}
schema_name = get_namespace_name(rel->rd_rel->relnamespace);
if (schema_name == NULL) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_SCHEMA_NAME), errmsg("Invalid schema oid: %u", rel->rd_rel->relnamespace)));
}
query = makeStringInfo();
appendStringInfo(query, "vacuum deltamerge \"%s\".\"%s\"", schema_name, rel->rd_rel->relname.data);
elog(DEBUG1, "%s under ExclusiveLock mode.", query->data);
/*
* "deltamerge" on other remote CNs do nothing except locking table.
* NOTE: deltamerge on all CNs is in ONE transaction. it will block
* insert/delete/update on all CNs.
*/
ExecUtilityStmtOnNodes(query->data, NULL, false, false, EXEC_ON_COORDS, false);
/*
* do real job on all DNs.
* NOTE: deltamerge on DNs is in local transaction.
*/
send_delta_merge_req(query->data);
relation_close(rel, NoLock);
/*
* Be carefull to use this function, only in the following condition:
* 1. the relation can not be in opening state;
* 2. the pointer of the rel cache can not be referenced by other place;
*/
RelationForgetRelation(RelationGetRelid(rel));
pfree_ext(query->data);
pfree_ext(query);
PopActiveSnapshot();
CommitTransactionCommand();
}
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
MemoryContextDelete(t_thrd.vacuum_cxt.vac_context);
t_thrd.vacuum_cxt.vac_context = NULL;
}
#endif
/*
* @Description: Check whether only one temp relation for analyze
* @Return true if analyze one temp relation or false
*/
bool IsAnalyzeTempRel(VacuumStmt* stmt)
{
bool is_local_tmp_table = false;
/* in case that t_thrd.vacuum_cxt.vac_context would be invalid */
if (!MemoryContextIsValid(t_thrd.vacuum_cxt.vac_context))
t_thrd.vacuum_cxt.vac_context = NULL;
if (stmt->options & VACOPT_ANALYZE) {
if (stmt->relation) {
Oid rel_id = RangeVarGetRelid(stmt->relation, AccessShareLock, false);
Relation rel = relation_open(rel_id, AccessShareLock);
if (RelationIsLocalTemp(rel)) {
is_local_tmp_table = true;
}
relation_close(rel, NoLock);
}
}
return is_local_tmp_table;
}
#ifdef ENABLE_MULTIPLE_NODES
static void cn_do_vacuum_mpp_table(VacuumStmt* stmt, const char* query_string, bool is_top_level, bool sent_to_remote)
{
ExecNodes* exec_nodes = NULL;
RemoteQueryExecType exec_type = EXEC_ON_DATANODES;
bool is_vacuum = false;
if (stmt->relation == NULL && !superuser() && in_logic_cluster()) {
/*
* In logic cluster mode, superuser and system DBA can execute Vacuum Full
* on all nodes; logic cluster users can execute Vacuum on its node group;
* other users can't execute Vacuum in DN nodes.
*/
Oid curr_group_oid = get_current_lcgroup_oid();
if (OidIsValid(curr_group_oid)) {
exec_nodes = GetNodeGroupExecNodes(curr_group_oid);
} else {
exec_type = EXEC_ON_NONE;
ereport(NOTICE,
(errmsg("Vacuum do not run in DNs because User \"%s\" don't "
"attach to any logic cluster.",
GetUserNameFromId(GetUserId()))));
}
}
// Step 0: Notify gtm if it is a vacuum
is_vacuum = (stmt->options & VACOPT_VACUUM) && (!(stmt->options & VACOPT_FULL));
if (is_vacuum && GTM_MODE) {
if (SetGTMVacuumFlag(GetCurrentTransactionKeyIfAny(), is_vacuum))
ereport(ERROR, (errcode(ERRCODE_CONNECTION_FAILURE), errmsg("GTM error, could not set vacuum flag")));
}
// Step 1: Execute query_string on all datanode
ExecUtilityStmtOnNodes(query_string, exec_nodes, sent_to_remote, true, exec_type, false, (Node*)stmt);
FreeExecNodes(&exec_nodes);
// We don't use multi transaction for analyze local temp relation
// But still use multip tratinsactics for andalyze normal relatheions
//
if (!IS_ONLY_ANALYZE_TMPTABLE) {
PopActiveSnapshot();
CommitTransactionCommand();
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
} else {
CommandCounterIncrement();
}
// Step 2: Do local vacuum
vacuum(stmt, InvalidOid, true, NULL, is_top_level);
}
/*
* The SQL command is just "analyze" without table specified, so
* we must find all the plain tables from pg_class.
*/
static List* get_analyzable_relations(bool is_foreign_tables)
{
Relation pgclass;
HeapScanDesc scan;
HeapTuple tuple;
ScanKeyData key;
Oid rel_oid;
List* oid_list = NIL;
pgclass = heap_open(RelationRelationId, AccessShareLock);
if (!is_foreign_tables) {
/* Process all plain relations listed in pg_class */
ScanKeyInit(&key, Anum_pg_class_relkind, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(RELKIND_RELATION));
scan = heap_beginscan(pgclass, SnapshotNow, 1, &key);
} else {
ScanKeyInit(&key, Anum_pg_class_relkind, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(RELKIND_FOREIGN_TABLE));
scan = heap_beginscan(pgclass, SnapshotNow, 1, &key);
}
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) {
Form_pg_class class_form = (Form_pg_class)GETSTRUCT(tuple);
if ((!is_foreign_tables && (class_form->relkind == RELKIND_RELATION)) ||
(is_foreign_tables && (class_form->relkind == RELKIND_FOREIGN_TABLE))) {
rel_oid = HeapTupleGetOid(tuple);
/* we should do some restriction for non-hdfs foreign table before do analyze. */
if (!is_foreign_tables) {
/* when vacuum/vacuum full/analyze the total database,
we skip some collect of relations, for example in CSTORE namespace,
skip other temp namespace, and our own invalid temp namespace. */
if (class_form->relnamespace == CSTORE_NAMESPACE || IsToastNamespace(class_form->relnamespace) ||
isOtherTempNamespace(class_form->relnamespace) ||
(isAnyTempNamespace(class_form->relnamespace) && !checkGroup(rel_oid, true))) {
continue;
}
/* ignore system tables, inheritors */
if (rel_oid < FirstNormalObjectId || IsInheritor(rel_oid))
continue;
} else {
/*
* we should be sure every foreign table in pg_class is hdfs foreign table
* if we do analyze command for hdfs foreign table.
*/
if (!IsHDFSTableAnalyze(rel_oid) && !IsPGFDWTableAnalyze(rel_oid))
continue;
}
if (!superuser() && in_logic_cluster()) {
Oid curr_group_oid = get_current_lcgroup_oid();
Oid table_group_oid = get_pgxc_class_groupoid(rel_oid);
if (curr_group_oid != table_group_oid)
continue;
}
oid_list = lappend_oid(oid_list, rel_oid);
}
}
heap_endscan(scan);
heap_close(pgclass, AccessShareLock);
return oid_list;
}
static void do_global_analyze_pgfdw_Rel(
VacuumStmt* stmt, Oid rel_id, const char* query_string, bool has_var, bool sent_to_remote, bool is_top_level)
{
Assert(!IS_PGXC_DATANODE);
FdwRoutine* fdw_routine = GetFdwRoutineByRelId(rel_id);
bool ret_value = false;
PGFDWTableAnalyze* info = (PGFDWTableAnalyze*)palloc0(sizeof(PGFDWTableAnalyze));
info->relid = rel_id;
volatile bool use_own_xacts = true;
if (use_own_xacts) {
if (ActiveSnapshotSet())
PopActiveSnapshot();
/* matches the StartTransaction in PostgresMain() */
CommitTransactionCommand();
}
if (use_own_xacts) {
StartTransactionCommand();
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
}
if (NULL != fdw_routine->AnalyzeForeignTable) {
ret_value = fdw_routine->AnalyzeForeignTable(NULL, NULL, NULL, (void*)info, false);
}
FetchGlobalPgfdwStatistics(stmt, has_var, info);
if (use_own_xacts) {
PopActiveSnapshot();
CommitTransactionCommand();
}
if (use_own_xacts) {
/*
* This matches the CommitTransaction waiting for us in
* PostgresMain().
*/
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
}
if (has_var) {
char* query_string_with_info = NULL;
stmt->orgCnNodeNo = PGXCNodeGetNodeIdFromName(g_instance.attr.attr_common.PGXCNodeName, PGXC_NODE_COORDINATOR);
attatch_global_info(&query_string_with_info, stmt, query_string, has_var, ANALYZENORMAL, rel_id, NULL);
if (!IsConnFromCoord()) {
notify_other_cn_get_statistics(query_string_with_info, sent_to_remote);
}
if (query_string_with_info != NULL) {
pfree_ext(query_string_with_info);
query_string_with_info = NULL;
}
}
}
/**
* @global stats
* @Description: do analyze for one hdfs rel
* @in stmt - the statment for analyze or vacuum command
* @in rel_id - the relation oid for analyze or vacuum
* @in deltarelid - the relation oid for delta table
* @in query_string - the query string for analyze or vacuum command
* @in is_replication - the flag identify the relation which analyze or vacuum is replication or not
* @in has_var - the flag identify do analyze for one relation or all database
* @in attnum - how many attribute num of the relation
* @in sent_to_remote - identify if this statement has been sent to the nodes
* @in is_top_level - is_top_level should be passed down from ProcessUtility
* @in global_stats_context - the global stats context which create in caller, we will switch to it when we malloc
* stadndistinct.
* @return: void
*
* step 1: get estimate total rows from DNs
* step 2: compute sample rate
* step 3: get sample rows from DNs
* step 4: get real total tuples from pg_class in DNs except replication table
* step 5: get stadndistinct from DN1
* step 6: compute statistics and update in pg_statistics
*/
static void do_global_analyze_hdfs_rel(VacuumStmt* stmt, Oid rel_id, Oid deltarelid, const char* query_string,
bool is_replication, bool has_var, int attnum, bool sent_to_remote, bool is_top_level,
MemoryContext global_stats_context, int* table_num)
{
VacuumStmt* delta_stmt = NULL;
char* query_string_with_info = NULL;
MemoryContext old_context = NULL;
int one_tuple_size1 = 0;
int one_tuple_size2 = 0;
if (!check_analyze_permission(rel_id)) {
(*table_num)--;
return;
}
/* get one_tuple_size for analyze memory control on cn */
if (*table_num == 1) {
Relation rel1 = relation_open(rel_id, AccessShareLock);
one_tuple_size1 = GetOneTupleSize(stmt, rel1);
relation_close(rel1, AccessShareLock);
Relation rel2 = relation_open(deltarelid, AccessShareLock);
one_tuple_size2 = GetOneTupleSize(stmt, rel2);
relation_close(rel2, AccessShareLock);
}
/*
* get delta relation information and make new VacuumStmt for
* get reltuples and dndistinct from dn for delta table.
*/
Relation delta_rel = relation_open(deltarelid, AccessShareLock);
delta_stmt = makeNode(VacuumStmt);
delta_stmt->relation = makeNode(RangeVar);
delta_stmt->relation->relname = pstrdup(RelationGetRelationName(delta_rel));
delta_stmt->relation->schemaname = get_namespace_name(CSTORE_NAMESPACE, true);
delta_stmt->options = stmt->options;
delta_stmt->flags = stmt->flags;
delta_stmt->va_cols = (List*)copyObject(stmt->va_cols);
relation_close(delta_rel, AccessShareLock);
/*
* if it is a REPLICATION table, we set stmt->totalRowCnts = 0, then the sample rate=0, DNs send 0 rows to CN.
* then CN do not compute stats and just fetch stats from DN1. same for System Relation
*/
if (is_replication) {
stmt->nodeNo = 0;
/* set inital value of totalRowCnts, because we will get real total row count from DN1 for replication later. */
stmt->pstGlobalStatEx[ANALYZEMAIN - 1].totalRowCnts = 0;
stmt->pstGlobalStatEx[ANALYZEDELTA - 1].totalRowCnts = 0;
stmt->pstGlobalStatEx[ANALYZECOMPLEX - 1].totalRowCnts = 0;
stmt->pstGlobalStatEx[ANALYZEMAIN - 1].topRowCnts = 0;
stmt->pstGlobalStatEx[ANALYZEDELTA - 1].topRowCnts = 0;
stmt->pstGlobalStatEx[ANALYZECOMPLEX - 1].topRowCnts = 0;
/* memory released after both main and delta finish */
stmt->pstGlobalStatEx[ANALYZEMAIN - 1].topMemSize =
DEFAULT_SAMPLE_ROWCNT * (one_tuple_size1 + one_tuple_size2) / 1024;
stmt->pstGlobalStatEx[ANALYZEDELTA - 1].topMemSize = 0;
stmt->pstGlobalStatEx[ANALYZECOMPLEX - 1].topMemSize = 0;
stmt->pstGlobalStatEx[ANALYZEMAIN - 1].num_samples = 0;
stmt->pstGlobalStatEx[ANALYZEDELTA - 1].num_samples = 0;
stmt->pstGlobalStatEx[ANALYZECOMPLEX - 1].num_samples = 0;
/* identify the current table is replication. */
stmt->pstGlobalStatEx[ANALYZEMAIN - 1].isReplication = true;
stmt->pstGlobalStatEx[ANALYZEDELTA - 1].isReplication = true;
stmt->pstGlobalStatEx[ANALYZECOMPLEX - 1].isReplication = true;
} else {
/* Set inital sampleRate, it means CN get estimate total row count from DN If sampleRate is -1. */
global_stats_set_samplerate(ANALYZEMAIN, stmt, NULL);
stmt->pstGlobalStatEx[ANALYZEMAIN - 1].isReplication = false;
stmt->pstGlobalStatEx[ANALYZEDELTA - 1].isReplication = false;
stmt->pstGlobalStatEx[ANALYZECOMPLEX - 1].isReplication = false;
stmt->pstGlobalStatEx[ANALYZEMAIN - 1].exec_query = true;
stmt->pstGlobalStatEx[ANALYZEDELTA - 1].exec_query = true;
stmt->pstGlobalStatEx[ANALYZECOMPLEX - 1].exec_query = true;
stmt->sampleTableRequired = false;
/*
* Create temp table on coordinator for analyze with relative-estimation or
* absolute-estimate with debuging.
*/
analyze_tmptbl_debug_cn(rel_id, InvalidOid, stmt, false);
analyze_tmptbl_debug_cn(deltarelid, rel_id, stmt, true);
/* Attatch global info to the query_string for get estimate total rows. */
attatch_global_info(&query_string_with_info, stmt, query_string, has_var, ANALYZEMAIN, InvalidOid);
/*
* step 1: get estimate total rows from DNs
* we set sampleRate is -1 identify the action.
* if sampleRate more or equal 0, it means CN should get sample rows from DN.
*/
DEBUG_START_TIMER;
(void)ExecRemoteVacuumStmt(stmt, query_string_with_info, sent_to_remote, ARQ_TYPE_TOTALROWCNTS, ANALYZEMAIN);
DEBUG_STOP_TIMER(
"Fetch estimate totalRowCount for table: %s, %s", stmt->relation->relname, delta_stmt->relation->relname);
}
/* workload client manager */
if (*table_num > 0) {
UtilityDesc desc;
errno_t rc = memset_s(&desc, sizeof(UtilityDesc), 0, sizeof(UtilityDesc));
securec_check(rc, "\0", "\0");
AdaptMem* mem_usage = &stmt->memUsage;
bool need_sort = false;
/* cn row count, no more than DEFAULT_SAMPLE_ROWCNT */
int cnRowCnts1 = (int)Min(DEFAULT_SAMPLE_ROWCNT, stmt->pstGlobalStatEx[ANALYZEMAIN - 1].totalRowCnts);
int cnRowCnts2 = (int)Min(DEFAULT_SAMPLE_ROWCNT, stmt->pstGlobalStatEx[ANALYZEDELTA - 1].totalRowCnts);
/* dn memsize (top) */
int top_mem_size = stmt->pstGlobalStatEx[ANALYZEMAIN - 1].topMemSize;
if (stmt->options & VACOPT_FULL) {
Relation rel = relation_open(rel_id, AccessShareLock);
if (rel->rd_rel->relhasclusterkey) {
SetSortMemInfo(&desc,
cnRowCnts1 + cnRowCnts2,
Max(one_tuple_size1, one_tuple_size2),
true,
false,
u_sess->attr.attr_memory.work_mem);
need_sort = true;
}
relation_close(rel, AccessShareLock);
}
/* Get top row count of cn/dn */
top_mem_size = Max(top_mem_size, (cnRowCnts1 * one_tuple_size1 + cnRowCnts2 * one_tuple_size2) / 1024);
desc.query_mem[0] = (int)Max(desc.query_mem[0], BIG_MEM_RATIO * top_mem_size);
/* Adjust assigned query mem if it's too small */
desc.assigned_mem = Max(Max(desc.query_mem[0], STATEMENT_MIN_MEM * 1024L), desc.assigned_mem);
desc.cost = (double)(desc.query_mem[0] / PAGE_SIZE) * u_sess->attr.attr_sql.seq_page_cost;
if (*table_num > 1) {
desc.query_mem[0] = Max(desc.query_mem[0], STATEMENT_MIN_MEM * 1024L);
/* Adjust assigned query mem if it's too small */
desc.assigned_mem = Max(Max(desc.query_mem[0], STATEMENT_MIN_MEM * 1024L), desc.assigned_mem);
desc.cost = g_instance.cost_cxt.disable_cost;
}
if (desc.query_mem[1] == 0)
desc.query_mem[1] = desc.query_mem[0];
WLMInitQueryPlan((QueryDesc*)&desc, false);
dywlm_client_manager((QueryDesc*)&desc, false);
if (need_sort)
AdjustIdxMemInfo(mem_usage, &desc);
/* set 0 for only send to wlm at first time */
*table_num = 0;
}
/*
* step 2: compute sample rate for normal table.
* compute sample rate for HDFS table(include dfs/delta/complex table).
*/
(void)compute_sample_size(stmt, 0, NULL, rel_id, ANALYZEMAIN - 1);
(void)compute_sample_size(stmt, 0, NULL, deltarelid, ANALYZEDELTA - 1);
(void)compute_sample_size(stmt, 0, NULL, rel_id, ANALYZECOMPLEX - 1);
elog(DEBUG1,
"Step 2: Compute sample rate[%.12f][%.12f][%.12f] for DFS table.",
stmt->pstGlobalStatEx[ANALYZEMAIN - 1].sampleRate,
stmt->pstGlobalStatEx[ANALYZEDELTA - 1].sampleRate,
stmt->pstGlobalStatEx[ANALYZECOMPLEX - 1].sampleRate);
/* Attatch global info to the query_string. for get sample. */
attatch_global_info(&query_string_with_info, stmt, query_string, has_var, ANALYZEMAIN, InvalidOid);
elog(DEBUG1,
"Analyzing [%s], oid is [%d], the messege is [%s]",
stmt->relation->relname,
rel_id,
query_string_with_info);
/*
* We only send query string to all datanodes in two cases:
* 1. for replication table;
* 2. there is no split map of all datanodes for hdfs foreign tables.
*/
if (stmt->pstGlobalStatEx[ANALYZEMAIN - 1].isReplication) {
ExecUtilityStmtOnNodes(query_string_with_info, NULL, sent_to_remote, true, EXEC_ON_DATANODES, false);
} else {
/* step 3: get sample rows and real total rows from DNs */
DEBUG_START_TIMER;
stmt->sampleRows = ExecRemoteVacuumStmt(stmt, query_string_with_info, sent_to_remote, ARQ_TYPE_SAMPLE, ANALYZEMAIN);
DEBUG_STOP_TIMER(
"Get sample rows from all DNs for table: %s, %s", stmt->relation->relname, delta_stmt->relation->relname);
}
PopActiveSnapshot();
/*
* It will swich CurrentMemoryContext to t_thrd.top_mem_cxt after call CommitTransactionCommand(),
* and swich to u_sess->top_transaction_mem_cxt after call StartTransactionCommand().
*/
CommitTransactionCommand();
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
/* step 4: get stadndistinct from DN1. */
/* current memctx is u_sess->top_transaction_mem_cxt, we will malloc memory for stadndistinct, so we must switch to
* global_stats_context. */
old_context = MemoryContextSwitchTo(global_stats_context);
DEBUG_START_TIMER;
/* get dndistinct from dn1 for delta table. */
delta_stmt->tableidx = ANALYZEDELTA - 1;
/* get some statistic info which have collected for delta table. */
delta_stmt->pstGlobalStatEx[delta_stmt->tableidx].totalRowCnts =
stmt->pstGlobalStatEx[delta_stmt->tableidx].totalRowCnts;
delta_stmt->pstGlobalStatEx[delta_stmt->tableidx].eAnalyzeMode = ANALYZEDELTA;
delta_stmt->pstGlobalStatEx[delta_stmt->tableidx].isReplication =
stmt->pstGlobalStatEx[delta_stmt->tableidx].isReplication;
/* set inital value of dndistinct. */
set_dndistinct_coors(delta_stmt, attnum);
/* fetch dndistinct from dn1 and copy to stmt using for vacuum later. */
FetchGlobalStatistics(delta_stmt, deltarelid, stmt->relation, is_replication);
stmt->pstGlobalStatEx[ANALYZEDELTA - 1].eAnalyzeMode = ANALYZEDELTA;
stmt->pstGlobalStatEx[delta_stmt->tableidx].totalRowCnts =
delta_stmt->pstGlobalStatEx[delta_stmt->tableidx].totalRowCnts;
stmt->pstGlobalStatEx[delta_stmt->tableidx].dndistinct = delta_stmt->pstGlobalStatEx[delta_stmt->tableidx].dndistinct;
stmt->pstGlobalStatEx[delta_stmt->tableidx].correlations =
delta_stmt->pstGlobalStatEx[delta_stmt->tableidx].correlations;
stmt->pstGlobalStatEx[delta_stmt->tableidx].attnum = delta_stmt->pstGlobalStatEx[delta_stmt->tableidx].attnum;
/* get dndistinct from dn1 for dfs table. */
stmt->tableidx = ANALYZEMAIN - 1;
stmt->pstGlobalStatEx[stmt->tableidx].eAnalyzeMode = ANALYZEMAIN;
set_dndistinct_coors(stmt, attnum);
/* there is no tuples, we need not go on continue. */
FetchGlobalStatistics(stmt, rel_id, NULL, is_replication);
/* get dndistinct from dn1 for complex table. */
stmt->tableidx = ANALYZECOMPLEX - 1;
stmt->pstGlobalStatEx[stmt->tableidx].eAnalyzeMode = ANALYZECOMPLEX;
set_dndistinct_coors(stmt, attnum);
/* there is no tuples, we need not go on continue. */
FetchGlobalStatistics(stmt, rel_id, NULL, is_replication);
DEBUG_STOP_TIMER(
"Fetch statistics from DN1 for table: %s, %s", stmt->relation->relname, delta_stmt->relation->relname);
/* malloc sample memory for complex table and copy sample rows from dfs table and delta table. */
if (!stmt->sampleTableRequired)
set_complex_sample(stmt);
(void)MemoryContextSwitchTo(old_context);
/* step 5: compute statistics and update in pg_statistics. */
vacuum(stmt, InvalidOid, true, NULL, is_top_level);
/*
* for VACUUM ANALYZE table where options=3, vacuum has set use_own_xacts true and already poped ActiveSnapshot,
* so we don't want to pop again.
*/
if (ActiveSnapshotSet())
PopActiveSnapshot();
CommitTransactionCommand();
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
if (stmt->tmpSampleTblNameList && log_min_messages > DEBUG1) {
dropSampleTable(get_sample_tblname(ANALYZEMAIN, stmt->tmpSampleTblNameList));
dropSampleTable(get_sample_tblname(ANALYZEDELTA, stmt->tmpSampleTblNameList));
/*
* Drop table is twophase-transaction, if dn fault at this time,
* current transaction don't be cleanned immadiately. So we should commit the transaction and
* Start a new transaction.
*/
PopActiveSnapshot();
CommitTransactionCommand();
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
}
/* Notify the other CN nodes if do analyze/vacuum one relation and the relation is not temporary. */
if (has_var && !IsTempTable(rel_id)) {
notify_other_cn_get_statistics(query_string_with_info, sent_to_remote);
}
if (query_string_with_info != NULL) {
pfree_ext(query_string_with_info);
query_string_with_info = NULL;
}
return;
}
/**
* @global stats
* @Description: do analyze for one rel
* @in stmt - the statment for analyze or vacuum command
* @in rel_id - the relation oid for analyze or vacuum
* @in query_string - the query string for analyze or vacuum command
* @in is_replication - the flag identify the relation which analyze or vacuum is replication or not
* @in has_var - the flag identify do analyze for one relation or all database
* @in attnum - how many attribute num of the relation
* @in sent_to_remote - identify if this statement has been sent to the nodes
* @in is_top_level - is_top_level should be passed down from ProcessUtility
* @return: void
*
* step 1: get estimate total rows from DNs
* step 2: compute sample rate
* step 3: get sample rows from DNs
* step 4: get real total tuples from pg_class in DNs except replication table
* step 5: get stadndistinct from DN1
* step 6: compute statistics and update in pg_statistics
*/
static void do_global_analyze_rel(VacuumStmt* stmt, Oid rel_id, const char* query_string, bool is_replication, bool has_var,
int attnum, bool sent_to_remote, bool is_top_level, int* table_num)
{
char* query_string_with_info = NULL;
char* foreign_tbl_schedul_message = NULL;
stmt->tableidx = ANALYZENORMAL;
int one_tuple_size = 0;
if (!check_analyze_permission(rel_id)) {
(*table_num)--;
return;
}
/* get one_tuple_size for analyze memory control on cn */
if (*table_num == 1) {
Relation rel = relation_open(rel_id, AccessShareLock);
one_tuple_size = GetOneTupleSize(stmt, rel);
relation_close(rel, AccessShareLock);
}
/*
* Call scheduler to get datanode which will execute analyze operation
* and file list.
*/
if (stmt->isForeignTables && !stmt->isPgFdwForeignTables)
foreign_tbl_schedul_message = get_scheduling_message(rel_id, stmt);
/*
* if it is a REPLICATION table, we set stmt->totalRowCnts = 0, then the sample rate=0, DNs send 0 rows to CN.
* then CN do not compute stats and just fetch stats from DN1. same for System Relation
*/
if (is_replication) {
stmt->nodeNo = stmt->isForeignTables ? stmt->nodeNo : 0;
/* set inital value of totalRowCnts, because we will get real total row count from DN1 for replication later. */
stmt->pstGlobalStatEx[ANALYZENORMAL].totalRowCnts = 0;
stmt->pstGlobalStatEx[ANALYZENORMAL].topRowCnts = 0;
stmt->pstGlobalStatEx[ANALYZENORMAL].topMemSize = DEFAULT_SAMPLE_ROWCNT * one_tuple_size / 1024;
stmt->num_samples = 0;
/* identify the current table is replication. */
stmt->pstGlobalStatEx[ANALYZENORMAL].isReplication = true;
} else {
/* Set inital sampleRate, it means CN get estimate total row count from DN If sampleRate is -1. */
global_stats_set_samplerate(ANALYZENORMAL, stmt, NULL);
stmt->pstGlobalStatEx[ANALYZENORMAL].isReplication = false;
stmt->pstGlobalStatEx[ANALYZENORMAL].exec_query = true;
stmt->sampleTableRequired = false;
analyze_tmptbl_debug_cn(rel_id, InvalidOid, stmt, true);
/* Attatch global info to the query_string for get estimate total rows. */
attatch_global_info(
&query_string_with_info, stmt, query_string, has_var, ANALYZENORMAL, rel_id, foreign_tbl_schedul_message);
/*
* Step 1: get estimated total rows from DNs
*
* We set sampleRate to -1 identify the action.
* if sampleRate more or equal 0, it means CN should get sample rows from DN.
*/
DEBUG_START_TIMER;
(void)ExecRemoteVacuumStmt(stmt, query_string_with_info, sent_to_remote, ARQ_TYPE_TOTALROWCNTS, ANALYZENORMAL);
DEBUG_STOP_TIMER("Fetch estimate totalRowCount for table: %s", stmt->relation->relname);
elog(ES_LOGLEVEL,
"Step 1 > table: %s, estimated total row count: [%.2lf]",
stmt->relation->relname,
stmt->pstGlobalStatEx[ANALYZENORMAL].totalRowCnts);
/* The memory of query_string_with_info can be freed because it is no use. */
if (query_string_with_info != NULL) {
pfree_ext(query_string_with_info);
query_string_with_info = NULL;
}
}
/* workload client manager */
if (*table_num > 0) {
UtilityDesc desc;
errno_t rc = memset_s(&desc, sizeof(UtilityDesc), 0, sizeof(UtilityDesc));
securec_check(rc, "\0", "\0");
AdaptMem* mem_usage = &stmt->memUsage;
bool need_sort = false;
/* cn row count, no more than DEFAULT_SAMPLE_ROWCNT */
int cn_row_cnts = (int)Min(DEFAULT_SAMPLE_ROWCNT, stmt->pstGlobalStatEx[ANALYZENORMAL].totalRowCnts);
/* dn mem size (top) */
int top_mem_size = stmt->pstGlobalStatEx[ANALYZENORMAL].topMemSize;
bool use_tenant = false;
int available_mem = 0;
int max_mem = 0;
dywlm_client_get_memory_info(&max_mem, &available_mem, &use_tenant);
if (stmt->options & VACOPT_FULL) {
Relation rel = relation_open(rel_id, AccessShareLock);
if (rel->rd_rel->relhasclusterkey) {
SetSortMemInfo(&desc, cn_row_cnts, one_tuple_size, true, false, u_sess->attr.attr_memory.work_mem);
need_sort = true;
}
/* when rel has index, we give a rough estimation */
List* index_ids = RelationGetIndexList(rel);
if (index_ids != NIL) {
if (desc.assigned_mem == 0) {
desc.assigned_mem = max_mem;
}
desc.cost = g_instance.cost_cxt.disable_cost;
desc.query_mem[0] = Max(STATEMENT_MIN_MEM * 1024, desc.query_mem[0]);
need_sort = true;
list_free_ext(index_ids);
}
relation_close(rel, AccessShareLock);
}
/* Get top row count of cn/dn */
top_mem_size = Max(top_mem_size, cn_row_cnts * one_tuple_size / 1024);
desc.query_mem[0] = (int)Max(BIG_MEM_RATIO * top_mem_size, desc.query_mem[0]);
/* Adjust assigned query mem if it's too small */
desc.assigned_mem = Max(Max(desc.query_mem[0], STATEMENT_MIN_MEM * 1024L), desc.assigned_mem);
desc.cost = Max((double)(desc.query_mem[0] / PAGE_SIZE) * u_sess->attr.attr_sql.seq_page_cost, desc.cost);
if (*table_num > 1) {
desc.query_mem[0] = Max(desc.query_mem[0], STATEMENT_MIN_MEM * 1024L);
/* Adjust assigned query mem if it's too small */
desc.assigned_mem = Max(Max(desc.query_mem[0], STATEMENT_MIN_MEM * 1024L), desc.assigned_mem);
desc.cost = g_instance.cost_cxt.disable_cost;
}
/* We use assigned variable if query_mem is set */
if (VALID_QUERY_MEM()) {
desc.query_mem[0] = Max(desc.query_mem[0], max_mem);
desc.query_mem[1] = Max(desc.query_mem[1], max_mem);
}
if (desc.query_mem[1] == 0)
desc.query_mem[1] = desc.query_mem[0];
WLMInitQueryPlan((QueryDesc*)&desc, false);
dywlm_client_manager((QueryDesc*)&desc, false);
if (need_sort)
AdjustIdxMemInfo(mem_usage, &desc);
/* set 0 for only send to wlm at first time */
*table_num = 0;
}
/* get memory for stadndistinct. */
set_dndistinct_coors(stmt, attnum);
/* step 2: compute sample rate for normal table. */
(void)compute_sample_size(stmt, 0, NULL, rel_id, ANALYZENORMAL);
elog(DEBUG1,
"Step 2: Compute sample rate[%.12f] for normal table: %s.",
stmt->pstGlobalStatEx[0].sampleRate,
stmt->relation->relname);
elog(ES_LOGLEVEL,
"Step 2 > table: %s, compute sample rate [%.12f]",
stmt->relation->relname,
stmt->pstGlobalStatEx[ANALYZENORMAL].sampleRate);
/* Attatch global info to the query_string. for get sample. */
attatch_global_info(&query_string_with_info, stmt, query_string, has_var, ANALYZENORMAL, rel_id, foreign_tbl_schedul_message);
elog(DEBUG1,
"Analyzing [%s], oid is [%d], the messege is [%s]",
stmt->relation->relname,
rel_id,
query_string_with_info);
/*
* We only send query string to all datanodes in two cases:
* 1. for replication table;
* 2. there is no split map of all datanodes for hdfs foreign tables.
*/
if (stmt->pstGlobalStatEx[stmt->tableidx].isReplication ||
(stmt->isForeignTables && (stmt->hdfsforeignMapDnList == NIL))) {
bool analyze_force_autocommit = IS_ONLY_ANALYZE_TMPTABLE ? false : true;
ExecUtilityStmtOnNodes(
query_string_with_info, NULL, sent_to_remote, analyze_force_autocommit, EXEC_ON_DATANODES, false, (Node*)stmt);
} else {
DEBUG_START_TIMER;
/* step 3: get sample rows and real total rows from DNs */
stmt->sampleRows =
ExecRemoteVacuumStmt(stmt, query_string_with_info, sent_to_remote, ARQ_TYPE_SAMPLE, ANALYZENORMAL);
DEBUG_STOP_TIMER("Get sample rows from all DNs for table: %s", stmt->relation->relname);
}
if (stmt->isForeignTables && !stmt->isPgFdwForeignTables)
pfree_ext(foreign_tbl_schedul_message);
if (!IS_ONLY_ANALYZE_TMPTABLE) {
if (ActiveSnapshotSet())
PopActiveSnapshot();
/*
* It will switch CurrentMemoryContext to t_thrd.top_mem_cxt after call CommitTransactionCommand(),
* and switch to u_sess->top_transaction_mem_cxt after call StartTransactionCommand().
*/
CommitTransactionCommand();
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
} else
CommandCounterIncrement();
/* step 4: get stadndistinct from DN1. */
stmt->pstGlobalStatEx[stmt->tableidx].eAnalyzeMode = ANALYZENORMAL;
DEBUG_START_TIMER;
FetchGlobalStatistics(stmt, rel_id, NULL, is_replication);
DEBUG_STOP_TIMER("Fetch statistics from DN1 for table: %s", stmt->relation->relname);
/* step 5: compute statistics and update in pg_statistics. */
vacuum(stmt, InvalidOid, true, NULL, is_top_level);
if (!IS_ONLY_ANALYZE_TMPTABLE) {
/*
* for VACUUM ANALYZE table where options=3, vacuum has set use_own_xacts
* true and already poped ActiveSnapshot, so we don't want to pop again.
*/
if (ActiveSnapshotSet())
PopActiveSnapshot();
CommitTransactionCommand();
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
} else
CommandCounterIncrement();
if (stmt->tmpSampleTblNameList && log_min_messages > DEBUG1) {
dropSampleTable(get_sample_tblname(ANALYZENORMAL, stmt->tmpSampleTblNameList));
/*
* Drop table is twophase-transaction, if dn fault at this time,
* current transaction don't be cleanned immadiately. So we should commit the transaction and
* Start a new transaction.
*/
PopActiveSnapshot();
CommitTransactionCommand();
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
}
/* Notify the other CN nodes if do analyze/vacuum one relation and the relation is not temporary. */
if (has_var && !IsTempTable(rel_id)) {
notify_other_cn_get_statistics(query_string_with_info, sent_to_remote);
}
if (query_string_with_info != NULL) {
pfree_ext(query_string_with_info);
query_string_with_info = NULL;
}
return;
}
/* Process analyze command from gsql in CN. */
static void do_global_analyze_mpp_table(VacuumStmt* stmt, const char* query_string, bool is_top_level, bool sent_to_remote)
{
List* oid_list = NIL;
ListCell* cur = NULL;
Oid rel_oid = InvalidOid;
bool has_var = false;
char* query_string_with_info = NULL;
MemoryContext old_context = NULL;
MemoryContext global_stats_context = NULL;
PgxcNodeGetOids(NULL, NULL, NULL, (int*)&stmt->DnCnt, false);
/* Set flag has_var which identify do analyze for one relation or all database. */
if (stmt->relation == NULL) {
has_var = false;
oid_list = get_analyzable_relations(stmt->isForeignTables);
} else {
rel_oid = RangeVarGetRelid(stmt->relation, NoLock, false);
oid_list = lappend_oid(oid_list, rel_oid);
has_var = true;
}
/* Create new memcontext for analyze/vacuum. */
global_stats_context = AllocSetContextCreate(t_thrd.mem_cxt.msg_mem_cxt,
"global_stats_context",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/*
* Switch to global_stats_context because it only used to get sample from all datanodes
* and we want to put this memory into this memory context.
*/
old_context = MemoryContextSwitchTo(global_stats_context);
/* set table_num for wlm */
int table_num = 0;
if (IS_PGXC_COORDINATOR && ENABLE_WORKLOAD_CONTROL && !IsAnyAutoVacuumProcess())
table_num = list_length(oid_list);
/* Loop to process each selected relation. */
foreach (cur, oid_list) {
/*
* Check if the table is REPLICATION for global analyze,
* if it is a REPLICATION table, just fetch stats from DN1
*/
int attnum = 0;
bool is_replication = false;
Oid rel_id = lfirst_oid(cur);
DistributeBy* distributeby = NULL;
Relation rel = try_relation_open(rel_id, AccessShareLock);
if (!rel) {
table_num--;
elog(DEBUG1, "Skip analyze table for cannot get AccessShareLock");
continue;
}
if (RelationIsView(rel)) {
elog(LOG, "View is an unanalyzable object");
relation_close(rel, AccessShareLock);
continue;
}
if (RelationIsSequnce(rel)) {
elog(LOG, "Sequnce is an unanalyzable object");
relation_close(rel, AccessShareLock);
continue;
}
attnum = rel->rd_att->natts;
/* No need to send analyze/vacuum command to dn for non-ordinary table and non-toast table. */
if (rel_id < FirstNormalObjectId ||
(rel->rd_rel->relkind != RELKIND_RELATION && rel->rd_rel->relkind != RELKIND_TOASTVALUE &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)) {
/* workload client manager */
if (table_num > 0) {
UtilityDesc desc;
errno_t rc = memset_s(&desc, sizeof(UtilityDesc), 0, sizeof(UtilityDesc));
securec_check(rc, "\0", "\0");
if (table_num == 1) {
int one_tuple_size = GetOneTupleSize(stmt, rel);
desc.query_mem[0] = (int)(BIG_MEM_RATIO * DEFAULT_SAMPLE_ROWCNT * one_tuple_size / 1024L);
desc.cost = (double)(desc.query_mem[0] / PAGE_SIZE) * u_sess->attr.attr_sql.seq_page_cost;
} else {
/* not use work_mem for analyze all database, because it may be different in cn/dn */
desc.query_mem[0] = STATEMENT_MIN_MEM * 1024L;
desc.cost = g_instance.cost_cxt.disable_cost;
}
relation_close(rel, AccessShareLock);
desc.query_mem[1] = desc.query_mem[0];
WLMInitQueryPlan((QueryDesc*)&desc, false);
dywlm_client_manager((QueryDesc*)&desc, false);
/* set 0 for only send to wlm at first time */
table_num = 0;
} else
relation_close(rel, AccessShareLock);
stmt->tableidx = ANALYZENORMAL;
set_dndistinct_coors(stmt, attnum);
/* All the coordinator and datanodes work */
ExecUtilityStmtOnNodes(query_string, NULL, sent_to_remote, true, EXEC_ON_ALL_NODES, false, (Node*)stmt);
vacuum(stmt, InvalidOid, true, NULL, is_top_level);
continue;
}
/* Check the relation which analyze or vacuum is replication or not. */
if (!IsSystemRelation(rel)) {
distributeby = getTableDistribution(rel_id);
stmt->disttype = distributeby->disttype;
is_replication = (distributeby->disttype == DISTTYPE_REPLICATION);
}
/* It should get relname and schemaname for every relation if do analyze for all database */
if (!has_var) {
stmt->relation = makeNode(RangeVar);
stmt->relation->relname = pstrdup(RelationGetRelationName(rel));
}
PG_TRY();
{
stmt->relation->schemaname = get_namespace_name(RelationGetNamespace(rel), true);
/* do analyze for HDFS table. */
if (!(stmt->isForeignTables && IsHDFSTableAnalyze(rel_id)) && RelationIsDfsStore(rel)) {
Oid deltaRelId = rel->rd_rel->reldeltarelid;
relation_close(rel, AccessShareLock);
do_global_analyze_hdfs_rel(stmt,
rel_id,
deltaRelId,
query_string,
is_replication,
has_var,
attnum,
sent_to_remote,
is_top_level,
global_stats_context,
&table_num);
} else if (IsPGFDWTableAnalyze(rel_id)) {
relation_close(rel, AccessShareLock);
/* do analyze for postgres foreign table. */
do_global_analyze_pgfdw_Rel(stmt, rel_id, query_string, has_var, sent_to_remote, is_top_level);
} else {
/* do analyze for one relation of normal or hdfs foreign table. */
relation_close(rel, AccessShareLock);
do_global_analyze_rel(
stmt, rel_id, query_string, is_replication, has_var, attnum, sent_to_remote, is_top_level, &table_num);
}
stmt->relation->schemaname = NULL;
}
PG_CATCH();
{
stmt->relation->schemaname = NULL;
PG_RE_THROW();
}
PG_END_TRY();
/*
* Analyzing a big table or analyzing database may consume a lot of memeory,
* so we reset the memory after analyze each relation in order to avoid memory increase.
*/
MemoryContextReset(global_stats_context);
/*
* We have finished analyze for one relation, so we should switch to global_stats_context
* in order to do analyze for next relation.
*/
(void)MemoryContextSwitchTo(global_stats_context);
stmt->tmpSampleTblNameList = NIL;
}
/* Notify the other CN get statistic if analyze for all database. */
if (!has_var) {
/*
* We should construct the new query string include the PGXCNodeId of the current CN,
* in order to tell other CN get statistic, because other CN need to know which CN is orignal.
* the AnalyzeMode is no use because it will send analyze command with no table name to other CN.
*/
attatch_global_info(&query_string_with_info, stmt, query_string, true, ANALYZENORMAL, InvalidOid);
notify_other_cn_get_statistics(query_string_with_info, sent_to_remote);
if (query_string_with_info != NULL) {
pfree_ext(query_string_with_info);
query_string_with_info = NULL;
}
}
(void)MemoryContextSwitchTo(old_context);
MemoryContextDelete(global_stats_context);
}
/*
* do_vacuum_mpp_table_local_cn
*
* @Description: do vacuum/analyze where come from client in local coordinator.
* @in stmt - the statment for analyze or vacuum command
* @in query_string - the query string for analyze or vacuum command
* @in is_top_level - true if executing a "top level" (interactively issued) command
* @in sent_to_remote - identify if this statement has been sent to the nodes
*/
static void do_vacuum_mpp_table_local_cn(VacuumStmt* stmt, const char* query_string, bool is_top_level, bool sent_to_remote)
{
/* clean empty hdfs directories of value partition just on main CN */
if (stmt->options & VACOPT_HDFSDIRECTORY) {
vacuum(stmt, InvalidOid, true, NULL, is_top_level);
return;
}
/*
* Now VACUUM FULL statement has different executing order from
* normal VACUUM statement. Degrading lock level results in this different.
*/
if (stmt->options & VACOPT_VERIFY) {
if (stmt->relation != NULL) {
DoGlobalVerifyMppTable(stmt, query_string, sent_to_remote);
} else {
DoGlobalVerifyDatabase(stmt, query_string, sent_to_remote);
}
} else if (stmt->options & VACOPT_ANALYZE)
do_global_analyze_mpp_table(stmt, query_string, is_top_level, sent_to_remote);
else if (stmt->options & VACOPT_FULL)
cn_do_vacuum_full_mpp_table(stmt, query_string, is_top_level, sent_to_remote);
else if (stmt->options & VACOPT_MERGE)
delta_merge_with_exclusive_lock(stmt);
else
cn_do_vacuum_mpp_table(stmt, query_string, is_top_level, sent_to_remote);
}
#endif
/*
* DoVacuumMppTableOtherCN
*
* @Description: do vacuum/analyze where come from original cn in other coordinator.
* @in stmt - the statment for analyze or vacuum command
* @in query_string - the query string for analyze or vacuum command
* @in is_top_level - true if executing a "top level" (interactively issued) command
* @in sent_to_remote - identify if this statement has been sent to the nodes
*/
static void do_vacuum_mpp_table_other_node(VacuumStmt* stmt, const char* query_string, bool is_top_level, bool sent_to_remote)
{
/* clean empty hdfs directories of value partition just on "main" CN */
if (stmt->options & VACOPT_HDFSDIRECTORY)
return;
/* Do deltamerge on other remote CNs. */
if (stmt->options & VACOPT_MERGE) {
begin_delta_merge(stmt);
return;
}
if (stmt->options & VACOPT_VERIFY) {
DoVerifyTableOtherNode(stmt, sent_to_remote);
return;
}
/* Some coordinator node told me to fetch local table/tables statistics information. */
if (IS_PGXC_COORDINATOR && (stmt->options & VACOPT_ANALYZE))
FetchGlobalStatistics(stmt, InvalidOid, NULL);
/* If we only need to estimate rows, vacuum operation should be skipped */
if (NEED_EST_TOTAL_ROWS_DN(stmt))
stmt->options &= ~(VACOPT_VACUUM | VACOPT_FULL | VACOPT_FREEZE);
if (!stmt->isPgFdwForeignTables) {
vacuum(stmt, InvalidOid, true, NULL, is_top_level);
}
}
void ClearVacuumStmt(VacuumStmt* stmt)
{
if (stmt->relation && (stmt->options & VACOPT_ANALYZE)) {
// The memory context of schemaname has been reset,
// so there is no memory leak here
//
stmt->relation->schemaname = NULL;
}
}
void ClearCreateSeqStmtUUID(CreateSeqStmt* stmt)
{
stmt->uuid = INVALIDSEQUUID;
}
void ClearCreateStmtUUIDS(CreateStmt* stmt)
{
stmt->uuids = NIL;
}
/*
* @hdfs
* DoVacuumMappTable
*
* We capsule orignal VacuumStmt processing code in this function.
* Processe MPP local table/tables here.
*/
void DoVacuumMppTable(VacuumStmt* stmt, const char* query_string, bool is_top_level, bool sent_to_remote)
{
elog_node_display(ES_LOGLEVEL, "[DoVacuumMppTable]", stmt->va_cols, true);
char* cmdname = NULL;
if (stmt->options & VACOPT_MERGE) {
cmdname = "DELTA MERGE";
} else if (stmt->options & VACOPT_VACUUM) {
cmdname = "VACUUM";
} else if (stmt->options & VACOPT_VERIFY) {
cmdname = "VERIFY";
} else {
cmdname = "ANALYZE";
}
/* It is no means to vacuum a foreign table */
if ((stmt->isForeignTables || stmt->isPgFdwForeignTables) && (stmt->options & VACOPT_VACUUM)) {
ereport(WARNING, (errmsg("skipping \"%s\" --- cannot vacuum a foreign table", stmt->relation->relname)));
return;
}
/* we choose to allow this during "read only" transactions */
PreventCommandDuringRecovery(cmdname);
#ifdef PGXC
/*
* We have to run the command on nodes before Coordinator
* because vacuum() pops active snapshot and we can not
* send it to nodes.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
#ifdef ENABLE_MULTIPLE_NODES
// Analyze only temp relation can run inside a transaction block
// other commands can't be allowed in DoVacuumMppTable
stmt->isAnalyzeTmpTable = IsAnalyzeTempRel(stmt);
if (!IS_ONLY_ANALYZE_TMPTABLE) {
PreventTransactionChain(is_top_level, cmdname);
/*
* With jdbc terminal, the cid of vacuum statement should be zero;
* Cause CN will set the vacuum statement blockstate to TBLOCK_STARTED(running single-query transaction)
*/
if (GetCurrentCommandId(false) > 0 && ((stmt)->options & VACOPT_VACUUM))
ereport(ERROR,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("%s can not run inside a transaction block", cmdname)));
}
/* Do vacuum/analyze in local CN. */
do_vacuum_mpp_table_local_cn(stmt, query_string, is_top_level, sent_to_remote);
#endif
} else {
/* For single node case, need to judge temp rel as it never did before */
if (IS_SINGLE_NODE) {
stmt->isAnalyzeTmpTable = IsAnalyzeTempRel(stmt);
}
/* For single node case, keep the same behavior as PGXC */
if (IS_SINGLE_NODE && !IS_ONLY_ANALYZE_TMPTABLE) {
PreventTransactionChain(is_top_level, cmdname);
if (GetCurrentCommandId(false) > 0 && ((stmt)->options & VACOPT_VACUUM))
ereport(ERROR,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("%s can not run inside a transaction block", cmdname)));
}
/* Do vacuum/analyze on other node include CN and DN. */
do_vacuum_mpp_table_other_node(stmt, query_string, is_top_level, sent_to_remote);
/*
* There is plan cache in store procedure, some data structure
* must be rest, because memcontext has been reset. We can't use
* those pointer when get stmt from plan cache.
*/
ClearVacuumStmt(stmt);
}
#endif
}
/*
* @hdfs
* IsSupportForeignTableAnalyze
*
* Use foreign_table_id to judge if a foreign table support analysis operation.
*/
bool IsHDFSTableAnalyze(Oid foreign_table_id)
{
HeapTuple tuple = NULL;
Form_pg_class class_form = NULL;
bool ret_value = false;
/*
* Find the tuple in pg_class, using syscache for the lookup.
*/
tuple = SearchSysCacheCopyWithLogLevel(RELOID, ObjectIdGetDatum(foreign_table_id), LOG);
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", foreign_table_id)));
}
class_form = (Form_pg_class)GETSTRUCT(tuple);
/*
* Judge if the relation is a foreign table
*/
if (RELKIND_FOREIGN_TABLE != class_form->relkind) {
ret_value = false;
} else {
/*
* Judge if a foreign supports analysis opertation. If a foreign table
* implements AnalyzeForeignTable, we think it supports foreign table
* analyze operation.
*/
FdwRoutine* table_fdw_routine = GetFdwRoutineByRelId(foreign_table_id);
if (table_fdw_routine->AnalyzeForeignTable == NULL) {
/*
* Foreign table does not support analyze operation.
*/
ereport(WARNING, (errmsg("The operation foreign table doesn't support analyze command.")));
ret_value = false;
} else {
if (isObsOrHdfsTableFormTblOid(foreign_table_id) || IS_OBS_CSV_TXT_FOREIGN_TABLE(foreign_table_id))
ret_value = true;
else if (IS_LOGFDW_FOREIGN_TABLE(foreign_table_id)) {
ret_value = true;
} else
ret_value = false;
}
}
return ret_value;
}
/*
* @pgfdw
* IsPGFDWTableAnalyze
*
* Use foreign_table_id to judge if is a gc_fdw foreign table support analysis operation.
*/
bool IsPGFDWTableAnalyze(Oid foreign_table_id)
{
HeapTuple tuple = NULL;
Form_pg_class class_form = NULL;
/*
* Find the tuple in pg_class, using syscache for the lookup.
*/
tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(foreign_table_id));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", foreign_table_id)));
}
class_form = (Form_pg_class)GETSTRUCT(tuple);
/*
* Judge if the relation is a foreign table
*/
if (class_form->relkind == RELKIND_FOREIGN_TABLE&& IS_POSTGRESFDW_FOREIGN_TABLE(foreign_table_id)) {
return true;
}
return false;
}
bool IsMOTForeignTable(Oid foreignTableId)
{
HeapTuple tuple = NULL;
Form_pg_class classForm = NULL;
/*
* Find the tuple in pg_class, using syscache for the lookup.
*/
tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(foreignTableId));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for relation %u", foreignTableId)));
}
classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
* Judge if the relation is a foreign table
*/
if (RELKIND_FOREIGN_TABLE == classForm->relkind && isMOTFromTblOid(foreignTableId)) {
return true;
}
return false;
}
/*
* @global stats
* @Description: set sample rate in local which compute by total row count.
* @in e_analyze_mode - identify which type the table, normal talbe/dfs table/delta table
* @in stmt - the statment for analyze or vacuum command
* @in newSampleRate - the estimate total row count which get from dn
* @return: void
*/
void global_stats_set_samplerate(AnalyzeMode e_analyze_mode, VacuumStmt* stmt, const double* newSampleRate)
{
/* Set inital sampleRate, it means CN get estimate total row count from DN If sampleRate is -1. */
if (e_analyze_mode == ANALYZENORMAL) {
stmt->pstGlobalStatEx[0].sampleRate = ((NULL == newSampleRate) ? GET_ESTTOTALROWCNTS_FLAG : newSampleRate[0]);
} else {
for (int i = 0; i < ANALYZE_MODE_MAX_NUM - 1; i++) {
stmt->pstGlobalStatEx[i].sampleRate =
((NULL == newSampleRate) ? GET_ESTTOTALROWCNTS_FLAG : newSampleRate[i]);
}
}
}
#ifdef ENABLE_MULTIPLE_NODES
/*
* @global stats
* @Description: initial stadndistinct and coorelations which using for get the value from dn1 and set in local's
* pg_statistic.
* @in stmt - the statment for analyze or vacuum command
* @in attnum - the num of attribute for current table which do analyze
* @return: void
*/
static void set_dndistinct_coors(VacuumStmt* stmt, int attnum)
{
stmt->pstGlobalStatEx[stmt->tableidx].attnum = attnum;
stmt->pstGlobalStatEx[stmt->tableidx].dndistinct = (double*)palloc0(sizeof(double) * attnum);
stmt->pstGlobalStatEx[stmt->tableidx].correlations = (double*)palloc0(sizeof(double) * attnum);
for (int i = 0; i < attnum; i++) {
stmt->pstGlobalStatEx[stmt->tableidx].dndistinct[i] = 1;
stmt->pstGlobalStatEx[stmt->tableidx].correlations[i] = 1;
}
}
#endif
/*
* @global stats
* @Description: for a relation to be analyzed, check the permission. If no permisision, skip it.
* @in rel_id - oid of the relation to be checked
* @return: whether has permission
*/
bool check_analyze_permission(Oid rel_id)
{
Relation rel = try_relation_open(rel_id, ShareUpdateExclusiveLock);
/* if the rel is invalid, just return false and outter level will skip it */
if (!rel) {
elog(DEBUG1, "Skip analyze table for cannot get ShareUpdateExclusiveLock");
return false;
}
/*
* If rel is in read only mode(none redistribution scenario), we skip analyze
* the relation.
*/
if (!u_sess->attr.attr_sql.enable_cluster_resize && RelationInClusterResizingReadOnly(rel)) {
ereport(WARNING,
(errmsg(
"skipping \"%s\" --- only none read-only mode can do analyze command", RelationGetRelationName(rel))));
relation_close(rel, ShareUpdateExclusiveLock);
return false;
}
if (!(pg_class_ownercheck(RelationGetRelid(rel), GetUserId()) ||
(pg_database_ownercheck(u_sess->proc_cxt.MyDatabaseId, GetUserId()) && !rel->rd_rel->relisshared))) {
/*
* we may have checked the first two situations before, just double check to
* make sure no miss in every cases.
*/
if (rel->rd_rel->relisshared)
ereport(WARNING,
(errmsg("skipping \"%s\" --- only system admin can analyze it", RelationGetRelationName(rel))));
else if (rel->rd_rel->relnamespace == PG_CATALOG_NAMESPACE)
ereport(WARNING,
(errmsg("skipping \"%s\" --- only system admin or database owner can analyze it",
RelationGetRelationName(rel))));
else
ereport(WARNING,
(errmsg(
"skipping \"%s\" --- only table or database owner can analyze it", RelationGetRelationName(rel))));
relation_close(rel, ShareUpdateExclusiveLock);
return false;
}
relation_close(rel, ShareUpdateExclusiveLock);
return true;
}
/*
* @NodeGroup Support
* @Description: Find ExecNodes for given rel_id(for index case) we return the base
* table's execnodes
*
* @Return: return the rel_id's execnodes
*/
ExecNodes* RelidGetExecNodes(Oid rel_id, bool isutility)
{
ExecNodes* exec_nodes = makeNode(ExecNodes);
int nmembers = 0;
Oid* members = NULL;
char relkind = get_rel_relkind(rel_id);
Oid group_oid = InvalidOid;
if (rel_id > FirstNormalObjectId) {
if (relkind == RELKIND_RELATION) {
/* For relation, go to pgxc_class to fetch group list directly */
nmembers = get_pgxc_classnodes(rel_id, &members);
/* Binding group_oid for none system table */
group_oid = get_pgxc_class_groupoid(rel_id);
} else if (relkind == RELKIND_INDEX) {
/*
* For index, there is enry in pgxc_class, so we first get index's
* base rel_id and then fetch group list from pgxc_class
*/
Oid base_relid = IndexGetRelation(rel_id, false);
nmembers = get_pgxc_classnodes(base_relid, &members);
/* Binding group_oid for none system table */
group_oid = get_pgxc_class_groupoid(base_relid);
} else if (relkind == RELKIND_FOREIGN_TABLE) {
if (in_logic_cluster() && is_pgxc_class_table(rel_id)) {
/* For relation, go to pgxc_class to fetch group list directly */
nmembers = get_pgxc_classnodes(rel_id, &members);
/* Binding group_oid for none system table */
group_oid = get_pgxc_class_groupoid(rel_id);
}
}
} else {
group_oid = ng_get_installation_group_oid();
nmembers = get_pgxc_groupmembers(group_oid, &members);
AssertEreport(nmembers > 0 && members != NULL, MOD_EXECUTOR, "Invalid group member value");
}
/*
* Notice!!
* In cluster resizing stage we need special processing logics in utility statement.
*
* So, as normal, when target node group's status is marked as 'installation' or
* 'redistribution', we have to issue a full-DN request.
*/
if (isutility && OidIsValid(group_oid)) {
char* group_name = get_pgxc_groupname(group_oid);
if (need_full_dn_execution(group_name)) {
exec_nodes->nodeList = GetAllDataNodes();
} else {
if (IsLogicClusterRedistributed(group_name) &&
(relkind == RELKIND_FOREIGN_TABLE || !RelationIsDeleteDeltaTable(get_rel_name(rel_id)))) {
int dst_nmembers = 0;
Oid* dst_members = NULL;
Oid destgroup_oid = PgxcGroupGetRedistDestGroupOid();
if (OidIsValid(destgroup_oid))
dst_nmembers = get_pgxc_groupmembers(destgroup_oid, &dst_members);
if (dst_nmembers > nmembers) {
nmembers = dst_nmembers;
if (members != NULL)
pfree_ext(members);
members = dst_members;
} else if (dst_members != NULL) {
pfree_ext(dst_members);
}
}
/* Creating executing-node list */
exec_nodes->nodeList = GetNodeGroupNodeList(members, nmembers);
}
} else {
/* Creating executing-node list */
exec_nodes->nodeList = GetNodeGroupNodeList(members, nmembers);
}
Distribution* distribution = ng_convert_to_distribution(exec_nodes->nodeList);
distribution->group_oid = group_oid;
ng_set_distribution(&exec_nodes->distribution, distribution);
if (members != NULL)
pfree_ext(members);
return exec_nodes;
}
/*
* @NodeGroup Support
* @Description: Determine the list objects is in same node group
*
* @Return: 'true' in same group, 'false' in different group
*/
bool ObjectsInSameNodeGroup(List* objects, NodeTag stmttype)
{
ListCell* cell = NULL;
Oid first_ngroup_oid = InvalidOid;
Oid table_oid = InvalidOid;
Oid group_oid = InvalidOid;
if (objects == NIL || list_length(objects) == 1) {
return true;
}
/* Scanning dropping list to error-out un-allowed cases */
foreach (cell, objects) {
RangeVar* rel = NULL;
if (stmttype == T_DropStmt) {
/* In DROP Table statements, we dereference list as name list */
rel = makeRangeVarFromNameList((List*)lfirst(cell));
} else if (stmttype == T_TruncateStmt) {
/* In TRUNCATE table statements, we dereference list as range var */
rel = (RangeVar*)lfirst(cell);
}
table_oid = RangeVarGetRelid(rel, NoLock, true);
/* table_oid is InvalidOid means it is dropping a none-exists table */
if (table_oid == InvalidOid) {
continue;
}
/*
* Only check relation, for index, view is not associated with
* nodegroup they belongs to its base table
*/
if (get_rel_relkind(table_oid) != RELKIND_RELATION) {
continue;
}
/* Fetching group_oid for given relation */
group_oid = get_pgxc_class_groupoid(table_oid);
AssertEreport(group_oid != InvalidOid, MOD_EXECUTOR, "group OID is invalid");
if (first_ngroup_oid == InvalidOid) {
first_ngroup_oid = group_oid;
/*
* In 1st round of dropping list iteration we don't have
* to process following verification.
*/
continue;
}
if (first_ngroup_oid != group_oid) {
return false;
}
}
return true;
}
/*
* @NodeGroup Support
* @Description: Return correct ExecNodes with given parser node
*
* @Return: execnodes of target table in ReIndex, Grant, Lock statements
*/
static ExecNodes* assign_utility_stmt_exec_nodes(Node* parse_tree)
{
ExecNodes* nodes = NULL;
Oid rel_id = InvalidOid;
AssertEreport(parse_tree, MOD_EXECUTOR, "parser tree is NULL");
AssertEreport(IS_PGXC_COORDINATOR && !IsConnFromCoord(), MOD_EXECUTOR, "the node is not a CN node");
/* Do special processing for nodegroup support */
switch (nodeTag(parse_tree)) {
case T_ReindexStmt: {
ReindexStmt* stmt = (ReindexStmt*)parse_tree;
if (stmt->relation) {
rel_id = RangeVarGetRelid(stmt->relation, NoLock, false);
nodes = RelidGetExecNodes(rel_id);
}
break;
}
case T_GrantStmt: {
GrantStmt* stmt = (GrantStmt*)parse_tree;
if (stmt->objects && stmt->objtype == ACL_OBJECT_RELATION && stmt->targtype == ACL_TARGET_OBJECT) {
RangeVar* relvar = (RangeVar*)list_nth(stmt->objects, 0);
Oid relation_id = RangeVarGetRelid(relvar, NoLock, false);
nodes = RelidGetExecNodes(relation_id);
}
break;
}
case T_ClusterStmt: {
ClusterStmt* stmt = (ClusterStmt*)parse_tree;
if (stmt->relation) {
Oid relation_id = RangeVarGetRelid(stmt->relation, NoLock, false);
nodes = RelidGetExecNodes(relation_id);
}
break;
}
case T_LockStmt: {
LockStmt* stmt = (LockStmt*)parse_tree;
if (stmt->relations) {
RangeVar* relvar = (RangeVar*)list_nth(stmt->relations, 0);
Oid relation_id = RangeVarGetRelid(relvar, NoLock, false);
nodes = RelidGetExecNodes(relation_id);
}
break;
}
case T_VacuumStmt: {
VacuumStmt* stmt = (VacuumStmt*)parse_tree;
if (stmt->relation) {
Oid relation_id = RangeVarGetRelid(stmt->relation, NoLock, false);
nodes = RelidGetExecNodes(relation_id);
} else if (stmt->options & VACOPT_VERIFY) {
nodes = RelidGetExecNodes(stmt->curVerifyRel);
}
break;
}
case T_RenameStmt: {
RenameStmt* stmt = (RenameStmt*)parse_tree;
if (stmt->relation) {
Oid relation_id = RangeVarGetRelid(stmt->relation, NoLock, stmt->missing_ok);
nodes = RelidGetExecNodes(relation_id);
} else if (stmt->renameType == OBJECT_FUNCTION) {
Oid funcid = LookupFuncNameTypeNames(stmt->object, stmt->objarg, false);
nodes = GetFunctionNodes(funcid);
}
break;
}
case T_AlterObjectSchemaStmt: {
AlterObjectSchemaStmt* stmt = (AlterObjectSchemaStmt*)parse_tree;
if (stmt->relation) {
rel_id = RangeVarGetRelid(stmt->relation, NoLock, true);
nodes = RelidGetExecNodes(rel_id);
} else if (stmt->objectType == OBJECT_FUNCTION) {
Oid funcid = LookupFuncNameTypeNames(stmt->object, stmt->objarg, false);
nodes = GetFunctionNodes(funcid);
}
break;
}
case T_CreateTrigStmt: {
CreateTrigStmt* stmt = (CreateTrigStmt*)parse_tree;
if (stmt->relation) {
/*
* Notice : When support create or replace trigger in future,
* we may need adjust the missing_ok parameter here.
*/
rel_id = RangeVarGetRelidExtended(stmt->relation, NoLock, false, false, false, true, NULL, NULL);
nodes = RelidGetExecNodes(rel_id);
}
break;
}
default: {
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized nodes %s in node group utility execution", nodeTagToString(parse_tree->type))));
}
}
return nodes;
}
#ifdef ENABLE_MULTIPLE_NODES
/*
* Description: Create temp table on coordinator for analyze with relative-estimation or
* absolute-estimate with debuging.
*
* Parameters:
* @in rel_id: relation oid
* @in stmt: the statment for analyze or vacuum command
* @in iscommit: commit and start a new transaction if non-temp table or delta table.
*
* Returns: void
*/
static void analyze_tmptbl_debug_cn(Oid rel_id, Oid main_relid, VacuumStmt* stmt, bool iscommit)
{
/*
* Don't create temp sample table for u_sess->cmd_cxt.default_statistics_target is absolute-estimate
* or analyze for hdfs foreign table or temp table.
*/
if (stmt == NULL || stmt->isAnalyzeTmpTable || stmt->disttype != DISTTYPE_HASH ||
(default_statistics_target >= 0 && log_min_messages > DEBUG1)) {
return;
}
MemoryContext oldcontext = CurrentMemoryContext;
char* table_name = buildTempSampleTable(rel_id, main_relid, TempSmpleTblType_Table);
if (table_name != NULL) {
stmt->tmpSampleTblNameList = lappend(stmt->tmpSampleTblNameList, makeString(table_name));
}
if (iscommit) {
/* Create temp table using a single transaction seperate with VACUUM ANALYZE. */
PopActiveSnapshot();
CommitTransactionCommand();
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
} else {
CommandCounterIncrement();
}
(void)MemoryContextSwitchTo(oldcontext);
}
#endif
/*
* Return if we need a full_datanodes execution with given group,
* for example group_name is installation group or redistribution group
* in cluster resizing stage.
*/
static bool need_full_dn_execution(const char* group_name)
{
#ifdef ENABLE_MULTIPLE_NODES
BumpPGXCGlobalVersion(); /* force re-fetch of redistribution and installation groups */
#endif
if (in_logic_cluster()) {
return false;
}
const char* redistribution_group_name = PgxcGroupGetInRedistributionGroup();
const char* installation_group_name = PgxcGroupGetInstallationGroup();
if ((installation_group_name && strcmp(group_name, installation_group_name) == 0) ||
(redistribution_group_name && strcmp(group_name, redistribution_group_name) == 0)) {
return true;
}
return false;
}
/*
* EstIdxMemInfo
* According to resource usage and cardinality estimation, assign query
* max/min mem, assigned statement mem in utilityDesc struct
*
* We use this function to evaluate mem in three cases:
* 1. index case: create index/ reindex
* 2. cluster case: now only btree is needed for sort
* 3. vacuum full case: now only cstore table with pck is needed for sort
*
* Parameters:
* @in rel: input relation
* @in relation: parser struct of relation, containing nspname and relname
* @out desc: set meminfo in this struct
* @in info: index info, or NULL when we do cluster operation
* @in access_method: access method of index, or NULL when we do cluster operation
*
* Returns: void
*/
void EstIdxMemInfo(Relation rel, RangeVar* relation, UtilityDesc* desc, IndexInfo* info, const char* access_method)
{
VacuumStmt* stmt = makeNode(VacuumStmt);
char* query_string_with_info = NULL;
StringInfoData s;
bool vectorized = false;
AnalyzeMode mode = ANALYZENORMAL;
/* skip system table */
if (rel->rd_id < FirstNormalObjectId)
return;
/* Only the following 4 kinds of indices may have sort op to consume memory */
if (info != NULL && access_method && strncmp(access_method, "btree", strlen("btree")) &&
strncmp(access_method, "cbtree", strlen("cbtree")) && strncmp(access_method, "hash", strlen("hash")) &&
strncmp(access_method, "psort", strlen("psort")))
return;
/* for hdfs table, we should set to get estimated rows from complex table */
if (RelationIsPAXFormat(rel))
mode = ANALYZEMAIN;
/* Set inital sampleRate, it means CN get estimate total row count from DN If sampleRate is -1. */
global_stats_set_samplerate(mode, stmt, NULL);
if (mode == ANALYZENORMAL) {
stmt->pstGlobalStatEx[ANALYZENORMAL].isReplication = false;
stmt->pstGlobalStatEx[ANALYZENORMAL].exec_query = true;
} else {
stmt->pstGlobalStatEx[ANALYZEMAIN - 1].isReplication = false;
stmt->pstGlobalStatEx[ANALYZEDELTA - 1].isReplication = false;
stmt->pstGlobalStatEx[ANALYZECOMPLEX - 1].isReplication = false;
stmt->pstGlobalStatEx[ANALYZEMAIN - 1].exec_query = true;
stmt->pstGlobalStatEx[ANALYZEDELTA - 1].exec_query = true;
stmt->pstGlobalStatEx[ANALYZECOMPLEX - 1].exec_query = true;
}
stmt->sampleTableRequired = false;
const char* schemaname = NULL;
const char* relname = NULL;
if (relation != NULL) {
if (relation->schemaname != NULL)
schemaname = quote_identifier((const char*)relation->schemaname);
else
schemaname = quote_identifier((const char*)get_namespace_name(get_rel_namespace(rel->rd_id), true));
relname = quote_identifier((const char*)relation->relname);
} else {
schemaname = quote_identifier((const char*)get_namespace_name(get_rel_namespace(rel->rd_id), true));
relname = quote_identifier((const char*)get_rel_name(rel->rd_id));
}
initStringInfo(&s);
appendStringInfo(&s, "analyze %s.%s;", schemaname, relname);
/* Attatch global info to the query_string for get estimate total rows. */
attatch_global_info(&query_string_with_info, stmt, s.data, true, mode, rel->rd_id, NULL);
(void)ExecRemoteVacuumStmt(stmt, query_string_with_info, false, ARQ_TYPE_TOTALROWCNTS, mode, rel->rd_id);
/* For hdfs table, we only create index on main table */
double maxRowCnt = (mode == ANALYZENORMAL) ? stmt->pstGlobalStatEx[ANALYZENORMAL].topRowCnts :
stmt->pstGlobalStatEx[ANALYZEMAIN - 1].topRowCnts;
double sortRowCnt = maxRowCnt;
int32 width = 0;
/* For partition table, we assume each partition is the same size */
if (RELATION_IS_PARTITIONED(rel)) {
if (rel->partMap && rel->partMap->type == PART_TYPE_RANGE) {
int sumtotal = getPartitionNumber(rel->partMap);
if (sumtotal > 0) {
sortRowCnt /= sumtotal;
}
}
}
/* only psort index use partial vectorized sort */
if ((access_method != NULL && strncmp(access_method, "psort", strlen("psort")) == 0) ||
rel->rd_rel->relhasclusterkey) {
sortRowCnt = Min(sortRowCnt, RelationGetPartialClusterRows(rel));
vectorized = true;
}
if (info != NULL) {
if (access_method != NULL && strncmp(access_method, "hash", strlen("hash")) == 0)
width = sizeof(Datum);
else
width = getIdxDataWidth(rel, info, vectorized);
} else
width = get_relation_data_width(rel->rd_id, InvalidOid, NULL, rel->rd_rel->relhasclusterkey);
SetSortMemInfo(
desc, (int)sortRowCnt, width, vectorized, (info != NULL), u_sess->attr.attr_memory.maintenance_work_mem);
}
/*
* SetSortMemInfo
* According to resource usage and cardinality estimation, assign query
* max/min mem, assigned statement mem in utilityDesc struct
*
* Parameters:
* @out desc: set meminfo in this struct
* @in row_count: estimated sort row count
* @in width: estimated tuple width
* @in vectorized: whether vertorized sort will be used for sort
* @in index_sort: if sort tuple is from index
* @in default_size: default size used when resource management is unavailable
* @in copy_rel: input relation for copy, or NULL for non-copy case
*
* Returns: void
*/
void SetSortMemInfo(
UtilityDesc* desc, int row_count, int width, bool vectorized, bool index_sort, Size default_size, Relation copy_rel)
{
Path sort_path; /* dummy for result of cost_sort */
OpMemInfo mem_info;
bool use_tenant = false;
int max_mem = 0;
int available_mem = 0;
dywlm_client_get_memory_info(&max_mem, &available_mem, &use_tenant);
/*
* When database is startup, the require of resource mem will be deferred,
* so give a default size in case no resource info available
*/
if (max_mem == 0)
max_mem = available_mem = default_size;
if (copy_rel != NULL) {
bool is_dfs_table = RelationIsPAXFormat(copy_rel);
Oid rel_oid = copy_rel->rd_id;
cost_insert(&sort_path, vectorized, 0.0, row_count, width, 0.0, available_mem, 1, rel_oid, is_dfs_table, &mem_info);
} else {
cost_sort(&sort_path, NIL, 0.0, row_count, width, 0.0, available_mem, -1.0, vectorized, 1, &mem_info, index_sort);
}
desc->cost = sort_path.total_cost + u_sess->attr.attr_sql.seq_page_cost * cost_page_size(row_count, width);
desc->query_mem[0] = (int)ceil(Min(mem_info.maxMem, MAX_OP_MEM * 2));
if (desc->query_mem[0] > STATEMENT_MIN_MEM * 1024L) {
desc->query_mem[1] = (int)Max(STATEMENT_MIN_MEM * 1024L, desc->query_mem[0] * DECREASED_MIN_CMP_GAP);
} else
desc->query_mem[1] = desc->query_mem[0];
/* We use assigned variable if query_mem is set */
if (VALID_QUERY_MEM()) {
desc->query_mem[0] = Max(desc->query_mem[0], max_mem);
desc->query_mem[1] = Max(desc->query_mem[1], max_mem);
}
desc->min_mem = (int)(mem_info.minMem);
desc->assigned_mem = max_mem;
/* cut query_mem if exceeds max mem */
if (desc->query_mem[0] > desc->assigned_mem)
desc->query_mem[0] = desc->assigned_mem;
if (desc->query_mem[1] > desc->assigned_mem)
desc->query_mem[1] = desc->assigned_mem;
}
/*
* AdjustIdxMemInfo
* Assign the final decided mem info into IndexStmt, so it can then pass
* down to datanode for memory control
*
* Parameters:
* @out mem_info: adaptive mem struct to assign mem info
* @in desc: Utility desc struct that has mem info to assign
*
* Returns: void
*/
void AdjustIdxMemInfo(AdaptMem* mem_info, UtilityDesc* desc)
{
mem_info->work_mem = Min(desc->query_mem[0], desc->assigned_mem);
mem_info->max_mem = desc->assigned_mem;
/* Adjust operator mem if it's close to max or min mem */
if (mem_info->work_mem < MIN_OP_MEM)
mem_info->work_mem = MIN_OP_MEM;
else if (mem_info->work_mem < mem_info->max_mem)
mem_info->work_mem = (int)Min(mem_info->work_mem * BIG_MEM_RATIO, mem_info->max_mem);
else if (mem_info->work_mem == desc->min_mem)
mem_info->work_mem = (int)(mem_info->work_mem * SMALL_MEM_RATIO);
/* reset mem info for CN execution */
desc->query_mem[0] = 0;
desc->query_mem[1] = 0;
}
/*
* constructMesageWithMemInfo
* if index mem info is adaptive, we should pass it down to datanode.
* This function is used to combine mem info to the message.
*
* Parameters:
* @in query_string: original query string
* @in mem_info: adapt mem struct that contains mem info
*
* Returns: combined message
*/
char* ConstructMesageWithMemInfo(const char* query_string, AdaptMem mem_info)
{
char* final_string = (char*)query_string;
/* if index build mem is adaptive, we should pass it to datanode */
if (mem_info.work_mem > 0) {
List* l = NIL;
l = lappend_int(l, mem_info.work_mem);
l = lappend_int(l, mem_info.max_mem);
char* op_string = nodeToString(l);
AssembleHybridMessage(&final_string, query_string, op_string);
pfree_ext(op_string);
list_free_ext(l);
query_string = final_string;
}
return final_string;
}