Merge lp:~pbeaman/akiban-persistit/tree-visitor-prototype into lp:akiban-persistit

Proposed by Peter Beaman
Status: Merged
Approved by: Peter Beaman
Approved revision: 417
Merged at revision: 411
Proposed branch: lp:~pbeaman/akiban-persistit/tree-visitor-prototype
Merge into: lp:akiban-persistit
Diff against target: 615 lines (+407/-17)
4 files modified
src/main/java/com/persistit/Exchange.java (+158/-16)
src/main/java/com/persistit/ReadOnlyExchange.java (+82/-0)
src/test/java/com/persistit/unit/ExchangeTest.java (+1/-1)
src/test/java/com/persistit/unit/TraverseVisitorTest.java (+166/-0)
To merge this branch: bzr merge lp:~pbeaman/akiban-persistit/tree-visitor-prototype
Reviewer Review Type Date Requested Status
Akiban Build User Needs Fixing
Nathan Williams Needs Information
Review via email: mp+140559@code.launchpad.net

Description of the change

Adds new Exchange#traverse methods and a new interface Exchange.TraverseVisitor. JavaDoc for these describes the functionality and restrictions.

A simple benchmark loop showed that the TraverseVisitor produces key-value pairs almost twice as fast as a regular traverse operations.

Fortunately, the implementation turned out to be extremely simple so very little delicate Exchange code needed to be disturbed.

This is a proposal is intended to help review the design. Probably the best way to evaluate it is to look at the diff and experiment with it as a prototype. I expect we'll go back and forth on things a bit before final approval into trunk.

To post a comment you must log in.
Revision history for this message
Peter Beaman (pbeaman) wrote :

Comments from email by Yuval:

On Tue, Dec 18, 2012 at 4:58 PM, Peter Beaman <email address hidden> wrote:
Must not modify the state of the Exchange instance. The visit method may use any accessor method on the Exchange, including Exchange.getKey() and Exchange.getValue(). However, the key value itself should not be modified.

It would be nice if we could get at this via the type system. Specifically, we could create interfaces for Exchange, Key and Values which contain only the read-only methods for each of those classes, and have TraverseVisitor.visit take a ReadExchange instead of an Exchange

Must not perform update operations affecting the Tree on which this Exchange operates.

What happens if the visitor breaks this constraint (or the one above, if we don't do the interface idea -- or if we do, but don't use a delegate pattern so that the visitor can just downcast its ReadExchange to Exchange)? It'd be nice if we could put some fail-fast check in there. Since the javadoc implies that there's some sort of Tree-grained (or larger?) lock taken during the visit, it'd be easy to add a "boolean visitHappening" and check this during all modification operations. It wouldn't even need to be volatile, since the lock will provide the HB edge.

Revision history for this message
Peter Beaman (pbeaman) wrote :

Thanks for the suggestion. I have modified the merge proposal to include a new ReadOnlyExchange interface that supplied only "safe" methods. The instance passed to visit is the original Exchange, so a perverse program could cast it. But the documentation and intent are clear, I believe.

I also added tests to prove that the methods provided by ReadOnlyExchange are indeed safe. It turns out that the visitor can indeed modify the key without causing an unwanted side-effects, so that restriction is removed. This may actually be a desirable operation for a visitor. For example, it might be useful to do a deep traversal of a group, but to skip over unwanted branches. A TraverseVisitor could detect that an unwanted branch has been entered and modify the key to skip past it.

There's no really good way to add safety checks to prevent code using this method from deadlocking, so I think we continue to need a warning The issue is that any call to modify any Tree from any Exchange is susceptible to the problem. We already spend a fair amount of CPU cycles checking for incorrect code patterns; in this case I think a warning is sufficient.

Revision history for this message
Nathan Williams (nwilliams) wrote :

Sorry for the review slowness.

There is extremely little to actually do the visitor -- nice!

However, I'm not so sure that all the methods on ReadOnlyExchange are "safe". They are truly read only, but they do modify internal state. A nested traverse, due to calling one of the has* methods, would bork the _spareKey1, I think.

My only other nit is that the ReadOnlyExchange is an interface so the abstract clauses on each method are superfluous. Persistit has interface styles with both, but my 'druthers would be new ones without.

review: Needs Information
Revision history for this message
Peter Beaman (pbeaman) wrote :

Good catch on the calls to traverse. I removed the hasXXX methods from ReadOnlyExchange. I had to do something ugly to the unit test that actually used them, and would gladly accept a suggestion for something better.

Re the "abstract" clauses, my 'druthers too. They came from the Eclipse refactor operation I did to create the class in the the first place and I didn't notice them. Overall, using refactoring to create this interface resulted in more problems them it solved - it was an educational experience.

Revision history for this message
Nathan Williams (nwilliams) wrote :

I think isValueDefined() is also a hidden traverse, as it is actually isKeyDefined().

The unit test doesn't bother me much. It's simple and easy to change if ever needed.

Looks good otherwise.

Revision history for this message
Peter Beaman (pbeaman) wrote :

Yes, you're right. Removed.

Revision history for this message
Peter Beaman (pbeaman) wrote :

A-pproving, per discussion with Tim and Nathan.

Revision history for this message
Akiban Build User (build-akiban) wrote :

There were 2 failures during build/test:

* job server-packages failed at build number 2960: http://172.16.20.104:8080/job/server-packages/2960/

* view must-pass failed: server-packages is red

review: Needs Fixing
Revision history for this message
Peter Beaman (pbeaman) wrote :

Packaging error. Will re A-pprove this.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/main/java/com/persistit/Exchange.java'
2--- src/main/java/com/persistit/Exchange.java 2012-11-28 19:07:46 +0000
3+++ src/main/java/com/persistit/Exchange.java 2013-01-07 23:12:21 +0000
4@@ -132,7 +132,7 @@
5 *
6 * @version 1.0
7 */
8-public class Exchange {
9+public class Exchange implements ReadOnlyExchange {
10
11 public enum Sequence {
12 NONE, FORWARD, REVERSE
13@@ -639,6 +639,44 @@
14 }
15
16 /**
17+ * A visitor used with the
18+ * {@link Exchange#traverse(Key.Direction, boolean, int, TraverseVisitor)}
19+ * The {@link #visit(ReadOnlyExchange)} method is called once for each
20+ * <code>Key</code> traversed by the <code>traverse</code> method.
21+ */
22+ public interface TraverseVisitor {
23+ /**
24+ * Receive an Exchange having <code>Key</code> and <code>Value</code>
25+ * values set by
26+ * {@link Exchange#traverse(Key.Direction, boolean, int, TraverseVisitor)}
27+ * . This method will be called once for each key encountered in the
28+ * traversal. This method may return <code>false</code> to stop
29+ * traversing additional keys. </p>
30+ * <p>
31+ * The implementation of this method:
32+ * <ul>
33+ * <li>Must return quickly, especially in a multi-threaded environment,
34+ * to avoid blocking other threads that may attempt to update records in
35+ * the same <code>Buffer</code>,
36+ * <li>Must not perform update operations on any <codeExchange</code>,
37+ * especially in a multi-threaded environment, to prevent deadlocks,
38+ * <li>May read and modify the <code>Key</code> and <code>Value</code>
39+ * fields of the supplied <code>ReadOnlyExchange</code>. Note, however,
40+ * that modifying the <code>Key</code> affects the results of subsequent
41+ * traversal operations.
42+ * </ul>
43+ *
44+ * @param ex
45+ * a {@link ReadOnlyExchange} from which the current
46+ * <code>Key</code> and <code>Value</code> may be read
47+ * @return <code>true</code> to continue traversing keys, or
48+ * <code>false</code> to stop
49+ * @throws PersistitException
50+ */
51+ public boolean visit(final ReadOnlyExchange ex) throws PersistitException;
52+ }
53+
54+ /**
55 * Delegate to {@link Key#reset} on the associated <code>Key</code> object.
56 *
57 * @return This <code>Exchange</code> to permit method call chaining.
58@@ -893,6 +931,7 @@
59 *
60 * @return This <code>Key</code>.
61 */
62+ @Override
63 public Key getKey() {
64 assertCorrectThread(true);
65 return _key;
66@@ -903,6 +942,7 @@
67 *
68 * @return The <code>Value</code>.
69 */
70+ @Override
71 public Value getValue() {
72 assertCorrectThread(true);
73 return _value;
74@@ -918,6 +958,7 @@
75 *
76 * @return The <code>Volume</code>.
77 */
78+ @Override
79 public Volume getVolume() {
80 assertCorrectThread(true);
81 return _volume;
82@@ -928,6 +969,7 @@
83 *
84 * @return The <code>Tree</code>
85 */
86+ @Override
87 public Tree getTree() {
88 assertCorrectThread(true);
89 return _tree;
90@@ -938,6 +980,7 @@
91 *
92 * @return The <code>Persistit</code> instance.
93 */
94+ @Override
95 public Persistit getPersistitInstance() {
96 assertCorrectThread(true);
97 return _persistit;
98@@ -952,6 +995,7 @@
99 *
100 * @return The change count
101 */
102+ @Override
103 public long getChangeCount() {
104 assertCorrectThread(true);
105 return _tree.getChangeCount();
106@@ -2010,7 +2054,7 @@
107 */
108 public boolean traverse(final Direction direction, final boolean deep, final int minimumBytes)
109 throws PersistitException {
110- return traverse(direction, deep, minimumBytes, 0, 0);
111+ return traverse(direction, deep, minimumBytes, 0, 0, null);
112 }
113
114 /**
115@@ -2027,10 +2071,9 @@
116 * visibility</i>, <code>false</code> is immediately returned.
117 */
118 private boolean traverse(final Direction direction, final boolean deep, final int minimumBytes,
119- final int minKeyDepth, final int matchUpToIndex) throws PersistitException {
120+ final int minKeyDepth, final int matchUpToIndex, final TraverseVisitor visitor) throws PersistitException {
121 assertCorrectThread(true);
122 _persistit.checkClosed();
123-
124 final Key spareKey = _spareKey1;
125 final boolean doFetch = minimumBytes > 0;
126 final boolean doModify = minimumBytes >= 0;
127@@ -2038,8 +2081,9 @@
128 final Value outValue = doFetch ? _value : _spareValue;
129 outValue.clear();
130
131+ Direction dir = direction;
132 Buffer buffer = null;
133- boolean edge = direction == EQ || direction == GTEQ || direction == LTEQ;
134+ boolean edge = dir == EQ || dir == GTEQ || dir == LTEQ;
135 boolean nudged = false;
136
137 if (_key.getEncodedSize() == 0) {
138@@ -2121,11 +2165,11 @@
139 matches = true;
140 } else if (edge && !deep && Buffer.decodeDepth(foundAt) == index) {
141 matches = true;
142- } else if (direction == EQ) {
143+ } else if (dir == EQ) {
144 matches = false;
145 } else {
146 edge = false;
147- foundAt = buffer.traverse(_key, direction, foundAt);
148+ foundAt = buffer.traverse(_key, dir, foundAt);
149 if (buffer.isAfterRightEdge(foundAt)) {
150 final long rightSiblingPage = buffer.getRightSibling();
151
152@@ -2139,7 +2183,7 @@
153 //
154 buffer = rightSibling;
155 checkPageType(buffer, PAGE_TYPE_DATA, false);
156- foundAt = buffer.traverse(_key, direction, buffer.toKeyBlock(0));
157+ foundAt = buffer.traverse(_key, dir, buffer.toKeyBlock(0));
158 matches = !buffer.isAfterRightEdge(foundAt);
159 } else {
160 matches = false;
161@@ -2189,14 +2233,14 @@
162 matches = false;
163 } else {
164 if (deep) {
165- matches |= direction != EQ;
166+ matches |= dir != EQ;
167 index = _key.getEncodedSize();
168
169 if (matches) {
170 matches = fetchFromBufferInternal(buffer, outValue, foundAt, minimumBytes);
171- if (!matches && direction != EQ) {
172+ if (!matches && dir != EQ) {
173 nudged = false;
174- nudgeForMVCC = (direction == GTEQ || direction == LTEQ);
175+ nudgeForMVCC = (dir == GTEQ || dir == LTEQ);
176 buffer.release();
177 buffer = null;
178 continue;
179@@ -2225,10 +2269,10 @@
180 nudged = false;
181 buffer.release();
182 buffer = null;
183- if (direction == EQ) {
184+ if (dir == EQ) {
185 matches = false;
186 } else {
187- nudgeForMVCC = (direction == GTEQ || direction == LTEQ);
188+ nudgeForMVCC = (dir == GTEQ || dir == LTEQ);
189 continue;
190 }
191 }
192@@ -2284,6 +2328,18 @@
193 // Done
194 _volume.getStatistics().bumpTraverseCounter();
195 _tree.getStatistics().bumpTraverseCounter();
196+ if (matches && visitor != null && visitor.visit(this)) {
197+ nudged = false;
198+ edge = false;
199+ if (dir == GTEQ) {
200+ dir = GT;
201+ } else if (dir == LTEQ) {
202+ dir = LT;
203+ } else if (dir == EQ) {
204+ return false;
205+ }
206+ continue;
207+ }
208 return matches;
209 }
210 } finally {
211@@ -2296,6 +2352,91 @@
212
213 /**
214 * <p>
215+ * Performs generalized tree traversal using a {@link TraverseVisitor}. The
216+ * direction value indicates whether to traverse forward or backward in
217+ * collation sequence and whether the key being sought must be strictly
218+ * greater than or less than the supplied key.
219+ * </p>
220+ * <p>
221+ * Unlike {@link #traverse(Key.Direction, boolean, int)}, this method does
222+ * not return each time a new key is encountered in the traversal. Instead,
223+ * the {@link TraverseVisitor#visit(ReadOnlyExchange)} method is called once
224+ * for each key. This method avoids performing initial verification of the
225+ * key value and usually avoids locking a <code>Buffer</code> for every
226+ * record returned. It may offer better performance in circumstances where a
227+ * long sequence of keys is being examined. Note that
228+ * <code>ReadOnlyExchange</code> is an interface implemented by this class
229+ * which supplies the subset of methods that may be used safely within the
230+ * visitor.
231+ * </p>
232+ * <p>
233+ * During the call the {@link Buffer} containing the key is locked with a
234+ * non-exclusive claim, and any thread attempting to update records in the
235+ * same <code>Buffer</code> will block. Therefore the <code>visit</code>
236+ * method must be written carefully. See
237+ * {@link TraverseVisitor#visit(ReadOnlyExchange)} for guidelines.
238+ * </p>
239+ * <p>
240+ * This method normally modifies both the <code>Key</code> and
241+ * <code>Value</code> fields of this <code>Exchange</code>: the
242+ * <code>Key</code> is modified to reflect the key found through traversal,
243+ * and the <code>Value</code> field is modified to contain the value
244+ * associated with that key. However, this behavior can be modified by the
245+ * <code>minimumBytes</code> parameter. If <code>minimumBytes</code> is less
246+ * than or equal to zero then only the <code>Key</code> is modified. If it
247+ * is greater than zero, then the traverse method may choose to populate
248+ * only the specified number of bytes of the <code>Value</code>.
249+ * </p>
250+ * <p>
251+ * The <code>direction</code> value must be one of:
252+ * <dl>
253+ * <dt>Key.GT:</dt>
254+ * <dd>Find the next key that is strictly greater than the supplied key. If
255+ * there is none, return false.</dd>
256+ * <dt>Key.GTEQ:</dt>
257+ * <dd>If the supplied key exists in the database, return that key;
258+ * otherwise find the next greater key and return it.</dd>
259+ * <dt>Key.EQ:</dt>
260+ * <dd>Return <code>true</code> iff the specified key exists in the
261+ * database. Does not update the Key.</dd>
262+ * <dt>Key.LT:</dt>
263+ * <dd>Find the next key that is strictly less than the supplied key. If
264+ * there is none, return false.</dd>
265+ * <dt>Key.LTEQ:</dt>
266+ * <dd>If the supplied key exists in the database, return that key;
267+ * otherwise find the next smaller key and return it.</dd>
268+ * </dl>
269+ * </p>
270+ *
271+ * @param direction
272+ * One of Key.GT, Key.GTEQ, Key.EQ, Key.LT or Key.LTEQ.
273+ *
274+ * @param deep
275+ * Determines whether the result should represent the next (or
276+ * previous) physical key in the <code>Tree</code> or should be
277+ * restricted to just the logical siblings of the current key.
278+ * (See <a href="Key.html#_keyChildren">Logical Key Children and
279+ * Siblings</a>).
280+ *
281+ * @param minimumBytes
282+ * The minimum number of bytes to fetch. See {@link #fetch(int)}.
283+ *
284+ * @param visitor
285+ * The application-supplied <code>TraverseVisitor</code>.
286+ *
287+ * @return <code>true</code> if additional keys remaining in the traversal
288+ * set, or <code>false</code> to indicate that keys are exhausted.
289+ *
290+ * @throws PersistitException
291+ */
292+
293+ public boolean traverse(final Direction direction, final boolean deep, final int minimumBytes,
294+ final TraverseVisitor visitor) throws PersistitException {
295+ return traverse(direction, deep, Math.max(0, minimumBytes), 0, 0, visitor);
296+ }
297+
298+ /**
299+ * <p>
300 * Performs generalized tree traversal constrained by a supplied
301 * {@link KeyFilter}. The direction value indicates whether to traverse
302 * forward or backward in collation sequence, and whether the key being
303@@ -2370,7 +2511,7 @@
304 }
305 if (keyFilter.isKeyPrefixFilter()) {
306 return traverse(direction, true, minBytes, keyFilter.getMinimumDepth(),
307- keyFilter.getKeyPrefixByteCount());
308+ keyFilter.getKeyPrefixByteCount(), null);
309 }
310 final boolean matched = traverse(direction, true, minBytes);
311 totalVisited += _keysVisitedDuringTraverse;
312@@ -2929,7 +3070,7 @@
313 assertCorrectThread(true);
314 _key.copyTo(_spareKey2);
315 final int size = _key.getEncodedSize();
316- final boolean result = traverse(GT, true, 0, _key.getDepth() + 1, size);
317+ final boolean result = traverse(GT, true, 0, _key.getDepth() + 1, size, null);
318 _spareKey2.copyTo(_key);
319 return result;
320 }
321@@ -3834,7 +3975,7 @@
322 _value.setPointerPageType(buffer.getPageType());
323 buffer.release();
324 buffer = null;
325- storeInternal(_spareKey2, _value, level + 1, Exchange.StoreOptions.NONE);
326+ storeInternal(_spareKey2, _value, level + 1, StoreOptions.NONE);
327 return true;
328 } finally {
329 _treeHolder.release();
330@@ -3887,6 +4028,7 @@
331 *
332 * @return The <code>Transaction</code> context for this thread.
333 */
334+ @Override
335 public Transaction getTransaction() {
336 assertCorrectThread(true);
337 return _transaction;
338
339=== added file 'src/main/java/com/persistit/ReadOnlyExchange.java'
340--- src/main/java/com/persistit/ReadOnlyExchange.java 1970-01-01 00:00:00 +0000
341+++ src/main/java/com/persistit/ReadOnlyExchange.java 2013-01-07 23:12:21 +0000
342@@ -0,0 +1,82 @@
343+/**
344+ * Copyright © 2012 Akiban Technologies, Inc. All rights reserved.
345+ *
346+ * This program and the accompanying materials are made available
347+ * under the terms of the Eclipse Public License v1.0 which
348+ * accompanies this distribution, and is available at
349+ * http://www.eclipse.org/legal/epl-v10.html
350+ *
351+ * This program may also be available under different license terms.
352+ * For more information, see www.akiban.com or contact licensing@akiban.com.
353+ *
354+ * Contributors:
355+ * Akiban Technologies, Inc.
356+ */
357+
358+package com.persistit;
359+
360+/**
361+ * Methods of the {@link Exchange} class that are safe to use within a
362+ * {@link Exchange.TraverseVisitor}.
363+ *
364+ * @author peter
365+ */
366+public interface ReadOnlyExchange {
367+
368+ /**
369+ * Return the {@link Key} associated with this <code>Exchange</code>.
370+ *
371+ * @return This <code>Key</code>.
372+ */
373+ public Key getKey();
374+
375+ /**
376+ * Return the {@link Value} associated with this <code>Exchange</code>.
377+ *
378+ * @return The <code>Value</code>.
379+ */
380+ public Value getValue();
381+
382+ /**
383+ * Return the {@link Volume} containing the data accessed by this
384+ * <code>Exchange</code>.
385+ *
386+ * @return The <code>Volume</code>.
387+ */
388+ public Volume getVolume();
389+
390+ /**
391+ * Return the {@link Tree} on which this <code>Exchange</code> operates.
392+ *
393+ * @return The <code>Tree</code>
394+ */
395+ public Tree getTree();
396+
397+ /**
398+ * Return the Persistit instance from which this Exchange was created.
399+ *
400+ * @return The <code>Persistit</code> instance.
401+ */
402+ public Persistit getPersistitInstance();
403+
404+ /**
405+ * Return the count of structural changes committed to the {@link Tree} on
406+ * which this <code>Exchange</code> operates. This count includes changes
407+ * committed by all Threads, including the current one. A structural change
408+ * is one in which at least one key is inserted or deleted. Replacement of
409+ * an existing value is not counted.
410+ *
411+ * @return The change count
412+ */
413+ public long getChangeCount();
414+
415+ /**
416+ * The transaction context for this Exchange. By default, this is the
417+ * transaction context of the current thread, and by default, all
418+ * <code>Exchange</code>s created by a thread share the same transaction
419+ * context.
420+ *
421+ * @return The <code>Transaction</code> context for this thread.
422+ */
423+ public Transaction getTransaction();
424+}
425
426=== modified file 'src/test/java/com/persistit/unit/ExchangeTest.java'
427--- src/test/java/com/persistit/unit/ExchangeTest.java 2012-10-15 18:08:24 +0000
428+++ src/test/java/com/persistit/unit/ExchangeTest.java 2013-01-07 23:12:21 +0000
429@@ -216,6 +216,7 @@
430 assertEquals(false, ex.traverse(Key.GTEQ, kf, 4));
431 assertEquals(false, ex.traverse(Key.LT, kf, 4));
432 assertEquals(false, ex.traverse(Key.LTEQ, kf, 4));
433+
434 }
435
436 @Test
437@@ -489,7 +490,6 @@
438 keyCheck(ex, deep ? "{1,2}" : "{1}");
439 assertEquals("Should be true", true, ex.traverse(Key.EQ, deep, 0));
440 keyCheck(ex, deep ? "{1,2}" : "{1}");
441-
442 }
443
444 private void keyCheck(final Exchange ex, final String expected) {
445
446=== added file 'src/test/java/com/persistit/unit/TraverseVisitorTest.java'
447--- src/test/java/com/persistit/unit/TraverseVisitorTest.java 1970-01-01 00:00:00 +0000
448+++ src/test/java/com/persistit/unit/TraverseVisitorTest.java 2013-01-07 23:12:21 +0000
449@@ -0,0 +1,166 @@
450+/**
451+ * Copyright © 2011-2012 Akiban Technologies, Inc. All rights reserved.
452+ *
453+ * This program and the accompanying materials are made available
454+ * under the terms of the Eclipse Public License v1.0 which
455+ * accompanies this distribution, and is available at
456+ * http://www.eclipse.org/legal/epl-v10.html
457+ *
458+ * This program may also be available under different license terms.
459+ * For more information, see www.akiban.com or contact licensing@akiban.com.
460+ *
461+ * Contributors:
462+ * Akiban Technologies, Inc.
463+ */
464+
465+package com.persistit.unit;
466+
467+import static org.junit.Assert.assertEquals;
468+
469+import java.util.concurrent.atomic.AtomicBoolean;
470+import java.util.concurrent.atomic.AtomicInteger;
471+
472+import org.junit.Test;
473+
474+import com.persistit.Exchange;
475+import com.persistit.Exchange.TraverseVisitor;
476+import com.persistit.Key;
477+import com.persistit.PersistitUnitTestCase;
478+import com.persistit.ReadOnlyExchange;
479+import com.persistit.exception.PersistitException;
480+
481+public class TraverseVisitorTest extends PersistitUnitTestCase {
482+
483+ private final AtomicInteger visited = new AtomicInteger();
484+ private final AtomicInteger visitLimit = new AtomicInteger(Integer.MAX_VALUE);
485+ private final AtomicBoolean reverse = new AtomicBoolean();
486+
487+ @Test
488+ public void simpleTraverseVisitor() throws PersistitException {
489+ final TraverseVisitor tv = new Exchange.TraverseVisitor() {
490+
491+ @Override
492+ public boolean visit(final ReadOnlyExchange ex) {
493+ visited.incrementAndGet();
494+ return visited.get() < visitLimit.get();
495+ }
496+ };
497+ doCombo(tv);
498+
499+ }
500+
501+ @Test
502+ public void traverseVisitorNonMutatingTraverseMethods() throws Exception {
503+
504+ final TraverseVisitor tv = new Exchange.TraverseVisitor() {
505+
506+ @Override
507+ public boolean visit(final ReadOnlyExchange ex) throws PersistitException {
508+ visited.incrementAndGet();
509+ final int v = visited.get();
510+ return v < visitLimit.get();
511+ }
512+ };
513+
514+ doCombo(tv);
515+ }
516+
517+ /**
518+ * Demonstrates that a TraverseVisitor can (carefully) modify the Key of a
519+ * supplied ReadOnlyExchange.
520+ *
521+ * @throws Exception
522+ */
523+ @Test
524+ public void testMutateKey() throws Exception {
525+ final TraverseVisitor tv = new Exchange.TraverseVisitor() {
526+
527+ @Override
528+ public boolean visit(final ReadOnlyExchange ex) throws PersistitException {
529+ visited.incrementAndGet();
530+ final int v = visited.get();
531+ if (reverse.get()) {
532+ final int k = ex.getKey().reset().decodeInt();
533+ ex.getKey().clear().append(k - 1).append(0);
534+ } else {
535+ ex.getKey().append("a");
536+ }
537+ return v < visitLimit.get();
538+ }
539+ };
540+
541+ doCombo(tv);
542+
543+ }
544+
545+ private void doCombo(final TraverseVisitor tv) throws PersistitException {
546+ final Exchange ex = _persistit.getExchange("persistit", "gogo", true);
547+ final String mockValue = createString(64);
548+ for (int i = 0; i < 1000; i++) {
549+ ex.clear().append(i);
550+ ex.getValue().put(mockValue);
551+ ex.store();
552+ }
553+ final int max = Integer.MAX_VALUE;
554+ doTraverse(ex, tv, false, Key.GT, max, 1000);
555+ doTraverse(ex, tv, false, Key.GT, max, 1000);
556+ doTraverse(ex, tv, false, Key.LT, max, 1000);
557+ doTraverse(ex, tv, false, Key.LT, max, 1000);
558+ doTraverse(ex, tv, false, Key.GT, 1, 1);
559+ doTraverse(ex, tv, false, Key.GT, 1, 1);
560+ doTraverse(ex, tv, false, Key.GT, 10, 10);
561+ doTraverse(ex, tv, false, Key.GT, 10, 10);
562+ doTraverse(ex, tv, false, Key.LT, 1, 1);
563+ doTraverse(ex, tv, false, Key.LT, 1, 1);
564+ doTraverse(ex, tv, false, Key.LT, 10, 10);
565+ doTraverse(ex, tv, false, Key.LT, 10, 10);
566+
567+ doTraverse(ex, tv, true, Key.GT, max, 1000);
568+ doTraverse(ex, tv, true, Key.GT, max, 1000);
569+ doTraverse(ex, tv, true, Key.LT, max, 1000);
570+ doTraverse(ex, tv, true, Key.LT, max, 1000);
571+ doTraverse(ex, tv, true, Key.GT, 1, 1);
572+ doTraverse(ex, tv, true, Key.GT, 1, 1);
573+ doTraverse(ex, tv, true, Key.GT, 10, 10);
574+ doTraverse(ex, tv, true, Key.GT, 10, 10);
575+ doTraverse(ex, tv, true, Key.LT, 1, 1);
576+ doTraverse(ex, tv, true, Key.LT, 1, 1);
577+ doTraverse(ex, tv, true, Key.LT, 10, 10);
578+ doTraverse(ex, tv, true, Key.LT, 10, 10);
579+
580+ doTraverse(ex, tv, false, Key.GTEQ, max, 1000);
581+ doTraverse(ex, tv, false, Key.GTEQ, max, 1000);
582+ doTraverse(ex, tv, false, Key.LTEQ, max, 1000);
583+ doTraverse(ex, tv, false, Key.LTEQ, max, 1000);
584+
585+ doTraverseFrom(1, ex, tv, false, Key.GT, max, 998);
586+ doTraverseFrom(1, ex, tv, false, Key.GTEQ, max, 999);
587+ doTraverseFrom(998, ex, tv, false, Key.LT, max, 998);
588+ doTraverseFrom(998, ex, tv, false, Key.LTEQ, max, 999);
589+
590+ doTraverseFrom(500, ex, tv, false, Key.EQ, max, 1);
591+ doTraverseFrom(1001, ex, tv, false, Key.EQ, max, 0);
592+ }
593+
594+ private void doTraverse(final Exchange ex, final TraverseVisitor tv, final boolean deep,
595+ final Key.Direction direction, final int limit, final int expected) throws PersistitException {
596+ ex.clear().append(direction == Key.LT || direction == Key.LTEQ ? Key.AFTER : Key.BEFORE);
597+ doTraverse0(ex, tv, deep, direction, limit, expected);
598+ }
599+
600+ private void doTraverseFrom(final int from, final Exchange ex, final TraverseVisitor tv, final boolean deep,
601+ final Key.Direction direction, final int limit, final int expected) throws PersistitException {
602+ ex.clear().append(from);
603+ doTraverse0(ex, tv, deep, direction, limit, expected);
604+ }
605+
606+ private void doTraverse0(final Exchange ex, final TraverseVisitor tv, final boolean deep,
607+ final Key.Direction direction, final int limit, final int expected) throws PersistitException {
608+ reverse.set(direction == Key.LT || direction == Key.LTEQ);
609+ visited.set(0);
610+ visitLimit.set(limit);
611+ assertEquals(limit < 1000, ex.traverse(direction, deep, Integer.MAX_VALUE, tv));
612+ assertEquals(expected, visited.get());
613+ }
614+
615+}

Subscribers

People subscribed via source and target branches