1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2014 Oracle and/or its affiliates.  All rights reserved.
5  *
6  * $Id$
7  */
8 package com.sleepycat.je.trigger;
9 
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertNull;
12 
13 import java.util.Arrays;
14 import java.util.LinkedList;
15 import java.util.List;
16 
17 import org.junit.After;
18 import org.junit.Before;
19 import org.junit.Test;
20 
21 import com.sleepycat.je.Database;
22 import com.sleepycat.je.DatabaseConfig;
23 import com.sleepycat.je.DatabaseEntry;
24 import com.sleepycat.je.DbInternal;
25 import com.sleepycat.je.Environment;
26 import com.sleepycat.je.OperationStatus;
27 import com.sleepycat.je.Transaction;
28 import com.sleepycat.je.TransactionConfig;
29 import com.sleepycat.je.dbi.DatabaseImpl;
30 import com.sleepycat.je.dbi.TriggerManager.MapOver;
31 
32 /**
33  * This set of unit tests exercises all standalone trigger invocations.
34  */
35 
36 public class InvokeTest extends TestBase {
37 
38     Environment env;
39     Database db1 = null;
40     int triggerCount = -1;
41     /* The number of nodes. it's > 1 if this is a replicated environment. */
42     protected int nNodes = 1;
43 
getTriggers()44     protected List<Trigger> getTriggers() {
45         return new LinkedList<Trigger>(Arrays.asList((Trigger) new DBT("t1"),
46                              (Trigger) new DBT("t2")));
47     }
48 
getTransientTriggers()49     protected List<Trigger> getTransientTriggers() {
50         return new LinkedList<Trigger>(Arrays.asList((Trigger) new TDBT("tt1"),
51                              (Trigger) new TDBT("tt2")));
52     }
53 
getTriggersPlusOne()54     protected List<Trigger> getTriggersPlusOne() {
55         List<Trigger> triggers = getTriggers();
56         triggers.add(new InvokeTest.DBT("t3"));
57         return triggers;
58     }
59 
getTransactionConfig()60     protected TransactionConfig getTransactionConfig() {
61         return null;
62     }
63 
64     @Before
setUp()65     public void setUp()
66         throws Exception {
67 
68         super.setUp();
69         List<Trigger> triggers = getTriggers();
70         triggerCount = triggers.size();
71         dbConfig.setTriggers(triggers);
72 
73         dbConfig.setOverrideTriggers(true);
74 
75         env = create(envRoot, envConfig);
76         Transaction transaction =
77             env.beginTransaction(null, getTransactionConfig());
78         db1 = env.openDatabase(transaction, "db1", dbConfig);
79         transaction.commit();
80         dbConfig.setOverrideTriggers(false);
81         resetTriggers();
82     }
83 
84     @After
tearDown()85     public void tearDown()
86         throws Exception {
87 
88         db1.close();
89         close(env);
90         super.tearDown();
91     }
92 
93     @Test
testAddRemoveTriggerExistindDbTrans()94     public void testAddRemoveTriggerExistindDbTrans() {
95         Transaction transaction =
96             env.beginTransaction(null, getTransactionConfig());
97         addRemoveTriggerExistingDb(transaction);
98         transaction.commit();
99     }
100 
101     @Test
testAddRemoveTriggerExistindDbAuto()102     public void testAddRemoveTriggerExistindDbAuto() {
103         addRemoveTriggerExistingDb(null);
104     }
105 
addRemoveTriggerExistingDb(Transaction transaction)106     void addRemoveTriggerExistingDb(Transaction transaction) {
107         db1.close();
108         resetTriggers();
109 
110         /* read/write open. */
111         db1 = env.openDatabase(transaction, "db1", dbConfig);
112         verifyAddTrigger(0);
113         verifyRemoveTrigger(0);
114         checkNullOpenTriggerCount(1);
115         db1.close();
116         resetTriggers();
117 
118         dbConfig.setOverrideTriggers(true);
119         dbConfig.setTriggers(getTriggersPlusOne());
120         db1 = env.openDatabase(transaction, "db1", dbConfig);
121         DatabaseImpl db1Impl = DbInternal.getDatabaseImpl(db1);
122         DBT t3 = (DBT) db1Impl.getTriggers().get(2);
123         assertEquals("t3", t3.getName());
124         assertEquals(1, t3.ts.nAddTrigger);
125         assertEquals(0, t3.ts.nRemoveTrigger);
126         db1.close();
127         resetTriggers();
128 
129         dbConfig.setTriggers(getTriggers());
130         db1 = env.openDatabase(transaction, "db1", dbConfig);
131         db1Impl = DbInternal.getDatabaseImpl(db1);
132         assertEquals("t3", t3.getName());
133         assertEquals(0, t3.ts.nAddTrigger);
134         assertEquals(1, t3.ts.nRemoveTrigger);
135     }
136 
137     /**
138      * Simply verifies that transient triggers are indeed transient, i.e., not
139      * stored in the DatabaseImpl.  Also checks that a transient trigger can be
140      * added when setOverrideTriggers(true) is not called.
141      */
142     @Test
testBasicTransientTrigger()143     public void testBasicTransientTrigger() {
144 
145         /* Set two transient triggers in new DB. */
146         List<Trigger> tList = getTransientTriggers();
147         DatabaseConfig tdbConfig = getDBConfig();
148         tdbConfig.setOverrideTriggers(false);
149         tdbConfig.setTriggers(tList);
150         Database dbc = env.openDatabase(null, "dbc", tdbConfig);
151         assertEquals(tList, dbc.getConfig().getTriggers());
152         for (Trigger trigger : dbc.getConfig().getTriggers()) {
153             assertEquals("dbc", trigger.getDatabaseName());
154         }
155         verifyAddTrigger(1);
156         checkTransientTriggerCount(1);
157         resetTriggers();
158 
159         /* Test simple transctional put(). */
160         Transaction transaction = env.beginTransaction(null, null);
161         DatabaseEntry key = new DatabaseEntry(new byte[] {1});
162         DatabaseEntry data = new DatabaseEntry(new byte[] {2});
163         dbc.put(transaction, key, data);
164         verifyPut(1, key, data, null);
165         transaction.commit();
166         verifyCommit(1);
167         checkTransientTriggerCount(1);
168         resetTriggers();
169 
170         /* Close DB and reopen -- there should be no triggers. */
171         dbc.close();
172         verifyRemoveTrigger(1);
173         checkTransientTriggerCount(1);
174         resetTriggers();
175         tdbConfig = getDBConfig();
176         tdbConfig.setOverrideTriggers(false);
177         tdbConfig.setAllowCreate(false);
178         dbc = env.openDatabase(null, "dbc", tdbConfig);
179         assertNull(dbc.getConfig().getTriggers());
180         verifyAddTrigger(0);
181         dbc.close();
182         verifyRemoveTrigger(0);
183         checkTransientTriggerCount(0);
184 
185         /* Remove DB and recreate -- there should be no triggers. */
186         env.removeDatabase(null, "dbc");
187         tdbConfig = getDBConfig();
188         tdbConfig.setOverrideTriggers(false);
189         dbc = env.openDatabase(null, "dbc", tdbConfig);
190         assertNull(dbc.getConfig().getTriggers());
191         verifyAddTrigger(0);
192         dbc.close();
193         verifyRemoveTrigger(0);
194         checkTransientTriggerCount(0);
195 
196         /* Add triggers to existing DB without overriding config. */
197         tdbConfig = getDBConfig();
198         tdbConfig.setOverrideTriggers(false);
199         tdbConfig.setTriggers(tList);
200         tdbConfig.setAllowCreate(false);
201         dbc = env.openDatabase(null, "dbc", tdbConfig);
202         assertEquals(tList, dbc.getConfig().getTriggers());
203         for (Trigger trigger : dbc.getConfig().getTriggers()) {
204             assertEquals("dbc", trigger.getDatabaseName());
205         }
206         verifyAddTrigger(1);
207         dbc.close();
208         verifyRemoveTrigger(1);
209         checkTransientTriggerCount(1);
210         resetTriggers();
211 
212         /* Clean up. */
213         env.removeDatabase(null, "dbc");
214     }
215 
create(Transaction transaction)216     private void create(Transaction transaction) {
217         List<Trigger> tgs = getTriggers();
218         DatabaseConfig tdbConfig = getDBConfig();
219         dbConfig.setOverrideTriggers(true);
220         tdbConfig.setTriggers(tgs);
221         verifyOpen(0, 0);
222         Database dbc = env.openDatabase(transaction, "dbc", tdbConfig );
223 
224         for (Trigger trigger : dbc.getConfig().getTriggers()) {
225             assertEquals("dbc", trigger.getDatabaseName());
226         }
227         verifyOpen(1, 1);
228         verifyAddTrigger(1);
229 
230         if (transaction == null) {
231             verifyCommit(1);
232         }
233         dbc.close();
234         verifyClose(1);
235         /* Read triggers from the existing database. */
236         checkTriggerCount(1);
237         resetTriggers();
238         dbc = env.openDatabase(transaction, "dbc", dbConfig);
239         verifyOpen(0,1);
240         verifyAddTrigger(0);
241         /* Not a new database no create and consequently commit triggers. */
242         verifyCommit(0);
243         checkNullOpenTriggerCount(1);
244         assertEquals(2, db1.getConfig().getTriggers().size());
245         dbc.close();
246         env.removeDatabase(transaction, "dbc");
247     }
248 
249     @Test
testCreateAuto()250     public void testCreateAuto() {
251         create(null);
252     }
253 
254     @Test
testCreateTrans()255     public void testCreateTrans() {
256         Transaction transaction =
257             env.beginTransaction(null, getTransactionConfig());
258         create(transaction);
259         transaction.commit();
260     }
261 
262     @Test
testOpenAuto()263     public void testOpenAuto() {
264         open(null);
265     }
266 
267     @Test
testOpenTrans()268     public void testOpenTrans() {
269         Transaction transaction =
270             env.beginTransaction(null, getTransactionConfig());
271         open(transaction);
272         transaction.commit();
273     }
274 
open(Transaction transaction)275     private void open(Transaction transaction) {
276         db1.close();
277         resetTriggers();
278         /* read/write open. */
279         db1 = env.openDatabase(transaction, "db1", dbConfig);
280         verifyOpen(0,1);
281         checkNullOpenTriggerCount(1);
282         db1.close();
283         resetTriggers();
284 
285         DatabaseConfig config = getDBConfig();
286         config.setReadOnly(true);
287         db1 = env.openDatabase(transaction, "db1", config);
288         verifyOpen(0,0);
289         checkTriggerCount(0);
290         resetTriggers();
291 
292         config.setReadOnly(false);
293         Database db11 = env.openDatabase(transaction, "db1", config);
294         verifyOpen(0,1);
295         checkNullOpenTriggerCount(1);
296         db11.close();
297         resetTriggers();
298     }
299 
300     @Test
testClose()301     public void testClose() {
302         closeDb();
303     }
304 
closeDb()305     private void closeDb() {
306         resetTriggers();
307         db1.close();
308         verifyClose(1);
309         checkNullOpenTriggerCount(1);
310     }
311 
rename(Transaction transaction)312     private void rename(Transaction transaction) {
313         db1.close();
314         env.renameDatabase(transaction, "db1", "dbr1");
315         verifyRename("dbr1", 1);
316         checkTriggerCount(1);
317     }
318 
319     @Test
testRenameAuto()320     public void testRenameAuto() {
321         rename(null);
322     }
323 
324     @Test
testRenameTrans()325     public void testRenameTrans() {
326         Transaction transaction = env.beginTransaction(null, null);
327         rename(transaction);
328         checkTriggerCount(1);
329         transaction.commit();
330     }
331 
332     @Test
testRenameAbort()333     public void testRenameAbort() {
334         Transaction transaction = env.beginTransaction(null, null);
335         rename(transaction);
336         checkTriggerCount(1);
337         transaction.abort();
338         db1 = env.openDatabase(null, "db1", dbConfig);
339         verifyDB1Triggers();
340         verifyAbort(1);
341     }
342 
truncate(Transaction transaction)343     private void truncate(Transaction transaction) {
344         db1.close();
345         env.truncateDatabase(transaction, "db1", false);
346         verifyTruncate(1);
347         checkTriggerCount(1);
348     }
349 
350     @Test
testTruncateAuto()351     public void testTruncateAuto() {
352         truncate(null);
353     }
354 
355     @Test
testTruncateTrans()356     public void testTruncateTrans() {
357         Transaction transaction = env.beginTransaction(null, null);
358         truncate(transaction);
359         transaction.commit();
360 
361         /*
362          * Truncate does a rename under the covers so make sure the triggers
363          * are present on the new empty database.
364          */
365         db1 = env.openDatabase(null, "db1", dbConfig);
366         verifyDB1Triggers();
367     }
368 
verifyDB1Triggers()369     private void verifyDB1Triggers() {
370         assertEquals("db1", db1.getDatabaseName());
371         assertEquals(triggerCount, db1.getConfig().getTriggers().size());
372         for (Trigger t : db1.getConfig().getTriggers()) {
373             assertEquals("db1", t.getDatabaseName());
374         }
375     }
376 
remove(Transaction transaction)377     private void remove(Transaction transaction) {
378         db1.close();
379         env.removeDatabase(transaction, "db1");
380         verifyRemove(1);
381         verifyRemoveTrigger(1);
382         checkTriggerCount(1);
383     }
384 
385     @Test
testRemoveAuto()386     public void testRemoveAuto() {
387         remove(null);
388     }
389 
390     @Test
testRemoveTrans()391     public void testRemoveTrans() {
392         Transaction transaction = env.beginTransaction(null, null);
393         remove(transaction);
394         transaction.commit();
395     }
396 
397     @Test
testKVOpsAuto()398     public void testKVOpsAuto() {
399         KVOps(null);
400     }
401 
402     @Test
testKVOpsTrans()403     public void testKVOpsTrans() {
404         Transaction transaction = env.beginTransaction(null, null);
405         KVOps(transaction);
406         transaction.commit();
407     }
408 
409     @Test
testKVOpsAbort()410     public void testKVOpsAbort() {
411         Transaction transaction = env.beginTransaction(null, null);
412         KVOps(transaction);
413         transaction.abort();
414         verifyAbort(1);
415     }
416 
KVOps(Transaction transaction)417     private void KVOps(Transaction transaction) {
418 
419         DatabaseEntry key = new DatabaseEntry();
420         key.setData(new byte[]{1});
421         DatabaseEntry data1 = new DatabaseEntry();
422         data1.setData(new byte[]{2});
423         verifyPut(0, null, null, null);
424 
425         db1.put(transaction, key, data1);
426         verifyPut(1, key, data1, null);
427         checkTriggerCount(1);
428         resetTriggers();
429         DatabaseEntry data2 = new DatabaseEntry();
430         data2.setData(new byte[]{3});
431         db1.put(transaction, key, data2);
432         verifyPut(1, key, data2, data1);
433         checkTriggerCount(1);
434         resetTriggers();
435 
436         OperationStatus status = db1.delete(transaction, key);
437         assertEquals(OperationStatus.SUCCESS, status);
438         verifyDelete(1, key, data2);
439         checkTriggerCount(1);
440         resetTriggers();
441 
442         status = db1.delete(transaction, key);
443         assertEquals(OperationStatus.NOTFOUND, status);
444         verifyDelete(0, null, null);
445         checkTriggerCount(0);
446 
447         db1.close();
448     }
449 
450     /**
451      * Ensure recovery replay of MapLNs executes properly.  This tests a fix
452      * for an NPE that occurred during DatabaseImpl.readFromLog, which was
453      * calling DatabaseImpl.getName prior to instantiation of DbTree.
454      */
455     @Test
testBasicRecovery()456     public void testBasicRecovery() {
457         db1.close();
458         close(env);
459         resetTriggers();
460         env = create(envRoot, envConfig);
461         db1 = env.openDatabase(null, "db1", dbConfig);
462         checkNullOpenTriggerCount(1);
463         verifyAddTrigger(0);
464         verifyRemoveTrigger(0);
465     }
466 
resetTriggers()467     private void resetTriggers() {
468         for (Trigger t : TestBase.invokedTriggers) {
469             ((TDBT)t).clear();
470         }
471         TestBase.invokedTriggers.clear();
472     }
473 
verifyDelete(final int nDelete, final DatabaseEntry key, final DatabaseEntry oldData)474     protected void verifyDelete(final int nDelete,
475                                 final DatabaseEntry key,
476                                 final DatabaseEntry oldData) {
477 
478         new MapOver<Trigger,Trigger>(TestBase.invokedTriggers) {
479 
480             @Override
481             protected Trigger fun(Trigger e) {
482                 final TDBT dbt = (TDBT)e;
483                 assertEquals(nDelete, dbt.ts.nDelete);
484                 assertEquals(key, dbt.ts.key);
485                 assertEquals(oldData, dbt.ts.oldData);
486                 dbt.ts.nDelete = 0;
487                 return e;
488             }
489         }.run();
490     }
491 
verifyPut(final int nPut, final DatabaseEntry key, final DatabaseEntry newData, final DatabaseEntry oldData)492     protected void verifyPut(final int nPut,
493                              final DatabaseEntry key,
494                              final DatabaseEntry newData,
495                              final DatabaseEntry oldData) {;
496 
497         new MapOver<Trigger,Trigger>(TestBase.invokedTriggers) {
498 
499             @Override
500             protected Trigger fun(Trigger e) {
501                 final TDBT dbt = (TDBT)e;
502                 assertEquals(nPut, dbt.ts.nPut);
503                 dbt.ts.nPut = 0;
504                 assertEquals(key, dbt.ts.key);
505                 assertEquals(newData, dbt.ts.newData);
506                 assertEquals(oldData, dbt.ts.oldData);
507                 return e;
508             }
509         }.run();
510     }
511 
verifyOpen(final int nCreate, final int nOpen)512     protected void verifyOpen(final int nCreate, final int nOpen) {
513 
514         new MapOver<Trigger,Trigger>(TestBase.invokedTriggers) {
515 
516             @Override
517             protected Trigger fun(Trigger e) {
518                 assertEquals(nOpen, ((TDBT)e).ts.nOpen);
519                 assertEquals(nCreate, ((TDBT)e).ts.nCreate);
520                 ((TDBT)e).ts.nCreate = 0;
521                 ((TDBT)e).ts.nOpen = 0;
522                 return e;
523             }
524         }.run();
525     }
526 
verifyClose(final int nClose)527     protected void verifyClose(final int nClose) {
528 
529         new MapOver<Trigger,Trigger>(TestBase.invokedTriggers) {
530 
531             @Override
532             protected Trigger fun(Trigger e) {
533                 assertEquals(nClose, ((TDBT)e).ts.nClose);
534                 ((TDBT)e).ts.nClose = 0;
535                 return e;
536             }
537         }.run();
538     }
539 
540     /* The triggers should have been executed on all nodes. */
checkTriggerCount(final int count)541     protected void checkTriggerCount(final int count) {
542         assertEquals((count * nNodes * triggerCount),
543                      TestBase.invokedTriggers.size());
544     }
545 
546     /* Transient triggers are only executed on the node they're configured. */
checkTransientTriggerCount(final int count)547     protected void checkTransientTriggerCount(final int count) {
548         assertEquals((count * triggerCount),
549                      TestBase.invokedTriggers.size());
550     }
551 
552     /*
553      * Null open triggers, ones where the db is opened for writes,
554      * but no writes are actually done, will not fire on replica nodes.
555      */
checkNullOpenTriggerCount(final int count)556     protected void checkNullOpenTriggerCount(final int count) {
557         assertEquals((count * triggerCount),
558                      TestBase.invokedTriggers.size());
559     }
560 
verifyRemove(final int nRemove)561     protected void verifyRemove(final int nRemove) {
562 
563         new MapOver<Trigger,Trigger>(TestBase.invokedTriggers) {
564 
565             @Override
566             protected Trigger fun(Trigger e) {
567                 assertEquals(nRemove, ((DBT)e).ts.nRemove);
568                 ((DBT)e).ts.nRemove = 0;
569                 return e;
570             }
571         }.run();
572     }
573 
verifyTruncate(final int nTruncate)574     protected void verifyTruncate(final int nTruncate) {
575 
576         new MapOver<Trigger,Trigger>(TestBase.invokedTriggers) {
577 
578             @Override
579             protected Trigger fun(Trigger e) {
580                 assertEquals(nTruncate, ((DBT)e).ts.nTruncate);
581                 ((DBT)e).ts.nTruncate = 0;
582                 return e;
583             }
584         }.run();
585     }
586 
verifyRename(final String newName, final int nRename)587     protected void verifyRename(final String newName,
588                                 final int nRename) {
589 
590         new MapOver<Trigger,Trigger>(TestBase.invokedTriggers) {
591 
592             @Override
593             protected Trigger fun(Trigger e) {
594                 final DBT dbt = (DBT)e;
595                 assertEquals(nRename, dbt.ts.nRename);
596                 newName.equals(dbt.ts.newName);
597                 dbt.ts.nRename = 0;
598                 assertEquals(newName, dbt.getDatabaseName());
599                 return e;
600             }
601         }.run();
602     }
603 
verifyCommit(final int nCommit)604     protected void verifyCommit(final int nCommit) {
605 
606         new MapOver<Trigger,Trigger>(TestBase.invokedTriggers) {
607 
608             @Override
609             protected Trigger fun(Trigger e) {
610                 assertEquals(nCommit, ((TDBT)e).ts.nCommit);
611                 ((TDBT)e).ts.nCommit = 0;
612                 return e;
613             }
614         }.run();
615     }
616 
verifyAbort(final int nAbort)617     protected void verifyAbort(final int nAbort) {
618 
619         new MapOver<Trigger,Trigger>(TestBase.invokedTriggers) {
620 
621             @Override
622             protected Trigger fun(Trigger e) {
623                 assertEquals(nAbort, ((TDBT)e).ts.nAbort);
624                 ((TDBT)e).ts.nAbort = 0;
625                 return e;
626             }
627         }.run();
628     }
629 
verifyRemoveTrigger(final int nRemoveTrigger)630     protected void verifyRemoveTrigger(final int nRemoveTrigger) {
631 
632         new MapOver<Trigger,Trigger>(TestBase.invokedTriggers) {
633 
634             @Override
635             protected Trigger fun(Trigger e) {
636                 assertEquals(nRemoveTrigger, ((TDBT)e).ts.nRemoveTrigger);
637                 ((TDBT)e).ts.nRemoveTrigger = 0;
638                 return e;
639             }
640         }.run();
641     }
642 
verifyAddTrigger(final int nAddTrigger)643     protected void verifyAddTrigger(final int nAddTrigger) {
644 
645         new MapOver<Trigger,Trigger>(TestBase.invokedTriggers) {
646 
647             @Override
648             protected Trigger fun(Trigger e) {
649                 assertEquals(nAddTrigger, ((TDBT)e).ts.nAddTrigger);
650                 ((TDBT)e).ts.nAddTrigger = 0;
651                 return e;
652             }
653         }.run();
654     }
655 }
656