diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java index 2b24348ceac..ab3d2d29091 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java @@ -31,6 +31,7 @@ import org.elasticsearch.search.fetch.FetchPhase; import org.elasticsearch.search.fetch.explain.ExplainSearchHitPhase; import org.elasticsearch.search.fetch.matchedfilters.MatchedFiltersSearchHitPhase; import org.elasticsearch.search.fetch.script.ScriptFieldsSearchHitPhase; +import org.elasticsearch.search.fetch.version.VersionSearchHitPhase; import org.elasticsearch.search.highlight.HighlightPhase; import org.elasticsearch.search.query.QueryPhase; @@ -52,6 +53,7 @@ public class SearchModule extends AbstractModule implements SpawnModules { bind(FetchPhase.class).asEagerSingleton(); bind(ExplainSearchHitPhase.class).asEagerSingleton(); bind(ScriptFieldsSearchHitPhase.class).asEagerSingleton(); + bind(VersionSearchHitPhase.class).asEagerSingleton(); bind(MatchedFiltersSearchHitPhase.class).asEagerSingleton(); bind(HighlightPhase.class).asEagerSingleton(); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java index 6a64f947f57..4540a6b1b7b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java @@ -23,10 +23,8 @@ import org.apache.lucene.document.Document; import org.apache.lucene.document.FieldSelector; import org.apache.lucene.document.Fieldable; import org.apache.lucene.index.IndexReader; -import org.apache.lucene.index.Term; import org.elasticsearch.common.collect.ImmutableMap; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.lucene.uid.UidField; import org.elasticsearch.index.mapper.*; import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.SearchParseElement; @@ -34,6 +32,7 @@ import org.elasticsearch.search.SearchPhase; import org.elasticsearch.search.fetch.explain.ExplainSearchHitPhase; import org.elasticsearch.search.fetch.matchedfilters.MatchedFiltersSearchHitPhase; import org.elasticsearch.search.fetch.script.ScriptFieldsSearchHitPhase; +import org.elasticsearch.search.fetch.version.VersionSearchHitPhase; import org.elasticsearch.search.highlight.HighlightPhase; import org.elasticsearch.search.internal.InternalSearchHit; import org.elasticsearch.search.internal.InternalSearchHitField; @@ -53,8 +52,8 @@ public class FetchPhase implements SearchPhase { private final SearchHitPhase[] hitPhases; @Inject public FetchPhase(HighlightPhase highlightPhase, ScriptFieldsSearchHitPhase scriptFieldsPhase, - MatchedFiltersSearchHitPhase matchFiltersPhase, ExplainSearchHitPhase explainPhase) { - this.hitPhases = new SearchHitPhase[]{scriptFieldsPhase, matchFiltersPhase, explainPhase, highlightPhase}; + MatchedFiltersSearchHitPhase matchFiltersPhase, ExplainSearchHitPhase explainPhase, VersionSearchHitPhase versionPhase) { + this.hitPhases = new SearchHitPhase[]{scriptFieldsPhase, matchFiltersPhase, explainPhase, highlightPhase, versionPhase}; } @Override public Map parseElements() { @@ -83,12 +82,8 @@ public class FetchPhase implements SearchPhase { byte[] source = extractSource(doc, documentMapper); // get the version - long version = UidField.loadVersion(context.searcher().getIndexReader(), new Term(UidFieldMapper.NAME, doc.get(UidFieldMapper.NAME))); - if (version < 0) { - version = -1; - } - InternalSearchHit searchHit = new InternalSearchHit(docId, uid.id(), uid.type(), version, source, null); + InternalSearchHit searchHit = new InternalSearchHit(docId, uid.id(), uid.type(), source, null); hits[index] = searchHit; for (Object oField : doc.getFields()) { @@ -134,22 +129,14 @@ public class FetchPhase implements SearchPhase { hitField.values().add(value); } - boolean hitPhaseExecutionRequired = false; + int readerIndex = context.searcher().readerIndex(docId); + IndexReader subReader = context.searcher().subReaders()[readerIndex]; + int subDoc = docId - context.searcher().docStarts()[readerIndex]; for (SearchHitPhase hitPhase : hitPhases) { + SearchHitPhase.HitContext hitContext = new SearchHitPhase.HitContext(); if (hitPhase.executionNeeded(context)) { - hitPhaseExecutionRequired = true; - break; - } - } - - if (hitPhaseExecutionRequired) { - int readerIndex = context.searcher().readerIndex(docId); - IndexReader subReader = context.searcher().subReaders()[readerIndex]; - int subDoc = docId - context.searcher().docStarts()[readerIndex]; - for (SearchHitPhase hitPhase : hitPhases) { - if (hitPhase.executionNeeded(context)) { - hitPhase.execute(context, searchHit, uid, subReader, subDoc); - } + hitContext.reset(searchHit, subReader, subDoc, doc); + hitPhase.execute(context, hitContext); } } } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/SearchHitPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/SearchHitPhase.java index e8b1bb573d3..7ce7ef9c13a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/SearchHitPhase.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/SearchHitPhase.java @@ -19,6 +19,7 @@ package org.elasticsearch.search.fetch; +import org.apache.lucene.document.Document; import org.apache.lucene.index.IndexReader; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.index.mapper.Uid; @@ -33,6 +34,36 @@ import java.util.Map; */ public interface SearchHitPhase { + public static class HitContext { + private InternalSearchHit hit; + private IndexReader reader; + private int docId; + private Document doc; + + public void reset(InternalSearchHit hit, IndexReader reader, int docId, Document doc) { + this.hit = hit; + this.reader = reader; + this.docId = docId; + this.doc = doc; + } + + public InternalSearchHit hit() { + return hit; + } + + public IndexReader reader() { + return reader; + } + + public int docId() { + return docId; + } + + public Document doc() { + return doc; + } + } + Map parseElements(); boolean executionNeeded(SearchContext context); @@ -40,5 +71,5 @@ public interface SearchHitPhase { /** * Executes the hit level phase, with a reader and doc id (note, its a low level reader, and the matching doc). */ - void execute(SearchContext context, InternalSearchHit hit, Uid uid, IndexReader reader, int docId) throws ElasticSearchException; + void execute(SearchContext context, HitContext hitContext) throws ElasticSearchException; } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/explain/ExplainSearchHitPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/explain/ExplainSearchHitPhase.java index e94fcc3eefb..b0aa6ffcffe 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/explain/ExplainSearchHitPhase.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/explain/ExplainSearchHitPhase.java @@ -45,12 +45,12 @@ public class ExplainSearchHitPhase implements SearchHitPhase { return context.explain(); } - @Override public void execute(SearchContext context, InternalSearchHit hit, Uid uid, IndexReader reader, int docId) throws ElasticSearchException { + @Override public void execute(SearchContext context, HitContext hitContext) throws ElasticSearchException { try { // we use the top level doc id, since we work with the top level searcher - hit.explanation(context.searcher().explain(context.query(), hit.docId())); + hitContext.hit().explanation(context.searcher().explain(context.query(), hitContext.hit().docId())); } catch (IOException e) { - throw new FetchPhaseExecutionException(context, "Failed to explain doc [" + docId + "]", e); + throw new FetchPhaseExecutionException(context, "Failed to explain doc [" + hitContext.hit().type() + "#" + hitContext.hit().id() + "]", e); } } } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/matchedfilters/MatchedFiltersSearchHitPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/matchedfilters/MatchedFiltersSearchHitPhase.java index dc257a99eb3..e641a7d209d 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/matchedfilters/MatchedFiltersSearchHitPhase.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/matchedfilters/MatchedFiltersSearchHitPhase.java @@ -49,21 +49,21 @@ public class MatchedFiltersSearchHitPhase implements SearchHitPhase { return !context.parsedQuery().namedFilters().isEmpty(); } - @Override public void execute(SearchContext context, InternalSearchHit hit, Uid uid, IndexReader reader, int docId) throws ElasticSearchException { + @Override public void execute(SearchContext context, HitContext hitContext) throws ElasticSearchException { List matchedFilters = Lists.newArrayListWithCapacity(2); for (Map.Entry entry : context.parsedQuery().namedFilters().entrySet()) { String name = entry.getKey(); Filter filter = entry.getValue(); filter = context.filterCache().cache(filter); try { - DocIdSet docIdSet = filter.getDocIdSet(reader); - if (docIdSet instanceof DocSet && ((DocSet) docIdSet).get(docId)) { + DocIdSet docIdSet = filter.getDocIdSet(hitContext.reader()); + if (docIdSet instanceof DocSet && ((DocSet) docIdSet).get(hitContext.docId())) { matchedFilters.add(name); } } catch (IOException e) { // ignore } } - hit.matchedFilters(matchedFilters.toArray(new String[matchedFilters.size()])); + hitContext.hit().matchedFilters(matchedFilters.toArray(new String[matchedFilters.size()])); } } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/script/ScriptFieldsSearchHitPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/script/ScriptFieldsSearchHitPhase.java index d000d59315b..ced3351c833 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/script/ScriptFieldsSearchHitPhase.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/script/ScriptFieldsSearchHitPhase.java @@ -54,13 +54,13 @@ public class ScriptFieldsSearchHitPhase implements SearchHitPhase { return context.hasScriptFields(); } - @Override public void execute(SearchContext context, InternalSearchHit hit, Uid uid, IndexReader reader, int docId) throws ElasticSearchException { + @Override public void execute(SearchContext context, HitContext hitContext) throws ElasticSearchException { for (ScriptFieldsContext.ScriptField scriptField : context.scriptFields().fields()) { - scriptField.script().setNextReader(reader); + scriptField.script().setNextReader(hitContext.reader()); Object value; try { - value = scriptField.script().execute(docId); + value = scriptField.script().execute(hitContext.docId()); } catch (RuntimeException e) { if (scriptField.ignoreException()) { continue; @@ -68,14 +68,14 @@ public class ScriptFieldsSearchHitPhase implements SearchHitPhase { throw e; } - if (hit.fieldsOrNull() == null) { - hit.fields(new HashMap(2)); + if (hitContext.hit().fieldsOrNull() == null) { + hitContext.hit().fields(new HashMap(2)); } - SearchHitField hitField = hit.fields().get(scriptField.name()); + SearchHitField hitField = hitContext.hit().fields().get(scriptField.name()); if (hitField == null) { hitField = new InternalSearchHitField(scriptField.name(), new ArrayList(2)); - hit.fields().put(scriptField.name(), hitField); + hitContext.hit().fields().put(scriptField.name(), hitField); } hitField.values().add(value); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/version/VersionSearchHitPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/version/VersionSearchHitPhase.java new file mode 100644 index 00000000000..0ac08bcbdd7 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/version/VersionSearchHitPhase.java @@ -0,0 +1,53 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.search.fetch.version; + +import org.apache.lucene.index.Term; +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.common.collect.ImmutableMap; +import org.elasticsearch.common.lucene.uid.UidField; +import org.elasticsearch.index.mapper.UidFieldMapper; +import org.elasticsearch.search.SearchParseElement; +import org.elasticsearch.search.fetch.SearchHitPhase; +import org.elasticsearch.search.internal.SearchContext; + +import java.util.Map; + +/** + * @author kimchy (shay.banon) + */ +public class VersionSearchHitPhase implements SearchHitPhase { + + @Override public Map parseElements() { + return ImmutableMap.of(); + } + + @Override public boolean executionNeeded(SearchContext context) { + return true; + } + + @Override public void execute(SearchContext context, HitContext hitContext) throws ElasticSearchException { + long version = UidField.loadVersion(hitContext.reader(), new Term(UidFieldMapper.NAME, hitContext.doc().get(UidFieldMapper.NAME))); + if (version < 0) { + version = -1; + } + hitContext.hit().version(version); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java index 165f97312e7..8fc0fbcdeee 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java @@ -66,9 +66,9 @@ public class HighlightPhase implements SearchHitPhase { return context.highlight() != null; } - @Override public void execute(SearchContext context, InternalSearchHit hit, Uid uid, IndexReader reader, int docId) throws ElasticSearchException { + @Override public void execute(SearchContext context, HitContext hitContext) throws ElasticSearchException { try { - DocumentMapper documentMapper = context.mapperService().documentMapper(hit.type()); + DocumentMapper documentMapper = context.mapperService().documentMapper(hitContext.hit().type()); Map highlightFields = newHashMap(); for (SearchContextHighlight.Field field : context.highlight().fields()) { @@ -104,7 +104,7 @@ public class HighlightPhase implements SearchHitPhase { List textsToHighlight; if (mapper.stored()) { try { - Document doc = reader.document(docId, new SingleFieldSelector(mapper.names().indexName())); + Document doc = hitContext.reader().document(hitContext.docId(), new SingleFieldSelector(mapper.names().indexName())); textsToHighlight = new ArrayList(doc.getFields().size()); for (Fieldable docField : doc.getFields()) { if (docField.stringValue() != null) { @@ -116,8 +116,8 @@ public class HighlightPhase implements SearchHitPhase { } } else { SearchLookup lookup = context.lookup(); - lookup.setNextReader(reader); - lookup.setNextDocId(docId); + lookup.setNextReader(hitContext.reader()); + lookup.setNextDocId(hitContext.docId()); textsToHighlight = lookup.source().getValues(mapper.names().fullName()); } @@ -125,7 +125,7 @@ public class HighlightPhase implements SearchHitPhase { try { for (Object textToHighlight : textsToHighlight) { String text = textToHighlight.toString(); - Analyzer analyzer = context.mapperService().documentMapper(hit.type()).mappers().indexAnalyzer(); + Analyzer analyzer = context.mapperService().documentMapper(hitContext.hit().type()).mappers().indexAnalyzer(); TokenStream tokenStream = analyzer.reusableTokenStream(mapper.names().indexName(), new FastStringReader(text)); TextFragment[] bestTextFragments = highlighter.getBestTextFragments(tokenStream, text, false, field.numberOfFragments()); Collections.addAll(fragsList, bestTextFragments); @@ -149,13 +149,13 @@ public class HighlightPhase implements SearchHitPhase { highlightFields.put(highlightField.name(), highlightField); } else { FastVectorHighlighter highlighter = buildHighlighter(context, mapper, field); - FieldQuery fieldQuery = buildFieldQuery(highlighter, context.query(), reader, field); + FieldQuery fieldQuery = buildFieldQuery(highlighter, context.query(), hitContext.reader(), field); String[] fragments; try { // a HACK to make highlighter do highlighting, even though its using the single frag list builder int numberOfFragments = field.numberOfFragments() == 0 ? 1 : field.numberOfFragments(); - fragments = highlighter.getBestFragments(fieldQuery, reader, docId, mapper.names().indexName(), field.fragmentCharSize(), numberOfFragments); + fragments = highlighter.getBestFragments(fieldQuery, hitContext.reader(), hitContext.docId(), mapper.names().indexName(), field.fragmentCharSize(), numberOfFragments); } catch (IOException e) { throw new FetchPhaseExecutionException(context, "Failed to highlight field [" + field.field() + "]", e); } @@ -164,7 +164,7 @@ public class HighlightPhase implements SearchHitPhase { } } - hit.highlightFields(highlightFields); + hitContext.hit().highlightFields(highlightFields); } finally { CustomFieldQuery.reader.remove(); CustomFieldQuery.highlightFilters.remove(); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/InternalSearchHit.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/InternalSearchHit.java index 7bfd891b970..5fae4fddf90 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/InternalSearchHit.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/InternalSearchHit.java @@ -84,11 +84,10 @@ public class InternalSearchHit implements SearchHit { } - public InternalSearchHit(int docId, String id, String type, long version, byte[] source, Map fields) { + public InternalSearchHit(int docId, String id, String type, byte[] source, Map fields) { this.docId = docId; this.id = id; this.type = type; - this.version = version; this.source = source; this.fields = fields; } @@ -113,6 +112,10 @@ public class InternalSearchHit implements SearchHit { return score(); } + public void version(long version) { + this.version = version; + } + @Override public long version() { return this.version; } diff --git a/modules/test/integration/src/test/java/org/elasticsearch/test/integration/versioning/SimpleVersioningTests.java b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/versioning/SimpleVersioningTests.java index 5ced4ec8723..dca6e165455 100644 --- a/modules/test/integration/src/test/java/org/elasticsearch/test/integration/versioning/SimpleVersioningTests.java +++ b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/versioning/SimpleVersioningTests.java @@ -106,9 +106,13 @@ public class SimpleVersioningTests extends AbstractNodesTests { } client.admin().indices().prepareRefresh().execute().actionGet(); - assertThat(client.prepareGet("test", "type", "1").execute().actionGet().version(), equalTo(2l)); + for (int i = 0; i < 10; i++) { + assertThat(client.prepareGet("test", "type", "1").execute().actionGet().version(), equalTo(2l)); + } - SearchResponse searchResponse = client.prepareSearch().setQuery(matchAllQuery()).execute().actionGet(); - assertThat(searchResponse.hits().getAt(0).version(), equalTo(2l)); + for (int i = 0; i < 10; i++) { + SearchResponse searchResponse = client.prepareSearch().setQuery(matchAllQuery()).execute().actionGet(); + assertThat(searchResponse.hits().getAt(0).version(), equalTo(2l)); + } } }