mirror of
https://github.com/postgres/postgres.git
synced 2025-06-16 06:01:02 +03:00
pg_trigger's index on tgrelid is replaced by a unique index on
(tgrelid, tgname). This provides an additional check on trigger name uniqueness per-table (which was already enforced by the code anyway). With this change, RelationBuildTriggers will read the triggers in order by tgname, since it's scanning using this index. Since a predictable trigger ordering has been requested for some time, document this behavior as a feature. Also document that rules fire in name order, since yesterday's changes to pg_rewrite indexing cause that too.
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_rule.sgml,v 1.33 2002/03/22 19:20:39 petere Exp $
|
$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_rule.sgml,v 1.34 2002/04/19 16:36:08 tgl Exp $
|
||||||
PostgreSQL documentation
|
PostgreSQL documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ PostgreSQL documentation
|
|||||||
</refsynopsisdivinfo>
|
</refsynopsisdivinfo>
|
||||||
<synopsis>
|
<synopsis>
|
||||||
CREATE RULE <replaceable class="parameter">name</replaceable> AS ON <replaceable class="parameter">event</replaceable>
|
CREATE RULE <replaceable class="parameter">name</replaceable> AS ON <replaceable class="parameter">event</replaceable>
|
||||||
TO <replaceable class="parameter">object</replaceable> [ WHERE <replaceable class="parameter">condition</replaceable> ]
|
TO <replaceable class="parameter">table</replaceable> [ WHERE <replaceable class="parameter">condition</replaceable> ]
|
||||||
DO [ INSTEAD ] <replaceable class="parameter">action</replaceable>
|
DO [ INSTEAD ] <replaceable class="parameter">action</replaceable>
|
||||||
|
|
||||||
where <replaceable class="PARAMETER">action</replaceable> can be:
|
where <replaceable class="PARAMETER">action</replaceable> can be:
|
||||||
@ -48,7 +48,8 @@ NOTHING
|
|||||||
<term><replaceable class="parameter">name</replaceable></term>
|
<term><replaceable class="parameter">name</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The name of a rule to create.
|
The name of a rule to create. This must be distinct from the name
|
||||||
|
of any other rule for the same table.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -63,14 +64,11 @@ NOTHING
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><replaceable class="parameter">object</replaceable></term>
|
<term><replaceable class="parameter">table</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Object is either <replaceable class="parameter">table</replaceable>
|
The name (optionally schema-qualified) of the table or view the rule
|
||||||
or <replaceable class="parameter">table</replaceable>.<replaceable
|
applies to.
|
||||||
class="parameter">column</replaceable>. (Currently, only the
|
|
||||||
<replaceable class="parameter">table</replaceable> form is
|
|
||||||
actually implemented.)
|
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -103,8 +101,7 @@ NOTHING
|
|||||||
Within the <replaceable class="parameter">condition</replaceable>
|
Within the <replaceable class="parameter">condition</replaceable>
|
||||||
and <replaceable class="PARAMETER">action</replaceable>, the special
|
and <replaceable class="PARAMETER">action</replaceable>, the special
|
||||||
table names <literal>new</literal> and <literal>old</literal> may be
|
table names <literal>new</literal> and <literal>old</literal> may be
|
||||||
used to refer to values in the referenced table (the
|
used to refer to values in the referenced table.
|
||||||
<replaceable class="parameter">object</replaceable>).
|
|
||||||
<literal>new</literal> is valid in ON INSERT and ON UPDATE rules
|
<literal>new</literal> is valid in ON INSERT and ON UPDATE rules
|
||||||
to refer to the new row being inserted or updated.
|
to refer to the new row being inserted or updated.
|
||||||
<literal>old</literal> is valid in ON UPDATE and ON DELETE
|
<literal>old</literal> is valid in ON UPDATE and ON DELETE
|
||||||
@ -159,7 +156,7 @@ CREATE
|
|||||||
accessed, inserted, updated, or deleted, there is an old instance (for
|
accessed, inserted, updated, or deleted, there is an old instance (for
|
||||||
selects, updates and deletes) and a new instance (for inserts and
|
selects, updates and deletes) and a new instance (for inserts and
|
||||||
updates). All the rules for the given event type and the given target
|
updates). All the rules for the given event type and the given target
|
||||||
object (table) are examined, in an unspecified order. If the
|
table are examined successively (in order by name). If the
|
||||||
<replaceable class="parameter">condition</replaceable> specified in the
|
<replaceable class="parameter">condition</replaceable> specified in the
|
||||||
WHERE clause (if any) is true, the
|
WHERE clause (if any) is true, the
|
||||||
<replaceable class="parameter">action</replaceable> part of the rule is
|
<replaceable class="parameter">action</replaceable> part of the rule is
|
||||||
@ -178,8 +175,7 @@ CREATE
|
|||||||
The <replaceable class="parameter">action</replaceable> part of the
|
The <replaceable class="parameter">action</replaceable> part of the
|
||||||
rule can consist of one or more queries. To write multiple queries,
|
rule can consist of one or more queries. To write multiple queries,
|
||||||
surround them with parentheses. Such queries will be performed in the
|
surround them with parentheses. Such queries will be performed in the
|
||||||
specified order (whereas there are no guarantees about the execution
|
specified order. The <replaceable
|
||||||
order of multiple rules for an object). The <replaceable
|
|
||||||
class="parameter">action</replaceable> can also be NOTHING indicating
|
class="parameter">action</replaceable> can also be NOTHING indicating
|
||||||
no action. Thus, a DO INSTEAD NOTHING rule suppresses the original
|
no action. Thus, a DO INSTEAD NOTHING rule suppresses the original
|
||||||
query from executing (when its condition is true); a DO NOTHING rule
|
query from executing (when its condition is true); a DO NOTHING rule
|
||||||
@ -192,6 +188,20 @@ CREATE
|
|||||||
command that caused activation.
|
command that caused activation.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
It is important to realize that a rule is really a query transformation
|
||||||
|
mechanism, or query macro. The entire query is processed to convert it
|
||||||
|
into a series of queries that include the rule actions. This occurs
|
||||||
|
before evaluation of the query starts. So, conditional rules are
|
||||||
|
handled by adding the rule condition to the WHERE clause of the action(s)
|
||||||
|
derived from the rule. The above description of a rule as an operation
|
||||||
|
that executes for each row is thus somewhat misleading. If you actually
|
||||||
|
want an operation that fires independently for each physical row, you
|
||||||
|
probably want to use a trigger not a rule. Rules are most useful for
|
||||||
|
situations that call for transforming entire queries independently of
|
||||||
|
the specific data being handled.
|
||||||
|
</para>
|
||||||
|
|
||||||
<refsect2 id="R2-SQL-CREATERULE-3">
|
<refsect2 id="R2-SQL-CREATERULE-3">
|
||||||
<refsect2info>
|
<refsect2info>
|
||||||
<date>2001-11-06</date>
|
<date>2001-11-06</date>
|
||||||
@ -202,7 +212,7 @@ CREATE
|
|||||||
<para>
|
<para>
|
||||||
Presently, ON SELECT rules must be unconditional INSTEAD rules and must
|
Presently, ON SELECT rules must be unconditional INSTEAD rules and must
|
||||||
have actions that consist of a single SELECT query. Thus, an ON SELECT
|
have actions that consist of a single SELECT query. Thus, an ON SELECT
|
||||||
rule effectively turns the object table into a view, whose visible
|
rule effectively turns the table into a view, whose visible
|
||||||
contents are the rows returned by the rule's SELECT query rather than
|
contents are the rows returned by the rule's SELECT query rather than
|
||||||
whatever had been stored in the table (if anything). It is considered
|
whatever had been stored in the table (if anything). It is considered
|
||||||
better style to write a CREATE VIEW command than to create a real table
|
better style to write a CREATE VIEW command than to create a real table
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.22 2002/01/20 22:19:56 petere Exp $
|
$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.23 2002/04/19 16:36:08 tgl Exp $
|
||||||
PostgreSQL documentation
|
PostgreSQL documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -44,15 +44,8 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
|
|||||||
<term><replaceable class="parameter">name</replaceable></term>
|
<term><replaceable class="parameter">name</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The name to give the new trigger.
|
The name to give the new trigger. This must be distinct from the name
|
||||||
</para>
|
of any other trigger for the same table.
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
<varlistentry>
|
|
||||||
<term><replaceable class="parameter">table</replaceable></term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
The name of an existing table.
|
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -64,11 +57,32 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><replaceable class="parameter">table</replaceable></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The name (optionally schema-qualified) of the table the trigger is for.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><replaceable class="parameter">func</replaceable></term>
|
<term><replaceable class="parameter">func</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
A user-supplied function.
|
A user-supplied function that is declared as taking no arguments
|
||||||
|
and returning type <literal>opaque</>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><replaceable class="parameter">arguments</replaceable></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
An optional comma-separated list of arguments to be provided to the
|
||||||
|
function when the trigger is executed, along with the standard trigger
|
||||||
|
data such as old and new tuple contents. The arguments are literal
|
||||||
|
string constants. Simple names and numeric constants may be written
|
||||||
|
here too, but they will all be converted to strings.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -130,6 +144,12 @@ CREATE
|
|||||||
after the event, all changes, including the last insertion, update,
|
after the event, all changes, including the last insertion, update,
|
||||||
or deletion, are <quote>visible</quote> to the trigger.
|
or deletion, are <quote>visible</quote> to the trigger.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If multiple triggers of the same kind are defined for the same event,
|
||||||
|
they will be fired in alphabetical order by name.
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
<command>SELECT</command> does not modify any rows so you can not
|
<command>SELECT</command> does not modify any rows so you can not
|
||||||
create <command>SELECT</command> triggers. Rules and views are more
|
create <command>SELECT</command> triggers. Rules and views are more
|
||||||
@ -262,6 +282,12 @@ CREATE TABLE distributors (
|
|||||||
</listitem>
|
</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
SQL99 specifies that multiple triggers should be fired in
|
||||||
|
time-of-creation order. <productname>PostgreSQL</productname>
|
||||||
|
uses name order, which was judged more convenient to work with.
|
||||||
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.22 2002/04/01 22:36:06 tgl Exp $
|
$Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.23 2002/04/19 16:36:08 tgl Exp $
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<chapter id="triggers">
|
<chapter id="triggers">
|
||||||
@ -14,8 +14,8 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.22 2002/04/01 22:36:06 tgl
|
|||||||
AFTER on INSERT, DELETE or UPDATE of a tuple as a trigger event.
|
AFTER on INSERT, DELETE or UPDATE of a tuple as a trigger event.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<sect1 id="trigger-create">
|
<sect1 id="trigger-definition">
|
||||||
<title>Trigger Creation</title>
|
<title>Trigger Definition</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
If a trigger event occurs, the trigger manager (called by the Executor)
|
If a trigger event occurs, the trigger manager (called by the Executor)
|
||||||
@ -24,13 +24,17 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.22 2002/04/01 22:36:06 tgl
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The trigger function must be defined before the trigger is created as a
|
The trigger function must be defined before the trigger itself can be
|
||||||
function taking no arguments and returning opaque. If the function is
|
created. The trigger function must be declared as a
|
||||||
written in C, it must use the <quote>version 1</> function manager interface.
|
function taking no arguments and returning type <literal>opaque</>.
|
||||||
|
(The trigger function receives its input through a TriggerData
|
||||||
|
structure, not in the form of ordinary function arguments.)
|
||||||
|
If the function is written in C, it must use the <quote>version 1</>
|
||||||
|
function manager interface.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The syntax for creating triggers is as follows:
|
The syntax for creating triggers is:
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT | DELETE | UPDATE [ OR ... ] ]
|
CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT | DELETE | UPDATE [ OR ... ] ]
|
||||||
@ -48,9 +52,9 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The name of the trigger is
|
The trigger must have a name distinct from all other triggers on
|
||||||
used if you ever have to delete the trigger.
|
the same table. The name is needed
|
||||||
It is used as an argument to the <command>DROP TRIGGER</command> command.
|
if you ever have to delete the trigger.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -72,7 +76,7 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
<term>UPDATE</term>
|
<term>UPDATE</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The next element of the command determines on what event(s) will trigger
|
The next element of the command determines what event(s) will trigger
|
||||||
the function. Multiple events can be specified separated by OR.
|
the function. Multiple events can be specified separated by OR.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
@ -82,7 +86,7 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
<term><replaceable>relation</replaceable></term>
|
<term><replaceable>relation</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The relation name determines which table the event applies to.
|
The relation name indicates which table the event applies to.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -94,6 +98,7 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
<para>
|
<para>
|
||||||
The FOR EACH clause determines whether the trigger is fired for each
|
The FOR EACH clause determines whether the trigger is fired for each
|
||||||
affected row or before (or after) the entire statement has completed.
|
affected row or before (or after) the entire statement has completed.
|
||||||
|
Currently only the ROW case is supported.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -102,7 +107,7 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
<term><replaceable>procedure</replaceable></term>
|
<term><replaceable>procedure</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The procedure name is the function called.
|
The procedure name is the function to be called.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -112,23 +117,23 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The arguments passed to the function in the TriggerData structure.
|
The arguments passed to the function in the TriggerData structure.
|
||||||
The purpose of passing arguments to the function is to allow different
|
This is either empty or a list of one or more simple literal
|
||||||
|
constants (which will be passed to the function as strings).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The purpose of including arguments in the trigger definition
|
||||||
|
is to allow different
|
||||||
triggers with similar requirements to call the same function.
|
triggers with similar requirements to call the same function.
|
||||||
</para>
|
As an example, there could be a generalized trigger
|
||||||
|
function that takes as its arguments two field names and puts the
|
||||||
<para>
|
current user in one and the current timestamp in the other.
|
||||||
Also, <replaceable>procedure</replaceable>
|
Properly written, this trigger function would be independent of
|
||||||
may be used for triggering different relations (these
|
the specific table it is triggering on. So the same function
|
||||||
functions are named as <firstterm>general trigger functions</>).
|
could be used for INSERT events on any table with suitable fields,
|
||||||
</para>
|
to automatically track creation of records in a transaction table for
|
||||||
|
example. It could also be used to track last-update events if
|
||||||
<para>
|
defined as an UPDATE trigger.
|
||||||
As example of using both features above, there could be a general
|
|
||||||
function that takes as its arguments two field names and puts the current
|
|
||||||
user in one and the current timestamp in the other. This allows triggers to
|
|
||||||
be written on INSERT events to automatically track creation of records in a
|
|
||||||
transaction table for example. It could also be used as a <quote>last updated</>
|
|
||||||
function if used in an UPDATE event.
|
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -136,8 +141,8 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Trigger functions return HeapTuple to the calling Executor. This
|
Trigger functions return a HeapTuple to the calling Executor. The return
|
||||||
is ignored for triggers fired after an INSERT, DELETE or UPDATE operation
|
value is ignored for triggers fired AFTER an operation,
|
||||||
but it allows BEFORE triggers to:
|
but it allows BEFORE triggers to:
|
||||||
|
|
||||||
<itemizedlist>
|
<itemizedlist>
|
||||||
@ -150,33 +155,41 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Return a pointer to another tuple (INSERT and UPDATE only) which will
|
For INSERT and UPDATE triggers only, the returned tuple becomes the
|
||||||
be inserted (as the new version of the updated tuple if UPDATE) instead
|
tuple which will be inserted or will replace the tuple being updated.
|
||||||
of original tuple.
|
This allows the trigger function to modify the row being inserted or
|
||||||
|
updated.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
|
|
||||||
|
A BEFORE trigger that does not intend to cause either of these behaviors
|
||||||
|
must be careful to return the same NEW tuple it is passed.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Note that there is no initialization performed by the CREATE TRIGGER
|
Note that there is no initialization performed by the CREATE TRIGGER
|
||||||
handler. This will be changed in the future. Also, if more than one trigger
|
handler. This may be changed in the future.
|
||||||
is defined for the same event on the same relation, the order of trigger
|
</para>
|
||||||
firing is unpredictable. This may be changed in the future.
|
|
||||||
|
<para>
|
||||||
|
If more than one trigger
|
||||||
|
is defined for the same event on the same relation, the triggers will
|
||||||
|
be fired in alphabetical order by name. In the case of BEFORE triggers,
|
||||||
|
the possibly-modified tuple returned by each trigger becomes the input
|
||||||
|
to the next trigger. If any BEFORE trigger returns NULL, the operation
|
||||||
|
is abandoned and subsequent triggers are not fired.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
If a trigger function executes SQL-queries (using SPI) then these queries
|
If a trigger function executes SQL-queries (using SPI) then these queries
|
||||||
may fire triggers again. This is known as cascading triggers. There is no
|
may fire triggers again. This is known as cascading triggers. There is no
|
||||||
explicit limitation on the number of cascade levels.
|
direct limitation on the number of cascade levels. It is possible for
|
||||||
</para>
|
cascades to cause recursive invocation of the same trigger --- for
|
||||||
|
example, an INSERT trigger might execute a query that inserts an
|
||||||
<para>
|
additional tuple into the same table, causing the INSERT trigger to be
|
||||||
If a trigger is fired by INSERT and inserts a new tuple in the same
|
fired again. It is the trigger programmer's
|
||||||
relation then this trigger will be fired again. Currently, there is nothing
|
responsibility to avoid infinite recursion in such scenarios.
|
||||||
provided for synchronization (etc) of these cases but this may change. At
|
|
||||||
the moment, there is function funny_dup17() in the regress tests which uses
|
|
||||||
some techniques to stop recursion (cascading) on itself...
|
|
||||||
</para>
|
</para>
|
||||||
</sect1>
|
</sect1>
|
||||||
|
|
||||||
@ -326,7 +339,7 @@ typedef struct TriggerData
|
|||||||
<para>
|
<para>
|
||||||
is a pointer to structure describing the triggered relation. Look at
|
is a pointer to structure describing the triggered relation. Look at
|
||||||
src/include/utils/rel.h for details about this structure. The most
|
src/include/utils/rel.h for details about this structure. The most
|
||||||
interest things are tg_relation->rd_att (descriptor of the relation
|
interesting things are tg_relation->rd_att (descriptor of the relation
|
||||||
tuples) and tg_relation->rd_rel->relname (relation's name. This is not
|
tuples) and tg_relation->rd_rel->relname (relation's name. This is not
|
||||||
char*, but NameData. Use SPI_getrelname(tg_relation) to get char* if
|
char*, but NameData. Use SPI_getrelname(tg_relation) to get char* if
|
||||||
you need a copy of name).
|
you need a copy of name).
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/catalog/indexing.c,v 1.91 2002/04/18 20:01:09 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/catalog/indexing.c,v 1.92 2002/04/19 16:36:08 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -74,7 +74,7 @@ char *Name_pg_shadow_indices[Num_pg_shadow_indices] =
|
|||||||
char *Name_pg_statistic_indices[Num_pg_statistic_indices] =
|
char *Name_pg_statistic_indices[Num_pg_statistic_indices] =
|
||||||
{StatisticRelidAttnumIndex};
|
{StatisticRelidAttnumIndex};
|
||||||
char *Name_pg_trigger_indices[Num_pg_trigger_indices] =
|
char *Name_pg_trigger_indices[Num_pg_trigger_indices] =
|
||||||
{TriggerRelidIndex, TriggerConstrNameIndex, TriggerConstrRelidIndex, TriggerOidIndex};
|
{TriggerRelidNameIndex, TriggerConstrNameIndex, TriggerConstrRelidIndex, TriggerOidIndex};
|
||||||
char *Name_pg_type_indices[Num_pg_type_indices] =
|
char *Name_pg_type_indices[Num_pg_type_indices] =
|
||||||
{TypeNameNspIndex, TypeOidIndex};
|
{TypeNameNspIndex, TypeOidIndex};
|
||||||
char *Name_pg_description_indices[Num_pg_description_indices] =
|
char *Name_pg_description_indices[Num_pg_description_indices] =
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Copyright (c) 1999-2001, PostgreSQL Global Development Group
|
* Copyright (c) 1999-2001, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/commands/comment.c,v 1.42 2002/04/18 20:01:09 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/commands/comment.c,v 1.43 2002/04/19 16:36:08 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -753,7 +753,7 @@ CommentTrigger(List *qualname, char *comment)
|
|||||||
Relation pg_trigger,
|
Relation pg_trigger,
|
||||||
relation;
|
relation;
|
||||||
HeapTuple triggertuple;
|
HeapTuple triggertuple;
|
||||||
HeapScanDesc scan;
|
SysScanDesc scan;
|
||||||
ScanKeyData entry[2];
|
ScanKeyData entry[2];
|
||||||
Oid oid;
|
Oid oid;
|
||||||
|
|
||||||
@ -774,17 +774,22 @@ CommentTrigger(List *qualname, char *comment)
|
|||||||
elog(ERROR, "you are not permitted to comment on trigger '%s' for relation '%s'",
|
elog(ERROR, "you are not permitted to comment on trigger '%s' for relation '%s'",
|
||||||
trigname, RelationGetRelationName(relation));
|
trigname, RelationGetRelationName(relation));
|
||||||
|
|
||||||
/* Fetch the trigger oid from pg_trigger */
|
/*
|
||||||
|
* Fetch the trigger tuple from pg_trigger. There can be only one
|
||||||
|
* because of the unique index.
|
||||||
|
*/
|
||||||
pg_trigger = heap_openr(TriggerRelationName, AccessShareLock);
|
pg_trigger = heap_openr(TriggerRelationName, AccessShareLock);
|
||||||
ScanKeyEntryInitialize(&entry[0], 0x0, Anum_pg_trigger_tgrelid,
|
ScanKeyEntryInitialize(&entry[0], 0x0,
|
||||||
|
Anum_pg_trigger_tgrelid,
|
||||||
F_OIDEQ,
|
F_OIDEQ,
|
||||||
ObjectIdGetDatum(RelationGetRelid(relation)));
|
ObjectIdGetDatum(RelationGetRelid(relation)));
|
||||||
ScanKeyEntryInitialize(&entry[1], 0x0, Anum_pg_trigger_tgname,
|
ScanKeyEntryInitialize(&entry[1], 0x0,
|
||||||
|
Anum_pg_trigger_tgname,
|
||||||
F_NAMEEQ,
|
F_NAMEEQ,
|
||||||
CStringGetDatum(trigname));
|
CStringGetDatum(trigname));
|
||||||
scan = heap_beginscan(pg_trigger, 0, SnapshotNow, 2, entry);
|
scan = systable_beginscan(pg_trigger, TriggerRelidNameIndex, true,
|
||||||
triggertuple = heap_getnext(scan, 0);
|
SnapshotNow, 2, entry);
|
||||||
|
triggertuple = systable_getnext(scan);
|
||||||
|
|
||||||
/* If no trigger exists for the relation specified, notify user */
|
/* If no trigger exists for the relation specified, notify user */
|
||||||
|
|
||||||
@ -794,7 +799,7 @@ CommentTrigger(List *qualname, char *comment)
|
|||||||
|
|
||||||
oid = triggertuple->t_data->t_oid;
|
oid = triggertuple->t_data->t_oid;
|
||||||
|
|
||||||
heap_endscan(scan);
|
systable_endscan(scan);
|
||||||
|
|
||||||
/* Create the comments with the pg_trigger oid */
|
/* Create the comments with the pg_trigger oid */
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.3 2002/04/18 20:01:09 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.4 2002/04/19 16:36:08 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -2909,10 +2909,10 @@ update_ri_trigger_args(Oid relid,
|
|||||||
if (fk_scan)
|
if (fk_scan)
|
||||||
irel = index_openr(TriggerConstrRelidIndex);
|
irel = index_openr(TriggerConstrRelidIndex);
|
||||||
else
|
else
|
||||||
irel = index_openr(TriggerRelidIndex);
|
irel = index_openr(TriggerRelidNameIndex);
|
||||||
|
|
||||||
ScanKeyEntryInitialize(&skey[0], 0x0,
|
ScanKeyEntryInitialize(&skey[0], 0x0,
|
||||||
1, /* always column 1 of index */
|
1, /* column 1 of index in either case */
|
||||||
F_OIDEQ,
|
F_OIDEQ,
|
||||||
ObjectIdGetDatum(relid));
|
ObjectIdGetDatum(relid));
|
||||||
idxtgscan = index_beginscan(irel, false, 1, skey);
|
idxtgscan = index_beginscan(irel, false, 1, skey);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.113 2002/04/12 20:38:24 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.114 2002/04/19 16:36:08 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -90,14 +90,15 @@ CreateTrigger(CreateTrigStmt *stmt)
|
|||||||
elog(ERROR, "permission denied");
|
elog(ERROR, "permission denied");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If trigger is a constraint, user trigger name as constraint name
|
* If trigger is an RI constraint, use trigger name as constraint name
|
||||||
* and build a unique trigger name instead.
|
* and build a unique trigger name instead.
|
||||||
*/
|
*/
|
||||||
if (stmt->isconstraint)
|
if (stmt->isconstraint)
|
||||||
{
|
{
|
||||||
constrname = stmt->trigname;
|
constrname = stmt->trigname;
|
||||||
|
snprintf(constrtrigname, sizeof(constrtrigname),
|
||||||
|
"RI_ConstraintTrigger_%u", newoid());
|
||||||
stmt->trigname = constrtrigname;
|
stmt->trigname = constrtrigname;
|
||||||
sprintf(constrtrigname, "RI_ConstraintTrigger_%u", newoid());
|
|
||||||
|
|
||||||
if (stmt->constrrel != NULL)
|
if (stmt->constrrel != NULL)
|
||||||
constrrelid = RangeVarGetRelid(stmt->constrrel, false);
|
constrrelid = RangeVarGetRelid(stmt->constrrel, false);
|
||||||
@ -139,15 +140,20 @@ CreateTrigger(CreateTrigStmt *stmt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Scan pg_trigger for existing triggers on relation. NOTE that this
|
* Scan pg_trigger for existing triggers on relation. We do this mainly
|
||||||
* is cool only because we have AccessExclusiveLock on the relation,
|
* because we must count them; a secondary benefit is to give a nice
|
||||||
* so the trigger set won't be changing underneath us.
|
* error message if there's already a trigger of the same name. (The
|
||||||
|
* unique index on tgrelid/tgname would complain anyway.)
|
||||||
|
*
|
||||||
|
* NOTE that this is cool only because we have AccessExclusiveLock on the
|
||||||
|
* relation, so the trigger set won't be changing underneath us.
|
||||||
*/
|
*/
|
||||||
tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
|
tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
|
||||||
ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgrelid,
|
ScanKeyEntryInitialize(&key, 0,
|
||||||
|
Anum_pg_trigger_tgrelid,
|
||||||
F_OIDEQ,
|
F_OIDEQ,
|
||||||
ObjectIdGetDatum(RelationGetRelid(rel)));
|
ObjectIdGetDatum(RelationGetRelid(rel)));
|
||||||
tgscan = systable_beginscan(tgrel, TriggerRelidIndex, true,
|
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
|
||||||
SnapshotNow, 1, &key);
|
SnapshotNow, 1, &key);
|
||||||
while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
|
while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
|
||||||
{
|
{
|
||||||
@ -336,15 +342,20 @@ DropTrigger(Oid relid, const char *trigname)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Search pg_trigger, delete target trigger, count remaining triggers
|
* Search pg_trigger, delete target trigger, count remaining triggers
|
||||||
* for relation. Note this is OK only because we have
|
* for relation. (Although we could fetch and delete the target
|
||||||
* AccessExclusiveLock on the rel, so no one else is creating/deleting
|
* trigger directly, we'd still have to scan the remaining triggers,
|
||||||
* triggers on this rel at the same time.
|
* so we may as well do both in one indexscan.)
|
||||||
|
*
|
||||||
|
* Note this is OK only because we have AccessExclusiveLock on the rel,
|
||||||
|
* so no one else is creating/deleting triggers on this rel at the same
|
||||||
|
* time.
|
||||||
*/
|
*/
|
||||||
tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
|
tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
|
||||||
ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgrelid,
|
ScanKeyEntryInitialize(&key, 0,
|
||||||
|
Anum_pg_trigger_tgrelid,
|
||||||
F_OIDEQ,
|
F_OIDEQ,
|
||||||
ObjectIdGetDatum(relid));
|
ObjectIdGetDatum(relid));
|
||||||
tgscan = systable_beginscan(tgrel, TriggerRelidIndex, true,
|
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
|
||||||
SnapshotNow, 1, &key);
|
SnapshotNow, 1, &key);
|
||||||
while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
|
while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
|
||||||
{
|
{
|
||||||
@ -409,10 +420,11 @@ RelationRemoveTriggers(Relation rel)
|
|||||||
bool found = false;
|
bool found = false;
|
||||||
|
|
||||||
tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
|
tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
|
||||||
ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgrelid,
|
ScanKeyEntryInitialize(&key, 0,
|
||||||
|
Anum_pg_trigger_tgrelid,
|
||||||
F_OIDEQ,
|
F_OIDEQ,
|
||||||
ObjectIdGetDatum(RelationGetRelid(rel)));
|
ObjectIdGetDatum(RelationGetRelid(rel)));
|
||||||
tgscan = systable_beginscan(tgrel, TriggerRelidIndex, true,
|
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
|
||||||
SnapshotNow, 1, &key);
|
SnapshotNow, 1, &key);
|
||||||
|
|
||||||
while (HeapTupleIsValid(tup = systable_getnext(tgscan)))
|
while (HeapTupleIsValid(tup = systable_getnext(tgscan)))
|
||||||
@ -462,7 +474,8 @@ RelationRemoveTriggers(Relation rel)
|
|||||||
/*
|
/*
|
||||||
* Also drop all constraint triggers referencing this relation
|
* Also drop all constraint triggers referencing this relation
|
||||||
*/
|
*/
|
||||||
ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgconstrrelid,
|
ScanKeyEntryInitialize(&key, 0,
|
||||||
|
Anum_pg_trigger_tgconstrrelid,
|
||||||
F_OIDEQ,
|
F_OIDEQ,
|
||||||
ObjectIdGetDatum(RelationGetRelid(rel)));
|
ObjectIdGetDatum(RelationGetRelid(rel)));
|
||||||
tgscan = systable_beginscan(tgrel, TriggerConstrRelidIndex, true,
|
tgscan = systable_beginscan(tgrel, TriggerConstrRelidIndex, true,
|
||||||
@ -502,7 +515,7 @@ RelationBuildTriggers(Relation relation)
|
|||||||
{
|
{
|
||||||
TriggerDesc *trigdesc;
|
TriggerDesc *trigdesc;
|
||||||
int ntrigs = relation->rd_rel->reltriggers;
|
int ntrigs = relation->rd_rel->reltriggers;
|
||||||
Trigger *triggers = NULL;
|
Trigger *triggers;
|
||||||
int found = 0;
|
int found = 0;
|
||||||
Relation tgrel;
|
Relation tgrel;
|
||||||
ScanKeyData skey;
|
ScanKeyData skey;
|
||||||
@ -511,6 +524,15 @@ RelationBuildTriggers(Relation relation)
|
|||||||
struct varlena *val;
|
struct varlena *val;
|
||||||
bool isnull;
|
bool isnull;
|
||||||
|
|
||||||
|
triggers = (Trigger *) MemoryContextAlloc(CacheMemoryContext,
|
||||||
|
ntrigs * sizeof(Trigger));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note: since we scan the triggers using TriggerRelidNameIndex,
|
||||||
|
* we will be reading the triggers in name order, except possibly
|
||||||
|
* during emergency-recovery operations (ie, IsIgnoringSystemIndexes).
|
||||||
|
* This in turn ensures that triggers will be fired in name order.
|
||||||
|
*/
|
||||||
ScanKeyEntryInitialize(&skey,
|
ScanKeyEntryInitialize(&skey,
|
||||||
(bits16) 0x0,
|
(bits16) 0x0,
|
||||||
(AttrNumber) Anum_pg_trigger_tgrelid,
|
(AttrNumber) Anum_pg_trigger_tgrelid,
|
||||||
@ -518,7 +540,7 @@ RelationBuildTriggers(Relation relation)
|
|||||||
ObjectIdGetDatum(RelationGetRelid(relation)));
|
ObjectIdGetDatum(RelationGetRelid(relation)));
|
||||||
|
|
||||||
tgrel = heap_openr(TriggerRelationName, AccessShareLock);
|
tgrel = heap_openr(TriggerRelationName, AccessShareLock);
|
||||||
tgscan = systable_beginscan(tgrel, TriggerRelidIndex, true,
|
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
|
||||||
SnapshotNow, 1, &skey);
|
SnapshotNow, 1, &skey);
|
||||||
|
|
||||||
while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
|
while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
|
||||||
@ -526,16 +548,9 @@ RelationBuildTriggers(Relation relation)
|
|||||||
Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
|
Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
|
||||||
Trigger *build;
|
Trigger *build;
|
||||||
|
|
||||||
if (found == ntrigs)
|
if (found >= ntrigs)
|
||||||
elog(ERROR, "RelationBuildTriggers: unexpected record found for rel %s",
|
elog(ERROR, "RelationBuildTriggers: unexpected record found for rel %s",
|
||||||
RelationGetRelationName(relation));
|
RelationGetRelationName(relation));
|
||||||
|
|
||||||
if (triggers == NULL)
|
|
||||||
triggers = (Trigger *) MemoryContextAlloc(CacheMemoryContext,
|
|
||||||
sizeof(Trigger));
|
|
||||||
else
|
|
||||||
triggers = (Trigger *) repalloc(triggers,
|
|
||||||
(found + 1) * sizeof(Trigger));
|
|
||||||
build = &(triggers[found]);
|
build = &(triggers[found]);
|
||||||
|
|
||||||
build->tgoid = htup->t_data->t_oid;
|
build->tgoid = htup->t_data->t_oid;
|
||||||
@ -730,6 +745,9 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
|
|||||||
* We need not examine the "index" data, just the trigger array
|
* We need not examine the "index" data, just the trigger array
|
||||||
* itself; if we have the same triggers with the same types, the
|
* itself; if we have the same triggers with the same types, the
|
||||||
* derived index data should match.
|
* derived index data should match.
|
||||||
|
*
|
||||||
|
* As of 7.3 we assume trigger set ordering is significant in the
|
||||||
|
* comparison; so we just compare corresponding slots of the two sets.
|
||||||
*/
|
*/
|
||||||
if (trigdesc1 != NULL)
|
if (trigdesc1 != NULL)
|
||||||
{
|
{
|
||||||
@ -740,21 +758,9 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
|
|||||||
for (i = 0; i < trigdesc1->numtriggers; i++)
|
for (i = 0; i < trigdesc1->numtriggers; i++)
|
||||||
{
|
{
|
||||||
Trigger *trig1 = trigdesc1->triggers + i;
|
Trigger *trig1 = trigdesc1->triggers + i;
|
||||||
Trigger *trig2 = NULL;
|
Trigger *trig2 = trigdesc2->triggers + i;
|
||||||
|
|
||||||
/*
|
if (trig1->tgoid != trig2->tgoid)
|
||||||
* We can't assume that the triggers are always read from
|
|
||||||
* pg_trigger in the same order; so use the trigger OIDs to
|
|
||||||
* identify the triggers to compare. (We assume here that the
|
|
||||||
* same OID won't appear twice in either trigger set.)
|
|
||||||
*/
|
|
||||||
for (j = 0; j < trigdesc2->numtriggers; j++)
|
|
||||||
{
|
|
||||||
trig2 = trigdesc2->triggers + j;
|
|
||||||
if (trig1->tgoid == trig2->tgoid)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (j >= trigdesc2->numtriggers)
|
|
||||||
return false;
|
return false;
|
||||||
if (strcmp(trig1->tgname, trig2->tgname) != 0)
|
if (strcmp(trig1->tgname, trig2->tgname) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
74
src/backend/utils/cache/relcache.c
vendored
74
src/backend/utils/cache/relcache.c
vendored
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/utils/cache/relcache.c,v 1.161 2002/04/18 20:01:09 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/utils/cache/relcache.c,v 1.162 2002/04/19 16:36:08 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -635,10 +635,10 @@ RelationBuildRuleLock(Relation relation)
|
|||||||
{
|
{
|
||||||
MemoryContext rulescxt;
|
MemoryContext rulescxt;
|
||||||
MemoryContext oldcxt;
|
MemoryContext oldcxt;
|
||||||
HeapTuple pg_rewrite_tuple;
|
HeapTuple rewrite_tuple;
|
||||||
Relation pg_rewrite_desc;
|
Relation rewrite_desc;
|
||||||
TupleDesc pg_rewrite_tupdesc;
|
TupleDesc rewrite_tupdesc;
|
||||||
SysScanDesc pg_rewrite_scan;
|
SysScanDesc rewrite_scan;
|
||||||
ScanKeyData key;
|
ScanKeyData key;
|
||||||
RuleLock *rulelock;
|
RuleLock *rulelock;
|
||||||
int numlocks;
|
int numlocks;
|
||||||
@ -657,7 +657,7 @@ RelationBuildRuleLock(Relation relation)
|
|||||||
relation->rd_rulescxt = rulescxt;
|
relation->rd_rulescxt = rulescxt;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* form an array to hold the rewrite rules (the array is extended if
|
* allocate an array to hold the rewrite rules (the array is extended if
|
||||||
* necessary)
|
* necessary)
|
||||||
*/
|
*/
|
||||||
maxlocks = 4;
|
maxlocks = 4;
|
||||||
@ -675,18 +675,22 @@ RelationBuildRuleLock(Relation relation)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* open pg_rewrite and begin a scan
|
* open pg_rewrite and begin a scan
|
||||||
|
*
|
||||||
|
* Note: since we scan the rules using RewriteRelRulenameIndex,
|
||||||
|
* we will be reading the rules in name order, except possibly
|
||||||
|
* during emergency-recovery operations (ie, IsIgnoringSystemIndexes).
|
||||||
|
* This in turn ensures that rules will be fired in name order.
|
||||||
*/
|
*/
|
||||||
pg_rewrite_desc = heap_openr(RewriteRelationName, AccessShareLock);
|
rewrite_desc = heap_openr(RewriteRelationName, AccessShareLock);
|
||||||
pg_rewrite_tupdesc = RelationGetDescr(pg_rewrite_desc);
|
rewrite_tupdesc = RelationGetDescr(rewrite_desc);
|
||||||
pg_rewrite_scan = systable_beginscan(pg_rewrite_desc,
|
rewrite_scan = systable_beginscan(rewrite_desc,
|
||||||
RewriteRelRulenameIndex,
|
RewriteRelRulenameIndex,
|
||||||
criticalRelcachesBuilt,
|
true, SnapshotNow,
|
||||||
SnapshotNow,
|
1, &key);
|
||||||
1, &key);
|
|
||||||
|
|
||||||
while (HeapTupleIsValid(pg_rewrite_tuple = systable_getnext(pg_rewrite_scan)))
|
while (HeapTupleIsValid(rewrite_tuple = systable_getnext(rewrite_scan)))
|
||||||
{
|
{
|
||||||
Form_pg_rewrite rewrite_form = (Form_pg_rewrite) GETSTRUCT(pg_rewrite_tuple);
|
Form_pg_rewrite rewrite_form = (Form_pg_rewrite) GETSTRUCT(rewrite_tuple);
|
||||||
bool isnull;
|
bool isnull;
|
||||||
Datum ruleaction;
|
Datum ruleaction;
|
||||||
Datum rule_evqual;
|
Datum rule_evqual;
|
||||||
@ -697,7 +701,7 @@ RelationBuildRuleLock(Relation relation)
|
|||||||
rule = (RewriteRule *) MemoryContextAlloc(rulescxt,
|
rule = (RewriteRule *) MemoryContextAlloc(rulescxt,
|
||||||
sizeof(RewriteRule));
|
sizeof(RewriteRule));
|
||||||
|
|
||||||
rule->ruleId = pg_rewrite_tuple->t_data->t_oid;
|
rule->ruleId = rewrite_tuple->t_data->t_oid;
|
||||||
|
|
||||||
rule->event = rewrite_form->ev_type - '0';
|
rule->event = rewrite_form->ev_type - '0';
|
||||||
rule->attrno = rewrite_form->ev_attr;
|
rule->attrno = rewrite_form->ev_attr;
|
||||||
@ -705,9 +709,9 @@ RelationBuildRuleLock(Relation relation)
|
|||||||
|
|
||||||
/* Must use heap_getattr to fetch ev_qual and ev_action */
|
/* Must use heap_getattr to fetch ev_qual and ev_action */
|
||||||
|
|
||||||
ruleaction = heap_getattr(pg_rewrite_tuple,
|
ruleaction = heap_getattr(rewrite_tuple,
|
||||||
Anum_pg_rewrite_ev_action,
|
Anum_pg_rewrite_ev_action,
|
||||||
pg_rewrite_tupdesc,
|
rewrite_tupdesc,
|
||||||
&isnull);
|
&isnull);
|
||||||
Assert(!isnull);
|
Assert(!isnull);
|
||||||
ruleaction_str = DatumGetCString(DirectFunctionCall1(textout,
|
ruleaction_str = DatumGetCString(DirectFunctionCall1(textout,
|
||||||
@ -717,13 +721,13 @@ RelationBuildRuleLock(Relation relation)
|
|||||||
MemoryContextSwitchTo(oldcxt);
|
MemoryContextSwitchTo(oldcxt);
|
||||||
pfree(ruleaction_str);
|
pfree(ruleaction_str);
|
||||||
|
|
||||||
rule_evqual = heap_getattr(pg_rewrite_tuple,
|
rule_evqual = heap_getattr(rewrite_tuple,
|
||||||
Anum_pg_rewrite_ev_qual,
|
Anum_pg_rewrite_ev_qual,
|
||||||
pg_rewrite_tupdesc,
|
rewrite_tupdesc,
|
||||||
&isnull);
|
&isnull);
|
||||||
Assert(!isnull);
|
Assert(!isnull);
|
||||||
rule_evqual_str = DatumGetCString(DirectFunctionCall1(textout,
|
rule_evqual_str = DatumGetCString(DirectFunctionCall1(textout,
|
||||||
rule_evqual));
|
rule_evqual));
|
||||||
oldcxt = MemoryContextSwitchTo(rulescxt);
|
oldcxt = MemoryContextSwitchTo(rulescxt);
|
||||||
rule->qual = (Node *) stringToNode(rule_evqual_str);
|
rule->qual = (Node *) stringToNode(rule_evqual_str);
|
||||||
MemoryContextSwitchTo(oldcxt);
|
MemoryContextSwitchTo(oldcxt);
|
||||||
@ -741,8 +745,8 @@ RelationBuildRuleLock(Relation relation)
|
|||||||
/*
|
/*
|
||||||
* end the scan and close the attribute relation
|
* end the scan and close the attribute relation
|
||||||
*/
|
*/
|
||||||
systable_endscan(pg_rewrite_scan);
|
systable_endscan(rewrite_scan);
|
||||||
heap_close(pg_rewrite_desc, AccessShareLock);
|
heap_close(rewrite_desc, AccessShareLock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* form a RuleLock and insert into relation
|
* form a RuleLock and insert into relation
|
||||||
@ -764,9 +768,13 @@ RelationBuildRuleLock(Relation relation)
|
|||||||
static bool
|
static bool
|
||||||
equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2)
|
equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2)
|
||||||
{
|
{
|
||||||
int i,
|
int i;
|
||||||
j;
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* As of 7.3 we assume the rule ordering is repeatable,
|
||||||
|
* because RelationBuildRuleLock should read 'em in a
|
||||||
|
* consistent order. So just compare corresponding slots.
|
||||||
|
*/
|
||||||
if (rlock1 != NULL)
|
if (rlock1 != NULL)
|
||||||
{
|
{
|
||||||
if (rlock2 == NULL)
|
if (rlock2 == NULL)
|
||||||
@ -776,21 +784,9 @@ equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2)
|
|||||||
for (i = 0; i < rlock1->numLocks; i++)
|
for (i = 0; i < rlock1->numLocks; i++)
|
||||||
{
|
{
|
||||||
RewriteRule *rule1 = rlock1->rules[i];
|
RewriteRule *rule1 = rlock1->rules[i];
|
||||||
RewriteRule *rule2 = NULL;
|
RewriteRule *rule2 = rlock2->rules[i];
|
||||||
|
|
||||||
/*
|
if (rule1->ruleId != rule2->ruleId)
|
||||||
* We can't assume that the rules are always read from
|
|
||||||
* pg_rewrite in the same order; so use the rule OIDs to
|
|
||||||
* identify the rules to compare. (We assume here that the
|
|
||||||
* same OID won't appear twice in either ruleset.)
|
|
||||||
*/
|
|
||||||
for (j = 0; j < rlock2->numLocks; j++)
|
|
||||||
{
|
|
||||||
rule2 = rlock2->rules[j];
|
|
||||||
if (rule1->ruleId == rule2->ruleId)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (j >= rlock2->numLocks)
|
|
||||||
return false;
|
return false;
|
||||||
if (rule1->event != rule2->event)
|
if (rule1->event != rule2->event)
|
||||||
return false;
|
return false;
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $Id: catversion.h,v 1.118 2002/04/18 20:01:10 tgl Exp $
|
* $Id: catversion.h,v 1.119 2002/04/19 16:36:08 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -53,6 +53,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* yyyymmddN */
|
/* yyyymmddN */
|
||||||
#define CATALOG_VERSION_NO 200204181
|
#define CATALOG_VERSION_NO 200204182
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $Id: indexing.h,v 1.65 2002/04/18 20:01:10 tgl Exp $
|
* $Id: indexing.h,v 1.66 2002/04/19 16:36:08 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -86,7 +86,7 @@
|
|||||||
#define StatisticRelidAttnumIndex "pg_statistic_relid_att_index"
|
#define StatisticRelidAttnumIndex "pg_statistic_relid_att_index"
|
||||||
#define TriggerConstrNameIndex "pg_trigger_tgconstrname_index"
|
#define TriggerConstrNameIndex "pg_trigger_tgconstrname_index"
|
||||||
#define TriggerConstrRelidIndex "pg_trigger_tgconstrrelid_index"
|
#define TriggerConstrRelidIndex "pg_trigger_tgconstrrelid_index"
|
||||||
#define TriggerRelidIndex "pg_trigger_tgrelid_index"
|
#define TriggerRelidNameIndex "pg_trigger_tgrelid_tgname_index"
|
||||||
#define TriggerOidIndex "pg_trigger_oid_index"
|
#define TriggerOidIndex "pg_trigger_oid_index"
|
||||||
#define TypeNameNspIndex "pg_type_typname_nsp_index"
|
#define TypeNameNspIndex "pg_type_typname_nsp_index"
|
||||||
#define TypeOidIndex "pg_type_oid_index"
|
#define TypeOidIndex "pg_type_oid_index"
|
||||||
@ -182,9 +182,11 @@ DECLARE_UNIQUE_INDEX(pg_rewrite_rel_rulename_index on pg_rewrite using btree(ev_
|
|||||||
DECLARE_UNIQUE_INDEX(pg_shadow_usename_index on pg_shadow using btree(usename name_ops));
|
DECLARE_UNIQUE_INDEX(pg_shadow_usename_index on pg_shadow using btree(usename name_ops));
|
||||||
DECLARE_UNIQUE_INDEX(pg_shadow_usesysid_index on pg_shadow using btree(usesysid int4_ops));
|
DECLARE_UNIQUE_INDEX(pg_shadow_usesysid_index on pg_shadow using btree(usesysid int4_ops));
|
||||||
DECLARE_UNIQUE_INDEX(pg_statistic_relid_att_index on pg_statistic using btree(starelid oid_ops, staattnum int2_ops));
|
DECLARE_UNIQUE_INDEX(pg_statistic_relid_att_index on pg_statistic using btree(starelid oid_ops, staattnum int2_ops));
|
||||||
|
/* This following index is not used for a cache and is not unique */
|
||||||
DECLARE_INDEX(pg_trigger_tgconstrname_index on pg_trigger using btree(tgconstrname name_ops));
|
DECLARE_INDEX(pg_trigger_tgconstrname_index on pg_trigger using btree(tgconstrname name_ops));
|
||||||
|
/* This following index is not used for a cache and is not unique */
|
||||||
DECLARE_INDEX(pg_trigger_tgconstrrelid_index on pg_trigger using btree(tgconstrrelid oid_ops));
|
DECLARE_INDEX(pg_trigger_tgconstrrelid_index on pg_trigger using btree(tgconstrrelid oid_ops));
|
||||||
DECLARE_INDEX(pg_trigger_tgrelid_index on pg_trigger using btree(tgrelid oid_ops));
|
DECLARE_UNIQUE_INDEX(pg_trigger_tgrelid_tgname_index on pg_trigger using btree(tgrelid oid_ops, tgname name_ops));
|
||||||
DECLARE_UNIQUE_INDEX(pg_trigger_oid_index on pg_trigger using btree(oid oid_ops));
|
DECLARE_UNIQUE_INDEX(pg_trigger_oid_index on pg_trigger using btree(oid oid_ops));
|
||||||
DECLARE_UNIQUE_INDEX(pg_type_oid_index on pg_type using btree(oid oid_ops));
|
DECLARE_UNIQUE_INDEX(pg_type_oid_index on pg_type using btree(oid oid_ops));
|
||||||
DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index on pg_type using btree(typname name_ops, typnamespace oid_ops));
|
DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index on pg_type using btree(typname name_ops, typnamespace oid_ops));
|
||||||
|
@ -899,7 +899,7 @@ delete from pktable where base1=2;
|
|||||||
ERROR: <unnamed> referential integrity violation - key in pktable still referenced from pktable
|
ERROR: <unnamed> referential integrity violation - key in pktable still referenced from pktable
|
||||||
-- fails (1,1) is being referenced (twice)
|
-- fails (1,1) is being referenced (twice)
|
||||||
update pktable set base1=3 where base1=1;
|
update pktable set base1=3 where base1=1;
|
||||||
ERROR: <unnamed> referential integrity violation - key in pktable still referenced from pktable
|
ERROR: <unnamed> referential integrity violation - key referenced from pktable not found in pktable
|
||||||
-- this sequence of two deletes will work, since after the first there will be no (2,*) references
|
-- this sequence of two deletes will work, since after the first there will be no (2,*) references
|
||||||
delete from pktable where base2=2;
|
delete from pktable where base2=2;
|
||||||
delete from pktable where base1=2;
|
delete from pktable where base1=2;
|
||||||
|
Reference in New Issue
Block a user