diff --git a/doc/src/sgml/ref/alter_tablespace.sgml b/doc/src/sgml/ref/alter_tablespace.sgml
index 0dfa4652fd71684564c1090c9b09f06cd7c7ef6e..99ee08a8da510cff8fb9a8732a9c8b675127a1f0 100644
--- a/doc/src/sgml/ref/alter_tablespace.sgml
+++ b/doc/src/sgml/ref/alter_tablespace.sgml
@@ -25,7 +25,7 @@ ALTER TABLESPACE <replaceable>name</replaceable> RENAME TO <replaceable>new_name
 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> [, ... ] )
-ALTER TABLESPACE <replaceable>name</replaceable> MOVE { ALL | TABLES | INDEXES | MATERIALIZED VIEWS } TO <replaceable>new_tablespace</replaceable> [ NOWAIT ]
+ALTER TABLESPACE <replaceable>name</replaceable> MOVE { ALL | TABLES | INDEXES | MATERIALIZED VIEWS } [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ...] ] TO <replaceable>new_tablespace</replaceable> [ NOWAIT ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -34,8 +34,8 @@ ALTER TABLESPACE <replaceable>name</replaceable> MOVE { ALL | TABLES | INDEXES |
 
   <para>
    <command>ALTER TABLESPACE</command> can be used to change the definition of
-   a tablespace or to migrate all of the objects in the current database which
-   are owned by the user out of a given tablespace.
+   a tablespace or to migrate objects in the current database between
+   tablespaces.
   </para>
 
   <para>
@@ -44,13 +44,19 @@ ALTER TABLESPACE <replaceable>name</replaceable> MOVE { ALL | TABLES | INDEXES |
    owning role.
    (Note that superusers have these privileges automatically.)
 
-   Users may use ALTER TABLESPACE ... MOVE to move either ALL of their objects,
-   or just TABLES, INDEXES, or MATERIALIZED VIEWS, but they must have CREATE
-   rights on the new tablespace and only objects, directly or indirectly, owned
-   by the user will be moved.  Note that the superuser is considered an owner
-   of all objects and therefore an ALTER TABLESPACE ... MOVE ALL issued by the
-   superuser will move all objects in the current database which are in the
-   tablespace.
+   Users may use ALTER TABLESPACE ... MOVE to move objects between tablespaces.
+   ALL will move all tables, indexes and materialized views while specifying
+   TABLES will move only tables (but not their indexes), INDEXES will only move
+   indexes (including those underneath materialized views, but not tables) and
+   MATERIALIZED VIEWS will only move the table relation of the materialized
+   view (but no indexes associated with it).  Users may also specify a list of
+   roles whose objects are to be moved using OWNED BY.
+
+   Users must have CREATE rights on the new tablespace and be considered an
+   owner (either directly or indirectly) on all objects to be moved.  Note that
+   the superuser is considered an owner of all objects and therefore an
+   ALTER TABLESPACE ... MOVE ALL issued by the superuser will move all objects
+   in the current database which are in the tablespace.
 
    All objects to be moved will be locked immediately by the command.  The
    NOWAIT option, if specified, will cause the command to fail if it is unable
@@ -115,6 +121,15 @@ ALTER TABLESPACE <replaceable>name</replaceable> MOVE { ALL | TABLES | INDEXES |
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">role_name</replaceable></term>
+    <listitem>
+     <para>
+      Role(s) whose objects are to be moved.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">new_tablespace</replaceable></term>
     <listitem>
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 05a89f0bde213557b3006450f832c9f846689ef0..d73e5e826dce25421d3d7191159947521890535d 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -67,6 +67,7 @@
 #include "commands/seclabel.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "commands/user.h"
 #include "common/relpath.h"
 #include "miscadmin.h"
 #include "postmaster/bgwriter.h"
@@ -994,6 +995,7 @@ AlterTableSpaceMove(AlterTableSpaceMoveStmt *stmt)
 	HeapTuple	tuple;
 	Oid			orig_tablespaceoid;
 	Oid			new_tablespaceoid;
+	List	   *role_oids = roleNamesToIds(stmt->roles);
 
 	/* Ensure we were not asked to move something we can't */
 	if (!stmt->move_all && stmt->objtype != OBJECT_TABLE &&
@@ -1075,14 +1077,10 @@ AlterTableSpaceMove(AlterTableSpaceMoveStmt *stmt)
 			relForm->relnamespace == PG_TOAST_NAMESPACE)
 			continue;
 
-		/*
-		 * Only move objects that we are considered an owner of and only
-		 * objects which can actually have a tablespace.
-		 */
-		if (!pg_class_ownercheck(relOid, GetUserId()) ||
-			(relForm->relkind != RELKIND_RELATION &&
-			 relForm->relkind != RELKIND_INDEX &&
-			 relForm->relkind != RELKIND_MATVIEW))
+		/* Only consider objects which live in tablespaces */
+		if (relForm->relkind != RELKIND_RELATION &&
+			relForm->relkind != RELKIND_INDEX &&
+			relForm->relkind != RELKIND_MATVIEW)
 			continue;
 
 		/* Check if we were asked to only move a certain type of object */
@@ -1095,6 +1093,21 @@ AlterTableSpaceMove(AlterTableSpaceMoveStmt *stmt)
 			  relForm->relkind != RELKIND_MATVIEW)))
 			continue;
 
+		/* Check if we are only moving objects owned by certain roles */
+		if (role_oids != NIL && !list_member_oid(role_oids, relForm->relowner))
+			continue;
+
+		/*
+		 * Handle permissions-checking here since we are locking the tables
+		 * and also to avoid doing a bunch of work only to fail part-way.
+		 * Note that permissions will also be checked by AlterTableInternal().
+		 *
+		 * Caller must be considered an owner on the table to move it.
+		 */
+		if (!pg_class_ownercheck(relOid, GetUserId()))
+			aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+						   NameStr(relForm->relname));
+
 		if (stmt->nowait &&
 			!ConditionalLockRelationOid(relOid, AccessExclusiveLock))
 			ereport(ERROR,
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index f8cf2a1b462b6f6355578dfff58bbbf4ca23ebb5..bcdc392a817525df11e5f7ae3182d53a1df7964c 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -48,7 +48,6 @@ extern bool Password_encryption;
 /* Hook to check passwords in CreateRole() and AlterRole() */
 check_password_hook_type check_password_hook = NULL;
 
-static List *roleNamesToIds(List *memberNames);
 static void AddRoleMems(const char *rolename, Oid roleid,
 			List *memberNames, List *memberIds,
 			Oid grantorId, bool admin_opt);
@@ -1302,7 +1301,7 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
  * Given a list of role names (as String nodes), generate a list of role OIDs
  * in the same order.
  */
-static List *
+List *
 roleNamesToIds(List *memberNames)
 {
 	List	   *result = NIL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bb356d0b26366c724d8da7aa86c7bd49f8c7788a..f90cb6797de7f819cdbb087128a9d92b25e75340 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3404,6 +3404,9 @@ _copyAlterTableSpaceMoveStmt(const AlterTableSpaceMoveStmt *from)
 	AlterTableSpaceMoveStmt *newnode = makeNode(AlterTableSpaceMoveStmt);
 
 	COPY_STRING_FIELD(orig_tablespacename);
+	COPY_SCALAR_FIELD(objtype);
+	COPY_SCALAR_FIELD(move_all);
+	COPY_NODE_FIELD(roles);
 	COPY_STRING_FIELD(new_tablespacename);
 	COPY_SCALAR_FIELD(nowait);
 
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5908d9abcf91368e96b77cb4191bec3054f83a5f..9438e7861d8f189a9362ea661684bb6b78f3fac9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1640,6 +1640,9 @@ _equalAlterTableSpaceMoveStmt(const AlterTableSpaceMoveStmt *a,
 							  const AlterTableSpaceMoveStmt *b)
 {
 	COMPARE_STRING_FIELD(orig_tablespacename);
+	COMPARE_SCALAR_FIELD(objtype);
+	COMPARE_SCALAR_FIELD(move_all);
+	COMPARE_NODE_FIELD(roles);
 	COMPARE_STRING_FIELD(new_tablespacename);
 	COMPARE_SCALAR_FIELD(nowait);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 363a22c16906cde2bc4b5820777450b58d38ffea..0787eb7c5d4c2eda4ec57592c2f3bbe0281a1de0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -7325,9 +7325,11 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
 					AlterTableSpaceMoveStmt *n =
 						makeNode(AlterTableSpaceMoveStmt);
 					n->orig_tablespacename = $3;
+					n->objtype = -1;
+					n->move_all = true;
+					n->roles = NIL;
 					n->new_tablespacename = $7;
 					n->nowait = $8;
-					n->move_all = true;
 					$$ = (Node *)n;
 				}
 			| ALTER TABLESPACE name MOVE TABLES TO name opt_nowait
@@ -7335,10 +7337,11 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
 					AlterTableSpaceMoveStmt *n =
 						makeNode(AlterTableSpaceMoveStmt);
 					n->orig_tablespacename = $3;
-					n->new_tablespacename = $7;
-					n->nowait = $8;
 					n->objtype = OBJECT_TABLE;
 					n->move_all = false;
+					n->roles = NIL;
+					n->new_tablespacename = $7;
+					n->nowait = $8;
 					$$ = (Node *)n;
 				}
 			| ALTER TABLESPACE name MOVE INDEXES TO name opt_nowait
@@ -7346,10 +7349,11 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
 					AlterTableSpaceMoveStmt *n =
 						makeNode(AlterTableSpaceMoveStmt);
 					n->orig_tablespacename = $3;
-					n->new_tablespacename = $7;
-					n->nowait = $8;
 					n->objtype = OBJECT_INDEX;
 					n->move_all = false;
+					n->roles = NIL;
+					n->new_tablespacename = $7;
+					n->nowait = $8;
 					$$ = (Node *)n;
 				}
 			| ALTER TABLESPACE name MOVE MATERIALIZED VIEWS TO name opt_nowait
@@ -7357,10 +7361,59 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
 					AlterTableSpaceMoveStmt *n =
 						makeNode(AlterTableSpaceMoveStmt);
 					n->orig_tablespacename = $3;
+					n->objtype = OBJECT_MATVIEW;
+					n->move_all = false;
+					n->roles = NIL;
 					n->new_tablespacename = $8;
 					n->nowait = $9;
+					$$ = (Node *)n;
+				}
+			| ALTER TABLESPACE name MOVE ALL OWNED BY role_list TO name opt_nowait
+				{
+					AlterTableSpaceMoveStmt *n =
+						makeNode(AlterTableSpaceMoveStmt);
+					n->orig_tablespacename = $3;
+					n->objtype = -1;
+					n->move_all = true;
+					n->roles = $8;
+					n->new_tablespacename = $10;
+					n->nowait = $11;
+					$$ = (Node *)n;
+				}
+			| ALTER TABLESPACE name MOVE TABLES OWNED BY role_list TO name opt_nowait
+				{
+					AlterTableSpaceMoveStmt *n =
+						makeNode(AlterTableSpaceMoveStmt);
+					n->orig_tablespacename = $3;
+					n->objtype = OBJECT_TABLE;
+					n->move_all = false;
+					n->roles = $8;
+					n->new_tablespacename = $10;
+					n->nowait = $11;
+					$$ = (Node *)n;
+				}
+			| ALTER TABLESPACE name MOVE INDEXES OWNED BY role_list TO name opt_nowait
+				{
+					AlterTableSpaceMoveStmt *n =
+						makeNode(AlterTableSpaceMoveStmt);
+					n->orig_tablespacename = $3;
+					n->objtype = OBJECT_INDEX;
+					n->move_all = false;
+					n->roles = $8;
+					n->new_tablespacename = $10;
+					n->nowait = $11;
+					$$ = (Node *)n;
+				}
+			| ALTER TABLESPACE name MOVE MATERIALIZED VIEWS OWNED BY role_list TO name opt_nowait
+				{
+					AlterTableSpaceMoveStmt *n =
+						makeNode(AlterTableSpaceMoveStmt);
+					n->orig_tablespacename = $3;
 					n->objtype = OBJECT_MATVIEW;
 					n->move_all = false;
+					n->roles = $9;
+					n->new_tablespacename = $11;
+					n->nowait = $12;
 					$$ = (Node *)n;
 				}
 			| ALTER TABLESPACE name SET reloptions
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 9e73a195e3ff8b22e2196ceb17d2f67e8719595d..d76685182f8b56de753dc9d4453dfc01c0a8eff4 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -30,5 +30,6 @@ extern void GrantRole(GrantRoleStmt *stmt);
 extern Oid	RenameRole(const char *oldname, const char *newname);
 extern void DropOwnedObjects(DropOwnedStmt *stmt);
 extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
+extern List *roleNamesToIds(List *memberNames);
 
 #endif   /* USER_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 846c31aebdfdadf1112e6ed11d77642d5c543355..ad58b3949b60848c2e3911770e576d5ba5eeddcf 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1691,10 +1691,11 @@ typedef struct AlterTableSpaceMoveStmt
 {
 	NodeTag		type;
 	char	   *orig_tablespacename;
+	ObjectType	objtype;		/* set to -1 if move_all is true */
+	bool		move_all;		/* move all, or just objtype objects? */
+	List	   *roles;			/* List of roles to move objects of */
 	char	   *new_tablespacename;
-	ObjectType	objtype;
 	bool		nowait;
-	bool		move_all;
 } AlterTableSpaceMoveStmt;
 
 /* ----------------------