Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit b23cd18

Browse files
committed
Remove logic for converting a table to a view.
Up to now we have allowed manual creation of an ON SELECT rule on a table to convert it into a view. That was never anything but a horrid, error-prone hack though. pg_dump used to rely on that behavior to deal with cases involving circular dependencies, where a dependency loop could be broken by separating the creation of a view from installation of its ON SELECT rule. However, we changed pg_dump to use CREATE OR REPLACE VIEW for that in commit d8c05af (which was later back-patched as far as 9.4), so there's not a good argument anymore for continuing to support the behavior. The proximate reason for axing it now is that we found that the new statistics code has failure modes associated with the relkind change caused by this behavior. We'll patch around that in v15, but going forward it seems like a better idea to get rid of the need to support relkind changes. Discussion: https://postgr.es/m/CALDaNm2yXz+zOtv7y5zBd5WKT8O0Ld3YxikuU3dcyCvxF7gypA@mail.gmail.com
1 parent 66456da commit b23cd18

File tree

8 files changed

+45
-305
lines changed

8 files changed

+45
-305
lines changed

doc/src/sgml/rules.sgml

+17-11
Original file line numberDiff line numberDiff line change
@@ -280,28 +280,34 @@
280280

281281
<para>
282282
Views in <productname>PostgreSQL</productname> are implemented
283-
using the rule system. In fact, there is essentially no difference
284-
between:
283+
using the rule system. A view is basically an empty table (having no
284+
actual storage) with an <literal>ON SELECT DO INSTEAD</literal> rule.
285+
Conventionally, that rule is named <literal>_RETURN</literal>.
286+
So a view like
285287

286288
<programlisting>
287289
CREATE VIEW myview AS SELECT * FROM mytab;
288290
</programlisting>
289291

290-
compared against the two commands:
292+
is very nearly the same thing as
291293

292294
<programlisting>
293295
CREATE TABLE myview (<replaceable>same column list as mytab</replaceable>);
294296
CREATE RULE "_RETURN" AS ON SELECT TO myview DO INSTEAD
295297
SELECT * FROM mytab;
296298
</programlisting>
297299

298-
because this is exactly what the <command>CREATE VIEW</command>
299-
command does internally. This has some side effects. One of them
300-
is that the information about a view in the
301-
<productname>PostgreSQL</productname> system catalogs is exactly
302-
the same as it is for a table. So for the parser, there is
303-
absolutely no difference between a table and a view. They are the
304-
same thing: relations.
300+
although you can't actually write that, because tables are not
301+
allowed to have <literal>ON SELECT</literal> rules.
302+
</para>
303+
304+
<para>
305+
A view can also have other kinds of <literal>DO INSTEAD</literal>
306+
rules, allowing <command>INSERT</command>, <command>UPDATE</command>,
307+
or <command>DELETE</command> commands to be performed on the view
308+
despite its lack of underlying storage.
309+
This is discussed further below, in
310+
<xref linkend="rules-views-update"/>.
305311
</para>
306312

307313
<sect2 id="rules-select">
@@ -1111,7 +1117,7 @@ SELECT word FROM words ORDER BY word &lt;-&gt; 'caterpiler' LIMIT 10;
11111117
<para>
11121118
Rules that are defined on <command>INSERT</command>, <command>UPDATE</command>,
11131119
and <command>DELETE</command> are significantly different from the view rules
1114-
described in the previous section. First, their <command>CREATE
1120+
described in the previous sections. First, their <command>CREATE
11151121
RULE</command> command allows more:
11161122

11171123
<itemizedlist>

src/backend/rewrite/rewriteDefine.c

+12-177
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ DefineQueryRewrite(const char *rulename,
239239
Relation event_relation;
240240
ListCell *l;
241241
Query *query;
242-
bool RelisBecomingView = false;
243242
Oid ruleId = InvalidOid;
244243
ObjectAddress address;
245244

@@ -311,7 +310,18 @@ DefineQueryRewrite(const char *rulename,
311310
/*
312311
* Rules ON SELECT are restricted to view definitions
313312
*
314-
* So there cannot be INSTEAD NOTHING, ...
313+
* So this had better be a view, ...
314+
*/
315+
if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
316+
event_relation->rd_rel->relkind != RELKIND_MATVIEW)
317+
ereport(ERROR,
318+
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
319+
errmsg("relation \"%s\" cannot have ON SELECT rules",
320+
RelationGetRelationName(event_relation)),
321+
errdetail_relkind_not_supported(event_relation->rd_rel->relkind)));
322+
323+
/*
324+
* ... there cannot be INSTEAD NOTHING, ...
315325
*/
316326
if (action == NIL)
317327
ereport(ERROR,
@@ -407,93 +417,6 @@ DefineQueryRewrite(const char *rulename,
407417
ViewSelectRuleName)));
408418
rulename = pstrdup(ViewSelectRuleName);
409419
}
410-
411-
/*
412-
* Are we converting a relation to a view?
413-
*
414-
* If so, check that the relation is empty because the storage for the
415-
* relation is going to be deleted. Also insist that the rel not be
416-
* involved in partitioning, nor have any triggers, indexes, child or
417-
* parent tables, RLS policies, or RLS enabled. (Note: some of these
418-
* tests are too strict, because they will reject relations that once
419-
* had such but don't anymore. But we don't really care, because this
420-
* whole business of converting relations to views is just an obsolete
421-
* kluge to allow dump/reload of views that participate in circular
422-
* dependencies.)
423-
*/
424-
if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
425-
event_relation->rd_rel->relkind != RELKIND_MATVIEW)
426-
{
427-
TableScanDesc scanDesc;
428-
Snapshot snapshot;
429-
TupleTableSlot *slot;
430-
431-
if (event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
432-
ereport(ERROR,
433-
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
434-
errmsg("cannot convert partitioned table \"%s\" to a view",
435-
RelationGetRelationName(event_relation))));
436-
437-
/* only case left: */
438-
Assert(event_relation->rd_rel->relkind == RELKIND_RELATION);
439-
440-
if (event_relation->rd_rel->relispartition)
441-
ereport(ERROR,
442-
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
443-
errmsg("cannot convert partition \"%s\" to a view",
444-
RelationGetRelationName(event_relation))));
445-
446-
snapshot = RegisterSnapshot(GetLatestSnapshot());
447-
scanDesc = table_beginscan(event_relation, snapshot, 0, NULL);
448-
slot = table_slot_create(event_relation, NULL);
449-
if (table_scan_getnextslot(scanDesc, ForwardScanDirection, slot))
450-
ereport(ERROR,
451-
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
452-
errmsg("could not convert table \"%s\" to a view because it is not empty",
453-
RelationGetRelationName(event_relation))));
454-
ExecDropSingleTupleTableSlot(slot);
455-
table_endscan(scanDesc);
456-
UnregisterSnapshot(snapshot);
457-
458-
if (event_relation->rd_rel->relhastriggers)
459-
ereport(ERROR,
460-
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
461-
errmsg("could not convert table \"%s\" to a view because it has triggers",
462-
RelationGetRelationName(event_relation)),
463-
errhint("In particular, the table cannot be involved in any foreign key relationships.")));
464-
465-
if (event_relation->rd_rel->relhasindex)
466-
ereport(ERROR,
467-
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
468-
errmsg("could not convert table \"%s\" to a view because it has indexes",
469-
RelationGetRelationName(event_relation))));
470-
471-
if (event_relation->rd_rel->relhassubclass)
472-
ereport(ERROR,
473-
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
474-
errmsg("could not convert table \"%s\" to a view because it has child tables",
475-
RelationGetRelationName(event_relation))));
476-
477-
if (has_superclass(RelationGetRelid(event_relation)))
478-
ereport(ERROR,
479-
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
480-
errmsg("could not convert table \"%s\" to a view because it has parent tables",
481-
RelationGetRelationName(event_relation))));
482-
483-
if (event_relation->rd_rel->relrowsecurity)
484-
ereport(ERROR,
485-
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
486-
errmsg("could not convert table \"%s\" to a view because it has row security enabled",
487-
RelationGetRelationName(event_relation))));
488-
489-
if (relation_has_policies(event_relation))
490-
ereport(ERROR,
491-
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
492-
errmsg("could not convert table \"%s\" to a view because it has row security policies",
493-
RelationGetRelationName(event_relation))));
494-
495-
RelisBecomingView = true;
496-
}
497420
}
498421
else
499422
{
@@ -569,94 +492,6 @@ DefineQueryRewrite(const char *rulename,
569492
SetRelationRuleStatus(event_relid, true);
570493
}
571494

572-
/* ---------------------------------------------------------------------
573-
* If the relation is becoming a view:
574-
* - delete the associated storage files
575-
* - get rid of any system attributes in pg_attribute; a view shouldn't
576-
* have any of those
577-
* - remove the toast table; there is no need for it anymore, and its
578-
* presence would make vacuum slightly more complicated
579-
* - set relkind to RELKIND_VIEW, and adjust other pg_class fields
580-
* to be appropriate for a view
581-
*
582-
* NB: we had better have AccessExclusiveLock to do this ...
583-
* ---------------------------------------------------------------------
584-
*/
585-
if (RelisBecomingView)
586-
{
587-
Relation relationRelation;
588-
Oid toastrelid;
589-
HeapTuple classTup;
590-
Form_pg_class classForm;
591-
592-
relationRelation = table_open(RelationRelationId, RowExclusiveLock);
593-
toastrelid = event_relation->rd_rel->reltoastrelid;
594-
595-
/* drop storage while table still looks like a table */
596-
RelationDropStorage(event_relation);
597-
DeleteSystemAttributeTuples(event_relid);
598-
599-
/*
600-
* Drop the toast table if any. (This won't take care of updating the
601-
* toast fields in the relation's own pg_class entry; we handle that
602-
* below.)
603-
*/
604-
if (OidIsValid(toastrelid))
605-
{
606-
ObjectAddress toastobject;
607-
608-
/*
609-
* Delete the dependency of the toast relation on the main
610-
* relation so we can drop the former without dropping the latter.
611-
*/
612-
deleteDependencyRecordsFor(RelationRelationId, toastrelid,
613-
false);
614-
615-
/* Make deletion of dependency record visible */
616-
CommandCounterIncrement();
617-
618-
/* Now drop toast table, including its index */
619-
toastobject.classId = RelationRelationId;
620-
toastobject.objectId = toastrelid;
621-
toastobject.objectSubId = 0;
622-
performDeletion(&toastobject, DROP_RESTRICT,
623-
PERFORM_DELETION_INTERNAL);
624-
}
625-
626-
/*
627-
* SetRelationRuleStatus may have updated the pg_class row, so we must
628-
* advance the command counter before trying to update it again.
629-
*/
630-
CommandCounterIncrement();
631-
632-
/*
633-
* Fix pg_class entry to look like a normal view's, including setting
634-
* the correct relkind and removal of reltoastrelid of the toast table
635-
* we potentially removed above.
636-
*/
637-
classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(event_relid));
638-
if (!HeapTupleIsValid(classTup))
639-
elog(ERROR, "cache lookup failed for relation %u", event_relid);
640-
classForm = (Form_pg_class) GETSTRUCT(classTup);
641-
642-
classForm->relam = InvalidOid;
643-
classForm->reltablespace = InvalidOid;
644-
classForm->relpages = 0;
645-
classForm->reltuples = -1;
646-
classForm->relallvisible = 0;
647-
classForm->reltoastrelid = InvalidOid;
648-
classForm->relhasindex = false;
649-
classForm->relkind = RELKIND_VIEW;
650-
classForm->relfrozenxid = InvalidTransactionId;
651-
classForm->relminmxid = InvalidMultiXactId;
652-
classForm->relreplident = REPLICA_IDENTITY_NOTHING;
653-
654-
CatalogTupleUpdate(relationRelation, &classTup->t_self, classTup);
655-
656-
heap_freetuple(classTup);
657-
table_close(relationRelation, RowExclusiveLock);
658-
}
659-
660495
ObjectAddressSet(address, RewriteRelationId, ruleId);
661496

662497
/* Close rel, but keep lock till commit... */

src/test/modules/test_ddl_deparse/expected/create_rule.out

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
---
22
--- CREATE_RULE
33
---
4+
--- Note: views' ON SELECT rules are tested elsewhere.
5+
---
46
CREATE RULE rule_1 AS
57
ON INSERT
68
TO datatype_table
@@ -16,12 +18,6 @@ CREATE RULE rule_3 AS
1618
TO datatype_table
1719
DO ALSO NOTHING;
1820
NOTICE: DDL test: type simple, tag CREATE RULE
19-
CREATE RULE "_RETURN" AS
20-
ON SELECT
21-
TO like_datatype_table
22-
DO INSTEAD
23-
SELECT * FROM datatype_view;
24-
NOTICE: DDL test: type simple, tag CREATE RULE
2521
CREATE RULE rule_3 AS
2622
ON DELETE
2723
TO like_datatype_table

src/test/modules/test_ddl_deparse/sql/create_rule.sql

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
---
22
--- CREATE_RULE
33
---
4+
--- Note: views' ON SELECT rules are tested elsewhere.
5+
---
46

57

68
CREATE RULE rule_1 AS
@@ -18,12 +20,6 @@ CREATE RULE rule_3 AS
1820
TO datatype_table
1921
DO ALSO NOTHING;
2022

21-
CREATE RULE "_RETURN" AS
22-
ON SELECT
23-
TO like_datatype_table
24-
DO INSTEAD
25-
SELECT * FROM datatype_view;
26-
2723
CREATE RULE rule_3 AS
2824
ON DELETE
2925
TO like_datatype_table

src/test/regress/expected/rowsecurity.out

-22
Original file line numberDiff line numberDiff line change
@@ -3942,28 +3942,6 @@ DROP ROLE regress_rls_frank; -- succeeds
39423942
ROLLBACK TO q;
39433943
ROLLBACK; -- cleanup
39443944
--
3945-
-- Converting table to view
3946-
--
3947-
BEGIN;
3948-
CREATE TABLE t (c int);
3949-
CREATE POLICY p ON t USING (c % 2 = 1);
3950-
ALTER TABLE t ENABLE ROW LEVEL SECURITY;
3951-
SAVEPOINT q;
3952-
CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD
3953-
SELECT * FROM generate_series(1,5) t0(c); -- fails due to row-level security enabled
3954-
ERROR: could not convert table "t" to a view because it has row security enabled
3955-
ROLLBACK TO q;
3956-
ALTER TABLE t DISABLE ROW LEVEL SECURITY;
3957-
SAVEPOINT q;
3958-
CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD
3959-
SELECT * FROM generate_series(1,5) t0(c); -- fails due to policy p on t
3960-
ERROR: could not convert table "t" to a view because it has row security policies
3961-
ROLLBACK TO q;
3962-
DROP POLICY p ON t;
3963-
CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD
3964-
SELECT * FROM generate_series(1,5) t0(c); -- succeeds
3965-
ROLLBACK;
3966-
--
39673945
-- Policy expression handling
39683946
--
39693947
BEGIN;

0 commit comments

Comments
 (0)