001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: SecondaryDatabase.java,v 1.52.2.3 2008/01/07 15:14:08 cwl Exp $
007: */
008:
009: package com.sleepycat.je;
010:
011: import java.util.Collections;
012: import java.util.HashSet;
013: import java.util.Iterator;
014: import java.util.Set;
015: import java.util.logging.Level;
016: import java.util.logging.Logger;
017:
018: import com.sleepycat.je.dbi.DatabaseImpl;
019: import com.sleepycat.je.dbi.GetMode;
020: import com.sleepycat.je.dbi.PutMode;
021: import com.sleepycat.je.dbi.CursorImpl.SearchMode;
022: import com.sleepycat.je.txn.Locker;
023: import com.sleepycat.je.txn.LockerFactory;
024: import com.sleepycat.je.utilint.DatabaseUtil;
025:
026: /**
027: * Javadoc for this public class is generated via
028: * the doc templates in the doc_src directory.
029: */
030: public class SecondaryDatabase extends Database {
031:
032: private Database primaryDb;
033: private SecondaryConfig secondaryConfig;
034: private SecondaryTrigger secondaryTrigger;
035: private ForeignKeyTrigger foreignKeyTrigger;
036:
037: /**
038: * Creates a secondary database but does not open or fully initialize it.
039: */
040: SecondaryDatabase(Environment env, SecondaryConfig secConfig,
041: Database primaryDatabase) throws DatabaseException {
042:
043: super (env);
044: DatabaseUtil.checkForNullParam(primaryDatabase,
045: "primaryDatabase");
046: primaryDatabase.checkRequiredDbState(OPEN,
047: "Can't use as primary:");
048: if (primaryDatabase.configuration.getSortedDuplicates()) {
049: throw new IllegalArgumentException(
050: "Duplicates must not be allowed for a primary database: "
051: + primaryDatabase.getDebugName());
052: }
053: if (env.getEnvironmentImpl() != primaryDatabase
054: .getEnvironment().getEnvironmentImpl()) {
055: throw new IllegalArgumentException(
056: "Primary and secondary databases must be in the same"
057: + " environment");
058: }
059: if (secConfig.getKeyCreator() != null
060: && secConfig.getMultiKeyCreator() != null) {
061: throw new IllegalArgumentException(
062: "secConfig.getKeyCreator() and getMultiKeyCreator() may not"
063: + " both be non-null");
064: }
065: if (!primaryDatabase.configuration.getReadOnly()
066: && secConfig.getKeyCreator() == null
067: && secConfig.getMultiKeyCreator() == null) {
068: throw new NullPointerException(
069: "secConfig and getKeyCreator()/getMultiKeyCreator()"
070: + " may be null only if the primary database is read-only");
071: }
072: if (secConfig.getForeignKeyNullifier() != null
073: && secConfig.getForeignMultiKeyNullifier() != null) {
074: throw new IllegalArgumentException(
075: "secConfig.getForeignKeyNullifier() and"
076: + " getForeignMultiKeyNullifier() may not both be non-null");
077: }
078: if (secConfig.getForeignKeyDeleteAction() == ForeignKeyDeleteAction.NULLIFY
079: && secConfig.getForeignKeyNullifier() == null
080: && secConfig.getForeignMultiKeyNullifier() == null) {
081: throw new NullPointerException(
082: "ForeignKeyNullifier or ForeignMultiKeyNullifier must be"
083: + " non-null when ForeignKeyDeleteAction is NULLIFY");
084: }
085: if (secConfig.getForeignKeyNullifier() != null
086: && secConfig.getMultiKeyCreator() != null) {
087: throw new IllegalArgumentException(
088: "ForeignKeyNullifier may not be used with"
089: + " SecondaryMultiKeyCreator -- use"
090: + " ForeignMultiKeyNullifier instead");
091: }
092: if (secConfig.getForeignKeyDatabase() != null) {
093: Database foreignDb = secConfig.getForeignKeyDatabase();
094: if (foreignDb.getDatabaseImpl().getSortedDuplicates()) {
095: throw new IllegalArgumentException(
096: "Duplicates must not be allowed for a foreign key "
097: + " database: "
098: + foreignDb.getDebugName());
099: }
100: }
101: primaryDb = primaryDatabase;
102: secondaryTrigger = new SecondaryTrigger(this );
103: if (secConfig.getForeignKeyDatabase() != null) {
104: foreignKeyTrigger = new ForeignKeyTrigger(this );
105: }
106: }
107:
108: /**
109: * Create a database, called by Environment
110: */
111: void initNew(Environment env, Locker locker, String databaseName,
112: DatabaseConfig dbConfig) throws DatabaseException {
113:
114: super .initNew(env, locker, databaseName, dbConfig);
115: init(locker);
116: }
117:
118: /**
119: * Open a database, called by Environment
120: */
121: void initExisting(Environment env, Locker locker,
122: DatabaseImpl database, DatabaseConfig dbConfig)
123: throws DatabaseException {
124:
125: /* Disallow one secondary associated with two different primaries. */
126: Database otherPriDb = database.findPrimaryDatabase();
127: if (otherPriDb != null
128: && otherPriDb.getDatabaseImpl() != primaryDb
129: .getDatabaseImpl()) {
130: throw new IllegalArgumentException(
131: "Secondary is already associated with a different primary: "
132: + otherPriDb.getDebugName());
133: }
134:
135: super .initExisting(env, locker, database, dbConfig);
136: init(locker);
137: }
138:
139: /**
140: * Adds secondary to primary's list, and populates the secondary if needed.
141: */
142: private void init(Locker locker) throws DatabaseException {
143:
144: trace(Level.FINEST, "SecondaryDatabase open");
145:
146: secondaryConfig = (SecondaryConfig) configuration;
147:
148: /* Insert foreign key triggers at the front of the list and append
149: * secondary triggers at the end, so that ForeignKeyDeleteAction.ABORT
150: * is applied before deleting the secondary keys. */
151:
152: primaryDb.addTrigger(secondaryTrigger, false);
153:
154: Database foreignDb = secondaryConfig.getForeignKeyDatabase();
155: if (foreignDb != null) {
156: foreignDb.addTrigger(foreignKeyTrigger, true);
157: }
158:
159: /* Populate secondary if requested and secondary is empty. */
160: if (secondaryConfig.getAllowPopulate()) {
161: Cursor secCursor = null;
162: Cursor priCursor = null;
163: try {
164: secCursor = new Cursor(this , locker, null);
165: DatabaseEntry key = new DatabaseEntry();
166: DatabaseEntry data = new DatabaseEntry();
167: OperationStatus status = secCursor.position(key, data,
168: LockMode.DEFAULT, true);
169: if (status == OperationStatus.NOTFOUND) {
170: /* Is empty, so populate */
171: priCursor = new Cursor(primaryDb, locker, null);
172: status = priCursor.position(key, data,
173: LockMode.DEFAULT, true);
174: while (status == OperationStatus.SUCCESS) {
175: updateSecondary(locker, secCursor, key, null,
176: data);
177: status = priCursor.retrieveNext(key, data,
178: LockMode.DEFAULT, GetMode.NEXT);
179: }
180: }
181: } finally {
182: if (secCursor != null) {
183: secCursor.close();
184: }
185: if (priCursor != null) {
186: priCursor.close();
187: }
188: }
189: }
190: }
191:
192: /**
193: * Javadoc for this public method is generated via
194: * the doc templates in the doc_src directory.
195: */
196: public synchronized void close() throws DatabaseException {
197:
198: if (primaryDb != null && secondaryTrigger != null) {
199: primaryDb.removeTrigger(secondaryTrigger);
200: }
201: Database foreignDb = secondaryConfig.getForeignKeyDatabase();
202: if (foreignDb != null && foreignKeyTrigger != null) {
203: foreignDb.removeTrigger(foreignKeyTrigger);
204: }
205: super .close();
206: }
207:
208: /**
209: * Should be called by the secondaryTrigger while holding a write lock on
210: * the trigger list.
211: */
212: void clearPrimary() {
213: primaryDb = null;
214: secondaryTrigger = null;
215: }
216:
217: /**
218: * Should be called by the foreignKeyTrigger while holding a write lock on
219: * the trigger list.
220: */
221: void clearForeignKeyTrigger() {
222: foreignKeyTrigger = null;
223: }
224:
225: /**
226: * Javadoc for this public method is generated via
227: * the doc templates in the doc_src directory.
228: */
229: public Database getPrimaryDatabase() throws DatabaseException {
230:
231: return primaryDb;
232: }
233:
234: /**
235: * Javadoc for this public method is generated via
236: * the doc templates in the doc_src directory.
237: */
238: public SecondaryConfig getSecondaryConfig()
239: throws DatabaseException {
240:
241: return (SecondaryConfig) getConfig();
242: }
243:
244: /**
245: * Returns the secondary config without cloning, for internal use.
246: */
247: public SecondaryConfig getPrivateSecondaryConfig() {
248: return secondaryConfig;
249: }
250:
251: /**
252: * Javadoc for this public method is generated via
253: * the doc templates in the doc_src directory.
254: */
255: public SecondaryCursor openSecondaryCursor(Transaction txn,
256: CursorConfig cursorConfig) throws DatabaseException {
257:
258: return (SecondaryCursor) openCursor(txn, cursorConfig);
259: }
260:
261: /**
262: * Overrides Database method.
263: */
264: Cursor newDbcInstance(Transaction txn, CursorConfig cursorConfig)
265: throws DatabaseException {
266:
267: return new SecondaryCursor(this , txn, cursorConfig);
268: }
269:
270: /**
271: * Javadoc for this public method is generated via
272: * the doc templates in the doc_src directory.
273: */
274: public OperationStatus delete(Transaction txn, DatabaseEntry key)
275: throws DatabaseException {
276:
277: checkEnv();
278: DatabaseUtil.checkForNullDbt(key, "key", true);
279: checkRequiredDbState(OPEN,
280: "Can't call SecondaryDatabase.delete:");
281: trace(Level.FINEST, "SecondaryDatabase.delete", txn, key, null,
282: null);
283:
284: Locker locker = null;
285: Cursor cursor = null;
286:
287: OperationStatus commitStatus = OperationStatus.NOTFOUND;
288: try {
289: locker = LockerFactory.getWritableLocker(envHandle, txn,
290: isTransactional());
291:
292: /* Read the primary key (the data of a secondary). */
293: cursor = new Cursor(this , locker, null);
294: DatabaseEntry pKey = new DatabaseEntry();
295: OperationStatus searchStatus = cursor.search(key, pKey,
296: LockMode.RMW, SearchMode.SET);
297:
298: /*
299: * For each duplicate secondary key, delete the primary record and
300: * all its associated secondary records, including the one
301: * referenced by this secondary cursor.
302: */
303: while (searchStatus == OperationStatus.SUCCESS) {
304: commitStatus = primaryDb.deleteInternal(locker, pKey,
305: null);
306: if (commitStatus != OperationStatus.SUCCESS) {
307: throw secondaryCorruptException();
308: }
309: searchStatus = cursor.retrieveNext(key, pKey,
310: LockMode.RMW, GetMode.NEXT_DUP);
311: }
312: return commitStatus;
313: } catch (Error E) {
314: DbInternal.envGetEnvironmentImpl(envHandle).invalidate(E);
315: throw E;
316: } finally {
317: if (cursor != null) {
318: cursor.close();
319: }
320: if (locker != null) {
321: locker.operationEnd(commitStatus);
322: }
323: }
324: }
325:
326: /**
327: * Javadoc for this public method is generated via
328: * the doc templates in the doc_src directory.
329: */
330: public OperationStatus get(Transaction txn, DatabaseEntry key,
331: DatabaseEntry data, LockMode lockMode)
332: throws DatabaseException {
333:
334: return get(txn, key, new DatabaseEntry(), data, lockMode);
335: }
336:
337: /**
338: * Javadoc for this public method is generated via
339: * the doc templates in the doc_src directory.
340: */
341: public OperationStatus get(Transaction txn, DatabaseEntry key,
342: DatabaseEntry pKey, DatabaseEntry data, LockMode lockMode)
343: throws DatabaseException {
344:
345: checkEnv();
346: DatabaseUtil.checkForNullDbt(key, "key", true);
347: DatabaseUtil.checkForNullDbt(pKey, "pKey", false);
348: DatabaseUtil.checkForNullDbt(data, "data", false);
349: checkRequiredDbState(OPEN, "Can't call SecondaryDatabase.get:");
350: trace(Level.FINEST, "SecondaryDatabase.get", txn, key, null,
351: lockMode);
352:
353: CursorConfig cursorConfig = CursorConfig.DEFAULT;
354: if (lockMode == LockMode.READ_COMMITTED) {
355: cursorConfig = CursorConfig.READ_COMMITTED;
356: lockMode = null;
357: }
358:
359: SecondaryCursor cursor = null;
360: try {
361: cursor = new SecondaryCursor(this , txn, cursorConfig);
362: return cursor.search(key, pKey, data, lockMode,
363: SearchMode.SET);
364: } catch (Error E) {
365: DbInternal.envGetEnvironmentImpl(envHandle).invalidate(E);
366: throw E;
367: } finally {
368: if (cursor != null) {
369: cursor.close();
370: }
371: }
372: }
373:
374: /**
375: * Javadoc for this public method is generated via
376: * the doc templates in the doc_src directory.
377: */
378: public OperationStatus getSearchBoth(Transaction txn,
379: DatabaseEntry key, DatabaseEntry data, LockMode lockMode)
380: throws DatabaseException {
381:
382: throw notAllowedException();
383: }
384:
385: /**
386: * Javadoc for this public method is generated via
387: * the doc templates in the doc_src directory.
388: */
389: public OperationStatus getSearchBoth(Transaction txn,
390: DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data,
391: LockMode lockMode) throws DatabaseException {
392:
393: checkEnv();
394: DatabaseUtil.checkForNullDbt(key, "key", true);
395: DatabaseUtil.checkForNullDbt(pKey, "pKey", true);
396: DatabaseUtil.checkForNullDbt(data, "data", false);
397: checkRequiredDbState(OPEN,
398: "Can't call SecondaryDatabase.getSearchBoth:");
399: trace(Level.FINEST, "SecondaryDatabase.getSearchBoth", txn,
400: key, data, lockMode);
401:
402: CursorConfig cursorConfig = CursorConfig.DEFAULT;
403: if (lockMode == LockMode.READ_COMMITTED) {
404: cursorConfig = CursorConfig.READ_COMMITTED;
405: lockMode = null;
406: }
407:
408: SecondaryCursor cursor = null;
409: try {
410: cursor = new SecondaryCursor(this , txn, cursorConfig);
411: return cursor.search(key, pKey, data, lockMode,
412: SearchMode.BOTH);
413: } catch (Error E) {
414: DbInternal.envGetEnvironmentImpl(envHandle).invalidate(E);
415: throw E;
416: } finally {
417: if (cursor != null) {
418: cursor.close();
419: }
420: }
421: }
422:
423: /**
424: * Javadoc for this public method is generated via
425: * the doc templates in the doc_src directory.
426: */
427: public OperationStatus put(Transaction txn, DatabaseEntry key,
428: DatabaseEntry data) throws DatabaseException {
429:
430: throw notAllowedException();
431: }
432:
433: /**
434: * Javadoc for this public method is generated via
435: * the doc templates in the doc_src directory.
436: */
437: public OperationStatus putNoOverwrite(Transaction txn,
438: DatabaseEntry key, DatabaseEntry data)
439: throws DatabaseException {
440:
441: throw notAllowedException();
442: }
443:
444: /**
445: * Javadoc for this public method is generated via
446: * the doc templates in the doc_src directory.
447: */
448: public OperationStatus putNoDupData(Transaction txn,
449: DatabaseEntry key, DatabaseEntry data)
450: throws DatabaseException {
451:
452: throw notAllowedException();
453: }
454:
455: /**
456: * Javadoc for this public method is generated via
457: * the doc templates in the doc_src directory.
458: */
459: public JoinCursor join(Cursor[] cursors, JoinConfig config)
460: throws DatabaseException {
461:
462: throw notAllowedException();
463: }
464:
465: /**
466: * Javadoc for this public method is generated via
467: * the doc templates in the doc_src directory.
468: * @deprecated
469: */
470: public int truncate(Transaction txn, boolean countRecords)
471: throws DatabaseException {
472:
473: throw notAllowedException();
474: }
475:
476: /**
477: * Updates a single secondary when a put() or delete() is performed on
478: * the primary.
479: *
480: * @param locker the internal locker.
481: *
482: * @param cursor secondary cursor to use, or null if this method should
483: * open and close a cursor if one is needed.
484: *
485: * @param priKey the primary key.
486: *
487: * @param oldData the primary data before the change, or null if the record
488: * did not previously exist.
489: *
490: * @param newData the primary data after the change, or null if the record
491: * has been deleted.
492: */
493: void updateSecondary(Locker locker, Cursor cursor,
494: DatabaseEntry priKey, DatabaseEntry oldData,
495: DatabaseEntry newData) throws DatabaseException {
496:
497: /*
498: * If we're updating the primary and the secondary key cannot be
499: * changed, optimize for that case by doing nothing.
500: */
501: if (secondaryConfig.getImmutableSecondaryKey()
502: && oldData != null && newData != null) {
503: return;
504: }
505:
506: SecondaryKeyCreator keyCreator = secondaryConfig
507: .getKeyCreator();
508: if (keyCreator != null) {
509: /* Each primary record may have a single secondary key. */
510: assert secondaryConfig.getMultiKeyCreator() == null;
511:
512: /* Get old and new secondary keys. */
513: DatabaseEntry oldSecKey = null;
514: if (oldData != null) {
515: oldSecKey = new DatabaseEntry();
516: if (!keyCreator.createSecondaryKey(this , priKey,
517: oldData, oldSecKey)) {
518: oldSecKey = null;
519: }
520: }
521: DatabaseEntry newSecKey = null;
522: if (newData != null) {
523: newSecKey = new DatabaseEntry();
524: if (!keyCreator.createSecondaryKey(this , priKey,
525: newData, newSecKey)) {
526: newSecKey = null;
527: }
528: }
529:
530: /* Update secondary if old and new keys are unequal. */
531: if ((oldSecKey != null && !oldSecKey.equals(newSecKey))
532: || (newSecKey != null && !newSecKey
533: .equals(oldSecKey))) {
534:
535: boolean localCursor = (cursor == null);
536: if (localCursor) {
537: cursor = new Cursor(this , locker, null);
538: }
539: try {
540: /* Delete the old key. */
541: if (oldSecKey != null) {
542: deleteKey(cursor, priKey, oldSecKey);
543: }
544: /* Insert the new key. */
545: if (newSecKey != null) {
546: insertKey(locker, cursor, priKey, newSecKey);
547: }
548: } finally {
549: if (localCursor && cursor != null) {
550: cursor.close();
551: }
552: }
553: }
554: } else {
555: /* Each primary record may have multiple secondary keys. */
556: SecondaryMultiKeyCreator multiKeyCreator = secondaryConfig
557: .getMultiKeyCreator();
558: assert multiKeyCreator != null;
559:
560: /* Get old and new secondary keys. */
561: Set oldKeys = Collections.EMPTY_SET;
562: Set newKeys = Collections.EMPTY_SET;
563: if (oldData != null) {
564: oldKeys = new HashSet();
565: multiKeyCreator.createSecondaryKeys(this , priKey,
566: oldData, oldKeys);
567: }
568: if (newData != null) {
569: newKeys = new HashSet();
570: multiKeyCreator.createSecondaryKeys(this , priKey,
571: newData, newKeys);
572: }
573:
574: /* Update the secondary if there is a difference. */
575: if (!oldKeys.equals(newKeys)) {
576:
577: boolean localCursor = (cursor == null);
578: if (localCursor) {
579: cursor = new Cursor(this , locker, null);
580: }
581: try {
582: /* Delete old keys that are no longer present. */
583: Set oldKeysCopy = oldKeys;
584: if (oldKeys != Collections.EMPTY_SET) {
585: oldKeysCopy = new HashSet(oldKeys);
586: oldKeys.removeAll(newKeys);
587: for (Iterator i = oldKeys.iterator(); i
588: .hasNext();) {
589: DatabaseEntry oldKey = (DatabaseEntry) i
590: .next();
591: deleteKey(cursor, priKey, oldKey);
592: }
593: }
594: /* Insert new keys that were not present before. */
595: if (newKeys != Collections.EMPTY_SET) {
596: newKeys.removeAll(oldKeysCopy);
597: for (Iterator i = newKeys.iterator(); i
598: .hasNext();) {
599: DatabaseEntry newKey = (DatabaseEntry) i
600: .next();
601: insertKey(locker, cursor, priKey, newKey);
602: }
603: }
604: } finally {
605: if (localCursor && cursor != null) {
606: cursor.close();
607: }
608: }
609: }
610: }
611: }
612:
613: /**
614: * Deletes an old secondary key.
615: */
616: private void deleteKey(Cursor cursor, DatabaseEntry priKey,
617: DatabaseEntry oldSecKey) throws DatabaseException {
618:
619: OperationStatus status = cursor.search(oldSecKey, priKey,
620: LockMode.RMW, SearchMode.BOTH);
621: if (status == OperationStatus.SUCCESS) {
622: cursor.deleteInternal();
623: } else {
624: throw new DatabaseException("Secondary " + getDebugName()
625: + " is corrupt: the primary record contains a key"
626: + " that is not present in the secondary");
627: }
628: }
629:
630: /**
631: * Inserts a new secondary key.
632: */
633: private void insertKey(Locker locker, Cursor cursor,
634: DatabaseEntry priKey, DatabaseEntry newSecKey)
635: throws DatabaseException {
636:
637: /* Check for the existence of a foreign key. */
638: Database foreignDb = secondaryConfig.getForeignKeyDatabase();
639: if (foreignDb != null) {
640: Cursor foreignCursor = null;
641: try {
642: foreignCursor = new Cursor(foreignDb, locker, null);
643: DatabaseEntry tmpData = new DatabaseEntry();
644: OperationStatus status = foreignCursor.search(
645: newSecKey, tmpData, LockMode.DEFAULT,
646: SearchMode.SET);
647: if (status != OperationStatus.SUCCESS) {
648: throw new DatabaseException("Secondary "
649: + getDebugName()
650: + " foreign key not allowed: it is not"
651: + " present in the foreign database "
652: + foreignDb.getDebugName());
653: }
654: } finally {
655: if (foreignCursor != null) {
656: foreignCursor.close();
657: }
658: }
659: }
660:
661: /* Insert the new key. */
662: OperationStatus status;
663: if (configuration.getSortedDuplicates()) {
664: status = cursor.putInternal(newSecKey, priKey,
665: PutMode.NODUP);
666: } else {
667: status = cursor.putInternal(newSecKey, priKey,
668: PutMode.NOOVERWRITE);
669: }
670: if (status != OperationStatus.SUCCESS) {
671: throw new DatabaseException(
672: "Could not insert secondary key in "
673: + getDebugName() + ' ' + status);
674: }
675: }
676:
677: /**
678: * Called by the ForeignKeyTrigger when a record in the foreign database is
679: * deleted.
680: *
681: * @param secKey is the primary key of the foreign database, which is the
682: * secondary key (ordinary key) of this secondary database.
683: */
684: void onForeignKeyDelete(Locker locker, DatabaseEntry secKey)
685: throws DatabaseException {
686:
687: ForeignKeyDeleteAction deleteAction = secondaryConfig
688: .getForeignKeyDeleteAction();
689:
690: /* Use RMW if we're going to be deleting the secondary records. */
691: LockMode lockMode = (deleteAction == ForeignKeyDeleteAction.ABORT) ? LockMode.DEFAULT
692: : LockMode.RMW;
693:
694: /*
695: * Use the deleted foreign primary key to read the data of this
696: * database, which is the associated primary's key.
697: */
698: DatabaseEntry priKey = new DatabaseEntry();
699: Cursor cursor = null;
700: OperationStatus status;
701: try {
702: cursor = new Cursor(this , locker, null);
703: status = cursor.search(secKey, priKey, lockMode,
704: SearchMode.SET);
705: while (status == OperationStatus.SUCCESS) {
706:
707: if (deleteAction == ForeignKeyDeleteAction.ABORT) {
708:
709: /*
710: * ABORT - throw an exception to cause the user to abort
711: * the transaction.
712: */
713: throw new DatabaseException(
714: "Secondary "
715: + getDebugName()
716: + " refers to a foreign key that has been deleted"
717: + " (ForeignKeyDeleteAction.ABORT)");
718:
719: } else if (deleteAction == ForeignKeyDeleteAction.CASCADE) {
720:
721: /*
722: * CASCADE - delete the associated primary record.
723: */
724: Cursor priCursor = null;
725: try {
726: DatabaseEntry data = new DatabaseEntry();
727: priCursor = new Cursor(primaryDb, locker, null);
728: status = priCursor.search(priKey, data,
729: LockMode.RMW, SearchMode.SET);
730: if (status == OperationStatus.SUCCESS) {
731: priCursor.delete();
732: } else {
733: throw secondaryCorruptException();
734: }
735: } finally {
736: if (priCursor != null) {
737: priCursor.close();
738: }
739: }
740:
741: } else if (deleteAction == ForeignKeyDeleteAction.NULLIFY) {
742:
743: /*
744: * NULLIFY - set the secondary key to null in the
745: * associated primary record.
746: */
747: Cursor priCursor = null;
748: try {
749: DatabaseEntry data = new DatabaseEntry();
750: priCursor = new Cursor(primaryDb, locker, null);
751: status = priCursor.search(priKey, data,
752: LockMode.RMW, SearchMode.SET);
753: if (status == OperationStatus.SUCCESS) {
754: ForeignMultiKeyNullifier multiNullifier = secondaryConfig
755: .getForeignMultiKeyNullifier();
756: if (multiNullifier != null) {
757: if (multiNullifier.nullifyForeignKey(
758: this , priKey, data, secKey)) {
759: priCursor.putCurrent(data);
760: }
761: } else {
762: ForeignKeyNullifier nullifier = secondaryConfig
763: .getForeignKeyNullifier();
764: if (nullifier.nullifyForeignKey(this ,
765: data)) {
766: priCursor.putCurrent(data);
767: }
768: }
769: } else {
770: throw secondaryCorruptException();
771: }
772: } finally {
773: if (priCursor != null) {
774: priCursor.close();
775: }
776: }
777: } else {
778: /* Should never occur. */
779: throw new IllegalStateException();
780: }
781:
782: status = cursor.retrieveNext(secKey, priKey,
783: LockMode.DEFAULT, GetMode.NEXT_DUP);
784: }
785: } finally {
786: if (cursor != null) {
787: cursor.close();
788: }
789: }
790: }
791:
792: DatabaseException secondaryCorruptException()
793: throws DatabaseException {
794:
795: throw new DatabaseException("Secondary " + getDebugName()
796: + " is corrupt: it refers"
797: + " to a missing key in the primary database");
798: }
799:
800: static UnsupportedOperationException notAllowedException() {
801:
802: throw new UnsupportedOperationException(
803: "Operation not allowed on a secondary");
804: }
805:
806: /**
807: * Send trace messages to the java.util.logger. Don't rely on the
808: * logger alone to conditionalize whether we send this message,
809: * we don't even want to construct the message if the level is
810: * not enabled.
811: */
812: void trace(Level level, String methodName) throws DatabaseException {
813:
814: Logger logger = envHandle.getEnvironmentImpl().getLogger();
815: if (logger.isLoggable(level)) {
816: StringBuffer sb = new StringBuffer();
817: sb.append(methodName);
818: sb.append(" name=").append(getDebugName());
819: sb.append(" primary=").append(primaryDb.getDebugName());
820:
821: logger.log(level, sb.toString());
822: }
823: }
824: }
|