/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.elasticsearch.index.mapper; import org.apache.lucene.document.InetAddressPoint; import org.apache.lucene.index.IndexableField; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.network.InetAddresses; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import java.io.IOException; import java.net.InetAddress; import java.util.Arrays; import java.util.HashSet; import java.util.Locale; import static org.elasticsearch.index.query.RangeQueryBuilder.GT_FIELD; import static org.elasticsearch.index.query.RangeQueryBuilder.GTE_FIELD; import static org.elasticsearch.index.query.RangeQueryBuilder.LT_FIELD; import static org.elasticsearch.index.query.RangeQueryBuilder.LTE_FIELD; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; public class RangeFieldMapperTests extends AbstractNumericFieldMapperTestCase { private static String FROM_DATE = "2016-10-31"; private static String TO_DATE = "2016-11-01 20:00:00"; private static String FROM_IP = "::ffff:c0a8:107"; private static String TO_IP = "2001:db8::"; private static int FROM = 5; private static String FROM_STR = FROM + ""; private static int TO = 10; private static String TO_STR = TO + ""; private static String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"; @Override protected void setTypeList() { TYPES = new HashSet<>(Arrays.asList("date_range", "ip_range", "float_range", "double_range", "integer_range", "long_range")); } private Object getFrom(String type) { if (type.equals("date_range")) { return FROM_DATE; } else if (type.equals("ip_range")) { return FROM_IP; } return random().nextBoolean() ? FROM : FROM_STR; } private String getFromField() { return random().nextBoolean() ? GT_FIELD.getPreferredName() : GTE_FIELD.getPreferredName(); } private String getToField() { return random().nextBoolean() ? LT_FIELD.getPreferredName() : LTE_FIELD.getPreferredName(); } private Object getTo(String type) { if (type.equals("date_range")) { return TO_DATE; } else if (type.equals("ip_range")) { return TO_IP; } return random().nextBoolean() ? TO : TO_STR; } private Object getMax(String type) { if (type.equals("date_range") || type.equals("long_range")) { return Long.MAX_VALUE; } else if (type.equals("ip_range")) { return InetAddressPoint.MAX_VALUE; } else if (type.equals("integer_range")) { return Integer.MAX_VALUE; } else if (type.equals("float_range")) { return Float.POSITIVE_INFINITY; } return Double.POSITIVE_INFINITY; } @Override public void doTestDefaults(String type) throws Exception { XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties").startObject("field").field("type", type); if (type.equals("date_range")) { mapping = mapping.field("format", DATE_FORMAT); } mapping = mapping.endObject().endObject().endObject().endObject(); DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping.string())); assertEquals(mapping.string(), mapper.mappingSource().toString()); ParsedDocument doc = mapper.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() .startObject() .startObject("field") .field(getFromField(), getFrom(type)) .field(getToField(), getTo(type)) .endObject() .endObject().bytes(), XContentType.JSON)); IndexableField[] fields = doc.rootDoc().getFields("field"); assertEquals(1, fields.length); IndexableField pointField = fields[0]; assertEquals(2, pointField.fieldType().pointDimensionCount()); assertFalse(pointField.fieldType().stored()); } @Override protected void doTestNotIndexed(String type) throws Exception { XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties").startObject("field").field("type", type).field("index", false); if (type.equals("date_range")) { mapping = mapping.field("format", DATE_FORMAT); } mapping = mapping.endObject().endObject().endObject().endObject(); DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping.string())); assertEquals(mapping.string(), mapper.mappingSource().toString()); ParsedDocument doc = mapper.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() .startObject() .startObject("field") .field(getFromField(), getFrom(type)) .field(getToField(), getTo(type)) .endObject() .endObject().bytes(), XContentType.JSON)); IndexableField[] fields = doc.rootDoc().getFields("field"); assertEquals(0, fields.length); } @Override protected void doTestNoDocValues(String type) throws Exception { XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties").startObject("field").field("type", type).field("doc_values", false); if (type.equals("date_range")) { mapping = mapping.field("format", DATE_FORMAT); } mapping = mapping.endObject().endObject().endObject().endObject(); DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping.string())); assertEquals(mapping.string(), mapper.mappingSource().toString()); ParsedDocument doc = mapper.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() .startObject() .startObject("field") .field(getFromField(), getFrom(type)) .field(getToField(), getTo(type)) .endObject() .endObject().bytes(), XContentType.JSON)); IndexableField[] fields = doc.rootDoc().getFields("field"); assertEquals(1, fields.length); IndexableField pointField = fields[0]; assertEquals(2, pointField.fieldType().pointDimensionCount()); } @Override protected void doTestStore(String type) throws Exception { XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties").startObject("field").field("type", type).field("store", true); if (type.equals("date_range")) { mapping = mapping.field("format", DATE_FORMAT); } mapping = mapping.endObject().endObject().endObject().endObject(); DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping.string())); assertEquals(mapping.string(), mapper.mappingSource().toString()); ParsedDocument doc = mapper.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() .startObject() .startObject("field") .field(getFromField(), getFrom(type)) .field(getToField(), getTo(type)) .endObject() .endObject().bytes(), XContentType.JSON)); IndexableField[] fields = doc.rootDoc().getFields("field"); assertEquals(2, fields.length); IndexableField pointField = fields[0]; assertEquals(2, pointField.fieldType().pointDimensionCount()); IndexableField storedField = fields[1]; assertTrue(storedField.fieldType().stored()); String strVal = "5"; if (type.equals("date_range")) { strVal = "1477872000000"; } else if (type.equals("ip_range")) { strVal = InetAddresses.toAddrString(InetAddresses.forString("192.168.1.7")) + " : " + InetAddresses.toAddrString(InetAddresses.forString("2001:db8:0:0:0:0:0:0")); } assertThat(storedField.stringValue(), containsString(strVal)); } @Override public void doTestCoerce(String type) throws IOException { XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties").startObject("field").field("type", type); if (type.equals("date_range")) { mapping = mapping.field("format", DATE_FORMAT); } mapping = mapping.endObject().endObject().endObject().endObject(); DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping.string())); assertEquals(mapping.string(), mapper.mappingSource().toString()); ParsedDocument doc = mapper.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() .startObject() .startObject("field") .field(getFromField(), getFrom(type)) .field(getToField(), getTo(type)) .endObject() .endObject().bytes(), XContentType.JSON)); IndexableField[] fields = doc.rootDoc().getFields("field"); assertEquals(1, fields.length); IndexableField pointField = fields[0]; assertEquals(2, pointField.fieldType().pointDimensionCount()); mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties").startObject("field").field("type", type).field("coerce", false).endObject().endObject() .endObject().endObject(); DocumentMapper mapper2 = parser.parse("type", new CompressedXContent(mapping.string())); assertEquals(mapping.string(), mapper2.mappingSource().toString()); ThrowingRunnable runnable = () -> mapper2.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() .startObject() .startObject("field") .field(getFromField(), "5.2") .field(getToField(), "10") .endObject() .endObject().bytes(), XContentType.JSON)); MapperParsingException e = expectThrows(MapperParsingException.class, runnable); assertThat(e.getCause().getMessage(), anyOf(containsString("passed as String"), containsString("failed to parse date"), containsString("is not an IP string literal"))); } @Override protected void doTestNullValue(String type) throws IOException { XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties").startObject("field").field("type", type).field("store", true); if (type.equals("date_range")) { mapping = mapping.field("format", DATE_FORMAT); } mapping = mapping.endObject().endObject().endObject().endObject(); DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping.string())); assertEquals(mapping.string(), mapper.mappingSource().toString()); // test null value for min and max ParsedDocument doc = mapper.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() .startObject() .startObject("field") .nullField(getFromField()) .nullField(getToField()) .endObject() .endObject().bytes(), XContentType.JSON)); assertEquals(2, doc.rootDoc().getFields("field").length); IndexableField[] fields = doc.rootDoc().getFields("field"); IndexableField storedField = fields[1]; String expected = type.equals("ip_range") ? InetAddresses.toAddrString((InetAddress)getMax(type)) : getMax(type) +""; assertThat(storedField.stringValue(), containsString(expected)); // test null max value doc = mapper.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() .startObject() .startObject("field") .field(getFromField(), getFrom(type)) .nullField(getToField()) .endObject() .endObject().bytes(), XContentType.JSON)); fields = doc.rootDoc().getFields("field"); assertEquals(2, fields.length); IndexableField pointField = fields[0]; assertEquals(2, pointField.fieldType().pointDimensionCount()); assertFalse(pointField.fieldType().stored()); storedField = fields[1]; assertTrue(storedField.fieldType().stored()); String strVal = "5"; if (type.equals("date_range")) { strVal = "1477872000000"; } else if (type.equals("ip_range")) { strVal = InetAddresses.toAddrString(InetAddresses.forString("192.168.1.7")) + " : " + InetAddresses.toAddrString(InetAddresses.forString("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); } assertThat(storedField.stringValue(), containsString(strVal)); } public void testNoBounds() throws Exception { for (String type : TYPES) { doTestNoBounds(type); } } public void doTestNoBounds(String type) throws IOException { XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties").startObject("field").field("type", type).field("store", true); if (type.equals("date_range")) { mapping = mapping.field("format", DATE_FORMAT); } mapping = mapping.endObject().endObject().endObject().endObject(); DocumentMapper mapper = parser.parse("type", new CompressedXContent(mapping.string())); assertEquals(mapping.string(), mapper.mappingSource().toString()); // test no bounds specified ParsedDocument doc = mapper.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() .startObject() .startObject("field") .endObject() .endObject().bytes(), XContentType.JSON)); IndexableField[] fields = doc.rootDoc().getFields("field"); assertEquals(2, fields.length); IndexableField pointField = fields[0]; assertEquals(2, pointField.fieldType().pointDimensionCount()); assertFalse(pointField.fieldType().stored()); IndexableField storedField = fields[1]; assertTrue(storedField.fieldType().stored()); String expected = type.equals("ip_range") ? InetAddresses.toAddrString((InetAddress)getMax(type)) : getMax(type) +""; assertThat(storedField.stringValue(), containsString(expected)); } public void testIllegalArguments() throws Exception { XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties").startObject("field").field("type", RangeFieldMapper.RangeType.INTEGER.name) .field("format", DATE_FORMAT).endObject().endObject().endObject().endObject(); ThrowingRunnable runnable = () -> parser.parse("type", new CompressedXContent(mapping.string())); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, runnable); assertThat(e.getMessage(), containsString("should not define a dateTimeFormatter")); } public void testSerializeDefaults() throws Exception { for (String type : TYPES) { String mapping = XContentFactory.jsonBuilder().startObject().startObject("type") .startObject("properties").startObject("field").field("type", type).endObject().endObject() .endObject().endObject().string(); DocumentMapper docMapper = parser.parse("type", new CompressedXContent(mapping)); RangeFieldMapper mapper = (RangeFieldMapper) docMapper.root().getMapper("field"); XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); mapper.doXContentBody(builder, true, ToXContent.EMPTY_PARAMS); String got = builder.endObject().string(); // if type is date_range we check that the mapper contains the default format and locale // otherwise it should not contain a locale or format assertTrue(got, got.contains("\"format\":\"strict_date_optional_time||epoch_millis\"") == type.equals("date_range")); assertTrue(got, got.contains("\"locale\":" + "\"" + Locale.ROOT + "\"") == type.equals("date_range")); } } }