This is an automated email from the ASF dual-hosted git repository.
domgarguilo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/main by this push:
new b2c01a2532 Add RowRange object (#3342)
b2c01a2532 is described below
commit b2c01a2532d4c0d123e3d4128f8a5a1b97a30de2
Author: Dom G. <[email protected]>
AuthorDate: Wed Oct 1 14:00:13 2025 -0400
Add RowRange object (#3342)
* Add RowRange object which represents a range of rows with inclusive or
exclusive bounds
---------
Co-authored-by: Keith Turner <[email protected]>
Co-authored-by: Christopher Tubbs <[email protected]>
---
.../apache/accumulo/core/client/admin/FindMax.java | 50 +-
.../core/client/admin/TableOperations.java | 26 +-
.../core/client/rfile/RFileSummariesRetriever.java | 4 +-
.../core/clientImpl/TableOperationsImpl.java | 11 +-
.../org/apache/accumulo/core/data/RowRange.java | 656 +++++++++++++++
.../org/apache/accumulo/core/summary/Gatherer.java | 68 +-
.../accumulo/core/summary/SummaryReader.java | 2 +-
.../accumulo/core/summary/SummarySerializer.java | 52 +-
.../core/clientImpl/TableOperationsHelperTest.java | 4 +-
.../apache/accumulo/core/data/RowRangeTest.java | 910 +++++++++++++++++++++
.../server/compaction/CompactionPluginUtils.java | 5 +-
.../accumulo/shell/commands/MaxRowCommand.java | 11 +-
.../apache/accumulo/test/ComprehensiveITBase.java | 3 +-
...ComprehensiveTableOperationsIT_SimpleSuite.java | 3 +-
.../java/org/apache/accumulo/test/FindMaxIT.java | 76 +-
.../accumulo/test/NamespacesIT_SimpleSuite.java | 3 +-
16 files changed, 1764 insertions(+), 120 deletions(-)
diff --git
a/core/src/main/java/org/apache/accumulo/core/client/admin/FindMax.java
b/core/src/main/java/org/apache/accumulo/core/client/admin/FindMax.java
index a953d7d46f..71570e4acf 100644
--- a/core/src/main/java/org/apache/accumulo/core/client/admin/FindMax.java
+++ b/core/src/main/java/org/apache/accumulo/core/client/admin/FindMax.java
@@ -25,10 +25,10 @@ import java.util.Map.Entry;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.client.Scanner;
-import org.apache.accumulo.core.client.TableNotFoundException;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.PartialKey;
import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.SortedKeyIterator;
import org.apache.hadoop.io.Text;
@@ -93,18 +93,17 @@ public class FindMax {
return ret;
}
- private static Text _findMax(Scanner scanner, Text start, boolean inclStart,
Text end,
- boolean inclEnd) {
+ private static Text _findMax(Scanner scanner, RowRange rowRange) {
+ final Text lowerBound = rowRange.getLowerBound();
+ final Text upperBound = rowRange.getUpperBound();
+ final boolean lowerBoundInclusive = rowRange.isLowerBoundInclusive();
+ final boolean upperBoundInclusive = rowRange.isUpperBoundInclusive();
- // System.out.printf("findMax(%s, %s, %s, %s)%n",
Key.toPrintableString(start.getBytes(), 0,
- // start.getLength(), 1000), inclStart,
- // Key.toPrintableString(end.getBytes(), 0, end.getLength(), 1000),
inclEnd);
-
- int cmp = start.compareTo(end);
+ int cmp = lowerBound.compareTo(upperBound);
if (cmp >= 0) {
- if (inclStart && inclEnd && cmp == 0) {
- scanner.setRange(new Range(start, true, end, true));
+ if (lowerBoundInclusive && upperBoundInclusive && cmp == 0) {
+ scanner.setRange(new Range(lowerBound, true, upperBound, true));
Iterator<Entry<Key,Value>> iter = scanner.iterator();
if (iter.hasNext()) {
return iter.next().getKey().getRow();
@@ -114,11 +113,12 @@ public class FindMax {
return null;
}
- Text mid = findMidPoint(start, end);
+ Text mid = findMidPoint(lowerBound, upperBound);
// System.out.println("mid = :"+Key.toPrintableString(mid.getBytes(), 0,
mid.getLength(),
// 1000)+":");
- scanner.setRange(new Range(mid, mid.equals(start) ? inclStart : true, end,
inclEnd));
+ scanner.setRange(new Range(mid, mid.equals(lowerBound) ?
lowerBoundInclusive : true, upperBound,
+ upperBoundInclusive));
Iterator<Entry<Key,Value>> iter = scanner.iterator();
@@ -135,7 +135,8 @@ public class FindMax {
return next.getRow();
}
- Text ret = _findMax(scanner, next.followingKey(PartialKey.ROW).getRow(),
true, end, inclEnd);
+ Text ret = _findMax(scanner,
RowRange.range(next.followingKey(PartialKey.ROW).getRow(), true,
+ upperBound, upperBoundInclusive));
if (ret == null) {
return next.getRow();
} else {
@@ -143,7 +144,8 @@ public class FindMax {
}
} else {
- return _findMax(scanner, start, inclStart, mid, mid.equals(start) ?
inclStart : false);
+ return _findMax(scanner, RowRange.range(lowerBound, lowerBoundInclusive,
mid,
+ mid.equals(lowerBound) ? lowerBoundInclusive : false));
}
}
@@ -163,22 +165,26 @@ public class FindMax {
return end;
}
- public static Text findMax(Scanner scanner, Text start, boolean is, Text
end, boolean ie)
- throws TableNotFoundException {
+ public static Text findMax(Scanner scanner, RowRange rowRange) {
scanner.setBatchSize(12);
IteratorSetting cfg = new IteratorSetting(Integer.MAX_VALUE,
SortedKeyIterator.class);
scanner.addScanIterator(cfg);
- if (start == null) {
- start = new Text();
- is = true;
+ Text lowerBound = rowRange.getLowerBound();
+ boolean lowerBoundInclusive = rowRange.isLowerBoundInclusive();
+ if (lowerBound == null) {
+ lowerBound = new Text();
+ lowerBoundInclusive = true;
}
- if (end == null) {
- end = findInitialEnd(scanner);
+ Text upperBound = rowRange.getUpperBound();
+ final boolean upperBoundInclusive = rowRange.isUpperBoundInclusive();
+ if (upperBound == null) {
+ upperBound = findInitialEnd(scanner);
}
- return _findMax(scanner, start, is, end, ie);
+ return _findMax(scanner,
+ RowRange.range(lowerBound, lowerBoundInclusive, upperBound,
upperBoundInclusive));
}
}
diff --git
a/core/src/main/java/org/apache/accumulo/core/client/admin/TableOperations.java
b/core/src/main/java/org/apache/accumulo/core/client/admin/TableOperations.java
index d08e6978d8..147383df11 100644
---
a/core/src/main/java/org/apache/accumulo/core/client/admin/TableOperations.java
+++
b/core/src/main/java/org/apache/accumulo/core/client/admin/TableOperations.java
@@ -44,6 +44,7 @@ import org.apache.accumulo.core.client.summary.Summarizer;
import org.apache.accumulo.core.client.summary.SummarizerConfiguration;
import org.apache.accumulo.core.data.LoadPlan;
import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.core.security.TablePermission;
@@ -249,6 +250,7 @@ public interface TableOperations {
* Finds the max row within a given range. To find the max row in a table,
pass null for start and
* end row.
*
+ * @param tableName the table to search
* @param auths find the max row that can seen with these auths
* @param startRow row to start looking at, null means -Infinity
* @param startInclusive determines if the start row is included
@@ -256,9 +258,29 @@ public interface TableOperations {
* @param endInclusive determines if the end row is included
*
* @return The max row in the range, or null if there is no visible data in
the range.
+ *
+ * @deprecated since 4.0.0, use {@link #getMaxRow(String, Authorizations,
RowRange)} instead
+ */
+ @Deprecated(since = "4.0.0")
+ default Text getMaxRow(String tableName, Authorizations auths, Text startRow,
+ boolean startInclusive, Text endRow, boolean endInclusive)
+ throws TableNotFoundException, AccumuloException,
AccumuloSecurityException {
+ return getMaxRow(tableName, auths,
+ RowRange.range(startRow, startInclusive, endRow, endInclusive));
+ }
+
+ /**
+ * Finds the max row within a given row range. To find the max row in the
whole table, pass
+ * {@link RowRange#all()} as the row range.
+ *
+ * @param tableName the table to search
+ * @param auths find the max row that can seen with these auths
+ * @param range the range of rows to search
+ *
+ * @return The max row in the range, or null if there is no visible data in
the range.
+ * @since 4.0.0
*/
- Text getMaxRow(String tableName, Authorizations auths, Text startRow,
boolean startInclusive,
- Text endRow, boolean endInclusive)
+ Text getMaxRow(String tableName, Authorizations auths, RowRange range)
throws TableNotFoundException, AccumuloException,
AccumuloSecurityException;
/**
diff --git
a/core/src/main/java/org/apache/accumulo/core/client/rfile/RFileSummariesRetriever.java
b/core/src/main/java/org/apache/accumulo/core/client/rfile/RFileSummariesRetriever.java
index 69b03c4a14..b53f7042b3 100644
---
a/core/src/main/java/org/apache/accumulo/core/client/rfile/RFileSummariesRetriever.java
+++
b/core/src/main/java/org/apache/accumulo/core/client/rfile/RFileSummariesRetriever.java
@@ -33,9 +33,9 @@ import
org.apache.accumulo.core.client.rfile.RFileScannerBuilder.InputArgs;
import org.apache.accumulo.core.client.summary.SummarizerConfiguration;
import org.apache.accumulo.core.client.summary.Summary;
import org.apache.accumulo.core.crypto.CryptoFactoryLoader;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.spi.crypto.CryptoEnvironment;
import org.apache.accumulo.core.spi.crypto.CryptoService;
-import org.apache.accumulo.core.summary.Gatherer;
import org.apache.accumulo.core.summary.SummarizerFactory;
import org.apache.accumulo.core.summary.SummaryCollection;
import org.apache.accumulo.core.summary.SummaryReader;
@@ -94,7 +94,7 @@ class RFileSummariesRetriever implements
SummaryInputArguments, SummaryFSOptions
SummaryReader fileSummary =
SummaryReader.load(in.getFileSystem().getConf(), sources[i],
"source-" + i, summarySelector, factory, cservice);
SummaryCollection sc = fileSummary
- .getSummaries(Collections.singletonList(new
Gatherer.RowRange(startRow, endRow)));
+ .getSummaries(Collections.singletonList(RowRange.range(startRow,
false, endRow, true)));
all.merge(sc, factory);
}
return all.getSummaries();
diff --git
a/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java
b/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java
index 082b57169c..a326f2336a 100644
---
a/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java
+++
b/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java
@@ -131,6 +131,7 @@ import org.apache.accumulo.core.data.ByteSequence;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.PartialKey;
import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.core.data.TabletId;
import org.apache.accumulo.core.data.constraints.Constraint;
@@ -1571,12 +1572,12 @@ public class TableOperationsImpl extends
TableOperationsHelper {
}
@Override
- public Text getMaxRow(String tableName, Authorizations auths, Text startRow,
- boolean startInclusive, Text endRow, boolean endInclusive) throws
TableNotFoundException {
+ public Text getMaxRow(String tableName, Authorizations auths, RowRange
rowRange)
+ throws TableNotFoundException {
EXISTING_TABLE_NAME.validate(tableName);
-
- Scanner scanner = context.createScanner(tableName, auths);
- return FindMax.findMax(scanner, startRow, startInclusive, endRow,
endInclusive);
+ try (Scanner scanner = context.createScanner(tableName, auths)) {
+ return FindMax.findMax(scanner, rowRange);
+ }
}
@Override
diff --git a/core/src/main/java/org/apache/accumulo/core/data/RowRange.java
b/core/src/main/java/org/apache/accumulo/core/data/RowRange.java
new file mode 100644
index 0000000000..56553a7c7e
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/data/RowRange.java
@@ -0,0 +1,656 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * https://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 org.apache.accumulo.core.data;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.hadoop.io.BinaryComparable;
+import org.apache.hadoop.io.Text;
+
+/**
+ * This class is used to specify a range of rows.
+ *
+ * @since 4.0.0
+ */
+public class RowRange implements Comparable<RowRange> {
+
+ private static final Comparator<Text> LOWER_BOUND_COMPARATOR =
+ Comparator.nullsFirst(Text::compareTo);
+ private static final Comparator<Text> UPPER_BOUND_COMPARATOR =
+ Comparator.nullsLast(Text::compareTo);
+ private static final Comparator<RowRange> ROW_RANGE_COMPARATOR = (r1, r2) ->
{
+ if (r1.lowerBound == null && r2.lowerBound == null) {
+ return 0;
+ } else if (r1.lowerBound == null) {
+ return -1;
+ } else if (r2.lowerBound == null) {
+ return 1;
+ }
+ return r1.compareTo(r2);
+ };
+
+ final private Text lowerBound;
+ final private Text upperBound;
+ final private boolean lowerBoundInclusive;
+ final private boolean upperBoundInclusive;
+ final private boolean infiniteLowerBound;
+ final private boolean infiniteUpperBound;
+
+ /**
+ * Creates a range that includes all possible rows.
+ */
+ public static RowRange all() {
+ return range((Text) null, true, null, true);
+ }
+
+ /**
+ * Creates a range of rows from lowerBound exclusive to upperBound exclusive.
+ *
+ * @param lowerBound starting row
+ * @param upperBound ending row
+ * @throws IllegalArgumentException if upper bound is before lower bound
+ */
+ public static RowRange open(Text lowerBound, Text upperBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.lessThan(row)?");
+ requireNonNull(upperBound, "Did you mean to use
RowRange.greaterThan(row)?");
+ return range(lowerBound, false, upperBound, false);
+ }
+
+ /**
+ * Creates a range of rows from lowerBound exclusive to upperBound exclusive.
+ *
+ * @param lowerBound starting row
+ * @param upperBound ending row
+ * @throws IllegalArgumentException if upper bound is before lower bound
+ */
+ public static RowRange open(CharSequence lowerBound, CharSequence
upperBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.lessThan(row)?");
+ requireNonNull(upperBound, "Did you mean to use
RowRange.greaterThan(row)?");
+ return range(lowerBound, false, upperBound, false);
+ }
+
+ /**
+ * Creates a range of rows from lowerBound inclusive to upperBound inclusive.
+ *
+ * @param lowerBound starting row
+ * @param upperBound ending row
+ * @throws IllegalArgumentException if upper bound is before lower bound
+ */
+ public static RowRange closed(Text lowerBound, Text upperBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.atMost(row)?");
+ requireNonNull(upperBound, "Did you mean to use RowRange.atLeast(row)?");
+ return range(lowerBound, true, upperBound, true);
+ }
+
+ /**
+ * Creates a range of rows from lowerBound inclusive to upperBound inclusive.
+ *
+ * @param lowerBound starting row
+ * @param upperBound ending row
+ * @throws IllegalArgumentException if upper bound is before lower bound
+ */
+ public static RowRange closed(CharSequence lowerBound, CharSequence
upperBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.atMost(row)?");
+ requireNonNull(upperBound, "Did you mean to use RowRange.atLeast(row)?");
+ return range(lowerBound, true, upperBound, true);
+ }
+
+ /**
+ * Creates a range that covers an entire row.
+ *
+ * @param row row to cover
+ */
+ public static RowRange closed(Text row) {
+ requireNonNull(row, "Did you mean to use RowRange.all()?");
+ return range(row, true, row, true);
+ }
+
+ /**
+ * Creates a range that covers an entire row.
+ *
+ * @param row row to cover
+ */
+ public static RowRange closed(CharSequence row) {
+ requireNonNull(row, "Did you mean to use RowRange.all()?");
+ return range(row, true, row, true);
+ }
+
+ /**
+ * Creates a range of rows from lowerBound exclusive to upperBound inclusive.
+ *
+ * @param lowerBound starting row
+ * @param upperBound ending row
+ * @throws IllegalArgumentException if upper bound is before lower bound
+ */
+ public static RowRange openClosed(Text lowerBound, Text upperBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.atMost(row)?");
+ requireNonNull(upperBound, "Did you mean to use
RowRange.greaterThan(row)?");
+ return range(lowerBound, false, upperBound, true);
+ }
+
+ /**
+ * Creates a range of rows from lowerBound exclusive to upperBound inclusive.
+ *
+ * @param lowerBound starting row
+ * @param upperBound ending row
+ * @throws IllegalArgumentException if upper bound is before lower bound
+ */
+ public static RowRange openClosed(CharSequence lowerBound, CharSequence
upperBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.atMost(row)?");
+ requireNonNull(upperBound, "Did you mean to use
RowRange.greaterThan(row)?");
+ return range(lowerBound, false, upperBound, true);
+ }
+
+ /**
+ * Creates a range of rows from lowerBound inclusive to upperBound exclusive.
+ *
+ * @param lowerBound starting row
+ * @param upperBound ending row
+ * @throws IllegalArgumentException if upper bound is before lower bound
+ */
+ public static RowRange closedOpen(Text lowerBound, Text upperBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.lessThan(row)?");
+ requireNonNull(upperBound, "Did you mean to use RowRange.atLeast(row)?");
+ return range(lowerBound, true, upperBound, false);
+ }
+
+ /**
+ * Creates a range of rows from lowerBound inclusive to upperBound exclusive.
+ *
+ * @param lowerBound starting row
+ * @param upperBound ending row
+ * @throws IllegalArgumentException if upper bound is before lower bound
+ */
+ public static RowRange closedOpen(CharSequence lowerBound, CharSequence
upperBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.lessThan(row)?");
+ requireNonNull(upperBound, "Did you mean to use RowRange.atLeast(row)?");
+ return range(lowerBound, true, upperBound, false);
+ }
+
+ /**
+ * Creates a range of rows strictly greater than the given row.
+ *
+ * @param lowerBound starting row
+ */
+ public static RowRange greaterThan(Text lowerBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.all()?");
+ return range(lowerBound, false, null, true);
+ }
+
+ /**
+ * Creates a range of rows strictly greater than the given row.
+ *
+ * @param lowerBound starting row
+ */
+ public static RowRange greaterThan(CharSequence lowerBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.all()?");
+ return range(lowerBound, false, null, true);
+ }
+
+ /**
+ * Creates a range of rows greater than or equal to the given row.
+ *
+ * @param lowerBound starting row
+ */
+ public static RowRange atLeast(Text lowerBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.all()?");
+ return range(lowerBound, true, null, true);
+ }
+
+ /**
+ * Creates a range of rows greater than or equal to the given row.
+ *
+ * @param lowerBound starting row
+ */
+ public static RowRange atLeast(CharSequence lowerBound) {
+ requireNonNull(lowerBound, "Did you mean to use RowRange.all()?");
+ return range(lowerBound, true, null, true);
+ }
+
+ /**
+ * Creates a range of rows strictly less than the given row.
+ *
+ * @param upperBound ending row
+ */
+ public static RowRange lessThan(Text upperBound) {
+ requireNonNull(upperBound, "Did you mean to use RowRange.all()?");
+ return range(null, true, upperBound, false);
+ }
+
+ /**
+ * Creates a range of rows strictly less than the given row.
+ *
+ * @param upperBound ending row
+ */
+ public static RowRange lessThan(CharSequence upperBound) {
+ requireNonNull(upperBound, "Did you mean to use RowRange.all()?");
+ return range(null, true, upperBound, false);
+ }
+
+ /**
+ * Creates a range of rows less than or equal to the given row.
+ *
+ * @param upperBound ending row
+ */
+ public static RowRange atMost(Text upperBound) {
+ requireNonNull(upperBound, "Did you mean to use RowRange.all()?");
+ return range(null, true, upperBound, true);
+ }
+
+ /**
+ * Creates a range of rows less than or equal to the given row.
+ *
+ * @param upperBound ending row
+ */
+ public static RowRange atMost(CharSequence upperBound) {
+ requireNonNull(upperBound, "Did you mean to use RowRange.all()?");
+ return range(null, true, upperBound, true);
+ }
+
+ /**
+ * Creates a range of rows from the given lowerBound to the given upperBound.
+ *
+ * @param lowerBound starting row; set to null for the smallest possible row
(an empty one)
+ * @param lowerBoundInclusive set to true to include lower bound, false to
skip
+ * @param upperBound ending row; set to null for positive infinity
+ * @param upperBoundInclusive set to true to include upper bound, false to
skip
+ * @throws IllegalArgumentException if upper bound is before lower bound
+ */
+ public static RowRange range(Text lowerBound, boolean lowerBoundInclusive,
Text upperBound,
+ boolean upperBoundInclusive) {
+ return new RowRange(lowerBound, lowerBoundInclusive, upperBound,
upperBoundInclusive);
+ }
+
+ /**
+ * Creates a range of rows from the given lowerBound to the given upperBound.
+ *
+ * @param lowerBound starting row; set to null for the smallest possible row
(an empty one)
+ * @param lowerBoundInclusive set to true to include lower bound, false to
skip
+ * @param upperBound ending row; set to null for positive infinity
+ * @param upperBoundInclusive set to true to include upper bound, false to
skip
+ * @throws IllegalArgumentException if upper bound is before lower bound
+ */
+ public static RowRange range(CharSequence lowerBound, boolean
lowerBoundInclusive,
+ CharSequence upperBound, boolean upperBoundInclusive) {
+ return new RowRange(lowerBound == null ? null : new
Text(lowerBound.toString()),
+ lowerBoundInclusive, upperBound == null ? null : new
Text(upperBound.toString()),
+ upperBoundInclusive);
+ }
+
+ /**
+ * Creates a range of rows from the given lowerBound to the given upperBound.
+ *
+ * @param lowerBound starting row; set to null for the smallest possible row
(an empty one)
+ * @param lowerBoundInclusive set to true to include lower bound, false to
skip
+ * @param upperBound ending row; set to null for positive infinity
+ * @param upperBoundInclusive set to true to include upper bound, false to
skip
+ * @throws IllegalArgumentException if upper bound is before lower bound
+ */
+ private RowRange(Text lowerBound, boolean lowerBoundInclusive, Text
upperBound,
+ boolean upperBoundInclusive) {
+ this.lowerBound = lowerBound == null ? null : new Text(lowerBound);
+ this.lowerBoundInclusive = lowerBoundInclusive;
+ this.infiniteLowerBound = lowerBound == null;
+ this.upperBound = upperBound == null ? null : new Text(upperBound);
+ this.upperBoundInclusive = upperBoundInclusive;
+ this.infiniteUpperBound = upperBound == null;
+
+ if (!infiniteLowerBound && !infiniteUpperBound && isAfterImpl(upperBound))
{
+ throw new IllegalArgumentException(
+ "Lower bound must be less than upper bound in row range " + this);
+ }
+ }
+
+ public Text getLowerBound() {
+ return lowerBound;
+ }
+
+ /**
+ * @return true if the lower bound is inclusive or null, otherwise false
+ */
+ public boolean isLowerBoundInclusive() {
+ return lowerBoundInclusive || lowerBound == null;
+ }
+
+ public Text getUpperBound() {
+ return upperBound;
+ }
+
+ /**
+ * @return true if the upper bound is inclusive or null, otherwise false
+ */
+ public boolean isUpperBoundInclusive() {
+ return upperBoundInclusive || upperBound == null;
+ }
+
+ /**
+ * Converts this row range to a {@link Range} object.
+ */
+ public Range asRange() {
+ return new Range(lowerBound, lowerBoundInclusive, upperBound,
upperBoundInclusive);
+ }
+
+ /**
+ * @param that row to check
+ * @return true if this row range's lower bound is greater than the given
row, otherwise false
+ */
+ public boolean isAfter(Text that) {
+ return isAfterImpl(that);
+ }
+
+ /**
+ * @param that row to check
+ * @return true if this row range's upper bound is less than the given row,
otherwise false
+ */
+ public boolean isBefore(Text that) {
+ if (infiniteUpperBound) {
+ return false;
+ }
+
+ if (upperBoundInclusive) {
+ return that.compareTo(upperBound) > 0;
+ }
+ return that.compareTo(upperBound) >= 0;
+ }
+
+ /**
+ * Implements logic of {@link #isAfter(Text)}, but in a private method, so
that it can be safely
+ * used by constructors if a subclass overrides that {@link #isAfter(Text)}
+ */
+ private boolean isAfterImpl(Text row) {
+ if (this.infiniteLowerBound) {
+ return false;
+ }
+
+ if (lowerBoundInclusive) {
+ return row.compareTo(lowerBound) < 0;
+ }
+ return row.compareTo(lowerBound) <= 0;
+ }
+
+ @Override
+ public String toString() {
+ final String startRangeSymbol = (lowerBoundInclusive && lowerBound !=
null) ? "[" : "(";
+ final String lowerBound = this.lowerBound == null ? "-inf" :
this.lowerBound.toString();
+ final String upperBound = this.upperBound == null ? "+inf" :
this.upperBound.toString();
+ final String endRangeSymbol = (upperBoundInclusive && this.upperBound !=
null) ? "]" : ")";
+ return startRangeSymbol + lowerBound + "," + upperBound + endRangeSymbol;
+ }
+
+ @Override
+ public int hashCode() {
+ int lowerHash = infiniteLowerBound ? 0 : lowerBound.hashCode() +
(lowerBoundInclusive ? 1 : 0);
+ int upperHash = infiniteUpperBound ? 0 : upperBound.hashCode() +
(upperBoundInclusive ? 1 : 0);
+
+ return lowerHash + upperHash;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof RowRange)) {
+ return false;
+ }
+ return equals((RowRange) other);
+ }
+
+ /**
+ * Determines if this row range equals another.
+ *
+ * @param other range to compare
+ * @return true if row ranges are equal, otherwise false
+ */
+ public boolean equals(RowRange other) {
+ return compareTo(other) == 0;
+ }
+
+ /**
+ * Compares this row range to another row range. Compares in order: lower
bound, inclusiveness of
+ * lower bound, upper bound, inclusiveness of upper bound. Infinite rows
sort last, and
+ * non-infinite rows are compared with {@link
Text#compareTo(BinaryComparable)}. Inclusive sorts
+ * before non-inclusive.
+ *
+ * @param other row range to compare
+ * @return comparison result
+ */
+ @Override
+ public int compareTo(RowRange other) {
+ // Compare infinite lower bounds
+ int comp = Boolean.compare(this.infiniteLowerBound,
other.infiniteLowerBound);
+
+ if (comp == 0) {
+ // Compare non-infinite lower bounds and lower bound inclusiveness
+ if (!this.infiniteLowerBound) {
+ comp = LOWER_BOUND_COMPARATOR.compare(this.lowerBound,
other.lowerBound);
+ if (comp == 0) {
+ comp = Boolean.compare(other.lowerBoundInclusive,
this.lowerBoundInclusive);
+ }
+ }
+ }
+
+ if (comp == 0) {
+ // Compare infinite upper bounds
+ comp = Boolean.compare(this.infiniteUpperBound,
other.infiniteUpperBound);
+
+ // Compare non-infinite upper bounds and upper bound inclusiveness
+ if (comp == 0 && !this.infiniteUpperBound) {
+ comp = UPPER_BOUND_COMPARATOR.compare(this.upperBound,
other.upperBound);
+ if (comp == 0) {
+ comp = Boolean.compare(this.upperBoundInclusive,
other.upperBoundInclusive);
+ }
+ }
+ }
+
+ return comp;
+ }
+
+ /**
+ * Determines if this row range contains the given row.
+ *
+ * @param row row to check
+ * @return true if the row is contained in the row range, otherwise false
+ */
+ public boolean contains(Text row) {
+ if (infiniteLowerBound) {
+ return !isBefore(row);
+ } else if (infiniteUpperBound) {
+ return !isAfter(row);
+ } else {
+ return !isAfter(row) && !isBefore(row);
+ }
+ }
+
+ /**
+ * Merges overlapping and adjacent row ranges. Adjacent ranges are those
that share an endpoint
+ * where at least one range includes that endpoint. For example, given the
following input:
+ *
+ * <pre>
+ * [a,c], (c, d], (g,m), (j,t]
+ * </pre>
+ *
+ * the following row ranges would be returned:
+ *
+ * <pre>
+ * [a,d], (g,t]
+ * </pre>
+ *
+ * @param rowRanges the collection of row ranges to merge
+ * @return a list of merged row ranges
+ */
+ public static List<RowRange> mergeOverlapping(Collection<RowRange>
rowRanges) {
+ if (rowRanges.isEmpty()) {
+ return Collections.emptyList();
+ }
+ if (rowRanges.size() == 1) {
+ return Collections.singletonList(rowRanges.iterator().next());
+ }
+
+ List<RowRange> sortedRowRanges = new ArrayList<>(rowRanges);
+ // Sort row ranges by their lowerBound values
+ sortedRowRanges.sort(ROW_RANGE_COMPARATOR);
+
+ ArrayList<RowRange> mergedRowRanges = new ArrayList<>(rowRanges.size());
+
+ // Initialize the current range for merging
+ RowRange currentRange = sortedRowRanges.get(0);
+ boolean currentLowerBoundInclusive =
sortedRowRanges.get(0).lowerBoundInclusive;
+
+ // Iterate through the sorted row ranges, merging overlapping and adjacent
ranges
+ for (int i = 1; i < sortedRowRanges.size(); i++) {
+ if (currentRange.infiniteLowerBound && currentRange.infiniteUpperBound) {
+ // The current range covers all possible rows, no further merging
needed
+ break;
+ }
+
+ RowRange nextRange = sortedRowRanges.get(i);
+
+ boolean lowerBoundsEqual = (currentRange.lowerBound == null &&
nextRange.lowerBound == null)
+ || (currentRange.lowerBound != null
+ && currentRange.lowerBound.equals(nextRange.lowerBound));
+
+ boolean shouldMerge = lowerBoundsEqual ||
currentRange.infiniteUpperBound;
+
+ if (!shouldMerge) {
+ if (nextRange.lowerBound == null) {
+ shouldMerge = true;
+ } else if (!currentRange.isBefore(nextRange.lowerBound)) {
+ shouldMerge = true;
+ } else if (currentRange.upperBound != null
+ && currentRange.upperBound.equals(nextRange.lowerBound)
+ && nextRange.lowerBoundInclusive) {
+ shouldMerge = true;
+ }
+ }
+
+ if (shouldMerge) {
+ int comparison;
+ if (nextRange.infiniteUpperBound) {
+ comparison = 1;
+ } else if (currentRange.upperBound == null) {
+ comparison = -1;
+ } else {
+ comparison = nextRange.upperBound.compareTo(currentRange.upperBound);
+ }
+ if (comparison > 0 || (comparison == 0 &&
nextRange.upperBoundInclusive)) {
+ currentRange = RowRange.range(currentRange.lowerBound,
currentLowerBoundInclusive,
+ nextRange.upperBound, nextRange.upperBoundInclusive);
+ } // else current range contains the next range
+ } else {
+ // No overlap or adjacency
+ // add the current range to the merged list and update the current
range
+ mergedRowRanges.add(currentRange);
+ currentRange = nextRange;
+ currentLowerBoundInclusive = nextRange.lowerBoundInclusive;
+ }
+ }
+
+ // Add the final current range to the merged list
+ mergedRowRanges.add(currentRange);
+
+ return mergedRowRanges;
+ }
+
+ /**
+ * Creates a row range which represents the intersection of this row range
and the given row
+ * range. The following example will print true.
+ *
+ * <pre>
+ * RowRange rowRange1 = RowRange.closed("a", "f");
+ * RowRange rowRange2 = RowRange.closed("c", "n");
+ * RowRange rowRange3 = rowRange1.clip(rowRange2);
+ * System.out.println(rowRange3.equals(RowRange.closed("c",
"f")));
+ * </pre>
+ *
+ * @param rowRange row range to clip to
+ * @return the intersection of this row range and the given row range
+ * @throws IllegalArgumentException if row ranges do not overlap
+ */
+ public RowRange clip(RowRange rowRange) {
+ return clip(rowRange, false);
+ }
+
+ /**
+ * Creates a row range which represents the intersection of this row range
and the given row
+ * range. Unlike {@link #clip(RowRange)}, this method can optionally return
null if the row ranges
+ * do not overlap, instead of throwing an exception. The
returnNullIfDisjoint parameter controls
+ * this behavior.
+ *
+ * @param rowRange row range to clip to
+ * @param returnNullIfDisjoint true to return null if row ranges are
disjoint, false to throw an
+ * exception
+ * @return the intersection of this row range and the given row range, or
null if row ranges do
+ * not overlap and returnNullIfDisjoint is true
+ * @throws IllegalArgumentException if row ranges do not overlap and
returnNullIfDisjoint is false
+ * @see #clip(RowRange)
+ */
+ public RowRange clip(RowRange rowRange, boolean returnNullIfDisjoint) {
+ // Initialize lower bound and upper bound values with the current
instance's values
+ Text lowerBound = this.lowerBound;
+ boolean lowerBoundInclusive = this.lowerBoundInclusive;
+ Text upperBound = this.upperBound;
+ boolean upperBoundInclusive = this.upperBoundInclusive;
+
+ // If the input rowRange has a defined lowerBound, update lowerBound and
lowerBoundInclusive if
+ // needed
+ if (rowRange.lowerBound != null) {
+ // If the input rowRange's lowerBound is after this instance's
upperBound or equal but not
+ // inclusive, they do not overlap
+ if (isBefore(rowRange.lowerBound) ||
(rowRange.lowerBound.equals(this.upperBound)
+ && !(rowRange.lowerBoundInclusive && this.upperBoundInclusive))) {
+ if (returnNullIfDisjoint) {
+ return null;
+ }
+ throw new IllegalArgumentException("RowRange " + rowRange + " does not
overlap " + this);
+ } else if (!isAfter(rowRange.lowerBound)) {
+ // If the input rowRange's lowerBound is within this instance's range,
use it as the new
+ // lowerBound
+ lowerBound = rowRange.lowerBound;
+ lowerBoundInclusive = rowRange.lowerBoundInclusive;
+ }
+ }
+
+ // If the input rowRange has a defined upperBound, update upperBound and
upperBoundInclusive if
+ // needed
+ if (rowRange.upperBound != null) {
+ // If the input rowRange's upperBound is before this instance's
lowerBound or equal but not
+ // inclusive, they do not overlap
+ if (isAfter(rowRange.upperBound) ||
(rowRange.upperBound.equals(this.lowerBound)
+ && !(rowRange.upperBoundInclusive && this.lowerBoundInclusive))) {
+ if (returnNullIfDisjoint) {
+ return null;
+ }
+ throw new IllegalArgumentException("RowRange " + rowRange + " does not
overlap " + this);
+ } else if (!isBefore(rowRange.upperBound)) {
+ // If the input rowRange's upperBound is within this instance's range,
use it as the new
+ // upperBound
+ upperBound = rowRange.upperBound;
+ upperBoundInclusive = rowRange.upperBoundInclusive;
+ }
+ }
+
+ // Return a new RowRange instance representing the intersection of the two
ranges
+ return RowRange.range(lowerBound, lowerBoundInclusive, upperBound,
upperBoundInclusive);
+ }
+
+}
diff --git a/core/src/main/java/org/apache/accumulo/core/summary/Gatherer.java
b/core/src/main/java/org/apache/accumulo/core/summary/Gatherer.java
index d9258a1130..c67965a926 100644
--- a/core/src/main/java/org/apache/accumulo/core/summary/Gatherer.java
+++ b/core/src/main/java/org/apache/accumulo/core/summary/Gatherer.java
@@ -57,8 +57,8 @@ import org.apache.accumulo.core.conf.AccumuloConfiguration;
import org.apache.accumulo.core.data.ByteSequence;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.data.TableId;
-import org.apache.accumulo.core.dataImpl.KeyExtent;
import org.apache.accumulo.core.dataImpl.thrift.TRowRange;
import org.apache.accumulo.core.dataImpl.thrift.TSummaries;
import org.apache.accumulo.core.dataImpl.thrift.TSummaryRequest;
@@ -216,15 +216,16 @@ public class Gatherer {
location = tservers.get(idx).toHostPortString();
}
+ Function<RowRange,TRowRange> toTRowRange =
+ r -> new TRowRange(TextUtil.getByteBuffer(r.getLowerBound()),
+ TextUtil.getByteBuffer(r.getUpperBound()));
+
// merge contiguous ranges
List<Range> merged = Range.mergeOverlapping(entry.getValue().stream()
.map(tm ->
tm.getExtent().toDataRange()).collect(Collectors.toList()));
+ // clip ranges to queried range
List<TRowRange> ranges =
- merged.stream().map(r ->
toClippedExtent(r).toThrift()).collect(Collectors.toList()); // clip
-
// ranges
-
// to
-
// queried
-
// range
+
merged.stream().map(this::toClippedExtent).map(toTRowRange).collect(Collectors.toList());
locations.computeIfAbsent(location, s -> new
HashMap<>()).put(entry.getKey(), ranges);
}
@@ -434,11 +435,15 @@ public class Gatherer {
public Future<SummaryCollection> processFiles(FileSystemResolver volMgr,
Map<String,List<TRowRange>> files, BlockCache summaryCache, BlockCache
indexCache,
Cache<String,Long> fileLenCache, ExecutorService srp) {
+ Function<TRowRange,RowRange> fromThrift = tRowRange -> {
+ Text lowerBound = ByteBufferUtil.toText(tRowRange.startRow);
+ Text upperBound = ByteBufferUtil.toText(tRowRange.endRow);
+ return RowRange.range(lowerBound, false, upperBound, true);
+ };
List<CompletableFuture<SummaryCollection>> futures = new ArrayList<>();
for (Entry<String,List<TRowRange>> entry : files.entrySet()) {
futures.add(CompletableFuture.supplyAsync(() -> {
- List<RowRange> rrl =
-
entry.getValue().stream().map(RowRange::new).collect(Collectors.toList());
+ List<RowRange> rrl =
entry.getValue().stream().map(fromThrift).collect(Collectors.toList());
return getSummaries(volMgr, entry.getKey(), rrl, summaryCache,
indexCache, fileLenCache);
}, srp));
}
@@ -537,51 +542,10 @@ public class Gatherer {
private RowRange toClippedExtent(Range r) {
r = clipRange.clip(r);
- Text startRow = removeTrailingZeroFromRow(r.getStartKey());
- Text endRow = removeTrailingZeroFromRow(r.getEndKey());
-
- return new RowRange(startRow, endRow);
- }
-
- public static class RowRange {
- private final Text startRow;
- private final Text endRow;
-
- public RowRange(KeyExtent ke) {
- this.startRow = ke.prevEndRow();
- this.endRow = ke.endRow();
- }
-
- public RowRange(TRowRange trr) {
- this.startRow = ByteBufferUtil.toText(trr.startRow);
- this.endRow = ByteBufferUtil.toText(trr.endRow);
- }
-
- public RowRange(Text startRow, Text endRow) {
- this.startRow = startRow;
- this.endRow = endRow;
- }
+ final Text lowerBound = removeTrailingZeroFromRow(r.getStartKey());
+ final Text upperBound = removeTrailingZeroFromRow(r.getEndKey());
- public Range toRange() {
- return new Range(startRow, false, endRow, true);
- }
-
- public TRowRange toThrift() {
- return new TRowRange(TextUtil.getByteBuffer(startRow),
TextUtil.getByteBuffer(endRow));
- }
-
- public Text getStartRow() {
- return startRow;
- }
-
- public Text getEndRow() {
- return endRow;
- }
-
- @Override
- public String toString() {
- return startRow + " " + endRow;
- }
+ return RowRange.range(lowerBound, false, upperBound, true);
}
private SummaryCollection getSummaries(FileSystemResolver volMgr, String
file,
diff --git
a/core/src/main/java/org/apache/accumulo/core/summary/SummaryReader.java
b/core/src/main/java/org/apache/accumulo/core/summary/SummaryReader.java
index 3a600150b1..7888e80b67 100644
--- a/core/src/main/java/org/apache/accumulo/core/summary/SummaryReader.java
+++ b/core/src/main/java/org/apache/accumulo/core/summary/SummaryReader.java
@@ -31,6 +31,7 @@ import java.util.function.Predicate;
import org.apache.accumulo.core.client.rfile.RFileSource;
import org.apache.accumulo.core.client.summary.SummarizerConfiguration;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.file.NoSuchMetaStoreException;
import org.apache.accumulo.core.file.blockfile.impl.BasicCacheProvider;
import org.apache.accumulo.core.file.blockfile.impl.CachableBlockFile;
@@ -42,7 +43,6 @@ import org.apache.accumulo.core.spi.cache.BlockCache;
import org.apache.accumulo.core.spi.cache.CacheEntry;
import org.apache.accumulo.core.spi.cache.CacheType;
import org.apache.accumulo.core.spi.crypto.CryptoService;
-import org.apache.accumulo.core.summary.Gatherer.RowRange;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
diff --git
a/core/src/main/java/org/apache/accumulo/core/summary/SummarySerializer.java
b/core/src/main/java/org/apache/accumulo/core/summary/SummarySerializer.java
index 58b6e12191..1d5b257dd8 100644
--- a/core/src/main/java/org/apache/accumulo/core/summary/SummarySerializer.java
+++ b/core/src/main/java/org/apache/accumulo/core/summary/SummarySerializer.java
@@ -24,6 +24,7 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -35,8 +36,8 @@ import
org.apache.accumulo.core.client.summary.Summarizer.Collector;
import org.apache.accumulo.core.client.summary.Summarizer.Combiner;
import org.apache.accumulo.core.client.summary.SummarizerConfiguration;
import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.data.Value;
-import org.apache.accumulo.core.summary.Gatherer.RowRange;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableUtils;
@@ -97,17 +98,8 @@ class SummarySerializer {
}
public boolean exceedsRange(List<RowRange> ranges) {
- boolean er = false;
- for (LgSummaries lgs : allSummaries) {
- for (RowRange ke : ranges) {
- er |= lgs.exceedsRange(ke.getStartRow(), ke.getEndRow());
- if (er) {
- return er;
- }
- }
- }
-
- return er;
+ ranges.forEach(SummarySerializer::validateSummaryRange);
+ return Arrays.stream(allSummaries).anyMatch(lgs ->
ranges.stream().anyMatch(lgs::exceedsRange));
}
public boolean exceededMaxSize() {
@@ -431,7 +423,10 @@ class SummarySerializer {
this.lgroupName = lgroupName;
}
- boolean exceedsRange(Text startRow, Text endRow) {
+ boolean exceedsRange(RowRange range) {
+
+ Text startRow = range.getLowerBound();
+ Text endRow = range.getUpperBound();
if (summaries.length == 0) {
return false;
@@ -463,22 +458,23 @@ class SummarySerializer {
void getSummary(List<RowRange> ranges, Combiner combiner, Map<String,Long>
summary) {
boolean[] summariesThatOverlap = new boolean[summaries.length];
- for (RowRange keyExtent : ranges) {
- Text startRow = keyExtent.getStartRow();
- Text endRow = keyExtent.getEndRow();
+ for (RowRange rowRange : ranges) {
+ validateSummaryRange(rowRange);
+ Text lowerBound = rowRange.getLowerBound();
+ Text upperBound = rowRange.getUpperBound();
- if (endRow != null && endRow.compareTo(firstRow) < 0) {
+ if (upperBound != null && upperBound.compareTo(firstRow) < 0) {
continue;
}
int start = -1;
int end = summaries.length - 1;
- if (startRow == null) {
+ if (lowerBound == null) {
start = 0;
} else {
for (int i = 0; i < summaries.length; i++) {
- if (startRow.compareTo(summaries[i].getLastRow()) < 0) {
+ if (lowerBound.compareTo(summaries[i].getLastRow()) < 0) {
start = i;
break;
}
@@ -489,11 +485,11 @@ class SummarySerializer {
continue;
}
- if (endRow == null) {
+ if (upperBound == null) {
end = summaries.length - 1;
} else {
for (int i = start; i < summaries.length; i++) {
- if (endRow.compareTo(summaries[i].getLastRow()) < 0) {
+ if (upperBound.compareTo(summaries[i].getLastRow()) < 0) {
end = i;
break;
}
@@ -513,6 +509,20 @@ class SummarySerializer {
}
}
+ /**
+ * Ensures that the lower bound is exclusive and the upper bound is inclusive
+ */
+ private static void validateSummaryRange(RowRange rowRange) {
+ if (rowRange.getLowerBound() != null) {
+ Preconditions.checkState(!rowRange.isLowerBoundInclusive(),
+ "Summary row range lower bound must be exclusive: %s", rowRange);
+ }
+ if (rowRange.getUpperBound() != null) {
+ Preconditions.checkState(rowRange.isUpperBoundInclusive(),
+ "Summary row range upper bound must be inclusive: %s", rowRange);
+ }
+ }
+
private static LgSummaries readLGroup(DataInputStream in, String[] symbols)
throws IOException {
String lgroupName = in.readUTF();
diff --git
a/core/src/test/java/org/apache/accumulo/core/clientImpl/TableOperationsHelperTest.java
b/core/src/test/java/org/apache/accumulo/core/clientImpl/TableOperationsHelperTest.java
index f801745dc4..040d21fd0a 100644
---
a/core/src/test/java/org/apache/accumulo/core/clientImpl/TableOperationsHelperTest.java
+++
b/core/src/test/java/org/apache/accumulo/core/clientImpl/TableOperationsHelperTest.java
@@ -53,6 +53,7 @@ import
org.apache.accumulo.core.client.admin.TabletMergeability;
import org.apache.accumulo.core.client.sample.SamplerConfiguration;
import org.apache.accumulo.core.client.summary.SummarizerConfiguration;
import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.hadoop.io.Text;
@@ -96,8 +97,7 @@ public class TableOperationsHelperTest {
}
@Override
- public Text getMaxRow(String tableName, Authorizations auths, Text
startRow,
- boolean startInclusive, Text endRow, boolean endInclusive) {
+ public Text getMaxRow(String tableName, Authorizations auths, RowRange
range) {
return null;
}
diff --git a/core/src/test/java/org/apache/accumulo/core/data/RowRangeTest.java
b/core/src/test/java/org/apache/accumulo/core/data/RowRangeTest.java
new file mode 100644
index 0000000000..4a980a3b4e
--- /dev/null
+++ b/core/src/test/java/org/apache/accumulo/core/data/RowRangeTest.java
@@ -0,0 +1,910 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * https://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 org.apache.accumulo.core.data;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.apache.hadoop.io.Text;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class RowRangeTest {
+
+ @Nested
+ class TestBasic {
+
+ @Test
+ void testGetters() {
+ Text lowerBound = new Text("r1");
+ Text upperBound = new Text("r1");
+ boolean isLowerBoundInclusive = true;
+ boolean isUpperBoundInclusive = false;
+
+ RowRange range =
+ RowRange.range(lowerBound, isLowerBoundInclusive, upperBound,
isUpperBoundInclusive);
+
+ assertEquals(lowerBound, range.getLowerBound());
+ assertEquals(isLowerBoundInclusive, range.isLowerBoundInclusive());
+ assertEquals(upperBound, range.getUpperBound());
+ assertEquals(isUpperBoundInclusive, range.isUpperBoundInclusive());
+
+ lowerBound = new Text("r11");
+ upperBound = new Text("r22");
+ isLowerBoundInclusive = false;
+ isUpperBoundInclusive = true;
+
+ range = RowRange.range(lowerBound, isLowerBoundInclusive, upperBound,
isUpperBoundInclusive);
+
+ assertEquals(lowerBound, range.getLowerBound());
+ assertEquals(isLowerBoundInclusive, range.isLowerBoundInclusive());
+ assertEquals(upperBound, range.getUpperBound());
+ assertEquals(isUpperBoundInclusive, range.isUpperBoundInclusive());
+ }
+
+ @Test
+ void testDefensiveCopyOfBounds() {
+ Text originalLower = new Text("a");
+ Text originalUpper = new Text("z");
+
+ RowRange range = RowRange.range(originalLower, true, originalUpper,
false);
+
+ assertNotSame(originalLower, range.getLowerBound());
+ assertNotSame(originalUpper, range.getUpperBound());
+
+ originalLower.set("changed");
+ originalUpper.set("changed");
+
+ assertEquals(new Text("a"), range.getLowerBound());
+ assertEquals(new Text("z"), range.getUpperBound());
+ }
+
+ @Test
+ void testupperBoundBeforelowerBound() {
+ final Text lowerBound = new Text("r1");
+ final Text upperBound = new Text("r0");
+
+ assertTrue(lowerBound.compareTo(upperBound) > 0);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> RowRange.range(lowerBound, true, upperBound, false));
+ }
+
+ @Test
+ void testBeforelowerBoundWithInfinitelowerBound() {
+ RowRange lessThanR1 = RowRange.lessThan("r1");
+
+ assertFalse(lessThanR1.isAfter(new Text("r0")));
+ assertFalse(lessThanR1.isAfter(new Text("r1")));
+ assertFalse(lessThanR1.isAfter(new Text("r2")));
+
+ RowRange rowRangeR1 = RowRange.closed("r1");
+
+ assertTrue(rowRangeR1.isAfter(new Text("r")));
+ assertTrue(rowRangeR1.isAfter(new Text("r0")));
+ assertFalse(rowRangeR1.isAfter(new Text("r1")));
+ assertFalse(rowRangeR1.isAfter(new Text("r2")));
+ }
+
+ @Test
+ public void testAfterupperBoundWithupperBoundInclusive() {
+ RowRange range = RowRange.closed(new Text("a"), new Text("c"));
+ assertFalse(range.isBefore(new Text("c")));
+ assertFalse(range.isBefore(new Text("b")));
+ assertFalse(range.isBefore(new Text("a")));
+ assertTrue(range.isBefore(new Text("d")));
+ }
+
+ @Test
+ public void testAfterupperBoundWithupperBoundExclusive() {
+ RowRange range = RowRange.open(new Text("a"), new Text("c"));
+ assertTrue(range.isBefore(new Text("c")));
+ assertFalse(range.isBefore(new Text("b")));
+ assertFalse(range.isBefore(new Text("a")));
+ }
+
+ @Test
+ public void testAfterupperBoundWithInfiniteupperBound() {
+ RowRange range = RowRange.greaterThan(new Text("a"));
+ assertFalse(range.isBefore(new Text("a")));
+ assertFalse(range.isBefore(new Text("b")));
+ }
+
+ @Test
+ public void testToRange() {
+ // Closed range
+ RowRange closedRange = RowRange.closed(new Text("a"), new Text("c"));
+ Range expectedClosedRange = new Range(new Text("a"), true, new
Text("c"), true);
+ assertEquals(expectedClosedRange, closedRange.asRange());
+
+ // Open range
+ RowRange openRange = RowRange.open(new Text("a"), new Text("c"));
+ Range expectedOpenRange = new Range(new Text("a"), false, new Text("c"),
false);
+ assertEquals(expectedOpenRange, openRange.asRange());
+
+ // Range with infinite upper bound
+ RowRange infiniteUpperBound = RowRange.greaterThan(new Text("a"));
+ Range expectedInfiniteUpperBound = new Range(new Text("a"), false, null,
true);
+ assertEquals(expectedInfiniteUpperBound, infiniteUpperBound.asRange());
+
+ // Range with no lower bound
+ RowRange infiniteLowerBound = RowRange.lessThan(new Text("c"));
+ Range expectedInfiniteLowerBound = new Range(null, true, new Text("c"),
false);
+ assertEquals(expectedInfiniteLowerBound, infiniteLowerBound.asRange());
+
+ // All rows range
+ RowRange allRange = RowRange.all();
+ Range expectedAllRange = new Range();
+ assertEquals(expectedAllRange, allRange.asRange());
+ }
+
+ @Test
+ public void testStaticMethodsThrowExceptionOnNullArgument() {
+ Stream<Runnable> methods = Stream.of(() -> RowRange.open(null, ""),
+ () -> RowRange.open("", null), () -> RowRange.closed(null, ""),
+ () -> RowRange.closed("", null), () -> RowRange.closed((Text) null),
+ () -> RowRange.openClosed(null, ""), () -> RowRange.openClosed("",
null),
+ () -> RowRange.closedOpen(null, ""), () -> RowRange.closedOpen("",
null),
+ () -> RowRange.greaterThan((Text) null), () ->
RowRange.atLeast((Text) null),
+ () -> RowRange.lessThan((Text) null), () -> RowRange.atMost((Text)
null));
+
+ methods.forEach(method -> assertThrows(NullPointerException.class,
method::run));
+ }
+
+ }
+
+ @Nested
+ class StaticEntryPointTests {
+
+ @Test
+ void testOpenEquality() {
+ RowRange range1 = RowRange.open("r1", "row5");
+ RowRange range2 = RowRange.open(new Text("r1"), new Text("row5"));
+ RowRange range3 = RowRange.range(new Text("r1"), false, new
Text("row5"), false);
+
+ assertTrue(range1.equals(range2));
+ assertTrue(range1.equals(range3));
+ assertTrue(range2.equals(range3));
+ }
+
+ @Test
+ void testClosedEquality() {
+ RowRange range1 = RowRange.closed("r1", "row5");
+ RowRange range2 = RowRange.closed(new Text("r1"), new Text("row5"));
+ RowRange range3 = RowRange.range(new Text("r1"), true, new Text("row5"),
true);
+
+ assertTrue(range1.equals(range2));
+ assertTrue(range1.equals(range3));
+ assertTrue(range2.equals(range3));
+ }
+
+ @Test
+ void testClosedSingleRowEquality() {
+ RowRange range1 = RowRange.closed("r1");
+ RowRange range2 = RowRange.closed(new Text("r1"));
+ RowRange range3 = RowRange.range(new Text("r1"), true, new Text("r1"),
true);
+
+ assertTrue(range1.equals(range2));
+ assertTrue(range1.equals(range3));
+ assertTrue(range2.equals(range3));
+ }
+
+ @Test
+ void testClosedOpenEquality() {
+ RowRange range1 = RowRange.closedOpen("r1", "row5");
+ RowRange range2 = RowRange.closedOpen(new Text("r1"), new Text("row5"));
+ RowRange range3 = RowRange.range(new Text("r1"), true, new Text("row5"),
false);
+
+ assertTrue(range1.equals(range2));
+ assertTrue(range1.equals(range3));
+ assertTrue(range2.equals(range3));
+ }
+
+ @Test
+ void testOpenClosedEquality() {
+ RowRange range1 = RowRange.openClosed("r1", "row5");
+ RowRange range2 = RowRange.openClosed(new Text("r1"), new Text("row5"));
+ RowRange range3 = RowRange.range(new Text("r1"), false, new
Text("row5"), true);
+
+ assertTrue(range1.equals(range2));
+ assertTrue(range1.equals(range3));
+ assertTrue(range2.equals(range3));
+ }
+
+ @Test
+ void testGreaterThanEquality() {
+ RowRange range1 = RowRange.greaterThan("r1");
+ RowRange range2 = RowRange.greaterThan(new Text("r1"));
+ RowRange range3 = RowRange.range(new Text("r1"), false, null, false);
+
+ assertTrue(range1.equals(range2));
+ assertTrue(range1.equals(range3));
+ assertTrue(range2.equals(range3));
+ }
+
+ @Test
+ void testAtLeastEquality() {
+ RowRange range1 = RowRange.atLeast("r1");
+ RowRange range2 = RowRange.atLeast(new Text("r1"));
+ RowRange range3 = RowRange.range(new Text("r1"), true, null, false);
+
+ assertTrue(range1.equals(range2));
+ assertTrue(range1.equals(range3));
+ assertTrue(range2.equals(range3));
+ }
+
+ @Test
+ void testLessThanEquality() {
+ RowRange range1 = RowRange.lessThan("row5");
+ RowRange range2 = RowRange.lessThan(new Text("row5"));
+ RowRange range3 = RowRange.range(null, false, new Text("row5"), false);
+
+ assertTrue(range1.equals(range2));
+ assertTrue(range1.equals(range3));
+ assertTrue(range2.equals(range3));
+ }
+
+ @Test
+ void testAtMostEquality() {
+ RowRange range1 = RowRange.atMost("row5");
+ RowRange range2 = RowRange.atMost(new Text("row5"));
+ RowRange range3 = RowRange.range(null, false, new Text("row5"), true);
+
+ assertTrue(range1.equals(range2));
+ assertTrue(range1.equals(range3));
+ assertTrue(range2.equals(range3));
+ }
+
+ @Test
+ void testAllEquality() {
+ RowRange range1 = RowRange.all();
+ RowRange range2 = RowRange.range((Text) null, false, null, false);
+
+ assertTrue(range1.equals(range2));
+ }
+ }
+
+ @Nested
+ class EqualsTests {
+
+ @Test
+ void testEqualsWithObject() {
+ Object rowRange = RowRange.closedOpen("r1", "row5");
+ Object rowRange1 = RowRange.closedOpen("r1", "row5");
+ RowRange rowRange2 = RowRange.closedOpen("r1", "row5");
+
+ assertEquals(rowRange, rowRange1);
+ assertEquals(rowRange, rowRange2);
+
+ String badRange = "r1";
+ assertNotEquals(rowRange, badRange);
+ }
+
+ @Test
+ void testEqualsWithDifferentRanges() {
+ RowRange range1 = RowRange.closedOpen("r1", "row5");
+ RowRange range2 = RowRange.closedOpen("r2", "row4");
+ assertFalse(range1.equals(range2));
+ }
+
+ @Test
+ void testEqualsWithSameRange() {
+ RowRange range1 = RowRange.closedOpen("r1", "row5");
+ RowRange range2 = RowRange.closedOpen("r1", "row5");
+ assertTrue(range1.equals(range2));
+ }
+
+ @Test
+ void testEqualsWithDifferentlowerBoundInclusiveness() {
+ RowRange range1 = RowRange.closedOpen("r1", "row5");
+ RowRange range2 = RowRange.openClosed("r1", "row5");
+ assertFalse(range1.equals(range2));
+ }
+
+ @Test
+ void testEqualsWithDifferentupperBoundInclusiveness() {
+ RowRange range1 = RowRange.closedOpen("r1", "row5");
+ RowRange range2 = RowRange.closedOpen("r1", "row4");
+ assertFalse(range1.equals(range2));
+ }
+
+ @Test
+ void testEqualsWithDifferentlowerBoundAndupperBoundInclusiveness() {
+ RowRange range1 = RowRange.closedOpen("r1", "row5");
+ RowRange range2 = RowRange.openClosed("r1", "row4");
+ assertFalse(range1.equals(range2));
+ }
+
+ @Test
+ void testOverloadEquality() {
+ RowRange range1 = RowRange.closedOpen("r1", "row5");
+ RowRange range2 = RowRange.closedOpen(new Text("r1"), new Text("row5"));
+ RowRange range3 = RowRange.atLeast("row8");
+ RowRange range4 = RowRange.atLeast(new Text("row8"));
+ RowRange range5 = RowRange.lessThan("r2");
+ RowRange range6 = RowRange.lessThan(new Text("r2"));
+ RowRange range7 = RowRange.atMost("r3");
+ RowRange range8 = RowRange.atMost(new Text("r3"));
+
+ // Test that all ranges created using different entry point methods are
equal
+ assertTrue(range1.equals(range2));
+ assertTrue(range3.equals(range4));
+ assertTrue(range5.equals(range6));
+ assertTrue(range7.equals(range8));
+
+ // Test that ranges with different properties are not equal
+ assertFalse(range1.equals(range3));
+ assertFalse(range1.equals(range5));
+ assertFalse(range1.equals(range7));
+ assertFalse(range3.equals(range5));
+ assertFalse(range3.equals(range7));
+ assertFalse(range5.equals(range7));
+ }
+ }
+
+ @Nested
+ class CompareToTests {
+
+ @Test
+ void testCompareWithSameRange() {
+ RowRange range1 = RowRange.open("r1", "r3");
+ RowRange range2 = RowRange.open("r1", "r3");
+
+ assertEquals(0, range1.compareTo(range2));
+ assertEquals(0, range2.compareTo(range1));
+
+ range1 = RowRange.closed("r1", "r3");
+ range2 = RowRange.closed("r1", "r3");
+
+ assertEquals(0, range1.compareTo(range2));
+ assertEquals(0, range2.compareTo(range1));
+
+ range1 = RowRange.closedOpen("r1", "r3");
+ range2 = RowRange.closedOpen("r1", "r3");
+
+ assertEquals(0, range1.compareTo(range2));
+ assertEquals(0, range2.compareTo(range1));
+
+ range1 = RowRange.closed("r1");
+ range2 = RowRange.closed("r1");
+
+ assertEquals(0, range1.compareTo(range2));
+ assertEquals(0, range2.compareTo(range1));
+
+ range1 = RowRange.openClosed("r1", "r3");
+ range2 = RowRange.openClosed("r1", "r3");
+
+ assertEquals(0, range1.compareTo(range2));
+ assertEquals(0, range2.compareTo(range1));
+
+ range1 = RowRange.greaterThan("r1");
+ range2 = RowRange.greaterThan("r1");
+
+ assertEquals(0, range1.compareTo(range2));
+ assertEquals(0, range2.compareTo(range1));
+
+ range1 = RowRange.atLeast("r1");
+ range2 = RowRange.atLeast("r1");
+
+ assertEquals(0, range1.compareTo(range2));
+ assertEquals(0, range2.compareTo(range1));
+
+ range1 = RowRange.lessThan("r1");
+ range2 = RowRange.lessThan("r1");
+
+ assertEquals(0, range1.compareTo(range2));
+ assertEquals(0, range2.compareTo(range1));
+
+ range1 = RowRange.atMost("r1");
+ range2 = RowRange.atMost("r1");
+
+ assertEquals(0, range1.compareTo(range2));
+ assertEquals(0, range2.compareTo(range1));
+
+ range1 = RowRange.all();
+ range2 = RowRange.all();
+
+ assertEquals(0, range1.compareTo(range2));
+ assertEquals(0, range2.compareTo(range1));
+ }
+
+ @Test
+ void testCompareWithDifferentlowerBound() {
+ RowRange open_r1_r3 = RowRange.open("r1", "r3");
+ RowRange open_r2_r3 = RowRange.open("r2", "r3");
+
+ assertTrue(open_r1_r3.compareTo(open_r2_r3) < 0);
+ assertTrue(open_r2_r3.compareTo(open_r1_r3) > 0);
+ }
+
+ @Test
+ void testCompareWithDifferentupperBound() {
+ RowRange open_r1_r3 = RowRange.open("r1", "r3");
+ RowRange open_r1_r4 = RowRange.open("r1", "r4");
+
+ assertTrue(open_r1_r3.compareTo(open_r1_r4) < 0);
+ assertTrue(open_r1_r4.compareTo(open_r1_r3) > 0);
+ }
+
+ @Test
+ void testCompareWithDifferentlowerBoundInclusiveness() {
+ RowRange open = RowRange.open("r1", "r3");
+ RowRange closedOpen = RowRange.closedOpen("r1", "r3");
+
+ assertTrue(open.compareTo(closedOpen) > 0);
+ assertTrue(closedOpen.compareTo(open) < 0);
+ }
+
+ @Test
+ void testCompareWithDifferentupperBoundInclusiveness() {
+ RowRange open = RowRange.open("r1", "r3");
+ RowRange openClosed = RowRange.openClosed("r1", "r3");
+
+ assertTrue(open.compareTo(openClosed) < 0);
+ assertTrue(openClosed.compareTo(open) > 0);
+ }
+
+ @Test
+ void testCompareWithInfiniteupperBound() {
+ RowRange atLeast_r1 = RowRange.atLeast("r1");
+ RowRange greaterThan_r1 = RowRange.greaterThan("r1");
+ RowRange all = RowRange.all();
+
+ assertTrue(atLeast_r1.compareTo(all) < 0);
+ assertTrue(all.compareTo(atLeast_r1) > 0);
+
+ assertTrue(greaterThan_r1.compareTo(all) < 0);
+ assertTrue(all.compareTo(greaterThan_r1) > 0);
+
+ assertTrue(atLeast_r1.compareTo(greaterThan_r1) < 0);
+ assertTrue(greaterThan_r1.compareTo(atLeast_r1) > 0);
+ }
+
+ @Test
+ void testCompareWithInfinitelowerBound() {
+ RowRange atMost_r1 = RowRange.atMost("r1");
+ RowRange lessThan_r1 = RowRange.lessThan("r1");
+ RowRange all = RowRange.all();
+
+ assertTrue(atMost_r1.compareTo(all) < 0);
+ assertTrue(all.compareTo(atMost_r1) > 0);
+
+ assertTrue(lessThan_r1.compareTo(all) < 0);
+ assertTrue(all.compareTo(lessThan_r1) > 0);
+
+ assertTrue(atMost_r1.compareTo(lessThan_r1) > 0);
+ assertTrue(lessThan_r1.compareTo(atMost_r1) < 0);
+ }
+ }
+
+ @Nested
+ class ContainsTests {
+
+ @Test
+ void testContainsWithAllRange() {
+ RowRange range = RowRange.all();
+ assertTrue(range.contains(new Text(new byte[] {})));
+ assertTrue(range.contains(new Text("r1")));
+ assertTrue(range.contains(new Text("r2")));
+ assertTrue(range.contains(new Text("row3")));
+ }
+
+ @Test
+ void testContainsWithOpenRange() {
+ RowRange range = RowRange.open("r1", "r3");
+ assertFalse(range.contains(new Text(new byte[] {})));
+ assertFalse(range.contains(new Text("r0")));
+ assertFalse(range.contains(new Text("r1")));
+ assertTrue(range.contains(new Text(new byte[] {'r', '1', 0})));
+ assertTrue(range.contains(new Text("r11")));
+ assertTrue(range.contains(new Text("r2")));
+ assertFalse(range.contains(new Text(new byte[] {'r', '3', 0})));
+ assertFalse(range.contains(new Text("r3")));
+ }
+
+ @Test
+ void testContainsWithClosedRange() {
+ RowRange range = RowRange.closed("r1", "r3");
+ assertFalse(range.contains(new Text("r0")));
+ assertTrue(range.contains(new Text("r1")));
+ assertTrue(range.contains(new Text("r2")));
+ assertTrue(range.contains(new Text("r3")));
+ }
+
+ @Test
+ void testContainsWithOpenClosedRange() {
+ RowRange range = RowRange.openClosed("r1", "r3");
+ assertFalse(range.contains(new Text("r0")));
+ assertFalse(range.contains(new Text("r1")));
+ assertTrue(range.contains(new Text("r2")));
+ assertTrue(range.contains(new Text("r3")));
+ assertFalse(range.contains(new Text("r30")));
+ }
+
+ @Test
+ void testContainsWithClosedOpenRange() {
+ RowRange range = RowRange.closedOpen("r1", "r3");
+ assertFalse(range.contains(new Text("r0")));
+ assertTrue(range.contains(new Text("r1")));
+ assertTrue(range.contains(new Text("r2")));
+ assertFalse(range.contains(new Text("r3")));
+ }
+
+ @Test
+ void testContainsWithSingleRowRange() {
+ RowRange range = RowRange.closed("r1");
+ assertTrue(range.contains(new Text("r1")));
+ assertFalse(range.contains(new Text("r2")));
+ }
+
+ @Test
+ void testContainsWithAtLeastRange() {
+ RowRange range = RowRange.atLeast("r1");
+ assertTrue(range.contains(new Text("r1")));
+ assertTrue(range.contains(new Text("r2")));
+ assertFalse(range.contains(new Text("")));
+ }
+
+ @Test
+ void testContainsWithAtMostRange() {
+ RowRange range = RowRange.atMost("r1");
+ assertTrue(range.contains(new Text(new byte[] {})));
+ assertTrue(range.contains(new Text("r1")));
+ assertFalse(range.contains(new Text("r10")));
+ assertFalse(range.contains(new Text("r2")));
+ assertTrue(range.contains(new Text("")));
+ }
+ }
+
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ @Nested
+ class MergeOverlappingTests {
+
+ @ParameterizedTest
+ @MethodSource({"rowRangeProvider", "rowRangeProvider1"})
+ public void testMergeOverlapping(List<RowRange> rowRangesToMerge,
List<RowRange> expected) {
+ List<RowRange> actual = RowRange.mergeOverlapping(rowRangesToMerge);
+ verifyMerge(expected, actual);
+ }
+
+ private void verifyMerge(List<RowRange> expectedList, List<RowRange>
actualList) {
+ HashSet<RowRange> expectedSet = new HashSet<>(expectedList);
+ HashSet<RowRange> actualSet = new HashSet<>(actualList);
+ assertEquals(expectedSet, actualSet, "Expected: " + expectedSet + "
Actual: " + actualSet);
+ }
+
+ Stream<Arguments> rowRangeProvider() {
+ return Stream.of(
+ // [a,c] [a,b] -> [a,c]
+ Arguments.of(List.of(RowRange.closed("a", "c"), RowRange.closed("a",
"b")),
+ List.of(RowRange.closed("a", "c"))),
+ // [a,c] [d,f] -> [a,c] [d,f]
+ Arguments.of(List.of(RowRange.closed("a", "c"), RowRange.closed("d",
"f")),
+ List.of(RowRange.closed("a", "c"), RowRange.closed("d", "f"))),
+ // [a,e] [b,f] [c,r] [g,j] [t,x] -> [a,r] [t,x]
+ Arguments.of(
+ List.of(RowRange.closed("a", "e"), RowRange.closed("b", "f"),
+ RowRange.closed("c", "r"), RowRange.closed("g", "j"),
RowRange.closed("t", "x")),
+ List.of(RowRange.closed("a", "r"), RowRange.closed("t", "x"))),
+ // [a,e] [b,f] [c,r] [g,j] -> [a,r]
+ Arguments.of(
+ List.of(RowRange.closed("a", "e"), RowRange.closed("b", "f"),
+ RowRange.closed("c", "r"), RowRange.closed("g", "j")),
+ List.of(RowRange.closed("a", "r"))),
+ // [a,e] -> [a,e]
+ Arguments.of(List.of(RowRange.closed("a", "e")),
List.of(RowRange.closed("a", "e"))),
+ // [] -> []
+ Arguments.of(List.of(), List.of()),
+ // [a,e] [g,q] [r,z] -> [a,e] [g,q] [r,z]
+ Arguments.of(
+ List.of(RowRange.closed("a", "e"), RowRange.closed("g", "q"),
+ RowRange.closed("r", "z")),
+ List.of(RowRange.closed("a", "e"), RowRange.closed("g", "q"),
+ RowRange.closed("r", "z"))),
+ // [a,c] [a,c] -> [a,c]
+ Arguments.of(List.of(RowRange.closed("a", "c"), RowRange.closed("a",
"c")),
+ List.of(RowRange.closed("a", "c"))),
+ // [ALL] -> [ALL]
+ Arguments.of(List.of(RowRange.all()), List.of(RowRange.all())),
+ // [ALL] [a,c] -> [ALL]
+ Arguments.of(List.of(RowRange.all(), RowRange.closed("a", "c")),
List.of(RowRange.all())),
+ // [a,c] [ALL] -> [ALL]
+ Arguments.of(List.of(RowRange.closed("a", "c"), RowRange.all()),
List.of(RowRange.all())),
+ // [b,d] [c,+inf) -> [b,+inf)
+ Arguments.of(List.of(RowRange.closed("b", "d"),
RowRange.atLeast("c")),
+ List.of(RowRange.atLeast("b"))),
+ // [b,d] [a,+inf) -> [a,+inf)
+ Arguments.of(List.of(RowRange.closed("b", "d"),
RowRange.atLeast("a")),
+ List.of(RowRange.atLeast("a"))),
+ // [b,d] [e,+inf) -> [b,d] [e,+inf)
+ Arguments.of(List.of(RowRange.closed("b", "d"),
RowRange.atLeast("e")),
+ List.of(RowRange.closed("b", "d"), RowRange.atLeast("e"))),
+ // [b,d] [e,+inf) [c,f] -> [b,+inf)
+ Arguments.of(
+ List.of(RowRange.closed("b", "d"), RowRange.atLeast("e"),
RowRange.closed("c", "f")),
+ List.of(RowRange.atLeast("b"))),
+ // [b,d] [f,+inf) [c,e] -> [b,e] [f,+inf)
+ Arguments.of(
+ List.of(RowRange.closed("b", "d"), RowRange.atLeast("f"),
RowRange.closed("c", "e")),
+ List.of(RowRange.closed("b", "e"), RowRange.atLeast("f"))),
+ // [b,d] [r,+inf) [c,e] [g,t] -> [b,e] [g,+inf)
+ Arguments.of(
+ List.of(RowRange.closed("b", "d"), RowRange.atLeast("r"),
RowRange.closed("c", "e"),
+ RowRange.closed("g", "t")),
+ List.of(RowRange.closed("b", "e"), RowRange.atLeast("g"))),
+ // (-inf,d] [r,+inf) [c,e] [g,t] -> (-inf,e] [g,+inf)
+ Arguments.of(List.of(RowRange.atMost("d"), RowRange.atLeast("r"),
+ RowRange.closed("c", "e"), RowRange.closed("g", "t")),
+ List.of(RowRange.atMost("e"), RowRange.atLeast("g"))),
+ // (-inf,d] [r,+inf) [c,e] [g,t] [d,h] -> (-inf,+inf)
+ Arguments.of(List.of(RowRange.atMost("d"), RowRange.atLeast("r"),
+ RowRange.closed("c", "e"), RowRange.closed("g", "t"),
RowRange.closed("d", "h")),
+ List.of(RowRange.all())),
+ // [a,b) (b,c) -> [a,b), (b,c)
+ Arguments.of(List.of(RowRange.closedOpen("a", "b"),
RowRange.open("b", "c")),
+ List.of(RowRange.closedOpen("a", "b"), RowRange.open("b", "c"))),
+ // [a,b) [b,c) -> [a,c)
+ Arguments.of(List.of(RowRange.closedOpen("a", "b"),
RowRange.closedOpen("b", "c")),
+ List.of(RowRange.closedOpen("a", "c"))),
+ // [a,b] (b,c) -> [a,c)
+ Arguments.of(List.of(RowRange.closed("a", "b"), RowRange.open("b",
"c")),
+ List.of(RowRange.closedOpen("a", "c"))),
+ // [a,b] [b,c) -> [a,c)
+ Arguments.of(List.of(RowRange.closed("a", "b"),
RowRange.closedOpen("b", "c")),
+ List.of(RowRange.closedOpen("a", "c"))),
+ // (-inf,b] (-inf,c] -> (-inf,c]
+ Arguments.of(List.of(RowRange.atMost("b"), RowRange.atMost("c")),
+ List.of(RowRange.atMost("c"))),
+ // (-inf,b] (-inf,c] [a,d] -> (-inf,d]
+ Arguments.of(
+ List.of(RowRange.atMost("b"), RowRange.atMost("c"),
RowRange.closed("a", "d")),
+ List.of(RowRange.atMost("d"))),
+ // (-inf,b] (-inf,c] [a,d] [e,f] -> (-inf,d] [e,f]
+ Arguments.of(
+ List.of(RowRange.atMost("b"), RowRange.atMost("c"),
RowRange.closed("a", "d"),
+ RowRange.closed("e", "f")),
+ List.of(RowRange.atMost("d"), RowRange.closed("e", "f"))),
+ // [a] [b] -> [a] [b]
+ Arguments.of(List.of(RowRange.closed("a"), RowRange.closed("b")),
+ List.of(RowRange.closed("a"), RowRange.closed("b"))),
+ // (a,b] [b,c] -> (a,c]
+ Arguments.of(List.of(RowRange.openClosed("a", "b"),
RowRange.closed("b", "c")),
+ List.of(RowRange.openClosed("a", "c"))),
+ // (a,b] (b,+inf) -> (a,+inf)
+ Arguments.of(List.of(RowRange.openClosed("a", "b"),
RowRange.atLeast("b")),
+ List.of(RowRange.greaterThan("a"))),
+ // (-inf,b) (-inf,c) -> (-inf,c)
+ Arguments.of(List.of(RowRange.lessThan("b"), RowRange.lessThan("c")),
+ List.of(RowRange.lessThan("c"))));
+ }
+
+ Stream<Arguments> rowRangeProvider1() {
+ Stream.Builder<Arguments> builder = Stream.builder();
+
+ for (boolean b1 : new boolean[] {true, false}) {
+ for (boolean b2 : new boolean[] {true, false}) {
+ for (boolean b3 : new boolean[] {true, false}) {
+ for (boolean b4 : new boolean[] {true, false}) {
+ List<RowRange> rl =
+ List.of(RowRange.range("a", b1, "m", b2),
RowRange.range("b", b3, "n", b4));
+ List<RowRange> expected = List.of(RowRange.range("a", b1, "n",
b4));
+ builder.add(Arguments.of(rl, expected));
+
+ rl = List.of(RowRange.range("a", b1, "m", b2),
RowRange.range("a", b3, "n", b4));
+ expected = List.of(RowRange.range("a", b1 || b3, "n", b4));
+ builder.add(Arguments.of(rl, expected));
+
+ rl = List.of(RowRange.range("a", b1, "n", b2),
RowRange.range("b", b3, "n", b4));
+ expected = List.of(RowRange.range("a", b1, "n", b2 || b4));
+ builder.add(Arguments.of(rl, expected));
+
+ rl = List.of(RowRange.range("a", b1, "n", b2),
RowRange.range("a", b3, "n", b4));
+ expected = List.of(RowRange.range("a", b1 || b3, "n", b2 || b4));
+ builder.add(Arguments.of(rl, expected));
+ }
+ }
+ }
+ }
+
+ return builder.build();
+ }
+
+ }
+
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ @Nested
+ class ClipTests {
+
+ @ParameterizedTest
+ @MethodSource("clipTestArguments")
+ void testClip(RowRange fence, RowRange range, RowRange expected) {
+ if (expected != null) {
+ RowRange clipped = fence.clip(range);
+ assertEquals(expected, clipped);
+ } else {
+ assertThrows(IllegalArgumentException.class, () -> fence.clip(range));
+ }
+ }
+
+ private Stream<Arguments> clipTestArguments() {
+ RowRange fenceOpen = RowRange.open("a", "c");
+ RowRange fenceClosedOpen = RowRange.closedOpen("a", "c");
+ RowRange fenceOpenClosed = RowRange.openClosed("a", "c");
+ RowRange fenceClosed = RowRange.closed("a", "c");
+
+ RowRange fenceOpenCN = RowRange.open("c", "n");
+ RowRange fenceClosedCN = RowRange.closed("c", "n");
+ RowRange fenceClosedB = RowRange.closed("b");
+
+ return Stream.of(
+ // (a,c) (a,c) -> (a,c)
+ Arguments.of(fenceOpen, RowRange.open("a", "c"), RowRange.open("a",
"c")),
+ // (a,c) [a,c) -> (a,c)
+ Arguments.of(fenceOpen, RowRange.closedOpen("a", "c"),
RowRange.open("a", "c")),
+ // (a,c) (a,c] -> (a,c)
+ Arguments.of(fenceOpen, RowRange.openClosed("a", "c"),
RowRange.open("a", "c")),
+ // (a,c) [a,c] -> (a,c)
+ Arguments.of(fenceOpen, RowRange.closed("a", "c"),
RowRange.open("a", "c")),
+
+ // [a,c) (a,c) -> (a,c)
+ Arguments.of(fenceClosedOpen, RowRange.open("a", "c"),
RowRange.open("a", "c")),
+ // [a,c) [a,c) -> [a,c)
+ Arguments.of(fenceClosedOpen, RowRange.closedOpen("a", "c"),
+ RowRange.closedOpen("a", "c")),
+ // [a,c) (a,c] -> (a,c)
+ Arguments.of(fenceClosedOpen, RowRange.openClosed("a", "c"),
RowRange.open("a", "c")),
+ // [a,c) [a,c] -> [a,c)
+ Arguments.of(fenceClosedOpen, RowRange.closed("a", "c"),
RowRange.closedOpen("a", "c")),
+
+ // (a,c] (a,c) -> (a,c)
+ Arguments.of(fenceOpenClosed, RowRange.open("a", "c"),
RowRange.open("a", "c")),
+ // (a,c] [a,c) -> (a,c)
+ Arguments.of(fenceOpenClosed, RowRange.closedOpen("a", "c"),
RowRange.open("a", "c")),
+ // (a,c] (a,c] -> (a,c]
+ Arguments.of(fenceOpenClosed, RowRange.openClosed("a", "c"),
+ RowRange.openClosed("a", "c")),
+ // (a,c] [a,c] -> (a,c]
+ Arguments.of(fenceOpenClosed, RowRange.closed("a", "c"),
RowRange.openClosed("a", "c")),
+ // (a,c] (-inf, c) -> (a,c)
+ Arguments.of(fenceOpenClosed, RowRange.lessThan("c"),
RowRange.open("a", "c")),
+ // (a,c] (-inf, a) -> empty
+ Arguments.of(fenceOpenClosed, RowRange.lessThan("a"), null),
+
+ // [a,c] (a,c) -> (a,c)
+ Arguments.of(fenceClosed, RowRange.open("a", "c"),
RowRange.open("a", "c")),
+ // [a,c] [a,c) -> [a,c)
+ Arguments.of(fenceClosed, RowRange.closedOpen("a", "c"),
RowRange.closedOpen("a", "c")),
+ // [a,c] (a,c] -> (a,c]
+ Arguments.of(fenceClosed, RowRange.openClosed("a", "c"),
RowRange.openClosed("a", "c")),
+ // [a,c] [a,c] -> [a,c]
+ Arguments.of(fenceClosed, RowRange.closed("a", "c"),
RowRange.closed("a", "c")),
+
+ // (a,c) (-inf, +inf) -> (a,c)
+ Arguments.of(fenceOpen, RowRange.all(), fenceOpen),
+ // (a,c) [a, +inf) -> (a,c)
+ Arguments.of(fenceOpen, RowRange.atLeast("a"), fenceOpen),
+ // (a,c) (-inf, c] -> (a,c)
+ Arguments.of(fenceOpen, RowRange.atMost("c"), fenceOpen),
+ // (a,c) [a,c] -> (a,c)
+ Arguments.of(fenceOpen, RowRange.closed("a", "c"), fenceOpen),
+
+ // (a,c) (0,z) -> (a,c)
+ Arguments.of(fenceOpen, RowRange.open("0", "z"), fenceOpen),
+ // (a,c) [0,z) -> (a,c)
+ Arguments.of(fenceOpen, RowRange.closedOpen("0", "z"), fenceOpen),
+ // (a,c) (0,z] -> (a,c)
+ Arguments.of(fenceOpen, RowRange.openClosed("0", "z"), fenceOpen),
+ // (a,c) [0,z] -> (a,c)
+ Arguments.of(fenceOpen, RowRange.closed("0", "z"), fenceOpen),
+
+ // (a,c) (0,b) -> (a,b)
+ Arguments.of(fenceOpen, RowRange.open("0", "b"), RowRange.open("a",
"b")),
+ // (a,c) [0,b) -> (a,b)
+ Arguments.of(fenceOpen, RowRange.closedOpen("0", "b"),
RowRange.open("a", "b")),
+ // (a,c) (0,b] -> (a,b]
+ Arguments.of(fenceOpen, RowRange.openClosed("0", "b"),
RowRange.openClosed("a", "b")),
+ // (a,c) [0,b] -> (a,b]
+ Arguments.of(fenceOpen, RowRange.closed("0", "b"),
RowRange.openClosed("a", "b")),
+
+ // (a,c) (a1,z) -> (a1,c)
+ Arguments.of(fenceOpen, RowRange.open("a1", "z"),
RowRange.open("a1", "c")),
+ // (a,c) [a1,z) -> [a1,c)
+ Arguments.of(fenceOpen, RowRange.closedOpen("a1", "z"),
RowRange.closedOpen("a1", "c")),
+ // (a,c) (a1,z] -> (a1,c)
+ Arguments.of(fenceOpen, RowRange.openClosed("a1", "z"),
RowRange.open("a1", "c")),
+ // (a,c) [a1,z] -> [a1,c)
+ Arguments.of(fenceOpen, RowRange.closed("a1", "z"),
RowRange.closedOpen("a1", "c")),
+
+ // (a,c) (a1,b) -> (a1,b)
+ Arguments.of(fenceOpen, RowRange.open("a1", "b"),
RowRange.open("a1", "b")),
+ // (a,c) [a1,b) -> [a1,b)
+ Arguments.of(fenceOpen, RowRange.closedOpen("a1", "b"),
RowRange.closedOpen("a1", "b")),
+ // (a,c) (a1,b] -> (a1,b]
+ Arguments.of(fenceOpen, RowRange.openClosed("a1", "b"),
RowRange.openClosed("a1", "b")),
+ // (a,c) [a1,b] -> [a1,b]
+ Arguments.of(fenceOpen, RowRange.closed("a1", "b"),
RowRange.closed("a1", "b")),
+ // (a,c) (a,+inf) -> (a,c)
+ Arguments.of(fenceOpen, RowRange.greaterThan("a"),
RowRange.open("a", "c")),
+ // (a,c) (1,+inf) -> (a,c)
+ Arguments.of(fenceOpen, RowRange.greaterThan("1"),
RowRange.open("a", "c")),
+
+ // (c,n) (a,c) -> empty
+ Arguments.of(fenceOpenCN, RowRange.open("a", "c"), null),
+ // (c,n) (a,c] -> empty
+ Arguments.of(fenceOpenCN, RowRange.closedOpen("a", "c"), null),
+ // (c,n) (n,r) -> empty
+ Arguments.of(fenceOpenCN, RowRange.open("n", "r"), null),
+ // (c,n) [n,r) -> empty
+ Arguments.of(fenceOpenCN, RowRange.closedOpen("n", "r"), null),
+ // (c,n) (a,b) -> empty
+ Arguments.of(fenceOpenCN, RowRange.open("a", "b"), null),
+ // (c,n) (a,b] -> empty
+ Arguments.of(fenceOpenCN, RowRange.closedOpen("a", "b"), null),
+
+ // [c,n] (a,c) -> empty
+ Arguments.of(fenceClosedCN, RowRange.open("a", "c"), null),
+ // [c,n] (a,c] -> (c,c)
+ Arguments.of(fenceClosedCN, RowRange.openClosed("a", "c"),
RowRange.closed("c")),
+ // [c,n] (n,r) -> (n,n)
+ Arguments.of(fenceClosedCN, RowRange.open("n", "r"), null),
+ // [c,n] [n,r) -> (n,n)
+ Arguments.of(fenceClosedCN, RowRange.closedOpen("n", "r"),
RowRange.closed("n")),
+ // [c,n] (q,r) -> empty
+ Arguments.of(fenceClosedCN, RowRange.open("q", "r"), null),
+ // [c,n] [q,r) -> empty
+ Arguments.of(fenceClosedCN, RowRange.closedOpen("q", "r"), null),
+
+ // [b] (b,c) -> empty
+ Arguments.of(fenceClosedB, RowRange.open("b", "c"), null),
+ // [b] [b,c) -> [b]
+ Arguments.of(fenceClosedB, RowRange.closedOpen("b", "c"),
RowRange.closed("b")),
+ // [b] (a,b) -> empty
+ Arguments.of(fenceClosedB, RowRange.open("a", "b"), null),
+ // [b] (a,b] -> [b]
+ Arguments.of(fenceClosedB, RowRange.openClosed("a", "b"),
RowRange.closed("b")));
+ }
+
+ @Test
+ public void clipReturnsNull() {
+ RowRange range = RowRange.open("a", "b");
+ RowRange afterRange = RowRange.closed("c");
+ RowRange beforeRange = RowRange.closed("a");
+
+ RowRange clipped = range.clip(afterRange, true);
+ assertNull(clipped);
+
+ clipped = range.clip(beforeRange, true);
+ assertNull(clipped);
+ }
+ }
+
+}
diff --git
a/server/base/src/main/java/org/apache/accumulo/server/compaction/CompactionPluginUtils.java
b/server/base/src/main/java/org/apache/accumulo/server/compaction/CompactionPluginUtils.java
index c62a7bcc00..854f096ebd 100644
---
a/server/base/src/main/java/org/apache/accumulo/server/compaction/CompactionPluginUtils.java
+++
b/server/base/src/main/java/org/apache/accumulo/server/compaction/CompactionPluginUtils.java
@@ -47,6 +47,7 @@ import org.apache.accumulo.core.conf.AccumuloConfiguration;
import org.apache.accumulo.core.conf.ConfigurationTypeHelper;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.core.data.TabletId;
import org.apache.accumulo.core.data.Value;
@@ -61,7 +62,6 @@ import org.apache.accumulo.core.metadata.schema.DataFileValue;
import org.apache.accumulo.core.sample.impl.SamplerConfigurationImpl;
import org.apache.accumulo.core.spi.common.ServiceEnvironment;
import org.apache.accumulo.core.spi.compaction.CompactionDispatcher;
-import org.apache.accumulo.core.summary.Gatherer;
import org.apache.accumulo.core.summary.SummarizerFactory;
import org.apache.accumulo.core.summary.SummaryCollection;
import org.apache.accumulo.core.summary.SummaryReader;
@@ -161,7 +161,8 @@ public class CompactionPluginUtils {
SummaryCollection fsc = SummaryReader
.load(conf, source, file.getFileName(), summarySelector,
factory,
tableConf.getCryptoService())
- .getSummaries(Collections.singletonList(new
Gatherer.RowRange(extent)));
+ .getSummaries(Collections.singletonList(
+ RowRange.range(extent.prevEndRow(), false,
extent.endRow(), true)));
sc.merge(fsc, factory);
}
diff --git
a/shell/src/main/java/org/apache/accumulo/shell/commands/MaxRowCommand.java
b/shell/src/main/java/org/apache/accumulo/shell/commands/MaxRowCommand.java
index cd967f445d..dca9aa7969 100644
--- a/shell/src/main/java/org/apache/accumulo/shell/commands/MaxRowCommand.java
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/MaxRowCommand.java
@@ -19,6 +19,7 @@
package org.apache.accumulo.shell.commands;
import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.shell.Shell;
import org.apache.commons.cli.CommandLine;
@@ -33,12 +34,14 @@ public class MaxRowCommand extends ScanCommand {
final Range range = getRange(cl);
final Authorizations auths = getAuths(cl, shellState);
- final Text startRow = range.getStartKey() == null ? null :
range.getStartKey().getRow();
- final Text endRow = range.getEndKey() == null ? null :
range.getEndKey().getRow();
+ final Text lowerBound = range.getStartKey() == null ? null :
range.getStartKey().getRow();
+ final Text upperBound = range.getEndKey() == null ? null :
range.getEndKey().getRow();
+ final RowRange rowRange = RowRange.range(lowerBound,
range.isStartKeyInclusive(), upperBound,
+ range.isEndKeyInclusive());
try {
- final Text max =
shellState.getAccumuloClient().tableOperations().getMaxRow(tableName, auths,
- startRow, range.isStartKeyInclusive(), endRow,
range.isEndKeyInclusive());
+ final Text max =
+
shellState.getAccumuloClient().tableOperations().getMaxRow(tableName, auths,
rowRange);
if (max != null) {
shellState.getWriter().println(max);
}
diff --git
a/test/src/main/java/org/apache/accumulo/test/ComprehensiveITBase.java
b/test/src/main/java/org/apache/accumulo/test/ComprehensiveITBase.java
index 02b8bb253b..df1fdf03b9 100644
--- a/test/src/main/java/org/apache/accumulo/test/ComprehensiveITBase.java
+++ b/test/src/main/java/org/apache/accumulo/test/ComprehensiveITBase.java
@@ -86,6 +86,7 @@ import org.apache.accumulo.core.data.ConditionalMutation;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.data.constraints.Constraint;
import org.apache.accumulo.core.data.constraints.DefaultKeySizeConstraint;
@@ -656,7 +657,7 @@ public abstract class ComprehensiveITBase extends
SharedMiniClusterBase {
.stream().flatMap(Set::stream).collect(MoreCollectors.onlyElement()));
// ensure no new data was written
assertEquals(new Text(row(99)),
client.tableOperations().getMaxRow(rootsTable,
- AUTHORIZATIONS, new Text(row(98)), true, new Text(row(110)),
true));
+ AUTHORIZATIONS, RowRange.closed(new Text(row(98)), new
Text(row(110)))));
client.securityOperations().grantTablePermission("user1", rootsTable,
TablePermission.WRITE);
diff --git
a/test/src/main/java/org/apache/accumulo/test/ComprehensiveTableOperationsIT_SimpleSuite.java
b/test/src/main/java/org/apache/accumulo/test/ComprehensiveTableOperationsIT_SimpleSuite.java
index 8605352c8b..bb929ad151 100644
---
a/test/src/main/java/org/apache/accumulo/test/ComprehensiveTableOperationsIT_SimpleSuite.java
+++
b/test/src/main/java/org/apache/accumulo/test/ComprehensiveTableOperationsIT_SimpleSuite.java
@@ -59,6 +59,7 @@ import
org.apache.accumulo.core.clientImpl.TabletMergeabilityUtil;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.Filter;
@@ -325,7 +326,7 @@ public class ComprehensiveTableOperationsIT_SimpleSuite
extends SharedMiniCluste
createFateTableRow(userTable);
createScanRefTableRow();
for (var sysTable : SystemTables.tableNames()) {
- var maxRow = ops.getMaxRow(sysTable, Authorizations.EMPTY, null, true,
null, true);
+ var maxRow = ops.getMaxRow(sysTable, Authorizations.EMPTY,
RowRange.all());
log.info("Max row of {} : {}", sysTable, maxRow);
assertNotNull(maxRow);
}
diff --git a/test/src/main/java/org/apache/accumulo/test/FindMaxIT.java
b/test/src/main/java/org/apache/accumulo/test/FindMaxIT.java
index e6eacdd0c0..c71bd4b308 100644
--- a/test/src/main/java/org/apache/accumulo/test/FindMaxIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/FindMaxIT.java
@@ -30,6 +30,7 @@ import org.apache.accumulo.core.client.BatchWriter;
import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.harness.AccumuloClusterHarness;
@@ -50,6 +51,7 @@ public class FindMaxIT extends AccumuloClusterHarness {
return m;
}
+ @SuppressWarnings("deprecation")
@Test
public void test1() throws Exception {
try (AccumuloClient client =
Accumulo.newClient().from(getClientProps()).build()) {
@@ -90,10 +92,6 @@ public class FindMaxIT extends AccumuloClusterHarness {
true, rows.get(i), false);
assertEquals(rows.get(i - 1), max);
- max = client.tableOperations().getMaxRow(tableName,
Authorizations.EMPTY, rows.get(i - 1),
- false, rows.get(i), false);
- assertNull(max);
-
max = client.tableOperations().getMaxRow(tableName,
Authorizations.EMPTY, null, true,
rows.get(i), true);
assertEquals(rows.get(i), max);
@@ -122,4 +120,74 @@ public class FindMaxIT extends AccumuloClusterHarness {
}
}
}
+
+ @Test
+ public void testRowRange() throws Exception {
+ try (AccumuloClient client =
Accumulo.newClient().from(getClientProps()).build()) {
+ String tableName = getUniqueNames(1)[0];
+
+ client.tableOperations().create(tableName);
+
+ try (BatchWriter bw = client.createBatchWriter(tableName)) {
+ bw.addMutation(nm(new byte[] {0}));
+ bw.addMutation(nm(new byte[] {0, 0}));
+ bw.addMutation(nm(new byte[] {0, 1}));
+ bw.addMutation(nm(new byte[] {0, 1, 0}));
+ bw.addMutation(nm(new byte[] {1, 0}));
+ bw.addMutation(nm(new byte[] {'a', 'b', 'c'}));
+ bw.addMutation(nm(new byte[] {(byte) 0xff}));
+ bw.addMutation(nm(new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff,
(byte) 0xff,
+ (byte) 0xff, (byte) 0xff}));
+
+ for (int i = 0; i < 1000; i += 5) {
+ bw.addMutation(nm(String.format("r%05d", i)));
+ }
+ }
+
+ try (Scanner scanner = client.createScanner(tableName,
Authorizations.EMPTY)) {
+
+ ArrayList<Text> rows = new ArrayList<>();
+
+ for (Entry<Key,Value> entry : scanner) {
+ rows.add(entry.getKey().getRow());
+ }
+
+ for (int i = rows.size() - 1; i > 0; i--) {
+ RowRange range = RowRange.closedOpen(rows.get(i - 1), rows.get(i));
+ Text max = client.tableOperations().getMaxRow(tableName,
Authorizations.EMPTY, range);
+ assertEquals(rows.get(i - 1), max);
+
+ range = RowRange.closed(rows.get(i - 1), rows.get(i));
+ max = client.tableOperations().getMaxRow(tableName,
Authorizations.EMPTY, range);
+ assertEquals(rows.get(i), max);
+
+ range = RowRange.atMost(rows.get(i));
+ max = client.tableOperations().getMaxRow(tableName,
Authorizations.EMPTY, range);
+ assertEquals(rows.get(i), max);
+
+ range = RowRange.closed(rows.get(i));
+ max = client.tableOperations().getMaxRow(tableName,
Authorizations.EMPTY, range);
+ assertEquals(rows.get(i), max);
+
+ range = RowRange.openClosed(rows.get(i - 1), rows.get(i));
+ max = client.tableOperations().getMaxRow(tableName,
Authorizations.EMPTY, range);
+ assertEquals(rows.get(i), max);
+
+ }
+
+ RowRange range = RowRange.all();
+ Text max = client.tableOperations().getMaxRow(tableName,
Authorizations.EMPTY, range);
+ assertEquals(rows.get(rows.size() - 1), max);
+
+ range = RowRange.lessThan(new Text(new byte[] {0}));
+ max = client.tableOperations().getMaxRow(tableName,
Authorizations.EMPTY, range);
+ assertNull(max);
+
+ range = RowRange.atMost(new Text(new byte[] {0}));
+ max = client.tableOperations().getMaxRow(tableName,
Authorizations.EMPTY, range);
+ assertEquals(rows.get(0), max);
+ }
+ }
+
+ }
}
diff --git
a/test/src/main/java/org/apache/accumulo/test/NamespacesIT_SimpleSuite.java
b/test/src/main/java/org/apache/accumulo/test/NamespacesIT_SimpleSuite.java
index 6dbc831195..2b04ea8839 100644
--- a/test/src/main/java/org/apache/accumulo/test/NamespacesIT_SimpleSuite.java
+++ b/test/src/main/java/org/apache/accumulo/test/NamespacesIT_SimpleSuite.java
@@ -68,6 +68,7 @@ import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.RowRange;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.Filter;
import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
@@ -1044,7 +1045,7 @@ public class NamespacesIT_SimpleSuite extends
SharedMiniClusterBase {
assertNoTableNoNamespace(() -> ops.getIteratorSetting(tableName, "a",
IteratorScope.scan));
assertNoTableNoNamespace(() -> ops.getLocalityGroups(tableName));
assertNoTableNoNamespace(
- () -> ops.getMaxRow(tableName, Authorizations.EMPTY, a, true, z,
true));
+ () -> ops.getMaxRow(tableName, Authorizations.EMPTY,
RowRange.closed(a, z)));
assertNoTableNoNamespace(() -> ops.getConfiguration(tableName));
assertNoTableNoNamespace(() ->
ops.importDirectory("").to(tableName).load());
assertNoTableNoNamespace(() -> ops.testClassLoad(tableName,
VersioningIterator.class.getName(),