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

gortiz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 36be41ba283 Add unit tests for RangeIndexHandler version-change 
detection (#18958)
36be41ba283 is described below

commit 36be41ba283b4ea4827ee973f6d76c8e0a7d25f3
Author: Akanksha kedia <[email protected]>
AuthorDate: Tue Jul 14 19:34:39 2026 +0530

    Add unit tests for RangeIndexHandler version-change detection (#18958)
---
 .../invertedindex/RangeIndexHandlerTest.java       | 237 +++++++++++++++++++++
 1 file changed, 237 insertions(+)

diff --git 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/invertedindex/RangeIndexHandlerTest.java
 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/invertedindex/RangeIndexHandlerTest.java
new file mode 100644
index 00000000000..71a3a4a5a9c
--- /dev/null
+++ 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/invertedindex/RangeIndexHandlerTest.java
@@ -0,0 +1,237 @@
+/**
+ * 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.pinot.segment.local.segment.index.loader.invertedindex;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import org.apache.pinot.segment.spi.ColumnMetadata;
+import org.apache.pinot.segment.spi.index.FieldIndexConfigs;
+import org.apache.pinot.segment.spi.index.RangeIndexConfig;
+import org.apache.pinot.segment.spi.index.StandardIndexes;
+import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl;
+import org.apache.pinot.segment.spi.memory.PinotDataBuffer;
+import org.apache.pinot.segment.spi.store.SegmentDirectory;
+import org.apache.pinot.spi.config.table.TableConfig;
+import org.apache.pinot.spi.data.Schema;
+import org.testng.annotations.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+
+/**
+ * Unit tests for {@link RangeIndexHandler}.
+ *
+ * <p>Covers:
+ * <ul>
+ *   <li>Version change detection (v1 on disk, v2 configured → rebuild 
required)</li>
+ *   <li>No rebuild when on-disk version matches configured version</li>
+ *   <li>Index removal when column is no longer in config</li>
+ *   <li>New index creation when column is added to config (unsorted 
column)</li>
+ *   <li>Sorted columns are skipped even when range index is configured</li>
+ * </ul>
+ */
+public class RangeIndexHandlerTest {
+  private static final String COLUMN = "price";
+
+  // 
---------------------------------------------------------------------------
+  // Version change detection
+  // 
---------------------------------------------------------------------------
+
+  @Test
+  public void testNeedUpdateReturnsTrueWhenVersionChangesV1ToV2()
+      throws Exception {
+    // On-disk version is 1 (RangeIndexCreator), config requests version 2 
(BitSlicedRangeIndexCreator).
+    SegmentDirectory segmentDirectory = mockSegmentDirectory(COLUMN);
+    SegmentDirectory.Reader reader = mockReaderWithVersion(segmentDirectory, 
COLUMN, 1);
+
+    RangeIndexHandler handler = createHandler(segmentDirectory, new 
RangeIndexConfig(2));
+
+    assertTrue(handler.needUpdateIndices(reader),
+        "Rebuild expected when on-disk range index version differs from 
configured version");
+  }
+
+  @Test
+  public void testNeedUpdateReturnsTrueWhenVersionChangesV2ToV1()
+      throws Exception {
+    // On-disk version is 2, config requests version 1 — downgrade also 
requires rebuild.
+    SegmentDirectory segmentDirectory = mockSegmentDirectory(COLUMN);
+    SegmentDirectory.Reader reader = mockReaderWithVersion(segmentDirectory, 
COLUMN, 2);
+
+    RangeIndexHandler handler = createHandler(segmentDirectory, new 
RangeIndexConfig(1));
+
+    assertTrue(handler.needUpdateIndices(reader),
+        "Rebuild expected when configured version downgrades from v2 to v1");
+  }
+
+  @Test
+  public void testNeedUpdateReturnsFalseWhenVersionUnchanged()
+      throws Exception {
+    // On-disk version matches configured version — no rebuild needed.
+    SegmentDirectory segmentDirectory = mockSegmentDirectory(COLUMN);
+    SegmentDirectory.Reader reader = mockReaderWithVersion(segmentDirectory, 
COLUMN, 2);
+
+    RangeIndexHandler handler = createHandler(segmentDirectory, new 
RangeIndexConfig(2));
+
+    assertFalse(handler.needUpdateIndices(reader),
+        "No rebuild expected when on-disk version matches configured version");
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Index removal when column is dropped from config
+  // 
---------------------------------------------------------------------------
+
+  @Test
+  public void testNeedUpdateReturnsTrueWhenColumnRemovedFromConfig()
+      throws Exception {
+    // Column has a range index on disk but the new config has no range index 
for it.
+    SegmentDirectory segmentDirectory = mockSegmentDirectory(COLUMN);
+    SegmentDirectory.Reader reader = mock(SegmentDirectory.Reader.class);
+    when(reader.toSegmentDirectory()).thenReturn(segmentDirectory);
+
+    // Handler with no columns configured for range index.
+    RangeIndexHandler handler = new RangeIndexHandler(segmentDirectory, 
Map.of(),
+        mock(TableConfig.class), mock(Schema.class));
+
+    assertTrue(handler.needUpdateIndices(reader),
+        "Rebuild expected to remove index when column is dropped from range 
index config");
+  }
+
+  @Test
+  public void testUpdateIndicesRemovesIndexWhenColumnDroppedFromConfig()
+      throws Exception {
+    SegmentDirectory segmentDirectory = mockSegmentDirectory(COLUMN);
+    SegmentDirectory.Writer writer = mock(SegmentDirectory.Writer.class);
+    when(writer.toSegmentDirectory()).thenReturn(segmentDirectory);
+
+    RangeIndexHandler handler = new RangeIndexHandler(segmentDirectory, 
Map.of(),
+        mock(TableConfig.class), mock(Schema.class));
+    handler.updateIndices(writer);
+
+    verify(writer).removeIndex(COLUMN, StandardIndexes.range());
+  }
+
+  // 
---------------------------------------------------------------------------
+  // New column added to config
+  // 
---------------------------------------------------------------------------
+
+  @Test
+  public void testNeedUpdateReturnsTrueWhenNewUnsortedColumnAdded()
+      throws Exception {
+    // Column has no range index yet and is not sorted — needs creation.
+    SegmentMetadataImpl segmentMetadata = mock(SegmentMetadataImpl.class);
+    when(segmentMetadata.getName()).thenReturn("testSegment");
+    when(segmentMetadata.getTotalDocs()).thenReturn(1);
+    when(segmentMetadata.getAllColumns()).thenReturn(new 
TreeSet<>(Set.of(COLUMN)));
+
+    ColumnMetadata columnMetadata = mock(ColumnMetadata.class);
+    when(columnMetadata.isSorted()).thenReturn(false);
+    
when(segmentMetadata.getColumnMetadataFor(COLUMN)).thenReturn(columnMetadata);
+
+    SegmentDirectory segmentDirectory = mock(SegmentDirectory.class);
+    when(segmentDirectory.getSegmentMetadata()).thenReturn(segmentMetadata);
+    // No existing range index columns.
+    
when(segmentDirectory.getColumnsWithIndex(StandardIndexes.range())).thenReturn(Set.of());
+
+    SegmentDirectory.Reader reader = mock(SegmentDirectory.Reader.class);
+    when(reader.toSegmentDirectory()).thenReturn(segmentDirectory);
+
+    RangeIndexHandler handler = createHandler(segmentDirectory, new 
RangeIndexConfig(2));
+
+    assertTrue(handler.needUpdateIndices(reader),
+        "New range index expected for unsorted column not yet indexed");
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Sorted columns are skipped
+  // 
---------------------------------------------------------------------------
+
+  @Test
+  public void testNeedUpdateReturnsFalseWhenColumnIsSorted()
+      throws Exception {
+    // Sorted columns use a different query path — range index is not created 
for them.
+    SegmentMetadataImpl segmentMetadata = mock(SegmentMetadataImpl.class);
+    when(segmentMetadata.getName()).thenReturn("testSegment");
+    when(segmentMetadata.getTotalDocs()).thenReturn(1);
+    when(segmentMetadata.getAllColumns()).thenReturn(new 
TreeSet<>(Set.of(COLUMN)));
+
+    ColumnMetadata columnMetadata = mock(ColumnMetadata.class);
+    when(columnMetadata.isSorted()).thenReturn(true);
+    
when(segmentMetadata.getColumnMetadataFor(COLUMN)).thenReturn(columnMetadata);
+
+    SegmentDirectory segmentDirectory = mock(SegmentDirectory.class);
+    when(segmentDirectory.getSegmentMetadata()).thenReturn(segmentMetadata);
+    
when(segmentDirectory.getColumnsWithIndex(StandardIndexes.range())).thenReturn(Set.of());
+
+    SegmentDirectory.Reader reader = mock(SegmentDirectory.Reader.class);
+    when(reader.toSegmentDirectory()).thenReturn(segmentDirectory);
+
+    RangeIndexHandler handler = createHandler(segmentDirectory, new 
RangeIndexConfig(2));
+
+    assertFalse(handler.needUpdateIndices(reader),
+        "No range index creation expected for sorted columns");
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Helpers
+  // 
---------------------------------------------------------------------------
+
+  private static RangeIndexHandler createHandler(SegmentDirectory 
segmentDirectory,
+      RangeIndexConfig rangeIndexConfig) {
+    FieldIndexConfigs fieldIndexConfigs =
+        new FieldIndexConfigs.Builder().add(StandardIndexes.range(), 
rangeIndexConfig).build();
+    return new RangeIndexHandler(segmentDirectory, Map.of(COLUMN, 
fieldIndexConfigs),
+        mock(TableConfig.class), mock(Schema.class));
+  }
+
+  /**
+   * Creates a {@link SegmentDirectory} mock where {@code column} already has 
a range index on disk.
+   */
+  private static SegmentDirectory mockSegmentDirectory(String column) {
+    SegmentMetadataImpl segmentMetadata = mock(SegmentMetadataImpl.class);
+    when(segmentMetadata.getName()).thenReturn("testSegment");
+    when(segmentMetadata.getTotalDocs()).thenReturn(1);
+    when(segmentMetadata.getAllColumns()).thenReturn(new 
TreeSet<>(Set.of(column)));
+
+    SegmentDirectory segmentDirectory = mock(SegmentDirectory.class);
+    when(segmentDirectory.getSegmentMetadata()).thenReturn(segmentMetadata);
+    
when(segmentDirectory.getColumnsWithIndex(StandardIndexes.range())).thenReturn(Set.of(column));
+    return segmentDirectory;
+  }
+
+  /**
+   * Creates a {@link SegmentDirectory.Reader} mock whose range index buffer 
for {@code column}
+   * reports the given on-disk {@code version} at offset 0.
+   */
+  private static SegmentDirectory.Reader 
mockReaderWithVersion(SegmentDirectory segmentDirectory,
+      String column, int version)
+      throws Exception {
+    PinotDataBuffer buffer = mock(PinotDataBuffer.class);
+    when(buffer.getInt(0)).thenReturn(version);
+
+    SegmentDirectory.Reader reader = mock(SegmentDirectory.Reader.class);
+    when(reader.toSegmentDirectory()).thenReturn(segmentDirectory);
+    when(reader.getIndexFor(column, 
StandardIndexes.range())).thenReturn(buffer);
+    return reader;
+  }
+}


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

Reply via email to