Header And Logo

PostgreSQL
| The world's most advanced open source database.

Functions

cluster.h File Reference

#include "nodes/parsenodes.h"
#include "storage/lock.h"
#include "utils/relcache.h"
Include dependency graph for cluster.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Functions

void cluster (ClusterStmt *stmt, bool isTopLevel)
void cluster_rel (Oid tableOid, Oid indexOid, bool recheck, bool verbose, int freeze_min_age, int freeze_table_age)
void check_index_is_clusterable (Relation OldHeap, Oid indexOid, bool recheck, LOCKMODE lockmode)
void mark_index_clustered (Relation rel, Oid indexOid, bool is_internal)
Oid make_new_heap (Oid OIDOldHeap, Oid NewTableSpace)
void finish_heap_swap (Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, bool check_constraints, bool is_internal, TransactionId frozenXid, MultiXactId frozenMulti)

Function Documentation

void check_index_is_clusterable ( Relation  OldHeap,
Oid  indexOid,
bool  recheck,
LOCKMODE  lockmode 
)

Definition at line 421 of file cluster.c.

References Anum_pg_index_indpred, ereport, errcode(), errmsg(), ERROR, heap_attisnull(), index_close(), index_open(), IndexIsValid, NoLock, NULL, RelationData::rd_am, RelationData::rd_index, RelationData::rd_indextuple, RelationGetRelationName, and RelationGetRelid.

Referenced by ATExecClusterOn(), and cluster_rel().

{
    Relation    OldIndex;

    OldIndex = index_open(indexOid, lockmode);

    /*
     * Check that index is in fact an index on the given relation
     */
    if (OldIndex->rd_index == NULL ||
        OldIndex->rd_index->indrelid != RelationGetRelid(OldHeap))
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                 errmsg("\"%s\" is not an index for table \"%s\"",
                        RelationGetRelationName(OldIndex),
                        RelationGetRelationName(OldHeap))));

    /* Index AM must allow clustering */
    if (!OldIndex->rd_am->amclusterable)
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("cannot cluster on index \"%s\" because access method does not support clustering",
                        RelationGetRelationName(OldIndex))));

    /*
     * Disallow clustering on incomplete indexes (those that might not index
     * every row of the relation).  We could relax this by making a separate
     * seqscan pass over the table to copy the missing rows, but that seems
     * expensive and tedious.
     */
    if (!heap_attisnull(OldIndex->rd_indextuple, Anum_pg_index_indpred))
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("cannot cluster on partial index \"%s\"",
                        RelationGetRelationName(OldIndex))));

    /*
     * Disallow if index is left over from a failed CREATE INDEX CONCURRENTLY;
     * it might well not contain entries for every heap row, or might not even
     * be internally consistent.  (But note that we don't check indcheckxmin;
     * the worst consequence of following broken HOT chains would be that we
     * might put recently-dead tuples out-of-order in the new table, and there
     * is little harm in that.)
     */
    if (!IndexIsValid(OldIndex->rd_index))
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("cannot cluster on invalid index \"%s\"",
                        RelationGetRelationName(OldIndex))));

    /* Drop relcache refcnt on OldIndex, but keep lock */
    index_close(OldIndex, NoLock);
}

void cluster ( ClusterStmt stmt,
bool  isTopLevel 
)

Definition at line 105 of file cluster.c.

References AccessExclusiveLock, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, ALLOCSET_DEFAULT_MINSIZE, AllocSetContextCreate(), cluster_rel(), CommitTransactionCommand(), elog, ereport, errcode(), errmsg(), ERROR, get_relname_relid(), get_tables_to_cluster(), GETSTRUCT, GetTransactionSnapshot(), heap_close, heap_open(), HeapTupleIsValid, ClusterStmt::indexname, RelToCluster::indexOid, INDEXRELID, lfirst, lfirst_oid, MemoryContextDelete(), NoLock, NULL, ObjectIdGetDatum, OidIsValid, PopActiveSnapshot(), PortalContext, PreventTransactionChain(), PushActiveSnapshot(), RangeVarCallbackOwnsTable(), RangeVarGetRelidExtended(), RelationData::rd_rel, ClusterStmt::relation, RELATION_IS_OTHER_TEMP, RelationGetIndexList(), ReleaseSysCache(), RangeVar::relname, SearchSysCache1, StartTransactionCommand(), RelToCluster::tableOid, and ClusterStmt::verbose.

Referenced by standard_ProcessUtility().

{
    if (stmt->relation != NULL)
    {
        /* This is the single-relation case. */
        Oid         tableOid,
                    indexOid = InvalidOid;
        Relation    rel;

        /* Find, lock, and check permissions on the table */
        tableOid = RangeVarGetRelidExtended(stmt->relation,
                                            AccessExclusiveLock,
                                            false, false,
                                            RangeVarCallbackOwnsTable, NULL);
        rel = heap_open(tableOid, NoLock);

        /*
         * Reject clustering a remote temp table ... their local buffer
         * manager is not going to cope.
         */
        if (RELATION_IS_OTHER_TEMP(rel))
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
               errmsg("cannot cluster temporary tables of other sessions")));

        if (stmt->indexname == NULL)
        {
            ListCell   *index;

            /* We need to find the index that has indisclustered set. */
            foreach(index, RelationGetIndexList(rel))
            {
                HeapTuple   idxtuple;
                Form_pg_index indexForm;

                indexOid = lfirst_oid(index);
                idxtuple = SearchSysCache1(INDEXRELID,
                                           ObjectIdGetDatum(indexOid));
                if (!HeapTupleIsValid(idxtuple))
                    elog(ERROR, "cache lookup failed for index %u", indexOid);
                indexForm = (Form_pg_index) GETSTRUCT(idxtuple);
                if (indexForm->indisclustered)
                {
                    ReleaseSysCache(idxtuple);
                    break;
                }
                ReleaseSysCache(idxtuple);
                indexOid = InvalidOid;
            }

            if (!OidIsValid(indexOid))
                ereport(ERROR,
                        (errcode(ERRCODE_UNDEFINED_OBJECT),
                         errmsg("there is no previously clustered index for table \"%s\"",
                                stmt->relation->relname)));
        }
        else
        {
            /*
             * The index is expected to be in the same namespace as the
             * relation.
             */
            indexOid = get_relname_relid(stmt->indexname,
                                         rel->rd_rel->relnamespace);
            if (!OidIsValid(indexOid))
                ereport(ERROR,
                        (errcode(ERRCODE_UNDEFINED_OBJECT),
                       errmsg("index \"%s\" for table \"%s\" does not exist",
                              stmt->indexname, stmt->relation->relname)));
        }

        /* close relation, keep lock till commit */
        heap_close(rel, NoLock);

        /* Do the job */
        cluster_rel(tableOid, indexOid, false, stmt->verbose, -1, -1);
    }
    else
    {
        /*
         * This is the "multi relation" case. We need to cluster all tables
         * that have some index with indisclustered set.
         */
        MemoryContext cluster_context;
        List       *rvs;
        ListCell   *rv;

        /*
         * We cannot run this form of CLUSTER inside a user transaction block;
         * we'd be holding locks way too long.
         */
        PreventTransactionChain(isTopLevel, "CLUSTER");

        /*
         * Create special memory context for cross-transaction storage.
         *
         * Since it is a child of PortalContext, it will go away even in case
         * of error.
         */
        cluster_context = AllocSetContextCreate(PortalContext,
                                                "Cluster",
                                                ALLOCSET_DEFAULT_MINSIZE,
                                                ALLOCSET_DEFAULT_INITSIZE,
                                                ALLOCSET_DEFAULT_MAXSIZE);

        /*
         * Build the list of relations to cluster.  Note that this lives in
         * cluster_context.
         */
        rvs = get_tables_to_cluster(cluster_context);

        /* Commit to get out of starting transaction */
        PopActiveSnapshot();
        CommitTransactionCommand();

        /* Ok, now that we've got them all, cluster them one by one */
        foreach(rv, rvs)
        {
            RelToCluster *rvtc = (RelToCluster *) lfirst(rv);

            /* Start a new transaction for each relation. */
            StartTransactionCommand();
            /* functions in indexes may want a snapshot set */
            PushActiveSnapshot(GetTransactionSnapshot());
            cluster_rel(rvtc->tableOid, rvtc->indexOid, true, stmt->verbose,
                        -1, -1);
            PopActiveSnapshot();
            CommitTransactionCommand();
        }

        /* Start a new transaction for the cleanup work. */
        StartTransactionCommand();

        /* Clean up working storage */
        MemoryContextDelete(cluster_context);
    }
}

void cluster_rel ( Oid  tableOid,
Oid  indexOid,
bool  recheck,
bool  verbose,
int  freeze_min_age,
int  freeze_table_age 
)

Definition at line 261 of file cluster.c.

References AccessExclusiveLock, CHECK_FOR_INTERRUPTS, check_index_is_clusterable(), CheckTableNotInUse(), ereport, errcode(), errmsg(), ERROR, GETSTRUCT, GetUserId(), HeapTupleIsValid, INDEXRELID, ObjectIdGetDatum, OidIsValid, pg_class_ownercheck(), RelationData::rd_ispopulated, RelationData::rd_rel, rebuild_relation(), relation_close(), RELATION_IS_OTHER_TEMP, ReleaseSysCache(), RELKIND_MATVIEW, RELOID, SearchSysCache1, SearchSysCacheExists1, TransferPredicateLocksToHeapRelation(), and try_relation_open().

Referenced by cluster(), and vacuum_rel().

{
    Relation    OldHeap;

    /* Check for user-requested abort. */
    CHECK_FOR_INTERRUPTS();

    /*
     * We grab exclusive access to the target rel and index for the duration
     * of the transaction.  (This is redundant for the single-transaction
     * case, since cluster() already did it.)  The index lock is taken inside
     * check_index_is_clusterable.
     */
    OldHeap = try_relation_open(tableOid, AccessExclusiveLock);

    /* If the table has gone away, we can skip processing it */
    if (!OldHeap)
        return;

    /*
     * Since we may open a new transaction for each relation, we have to check
     * that the relation still is what we think it is.
     *
     * If this is a single-transaction CLUSTER, we can skip these tests. We
     * *must* skip the one on indisclustered since it would reject an attempt
     * to cluster a not-previously-clustered index.
     */
    if (recheck)
    {
        HeapTuple   tuple;
        Form_pg_index indexForm;

        /* Check that the user still owns the relation */
        if (!pg_class_ownercheck(tableOid, GetUserId()))
        {
            relation_close(OldHeap, AccessExclusiveLock);
            return;
        }

        /*
         * Silently skip a temp table for a remote session.  Only doing this
         * check in the "recheck" case is appropriate (which currently means
         * somebody is executing a database-wide CLUSTER), because there is
         * another check in cluster() which will stop any attempt to cluster
         * remote temp tables by name.  There is another check in cluster_rel
         * which is redundant, but we leave it for extra safety.
         */
        if (RELATION_IS_OTHER_TEMP(OldHeap))
        {
            relation_close(OldHeap, AccessExclusiveLock);
            return;
        }

        if (OidIsValid(indexOid))
        {
            /*
             * Check that the index still exists
             */
            if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))
            {
                relation_close(OldHeap, AccessExclusiveLock);
                return;
            }

            /*
             * Check that the index is still the one with indisclustered set.
             */
            tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid));
            if (!HeapTupleIsValid(tuple))       /* probably can't happen */
            {
                relation_close(OldHeap, AccessExclusiveLock);
                return;
            }
            indexForm = (Form_pg_index) GETSTRUCT(tuple);
            if (!indexForm->indisclustered)
            {
                ReleaseSysCache(tuple);
                relation_close(OldHeap, AccessExclusiveLock);
                return;
            }
            ReleaseSysCache(tuple);
        }
    }

    /*
     * We allow VACUUM FULL, but not CLUSTER, on shared catalogs.  CLUSTER
     * would work in most respects, but the index would only get marked as
     * indisclustered in the current database, leading to unexpected behavior
     * if CLUSTER were later invoked in another database.
     */
    if (OidIsValid(indexOid) && OldHeap->rd_rel->relisshared)
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("cannot cluster a shared catalog")));

    /*
     * Don't process temp tables of other backends ... their local buffer
     * manager is not going to cope.
     */
    if (RELATION_IS_OTHER_TEMP(OldHeap))
    {
        if (OidIsValid(indexOid))
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
               errmsg("cannot cluster temporary tables of other sessions")));
        else
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("cannot vacuum temporary tables of other sessions")));
    }

    /*
     * Also check for active uses of the relation in the current transaction,
     * including open scans and pending AFTER trigger events.
     */
    CheckTableNotInUse(OldHeap, OidIsValid(indexOid) ? "CLUSTER" : "VACUUM");

    /* Check heap and index are valid to cluster on */
    if (OidIsValid(indexOid))
        check_index_is_clusterable(OldHeap, indexOid, recheck, AccessExclusiveLock);

    /*
     * Quietly ignore the request if this is a materialized view which has not
     * been populated from its query. No harm is done because there is no data
     * to deal with, and we don't want to throw an error if this is part of a
     * multi-relation request -- for example, CLUSTER was run on the entire
     * database.
     */
    if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW &&
        !OldHeap->rd_ispopulated)
    {
        relation_close(OldHeap, AccessExclusiveLock);
        return;
    }

    /*
     * All predicate locks on the tuples or pages are about to be made
     * invalid, because we move tuples around.  Promote them to relation
     * locks.  Predicate locks on indexes will be promoted when they are
     * reindexed.
     */
    TransferPredicateLocksToHeapRelation(OldHeap);

    /* rebuild_relation does all the dirty work */
    rebuild_relation(OldHeap, indexOid, freeze_min_age, freeze_table_age,
                     verbose);

    /* NB: rebuild_relation does heap_close() on OldHeap */
}

void finish_heap_swap ( Oid  OIDOldHeap,
Oid  OIDNewHeap,
bool  is_system_catalog,
bool  swap_toast_by_content,
bool  check_constraints,
bool  is_internal,
TransactionId  frozenXid,
MultiXactId  frozenMulti 
)

Definition at line 1445 of file cluster.c.

References AccessShareLock, CacheInvalidateCatalog(), DROP_RESTRICT, heap_open(), i, NAMEDATALEN, NoLock, OidIsValid, PERFORM_DELETION_INTERNAL, performDeletion(), RelationData::rd_rel, reindex_relation(), relation_close(), relation_open(), RelationMapRemoveMapping(), RelationRelationId, RenameRelationInternal(), snprintf(), and swap_relation_files().

Referenced by ATRewriteTables(), ExecRefreshMatView(), and rebuild_relation().

{
    ObjectAddress object;
    Oid         mapped_tables[4];
    int         reindex_flags;
    int         i;

    /* Zero out possible results from swapped_relation_files */
    memset(mapped_tables, 0, sizeof(mapped_tables));

    /*
     * Swap the contents of the heap relations (including any toast tables).
     * Also set old heap's relfrozenxid to frozenXid.
     */
    swap_relation_files(OIDOldHeap, OIDNewHeap,
                        (OIDOldHeap == RelationRelationId),
                        swap_toast_by_content, is_internal,
                        frozenXid, frozenMulti, mapped_tables);

    /*
     * If it's a system catalog, queue an sinval message to flush all
     * catcaches on the catalog when we reach CommandCounterIncrement.
     */
    if (is_system_catalog)
        CacheInvalidateCatalog(OIDOldHeap);

    /*
     * Rebuild each index on the relation (but not the toast table, which is
     * all-new at this point).  It is important to do this before the DROP
     * step because if we are processing a system catalog that will be used
     * during DROP, we want to have its indexes available.  There is no
     * advantage to the other order anyway because this is all transactional,
     * so no chance to reclaim disk space before commit.  We do not need a
     * final CommandCounterIncrement() because reindex_relation does it.
     *
     * Note: because index_build is called via reindex_relation, it will never
     * set indcheckxmin true for the indexes.  This is OK even though in some
     * sense we are building new indexes rather than rebuilding existing ones,
     * because the new heap won't contain any HOT chains at all, let alone
     * broken ones, so it can't be necessary to set indcheckxmin.
     */
    reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;
    if (check_constraints)
        reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
    reindex_relation(OIDOldHeap, reindex_flags);

    /* Destroy new heap with old filenode */
    object.classId = RelationRelationId;
    object.objectId = OIDNewHeap;
    object.objectSubId = 0;

    /*
     * The new relation is local to our transaction and we know nothing
     * depends on it, so DROP_RESTRICT should be OK.
     */
    performDeletion(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);

    /* performDeletion does CommandCounterIncrement at end */

    /*
     * Now we must remove any relation mapping entries that we set up for the
     * transient table, as well as its toast table and toast index if any. If
     * we fail to do this before commit, the relmapper will complain about new
     * permanent map entries being added post-bootstrap.
     */
    for (i = 0; OidIsValid(mapped_tables[i]); i++)
        RelationMapRemoveMapping(mapped_tables[i]);

    /*
     * At this point, everything is kosher except that, if we did toast swap
     * by links, the toast table's name corresponds to the transient table.
     * The name is irrelevant to the backend because it's referenced by OID,
     * but users looking at the catalogs could be confused.  Rename it to
     * prevent this problem.
     *
     * Note no lock required on the relation, because we already hold an
     * exclusive lock on it.
     */
    if (!swap_toast_by_content)
    {
        Relation    newrel;

        newrel = heap_open(OIDOldHeap, NoLock);
        if (OidIsValid(newrel->rd_rel->reltoastrelid))
        {
            Relation    toastrel;
            Oid         toastidx;
            char        NewToastName[NAMEDATALEN];

            toastrel = relation_open(newrel->rd_rel->reltoastrelid,
                                     AccessShareLock);
            toastidx = toastrel->rd_rel->reltoastidxid;
            relation_close(toastrel, AccessShareLock);

            /* rename the toast table ... */
            snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u",
                     OIDOldHeap);
            RenameRelationInternal(newrel->rd_rel->reltoastrelid,
                                   NewToastName, true);

            /* ... and its index too */
            snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u_index",
                     OIDOldHeap);
            RenameRelationInternal(toastidx,
                                   NewToastName, true);
        }
        relation_close(newrel, NoLock);
    }
}

Oid make_new_heap ( Oid  OIDOldHeap,
Oid  NewTableSpace 
)

Definition at line 614 of file cluster.c.

References AccessExclusiveLock, AlterTableCreateToastTable(), Anum_pg_class_reloptions, Assert, CommandCounterIncrement(), elog, ERROR, heap_close, heap_create_with_catalog(), heap_open(), HeapTupleIsValid, InvalidOid, NIL, NoLock, ObjectIdGetDatum, OidIsValid, ONCOMMIT_NOOP, RelationData::rd_rel, RelationGetDescr, RelationGetNamespace, RelationIsMapped, ReleaseSysCache(), RELOID, SearchSysCache1, snprintf(), and SysCacheGetAttr().

Referenced by ATRewriteTables(), ExecRefreshMatView(), and rebuild_relation().

{
    TupleDesc   OldHeapDesc;
    char        NewHeapName[NAMEDATALEN];
    Oid         OIDNewHeap;
    Oid         toastid;
    Relation    OldHeap;
    HeapTuple   tuple;
    Datum       reloptions;
    bool        isNull;

    OldHeap = heap_open(OIDOldHeap, AccessExclusiveLock);
    OldHeapDesc = RelationGetDescr(OldHeap);

    /*
     * Note that the NewHeap will not receive any of the defaults or
     * constraints associated with the OldHeap; we don't need 'em, and there's
     * no reason to spend cycles inserting them into the catalogs only to
     * delete them.
     */

    /*
     * But we do want to use reloptions of the old heap for new heap.
     */
    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(OIDOldHeap));
    if (!HeapTupleIsValid(tuple))
        elog(ERROR, "cache lookup failed for relation %u", OIDOldHeap);
    reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
                                 &isNull);
    if (isNull)
        reloptions = (Datum) 0;

    /*
     * Create the new heap, using a temporary name in the same namespace as
     * the existing table.  NOTE: there is some risk of collision with user
     * relnames.  Working around this seems more trouble than it's worth; in
     * particular, we can't create the new heap in a different namespace from
     * the old, or we will have problems with the TEMP status of temp tables.
     *
     * Note: the new heap is not a shared relation, even if we are rebuilding
     * a shared rel.  However, we do make the new heap mapped if the source is
     * mapped.  This simplifies swap_relation_files, and is absolutely
     * necessary for rebuilding pg_class, for reasons explained there.
     */
    snprintf(NewHeapName, sizeof(NewHeapName), "pg_temp_%u", OIDOldHeap);

    OIDNewHeap = heap_create_with_catalog(NewHeapName,
                                          RelationGetNamespace(OldHeap),
                                          NewTableSpace,
                                          InvalidOid,
                                          InvalidOid,
                                          InvalidOid,
                                          OldHeap->rd_rel->relowner,
                                          OldHeapDesc,
                                          NIL,
                                          OldHeap->rd_rel->relkind,
                                          OldHeap->rd_rel->relpersistence,
                                          false,
                                          RelationIsMapped(OldHeap),
                                          true,
                                          0,
                                          ONCOMMIT_NOOP,
                                          reloptions,
                                          false,
                                          true,
                                          true);
    Assert(OIDNewHeap != InvalidOid);

    ReleaseSysCache(tuple);

    /*
     * Advance command counter so that the newly-created relation's catalog
     * tuples will be visible to heap_open.
     */
    CommandCounterIncrement();

    /*
     * If necessary, create a TOAST table for the new relation.
     *
     * If the relation doesn't have a TOAST table already, we can't need one
     * for the new relation.  The other way around is possible though: if some
     * wide columns have been dropped, AlterTableCreateToastTable can decide
     * that no TOAST table is needed for the new table.
     *
     * Note that AlterTableCreateToastTable ends with CommandCounterIncrement,
     * so that the TOAST table will be visible for insertion.
     */
    toastid = OldHeap->rd_rel->reltoastrelid;
    if (OidIsValid(toastid))
    {
        /* keep the existing toast table's reloptions, if any */
        tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
        if (!HeapTupleIsValid(tuple))
            elog(ERROR, "cache lookup failed for relation %u", toastid);
        reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
                                     &isNull);
        if (isNull)
            reloptions = (Datum) 0;

        AlterTableCreateToastTable(OIDNewHeap, reloptions);

        ReleaseSysCache(tuple);
    }

    heap_close(OldHeap, NoLock);

    return OIDNewHeap;
}

void mark_index_clustered ( Relation  rel,
Oid  indexOid,
bool  is_internal 
)

Definition at line 486 of file cluster.c.

References CatalogUpdateIndexes(), elog, ERROR, GETSTRUCT, heap_close, heap_freetuple(), heap_open(), HeapTupleIsValid, IndexIsValid, IndexRelationId, INDEXRELID, InvalidOid, InvokeObjectPostAlterHookArg, lfirst_oid, ObjectIdGetDatum, OidIsValid, RelationGetIndexList(), ReleaseSysCache(), RowExclusiveLock, SearchSysCache1, SearchSysCacheCopy1, simple_heap_update(), and HeapTupleData::t_self.

Referenced by ATExecClusterOn(), ATExecDropCluster(), and rebuild_relation().

{
    HeapTuple   indexTuple;
    Form_pg_index indexForm;
    Relation    pg_index;
    ListCell   *index;

    /*
     * If the index is already marked clustered, no need to do anything.
     */
    if (OidIsValid(indexOid))
    {
        indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid));
        if (!HeapTupleIsValid(indexTuple))
            elog(ERROR, "cache lookup failed for index %u", indexOid);
        indexForm = (Form_pg_index) GETSTRUCT(indexTuple);

        if (indexForm->indisclustered)
        {
            ReleaseSysCache(indexTuple);
            return;
        }

        ReleaseSysCache(indexTuple);
    }

    /*
     * Check each index of the relation and set/clear the bit as needed.
     */
    pg_index = heap_open(IndexRelationId, RowExclusiveLock);

    foreach(index, RelationGetIndexList(rel))
    {
        Oid         thisIndexOid = lfirst_oid(index);

        indexTuple = SearchSysCacheCopy1(INDEXRELID,
                                         ObjectIdGetDatum(thisIndexOid));
        if (!HeapTupleIsValid(indexTuple))
            elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
        indexForm = (Form_pg_index) GETSTRUCT(indexTuple);

        /*
         * Unset the bit if set.  We know it's wrong because we checked this
         * earlier.
         */
        if (indexForm->indisclustered)
        {
            indexForm->indisclustered = false;
            simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
            CatalogUpdateIndexes(pg_index, indexTuple);
        }
        else if (thisIndexOid == indexOid)
        {
            /* this was checked earlier, but let's be real sure */
            if (!IndexIsValid(indexForm))
                elog(ERROR, "cannot cluster on invalid index %u", indexOid);
            indexForm->indisclustered = true;
            simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
            CatalogUpdateIndexes(pg_index, indexTuple);
        }

        InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0,
                                     InvalidOid, is_internal);

        heap_freetuple(indexTuple);
    }

    heap_close(pg_index, RowExclusiveLock);
}