Ah, naturally I found the answer right after sending this. The secret: PRAGMA recursive_triggers = 1;
As stated here: https://www.sqlite.org/lang_conflict.html "When the REPLACE conflict resolution strategy deletes rows in order to satisfy a constraint, delete triggers fire if and only if recursive triggers are enabled." Re-running my test with this shows it causes the delete trigger to fire as expected: sqlite> PRAGMA recursive_triggers = 1; sqlite> insert or replace into nvps values ( 'name1', 'value4' ); sqlite> select * from nvps; name1|value4 sqlite> select * from actions; inserted "name1", "value1" inserted "name2", "value2" updated "name2" from "value2" to "value2.1" deleted "name2", "value2.1" deleted "name1", "value1" inserted "name1", "value4" sqlite> Awesome. Go sqlite! -david On Sat, May 30, 2015 at 11:33 PM, David Barrett <dbarrett at expensify.com> wrote: > I fear I already know the answer to this, but I want to ask to make sure: > is there ANY way to craft a trigger that can detect when an INSERT OR > REPLACE trigger does a REPLACE versus an INSERT? I found this thread which > suggested it wasn't possible: > > > http://sqlite.1065341.n5.nabble.com/Trigger-logic-with-INSERT-OR-REPLACE-td65541.html > > But it wasn't totally clear to me, so I tried to construct a test case > myself -- and to my dismay, it confirmed (so far as I can tell) that a > REPLACE looks to the trigger exactly like an INSERT. Is there any trick > I'm overlooking, or is that just a current limitation of sqlite? > > The test I ran follows: > > Davids-MacBook-Air:~ dbarrett$ sqlite3 > SQLite version 3.8.5 2014-08-15 22:37:57 > Enter ".help" for usage hints. > Connected to a transient in-memory database. > Use ".open FILENAME" to reopen on a persistent database. > sqlite> /* ----------------------------------------- > ...> Create a simple name/value pair table > ...> ------------------------------------------ */ > sqlite> create table nvps ( name primary key, value ); > sqlite> > sqlite> /* ----------------------------------------- > ...> Create a table of "actions" performed on > ...> the NVP table > ...> ------------------------------------------ */ > sqlite> create table actions ( actions ); > sqlite> > sqlite> /* ----------------------------------------- > ...> Create the triggers to log actions > ...> ------------------------------------------ */ > sqlite> create trigger onInsert after insert on nvps > ...> begin > ...> insert into actions values ('inserted "' || new.name || '", "' || > new.value || '"' ); > ...> end; > sqlite> > sqlite> create trigger onUpdate after update on nvps > ...> begin > ...> insert into actions values ('updated "' || new.name || '" from "' > || old.value || '" to "' || new.value || '"' ); > ...> end; > sqlite> > sqlite> create trigger onDelete after delete on nvps > ...> begin > ...> insert into actions values ('deleted "' || old.name || '", "' || > old.value || '"' ); > ...> end; > sqlite> > sqlite> /* ----------------------------------------- > ...> Insert a couple values; note that the > ...> triggers work as expected in all cases. > ...> ------------------------------------------ */ > sqlite> insert into nvps values ( 'name1', 'value1' ); > sqlite> insert into nvps values ( 'name2', 'value2' ); > sqlite> update nvps set value='value2.1' where name='name2'; > sqlite> delete from nvps where name='name2'; > sqlite> select * from nvps; > name1|value1 > sqlite> select * from actions; > inserted "name1", "value1" > inserted "name2", "value2" > updated "name2" from "value2" to "value2.1" > deleted "name2", "value2.1" > sqlite> > sqlite> /* ----------------------------------------- > ...> Attempt to insert a non-unique value into > ...> a column with a unique constraint -- note > ...> how it fails. > ...> ------------------------------------------ */ > sqlite> insert into nvps values ( 'name1', 'value3' ); > Error: UNIQUE constraint failed: nvps.name > sqlite> > sqlite> /* ----------------------------------------- > ...> Finally, attempt the same insert, but with > ...> an "or replace". Note how it works, and > ...> correctly logs the the insert, but doesn't > ...> also log a delete. In other words, no > ...> trigger fired that indicates we replaced a > ...> value. > ...> ------------------------------------------ */ > sqlite> insert or replace into nvps values ( 'name1', 'value4' ); > sqlite> select * from nvps; > name1|value4 > sqlite> select * from actions; > inserted "name1", "value1" > inserted "name2", "value2" > updated "name2" from "value2" to "value2.1" > deleted "name2", "value2.1" > inserted "name1", "value4" > sqlite> > > Any suggestions? Thanks! > > -david >