Format default values of IP ranges to match other range bound

This commit is contained in:
Oleksandr Kolomiiets 2024-04-23 15:01:51 -07:00
parent a2fdc53053
commit acb513945b
5 changed files with 129 additions and 59 deletions

View file

@ -323,7 +323,7 @@ setup:
index: index:
index: synthetic_source_test index: synthetic_source_test
id: "4" id: "4"
body: { "ip_range" : { "gt": "192.168.0.4", "lt": "192.168.0.8" } } body: { "ip_range" : { "gt": "2001:db8::", "lt": "200a:100::" } }
- do: - do:
index: index:
@ -347,7 +347,7 @@ setup:
index: index:
index: synthetic_source_test index: synthetic_source_test
id: "8" id: "8"
body: { "ip_range": { "gte": "10.10.10.10" } } body: { "ip_range": { "gte": "2001:db8::" } }
- do: - do:
indices.refresh: {} indices.refresh: {}
@ -367,7 +367,7 @@ setup:
ip_range: { "gte": "192.168.0.4", "lte": "192.168.0.4" } ip_range: { "gte": "192.168.0.4", "lte": "192.168.0.4" }
- match: - match:
hits.hits.3._source: hits.hits.3._source:
ip_range: { "gte": "192.168.0.5", "lte": "192.168.0.7" } ip_range: { "gte": "2001:db8::1", "lte": "200a:ff:ffff:ffff:ffff:ffff:ffff:ffff" }
- match: - match:
hits.hits.4._source: hits.hits.4._source:
ip_range: { "gte": "74.125.227.0", "lte": "74.125.227.127" } ip_range: { "gte": "74.125.227.0", "lte": "74.125.227.127" }
@ -375,10 +375,10 @@ setup:
hits.hits.5._source: {} hits.hits.5._source: {}
- match: - match:
hits.hits.6._source: hits.hits.6._source:
ip_range: { "gte": "::", "lte": "10.10.10.10" } ip_range: { "gte": "0.0.0.0", "lte": "10.10.10.10" }
- match: - match:
hits.hits.7._source: hits.hits.7._source:
ip_range: { "gte": "10.10.10.10", "lte": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" } ip_range: { "gte": "2001:db8::", "lte": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" }
--- ---
"Date range": "Date range":

View file

@ -381,17 +381,37 @@ public class RangeFieldMapper extends FieldMapper {
@Override @Override
protected void parseCreateField(DocumentParserContext context) throws IOException { protected void parseCreateField(DocumentParserContext context) throws IOException {
Range range;
XContentParser parser = context.parser(); XContentParser parser = context.parser();
final XContentParser.Token start = parser.currentToken(); if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
if (start == XContentParser.Token.VALUE_NULL) {
return; return;
} else if (start == XContentParser.Token.START_OBJECT) { }
Range range = parseRange(parser);
context.doc().addAll(fieldType().rangeType.createFields(context, name(), range, index, hasDocValues, store));
if (hasDocValues == false && (index || store)) {
context.addToFieldNames(fieldType().name());
}
}
private Range parseRange(XContentParser parser) throws IOException {
final XContentParser.Token start = parser.currentToken();
if (fieldType().rangeType == RangeType.IP && start == XContentParser.Token.VALUE_STRING) {
return parseIpRangeFromCidr(parser);
}
if (start != XContentParser.Token.START_OBJECT) {
throw new DocumentParsingException(
parser.getTokenLocation(),
"error parsing field [" + name() + "], expected an object but got " + parser.currentName()
);
}
RangeFieldType fieldType = fieldType(); RangeFieldType fieldType = fieldType();
RangeType rangeType = fieldType.rangeType; RangeType rangeType = fieldType.rangeType;
String fieldName = null; String fieldName = null;
Object from = rangeType.minValue(); Object parsedFrom = null;
Object to = rangeType.maxValue(); Object parsedTo = null;
boolean includeFrom = DEFAULT_INCLUDE_LOWER; boolean includeFrom = DEFAULT_INCLUDE_LOWER;
boolean includeTo = DEFAULT_INCLUDE_UPPER; boolean includeTo = DEFAULT_INCLUDE_UPPER;
XContentParser.Token token; XContentParser.Token token;
@ -402,22 +422,22 @@ public class RangeFieldMapper extends FieldMapper {
if (fieldName.equals(GT_FIELD.getPreferredName())) { if (fieldName.equals(GT_FIELD.getPreferredName())) {
includeFrom = false; includeFrom = false;
if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { if (parser.currentToken() != XContentParser.Token.VALUE_NULL) {
from = rangeType.parseFrom(fieldType, parser, coerce.value(), includeFrom); parsedFrom = rangeType.parseFrom(fieldType, parser, coerce.value(), includeFrom);
} }
} else if (fieldName.equals(GTE_FIELD.getPreferredName())) { } else if (fieldName.equals(GTE_FIELD.getPreferredName())) {
includeFrom = true; includeFrom = true;
if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { if (parser.currentToken() != XContentParser.Token.VALUE_NULL) {
from = rangeType.parseFrom(fieldType, parser, coerce.value(), includeFrom); parsedFrom = rangeType.parseFrom(fieldType, parser, coerce.value(), includeFrom);
} }
} else if (fieldName.equals(LT_FIELD.getPreferredName())) { } else if (fieldName.equals(LT_FIELD.getPreferredName())) {
includeTo = false; includeTo = false;
if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { if (parser.currentToken() != XContentParser.Token.VALUE_NULL) {
to = rangeType.parseTo(fieldType, parser, coerce.value(), includeTo); parsedTo = rangeType.parseTo(fieldType, parser, coerce.value(), includeTo);
} }
} else if (fieldName.equals(LTE_FIELD.getPreferredName())) { } else if (fieldName.equals(LTE_FIELD.getPreferredName())) {
includeTo = true; includeTo = true;
if (parser.currentToken() != XContentParser.Token.VALUE_NULL) { if (parser.currentToken() != XContentParser.Token.VALUE_NULL) {
to = rangeType.parseTo(fieldType, parser, coerce.value(), includeTo); parsedTo = rangeType.parseTo(fieldType, parser, coerce.value(), includeTo);
} }
} else { } else {
throw new DocumentParsingException( throw new DocumentParsingException(
@ -427,20 +447,10 @@ public class RangeFieldMapper extends FieldMapper {
} }
} }
} }
range = new Range(rangeType, from, to, includeFrom, includeTo); Object from = parsedFrom != null ? parsedFrom : rangeType.defaultFrom(parsedTo);
} else if (fieldType().rangeType == RangeType.IP && start == XContentParser.Token.VALUE_STRING) { Object to = parsedTo != null ? parsedTo : rangeType.defaultTo(parsedFrom);
range = parseIpRangeFromCidr(parser);
} else {
throw new DocumentParsingException(
parser.getTokenLocation(),
"error parsing field [" + name() + "], expected an object but got " + parser.currentName()
);
}
context.doc().addAll(fieldType().rangeType.createFields(context, name(), range, index, hasDocValues, store));
if (hasDocValues == false && (index || store)) { return new Range(rangeType, from, to, includeFrom, includeTo);
context.addToFieldNames(fieldType().name());
}
} }
private static Range parseIpRangeFromCidr(final XContentParser parser) throws IOException { private static Range parseIpRangeFromCidr(final XContentParser parser) throws IOException {

View file

@ -31,6 +31,7 @@ import org.elasticsearch.lucene.queries.BinaryDocValuesRangeQuery;
import org.elasticsearch.xcontent.XContentParser; import org.elasticsearch.xcontent.XContentParser;
import java.io.IOException; import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
@ -64,6 +65,26 @@ public enum RangeType {
return included ? address : nextDown(address); return included ? address : nextDown(address);
} }
public Object defaultFrom(Object parsedTo) {
if (parsedTo == null) {
return minValue();
}
// Make sure that we keep the range inside the same address family.
// `minValue()` is always IPv6 so we need to adjust it.
return parsedTo instanceof Inet4Address ? InetAddressPoint.decode(new byte[4]) : minValue();
}
public Object defaultTo(Object parsedFrom) {
if (parsedFrom == null) {
return maxValue();
}
// Make sure that we keep the range inside the same address family.
// `maxValue()` is always IPv6 so we need to adjust it.
return parsedFrom instanceof Inet4Address ? InetAddressPoint.decode(new byte[] { -1, -1, -1, -1 }) : maxValue();
}
@Override @Override
public InetAddress parseValue(Object value, boolean coerce, @Nullable DateMathParser dateMathParser) { public InetAddress parseValue(Object value, boolean coerce, @Nullable DateMathParser dateMathParser) {
if (value instanceof InetAddress) { if (value instanceof InetAddress) {
@ -844,6 +865,14 @@ public enum RangeType {
return included ? value : (Number) nextDown(value); return included ? value : (Number) nextDown(value);
} }
public Object defaultFrom(Object parsedTo) {
return minValue();
}
public Object defaultTo(Object parsedFrom) {
return maxValue();
}
public abstract Object minValue(); public abstract Object minValue();
public abstract Object maxValue(); public abstract Object maxValue();

View file

@ -87,6 +87,37 @@ public class IpRangeFieldMapperTests extends RangeFieldMapperTests {
} }
} }
@Override
public void testNullBounds() throws IOException {
DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> {
minimalMapping(b);
b.field("store", true);
}));
ParsedDocument bothNull = mapper.parse(source(b -> b.startObject("field").nullField("gte").nullField("lte").endObject()));
assertThat(storedValue(bothNull), equalTo("[:: : ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]"));
ParsedDocument onlyFromIPv4 = mapper.parse(
source(b -> b.startObject("field").field("gte", rangeValue()).nullField("lte").endObject())
);
assertThat(storedValue(onlyFromIPv4), equalTo("[192.168.1.7 : 255.255.255.255]"));
ParsedDocument onlyToIPv4 = mapper.parse(
source(b -> b.startObject("field").nullField("gte").field("lte", rangeValue()).endObject())
);
assertThat(storedValue(onlyToIPv4), equalTo("[0.0.0.0 : 192.168.1.7]"));
ParsedDocument onlyFromIPv6 = mapper.parse(
source(b -> b.startObject("field").field("gte", "2001:db8::").nullField("lte").endObject())
);
assertThat(storedValue(onlyFromIPv6), equalTo("[2001:db8:: : ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]"));
ParsedDocument onlyToIPv6 = mapper.parse(
source(b -> b.startObject("field").nullField("gte").field("lte", "2001:db8::").endObject())
);
assertThat(storedValue(onlyToIPv6), equalTo("[:: : 2001:db8::]"));
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void testValidSyntheticSource() throws IOException { public void testValidSyntheticSource() throws IOException {
CheckedConsumer<XContentBuilder, IOException> mapping = b -> { CheckedConsumer<XContentBuilder, IOException> mapping = b -> {

View file

@ -236,14 +236,14 @@ public abstract class RangeFieldMapperTests extends MapperTestCase {
} }
} }
private static String storedValue(ParsedDocument doc) { protected static String storedValue(ParsedDocument doc) {
assertEquals(3, doc.rootDoc().getFields("field").size()); assertEquals(3, doc.rootDoc().getFields("field").size());
List<IndexableField> fields = doc.rootDoc().getFields("field"); List<IndexableField> fields = doc.rootDoc().getFields("field");
IndexableField storedField = fields.get(2); IndexableField storedField = fields.get(2);
return storedField.stringValue(); return storedField.stringValue();
} }
public final void testNullBounds() throws IOException { public void testNullBounds() throws IOException {
// null, null => min, max // null, null => min, max
assertNullBounds(b -> b.startObject("field").nullField("gte").nullField("lte").endObject(), true, true); assertNullBounds(b -> b.startObject("field").nullField("gte").nullField("lte").endObject(), true, true);