1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2000,2008 Oracle. All rights reserved. 5 * 6 * $Id: ForeignKeyTest.java,v 12.1 2008/02/07 17:12:31 mark Exp $ 7 */ 8 9package com.sleepycat.collections.test; 10 11import java.util.Map; 12 13import junit.framework.Test; 14import junit.framework.TestCase; 15import junit.framework.TestSuite; 16 17import com.sleepycat.bind.serial.StoredClassCatalog; 18import com.sleepycat.bind.serial.TupleSerialMarshalledKeyCreator; 19import com.sleepycat.bind.serial.test.MarshalledObject; 20import com.sleepycat.collections.CurrentTransaction; 21import com.sleepycat.collections.TupleSerialFactory; 22import com.sleepycat.compat.DbCompat; 23import com.sleepycat.db.Database; 24import com.sleepycat.db.DatabaseConfig; 25import com.sleepycat.db.DatabaseException; 26import com.sleepycat.db.Environment; 27import com.sleepycat.db.ForeignKeyDeleteAction; 28import com.sleepycat.db.SecondaryConfig; 29import com.sleepycat.db.SecondaryDatabase; 30import com.sleepycat.util.ExceptionUnwrapper; 31import com.sleepycat.util.RuntimeExceptionWrapper; 32import com.sleepycat.util.test.SharedTestUtils; 33import com.sleepycat.util.test.TestEnv; 34 35/** 36 * @author Mark Hayes 37 */ 38public class ForeignKeyTest extends TestCase { 39 40 private static final ForeignKeyDeleteAction[] ACTIONS = { 41 ForeignKeyDeleteAction.ABORT, 42 ForeignKeyDeleteAction.NULLIFY, 43 ForeignKeyDeleteAction.CASCADE, 44 }; 45 private static final String[] ACTION_LABELS = { 46 "ABORT", 47 "NULLIFY", 48 "CASCADE", 49 }; 50 51 public static void main(String[] args) 52 throws Exception { 53 54 junit.framework.TestResult tr = 55 junit.textui.TestRunner.run(suite()); 56 if (tr.errorCount() > 0 || 57 tr.failureCount() > 0) { 58 System.exit(1); 59 } else { 60 System.exit(0); 61 } 62 } 63 64 public static Test suite() 65 throws Exception { 66 67 TestSuite suite = new TestSuite(); 68 for (int i = 0; i < TestEnv.ALL.length; i += 1) { 69 for (int j = 0; j < ACTIONS.length; j += 1) { 70 suite.addTest(new ForeignKeyTest(TestEnv.ALL[i], 71 ACTIONS[j], 72 ACTION_LABELS[j])); 73 } 74 } 75 return suite; 76 } 77 78 private TestEnv testEnv; 79 private Environment env; 80 private StoredClassCatalog catalog; 81 private TupleSerialFactory factory; 82 private Database store1; 83 private Database store2; 84 private SecondaryDatabase index1; 85 private SecondaryDatabase index2; 86 private Map storeMap1; 87 private Map storeMap2; 88 private Map indexMap1; 89 private Map indexMap2; 90 private ForeignKeyDeleteAction onDelete; 91 92 public ForeignKeyTest(TestEnv testEnv, ForeignKeyDeleteAction onDelete, 93 String onDeleteLabel) { 94 95 super("ForeignKeyTest-" + testEnv.getName() + '-' + onDeleteLabel); 96 97 this.testEnv = testEnv; 98 this.onDelete = onDelete; 99 } 100 101 public void setUp() 102 throws Exception { 103 104 SharedTestUtils.printTestName(getName()); 105 env = testEnv.open(getName()); 106 107 createDatabase(); 108 } 109 110 public void tearDown() { 111 112 try { 113 if (index1 != null) { 114 index1.close(); 115 } 116 if (index2 != null) { 117 index2.close(); 118 } 119 if (store1 != null) { 120 store1.close(); 121 } 122 if (store2 != null) { 123 store2.close(); 124 } 125 if (catalog != null) { 126 catalog.close(); 127 } 128 if (env != null) { 129 env.close(); 130 } 131 } catch (Exception e) { 132 System.out.println("Ignored exception during tearDown: " + e); 133 } finally { 134 /* Ensure that GC can cleanup. */ 135 env = null; 136 testEnv = null; 137 catalog = null; 138 store1 = null; 139 store2 = null; 140 index1 = null; 141 index2 = null; 142 factory = null; 143 storeMap1 = null; 144 storeMap2 = null; 145 indexMap1 = null; 146 indexMap2 = null; 147 } 148 } 149 150 public void runTest() 151 throws Exception { 152 153 try { 154 createViews(); 155 writeAndRead(); 156 } catch (Exception e) { 157 throw ExceptionUnwrapper.unwrap(e); 158 } 159 } 160 161 private void createDatabase() 162 throws Exception { 163 164 catalog = new StoredClassCatalog(openDb("catalog.db")); 165 factory = new TupleSerialFactory(catalog); 166 assertSame(catalog, factory.getCatalog()); 167 168 store1 = openDb("store1.db"); 169 store2 = openDb("store2.db"); 170 index1 = openSecondaryDb(factory, "1", store1, "index1.db", null); 171 index2 = openSecondaryDb(factory, "2", store2, "index2.db", store1); 172 } 173 174 private Database openDb(String file) 175 throws Exception { 176 177 DatabaseConfig config = new DatabaseConfig(); 178 DbCompat.setTypeBtree(config); 179 config.setTransactional(testEnv.isTxnMode()); 180 config.setAllowCreate(true); 181 182 return DbCompat.testOpenDatabase(env, null, file, null, config); 183 } 184 185 private SecondaryDatabase openSecondaryDb(TupleSerialFactory factory, 186 String keyName, 187 Database primary, 188 String file, 189 Database foreignStore) 190 throws Exception { 191 192 TupleSerialMarshalledKeyCreator keyCreator = 193 factory.getKeyCreator(MarshalledObject.class, keyName); 194 195 SecondaryConfig secConfig = new SecondaryConfig(); 196 DbCompat.setTypeBtree(secConfig); 197 secConfig.setTransactional(testEnv.isTxnMode()); 198 secConfig.setAllowCreate(true); 199 secConfig.setKeyCreator(keyCreator); 200 if (foreignStore != null) { 201 secConfig.setForeignKeyDatabase(foreignStore); 202 secConfig.setForeignKeyDeleteAction(onDelete); 203 if (onDelete == ForeignKeyDeleteAction.NULLIFY) { 204 secConfig.setForeignKeyNullifier(keyCreator); 205 } 206 } 207 208 return DbCompat.testOpenSecondaryDatabase 209 (env, null, file, null, primary, secConfig); 210 } 211 212 private void createViews() 213 throws Exception { 214 215 storeMap1 = factory.newMap(store1, String.class, 216 MarshalledObject.class, true); 217 storeMap2 = factory.newMap(store2, String.class, 218 MarshalledObject.class, true); 219 indexMap1 = factory.newMap(index1, String.class, 220 MarshalledObject.class, true); 221 indexMap2 = factory.newMap(index2, String.class, 222 MarshalledObject.class, true); 223 } 224 225 private void writeAndRead() 226 throws Exception { 227 228 CurrentTransaction txn = CurrentTransaction.getInstance(env); 229 if (txn != null) { 230 txn.beginTransaction(null); 231 } 232 233 MarshalledObject o1 = new MarshalledObject("data1", "pk1", "ik1", ""); 234 assertNull(storeMap1.put(null, o1)); 235 236 assertEquals(o1, storeMap1.get("pk1")); 237 assertEquals(o1, indexMap1.get("ik1")); 238 239 MarshalledObject o2 = new MarshalledObject("data2", "pk2", "", "pk1"); 240 assertNull(storeMap2.put(null, o2)); 241 242 assertEquals(o2, storeMap2.get("pk2")); 243 assertEquals(o2, indexMap2.get("pk1")); 244 245 if (txn != null) { 246 txn.commitTransaction(); 247 txn.beginTransaction(null); 248 } 249 250 /* 251 * store1 contains o1 with primary key "pk1" and index key "ik1". 252 * 253 * store2 contains o2 with primary key "pk2" and foreign key "pk1", 254 * which is the primary key of store1. 255 */ 256 257 if (onDelete == ForeignKeyDeleteAction.ABORT) { 258 259 /* Test that we abort trying to delete a referenced key. */ 260 261 try { 262 storeMap1.remove("pk1"); 263 fail(); 264 } catch (RuntimeExceptionWrapper expected) { 265 assertTrue(expected.getCause() instanceof DatabaseException); 266 if (txn != null) { 267 txn.abortTransaction(); 268 txn.beginTransaction(null); 269 } 270 } 271 272 /* Test that we can put a record into store2 with a null foreign 273 * key value. */ 274 275 o2 = new MarshalledObject("data2", "pk2", "", ""); 276 assertNotNull(storeMap2.put(null, o2)); 277 assertEquals(o2, storeMap2.get("pk2")); 278 279 /* The index2 record should have been deleted since the key was set 280 * to null above. */ 281 282 assertNull(indexMap2.get("pk1")); 283 284 /* Test that now we can delete the record in store1, since it is no 285 * longer referenced. */ 286 287 assertNotNull(storeMap1.remove("pk1")); 288 assertNull(storeMap1.get("pk1")); 289 assertNull(indexMap1.get("ik1")); 290 291 } else if (onDelete == ForeignKeyDeleteAction.NULLIFY) { 292 293 /* Delete the referenced key. */ 294 295 assertNotNull(storeMap1.remove("pk1")); 296 assertNull(storeMap1.get("pk1")); 297 assertNull(indexMap1.get("ik1")); 298 299 /* The store2 record should still exist, but should have an empty 300 * secondary key since it was nullified. */ 301 302 o2 = (MarshalledObject) storeMap2.get("pk2"); 303 assertNotNull(o2); 304 assertEquals("data2", o2.getData()); 305 assertEquals("pk2", o2.getPrimaryKey()); 306 assertEquals("", o2.getIndexKey1()); 307 assertEquals("", o2.getIndexKey2()); 308 309 } else if (onDelete == ForeignKeyDeleteAction.CASCADE) { 310 311 /* Delete the referenced key. */ 312 313 assertNotNull(storeMap1.remove("pk1")); 314 assertNull(storeMap1.get("pk1")); 315 assertNull(indexMap1.get("ik1")); 316 317 /* The store2 record should have deleted also. */ 318 319 assertNull(storeMap2.get("pk2")); 320 assertNull(indexMap2.get("pk1")); 321 322 } else { 323 throw new IllegalStateException(); 324 } 325 326 /* 327 * Test that a foreign key value may not be used that is not present 328 * in the foreign store. "pk2" is not in store1 in this case. 329 */ 330 assertNull(storeMap1.get("pk2")); 331 MarshalledObject o3 = new MarshalledObject("data3", "pk3", "", "pk2"); 332 try { 333 storeMap2.put(null, o3); 334 fail(); 335 } catch (RuntimeExceptionWrapper expected) { 336 assertTrue(expected.getCause() instanceof DatabaseException); 337 } 338 339 if (txn != null) { 340 txn.commitTransaction(); 341 } 342 } 343} 344