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