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  */
7 
8 package com.sleepycat.je.test;
9 
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertNotNull;
12 import static org.junit.Assert.assertSame;
13 import static org.junit.Assert.assertTrue;
14 
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21 
22 import org.junit.After;
23 import org.junit.Test;
24 import org.junit.runner.RunWith;
25 import org.junit.runners.Parameterized;
26 import org.junit.runners.Parameterized.Parameters;
27 
28 import com.sleepycat.je.Cursor;
29 import com.sleepycat.je.Database;
30 import com.sleepycat.je.DatabaseConfig;
31 import com.sleepycat.je.DatabaseEntry;
32 import com.sleepycat.je.DatabaseException;
33 import com.sleepycat.je.OperationStatus;
34 import com.sleepycat.je.SecondaryConfig;
35 import com.sleepycat.je.SecondaryCursor;
36 import com.sleepycat.je.SecondaryDatabase;
37 import com.sleepycat.je.SecondaryMultiKeyCreator;
38 import com.sleepycat.je.Transaction;
39 import com.sleepycat.util.test.TxnTestCase;
40 
41 /**
42  * Tests multi-key secondary operations.  Exhaustive API testing of multi-key
43  * secondaries is part of SecondaryTest and ForeignKeyTest, which test the use
44  * of a single key with SecondaryMultiKeyCreator.  This class adds tests for
45  * multiple keys per record.
46  */
47 @RunWith(Parameterized.class)
48 public class ToManyTest extends TxnTestCase {
49 
50     /*
51      * The primary database has a single byte key and byte[] array data.  Each
52      * byte of the data array is a secondary key in the to-many index.
53      *
54      * The primary map mirrors the primary database and contains Byte keys and
55      * a set of Byte objects for each map entry value.  The secondary map
56      * mirrors the secondary database, and for every secondary key (Byte)
57      * contains a set of primary keys (set of Byte).
58      */
59     private Map<Byte, Set<Byte>> priMap0 = new HashMap<Byte, Set<Byte>>();
60     private Map<Byte, Set<Byte>> secMap0 = new HashMap<Byte, Set<Byte>>();
61     private Database priDb;
62     private SecondaryDatabase secDb;
63 
64     @Parameters
genParams()65     public static List<Object[]> genParams() {
66 
67         /*
68          * This test does not work with TXN_NULL because with transactions we
69          * cannot abort the update in a one-to-many test when secondary key
70          * already exists in another primary record.
71          */
72         return getTxnParams(
73             new String[] {TxnTestCase.TXN_USER, TxnTestCase.TXN_AUTO}, false);
74     }
75 
ToManyTest(String type)76     public ToManyTest(String type){
77         initEnvConfig();
78         txnType = type;
79         isTransactional = (txnType != TXN_NULL);
80         customName = txnType;
81     }
82 
83     @After
tearDown()84     public void tearDown()
85         throws Exception {
86 
87         super.tearDown();
88         priMap0 = null;
89         secMap0 = null;
90         priDb = null;
91         secDb = null;
92     }
93 
94     @Test
testManyToMany()95     public void testManyToMany()
96         throws DatabaseException {
97 
98         priDb = openPrimary("pri");
99         secDb = openSecondary(priDb, "sec", true /*dups*/);
100 
101         writeAndVerify((byte) 0, new byte[] {});
102         writeAndVerify((byte) 0, null);
103         writeAndVerify((byte) 0, new byte[] {0, 1, 2});
104         writeAndVerify((byte) 0, null);
105         writeAndVerify((byte) 0, new byte[] {});
106         writeAndVerify((byte) 0, new byte[] {0});
107         writeAndVerify((byte) 0, new byte[] {0, 1});
108         writeAndVerify((byte) 0, new byte[] {0, 1, 2});
109         writeAndVerify((byte) 0, new byte[] {1, 2});
110         writeAndVerify((byte) 0, new byte[] {2});
111         writeAndVerify((byte) 0, new byte[] {});
112         writeAndVerify((byte) 0, null);
113 
114         writeAndVerify((byte) 0, new byte[] {0, 1, 2});
115         writeAndVerify((byte) 1, new byte[] {1, 2, 3});
116         writeAndVerify((byte) 0, null);
117         writeAndVerify((byte) 1, null);
118         writeAndVerify((byte) 0, new byte[] {0, 1, 2});
119         writeAndVerify((byte) 1, new byte[] {1, 2, 3});
120         writeAndVerify((byte) 0, new byte[] {0});
121         writeAndVerify((byte) 1, new byte[] {3});
122         writeAndVerify((byte) 0, null);
123         writeAndVerify((byte) 1, null);
124 
125         secDb.close();
126         priDb.close();
127     }
128 
129     @Test
testOneToMany()130     public void testOneToMany()
131         throws DatabaseException {
132 
133         priDb = openPrimary("pri");
134         secDb = openSecondary(priDb, "sec", false /*dups*/);
135 
136         writeAndVerify((byte) 0, new byte[] {1, 5});
137         writeAndVerify((byte) 1, new byte[] {2, 4});
138         writeAndVerify((byte) 0, new byte[] {0, 1, 5, 6});
139         writeAndVerify((byte) 1, new byte[] {2, 3, 4});
140         write((byte) 0, new byte[] {3}, true /*expectException*/);
141         writeAndVerify((byte) 1, new byte[] {});
142         writeAndVerify((byte) 0, new byte[] {0, 1, 2, 3, 4, 5, 6});
143         writeAndVerify((byte) 0, null);
144         writeAndVerify((byte) 1, new byte[] {0, 1, 2, 3, 4, 5, 6});
145         writeAndVerify((byte) 1, null);
146 
147         secDb.close();
148         priDb.close();
149     }
150 
151     /**
152      * Puts or deletes a single primary record, updates the maps, and verifies
153      * that the maps match the databases.
154      */
writeAndVerify(byte priKey, byte[] priData)155     private void writeAndVerify(byte priKey, byte[] priData)
156         throws DatabaseException {
157 
158         write(priKey, priData, false /*expectException*/);
159         updateMaps(new Byte(priKey), bytesToSet(priData));
160         verify();
161     }
162 
163     /**
164      * Puts or deletes a single primary record.
165      */
write(byte priKey, byte[] priData, boolean expectException)166     private void write(byte priKey, byte[] priData, boolean expectException)
167         throws DatabaseException {
168 
169         DatabaseEntry keyEntry = new DatabaseEntry(new byte[] { priKey });
170         DatabaseEntry dataEntry = new DatabaseEntry(priData);
171 
172         Transaction txn = txnBegin();
173         try {
174             OperationStatus status;
175             if (priData != null) {
176                 status = priDb.put(txn, keyEntry, dataEntry);
177             } else {
178                 status = priDb.delete(txn, keyEntry);
179             }
180             assertSame(OperationStatus.SUCCESS, status);
181             txnCommit(txn);
182             assertTrue(!expectException);
183         } catch (Exception e) {
184             txnAbort(txn);
185             assertTrue(e.toString(), expectException);
186         }
187     }
188 
189     /**
190      * Updates map 0 to reflect a record added to the primary database.
191      */
updateMaps(Byte priKey, Set<Byte> newPriData)192     private void updateMaps(Byte priKey, Set<Byte> newPriData) {
193 
194         /* Remove old secondary keys. */
195         Set<Byte> oldPriData = priMap0.get(priKey);
196         if (oldPriData != null) {
197             for (Iterator<Byte> i = oldPriData.iterator(); i.hasNext();) {
198                 Byte secKey = i.next();
199                 Set<Byte> priKeySet = secMap0.get(secKey);
200                 assertNotNull(priKeySet);
201                 assertTrue(priKeySet.remove(priKey));
202                 if (priKeySet.isEmpty()) {
203                     secMap0.remove(secKey);
204                 }
205             }
206         }
207 
208         if (newPriData != null) {
209             /* Put primary entry. */
210             priMap0.put(priKey, newPriData);
211             /* Add new secondary keys. */
212             for (Iterator<Byte> i = newPriData.iterator(); i.hasNext();) {
213                 Byte secKey = i.next();
214                 Set<Byte> priKeySet = secMap0.get(secKey);
215                 if (priKeySet == null) {
216                     priKeySet = new HashSet<Byte>();
217                     secMap0.put(secKey, priKeySet);
218                 }
219                 assertTrue(priKeySet.add(priKey));
220             }
221         } else {
222             /* Remove primary entry. */
223             priMap0.remove(priKey);
224         }
225     }
226 
227     /**
228      * Verifies that the maps match the databases.
229      */
verify()230     private void verify()
231         throws DatabaseException {
232 
233         Transaction txn = txnBeginCursor();
234         DatabaseEntry priKeyEntry = new DatabaseEntry();
235         DatabaseEntry secKeyEntry = new DatabaseEntry();
236         DatabaseEntry dataEntry = new DatabaseEntry();
237         Map<Byte, Set<Byte>> priMap1 = new HashMap<Byte, Set<Byte>>();
238         Map<Byte, Set<Byte>> priMap2 = new HashMap<Byte, Set<Byte>>();
239         Map<Byte, Set<Byte>> secMap1 = new HashMap<Byte, Set<Byte>>();
240         Map<Byte, Set<Byte>> secMap2 = new HashMap<Byte, Set<Byte>>();
241 
242         /* Build map 1 from the primary database. */
243         priMap2 = new HashMap<Byte, Set<Byte>>();
244         Cursor priCursor = priDb.openCursor(txn, null);
245         while (priCursor.getNext(priKeyEntry, dataEntry, null) ==
246                OperationStatus.SUCCESS) {
247             Byte priKey = new Byte(priKeyEntry.getData()[0]);
248             Set<Byte> priData = bytesToSet(dataEntry.getData());
249 
250             /* Update primary map. */
251             priMap1.put(priKey, priData);
252 
253             /* Update secondary map. */
254             for (Iterator<Byte> i = priData.iterator(); i.hasNext();) {
255                 Byte secKey = i.next();
256                 Set<Byte> priKeySet = secMap1.get(secKey);
257                 if (priKeySet == null) {
258                     priKeySet = new HashSet<Byte>();
259                     secMap1.put(secKey, priKeySet);
260                 }
261                 assertTrue(priKeySet.add(priKey));
262             }
263 
264             /*
265              * Add empty primary records to priMap2 while we're here, since
266              * they cannot be built from the secondary database.
267              */
268             if (priData.isEmpty()) {
269                 priMap2.put(priKey, priData);
270             }
271         }
272         priCursor.close();
273 
274         /* Build map 2 from the secondary database. */
275         SecondaryCursor secCursor = secDb.openSecondaryCursor(txn, null);
276         while (secCursor.getNext(secKeyEntry, priKeyEntry, dataEntry, null) ==
277                OperationStatus.SUCCESS) {
278             Byte priKey = new Byte(priKeyEntry.getData()[0]);
279             Byte secKey = new Byte(secKeyEntry.getData()[0]);
280 
281             /* Update primary map. */
282             Set<Byte> priData = priMap2.get(priKey);
283             if (priData == null) {
284                 priData = new HashSet<Byte>();
285                 priMap2.put(priKey, priData);
286             }
287             priData.add(secKey);
288 
289             /* Update secondary map. */
290             Set<Byte> secData = secMap2.get(secKey);
291             if (secData == null) {
292                 secData = new HashSet<Byte>();
293                 secMap2.put(secKey, secData);
294             }
295             secData.add(priKey);
296         }
297         secCursor.close();
298 
299         /* Compare. */
300         assertEquals(priMap0, priMap1);
301         assertEquals(priMap1, priMap2);
302         assertEquals(secMap0, secMap1);
303         assertEquals(secMap1, secMap2);
304 
305         txnCommit(txn);
306     }
307 
bytesToSet(byte[] bytes)308     private Set<Byte> bytesToSet(byte[] bytes) {
309         Set<Byte> set = null;
310         if (bytes != null) {
311             set = new HashSet<Byte>();
312             for (int i = 0; i < bytes.length; i += 1) {
313                 set.add(new Byte(bytes[i]));
314             }
315         }
316         return set;
317     }
318 
openPrimary(String name)319     private Database openPrimary(String name)
320         throws DatabaseException {
321 
322         DatabaseConfig dbConfig = new DatabaseConfig();
323         dbConfig.setTransactional(isTransactional);
324         dbConfig.setAllowCreate(true);
325 
326         Transaction txn = txnBegin();
327         try {
328             return env.openDatabase(txn, name, dbConfig);
329         } finally {
330             txnCommit(txn);
331         }
332     }
333 
openSecondary(Database priDb, String dbName, boolean dups)334     private SecondaryDatabase openSecondary(Database priDb,
335                                             String dbName,
336                                             boolean dups)
337         throws DatabaseException {
338 
339         SecondaryConfig dbConfig = new SecondaryConfig();
340         dbConfig.setTransactional(isTransactional);
341         dbConfig.setAllowCreate(true);
342         dbConfig.setSortedDuplicates(dups);
343         dbConfig.setMultiKeyCreator(new MyKeyCreator());
344 
345         Transaction txn = txnBegin();
346         try {
347             return env.openSecondaryDatabase(txn, dbName, priDb, dbConfig);
348         } finally {
349             txnCommit(txn);
350         }
351     }
352 
353     private static class MyKeyCreator implements SecondaryMultiKeyCreator {
354 
createSecondaryKeys(SecondaryDatabase secondary, DatabaseEntry key, DatabaseEntry data, Set<DatabaseEntry> results)355         public void createSecondaryKeys(SecondaryDatabase secondary,
356                                         DatabaseEntry key,
357                                         DatabaseEntry data,
358                                         Set<DatabaseEntry> results) {
359             for (int i = 0; i < data.getSize(); i+= 1) {
360                 results.add(new DatabaseEntry(data.getData(), i, 1));
361             }
362         }
363     }
364 }
365