# REST Suggester API

The REST Suggester API binds the 'Suggest API' to the REST Layer directly. Hence there is no need to touch the query layer for requesting suggestions.
This API extracts the Phrase Suggester API and makes 'suggestion request' top-level objects in suggestion requests. The complete API can be found in the
underlying ["Suggest Feature API"](http://www.elasticsearch.org/guide/reference/api/search/suggest.html).

# API Example
The following examples show how Suggest Actions work on the REST layer. According to this a simple request and its response will be shown.

## Suggestion Request
```json
curl -XPOST 'localhost:9200/_suggest?pretty=true' -d '{
    "text" : "Xor the Got-Jewel",
    "simple_phrase" : {
        "phrase" : {
            "analyzer" : "bigram",
            "field" : "bigram",
            "size" : 1,
            "real_word_error_likelihood" : 0.95,
            "max_errors" : 0.5,
            "gram_size" : 2
        }
    }
}'
```
This example shows how to query a suggestion for the global text 'Xor the Got-Jewel'. A 'simple phrase' suggestion is requested and
a 'direct generator' is configured to generate the candidates.

## Suggestion Response
On success the request above will reply with a response like the following:
```json
{
    "simple_phrase" : [ {
        "text" : "Xor the Got-Jewel",
        "offset" : 0,
        "length" : 17,
        "options" : [ {
            "text" : "xorr the the got got jewel",
            "score" : 3.5283546E-4
        } ]
    } ]
}
```
The 'suggest'-response contains a single 'simple phrase' which contains an 'option' in turn. This option represents a suggestion of the
queried text. It contains the corrected text and a score indicating the probability of this option to be meant.

Closes #2774
This commit is contained in:
Florian Schilling 2013-03-11 17:39:18 +01:00 committed by Simon Willnauer
parent a127f2d2e8
commit 25bd9cecd0
36 changed files with 1476 additions and 555 deletions

View File

@ -112,6 +112,8 @@ import org.elasticsearch.action.percolate.PercolateAction;
import org.elasticsearch.action.percolate.TransportPercolateAction;
import org.elasticsearch.action.search.*;
import org.elasticsearch.action.search.type.*;
import org.elasticsearch.action.suggest.SuggestAction;
import org.elasticsearch.action.suggest.TransportSuggestAction;
import org.elasticsearch.action.support.TransportAction;
import org.elasticsearch.action.update.TransportUpdateAction;
import org.elasticsearch.action.update.UpdateAction;
@ -205,6 +207,7 @@ public class ActionModule extends AbstractModule {
registerAction(DeleteAction.INSTANCE, TransportDeleteAction.class,
TransportIndexDeleteAction.class, TransportShardDeleteAction.class);
registerAction(CountAction.INSTANCE, TransportCountAction.class);
registerAction(SuggestAction.INSTANCE, TransportSuggestAction.class);
registerAction(UpdateAction.INSTANCE, TransportUpdateAction.class);
registerAction(MultiGetAction.INSTANCE, TransportMultiGetAction.class,
TransportShardMultiGetAction.class);

View File

@ -0,0 +1,59 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.action.suggest;
import java.io.IOException;
import org.elasticsearch.action.support.broadcast.BroadcastShardOperationRequest;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
/**
* Internal suggest request executed directly against a specific index shard.
*/
final class ShardSuggestRequest extends BroadcastShardOperationRequest {
private BytesReference suggestSource;
ShardSuggestRequest() {
}
public ShardSuggestRequest(String index, int shardId, SuggestRequest request) {
super(index, shardId, request);
this.suggestSource = request.suggest();
}
public BytesReference suggest() {
return suggestSource;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
suggestSource = in.readBytesReference();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeBytesReference(suggestSource);
}
}

View File

@ -0,0 +1,60 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.action.suggest;
import org.elasticsearch.action.support.broadcast.BroadcastShardOperationResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.search.suggest.Suggest;
import java.io.IOException;
/**
* Internal suggest response of a shard suggest request executed directly against a specific shard.
*/
class ShardSuggestResponse extends BroadcastShardOperationResponse {
private final Suggest suggest;
ShardSuggestResponse() {
this.suggest = new Suggest();
}
public ShardSuggestResponse(String index, int shardId, Suggest suggest) {
super(index, shardId);
this.suggest = suggest;
}
public Suggest getSuggest() {
return this.suggest;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
suggest.readFrom(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
suggest.writeTo(out);
}
}

View File

@ -0,0 +1,46 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.action.suggest;
import org.elasticsearch.action.Action;
import org.elasticsearch.client.Client;
import org.elasticsearch.search.suggest.Suggest;
/**
*/
public class SuggestAction extends Action<SuggestRequest, SuggestResponse, SuggestRequestBuilder> {
public static final SuggestAction INSTANCE = new SuggestAction();
public static final String NAME = "suggest";
private SuggestAction() {
super(NAME);
}
@Override
public SuggestResponse newResponse() {
return new SuggestResponse(new Suggest());
}
@Override
public SuggestRequestBuilder newRequestBuilder(Client client) {
return new SuggestRequestBuilder(client);
}
}

View File

@ -0,0 +1,168 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.action.suggest;
import java.io.IOException;
import java.util.Arrays;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.broadcast.BroadcastOperationRequest;
import org.elasticsearch.client.Requests;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentType;
/**
* A request to get suggestions for corrections of phrases. Best created with
* {@link org.elasticsearch.client.Requests#suggestRequest(String...)}.
* <p/>
* <p>The request requires the query source to be set either using {@link #query(org.elasticsearch.index.query.QueryBuilder)},
* or {@link #query(byte[])}.
*
* @see SuggestResponse
* @see org.elasticsearch.client.Client#suggest(SuggestRequest)
* @see org.elasticsearch.client.Requests#suggestRequest(String...)
*/
public final class SuggestRequest extends BroadcastOperationRequest<SuggestRequest> {
static final XContentType contentType = Requests.CONTENT_TYPE;
@Nullable
private String routing;
@Nullable
private String preference;
private BytesReference suggestSource;
private boolean suggestSourceUnsafe;
SuggestRequest() {
}
/**
* Constructs a new suggest request against the provided indices. No indices provided means it will
* run against all indices.
*/
public SuggestRequest(String... indices) {
super(indices);
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = super.validate();
return validationException;
}
@Override
protected void beforeStart() {
if (suggestSourceUnsafe) {
suggest(suggestSource.copyBytesArray(), false);
}
}
/**
* The Phrase to get correction suggestions for
*/
BytesReference suggest() {
return suggestSource;
}
/**
* set a new source for the suggest query
*/
public SuggestRequest suggest(BytesReference suggestSource) {
return suggest(suggestSource, false);
}
/**
* A comma separated list of routing values to control the shards the search will be executed on.
*/
public String routing() {
return this.routing;
}
/**
* A comma separated list of routing values to control the shards the search will be executed on.
*/
public SuggestRequest routing(String routing) {
this.routing = routing;
return this;
}
/**
* The routing values to control the shards that the search will be executed on.
*/
public SuggestRequest routing(String... routings) {
this.routing = Strings.arrayToCommaDelimitedString(routings);
return this;
}
public SuggestRequest preference(String preference) {
this.preference = preference;
return this;
}
public String preference() {
return this.preference;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
routing = in.readOptionalString();
preference = in.readOptionalString();
suggest(in.readBytesReference());
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeOptionalString(routing);
out.writeOptionalString(preference);
out.writeBytesReference(suggestSource);
}
@Override
public String toString() {
String sSource = "_na_";
try {
sSource = XContentHelper.convertToJson(suggestSource, false);
} catch (Exception e) {
// ignore
}
return "[" + Arrays.toString(indices) + "]" + ", suggestSource[" + sSource + "]";
}
public SuggestRequest suggest(BytesReference suggestSource, boolean contentUnsafe) {
this.suggestSource = suggestSource;
this.suggestSourceUnsafe = contentUnsafe;
return this;
}
public SuggestRequest suggest(String source) {
return suggest(new BytesArray(source));
}
}

View File

@ -0,0 +1,99 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.action.suggest;
import java.io.IOException;
import org.elasticsearch.ElasticSearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.broadcast.BroadcastOperationRequestBuilder;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.internal.InternalClient;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder;
/**
* A suggest action request builder.
*/
public class SuggestRequestBuilder extends BroadcastOperationRequestBuilder<SuggestRequest, SuggestResponse, SuggestRequestBuilder> {
final SuggestBuilder suggest = new SuggestBuilder();
public SuggestRequestBuilder(Client client) {
super((InternalClient) client, new SuggestRequest());
}
/**
* Add a definition for suggestions to the request
*/
public <T> SuggestRequestBuilder addSuggestion(SuggestionBuilder<T> suggestion) {
suggest.addSuggestion(suggestion);
return this;
}
/**
* A comma separated list of routing values to control the shards the search will be executed on.
*/
public SuggestRequestBuilder setRouting(String routing) {
request.routing(routing);
return this;
}
public SuggestRequestBuilder setSuggestText(String globalText) {
this.suggest.setText(globalText);
return this;
}
/**
* Sets the preference to execute the search. Defaults to randomize across shards. Can be set to
* <tt>_local</tt> to prefer local shards, <tt>_primary</tt> to execute only on primary shards,
* _shards:x,y to operate on shards x & y, or a custom value, which guarantees that the same order
* will be used across different requests.
*/
public SuggestRequestBuilder setPreference(String preference) {
request.preference(preference);
return this;
}
/**
* The routing values to control the shards that the search will be executed on.
*/
public SuggestRequestBuilder setRouting(String... routing) {
request.routing(routing);
return this;
}
@Override
protected void doExecute(ActionListener<SuggestResponse> listener) {
try {
XContentBuilder builder = XContentFactory.contentBuilder(SuggestRequest.contentType);
XContentBuilder content = suggest.toXContent(builder, ToXContent.EMPTY_PARAMS);
content.close();
request.suggest(content.bytes());
} catch (IOException e) {
throw new ElasticSearchException("Unable to build suggestion request", e);
}
((InternalClient) client).suggest(request, listener);
}
}

View File

@ -0,0 +1,83 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.action.suggest;
import java.io.IOException;
import java.util.List;
import org.elasticsearch.action.ShardOperationFailedException;
import org.elasticsearch.action.support.broadcast.BroadcastOperationResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.search.suggest.Suggest;
/**
* The response of the suggest action.
*/
public final class SuggestResponse extends BroadcastOperationResponse {
private final Suggest suggest;
SuggestResponse(Suggest suggest) {
this.suggest = suggest;
}
SuggestResponse(Suggest suggest, int totalShards, int successfulShards, int failedShards, List<ShardOperationFailedException> shardFailures) {
super(totalShards, successfulShards, failedShards, shardFailures);
this.suggest = suggest;
}
/**
* The Suggestions of the phrase.
*/
public Suggest getSuggest() {
return suggest;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
this.suggest.readFrom(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
this.suggest.writeTo(out);
}
@Override
public String toString() {
String source;
try {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
suggest.toXContent(builder, null);
source = XContentHelper.convertToJson(builder.bytes(), true);
} catch (IOException e) {
source = "Error: " + e.getMessage();
}
return "Suggest Response["+source+"]";
}
}

View File

@ -0,0 +1,176 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.action.suggest;
import static com.google.common.collect.Lists.newArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReferenceArray;
import org.elasticsearch.ElasticSearchException;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.action.ShardOperationFailedException;
import org.elasticsearch.action.support.DefaultShardOperationFailedException;
import org.elasticsearch.action.support.broadcast.BroadcastShardOperationFailedException;
import org.elasticsearch.action.support.broadcast.TransportBroadcastOperationAction;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.routing.GroupShardsIterator;
import org.elasticsearch.cluster.routing.ShardRouting;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.engine.Engine;
import org.elasticsearch.index.service.IndexService;
import org.elasticsearch.index.shard.service.IndexShard;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestParseElement;
import org.elasticsearch.search.suggest.SuggestPhase;
import org.elasticsearch.search.suggest.SuggestionSearchContext;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
/**
* Defines the transport of a suggestion request across the cluster
*/
public class TransportSuggestAction extends TransportBroadcastOperationAction<SuggestRequest, SuggestResponse, ShardSuggestRequest, ShardSuggestResponse> {
private final IndicesService indicesService;
private final SuggestPhase suggestPhase;
@Inject
public TransportSuggestAction(Settings settings, ThreadPool threadPool, ClusterService clusterService, TransportService transportService,
IndicesService indicesService) {
super(settings, threadPool, clusterService, transportService);
this.indicesService = indicesService;
this.suggestPhase = new SuggestPhase(settings);
}
@Override
protected String executor() {
return ThreadPool.Names.SEARCH;
}
@Override
protected String transportAction() {
return SuggestAction.NAME;
}
@Override
protected SuggestRequest newRequest() {
return new SuggestRequest();
}
@Override
protected ShardSuggestRequest newShardRequest() {
return new ShardSuggestRequest();
}
@Override
protected ShardSuggestRequest newShardRequest(ShardRouting shard, SuggestRequest request) {
return new ShardSuggestRequest(shard.index(), shard.id(), request);
}
@Override
protected ShardSuggestResponse newShardResponse() {
return new ShardSuggestResponse();
}
@Override
protected GroupShardsIterator shards(ClusterState clusterState, SuggestRequest request, String[] concreteIndices) {
Map<String, Set<String>> routingMap = clusterState.metaData().resolveSearchRouting(request.routing(), request.indices());
return clusterService.operationRouting().searchShards(clusterState, request.indices(), concreteIndices, routingMap, request.preference());
}
@Override
protected ClusterBlockException checkGlobalBlock(ClusterState state, SuggestRequest request) {
return state.blocks().globalBlockedException(ClusterBlockLevel.READ);
}
@Override
protected ClusterBlockException checkRequestBlock(ClusterState state, SuggestRequest countRequest, String[] concreteIndices) {
return state.blocks().indicesBlockedException(ClusterBlockLevel.READ, concreteIndices);
}
@Override
protected SuggestResponse newResponse(SuggestRequest request, AtomicReferenceArray shardsResponses, ClusterState clusterState) {
int successfulShards = 0;
int failedShards = 0;
final Map<String, List<Suggest.Suggestion>> groupedSuggestions = new HashMap<String, List<Suggest.Suggestion>>();
List<ShardOperationFailedException> shardFailures = null;
for (int i = 0; i < shardsResponses.length(); i++) {
Object shardResponse = shardsResponses.get(i);
if (shardResponse == null) {
failedShards++;
} else if (shardResponse instanceof BroadcastShardOperationFailedException) {
failedShards++;
if (shardFailures == null) {
shardFailures = newArrayList();
}
shardFailures.add(new DefaultShardOperationFailedException((BroadcastShardOperationFailedException) shardResponse));
} else {
Suggest suggest = ((ShardSuggestResponse) shardResponse).getSuggest();
Suggest.group(groupedSuggestions, suggest);
successfulShards++;
}
}
return new SuggestResponse(new Suggest(Suggest.reduce(groupedSuggestions)), shardsResponses.length(), successfulShards, failedShards, shardFailures);
}
@Override
protected ShardSuggestResponse shardOperation(ShardSuggestRequest request) throws ElasticSearchException {
IndexService indexService = indicesService.indexServiceSafe(request.index());
IndexShard indexShard = indexService.shardSafe(request.shardId());
final Engine.Searcher searcher = indexShard.searcher();
try {
BytesReference suggest = request.suggest();
if (suggest != null && suggest.length() > 0) {
final SuggestParseElement element = new SuggestParseElement();
final XContentParser parser = XContentFactory.xContent(suggest).createParser(suggest);
if(parser.nextToken() != XContentParser.Token.START_OBJECT) {
throw new ElasticSearchIllegalArgumentException("Object expected");
}
final SuggestionSearchContext context = element.parseInternal(parser, indexService.mapperService());
final Suggest result = suggestPhase.execute(context, searcher.reader());
return new ShardSuggestResponse(request.index(), request.shardId(), result);
}
return new ShardSuggestResponse(request.index(), request.shardId(), new Suggest());
} catch(Throwable ex) {
throw new ElasticSearchException("Failed to execute suggest", ex);
} finally {
searcher.release();
}
}
}

View File

@ -0,0 +1,23 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.
*/
/**
* Suggest action.
*/
package org.elasticsearch.action.suggest;

View File

@ -19,27 +19,25 @@
package org.elasticsearch.action.support.broadcast;
import com.google.common.collect.ImmutableList;
import static org.elasticsearch.action.support.DefaultShardOperationFailedException.readShardOperationFailed;
import java.io.IOException;
import java.util.List;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.ShardOperationFailedException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.action.support.DefaultShardOperationFailedException.readShardOperationFailed;
/**
* Base class for all broadcast operation based responses.
*/
public abstract class BroadcastOperationResponse extends ActionResponse {
private static final ShardOperationFailedException[] EMPTY = new ShardOperationFailedException[0];
private int totalShards;
private int successfulShards;
private int failedShards;
private List<ShardOperationFailedException> shardFailures = ImmutableList.of();
private ShardOperationFailedException[] shardFailures = EMPTY;
protected BroadcastOperationResponse() {
}
@ -48,10 +46,7 @@ public abstract class BroadcastOperationResponse extends ActionResponse {
this.totalShards = totalShards;
this.successfulShards = successfulShards;
this.failedShards = failedShards;
this.shardFailures = shardFailures;
if (shardFailures == null) {
this.shardFailures = ImmutableList.of();
}
this.shardFailures = shardFailures == null ? EMPTY : shardFailures.toArray(new ShardOperationFailedException[shardFailures.size()]);
}
/**
@ -78,10 +73,7 @@ public abstract class BroadcastOperationResponse extends ActionResponse {
/**
* The list of shard failures exception.
*/
public List<? extends ShardOperationFailedException> getShardFailures() {
if (shardFailures == null) {
return ImmutableList.of();
}
public ShardOperationFailedException[] getShardFailures() {
return shardFailures;
}
@ -93,9 +85,9 @@ public abstract class BroadcastOperationResponse extends ActionResponse {
failedShards = in.readVInt();
int size = in.readVInt();
if (size > 0) {
shardFailures = new ArrayList<ShardOperationFailedException>(size);
shardFailures = new ShardOperationFailedException[size];
for (int i = 0; i < size; i++) {
shardFailures.add(readShardOperationFailed(in));
shardFailures[i] = readShardOperationFailed(in);
}
}
}
@ -106,7 +98,7 @@ public abstract class BroadcastOperationResponse extends ActionResponse {
out.writeVInt(totalShards);
out.writeVInt(successfulShards);
out.writeVInt(failedShards);
out.writeVInt(shardFailures.size());
out.writeVInt(shardFailures.length);
for (ShardOperationFailedException exp : shardFailures) {
exp.writeTo(out);
}

View File

@ -45,6 +45,9 @@ import org.elasticsearch.action.percolate.PercolateRequest;
import org.elasticsearch.action.percolate.PercolateRequestBuilder;
import org.elasticsearch.action.percolate.PercolateResponse;
import org.elasticsearch.action.search.*;
import org.elasticsearch.action.suggest.SuggestRequest;
import org.elasticsearch.action.suggest.SuggestRequestBuilder;
import org.elasticsearch.action.suggest.SuggestResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.action.update.UpdateResponse;
@ -331,6 +334,29 @@ public interface Client {
*/
CountRequestBuilder prepareCount(String... indices);
/**
* Suggestion matching a specific phrase.
*
* @param request The suggest request
* @return The result future
* @see Requests#suggestRequest(String...)
*/
ActionFuture<SuggestResponse> suggest(SuggestRequest request);
/**
* Suggestions matching a specific phrase.
*
* @param request The suggest request
* @param listener A listener to be notified of the result
* @see Requests#suggestRequest(String...)
*/
void suggest(SuggestRequest request, ActionListener<SuggestResponse> listener);
/**
* Suggestions matching a specific phrase.
*/
SuggestRequestBuilder prepareSuggest(String... indices);
/**
* Search across one or more indices and one or more types with a query.
*

View File

@ -53,6 +53,10 @@ import org.elasticsearch.action.percolate.PercolateRequest;
import org.elasticsearch.action.percolate.PercolateRequestBuilder;
import org.elasticsearch.action.percolate.PercolateResponse;
import org.elasticsearch.action.search.*;
import org.elasticsearch.action.suggest.SuggestAction;
import org.elasticsearch.action.suggest.SuggestRequest;
import org.elasticsearch.action.suggest.SuggestRequestBuilder;
import org.elasticsearch.action.suggest.SuggestResponse;
import org.elasticsearch.action.update.UpdateAction;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateRequestBuilder;
@ -260,6 +264,21 @@ public abstract class AbstractClient implements InternalClient {
return new CountRequestBuilder(this).setIndices(indices);
}
@Override
public ActionFuture<SuggestResponse> suggest(final SuggestRequest request) {
return execute(SuggestAction.INSTANCE, request);
}
@Override
public void suggest(final SuggestRequest request, final ActionListener<SuggestResponse> listener) {
execute(SuggestAction.INSTANCE, request, listener);
}
@Override
public SuggestRequestBuilder prepareSuggest(String... indices) {
return new SuggestRequestBuilder(this).setIndices(indices);
}
@Override
public ActionFuture<SearchResponse> moreLikeThis(final MoreLikeThisRequest request) {
return execute(MoreLikeThisAction.INSTANCE, request);

View File

@ -42,6 +42,8 @@ import org.elasticsearch.action.mlt.MoreLikeThisRequest;
import org.elasticsearch.action.percolate.PercolateRequest;
import org.elasticsearch.action.percolate.PercolateResponse;
import org.elasticsearch.action.search.*;
import org.elasticsearch.action.suggest.SuggestRequest;
import org.elasticsearch.action.suggest.SuggestResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.AdminClient;
@ -378,6 +380,16 @@ public class TransportClient extends AbstractClient {
internalClient.count(request, listener);
}
@Override
public ActionFuture<SuggestResponse> suggest(SuggestRequest request) {
return internalClient.suggest(request);
}
@Override
public void suggest(SuggestRequest request, ActionListener<SuggestResponse> listener) {
internalClient.suggest(request, listener);
}
@Override
public ActionFuture<SearchResponse> search(SearchRequest request) {
return internalClient.search(request);

View File

@ -77,6 +77,7 @@ import org.elasticsearch.rest.action.percolate.RestPercolateAction;
import org.elasticsearch.rest.action.search.RestMultiSearchAction;
import org.elasticsearch.rest.action.search.RestSearchAction;
import org.elasticsearch.rest.action.search.RestSearchScrollAction;
import org.elasticsearch.rest.action.suggest.RestSuggestAction;
import org.elasticsearch.rest.action.update.RestUpdateAction;
import java.util.List;
@ -153,6 +154,7 @@ public class RestActionModule extends AbstractModule {
bind(RestDeleteAction.class).asEagerSingleton();
bind(RestDeleteByQueryAction.class).asEagerSingleton();
bind(RestCountAction.class).asEagerSingleton();
bind(RestSuggestAction.class).asEagerSingleton();
bind(RestBulkAction.class).asEagerSingleton();
bind(RestUpdateAction.class).asEagerSingleton();
bind(RestPercolateAction.class).asEagerSingleton();

View File

@ -0,0 +1,131 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.rest.action.suggest;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.POST;
import static org.elasticsearch.rest.RestStatus.BAD_REQUEST;
import static org.elasticsearch.rest.RestStatus.OK;
import static org.elasticsearch.rest.action.support.RestActions.buildBroadcastShardsHeader;
import java.io.IOException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.suggest.SuggestRequest;
import org.elasticsearch.action.suggest.SuggestResponse;
import org.elasticsearch.action.support.IgnoreIndices;
import org.elasticsearch.action.support.broadcast.BroadcastOperationThreading;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.XContentRestResponse;
import org.elasticsearch.rest.XContentThrowableRestResponse;
import org.elasticsearch.rest.action.support.RestActions;
import org.elasticsearch.rest.action.support.RestXContentBuilder;
import org.elasticsearch.search.suggest.Suggest;
/**
*
*/
public class RestSuggestAction extends BaseRestHandler {
@Inject
public RestSuggestAction(Settings settings, Client client, RestController controller) {
super(settings, client);
controller.registerHandler(POST, "/_suggest", this);
controller.registerHandler(GET, "/_suggest", this);
controller.registerHandler(POST, "/{index}/_suggest", this);
controller.registerHandler(GET, "/{index}/_suggest", this);
}
@Override
public void handleRequest(final RestRequest request, final RestChannel channel) {
SuggestRequest suggestRequest = new SuggestRequest(RestActions.splitIndices(request.param("index")));
if (request.hasParam("ignore_indices")) {
suggestRequest.ignoreIndices(IgnoreIndices.fromString(request.param("ignore_indices")));
}
suggestRequest.listenerThreaded(false);
try {
BroadcastOperationThreading operationThreading = BroadcastOperationThreading.fromString(request.param("operation_threading"), BroadcastOperationThreading.SINGLE_THREAD);
if (operationThreading == BroadcastOperationThreading.NO_THREADS) {
// since we don't spawn, don't allow no_threads, but change it to a single thread
operationThreading = BroadcastOperationThreading.SINGLE_THREAD;
}
suggestRequest.operationThreading(operationThreading);
if (request.hasContent()) {
suggestRequest.suggest(request.content(), request.contentUnsafe());
} else {
String source = request.param("source");
if (source != null) {
suggestRequest.suggest(source);
} else {
BytesReference querySource = RestActions.parseQuerySource(request);
if (querySource != null) {
suggestRequest.suggest(querySource, false);
}
}
}
suggestRequest.routing(request.param("routing"));
suggestRequest.preference(request.param("preference"));
} catch (Exception e) {
try {
XContentBuilder builder = RestXContentBuilder.restContentBuilder(request);
channel.sendResponse(new XContentRestResponse(request, BAD_REQUEST, builder.startObject().field("error", e.getMessage()).endObject()));
} catch (IOException e1) {
logger.error("Failed to send failure response", e1);
}
return;
}
client.suggest(suggestRequest, new ActionListener<SuggestResponse>() {
@Override
public void onResponse(SuggestResponse response) {
try {
XContentBuilder builder = RestXContentBuilder.restContentBuilder(request);
builder.startObject();
buildBroadcastShardsHeader(builder, response);
Suggest suggest = response.getSuggest();
if (suggest != null) {
suggest.toXContent(builder, request);
}
builder.endObject();
channel.sendResponse(new XContentRestResponse(request, OK, builder));
} catch (Exception e) {
onFailure(e);
}
}
@Override
public void onFailure(Throwable e) {
try {
channel.sendResponse(new XContentThrowableRestResponse(request, e));
} catch (IOException e1) {
logger.error("Failed to send failure response", e1);
}
}
});
}
}

View File

@ -52,7 +52,7 @@ public class RestActions {
builder.field("total", response.getTotalShards());
builder.field("successful", response.getSuccessfulShards());
builder.field("failed", response.getFailedShards());
if (!response.getShardFailures().isEmpty()) {
if (response.getShardFailures()!=null && response.getShardFailures().length>0) {
builder.startArray("failures");
for (ShardOperationFailedException shardFailure : response.getShardFailures()) {
builder.startObject();

View File

@ -408,7 +408,7 @@ public class SearchSourceBuilder implements ToXContent {
public SuggestBuilder suggest() {
if (suggestBuilder == null) {
suggestBuilder = new SuggestBuilder();
suggestBuilder = new SuggestBuilder("suggest");
}
return suggestBuilder;
}

View File

@ -380,36 +380,24 @@ public class SearchPhaseController extends AbstractComponent {
// merge suggest results
Suggest suggest = null;
if (!queryResults.isEmpty()) {
final Map<String, List<Suggest.Suggestion>> groupedSuggestions = new HashMap<String, List<Suggest.Suggestion>>();
boolean hasSuggestions = false;
for (QuerySearchResultProvider resultProvider : queryResults.values()) {
Suggest shardResult = resultProvider.queryResult().suggest();
if (shardResult == null) {
continue;
}
hasSuggestions = true;
for (Suggestion<? extends Entry<? extends Option>> suggestion : shardResult) {
List<Suggestion> list = groupedSuggestions.get(suggestion.getName());
if (list == null) {
list = new ArrayList<Suggest.Suggestion>();
groupedSuggestions.put(suggestion.getName(), list);
}
list.add(suggestion);
}
Suggest.group(groupedSuggestions, shardResult);
}
List<Suggestion<? extends Entry<? extends Option>>> reduced = new ArrayList<Suggestion<? extends Entry<? extends Option>>>();
for (java.util.Map.Entry<String, List<Suggestion>> unmergedResults : groupedSuggestions.entrySet()) {
List<Suggestion> value = unmergedResults.getValue();
Suggestion reduce = value.get(0).reduce(value);
reduce.trim();
reduced.add(reduce);
}
suggest = hasSuggestions ? new Suggest(reduced) : null;
suggest = hasSuggestions ? new Suggest(Suggest.Fields.SUGGEST, Suggest.reduce(groupedSuggestions)) : null;
}
InternalSearchHits searchHits = new InternalSearchHits(hits.toArray(new InternalSearchHit[hits.size()]), totalHits, maxScore);
return new InternalSearchResponse(searchHits, facets, suggest, timedOut);
}
}

View File

@ -99,7 +99,7 @@ public class InternalSearchResponse implements Streamable, ToXContent {
facets = InternalFacets.readFacets(in);
}
if (in.readBoolean()) {
suggest = Suggest.readSuggest(in);
suggest = Suggest.readSuggest(Suggest.Fields.SUGGEST, in);
}
timedOut = in.readBoolean();
}

View File

@ -147,7 +147,7 @@ public class QuerySearchResult extends TransportResponse implements QuerySearchR
facets = InternalFacets.readFacets(in);
}
if (in.readBoolean()) {
suggest = Suggest.readSuggest(in);
suggest = Suggest.readSuggest(Suggest.Fields.SUGGEST, in);
}
searchTimedOut = in.readBoolean();
}

View File

@ -35,7 +35,6 @@ import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentBuilderString;
import org.elasticsearch.search.suggest.Suggest.Suggestion;
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry;
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
import org.elasticsearch.search.suggest.term.TermSuggestion;
@ -45,8 +44,8 @@ import org.elasticsearch.search.suggest.term.TermSuggestion;
*/
public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? extends Option>>>, Streamable, ToXContent {
static class Fields {
static final XContentBuilderString SUGGEST = new XContentBuilderString("suggest");
public static class Fields {
public static final XContentBuilderString SUGGEST = new XContentBuilderString("suggest");
}
private static final Comparator<Option> COMPARATOR = new Comparator<Suggest.Suggestion.Entry.Option>() {
@ -60,14 +59,26 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
}
};
private final XContentBuilderString name;
private List<Suggestion<? extends Entry<? extends Option>>> suggestions;
private Map<String, Suggestion<? extends Entry<? extends Option>>> suggestMap;
public Suggest() {
this.name = null;
}
public Suggest(XContentBuilderString name) {
this.name = name;
}
public Suggest(List<Suggestion<? extends Entry<? extends Option>>> suggestions) {
this(null, suggestions);
}
public Suggest(XContentBuilderString name, List<Suggestion<? extends Entry<? extends Option>>> suggestions) {
this.name = name;
this.suggestions = suggestions;
}
@ -128,19 +139,49 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(Fields.SUGGEST);
for (Suggestion<?> suggestion : suggestions) {
suggestion.toXContent(builder, params);
if(name == null) {
for (Suggestion<?> suggestion : suggestions) {
suggestion.toXContent(builder, params);
}
} else {
builder.startObject(name);
for (Suggestion<?> suggestion : suggestions) {
suggestion.toXContent(builder, params);
}
builder.endObject();
}
builder.endObject();
return null;
return builder;
}
public static Suggest readSuggest(StreamInput in) throws IOException {
Suggest result = new Suggest();
public static Suggest readSuggest(XContentBuilderString name, StreamInput in) throws IOException {
Suggest result = new Suggest(name);
result.readFrom(in);
return result;
}
public static Map<String, List<Suggest.Suggestion>> group(Map<String, List<Suggest.Suggestion>> groupedSuggestions, Suggest suggest) {
for (Suggestion<? extends Entry<? extends Option>> suggestion : suggest) {
List<Suggestion> list = groupedSuggestions.get(suggestion.getName());
if (list == null) {
list = new ArrayList<Suggest.Suggestion>();
groupedSuggestions.put(suggestion.getName(), list);
}
list.add(suggestion);
}
return groupedSuggestions;
}
public static List<Suggestion<? extends Entry<? extends Option>>> reduce(Map<String, List<Suggest.Suggestion>> groupedSuggestions) {
List<Suggestion<? extends Entry<? extends Option>>> reduced = new ArrayList<Suggestion<? extends Entry<? extends Option>>>(groupedSuggestions.size());
for (java.util.Map.Entry<String, List<Suggestion>> unmergedResults : groupedSuggestions.entrySet()) {
List<Suggestion> value = unmergedResults.getValue();
Suggestion reduce = value.get(0).reduce(value);
reduce.trim();
reduced.add(reduce);
}
return reduced;
}
/**
* The suggestion responses corresponding with the suggestions in the request.

View File

@ -37,10 +37,19 @@ import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
*/
public class SuggestBuilder implements ToXContent {
private final String name;
private String globalText;
private final List<SuggestionBuilder<?>> suggestions = new ArrayList<SuggestionBuilder<?>>();
public SuggestBuilder() {
this.name = null;
}
public SuggestBuilder(String name) {
this.name = name;
}
/**
* Sets the text to provide suggestions for. The suggest text is a required option that needs
* to be set either via this setter or via the {@link org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder#setText(String)} method.
@ -61,7 +70,7 @@ public class SuggestBuilder implements ToXContent {
suggestions.add(suggestion);
return this;
}
/**
* Returns all suggestions with the defined names.
*/
@ -71,7 +80,12 @@ public class SuggestBuilder implements ToXContent {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("suggest");
if(name == null) {
builder.startObject();
} else {
builder.startObject(name);
}
if (globalText != null) {
builder.field("text", globalText);
}

View File

@ -21,9 +21,9 @@ package org.elasticsearch.search.suggest;
import java.io.IOException;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.index.mapper.MapperService;
public interface SuggestContextParser {
public SuggestionSearchContext.SuggestionContext parse(XContentParser parser, SearchContext context) throws IOException;
public SuggestionSearchContext.SuggestionContext parse(XContentParser parser, MapperService mapperService) throws IOException;
}

View File

@ -23,6 +23,7 @@ import java.io.IOException;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.search.SearchParseElement;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContext;
@ -38,6 +39,11 @@ public class SuggestParseElement implements SearchParseElement {
@Override
public void parse(XContentParser parser, SearchContext context) throws Exception {
SuggestionSearchContext suggestionSearchContext = parseInternal(parser, context.mapperService());
context.suggest(suggestionSearchContext);
}
public SuggestionSearchContext parseInternal(XContentParser parser, MapperService mapperService) throws IOException {
SuggestionSearchContext suggestionSearchContext = new SuggestionSearchContext();
BytesRef globalText = null;
String fieldName = null;
@ -76,20 +82,20 @@ public class SuggestParseElement implements SearchParseElement {
} else {
throw new ElasticSearchIllegalArgumentException("Suggester[" + fieldName + "] not supported");
}
parseAndVerify(parser, context, suggestionSearchContext, globalText, suggestionName, suggestText, contextParser);
parseAndVerify(parser, mapperService, suggestionSearchContext, globalText, suggestionName, suggestText, contextParser);
}
}
}
}
context.suggest(suggestionSearchContext);
return suggestionSearchContext;
}
public void parseAndVerify(XContentParser parser, SearchContext context, SuggestionSearchContext suggestionSearchContext,
public void parseAndVerify(XContentParser parser, MapperService mapperService, SuggestionSearchContext suggestionSearchContext,
BytesRef globalText, String suggestionName, BytesRef suggestText, SuggestContextParser suggestParser ) throws IOException {
SuggestionContext suggestion = suggestParser.parse(parser, context);
SuggestionContext suggestion = suggestParser.parse(parser, mapperService);
suggestion.setText(suggestText);
SuggestUtils.verifySuggestion(context, globalText, suggestion);
SuggestUtils.verifySuggestion(mapperService, globalText, suggestion);
suggestionSearchContext.addSuggestion(suggestionName, suggestion);
}

View File

@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.util.CharsRef;
import org.elasticsearch.ElasticSearchException;
import org.elasticsearch.common.component.AbstractComponent;
@ -60,27 +61,27 @@ public class SuggestPhase extends AbstractComponent implements SearchPhase {
@Override
public void execute(SearchContext context) throws ElasticSearchException {
try {
SuggestionSearchContext suggest = context.suggest();
final SuggestionSearchContext suggest = context.suggest();
if (suggest == null) {
return;
}
context.queryResult().suggest(execute(suggest, context.searcher().getIndexReader()));
}
public Suggest execute(SuggestionSearchContext suggest, IndexReader reader) {
try {
CharsRef spare = new CharsRef(); // Maybe add CharsRef to CacheRecycler?
final List<Suggestion<? extends Entry<? extends Option>>> suggestions = new ArrayList<Suggestion<? extends Entry<? extends Option>>>(suggest.suggestions().size());
for (Map.Entry<String, SuggestionSearchContext.SuggestionContext> entry : suggest.suggestions().entrySet()) {
SuggestionSearchContext.SuggestionContext suggestion = entry.getValue();
Suggester<SuggestionContext> suggester = suggestion.getSuggester();
Suggestion<? extends Entry<? extends Option>> result = suggester.execute(entry.getKey(), suggestion, context, spare);
Suggestion<? extends Entry<? extends Option>> result = suggester.execute(entry.getKey(), suggestion, reader, spare);
assert entry.getKey().equals(result.name);
suggestions.add(result);
}
context.queryResult().suggest(new Suggest(suggestions));
return new Suggest(Suggest.Fields.SUGGEST, suggestions);
} catch (IOException e) {
throw new ElasticSearchException("I/O exception during suggest phase", e);
}
} catch (NullPointerException e) {
e.printStackTrace();
}
}
}

View File

@ -48,6 +48,7 @@ import org.elasticsearch.index.analysis.CustomAnalyzer;
import org.elasticsearch.index.analysis.NamedAnalyzer;
import org.elasticsearch.index.analysis.ShingleTokenFilterFactory;
import org.elasticsearch.index.analysis.TokenFilterFactory;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContext;
@ -232,12 +233,12 @@ public final class SuggestUtils {
return true;
}
public static boolean parseSuggestContext(XContentParser parser, SearchContext context, String fieldName,
public static boolean parseSuggestContext(XContentParser parser, MapperService mapperService, String fieldName,
SuggestionSearchContext.SuggestionContext suggestion) throws IOException {
if ("analyzer".equals(fieldName)) {
String analyzerName = parser.text();
Analyzer analyzer = context.mapperService().analysisService().analyzer(analyzerName);
Analyzer analyzer = mapperService.analysisService().analyzer(analyzerName);
if (analyzer == null) {
throw new ElasticSearchIllegalArgumentException("Analyzer [" + analyzerName + "] doesn't exists");
}
@ -256,7 +257,7 @@ public final class SuggestUtils {
}
public static void verifySuggestion(SearchContext context, BytesRef globalText, SuggestionContext suggestion) {
public static void verifySuggestion(MapperService mapperService, BytesRef globalText, SuggestionContext suggestion) {
// Verify options and set defaults
if (suggestion.getField() == null) {
throw new ElasticSearchIllegalArgumentException("The required field option is missing");
@ -268,7 +269,7 @@ public final class SuggestUtils {
suggestion.setText(globalText);
}
if (suggestion.getAnalyzer() == null) {
suggestion.setAnalyzer(context.mapperService().searchAnalyzer());
suggestion.setAnalyzer(mapperService.searchAnalyzer());
}
if (suggestion.getShardSize() == -1) {
suggestion.setShardSize(Math.max(suggestion.getSize(), 5));

View File

@ -19,13 +19,13 @@
package org.elasticsearch.search.suggest;
import java.io.IOException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.util.CharsRef;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.search.suggest.Suggest.Suggestion;
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry;
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
public interface Suggester<T extends SuggestionSearchContext.SuggestionContext> {
public abstract Suggestion<? extends Entry<? extends Option>> execute(String name, T suggestion, SearchContext context, CharsRef spare)
public abstract Suggestion<? extends Entry<? extends Option>> execute(String name, T suggestion, IndexReader indexReader, CharsRef spare)
throws IOException;
}

View File

@ -55,7 +55,7 @@ public class SuggestionSearchContext {
this.text = text;
}
protected SuggestionContext(Suggester suggester) {
protected SuggestionContext(Suggester suggester) {
this.suggester = suggester;
}

View File

@ -44,7 +44,7 @@ import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.index.analysis.ShingleTokenFilterFactory;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.search.suggest.SuggestContextParser;
import org.elasticsearch.search.suggest.SuggestUtils;
import org.elasticsearch.search.suggest.SuggestionSearchContext;
@ -54,7 +54,7 @@ public final class PhraseSuggestParser implements SuggestContextParser {
private final PhraseSuggester suggester = new PhraseSuggester();
public SuggestionSearchContext.SuggestionContext parse(XContentParser parser, SearchContext context) throws IOException {
public SuggestionSearchContext.SuggestionContext parse(XContentParser parser, MapperService mapperService) throws IOException {
PhraseSuggestionContext suggestion = new PhraseSuggestionContext(suggester);
XContentParser.Token token;
String fieldName = null;
@ -63,7 +63,7 @@ public final class PhraseSuggestParser implements SuggestContextParser {
if (token == XContentParser.Token.FIELD_NAME) {
fieldName = parser.currentName();
} else if (token.isValue()) {
if (!SuggestUtils.parseSuggestContext(parser, context, fieldName, suggestion)) {
if (!SuggestUtils.parseSuggestContext(parser, mapperService, fieldName, suggestion)) {
if ("real_word_error_likelihood".equals(fieldName)) {
suggestion.setRealWordErrorLikelihood(parser.floatValue());
if (suggestion.realworldErrorLikelyhood() <= 0.0) {
@ -103,10 +103,10 @@ public final class PhraseSuggestParser implements SuggestContextParser {
fieldName = parser.currentName();
}
if (token.isValue()) {
parseCandidateGenerator(parser, context, fieldName, generator);
parseCandidateGenerator(parser, mapperService, fieldName, generator);
}
}
verifyGenerator(context, generator);
verifyGenerator(generator);
suggestion.addGenerator(generator);
}
} else {
@ -128,7 +128,7 @@ public final class PhraseSuggestParser implements SuggestContextParser {
}
if (!gramSizeSet || suggestion.generators().isEmpty()) {
final ShingleTokenFilterFactory.Factory shingleFilterFactory = SuggestUtils.getShingleFilterFactory(suggestion.getAnalyzer() == null ? context.mapperService().fieldSearchAnalyzer(suggestion.getField()) : suggestion.getAnalyzer()); ;
final ShingleTokenFilterFactory.Factory shingleFilterFactory = SuggestUtils.getShingleFilterFactory(suggestion.getAnalyzer() == null ? mapperService.fieldSearchAnalyzer(suggestion.getField()) : suggestion.getAnalyzer()); ;
if (!gramSizeSet) {
// try to detect the shingle size
if (shingleFilterFactory != null) {
@ -257,14 +257,14 @@ public final class PhraseSuggestParser implements SuggestContextParser {
}
}
private void verifyGenerator(SearchContext context, PhraseSuggestionContext.DirectCandidateGenerator suggestion) {
private void verifyGenerator(PhraseSuggestionContext.DirectCandidateGenerator suggestion) {
// Verify options and set defaults
if (suggestion.field() == null) {
throw new ElasticSearchIllegalArgumentException("The required field option is missing");
}
}
private void parseCandidateGenerator(XContentParser parser, SearchContext context, String fieldName,
private void parseCandidateGenerator(XContentParser parser, MapperService mapperService, String fieldName,
PhraseSuggestionContext.DirectCandidateGenerator generator) throws IOException {
if (!SuggestUtils.parseDirectSpellcheckerSettings(parser, fieldName, generator)) {
if ("field".equals(fieldName)) {
@ -273,14 +273,14 @@ public final class PhraseSuggestParser implements SuggestContextParser {
generator.size(parser.intValue());
} else if ("pre_filter".equals(fieldName) || "preFilter".equals(fieldName)) {
String analyzerName = parser.text();
Analyzer analyzer = context.mapperService().analysisService().analyzer(analyzerName);
Analyzer analyzer = mapperService.analysisService().analyzer(analyzerName);
if (analyzer == null) {
throw new ElasticSearchIllegalArgumentException("Analyzer [" + analyzerName + "] doesn't exists");
}
generator.preFilter(analyzer);
} else if ("post_filter".equals(fieldName) || "postFilter".equals(fieldName)) {
String analyzerName = parser.text();
Analyzer analyzer = context.mapperService().analysisService().analyzer(analyzerName);
Analyzer analyzer = mapperService.analysisService().analyzer(analyzerName);
if (analyzer == null) {
throw new ElasticSearchIllegalArgumentException("Analyzer [" + analyzerName + "] doesn't exists");
}

View File

@ -30,7 +30,6 @@ import org.apache.lucene.util.CharsRef;
import org.apache.lucene.util.UnicodeUtil;
import org.elasticsearch.common.text.StringText;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.search.suggest.Suggest.Suggestion;
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry;
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
@ -50,8 +49,7 @@ final class PhraseSuggester implements Suggester<PhraseSuggestionContext> {
*/
@Override
public Suggestion<? extends Entry<? extends Option>> execute(String name, PhraseSuggestionContext suggestion,
SearchContext context, CharsRef spare) throws IOException {
final IndexReader indexReader = context.searcher().getIndexReader();
IndexReader indexReader, CharsRef spare) throws IOException {
double realWordErrorLikelihood = suggestion.realworldErrorLikelyhood();
List<PhraseSuggestionContext.DirectCandidateGenerator> generators = suggestion.generators();
CandidateGenerator[] gens = new CandidateGenerator[generators.size()];

View File

@ -22,7 +22,7 @@ import java.io.IOException;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.search.suggest.DirectSpellcheckerSettings;
import org.elasticsearch.search.suggest.SuggestContextParser;
import org.elasticsearch.search.suggest.SuggestUtils;
@ -31,7 +31,7 @@ import org.elasticsearch.search.suggest.SuggestionSearchContext;
public final class TermSuggestParser implements SuggestContextParser {
private final TermSuggester suggester = new TermSuggester();
public SuggestionSearchContext.SuggestionContext parse(XContentParser parser, SearchContext context) throws IOException {
public SuggestionSearchContext.SuggestionContext parse(XContentParser parser, MapperService mapperService) throws IOException {
XContentParser.Token token;
String fieldName = null;
TermSuggestionContext suggestion = new TermSuggestionContext(suggester);
@ -40,7 +40,7 @@ public final class TermSuggestParser implements SuggestContextParser {
if (token == XContentParser.Token.FIELD_NAME) {
fieldName = parser.currentName();
} else if (token.isValue()) {
parseTokenValue(parser, context, fieldName, suggestion, settings);
parseTokenValue(parser, mapperService, fieldName, suggestion, settings);
} else {
throw new ElasticSearchIllegalArgumentException("suggester[term] doesn't support field [" + fieldName + "]");
}
@ -48,9 +48,9 @@ public final class TermSuggestParser implements SuggestContextParser {
return suggestion;
}
private void parseTokenValue(XContentParser parser, SearchContext context, String fieldName, TermSuggestionContext suggestion,
private void parseTokenValue(XContentParser parser, MapperService mapperService, String fieldName, TermSuggestionContext suggestion,
DirectSpellcheckerSettings settings) throws IOException {
if (!(SuggestUtils.parseSuggestContext(parser, context, fieldName, suggestion) || SuggestUtils.parseDirectSpellcheckerSettings(
if (!(SuggestUtils.parseSuggestContext(parser, mapperService, fieldName, suggestion) || SuggestUtils.parseDirectSpellcheckerSettings(
parser, fieldName, settings))) {
throw new ElasticSearchIllegalArgumentException("suggester[term] doesn't support [" + fieldName + "]");

View File

@ -32,7 +32,6 @@ import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.text.BytesText;
import org.elasticsearch.common.text.StringText;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.search.suggest.SuggestUtils;
import org.elasticsearch.search.suggest.Suggester;
import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContext;
@ -40,7 +39,7 @@ import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContex
final class TermSuggester implements Suggester<TermSuggestionContext> {
@Override
public TermSuggestion execute(String name, TermSuggestionContext suggestion, SearchContext context, CharsRef spare) throws IOException {
public TermSuggestion execute(String name, TermSuggestionContext suggestion, IndexReader indexReader, CharsRef spare) throws IOException {
DirectSpellChecker directSpellChecker = SuggestUtils.getDirectSpellChecker(suggestion.getDirectSpellCheckerSettings());
TermSuggestion response = new TermSuggestion(
@ -48,7 +47,6 @@ final class TermSuggester implements Suggester<TermSuggestionContext> {
);
List<Token> tokens = queryTerms(suggestion, spare);
for (Token token : tokens) {
IndexReader indexReader = context.searcher().getIndexReader();
// TODO: Extend DirectSpellChecker in 4.1, to get the raw suggested words as BytesRef
SuggestWord[] suggestedWords = directSpellChecker.suggestSimilar(
token.term, suggestion.getShardSize(), indexReader, suggestion.getDirectSpellCheckerSettings().suggestMode()

View File

@ -220,7 +220,7 @@ public class DocumentActionsTests extends AbstractNodesTests {
for (int i = 0; i < 5; i++) {
// test successful
CountResponse countResponse = client1.prepareCount("test").setQuery(termQuery("_type", "type1")).setOperationThreading(BroadcastOperationThreading.NO_THREADS).execute().actionGet();
assertThat("Failures " + countResponse.getShardFailures(), countResponse.getShardFailures().size(), equalTo(0));
assertThat("Failures " + countResponse.getShardFailures(), countResponse.getShardFailures()==null?0:countResponse.getShardFailures().length, equalTo(0));
assertThat(countResponse.getCount(), equalTo(2l));
assertThat(countResponse.getSuccessfulShards(), equalTo(5));
assertThat(countResponse.getFailedShards(), equalTo(0));
@ -244,7 +244,7 @@ public class DocumentActionsTests extends AbstractNodesTests {
// count with no query is a match all one
countResponse = client1.prepareCount("test").execute().actionGet();
assertThat("Failures " + countResponse.getShardFailures(), countResponse.getShardFailures().size(), equalTo(0));
assertThat("Failures " + countResponse.getShardFailures(), countResponse.getShardFailures()==null?0:countResponse.getShardFailures().length, equalTo(0));
assertThat(countResponse.getCount(), equalTo(2l));
assertThat(countResponse.getSuccessfulShards(), equalTo(5));
assertThat(countResponse.getFailedShards(), equalTo(0));

View File

@ -894,31 +894,31 @@ public class SimpleChildQuerySearchTests extends AbstractNodesTests {
.setQuery(topChildrenQuery("child", termQuery("c_field1", "1")))
.execute().actionGet();
assertThat(countResponse.getFailedShards(), equalTo(1));
assertThat(countResponse.getShardFailures().get(0).reason().contains("top_children query hasn't executed properly"), equalTo(true));
assertThat(countResponse.getShardFailures()[0].reason().contains("top_children query hasn't executed properly"), equalTo(true));
countResponse = client.prepareCount("test")
.setQuery(hasChildQuery("child", termQuery("c_field1", "2")).scoreType("max"))
.execute().actionGet();
assertThat(countResponse.getFailedShards(), equalTo(1));
assertThat(countResponse.getShardFailures().get(0).reason().contains("has_child query hasn't executed properly"), equalTo(true));
assertThat(countResponse.getShardFailures()[0].reason().contains("has_child query hasn't executed properly"), equalTo(true));
countResponse = client.prepareCount("test")
.setQuery(hasParentQuery("parent", termQuery("p_field1", "1")).scoreType("score"))
.execute().actionGet();
assertThat(countResponse.getFailedShards(), equalTo(1));
assertThat(countResponse.getShardFailures().get(0).reason().contains("has_parent query hasn't executed properly"), equalTo(true));
assertThat(countResponse.getShardFailures()[0].reason().contains("has_parent query hasn't executed properly"), equalTo(true));
countResponse = client.prepareCount("test")
.setQuery(constantScoreQuery(hasChildFilter("child", termQuery("c_field1", "2"))))
.execute().actionGet();
assertThat(countResponse.getFailedShards(), equalTo(1));
assertThat(countResponse.getShardFailures().get(0).reason().contains("has_child filter hasn't executed properly"), equalTo(true));
assertThat(countResponse.getShardFailures()[0].reason().contains("has_child filter hasn't executed properly"), equalTo(true));
countResponse = client.prepareCount("test")
.setQuery(constantScoreQuery(hasParentFilter("parent", termQuery("p_field1", "1"))))
.execute().actionGet();
assertThat(countResponse.getFailedShards(), equalTo(1));
assertThat(countResponse.getShardFailures().get(0).reason().contains("has_parent filter hasn't executed properly"), equalTo(true));
assertThat(countResponse.getShardFailures()[0].reason().contains("has_parent filter hasn't executed properly"), equalTo(true));
}
@Test

View File

@ -0,0 +1,60 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.test.unit.action.suggest;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import java.util.Arrays;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.action.suggest.SuggestRequestBuilder;
import org.elasticsearch.action.suggest.SuggestResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder;
import org.elasticsearch.test.integration.search.suggest.SuggestSearchTests;
import org.testng.annotations.Test;
/**
*/
@Test
public class SuggestActionTests extends SuggestSearchTests {
protected Suggest searchSuggest(Client client, String suggestText, int expectShardsFailed, SuggestionBuilder<?>... suggestions) {
SuggestRequestBuilder builder = client.prepareSuggest();
if (suggestText != null) {
builder.setSuggestText(suggestText);
}
for (SuggestionBuilder<?> suggestion : suggestions) {
builder.addSuggestion(suggestion);
}
SuggestResponse actionGet = builder.execute().actionGet();
assertThat(Arrays.toString(actionGet.getShardFailures()), actionGet.getFailedShards(), equalTo(expectShardsFailed));
if (expectShardsFailed > 0) {
throw new SearchPhaseExecutionException("suggest", "Suggest execution failed", new ShardSearchFailure[0]);
}
return actionGet.getSuggest();
}
}