LUCENE-9744: NPE on a degenerate query in MinimumShouldMatchIntervalsSource$MinimumMatchesIterator.getSubMatches() (#2323)

This commit is contained in:
Dawid Weiss 2021-02-08 21:49:00 +01:00 committed by GitHub
parent 80803eb9ad
commit 077f8ccf70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 119 additions and 1 deletions

View File

@ -288,7 +288,9 @@ Optimizations
Bug Fixes
---------------------
(No changes)
* LUCENE-9744: NPE on a degenerate query in MinimumShouldMatchIntervalsSource
$MinimumMatchesIterator.getSubMatches(). (Alan Woodward)
Other
---------------------

View File

@ -404,6 +404,16 @@ public final class Intervals {
* Return intervals that span combinations of intervals from {@code minShouldMatch} of the sources
*/
public static IntervalsSource atLeast(int minShouldMatch, IntervalsSource... sources) {
if (minShouldMatch == sources.length) {
return unordered(sources);
}
if (minShouldMatch > sources.length) {
return new NoMatchIntervalsSource(
"Too few sources to match minimum of ["
+ minShouldMatch
+ "]: "
+ Arrays.toString(sources));
}
return new MinimumShouldMatchIntervalsSource(sources, minShouldMatch);
}

View File

@ -42,6 +42,7 @@ class MinimumShouldMatchIntervalsSource extends IntervalsSource {
private final int minShouldMatch;
MinimumShouldMatchIntervalsSource(IntervalsSource[] sources, int minShouldMatch) {
assert minShouldMatch < sources.length;
this.sources = sources;
this.minShouldMatch = minShouldMatch;
}

View File

@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.lucene.queries.intervals;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.QueryVisitor;
/** A source returning no matches */
class NoMatchIntervalsSource extends IntervalsSource {
final String reason;
NoMatchIntervalsSource(String reason) {
this.reason = reason;
}
@Override
public IntervalIterator intervals(String field, LeafReaderContext ctx) throws IOException {
return null;
}
@Override
public IntervalMatchesIterator matches(String field, LeafReaderContext ctx, int doc)
throws IOException {
return null;
}
@Override
public void visit(String field, QueryVisitor visitor) {}
@Override
public int minExtent() {
return 0;
}
@Override
public Collection<IntervalsSource> pullUpDisjunctions() {
return Collections.singleton(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NoMatchIntervalsSource that = (NoMatchIntervalsSource) o;
return Objects.equals(reason, that.reason);
}
@Override
public int hashCode() {
return Objects.hash(reason);
}
@Override
public String toString() {
return "NOMATCH(" + reason + ")";
}
}

View File

@ -756,6 +756,27 @@ public class TestIntervals extends LuceneTestCase {
assertEquals(3, source.minExtent());
}
public void testDegenerateMinShouldMatch() throws IOException {
IntervalsSource source =
Intervals.ordered(
Intervals.atLeast(1, Intervals.term("interest")),
Intervals.atLeast(1, Intervals.term("anyone")));
MatchesIterator mi = getMatches(source, 0, "field1");
assertMatch(mi, 2, 4, 11, 29);
MatchesIterator subs = mi.getSubMatches();
assertNotNull(subs);
assertMatch(subs, 2, 2, 11, 19);
assertMatch(subs, 4, 4, 23, 29);
assertFalse(subs.next());
assertFalse(mi.next());
}
public void testNoMatchMinShouldMatch() throws IOException {
IntervalsSource source = Intervals.atLeast(4, Intervals.term("a"), Intervals.term("b"));
checkIntervals(source, "field", 0, new int[][] {});
}
public void testDefinedGaps() throws IOException {
IntervalsSource source =
Intervals.phrase(

View File

@ -111,4 +111,13 @@ public class TestSimplifications extends LuceneTestCase {
Intervals.term("a"), Intervals.term("b"), Intervals.term("c"), Intervals.term("d")),
actual);
}
public void testMinShouldMatchSimplifications() {
IntervalsSource expected = Intervals.unordered(Intervals.term("a"), Intervals.term("b"));
assertEquals(expected, Intervals.atLeast(2, Intervals.term("a"), Intervals.term("b")));
assertEquals(
"NOMATCH(Too few sources to match minimum of [3]: [a, b])",
Intervals.atLeast(3, Intervals.term("a"), Intervals.term("b")).toString());
}
}