Thread: Disabling triggers / constraints

Disabling triggers / constraints

From
Jorge Pereira
Date:
After trying to do some custom dumping/restoring, and having to resort
to the awful trick of changing the trigger counts on the catalog (the
stuff pg_dump adds), decided to add a couple little variables to control
disabling constraints and triggers. Added them to guc.c so that they
show up in the SET/SHOW list, and added checks accordingly (always at
top level, to avoid unnecessary function calls and loops). Variables are
'disable_constraints' and 'disable_triggers' and they are "false" by
default. I find it quite useful for loading large sets of data (I make
heavy use of CHECK constraints).

I am unsure wether it is right to send such an uncalled-for patch, but
seeing as this is such a simple thing that can be quite useful (large
volumes of data), I thought it could be useful.

(apply with patch -p0 from the root distro directory)

Cheers
- Jorge Pereira

*** src/backend/utils/misc/guc.c.orig    2004-05-19 18:52:08.718580424 +0100
--- src/backend/utils/misc/guc.c    2004-05-19 18:50:46.162130912 +0100
***************
*** 131,136 ****
--- 131,140 ----
  int            log_min_duration_statement = -1;


+ /*  Control disabling of triggers (BS,AS,BR and AR) and constraints (useful on mass insert from dumps)*/
+ bool            disable_constraints;
+ bool            disable_triggers;
+

  /*
   * These variables are all dummies that don't do anything, except in some
*** src/backend/executor/execMain.c.orig    2004-05-19 18:53:14.251617888 +0100
--- src/backend/executor/execMain.c    2004-05-19 18:53:19.501819736 +0100
***************
*** 90,95 ****
--- 90,99 ----
                    evalPlanQual *priorepq);
  static void EvalPlanQualStop(evalPlanQual *epq);

+
+ extern bool disable_constraints;
+ extern bool disable_triggers;
+
  /* end of local decls */


***************
*** 1063,1068 ****
--- 1067,1073 ----
      /*
       * Process BEFORE EACH STATEMENT triggers
       */
+     if ( !disable_triggers )
      switch (operation)
      {
          case CMD_UPDATE:
***************
*** 1281,1286 ****
--- 1286,1292 ----
      /*
       * Process AFTER EACH STATEMENT triggers
       */
+     if ( !disable_triggers )
      switch (operation)
      {
          case CMD_UPDATE:
***************
*** 1379,1385 ****
      resultRelationDesc = resultRelInfo->ri_RelationDesc;

      /* BEFORE ROW INSERT Triggers */
!     if (resultRelInfo->ri_TrigDesc &&
        resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
      {
          HeapTuple    newtuple;
--- 1385,1391 ----
      resultRelationDesc = resultRelInfo->ri_RelationDesc;

      /* BEFORE ROW INSERT Triggers */
!     if ( !disable_triggers && resultRelInfo->ri_TrigDesc &&
        resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
      {
          HeapTuple    newtuple;
***************
*** 1405,1411 ****
      /*
       * Check the constraints of the tuple
       */
!     if (resultRelationDesc->rd_att->constr)
          ExecConstraints(resultRelInfo, slot, estate);

      /*
--- 1411,1417 ----
      /*
       * Check the constraints of the tuple
       */
!     if ( !disable_constraints && resultRelationDesc->rd_att->constr )
          ExecConstraints(resultRelInfo, slot, estate);

      /*
***************
*** 1431,1437 ****
          ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);

      /* AFTER ROW INSERT Triggers */
!     ExecARInsertTriggers(estate, resultRelInfo, tuple);
  }

  /* ----------------------------------------------------------------
--- 1437,1444 ----
          ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);

      /* AFTER ROW INSERT Triggers */
!     if ( !disable_triggers )
!         ExecARInsertTriggers(estate, resultRelInfo, tuple);
  }

  /* ----------------------------------------------------------------
***************
*** 1458,1464 ****
      resultRelationDesc = resultRelInfo->ri_RelationDesc;

      /* BEFORE ROW DELETE Triggers */
!     if (resultRelInfo->ri_TrigDesc &&
        resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
      {
          bool        dodelete;
--- 1465,1471 ----
      resultRelationDesc = resultRelInfo->ri_RelationDesc;

      /* BEFORE ROW DELETE Triggers */
!     if ( !disable_triggers && resultRelInfo->ri_TrigDesc &&
        resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
      {
          bool        dodelete;
***************
*** 1525,1531 ****
       */

      /* AFTER ROW DELETE Triggers */
!     ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
  }

  /* ----------------------------------------------------------------
--- 1532,1539 ----
       */

      /* AFTER ROW DELETE Triggers */
!     if ( !disable_triggers )
!         ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
  }

  /* ----------------------------------------------------------------
***************
*** 1569,1575 ****
      resultRelationDesc = resultRelInfo->ri_RelationDesc;

      /* BEFORE ROW UPDATE Triggers */
!     if (resultRelInfo->ri_TrigDesc &&
        resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
      {
          HeapTuple    newtuple;
--- 1577,1583 ----
      resultRelationDesc = resultRelInfo->ri_RelationDesc;

      /* BEFORE ROW UPDATE Triggers */
!     if ( !disable_triggers && resultRelInfo->ri_TrigDesc &&
        resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
      {
          HeapTuple    newtuple;
***************
*** 1604,1610 ****
       * there's no need to do them again.)
       */
  lreplace:;
!     if (resultRelationDesc->rd_att->constr)
          ExecConstraints(resultRelInfo, slot, estate);

      /*
--- 1612,1618 ----
       * there's no need to do them again.)
       */
  lreplace:;
!     if ( !disable_constraints && resultRelationDesc->rd_att->constr )
          ExecConstraints(resultRelInfo, slot, estate);

      /*
***************
*** 1678,1684 ****
          ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);

      /* AFTER ROW UPDATE Triggers */
!     ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
  }

  static const char *
--- 1686,1693 ----
          ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);

      /* AFTER ROW UPDATE Triggers */
!     if ( !disable_triggers )
!         ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
  }

  static const char *


Re: Disabling triggers / constraints

From
Tom Lane
Date:
Jorge Pereira <jrp@ideiaprima.com> writes:
> ... decided to add a couple little variables to control
> disabling constraints and triggers.

I'm not of the opinion that we actually want any such thing, as it's a
blatant violation of the fundamental concept of data integrity.  But in
any case not with such poor control over which triggers get suppressed.
A per-table setting with appropriate permissions checks might possibly
be acceptable.

            regards, tom lane

Re: Disabling triggers / constraints

From
Jorge Pereira
Date:
Tom Lane wrote:

>Jorge Pereira <jrp@ideiaprima.com> writes:
>
>
>>... decided to add a couple little variables to control disabling constraints and triggers.
>>
>>
>I'm not of the opinion that we actually want any such thing, as it's a
>blatant violation of the fundamental concept of data integrity.
>
>
I can understand your concerns. But for the sake of context for context,
here's an example of the code generated by pg_dump --disable-triggers:

| -- Disable triggers
| UPDATE pg_catalog.pg_class SET reltriggers = 0 WHERE oid =
'table_1'::pg_catalog.regclass;
| -- INSERT / UPDATE statements;
| -- Enable triggers
| UPDATE pg_catalog.pg_class SET reltriggers = (SELECT
pg_catalog.count(*) FROM pg_catalog.pg_trigger where pg_class.oid =
tgrelid) WHERE oid = 'table_1'::pg_catalog.regclass;


What I propose is

| SET disable_triggers=1;
| -- INSERT / UPDATE statements;
| SET disable_triggers=0;

This is not an option for daily use, just something that can be set to
allow large volumes of data that is known to be conforming to be put
into the database. It is critical for datawarehousing operations, where
large volumes of data  (on the TB scale) already processed and validated
need to be put into the database. It is quite useful also for situations
where checks depend on the existence of data in the database. I can put
forward a few examples if it's deemed appropriate. :)
In comparison, most DBs I've experienced with (Oracle, MySQL and argh
MSSQL) have some way of disablling integrity checks and triggers (mainly
for loading large sets of data known to be good).

I don't see a need to do it on a per-table basis, seeing as this is
mostly a per-datablock need - I couldn't think of a situation where
enabling it only on one table would be benefitial, as that would imply
that some of tha data you are inputing might not be conforming - which
in turn means you shouldn't even be using this.
On the other hand, you're absolutely right in that this is clearly
something that should be done only by the database owner. a) would
something similar be considered if such permission check was added (for
owner only)? b) would it be considered only if changeable on a per-table
basis?

I'm new here. :) I hope I don't come across as someone trying to force
his view of things, really just trying to pass on the experience I've
had before, and which led me to the despair of having to go and tweak
code. ;) Good thing of OS that I could.

Cheers
- Jorge Pereira

Re: Disabling triggers / constraints

From
Bruce Momjian
Date:
Yes, agreed.  I think we decided that super-user-only could disable
trigger on a global basis.  I prevent folks from mucking with the system
tables to do it.

---------------------------------------------------------------------------

Jorge Pereira wrote:
> Tom Lane wrote:
>
> >Jorge Pereira <jrp@ideiaprima.com> writes:
> >
> >
> >>... decided to add a couple little variables to control disabling constraints and triggers.
> >>
> >>
> >I'm not of the opinion that we actually want any such thing, as it's a
> >blatant violation of the fundamental concept of data integrity.
> >
> >
> I can understand your concerns. But for the sake of context for context,
> here's an example of the code generated by pg_dump --disable-triggers:
>
> | -- Disable triggers
> | UPDATE pg_catalog.pg_class SET reltriggers = 0 WHERE oid =
> 'table_1'::pg_catalog.regclass;
> | -- INSERT / UPDATE statements;
> | -- Enable triggers
> | UPDATE pg_catalog.pg_class SET reltriggers = (SELECT
> pg_catalog.count(*) FROM pg_catalog.pg_trigger where pg_class.oid =
> tgrelid) WHERE oid = 'table_1'::pg_catalog.regclass;
>
>
> What I propose is
>
> | SET disable_triggers=1;
> | -- INSERT / UPDATE statements;
> | SET disable_triggers=0;
>
> This is not an option for daily use, just something that can be set to
> allow large volumes of data that is known to be conforming to be put
> into the database. It is critical for datawarehousing operations, where
> large volumes of data  (on the TB scale) already processed and validated
> need to be put into the database. It is quite useful also for situations
> where checks depend on the existence of data in the database. I can put
> forward a few examples if it's deemed appropriate. :)
> In comparison, most DBs I've experienced with (Oracle, MySQL and argh
> MSSQL) have some way of disablling integrity checks and triggers (mainly
> for loading large sets of data known to be good).
>
> I don't see a need to do it on a per-table basis, seeing as this is
> mostly a per-datablock need - I couldn't think of a situation where
> enabling it only on one table would be benefitial, as that would imply
> that some of tha data you are inputing might not be conforming - which
> in turn means you shouldn't even be using this.
> On the other hand, you're absolutely right in that this is clearly
> something that should be done only by the database owner. a) would
> something similar be considered if such permission check was added (for
> owner only)? b) would it be considered only if changeable on a per-table
> basis?
>
> I'm new here. :) I hope I don't come across as someone trying to force
> his view of things, really just trying to pass on the experience I've
> had before, and which led me to the despair of having to go and tweak
> code. ;) Good thing of OS that I could.
>
> Cheers
> - Jorge Pereira
>
> ---------------------------(end of broadcast)---------------------------
> TIP 2: you can get off all lists at once with the unregister command
>     (send "unregister YourEmailAddressHere" to majordomo@postgresql.org)
>

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: Disabling triggers / constraints

From
Christopher Kings-Lynne
Date:
> Yes, agreed.  I think we decided that super-user-only could disable
> trigger on a global basis.  I prevent folks from mucking with the system
> tables to do it.

It should be a per-table thing:

ALTER TABLE blah [ DISABLE | ENABLE ] [ALL | FOREIGN KEY ] TRIGGERS;

Chris


Re: Disabling triggers / constraints

From
Jorge Pereira
Date:
Christopher Kings-Lynne wrote:

>> Yes, agreed.  I think we decided that super-user-only could disable
>> trigger on a global basis.  I prevent folks from mucking with the system
>> tables to do it.
>
>
> It should be a per-table thing:
>
> ALTER TABLE blah [ DISABLE | ENABLE ] [ALL | FOREIGN KEY ] TRIGGERS;

Doing it with "alter table" seems to imply that the change is permanent
(eg, the table loses checking), whereas that is most certainly not what
is wanted. With a SET variable it lasts only for the session, and
doesn't have to be reset manually.

Assuming one wants the setting to last, as far as I can think of, an
alter table would also mean either a) doing the aforementioned juggling
with setting number of triggers to 0 and recounting when enabling or b)
adding a new field to tables on the catalog. Solution a) I think is a
nasty hack, and doesn't reflect the fact that the table *does* have
triggers (which just happen to be disabled), whereas solution a)
probably implies adding a field to tables in the catalog - and it's
probably too much trouble?
On the other hand, "alter table" has the advantage of being far more
intuitive. Any other comments on this?

Re: Disabling triggers / constraints

From
Christopher Kings-Lynne
Date:
> Doing it with "alter table" seems to imply that the change is permanent
> (eg, the table loses checking), whereas that is most certainly not what
> is wanted. With a SET variable it lasts only for the session, and
> doesn't have to be reset manually.

Ah, then in that case, how about adding to the existing SET CONSTRAINTS
command?

Chris



Re: Disabling triggers / constraints

From
Tom Lane
Date:
Christopher Kings-Lynne <chriskl@familyhealth.com.au> writes:
> It should be a per-table thing:

Exactly.  I don't care whether you're superuser, you should not be able
to disable all triggers, and certainly any facility provided for this
purpose should not encourage you to do that instead of disabling just
the triggers you need.  Think about the triggers on pg_shadow ...

            regards, tom lane

Re: Disabling triggers / constraints

From
Tom Lane
Date:
Jorge Pereira <jrp@ideiaprima.com> writes:
> I can understand your concerns. But for the sake of context for context,
> here's an example of the code generated by pg_dump --disable-triggers:

I'm well aware of what pg_dump does.  Please observe that what it does
(a) affects only one table at a time, and (b) requires superuser
privileges.

> What I propose is

> | SET disable_triggers=1;
> | -- INSERT / UPDATE statements;
> | SET disable_triggers=0;

This is totally unsafe, the more so the more triggers you have.

> I don't see a need to do it on a per-table basis,

Security: a global disable may break things you didn't even know
existed.  In particular I do not wish to see such a feature able to
disable triggers on tables you don't own.

            regards, tom lane