github-advanced-security[bot] commented on code in PR #20129:
URL: https://github.com/apache/druid/pull/20129#discussion_r3848527270


##########
benchmarks/src/test/java/org/apache/druid/benchmark/query/TimeseriesBenchmark.java:
##########
@@ -322,57 +326,54 @@
     private int numSegments;
 
     private ExecutorService executorService;
-    private File qIndexesDir;
+    private SegmentGenerator segmentGenerator;
     private List<QueryableIndex> qIndexes;
 
     @Setup
-    public void setup(TimeseriesBenchmark global) throws IOException
+    public void setup(TimeseriesBenchmark global)
     {
-      global.appendableIndexSpec = new OnheapIncrementalIndex.Spec();
-
       executorService = Execs.multiThreaded(numSegments, 
"TimeseriesThreadPool");
 
-      qIndexesDir = FileUtils.createTempDir();
+      segmentGenerator = new SegmentGenerator();
       qIndexes = new ArrayList<>();
 
       for (int i = 0; i < numSegments; i++) {
         log.info("Generating rows for segment " + i);
 
-        IncrementalIndex incIndex = global.makeIncIndex();
-        global.generator.reset(RNG_SEED + i).addToIndex(incIndex, 
global.rowsPerSegment);
-
-        File indexFile = INDEX_MERGER_V9.persist(
-            incIndex,
-            new File(qIndexesDir, String.valueOf(i)),
-            IndexSpec.getDefault(),
-            null
+        qIndexes.add(
+            segmentGenerator.generate(
+                global.makeDataSegment(i),
+                global.schemaInfo,
+                Granularities.NONE,
+                global.rowsPerSegment
+            )
         );
-        incIndex.close();
-
-        qIndexes.add(INDEX_IO.loadIndex(indexFile));
       }
     }
 
     @TearDown
-    public void tearDown()
+    public void tearDown() throws IOException
     {
       for (QueryableIndex index : qIndexes) {
         if (index != null) {
           index.close();
         }
       }
-      if (qIndexesDir != null) {
-        qIndexesDir.delete();
+      if (segmentGenerator != null) {
+        segmentGenerator.close();
       }
     }
   }
 
-  private IncrementalIndex makeIncIndex()
+  private DataSegment makeDataSegment(final int segmentNumber)
   {
-    return appendableIndexSpec.builder()
-        .setSimpleTestingIndexSchema(schemaInfo.getAggsArray())
-        .setMaxRowCount(rowsPerSegment)
-        .build();
+    return DataSegment.builder()

Review Comment:
   ## CodeQL / Deprecated method or constructor invocation
   
   Invoking [DataSegment.builder](1) should be avoided because it has been 
deprecated.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11911)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11923)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    long n = 0;
+    while (reference.hasNext()) {
+      assertTrue(iterator.hasNext(), "ran out early at " + n);
+      assertEquals(reference.next(), iterator.next(), "value " + n);
+      n++;
+    }
+    assertFalse(iterator.hasNext(), "extra values after " + n);
+    assertThrows(NoSuchElementException.class, iterator::next);
+    assertThrows(NoSuchElementException.class, iterator::peekNext);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testPeekNextDoesNotAdvance(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    while (reference.hasNext()) {
+      final int value = reference.next();
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.next());
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomSeeksInBothDirections(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(5678);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 200_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      assertEquals(expected(bitmap, target), peek(iterator), "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAscendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11920)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    long n = 0;
+    while (reference.hasNext()) {
+      assertTrue(iterator.hasNext(), "ran out early at " + n);
+      assertEquals(reference.next(), iterator.next(), "value " + n);
+      n++;
+    }
+    assertFalse(iterator.hasNext(), "extra values after " + n);
+    assertThrows(NoSuchElementException.class, iterator::next);
+    assertThrows(NoSuchElementException.class, iterator::peekNext);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testPeekNextDoesNotAdvance(final String name, final 
ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11922)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    long n = 0;
+    while (reference.hasNext()) {
+      assertTrue(iterator.hasNext(), "ran out early at " + n);
+      assertEquals(reference.next(), iterator.next(), "value " + n);
+      n++;
+    }
+    assertFalse(iterator.hasNext(), "extra values after " + n);
+    assertThrows(NoSuchElementException.class, iterator::next);
+    assertThrows(NoSuchElementException.class, iterator::peekNext);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testPeekNextDoesNotAdvance(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    while (reference.hasNext()) {
+      final int value = reference.next();
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.next());
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomSeeksInBothDirections(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(5678);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 200_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      assertEquals(expected(bitmap, target), peek(iterator), "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAscendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = 0; target < span; target += step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testDescendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = span - 1; target >= 0; target -= step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSeekIsIdempotent(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(2468);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 20_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      final long first = peek(iterator);
+      iterator.seek(target);
+      assertEquals(first, peek(iterator), "second seek(" + 
Integer.toUnsignedString(target) + ")");
+      assertEquals(expected(bitmap, target), first, "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testInterleavedNextAndAdvanceIfNeeded(final String name, final 
ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11917)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    long n = 0;
+    while (reference.hasNext()) {
+      assertTrue(iterator.hasNext(), "ran out early at " + n);
+      assertEquals(reference.next(), iterator.next(), "value " + n);
+      n++;
+    }
+    assertFalse(iterator.hasNext(), "extra values after " + n);
+    assertThrows(NoSuchElementException.class, iterator::next);
+    assertThrows(NoSuchElementException.class, iterator::peekNext);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testPeekNextDoesNotAdvance(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    while (reference.hasNext()) {
+      final int value = reference.next();
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.next());
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomSeeksInBothDirections(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(5678);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 200_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      assertEquals(expected(bitmap, target), peek(iterator), "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAscendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = 0; target < span; target += step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testDescendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = span - 1; target >= 0; target -= step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSeekIsIdempotent(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(2468);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 20_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      final long first = peek(iterator);
+      iterator.seek(target);
+      assertEquals(first, peek(iterator), "second seek(" + 
Integer.toUnsignedString(target) + ")");
+      assertEquals(expected(bitmap, target), first, "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testInterleavedNextAndAdvanceIfNeeded(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final PeekableIntIterator reference = bitmap.getIntIterator();
+    final Random random = new Random(4321);
+    long cursor = 0;
+    while (reference.hasNext() && iterator.hasNext()) {
+      if (random.nextBoolean()) {
+        assertEquals(reference.next(), iterator.next(), "next at " + cursor);
+      } else {
+        // advanceIfNeeded is forward only, so never ask for less than where 
the iterator already is.
+        cursor = Math.max(cursor, peek(iterator)) + random.nextInt(20000);
+        if (cursor >= UNSIGNED_LIMIT) {
+          break;
+        }
+        reference.advanceIfNeeded((int) cursor);
+        iterator.advanceIfNeeded((int) cursor);
+        assertEquals(peek(reference), peek(iterator), "advanceIfNeeded(" + 
cursor + ")");
+      }
+    }
+    assertEquals(reference.hasNext(), iterator.hasNext());
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomOperationsMatchReference(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final Random random = new Random(13579);
+    final long span = span(bitmap);
+    SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    PeekableIntIterator reference = bitmap.getIntIterator();
+
+    for (int op = 0; op < 100_000; op++) {
+      final int choice = random.nextInt(10);
+      final String what;
+      if (choice < 4) {
+        what = "next";
+        assertEquals(reference.hasNext(), iterator.hasNext(), "hasNext before 
" + what + " at op " + op);
+        if (reference.hasNext()) {
+          assertEquals(reference.next(), iterator.next(), "next at op " + op);
+        } else {
+          assertThrows(NoSuchElementException.class, iterator::next, "next at 
op " + op);
+        }
+      } else if (choice < 7) {
+        // Forward only, per the advanceIfNeeded contract. An exhausted 
iterator accepts anything, since it must
+        // stay exhausted either way.
+        final long from = Math.max(0, peek(iterator));
+        final long minval = from + random.nextLong(span - from);
+        what = "advanceIfNeeded(" + minval + ")";
+        reference.advanceIfNeeded((int) minval);
+        iterator.advanceIfNeeded((int) minval);
+      } else if (choice < 9) {
+        final long target = random.nextLong(span);
+        what = "seek(" + target + ")";
+        iterator.seek((int) target);
+        reference = bitmap.getIntIterator();
+        reference.advanceIfNeeded((int) target);
+      } else {
+        what = "clone";
+        iterator = iterator.clone();
+        reference = reference.clone();
+      }
+      assertEquals(peek(reference), peek(iterator), what + " at op " + op);
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testCloneIsIndependent(final String name, final 
ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11915)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    long n = 0;
+    while (reference.hasNext()) {
+      assertTrue(iterator.hasNext(), "ran out early at " + n);
+      assertEquals(reference.next(), iterator.next(), "value " + n);
+      n++;
+    }
+    assertFalse(iterator.hasNext(), "extra values after " + n);
+    assertThrows(NoSuchElementException.class, iterator::next);
+    assertThrows(NoSuchElementException.class, iterator::peekNext);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testPeekNextDoesNotAdvance(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    while (reference.hasNext()) {
+      final int value = reference.next();
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.next());
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomSeeksInBothDirections(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(5678);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 200_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      assertEquals(expected(bitmap, target), peek(iterator), "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAscendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = 0; target < span; target += step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testDescendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = span - 1; target >= 0; target -= step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSeekIsIdempotent(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(2468);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 20_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      final long first = peek(iterator);
+      iterator.seek(target);
+      assertEquals(first, peek(iterator), "second seek(" + 
Integer.toUnsignedString(target) + ")");
+      assertEquals(expected(bitmap, target), first, "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testInterleavedNextAndAdvanceIfNeeded(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final PeekableIntIterator reference = bitmap.getIntIterator();
+    final Random random = new Random(4321);
+    long cursor = 0;
+    while (reference.hasNext() && iterator.hasNext()) {
+      if (random.nextBoolean()) {
+        assertEquals(reference.next(), iterator.next(), "next at " + cursor);
+      } else {
+        // advanceIfNeeded is forward only, so never ask for less than where 
the iterator already is.
+        cursor = Math.max(cursor, peek(iterator)) + random.nextInt(20000);
+        if (cursor >= UNSIGNED_LIMIT) {
+          break;
+        }
+        reference.advanceIfNeeded((int) cursor);
+        iterator.advanceIfNeeded((int) cursor);
+        assertEquals(peek(reference), peek(iterator), "advanceIfNeeded(" + 
cursor + ")");
+      }
+    }
+    assertEquals(reference.hasNext(), iterator.hasNext());
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomOperationsMatchReference(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final Random random = new Random(13579);
+    final long span = span(bitmap);
+    SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    PeekableIntIterator reference = bitmap.getIntIterator();
+
+    for (int op = 0; op < 100_000; op++) {
+      final int choice = random.nextInt(10);
+      final String what;
+      if (choice < 4) {
+        what = "next";
+        assertEquals(reference.hasNext(), iterator.hasNext(), "hasNext before 
" + what + " at op " + op);
+        if (reference.hasNext()) {
+          assertEquals(reference.next(), iterator.next(), "next at op " + op);
+        } else {
+          assertThrows(NoSuchElementException.class, iterator::next, "next at 
op " + op);
+        }
+      } else if (choice < 7) {
+        // Forward only, per the advanceIfNeeded contract. An exhausted 
iterator accepts anything, since it must
+        // stay exhausted either way.
+        final long from = Math.max(0, peek(iterator));
+        final long minval = from + random.nextLong(span - from);
+        what = "advanceIfNeeded(" + minval + ")";
+        reference.advanceIfNeeded((int) minval);
+        iterator.advanceIfNeeded((int) minval);
+      } else if (choice < 9) {
+        final long target = random.nextLong(span);
+        what = "seek(" + target + ")";
+        iterator.seek((int) target);
+        reference = bitmap.getIntIterator();
+        reference.advanceIfNeeded((int) target);
+      } else {
+        what = "clone";
+        iterator = iterator.clone();
+        reference = reference.clone();
+      }
+      assertEquals(peek(reference), peek(iterator), what + " at op " + op);
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testCloneIsIndependent(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator original = new 
SeekableRoaringIntIterator(bitmap);
+    for (int i = 0; i < 100 && original.hasNext(); i++) {
+      original.next();
+    }
+    final SeekableRoaringIntIterator copy = original.clone();
+    assertEquals(original.hasNext(), copy.hasNext());
+    while (original.hasNext()) {
+      assertTrue(copy.hasNext());
+      assertEquals(original.next(), copy.next());
+    }
+    assertFalse(copy.hasNext());
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testCloneDoesNotShareCursor(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator original = new 
SeekableRoaringIntIterator(bitmap);
+    final long first = peek(original);
+
+    // Draining the copy leaves the original untouched.
+    final SeekableRoaringIntIterator copy = original.clone();
+    while (copy.hasNext()) {
+      copy.next();
+    }
+    assertFalse(copy.hasNext());
+    assertEquals(first, peek(original));
+
+    // And seeking the original leaves an earlier copy untouched.
+    final SeekableRoaringIntIterator pinned = original.clone();
+    final int last = bitmap.last();
+    original.seek(last);
+    assertEquals(Integer.toUnsignedLong(last), peek(original));
+    assertEquals(first, peek(pinned));
+
+    // A clone of an exhausted iterator is exhausted, and can be brought back 
on its own.
+    original.next();
+    assertFalse(original.hasNext());
+    final SeekableRoaringIntIterator exhausted = original.clone();
+    assertFalse(exhausted.hasNext());
+    assertThrows(NoSuchElementException.class, exhausted::next);
+    exhausted.seek(0);
+    assertEquals(first, peek(exhausted));
+    assertFalse(original.hasNext());
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAdvanceIfNeededDoesNotMoveBackwards(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(97531);
+
+    for (int trial = 0; trial < 20_000 && iterator.hasNext(); trial++) {
+      final long position = peek(iterator);
+      iterator.advanceIfNeeded((int) random.nextLong(position + 1));
+      assertEquals(position, peek(iterator), "backwards advanceIfNeeded from " 
+ position);
+      iterator.next();
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAdvanceIfNeededOnExhaustedIteratorIsANoOp(final String name, 
final ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11912)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    long n = 0;
+    while (reference.hasNext()) {
+      assertTrue(iterator.hasNext(), "ran out early at " + n);
+      assertEquals(reference.next(), iterator.next(), "value " + n);
+      n++;
+    }
+    assertFalse(iterator.hasNext(), "extra values after " + n);
+    assertThrows(NoSuchElementException.class, iterator::next);
+    assertThrows(NoSuchElementException.class, iterator::peekNext);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testPeekNextDoesNotAdvance(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    while (reference.hasNext()) {
+      final int value = reference.next();
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.next());
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomSeeksInBothDirections(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(5678);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 200_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      assertEquals(expected(bitmap, target), peek(iterator), "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAscendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = 0; target < span; target += step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testDescendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = span - 1; target >= 0; target -= step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSeekIsIdempotent(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(2468);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 20_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      final long first = peek(iterator);
+      iterator.seek(target);
+      assertEquals(first, peek(iterator), "second seek(" + 
Integer.toUnsignedString(target) + ")");
+      assertEquals(expected(bitmap, target), first, "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testInterleavedNextAndAdvanceIfNeeded(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final PeekableIntIterator reference = bitmap.getIntIterator();
+    final Random random = new Random(4321);
+    long cursor = 0;
+    while (reference.hasNext() && iterator.hasNext()) {
+      if (random.nextBoolean()) {
+        assertEquals(reference.next(), iterator.next(), "next at " + cursor);
+      } else {
+        // advanceIfNeeded is forward only, so never ask for less than where 
the iterator already is.
+        cursor = Math.max(cursor, peek(iterator)) + random.nextInt(20000);
+        if (cursor >= UNSIGNED_LIMIT) {
+          break;
+        }
+        reference.advanceIfNeeded((int) cursor);
+        iterator.advanceIfNeeded((int) cursor);
+        assertEquals(peek(reference), peek(iterator), "advanceIfNeeded(" + 
cursor + ")");
+      }
+    }
+    assertEquals(reference.hasNext(), iterator.hasNext());
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomOperationsMatchReference(final String name, final 
ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11916)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    long n = 0;
+    while (reference.hasNext()) {
+      assertTrue(iterator.hasNext(), "ran out early at " + n);
+      assertEquals(reference.next(), iterator.next(), "value " + n);
+      n++;
+    }
+    assertFalse(iterator.hasNext(), "extra values after " + n);
+    assertThrows(NoSuchElementException.class, iterator::next);
+    assertThrows(NoSuchElementException.class, iterator::peekNext);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testPeekNextDoesNotAdvance(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    while (reference.hasNext()) {
+      final int value = reference.next();
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.next());
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomSeeksInBothDirections(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(5678);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 200_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      assertEquals(expected(bitmap, target), peek(iterator), "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAscendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = 0; target < span; target += step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testDescendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11919)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    long n = 0;
+    while (reference.hasNext()) {
+      assertTrue(iterator.hasNext(), "ran out early at " + n);
+      assertEquals(reference.next(), iterator.next(), "value " + n);
+      n++;
+    }
+    assertFalse(iterator.hasNext(), "extra values after " + n);
+    assertThrows(NoSuchElementException.class, iterator::next);
+    assertThrows(NoSuchElementException.class, iterator::peekNext);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testPeekNextDoesNotAdvance(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    while (reference.hasNext()) {
+      final int value = reference.next();
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.next());
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomSeeksInBothDirections(final String name, final 
ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11921)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    long n = 0;
+    while (reference.hasNext()) {
+      assertTrue(iterator.hasNext(), "ran out early at " + n);
+      assertEquals(reference.next(), iterator.next(), "value " + n);
+      n++;
+    }
+    assertFalse(iterator.hasNext(), "extra values after " + n);
+    assertThrows(NoSuchElementException.class, iterator::next);
+    assertThrows(NoSuchElementException.class, iterator::peekNext);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testPeekNextDoesNotAdvance(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    while (reference.hasNext()) {
+      final int value = reference.next();
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.next());
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomSeeksInBothDirections(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(5678);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 200_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      assertEquals(expected(bitmap, target), peek(iterator), "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAscendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = 0; target < span; target += step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testDescendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = span - 1; target >= 0; target -= step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSeekIsIdempotent(final String name, final 
ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11918)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    long n = 0;
+    while (reference.hasNext()) {
+      assertTrue(iterator.hasNext(), "ran out early at " + n);
+      assertEquals(reference.next(), iterator.next(), "value " + n);
+      n++;
+    }
+    assertFalse(iterator.hasNext(), "extra values after " + n);
+    assertThrows(NoSuchElementException.class, iterator::next);
+    assertThrows(NoSuchElementException.class, iterator::peekNext);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testPeekNextDoesNotAdvance(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    while (reference.hasNext()) {
+      final int value = reference.next();
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.next());
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomSeeksInBothDirections(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(5678);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 200_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      assertEquals(expected(bitmap, target), peek(iterator), "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAscendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = 0; target < span; target += step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testDescendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = span - 1; target >= 0; target -= step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSeekIsIdempotent(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(2468);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 20_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      final long first = peek(iterator);
+      iterator.seek(target);
+      assertEquals(first, peek(iterator), "second seek(" + 
Integer.toUnsignedString(target) + ")");
+      assertEquals(expected(bitmap, target), first, "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testInterleavedNextAndAdvanceIfNeeded(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final PeekableIntIterator reference = bitmap.getIntIterator();
+    final Random random = new Random(4321);
+    long cursor = 0;
+    while (reference.hasNext() && iterator.hasNext()) {
+      if (random.nextBoolean()) {
+        assertEquals(reference.next(), iterator.next(), "next at " + cursor);
+      } else {
+        // advanceIfNeeded is forward only, so never ask for less than where 
the iterator already is.
+        cursor = Math.max(cursor, peek(iterator)) + random.nextInt(20000);
+        if (cursor >= UNSIGNED_LIMIT) {
+          break;
+        }
+        reference.advanceIfNeeded((int) cursor);
+        iterator.advanceIfNeeded((int) cursor);
+        assertEquals(peek(reference), peek(iterator), "advanceIfNeeded(" + 
cursor + ")");
+      }
+    }
+    assertEquals(reference.hasNext(), iterator.hasNext());
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomOperationsMatchReference(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final Random random = new Random(13579);
+    final long span = span(bitmap);
+    SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    PeekableIntIterator reference = bitmap.getIntIterator();
+
+    for (int op = 0; op < 100_000; op++) {
+      final int choice = random.nextInt(10);
+      final String what;
+      if (choice < 4) {
+        what = "next";
+        assertEquals(reference.hasNext(), iterator.hasNext(), "hasNext before 
" + what + " at op " + op);
+        if (reference.hasNext()) {
+          assertEquals(reference.next(), iterator.next(), "next at op " + op);
+        } else {
+          assertThrows(NoSuchElementException.class, iterator::next, "next at 
op " + op);
+        }
+      } else if (choice < 7) {
+        // Forward only, per the advanceIfNeeded contract. An exhausted 
iterator accepts anything, since it must
+        // stay exhausted either way.
+        final long from = Math.max(0, peek(iterator));
+        final long minval = from + random.nextLong(span - from);
+        what = "advanceIfNeeded(" + minval + ")";
+        reference.advanceIfNeeded((int) minval);
+        iterator.advanceIfNeeded((int) minval);
+      } else if (choice < 9) {
+        final long target = random.nextLong(span);
+        what = "seek(" + target + ")";
+        iterator.seek((int) target);
+        reference = bitmap.getIntIterator();
+        reference.advanceIfNeeded((int) target);
+      } else {
+        what = "clone";
+        iterator = iterator.clone();
+        reference = reference.clone();
+      }
+      assertEquals(peek(reference), peek(iterator), what + " at op " + op);
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testCloneIsIndependent(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator original = new 
SeekableRoaringIntIterator(bitmap);
+    for (int i = 0; i < 100 && original.hasNext(); i++) {
+      original.next();
+    }
+    final SeekableRoaringIntIterator copy = original.clone();
+    assertEquals(original.hasNext(), copy.hasNext());
+    while (original.hasNext()) {
+      assertTrue(copy.hasNext());
+      assertEquals(original.next(), copy.next());
+    }
+    assertFalse(copy.hasNext());
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testCloneDoesNotShareCursor(final String name, final 
ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11914)



##########
processing/src/test/java/org/apache/druid/collections/bitmap/SeekableRoaringIntIteratorTest.java:
##########
@@ -0,0 +1,597 @@
+/*
+ * 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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.collections.bitmap;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.roaringbitmap.IntIterator;
+import org.roaringbitmap.PeekableIntIterator;
+import org.roaringbitmap.buffer.DruidRoaringBufferAccess;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MappeableArrayContainer;
+import org.roaringbitmap.buffer.MappeableBitmapContainer;
+import org.roaringbitmap.buffer.MappeableRunContainer;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.PointableRoaringArray;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Checks {@link SeekableRoaringIntIterator} against {@link 
ImmutableRoaringBitmap#getIntIterator()}.
+ *
+ * <p>Bitmap values are unsigned, so this test carries them around as longs in 
{@code [0, 0xFFFFFFFF]} and uses
+ * {@link #NONE} for "the iterator is exhausted".
+ */
+public class SeekableRoaringIntIteratorTest
+{
+  /**
+   * Stands in for "no value", since every int is a legal bitmap value.
+   */
+  private static final long NONE = -1L;
+  private static final long UNSIGNED_LIMIT = 1L << 32;
+  private static final long RANDOM_SEED = 1234;
+
+  public static Stream<Arguments> bitmaps()
+  {
+    final List<Arguments> cases = new ArrayList<>();
+    final Random random = new Random(RANDOM_SEED);
+
+    // Sparse bitmaps. Should end up as array containers.
+    final MutableRoaringBitmap sparse = new MutableRoaringBitmap();
+    for (int i = 0; i < 3_000_000; i += 1 + random.nextInt(4000)) {
+      sparse.add(i);
+    }
+    cases.add(Arguments.of("sparse", toBufferBackedBitmap(sparse)));
+
+    // Dense bitmaps. Should end up as bitmap containers.
+    final MutableRoaringBitmap dense = new MutableRoaringBitmap();
+    for (int i = 0; i < 1_000_000; i++) {
+      if (random.nextDouble() < 0.6) {
+        dense.add(i);
+      }
+    }
+    cases.add(Arguments.of("dense", toBufferBackedBitmap(dense)));
+
+    // Runs, with whole containers missing in between. Should end up as run 
containers.
+    final MutableRoaringBitmap runs = new MutableRoaringBitmap();
+    runs.add(200_000L, 400_000L);
+    runs.add(2_000_000L, 2_300_000L);
+    runs.add(9_000_000L, 9_010_000L);
+    cases.add(Arguments.of("runs", toBufferBackedBitmap(runs)));
+
+    // A single value far from the origin.
+    final MutableRoaringBitmap lonely = new MutableRoaringBitmap();
+    lonely.add(15_000_000);
+    cases.add(Arguments.of("lonely", toBufferBackedBitmap(lonely)));
+
+    // Backed by a MutableRoaringArray rather than ByteBuffer, which is what
+    // WrappedRoaringBitmap.toImmutableBitmap() hands out.
+    final MutableRoaringBitmap heap = new MutableRoaringBitmap();
+    heap.add(50L, 70L);
+    heap.add(300_000L, 305_000L);
+    heap.add(5_000_000L, 5_000_100L);
+    heap.runOptimize();
+    cases.add(Arguments.of("heap", heap.toImmutableRoaringBitmap()));
+
+    // All three kinds of distributions in one bitmap.
+    final MutableRoaringBitmap mixed = new MutableRoaringBitmap();
+    for (int i = 0; i < 100; i++) {
+      mixed.add(i * 37);
+    }
+    for (int i = 0; i < 65536; i++) {
+      if (random.nextDouble() < 0.5) {
+        mixed.add(65536 + i);
+      }
+    }
+    mixed.add(2 * 65536L, 3 * 65536L);
+    mixed.add(4 * 65536 + 11);
+    mixed.add(4 * 65536 + 65535);
+    cases.add(Arguments.of("mixed", toBufferBackedBitmap(mixed)));
+
+    // Consecutive container keys.
+    final MutableRoaringBitmap adjacent = new MutableRoaringBitmap();
+    for (int key = 0; key < 10; key++) {
+      for (int i = 0; i < 20; i++) {
+        adjacent.add(key * 65536 + i * 3000);
+      }
+    }
+    cases.add(Arguments.of("adjacent", toBufferBackedBitmap(adjacent)));
+
+    // The first and last value of several containers, so seeks land exactly 
on container edges.
+    final MutableRoaringBitmap boundaries = new MutableRoaringBitmap();
+    for (int key = 0; key < 5; key++) {
+      boundaries.add(key * 65536);
+      boundaries.add(key * 65536 + 65535);
+    }
+    cases.add(Arguments.of("boundaries", toBufferBackedBitmap(boundaries)));
+
+    // Values above Integer.MAX_VALUE, where the container key has its high 
bit set and hs is negative.
+    final MutableRoaringBitmap high = new MutableRoaringBitmap();
+    high.add(1);
+    high.add(0x7FFFFFFF);
+    high.add(0x80000000);
+    high.add(0x80000001);
+    high.add(0xC0000000L, 0xC0010000L);
+    high.add(0xFFFF0000L, 0xFFFF0010L);
+    high.add(-1); // 0xFFFFFFFF, the largest unsigned value
+    cases.add(Arguments.of("high", toBufferBackedBitmap(high)));
+
+    return cases.stream();
+  }
+
+  @Test
+  public void testFixturesCoverEveryContainerKind()
+  {
+    // Verify we really do have all three kinds of containers in the test 
bitmaps.
+    final Set<String> kinds = new HashSet<>();
+    bitmaps().forEach(args -> {
+      final ImmutableRoaringBitmap bitmap = (ImmutableRoaringBitmap) 
args.get()[1];
+      final PointableRoaringArray containers = 
DruidRoaringBufferAccess.highLowContainer(bitmap);
+      for (int i = 0; i < containers.size(); i++) {
+        
kinds.add(containers.getContainerAtIndex(i).getClass().getSimpleName());
+      }
+    });
+
+    assertTrue(kinds.contains(MappeableArrayContainer.class.getSimpleName()), 
"array containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableBitmapContainer.class.getSimpleName()), 
"bitmap containers, got " + kinds);
+    assertTrue(kinds.contains(MappeableRunContainer.class.getSimpleName()), 
"run containers, got " + kinds);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSequentialIteration(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    long n = 0;
+    while (reference.hasNext()) {
+      assertTrue(iterator.hasNext(), "ran out early at " + n);
+      assertEquals(reference.next(), iterator.next(), "value " + n);
+      n++;
+    }
+    assertFalse(iterator.hasNext(), "extra values after " + n);
+    assertThrows(NoSuchElementException.class, iterator::next);
+    assertThrows(NoSuchElementException.class, iterator::peekNext);
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testPeekNextDoesNotAdvance(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final IntIterator reference = bitmap.getIntIterator();
+    while (reference.hasNext()) {
+      final int value = reference.next();
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.peekNext());
+      assertEquals(value, iterator.next());
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomSeeksInBothDirections(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(5678);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 200_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      assertEquals(expected(bitmap, target), peek(iterator), "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAscendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = 0; target < span; target += step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testDescendingSeeks(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final long span = span(bitmap);
+    final long step = Math.max(1, span / 20_000);
+    for (long target = span - 1; target >= 0; target -= step) {
+      iterator.seek((int) target);
+      assertEquals(expected(bitmap, (int) target), peek(iterator), "seek(" + 
target + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testSeekIsIdempotent(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final Random random = new Random(2468);
+    final long span = span(bitmap);
+    for (int trial = 0; trial < 20_000; trial++) {
+      final int target = (int) random.nextLong(span);
+      iterator.seek(target);
+      final long first = peek(iterator);
+      iterator.seek(target);
+      assertEquals(first, peek(iterator), "second seek(" + 
Integer.toUnsignedString(target) + ")");
+      assertEquals(expected(bitmap, target), first, "seek(" + 
Integer.toUnsignedString(target) + ")");
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testInterleavedNextAndAdvanceIfNeeded(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    final PeekableIntIterator reference = bitmap.getIntIterator();
+    final Random random = new Random(4321);
+    long cursor = 0;
+    while (reference.hasNext() && iterator.hasNext()) {
+      if (random.nextBoolean()) {
+        assertEquals(reference.next(), iterator.next(), "next at " + cursor);
+      } else {
+        // advanceIfNeeded is forward only, so never ask for less than where 
the iterator already is.
+        cursor = Math.max(cursor, peek(iterator)) + random.nextInt(20000);
+        if (cursor >= UNSIGNED_LIMIT) {
+          break;
+        }
+        reference.advanceIfNeeded((int) cursor);
+        iterator.advanceIfNeeded((int) cursor);
+        assertEquals(peek(reference), peek(iterator), "advanceIfNeeded(" + 
cursor + ")");
+      }
+    }
+    assertEquals(reference.hasNext(), iterator.hasNext());
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testRandomOperationsMatchReference(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final Random random = new Random(13579);
+    final long span = span(bitmap);
+    SeekableRoaringIntIterator iterator = new 
SeekableRoaringIntIterator(bitmap);
+    PeekableIntIterator reference = bitmap.getIntIterator();
+
+    for (int op = 0; op < 100_000; op++) {
+      final int choice = random.nextInt(10);
+      final String what;
+      if (choice < 4) {
+        what = "next";
+        assertEquals(reference.hasNext(), iterator.hasNext(), "hasNext before 
" + what + " at op " + op);
+        if (reference.hasNext()) {
+          assertEquals(reference.next(), iterator.next(), "next at op " + op);
+        } else {
+          assertThrows(NoSuchElementException.class, iterator::next, "next at 
op " + op);
+        }
+      } else if (choice < 7) {
+        // Forward only, per the advanceIfNeeded contract. An exhausted 
iterator accepts anything, since it must
+        // stay exhausted either way.
+        final long from = Math.max(0, peek(iterator));
+        final long minval = from + random.nextLong(span - from);
+        what = "advanceIfNeeded(" + minval + ")";
+        reference.advanceIfNeeded((int) minval);
+        iterator.advanceIfNeeded((int) minval);
+      } else if (choice < 9) {
+        final long target = random.nextLong(span);
+        what = "seek(" + target + ")";
+        iterator.seek((int) target);
+        reference = bitmap.getIntIterator();
+        reference.advanceIfNeeded((int) target);
+      } else {
+        what = "clone";
+        iterator = iterator.clone();
+        reference = reference.clone();
+      }
+      assertEquals(peek(reference), peek(iterator), what + " at op " + op);
+    }
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testCloneIsIndependent(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator original = new 
SeekableRoaringIntIterator(bitmap);
+    for (int i = 0; i < 100 && original.hasNext(); i++) {
+      original.next();
+    }
+    final SeekableRoaringIntIterator copy = original.clone();
+    assertEquals(original.hasNext(), copy.hasNext());
+    while (original.hasNext()) {
+      assertTrue(copy.hasNext());
+      assertEquals(original.next(), copy.next());
+    }
+    assertFalse(copy.hasNext());
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testCloneDoesNotShareCursor(final String name, final 
ImmutableRoaringBitmap bitmap)
+  {
+    final SeekableRoaringIntIterator original = new 
SeekableRoaringIntIterator(bitmap);
+    final long first = peek(original);
+
+    // Draining the copy leaves the original untouched.
+    final SeekableRoaringIntIterator copy = original.clone();
+    while (copy.hasNext()) {
+      copy.next();
+    }
+    assertFalse(copy.hasNext());
+    assertEquals(first, peek(original));
+
+    // And seeking the original leaves an earlier copy untouched.
+    final SeekableRoaringIntIterator pinned = original.clone();
+    final int last = bitmap.last();
+    original.seek(last);
+    assertEquals(Integer.toUnsignedLong(last), peek(original));
+    assertEquals(first, peek(pinned));
+
+    // A clone of an exhausted iterator is exhausted, and can be brought back 
on its own.
+    original.next();
+    assertFalse(original.hasNext());
+    final SeekableRoaringIntIterator exhausted = original.clone();
+    assertFalse(exhausted.hasNext());
+    assertThrows(NoSuchElementException.class, exhausted::next);
+    exhausted.seek(0);
+    assertEquals(first, peek(exhausted));
+    assertFalse(original.hasNext());
+  }
+
+  @ParameterizedTest(name = "{0}")
+  @MethodSource("bitmaps")
+  public void testAdvanceIfNeededDoesNotMoveBackwards(final String name, final 
ImmutableRoaringBitmap bitmap)

Review Comment:
   ## CodeQL / Useless parameter
   
   The parameter 'name' is never used.
   
   [Show more 
details](https://github.com/apache/druid/security/code-scanning/11913)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to