From 367b5f81a28cb5c49ea739c34ea814e6f181c4ab Mon Sep 17 00:00:00 2001 From: Mayank Aggarwal Date: Tue, 28 Jul 2026 10:46:08 +0530 Subject: [PATCH 1/7] Route numeric and date queries through DV branch on pluggable-dataformat indices Composite (pluggable-dataformat) indices write no BKD for numeric or date fields on the Lucene secondary. IndexOrDocValuesQuery's cost-based dispatch picks the empty point side (cost=0) and returns zero hits for range, term, terms, and bitmap queries. Introduce QueryShardContext.isPluggableDataFormatEnabled() as a null-safe gate. NumberFieldType and DateFieldType consult this gate: on Mustang-backed indices they treat the field as not-searchable so range/term/terms/bitmap queries build the pure doc-values branch that resolves against codec-served DV columns. Non-pluggable-dataformat indices retain isSearchable() behavior unchanged, preserving the BKD fast path. Extends NumberFieldType.bitmapQuery to take a QueryShardContext so it can consult the same gate. Updates the one caller in TermsQueryBuilder and the Mockito stub in TermQueryWithDocIdAndQueryTests. Adds unit tests in NumberFieldTypeTests, DateFieldTypeTests, and QueryShardContextTests covering both gate states. Signed-off-by: Mayank Aggarwal --- .../index/mapper/DateFieldMapper.java | 11 ++- .../index/mapper/NumberFieldMapper.java | 29 +++++-- .../index/query/QueryShardContext.java | 11 +++ .../index/query/TermsQueryBuilder.java | 2 +- .../index/mapper/DateFieldTypeTests.java | 44 ++++++++++ .../index/mapper/NumberFieldTypeTests.java | 83 ++++++++++++++++--- .../index/query/QueryShardContextTests.java | 77 +++++++++++++++++ .../TermQueryWithDocIdAndQueryTests.java | 2 +- 8 files changed, 240 insertions(+), 19 deletions(-) diff --git a/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java index 1448a362d92bf..ce27950b6a7e7 100644 --- a/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java @@ -538,8 +538,15 @@ public Query rangeQuery( (l, u, nowUsed) -> { Query dvQuery = hasDocValues() ? SortedNumericDocValuesField.newSlowRangeQuery(name(), l, u) : null; - // Not searchable. Must have doc values. - if (!isSearchable()) { + // On indices backed by a pluggable dataformat (composite primary + Lucene secondary) + // the Lucene secondary writes no BKD for date fields, so the point-side of + // IndexOrDocValuesQuery reports cost=0 and wins the cost race — returning zero hits. + // Force the pure doc-values branch so range/term queries resolve correctly against + // the codec-served DV column. + boolean effectiveSearchable = isSearchable() && !(context != null && context.isPluggableDataFormatEnabled()); + + // Not searchable (either declared, or gated off by the pluggable-dataformat check). Must have doc values. + if (!effectiveSearchable) { if (context.indexSortedOnField(name())) { dvQuery = new IndexSortSortedNumericDocValuesRangeQuery(name(), l, u, dvQuery); } diff --git a/server/src/main/java/org/opensearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/NumberFieldMapper.java index 5dff164b9532f..7ca8af63ec735 100644 --- a/server/src/main/java/org/opensearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/opensearch/index/mapper/NumberFieldMapper.java @@ -1986,10 +1986,29 @@ public NumericType numericType() { return type.numericType(); } + /** + * Returns whether this numeric field should be advertised as searchable to the + * per-type query factory. On indices backed by a pluggable dataformat (composite + * primary + Lucene secondary), the Lucene secondary writes no BKD for numeric + * fields, so the point-side of {@link org.apache.lucene.search.IndexOrDocValuesQuery} + * reports {@code cost=0} and wins the cost race — returning zero hits. Routing + * through the pure doc-values branch (by treating the field as not-searchable) + * avoids that trap and executes correctly against the codec-served DV column. + * + *

Non-pluggable-dataformat indices retain their normal {@link #isSearchable()} + * behavior so the BKD fast path is preserved. + */ + private boolean effectiveSearchable(QueryShardContext context) { + if (context != null && context.isPluggableDataFormatEnabled()) { + return false; + } + return isSearchable(); + } + @Override public Query termQuery(Object value, QueryShardContext context) { failIfNotIndexedAndNoDocValues(); - Query query = type.termQuery(name(), value, hasDocValues(), isSearchable()); + Query query = type.termQuery(name(), value, hasDocValues(), effectiveSearchable(context)); if (boost() != 1f) { query = new BoostQuery(query, boost()); } @@ -1999,16 +2018,16 @@ public Query termQuery(Object value, QueryShardContext context) { @Override public Query termsQuery(List values, QueryShardContext context) { failIfNotIndexedAndNoDocValues(); - Query query = type.termsQuery(name(), values, hasDocValues(), isSearchable()); + Query query = type.termsQuery(name(), values, hasDocValues(), effectiveSearchable(context)); if (boost() != 1f) { query = new BoostQuery(query, boost()); } return query; } - public Query bitmapQuery(BytesArray bitmap) { + public Query bitmapQuery(BytesArray bitmap, QueryShardContext context) { failIfNotIndexedAndNoDocValues(); - return type.bitmapQuery(name(), bitmap, isSearchable(), hasDocValues()); + return type.bitmapQuery(name(), bitmap, effectiveSearchable(context), hasDocValues()); } @Override @@ -2021,7 +2040,7 @@ public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower includeLower, includeUpper, hasDocValues(), - isSearchable(), + effectiveSearchable(context), context ); if (boost() != 1f) { diff --git a/server/src/main/java/org/opensearch/index/query/QueryShardContext.java b/server/src/main/java/org/opensearch/index/query/QueryShardContext.java index f2c278f04b021..e9a152c9bb36a 100644 --- a/server/src/main/java/org/opensearch/index/query/QueryShardContext.java +++ b/server/src/main/java/org/opensearch/index/query/QueryShardContext.java @@ -691,6 +691,17 @@ public IndexSettings getIndexSettings() { return indexSettings; } + /** + * Null-safe check for the pluggable-dataformat gate. Returns {@code true} only when this + * context has index-scoped settings and the setting is enabled. Callers use this to route + * query construction (e.g. numeric range/term/bitmap) through the pure doc-values branch + * on pluggable-dataformat indices, where the Lucene secondary writes no BKD for numeric + * fields. + */ + public boolean isPluggableDataFormatEnabled() { + return indexSettings != null && indexSettings.isPluggableDataFormatEnabled(); + } + /** * Return the MapperService. */ diff --git a/server/src/main/java/org/opensearch/index/query/TermsQueryBuilder.java b/server/src/main/java/org/opensearch/index/query/TermsQueryBuilder.java index 41e436f2ed59d..9cd06c280bcce 100644 --- a/server/src/main/java/org/opensearch/index/query/TermsQueryBuilder.java +++ b/server/src/main/java/org/opensearch/index/query/TermsQueryBuilder.java @@ -565,7 +565,7 @@ protected Query doToQuery(QueryShardContext context) throws IOException { && values.size() == 1 && values.get(0) instanceof BytesArray bytesArray && fieldType.unwrap() instanceof NumberFieldMapper.NumberFieldType numberFieldType) { - return numberFieldType.bitmapQuery(bytesArray); + return numberFieldType.bitmapQuery(bytesArray, context); } return fieldType.termsQuery(values, context); } diff --git a/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java index b436f8a8a8ecd..ba4afeb44a4cd 100644 --- a/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java @@ -654,4 +654,48 @@ public void testDateFieldTypeWithNulls() throws IOException { } IOUtils.close(reader, w, dir); } + + /** + * On a pluggable-dataformat index the mapper must skip the point-based query construction + * for date ranges and emit only the doc-values range. The Lucene secondary writes no BKD + * on such indices, so keeping the point side would let the cost-based dispatch inside + * {@link IndexOrDocValuesQuery} pick an empty {@code PointValues} and return zero hits. + */ + public void testRangeQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + DateFieldType ft = new DateFieldType("field"); + String date1 = "2015-10-12T14:10:55"; + String date2 = "2016-04-28T11:33:52"; + long instant1 = DateFormatters.from(DateFieldMapper.getDefaultDateTimeFormatter().parse(date1)).toInstant().toEpochMilli(); + long instant2 = DateFormatters.from(DateFieldMapper.getDefaultDateTimeFormatter().parse(date2)).toInstant().toEpochMilli() + 999; + Query expected = SortedNumericDocValuesField.newSlowRangeQuery("field", instant1, instant2); + Query actual = ft.rangeQuery(date1, date2, true, true, null, null, null, mockPluggableDataFormatContext()); + assertEquals(expected, actual); + } + + /** + * Term queries on a date field route through the same lambda as range queries, so the same + * gate applies: on a pluggable-dataformat index the point path must not be built. + */ + public void testTermQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + DateFieldType ft = new DateFieldType("field"); + String date = "2015-10-12T14:10:55"; + long instant = DateFormatters.from(DateFieldMapper.getDefaultDateTimeFormatter().parse(date)).toInstant().toEpochMilli(); + long lower = instant; + long upper = DateFormatters.from(DateFieldMapper.getDefaultDateTimeFormatter().parse(date)).toInstant().toEpochMilli() + 999; + Query expected = SortedNumericDocValuesField.newSlowRangeQuery("field", lower, upper); + Query actual = ft.termQuery(date, mockPluggableDataFormatContext()); + assertEquals(expected, actual); + } + + /** + * A {@link QueryShardContext} mock that reports the pluggable-dataformat gate as enabled. + * Simulates a Mustang-backed index without needing the real feature-flag / IndexSettings + * plumbing — the mapper code only cares about the boolean returned here. + */ + private static QueryShardContext mockPluggableDataFormatContext() { + QueryShardContext ctx = org.mockito.Mockito.mock(QueryShardContext.class); + org.mockito.Mockito.when(ctx.isPluggableDataFormatEnabled()).thenReturn(true); + org.mockito.Mockito.when(ctx.indexSortedOnField(org.mockito.ArgumentMatchers.anyString())).thenReturn(false); + return ctx; + } } diff --git a/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java index ced21855038bf..5f89331bcd689 100644 --- a/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java @@ -1006,19 +1006,19 @@ public void testBitmapQuery() throws IOException { NumberFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER); assertEquals( new IndexOrDocValuesQuery(new BitmapIndexQuery("field", r), new BitmapDocValuesQuery("field", r)), - ft.bitmapQuery(bitmap) + ft.bitmapQuery(bitmap, null) ); ft = new NumberFieldType("field", NumberType.INTEGER, false, false, true, true, true, null, Collections.emptyMap()); - assertEquals(new BitmapDocValuesQuery("field", r), ft.bitmapQuery(bitmap)); + assertEquals(new BitmapDocValuesQuery("field", r), ft.bitmapQuery(bitmap, null)); ft = new NumberFieldType("field", NumberType.INTEGER, true, false, false, false, true, null, Collections.emptyMap()); - assertEquals(new BitmapIndexQuery("field", r), ft.bitmapQuery(bitmap)); + assertEquals(new BitmapIndexQuery("field", r), ft.bitmapQuery(bitmap, null)); Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, new IndexWriterConfig()); DirectoryReader reader = DirectoryReader.open(w); - assertEquals(new MatchNoDocsQuery(), ft.bitmapQuery(bitmap).rewrite(newSearcher(reader))); + assertEquals(new MatchNoDocsQuery(), ft.bitmapQuery(bitmap, null).rewrite(newSearcher(reader))); reader.close(); w.close(); dir.close(); @@ -1026,7 +1026,7 @@ public void testBitmapQuery() throws IOException { NumberType type = randomValueOtherThan(NumberType.INTEGER, () -> randomFrom(NumberType.values())); ft = new NumberFieldMapper.NumberFieldType("field", type); NumberFieldType finalFt = ft; - assertThrows(IllegalArgumentException.class, () -> finalFt.bitmapQuery(bitmap)); + assertThrows(IllegalArgumentException.class, () -> finalFt.bitmapQuery(bitmap, null)); } public void testBitmapQuery64() throws IOException { @@ -1045,20 +1045,20 @@ public void testBitmapQuery64() throws IOException { assertEquals( new IndexOrDocValuesQuery(new Bitmap64IndexQuery("field", r), new Bitmap64DocValuesQuery("field", r)), - ft.bitmapQuery(bitmap) + ft.bitmapQuery(bitmap, null) ); ft = new NumberFieldType("field", NumberType.LONG, false, false, true, true, true, null, Collections.emptyMap()); - assertEquals(new Bitmap64DocValuesQuery("field", r), ft.bitmapQuery(bitmap)); + assertEquals(new Bitmap64DocValuesQuery("field", r), ft.bitmapQuery(bitmap, null)); ft = new NumberFieldType("field", NumberType.LONG, true, false, false, false, true, null, Collections.emptyMap()); - assertEquals(new Bitmap64IndexQuery("field", r), ft.bitmapQuery(bitmap)); + assertEquals(new Bitmap64IndexQuery("field", r), ft.bitmapQuery(bitmap, null)); Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, new IndexWriterConfig()); DirectoryReader reader = DirectoryReader.open(w); - assertEquals(new MatchNoDocsQuery(), ft.bitmapQuery(bitmap).rewrite(newSearcher(reader))); + assertEquals(new MatchNoDocsQuery(), ft.bitmapQuery(bitmap, null).rewrite(newSearcher(reader))); reader.close(); w.close(); @@ -1068,7 +1068,7 @@ public void testBitmapQuery64() throws IOException { ft = new NumberFieldMapper.NumberFieldType("field", type); NumberFieldType finalFt = ft; - assertThrows(IllegalArgumentException.class, () -> finalFt.bitmapQuery(bitmap)); + assertThrows(IllegalArgumentException.class, () -> finalFt.bitmapQuery(bitmap, null)); } public void testFetchUnsignedLongDocValues() throws IOException { @@ -1094,4 +1094,67 @@ public void testFetchUnsignedLongDocValues() throws IOException { } IOUtils.close(w, dir); } + + /** + * On a non-pluggable-dataformat index (default) a numeric range query is built as + * {@link ApproximateScoreQuery} wrapping an {@link IndexOrDocValuesQuery} — the BKD + * fast path is preserved. + */ + public void testRangeQueryUsesPointsWhenPluggableDataFormatDisabled() { + NumberFieldType ft = new NumberFieldType("field", NumberType.LONG); + Query expected = new ApproximateScoreQuery( + new IndexOrDocValuesQuery( + LongPoint.newRangeQuery("field", 1L, 10L), + SortedNumericDocValuesField.newSlowRangeQuery("field", 1L, 10L) + ), + new ApproximatePointRangeQuery("field", pack(1L).bytes, pack(10L).bytes, 1, ApproximatePointRangeQuery.LONG_FORMAT) + ); + assertEquals(expected, ft.rangeQuery(1L, 10L, true, true, null, null, null, MOCK_QSC)); + } + + /** + * On a pluggable-dataformat index the mapper must skip the point-based query construction + * and emit only the doc-values range. The Lucene secondary writes no BKD on such indices, + * so keeping the point side would let the cost-based dispatch inside + * {@link IndexOrDocValuesQuery} pick an empty {@code PointValues} and return zero hits. + */ + public void testRangeQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + NumberFieldType ft = new NumberFieldType("field", NumberType.LONG); + Query query = ft.rangeQuery(1L, 10L, true, true, null, null, null, mockPluggableDataFormatContext()); + Query expected = SortedNumericDocValuesField.newSlowRangeQuery("field", 1L, 10L); + assertEquals(expected, query); + } + + /** + * Same routing rule applies to numeric term queries: on a pluggable-dataformat index the + * DV-only exact-range form must be emitted, not the {@code IndexOrDocValuesQuery} that + * pairs a {@code LongPoint.newExactQuery} with a DV range. + */ + public void testTermQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + NumberFieldType ft = new NumberFieldType("field", NumberType.LONG); + Query query = ft.termQuery(42L, mockPluggableDataFormatContext()); + Query expected = SortedNumericDocValuesField.newSlowRangeQuery("field", 42L, 42L); + assertEquals(expected, query); + } + + /** + * Terms queries also route to the DV set-query form on a pluggable-dataformat index. + */ + public void testTermsQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + NumberFieldType ft = new NumberFieldType("field", NumberType.LONG); + Query query = ft.termsQuery(java.util.List.of(1L, 2L, 3L), mockPluggableDataFormatContext()); + Query expected = SortedNumericDocValuesField.newSlowSetQuery("field", 1L, 2L, 3L); + assertEquals(expected, query); + } + + /** + * A {@link QueryShardContext} mock that reports the pluggable-dataformat gate as enabled. + * Simulates a Mustang-backed index without needing the real feature-flag / IndexSettings + * plumbing — the mapper code only cares about the boolean returned here. + */ + private static QueryShardContext mockPluggableDataFormatContext() { + QueryShardContext ctx = org.mockito.Mockito.mock(QueryShardContext.class); + org.mockito.Mockito.when(ctx.isPluggableDataFormatEnabled()).thenReturn(true); + return ctx; + } } diff --git a/server/src/test/java/org/opensearch/index/query/QueryShardContextTests.java b/server/src/test/java/org/opensearch/index/query/QueryShardContextTests.java index 12677edc8efa7..a496f2d8a230d 100644 --- a/server/src/test/java/org/opensearch/index/query/QueryShardContextTests.java +++ b/server/src/test/java/org/opensearch/index/query/QueryShardContextTests.java @@ -51,6 +51,7 @@ import org.opensearch.common.TriFunction; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.BigArrays; +import org.opensearch.common.util.FeatureFlags; import org.opensearch.core.common.io.stream.NamedWriteableRegistry; import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.core.xcontent.NamedXContentRegistry; @@ -328,6 +329,53 @@ public void testSearchLookupShardId() { assertEquals(SHARD_ID, searchLookup.shardId()); } + /** + * When the index-level setting is on AND the pluggable-dataformat feature flag is on, + * the gate is on. Mapper-layer callers use this to route numeric/date queries down the + * doc-values branch. + */ + @LockFeatureFlag(FeatureFlags.PLUGGABLE_DATAFORMAT_EXPERIMENTAL_FLAG) + public void testIsPluggableDataFormatEnabledWhenIndexSettingAndFeatureFlagOn() { + Settings settings = Settings.builder() + .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT) + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1) + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1) + .put(IndexSettings.PLUGGABLE_DATAFORMAT_ENABLED_SETTING.getKey(), true) + .build(); + QueryShardContext context = queryShardContextWithIndexSettings(settings); + assertTrue(context.isPluggableDataFormatEnabled()); + } + + /** + * Default state: neither the index-level setting nor the feature flag is on. This is what + * every non-Mustang index sees — BKD fast path is preserved. + */ + public void testIsPluggableDataFormatEnabledWhenNeitherFlagIsOn() { + Settings settings = Settings.builder() + .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT) + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1) + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1) + .build(); + QueryShardContext context = queryShardContextWithIndexSettings(settings); + assertFalse(context.isPluggableDataFormatEnabled()); + } + + /** + * The index-level setting alone is not enough — the feature flag must also be on for the + * gate to activate. Verifies the two conditions are combined (AND, not OR). + */ + public void testIsPluggableDataFormatEnabledWhenOnlyIndexSettingIsOn() { + Settings settings = Settings.builder() + .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT) + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1) + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1) + .put(IndexSettings.PLUGGABLE_DATAFORMAT_ENABLED_SETTING.getKey(), true) + .build(); + // Feature flag intentionally NOT enabled → gate must remain off. + QueryShardContext context = queryShardContextWithIndexSettings(settings); + assertFalse(context.isPluggableDataFormatEnabled()); + } + public static QueryShardContext createQueryShardContext(String indexUuid, String clusterAlias) { return createQueryShardContext(indexUuid, clusterAlias, null); } @@ -479,4 +527,33 @@ public void collect(int doc) throws IOException { } } + /** + * Builds a minimal {@link QueryShardContext} for a hypothetical index with the given + * {@link Settings}, wiring in real {@link IndexSettings}. Used by the pluggable-dataformat + * gate tests where the value of {@link QueryShardContext#isPluggableDataFormatEnabled()} + * depends on both the index-level setting and the node-level feature flag. + */ + private static QueryShardContext queryShardContextWithIndexSettings(Settings settings) { + IndexMetadata indexMetadata = new IndexMetadata.Builder("index").settings(settings).build(); + IndexSettings indexSettings = new IndexSettings(indexMetadata, settings); + return new QueryShardContext( + 0, + indexSettings, + BigArrays.NON_RECYCLING_INSTANCE, + null, + null, + null, + null, + null, + NamedXContentRegistry.EMPTY, + new NamedWriteableRegistry(Collections.emptyList()), + null, + null, + () -> 0L, + null, + null, + () -> true, + null + ); + } } diff --git a/server/src/test/java/org/opensearch/index/query/TermQueryWithDocIdAndQueryTests.java b/server/src/test/java/org/opensearch/index/query/TermQueryWithDocIdAndQueryTests.java index 09b7ddd5dd31e..71a2d76c0acb3 100644 --- a/server/src/test/java/org/opensearch/index/query/TermQueryWithDocIdAndQueryTests.java +++ b/server/src/test/java/org/opensearch/index/query/TermQueryWithDocIdAndQueryTests.java @@ -86,7 +86,7 @@ public void testTermsQueryWithBitmapValueType() throws Exception { when(context.fieldMapper("student_id")).thenReturn(numberFieldType); when(numberFieldType.unwrap()).thenReturn(numberFieldType); Query bitmapQuery = mock(Query.class); - when(numberFieldType.bitmapQuery(any(BytesArray.class))).thenReturn(bitmapQuery); + when(numberFieldType.bitmapQuery(any(BytesArray.class), any(QueryShardContext.class))).thenReturn(bitmapQuery); Query result = builder.doToQuery(context); assertNotNull(result); From 9b3811813479f82742b7d2cc25a41f4781386930 Mon Sep 17 00:00:00 2001 From: Mayank Aggarwal Date: Thu, 30 Jul 2026 13:51:56 +0530 Subject: [PATCH 2/7] Use static Mockito imports in pluggable-dataformat gating tests Address PR feedback: replace fully-qualified org.mockito.Mockito.mock/when and org.mockito.ArgumentMatchers.anyString references with static imports in NumberFieldTypeTests and DateFieldTypeTests. Signed-off-by: Mayank Aggarwal --- .../org/opensearch/index/mapper/DateFieldTypeTests.java | 9 ++++++--- .../opensearch/index/mapper/NumberFieldTypeTests.java | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java index ba4afeb44a4cd..fa8995633b297 100644 --- a/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java @@ -92,6 +92,9 @@ import java.util.Locale; import static org.apache.lucene.document.LongPoint.pack; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class DateFieldTypeTests extends FieldTypeTestCase { @@ -693,9 +696,9 @@ public void testTermQueryUsesDocValuesWhenPluggableDataFormatEnabled() { * plumbing — the mapper code only cares about the boolean returned here. */ private static QueryShardContext mockPluggableDataFormatContext() { - QueryShardContext ctx = org.mockito.Mockito.mock(QueryShardContext.class); - org.mockito.Mockito.when(ctx.isPluggableDataFormatEnabled()).thenReturn(true); - org.mockito.Mockito.when(ctx.indexSortedOnField(org.mockito.ArgumentMatchers.anyString())).thenReturn(false); + QueryShardContext ctx = mock(QueryShardContext.class); + when(ctx.isPluggableDataFormatEnabled()).thenReturn(true); + when(ctx.indexSortedOnField(anyString())).thenReturn(false); return ctx; } } diff --git a/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java index 5f89331bcd689..066698f9462df 100644 --- a/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java @@ -104,6 +104,8 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.apache.lucene.document.LongPoint.pack; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class NumberFieldTypeTests extends FieldTypeTestCase { @@ -1153,8 +1155,8 @@ public void testTermsQueryUsesDocValuesWhenPluggableDataFormatEnabled() { * plumbing — the mapper code only cares about the boolean returned here. */ private static QueryShardContext mockPluggableDataFormatContext() { - QueryShardContext ctx = org.mockito.Mockito.mock(QueryShardContext.class); - org.mockito.Mockito.when(ctx.isPluggableDataFormatEnabled()).thenReturn(true); + QueryShardContext ctx = mock(QueryShardContext.class); + when(ctx.isPluggableDataFormatEnabled()).thenReturn(true); return ctx; } } From a21df7edae74bc660c77a7b4e0094bc22c94d865 Mon Sep 17 00:00:00 2001 From: Mayank Aggarwal Date: Thu, 30 Jul 2026 14:54:51 +0530 Subject: [PATCH 3/7] Drop redundant null check in DateFieldMapper effectiveSearchable Address PR feedback: QueryShardContext is always non-null on the query execution path, so the defensive null check is unnecessary noise. Signed-off-by: Mayank Aggarwal --- .../main/java/org/opensearch/index/mapper/DateFieldMapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java index ce27950b6a7e7..0eee8ba3cd2d8 100644 --- a/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java @@ -543,7 +543,7 @@ public Query rangeQuery( // IndexOrDocValuesQuery reports cost=0 and wins the cost race — returning zero hits. // Force the pure doc-values branch so range/term queries resolve correctly against // the codec-served DV column. - boolean effectiveSearchable = isSearchable() && !(context != null && context.isPluggableDataFormatEnabled()); + boolean effectiveSearchable = isSearchable() && !context.isPluggableDataFormatEnabled(); // Not searchable (either declared, or gated off by the pluggable-dataformat check). Must have doc values. if (!effectiveSearchable) { From d0e80608d0094e591483881b720c50099dbe3d42 Mon Sep 17 00:00:00 2001 From: Mayank Aggarwal Date: Fri, 31 Jul 2026 10:04:28 +0530 Subject: [PATCH 4/7] Extend pluggable-dataformat gating to IP, Boolean, and ScaledFloat mappers Move the isEffectiveSearchable(context) check onto MappedFieldType so any mapper can consult it, then extend the routing gate to IpFieldMapper, BooleanFieldMapper, and ScaledFloatFieldMapper. On pluggable-dataformat indices the Lucene secondary writes no BKD for these types, so their default composition of IndexOrDocValuesQuery(pointQuery, dvQuery) would let the point side win the cost race and return zero hits. NumberFieldMapper and DateFieldMapper migrated to the base method; their per-mapper effectiveSearchable helpers are removed. Tests updated to pass MOCK_QSC (non-pluggable context) where they previously passed null, since isEffectiveSearchable now requires a non-null context. Added positive gate tests per mapper asserting DV-only queries on pluggable. Signed-off-by: Mayank Aggarwal --- .../index/mapper/ScaledFloatFieldMapper.java | 6 +- .../mapper/ScaledFloatFieldTypeTests.java | 45 +++++++- .../index/mapper/BooleanFieldMapper.java | 3 +- .../index/mapper/DateFieldMapper.java | 10 +- .../index/mapper/IpFieldMapper.java | 12 +- .../index/mapper/MappedFieldType.java | 16 +++ .../index/mapper/NumberFieldMapper.java | 27 +---- .../index/mapper/BooleanFieldTypeTests.java | 65 +++++++---- .../index/mapper/IpFieldTypeTests.java | 107 ++++++++++++------ .../index/mapper/NumberFieldTypeTests.java | 44 +++---- 10 files changed, 214 insertions(+), 121 deletions(-) diff --git a/modules/mapper-extras/src/main/java/org/opensearch/index/mapper/ScaledFloatFieldMapper.java b/modules/mapper-extras/src/main/java/org/opensearch/index/mapper/ScaledFloatFieldMapper.java index 9329d08e03765..87b34eca320e4 100644 --- a/modules/mapper-extras/src/main/java/org/opensearch/index/mapper/ScaledFloatFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/opensearch/index/mapper/ScaledFloatFieldMapper.java @@ -262,7 +262,7 @@ protected FieldTypeCapabilities.Capability searchCapability() { public Query termQuery(Object value, QueryShardContext context) { failIfNotIndexedAndNoDocValues(); long scaledValue = Math.round(scale(value)); - Query query = NumberFieldMapper.NumberType.LONG.termQuery(name(), scaledValue, hasDocValues(), isSearchable()); + Query query = NumberFieldMapper.NumberType.LONG.termQuery(name(), scaledValue, hasDocValues(), isEffectiveSearchable(context)); if (boost() != 1f) { query = new BoostQuery(query, boost()); } @@ -281,7 +281,7 @@ public Query termsQuery(List values, QueryShardContext context) { name(), Collections.unmodifiableList(scaledValues), hasDocValues(), - isSearchable() + isEffectiveSearchable(context) ); if (boost() != 1f) { query = new BoostQuery(query, boost()); @@ -307,7 +307,7 @@ public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower includeLower, includeUpper, hasDocValues(), - isSearchable(), + isEffectiveSearchable(context), context ); if (boost() != 1f) { diff --git a/modules/mapper-extras/src/test/java/org/opensearch/index/mapper/ScaledFloatFieldTypeTests.java b/modules/mapper-extras/src/test/java/org/opensearch/index/mapper/ScaledFloatFieldTypeTests.java index 10ccdc02a0690..b72865da479b9 100644 --- a/modules/mapper-extras/src/test/java/org/opensearch/index/mapper/ScaledFloatFieldTypeTests.java +++ b/modules/mapper-extras/src/test/java/org/opensearch/index/mapper/ScaledFloatFieldTypeTests.java @@ -51,12 +51,16 @@ import org.opensearch.index.fielddata.IndexNumericFieldData; import org.opensearch.index.fielddata.LeafNumericFieldData; import org.opensearch.index.fielddata.SortedNumericDoubleValues; +import org.opensearch.index.query.QueryShardContext; import org.opensearch.search.approximate.ApproximateScoreQuery; import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class ScaledFloatFieldTypeTests extends FieldTypeTestCase { public void testTermQuery() { @@ -68,7 +72,7 @@ public void testTermQuery() { long scaledValue = Math.round(value * ft.getScalingFactor()); Query dvQuery = SortedNumericDocValuesField.newSlowExactQuery("scaled_float", scaledValue); Query query = new IndexOrDocValuesQuery(LongPoint.newExactQuery("scaled_float", scaledValue), dvQuery); - assertEquals(query, ft.termQuery(value, null)); + assertEquals(query, ft.termQuery(value, MOCK_QSC)); } public void testTermsQuery() { @@ -80,7 +84,7 @@ public void testTermsQuery() { long scaledValue1 = Math.round(value1 * ft.getScalingFactor()); double value2 = (randomDouble() * 2 - 1) * 10000; long scaledValue2 = Math.round(value2 * ft.getScalingFactor()); - assertEquals(LongField.newSetQuery("scaled_float", scaledValue1, scaledValue2), ft.termsQuery(Arrays.asList(value1, value2), null)); + assertEquals(LongField.newSetQuery("scaled_float", scaledValue1, scaledValue2), ft.termsQuery(Arrays.asList(value1, value2), MOCK_QSC)); } public void testRangeQuery() throws IOException { @@ -343,4 +347,41 @@ public void testLargeNumberIndexingAndQuerying() throws IOException { assertEquals("Terms query should find both documents", 2, searcher.count(termsQuery)); IOUtils.close(reader, dir); } + + /** + * On a pluggable-dataformat index the scaled_float mapper delegates to + * {@code NumberType.LONG} with {@code isEffectiveSearchable(context)} so the point-side of + * {@link IndexOrDocValuesQuery} is dropped. The Lucene secondary writes no BKD on such + * indices; keeping the point side would let the cost-based dispatch inside + * {@link IndexOrDocValuesQuery} pick an empty {@code PointValues} and return zero hits. + */ + public void testTermQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + ScaledFloatFieldMapper.ScaledFloatFieldType ft = new ScaledFloatFieldMapper.ScaledFloatFieldType("scaled_float", 100.0); + Query query = ft.termQuery(13.5, mockPluggableDataFormatContext()); + Query expected = SortedNumericDocValuesField.newSlowRangeQuery("scaled_float", 1350L, 1350L); + assertEquals(expected, query); + } + + /** Terms queries route to the DV-only branch on pluggable-dataformat indices. */ + public void testTermsQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + ScaledFloatFieldMapper.ScaledFloatFieldType ft = new ScaledFloatFieldMapper.ScaledFloatFieldType("scaled_float", 100.0); + Query query = ft.termsQuery(Arrays.asList(1.0, 2.0), mockPluggableDataFormatContext()); + Query expected = SortedNumericDocValuesField.newSlowSetQuery("scaled_float", 100L, 200L); + assertEquals(expected, query); + } + + /** Range queries route to the DV-only branch on pluggable-dataformat indices. */ + public void testRangeQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + ScaledFloatFieldMapper.ScaledFloatFieldType ft = new ScaledFloatFieldMapper.ScaledFloatFieldType("scaled_float", 100.0); + Query query = ft.rangeQuery(1.0, 10.0, true, true, mockPluggableDataFormatContext()); + Query expected = SortedNumericDocValuesField.newSlowRangeQuery("scaled_float", 100L, 1000L); + assertEquals(expected, query); + } + + /** A {@link QueryShardContext} mock that reports the pluggable-dataformat gate as enabled. */ + private static QueryShardContext mockPluggableDataFormatContext() { + QueryShardContext ctx = mock(QueryShardContext.class); + when(ctx.isPluggableDataFormatEnabled()).thenReturn(true); + return ctx; + } } diff --git a/server/src/main/java/org/opensearch/index/mapper/BooleanFieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/BooleanFieldMapper.java index 93c940ab4bbb5..472ab432c6104 100644 --- a/server/src/main/java/org/opensearch/index/mapper/BooleanFieldMapper.java +++ b/server/src/main/java/org/opensearch/index/mapper/BooleanFieldMapper.java @@ -270,9 +270,10 @@ public DocValueFormat docValueFormat(@Nullable String format, ZoneId timeZone) { @Override public Query termQuery(Object value, QueryShardContext context) { failIfNotIndexedAndNoDocValues(); - if (!isSearchable()) { + if (!isEffectiveSearchable(context)) { return SortedNumericDocValuesField.newSlowExactQuery(name(), Values.TRUE.bytesEquals(indexedValueForSearch(value)) ? 1 : 0); } + Query query = new TermQuery(new Term(name(), indexedValueForSearch(value))); if (boost() != 1f) { query = new BoostQuery(query, boost()); diff --git a/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java index 0eee8ba3cd2d8..9e591699807e0 100644 --- a/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java @@ -538,15 +538,7 @@ public Query rangeQuery( (l, u, nowUsed) -> { Query dvQuery = hasDocValues() ? SortedNumericDocValuesField.newSlowRangeQuery(name(), l, u) : null; - // On indices backed by a pluggable dataformat (composite primary + Lucene secondary) - // the Lucene secondary writes no BKD for date fields, so the point-side of - // IndexOrDocValuesQuery reports cost=0 and wins the cost race — returning zero hits. - // Force the pure doc-values branch so range/term queries resolve correctly against - // the codec-served DV column. - boolean effectiveSearchable = isSearchable() && !context.isPluggableDataFormatEnabled(); - - // Not searchable (either declared, or gated off by the pluggable-dataformat check). Must have doc values. - if (!effectiveSearchable) { + if (!isEffectiveSearchable(context)) { if (context.indexSortedOnField(name())) { dvQuery = new IndexSortSortedNumericDocValuesRangeQuery(name(), l, u, dvQuery); } diff --git a/server/src/main/java/org/opensearch/index/mapper/IpFieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/IpFieldMapper.java index f51a2c8ea238c..54b5d8f0ba52b 100644 --- a/server/src/main/java/org/opensearch/index/mapper/IpFieldMapper.java +++ b/server/src/main/java/org/opensearch/index/mapper/IpFieldMapper.java @@ -302,10 +302,11 @@ public Query termQuery(Object value, @Nullable QueryShardContext context) { true ); } - if (isSearchable() && hasDocValues()) { + boolean effectiveSearchable = isEffectiveSearchable(context); + if (effectiveSearchable && hasDocValues()) { return new IndexOrDocValuesQuery(pointQuery, dvQuery); } else { - return isSearchable() ? pointQuery : dvQuery; + return effectiveSearchable ? pointQuery : dvQuery; } } @@ -317,7 +318,7 @@ public Query termsQuery(List values, QueryShardContext context) { List masks = new ArrayList<>(); parseIps(values, concreteIPs, masks); - if (!isSearchable()) { + if (!isEffectiveSearchable(context)) { return hasDocValues() ? docValuesTermsQuery(concreteIPs, masks) : new MatchNoDocsQuery("never happened"); } @@ -440,10 +441,11 @@ public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower true ); } - if (isSearchable() && hasDocValues()) { + boolean effectiveSearchable = isEffectiveSearchable(context); + if (effectiveSearchable && hasDocValues()) { return new IndexOrDocValuesQuery(pointQuery, dvQuery); } else { - return isSearchable() ? pointQuery : dvQuery; + return effectiveSearchable ? pointQuery : dvQuery; } }); } diff --git a/server/src/main/java/org/opensearch/index/mapper/MappedFieldType.java b/server/src/main/java/org/opensearch/index/mapper/MappedFieldType.java index 6310a7f270fb5..14041d136732d 100644 --- a/server/src/main/java/org/opensearch/index/mapper/MappedFieldType.java +++ b/server/src/main/java/org/opensearch/index/mapper/MappedFieldType.java @@ -190,6 +190,22 @@ public boolean isSearchable() { return isIndexed; } + /** + * Returns whether this numeric field should be advertised as searchable to the + * per-type query factory. On indices backed by a pluggable dataformat (composite + * primary + Lucene secondary), the Lucene secondary writes no BKD for numeric + * fields, so the point-side of {@link org.apache.lucene.search.IndexOrDocValuesQuery} + * reports {@code cost=0} and wins the cost race — returning zero hits. Routing + * through the pure doc-values branch (by treating the field as not-searchable) + * avoids that trap and executes correctly against the codec-served DV column. + * + *

Non-pluggable-dataformat indices retain their normal {@link #isSearchable()} + * behavior so the BKD fast path is preserved. + */ + public boolean isEffectiveSearchable(QueryShardContext context) { + return isSearchable() && !context.isPluggableDataFormatEnabled(); + } + /** * Returns true if the field is stored separately. */ diff --git a/server/src/main/java/org/opensearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/NumberFieldMapper.java index 7ca8af63ec735..cbdf162d64ebd 100644 --- a/server/src/main/java/org/opensearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/opensearch/index/mapper/NumberFieldMapper.java @@ -1986,29 +1986,10 @@ public NumericType numericType() { return type.numericType(); } - /** - * Returns whether this numeric field should be advertised as searchable to the - * per-type query factory. On indices backed by a pluggable dataformat (composite - * primary + Lucene secondary), the Lucene secondary writes no BKD for numeric - * fields, so the point-side of {@link org.apache.lucene.search.IndexOrDocValuesQuery} - * reports {@code cost=0} and wins the cost race — returning zero hits. Routing - * through the pure doc-values branch (by treating the field as not-searchable) - * avoids that trap and executes correctly against the codec-served DV column. - * - *

Non-pluggable-dataformat indices retain their normal {@link #isSearchable()} - * behavior so the BKD fast path is preserved. - */ - private boolean effectiveSearchable(QueryShardContext context) { - if (context != null && context.isPluggableDataFormatEnabled()) { - return false; - } - return isSearchable(); - } - @Override public Query termQuery(Object value, QueryShardContext context) { failIfNotIndexedAndNoDocValues(); - Query query = type.termQuery(name(), value, hasDocValues(), effectiveSearchable(context)); + Query query = type.termQuery(name(), value, hasDocValues(), isEffectiveSearchable(context)); if (boost() != 1f) { query = new BoostQuery(query, boost()); } @@ -2018,7 +1999,7 @@ public Query termQuery(Object value, QueryShardContext context) { @Override public Query termsQuery(List values, QueryShardContext context) { failIfNotIndexedAndNoDocValues(); - Query query = type.termsQuery(name(), values, hasDocValues(), effectiveSearchable(context)); + Query query = type.termsQuery(name(), values, hasDocValues(), isEffectiveSearchable(context)); if (boost() != 1f) { query = new BoostQuery(query, boost()); } @@ -2027,7 +2008,7 @@ public Query termsQuery(List values, QueryShardContext context) { public Query bitmapQuery(BytesArray bitmap, QueryShardContext context) { failIfNotIndexedAndNoDocValues(); - return type.bitmapQuery(name(), bitmap, effectiveSearchable(context), hasDocValues()); + return type.bitmapQuery(name(), bitmap, isEffectiveSearchable(context), hasDocValues()); } @Override @@ -2040,7 +2021,7 @@ public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower includeLower, includeUpper, hasDocValues(), - effectiveSearchable(context), + isEffectiveSearchable(context), context ); if (boost() != 1f) { diff --git a/server/src/test/java/org/opensearch/index/mapper/BooleanFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/BooleanFieldTypeTests.java index 2ddec0c628bac..b0ff9f82111f3 100644 --- a/server/src/test/java/org/opensearch/index/mapper/BooleanFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/BooleanFieldTypeTests.java @@ -38,14 +38,25 @@ import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.TermQuery; import org.apache.lucene.util.BytesRef; +import org.opensearch.index.query.QueryShardContext; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class BooleanFieldTypeTests extends FieldTypeTestCase { + /** Pluggable-dataformat context — for tests that exercise the isEffectiveSearchable gate. */ + private static QueryShardContext pluggableContext() { + QueryShardContext ctx = mock(QueryShardContext.class); + when(ctx.isPluggableDataFormatEnabled()).thenReturn(true); + return ctx; + } + public void testValueFormat() { MappedFieldType ft = new BooleanFieldMapper.BooleanFieldType("field"); assertEquals(false, ft.docValueFormat(null, null).format(0)); @@ -63,68 +74,80 @@ public void testValueForSearch() { public void testTermQuery() { MappedFieldType ft = new BooleanFieldMapper.BooleanFieldType("field"); - assertEquals(new TermQuery(new Term("field", "T")), ft.termQuery("true", null)); - assertEquals(new TermQuery(new Term("field", "F")), ft.termQuery("false", null)); + assertEquals(new TermQuery(new Term("field", "T")), ft.termQuery("true", MOCK_QSC)); + assertEquals(new TermQuery(new Term("field", "F")), ft.termQuery("false", MOCK_QSC)); MappedFieldType doc_ft = new BooleanFieldMapper.BooleanFieldType("field", false, true); - assertEquals(SortedNumericDocValuesField.newSlowExactQuery("field", 1), doc_ft.termQuery("true", null)); - assertEquals(SortedNumericDocValuesField.newSlowExactQuery("field", 0), doc_ft.termQuery("false", null)); + assertEquals(SortedNumericDocValuesField.newSlowExactQuery("field", 1), doc_ft.termQuery("true", MOCK_QSC)); + assertEquals(SortedNumericDocValuesField.newSlowExactQuery("field", 0), doc_ft.termQuery("false", MOCK_QSC)); MappedFieldType boost_ft = new BooleanFieldMapper.BooleanFieldType("field"); boost_ft.setBoost(2f); - assertEquals(new BoostQuery(new TermQuery(new Term("field", "T")), 2f), boost_ft.termQuery("true", null)); - assertEquals(new BoostQuery(new TermQuery(new Term("field", "F")), 2f), boost_ft.termQuery("false", null)); + assertEquals(new BoostQuery(new TermQuery(new Term("field", "T")), 2f), boost_ft.termQuery("true", MOCK_QSC)); + assertEquals(new BoostQuery(new TermQuery(new Term("field", "F")), 2f), boost_ft.termQuery("false", MOCK_QSC)); MappedFieldType unsearchable = new BooleanFieldMapper.BooleanFieldType("field", false, false); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQuery("true", null)); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQuery("true", MOCK_QSC)); assertEquals("Cannot search on field [field] since it is both not indexed, and does not have doc_values enabled.", e.getMessage()); } + /** + * On a pluggable-dataformat index the boolean mapper must route to the doc-values exact + * query rather than a postings {@code TermQuery}. The Lucene secondary writes no postings + * for boolean fields on such indices, so a {@code TermQuery} would return zero hits. + */ + public void testTermQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + MappedFieldType ft = new BooleanFieldMapper.BooleanFieldType("field"); + QueryShardContext pluggable = pluggableContext(); + assertEquals(SortedNumericDocValuesField.newSlowExactQuery("field", 1), ft.termQuery("true", pluggable)); + assertEquals(SortedNumericDocValuesField.newSlowExactQuery("field", 0), ft.termQuery("false", pluggable)); + } + public void testTermsQuery() { MappedFieldType ft = new BooleanFieldMapper.BooleanFieldType("field"); List terms = new ArrayList<>(); terms.add(new BytesRef("true")); terms.add(new BytesRef("false")); - assertEquals(new FieldExistsQuery("field"), ft.termsQuery(terms, null)); + assertEquals(new FieldExistsQuery("field"), ft.termsQuery(terms, MOCK_QSC)); List newTerms = new ArrayList<>(); newTerms.add(new BytesRef("true")); - assertEquals(new TermQuery(new Term("field", "T")), ft.termsQuery(newTerms, null)); + assertEquals(new TermQuery(new Term("field", "T")), ft.termsQuery(newTerms, MOCK_QSC)); List incorrectTerms = new ArrayList<>(); incorrectTerms.add(new BytesRef("true")); incorrectTerms.add(new BytesRef("random")); - IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> ft.termsQuery(incorrectTerms, null)); + IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> ft.termsQuery(incorrectTerms, MOCK_QSC)); assertEquals("Can't parse boolean value [random], expected [true] or [false]", ex.getMessage()); MappedFieldType doc_only_ft = new BooleanFieldMapper.BooleanFieldType("field", false, true); - assertEquals(SortedNumericDocValuesField.newSlowExactQuery("field", 1), doc_only_ft.termsQuery(newTerms, null)); + assertEquals(SortedNumericDocValuesField.newSlowExactQuery("field", 1), doc_only_ft.termsQuery(newTerms, MOCK_QSC)); MappedFieldType unsearchable = new BooleanFieldMapper.BooleanFieldType("field", false, false); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termsQuery(terms, null)); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termsQuery(terms, MOCK_QSC)); assertEquals("Cannot search on field [field] since it is both not indexed, and does not have doc_values enabled.", e.getMessage()); } public void testRangeQuery() { BooleanFieldMapper.BooleanFieldType ft = new BooleanFieldMapper.BooleanFieldType("field"); - assertEquals(new FieldExistsQuery("field"), ft.rangeQuery(false, true, true, true, null)); + assertEquals(new FieldExistsQuery("field"), ft.rangeQuery(false, true, true, true, MOCK_QSC)); - assertEquals(new TermQuery(new Term("field", "T")), ft.rangeQuery(false, true, false, true, null)); + assertEquals(new TermQuery(new Term("field", "T")), ft.rangeQuery(false, true, false, true, MOCK_QSC)); - assertEquals(new TermQuery(new Term("field", "F")), ft.rangeQuery(false, true, true, false, null)); + assertEquals(new TermQuery(new Term("field", "F")), ft.rangeQuery(false, true, true, false, MOCK_QSC)); - assertEquals(new MatchNoDocsQuery(), ft.rangeQuery(false, true, false, false, null)); + assertEquals(new MatchNoDocsQuery(), ft.rangeQuery(false, true, false, false, MOCK_QSC)); - assertEquals(new MatchNoDocsQuery(), ft.rangeQuery(false, true, false, false, null)); + assertEquals(new MatchNoDocsQuery(), ft.rangeQuery(false, true, false, false, MOCK_QSC)); - assertEquals(new TermQuery(new Term("field", "F")), ft.rangeQuery(false, false, true, true, null)); + assertEquals(new TermQuery(new Term("field", "F")), ft.rangeQuery(false, false, true, true, MOCK_QSC)); - assertEquals(new TermQuery(new Term("field", "F")), ft.rangeQuery(null, false, true, true, null)); + assertEquals(new TermQuery(new Term("field", "F")), ft.rangeQuery(null, false, true, true, MOCK_QSC)); - assertEquals(new FieldExistsQuery("field"), ft.rangeQuery(false, null, true, true, null)); + assertEquals(new FieldExistsQuery("field"), ft.rangeQuery(false, null, true, true, MOCK_QSC)); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> ft.rangeQuery("random", null, true, true, null)); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> ft.rangeQuery("random", null, true, true, MOCK_QSC)); assertEquals("Can't parse boolean value [random], expected [true] or [false]", e.getMessage()); } diff --git a/server/src/test/java/org/opensearch/index/mapper/IpFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/IpFieldTypeTests.java index d11d32cf65bc1..e22f656cbd587 100644 --- a/server/src/test/java/org/opensearch/index/mapper/IpFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/IpFieldTypeTests.java @@ -46,6 +46,7 @@ import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.common.network.InetAddresses; import org.opensearch.common.settings.Settings; +import org.opensearch.index.query.QueryShardContext; import java.io.IOException; import java.net.InetAddress; @@ -54,6 +55,9 @@ import java.util.List; import java.util.Objects; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class IpFieldTypeTests extends FieldTypeTestCase { public void testValueFormat() throws Exception { @@ -90,7 +94,7 @@ public void testTermQuery() { query, SortedSetDocValuesField.newSlowExactQuery("field", new BytesRef(((PointRangeQuery) query).getLowerPoint())) ), - ft.termQuery(ip, null) + ft.termQuery(ip, MOCK_QSC) ); ip = "192.168.1.7"; @@ -100,7 +104,7 @@ public void testTermQuery() { query, SortedSetDocValuesField.newSlowExactQuery("field", new BytesRef(((PointRangeQuery) query).getLowerPoint())) ), - ft.termQuery(ip, null) + ft.termQuery(ip, MOCK_QSC) ); ip = "2001:db8::2:1"; @@ -118,7 +122,7 @@ public void testTermQuery() { true ) ), - ft.termQuery(prefix, null) + ft.termQuery(prefix, MOCK_QSC) ); ip = "192.168.1.7"; @@ -135,11 +139,11 @@ public void testTermQuery() { true ) ), - ft.termQuery(prefix, null) + ft.termQuery(prefix, MOCK_QSC) ); MappedFieldType unsearchable = new IpFieldMapper.IpFieldType("field", false, false, false, null, Collections.emptyMap()); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQuery("::1", null)); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQuery("::1", MOCK_QSC)); assertEquals("Cannot search on field [field] since it is both not indexed, and does not have doc_values enabled.", e.getMessage()); } @@ -151,14 +155,14 @@ public void testDvOnlyTermQuery() { assertEquals( SortedSetDocValuesField.newSlowExactQuery("field", new BytesRef(((PointRangeQuery) query).getLowerPoint())), - dvOnly.termQuery(ip, null) + dvOnly.termQuery(ip, MOCK_QSC) ); ip = "192.168.1.7"; query = InetAddressPoint.newExactQuery("field", InetAddresses.forString(ip)); assertEquals( SortedSetDocValuesField.newSlowExactQuery("field", new BytesRef(((PointRangeQuery) query).getLowerPoint())), - dvOnly.termQuery(ip, null) + dvOnly.termQuery(ip, MOCK_QSC) ); ip = "2001:db8::2:1"; @@ -172,7 +176,7 @@ public void testDvOnlyTermQuery() { true, true ), - dvOnly.termQuery(prefix, null) + dvOnly.termQuery(prefix, MOCK_QSC) ); ip = "192.168.1.7"; @@ -185,7 +189,7 @@ public void testDvOnlyTermQuery() { true, true ), - dvOnly.termQuery(prefix, null) + dvOnly.termQuery(prefix, MOCK_QSC) ); } @@ -198,15 +202,15 @@ public void testTermsQuery() { assertEquals( InetAddressPoint.newSetQuery("field", InetAddresses.forString("::2"), InetAddresses.forString("::5")), - ft.termsQuery(Arrays.asList(InetAddresses.forString("::2"), InetAddresses.forString("::5")), null) + ft.termsQuery(Arrays.asList(InetAddresses.forString("::2"), InetAddresses.forString("::5")), MOCK_QSC) ); assertEquals( InetAddressPoint.newSetQuery("field", InetAddresses.forString("::2"), InetAddresses.forString("::5")), - ft.termsQuery(Arrays.asList("::2", "::5"), null) + ft.termsQuery(Arrays.asList("::2", "::5"), MOCK_QSC) ); // if the list includes a prefix query we fallback to a bool query - Query actual = ft.termsQuery(Arrays.asList("::42", "::2/16"), null); + Query actual = ft.termsQuery(Arrays.asList("::42", "::2/16"), MOCK_QSC); assertTrue(actual instanceof ConstantScoreQuery); assertTrue(((ConstantScoreQuery) actual).getQuery() instanceof BooleanQuery); BooleanQuery bq = (BooleanQuery) ((ConstantScoreQuery) actual).getQuery(); @@ -219,13 +223,13 @@ public void testDvOnlyTermsQuery() { assertEquals( SortedSetDocValuesField.newSlowSetQuery("field", List.of(ipToByteRef("::2"), ipToByteRef("::5"))), - dvOnly.termsQuery(Arrays.asList(InetAddresses.forString("::2"), InetAddresses.forString("::5")), null) + dvOnly.termsQuery(Arrays.asList(InetAddresses.forString("::2"), InetAddresses.forString("::5")), MOCK_QSC) ); assertEquals( SortedSetDocValuesField.newSlowSetQuery("field", List.of(ipToByteRef("::2"), ipToByteRef("::5"))), - dvOnly.termsQuery(Arrays.asList("::2", "::5"), null) + dvOnly.termsQuery(Arrays.asList("::2", "::5"), MOCK_QSC) ); - assertEquals(SortedSetDocValuesField.newSlowExactQuery("field", ipToByteRef("::2")), dvOnly.termsQuery(List.of("::2"), null)); + assertEquals(SortedSetDocValuesField.newSlowExactQuery("field", ipToByteRef("::2")), dvOnly.termsQuery(List.of("::2"), MOCK_QSC)); assertEquals( SortedSetDocValuesField.newSlowRangeQuery( "field", @@ -234,24 +238,24 @@ public void testDvOnlyTermsQuery() { true, true ), - dvOnly.termsQuery(List.of("::2/16"), null) + dvOnly.termsQuery(List.of("::2/16"), MOCK_QSC) ); // multirange handles both DocValuesMultiRangeQuery.SortedSetStabbingBuilder expect = new DocValuesMultiRangeQuery.SortedSetStabbingBuilder("field"); expect.add(ipToByteRef("::42")); expect.add(ipToByteRef("::"), ipToByteRef("::ffff:ffff:ffff:ffff:ffff:ffff:ffff")); - assertEquals(expect.build(), dvOnly.termsQuery(Arrays.asList("::42", "::2/16"), null)); + assertEquals(expect.build(), dvOnly.termsQuery(Arrays.asList("::42", "::2/16"), MOCK_QSC)); } public void testDvVsPoint() { MappedFieldType indexOnly = new IpFieldMapper.IpFieldType("field", true, false, false, null, Collections.emptyMap()); MappedFieldType dvOnly = new IpFieldMapper.IpFieldType("field", false, false, true, null, Collections.emptyMap()); MappedFieldType indexDv = new IpFieldMapper.IpFieldType("field", true, false, true, null, Collections.emptyMap()); - assertNotEquals("obey DocValues", indexOnly.termsQuery(List.of("::2/16"), null), indexDv.termsQuery(List.of("::2/16"), null)); - assertEquals(dvOnly.termQuery("::2/16", null), dvOnly.termsQuery(List.of("::2/16"), null)); + assertNotEquals("obey DocValues", indexOnly.termsQuery(List.of("::2/16"), MOCK_QSC), indexDv.termsQuery(List.of("::2/16"), MOCK_QSC)); + assertEquals(dvOnly.termQuery("::2/16", MOCK_QSC), dvOnly.termsQuery(List.of("::2/16"), MOCK_QSC)); assertEquals( - new IndexOrDocValuesQuery(indexOnly.termsQuery(List.of("::2/16"), null), dvOnly.termsQuery(List.of("::2/16"), null)), - indexDv.termsQuery(List.of("::2/16"), null) + new IndexOrDocValuesQuery(indexOnly.termsQuery(List.of("::2/16"), MOCK_QSC), dvOnly.termsQuery(List.of("::2/16"), MOCK_QSC)), + indexDv.termsQuery(List.of("::2/16"), MOCK_QSC) ); } @@ -269,7 +273,7 @@ public void testRangeQuery() { true ) ), - ft.rangeQuery(null, null, randomBoolean(), randomBoolean(), null, null, null, null) + ft.rangeQuery(null, null, randomBoolean(), randomBoolean(), null, null, null, MOCK_QSC) ); query = InetAddressPoint.newRangeQuery("field", InetAddresses.forString("::"), InetAddresses.forString("192.168.2.0")); @@ -284,7 +288,7 @@ public void testRangeQuery() { true ) ), - ft.rangeQuery(null, "192.168.2.0", randomBoolean(), true, null, null, null, null) + ft.rangeQuery(null, "192.168.2.0", randomBoolean(), true, null, null, null, MOCK_QSC) ); query = InetAddressPoint.newRangeQuery("field", InetAddresses.forString("::"), InetAddresses.forString("192.168.1.255")); @@ -299,7 +303,7 @@ public void testRangeQuery() { true ) ), - ft.rangeQuery(null, "192.168.2.0", randomBoolean(), false, null, null, null, null) + ft.rangeQuery(null, "192.168.2.0", randomBoolean(), false, null, null, null, MOCK_QSC) ); query = InetAddressPoint.newRangeQuery("field", InetAddresses.forString("2001:db8::"), InetAddressPoint.MAX_VALUE); @@ -314,7 +318,7 @@ public void testRangeQuery() { true ) ), - ft.rangeQuery("2001:db8::", null, true, randomBoolean(), null, null, null, null) + ft.rangeQuery("2001:db8::", null, true, randomBoolean(), null, null, null, MOCK_QSC) ); query = InetAddressPoint.newRangeQuery("field", InetAddresses.forString("2001:db8::1"), InetAddressPoint.MAX_VALUE); @@ -329,7 +333,7 @@ public void testRangeQuery() { true ) ), - ft.rangeQuery("2001:db8::", null, false, randomBoolean(), null, null, null, null) + ft.rangeQuery("2001:db8::", null, false, randomBoolean(), null, null, null, MOCK_QSC) ); query = InetAddressPoint.newRangeQuery("field", InetAddresses.forString("2001:db8::"), InetAddresses.forString("2001:db8::ffff")); @@ -344,7 +348,7 @@ public void testRangeQuery() { true ) ), - ft.rangeQuery("2001:db8::", "2001:db8::ffff", true, true, null, null, null, null) + ft.rangeQuery("2001:db8::", "2001:db8::ffff", true, true, null, null, null, MOCK_QSC) ); query = InetAddressPoint.newRangeQuery("field", InetAddresses.forString("2001:db8::1"), InetAddresses.forString("2001:db8::fffe")); @@ -359,7 +363,7 @@ public void testRangeQuery() { true ) ), - ft.rangeQuery("2001:db8::", "2001:db8::ffff", false, false, null, null, null, null) + ft.rangeQuery("2001:db8::", "2001:db8::ffff", false, false, null, null, null, MOCK_QSC) ); query = InetAddressPoint.newRangeQuery("field", InetAddresses.forString("2001:db8::2"), InetAddresses.forString("2001:db8::")); @@ -375,11 +379,11 @@ public void testRangeQuery() { ) ), // same lo/hi values but inclusive=false so this won't match anything - ft.rangeQuery("2001:db8::1", "2001:db8::1", false, false, null, null, null, null) + ft.rangeQuery("2001:db8::1", "2001:db8::1", false, false, null, null, null, MOCK_QSC) ); // Upper bound is the min IP and is not inclusive - assertEquals(new MatchNoDocsQuery(), ft.rangeQuery("::", "::", true, false, null, null, null, null)); + assertEquals(new MatchNoDocsQuery(), ft.rangeQuery("::", "::", true, false, null, null, null, MOCK_QSC)); // Lower bound is the max IP and is not inclusive assertEquals( @@ -392,7 +396,7 @@ public void testRangeQuery() { null, null, null, - null + MOCK_QSC ) ); @@ -409,7 +413,7 @@ public void testRangeQuery() { ) ), // same lo/hi values but inclusive=false so this won't match anything - ft.rangeQuery("::", "0.0.0.0", true, false, null, null, null, null) + ft.rangeQuery("::", "0.0.0.0", true, false, null, null, null, MOCK_QSC) ); query = InetAddressPoint.newRangeQuery("field", InetAddresses.forString("::1:0:0:0"), InetAddressPoint.MAX_VALUE); @@ -425,7 +429,7 @@ public void testRangeQuery() { ) ), // same lo/hi values but inclusive=false so this won't match anything - ft.rangeQuery("255.255.255.255", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", false, true, null, null, null, null) + ft.rangeQuery("255.255.255.255", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", false, true, null, null, null, MOCK_QSC) ); query = InetAddressPoint.newRangeQuery("field", InetAddresses.forString("192.168.1.7"), InetAddresses.forString("2001:db8::")); @@ -441,13 +445,13 @@ public void testRangeQuery() { true ) ), - ft.rangeQuery("::ffff:c0a8:107", "2001:db8::", true, true, null, null, null, null) + ft.rangeQuery("::ffff:c0a8:107", "2001:db8::", true, true, null, null, null, MOCK_QSC) ); MappedFieldType unsearchable = new IpFieldMapper.IpFieldType("field", false, false, false, null, Collections.emptyMap()); IllegalArgumentException e = expectThrows( IllegalArgumentException.class, - () -> unsearchable.rangeQuery("::1", "2001::", true, true, null, null, null, null) + () -> unsearchable.rangeQuery("::1", "2001::", true, true, null, null, null, MOCK_QSC) ); assertEquals("Cannot search on field [field] since it is both not indexed, and does not have doc_values enabled.", e.getMessage()); } @@ -466,4 +470,37 @@ public void testFetchSourceValue() throws IOException { .fieldType(); assertEquals(Collections.singletonList("2001:db8::2:7"), fetchSourceValue(nullValueMapper, null)); } + + /** + * On a pluggable-dataformat index the mapper must skip the point-based query construction + * and emit only the doc-values term query. The Lucene secondary writes no BKD on such + * indices, so keeping the point side would let the cost-based dispatch inside + * {@link IndexOrDocValuesQuery} pick an empty {@code PointValues} and return zero hits. + */ + public void testTermQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + MappedFieldType ft = new IpFieldMapper.IpFieldType("field"); + Query query = ft.termQuery("192.168.1.1", mockPluggableDataFormatContext()); + assertFalse("term path must not wrap points in IndexOrDocValuesQuery on pluggable", query instanceof IndexOrDocValuesQuery); + } + + /** Terms queries route to the DV-only branch on pluggable-dataformat indices. */ + public void testTermsQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + MappedFieldType ft = new IpFieldMapper.IpFieldType("field"); + Query query = ft.termsQuery(List.of("192.168.1.1"), mockPluggableDataFormatContext()); + assertFalse("terms path must not wrap points in IndexOrDocValuesQuery on pluggable", query instanceof IndexOrDocValuesQuery); + } + + /** Range queries route to the DV-only branch on pluggable-dataformat indices. */ + public void testRangeQueryUsesDocValuesWhenPluggableDataFormatEnabled() { + MappedFieldType ft = new IpFieldMapper.IpFieldType("field"); + Query query = ft.rangeQuery("10.0.0.0", "10.255.255.255", true, true, null, null, null, mockPluggableDataFormatContext()); + assertFalse("range path must not wrap points in IndexOrDocValuesQuery on pluggable", query instanceof IndexOrDocValuesQuery); + } + + /** Pluggable-dataformat context — for tests that exercise the isEffectiveSearchable gate. */ + private static QueryShardContext mockPluggableDataFormatContext() { + QueryShardContext ctx = mock(QueryShardContext.class); + when(ctx.isPluggableDataFormatEnabled()).thenReturn(true); + return ctx; + } } diff --git a/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java index 066698f9462df..fadb8a251bc8b 100644 --- a/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/NumberFieldTypeTests.java @@ -139,46 +139,46 @@ public void testIntegerTermsQueryWithDecimalPart() { MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER); assertEquals( new IndexOrDocValuesQuery(IntPoint.newSetQuery("field", 1), SortedNumericDocValuesField.newSlowSetQuery("field", 1)), - ft.termsQuery(Arrays.asList(1, 2.1), null) + ft.termsQuery(Arrays.asList(1, 2.1), MOCK_QSC) ); assertEquals( new IndexOrDocValuesQuery(IntPoint.newSetQuery("field", 1), SortedNumericDocValuesField.newSlowSetQuery("field", 1)), - ft.termsQuery(Arrays.asList(1.0, 2.1), null) + ft.termsQuery(Arrays.asList(1.0, 2.1), MOCK_QSC) ); - assertTrue(ft.termsQuery(Arrays.asList(1.1, 2.1), null) instanceof MatchNoDocsQuery); + assertTrue(ft.termsQuery(Arrays.asList(1.1, 2.1), MOCK_QSC) instanceof MatchNoDocsQuery); } public void testLongTermsQueryWithDecimalPart() { MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.LONG); assertEquals( new IndexOrDocValuesQuery(LongPoint.newSetQuery("field", 1), SortedNumericDocValuesField.newSlowSetQuery("field", 1)), - ft.termsQuery(Arrays.asList(1, 2.1), null) + ft.termsQuery(Arrays.asList(1, 2.1), MOCK_QSC) ); assertEquals( new IndexOrDocValuesQuery(LongPoint.newSetQuery("field", 1), SortedNumericDocValuesField.newSlowSetQuery("field", 1)), - ft.termsQuery(Arrays.asList(1.0, 2.1), null) + ft.termsQuery(Arrays.asList(1.0, 2.1), MOCK_QSC) ); - assertTrue(ft.termsQuery(Arrays.asList(1.1, 2.1), null) instanceof MatchNoDocsQuery); + assertTrue(ft.termsQuery(Arrays.asList(1.1, 2.1), MOCK_QSC) instanceof MatchNoDocsQuery); } public void testByteTermQueryWithDecimalPart() { MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.BYTE); - assertTrue(ft.termQuery(42.1, null) instanceof MatchNoDocsQuery); + assertTrue(ft.termQuery(42.1, MOCK_QSC) instanceof MatchNoDocsQuery); } public void testShortTermQueryWithDecimalPart() { MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.SHORT); - assertTrue(ft.termQuery(42.1, null) instanceof MatchNoDocsQuery); + assertTrue(ft.termQuery(42.1, MOCK_QSC) instanceof MatchNoDocsQuery); } public void testIntegerTermQueryWithDecimalPart() { MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER); - assertTrue(ft.termQuery(42.1, null) instanceof MatchNoDocsQuery); + assertTrue(ft.termQuery(42.1, MOCK_QSC) instanceof MatchNoDocsQuery); } public void testLongTermQueryWithDecimalPart() { MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG); - assertTrue(ft.termQuery(42.1, null) instanceof MatchNoDocsQuery); + assertTrue(ft.termQuery(42.1, MOCK_QSC) instanceof MatchNoDocsQuery); } private static MappedFieldType unsearchable() { @@ -189,10 +189,10 @@ public void testTermQuery() { MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG); Query dvQuery = SortedNumericDocValuesField.newSlowExactQuery("field", 42); Query query = new IndexOrDocValuesQuery(LongPoint.newExactQuery("field", 42), dvQuery); - assertEquals(query, ft.termQuery("42", null)); + assertEquals(query, ft.termQuery("42", MOCK_QSC)); MappedFieldType unsearchable = unsearchable(); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQuery("42", null)); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQuery("42", MOCK_QSC)); assertEquals("Cannot search on field [field] since it is both not indexed, and does not have doc_values enabled.", e.getMessage()); } @@ -1008,19 +1008,19 @@ public void testBitmapQuery() throws IOException { NumberFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER); assertEquals( new IndexOrDocValuesQuery(new BitmapIndexQuery("field", r), new BitmapDocValuesQuery("field", r)), - ft.bitmapQuery(bitmap, null) + ft.bitmapQuery(bitmap, MOCK_QSC) ); ft = new NumberFieldType("field", NumberType.INTEGER, false, false, true, true, true, null, Collections.emptyMap()); - assertEquals(new BitmapDocValuesQuery("field", r), ft.bitmapQuery(bitmap, null)); + assertEquals(new BitmapDocValuesQuery("field", r), ft.bitmapQuery(bitmap, MOCK_QSC)); ft = new NumberFieldType("field", NumberType.INTEGER, true, false, false, false, true, null, Collections.emptyMap()); - assertEquals(new BitmapIndexQuery("field", r), ft.bitmapQuery(bitmap, null)); + assertEquals(new BitmapIndexQuery("field", r), ft.bitmapQuery(bitmap, MOCK_QSC)); Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, new IndexWriterConfig()); DirectoryReader reader = DirectoryReader.open(w); - assertEquals(new MatchNoDocsQuery(), ft.bitmapQuery(bitmap, null).rewrite(newSearcher(reader))); + assertEquals(new MatchNoDocsQuery(), ft.bitmapQuery(bitmap, MOCK_QSC).rewrite(newSearcher(reader))); reader.close(); w.close(); dir.close(); @@ -1028,7 +1028,7 @@ public void testBitmapQuery() throws IOException { NumberType type = randomValueOtherThan(NumberType.INTEGER, () -> randomFrom(NumberType.values())); ft = new NumberFieldMapper.NumberFieldType("field", type); NumberFieldType finalFt = ft; - assertThrows(IllegalArgumentException.class, () -> finalFt.bitmapQuery(bitmap, null)); + assertThrows(IllegalArgumentException.class, () -> finalFt.bitmapQuery(bitmap, MOCK_QSC)); } public void testBitmapQuery64() throws IOException { @@ -1047,20 +1047,20 @@ public void testBitmapQuery64() throws IOException { assertEquals( new IndexOrDocValuesQuery(new Bitmap64IndexQuery("field", r), new Bitmap64DocValuesQuery("field", r)), - ft.bitmapQuery(bitmap, null) + ft.bitmapQuery(bitmap, MOCK_QSC) ); ft = new NumberFieldType("field", NumberType.LONG, false, false, true, true, true, null, Collections.emptyMap()); - assertEquals(new Bitmap64DocValuesQuery("field", r), ft.bitmapQuery(bitmap, null)); + assertEquals(new Bitmap64DocValuesQuery("field", r), ft.bitmapQuery(bitmap, MOCK_QSC)); ft = new NumberFieldType("field", NumberType.LONG, true, false, false, false, true, null, Collections.emptyMap()); - assertEquals(new Bitmap64IndexQuery("field", r), ft.bitmapQuery(bitmap, null)); + assertEquals(new Bitmap64IndexQuery("field", r), ft.bitmapQuery(bitmap, MOCK_QSC)); Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, new IndexWriterConfig()); DirectoryReader reader = DirectoryReader.open(w); - assertEquals(new MatchNoDocsQuery(), ft.bitmapQuery(bitmap, null).rewrite(newSearcher(reader))); + assertEquals(new MatchNoDocsQuery(), ft.bitmapQuery(bitmap, MOCK_QSC).rewrite(newSearcher(reader))); reader.close(); w.close(); @@ -1070,7 +1070,7 @@ public void testBitmapQuery64() throws IOException { ft = new NumberFieldMapper.NumberFieldType("field", type); NumberFieldType finalFt = ft; - assertThrows(IllegalArgumentException.class, () -> finalFt.bitmapQuery(bitmap, null)); + assertThrows(IllegalArgumentException.class, () -> finalFt.bitmapQuery(bitmap, MOCK_QSC)); } public void testFetchUnsignedLongDocValues() throws IOException { From a6dcb0ac0d67cbe07faa5047bb75dd763ef8d077 Mon Sep 17 00:00:00 2001 From: Mayank Aggarwal Date: Fri, 31 Jul 2026 12:30:31 +0530 Subject: [PATCH 5/7] Apply spotless formatting Signed-off-by: Mayank Aggarwal --- .../opensearch/index/mapper/ScaledFloatFieldTypeTests.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/mapper-extras/src/test/java/org/opensearch/index/mapper/ScaledFloatFieldTypeTests.java b/modules/mapper-extras/src/test/java/org/opensearch/index/mapper/ScaledFloatFieldTypeTests.java index b72865da479b9..91627d73a50fb 100644 --- a/modules/mapper-extras/src/test/java/org/opensearch/index/mapper/ScaledFloatFieldTypeTests.java +++ b/modules/mapper-extras/src/test/java/org/opensearch/index/mapper/ScaledFloatFieldTypeTests.java @@ -84,7 +84,10 @@ public void testTermsQuery() { long scaledValue1 = Math.round(value1 * ft.getScalingFactor()); double value2 = (randomDouble() * 2 - 1) * 10000; long scaledValue2 = Math.round(value2 * ft.getScalingFactor()); - assertEquals(LongField.newSetQuery("scaled_float", scaledValue1, scaledValue2), ft.termsQuery(Arrays.asList(value1, value2), MOCK_QSC)); + assertEquals( + LongField.newSetQuery("scaled_float", scaledValue1, scaledValue2), + ft.termsQuery(Arrays.asList(value1, value2), MOCK_QSC) + ); } public void testRangeQuery() throws IOException { From dd1f5fdb8f69dd48556a51eab7f2d2c3ae2547f9 Mon Sep 17 00:00:00 2001 From: Mayank Aggarwal Date: Fri, 31 Jul 2026 13:12:37 +0530 Subject: [PATCH 6/7] spotless fix for test classes Signed-off-by: Mayank Aggarwal --- .../org/opensearch/index/mapper/BooleanFieldTypeTests.java | 5 ++++- .../java/org/opensearch/index/mapper/IpFieldTypeTests.java | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/opensearch/index/mapper/BooleanFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/BooleanFieldTypeTests.java index b0ff9f82111f3..94a8860a675a7 100644 --- a/server/src/test/java/org/opensearch/index/mapper/BooleanFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/BooleanFieldTypeTests.java @@ -147,7 +147,10 @@ public void testRangeQuery() { assertEquals(new FieldExistsQuery("field"), ft.rangeQuery(false, null, true, true, MOCK_QSC)); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> ft.rangeQuery("random", null, true, true, MOCK_QSC)); + IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> ft.rangeQuery("random", null, true, true, MOCK_QSC) + ); assertEquals("Can't parse boolean value [random], expected [true] or [false]", e.getMessage()); } diff --git a/server/src/test/java/org/opensearch/index/mapper/IpFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/IpFieldTypeTests.java index e22f656cbd587..45f3c6250e3de 100644 --- a/server/src/test/java/org/opensearch/index/mapper/IpFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/IpFieldTypeTests.java @@ -251,7 +251,11 @@ public void testDvVsPoint() { MappedFieldType indexOnly = new IpFieldMapper.IpFieldType("field", true, false, false, null, Collections.emptyMap()); MappedFieldType dvOnly = new IpFieldMapper.IpFieldType("field", false, false, true, null, Collections.emptyMap()); MappedFieldType indexDv = new IpFieldMapper.IpFieldType("field", true, false, true, null, Collections.emptyMap()); - assertNotEquals("obey DocValues", indexOnly.termsQuery(List.of("::2/16"), MOCK_QSC), indexDv.termsQuery(List.of("::2/16"), MOCK_QSC)); + assertNotEquals( + "obey DocValues", + indexOnly.termsQuery(List.of("::2/16"), MOCK_QSC), + indexDv.termsQuery(List.of("::2/16"), MOCK_QSC) + ); assertEquals(dvOnly.termQuery("::2/16", MOCK_QSC), dvOnly.termsQuery(List.of("::2/16"), MOCK_QSC)); assertEquals( new IndexOrDocValuesQuery(indexOnly.termsQuery(List.of("::2/16"), MOCK_QSC), dvOnly.termsQuery(List.of("::2/16"), MOCK_QSC)), From 39f1670ae3be9f9bc364f1fc527a5e8392c80355 Mon Sep 17 00:00:00 2001 From: Mayank Aggarwal Date: Fri, 31 Jul 2026 13:32:25 +0530 Subject: [PATCH 7/7] Retrigger CI Signed-off-by: Mayank Aggarwal