Skip to content

Commit 3c7b671

Browse files
SOLR-18019: Optimize existence queries for non-docValued PointFields (#3930)
1 parent 8539042 commit 3c7b671

File tree

4 files changed

+88
-23
lines changed

4 files changed

+88
-23
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
2+
title: Optimize existence queries for non-docValued FloatPointField and DoublePointField
3+
type: other # added, changed, fixed, deprecated, removed, dependency_update, security, other
4+
authors:
5+
- name: Houston Putman
6+
nick: HoustonPutman
7+
url: https://home.apache.org/phonebook.html?uid=houston
8+
links:
9+
- name: SOLR-18019
10+
url: https://issues.apache.org/jira/browse/SOLR-18019

solr/core/src/java/org/apache/solr/schema/DoublePointField.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,17 @@ public IndexableField createField(SchemaField field, Object value) {
178178
protected StoredField getStoredField(SchemaField sf, Object value) {
179179
return new StoredField(sf.getName(), (Double) this.toNativeType(value));
180180
}
181+
182+
/**
183+
* Override the default existence behavior, so that the non-docValued/norms implementation matches
184+
* NaN values for double and float fields. The [* TO *] query for those fields does not match
185+
* 'NaN' values, so they must be matched separately.
186+
*
187+
* <p>For doubles and floats the query behavior is equivalent to field:[-Infinity TO NaN] since
188+
* NaN has a value greater than +Infinity
189+
*/
190+
@Override
191+
public Query getSpecializedExistenceQuery(QParser parser, SchemaField field) {
192+
return DoublePoint.newRangeQuery(field.getName(), Double.NEGATIVE_INFINITY, Double.NaN);
193+
}
181194
}

solr/core/src/java/org/apache/solr/schema/FloatPointField.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,17 @@ public IndexableField createField(SchemaField field, Object value) {
178178
protected StoredField getStoredField(SchemaField sf, Object value) {
179179
return new StoredField(sf.getName(), (Float) this.toNativeType(value));
180180
}
181+
182+
/**
183+
* Override the default existence behavior, so that the non-docValued/norms implementation matches
184+
* NaN values for double and float fields. The [* TO *] query for those fields does not match
185+
* 'NaN' values, so they must be matched separately.
186+
*
187+
* <p>For doubles and floats the query behavior is equivalent to field:[-Infinity TO NaN] since
188+
* NaN has a value greater than +Infinity
189+
*/
190+
@Override
191+
public Query getSpecializedExistenceQuery(QParser parser, SchemaField field) {
192+
return FloatPoint.newRangeQuery(field.getName(), Float.NEGATIVE_INFINITY, Float.NaN);
193+
}
181194
}

solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import java.util.Locale;
3333
import java.util.Map;
3434
import java.util.Random;
35+
import org.apache.lucene.document.DoublePoint;
36+
import org.apache.lucene.document.FloatPoint;
3537
import org.apache.lucene.search.BooleanClause;
3638
import org.apache.lucene.search.BooleanQuery;
3739
import org.apache.lucene.search.BoostQuery;
@@ -40,6 +42,7 @@
4042
import org.apache.lucene.search.IndexOrDocValuesQuery;
4143
import org.apache.lucene.search.IndexSearcher;
4244
import org.apache.lucene.search.PointInSetQuery;
45+
import org.apache.lucene.search.PointRangeQuery;
4346
import org.apache.lucene.search.Query;
4447
import org.apache.lucene.search.TermInSetQuery;
4548
import org.apache.lucene.search.TermQuery;
@@ -1834,29 +1837,55 @@ public void testFieldExistsQueries() throws SyntaxError {
18341837
createdQuery instanceof FieldExistsQuery);
18351838
} else if (schemaField.getType().getNumberType() == NumberType.DOUBLE
18361839
|| schemaField.getType().getNumberType() == NumberType.FLOAT) {
1837-
assertTrue(
1838-
"PointField with NaN values must include \"exists or NaN\" if the field doesn't have norms or docValues: \""
1839-
+ query
1840-
+ "\".",
1841-
createdQuery instanceof ConstantScoreQuery);
1842-
assertTrue(
1843-
"PointField with NaN values must include \"exists or NaN\" if the field doesn't have norms or docValues: \""
1844-
+ query
1845-
+ "\".",
1846-
((ConstantScoreQuery) createdQuery).getQuery() instanceof BooleanQuery);
1847-
assertEquals(
1848-
"PointField with NaN values must include \"exists or NaN\" if the field doesn't have norms or docValues: \""
1849-
+ query
1850-
+ "\". This boolean query must be an OR.",
1851-
1,
1852-
((BooleanQuery) ((ConstantScoreQuery) createdQuery).getQuery())
1853-
.getMinimumNumberShouldMatch());
1854-
assertEquals(
1855-
"PointField with NaN values must include \"exists or NaN\" if the field doesn't have norms or docValues: \""
1856-
+ query
1857-
+ "\". This boolean query must have 2 clauses.",
1858-
2,
1859-
((BooleanQuery) ((ConstantScoreQuery) createdQuery).getQuery()).clauses().size());
1840+
if (schemaField.getType().isPointField()) {
1841+
assertTrue(
1842+
"PointField with NaN values must do a range query with an upper bound of NaN (Sorted higher than +Infinity) if the field doesn't have norms or docValues: \""
1843+
+ query
1844+
+ "\".",
1845+
createdQuery instanceof PointRangeQuery);
1846+
if (schemaField.getType().getNumberType() == NumberType.DOUBLE) {
1847+
assertEquals(
1848+
"PointField with NaN values must do a range query with an upper bound of NaN (Sorted higher than +Infinity) if the field doesn't have norms or docValues: \""
1849+
+ query
1850+
+ "\".",
1851+
Double.NaN,
1852+
DoublePoint.decodeDimension(
1853+
((PointRangeQuery) createdQuery).getUpperPoint(), 0),
1854+
0);
1855+
} else {
1856+
assertEquals(
1857+
"PointField with NaN values must do a range query with an upper bound of NaN (Sorted higher than +Infinity) if the field doesn't have norms or docValues: \""
1858+
+ query
1859+
+ "\".",
1860+
Float.NaN,
1861+
FloatPoint.decodeDimension(((PointRangeQuery) createdQuery).getUpperPoint(), 0),
1862+
0);
1863+
}
1864+
} else {
1865+
assertTrue(
1866+
"PointField with NaN values must include \"exists or NaN\" if the field doesn't have norms or docValues: \""
1867+
+ query
1868+
+ "\".",
1869+
createdQuery instanceof ConstantScoreQuery);
1870+
assertTrue(
1871+
"NumericField with NaN values must include \"exists or NaN\" if the field doesn't have norms or docValues: \""
1872+
+ query
1873+
+ "\".",
1874+
((ConstantScoreQuery) createdQuery).getQuery() instanceof BooleanQuery);
1875+
assertEquals(
1876+
"NumericField with NaN values must include \"exists or NaN\" if the field doesn't have norms or docValues: \""
1877+
+ query
1878+
+ "\". This boolean query must be an OR.",
1879+
1,
1880+
((BooleanQuery) ((ConstantScoreQuery) createdQuery).getQuery())
1881+
.getMinimumNumberShouldMatch());
1882+
assertEquals(
1883+
"NumericField with NaN values must include \"exists or NaN\" if the field doesn't have norms or docValues: \""
1884+
+ query
1885+
+ "\". This boolean query must have 2 clauses.",
1886+
2,
1887+
((BooleanQuery) ((ConstantScoreQuery) createdQuery).getQuery()).clauses().size());
1888+
}
18601889
} else {
18611890
assertFalse(
18621891
"Field doesn't have docValues, so existence query \""

0 commit comments

Comments
 (0)