Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- APIs for stream transport and new stream-based search api action ([#18722](https://github.com/opensearch-project/OpenSearch/pull/18722))
- Added the core process for warming merged segments in remote-store enabled domains ([#18683](https://github.com/opensearch-project/OpenSearch/pull/18683))
- Optimize Composite Aggregations by removing unnecessary object allocations ([#18531](https://github.com/opensearch-project/OpenSearch/pull/18531))
- [Star-Tree] Add search support for ip field type ([#18671](https://github.com/opensearch-project/OpenSearch/pull/18671))

### Changed
- Update Subject interface to use CheckedRunnable ([#18570](https://github.com/opensearch-project/OpenSearch/issues/18570))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ public String typeName() {
return CONTENT_TYPE;
}

private static InetAddress parse(Object value) {
public static InetAddress parse(Object value) {
if (value instanceof InetAddress) {
return (InetAddress) value;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.FloatPoint;
import org.apache.lucene.document.InetAddressPoint;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.sandbox.document.HalfFloatPoint;
import org.apache.lucene.util.BytesRef;
Expand All @@ -32,6 +33,7 @@
import org.opensearch.search.startree.filter.RangeMatchDimFilter;

import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
Expand Down Expand Up @@ -161,7 +163,9 @@ class Factory {
org.opensearch.index.mapper.KeywordFieldMapper.CONTENT_TYPE,
new KeywordFieldMapper(),
UNSIGNED_LONG.typeName(),
new UnsignedLongFieldMapperNumeric()
new UnsignedLongFieldMapperNumeric(),
org.opensearch.index.mapper.IpFieldMapper.CONTENT_TYPE,
new IpFieldMapper()
);

public static DimensionFilterMapper fromMappedFieldType(MappedFieldType mappedFieldType, SearchContext searchContext) {
Expand Down Expand Up @@ -406,25 +410,25 @@ Number getNextHigh(Number parsedValue) {
}
}

class KeywordFieldMapper implements DimensionFilterMapper {
abstract class OrdinalFieldMapper implements DimensionFilterMapper {

abstract Object parseRawField(String field, Object rawValue, MappedFieldType mappedFieldType) throws IllegalArgumentException;

@Override
public DimensionFilter getExactMatchFilter(MappedFieldType mappedFieldType, List<Object> rawValues) {
KeywordFieldType keywordFieldType = (KeywordFieldType) mappedFieldType;
List<Object> convertedValues = new ArrayList<>(rawValues.size());
for (Object rawValue : rawValues) {
convertedValues.add(parseRawKeyword(mappedFieldType.name(), rawValue, keywordFieldType));
convertedValues.add(parseRawField(mappedFieldType.name(), rawValue, mappedFieldType));
}
return new ExactMatchDimFilter(mappedFieldType.name(), convertedValues);
}

@Override
public DimensionFilter getRangeMatchFilter(MappedFieldType mappedFieldType, StarTreeRangeQuery rangeQuery) {
KeywordFieldType keywordFieldType = (KeywordFieldType) mappedFieldType;
return new RangeMatchDimFilter(
mappedFieldType.name(),
parseRawKeyword(mappedFieldType.name(), rangeQuery.from(), keywordFieldType),
parseRawKeyword(mappedFieldType.name(), rangeQuery.to(), keywordFieldType),
parseRawField(mappedFieldType.name(), rangeQuery.from(), mappedFieldType),
parseRawField(mappedFieldType.name(), rangeQuery.to(), mappedFieldType),
rangeQuery.includeLower(),
rangeQuery.includeUpper()
);
Expand Down Expand Up @@ -484,8 +488,20 @@ public Optional<Long> getMatchingOrdinal(
}
}

@Override
public int compareValues(Object v1, Object v2) {
if (!(v1 instanceof BytesRef) || !(v2 instanceof BytesRef)) {
throw new IllegalArgumentException("Expected BytesRef values for comparison");
}
return ((BytesRef) v1).compareTo((BytesRef) v2);
}
}

class KeywordFieldMapper extends OrdinalFieldMapper {

// TODO : Think around making TermBasedFT#indexedValueForSearch() accessor public for reuse here.
private Object parseRawKeyword(String field, Object rawValue, KeywordFieldType keywordFieldType) {
Object parseRawField(String field, Object rawValue, MappedFieldType mappedFieldType) {
KeywordFieldType keywordFieldType = (KeywordFieldType) mappedFieldType;
Object parsedValue = null;
if (rawValue != null) {
if (keywordFieldType.getTextSearchInfo().getSearchAnalyzer() == Lucene.KEYWORD_ANALYZER) {
Expand All @@ -499,12 +515,51 @@ private Object parseRawKeyword(String field, Object rawValue, KeywordFieldType k
}
return parsedValue;
}
}

@Override
public int compareValues(Object v1, Object v2) {
if (!(v1 instanceof BytesRef) || !(v2 instanceof BytesRef)) {
throw new IllegalArgumentException("Expected BytesRef values for keyword comparison");
/**
* This class provides functionality to map IP address values for exact and range-based
* filtering within a Star-Tree index. It handles the conversion of IP address
* objects into sortable {@link BytesRef}.
*/
class IpFieldMapper extends OrdinalFieldMapper {

/**
* Parses a raw IP address value into a sortable {@link BytesRef}.
*
* This method handles various input types, including {@link InetAddress}, {@link BytesRef},
* and {@link String}, converting them into a binary representation using
* {@link InetAddressPoint#encode(InetAddress)}.
*
* @param field The name of the field being processed.
* @param rawValue The raw IP address value.
* @return A {@link BytesRef} representation of the IP address, or null if the input is null.
*/
Object parseRawField(String field, Object rawValue, MappedFieldType mappedFieldType) throws IllegalArgumentException {
Object parsedValue = null;
if (rawValue != null) {
try {
switch (rawValue) {
case InetAddress inetAddress -> {
parsedValue = new BytesRef(InetAddressPoint.encode(inetAddress));
}
case BytesRef bytesRef -> {
return bytesRef;
}
case String s -> {
InetAddress addr = InetAddress.getByName(s);
parsedValue = new BytesRef(InetAddressPoint.encode(addr));
}
default -> {
throw new IllegalArgumentException(
"Unsupported value type for IP field [" + field + "]: " + rawValue.getClass().getName()
);
}
}
} catch (Exception e) {
throw new IllegalArgumentException("Failed to parse IP value for field [" + field + "]", e);
}
}
return ((BytesRef) v1).compareTo((BytesRef) v2);
return parsedValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

package org.opensearch.search.aggregations.startree;

import org.apache.lucene.document.InetAddressPoint;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.util.BytesRef;
import org.opensearch.index.compositeindex.datacube.Metric;
Expand All @@ -18,7 +19,9 @@
import org.opensearch.index.compositeindex.datacube.startree.index.StarTreeValues;
import org.opensearch.index.compositeindex.datacube.startree.utils.iterator.SortedSetStarTreeValuesIterator;
import org.opensearch.index.mapper.CompositeDataCubeFieldType;
import org.opensearch.index.mapper.IpFieldMapper;
import org.opensearch.index.mapper.KeywordFieldMapper;
import org.opensearch.index.mapper.MappedFieldType;
import org.opensearch.index.mapper.MapperService;
import org.opensearch.index.mapper.NumberFieldMapper;
import org.opensearch.index.mapper.StarTreeMapper;
Expand All @@ -38,6 +41,8 @@
import org.opensearch.test.OpenSearchTestCase;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
Expand All @@ -47,12 +52,35 @@

public class DimensionFilterAndMapperTests extends OpenSearchTestCase {

public void testKeywordOrdinalMapping() throws IOException {
public void testIpMapping() throws Exception {
MappedFieldType mappedFieldType = new IpFieldMapper.IpFieldType("ip_field");
BytesRef ipAsBytes = new BytesRef(InetAddressPoint.encode(InetAddress.getByName("192.168.1.1")));
testOrdinalMapping(mappedFieldType, ipAsBytes);
}

public void testRawValuesIpParsing() throws UnknownHostException {
SearchContext searchContext = mock(SearchContext.class);
DimensionFilterMapper dimensionFilterMapper = DimensionFilterMapper.Factory.fromMappedFieldType(
new KeywordFieldMapper.KeywordFieldType("keyword"),
searchContext
MappedFieldType mappedFieldType = new IpFieldMapper.IpFieldType("ip_field");
DimensionFilterMapper dimensionFilterMapper = DimensionFilterMapper.Factory.fromMappedFieldType(mappedFieldType, searchContext);

assertThrows(IllegalArgumentException.class, () -> dimensionFilterMapper.getExactMatchFilter(mappedFieldType, List.of(1.0f)));
assertThrows(
IllegalArgumentException.class,
() -> dimensionFilterMapper.getExactMatchFilter(mappedFieldType, List.of("not.a.valid.ip"))
);
DimensionFilter df = dimensionFilterMapper.getExactMatchFilter(mappedFieldType, List.of(InetAddress.getByName("192.168.1.1")));
assertEquals("ip_field", df.getMatchingDimension());
}

public void testKeywordOrdinalMapping() throws IOException {
MappedFieldType mappedFieldType = new KeywordFieldMapper.KeywordFieldType("keyword");
BytesRef bytesRef = new BytesRef(new byte[] { 17, 29 });
testOrdinalMapping(mappedFieldType, bytesRef);
}

private void testOrdinalMapping(final MappedFieldType mappedFieldType, final BytesRef bytesRef) throws IOException {
SearchContext searchContext = mock(SearchContext.class);
DimensionFilterMapper dimensionFilterMapper = DimensionFilterMapper.Factory.fromMappedFieldType(mappedFieldType, searchContext);
StarTreeValues starTreeValues = mock(StarTreeValues.class);
SortedSetStarTreeValuesIterator sortedSetStarTreeValuesIterator = mock(SortedSetStarTreeValuesIterator.class);
TermsEnum termsEnum = mock(TermsEnum.class);
Expand All @@ -61,7 +89,6 @@ public void testKeywordOrdinalMapping() throws IOException {
Optional<Long> matchingOrdinal;

// Case Exact Match and found
BytesRef bytesRef = new BytesRef(new byte[] { 17, 29 });
when(sortedSetStarTreeValuesIterator.lookupTerm(bytesRef)).thenReturn(1L);
matchingOrdinal = dimensionFilterMapper.getMatchingOrdinal("field", bytesRef, starTreeValues, MatchType.EXACT);
assertTrue(matchingOrdinal.isPresent());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
import org.apache.lucene.document.DoubleField;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FloatField;
import org.apache.lucene.document.InetAddressPoint;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.KeywordField;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.SortedNumericDocValuesField;
import org.apache.lucene.document.SortedSetDocValuesField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexableField;
Expand All @@ -32,6 +34,7 @@
import org.apache.lucene.search.Query;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.index.RandomIndexWriter;
import org.apache.lucene.util.BytesRef;
import org.opensearch.common.lucene.Lucene;
import org.opensearch.common.settings.Settings;
import org.opensearch.common.util.MockBigArrays;
Expand All @@ -47,6 +50,7 @@
import org.opensearch.index.compositeindex.datacube.MetricStat;
import org.opensearch.index.compositeindex.datacube.NumericDimension;
import org.opensearch.index.compositeindex.datacube.OrdinalDimension;
import org.opensearch.index.mapper.IpFieldMapper;
import org.opensearch.index.mapper.KeywordFieldMapper;
import org.opensearch.index.mapper.MappedFieldType;
import org.opensearch.index.mapper.MapperService;
Expand Down Expand Up @@ -77,6 +81,7 @@

import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -136,6 +141,7 @@ public void testStarTreeDocValues() throws IOException {
new DimensionFieldData("half_float_field", () -> random().nextFloat(50), DimensionTypes.HALF_FLOAT),
new DimensionFieldData("float_field", () -> random().nextFloat(50), DimensionTypes.FLOAT),
new DimensionFieldData("double_field", () -> random().nextDouble(50), DimensionTypes.DOUBLE),
new DimensionFieldData("ip_field", this::randomIp, DimensionTypes.IP),
new DimensionFieldData("unsigned_long_field", () -> {
long queryValue = randomBoolean()
? 9223372036854775807L - random().nextInt(100000)
Expand Down Expand Up @@ -658,6 +664,28 @@ NumberFieldMapper.NumberType numberType() {
public IndexableField getField(String fieldName, Supplier<Object> valueSupplier) {
return new BigIntegerField(fieldName, (BigInteger) valueSupplier.get(), Field.Store.YES);
}
}),
IP(new DimensionFieldDataSupplier() {
@Override
public IndexableField getField(String fieldName, Supplier<Object> valueSupplier) {
try {
InetAddress address = InetAddress.getByName((String) valueSupplier.get());
return new SortedSetDocValuesField(fieldName, new BytesRef(InetAddressPoint.encode(address)));

} catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
public MappedFieldType getMappedField(String fieldName) {
return new IpFieldMapper.IpFieldType(fieldName);
}

@Override
public Dimension getDimension(String fieldName) {
return new OrdinalDimension(fieldName);
}
});

private final DimensionFieldDataSupplier dimensionFieldDataSupplier;
Expand All @@ -680,4 +708,8 @@ private String asUnsignedDecimalString(long l) {
return b.toString();
}

private String randomIp() {
Random r = random();
return r.nextInt(256) + "." + r.nextInt(256) + "." + r.nextInt(256) + "." + r.nextInt(256);
}
}
Loading