Friday, November 23, 2012

Connection-Dependent Trigger Logic

Question: How do I code a database trigger to do one thing when it is executed during a MobiLink synchronization, and something different when it is executed by an application?

Answer: This is a specific example of a common problem in triggers: "Who fired me?"

One solution is to code a connection-level CREATE VARIABLE statement in the code that fires the trigger, together a SET statement to specify "Who am I", and use an IF statement inside the trigger to ask the question "Who fired me?":

-------------------------------------------------------
-- Table and trigger.

CREATE TABLE t (
   pkey   INTEGER NOT NULL PRIMARY KEY,
   data   INTEGER NOT NULL );

CREATE TRIGGER tri_t
   BEFORE INSERT ON t
   REFERENCING NEW AS new_t
   FOR EACH ROW
BEGIN
   IF who_am_i = 'accounting application' THEN
      MESSAGE STRING ( 'Trigger fired by accounting application.' ) TO CONSOLE;
   ELSE  
      MESSAGE STRING ( 'Trigger fired by something else.' ) TO CONSOLE;
   END IF;
END;

-------------------------------------------------------
-- Connection 1.

CREATE VARIABLE who_am_i VARCHAR ( 100 );

SET who_am_i = 'accounting application';

INSERT t VALUES ( 1, 1 );
COMMIT;

-------------------------------------------------------
-- Connection 2.

CREATE VARIABLE who_am_i VARCHAR ( 100 );

SET who_am_i = 'payroll application';

INSERT t VALUES ( 2, 2 );
COMMIT;

-------------------------------------------------------
-- MESSAGE output.

Trigger fired by accounting application.
Trigger fired by something else.
This solution won't work so well with MobiLink. While it may be possible to code a CREATE VARIABLE statement in a script run by the MobiLink server, no such opportunity exists with the MobiLink client dbmlsync.exe.

In the case of the MobiLink client, the connection name can be used to answer "Who fired me?" because dbmlsync.exe always uses the same name "DBMLsync_main" to when connecting to the remote database.

You can use the connection name with the MobiLink server as well, but you'll have to explicitly specify the CON parameter in the command line like this:
"%SQLANY12%\bin64\mlsrv12.exe"^
  -c "DSN=cons;UID=dba;PWD=sql;CON=MLSRV"^
  -o mlsrv12_log_cons.txt^
  -os 10M^
  -ppv 60^
  -vcefhkmnopstuU^
  -zu+ 
Here's a trigger that calls CONNECTION_PROPERTY ( 'Name' ) to see if it's MobiLink that's fired it, or something else; the same code works on both the consolidated database and the remote:
CREATE TRIGGER tri_t1
   BEFORE INSERT ON t1
   REFERENCING NEW AS new_t1
   FOR EACH ROW
BEGIN
   IF CONNECTION_PROPERTY ( 'Name' ) LIKE 'DBMLsync%'
   OR CONNECTION_PROPERTY ( 'Name' ) LIKE 'MLSRV%' THEN
      MESSAGE STRING ( 'Trigger fired by MobiLink client or server connection.' ) TO CONSOLE;
   ELSE  
      MESSAGE STRING ( 'Trigger fired by application connection.' ) TO CONSOLE;
   END IF;
END;
The two techniques can be combined (CONNECTION_PROPERTY and CREATE VARIABLE), and the VAREXISTS() function can be used inside the trigger to check if the CREATE VARIABLE has been executed or not:
CREATE TRIGGER tri_t1
   BEFORE INSERT ON t1
   REFERENCING NEW AS new_t1
   FOR EACH ROW
BEGIN
   IF CONNECTION_PROPERTY ( 'Name' ) LIKE 'DBMLsync%'
   OR CONNECTION_PROPERTY ( 'Name' ) LIKE 'MLSRV%' THEN
      MESSAGE STRING ( 'Trigger fired by MobiLink client or server connection.' ) TO CONSOLE;
   ELSE 
      IF VAREXISTS ( 'who_am_i' ) = 1 THEN
         MESSAGE STRING ( 'Trigger fired by ', who_am_i ) TO CONSOLE;
       ELSE
         MESSAGE STRING ( 'Trigger fired by unknown application.' ) TO CONSOLE;
      END IF;
   END IF;
END;




2 comments:

Anonymous said...

FWIW, SQL Remote has a more appropriate means to distinguish remote and local connections: It's really easy to mark trigger code to be skipped (or run) when triggered by SQL Remote w.t.h. of the CURRENT REMOTE USER IS [NOT] NULL special value.

Another method for use with DBMlSync would be to use a particular user when synchronizing...

Regards
Volker

Jeff Albion said...

While it may be possible to code a CREATE VARIABLE statement in a script run by the MobiLink server, no such opportunity exists with the MobiLink client dbmlsync.exe.

You could consider a dbmlsync hook for this task to create/set the variable:

=========
CREATE PROCEDURE sp_hook_dbmlsync_begin ()
BEGIN
CREATE VARIABLE who_am_i VARCHAR( 100 );
SET who_am_i = 'dbmlsync';
END;
=========

The original trigger logic on the table then works the same way.