blob: 394840603ba6bcbc6d1b8451f30cde21d20ccbfe [file] [log] [blame]
/*
* 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.search;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.FilterLeafReader;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.PostingsEnum;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.index.ReaderUtil;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.search.spans.SpanNearQuery;
import org.apache.lucene.search.spans.SpanOrQuery;
import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.search.spans.SpanTermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.LuceneTestCase;
public class TestMatchesIterator extends LuceneTestCase {
protected IndexSearcher searcher;
protected Directory directory;
protected IndexReader reader = null;
private static final String FIELD_WITH_OFFSETS = "field_offsets";
private static final String FIELD_NO_OFFSETS = "field_no_offsets";
private static final String FIELD_DOCS_ONLY = "field_docs_only";
private static final String FIELD_FREQS = "field_freqs";
private static final String FIELD_POINT = "field_point";
private static final FieldType OFFSETS = new FieldType(TextField.TYPE_STORED);
static {
OFFSETS.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS);
}
private static final FieldType DOCS = new FieldType(TextField.TYPE_STORED);
static {
DOCS.setIndexOptions(IndexOptions.DOCS);
}
private static final FieldType DOCS_AND_FREQS = new FieldType(TextField.TYPE_STORED);
static {
DOCS_AND_FREQS.setIndexOptions(IndexOptions.DOCS_AND_FREQS);
}
@Override
public void tearDown() throws Exception {
reader.close();
directory.close();
super.tearDown();
}
@Override
public void setUp() throws Exception {
super.setUp();
directory = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), directory,
newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
for (int i = 0; i < docFields.length; i++) {
Document doc = new Document();
doc.add(newField(FIELD_WITH_OFFSETS, docFields[i], OFFSETS));
doc.add(newField(FIELD_NO_OFFSETS, docFields[i], TextField.TYPE_STORED));
doc.add(newField(FIELD_DOCS_ONLY, docFields[i], DOCS));
doc.add(newField(FIELD_FREQS, docFields[i], DOCS_AND_FREQS));
doc.add(new IntPoint(FIELD_POINT, 10));
doc.add(new NumericDocValuesField(FIELD_POINT, 10));
doc.add(new NumericDocValuesField("id", i));
doc.add(newField("id", Integer.toString(i), TextField.TYPE_STORED));
writer.addDocument(doc);
}
writer.forceMerge(1);
reader = writer.getReader();
writer.close();
searcher = newSearcher(getOnlyLeafReader(reader));
}
protected String[] docFields = {
"w1 w2 w3 w4 w5",
"w1 w3 w2 w3 zz",
"w1 xx w2 yy w4",
"w1 w2 w1 w4 w2 w3",
"a phrase sentence with many phrase sentence iterations of a phrase sentence",
"nothing matches this document"
};
private void checkMatches(Query q, String field, int[][] expected) throws IOException {
Weight w = searcher.createWeight(searcher.rewrite(q), ScoreMode.COMPLETE, 1);
for (int i = 0; i < expected.length; i++) {
LeafReaderContext ctx = searcher.leafContexts.get(ReaderUtil.subIndex(expected[i][0], searcher.leafContexts));
int doc = expected[i][0] - ctx.docBase;
Matches matches = w.matches(ctx, doc);
if (matches == null) {
assertEquals(expected[i].length, 1);
continue;
}
MatchesIterator it = matches.getMatches(field);
if (expected[i].length == 1) {
assertNull(it);
continue;
}
checkFieldMatches(it, expected[i]);
checkFieldMatches(matches.getMatches(field), expected[i]); // test multiple calls
}
}
private void checkLabelCount(Query q, String field, int[] expected) throws IOException {
Weight w = searcher.createWeight(searcher.rewrite(q), ScoreMode.COMPLETE, 1);
for (int i = 0; i < expected.length; i++) {
LeafReaderContext ctx = searcher.leafContexts.get(ReaderUtil.subIndex(i, searcher.leafContexts));
int doc = i - ctx.docBase;
Matches matches = w.matches(ctx, doc);
if (matches == null) {
assertEquals("Expected to get matches on document " + i, 0, expected[i]);
continue;
}
MatchesIterator it = matches.getMatches(field);
if (expected[i] == 0) {
assertNull(it);
continue;
}
else {
assertNotNull(it);
}
IdentityHashMap<Query, Integer> labels = new IdentityHashMap<>();
while (it.next()) {
labels.put(it.getQuery(), 1);
}
assertEquals(expected[i], labels.size());
}
}
private void checkFieldMatches(MatchesIterator it, int[] expected) throws IOException {
int pos = 1;
while (it.next()) {
//System.out.println(expected[i][pos] + "->" + expected[i][pos + 1] + "[" + expected[i][pos + 2] + "->" + expected[i][pos + 3] + "]");
assertEquals("Wrong start position", expected[pos], it.startPosition());
assertEquals("Wrong end position", expected[pos + 1], it.endPosition());
assertEquals("Wrong start offset", expected[pos + 2], it.startOffset());
assertEquals("Wrong end offset", expected[pos + 3], it.endOffset());
pos += 4;
}
assertEquals(expected.length, pos);
}
private void checkNoPositionsMatches(Query q, String field, boolean[] expected) throws IOException {
Weight w = searcher.createWeight(searcher.rewrite(q), ScoreMode.COMPLETE, 1);
for (int i = 0; i < expected.length; i++) {
LeafReaderContext ctx = searcher.leafContexts.get(ReaderUtil.subIndex(i, searcher.leafContexts));
int doc = i - ctx.docBase;
Matches matches = w.matches(ctx, doc);
if (expected[i]) {
MatchesIterator mi = matches.getMatches(field);
assertNull(mi);
}
else {
assertNull(matches);
}
}
}
private void checkSubMatches(Query q, String[][] expectedNames) throws IOException {
Weight w = searcher.createWeight(searcher.rewrite(q), ScoreMode.COMPLETE_NO_SCORES, 1);
for (int i = 0; i < expectedNames.length; i++) {
LeafReaderContext ctx = searcher.leafContexts.get(ReaderUtil.subIndex(i, searcher.leafContexts));
int doc = i - ctx.docBase;
Matches matches = w.matches(ctx, doc);
if (matches == null) {
assertEquals("Expected to get no matches on document " + i, 0, expectedNames[i].length);
continue;
}
Set<String> expectedQueries = new HashSet<>(Arrays.asList(expectedNames[i]));
Set<String> actualQueries = NamedMatches.findNamedMatches(matches)
.stream().map(NamedMatches::getName).collect(Collectors.toSet());
Set<String> unexpected = new HashSet<>(actualQueries);
unexpected.removeAll(expectedQueries);
assertEquals("Unexpected matching leaf queries: " + unexpected, 0, unexpected.size());
Set<String> missing = new HashSet<>(expectedQueries);
missing.removeAll(actualQueries);
assertEquals("Missing matching leaf queries: " + missing, 0, missing.size());
}
}
private void assertIsLeafMatch(Query q, String field) throws IOException {
Weight w = searcher.createWeight(searcher.rewrite(q), ScoreMode.COMPLETE, 1);
for (int i = 0; i < searcher.reader.maxDoc(); i++) {
LeafReaderContext ctx = searcher.leafContexts.get(ReaderUtil.subIndex(i, searcher.leafContexts));
int doc = i - ctx.docBase;
Matches matches = w.matches(ctx, doc);
if (matches == null) {
return;
}
MatchesIterator mi = matches.getMatches(field);
if (mi == null) {
return;
}
while (mi.next()) {
assertNull(mi.getSubMatches());
}
}
}
private void checkTermMatches(Query q, String field, TermMatch[][][] expected) throws IOException {
Weight w = searcher.createWeight(searcher.rewrite(q), ScoreMode.COMPLETE, 1);
for (int i = 0; i < expected.length; i++) {
LeafReaderContext ctx = searcher.leafContexts.get(ReaderUtil.subIndex(i, searcher.leafContexts));
int doc = i - ctx.docBase;
Matches matches = w.matches(ctx, doc);
if (matches == null) {
assertEquals(expected[i].length, 0);
continue;
}
MatchesIterator it = matches.getMatches(field);
if (expected[i].length == 0) {
assertNull(it);
continue;
}
checkTerms(expected[i], it);
}
}
private void checkTerms(TermMatch[][] expected, MatchesIterator it) throws IOException {
int upTo = 0;
while (it.next()) {
Set<TermMatch> expectedMatches = new HashSet<>(Arrays.asList(expected[upTo]));
MatchesIterator submatches = it.getSubMatches();
while (submatches.next()) {
TermMatch tm = new TermMatch(submatches.startPosition(), submatches.startOffset(), submatches.endOffset());
if (expectedMatches.remove(tm) == false) {
fail("Unexpected term match: " + tm);
}
}
if (expectedMatches.size() != 0) {
fail("Missing term matches: " + expectedMatches.stream().map(Object::toString).collect(Collectors.joining(", ")));
}
upTo++;
}
if (upTo < expected.length - 1) {
fail("Missing expected match");
}
}
static class TermMatch {
public final int position;
public final int startOffset;
public final int endOffset;
public TermMatch(PostingsEnum pe, int position) throws IOException {
this.position = position;
this.startOffset = pe.startOffset();
this.endOffset = pe.endOffset();
}
public TermMatch(int position, int startOffset, int endOffset) {
this.position = position;
this.startOffset = startOffset;
this.endOffset = endOffset;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TermMatch termMatch = (TermMatch) o;
return position == termMatch.position &&
startOffset == termMatch.startOffset &&
endOffset == termMatch.endOffset;
}
@Override
public int hashCode() {
return Objects.hash(position, startOffset, endOffset);
}
@Override
public String toString() {
return position + "[" + startOffset + "->" + endOffset + "]";
}
}
public void testTermQuery() throws IOException {
Term t = new Term(FIELD_WITH_OFFSETS, "w1");
Query q = NamedMatches.wrapQuery("q", new TermQuery(t));
checkMatches(q, FIELD_WITH_OFFSETS, new int[][]{
{ 0, 0, 0, 0, 2 },
{ 1, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 2 },
{ 3, 0, 0, 0, 2, 2, 2, 6, 8 },
{ 4 }
});
checkLabelCount(q, FIELD_WITH_OFFSETS, new int[]{ 1, 1, 1, 1, 0, 0 });
assertIsLeafMatch(q, FIELD_WITH_OFFSETS);
checkSubMatches(q, new String[][]{ {"q"}, {"q"}, {"q"}, {"q"}, {}, {}});
}
public void testTermQueryNoStoredOffsets() throws IOException {
Query q = new TermQuery(new Term(FIELD_NO_OFFSETS, "w1"));
checkMatches(q, FIELD_NO_OFFSETS, new int[][]{
{ 0, 0, 0, -1, -1 },
{ 1, 0, 0, -1, -1 },
{ 2, 0, 0, -1, -1 },
{ 3, 0, 0, -1, -1, 2, 2, -1, -1 },
{ 4 }
});
}
public void testTermQueryNoPositions() throws IOException {
for (String field : new String[]{ FIELD_DOCS_ONLY, FIELD_FREQS }) {
Query q = new TermQuery(new Term(field, "w1"));
checkNoPositionsMatches(q, field, new boolean[]{ true, true, true, true, false });
}
}
public void testDisjunction() throws IOException {
Query w1 = NamedMatches.wrapQuery("w1", new TermQuery(new Term(FIELD_WITH_OFFSETS, "w1")));
Query w3 = NamedMatches.wrapQuery("w3", new TermQuery(new Term(FIELD_WITH_OFFSETS, "w3")));
Query q = new BooleanQuery.Builder()
.add(w1, BooleanClause.Occur.SHOULD)
.add(w3, BooleanClause.Occur.SHOULD)
.build();
checkMatches(q, FIELD_WITH_OFFSETS, new int[][]{
{ 0, 0, 0, 0, 2, 2, 2, 6, 8 },
{ 1, 0, 0, 0, 2, 1, 1, 3, 5, 3, 3, 9, 11 },
{ 2, 0, 0, 0, 2 },
{ 3, 0, 0, 0, 2, 2, 2, 6, 8, 5, 5, 15, 17 },
{ 4 }
});
checkLabelCount(q, FIELD_WITH_OFFSETS, new int[]{ 2, 2, 1, 2, 0, 0 });
assertIsLeafMatch(q, FIELD_WITH_OFFSETS);
checkSubMatches(q, new String[][]{ {"w1", "w3"}, {"w1", "w3"}, {"w1"}, {"w1", "w3"}, {}, {}});
}
public void testDisjunctionNoPositions() throws IOException {
for (String field : new String[]{ FIELD_DOCS_ONLY, FIELD_FREQS }) {
Query q = new BooleanQuery.Builder()
.add(new TermQuery(new Term(field, "w1")), BooleanClause.Occur.SHOULD)
.add(new TermQuery(new Term(field, "w3")), BooleanClause.Occur.SHOULD)
.build();
checkNoPositionsMatches(q, field, new boolean[]{ true, true, true, true, false });
}
}
public void testReqOpt() throws IOException {
Query q = new BooleanQuery.Builder()
.add(new TermQuery(new Term(FIELD_WITH_OFFSETS, "w1")), BooleanClause.Occur.SHOULD)
.add(new TermQuery(new Term(FIELD_WITH_OFFSETS, "w3")), BooleanClause.Occur.MUST)
.build();
checkMatches(q, FIELD_WITH_OFFSETS, new int[][]{
{ 0, 0, 0, 0, 2, 2, 2, 6, 8 },
{ 1, 0, 0, 0, 2, 1, 1, 3, 5, 3, 3, 9, 11 },
{ 2 },
{ 3, 0, 0, 0, 2, 2, 2, 6, 8, 5, 5, 15, 17 },
{ 4 }
});
checkLabelCount(q, FIELD_WITH_OFFSETS, new int[]{ 2, 2, 0, 2, 0, 0 });
}
public void testReqOptNoPositions() throws IOException {
for (String field : new String[]{ FIELD_DOCS_ONLY, FIELD_FREQS }) {
Query q = new BooleanQuery.Builder()
.add(new TermQuery(new Term(field, "w1")), BooleanClause.Occur.SHOULD)
.add(new TermQuery(new Term(field, "w3")), BooleanClause.Occur.MUST)
.build();
checkNoPositionsMatches(q, field, new boolean[]{ true, true, false, true, false });
}
}
public void testMinShouldMatch() throws IOException {
Query w1 = NamedMatches.wrapQuery("w1", new TermQuery(new Term(FIELD_WITH_OFFSETS, "w1")));
Query w3 = NamedMatches.wrapQuery("w3", new TermQuery(new Term(FIELD_WITH_OFFSETS, "w3")));
Query w4 = new TermQuery(new Term(FIELD_WITH_OFFSETS, "w4"));
Query xx = NamedMatches.wrapQuery("xx", new TermQuery(new Term(FIELD_WITH_OFFSETS, "xx")));
Query q = new BooleanQuery.Builder()
.add(w3, BooleanClause.Occur.SHOULD)
.add(new BooleanQuery.Builder()
.add(w1, BooleanClause.Occur.SHOULD)
.add(w4, BooleanClause.Occur.SHOULD)
.add(xx, BooleanClause.Occur.SHOULD)
.setMinimumNumberShouldMatch(2)
.build(), BooleanClause.Occur.SHOULD)
.build();
checkMatches(q, FIELD_WITH_OFFSETS, new int[][]{
{ 0, 0, 0, 0, 2, 2, 2, 6, 8, 3, 3, 9, 11 },
{ 1, 1, 1, 3, 5, 3, 3, 9, 11 },
{ 2, 0, 0, 0, 2, 1, 1, 3, 5, 4, 4, 12, 14 },
{ 3, 0, 0, 0, 2, 2, 2, 6, 8, 3, 3, 9, 11, 5, 5, 15, 17 },
{ 4 }
});
checkLabelCount(q, FIELD_WITH_OFFSETS, new int[]{ 3, 1, 3, 3, 0, 0 });
assertIsLeafMatch(q, FIELD_WITH_OFFSETS);
checkSubMatches(q, new String[][]{ {"w1", "w3"}, {"w3"}, {"w1", "xx"}, {"w1", "w3"}, {}, {}});
}
public void testMinShouldMatchNoPositions() throws IOException {
for (String field : new String[]{ FIELD_FREQS, FIELD_DOCS_ONLY }) {
Query q = new BooleanQuery.Builder()
.add(new TermQuery(new Term(field, "w3")), BooleanClause.Occur.SHOULD)
.add(new BooleanQuery.Builder()
.add(new TermQuery(new Term(field, "w1")), BooleanClause.Occur.SHOULD)
.add(new TermQuery(new Term(field, "w4")), BooleanClause.Occur.SHOULD)
.add(new TermQuery(new Term(field, "xx")), BooleanClause.Occur.SHOULD)
.setMinimumNumberShouldMatch(2)
.build(), BooleanClause.Occur.SHOULD)
.build();
checkNoPositionsMatches(q, field, new boolean[]{ true, true, true, true, false });
}
}
public void testExclusion() throws IOException {
Query q = new BooleanQuery.Builder()
.add(new TermQuery(new Term(FIELD_WITH_OFFSETS, "w3")), BooleanClause.Occur.SHOULD)
.add(new TermQuery(new Term(FIELD_WITH_OFFSETS, "zz")), BooleanClause.Occur.MUST_NOT)
.build();
checkMatches(q, FIELD_WITH_OFFSETS, new int[][]{
{ 0, 2, 2, 6, 8 },
{ 1 },
{ 2 },
{ 3, 5, 5, 15, 17 },
{ 4 }
});
}
public void testExclusionNoPositions() throws IOException {
for (String field : new String[]{ FIELD_FREQS, FIELD_DOCS_ONLY }) {
Query q = new BooleanQuery.Builder()
.add(new TermQuery(new Term(field, "w3")), BooleanClause.Occur.SHOULD)
.add(new TermQuery(new Term(field, "zz")), BooleanClause.Occur.MUST_NOT)
.build();
checkNoPositionsMatches(q, field, new boolean[]{ true, false, false, true, false });
}
}
public void testConjunction() throws IOException {
Query q = new BooleanQuery.Builder()
.add(new TermQuery(new Term(FIELD_WITH_OFFSETS, "w3")), BooleanClause.Occur.MUST)
.add(new TermQuery(new Term(FIELD_WITH_OFFSETS, "w4")), BooleanClause.Occur.MUST)
.build();
checkMatches(q, FIELD_WITH_OFFSETS, new int[][]{
{ 0, 2, 2, 6, 8, 3, 3, 9, 11 },
{ 1 },
{ 2 },
{ 3, 3, 3, 9, 11, 5, 5, 15, 17 },
{ 4 }
});
}
public void testConjunctionNoPositions() throws IOException {
for (String field : new String[]{ FIELD_FREQS, FIELD_DOCS_ONLY }) {
Query q = new BooleanQuery.Builder()
.add(new TermQuery(new Term(field, "w3")), BooleanClause.Occur.MUST)
.add(new TermQuery(new Term(field, "w4")), BooleanClause.Occur.MUST)
.build();
checkNoPositionsMatches(q, field, new boolean[]{ true, false, false, true, false });
}
}
public void testWildcards() throws IOException {
Query q = new PrefixQuery(new Term(FIELD_WITH_OFFSETS, "x"));
checkMatches(q, FIELD_WITH_OFFSETS, new int[][]{
{ 0 },
{ 1 },
{ 2, 1, 1, 3, 5 },
{ 3 },
{ 4 }
});
Query rq = new RegexpQuery(new Term(FIELD_WITH_OFFSETS, "w[1-2]"));
checkMatches(rq, FIELD_WITH_OFFSETS, new int[][]{
{ 0, 0, 0, 0, 2, 1, 1, 3, 5 },
{ 1, 0, 0, 0, 2, 2, 2, 6, 8 },
{ 2, 0, 0, 0, 2, 2, 2, 6, 8 },
{ 3, 0, 0, 0, 2, 1, 1, 3, 5, 2, 2, 6, 8, 4, 4, 12, 14 },
{ 4 }
});
checkLabelCount(rq, FIELD_WITH_OFFSETS, new int[]{ 1, 1, 1, 1, 0 });
assertIsLeafMatch(rq, FIELD_WITH_OFFSETS);
}
public void testNoMatchWildcards() throws IOException {
Query nomatch = new PrefixQuery(new Term(FIELD_WITH_OFFSETS, "wibble"));
Matches matches = searcher.createWeight(searcher.rewrite(nomatch), ScoreMode.COMPLETE_NO_SCORES, 1)
.matches(searcher.leafContexts.get(0), 0);
assertNull(matches);
}
public void testWildcardsNoPositions() throws IOException {
for (String field : new String[]{ FIELD_FREQS, FIELD_DOCS_ONLY }) {
Query q = new PrefixQuery(new Term(field, "x"));
checkNoPositionsMatches(q, field, new boolean[]{ false, false, true, false, false });
}
}
public void testSynonymQuery() throws IOException {
Query q = new SynonymQuery.Builder(FIELD_WITH_OFFSETS)
.addTerm(new Term(FIELD_WITH_OFFSETS, "w1")).addTerm(new Term(FIELD_WITH_OFFSETS, "w2"))
.build();
checkMatches(q, FIELD_WITH_OFFSETS, new int[][]{
{ 0, 0, 0, 0, 2, 1, 1, 3, 5 },
{ 1, 0, 0, 0, 2, 2, 2, 6, 8 },
{ 2, 0, 0, 0, 2, 2, 2, 6, 8 },
{ 3, 0, 0, 0, 2, 1, 1, 3, 5, 2, 2, 6, 8, 4, 4, 12, 14 },
{ 4 }
});
assertIsLeafMatch(q, FIELD_WITH_OFFSETS);
}
public void testSynonymQueryNoPositions() throws IOException {
for (String field : new String[]{ FIELD_FREQS, FIELD_DOCS_ONLY }) {
Query q = new SynonymQuery.Builder(field).addTerm(new Term(field, "w1")).addTerm(new Term(field, "w2")).build();
checkNoPositionsMatches(q, field, new boolean[]{ true, true, true, true, false });
}
}
public void testMultipleFields() throws IOException {
Query q = new BooleanQuery.Builder()
.add(new TermQuery(new Term("id", "1")), BooleanClause.Occur.SHOULD)
.add(new TermQuery(new Term(FIELD_WITH_OFFSETS, "w3")), BooleanClause.Occur.MUST)
.build();
Weight w = searcher.createWeight(searcher.rewrite(q), ScoreMode.COMPLETE, 1);
LeafReaderContext ctx = searcher.leafContexts.get(ReaderUtil.subIndex(1, searcher.leafContexts));
Matches m = w.matches(ctx, 1 - ctx.docBase);
assertNotNull(m);
checkFieldMatches(m.getMatches("id"), new int[]{ -1, 0, 0, -1, -1 });
checkFieldMatches(m.getMatches(FIELD_WITH_OFFSETS), new int[]{ -1, 1, 1, 3, 5, 3, 3, 9, 11 });
assertNull(m.getMatches("bogus"));
Set<String> fields = new HashSet<>();
for (String field : m) {
fields.add(field);
}
assertEquals(2, fields.size());
assertTrue(fields.contains(FIELD_WITH_OFFSETS));
assertTrue(fields.contains("id"));
assertEquals(2, AssertingMatches.unWrap(m).getSubMatches().size());
}
// 0 1 2 3 4 5 6 7
// "a phrase sentence with many phrase sentence iterations of a phrase sentence",
public void testSloppyPhraseQueryWithRepeats() throws IOException {
Term p = new Term(FIELD_WITH_OFFSETS, "phrase");
Term s = new Term(FIELD_WITH_OFFSETS, "sentence");
PhraseQuery pq = new PhraseQuery(10, FIELD_WITH_OFFSETS, "phrase", "sentence", "sentence");
checkMatches(pq, FIELD_WITH_OFFSETS, new int[][]{
{ 0 }, { 1 }, { 2 }, { 3 },
{ 4, 1, 6, 2, 43, 2, 11, 9, 75, 5, 11, 28, 75, 6, 11, 35, 75 }
});
checkLabelCount(pq, FIELD_WITH_OFFSETS, new int[]{ 0, 0, 0, 0, 1 });
assertIsLeafMatch(pq, FIELD_WITH_OFFSETS);
}
public void testSloppyPhraseQuery() throws IOException {
PhraseQuery pq = new PhraseQuery(4, FIELD_WITH_OFFSETS, "a", "sentence");
checkMatches(pq, FIELD_WITH_OFFSETS, new int[][]{
{ 0 }, { 1 }, { 2 }, { 3 },
{ 4, 0, 2, 0, 17, 6, 9, 35, 59, 9, 11, 58, 75 }
});
assertIsLeafMatch(pq, FIELD_WITH_OFFSETS);
}
public void testExactPhraseQuery() throws IOException {
PhraseQuery pq = new PhraseQuery(FIELD_WITH_OFFSETS, "phrase", "sentence");
checkMatches(pq, FIELD_WITH_OFFSETS, new int[][]{
{ 0 }, { 1 }, { 2 }, { 3 },
{ 4, 1, 2, 2, 17, 5, 6, 28, 43, 10, 11, 60, 75 }
});
Term a = new Term(FIELD_WITH_OFFSETS, "a");
Term s = new Term(FIELD_WITH_OFFSETS, "sentence");
PhraseQuery pq2 = new PhraseQuery.Builder()
.add(a)
.add(s, 2)
.build();
checkMatches(pq2, FIELD_WITH_OFFSETS, new int[][]{
{ 0 }, { 1 }, { 2 }, { 3 },
{ 4, 0, 2, 0, 17, 9, 11, 58, 75 }
});
assertIsLeafMatch(pq2, FIELD_WITH_OFFSETS);
}
// 0 1 2 3 4 5 6 7
// "a phrase sentence with many phrase sentence iterations of a phrase sentence",
public void testSloppyMultiPhraseQuery() throws IOException {
Term p = new Term(FIELD_WITH_OFFSETS, "phrase");
Term s = new Term(FIELD_WITH_OFFSETS, "sentence");
Term i = new Term(FIELD_WITH_OFFSETS, "iterations");
MultiPhraseQuery mpq = new MultiPhraseQuery.Builder()
.add(p)
.add(new Term[]{ s, i })
.setSlop(4)
.build();
checkMatches(mpq, FIELD_WITH_OFFSETS, new int[][]{
{ 0 }, { 1 }, { 2 }, { 3 },
{ 4, 1, 2, 2, 17, 5, 6, 28, 43, 5, 7, 28, 54, 10, 11, 60, 75 }
});
assertIsLeafMatch(mpq, FIELD_WITH_OFFSETS);
}
public void testExactMultiPhraseQuery() throws IOException {
MultiPhraseQuery mpq = new MultiPhraseQuery.Builder()
.add(new Term(FIELD_WITH_OFFSETS, "sentence"))
.add(new Term[]{ new Term(FIELD_WITH_OFFSETS, "with"), new Term(FIELD_WITH_OFFSETS, "iterations") })
.build();
checkMatches(mpq, FIELD_WITH_OFFSETS, new int[][]{
{ 0 }, { 1 }, { 2 }, { 3 },
{ 4, 2, 3, 9, 22, 6, 7, 35, 54 }
});
MultiPhraseQuery mpq2 = new MultiPhraseQuery.Builder()
.add(new Term[]{ new Term(FIELD_WITH_OFFSETS, "a"), new Term(FIELD_WITH_OFFSETS, "many")})
.add(new Term(FIELD_WITH_OFFSETS, "phrase"))
.build();
checkMatches(mpq2, FIELD_WITH_OFFSETS, new int[][]{
{ 0 }, { 1 }, { 2 }, { 3 },
{ 4, 0, 1, 0, 8, 4, 5, 23, 34, 9, 10, 58, 66 }
});
assertIsLeafMatch(mpq2, FIELD_WITH_OFFSETS);
}
// 0 1 2 3 4 5 6 7
// "a phrase sentence with many phrase sentence iterations of a phrase sentence",
public void testSpanQuery() throws IOException {
SpanQuery subq = SpanNearQuery.newOrderedNearQuery(FIELD_WITH_OFFSETS)
.addClause(new SpanTermQuery(new Term(FIELD_WITH_OFFSETS, "with")))
.addClause(new SpanTermQuery(new Term(FIELD_WITH_OFFSETS, "many")))
.build();
Query q = SpanNearQuery.newOrderedNearQuery(FIELD_WITH_OFFSETS)
.addClause(new SpanTermQuery(new Term(FIELD_WITH_OFFSETS, "sentence")))
.addClause(new SpanOrQuery(subq, new SpanTermQuery(new Term(FIELD_WITH_OFFSETS, "iterations"))))
.build();
checkMatches(q, FIELD_WITH_OFFSETS, new int[][]{
{ 0 }, { 1 }, { 2 }, { 3 },
{ 4, 2, 4, 9, 27, 6, 7, 35, 54 }
});
checkLabelCount(q, FIELD_WITH_OFFSETS, new int[]{ 0, 0, 0, 0, 1 });
checkTermMatches(q, FIELD_WITH_OFFSETS, new TermMatch[][][]{
{}, {}, {}, {},
{
{
new TermMatch(2, 9, 17),
new TermMatch(3, 18, 22),
new TermMatch(4, 23, 27)
}, {
new TermMatch(6, 35, 43), new TermMatch(7, 44, 54)
}
}
});
}
public void testPointQuery() throws IOException {
IndexOrDocValuesQuery pointQuery = new IndexOrDocValuesQuery(
IntPoint.newExactQuery(FIELD_POINT, 10),
NumericDocValuesField.newSlowExactQuery(FIELD_POINT, 10)
);
Term t = new Term(FIELD_WITH_OFFSETS, "w1");
Query query = new BooleanQuery.Builder()
.add(new TermQuery(t), BooleanClause.Occur.MUST)
.add(pointQuery, BooleanClause.Occur.MUST)
.build();
checkMatches(pointQuery, FIELD_WITH_OFFSETS, new int[][]{});
checkMatches(query, FIELD_WITH_OFFSETS, new int[][]{
{ 0, 0, 0, 0, 2 },
{ 1, 0, 0, 0, 2 },
{ 2, 0, 0, 0, 2 },
{ 3, 0, 0, 0, 2, 2, 2, 6, 8 },
{ 4 }
});
pointQuery = new IndexOrDocValuesQuery(
IntPoint.newExactQuery(FIELD_POINT, 11),
NumericDocValuesField.newSlowExactQuery(FIELD_POINT, 11)
);
query = new BooleanQuery.Builder()
.add(new TermQuery(t), BooleanClause.Occur.MUST)
.add(pointQuery, BooleanClause.Occur.MUST)
.build();
checkMatches(query, FIELD_WITH_OFFSETS, new int[][]{});
query = new BooleanQuery.Builder()
.add(new TermQuery(t), BooleanClause.Occur.MUST)
.add(pointQuery, BooleanClause.Occur.SHOULD)
.build();
checkMatches(query, FIELD_WITH_OFFSETS, new int[][]{
{0, 0, 0, 0, 2},
{1, 0, 0, 0, 2},
{2, 0, 0, 0, 2},
{3, 0, 0, 0, 2, 2, 2, 6, 8},
{4}
});
}
public void testMinimalSeekingWithWildcards() throws IOException {
SeekCountingLeafReader reader = new SeekCountingLeafReader(getOnlyLeafReader(this.reader));
this.searcher = new IndexSearcher(reader);
Query query = new PrefixQuery(new Term(FIELD_WITH_OFFSETS, "w"));
Weight w = searcher.createWeight(query.rewrite(reader), ScoreMode.COMPLETE, 1);
// docs 0-3 match several different terms here, but we only seek to the first term and
// then short-cut return; other terms are ignored until we try and iterate over matches
int[] expectedSeeks = new int[]{ 1, 1, 1, 1, 6, 6 };
int i = 0;
for (LeafReaderContext ctx : reader.leaves()) {
for (int doc = 0; doc < ctx.reader().maxDoc(); doc++) {
reader.seeks = 0;
w.matches(ctx, doc);
assertEquals("Unexpected seek count on doc " + doc, expectedSeeks[i], reader.seeks);
i++;
}
}
}
private static class SeekCountingLeafReader extends FilterLeafReader {
int seeks = 0;
public SeekCountingLeafReader(LeafReader in) {
super(in);
}
@Override
public Terms terms(String field) throws IOException {
Terms terms = super.terms(field);
if (terms == null) {
return null;
}
return new FilterTerms(terms) {
@Override
public TermsEnum iterator() throws IOException {
return new FilterTermsEnum(super.iterator()) {
@Override
public boolean seekExact(BytesRef text) throws IOException {
seeks++;
return super.seekExact(text);
}
};
}
};
}
@Override
public CacheHelper getCoreCacheHelper() {
return null;
}
@Override
public CacheHelper getReaderCacheHelper() {
return null;
}
}
}