<!--  --> 
<sect1 id="triggers"><title>&slony1; Trigger Handling</title>

<indexterm><primary>trigger handling</primary></indexterm>

<para> In &postgres; version 8.3, new functionality was added where
triggers and rules may have their behaviour altered via <command>ALTER
TABLE</command>, to specify  the following alterations:</para>

<itemizedlist>

<listitem><para> <command> DISABLE TRIGGER trigger_name</command>  </para></listitem>
<listitem><para> <command> ENABLE TRIGGER trigger_name</command>  </para></listitem>
<listitem><para> <command> ENABLE REPLICA TRIGGER trigger_name</command>  </para></listitem>
<listitem><para> <command> ENABLE ALWAYS TRIGGER trigger_name</command>  </para></listitem>
<listitem><para> <command> DISABLE RULE rewrite_rule_name</command>  </para></listitem>
<listitem><para> <command> ENABLE RULE rewrite_rule_name</command>  </para></listitem>
<listitem><para> <command> ENABLE REPLICA RULE rewrite_rule_name</command>  </para></listitem>
<listitem><para> <command> ENABLE ALWAYS RULE rewrite_rule_name</command>  </para></listitem>

</itemizedlist>

<para> A new GUC variable, <envar>session_replication_role</envar>
controls whether the session is in <envar>origin</envar>,
<envar>replica</envar>, or <envar>local</envar> mode, which then, in
combination with the above enabling/disabling options, controls
whether or not the trigger function actually runs. </para>

<para> We may characterize when triggers fire, under &slony1;
replication, based on the following table; the same rules apply to
&postgres; rules.</para>

<table id="triggerbehaviour"> <title> Trigger Behaviour </title>
<tgroup cols="7">
<thead>
 <row> <entry>Trigger Form</entry> <entry>When Established</entry>  <entry>Log Trigger</entry> <entry>denyaccess Trigger</entry>  <entry>Action - origin</entry> <entry>Action - replica</entry>  <entry> Action - local</entry> </row>
</thead>
<tbody>
<row> <entry>DISABLE TRIGGER</entry> <entry>User request</entry> <entry>disabled on subscriber</entry> <entry>enabled on subscriber</entry> <entry>does not fire</entry>  <entry>does not fire</entry>  <entry>does not fire</entry> </row>
<row> <entry>ENABLE TRIGGER</entry> <entry>Default</entry> <entry>enabled on subscriber</entry> <entry>disabled on subscriber</entry> <entry>fires</entry>  <entry>does not fire</entry>  <entry>fires</entry> </row>
<row> <entry>ENABLE REPLICA TRIGGER</entry> <entry>User request</entry> <entry>inappropriate</entry> <entry>inappropriate</entry> <entry>does not fire</entry>  <entry>fires</entry>  <entry>does not fire</entry> </row>
<row> <entry>ENABLE ALWAYS TRIGGER</entry> <entry>User request</entry> <entry>inappropriate</entry> <entry>inappropriate</entry> <entry>fires</entry>  <entry>fires</entry>  <entry>fires</entry> </row>
</tbody>
</tgroup>
</table>

<para> There are, correspondingly, now, several ways in which &slony1;
interacts with this.  Let us outline those times that are interesting:
</para>

<itemizedlist>

<listitem><para> Before replication is set up,
<emphasis>every</emphasis> database starts out in
<quote>origin</quote> status, and, by default, all triggers are of the
<command>ENABLE TRIGGER</command> form, so they all run, as is normal
in a system uninvolved in replication. </para> </listitem>

<listitem><para> When a &slony1; subscription is set up, on the origin
node, both the <function>logtrigger</function> and
<function>denyaccess</function> triggers are added, the former being
enabled, and running, the latter being disabled, so it does not
run. </para>

<para> From a locking perspective, each <xref
linkend="stmtsetaddtable"> request will need to briefly take out an
exclusive lock on each table as it attaches these triggers, which is
much the same as has always been the case with &slony1;. </para>
</listitem>

<listitem><para> On the subscriber, the subscription process will add
the same triggers, but with the polarities <quote>reversed</quote>, to
protect data from accidental corruption on subscribers.  </para>

<para> From a locking perspective, again, there is not much difference
from earlier &slony1; behaviour, as the subscription process, due to
running <command>TRUNCATE</command>, copying data, and altering table
schemas, requires <emphasis>extensive</emphasis> exclusive table
locks, and the changes in trigger behaviour do not change those
requirements.  </para>


</listitem>

<listitem><para>If you restore a backup of a Slony-I node (taken by pg_dump or 
any other method), and drop the Slony-I namespace, this now cleanly removes 
all Slony-I components, leaving the database, including its schema, 
in a <quote>pristine</quote>, consistent fashion, ready for whatever use may 
be desired. 
</para></listitem>

<listitem><para> &rddlchanges; is now performed in quite a different
way: rather than altering each replicated table to <quote>take it out
of replicated mode</quote>, &slony1; instead simply shifts into the
<command>local</command> status for the duration of this event.  </para>

<para> On the origin, this deactivates the
<function>logtrigger</function> trigger. </para>

<para> On each subscriber, this deactivates the
<function>denyaccess</function> trigger. </para>

</listitem>

<listitem><para> At the time of invoking <xref linkend="stmtmoveset">
against the former origin, &slony1; must transform that node into a
subscriber, which requires dropping the <function>lockset</function>
triggers, disabling the <function>logtrigger</function> triggers, and
enabling the <function>denyaccess</function> triggers. </para>

<para> At about the same time, when processing <xref
linkend="stmtmoveset"> against the new origin, &slony1; must transform
that node into an origin, which requires disabling the formerly active
<function>denyaccess</function> triggers, and enabling the
<function>logtrigger</function> triggers. </para>

<para> From a locking perspective &slony1; will need to take out exclusive
locks  to disable and enable the respective
triggers.
</para>

</listitem>

<listitem><para> Similarly to <xref linkend="stmtmoveset">, <xref
linkend="stmtfailover"> transforms a subscriber node into an origin,
which requires disabling the formerly active
<function>denyaccess</function> triggers, and enabling the
<function>logtrigger</function> triggers.  The locking implications
are again, much the same, requiring an exclusive lock on each such
table.  </para> </listitem>

</itemizedlist>

<sect2> <title><command>TRUNCATE</command> in &postgres; 8.4+</title>

<para> In &postgres; 8.4, triggers were augmented to support the
<command>TRUNCATE</command> event.  Thus, one may create a trigger
which runs when one requests <command>TRUNCATE</command> on a table, as
follows:

<screen>
create trigger "_@CLUSTERNAME@_truncatetrigger" 
   before truncate on my_table 
   for each statement 
     execute procedure @NAMESPACE@.log_truncate(22);
</screen></para>

<para> &slony1; supports this on nodes running &postgres; 8.4 and
above, as follows:

<itemizedlist>

<listitem><para> Tables have an additional two triggers attached to them:
<itemizedlist>
<listitem><para> <function>log_truncate(tab_id)</function></para> 

<para> Running on the origin, this captures
<command>TRUNCATE</command> requests, and stores them in &sllog1; and
&sllog2; so that they are applied at the appropriate point on
subscriber nodes.</para></listitem>

<listitem><para> <function>truncate_deny()</function></para>

<para> Running on subscriber nodes, this forbids running
<command>TRUNCATE</command> directly against replicated tables on
these nodes, in much the same way <function>denyAccess()</function>
forbids running <command>INSERT/UPDATE/DELETE</command> directly
against replicated tables.</para></listitem>

</itemizedlist></para></listitem>

<listitem><para> For each table, the command <command>TRUNCATE TABLE
ONLY my_schema.my_table CASCADE;</command> is submitted.</para>

<para> Various options were considered (see <ulink
url="http://www.slony.info/bugzilla/show_bug.cgi?id=134"> Bugzilla Bug
#134 </ulink>), after which <command>CASCADE</command> was concluded
to be the appropriate answer.

<warning><para> If you have a subscriber node where additional tables
have gotten attached via foreign keys to a replicated table, then
running <command>TRUNCATE</command> against that parent table will
also <command>TRUNCATE</command> <emphasis>all the
children.</emphasis></para>

<para> Of course, it should be observed that this was a
<emphasis>terribly dangerous</emphasis> thing to have done because
deleting data from the parent table would already either:

<itemizedlist>

<listitem><para> Lead to deleting data from the child tables, this meaning the addition of <command>TRUNCATE</command> support is really no change at all;</para></listitem>

<listitem><para> Lead to foreign keys being broken on the subscriber, causing replication to keel over.</para></listitem>
</itemizedlist></para>

<para> (In effect, we're not really worsening things.)</para></warning></para></listitem>

<listitem><para> Note that if a request truncates several tables
(<emphasis>e.g.</emphasis> - as where a table has a hierachy of
children), then a request will be logged in &sllog1;/&sllog2; for each
table, and the <command>TRUNCATE CASCADE</command> will
<emphasis>effectively</emphasis> mean that the child tables will be
truncated, first indirectly, then directly.  If there is a hierarchy
of 3 tables, <envar>t1</envar>, <envar>t2</envar>, and
<envar>t3</envar>, then <envar>t3</envar> will get truncated three
times.  It's empty after the first <command>TRUNCATE</command>, so
additional iterations will be cheap.</para></listitem>

<listitem><para> If mixing &postgres; 8.3 and higher versions within a
cluster:
<itemizedlist>

<listitem><para> &postgres; 8.3 nodes will not capture
<command>TRUNCATE</command> requests, neither to log the need to
propagate the TRUNCATE, nor to prevent it, on either origin or
replica.</para></listitem>

<listitem><para> &postgres; 8.4 nodes <emphasis>do</emphasis> capture
<command>TRUNCATE</command> requests for both
purposes.</para></listitem>

<listitem><para> If a &postgres; 8.4+ node captures a
<command>TRUNCATE</command> request, it will apply fine against a
subscriber running &postgres; 8.3.</para></listitem>

</itemizedlist></para></listitem>

</itemizedlist></para></sect2>

</sect1>
<!-- Keep this comment at the end of the file
Local variables:
mode:sgml
sgml-omittag:nil
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
sgml-parent-document:"slony.sgml"
sgml-exposed-tags:nil
sgml-local-catalogs:("/usr/lib/sgml/catalog")
sgml-local-ecat-files:nil
End:
-->
