diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index df8f858c545576ee4a111495e4667c57afdcb62f..34f472555e591458169cdd08266eba53b175b958 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.241 2009/12/25 01:09:31 rhaas Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.242 2010/01/05 21:53:58 rhaas Exp $ -->
 
 <chapter Id="runtime-config">
   <title>Server Configuration</title>
@@ -2000,6 +2000,9 @@ archive_command = 'copy "%p" "C:\\server\\archivedir\\%f"'  # Windows
        <para>
         Sets the planner's estimate of the cost of a disk page fetch
         that is part of a series of sequential fetches.  The default is 1.0.
+        This value can be overriden for a particular tablespace by setting
+        the tablespace parameter of the same name
+        (see <xref linkend="sql-altertablespace">).
        </para>
       </listitem>
      </varlistentry>
@@ -2013,6 +2016,12 @@ archive_command = 'copy "%p" "C:\\server\\archivedir\\%f"'  # Windows
        <para>
         Sets the planner's estimate of the cost of a
         non-sequentially-fetched disk page.  The default is 4.0.
+        This value can be overriden for a particular tablespace by setting
+        the tablespace parameter of the same name
+        (see <xref linkend="sql-altertablespace">).
+       </para>
+
+	   <para>
         Reducing this value relative to <varname>seq_page_cost</>
         will cause the system to prefer index scans; raising it will
         make index scans look relatively more expensive.  You can raise
diff --git a/doc/src/sgml/ref/alter_tablespace.sgml b/doc/src/sgml/ref/alter_tablespace.sgml
index 8c5341dd0845553305bd53bb86f9610f0703b26c..758ee13aa6910d5edeb0594e3e311ccfb1e2f3c6 100644
--- a/doc/src/sgml/ref/alter_tablespace.sgml
+++ b/doc/src/sgml/ref/alter_tablespace.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/alter_tablespace.sgml,v 1.5 2009/09/19 10:23:26 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/alter_tablespace.sgml,v 1.6 2010/01/05 21:53:58 rhaas Exp $
 PostgreSQL documentation
 -->
 
@@ -23,6 +23,8 @@ PostgreSQL documentation
 <synopsis>
 ALTER TABLESPACE <replaceable>name</replaceable> RENAME TO <replaceable>new_name</replaceable>
 ALTER TABLESPACE <replaceable>name</replaceable> OWNER TO <replaceable>new_owner</replaceable>
+ALTER TABLESPACE <replaceable>name</replaceable> SET ( <replaceable class="PARAMETER">tablespace_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+ALTER TABLESPACE <replaceable>name</replaceable> RESET ( <replaceable class="PARAMETER">tablespace_option</replaceable> [, ... ] )
 </synopsis>
  </refsynopsisdiv>
   
@@ -74,6 +76,24 @@ ALTER TABLESPACE <replaceable>name</replaceable> OWNER TO <replaceable>new_owner
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">tablespace_parameter</replaceable></term>
+    <listitem>
+     <para>
+      A tablespace parameter to be set or reset.  Currently, the only
+      available parameters are <varname>seq_page_cost</> and
+      <varname>random_page_cost</>.  Setting either value for a particular
+      tablespace will override the planner's usual estimate of the cost of
+      reading pages from tables in that tablespace, as established by
+      the configuration parameters of the same name (see
+      <xref linkend="guc-seq-page-cost">,
+      <xref linkend="guc-random-page-cost">).  This may be useful if one
+      tablespace is located on a disk which is faster or slower than the
+      remainder of the I/O subsystem.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 2e3fa5bcae50baeaba95cf0c31072544ea77747b..28eacb4e478eb54a44888ce7d3560fd7c208e81a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/common/reloptions.c,v 1.30 2010/01/02 16:57:33 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/common/reloptions.c,v 1.31 2010/01/05 21:53:58 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,6 +21,7 @@
 #include "access/reloptions.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
+#include "commands/tablespace.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -179,6 +180,22 @@ static relopt_real realRelOpts[] =
 		},
 		-1, 0.0, 100.0
 	},
+	{
+		{
+			"seq_page_cost",
+			"Sets the planner's estimate of the cost of a sequentially fetched disk page.",
+			RELOPT_KIND_TABLESPACE
+		},
+		-1, 0.0, DBL_MAX
+	},
+	{
+		{
+			"random_page_cost",
+			"Sets the planner's estimate of the cost of a nonsequentially fetched disk page.",
+			RELOPT_KIND_TABLESPACE
+		},
+		-1, 0.0, DBL_MAX
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -1168,3 +1185,34 @@ index_reloptions(RegProcedure amoptions, Datum reloptions, bool validate)
 
 	return DatumGetByteaP(result);
 }
+
+/*
+ * Option parser for tablespace reloptions
+ */
+bytea *
+tablespace_reloptions(Datum reloptions, bool validate)
+{
+	relopt_value *options;
+	TableSpaceOpts	*tsopts;
+	int			numoptions;
+	static const relopt_parse_elt tab[] = {
+		{"random_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, random_page_cost)},
+		{"seq_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, seq_page_cost)}
+	};
+
+	options = parseRelOptions(reloptions, validate, RELOPT_KIND_TABLESPACE,
+							  &numoptions);
+
+	/* if none set, we're done */
+	if (numoptions == 0)
+		return NULL;
+
+	tsopts = allocateReloptStruct(sizeof(TableSpaceOpts), options, numoptions);
+
+	fillRelOptions((void *) tsopts, sizeof(TableSpaceOpts), options, numoptions,
+				   validate, tab, lengthof(tab));
+
+	pfree(options);
+
+	return (bytea *) tsopts;
+}
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d98ebdda4f8d311ad1be05aa17f20adeb84dee73..6c16fd6a6e21519101d5164760c04a9c37101640 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.159 2010/01/02 16:57:36 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.160 2010/01/05 21:53:58 rhaas Exp $
  *
  * NOTES
  *	  See acl.h.
@@ -2783,18 +2783,11 @@ ExecGrant_Tablespace(InternalGrant *istmt)
 		int			nnewmembers;
 		Oid		   *oldmembers;
 		Oid		   *newmembers;
-		ScanKeyData entry[1];
-		SysScanDesc scan;
 		HeapTuple	tuple;
 
-		/* There's no syscache for pg_tablespace, so must look the hard way */
-		ScanKeyInit(&entry[0],
-					ObjectIdAttributeNumber,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(tblId));
-		scan = systable_beginscan(relation, TablespaceOidIndexId, true,
-								  SnapshotNow, 1, entry);
-		tuple = systable_getnext(scan);
+		/* Search syscache for pg_tablespace */
+		tuple = SearchSysCache(TABLESPACEOID, ObjectIdGetDatum(tblId),
+							   0, 0, 0);
 		if (!HeapTupleIsValid(tuple))
 			elog(ERROR, "cache lookup failed for tablespace %u", tblId);
 
@@ -2865,8 +2858,7 @@ ExecGrant_Tablespace(InternalGrant *istmt)
 							  noldmembers, oldmembers,
 							  nnewmembers, newmembers);
 
-		systable_endscan(scan);
-
+		ReleaseSysCache(tuple);
 		pfree(new_acl);
 
 		/* prevent error when processing duplicate objects */
@@ -3696,9 +3688,6 @@ pg_tablespace_aclmask(Oid spc_oid, Oid roleid,
 					  AclMode mask, AclMaskHow how)
 {
 	AclMode		result;
-	Relation	pg_tablespace;
-	ScanKeyData entry[1];
-	SysScanDesc scan;
 	HeapTuple	tuple;
 	Datum		aclDatum;
 	bool		isNull;
@@ -3711,17 +3700,9 @@ pg_tablespace_aclmask(Oid spc_oid, Oid roleid,
 
 	/*
 	 * Get the tablespace's ACL from pg_tablespace
-	 *
-	 * There's no syscache for pg_tablespace, so must look the hard way
 	 */
-	pg_tablespace = heap_open(TableSpaceRelationId, AccessShareLock);
-	ScanKeyInit(&entry[0],
-				ObjectIdAttributeNumber,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(spc_oid));
-	scan = systable_beginscan(pg_tablespace, TablespaceOidIndexId, true,
-							  SnapshotNow, 1, entry);
-	tuple = systable_getnext(scan);
+	tuple = SearchSysCache(TABLESPACEOID, ObjectIdGetDatum(spc_oid),
+						   0, 0, 0);
 	if (!HeapTupleIsValid(tuple))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -3729,8 +3710,9 @@ pg_tablespace_aclmask(Oid spc_oid, Oid roleid,
 
 	ownerId = ((Form_pg_tablespace) GETSTRUCT(tuple))->spcowner;
 
-	aclDatum = heap_getattr(tuple, Anum_pg_tablespace_spcacl,
-							RelationGetDescr(pg_tablespace), &isNull);
+	aclDatum = SysCacheGetAttr(TABLESPACEOID, tuple,
+								   Anum_pg_tablespace_spcacl,
+								   &isNull);
 
 	if (isNull)
 	{
@@ -3750,8 +3732,7 @@ pg_tablespace_aclmask(Oid spc_oid, Oid roleid,
 	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
 		pfree(acl);
 
-	systable_endscan(scan);
-	heap_close(pg_tablespace, AccessShareLock);
+	ReleaseSysCache(tuple);
 
 	return result;
 }
@@ -4338,9 +4319,6 @@ pg_namespace_ownercheck(Oid nsp_oid, Oid roleid)
 bool
 pg_tablespace_ownercheck(Oid spc_oid, Oid roleid)
 {
-	Relation	pg_tablespace;
-	ScanKeyData entry[1];
-	SysScanDesc scan;
 	HeapTuple	spctuple;
 	Oid			spcowner;
 
@@ -4348,17 +4326,9 @@ pg_tablespace_ownercheck(Oid spc_oid, Oid roleid)
 	if (superuser_arg(roleid))
 		return true;
 
-	/* There's no syscache for pg_tablespace, so must look the hard way */
-	pg_tablespace = heap_open(TableSpaceRelationId, AccessShareLock);
-	ScanKeyInit(&entry[0],
-				ObjectIdAttributeNumber,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(spc_oid));
-	scan = systable_beginscan(pg_tablespace, TablespaceOidIndexId, true,
-							  SnapshotNow, 1, entry);
-
-	spctuple = systable_getnext(scan);
-
+	/* Search syscache for pg_tablespace */
+	spctuple = SearchSysCache(TABLESPACEOID, ObjectIdGetDatum(spc_oid),
+							  0, 0, 0);
 	if (!HeapTupleIsValid(spctuple))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -4366,8 +4336,7 @@ pg_tablespace_ownercheck(Oid spc_oid, Oid roleid)
 
 	spcowner = ((Form_pg_tablespace) GETSTRUCT(spctuple))->spcowner;
 
-	systable_endscan(scan);
-	heap_close(pg_tablespace, AccessShareLock);
+	ReleaseSysCache(spctuple);
 
 	return has_privs_of_role(roleid, spcowner);
 }
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 927711eed27f968067052310ae86769e585ff677..0da15569f3644d01476c574353c87b58b7ed828d 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -37,7 +37,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.65 2010/01/02 16:57:37 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.66 2010/01/05 21:53:58 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,6 +49,7 @@
 #include <sys/stat.h>
 
 #include "access/heapam.h"
+#include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -57,6 +58,7 @@
 #include "catalog/indexing.h"
 #include "catalog/pg_tablespace.h"
 #include "commands/comment.h"
+#include "commands/defrem.h"
 #include "commands/tablespace.h"
 #include "miscadmin.h"
 #include "postmaster/bgwriter.h"
@@ -70,6 +72,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 #include "utils/tqual.h"
 
 
@@ -290,6 +293,7 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
 	values[Anum_pg_tablespace_spclocation - 1] =
 		CStringGetTextDatum(location);
 	nulls[Anum_pg_tablespace_spcacl - 1] = true;
+	nulls[Anum_pg_tablespace_spcoptions - 1] = true;
 
 	tuple = heap_form_tuple(rel->rd_att, values, nulls);
 
@@ -912,6 +916,73 @@ AlterTableSpaceOwner(const char *name, Oid newOwnerId)
 }
 
 
+/*
+ * Alter table space options
+ */
+void
+AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
+{
+	Relation	rel;
+	ScanKeyData entry[1];
+	HeapScanDesc scandesc;
+	HeapTuple	tup;
+	Datum		datum;
+	Datum		newOptions;
+	Datum		repl_val[Natts_pg_tablespace];
+	bool		isnull;
+	bool		repl_null[Natts_pg_tablespace];
+	bool		repl_repl[Natts_pg_tablespace];
+	HeapTuple	newtuple;
+
+	/* Search pg_tablespace */
+	rel = heap_open(TableSpaceRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&entry[0],
+				Anum_pg_tablespace_spcname,
+				BTEqualStrategyNumber, F_NAMEEQ,
+				CStringGetDatum(stmt->tablespacename));
+	scandesc = heap_beginscan(rel, SnapshotNow, 1, entry);
+	tup = heap_getnext(scandesc, ForwardScanDirection);
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("tablespace \"%s\" does not exist",
+					stmt->tablespacename)));
+
+	/* Must be owner of the existing object */
+	if (!pg_tablespace_ownercheck(HeapTupleGetOid(tup), GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
+					   stmt->tablespacename);
+
+	/* Generate new proposed spcoptions (text array) */
+	datum = heap_getattr(tup, Anum_pg_tablespace_spcoptions,
+						 RelationGetDescr(rel), &isnull);
+	newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
+									 stmt->options, NULL, NULL, false,
+									 stmt->isReset);
+	(void) tablespace_reloptions(newOptions, true);
+
+	/* Build new tuple. */
+	memset(repl_null, false, sizeof(repl_null));
+	memset(repl_repl, false, sizeof(repl_repl));
+	if (newOptions != (Datum) 0)
+		repl_val[Anum_pg_tablespace_spcoptions - 1] = newOptions;
+	else
+		repl_null[Anum_pg_tablespace_spcoptions - 1] = true;
+	repl_repl[Anum_pg_tablespace_spcoptions - 1] = true;
+	newtuple = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val,
+								 repl_null, repl_repl);
+
+	/* Update system catalog. */
+	simple_heap_update(rel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(rel, newtuple);
+	heap_freetuple(newtuple);
+
+	/* Conclude heap scan. */
+	heap_endscan(scandesc);
+	heap_close(rel, NoLock);
+}
+
 /*
  * Routines for handling the GUC variable 'default_tablespace'.
  */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7cbbb1f913d5804d841b020b027e92310794f684..8d1f1641d7cffa65526d5b67e329b7475ba8eae0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.458 2010/01/02 16:57:45 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.459 2010/01/05 21:53:58 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -3064,6 +3064,18 @@ _copyDropTableSpaceStmt(DropTableSpaceStmt *from)
 	return newnode;
 }
 
+static AlterTableSpaceOptionsStmt *
+_copyAlterTableSpaceOptionsStmt(AlterTableSpaceOptionsStmt *from)
+{
+	AlterTableSpaceOptionsStmt *newnode = makeNode(AlterTableSpaceOptionsStmt);
+
+	COPY_STRING_FIELD(tablespacename);
+	COPY_NODE_FIELD(options);
+	COPY_SCALAR_FIELD(isReset);
+
+	return newnode;
+}
+
 static CreateFdwStmt *
 _copyCreateFdwStmt(CreateFdwStmt *from)
 {
@@ -4028,6 +4040,9 @@ copyObject(void *from)
 		case T_DropTableSpaceStmt:
 			retval = _copyDropTableSpaceStmt(from);
 			break;
+		case T_AlterTableSpaceOptionsStmt:
+			retval = _copyAlterTableSpaceOptionsStmt(from);
+			break;
 		case T_CreateFdwStmt:
 			retval = _copyCreateFdwStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 20abee3898d664affa44e4723d3ea0a2ebea933f..24e5377307715ebccc18c940571b72ed4e940d0e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -22,7 +22,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.379 2010/01/02 16:57:46 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.380 2010/01/05 21:53:58 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1568,6 +1568,17 @@ _equalDropTableSpaceStmt(DropTableSpaceStmt *a, DropTableSpaceStmt *b)
 	return true;
 }
 
+static bool
+_equalAlterTableSpaceOptionsStmt(AlterTableSpaceOptionsStmt *a,
+											 AlterTableSpaceOptionsStmt *b)
+{
+	COMPARE_STRING_FIELD(tablespacename);
+	COMPARE_NODE_FIELD(options);
+	COMPARE_SCALAR_FIELD(isReset);
+
+	return true;
+}
+
 static bool
 _equalCreateFdwStmt(CreateFdwStmt *a, CreateFdwStmt *b)
 {
@@ -2720,6 +2731,9 @@ equal(void *a, void *b)
 		case T_DropTableSpaceStmt:
 			retval = _equalDropTableSpaceStmt(a, b);
 			break;
+		case T_AlterTableSpaceOptionsStmt:
+			retval = _equalAlterTableSpaceOptionsStmt(a, b);
+			break;
 		case T_CreateFdwStmt:
 			retval = _equalCreateFdwStmt(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0342e66b515d36e369bc7f33176a14b1b6e53430..5bd092eae25a772c8bac2f35a46f7f49444ecaeb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.379 2010/01/02 16:57:46 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.380 2010/01/05 21:53:58 rhaas Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -1590,6 +1590,7 @@ _outRelOptInfo(StringInfo str, RelOptInfo *node)
 	WRITE_NODE_FIELD(cheapest_total_path);
 	WRITE_NODE_FIELD(cheapest_unique_path);
 	WRITE_UINT_FIELD(relid);
+	WRITE_UINT_FIELD(reltablespace);
 	WRITE_ENUM_FIELD(rtekind, RTEKind);
 	WRITE_INT_FIELD(min_attr);
 	WRITE_INT_FIELD(max_attr);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index bc028ee1473a6b99eb600321462307a58b7cce75..f92c944925fa9466ad4538f9d45194cd806772c5 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -27,6 +27,11 @@
  * detail.	Note that all of these parameters are user-settable, in case
  * the default values are drastically off for a particular platform.
  *
+ * seq_page_cost and random_page_cost can also be overridden for an individual
+ * tablespace, in case some data is on a fast disk and other data is on a slow
+ * disk.  Per-tablespace overrides never apply to temporary work files such as
+ * an external sort or a materialize node that overflows work_mem.
+ *
  * We compute two separate costs for each path:
  *		total_cost: total estimated cost to fetch all tuples
  *		startup_cost: cost that is expended before first tuple is fetched
@@ -54,7 +59,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.213 2010/01/02 16:57:46 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.214 2010/01/05 21:53:58 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -76,6 +81,7 @@
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
 #include "utils/selfuncs.h"
+#include "utils/spccache.h"
 #include "utils/tuplesort.h"
 
 
@@ -164,6 +170,7 @@ void
 cost_seqscan(Path *path, PlannerInfo *root,
 			 RelOptInfo *baserel)
 {
+	double		spc_seq_page_cost;
 	Cost		startup_cost = 0;
 	Cost		run_cost = 0;
 	Cost		cpu_per_tuple;
@@ -175,10 +182,15 @@ cost_seqscan(Path *path, PlannerInfo *root,
 	if (!enable_seqscan)
 		startup_cost += disable_cost;
 
+	/* fetch estimated page cost for tablespace containing table */
+	get_tablespace_page_costs(baserel->reltablespace,
+							  NULL,
+							  &spc_seq_page_cost);
+
 	/*
 	 * disk costs
 	 */
-	run_cost += seq_page_cost * baserel->pages;
+	run_cost += spc_seq_page_cost * baserel->pages;
 
 	/* CPU costs */
 	startup_cost += baserel->baserestrictcost.startup;
@@ -226,6 +238,8 @@ cost_index(IndexPath *path, PlannerInfo *root,
 	Selectivity indexSelectivity;
 	double		indexCorrelation,
 				csquared;
+	double		spc_seq_page_cost,
+				spc_random_page_cost;
 	Cost		min_IO_cost,
 				max_IO_cost;
 	Cost		cpu_per_tuple;
@@ -272,13 +286,18 @@ cost_index(IndexPath *path, PlannerInfo *root,
 	/* estimate number of main-table tuples fetched */
 	tuples_fetched = clamp_row_est(indexSelectivity * baserel->tuples);
 
+	/* fetch estimated page costs for tablespace containing table */
+	get_tablespace_page_costs(baserel->reltablespace,
+							  &spc_random_page_cost,
+							  &spc_seq_page_cost);
+
 	/*----------
 	 * Estimate number of main-table pages fetched, and compute I/O cost.
 	 *
 	 * When the index ordering is uncorrelated with the table ordering,
 	 * we use an approximation proposed by Mackert and Lohman (see
 	 * index_pages_fetched() for details) to compute the number of pages
-	 * fetched, and then charge random_page_cost per page fetched.
+	 * fetched, and then charge spc_random_page_cost per page fetched.
 	 *
 	 * When the index ordering is exactly correlated with the table ordering
 	 * (just after a CLUSTER, for example), the number of pages fetched should
@@ -286,7 +305,7 @@ cost_index(IndexPath *path, PlannerInfo *root,
 	 * will be sequential fetches, not the random fetches that occur in the
 	 * uncorrelated case.  So if the number of pages is more than 1, we
 	 * ought to charge
-	 *		random_page_cost + (pages_fetched - 1) * seq_page_cost
+	 *		spc_random_page_cost + (pages_fetched - 1) * spc_seq_page_cost
 	 * For partially-correlated indexes, we ought to charge somewhere between
 	 * these two estimates.  We currently interpolate linearly between the
 	 * estimates based on the correlation squared (XXX is that appropriate?).
@@ -309,7 +328,7 @@ cost_index(IndexPath *path, PlannerInfo *root,
 											(double) index->pages,
 											root);
 
-		max_IO_cost = (pages_fetched * random_page_cost) / num_scans;
+		max_IO_cost = (pages_fetched * spc_random_page_cost) / num_scans;
 
 		/*
 		 * In the perfectly correlated case, the number of pages touched by
@@ -328,7 +347,7 @@ cost_index(IndexPath *path, PlannerInfo *root,
 											(double) index->pages,
 											root);
 
-		min_IO_cost = (pages_fetched * random_page_cost) / num_scans;
+		min_IO_cost = (pages_fetched * spc_random_page_cost) / num_scans;
 	}
 	else
 	{
@@ -342,13 +361,13 @@ cost_index(IndexPath *path, PlannerInfo *root,
 											root);
 
 		/* max_IO_cost is for the perfectly uncorrelated case (csquared=0) */
-		max_IO_cost = pages_fetched * random_page_cost;
+		max_IO_cost = pages_fetched * spc_random_page_cost;
 
 		/* min_IO_cost is for the perfectly correlated case (csquared=1) */
 		pages_fetched = ceil(indexSelectivity * (double) baserel->pages);
-		min_IO_cost = random_page_cost;
+		min_IO_cost = spc_random_page_cost;
 		if (pages_fetched > 1)
-			min_IO_cost += (pages_fetched - 1) * seq_page_cost;
+			min_IO_cost += (pages_fetched - 1) * spc_seq_page_cost;
 	}
 
 	/*
@@ -553,6 +572,8 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
 	Cost		cost_per_page;
 	double		tuples_fetched;
 	double		pages_fetched;
+	double		spc_seq_page_cost,
+				spc_random_page_cost;
 	double		T;
 
 	/* Should only be applied to base relations */
@@ -571,6 +592,11 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
 
 	startup_cost += indexTotalCost;
 
+	/* Fetch estimated page costs for tablespace containing table. */
+	get_tablespace_page_costs(baserel->reltablespace,
+							  &spc_random_page_cost,
+							  &spc_seq_page_cost);
+
 	/*
 	 * Estimate number of main-table pages fetched.
 	 */
@@ -609,17 +635,18 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
 		pages_fetched = ceil(pages_fetched);
 
 	/*
-	 * For small numbers of pages we should charge random_page_cost apiece,
+	 * For small numbers of pages we should charge spc_random_page_cost apiece,
 	 * while if nearly all the table's pages are being read, it's more
-	 * appropriate to charge seq_page_cost apiece.	The effect is nonlinear,
+	 * appropriate to charge spc_seq_page_cost apiece.	The effect is nonlinear,
 	 * too. For lack of a better idea, interpolate like this to determine the
 	 * cost per page.
 	 */
 	if (pages_fetched >= 2.0)
-		cost_per_page = random_page_cost -
-			(random_page_cost - seq_page_cost) * sqrt(pages_fetched / T);
+		cost_per_page = spc_random_page_cost -
+			(spc_random_page_cost - spc_seq_page_cost)
+			* sqrt(pages_fetched / T);
 	else
-		cost_per_page = random_page_cost;
+		cost_per_page = spc_random_page_cost;
 
 	run_cost += pages_fetched * cost_per_page;
 
@@ -783,6 +810,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
 	QualCost	tid_qual_cost;
 	int			ntuples;
 	ListCell   *l;
+	double		spc_random_page_cost;
 
 	/* Should only be applied to base relations */
 	Assert(baserel->relid > 0);
@@ -835,8 +863,13 @@ cost_tidscan(Path *path, PlannerInfo *root,
 	 */
 	cost_qual_eval(&tid_qual_cost, tidquals, root);
 
+	/* fetch estimated page cost for tablespace containing table */
+	get_tablespace_page_costs(baserel->reltablespace,
+							  &spc_random_page_cost,
+							  NULL);
+
 	/* disk costs --- assume each tuple on a different page */
-	run_cost += random_page_cost * ntuples;
+	run_cost += spc_random_page_cost * ntuples;
 
 	/* CPU costs */
 	startup_cost += baserel->baserestrictcost.startup +
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 1683181107279098870f4dfe88a8552d7ba71860..fdce5bb5a3f24f8803d69fb58c0c7c12c2973dc0 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/plancat.c,v 1.161 2010/01/02 16:57:48 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/plancat.c,v 1.162 2010/01/05 21:53:58 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -91,6 +91,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 	rel->min_attr = FirstLowInvalidHeapAttributeNumber + 1;
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
+	rel->reltablespace = RelationGetForm(relation)->reltablespace;
 
 	Assert(rel->max_attr >= rel->min_attr);
 	rel->attr_needed = (Relids *)
@@ -183,6 +184,8 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 			info = makeNode(IndexOptInfo);
 
 			info->indexoid = index->indexrelid;
+			info->reltablespace =
+				RelationGetForm(indexRelation)->reltablespace;
 			info->rel = rel;
 			info->ncolumns = ncolumns = index->indnatts;
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ed906c1fedc565e56c80daae40f6ec73b39d68f5..2ea08893f3b3bba0d9b8c0d4fd7c16d489cb883e 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.701 2010/01/02 16:57:48 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.702 2010/01/05 21:53:58 rhaas Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -5687,6 +5687,24 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
 					n->newname = $6;
 					$$ = (Node *)n;
 				}
+			| ALTER TABLESPACE name SET reloptions
+				{
+					AlterTableSpaceOptionsStmt *n =
+						makeNode(AlterTableSpaceOptionsStmt);
+					n->tablespacename = $3;
+					n->options = $5;
+					n->isReset = FALSE;
+					$$ = (Node *)n;
+				}
+			| ALTER TABLESPACE name RESET reloptions
+				{
+					AlterTableSpaceOptionsStmt *n =
+						makeNode(AlterTableSpaceOptionsStmt);
+					n->tablespacename = $3;
+					n->options = $5;
+					n->isReset = TRUE;
+					$$ = (Node *)n;
+				}
 			| ALTER TEXT_P SEARCH PARSER any_name RENAME TO name
 				{
 					RenameStmt *n = makeNode(RenameStmt);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3bdc6e7819c7bdad60ec8f5eac18e5938c01488c..3da89ba08a7dad760b7898cdd7613f3dcc9af09b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.326 2010/01/02 16:57:53 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.327 2010/01/05 21:53:58 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -218,6 +218,7 @@ check_xact_readonly(Node *parsetree)
 		case T_CreateUserMappingStmt:
 		case T_AlterUserMappingStmt:
 		case T_DropUserMappingStmt:
+		case T_AlterTableSpaceOptionsStmt:
 			ereport(ERROR,
 					(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
 					 errmsg("transaction is read-only")));
@@ -528,6 +529,10 @@ standard_ProcessUtility(Node *parsetree,
 			DropTableSpace((DropTableSpaceStmt *) parsetree);
 			break;
 
+		case T_AlterTableSpaceOptionsStmt:
+			AlterTableSpaceOptions((AlterTableSpaceOptionsStmt *) parsetree);
+			break;
+
 		case T_CreateFdwStmt:
 			CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
 			break;
@@ -1456,6 +1461,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "DROP TABLESPACE";
 			break;
 
+		case T_AlterTableSpaceOptionsStmt:
+			tag = "ALTER TABLESPACE";
+			break;
+
 		case T_CreateFdwStmt:
 			tag = "CREATE FOREIGN DATA WRAPPER";
 			break;
@@ -2238,6 +2247,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_AlterTableSpaceOptionsStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		case T_CreateFdwStmt:
 		case T_AlterFdwStmt:
 		case T_DropFdwStmt:
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 2506eaaf82d102f84c246055c7078dd86b453b63..4bd302eab5164b030d428c913d4e14c72f357dd5 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.267 2010/01/04 02:44:39 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.268 2010/01/05 21:53:59 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -119,6 +119,7 @@
 #include "utils/nabstime.h"
 #include "utils/pg_locale.h"
 #include "utils/selfuncs.h"
+#include "utils/spccache.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
@@ -5648,6 +5649,7 @@ genericcostestimate(PlannerInfo *root,
 	QualCost	index_qual_cost;
 	double		qual_op_cost;
 	double		qual_arg_cost;
+	double		spc_random_page_cost;
 	List	   *selectivityQuals;
 	ListCell   *l;
 
@@ -5756,6 +5758,11 @@ genericcostestimate(PlannerInfo *root,
 	else
 		numIndexPages = 1.0;
 
+	/* fetch estimated page cost for schema containing index */
+	get_tablespace_page_costs(index->reltablespace,
+							  &spc_random_page_cost,
+							  NULL);
+
 	/*
 	 * Now compute the disk access costs.
 	 *
@@ -5802,15 +5809,16 @@ genericcostestimate(PlannerInfo *root,
 		 * share for each outer scan.  (Don't pro-rate for ScalarArrayOpExpr,
 		 * since that's internal to the indexscan.)
 		 */
-		*indexTotalCost = (pages_fetched * random_page_cost) / num_outer_scans;
+		*indexTotalCost = (pages_fetched * spc_random_page_cost)
+							/ num_outer_scans;
 	}
 	else
 	{
 		/*
-		 * For a single index scan, we just charge random_page_cost per page
-		 * touched.
+		 * For a single index scan, we just charge spc_random_page_cost per
+		 * page touched.
 		 */
-		*indexTotalCost = numIndexPages * random_page_cost;
+		*indexTotalCost = numIndexPages * spc_random_page_cost;
 	}
 
 	/*
@@ -5825,11 +5833,11 @@ genericcostestimate(PlannerInfo *root,
 	 *
 	 * We can deal with this by adding a very small "fudge factor" that
 	 * depends on the index size.  The fudge factor used here is one
-	 * random_page_cost per 100000 index pages, which should be small enough
-	 * to not alter index-vs-seqscan decisions, but will prevent indexes of
-	 * different sizes from looking exactly equally attractive.
+	 * spc_random_page_cost per 100000 index pages, which should be small
+	 * enough to not alter index-vs-seqscan decisions, but will prevent
+	 * indexes of different sizes from looking exactly equally attractive.
 	 */
-	*indexTotalCost += index->pages * random_page_cost / 100000.0;
+	*indexTotalCost += index->pages * spc_random_page_cost / 100000.0;
 
 	/*
 	 * CPU cost: any complex expressions in the indexquals will need to be
diff --git a/src/backend/utils/cache/Makefile b/src/backend/utils/cache/Makefile
index 1766c0315f872f2692117e7f1f276af7bea442de..1a3d2cc482e2229c46cbc7ab9776d984b17ee397 100644
--- a/src/backend/utils/cache/Makefile
+++ b/src/backend/utils/cache/Makefile
@@ -4,7 +4,7 @@
 #    Makefile for utils/cache
 #
 # IDENTIFICATION
-#    $PostgreSQL: pgsql/src/backend/utils/cache/Makefile,v 1.23 2008/02/19 10:30:08 petere Exp $
+#    $PostgreSQL: pgsql/src/backend/utils/cache/Makefile,v 1.24 2010/01/05 21:53:59 rhaas Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catcache.o inval.o plancache.o relcache.o \
-	syscache.o lsyscache.o typcache.o ts_cache.o
+	spccache.o syscache.o lsyscache.o typcache.o ts_cache.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c
new file mode 100644
index 0000000000000000000000000000000000000000..00629c09f069ab18d7b902da8f91319dd049ab88
--- /dev/null
+++ b/src/backend/utils/cache/spccache.c
@@ -0,0 +1,183 @@
+/*-------------------------------------------------------------------------
+ *
+ * spccache.c
+ *	  Tablespace cache management.
+ *
+ * We cache the parsed version of spcoptions for each tablespace to avoid
+ * needing to reparse on every lookup.  Right now, there doesn't appear to
+ * be a measurable performance gain from doing this, but that might change
+ * in the future as we add more options.
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/spccache.c,v 1.1 2010/01/05 21:53:59 rhaas Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "access/reloptions.h"
+#include "catalog/pg_tablespace.h"
+#include "commands/tablespace.h"
+#include "miscadmin.h"
+#include "optimizer/cost.h"
+#include "utils/catcache.h"
+#include "utils/hsearch.h"
+#include "utils/inval.h"
+#include "utils/spccache.h"
+#include "utils/syscache.h"
+
+static HTAB *TableSpaceCacheHash = NULL;
+
+typedef struct {
+	Oid			oid;
+	TableSpaceOpts *opts;
+} TableSpace;
+
+/*
+ * InvalidateTableSpaceCacheCallback
+ *		Flush all cache entries when pg_tablespace is updated.
+ *
+ * When pg_tablespace is updated, we must flush the cache entry at least
+ * for that tablespace.  Currently, we just flush them all.  This is quick
+ * and easy and doesn't cost much, since there shouldn't be terribly many
+ * tablespaces, nor do we expect them to be frequently modified.
+ */
+static void
+InvalidateTableSpaceCacheCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
+{
+	HASH_SEQ_STATUS status;
+	TableSpace *spc;
+
+	hash_seq_init(&status, TableSpaceCacheHash);
+	while ((spc = (TableSpace *) hash_seq_search(&status)) != NULL)
+	{
+		if (hash_search(TableSpaceCacheHash, (void *) &spc->oid, HASH_REMOVE,
+						NULL) == NULL)
+			elog(ERROR, "hash table corrupted");
+		if (spc->opts)
+			pfree(spc->opts);
+	}
+}
+
+/*
+ * InitializeTableSpaceCache
+ *		Initiate the tablespace cache.
+ */
+static void
+InitializeTableSpaceCache(void)
+{
+	HASHCTL ctl;
+
+	/* Initialize the hash table. */
+	MemSet(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(TableSpace);
+	ctl.hash = tag_hash;
+	TableSpaceCacheHash =
+		hash_create("TableSpace cache", 16, &ctl,
+				    HASH_ELEM | HASH_FUNCTION);
+
+	/* Make sure we've initialized CacheMemoryContext. */
+	if (!CacheMemoryContext)
+		CreateCacheMemoryContext();
+
+	/* Watch for invalidation events. */
+	CacheRegisterSyscacheCallback(TABLESPACEOID,
+								  InvalidateTableSpaceCacheCallback,
+								  (Datum) 0);
+}
+
+/*
+ * get_tablespace
+ *		Fetch TableSpace structure for a specified table OID.
+ *
+ * Pointers returned by this function should not be stored, since a cache
+ * flush will invalidate them.
+ */
+static TableSpace *
+get_tablespace(Oid spcid)
+{
+	HeapTuple	tp;
+	TableSpace *spc;
+	bool		found;
+
+	/*
+	 * Since spcid is always from a pg_class tuple, InvalidOid implies the
+	 * default.
+	 */
+	if (spcid == InvalidOid)
+		spcid = MyDatabaseTableSpace;
+
+	/* Find existing cache entry, or create a new one. */
+	if (!TableSpaceCacheHash)
+		InitializeTableSpaceCache();
+	spc = (TableSpace *) hash_search(TableSpaceCacheHash, (void *) &spcid,
+									 HASH_ENTER, &found);
+	if (found)
+		return spc;
+
+	/*
+	 * Not found in TableSpace cache.  Check catcache.  If we don't find a
+	 * valid HeapTuple, it must mean someone has managed to request tablespace
+	 * details for a non-existent tablespace.  We'll just treat that case as if
+	 * no options were specified.
+	 */
+	tp = SearchSysCache(TABLESPACEOID, ObjectIdGetDatum(spcid), 0, 0, 0);
+	if (!HeapTupleIsValid(tp))
+		spc->opts = NULL;
+	else
+	{
+		Datum	datum;
+		bool	isNull;
+		MemoryContext octx;
+
+		datum = SysCacheGetAttr(TABLESPACEOID,
+								tp,
+								Anum_pg_tablespace_spcoptions,
+								&isNull);
+		if (isNull)
+			spc->opts = NULL;
+		else
+		{
+			octx = MemoryContextSwitchTo(CacheMemoryContext);
+			spc->opts = (TableSpaceOpts *) tablespace_reloptions(datum, false);
+			MemoryContextSwitchTo(octx);
+		}
+		ReleaseSysCache(tp);
+	}
+
+	/* Update new TableSpace cache entry with results of option parsing. */
+	return spc;
+}
+
+/*
+ * get_tablespace_page_costs
+ *		Return random and sequential page costs for a given tablespace.
+ */
+void
+get_tablespace_page_costs(Oid spcid, double *spc_random_page_cost,
+							   double *spc_seq_page_cost)
+{
+	TableSpace *spc = get_tablespace(spcid);
+
+	Assert(spc != NULL);
+
+	if (spc_random_page_cost)
+	{
+		if (!spc->opts || spc->opts->random_page_cost < 0)
+			*spc_random_page_cost = random_page_cost;
+		else
+			*spc_random_page_cost = spc->opts->random_page_cost;
+	}
+
+	if (spc_seq_page_cost)
+	{
+		if (!spc->opts || spc->opts->seq_page_cost < 0)
+			*spc_seq_page_cost = seq_page_cost;
+		else
+			*spc_seq_page_cost = spc->opts->seq_page_cost;
+	}
+}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index bd747abc80c5be0526911cb27400f681cb5f5b9b..f35712732b2f349a9691316e24ec80f83a121fac 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/syscache.c,v 1.123 2010/01/02 16:57:56 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/syscache.c,v 1.124 2010/01/05 21:53:59 rhaas Exp $
  *
  * NOTES
  *	  These routines allow the parser/planner/executor to perform
@@ -43,6 +43,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_statistic.h"
+#include "catalog/pg_tablespace.h"
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_config_map.h"
 #include "catalog/pg_ts_dict.h"
@@ -609,6 +610,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		1024
 	},
+	{TableSpaceRelationId,		/* TABLESPACEOID */
+		TablespaceOidIndexId,
+		0,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0,
+		},
+		16
+	},
 	{TSConfigMapRelationId,		/* TSCONFIGMAP */
 		TSConfigMapIndexId,
 		0,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 8ddc0384789b11ada05f795b377bb1a6faafb9c6..f46a29586cbffb846b0153d69122656a3f2d6268 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dumpall.c,v 1.129 2010/01/02 16:57:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dumpall.c,v 1.130 2010/01/05 21:53:59 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -956,19 +956,28 @@ dumpTablespaces(PGconn *conn)
 	 * Get all tablespaces except built-in ones (which we assume are named
 	 * pg_xxx)
 	 */
-	if (server_version >= 80200)
+	if (server_version >= 80500)
 		res = executeQuery(conn, "SELECT spcname, "
 						 "pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
 						   "spclocation, spcacl, "
+						   "array_to_string(spcoptions, ', '),"
 						"pg_catalog.shobj_description(oid, 'pg_tablespace') "
 						   "FROM pg_catalog.pg_tablespace "
 						   "WHERE spcname !~ '^pg_' "
 						   "ORDER BY 1");
+	else if (server_version >= 80200)
+		res = executeQuery(conn, "SELECT spcname, "
+						 "pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
+						   "spclocation, spcacl, null, "
+						"pg_catalog.shobj_description(oid, 'pg_tablespace'), "
+						   "FROM pg_catalog.pg_tablespace "
+						   "WHERE spcname !~ '^pg_' "
+						   "ORDER BY 1");
 	else
 		res = executeQuery(conn, "SELECT spcname, "
 						 "pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
 						   "spclocation, spcacl, "
-						   "null "
+						   "null, null "
 						   "FROM pg_catalog.pg_tablespace "
 						   "WHERE spcname !~ '^pg_' "
 						   "ORDER BY 1");
@@ -983,7 +992,8 @@ dumpTablespaces(PGconn *conn)
 		char	   *spcowner = PQgetvalue(res, i, 1);
 		char	   *spclocation = PQgetvalue(res, i, 2);
 		char	   *spcacl = PQgetvalue(res, i, 3);
-		char	   *spccomment = PQgetvalue(res, i, 4);
+		char	   *spcoptions = PQgetvalue(res, i, 4);
+		char	   *spccomment = PQgetvalue(res, i, 5);
 		char	   *fspcname;
 
 		/* needed for buildACLCommands() */
@@ -996,6 +1006,10 @@ dumpTablespaces(PGconn *conn)
 		appendStringLiteralConn(buf, spclocation, conn);
 		appendPQExpBuffer(buf, ";\n");
 
+		if (spcoptions && spcoptions[0] != '\0')
+			appendPQExpBuffer(buf, "ALTER TABLESPACE %s SET (%s);\n",
+							  fspcname, spcoptions);
+
 		if (!skip_acls &&
 			!buildACLCommands(fspcname, NULL, "TABLESPACE", spcacl, spcowner,
 							  "", server_version, buf))
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a845c1d83cf9f66390fdb2c22c676665d254385f..f7f5587f7c1679396d258d330f6a4c2ebeb586fa 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -1,7 +1,8 @@
 /*-------------------------------------------------------------------------
  *
  * reloptions.h
- *	  Core support for relation options (pg_class.reloptions)
+ *	  Core support for relation and tablespace options (pg_class.reloptions
+ *	  and pg_tablespace.spcoptions)
  *
  * Note: the functions dealing with text-array reloptions values declare
  * them as Datum, not ArrayType *, to avoid needing to include array.h
@@ -11,7 +12,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/reloptions.h,v 1.17 2010/01/02 16:58:00 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/reloptions.h,v 1.18 2010/01/05 21:53:59 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,8 +40,9 @@ typedef enum relopt_kind
 	RELOPT_KIND_HASH = (1 << 3),
 	RELOPT_KIND_GIN = (1 << 4),
 	RELOPT_KIND_GIST = (1 << 5),
+	RELOPT_KIND_TABLESPACE = (1 << 6),
 	/* if you add a new kind, make sure you update "last_default" too */
-	RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_GIST,
+	RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_TABLESPACE,
 	/* some compilers treat enums as signed ints, so we can't use 1 << 31 */
 	RELOPT_KIND_MAX = (1 << 30)
 } relopt_kind;
@@ -264,5 +266,6 @@ extern bytea *default_reloptions(Datum reloptions, bool validate,
 extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate);
 extern bytea *index_reloptions(RegProcedure amoptions, Datum reloptions,
 				 bool validate);
+extern bytea *tablespace_reloptions(Datum reloptions, bool validate);
 
 #endif   /* RELOPTIONS_H */
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 49bb91981834362f6ea2665b41513b3cd076f525..6e7a8c33fc4ac8ca63e3e3ca424756a714d0158b 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.566 2010/01/04 12:50:49 heikki Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.567 2010/01/05 21:53:59 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201001041
+#define CATALOG_VERSION_NO	201001051
 
 #endif
diff --git a/src/include/catalog/pg_tablespace.h b/src/include/catalog/pg_tablespace.h
index 1d15cdfa789d01a03379b34b482d2aa43cb3fa39..1c189f56510ac505cac8f6ea8b9600498cd998bb 100644
--- a/src/include/catalog/pg_tablespace.h
+++ b/src/include/catalog/pg_tablespace.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_tablespace.h,v 1.14 2010/01/05 01:06:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_tablespace.h,v 1.15 2010/01/05 21:53:59 rhaas Exp $
  *
  * NOTES
  *	  the genbki.pl script reads this file and generates .bki
@@ -34,6 +34,7 @@ CATALOG(pg_tablespace,1213) BKI_SHARED_RELATION
 	Oid			spcowner;		/* owner of tablespace */
 	text		spclocation;	/* physical location (VAR LENGTH) */
 	aclitem		spcacl[1];		/* access permissions (VAR LENGTH) */
+	text		spcoptions[1];	/* per-tablespace options */
 } FormData_pg_tablespace;
 
 /* ----------------
@@ -48,14 +49,15 @@ typedef FormData_pg_tablespace *Form_pg_tablespace;
  * ----------------
  */
 
-#define Natts_pg_tablespace				4
+#define Natts_pg_tablespace				5
 #define Anum_pg_tablespace_spcname		1
 #define Anum_pg_tablespace_spcowner		2
 #define Anum_pg_tablespace_spclocation	3
 #define Anum_pg_tablespace_spcacl		4
+#define Anum_pg_tablespace_spcoptions	5
 
-DATA(insert OID = 1663 ( pg_default PGUID "" _null_ ));
-DATA(insert OID = 1664 ( pg_global	PGUID "" _null_ ));
+DATA(insert OID = 1663 ( pg_default PGUID "" _null_ _null_ ));
+DATA(insert OID = 1664 ( pg_global	PGUID "" _null_ _null_ ));
 
 #define DEFAULTTABLESPACE_OID 1663
 #define GLOBALTABLESPACE_OID 1664
diff --git a/src/include/commands/tablespace.h b/src/include/commands/tablespace.h
index 332bf46f0aea8affbf1094cd06e704b659302bf2..9330a1f81feb5151fd6ae59e1840bf395a630fed 100644
--- a/src/include/commands/tablespace.h
+++ b/src/include/commands/tablespace.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/tablespace.h,v 1.21 2010/01/02 16:58:03 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/tablespace.h,v 1.22 2010/01/05 21:53:59 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,11 +32,17 @@ typedef struct xl_tblspc_drop_rec
 	Oid			ts_id;
 } xl_tblspc_drop_rec;
 
+typedef struct TableSpaceOpts
+{
+	float8		random_page_cost;
+	float8		seq_page_cost;
+} TableSpaceOpts;
 
 extern void CreateTableSpace(CreateTableSpaceStmt *stmt);
 extern void DropTableSpace(DropTableSpaceStmt *stmt);
 extern void RenameTableSpace(const char *oldname, const char *newname);
 extern void AlterTableSpaceOwner(const char *name, Oid newOwnerId);
+extern void AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt);
 
 extern void TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a84de410963cccb10600c5a7b99be3673f53a89b..e1da4954390b01f771c02bf2e7afe53303fca7c6 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.232 2010/01/02 16:58:04 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.233 2010/01/05 21:53:59 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -346,6 +346,7 @@ typedef enum NodeTag
 	T_CreateUserMappingStmt,
 	T_AlterUserMappingStmt,
 	T_DropUserMappingStmt,
+	T_AlterTableSpaceOptionsStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d94c6f990868d1d5df504a0eec04df1b8f409236..18673ec30d8a11fd42dc0b72119e25c692c0e9f9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.421 2010/01/02 16:58:04 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.422 2010/01/05 21:53:59 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1477,6 +1477,14 @@ typedef struct DropTableSpaceStmt
 	bool		missing_ok;		/* skip error if missing? */
 } DropTableSpaceStmt;
 
+typedef struct AlterTableSpaceOptionsStmt
+{
+	NodeTag		type;
+	char	   *tablespacename;
+	List	   *options;
+	bool		isReset;
+} AlterTableSpaceOptionsStmt;
+
 /* ----------------------
  *		Create/Drop FOREIGN DATA WRAPPER Statements
  * ----------------------
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index df8f4afc8d49a5f9b1ca5cb4f5f458c558ef9537..fd93dfcce347791936916317efac6016dcfed4e9 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.182 2010/01/02 16:58:07 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.183 2010/01/05 21:54:00 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -371,6 +371,7 @@ typedef struct RelOptInfo
 
 	/* information about a base rel (not set for join rels!) */
 	Index		relid;
+	Oid			reltablespace;	/* containing tablespace */
 	RTEKind		rtekind;		/* RELATION, SUBQUERY, or FUNCTION */
 	AttrNumber	min_attr;		/* smallest attrno of rel (often <0) */
 	AttrNumber	max_attr;		/* largest attrno of rel */
@@ -435,6 +436,7 @@ typedef struct IndexOptInfo
 	NodeTag		type;
 
 	Oid			indexoid;		/* OID of the index relation */
+	Oid			reltablespace;	/* tablespace of index (not table) */
 	RelOptInfo *rel;			/* back-link to index's table */
 
 	/* statistics from pg_class */
diff --git a/src/include/utils/spccache.h b/src/include/utils/spccache.h
new file mode 100644
index 0000000000000000000000000000000000000000..73b9f7370d95ae7fd1a9e04596bd41c3ce979853
--- /dev/null
+++ b/src/include/utils/spccache.h
@@ -0,0 +1,19 @@
+/*-------------------------------------------------------------------------
+ *
+ * spccache.h
+ *	  Tablespace cache.
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/utils/spccache.h,v 1.1 2010/01/05 21:54:00 rhaas Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SPCCACHE_H
+#define SPCCACHE_H
+
+void get_tablespace_page_costs(Oid spcid, float8 *spc_random_page_cost,
+					     float8 *spc_seq_page_cost);
+
+#endif   /* SPCCACHE_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 6b12c84da2e65e920298a534bed8088560b85a14..8cdd7e79ee1142cd0bdab53d2544829c80293d24 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/syscache.h,v 1.77 2010/01/02 16:58:10 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/syscache.h,v 1.78 2010/01/05 21:54:00 rhaas Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -71,6 +71,7 @@ enum SysCacheIdentifier
 	RELOID,
 	RULERELNAME,
 	STATRELATTINH,
+	TABLESPACEOID,
 	TSCONFIGMAP,
 	TSCONFIGNAMENSP,
 	TSCONFIGOID,
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index df5479d5893e028b26afbaef5da75156b7f68966..dba96f4547fa34424ee76dccaa676e8a21f0c7c6 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -1,6 +1,12 @@
 -- create a tablespace we can use
 CREATE TABLESPACE testspace LOCATION '@testtablespace@';
 
+-- try setting and resetting some properties for the new tablespace
+ALTER TABLESPACE testspace SET (random_page_cost = 1.0);
+ALTER TABLESPACE testspace SET (some_nonexistent_parameter = true);  -- fail
+ALTER TABLESPACE testspace RESET (random_page_cost = 2.0); -- fail
+ALTER TABLESPACE testspace RESET (random_page_cost, seq_page_cost); -- ok
+
 -- create a schema we can use
 CREATE SCHEMA testschema;
 
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index e57ad2b21745f926bed270318ef8b906a28fa97f..79b12a869860d450304763f5a36b1192790bb839 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -1,5 +1,12 @@
 -- create a tablespace we can use
 CREATE TABLESPACE testspace LOCATION '@testtablespace@';
+-- try setting and resetting some properties for the new tablespace
+ALTER TABLESPACE testspace SET (random_page_cost = 1.0);
+ALTER TABLESPACE testspace SET (some_nonexistent_parameter = true);  -- fail
+ERROR:  unrecognized parameter "some_nonexistent_parameter"
+ALTER TABLESPACE testspace RESET (random_page_cost = 2.0); -- fail
+ERROR:  RESET must not include values for parameters
+ALTER TABLESPACE testspace RESET (random_page_cost, seq_page_cost); -- ok
 -- create a schema we can use
 CREATE SCHEMA testschema;
 -- try a table