diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 559eb898a9..bea1d4fc1a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8880,6 +8880,51 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-default-transaction-nesting" xreflabel="default_transaction_nesting">
+      <term><varname>nested_transactions</varname> (<type>enum</type>)
+      <indexterm>
+       <primary>nested transactions</primary>
+      </indexterm>
+      <indexterm>
+       <primary><varname>nested_transactions</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        This parameter controls the behavior of transactions when
+        <command>BEGIN</command>, <command>COMMIT</command> or
+        <command>ROLLBACK</command> are received when already in a transaction
+        block.
+       </para>
+       <para>
+        The default is <quote>off</quote>, indicating
+        that when a transaction block is already in progress, a
+        <command>BEGIN</command> will throw a <literal>WARNING</literal>
+        but otherwise do nothing. This is the historic behavior of
+        <productname>PostgreSQL</productname>.
+       </para>
+       <para>
+        A setting of <quote>all</quote> will cause a nested
+        <command>BEGIN</command> to start a subtransaction, which will end when
+        a <command>COMMIT</command> or <command>ROLLBACK</command> is
+        received. In that case a <command>ROLLBACK</command> will only
+        abort the subtransaction. Once we reach the top-level transaction,
+        the final <command>COMMIT</command> will end the transction.
+        This ensures that commands at each transaction nesting level are atomic.
+       </para>
+       <para>
+        A setting of <quote>outer</quote> will cause a nested
+        <command>BEGIN</command> to be remembered, so that an equal number
+        of <command>COMMIT</command> or <command>ROLLBACK</command> commands
+        are required to end the nesting. In that case a <command>ROLLBACK</command>
+        at any level will be abort the entire outer transaction.
+        Once we reach the top-level transaction,
+        the final <command>COMMIT</command> will end the transction.
+        This ensures that all commands within the outer transaction are atomic.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-transaction-isolation" xreflabel="transaction_isolation">
       <term><varname>transaction_isolation</varname> (<type>enum</type>)
       <indexterm>
diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 72af656060..50ccb0daca 100644
--- a/src/backend/access/transam/README
+++ b/src/backend/access/transam/README
@@ -894,3 +894,79 @@ yet simplifies emulation of subtransactions considerably.
 
 Further details on locking mechanics in recovery are given in comments
 with the Lock rmgr code.
+
+Nested Transactions
+-------------------
+
+Nested BEGIN/COMMIT statements can be confusing for developers. In this
+example, the first BEGIN and the first COMMIT match, leaving the commands
+between the first and second COMMIT outside of a transaction block,
+allowing them to commit separately from each other.  The second BEGIN and
+second COMMIT throw WARNING messages but if an ERROR occurs, the result
+is not atomic and can be hard to manually undo the damage.
+
+BEGIN
+INSERT
+BEGIN
+WARNING:  there is already a transaction in progress
+INSERT
+COMMIT
+INSERT
+ERROR:  syntax error
+COMMIT
+WARNING:  there is no transaction in progress
+SELECT
+val
+---
+1
+2
+(2 rows)
+
+Using SET nested_transactions = 'outer', we get simpler and more understandable
+behavior, with all commands now within the outer transaction block.
+In this example, the ERROR causes rollback of all commands because nested
+begin and commit commands are ignored, while we track the nesting level.
+
+BEGIN
+INSERT
+BEGIN
+NOTICE:  nested BEGIN, level 1
+INSERT
+COMMIT
+NOTICE:  nested COMMIT, level 1
+INSERT
+ERROR:  syntax error
+ROLLBACK
+SELECT
+val
+--
+(0 rows)
+
+Notices are shown, so that developers can be certain of the behavior.
+
+Nested COMMIT/ROLLBACK affects all subtransactions under the transaction
+that issued BEGIN, irrespective of subsequent SAVEPOINT/ROLLBACK/RELEASE.
+Nesting can occur to any level.
+
+An alternative approach is to use SET nested_transactions = 'all',
+which changes nested BEGINs into subtransactions and COMMITs into subcommits.
+This allows us to trap nested errors and yet continue the main transaction.
+In this example, a nested ERROR aborts the nested subtransaction, but not
+the main transaction.
+
+BEGIN
+INSERT
+BEGIN
+NOTICE:  BEGIN starts nested subtransaction, level 1
+INSERT
+ERROR:  syntax error
+COMMIT
+NOTICE:  COMMIT will rollback nested subtransaction, level 1
+INSERT
+ROLLBACK
+SELECT
+val
+--
+(0 rows)
+
+The default behavior is equivalent to using SET nested_transactions = 'off'.
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index fd5103a78e..7c049b49f5 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -83,6 +83,12 @@ bool		XactReadOnly;
 bool		DefaultXactDeferrable = false;
 bool		XactDeferrable;
 
+int			DefaultXactNesting;
+int			XactNesting = XACT_NEST_OFF;
+int			XactNestingLevel = 0;
+
+#define	NESTED_XACT_NAME	"_internal_nested_xact"
+
 int			synchronous_commit = SYNCHRONOUS_COMMIT_ON;
 
 /*
@@ -2049,6 +2055,8 @@ StartTransaction(void)
 	}
 	XactDeferrable = DefaultXactDeferrable;
 	XactIsoLevel = DefaultXactIsoLevel;
+	XactNesting = DefaultXactNesting;
+	XactNestingLevel = 0;
 	forceSyncCommit = false;
 	MyXactFlags = 0;
 
@@ -2992,7 +3000,7 @@ StartTransactionCommand(void)
 
 /*
  * Simple system for saving and restoring transaction characteristics
- * (isolation level, read only, deferrable).  We need this for transaction
+ * (isolation level, read only, deferrable, nesting).  We need this for transaction
  * chaining, so that we can set the characteristics of the new transaction to
  * be the same as the previous one.  (We need something like this because the
  * GUC system resets the characteristics at transaction end, so for example
@@ -3004,6 +3012,7 @@ SaveTransactionCharacteristics(SavedTransactionCharacteristics *s)
 	s->save_XactIsoLevel = XactIsoLevel;
 	s->save_XactReadOnly = XactReadOnly;
 	s->save_XactDeferrable = XactDeferrable;
+	s->save_XactNesting = XactNesting;
 }
 
 void
@@ -3012,6 +3021,7 @@ RestoreTransactionCharacteristics(const SavedTransactionCharacteristics *s)
 	XactIsoLevel = s->save_XactIsoLevel;
 	XactReadOnly = s->save_XactReadOnly;
 	XactDeferrable = s->save_XactDeferrable;
+	XactNesting = s->save_XactNesting;
 }
 
 
@@ -3182,7 +3192,10 @@ CommitTransactionCommand(void)
 				CommitSubTransaction();
 				s = CurrentTransactionState;	/* changed by pop */
 			} while (s->blockState == TBLOCK_SUBCOMMIT);
-			/* If we had a COMMIT command, finish off the main xact too */
+			/*
+			 * If we had a COMMIT command, finish off the main xact too,
+			 * unless this is a nested transaction.
+			 */
 			if (s->blockState == TBLOCK_END)
 			{
 				Assert(s->parent == NULL);
@@ -3202,7 +3215,7 @@ CommitTransactionCommand(void)
 				PrepareTransaction();
 				s->blockState = TBLOCK_DEFAULT;
 			}
-			else
+			else if (XactNesting == XACT_NEST_OFF)
 				elog(ERROR, "CommitTransactionCommand: unexpected state %s",
 					 BlockStateAsString(s->blockState));
 			break;
@@ -3765,13 +3778,37 @@ BeginTransactionBlock(void)
 			 * Already a transaction block in progress.
 			 */
 		case TBLOCK_INPROGRESS:
-		case TBLOCK_PARALLEL_INPROGRESS:
 		case TBLOCK_SUBINPROGRESS:
+			if (XactNesting == XACT_NEST_ALL)
+			{
+				/*
+				 * BEGIN starts a nested subtransaction.
+				 */
+				XactNestingLevel++;
+				ereport(NOTICE,
+						(errmsg("BEGIN starts nested subtransaction, level %u", XactNestingLevel)));
+				BeginInternalSubTransaction(NESTED_XACT_NAME);
+				break;
+			}
+			else if (XactNesting == XACT_NEST_OUTER)
+			{
+				XactNestingLevel++;
+				ereport(NOTICE,
+							(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
+							 errmsg("nested BEGIN, level %u", XactNestingLevel)));
+				break;
+			}
+			/* else drop thru */
+
+		case TBLOCK_PARALLEL_INPROGRESS:
 		case TBLOCK_ABORT:
 		case TBLOCK_SUBABORT:
 			ereport(WARNING,
 					(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
 					 errmsg("there is already a transaction in progress")));
+			if (XactNesting == XACT_NEST_OUTER)
+				XactNestingLevel++;
+
 			break;
 
 			/* These cases are invalid. */
@@ -3815,6 +3852,10 @@ PrepareTransactionBlock(const char *gid)
 	/* Set up to commit the current transaction */
 	result = EndTransactionBlock(false);
 
+	/* Don't allow prepare until we are back to an unnested state at level 0 */
+	if (XactNestingLevel > 0)
+		return false;
+
 	/* If successful, change outer tblock state to PREPARE */
 	if (result)
 	{
@@ -3863,6 +3904,7 @@ EndTransactionBlock(bool chain)
 {
 	TransactionState s = CurrentTransactionState;
 	bool		result = false;
+	bool found_subxact = false;
 
 	switch (s->blockState)
 	{
@@ -3871,7 +3913,19 @@ EndTransactionBlock(bool chain)
 			 * to COMMIT.
 			 */
 		case TBLOCK_INPROGRESS:
-			s->blockState = TBLOCK_END;
+			if (XactNesting == XACT_NEST_OUTER)
+			{
+				if (XactNestingLevel <= 0)
+					s->blockState = TBLOCK_END;
+				else
+					ereport(NOTICE,
+							(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
+							 errmsg("nested COMMIT, level %u", XactNestingLevel)));
+				XactNestingLevel--;
+				return true;
+			}
+			else
+				s->blockState = TBLOCK_END;
 			result = true;
 			break;
 
@@ -3900,6 +3954,16 @@ EndTransactionBlock(bool chain)
 			 * CommitTransactionCommand it's time to exit the block.
 			 */
 		case TBLOCK_ABORT:
+			if (XactNesting == XACT_NEST_OUTER)
+			{
+				if (XactNestingLevel > 0)
+				{
+					ereport(NOTICE,
+							(errmsg("nested COMMIT, level %u in aborted transaction", XactNestingLevel)));
+					XactNestingLevel--;
+					return false;
+				}
+			}
 			s->blockState = TBLOCK_ABORT_END;
 			break;
 
@@ -3908,8 +3972,14 @@ EndTransactionBlock(bool chain)
 			 * open subtransactions and then commit the main transaction.
 			 */
 		case TBLOCK_SUBINPROGRESS:
-			while (s->parent != NULL)
+			while (s->parent != NULL && !found_subxact)
 			{
+				if (XactNesting == XACT_NEST_ALL &&
+					XactNestingLevel > 0 &&
+					PointerIsValid(s->name) &&
+					strcmp(s->name, NESTED_XACT_NAME) == 0)
+					found_subxact = true;
+
 				if (s->blockState == TBLOCK_SUBINPROGRESS)
 					s->blockState = TBLOCK_SUBCOMMIT;
 				else
@@ -3917,7 +3987,26 @@ EndTransactionBlock(bool chain)
 						 BlockStateAsString(s->blockState));
 				s = s->parent;
 			}
-			if (s->blockState == TBLOCK_INPROGRESS)
+			if (XactNesting == XACT_NEST_ALL && XactNestingLevel > 0)
+			{
+				if (s->parent == NULL && !found_subxact)
+					ereport(ERROR,
+							(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+							 errmsg("nested transaction does not exist")));
+
+				/*
+				 * With transaction nesting, the COMMIT command no
+				 * longer forces main transaction abort, only the
+				 * subcommit of subxacts up to the last nested BEGIN.
+				 * We only mark state here; CommitTransactionCommand()
+				 * will release portals and resources.
+				 */
+				ereport(NOTICE,
+						(errmsg("COMMIT will commit nested subtransaction, level %u", XactNestingLevel)));
+				XactNestingLevel--;
+				return true;
+			}
+			else if (s->blockState == TBLOCK_INPROGRESS)
 				s->blockState = TBLOCK_END;
 			else
 				elog(FATAL, "EndTransactionBlock: unexpected state %s",
@@ -3931,8 +4020,14 @@ EndTransactionBlock(bool chain)
 			 * transaction.
 			 */
 		case TBLOCK_SUBABORT:
-			while (s->parent != NULL)
+			while (s->parent != NULL && !found_subxact)
 			{
+				if (XactNesting == XACT_NEST_ALL &&
+					XactNestingLevel > 0 &&
+					PointerIsValid(s->name) &&
+					strcmp(s->name, NESTED_XACT_NAME) == 0)
+					found_subxact = true;
+
 				if (s->blockState == TBLOCK_SUBINPROGRESS)
 					s->blockState = TBLOCK_SUBABORT_PENDING;
 				else if (s->blockState == TBLOCK_SUBABORT)
@@ -3942,6 +4037,18 @@ EndTransactionBlock(bool chain)
 						 BlockStateAsString(s->blockState));
 				s = s->parent;
 			}
+			if (XactNesting == XACT_NEST_ALL && XactNestingLevel > 0)
+			{
+				if (!found_subxact)
+					ereport(ERROR,
+							(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+							 errmsg("nested transaction does not exist")));
+
+				ereport(NOTICE,
+						(errmsg("COMMIT will rollback nested subtransaction, level %u", XactNestingLevel)));
+				XactNestingLevel--;
+				return false;
+			}
 			if (s->blockState == TBLOCK_INPROGRESS)
 				s->blockState = TBLOCK_ABORT_PENDING;
 			else if (s->blockState == TBLOCK_ABORT)
@@ -4022,6 +4129,7 @@ void
 UserAbortTransactionBlock(bool chain)
 {
 	TransactionState s = CurrentTransactionState;
+	bool found_subxact = false;
 
 	switch (s->blockState)
 	{
@@ -4031,7 +4139,14 @@ UserAbortTransactionBlock(bool chain)
 			 * exit the transaction block.
 			 */
 		case TBLOCK_INPROGRESS:
-			s->blockState = TBLOCK_ABORT_PENDING;
+			if (XactNesting == XACT_NEST_OUTER && XactNestingLevel > 0)
+			{
+				/* Throw ERROR */
+				ereport(ERROR,
+						(errmsg("nested ROLLBACK, level %u aborts outer transaction", XactNestingLevel--)));
+			}
+			else
+				s->blockState = TBLOCK_ABORT_PENDING;
 			break;
 
 			/*
@@ -4041,7 +4156,19 @@ UserAbortTransactionBlock(bool chain)
 			 * idle state.
 			 */
 		case TBLOCK_ABORT:
-			s->blockState = TBLOCK_ABORT_END;
+			if (XactNesting == XACT_NEST_OUTER)
+			{
+				if (XactNestingLevel <= 0)
+					s->blockState = TBLOCK_ABORT_END;
+				else
+				{
+					ereport(NOTICE,
+							(errmsg("nested ROLLBACK, level %u in aborted transaction", XactNestingLevel--)));
+				}
+				return;
+			}
+			else
+				s->blockState = TBLOCK_ABORT_END;
 			break;
 
 			/*
@@ -4050,8 +4177,14 @@ UserAbortTransactionBlock(bool chain)
 			 */
 		case TBLOCK_SUBINPROGRESS:
 		case TBLOCK_SUBABORT:
-			while (s->parent != NULL)
+			while (s->parent != NULL && !found_subxact)
 			{
+				if (XactNesting == XACT_NEST_ALL &&
+					XactNestingLevel > 0 &&
+					PointerIsValid(s->name) &&
+					strcmp(s->name, NESTED_XACT_NAME) == 0)
+					found_subxact = true;
+
 				if (s->blockState == TBLOCK_SUBINPROGRESS)
 					s->blockState = TBLOCK_SUBABORT_PENDING;
 				else if (s->blockState == TBLOCK_SUBABORT)
@@ -4061,6 +4194,18 @@ UserAbortTransactionBlock(bool chain)
 						 BlockStateAsString(s->blockState));
 				s = s->parent;
 			}
+			if (XactNesting == XACT_NEST_ALL && XactNestingLevel > 0)
+			{
+				if (!found_subxact)
+					ereport(ERROR,
+							(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+							 errmsg("nested transaction does not exist")));
+
+				ereport(NOTICE,
+						(errmsg("ROLLBACK will rollback nested subtransaction, level %u", XactNestingLevel)));
+				XactNestingLevel--;
+				return;
+			}
 			if (s->blockState == TBLOCK_INPROGRESS)
 				s->blockState = TBLOCK_ABORT_PENDING;
 			else if (s->blockState == TBLOCK_ABORT)
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 05ab087934..1ddf1d9792 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -195,6 +195,13 @@ static const struct config_enum_entry isolation_level_options[] = {
 	{NULL, 0}
 };
 
+static const struct config_enum_entry xact_nesting_options[] = {
+	{"off", XACT_NEST_OFF, false},
+	{"all", XACT_NEST_ALL, false},
+	{"outer", XACT_NEST_OUTER, false},
+	{NULL, 0, false}
+};
+
 static const struct config_enum_entry session_replication_role_options[] = {
 	{"origin", SESSION_REPLICATION_ROLE_ORIGIN, false},
 	{"replica", SESSION_REPLICATION_ROLE_REPLICA, false},
@@ -4533,6 +4540,16 @@ struct config_enum ConfigureNamesEnum[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"nested_transactions", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the transaction nesting behavior for each new transaction."),
+			NULL
+		},
+		&DefaultXactNesting,
+		XACT_NEST_OFF, xact_nesting_options,
+		NULL, NULL, NULL
+	},
+
 	{
 		{"default_transaction_isolation", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the transaction isolation level of each new transaction."),
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index c604ee11f8..1cdc697fe1 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -41,6 +41,15 @@
 extern PGDLLIMPORT int DefaultXactIsoLevel;
 extern PGDLLIMPORT int XactIsoLevel;
 
+/*
+ * Xact nesting
+ */
+#define XACT_NEST_OFF	0
+#define XACT_NEST_ALL	1
+#define XACT_NEST_OUTER	2
+
+extern PGDLLIMPORT int DefaultXactNesting;
+
 /*
  * We implement three isolation levels internally.
  * The two stronger ones use one snapshot per database transaction;
@@ -147,6 +156,7 @@ typedef struct SavedTransactionCharacteristics
 	int			save_XactIsoLevel;
 	bool		save_XactReadOnly;
 	bool		save_XactDeferrable;
+	int			save_XactNesting;
 } SavedTransactionCharacteristics;
 
 
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 2b2cff7d91..cf153a3393 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -1147,6 +1147,229 @@ SELECT * FROM abc ORDER BY 1;
  17
 (3 rows)
 
+SET nested_transactions = 'off';
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+WARNING:  there is already a transaction in progress
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+WARNING:  there is no transaction in progress
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+WARNING:  there is already a transaction in progress
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+INSERT INTO abc VALUES (3);
+COMMIT;
+WARNING:  there is no transaction in progress
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+ 3
+(1 row)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+WARNING:  there is already a transaction in progress
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3))); --error
+ERROR:  syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (3)));
+                                  ^
+COMMIT;
+WARNING:  there is no transaction in progress
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+ 1
+ 2
+(2 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+WARNING:  there is already a transaction in progress
+INSERT INTO abc VALUES (2))); --error
+ERROR:  syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (2)));
+                                  ^
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+WARNING:  there is no transaction in progress
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+ 3
+(1 row)
+
+SET nested_transactions = 'all';
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE:  BEGIN starts nested subtransaction, level 1
+INSERT INTO abc VALUES (2);
+COMMIT;
+NOTICE:  COMMIT will commit nested subtransaction, level 1
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE:  BEGIN starts nested subtransaction, level 1
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+NOTICE:  ROLLBACK will rollback nested subtransaction, level 1
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+ 1
+ 3
+(2 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE:  BEGIN starts nested subtransaction, level 1
+INSERT INTO abc VALUES (2);
+COMMIT;
+NOTICE:  COMMIT will commit nested subtransaction, level 1
+INSERT INTO abc VALUES (3))); --error
+ERROR:  syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (3)));
+                                  ^
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+(0 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE:  BEGIN starts nested subtransaction, level 1
+INSERT INTO abc VALUES (2))); --error
+ERROR:  syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (2)));
+                                  ^
+COMMIT;
+NOTICE:  COMMIT will rollback nested subtransaction, level 1
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+ 1
+ 3
+(2 rows)
+
+SET nested_transactions = 'outer';
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE:  nested BEGIN, level 1
+INSERT INTO abc VALUES (2);
+COMMIT;
+NOTICE:  nested COMMIT, level 1
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE:  nested BEGIN, level 1
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+ERROR:  nested ROLLBACK, level 1 aborts outer transaction
+INSERT INTO abc VALUES (3);
+ERROR:  current transaction is aborted, commands ignored until end of transaction block
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+(0 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE:  nested BEGIN, level 1
+INSERT INTO abc VALUES (2);
+COMMIT;
+NOTICE:  nested COMMIT, level 1
+INSERT INTO abc VALUES (3))); --error
+ERROR:  syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (3)));
+                                  ^
+INSERT INTO abc VALUES (4);
+ERROR:  current transaction is aborted, commands ignored until end of transaction block
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+(0 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE:  nested BEGIN, level 1
+INSERT INTO abc VALUES (2))); --error
+ERROR:  syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (2)));
+                                  ^
+COMMIT;
+NOTICE:  nested COMMIT, level 1 in aborted transaction
+INSERT INTO abc VALUES (3);
+ERROR:  current transaction is aborted, commands ignored until end of transaction block
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+(0 rows)
+
+RESET nested_transactions;
 DROP TABLE abc;
 -- Test for successful cleanup of an aborted transaction at session exit.
 -- THIS MUST BE THE LAST TEST IN THIS FILE.
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 7ee5f6aaa5..4df2d4d382 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -611,8 +611,136 @@ RESET default_transaction_isolation;
 
 SELECT * FROM abc ORDER BY 1;
 
-DROP TABLE abc;
+SET nested_transactions = 'off';
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3))); --error
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
 
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2))); --error
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+SET nested_transactions = 'all';
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3))); --error
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2))); --error
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+SET nested_transactions = 'outer';
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3))); --error
+INSERT INTO abc VALUES (4);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2))); --error
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+RESET nested_transactions;
+
+DROP TABLE abc;
 
 -- Test for successful cleanup of an aborted transaction at session exit.
 -- THIS MUST BE THE LAST TEST IN THIS FILE.
