Commit 2ec993a7 authored by Tom Lane's avatar Tom Lane

Support triggers on views.

This patch adds the SQL-standard concept of an INSTEAD OF trigger, which
is fired instead of performing a physical insert/update/delete.  The
trigger function is passed the entire old and/or new rows of the view,
and must figure out what to do to the underlying tables to implement
the update.  So this feature can be used to implement updatable views
using trigger programming style rather than rule hacking.

In passing, this patch corrects the names of some columns in the
information_schema.triggers view.  It seems the SQL committee renamed
them somewhere between SQL:99 and SQL:2003.

Dean Rasheed, reviewed by Bernd Helmle; some additional hacking by me.
parent f7b15b50
......@@ -4243,7 +4243,7 @@
<para>
The catalog <structname>pg_seclabel</structname> stores security
labels on database objects. See the
labels on database objects. See the
<xref linkend="sql-security-label"> statement.
</para>
......@@ -4795,7 +4795,8 @@
</indexterm>
<para>
The catalog <structname>pg_trigger</structname> stores triggers on tables.
The catalog <structname>pg_trigger</structname> stores triggers on tables
and views.
See <xref linkend="sql-createtrigger">
for more information.
</para>
......@@ -4839,7 +4840,7 @@
<entry><structfield>tgtype</structfield></entry>
<entry><type>int2</type></entry>
<entry></entry>
<entry>Bit mask identifying trigger conditions</entry>
<entry>Bit mask identifying trigger firing conditions</entry>
</row>
<row>
......@@ -4956,7 +4957,7 @@
<note>
<para>
<literal>pg_class.relhastriggers</literal>
must be true if a table has any triggers in this catalog.
must be true if a relation has any triggers in this catalog.
</para>
</note>
......
......@@ -4885,8 +4885,8 @@ ORDER BY c.ordinal_position;
<para>
The view <literal>triggers</literal> contains all triggers defined
in the current database on tables that the current user owns or has
some non-SELECT privilege on.
in the current database on tables and views that the current user owns
or has some non-SELECT privilege on.
</para>
<table>
......@@ -4987,34 +4987,34 @@ ORDER BY c.ordinal_position;
</row>
<row>
<entry><literal>condition_timing</literal></entry>
<entry><literal>action_timing</literal></entry>
<entry><type>character_data</type></entry>
<entry>
Time at which the trigger fires (<literal>BEFORE</literal> or
<literal>AFTER</literal>)
Time at which the trigger fires (<literal>BEFORE</literal>,
<literal>AFTER</literal>, or <literal>INSTEAD OF</literal>)
</entry>
</row>
<row>
<entry><literal>condition_reference_old_table</literal></entry>
<entry><literal>action_reference_old_table</literal></entry>
<entry><type>sql_identifier</type></entry>
<entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
</row>
<row>
<entry><literal>condition_reference_new_table</literal></entry>
<entry><literal>action_reference_new_table</literal></entry>
<entry><type>sql_identifier</type></entry>
<entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
</row>
<row>
<entry><literal>condition_reference_old_row</literal></entry>
<entry><literal>action_reference_old_row</literal></entry>
<entry><type>sql_identifier</type></entry>
<entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
</row>
<row>
<entry><literal>condition_reference_new_row</literal></entry>
<entry><literal>action_reference_new_row</literal></entry>
<entry><type>sql_identifier</type></entry>
<entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
</row>
......@@ -5032,9 +5032,9 @@ ORDER BY c.ordinal_position;
Triggers in <productname>PostgreSQL</productname> have two
incompatibilities with the SQL standard that affect the
representation in the information schema. First, trigger names are
local to the table in <productname>PostgreSQL</productname>, rather
local to each table in <productname>PostgreSQL</productname>, rather
than being independent schema objects. Therefore there can be duplicate
trigger names defined in one schema, as long as they belong to
trigger names defined in one schema, so long as they belong to
different tables. (<literal>trigger_catalog</literal> and
<literal>trigger_schema</literal> are really the values pertaining
to the table that the trigger is defined on.) Second, triggers can
......@@ -5045,14 +5045,34 @@ ORDER BY c.ordinal_position;
multiple rows in the information schema, one for each type of
event. As a consequence of these two issues, the primary key of
the view <literal>triggers</literal> is really
<literal>(trigger_catalog, trigger_schema, trigger_name,
event_object_table, event_manipulation)</literal> instead of
<literal>(trigger_catalog, trigger_schema, event_object_table,
trigger_name, event_manipulation)</literal> instead of
<literal>(trigger_catalog, trigger_schema, trigger_name)</literal>,
which is what the SQL standard specifies. Nonetheless, if you
define your triggers in a manner that conforms with the SQL
standard (trigger names unique in the schema and only one event
type per trigger), this will not affect you.
</para>
<note>
<para>
Prior to <productname>PostgreSQL</> 9.1, this view's columns
<structfield>action_timing</structfield>,
<structfield>action_reference_old_table</structfield>,
<structfield>action_reference_new_table</structfield>,
<structfield>action_reference_old_row</structfield>, and
<structfield>action_reference_new_row</structfield>
were named
<structfield>condition_timing</structfield>,
<structfield>condition_reference_old_table</structfield>,
<structfield>condition_reference_new_table</structfield>,
<structfield>condition_reference_old_row</structfield>, and
<structfield>condition_reference_new_row</structfield>
respectively.
That was how they were named in the SQL:1999 standard.
The new naming conforms to SQL:2003 and later.
</para>
</note>
</sect1>
<sect1 id="infoschema-usage-privileges">
......@@ -5562,19 +5582,28 @@ ORDER BY c.ordinal_position;
<row>
<entry><literal>is_trigger_updatable</literal></entry>
<entry><type>yes_or_no</type></entry>
<entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
<entry>
<literal>YES</> if the view has an <literal>INSTEAD OF</>
<command>UPDATE</> trigger defined on it, <literal>NO</> if not
</entry>
</row>
<row>
<entry><literal>is_trigger_deletable</literal></entry>
<entry><type>yes_or_no</type></entry>
<entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
<entry>
<literal>YES</> if the view has an <literal>INSTEAD OF</>
<command>DELETE</> trigger defined on it, <literal>NO</> if not
</entry>
</row>
<row>
<entry><literal>is_trigger_insertable_into</literal></entry>
<entry><type>yes_or_no</type></entry>
<entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
<entry>
<literal>YES</> if the view has an <literal>INSTEAD OF</>
<command>INSERT</> trigger defined on it, <literal>NO</> if not
</entry>
</row>
</tbody>
</tgroup>
......
......@@ -999,7 +999,9 @@ $$ LANGUAGE plperl;
<term><literal>$_TD-&gt;{when}</literal></term>
<listitem>
<para>
When the trigger was called: <literal>BEFORE</literal>, <literal>AFTER</literal>, or <literal>UNKNOWN</literal>
When the trigger was called: <literal>BEFORE</literal>,
<literal>AFTER</literal>, <literal>INSTEAD OF</literal>, or
<literal>UNKNOWN</literal>
</para>
</listitem>
</varlistentry>
......
......@@ -3112,9 +3112,9 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
<term><varname>TG_WHEN</varname></term>
<listitem>
<para>
Data type <type>text</type>; a string of either
<literal>BEFORE</literal> or <literal>AFTER</literal>
depending on the trigger's definition.
Data type <type>text</type>; a string of
<literal>BEFORE</literal>, <literal>AFTER</literal>, or
<literal>INSTEAD OF</literal>, depending on the trigger's definition.
</para>
</listitem>
</varlistentry>
......@@ -3234,8 +3234,25 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
effect, but it has to be nonnull to allow the trigger action to
proceed. Note that <varname>NEW</varname> is null
in <command>DELETE</command> triggers, so returning that is
usually not sensible. A useful idiom in <command>DELETE</command>
triggers might be to return <varname>OLD</varname>.
usually not sensible. The usual idiom in <command>DELETE</command>
triggers is to return <varname>OLD</varname>.
</para>
<para>
<literal>INSTEAD OF</> triggers (which are always row-level triggers,
and may only be used on views) can return null to signal that they did
not perform any updates, and that the rest of the operation for this
row should be skipped (i.e., subsequent triggers are not fired, and the
row is not counted in the rows-affected status for the surrounding
<command>INSERT</>/<command>UPDATE</>/<command>DELETE</>).
Otherwise a nonnull value should be returned, to signal
that the trigger performed the requested operation. For
<command>INSERT</> and <command>UPDATE</> operations, the return value
should be <varname>NEW</>, which the trigger function may modify to
support <command>INSERT RETURNING</> and <command>UPDATE RETURNING</>
(this will also affect the row value passed to any subsequent triggers).
For <command>DELETE</> operations, the return value should be
<varname>OLD</>.
</para>
<para>
......@@ -3354,6 +3371,85 @@ AFTER INSERT OR UPDATE OR DELETE ON emp
</programlisting>
</example>
<para>
A variation of the previous example uses a view joining the main table
to the audit table, to show when each entry was last modified. This
approach still records the full audit trail of changes to the table,
but also presents a simplified view of the audit trail, showing just
the last modified timestamp derived from the audit trail for each entry.
<xref linkend="plpgsql-view-trigger-audit-example"> shows an example
of an audit trigger on a view in <application>PL/pgSQL</application>.
</para>
<example id="plpgsql-view-trigger-audit-example">
<title>A <application>PL/pgSQL</application> View Trigger Procedure For Auditing</title>
<para>
This example uses a trigger on the view to make it updatable, and
ensure that any insert, update or delete of a row in the view is
recorded (i.e., audited) in the emp_audit table. The current time
and user name are recorded, together with the type of operation
performed, and the view displays the last modified time of each row.
</para>
<programlisting>
CREATE TABLE emp (
empname text PRIMARY KEY,
salary integer
);
CREATE TABLE emp_audit(
operation char(1) NOT NULL,
userid text NOT NULL,
empname text NOT NULL,
salary integer,
stamp timestamp NOT NULL
);
CREATE VIEW emp_view AS
SELECT e.empname,
e.salary,
max(ea.stamp) AS last_updated
FROM emp e
LEFT JOIN emp_audit ea ON ea.empname = e.empname
GROUP BY 1, 2;
CREATE OR REPLACE FUNCTION update_emp_view() RETURNS TRIGGER AS $$
BEGIN
--
-- Perform the required operation on emp, and create a row in emp_audit
-- to reflect the change made to emp.
--
IF (TG_OP = 'DELETE') THEN
DELETE FROM emp WHERE empname = OLD.empname;
IF NOT FOUND THEN RETURN NULL; END IF;
OLD.last_updated = now();
INSERT INTO emp_audit VALUES('D', user, OLD.*);
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE emp SET salary = NEW.salary WHERE empname = OLD.empname;
IF NOT FOUND THEN RETURN NULL; END IF;
NEW.last_updated = now();
INSERT INTO emp_audit VALUES('U', user, NEW.*);
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp VALUES(NEW.empname, NEW.salary);
NEW.last_updated = now();
INSERT INTO emp_audit VALUES('I', user, NEW.*);
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER emp_audit
INSTEAD OF INSERT OR UPDATE OR DELETE ON emp_view
FOR EACH ROW EXECUTE PROCEDURE update_emp_view();
</programlisting>
</example>
<para>
One use of triggers is to maintain a summary table
of another table. The resulting summary can be used in place of the
......
......@@ -609,7 +609,7 @@ CREATE TYPE greeting AS (
who text
);
</programlisting>
A set result can be returned from a:
<variablelist>
......@@ -751,8 +751,7 @@ $$ LANGUAGE plpythonu;
<para>
contains the event as a string:
<literal>INSERT</>, <literal>UPDATE</>,
<literal>DELETE</>, <literal>TRUNCATE</>,
or <literal>UNKNOWN</>.
<literal>DELETE</>, or <literal>TRUNCATE</>.
</para>
</listitem>
</varlistentry>
......@@ -761,8 +760,8 @@ $$ LANGUAGE plpythonu;
<term><literal>TD["when"]</></term>
<listitem>
<para>
contains one of <literal>BEFORE</>, <literal>AFTER</>,
or <literal>UNKNOWN</>.
contains one of <literal>BEFORE</>, <literal>AFTER</>, or
<literal>INSTEAD OF</>.
</para>
</listitem>
</varlistentry>
......@@ -771,8 +770,7 @@ $$ LANGUAGE plpythonu;
<term><literal>TD["level"]</></term>
<listitem>
<para>
contains one of <literal>ROW</>,
<literal>STATEMENT</>, or <literal>UNKNOWN</>.
contains <literal>ROW</> or <literal>STATEMENT</>.
</para>
</listitem>
</varlistentry>
......@@ -838,12 +836,14 @@ $$ LANGUAGE plpythonu;
</para>
<para>
If <literal>TD["when"]</literal> is <literal>BEFORE</> and
If <literal>TD["when"]</literal> is <literal>BEFORE</> or
<literal>INSTEAD OF</> and
<literal>TD["level"]</literal> is <literal>ROW</>, you can
return <literal>None</literal> or <literal>"OK"</literal> from the
Python function to indicate the row is unmodified,
<literal>"SKIP"</> to abort the event, or <literal>"MODIFY"</> to
indicate you've modified the row.
<literal>"SKIP"</> to abort the event, or if <literal>TD["event"]</>
is <command>INSERT</> or <command>UPDATE</> you can return
<literal>"MODIFY"</> to indicate you've modified the new row.
Otherwise the return value is ignored.
</para>
</sect1>
......
......@@ -591,8 +591,8 @@ SELECT 'doesn''t' AS ret
<term><varname>$TG_when</varname></term>
<listitem>
<para>
The string <literal>BEFORE</> or <literal>AFTER</> depending on the
type of trigger event.
The string <literal>BEFORE</>, <literal>AFTER</>, or
<literal>INSTEAD OF</>, depending on the type of trigger event.
</para>
</listitem>
</varlistentry>
......@@ -665,10 +665,14 @@ SELECT 'doesn''t' AS ret
the operation (<command>INSERT</>/<command>UPDATE</>/<command>DELETE</>) that fired the trigger will proceed
normally. <literal>SKIP</> tells the trigger manager to silently suppress
the operation for this row. If a list is returned, it tells PL/Tcl to
return a modified row to the trigger manager that will be inserted
instead of the one given in <varname>$NEW</>. (This works for <command>INSERT</> and <command>UPDATE</>
only.) Needless to say that all this is only meaningful when the trigger
is <literal>BEFORE</> and <command>FOR EACH ROW</>; otherwise the return value is ignored.
return a modified row to the trigger manager. This is only meaningful
for row-level <literal>BEFORE</> <command>INSERT</> or <command>UPDATE</>
triggers for which the modified row will be inserted instead of the one
given in <varname>$NEW</>; or for row-level <literal>INSTEAD OF</>
<command>INSERT</> or <command>UPDATE</> triggers where the returned row
is used to support <command>INSERT RETURNING</> and
<command>UPDATE RETURNING</> commands. The return value is ignored for
other types of triggers.
</para>
<para>
......
......@@ -53,7 +53,7 @@ CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS
physical row, you probably want to use a trigger, not a rule.
More information about the rules system is in <xref linkend="rules">.
</para>
<para>
Presently, <literal>ON SELECT</literal> rules must be unconditional
<literal>INSTEAD</literal> rules and must have actions that consist
......@@ -73,7 +73,9 @@ CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS
sufficient for your purposes) to replace update actions on the view
with appropriate updates on other tables. If you want to support
<command>INSERT RETURNING</> and so on, then be sure to put a suitable
<literal>RETURNING</> clause into each of these rules.
<literal>RETURNING</> clause into each of these rules. Alternatively,
an updatable view can be implemented using <literal>INSTEAD OF</>
triggers (see <xref linkend="sql-createtrigger">).
</para>
<para>
......@@ -232,12 +234,12 @@ CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS
<programlisting>
CREATE RULE "_RETURN" AS
ON SELECT TO t1
DO INSTEAD
DO INSTEAD
SELECT * FROM t2;
CREATE RULE "_RETURN" AS
ON SELECT TO t2
DO INSTEAD
DO INSTEAD
SELECT * FROM t1;
SELECT * FROM t1;
......
......@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTER } { <replaceable class="PARAMETER">event</replaceable> [ OR ... ] }
CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="PARAMETER">event</replaceable> [ OR ... ] }
ON <replaceable class="PARAMETER">table</replaceable> [ FOR [ EACH ] { ROW | STATEMENT } ]
[ WHEN ( <replaceable class="parameter">condition</replaceable> ) ]
EXECUTE PROCEDURE <replaceable class="PARAMETER">function_name</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
......@@ -33,21 +33,22 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
<para>
<command>CREATE TRIGGER</command> creates a new trigger. The
trigger will be associated with the specified table and will
trigger will be associated with the specified table or view and will
execute the specified function <replaceable
class="parameter">function_name</replaceable> when certain events occur.
</para>
<para>
The trigger can be specified to fire either before the
The trigger can be specified to fire before the
operation is attempted on a row (before constraints are checked and
the <command>INSERT</command>, <command>UPDATE</command>, or
<command>DELETE</command> is attempted) or after the operation has
<command>DELETE</command> is attempted); or after the operation has
completed (after constraints are checked and the
<command>INSERT</command>, <command>UPDATE</command>, or
<command>DELETE</command> has completed). If the trigger fires
before the event, the trigger can skip the operation for the
current row, or change the row being inserted (for
<command>DELETE</command> has completed); or instead of the operation
(in the case of inserts, updates or deletes on a view).
If the trigger fires before or instead of the event, the trigger can skip
the operation for the current row, or change the row being inserted (for
<command>INSERT</command> and <command>UPDATE</command> operations
only). If the trigger fires after the event, all changes, including
the effects of other triggers, are <quote>visible</quote>
......@@ -68,11 +69,71 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
</para>
<para>
In addition, triggers may be defined to fire for a
Triggers that are specified to fire <literal>INSTEAD OF</> the trigger
event must be marked <literal>FOR EACH ROW</>, and can only be defined
on views. <literal>BEFORE</> and <literal>AFTER</> triggers on a view
must be marked as <literal>FOR EACH STATEMENT</>.
</para>
<para>
In addition, triggers may be defined to fire for
<command>TRUNCATE</command>, though only
<literal>FOR EACH STATEMENT</literal>.
</para>
<para>
The following table summarizes which types of triggers may be used on
tables and views:
</para>
<informaltable id="supported-trigger-types">
<tgroup cols="4">
<thead>
<row>
<entry>When</entry>
<entry>Event</entry>
<entry>Row-level</entry>
<entry>Statement-level</entry>
</row>
</thead>
<tbody>
<row>
<entry align="center" morerows="1"><literal>BEFORE</></entry>
<entry align="center"><command>INSERT</>/<command>UPDATE</>/<command>DELETE</></entry>
<entry align="center">Tables</entry>
<entry align="center">Tables and views</entry>
</row>
<row>
<entry align="center"><command>TRUNCATE</></entry>
<entry align="center">&mdash;</entry>
<entry align="center">Tables</entry>
</row>
<row>
<entry align="center" morerows="1"><literal>AFTER</></entry>
<entry align="center"><command>INSERT</>/<command>UPDATE</>/<command>DELETE</></entry>
<entry align="center">Tables</entry>
<entry align="center">Tables and views</entry>
</row>
<row>
<entry align="center"><command>TRUNCATE</></entry>
<entry align="center">&mdash;</entry>
<entry align="center">Tables</entry>
</row>
<row>
<entry align="center" morerows="1"><literal>INSTEAD OF</></entry>
<entry align="center"><command>INSERT</>/<command>UPDATE</>/<command>DELETE</></entry>
<entry align="center">Views</entry>
<entry align="center">&mdash;</entry>
</row>
<row>
<entry align="center"><command>TRUNCATE</></entry>
<entry align="center">&mdash;</entry>
<entry align="center">&mdash;</entry>
</row>
</tbody>
</tgroup>
</informaltable>
<para>
Also, a trigger definition can specify a Boolean <literal>WHEN</>
condition, which will be tested to see whether the trigger should
......@@ -116,10 +177,11 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
<varlistentry>
<term><literal>BEFORE</literal></term>
<term><literal>AFTER</literal></term>
<term><literal>INSTEAD OF</literal></term>
<listitem>
<para>
Determines whether the function is called before or after the
event.
Determines whether the function is called before, after, or instead of
the event.
</para>
</listitem>
</varlistentry>
......@@ -143,6 +205,10 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
The trigger will only fire if at least one of the listed columns
is mentioned as a target of the update.
</para>
<para>
<literal>UPDATE INSTEAD OF</> triggers do not support lists of columns.
</para>
</listitem>
</varlistentry>
......@@ -150,7 +216,7 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
<term><replaceable class="parameter">table</replaceable></term>
<listitem>
<para>
The name (optionally schema-qualified) of the table the trigger
The name (optionally schema-qualified) of the table or view the trigger
is for.
</para>
</listitem>
......@@ -188,6 +254,11 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
and <literal>DELETE</> triggers cannot refer to <literal>NEW</>.
</para>
<para>
<literal>INSTEAD OF</> triggers do not support <literal>WHEN</>
conditions.
</para>
<para>
Currently, <literal>WHEN</literal> expressions cannot contain
subqueries.
......@@ -326,6 +397,16 @@ CREATE TRIGGER log_update
WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE PROCEDURE log_account_update();
</programlisting>
Execute the function <function>view_insert_row</> for each row to insert
rows into the tables underlying a view:
<programlisting>
CREATE TRIGGER view_insert
INSTEAD OF INSERT ON my_view
FOR EACH ROW
EXECUTE PROCEDURE view_insert_row();
</programlisting>
</para>
<para>
......@@ -396,7 +477,8 @@ CREATE TRIGGER log_update
<para>
The ability to fire triggers for <command>TRUNCATE</command> is a
<productname>PostgreSQL</> extension of the SQL standard.
<productname>PostgreSQL</> extension of the SQL standard, as is the
ability to define statement-level triggers on views.
</para>
</refsect1>
......
This diff is collapsed.
This diff is collapsed.
......@@ -824,8 +824,8 @@ index_create(Oid heapRelationId,
trigger->relation = heapRel;
trigger->funcname = SystemFuncName("unique_key_recheck");
trigger->args = NIL;
trigger->before = false;
trigger->row = true;
trigger->timing = TRIGGER_TYPE_AFTER;
trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
trigger->columns = NIL;
trigger->whenClause = NULL;
......
......@@ -1934,18 +1934,22 @@ CREATE VIEW triggers AS
position('EXECUTE PROCEDURE' in substring(pg_get_triggerdef(t.oid) from 48)) + 47)
AS character_data) AS action_statement,
CAST(
CASE WHEN t.tgtype & 1 = 1 THEN 'ROW' ELSE 'STATEMENT' END
-- hard-wired reference to TRIGGER_TYPE_ROW
CASE t.tgtype & 1 WHEN 1 THEN 'ROW' ELSE 'STATEMENT' END
AS character_data) AS action_orientation,
CAST(
CASE WHEN t.tgtype & 2 = 2 THEN 'BEFORE' ELSE 'AFTER' END
AS character_data) AS condition_timing,
CAST(null AS sql_identifier) AS condition_reference_old_table,
CAST(null AS sql_identifier) AS condition_reference_new_table,
CAST(null AS sql_identifier) AS condition_reference_old_row,
CAST(null AS sql_identifier) AS condition_reference_new_row,
-- hard-wired refs to TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_INSTEAD
CASE t.tgtype & 66 WHEN 2 THEN 'BEFORE' WHEN 64 THEN 'INSTEAD OF' ELSE 'AFTER' END
AS character_data) AS action_timing,
CAST(null AS sql_identifier) AS action_reference_old_table,
CAST(null AS sql_identifier) AS action_reference_new_table,
CAST(null AS sql_identifier) AS action_reference_old_row,
CAST(null AS sql_identifier) AS action_reference_new_row,
CAST(null AS time_stamp) AS created
FROM pg_namespace n, pg_class c, pg_trigger t,
-- hard-wired refs to TRIGGER_TYPE_INSERT, TRIGGER_TYPE_DELETE,
-- TRIGGER_TYPE_UPDATE; we intentionally omit TRIGGER_TYPE_TRUNCATE
(VALUES (4, 'INSERT'),
(8, 'DELETE'),
(16, 'UPDATE')) AS em (num, text)
......@@ -2233,9 +2237,23 @@ CREATE VIEW views AS
THEN 'YES' ELSE 'NO' END
AS yes_or_no) AS is_insertable_into,
CAST('NO' AS yes_or_no) AS is_trigger_updatable,
CAST('NO' AS yes_or_no) AS is_trigger_deletable,
CAST('NO' AS yes_or_no) AS is_trigger_insertable_into
CAST(
-- TRIGGER_TYPE_ROW + TRIGGER_TYPE_INSTEAD + TRIGGER_TYPE_UPDATE
CASE WHEN EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid = c.oid AND tgtype & 81 = 81)
THEN 'YES' ELSE 'NO' END
AS yes_or_no) AS is_trigger_updatable,
CAST(
-- TRIGGER_TYPE_ROW + TRIGGER_TYPE_INSTEAD + TRIGGER_TYPE_DELETE
CASE WHEN EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid = c.oid AND tgtype & 73 = 73)
THEN 'YES' ELSE 'NO' END
AS yes_or_no) AS is_trigger_deletable,
CAST(
-- TRIGGER_TYPE_ROW + TRIGGER_TYPE_INSTEAD + TRIGGER_TYPE_INSERT
CASE WHEN EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid = c.oid AND tgtype & 69 = 69)
THEN 'YES' ELSE 'NO' END
AS yes_or_no) AS is_trigger_insertable_into
FROM pg_namespace nc, pg_class c
......
......@@ -426,7 +426,7 @@ T211 Basic trigger capability 06 Support for run-time rules for the interaction
T211 Basic trigger capability 07 TRIGGER privilege YES
T211 Basic trigger capability 08 Multiple triggers for the same event are executed in the order in which they were created in the catalog NO intentionally omitted
T212 Enhanced trigger capability YES
T213 INSTEAD OF triggers NO
T213 INSTEAD OF triggers YES
T231 Sensitive cursors YES
T241 START TRANSACTION statement YES
T251 SET TRANSACTION statement: LOCAL option NO
......
......@@ -2155,7 +2155,7 @@ CopyFrom(CopyState cstate)
/* BEFORE ROW INSERT Triggers */
if (resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
resultRelInfo->ri_TrigDesc->trig_insert_before_row)
{
HeapTuple newtuple;
......
......@@ -5690,8 +5690,8 @@ CreateFKCheckTrigger(RangeVar *myRel, Constraint *fkconstraint,
fk_trigger = makeNode(CreateTrigStmt);
fk_trigger->trigname = "RI_ConstraintTrigger";
fk_trigger->relation = myRel;
fk_trigger->before = false;
fk_trigger->row = true;
fk_trigger->timing = TRIGGER_TYPE_AFTER;
/* Either ON INSERT or ON UPDATE */
if (on_insert)
......@@ -5753,8 +5753,8 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
fk_trigger = makeNode(CreateTrigStmt);
fk_trigger->trigname = "RI_ConstraintTrigger";
fk_trigger->relation = fkconstraint->pktable;
fk_trigger->before = false;
fk_trigger->row = true;
fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->events = TRIGGER_TYPE_DELETE;
fk_trigger->columns = NIL;
fk_trigger->whenClause = NULL;
......@@ -5806,8 +5806,8 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
fk_trigger = makeNode(CreateTrigStmt);
fk_trigger->trigname = "RI_ConstraintTrigger";
fk_trigger->relation = fkconstraint->pktable;
fk_trigger->before = false;
fk_trigger->row = true;
fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->events = TRIGGER_TYPE_UPDATE;
fk_trigger->columns = NIL;
fk_trigger->whenClause = NULL;
......
This diff is collapsed.
......@@ -877,9 +877,13 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
CmdType operation,
int instrument_options)
{
TriggerDesc *trigDesc = resultRelationDesc->trigdesc;
/*
* Check valid relkind ... parser and/or planner should have noticed this
* already, but let's make sure.
* Check valid relkind ... in most cases parser and/or planner should have
* noticed this already, but let's make sure. In the view case we do need
* a test here, because if the view wasn't rewritten by a rule, it had
* better have an INSTEAD trigger.
*/
switch (resultRelationDesc->rd_rel->relkind)
{
......@@ -899,10 +903,36 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
RelationGetRelationName(resultRelationDesc))));
break;
case RELKIND_VIEW:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change view \"%s\"",
RelationGetRelationName(resultRelationDesc))));
switch (operation)
{
case CMD_INSERT:
if (!trigDesc || !trigDesc->trig_insert_instead_row)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot insert into view \"%s\"",
RelationGetRelationName(resultRelationDesc)),
errhint("You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.")));
break;
case CMD_UPDATE:
if (!trigDesc || !trigDesc->trig_update_instead_row)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot update view \"%s\"",
RelationGetRelationName(resultRelationDesc)),
errhint("You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.")));
break;
case CMD_DELETE:
if (!trigDesc || !trigDesc->trig_delete_instead_row)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot delete from view \"%s\"",
RelationGetRelationName(resultRelationDesc)),
errhint("You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.")));
break;
default:
elog(ERROR, "unrecognized CmdType: %d", (int) operation);
break;
}
break;
default:
ereport(ERROR,
......@@ -921,7 +951,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_IndexRelationDescs = NULL;
resultRelInfo->ri_IndexRelationInfo = NULL;
/* make a copy so as not to depend on relcache info not changing... */
resultRelInfo->ri_TrigDesc = CopyTriggerDesc(resultRelationDesc->trigdesc);
resultRelInfo->ri_TrigDesc = CopyTriggerDesc(trigDesc);
if (resultRelInfo->ri_TrigDesc)
{
int n = resultRelInfo->ri_TrigDesc->numtriggers;
......
This diff is collapsed.
......@@ -3243,8 +3243,8 @@ _copyCreateTrigStmt(CreateTrigStmt *from)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(funcname);
COPY_NODE_FIELD(args);
COPY_SCALAR_FIELD(before);
COPY_SCALAR_FIELD(row);
COPY_SCALAR_FIELD(timing);
COPY_SCALAR_FIELD(events);
COPY_NODE_FIELD(columns);
COPY_NODE_FIELD(whenClause);
......
......@@ -1698,8 +1698,8 @@ _equalCreateTrigStmt(CreateTrigStmt *a, CreateTrigStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(funcname);
COMPARE_NODE_FIELD(args);
COMPARE_SCALAR_FIELD(before);
COMPARE_SCALAR_FIELD(row);
COMPARE_SCALAR_FIELD(timing);
COMPARE_SCALAR_FIELD(events);
COMPARE_NODE_FIELD(columns);
COMPARE_NODE_FIELD(whenClause);
......
......@@ -3,14 +3,14 @@
* preptlist.c
* Routines to preprocess the parse tree target list
*
* This module takes care of altering the query targetlist as needed for
* INSERT, UPDATE, and DELETE queries. For INSERT and UPDATE queries,
* the targetlist must contain an entry for each attribute of the target
* relation in the correct order. For both UPDATE and DELETE queries,
* we need a junk targetlist entry holding the CTID attribute --- the
* executor relies on this to find the tuple to be replaced/deleted.
* We may also need junk tlist entries for Vars used in the RETURNING list
* and row ID information needed for EvalPlanQual checking.
* For INSERT and UPDATE queries, the targetlist must contain an entry for
* each attribute of the target relation in the correct order. For all query
* types, we may need to add junk tlist entries for Vars used in the RETURNING
* list and row ID information needed for EvalPlanQual checking.
*
* NOTE: the rewriter's rewriteTargetListIU and rewriteTargetListUD
* routines also do preprocessing of the targetlist. The division of labor
* between here and there is a bit arbitrary and historical.
*
*
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
......@@ -77,37 +77,6 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
tlist = expand_targetlist(tlist, command_type,
result_relation, range_table);
/*
* for "update" and "delete" queries, add ctid of the result relation into
* the target list so that the ctid will propagate through execution and
* ExecutePlan() will be able to identify the right tuple to replace or
* delete. This extra field is marked "junk" so that it is not stored
* back into the tuple.
*/
if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
{
TargetEntry *tle;
Var *var;
var = makeVar(result_relation, SelfItemPointerAttributeNumber,
TIDOID, -1, 0);
tle = makeTargetEntry((Expr *) var,
list_length(tlist) + 1,
pstrdup("ctid"),
true);
/*
* For an UPDATE, expand_targetlist already created a fresh tlist. For
* DELETE, better do a listCopy so that we don't destructively modify
* the original tlist (is this really necessary?).
*/
if (command_type == CMD_DELETE)
tlist = list_copy(tlist);
tlist = lappend(tlist, tle);
}
/*
* Add necessary junk columns for rowmarked rels. These values are needed
* for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual
......@@ -235,9 +204,6 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
* Given a target list as generated by the parser and a result relation,
* add targetlist entries for any missing attributes, and ensure the
* non-junk attributes appear in proper field order.
*
* NOTE: if you are tempted to put more processing here, consider whether
* it shouldn't go in the rewriter's rewriteTargetList() instead.
*/
static List *
expand_targetlist(List *tlist, int command_type,
......
......@@ -246,8 +246,8 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
%type <str> OptSchemaName
%type <list> OptSchemaEltList
%type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
%type <boolean> TriggerForSpec TriggerForType
%type <ival> TriggerActionTime
%type <list> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <node> TriggerWhen
......@@ -311,7 +311,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
%type <fun_param_mode> arg_class
%type <typnam> func_return func_type
%type <boolean> TriggerForType OptTemp
%type <boolean> OptTemp opt_trusted opt_restart_seqs
%type <oncommit> OnCommitOption
%type <node> for_locking_item
......@@ -3448,8 +3448,8 @@ CreateTrigStmt:
n->relation = $7;
n->funcname = $12;
n->args = $14;
n->before = $4;
n->row = $8;
n->timing = $4;
n->events = intVal(linitial($5));
n->columns = (List *) lsecond($5);
n->whenClause = $9;
......@@ -3469,8 +3469,8 @@ CreateTrigStmt:
n->relation = $8;
n->funcname = $17;
n->args = $19;
n->before = FALSE;
n->row = TRUE;
n->timing = TRIGGER_TYPE_AFTER;
n->events = intVal(linitial($6));
n->columns = (List *) lsecond($6);
n->whenClause = $14;
......@@ -3483,8 +3483,9 @@ CreateTrigStmt:
;
TriggerActionTime:
BEFORE { $$ = TRUE; }
| AFTER { $$ = FALSE; }
BEFORE { $$ = TRIGGER_TYPE_BEFORE; }
| AFTER { $$ = TRIGGER_TYPE_AFTER; }
| INSTEAD OF { $$ = TRIGGER_TYPE_INSTEAD; }
;
TriggerEvents:
......@@ -3525,7 +3526,7 @@ TriggerOneEvent:
;
TriggerForSpec:
FOR TriggerForOpt TriggerForType
FOR TriggerForOptEach TriggerForType
{
$$ = $3;
}
......@@ -3539,7 +3540,7 @@ TriggerForSpec:
}
;
TriggerForOpt:
TriggerForOptEach:
EACH {}
| /*EMPTY*/ {}
;
......
This diff is collapsed.
......@@ -1262,7 +1262,7 @@ ResolveNew_callback(Var *var,
/* Normal case referencing one targetlist element */
tle = get_tle_by_resno(rcon->targetlist, var->varattno);
if (tle == NULL)
if (tle == NULL || tle->resjunk)
{
/* Failed to find column in insert/update tlist */
if (rcon->event == CMD_UPDATE)
......
......@@ -545,8 +545,13 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
appendStringInfo(&buf, "BEFORE");
else
else if (TRIGGER_FOR_AFTER(trigrec->tgtype))
appendStringInfo(&buf, "AFTER");
else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype))
appendStringInfo(&buf, "INSTEAD OF");
else
elog(ERROR, "unexpected tgtype value: %d", trigrec->tgtype);
if (TRIGGER_FOR_INSERT(trigrec->tgtype))
{
appendStringInfo(&buf, " INSERT");
......
......@@ -12096,11 +12096,19 @@ dumpTrigger(Archive *fout, TriggerInfo *tginfo)
appendPQExpBuffer(query, "\n ");
/* Trigger type */
findx = 0;
if (TRIGGER_FOR_BEFORE(tginfo->tgtype))
appendPQExpBuffer(query, "BEFORE");
else
else if (TRIGGER_FOR_AFTER(tginfo->tgtype))
appendPQExpBuffer(query, "AFTER");
else if (TRIGGER_FOR_INSTEAD(tginfo->tgtype))
appendPQExpBuffer(query, "INSTEAD OF");
else
{
write_msg(NULL, "unexpected tgtype value: %d\n", tginfo->tgtype);
exit_nicely();
}
findx = 0;
if (TRIGGER_FOR_INSERT(tginfo->tgtype))
{
appendPQExpBuffer(query, " INSERT");
......
......@@ -1820,10 +1820,17 @@ describeOneTableDetails(const char *schemaname,
}
PQclear(result);
}
}
/*
* Print triggers next, if any (but only user-defined triggers). This
* could apply to either a table or a view.
*/
if (tableinfo.hastriggers)
{
PGresult *result;
int tuples;
/* print triggers (but only user-defined triggers) */
if (tableinfo.hastriggers)
{
printfPQExpBuffer(&buf,
"SELECT t.tgname, "
"pg_catalog.pg_get_triggerdef(t.oid%s), "
......@@ -1934,7 +1941,15 @@ describeOneTableDetails(const char *schemaname,
}
}
PQclear(result);
}
}
/*
* Finish printing the footer information about a table.
*/
if (tableinfo.relkind == 'r')
{
PGresult *result;
int tuples;
/* print inherited tables */
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno", oid);
......
......@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201009281
#define CATALOG_VERSION_NO 201010101
#endif
......@@ -38,7 +38,7 @@ CATALOG(pg_trigger,2620)
Oid tgrelid; /* relation trigger is attached to */
NameData tgname; /* trigger's name */
Oid tgfoid; /* OID of function to be called */
int2 tgtype; /* BEFORE/AFTER UPDATE/DELETE/INSERT
int2 tgtype; /* BEFORE/AFTER/INSTEAD, UPDATE/DELETE/INSERT,
* ROW/STATEMENT; see below */
char tgenabled; /* trigger's firing configuration WRT
* session_replication_role */
......@@ -91,22 +91,49 @@ typedef FormData_pg_trigger *Form_pg_trigger;
#define TRIGGER_TYPE_DELETE (1 << 3)
#define TRIGGER_TYPE_UPDATE (1 << 4)
#define TRIGGER_TYPE_TRUNCATE (1 << 5)
#define TRIGGER_TYPE_INSTEAD (1 << 6)
#define TRIGGER_TYPE_LEVEL_MASK (TRIGGER_TYPE_ROW)
#define TRIGGER_TYPE_STATEMENT 0
/* Note bits within TRIGGER_TYPE_TIMING_MASK aren't adjacent */
#define TRIGGER_TYPE_TIMING_MASK \
(TRIGGER_TYPE_BEFORE | TRIGGER_TYPE_INSTEAD)
#define TRIGGER_TYPE_AFTER 0
#define TRIGGER_TYPE_EVENT_MASK \
(TRIGGER_TYPE_INSERT | TRIGGER_TYPE_DELETE | TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_TRUNCATE)
/* Macros for manipulating tgtype */
#define TRIGGER_CLEAR_TYPE(type) ((type) = 0)
#define TRIGGER_SETT_ROW(type) ((type) |= TRIGGER_TYPE_ROW)
#define TRIGGER_SETT_STATEMENT(type) ((type) |= TRIGGER_TYPE_STATEMENT)
#define TRIGGER_SETT_BEFORE(type) ((type) |= TRIGGER_TYPE_BEFORE)
#define TRIGGER_SETT_AFTER(type) ((type) |= TRIGGER_TYPE_AFTER)
#define TRIGGER_SETT_INSTEAD(type) ((type) |= TRIGGER_TYPE_INSTEAD)
#define TRIGGER_SETT_INSERT(type) ((type) |= TRIGGER_TYPE_INSERT)
#define TRIGGER_SETT_DELETE(type) ((type) |= TRIGGER_TYPE_DELETE)
#define TRIGGER_SETT_UPDATE(type) ((type) |= TRIGGER_TYPE_UPDATE)
#define TRIGGER_SETT_TRUNCATE(type) ((type) |= TRIGGER_TYPE_TRUNCATE)
#define TRIGGER_FOR_ROW(type) ((type) & TRIGGER_TYPE_ROW)
#define TRIGGER_FOR_BEFORE(type) ((type) & TRIGGER_TYPE_BEFORE)
#define TRIGGER_FOR_BEFORE(type) (((type) & TRIGGER_TYPE_TIMING_MASK) == TRIGGER_TYPE_BEFORE)
#define TRIGGER_FOR_AFTER(type) (((type) & TRIGGER_TYPE_TIMING_MASK) == TRIGGER_TYPE_AFTER)
#define TRIGGER_FOR_INSTEAD(type) (((type) & TRIGGER_TYPE_TIMING_MASK) == TRIGGER_TYPE_INSTEAD)
#define TRIGGER_FOR_INSERT(type) ((type) & TRIGGER_TYPE_INSERT)
#define TRIGGER_FOR_DELETE(type) ((type) & TRIGGER_TYPE_DELETE)
#define TRIGGER_FOR_UPDATE(type) ((type) & TRIGGER_TYPE_UPDATE)
#define TRIGGER_FOR_TRUNCATE(type) ((type) & TRIGGER_TYPE_TRUNCATE)
/*
* Efficient macro for checking if tgtype matches a particular level
* (TRIGGER_TYPE_ROW or TRIGGER_TYPE_STATEMENT), timing (TRIGGER_TYPE_BEFORE,
* TRIGGER_TYPE_AFTER or TRIGGER_TYPE_INSTEAD), and event (TRIGGER_TYPE_INSERT,
* TRIGGER_TYPE_DELETE, TRIGGER_TYPE_UPDATE, or TRIGGER_TYPE_TRUNCATE). Note
* that a tgtype can match more than one event, but only one level or timing.
*/
#define TRIGGER_TYPE_MATCHES(type, level, timing, event) \
(((type) & (TRIGGER_TYPE_LEVEL_MASK | TRIGGER_TYPE_TIMING_MASK | (event))) == ((level) | (timing) | (event)))
#endif /* PG_TRIGGER_H */
......@@ -51,44 +51,48 @@ typedef struct TriggerData
#define TRIGGER_EVENT_UPDATE 0x00000002
#define TRIGGER_EVENT_TRUNCATE 0x00000003
#define TRIGGER_EVENT_OPMASK 0x00000003
#define TRIGGER_EVENT_ROW 0x00000004
#define TRIGGER_EVENT_BEFORE 0x00000008
#define TRIGGER_EVENT_AFTER 0x00000000
#define TRIGGER_EVENT_INSTEAD 0x00000010
#define TRIGGER_EVENT_TIMINGMASK 0x00000018
/* More TriggerEvent flags, used only within trigger.c */
#define AFTER_TRIGGER_DEFERRABLE 0x00000010
#define AFTER_TRIGGER_INITDEFERRED 0x00000020
#define AFTER_TRIGGER_DEFERRABLE 0x00000020
#define AFTER_TRIGGER_INITDEFERRED 0x00000040
#define TRIGGER_FIRED_BY_INSERT(event) \
(((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \
TRIGGER_EVENT_INSERT)
#define TRIGGER_FIRED_BY_INSERT(event) \
(((event) & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_INSERT)
#define TRIGGER_FIRED_BY_DELETE(event) \
(((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \
TRIGGER_EVENT_DELETE)
#define TRIGGER_FIRED_BY_DELETE(event) \
(((event) & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_DELETE)
#define TRIGGER_FIRED_BY_UPDATE(event) \
(((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \
TRIGGER_EVENT_UPDATE)
#define TRIGGER_FIRED_BY_UPDATE(event) \
(((event) & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE)
#define TRIGGER_FIRED_BY_TRUNCATE(event) \
(((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \
TRIGGER_EVENT_TRUNCATE)
(((event) & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_TRUNCATE)
#define TRIGGER_FIRED_FOR_ROW(event) \
((event) & TRIGGER_EVENT_ROW)
#define TRIGGER_FIRED_FOR_ROW(event) \
((TriggerEvent) (event) & TRIGGER_EVENT_ROW)
#define TRIGGER_FIRED_FOR_STATEMENT(event) \
(!TRIGGER_FIRED_FOR_ROW(event))
#define TRIGGER_FIRED_FOR_STATEMENT(event) \
(!TRIGGER_FIRED_FOR_ROW (event))
#define TRIGGER_FIRED_BEFORE(event) \
(((event) & TRIGGER_EVENT_TIMINGMASK) == TRIGGER_EVENT_BEFORE)
#define TRIGGER_FIRED_BEFORE(event) \
((TriggerEvent) (event) & TRIGGER_EVENT_BEFORE)
#define TRIGGER_FIRED_AFTER(event) \
(((event) & TRIGGER_EVENT_TIMINGMASK) == TRIGGER_EVENT_AFTER)
#define TRIGGER_FIRED_AFTER(event) \
(!TRIGGER_FIRED_BEFORE (event))
#define TRIGGER_FIRED_INSTEAD(event) \
(((event) & TRIGGER_EVENT_TIMINGMASK) == TRIGGER_EVENT_INSTEAD)
/*
* Definitions for the replication role based firing.
* Definitions for replication role based firing.
*/
#define SESSION_REPLICATION_ROLE_ORIGIN 0
#define SESSION_REPLICATION_ROLE_REPLICA 1
......@@ -135,6 +139,9 @@ extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
HeapTuple trigtuple,
List *recheckIndexes);
extern HeapTuple ExecIRInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
HeapTuple trigtuple);
extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate,
......@@ -146,6 +153,9 @@ extern bool ExecBRDeleteTriggers(EState *estate,
extern void ExecARDeleteTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid);
extern bool ExecIRDeleteTriggers(EState *estate,
ResultRelInfo *relinfo,
HeapTuple trigtuple);
extern void ExecBSUpdateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASUpdateTriggers(EState *estate,
......@@ -160,6 +170,10 @@ extern void ExecARUpdateTriggers(EState *estate,
ItemPointer tupleid,
HeapTuple newtuple,
List *recheckIndexes);
extern HeapTuple ExecIRUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
HeapTuple oldtuple,
HeapTuple newtuple);
extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
......
......@@ -1608,10 +1608,11 @@ typedef struct CreateTrigStmt
RangeVar *relation; /* relation trigger is on */
List *funcname; /* qual. name of function to call */
List *args; /* list of (T_String) Values or NIL */
bool before; /* BEFORE/AFTER */
bool row; /* ROW/STATEMENT */
/* timing uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
int16 timing; /* BEFORE, AFTER, or INSTEAD */
/* events uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
int16 events; /* INSERT/UPDATE/DELETE/TRUNCATE */
int16 events; /* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
List *columns; /* column names, or NIL for all columns */
Node *whenClause; /* qual expression, or NULL if none */
bool isconstraint; /* This is a constraint trigger */
......
......@@ -71,26 +71,31 @@ typedef struct Trigger
typedef struct TriggerDesc
{
Trigger *triggers; /* array of Trigger structs */
int numtriggers; /* number of array entries */
/*
* Index data to identify which triggers are which. Since each trigger
* can appear in more than one class, for each class we provide a list of
* integer indexes into the triggers array. The class codes are defined
* by TRIGGER_EVENT_xxx macros in commands/trigger.h.
* These flags indicate whether the array contains at least one of each
* type of trigger. We use these to skip searching the array if not.
*/
#define TRIGGER_NUM_EVENT_CLASSES 4
uint16 n_before_statement[TRIGGER_NUM_EVENT_CLASSES];
uint16 n_before_row[TRIGGER_NUM_EVENT_CLASSES];
uint16 n_after_row[TRIGGER_NUM_EVENT_CLASSES];
uint16 n_after_statement[TRIGGER_NUM_EVENT_CLASSES];
int *tg_before_statement[TRIGGER_NUM_EVENT_CLASSES];
int *tg_before_row[TRIGGER_NUM_EVENT_CLASSES];
int *tg_after_row[TRIGGER_NUM_EVENT_CLASSES];
int *tg_after_statement[TRIGGER_NUM_EVENT_CLASSES];
/* The actual array of triggers is here */
Trigger *triggers;
int numtriggers;
bool trig_insert_before_row;
bool trig_insert_after_row;
bool trig_insert_instead_row;
bool trig_insert_before_statement;
bool trig_insert_after_statement;
bool trig_update_before_row;
bool trig_update_after_row;
bool trig_update_instead_row;
bool trig_update_before_statement;
bool trig_update_after_statement;
bool trig_delete_before_row;
bool trig_delete_after_row;
bool trig_delete_instead_row;
bool trig_delete_before_statement;
bool trig_delete_after_statement;
/* there are no row-level truncate triggers */
bool trig_truncate_before_statement;
bool trig_truncate_after_statement;
} TriggerDesc;
......
......@@ -124,6 +124,84 @@ NOTICE: $_TD->{when} = 'BEFORE'
CONTEXT: PL/Perl function "trigger_data"
DROP TRIGGER show_trigger_data_trig on trigger_test;
insert into trigger_test values(1,'insert');
CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
CREATE TRIGGER show_trigger_data_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
insert into trigger_test_view values(2,'insert');
NOTICE: $_TD->{argc} = '2'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{args} = ['24', 'skidoo view']
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{event} = 'INSERT'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{level} = 'ROW'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{name} = 'show_trigger_data_trig'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{new} = {'i' => '2', 'v' => 'insert'}
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{relid} = 'bogus:12345'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{relname} = 'trigger_test_view'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{table_name} = 'trigger_test_view'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{table_schema} = 'public'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{when} = 'INSTEAD OF'
CONTEXT: PL/Perl function "trigger_data"
update trigger_test_view set v = 'update' where i = 1;
NOTICE: $_TD->{argc} = '2'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{args} = ['24', 'skidoo view']
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{event} = 'UPDATE'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{level} = 'ROW'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{name} = 'show_trigger_data_trig'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{new} = {'i' => '1', 'v' => 'update'}
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{old} = {'i' => '1', 'v' => 'insert'}
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{relid} = 'bogus:12345'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{relname} = 'trigger_test_view'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{table_name} = 'trigger_test_view'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{table_schema} = 'public'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{when} = 'INSTEAD OF'
CONTEXT: PL/Perl function "trigger_data"
delete from trigger_test_view;
NOTICE: $_TD->{argc} = '2'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{args} = ['24', 'skidoo view']
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{event} = 'DELETE'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{level} = 'ROW'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{name} = 'show_trigger_data_trig'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{old} = {'i' => '1', 'v' => 'insert'}
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{relid} = 'bogus:12345'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{relname} = 'trigger_test_view'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{table_name} = 'trigger_test_view'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{table_schema} = 'public'
CONTEXT: PL/Perl function "trigger_data"
NOTICE: $_TD->{when} = 'INSTEAD OF'
CONTEXT: PL/Perl function "trigger_data"
DROP VIEW trigger_test_view;
delete from trigger_test;
DROP FUNCTION trigger_data();
CREATE OR REPLACE FUNCTION valid_id() RETURNS trigger AS $$
......
......@@ -1087,6 +1087,8 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
when = "BEFORE";
else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
when = "AFTER";
else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event))
when = "INSTEAD OF";
else
when = "UNKNOWN";
hv_store_string(hv, "when", newSVstring(when));
......
......@@ -60,6 +60,20 @@ update trigger_test set v = 'update' where i = 1;
delete from trigger_test;
DROP TRIGGER show_trigger_data_trig on trigger_test;
insert into trigger_test values(1,'insert');
CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
CREATE TRIGGER show_trigger_data_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
insert into trigger_test_view values(2,'insert');
update trigger_test_view set v = 'update' where i = 1;
delete from trigger_test_view;
DROP VIEW trigger_test_view;
delete from trigger_test;
DROP FUNCTION trigger_data();
......
......@@ -581,8 +581,10 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
var->value = CStringGetTextDatum("BEFORE");
else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
var->value = CStringGetTextDatum("AFTER");
else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
var->value = CStringGetTextDatum("INSTEAD OF");
else
elog(ERROR, "unrecognized trigger execution time: not BEFORE or AFTER");
elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF");
var->isnull = false;
var->freeval = true;
......
......@@ -294,11 +294,81 @@ NOTICE: TD[table_schema] => public
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[when] => BEFORE
CONTEXT: PL/Python function "trigger_data"
DROP TRIGGER show_trigger_data_trig_stmt on trigger_test;
DROP TRIGGER show_trigger_data_trig_before on trigger_test;
DROP TRIGGER show_trigger_data_trig_after on trigger_test;
insert into trigger_test values(1,'insert');
CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
CREATE TRIGGER show_trigger_data_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
insert into trigger_test_view values(2,'insert');
NOTICE: TD[args] => ['24', 'skidoo view']
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[event] => INSERT
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[level] => ROW
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[name] => show_trigger_data_trig
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[new] => {'i': 2, 'v': 'insert'}
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[old] => None
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[relid] => bogus:12345
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[table_name] => trigger_test_view
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[table_schema] => public
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[when] => INSTEAD OF
CONTEXT: PL/Python function "trigger_data"
update trigger_test_view set v = 'update' where i = 1;
NOTICE: TD[args] => ['24', 'skidoo view']
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[event] => UPDATE
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[level] => ROW
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[name] => show_trigger_data_trig
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[new] => {'i': 1, 'v': 'update'}
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[old] => {'i': 1, 'v': 'insert'}
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[relid] => bogus:12345
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[table_name] => trigger_test_view
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[table_schema] => public
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[when] => INSTEAD OF
CONTEXT: PL/Python function "trigger_data"
delete from trigger_test_view;
NOTICE: TD[args] => ['24', 'skidoo view']
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[event] => DELETE
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[level] => ROW
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[name] => show_trigger_data_trig
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[new] => None
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[old] => {'i': 1, 'v': 'insert'}
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[relid] => bogus:12345
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[table_name] => trigger_test_view
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[table_schema] => public
CONTEXT: PL/Python function "trigger_data"
NOTICE: TD[when] => INSTEAD OF
CONTEXT: PL/Python function "trigger_data"
DROP FUNCTION trigger_data() CASCADE;
NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to trigger show_trigger_data_trig_before on table trigger_test
drop cascades to trigger show_trigger_data_trig_after on table trigger_test
drop cascades to trigger show_trigger_data_trig_stmt on table trigger_test
NOTICE: drop cascades to trigger show_trigger_data_trig on view trigger_test_view
DROP VIEW trigger_test_view;
delete from trigger_test;
--
-- trigger error handling
--
......
......@@ -845,6 +845,8 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
pltwhen = PyString_FromString("BEFORE");
else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
pltwhen = PyString_FromString("AFTER");
else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event))
pltwhen = PyString_FromString("INSTEAD OF");
else
{
elog(ERROR, "unrecognized WHEN tg_event: %u", tdata->tg_event);
......
......@@ -99,7 +99,24 @@ update trigger_test set v = 'update' where i = 1;
delete from trigger_test;
truncate table trigger_test;
DROP TRIGGER show_trigger_data_trig_stmt on trigger_test;
DROP TRIGGER show_trigger_data_trig_before on trigger_test;
DROP TRIGGER show_trigger_data_trig_after on trigger_test;
insert into trigger_test values(1,'insert');
CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
CREATE TRIGGER show_trigger_data_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
insert into trigger_test_view values(2,'insert');
update trigger_test_view set v = 'update' where i = 1;
delete from trigger_test_view;
DROP FUNCTION trigger_data() CASCADE;
DROP VIEW trigger_test_view;
delete from trigger_test;
--
......
......@@ -196,6 +196,42 @@ NOTICE: TG_table_name: trigger_test
NOTICE: TG_table_schema: public
NOTICE: TG_when: BEFORE
NOTICE: args: {23 skidoo}
insert into trigger_test_view values(2,'insert');
NOTICE: NEW: {i: 2, v: insert}
NOTICE: OLD: {}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_view_trig
NOTICE: TG_op: INSERT
NOTICE: TG_relatts: {{} i v}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_view
NOTICE: TG_table_schema: public
NOTICE: TG_when: {INSTEAD OF}
NOTICE: args: {24 {skidoo view}}
update trigger_test_view set v = 'update' where i=1;
NOTICE: NEW: {i: 1, v: update}
NOTICE: OLD: {i: 1, v: insert}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_view_trig
NOTICE: TG_op: UPDATE
NOTICE: TG_relatts: {{} i v}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_view
NOTICE: TG_table_schema: public
NOTICE: TG_when: {INSTEAD OF}
NOTICE: args: {24 {skidoo view}}
delete from trigger_test_view;
NOTICE: NEW: {}
NOTICE: OLD: {i: 1, v: insert}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_view_trig
NOTICE: TG_op: DELETE
NOTICE: TG_relatts: {{} i v}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_view
NOTICE: TG_table_schema: public
NOTICE: TG_when: {INSTEAD OF}
NOTICE: args: {24 {skidoo view}}
update trigger_test set v = 'update' where i = 1;
NOTICE: NEW: {i: 1, v: update}
NOTICE: OLD: {i: 1, v: insert}
......
......@@ -196,6 +196,42 @@ NOTICE: TG_table_name: trigger_test
NOTICE: TG_table_schema: public
NOTICE: TG_when: BEFORE
NOTICE: args: {23 skidoo}
insert into trigger_test_view values(2,'insert');
NOTICE: NEW: {i: 2, v: insert}
NOTICE: OLD: {}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_view_trig
NOTICE: TG_op: INSERT
NOTICE: TG_relatts: {{} i v}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_view
NOTICE: TG_table_schema: public
NOTICE: TG_when: {INSTEAD OF}
NOTICE: args: {24 {skidoo view}}
update trigger_test_view set v = 'update' where i=1;
NOTICE: NEW: {i: 1, v: update}
NOTICE: OLD: {i: 1, v: insert}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_view_trig
NOTICE: TG_op: UPDATE
NOTICE: TG_relatts: {{} i v}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_view
NOTICE: TG_table_schema: public
NOTICE: TG_when: {INSTEAD OF}
NOTICE: args: {24 {skidoo view}}
delete from trigger_test_view;
NOTICE: NEW: {}
NOTICE: OLD: {i: 1, v: insert}
NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_view_trig
NOTICE: TG_op: DELETE
NOTICE: TG_relatts: {{} i v}
NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test_view
NOTICE: TG_table_schema: public
NOTICE: TG_when: {INSTEAD OF}
NOTICE: args: {24 {skidoo view}}
update trigger_test set v = 'update' where i = 1;
NOTICE: NEW: {i: 1, v: update}
NOTICE: OLD: {i: 1, v: insert}
......
......@@ -51,6 +51,7 @@ create function check_pkey1_exists(int4, bpchar) returns bool as E'
-- dump trigger data
CREATE TABLE trigger_test
(i int, v text );
CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
if { [info exists TG_relid] } {
......@@ -85,6 +86,9 @@ $_$;
CREATE TRIGGER show_trigger_data_trig
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER show_trigger_data_view_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
--
-- Trigger function on every change to T_pkey1
--
......
......@@ -889,6 +889,8 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted)
Tcl_DStringAppendElement(&tcl_cmd, "BEFORE");
else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
Tcl_DStringAppendElement(&tcl_cmd, "AFTER");
else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
Tcl_DStringAppendElement(&tcl_cmd, "INSTEAD OF");
else
elog(ERROR, "unrecognized WHEN tg_event: %u", trigdata->tg_event);
......
......@@ -73,8 +73,12 @@ select 100 @< 4;
select * from T_pkey1 order by key1 using @<, key2;
select * from T_pkey2 order by key1 using @<, key2;
-- show dump of trigger data
insert into trigger_test values(1,'insert');
insert into trigger_test_view values(2,'insert');
update trigger_test_view set v = 'update' where i=1;
delete from trigger_test_view;
update trigger_test set v = 'update' where i = 1;
delete from trigger_test;
......@@ -60,6 +60,8 @@ create function check_pkey1_exists(int4, bpchar) returns bool as E'
CREATE TABLE trigger_test
(i int, v text );
CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
if { [info exists TG_relid] } {
......@@ -96,6 +98,9 @@ CREATE TRIGGER show_trigger_data_trig
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER show_trigger_data_view_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
--
-- Trigger function on every change to T_pkey1
......
This diff is collapsed.
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