/*
* 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 com.carrotsearch.randomizedtesting.generators.RandomPicks;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FloatPoint;
import org.apache.lucene.document.HalfFloatPoint;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.SortedNumericDocValuesField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.IndexOrDocValuesQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.TestUtil;
import org.elasticsearch.index.mapper.MappedFieldType.Relation;
import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType;
import org.hamcrest.Matchers;
import org.junit.Before;
import java.io.IOException;
import java.util.Arrays;
import java.util.function.Supplier;
public class NumberFieldTypeTests extends FieldTypeTestCase {
NumberType type;
@Before
public void pickType() {
type = RandomPicks.randomFrom(random(), NumberFieldMapper.NumberType.values());
}
@Override
protected MappedFieldType createDefaultFieldType() {
return new NumberFieldMapper.NumberFieldType(type);
}
public void testIsFieldWithinQuery() throws IOException {
MappedFieldType ft = createDefaultFieldType();
// current impl ignores args and should always return INTERSECTS
assertEquals(Relation.INTERSECTS, ft.isFieldWithinQuery(null, randomDouble(), randomDouble(),
randomBoolean(), randomBoolean(), null, null, null));
}
public void testIntegerTermsQueryWithDecimalPart() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.INTEGER);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
assertEquals(IntPoint.newSetQuery("field", 1), ft.termsQuery(Arrays.asList(1, 2.1), null));
assertEquals(IntPoint.newSetQuery("field", 1), ft.termsQuery(Arrays.asList(1.0, 2.1), null));
assertTrue(ft.termsQuery(Arrays.asList(1.1, 2.1), null) instanceof MatchNoDocsQuery);
}
public void testLongTermsQueryWithDecimalPart() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.LONG);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
assertEquals(LongPoint.newSetQuery("field", 1), ft.termsQuery(Arrays.asList(1, 2.1), null));
assertEquals(LongPoint.newSetQuery("field", 1), ft.termsQuery(Arrays.asList(1.0, 2.1), null));
assertTrue(ft.termsQuery(Arrays.asList(1.1, 2.1), null) instanceof MatchNoDocsQuery);
}
public void testByteTermQueryWithDecimalPart() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.BYTE);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
assertTrue(ft.termQuery(42.1, null) instanceof MatchNoDocsQuery);
}
public void testShortTermQueryWithDecimalPart() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.SHORT);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
assertTrue(ft.termQuery(42.1, null) instanceof MatchNoDocsQuery);
}
public void testIntegerTermQueryWithDecimalPart() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.INTEGER);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
assertTrue(ft.termQuery(42.1, null) instanceof MatchNoDocsQuery);
}
public void testLongTermQueryWithDecimalPart() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberFieldMapper.NumberType.LONG);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
assertTrue(ft.termQuery(42.1, null) instanceof MatchNoDocsQuery);
}
public void testTermQuery() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberFieldMapper.NumberType.LONG);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
assertEquals(LongPoint.newExactQuery("field", 42), ft.termQuery("42", null));
ft.setIndexOptions(IndexOptions.NONE);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> ft.termQuery("42", null));
assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage());
}
public void testRangeQueryWithNegativeBounds() {
MappedFieldType ftInt = new NumberFieldMapper.NumberFieldType(NumberType.INTEGER);
ftInt.setName("field");
ftInt.setIndexOptions(IndexOptions.DOCS);
assertEquals(ftInt.rangeQuery(-3, -3, true, true, null), ftInt.rangeQuery(-3.5, -2.5, true, true, null));
assertEquals(ftInt.rangeQuery(-3, -3, true, true, null), ftInt.rangeQuery(-3.5, -2.5, false, false, null));
assertEquals(ftInt.rangeQuery(0, 0, true, true, null), ftInt.rangeQuery(-0.5, 0.5, true, true, null));
assertEquals(ftInt.rangeQuery(0, 0, true, true, null), ftInt.rangeQuery(-0.5, 0.5, false, false, null));
assertEquals(ftInt.rangeQuery(1, 2, true, true, null), ftInt.rangeQuery(0.5, 2.5, true, true, null));
assertEquals(ftInt.rangeQuery(1, 2, true, true, null), ftInt.rangeQuery(0.5, 2.5, false, false, null));
assertEquals(ftInt.rangeQuery(0, 2, true, true, null), ftInt.rangeQuery(-0.5, 2.5, true, true, null));
assertEquals(ftInt.rangeQuery(0, 2, true, true, null), ftInt.rangeQuery(-0.5, 2.5, false, false, null));
assertEquals(ftInt.rangeQuery(-2, 0, true, true, null), ftInt.rangeQuery(-2.5, 0.5, true, true, null));
assertEquals(ftInt.rangeQuery(-2, 0, true, true, null), ftInt.rangeQuery(-2.5, 0.5, false, false, null));
assertEquals(ftInt.rangeQuery(-2, -1, true, true, null), ftInt.rangeQuery(-2.5, -0.5, true, true, null));
assertEquals(ftInt.rangeQuery(-2, -1, true, true, null), ftInt.rangeQuery(-2.5, -0.5, false, false, null));
MappedFieldType ftLong = new NumberFieldMapper.NumberFieldType(NumberType.LONG);
ftLong.setName("field");
ftLong.setIndexOptions(IndexOptions.DOCS);
assertEquals(ftLong.rangeQuery(-3, -3, true, true, null), ftLong.rangeQuery(-3.5, -2.5, true, true, null));
assertEquals(ftLong.rangeQuery(-3, -3, true, true, null), ftLong.rangeQuery(-3.5, -2.5, false, false, null));
assertEquals(ftLong.rangeQuery(0, 0, true, true, null), ftLong.rangeQuery(-0.5, 0.5, true, true, null));
assertEquals(ftLong.rangeQuery(0, 0, true, true, null), ftLong.rangeQuery(-0.5, 0.5, false, false, null));
assertEquals(ftLong.rangeQuery(1, 2, true, true, null), ftLong.rangeQuery(0.5, 2.5, true, true, null));
assertEquals(ftLong.rangeQuery(1, 2, true, true, null), ftLong.rangeQuery(0.5, 2.5, false, false, null));
assertEquals(ftLong.rangeQuery(0, 2, true, true, null), ftLong.rangeQuery(-0.5, 2.5, true, true, null));
assertEquals(ftLong.rangeQuery(0, 2, true, true, null), ftLong.rangeQuery(-0.5, 2.5, false, false, null));
assertEquals(ftLong.rangeQuery(-2, 0, true, true, null), ftLong.rangeQuery(-2.5, 0.5, true, true, null));
assertEquals(ftLong.rangeQuery(-2, 0, true, true, null), ftLong.rangeQuery(-2.5, 0.5, false, false, null));
assertEquals(ftLong.rangeQuery(-2, -1, true, true, null), ftLong.rangeQuery(-2.5, -0.5, true, true, null));
assertEquals(ftLong.rangeQuery(-2, -1, true, true, null), ftLong.rangeQuery(-2.5, -0.5, false, false, null));
}
public void testByteRangeQueryWithDecimalParts() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.BYTE);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
assertEquals(ft.rangeQuery(2, 10, true, true, null), ft.rangeQuery(1.1, 10, true, true, null));
assertEquals(ft.rangeQuery(2, 10, true, true, null), ft.rangeQuery(1.1, 10, false, true, null));
assertEquals(ft.rangeQuery(1, 10, true, true, null), ft.rangeQuery(1, 10.1, true, true, null));
assertEquals(ft.rangeQuery(1, 10, true, true, null), ft.rangeQuery(1, 10.1, true, false, null));
}
public void testShortRangeQueryWithDecimalParts() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.SHORT);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
assertEquals(ft.rangeQuery(2, 10, true, true, null), ft.rangeQuery(1.1, 10, true, true, null));
assertEquals(ft.rangeQuery(2, 10, true, true, null), ft.rangeQuery(1.1, 10, false, true, null));
assertEquals(ft.rangeQuery(1, 10, true, true, null), ft.rangeQuery(1, 10.1, true, true, null));
assertEquals(ft.rangeQuery(1, 10, true, true, null), ft.rangeQuery(1, 10.1, true, false, null));
}
public void testIntegerRangeQueryWithDecimalParts() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.INTEGER);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
assertEquals(ft.rangeQuery(2, 10, true, true, null), ft.rangeQuery(1.1, 10, true, true, null));
assertEquals(ft.rangeQuery(2, 10, true, true, null), ft.rangeQuery(1.1, 10, false, true, null));
assertEquals(ft.rangeQuery(1, 10, true, true, null), ft.rangeQuery(1, 10.1, true, true, null));
assertEquals(ft.rangeQuery(1, 10, true, true, null), ft.rangeQuery(1, 10.1, true, false, null));
}
public void testLongRangeQueryWithDecimalParts() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.LONG);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
assertEquals(ft.rangeQuery(2, 10, true, true, null), ft.rangeQuery(1.1, 10, true, true, null));
assertEquals(ft.rangeQuery(2, 10, true, true, null), ft.rangeQuery(1.1, 10, false, true, null));
assertEquals(ft.rangeQuery(1, 10, true, true, null), ft.rangeQuery(1, 10.1, true, true, null));
assertEquals(ft.rangeQuery(1, 10, true, true, null), ft.rangeQuery(1, 10.1, true, false, null));
}
public void testRangeQuery() {
MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberFieldMapper.NumberType.LONG);
ft.setName("field");
ft.setIndexOptions(IndexOptions.DOCS);
Query expected = new IndexOrDocValuesQuery(
LongPoint.newRangeQuery("field", 1, 3),
SortedNumericDocValuesField.newRangeQuery("field", 1, 3));
assertEquals(expected, ft.rangeQuery("1", "3", true, true, null));
ft.setIndexOptions(IndexOptions.NONE);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> ft.rangeQuery("1", "3", true, true, null));
assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage());
}
public void testConversions() {
assertEquals((byte) 3, NumberType.BYTE.parse(3d, true));
assertEquals((short) 3, NumberType.SHORT.parse(3d, true));
assertEquals(3, NumberType.INTEGER.parse(3d, true));
assertEquals(3L, NumberType.LONG.parse(3d, true));
assertEquals(3f, NumberType.HALF_FLOAT.parse(3d, true));
assertEquals(3f, NumberType.FLOAT.parse(3d, true));
assertEquals(3d, NumberType.DOUBLE.parse(3d, true));
assertEquals((byte) 3, NumberType.BYTE.parse(3.5, true));
assertEquals((short) 3, NumberType.SHORT.parse(3.5, true));
assertEquals(3, NumberType.INTEGER.parse(3.5, true));
assertEquals(3L, NumberType.LONG.parse(3.5, true));
assertEquals(3.5f, NumberType.FLOAT.parse(3.5, true));
assertEquals(3.5d, NumberType.DOUBLE.parse(3.5, true));
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> NumberType.BYTE.parse(128, true));
assertEquals("Value [128] is out of range for a byte", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> NumberType.SHORT.parse(65536, true));
assertEquals("Value [65536] is out of range for a short", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> NumberType.INTEGER.parse(2147483648L, true));
assertEquals("Value [2147483648] is out of range for an integer", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> NumberType.LONG.parse(10000000000000000000d, true));
assertEquals("Value [1.0E19] is out of range for a long", e.getMessage());
assertEquals(1.1f, NumberType.HALF_FLOAT.parse(1.1, true));
assertEquals(1.1f, NumberType.FLOAT.parse(1.1, true));
assertEquals(1.1d, NumberType.DOUBLE.parse(1.1, true));
}
public void testHalfFloatRange() throws IOException {
// make sure the accuracy loss of half floats only occurs at index time
// this test checks that searching half floats yields the same results as
// searching floats that are rounded to the closest half float
Directory dir = newDirectory();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(null));
final int numDocs = 10000;
for (int i = 0; i < numDocs; ++i) {
Document doc = new Document();
float value = (randomFloat() * 2 - 1) * 70000;
float rounded = HalfFloatPoint.sortableShortToHalfFloat(HalfFloatPoint.halfFloatToSortableShort(value));
doc.add(new HalfFloatPoint("half_float", value));
doc.add(new FloatPoint("float", rounded));
w.addDocument(doc);
}
final DirectoryReader reader = DirectoryReader.open(w);
w.close();
IndexSearcher searcher = newSearcher(reader);
final int numQueries = 1000;
for (int i = 0; i < numQueries; ++i) {
float l = (randomFloat() * 2 - 1) * 70000;
float u = (randomFloat() * 2 - 1) * 70000;
boolean includeLower = randomBoolean();
boolean includeUpper = randomBoolean();
Query floatQ = NumberFieldMapper.NumberType.FLOAT.rangeQuery("float", l, u, includeLower, includeUpper, false);
Query halfFloatQ = NumberFieldMapper.NumberType.HALF_FLOAT.rangeQuery("half_float", l, u, includeLower, includeUpper, false);
assertEquals(searcher.count(floatQ), searcher.count(halfFloatQ));
}
IOUtils.close(reader, dir);
}
public void testNegativeZero() {
assertEquals(
NumberType.DOUBLE.rangeQuery("field", null, -0d, true, true, false),
NumberType.DOUBLE.rangeQuery("field", null, +0d, true, false, false));
assertEquals(
NumberType.FLOAT.rangeQuery("field", null, -0f, true, true, false),
NumberType.FLOAT.rangeQuery("field", null, +0f, true, false, false));
assertEquals(
NumberType.HALF_FLOAT.rangeQuery("field", null, -0f, true, true, false),
NumberType.HALF_FLOAT.rangeQuery("field", null, +0f, true, false, false));
assertFalse(NumberType.DOUBLE.termQuery("field", -0d).equals(NumberType.DOUBLE.termQuery("field", +0d)));
assertFalse(NumberType.FLOAT.termQuery("field", -0f).equals(NumberType.FLOAT.termQuery("field", +0f)));
assertFalse(NumberType.HALF_FLOAT.termQuery("field", -0f).equals(NumberType.HALF_FLOAT.termQuery("field", +0f)));
}
// Make sure we construct the IndexOrDocValuesQuery objects with queries that match
// the same ranges
public void testDocValueByteRange() throws Exception {
doTestDocValueRangeQueries(NumberType.BYTE, () -> (byte) random().nextInt(256));
}
public void testDocValueShortRange() throws Exception {
doTestDocValueRangeQueries(NumberType.SHORT, () -> (short) random().nextInt(65536));
}
public void testDocValueIntRange() throws Exception {
doTestDocValueRangeQueries(NumberType.INTEGER, random()::nextInt);
}
public void testDocValueLongRange() throws Exception {
doTestDocValueRangeQueries(NumberType.LONG, random()::nextLong);
}
public void testDocValueHalfFloatRange() throws Exception {
doTestDocValueRangeQueries(NumberType.HALF_FLOAT, random()::nextFloat);
}
public void testDocValueFloatRange() throws Exception {
doTestDocValueRangeQueries(NumberType.FLOAT, random()::nextFloat);
}
public void testDocValueDoubleRange() throws Exception {
doTestDocValueRangeQueries(NumberType.DOUBLE, random()::nextDouble);
}
public void doTestDocValueRangeQueries(NumberType type, Supplier<Number> valueSupplier) throws Exception {
Directory dir = newDirectory();
IndexWriter w = new IndexWriter(dir, newIndexWriterConfig());
final int numDocs = TestUtil.nextInt(random(), 100, 500);
for (int i = 0; i < numDocs; ++i) {
w.addDocument(type.createFields("foo", valueSupplier.get(), true, true, false));
}
DirectoryReader reader = DirectoryReader.open(w);
IndexSearcher searcher = newSearcher(reader);
w.close();
final int iters = 10;
for (int iter = 0; iter < iters; ++iter) {
Query query = type.rangeQuery("foo",
random().nextBoolean() ? null : valueSupplier.get(),
random().nextBoolean() ? null : valueSupplier.get(),
randomBoolean(), randomBoolean(), true);
assertThat(query, Matchers.instanceOf(IndexOrDocValuesQuery.class));
IndexOrDocValuesQuery indexOrDvQuery = (IndexOrDocValuesQuery) query;
assertEquals(
searcher.count(indexOrDvQuery.getIndexQuery()),
searcher.count(indexOrDvQuery.getRandomAccessQuery()));
}
reader.close();
dir.close();
}
}