1 /*- 2 * Copyright (c) 2002, 2020 Oracle and/or its affiliates. All rights reserved. 3 * 4 * See the file LICENSE for license information. 5 * 6 */ 7 8 package com.sleepycat.util.keyrange; 9 10 import com.sleepycat.compat.DbCompat; 11 import com.sleepycat.compat.DbCompat.OpReadOptions; 12 import com.sleepycat.compat.DbCompat.OpResult; 13 import com.sleepycat.db.Cursor; 14 import com.sleepycat.db.DatabaseEntry; 15 import com.sleepycat.db.DatabaseException; 16 import com.sleepycat.db.OperationStatus; 17 import com.sleepycat.db.SecondaryCursor; 18 19 /** 20 * A cursor-like interface that enforces a key range. The method signatures 21 * are actually those of SecondaryCursor, but the pKey parameter may be null. 22 * It was done this way to avoid doubling the number of methods. 23 * 24 * <p>This is not a fully general implementation of a range cursor and should 25 * not be used directly by applications; however, it may evolve into a 26 * generally useful range cursor some day.</p> 27 * 28 * @author Mark Hayes 29 */ 30 public class RangeCursor implements Cloneable { 31 32 /** 33 * The cursor and secondary cursor are the same object. The secCursor is 34 * null if the database is not a secondary database. 35 */ 36 private Cursor cursor; 37 private SecondaryCursor secCursor; 38 39 /** 40 * The range is always non-null, but may be unbounded meaning that it is 41 * open and not used. 42 */ 43 private KeyRange range; 44 45 /** 46 * The pkRange may be non-null only if the range is a single-key range 47 * and the cursor is a secondary cursor. It further restricts the range of 48 * primary keys in a secondary database. 49 */ 50 private KeyRange pkRange; 51 52 /** 53 * If the DB supported sorted duplicates, then calling 54 * Cursor.getSearchBothRange is allowed. 55 */ 56 private boolean sortedDups; 57 58 /** 59 * The privXxx entries are used only when the range is bounded. We read 60 * into these private entries to avoid modifying the caller's entry 61 * parameters in the case where we read successfully but the key is out of 62 * range. In that case we return NOTFOUND and we want to leave the entry 63 * parameters unchanged. 64 */ 65 private DatabaseEntry privKey; 66 private DatabaseEntry privPKey; 67 private DatabaseEntry privData; 68 69 /** 70 * The initialized flag is set to true whenever we successfully position 71 * the cursor. It is used to implement the getNext/Prev logic for doing a 72 * getFirst/Last when the cursor is not initialized. We can't rely on 73 * Cursor to do that for us, since if we position the underlying cursor 74 * successfully but the key is out of range, we have no way to set the 75 * underlying cursor to uninitialized. A range cursor always starts in the 76 * uninitialized state. 77 */ 78 private boolean initialized; 79 80 /** 81 * Creates a range cursor with a duplicate range. 82 */ RangeCursor(KeyRange range, KeyRange pkRange, boolean sortedDups, Cursor cursor)83 public RangeCursor(KeyRange range, 84 KeyRange pkRange, 85 boolean sortedDups, 86 Cursor cursor) { 87 if (pkRange != null && !range.singleKey) { 88 throw new IllegalArgumentException(); 89 } 90 this.range = range; 91 this.pkRange = pkRange; 92 this.sortedDups = sortedDups; 93 this.cursor = cursor; 94 init(); 95 if (pkRange != null && secCursor == null) { 96 throw new IllegalArgumentException(); 97 } 98 } 99 100 /** 101 * Create a cloned range cursor. The caller must clone the underlying 102 * cursor before using this constructor, because cursor open/close is 103 * handled specially for CDS cursors outside this class. 104 */ dup(boolean samePosition)105 public RangeCursor dup(boolean samePosition) 106 throws DatabaseException { 107 108 try { 109 RangeCursor c = (RangeCursor) super.clone(); 110 c.cursor = dupCursor(cursor, samePosition); 111 c.init(); 112 return c; 113 } catch (CloneNotSupportedException neverHappens) { 114 return null; 115 } 116 } 117 118 /** 119 * Used for opening and duping (cloning). 120 */ init()121 private void init() { 122 123 if (cursor instanceof SecondaryCursor) { 124 secCursor = (SecondaryCursor) cursor; 125 } else { 126 secCursor = null; 127 } 128 129 if (range.hasBound()) { 130 privKey = new DatabaseEntry(); 131 privPKey = new DatabaseEntry(); 132 privData = new DatabaseEntry(); 133 } else { 134 privKey = null; 135 privPKey = null; 136 privData = null; 137 } 138 } 139 140 /** 141 * Returns whether the cursor is initialized at a valid position. 142 */ isInitialized()143 public boolean isInitialized() { 144 return initialized; 145 } 146 147 /** 148 * Returns the underlying cursor. Used for cloning. 149 */ getCursor()150 public Cursor getCursor() { 151 return cursor; 152 } 153 154 /** 155 * When an unbounded range is used, this method is called to use the 156 * callers entry parameters directly, to avoid the extra step of copying 157 * between the private entries and the caller's entries. 158 */ setParams(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data)159 private void setParams(DatabaseEntry key, 160 DatabaseEntry pKey, 161 DatabaseEntry data) { 162 privKey = key; 163 privPKey = pKey; 164 privData = data; 165 } 166 167 /** 168 * Dups the cursor, sets the cursor and secCursor fields to the duped 169 * cursor, and returns the old cursor. Always call endOperation in a 170 * finally clause after calling beginOperation. 171 * 172 * <p>If the returned cursor == the cursor field, the cursor is 173 * uninitialized and was not duped; this case is handled correctly by 174 * endOperation.</p> 175 */ beginOperation()176 private Cursor beginOperation() 177 throws DatabaseException { 178 179 Cursor oldCursor = cursor; 180 if (initialized) { 181 cursor = dupCursor(cursor, true); 182 if (secCursor != null) { 183 secCursor = (SecondaryCursor) cursor; 184 } 185 } else { 186 return cursor; 187 } 188 return oldCursor; 189 } 190 191 /** 192 * If the operation succeeded, leaves the duped cursor in place and closes 193 * the oldCursor. If the operation failed, moves the oldCursor back in 194 * place and closes the duped cursor. oldCursor may be null if 195 * beginOperation was not called, in cases where we don't need to dup 196 * the cursor. Always call endOperation when a successful operation ends, 197 * in order to set the initialized field. 198 */ endOperation(Cursor oldCursor, OpResult result, DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data)199 private void endOperation(Cursor oldCursor, 200 OpResult result, 201 DatabaseEntry key, 202 DatabaseEntry pKey, 203 DatabaseEntry data) 204 throws DatabaseException { 205 206 if (result.isSuccess()) { 207 if (oldCursor != null && oldCursor != cursor) { 208 closeCursor(oldCursor); 209 } 210 if (key != null) { 211 swapData(key, privKey); 212 } 213 if (pKey != null && secCursor != null) { 214 swapData(pKey, privPKey); 215 } 216 if (data != null) { 217 swapData(data, privData); 218 } 219 initialized = true; 220 } else { 221 if (oldCursor != null && oldCursor != cursor) { 222 closeCursor(cursor); 223 cursor = oldCursor; 224 if (secCursor != null) { 225 secCursor = (SecondaryCursor) cursor; 226 } 227 } 228 } 229 } 230 231 /** 232 * Swaps the contents of the two entries. Used to return entry data to 233 * the caller when the operation was successful. 234 */ swapData(DatabaseEntry e1, DatabaseEntry e2)235 private static void swapData(DatabaseEntry e1, DatabaseEntry e2) { 236 237 byte[] d1 = e1.getData(); 238 int o1 = e1.getOffset(); 239 int s1 = e1.getSize(); 240 241 e1.setData(e2.getData(), e2.getOffset(), e2.getSize()); 242 e2.setData(d1, o1, s1); 243 } 244 245 /** 246 * Shares the same byte array, offset and size between two entries. 247 * Used when copying the entry data is not necessary because it is known 248 * that the underlying operation will not modify the entry, for example, 249 * with getSearchKey. 250 */ shareData(DatabaseEntry from, DatabaseEntry to)251 private static void shareData(DatabaseEntry from, DatabaseEntry to) { 252 253 if (from != null) { 254 to.setData(from.getData(), from.getOffset(), from.getSize()); 255 } 256 } 257 getFirst(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)258 public OpResult getFirst(DatabaseEntry key, 259 DatabaseEntry pKey, 260 DatabaseEntry data, 261 OpReadOptions options) 262 throws DatabaseException { 263 264 OpResult result; 265 if (!range.hasBound()) { 266 setParams(key, pKey, data); 267 result = doGetFirst(options); 268 endOperation(null, result, null, null, null); 269 return result; 270 } 271 if (pkRange != null && pkRange.isSingleKey()) { 272 KeyRange.copy(range.beginKey, privKey); 273 KeyRange.copy(pkRange.beginKey, privPKey); 274 result = doGetSearchBoth(options); 275 endOperation(null, result, key, pKey, data); 276 return result; 277 } 278 if (pkRange != null) { 279 KeyRange.copy(range.beginKey, privKey); 280 result = OpResult.FAILURE; 281 Cursor oldCursor = beginOperation(); 282 try { 283 if (pkRange.beginKey == null || !sortedDups) { 284 result = doGetSearchKey(options); 285 } else { 286 KeyRange.copy(pkRange.beginKey, privPKey); 287 result = doGetSearchBothRange(options); 288 if (result.isSuccess() && 289 !pkRange.beginInclusive && 290 pkRange.compare(privPKey, pkRange.beginKey) == 0) { 291 result = doGetNextDup(options); 292 } 293 } 294 if (result.isSuccess() && 295 !pkRange.check(privPKey)) { 296 result = OpResult.FAILURE; 297 } 298 } finally { 299 endOperation(oldCursor, result, key, pKey, data); 300 } 301 } else if (range.singleKey) { 302 KeyRange.copy(range.beginKey, privKey); 303 result = doGetSearchKey(options); 304 endOperation(null, result, key, pKey, data); 305 } else { 306 result = OpResult.FAILURE; 307 Cursor oldCursor = beginOperation(); 308 try { 309 if (range.beginKey == null) { 310 result = doGetFirst(options); 311 } else { 312 KeyRange.copy(range.beginKey, privKey); 313 result = doGetSearchKeyRange(options); 314 if (result.isSuccess() && 315 !range.beginInclusive && 316 range.compare(privKey, range.beginKey) == 0) { 317 result = doGetNextNoDup(options); 318 } 319 } 320 if (result.isSuccess() && 321 !range.check(privKey)) { 322 result = OpResult.FAILURE; 323 } 324 } finally { 325 endOperation(oldCursor, result, key, pKey, data); 326 } 327 } 328 return result; 329 } 330 331 /** 332 * This method will restart the operation when a key range is used and an 333 * insertion at the end of the key range is performed in another thread. 334 * The restarts are needed because a sequence of cursor movements is 335 * performed, and serializable isolation cannot be relied on to prevent 336 * insertions in other threads. Without the restarts, getLast could return 337 * NOTFOUND when keys in the range exist. This may only be an issue for JE 338 * since it uses record locking, while DB core uses page locking. 339 */ getLast(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)340 public OpResult getLast(DatabaseEntry key, 341 DatabaseEntry pKey, 342 DatabaseEntry data, 343 OpReadOptions options) 344 throws DatabaseException { 345 346 OpResult result = OpResult.FAILURE; 347 348 if (!range.hasBound()) { 349 setParams(key, pKey, data); 350 result = doGetLast(options); 351 endOperation(null, result, null, null, null); 352 return result; 353 } 354 355 Cursor oldCursor = beginOperation(); 356 try { 357 if (pkRange != null) { 358 result = getLastInPKeyRange(options); 359 360 /* Final check on candidate key and pKey value. */ 361 if (result.isSuccess() && 362 !(range.check(privKey) && pkRange.check(privPKey))) { 363 result = OpResult.FAILURE; 364 } 365 } else { 366 result = getLastInKeyRange(options); 367 368 /* Final check on candidate key value. */ 369 if (result.isSuccess() && 370 !range.check(privKey)) { 371 result = OpResult.FAILURE; 372 } 373 } 374 375 return result; 376 } finally { 377 endOperation(oldCursor, result, key, pKey, data); 378 } 379 } 380 381 /** 382 * Performs getLast operation when a main key range is specified but 383 * pkRange is null. Does everything but the final checks for key in range, 384 * i.e., when SUCCESS is returned the caller should do the final check. 385 */ getLastInKeyRange(OpReadOptions options)386 private OpResult getLastInKeyRange(OpReadOptions options) 387 throws DatabaseException { 388 389 /* Without an endKey, getLast returns the candidate record. */ 390 if (range.endKey == null) { 391 return doGetLast(options); 392 } 393 394 /* 395 * K stands for the main key at the cursor position in the comments 396 * below. 397 */ 398 while (true) { 399 KeyRange.copy(range.endKey, privKey); 400 OpResult result = doGetSearchKeyRange(options); 401 402 if (result.isSuccess()) { 403 404 /* Found K >= endKey. */ 405 if (range.endInclusive && 406 range.compare(range.endKey, privKey) == 0) { 407 408 /* K == endKey and endKey is inclusive. */ 409 410 if (!sortedDups) { 411 /* If dups are not configured, we're done. */ 412 return result; 413 } 414 415 /* 416 * If there are dups, we're positioned at endKey's first 417 * dup and we want to move to its last dup. Move to the 418 * first dup for the next main key (getNextNoDup) and then 419 * the prev record. In the absence of insertions by other 420 * threads, the prev record is the last dup for endKey. 421 */ 422 result = doGetNextNoDup(options); 423 if (result.isSuccess()) { 424 425 /* 426 * K > endKey. Move backward to the last dup for 427 * endKey. 428 */ 429 result = doGetPrev(options); 430 } else { 431 432 /* 433 * endKey is the last main key in the DB. Its last dup 434 * is the last key in the DB. 435 */ 436 result = doGetLast(options); 437 } 438 } else { 439 440 /* 441 * K > endKey or endKey is exclusive (and K >= endKey). In 442 * both cases, moving to the prev key finds the last key in 443 * the range, whether or not there are dups. 444 */ 445 result = doGetPrev(options); 446 } 447 } else { 448 449 /* 450 * There are no keys >= endKey in the DB. The last key in the 451 * range is the last key in the DB. 452 */ 453 result = doGetLast(options); 454 } 455 456 if (!result.isSuccess()) { 457 return result; 458 } 459 460 if (!range.checkEnd(privKey, true)) { 461 462 /* 463 * The last call above (getPrev or getLast) returned a key 464 * outside the endKey range. Another thread must have inserted 465 * this key. Start over. 466 */ 467 continue; 468 } 469 470 return result; 471 } 472 } 473 474 /** 475 * Performs getLast operation when both a main key range (which must be a 476 * single key range) and a pkRange are specified. Does everything but the 477 * final checks for key and pKey in range, i.e., when SUCCESS is returned 478 * the caller should do the final two checks. 479 */ getLastInPKeyRange(OpReadOptions options)480 private OpResult getLastInPKeyRange(OpReadOptions options) 481 throws DatabaseException { 482 483 /* We can do an exact search when range and pkRange are single keys. */ 484 if (pkRange.isSingleKey()) { 485 KeyRange.copy(range.beginKey, privKey); 486 KeyRange.copy(pkRange.beginKey, privPKey); 487 return doGetSearchBoth(options); 488 } 489 490 /* 491 * When dups are not configured, getSearchKey for the main key returns 492 * the only possible candidate record. 493 */ 494 if (!sortedDups) { 495 KeyRange.copy(range.beginKey, privKey); 496 return doGetSearchKey(options); 497 } 498 499 /* 500 * K stands for the main key and D for the duplicate (data item) at the 501 * cursor position in the comments below 502 */ 503 while (true) { 504 505 if (pkRange.endKey != null) { 506 507 KeyRange.copy(range.beginKey, privKey); 508 KeyRange.copy(pkRange.endKey, privPKey); 509 OpResult result = doGetSearchBothRange(options); 510 511 if (result.isSuccess()) { 512 513 /* Found D >= endKey. */ 514 if (!pkRange.endInclusive || 515 pkRange.compare(pkRange.endKey, privPKey) != 0) { 516 517 /* 518 * D > endKey or endKey is exclusive (and D >= endKey). 519 * In both cases, moving to the prev dup finds the last 520 * key in the range. 521 */ 522 result = doGetPrevDup(options); 523 524 if (!result.isSuccess()) { 525 return result; 526 } 527 528 if (!pkRange.checkEnd(privPKey, true)) { 529 530 /* 531 * getPrevDup returned a key outside the endKey 532 * range. Another thread must have inserted this 533 * key. Start over. 534 */ 535 continue; 536 } 537 } 538 /* Else D == endKey and endKey is inclusive. */ 539 540 return result; 541 } 542 /* Else there are no dups >= endKey. Fall through. */ 543 } 544 545 /* 546 * We're here for one of two reasons: 547 * 1. pkRange.endKey == null. 548 * 2. There are no dups >= endKey for the main key (status 549 * returned by getSearchBothRange above was not SUCCESS). 550 * In both cases, the last dup in the range is the last dup for the 551 * main key. 552 */ 553 KeyRange.copy(range.beginKey, privKey); 554 OpResult result = doGetSearchKey(options); 555 556 if (!result.isSuccess()) { 557 return result; 558 } 559 560 /* 561 * K == the main key and D is its first dup. We want to move to its 562 * last dup. Move to the first dup for the next main key; 563 * (getNextNoDup) and then the prev record. In the absence of 564 * insertions by other threads, the prev record is the last dup for 565 * the main key. 566 */ 567 result = doGetNextNoDup(options); 568 569 if (result.isSuccess()) { 570 571 /* 572 * K > main key and D is its first dup. Move to the prev record 573 * which should be the last dup for the main key. 574 */ 575 result = doGetPrev(options); 576 } else { 577 578 /* 579 * The main key specified is the last main key in the DB. Its 580 * last dup is the last record in the DB. 581 */ 582 result = doGetLast(options); 583 } 584 585 if (!result.isSuccess()) { 586 return result; 587 } 588 589 if (!range.checkEnd(privKey, true)) { 590 591 /* 592 * The last call above (getPrev or getLast) returned a key 593 * outside the endKey range. Another thread must have inserted 594 * this key. Start over. 595 */ 596 continue; 597 } 598 599 return result; 600 } 601 } 602 getNext(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)603 public OpResult getNext(DatabaseEntry key, 604 DatabaseEntry pKey, 605 DatabaseEntry data, 606 OpReadOptions options) 607 throws DatabaseException { 608 609 OpResult result; 610 if (!initialized) { 611 return getFirst(key, pKey, data, options); 612 } 613 if (!range.hasBound()) { 614 setParams(key, pKey, data); 615 result = doGetNext(options); 616 endOperation(null, result, null, null, null); 617 return result; 618 } 619 if (pkRange != null) { 620 if (pkRange.endKey == null) { 621 result = doGetNextDup(options); 622 endOperation(null, result, key, pKey, data); 623 } else { 624 result = OpResult.FAILURE; 625 Cursor oldCursor = beginOperation(); 626 try { 627 result = doGetNextDup(options); 628 if (result.isSuccess() && 629 !pkRange.checkEnd(privPKey, true)) { 630 result = OpResult.FAILURE; 631 } 632 } finally { 633 endOperation(oldCursor, result, key, pKey, data); 634 } 635 } 636 } else if (range.singleKey) { 637 result = doGetNextDup(options); 638 endOperation(null, result, key, pKey, data); 639 } else { 640 result = OpResult.FAILURE; 641 Cursor oldCursor = beginOperation(); 642 try { 643 result = doGetNext(options); 644 if (result.isSuccess() && 645 !range.check(privKey)) { 646 result = OpResult.FAILURE; 647 } 648 } finally { 649 endOperation(oldCursor, result, key, pKey, data); 650 } 651 } 652 return result; 653 } 654 getNextNoDup(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)655 public OpResult getNextNoDup(DatabaseEntry key, 656 DatabaseEntry pKey, 657 DatabaseEntry data, 658 OpReadOptions options) 659 throws DatabaseException { 660 661 OpResult result; 662 if (!initialized) { 663 return getFirst(key, pKey, data, options); 664 } 665 if (!range.hasBound()) { 666 setParams(key, pKey, data); 667 result = doGetNextNoDup(options); 668 endOperation(null, result, null, null, null); 669 return result; 670 } 671 if (range.singleKey) { 672 result = OpResult.FAILURE; 673 } else { 674 result = OpResult.FAILURE; 675 Cursor oldCursor = beginOperation(); 676 try { 677 result = doGetNextNoDup(options); 678 if (result.isSuccess() && 679 !range.check(privKey)) { 680 result = OpResult.FAILURE; 681 } 682 } finally { 683 endOperation(oldCursor, result, key, pKey, data); 684 } 685 } 686 return result; 687 } 688 getPrev(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)689 public OpResult getPrev(DatabaseEntry key, 690 DatabaseEntry pKey, 691 DatabaseEntry data, 692 OpReadOptions options) 693 throws DatabaseException { 694 695 OpResult result; 696 if (!initialized) { 697 return getLast(key, pKey, data, options); 698 } 699 if (!range.hasBound()) { 700 setParams(key, pKey, data); 701 result = doGetPrev(options); 702 endOperation(null, result, null, null, null); 703 return result; 704 } 705 if (pkRange != null) { 706 if (pkRange.beginKey == null) { 707 result = doGetPrevDup(options); 708 endOperation(null, result, key, pKey, data); 709 } else { 710 result = OpResult.FAILURE; 711 Cursor oldCursor = beginOperation(); 712 try { 713 result = doGetPrevDup(options); 714 if (result.isSuccess() && 715 !pkRange.checkBegin(privPKey, true)) { 716 result = OpResult.FAILURE; 717 } 718 } finally { 719 endOperation(oldCursor, result, key, pKey, data); 720 } 721 } 722 } else if (range.singleKey) { 723 result = doGetPrevDup(options); 724 endOperation(null, result, key, pKey, data); 725 } else { 726 result = OpResult.FAILURE; 727 Cursor oldCursor = beginOperation(); 728 try { 729 result = doGetPrev(options); 730 if (result.isSuccess() && 731 !range.check(privKey)) { 732 result = OpResult.FAILURE; 733 } 734 } finally { 735 endOperation(oldCursor, result, key, pKey, data); 736 } 737 } 738 return result; 739 } 740 getPrevNoDup(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)741 public OpResult getPrevNoDup(DatabaseEntry key, 742 DatabaseEntry pKey, 743 DatabaseEntry data, 744 OpReadOptions options) 745 throws DatabaseException { 746 747 OpResult result; 748 if (!initialized) { 749 return getLast(key, pKey, data, options); 750 } 751 if (!range.hasBound()) { 752 setParams(key, pKey, data); 753 result = doGetPrevNoDup(options); 754 endOperation(null, result, null, null, null); 755 return result; 756 } 757 if (range.singleKey) { 758 result = OpResult.FAILURE; 759 } else { 760 result = OpResult.FAILURE; 761 Cursor oldCursor = beginOperation(); 762 try { 763 result = doGetPrevNoDup(options); 764 if (result.isSuccess() && 765 !range.check(privKey)) { 766 result = OpResult.FAILURE; 767 } 768 } finally { 769 endOperation(oldCursor, result, key, pKey, data); 770 } 771 } 772 return result; 773 } 774 getSearchKey(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)775 public OpResult getSearchKey(DatabaseEntry key, 776 DatabaseEntry pKey, 777 DatabaseEntry data, 778 OpReadOptions options) 779 throws DatabaseException { 780 781 OpResult result; 782 if (!range.hasBound()) { 783 setParams(key, pKey, data); 784 result = doGetSearchKey(options); 785 endOperation(null, result, null, null, null); 786 return result; 787 } 788 if (!range.check(key)) { 789 result = OpResult.FAILURE; 790 } else if (pkRange != null) { 791 result = OpResult.FAILURE; 792 Cursor oldCursor = beginOperation(); 793 try { 794 shareData(key, privKey); 795 result = doGetSearchKey(options); 796 if (result.isSuccess() && 797 !pkRange.check(privPKey)) { 798 result = OpResult.FAILURE; 799 } 800 } finally { 801 endOperation(oldCursor, result, key, pKey, data); 802 } 803 } else { 804 shareData(key, privKey); 805 result = doGetSearchKey(options); 806 endOperation(null, result, key, pKey, data); 807 } 808 return result; 809 } 810 getSearchBoth(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)811 public OpResult getSearchBoth(DatabaseEntry key, 812 DatabaseEntry pKey, 813 DatabaseEntry data, 814 OpReadOptions options) 815 throws DatabaseException { 816 817 OpResult result; 818 if (!range.hasBound()) { 819 setParams(key, pKey, data); 820 result = doGetSearchBoth(options); 821 endOperation(null, result, null, null, null); 822 return result; 823 } 824 if (!range.check(key) || 825 (pkRange != null && !pkRange.check(pKey))) { 826 result = OpResult.FAILURE; 827 } else { 828 shareData(key, privKey); 829 if (secCursor != null) { 830 shareData(pKey, privPKey); 831 } else { 832 shareData(data, privData); 833 } 834 result = doGetSearchBoth(options); 835 endOperation(null, result, key, pKey, data); 836 } 837 return result; 838 } 839 getSearchKeyRange(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)840 public OpResult getSearchKeyRange(DatabaseEntry key, 841 DatabaseEntry pKey, 842 DatabaseEntry data, 843 OpReadOptions options) 844 throws DatabaseException { 845 846 OpResult result = OpResult.FAILURE; 847 if (!range.hasBound()) { 848 setParams(key, pKey, data); 849 result = doGetSearchKeyRange(options); 850 endOperation(null, result, null, null, null); 851 return result; 852 } 853 Cursor oldCursor = beginOperation(); 854 try { 855 shareData(key, privKey); 856 result = doGetSearchKeyRange(options); 857 if (result.isSuccess() && 858 (!range.check(privKey) || 859 (pkRange != null && !pkRange.check(pKey)))) { 860 result = OpResult.FAILURE; 861 } 862 } finally { 863 endOperation(oldCursor, result, key, pKey, data); 864 } 865 return result; 866 } 867 getSearchBothRange(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)868 public OpResult getSearchBothRange(DatabaseEntry key, 869 DatabaseEntry pKey, 870 DatabaseEntry data, 871 OpReadOptions options) 872 throws DatabaseException { 873 874 OpResult result = OpResult.FAILURE; 875 if (!range.hasBound()) { 876 setParams(key, pKey, data); 877 result = doGetSearchBothRange(options); 878 endOperation(null, result, null, null, null); 879 return result; 880 } 881 Cursor oldCursor = beginOperation(); 882 try { 883 shareData(key, privKey); 884 if (secCursor != null) { 885 shareData(pKey, privPKey); 886 } else { 887 shareData(data, privData); 888 } 889 result = doGetSearchBothRange(options); 890 if (result.isSuccess() && 891 (!range.check(privKey) || 892 (pkRange != null && !pkRange.check(pKey)))) { 893 result = OpResult.FAILURE; 894 } 895 } finally { 896 endOperation(oldCursor, result, key, pKey, data); 897 } 898 return result; 899 } 900 getSearchRecordNumber(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)901 public OpResult getSearchRecordNumber(DatabaseEntry key, 902 DatabaseEntry pKey, 903 DatabaseEntry data, 904 OpReadOptions options) 905 throws DatabaseException { 906 907 OpResult result; 908 if (!range.hasBound()) { 909 setParams(key, pKey, data); 910 result = doGetSearchRecordNumber(options); 911 endOperation(null, result, null, null, null); 912 return result; 913 } 914 if (!range.check(key)) { 915 result = OpResult.FAILURE; 916 } else { 917 shareData(key, privKey); 918 result = doGetSearchRecordNumber(options); 919 endOperation(null, result, key, pKey, data); 920 } 921 return result; 922 } 923 getNextDup(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)924 public OpResult getNextDup(DatabaseEntry key, 925 DatabaseEntry pKey, 926 DatabaseEntry data, 927 OpReadOptions options) 928 throws DatabaseException { 929 930 if (!initialized) { 931 throw new IllegalStateException("Cursor not initialized"); 932 } 933 OpResult result; 934 if (!range.hasBound()) { 935 setParams(key, pKey, data); 936 result = doGetNextDup(options); 937 endOperation(null, result, null, null, null); 938 } else if (pkRange != null && pkRange.endKey != null) { 939 result = OpResult.FAILURE; 940 Cursor oldCursor = beginOperation(); 941 try { 942 result = doGetNextDup(options); 943 if (result.isSuccess() && 944 !pkRange.checkEnd(privPKey, true)) { 945 result = OpResult.FAILURE; 946 } 947 } finally { 948 endOperation(oldCursor, result, key, pKey, data); 949 } 950 } else { 951 result = doGetNextDup(options); 952 endOperation(null, result, key, pKey, data); 953 } 954 return result; 955 } 956 getPrevDup(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)957 public OpResult getPrevDup(DatabaseEntry key, 958 DatabaseEntry pKey, 959 DatabaseEntry data, 960 OpReadOptions options) 961 throws DatabaseException { 962 963 if (!initialized) { 964 throw new IllegalStateException("Cursor not initialized"); 965 } 966 OpResult result; 967 if (!range.hasBound()) { 968 setParams(key, pKey, data); 969 result = doGetPrevDup(options); 970 endOperation(null, result, null, null, null); 971 } else if (pkRange != null && pkRange.beginKey != null) { 972 result = OpResult.FAILURE; 973 Cursor oldCursor = beginOperation(); 974 try { 975 result = doGetPrevDup(options); 976 if (result.isSuccess() && 977 !pkRange.checkBegin(privPKey, true)) { 978 result = OpResult.FAILURE; 979 } 980 } finally { 981 endOperation(oldCursor, result, key, pKey, data); 982 } 983 } else { 984 result = doGetPrevDup(options); 985 endOperation(null, result, key, pKey, data); 986 } 987 return result; 988 } 989 getCurrent(DatabaseEntry key, DatabaseEntry pKey, DatabaseEntry data, OpReadOptions options)990 public OpResult getCurrent(DatabaseEntry key, 991 DatabaseEntry pKey, 992 DatabaseEntry data, 993 OpReadOptions options) 994 throws DatabaseException { 995 996 if (!initialized) { 997 throw new IllegalStateException("Cursor not initialized"); 998 } 999 if (secCursor != null && pKey != null) { 1000 return OpResult.make( 1001 secCursor.getCurrent(key, pKey, data, options.getLockMode())); 1002 } else { 1003 return OpResult.make( 1004 cursor.getCurrent(key, data, options.getLockMode())); 1005 } 1006 } 1007 1008 /* 1009 * Pass-thru methods. 1010 */ 1011 close()1012 public void close() 1013 throws DatabaseException { 1014 1015 closeCursor(cursor); 1016 } 1017 count()1018 public int count() 1019 throws DatabaseException { 1020 1021 return cursor.count(); 1022 } 1023 delete()1024 public OperationStatus delete() 1025 throws DatabaseException { 1026 1027 return cursor.delete(); 1028 } 1029 put(DatabaseEntry key, DatabaseEntry data)1030 public OperationStatus put(DatabaseEntry key, DatabaseEntry data) 1031 throws DatabaseException { 1032 1033 return cursor.put(key, data); 1034 } 1035 putNoOverwrite(DatabaseEntry key, DatabaseEntry data)1036 public OperationStatus putNoOverwrite(DatabaseEntry key, 1037 DatabaseEntry data) 1038 throws DatabaseException { 1039 1040 return cursor.putNoOverwrite(key, data); 1041 } 1042 putNoDupData(DatabaseEntry key, DatabaseEntry data)1043 public OperationStatus putNoDupData(DatabaseEntry key, DatabaseEntry data) 1044 throws DatabaseException { 1045 1046 return cursor.putNoDupData(key, data); 1047 } 1048 putCurrent(DatabaseEntry data)1049 public OperationStatus putCurrent(DatabaseEntry data) 1050 throws DatabaseException { 1051 1052 return cursor.putCurrent(data); 1053 } 1054 putAfter(DatabaseEntry key, DatabaseEntry data)1055 public OperationStatus putAfter(DatabaseEntry key, DatabaseEntry data) 1056 throws DatabaseException { 1057 1058 return DbCompat.putAfter(cursor, key, data); 1059 } 1060 putBefore(DatabaseEntry key, DatabaseEntry data)1061 public OperationStatus putBefore(DatabaseEntry key, DatabaseEntry data) 1062 throws DatabaseException { 1063 1064 return DbCompat.putBefore(cursor, key, data); 1065 } 1066 doGetFirst(OpReadOptions options)1067 private OpResult doGetFirst(OpReadOptions options) 1068 throws DatabaseException { 1069 1070 if (secCursor != null && privPKey != null) { 1071 return OpResult.make( 1072 secCursor.getFirst( 1073 privKey, privPKey, privData, options.getLockMode())); 1074 } else { 1075 return OpResult.make( 1076 cursor.getFirst(privKey, privData, options.getLockMode())); 1077 } 1078 } 1079 doGetLast(OpReadOptions options)1080 private OpResult doGetLast(OpReadOptions options) 1081 throws DatabaseException { 1082 1083 if (secCursor != null && privPKey != null) { 1084 return OpResult.make( 1085 secCursor.getLast( 1086 privKey, privPKey, privData, options.getLockMode())); 1087 } else { 1088 return OpResult.make( 1089 cursor.getLast(privKey, privData, options.getLockMode())); 1090 } 1091 } 1092 doGetNext(OpReadOptions options)1093 private OpResult doGetNext(OpReadOptions options) 1094 throws DatabaseException { 1095 1096 if (secCursor != null && privPKey != null) { 1097 return OpResult.make( 1098 secCursor.getNext( 1099 privKey, privPKey, privData, options.getLockMode())); 1100 } else { 1101 return OpResult.make( 1102 cursor.getNext(privKey, privData, options.getLockMode())); 1103 } 1104 } 1105 doGetNextDup(OpReadOptions options)1106 private OpResult doGetNextDup(OpReadOptions options) 1107 throws DatabaseException { 1108 1109 if (secCursor != null && privPKey != null) { 1110 return OpResult.make( 1111 secCursor.getNextDup( 1112 privKey, privPKey, privData, options.getLockMode())); 1113 } else { 1114 return OpResult.make( 1115 cursor.getNextDup(privKey, privData, options.getLockMode())); 1116 } 1117 } 1118 doGetNextNoDup(OpReadOptions options)1119 private OpResult doGetNextNoDup(OpReadOptions options) 1120 throws DatabaseException { 1121 1122 if (secCursor != null && privPKey != null) { 1123 return OpResult.make( 1124 secCursor.getNextNoDup( 1125 privKey, privPKey, privData, options.getLockMode())); 1126 } else { 1127 return OpResult.make( 1128 cursor.getNextNoDup(privKey, privData, options.getLockMode())); 1129 } 1130 } 1131 doGetPrev(OpReadOptions options)1132 private OpResult doGetPrev(OpReadOptions options) 1133 throws DatabaseException { 1134 1135 if (secCursor != null && privPKey != null) { 1136 return OpResult.make( 1137 secCursor.getPrev( 1138 privKey, privPKey, privData, options.getLockMode())); 1139 } else { 1140 return OpResult.make( 1141 cursor.getPrev(privKey, privData, options.getLockMode())); 1142 } 1143 } 1144 doGetPrevDup(OpReadOptions options)1145 private OpResult doGetPrevDup(OpReadOptions options) 1146 throws DatabaseException { 1147 1148 if (secCursor != null && privPKey != null) { 1149 return OpResult.make( 1150 secCursor.getPrevDup( 1151 privKey, privPKey, privData, options.getLockMode())); 1152 } else { 1153 return OpResult.make( 1154 cursor.getPrevDup(privKey, privData, options.getLockMode())); 1155 } 1156 } 1157 doGetPrevNoDup(OpReadOptions options)1158 private OpResult doGetPrevNoDup(OpReadOptions options) 1159 throws DatabaseException { 1160 1161 if (secCursor != null && privPKey != null) { 1162 return OpResult.make( 1163 secCursor.getPrevNoDup( 1164 privKey, privPKey, privData, options.getLockMode())); 1165 } else { 1166 return OpResult.make( 1167 cursor.getPrevNoDup(privKey, privData, options.getLockMode())); 1168 } 1169 } 1170 doGetSearchKey(OpReadOptions options)1171 private OpResult doGetSearchKey(OpReadOptions options) 1172 throws DatabaseException { 1173 1174 if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) { 1175 return OpResult.FAILURE; 1176 } 1177 if (secCursor != null && privPKey != null) { 1178 return OpResult.make( 1179 secCursor.getSearchKey( 1180 privKey, privPKey, privData, options.getLockMode())); 1181 } else { 1182 return OpResult.make( 1183 cursor.getSearchKey(privKey, privData, options.getLockMode())); 1184 } 1185 } 1186 doGetSearchKeyRange(OpReadOptions options)1187 private OpResult doGetSearchKeyRange(OpReadOptions options) 1188 throws DatabaseException { 1189 1190 if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) { 1191 return OpResult.FAILURE; 1192 } 1193 if (secCursor != null && privPKey != null) { 1194 return OpResult.make( 1195 secCursor.getSearchKeyRange( 1196 privKey, privPKey, privData, options.getLockMode())); 1197 } else { 1198 return OpResult.make( 1199 cursor.getSearchKeyRange( 1200 privKey, privData, options.getLockMode())); 1201 } 1202 } 1203 doGetSearchBoth(OpReadOptions options)1204 private OpResult doGetSearchBoth(OpReadOptions options) 1205 throws DatabaseException { 1206 1207 if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) { 1208 return OpResult.FAILURE; 1209 } 1210 if (secCursor != null && privPKey != null) { 1211 return OpResult.make( 1212 secCursor.getSearchBoth( 1213 privKey, privPKey, privData, options.getLockMode())); 1214 } else { 1215 return OpResult.make( 1216 cursor.getSearchBoth( 1217 privKey, privData, options.getLockMode())); 1218 } 1219 } 1220 doGetSearchBothRange(OpReadOptions options)1221 private OpResult doGetSearchBothRange(OpReadOptions options) 1222 throws DatabaseException { 1223 1224 if (checkRecordNumber() && DbCompat.getRecordNumber(privKey) <= 0) { 1225 return OpResult.FAILURE; 1226 } 1227 if (secCursor != null && privPKey != null) { 1228 return OpResult.make( 1229 secCursor.getSearchBothRange( 1230 privKey, privPKey, privData, options.getLockMode())); 1231 } else { 1232 return OpResult.make( 1233 cursor.getSearchBothRange( 1234 privKey, privData, options.getLockMode())); 1235 } 1236 } 1237 doGetSearchRecordNumber(OpReadOptions options)1238 private OpResult doGetSearchRecordNumber(OpReadOptions options) 1239 throws DatabaseException { 1240 1241 if (DbCompat.getRecordNumber(privKey) <= 0) { 1242 return OpResult.FAILURE; 1243 } 1244 if (secCursor != null && privPKey != null) { 1245 return OpResult.make( 1246 DbCompat.getSearchRecordNumber( 1247 secCursor, privKey, privPKey, privData, 1248 options.getLockMode())); 1249 } else { 1250 return OpResult.make( 1251 DbCompat.getSearchRecordNumber( 1252 cursor, privKey, privData, options.getLockMode())); 1253 } 1254 } 1255 1256 /* 1257 * Protected methods for duping and closing cursors. These are overridden 1258 * by the collections API to implement cursor pooling for CDS. 1259 */ 1260 1261 /** 1262 * Dups the given cursor. 1263 */ dupCursor(Cursor cursor, boolean samePosition)1264 protected Cursor dupCursor(Cursor cursor, boolean samePosition) 1265 throws DatabaseException { 1266 1267 return cursor.dup(samePosition); 1268 } 1269 1270 /** 1271 * Closes the given cursor. 1272 */ closeCursor(Cursor cursor)1273 protected void closeCursor(Cursor cursor) 1274 throws DatabaseException { 1275 1276 cursor.close(); 1277 } 1278 1279 /** 1280 * If the database is a RECNO or QUEUE database, we know its keys are 1281 * record numbers. We treat a non-positive record number as out of bounds, 1282 * that is, we return NOTFOUND rather than throwing 1283 * IllegalArgumentException as would happen if we passed a non-positive 1284 * record number into the DB cursor. This behavior is required by the 1285 * collections interface. 1286 */ checkRecordNumber()1287 protected boolean checkRecordNumber() { 1288 return false; 1289 } 1290 } 1291