diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index dc688c415f..1f212b4d68 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1322,8 +1322,6 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     If a table parameter value is set and the
     equivalent <literal>toast.</literal> parameter is not, the TOAST table
     will use the table's parameter value.
-    Specifying these parameters for partitioned tables is not supported,
-    but you may specify them for individual leaf partitions.
    </para>
 
    <variablelist>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228a8c..35bc2e5bdb 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -108,7 +108,7 @@ static relopt_bool boolRelOpts[] =
 		{
 			"autovacuum_enabled",
 			"Enables autovacuum in this relation",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST | RELOPT_KIND_PARTITIONED,
 			ShareUpdateExclusiveLock
 		},
 		true
@@ -246,7 +246,7 @@ static relopt_int intRelOpts[] =
 		{
 			"autovacuum_analyze_threshold",
 			"Minimum number of tuple inserts, updates or deletes prior to analyze",
-			RELOPT_KIND_HEAP,
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
 			ShareUpdateExclusiveLock
 		},
 		-1, 0, INT_MAX
@@ -420,7 +420,7 @@ static relopt_real realRelOpts[] =
 		{
 			"autovacuum_analyze_scale_factor",
 			"Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples",
-			RELOPT_KIND_HEAP,
+			RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
 			ShareUpdateExclusiveLock
 		},
 		-1, 0.0, 100.0
@@ -1961,13 +1961,12 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 bytea *
 partitioned_table_reloptions(Datum reloptions, bool validate)
 {
+
 	/*
-	 * There are no options for partitioned tables yet, but this is able to do
-	 * some validation.
+	 * autovacuum_enabled, autovacuum_analyze_threshold and
+	 * autovacuum_analyze_scale_factor are supported for partitioned tables.
 	 */
-	return (bytea *) build_reloptions(reloptions, validate,
-									  RELOPT_KIND_PARTITIONED,
-									  0, NULL, 0);
+	return default_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED);
 }
 
 /*
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5314e9348f..78cc3a0e84 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -585,7 +585,7 @@ CREATE VIEW pg_stat_all_tables AS
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't', 'm')
+    WHERE C.relkind IN ('r', 't', 'm', 'p')
     GROUP BY C.oid, N.nspname, C.relname;
 
 CREATE VIEW pg_stat_xact_all_tables AS
@@ -605,7 +605,7 @@ CREATE VIEW pg_stat_xact_all_tables AS
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't', 'm')
+    WHERE C.relkind IN ('r', 't', 'm', 'p')
     GROUP BY C.oid, N.nspname, C.relname;
 
 CREATE VIEW pg_stat_sys_tables AS
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 924ef37c81..de7f1c3bb1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -655,13 +655,11 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 	}
 
 	/*
-	 * Report ANALYZE to the stats collector, too.  However, if doing
-	 * inherited stats we shouldn't report, because the stats collector only
-	 * tracks per-table stats.  Reset the changes_since_analyze counter only
-	 * if we analyzed all columns; otherwise, there is still work for
-	 * auto-analyze to do.
+	 * Report ANALYZE to the stats collector, too.  Reset the
+	 * changes_since_analyze counter only if we analyzed all columns;
+	 * otherwise, there is still work for auto-analyze to do.
 	 */
-	if (!inh)
+	if (!inh || onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		pgstat_report_analyze(onerel, totalrows, totaldeadrows,
 							  (va_cols == NIL));
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d32de23e62..0c9319467f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -829,6 +829,7 @@ expand_vacuum_rel(VacuumRelation *vrel, int options)
 				vacrels = lappend(vacrels, makeVacuumRelation(NULL,
 															  part_oid,
 															  vrel->va_cols));
+
 				MemoryContextSwitchTo(oldcontext);
 			}
 		}
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 9c7d4b0c60..41ef280646 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -75,6 +75,7 @@
 #include "catalog/dependency.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_inherits.h"
 #include "commands/dbcommands.h"
 #include "commands/vacuum.h"
 #include "lib/ilist.h"
@@ -2032,11 +2033,11 @@ do_autovacuum(void)
 	 * Scan pg_class to determine which tables to vacuum.
 	 *
 	 * We do this in two passes: on the first one we collect the list of plain
-	 * relations and materialized views, and on the second one we collect
-	 * TOAST tables. The reason for doing the second pass is that during it we
-	 * want to use the main relation's pg_class.reloptions entry if the TOAST
-	 * table does not have any, and we cannot obtain it unless we know
-	 * beforehand what's the main table OID.
+	 * relations, materialized views and partitioned tables, and on the second
+	 * one we collect TOAST tables. The reason for doing the second pass is that
+	 * during it we want to use the main relation's pg_class.reloptions entry
+	 * if the TOAST table does not have any, and we cannot obtain it unless we
+	 * know beforehand what's the main table OID.
 	 *
 	 * We need to check TOAST tables separately because in cases with short,
 	 * wide tables there might be proportionally much more activity in the
@@ -2059,7 +2060,8 @@ do_autovacuum(void)
 		bool		wraparound;
 
 		if (classForm->relkind != RELKIND_RELATION &&
-			classForm->relkind != RELKIND_MATVIEW)
+			classForm->relkind != RELKIND_MATVIEW &&
+			classForm->relkind != RELKIND_PARTITIONED_TABLE)
 			continue;
 
 		relid = classForm->oid;
@@ -2722,6 +2724,7 @@ extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc)
 
 	Assert(((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_RELATION ||
 		   ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW ||
+		   ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_PARTITIONED_TABLE ||
 		   ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE);
 
 	relopts = extractRelOptions(tup, pg_class_desc, NULL);
@@ -2996,6 +2999,38 @@ relation_needs_vacanalyze(Oid relid,
 	AssertArg(OidIsValid(relid));
 
 	/*
+	 * If the relation is a partitioned table, we must add up childrens'
+	 * reltuples.
+	 */
+	if (classForm->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		List     *children;
+		ListCell *lc;
+
+		reltuples = 0;
+
+		/* Find all members of inheritance set taking AccessShareLock */
+		children = find_all_inheritors(relid, AccessShareLock, NULL);
+
+		foreach(lc, children)
+		{
+			Oid        childOID = lfirst_oid(lc);
+			HeapTuple  childtuple;
+			Form_pg_class childclass;
+
+			childtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(childOID));
+			childclass = (Form_pg_class) GETSTRUCT(childtuple);
+
+			/* Skip foreign partitions */
+			if (childclass->relkind == RELKIND_FOREIGN_TABLE)
+				continue;
+
+			/* Sum up the child's reltuples for its parent table */
+			reltuples += childclass->reltuples;
+		}
+	}
+
+	/*
 	 * Determine vacuum/analyze equation parameters.  We have two possible
 	 * sources: the passed reloptions (which could be a main table or a toast
 	 * table), or the autovacuum GUC variables.
@@ -3071,7 +3106,8 @@ relation_needs_vacanalyze(Oid relid,
 	 */
 	if (PointerIsValid(tabentry) && AutoVacuumingActive())
 	{
-		reltuples = classForm->reltuples;
+		if (classForm->relkind != RELKIND_PARTITIONED_TABLE)
+			reltuples = classForm->reltuples;
 		vactuples = tabentry->n_dead_tuples;
 		instuples = tabentry->inserts_since_vacuum;
 		anltuples = tabentry->changes_since_analyze;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index c022597bc0..116d24facb 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -38,6 +38,7 @@
 #include "access/transam.h"
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
+#include "catalog/partition.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_proc.h"
 #include "common/ip.h"
@@ -1529,8 +1530,12 @@ pgstat_report_analyze(Relation rel,
 	msg.m_autovacuum = IsAutoVacuumWorkerProcess();
 	msg.m_resetcounter = resetcounter;
 	msg.m_analyzetime = GetCurrentTimestamp();
-	msg.m_live_tuples = livetuples;
-	msg.m_dead_tuples = deadtuples;
+	msg.m_live_tuples = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		? 0  /* partitioned tables don't have any data, so it's 0 */
+		: livetuples;
+	msg.m_dead_tuples = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		? 0 /* partitioned tables don't have any data, so it's 0 */
+		: deadtuples;
 	pgstat_send(&msg, sizeof(msg));
 }
 
@@ -1807,7 +1812,8 @@ pgstat_initstats(Relation rel)
 	char		relkind = rel->rd_rel->relkind;
 
 	/* We only count stats for things that have storage */
-	if (!RELKIND_HAS_STORAGE(relkind))
+	if (!RELKIND_HAS_STORAGE(relkind) ||
+		relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		rel->pgstat_info = NULL;
 		return;
@@ -2003,6 +2009,28 @@ pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
 		/* We have to log the effect at the proper transactional level */
 		int			nest_level = GetCurrentTransactionNestLevel();
 
+		/*
+		 * If this relation is partitioned, we store all ancestors' oid
+		 * to propagate its changed_tuples to their parents when this
+		 * transaction is committed.
+		 */
+		if (rel->rd_rel->relispartition && pgstat_info->ancestors == NULL)
+		{
+			List      *ancestors;
+			ListCell  *lc;
+			int       i = 0;
+
+			ancestors = get_partition_ancestors(rel->rd_rel->oid);
+			pgstat_info->ancestors =
+				(Oid *) MemoryContextAllocZero(TopTransactionContext,
+											   sizeof(Oid) * (ancestors->length + 1));
+			foreach(lc, ancestors)
+			{
+				pgstat_info->ancestors[i] = lfirst_oid(lc);
+				++i;
+			}
+		}
+
 		if (pgstat_info->trans == NULL ||
 			pgstat_info->trans->nest_level != nest_level)
 			add_tabstat_xact_level(pgstat_info, nest_level);
@@ -2024,6 +2052,28 @@ pgstat_count_heap_update(Relation rel, bool hot)
 		/* We have to log the effect at the proper transactional level */
 		int			nest_level = GetCurrentTransactionNestLevel();
 
+		/*
+		 * If this relation is partitioned, we store all ancestors' oid
+		 * to propagate its changed_tuples to their parents when this
+		 * transaction is committed.
+		 */
+		if (rel->rd_rel->relispartition && pgstat_info->ancestors == NULL)
+		{
+			List      *ancestors;
+			ListCell  *lc;
+			int       i = 0;
+
+			ancestors = get_partition_ancestors(rel->rd_rel->oid);
+			pgstat_info->ancestors =
+				(Oid *) MemoryContextAllocZero(TopTransactionContext,
+											   sizeof(Oid) * (ancestors->length + 1));
+			foreach(lc, ancestors)
+			{
+				pgstat_info->ancestors[i] = lfirst_oid(lc);
+				++i;
+			}
+		}
+
 		if (pgstat_info->trans == NULL ||
 			pgstat_info->trans->nest_level != nest_level)
 			add_tabstat_xact_level(pgstat_info, nest_level);
@@ -2049,6 +2099,28 @@ pgstat_count_heap_delete(Relation rel)
 		/* We have to log the effect at the proper transactional level */
 		int			nest_level = GetCurrentTransactionNestLevel();
 
+		/*
+		 * If this relation is partitioned, we store all ancestors' oid
+		 * to propagate its changed_tuples to their parents when this
+		 * transaction is committed.
+		 */
+		if (rel->rd_rel->relispartition && pgstat_info->ancestors == NULL)
+		{
+			List      *ancestors;
+			ListCell  *lc;
+			int       i = 0;
+
+			ancestors = get_partition_ancestors(rel->rd_rel->oid);
+			pgstat_info->ancestors =
+				(Oid *) MemoryContextAllocZero(TopTransactionContext,
+											   sizeof(Oid) * (ancestors->length + 1));
+			foreach(lc, ancestors)
+			{
+				pgstat_info->ancestors[i] = lfirst_oid(lc);
+				++i;
+			}
+		}
+
 		if (pgstat_info->trans == NULL ||
 			pgstat_info->trans->nest_level != nest_level)
 			add_tabstat_xact_level(pgstat_info, nest_level);
@@ -2203,6 +2275,29 @@ AtEOXact_PgStat(bool isCommit, bool parallel)
 				tabstat->t_counts.t_changed_tuples +=
 					trans->tuples_inserted + trans->tuples_updated +
 					trans->tuples_deleted;
+
+				/*
+				 * If this relation is partitioned, propagate its own
+				 * changed_tuples to their all ancestors.
+				 */
+				if (tabstat->ancestors != NULL)
+				{
+					int i = 0;
+
+					for(;;)
+					{
+						PgStat_TableStatus *entry;
+						Oid                relid = tabstat->ancestors[i];
+
+						if(relid == InvalidOid)
+							break;
+
+						entry = get_tabstat_entry(relid, false);
+						entry->t_counts.t_changed_tuples +=
+							tabstat->t_counts.t_changed_tuples;
+						++i;
+					}
+				}
 			}
 			else
 			{
@@ -6354,7 +6449,6 @@ pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len)
 	}
 }
 
-
 /* ----------
  * pgstat_recv_archiver() -
  *
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 1387201382..239e7e688a 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -156,6 +156,7 @@ typedef enum PgStat_Single_Reset_Type
 typedef struct PgStat_TableStatus
 {
 	Oid			t_id;			/* table's OID */
+	Oid        *ancestors;      /* all ancestors */
 	bool		t_shared;		/* is it a shared catalog? */
 	struct PgStat_TableXactStatus *trans;	/* lowest subxact's counts */
 	PgStat_TableCounts t_counts;	/* event counts to be sent */
@@ -403,7 +404,6 @@ typedef struct PgStat_MsgAnalyze
 	PgStat_Counter m_dead_tuples;
 } PgStat_MsgAnalyze;
 
-
 /* ----------
  * PgStat_MsgArchiver			Sent by the archiver to update statistics.
  * ----------
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b813e32215..8c31310f9c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1792,7 +1792,7 @@ pg_stat_all_tables| SELECT c.oid AS relid,
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
-  WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]))
+  WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
 pg_stat_archiver| SELECT s.archived_count,
     s.last_archived_wal,
@@ -2151,7 +2151,7 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
-  WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]))
+  WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char", 'p'::"char"]))
   GROUP BY c.oid, n.nspname, c.relname;
 pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
     pg_stat_xact_all_tables.schemaname,
