/** * Copyright 2011-2013 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.unit.UnitTestProperties.VOLUME_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import com.persistit.unit.UnitTestProperties; public class TreeBuilderTest extends PersistitUnitTestCase { private final static int COUNT = 100000; private final AtomicInteger _duplicates = new AtomicInteger(); private TreeBuilder getBasicTreeBuilder() { final TreeBuilder tb = new TreeBuilder(_persistit) { @Override protected void reportSorted(final long count) { System.out.println("Sorted " + count); } @Override protected void reportMerged(final long count) { System.out.println("Merged " + count); } @Override protected boolean duplicateKeyDetected(final Tree tree, final Key key, final Value v1, final Value v2) { System.out.println("Duplicate key " + key); _duplicates.incrementAndGet(); return false; } }; tb.setReportKeyCountMultiple(COUNT / 2); return tb; } @Test public void basicTest() throws Exception { final TreeBuilder tb = getBasicTreeBuilder(); final List<Integer> shuffled = new ArrayList<Integer>(COUNT); for (int i = 0; i < COUNT; i++) { shuffled.add(i); } Collections.shuffle(shuffled); final Exchange a = _persistit.getExchange(VOLUME_NAME, "a", true); final Exchange b = _persistit.getExchange(VOLUME_NAME, "b", true); final Exchange c = _persistit.getExchange(VOLUME_NAME, "c", true); for (int i = 0; i < COUNT; i++) { final int ka = shuffled.get(i); final int kb = shuffled.get((i + COUNT / 3) % COUNT); final int kc = shuffled.get((i + (2 * COUNT) / 3) % COUNT); a.clear().append(ka).append("a"); a.getValue().put(i); b.clear().append(kb).append("b"); b.getValue().put(i); c.clear().append(kc).append("c"); c.getValue().put(i); tb.store(a); tb.store(b); tb.store(c); } tb.merge(); a.clear(); b.clear(); c.clear(); int count = 0; while (a.next(true) && b.next(true) && c.next(true)) { assertEquals("Expect correct key value", count, a.getKey().decodeInt()); assertEquals("Expect correct key value", count, b.getKey().decodeInt()); assertEquals("Expect correct key value", count, c.getKey().decodeInt()); count++; } assertEquals("Expect every key value", COUNT, count); _persistit.flush(); assertEquals(0, a.getBufferPool().getDirtyPageCount()); } @Test public void customizationMethods() throws Exception { final AtomicBoolean doReplace = new AtomicBoolean(); final AtomicInteger duplicateCount = new AtomicInteger(); final AtomicInteger beforeMergeCount = new AtomicInteger(); final AtomicInteger afterMergeCount = new AtomicInteger(); final TreeBuilder tb = new TreeBuilder(_persistit) { @Override protected boolean duplicateKeyDetected(final Tree tree, final Key key, final Value v1, final Value v2) { duplicateCount.incrementAndGet(); return doReplace.get(); } @Override protected boolean beforeMergeKey(final Exchange ex) throws Exception { beforeMergeCount.incrementAndGet(); return ex.getKey().decodeInt() != 3; } @Override protected void afterMergeKey(final Exchange ex) throws Exception { afterMergeCount.incrementAndGet(); } }; final Exchange ex = _persistit.getExchange(VOLUME_NAME, "a", true); doReplace.set(true); ex.to(1).getValue().put("abc"); tb.store(ex); ex.to(1).getValue().put("def"); tb.store(ex); assertEquals("Should have registered a dup", 1, duplicateCount.get()); doReplace.set(false); ex.to(2).getValue().put("abc"); tb.store(ex); ex.to(2).getValue().put("def"); tb.store(ex); assertEquals("Should have registered a dup", 2, duplicateCount.get()); tb.unitTestNextSortFile(); ex.to(1).getValue().put("ghi"); tb.store(ex); ex.to(2).getValue().put("ghi"); tb.store(ex); assertEquals("Should not have registered a dup yet", 2, duplicateCount.get()); ex.to(3).getValue().put("abc"); tb.store(ex); doReplace.set(false); tb.merge(); assertEquals("Should have registered two dups", 4, duplicateCount.get()); assertEquals("beforeMergeKey should be thrice", 3, beforeMergeCount.get()); assertEquals("afterMergeKey should be called twice", 2, afterMergeCount.get()); final StringBuilder result = new StringBuilder(); ex.clear().append(Key.BEFORE); while (ex.next(true)) { result.append(String.format("%s=%s,", ex.getKey(), ex.getValue())); } assertEquals("Expected result", "{1}=\"def\",{2}=\"abc\",", result.toString()); } @Test public void duplicatePriority1() throws Exception { duplicatePriorityCheck(new TreeBuilder(_persistit) { // Larger value wins @Override protected boolean duplicateKeyDetected(final Tree tree, final Key key, final Value v1, final Value v2) { final String s1 = v1.getString(); final String s2 = v2.getString(); return s1.compareTo(s2) < 0; } }, "xuorcxq"); } @Test public void duplicatePriority2() throws Exception { duplicatePriorityCheck(new TreeBuilder(_persistit) { // First value wins @Override protected boolean duplicateKeyDetected(final Tree tree, final Key key, final Value v1, final Value v2) { return false; } }, "xmnraxq"); } private void duplicatePriorityCheck(final TreeBuilder tb, final String expected) throws Exception { final Exchange ex = _persistit.getExchange(VOLUME_NAME, "a", true); final String nul = null; insertKeys(ex, tb, "x", "m", "n", nul, "a", nul, "q"); tb.unitTestNextSortFile(); insertKeys(ex, tb, nul, "t", "o", "r", nul, nul, nul); tb.unitTestNextSortFile(); insertKeys(ex, tb, nul, "u", "m", "j", "c", "x", "l"); tb.unitTestNextSortFile(); insertKeys(ex, tb, nul, "m", nul, nul, "a", nul, "q"); tb.merge(); final StringBuilder result = new StringBuilder(); for (int i = 0; i < 10; i++) { ex.to(i).fetch(); if (ex.isValueDefined()) { result.append(ex.getValue().getString()); } } assertEquals(expected, result.toString()); } private void insertKeys(final Exchange ex, final TreeBuilder tb, final String... args) throws Exception { for (int i = 0; i < args.length; i++) { if (args[i] != null) { ex.to(i).getValue().put(args[i]); tb.store(ex); } } } @Test public void multipleDirectories() throws Exception { final TreeBuilder tb = getBasicTreeBuilder(); final List<File> directories = new ArrayList<File>(); final Random random = new Random(); try { for (int i = 0; i < 3; i++) { final File file = File.createTempFile("TreeBuilderTest", ""); file.delete(); assertTrue("Expect to make directory", file.mkdir()); directories.add(file); } tb.setSortTreeDirectories(directories); final Exchange ex = _persistit.getExchange(VOLUME_NAME, "TreeBuilderTest", true); for (int i = 0; i < COUNT; i++) { final int k = random.nextInt(); ex.to(k); ex.getValue().put(RED_FOX + "," + k); tb.store(ex); if (((i + 1) % (COUNT / 10)) == 0) { tb.unitTestNextSortFile(); } } for (final File file : directories) { assertTrue("Expect some files in each directory", file.list().length > 0); } tb.merge(); for (final File file : directories) { assertTrue("Expect no remaining files", file.list().length == 0); } ex.to(Key.BEFORE); int count = 0; while (ex.next()) { count++; final int k = ex.getKey().decodeInt(); assertEquals(RED_FOX + "," + k, ex.getValue().getString()); } assert count + _duplicates.get() == COUNT; } finally { for (final File file : directories) { UnitTestProperties.cleanUpDirectory(file); } } } }