EphemeralNamedRelation and materialized view

Started by Yugo Nagataover 1 year ago11 messages
#1Yugo Nagata
nagata@sraoss.co.jp
1 attachment(s)

Hi,

While looking into the commit b4da732fd64e936970f38c792f8b32c4bdf2bcd5,
I noticed that we can create a materialized view using Ephemeral Named
Relation in PostgreSQL 16 or earler.

postgres=# create table tbl (i int);
CREATE TABLE
^
postgres=# create or replace function f() returns trigger as $$ begin
create materialized view mv as select * from enr; return new; end; $$ language plpgsql;
CREATE FUNCTION

postgres=# create trigger trig after insert on tbl referencing new table as enr execute function f();
CREATE TRIGGER

postgres=# insert into tbl values (10);

postgres=# \d
List of relations
Schema | Name | Type | Owner
--------+------+-------------------+--------
public | mv | materialized view | yugo-n
public | tbl | table | yugo-n
(2 rows)

We cannot refresh or get the deinition of it, though.

postgres=# refresh materialized view mv;
ERROR: executor could not find named tuplestore "enr"

postgres=# \d+ mv
ERROR: unrecognized RTE kind: 7

In PostgreSQL 17, materialized view using ENR cannot be created
because queryEnv is not pass to RefreshMatViewByOid introduced by b4da732fd64.
When we try to create it, the error is raised.

ERROR: executor could not find named tuplestore "enr"

Although it is hard to imagine users actually try to create materialized view
using ENR, how about prohibiting it even in PG16 or earlier by passing NULL
as queryEnv arg in CreateQueryDesc to avoid to create useless matviews accidentally,
as the attached patch?

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

prohibit_use_enr_in_matview.patchtext/x-diff; name=prohibit_use_enr_in_matview.patchDownload
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index e91920ca14..fda1e4d92b 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -327,7 +327,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 		/* Create a QueryDesc, redirecting output to our tuple receiver */
 		queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
 									GetActiveSnapshot(), InvalidSnapshot,
-									dest, params, queryEnv, 0);
+									dest, params, is_matview ? NULL : queryEnv, 0);
 
 		/* call ExecutorStart to prepare the plan for execution */
 		ExecutorStart(queryDesc, GetIntoRelEFlags(into));
#2Kirill Reshke
reshkekirill@gmail.com
In reply to: Yugo Nagata (#1)
Re: EphemeralNamedRelation and materialized view

On Fri, 26 Jul 2024 at 12:07, Yugo Nagata <nagata@sraoss.co.jp> wrote:

Hi,

While looking into the commit b4da732fd64e936970f38c792f8b32c4bdf2bcd5,
I noticed that we can create a materialized view using Ephemeral Named
Relation in PostgreSQL 16 or earler.

postgres=# create table tbl (i int);
CREATE TABLE
^
postgres=# create or replace function f() returns trigger as $$ begin
create materialized view mv as select * from enr; return new; end; $$ language plpgsql;
CREATE FUNCTION

postgres=# create trigger trig after insert on tbl referencing new table as enr execute function f();
CREATE TRIGGER

postgres=# insert into tbl values (10);

postgres=# \d
List of relations
Schema | Name | Type | Owner
--------+------+-------------------+--------
public | mv | materialized view | yugo-n
public | tbl | table | yugo-n
(2 rows)

We cannot refresh or get the deinition of it, though.

postgres=# refresh materialized view mv;
ERROR: executor could not find named tuplestore "enr"

postgres=# \d+ mv
ERROR: unrecognized RTE kind: 7

In PostgreSQL 17, materialized view using ENR cannot be created
because queryEnv is not pass to RefreshMatViewByOid introduced by b4da732fd64.
When we try to create it, the error is raised.

ERROR: executor could not find named tuplestore "enr"

Although it is hard to imagine users actually try to create materialized view
using ENR, how about prohibiting it even in PG16 or earlier by passing NULL
as queryEnv arg in CreateQueryDesc to avoid to create useless matviews accidentally,
as the attached patch?

Regards,
Yugo Nagata

--
Yugo Nagata <nagata@sraoss.co.jp>

Hi
I think this is a clear bug fix, and should be backported in pg v12-v16.
LTGM

P.S should be set https://commitfest.postgresql.org/49/5153/ entry as RFC?

--
Best regards,
Kirill Reshke

#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Yugo Nagata (#1)
Re: EphemeralNamedRelation and materialized view

Yugo Nagata <nagata@sraoss.co.jp> writes:

While looking into the commit b4da732fd64e936970f38c792f8b32c4bdf2bcd5,
I noticed that we can create a materialized view using Ephemeral Named
Relation in PostgreSQL 16 or earler.

Yeah, we should reject that, but I feel like this patch is not
ambitious enough, because the 17-and-up behavior isn't exactly
polished either.

I tried variants of this function in HEAD:

1. With "create table mv as select * from enr", it works and
does what you'd expect.

2. With "create view mv as select * from enr", you get

regression=# insert into tbl values (10);
ERROR: relation "enr" does not exist
LINE 1: create view mv as select * from enr
^
QUERY: create view mv as select * from enr
CONTEXT: PL/pgSQL function f() line 2 at SQL statement
regression=# \errverbose
ERROR: 42P01: relation "enr" does not exist
LINE 1: create view mv as select * from enr
^
QUERY: create view mv as select * from enr
CONTEXT: PL/pgSQL function f() line 2 at SQL statement
LOCATION: parserOpenTable, parse_relation.c:1452

3. With "create materialized view ..." you get

regression=# insert into tbl values (10);
ERROR: executor could not find named tuplestore "enr"
CONTEXT: SQL statement "create materialized view mv as select * from enr"
PL/pgSQL function f() line 2 at SQL statement
regression=# \errverbose
ERROR: XX000: executor could not find named tuplestore "enr"
CONTEXT: SQL statement "create materialized view mv as select * from enr"
PL/pgSQL function f() line 2 at SQL statement
LOCATION: ExecInitNamedTuplestoreScan, nodeNamedtuplestorescan.c:107

I don't think hitting an internal error is good enough.
Why doesn't this case act like case 2?

You could even argue that case 2 isn't good enough either,
and we should be delivering a specific error message saying
that an ENR can't be used in a view/matview. To do that,
we'd likely need to pass down the QueryEnvironment in more
places not fewer.

regards, tom lane

#4Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Tom Lane (#3)
1 attachment(s)
Re: EphemeralNamedRelation and materialized view

On Sun, 03 Nov 2024 13:42:33 -0500
Tom Lane <tgl@sss.pgh.pa.us> wrote:

Yugo Nagata <nagata@sraoss.co.jp> writes:

While looking into the commit b4da732fd64e936970f38c792f8b32c4bdf2bcd5,
I noticed that we can create a materialized view using Ephemeral Named
Relation in PostgreSQL 16 or earler.

Yeah, we should reject that, but I feel like this patch is not
ambitious enough, because the 17-and-up behavior isn't exactly
polished either.

I tried variants of this function in HEAD:

1. With "create table mv as select * from enr", it works and
does what you'd expect.

2. With "create view mv as select * from enr", you get

regression=# insert into tbl values (10);
ERROR: relation "enr" does not exist
LINE 1: create view mv as select * from enr
^
QUERY: create view mv as select * from enr
CONTEXT: PL/pgSQL function f() line 2 at SQL statement
regression=# \errverbose
ERROR: 42P01: relation "enr" does not exist
LINE 1: create view mv as select * from enr
^
QUERY: create view mv as select * from enr
CONTEXT: PL/pgSQL function f() line 2 at SQL statement
LOCATION: parserOpenTable, parse_relation.c:1452

3. With "create materialized view ..." you get

regression=# insert into tbl values (10);
ERROR: executor could not find named tuplestore "enr"
CONTEXT: SQL statement "create materialized view mv as select * from enr"
PL/pgSQL function f() line 2 at SQL statement
regression=# \errverbose
ERROR: XX000: executor could not find named tuplestore "enr"
CONTEXT: SQL statement "create materialized view mv as select * from enr"
PL/pgSQL function f() line 2 at SQL statement
LOCATION: ExecInitNamedTuplestoreScan, nodeNamedtuplestorescan.c:107

I don't think hitting an internal error is good enough.
Why doesn't this case act like case 2?

I agree that raising an internal error is not enough. I attached a updated
patch that outputs a message saying that an ENR can't be used in a matview.

You could even argue that case 2 isn't good enough either,
and we should be delivering a specific error message saying
that an ENR can't be used in a view/matview. To do that,
we'd likely need to pass down the QueryEnvironment in more
places not fewer.

We can raise a similar error for (not materialized) views by passing
QueryEnv to DefineView() (or in ealier stage) , but there are other
objects that can contain ENR in their definition, for examle, functions,
cursor, or RLS policies. Is it worth introducing this version of error
message for all these objects?

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

0001-Prohibit-materialized-views-to-use-ephemeral-named-r.patchtext/x-diff; name=0001-Prohibit-materialized-views-to-use-ephemeral-named-r.patchDownload
From 6c72c884954a4223fad192ff021803bf7835e7d7 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Fri, 15 Nov 2024 15:07:17 +0900
Subject: [PATCH] Prohibit materialized views to use ephemeral named relations

---
 src/backend/parser/analyze.c        | 11 ++++++--
 src/backend/parser/parse_relation.c | 43 ++++++++++++++++++++++++++++-
 src/include/parser/parse_relation.h |  1 +
 3 files changed, 51 insertions(+), 4 deletions(-)

diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 506e063161..c96088c2cc 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -3125,15 +3125,20 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					 errmsg("materialized views must not use data-modifying statements in WITH")));
 
 		/*
-		 * Check whether any temporary database objects are used in the
-		 * creation query. It would be hard to refresh data or incrementally
-		 * maintain it if a source disappeared.
+		 * Check whether any temporary database objects or ephemeral named
+		 * relations are used in the creation query. It would be hard to refresh
+		 * data or incrementally maintain it if a source disappeared.
 		 */
 		if (isQueryUsingTempRelation(query))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (isQueryUsingENR(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use ephemeral named relations")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 8075b1b8a1..0e9cb7a1c6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -102,6 +102,7 @@ static int	specialAttNum(const char *attname);
 static bool rte_visible_if_lateral(ParseState *pstate, RangeTblEntry *rte);
 static bool rte_visible_if_qualified(ParseState *pstate, RangeTblEntry *rte);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+static bool isQueryUsingENR_walker(Node *node, void *context);
 
 
 /*
@@ -3893,7 +3894,7 @@ rte_visible_if_qualified(ParseState *pstate, RangeTblEntry *rte)
 
 /*
  * Examine a fully-parsed query, and return true iff any relation underlying
- * the query is a temporary relation (table, view, or materialized view).
+ * the query is an ephemeral named relation.
  */
 bool
 isQueryUsingTempRelation(Query *query)
@@ -3938,6 +3939,46 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  context);
 }
 
+/*
+ * Examine a fully-parsed query, and return true iff any relation underlying
+ * the query is an ephemeral named relation.
+ */
+bool
+isQueryUsingENR(Query *query)
+{
+	return isQueryUsingENR_walker((Node *) query, NULL);
+}
+
+static bool
+isQueryUsingENR_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_NAMEDTUPLESTORE)
+				return true;
+		}
+
+		return query_tree_walker(query,
+								 isQueryUsingENR_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  isQueryUsingENR_walker,
+								  context);
+}
+
 /*
  * addRTEPermissionInfo
  *		Creates RTEPermissionInfo for a given RTE and adds it into the
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 91fd8e243b..2849da7fbd 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -127,5 +127,6 @@ extern const NameData *attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
 extern Oid	attnumCollationId(Relation rd, int attid);
 extern bool isQueryUsingTempRelation(Query *query);
+extern bool isQueryUsingENR(Query *query);
 
 #endif							/* PARSE_RELATION_H */
-- 
2.34.1

#5Michael Paquier
michael@paquier.xyz
In reply to: Yugo NAGATA (#4)
Re: EphemeralNamedRelation and materialized view

On Fri, Nov 15, 2024 at 05:36:47PM +0900, Yugo NAGATA wrote:

I agree that raising an internal error is not enough. I attached a updated
patch that outputs a message saying that an ENR can't be used in a matview.

Hmm.. To get a better idea of the scope you are foreseeing here,
should this include some test coverage?
--
Michael

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Yugo NAGATA (#4)
Re: EphemeralNamedRelation and materialized view

Yugo NAGATA <nagata@sraoss.co.jp> writes:

You could even argue that case 2 isn't good enough either,
and we should be delivering a specific error message saying
that an ENR can't be used in a view/matview. To do that,
we'd likely need to pass down the QueryEnvironment in more
places not fewer.

We can raise a similar error for (not materialized) views by passing
QueryEnv to DefineView() (or in ealier stage) , but there are other
objects that can contain ENR in their definition, for examle, functions,
cursor, or RLS policies. Is it worth introducing this version of error
message for all these objects?

If it's worth checking for here, why not in other cases?

I'm not sure I like using isQueryUsingTempRelation as a model,
because its existing use in transformCreateTableAsStmt seems
like mostly a hack. (And I definitely don't love introducing
yet another scan of the query.) It seems to me that we should
think about this, for MVs as well as those other object types,
as fundamentally a dependency problem. That is, the reason
we can't allow a reference to an ENR in a long-lived object
is that we have no catalog representation for the reference.
So that leads to thinking that the issue ought to be handled
in recordDependencyOnExpr and friends. If we see an ENR while
scanning a rangetable to extract dependencies, then complain.
This might be a bit messy to produce good error messages for,
though.

Speaking of error messages, I'm not sure that it's okay to
use the phrase "ephemeral named relation" in a user-facing
error message. We don't use that term in our documentation
AFAICS, except in some SPI documentation that most users
will never have read. In the context of triggers, "transition
relation" seems to be what the docs use.

regards, tom lane

#7Kirill Reshke
reshkekirill@gmail.com
In reply to: Yugo NAGATA (#4)
Re: EphemeralNamedRelation and materialized view

On Fri, 15 Nov 2024 at 13:37, Yugo NAGATA <nagata@sraoss.co.jp> wrote:

On Sun, 03 Nov 2024 13:42:33 -0500
Tom Lane <tgl@sss.pgh.pa.us> wrote:

Yugo Nagata <nagata@sraoss.co.jp> writes:

While looking into the commit b4da732fd64e936970f38c792f8b32c4bdf2bcd5,
I noticed that we can create a materialized view using Ephemeral Named
Relation in PostgreSQL 16 or earler.

Yeah, we should reject that, but I feel like this patch is not
ambitious enough, because the 17-and-up behavior isn't exactly
polished either.

I tried variants of this function in HEAD:

1. With "create table mv as select * from enr", it works and
does what you'd expect.

2. With "create view mv as select * from enr", you get

regression=# insert into tbl values (10);
ERROR: relation "enr" does not exist
LINE 1: create view mv as select * from enr
^
QUERY: create view mv as select * from enr
CONTEXT: PL/pgSQL function f() line 2 at SQL statement
regression=# \errverbose
ERROR: 42P01: relation "enr" does not exist
LINE 1: create view mv as select * from enr
^
QUERY: create view mv as select * from enr
CONTEXT: PL/pgSQL function f() line 2 at SQL statement
LOCATION: parserOpenTable, parse_relation.c:1452

3. With "create materialized view ..." you get

regression=# insert into tbl values (10);
ERROR: executor could not find named tuplestore "enr"
CONTEXT: SQL statement "create materialized view mv as select * from enr"
PL/pgSQL function f() line 2 at SQL statement
regression=# \errverbose
ERROR: XX000: executor could not find named tuplestore "enr"
CONTEXT: SQL statement "create materialized view mv as select * from enr"
PL/pgSQL function f() line 2 at SQL statement
LOCATION: ExecInitNamedTuplestoreScan, nodeNamedtuplestorescan.c:107

I don't think hitting an internal error is good enough.
Why doesn't this case act like case 2?

I agree that raising an internal error is not enough. I attached a updated
patch that outputs a message saying that an ENR can't be used in a matview.

You could even argue that case 2 isn't good enough either,
and we should be delivering a specific error message saying
that an ENR can't be used in a view/matview. To do that,
we'd likely need to pass down the QueryEnvironment in more
places not fewer.

We can raise a similar error for (not materialized) views by passing
QueryEnv to DefineView() (or in ealier stage) , but there are other
objects that can contain ENR in their definition, for examle, functions,
cursor, or RLS policies. Is it worth introducing this version of error
message for all these objects?

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Hi!

There are review comments that need to be addressed.

Commitfest status is now waiting on the author.

[0]: /messages/by-id/ZzrHUEaWB67EAZpW@paquier.xyz
[1]: /messages/by-id/222722.1732124596@sss.pgh.pa.us

--
Best regards,
Kirill Reshke

#8Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Tom Lane (#6)
1 attachment(s)
Re: EphemeralNamedRelation and materialized view

On Wed, 20 Nov 2024 12:43:16 -0500
Tom Lane <tgl@sss.pgh.pa.us> wrote:

Yugo NAGATA <nagata@sraoss.co.jp> writes:

You could even argue that case 2 isn't good enough either,
and we should be delivering a specific error message saying
that an ENR can't be used in a view/matview. To do that,
we'd likely need to pass down the QueryEnvironment in more
places not fewer.

We can raise a similar error for (not materialized) views by passing
QueryEnv to DefineView() (or in ealier stage) , but there are other
objects that can contain ENR in their definition, for examle, functions,
cursor, or RLS policies. Is it worth introducing this version of error
message for all these objects?

If it's worth checking for here, why not in other cases?

I'm not sure I like using isQueryUsingTempRelation as a model,
because its existing use in transformCreateTableAsStmt seems
like mostly a hack. (And I definitely don't love introducing
yet another scan of the query.) It seems to me that we should
think about this, for MVs as well as those other object types,
as fundamentally a dependency problem. That is, the reason
we can't allow a reference to an ENR in a long-lived object
is that we have no catalog representation for the reference.
So that leads to thinking that the issue ought to be handled
in recordDependencyOnExpr and friends. If we see an ENR while
scanning a rangetable to extract dependencies, then complain.
This might be a bit messy to produce good error messages for,
though.

Speaking of error messages, I'm not sure that it's okay to
use the phrase "ephemeral named relation" in a user-facing
error message. We don't use that term in our documentation
AFAICS, except in some SPI documentation that most users
will never have read. In the context of triggers, "transition
relation" seems to be what the docs use.

Thank you for your suggestion.

I've attached a updated patch. Use of ENRs are now checked in
find_expr_references_walker() called from recordDependencyOnExpr().

The message is changed to "transition tables cannot be used rule"
because the view definition is stored in the pg_rewrite catalog as
a rule.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

v2-0001-Prohibit-materialized-views-to-use-ephemeral-name.patchtext/x-diff; name=v2-0001-Prohibit-materialized-views-to-use-ephemeral-name.patchDownload
From 34d09775e92b0ac39cd8656d69164b13c92f6fa2 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Mon, 2 Dec 2024 09:39:34 +0900
Subject: [PATCH v2] Prohibit materialized views to use ephemeral named
 relations

---
 src/backend/catalog/dependency.c       | 24 ++++++++++++++++++++++++
 src/test/regress/expected/triggers.out | 13 +++++++++++++
 src/test/regress/sql/triggers.sql      | 13 +++++++++++++
 3 files changed, 50 insertions(+)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 2afc550540..f323e8c91e 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -138,6 +138,7 @@ typedef struct
 /* for find_expr_references_walker */
 typedef struct
 {
+	const ObjectAddress *depender;	/* depender object */
 	ObjectAddresses *addrs;		/* addresses being accumulated */
 	List	   *rtables;		/* list of rangetables to resolve Vars */
 } find_expr_references_context;
@@ -1556,6 +1557,7 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 {
 	find_expr_references_context context;
 
+	context.depender = depender;
 	context.addrs = new_object_addresses();
 
 	/* Set up interpretation for Vars at varlevelsup = 0 */
@@ -1602,6 +1604,7 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 	find_expr_references_context context;
 	RangeTblEntry rte = {0};
 
+	context.depender = depender;
 	context.addrs = new_object_addresses();
 
 	/* We gin up a rather bogus rangetable list to handle Vars */
@@ -1739,6 +1742,17 @@ find_expr_references_walker(Node *node,
 			/* (done out of line, because it's a bit bulky) */
 			process_function_rte_ref(rte, var->varattno, context);
 		}
+		else if (rte->rtekind == RTE_NAMEDTUPLESTORE)
+		{
+			/*
+			 * Catalog objects cannot depend on a transtion table which has
+			 * no catalog representation.
+			 */
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("transition table cannot be used in %s",
+							getObjectTypeDescription(context->depender, true))));
+		}
 
 		/*
 		 * Vars referencing other RTE types require no additional work.  In
@@ -2191,6 +2205,16 @@ find_expr_references_walker(Node *node,
 					}
 					context->rtables = list_delete_first(context->rtables);
 					break;
+				case RTE_NAMEDTUPLESTORE:
+					/*
+					 * Catalog objects cannot depend on a transtion table
+					 * which has no catalog representation.
+					 */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("transition table cannot be used in %s",
+									getObjectTypeDescription(context->depender, true))));
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index a044d6afe2..e3522cf3bb 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3728,3 +3728,16 @@ Inherits: parent
 
 drop table parent, child;
 drop function f();
+-- Verify prohibition of materialized views defined using tansition table
+create table my_table (a int);
+create function make_matview() returns trigger as
+$$ begin create materialized view mv as select * from new_table; return new; end $$
+language plpgsql;
+create trigger make_matview after insert on my_table
+referencing new table as new_table
+for each statement execute function make_matview();
+insert into my_table values (42);
+ERROR:  transition tables cannot be used in rule
+CONTEXT:  SQL statement "create materialized view mv as select * from new_table"
+PL/pgSQL function make_matview() line 1 at SQL statement
+drop table my_table;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 51610788b2..f83bd84e6c 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2551,6 +2551,7 @@ merge into merge_target_table t
 using merge_source_table s
 on t.a = s.a
 when matched and s.a <= 2 then
+
 	update set b = t.b || ' updated by merge'
 when matched and s.a > 2 then
 	delete
@@ -2820,3 +2821,15 @@ alter trigger parenttrig on parent rename to anothertrig;
 
 drop table parent, child;
 drop function f();
+
+-- Verify prohibition of materialized views defined using tansition table
+
+create table my_table (a int);
+create function make_matview() returns trigger as
+$$ begin create materialized view mv as select * from new_table; return new; end $$
+language plpgsql;
+create trigger make_matview after insert on my_table
+referencing new table as new_table
+for each statement execute function make_matview();
+insert into my_table values (42);
+drop table my_table;
-- 
2.34.1

#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Yugo NAGATA (#8)
1 attachment(s)
Re: EphemeralNamedRelation and materialized view

Yugo NAGATA <nagata@sraoss.co.jp> writes:

On Wed, 20 Nov 2024 12:43:16 -0500
Tom Lane <tgl@sss.pgh.pa.us> wrote:

... It seems to me that we should
think about this, for MVs as well as those other object types,
as fundamentally a dependency problem. That is, the reason
we can't allow a reference to an ENR in a long-lived object
is that we have no catalog representation for the reference.
So that leads to thinking that the issue ought to be handled
in recordDependencyOnExpr and friends. If we see an ENR while
scanning a rangetable to extract dependencies, then complain.
This might be a bit messy to produce good error messages for,
though.

I've attached a updated patch. Use of ENRs are now checked in
find_expr_references_walker() called from recordDependencyOnExpr().

This looks pretty good to me, except that I question the use of
getObjectTypeDescription() in the error message. There are a
few things not to like about that:

1. This is kind of an off-label use of getObjectTypeDescription,
in that we can't expect the object to be visible yet in the catalogs.
Yeah, we can hack around that by passing missing_ok = true, but it
still seems like a kluge.

2. The grammar isn't great, and translatability of the message
would be poor I think.

3. As your test case demonstrates, the message is going to complain
about a "rule" if the problem is with a view or matview, because
we represent the dependency as being from the view's ON SELECT rule.
This seems quite confusing for anyone not deeply versed in PG's inner
workings.

After some thought I propose that we just complain that a "persistent
object" can't depend on a transition table, and not try to identify
the depender any more closely than that. We can still add some
context to the message by showing the transition table's name,
since that's readily available from the RTE. See attached v3,
where I also did a bit of editing of the comments and test case.

BTW, I'm not entirely convinced that the first addition (in Var
processing) is necessary. Such a Var must refer to an RTE
somewhere, and I'm having a hard time coming up with a case
where the RTE wouldn't also be part of what we scan for
dependencies. It's harmless enough to have the extra check,
but can you think of a case where it's actually needed?

regards, tom lane

Attachments:

v3-0001-Disallow-NAMEDTUPLESTORE-RTEs-in-stored-views-rul.patchtext/x-diff; charset=us-ascii; name*0=v3-0001-Disallow-NAMEDTUPLESTORE-RTEs-in-stored-views-rul.p; name*1=atchDownload
From a0014121e3ca72cb60a7fb627a19c71c85ca84ae Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 30 Dec 2024 15:59:32 -0500
Subject: [PATCH v3] Disallow NAMEDTUPLESTORE RTEs in stored views, rules, etc.

A named tuplestore is necessarily a transient object, so it makes
no sense to reference one in a persistent object such as a view.
We didn't previously prevent that, with the result that if you
tried you would get some weird failure about how the executor
couldn't find the tuplestore.

We can mechanize a check for this case cheaply by making dependency
extraction complain if it comes across such an RTE.  This is a
plausible way of dealing with it since part of the problem is that we
have no way to make a pg_depend representation of a named tuplestore.

Report and fix by Yugo Nagata.  Although this is an old problem,
it's a very weird corner case and there have been no reports from
end users.  So it seems sufficient to fix it in master.

Discussion: https://postgr.es/m/20240726160714.e74d0db579f2c017e1ca0b7e@sraoss.co.jp
---
 src/backend/catalog/dependency.c       | 28 ++++++++++++++++++++++++++
 src/test/regress/expected/triggers.out | 20 ++++++++++++++++++
 src/test/regress/sql/triggers.sql      | 19 +++++++++++++++++
 3 files changed, 67 insertions(+)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 2afc550540..8355481e82 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1739,6 +1739,19 @@ find_expr_references_walker(Node *node,
 			/* (done out of line, because it's a bit bulky) */
 			process_function_rte_ref(rte, var->varattno, context);
 		}
+		else if (rte->rtekind == RTE_NAMEDTUPLESTORE)
+		{
+			/*
+			 * Cataloged objects cannot depend on tuplestores, because those
+			 * have no cataloged representation.  For now we can call the
+			 * tuplestore a "transition table" because that's the only kind
+			 * exposed to SQL, but someday we might have to work harder.
+			 */
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("transition table \"%s\" cannot be referenced in a persistent object",
+							rte->eref->aliasname)));
+		}
 
 		/*
 		 * Vars referencing other RTE types require no additional work.  In
@@ -2191,7 +2204,22 @@ find_expr_references_walker(Node *node,
 					}
 					context->rtables = list_delete_first(context->rtables);
 					break;
+				case RTE_NAMEDTUPLESTORE:
+
+					/*
+					 * Cataloged objects cannot depend on tuplestores, because
+					 * those have no cataloged representation.  For now we can
+					 * call the tuplestore a "transition table" because that's
+					 * the only kind exposed to SQL, but someday we might have
+					 * to work harder.
+					 */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("transition table \"%s\" cannot be referenced in a persistent object",
+									rte->eref->aliasname)));
+					break;
 				default:
+					/* Other RTE types can be ignored here */
 					break;
 			}
 		}
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index a044d6afe2..0657da1757 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3315,6 +3315,26 @@ create trigger my_table_col_update_trig
 ERROR:  transition tables cannot be specified for triggers with column lists
 drop table my_table;
 --
+-- Verify that transition tables can't be used in, eg, a view.
+--
+create table my_table (a int);
+create function make_bogus_matview() returns trigger as
+$$ begin
+  create materialized view transition_test_mv as select * from new_table;
+  return new;
+end $$
+language plpgsql;
+create trigger make_bogus_matview
+  after insert on my_table
+  referencing new table as new_table
+  for each statement execute function make_bogus_matview();
+insert into my_table values (42);  -- error
+ERROR:  transition table "new_table" cannot be referenced in a persistent object
+CONTEXT:  SQL statement "create materialized view transition_test_mv as select * from new_table"
+PL/pgSQL function make_bogus_matview() line 2 at SQL statement
+drop table my_table;
+drop function make_bogus_matview();
+--
 -- Test firing of triggers with transition tables by foreign key cascades
 --
 create table refd_table (a int primary key, b text);
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 51610788b2..7e2f7597c1 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2434,6 +2434,25 @@ create trigger my_table_col_update_trig
 
 drop table my_table;
 
+--
+-- Verify that transition tables can't be used in, eg, a view.
+--
+
+create table my_table (a int);
+create function make_bogus_matview() returns trigger as
+$$ begin
+  create materialized view transition_test_mv as select * from new_table;
+  return new;
+end $$
+language plpgsql;
+create trigger make_bogus_matview
+  after insert on my_table
+  referencing new table as new_table
+  for each statement execute function make_bogus_matview();
+insert into my_table values (42);  -- error
+drop table my_table;
+drop function make_bogus_matview();
+
 --
 -- Test firing of triggers with transition tables by foreign key cascades
 --
-- 
2.43.5

#10Yugo NAGATA
nagata@sraoss.co.jp
In reply to: Tom Lane (#9)
1 attachment(s)
Re: EphemeralNamedRelation and materialized view

On Mon, 30 Dec 2024 16:06:06 -0500
Tom Lane <tgl@sss.pgh.pa.us> wrote:

Yugo NAGATA <nagata@sraoss.co.jp> writes:

On Wed, 20 Nov 2024 12:43:16 -0500
Tom Lane <tgl@sss.pgh.pa.us> wrote:

... It seems to me that we should
think about this, for MVs as well as those other object types,
as fundamentally a dependency problem. That is, the reason
we can't allow a reference to an ENR in a long-lived object
is that we have no catalog representation for the reference.
So that leads to thinking that the issue ought to be handled
in recordDependencyOnExpr and friends. If we see an ENR while
scanning a rangetable to extract dependencies, then complain.
This might be a bit messy to produce good error messages for,
though.

I've attached a updated patch. Use of ENRs are now checked in
find_expr_references_walker() called from recordDependencyOnExpr().

This looks pretty good to me, except that I question the use of
getObjectTypeDescription() in the error message. There are a
few things not to like about that:

1. This is kind of an off-label use of getObjectTypeDescription,
in that we can't expect the object to be visible yet in the catalogs.
Yeah, we can hack around that by passing missing_ok = true, but it
still seems like a kluge.

2. The grammar isn't great, and translatability of the message
would be poor I think.

3. As your test case demonstrates, the message is going to complain
about a "rule" if the problem is with a view or matview, because
we represent the dependency as being from the view's ON SELECT rule.
This seems quite confusing for anyone not deeply versed in PG's inner
workings.

After some thought I propose that we just complain that a "persistent
object" can't depend on a transition table, and not try to identify
the depender any more closely than that. We can still add some
context to the message by showing the transition table's name,
since that's readily available from the RTE. See attached v3,
where I also did a bit of editing of the comments and test case.

Thank you for your reviewing and editing the patch!
I agree with your proposal on the error message handling.

BTW, I'm not entirely convinced that the first addition (in Var
processing) is necessary. Such a Var must refer to an RTE
somewhere, and I'm having a hard time coming up with a case
where the RTE wouldn't also be part of what we scan for
dependencies. It's harmless enough to have the extra check,
but can you think of a case where it's actually needed?

On second thought, I could not think of such a case. This part
can be removed. I attached v4 patch.

Regards,
Yugo Nagata

--
Yugo NAGATA <nagata@sraoss.co.jp>

Attachments:

v4-0001-Disallow-NAMEDTUPLESTORE-RTEs-in-stored-views-rul.patchtext/x-diff; name=v4-0001-Disallow-NAMEDTUPLESTORE-RTEs-in-stored-views-rul.patchDownload
From 40f96b4ce8e93e8920e840e52b6a78f60a319efd Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 30 Dec 2024 15:59:32 -0500
Subject: [PATCH v4] Disallow NAMEDTUPLESTORE RTEs in stored views, rules, etc.

A named tuplestore is necessarily a transient object, so it makes
no sense to reference one in a persistent object such as a view.
We didn't previously prevent that, with the result that if you
tried you would get some weird failure about how the executor
couldn't find the tuplestore.

We can mechanize a check for this case cheaply by making dependency
extraction complain if it comes across such an RTE.  This is a
plausible way of dealing with it since part of the problem is that we
have no way to make a pg_depend representation of a named tuplestore.

Report and fix by Yugo Nagata.  Although this is an old problem,
it's a very weird corner case and there have been no reports from
end users.  So it seems sufficient to fix it in master.

Discussion: https://postgr.es/m/20240726160714.e74d0db579f2c017e1ca0b7e@sraoss.co.jp
---
 src/backend/catalog/dependency.c       | 15 +++++++++++++++
 src/test/regress/expected/triggers.out | 20 ++++++++++++++++++++
 src/test/regress/sql/triggers.sql      | 19 +++++++++++++++++++
 3 files changed, 54 insertions(+)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 096b68c7f3..18316a3968 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2191,7 +2191,22 @@ find_expr_references_walker(Node *node,
 					}
 					context->rtables = list_delete_first(context->rtables);
 					break;
+				case RTE_NAMEDTUPLESTORE:
+
+					/*
+					 * Cataloged objects cannot depend on tuplestores, because
+					 * those have no cataloged representation.  For now we can
+					 * call the tuplestore a "transition table" because that's
+					 * the only kind exposed to SQL, but someday we might have
+					 * to work harder.
+					 */
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("transition table \"%s\" cannot be referenced in a persistent object",
+									rte->eref->aliasname)));
+					break;
 				default:
+					/* Other RTE types can be ignored here */
 					break;
 			}
 		}
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index a044d6afe2..0657da1757 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3315,6 +3315,26 @@ create trigger my_table_col_update_trig
 ERROR:  transition tables cannot be specified for triggers with column lists
 drop table my_table;
 --
+-- Verify that transition tables can't be used in, eg, a view.
+--
+create table my_table (a int);
+create function make_bogus_matview() returns trigger as
+$$ begin
+  create materialized view transition_test_mv as select * from new_table;
+  return new;
+end $$
+language plpgsql;
+create trigger make_bogus_matview
+  after insert on my_table
+  referencing new table as new_table
+  for each statement execute function make_bogus_matview();
+insert into my_table values (42);  -- error
+ERROR:  transition table "new_table" cannot be referenced in a persistent object
+CONTEXT:  SQL statement "create materialized view transition_test_mv as select * from new_table"
+PL/pgSQL function make_bogus_matview() line 2 at SQL statement
+drop table my_table;
+drop function make_bogus_matview();
+--
 -- Test firing of triggers with transition tables by foreign key cascades
 --
 create table refd_table (a int primary key, b text);
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 51610788b2..7e2f7597c1 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2434,6 +2434,25 @@ create trigger my_table_col_update_trig
 
 drop table my_table;
 
+--
+-- Verify that transition tables can't be used in, eg, a view.
+--
+
+create table my_table (a int);
+create function make_bogus_matview() returns trigger as
+$$ begin
+  create materialized view transition_test_mv as select * from new_table;
+  return new;
+end $$
+language plpgsql;
+create trigger make_bogus_matview
+  after insert on my_table
+  referencing new table as new_table
+  for each statement execute function make_bogus_matview();
+insert into my_table values (42);  -- error
+drop table my_table;
+drop function make_bogus_matview();
+
 --
 -- Test firing of triggers with transition tables by foreign key cascades
 --
-- 
2.34.1

#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Yugo NAGATA (#10)
Re: EphemeralNamedRelation and materialized view

Yugo NAGATA <nagata@sraoss.co.jp> writes:

Thank you for your reviewing and editing the patch!
I agree with your proposal on the error message handling.

Cool, pushed v4 then.

regards, tom lane