1996-07-09 06:22:35 +00:00
/*-------------------------------------------------------------------------
*
1999-02-13 23:22:53 +00:00
* cluster . c
2010-02-07 20:48:13 +00:00
* CLUSTER a table on an index . This is now also used for VACUUM FULL .
2002-08-11 21:17:35 +00:00
*
* There is hardly anything left of Paul Brown ' s original implementation . . .
1996-07-09 06:22:35 +00:00
*
*
2010-01-02 16:58:17 +00:00
* Portions Copyright ( c ) 1996 - 2010 , PostgreSQL Global Development Group
2000-01-26 05:58:53 +00:00
* Portions Copyright ( c ) 1994 - 5 , Regents of the University of California
1996-07-09 06:22:35 +00:00
*
*
* IDENTIFICATION
2010-02-07 22:40:33 +00:00
* $ PostgreSQL : pgsql / src / backend / commands / cluster . c , v 1.199 2010 / 02 / 07 22 : 40 : 33 tgl Exp $
1996-07-09 06:22:35 +00:00
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
1999-07-15 23:04:24 +00:00
# include "postgres.h"
1996-07-09 06:22:35 +00:00
1999-07-15 23:04:24 +00:00
# include "access/genam.h"
1999-07-16 05:00:38 +00:00
# include "access/heapam.h"
2008-06-19 00:46:06 +00:00
# include "access/relscan.h"
2007-04-08 01:26:33 +00:00
# include "access/rewriteheap.h"
2007-05-19 01:02:34 +00:00
# include "access/transam.h"
2006-07-13 16:49:20 +00:00
# include "access/xact.h"
2002-08-11 21:17:35 +00:00
# include "catalog/catalog.h"
2002-07-12 18:43:19 +00:00
# include "catalog/dependency.h"
1999-07-16 05:00:38 +00:00
# include "catalog/heap.h"
1999-07-15 23:04:24 +00:00
# include "catalog/index.h"
2002-08-10 20:43:46 +00:00
# include "catalog/indexing.h"
2002-11-15 03:09:39 +00:00
# include "catalog/namespace.h"
2008-10-14 17:19:50 +00:00
# include "catalog/pg_namespace.h"
2006-07-31 01:16:38 +00:00
# include "catalog/toasting.h"
1999-07-15 23:04:24 +00:00
# include "commands/cluster.h"
2008-01-30 19:46:48 +00:00
# include "commands/tablecmds.h"
2008-01-02 23:34:42 +00:00
# include "commands/trigger.h"
2007-05-17 15:28:29 +00:00
# include "commands/vacuum.h"
2000-07-04 06:11:54 +00:00
# include "miscadmin.h"
2008-05-12 00:00:54 +00:00
# include "storage/bufmgr.h"
2007-04-08 01:26:33 +00:00
# include "storage/procarray.h"
2002-11-15 03:09:39 +00:00
# include "utils/acl.h"
2002-08-10 20:43:46 +00:00
# include "utils/fmgroids.h"
2004-05-06 16:10:57 +00:00
# include "utils/inval.h"
2002-03-29 22:10:34 +00:00
# include "utils/lsyscache.h"
2005-05-06 17:24:55 +00:00
# include "utils/memutils.h"
2002-08-10 20:43:46 +00:00
# include "utils/relcache.h"
2010-02-07 20:48:13 +00:00
# include "utils/relmapper.h"
2008-03-26 18:48:59 +00:00
# include "utils/snapmgr.h"
2007-04-08 01:26:33 +00:00
# include "utils/syscache.h"
2008-03-26 21:10:39 +00:00
# include "utils/tqual.h"
1996-07-09 06:22:35 +00:00
2002-12-30 18:42:17 +00:00
/*
* This struct is used to pass around the information on tables to be
2002-11-15 03:09:39 +00:00
* clustered . We need this so we can make a list of them when invoked without
* a specific table / index pair .
*/
typedef struct
{
2003-08-04 00:43:34 +00:00
Oid tableOid ;
Oid indexOid ;
2003-08-08 21:42:59 +00:00
} RelToCluster ;
2002-11-15 03:09:39 +00:00
2002-12-30 18:42:17 +00:00
2010-01-06 05:31:14 +00:00
static void rebuild_relation ( Relation OldHeap , Oid indexOid ,
int freeze_min_age , int freeze_table_age ) ;
2010-02-04 00:09:14 +00:00
static void copy_heap_data ( Oid OIDNewHeap , Oid OIDOldHeap , Oid OIDOldIndex ,
int freeze_min_age , int freeze_table_age ,
bool * pSwapToastByContent , TransactionId * pFreezeXid ) ;
2002-12-30 18:42:17 +00:00
static List * get_tables_to_cluster ( MemoryContext cluster_context ) ;
/*---------------------------------------------------------------------------
2003-08-04 00:43:34 +00:00
* This cluster code allows for clustering multiple tables at once . Because
2002-12-30 18:42:17 +00:00
* of this , we cannot just run everything on a single transaction , or we
* would be forced to acquire exclusive locks on all the tables being
* clustered , simultaneously - - - very likely leading to deadlock .
*
* To solve this we follow a similar strategy to VACUUM code ,
* clustering each relation in a separate transaction . For this to work ,
* we need to :
2003-08-04 00:43:34 +00:00
* - provide a separate memory context so that we can pass information in
* a way that survives across transactions
* - start a new transaction every time a new relation is clustered
* - check for validity of the information on to - be - clustered relations ,
* as someone might have deleted a relation behind our back , or
* clustered one on a different index
* - end the transaction
2002-12-30 18:42:17 +00:00
*
* The single - relation case does not have any such overhead .
*
2007-11-15 21:14:46 +00:00
* We also allow a relation to be specified without index . In that case ,
2002-12-30 18:42:17 +00:00
* the indisclustered bit will be looked up , and an ERROR will be thrown
* if there is no index with the bit set .
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
void
2007-03-13 00:33:44 +00:00
cluster ( ClusterStmt * stmt , bool isTopLevel )
2002-12-30 18:42:17 +00:00
{
if ( stmt - > relation ! = NULL )
{
/* This is the single-relation case. */
2003-08-04 00:43:34 +00:00
Oid tableOid ,
indexOid = InvalidOid ;
Relation rel ;
2002-12-30 18:42:17 +00:00
/* Find and lock the table */
2002-12-30 19:45:17 +00:00
rel = heap_openrv ( stmt - > relation , AccessExclusiveLock ) ;
2002-12-30 18:42:17 +00:00
2002-12-30 19:45:17 +00:00
tableOid = RelationGetRelid ( rel ) ;
2002-12-30 18:42:17 +00:00
/* Check permissions */
2003-08-01 00:15:26 +00:00
if ( ! pg_class_ownercheck ( tableOid , GetUserId ( ) ) )
aclcheck_error ( ACLCHECK_NOT_OWNER , ACL_KIND_CLASS ,
RelationGetRelationName ( rel ) ) ;
2002-12-30 18:42:17 +00:00
2007-09-10 21:59:37 +00:00
/*
2007-11-15 21:14:46 +00:00
* Reject clustering a remote temp table . . . their local buffer
* manager is not going to cope .
2007-09-10 21:59:37 +00:00
*/
2009-03-31 22:12:48 +00:00
if ( RELATION_IS_OTHER_TEMP ( rel ) )
2007-09-10 21:59:37 +00:00
ereport ( ERROR ,
( errcode ( ERRCODE_FEATURE_NOT_SUPPORTED ) ,
2007-11-15 21:14:46 +00:00
errmsg ( " cannot cluster temporary tables of other sessions " ) ) ) ;
2007-09-10 21:59:37 +00:00
2002-12-30 18:42:17 +00:00
if ( stmt - > indexname = = NULL )
{
2004-05-26 04:41:50 +00:00
ListCell * index ;
2002-12-30 18:42:17 +00:00
/* We need to find the index that has indisclustered set. */
2003-08-04 00:43:34 +00:00
foreach ( index , RelationGetIndexList ( rel ) )
2002-12-30 18:42:17 +00:00
{
2003-08-04 00:43:34 +00:00
HeapTuple idxtuple ;
Form_pg_index indexForm ;
2002-12-30 18:42:17 +00:00
2004-05-26 04:41:50 +00:00
indexOid = lfirst_oid ( index ) ;
2002-12-30 18:42:17 +00:00
idxtuple = SearchSysCache ( INDEXRELID ,
ObjectIdGetDatum ( indexOid ) ,
0 , 0 , 0 ) ;
if ( ! HeapTupleIsValid ( idxtuple ) )
2003-07-20 21:56:35 +00:00
elog ( ERROR , " cache lookup failed for index %u " , indexOid ) ;
2002-12-30 18:42:17 +00:00
indexForm = ( Form_pg_index ) GETSTRUCT ( idxtuple ) ;
if ( indexForm - > indisclustered )
{
ReleaseSysCache ( idxtuple ) ;
break ;
}
ReleaseSysCache ( idxtuple ) ;
indexOid = InvalidOid ;
}
if ( ! OidIsValid ( indexOid ) )
2003-07-20 21:56:35 +00:00
ereport ( ERROR ,
( errcode ( ERRCODE_UNDEFINED_OBJECT ) ,
errmsg ( " there is no previously clustered index for table \" %s \" " ,
stmt - > relation - > relname ) ) ) ;
2002-12-30 18:42:17 +00:00
}
else
{
2003-08-04 00:43:34 +00:00
/*
* The index is expected to be in the same namespace as the
* relation .
*/
2002-12-30 18:42:17 +00:00
indexOid = get_relname_relid ( stmt - > indexname ,
rel - > rd_rel - > relnamespace ) ;
if ( ! OidIsValid ( indexOid ) )
2003-07-20 21:56:35 +00:00
ereport ( ERROR ,
( errcode ( ERRCODE_UNDEFINED_OBJECT ) ,
2005-10-15 02:49:52 +00:00
errmsg ( " index \" %s \" for table \" %s \" does not exist " ,
stmt - > indexname , stmt - > relation - > relname ) ) ) ;
2002-12-30 18:42:17 +00:00
}
/* close relation, keep lock till commit */
heap_close ( rel , NoLock ) ;
/* Do the job */
2010-01-06 05:31:14 +00:00
cluster_rel ( tableOid , indexOid , false , stmt - > verbose , - 1 , - 1 ) ;
2002-12-30 18:42:17 +00:00
}
else
{
/*
2005-10-15 02:49:52 +00:00
* This is the " multi relation " case . We need to cluster all tables
* that have some index with indisclustered set .
2002-12-30 18:42:17 +00:00
*/
2003-08-04 00:43:34 +00:00
MemoryContext cluster_context ;
2004-05-26 04:41:50 +00:00
List * rvs ;
ListCell * rv ;
2002-12-30 18:42:17 +00:00
/*
2005-10-15 02:49:52 +00:00
* We cannot run this form of CLUSTER inside a user transaction block ;
* we ' d be holding locks way too long .
2002-12-30 18:42:17 +00:00
*/
2007-03-13 00:33:44 +00:00
PreventTransactionChain ( isTopLevel , " CLUSTER " ) ;
2002-12-30 18:42:17 +00:00
/*
* Create special memory context for cross - transaction storage .
*
2005-11-22 18:17:34 +00:00
* Since it is a child of PortalContext , it will go away even in case
* of error .
2002-12-30 18:42:17 +00:00
*/
2003-05-02 20:54:36 +00:00
cluster_context = AllocSetContextCreate ( PortalContext ,
2002-12-30 18:42:17 +00:00
" Cluster " ,
ALLOCSET_DEFAULT_MINSIZE ,
ALLOCSET_DEFAULT_INITSIZE ,
ALLOCSET_DEFAULT_MAXSIZE ) ;
/*
2005-10-15 02:49:52 +00:00
* Build the list of relations to cluster . Note that this lives in
* cluster_context .
2002-12-30 18:42:17 +00:00
*/
rvs = get_tables_to_cluster ( cluster_context ) ;
/* Commit to get out of starting transaction */
2008-05-12 20:02:02 +00:00
PopActiveSnapshot ( ) ;
2003-05-14 03:26:03 +00:00
CommitTransactionCommand ( ) ;
2002-12-30 18:42:17 +00:00
/* Ok, now that we've got them all, cluster them one by one */
2003-08-04 00:43:34 +00:00
foreach ( rv , rvs )
2002-12-30 18:42:17 +00:00
{
2003-08-04 00:43:34 +00:00
RelToCluster * rvtc = ( RelToCluster * ) lfirst ( rv ) ;
2002-12-30 18:42:17 +00:00
/* Start a new transaction for each relation. */
2003-05-14 03:26:03 +00:00
StartTransactionCommand ( ) ;
2004-09-13 20:10:13 +00:00
/* functions in indexes may want a snapshot set */
2008-05-12 20:02:02 +00:00
PushActiveSnapshot ( GetTransactionSnapshot ( ) ) ;
2010-02-07 20:48:13 +00:00
cluster_rel ( rvtc - > tableOid , rvtc - > indexOid , true , stmt - > verbose ,
- 1 , - 1 ) ;
2008-05-12 20:02:02 +00:00
PopActiveSnapshot ( ) ;
2003-05-14 03:26:03 +00:00
CommitTransactionCommand ( ) ;
2002-12-30 18:42:17 +00:00
}
/* Start a new transaction for the cleanup work. */
2003-05-14 03:26:03 +00:00
StartTransactionCommand ( ) ;
2002-08-10 20:43:46 +00:00
2002-12-30 18:42:17 +00:00
/* Clean up working storage */
MemoryContextDelete ( cluster_context ) ;
}
}
1997-08-19 21:40:56 +00:00
1996-07-09 06:22:35 +00:00
/*
2002-12-30 18:42:17 +00:00
* cluster_rel
1996-07-09 06:22:35 +00:00
*
2002-08-10 20:43:46 +00:00
* This clusters the table by creating a new , clustered table and
* swapping the relfilenodes of the new table and the old table , so
2002-09-04 20:31:48 +00:00
* the OID of the original table is preserved . Thus we do not lose
2002-08-10 20:43:46 +00:00
* GRANT , inheritance nor references to this table ( this was a bug
2002-08-11 21:17:35 +00:00
* in releases thru 7.3 ) .
2002-08-10 20:43:46 +00:00
*
2010-02-07 20:48:13 +00:00
* Indexes are rebuilt too , via REINDEX . Since we are effectively bulk - loading
2002-08-11 21:17:35 +00:00
* the new table , it ' s better to create the indexes afterwards than to fill
* them incrementally while we load the table .
2010-01-06 05:31:14 +00:00
*
* If indexOid is InvalidOid , the table will be rewritten in physical order
2010-02-07 20:48:13 +00:00
* instead of index order . This is the new implementation of VACUUM FULL ,
* and error messages should refer to the operation as VACUUM not CLUSTER .
1996-07-09 06:22:35 +00:00
*/
2010-01-06 05:31:14 +00:00
void
cluster_rel ( Oid tableOid , Oid indexOid , bool recheck , bool verbose ,
int freeze_min_age , int freeze_table_age )
1996-07-09 06:22:35 +00:00
{
2004-05-06 16:10:57 +00:00
Relation OldHeap ;
1997-09-07 05:04:48 +00:00
2002-11-15 03:09:39 +00:00
/* Check for user-requested abort. */
CHECK_FOR_INTERRUPTS ( ) ;
2006-08-18 16:09:13 +00:00
/*
* 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 .
*/
2010-01-06 05:31:14 +00:00
OldHeap = try_relation_open ( tableOid , AccessExclusiveLock ) ;
2006-08-18 16:09:13 +00:00
/* If the table has gone away, we can skip processing it */
if ( ! OldHeap )
return ;
2002-12-30 18:42:17 +00:00
/*
2005-10-15 02:49:52 +00:00
* Since we may open a new transaction for each relation , we have to check
* that the relation still is what we think it is .
2002-12-30 18:42:17 +00:00
*
2005-11-22 18:17:34 +00:00
* 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 .
2002-11-15 03:09:39 +00:00
*/
2002-12-30 18:42:17 +00:00
if ( recheck )
{
2003-08-04 00:43:34 +00:00
HeapTuple tuple ;
Form_pg_index indexForm ;
2002-12-30 18:42:17 +00:00
2006-08-18 16:09:13 +00:00
/* Check that the user still owns the relation */
2010-01-06 05:31:14 +00:00
if ( ! pg_class_ownercheck ( tableOid , GetUserId ( ) ) )
2006-08-18 16:09:13 +00:00
{
relation_close ( OldHeap , AccessExclusiveLock ) ;
return ;
}
2007-09-10 21:59:37 +00:00
/*
* 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
2007-11-15 21:14:46 +00:00
* remote temp tables by name . There is another check in
2010-02-07 20:48:13 +00:00
* cluster_rel which is redundant , but we leave it for extra safety .
2007-09-10 21:59:37 +00:00
*/
2009-03-31 22:12:48 +00:00
if ( RELATION_IS_OTHER_TEMP ( OldHeap ) )
2007-09-10 21:59:37 +00:00
{
relation_close ( OldHeap , AccessExclusiveLock ) ;
return ;
}
2010-01-06 05:31:14 +00:00
if ( OidIsValid ( indexOid ) )
2006-08-18 16:09:13 +00:00
{
2010-01-06 05:31:14 +00:00
/*
* Check that the index still exists
*/
if ( ! SearchSysCacheExists ( RELOID ,
ObjectIdGetDatum ( indexOid ) ,
0 , 0 , 0 ) )
{
relation_close ( OldHeap , AccessExclusiveLock ) ;
return ;
}
2002-11-15 03:09:39 +00:00
2010-01-06 05:31:14 +00:00
/*
* Check that the index is still the one with indisclustered set .
*/
tuple = SearchSysCache ( INDEXRELID ,
ObjectIdGetDatum ( indexOid ) ,
0 , 0 , 0 ) ;
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 ;
}
2002-11-15 03:09:39 +00:00
ReleaseSysCache ( tuple ) ;
}
}
2010-02-07 20:48:13 +00:00
/*
* 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 " ) ;
2010-01-06 05:31:14 +00:00
/* Check heap and index are valid to cluster on */
2010-02-07 20:48:13 +00:00
if ( OidIsValid ( indexOid ) )
check_index_is_clusterable ( OldHeap , indexOid , recheck ) ;
2004-05-06 16:10:57 +00:00
2010-02-07 20:48:13 +00:00
/* Log what we're doing (this could use more effort) */
2010-01-06 05:31:14 +00:00
if ( OidIsValid ( indexOid ) )
ereport ( verbose ? INFO : DEBUG2 ,
( errmsg ( " clustering \" %s.%s \" " ,
get_namespace_name ( RelationGetNamespace ( OldHeap ) ) ,
RelationGetRelationName ( OldHeap ) ) ) ) ;
else
ereport ( verbose ? INFO : DEBUG2 ,
( errmsg ( " vacuuming \" %s.%s \" " ,
get_namespace_name ( RelationGetNamespace ( OldHeap ) ) ,
RelationGetRelationName ( OldHeap ) ) ) ) ;
2010-02-07 20:48:13 +00:00
/* rebuild_relation does all the dirty work */
2010-01-06 05:31:14 +00:00
rebuild_relation ( OldHeap , indexOid , freeze_min_age , freeze_table_age ) ;
2004-05-06 16:10:57 +00:00
/* NB: rebuild_relation does heap_close() on OldHeap */
}
/*
2010-01-06 05:31:14 +00:00
* Verify that the specified heap and index are valid to cluster on
2004-05-06 16:10:57 +00:00
*
* Side effect : obtains exclusive lock on the index . The caller should
* already have exclusive lock on the table , so the index lock is likely
* redundant , but it seems best to grab it anyway to ensure the index
* definition can ' t change under us .
*/
void
2005-05-10 13:16:26 +00:00
check_index_is_clusterable ( Relation OldHeap , Oid indexOid , bool recheck )
2004-05-06 16:10:57 +00:00
{
Relation OldIndex ;
2006-07-31 20:09:10 +00:00
OldIndex = index_open ( indexOid , AccessExclusiveLock ) ;
2001-01-10 01:12:28 +00:00
2000-05-11 03:54:18 +00:00
/*
2000-11-08 22:10:03 +00:00
* Check that index is in fact an index on the given relation
2000-05-11 03:54:18 +00:00
*/
2002-08-11 21:17:35 +00:00
if ( OldIndex - > rd_index = = NULL | |
2004-05-06 16:10:57 +00:00
OldIndex - > rd_index - > indrelid ! = RelationGetRelid ( OldHeap ) )
2003-07-20 21:56:35 +00:00
ereport ( ERROR ,
( errcode ( ERRCODE_WRONG_OBJECT_TYPE ) ,
errmsg ( " \" %s \" is not an index for table \" %s \" " ,
RelationGetRelationName ( OldIndex ) ,
RelationGetRelationName ( OldHeap ) ) ) ) ;
2000-11-08 22:10:03 +00:00
2010-02-07 20:48:13 +00:00
/* 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 ) ) ) ) ;
2003-03-03 04:37:37 +00:00
/*
2005-10-15 02:49:52 +00:00
* 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 .
2003-03-03 04:37:37 +00:00
*/
2003-05-28 16:04:02 +00:00
if ( ! heap_attisnull ( OldIndex - > rd_indextuple , Anum_pg_index_indpred ) )
2003-07-20 21:56:35 +00:00
ereport ( ERROR ,
( errcode ( ERRCODE_FEATURE_NOT_SUPPORTED ) ,
2005-05-10 13:16:26 +00:00
errmsg ( " cannot cluster on partial index \" %s \" " ,
RelationGetRelationName ( OldIndex ) ) ) ) ;
2005-10-15 02:49:52 +00:00
2003-03-03 04:37:37 +00:00
if ( ! OldIndex - > rd_am - > amindexnulls )
{
AttrNumber colno ;
/*
2005-10-15 02:49:52 +00:00
* If the AM doesn ' t index nulls , then it ' s a partial index unless we
* can prove all the rows are non - null . Note we only need look at the
* first column ; multicolumn - capable AMs are * required * to index nulls
* in columns after the first .
2003-03-03 04:37:37 +00:00
*/
2005-03-29 00:17:27 +00:00
colno = OldIndex - > rd_index - > indkey . values [ 0 ] ;
2003-05-28 16:04:02 +00:00
if ( colno > 0 )
{
/* ordinary user attribute */
2003-03-03 04:37:37 +00:00
if ( ! OldHeap - > rd_att - > attrs [ colno - 1 ] - > attnotnull )
2003-07-20 21:56:35 +00:00
ereport ( ERROR ,
( errcode ( ERRCODE_FEATURE_NOT_SUPPORTED ) ,
2005-10-29 00:31:52 +00:00
errmsg ( " cannot cluster on index \" %s \" because access method does not handle null values " ,
2005-10-15 02:49:52 +00:00
RelationGetRelationName ( OldIndex ) ) ,
2005-10-29 00:31:52 +00:00
recheck
Wording cleanup for error messages. Also change can't -> cannot.
Standard English uses "may", "can", and "might" in different ways:
may - permission, "You may borrow my rake."
can - ability, "I can lift that log."
might - possibility, "It might rain today."
Unfortunately, in conversational English, their use is often mixed, as
in, "You may use this variable to do X", when in fact, "can" is a better
choice. Similarly, "It may crash" is better stated, "It might crash".
2007-02-01 19:10:30 +00:00
? errhint ( " You might be able to work around this by marking column \" %s \" NOT NULL, or use ALTER TABLE ... SET WITHOUT CLUSTER to remove the cluster specification from the table. " ,
2005-11-22 18:17:34 +00:00
NameStr ( OldHeap - > rd_att - > attrs [ colno - 1 ] - > attname ) )
Wording cleanup for error messages. Also change can't -> cannot.
Standard English uses "may", "can", and "might" in different ways:
may - permission, "You may borrow my rake."
can - ability, "I can lift that log."
might - possibility, "It might rain today."
Unfortunately, in conversational English, their use is often mixed, as
in, "You may use this variable to do X", when in fact, "can" is a better
choice. Similarly, "It may crash" is better stated, "It might crash".
2007-02-01 19:10:30 +00:00
: errhint ( " You might be able to work around this by marking column \" %s \" NOT NULL. " ,
2005-11-22 18:17:34 +00:00
NameStr ( OldHeap - > rd_att - > attrs [ colno - 1 ] - > attname ) ) ) ) ;
2003-05-28 16:04:02 +00:00
}
else if ( colno < 0 )
{
/* system column --- okay, always non-null */
}
else
/* index expression, lose... */
2003-07-20 21:56:35 +00:00
ereport ( ERROR ,
( errcode ( ERRCODE_FEATURE_NOT_SUPPORTED ) ,
2005-10-29 00:31:52 +00:00
errmsg ( " cannot cluster on expressional index \" %s \" because its index access method does not handle null values " ,
2005-10-15 02:49:52 +00:00
RelationGetRelationName ( OldIndex ) ) ) ) ;
2003-03-03 04:37:37 +00:00
}
2007-09-29 18:05:20 +00:00
/*
* 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 ( ! OldIndex - > rd_index - > indisvalid )
ereport ( ERROR ,
( errcode ( ERRCODE_FEATURE_NOT_SUPPORTED ) ,
errmsg ( " cannot cluster on invalid index \" %s \" " ,
RelationGetRelationName ( OldIndex ) ) ) ) ;
2002-12-30 18:42:17 +00:00
/* Drop relcache refcnt on OldIndex, but keep lock */
2006-07-31 20:09:10 +00:00
index_close ( OldIndex , NoLock ) ;
2002-11-23 04:05:52 +00:00
}
2002-12-30 18:42:17 +00:00
/*
2004-05-08 00:34:49 +00:00
* mark_index_clustered : mark the specified index as the one clustered on
2002-12-30 18:42:17 +00:00
*
2004-05-08 00:34:49 +00:00
* With indexOid = = InvalidOid , will mark all indexes of rel not - clustered .
*/
void
mark_index_clustered ( Relation rel , Oid indexOid )
{
HeapTuple indexTuple ;
Form_pg_index indexForm ;
Relation pg_index ;
2004-05-26 04:41:50 +00:00
ListCell * index ;
2004-05-08 00:34:49 +00:00
/*
* If the index is already marked clustered , no need to do anything .
*/
if ( OidIsValid ( indexOid ) )
{
indexTuple = SearchSysCache ( INDEXRELID ,
ObjectIdGetDatum ( indexOid ) ,
0 , 0 , 0 ) ;
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 .
*/
2005-04-14 20:03:27 +00:00
pg_index = heap_open ( IndexRelationId , RowExclusiveLock ) ;
2004-05-08 00:34:49 +00:00
foreach ( index , RelationGetIndexList ( rel ) )
{
2004-08-29 05:07:03 +00:00
Oid thisIndexOid = lfirst_oid ( index ) ;
2004-05-08 00:34:49 +00:00
indexTuple = SearchSysCacheCopy ( INDEXRELID ,
ObjectIdGetDatum ( thisIndexOid ) ,
0 , 0 , 0 ) ;
if ( ! HeapTupleIsValid ( indexTuple ) )
elog ( ERROR , " cache lookup failed for index %u " , thisIndexOid ) ;
indexForm = ( Form_pg_index ) GETSTRUCT ( indexTuple ) ;
/*
2005-10-15 02:49:52 +00:00
* Unset the bit if set . We know it ' s wrong because we checked this
* earlier .
2004-05-08 00:34:49 +00:00
*/
if ( indexForm - > indisclustered )
{
indexForm - > indisclustered = false ;
simple_heap_update ( pg_index , & indexTuple - > t_self , indexTuple ) ;
CatalogUpdateIndexes ( pg_index , indexTuple ) ;
/* Ensure we see the update in the index's relcache entry */
CacheInvalidateRelcacheByRelid ( thisIndexOid ) ;
}
else if ( thisIndexOid = = indexOid )
{
indexForm - > indisclustered = true ;
simple_heap_update ( pg_index , & indexTuple - > t_self , indexTuple ) ;
CatalogUpdateIndexes ( pg_index , indexTuple ) ;
/* Ensure we see the update in the index's relcache entry */
CacheInvalidateRelcacheByRelid ( thisIndexOid ) ;
}
heap_freetuple ( indexTuple ) ;
}
heap_close ( pg_index , RowExclusiveLock ) ;
}
/*
2010-01-06 05:31:14 +00:00
* rebuild_relation : rebuild an existing relation in index or physical order
2002-12-30 18:42:17 +00:00
*
* OldHeap : table to rebuild - - - must be opened and exclusive - locked !
2010-01-06 05:31:14 +00:00
* indexOid : index to cluster by , or InvalidOid to rewrite in physical order .
2002-12-30 18:42:17 +00:00
*
* NB : this routine closes OldHeap at the right time ; caller should not .
*/
2004-05-08 00:34:49 +00:00
static void
2010-01-06 05:31:14 +00:00
rebuild_relation ( Relation OldHeap , Oid indexOid ,
int freeze_min_age , int freeze_table_age )
2002-11-23 04:05:52 +00:00
{
2002-12-30 18:42:17 +00:00
Oid tableOid = RelationGetRelid ( OldHeap ) ;
2004-07-11 23:13:58 +00:00
Oid tableSpace = OldHeap - > rd_rel - > reltablespace ;
2002-11-23 04:05:52 +00:00
Oid OIDNewHeap ;
2010-02-07 20:48:13 +00:00
bool is_system_catalog ;
2010-02-04 00:09:14 +00:00
bool swap_toast_by_content ;
2007-05-18 23:19:42 +00:00
TransactionId frozenXid ;
2002-11-23 04:05:52 +00:00
2004-05-08 00:34:49 +00:00
/* Mark the correct index as clustered */
2010-01-06 05:31:14 +00:00
if ( OidIsValid ( indexOid ) )
mark_index_clustered ( OldHeap , indexOid ) ;
2002-12-30 18:42:17 +00:00
2010-02-07 20:48:13 +00:00
/* Remember if it's a system catalog */
is_system_catalog = IsSystemRelation ( OldHeap ) ;
2002-12-30 18:42:17 +00:00
/* Close relcache entry, but keep lock until transaction commit */
heap_close ( OldHeap , NoLock ) ;
2010-02-04 00:09:14 +00:00
/* Create the transient table that will receive the re-ordered data */
OIDNewHeap = make_new_heap ( tableOid , tableSpace ) ;
2002-07-12 18:43:19 +00:00
2010-02-04 00:09:14 +00:00
/* Copy the heap data into the new table in the desired order */
copy_heap_data ( OIDNewHeap , tableOid , indexOid ,
freeze_min_age , freeze_table_age ,
& swap_toast_by_content , & frozenXid ) ;
1998-01-10 05:19:22 +00:00
2010-02-07 20:48:13 +00:00
/*
* Swap the physical files of the target and transient tables , then
* rebuild the target ' s indexes and throw away the transient table .
*/
finish_heap_swap ( tableOid , OIDNewHeap , is_system_catalog ,
swap_toast_by_content , frozenXid ) ;
1996-07-09 06:22:35 +00:00
}
2010-02-04 00:09:14 +00:00
2002-08-11 21:17:35 +00:00
/*
2010-02-04 00:09:14 +00:00
* Create the transient table that will be filled with new data during
* CLUSTER , ALTER TABLE , and similar operations . The transient table
* duplicates the logical structure of the OldHeap , but is placed in
* NewTableSpace which might be different from OldHeap ' s .
*
* After this , the caller should load the new heap with transferred / modified
2010-02-07 20:48:13 +00:00
* data , then call finish_heap_swap to complete the operation .
2002-08-11 21:17:35 +00:00
*/
2004-05-05 04:48:48 +00:00
Oid
2010-02-04 00:09:14 +00:00
make_new_heap ( Oid OIDOldHeap , Oid NewTableSpace )
1996-07-09 06:22:35 +00:00
{
1997-09-08 02:41:22 +00:00
TupleDesc OldHeapDesc ,
tupdesc ;
2010-02-04 00:09:14 +00:00
char NewHeapName [ NAMEDATALEN ] ;
1997-09-08 02:41:22 +00:00
Oid OIDNewHeap ;
2009-02-02 19:31:40 +00:00
Oid toastid ;
2000-11-08 22:10:03 +00:00
Relation OldHeap ;
2006-07-02 02:23:23 +00:00
HeapTuple tuple ;
2006-07-03 22:45:41 +00:00
Datum reloptions ;
bool isNull ;
1997-09-07 05:04:48 +00:00
1999-09-18 19:08:25 +00:00
OldHeap = heap_open ( OIDOldHeap , AccessExclusiveLock ) ;
1998-09-01 03:29:17 +00:00
OldHeapDesc = RelationGetDescr ( OldHeap ) ;
1997-09-07 05:04:48 +00:00
/*
2001-03-22 04:01:46 +00:00
* Need to make a copy of the tuple descriptor , since
2009-06-11 14:49:15 +00:00
* heap_create_with_catalog modifies it . 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 .
1997-09-07 05:04:48 +00:00
*/
2008-05-09 23:32:05 +00:00
tupdesc = CreateTupleDescCopy ( OldHeapDesc ) ;
1997-09-07 05:04:48 +00:00
2006-07-02 02:23:23 +00:00
/*
2010-02-04 00:09:14 +00:00
* But we do want to use reloptions of the old heap for new heap .
2006-07-02 02:23:23 +00:00
*/
tuple = SearchSysCache ( RELOID ,
ObjectIdGetDatum ( OIDOldHeap ) ,
0 , 0 , 0 ) ;
2006-07-03 22:45:41 +00:00
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 ;
2006-07-02 02:23:23 +00:00
2010-02-04 00:09:14 +00:00
/*
* 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 .
2010-02-07 20:48:13 +00:00
*
* 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 .
2010-02-04 00:09:14 +00:00
*/
snprintf ( NewHeapName , sizeof ( NewHeapName ) , " pg_temp_%u " , OIDOldHeap ) ;
OIDNewHeap = heap_create_with_catalog ( NewHeapName ,
2002-03-26 19:17:02 +00:00
RelationGetNamespace ( OldHeap ) ,
2004-08-29 05:07:03 +00:00
NewTableSpace ,
2005-04-14 01:38:22 +00:00
InvalidOid ,
2009-09-26 22:42:03 +00:00
InvalidOid ,
2010-01-28 23:21:13 +00:00
InvalidOid ,
2005-08-26 03:08:15 +00:00
OldHeap - > rd_rel - > relowner ,
2002-03-26 19:17:02 +00:00
tupdesc ,
2008-05-09 23:32:05 +00:00
NIL ,
2001-08-10 18:57:42 +00:00
OldHeap - > rd_rel - > relkind ,
2010-02-07 20:48:13 +00:00
false ,
RelationIsMapped ( OldHeap ) ,
2004-03-23 19:35:17 +00:00
true ,
0 ,
2002-11-11 22:19:25 +00:00
ONCOMMIT_NOOP ,
2006-07-03 22:45:41 +00:00
reloptions ,
2009-10-05 19:24:49 +00:00
false ,
2010-02-07 20:48:13 +00:00
true ) ;
2006-07-02 02:23:23 +00:00
ReleaseSysCache ( tuple ) ;
1997-09-07 05:04:48 +00:00
2001-01-01 21:35:00 +00:00
/*
2005-10-15 02:49:52 +00:00
* Advance command counter so that the newly - created relation ' s catalog
* tuples will be visible to heap_open .
2001-01-01 21:35:00 +00:00
*/
CommandCounterIncrement ( ) ;
/*
2010-02-07 20:48:13 +00:00
* 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 .
2001-01-01 21:35:00 +00:00
*/
2009-02-02 19:31:40 +00:00
toastid = OldHeap - > rd_rel - > reltoastrelid ;
if ( OidIsValid ( toastid ) )
{
2010-02-07 20:48:13 +00:00
/* keep the existing toast table's reloptions, if any */
2009-02-02 19:31:40 +00:00
tuple = SearchSysCache ( RELOID ,
ObjectIdGetDatum ( toastid ) ,
0 , 0 , 0 ) ;
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 ;
2010-02-07 20:48:13 +00:00
AlterTableCreateToastTable ( OIDNewHeap , reloptions ) ;
2009-02-02 19:31:40 +00:00
ReleaseSysCache ( tuple ) ;
2010-02-07 20:48:13 +00:00
}
1997-09-07 05:04:48 +00:00
2000-11-08 22:10:03 +00:00
heap_close ( OldHeap , NoLock ) ;
1997-09-07 05:04:48 +00:00
2000-11-08 22:10:03 +00:00
return OIDNewHeap ;
1996-07-09 06:22:35 +00:00
}
2002-08-11 21:17:35 +00:00
/*
2010-02-04 00:09:14 +00:00
* Do the physical copying of heap data .
*
* There are two output parameters :
* * pSwapToastByContent is set true if toast tables must be swapped by content .
* * pFreezeXid receives the TransactionId used as freeze cutoff point .
2002-08-11 21:17:35 +00:00
*/
2010-02-04 00:09:14 +00:00
static void
2010-01-06 05:31:14 +00:00
copy_heap_data ( Oid OIDNewHeap , Oid OIDOldHeap , Oid OIDOldIndex ,
2010-02-04 00:09:14 +00:00
int freeze_min_age , int freeze_table_age ,
bool * pSwapToastByContent , TransactionId * pFreezeXid )
1996-07-09 06:22:35 +00:00
{
2002-08-11 21:17:35 +00:00
Relation NewHeap ,
OldHeap ,
OldIndex ;
2005-02-06 20:19:08 +00:00
TupleDesc oldTupDesc ;
TupleDesc newTupDesc ;
int natts ;
Datum * values ;
2007-04-08 01:26:33 +00:00
bool * isnull ;
2010-01-06 05:31:14 +00:00
IndexScanDesc indexScan ;
HeapScanDesc heapScan ;
2007-03-29 00:15:39 +00:00
bool use_wal ;
2010-02-07 20:48:13 +00:00
bool is_system_catalog ;
2007-04-08 01:26:33 +00:00
TransactionId OldestXmin ;
2007-05-17 15:28:29 +00:00
TransactionId FreezeXid ;
2007-04-08 01:26:33 +00:00
RewriteState rwstate ;
1997-09-07 05:04:48 +00:00
/*
2005-02-06 20:19:08 +00:00
* Open the relations we need .
1997-09-07 05:04:48 +00:00
*/
2002-08-11 21:17:35 +00:00
NewHeap = heap_open ( OIDNewHeap , AccessExclusiveLock ) ;
OldHeap = heap_open ( OIDOldHeap , AccessExclusiveLock ) ;
2010-01-06 05:31:14 +00:00
if ( OidIsValid ( OIDOldIndex ) )
OldIndex = index_open ( OIDOldIndex , AccessExclusiveLock ) ;
else
OldIndex = NULL ;
1997-09-07 05:04:48 +00:00
2005-02-06 20:19:08 +00:00
/*
2005-10-15 02:49:52 +00:00
* Their tuple descriptors should be exactly alike , but here we only need
* assume that they have the same number of columns .
2005-02-06 20:19:08 +00:00
*/
oldTupDesc = RelationGetDescr ( OldHeap ) ;
newTupDesc = RelationGetDescr ( NewHeap ) ;
Assert ( newTupDesc - > natts = = oldTupDesc - > natts ) ;
2007-04-08 01:26:33 +00:00
/* Preallocate values/isnull arrays */
2005-02-06 20:19:08 +00:00
natts = newTupDesc - > natts ;
2007-04-08 01:26:33 +00:00
values = ( Datum * ) palloc ( natts * sizeof ( Datum ) ) ;
isnull = ( bool * ) palloc ( natts * sizeof ( bool ) ) ;
2005-02-06 20:19:08 +00:00
2007-03-29 00:15:39 +00:00
/*
2010-01-15 09:19:10 +00:00
* We need to log the copied data in WAL iff WAL archiving / streaming
* is enabled AND it ' s not a temp rel .
2007-03-29 00:15:39 +00:00
*/
2010-01-15 09:19:10 +00:00
use_wal = XLogIsNeeded ( ) & & ! NewHeap - > rd_istemp ;
2007-03-29 00:15:39 +00:00
2010-01-20 19:43:40 +00:00
/*
* Write an XLOG UNLOGGED record if WAL - logging was skipped because
* WAL archiving is not enabled .
*/
if ( ! use_wal & & ! NewHeap - > rd_istemp )
{
2010-02-07 20:48:13 +00:00
char reason [ NAMEDATALEN + 32 ] ;
if ( OldIndex ! = NULL )
snprintf ( reason , sizeof ( reason ) , " CLUSTER on \" %s \" " ,
RelationGetRelationName ( NewHeap ) ) ;
else
snprintf ( reason , sizeof ( reason ) , " VACUUM FULL on \" %s \" " ,
RelationGetRelationName ( NewHeap ) ) ;
2010-01-20 19:43:40 +00:00
XLogReportUnloggedStatement ( reason ) ;
}
2007-03-29 00:15:39 +00:00
/* use_wal off requires rd_targblock be initially invalid */
Assert ( NewHeap - > rd_targblock = = InvalidBlockNumber ) ;
2010-02-04 00:09:14 +00:00
/*
* If both tables have TOAST tables , perform toast swap by content . It is
* possible that the old table has a toast table but the new one doesn ' t ,
* if toastable columns have been dropped . In that case we have to do
* swap by links . This is okay because swap by content is only essential
* for system catalogs , and we don ' t support schema changes for them .
*/
if ( OldHeap - > rd_rel - > reltoastrelid & & NewHeap - > rd_rel - > reltoastrelid )
{
* pSwapToastByContent = true ;
/*
* When doing swap by content , any toast pointers written into NewHeap
* must use the old toast table ' s OID , because that ' s where the toast
* data will eventually be found . Set this up by setting rd_toastoid .
* Note that we must hold NewHeap open until we are done writing data ,
* since the relcache will not guarantee to remember this setting once
* the relation is closed . Also , this technique depends on the fact
* that no one will try to read from the NewHeap until after we ' ve
* finished writing it and swapping the rels - - - otherwise they could
* follow the toast pointers to the wrong place .
*/
NewHeap - > rd_toastoid = OldHeap - > rd_rel - > reltoastrelid ;
}
else
* pSwapToastByContent = false ;
2007-05-17 15:28:29 +00:00
/*
* compute xids used to freeze and weed out dead tuples . We use - 1
2007-11-15 21:14:46 +00:00
* freeze_min_age to avoid having CLUSTER freeze tuples earlier than a
* plain VACUUM would .
2007-05-17 15:28:29 +00:00
*/
2010-01-06 05:31:14 +00:00
vacuum_set_xid_limits ( freeze_min_age , freeze_table_age ,
2010-02-04 00:09:14 +00:00
OldHeap - > rd_rel - > relisshared ,
& OldestXmin , & FreezeXid , NULL ) ;
2007-04-08 01:26:33 +00:00
2008-01-15 21:20:28 +00:00
/*
2009-06-11 14:49:15 +00:00
* FreezeXid will become the table ' s new relfrozenxid , and that mustn ' t go
* backwards , so take the max .
2008-01-15 21:20:28 +00:00
*/
if ( TransactionIdPrecedes ( FreezeXid , OldHeap - > rd_rel - > relfrozenxid ) )
FreezeXid = OldHeap - > rd_rel - > relfrozenxid ;
2010-02-04 00:09:14 +00:00
/* return selected value to caller */
* pFreezeXid = FreezeXid ;
2010-02-07 20:48:13 +00:00
/* Remember if it's a system catalog */
is_system_catalog = IsSystemRelation ( OldHeap ) ;
2007-04-08 01:26:33 +00:00
/* Initialize the rewrite operation */
2007-05-17 15:28:29 +00:00
rwstate = begin_heap_rewrite ( NewHeap , OldestXmin , FreezeXid , use_wal ) ;
2007-04-08 01:26:33 +00:00
2005-02-06 20:19:08 +00:00
/*
2010-02-04 00:09:14 +00:00
* Scan through the OldHeap , either in OldIndex order or sequentially ,
* and copy each tuple into the NewHeap . To ensure we see recently - dead
* tuples that still need to be copied , we scan with SnapshotAny and use
* HeapTupleSatisfiesVacuum for the visibility test .
2005-02-06 20:19:08 +00:00
*/
2010-01-06 05:31:14 +00:00
if ( OldIndex ! = NULL )
2010-01-06 11:25:39 +00:00
{
heapScan = NULL ;
2010-01-06 05:31:14 +00:00
indexScan = index_beginscan ( OldHeap , OldIndex ,
2010-02-04 00:09:14 +00:00
SnapshotAny , 0 , ( ScanKey ) NULL ) ;
2010-01-06 11:25:39 +00:00
}
2010-01-06 05:31:14 +00:00
else
2010-01-06 11:25:39 +00:00
{
2010-01-06 05:31:14 +00:00
heapScan = heap_beginscan ( OldHeap , SnapshotAny , 0 , ( ScanKey ) NULL ) ;
2010-01-06 11:25:39 +00:00
indexScan = NULL ;
}
1997-09-07 05:04:48 +00:00
2010-01-06 05:31:14 +00:00
for ( ; ; )
1997-09-07 05:04:48 +00:00
{
2010-01-06 05:31:14 +00:00
HeapTuple tuple ;
2007-04-08 01:26:33 +00:00
HeapTuple copiedTuple ;
2010-01-06 05:31:14 +00:00
Buffer buf ;
2007-04-08 01:26:33 +00:00
bool isdead ;
int i ;
CHECK_FOR_INTERRUPTS ( ) ;
2010-01-06 05:31:14 +00:00
if ( OldIndex ! = NULL )
{
tuple = index_getnext ( indexScan , ForwardScanDirection ) ;
if ( tuple = = NULL )
break ;
2008-04-13 19:18:14 +00:00
2010-01-06 05:31:14 +00:00
/* Since we used no scan keys, should never need to recheck */
if ( indexScan - > xs_recheck )
elog ( ERROR , " CLUSTER does not support lossy index conditions " ) ;
2007-04-08 01:26:33 +00:00
2010-01-06 05:31:14 +00:00
buf = indexScan - > xs_cbuf ;
}
else
{
tuple = heap_getnext ( heapScan , ForwardScanDirection ) ;
if ( tuple = = NULL )
break ;
buf = heapScan - > rs_cbuf ;
}
LockBuffer ( buf , BUFFER_LOCK_SHARE ) ;
switch ( HeapTupleSatisfiesVacuum ( tuple - > t_data , OldestXmin , buf ) )
2007-04-08 01:26:33 +00:00
{
case HEAPTUPLE_DEAD :
/* Definitely dead */
isdead = true ;
break ;
case HEAPTUPLE_LIVE :
case HEAPTUPLE_RECENTLY_DEAD :
/* Live or recently dead, must copy it */
isdead = false ;
break ;
case HEAPTUPLE_INSERT_IN_PROGRESS :
2007-11-15 21:14:46 +00:00
2007-04-08 01:26:33 +00:00
/*
2010-02-07 20:48:13 +00:00
* Since we hold exclusive lock on the relation , normally
* the only way to see this is if it was inserted earlier
* in our own transaction . However , it can happen in system
* catalogs , since we tend to release write lock before commit
* there . Give a warning if neither case applies ; but in
* any case we had better copy it .
2007-04-08 01:26:33 +00:00
*/
2010-02-07 20:48:13 +00:00
if ( ! is_system_catalog & &
! TransactionIdIsCurrentTransactionId ( HeapTupleHeaderGetXmin ( tuple - > t_data ) ) )
elog ( WARNING , " concurrent insert in progress within table \" %s \" " ,
RelationGetRelationName ( OldHeap ) ) ;
2007-04-08 01:26:33 +00:00
/* treat as live */
isdead = false ;
break ;
case HEAPTUPLE_DELETE_IN_PROGRESS :
2007-11-15 21:14:46 +00:00
2007-04-08 01:26:33 +00:00
/*
2010-02-07 20:48:13 +00:00
* Similar situation to INSERT_IN_PROGRESS case .
2007-04-08 01:26:33 +00:00
*/
Assert ( ! ( tuple - > t_data - > t_infomask & HEAP_XMAX_IS_MULTI ) ) ;
2010-02-07 20:48:13 +00:00
if ( ! is_system_catalog & &
! TransactionIdIsCurrentTransactionId ( HeapTupleHeaderGetXmax ( tuple - > t_data ) ) )
elog ( WARNING , " concurrent delete in progress within table \" %s \" " ,
RelationGetRelationName ( OldHeap ) ) ;
2007-04-08 01:26:33 +00:00
/* treat as recently dead */
isdead = false ;
break ;
default :
elog ( ERROR , " unexpected HeapTupleSatisfiesVacuum result " ) ;
2007-11-15 21:14:46 +00:00
isdead = false ; /* keep compiler quiet */
2007-04-08 01:26:33 +00:00
break ;
}
2010-01-06 05:31:14 +00:00
LockBuffer ( buf , BUFFER_LOCK_UNLOCK ) ;
2007-04-08 01:26:33 +00:00
if ( isdead )
{
/* heap rewrite module still needs to see it... */
rewrite_heap_dead_tuple ( rwstate , tuple ) ;
continue ;
}
2002-05-20 23:51:44 +00:00
/*
2007-04-08 01:26:33 +00:00
* We cannot simply copy the tuple as - is , for several reasons :
2005-02-06 20:19:08 +00:00
*
2007-04-08 01:26:33 +00:00
* 1. We ' d like to squeeze out the values of any dropped columns , both
2005-11-22 18:17:34 +00:00
* to save space and to ensure we have no corner - case failures . ( It ' s
2005-10-15 02:49:52 +00:00
* possible for example that the new table hasn ' t got a TOAST table
* and so is unable to store any large values of dropped cols . )
2002-08-11 21:17:35 +00:00
*
2007-04-08 01:26:33 +00:00
* 2. The tuple might not even be legal for the new table ; this is
2005-02-06 20:19:08 +00:00
* currently only known to happen as an after - effect of ALTER TABLE
* SET WITHOUT OIDS .
*
* So , we must reconstruct the tuple from component Datums .
2002-05-20 23:51:44 +00:00
*/
2007-04-08 01:26:33 +00:00
heap_deform_tuple ( tuple , oldTupDesc , values , isnull ) ;
2005-02-06 20:19:08 +00:00
/* Be sure to null out any dropped columns */
for ( i = 0 ; i < natts ; i + + )
{
if ( newTupDesc - > attrs [ i ] - > attisdropped )
2007-04-08 01:26:33 +00:00
isnull [ i ] = true ;
2005-02-06 20:19:08 +00:00
}
2007-04-08 01:26:33 +00:00
copiedTuple = heap_form_tuple ( newTupDesc , values , isnull ) ;
2005-02-06 20:19:08 +00:00
/* Preserve OID, if any */
if ( NewHeap - > rd_rel - > relhasoids )
HeapTupleSetOid ( copiedTuple , HeapTupleGetOid ( tuple ) ) ;
2002-08-11 21:17:35 +00:00
2007-04-08 01:26:33 +00:00
/* The heap rewrite module does the rest */
rewrite_heap_tuple ( rwstate , tuple , copiedTuple ) ;
2002-05-20 23:51:44 +00:00
heap_freetuple ( copiedTuple ) ;
1997-09-07 05:04:48 +00:00
}
2000-05-11 03:54:18 +00:00
2010-01-06 05:31:14 +00:00
if ( OldIndex ! = NULL )
index_endscan ( indexScan ) ;
else
heap_endscan ( heapScan ) ;
1997-09-07 05:04:48 +00:00
2007-04-08 01:26:33 +00:00
/* Write out any remaining tuples, and fsync if needed */
end_heap_rewrite ( rwstate ) ;
2005-02-06 20:19:08 +00:00
2010-02-04 00:09:14 +00:00
/* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */
NewHeap - > rd_toastoid = InvalidOid ;
/* Clean up */
2007-04-08 01:26:33 +00:00
pfree ( values ) ;
pfree ( isnull ) ;
2007-03-29 00:15:39 +00:00
2010-01-06 05:31:14 +00:00
if ( OldIndex ! = NULL )
index_close ( OldIndex , NoLock ) ;
2002-08-11 21:17:35 +00:00
heap_close ( OldHeap , NoLock ) ;
heap_close ( NewHeap , NoLock ) ;
1996-07-09 06:22:35 +00:00
}
2002-08-10 20:43:46 +00:00
2002-08-11 21:17:35 +00:00
/*
2004-07-11 23:13:58 +00:00
* Swap the physical files of two given relations .
*
* We swap the physical identity ( reltablespace and relfilenode ) while
* keeping the same logical identities of the two relations .
2002-08-11 21:17:35 +00:00
*
2010-02-04 00:09:14 +00:00
* We can swap associated TOAST data in either of two ways : recursively swap
* the physical content of the toast tables ( and their indexes ) , or swap the
* TOAST links in the given relations ' pg_class entries . The former is needed
* to manage rewrites of shared catalogs ( where we cannot change the pg_class
* links ) while the latter is the only way to handle cases in which a toast
* table is added or removed altogether .
2007-05-18 23:19:42 +00:00
*
* Additionally , the first relation is marked with relfrozenxid set to
2010-02-07 20:48:13 +00:00
* frozenXid . It seems a bit ugly to have this here , but the caller would
2010-02-04 00:09:14 +00:00
* have to do it anyway , so having it here saves a heap_update . Note : in
* the swap - toast - links case , we assume we don ' t need to change the toast
* table ' s relfrozenxid : the new version of the toast table should already
* have relfrozenxid set to RecentXmin , which is good enough .
2010-02-07 20:48:13 +00:00
*
* Lastly , if r2 and its toast table and toast index ( if any ) are mapped ,
* their OIDs are emitted into mapped_tables [ ] . This is hacky but beats
* having to look the information up again later in finish_heap_swap .
2002-08-10 20:43:46 +00:00
*/
2010-02-07 20:48:13 +00:00
static void
swap_relation_files ( Oid r1 , Oid r2 , bool target_is_pg_class ,
bool swap_toast_by_content ,
TransactionId frozenXid ,
Oid * mapped_tables )
2002-08-10 20:43:46 +00:00
{
2005-03-20 22:00:54 +00:00
Relation relRelation ;
2002-08-11 21:17:35 +00:00
HeapTuple reltup1 ,
reltup2 ;
Form_pg_class relform1 ,
relform2 ;
2010-02-07 20:48:13 +00:00
Oid relfilenode1 ,
relfilenode2 ;
2002-08-11 21:17:35 +00:00
Oid swaptemp ;
2002-08-10 21:00:34 +00:00
CatalogIndexState indstate ;
2002-08-10 20:43:46 +00:00
2002-08-11 21:17:35 +00:00
/* We need writable copies of both pg_class tuples. */
2005-04-14 20:03:27 +00:00
relRelation = heap_open ( RelationRelationId , RowExclusiveLock ) ;
2002-08-10 20:43:46 +00:00
2002-08-11 21:17:35 +00:00
reltup1 = SearchSysCacheCopy ( RELOID ,
ObjectIdGetDatum ( r1 ) ,
0 , 0 , 0 ) ;
if ( ! HeapTupleIsValid ( reltup1 ) )
2003-07-20 21:56:35 +00:00
elog ( ERROR , " cache lookup failed for relation %u " , r1 ) ;
2002-08-11 21:17:35 +00:00
relform1 = ( Form_pg_class ) GETSTRUCT ( reltup1 ) ;
reltup2 = SearchSysCacheCopy ( RELOID ,
ObjectIdGetDatum ( r2 ) ,
0 , 0 , 0 ) ;
if ( ! HeapTupleIsValid ( reltup2 ) )
2003-07-20 21:56:35 +00:00
elog ( ERROR , " cache lookup failed for relation %u " , r2 ) ;
2002-08-11 21:17:35 +00:00
relform2 = ( Form_pg_class ) GETSTRUCT ( reltup2 ) ;
2002-08-10 20:43:46 +00:00
2010-02-07 20:48:13 +00:00
relfilenode1 = relform1 - > relfilenode ;
relfilenode2 = relform2 - > relfilenode ;
2002-08-10 20:43:46 +00:00
2010-02-07 20:48:13 +00:00
if ( OidIsValid ( relfilenode1 ) & & OidIsValid ( relfilenode2 ) )
{
/* Normal non-mapped relations: swap relfilenodes and reltablespaces */
Assert ( ! target_is_pg_class ) ;
2004-07-11 23:13:58 +00:00
2010-02-07 20:48:13 +00:00
swaptemp = relform1 - > relfilenode ;
relform1 - > relfilenode = relform2 - > relfilenode ;
relform2 - > relfilenode = swaptemp ;
swaptemp = relform1 - > reltablespace ;
relform1 - > reltablespace = relform2 - > reltablespace ;
relform2 - > reltablespace = swaptemp ;
/* Also swap toast links, if we're swapping by links */
if ( ! swap_toast_by_content )
{
swaptemp = relform1 - > reltoastrelid ;
relform1 - > reltoastrelid = relform2 - > reltoastrelid ;
relform2 - > reltoastrelid = swaptemp ;
/* we should NOT swap reltoastidxid */
}
}
else
2010-02-04 00:09:14 +00:00
{
2010-02-07 20:48:13 +00:00
/*
* Mapped - relation case . Here we have to swap the relation mappings
* instead of modifying the pg_class columns . Both must be mapped .
*/
if ( OidIsValid ( relfilenode1 ) | | OidIsValid ( relfilenode2 ) )
elog ( ERROR , " cannot swap mapped relation \" %s \" with non-mapped relation " ,
NameStr ( relform1 - > relname ) ) ;
/*
* We can ' t change the tablespace of a mapped rel , and we can ' t handle
* toast link swapping for one either , because we must not apply any
* critical changes to its pg_class row . These cases should be
* prevented by upstream permissions tests , so this check is a
* non - user - facing emergency backstop .
*/
if ( relform1 - > reltablespace ! = relform2 - > reltablespace )
elog ( ERROR , " cannot change tablespace of mapped relation \" %s \" " ,
NameStr ( relform1 - > relname ) ) ;
if ( ! swap_toast_by_content & &
( relform1 - > reltoastrelid | | relform2 - > reltoastrelid ) )
elog ( ERROR , " cannot swap toast by links for mapped relation \" %s \" " ,
NameStr ( relform1 - > relname ) ) ;
2002-08-10 20:43:46 +00:00
2010-02-07 20:48:13 +00:00
/*
* Fetch the mappings - - - shouldn ' t fail , but be paranoid
*/
relfilenode1 = RelationMapOidToFilenode ( r1 , relform1 - > relisshared ) ;
if ( ! OidIsValid ( relfilenode1 ) )
elog ( ERROR , " could not find relation mapping for relation \" %s \" , OID %u " ,
NameStr ( relform1 - > relname ) , r1 ) ;
relfilenode2 = RelationMapOidToFilenode ( r2 , relform2 - > relisshared ) ;
if ( ! OidIsValid ( relfilenode2 ) )
elog ( ERROR , " could not find relation mapping for relation \" %s \" , OID %u " ,
NameStr ( relform2 - > relname ) , r2 ) ;
/*
* Send replacement mappings to relmapper . Note these won ' t actually
* take effect until CommandCounterIncrement .
*/
RelationMapUpdateMap ( r1 , relfilenode2 , relform1 - > relisshared , false ) ;
RelationMapUpdateMap ( r2 , relfilenode1 , relform2 - > relisshared , false ) ;
/* Pass OIDs of mapped r2 tables back to caller */
* mapped_tables + + = r2 ;
2010-02-04 00:09:14 +00:00
}
/*
2010-02-07 20:48:13 +00:00
* In the case of a shared catalog , these next few steps will only affect
* our own database ' s pg_class row ; but that ' s okay , because they are
* all noncritical updates . That ' s also an important fact for the case
* of a mapped catalog , because it ' s possible that we ' ll commit the map
* change and then fail to commit the pg_class update .
2010-02-04 00:09:14 +00:00
*/
2002-08-10 20:43:46 +00:00
2007-05-18 23:19:42 +00:00
/* set rel1's frozen Xid */
2010-02-04 00:09:14 +00:00
if ( relform1 - > relkind ! = RELKIND_INDEX )
{
Assert ( TransactionIdIsNormal ( frozenXid ) ) ;
relform1 - > relfrozenxid = frozenXid ;
}
2007-05-18 23:19:42 +00:00
2002-11-02 21:20:40 +00:00
/* swap size statistics too, since new rel has freshly-updated stats */
{
2003-08-04 00:43:34 +00:00
int4 swap_pages ;
float4 swap_tuples ;
2002-11-02 21:20:40 +00:00
swap_pages = relform1 - > relpages ;
relform1 - > relpages = relform2 - > relpages ;
relform2 - > relpages = swap_pages ;
swap_tuples = relform1 - > reltuples ;
relform1 - > reltuples = relform2 - > reltuples ;
relform2 - > reltuples = swap_tuples ;
}
2010-02-07 20:48:13 +00:00
/*
* Update the tuples in pg_class - - - unless the target relation of the
* swap is pg_class itself . In that case , there is zero point in making
* changes because we ' d be updating the old data that we ' re about to
* throw away . Because the real work being done here for a mapped relation
* is just to change the relation map settings , it ' s all right to not
* update the pg_class rows in this case .
*/
if ( ! target_is_pg_class )
{
simple_heap_update ( relRelation , & reltup1 - > t_self , reltup1 ) ;
simple_heap_update ( relRelation , & reltup2 - > t_self , reltup2 ) ;
/* Keep system catalogs current */
indstate = CatalogOpenIndexes ( relRelation ) ;
CatalogIndexInsert ( indstate , reltup1 ) ;
CatalogIndexInsert ( indstate , reltup2 ) ;
CatalogCloseIndexes ( indstate ) ;
}
else
{
/* no update ... but we do still need relcache inval */
CacheInvalidateRelcacheByTuple ( reltup1 ) ;
CacheInvalidateRelcacheByTuple ( reltup2 ) ;
}
2002-08-10 20:43:46 +00:00
2002-08-11 21:17:35 +00:00
/*
2005-10-15 02:49:52 +00:00
* If we have toast tables associated with the relations being swapped ,
2010-02-04 00:09:14 +00:00
* deal with them too .
2002-08-11 21:17:35 +00:00
*/
if ( relform1 - > reltoastrelid | | relform2 - > reltoastrelid )
{
2010-02-04 00:09:14 +00:00
if ( swap_toast_by_content )
2004-05-05 04:48:48 +00:00
{
2010-02-04 00:09:14 +00:00
if ( relform1 - > reltoastrelid & & relform2 - > reltoastrelid )
{
/* Recursively swap the contents of the toast tables */
swap_relation_files ( relform1 - > reltoastrelid ,
relform2 - > reltoastrelid ,
2010-02-07 20:48:13 +00:00
target_is_pg_class ,
swap_toast_by_content ,
frozenXid ,
mapped_tables ) ;
2010-02-04 00:09:14 +00:00
}
else
{
/* caller messed up */
elog ( ERROR , " cannot swap toast files by content when there's only one " ) ;
}
2004-05-05 04:48:48 +00:00
}
2010-02-04 00:09:14 +00:00
else
2004-05-05 04:48:48 +00:00
{
2010-02-04 00:09:14 +00:00
/*
* We swapped the ownership links , so we need to change dependency
* data to match .
*
* NOTE : it is possible that only one table has a toast table .
*
* NOTE : at present , a TOAST table ' s only dependency is the one on
* its owning table . If more are ever created , we ' d need to use
* something more selective than deleteDependencyRecordsFor ( ) to
* get rid of just the link we want .
*/
ObjectAddress baseobject ,
toastobject ;
long count ;
2010-02-07 20:48:13 +00:00
/*
* We disallow this case for system catalogs , to avoid the
* possibility that the catalog we ' re rebuilding is one of the
* ones the dependency changes would change . It ' s too late
* to be making any data changes to the target catalog .
*/
if ( IsSystemClass ( relform1 ) )
elog ( ERROR , " cannot swap toast files by links for system catalogs " ) ;
2010-02-04 00:09:14 +00:00
/* Delete old dependencies */
if ( relform1 - > reltoastrelid )
{
count = deleteDependencyRecordsFor ( RelationRelationId ,
relform1 - > reltoastrelid ) ;
if ( count ! = 1 )
elog ( ERROR , " expected one dependency record for TOAST table, found %ld " ,
count ) ;
}
if ( relform2 - > reltoastrelid )
{
count = deleteDependencyRecordsFor ( RelationRelationId ,
relform2 - > reltoastrelid ) ;
if ( count ! = 1 )
elog ( ERROR , " expected one dependency record for TOAST table, found %ld " ,
count ) ;
}
2002-08-11 21:17:35 +00:00
2010-02-04 00:09:14 +00:00
/* Register new dependencies */
baseobject . classId = RelationRelationId ;
baseobject . objectSubId = 0 ;
toastobject . classId = RelationRelationId ;
toastobject . objectSubId = 0 ;
2002-08-11 21:17:35 +00:00
2010-02-04 00:09:14 +00:00
if ( relform1 - > reltoastrelid )
{
baseobject . objectId = r1 ;
toastobject . objectId = relform1 - > reltoastrelid ;
recordDependencyOn ( & toastobject , & baseobject ,
DEPENDENCY_INTERNAL ) ;
}
2002-08-11 21:17:35 +00:00
2010-02-04 00:09:14 +00:00
if ( relform2 - > reltoastrelid )
{
baseobject . objectId = r2 ;
toastobject . objectId = relform2 - > reltoastrelid ;
recordDependencyOn ( & toastobject , & baseobject ,
DEPENDENCY_INTERNAL ) ;
}
2004-05-05 04:48:48 +00:00
}
2002-08-11 21:17:35 +00:00
}
2010-02-04 00:09:14 +00:00
/*
* If we ' re swapping two toast tables by content , do the same for their
* indexes .
*/
if ( swap_toast_by_content & &
relform1 - > reltoastidxid & & relform2 - > reltoastidxid )
swap_relation_files ( relform1 - > reltoastidxid ,
relform2 - > reltoastidxid ,
2010-02-07 20:48:13 +00:00
target_is_pg_class ,
swap_toast_by_content ,
InvalidTransactionId ,
mapped_tables ) ;
2002-08-11 21:17:35 +00:00
/* Clean up. */
heap_freetuple ( reltup1 ) ;
heap_freetuple ( reltup2 ) ;
heap_close ( relRelation , RowExclusiveLock ) ;
2010-02-07 20:48:13 +00:00
/*
* Close both relcache entries ' smgr links . We need this kluge because
* both links will be invalidated during upcoming CommandCounterIncrement .
* Whichever of the rels is the second to be cleared will have a dangling
* reference to the other ' s smgr entry . Rather than trying to avoid this
* by ordering operations just so , it ' s easiest to close the links first .
* ( Fortunately , since one of the entries is local in our transaction ,
* it ' s sufficient to clear out our own relcache this way ; the problem
* cannot arise for other backends when they see our update on the
* non - transient relation . )
*
* Caution : the placement of this step interacts with the decision to
* handle toast rels by recursion . When we are trying to rebuild pg_class
* itself , the smgr close on pg_class must happen after all accesses in
* this function .
*/
RelationCloseSmgrByOid ( r1 ) ;
RelationCloseSmgrByOid ( r2 ) ;
2002-08-10 20:43:46 +00:00
}
2002-11-15 03:09:39 +00:00
2010-02-04 00:09:14 +00:00
/*
* Remove the transient table that was built by make_new_heap , and finish
* cleaning up ( including rebuilding all indexes on the old heap ) .
*/
void
2010-02-07 20:48:13 +00:00
finish_heap_swap ( Oid OIDOldHeap , Oid OIDNewHeap ,
bool is_system_catalog ,
bool swap_toast_by_content ,
TransactionId frozenXid )
2010-02-04 00:09:14 +00:00
{
ObjectAddress object ;
2010-02-07 20:48:13 +00:00
Oid mapped_tables [ 4 ] ;
int i ;
2010-02-04 00:09:14 +00:00
2010-02-07 20:48:13 +00:00
/* 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 , frozenXid , 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 .
*/
reindex_relation ( OIDOldHeap , false , true ) ;
2010-02-04 00:09:14 +00:00
/* 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 ) ;
/* performDeletion does CommandCounterIncrement at end */
/*
2010-02-07 20:48:13 +00:00
* 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 .
2010-02-04 00:09:14 +00:00
*/
2010-02-07 20:48:13 +00:00
for ( i = 0 ; OidIsValid ( mapped_tables [ i ] ) ; i + + )
RelationMapRemoveMapping ( mapped_tables [ i ] ) ;
2010-02-04 00:09:14 +00:00
/*
* 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 ;
Oid toastnamespace ;
char NewToastName [ NAMEDATALEN ] ;
toastrel = relation_open ( newrel - > rd_rel - > reltoastrelid ,
AccessShareLock ) ;
toastidx = toastrel - > rd_rel - > reltoastidxid ;
toastnamespace = toastrel - > rd_rel - > relnamespace ;
relation_close ( toastrel , AccessShareLock ) ;
/* rename the toast table ... */
snprintf ( NewToastName , NAMEDATALEN , " pg_toast_%u " ,
OIDOldHeap ) ;
RenameRelationInternal ( newrel - > rd_rel - > reltoastrelid ,
NewToastName ,
toastnamespace ) ;
/* ... and its index too */
snprintf ( NewToastName , NAMEDATALEN , " pg_toast_%u_index " ,
OIDOldHeap ) ;
RenameRelationInternal ( toastidx ,
NewToastName ,
toastnamespace ) ;
}
relation_close ( newrel , NoLock ) ;
}
}
2002-12-30 18:42:17 +00:00
/*
* Get a list of tables that the current user owns and
2002-11-15 03:09:39 +00:00
* have indisclustered set . Return the list in a List * of rvsToCluster
2002-12-04 05:18:38 +00:00
* with the tableOid and the indexOid on which the table is already
2002-11-15 03:09:39 +00:00
* clustered .
*/
2002-12-30 18:42:17 +00:00
static List *
get_tables_to_cluster ( MemoryContext cluster_context )
2002-11-15 03:09:39 +00:00
{
2003-08-04 00:43:34 +00:00
Relation indRelation ;
HeapScanDesc scan ;
ScanKeyData entry ;
HeapTuple indexTuple ;
Form_pg_index index ;
MemoryContext old_context ;
RelToCluster * rvtc ;
List * rvs = NIL ;
2002-11-15 03:09:39 +00:00
/*
2002-12-30 18:42:17 +00:00
* Get all indexes that have indisclustered set and are owned by
2005-10-15 02:49:52 +00:00
* appropriate user . System relations or nailed - in relations cannot ever
* have indisclustered set , because CLUSTER will refuse to set it when
* called with one of them as argument .
2002-11-15 03:09:39 +00:00
*/
2005-04-14 20:03:27 +00:00
indRelation = heap_open ( IndexRelationId , AccessShareLock ) ;
2003-11-12 21:15:59 +00:00
ScanKeyInit ( & entry ,
Anum_pg_index_indisclustered ,
BTEqualStrategyNumber , F_BOOLEQ ,
BoolGetDatum ( true ) ) ;
2002-11-15 03:09:39 +00:00
scan = heap_beginscan ( indRelation , SnapshotNow , 1 , & entry ) ;
while ( ( indexTuple = heap_getnext ( scan , ForwardScanDirection ) ) ! = NULL )
{
index = ( Form_pg_index ) GETSTRUCT ( indexTuple ) ;
2003-08-01 00:15:26 +00:00
if ( ! pg_class_ownercheck ( index - > indrelid , GetUserId ( ) ) )
2002-11-15 03:09:39 +00:00
continue ;
/*
2005-10-15 02:49:52 +00:00
* We have to build the list in a different memory context so it will
* survive the cross - transaction processing
2002-11-15 03:09:39 +00:00
*/
old_context = MemoryContextSwitchTo ( cluster_context ) ;
2002-12-30 18:42:17 +00:00
rvtc = ( RelToCluster * ) palloc ( sizeof ( RelToCluster ) ) ;
2002-11-15 03:09:39 +00:00
rvtc - > tableOid = index - > indrelid ;
2002-12-30 18:42:17 +00:00
rvtc - > indexOid = index - > indexrelid ;
rvs = lcons ( rvtc , rvs ) ;
2002-11-15 03:09:39 +00:00
MemoryContextSwitchTo ( old_context ) ;
}
heap_endscan ( scan ) ;
2002-12-30 18:42:17 +00:00
relation_close ( indRelation , AccessShareLock ) ;
2002-11-15 03:09:39 +00:00
return rvs ;
}