/** * Copyright 2011-2012 Akiban Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.persistit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.junit.Test; import com.persistit.exception.PersistitException; public class MVCCBasicTest extends MVCCTestBase { private static final String KEY1 = "k1"; private static final String KEY2 = "k2"; private static final long VALUE1 = 12345L; private static final long VALUE2 = 67890L; @Test public void testTwoTrxDifferentTimestamps() throws PersistitException { trx1.begin(); trx2.begin(); try { assertFalse("differing start timestamps", trx1.getStartTimestamp() == trx2.getStartTimestamp()); trx1.commit(); trx2.commit(); } finally { trx1.end(); trx2.end(); } } @Test public void testSingleTrxWriteAndRead() throws Exception { trx1.begin(); try { store(ex1, KEY1, VALUE1); assertEquals("fetch before commit", VALUE1, fetch(ex1, KEY1)); trx1.commit(); } finally { trx1.end(); } trx1.begin(); try { assertEquals("fetch after commit", VALUE1, fetch(ex1, KEY1)); trx1.commit(); } finally { trx1.end(); } } @Test public void testTwoTrxDistinctWritesOverlappedReads() throws Exception { trx1.begin(); trx2.begin(); try { store(ex1, KEY1, VALUE1); store(ex2, KEY2, VALUE2); fetch(ex2, KEY1, false); assertFalse("trx2 sees uncommitted trx1 value", ex2.getValue().isDefined()); fetch(ex1, KEY2, false); assertFalse("trx1 sees uncommitted trx2 value", ex1.getValue().isDefined()); trx1.commit(); fetch(ex2, KEY1, false); assertFalse("trx2 sees committed trx1 from future", ex2.getValue().isDefined()); trx2.commit(); } finally { trx1.end(); trx2.end(); } // Both should see both now trx1.begin(); trx2.begin(); try { assertEquals("original trx1 value from new trx1", VALUE1, fetch(ex1, KEY1)); assertEquals("original trx2 value from new trx1", VALUE2, fetch(ex1, KEY2)); trx1.commit(); assertEquals("original trx1 value from new trx2", VALUE1, fetch(ex2, KEY1)); assertEquals("original trx2 value from new trx2", VALUE2, fetch(ex2, KEY2)); trx2.commit(); } finally { trx1.end(); trx2.end(); } } @Test public void testSingleTrxManyInserts() throws Exception { // Enough for a new index level and many splits final int INSERT_COUNT = 5000; for (int i = 0; i < INSERT_COUNT; ++i) { trx1.begin(); try { store(ex1, i, i * 2); trx1.commit(); } finally { trx1.end(); } } trx1.begin(); try { for (int i = 0; i < INSERT_COUNT; ++i) { assertEquals(i * 2, fetch(ex1, i)); } trx1.commit(); } finally { trx1.end(); } } @Test public void testSingleTrxMultipleLongRecordVersions() throws Exception { final int VERSIONS_TO_STORE = 5; final String longStr = createString(ex1.getVolume().getPageSize()); for (int curVer = 0; curVer < VERSIONS_TO_STORE; ++curVer) { trx1.begin(); try { store(ex1, curVer, longStr); ex1.getValue().clear(); ex1.fetch(); assertEquals("key after fetch pre-commit", curVer, ex1.getKey().decodeInt()); assertEquals("value after fetch pre-commit", longStr, ex1.getValue().getString()); trx1.commit(); } finally { trx1.end(); } } for (int curVer = 0; curVer < VERSIONS_TO_STORE; ++curVer) { trx1.begin(); try { fetch(ex1, curVer, false); assertEquals("fetched key post-commit", curVer, ex1.getKey().decodeInt()); assertEquals("fetched value post-commit", longStr, ex1.getValue().getString()); trx1.commit(); } finally { trx1.end(); } } } /* * Store dozens of small, unique versions of a single key to result in * resulting in a LONG MVV value. Check etch pre and post commit. */ @Test public void testLongMVVFromManySmall() throws Exception { final int PER_LENGTH = 250; final String smallStr = createString(PER_LENGTH); final int versionCount = (int) ((ex1.getVolume().getPageSize() / PER_LENGTH) * 1.1); for (int i = 1; i <= versionCount; ++i) { trx1.begin(); try { final String value = smallStr + i; store(ex1, KEY1, value); assertEquals("value pre-commit version " + i, value, fetch(ex1, KEY1)); trx1.commit(); trx1.end(); trx1.begin(); assertEquals("value post-commit version " + i, value, fetch(ex1, KEY1)); trx1.commit(); } finally { trx1.end(); } } } /* * Store multiple unique versions of a single key, with individual versions * are both short and long records, resulting in a LONG MVV value. Check * fetch pre and post commit. */ @Test public void testLongMVVFromManySmallAndLong() throws Exception { final int pageSize = ex1.getVolume().getPageSize(); final String longStr = createString(pageSize); final double[] valueLengths = { pageSize * 0.05, 10, pageSize * 0.80, 0, pageSize * 0.20, 25, pageSize * 0.40, 10, pageSize * 0.10, 45, }; for (int i = 0; i < valueLengths.length; ++i) { trx1.begin(); try { final int length = (int) valueLengths[i]; final String value = longStr.substring(0, length); store(ex1, KEY1, value); assertEquals("value pre-commit version " + i, value, fetch(ex1, KEY1)); trx1.commit(); trx1.end(); trx1.begin(); assertEquals("value post-commit version " + i, value, fetch(ex1, KEY1)); trx1.commit(); } finally { trx1.end(); } } } @Test public void testIsValuedDefinedTwoTrx() throws Exception { trx1.begin(); trx2.begin(); try { store(ex1, "trx1", 1); store(ex2, "trx2", 2); assertFalse("trx1 sees uncommitted trx2 key", ex1.clear().append("trx2").isValueDefined()); assertFalse("trx2 sees uncommitted trx2 key", ex2.clear().append("trx1").isValueDefined()); trx1.commit(); trx2.commit(); } finally { trx1.end(); } trx1.begin(); try { assertTrue("committed trx1 key", ex1.clear().append("trx1").isValueDefined()); assertTrue("committed trx2 key", ex1.clear().append("trx2").isValueDefined()); trx1.commit(); } finally { trx1.end(); } } @Test public void testTraverseShallowTwoTrx() throws Exception { final List<KVPair> baseList = kvList("a", "A", "z", "Z"); trx1.begin(); try { storeAll(ex1, baseList); trx1.commit(); } finally { trx1.end(); } List<KVPair> trx1List = kvList("d", "D", "trx1", 111, "x", "X"); List<KVPair> trx2List = kvList("b", "B", "c", "C", "trx2", 222); trx1.begin(); trx2.begin(); try { storeAll(ex1, trx1List); storeAll(ex2, trx2List); storeAll(ex1, kvList(arr("e", "trx1"), 1, arr("h", "trx1"), 11)); storeAll(ex2, kvList(arr("f", "trx2"), 2, arr("g", "trx2"), 22)); trx1List.addAll(kvList("e", "UD", "h", "UD")); trx2List.addAll(kvList("f", "UD", "g", "UD")); trx1List = combine(trx1List, baseList); trx2List = combine(trx2List, baseList); assertEquals("trx1 forward,shallow traversal", trx1List, traverseAllFoward(ex1, false)); assertEquals("trx2 forward,shallow traversal", trx2List, traverseAllFoward(ex2, false)); Collections.reverse(trx1List); Collections.reverse(trx2List); assertEquals("trx1 reverse,shallow traversal", trx1List, traverseAllReverse(ex1, false)); assertEquals("trx2 reverse,shallow traversal", trx2List, traverseAllReverse(ex2, false)); trx1.commit(); trx2.commit(); } finally { trx1.end(); trx2.end(); } trx1.begin(); try { final List<KVPair> fList = combine(trx1List, trx2List); assertEquals("final forward,shallow traversal", fList, traverseAllFoward(ex1, false)); Collections.reverse(fList); assertEquals("final reverse,shallow traversal", fList, traverseAllReverse(ex1, false)); trx1.commit(); } finally { trx1.end(); } } @Test public void testTraverseDeepTwoTrx() throws Exception { final List<KVPair> baseList = kvList("a", "A", "z", "Z"); trx1.begin(); try { storeAll(ex1, baseList); trx1.commit(); } finally { trx1.end(); } List<KVPair> trx1List = kvList(arr("b", "trx1"), 1, arr("d", "trx1"), 11, "trx1", 111); List<KVPair> trx2List = kvList(arr("b", "trx2"), 2, arr("c", "trx2"), 22, "trx2", 222); trx1.begin(); trx2.begin(); try { storeAll(ex1, trx1List); storeAll(ex2, trx2List); trx1List = combine(trx1List, baseList); trx2List = combine(trx2List, baseList); assertEquals("trx1 forward,deep traversal", trx1List, traverseAllFoward(ex1, true)); assertEquals("trx2 forward,deep traversal", trx2List, traverseAllFoward(ex2, true)); Collections.reverse(trx1List); Collections.reverse(trx2List); assertEquals("trx1 reverse,deep traversal", trx1List, traverseAllReverse(ex1, true)); assertEquals("trx2 reverse,deep traversal", trx2List, traverseAllReverse(ex2, true)); trx1.commit(); trx2.commit(); } finally { trx1.end(); trx2.end(); } trx1.begin(); try { final List<KVPair> fList = combine(trx1List, trx2List); assertEquals("final forward,deep traversal", fList, traverseAllFoward(ex1, true)); Collections.reverse(fList); assertEquals("final reverse,deep traversal", fList, traverseAllReverse(ex1, true)); trx1.commit(); } finally { trx1.end(); } } @Test public void testTwoTrxManyTraverseManyKeys() throws Exception { final int MIN_PAGES = 6; final int MAX_KV_PER_PAGE = ex1.getVolume().getPageSize() / (8 + 14); // ##,trxX // => // MVV,VER,LEN,## final int KVS_PER_TRX = (MIN_PAGES * MAX_KV_PER_PAGE) / 2; final int TOTAL_KVS = KVS_PER_TRX * 2; trx1.begin(); trx2.begin(); try { for (int i = 0; i < TOTAL_KVS; ++i) { if (i % 2 == 0) { store(ex1, i, "trx1", i); } else { store(ex2, i, "trx2", i); } } final Exchange[] exchanges = { ex1, ex1 }; final Key.Direction[] directions = { Key.GT, Key.LT }; final boolean[] deepFlags = { true, false }; for (final Exchange ex : exchanges) { final String expectedSeg2 = (ex == ex1) ? "trx1" : "trx2"; final Key key = ex.getKey(); final Value value = ex.getValue(); for (final Key.Direction dir : directions) { final Key.EdgeValue startEdge = (dir == Key.GT) ? Key.BEFORE : Key.AFTER; for (final boolean deep : deepFlags) { final String desc = expectedSeg2 + " " + dir + " " + (deep ? "deep" : "shallow") + ", "; int traverseCount = 0; ex.clear().append(startEdge); while (ex.traverse(dir, deep)) { ++traverseCount; if (deep) { assertEquals(desc + "key depth", 2, key.getDepth()); final int keySeg1 = key.indexTo(0).decodeInt(); final String keySeg2 = key.indexTo(1).decodeString(); final int val = value.getInt(); assertEquals(desc + "key seg1 equals value", keySeg1, val); assertEquals(desc + "key seg2", expectedSeg2, keySeg2); } else { assertEquals(desc + "key depth", 1, key.getDepth()); assertEquals(desc + "value defined", false, value.isDefined()); } } assertEquals(desc + "traverse count", KVS_PER_TRX, traverseCount); } } } trx1.commit(); trx2.commit(); } finally { trx1.end(); trx2.end(); } } /* * Simple sanity check as KeyFilter inspects the keys but doesn't care, * directly, about MVCC */ @Test public void testKeyFilterTraverseTwoTrx() throws Exception { trx1.begin(); trx2.begin(); try { final List<KVPair> trx1List = kvList("a", "A", "c", "C", "e", "E", "f", "f", "i", "I"); final List<KVPair> trx2List = kvList("b", "B", "d", "D", "g", "G", "h", "H", "j", "J"); storeAll(ex1, trx1List); storeAll(ex2, trx2List); final KeyFilter filter = new KeyFilter(new KeyFilter.Term[] { KeyFilter.rangeTerm("b", "i") }); trx1List.remove(0); trx2List.remove(trx2List.size() - 1); assertEquals("trx1 forward filter traversal", trx1List, doTraverse(Key.BEFORE, ex1, Key.GT, filter)); assertEquals("trx2 forward filter traversal", trx2List, doTraverse(Key.BEFORE, ex2, Key.GT, filter)); Collections.reverse(trx1List); Collections.reverse(trx2List); assertEquals("trx1 reverse filter traversal", trx1List, doTraverse(Key.AFTER, ex1, Key.LT, filter)); assertEquals("trx2 reverse filter traversal", trx2List, doTraverse(Key.AFTER, ex2, Key.LT, filter)); trx1.commit(); trx2.commit(); } finally { trx1.end(); } } /* * Bug found independently of MVCC but fixed due to traverse() changes */ @Test public void testShallowTraverseWrongParentValueBug() throws Exception { trx1.begin(); try { final List<KVPair> kvList = kvList("a", "A", "b", "B", "z", "Z"); storeAll(ex1, kvList); store(ex1, "a", "a", "AA"); assertEquals("forward traversal", kvList, traverseAllFoward(ex1, false)); Collections.reverse(kvList); assertEquals("reverse traversal", kvList, traverseAllReverse(ex1, false)); trx1.commit(); } finally { trx1.end(); } } @Test public void testSingleTrxStoreRemoveFetch() throws Exception { trx1.begin(); try { store(ex1, KEY1, VALUE1); assertEquals("fetched value pre-remove pre-commit", VALUE1, fetch(ex1, KEY1)); assertTrue("key existed pre-remove", remove(ex1, KEY1)); fetch(ex1, KEY1, false); assertFalse("fetched value defined post-remove pre-commit", ex1.getValue().isDefined()); ex1.clear().append(KEY1); assertFalse("key defined post-remove pre-commit", ex1.isValueDefined()); trx1.commit(); } finally { trx1.end(); } trx1.begin(); try { fetch(ex1, KEY1, false); assertFalse("fetched value defined post-remove pre-commit", ex1.getValue().isDefined()); ex1.clear().append(KEY1); assertFalse("key defined post-remove pre-commit", ex1.isValueDefined()); trx1.commit(); } finally { trx1.end(); } } @Test public void testRemovedKeysHaveChildrenBug() throws Exception { final List<KVPair> keepList = kvList(arr("a", 1), "A1", arr("c", 2), "C2"); final List<KVPair> removeList = kvList(arr("b", 1), "B1", arr("b", 2), "B2", arr("b", 3), "B3", arr("c", 1), "C1"); trx1.begin(); try { storeAll(ex1, keepList); storeAll(ex1, removeList); trx1.commit(); } finally { trx1.end(); } // concurrent transaction to prevent pruning of originals trx1.begin(); try { trx2.begin(); try { final Key key = ex2.getKey(); key.clear().append("b").append(1); assertEquals(key + " initially exists", true, ex2.isValueDefined()); key.clear().append("b"); assertEquals(key + " initially has children", true, ex2.hasChildren()); removeAll(ex2, removeList); key.clear().append("b").append(1); assertEquals(key + " exists after removal", false, ex2.isValueDefined()); key.clear().append("b"); assertEquals(key + " has children after removal", false, ex2.hasChildren()); trx2.commit(); } finally { trx2.end(); } trx1.commit(); } finally { trx1.end(); } } @Test public void testTwoTrxRemoveRanges() throws Exception { final List<KVPair> bothList = kvList("a", "A", "m", "M", "z", "Z"); trx1.begin(); try { storeAll(ex1, bothList); trx1.commit(); } finally { trx1.end(); } final Key ka = new Key(_persistit); final Key kb = new Key(_persistit); trx1.begin(); trx2.begin(); try { final List<KVPair> trx1List1 = kvList("b", "B", "e", "e", "f", "f", "x", "X"); storeAll(ex1, trx1List1); final List<KVPair> trx2List = kvList("d", "D", "n", "N", "v", "V", "y", "Y"); storeAll(ex2, trx2List); // Explicitly testing overlapping ranges, as the overlaps should // not be visible to each other ka.clear().append("b"); kb.clear().append("v"); assertTrue("trx1 keys removed", ex1.removeKeyRange(ka, kb)); final List<KVPair> trx1List2 = kvList("a", "A", "x", "X", "z", "Z"); assertEquals("trx1 traverse post removeKeyRange", trx1List2, traverseAllFoward(ex1, true)); assertEquals("trx2 traverse post trx1 removeKeyRange", combine(bothList, trx2List), traverseAllFoward(ex2, true)); ka.clear().append("n"); kb.clear().append(Key.AFTER); assertTrue("trx2 keys removed", ex2.removeKeyRange(ka, kb)); assertEquals("trx2 traverse post removeAll", kvList("a", "A", "d", "D", "m", "M"), traverseAllFoward(ex2, true)); assertEquals("trx1 traverse post trx2 removeAll", trx1List2, traverseAllFoward(ex1, true)); trx1.commit(); trx2.commit(); } finally { trx1.end(); trx2.end(); } trx1.begin(); try { assertEquals("traverse post-commit", kvList("a", "A", "d", "D", "x", "X"), traverseAllFoward(ex1, true)); trx1.commit(); } finally { trx1.end(); } } @Test public void testRemoveWithSplitsSmall() throws Exception { final int keyCount = _persistit.getBufferPool(ex1.getVolume().getPageSize()).getMaxKeys(); insertRemoveAllAndVerify(keyCount); } @Test public void testRemoveWithSplitsMedium() throws Exception { final int keyCount = _persistit.getBufferPool(ex1.getVolume().getPageSize()).getMaxKeys() * 5; insertRemoveAllAndVerify(keyCount); } @Test public void testRemoveWithSplitsLarge() throws Exception { final int keyCount = _persistit.getBufferPool(ex1.getVolume().getPageSize()).getMaxKeys() * 10; insertRemoveAllAndVerify(keyCount); } private void insertRemoveAllAndVerify(final int keyCount) throws Exception { trx1.begin(); try { for (int i = 0; i < keyCount; ++i) { ex1.getValue().clear(); ex1.clear().append(String.format("%05d", i)).store(); } trx1.commit(); } finally { trx1.end(); } trx1.begin(); try { assertEquals("traversed count initial", keyCount, traverseAllFoward(ex1, true).size()); ex1.removeAll(); assertEquals("traversed count post-remove pre-commit", 0, traverseAllFoward(ex1, true).size()); trx1.commit(); } finally { trx1.end(); } trx1.begin(); try { assertEquals("traverse post-remove post-commit", 0, traverseAllFoward(ex1, true).size()); trx1.commit(); } finally { trx1.end(); } } @Test public void testKeysVisitedDuringTraverse() throws PersistitException { final int TOTAL_DEPTH_1 = 10; final int TOTAL_DEPTH_2 = 5; trx1.begin(); try { int curKey = 0; for (int d1 = 0; d1 < TOTAL_DEPTH_1; ++d1) { final String s = String.valueOf((char) ('a' + d1)); ex1.clear().append(s); ex1.getValue().clear().put(s.toUpperCase()); ex1.store(); for (int d2 = 1; d2 <= TOTAL_DEPTH_2; ++d2) { ex1.setDepth(1); ex1.append(d2); ex1.getValue().clear().put(++curKey); ex1.store(); } } trx1.commit(); } finally { trx1.end(); } trx1.begin(); try { ex1.clear().append("a"); assertEquals("'a' hasChildren", true, ex1.hasChildren()); assertEquals("keys traversed for 'a' hasChildren", 1, ex1.getKeysVisitedDuringTraverse()); ex1.clear().append("a").append(TOTAL_DEPTH_2); assertEquals("'a,1' hasChildren", false, ex1.hasChildren()); assertEquals("keys traversed for 'a' hasChildren", 1, ex1.getKeysVisitedDuringTraverse()); // Remove everything between A and J final Key removeBegin = new Key(_persistit); removeBegin.append("a").nudgeDeeper(); final Key removeEnd = new Key(_persistit); removeEnd.append("j"); ex1.removeKeyRange(removeBegin, removeEnd); // Can stop when we hit first sibling (depth < traverse minDepth) ex1.clear().append("a"); assertEquals("'a' hasChildren after remove", false, ex1.hasChildren()); assertEquals("keys traversed for 'a' hasChildren post-remove", TOTAL_DEPTH_2 + 1, ex1.getKeysVisitedDuringTraverse()); // Should be able to stop when first (depth < traverse minDepth) ex1.clear().append("a").append(TOTAL_DEPTH_2); assertEquals("'a,1' hasChildren after remove", false, ex1.hasChildren()); assertEquals("keys traversed for 'a' hasChildren post-remove", 1, ex1.getKeysVisitedDuringTraverse()); // Same optimization test, by way of specially known KeyFilter ex1.clear().append("a"); final KeyFilter filter1 = new KeyFilter(ex1.getKey(), ex1.getKey().getDepth() + 1, Integer.MAX_VALUE); assertEquals("traverse w/filter1 found key post-remove", false, ex1.traverse(Key.GT, filter1, Integer.MAX_VALUE)); assertEquals("keys traversed with filter1 post-remove", TOTAL_DEPTH_2 + 1, ex1.getKeysVisitedDuringTraverse()); // If not using the 'special' KeyFilter, we can't exit traverse // early final KeyFilter filter2 = new KeyFilter(new KeyFilter.Term[] { KeyFilter.simpleTerm("a") }, 2, Integer.MAX_VALUE); assertEquals("traverse w/filter2 found key post-remove", false, ex1.traverse(Key.GT, filter2, Integer.MAX_VALUE)); // All depth1 and depth2 in range ('a','j') final int expectedKeys = (TOTAL_DEPTH_1 - 1) * TOTAL_DEPTH_2 + TOTAL_DEPTH_1 - 1; assertEquals("keys traversed with filter2 post-remove", expectedKeys, ex1.getKeysVisitedDuringTraverse()); trx1.commit(); } finally { trx1.end(); } } /* * Make sure traverse() exits as soon as possible even when the keys are not * parent/child segments but the search key is just truncated. */ @Test public void testKeysVisitedDuringTraverseUniformDepth() throws PersistitException { final int KEY_COUNT = 50; trx1.begin(); try { for (int i = 1; i <= KEY_COUNT; ++i) { ex1.clear().append(i).append(i); ex1.store(); } trx1.commit(); } finally { trx1.end(); } trx1.begin(); try { ex1.clear().append(10); assertEquals("'10' hasChildren", true, ex1.hasChildren()); assertEquals("keys traversed for '10' hasChildren", 1, ex1.getKeysVisitedDuringTraverse()); ex1.clear().append(10).append(10); assertEquals("'10,10' hasChildren", false, ex1.hasChildren()); assertEquals("keys traversed for '10,10' hasChildren", 1, ex1.getKeysVisitedDuringTraverse()); ex1.clear().append(10); final KeyFilter filter1 = new KeyFilter(ex1.getKey(), ex1.getKey().getDepth() + 1, Integer.MAX_VALUE); assertEquals("traverse w/filter found key", true, ex1.traverse(Key.GT, filter1, Integer.MAX_VALUE)); assertEquals("keys traversed w/filter", 1, ex1.getKeysVisitedDuringTraverse()); // Remove everything between 10,10 and 40,40 final Key removeBegin = new Key(_persistit); removeBegin.append(10).append(10); final Key removeEnd = new Key(_persistit); removeEnd.append(40).append(40); ex1.removeKeyRange(removeBegin, removeEnd); ex1.clear().append(10); assertEquals("'10' hasChildren post-remove", false, ex1.hasChildren()); assertEquals("keys traversed for '10' hasChildren post-remove", 2, ex1.getKeysVisitedDuringTraverse()); ex1.clear().append(10).append(10); assertEquals("'10,10' hasChildren", false, ex1.hasChildren()); assertEquals("keys traversed for '10,10' hasChildren post-remove", 1, ex1.getKeysVisitedDuringTraverse()); ex1.clear().append(10); final KeyFilter filter2 = new KeyFilter(ex1.getKey(), ex1.getKey().getDepth() + 1, Integer.MAX_VALUE); assertEquals("traverse filter found key post-remove", false, ex1.traverse(Key.GT, filter2, Integer.MAX_VALUE)); assertEquals("keys traversed w/filter post-remove", 2, ex1.getKeysVisitedDuringTraverse()); trx1.commit(); } finally { trx1.end(); } } @Test public void testRedundantRemoveReturnValue() throws PersistitException { trx1.begin(); try { store(ex1, KEY1, VALUE1); assertEquals("fetch after store", VALUE1, fetch(ex1, KEY1)); trx1.commit(); } finally { trx1.end(); } trx1.begin(); try { assertEquals("fetch from new trx after commit", VALUE1, fetch(ex1, KEY1)); assertEquals("key was removed first time", true, remove(ex1, KEY1)); assertEquals("key is defined after remove", false, ex1.clear().append(KEY1).isValueDefined()); ex1.clear().append(KEY1).fetch(); assertEquals("value is defined after remove", false, ex1.getValue().isDefined()); assertEquals("key was removed second time", false, remove(ex1, KEY1)); trx1.commit(); } finally { trx1.end(); } trx1.begin(); try { assertEquals("key is defined from new trx after remove", false, ex1.clear().append(KEY1).isValueDefined()); assertEquals("value is defined from new trx after remove", false, ex1.getValue().isDefined()); assertEquals("key was removed from new trx", false, remove(ex1, KEY1)); trx1.commit(); } finally { trx1.end(); } } @Test public void testSimpleTransactionStepUsage() throws Exception { final int KEY_COUNT = 20; final List<KVPair> baseList = kvList(); final List<KVPair> secondList = kvList(); for (int i = 0; i < KEY_COUNT; ++i) { final int value = i * 10; baseList.add(new KVPair(i, null, value)); secondList.add(new KVPair(i + KEY_COUNT, null, value + 1)); } // Store originals trx1.begin(); try { storeAll(ex1, baseList); trx1.commit(); } finally { trx1.end(); } // Traverse and update at the same time, toggling step back and forth // This emulates the server usage by the operators final List<KVPair> traversedList = kvList(); trx1.begin(); Exchange storeEx = null; try { final Iterator<KVPair> insertIt = secondList.iterator(); _persistit.setSessionId(trx1.getSessionId()); storeEx = _persistit.getExchange(TEST_VOLUME_NAME, TEST_TREE_NAME, false); ex1.clear().append(Key.BEFORE); while (ex1.next()) { traversedList.add(new KVPair(ex1.getKey().decodeInt(), null, ex1.getValue().getInt())); if (insertIt.hasNext()) { final int prevStep = trx1.incrementStep(); final KVPair pair = insertIt.next(); store(storeEx, pair.k1, pair.v); trx1.setStep(prevStep); } } assertEquals("only traversed original keys", baseList, traversedList); trx1.commit(); } finally { if (storeEx != null) { _persistit.releaseExchange(storeEx); } trx1.end(); } trx1.begin(); try { final List<KVPair> combined = combine(baseList, secondList); final List<KVPair> traversed = traverseAllFoward(ex1, true); assertEquals("traversed all after commit", combined, traversed); trx1.commit(); } finally { trx1.end(); } } @Test public void testTransactionStepTraverseBeforeCommit() throws Exception { final int KEY_COUNT = 10; final List<KVPair> originalList = kvList(); final List<KVPair> updatedList = kvList(); for (int i = 0; i < KEY_COUNT; ++i) { originalList.add(new KVPair(i, null, i * 10)); updatedList.add(new KVPair(i + KEY_COUNT, null, i * 10 + 1)); } Exchange storeEx = null; trx1.begin(); try { storeEx = createExchange(trx1); trx1.setStep(0); storeAll(ex1, originalList); final List<KVPair> traversedStep0 = kvList(); final Iterator<KVPair> updatedIt = updatedList.iterator(); ex1.clear().append(Key.BEFORE); while (ex1.next()) { traversedStep0.add(new KVPair(ex1.getKey().decodeInt(), null, ex1.getValue().getInt())); if (updatedIt.hasNext()) { final int prevStep = trx1.incrementStep(); ex1.remove(); final KVPair pair = updatedIt.next(); store(storeEx, pair.k1, pair.v); trx1.setStep(prevStep); } } trx1.setStep(1); final List<KVPair> traversedStep1 = traverseAllFoward(ex1, true); trx1.setStep(2); final List<KVPair> traversedStep2 = traverseAllFoward(ex1, true); assertEquals("traversed only originals from step 0", originalList, traversedStep0); assertEquals("traversed only originals from step 1", updatedList, traversedStep1); assertEquals("traversed only updated from step 2", updatedList, traversedStep2); trx1.commit(); } finally { releaseExchange(storeEx); trx1.end(); } trx1.begin(); try { final List<KVPair> traversed = traverseAllFoward(ex1, true); assertEquals("traversed only updated after commit", updatedList, traversed); trx1.commit(); } finally { trx1.end(); } } @Test public void testUnorderedStepStoreAndFetch() throws PersistitException { final int STEP_COUNT = 10; final int stepOrder[] = { 8, 7, 9, 4, 6, 2, 0, 1, 3, 5 }; assertEquals("step order array size", STEP_COUNT, stepOrder.length); trx1.begin(); try { for (final int step : stepOrder) { trx1.setStep(step); store(ex1, KEY1, step); } for (int i = 0; i < STEP_COUNT; ++i) { trx1.setStep(i); fetch(ex1, KEY1, false); assertEquals("fetched value from step " + i, i, ex1.getValue().getInt()); } trx1.commit(); } finally { trx1.end(); } } @Test public void testHigherVersionWithLowerStep() throws PersistitException { trx1.begin(); try { trx1.setStep(5); store(ex1, KEY1, VALUE1); assertEquals("fetch after store from trx1, step 5", VALUE1, fetch(ex1, KEY1)); trx1.commit(); } finally { trx1.end(); } // Concurrent txn to prevent prune trx1.begin(); try { trx2.begin(); try { trx2.setStep(0); store(ex2, KEY1, VALUE2); assertEquals("fetch after store from tx2, step 0", VALUE2, fetch(ex2, KEY1)); trx2.commit(); } finally { trx2.end(); } trx1.commit(); } finally { trx1.end(); } } @Test public void testStoreReallyLongRecord() throws PersistitException { // Enough that if the length portion of the MVV is signed we fail final String LONG_STR = createString(Short.MAX_VALUE + 2); trx1.begin(); try { store(ex1, KEY1, LONG_STR); assertEquals("fetched value", LONG_STR, fetch(ex1, KEY1)); trx1.commit(); } finally { trx1.end(); } trx1.begin(); try { assertEquals("fetched committed value", LONG_STR, fetch(ex1, KEY1)); trx1.commit(); } finally { trx1.end(); } } // // Test Helpers // private Exchange createExchange(final Transaction txn) throws PersistitException { _persistit.setSessionId(txn.getSessionId()); return _persistit.getExchange(TEST_VOLUME_NAME, TEST_TREE_NAME, true); } private void releaseExchange(final Exchange ex) { if (ex != null) { _persistit.releaseExchange(ex); } } }