/** * Copyright 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 com.persistit.Buffer.EXACT_MASK; import static com.persistit.Buffer.FIXUP_MASK; import static com.persistit.Buffer.KEYBLOCK_LENGTH; import static com.persistit.Buffer.P_MASK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Arrays; import org.junit.Test; import com.persistit.ValueHelper.RawValueWriter; import com.persistit.exception.PersistitException; import com.persistit.exception.RebalanceException; import com.persistit.policy.JoinPolicy; import com.persistit.policy.SplitPolicy; public class BufferTest2 extends PersistitUnitTestCase { Exchange ex; Buffer b1; Buffer b2; RawValueWriter vw; final StringBuilder sb = new StringBuilder(); @Override public void setUp() throws Exception { super.setUp(); ex = _persistit.getExchange("persistit", "BufferTest", true); b1 = ex.getBufferPool().get(ex.getVolume(), 1, true, false); b2 = ex.getBufferPool().get(ex.getVolume(), 2, true, false); while (sb.length() < Buffer.MAX_BUFFER_SIZE) { sb.append(RED_FOX); } vw = new RawValueWriter(); vw.init(ex.getValue()); } @Override public void tearDown() throws Exception { ex = null; b1 = null; b2 = null; vw = null; super.tearDown(); } @Test public void testPreviousKey() throws PersistitException { setUpPrettyFullBufferWithChangingEbc(RED_FOX.length()); ex.getKey().clear().append(Key.AFTER); for (int p = b1.getKeyBlockEnd(); p > 0; p -= KEYBLOCK_LENGTH) { ex.getKey().copyTo(ex.getAuxiliaryKey1()); final int result = b1.previousKey(ex.getKey(), p); if (p > b1.getKeyBlockStart()) { assertTrue("Key is out of sequence", ex.getKey().compareTo(ex.getAuxiliaryKey1()) < 0); assertEquals("Wrong result from previousKey", result & P_MASK, p - KEYBLOCK_LENGTH); } } } @Test public void testFindKey() throws PersistitException { setUpPrettyFullBufferWithChangingEbc(RED_FOX.length()); setUpDeepKey(10); int foundAt = b1.findKey(ex.getKey()); assertTrue("Must be an exact match", (foundAt & EXACT_MASK) != 0); ex.append("z"); foundAt = b1.findKey(ex.getKey()); assertFalse("Must not be an exact match", (foundAt & EXACT_MASK) != 0); } @Test public void testJoinRightBias1() throws PersistitException { setUpPrettyFullBuffers(Buffer.PAGE_TYPE_INDEX_MIN, 0, false); for (int c = b1.getKeyCount() - 1; c > 0; c -= 7) { setUpDeepKey(ex, 'a', c, 0); final int foundAt = b1.findKey(ex.getKey()); b1.removeKeys(foundAt, foundAt, ex.getAuxiliaryKey1()); } final boolean splitRight = b1.join(b2, b1.getKeyBlockEnd() - 4, b2.getKeyBlockStart() + 4, ex.getAuxiliaryKey1(), ex.getAuxiliaryKey2(), JoinPolicy.RIGHT_BIAS); assertTrue("Should have re-split", splitRight); assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); } @Test public void testJoinRightBias2() throws PersistitException { setUpPrettyFullBuffers(Buffer.PAGE_TYPE_INDEX_MIN, 0, false); for (int c = b1.getKeyCount() + 1; c < b1.getKeyCount() + b2.getKeyCount(); c += 19) { setUpDeepKey(ex, 'a', c, 0); final int foundAt = b2.findKey(ex.getKey()); b2.removeKeys(foundAt, foundAt, ex.getAuxiliaryKey1()); } final boolean splitRight = b1.join(b2, b1.getKeyBlockEnd() - 4, b2.getKeyBlockStart() + 4, ex.getAuxiliaryKey1(), ex.getAuxiliaryKey2(), JoinPolicy.RIGHT_BIAS); assertTrue("Should have re-split", splitRight); assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); } @Test public void testJoinLeftBias2() throws PersistitException { setUpPrettyFullBuffers(Buffer.PAGE_TYPE_INDEX_MIN, 0, false); for (int c = b1.getKeyCount() - 1; c > 0; c -= 7) { setUpDeepKey(ex, 'a', c, 0); final int foundAt = b1.findKey(ex.getKey()); b1.removeKeys(foundAt, foundAt, ex.getAuxiliaryKey1()); } final boolean splitLeft = b1.join(b2, b1.getKeyBlockEnd() - 4, b2.getKeyBlockStart() + 4, ex.getAuxiliaryKey1(), ex.getAuxiliaryKey2(), JoinPolicy.LEFT_BIAS); assertTrue("Should have re-split", splitLeft); assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); } @Test public void testJoinEvenBias1() throws PersistitException { setUpPrettyFullBuffers(Buffer.PAGE_TYPE_INDEX_MIN, 0, false); for (int c = b1.getKeyCount() - 1; c > 0; c -= 7) { setUpDeepKey(ex, 'a', c, 0); final int foundAt = b1.findKey(ex.getKey()); b1.removeKeys(foundAt, foundAt, ex.getAuxiliaryKey1()); } final boolean splitEven = b1.join(b2, b1.getKeyBlockEnd() - 4, b2.getKeyBlockStart() + 4, ex.getAuxiliaryKey1(), ex.getAuxiliaryKey2(), JoinPolicy.EVEN_BIAS); assertTrue("Should have split", splitEven); assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); } @Test public void testJoinEvenBias2() throws PersistitException { setUpPrettyFullBuffers(Buffer.PAGE_TYPE_DATA, 30, false); for (int c = b1.getKeyCount() - 1; c > 0; c -= 7) { setUpDeepKey(ex, 'a', c, 0); final int foundAt = b1.findKey(ex.getKey()); b1.removeKeys(foundAt, foundAt, ex.getAuxiliaryKey1()); } final boolean splitEven = b1.join(b2, b1.getKeyBlockEnd() - 4, b2.getKeyBlockStart() + 232, ex.getAuxiliaryKey1(), ex.getAuxiliaryKey2(), JoinPolicy.EVEN_BIAS); assertTrue("Should have split", splitEven); assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); } @Test public void testRebalanceException() throws Exception { setUpPrettyFullBuffers(Buffer.PAGE_TYPE_DATA, RED_FOX.length(), true); assertTrue(b1.verify(null, null) == null); assertTrue(b2.verify(null, null) == null); final int end1 = b1.getKeyBlockEnd(); final int avail1 = b1.getAvailableSize(); final int end2 = b2.getKeyBlockEnd(); final int avail2 = b2.getAvailableSize(); try { /* * Now attempt to join the two pages while removing the edge key */ b1.join(b2, b1.getKeyBlockEnd() - 4, b2.getKeyBlockStart() + 4, ex.getAuxiliaryKey1(), ex.getAuxiliaryKey2(), JoinPolicy.EVEN_BIAS); fail("RebalanceException not thrown"); } catch (final RebalanceException e) { // expected } // make sure the buffers weren't changed or corrupted assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); assertEquals("RebalanceException modified the page", end1, b1.getKeyBlockEnd()); assertEquals("RebalanceException modified the page", end2, b2.getKeyBlockEnd()); assertEquals("RebalanceException modified the page", avail1, b1.getAvailableSize()); assertEquals("RebalanceException modified the page", avail2, b2.getAvailableSize()); } @Test public void testSplitAtEqualsInsertAt() throws Exception { b1.init(Buffer.PAGE_TYPE_DATA); b2.init(Buffer.PAGE_TYPE_DATA); ex.clear(); setUpValue(true, RED_FOX.length()); // count how many will fit int a; for (a = 1;; a++) { ex.to(String.format("%6dz", a)); if (b1.putValue(ex.getKey(), vw) == -1) { break; } } b1.init(Buffer.PAGE_TYPE_DATA); for (int b = 1; b < a - 1; b++) { ex.to(String.format("%6dz", b)); b1.putValue(ex.getKey(), vw); } ex.getValue().clear(); ex.to(String.format("%6dz", a - 1)); assertTrue("Expected empty value to fit at end of buffer", b1.putValue(ex.getKey(), vw) != -1); /* * Split on key being inserted. */ setUpValue(true, RED_FOX.length()); ex.to(String.format("%6d", a / 2 + 1)); final int foundAt1 = b1.findKey(ex.getKey()); assertTrue("Expect FIXUP_REQUIRED flag", (foundAt1 & FIXUP_MASK) != 0); final int splitAt1 = b1.split(b2, ex.getKey(), vw, foundAt1, ex.getAuxiliaryKey1(), Exchange.Sequence.NONE, SplitPolicy.EVEN_BIAS); assertTrue("Split failed", splitAt1 != -1); assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); /* * Restore buffer setup */ b1.init(Buffer.PAGE_TYPE_DATA); b2.init(Buffer.PAGE_TYPE_DATA); b1.init(Buffer.PAGE_TYPE_DATA); setUpValue(true, RED_FOX.length()); for (int b = 1; b < a - 1; b++) { ex.to(String.format("%6dz", b)); b1.putValue(ex.getKey(), vw); } /* * Split on key being replaced */ ex.getValue().clear(); ex.to(String.format("%6dz", a - 1)); assertTrue("Expected empty value to fit at end of buffer", b1.putValue(ex.getKey(), vw) != -1); setUpValue(true, RED_FOX.length()); ex.to(String.format("%6dz", a / 2)); final int foundAt2 = b1.findKey(ex.getKey()); assertTrue("Expect EXACT flag", (foundAt2 & Buffer.EXACT_MASK) != 0); setUpValue(true, RED_FOX.length() * 2); final int splitAt2 = b1.split(b2, ex.getKey(), vw, foundAt2, ex.getAuxiliaryKey1(), Exchange.Sequence.NONE, SplitPolicy.EVEN_BIAS); assertTrue("Split failed", splitAt2 != -1); assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); /* * Restore buffer setup */ b1.init(Buffer.PAGE_TYPE_DATA); b2.init(Buffer.PAGE_TYPE_DATA); b1.init(Buffer.PAGE_TYPE_DATA); setUpValue(true, RED_FOX.length()); for (int b = 1; b < a - 1; b++) { ex.to(String.format("%6dz", b)); b1.putValue(ex.getKey(), vw); } /* * Split on key being replaced at end of buffer */ ex.getValue().clear(); ex.to(String.format("%6dz", a - 1)); assertTrue("Expected empty value to fit at end of buffer", b1.putValue(ex.getKey(), vw) != -1); setUpValue(true, RED_FOX.length()); ex.to(String.format("%6dz", a - 2)); final int foundAt3 = b1.findKey(ex.getKey()); assertTrue("Expect EXACT flag", (foundAt3 & Buffer.EXACT_MASK) != 0); setUpValue(true, RED_FOX.length() * 2); final int splitAt3 = b1.split(b2, ex.getKey(), vw, foundAt3, ex.getAuxiliaryKey1(), Exchange.Sequence.NONE, SplitPolicy.RIGHT_BIAS); assertTrue("Split failed", splitAt3 != -1); assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); } @Test public void testRemoveKeys() throws Exception { setUpPrettyFullBufferWithChangingEbc(6); setUpDeepKey(104); int foundAt1 = b1.findKey(ex.getKey()); setUpDeepKey(112); int foundAt2 = b1.findKey(ex.getKey()); if ((foundAt1 & Buffer.P_MASK) > (foundAt2 & Buffer.P_MASK)) { final int t = foundAt1; foundAt1 = foundAt2; foundAt2 = t; } b1.removeKeys(foundAt1, foundAt1, ex.getAuxiliaryKey1()); assertTrue("Verify failed", b1.verify(null, null) == null); b1.removeKeys(foundAt1 & ~EXACT_MASK, foundAt2, ex.getAuxiliaryKey1()); } @Test public void splitAndJoinBuffersWithZeroEbcKeys() throws Exception { b1.init(Buffer.PAGE_TYPE_DATA); b2.init(Buffer.PAGE_TYPE_DATA); setUpValue(true, b1.getBufferSize() / 100); int a; for (a = 1;; a++) { setUpShallowKey(a); if (b1.putValue(ex.getKey(), vw) == -1) { break; } } setUpShallowKey(a / 2); ex.getKey().getEncodedBytes()[6] = 0x7F; ex.getKey().setEncodedSize(7); final int foundAt = b1.findKey(ex.getKey()); final int splitAt = b1.split(b2, ex.getKey(), vw, foundAt, ex.getAuxiliaryKey1(), Exchange.Sequence.NONE, SplitPolicy.EVEN_BIAS); assertTrue("Split failed", splitAt != -1); assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); for (int b = 1; b < a; b++) { setUpShallowKey(b); if (b <= a / 2) { b1.fetch(b1.findKey(ex.getKey()), ex.getValue()); } else { b2.fetch(b2.findKey(ex.getKey()), ex.getValue()); } assertTrue("Value should be defined", ex.getValue().isDefined()); } setUpShallowKey(a / 2); ex.getKey().getEncodedBytes()[6] = 0x7F; ex.getKey().setEncodedSize(7); final int foundAt1 = b1.findKey(ex.getKey()); int foundAt2 = b2.findKey(ex.getKey()); assertTrue("Should have found exact match", (foundAt2 & EXACT_MASK) > 0); foundAt2 = b2.nextKeyBlock(foundAt2); final boolean rebalanced = b1.join(b2, foundAt1, foundAt2, ex.getKey(), ex.getAuxiliaryKey1(), JoinPolicy.EVEN_BIAS); assertTrue("Should have joined pages", !rebalanced); assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); } private void setUpPrettyFullBufferWithChangingEbc(final int valueLength) throws PersistitException { b1.init(Buffer.PAGE_TYPE_DATA); setUpValue(false, valueLength); int a; for (a = 0;; a++) { setUpDeepKey(a); if (b1.putValue(ex.getKey(), vw) == -1) { a -= 1; break; } } } private void setUpPrettyFullBuffers(final int type, final int valueLength, final boolean discontinuous) throws PersistitException { b1.init(type); b2.init(type); final boolean isData = type == Buffer.PAGE_TYPE_DATA; int a, b; // load page A with keys of increasing length for (a = 10;; a++) { setUpDeepKey(ex, 'a', a, isData ? 0 : a + 1000000); setUpValue(isData, valueLength); if (b1.putValue(ex.getKey(), vw) == -1) { a -= 1; break; } } // set up the right edge key in page A setUpDeepKey(ex, 'a', a, isData ? 0 : -1); ex.getValue().clear(); b1.putValue(ex.getKey(), vw); // set up the left edge key in page B setUpDeepKey(ex, 'a', a, isData ? 0 : a + 1000000); setUpValue(isData, valueLength); b2.putValue(ex.getKey(), vw); // load additional keys into page B for (b = a;; b++) { setUpDeepKey(ex, discontinuous && b > a ? 'b' : 'a', b, b + 1000000); setUpValue(isData, valueLength); if (b2.putValue(ex.getKey(), vw) == -1) { break; } } assertTrue("Verify failed", b1.verify(null, null) == null); assertTrue("Verify failed", b2.verify(null, null) == null); } private void setUpValue(final boolean isData, final int length) { if (isData) { ex.getValue().put(sb.toString().substring(0, length)); } else { ex.getValue().clear(); } } private void setUpDeepKey(final int a) { setUpDeepKey(ex, "abcdefg".charAt(a % 7), (a % (2000 / 13)) * 13, 0); } private void setUpDeepKey(final Exchange ex, final char fill, final int n, final long pointer) { ex.getKey().clear().append(keyString(fill, n, n - 4, 4, n)).append(1); ex.getValue().setPointerValue(pointer); } private void setUpShallowKey(final int a) { setUpShallowKey(ex, a, 0); } private void setUpShallowKey(final Exchange ex, final int value, final long pointer) { final byte[] bytes = ex.getKey().clear().getEncodedBytes(); Arrays.fill(bytes, (byte) 0); /* * Convert int to little-endian form so that byte 0 varies fastest. 7 * bits per byte, no zeroes. */ bytes[0] = (byte) (((value >>> 0) & 0x7F) + 1); bytes[1] = (byte) (((value >>> 7) & 0x7F) + 1); bytes[2] = (byte) (((value >>> 14) & 0x7F) + 1); bytes[3] = (byte) (((value >>> 21) & 0x7F) + 1); bytes[5] = (byte) (((value >>> 28) & 0x7F) + 1); ex.getKey().setEncodedSize(6); ex.getValue().setPointerValue(pointer); } String keyString(final char fill, final int length, final int prefix, final int width, final int k) { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < prefix && i < length; i++) { sb.append(fill); } sb.append(String.format("%0" + width + "d", k)); for (int i = length - sb.length(); i < length; i++) { sb.append(fill); } sb.setLength(length); return sb.toString(); } }