diff --git a/src/main/java/com/salesforce/phoenix/compile/ScanRanges.java b/src/main/java/com/salesforce/phoenix/compile/ScanRanges.java index f49ed635..5d8120a1 100644 --- a/src/main/java/com/salesforce/phoenix/compile/ScanRanges.java +++ b/src/main/java/com/salesforce/phoenix/compile/ScanRanges.java @@ -1,3 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2013, Salesforce.com, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of Salesforce.com nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ package com.salesforce.phoenix.compile; import java.util.Collections; @@ -5,12 +32,15 @@ import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.hadoop.hbase.util.Bytes; import com.salesforce.phoenix.query.KeyRange; import com.salesforce.phoenix.schema.RowKeySchema; import com.salesforce.phoenix.schema.ValueBitSet; +import com.salesforce.phoenix.util.ByteUtil; import com.salesforce.phoenix.util.ScanUtil; + public class ScanRanges { private static final List> EVERYTHING_RANGES = Collections.>emptyList(); private static final List> NOTHING_RANGES = Collections.>singletonList(Collections.singletonList(KeyRange.EMPTY_RANGE)); @@ -23,13 +53,12 @@ public static ScanRanges create(List> ranges, RowKeySchema schema } else if (ranges.size() == 1 && ranges.get(0).size() == 1 && ranges.get(0).get(0) == KeyRange.EMPTY_RANGE) { return NOTHING; } - return new ScanRanges(ranges, schema); } - + private final List> ranges; private final RowKeySchema schema; - + private ScanRanges (List> ranges, RowKeySchema schema) { this.ranges = ranges; this.schema = schema; @@ -42,11 +71,11 @@ public List> getRanges() { public RowKeySchema getSchema() { return schema; } - + public boolean isEverything() { return this == EVERYTHING; } - + public boolean isDegenerate() { return this == NOTHING; } @@ -89,10 +118,10 @@ public boolean isSingleRowScan() { } public void setScanStartStopRow(Scan scan) { - if (this == EVERYTHING) { + if (isEverything()) { return; } - if (this == NOTHING) { + if (isDegenerate()) { scan.setStartRow(KeyRange.EMPTY_RANGE.getLowerRange()); scan.setStopRow(KeyRange.EMPTY_RANGE.getUpperRange()); return; @@ -111,14 +140,22 @@ public void setScanStartStopRow(Scan scan) { private static final ImmutableBytesWritable UNBOUND_LOWER = new ImmutableBytesWritable(KeyRange.UNBOUND_LOWER); private static final ImmutableBytesWritable UNBOUND_UPPER = new ImmutableBytesWritable(KeyRange.UNBOUND_UPPER); - - public boolean intersect(byte[] lowerInclusiveKey, byte[] upperExclusiveKey) { - if (this == EVERYTHING) { + + public boolean intersect(KeyRange keyRange) { + if (isEverything()) { return true; } - if (this == NOTHING) { + if (isDegenerate()) { return false; } + byte[] lowerInclusiveKey = keyRange.getLowerRange(); + if (!keyRange.isLowerInclusive() && !Bytes.equals(lowerInclusiveKey, KeyRange.UNBOUND_LOWER)) { + lowerInclusiveKey = ByteUtil.nextKey(lowerInclusiveKey); + } + byte[] upperExclusiveKey = keyRange.getUpperRange(); + if (keyRange.isUpperInclusive()) { + upperExclusiveKey = ByteUtil.nextKey(upperExclusiveKey); + } int i = 0; int[] position = new int[ranges.size()]; diff --git a/src/main/java/com/salesforce/phoenix/filter/SkipScanFilter.java b/src/main/java/com/salesforce/phoenix/filter/SkipScanFilter.java index 10486c37..9f181021 100644 --- a/src/main/java/com/salesforce/phoenix/filter/SkipScanFilter.java +++ b/src/main/java/com/salesforce/phoenix/filter/SkipScanFilter.java @@ -38,7 +38,6 @@ import org.apache.hadoop.io.WritableUtils; import com.google.common.base.Objects; -import com.google.common.collect.Lists; import com.google.common.hash.*; import com.salesforce.phoenix.query.KeyRange; import com.salesforce.phoenix.query.KeyRange.Bound; @@ -70,7 +69,6 @@ public class SkipScanFilter extends FilterBase { private int startKeyLength; private byte[] endKey; private int endKeyLength; - private int estimateSplitNum; private final ImmutableBytesWritable ptr = new ImmutableBytesWritable(); @@ -107,50 +105,6 @@ private void init(List> slots, RowKeySchema schema, int maxKeyLen endKeyLength = 0; // Start key for the scan will initially be set to start at the right place // We just need to set the end key for when we need to calculate the next skip hint - this.estimateSplitNum = estimateSplitNum(); - } - - // Estimate the number of splits that would be generated from the slots. - private int estimateSplitNum() { - int estimate = 0; - do { - estimate += 1; - } while (incrementKey()); - return estimate; - } - - // Used externally by region splitters to generate all split ranges. - public List generateSplitRanges(int maxConcurrency) { - List splits = Lists.newArrayListWithCapacity(estimateSplitNum); - // steps we need to increment when chunking multiple key range together. - int step = (int) Math.ceil(((double) estimateSplitNum) / maxConcurrency) - 1; - boolean terminated = false, lowerInclusive; - while (!terminated) { - // Do not need to care about the length since we set the key from the very beginning. - setStartKey(); - lowerInclusive = isKeyInclusive(Bound.LOWER); - terminated = !incrementKey(step, Bound.UPPER); - if (!terminated) { - setEndKey(); - } else { - // We have wrapped around already, set the key to be the last key. - for (int i=0; i= 0 && (position[idx] = (position[idx] + 1) % slots.get(idx).size()) == 0) { - idx--; - } - return idx >= 0; - } - - private void setBoundSlotPosition(Bound bound) { - // Find first index on the current position that is a range slot. - int idx; - for (idx = 0; idx < slots.size(); idx++) { - if (!slots.get(idx).get(position[idx]).isSingleKey()) { - break; - } - } - // If the idx is not the last position, reset the slots beyond to become 0th position. If - // we are setting the position for a lower bound, reset all of them to 0. If we are setting - // the position for an uppser bound, reset all of them to the last index. If the bound is - // not specified, set all positions to 0. - if (idx < slots.size() - 1) { - if (bound == Bound.LOWER) { - for (int i = idx + 1; i < slots.size(); i++) { - position[i] = 0; - } - } else { - for (int i = idx + 1; i < slots.size(); i++) { - position[i] = slots.get(i).size() - 1; - } - } - } - } - private int getTerminatorCount(RowKeySchema schema) { int nTerminators = 0; for (int i = 0; i < schema.getFieldCount(); i++) { diff --git a/src/main/java/com/salesforce/phoenix/iterate/DefaultParallelIteratorRegionSplitter.java b/src/main/java/com/salesforce/phoenix/iterate/DefaultParallelIteratorRegionSplitter.java index 8676cf33..a8913287 100644 --- a/src/main/java/com/salesforce/phoenix/iterate/DefaultParallelIteratorRegionSplitter.java +++ b/src/main/java/com/salesforce/phoenix/iterate/DefaultParallelIteratorRegionSplitter.java @@ -37,6 +37,7 @@ import org.apache.hadoop.hbase.util.Bytes; import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; import com.google.common.collect.*; import com.salesforce.phoenix.compile.StatementContext; import com.salesforce.phoenix.query.*; @@ -77,7 +78,32 @@ protected DefaultParallelIteratorRegionSplitter(StatementContext context, TableR protected List> getAllRegions() throws SQLException { Scan scan = context.getScan(); NavigableMap allTableRegions = context.getConnection().getQueryServices().getAllTableRegions(table); - return ParallelIterators.filterRegions(allTableRegions, scan.getStartRow(), scan.getStopRow()); + return filterRegions(allTableRegions, scan.getStartRow(), scan.getStopRow()); + } + + /** + * Filters out regions that intersect with key range specified by the startKey and stopKey + * @param allTableRegions all region infos for a given table + * @param startKey the lower bound of key range, inclusive + * @param stopKey the upper bound of key range, inclusive + * @return regions that intersect with the key range given by the startKey and stopKey + */ + // exposed for tests + public static List> filterRegions(NavigableMap allTableRegions, byte[] startKey, byte[] stopKey) { + Iterable> regions; + final KeyRange keyRange = KeyRange.getKeyRange(startKey, true, stopKey, false, false); + if (keyRange == KeyRange.EVERYTHING_RANGE) { + regions = allTableRegions.entrySet(); + } else { + regions = Iterables.filter(allTableRegions.entrySet(), new Predicate>() { + @Override + public boolean apply(Map.Entry region) { + KeyRange regionKeyRange = KeyRange.getKeyRange(region.getKey()); + return keyRange.intersect(regionKeyRange) != KeyRange.EMPTY_RANGE; + } + }); + } + return Lists.newArrayList(regions); } protected List genKeyRanges(List> regions) { @@ -95,7 +121,7 @@ protected List genKeyRanges(List> r // // if r >= t: // scan using regional boundaries - // elif r/2 > t: + // elif r > t/2: // split each region in s splits such that: // s = max(x) where s * x < m // else: diff --git a/src/main/java/com/salesforce/phoenix/iterate/ParallelIterators.java b/src/main/java/com/salesforce/phoenix/iterate/ParallelIterators.java index 0d3d13df..9d7b7a3f 100644 --- a/src/main/java/com/salesforce/phoenix/iterate/ParallelIterators.java +++ b/src/main/java/com/salesforce/phoenix/iterate/ParallelIterators.java @@ -39,9 +39,6 @@ import org.apache.hadoop.hbase.util.Pair; import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.salesforce.phoenix.compile.StatementContext; import com.salesforce.phoenix.execute.RowCounter; import com.salesforce.phoenix.job.JobManager.JobCallable; @@ -80,31 +77,6 @@ public ParallelIterators(StatementContext context, TableRef table, RowCounter ro this.splits = getSplits(context, table); } - /** - * Filters out regions that intersect with key range specified by the startKey and stopKey - * @param allTableRegions all region infos for a given table - * @param startKey the lower bound of key range, inclusive - * @param stopKey the upper bound of key range, inclusive - * @return regions that intersect with the key range given by the startKey and stopKey - */ - // exposed for tests - public static List> filterRegions(NavigableMap allTableRegions, byte[] startKey, byte[] stopKey) { - Iterable> regions; - final KeyRange keyRange = KeyRange.getKeyRange(startKey, true, stopKey, false, false); - if (keyRange == KeyRange.EVERYTHING_RANGE) { - regions = allTableRegions.entrySet(); - } else { - regions = Iterables.filter(allTableRegions.entrySet(), new Predicate>() { - @Override - public boolean apply(Map.Entry region) { - KeyRange regionKeyRange = KeyRange.getKeyRange(region.getKey()); - return keyRange.intersect(regionKeyRange) != KeyRange.EMPTY_RANGE; - } - }); - } - return Lists.newArrayList(regions); - } - /** * Splits the given scan's key range so that each split can be queried in parallel * diff --git a/src/main/java/com/salesforce/phoenix/iterate/SkipRangeParallelIteratorRegionSplitter.java b/src/main/java/com/salesforce/phoenix/iterate/SkipRangeParallelIteratorRegionSplitter.java index e8b87a52..4566ccec 100644 --- a/src/main/java/com/salesforce/phoenix/iterate/SkipRangeParallelIteratorRegionSplitter.java +++ b/src/main/java/com/salesforce/phoenix/iterate/SkipRangeParallelIteratorRegionSplitter.java @@ -33,9 +33,10 @@ import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.ServerName; -import com.google.common.collect.ImmutableList; +import com.google.common.base.Predicate; +import com.google.common.collect.*; +import com.salesforce.phoenix.compile.ScanRanges; import com.salesforce.phoenix.compile.StatementContext; -import com.salesforce.phoenix.filter.SkipScanFilter; import com.salesforce.phoenix.query.KeyRange; import com.salesforce.phoenix.schema.TableRef; @@ -55,16 +56,27 @@ protected SkipRangeParallelIteratorRegionSplitter(StatementContext context, Tabl @Override protected List> getAllRegions() throws SQLException { - Set> allRegions = new HashSet>(); - SkipScanFilter filter = new SkipScanFilter(context.getScanRanges().getRanges(), table.getTable().getRowKeySchema()); - // TODO: put generateSplitRanges on ScanRanges? - List keyRanges = filter.generateSplitRanges(maxConcurrency); NavigableMap allTableRegions = context.getConnection().getQueryServices().getAllTableRegions(table); - for (KeyRange range: keyRanges) { - List> regions = ParallelIterators.filterRegions(allTableRegions, range.getLowerRange(), range.getUpperRange()); - allRegions.addAll(regions); + return filterRegions(allTableRegions, context.getScanRanges()); + } + + public static List> filterRegions(NavigableMap allTableRegions, final ScanRanges ranges) { + Iterable> regions; + if (ranges == ScanRanges.EVERYTHING) { + regions = allTableRegions.entrySet(); + } else if (ranges == ScanRanges.NOTHING) { + return Lists.>newArrayList(); + } else { + regions = Iterables.filter(allTableRegions.entrySet(), + new Predicate>() { + @Override + public boolean apply(Map.Entry region) { + KeyRange regionKeyRange = KeyRange.getKeyRange(region.getKey()); + return ranges.intersect(regionKeyRange); + } + }); } - return ImmutableList.>copyOf(allRegions); + return Lists.newArrayList(regions); } } diff --git a/src/main/java/com/salesforce/phoenix/query/KeyRange.java b/src/main/java/com/salesforce/phoenix/query/KeyRange.java index 01573f67..462a87a2 100644 --- a/src/main/java/com/salesforce/phoenix/query/KeyRange.java +++ b/src/main/java/com/salesforce/phoenix/query/KeyRange.java @@ -187,7 +187,7 @@ public int compareLowerToUpperBound( byte[] b, int o, int l) { * and 0 if they are equal. */ public int compareLowerToUpperBound( byte[] b, int o, int l, boolean isInclusive) { - if (lowerUnbound()) { + if (lowerUnbound() || (Bytes.equals(b, KeyRange.UNBOUND_UPPER))) { return -1; } int cmp = Bytes.compareTo(lowerRange, 0, lowerRange.length, b, o, l); @@ -208,7 +208,7 @@ public int compareUpperToLowerBound(byte[] b, int o, int l) { } public int compareUpperToLowerBound(byte[] b, int o, int l, boolean isInclusive) { - if (upperUnbound()) { + if (upperUnbound() || Bytes.equals(b, KeyRange.UNBOUND_LOWER)) { return 1; } int cmp = Bytes.compareTo(upperRange, 0, upperRange.length, b, o, l); diff --git a/src/test/java/com/salesforce/phoenix/compile/ScanRangesTest.java b/src/test/java/com/salesforce/phoenix/compile/ScanRangesTest.java index c4240982..e937ad6e 100644 --- a/src/test/java/com/salesforce/phoenix/compile/ScanRangesTest.java +++ b/src/test/java/com/salesforce/phoenix/compile/ScanRangesTest.java @@ -43,7 +43,6 @@ import com.salesforce.phoenix.query.KeyRange; import com.salesforce.phoenix.schema.*; import com.salesforce.phoenix.schema.RowKeySchema.RowKeySchemaBuilder; -import com.salesforce.phoenix.util.ByteUtil; /** @@ -65,15 +64,7 @@ public ScanRangesTest(ScanRanges scanRanges, int[] widths, @Test public void test() { - byte[] lower = keyRange.getLowerRange(); - if (!keyRange.isLowerInclusive()) { - lower = ByteUtil.nextKey(lower); - } - byte[] upper = keyRange.getUpperRange(); - if (keyRange.isUpperInclusive()) { - upper = ByteUtil.nextKey(upper); - } - assertEquals(expectedResult, scanRanges.intersect(lower,upper)); + assertEquals(expectedResult, scanRanges.intersect(keyRange)); } private static KeyRange getKeyRange(byte[] lowerRange, boolean lowerInclusive, byte[] upperRange, boolean upperInclusive) { @@ -209,6 +200,21 @@ public static Collection data() { getKeyRange(Bytes.toBytes("B"), true, Bytes.toBytes("B"), true),}}, new int[] {1,1,1}, getKeyRange(Bytes.toBytes("c1A"), false, Bytes.toBytes("c9Z"), true), false)); + // KeyRange contains unbound lower bound. + testCases.addAll( + foreach(new KeyRange[][]{{ + getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true),},{ + getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true),},{ + getKeyRange(Bytes.toBytes("A"), true, Bytes.toBytes("B"), true),}}, + new int[] {1,1,1}, getKeyRange(KeyRange.UNBOUND_LOWER, false, Bytes.toBytes("a0Z"), true), + false)); + testCases.addAll( + foreach(new KeyRange[][]{{ + getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true),},{ + getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true),},{ + getKeyRange(Bytes.toBytes("A"), true, Bytes.toBytes("B"), true),}}, + new int[] {1,1,1}, getKeyRange(KeyRange.UNBOUND_LOWER, false, Bytes.toBytes("a1C"), true), + true)); return testCases; } diff --git a/src/test/java/com/salesforce/phoenix/end2end/DefaultParallelIteratorsTest.java b/src/test/java/com/salesforce/phoenix/end2end/DefaultParallelIteratorsRegionSplitterTest.java similarity index 98% rename from src/test/java/com/salesforce/phoenix/end2end/DefaultParallelIteratorsTest.java rename to src/test/java/com/salesforce/phoenix/end2end/DefaultParallelIteratorsRegionSplitterTest.java index cd4ae735..2a0557f6 100644 --- a/src/test/java/com/salesforce/phoenix/end2end/DefaultParallelIteratorsTest.java +++ b/src/test/java/com/salesforce/phoenix/end2end/DefaultParallelIteratorsRegionSplitterTest.java @@ -43,7 +43,6 @@ import com.salesforce.phoenix.compile.StatementContext; import com.salesforce.phoenix.iterate.DefaultParallelIteratorRegionSplitter; -import com.salesforce.phoenix.iterate.ParallelIterators; import com.salesforce.phoenix.jdbc.PhoenixConnection; import com.salesforce.phoenix.query.*; import com.salesforce.phoenix.query.StatsManagerImpl.TimeKeeper; @@ -58,7 +57,7 @@ * @author syyang * @since 0.1 */ -public class DefaultParallelIteratorsTest extends BaseClientMangedTimeTest { +public class DefaultParallelIteratorsRegionSplitterTest extends BaseClientMangedTimeTest { private static final byte[] KMIN = new byte[] {'!'}; private static final byte[] KMIN2 = new byte[] {'.'}; @@ -101,13 +100,14 @@ private static NavigableMap getRegions(TableRef table) return MetaScanner.allTableRegions(driver.getQueryServices().getConfig(), table.getTableName(), false); } - private static List getSplits(TableRef table, final Scan scan, final NavigableMap regions) throws SQLException { + private static List getSplits(TableRef table, final Scan scan, final NavigableMap regions) + throws SQLException { PhoenixConnection connection = DriverManager.getConnection(getUrl(), TEST_PROPERTIES).unwrap(PhoenixConnection.class); StatementContext context = new StatementContext(connection, null, Collections.emptyList(), 0, scan); DefaultParallelIteratorRegionSplitter splitter = new DefaultParallelIteratorRegionSplitter(context, table) { @Override protected List> getAllRegions() throws SQLException { - return ParallelIterators.filterRegions(regions, scan.getStartRow(), scan.getStopRow()); + return DefaultParallelIteratorRegionSplitter.filterRegions(regions, scan.getStartRow(), scan.getStopRow()); } }; List keyRanges = splitter.getSplits(); diff --git a/src/test/java/com/salesforce/phoenix/end2end/SkipRangeParallelIteratorRegionSplitterTest.java b/src/test/java/com/salesforce/phoenix/end2end/SkipRangeParallelIteratorRegionSplitterTest.java new file mode 100644 index 00000000..dbc7b135 --- /dev/null +++ b/src/test/java/com/salesforce/phoenix/end2end/SkipRangeParallelIteratorRegionSplitterTest.java @@ -0,0 +1,340 @@ +/******************************************************************************* + * Copyright (c) 2013, Salesforce.com, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of Salesforce.com nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.salesforce.phoenix.end2end; + +import static com.salesforce.phoenix.util.TestUtil.*; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.sql.*; +import java.util.*; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.client.MetaScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.salesforce.phoenix.compile.ScanRanges; +import com.salesforce.phoenix.compile.StatementContext; +import com.salesforce.phoenix.filter.SkipScanFilter; +import com.salesforce.phoenix.iterate.*; +import com.salesforce.phoenix.jdbc.PhoenixConnection; +import com.salesforce.phoenix.query.KeyRange; +import com.salesforce.phoenix.query.QueryServices; +import com.salesforce.phoenix.schema.*; +import com.salesforce.phoenix.schema.RowKeySchema.RowKeySchemaBuilder; +import com.salesforce.phoenix.util.PhoenixRuntime; + + +/** + * Tests for {@link SkipRangeParallelIteratorRegionSplitter}. + */ +@RunWith(Parameterized.class) +public class SkipRangeParallelIteratorRegionSplitterTest extends BaseClientMangedTimeTest { + + private static final String TABLE_NAME = "TEST_SKIP_RANGE_PARALLEL_ITERATOR"; + private static final String DDL = "CREATE TABLE " + TABLE_NAME + " (id char(3) NOT NULL PRIMARY KEY, value integer)"; + private static final byte[] Ka1A = Bytes.toBytes("a1A"); + private static final byte[] Ka1B = Bytes.toBytes("a1B"); + private static final byte[] Ka1C = Bytes.toBytes("a1C"); + private static final byte[] Ka1D = Bytes.toBytes("a1D"); + private static final byte[] Ka1E = Bytes.toBytes("a1E"); + private static final byte[] Ka1F = Bytes.toBytes("a1F"); + private static final byte[] Ka1G = Bytes.toBytes("a1G"); + private static final byte[] Ka1H = Bytes.toBytes("a1H"); + private static final byte[] Ka1I = Bytes.toBytes("a1I"); + private static final byte[] Ka2A = Bytes.toBytes("a2A"); + + private final Scan scan; + private final ScanRanges scanRanges; + private final List expectedSplits; + + public SkipRangeParallelIteratorRegionSplitterTest(Scan scan, ScanRanges scanRanges, List expectedSplits) { + this.scan = scan; + this.scanRanges = scanRanges; + this.expectedSplits = expectedSplits; + } + + @Test + public void testGetSplitsWithSkipScanFilter() throws Exception { + long ts = nextTimestamp(); + TableRef table = initTableValues(ts, 3, 5); + NavigableMap regions = getRegions(table); + List splits = getSplits(table, scan, regions, scanRanges); + assertEquals("Unexpected number of splits: " + splits.size(), expectedSplits.size(), splits.size()); + for (int i=0; i data() { + List testCases = Lists.newArrayList(); + // Scan range is empty. + testCases.addAll( + foreach(ScanRanges.NOTHING, + new int[] {1,1,1}, + new KeyRange[] { })); + // Scan range is everything. + testCases.addAll( + foreach(ScanRanges.EVERYTHING, + new int[] {1,1,1}, + new KeyRange[] { + getKeyRange(KeyRange.UNBOUND_LOWER, true, Ka1A, false), + getKeyRange(Ka1A, true, Ka1B, false), + getKeyRange(Ka1B, true, Ka1E, false), + getKeyRange(Ka1E, true, Ka1G, false), + getKeyRange(Ka1G, true, Ka1I, false), + getKeyRange(Ka1I, true, Ka2A, false), + getKeyRange(Ka2A, true, KeyRange.UNBOUND_UPPER, false) + })); + // Scan range lies inside first region. + testCases.addAll( + foreach(new KeyRange[][]{ + { + getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true) + },{ + getKeyRange(Bytes.toBytes("0"), true, Bytes.toBytes("0"), true) + },{ + getKeyRange(Bytes.toBytes("A"), true, Bytes.toBytes("Z"), true) + }}, + new int[] {1,1,1}, + new KeyRange[] { + getKeyRange(KeyRange.UNBOUND_LOWER, true, Ka1A, false) + })); + // Scan range lies in between first and second, intersecting bound on second. + testCases.addAll( + foreach(new KeyRange[][]{ + { + getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true) + },{ + getKeyRange(Bytes.toBytes("0"), true, Bytes.toBytes("0"), true), + getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true) + },{ + getKeyRange(Bytes.toBytes("A"), true, Bytes.toBytes("A"), true) + }}, + new int[] {1,1,1}, + new KeyRange[] { + getKeyRange(KeyRange.UNBOUND_LOWER, true, Ka1A, false), + getKeyRange(Ka1A, true, Ka1B, false), + })); + // Scan range spans third, split into 3 due to concurrency config. + testCases.addAll( + foreach(new KeyRange[][]{ + { + getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true) + },{ + getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true) + },{ + getKeyRange(Bytes.toBytes("B"), true, Bytes.toBytes("E"), false) + }}, + new int[] {1,1,1}, + new KeyRange[] { + getKeyRange(Ka1B, true, Ka1C, false), + getKeyRange(Ka1C, true, Ka1D, false), + getKeyRange(Ka1D, true, Ka1E, false), + })); + // Scan range spans third, split into 3 due to concurrency config. + testCases.addAll( + foreach(new KeyRange[][]{ + { + getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true) + },{ + getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true) + },{ + getKeyRange(Bytes.toBytes("B"), true, Bytes.toBytes("E"), false) + }}, + new int[] {1,1,1}, + new KeyRange[] { + getKeyRange(Ka1B, true, Ka1C, false), + getKeyRange(Ka1C, true, Ka1D, false), + getKeyRange(Ka1D, true, Ka1E, false), + })); + // Scan range spans 2 ranges, split into 4 due to concurrency config. + testCases.addAll( + foreach(new KeyRange[][]{ + { + getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true) + },{ + getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true) + },{ + getKeyRange(Bytes.toBytes("F"), true, Bytes.toBytes("H"), false) + }}, + new int[] {1,1,1}, + new KeyRange[] { + getKeyRange(Ka1E, true, Ka1F, false), + getKeyRange(Ka1F, true, Ka1G, false), + getKeyRange(Ka1G, true, Ka1H, false), + getKeyRange(Ka1H, true, Ka1I, false), + })); + // Scan range spans more than 3 range, no split. + testCases.addAll( + foreach(new KeyRange[][]{ + { + getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true), + getKeyRange(Bytes.toBytes("b"), true, Bytes.toBytes("b"), true) + },{ + getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true), + getKeyRange(Bytes.toBytes("2"), true, Bytes.toBytes("2"), true), + },{ + getKeyRange(Bytes.toBytes("A"), true, Bytes.toBytes("A"), true), + getKeyRange(Bytes.toBytes("C"), true, Bytes.toBytes("D"), true), + getKeyRange(Bytes.toBytes("G"), true, Bytes.toBytes("G"), true) + }}, + new int[] {1,1,1}, + new KeyRange[] { + getKeyRange(Ka1A, true, Ka1B, false), + getKeyRange(Ka1B, true, Ka1E, false), + getKeyRange(Ka1G, true, Ka1I, false), + getKeyRange(Ka2A, true, KeyRange.UNBOUND_UPPER, false) + })); + return testCases; + } + + private static RowKeySchema buildSchema(int[] widths) { + RowKeySchemaBuilder builder = new RowKeySchemaBuilder().setMinNullable(10); + for (final int width : widths) { + builder.addField(new PDatum() { + @Override + public boolean isNullable() { + return false; + } + @Override + public PDataType getDataType() { + return PDataType.CHAR; + } + @Override + public Integer getByteSize() { + return width; + } + @Override + public Integer getMaxLength() { + return width; + } + @Override + public Integer getScale() { + return null; + } + }); + } + return builder.build(); + } + + private static Collection foreach(ScanRanges scanRanges, int[] widths, KeyRange[] expectedSplits) { + SkipScanFilter filter = new SkipScanFilter(scanRanges.getRanges(), buildSchema(widths)); + Scan scan = new Scan().setFilter(filter).setStartRow(KeyRange.UNBOUND_LOWER).setStopRow(KeyRange.UNBOUND_UPPER); + List ret = Lists.newArrayList(); + ret.add(new Object[] {scan, scanRanges, Arrays.asList(expectedSplits)}); + return ret; + } + + private static Collection foreach(KeyRange[][] ranges, int[] widths, KeyRange[] expectedSplits) { + RowKeySchema schema = buildSchema(widths); + List> slots = Lists.transform(Lists.newArrayList(ranges), ARRAY_TO_LIST); + SkipScanFilter filter = new SkipScanFilter(slots, schema); + // Always set start and stop key to max to verify we are using the information in skipscan + // filter over the scan's KMIN and KMAX. + Scan scan = new Scan().setFilter(filter).setStartRow(KeyRange.UNBOUND_LOWER).setStopRow(KeyRange.UNBOUND_UPPER); + ScanRanges scanRanges = ScanRanges.create(slots, schema); + List ret = Lists.newArrayList(); + ret.add(new Object[] {scan, scanRanges, Arrays.asList(expectedSplits)}); + return ret; + } + + private static final Function> ARRAY_TO_LIST = + new Function>() { + @Override + public List apply(KeyRange[] input) { + return Lists.newArrayList(input); + } + }; + + private static TableRef initTableValues(long ts, int targetQueryConcurrency, int maxQueryConcurrency) throws Exception { + byte[][] splits = new byte[][] {Ka1A, Ka1B, Ka1E, Ka1G, Ka1I, Ka2A}; + Configuration config = driver.getQueryServices().getConfig(); + config.setInt(QueryServices.MAX_QUERY_CONCURRENCY_ATTRIB, maxQueryConcurrency); + config.setInt(QueryServices.TARGET_QUERY_CONCURRENCY_ATTRIB, targetQueryConcurrency); + createTestTable(getUrl(),DDL,splits, ts-2); + String url = getUrl() + ";" + PhoenixRuntime.CURRENT_SCN_ATTRIB + "=" + ts; + Properties props = new Properties(TEST_PROPERTIES); + Connection conn = DriverManager.getConnection(url, props); + PreparedStatement stmt = conn.prepareStatement( + "upsert into " + TABLE_NAME + " VALUES (?, ?)"); + stmt.setString(1, new String("a1A")); + stmt.setInt(2, 1); + stmt.execute(); + stmt.setString(1, new String("a1E")); + stmt.setInt(2, 2); + stmt.execute(); + conn.commit(); + conn.close(); + PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class); + PSchema schema = pconn.getPMetaData().getSchemas().get(""); + return new TableRef(null,schema.getTable(TABLE_NAME),schema, ts); + } + + private static NavigableMap getRegions(TableRef table) throws IOException { + return MetaScanner.allTableRegions(driver.getQueryServices().getConfig(), table.getTableName(), false); + } + + private static List getSplits(TableRef table, final Scan scan, final NavigableMap regions, + final ScanRanges scanRanges) throws SQLException { + PhoenixConnection connection = DriverManager.getConnection(getUrl(), TEST_PROPERTIES).unwrap(PhoenixConnection.class); + StatementContext context = new StatementContext(connection, null, Collections.emptyList(), 0, scan); + context.setScanRanges(scanRanges); + SkipRangeParallelIteratorRegionSplitter splitter = SkipRangeParallelIteratorRegionSplitter.getInstance(context, table); + List keyRanges = splitter.getSplits(); + Collections.sort(keyRanges, new Comparator() { + @Override + public int compare(KeyRange o1, KeyRange o2) { + return Bytes.compareTo(o1.getLowerRange(),o2.getLowerRange()); + } + }); + return keyRanges; + } +} diff --git a/src/test/java/com/salesforce/phoenix/filter/SkipScanFilterRangeSplitTest.java b/src/test/java/com/salesforce/phoenix/filter/SkipScanFilterRangeSplitTest.java deleted file mode 100644 index 15d22b4c..00000000 --- a/src/test/java/com/salesforce/phoenix/filter/SkipScanFilterRangeSplitTest.java +++ /dev/null @@ -1,270 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2013, Salesforce.com, Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of Salesforce.com nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ -package com.salesforce.phoenix.filter; - -import static org.junit.Assert.assertEquals; - -import java.sql.SQLException; -import java.util.*; - -import org.apache.hadoop.hbase.util.Bytes; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import com.google.common.base.Function; -import com.google.common.collect.Lists; -import com.salesforce.phoenix.iterate.SkipRangeParallelIteratorRegionSplitter; -import com.salesforce.phoenix.query.BaseTest; -import com.salesforce.phoenix.query.KeyRange; -import com.salesforce.phoenix.schema.*; -import com.salesforce.phoenix.schema.RowKeySchema.RowKeySchemaBuilder; - - -/** - * Test for {@link SkipRangeParallelIteratorRegionSplitter}. - */ -@RunWith(Parameterized.class) -public class SkipScanFilterRangeSplitTest extends BaseTest { - - private final static int MAX_CONCURRENCY = 5; - private final SkipScanFilter filter; - private final List expectedSplits; - - public SkipScanFilterRangeSplitTest(List> slots, int[] widths, KeyRange[] expectedSplits) throws SQLException { - RowKeySchemaBuilder builder = new RowKeySchemaBuilder().setMinNullable(10); - for (final int width : widths) { - builder.addField(new PDatum() { - @Override - public boolean isNullable() { - return false; - } - @Override - public PDataType getDataType() { - return PDataType.CHAR; - } - @Override - public Integer getByteSize() { - return width; - } - @Override - public Integer getMaxLength() { - return width; - } - @Override - public Integer getScale() { - return null; - } - }); - } - this.filter = new SkipScanFilter(slots, builder.build()); - List splits = Arrays.asList(expectedSplits); - Collections.sort(splits, new Comparator() { - @Override - public int compare(KeyRange o1, KeyRange o2) { - return Bytes.compareTo(o1.getLowerRange(),o2.getLowerRange()); - } - }); - this.expectedSplits = splits; - } - - @Test - public void test() { - // table and alltableRegions not used for SkipScanParallelIterator. - List keyRanges = filter.generateSplitRanges(MAX_CONCURRENCY); - Collections.sort(keyRanges, new Comparator() { - @Override - public int compare(KeyRange o1, KeyRange o2) { - return Bytes.compareTo(o1.getLowerRange(),o2.getLowerRange()); - } - }); - assertEquals("Unexpected number of splits: " + keyRanges, expectedSplits.size(), keyRanges.size()); - for (int i=0; i data() { - List testCases = Lists.newArrayList(); - // All ranges are single keys. - testCases.addAll( - foreach(new KeyRange[][]{{ - getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true), - getKeyRange(Bytes.toBytes("b"), true, Bytes.toBytes("b"), true), - getKeyRange(Bytes.toBytes("c"), true, Bytes.toBytes("c"), true), - }}, - new int[] {1}, - new KeyRange[] { - getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("b"), false), - getKeyRange(Bytes.toBytes("b"), true, Bytes.toBytes("c"), false), - getKeyRange(Bytes.toBytes("c"), true, Bytes.toBytes("d"), false), - })); - testCases.addAll( - foreach(new KeyRange[][]{{ - getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true), - getKeyRange(Bytes.toBytes("k"), true, Bytes.toBytes("k"), true), - getKeyRange(Bytes.toBytes("t"), true, Bytes.toBytes("t"), true), - }}, - new int[] {1}, - new KeyRange[] { - getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("b"), false), - getKeyRange(Bytes.toBytes("k"), true, Bytes.toBytes("l"), false), - getKeyRange(Bytes.toBytes("t"), true, Bytes.toBytes("u"), false), - })); - testCases.addAll( - foreach(new KeyRange[][]{{ - getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true), - getKeyRange(Bytes.toBytes("b"), true, Bytes.toBytes("b"), true), - }, { - getKeyRange(Bytes.toBytes("c"), true, Bytes.toBytes("c"), true), - getKeyRange(Bytes.toBytes("d"), true, Bytes.toBytes("d"), true), - }}, - new int[] {1,1}, - new KeyRange[] { - getKeyRange(Bytes.toBytes("ac"), true, Bytes.toBytes("ad"), false), - getKeyRange(Bytes.toBytes("ad"), true, Bytes.toBytes("ae"), false), - getKeyRange(Bytes.toBytes("bc"), true, Bytes.toBytes("bd"), false), - getKeyRange(Bytes.toBytes("bd"), true, Bytes.toBytes("be"), false), - })); - // chunks 2 ranges into one. - testCases.addAll( - foreach(new KeyRange[][]{{ - getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true), - getKeyRange(Bytes.toBytes("b"), true, Bytes.toBytes("b"), true), - getKeyRange(Bytes.toBytes("c"), true, Bytes.toBytes("c"), true), - }, { - getKeyRange(Bytes.toBytes("d"), true, Bytes.toBytes("d"), true), - getKeyRange(Bytes.toBytes("e"), true, Bytes.toBytes("e"), true), - getKeyRange(Bytes.toBytes("f"), true, Bytes.toBytes("f"), true), - }}, - new int[] {1,1}, - new KeyRange[] { - getKeyRange(Bytes.toBytes("ad"), true, Bytes.toBytes("af"), false), - getKeyRange(Bytes.toBytes("af"), true, Bytes.toBytes("be"), false), - getKeyRange(Bytes.toBytes("be"), true, Bytes.toBytes("bg"), false), - getKeyRange(Bytes.toBytes("cd"), true, Bytes.toBytes("cf"), false), - getKeyRange(Bytes.toBytes("cf"), true, Bytes.toBytes("cg"), false), - })); - // Some slots contains range keys. - testCases.addAll( - foreach(new KeyRange[][]{{ - getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("b"), false), - getKeyRange(Bytes.toBytes("b"), true, Bytes.toBytes("c"), false), - getKeyRange(Bytes.toBytes("c"), true, Bytes.toBytes("d"), false), - }}, - new int[] {1}, - new KeyRange[] { - getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("b"), false), - getKeyRange(Bytes.toBytes("b"), true, Bytes.toBytes("c"), false), - getKeyRange(Bytes.toBytes("c"), true, Bytes.toBytes("d"), false), - })); - testCases.addAll( - foreach(new KeyRange[][]{{ - getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("b"), true), - getKeyRange(Bytes.toBytes("c"), true, Bytes.toBytes("d"), true), - getKeyRange(Bytes.toBytes("e"), true, Bytes.toBytes("f"), true), - },{ - getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("1"), true), - getKeyRange(Bytes.toBytes("2"), true, Bytes.toBytes("2"), true), - getKeyRange(Bytes.toBytes("3"), true, Bytes.toBytes("3"), true), - }}, - new int[] {1,1}, - new KeyRange[] { - getKeyRange(Bytes.toBytes("a1"), true, Bytes.toBytes("b4"), false), - getKeyRange(Bytes.toBytes("c1"), true, Bytes.toBytes("d4"), false), - getKeyRange(Bytes.toBytes("e1"), true, Bytes.toBytes("f4"), false), - })); - // 2 ranges in one chunk. - testCases.addAll( - foreach(new KeyRange[][]{{ - getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true), - getKeyRange(Bytes.toBytes("b"), true, Bytes.toBytes("b"), true), - getKeyRange(Bytes.toBytes("c"), true, Bytes.toBytes("c"), true), - },{ - getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("2"), true), - getKeyRange(Bytes.toBytes("3"), true, Bytes.toBytes("4"), true), - getKeyRange(Bytes.toBytes("5"), true, Bytes.toBytes("6"), true), - },{ - getKeyRange(Bytes.toBytes("A"), true, Bytes.toBytes("A"), true), - }}, - new int[] {1,1,1}, - new KeyRange[] { - getKeyRange(Bytes.toBytes("a1A"), true, Bytes.toBytes("a4B"), false), - getKeyRange(Bytes.toBytes("a5A"), true, Bytes.toBytes("b2B"), false), - getKeyRange(Bytes.toBytes("b3A"), true, Bytes.toBytes("b6B"), false), - getKeyRange(Bytes.toBytes("c1A"), true, Bytes.toBytes("c4B"), false), - getKeyRange(Bytes.toBytes("c5A"), true, Bytes.toBytes("c6B"), false), - })); - // Combination of cases. 19 ranges, 4 ranges in each chunk, 3 ranges in last chunk. - testCases.addAll( - foreach(new KeyRange[][]{{ - getKeyRange(Bytes.toBytes("a"), true, Bytes.toBytes("a"), true), - getKeyRange(Bytes.toBytes("b"), true, Bytes.toBytes("c"), true), - getKeyRange(Bytes.toBytes("d"), true, Bytes.toBytes("d"), true), - getKeyRange(Bytes.toBytes("e"), true, Bytes.toBytes("e"), true), - },{ - getKeyRange(Bytes.toBytes("1"), true, Bytes.toBytes("2"), true), - getKeyRange(Bytes.toBytes("3"), true, Bytes.toBytes("3"), true), - getKeyRange(Bytes.toBytes("4"), true, Bytes.toBytes("4"), true), - getKeyRange(Bytes.toBytes("5"), true, Bytes.toBytes("6"), true), - },{ - getKeyRange(Bytes.toBytes("A"), true, Bytes.toBytes("A"), true), - getKeyRange(Bytes.toBytes("B"), true, Bytes.toBytes("B"), true), - }}, - new int[] {1,1,1}, - new KeyRange[] { - getKeyRange(Bytes.toBytes("a1A"), true, Bytes.toBytes("a4B"), false), - getKeyRange(Bytes.toBytes("a4B"), true, Bytes.toBytes("d2C"), false), - getKeyRange(Bytes.toBytes("d3B"), true, Bytes.toBytes("d6C"), false), - getKeyRange(Bytes.toBytes("e1A"), true, Bytes.toBytes("e4B"), false), - getKeyRange(Bytes.toBytes("e4B"), true, Bytes.toBytes("e6C"), false), - })); - return testCases; - } - - private static Collection foreach(KeyRange[][] ranges, int[] widths, KeyRange[] expectedSplits) { - List> slots = Lists.transform(Lists.newArrayList(ranges), ARRAY_TO_LIST); - List ret = Lists.newArrayList(); - ret.add(new Object[] {slots, widths, expectedSplits}); - return ret; - } - - private static final Function> ARRAY_TO_LIST = - new Function>() { - @Override - public List apply(KeyRange[] input) { - return Lists.newArrayList(input); - } - }; -}