diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index ef8b57e0ebe75155535283f2f85ea45b17af383a..263e5024b62148863c139a523222af253871288a 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.72 2004/06/02 21:04:40 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.73 2004/07/11 23:13:51 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -39,10 +39,11 @@ where <replaceable class="PARAMETER">action</replaceable> is one of:
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
     ADD <replaceable class="PARAMETER">table_constraint</replaceable>
     DROP CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
-    SET WITHOUT OIDS
-    OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
     SET WITHOUT CLUSTER
+    SET WITHOUT OIDS
+    OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
+    SET TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable>
 </synopsis>
  </refsynopsisdiv>
 
@@ -181,6 +182,29 @@ where <replaceable class="PARAMETER">action</replaceable> is one of:
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>CLUSTER</literal></term>
+    <listitem>
+     <para>
+      This form selects the default index for future 
+      <xref linkend="SQL-CLUSTER" endterm="sql-cluster-title">
+      operations.  It does not actually re-cluster the table.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>SET WITHOUT CLUSTER</literal></term>
+    <listitem>
+     <para>
+      This form removes the most recently used
+      <xref linkend="SQL-CLUSTER" endterm="sql-cluster-title">
+      index specification from the table.  This affects
+      future cluster operations that don't specify an index.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET WITHOUT OIDS</literal></term>
     <listitem>
@@ -211,28 +235,19 @@ where <replaceable class="PARAMETER">action</replaceable> is one of:
    </varlistentry>
 
    <varlistentry>
-    <term><literal>CLUSTER</literal></term>
+    <term><literal>SET TABLESPACE</literal></term>
     <listitem>
      <para>
-      This form selects the default index for future 
-      <xref linkend="SQL-CLUSTER" endterm="sql-cluster-title">
-      operations.
+      This form changes the table's tablespace to the specified tablespace and
+      moves the data file(s) associated with the table to the new tablespace.
+      Indexes on the table, if any, are not moved; but they can be moved
+      separately with additional <literal>SET TABLESPACE</literal> commands.
+      See also 
+      <xref linkend="SQL-CREATETABLESPACE" endterm="sql-createtablespace-title">.
      </para>
     </listitem>
    </varlistentry>
 
-   <varlistentry>
-    <term><literal>SET WITHOUT CLUSTER</literal></term>
-    <listitem>
-     <para>
-      This form removes the most recently used
-      <xref linkend="SQL-CLUSTER" endterm="sql-cluster-title">
-      index specification from the table.  This affects
-      future cluster operations that don't specify an index.
-     </para>
-    </listitem>
-   </varlistentry>
- 
    <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
@@ -293,29 +308,29 @@ where <replaceable class="PARAMETER">action</replaceable> is one of:
      </varlistentry>
 
      <varlistentry>
-      <term><replaceable class="PARAMETER">type</replaceable></term>
+      <term><replaceable class="PARAMETER">new_column</replaceable></term>
       <listitem>
        <para>
-	Data type of the new column, or new data type for an existing
-	column.
+	New name for an existing column.
        </para>
       </listitem>
      </varlistentry>
 
      <varlistentry>
-      <term><replaceable class="PARAMETER">new_column</replaceable></term>
+      <term><replaceable class="PARAMETER">new_name</replaceable></term>
       <listitem>
        <para>
-	New name for an existing column.
+	New name for the table.
        </para>
       </listitem>
      </varlistentry>
 
      <varlistentry>
-      <term><replaceable class="PARAMETER">new_name</replaceable></term>
+      <term><replaceable class="PARAMETER">type</replaceable></term>
       <listitem>
        <para>
-	New name for the table.
+	Data type of the new column, or new data type for an existing
+	column.
        </para>
       </listitem>
      </varlistentry>
@@ -339,10 +354,21 @@ where <replaceable class="PARAMETER">action</replaceable> is one of:
      </varlistentry>
 
      <varlistentry>
-      <term><replaceable class="PARAMETER">new_owner</replaceable></term>
+      <term><literal>CASCADE</literal></term>
       <listitem>
        <para>
-	The user name of the new owner of the table.
+        Automatically drop objects that depend on the dropped column
+	or constraint (for example, views referencing the column).
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>RESTRICT</literal></term>
+      <listitem>
+       <para>
+        Refuse to drop the column or constraint if there are any dependent
+	objects. This is the default behavior.
        </para>
       </listitem>
      </varlistentry>
@@ -357,21 +383,19 @@ where <replaceable class="PARAMETER">action</replaceable> is one of:
      </varlistentry>
 
      <varlistentry>
-      <term><literal>CASCADE</literal></term>
+      <term><replaceable class="PARAMETER">new_owner</replaceable></term>
       <listitem>
        <para>
-        Automatically drop objects that depend on the dropped column
-	or constraint (for example, views referencing the column).
+	The user name of the new owner of the table.
        </para>
       </listitem>
      </varlistentry>
 
      <varlistentry>
-      <term><literal>RESTRICT</literal></term>
+      <term><replaceable class="PARAMETER">tablespace_name</replaceable></term>
       <listitem>
        <para>
-        Refuse to drop the column or constraint if there are any dependent
-	objects. This is the default behavior.
+	The tablespace name to which the table will be moved.
        </para>
       </listitem>
      </varlistentry>
@@ -551,6 +575,14 @@ ALTER TABLE distributors ADD CONSTRAINT dist_id_zipcode_key UNIQUE (dist_id, zip
 ALTER TABLE distributors ADD PRIMARY KEY (dist_id);
 </programlisting>
   </para>
+
+  <para> 
+	To move a table to a different tablespace:
+<programlisting>
+ALTER TABLE distributors SET TABLESPACE fasttablespace;
+</programlisting>
+  </para>
+
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index d81771c7a043d11825eac041d9f59bb40a25ed83..391504fb37d1f6b62825c54329e75734bda83c19 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.126 2004/06/18 06:13:22 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.127 2004/07/11 23:13:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -485,6 +485,7 @@ static void
 rebuild_relation(Relation OldHeap, Oid indexOid)
 {
 	Oid			tableOid = RelationGetRelid(OldHeap);
+	Oid			tableSpace = OldHeap->rd_rel->reltablespace;
 	Oid			OIDNewHeap;
 	char		NewHeapName[NAMEDATALEN];
 	ObjectAddress object;
@@ -505,7 +506,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid)
 	 */
 	snprintf(NewHeapName, sizeof(NewHeapName), "pg_temp_%u", tableOid);
 
-	OIDNewHeap = make_new_heap(tableOid, NewHeapName);
+	OIDNewHeap = make_new_heap(tableOid, NewHeapName, tableSpace);
 
 	/*
 	 * We don't need CommandCounterIncrement() because make_new_heap did
@@ -520,8 +521,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid)
 	/* To make the new heap's data visible (probably not needed?). */
 	CommandCounterIncrement();
 
-	/* Swap the relfilenodes of the old and new heaps. */
-	swap_relfilenodes(tableOid, OIDNewHeap);
+	/* Swap the physical files of the old and new heaps. */
+	swap_relation_files(tableOid, OIDNewHeap);
 
 	CommandCounterIncrement();
 
@@ -550,7 +551,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid)
  * Create the new table that we will fill with correctly-ordered data.
  */
 Oid
-make_new_heap(Oid OIDOldHeap, const char *NewName)
+make_new_heap(Oid OIDOldHeap, const char *NewName, Oid NewTableSpace)
 {
 	TupleDesc	OldHeapDesc,
 				tupdesc;
@@ -568,7 +569,7 @@ make_new_heap(Oid OIDOldHeap, const char *NewName)
 
 	OIDNewHeap = heap_create_with_catalog(NewName,
 										  RelationGetNamespace(OldHeap),
-		                                  OldHeap->rd_rel->reltablespace,
+		                                  NewTableSpace,
 										  tupdesc,
 										  OldHeap->rd_rel->relkind,
 										  OldHeap->rd_rel->relisshared,
@@ -646,13 +647,16 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
 }
 
 /*
- * Swap the relfilenodes for two given relations.
+ * 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.
  *
  * Also swap any TOAST links, so that the toast data moves along with
  * the main-table data.
  */
 void
-swap_relfilenodes(Oid r1, Oid r2)
+swap_relation_files(Oid r1, Oid r2)
 {
 	Relation	relRelation,
 				rel;
@@ -695,12 +699,16 @@ swap_relfilenodes(Oid r1, Oid r2)
 	relation_close(rel, NoLock);
 
 	/*
-	 * Actually swap the filenode and TOAST fields in the two tuples
+	 * Actually swap the fields in the two tuples
 	 */
 	swaptemp = relform1->relfilenode;
 	relform1->relfilenode = relform2->relfilenode;
 	relform2->relfilenode = swaptemp;
 
+	swaptemp = relform1->reltablespace;
+	relform1->reltablespace = relform2->reltablespace;
+	relform2->reltablespace = swaptemp;
+
 	swaptemp = relform1->reltoastrelid;
 	relform1->reltoastrelid = relform2->reltoastrelid;
 	relform2->reltoastrelid = swaptemp;
@@ -793,13 +801,16 @@ swap_relfilenodes(Oid r1, Oid r2)
 
 	/*
 	 * Blow away the old relcache entries now.	We need this kluge because
-	 * relcache.c indexes relcache entries by rd_node as well as OID. It
-	 * will get confused if it is asked to (re)build an entry with a new
-	 * rd_node value when there is still another entry laying about with
-	 * that same rd_node value.  (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-local relation.)
+	 * relcache.c keeps a link to the smgr relation for the physical file,
+	 * and that will be out of date as soon as we do CommandCounterIncrement.
+	 * Whichever of the rels is the second to be cleared during cache
+	 * invalidation will have a dangling reference to an already-deleted smgr
+	 * relation.  Rather than trying to avoid this by ordering operations
+	 * just so, it's easiest to not have the relcache entries there at all.
+	 * (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-local relation.)
 	 */
 	RelationForgetRelation(r1);
 	RelationForgetRelation(r2);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 392822abf503c785587920a8ca9a74f9089ea00d..a5a7deadcfd5933b33cfca936efe4d3db0837916 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.118 2004/07/01 00:50:10 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.119 2004/07/11 23:13:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -52,6 +52,7 @@
 #include "parser/parse_relation.h"
 #include "parser/parse_type.h"
 #include "rewrite/rewriteHandler.h"
+#include "storage/smgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -120,6 +121,7 @@ typedef struct AlteredTableInfo
 	/* Information saved by Phases 1/2 for Phase 3: */
 	List	   *constraints;	/* List of NewConstraint */
 	List	   *newvals;		/* List of NewColumnValue */
+	Oid			newTableSpace;	/* new tablespace; 0 means no change */
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -237,6 +239,10 @@ static void ATPostAlterTypeParse(char *cmd, List **wqueue);
 static void ATExecChangeOwner(Oid relationOid, int32 newOwnerSysId);
 static void ATExecClusterOn(Relation rel, const char *indexName);
 static void ATExecDropCluster(Relation rel);
+static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
+								char *tablespacename);
+static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace);
+static void copy_relation_data(Relation rel, SMgrRelation dst);
 static int	ri_trigger_type(Oid tgfoid);
 static void update_ri_trigger_args(Oid relid,
 					   const char *oldname,
@@ -1946,6 +1952,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			}
 			pass = AT_PASS_DROP;
 			break;
+		case AT_SetTableSpace:	/* SET TABLESPACE */
+			/* This command never recurses */
+			ATPrepSetTableSpace(tab, rel, cmd->name);
+			pass = AT_PASS_MISC; /* doesn't actually matter */
+			break;
 		default:	/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -2097,6 +2108,11 @@ ATExecCmd(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd)
 			 * to do the real work
 			 */
 			break;
+		case AT_SetTableSpace:		/* SET TABLESPACE */
+			/*
+			 * Nothing to do here; Phase 3 does the work
+			 */
+			break;
 		default:	/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -2132,6 +2148,7 @@ ATRewriteTables(List **wqueue)
 			/* Build a temporary relation and copy data */
 			Oid			OIDNewHeap;
 			char		NewHeapName[NAMEDATALEN];
+			Oid			NewTableSpace;
 			Relation	OldHeap;
 			ObjectAddress	object;
 
@@ -2157,6 +2174,15 @@ ATRewriteTables(List **wqueue)
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot rewrite temporary tables of other sessions")));
 
+			/*
+			 * Select destination tablespace (same as original unless user
+			 * requested a change)
+			 */
+			if (tab->newTableSpace)
+				NewTableSpace = tab->newTableSpace;
+			else
+				NewTableSpace = OldHeap->rd_rel->reltablespace;
+
 			heap_close(OldHeap, NoLock);
 
 			/*
@@ -2170,7 +2196,7 @@ ATRewriteTables(List **wqueue)
 			snprintf(NewHeapName, sizeof(NewHeapName),
 					 "pg_temp_%u", tab->relid);
 
-			OIDNewHeap = make_new_heap(tab->relid, NewHeapName);
+			OIDNewHeap = make_new_heap(tab->relid, NewHeapName, NewTableSpace);
 
 			/*
 			 * Copy the heap data into the new table with the desired
@@ -2179,8 +2205,8 @@ ATRewriteTables(List **wqueue)
 			 */
 			ATRewriteTable(tab, OIDNewHeap);
 
-			/* Swap the relfilenodes of the old and new heaps. */
-			swap_relfilenodes(tab->relid, OIDNewHeap);
+			/* Swap the physical files of the old and new heaps. */
+			swap_relation_files(tab->relid, OIDNewHeap);
 
 			CommandCounterIncrement();
 
@@ -2203,13 +2229,20 @@ ATRewriteTables(List **wqueue)
 			 */
 			reindex_relation(tab->relid, false);
 		}
-		else if (tab->constraints != NIL)
+		else
 		{
 			/*
 			 * Test the current data within the table against new constraints
 			 * generated by ALTER TABLE commands, but don't rebuild data.
 			 */
-			ATRewriteTable(tab, InvalidOid);
+			if (tab->constraints != NIL)
+				ATRewriteTable(tab, InvalidOid);
+			/*
+			 * If we had SET TABLESPACE but no reason to reconstruct tuples,
+			 * just do a block-by-block copy.
+			 */
+			if (tab->newTableSpace)
+				ATExecSetTableSpace(tab->relid, tab->newTableSpace);
 		}
 	}
 
@@ -5185,6 +5218,249 @@ ATExecDropCluster(Relation rel)
 	mark_index_clustered(rel, InvalidOid);
 }
 
+/*
+ * ALTER TABLE SET TABLESPACE
+ */
+static void
+ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, char *tablespacename)
+{
+	Oid			tablespaceId;
+	AclResult   aclresult;
+
+	/*
+	 * We do our own permission checking because we want to allow this on
+	 * indexes.
+	 */
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a table or index",
+						RelationGetRelationName(rel))));
+
+	/* Permissions checks */
+	if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+					   RelationGetRelationName(rel));
+
+	if (!allowSystemTableMods && IsSystemRelation(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a system catalog",
+						RelationGetRelationName(rel))));
+
+	/* Check that the tablespace exists */
+	tablespaceId = get_tablespace_oid(tablespacename);
+	if (!OidIsValid(tablespaceId))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("tablespace \"%s\" does not exist", tablespacename)));
+
+	/* Check its permissions */
+	aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), ACL_CREATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, ACL_KIND_TABLESPACE, tablespacename);
+
+	/* Save info for Phase 3 to do the real work */
+	if (OidIsValid(tab->newTableSpace))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("multiple SET TABLESPACE subcommands are not valid")));
+	tab->newTableSpace = tablespaceId;
+}
+
+/*
+ * Execute ALTER TABLE SET TABLESPACE for cases where there is no tuple
+ * rewriting to be done, so we just want to copy the data as fast as possible.
+ */
+static void
+ATExecSetTableSpace(Oid tableOid, Oid newTableSpace)
+{
+	Relation	rel;
+	Oid			oldTableSpace;
+	Oid			reltoastrelid;
+	Oid			reltoastidxid;
+	RelFileNode newrnode;
+	SMgrRelation dstrel;
+	Relation	pg_class;
+	HeapTuple	tuple;
+	Form_pg_class rd_rel;
+
+	rel = relation_open(tableOid, NoLock);
+
+	/*
+	 * We can never allow moving of shared or nailed-in-cache relations,
+	 * because we can't support changing their reltablespace values.
+	 */
+	if (rel->rd_rel->relisshared || rel->rd_isnailed)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot move system relation \"%s\"",
+						RelationGetRelationName(rel))));
+
+	/*
+	 * Don't allow moving temp tables of other backends ... their
+	 * local buffer manager is not going to cope.
+	 */
+	if (isOtherTempNamespace(RelationGetNamespace(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot move temporary tables of other sessions")));
+
+	/*
+	 * No work if no change in tablespace.
+	 */
+	oldTableSpace = rel->rd_rel->reltablespace;
+	if (newTableSpace == oldTableSpace ||
+		(newTableSpace == MyDatabaseTableSpace && oldTableSpace == 0))
+	{
+		relation_close(rel, NoLock);
+		return;
+	}
+
+	reltoastrelid = rel->rd_rel->reltoastrelid;
+	reltoastidxid = rel->rd_rel->reltoastidxid;
+
+	/* Get a modifiable copy of the relation's pg_class row */
+	pg_class = heap_openr(RelationRelationName, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy(RELOID,
+							   ObjectIdGetDatum(tableOid),
+							   0, 0, 0);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", tableOid);
+	rd_rel = (Form_pg_class) GETSTRUCT(tuple);
+
+	/* create another storage file. Is it a little ugly ? */
+	/* NOTE: any conflict in relfilenode value will be caught here */
+	newrnode = rel->rd_node;
+	newrnode.spcNode = newTableSpace;
+
+	dstrel = smgropen(newrnode);
+	smgrcreate(dstrel, rel->rd_istemp, false);
+
+	/* schedule unlinking old physical file */
+	if (rel->rd_smgr == NULL)
+		rel->rd_smgr = smgropen(rel->rd_node);
+	smgrscheduleunlink(rel->rd_smgr, rel->rd_istemp);
+
+	/* copy relation data to the new physical file */
+	copy_relation_data(rel, dstrel);
+
+	/*
+	 * Now drop smgr references.  We need not smgrclose() the old file,
+	 * since it will be dropped anyway at commit by the pending unlink.
+	 * We do need to get rid of relcache's reference to it, however.
+	 */
+	smgrclose(dstrel);
+	rel->rd_smgr = NULL;
+
+	/* update the pg_class row */
+	rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace) ? InvalidOid : newTableSpace;
+	simple_heap_update(pg_class, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(pg_class, tuple);
+
+	heap_freetuple(tuple);
+
+	heap_close(pg_class, RowExclusiveLock);
+
+	relation_close(rel, NoLock);
+
+	/* Make sure the reltablespace change is visible */
+	CommandCounterIncrement();
+
+	/* Move associated toast relation and/or index, too */
+	if (OidIsValid(reltoastrelid))
+		ATExecSetTableSpace(reltoastrelid, newTableSpace);
+	if (OidIsValid(reltoastidxid))
+		ATExecSetTableSpace(reltoastidxid, newTableSpace);
+}
+
+/*
+ * Copy data, block by block
+ */
+static void
+copy_relation_data(Relation rel, SMgrRelation dst)
+{
+	SMgrRelation src = rel->rd_smgr;
+	bool		use_wal;
+	BlockNumber nblocks;
+	BlockNumber blkno;
+	char buf[BLCKSZ];
+	Page		page = (Page) buf;
+
+	/*
+	 * Since we copy the data directly without looking at the shared buffers,
+	 * we'd better first flush out any pages of the source relation that are
+	 * in shared buffers.  We assume no new pages will get loaded into
+	 * buffers while we are holding exclusive lock on the rel.
+	 */
+	FlushRelationBuffers(rel, 0);
+
+	/*
+	 * We need to log the copied data in WAL iff WAL archiving is enabled
+	 * AND it's not a temp rel.
+	 *
+	 * XXX when WAL archiving is actually supported, this test will likely
+	 * need to change; and the hardwired extern is cruddy anyway ...
+	 */
+	{
+		extern char XLOG_archive_dir[];
+
+		use_wal = XLOG_archive_dir[0] && !rel->rd_istemp;
+	}
+
+	nblocks = RelationGetNumberOfBlocks(rel);
+	for (blkno = 0; blkno < nblocks; blkno++)
+	{
+		smgrread(src, blkno, buf);
+
+		/* XLOG stuff */
+		if (use_wal)
+		{
+			xl_heap_newpage xlrec;
+			XLogRecPtr	recptr;
+			XLogRecData rdata[2];
+
+			/* NO ELOG(ERROR) from here till newpage op is logged */
+			START_CRIT_SECTION();
+
+			xlrec.node = dst->smgr_rnode;
+			xlrec.blkno = blkno;
+
+			rdata[0].buffer = InvalidBuffer;
+			rdata[0].data = (char *) &xlrec;
+			rdata[0].len = SizeOfHeapNewpage;
+			rdata[0].next = &(rdata[1]);
+
+			rdata[1].buffer = InvalidBuffer;
+			rdata[1].data = (char *) page;
+			rdata[1].len = BLCKSZ;
+			rdata[1].next = NULL;
+
+			recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_NEWPAGE, rdata);
+
+			PageSetLSN(page, recptr);
+			PageSetSUI(page, ThisStartUpID);
+
+			END_CRIT_SECTION();
+		}
+
+		/*
+		 * Now write the page.  If not using WAL, say isTemp = true, to
+		 * suppress duplicate fsync.  If we are using WAL, it surely isn't a
+		 * temp rel, so !use_wal is a sufficient condition.
+		 */
+		smgrwrite(dst, blkno, buf, !use_wal);
+	}
+
+	/*
+	 * If we weren't using WAL, and the rel isn't temp, we must fsync it
+	 * down to disk before it's safe to commit the transaction.
+	 */
+	if (!use_wal && !rel->rd_istemp)
+		smgrimmedsync(dst);
+}
 
 /*
  * ALTER TABLE CREATE TOAST TABLE
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7446bc612d5d2f16fc33efcbdd8ea815515663c0..e9af75baaa376af864060055322adb96f970bb1d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.465 2004/06/28 01:19:11 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.466 2004/07/11 23:13:54 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -1286,6 +1286,14 @@ alter_table_cmd:
 					n->name = NULL;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET TABLESPACE <tablespacename> */
+			| SET TABLESPACE name
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetTableSpace;
+					n->name = $3;
+					$$ = (Node *)n;
+				}
 		;
 
 alter_column_default:
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 820c95210805adfbcc60fd469fcb163bd897f00e..11e8a391817e0cc8b46683b39cb0e27457ac8e87 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.23 2004/05/08 00:34:49 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.24 2004/07/11 23:13:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,7 +21,8 @@ extern void cluster(ClusterStmt *stmt);
 
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid);
 extern void mark_index_clustered(Relation rel, Oid indexOid);
-extern Oid	make_new_heap(Oid OIDOldHeap, const char *NewName);
-extern void swap_relfilenodes(Oid r1, Oid r2);
+extern Oid	make_new_heap(Oid OIDOldHeap, const char *NewName,
+						  Oid NewTableSpace);
+extern void swap_relation_files(Oid r1, Oid r2);
 
 #endif   /* CLUSTER_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 22d18ef4f70e665dda8e583d83ad2390a4e3d8f4..3745972bd50b496e0fefe204456adb8400a8448f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.260 2004/06/25 21:55:59 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.261 2004/07/11 23:13:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -806,7 +806,8 @@ typedef enum AlterTableType
 	AT_ChangeOwner,				/* change owner */
 	AT_ClusterOn,				/* CLUSTER ON */
 	AT_DropCluster,				/* SET WITHOUT CLUSTER */
-	AT_DropOids					/* SET WITHOUT OIDS */
+	AT_DropOids,				/* SET WITHOUT OIDS */
+	AT_SetTableSpace			/* SET TABLESPACE */
 } AlterTableType;
 
 typedef struct AlterTableCmd	/* one subcommand of an ALTER TABLE */
@@ -814,7 +815,7 @@ typedef struct AlterTableCmd	/* one subcommand of an ALTER TABLE */
 	NodeTag		type;
 	AlterTableType subtype;		/* Type of table alteration to apply */
 	char	   *name;			/* column or constraint name to act on, or
-								 * new owner */
+								 * new owner or tablespace */
 	Node	   *def;			/* definition of new column, column type,
 								 * index, or constraint */
 	Node	   *transform;		/* transformation expr for ALTER TYPE */