Commit a99c42f2 authored by Tom Lane's avatar Tom Lane

Support automatically-updatable views.

This patch makes "simple" views automatically updatable, without the need
to create either INSTEAD OF triggers or INSTEAD rules.  "Simple" views
are those classified as updatable according to SQL-92 rules.  The rewriter
transforms INSERT/UPDATE/DELETE commands on such views directly into an
equivalent command on the underlying table, which will generally have
noticeably better performance than is possible with either triggers or
user-written rules.  A view that has INSTEAD OF triggers or INSTEAD rules
continues to operate the same as before.

For the moment, security_barrier views are not considered simple.
Also, we do not support WITH CHECK OPTION.  These features may be
added in future.

Dean Rasheed, reviewed by Amit Kapila
parent d12d9f59
...@@ -110,7 +110,7 @@ ...@@ -110,7 +110,7 @@
<simpara>triggers</simpara> <simpara>triggers</simpara>
</listitem> </listitem>
<listitem> <listitem>
<simpara>views</simpara> <simpara>updatable views</simpara>
</listitem> </listitem>
<listitem> <listitem>
<simpara>transactional integrity</simpara> <simpara>transactional integrity</simpara>
......
...@@ -147,11 +147,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ...@@ -147,11 +147,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
<listitem> <listitem>
<para> <para>
These forms set or remove the default value for a column. These forms set or remove the default value for a column.
The default values only apply to subsequent <command>INSERT</command> Default values only apply in subsequent <command>INSERT</command>
commands; they do not cause rows already in the table to change. or <command>UPDATE</> commands; they do not cause rows already in the
Defaults can also be created for views, in which case they are table to change.
inserted into <command>INSERT</> statements on the view before
the view's <literal>ON INSERT</literal> rule is applied.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -80,10 +80,11 @@ ALTER VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RESET ...@@ -80,10 +80,11 @@ ALTER VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RESET
<listitem> <listitem>
<para> <para>
These forms set or remove the default value for a column. These forms set or remove the default value for a column.
A default value associated with a view column is A view column's default value is substituted into any
inserted into <command>INSERT</> statements on the view before <command>INSERT</> or <command>UPDATE</> command whose target is the
the view's <literal>ON INSERT</literal> rule is applied, if view, before applying any rules or triggers for the view. The view's
the <command>INSERT</> does not specify a value for the column. default will therefore take precedence over any default values from
underlying relations.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -45,10 +45,10 @@ CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS ...@@ -45,10 +45,10 @@ CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS
additional commands to be executed when a given command on a given additional commands to be executed when a given command on a given
table is executed. Alternatively, an <literal>INSTEAD</literal> table is executed. Alternatively, an <literal>INSTEAD</literal>
rule can replace a given command by another, or cause a command rule can replace a given command by another, or cause a command
not to be executed at all. Rules are used to implement table not to be executed at all. Rules are used to implement SQL
views as well. It is important to realize that a rule is really views as well. It is important to realize that a rule is really
a command transformation mechanism, or command macro. The a command transformation mechanism, or command macro. The
transformation happens before the execution of the commands starts. transformation happens before the execution of the command starts.
If you actually want an operation that fires independently for each If you actually want an operation that fires independently for each
physical row, you probably want to use a trigger, not a rule. physical row, you probably want to use a trigger, not a rule.
More information about the rules system is in <xref linkend="rules">. More information about the rules system is in <xref linkend="rules">.
...@@ -73,13 +73,11 @@ CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS ...@@ -73,13 +73,11 @@ CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS
sufficient for your purposes) to replace update actions on the view sufficient for your purposes) to replace update actions on the view
with appropriate updates on other tables. If you want to support with appropriate updates on other tables. If you want to support
<command>INSERT RETURNING</> and so on, then be sure to put a suitable <command>INSERT RETURNING</> and so on, then be sure to put a suitable
<literal>RETURNING</> clause into each of these rules. Alternatively, <literal>RETURNING</> clause into each of these rules.
an updatable view can be implemented using <literal>INSTEAD OF</>
triggers (see <xref linkend="sql-createtrigger">).
</para> </para>
<para> <para>
There is a catch if you try to use conditional rules for view There is a catch if you try to use conditional rules for complex view
updates: there <emphasis>must</> be an unconditional updates: there <emphasis>must</> be an unconditional
<literal>INSTEAD</literal> rule for each action you wish to allow <literal>INSTEAD</literal> rule for each action you wish to allow
on the view. If the rule is conditional, or is not on the view. If the rule is conditional, or is not
...@@ -95,6 +93,21 @@ CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS ...@@ -95,6 +93,21 @@ CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS
<literal>INSTEAD NOTHING</literal> action. (This method does not <literal>INSTEAD NOTHING</literal> action. (This method does not
currently work to support <literal>RETURNING</> queries, however.) currently work to support <literal>RETURNING</> queries, however.)
</para> </para>
<note>
<para>
A view that is simple enough to be automatically updatable (see <xref
linkend="sql-createview">) does not require a user-created rule in
order to be updatable. While you can create an explicit rule anyway,
the automatic update transformation will generally outperform an
explicit rule.
</para>
<para>
Another alternative worth considering is to use <literal>INSTEAD OF</>
triggers (see <xref linkend="sql-createtrigger">) in place of rules.
</para>
</note>
</refsect1> </refsect1>
<refsect1> <refsect1>
......
...@@ -127,17 +127,6 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">n ...@@ -127,17 +127,6 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">n
<refsect1> <refsect1>
<title>Notes</title> <title>Notes</title>
<para>
Currently, views are read only: the system will not allow an insert,
update, or delete on a view. You can get the effect of an updatable
view by creating <literal>INSTEAD</> triggers on the view, which
must convert attempted inserts, etc. on the view into
appropriate actions on other tables. For more information see
<xref linkend="sql-createtrigger">. Another possibility is to create
rules (see <xref linkend="sql-createrule">), but in practice triggers
are easier to understand and use correctly.
</para>
<para> <para>
Use the <xref linkend="sql-dropview"> Use the <xref linkend="sql-dropview">
statement to drop views. statement to drop views.
...@@ -175,6 +164,105 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello; ...@@ -175,6 +164,105 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
to replace it (this includes being a member of the owning role). to replace it (this includes being a member of the owning role).
</para> </para>
<refsect2 id="SQL-CREATEVIEW-updatable-views">
<title id="SQL-CREATEVIEW-updatable-views-title">Updatable Views</title>
<indexterm zone="sql-createview-updatable-views">
<primary>updatable views</primary>
</indexterm>
<para>
Simple views are automatically updatable: the system will allow
<command>INSERT</>, <command>UPDATE</> and <command>DELETE</> statements
to be used on the view in the same way as on a regular table. A view is
automatically updatable if it satisfies all of the following conditions:
<itemizedlist>
<listitem>
<para>
The view must have exactly one entry in its <literal>FROM</> list,
which must be a table or another updatable view.
</para>
</listitem>
<listitem>
<para>
The view definition must not contain <literal>WITH</>,
<literal>DISTINCT</>, <literal>GROUP BY</>, <literal>HAVING</>,
<literal>LIMIT</>, or <literal>OFFSET</> clauses at the top level.
</para>
</listitem>
<listitem>
<para>
The view definition must not contain set operations (<literal>UNION</>,
<literal>INTERSECT</> or <literal>EXCEPT</>) at the top level.
</para>
</listitem>
<listitem>
<para>
All columns in the view's select list must be simple references to
columns of the underlying relation. They cannot be expressions,
literals or functions. System columns cannot be referenced, either.
</para>
</listitem>
<listitem>
<para>
No column of the underlying relation can appear more than once in
the view's select list.
</para>
</listitem>
<listitem>
<para>
The view must not have the <literal>security_barrier</> property.
</para>
</listitem>
</itemizedlist>
</para>
<para>
If the view is automatically updatable the system will convert any
<command>INSERT</>, <command>UPDATE</> or <command>DELETE</> statement
on the view into the corresponding statement on the underlying base
relation.
</para>
<para>
If an automatically updatable view contains a <literal>WHERE</>
condition, the condition restricts which rows of the base relation are
available to be modified by <command>UPDATE</> and <command>DELETE</>
statements on the view. However, an <command>UPDATE</> is allowed to
change a row so that it no longer satisfies the <literal>WHERE</>
condition, and thus is no longer visible through the view. Similarly,
an <command>INSERT</> command can potentially insert base-relation rows
that do not satisfy the <literal>WHERE</> condition and thus are not
visible through the view.
</para>
<para>
A more complex view that does not satisfy all these conditions is
read-only by default: the system will not allow an insert, update, or
delete on the view. You can get the effect of an updatable view by
creating <literal>INSTEAD OF</> triggers on the view, which must
convert attempted inserts, etc. on the view into appropriate actions
on other tables. For more information see <xref
linkend="sql-createtrigger">. Another possibility is to create rules
(see <xref linkend="sql-createrule">), but in practice triggers are
easier to understand and use correctly.
</para>
<para>
Note that the user performing the insert, update or delete on the view
must have the corresponding insert, update or delete privilege on the
view. In addition the view's owner must have the relevant privileges on
the underlying base relations, but the user performing the update does
not need any permissions on the underlying base relations (see
<xref linkend="rules-privileges">).
</para>
</refsect2>
</refsect1> </refsect1>
<refsect1> <refsect1>
...@@ -217,11 +305,15 @@ CREATE VIEW <replaceable class="parameter">name</replaceable> [ ( <replaceable c ...@@ -217,11 +305,15 @@ CREATE VIEW <replaceable class="parameter">name</replaceable> [ ( <replaceable c
<term><literal>CHECK OPTION</literal></term> <term><literal>CHECK OPTION</literal></term>
<listitem> <listitem>
<para> <para>
This option has to do with updatable views. All This option controls the behavior of automatically updatable views.
<command>INSERT</> and <command>UPDATE</> commands on the view When given, <command>INSERT</> and <command>UPDATE</> commands on
will be checked to ensure data satisfy the view-defining the view will be checked to ensure new rows satisfy the
condition (that is, the new data would be visible through the view-defining condition (that is, the new rows would be visible
view). If they do not, the update will be rejected. through the view). If they do not, the update will be rejected.
Without <literal>CHECK OPTION</literal>, <command>INSERT</> and
<command>UPDATE</> commands on the view are allowed to create rows
that are not visible through the view. (The latter behavior is the
only one currently provided by <productname>PostgreSQL</>.)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -252,6 +344,7 @@ CREATE VIEW <replaceable class="parameter">name</replaceable> [ ( <replaceable c ...@@ -252,6 +344,7 @@ CREATE VIEW <replaceable class="parameter">name</replaceable> [ ( <replaceable c
<command>CREATE OR REPLACE VIEW</command> is a <command>CREATE OR REPLACE VIEW</command> is a
<productname>PostgreSQL</productname> language extension. <productname>PostgreSQL</productname> language extension.
So is the concept of a temporary view. So is the concept of a temporary view.
The <literal>WITH</> clause is an extension as well.
</para> </para>
</refsect1> </refsect1>
......
...@@ -808,13 +808,28 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a; ...@@ -808,13 +808,28 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
<para> <para>
What happens if a view is named as the target relation for an What happens if a view is named as the target relation for an
<command>INSERT</command>, <command>UPDATE</command>, or <command>INSERT</command>, <command>UPDATE</command>, or
<command>DELETE</command>? Simply doing the substitutions <command>DELETE</command>? Doing the substitutions
described above would give a query tree in which the result described above would give a query tree in which the result
relation points at a subquery range-table entry, which will not relation points at a subquery range-table entry, which will not
work. Instead, the rewriter assumes that the operation will be work. There are several ways in which <productname>PostgreSQL</>
handled by an <literal>INSTEAD OF</> trigger on the view. can support the appearance of updating a view, however.
(If there is no such trigger, the executor will throw an error </para>
when execution starts.) Rewriting works slightly differently
<para>
If the subquery selects from a single base relation and is simple
enough, the rewriter can automatically replace the subquery with the
underlying base relation so that the <command>INSERT</command>,
<command>UPDATE</command>, or <command>DELETE</command> is applied to
the base relation in the appropriate way. Views that are
<quote>simple enough</> for this are called <firstterm>automatically
updatable</>. For detailed information on the kinds of view that can
be automatically updated, see <xref linkend="sql-createview">.
</para>
<para>
Alternatively, the operation may be handled by a user-provided
<literal>INSTEAD OF</> trigger on the view.
Rewriting works slightly differently
in this case. For <command>INSERT</command>, the rewriter does in this case. For <command>INSERT</command>, the rewriter does
nothing at all with the view, leaving it as the result relation nothing at all with the view, leaving it as the result relation
for the query. For <command>UPDATE</command> and for the query. For <command>UPDATE</command> and
...@@ -842,10 +857,8 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a; ...@@ -842,10 +857,8 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
</para> </para>
<para> <para>
If there are no <literal>INSTEAD OF</> triggers to update the view, Another possibility is for the user to define <literal>INSTEAD</>
the executor will throw an error, because it cannot automatically rules that specify substitute actions for <command>INSERT</command>,
update a view by itself. To change this, we can define rules that
modify the behavior of <command>INSERT</command>,
<command>UPDATE</command>, and <command>DELETE</command> commands on <command>UPDATE</command>, and <command>DELETE</command> commands on
a view. These rules will rewrite the command, typically into a command a view. These rules will rewrite the command, typically into a command
that updates one or more tables, rather than views. That is the topic that updates one or more tables, rather than views. That is the topic
...@@ -860,6 +873,22 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a; ...@@ -860,6 +873,22 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
evaluated first, and depending on the result, the triggers may not be evaluated first, and depending on the result, the triggers may not be
used at all. used at all.
</para> </para>
<para>
Automatic rewriting of an <command>INSERT</command>,
<command>UPDATE</command>, or <command>DELETE</command> query on a
simple view is always tried last. Therefore, if a view has rules or
triggers, they will override the default behavior of automatically
updatable views.
</para>
<para>
If there are no <literal>INSTEAD</> rules or <literal>INSTEAD OF</>
triggers for the view, and the rewriter cannot automatically rewrite
the query as an update on the underlying base relation, an error will
be thrown because the executor cannot update a view as such.
</para>
</sect2> </sect2>
</sect1> </sect1>
......
...@@ -730,10 +730,8 @@ CREATE VIEW columns AS ...@@ -730,10 +730,8 @@ CREATE VIEW columns AS
CAST('NEVER' AS character_data) AS is_generated, CAST('NEVER' AS character_data) AS is_generated,
CAST(null AS character_data) AS generation_expression, CAST(null AS character_data) AS generation_expression,
CAST(CASE WHEN c.relkind = 'r' CAST(CASE WHEN c.relkind = 'r' OR
OR (c.relkind = 'v' (c.relkind = 'v' AND pg_view_is_updatable(c.oid))
AND EXISTS (SELECT 1 FROM pg_rewrite WHERE ev_class = c.oid AND ev_type = '2' AND is_instead)
AND EXISTS (SELECT 1 FROM pg_rewrite WHERE ev_class = c.oid AND ev_type = '4' AND is_instead))
THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_updatable THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_updatable
FROM (pg_attribute a LEFT JOIN pg_attrdef ad ON attrelid = adrelid AND attnum = adnum) FROM (pg_attribute a LEFT JOIN pg_attrdef ad ON attrelid = adrelid AND attnum = adnum)
...@@ -1896,9 +1894,8 @@ CREATE VIEW tables AS ...@@ -1896,9 +1894,8 @@ CREATE VIEW tables AS
CAST(nt.nspname AS sql_identifier) AS user_defined_type_schema, CAST(nt.nspname AS sql_identifier) AS user_defined_type_schema,
CAST(t.typname AS sql_identifier) AS user_defined_type_name, CAST(t.typname AS sql_identifier) AS user_defined_type_name,
CAST(CASE WHEN c.relkind = 'r' CAST(CASE WHEN c.relkind = 'r' OR
OR (c.relkind = 'v' (c.relkind = 'v' AND pg_view_is_insertable(c.oid))
AND EXISTS (SELECT 1 FROM pg_rewrite WHERE ev_class = c.oid AND ev_type = '3' AND is_instead))
THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_insertable_into, THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_insertable_into,
CAST(CASE WHEN t.typname IS NOT NULL THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_typed, CAST(CASE WHEN t.typname IS NOT NULL THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_typed,
...@@ -2497,14 +2494,11 @@ CREATE VIEW views AS ...@@ -2497,14 +2494,11 @@ CREATE VIEW views AS
CAST('NONE' AS character_data) AS check_option, CAST('NONE' AS character_data) AS check_option,
CAST( CAST(
CASE WHEN EXISTS (SELECT 1 FROM pg_rewrite WHERE ev_class = c.oid AND ev_type = '2' AND is_instead) CASE WHEN pg_view_is_updatable(c.oid) THEN 'YES' ELSE 'NO' END
AND EXISTS (SELECT 1 FROM pg_rewrite WHERE ev_class = c.oid AND ev_type = '4' AND is_instead)
THEN 'YES' ELSE 'NO' END
AS yes_or_no) AS is_updatable, AS yes_or_no) AS is_updatable,
CAST( CAST(
CASE WHEN EXISTS (SELECT 1 FROM pg_rewrite WHERE ev_class = c.oid AND ev_type = '3' AND is_instead) CASE WHEN pg_view_is_insertable(c.oid) THEN 'YES' ELSE 'NO' END
THEN 'YES' ELSE 'NO' END
AS yes_or_no) AS is_insertable_into, AS yes_or_no) AS is_insertable_into,
CAST( CAST(
......
...@@ -923,9 +923,8 @@ InitPlan(QueryDesc *queryDesc, int eflags) ...@@ -923,9 +923,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
/* /*
* Check that a proposed result relation is a legal target for the operation * Check that a proposed result relation is a legal target for the operation
* *
* In most cases parser and/or planner should have noticed this already, but * Generally the parser and/or planner should have noticed any such mistake
* let's make sure. In the view case we do need a test here, because if the * already, but let's make sure.
* view wasn't rewritten by a rule, it had better have an INSTEAD trigger.
* *
* Note: when changing this function, you probably also need to look at * Note: when changing this function, you probably also need to look at
* CheckValidRowMarkRel. * CheckValidRowMarkRel.
...@@ -953,6 +952,13 @@ CheckValidResultRel(Relation resultRel, CmdType operation) ...@@ -953,6 +952,13 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
RelationGetRelationName(resultRel)))); RelationGetRelationName(resultRel))));
break; break;
case RELKIND_VIEW: case RELKIND_VIEW:
/*
* Okay only if there's a suitable INSTEAD OF trigger. Messages
* here should match rewriteHandler.c's rewriteTargetView, except
* that we omit errdetail because we haven't got the information
* handy (and given that we really shouldn't get here anyway,
* it's not worth great exertion to get).
*/
switch (operation) switch (operation)
{ {
case CMD_INSERT: case CMD_INSERT:
...@@ -961,7 +967,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation) ...@@ -961,7 +967,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot insert into view \"%s\"", errmsg("cannot insert into view \"%s\"",
RelationGetRelationName(resultRel)), RelationGetRelationName(resultRel)),
errhint("You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger."))); errhint("To make the view insertable, provide an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.")));
break; break;
case CMD_UPDATE: case CMD_UPDATE:
if (!trigDesc || !trigDesc->trig_update_instead_row) if (!trigDesc || !trigDesc->trig_update_instead_row)
...@@ -969,7 +975,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation) ...@@ -969,7 +975,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot update view \"%s\"", errmsg("cannot update view \"%s\"",
RelationGetRelationName(resultRel)), RelationGetRelationName(resultRel)),
errhint("You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger."))); errhint("To make the view updatable, provide an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.")));
break; break;
case CMD_DELETE: case CMD_DELETE:
if (!trigDesc || !trigDesc->trig_delete_instead_row) if (!trigDesc || !trigDesc->trig_delete_instead_row)
...@@ -977,7 +983,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation) ...@@ -977,7 +983,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot delete from view \"%s\"", errmsg("cannot delete from view \"%s\"",
RelationGetRelationName(resultRel)), RelationGetRelationName(resultRel)),
errhint("You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger."))); errhint("To make the view updatable, provide an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.")));
break; break;
default: default:
elog(ERROR, "unrecognized CmdType: %d", (int) operation); elog(ERROR, "unrecognized CmdType: %d", (int) operation);
...@@ -1028,7 +1034,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType) ...@@ -1028,7 +1034,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
RelationGetRelationName(rel)))); RelationGetRelationName(rel))));
break; break;
case RELKIND_VIEW: case RELKIND_VIEW:
/* Should not get here */ /* Should not get here; planner should have expanded the view */
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot lock rows in view \"%s\"", errmsg("cannot lock rows in view \"%s\"",
......
This diff is collapsed.
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include "miscadmin.h" #include "miscadmin.h"
#include "parser/keywords.h" #include "parser/keywords.h"
#include "postmaster/syslogger.h" #include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h" #include "storage/fd.h"
#include "storage/pmsignal.h" #include "storage/pmsignal.h"
#include "storage/proc.h" #include "storage/proc.h"
...@@ -523,3 +524,33 @@ pg_collation_for(PG_FUNCTION_ARGS) ...@@ -523,3 +524,33 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_NULL(); PG_RETURN_NULL();
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid))); PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
} }
/*
* information_schema support functions
*
* Test whether a view (identified by pg_class OID) is insertable-into or
* updatable. The latter requires delete capability too. This is an
* artifact of the way the SQL standard defines the information_schema views:
* if we defined separate functions for update and delete, we'd double the
* work required to compute the view columns.
*
* These rely on relation_is_updatable(), which is in rewriteHandler.c.
*/
Datum
pg_view_is_insertable(PG_FUNCTION_ARGS)
{
Oid viewoid = PG_GETARG_OID(0);
int req_events = (1 << CMD_INSERT);
PG_RETURN_BOOL(relation_is_updatable(viewoid, req_events));
}
Datum
pg_view_is_updatable(PG_FUNCTION_ARGS)
{
Oid viewoid = PG_GETARG_OID(0);
int req_events = (1 << CMD_UPDATE) | (1 << CMD_DELETE);
PG_RETURN_BOOL(relation_is_updatable(viewoid, req_events));
}
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201211281 #define CATALOG_VERSION_NO 201212081
#endif #endif
...@@ -1976,6 +1976,11 @@ DESCR("type of the argument"); ...@@ -1976,6 +1976,11 @@ DESCR("type of the argument");
DATA(insert OID = 3162 ( pg_collation_for PGNSP PGUID 12 1 0 0 0 f f f f f f s 1 0 25 "2276" _null_ _null_ _null_ _null_ pg_collation_for _null_ _null_ _null_ )); DATA(insert OID = 3162 ( pg_collation_for PGNSP PGUID 12 1 0 0 0 f f f f f f s 1 0 25 "2276" _null_ _null_ _null_ _null_ pg_collation_for _null_ _null_ _null_ ));
DESCR("collation of the argument; implementation of the COLLATION FOR expression"); DESCR("collation of the argument; implementation of the COLLATION FOR expression");
DATA(insert OID = 3842 ( pg_view_is_insertable PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_view_is_insertable _null_ _null_ _null_ ));
DESCR("is a view insertable-into");
DATA(insert OID = 3843 ( pg_view_is_updatable PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_view_is_updatable _null_ _null_ _null_ ));
DESCR("is a view updatable");
/* Deferrable unique constraint trigger */ /* Deferrable unique constraint trigger */
DATA(insert OID = 1250 ( unique_key_recheck PGNSP PGUID 12 1 0 0 0 f f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_recheck _null_ _null_ _null_ )); DATA(insert OID = 1250 ( unique_key_recheck PGNSP PGUID 12 1 0 0 0 f f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_recheck _null_ _null_ _null_ ));
DESCR("deferred UNIQUE constraint check"); DESCR("deferred UNIQUE constraint check");
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
extern List *QueryRewrite(Query *parsetree); extern List *QueryRewrite(Query *parsetree);
extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown); extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
extern Node *build_column_default(Relation rel, int attrno); extern Node *build_column_default(Relation rel, int attrno);
extern bool relation_is_updatable(Oid reloid, int req_events);
#endif /* REWRITEHANDLER_H */ #endif /* REWRITEHANDLER_H */
...@@ -482,6 +482,8 @@ extern Datum pg_sleep(PG_FUNCTION_ARGS); ...@@ -482,6 +482,8 @@ extern Datum pg_sleep(PG_FUNCTION_ARGS);
extern Datum pg_get_keywords(PG_FUNCTION_ARGS); extern Datum pg_get_keywords(PG_FUNCTION_ARGS);
extern Datum pg_typeof(PG_FUNCTION_ARGS); extern Datum pg_typeof(PG_FUNCTION_ARGS);
extern Datum pg_collation_for(PG_FUNCTION_ARGS); extern Datum pg_collation_for(PG_FUNCTION_ARGS);
extern Datum pg_view_is_insertable(PG_FUNCTION_ARGS);
extern Datum pg_view_is_updatable(PG_FUNCTION_ARGS);
/* oid.c */ /* oid.c */
extern Datum oidin(PG_FUNCTION_ARGS); extern Datum oidin(PG_FUNCTION_ARGS);
......
...@@ -820,20 +820,6 @@ DROP TABLE min_updates_test_oids; ...@@ -820,20 +820,6 @@ DROP TABLE min_updates_test_oids;
-- Test triggers on views -- Test triggers on views
-- --
CREATE VIEW main_view AS SELECT a, b FROM main_table; CREATE VIEW main_view AS SELECT a, b FROM main_table;
-- Updates should fail without rules or triggers
INSERT INTO main_view VALUES (1,2);
ERROR: cannot insert into view "main_view"
HINT: You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.
UPDATE main_view SET b = 20 WHERE a = 50;
ERROR: cannot update view "main_view"
HINT: You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.
DELETE FROM main_view WHERE a = 50;
ERROR: cannot delete from view "main_view"
HINT: You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
-- Should fail even when there are no matching rows
DELETE FROM main_view WHERE a = 51;
ERROR: cannot delete from view "main_view"
HINT: You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
-- VIEW trigger function -- VIEW trigger function
CREATE OR REPLACE FUNCTION view_trigger() RETURNS trigger CREATE OR REPLACE FUNCTION view_trigger() RETURNS trigger
LANGUAGE plpgsql AS $$ LANGUAGE plpgsql AS $$
......
This diff is collapsed.
...@@ -59,7 +59,7 @@ test: create_index create_view ...@@ -59,7 +59,7 @@ test: create_index create_view
# ---------- # ----------
# Another group of parallel tests # Another group of parallel tests
# ---------- # ----------
test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views
# ---------- # ----------
# sanity_check does a vacuum, affecting the sort order of SELECT * # sanity_check does a vacuum, affecting the sort order of SELECT *
......
...@@ -67,6 +67,7 @@ test: create_table_like ...@@ -67,6 +67,7 @@ test: create_table_like
test: typed_table test: typed_table
test: vacuum test: vacuum
test: drop_if_exists test: drop_if_exists
test: updatable_views
test: sanity_check test: sanity_check
test: errors test: errors
test: select test: select
......
...@@ -611,13 +611,6 @@ DROP TABLE min_updates_test_oids; ...@@ -611,13 +611,6 @@ DROP TABLE min_updates_test_oids;
CREATE VIEW main_view AS SELECT a, b FROM main_table; CREATE VIEW main_view AS SELECT a, b FROM main_table;
-- Updates should fail without rules or triggers
INSERT INTO main_view VALUES (1,2);
UPDATE main_view SET b = 20 WHERE a = 50;
DELETE FROM main_view WHERE a = 50;
-- Should fail even when there are no matching rows
DELETE FROM main_view WHERE a = 51;
-- VIEW trigger function -- VIEW trigger function
CREATE OR REPLACE FUNCTION view_trigger() RETURNS trigger CREATE OR REPLACE FUNCTION view_trigger() RETURNS trigger
LANGUAGE plpgsql AS $$ LANGUAGE plpgsql AS $$
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment