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