diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 18948c8..ef28d91 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -586,11 +586,11 @@ do_compile(FunctionCallInfo fcinfo,
 				  errmsg("trigger functions cannot have declared arguments"),
 						 errhint("The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead.")));
 
-			/* Add the record for referencing NEW */
+			/* Add the record for referencing NEW ROW */
 			rec = plpgsql_build_record("new", 0, true);
 			function->new_varno = rec->dno;
 
-			/* Add the record for referencing OLD */
+			/* Add the record for referencing OLD ROW */
 			rec = plpgsql_build_record("old", 0, true);
 			function->old_varno = rec->dno;
 
@@ -2450,15 +2450,16 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
 	hashkey->isTrigger = CALLED_AS_TRIGGER(fcinfo);
 
 	/*
-	 * if trigger, get relation OID.  In validation mode we do not know what
-	 * relation is intended to be used, so we leave trigrelOid zero; the hash
-	 * entry built in this case will never really be used.
+	 * if trigger, get its OID.  In validation mode we do not know what
+	 * relation or transition table names are intended to be used, so we leave
+	 * trigOid zero; the hash entry built in this case will never really be
+	 * used.
 	 */
 	if (hashkey->isTrigger && !forValidator)
 	{
 		TriggerData *trigdata = (TriggerData *) fcinfo->context;
 
-		hashkey->trigrelOid = RelationGetRelid(trigdata->tg_relation);
+		hashkey->trigOid = trigdata->tg_trigger->tgoid;
 	}
 
 	/* get input collation, if known */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 77e7440..44f7370 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -688,6 +688,32 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 	else
 		elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
 
+	/* Make old and new tables available to queries. */
+	if (trigdata->tg_newtable)
+	{
+		rc = SPI_register_relation(trigdata->tg_trigger->tgnewtable,
+								   trigdata->tg_relation->rd_att,
+								   100,
+								   trigdata->tg_newtable);
+		if (rc != SPI_OK_REGISTER)
+			elog(ERROR,
+				 "could not register relation \"%s\": %s",
+				 trigdata->tg_trigger->tgnewtable,
+				 SPI_result_code_string(rc));
+	}
+	if (trigdata->tg_oldtable)
+	{
+		rc = SPI_register_relation(trigdata->tg_trigger->tgoldtable,
+								   trigdata->tg_relation->rd_att,
+								   100,
+								   trigdata->tg_oldtable);
+		if (rc != SPI_OK_REGISTER)
+			elog(ERROR,
+				 "could not register relation \"%s\": %s",
+				 trigdata->tg_trigger->tgnewtable,
+				 SPI_result_code_string(rc));
+	}
+
 	/*
 	 * Assign the special tg_ variables
 	 */
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index c84a97b..8a9dcac 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -782,12 +782,12 @@ typedef struct PLpgSQL_func_hashkey
 	/* be careful that pad bytes in this struct get zeroed! */
 
 	/*
-	 * For a trigger function, the OID of the relation triggered on is part of
-	 * the hash key --- we want to compile the trigger separately for each
-	 * relation it is used with, in case the rowtype is different.  Zero if
-	 * not called as a trigger.
+	 * For a trigger function, the OID of the trigger is part of the hash key
+	 * --- we want to compile the trigger function separately for each trigger
+	 * it is used with, in case the rowtype or transition table names are
+	 * different.  Zero if not called as a trigger.
 	 */
-	Oid			trigrelOid;
+	Oid			trigOid;
 
 	/*
 	 * We must include the input collation as part of the hash key too,
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 147fb9f..8bd6a76 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -5662,3 +5662,84 @@ end;
 $$;
 ERROR:  value for domain plpgsql_domain violates check constraint "plpgsql_domain_check"
 CONTEXT:  PL/pgSQL function inline_code_block line 4 at assignment
+--
+-- test usage of transition tables in AFTER triggers
+--
+CREATE TABLE v (id int PRIMARY KEY, val text);
+CREATE OR REPLACE FUNCTION v_ins_func()
+  RETURNS trigger
+  LANGUAGE plpgsql
+AS $$
+DECLARE
+  t text;
+  l text;
+BEGIN
+  t = '';
+  FOR l IN EXECUTE
+           $q$
+             EXPLAIN (TIMING off, COSTS off, VERBOSE on)
+             SELECT * FROM newtable
+           $q$ LOOP
+    t = t || l || E'\n';
+  END LOOP;
+
+  RAISE INFO '%', t;
+  RETURN new;
+END;
+$$;
+CREATE TRIGGER v_ins_trig
+  AFTER INSERT ON v
+  REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable
+  FOR EACH STATEMENT
+  EXECUTE PROCEDURE v_ins_func();
+ERROR:  OLD TABLE can only be specified for a DELETE or UPDATE trigger
+CREATE TRIGGER v_ins_trig
+  AFTER INSERT ON v
+  REFERENCING NEW TABLE AS newtable
+  FOR EACH STATEMENT
+  EXECUTE PROCEDURE v_ins_func();
+INSERT INTO v VALUES (1, 'One'), (2, 'Two');
+INFO:  SPI Relation Scan
+  Output: id, val
+
+INSERT INTO v VALUES (3, 'Three'), (4, 'Four');
+INFO:  SPI Relation Scan
+  Output: id, val
+
+CREATE OR REPLACE FUNCTION v_upd_func()
+  RETURNS trigger
+  LANGUAGE plpgsql
+AS $$
+DECLARE
+  t text;
+  l text;
+BEGIN
+  t = '';
+  FOR l IN EXECUTE
+           $q$
+             EXPLAIN (TIMING off, COSTS off, VERBOSE on)
+             SELECT * FROM oldtable ot FULL JOIN newtable nt USING (id)
+           $q$ LOOP
+    t = t || l || E'\n';
+  END LOOP;
+
+  RAISE INFO '%', t;
+  RETURN new;
+END;
+$$;
+CREATE TRIGGER v_upd_trig
+  AFTER UPDATE ON v
+  REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable
+  FOR EACH STATEMENT
+  EXECUTE PROCEDURE v_upd_func();
+UPDATE v SET val = '*' || val || '*' WHERE id BETWEEN 2 AND 3;
+INFO:  Hash Full Join
+  Output: COALESCE(ot.id, nt.id), ot.val, nt.val
+  Hash Cond: (ot.id = nt.id)
+  ->  SPI Relation Scan
+        Output: ot.id, ot.val
+  ->  Hash
+        Output: nt.id, nt.val
+        ->  SPI Relation Scan
+              Output: nt.id, nt.val
+
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 49223ff..c60ac52 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -4450,3 +4450,76 @@ begin
   v_test := 0;  -- fail
 end;
 $$;
+
+--
+-- test usage of transition tables in AFTER triggers
+--
+
+CREATE TABLE v (id int PRIMARY KEY, val text);
+
+CREATE OR REPLACE FUNCTION v_ins_func()
+  RETURNS trigger
+  LANGUAGE plpgsql
+AS $$
+DECLARE
+  t text;
+  l text;
+BEGIN
+  t = '';
+  FOR l IN EXECUTE
+           $q$
+             EXPLAIN (TIMING off, COSTS off, VERBOSE on)
+             SELECT * FROM newtable
+           $q$ LOOP
+    t = t || l || E'\n';
+  END LOOP;
+
+  RAISE INFO '%', t;
+  RETURN new;
+END;
+$$;
+
+CREATE TRIGGER v_ins_trig
+  AFTER INSERT ON v
+  REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable
+  FOR EACH STATEMENT
+  EXECUTE PROCEDURE v_ins_func();
+
+CREATE TRIGGER v_ins_trig
+  AFTER INSERT ON v
+  REFERENCING NEW TABLE AS newtable
+  FOR EACH STATEMENT
+  EXECUTE PROCEDURE v_ins_func();
+
+INSERT INTO v VALUES (1, 'One'), (2, 'Two');
+INSERT INTO v VALUES (3, 'Three'), (4, 'Four');
+
+CREATE OR REPLACE FUNCTION v_upd_func()
+  RETURNS trigger
+  LANGUAGE plpgsql
+AS $$
+DECLARE
+  t text;
+  l text;
+BEGIN
+  t = '';
+  FOR l IN EXECUTE
+           $q$
+             EXPLAIN (TIMING off, COSTS off, VERBOSE on)
+             SELECT * FROM oldtable ot FULL JOIN newtable nt USING (id)
+           $q$ LOOP
+    t = t || l || E'\n';
+  END LOOP;
+
+  RAISE INFO '%', t;
+  RETURN new;
+END;
+$$;
+
+CREATE TRIGGER v_upd_trig
+  AFTER UPDATE ON v
+  REFERENCING OLD TABLE AS oldtable NEW TABLE AS newtable
+  FOR EACH STATEMENT
+  EXECUTE PROCEDURE v_upd_func();
+
+UPDATE v SET val = '*' || val || '*' WHERE id BETWEEN 2 AND 3;
