@ -32,7 +32,9 @@
# include "catalog/index.h"
# include "catalog/index.h"
# include "catalog/namespace.h"
# include "catalog/namespace.h"
# include "catalog/objectaccess.h"
# include "catalog/objectaccess.h"
# include "catalog/partition.h"
# include "catalog/pg_am.h"
# include "catalog/pg_am.h"
# include "catalog/pg_inherits.h"
# include "catalog/toasting.h"
# include "catalog/toasting.h"
# include "commands/cluster.h"
# include "commands/cluster.h"
# include "commands/defrem.h"
# include "commands/defrem.h"
@ -68,11 +70,14 @@ typedef struct
} RelToCluster ;
} RelToCluster ;
static void cluster_multiple_rels ( List * rtcs , ClusterParams * params ) ;
static void rebuild_relation ( Relation OldHeap , Oid indexOid , bool verbose ) ;
static void rebuild_relation ( Relation OldHeap , Oid indexOid , bool verbose ) ;
static void copy_table_data ( Oid OIDNewHeap , Oid OIDOldHeap , Oid OIDOldIndex ,
static void copy_table_data ( Oid OIDNewHeap , Oid OIDOldHeap , Oid OIDOldIndex ,
bool verbose , bool * pSwapToastByContent ,
bool verbose , bool * pSwapToastByContent ,
TransactionId * pFreezeXid , MultiXactId * pCutoffMulti ) ;
TransactionId * pFreezeXid , MultiXactId * pCutoffMulti ) ;
static List * get_tables_to_cluster ( MemoryContext cluster_context ) ;
static List * get_tables_to_cluster ( MemoryContext cluster_context ) ;
static List * get_tables_to_cluster_partitioned ( MemoryContext cluster_context ,
Oid indexOid ) ;
/*---------------------------------------------------------------------------
/*---------------------------------------------------------------------------
@ -105,6 +110,10 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
ListCell * lc ;
ListCell * lc ;
ClusterParams params = { 0 } ;
ClusterParams params = { 0 } ;
bool verbose = false ;
bool verbose = false ;
Relation rel = NULL ;
Oid indexOid = InvalidOid ;
MemoryContext cluster_context ;
List * rtcs ;
/* Parse option list */
/* Parse option list */
foreach ( lc , stmt - > params )
foreach ( lc , stmt - > params )
@ -126,11 +135,13 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
if ( stmt - > relation ! = NULL )
if ( stmt - > relation ! = NULL )
{
{
/* This is the single-relation case. */
/* This is the single-relation case. */
Oid tableOid ,
Oid tableOid ;
indexOid = InvalidOid ;
Relation rel ;
/* Find, lock, and check permissions on the table */
/*
* Find , lock , and check permissions on the table . We obtain
* AccessExclusiveLock right away to avoid lock - upgrade hazard in the
* single - transaction case .
*/
tableOid = RangeVarGetRelidExtended ( stmt - > relation ,
tableOid = RangeVarGetRelidExtended ( stmt - > relation ,
AccessExclusiveLock ,
AccessExclusiveLock ,
0 ,
0 ,
@ -146,14 +157,6 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
( errcode ( ERRCODE_FEATURE_NOT_SUPPORTED ) ,
( errcode ( ERRCODE_FEATURE_NOT_SUPPORTED ) ,
errmsg ( " cannot cluster temporary tables of other sessions " ) ) ) ;
errmsg ( " cannot cluster temporary tables of other sessions " ) ) ) ;
/*
* Reject clustering a partitioned table .
*/
if ( rel - > rd_rel - > relkind = = RELKIND_PARTITIONED_TABLE )
ereport ( ERROR ,
( errcode ( ERRCODE_FEATURE_NOT_SUPPORTED ) ,
errmsg ( " cannot cluster a partitioned table " ) ) ) ;
if ( stmt - > indexname = = NULL )
if ( stmt - > indexname = = NULL )
{
{
ListCell * index ;
ListCell * index ;
@ -188,71 +191,101 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
stmt - > indexname , stmt - > relation - > relname ) ) ) ;
stmt - > indexname , stmt - > relation - > relname ) ) ) ;
}
}
/* close relation, keep lock till commit */
if ( rel - > rd_rel - > relkind ! = RELKIND_PARTITIONED_TABLE )
table_close ( rel , NoLock ) ;
{
/* close relation, keep lock till commit */
table_close ( rel , NoLock ) ;
/* Do the job. */
/* Do the job. */
cluster_rel ( tableOid , indexOid , & params ) ;
cluster_rel ( tableOid , indexOid , & params ) ;
return ;
}
}
/*
* By here , we know we are in a multi - table situation . In order to avoid
* holding locks for too long , we want to process each table in its own
* transaction . This forces us to disallow running inside a user
* transaction block .
*/
PreventInTransactionBlock ( isTopLevel , " CLUSTER " ) ;
/* Also, we need a memory context to hold our list of relations */
cluster_context = AllocSetContextCreate ( PortalContext ,
" Cluster " ,
ALLOCSET_DEFAULT_SIZES ) ;
/*
* Either we ' re processing a partitioned table , or we were not given any
* table name at all . In either case , obtain a list of relations to
* process .
*
* In the former case , an index name must have been given , so we don ' t
* need to recheck its " indisclustered " bit , but we have to check that it
* is an index that we can cluster on . In the latter case , we set the
* option bit to have indisclustered verified .
*
* Rechecking the relation itself is necessary here in all cases .
*/
params . options | = CLUOPT_RECHECK ;
if ( rel ! = NULL )
{
Assert ( rel - > rd_rel - > relkind = = RELKIND_PARTITIONED_TABLE ) ;
check_index_is_clusterable ( rel , indexOid , true , AccessShareLock ) ;
rtcs = get_tables_to_cluster_partitioned ( cluster_context , indexOid ) ;
/* close relation, releasing lock on parent table */
table_close ( rel , AccessExclusiveLock ) ;
}
}
else
else
{
{
/*
rtcs = get_tables_to_cluster ( cluster_context ) ;
* This is the " multi relation " case . We need to cluster all tables
params . options | = CLUOPT_RECHECK_ISCLUSTERED ;
* that have some index with indisclustered set .
}
*/
MemoryContext cluster_context ;
List * rvs ;
ListCell * rv ;
/*
/* Do the job. */
* We cannot run this form of CLUSTER inside a user transaction block ;
cluster_multiple_rels ( rtcs , & params ) ;
* we ' d be holding locks way too long .
*/
PreventInTransactionBlock ( isTopLevel , " CLUSTER " ) ;
/*
/* Start a new transaction for the cleanup work. */
* Create special memory context for cross - transaction storage .
StartTransactionCommand ( ) ;
*
* Since it is a child of PortalContext , it will go away even in case
* of error .
*/
cluster_context = AllocSetContextCreate ( PortalContext ,
" Cluster " ,
ALLOCSET_DEFAULT_SIZES ) ;
/*
/* Clean up working storage */
* Build the list of relations to cluster . Note that this lives in
MemoryContextDelete ( cluster_context ) ;
* cluster_context .
}
*/
rvs = get_tables_to_cluster ( cluster_context ) ;
/* Commit to get out of starting transaction */
/*
PopActiveSnapshot ( ) ;
* Given a list of relations to cluster , process each of them in a separate
CommitTransactionCommand ( ) ;
* transaction .
*
* We expect to be in a transaction at start , but there isn ' t one when we
* return .
*/
static void
cluster_multiple_rels ( List * rtcs , ClusterParams * params )
{
ListCell * lc ;
/* Ok, now that we've got them all, cluster them one by one */
/* Commit to get out of starting transaction */
foreach ( rv , rvs )
PopActiveSnapshot ( ) ;
{
CommitTransactionCommand ( ) ;
RelToCluster * rvtc = ( RelToCluster * ) lfirst ( rv ) ;
ClusterParams cluster_params = params ;
/* Start a new transaction for each relation. */
/* Cluster the tables, each in a separate transaction */
StartTransactionCommand ( ) ;
foreach ( lc , rtcs )
/* functions in indexes may want a snapshot set */
{
PushActiveSnapshot ( GetTransactionSnapshot ( ) ) ;
RelToCluster * rtc = ( RelToCluster * ) lfirst ( lc ) ;
/* Do the job. */
cluster_params . options | = CLUOPT_RECHECK ;
cluster_rel ( rvtc - > tableOid , rvtc - > indexOid ,
& cluster_params ) ;
PopActiveSnapshot ( ) ;
CommitTransactionCommand ( ) ;
}
/* Start a new transaction for the cleanup work . */
/* Start a new transaction for each relation. */
StartTransactionCommand ( ) ;
StartTransactionCommand ( ) ;
/* Clean up working storage */
/* functions in indexes may want a snapshot set */
MemoryContextDelete ( cluster_context ) ;
PushActiveSnapshot ( GetTransactionSnapshot ( ) ) ;
/* Do the job. */
cluster_rel ( rtc - > tableOid , rtc - > indexOid , params ) ;
PopActiveSnapshot ( ) ;
CommitTransactionCommand ( ) ;
}
}
}
}
@ -327,10 +360,11 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
/*
/*
* Silently skip a temp table for a remote session . Only doing this
* Silently skip a temp table for a remote session . Only doing this
* check in the " recheck " case is appropriate ( which currently means
* check in the " recheck " case is appropriate ( which currently means
* somebody is executing a database - wide CLUSTER ) , because there is
* somebody is executing a database - wide CLUSTER or on a partitioned
* another check in cluster ( ) which will stop any attempt to cluster
* table ) , because there is another check in cluster ( ) which will stop
* remote temp tables by name . There is another check in cluster_rel
* any attempt to cluster remote temp tables by name . There is
* which is redundant , but we leave it for extra safety .
* another check in cluster_rel which is redundant , but we leave it
* for extra safety .
*/
*/
if ( RELATION_IS_OTHER_TEMP ( OldHeap ) )
if ( RELATION_IS_OTHER_TEMP ( OldHeap ) )
{
{
@ -352,9 +386,11 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
}
}
/*
/*
* Check that the index is still the one with indisclustered set .
* Check that the index is still the one with indisclustered set ,
* if needed .
*/
*/
if ( ! get_index_isclustered ( indexOid ) )
if ( ( params - > options & CLUOPT_RECHECK_ISCLUSTERED ) ! = 0 & &
! get_index_isclustered ( indexOid ) )
{
{
relation_close ( OldHeap , AccessExclusiveLock ) ;
relation_close ( OldHeap , AccessExclusiveLock ) ;
pgstat_progress_end_command ( ) ;
pgstat_progress_end_command ( ) ;
@ -415,6 +451,10 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
return ;
return ;
}
}
Assert ( OldHeap - > rd_rel - > relkind = = RELKIND_RELATION | |
OldHeap - > rd_rel - > relkind = = RELKIND_MATVIEW | |
OldHeap - > rd_rel - > relkind = = RELKIND_TOASTVALUE ) ;
/*
/*
* All predicate locks on the tuples or pages are about to be made
* All predicate locks on the tuples or pages are about to be made
* invalid , because we move tuples around . Promote them to relation
* invalid , because we move tuples around . Promote them to relation
@ -585,8 +625,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
TransactionId frozenXid ;
TransactionId frozenXid ;
MultiXactId cutoffMulti ;
MultiXactId cutoffMulti ;
/* Mark the correct index as clustered */
if ( OidIsValid ( indexOid ) )
if ( OidIsValid ( indexOid ) )
/* Mark the correct index as clustered */
mark_index_clustered ( OldHeap , indexOid , true ) ;
mark_index_clustered ( OldHeap , indexOid , true ) ;
/* Remember info about rel before closing OldHeap */
/* Remember info about rel before closing OldHeap */
@ -1528,8 +1568,8 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
/*
/*
* Reset the relrewrite for the toast . The command - counter
* Reset the relrewrite for the toast . The command - counter
* increment is required here as we are about to update
* increment is required here as we are about to update the tuple
* the tuple th at is updated as part of RenameRelationInternal .
* that is updated as part of RenameRelationInternal .
*/
*/
CommandCounterIncrement ( ) ;
CommandCounterIncrement ( ) ;
ResetRelRewrite ( newrel - > rd_rel - > reltoastrelid ) ;
ResetRelRewrite ( newrel - > rd_rel - > reltoastrelid ) ;
@ -1564,8 +1604,7 @@ get_tables_to_cluster(MemoryContext cluster_context)
HeapTuple indexTuple ;
HeapTuple indexTuple ;
Form_pg_index index ;
Form_pg_index index ;
MemoryContext old_context ;
MemoryContext old_context ;
RelToCluster * rvtc ;
List * rtcs = NIL ;
List * rvs = NIL ;
/*
/*
* Get all indexes that have indisclustered set and are owned by
* Get all indexes that have indisclustered set and are owned by
@ -1579,21 +1618,20 @@ get_tables_to_cluster(MemoryContext cluster_context)
scan = table_beginscan_catalog ( indRelation , 1 , & entry ) ;
scan = table_beginscan_catalog ( indRelation , 1 , & entry ) ;
while ( ( indexTuple = heap_getnext ( scan , ForwardScanDirection ) ) ! = NULL )
while ( ( indexTuple = heap_getnext ( scan , ForwardScanDirection ) ) ! = NULL )
{
{
RelToCluster * rtc ;
index = ( Form_pg_index ) GETSTRUCT ( indexTuple ) ;
index = ( Form_pg_index ) GETSTRUCT ( indexTuple ) ;
if ( ! pg_class_ownercheck ( index - > indrelid , GetUserId ( ) ) )
if ( ! pg_class_ownercheck ( index - > indrelid , GetUserId ( ) ) )
continue ;
continue ;
/*
/* Use a permanent memory context for the result list */
* We have to build the list in a different memory context so it will
* survive the cross - transaction processing
*/
old_context = MemoryContextSwitchTo ( cluster_context ) ;
old_context = MemoryContextSwitchTo ( cluster_context ) ;
rv tc = ( RelToCluster * ) palloc ( sizeof ( RelToCluster ) ) ;
rtc = ( RelToCluster * ) palloc ( sizeof ( RelToCluster ) ) ;
rv tc - > tableOid = index - > indrelid ;
rtc - > tableOid = index - > indrelid ;
rv tc - > indexOid = index - > indexrelid ;
rtc - > indexOid = index - > indexrelid ;
rv s = lappend ( rv s , rv tc ) ;
rtc s = lappend ( rtc s , rtc ) ;
MemoryContextSwitchTo ( old_context ) ;
MemoryContextSwitchTo ( old_context ) ;
}
}
@ -1601,5 +1639,48 @@ get_tables_to_cluster(MemoryContext cluster_context)
relation_close ( indRelation , AccessShareLock ) ;
relation_close ( indRelation , AccessShareLock ) ;
return rvs ;
return rtcs ;
}
/*
* Given an index on a partitioned table , return a list of RelToCluster for
* all the children leaves tables / indexes .
*
* Caller must hold lock on the table containing the index .
*
* Like expand_vacuum_rel , but here caller must hold AccessExclusiveLock
* on the table already .
*/
static List *
get_tables_to_cluster_partitioned ( MemoryContext cluster_context , Oid indexOid )
{
List * inhoids ;
ListCell * lc ;
List * rtcs = NIL ;
MemoryContext old_context ;
/* Do not lock the children until they're processed */
inhoids = find_all_inheritors ( indexOid , NoLock , NULL ) ;
/* Use a permanent memory context for the result list */
old_context = MemoryContextSwitchTo ( cluster_context ) ;
foreach ( lc , inhoids )
{
Oid indexrelid = lfirst_oid ( lc ) ;
Oid relid = IndexGetRelation ( indexrelid , false ) ;
RelToCluster * rtc ;
/* consider only leaf indexes */
if ( get_rel_relkind ( indexrelid ) ! = RELKIND_INDEX )
continue ;
rtc = ( RelToCluster * ) palloc ( sizeof ( RelToCluster ) ) ;
rtc - > tableOid = relid ;
rtc - > indexOid = indexrelid ;
rtcs = lappend ( rtcs , rtc ) ;
}
MemoryContextSwitchTo ( old_context ) ;
return rtcs ;
}
}