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