This is an automated email from the ASF dual-hosted git repository.

konstantinov pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git

commit 909ed8b949403abcd72995f5dedcbcd0f5f6531d
Merge: f05b27502f 1a12cbb349
Author: Dmitry Konstantinov <[email protected]>
AuthorDate: Sat Nov 22 11:47:05 2025 +0000

    Merge branch 'cassandra-5.0' into trunk
    
    * cassandra-5.0:
      Fix cleanup of old incremental repair sessions in case of owned token 
range changes or a table deleting

 CHANGES.txt                                        |   1 +
 src/java/org/apache/cassandra/dht/Range.java       |  15 +++
 .../cassandra/repair/consistent/LocalSessions.java |  30 ++++-
 ...ncrementalRepairCleanupAfterNodeAddingTest.java | 147 +++++++++++++++++++++
 test/unit/org/apache/cassandra/dht/RangeTest.java  |  44 ++++++
 .../repair/consistent/LocalSessionTest.java        |  16 +++
 6 files changed, 251 insertions(+), 2 deletions(-)

diff --cc CHANGES.txt
index b0b278a751,f6dfe4045a..e2262d4f38
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -333,21 -99,7 +333,22 @@@ Merged from 4.1
   * Optionally skip exception logging on invalid legacy protocol magic 
exception (CASSANDRA-19483)
   * Fix SimpleClient ability to release acquired capacity (CASSANDRA-20202)
   * Fix WaitQueue.Signal.awaitUninterruptibly may block forever if invoking 
thread is interrupted (CASSANDRA-20084)
 + * Run audit_logging_options through santiation and validation on startup 
(CASSANDRA-20208)
 + * Enforce CQL message size limit on multiframe messages (CASSANDRA-20052)
 + * Fix race condition in DecayingEstimatedHistogramReservoir during rescale 
(CASSANDRA-19365)
  Merged from 4.0:
++ * Fix cleanup of old incremental repair sessions in case of owned token 
range changes or a table deleting (CASSANDRA-20877)
 + * Fix memory leak in BufferPoolAllocator when a capacity needs to be 
extended (CASSANDRA-20753)
 + * Leveled Compaction doesn't validate maxBytesForLevel when the table is 
altered/created (CASSANDRA-20570)
 + * Updated dtest-api to 0.0.18 and removed JMX-related classes that now live 
in the dtest-api (CASSANDRA-20884)
 + * Fixed incorrect error message constant for keyspace name length validation 
(CASSANDRA-20915)
 + * Prevent too long table names not fitting file names (CASSANDRA-20389)
 + * Update Jackson to 2.19.2 (CASSANDRA-20848)
 + * Update commons-lang3 to 3.18.0 (CASSANDRA-20849)
 + * Add NativeTransportMaxConcurrentConnectionsPerIp to StorageProxyMBean 
(CASSANDRA-20642)
 + * Make secondary index implementations notified about rows in fully expired 
SSTables in compaction (CASSANDRA-20829)
 + * Ensure prepared_statement INSERT timestamp precedes eviction DELETE 
(CASSANDRA-19703)
 + * Gossip doesn't converge due to race condition when updating EndpointStates 
multiple fields (CASSANDRA-20659)
   * Handle sstable metadata stats file getting a new mtime after compaction 
has finished (CASSANDRA-18119)
   * Honor MAX_PARALLEL_TRANSFERS correctly (CASSANDRA-20532)
   * Updating a column with a new TTL but same expiration time is 
non-deterministic and causes repair mismatches. (CASSANDRA-20561)
diff --cc src/java/org/apache/cassandra/repair/consistent/LocalSessions.java
index 47d614216d,e2bfe05eb2..04fb7a6cf4
--- a/src/java/org/apache/cassandra/repair/consistent/LocalSessions.java
+++ b/src/java/org/apache/cassandra/repair/consistent/LocalSessions.java
@@@ -256,7 -262,15 +262,15 @@@ public class LocalSession
              if (state == null)
                  return false;
  
-             long minRepaired = state.minRepairedAt(session.ranges);
+             Collection<Range<Token>> actualRanges = 
rangesPerKeyspaceCache.computeIfAbsent(tableMetadata.keyspace, (keyspace) -> {
 -                List<Range<Token>> localRanges = 
getLocalRanges(tableMetadata.keyspace);
++                Collection<Range<Token>> localRanges = 
getLocalRanges(tableMetadata.keyspace);
+                 if (localRanges.isEmpty()) // to handle the case when we run 
before the information about owned ranges is properly populated
+                     return session.ranges;
+ 
+                 // ignore token ranges which were moved to other nodes and 
not owned by the current one anymore
+                 return Range.intersect(session.ranges, localRanges);
+             });
+             long minRepaired = state.minRepairedAt(actualRanges);
              if (minRepaired <= session.repairedAt)
                  return false;
          }
@@@ -264,6 -278,18 +278,18 @@@
          return true;
      }
  
+     @VisibleForTesting
+     protected TableMetadata getTableMetadata(TableId tableId)
+     {
+         return Schema.instance.getTableMetadata(tableId);
+     }
+ 
+     @VisibleForTesting
 -    protected List<Range<Token>> getLocalRanges(String keyspace)
++    protected Collection<Range<Token>> getLocalRanges(String keyspace)
+     {
+         return StorageService.instance.getLocalAndPendingRanges(keyspace);
+     }
+ 
      public RepairedState.Stats getRepairedStats(TableId tid, 
Collection<Range<Token>> ranges)
      {
          RepairedState state = repairedStates.get(tid);
diff --cc test/unit/org/apache/cassandra/dht/RangeTest.java
index e8198b9cb4,fd982d9866..cac2594ddc
--- a/test/unit/org/apache/cassandra/dht/RangeTest.java
+++ b/test/unit/org/apache/cassandra/dht/RangeTest.java
@@@ -766,417 -737,47 +766,461 @@@ public class RangeTest extends Cassandr
          assertEquals(Sets.newHashSet(r(1, 4), r(11, 15)), 
Range.subtract(ranges, asList(r(4, 7), r(8, 11))));
      }
  
 +    @Test
 +    public void testIntersectsBounds()
 +    {
 +        Range<Token> r = r(0, 100);
 +        assertTrue(r.intersects(bounds(5, 10)));
 +        assertTrue(r.intersects(bounds(100, 110)));
 +        assertTrue(r.intersects(bounds(-100, 200)));
 +        assertTrue(r.intersects(bounds(10, 15)));
 +        assertTrue(r.intersects(bounds(20,20)));
 +
 +        assertFalse(r.intersects(bounds(-5, 0)));
 +        assertFalse(r.intersects(bounds(-5, -1)));
 +        assertFalse(r.intersects(bounds(110, 114)));
 +    }
 +
 +    private static Bounds<Token> bounds(long left, long right)
 +    {
 +        return new Bounds<>(t(left), t(right));
 +    }
 +
 +    private static AbstractBounds<PartitionPosition> bounds(PartitionPosition 
left, boolean leftInclusive, PartitionPosition right, boolean rightInclusive)
 +    {
 +        return AbstractBounds.bounds(left, leftInclusive, right, 
rightInclusive);
 +    }
 +
 +    @Test
 +    @UseMurmur3Partitioner
 +    public void testIsInNormalizedRanges()
 +    {
 +        NormalizedRanges<Token> ranges = 
normalizedRanges(ImmutableList.of(fromString("(1,10]"), fromString("(10,20]"), 
fromString("(30,40]"), fromString("(50,60]"), fromString("(60,70]"), 
fromString("(80,90]"), fromString("(" + Long.MAX_VALUE + 
",-9223372036854775808]")));
 +        for (int ii = 0; ii < 100; ii++)
 +        {
 +            boolean isIn = ranges.intersects(new LongToken(ii));
 +            if (ii > 1 && ii <= 20)
 +                assertTrue("Index " + ii, isIn);
 +            else if (ii > 30 && ii <= 40)
 +                assertTrue("Index " + ii, isIn);
 +            else if (ii > 50 && ii <= 70)
 +                assertTrue("Index " + ii, isIn);
 +            else if (ii > 80 && ii <= 90)
 +                assertTrue("Index " + ii, isIn);
 +            else
 +                assertFalse("Index " + ii, isIn);
 +        }
 +        assertFalse(ranges.intersects(new LongToken(Long.MAX_VALUE)));
 +        assertTrue(ranges.intersects(new LongToken(Long.MIN_VALUE)));
 +        ranges = 
normalizedRanges(ImmutableList.of(fromString("(-9223372036854775808,-9223372036854775807]")));
 +        assertFalse(ranges.intersects(new LongToken(Long.MIN_VALUE)));
 +        assertTrue(ranges.intersects(new LongToken(Long.MIN_VALUE + 1)));
 +        ranges = normalizedRanges(ImmutableList.of(fromString("(" + 
(Long.MAX_VALUE - 1) + ",-9223372036854775808]")));
 +        assertFalse(ranges.intersects(new LongToken(Long.MAX_VALUE - 1)));
 +        assertTrue(ranges.intersects(new LongToken(Long.MAX_VALUE)));
 +        assertTrue(ranges.intersects(new LongToken(Long.MIN_VALUE)));
 +        assertFalse(ranges.intersects(new LongToken(Long.MAX_VALUE - 1)));
 +        assertTrue(ranges.intersects(new LongToken(Long.MAX_VALUE)));
 +        assertTrue(ranges.intersects(new LongToken(Long.MIN_VALUE)));
 +    }
 +
 +    @Test
 +    @UseMurmur3Partitioner
 +    public void testSubtractNormalizedRanges()
 +    {
 +        NormalizedRanges<Token> ranges = 
normalizedRanges(ImmutableList.of(fromString("(1,10]"), fromString("(10,20]"), 
fromString("(30,40]"), fromString("(50,60]"), fromString("(60,70]"), 
fromString("(80,90]"), fromString("(" + Long.MAX_VALUE + 
",-9223372036854775808]")));
 +        for (int ii = 0; ii < 100; ii++)
 +        {
 +            boolean isIn = ranges.intersects(new LongToken(ii));
 +            if (ii > 1 && ii <= 20)
 +                assertTrue("Index " + ii, isIn);
 +            else if (ii > 30 && ii <= 40)
 +                assertTrue("Index " + ii, isIn);
 +            else if (ii > 50 && ii <= 70)
 +                assertTrue("Index " + ii, isIn);
 +            else if (ii > 80 && ii <= 90)
 +                assertTrue("Index " + ii, isIn);
 +            else
 +                assertFalse("Index " + ii, isIn);
 +        }
 +        NormalizedRanges<Token> rightMostRange = 
normalizedRanges(ImmutableList.of(r(Long.MAX_VALUE, Long.MIN_VALUE)));
 +        NormalizedRanges<Token> maxLongRange = 
normalizedRanges(ImmutableList.of(r(Long.MAX_VALUE - 1, Long.MAX_VALUE)));
 +
 +        assertEquals(emptyList(),  ranges.subtract(ranges));
 +        assertEquals(emptyList(), rightMostRange.subtract(ranges));
 +        assertEquals(maxLongRange, maxLongRange.subtract(ranges));
 +        ranges = maxLongRange;
 +        assertEquals(emptyList(), ranges.subtract(ranges));
 +        assertEquals(rightMostRange, rightMostRange.subtract(ranges));
 +        assertEquals(emptyList(), maxLongRange.subtract(ranges));
 +        ranges = normalizedRanges(ImmutableList.of(fromString("(" + 
(Long.MAX_VALUE - 1) + ",-9223372036854775808]")));
 +        assertEquals(emptyList(), ranges.subtract(ranges));
 +        assertEquals(emptyList(), rightMostRange.subtract(ranges));
 +        assertEquals(emptyList(), maxLongRange.subtract(ranges));
 +    }
 +
 +    @Test
 +    public void testExpensiveChecksBurn()
 +    {
 +        long seed = System.nanoTime();
 +        System.out.println(seed);
 +        Random r = new java.util.Random(seed);
 +
 +        Stopwatch elapsed = Stopwatch.createStarted();
 +        while (elapsed.elapsed(SECONDS) != 10)
 +        {
 +            int numRanges = 3;
 +            List<Range<Token>> aList = new ArrayList();
 +            for (int ii = 0; ii < numRanges; ii++)
 +            {
 +                aList.add(new Range<>(new LongToken(r.nextLong()), new 
LongToken(r.nextLong())));
 +            }
 +            NormalizedRanges<Token> a = normalizedRanges(aList);
 +            List<Range<Token>> bList = new ArrayList();
 +            for (int ii = 0; ii < numRanges; ii++)
 +            {
 +                bList.add(new Range<>(new LongToken(r.nextLong()), new 
LongToken(r.nextLong())));
 +            }
 +            NormalizedRanges<Token> b = normalizedRanges(bList);
 +
 +            for (int ii = 0; ii < 1000; ii++)
 +            {
 +                Token t = new LongToken(r.nextLong());
 +                a.intersects(t);
 +                b.intersects(t);
 +            }
 +
 +            a.intersection(b);
 +            a.intersection(a);
 +            b.intersection(a);
 +            b.intersection(b);
 +
 +            a.subtract(b);
 +            a.subtract(a);
 +            b.subtract(a);
 +            b.subtract(b);
 +
 +            if (!a.isEmpty())
 +                a.invert();
 +            if (!b.isEmpty())
 +                b.invert();
 +        }
 +    }
 +
 +    @Test
 +    public void testIntersectionAndRemainder()
 +    {
 +        // Intersection will only make max key bounds so use minKeyBound
 +        // so you know where the bound came from
 +        Token oneT = t(1);
 +        PartitionPosition onePP = oneT.minKeyBound();
 +        Token twoT = t(2);
 +        PartitionPosition twoPP = twoT.minKeyBound();
 +        Token threeT = t(3);
 +        PartitionPosition threePP = threeT.minKeyBound();
 +        Token fourT = t(4);
 +        PartitionPosition fourPP = fourT.minKeyBound();
 +        Token fiveT = t(5);
 +        PartitionPosition fivePP = fiveT.minKeyBound();
 +        Token sixT = t(6);
 +        PartitionPosition sixPP = sixT.minKeyBound();
 +        Token sevenT = t(7);
 +        PartitionPosition sevenPP = sevenT.minKeyBound();
 +        Token eightT = t(8);
 +        PartitionPosition eightPP = eightT.minKeyBound();
 +        Token minT = Murmur3Partitioner.MINIMUM;
 +        PartitionPosition minPP = minT.minKeyBound();
 +
 +        Range<Token> r = r(threeT, sixT);
 +
 +        // Completely before
 +        testInclusivity(onePP, twoPP, r,
 +                        Pair.create(null, null),
 +                        Pair.create(null, null),
 +                        Pair.create(null, null),
 +                        Pair.create(null, null));
 +
 +        // Completely after
 +        testInclusivity(sevenPP, eightPP, r,
 +                        Pair.create(null, bounds(sevenPP, true, eightPP, 
true)),
 +                        Pair.create(null, bounds(sevenPP, true, eightPP, 
false)),
 +                        Pair.create(null, bounds(sevenPP, false, eightPP, 
true)),
 +                        Pair.create(null, bounds(sevenPP, false, eightPP, 
false)));
 +
 +        // Overlapping
 +        testInclusivity(threePP, sixPP, r,
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixPP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixPP, false), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixPP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixPP, false), null));
 +
 +        // Completely contained by range, should echo back the same bound
 +        testInclusivity(fourPP, fivePP, r,
 +                        Pair.create(bounds(fourPP, true, fivePP, true), null),
 +                        Pair.create(bounds(fourPP, true, fivePP, false), 
null),
 +                        Pair.create(bounds(fourPP, false, fivePP, true), 
null),
 +                        Pair.create(bounds(fourPP, false, fivePP, false), 
null));
 +
 +        // Overlap left only
 +        testInclusivity(threePP, fivePP, r,
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, false), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, false), null));
 +
 +        // Overlap right only
 +        testInclusivity(fourPP, sixPP, r,
 +                        Pair.create(bounds(fourPP, true, sixPP, true), null),
 +                        Pair.create(bounds(fourPP, true, sixPP, false), null),
 +                        Pair.create(bounds(fourPP, false, sixPP, true), null),
 +                        Pair.create(bounds(fourPP, false, sixPP, false), 
null));
 +
 +        // Contains range
 +        testInclusivity(twoPP, sevenPP, r,
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixT.maxKeyBound(), true), bounds(sixT.maxKeyBound(), false, sevenPP, true)),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixT.maxKeyBound(), true), bounds(sixT.maxKeyBound(), false, sevenPP, false)),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixT.maxKeyBound(), true), bounds(sixT.maxKeyBound(), false, sevenPP, true)),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixT.maxKeyBound(), true), bounds(sixT.maxKeyBound(), false, sevenPP, false)));
 +
 +        // Split by range left bound
 +        testInclusivity(twoPP, fivePP, r,
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, false), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, false), null));
 +
 +        // Split by range right bound
 +        testInclusivity(fivePP, sevenPP, r,
 +                        Pair.create(bounds(fivePP, true, sixT.maxKeyBound(), 
true), bounds(sixT.maxKeyBound(), false, sevenPP, true)),
 +                        Pair.create(bounds(fivePP, true, sixT.maxKeyBound(), 
true), bounds(sixT.maxKeyBound(), false, sevenPP, false)),
 +                        Pair.create(bounds(fivePP, false, sixT.maxKeyBound(), 
true), bounds(sixT.maxKeyBound(), false, sevenPP, true)),
 +                        Pair.create(bounds(fivePP, false, sixT.maxKeyBound(), 
true), bounds(sixT.maxKeyBound(), false, sevenPP, false)));
 +
 +        /*
 +         * Test size 1 bound
 +         */
 +        // Completely before
 +        testInclusivity(onePP, onePP, r,
 +                        Pair.create(null, null));
 +
 +        // Completely after
 +        testInclusivity(eightPP, eightPP, r,
 +                        Pair.create(null, bounds(eightPP, true, eightPP, 
true)));
 +
 +        // Completely contained by range, should echo back the same bound
 +        testInclusivity(fivePP, fivePP, r,
 +                        Pair.create(bounds(fivePP, true, fivePP, true), 
null));
 +
 +        // Overlap left only
 +        testInclusivity(threePP, threePP, r,
 +                        Pair.create(null, null));
 +
 +        // Overlap right only
 +        testInclusivity(sixPP, sixPP, r,
 +                        Pair.create(bounds(sixPP, true, sixPP, true), null));
 +
 +        /*
 +         * Test all cases where the right of Bounds is minimum
 +         */
 +        // Completely after
 +        testInclusivity(sevenPP, minPP, r,
 +                        Pair.create(null, bounds(sevenPP, true, minPP, true)),
 +                        Pair.create(null, bounds(sevenPP, true, minPP, 
false)),
 +                        Pair.create(null, bounds(sevenPP, false, minPP, 
true)),
 +                        Pair.create(null, bounds(sevenPP, false, minPP, 
false)));
 +
 +        // Contains range
 +        testInclusivity(twoPP, minPP, r,
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixT.maxKeyBound(), true), bounds(sixT.maxKeyBound(), false, minPP, true)),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixT.maxKeyBound(), true), bounds(sixT.maxKeyBound(), false, minPP, false)),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixT.maxKeyBound(), true), bounds(sixT.maxKeyBound(), false, minPP, true)),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
sixT.maxKeyBound(), true), bounds(sixT.maxKeyBound(), false, minPP, false)));
 +
 +        // Split by range right bound
 +        testInclusivity(fivePP, minPP, r,
 +                        Pair.create(bounds(fivePP, true, sixT.maxKeyBound(), 
true), bounds(sixT.maxKeyBound(), false, minPP, true)),
 +                        Pair.create(bounds(fivePP, true, sixT.maxKeyBound(), 
true), bounds(sixT.maxKeyBound(), false, minPP, false)),
 +                        Pair.create(bounds(fivePP, false, sixT.maxKeyBound(), 
true), bounds(sixT.maxKeyBound(), false, minPP, true)),
 +                        Pair.create(bounds(fivePP, false, sixT.maxKeyBound(), 
true), bounds(sixT.maxKeyBound(), false, minPP, false)));
 +
 +        /*
 +         * Test all cases where the right of the range is minimum
 +         */
 +        r = r(threeT, minT);
 +        // Completely before
 +        testInclusivity(onePP, twoPP, r,
 +                        Pair.create(null, null),
 +                        Pair.create(null, null),
 +                        Pair.create(null, null),
 +                        Pair.create(null, null));
 +
 +        // Overlapping
 +        testInclusivity(threePP, minPP, r,
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
minPP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
minPP, false), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
minPP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
minPP, false), null));
 +
 +        // Completely contained by range, should echo back the same bound
 +        testInclusivity(fourPP, fivePP, r,
 +                        Pair.create(bounds(fourPP, true, fivePP, true), null),
 +                        Pair.create(bounds(fourPP, true, fivePP, false), 
null),
 +                        Pair.create(bounds(fourPP, false, fivePP, true), 
null),
 +                        Pair.create(bounds(fourPP, false, fivePP, false), 
null));
 +
 +        // Overlap left only
 +        testInclusivity(threePP, fivePP, r,
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, false), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, false), null));
 +
 +        // Overlap right only
 +        testInclusivity(fourPP, minPP, r,
 +                        Pair.create(bounds(fourPP, true, minPP, true), null),
 +                        Pair.create(bounds(fourPP, true, minPP, false), null),
 +                        Pair.create(bounds(fourPP, false, minPP, true), null),
 +                        Pair.create(bounds(fourPP, false, minPP, false), 
null));
 +
 +        // Contains range
 +        testInclusivity(twoPP, minPP, r,
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
minPP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
minPP, false), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
minPP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
minPP, false), null));
 +
 +        // Split by range left bound
 +        testInclusivity(twoPP, fivePP, r,
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, false), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, true), null),
 +                        Pair.create(bounds(threeT.maxKeyBound(), false, 
fivePP, false), null));
 +    }
 +
 +    private void testInclusivity(PartitionPosition left, PartitionPosition 
right, Range<Token> range,
 +                                 Pair<AbstractBounds<PartitionPosition>, 
AbstractBounds<PartitionPosition>> expected)
 +    {
 +        testInclusivity(left, right, range, expected, null, null, null);
 +    }
 +
 +    private void testInclusivity(PartitionPosition left, PartitionPosition 
right, Range<Token> range,
 +                                 Pair<AbstractBounds<PartitionPosition>, 
AbstractBounds<PartitionPosition>> expected1,
 +                                 Pair<AbstractBounds<PartitionPosition>, 
AbstractBounds<PartitionPosition>> expected2,
 +                                 Pair<AbstractBounds<PartitionPosition>, 
AbstractBounds<PartitionPosition>> expected3,
 +                                 Pair<AbstractBounds<PartitionPosition>, 
AbstractBounds<PartitionPosition>> expected4)
 +    {
 +        testInclusivity(left, right, range, new Pair[] { expected1, 
expected2, expected3, expected4 });
 +    }
 +
 +    private void testInclusivity(PartitionPosition left, PartitionPosition 
right, Range<Token> range,
 +                                 Pair<AbstractBounds<PartitionPosition>, 
AbstractBounds<PartitionPosition>>[] expecteds)
 +    {
 +        int i = 0;
 +        for (Boolean leftInclusive : ImmutableList.of(true, false))
 +        {
 +            for (Boolean rightInclusive : ImmutableList.of(true, false))
 +            {
 +                Pair<AbstractBounds<PartitionPosition>, 
AbstractBounds<PartitionPosition>> expected = expecteds[i++];
 +                if (expected == null)
 +                    continue;
 +                AbstractBounds<PartitionPosition> expectedIntersection = 
expected.left;
 +                AbstractBounds<PartitionPosition> expectedRemainder = 
expected.right;
 +                AbstractBounds<PartitionPosition> testBounds = bounds(left, 
leftInclusive, right, rightInclusive);
 +                Pair<AbstractBounds<PartitionPosition>, 
AbstractBounds<PartitionPosition>> interSectionAndRemainder = 
intersectionAndRemainder(testBounds, range);
 +                AbstractBounds<PartitionPosition> intersection = 
interSectionAndRemainder.left;
 +                AbstractBounds<PartitionPosition> remainder = 
interSectionAndRemainder.right;
 +                String message = format("Expected %s intersecting inclusive 
left %b, inclusive right %b, %s with %s", expected, leftInclusive, 
rightInclusive, bounds(left, leftInclusive, right, rightInclusive), range);
 +                assertEquals(message, expected, 
intersectionAndRemainder(bounds(left, leftInclusive, right, rightInclusive), 
range));
 +                System.out.println(message.replace("Expected", "Expecting"));
 +
 +                if (remainder == testBounds)
 +                    assertNull(intersection);
 +                if (intersection == testBounds)
 +                    assertNull(remainder);
 +                if (Objects.equals(remainder, testBounds) && remainder != 
testBounds)
 +                    fail("Should return existing bounds");
 +                if (Objects.equals(intersection, testBounds) && intersection 
!= testBounds)
 +                    fail("Should return existing bounds");
 +
 +                // Need to validate that we roundtrip the actual exact input 
PartitionPosition and don't lose part of the input bound that might be needed
 +                // Remainder can either be the entire thing because there is 
no intersection
 +                // Remainder should always preserve the right bound since 
range is right inclusive, the left bound will always be a `maxKeyBound` from 
the range
 +                if (remainder != null && remainder != testBounds)
 +                {
 +                    assertTrue(remainder.right == expectedRemainder.right);
 +                    assertTrue(remainder.right == right);
 +                    assertEquals(remainder.inclusiveRight(), 
expectedRemainder.inclusiveRight());
 +                    assertFalse(remainder.inclusiveLeft());
 +                    assertFalse(remainder.left == left);
 +                    // Not strictly necessary, but we do always use the left 
of the range and create a new key bound
 +                    assertEquals(remainder.left, range.right.maxKeyBound());
 +                }
 +
 +                // Range is left exclusive so the left should be preserved if 
it is greater than range left
 +                // otherwise it should be replaced
 +                if (intersection != null && intersection != testBounds)
 +                {
 +                    if (intersection.left.getToken().compareTo(range.left) > 
0)
 +                    {
 +                        assertEquals(intersection.inclusiveLeft(), 
expectedIntersection.inclusiveLeft());
 +                        assertTrue(intersection.inclusiveRight());
 +                        assertTrue(intersection.left == 
expectedIntersection.left);
 +                        assertTrue(intersection.left == left);
 +                    }
 +                    else
 +                    {
 +                        // Should be replaced by a KeyBound from the range
 +                        assertTrue(intersection.left != 
expectedIntersection.left);
 +                        assertTrue(intersection.left != left);
 +                        // Max bound since range is not left inclusive and 
excluded
 +                        assertEquals(intersection.left, 
range.left.maxKeyBound());
 +                    }
 +                }
 +            }
 +        }
 +    }
++
+     @Test
+     public void testGroupIntersection()
+     {
+         assertEquals(Collections.emptyList(),
+                      Range.intersect(asList(r(1, 5), r(10, 15)),
+                                      asList(r(6, 7), r(20, 25))
+         ));
+ 
+         assertEquals(asList(r(5, 6)),
+                      Range.intersect(asList(r(1, 6), r(10, 15)),
+                                      asList(r(5, 10))
+         ));
+ 
+         assertEquals(asList(r(5, 6), r(10, 11)),
+                      Range.intersect(asList(r(1, 6), r(10, 15)),
+                                      asList(r(5, 11))
+                      ));
+ 
+         assertEquals(asList(r(5, 6), r(10, 11)),
+                      Range.intersect(asList(r(1, 6), r(10, 15)),
+                                      asList(r(5, 11))
+                      ));
+ 
+         assertEquals(asList(r(5, 6), r(10, 11), r(12, 15)),
+                      Range.intersect(asList(r(1, 6), r(10, 15)),
+                                      asList(r(5, 11), r(12, 20))
+                      ));
+ 
+         assertEquals(asList(r(5, 6), r(10, 15)),
+                      Range.intersect(asList(r(1, 6), r(10, 15)),
+                                      asList(r(5, 11), r(11, 20))
+                      ));
+ 
+         assertEquals(Collections.emptyList(),
+                      Range.intersect(Collections.emptyList(),
+                                      asList(r(5, 11), r(11, 20))
+                      ));
+ 
+         assertEquals(Collections.emptyList(),
+                      Range.intersect(asList(r(1, 6), r(10, 15)),
+                                      Collections.emptyList()
+                      ));
+     }
  }
diff --cc test/unit/org/apache/cassandra/repair/consistent/LocalSessionTest.java
index 5908e7d8e7,57976c3e33..e4965d2a87
--- a/test/unit/org/apache/cassandra/repair/consistent/LocalSessionTest.java
+++ b/test/unit/org/apache/cassandra/repair/consistent/LocalSessionTest.java
@@@ -49,8 -52,9 +52,9 @@@ import org.apache.cassandra.net.Message
  import org.apache.cassandra.repair.AbstractRepairTest;
  import org.apache.cassandra.locator.InetAddressAndPort;
  import org.apache.cassandra.repair.KeyspaceRepairManager;
 +import org.apache.cassandra.schema.Schema;
+ import org.apache.cassandra.schema.TableId;
  import org.apache.cassandra.schema.TableMetadata;
 -import org.apache.cassandra.schema.Schema;
  import org.apache.cassandra.schema.SchemaConstants;
  import org.apache.cassandra.db.ColumnFamilyStore;
  import org.apache.cassandra.db.Keyspace;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to