3964 lines
155 KiB
C++
3964 lines
155 KiB
C++
/* -------------------------------------------------------------------------
|
|
*
|
|
* indexcmds.cpp
|
|
* openGauss define and remove index code.
|
|
*
|
|
* 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
|
|
* Portions Copyright (c) 2021, openGauss Contributors
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/gausskernel/optimizer/commands/indexcmds.cpp
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include "access/cstore_delta.h"
|
|
#include "access/reloptions.h"
|
|
#include "access/tableam.h"
|
|
#include "access/xact.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/index.h"
|
|
#include "catalog/indexing.h"
|
|
#include "catalog/pg_constraint.h"
|
|
#include "catalog/pg_opclass.h"
|
|
#include "catalog/pg_opfamily.h"
|
|
#include "catalog/pg_partition.h"
|
|
#include "catalog/pg_partition_fn.h"
|
|
#include "catalog/pg_tablespace.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "catalog/gs_db_privilege.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/pg_namespace.h"
|
|
#include "commands/comment.h"
|
|
#include "commands/dbcommands.h"
|
|
#include "commands/defrem.h"
|
|
#include "catalog/heap.h"
|
|
#include "commands/tablecmds.h"
|
|
#include "commands/tablespace.h"
|
|
#include "foreign/foreign.h"
|
|
#include "mb/pg_wchar.h"
|
|
#include "miscadmin.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "optimizer/clauses.h"
|
|
#include "optimizer/planner.h"
|
|
#include "parser/parse_coerce.h"
|
|
#include "parser/parse_func.h"
|
|
#include "parser/parse_oper.h"
|
|
#ifdef PGXC
|
|
#include "optimizer/pgxcship.h"
|
|
#include "parser/parse_utilcmd.h"
|
|
#include "pgxc/pgxc.h"
|
|
#endif
|
|
#include "storage/lmgr.h"
|
|
#include "storage/proc.h"
|
|
#include "storage/procarray.h"
|
|
#include "storage/tcap.h"
|
|
#include "tcop/utility.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/inval.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/snapmgr.h"
|
|
#include "utils/syscache.h"
|
|
#include "access/heapam.h"
|
|
|
|
#include "securec.h"
|
|
|
|
/* non-export function prototypes */
|
|
void CheckPredicate(Expr* predicate);
|
|
Oid GetIndexOpClass(List* opclass, Oid attrType, const char* accessMethodName, Oid accessMethodId);
|
|
static char* ChooseIndexName(const char* tabname, Oid namespaceId, const List* colnames, const List* exclusionOpNames,
|
|
bool primary, bool isconstraint, bool psort = false);
|
|
static char* ChoosePartitionIndexName(const char* partname, Oid partitionedindexId,
|
|
List* colnames, /* List *exclusionOpNames, */
|
|
bool primary, bool isconstraint, bool psort = false);
|
|
|
|
static char* ChoosePartitionName(
|
|
const char* name1, const char* name2, const char* label, Oid partitionedrelid, char partType);
|
|
static char* ChooseIndexNameAddition(const List* colnames);
|
|
static void RangeVarCallbackForReindexIndex(
|
|
const RangeVar* relation, Oid relId, Oid oldRelId, bool target_is_partition, void* arg);
|
|
static bool columnIsExist(Relation rel, const Form_pg_attribute attTup, const List* indexParams);
|
|
static bool relationHasInformationalPrimaryKey(const Relation conrel);
|
|
static void handleErrMsgForInfoCnstrnt(const IndexStmt* stmt, const Relation rel);
|
|
static void buildConstraintNameForInfoCnstrnt(
|
|
const IndexStmt* stmt, Relation rel, char** indexRelationName, Oid namespaceId, const List* indexColNames);
|
|
static Oid buildInformationalConstraint(
|
|
IndexStmt* stmt, Oid indexRelationId, const char* indexRelationName, Relation rel, IndexInfo* indexInfo, Oid namespaceId);
|
|
static bool CheckGlobalIndexCompatible(Oid relOid, bool isGlobal, const IndexInfo* indexInfo, Oid methodOid);
|
|
static bool CheckIndexMethodConsistency(HeapTuple indexTuple, Relation indexRelation, Oid currMethodOid);
|
|
static bool CheckSimpleAttrsConsistency(HeapTuple tarTuple, const int16* currAttrsArray, int currKeyNum);
|
|
static int AttrComparator(const void* a, const void* b);
|
|
static void AddIndexColumnForGpi(IndexStmt* stmt);
|
|
static void AddIndexColumnForCbi(IndexStmt* stmt);
|
|
static void CheckIndexParamsNumber(IndexStmt* stmt);
|
|
static bool CheckIdxParamsOwnPartKey(Relation rel, const List* indexParams);
|
|
static bool CheckWhetherForbiddenFunctionalIdx(Oid relationId, Oid namespaceId, List* indexParams);
|
|
|
|
/*
|
|
* CheckIndexCompatible
|
|
* Determine whether an existing index definition is compatible with a
|
|
* prospective index definition, such that the existing index storage
|
|
* could become the storage of the new index, avoiding a rebuild.
|
|
*
|
|
* 'heapRelation': the relation the index would apply to.
|
|
* 'accessMethodName': name of the AM to use.
|
|
* 'attributeList': a list of IndexElem specifying columns and expressions
|
|
* to index on.
|
|
* 'exclusionOpNames': list of names of exclusion-constraint operators,
|
|
* or NIL if not an exclusion constraint.
|
|
*
|
|
* This is tailored to the needs of ALTER TABLE ALTER TYPE, which recreates
|
|
* any indexes that depended on a changing column from their pg_get_indexdef
|
|
* or pg_get_constraintdef definitions. We omit some of the sanity checks of
|
|
* DefineIndex. We assume that the old and new indexes have the same number
|
|
* of columns and that if one has an expression column or predicate, both do.
|
|
* Errors arising from the attribute list still apply.
|
|
*
|
|
* Most column type changes that can skip a table rewrite do not invalidate
|
|
* indexes. We ackowledge this when all operator classes, collations and
|
|
* exclusion operators match. Though we could further permit intra-opfamily
|
|
* changes for btree and hash indexes, that adds subtle complexity with no
|
|
* concrete benefit for core types. Note, that INCLUDE columns aren't
|
|
* checked by this function, for them it's enough that table rewrite is
|
|
* skipped.
|
|
|
|
* When a comparison or exclusion operator has a polymorphic input type, the
|
|
* actual input types must also match. This defends against the possibility
|
|
* that operators could vary behavior in response to get_fn_expr_argtype().
|
|
* At present, this hazard is theoretical: check_exclusion_constraint() and
|
|
* all core index access methods decline to set fn_expr for such calls.
|
|
*
|
|
* We do not yet implement a test to verify compatibility of expression
|
|
* columns or predicates, so assume any such index is incompatible.
|
|
*/
|
|
bool CheckIndexCompatible(Oid oldId, char* accessMethodName, List* attributeList, List* exclusionOpNames)
|
|
{
|
|
bool isconstraint = false;
|
|
Oid* typeObjectId = NULL;
|
|
Oid* collationObjectId = NULL;
|
|
Oid* classObjectId = NULL;
|
|
Oid accessMethodId;
|
|
Oid relationId;
|
|
HeapTuple tuple;
|
|
Form_pg_index indexForm;
|
|
Form_pg_am accessMethodForm;
|
|
bool amcanorder = false;
|
|
int16* coloptions = NULL;
|
|
IndexInfo* indexInfo = NULL;
|
|
int numberOfAttributes;
|
|
int old_natts;
|
|
bool isnull = false;
|
|
bool ret = true;
|
|
oidvector* old_indclass = NULL;
|
|
oidvector* old_indcollation = NULL;
|
|
Relation irel;
|
|
int i = 0;
|
|
Datum d;
|
|
|
|
/* Caller should already have the relation locked in some way. */
|
|
relationId = IndexGetRelation(oldId, false);
|
|
|
|
/*
|
|
* We can pretend isconstraint = false unconditionally. It only serves to
|
|
* decide the text of an error message that should never happen for us.
|
|
*/
|
|
isconstraint = false;
|
|
|
|
numberOfAttributes = list_length(attributeList);
|
|
if (numberOfAttributes <= 0 || numberOfAttributes > INDEX_MAX_KEYS) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("the number of attributes is illegal")));
|
|
}
|
|
|
|
/* look up the access method */
|
|
tuple = SearchSysCache1(AMNAME, PointerGetDatum(accessMethodName));
|
|
if (!HeapTupleIsValid(tuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("access method \"%s\" does not exist", accessMethodName)));
|
|
accessMethodId = HeapTupleGetOid(tuple);
|
|
accessMethodForm = (Form_pg_am)GETSTRUCT(tuple);
|
|
amcanorder = accessMethodForm->amcanorder;
|
|
ReleaseSysCache(tuple);
|
|
|
|
/*
|
|
* Compute the operator classes, collations, and exclusion operators for
|
|
* the new index, so we can test whether it's compatible with the existing
|
|
* one. Note that ComputeIndexAttrs might fail here, but that's OK:
|
|
* DefineIndex would have called this function with the same arguments
|
|
* later on, and it would have failed then anyway. Our attributeList
|
|
* contains only key attributes, thus we're filling ii_NumIndexAttrs and
|
|
* ii_NumIndexKeyAttrs with same value.
|
|
*/
|
|
indexInfo = makeNode(IndexInfo);
|
|
indexInfo->ii_NumIndexAttrs = numberOfAttributes;
|
|
indexInfo->ii_NumIndexKeyAttrs = numberOfAttributes;
|
|
indexInfo->ii_Expressions = NIL;
|
|
indexInfo->ii_ExpressionsState = NIL;
|
|
indexInfo->ii_PredicateState = NIL;
|
|
indexInfo->ii_ExclusionOps = NULL;
|
|
indexInfo->ii_ExclusionProcs = NULL;
|
|
indexInfo->ii_ExclusionStrats = NULL;
|
|
typeObjectId = (Oid*)palloc(numberOfAttributes * sizeof(Oid));
|
|
collationObjectId = (Oid*)palloc(numberOfAttributes * sizeof(Oid));
|
|
classObjectId = (Oid*)palloc(numberOfAttributes * sizeof(Oid));
|
|
coloptions = (int16*)palloc(numberOfAttributes * sizeof(int16));
|
|
ComputeIndexAttrs(indexInfo,
|
|
typeObjectId,
|
|
collationObjectId,
|
|
classObjectId,
|
|
coloptions,
|
|
attributeList,
|
|
exclusionOpNames,
|
|
relationId,
|
|
accessMethodName,
|
|
accessMethodId,
|
|
amcanorder,
|
|
isconstraint);
|
|
|
|
/* Get the soon-obsolete pg_index tuple. */
|
|
tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(oldId));
|
|
if (!HeapTupleIsValid(tuple))
|
|
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for index %u", oldId)));
|
|
indexForm = (Form_pg_index)GETSTRUCT(tuple);
|
|
|
|
/*
|
|
* We don't assess expressions or predicates; assume incompatibility.
|
|
* Also, if the index is invalid for any reason, treat it as incompatible.
|
|
*/
|
|
if (!(heap_attisnull(tuple, Anum_pg_index_indpred, NULL) && heap_attisnull(tuple, Anum_pg_index_indexprs, NULL) &&
|
|
IndexIsValid(indexForm))) {
|
|
ReleaseSysCache(tuple);
|
|
return false;
|
|
}
|
|
|
|
/* Any change in operator class or collation breaks compatibility. */
|
|
old_natts = GetIndexKeyAttsByTuple(NULL, tuple);
|
|
Assert(old_natts == numberOfAttributes);
|
|
|
|
d = SysCacheGetAttr(INDEXRELID, tuple, Anum_pg_index_indcollation, &isnull);
|
|
Assert(!isnull);
|
|
old_indcollation = (oidvector*)DatumGetPointer(d);
|
|
|
|
d = SysCacheGetAttr(INDEXRELID, tuple, Anum_pg_index_indclass, &isnull);
|
|
Assert(!isnull);
|
|
old_indclass = (oidvector*)DatumGetPointer(d);
|
|
|
|
ret = (memcmp(old_indclass->values, classObjectId, old_natts * sizeof(Oid)) == 0 &&
|
|
memcmp(old_indcollation->values, collationObjectId, old_natts * sizeof(Oid)) == 0);
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
/* For polymorphic opcintype, column type changes break compatibility. */
|
|
irel = index_open(oldId, AccessShareLock); /* caller probably has a lock */
|
|
for (i = 0; i < old_natts; i++) {
|
|
if (IsPolymorphicType(get_opclass_input_type(classObjectId[i])) &&
|
|
irel->rd_att->attrs[i]->atttypid != typeObjectId[i]) {
|
|
ret = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Any change in exclusion operator selections breaks compatibility. */
|
|
if (ret && indexInfo->ii_ExclusionOps != NULL) {
|
|
Oid* old_operators = NULL;
|
|
Oid* old_procs = NULL;
|
|
uint16* old_strats = NULL;
|
|
|
|
RelationGetExclusionInfo(irel, &old_operators, &old_procs, &old_strats);
|
|
ret = memcmp(old_operators, indexInfo->ii_ExclusionOps, old_natts * sizeof(Oid)) == 0;
|
|
|
|
/* Require an exact input type match for polymorphic operators. */
|
|
if (ret) {
|
|
for (i = 0; i < old_natts && ret; i++) {
|
|
Oid left, right;
|
|
|
|
op_input_types(indexInfo->ii_ExclusionOps[i], &left, &right);
|
|
if ((IsPolymorphicType(left) || IsPolymorphicType(right)) &&
|
|
irel->rd_att->attrs[i]->atttypid != typeObjectId[i]) {
|
|
ret = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
index_close(irel, NoLock);
|
|
return ret;
|
|
}
|
|
|
|
static void CheckPartitionUniqueKey(Relation rel, int2vector *partKey, IndexStmt *stmt, int numberOfAttributes)
|
|
{
|
|
int j;
|
|
|
|
if (partKey->dim1 > numberOfAttributes) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("unique local index columns must contain all the partition keys")));
|
|
}
|
|
|
|
for (j = 0; j < partKey->dim1; j++) {
|
|
int2 attNum = partKey->values[j];
|
|
Form_pg_attribute att_tup = rel->rd_att->attrs[attNum - 1];
|
|
|
|
if (!columnIsExist(rel, att_tup, stmt->indexParams)) {
|
|
ereport(
|
|
ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("unique local index columns must contain all the partition keys and collation must be default "
|
|
"collation")));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void CheckPartitionIndexDef(IndexStmt* stmt, List *partitionTableList)
|
|
{
|
|
List *partitionIndexdef = (List*)stmt->partClause;
|
|
|
|
int partitionLens = list_length(partitionTableList);
|
|
int idfLens = list_length(partitionIndexdef);
|
|
|
|
if (partitionLens > idfLens) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("Not enough index partition defined")));
|
|
} else if (partitionLens < idfLens) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("number of partitions of LOCAL index must equal that of the "
|
|
"underlying table")));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Extract SubPartitionIdfs when CREATE INDEX with subpartitions.
|
|
*/
|
|
static List *ExtractSubPartitionIdf(IndexStmt* stmt, List *partitionList,
|
|
List *subPartitionList, List *partitionIndexdef)
|
|
{
|
|
ListCell *lc1 = NULL;
|
|
ListCell *lc2 = NULL;
|
|
int subpartitionLens = 0;
|
|
int expectedSubLens = 0;
|
|
List *subPartitionIdf = NIL;
|
|
|
|
partitionIndexdef = (List*)stmt->partClause;
|
|
List *backupIdxdef = (List *)copyObject(partitionIndexdef);
|
|
int partitionLen = list_length(partitionIndexdef);
|
|
|
|
/* Fast check partition length */
|
|
if (partitionLen != partitionList->length) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("Wrong number of partitions when create index specify subpartition.")));
|
|
}
|
|
|
|
/* Next check specify subpartition with metadata in pg_partition */
|
|
foreach(lc1, subPartitionList) {
|
|
List *subPartitions = (List *)lfirst(lc1);
|
|
int subLens = list_length(subPartitions);
|
|
|
|
foreach(lc2, partitionIndexdef) {
|
|
RangePartitionindexDefState *idxDef = (RangePartitionindexDefState*)lfirst(lc2);
|
|
int idfLens = list_length(idxDef->sublist);
|
|
|
|
if (subLens == idfLens) {
|
|
subPartitionIdf = lappend(subPartitionIdf, copyObject(idxDef->sublist));
|
|
partitionIndexdef = list_delete(partitionIndexdef, lfirst(lc2));
|
|
break;
|
|
}
|
|
}
|
|
|
|
expectedSubLens += subPartitions->length;
|
|
}
|
|
|
|
/* Fail exactly match if partitionIndexdef */
|
|
if (partitionIndexdef != NULL) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("Cannot match subpartitions when create subpartition indexes.")));
|
|
}
|
|
|
|
/* Count sum of subpartitions */
|
|
foreach(lc1, backupIdxdef) {
|
|
RangePartitionindexDefState *def = (RangePartitionindexDefState*)lfirst(lc1);
|
|
subpartitionLens += list_length(def->sublist);
|
|
}
|
|
|
|
/* Check total subpartition number */
|
|
if (subpartitionLens != expectedSubLens) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("Wrong number of subpartitions when create index specify subpartition.")));
|
|
}
|
|
|
|
list_free_ext(backupIdxdef);
|
|
return subPartitionIdf;
|
|
}
|
|
|
|
/*
|
|
* DefineIndex
|
|
* Creates a new index.
|
|
*
|
|
*relationId: table oid for current index defination
|
|
* 'stmt': IndexStmt describing the properties of the new index.
|
|
* 'indexRelationId': normally InvalidOid, but during bootstrap can be
|
|
* nonzero to specify a preselected OID for the index.
|
|
* 'is_alter_table': this is due to an ALTER rather than a CREATE operation.
|
|
* 'check_rights': check for CREATE rights in the namespace. (This should
|
|
* be true except when ALTER is deleting/recreating an index.)
|
|
* 'skip_build': make the catalog entries but leave the index file empty;
|
|
* it will be filled later.
|
|
* 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
|
|
*
|
|
* Returns the OID of the created index.
|
|
*/
|
|
Oid DefineIndex(Oid relationId, IndexStmt* stmt, Oid indexRelationId, bool is_alter_table, bool check_rights,
|
|
bool skip_build, bool quiet)
|
|
{
|
|
char* indexRelationName = NULL;
|
|
char* accessMethodName = NULL;
|
|
Oid* typeObjectId = NULL;
|
|
Oid* collationObjectId = NULL;
|
|
Oid* classObjectId = NULL;
|
|
Oid accessMethodId = InvalidOid;
|
|
Oid namespaceId = InvalidOid;
|
|
Oid tablespaceId = InvalidOid;
|
|
bool dfsTablespace = false;
|
|
List* indexColNames = NIL;
|
|
List* allIndexParams = NIL;
|
|
Relation rel;
|
|
Relation indexRelation;
|
|
HeapTuple tuple;
|
|
Form_pg_am accessMethodForm;
|
|
bool amcanorder = false;
|
|
RegProcedure amoptions;
|
|
Datum reloptions;
|
|
int16* coloptions = NULL;
|
|
IndexInfo* indexInfo = NULL;
|
|
int numberOfAttributes = 0;
|
|
int numberOfKeyAttributes;
|
|
TransactionId limitXmin;
|
|
VirtualTransactionId* old_lockholders = NULL;
|
|
VirtualTransactionId* old_snapshots = NULL;
|
|
int n_old_snapshots = 0;
|
|
LockRelId heaprelid;
|
|
LOCKTAG heaplocktag;
|
|
LOCKMODE lockmode;
|
|
Snapshot snapshot;
|
|
int i = 0;
|
|
List* partitionTableList = NIL;
|
|
List* partitionIndexdef = NIL;
|
|
List* partitiontspList = NIL;
|
|
char relPersistence;
|
|
bool concurrent;
|
|
StdRdOptions* index_relopts;
|
|
int8 indexsplitMethod = INDEXSPLIT_NO_DEFAULT;
|
|
int crossbucketopt = -1;
|
|
List *subPartTspList = NULL;
|
|
List *subPartitionIndexDef = NULL;
|
|
List *subPartitionTupleList = NULL;
|
|
List *subPartitionOidList = NULL;
|
|
List *partitionOidList = NULL;
|
|
|
|
/*
|
|
* Force non-concurrent build on temporary relations, even if CONCURRENTLY
|
|
* was requested. Other backends can't access a temporary relation, so
|
|
* there's no harm in grabbing a stronger lock, and a non-concurrent DROP
|
|
* is more efficient. Do this before any use of the concurrent option is
|
|
* done.
|
|
*/
|
|
relPersistence = get_rel_persistence(relationId);
|
|
if (stmt->concurrent && !(relPersistence == RELPERSISTENCE_TEMP
|
|
|| relPersistence == RELPERSISTENCE_GLOBAL_TEMP)) {
|
|
concurrent = true;
|
|
} else {
|
|
concurrent = false;
|
|
}
|
|
|
|
/* Don't suppport gin/gist index on global temporary table */
|
|
if (relPersistence == RELPERSISTENCE_GLOBAL_TEMP &&
|
|
(strcmp(stmt->accessMethod, "gin") == 0 || strcmp(stmt->accessMethod, "gist") == 0)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("access method \"%s\" is not support for global temporary table index", stmt->accessMethod)));
|
|
}
|
|
|
|
/*
|
|
* Open heap relation, acquire a suitable lock on it, remember its OID
|
|
*
|
|
* Only SELECT ... FOR UPDATE/SHARE are allowed while doing a standard
|
|
* index build; but for concurrent builds we allow INSERT/UPDATE/DELETE
|
|
* (but not VACUUM).
|
|
*
|
|
* NB: Caller is responsible for making sure that relationId refers
|
|
* to the relation on which the index should be built; except in bootstrap
|
|
* mode, this will typically require the caller to have already locked
|
|
* the relation. To avoid lock upgrade hazards, that lock should be at
|
|
* least as strong as the one we take here.
|
|
*/
|
|
lockmode = concurrent ? ShareUpdateExclusiveLock : ShareLock;
|
|
rel = heap_open(relationId, lockmode);
|
|
|
|
TableCreateSupport indexCreateSupport{COMPRESS_TYPE_NONE, false, false, false, false, false};
|
|
ListCell *cell = NULL;
|
|
foreach (cell, stmt->options) {
|
|
DefElem *defElem = (DefElem *)lfirst(cell);
|
|
SetOneOfCompressOption(defElem, &indexCreateSupport);
|
|
}
|
|
|
|
CheckCompressOption(&indexCreateSupport);
|
|
|
|
/* Forbidden to create gin index on ustore table. */
|
|
if (rel->rd_tam_type == TAM_USTORE) {
|
|
if (strcmp(stmt->accessMethod, "btree") == 0) {
|
|
elog(ERROR, "btree index is not supported for ustore, please use ubtree instead");
|
|
}
|
|
if (strcmp(stmt->accessMethod, "ubtree") != 0) {
|
|
elog(ERROR, "%s index is not supported for ustore", (stmt->accessMethod));
|
|
}
|
|
if (stmt->deferrable == true) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmodule(MOD_EXECUTOR),
|
|
errmsg("Ustore table does not support to set deferrable."),
|
|
errdetail("N/A"),
|
|
errcause("feature not supported"),
|
|
erraction("check constraints of columns")));
|
|
}
|
|
}
|
|
|
|
if (strcmp(stmt->accessMethod, "ubtree") == 0 &&
|
|
rel->rd_tam_type != TAM_USTORE) {
|
|
elog(ERROR, "ubtree index is only supported for ustore");
|
|
}
|
|
|
|
relationId = RelationGetRelid(rel);
|
|
namespaceId = RelationGetNamespace(rel);
|
|
|
|
if (stmt->schemaname != NULL) {
|
|
if (namespaceId != get_namespace_oid(stmt->schemaname, false)) {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_EXECUTOR),
|
|
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
errmsg("index and table must be in the same schema")));
|
|
}
|
|
}
|
|
|
|
if (CheckWhetherForbiddenFunctionalIdx(relationId, namespaceId, stmt->indexParams)) {
|
|
ereport(ERROR,
|
|
(errmodule(MOD_EXECUTOR),
|
|
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("not supported to create a functional index on this table.")));
|
|
}
|
|
|
|
if (RELATION_IS_PARTITIONED(rel)) {
|
|
if (stmt->unique && (!stmt->isPartitioned || is_alter_table)) {
|
|
/*
|
|
* If index key of unique or primary key index include the partition key,
|
|
* default index is set to local index. Otherwise, set to global index.
|
|
*/
|
|
if (RelationIsSubPartitioned(rel)) {
|
|
List *partOidList = relationGetPartitionOidList(rel);
|
|
Assert(list_length(partOidList) != 0);
|
|
Partition part = partitionOpen(rel, linitial_oid(partOidList), NoLock);
|
|
Relation partRel = partitionGetRelation(rel, part);
|
|
stmt->isGlobal = !(CheckIdxParamsOwnPartKey(rel, stmt->indexParams) &&
|
|
CheckIdxParamsOwnPartKey(partRel, stmt->indexParams));
|
|
releaseDummyRelation(&partRel);
|
|
partitionClose(rel, part, NoLock);
|
|
if (partOidList != NULL) {
|
|
releasePartitionOidList(&partOidList);
|
|
}
|
|
} else {
|
|
stmt->isGlobal = !CheckIdxParamsOwnPartKey(rel, stmt->indexParams);
|
|
}
|
|
} else if (!stmt->isPartitioned) {
|
|
/* default partition index is set to Global index */
|
|
stmt->isGlobal = (!DEFAULT_CREATE_LOCAL_INDEX ? true : stmt->isGlobal);
|
|
}
|
|
stmt->isPartitioned = true;
|
|
#ifndef ENABLE_MULTIPLE_NODES
|
|
if (stmt->unique && stmt->isPartitioned && RelationIsCUFormat(rel)) {
|
|
/* CStore unique index must include the partition key and set to local index */
|
|
stmt->isGlobal = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (stmt->isGlobal && DISABLE_MULTI_NODES_GPI) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Global partition index only support single node mode.")));
|
|
}
|
|
if (stmt->isGlobal && t_thrd.proc->workingVersionNum < SUPPORT_GPI_VERSION_NUM) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg(
|
|
"global partition index not support on work version num less than %u.", SUPPORT_GPI_VERSION_NUM)));
|
|
}
|
|
|
|
if (RELATION_HAS_BUCKET(rel)) {
|
|
/* determine the crossbucket option */
|
|
stmt->crossbucket = get_crossbucket_option(&stmt->options, stmt->isGlobal, stmt->accessMethod, &crossbucketopt);
|
|
}
|
|
|
|
if (stmt->crossbucket) {
|
|
if (stmt->whereClause != NULL) {
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("cross-bucket index does not support WHERE clause")));
|
|
}
|
|
|
|
if (pg_strcasecmp(stmt->accessMethod, DEFAULT_INDEX_TYPE) != 0 && (crossbucketopt > 0)) {
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("cross-bucket index only supports btree access method")));
|
|
}
|
|
|
|
if (concurrent) {
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("cannot create CONCURRENTLY cross-bucket index")));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* normal table does not support local partitioned index
|
|
*/
|
|
if (stmt->isPartitioned) {
|
|
if (RelationIsValuePartitioned(rel)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("dfs value partition table does not support local partitioned indexes ")));
|
|
} else if (!RELATION_IS_PARTITIONED(rel)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("non-partitioned table does not support %s partitioned indexes ",
|
|
stmt->isGlobal ? "global" : "local")));
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
} else if (stmt->deferrable && stmt->unique) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Partition table does not support to set deferrable.")));
|
|
#endif
|
|
} else if (stmt->isGlobal && stmt->whereClause != NULL) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Global partition index does not support WHERE clause.")));
|
|
}
|
|
}
|
|
|
|
if (list_length(stmt->indexIncludingParams) > 0 && strcmp(stmt->accessMethod, "ubtree") != 0) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("create a index with include columns is only supported in ubtree")));
|
|
}
|
|
|
|
/*
|
|
* partitioned index not is not support concurrent index
|
|
*/
|
|
if (stmt->isPartitioned && concurrent) {
|
|
ereport(
|
|
ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create concurrent partitioned indexes ")));
|
|
}
|
|
|
|
CheckIndexParamsNumber(stmt);
|
|
|
|
/* Add special index columns tableoid to global partition index. */
|
|
if (stmt->isGlobal) {
|
|
AddIndexColumnForGpi(stmt);
|
|
}
|
|
|
|
/* Add special index columns tablebucketid to crossbucket index. */
|
|
if (stmt->crossbucket) {
|
|
AddIndexColumnForCbi(stmt);
|
|
}
|
|
|
|
if (list_intersection(stmt->indexParams, stmt->indexIncludingParams) != NIL) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("included columns must not intersect with key columns")));
|
|
}
|
|
|
|
/*
|
|
* count key attributes in index
|
|
*/
|
|
numberOfKeyAttributes = list_length(stmt->indexParams);
|
|
|
|
/*
|
|
* Calculate the new list of index columns including both key columns and
|
|
* INCLUDE columns. Later we can determine which of these are key columns,
|
|
* and which are just part of the INCLUDE list by checking the list
|
|
* position. A list item in a position less than ii_NumIndexKeyAttrs is
|
|
* part of the key columns, and anything equal to and over is part of the
|
|
* INCLUDE columns.
|
|
*/
|
|
allIndexParams = list_concat(list_copy(stmt->indexParams), list_copy(stmt->indexIncludingParams));
|
|
/*
|
|
* count attributes in index
|
|
*/
|
|
numberOfAttributes = list_length(allIndexParams);
|
|
if (numberOfAttributes <= 0) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("must specify at least one column")));
|
|
}
|
|
|
|
if (numberOfAttributes > INDEX_MAX_KEYS) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_TOO_MANY_COLUMNS), errmsg("cannot use more than %d columns in an index", INDEX_MAX_KEYS)));
|
|
}
|
|
|
|
indexRelationName = stmt->idxname;
|
|
|
|
handleErrMsgForInfoCnstrnt(stmt, rel);
|
|
|
|
/*
|
|
* Don't try to CREATE INDEX on temp tables of other backends.
|
|
*/
|
|
if (RELATION_IS_OTHER_TEMP(rel))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("cannot create indexes on temporary tables of other sessions")));
|
|
|
|
/*
|
|
* Verify we (still) have CREATE rights in the rel's namespace.
|
|
* (Presumably we did when the rel was created, but maybe not anymore.)
|
|
* Skip check if caller doesn't want it. Also skip check if
|
|
* bootstrapping, since permissions machinery may not be working yet.
|
|
*/
|
|
if (check_rights && !IsBootstrapProcessingMode()) {
|
|
(void)CheckCreatePrivilegeInNamespace(namespaceId, GetUserId(), CREATE_ANY_INDEX);
|
|
}
|
|
|
|
/*
|
|
* Select tablespace to use. If not specified, use default tablespace
|
|
* (which may in turn default to database's default).
|
|
*/
|
|
if (stmt->tableSpace) {
|
|
tablespaceId = get_tablespace_oid(stmt->tableSpace, false);
|
|
} else {
|
|
tablespaceId = GetDefaultTablespace(rel->rd_rel->relpersistence);
|
|
/* note InvalidOid is OK in this case */
|
|
}
|
|
|
|
dfsTablespace = IsSpecifiedTblspc(tablespaceId, FILESYSTEM_HDFS);
|
|
if (dfsTablespace) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("It is not supported to create index on DFS tablespace.")));
|
|
}
|
|
|
|
/* Check permissions except when using database's default */
|
|
if (stmt->isPartitioned && !stmt->isGlobal) {
|
|
/* LOCAL partition index check */
|
|
ListCell* cell = NULL;
|
|
|
|
partitionTableList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_PARTITION, relationId);
|
|
|
|
if (!PointerIsValid(partitionTableList)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("when creating partitioned index, get table partitions failed")));
|
|
}
|
|
|
|
if (RelationIsSubPartitioned(rel)) {
|
|
subPartitionTupleList =
|
|
searchPgSubPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partitionTableList);
|
|
}
|
|
|
|
if (PointerIsValid(stmt->partClause)) {
|
|
if (RelationIsSubPartitioned(rel)) {
|
|
ListCell* lc1 = NULL;
|
|
ListCell* lc2 = NULL;
|
|
List* subPartitions = NIL;
|
|
|
|
partitionIndexdef = (List*)stmt->partClause;
|
|
subPartitionIndexDef = ExtractSubPartitionIdf(stmt,
|
|
partitionTableList,
|
|
subPartitionTupleList,
|
|
partitionIndexdef);
|
|
|
|
/* Fill partitionOidList */
|
|
foreach (lc1, partitionTableList) {
|
|
HeapTuple tuple = (HeapTuple)lfirst(lc1);
|
|
partitionOidList = lappend_oid(partitionOidList, HeapTupleGetOid(tuple));
|
|
}
|
|
|
|
/* Fill subPartitionOidList */
|
|
foreach (lc1, subPartitionTupleList) {
|
|
subPartitions = (List*)lfirst(lc1);
|
|
|
|
List* subPartitionOids = NIL;
|
|
foreach (lc2, subPartitions) {
|
|
HeapTuple tuple = (HeapTuple)lfirst(lc2);
|
|
subPartitionOids = lappend_oid(subPartitionOids, HeapTupleGetOid(tuple));
|
|
}
|
|
subPartitionOidList = lappend(subPartitionOidList, subPartitionOids);
|
|
}
|
|
} else {
|
|
partitionIndexdef = (List*)stmt->partClause;
|
|
|
|
/* index partition's number must no less than table partition's number */
|
|
CheckPartitionIndexDef(stmt, partitionTableList);
|
|
}
|
|
} else {
|
|
if (!RelationIsSubPartitioned(rel)) {
|
|
/* construct the index list */
|
|
for (i = 0; i < partitionTableList->length; i++) {
|
|
RangePartitionindexDefState* def = makeNode(RangePartitionindexDefState);
|
|
partitionIndexdef = lappend(partitionIndexdef, def);
|
|
}
|
|
} else {
|
|
int j = 0;
|
|
/* construct the index list */
|
|
foreach (cell, subPartitionTupleList) {
|
|
List *sub = (List *)lfirst(cell);
|
|
List *partSubIndexDef = NULL;
|
|
for (j = 0; j < sub->length; j++) {
|
|
RangePartitionindexDefState *def = makeNode(RangePartitionindexDefState);
|
|
partSubIndexDef = lappend(partSubIndexDef, def);
|
|
}
|
|
|
|
subPartitionIndexDef = lappend(subPartitionIndexDef, partSubIndexDef);
|
|
}
|
|
foreach (cell, partitionTableList) {
|
|
HeapTuple tuple = (HeapTuple)lfirst(cell);
|
|
Oid partOid = HeapTupleGetOid(tuple);
|
|
partitionOidList = lappend_oid(partitionOidList, partOid);
|
|
}
|
|
foreach (cell, subPartitionTupleList) {
|
|
List* subPartTuples = (List*)lfirst(cell);
|
|
ListCell* lc = NULL;
|
|
List* subPartOids = NIL;
|
|
foreach (lc, subPartTuples) {
|
|
HeapTuple tuple = (HeapTuple)lfirst(lc);
|
|
Oid subPartOid = HeapTupleGetOid(tuple);
|
|
subPartOids = lappend_oid(subPartOids, subPartOid);
|
|
}
|
|
subPartitionOidList = lappend(subPartitionOidList, subPartOids);
|
|
}
|
|
}
|
|
}
|
|
|
|
freePartList(partitionTableList);
|
|
|
|
if (subPartitionTupleList != NULL) {
|
|
freeSubPartList(subPartitionTupleList);
|
|
}
|
|
|
|
if (!RelationIsSubPartitioned(rel)) {
|
|
foreach (cell, partitionIndexdef) {
|
|
RangePartitionindexDefState* def = (RangePartitionindexDefState*)lfirst(cell);
|
|
|
|
if (NULL != def->tablespace) {
|
|
/* use partition tablespace if user defines */
|
|
partitiontspList = lappend_oid(partitiontspList, get_tablespace_oid(def->tablespace, false));
|
|
} else {
|
|
/* use partitioned table' tablespace if user doesnt define */
|
|
partitiontspList = lappend_oid(partitiontspList, tablespaceId);
|
|
}
|
|
}
|
|
} else {
|
|
for (i = 0; i < subPartitionIndexDef->length; i++) {
|
|
List *sub = (List *)list_nth(subPartitionIndexDef, i);
|
|
partitiontspList = NULL;
|
|
foreach (cell, sub)
|
|
{
|
|
RangePartitionindexDefState* def = (RangePartitionindexDefState*)lfirst(cell);
|
|
if (NULL != def->tablespace) {
|
|
/* use partition tablespace if user defines */
|
|
partitiontspList = lappend_oid(partitiontspList, get_tablespace_oid(def->tablespace, false));
|
|
} else {
|
|
/* use partitioned table' tablespace if user doesnt define */
|
|
partitiontspList = lappend_oid(partitiontspList, tablespaceId);
|
|
}
|
|
}
|
|
|
|
subPartTspList = lappend(subPartTspList, partitiontspList);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* partitioned index need check every index partition tablespace
|
|
*/
|
|
if (!stmt->isPartitioned && OidIsValid(tablespaceId) && tablespaceId != u_sess->proc_cxt.MyDatabaseTableSpace) {
|
|
AclResult aclresult;
|
|
|
|
aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(tablespaceId));
|
|
} else if (stmt->isPartitioned) {
|
|
/* check the partition index tablepsace acl */
|
|
Oid tablespaceOid;
|
|
AclResult aclresult;
|
|
ListCell* tspcell = NULL;
|
|
|
|
if (!stmt->isGlobal) { // LOCAL partition index check
|
|
foreach (tspcell, partitiontspList) {
|
|
tablespaceOid = lfirst_oid(tspcell);
|
|
if (OidIsValid(tablespaceOid) && tablespaceOid != u_sess->proc_cxt.MyDatabaseTableSpace) {
|
|
aclresult = pg_tablespace_aclcheck(tablespaceOid, GetUserId(), ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK) {
|
|
aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(tablespaceOid));
|
|
}
|
|
}
|
|
/* In all cases disallow placing user relations in pg_global */
|
|
if (tablespaceOid == GLOBALTABLESPACE_OID) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("only shared relations can be placed in pg_global tablespace")));
|
|
}
|
|
}
|
|
} else {
|
|
if (OidIsValid(tablespaceId) && tablespaceId != u_sess->proc_cxt.MyDatabaseTableSpace) {
|
|
aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK) {
|
|
aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(tablespaceId));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check unique , if it is a unique/exclusion index,
|
|
* index column must include the partition key.
|
|
* For global partition index, we cancel this check.
|
|
*/
|
|
if (stmt->unique && !stmt->isGlobal) {
|
|
int2vector* partKey = GetPartitionKey(rel->partMap);
|
|
CheckPartitionUniqueKey(rel, partKey, stmt, numberOfAttributes);
|
|
if (RelationIsSubPartitioned(rel)) {
|
|
List *partOidList = relationGetPartitionOidList(rel);
|
|
Assert(list_length(partOidList) != 0);
|
|
Partition part = partitionOpen(rel, linitial_oid(partOidList), NoLock);
|
|
Relation partRel = partitionGetRelation(rel, part);
|
|
int2vector* subPartKey = GetPartitionKey(partRel->partMap);
|
|
CheckPartitionUniqueKey(partRel, subPartKey, stmt, numberOfAttributes);
|
|
releaseDummyRelation(&partRel);
|
|
partitionClose(rel, part, NoLock);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Force shared indexes into the pg_global tablespace. This is a bit of a
|
|
* hack but seems simpler than marking them in the BKI commands. On the
|
|
* other hand, if it's not shared, don't allow it to be placed there.
|
|
*/
|
|
if (rel->rd_rel->relisshared)
|
|
tablespaceId = GLOBALTABLESPACE_OID;
|
|
else if (tablespaceId == GLOBALTABLESPACE_OID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("only shared relations can be placed in pg_global tablespace")));
|
|
|
|
/*
|
|
* Choose the index column names.
|
|
*/
|
|
indexColNames = ChooseIndexColumnNames(allIndexParams);
|
|
|
|
/*
|
|
* Select name for index if caller didn't specify.
|
|
*/
|
|
indexRelationName = stmt->idxname;
|
|
if (indexRelationName == NULL) {
|
|
indexRelationName = ChooseIndexName(RelationGetRelationName(rel),
|
|
namespaceId,
|
|
indexColNames,
|
|
stmt->excludeOpNames,
|
|
stmt->primary,
|
|
stmt->isconstraint);
|
|
|
|
/* Build an not exists constraint name for informational. */
|
|
buildConstraintNameForInfoCnstrnt(stmt, rel, &indexRelationName, namespaceId, indexColNames);
|
|
}
|
|
|
|
/*
|
|
* look up the access method, verify it can handle the requested features
|
|
*/
|
|
accessMethodName = stmt->accessMethod;
|
|
if (RelationIsUstoreFormat(rel) &&
|
|
(strcmp(accessMethodName, "gist") == 0 || strcmp(accessMethodName, "gin") == 0)) {
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("access method \"%s\" is not supported in ustore", accessMethodName)));
|
|
}
|
|
tuple = SearchSysCache1(AMNAME, PointerGetDatum(accessMethodName));
|
|
if (!HeapTupleIsValid(tuple)) {
|
|
/*
|
|
* Hack to provide more-or-less-transparent updating of old RTREE
|
|
* indexes to GiST: if RTREE is requested and not found, use GIST.
|
|
*/
|
|
if (strcmp(accessMethodName, "rtree") == 0) {
|
|
ereport(NOTICE, (errmsg("substituting access method \"gist\" for obsolete method \"rtree\"")));
|
|
accessMethodName = "gist";
|
|
tuple = SearchSysCache1(AMNAME, PointerGetDatum(accessMethodName));
|
|
if (!HeapTupleIsValid(tuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("access method \"%s\" does not exist", accessMethodName)));
|
|
}
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("access method \"%s\" does not exist", accessMethodName)));
|
|
}
|
|
accessMethodId = HeapTupleGetOid(tuple);
|
|
accessMethodForm = (Form_pg_am)GETSTRUCT(tuple);
|
|
if (stmt->unique &&
|
|
#ifndef ENABLE_MULTIPLE_NODES
|
|
!accessMethodForm->amcanunique)
|
|
#else
|
|
(!accessMethodForm->amcanunique || accessMethodId == CBTREE_AM_OID))
|
|
#endif
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("access method \"%s\" does not support unique indexes", accessMethodName)));
|
|
|
|
if (numberOfAttributes > 1 && !accessMethodForm->amcanmulticol)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("access method \"%s\" does not support multicolumn indexes", accessMethodName)));
|
|
if (stmt->excludeOpNames && !OidIsValid(accessMethodForm->amgettuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("access method \"%s\" does not support exclusion constraints", accessMethodName)));
|
|
|
|
amcanorder = accessMethodForm->amcanorder;
|
|
amoptions = accessMethodForm->amoptions;
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
/*
|
|
* Validate predicate, if given
|
|
*/
|
|
if (stmt->whereClause)
|
|
CheckPredicate((Expr*)stmt->whereClause);
|
|
|
|
if (RelationIsUstoreFormat(rel)) {
|
|
DefElem* def = makeDefElem("storage_type", (Node*)makeString(TABLE_ACCESS_METHOD_USTORE));
|
|
if (stmt->options == NULL) {
|
|
stmt->options = list_make1(def);
|
|
} else {
|
|
ListCell* cell = NULL;
|
|
bool optionsHasStorage = false;
|
|
|
|
foreach (cell, stmt->options) {
|
|
DefElem* defElem = (DefElem*)lfirst(cell);
|
|
if (pg_strcasecmp(defElem->defname, "storage_type") == 0) {
|
|
optionsHasStorage = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!optionsHasStorage)
|
|
stmt->options = lappend(stmt->options, def);
|
|
}
|
|
}
|
|
|
|
if (indexCreateSupport.compressType || HasCompressOption(&indexCreateSupport)) {
|
|
foreach (cell, stmt->options) {
|
|
DefElem *defElem = (DefElem *)lfirst(cell);
|
|
if (pg_strcasecmp(defElem->defname, "storage_type") == 0 &&
|
|
pg_strcasecmp(defGetString(defElem), "ustore") == 0) {
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Can not use compress option in ustore index.")));
|
|
}
|
|
if (pg_strcasecmp(defElem->defname, "segment") == 0 && defGetBoolean(defElem)) {
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Can not use compress option in segment storage.")));
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Parse AM-specific options, convert to text array form, validate.
|
|
*/
|
|
reloptions = transformRelOptions((Datum)0, stmt->options, NULL, NULL, false, false);
|
|
|
|
index_relopts = (StdRdOptions *)index_reloptions(amoptions, reloptions, true);
|
|
if (index_relopts != NULL && strcmp(accessMethodName, "btree") == 0) {
|
|
/* check if the table supports to create crossbucket index */
|
|
if (is_contain_crossbucket(stmt->options) && !RELATION_HAS_BUCKET(rel) && !skip_build) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Could not create cross-bucket index for non-hashbucket table.")));
|
|
}
|
|
/* Set the page split method */
|
|
if (RelationIsIndexsplitMethodInsertpt(index_relopts)) {
|
|
indexsplitMethod = INDEXSPLIT_NO_INSERTPT;
|
|
}
|
|
pfree_ext(index_relopts);
|
|
}
|
|
|
|
/*
|
|
* Prepare arguments for index_create, primarily an IndexInfo structure.
|
|
* Note that ii_Predicate must be in implicit-AND format.
|
|
*/
|
|
indexInfo = makeNode(IndexInfo);
|
|
indexInfo->ii_NumIndexAttrs = numberOfAttributes;
|
|
indexInfo->ii_NumIndexKeyAttrs = numberOfKeyAttributes;
|
|
indexInfo->ii_Expressions = NIL; /* for now */
|
|
indexInfo->ii_ExpressionsState = NIL;
|
|
indexInfo->ii_Predicate = make_ands_implicit((Expr*)stmt->whereClause);
|
|
indexInfo->ii_PredicateState = NIL;
|
|
indexInfo->ii_ExclusionOps = NULL;
|
|
indexInfo->ii_ExclusionProcs = NULL;
|
|
indexInfo->ii_ExclusionStrats = NULL;
|
|
indexInfo->ii_Unique = stmt->unique;
|
|
/* In a concurrent build, mark it not-ready-for-inserts */
|
|
indexInfo->ii_ReadyForInserts = !concurrent;
|
|
indexInfo->ii_Concurrent = concurrent;
|
|
indexInfo->ii_BrokenHotChain = false;
|
|
indexInfo->ii_PgClassAttrId = 0;
|
|
indexInfo->ii_ParallelWorkers = 0;
|
|
|
|
typeObjectId = (Oid*)palloc(numberOfAttributes * sizeof(Oid));
|
|
collationObjectId = (Oid*)palloc(numberOfAttributes * sizeof(Oid));
|
|
classObjectId = (Oid*)palloc(numberOfAttributes * sizeof(Oid));
|
|
coloptions = (int16*)palloc(numberOfAttributes * sizeof(int16));
|
|
ComputeIndexAttrs(indexInfo,
|
|
typeObjectId,
|
|
collationObjectId,
|
|
classObjectId,
|
|
coloptions,
|
|
allIndexParams,
|
|
stmt->excludeOpNames,
|
|
relationId,
|
|
accessMethodName,
|
|
accessMethodId,
|
|
amcanorder,
|
|
stmt->isconstraint);
|
|
|
|
if (stmt->isPartitioned) {
|
|
if (PointerIsValid(indexInfo->ii_ExclusionOps)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Partitioned table does not support EXCLUDE index")));
|
|
}
|
|
if (stmt->isGlobal && PointerIsValid(indexInfo->ii_Expressions)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Global partition index does not support EXPRESSION index")));
|
|
}
|
|
if (!CheckGlobalIndexCompatible(relationId, stmt->isGlobal, indexInfo, accessMethodId)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Global and local partition index should not be on same column")));
|
|
}
|
|
}
|
|
|
|
if (stmt->crossbucket) {
|
|
if (PointerIsValid(indexInfo->ii_ExclusionOps)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("cross-bucket index does not support EXCLUDE constraints")));
|
|
}
|
|
if (PointerIsValid(indexInfo->ii_Expressions)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("cross-bucket index does not support EXPRESSION")));
|
|
}
|
|
}
|
|
|
|
#ifdef PGXC
|
|
/* Check if index is safely shippable */
|
|
if (IS_PGXC_COORDINATOR) {
|
|
List* indexAttrs = NIL;
|
|
|
|
/* Prepare call for shippability evaluation */
|
|
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) {
|
|
/*
|
|
* Expression attributes are set at 0, and do not make sense
|
|
* when comparing them to distribution columns, so bypass.
|
|
*/
|
|
if (indexInfo->ii_KeyAttrNumbers[i] > 0)
|
|
indexAttrs = lappend_int(indexAttrs, indexInfo->ii_KeyAttrNumbers[i]);
|
|
}
|
|
|
|
/* Finalize check */
|
|
if (!pgxc_check_index_shippability(GetRelationLocInfo(relationId),
|
|
stmt->primary,
|
|
stmt->unique,
|
|
stmt->excludeOpNames != NULL,
|
|
indexAttrs,
|
|
indexInfo->ii_Expressions))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Cannot create index whose evaluation cannot be "
|
|
"enforced to remote nodes")));
|
|
}
|
|
#endif
|
|
|
|
#ifndef ENABLE_MULTIPLE_NODES
|
|
if (RelationIsCUFormat(rel) && (stmt->primary || stmt->unique)) {
|
|
/* If it is a unique index, move data on delta to CU. */
|
|
MoveDeltaDataToCU(rel);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Extra checks when creating a PRIMARY KEY index.
|
|
* If be informational constraint, we are not to set not null in pg_attribute.
|
|
*/
|
|
if (stmt->primary && !stmt->internal_flag)
|
|
index_check_primary_key(rel, indexInfo, is_alter_table);
|
|
|
|
/*
|
|
* Report index creation if appropriate (delay this till after most of the
|
|
* error checks)
|
|
*/
|
|
if (stmt->isconstraint && !quiet) {
|
|
const char* constraint_type = NULL;
|
|
|
|
if (stmt->primary)
|
|
constraint_type = "PRIMARY KEY";
|
|
else if (stmt->unique)
|
|
constraint_type = "UNIQUE";
|
|
else if (stmt->excludeOpNames != NIL)
|
|
constraint_type = "EXCLUDE";
|
|
else {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("unknown constraint type")));
|
|
constraint_type = NULL; /* keep compiler quiet */
|
|
}
|
|
|
|
/*
|
|
* @hdfs
|
|
* Make a speciallized NOTICE msg for infotmatioanl constraint.
|
|
*/
|
|
if (stmt->internal_flag) {
|
|
ereport(NOTICE,
|
|
(errmsg("%s %s will create constraint \"%s\" for foreign table \"%s\"",
|
|
is_alter_table ? "ALTER FOREIGN TABLE / ADD" : "CREATE FOREIGN TABLE /",
|
|
constraint_type,
|
|
indexRelationName,
|
|
RelationGetRelationName(rel))));
|
|
} else {
|
|
ereport(NOTICE,
|
|
(errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
|
|
is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
|
|
constraint_type,
|
|
indexRelationName,
|
|
RelationGetRelationName(rel))));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A valid stmt->oldNode implies that we already have a built form of the
|
|
* index. The caller should also decline any index build.
|
|
*
|
|
* A valid stmt->oldPSortOid implies that we already have a built form
|
|
* of the psort index.
|
|
*/
|
|
if ((OidIsValid(stmt->oldNode) && !(skip_build && !concurrent) &&
|
|
!u_sess->attr.attr_sql.enable_cluster_resize) ||
|
|
(OidIsValid(stmt->oldPSortOid) && !OidIsValid(stmt->oldNode))) {
|
|
ereport(defence_errlevel(),
|
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
|
errmsg("If we can not reuse the index, we can not skip the build of index and not build concurrent "
|
|
"index."),
|
|
errhint("Please reindex the index.")));
|
|
}
|
|
|
|
IndexCreateExtraArgs extra;
|
|
SetIndexCreateExtraArgs(&extra, stmt->oldPSortOid, stmt->isPartitioned, stmt->isGlobal, stmt->crossbucket);
|
|
|
|
if (stmt->internal_flag) {
|
|
#ifdef ENABLE_MOT
|
|
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) {
|
|
Oid relationId = RelationGetRelid(rel);
|
|
ForeignTable *ftbl = GetForeignTable(relationId);
|
|
ForeignServer *server = GetForeignServer(ftbl->serverid);
|
|
if (isMOTTableFromSrvName(server->servername)) {
|
|
bool idxNameChanged = false;
|
|
indexRelationId = index_create(rel,
|
|
indexRelationName,
|
|
indexRelationId,
|
|
stmt->oldNode,
|
|
indexInfo,
|
|
indexColNames,
|
|
accessMethodId,
|
|
tablespaceId,
|
|
collationObjectId,
|
|
classObjectId,
|
|
coloptions,
|
|
reloptions,
|
|
stmt->primary,
|
|
stmt->isconstraint,
|
|
stmt->deferrable,
|
|
stmt->initdeferred,
|
|
g_instance.attr.attr_common.allowSystemTableMods,
|
|
true,
|
|
concurrent,
|
|
&extra);
|
|
heap_close(rel, NoLock);
|
|
|
|
if (stmt->idxname == NULL) {
|
|
stmt->idxname = indexRelationName;
|
|
idxNameChanged = true;
|
|
}
|
|
|
|
CreateForeignIndex(stmt, indexRelationId);
|
|
|
|
if (idxNameChanged) {
|
|
stmt->idxname = NULL;
|
|
}
|
|
return indexRelationId;
|
|
}
|
|
|
|
return buildInformationalConstraint(stmt, indexRelationId, indexRelationName, rel, indexInfo, namespaceId);
|
|
}
|
|
#endif
|
|
return buildInformationalConstraint(stmt, indexRelationId, indexRelationName, rel, indexInfo, namespaceId);
|
|
}
|
|
|
|
/* workload client manager */
|
|
if (IS_PGXC_COORDINATOR && ENABLE_WORKLOAD_CONTROL) {
|
|
if (!stmt->skip_mem_check && !skip_build)
|
|
EstIdxMemInfo(rel, stmt->relation, &indexInfo->ii_desc, indexInfo, accessMethodName);
|
|
WLMInitQueryPlan((QueryDesc*)&indexInfo->ii_desc, false);
|
|
dywlm_client_manager((QueryDesc*)&indexInfo->ii_desc, false);
|
|
if (!stmt->skip_mem_check && !skip_build)
|
|
AdjustIdxMemInfo(&stmt->memUsage, &indexInfo->ii_desc);
|
|
} else if (IS_PGXC_DATANODE && stmt->memUsage.work_mem > 0) {
|
|
indexInfo->ii_desc.query_mem[0] = stmt->memUsage.work_mem;
|
|
indexInfo->ii_desc.query_mem[1] = stmt->memUsage.max_mem;
|
|
}
|
|
|
|
/*
|
|
* Make the catalog entries for the index, including constraints. Then, if
|
|
* not skip_build || concurrent, actually build the index.
|
|
*/
|
|
indexRelationId = index_create(rel,
|
|
indexRelationName,
|
|
indexRelationId,
|
|
stmt->oldNode,
|
|
indexInfo,
|
|
indexColNames,
|
|
accessMethodId,
|
|
tablespaceId,
|
|
collationObjectId,
|
|
classObjectId,
|
|
coloptions,
|
|
reloptions,
|
|
stmt->primary,
|
|
stmt->isconstraint,
|
|
stmt->deferrable,
|
|
stmt->initdeferred,
|
|
(g_instance.attr.attr_common.allowSystemTableMods || u_sess->attr.attr_common.IsInplaceUpgrade),
|
|
skip_build || concurrent,
|
|
concurrent,
|
|
&extra,
|
|
false,
|
|
indexsplitMethod);
|
|
/* Add any requested comment */
|
|
if (stmt->idxcomment != NULL)
|
|
CreateComments(indexRelationId, RelationRelationId, 0, stmt->idxcomment);
|
|
|
|
/* create the LOCAL index partition */
|
|
if (stmt->isPartitioned && !stmt->isGlobal) {
|
|
Relation partitionedIndex = index_open(indexRelationId, AccessExclusiveLock);
|
|
|
|
if (rel->partMap->type == PART_TYPE_RANGE ||
|
|
rel->partMap->type == PART_TYPE_INTERVAL ||
|
|
rel->partMap->type == PART_TYPE_HASH ||
|
|
rel->partMap->type == PART_TYPE_LIST) {
|
|
MemoryContext partitionIndexMemContext = AllocSetContextCreate(CurrentMemoryContext,
|
|
"partition index create memory context",
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
|
MemoryContext oldMemContext = CurrentMemoryContext;
|
|
|
|
ListCell* tspcell = NULL;
|
|
ListCell* indexcell = NULL;
|
|
ListCell* partitioncell = NULL;
|
|
Oid partitionid = InvalidOid;
|
|
Oid partIndexFileNode = InvalidOid;
|
|
PartIndexCreateExtraArgs partExtra;
|
|
int count = 0;
|
|
char* indexname = NULL;
|
|
Partition partition = NULL;
|
|
Oid partitiontspid = InvalidOid;
|
|
RangePartitionindexDefState* indexdef = NULL;
|
|
List* partitionidlist = NIL;
|
|
Oid toastid = InvalidOid;
|
|
Relation pg_partition_rel = NULL;
|
|
int indexnum = 0;
|
|
indexInfo->ii_BrokenHotChain = false;
|
|
partExtra.crossbucket = stmt->crossbucket;
|
|
|
|
if (!RelationIsSubPartitioned(rel)) {
|
|
if (!PointerIsValid(partitionIndexdef)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("fail to get index info when create index partition")));
|
|
}
|
|
|
|
partitionidlist = relationGetPartitionOidList(rel);
|
|
} else {
|
|
if (!PointerIsValid(subPartitionIndexDef)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("fail to get index info when create index partition")));
|
|
}
|
|
}
|
|
pg_partition_rel = heap_open(PartitionRelationId, RowExclusiveLock);
|
|
|
|
// switch to partition memory context
|
|
oldMemContext = MemoryContextSwitchTo(partitionIndexMemContext);
|
|
|
|
if (!RelationIsSubPartitioned(rel)) {
|
|
forthree(tspcell, partitiontspList, indexcell, partitionIndexdef, partitioncell, partitionidlist)
|
|
{
|
|
partitiontspid = lfirst_oid(tspcell);
|
|
indexdef = (RangePartitionindexDefState*)lfirst(indexcell);
|
|
partitionid = lfirst_oid(partitioncell);
|
|
|
|
/* reset the extra arguments data. */
|
|
partExtra.existingPSortOid = InvalidOid;
|
|
|
|
if (PointerIsValid(stmt->partIndexOldNodes)) {
|
|
Assert(stmt->partIndexOldPSortOid);
|
|
partIndexFileNode = list_nth_oid(stmt->partIndexOldNodes, count);
|
|
partExtra.existingPSortOid = list_nth_oid(stmt->partIndexOldPSortOid, count);
|
|
count++;
|
|
}
|
|
if (u_sess->attr.attr_sql.enable_cluster_resize && stmt->partIndexUsable &&
|
|
!stmt->partIndexUsable[indexnum++]) {
|
|
ereport(LOG, (errmsg("In redistribution, the %dth partition of source index is unusable, "
|
|
"the tmp table will skip.", indexnum)));
|
|
continue;
|
|
}
|
|
|
|
partition = partitionOpen(rel, partitionid, ShareLock);
|
|
|
|
if (PointerIsValid(indexdef->name)) {
|
|
Oid indexid = InvalidOid;
|
|
|
|
indexid = GetSysCacheOid3(PARTPARTOID, PointerGetDatum(indexdef->name),
|
|
CharGetDatum(PART_OBJ_TYPE_INDEX_PARTITION),
|
|
ObjectIdGetDatum(indexRelationId));
|
|
if (OidIsValid(indexid)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("index partition with name \"%s\" already exists", indexdef->name)));
|
|
}
|
|
}
|
|
|
|
/* get index partition's name */
|
|
indexname =
|
|
(NULL != indexdef->name)
|
|
? indexdef->name
|
|
: ChoosePartitionIndexName(
|
|
PartitionGetPartitionName(partition), indexRelationId, indexColNames, false, false);
|
|
|
|
/* create index partition */
|
|
(void)partition_index_create(indexname,
|
|
partIndexFileNode,
|
|
partition,
|
|
partitiontspid,
|
|
partitionedIndex,
|
|
rel,
|
|
pg_partition_rel,
|
|
indexInfo,
|
|
indexColNames,
|
|
reloptions,
|
|
skip_build,
|
|
&partExtra);
|
|
partitionClose(rel, partition, NoLock);
|
|
|
|
// reset memory context
|
|
MemoryContextReset(partitionIndexMemContext);
|
|
}
|
|
} else {
|
|
ListCell *subTspCell = NULL;
|
|
ListCell *subIndexCell = NULL;
|
|
ListCell *subPartCell = NULL;
|
|
int partIdx = 0;
|
|
forthree(subTspCell, subPartTspList, subIndexCell, subPartitionIndexDef, subPartCell,
|
|
subPartitionOidList)
|
|
{
|
|
partitiontspList = (List *)lfirst(subTspCell);
|
|
partitionIndexdef = (List *)lfirst(subIndexCell);
|
|
List *subpartitionidlist = (List *)lfirst(subPartCell);
|
|
|
|
Oid partid = list_nth_oid(partitionOidList, partIdx++);
|
|
Partition p = partitionOpen(rel, partid, ShareLock);
|
|
Relation partRel = partitionGetRelation(rel, p);
|
|
|
|
forthree(tspcell, partitiontspList, indexcell, partitionIndexdef, partitioncell, subpartitionidlist)
|
|
{
|
|
Oid subPartitionOid = lfirst_oid(partitioncell);
|
|
partitiontspid = lfirst_oid(tspcell);
|
|
indexdef = (RangePartitionindexDefState*)lfirst(indexcell);
|
|
|
|
/* reset the extra arguments data. */
|
|
partExtra.existingPSortOid = InvalidOid;
|
|
|
|
if (PointerIsValid(stmt->partIndexOldNodes)) {
|
|
Assert(stmt->partIndexOldPSortOid);
|
|
partIndexFileNode = list_nth_oid(stmt->partIndexOldNodes, count);
|
|
partExtra.existingPSortOid = list_nth_oid(stmt->partIndexOldPSortOid, count);
|
|
count++;
|
|
}
|
|
|
|
partition = partitionOpen(partRel, subPartitionOid, ShareLock);
|
|
|
|
if (PointerIsValid(indexdef->name)) {
|
|
Oid indexid = InvalidOid;
|
|
|
|
indexid = GetSysCacheOid3(PARTPARTOID, PointerGetDatum(indexdef->name),
|
|
CharGetDatum(PART_OBJ_TYPE_INDEX_PARTITION),
|
|
ObjectIdGetDatum(indexRelationId));
|
|
if (OidIsValid(indexid)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("index subpartition with name \"%s\" already exists", indexdef->name)));
|
|
}
|
|
}
|
|
|
|
/* get index partition's name */
|
|
indexname = (NULL != indexdef->name)
|
|
? indexdef->name
|
|
: ChoosePartitionIndexName(PartitionGetPartitionName(partition),
|
|
indexRelationId, indexColNames, false, false);
|
|
|
|
/* create index partition */
|
|
(void)partition_index_create(indexname,
|
|
partIndexFileNode,
|
|
partition,
|
|
partitiontspid,
|
|
partitionedIndex,
|
|
partRel,
|
|
pg_partition_rel,
|
|
indexInfo,
|
|
indexColNames,
|
|
reloptions,
|
|
skip_build,
|
|
&partExtra);
|
|
partitionClose(partRel, partition, NoLock);
|
|
|
|
// reset memory context
|
|
MemoryContextReset(partitionIndexMemContext);
|
|
}
|
|
|
|
releaseDummyRelation(&partRel);
|
|
partitionClose(rel, p, ShareLock);
|
|
}
|
|
if (subPartitionOidList != NULL) {
|
|
ListCell* lc = NULL;
|
|
foreach(lc, subPartitionOidList) {
|
|
List* tmpList = (List*)lfirst(lc);
|
|
list_free_ext(tmpList);
|
|
}
|
|
list_free_ext(subPartitionOidList);
|
|
}
|
|
if (partitionOidList != NULL) {
|
|
list_free_ext(partitionOidList);
|
|
}
|
|
}
|
|
|
|
// delete memory context
|
|
(void)MemoryContextSwitchTo(oldMemContext);
|
|
MemoryContextDelete(partitionIndexMemContext);
|
|
|
|
if (rel->rd_rel->relkind == RELKIND_TOASTVALUE) {
|
|
toastid = RelationGetRelid(partitionedIndex);
|
|
}
|
|
/* update heap pg_class rows */
|
|
index_update_stats(rel, true, stmt->primary, toastid, InvalidOid, -1);
|
|
/* update index pg_class rows */
|
|
index_update_stats(partitionedIndex, false, false, InvalidOid, InvalidOid, -1);
|
|
|
|
heap_close(pg_partition_rel, RowExclusiveLock);
|
|
} else {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("unsupport partitioned strategy")));
|
|
}
|
|
|
|
heap_close(partitionedIndex, NoLock);
|
|
heap_close(rel, NoLock);
|
|
|
|
return indexRelationId;
|
|
}
|
|
|
|
if (!concurrent) {
|
|
/* Close the heap and we're done, in the non-concurrent case */
|
|
heap_close(rel, NoLock);
|
|
return indexRelationId;
|
|
}
|
|
|
|
// cstore relation doesn't support concurrent INDEX now.
|
|
if (OidIsValid(rel->rd_rel->relcudescrelid)) {
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("column store table does not support concurrent INDEX yet"),
|
|
errdetail("The feature is not currently supported")));
|
|
}
|
|
|
|
/* save lockrelid and locktag for below, then close rel */
|
|
heaprelid = rel->rd_lockInfo.lockRelId;
|
|
SET_LOCKTAG_RELATION(heaplocktag, heaprelid.dbId, heaprelid.relId);
|
|
heap_close(rel, NoLock);
|
|
|
|
/*
|
|
* For a concurrent build, it's important to make the catalog entries
|
|
* visible to other transactions before we start to build the index. That
|
|
* will prevent them from making incompatible HOT updates. The new index
|
|
* will be marked not indisready and not indisvalid, so that no one else
|
|
* tries to either insert into it or use it for queries.
|
|
*
|
|
* We must commit our current transaction so that the index becomes
|
|
* visible; then start another. Note that all the data structures we just
|
|
* built are lost in the commit. The only data we keep past here are the
|
|
* relation IDs.
|
|
*
|
|
* Before committing, get a session-level lock on the table, to ensure
|
|
* that neither it nor the index can be dropped before we finish. This
|
|
* cannot block, even if someone else is waiting for access, because we
|
|
* already have the same lock within our transaction.
|
|
*
|
|
* Note: we don't currently bother with a session lock on the index,
|
|
* because there are no operations that could change its state while we
|
|
* hold lock on the parent table. This might need to change later.
|
|
*/
|
|
LockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
|
|
|
|
PopActiveSnapshot();
|
|
CommitTransactionCommand();
|
|
StartTransactionCommand();
|
|
|
|
/*
|
|
* Phase 2 of concurrent index build (see comments for validate_index()
|
|
* for an overview of how this works)
|
|
*
|
|
* Now we must wait until no running transaction could have the table open
|
|
* with the old list of indexes. To do this, inquire which xacts
|
|
* currently would conflict with ShareLock on the table -- ie, which ones
|
|
* have a lock that permits writing the table. Then wait for each of
|
|
* these xacts to commit or abort. Note we do not need to worry about
|
|
* xacts that open the table for writing after this point; they will see
|
|
* the new index when they open it.
|
|
*
|
|
* Note: the reason we use actual lock acquisition here, rather than just
|
|
* checking the ProcArray and sleeping, is that deadlock is possible if
|
|
* one of the transactions in question is blocked trying to acquire an
|
|
* exclusive lock on our table. The lock code will detect deadlock and
|
|
* error out properly.
|
|
*
|
|
* Note: GetLockConflicts() never reports our own xid, hence we need not
|
|
* check for that. Also, prepared xacts are not reported, which is fine
|
|
* since they certainly aren't going to do anything more.
|
|
*/
|
|
old_lockholders = GetLockConflicts(&heaplocktag, ShareLock);
|
|
|
|
while (VirtualTransactionIdIsValid(*old_lockholders)) {
|
|
(void)VirtualXactLock(*old_lockholders, true);
|
|
old_lockholders++;
|
|
}
|
|
|
|
/*
|
|
* At this moment we are sure that there are no transactions with the
|
|
* table open for write that don't have this new index in their list of
|
|
* indexes. We have waited out all the existing transactions and any new
|
|
* transaction will have the new index in its list, but the index is still
|
|
* marked as "not-ready-for-inserts". The index is consulted while
|
|
* deciding HOT-safety though. This arrangement ensures that no new HOT
|
|
* chains can be created where the new tuple and the old tuple in the
|
|
* chain have different index keys.
|
|
*
|
|
* We now take a new snapshot, and build the index using all tuples that
|
|
* are visible in this snapshot. We can be sure that any HOT updates to
|
|
* these tuples will be compatible with the index, since any updates made
|
|
* by transactions that didn't know about the index are now committed or
|
|
* rolled back. Thus, each visible tuple is either the end of its
|
|
* HOT-chain or the extension of the chain is HOT-safe for this index.
|
|
*/
|
|
/* Open and lock the parent heap relation */
|
|
rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
|
|
|
|
/* And the target index relation */
|
|
indexRelation = index_open(indexRelationId, RowExclusiveLock);
|
|
|
|
/* Set ActiveSnapshot since functions in the indexes may need it */
|
|
PushActiveSnapshot(GetTransactionSnapshot());
|
|
|
|
/* We have to re-build the IndexInfo struct, since it was lost in commit */
|
|
indexInfo = BuildIndexInfo(indexRelation);
|
|
Assert(!indexInfo->ii_ReadyForInserts);
|
|
indexInfo->ii_Concurrent = true;
|
|
indexInfo->ii_BrokenHotChain = false;
|
|
|
|
u_sess->attr.attr_sql.create_index_concurrently = true;
|
|
/* Now build the index */
|
|
index_build(rel, NULL, indexRelation, NULL, indexInfo, stmt->primary, false, INDEX_CREATE_NONE_PARTITION);
|
|
|
|
/* Close both the relations, but keep the locks */
|
|
heap_close(rel, NoLock);
|
|
index_close(indexRelation, NoLock);
|
|
|
|
/*
|
|
* Update the pg_index row to mark the index as ready for inserts. Once we
|
|
* commit this transaction, any new transactions that open the table must
|
|
* insert new entries into the index for insertions and non-HOT updates.
|
|
*/
|
|
index_set_state_flags(indexRelationId, INDEX_CREATE_SET_READY);
|
|
|
|
/* we can do away with our snapshot */
|
|
PopActiveSnapshot();
|
|
|
|
/*
|
|
* Commit this transaction to make the indisready update visible.
|
|
*/
|
|
CommitTransactionCommand();
|
|
StartTransactionCommand();
|
|
|
|
/*
|
|
* Phase 3 of concurrent index build
|
|
*
|
|
* We once again wait until no transaction can have the table open with
|
|
* the index marked as read-only for updates.
|
|
*/
|
|
old_lockholders = GetLockConflicts(&heaplocktag, ShareLock);
|
|
|
|
while (VirtualTransactionIdIsValid(*old_lockholders)) {
|
|
(void)VirtualXactLock(*old_lockholders, true);
|
|
old_lockholders++;
|
|
}
|
|
|
|
/*
|
|
* Now take the "reference snapshot" that will be used by validate_index()
|
|
* to filter candidate tuples. Beware! There might still be snapshots in
|
|
* use that treat some transaction as in-progress that our reference
|
|
* snapshot treats as committed. If such a recently-committed transaction
|
|
* deleted tuples in the table, we will not include them in the index; yet
|
|
* those transactions which see the deleting one as still-in-progress will
|
|
* expect such tuples to be there once we mark the index as valid.
|
|
*
|
|
* We solve this by waiting for all endangered transactions to exit before
|
|
* we mark the index as valid.
|
|
*
|
|
* We also set ActiveSnapshot to this snap, since functions in indexes may
|
|
* need a snapshot.
|
|
*/
|
|
snapshot = RegisterSnapshot(GetTransactionSnapshot());
|
|
PushActiveSnapshot(snapshot);
|
|
|
|
/*
|
|
* Scan the index and the heap, insert any missing index entries.
|
|
*/
|
|
validate_index(relationId, indexRelationId, snapshot);
|
|
|
|
/*
|
|
* Drop the reference snapshot. We must do this before waiting out other
|
|
* snapshot holders, else we will deadlock against other processes also
|
|
* doing CREATE INDEX CONCURRENTLY, which would see our snapshot as one
|
|
* they must wait for. But first, save the snapshot's xmin to use as
|
|
* limitXmin for GetCurrentVirtualXIDs().
|
|
*/
|
|
limitXmin = snapshot->xmin;
|
|
|
|
PopActiveSnapshot();
|
|
UnregisterSnapshot(snapshot);
|
|
|
|
/*
|
|
* The snapshot subsystem could still contain registered snapshots that
|
|
* are holding back our process's advertised xmin; in particular, if
|
|
* default_transaction_isolation = serializable, there is a transaction
|
|
* snapshot that is still active. The CatalogSnapshot is likewise a
|
|
* hazard. To ensure no deadlocks, we must commit and start yet another
|
|
* transaction, and do our wait before any snapshot has been taken in it.
|
|
*/
|
|
CommitTransactionCommand();
|
|
StartTransactionCommand();
|
|
|
|
/*
|
|
* The index is now valid in the sense that it contains all currently
|
|
* interesting tuples. But since it might not contain tuples deleted just
|
|
* before the reference snap was taken, we have to wait out any
|
|
* transactions that might have older snapshots. Obtain a list of VXIDs
|
|
* of such transactions, and wait for them individually.
|
|
*
|
|
* We can exclude any running transactions that have xmin > the xmin of
|
|
* our reference snapshot; their oldest snapshot must be newer than ours.
|
|
* We can also exclude any transactions that have xmin = zero, since they
|
|
* evidently have no live snapshot at all (and any one they might be in
|
|
* process of taking is certainly newer than ours). Transactions in other
|
|
* DBs can be ignored too, since they'll never even be able to see this
|
|
* index.
|
|
*
|
|
* We can also exclude autovacuum processes and processes running manual
|
|
* lazy VACUUMs, because they won't be fazed by missing index entries
|
|
* either. (Manual ANALYZEs, however, can't be excluded because they
|
|
* might be within transactions that are going to do arbitrary operations
|
|
* later.)
|
|
*
|
|
* Also, GetCurrentVirtualXIDs never reports our own vxid, so we need not
|
|
* check for that.
|
|
*
|
|
* If a process goes idle-in-transaction with xmin zero, we do not need to
|
|
* wait for it anymore, per the above argument. We do not have the
|
|
* infrastructure right now to stop waiting if that happens, but we can at
|
|
* least avoid the folly of waiting when it is idle at the time we would
|
|
* begin to wait. We do this by repeatedly rechecking the output of
|
|
* GetCurrentVirtualXIDs. If, during any iteration, a particular vxid
|
|
* doesn't show up in the output, we know we can forget about it.
|
|
*/
|
|
old_snapshots =
|
|
GetCurrentVirtualXIDs(limitXmin, true, false, PROC_IS_AUTOVACUUM | PROC_IN_VACUUM, &n_old_snapshots);
|
|
|
|
for (i = 0; i < n_old_snapshots; i++) {
|
|
if (!VirtualTransactionIdIsValid(old_snapshots[i]))
|
|
continue; /* found uninteresting in previous cycle */
|
|
|
|
if (i > 0) {
|
|
/* see if anything's changed ... */
|
|
VirtualTransactionId* newer_snapshots = NULL;
|
|
int n_newer_snapshots;
|
|
int j;
|
|
int k;
|
|
|
|
newer_snapshots = GetCurrentVirtualXIDs(
|
|
limitXmin, true, false, PROC_IS_AUTOVACUUM | PROC_IN_VACUUM, &n_newer_snapshots);
|
|
for (j = i; j < n_old_snapshots; j++) {
|
|
if (!VirtualTransactionIdIsValid(old_snapshots[j]))
|
|
continue; /* found uninteresting in previous cycle */
|
|
for (k = 0; k < n_newer_snapshots; k++) {
|
|
if (VirtualTransactionIdEquals(old_snapshots[j], newer_snapshots[k]))
|
|
break;
|
|
}
|
|
if (k >= n_newer_snapshots) /* not there anymore */
|
|
SetInvalidVirtualTransactionId(old_snapshots[j]);
|
|
}
|
|
pfree_ext(newer_snapshots);
|
|
}
|
|
|
|
if (VirtualTransactionIdIsValid(old_snapshots[i]))
|
|
(void)VirtualXactLock(old_snapshots[i], true);
|
|
}
|
|
|
|
if (IS_PGXC_COORDINATOR) {
|
|
/*
|
|
* Last thing to do is release the session-level lock on the parent table.
|
|
*/
|
|
UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
|
|
|
|
return indexRelationId;
|
|
}
|
|
|
|
/*
|
|
* Index can now be marked valid -- update its pg_index entry
|
|
*/
|
|
index_set_state_flags(indexRelationId, INDEX_CREATE_SET_VALID);
|
|
|
|
/*
|
|
* The pg_index update will cause backends (including this one) to update
|
|
* relcache entries for the index itself, but we should also send a
|
|
* relcache inval on the parent table to force replanning of cached plans.
|
|
* Otherwise existing sessions might fail to use the new index where it
|
|
* would be useful. (Note that our earlier commits did not create reasons
|
|
* to replan; so relcache flush on the index itself was sufficient.)
|
|
*/
|
|
CacheInvalidateRelcacheByRelid(heaprelid.relId);
|
|
|
|
/*
|
|
* Last thing to do is release the session-level lock on the parent table.
|
|
*/
|
|
UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
|
|
|
|
return indexRelationId;
|
|
}
|
|
|
|
/*
|
|
* @@GaussDB@@
|
|
* Target : data partition
|
|
* Brief :
|
|
* Description :
|
|
* Input :
|
|
* Output :
|
|
* Return :
|
|
* Notes :
|
|
*/
|
|
static bool columnIsExist(Relation rel, const Form_pg_attribute attTup, const List* indexParams)
|
|
{
|
|
char* attname = NameStr(attTup->attname); /* get the column name */
|
|
ListCell* cell = NULL;
|
|
|
|
/* is the name exist in indexParams */
|
|
foreach (cell, indexParams) {
|
|
IndexElem* ielem = (IndexElem*)lfirst(cell);
|
|
|
|
if (NULL == ielem->name) {
|
|
Assert(NULL != ielem->expr);
|
|
/* it is a expression */
|
|
continue;
|
|
}
|
|
|
|
if (0 == strcmp(attname, ielem->name)) {
|
|
/* check the collation */
|
|
if (NULL == ielem->collation) {
|
|
return true;
|
|
} else {
|
|
/* collation is same,return true ,else return false */
|
|
HeapTuple tuple;
|
|
Value* colValue = (Value*)linitial(ielem->collation);
|
|
char* colName = colValue->val.str;
|
|
Oid colId = CollationGetCollid(colName);
|
|
Oid attColId = InvalidOid;
|
|
|
|
if (0 == colId) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("collation \"%s\" does not exist", colName)));
|
|
}
|
|
|
|
/* find the column type 's collation */
|
|
tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attTup->atttypid));
|
|
if (!HeapTupleIsValid(tuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
|
|
errmsg("cache lookup failed for type %u", attTup->atttypid)));
|
|
|
|
attColId = ((Form_pg_type)GETSTRUCT(tuple))->typcollation;
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
if (attColId == 0) {
|
|
/* type does not have a collation */
|
|
return true;
|
|
} else if (attColId == colId) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* CheckMutability
|
|
* Test whether given expression is mutable
|
|
*/
|
|
bool CheckMutability(Expr* expr)
|
|
{
|
|
/*
|
|
* First run the expression through the planner. This has a couple of
|
|
* important consequences. First, function default arguments will get
|
|
* inserted, which may affect volatility (consider "default now()").
|
|
* Second, inline-able functions will get inlined, which may allow us to
|
|
* conclude that the function is really less volatile than it's marked. As
|
|
* an example, polymorphic functions must be marked with the most volatile
|
|
* behavior that they have for any input type, but once we inline the
|
|
* function we may be able to conclude that it's not so volatile for the
|
|
* particular input type we're dealing with.
|
|
*
|
|
* We assume here that expression_planner() won't scribble on its input.
|
|
*/
|
|
expr = expression_planner(expr);
|
|
|
|
/* Now we can search for non-immutable functions */
|
|
return contain_mutable_functions((Node*)expr);
|
|
}
|
|
|
|
/*
|
|
* CheckPredicate
|
|
* Checks that the given partial-index predicate is valid.
|
|
*
|
|
* This used to also constrain the form of the predicate to forms that
|
|
* indxpath.c could do something with. However, that seems overly
|
|
* restrictive. One useful application of partial indexes is to apply
|
|
* a UNIQUE constraint across a subset of a table, and in that scenario
|
|
* any evaluatable predicate will work. So accept any predicate here
|
|
* (except ones requiring a plan), and let indxpath.c fend for itself.
|
|
*/
|
|
void CheckPredicate(Expr* predicate)
|
|
{
|
|
/*
|
|
* We don't currently support generation of an actual query plan for a
|
|
* predicate, only simple scalar expressions; hence these restrictions.
|
|
*/
|
|
if (contain_subplans((Node*)predicate))
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot use subquery in index predicate")));
|
|
if (contain_agg_clause((Node*)predicate))
|
|
ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), errmsg("cannot use aggregate in index predicate")));
|
|
|
|
/*
|
|
* A predicate using mutable functions is probably wrong, for the same
|
|
* reasons that we don't allow an index expression to use one.
|
|
*/
|
|
if (CheckMutability(predicate))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("functions in index predicate must be marked IMMUTABLE")));
|
|
}
|
|
|
|
/*
|
|
* Compute per-index-column information, including indexed column numbers
|
|
* or index expressions, opclasses, and indoptions.
|
|
*/
|
|
void ComputeIndexAttrs(IndexInfo* indexInfo, Oid* typeOidP, Oid* collationOidP, Oid* classOidP,
|
|
int16* colOptionP, List* attList, /* list of IndexElem's */
|
|
List* exclusionOpNames, Oid relId, const char* accessMethodName, Oid accessMethodId, bool amcanorder, bool isconstraint)
|
|
{
|
|
ListCell* nextExclOp = NULL;
|
|
ListCell* lc = NULL;
|
|
int attn;
|
|
int nkeycols = indexInfo->ii_NumIndexKeyAttrs;
|
|
|
|
/* Allocate space for exclusion operator info, if needed */
|
|
if (exclusionOpNames != NULL) {
|
|
Assert(list_length(exclusionOpNames) == nkeycols);
|
|
indexInfo->ii_ExclusionOps = (Oid*)palloc(sizeof(Oid) * nkeycols);
|
|
indexInfo->ii_ExclusionProcs = (Oid*)palloc(sizeof(Oid) * nkeycols);
|
|
indexInfo->ii_ExclusionStrats = (uint16*)palloc(sizeof(uint16) * nkeycols);
|
|
nextExclOp = list_head(exclusionOpNames);
|
|
} else
|
|
nextExclOp = NULL;
|
|
|
|
/*
|
|
* process attributeList
|
|
*/
|
|
attn = 0;
|
|
foreach (lc, attList) {
|
|
IndexElem* attribute = (IndexElem*)lfirst(lc);
|
|
Oid atttype;
|
|
Oid attcollation;
|
|
|
|
/*
|
|
* Process the column-or-expression to be indexed.
|
|
*/
|
|
if (attribute->name != NULL) {
|
|
/* Simple index attribute */
|
|
HeapTuple atttuple;
|
|
Form_pg_attribute attform;
|
|
|
|
Assert(attribute->expr == NULL);
|
|
atttuple = SearchSysCacheAttName(relId, attribute->name);
|
|
if (!HeapTupleIsValid(atttuple)) {
|
|
/* difference in error message spellings is historical */
|
|
if (isconstraint)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
|
errmsg("column \"%s\" named in key does not exist", attribute->name)));
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" does not exist", attribute->name)));
|
|
}
|
|
attform = (Form_pg_attribute)GETSTRUCT(atttuple);
|
|
indexInfo->ii_KeyAttrNumbers[attn] = attform->attnum;
|
|
atttype = attform->atttypid;
|
|
attcollation = attform->attcollation;
|
|
ReleaseSysCache(atttuple);
|
|
} else {
|
|
/* Index expression */
|
|
Node* expr = attribute->expr;
|
|
|
|
Assert(expr != NULL);
|
|
if (attn >= nkeycols) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("expressions are not supported in included columns")));
|
|
}
|
|
atttype = exprType(expr);
|
|
attcollation = exprCollation(expr);
|
|
|
|
/*
|
|
* Strip any top-level COLLATE clause. This ensures that we treat
|
|
* "x COLLATE y" and "(x COLLATE y)" alike.
|
|
*/
|
|
while (IsA(expr, CollateExpr))
|
|
expr = (Node*)((CollateExpr*)expr)->arg;
|
|
|
|
if (IsA(expr, Var) && ((Var*)expr)->varattno != InvalidAttrNumber) {
|
|
/*
|
|
* User wrote "(column)" or "(column COLLATE something)".
|
|
* Treat it like simple attribute anyway.
|
|
*/
|
|
indexInfo->ii_KeyAttrNumbers[attn] = ((Var*)expr)->varattno;
|
|
} else {
|
|
indexInfo->ii_KeyAttrNumbers[attn] = 0; /* marks expression */
|
|
indexInfo->ii_Expressions = lappend(indexInfo->ii_Expressions, expr);
|
|
|
|
/*
|
|
* We don't currently support generation of an actual query
|
|
* plan for an index expression, only simple scalar
|
|
* expressions; hence these restrictions.
|
|
*/
|
|
if (contain_subplans(expr))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot use subquery in index expression")));
|
|
if (contain_agg_clause(expr))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_GROUPING_ERROR), errmsg("cannot use aggregate function in index expression")));
|
|
|
|
/*
|
|
* A expression using mutable functions is probably wrong,
|
|
* since if you aren't going to get the same result for the
|
|
* same data every time, it's not clear what the index entries
|
|
* mean at all.
|
|
*/
|
|
if (CheckMutability((Expr*)expr))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("functions in index expression must be marked IMMUTABLE")));
|
|
}
|
|
}
|
|
|
|
typeOidP[attn] = atttype;
|
|
|
|
/*
|
|
* Included columns have no collation, no opclass and no ordering options.
|
|
*/
|
|
if (attn >= nkeycols) {
|
|
if (attribute->collation) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("including column does not support a collation")));
|
|
}
|
|
if (attribute->opclass) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("including column does not support an operator class")));
|
|
}
|
|
if (attribute->ordering != SORTBY_DEFAULT) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("including column does not support ASC/DESC options")));
|
|
}
|
|
if (attribute->nulls_ordering != SORTBY_NULLS_DEFAULT) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("including column does not support NULLS FIRST/LAST options")));
|
|
}
|
|
classOidP[attn] = InvalidOid;
|
|
colOptionP[attn] = 0;
|
|
collationOidP[attn] = InvalidOid;
|
|
attn++;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Apply collation override if any
|
|
*/
|
|
if (attribute->collation)
|
|
attcollation = get_collation_oid(attribute->collation, false);
|
|
|
|
/*
|
|
* Check we have a collation iff it's a collatable type. The only
|
|
* expected failures here are (1) COLLATE applied to a noncollatable
|
|
* type, or (2) index expression had an unresolved collation. But we
|
|
* might as well code this to be a complete consistency check.
|
|
*/
|
|
if (type_is_collatable(atttype)) {
|
|
if (!OidIsValid(attcollation))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDETERMINATE_COLLATION),
|
|
errmsg("could not determine which collation to use for index expression"),
|
|
errhint("Use the COLLATE clause to set the collation explicitly.")));
|
|
} else {
|
|
if (OidIsValid(attcollation))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("collations are not supported by type %s", format_type_be(atttype))));
|
|
}
|
|
|
|
collationOidP[attn] = attcollation;
|
|
|
|
/*
|
|
* Identify the opclass to use.
|
|
*/
|
|
classOidP[attn] = GetIndexOpClass(attribute->opclass, atttype, accessMethodName, accessMethodId);
|
|
|
|
/*
|
|
* Identify the exclusion operator, if any.
|
|
*/
|
|
if (nextExclOp != NULL) {
|
|
List* opname = (List*)lfirst(nextExclOp);
|
|
Oid opid;
|
|
Oid opfamily;
|
|
int strat;
|
|
|
|
/*
|
|
* Find the operator --- it must accept the column datatype
|
|
* without runtime coercion (but binary compatibility is OK)
|
|
*/
|
|
opid = compatible_oper_opid(opname, atttype, atttype, false);
|
|
|
|
/*
|
|
* Only allow commutative operators to be used in exclusion
|
|
* constraints. If X conflicts with Y, but Y does not conflict
|
|
* with X, bad things will happen.
|
|
*/
|
|
if (get_commutator(opid) != opid)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("operator %s is not commutative", format_operator(opid)),
|
|
errdetail("Only commutative operators can be used in exclusion constraints.")));
|
|
|
|
/*
|
|
* Operator must be a member of the right opfamily, too
|
|
*/
|
|
opfamily = get_opclass_family(classOidP[attn]);
|
|
strat = get_op_opfamily_strategy(opid, opfamily);
|
|
if (strat == 0) {
|
|
HeapTuple opftuple;
|
|
Form_pg_opfamily opfform;
|
|
|
|
/*
|
|
* attribute->opclass might not explicitly name the opfamily,
|
|
* so fetch the name of the selected opfamily for use in the
|
|
* error message.
|
|
*/
|
|
opftuple = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamily));
|
|
if (!HeapTupleIsValid(opftuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
|
|
errmsg("cache lookup failed for opfamily %u", opfamily)));
|
|
opfform = (Form_pg_opfamily)GETSTRUCT(opftuple);
|
|
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("operator %s is not a member of operator family \"%s\"",
|
|
format_operator(opid),
|
|
NameStr(opfform->opfname)),
|
|
errdetail(
|
|
"The exclusion operator must be related to the index operator class for the constraint.")));
|
|
}
|
|
|
|
indexInfo->ii_ExclusionOps[attn] = opid;
|
|
indexInfo->ii_ExclusionProcs[attn] = get_opcode(opid);
|
|
indexInfo->ii_ExclusionStrats[attn] = strat;
|
|
nextExclOp = lnext(nextExclOp);
|
|
}
|
|
|
|
/*
|
|
* Set up the per-column options (indoption field). For now, this is
|
|
* zero for any un-ordered index, while ordered indexes have DESC and
|
|
* NULLS FIRST/LAST options.
|
|
*/
|
|
colOptionP[attn] = 0;
|
|
if (amcanorder) {
|
|
/* default ordering is ASC */
|
|
if (attribute->ordering == SORTBY_DESC)
|
|
colOptionP[attn] = ((uint16)colOptionP[attn]) | INDOPTION_DESC;
|
|
/* default null ordering is LAST for ASC, FIRST for DESC */
|
|
if (attribute->nulls_ordering == SORTBY_NULLS_DEFAULT) {
|
|
if (attribute->ordering == SORTBY_DESC)
|
|
colOptionP[attn] = ((uint16)colOptionP[attn]) | INDOPTION_NULLS_FIRST;
|
|
} else if (attribute->nulls_ordering == SORTBY_NULLS_FIRST)
|
|
colOptionP[attn] = ((uint16)colOptionP[attn]) | INDOPTION_NULLS_FIRST;
|
|
} else {
|
|
/* index AM does not support ordering */
|
|
if (attribute->ordering != SORTBY_DEFAULT)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("access method \"%s\" does not support ASC/DESC options", accessMethodName)));
|
|
if (attribute->nulls_ordering != SORTBY_NULLS_DEFAULT)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("access method \"%s\" does not support NULLS FIRST/LAST options", accessMethodName)));
|
|
}
|
|
|
|
attn++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Resolve possibly-defaulted operator class specification
|
|
*/
|
|
Oid GetIndexOpClass(List* opclass, Oid attrType, const char* accessMethodName, Oid accessMethodId)
|
|
{
|
|
char* schemaname = NULL;
|
|
char* opcname = NULL;
|
|
HeapTuple tuple;
|
|
Oid opClassId, opInputType;
|
|
|
|
/*
|
|
* Release 7.0 removed network_ops, timespan_ops, and datetime_ops, so we
|
|
* ignore those opclass names so the default *_ops is used. This can be
|
|
* removed in some later release. bjm 2000/02/07
|
|
*
|
|
* Release 7.1 removes lztext_ops, so suppress that too for a while. tgl
|
|
* 2000/07/30
|
|
*
|
|
* Release 7.2 renames timestamp_ops to timestamptz_ops, so suppress that
|
|
* too for awhile. I'm starting to think we need a better approach. tgl
|
|
* 2000/10/01
|
|
*
|
|
* Release 8.0 removes bigbox_ops (which was dead code for a long while
|
|
* anyway). tgl 2003/11/11
|
|
*/
|
|
if (list_length(opclass) == 1) {
|
|
char* claname = strVal(linitial(opclass));
|
|
|
|
if (strcmp(claname, "network_ops") == 0 || strcmp(claname, "timespan_ops") == 0 ||
|
|
strcmp(claname, "datetime_ops") == 0 || strcmp(claname, "lztext_ops") == 0 ||
|
|
strcmp(claname, "timestamp_ops") == 0 || strcmp(claname, "bigbox_ops") == 0)
|
|
opclass = NIL;
|
|
}
|
|
|
|
if (opclass == NIL) {
|
|
/* no operator class specified, so find the default */
|
|
opClassId = GetDefaultOpClass(attrType, accessMethodId);
|
|
if (!OidIsValid(opClassId))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("data type %s has no default operator class for access method \"%s\"",
|
|
format_type_be(attrType),
|
|
accessMethodName),
|
|
errhint("You must specify an operator class for the index or define a default operator class for "
|
|
"the data type.")));
|
|
return opClassId;
|
|
}
|
|
|
|
/*
|
|
* Specific opclass name given, so look up the opclass.
|
|
* deconstruct the name list
|
|
*/
|
|
DeconstructQualifiedName(opclass, &schemaname, &opcname);
|
|
|
|
if (schemaname != NULL) {
|
|
/* Look in specific schema only */
|
|
Oid namespaceId;
|
|
|
|
namespaceId = LookupExplicitNamespace(schemaname);
|
|
tuple = SearchSysCache3(
|
|
CLAAMNAMENSP, ObjectIdGetDatum(accessMethodId), PointerGetDatum(opcname), ObjectIdGetDatum(namespaceId));
|
|
} else {
|
|
/* Unqualified opclass name, so search the search path */
|
|
opClassId = OpclassnameGetOpcid(accessMethodId, opcname);
|
|
if (!OidIsValid(opClassId))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg(
|
|
"operator class \"%s\" does not exist for access method \"%s\"", opcname, accessMethodName)));
|
|
tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opClassId));
|
|
}
|
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
NameListToString(opclass),
|
|
accessMethodName)));
|
|
|
|
/*
|
|
* Verify that the index operator class accepts this datatype. Note we
|
|
* will accept binary compatibility.
|
|
*/
|
|
opClassId = HeapTupleGetOid(tuple);
|
|
opInputType = ((Form_pg_opclass)GETSTRUCT(tuple))->opcintype;
|
|
|
|
if (!IsBinaryCoercible(attrType, opInputType))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("operator class \"%s\" does not accept data type %s",
|
|
NameListToString(opclass),
|
|
format_type_be(attrType))));
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
return opClassId;
|
|
}
|
|
|
|
/*
|
|
* GetDefaultOpClass
|
|
*
|
|
* Given the OIDs of a datatype and an access method, find the default
|
|
* operator class, if any. Returns InvalidOid if there is none.
|
|
*/
|
|
Oid GetDefaultOpClass(Oid type_id, Oid am_id)
|
|
{
|
|
Oid result = InvalidOid;
|
|
int nexact = 0;
|
|
int ncompatible = 0;
|
|
int ncompatiblepreferred = 0;
|
|
Relation rel;
|
|
ScanKeyData skey[1];
|
|
SysScanDesc scan;
|
|
HeapTuple tup;
|
|
TYPCATEGORY tcategory;
|
|
|
|
/* If it's a domain, look at the base type instead */
|
|
type_id = getBaseType(type_id);
|
|
|
|
tcategory = TypeCategory(type_id);
|
|
|
|
/*
|
|
* We scan through all the opclasses available for the access method,
|
|
* looking for one that is marked default and matches the target type
|
|
* (either exactly or binary-compatibly, but prefer an exact match).
|
|
*
|
|
* We could find more than one binary-compatible match. If just one is
|
|
* for a preferred type, use that one; otherwise we fail, forcing the user
|
|
* to specify which one he wants. (The preferred-type special case is a
|
|
* kluge for varchar: it's binary-compatible to both text and bpchar, so
|
|
* we need a tiebreaker.) If we find more than one exact match, then
|
|
* someone put bogus entries in pg_opclass.
|
|
*/
|
|
rel = heap_open(OperatorClassRelationId, AccessShareLock);
|
|
|
|
ScanKeyInit(&skey[0], Anum_pg_opclass_opcmethod, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(am_id));
|
|
|
|
scan = systable_beginscan(rel, OpclassAmNameNspIndexId, true, NULL, 1, skey);
|
|
|
|
while (HeapTupleIsValid(tup = systable_getnext(scan))) {
|
|
Form_pg_opclass opclass = (Form_pg_opclass)GETSTRUCT(tup);
|
|
|
|
/* ignore altogether if not a default opclass */
|
|
if (!opclass->opcdefault)
|
|
continue;
|
|
if (opclass->opcintype == type_id) {
|
|
nexact++;
|
|
result = HeapTupleGetOid(tup);
|
|
} else if (nexact == 0 && IsBinaryCoercible(type_id, opclass->opcintype)) {
|
|
if (IsPreferredType(tcategory, opclass->opcintype)) {
|
|
ncompatiblepreferred++;
|
|
result = HeapTupleGetOid(tup);
|
|
} else if (ncompatiblepreferred == 0) {
|
|
ncompatible++;
|
|
result = HeapTupleGetOid(tup);
|
|
}
|
|
}
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
|
|
heap_close(rel, AccessShareLock);
|
|
|
|
/* raise error if pg_opclass contains inconsistent data */
|
|
if (nexact > 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("there are multiple default operator classes for data type %s", format_type_be(type_id))));
|
|
|
|
if (nexact == 1 || ncompatiblepreferred == 1 || (ncompatiblepreferred == 0 && ncompatible == 1))
|
|
return result;
|
|
|
|
return InvalidOid;
|
|
}
|
|
|
|
/*
|
|
* makeObjectName()
|
|
*
|
|
* Create a name for an implicitly created index, sequence, constraint, etc.
|
|
*
|
|
* The parameters are typically: the original table name, the original field
|
|
* name, and a "type" string (such as "seq" or "pkey"). The field name
|
|
* and/or type can be NULL if not relevant.
|
|
*
|
|
* The result is a palloc'd string.
|
|
*
|
|
* The basic result we want is "name1_name2_label", omitting "_name2" or
|
|
* "_label" when those parameters are NULL. However, we must generate
|
|
* a name with less than NAMEDATALEN characters! So, we truncate one or
|
|
* both names if necessary to make a short-enough string. The label part
|
|
* is never truncated (so it had better be reasonably short).
|
|
*
|
|
* The caller is responsible for checking uniqueness of the generated
|
|
* name and retrying as needed; retrying will be done by altering the
|
|
* "label" string (which is why we never truncate that part).
|
|
*/
|
|
char* makeObjectName(const char* name1, const char* name2, const char* label, bool reverseTruncate)
|
|
{
|
|
char* name = NULL;
|
|
int overhead = 0; /* chars needed for label and underscores */
|
|
int availchars; /* chars available for name(s) */
|
|
int name1chars; /* chars allocated to name1 */
|
|
int name2chars; /* chars allocated to name2 */
|
|
int ndx;
|
|
errno_t rc;
|
|
|
|
name1chars = strlen(name1);
|
|
if (name2 != NULL) {
|
|
name2chars = strlen(name2);
|
|
overhead++; /* allow for separating underscore */
|
|
} else
|
|
name2chars = 0;
|
|
if (label != NULL)
|
|
overhead += strlen(label) + 1;
|
|
|
|
availchars = NAMEDATALEN - 1 - overhead;
|
|
Assert(availchars > 0); /* else caller chose a bad label */
|
|
|
|
/*
|
|
* If we must truncate, preferentially truncate the longer name. This
|
|
* logic could be expressed without a loop, but it's simple and obvious as
|
|
* a loop.
|
|
*/
|
|
while (name1chars + name2chars > availchars) {
|
|
if (!reverseTruncate) {
|
|
if (name1chars > name2chars) {
|
|
name1chars--;
|
|
} else {
|
|
name2chars--;
|
|
}
|
|
} else {
|
|
if (name2chars > 0) {
|
|
name2chars--;
|
|
} else if (name1chars > 0) {
|
|
name1chars--;
|
|
}
|
|
}
|
|
}
|
|
|
|
name1chars = pg_mbcliplen(name1, name1chars, name1chars);
|
|
if (name2 != NULL)
|
|
name2chars = pg_mbcliplen(name2, name2chars, name2chars);
|
|
|
|
/* Now construct the string using the chosen lengths */
|
|
Size name_len = name1chars + name2chars + overhead + 1;
|
|
name = (char*)palloc0(name_len);
|
|
rc = memcpy_s(name, name_len, name1, name1chars);
|
|
securec_check(rc, "\0", "\0");
|
|
ndx = name1chars;
|
|
if (name2 != NULL) {
|
|
name[ndx++] = '_';
|
|
rc = memcpy_s(name + ndx, (name_len - ndx), name2, name2chars);
|
|
securec_check(rc, "\0", "\0");
|
|
ndx += name2chars;
|
|
}
|
|
|
|
/* Copy label and make sure the last character is '\0' */
|
|
if (label && 0 != strlen(label)) {
|
|
name[ndx++] = '_';
|
|
rc = strcpy_s(name + ndx, (name_len - ndx), label);
|
|
securec_check(rc, "\0", "\0");
|
|
} else
|
|
name[ndx] = '\0';
|
|
|
|
return name;
|
|
}
|
|
|
|
/*
|
|
* Select a nonconflicting name for a new relation. This is ordinarily
|
|
* used to choose index names (which is why it's here) but it can also
|
|
* be used for sequences, or any autogenerated relation kind.
|
|
*
|
|
* name1, name2, and label are used the same way as for makeObjectName(),
|
|
* except that the label can't be NULL; digits will be appended to the label
|
|
* if needed to create a name that is unique within the specified namespace.
|
|
*
|
|
* labelLength is the length of label string without '\0'.
|
|
*
|
|
* Note: it is theoretically possible to get a collision anyway, if someone
|
|
* else chooses the same name concurrently. This is fairly unlikely to be
|
|
* a problem in practice, especially if one is holding an exclusive lock on
|
|
* the relation identified by name1. However, if choosing multiple names
|
|
* within a single command, you'd better create the new object and do
|
|
* CommandCounterIncrement before choosing the next one!
|
|
*
|
|
* Returns a palloc'd string.
|
|
*/
|
|
char* ChooseRelationName(const char* name1, const char* name2, const char* label, size_t labelLength,
|
|
Oid namespaceid, bool reverseTruncate)
|
|
{
|
|
int64 pass = 0;
|
|
char* relname = NULL;
|
|
char modlabel[NAMEDATALEN];
|
|
errno_t rc = EOK;
|
|
|
|
/* make sure modlabel is is zero-padded */
|
|
rc = memset_s(modlabel, NAMEDATALEN, '\0', NAMEDATALEN);
|
|
securec_check(rc, "", "");
|
|
|
|
/* try the unmodified label first */
|
|
if (label != NULL) {
|
|
/* Name string should be truncated into 63 chars and 1 '\0' */
|
|
rc = memcpy_s(modlabel, NAMEDATALEN - 1, label, labelLength);
|
|
securec_check(rc, "\0", "\0");
|
|
}
|
|
|
|
for (;;) {
|
|
relname = makeObjectName(name1, name2, modlabel, reverseTruncate);
|
|
|
|
if (!OidIsValid(get_relname_relid(relname, namespaceid)))
|
|
break;
|
|
|
|
/* found a conflict, so try a new name component */
|
|
pfree_ext(relname);
|
|
if (label != NULL) {
|
|
rc = snprintf_s(modlabel, sizeof(modlabel), sizeof(modlabel) - 1, "%s%ld", label, ++pass);
|
|
securec_check_ss(rc, "", "");
|
|
} else {
|
|
rc = snprintf_s(modlabel, sizeof(modlabel), sizeof(modlabel) - 1, "%ld", ++pass);
|
|
securec_check_ss(rc, "", "");
|
|
}
|
|
}
|
|
|
|
return relname;
|
|
}
|
|
|
|
static char* ChoosePartitionName(
|
|
const char* name1, const char* name2, const char* label, Oid partitionedrelid, char partType)
|
|
{
|
|
int pass = 0;
|
|
char* partname = NULL;
|
|
char modlabel[NAMEDATALEN];
|
|
errno_t rc = EOK;
|
|
/* try the unmodified label first */
|
|
rc = strncpy_s(modlabel, sizeof(modlabel), label, sizeof(modlabel) - 1);
|
|
securec_check(rc, "", "");
|
|
|
|
for (;;) {
|
|
partname = makeObjectName(name1, name2, modlabel);
|
|
|
|
if (!OidIsValid(GetSysCacheOid3(
|
|
PARTPARTOID, PointerGetDatum(partname), CharGetDatum(partType), ObjectIdGetDatum(partitionedrelid)))) {
|
|
break;
|
|
}
|
|
|
|
/* found a conflict, so try a new name component */
|
|
pfree_ext(partname);
|
|
rc = snprintf_s(modlabel, sizeof(modlabel), sizeof(modlabel) - 1, "%s%d", label, ++pass);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
}
|
|
|
|
return partname;
|
|
}
|
|
|
|
/*
|
|
* Select the name to be used for an index.
|
|
*
|
|
* The argument list is pretty ad-hoc :-(
|
|
*/
|
|
static char* ChooseIndexName(const char* tabname, Oid namespaceId, const List* colnames, const List* exclusionOpNames,
|
|
bool primary, bool isconstraint, bool psort)
|
|
{
|
|
char* indexname = NULL;
|
|
|
|
if (primary) {
|
|
/* the primary key's name does not depend on the specific column(s) */
|
|
indexname = ChooseRelationName(tabname, NULL, "pkey", strlen("pkey"), namespaceId);
|
|
} else if (exclusionOpNames != NIL) {
|
|
indexname = ChooseRelationName(tabname, ChooseIndexNameAddition(colnames), "excl", strlen("excl"), namespaceId);
|
|
} else if (isconstraint) {
|
|
indexname = ChooseRelationName(tabname, ChooseIndexNameAddition(colnames), "key", strlen("key"), namespaceId);
|
|
} else if (psort) {
|
|
indexname = ChooseRelationName(tabname, ChooseIndexNameAddition(colnames), "psort", strlen("psort"),
|
|
namespaceId);
|
|
} else {
|
|
indexname = ChooseRelationName(tabname, ChooseIndexNameAddition(colnames), "idx", strlen("idx"), namespaceId);
|
|
}
|
|
|
|
return indexname;
|
|
}
|
|
|
|
char* ChoosePSortIndexName(const char* tabname, Oid namespaceId, List* colnames)
|
|
{
|
|
return ChooseIndexName(tabname, namespaceId, colnames, NULL, false, false, true);
|
|
}
|
|
|
|
static char* ChoosePartitionIndexName(const char* partname, Oid partitionedindexId,
|
|
List* colnames, /* List *exclusionOpNames, */
|
|
bool primary, bool isconstraint, bool psort)
|
|
{
|
|
char* indexname = NULL;
|
|
|
|
if (primary) {
|
|
/* the primary key's name does not depend on the specific column(s) */
|
|
indexname = ChoosePartitionName(partname, NULL, "pkey", partitionedindexId, PART_OBJ_TYPE_INDEX_PARTITION);
|
|
} else if (isconstraint) {
|
|
indexname = ChoosePartitionName(
|
|
partname, ChooseIndexNameAddition(colnames), "key", partitionedindexId, PART_OBJ_TYPE_INDEX_PARTITION);
|
|
} else if (psort) {
|
|
indexname = ChoosePartitionName(
|
|
partname, ChooseIndexNameAddition(colnames), "psort", partitionedindexId, PART_OBJ_TYPE_INDEX_PARTITION);
|
|
} else {
|
|
indexname = ChoosePartitionName(
|
|
partname, ChooseIndexNameAddition(colnames), "idx", partitionedindexId, PART_OBJ_TYPE_INDEX_PARTITION);
|
|
}
|
|
|
|
return indexname;
|
|
}
|
|
/*
|
|
* Generate "name2" for a new index given the list of column names for it
|
|
* (as produced by ChooseIndexColumnNames). This will be passed to
|
|
* ChooseRelationName along with the parent table name and a suitable label.
|
|
*
|
|
* We know that less than NAMEDATALEN characters will actually be used,
|
|
* so we can truncate the result once we've generated that many.
|
|
*/
|
|
static char* ChooseIndexNameAddition(const List* colnames)
|
|
{
|
|
char buf[NAMEDATALEN * 2];
|
|
int buflen = 0;
|
|
ListCell* lc = NULL;
|
|
|
|
buf[0] = '\0';
|
|
foreach (lc, colnames) {
|
|
const char* name = (const char*)lfirst(lc);
|
|
|
|
if (buflen > 0)
|
|
buf[buflen++] = '_'; /* insert _ between names */
|
|
|
|
/*
|
|
* At this point we have buflen <= NAMEDATALEN. name should be less
|
|
* than NAMEDATALEN already, but use strlcpy for paranoia.
|
|
*/
|
|
strlcpy(buf + buflen, name, NAMEDATALEN);
|
|
buflen += strlen(buf + buflen);
|
|
if (buflen >= NAMEDATALEN)
|
|
break;
|
|
}
|
|
return pstrdup(buf);
|
|
}
|
|
|
|
/*
|
|
* Select the actual names to be used for the columns of an index, given the
|
|
* list of IndexElems for the columns. This is mostly about ensuring the
|
|
* names are unique so we don't get a conflicting-attribute-names error.
|
|
*
|
|
* Returns a List of plain strings (char *, not String nodes).
|
|
*/
|
|
List* ChooseIndexColumnNames(const List* indexElems)
|
|
{
|
|
List* result = NIL;
|
|
ListCell* lc = NULL;
|
|
errno_t rc;
|
|
|
|
foreach (lc, indexElems) {
|
|
IndexElem* ielem = (IndexElem*)lfirst(lc);
|
|
const char* origname = NULL;
|
|
const char* curname = NULL;
|
|
int i = 0;
|
|
char buf[NAMEDATALEN];
|
|
|
|
/* Get the preliminary name from the IndexElem */
|
|
if (ielem->indexcolname)
|
|
origname = ielem->indexcolname; /* caller-specified name */
|
|
else if (ielem->name)
|
|
origname = ielem->name; /* simple column reference */
|
|
else
|
|
origname = "expr"; /* default name for expression */
|
|
|
|
/* If it conflicts with any previous column, tweak it */
|
|
curname = origname;
|
|
for (i = 1;; i++) {
|
|
ListCell* lc2 = NULL;
|
|
char nbuf[32];
|
|
int nlen;
|
|
|
|
foreach (lc2, result) {
|
|
if (strcmp(curname, (char*)lfirst(lc2)) == 0)
|
|
break;
|
|
}
|
|
if (lc2 == NULL)
|
|
break; /* found nonconflicting name */
|
|
|
|
rc = sprintf_s(nbuf, sizeof(nbuf), "%d", i);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
|
|
/* Ensure generated names are shorter than NAMEDATALEN */
|
|
nlen = pg_mbcliplen(origname, strlen(origname), NAMEDATALEN - 1 - strlen(nbuf));
|
|
rc = memcpy_s(buf, nlen, origname, nlen);
|
|
securec_check(rc, "\0", "\0");
|
|
rc = strcpy_s(buf + nlen, NAMEDATALEN - 1 - nlen, nbuf);
|
|
securec_check(rc, "\0", "\0");
|
|
curname = buf;
|
|
}
|
|
|
|
/* And attach to the result list */
|
|
result = lappend(result, pstrdup(curname));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* ReindexIndex
|
|
* Recreate a specific index.
|
|
*/
|
|
void ReindexIndex(RangeVar* indexRelation, const char* partition_name, AdaptMem* mem_info)
|
|
{
|
|
Oid indOid;
|
|
Oid indPartOid = InvalidOid;
|
|
Oid heapOid = InvalidOid;
|
|
Oid heapPartOid = InvalidOid;
|
|
LOCKMODE lockmode;
|
|
Relation irel;
|
|
|
|
/* lock level used here should match index lock reindex_index() */
|
|
if (partition_name != NULL)
|
|
lockmode = AccessShareLock;
|
|
else
|
|
lockmode = AccessExclusiveLock;
|
|
|
|
indOid = RangeVarGetRelidExtended(indexRelation,
|
|
lockmode,
|
|
false,
|
|
false,
|
|
partition_name != NULL,
|
|
false,
|
|
RangeVarCallbackForReindexIndex,
|
|
(void*)&heapOid);
|
|
|
|
TrForbidAccessRbObject(RelationRelationId, indOid, indexRelation->relname);
|
|
|
|
/*
|
|
* Obtain the current persistence of the existing index. We already hold
|
|
* lock on the index.
|
|
*/
|
|
irel = index_open(indOid, NoLock);
|
|
index_close(irel, NoLock);
|
|
|
|
if (partition_name != NULL)
|
|
indPartOid = partitionNameGetPartitionOid(indOid,
|
|
partition_name,
|
|
PART_OBJ_TYPE_INDEX_PARTITION,
|
|
AccessExclusiveLock, // lock on index partition
|
|
false,
|
|
false,
|
|
PartitionNameCallbackForIndexPartition,
|
|
(void*)&heapPartOid,
|
|
ShareLock); // lock on heap partition
|
|
reindex_index(indOid, indPartOid, false, mem_info, false);
|
|
|
|
#ifndef ENABLE_MULTIPLE_NODES
|
|
Oid relId = IndexGetRelation(indOid, false);
|
|
if (RelationIsCUFormatByOid(relId) && irel->rd_index != NULL && irel->rd_index->indisunique) {
|
|
/*
|
|
* Unique index on CU owns a unique index on delta table, but delta index is not visble
|
|
* to user. We reindex delta index manually.
|
|
*/
|
|
ReindexDeltaIndex(indOid, indPartOid);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void PartitionNameCallbackForIndexPartition(Oid partitionedRelationOid, const char* partitionName, Oid partId,
|
|
Oid oldPartId, char partition_type, void* arg, LOCKMODE callbackobj_lockMode)
|
|
{
|
|
char relkind;
|
|
Oid* heapPartOid = (Oid*)arg;
|
|
Oid heapOid = InvalidOid;
|
|
|
|
heapOid = IndexGetRelation(partitionedRelationOid, false);
|
|
/*
|
|
* If we previously locked some other index's heap, and the name we're
|
|
* looking up no longer refers to that relation, release the now-useless
|
|
* lock.
|
|
*/
|
|
if (partId != oldPartId && OidIsValid(oldPartId)) {
|
|
/* lock level here should match reindex_index() heap lock */
|
|
/* the lock on heap is held by calling RangeVarCallbackForReindexIndex() */
|
|
UnlockPartition(heapOid, *heapPartOid, callbackobj_lockMode, PARTITION_LOCK);
|
|
*heapPartOid = InvalidOid;
|
|
}
|
|
|
|
/* If the relation does not exist, there's nothing more to do. */
|
|
if (!OidIsValid(partId) || !OidIsValid(partitionedRelationOid))
|
|
return;
|
|
|
|
/*
|
|
* If the relation does exist, check whether it's an index. But note that
|
|
* the relation might have been dropped between the time we did the name
|
|
* lookup and now. In that case, there's nothing to do.
|
|
*/
|
|
relkind = get_rel_relkind(partitionedRelationOid);
|
|
if (!relkind) {
|
|
return;
|
|
}
|
|
if (relkind != RELKIND_INDEX && relkind != RELKIND_GLOBAL_INDEX)
|
|
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not an index", partitionName)));
|
|
if (0 != memcmp(partitionName, getPartitionName(partId, false), strlen(partitionName)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" does not mean oid \"%u\"", partitionName, partId)));
|
|
if (partitionedRelationOid != partid_get_parentid(partId))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%u\" is not a child of \"%u\"", partId, partitionedRelationOid)));
|
|
|
|
/* Check permissions */
|
|
Oid tableOid = IndexGetRelation(partitionedRelationOid, false);
|
|
AclResult aclresult = pg_class_aclcheck(tableOid, GetUserId(), ACL_INDEX);
|
|
if (aclresult != ACLCHECK_OK && !pg_class_ownercheck(partitionedRelationOid, GetUserId())) {
|
|
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS, partitionName);
|
|
}
|
|
|
|
/* Lock heap before index to avoid deadlock. */
|
|
if (partId != oldPartId) {
|
|
/*
|
|
* Lock level here should match reindex_index() heap lock. If the OID
|
|
* isn't valid, it means the index as concurrently dropped, which is
|
|
* not a problem for us; just return normally.
|
|
*/
|
|
*heapPartOid = indexPartGetHeapPart(partId, true);
|
|
if (OidIsValid(*heapPartOid)) {
|
|
LockPartition(heapOid, *heapPartOid, callbackobj_lockMode, PARTITION_LOCK);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check permissions on table before acquiring relation lock; also lock
|
|
* the heap before the RangeVarGetRelidExtended takes the index lock, to avoid
|
|
* deadlocks.
|
|
*/
|
|
static void RangeVarCallbackForReindexIndex(
|
|
const RangeVar* relation, Oid relId, Oid oldRelId, bool target_is_partition, void* arg)
|
|
{
|
|
char relkind;
|
|
Oid* heapOid = (Oid*)arg;
|
|
|
|
/*
|
|
* If we previously locked some other index's heap, and the name we're
|
|
* looking up no longer refers to that relation, release the now-useless
|
|
* lock.
|
|
*/
|
|
if (relId != oldRelId && OidIsValid(oldRelId)) {
|
|
/* lock level here should match reindex_index() heap lock */
|
|
if (target_is_partition) {
|
|
UnlockRelationOid(*heapOid, AccessShareLock);
|
|
} else {
|
|
UnlockRelationOid(*heapOid, ShareLock);
|
|
}
|
|
*heapOid = InvalidOid;
|
|
}
|
|
|
|
/* If the relation does not exist, there's nothing more to do. */
|
|
if (!OidIsValid(relId))
|
|
return;
|
|
|
|
/*
|
|
* If the relation does exist, check whether it's an index. But note that
|
|
* the relation might have been dropped between the time we did the name
|
|
* lookup and now. In that case, there's nothing to do.
|
|
*/
|
|
relkind = get_rel_relkind(relId);
|
|
if (!relkind) {
|
|
return;
|
|
}
|
|
if (relkind != RELKIND_INDEX && relkind != RELKIND_GLOBAL_INDEX)
|
|
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not an index", relation->relname)));
|
|
|
|
if (target_is_partition && relkind == RELKIND_GLOBAL_INDEX) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot reindex global index with partition name")));
|
|
}
|
|
|
|
/* Check permissions */
|
|
Oid tableoid = IndexGetRelation(relId, false);
|
|
AclResult aclresult = pg_class_aclcheck(tableoid, GetUserId(), ACL_INDEX);
|
|
if (aclresult != ACLCHECK_OK && !pg_class_ownercheck(relId, GetUserId())) {
|
|
aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS, relation->relname);
|
|
}
|
|
|
|
/* Lock heap before index to avoid deadlock. */
|
|
if (relId != oldRelId) {
|
|
/*
|
|
* Lock level here should match reindex_index() heap lock. If the OID
|
|
* isn't valid, it means the index as concurrently dropped, which is
|
|
* not a problem for us; just return normally.
|
|
*/
|
|
*heapOid = IndexGetRelation(relId, true);
|
|
if (OidIsValid(*heapOid)) {
|
|
if (target_is_partition) {
|
|
LockRelationOid(*heapOid, AccessShareLock);
|
|
} else {
|
|
LockRelationOid(*heapOid, ShareLock);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ReindexTable
|
|
* Recreate all indexes of a table (and of its toast table, if any)
|
|
*/
|
|
void ReindexTable(RangeVar* relation, const char* partition_name, AdaptMem* mem_info)
|
|
{
|
|
Oid heapOid;
|
|
|
|
if (partition_name != NULL) {
|
|
Oid heapPartOid;
|
|
/* The lock level used here should match reindexPartition(). */
|
|
heapOid = RangeVarGetRelidExtended(
|
|
relation, AccessShareLock, false, false, false, false, RangeVarCallbackOwnsTable, NULL);
|
|
Relation rel = heap_open(heapOid, AccessShareLock);
|
|
if (RelationIsSubPartitioned(rel)) {
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
(errmsg("Un-support feature"),
|
|
errdetail("For subpartition table, REBUILD UNUSABLE LOCAL INDEXES is not yet supported."),
|
|
errcause("The function is not implemented."), erraction("Use other actions instead."))));
|
|
}
|
|
heap_close(rel, NoLock);
|
|
TrForbidAccessRbObject(RelationRelationId, heapOid, relation->relname);
|
|
|
|
heapPartOid = partitionNameGetPartitionOid(
|
|
heapOid, partition_name, PART_OBJ_TYPE_TABLE_PARTITION, ShareLock, false, false, NULL, NULL, NoLock);
|
|
reindexPartition(heapOid,
|
|
heapPartOid,
|
|
REINDEX_REL_PROCESS_TOAST | REINDEX_REL_SUPPRESS_INDEX_USE | REINDEX_REL_CHECK_CONSTRAINTS,
|
|
REINDEX_ALL_INDEX);
|
|
} else {
|
|
/* The lock level used here should match ReindexRelation(). */
|
|
heapOid =
|
|
RangeVarGetRelidExtended(relation, ShareLock, false, false, false, false, RangeVarCallbackOwnsTable, NULL);
|
|
|
|
TrForbidAccessRbObject(RelationRelationId, heapOid, relation->relname);
|
|
|
|
if (!ReindexRelation(heapOid,
|
|
REINDEX_REL_PROCESS_TOAST | REINDEX_REL_SUPPRESS_INDEX_USE | REINDEX_REL_CHECK_CONSTRAINTS,
|
|
REINDEX_ALL_INDEX, NULL, mem_info))
|
|
ereport(NOTICE, (errmsg("table \"%s\" has no indexes", relation->relname)));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ReindexInternal
|
|
* Recreate all indexes of its cudesc table(and of its toast table, if any)
|
|
* @ in relation: the column_table|hdfs_table is used to execute the operation of 'reindex internal table name'.
|
|
* @ in partition_name: the partition_table is used to execute the operation of 'reindex internal table name partition
|
|
*partition_name'.
|
|
*/
|
|
void ReindexInternal(RangeVar* relation, const char* partition_name)
|
|
{
|
|
Oid heapOid;
|
|
Relation rel;
|
|
Relation partitionRel;
|
|
Partition part;
|
|
const int flags = REINDEX_REL_PROCESS_TOAST | REINDEX_REL_SUPPRESS_INDEX_USE | REINDEX_REL_CHECK_CONSTRAINTS;
|
|
|
|
heapOid = RangeVarGetRelidExtended(
|
|
relation, AccessShareLock, false, false, false, false, RangeVarCallbackOwnsTable, NULL);
|
|
|
|
TrForbidAccessRbObject(RelationRelationId, heapOid, relation->relname);
|
|
|
|
rel = heap_open(heapOid, AccessShareLock);
|
|
|
|
/* 1. judge the cstore table and reindex its cudesc table.
|
|
* 2. judge the hdfs table and reindex its cudesc table.
|
|
* 3. others,such as row table and hdfs foreign table are to report error.
|
|
*/
|
|
if (RelationIsCUFormat(rel)) {
|
|
if (partition_name != NULL) {
|
|
Oid PartOid;
|
|
/* The lock level used here should match reindexPartition(). */
|
|
PartOid = partitionNameGetPartitionOid(heapOid,
|
|
partition_name,
|
|
PART_OBJ_TYPE_TABLE_PARTITION,
|
|
AccessShareLock,
|
|
false,
|
|
false,
|
|
NULL,
|
|
NULL,
|
|
NoLock);
|
|
part = partitionOpen(rel, PartOid, AccessShareLock);
|
|
partitionRel = partitionGetRelation(rel, part);
|
|
Oid cudescOid = partitionRel->rd_rel->relcudescrelid;
|
|
|
|
if (!ReindexRelation(cudescOid, flags, REINDEX_ALL_INDEX, NULL)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_PARTITION_ERROR),
|
|
errmsg("The operation of 'REINDEX INTERNAL TABLE %s PARTITION %s' failed. ",
|
|
relation->relname,
|
|
partition_name)));
|
|
}
|
|
partitionClose(rel, part, NoLock);
|
|
releaseDummyRelation(&partitionRel);
|
|
} else {
|
|
if (RELATION_IS_PARTITIONED(rel)) {
|
|
Oid partOid;
|
|
ListCell* cell = NULL;
|
|
List* partOidList = relationGetPartitionOidList(rel);
|
|
foreach (cell, partOidList) {
|
|
partOid = lfirst_oid(cell);
|
|
part = partitionOpen(rel, partOid, AccessShareLock);
|
|
partitionRel = partitionGetRelation(rel, part);
|
|
Oid cudescOid = partitionRel->rd_rel->relcudescrelid;
|
|
|
|
if (!ReindexRelation(cudescOid, flags, REINDEX_ALL_INDEX, NULL)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_PARTITION_ERROR),
|
|
errmsg("The operation of 'REINDEX INTERNAL TABLE %s' on part \"%u\" failed. ",
|
|
relation->relname,
|
|
partOid)));
|
|
}
|
|
partitionClose(rel, part, NoLock);
|
|
releaseDummyRelation(&partitionRel);
|
|
}
|
|
|
|
} else {
|
|
Oid cudescOid = rel->rd_rel->relcudescrelid;
|
|
|
|
if (!ReindexRelation(cudescOid, flags, REINDEX_ALL_INDEX, NULL)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
errmsg("The operation of 'REINDEX INTERNAL TABLE %s' failed. ", relation->relname)));
|
|
}
|
|
}
|
|
}
|
|
} else if (RelationIsPAXFormat(rel)) {
|
|
Oid cudescOid = rel->rd_rel->relcudescrelid;
|
|
if (!ReindexRelation(cudescOid, flags, REINDEX_ALL_INDEX, NULL)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
errmsg("The operation of 'REINDEX INTERNAL TABLE %s' failed. ", relation->relname)));
|
|
}
|
|
} else {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_TABLE),
|
|
errmsg("The table \"%s\" doesn't support the operation of 'REINDEX INTERNAL TABLE'. There is no Desc "
|
|
"table on it.",
|
|
relation->relname)));
|
|
}
|
|
|
|
heap_close(rel, NoLock);
|
|
}
|
|
|
|
/*
|
|
* ReindexDatabase
|
|
* Recreate indexes of a database.
|
|
*
|
|
* To reduce the probability of deadlocks, each table is reindexed in a
|
|
* separate transaction, so we can release the lock on it right away.
|
|
* That means this must not be called within a user transaction block!
|
|
*/
|
|
void ReindexDatabase(const char* databaseName, bool do_system, bool do_user, AdaptMem* mem_info)
|
|
{
|
|
Relation relationRelation;
|
|
TableScanDesc scan;
|
|
HeapTuple tuple;
|
|
MemoryContext private_context;
|
|
MemoryContext old;
|
|
List* relids = NIL;
|
|
ListCell* l = NULL;
|
|
|
|
AssertArg(databaseName);
|
|
|
|
if (strcmp(databaseName, get_and_check_db_name(u_sess->proc_cxt.MyDatabaseId)) != 0)
|
|
ereport(
|
|
ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("can only reindex the currently open database")));
|
|
|
|
if (!pg_database_ownercheck(u_sess->proc_cxt.MyDatabaseId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, databaseName);
|
|
|
|
/*
|
|
* Create a memory context that will survive forced transaction commits we
|
|
* do below. Since it is a child of t_thrd.mem_cxt.portal_mem_cxt, it will go away
|
|
* eventually even if we suffer an error; there's no need for special
|
|
* abort cleanup logic.
|
|
*/
|
|
private_context = AllocSetContextCreate(t_thrd.mem_cxt.portal_mem_cxt,
|
|
"ReindexDatabase",
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
|
|
|
/*
|
|
* We always want to reindex pg_class first. This ensures that if there
|
|
* is any corruption in pg_class' indexes, they will be fixed before we
|
|
* process any other tables. This is critical because reindexing itself
|
|
* will try to update pg_class.
|
|
*/
|
|
if (do_system) {
|
|
old = MemoryContextSwitchTo(private_context);
|
|
relids = lappend_oid(relids, RelationRelationId);
|
|
MemoryContextSwitchTo(old);
|
|
}
|
|
|
|
/*
|
|
* Scan pg_class to build a list of the relations we need to reindex.
|
|
*
|
|
* We only consider plain relations here (toast rels will be processed
|
|
* indirectly by ReindexRelation).
|
|
*/
|
|
relationRelation = heap_open(RelationRelationId, AccessShareLock);
|
|
scan = tableam_scan_begin(relationRelation, SnapshotNow, 0, NULL);
|
|
while ((tuple = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
|
|
Form_pg_class classtuple = (Form_pg_class)GETSTRUCT(tuple);
|
|
|
|
if (classtuple->relkind != RELKIND_RELATION && classtuple->relkind != RELKIND_MATVIEW)
|
|
continue;
|
|
|
|
/* Skip temp tables of other backends; we can't reindex them at all */
|
|
if (classtuple->relpersistence == RELPERSISTENCE_TEMP && !isTempNamespace(classtuple->relnamespace))
|
|
continue;
|
|
|
|
/* Check user/system classification, and optionally skip */
|
|
if (IsSystemClass(classtuple)) {
|
|
if (!do_system)
|
|
continue;
|
|
} else {
|
|
if (!do_user)
|
|
continue;
|
|
}
|
|
|
|
if (HeapTupleGetOid(tuple) == RelationRelationId)
|
|
continue; /* got it already */
|
|
|
|
/* Skip object in recycle bin. */
|
|
if (TrIsRefRbObjectEx(RelationRelationId, HeapTupleGetOid(tuple), NameStr(classtuple->relname))) {
|
|
continue;
|
|
}
|
|
|
|
old = MemoryContextSwitchTo(private_context);
|
|
relids = lappend_oid(relids, HeapTupleGetOid(tuple));
|
|
MemoryContextSwitchTo(old);
|
|
}
|
|
tableam_scan_end(scan);
|
|
heap_close(relationRelation, AccessShareLock);
|
|
|
|
/* Now reindex each rel in a separate transaction */
|
|
PopActiveSnapshot();
|
|
CommitTransactionCommand();
|
|
foreach (l, relids) {
|
|
Oid relid = lfirst_oid(l);
|
|
|
|
StartTransactionCommand();
|
|
/* functions in indexes may want a snapshot set */
|
|
PushActiveSnapshot(GetTransactionSnapshot());
|
|
PG_TRY();
|
|
{
|
|
|
|
if (ReindexRelation(relid, REINDEX_REL_PROCESS_TOAST | REINDEX_REL_CHECK_CONSTRAINTS,
|
|
REINDEX_ALL_INDEX, NULL, mem_info, true))
|
|
ereport(NOTICE,
|
|
(errmsg("table \"%s.%s\" was reindexed",
|
|
get_namespace_name(get_rel_namespace(relid)),
|
|
get_rel_name(relid))));
|
|
}
|
|
PG_CATCH();
|
|
{
|
|
if (geterrcode() == ERRCODE_RELATION_OPEN_ERROR && NULL == get_rel_name(relid)) {
|
|
ereport(LOG,
|
|
(errmsg("Table with OID \"%u\" doesn't exit.", relid),
|
|
errhint("Please don't drop table when the operation of 'REINDEX DATABASE' is executed.")));
|
|
|
|
FlushErrorState();
|
|
} else {
|
|
PG_RE_THROW();
|
|
}
|
|
}
|
|
PG_END_TRY();
|
|
|
|
PopActiveSnapshot();
|
|
CommitTransactionCommand();
|
|
}
|
|
StartTransactionCommand();
|
|
|
|
MemoryContextDelete(private_context);
|
|
}
|
|
|
|
/*
|
|
* @@GaussDB@@
|
|
* Target : data partition
|
|
* Brief :
|
|
* Description :
|
|
* Notes :
|
|
*/
|
|
void addIndexForPartition(Relation partitionedRelation, Oid partOid)
|
|
{
|
|
IndexInfo* indexInfo = NULL;
|
|
Relation indexRel = NULL;
|
|
List* indexElemList = NIL;
|
|
List* indexColNames = NIL;
|
|
List* indelist = NIL;
|
|
ListCell* cell = NULL;
|
|
Partition partition = NULL;
|
|
Relation partitionDelta = NULL;
|
|
char* indexPartitionName = NULL;
|
|
Oid indexRelOid = InvalidOid;
|
|
Oid indexHeapOid = InvalidOid;
|
|
char buf[NAMEDATALEN] = {0};
|
|
HeapTuple indexTuple;
|
|
Form_pg_index indexForm;
|
|
HeapTuple attTuple;
|
|
Form_pg_attribute attForm;
|
|
int2 indexColumns = -1;
|
|
int2 indexColumn = -1;
|
|
int2vector* idxKeys = NULL;
|
|
Datum idxKeysDatum;
|
|
bool isnull = false;
|
|
int i;
|
|
Relation pg_partition_rel;
|
|
|
|
if (!PointerIsValid(partitionedRelation) || !OidIsValid(partOid)) {
|
|
ereport(
|
|
ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Invalid relation to create index partition")));
|
|
}
|
|
|
|
/* get oid list of indexRel */
|
|
indelist = RelationGetSpecificKindIndexList(partitionedRelation, false);
|
|
if (!PointerIsValid(indelist)) {
|
|
return;
|
|
}
|
|
|
|
partition = partitionOpen(partitionedRelation, partOid, ShareLock);
|
|
pg_partition_rel = heap_open(PartitionRelationId, RowExclusiveLock);
|
|
|
|
foreach (cell, indelist) {
|
|
IndexElem* indexelem = NULL;
|
|
indexColNames = NIL;
|
|
indexElemList = NIL;
|
|
Oid partIndexOid = InvalidOid;
|
|
indexRelOid = lfirst_oid(cell);
|
|
indexRel = relation_open(indexRelOid, AccessShareLock);
|
|
indexInfo = BuildIndexInfo(indexRel);
|
|
|
|
indexTuple = SearchSysCacheCopy1(INDEXRELID, ObjectIdGetDatum(indexRel->rd_id));
|
|
if (!HeapTupleIsValid(indexTuple)) {
|
|
partitionClose(partitionedRelation, partition, NoLock);
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDEX_CORRUPTED), errmsg("fail to get index info for index %u", indexRel->rd_id)));
|
|
}
|
|
|
|
indexForm = (Form_pg_index)GETSTRUCT(indexTuple);
|
|
indexHeapOid = indexForm->indrelid;
|
|
indexColumns = indexForm->indnatts;
|
|
|
|
idxKeysDatum = SysCacheGetAttr(INDEXRELID, indexTuple, Anum_pg_index_indkey, &isnull);
|
|
Assert(!isnull);
|
|
|
|
idxKeys = (int2vector*)DatumGetPointer(idxKeysDatum);
|
|
|
|
Assert(idxKeys->dim1 == indexColumns);
|
|
|
|
for (i = 0; i < indexColumns; i++) {
|
|
indexColumn = idxKeys->values[i];
|
|
|
|
if (indexColumn == 0) {
|
|
indexElemList = lappend(indexElemList, makeNode(IndexElem));
|
|
continue;
|
|
}
|
|
attTuple = SearchSysCacheCopy2(ATTNUM, ObjectIdGetDatum(indexHeapOid), Int32GetDatum(indexColumn));
|
|
if (!HeapTupleIsValid(attTuple)) {
|
|
partitionClose(partitionedRelation, partition, NoLock);
|
|
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
|
|
errmsg("unable to find attribute %d for relation %u.", (int)indexColumn, indexHeapOid)));
|
|
}
|
|
|
|
attForm = (Form_pg_attribute)GETSTRUCT(attTuple);
|
|
|
|
errno_t rc = EOK;
|
|
rc = strncpy_s(buf, sizeof(buf), NameStr(attForm->attname), sizeof(buf) - 1);
|
|
securec_check(rc, "", "");
|
|
|
|
indexelem = makeNode(IndexElem);
|
|
indexelem->name = pstrdup(buf);
|
|
indexElemList = lappend(indexElemList, indexelem);
|
|
|
|
heap_freetuple(attTuple);
|
|
}
|
|
|
|
indexColNames = ChooseIndexColumnNames(indexElemList);
|
|
|
|
/* choose a default name */
|
|
indexPartitionName =
|
|
ChoosePartitionIndexName(PartitionGetPartitionName(partition), indexRelOid, indexColNames, false, false);
|
|
|
|
// inherit the relation-options of existing partitioned index relation.
|
|
// keep the same reloptions with all the exsiting partitions.
|
|
//
|
|
Datum indexRelOptions = (Datum)0;
|
|
HeapTuple indexTupleInPgclass = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(indexRelOid));
|
|
if (indexTupleInPgclass != NULL) {
|
|
indexRelOptions = SysCacheGetAttr(RELOID, indexTupleInPgclass, Anum_pg_class_reloptions, &isnull);
|
|
if (isnull) {
|
|
indexRelOptions = (Datum)0;
|
|
}
|
|
} else {
|
|
partitionClose(partitionedRelation, partition, NoLock);
|
|
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
|
|
errmsg("cache %d lookup failed for relation %u", RELOID, indexRelOid)));
|
|
}
|
|
|
|
PartIndexCreateExtraArgs partExtraArg;
|
|
partExtraArg.existingPSortOid = InvalidOid;
|
|
partExtraArg.crossbucket = RelationIsCrossBucketIndex(indexRel);
|
|
|
|
partIndexOid = partition_index_create(indexPartitionName,
|
|
InvalidOid,
|
|
partition,
|
|
indexRel->rd_rel->reltablespace,
|
|
indexRel,
|
|
partitionedRelation,
|
|
pg_partition_rel,
|
|
indexInfo,
|
|
indexColNames,
|
|
indexRelOptions,
|
|
false,
|
|
&partExtraArg);
|
|
|
|
#ifndef ENABLE_MULTIPLE_NODES
|
|
if (RelationIsCUFormat(partitionedRelation) && indexForm->indisunique) {
|
|
if (!PointerIsValid(partitionDelta)) {
|
|
partitionDelta = heap_open(partition->pd_part->reldeltarelid, ShareLock);
|
|
}
|
|
char partDeltaIdxName[NAMEDATALEN] = {0};
|
|
error_t ret = snprintf_s(partDeltaIdxName, sizeof(partDeltaIdxName),
|
|
sizeof(partDeltaIdxName) - 1, "pg_delta_part_index_%u", partIndexOid);
|
|
securec_check_ss_c(ret, "\0", "\0");
|
|
|
|
(void)CreateDeltaUniqueIndex(partitionDelta, partDeltaIdxName, indexInfo, indexElemList,
|
|
indexColNames, indexForm->indisprimary);
|
|
}
|
|
#endif
|
|
|
|
relation_close(indexRel, NoLock);
|
|
heap_freetuple(indexTuple);
|
|
heap_freetuple(indexTupleInPgclass);
|
|
}
|
|
|
|
heap_close(pg_partition_rel, RowExclusiveLock);
|
|
partitionClose(partitionedRelation, partition, NoLock);
|
|
|
|
if (PointerIsValid(partitionDelta)) {
|
|
heap_close(partitionDelta, NoLock);
|
|
}
|
|
}
|
|
|
|
void dropIndexForPartition(Oid partOid)
|
|
{
|
|
List* partIndexlist = NULL;
|
|
HeapTuple partIndexTuple = NULL;
|
|
Form_pg_partition partForm = NULL;
|
|
Oid partIndexOid = InvalidOid;
|
|
Relation indexRel = NULL;
|
|
ListCell* cell = NULL;
|
|
|
|
if (!OidIsValid(partOid)) {
|
|
return;
|
|
}
|
|
|
|
/* first get the list of index partition on targeting table partition */
|
|
partIndexlist = searchPartitionIndexesByblid(partOid);
|
|
if (!PointerIsValid(partIndexlist)) {
|
|
return;
|
|
}
|
|
|
|
/* iterate the index partition list */
|
|
foreach (cell, partIndexlist) {
|
|
partIndexTuple = (HeapTuple)lfirst(cell);
|
|
if (HeapTupleIsValid(partIndexTuple)) {
|
|
partForm = (Form_pg_partition)GETSTRUCT(partIndexTuple);
|
|
|
|
if (!PointerIsValid(partForm)) {
|
|
continue;
|
|
}
|
|
|
|
/* get partitioned index's oid, and index partition's oid*/
|
|
partIndexOid = HeapTupleGetOid(partIndexTuple);
|
|
indexRel = relation_open(partForm->parentid, AccessShareLock);
|
|
heapDropPartitionIndex(indexRel, partIndexOid);
|
|
relation_close(indexRel, NoLock);
|
|
}
|
|
}
|
|
|
|
freePartList(partIndexlist);
|
|
}
|
|
|
|
/*
|
|
* Brief : See whether relation has an existing a informational primary key.
|
|
* Description : See whether relation has an existing a informational primary key.
|
|
* Input : conrel, a relation struct object.
|
|
* Output : None.
|
|
* Return Value : return true if relation has existing a informational primary key.
|
|
* else, return false.
|
|
* Notes : None.
|
|
*/
|
|
static bool relationHasInformationalPrimaryKey(const Relation rel)
|
|
{
|
|
bool found = false;
|
|
HeapTuple htup;
|
|
ScanKeyData skey[1];
|
|
SysScanDesc conscan;
|
|
Relation conrel;
|
|
ScanKeyInit(
|
|
&skey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
|
|
conrel = heap_open(ConstraintRelationId, ShareUpdateExclusiveLock);
|
|
conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true, NULL, 1, skey);
|
|
while (HeapTupleIsValid(htup = systable_getnext(conscan))) {
|
|
Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(htup);
|
|
|
|
if ('p' == con->contype) {
|
|
/* Found it. */
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
systable_endscan(conscan);
|
|
heap_close(conrel, ShareUpdateExclusiveLock);
|
|
|
|
return found;
|
|
}
|
|
|
|
/*
|
|
* Handle the error message accroding to informational constaint.
|
|
*/
|
|
static void handleErrMsgForInfoCnstrnt(const IndexStmt* stmt, const Relation rel)
|
|
{
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION && rel->rd_rel->relkind != RELKIND_MATVIEW) {
|
|
Oid relationId = RelationGetRelid(rel);
|
|
/*
|
|
* @hdfs
|
|
* Because of using informational constraint on HDFS foreign talble,
|
|
* HDFS foreign table need take this "create index" branch.
|
|
*/
|
|
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE || rel->rd_rel->relkind == RELKIND_STREAM) {
|
|
ForeignTable* ftbl = NULL;
|
|
ForeignServer* fsvr = NULL;
|
|
ftbl = GetForeignTable(relationId);
|
|
|
|
Assert(ftbl != NULL);
|
|
fsvr = GetForeignServer(ftbl->serverid);
|
|
Assert(fsvr != NULL);
|
|
|
|
/*
|
|
* @hdfs
|
|
* When add or create a informational constraint for the HDFS foreign table,
|
|
* this branch will be covered.
|
|
*/
|
|
#ifdef ENABLE_MOT
|
|
if ((!isMOTFromTblOid(RelationGetRelid(rel)) &&
|
|
!CAN_BUILD_INFORMATIONAL_CONSTRAINT_BY_RELID(RelationGetRelid(rel))) || !stmt->internal_flag) {
|
|
#else
|
|
if (!CAN_BUILD_INFORMATIONAL_CONSTRAINT_BY_RELID(RelationGetRelid(rel)) || !stmt->internal_flag) {
|
|
#endif
|
|
/*
|
|
* Custom error message for FOREIGN TABLE since the term is close
|
|
* to a regular table and can confuse the user.
|
|
*/
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("cannot create index on foreign table \"%s\"", RelationGetRelationName(rel))));
|
|
}
|
|
} else {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", RelationGetRelationName(rel))));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Build constraint name for informational constaint.
|
|
*/
|
|
static void buildConstraintNameForInfoCnstrnt(
|
|
const IndexStmt* stmt, Relation rel, char** indexRelationName, Oid namespaceId, const List* indexColNames)
|
|
{
|
|
/*
|
|
* @hdfs
|
|
* If the IndexStmt bulit for informational consraint with constraint name, must check
|
|
* whether has same constraint name in pg_constraint. If found the same name, must rebuild
|
|
* other name for the constraint.
|
|
*/
|
|
if (stmt->idxname == NULL && stmt->internal_flag) {
|
|
if (FindExistingConstraint(*indexRelationName, rel)) {
|
|
if (stmt->primary) {
|
|
*indexRelationName = ChooseConstraintName(RelationGetRelationName(rel), NULL, "pkey", namespaceId, NIL);
|
|
} else if (stmt->isconstraint) {
|
|
*indexRelationName = ChooseConstraintName(
|
|
RelationGetRelationName(rel), ChooseIndexNameAddition(indexColNames), "key", namespaceId, NIL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Build the informational constraint.
|
|
*/
|
|
static Oid buildInformationalConstraint(
|
|
IndexStmt* stmt, Oid indexRelationId, const char* indexRelationName, Relation rel, IndexInfo* indexInfo, Oid namespaceId)
|
|
{
|
|
/*
|
|
* @hdfs
|
|
* Now, we have to deal with the soft constraint. Do not bulid index for it.
|
|
* So, Only update the pg_constraint for soft constraint.
|
|
* If the stmt->internal_flag is true, it means that stmt relate with the HDFS
|
|
* foreign table.
|
|
*/
|
|
char constraintType;
|
|
|
|
if (stmt->primary && relationHasInformationalPrimaryKey(rel)) {
|
|
heap_close(rel, NoLock);
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
errmsg(
|
|
"Multiple primary keys for foreign table \"%s\" are not allowed.", RelationGetRelationName(rel))));
|
|
}
|
|
/* Check constraint name indexRelationName in pg_constraint */
|
|
if (stmt->idxname != NULL && FindExistingConstraint(indexRelationName, rel)) {
|
|
heap_close(rel, NoLock);
|
|
ereport(
|
|
ERROR, (errcode(ERRCODE_UNIQUE_VIOLATION), errmsg("Constraint \"%s\" already exists.", indexRelationName)));
|
|
}
|
|
|
|
/* currently, the soft constraint only support primary key and unique type */
|
|
if (stmt->primary) {
|
|
constraintType = CONSTRAINT_PRIMARY;
|
|
} else {
|
|
constraintType = CONSTRAINT_UNIQUE;
|
|
}
|
|
|
|
/*
|
|
* Construct a pg_constraint entry.
|
|
*/
|
|
(void)CreateConstraintEntry(indexRelationName, /* constraintName */
|
|
namespaceId,
|
|
constraintType,
|
|
stmt->deferrable,
|
|
stmt->initdeferred,
|
|
true,
|
|
RelationGetRelid(rel),
|
|
indexInfo->ii_KeyAttrNumbers,
|
|
indexInfo->ii_NumIndexKeyAttrs,
|
|
indexInfo->ii_NumIndexAttrs,
|
|
InvalidOid, /* no domain */
|
|
indexRelationId, /* InvalidOid */
|
|
InvalidOid, /* no foreign key */
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
' ',
|
|
' ',
|
|
' ',
|
|
indexInfo->ii_ExclusionOps,
|
|
NULL, /* no check constraint */
|
|
NULL,
|
|
NULL,
|
|
true, /* islocal */
|
|
0, /* inhcount */
|
|
true, /* noinherit */
|
|
stmt->inforConstraint); /* informational constraint */
|
|
|
|
heap_close(rel, NoLock);
|
|
return InvalidOid;
|
|
}
|
|
|
|
/*
|
|
* Index constraint: Local partition index could not be on same column with global partition index
|
|
* This function check all exist index on table of 'relOid', compare index attr column with new index of 'indexInfo',
|
|
* return true indicate new index is compatible with all existing index, otherwise, return false.
|
|
*/
|
|
static bool CheckGlobalIndexCompatible(Oid relOid, bool isGlobal, const IndexInfo* indexInfo, Oid currMethodOid)
|
|
{
|
|
ScanKeyData skey[1];
|
|
SysScanDesc sysScan;
|
|
HeapTuple tarTuple;
|
|
Relation indexRelation;
|
|
bool ret = true;
|
|
errno_t rc;
|
|
bool isNull = false;
|
|
char currIdxKind = isGlobal ? RELKIND_GLOBAL_INDEX : RELKIND_INDEX;
|
|
int currSize = sizeof(AttrNumber) * indexInfo->ii_NumIndexKeyAttrs;
|
|
int currKeyNum = indexInfo->ii_NumIndexKeyAttrs;
|
|
|
|
ScanKeyInit(&skey[0], Anum_pg_index_indrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relOid));
|
|
indexRelation = heap_open(IndexRelationId, AccessShareLock);
|
|
sysScan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true, NULL, 1, skey);
|
|
|
|
AttrNumber* currAttrsArray = (AttrNumber*)palloc0(currSize);
|
|
rc = memcpy_s(currAttrsArray, currSize, indexInfo->ii_KeyAttrNumbers, currSize);
|
|
securec_check(rc, "\0", "\0");
|
|
qsort(currAttrsArray, currKeyNum, sizeof(AttrNumber), AttrComparator);
|
|
|
|
while (HeapTupleIsValid(tarTuple = systable_getnext(sysScan))) {
|
|
Form_pg_index indexTuple = (Form_pg_index)GETSTRUCT(tarTuple);
|
|
char tarIdxKind = get_rel_relkind(indexTuple->indexrelid);
|
|
/* only check index of different type(local and global) */
|
|
if (currIdxKind != tarIdxKind) {
|
|
if (!CheckIndexMethodConsistency(tarTuple, indexRelation, currMethodOid)) {
|
|
continue;
|
|
}
|
|
|
|
heap_getattr(tarTuple, Anum_pg_index_indexprs, RelationGetDescr(indexRelation), &isNull);
|
|
/*
|
|
* check expressions: This condition looks confused, here we judge whether tow index has expressions,
|
|
* like XOR operation. if two indexes both have expression or not, we continue to
|
|
* check next condition, otherwise, two index could be treated as compatible.
|
|
*/
|
|
if ((indexInfo->ii_Expressions != NIL) != (!isNull)) {
|
|
continue;
|
|
}
|
|
if (!CheckSimpleAttrsConsistency(tarTuple, currAttrsArray, currKeyNum)) {
|
|
ret = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
systable_endscan(sysScan);
|
|
heap_close(indexRelation, AccessShareLock);
|
|
pfree(currAttrsArray);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* check consistency of two index, we use first attrs opclass as key to search index method
|
|
*/
|
|
static bool CheckIndexMethodConsistency(HeapTuple indexTuple, Relation indexRelation, Oid currMethodOid)
|
|
{
|
|
bool isNull = false;
|
|
bool ret = true;
|
|
oidvector* opClass = (oidvector*)DatumGetPointer(
|
|
heap_getattr(indexTuple, Anum_pg_index_indclass, RelationGetDescr(indexRelation), &isNull));
|
|
if (opClass == NULL) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("Operator class search failed!")));
|
|
}
|
|
Oid opClassOid = opClass->values[0];
|
|
HeapTuple opClassTuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opClassOid));
|
|
if (!HeapTupleIsValid(opClassTuple)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("Operator class does not exist for index compatible check.")));
|
|
}
|
|
Oid tarMethodOid = ((Form_pg_opclass)GETSTRUCT(opClassTuple))->opcmethod;
|
|
if (tarMethodOid != currMethodOid) {
|
|
ret = false;
|
|
}
|
|
ReleaseSysCache(opClassTuple);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* check column consistency: if run here, then compare two indexes' simple index col,
|
|
* if index col array is totally same, it means not compatible situation.
|
|
*/
|
|
static bool CheckSimpleAttrsConsistency(HeapTuple tarTuple, const int16* currAttrsArray, int currKeyNum)
|
|
{
|
|
Form_pg_index indexTuple = (Form_pg_index)GETSTRUCT(tarTuple);
|
|
int tarKeyNum = GetIndexKeyAttsByTuple(NULL, tarTuple);
|
|
bool ret = true;
|
|
AttrNumber* indkeyValues = NULL;
|
|
int i;
|
|
if (tarKeyNum == currKeyNum) {
|
|
size_t indkeyValuesSize = sizeof(AttrNumber) * currKeyNum;
|
|
indkeyValues = (AttrNumber*)palloc0(indkeyValuesSize);
|
|
errno_t rc = memcpy_s(indkeyValues, indkeyValuesSize, indexTuple->indkey.values, indkeyValuesSize);
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
qsort(indkeyValues, currKeyNum, sizeof(AttrNumber), AttrComparator);
|
|
for (i = 0; i < currKeyNum; i++) {
|
|
if (indkeyValues[i] != currAttrsArray[i]) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == currKeyNum) { // attrs of two index is totally same, which indicates not compatible.
|
|
ret = false;
|
|
}
|
|
}
|
|
pfree_ext(indkeyValues);
|
|
return ret;
|
|
}
|
|
|
|
static int AttrComparator(const void* a, const void* b)
|
|
{
|
|
return *(AttrNumber*)a - *(AttrNumber*)b;
|
|
}
|
|
|
|
/* Set the internal index column partoid for global partition index */
|
|
static void AddIndexColumnForGpi(IndexStmt* stmt)
|
|
{
|
|
ListCell* cell = NULL;
|
|
foreach (cell, stmt->indexIncludingParams) {
|
|
IndexElem* param = (IndexElem*)lfirst(cell);
|
|
if (strcmp(param->name, "tableoid") == 0) {
|
|
return;
|
|
}
|
|
}
|
|
IndexElem* iparam = makeNode(IndexElem);
|
|
iparam->name = pstrdup("tableoid");
|
|
iparam->expr = NULL;
|
|
iparam->indexcolname = NULL;
|
|
iparam->collation = NIL;
|
|
iparam->opclass = NIL;
|
|
stmt->indexIncludingParams = lappend(stmt->indexIncludingParams, iparam);
|
|
}
|
|
|
|
/* Set the internal index column bucketid for crossbucket index */
|
|
static void AddIndexColumnForCbi(IndexStmt* stmt)
|
|
{
|
|
ListCell* cell = NULL;
|
|
foreach (cell, stmt->indexIncludingParams) {
|
|
IndexElem* param = (IndexElem*)lfirst(cell);
|
|
if (strcmp(param->name, "tablebucketid") == 0) {
|
|
return;
|
|
}
|
|
}
|
|
IndexElem* iparam = makeNode(IndexElem);
|
|
iparam->name = pstrdup("tablebucketid");
|
|
iparam->expr = NULL;
|
|
iparam->indexcolname = NULL;
|
|
iparam->collation = NIL;
|
|
iparam->opclass = NIL;
|
|
stmt->indexIncludingParams = lappend(stmt->indexIncludingParams, iparam);
|
|
}
|
|
static void CheckIndexParamsNumber(IndexStmt* stmt) {
|
|
if (list_length(stmt->indexParams) <= 0) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("must specify at least one column")));
|
|
}
|
|
if (list_length(stmt->indexParams) > INDEX_MAX_KEYS) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_TOO_MANY_COLUMNS), errmsg("cannot use more than %d columns in an index", INDEX_MAX_KEYS)));
|
|
}
|
|
if (stmt->isGlobal && stmt->crossbucket && (list_length(stmt->indexParams) > GLOBAL_CROSSBUCKET_INDEX_MAX_KEYS)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_TOO_MANY_COLUMNS),
|
|
errmsg("cannot use more than %d columns in a global cross-bucket index",
|
|
GLOBAL_CROSSBUCKET_INDEX_MAX_KEYS)));
|
|
} else if (stmt->isGlobal && (list_length(stmt->indexParams) > INDEX_MAX_KEYS - 1)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_TOO_MANY_COLUMNS),
|
|
errmsg("cannot use more than %d columns in an global partition index", INDEX_MAX_KEYS - 1)));
|
|
} else if (stmt->crossbucket && (list_length(stmt->indexParams) > INDEX_MAX_KEYS - 1)) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_TOO_MANY_COLUMNS),
|
|
errmsg("cannot use more than %d columns in a cross-bucket index", INDEX_MAX_KEYS - 1)));
|
|
}
|
|
}
|
|
|
|
|
|
static bool CheckIdxParamsOwnPartKey(Relation rel, const List* indexParams)
|
|
{
|
|
int2vector* partKey = ((RangePartitionMap*)rel->partMap)->partitionKey;
|
|
for (int i = 0; i < partKey->dim1; i++) {
|
|
int2 attNum = partKey->values[i];
|
|
Form_pg_attribute attTup = rel->rd_att->attrs[attNum - 1];
|
|
if (!columnIsExist(rel, attTup, indexParams)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
CheckWhetherForbiddenFunctionalIdx(Oid relationId, Oid namespaceId, List* indexParams)
|
|
{
|
|
ListCell* lc = NULL;
|
|
bool isFunctionalIdx = false;
|
|
|
|
foreach (lc, indexParams) {
|
|
IndexElem* elem = (IndexElem*)lfirst(lc);
|
|
if (PointerIsValid(elem) && PointerIsValid(elem->expr)
|
|
&& nodeTag(elem->expr) == T_FuncExpr) {
|
|
isFunctionalIdx = true;
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
* If the index is not a functional index, the function will return false directly.
|
|
* */
|
|
if (likely((!isFunctionalIdx))) {
|
|
return false;
|
|
}
|
|
|
|
/* Currently, there is only one element in the forbidden list.
|
|
* Hence we can determine it using the following method briefly.
|
|
* */
|
|
if (unlikely(namespaceId == PG_DB4AI_NAMESPACE)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
/*
|
|
* @Description : Mark index indisvalid.
|
|
* @in : schemaname, idxname
|
|
* @out : None
|
|
*/
|
|
Datum gs_mark_indisvalid(PG_FUNCTION_ARGS)
|
|
{
|
|
if ((IS_PGXC_COORDINATOR && !IsConnFromCoord()) || IS_PGXC_DATANODE) {
|
|
ereport(ERROR, (errmodule(MOD_FUNCTION), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Unsupported function for users."),
|
|
errdetail("This is an inner function used for CIC."),
|
|
errcause("This function is not supported for users to execute directly."),
|
|
erraction("Please do not execute this function.")));
|
|
} else {
|
|
char* schname = PG_GETARG_CSTRING(0);
|
|
char* idxname = PG_GETARG_CSTRING(1);
|
|
if (idxname == NULL || strlen(idxname) == 0) {
|
|
ereport(ERROR, (errmodule(MOD_INDEX), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("Invalid input index name."),
|
|
errdetail("The input index name is null."),
|
|
errcause("Input empty or less parameters."),
|
|
erraction("Please input the correct index name.")));
|
|
PG_RETURN_VOID();
|
|
}
|
|
mark_indisvalid_all_cns(schname, idxname);
|
|
}
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
/* Mark the given index indisvalid, used for create index concurrently */
|
|
void mark_indisvalid_local(char* schname, char* idxname)
|
|
{
|
|
Oid idx_oid = InvalidOid;
|
|
if (schname == NULL || strlen(schname) == 0) {
|
|
idx_oid = RangeVarGetRelid(makeRangeVar(NULL, idxname, -1), NoLock, false);
|
|
} else {
|
|
idx_oid = RangeVarGetRelid(makeRangeVar(schname, idxname, -1), NoLock, false);
|
|
}
|
|
|
|
if (!OidIsValid(idx_oid)) {
|
|
ereport(ERROR, (errmodule(MOD_INDEX), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("The given schema or index name cannot find."),
|
|
errdetail("Cannot find valid oid from the given index name."),
|
|
errcause("Input error schema or index name."),
|
|
erraction("Check the input schema and index name.")));
|
|
}
|
|
|
|
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && GetTopTransactionIdIfAny() != InvalidTransactionId) {
|
|
CommitTransactionCommand();
|
|
StartTransactionCommand();
|
|
}
|
|
|
|
Relation rel = heap_open(IndexGetRelation(idx_oid, false), ShareUpdateExclusiveLock);
|
|
LockRelId heaprelid = rel->rd_lockInfo.lockRelId;
|
|
heap_close(rel, NoLock);
|
|
|
|
LockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
|
|
index_set_state_flags(idx_oid, INDEX_CREATE_SET_VALID);
|
|
/*
|
|
* The pg_index update will cause backends (including this one) to update
|
|
* relcache entries for the index itself, but we should also send a
|
|
* relcache inval on the parent table to force replanning of cached plans.
|
|
* Otherwise existing sessions might fail to use the new index where it
|
|
* would be useful. (Note that our earlier commits did not create reasons
|
|
* to replan; so relcache flush on the index itself was sufficient.)
|
|
*/
|
|
CacheInvalidateRelcacheByRelid(heaprelid.relId);
|
|
|
|
UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
|
|
}
|
|
|
|
void mark_indisvalid_all_cns(char* schname, char* idxname)
|
|
{
|
|
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
|
|
ParallelFunctionState* state = NULL;
|
|
StringInfoData buf;
|
|
initStringInfo(&buf);
|
|
appendStringInfo(&buf, "select gs_mark_indisvalid(");
|
|
if (schname == NULL || strlen(schname) == 0) {
|
|
appendStringInfo(&buf, "'', %s)", quote_literal_cstr(idxname));
|
|
} else {
|
|
appendStringInfo(&buf, "%s, %s)", quote_literal_cstr(schname), quote_literal_cstr(idxname));
|
|
}
|
|
|
|
state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_COORDS, true);
|
|
FreeParallelFunctionState(state);
|
|
pfree_ext(buf.data);
|
|
}
|
|
|
|
mark_indisvalid_local(schname, idxname);
|
|
}
|
|
|
|
#endif
|