1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2000,2008 Oracle.  All rights reserved.
5 *
6 * $Id: DataCursor.java,v 12.11 2008/02/07 17:12:26 mark Exp $
7 */
8
9package com.sleepycat.collections;
10
11import com.sleepycat.compat.DbCompat;
12import com.sleepycat.db.Cursor;
13import com.sleepycat.db.CursorConfig;
14import com.sleepycat.db.DatabaseEntry;
15import com.sleepycat.db.DatabaseException;
16import com.sleepycat.db.JoinConfig;
17import com.sleepycat.db.JoinCursor;
18import com.sleepycat.db.LockMode;
19import com.sleepycat.db.OperationStatus;
20import com.sleepycat.util.keyrange.KeyRange;
21import com.sleepycat.util.keyrange.RangeCursor;
22
23/**
24 * Represents a Berkeley DB cursor and adds support for indices, bindings and
25 * key ranges.
26 *
27 * <p>This class operates on a view and takes care of reading and updating
28 * indices, calling bindings, constraining access to a key range, etc.</p>
29 *
30 * @author Mark Hayes
31 */
32final class DataCursor implements Cloneable {
33
34    /** Repositioned exactly to the key/data pair given. */
35    static final int REPOS_EXACT = 0;
36    /** Repositioned on a record following the key/data pair given. */
37    static final int REPOS_NEXT = 1;
38    /** Repositioned failed, no records on or after the key/data pair given. */
39    static final int REPOS_EOF = 2;
40
41    private RangeCursor cursor;
42    private JoinCursor joinCursor;
43    private DataView view;
44    private KeyRange range;
45    private boolean writeAllowed;
46    private boolean readUncommitted;
47    private DatabaseEntry keyThang;
48    private DatabaseEntry valueThang;
49    private DatabaseEntry primaryKeyThang;
50    private DatabaseEntry otherThang;
51    private DataCursor[] indexCursorsToClose;
52
53    /**
54     * Creates a cursor for a given view.
55     */
56    DataCursor(DataView view, boolean writeAllowed)
57        throws DatabaseException {
58
59        init(view, writeAllowed, null, null);
60    }
61
62    /**
63     * Creates a cursor for a given view.
64     */
65    DataCursor(DataView view, boolean writeAllowed, CursorConfig config)
66        throws DatabaseException {
67
68        init(view, writeAllowed, config, null);
69    }
70
71    /**
72     * Creates a cursor for a given view and single key range.
73     * Used by unit tests.
74     */
75    DataCursor(DataView view, boolean writeAllowed, Object singleKey)
76        throws DatabaseException {
77
78        init(view, writeAllowed, null, view.subRange(view.range, singleKey));
79    }
80
81    /**
82     * Creates a cursor for a given view and key range.
83     * Used by unit tests.
84     */
85    DataCursor(DataView view, boolean writeAllowed,
86               Object beginKey, boolean beginInclusive,
87               Object endKey, boolean endInclusive)
88        throws DatabaseException {
89
90        init(view, writeAllowed, null,
91             view.subRange
92                (view.range, beginKey, beginInclusive, endKey, endInclusive));
93    }
94
95    /**
96     * Creates a join cursor.
97     */
98    DataCursor(DataView view, DataCursor[] indexCursors,
99               JoinConfig joinConfig, boolean closeIndexCursors)
100        throws DatabaseException {
101
102        if (view.isSecondary()) {
103            throw new IllegalArgumentException(
104                "The primary collection in a join must not be a secondary " +
105                "database");
106        }
107        Cursor[] cursors = new Cursor[indexCursors.length];
108        for (int i = 0; i < cursors.length; i += 1) {
109            cursors[i] = indexCursors[i].cursor.getCursor();
110        }
111        joinCursor = view.db.join(cursors, joinConfig);
112        init(view, false, null, null);
113        if (closeIndexCursors) {
114            indexCursorsToClose = indexCursors;
115        }
116    }
117
118    /**
119     * Clones a cursor preserving the current position.
120     */
121    DataCursor cloneCursor()
122        throws DatabaseException {
123
124        checkNoJoinCursor();
125
126        DataCursor o;
127        try {
128            o = (DataCursor) super.clone();
129        } catch (CloneNotSupportedException neverHappens) {
130            return null;
131        }
132
133        o.initThangs();
134        KeyRange.copy(keyThang, o.keyThang);
135        KeyRange.copy(valueThang, o.valueThang);
136        if (primaryKeyThang != keyThang) {
137            KeyRange.copy(primaryKeyThang, o.primaryKeyThang);
138        }
139
140        o.cursor = cursor.dup(true);
141        return o;
142    }
143
144    /**
145     * Returns the internal range cursor.
146     */
147    RangeCursor getCursor() {
148        return cursor;
149    }
150
151    /**
152     * Constructor helper.
153     */
154    private void init(DataView view,
155                      boolean writeAllowed,
156                      CursorConfig config,
157                      KeyRange range)
158        throws DatabaseException {
159
160        if (config == null) {
161            config = view.cursorConfig;
162        }
163        this.view = view;
164        this.writeAllowed = writeAllowed && view.writeAllowed;
165        this.range = (range != null) ? range : view.range;
166        readUncommitted = config.getReadUncommitted() ||
167                          view.currentTxn.isReadUncommitted();
168        initThangs();
169
170        if (joinCursor == null) {
171            cursor = new MyRangeCursor
172                (this.range, config, view, this.writeAllowed);
173        }
174    }
175
176    /**
177     * Constructor helper.
178     */
179    private void initThangs()
180        throws DatabaseException {
181
182        keyThang = new DatabaseEntry();
183        primaryKeyThang = view.isSecondary() ? (new DatabaseEntry())
184                                             : keyThang;
185        valueThang = new DatabaseEntry();
186    }
187
188    /**
189     * Set entries from given byte arrays.
190     */
191    private void setThangs(byte[] keyBytes,
192                           byte[] priKeyBytes,
193                           byte[] valueBytes) {
194
195        keyThang.setData(KeyRange.copyBytes(keyBytes));
196
197        if (keyThang != primaryKeyThang) {
198            primaryKeyThang.setData(KeyRange.copyBytes(priKeyBytes));
199        }
200
201        valueThang.setData(KeyRange.copyBytes(valueBytes));
202    }
203
204    /**
205     * Closes the associated cursor.
206     */
207    void close()
208        throws DatabaseException {
209
210        if (joinCursor != null) {
211            JoinCursor toClose = joinCursor;
212            joinCursor = null;
213            toClose.close();
214        }
215        if (cursor != null) {
216            Cursor toClose = cursor.getCursor();
217            cursor = null;
218            view.currentTxn.closeCursor(toClose );
219        }
220        if (indexCursorsToClose != null) {
221            DataCursor[] toClose = indexCursorsToClose;
222            indexCursorsToClose = null;
223            for (int i = 0; i < toClose.length; i += 1) {
224                toClose[i].close();
225            }
226        }
227    }
228
229    /**
230     * Repositions to a given raw key/data pair, or just past it if that record
231     * has been deleted.
232     *
233     * @return REPOS_EXACT, REPOS_NEXT or REPOS_EOF.
234     */
235    int repositionRange(byte[] keyBytes,
236                        byte[] priKeyBytes,
237                        byte[] valueBytes,
238                        boolean lockForWrite)
239        throws DatabaseException {
240
241        LockMode lockMode = getLockMode(lockForWrite);
242        OperationStatus status = null;
243
244        /* Use the given key/data byte arrays. */
245        setThangs(keyBytes, priKeyBytes, valueBytes);
246
247        /* Position on or after the given key/data pair. */
248        if (view.dupsAllowed) {
249            status = cursor.getSearchBothRange(keyThang, primaryKeyThang,
250                                               valueThang, lockMode);
251        }
252        if (status != OperationStatus.SUCCESS) {
253            status = cursor.getSearchKeyRange(keyThang, primaryKeyThang,
254                                              valueThang, lockMode);
255        }
256
257        /* Return the result of the operation. */
258        if (status == OperationStatus.SUCCESS) {
259            if (!KeyRange.equalBytes(keyBytes, 0, keyBytes.length,
260                                     keyThang.getData(),
261                                     keyThang.getOffset(),
262                                     keyThang.getSize())) {
263                return REPOS_NEXT;
264            }
265            if (view.dupsAllowed) {
266                DatabaseEntry thang = view.isSecondary() ? primaryKeyThang
267                                                         : valueThang;
268                byte[] bytes = view.isSecondary() ? priKeyBytes
269                                                  : valueBytes;
270                if (!KeyRange.equalBytes(bytes, 0, bytes.length,
271                                         thang.getData(),
272                                         thang.getOffset(),
273                                         thang.getSize())) {
274                    return REPOS_NEXT;
275                }
276            }
277            return REPOS_EXACT;
278        } else {
279            return REPOS_EOF;
280        }
281    }
282
283    /**
284     * Repositions to a given raw key/data pair.
285     *
286     * @throws IllegalStateException when the database has unordered keys or
287     * unordered duplicates.
288     *
289     * @return whether the search succeeded.
290     */
291    boolean repositionExact(byte[] keyBytes,
292                            byte[] priKeyBytes,
293                            byte[] valueBytes,
294                            boolean lockForWrite)
295        throws DatabaseException {
296
297        LockMode lockMode = getLockMode(lockForWrite);
298        OperationStatus status = null;
299
300        /* Use the given key/data byte arrays. */
301        setThangs(keyBytes, priKeyBytes, valueBytes);
302
303        /* Position on the given key/data pair. */
304        if (view.recNumRenumber) {
305            /* getSearchBoth doesn't work with recno-renumber databases. */
306            status = cursor.getSearchKey(keyThang, primaryKeyThang,
307                                         valueThang, lockMode);
308        } else {
309            status = cursor.getSearchBoth(keyThang, primaryKeyThang,
310                                          valueThang, lockMode);
311        }
312
313        return (status == OperationStatus.SUCCESS);
314    }
315
316    /**
317     * Returns the view for this cursor.
318     */
319    DataView getView() {
320
321        return view;
322    }
323
324    /**
325     * Returns the range for this cursor.
326     */
327    KeyRange getRange() {
328
329        return range;
330    }
331
332    /**
333     * Returns whether write is allowed for this cursor, as specified to the
334     * constructor.
335     */
336    boolean isWriteAllowed() {
337
338        return writeAllowed;
339    }
340
341    /**
342     * Returns the key object for the last record read.
343     */
344    Object getCurrentKey()
345        throws DatabaseException {
346
347        return view.makeKey(keyThang, primaryKeyThang);
348    }
349
350    /**
351     * Returns the value object for the last record read.
352     */
353    Object getCurrentValue()
354        throws DatabaseException {
355
356        return view.makeValue(primaryKeyThang, valueThang);
357    }
358
359    /**
360     * Returns the internal key entry.
361     */
362    DatabaseEntry getKeyThang() {
363        return keyThang;
364    }
365
366    /**
367     * Returns the internal primary key entry, which is the same object as the
368     * key entry if the cursor is not for a secondary database.
369     */
370    DatabaseEntry getPrimaryKeyThang() {
371        return primaryKeyThang;
372    }
373
374    /**
375     * Returns the internal value entry.
376     */
377    DatabaseEntry getValueThang() {
378        return valueThang;
379    }
380
381    /**
382     * Returns whether record number access is allowed.
383     */
384    boolean hasRecNumAccess() {
385
386        return view.recNumAccess;
387    }
388
389    /**
390     * Returns the record number for the last record read.
391     */
392    int getCurrentRecordNumber()
393        throws DatabaseException {
394
395        if (view.btreeRecNumDb) {
396            /* BTREE-RECNO access. */
397            if (otherThang == null) {
398                otherThang = new DatabaseEntry();
399            }
400            DbCompat.getCurrentRecordNumber(cursor.getCursor(), otherThang,
401                                            getLockMode(false));
402            return DbCompat.getRecordNumber(otherThang);
403        } else {
404            /* QUEUE or RECNO database. */
405            return DbCompat.getRecordNumber(keyThang);
406        }
407    }
408
409    /**
410     * Binding version of Cursor.getCurrent(), no join cursor allowed.
411     */
412    OperationStatus getCurrent(boolean lockForWrite)
413        throws DatabaseException {
414
415        checkNoJoinCursor();
416        return cursor.getCurrent(keyThang, primaryKeyThang, valueThang,
417                                 getLockMode(lockForWrite));
418    }
419
420    /**
421     * Binding version of Cursor.getFirst(), join cursor is allowed.
422     */
423    OperationStatus getFirst(boolean lockForWrite)
424        throws DatabaseException {
425
426        LockMode lockMode = getLockMode(lockForWrite);
427        if (joinCursor != null) {
428            return joinCursor.getNext(keyThang, valueThang, lockMode);
429        } else {
430            return cursor.getFirst(keyThang, primaryKeyThang, valueThang,
431                                   lockMode);
432        }
433    }
434
435    /**
436     * Binding version of Cursor.getNext(), join cursor is allowed.
437     */
438    OperationStatus getNext(boolean lockForWrite)
439        throws DatabaseException {
440
441        LockMode lockMode = getLockMode(lockForWrite);
442        if (joinCursor != null) {
443            return joinCursor.getNext(keyThang, valueThang, lockMode);
444        } else {
445            return cursor.getNext(keyThang, primaryKeyThang, valueThang,
446                                  lockMode);
447        }
448    }
449
450    /**
451     * Binding version of Cursor.getNext(), join cursor is allowed.
452     */
453    OperationStatus getNextNoDup(boolean lockForWrite)
454        throws DatabaseException {
455
456        LockMode lockMode = getLockMode(lockForWrite);
457        if (joinCursor != null) {
458            return joinCursor.getNext(keyThang, valueThang, lockMode);
459        } else if (view.dupsView) {
460            return cursor.getNext
461                (keyThang, primaryKeyThang, valueThang, lockMode);
462        } else {
463            return cursor.getNextNoDup
464                (keyThang, primaryKeyThang, valueThang, lockMode);
465        }
466    }
467
468    /**
469     * Binding version of Cursor.getNextDup(), no join cursor allowed.
470     */
471    OperationStatus getNextDup(boolean lockForWrite)
472        throws DatabaseException {
473
474        checkNoJoinCursor();
475        if (view.dupsView) {
476            return null;
477        } else {
478            return cursor.getNextDup
479                (keyThang, primaryKeyThang, valueThang,
480                 getLockMode(lockForWrite));
481        }
482    }
483
484    /**
485     * Binding version of Cursor.getLast(), no join cursor allowed.
486     */
487    OperationStatus getLast(boolean lockForWrite)
488        throws DatabaseException {
489
490        checkNoJoinCursor();
491        return cursor.getLast(keyThang, primaryKeyThang, valueThang,
492                              getLockMode(lockForWrite));
493    }
494
495    /**
496     * Binding version of Cursor.getPrev(), no join cursor allowed.
497     */
498    OperationStatus getPrev(boolean lockForWrite)
499        throws DatabaseException {
500
501        checkNoJoinCursor();
502        return cursor.getPrev(keyThang, primaryKeyThang, valueThang,
503                              getLockMode(lockForWrite));
504    }
505
506    /**
507     * Binding version of Cursor.getPrevNoDup(), no join cursor allowed.
508     */
509    OperationStatus getPrevNoDup(boolean lockForWrite)
510        throws DatabaseException {
511
512        checkNoJoinCursor();
513        LockMode lockMode = getLockMode(lockForWrite);
514        if (view.dupsView) {
515            return null;
516        } else if (view.dupsView) {
517            return cursor.getPrev
518                (keyThang, primaryKeyThang, valueThang, lockMode);
519        } else {
520            return cursor.getPrevNoDup
521                (keyThang, primaryKeyThang, valueThang, lockMode);
522        }
523    }
524
525    /**
526     * Binding version of Cursor.getPrevDup(), no join cursor allowed.
527     */
528    OperationStatus getPrevDup(boolean lockForWrite)
529        throws DatabaseException {
530
531        checkNoJoinCursor();
532        if (view.dupsView) {
533            return null;
534        } else {
535            return cursor.getPrevDup
536                (keyThang, primaryKeyThang, valueThang,
537                 getLockMode(lockForWrite));
538        }
539    }
540
541    /**
542     * Binding version of Cursor.getSearchKey(), no join cursor allowed.
543     * Searches by record number in a BTREE-RECNO db with RECNO access.
544     */
545    OperationStatus getSearchKey(Object key, Object value,
546                                 boolean lockForWrite)
547        throws DatabaseException {
548
549        checkNoJoinCursor();
550        if (view.dupsView) {
551            if (view.useKey(key, value, primaryKeyThang, view.dupsRange)) {
552                KeyRange.copy(view.dupsKey, keyThang);
553                return cursor.getSearchBoth
554                    (keyThang, primaryKeyThang, valueThang,
555                     getLockMode(lockForWrite));
556            }
557        } else {
558            if (view.useKey(key, value, keyThang, range)) {
559                return doGetSearchKey(lockForWrite);
560            }
561        }
562        return OperationStatus.NOTFOUND;
563    }
564
565    /**
566     * Pass-thru version of Cursor.getSearchKey().
567     * Searches by record number in a BTREE-RECNO db with RECNO access.
568     */
569    private OperationStatus doGetSearchKey(boolean lockForWrite)
570        throws DatabaseException {
571
572        LockMode lockMode = getLockMode(lockForWrite);
573        if (view.btreeRecNumAccess) {
574            return cursor.getSearchRecordNumber(keyThang, primaryKeyThang,
575                                                valueThang, lockMode);
576        } else {
577            return cursor.getSearchKey(keyThang, primaryKeyThang,
578                                       valueThang, lockMode);
579        }
580    }
581
582    /**
583     * Binding version of Cursor.getSearchKeyRange(), no join cursor allowed.
584     */
585    OperationStatus getSearchKeyRange(Object key, Object value,
586                                      boolean lockForWrite)
587        throws DatabaseException {
588
589        checkNoJoinCursor();
590        LockMode lockMode = getLockMode(lockForWrite);
591        if (view.dupsView) {
592            if (view.useKey(key, value, primaryKeyThang, view.dupsRange)) {
593                KeyRange.copy(view.dupsKey, keyThang);
594                return cursor.getSearchBothRange
595                    (keyThang, primaryKeyThang, valueThang, lockMode);
596            }
597        } else {
598            if (view.useKey(key, value, keyThang, range)) {
599                return cursor.getSearchKeyRange
600                    (keyThang, primaryKeyThang, valueThang, lockMode);
601            }
602        }
603        return OperationStatus.NOTFOUND;
604    }
605
606    /**
607     * Find the given key and value using getSearchBoth if possible or a
608     * sequential scan otherwise, no join cursor allowed.
609     */
610    OperationStatus findBoth(Object key, Object value, boolean lockForWrite)
611        throws DatabaseException {
612
613        checkNoJoinCursor();
614        LockMode lockMode = getLockMode(lockForWrite);
615        view.useValue(value, valueThang, null);
616        if (view.dupsView) {
617            if (view.useKey(key, value, primaryKeyThang, view.dupsRange)) {
618                KeyRange.copy(view.dupsKey, keyThang);
619                if (otherThang == null) {
620                    otherThang = new DatabaseEntry();
621                }
622                OperationStatus status = cursor.getSearchBoth
623                    (keyThang, primaryKeyThang, otherThang, lockMode);
624                if (status == OperationStatus.SUCCESS &&
625                    KeyRange.equalBytes(otherThang, valueThang)) {
626                    return status;
627                }
628            }
629        } else if (view.useKey(key, value, keyThang, range)) {
630            if (view.isSecondary()) {
631                if (otherThang == null) {
632                    otherThang = new DatabaseEntry();
633                }
634                OperationStatus status = cursor.getSearchKey(keyThang,
635                                                             primaryKeyThang,
636                                                             otherThang,
637                                                             lockMode);
638                while (status == OperationStatus.SUCCESS) {
639                    if (KeyRange.equalBytes(otherThang, valueThang)) {
640                        return status;
641                    }
642                    status = cursor.getNextDup(keyThang, primaryKeyThang,
643                                               otherThang, lockMode);
644                }
645                /* if status != SUCCESS set range cursor to invalid? */
646            } else {
647                return cursor.getSearchBoth(keyThang, null, valueThang,
648                                            lockMode);
649            }
650        }
651        return OperationStatus.NOTFOUND;
652    }
653
654    /**
655     * Find the given value using getSearchBoth if possible or a sequential
656     * scan otherwise, no join cursor allowed.
657     */
658    OperationStatus findValue(Object value, boolean findFirst)
659        throws DatabaseException {
660
661        checkNoJoinCursor();
662
663        if (view.entityBinding != null && !view.isSecondary() &&
664            (findFirst || !view.dupsAllowed)) {
665            return findBoth(null, value, false);
666        } else {
667            if (otherThang == null) {
668                otherThang = new DatabaseEntry();
669            }
670            view.useValue(value, otherThang, null);
671            OperationStatus status = findFirst ? getFirst(false)
672                                               : getLast(false);
673            while (status == OperationStatus.SUCCESS) {
674                if (KeyRange.equalBytes(valueThang, otherThang)) {
675                    break;
676                }
677                status = findFirst ? getNext(false) : getPrev(false);
678            }
679            return status;
680        }
681    }
682
683    /**
684     * Calls Cursor.count(), no join cursor allowed.
685     */
686    int count()
687        throws DatabaseException {
688
689        checkNoJoinCursor();
690        if (view.dupsView) {
691            return 1;
692        } else {
693            return cursor.count();
694        }
695    }
696
697    /**
698     * Binding version of Cursor.putCurrent().
699     */
700    OperationStatus putCurrent(Object value)
701        throws DatabaseException {
702
703        checkWriteAllowed(false);
704        view.useValue(value, valueThang, keyThang);
705
706        /*
707         * Workaround for a DB core problem: With HASH type a put() with
708         * different data is allowed.
709         */
710        boolean hashWorkaround = (view.dupsOrdered && !view.ordered);
711        if (hashWorkaround) {
712            if (otherThang == null) {
713                otherThang = new DatabaseEntry();
714            }
715            cursor.getCurrent(keyThang, primaryKeyThang, otherThang,
716                              LockMode.DEFAULT);
717            if (KeyRange.equalBytes(valueThang, otherThang)) {
718                return OperationStatus.SUCCESS;
719            } else {
720                throw new IllegalArgumentException(
721                  "Current data differs from put data with sorted duplicates");
722            }
723        }
724
725        return cursor.putCurrent(valueThang);
726    }
727
728    /**
729     * Binding version of Cursor.putAfter().
730     */
731    OperationStatus putAfter(Object value)
732        throws DatabaseException {
733
734        checkWriteAllowed(false);
735        view.useValue(value, valueThang, null); /* why no key check? */
736        return cursor.putAfter(keyThang, valueThang);
737    }
738
739    /**
740     * Binding version of Cursor.putBefore().
741     */
742    OperationStatus putBefore(Object value)
743        throws DatabaseException {
744
745        checkWriteAllowed(false);
746        view.useValue(value, valueThang, keyThang);
747        return cursor.putBefore(keyThang, valueThang);
748    }
749
750    /**
751     * Binding version of Cursor.put(), optionally returning the old value and
752     * optionally using the current key instead of the key parameter.
753     */
754    OperationStatus put(Object key, Object value, Object[] oldValue,
755                        boolean useCurrentKey)
756        throws DatabaseException {
757
758        initForPut(key, value, oldValue, useCurrentKey);
759        return cursor.put(keyThang, valueThang);
760    }
761
762    /**
763     * Binding version of Cursor.putNoOverwrite(), optionally using the current
764     * key instead of the key parameter.
765     */
766    OperationStatus putNoOverwrite(Object key, Object value,
767                                   boolean useCurrentKey)
768        throws DatabaseException {
769
770        initForPut(key, value, null, useCurrentKey);
771        return cursor.putNoOverwrite(keyThang, valueThang);
772    }
773
774    /**
775     * Binding version of Cursor.putNoDupData(), optionally returning the old
776     * value and optionally using the current key instead of the key parameter.
777     */
778    OperationStatus putNoDupData(Object key, Object value, Object[] oldValue,
779                                 boolean useCurrentKey)
780        throws DatabaseException {
781
782        initForPut(key, value, oldValue, useCurrentKey);
783        if (view.dupsOrdered) {
784            return cursor.putNoDupData(keyThang, valueThang);
785        } else {
786            if (view.dupsAllowed) {
787                /* Unordered duplicates. */
788                OperationStatus status =
789                        cursor.getSearchBoth(keyThang, primaryKeyThang,
790                                             valueThang,
791                                             getLockMode(false));
792                if (status == OperationStatus.SUCCESS) {
793                    return OperationStatus.KEYEXIST;
794                } else {
795                    return cursor.put(keyThang, valueThang);
796                }
797            } else {
798                /* No duplicates. */
799                return cursor.putNoOverwrite(keyThang, valueThang);
800            }
801        }
802    }
803
804    /**
805     * Do setup for a put() operation.
806     */
807    private void initForPut(Object key, Object value, Object[] oldValue,
808                            boolean useCurrentKey)
809        throws DatabaseException {
810
811        checkWriteAllowed(false);
812        if (!useCurrentKey && !view.useKey(key, value, keyThang, range)) {
813            throw new IllegalArgumentException("key out of range");
814        }
815        if (oldValue != null) {
816            oldValue[0] = null;
817            if (!view.dupsAllowed) {
818                OperationStatus status = doGetSearchKey(true);
819                if (status == OperationStatus.SUCCESS) {
820                    oldValue[0] = getCurrentValue();
821                }
822            }
823        }
824        view.useValue(value, valueThang, keyThang);
825    }
826
827    /**
828     * Sets the key entry to the begin key of a single key range, so the next
829     * time a putXxx() method is called that key will be used.
830     */
831    void useRangeKey() {
832        if (!range.isSingleKey()) {
833            throw new IllegalStateException();
834        }
835        KeyRange.copy(range.getSingleKey(), keyThang);
836    }
837
838    /**
839     * Perform an arbitrary database 'delete' operation.
840     */
841    OperationStatus delete()
842        throws DatabaseException {
843
844        checkWriteAllowed(true);
845        return cursor.delete();
846    }
847
848    /**
849     * Returns the lock mode to use for a getXxx() operation.
850     */
851    LockMode getLockMode(boolean lockForWrite) {
852
853        /* Read-uncommmitted takes precedence over write-locking. */
854
855        if (readUncommitted) {
856            return LockMode.READ_UNCOMMITTED;
857        } else if (lockForWrite) {
858            return view.currentTxn.getWriteLockMode();
859        } else {
860            return LockMode.DEFAULT;
861        }
862    }
863
864    /**
865     * Throws an exception if a join cursor is in use.
866     */
867    private void checkNoJoinCursor() {
868
869        if (joinCursor != null) {
870            throw new UnsupportedOperationException
871                ("Not allowed with a join cursor");
872        }
873    }
874
875    /**
876     * Throws an exception if write is not allowed or if a join cursor is in
877     * use.
878     */
879    private void checkWriteAllowed(boolean allowSecondary) {
880
881        checkNoJoinCursor();
882
883        if (!writeAllowed || (!allowSecondary && view.isSecondary())) {
884            throw new UnsupportedOperationException
885                ("Writing is not allowed");
886        }
887    }
888}
889