001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2000,2008 Oracle. All rights reserved.
005: *
006: * $Id: ForeignKeyTest.java,v 1.30.2.2 2008/01/07 15:14:24 cwl Exp $
007: */
008:
009: package com.sleepycat.collections.test;
010:
011: import java.util.Map;
012:
013: import junit.framework.Test;
014: import junit.framework.TestCase;
015: import junit.framework.TestSuite;
016:
017: import com.sleepycat.bind.serial.StoredClassCatalog;
018: import com.sleepycat.bind.serial.TupleSerialMarshalledKeyCreator;
019: import com.sleepycat.bind.serial.test.MarshalledObject;
020: import com.sleepycat.collections.CurrentTransaction;
021: import com.sleepycat.collections.TupleSerialFactory;
022: import com.sleepycat.compat.DbCompat;
023: import com.sleepycat.je.Database;
024: import com.sleepycat.je.DatabaseConfig;
025: import com.sleepycat.je.DatabaseException;
026: import com.sleepycat.je.Environment;
027: import com.sleepycat.je.ForeignKeyDeleteAction;
028: import com.sleepycat.je.SecondaryConfig;
029: import com.sleepycat.je.SecondaryDatabase;
030: import com.sleepycat.util.ExceptionUnwrapper;
031: import com.sleepycat.util.RuntimeExceptionWrapper;
032:
033: /**
034: * @author Mark Hayes
035: */
036: public class ForeignKeyTest extends TestCase {
037:
038: private static final ForeignKeyDeleteAction[] ACTIONS = {
039: ForeignKeyDeleteAction.ABORT,
040: ForeignKeyDeleteAction.NULLIFY,
041: ForeignKeyDeleteAction.CASCADE, };
042: private static final String[] ACTION_LABELS = { "ABORT", "NULLIFY",
043: "CASCADE", };
044:
045: public static void main(String[] args) throws Exception {
046:
047: junit.framework.TestResult tr = junit.textui.TestRunner
048: .run(suite());
049: if (tr.errorCount() > 0 || tr.failureCount() > 0) {
050: System.exit(1);
051: } else {
052: System.exit(0);
053: }
054: }
055:
056: public static Test suite() throws Exception {
057:
058: TestSuite suite = new TestSuite();
059: for (int i = 0; i < TestEnv.ALL.length; i += 1) {
060: for (int j = 0; j < ACTIONS.length; j += 1) {
061: suite.addTest(new ForeignKeyTest(TestEnv.ALL[i],
062: ACTIONS[j], ACTION_LABELS[j]));
063: }
064: }
065: return suite;
066: }
067:
068: private TestEnv testEnv;
069: private Environment env;
070: private StoredClassCatalog catalog;
071: private TupleSerialFactory factory;
072: private Database store1;
073: private Database store2;
074: private SecondaryDatabase index1;
075: private SecondaryDatabase index2;
076: private Map storeMap1;
077: private Map storeMap2;
078: private Map indexMap1;
079: private Map indexMap2;
080: private ForeignKeyDeleteAction onDelete;
081:
082: public ForeignKeyTest(TestEnv testEnv,
083: ForeignKeyDeleteAction onDelete, String onDeleteLabel) {
084:
085: super ("ForeignKeyTest-" + testEnv.getName() + '-'
086: + onDeleteLabel);
087:
088: this .testEnv = testEnv;
089: this .onDelete = onDelete;
090: }
091:
092: public void setUp() throws Exception {
093:
094: DbTestUtil.printTestName(getName());
095: env = testEnv.open(getName());
096:
097: createDatabase();
098: }
099:
100: public void tearDown() {
101:
102: try {
103: if (index1 != null) {
104: index1.close();
105: }
106: if (index2 != null) {
107: index2.close();
108: }
109: if (store1 != null) {
110: store1.close();
111: }
112: if (store2 != null) {
113: store2.close();
114: }
115: if (catalog != null) {
116: catalog.close();
117: }
118: if (env != null) {
119: env.close();
120: }
121: } catch (Exception e) {
122: System.out.println("Ignored exception during tearDown: "
123: + e);
124: } finally {
125: /* Ensure that GC can cleanup. */
126: env = null;
127: testEnv = null;
128: catalog = null;
129: store1 = null;
130: store2 = null;
131: index1 = null;
132: index2 = null;
133: factory = null;
134: storeMap1 = null;
135: storeMap2 = null;
136: indexMap1 = null;
137: indexMap2 = null;
138: }
139: }
140:
141: public void runTest() throws Exception {
142:
143: try {
144: createViews();
145: writeAndRead();
146: } catch (Exception e) {
147: throw ExceptionUnwrapper.unwrap(e);
148: }
149: }
150:
151: private void createDatabase() throws Exception {
152:
153: catalog = new StoredClassCatalog(openDb("catalog.db"));
154: factory = new TupleSerialFactory(catalog);
155: assertSame(catalog, factory.getCatalog());
156:
157: store1 = openDb("store1.db");
158: store2 = openDb("store2.db");
159: index1 = openSecondaryDb(factory, "1", store1, "index1.db",
160: null);
161: index2 = openSecondaryDb(factory, "2", store2, "index2.db",
162: store1);
163: }
164:
165: private Database openDb(String file) throws Exception {
166:
167: DatabaseConfig config = new DatabaseConfig();
168: config.setTransactional(testEnv.isTxnMode());
169: config.setAllowCreate(true);
170:
171: return DbCompat.openDatabase(env, null, file, null, config);
172: }
173:
174: private SecondaryDatabase openSecondaryDb(
175: TupleSerialFactory factory, String keyName,
176: Database primary, String file, Database foreignStore)
177: throws Exception {
178:
179: TupleSerialMarshalledKeyCreator keyCreator = factory
180: .getKeyCreator(MarshalledObject.class, keyName);
181:
182: SecondaryConfig secConfig = new SecondaryConfig();
183: secConfig.setTransactional(testEnv.isTxnMode());
184: secConfig.setAllowCreate(true);
185: secConfig.setKeyCreator(keyCreator);
186: if (foreignStore != null) {
187: secConfig.setForeignKeyDatabase(foreignStore);
188: secConfig.setForeignKeyDeleteAction(onDelete);
189: secConfig.setForeignKeyNullifier(keyCreator);
190: }
191:
192: return DbCompat.openSecondaryDatabase(env, null, file, null,
193: primary, secConfig);
194: }
195:
196: private void createViews() throws Exception {
197:
198: storeMap1 = factory.newMap(store1, String.class,
199: MarshalledObject.class, true);
200: storeMap2 = factory.newMap(store2, String.class,
201: MarshalledObject.class, true);
202: indexMap1 = factory.newMap(index1, String.class,
203: MarshalledObject.class, true);
204: indexMap2 = factory.newMap(index2, String.class,
205: MarshalledObject.class, true);
206: }
207:
208: private void writeAndRead() throws Exception {
209:
210: CurrentTransaction txn = CurrentTransaction.getInstance(env);
211: if (txn != null) {
212: txn.beginTransaction(null);
213: }
214:
215: MarshalledObject o1 = new MarshalledObject("data1", "pk1",
216: "ik1", "");
217: assertNull(storeMap1.put(null, o1));
218:
219: assertEquals(o1, storeMap1.get("pk1"));
220: assertEquals(o1, indexMap1.get("ik1"));
221:
222: MarshalledObject o2 = new MarshalledObject("data2", "pk2", "",
223: "pk1");
224: assertNull(storeMap2.put(null, o2));
225:
226: assertEquals(o2, storeMap2.get("pk2"));
227: assertEquals(o2, indexMap2.get("pk1"));
228:
229: if (txn != null) {
230: txn.commitTransaction();
231: txn.beginTransaction(null);
232: }
233:
234: /*
235: * store1 contains o1 with primary key "pk1" and index key "ik1".
236: *
237: * store2 contains o2 with primary key "pk2" and foreign key "pk1",
238: * which is the primary key of store1.
239: */
240:
241: if (onDelete == ForeignKeyDeleteAction.ABORT) {
242:
243: /* Test that we abort trying to delete a referenced key. */
244:
245: try {
246: storeMap1.remove("pk1");
247: fail();
248: } catch (RuntimeExceptionWrapper expected) {
249: assertTrue(expected.getCause() instanceof DatabaseException);
250: if (txn != null) {
251: txn.abortTransaction();
252: txn.beginTransaction(null);
253: }
254: }
255:
256: /* Test that we can put a record into store2 with a null foreign
257: * key value. */
258:
259: o2 = new MarshalledObject("data2", "pk2", "", "");
260: assertNotNull(storeMap2.put(null, o2));
261: assertEquals(o2, storeMap2.get("pk2"));
262:
263: /* The index2 record should have been deleted since the key was set
264: * to null above. */
265:
266: assertNull(indexMap2.get("pk1"));
267:
268: /* Test that now we can delete the record in store1, since it is no
269: * longer referenced. */
270:
271: assertNotNull(storeMap1.remove("pk1"));
272: assertNull(storeMap1.get("pk1"));
273: assertNull(indexMap1.get("ik1"));
274:
275: } else if (onDelete == ForeignKeyDeleteAction.NULLIFY) {
276:
277: /* Delete the referenced key. */
278:
279: assertNotNull(storeMap1.remove("pk1"));
280: assertNull(storeMap1.get("pk1"));
281: assertNull(indexMap1.get("ik1"));
282:
283: /* The store2 record should still exist, but should have an empty
284: * secondary key since it was nullified. */
285:
286: o2 = (MarshalledObject) storeMap2.get("pk2");
287: assertNotNull(o2);
288: assertEquals("data2", o2.getData());
289: assertEquals("pk2", o2.getPrimaryKey());
290: assertEquals("", o2.getIndexKey1());
291: assertEquals("", o2.getIndexKey2());
292:
293: } else if (onDelete == ForeignKeyDeleteAction.CASCADE) {
294:
295: /* Delete the referenced key. */
296:
297: assertNotNull(storeMap1.remove("pk1"));
298: assertNull(storeMap1.get("pk1"));
299: assertNull(indexMap1.get("ik1"));
300:
301: /* The store2 record should have deleted also. */
302:
303: assertNull(storeMap2.get("pk2"));
304: assertNull(indexMap2.get("pk1"));
305:
306: } else {
307: throw new IllegalStateException();
308: }
309:
310: /*
311: * Test that a foreign key value may not be used that is not present
312: * in the foreign store. "pk2" is not in store1 in this case.
313: */
314: MarshalledObject o3 = new MarshalledObject("data3", "pk3", "",
315: "pk2");
316: try {
317: storeMap2.put(null, o3);
318: fail();
319: } catch (RuntimeExceptionWrapper expected) {
320: assertTrue(expected.getCause() instanceof DatabaseException);
321: }
322:
323: if (txn != null) {
324: txn.commitTransaction();
325: }
326: }
327: }
|