1 8 9 package com.sleepycat.je; 10 11 import java.util.Collections ; 12 import java.util.HashSet ; 13 import java.util.Iterator ; 14 import java.util.Set ; 15 import java.util.logging.Level ; 16 import java.util.logging.Logger ; 17 18 import com.sleepycat.je.dbi.DatabaseImpl; 19 import com.sleepycat.je.dbi.GetMode; 20 import com.sleepycat.je.dbi.PutMode; 21 import com.sleepycat.je.dbi.CursorImpl.SearchMode; 22 import com.sleepycat.je.txn.Locker; 23 import com.sleepycat.je.txn.LockerFactory; 24 import com.sleepycat.je.utilint.DatabaseUtil; 25 26 30 public class SecondaryDatabase extends Database { 31 32 private Database primaryDb; 33 private SecondaryConfig secondaryConfig; 34 private SecondaryTrigger secondaryTrigger; 35 private ForeignKeyTrigger foreignKeyTrigger; 36 37 40 SecondaryDatabase(Environment env, 41 SecondaryConfig secConfig, 42 Database primaryDatabase) 43 throws DatabaseException { 44 45 super(env); 46 DatabaseUtil.checkForNullParam(primaryDatabase, "primaryDatabase"); 47 primaryDatabase.checkRequiredDbState(OPEN, "Can't use as primary:"); 48 if (primaryDatabase.configuration.getSortedDuplicates()) { 49 throw new IllegalArgumentException 50 ("Duplicates must not be allowed for a primary database: " + 51 primaryDatabase.getDebugName()); 52 } 53 if (env.getEnvironmentImpl() != 54 primaryDatabase.getEnvironment().getEnvironmentImpl()) { 55 throw new IllegalArgumentException 56 ("Primary and secondary databases must be in the same" + 57 " environment"); 58 } 59 if (secConfig.getKeyCreator() != null && 60 secConfig.getMultiKeyCreator() != null) { 61 throw new IllegalArgumentException 62 ("secConfig.getKeyCreator() and getMultiKeyCreator() may not" + 63 " both be non-null"); 64 } 65 if (!primaryDatabase.configuration.getReadOnly() && 66 secConfig.getKeyCreator() == null && 67 secConfig.getMultiKeyCreator() == null) { 68 throw new NullPointerException 69 ("secConfig and getKeyCreator()/getMultiKeyCreator()" + 70 " may be null only if the primary database is read-only"); 71 } 72 if (secConfig.getForeignKeyNullifier() != null && 73 secConfig.getForeignMultiKeyNullifier() != null) { 74 throw new IllegalArgumentException 75 ("secConfig.getForeignKeyNullifier() and" + 76 " getForeignMultiKeyNullifier() may not both be non-null"); 77 } 78 if (secConfig.getForeignKeyDeleteAction() == 79 ForeignKeyDeleteAction.NULLIFY && 80 secConfig.getForeignKeyNullifier() == null && 81 secConfig.getForeignMultiKeyNullifier() == null) { 82 throw new NullPointerException 83 ("ForeignKeyNullifier or ForeignMultiKeyNullifier must be" + 84 " non-null when ForeignKeyDeleteAction is NULLIFY"); 85 } 86 if (secConfig.getForeignKeyNullifier() != null && 87 secConfig.getMultiKeyCreator() != null) { 88 throw new IllegalArgumentException 89 ("ForeignKeyNullifier may not be used with" + 90 " SecondaryMultiKeyCreator -- use" + 91 " ForeignMultiKeyNullifier instead"); 92 } 93 if (secConfig.getForeignKeyDatabase() != null) { 94 Database foreignDb = secConfig.getForeignKeyDatabase(); 95 if (foreignDb.getDatabaseImpl().getSortedDuplicates()) { 96 throw new IllegalArgumentException 97 ("Duplicates must not be allowed for a foreign key " + 98 " database: " + foreignDb.getDebugName()); 99 } 100 } 101 primaryDb = primaryDatabase; 102 secondaryTrigger = new SecondaryTrigger(this); 103 if (secConfig.getForeignKeyDatabase() != null) { 104 foreignKeyTrigger = new ForeignKeyTrigger(this); 105 } 106 } 107 108 111 void initNew(Environment env, 112 Locker locker, 113 String databaseName, 114 DatabaseConfig dbConfig) 115 throws DatabaseException { 116 117 super.initNew(env, locker, databaseName, dbConfig); 118 init(locker); 119 } 120 121 124 void initExisting(Environment env, 125 Locker locker, 126 DatabaseImpl database, 127 DatabaseConfig dbConfig) 128 throws DatabaseException { 129 130 131 Database otherPriDb = database.findPrimaryDatabase(); 132 if (otherPriDb != null && 133 otherPriDb.getDatabaseImpl() != primaryDb.getDatabaseImpl()) { 134 throw new IllegalArgumentException 135 ("Secondary is already associated with a different primary: " + 136 otherPriDb.getDebugName()); 137 } 138 139 super.initExisting(env, locker, database, dbConfig); 140 init(locker); 141 } 142 143 146 private void init(Locker locker) 147 throws DatabaseException { 148 149 trace(Level.FINEST, "SecondaryDatabase open"); 150 151 secondaryConfig = (SecondaryConfig) configuration; 152 153 156 157 primaryDb.addTrigger(secondaryTrigger, false); 158 159 Database foreignDb = secondaryConfig.getForeignKeyDatabase(); 160 if (foreignDb != null) { 161 foreignDb.addTrigger(foreignKeyTrigger, true); 162 } 163 164 165 if (secondaryConfig.getAllowPopulate()) { 166 Cursor secCursor = null; 167 Cursor priCursor = null; 168 try { 169 secCursor = new Cursor(this, locker, null); 170 DatabaseEntry key = new DatabaseEntry(); 171 DatabaseEntry data = new DatabaseEntry(); 172 OperationStatus status = secCursor.position(key, data, 173 LockMode.DEFAULT, 174 true); 175 if (status == OperationStatus.NOTFOUND) { 176 177 priCursor = new Cursor(primaryDb, locker, null); 178 status = priCursor.position(key, data, LockMode.DEFAULT, 179 true); 180 while (status == OperationStatus.SUCCESS) { 181 updateSecondary(locker, secCursor, key, null, data); 182 status = priCursor.retrieveNext(key, data, 183 LockMode.DEFAULT, 184 GetMode.NEXT); 185 } 186 } 187 } finally { 188 if (secCursor != null) { 189 secCursor.close(); 190 } 191 if (priCursor != null) { 192 priCursor.close(); 193 } 194 } 195 } 196 } 197 198 202 public synchronized void close() 203 throws DatabaseException { 204 205 if (primaryDb != null && secondaryTrigger != null) { 206 primaryDb.removeTrigger(secondaryTrigger); 207 } 208 Database foreignDb = secondaryConfig.getForeignKeyDatabase(); 209 if (foreignDb != null && foreignKeyTrigger != null) { 210 foreignDb.removeTrigger(foreignKeyTrigger); 211 } 212 super.close(); 213 } 214 215 219 void clearPrimary() { 220 primaryDb = null; 221 secondaryTrigger = null; 222 } 223 224 228 void clearForeignKeyTrigger() { 229 foreignKeyTrigger = null; 230 } 231 232 236 public Database getPrimaryDatabase() 237 throws DatabaseException { 238 239 return primaryDb; 240 } 241 242 246 public SecondaryConfig getSecondaryConfig() 247 throws DatabaseException { 248 249 return (SecondaryConfig) getConfig(); 250 } 251 252 255 public SecondaryConfig getPrivateSecondaryConfig() { 256 return secondaryConfig; 257 } 258 259 263 public SecondaryCursor openSecondaryCursor(Transaction txn, 264 CursorConfig cursorConfig) 265 throws DatabaseException { 266 267 return (SecondaryCursor) openCursor(txn, cursorConfig); 268 } 269 270 273 Cursor newDbcInstance(Transaction txn, 274 CursorConfig cursorConfig) 275 throws DatabaseException { 276 277 return new SecondaryCursor(this, txn, cursorConfig); 278 } 279 280 284 public OperationStatus delete(Transaction txn, 285 DatabaseEntry key) 286 throws DatabaseException { 287 288 checkEnv(); 289 DatabaseUtil.checkForNullDbt(key, "key", true); 290 checkRequiredDbState(OPEN, "Can't call SecondaryDatabase.delete:"); 291 trace(Level.FINEST, "SecondaryDatabase.delete", txn, 292 key, null, null); 293 294 Locker locker = null; 295 Cursor cursor = null; 296 297 OperationStatus commitStatus = OperationStatus.NOTFOUND; 298 try { 299 locker = LockerFactory.getWritableLocker 300 (envHandle, txn, isTransactional()); 301 302 303 cursor = new Cursor(this, locker, null); 304 DatabaseEntry pKey = new DatabaseEntry(); 305 OperationStatus searchStatus = 306 cursor.search(key, pKey, LockMode.RMW, SearchMode.SET); 307 308 313 while (searchStatus == OperationStatus.SUCCESS) { 314 commitStatus = primaryDb.deleteInternal(locker, pKey, null); 315 if (commitStatus != OperationStatus.SUCCESS) { 316 throw secondaryCorruptException(); 317 } 318 searchStatus = cursor.retrieveNext 319 (key, pKey, LockMode.RMW, GetMode.NEXT_DUP); 320 } 321 return commitStatus; 322 } catch (Error E) { 323 DbInternal.envGetEnvironmentImpl(envHandle).invalidate(E); 324 throw E; 325 } finally { 326 if (cursor != null) { 327 cursor.close(); 328 } 329 if (locker != null) { 330 locker.operationEnd(commitStatus); 331 } 332 } 333 } 334 335 339 public OperationStatus get(Transaction txn, 340 DatabaseEntry key, 341 DatabaseEntry data, 342 LockMode lockMode) 343 throws DatabaseException { 344 345 return get(txn, key, new DatabaseEntry(), data, lockMode); 346 } 347 348 352 public OperationStatus get(Transaction txn, 353 DatabaseEntry key, 354 DatabaseEntry pKey, 355 DatabaseEntry data, 356 LockMode lockMode) 357 throws DatabaseException { 358 359 checkEnv(); 360 DatabaseUtil.checkForNullDbt(key, "key", true); 361 DatabaseUtil.checkForNullDbt(pKey, "pKey", false); 362 DatabaseUtil.checkForNullDbt(data, "data", false); 363 checkRequiredDbState(OPEN, "Can't call SecondaryDatabase.get:"); 364 trace(Level.FINEST, "SecondaryDatabase.get", txn, key, null, lockMode); 365 366 CursorConfig cursorConfig = CursorConfig.DEFAULT; 367 if (lockMode == LockMode.READ_COMMITTED) { 368 cursorConfig = CursorConfig.READ_COMMITTED; 369 lockMode = null; 370 } 371 372 SecondaryCursor cursor = null; 373 try { 374 cursor = new SecondaryCursor(this, txn, cursorConfig); 375 return cursor.search(key, pKey, data, lockMode, SearchMode.SET); 376 } catch (Error E) { 377 DbInternal.envGetEnvironmentImpl(envHandle).invalidate(E); 378 throw E; 379 } finally { 380 if (cursor != null) { 381 cursor.close(); 382 } 383 } 384 } 385 386 390 public OperationStatus getSearchBoth(Transaction txn, 391 DatabaseEntry key, 392 DatabaseEntry data, 393 LockMode lockMode) 394 throws DatabaseException { 395 396 throw notAllowedException(); 397 } 398 399 403 public OperationStatus getSearchBoth(Transaction txn, 404 DatabaseEntry key, 405 DatabaseEntry pKey, 406 DatabaseEntry data, 407 LockMode lockMode) 408 throws DatabaseException { 409 410 checkEnv(); 411 DatabaseUtil.checkForNullDbt(key, "key", true); 412 DatabaseUtil.checkForNullDbt(pKey, "pKey", true); 413 DatabaseUtil.checkForNullDbt(data, "data", false); 414 checkRequiredDbState(OPEN, 415 "Can't call SecondaryDatabase.getSearchBoth:"); 416 trace(Level.FINEST, "SecondaryDatabase.getSearchBoth", txn, key, data, 417 lockMode); 418 419 CursorConfig cursorConfig = CursorConfig.DEFAULT; 420 if (lockMode == LockMode.READ_COMMITTED) { 421 cursorConfig = CursorConfig.READ_COMMITTED; 422 lockMode = null; 423 } 424 425 SecondaryCursor cursor = null; 426 try { 427 cursor = new SecondaryCursor(this, txn, cursorConfig); 428 return cursor.search(key, pKey, data, lockMode, SearchMode.BOTH); 429 } catch (Error E) { 430 DbInternal.envGetEnvironmentImpl(envHandle).invalidate(E); 431 throw E; 432 } finally { 433 if (cursor != null) { 434 cursor.close(); 435 } 436 } 437 } 438 439 443 public OperationStatus put(Transaction txn, 444 DatabaseEntry key, 445 DatabaseEntry data) 446 throws DatabaseException { 447 448 throw notAllowedException(); 449 } 450 451 455 public OperationStatus putNoOverwrite(Transaction txn, 456 DatabaseEntry key, 457 DatabaseEntry data) 458 throws DatabaseException { 459 460 throw notAllowedException(); 461 } 462 463 467 public OperationStatus putNoDupData(Transaction txn, 468 DatabaseEntry key, 469 DatabaseEntry data) 470 throws DatabaseException { 471 472 throw notAllowedException(); 473 } 474 475 479 public JoinCursor join(Cursor[] cursors, JoinConfig config) 480 throws DatabaseException { 481 482 throw notAllowedException(); 483 } 484 485 490 public int truncate(Transaction txn, boolean countRecords) 491 throws DatabaseException { 492 493 throw notAllowedException(); 494 } 495 496 513 void updateSecondary(Locker locker, 514 Cursor cursor, 515 DatabaseEntry priKey, 516 DatabaseEntry oldData, 517 DatabaseEntry newData) 518 throws DatabaseException { 519 520 524 if (secondaryConfig.getImmutableSecondaryKey() && 525 oldData != null && newData != null) { 526 return; 527 } 528 529 SecondaryKeyCreator keyCreator = secondaryConfig.getKeyCreator(); 530 if (keyCreator != null) { 531 532 assert secondaryConfig.getMultiKeyCreator() == null; 533 534 535 DatabaseEntry oldSecKey = null; 536 if (oldData != null) { 537 oldSecKey = new DatabaseEntry(); 538 if (!keyCreator.createSecondaryKey(this, priKey, oldData, 539 oldSecKey)) { 540 oldSecKey = null; 541 } 542 } 543 DatabaseEntry newSecKey = null; 544 if (newData != null) { 545 newSecKey = new DatabaseEntry(); 546 if (!keyCreator.createSecondaryKey(this, priKey, newData, 547 newSecKey)) { 548 newSecKey = null; 549 } 550 } 551 552 553 if ((oldSecKey != null && !oldSecKey.equals(newSecKey)) || 554 (newSecKey != null && !newSecKey.equals(oldSecKey))) { 555 556 boolean localCursor = (cursor == null); 557 if (localCursor) { 558 cursor = new Cursor(this, locker, null); 559 } 560 try { 561 562 if (oldSecKey != null) { 563 deleteKey(cursor, priKey, oldSecKey); 564 } 565 566 if (newSecKey != null) { 567 insertKey(locker, cursor, priKey, newSecKey); 568 } 569 } finally { 570 if (localCursor && cursor != null) { 571 cursor.close(); 572 } 573 } 574 } 575 } else { 576 577 SecondaryMultiKeyCreator multiKeyCreator = 578 secondaryConfig.getMultiKeyCreator(); 579 assert multiKeyCreator != null; 580 581 582 Set oldKeys = Collections.EMPTY_SET; 583 Set newKeys = Collections.EMPTY_SET; 584 if (oldData != null) { 585 oldKeys = new HashSet (); 586 multiKeyCreator.createSecondaryKeys(this, priKey, 587 oldData, oldKeys); 588 } 589 if (newData != null) { 590 newKeys = new HashSet (); 591 multiKeyCreator.createSecondaryKeys(this, priKey, 592 newData, newKeys); 593 } 594 595 596 if (!oldKeys.equals(newKeys)) { 597 598 boolean localCursor = (cursor == null); 599 if (localCursor) { 600 cursor = new Cursor(this, locker, null); 601 } 602 try { 603 604 Set oldKeysCopy = oldKeys; 605 if (oldKeys != Collections.EMPTY_SET) { 606 oldKeysCopy = new HashSet (oldKeys); 607 oldKeys.removeAll(newKeys); 608 for (Iterator i = oldKeys.iterator(); i.hasNext();) { 609 DatabaseEntry oldKey = (DatabaseEntry) i.next(); 610 deleteKey(cursor, priKey, oldKey); 611 } 612 } 613 614 if (newKeys != Collections.EMPTY_SET) { 615 newKeys.removeAll(oldKeysCopy); 616 for (Iterator i = newKeys.iterator(); i.hasNext();) { 617 DatabaseEntry newKey = (DatabaseEntry) i.next(); 618 insertKey(locker, cursor, priKey, newKey); 619 } 620 } 621 } finally { 622 if (localCursor && cursor != null) { 623 cursor.close(); 624 } 625 } 626 } 627 } 628 } 629 630 633 private void deleteKey(Cursor cursor, 634 DatabaseEntry priKey, 635 DatabaseEntry oldSecKey) 636 throws DatabaseException { 637 638 OperationStatus status = 639 cursor.search(oldSecKey, priKey, 640 LockMode.RMW, 641 SearchMode.BOTH); 642 if (status == OperationStatus.SUCCESS) { 643 cursor.deleteInternal(); 644 } else { 645 throw new DatabaseException 646 ("Secondary " + getDebugName() + 647 " is corrupt: the primary record contains a key" + 648 " that is not present in the secondary"); 649 } 650 } 651 652 655 private void insertKey(Locker locker, 656 Cursor cursor, 657 DatabaseEntry priKey, 658 DatabaseEntry newSecKey) 659 throws DatabaseException { 660 661 662 Database foreignDb = 663 secondaryConfig.getForeignKeyDatabase(); 664 if (foreignDb != null) { 665 Cursor foreignCursor = null; 666 try { 667 foreignCursor = new Cursor(foreignDb, locker, 668 null); 669 DatabaseEntry tmpData = new DatabaseEntry(); 670 OperationStatus status = 671 foreignCursor.search(newSecKey, tmpData, 672 LockMode.DEFAULT, 673 SearchMode.SET); 674 if (status != OperationStatus.SUCCESS) { 675 throw new DatabaseException 676 ("Secondary " + getDebugName() + 677 " foreign key not allowed: it is not" + 678 " present in the foreign database " + 679 foreignDb.getDebugName()); 680 } 681 } finally { 682 if (foreignCursor != null) { 683 foreignCursor.close(); 684 } 685 } 686 } 687 688 689 OperationStatus status; 690 if (configuration.getSortedDuplicates()) { 691 status = cursor.putInternal(newSecKey, priKey, 692 PutMode.NODUP); 693 } else { 694 status = cursor.putInternal(newSecKey, priKey, 695 PutMode.NOOVERWRITE); 696 } 697 if (status != OperationStatus.SUCCESS) { 698 throw new DatabaseException 699 ("Could not insert secondary key in " + 700 getDebugName() + ' ' + status); 701 } 702 } 703 704 711 void onForeignKeyDelete(Locker locker, DatabaseEntry secKey) 712 throws DatabaseException { 713 714 ForeignKeyDeleteAction deleteAction = 715 secondaryConfig.getForeignKeyDeleteAction(); 716 717 718 LockMode lockMode = (deleteAction == ForeignKeyDeleteAction.ABORT) 719 ? LockMode.DEFAULT : LockMode.RMW; 720 721 725 DatabaseEntry priKey = new DatabaseEntry(); 726 Cursor cursor = null; 727 OperationStatus status; 728 try { 729 cursor = new Cursor(this, locker, null); 730 status = cursor.search(secKey, priKey, lockMode, 731 SearchMode.SET); 732 while (status == OperationStatus.SUCCESS) { 733 734 if (deleteAction == ForeignKeyDeleteAction.ABORT) { 735 736 740 throw new DatabaseException 741 ("Secondary " + getDebugName() + 742 " refers to a foreign key that has been deleted" + 743 " (ForeignKeyDeleteAction.ABORT)"); 744 745 } else if (deleteAction == ForeignKeyDeleteAction.CASCADE) { 746 747 750 Cursor priCursor = null; 751 try { 752 DatabaseEntry data = new DatabaseEntry(); 753 priCursor = new Cursor(primaryDb, locker, null); 754 status = priCursor.search(priKey, data, LockMode.RMW, 755 SearchMode.SET); 756 if (status == OperationStatus.SUCCESS) { 757 priCursor.delete(); 758 } else { 759 throw secondaryCorruptException(); 760 } 761 } finally { 762 if (priCursor != null) { 763 priCursor.close(); 764 } 765 } 766 767 } else if (deleteAction == ForeignKeyDeleteAction.NULLIFY) { 768 769 773 Cursor priCursor = null; 774 try { 775 DatabaseEntry data = new DatabaseEntry(); 776 priCursor = new Cursor(primaryDb, locker, null); 777 status = priCursor.search(priKey, data, LockMode.RMW, 778 SearchMode.SET); 779 if (status == OperationStatus.SUCCESS) { 780 ForeignMultiKeyNullifier multiNullifier = 781 secondaryConfig.getForeignMultiKeyNullifier(); 782 if (multiNullifier != null) { 783 if (multiNullifier.nullifyForeignKey 784 (this, priKey, data, secKey)) { 785 priCursor.putCurrent(data); 786 } 787 } else { 788 ForeignKeyNullifier nullifier = 789 secondaryConfig.getForeignKeyNullifier(); 790 if (nullifier.nullifyForeignKey 791 (this, data)) { 792 priCursor.putCurrent(data); 793 } 794 } 795 } else { 796 throw secondaryCorruptException(); 797 } 798 } finally { 799 if (priCursor != null) { 800 priCursor.close(); 801 } 802 } 803 } else { 804 805 throw new IllegalStateException (); 806 } 807 808 status = cursor.retrieveNext(secKey, priKey, LockMode.DEFAULT, 809 GetMode.NEXT_DUP); 810 } 811 } finally { 812 if (cursor != null) { 813 cursor.close(); 814 } 815 } 816 } 817 818 DatabaseException secondaryCorruptException() 819 throws DatabaseException { 820 821 throw new DatabaseException 822 ("Secondary " + getDebugName() + " is corrupt: it refers" + 823 " to a missing key in the primary database"); 824 } 825 826 static UnsupportedOperationException notAllowedException() { 827 828 throw new UnsupportedOperationException 829 ("Operation not allowed on a secondary"); 830 } 831 832 838 void trace(Level level, 839 String methodName) 840 throws DatabaseException { 841 842 Logger logger = envHandle.getEnvironmentImpl().getLogger(); 843 if (logger.isLoggable(level)) { 844 StringBuffer sb = new StringBuffer (); 845 sb.append(methodName); 846 sb.append(" name=").append(getDebugName()); 847 sb.append(" primary=").append(primaryDb.getDebugName()); 848 849 logger.log(level, sb.toString()); 850 } 851 } 852 } 853 | Popular Tags |