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.4.2.5 2008/01/07 15:14:35 cwl Exp $
007: */
008:
009: package com.sleepycat.persist.test;
010:
011: import static com.sleepycat.persist.model.DeleteAction.ABORT;
012: import static com.sleepycat.persist.model.DeleteAction.CASCADE;
013: import static com.sleepycat.persist.model.DeleteAction.NULLIFY;
014: import static com.sleepycat.persist.model.Relationship.ONE_TO_ONE;
015:
016: import java.util.Enumeration;
017:
018: import junit.framework.Test;
019: import junit.framework.TestSuite;
020:
021: import com.sleepycat.je.DatabaseException;
022: import com.sleepycat.je.Transaction;
023: import com.sleepycat.je.test.TxnTestCase;
024: import com.sleepycat.persist.EntityStore;
025: import com.sleepycat.persist.PrimaryIndex;
026: import com.sleepycat.persist.SecondaryIndex;
027: import com.sleepycat.persist.StoreConfig;
028: import com.sleepycat.persist.model.DeleteAction;
029: import com.sleepycat.persist.model.Entity;
030: import com.sleepycat.persist.model.Persistent;
031: import com.sleepycat.persist.model.PrimaryKey;
032: import com.sleepycat.persist.model.SecondaryKey;
033:
034: /**
035: * @author Mark Hayes
036: */
037: public class ForeignKeyTest extends TxnTestCase {
038:
039: private static final DeleteAction[] ACTIONS = { ABORT, NULLIFY,
040: CASCADE, };
041:
042: private static final String[] ACTION_LABELS = { "ABORT", "NULLIFY",
043: "CASCADE", };
044:
045: public static Test suite() {
046: TestSuite suite = new TestSuite();
047: for (int i = 0; i < ACTIONS.length; i += 1) {
048: for (int j = 0; j < 2; j++) {
049: TestSuite txnSuite = txnTestSuite(ForeignKeyTest.class,
050: null, null);//envConfig, txnTypes);
051: Enumeration e = txnSuite.tests();
052: while (e.hasMoreElements()) {
053: ForeignKeyTest test = (ForeignKeyTest) e
054: .nextElement();
055: test.onDelete = ACTIONS[i];
056: test.onDeleteLabel = ACTION_LABELS[i];
057: test.useSubclass = (j == 0);
058: test.useSubclassLabel = (j == 0) ? "UseSubclass"
059: : "UseBaseclass";
060: suite.addTest(test);
061: }
062: }
063: }
064: return suite;
065: }
066:
067: private EntityStore store;
068: private PrimaryIndex<String, Entity1> pri1;
069: private PrimaryIndex<String, Entity2> pri2;
070: private SecondaryIndex<String, String, Entity1> sec1;
071: private SecondaryIndex<String, String, Entity2> sec2;
072: private DeleteAction onDelete;
073: private String onDeleteLabel;
074: private boolean useSubclass;
075: private String useSubclassLabel;
076:
077: public void tearDown() throws Exception {
078:
079: super .tearDown();
080: setName(getName() + '-' + onDeleteLabel + "-"
081: + useSubclassLabel);
082: }
083:
084: private void open() throws DatabaseException {
085:
086: StoreConfig config = new StoreConfig();
087: config.setAllowCreate(envConfig.getAllowCreate());
088: config.setTransactional(envConfig.getTransactional());
089:
090: store = new EntityStore(env, "test", config);
091:
092: pri1 = store.getPrimaryIndex(String.class, Entity1.class);
093: sec1 = store.getSecondaryIndex(pri1, String.class, "sk");
094: pri2 = store.getPrimaryIndex(String.class, Entity2.class);
095: sec2 = store.getSecondaryIndex(pri2, String.class, "sk_"
096: + onDeleteLabel);
097: }
098:
099: private void close() throws DatabaseException {
100:
101: store.close();
102: }
103:
104: public void testForeignKeys() throws Exception {
105:
106: open();
107: Transaction txn = txnBegin();
108:
109: Entity1 o1 = new Entity1("pk1", "sk1");
110: assertNull(pri1.put(txn, o1));
111:
112: assertEquals(o1, pri1.get(txn, "pk1", null));
113: assertEquals(o1, sec1.get(txn, "sk1", null));
114:
115: Entity2 o2 = (useSubclass ? new Entity3("pk2", "pk1", onDelete)
116: : new Entity2("pk2", "pk1", onDelete));
117: assertNull(pri2.put(txn, o2));
118:
119: assertEquals(o2, pri2.get(txn, "pk2", null));
120: assertEquals(o2, sec2.get(txn, "pk1", null));
121:
122: txnCommit(txn);
123: txn = txnBegin();
124:
125: /*
126: * pri1 contains o1 with primary key "pk1" and index key "sk1".
127: *
128: * pri2 contains o2 with primary key "pk2" and foreign key "pk1",
129: * which is the primary key of pri1.
130: */
131: if (onDelete == ABORT) {
132:
133: /* Test that we abort trying to delete a referenced key. */
134:
135: try {
136: pri1.delete(txn, "pk1");
137: fail();
138: } catch (DatabaseException expected) {
139: txnAbort(txn);
140: txn = txnBegin();
141: }
142:
143: /*
144: * Test that we can put a record into store2 with a null foreign
145: * key value.
146: */
147: o2 = (useSubclass ? new Entity3("pk2", null, onDelete)
148: : new Entity2("pk2", null, onDelete));
149: assertNotNull(pri2.put(txn, o2));
150: assertEquals(o2, pri2.get(txn, "pk2", null));
151:
152: /*
153: * The index2 record should have been deleted since the key was set
154: * to null above.
155: */
156: assertNull(sec2.get(txn, "pk1", null));
157:
158: /*
159: * Test that now we can delete the record in store1, since it is no
160: * longer referenced.
161: */
162: assertNotNull(pri1.delete(txn, "pk1"));
163: assertNull(pri1.get(txn, "pk1", null));
164: assertNull(sec1.get(txn, "sk1", null));
165:
166: } else if (onDelete == NULLIFY) {
167:
168: /* Delete the referenced key. */
169: assertNotNull(pri1.delete(txn, "pk1"));
170: assertNull(pri1.get(txn, "pk1", null));
171: assertNull(sec1.get(txn, "sk1", null));
172:
173: /*
174: * The store2 record should still exist, but should have an empty
175: * secondary key since it was nullified.
176: */
177: o2 = pri2.get(txn, "pk2", null);
178: assertNotNull(o2);
179: assertEquals("pk2", o2.pk);
180: assertEquals(null, o2.getSk(onDelete));
181:
182: } else if (onDelete == CASCADE) {
183:
184: /* Delete the referenced key. */
185: assertNotNull(pri1.delete(txn, "pk1"));
186: assertNull(pri1.get(txn, "pk1", null));
187: assertNull(sec1.get(txn, "sk1", null));
188:
189: /* The store2 record should have deleted also. */
190: assertNull(pri2.get(txn, "pk2", null));
191: assertNull(sec2.get(txn, "pk1", null));
192:
193: } else {
194: throw new IllegalStateException();
195: }
196:
197: /*
198: * Test that a foreign key value may not be used that is not present in
199: * the foreign store. "pk2" is not in store1 in this case.
200: */
201: Entity2 o3 = (useSubclass ? new Entity3("pk3", "pk2", onDelete)
202: : new Entity2("pk3", "pk2", onDelete));
203: try {
204: pri2.put(txn, o3);
205: fail();
206: } catch (DatabaseException expected) {
207: }
208:
209: txnCommit(txn);
210: close();
211: }
212:
213: @Entity
214: static class Entity1 {
215:
216: @PrimaryKey
217: String pk;
218:
219: @SecondaryKey(relate=ONE_TO_ONE)
220: String sk;
221:
222: private Entity1() {
223: }
224:
225: Entity1(String pk, String sk) {
226: this .pk = pk;
227: this .sk = sk;
228: }
229:
230: @Override
231: public boolean equals(Object other) {
232: Entity1 o = (Entity1) other;
233: return nullOrEqual(pk, o.pk) && nullOrEqual(sk, o.sk);
234: }
235: }
236:
237: @Entity
238: static class Entity2 {
239:
240: @PrimaryKey
241: String pk;
242:
243: @SecondaryKey(relate=ONE_TO_ONE,relatedEntity=Entity1.class,onRelatedEntityDelete=ABORT)
244: String sk_ABORT;
245:
246: @SecondaryKey(relate=ONE_TO_ONE,relatedEntity=Entity1.class,onRelatedEntityDelete=CASCADE)
247: String sk_CASCADE;
248:
249: @SecondaryKey(relate=ONE_TO_ONE,relatedEntity=Entity1.class,onRelatedEntityDelete=NULLIFY)
250: String sk_NULLIFY;
251:
252: private Entity2() {
253: }
254:
255: Entity2(String pk, String sk, DeleteAction action) {
256: this .pk = pk;
257: switch (action) {
258: case ABORT:
259: sk_ABORT = sk;
260: break;
261: case CASCADE:
262: sk_CASCADE = sk;
263: break;
264: case NULLIFY:
265: sk_NULLIFY = sk;
266: break;
267: default:
268: throw new IllegalArgumentException();
269: }
270: }
271:
272: String getSk(DeleteAction action) {
273: switch (action) {
274: case ABORT:
275: return sk_ABORT;
276: case CASCADE:
277: return sk_CASCADE;
278: case NULLIFY:
279: return sk_NULLIFY;
280: default:
281: throw new IllegalArgumentException();
282: }
283: }
284:
285: @Override
286: public boolean equals(Object other) {
287: Entity2 o = (Entity2) other;
288: return nullOrEqual(pk, o.pk)
289: && nullOrEqual(sk_ABORT, o.sk_ABORT)
290: && nullOrEqual(sk_CASCADE, o.sk_CASCADE)
291: && nullOrEqual(sk_NULLIFY, o.sk_NULLIFY);
292: }
293: }
294:
295: @Persistent
296: static class Entity3 extends Entity2 {
297: Entity3() {
298: }
299:
300: Entity3(String pk, String sk, DeleteAction action) {
301: super (pk, sk, action);
302: }
303: }
304:
305: static boolean nullOrEqual(Object o1, Object o2) {
306: if (o1 == null) {
307: return o2 == null;
308: } else {
309: return o1.equals(o2);
310: }
311: }
312: }
|