/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.github.geophile.erdo.map.mergescan; import com.github.geophile.erdo.map.testarraymap.TestArrayMap; import com.github.geophile.erdo.AbstractKey; import com.github.geophile.erdo.AbstractRecord; import com.github.geophile.erdo.TestFactory; import com.github.geophile.erdo.map.LazyRecord; import com.github.geophile.erdo.map.MapCursor; import com.github.geophile.erdo.transaction.TimestampSet; import org.junit.After; import org.junit.BeforeClass; import org.junit.Test; import java.io.IOException; import java.util.Random; import static junit.framework.Assert.*; public class FastMergeTest { @BeforeClass public static void beforeClass() throws IOException { TestKey.testErdoId(ERDO_ID); } @After public void after() { FACTORY.reset(); } @Test public void testOverlapping() throws IOException, InterruptedException { final int TRIALS = 10; final int SEED = 123456789; final int KEYS = 100; final int MAX_MULTI_RECORD_SIZE = 4; Random random = new Random(SEED); for (int t = 0; t < TRIALS; t++) { TestArrayMap a = arrayMap(KEYS, MAX_MULTI_RECORD_SIZE, random, 2, 0); TestArrayMap b = arrayMap(KEYS, MAX_MULTI_RECORD_SIZE, random, 2, 1); FastMergeCursor merge = new FastMergeCursor(MERGER); merge.addInput(a.cursor(null, false)); merge.addInput(b.cursor(null, false)); merge.start(); // Should see 0 .. 2 * KEYS - 1 int expected = 0; LazyRecord lazyRecord; while ((lazyRecord = merge.next()) != null) { AbstractRecord record = lazyRecord.materializeRecord(); if (record instanceof TestMultiRecord) { TestMultiRecord multiRecord = (TestMultiRecord) record; MapCursor cursor = multiRecord.cursor(); LazyRecord lazyScanRecord; while ((lazyScanRecord = cursor.next()) != null) { assertEquals(expected++, ((TestKey) lazyScanRecord.key()).key()); } } else if (record instanceof TestRecord) { assertEquals(expected++, ((TestKey)record.key()).key()); } else { fail(); } } assertEquals(2 * KEYS, expected); } } private void dump(String label, TestArrayMap map) throws IOException, InterruptedException { System.out.println(label); MapCursor cursor = map.cursor(null, false); LazyRecord lazyRecord; while ((lazyRecord = cursor.next()) != null) { System.out.println(String.format(" %s", lazyRecord.materializeRecord())); } } // TODO: This doesn't test overlapping MultiRecords @Test public void testRandom() throws IOException, InterruptedException { final int TRIALS = 1000; final int MAX_INPUTS = 20; final int AVERAGE_RECORDS_PER_INPUT = 500; final int MAX_MULTI_RECORD_SIZE = 5; final double SINGLETON_FREQUENCY = 0.25; final int SEED = 123456789; Random random = new Random(SEED); for (int t = 0; t < TRIALS; t++) { int inputs = 2 + random.nextInt(MAX_INPUTS - 1); // 2 - MAX_INPUTS int records = inputs * AVERAGE_RECORDS_PER_INPUT; TestArrayMap[] arrayMaps = new TestArrayMap[inputs]; for (int i = 0; i < inputs; i++) { arrayMaps[i] = new TestArrayMap(FACTORY, new TimestampSet()); } int count = 0; // Load records and multi-records into ArrayMaps int expectedSingletonRecordCount = 0; int expectedMultiRecordCount = 0; while (count < records) { AbstractRecord record; if (random.nextDouble() < SINGLETON_FREQUENCY) { record = record(count++); expectedSingletonRecordCount++; } else { int runSize = Math.min(records - count, 1 + random.nextInt(MAX_MULTI_RECORD_SIZE)); assertTrue(runSize > 0); assertTrue(runSize <= MAX_MULTI_RECORD_SIZE); record = multiRecord(count, runSize); count += runSize; expectedMultiRecordCount += runSize; } int a = random.nextInt(inputs); arrayMaps[a].put(record, false); } // Start a merge of the ArrayMaps FastMergeCursor merge = new FastMergeCursor(MERGER); for (TestArrayMap arrayMap : arrayMaps) { merge.addInput(arrayMap.cursor(null, false)); } merge.start(); // Check contents LazyRecord lazyRecord; int expectedKey = 0; int actualSingletonRecordCount = 0; int actualMultiRecordCount = 0; while ((lazyRecord = merge.next()) != null) { if (lazyRecord instanceof AbstractMultiRecord) { TestMultiRecord multiRecord = (TestMultiRecord) lazyRecord; for (AbstractRecord memberRecord : multiRecord.records()) { assertEquals(expectedKey, ((TestKey) memberRecord.key()).key()); expectedKey++; } actualMultiRecordCount += multiRecord.records().size(); } else { assertEquals(expectedKey, ((TestKey) lazyRecord.key()).key()); expectedKey++; actualSingletonRecordCount++; } } assertEquals(expectedSingletonRecordCount, actualSingletonRecordCount); assertEquals(expectedMultiRecordCount, actualMultiRecordCount); assertEquals(records, actualMultiRecordCount + actualSingletonRecordCount); } } private TestArrayMap arrayMap(int keys, int maxMultiRecordSize, Random random, int gap, int offset) { TestArrayMap map = new TestArrayMap(FACTORY, new TimestampSet()); int k = offset; int keyCount = 0; while (keyCount < keys) { int runSize = random.nextInt(maxMultiRecordSize) + 1; if (keyCount + runSize > keys) { runSize = keys - keyCount; } AbstractRecord record; if (runSize == 1) { record = record(k); k += gap; } else { TestMultiRecord multiRecord = new TestMultiRecord(new MultiRecordKey(new TestKey(k), new TestKey(k + gap * runSize))); for (int i = 0; i < runSize; i++) { TestKey key = new TestKey(k); multiRecord.append(new TestRecord(key, null)); k += gap; } record = multiRecord; } map.put(record, false); keyCount += runSize; } return map; } private TestRecord record(int key) { return new TestRecord(new TestKey(key), null); } private TestMultiRecord multiRecord(int start, int n) { assertTrue(n > 0); TestMultiRecord multiRecord = new TestMultiRecord(new MultiRecordKey(new TestKey(start), new TestKey(start + n))); for (int i = 0; i < n; i++) { TestKey key = new TestKey(start + i); TestRecord record = new TestRecord(key, null); multiRecord.append(record); } return multiRecord; } private static final TestFactory FACTORY = new TestFactory(); private static final Merger MERGER = new Merger() { @Override public Side merge(AbstractKey left, AbstractKey right) { int leftId = ((TestKey) left).id(); int rightId = ((TestKey) right).id(); return leftId < rightId ? Side.LEFT : Side.RIGHT; } }; private static final int ERDO_ID = 1; }