LUCENE-6418, LUCENE-6411: add AssertingSpans and fix the bugs it found

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1673036 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Robert Muir 2015-04-12 18:24:14 +00:00
parent 113d6c7c8f
commit ddf0c1d231
7 changed files with 573 additions and 67 deletions

View File

@ -122,25 +122,30 @@ public class SpanNotQuery extends SpanQuery implements Cloneable {
return new FilterSpans(includeSpans) {
// last document we have checked matches() against for the exclusion, and failed
// when using approximations, so we don't call it again, and pass thru all inclusions.
int lastNonMatchingDoc = -1;
int lastApproxDoc = -1;
boolean lastApproxResult = false;
@Override
protected AcceptStatus accept(Spans candidate) throws IOException {
// TODO: this logic is ugly and sneaky, can we clean it up?
int doc = candidate.docID();
if (doc > excludeSpans.docID()) {
// catch up 'exclude' to the current doc
if (excludeTwoPhase != null) {
if (excludeApproximation.advance(doc) == doc) {
if (!excludeTwoPhase.matches()) {
lastNonMatchingDoc = doc; // mark as non-match
}
lastApproxDoc = doc;
lastApproxResult = excludeTwoPhase.matches();
}
} else {
excludeSpans.advance(doc);
}
} else if (excludeTwoPhase != null && doc == excludeSpans.docID() && doc != lastApproxDoc) {
// excludeSpans already sitting on our candidate doc, but matches not called yet.
lastApproxDoc = doc;
lastApproxResult = excludeTwoPhase.matches();
}
if (doc == lastNonMatchingDoc || doc != excludeSpans.docID()) {
if (doc != excludeSpans.docID() || (doc == lastApproxDoc && lastApproxResult == false)) {
return AcceptStatus.YES;
}

View File

@ -97,4 +97,16 @@ public abstract class Spans extends DocIdSetIterator {
public TwoPhaseIterator asTwoPhaseIterator() {
return null;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Class<? extends Spans> clazz = getClass();
sb.append(clazz.isAnonymousClass() ? clazz.getName() : clazz.getSimpleName());
sb.append("(doc=").append(docID());
sb.append(",start=").append(startPosition());
sb.append(",end=").append(endPosition());
sb.append(")");
return sb.toString();
}
}

View File

@ -341,7 +341,7 @@ public class TestBasics extends LuceneTestCase {
assertTrue(searcher.explain(query, 891).getValue() > 0.0f);
}
@Test @AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/LUCENE-6418")
@Test
public void testNpeInSpanNearInSpanFirstInSpanNot() throws Exception {
int n = 5;
SpanTermQuery hun = new SpanTermQuery(new Term("field", "hundred"));

View File

@ -33,11 +33,20 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
// TODO: we could go a little crazy for a lot of these,
// but these are just simple minimal cases in case something
// goes horribly wrong. Put more intense tests elsewhere.
/** generally wrap with asserting. but not always, so we don't hide bugs */
private SpanQuery span(SpanQuery query) {
if (random().nextInt(100) <= 95) {
return new AssertingSpanQuery(query);
} else {
return query;
}
}
/** SpanTermQuery(A) = TermQuery(A) */
public void testSpanTermVersusTerm() throws Exception {
Term t1 = randomTerm();
assertSameSet(new TermQuery(t1), new SpanTermQuery(t1));
assertSameSet(new TermQuery(t1), span(new SpanTermQuery(t1)));
}
/** SpanOrQuery(A, B) = (A B) */
@ -47,7 +56,7 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
BooleanQuery q1 = new BooleanQuery();
q1.add(new TermQuery(t1), Occur.SHOULD);
q1.add(new TermQuery(t2), Occur.SHOULD);
SpanOrQuery q2 = new SpanOrQuery(new SpanTermQuery(t1), new SpanTermQuery(t2));
SpanQuery q2 = span(new SpanOrQuery(span(new SpanTermQuery(t1)), span(new SpanTermQuery(t2))));
assertSameSet(q1, q2);
}
@ -55,7 +64,8 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanNotVersusSpanTerm() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
assertSubsetOf(new SpanNotQuery(new SpanTermQuery(t1), new SpanTermQuery(t2)), new SpanTermQuery(t1));
assertSubsetOf(span(new SpanNotQuery(span(new SpanTermQuery(t1)), span(new SpanTermQuery(t2)))),
span(new SpanTermQuery(t1)));
}
/** SpanNotQuery(A, [B C]) ⊆ SpanTermQuery(A) */
@ -63,8 +73,11 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
Term t1 = randomTerm();
Term t2 = randomTerm();
Term t3 = randomTerm();
SpanQuery near = new SpanNearQuery(new SpanQuery[] { new SpanTermQuery(t2), new SpanTermQuery(t3) }, 10, random().nextBoolean());
assertSubsetOf(new SpanNotQuery(new SpanTermQuery(t1), near), new SpanTermQuery(t1));
SpanQuery near = span(new SpanNearQuery(new SpanQuery[] {
span(new SpanTermQuery(t2)),
span(new SpanTermQuery(t3))
}, 10, random().nextBoolean()));
assertSubsetOf(span(new SpanNotQuery(span(new SpanTermQuery(t1)), near)), span(new SpanTermQuery(t1)));
}
/** SpanNotQuery([A B], C) ⊆ SpanNearQuery([A B]) */
@ -72,8 +85,11 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
Term t1 = randomTerm();
Term t2 = randomTerm();
Term t3 = randomTerm();
SpanQuery near = new SpanNearQuery(new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) }, 10, random().nextBoolean());
assertSubsetOf(new SpanNotQuery(near, new SpanTermQuery(t3)), near);
SpanQuery near = span(new SpanNearQuery(new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
}, 10, random().nextBoolean()));
assertSubsetOf(span(new SpanNotQuery(near, span(new SpanTermQuery(t3)))), near);
}
/** SpanNotQuery([A B], [C D]) ⊆ SpanNearQuery([A B]) */
@ -82,23 +98,33 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
Term t2 = randomTerm();
Term t3 = randomTerm();
Term t4 = randomTerm();
SpanQuery near1 = new SpanNearQuery(new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) }, 10, random().nextBoolean());
SpanQuery near2 = new SpanNearQuery(new SpanQuery[] { new SpanTermQuery(t3), new SpanTermQuery(t4) }, 10, random().nextBoolean());
assertSubsetOf(new SpanNotQuery(near1, near2), near1);
SpanQuery near1 = span(new SpanNearQuery(new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
}, 10, random().nextBoolean()));
SpanQuery near2 = span(new SpanNearQuery(new SpanQuery[] {
span(new SpanTermQuery(t3)),
span(new SpanTermQuery(t4))
}, 10, random().nextBoolean()));
assertSubsetOf(span(new SpanNotQuery(near1, near2)), near1);
}
/** SpanFirstQuery(A, 10) ⊆ SpanTermQuery(A) */
public void testSpanFirstVersusSpanTerm() throws Exception {
Term t1 = randomTerm();
assertSubsetOf(new SpanFirstQuery(new SpanTermQuery(t1), 10), new SpanTermQuery(t1));
assertSubsetOf(span(new SpanFirstQuery(span(new SpanTermQuery(t1)), 10)),
span(new SpanTermQuery(t1)));
}
/** SpanNearQuery([A, B], 0, true) = "A B" */
public void testSpanNearVersusPhrase() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanNearQuery q1 = new SpanNearQuery(subquery, 0, true);
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
SpanQuery q1 = span(new SpanNearQuery(subquery, 0, true));
PhraseQuery q2 = new PhraseQuery();
q2.add(t1);
q2.add(t2);
@ -109,8 +135,11 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanNearVersusBooleanAnd() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanNearQuery q1 = new SpanNearQuery(subquery, Integer.MAX_VALUE, false);
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
SpanQuery q1 = span(new SpanNearQuery(subquery, Integer.MAX_VALUE, false));
BooleanQuery q2 = new BooleanQuery();
q2.add(new TermQuery(t1), Occur.MUST);
q2.add(new TermQuery(t2), Occur.MUST);
@ -121,9 +150,12 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanNearVersusSloppySpanNear() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanNearQuery q1 = new SpanNearQuery(subquery, 0, false);
SpanNearQuery q2 = new SpanNearQuery(subquery, 1, false);
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
SpanQuery q1 = span(new SpanNearQuery(subquery, 0, false));
SpanQuery q2 = span(new SpanNearQuery(subquery, 1, false));
assertSubsetOf(q1, q2);
}
@ -131,9 +163,12 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanNearInOrderVersusOutOfOrder() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanNearQuery q1 = new SpanNearQuery(subquery, 3, true);
SpanNearQuery q2 = new SpanNearQuery(subquery, 3, false);
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
SpanQuery q1 = span(new SpanNearQuery(subquery, 3, true));
SpanQuery q2 = span(new SpanNearQuery(subquery, 3, false));
assertSubsetOf(q1, q2);
}
@ -141,10 +176,13 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanNearIncreasingSloppiness() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
for (int i = 0; i < 10; i++) {
SpanNearQuery q1 = new SpanNearQuery(subquery, i, false);
SpanNearQuery q2 = new SpanNearQuery(subquery, i+1, false);
SpanQuery q1 = span(new SpanNearQuery(subquery, i, false));
SpanQuery q2 = span(new SpanNearQuery(subquery, i+1, false));
assertSubsetOf(q1, q2);
}
}
@ -154,10 +192,14 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
Term t1 = randomTerm();
Term t2 = randomTerm();
Term t3 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2), new SpanTermQuery(t3) };
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2)),
span(new SpanTermQuery(t3))
};
for (int i = 0; i < 10; i++) {
SpanNearQuery q1 = new SpanNearQuery(subquery, i, false);
SpanNearQuery q2 = new SpanNearQuery(subquery, i+1, false);
SpanQuery q1 = span(new SpanNearQuery(subquery, i, false));
SpanQuery q2 = span(new SpanNearQuery(subquery, i+1, false));
assertSubsetOf(q1, q2);
}
}
@ -166,10 +208,13 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanNearIncreasingOrderedSloppiness() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
for (int i = 0; i < 10; i++) {
SpanNearQuery q1 = new SpanNearQuery(subquery, i, false);
SpanNearQuery q2 = new SpanNearQuery(subquery, i+1, false);
SpanQuery q1 = span(new SpanNearQuery(subquery, i, false));
SpanQuery q2 = span(new SpanNearQuery(subquery, i+1, false));
assertSubsetOf(q1, q2);
}
}
@ -179,10 +224,14 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
Term t1 = randomTerm();
Term t2 = randomTerm();
Term t3 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2), new SpanTermQuery(t3) };
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2)),
span(new SpanTermQuery(t3))
};
for (int i = 0; i < 10; i++) {
SpanNearQuery q1 = new SpanNearQuery(subquery, i, true);
SpanNearQuery q2 = new SpanNearQuery(subquery, i+1, true);
SpanQuery q1 = span(new SpanNearQuery(subquery, i, true));
SpanQuery q2 = span(new SpanNearQuery(subquery, i+1, true));
assertSubsetOf(q1, q2);
}
}
@ -192,7 +241,7 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
Term t1 = randomTerm();
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
Query q1 = new SpanPositionRangeQuery(new SpanTermQuery(t1), i, i+j);
Query q1 = span(new SpanPositionRangeQuery(span(new SpanTermQuery(t1)), i, i+j));
Query q2 = new TermQuery(t1);
assertSubsetOf(q1, q2);
}
@ -204,8 +253,8 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
Term t1 = randomTerm();
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
Query q1 = new SpanPositionRangeQuery(new SpanTermQuery(t1), i, i+j);
Query q2 = new SpanPositionRangeQuery(new SpanTermQuery(t1), i, i+j+1);
Query q1 = span(new SpanPositionRangeQuery(span(new SpanTermQuery(t1)), i, i+j));
Query q2 = span(new SpanPositionRangeQuery(span(new SpanTermQuery(t1)), i, i+j+1));
assertSubsetOf(q1, q2);
}
}
@ -214,7 +263,7 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
/** SpanPositionRangeQuery(A, 0, ∞) = TermQuery(A) */
public void testSpanRangeTermEverything() throws Exception {
Term t1 = randomTerm();
Query q1 = new SpanPositionRangeQuery(new SpanTermQuery(t1), 0, Integer.MAX_VALUE);
Query q1 = span(new SpanPositionRangeQuery(span(new SpanTermQuery(t1)), 0, Integer.MAX_VALUE));
Query q2 = new TermQuery(t1);
assertSameSet(q1, q2);
}
@ -223,11 +272,14 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanRangeNear() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanQuery nearQuery = new SpanNearQuery(subquery, 10, true);
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
SpanQuery nearQuery = span(new SpanNearQuery(subquery, 10, true));
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
Query q1 = new SpanPositionRangeQuery(nearQuery, i, i+j);
Query q1 = span(new SpanPositionRangeQuery(nearQuery, i, i+j));
Query q2 = nearQuery;
assertSubsetOf(q1, q2);
}
@ -238,12 +290,15 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanRangeNearIncreasingEnd() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanQuery nearQuery = new SpanNearQuery(subquery, 10, true);
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
SpanQuery nearQuery = span(new SpanNearQuery(subquery, 10, true));
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
Query q1 = new SpanPositionRangeQuery(nearQuery, i, i+j);
Query q2 = new SpanPositionRangeQuery(nearQuery, i, i+j+1);
Query q1 = span(new SpanPositionRangeQuery(nearQuery, i, i+j));
Query q2 = span(new SpanPositionRangeQuery(nearQuery, i, i+j+1));
assertSubsetOf(q1, q2);
}
}
@ -253,9 +308,12 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanRangeNearEverything() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanQuery nearQuery = new SpanNearQuery(subquery, 10, true);
Query q1 = new SpanPositionRangeQuery(nearQuery, 0, Integer.MAX_VALUE);
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
SpanQuery nearQuery = span(new SpanNearQuery(subquery, 10, true));
Query q1 = span(new SpanPositionRangeQuery(nearQuery, 0, Integer.MAX_VALUE));
Query q2 = nearQuery;
assertSameSet(q1, q2);
}
@ -264,7 +322,7 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanFirstTerm() throws Exception {
Term t1 = randomTerm();
for (int i = 0; i < 10; i++) {
Query q1 = new SpanFirstQuery(new SpanTermQuery(t1), i);
Query q1 = span(new SpanFirstQuery(span(new SpanTermQuery(t1)), i));
Query q2 = new TermQuery(t1);
assertSubsetOf(q1, q2);
}
@ -274,8 +332,8 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanFirstTermIncreasing() throws Exception {
Term t1 = randomTerm();
for (int i = 0; i < 10; i++) {
Query q1 = new SpanFirstQuery(new SpanTermQuery(t1), i);
Query q2 = new SpanFirstQuery(new SpanTermQuery(t1), i+1);
Query q1 = span(new SpanFirstQuery(span(new SpanTermQuery(t1)), i));
Query q2 = span(new SpanFirstQuery(span(new SpanTermQuery(t1)), i+1));
assertSubsetOf(q1, q2);
}
}
@ -283,7 +341,7 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
/** SpanFirstQuery(A, ∞) = TermQuery(A) */
public void testSpanFirstTermEverything() throws Exception {
Term t1 = randomTerm();
Query q1 = new SpanFirstQuery(new SpanTermQuery(t1), Integer.MAX_VALUE);
Query q1 = span(new SpanFirstQuery(span(new SpanTermQuery(t1)), Integer.MAX_VALUE));
Query q2 = new TermQuery(t1);
assertSameSet(q1, q2);
}
@ -292,10 +350,13 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanFirstNear() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanQuery nearQuery = new SpanNearQuery(subquery, 10, true);
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
SpanQuery nearQuery = span(new SpanNearQuery(subquery, 10, true));
for (int i = 0; i < 10; i++) {
Query q1 = new SpanFirstQuery(nearQuery, i);
Query q1 = span(new SpanFirstQuery(nearQuery, i));
Query q2 = nearQuery;
assertSubsetOf(q1, q2);
}
@ -305,11 +366,14 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanFirstNearIncreasing() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanQuery nearQuery = new SpanNearQuery(subquery, 10, true);
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
SpanQuery nearQuery = span(new SpanNearQuery(subquery, 10, true));
for (int i = 0; i < 10; i++) {
Query q1 = new SpanFirstQuery(nearQuery, i);
Query q2 = new SpanFirstQuery(nearQuery, i+1);
Query q1 = span(new SpanFirstQuery(nearQuery, i));
Query q2 = span(new SpanFirstQuery(nearQuery, i+1));
assertSubsetOf(q1, q2);
}
}
@ -318,9 +382,12 @@ public class TestSpanSearchEquivalence extends SearchEquivalenceTestBase {
public void testSpanFirstNearEverything() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
SpanQuery subquery[] = new SpanQuery[] { new SpanTermQuery(t1), new SpanTermQuery(t2) };
SpanQuery nearQuery = new SpanNearQuery(subquery, 10, true);
Query q1 = new SpanFirstQuery(nearQuery, Integer.MAX_VALUE);
SpanQuery subquery[] = new SpanQuery[] {
span(new SpanTermQuery(t1)),
span(new SpanTermQuery(t2))
};
SpanQuery nearQuery = span(new SpanNearQuery(subquery, 10, true));
Query q1 = span(new SpanFirstQuery(nearQuery, Integer.MAX_VALUE));
Query q2 = nearQuery;
assertSameSet(q1, q2);
}

View File

@ -0,0 +1,123 @@
package org.apache.lucene.search.spans;
/*
* 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.
*/
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermContext;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.Bits;
/** Wraps a span query with asserts */
public class AssertingSpanQuery extends SpanQuery {
private final SpanQuery in;
public AssertingSpanQuery(SpanQuery in) {
this.in = in;
}
@Override
public Spans getSpans(LeafReaderContext context, Bits acceptDocs, Map<Term,TermContext> termContexts) throws IOException {
Spans spans = in.getSpans(context, acceptDocs, termContexts);
if (spans == null) {
return null;
} else {
return new AssertingSpans(spans);
}
}
@Override
public String getField() {
return in.getField();
}
@Override
public String toString(String field) {
return "AssertingSpanQuery(" + in.toString(field) + ")";
}
@Override
public SpanWeight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
// TODO: we are wasteful and createWeight twice in this case... use VirtualMethod?
// we need to not wrap if the query is e.g. a Payload one that overrides this (it should really be final)
SpanWeight weight = in.createWeight(searcher, needsScores);
if (weight.getClass() == SpanWeight.class) {
return super.createWeight(searcher, needsScores);
} else {
return weight;
}
}
@Override
public void setBoost(float b) {
in.setBoost(b);
}
@Override
public float getBoost() {
return in.getBoost();
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
Query q = in.rewrite(reader);
if (q == in) {
return this;
} else if (q instanceof SpanQuery) {
return new AssertingSpanQuery((SpanQuery) q);
} else {
return q;
}
}
@Override
public void extractTerms(Set<Term> terms) {
in.extractTerms(terms);
}
@Override
public Query clone() {
return new AssertingSpanQuery((SpanQuery) in.clone());
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((in == null) ? 0 : in.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!super.equals(obj)) return false;
if (getClass() != obj.getClass()) return false;
AssertingSpanQuery other = (AssertingSpanQuery) obj;
if (in == null) {
if (other.in != null) return false;
} else if (!in.equals(other.in)) return false;
return true;
}
}

View File

@ -0,0 +1,273 @@
package org.apache.lucene.search.spans;
/*
* 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.
*/
import java.io.IOException;
import java.util.Collection;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.search.spans.Spans;
/**
* Wraps a Spans with additional asserts
*/
class AssertingSpans extends Spans {
final Spans in;
int doc = -1;
/**
* tracks current state of this spans
*/
static enum State {
/**
* document iteration has not yet begun ({@link #docID()} = -1)
*/
DOC_START,
/**
* two-phase iterator has moved to a new docid, but {@link TwoPhaseIterator#matches()} has
* not been called or it returned false (so you should not do things with the enum)
*/
DOC_UNVERIFIED,
/**
* iterator set to a valid docID, but position iteration has not yet begun ({@link #startPosition() == -1})
*/
POS_START,
/**
* iterator set to a valid docID, and positioned (-1 < {@link #startPosition()} < {@link #NO_MORE_POSITIONS})
*/
ITERATING,
/**
* positions exhausted ({@link #startPosition()} = {@link #NO_MORE_POSITIONS})
*/
POS_FINISHED,
/**
* documents exhausted ({@link #docID()} = {@link #NO_MORE_DOCS})
*/
DOC_FINISHED
};
State state = State.DOC_START;
AssertingSpans(Spans in) {
this.in = in;
}
@Override
public int nextStartPosition() throws IOException {
assert state != State.DOC_START : "invalid position access, state=" + state + ": " + in;
assert state != State.DOC_FINISHED : "invalid position access, state=" + state + ": " + in;
assert state != State.DOC_UNVERIFIED : "invalid position access, state=" + state + ": " + in;
checkCurrentPositions();
// move to next position
int prev = in.startPosition();
int start = in.nextStartPosition();
assert start >= prev : "invalid startPosition (positions went backwards, previous=" + prev + "): " + in;
// transition state if necessary
if (start == NO_MORE_POSITIONS) {
state = State.POS_FINISHED;
} else {
state = State.ITERATING;
}
// check new positions
checkCurrentPositions();
return start;
}
private void checkCurrentPositions() {
int start = in.startPosition();
int end = in.endPosition();
if (state == State.DOC_START || state == State.DOC_UNVERIFIED || state == State.POS_START) {
assert start == -1 : "invalid startPosition (should be -1): " + in;
assert end == -1 : "invalid endPosition (should be -1): " + in;
} else if (state == State.POS_FINISHED) {
assert start == NO_MORE_POSITIONS : "invalid startPosition (should be NO_MORE_POSITIONS): " + in;
assert end == NO_MORE_POSITIONS : "invalid endPosition (should be NO_MORE_POSITIONS): " + in;
} else {
assert start >= 0 : "invalid startPosition (negative): " + in;
assert start <= end : "invalid startPosition (> endPosition): " + in;
}
}
@Override
public int startPosition() {
checkCurrentPositions();
return in.startPosition();
}
@Override
public int endPosition() {
checkCurrentPositions();
return in.endPosition();
}
@Override
public Collection<byte[]> getPayload() throws IOException {
assert state == State.ITERATING : "getPayload() called in illegal state: " + state + ": " + in;
return in.getPayload();
}
@Override
public boolean isPayloadAvailable() throws IOException {
assert state == State.ITERATING : "isPayloadAvailable() called in illegal state: " + state + ": " + in;
return in.isPayloadAvailable();
}
@Override
public int docID() {
int doc = in.docID();
assert doc == this.doc : "broken docID() impl: docID() = " + doc + ", but next/advance last returned: " + this.doc + ": " + in;
return doc;
}
@Override
public int nextDoc() throws IOException {
assert state != State.DOC_FINISHED : "nextDoc() called after NO_MORE_DOCS: " + in;
int nextDoc = in.nextDoc();
assert nextDoc > doc : "backwards nextDoc from " + doc + " to " + nextDoc + ": " + in;
if (nextDoc == DocIdSetIterator.NO_MORE_DOCS) {
state = State.DOC_FINISHED;
} else {
assert in.startPosition() == -1 : "invalid initial startPosition() [should be -1]: " + in;
assert in.endPosition() == -1 : "invalid initial endPosition() [should be -1]: " + in;
state = State.POS_START;
}
doc = nextDoc;
return docID();
}
@Override
public int advance(int target) throws IOException {
assert state != State.DOC_FINISHED : "advance() called after NO_MORE_DOCS: " + in;
assert target > doc : "target must be > docID(), got " + target + " <= " + doc + ": " + in;
int advanced = in.advance(target);
assert advanced >= target : "backwards advance from: " + target + " to: " + advanced + ": " + in;
if (advanced == DocIdSetIterator.NO_MORE_DOCS) {
state = State.DOC_FINISHED;
} else {
assert in.startPosition() == -1 : "invalid initial startPosition() [should be -1]: " + in;
assert in.endPosition() == -1 : "invalid initial endPosition() [should be -1]: " + in;
state = State.POS_START;
}
doc = advanced;
return docID();
}
@Override
public String toString() {
return "Asserting(" + in + ")";
}
@Override
public long cost() {
return in.cost();
}
@Override
public TwoPhaseIterator asTwoPhaseIterator() {
final TwoPhaseIterator iterator = in.asTwoPhaseIterator();
if (iterator == null) {
return null;
}
return new AssertingTwoPhaseView(iterator);
}
class AssertingTwoPhaseView extends TwoPhaseIterator {
final TwoPhaseIterator in;
int lastDoc = -1;
AssertingTwoPhaseView(TwoPhaseIterator iterator) {
super(new AssertingDISI(iterator.approximation()));
this.in = iterator;
}
@Override
public boolean matches() throws IOException {
if (approximation.docID() == -1 || approximation.docID() == DocIdSetIterator.NO_MORE_DOCS) {
throw new AssertionError("matches() should not be called on doc ID " + approximation.docID());
}
if (lastDoc == approximation.docID()) {
throw new AssertionError("matches() has been called twice on doc ID " + approximation.docID());
}
lastDoc = approximation.docID();
boolean v = in.matches();
if (v) {
state = State.POS_START;
}
return v;
}
}
class AssertingDISI extends DocIdSetIterator {
final DocIdSetIterator in;
AssertingDISI(DocIdSetIterator in) {
this.in = in;
}
@Override
public int docID() {
assert in.docID() == AssertingSpans.this.docID();
return in.docID();
}
@Override
public int nextDoc() throws IOException {
assert state != State.DOC_FINISHED : "nextDoc() called after NO_MORE_DOCS: " + in;
int nextDoc = in.nextDoc();
assert nextDoc > doc : "backwards nextDoc from " + doc + " to " + nextDoc + ": " + in;
if (nextDoc == DocIdSetIterator.NO_MORE_DOCS) {
state = State.DOC_FINISHED;
} else {
state = State.DOC_UNVERIFIED;
}
doc = nextDoc;
return docID();
}
@Override
public int advance(int target) throws IOException {
assert state != State.DOC_FINISHED : "advance() called after NO_MORE_DOCS: " + in;
assert target > doc : "target must be > docID(), got " + target + " <= " + doc + ": " + in;
int advanced = in.advance(target);
assert advanced >= target : "backwards advance from: " + target + " to: " + advanced + ": " + in;
if (advanced == DocIdSetIterator.NO_MORE_DOCS) {
state = State.DOC_FINISHED;
} else {
state = State.DOC_UNVERIFIED;
}
doc = advanced;
return docID();
}
@Override
public long cost() {
return in.cost();
}
}
}

View File

@ -0,0 +1,26 @@
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<!--
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.
-->
<!-- not a package-info.java, because we already defined this package in core/ -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>
<body>
Support for testing search spanqueries.
</body>
</html>