Files
openGauss-server/src/gausskernel/runtime/executor/execClusterResize.cpp
2021-03-06 12:39:28 +08:00

1174 lines
44 KiB
C++

/* -------------------------------------------------------------------------
*
* execClusterResize.cpp
* MPPDB ClusterResizing relevant routines
*
* 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
*
* IDENTIFICATION
* src/gausskernel/runtime/executor/execClusterResize.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "access/tableam.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_namespace.h"
#include "catalog/pgxc_group.h"
#include "executor/executor.h"
#include "executor/nodeModifyTable.h"
#include "optimizer/clauses.h"
#include "parser/analyze.h"
#include "parser/parsetree.h"
#include "pgxc/pgxc.h"
#include "pgxc/redistrib.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
#include "utils/elog.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/rel_gs.h"
#include "utils/syscache.h"
#include "utils/snapmgr.h"
#include "storage/lmgr.h"
/*
* ---------------------------------------------------------------------------------
* *Local functions/variables declaration fields*
* ---------------------------------------------------------------------------------
*/
/* delete delta table definition */
#define Natts_pg_delete_delta 3
#define Anum_pg_delete_delta_xcnodeid_and_dntableoid 1
#define Anum_pg_delete_delta_tablebucketid_and_ctid 2
#define RANGE_SCAN_IN_REDIS "tidge+tid+pg_get_redis_rel_start_ctid+tidle+tid+pg_get_redis_rel_end_ctid+"
static Node* eval_dnstable_func_mutator(
Relation rel, Node* node, StringInfo qual_str, RangeScanInRedis *rangeScanInRedis, bool isRoot);
static inline bool redis_tupleid_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs);
static inline bool redis_blocknum_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs);
static inline bool redis_offset_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs);
#define REDIS_TUPLEID_RETRIVE_FUNCSIG(rettype, argstype, nargs) \
(((nargs) == 1 && (rettype) == TIDOID && (argstype)[0] == TEXTOID) || \
((nargs) == 4 && (rettype) == TIDOID && (argstype)[0] == TEXTOID && (argstype)[1] == NAMEOID && \
(argstype)[2] == INT4OID && (argstype)[3] == INT4OID))
static inline bool redis_tupleid_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs)
{
if (pg_strcasecmp(funcname, "pg_get_redis_rel_start_ctid") == 0 &&
REDIS_TUPLEID_RETRIVE_FUNCSIG(rettype, argstype, nargs)) {
return true;
}
if (pg_strcasecmp(funcname, "pg_get_redis_rel_end_ctid") == 0 &&
REDIS_TUPLEID_RETRIVE_FUNCSIG(rettype, argstype, nargs)) {
return true;
}
return false;
}
static inline bool redis_offset_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs)
{
if (pg_strcasecmp(funcname, "pg_tupleid_get_offset") == 0 &&
(nargs == 1 && rettype == INT4OID && argstype[0] == TIDOID)) {
return true;
}
return false;
}
static inline bool redis_blocknum_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs)
{
if (pg_strcasecmp(funcname, "pg_tupleid_get_blocknum") == 0 &&
(nargs == 1 && rettype == INT8OID && argstype[0] == TIDOID)) {
return true;
}
return false;
}
static inline bool redis_ctid_retrive_function(const char* funcname, Oid rettype, const Oid* argstype, int nargs)
{
if (pg_strcasecmp(funcname, "pg_tupleid_get_ctid_to_bigint") == 0 &&
(nargs == 1 && rettype == INT8OID && argstype[0] == TIDOID)) {
return true;
}
return false;
}
/*
* - Brief: Record the given tuple's tupleid into pg_delete_delta table
* - Parameter:
* @rel: target relation of UPDATE/DELETE operation
* @tupleid: tupleid that needs record
* - Return:
* no return value
*/
void RecordDeletedTuple(Oid relid, int2 bucketid, const ItemPointer tupleid, const Relation deldelta_rel)
{
Assert(deldelta_rel);
Datum values[Natts_pg_delete_delta];
bool nulls[Natts_pg_delete_delta];
HeapTuple tup = NULL;
/* Iterate through attributes initializing nulls and values */
for (int i = 0; i < Natts_pg_delete_delta; i++) {
nulls[i] = false;
values[i] = (Datum)0;
}
values[Anum_pg_delete_delta_xcnodeid_and_dntableoid - 1] =
UInt64GetDatum(((uint64)u_sess->pgxc_cxt.PGXCNodeIdentifier << 32) | relid);
values[Anum_pg_delete_delta_tablebucketid_and_ctid - 1] =
UInt64GetDatum(((uint64)ItemPointerGetBlockNumber(tupleid) << 16) | ItemPointerGetOffsetNumber(tupleid));
if (bucketid != InvalidBktId) {
values[Anum_pg_delete_delta_tablebucketid_and_ctid - 1] |= ((uint64)bucketid << 48);
}
/* Record delta */
tup = heap_form_tuple(RelationGetDescr(deldelta_rel), values, nulls);
(void)simple_heap_insert(deldelta_rel, tup);
tableam_tops_free_tuple(tup);
}
/*
* - Brief: get and open delete_delta rel
* - Parameter:
* @rel: target relation of UPDATE/DELETE/TRUNCATE operation
* @lockmode: lock mode
* @isMultiCatchup: multi catchup delta or not
* - Return:
* delete_delta rel
*/
/*
* - Brief: Determine if the relation is under cluster resizing operation
* - Parameter:
* @rel: relation that needs to check
* - Return:
* @TRUE: relation is under cluster resizing
* @FALSE: relation is not under cluster resizing
*/
bool RelationInClusterResizing(const Relation rel)
{
Assert(rel != NULL);
/* Check relation's append_mode status */
if (!IsInitdb && RelationInRedistribute(rel))
return true;
return false;
}
/*
* - Brief: Determine if the relation is under cluster resizing read only operation
* - Parameter:
* @rel: relation that needs to check
* - Return:
* @TRUE: relation is under cluster resizing read only
* @FALSE: relation is not under cluster resizing read only
*/
bool RelationInClusterResizingReadOnly(const Relation rel)
{
Assert(rel != NULL);
/* Check relation's append_mode status */
if (!IsInitdb && (RelationInRedistributeReadOnly(rel) || RelationInRedistributeEndCatchup(rel)))
return true;
return false;
}
/*
* @Description: check whether relation is in redistribution though range variable.
* @in range_var: range variable which stored relation info.
* @return: true for in redistribution.
*/
bool CheckRangeVarInRedistribution(const RangeVar* range_var)
{
Relation relation;
Oid relid;
bool in_redis = false;
relid = RangeVarGetRelid(range_var, AccessShareLock, true);
if (OidIsValid(relid)) {
relation = relation_open(relid, NoLock);
/* If the relation is index, we should check the related table is resizing or not. */
if (RelationIsIndex(relation)) {
Oid heapOid = IndexGetRelation(relid, false);
Relation heapRelation = relation_open(heapOid, AccessShareLock);
in_redis = RelationInClusterResizing(heapRelation);
relation_close(heapRelation, AccessShareLock);
} else {
in_redis = RelationInClusterResizing(relation);
}
relation_close(relation, NoLock);
UnlockRelationOid(relid, AccessShareLock);
}
return in_redis;
}
/*
* - Brief: Determine if the table name is delete_delta table.
* - Parameter:
* @relname: name of target table
* - Return:
* @TRUE: the table is delete_delta table
* @FALSE: the table is not delete_delta table
*/
bool RelationIsDeleteDeltaTable(char* delete_delta_name)
{
Oid relid;
uint64 val;
char* endptr = NULL;
HeapTuple tuple;
if (IsInitdb) {
return false;
}
if (strncmp(delete_delta_name, "pg_delete_delta_", 16) != 0) {
return false;
}
val = strtoull(delete_delta_name + 16, &endptr, 0);
if ((errno == ERANGE) || (errno != 0 && val == 0)) {
return false;
}
if (endptr == delete_delta_name + 16 || *endptr != '\0') {
return false;
}
relid = (Oid)val;
if (!OidIsValid(relid)) {
return false;
}
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple)) {
elog(WARNING, "Table %u related to %s does not exists.", relid, delete_delta_name);
return false;
}
ReleaseSysCache(tuple);
return true;
}
/*
* - Brief: Determine if the Progress is under cluster resizing status
* - Return:
* @TRUE: Progress is under cluster resizing
* @FALSE: Progress is not under cluster resizing
*/
bool ClusterResizingInProgress()
{
Relation pgxc_group_rel = NULL;
TableScanDesc scan;
HeapTuple tup = NULL;
Datum datum;
bool isNull = false;
bool result = false;
pgxc_group_rel = heap_open(PgxcGroupRelationId, AccessShareLock);
if (!pgxc_group_rel) {
ereport(PANIC, (errcode(ERRCODE_RELATION_OPEN_ERROR), errmsg("can not open pgxc_group")));
}
scan = tableam_scan_begin(pgxc_group_rel, SnapshotNow, 0, NULL);
while ((tup = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
datum = heap_getattr(tup, Anum_pgxc_group_in_redistribution, RelationGetDescr(pgxc_group_rel), &isNull);
if ('y' == DatumGetChar(datum)) {
result = true;
break;
}
}
tableam_scan_end(scan);
heap_close(pgxc_group_rel, AccessShareLock);
return result;
}
/*
* - Brief: get the name of delete_delta table
* - Parameter:
* @relname: name of target table
* @delta_delta_name: output value for delete_delta table name
* @isMultiCatchup: multi catchup delta or not
* - Return:
* no return value
*/
static inline void RelationGetDeleteDeltaTableName(Relation rel, char* delete_delta_name, bool isMultiCatchup)
{
int rc = 0;
/* Check if output parameter it not palloc()-ed from caller side */
if (delete_delta_name == NULL || rel == NULL) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid parameter in function '%s'", __FUNCTION__)));
}
/*
* Look up Relation's reloptions to get table's cnoid to
* form the name of delete_delta table
*/
if (!IsInitdb) {
if (RelationInClusterResizing(rel) && !RelationInClusterResizingReadOnly(rel)) {
if (isMultiCatchup) {
rc = snprintf_s(delete_delta_name,
NAMEDATALEN,
NAMEDATALEN - 1,
REDIS_MULTI_CATCHUP_DELETE_DELTA_TABLE_PREFIX "%u",
RelationGetRelCnOid(rel));
} else {
rc = snprintf_s(delete_delta_name,
NAMEDATALEN,
NAMEDATALEN - 1,
REDIS_DELETE_DELTA_TABLE_PREFIX "%u",
RelationGetRelCnOid(rel));
}
securec_check_ss(rc, "\0", "\0");
} else {
elog(LOG, "rel %s doesn't exist in redistributing", RelationGetRelationName(rel));
rc = snprintf_s(delete_delta_name,
NAMEDATALEN,
NAMEDATALEN - 1,
REDIS_DELETE_DELTA_TABLE_PREFIX "%s",
RelationGetRelationName(rel));
securec_check_ss(rc, "\0", "\0");
}
}
return;
}
Relation GetAndOpenDeleteDeltaRel(const Relation rel, LOCKMODE lockmode, bool isMultiCatchup)
{
Relation deldelta_rel;
Oid deldelta_relid;
char delete_delta_tablename[NAMEDATALEN];
Oid data_redis_namespace;
errno_t errorno;
errorno = memset_s(delete_delta_tablename, NAMEDATALEN, 0, NAMEDATALEN);
securec_check_c(errorno, "\0", "\0");
RelationGetDeleteDeltaTableName(rel, (char*)delete_delta_tablename, isMultiCatchup);
data_redis_namespace = get_namespace_oid("data_redis", false);
/* We are going to fetch the delete delta relation under data_redis schema. */
deldelta_relid = get_relname_relid(delete_delta_tablename, data_redis_namespace);
if (!OidIsValid(deldelta_relid)) {
/*
* If multi catchup delta table is not there, just return NULL. We should not
* report error, because it is a valid case. Multi catchup delta table is
* dropped in each catchup iteration.
*/
if (isMultiCatchup) {
return NULL;
}
/*
* To support Update or Delete during extension, we need to add 2 more columns.
* more columns. Limited by MaxHeapAttributeNumber, if the table already contains too many columns,
* we don't allow update or delete anymore, but insert statement can still proceed.
*/
if (((rel->rd_att->natts > (MaxHeapAttributeNumber - (Natts_pg_delete_delta - 1))) &&
!RELATION_IS_PARTITIONED(rel)) ||
((rel->rd_att->natts > (MaxHeapAttributeNumber - Natts_pg_delete_delta)) && RELATION_IS_PARTITIONED(rel))) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("do not support update or delete on table %s, when do cluster resizing on it.",
RelationGetRelationName(rel)),
errdetail("Can not support online extension, if the table contains too many columns")));
}
/* ERROR case, should never come here */
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("delete delta table %s is not found when do cluster resizing table \"%s\"",
delete_delta_tablename,
RelationGetRelationName(rel))));
}
deldelta_rel = relation_open(deldelta_relid, lockmode);
elog(DEBUG1,
"Delete_delta table %s for relation %s being under cluster resizing is valid.",
delete_delta_tablename,
RelationGetRelationName(rel));
return deldelta_rel;
}
/*
* - Brief: Check the stmtment during online expansion, block unsupported ddl in cluster resizing.
* - Parameter:
* @rel: parsetree of DDL
* - Return:
* no return value
*/
void BlockUnsupportedDDL(const Node* parsetree)
{
if (IsInitdb) {
return;
}
Relation rel = NULL;
Oid relid = InvalidOid;
List* relidlist = NULL;
ListCell* lc = NULL;
LOCKMODE lockmode_getrelid = AccessShareLock;
LOCKMODE lockmode_openrel = AccessShareLock;
/*
* 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 (nodeTag(parsetree)) {
case T_CreatedbStmt:
case T_AlterDatabaseStmt:
case T_AlterDatabaseSetStmt:
case T_CreateTableSpaceStmt:
case T_DropTableSpaceStmt:
case T_AlterTableSpaceOptionsStmt: {
if (ClusterResizingInProgress()) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command during online expansion", CreateCommandTag((Node*)parsetree))));
}
return;
} break;
case T_DropdbStmt: {
if (ClusterResizingInProgress() && !u_sess->attr.attr_common.xc_maintenance_mode) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command during online expansion", CreateCommandTag((Node*)parsetree))));
} else if (ClusterResizingInProgress()) {
ereport(WARNING, (errmsg("Drop database during online expansion in maintenance mode!")));
}
return;
} break;
/* Block CURSOR for while table in cluster resizing */
case T_PlannedStmt: {
PlannedStmt* stmt = (PlannedStmt*)parsetree;
relidlist = stmt->relationOids;
} break;
/* Block RENAME while table in cluster resizing */
case T_RenameStmt: {
RenameStmt* stmt = (RenameStmt*)parsetree;
switch (stmt->renameType) {
case OBJECT_SCHEMA: {
Oid nsOid = get_namespace_oid(stmt->subname, true);
TRANSFER_DISABLE_DDL(nsOid);
break;
}
case OBJECT_TABLE: {
if (stmt->relation != NULL) {
Oid relOid = RangeVarGetRelid(stmt->relation, AccessShareLock, true);
if (OidIsValid(relOid)) {
Oid nsOid = GetNamespaceIdbyRelId(relOid);
UnlockRelationOid(relOid, AccessShareLock);
TRANSFER_DISABLE_DDL(nsOid);
}
}
break;
}
default:
break;
}
if (stmt->relation && CheckRangeVarInRedistribution(stmt->relation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command during online expansion on '%s'",
CreateCommandTag((Node*)parsetree),
stmt->relation->relname)));
} break;
/* Block ALTER set schema while table in cluster resizing */
case T_AlterObjectSchemaStmt: {
AlterObjectSchemaStmt* stmt = (AlterObjectSchemaStmt*)parsetree;
/* disable alter table set schema when transfer */
if (stmt->relation != NULL) {
Oid relOid = RangeVarGetRelid(stmt->relation, AccessShareLock, true);
if (OidIsValid(relOid)) {
Oid nsOid = GetNamespaceIdbyRelId(relOid);
UnlockRelationOid(relOid, AccessShareLock);
TRANSFER_DISABLE_DDL(nsOid);
if (stmt->newschema) {
nsOid = get_namespace_oid(stmt->newschema, true);
TRANSFER_DISABLE_DDL(nsOid);
}
}
}
if (stmt->relation && CheckRangeVarInRedistribution(stmt->relation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command during online expansion on '%s'",
CreateCommandTag((Node*)parsetree),
stmt->relation->relname)));
} break;
/* Block CREATE index while table in cluster resizing(for row table only) */
case T_IndexStmt: {
IndexStmt* stmt = (IndexStmt*)parsetree;
if (stmt->relation) {
relid = RangeVarGetRelid(stmt->relation, AccessShareLock, true);
if (OidIsValid(relid)) {
Relation relation = relation_open(relid, NoLock);
bool inRedis = RelationIsRowFormat(relation) && RelationInClusterResizing(relation);
relation_close(relation, NoLock);
UnlockRelationOid(relid, AccessShareLock);
if (inRedis) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command during online expansion on '%s'",
CreateCommandTag((Node*)parsetree),
stmt->relation->relname)));
}
}
}
} break;
/* Block REINDEX while table in cluster resizing(for row table only) */
case T_ReindexStmt: {
ReindexStmt* stmt = (ReindexStmt*)parsetree;
if (stmt->relation) {
relid = RangeVarGetRelid(stmt->relation, AccessShareLock, true);
if (OidIsValid(relid)) {
Relation relation = relation_open(relid, NoLock);
bool inRedis = false;
if (RelationIsRelation(relation)) {
inRedis = RelationIsRowFormat(relation) && RelationInClusterResizing(relation);
} else if (RelationIsIndex(relation)) {
Oid heapOid = IndexGetRelation(relid, false);
Relation heapRelation = relation_open(heapOid, AccessShareLock);
inRedis = RelationIsRowFormat(heapRelation) && RelationInClusterResizing(heapRelation);
relation_close(heapRelation, AccessShareLock);
}
relation_close(relation, NoLock);
UnlockRelationOid(relid, AccessShareLock);
if (inRedis) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command during online expansion on '%s'",
CreateCommandTag((Node*)parsetree),
stmt->relation->relname)));
}
}
}
} break;
/* Block ALTER-Table while table in cluster resizing */
case T_AlterTableStmt: {
AlterTableStmt* stmt = (AlterTableStmt*)parsetree;
AlterTableCmd* cmd = NULL;
foreach (lc, stmt->cmds) {
cmd = (AlterTableCmd*)lfirst(lc);
switch (cmd->subtype) {
case AT_TruncatePartition: {
/*
* We do not allow truncate partition when the target is in read only
* mode during online expansion time.
*/
if (stmt->relation) {
relid = RangeVarGetRelid(stmt->relation, lockmode_getrelid, true);
if (OidIsValid(relid)) {
/* disable alter table truncate partition during transfer */
if (CheckRangeVarInRedistribution(stmt->relation)) {
Oid nsOid = GetNamespaceIdbyRelId(relid);
TRANSFER_DISABLE_DDL(nsOid);
}
rel = relation_open(relid, NoLock);
if (RelationInClusterResizingReadOnly(rel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command with '%s' option during online expansion on "
"'%s' because the object is in read only mode.",
CreateCommandTag((Node*)parsetree),
CreateAlterTableCommandTag(cmd->subtype),
RelationGetRelationName(rel))));
}
relation_close(rel, NoLock);
if (u_sess->attr.attr_sql.enable_parallel_ddl)
UnlockRelationOid(relid, lockmode_getrelid);
}
}
} break;
case AT_AddNodeList:
case AT_DeleteNodeList:
break;
case AT_ResetRelOptions:
case AT_SetRelOptions:
if (cmd->def) {
List* options = (List*)cmd->def;
ListCell* opt = NULL;
DefElem* def = NULL;
foreach (opt, options) {
def = (DefElem*)lfirst(opt);
if (pg_strcasecmp(def->defname, "append_mode") == 0) {
break;
}
}
/* If rel option contain append_mode, then not check. */
if (opt != NULL) {
break;
}
}
/* fall through */
default: {
if (stmt->relation && !u_sess->attr.attr_sql.enable_cluster_resize &&
CheckRangeVarInRedistribution(stmt->relation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command with '%s' option during online expansion on '%s'",
CreateCommandTag((Node*)parsetree),
CreateAlterTableCommandTag(cmd->subtype),
stmt->relation->relname)));
} break;
}
}
return;
} break;
/* Block CREATE-RULE statements while target table in cluster resizing */
case T_RuleStmt: {
RuleStmt* stmt = (RuleStmt*)parsetree;
if (stmt->relation) {
relid = RangeVarGetRelid(stmt->relation, lockmode_getrelid, true);
relidlist = list_make1_oid(relid);
}
} break;
/* Block CREATE SEQUENCE set schema while owner table in cluster resizing */
case T_CreateSeqStmt: {
CreateSeqStmt* stmt = (CreateSeqStmt*)parsetree;
List* owned_by = NULL;
DefElem* defel = NULL;
foreach (lc, stmt->options) {
defel = (DefElem*)lfirst(lc);
if (pg_strcasecmp(defel->defname, "owned_by") == 0 && nodeTag(defel->arg) == T_List) {
owned_by = (List*)defel->arg;
}
}
if (owned_by != NULL) {
int owned_len = list_length(owned_by);
if (owned_len != 1) {
List* relname = list_truncate(list_copy(owned_by), owned_len - 1);
RangeVar* r = makeRangeVarFromNameList(relname);
if (r && CheckRangeVarInRedistribution(r))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command during online expansion on '%s'",
CreateCommandTag((Node*)parsetree),
r->relname)));
}
}
} break;
/* Block ALTER SEQUENCE while owner table in cluster resizing */
case T_AlterSeqStmt: {
AlterSeqStmt* stmt = (AlterSeqStmt*)parsetree;
List* owned_by = NIL;
DefElem* defel = NULL;
foreach (lc, stmt->options) {
defel = (DefElem*)lfirst(lc);
if (pg_strcasecmp(defel->defname, "owned_by") == 0 && nodeTag(defel->arg) == T_List) {
owned_by = (List*)defel->arg;
}
}
if (owned_by != NULL) {
int owned_len = list_length(owned_by);
if (owned_len != 1) {
List* relname = list_truncate(list_copy(owned_by), owned_len - 1);
RangeVar* r = makeRangeVarFromNameList(relname);
if (r && CheckRangeVarInRedistribution(r))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command during online expansion on '%s'",
CreateCommandTag((Node*)parsetree),
r->relname)));
}
}
} break;
/* Block CLUSTER while table in cluster resizing */
case T_ClusterStmt: {
ClusterStmt* stmt = (ClusterStmt*)parsetree;
if (stmt->relation && CheckRangeVarInRedistribution(stmt->relation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command during online expansion on '%s'",
CreateCommandTag((Node*)parsetree),
stmt->relation->relname)));
} break;
/* Block VACUUM FULL while table in cluster resizing */
case T_VacuumStmt: {
VacuumStmt* stmt = (VacuumStmt*)parsetree;
if ((stmt->options & VACOPT_VACUUM) || (stmt->options & VACOPT_MERGE)) {
if (stmt->relation) {
relid = RangeVarGetRelid(stmt->relation, lockmode_getrelid, true);
relidlist = list_make1_oid(relid);
}
}
if (stmt->options & VACOPT_FULL) {
if (OidIsValid(relid)) {
rel = relation_open(relid, lockmode_openrel);
if (RelationInClusterResizing(rel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport 'VACUUM FULL' command during online expansion on '%s'",
RelationGetRelationName(rel))));
}
relation_close(rel, lockmode_openrel);
}
}
} break;
/* Block truncate DDL when the target table is read only in cluster resizing */
case T_TruncateStmt: {
ListCell* cell = NULL;
TruncateStmt* stmt = (TruncateStmt*)parsetree;
foreach (cell, stmt->relations) {
RangeVar* rv = (RangeVar*)lfirst(cell);
if (CheckRangeVarInRedistribution(rv)) {
Oid relOid = RangeVarGetRelid(rv, lockmode_getrelid, true);
if (OidIsValid(relOid)) {
Oid nsOid = GetNamespaceIdbyRelId(relOid);
UnlockRelationOid(relOid, lockmode_getrelid);
TRANSFER_DISABLE_DDL(nsOid);
}
}
rel = heap_openrv(rv, lockmode_openrel);
if (RelationInClusterResizingReadOnly(rel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command during online expansion on '%s' because the object is in "
"read only mode.",
CreateCommandTag((Node*)parsetree),
RelationGetRelationName(rel))));
}
relation_close(rel, lockmode_openrel);
}
} break;
case T_DropStmt: {
DropStmt* stmt = (DropStmt*)parsetree;
switch (stmt->removeType) {
case OBJECT_TABLE: {
/* disable drop table when transfer */
ListCell* cell = NULL;
foreach (cell, stmt->objects) {
RangeVar* rel = makeRangeVarFromNameList((List*)lfirst(cell));
Oid relOid = RangeVarGetRelid(rel, AccessShareLock, true);
if (OidIsValid(relOid)) {
Oid nsOid = GetNamespaceIdbyRelId(relOid);
UnlockRelationOid(relOid, AccessShareLock);
TRANSFER_DISABLE_DDL(nsOid);
}
}
break;
}
case OBJECT_SCHEMA: {
/* disable drop schema when transfer */
ListCell* cell = NULL;
foreach (cell, stmt->objects) {
List* objname = (List*)lfirst(cell);
char* name = NameListToString(objname);
Oid nsOid = get_namespace_oid(name, true);
TRANSFER_DISABLE_DDL(nsOid);
}
break;
}
default:
break;
}
} break;
case T_CreateStmt: {
/* disable create table when transfer */
CreateStmt* stmt = (CreateStmt*)parsetree;
if (stmt->relation != NULL) {
Oid nsOid = RangeVarGetCreationNamespace(stmt->relation);
TRANSFER_DISABLE_DDL(nsOid);
}
} break;
default:
break;
}
foreach (lc, relidlist) {
relid = lfirst_oid(lc);
if (OidIsValid(relid) && get_rel_name(relid) != NULL) {
rel = relation_open(relid, lockmode_openrel);
if (RelationInClusterResizing(rel)) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Unsupport '%s' command during online expansion on '%s'",
CreateCommandTag((Node*)parsetree),
RelationGetRelationName(rel))));
}
relation_close(rel, lockmode_openrel);
}
}
}
/*
* - Brief: For online expanions, the shippable function is evaluated here, the module
* will be invoked in optimizer when do FQS evaluation, we have to define function
* as STABLE
* - Parameter:
* @funcid: oid of user defined function which is createed/dropped in scope of gs_redis
* - Return:
* @true: shippable
* @false: unshippable
*/
bool redis_func_shippable(Oid funcid)
{
const char* func_name = get_func_name(funcid);
Oid* argstype = NULL;
int nargs;
Oid rettype = InvalidOid;
bool result = false;
if (func_name == NULL) {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function with OID %u does not exist", funcid)));
}
/* Fetch function signatures */
rettype = get_func_signature(funcid, &argstype, &nargs);
if (redis_tupleid_retrive_function(func_name, rettype, argstype, nargs)) {
/* tupleid retrive functions is shippable to datanodes */
result = true;
} else if (redis_offset_retrive_function(func_name, rettype, argstype, nargs)) {
result = true;
} else if (redis_blocknum_retrive_function(func_name, rettype, argstype, nargs)) {
result = true;
} else if (redis_ctid_retrive_function(func_name, rettype, argstype, nargs)) {
result = true;
}
/* pfree */
if (argstype != NULL) {
pfree_ext(argstype);
argstype = NULL;
}
return result;
}
/*
* - Brief: determine if given funcid reflects a dn-stable function
* - Parameter:
* @funcid: function oid that to evaluate
* - Return:
* @result: true:dnstable false: not-dnstable function
*/
bool redis_func_dnstable(Oid funcid)
{
const char* func_name = get_func_name(funcid);
Oid* argstype = NULL;
int nargs;
Oid rettype = InvalidOid;
bool result = false;
if (func_name == NULL) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function with OID %u does not exist when checking function dnstable", funcid)));
}
/* Fetch function signatures */
rettype = get_func_signature(funcid, &argstype, &nargs);
if (redis_tupleid_retrive_function(func_name, rettype, argstype, nargs)) {
/* tupleid retrive functions is dnstable */
result = true;
}
return result;
}
/*
* - Brief: evaluate ctid functions into a const value to avoid per-scanning
* tuple invokation in seqscan.
* - Parameter:
* @rel: the rel being redistributing
* @original_quals: the original quals possible contains ctid_funcs
* @isRangeScanInRedis: if is a redis range scan
* - Return:
* @new_quals: quals which func call be replaced by a const
*/
List* eval_ctid_funcs(Relation rel, List* original_quals, RangeScanInRedis *rangeScanInRedis)
{
StringInfo qual_str = makeStringInfo();
/*
* we have to make a copy of the original quals, since the eval_dnstable_func_mutator
* will modify the it. the original qual will be needed again and again in later
* to be re-eval in partition table scans.
*/
List* new_quals = (List*)copyObject((const void*)(original_quals));
rangeScanInRedis->isRangeScanInRedis = false;
rangeScanInRedis->sliceTotal = 0;
rangeScanInRedis->sliceIndex = 0;
(void)eval_dnstable_func_mutator(rel, (Node*)new_quals, qual_str, rangeScanInRedis, true);
pfree_ext(qual_str->data);
pfree_ext(qual_str);
return new_quals;
}
static int32 get_expr_const_val(Node *val){
if (IsA(val, Const) && !((Const*)val)->constisnull && ((Const*)val)->consttype == INT4OID) {
return DatumGetInt32(((Const*)val)->constvalue);
} else {
return 0;
}
}
/*
* - Brief: working house for eval_dnstable_func() to evaluate dn stable function into a const
* value to avoid per-scanning tuple invocation in seqscan
* - Parameter:
* @rel: the rel being redistributing
* @node: expression node
* @qual_str: predicate pattern
* @isRangeScanInRedis: output to indicate if the predicate pattern is range scan in redis
* @isRoot: we want to compare the predicate pattern only once at root level
* - Return:
* @result: expression tree with dn stable function const-evaluated
*/
static Node* eval_dnstable_func_mutator(
Relation rel, Node* node, StringInfo qual_str, RangeScanInRedis *rangeScanInRedis, bool isRoot)
{
if (node == NULL)
return NULL;
if (IS_PGXC_COORDINATOR)
return node;
switch (nodeTag(node)) {
case T_FuncExpr: {
FuncExpr* expr = (FuncExpr*)node;
/* flatten dn stable function into const value */
if (redis_func_dnstable(expr->funcid)) {
Node* new_const = NULL;
char* funcname = get_func_name(expr->funcid);
if (funcname == NULL) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("operation expression function with OID %u does not exist.", expr->funcid)));
}
bool is_func_get_start_ctid = pg_strcasecmp(funcname, "pg_get_redis_rel_start_ctid") == 0;
bool is_func_get_end_ctid = pg_strcasecmp(funcname, "pg_get_redis_rel_end_ctid") == 0;
if (is_func_get_start_ctid || is_func_get_end_ctid){
int32 numSlices = get_expr_const_val((Node*)list_nth(expr->args, 2));
int32 idxSlices = get_expr_const_val((Node*)list_nth(expr->args, 3));
new_const = eval_redis_func_direct(rel, is_func_get_start_ctid, numSlices, idxSlices);
rangeScanInRedis->sliceIndex = idxSlices;
rangeScanInRedis->sliceTotal = numSlices;
} else {
new_const = eval_const_expressions(NULL, node);
}
appendStringInfoString(qual_str, get_func_name(expr->funcid));
appendStringInfoString(qual_str, "+");
return new_const;
}
break;
}
case T_List: {
List* l = (List*)node;
for (int i = 0; i < list_length(l); i++) {
Node* expr = (Node*)list_nth(l, i);
Node* new_expr = eval_dnstable_func_mutator(rel, expr, qual_str, rangeScanInRedis, false);
/*
* If a FuncExpr node is evalated into a T_Const value, we are hitting
* the point so replace it in qual list.
*/
if (expr && IsA(expr, FuncExpr) && new_expr && IsA(new_expr, Const)) {
l = list_delete_ptr(l, expr);
l = lappend(l, new_expr);
}
}
/*
* If the predicate at root is something like "where ctid between pg_get_redis_rel_start_ctid('xx')
* and pg_get_redis_rel_end_ctid('xx')" on DN, we will pushdown the predicate at scan node.
*/
if (isRoot && pg_strcasecmp(qual_str->data, RANGE_SCAN_IN_REDIS) == 0) {
rangeScanInRedis->isRangeScanInRedis = true;
}
break;
}
case T_OpExpr: {
OpExpr* opexpr = (OpExpr*)node;
char* funcname = get_func_name(opexpr->opfuncid);
if (funcname == NULL) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("operation expression function with OID %u does not exist.", opexpr->opfuncid)));
}
appendStringInfoString(qual_str, funcname);
appendStringInfoString(qual_str, "+");
eval_dnstable_func_mutator(rel, (Node*)opexpr->args, qual_str, rangeScanInRedis, false);
break;
}
case T_Var: {
Var* var = (Var*)node;
/* we only expect tid column in the predicate */
if (var->vartype == TIDOID) {
appendStringInfoString(qual_str, "tid");
appendStringInfoString(qual_str, "+");
}
break;
}
default: {
appendStringInfoString(qual_str, nodeTagToString(nodeTag(node)));
appendStringInfoString(qual_str, "+");
break;
}
}
return NULL;
}
/*
* - Brief: get and open new_table rel
* - Parameter:
* @rel: target relation of TRUNCATE operation
* - Return:
* new_table rel
*/
Relation GetAndOpenNewTableRel(const Relation rel, LOCKMODE lockmode)
{
Relation newtable_rel = NULL;
Oid newtable_relid = InvalidOid;
Oid data_redis_namespace;
char new_tablename[NAMEDATALEN];
errno_t errorno = EOK;
errorno = memset_s(new_tablename, NAMEDATALEN, 0, NAMEDATALEN);
securec_check_c(errorno, "\0", "\0");
RelationGetNewTableName(rel, (char*)new_tablename);
data_redis_namespace = get_namespace_oid("data_redis", false);
newtable_relid = get_relname_relid(new_tablename, data_redis_namespace);
if (!OidIsValid(newtable_relid)) {
/* ERROR case, should never come here */
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("new table %s is not found when do cluster resizing table \"%s\"",
new_tablename,
RelationGetRelationName(rel))));
}
newtable_rel = relation_open(newtable_relid, lockmode);
elog(LOG,
"New temp table %s for relation %s under cluster resizing is valid.",
new_tablename,
RelationGetRelationName(rel));
return newtable_rel;
}
/*
* - Brief: get the name of new table
* - Parameter:
* @relname: name of target table
* @newtable_name: output value for new table name
* - Return:
* no return value
*/
void RelationGetNewTableName(Relation rel, char* newtable_name)
{
int rc = 0;
/* Check if output parameter it not palloc()-ed from caller side */
if (newtable_name == NULL || rel == NULL) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Invalid parameter in function '%s' when getting the name of new table", __FUNCTION__)));
}
/*
* Look up relaion's reloptions to get table's cnoid to
* form the name of new table
*/
if (!IsInitdb) {
Oid rel_cn_oid = RelationGetRelCnOid(rel);
if (OidIsValid(rel_cn_oid)) {
rc = snprintf_s(newtable_name, NAMEDATALEN, NAMEDATALEN - 1, "data_redis_tmp_%u", rel_cn_oid);
} else {
elog(LOG, "rel %s doesn't exist in redistributing", RelationGetRelationName(rel));
rc = snprintf_s(
newtable_name, NAMEDATALEN, NAMEDATALEN - 1, "data_redis_tmp_%s", RelationGetRelationName(rel));
}
/* check the return value of security function */
securec_check_ss(rc, "\0", "\0");
}
return;
}