1 /*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2000, 2013 Oracle and/or its affiliates. All rights reserved. 5 * 6 */ 7 8 package com.sleepycat.collections; 9 10 import com.sleepycat.compat.DbCompat; 11 import com.sleepycat.bind.EntityBinding; 12 import com.sleepycat.bind.EntryBinding; 13 import com.sleepycat.db.CursorConfig; 14 import com.sleepycat.db.Database; 15 import com.sleepycat.db.DatabaseConfig; 16 import com.sleepycat.db.DatabaseEntry; 17 import com.sleepycat.db.DatabaseException; 18 import com.sleepycat.db.Environment; 19 import com.sleepycat.db.JoinConfig; 20 import com.sleepycat.db.OperationStatus; 21 import com.sleepycat.db.SecondaryConfig; 22 import com.sleepycat.db.SecondaryDatabase; 23 import com.sleepycat.db.SecondaryKeyCreator; 24 import com.sleepycat.db.Transaction; 25 import com.sleepycat.util.RuntimeExceptionWrapper; 26 import com.sleepycat.util.keyrange.KeyRange; 27 import com.sleepycat.util.keyrange.KeyRangeException; 28 29 /** 30 * Represents a Berkeley DB database and adds support for indices, bindings and 31 * key ranges. 32 * 33 * <p>This class defines a view and takes care of reading and updating indices, 34 * calling bindings, constraining access to a key range, etc.</p> 35 * 36 * @author Mark Hayes 37 */ 38 final class DataView implements Cloneable { 39 40 Database db; 41 SecondaryDatabase secDb; 42 CurrentTransaction currentTxn; 43 KeyRange range; 44 EntryBinding keyBinding; 45 EntryBinding valueBinding; 46 EntityBinding entityBinding; 47 PrimaryKeyAssigner keyAssigner; 48 SecondaryKeyCreator secKeyCreator; 49 CursorConfig cursorConfig; // Used for all operations via this view 50 boolean writeAllowed; // Read-write view 51 boolean ordered; // Not a HASH Db 52 boolean keyRangesAllowed; // BTREE only 53 boolean recNumAllowed; // QUEUE, RECNO, or BTREE-RECNUM Db 54 boolean recNumAccess; // recNumAllowed && using a rec num binding 55 boolean btreeRecNumDb; // BTREE-RECNUM Db 56 boolean btreeRecNumAccess; // recNumAccess && BTREE-RECNUM Db 57 boolean recNumRenumber; // RECNO-RENUM Db 58 boolean keysRenumbered; // recNumRenumber || btreeRecNumAccess 59 boolean dupsAllowed; // Dups configured 60 boolean dupsOrdered; // Sorted dups configured 61 boolean transactional; // Db is transactional 62 boolean readUncommittedAllowed; // Read-uncommited is optional in DB-CORE 63 64 /* 65 * If duplicatesView is called, dupsView will be true and dupsKey will be 66 * the secondary key used as the "single key" range. dupRange will be set 67 * as the range of the primary key values if subRange is subsequently 68 * called, to further narrow the view. 69 */ 70 DatabaseEntry dupsKey; 71 boolean dupsView; 72 KeyRange dupsRange; 73 74 /** 75 * Creates a view for a given database and bindings. The initial key range 76 * of the view will be open. 77 */ DataView(Database database, EntryBinding keyBinding, EntryBinding valueBinding, EntityBinding entityBinding, boolean writeAllowed, PrimaryKeyAssigner keyAssigner)78 DataView(Database database, EntryBinding keyBinding, 79 EntryBinding valueBinding, EntityBinding entityBinding, 80 boolean writeAllowed, PrimaryKeyAssigner keyAssigner) 81 throws IllegalArgumentException { 82 83 if (database == null) { 84 throw new IllegalArgumentException("database is null"); 85 } 86 db = database; 87 try { 88 currentTxn = 89 CurrentTransaction.getInstanceInternal(db.getEnvironment()); 90 DatabaseConfig dbConfig; 91 if (db instanceof SecondaryDatabase) { 92 secDb = (SecondaryDatabase) database; 93 SecondaryConfig secConfig = secDb.getSecondaryConfig(); 94 secKeyCreator = secConfig.getKeyCreator(); 95 dbConfig = secConfig; 96 } else { 97 dbConfig = db.getConfig(); 98 } 99 ordered = !DbCompat.isTypeHash(dbConfig); 100 keyRangesAllowed = DbCompat.isTypeBtree(dbConfig); 101 recNumAllowed = DbCompat.isTypeQueue(dbConfig) || 102 DbCompat.isTypeRecno(dbConfig) || 103 DbCompat.getBtreeRecordNumbers(dbConfig); 104 recNumRenumber = DbCompat.getRenumbering(dbConfig); 105 dupsAllowed = DbCompat.getSortedDuplicates(dbConfig) || 106 DbCompat.getUnsortedDuplicates(dbConfig); 107 dupsOrdered = DbCompat.getSortedDuplicates(dbConfig); 108 transactional = currentTxn.isTxnMode() && 109 dbConfig.getTransactional(); 110 readUncommittedAllowed = DbCompat.getReadUncommitted(dbConfig); 111 btreeRecNumDb = recNumAllowed && DbCompat.isTypeBtree(dbConfig); 112 range = new KeyRange(dbConfig.getBtreeComparator()); 113 } catch (DatabaseException e) { 114 throw RuntimeExceptionWrapper.wrapIfNeeded(e); 115 } 116 this.writeAllowed = writeAllowed; 117 this.keyBinding = keyBinding; 118 this.valueBinding = valueBinding; 119 this.entityBinding = entityBinding; 120 this.keyAssigner = keyAssigner; 121 cursorConfig = CursorConfig.DEFAULT; 122 123 if (valueBinding != null && entityBinding != null) 124 throw new IllegalArgumentException 125 ("both valueBinding and entityBinding are non-null"); 126 127 if (keyBinding instanceof com.sleepycat.bind.RecordNumberBinding) { 128 if (!recNumAllowed) { 129 throw new IllegalArgumentException 130 ("RecordNumberBinding requires DB_BTREE/DB_RECNUM, " + 131 "DB_RECNO, or DB_QUEUE"); 132 } 133 recNumAccess = true; 134 if (btreeRecNumDb) { 135 btreeRecNumAccess = true; 136 } 137 } 138 keysRenumbered = recNumRenumber || btreeRecNumAccess; 139 } 140 141 /** 142 * Clones the view. 143 */ cloneView()144 private DataView cloneView() { 145 146 try { 147 return (DataView) super.clone(); 148 } catch (CloneNotSupportedException willNeverOccur) { 149 throw DbCompat.unexpectedState(); 150 } 151 } 152 153 /** 154 * Return a new key-set view derived from this view by setting the 155 * entity and value binding to null. 156 * 157 * @return the derived view. 158 */ keySetView()159 DataView keySetView() { 160 161 if (keyBinding == null) { 162 throw new UnsupportedOperationException("Must have keyBinding"); 163 } 164 DataView view = cloneView(); 165 view.valueBinding = null; 166 view.entityBinding = null; 167 return view; 168 } 169 170 /** 171 * Return a new value-set view derived from this view by setting the 172 * key binding to null. 173 * 174 * @return the derived view. 175 */ valueSetView()176 DataView valueSetView() { 177 178 if (valueBinding == null && entityBinding == null) { 179 throw new UnsupportedOperationException 180 ("Must have valueBinding or entityBinding"); 181 } 182 DataView view = cloneView(); 183 view.keyBinding = null; 184 return view; 185 } 186 187 /** 188 * Return a new value-set view for single key range. 189 * 190 * @param singleKey the single key value. 191 * 192 * @return the derived view. 193 * 194 * @throws DatabaseException if a database problem occurs. 195 * 196 * @throws KeyRangeException if the specified range is not within the 197 * current range. 198 */ valueSetView(Object singleKey)199 DataView valueSetView(Object singleKey) 200 throws DatabaseException, KeyRangeException { 201 202 /* 203 * Must do subRange before valueSetView since the latter clears the 204 * key binding needed for the former. 205 */ 206 KeyRange singleKeyRange = subRange(range, singleKey); 207 DataView view = valueSetView(); 208 view.range = singleKeyRange; 209 return view; 210 } 211 212 /** 213 * Return a new value-set view for key range, optionally changing 214 * the key binding. 215 */ subView(Object beginKey, boolean beginInclusive, Object endKey, boolean endInclusive, EntryBinding keyBinding)216 DataView subView(Object beginKey, boolean beginInclusive, 217 Object endKey, boolean endInclusive, 218 EntryBinding keyBinding) 219 throws DatabaseException, KeyRangeException { 220 221 DataView view = cloneView(); 222 view.setRange(beginKey, beginInclusive, endKey, endInclusive); 223 if (keyBinding != null) view.keyBinding = keyBinding; 224 return view; 225 } 226 227 /** 228 * Return a new duplicates view for a given secondary key. 229 */ duplicatesView(Object secondaryKey, EntryBinding primaryKeyBinding)230 DataView duplicatesView(Object secondaryKey, 231 EntryBinding primaryKeyBinding) 232 throws DatabaseException, KeyRangeException { 233 234 if (!isSecondary()) { 235 throw new UnsupportedOperationException 236 ("Only allowed for maps on secondary databases"); 237 } 238 if (dupsView) { 239 throw DbCompat.unexpectedState(); 240 } 241 DataView view = cloneView(); 242 view.range = subRange(view.range, secondaryKey); 243 view.dupsKey = view.range.getSingleKey(); 244 view.dupsView = true; 245 view.keyBinding = primaryKeyBinding; 246 return view; 247 } 248 249 /** 250 * Returns a new view with a specified cursor configuration. 251 */ configuredView(CursorConfig config)252 DataView configuredView(CursorConfig config) { 253 254 DataView view = cloneView(); 255 view.cursorConfig = (config != null) ? 256 DbCompat.cloneCursorConfig(config) : CursorConfig.DEFAULT; 257 return view; 258 } 259 260 /** 261 * Returns the current transaction for the view or null if the environment 262 * is non-transactional. 263 */ getCurrentTxn()264 CurrentTransaction getCurrentTxn() { 265 266 return transactional ? currentTxn : null; 267 } 268 269 /** 270 * Sets this view's range to a subrange with the given parameters. 271 */ setRange(Object beginKey, boolean beginInclusive, Object endKey, boolean endInclusive)272 private void setRange(Object beginKey, boolean beginInclusive, 273 Object endKey, boolean endInclusive) 274 throws DatabaseException, KeyRangeException { 275 276 if ((beginKey != null || endKey != null) && !keyRangesAllowed) { 277 throw new UnsupportedOperationException 278 ("Key ranges allowed only for BTREE databases"); 279 } 280 KeyRange useRange = useSubRange(); 281 useRange = subRange 282 (useRange, beginKey, beginInclusive, endKey, endInclusive); 283 if (dupsView) { 284 dupsRange = useRange; 285 } else { 286 range = useRange; 287 } 288 } 289 290 /** 291 * Returns the key thang for a single key range, or null if a single key 292 * range is not used. 293 */ getSingleKeyThang()294 DatabaseEntry getSingleKeyThang() { 295 296 return range.getSingleKey(); 297 } 298 299 /** 300 * Returns the environment for the database. 301 */ getEnv()302 final Environment getEnv() { 303 304 return currentTxn.getEnvironment(); 305 } 306 307 /** 308 * Returns whether this is a view on a secondary database rather 309 * than directly on a primary database. 310 */ isSecondary()311 final boolean isSecondary() { 312 313 return (secDb != null); 314 } 315 316 /** 317 * Returns whether no records are present in the view. 318 * 319 * Auto-commit must be performed by the caller. 320 */ isEmpty()321 boolean isEmpty() 322 throws DatabaseException { 323 324 DataCursor cursor = new DataCursor(this, false); 325 try { 326 return cursor.getFirst(false) != OperationStatus.SUCCESS; 327 } finally { 328 cursor.close(); 329 } 330 } 331 332 /** 333 * Appends a value and returns the new key. If a key assigner is used 334 * it assigns the key, otherwise a QUEUE or RECNO database is required. 335 * 336 * Auto-commit must be performed by the caller. 337 */ append(Object value, Object[] retPrimaryKey, Object[] retValue)338 OperationStatus append(Object value, 339 Object[] retPrimaryKey, 340 Object[] retValue) 341 throws DatabaseException { 342 343 /* 344 * Flags will be NOOVERWRITE if used with assigner, or APPEND 345 * otherwise. 346 * Requires: if value param, value or entity binding 347 * Requires: if retPrimaryKey, primary key binding (no index). 348 * Requires: if retValue, value or entity binding 349 */ 350 DatabaseEntry keyThang = new DatabaseEntry(); 351 DatabaseEntry valueThang = new DatabaseEntry(); 352 useValue(value, valueThang, null); 353 OperationStatus status; 354 if (keyAssigner != null) { 355 keyAssigner.assignKey(keyThang); 356 if (!range.check(keyThang)) { 357 throw new IllegalArgumentException 358 ("assigned key out of range"); 359 } 360 DataCursor cursor = new DataCursor(this, true); 361 try { 362 status = cursor.getCursor().putNoOverwrite(keyThang, 363 valueThang); 364 } finally { 365 cursor.close(); 366 } 367 } else { 368 /* Assume QUEUE/RECNO access method. */ 369 if (currentTxn.isCDBCursorOpen(db)) { 370 throw new IllegalStateException 371 ("cannot open CDB write cursor when read cursor is open"); 372 } 373 status = DbCompat.append(db, useTransaction(), 374 keyThang, valueThang); 375 if (status == OperationStatus.SUCCESS && !range.check(keyThang)) { 376 db.delete(useTransaction(), keyThang); 377 throw new IllegalArgumentException 378 ("appended record number out of range"); 379 } 380 } 381 if (status == OperationStatus.SUCCESS) { 382 returnPrimaryKeyAndValue(keyThang, valueThang, 383 retPrimaryKey, retValue); 384 } 385 return status; 386 } 387 388 /** 389 * Returns the current transaction if the database is transaction, or null 390 * if the database is not transactional or there is no current transaction. 391 */ useTransaction()392 Transaction useTransaction() { 393 return transactional ? currentTxn.getTransaction() : null; 394 } 395 396 /** 397 * Deletes all records in the current range. 398 * 399 * Auto-commit must be performed by the caller. 400 */ clear()401 void clear() 402 throws DatabaseException { 403 404 DataCursor cursor = new DataCursor(this, true); 405 try { 406 OperationStatus status = OperationStatus.SUCCESS; 407 while (status == OperationStatus.SUCCESS) { 408 if (keysRenumbered) { 409 status = cursor.getFirst(true); 410 } else { 411 status = cursor.getNext(true); 412 } 413 if (status == OperationStatus.SUCCESS) { 414 cursor.delete(); 415 } 416 } 417 } finally { 418 cursor.close(); 419 } 420 } 421 422 /** 423 * Returns a cursor for this view that reads only records having the 424 * specified index key values. 425 */ join(DataView[] indexViews, Object[] indexKeys, JoinConfig joinConfig)426 DataCursor join(DataView[] indexViews, Object[] indexKeys, 427 JoinConfig joinConfig) 428 throws DatabaseException { 429 430 DataCursor joinCursor = null; 431 DataCursor[] indexCursors = new DataCursor[indexViews.length]; 432 try { 433 for (int i = 0; i < indexViews.length; i += 1) { 434 indexCursors[i] = new DataCursor(indexViews[i], false); 435 indexCursors[i].getSearchKey(indexKeys[i], null, false); 436 } 437 joinCursor = new DataCursor(this, indexCursors, joinConfig, true); 438 return joinCursor; 439 } finally { 440 if (joinCursor == null) { 441 // An exception is being thrown, so close cursors we opened. 442 for (int i = 0; i < indexCursors.length; i += 1) { 443 if (indexCursors[i] != null) { 444 try { indexCursors[i].close(); } 445 catch (Exception e) { 446 /* FindBugs, this is ok. */ 447 } 448 } 449 } 450 } 451 } 452 } 453 454 /** 455 * Returns a cursor for this view that reads only records having the 456 * index key values at the specified cursors. 457 */ join(DataCursor[] indexCursors, JoinConfig joinConfig)458 DataCursor join(DataCursor[] indexCursors, JoinConfig joinConfig) 459 throws DatabaseException { 460 461 return new DataCursor(this, indexCursors, joinConfig, false); 462 } 463 464 /** 465 * Returns primary key and value if return parameters are non-null. 466 */ returnPrimaryKeyAndValue(DatabaseEntry keyThang, DatabaseEntry valueThang, Object[] retPrimaryKey, Object[] retValue)467 private void returnPrimaryKeyAndValue(DatabaseEntry keyThang, 468 DatabaseEntry valueThang, 469 Object[] retPrimaryKey, 470 Object[] retValue) { 471 // Requires: if retPrimaryKey, primary key binding (no index). 472 // Requires: if retValue, value or entity binding 473 474 if (retPrimaryKey != null) { 475 if (keyBinding == null) { 476 throw new IllegalArgumentException 477 ("returning key requires primary key binding"); 478 } else if (isSecondary()) { 479 throw new IllegalArgumentException 480 ("returning key requires unindexed view"); 481 } else { 482 retPrimaryKey[0] = keyBinding.entryToObject(keyThang); 483 } 484 } 485 if (retValue != null) { 486 retValue[0] = makeValue(keyThang, valueThang); 487 } 488 } 489 490 /** 491 * Populates the key entry and returns whether the key is within range. 492 */ useKey(Object key, Object value, DatabaseEntry keyThang, KeyRange checkRange)493 boolean useKey(Object key, Object value, DatabaseEntry keyThang, 494 KeyRange checkRange) 495 throws DatabaseException { 496 497 if (key != null) { 498 if (keyBinding == null) { 499 throw new IllegalArgumentException 500 ("non-null key with null key binding"); 501 } 502 keyBinding.objectToEntry(key, keyThang); 503 } else { 504 if (value == null) { 505 throw new IllegalArgumentException("null key and null value"); 506 } 507 if (entityBinding == null) { 508 throw new IllegalStateException 509 ("EntityBinding required to derive key from value"); 510 } 511 if (!dupsView && isSecondary()) { 512 DatabaseEntry primaryKeyThang = new DatabaseEntry(); 513 entityBinding.objectToKey(value, primaryKeyThang); 514 DatabaseEntry valueThang = new DatabaseEntry(); 515 entityBinding.objectToData(value, valueThang); 516 secKeyCreator.createSecondaryKey(secDb, primaryKeyThang, 517 valueThang, keyThang); 518 } else { 519 entityBinding.objectToKey(value, keyThang); 520 } 521 } 522 if (recNumAccess && DbCompat.getRecordNumber(keyThang) <= 0) { 523 return false; 524 } 525 if (checkRange != null && !checkRange.check(keyThang)) { 526 return false; 527 } 528 return true; 529 } 530 531 /** 532 * Returns whether data keys can be derived from the value/entity binding 533 * of this view, which determines whether a value/entity object alone is 534 * sufficient for operations that require keys. 535 */ canDeriveKeyFromValue()536 final boolean canDeriveKeyFromValue() { 537 538 return (entityBinding != null); 539 } 540 541 /** 542 * Populates the value entry and throws an exception if the primary key 543 * would be changed via an entity binding. 544 */ useValue(Object value, DatabaseEntry valueThang, DatabaseEntry checkKeyThang)545 void useValue(Object value, DatabaseEntry valueThang, 546 DatabaseEntry checkKeyThang) { 547 if (valueBinding != null) { 548 /* Allow binding to handle null value. */ 549 valueBinding.objectToEntry(value, valueThang); 550 } else if (entityBinding != null) { 551 if (value == null) { 552 throw new IllegalArgumentException 553 ("null value with entity binding"); 554 } 555 entityBinding.objectToData(value, valueThang); 556 if (checkKeyThang != null) { 557 DatabaseEntry thang = new DatabaseEntry(); 558 entityBinding.objectToKey(value, thang); 559 if (!KeyRange.equalBytes(thang, checkKeyThang)) { 560 throw new IllegalArgumentException 561 ("cannot change primary key"); 562 } 563 } 564 } else { 565 if (value != null) { 566 throw new IllegalArgumentException 567 ("non-null value with null value/entity binding"); 568 } 569 valueThang.setData(KeyRange.ZERO_LENGTH_BYTE_ARRAY); 570 valueThang.setOffset(0); 571 valueThang.setSize(0); 572 } 573 } 574 575 /** 576 * Converts a key entry to a key object. 577 */ makeKey(DatabaseEntry keyThang, DatabaseEntry priKeyThang)578 Object makeKey(DatabaseEntry keyThang, DatabaseEntry priKeyThang) { 579 580 if (keyBinding == null) { 581 throw new UnsupportedOperationException(); 582 } else { 583 DatabaseEntry thang = dupsView ? priKeyThang : keyThang; 584 if (thang.getSize() == 0) { 585 return null; 586 } else { 587 return keyBinding.entryToObject(thang); 588 } 589 } 590 } 591 592 /** 593 * Converts a key-value entry pair to a value object. 594 */ makeValue(DatabaseEntry primaryKeyThang, DatabaseEntry valueThang)595 Object makeValue(DatabaseEntry primaryKeyThang, DatabaseEntry valueThang) { 596 597 Object value; 598 if (valueBinding != null) { 599 value = valueBinding.entryToObject(valueThang); 600 } else if (entityBinding != null) { 601 value = entityBinding.entryToObject(primaryKeyThang, 602 valueThang); 603 } else { 604 throw new UnsupportedOperationException 605 ("Requires valueBinding or entityBinding"); 606 } 607 return value; 608 } 609 610 /** 611 * Intersects the given key and the current range. 612 */ subRange(KeyRange useRange, Object singleKey)613 KeyRange subRange(KeyRange useRange, Object singleKey) 614 throws DatabaseException, KeyRangeException { 615 616 return useRange.subRange(makeRangeKey(singleKey)); 617 } 618 619 /** 620 * Intersects the given range and the current range. 621 */ subRange(KeyRange useRange, Object beginKey, boolean beginInclusive, Object endKey, boolean endInclusive)622 KeyRange subRange(KeyRange useRange, 623 Object beginKey, boolean beginInclusive, 624 Object endKey, boolean endInclusive) 625 throws DatabaseException, KeyRangeException { 626 627 if (beginKey == endKey && beginInclusive && endInclusive) { 628 return subRange(useRange, beginKey); 629 } 630 if (!ordered) { 631 throw new UnsupportedOperationException 632 ("Cannot use key ranges on an unsorted database"); 633 } 634 DatabaseEntry beginThang = 635 (beginKey != null) ? makeRangeKey(beginKey) : null; 636 DatabaseEntry endThang = 637 (endKey != null) ? makeRangeKey(endKey) : null; 638 639 return useRange.subRange(beginThang, beginInclusive, 640 endThang, endInclusive); 641 } 642 643 /** 644 * Returns the range to use for sub-ranges. Returns range if this is not a 645 * dupsView, or the dupsRange if this is a dupsView, creating dupsRange if 646 * necessary. 647 */ useSubRange()648 KeyRange useSubRange() 649 throws DatabaseException { 650 651 if (dupsView) { 652 synchronized (this) { 653 if (dupsRange == null) { 654 DatabaseConfig config = 655 secDb.getPrimaryDatabase().getConfig(); 656 dupsRange = new KeyRange(config.getBtreeComparator()); 657 } 658 } 659 return dupsRange; 660 } else { 661 return range; 662 } 663 } 664 665 /** 666 * Given a key object, make a key entry that can be used in a range. 667 */ makeRangeKey(Object key)668 private DatabaseEntry makeRangeKey(Object key) 669 throws DatabaseException { 670 671 DatabaseEntry thang = new DatabaseEntry(); 672 if (keyBinding != null) { 673 useKey(key, null, thang, null); 674 } else { 675 useKey(null, key, thang, null); 676 } 677 return thang; 678 } 679 } 680