Merge branch 'master' into index-lifecycle
This commit is contained in:
commit
2d925c9a9a
|
@ -4,7 +4,7 @@ lucene = 7.5.0-snapshot-608f0277b0
|
||||||
# optional dependencies
|
# optional dependencies
|
||||||
spatial4j = 0.7
|
spatial4j = 0.7
|
||||||
jts = 1.15.0
|
jts = 1.15.0
|
||||||
jackson = 2.8.10
|
jackson = 2.8.11
|
||||||
snakeyaml = 1.17
|
snakeyaml = 1.17
|
||||||
# when updating log4j, please update also docs/java-api/index.asciidoc
|
# when updating log4j, please update also docs/java-api/index.asciidoc
|
||||||
log4j = 2.11.1
|
log4j = 2.11.1
|
||||||
|
|
|
@ -163,8 +163,11 @@ import org.elasticsearch.search.aggregations.pipeline.derivative.DerivativePipel
|
||||||
import org.elasticsearch.search.aggregations.pipeline.derivative.ParsedDerivative;
|
import org.elasticsearch.search.aggregations.pipeline.derivative.ParsedDerivative;
|
||||||
import org.elasticsearch.search.suggest.Suggest;
|
import org.elasticsearch.search.suggest.Suggest;
|
||||||
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
|
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
|
||||||
|
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
|
||||||
import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
|
import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
|
||||||
|
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
|
||||||
import org.elasticsearch.search.suggest.term.TermSuggestion;
|
import org.elasticsearch.search.suggest.term.TermSuggestion;
|
||||||
|
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -1151,11 +1154,11 @@ public class RestHighLevelClient implements Closeable {
|
||||||
List<NamedXContentRegistry.Entry> entries = map.entrySet().stream()
|
List<NamedXContentRegistry.Entry> entries = map.entrySet().stream()
|
||||||
.map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue()))
|
.map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(TermSuggestion.NAME),
|
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(TermSuggestionBuilder.SUGGESTION_NAME),
|
||||||
(parser, context) -> TermSuggestion.fromXContent(parser, (String)context)));
|
(parser, context) -> TermSuggestion.fromXContent(parser, (String)context)));
|
||||||
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(PhraseSuggestion.NAME),
|
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(PhraseSuggestionBuilder.SUGGESTION_NAME),
|
||||||
(parser, context) -> PhraseSuggestion.fromXContent(parser, (String)context)));
|
(parser, context) -> PhraseSuggestion.fromXContent(parser, (String)context)));
|
||||||
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(CompletionSuggestion.NAME),
|
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(CompletionSuggestionBuilder.SUGGESTION_NAME),
|
||||||
(parser, context) -> CompletionSuggestion.fromXContent(parser, (String)context)));
|
(parser, context) -> CompletionSuggestion.fromXContent(parser, (String)context)));
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
eb21a035c66ad307e66ec8fce37f5d50fd62d039
|
|
|
@ -0,0 +1 @@
|
||||||
|
876ead1db19f0c9e79c9789273a3ef8c6fd6c29b
|
|
@ -6,6 +6,7 @@ After=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
RuntimeDirectory=elasticsearch
|
RuntimeDirectory=elasticsearch
|
||||||
|
PrivateTmp=true
|
||||||
Environment=ES_HOME=/usr/share/elasticsearch
|
Environment=ES_HOME=/usr/share/elasticsearch
|
||||||
Environment=ES_PATH_CONF=${path.conf}
|
Environment=ES_PATH_CONF=${path.conf}
|
||||||
Environment=PID_DIR=/var/run/elasticsearch
|
Environment=PID_DIR=/var/run/elasticsearch
|
||||||
|
|
|
@ -21,4 +21,10 @@ Aggregations::
|
||||||
* The Percentiles and PercentileRanks aggregations now return `null` in the REST response,
|
* The Percentiles and PercentileRanks aggregations now return `null` in the REST response,
|
||||||
instead of `NaN`. This makes it consistent with the rest of the aggregations. Note:
|
instead of `NaN`. This makes it consistent with the rest of the aggregations. Note:
|
||||||
this only applies to the REST response, the java objects continue to return `NaN` (also
|
this only applies to the REST response, the java objects continue to return `NaN` (also
|
||||||
consistent with other aggregations)
|
consistent with other aggregations)
|
||||||
|
|
||||||
|
Suggesters::
|
||||||
|
* Plugins that register suggesters can now define their own types of suggestions and must
|
||||||
|
explicitly indicate the type of suggestion that they produce. Existing plugins will
|
||||||
|
require changes to their plugin registration. See the `custom-suggester` example
|
||||||
|
plugin {pull}30284[#30284]
|
|
@ -14,6 +14,7 @@ The following settings *must* be considered before going to production:
|
||||||
* <<heap-size,Heap size>>
|
* <<heap-size,Heap size>>
|
||||||
* <<heap-dump-path,Heap dump path>>
|
* <<heap-dump-path,Heap dump path>>
|
||||||
* <<gc-logging,GC logging>>
|
* <<gc-logging,GC logging>>
|
||||||
|
* <<es-tmpdir,Temp directory>>
|
||||||
|
|
||||||
include::important-settings/path-settings.asciidoc[]
|
include::important-settings/path-settings.asciidoc[]
|
||||||
|
|
||||||
|
@ -31,4 +32,6 @@ include::important-settings/heap-dump-path.asciidoc[]
|
||||||
|
|
||||||
include::important-settings/gc-logging.asciidoc[]
|
include::important-settings/gc-logging.asciidoc[]
|
||||||
|
|
||||||
|
include::important-settings/es-tmpdir.asciidoc[]
|
||||||
|
|
||||||
include::important-settings/error-file.asciidoc[]
|
include::important-settings/error-file.asciidoc[]
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
[[es-tmpdir]]
|
||||||
|
=== Temp directory
|
||||||
|
|
||||||
|
By default, Elasticsearch uses a private temporary directory that the startup
|
||||||
|
script creates immediately below the system temporary directory.
|
||||||
|
|
||||||
|
On some Linux distributions a system utility will clean files and directories
|
||||||
|
from `/tmp` if they have not been recently accessed. This can lead to the
|
||||||
|
private temporary directory being removed while Elasticsearch is running if
|
||||||
|
features that require the temporary directory are not used for a long time.
|
||||||
|
This causes problems if a feature that requires the temporary directory is
|
||||||
|
subsequently used.
|
||||||
|
|
||||||
|
If you install Elasticsearch using the `.deb` or `.rpm` packages and run it
|
||||||
|
under `systemd` then the private temporary directory that Elasticsearch uses
|
||||||
|
is excluded from periodic cleanup.
|
||||||
|
|
||||||
|
However, if you intend to run the `.tar.gz` distribution on Linux for an
|
||||||
|
extended period then you should consider creating a dedicated temporary
|
||||||
|
directory for Elasticsearch that is not under a path that will have old files
|
||||||
|
and directories cleaned from it. This directory should have permissions set
|
||||||
|
so that only the user that Elasticsearch runs as can access it. Then set the
|
||||||
|
`$ES_TMPDIR` environment variable to point to it before starting Elasticsearch.
|
|
@ -1 +0,0 @@
|
||||||
eb21a035c66ad307e66ec8fce37f5d50fd62d039
|
|
|
@ -0,0 +1 @@
|
||||||
|
876ead1db19f0c9e79c9789273a3ef8c6fd6c29b
|
|
@ -1 +0,0 @@
|
||||||
1c58cc9313ddf19f0900cd61ed044874278ce320
|
|
|
@ -0,0 +1 @@
|
||||||
|
8b9826e16c3366764bfb7ad7362554f0471046c3
|
|
@ -1 +0,0 @@
|
||||||
e853081fadaad3e98ed801937acc3d8f77580686
|
|
|
@ -0,0 +1 @@
|
||||||
|
d9d1c49c5d9d5e46e2aee55f3cdd119286fe0fc1
|
|
@ -1 +0,0 @@
|
||||||
1e08caf1d787c825307d8cc6362452086020d853
|
|
|
@ -0,0 +1 @@
|
||||||
|
2e77c6ff7342cd61ab1ae7cb14ed16aebfc8a72a
|
|
@ -279,10 +279,6 @@ public final class Def {
|
||||||
String type = signature.substring(1, separator);
|
String type = signature.substring(1, separator);
|
||||||
String call = signature.substring(separator+1, separator2);
|
String call = signature.substring(separator+1, separator2);
|
||||||
int numCaptures = Integer.parseInt(signature.substring(separator2+1));
|
int numCaptures = Integer.parseInt(signature.substring(separator2+1));
|
||||||
Class<?> captures[] = new Class<?>[numCaptures];
|
|
||||||
for (int capture = 0; capture < captures.length; capture++) {
|
|
||||||
captures[capture] = callSiteType.parameterType(i + 1 + capture);
|
|
||||||
}
|
|
||||||
MethodHandle filter;
|
MethodHandle filter;
|
||||||
Class<?> interfaceType = method.typeParameters.get(i - 1 - replaced);
|
Class<?> interfaceType = method.typeParameters.get(i - 1 - replaced);
|
||||||
if (signature.charAt(0) == 'S') {
|
if (signature.charAt(0) == 'S') {
|
||||||
|
@ -294,11 +290,15 @@ public final class Def {
|
||||||
interfaceType,
|
interfaceType,
|
||||||
type,
|
type,
|
||||||
call,
|
call,
|
||||||
captures);
|
numCaptures);
|
||||||
} else if (signature.charAt(0) == 'D') {
|
} else if (signature.charAt(0) == 'D') {
|
||||||
// the interface type is now known, but we need to get the implementation.
|
// the interface type is now known, but we need to get the implementation.
|
||||||
// this is dynamically based on the receiver type (and cached separately, underneath
|
// this is dynamically based on the receiver type (and cached separately, underneath
|
||||||
// this cache). It won't blow up since we never nest here (just references)
|
// this cache). It won't blow up since we never nest here (just references)
|
||||||
|
Class<?> captures[] = new Class<?>[numCaptures];
|
||||||
|
for (int capture = 0; capture < captures.length; capture++) {
|
||||||
|
captures[capture] = callSiteType.parameterType(i + 1 + capture);
|
||||||
|
}
|
||||||
MethodType nestedType = MethodType.methodType(interfaceType, captures);
|
MethodType nestedType = MethodType.methodType(interfaceType, captures);
|
||||||
CallSite nested = DefBootstrap.bootstrap(painlessLookup,
|
CallSite nested = DefBootstrap.bootstrap(painlessLookup,
|
||||||
localMethods,
|
localMethods,
|
||||||
|
@ -331,57 +331,34 @@ public final class Def {
|
||||||
*/
|
*/
|
||||||
static MethodHandle lookupReference(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
|
static MethodHandle lookupReference(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
|
||||||
MethodHandles.Lookup methodHandlesLookup, String interfaceClass, Class<?> receiverClass, String name) throws Throwable {
|
MethodHandles.Lookup methodHandlesLookup, String interfaceClass, Class<?> receiverClass, String name) throws Throwable {
|
||||||
Class<?> interfaceType = painlessLookup.canonicalTypeNameToType(interfaceClass);
|
Class<?> interfaceType = painlessLookup.canonicalTypeNameToType(interfaceClass);
|
||||||
PainlessMethod interfaceMethod = painlessLookup.lookupPainlessClass(interfaceType).functionalMethod;
|
PainlessMethod interfaceMethod = painlessLookup.lookupFunctionalInterfacePainlessMethod(interfaceType);
|
||||||
if (interfaceMethod == null) {
|
if (interfaceMethod == null) {
|
||||||
throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
|
throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
|
||||||
}
|
}
|
||||||
int arity = interfaceMethod.typeParameters.size();
|
int arity = interfaceMethod.typeParameters.size();
|
||||||
PainlessMethod implMethod = lookupMethodInternal(painlessLookup, receiverClass, name, arity);
|
PainlessMethod implMethod = lookupMethodInternal(painlessLookup, receiverClass, name, arity);
|
||||||
return lookupReferenceInternal(painlessLookup, localMethods, methodHandlesLookup,
|
return lookupReferenceInternal(painlessLookup, localMethods, methodHandlesLookup,
|
||||||
interfaceType, PainlessLookupUtility.typeToCanonicalTypeName(implMethod.targetClass),
|
interfaceType, PainlessLookupUtility.typeToCanonicalTypeName(implMethod.targetClass),
|
||||||
implMethod.javaMethod.getName(), receiverClass);
|
implMethod.javaMethod.getName(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a method handle to an implementation of clazz, given method reference signature. */
|
/** Returns a method handle to an implementation of clazz, given method reference signature. */
|
||||||
private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
|
private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
|
||||||
MethodHandles.Lookup methodHandlesLookup, Class<?> clazz, String type, String call, Class<?>... captures) throws Throwable {
|
MethodHandles.Lookup methodHandlesLookup, Class<?> clazz, String type, String call, int captures) throws Throwable {
|
||||||
final FunctionRef ref;
|
final FunctionRef ref = FunctionRef.create(painlessLookup, localMethods, null, clazz, type, call, captures);
|
||||||
if ("this".equals(type)) {
|
final CallSite callSite = LambdaBootstrap.lambdaBootstrap(
|
||||||
// user written method
|
methodHandlesLookup,
|
||||||
PainlessMethod interfaceMethod = painlessLookup.lookupPainlessClass(clazz).functionalMethod;
|
ref.interfaceMethodName,
|
||||||
if (interfaceMethod == null) {
|
ref.factoryMethodType,
|
||||||
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
|
ref.interfaceMethodType,
|
||||||
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(clazz) + "], not a functional interface");
|
ref.delegateClassName,
|
||||||
}
|
ref.delegateInvokeType,
|
||||||
int arity = interfaceMethod.typeParameters.size() + captures.length;
|
ref.delegateMethodName,
|
||||||
LocalMethod localMethod = localMethods.get(Locals.buildLocalMethodKey(call, arity));
|
ref.delegateMethodType,
|
||||||
if (localMethod == null) {
|
ref.isDelegateInterface ? 1 : 0
|
||||||
// is it a synthetic method? If we generated the method ourselves, be more helpful. It can only fail
|
);
|
||||||
// because the arity does not match the expected interface type.
|
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, ref.factoryMethodType.parameterArray()));
|
||||||
if (call.contains("$")) {
|
|
||||||
throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.javaMethod.getName() +
|
|
||||||
"] in [" + clazz + "]");
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Unknown call [" + call + "] with [" + arity + "] arguments.");
|
|
||||||
}
|
|
||||||
ref = new FunctionRef(clazz, interfaceMethod, call, localMethod.methodType, captures.length);
|
|
||||||
} else {
|
|
||||||
// whitelist lookup
|
|
||||||
ref = FunctionRef.resolveFromLookup(painlessLookup, clazz, type, call, captures.length);
|
|
||||||
}
|
|
||||||
final CallSite callSite = LambdaBootstrap.lambdaBootstrap(
|
|
||||||
methodHandlesLookup,
|
|
||||||
ref.interfaceMethodName,
|
|
||||||
ref.factoryMethodType,
|
|
||||||
ref.interfaceMethodType,
|
|
||||||
ref.delegateClassName,
|
|
||||||
ref.delegateInvokeType,
|
|
||||||
ref.delegateMethodName,
|
|
||||||
ref.delegateMethodType,
|
|
||||||
ref.isDelegateInterface ? 1 : 0
|
|
||||||
);
|
|
||||||
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, captures));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,17 +20,17 @@
|
||||||
package org.elasticsearch.painless;
|
package org.elasticsearch.painless;
|
||||||
|
|
||||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
import org.elasticsearch.painless.Locals.LocalMethod;
|
||||||
import org.elasticsearch.painless.lookup.PainlessClass;
|
|
||||||
import org.elasticsearch.painless.lookup.PainlessConstructor;
|
import org.elasticsearch.painless.lookup.PainlessConstructor;
|
||||||
import org.elasticsearch.painless.lookup.PainlessLookup;
|
import org.elasticsearch.painless.lookup.PainlessLookup;
|
||||||
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
|
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
|
||||||
import org.elasticsearch.painless.lookup.PainlessMethod;
|
import org.elasticsearch.painless.lookup.PainlessMethod;
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodType;
|
import java.lang.invoke.MethodType;
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
|
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
|
||||||
import static org.objectweb.asm.Opcodes.H_INVOKEINTERFACE;
|
import static org.objectweb.asm.Opcodes.H_INVOKEINTERFACE;
|
||||||
|
@ -39,251 +39,210 @@ import static org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL;
|
||||||
import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
|
import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to a function or lambda.
|
* Contains all the values necessary to write the instruction to initiate a
|
||||||
* <p>
|
* {@link LambdaBootstrap} for either a function reference or a user-defined
|
||||||
* Once you have created one of these, you have "everything you need" to call {@link LambdaBootstrap}
|
* lambda function.
|
||||||
* either statically from bytecode with invokedynamic, or at runtime from Java.
|
|
||||||
*/
|
*/
|
||||||
public class FunctionRef {
|
public class FunctionRef {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new FunctionRef which will resolve {@code type::call} from the whitelist.
|
||||||
|
* @param painlessLookup the whitelist against which this script is being compiled
|
||||||
|
* @param localMethods user-defined and synthetic methods generated directly on the script class
|
||||||
|
* @param location the character number within the script at compile-time
|
||||||
|
* @param targetClass functional interface type to implement.
|
||||||
|
* @param typeName the left hand side of a method reference expression
|
||||||
|
* @param methodName the right hand side of a method reference expression
|
||||||
|
* @param numberOfCaptures number of captured arguments
|
||||||
|
*/
|
||||||
|
public static FunctionRef create(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods, Location location,
|
||||||
|
Class<?> targetClass, String typeName, String methodName, int numberOfCaptures) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(painlessLookup);
|
||||||
|
Objects.requireNonNull(targetClass);
|
||||||
|
Objects.requireNonNull(typeName);
|
||||||
|
Objects.requireNonNull(methodName);
|
||||||
|
|
||||||
|
String targetClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass);
|
||||||
|
PainlessMethod interfaceMethod;
|
||||||
|
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
interfaceMethod = painlessLookup.lookupFunctionalInterfacePainlessMethod(targetClass);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new IllegalArgumentException("cannot convert function reference [" + typeName + "::" + methodName + "] " +
|
||||||
|
"to a non-functional interface [" + targetClassName + "]", iae);
|
||||||
|
}
|
||||||
|
|
||||||
|
String interfaceMethodName = interfaceMethod.javaMethod.getName();
|
||||||
|
MethodType interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
|
||||||
|
String delegateClassName;
|
||||||
|
boolean isDelegateInterface;
|
||||||
|
int delegateInvokeType;
|
||||||
|
String delegateMethodName;
|
||||||
|
MethodType delegateMethodType;
|
||||||
|
|
||||||
|
Class<?> delegateMethodReturnType;
|
||||||
|
List<Class<?>> delegateMethodParameters;
|
||||||
|
int interfaceTypeParametersSize = interfaceMethod.typeParameters.size();
|
||||||
|
|
||||||
|
if ("this".equals(typeName)) {
|
||||||
|
Objects.requireNonNull(localMethods);
|
||||||
|
|
||||||
|
if (numberOfCaptures < 0) {
|
||||||
|
throw new IllegalStateException("internal error");
|
||||||
|
}
|
||||||
|
|
||||||
|
String localMethodKey = Locals.buildLocalMethodKey(methodName, numberOfCaptures + interfaceTypeParametersSize);
|
||||||
|
LocalMethod localMethod = localMethods.get(localMethodKey);
|
||||||
|
|
||||||
|
if (localMethod == null) {
|
||||||
|
throw new IllegalArgumentException("function reference [this::" + localMethodKey + "] " +
|
||||||
|
"matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " +
|
||||||
|
"not found" + (localMethodKey.contains("$") ? " due to an incorrect number of arguments" : "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
delegateClassName = CLASS_NAME;
|
||||||
|
isDelegateInterface = false;
|
||||||
|
delegateInvokeType = H_INVOKESTATIC;
|
||||||
|
delegateMethodName = localMethod.name;
|
||||||
|
delegateMethodType = localMethod.methodType;
|
||||||
|
|
||||||
|
delegateMethodReturnType = localMethod.returnType;
|
||||||
|
delegateMethodParameters = localMethod.typeParameters;
|
||||||
|
} else if ("new".equals(methodName)) {
|
||||||
|
if (numberOfCaptures != 0) {
|
||||||
|
throw new IllegalStateException("internal error");
|
||||||
|
}
|
||||||
|
|
||||||
|
PainlessConstructor painlessConstructor;
|
||||||
|
|
||||||
|
try {
|
||||||
|
painlessConstructor = painlessLookup.lookupPainlessConstructor(typeName, interfaceTypeParametersSize);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new IllegalArgumentException("function reference [" + typeName + "::new/" + interfaceTypeParametersSize + "] " +
|
||||||
|
"matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " +
|
||||||
|
"not found", iae);
|
||||||
|
}
|
||||||
|
|
||||||
|
delegateClassName = painlessConstructor.javaConstructor.getDeclaringClass().getName();
|
||||||
|
isDelegateInterface = false;
|
||||||
|
delegateInvokeType = H_NEWINVOKESPECIAL;
|
||||||
|
delegateMethodName = PainlessLookupUtility.CONSTRUCTOR_NAME;
|
||||||
|
delegateMethodType = painlessConstructor.methodType;
|
||||||
|
|
||||||
|
delegateMethodReturnType = painlessConstructor.javaConstructor.getDeclaringClass();
|
||||||
|
delegateMethodParameters = painlessConstructor.typeParameters;
|
||||||
|
} else {
|
||||||
|
if (numberOfCaptures != 0 && numberOfCaptures != 1) {
|
||||||
|
throw new IllegalStateException("internal error");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean captured = numberOfCaptures == 1;
|
||||||
|
PainlessMethod painlessMethod;
|
||||||
|
|
||||||
|
try {
|
||||||
|
painlessMethod = painlessLookup.lookupPainlessMethod(typeName, true, methodName, interfaceTypeParametersSize);
|
||||||
|
|
||||||
|
if (captured) {
|
||||||
|
throw new IllegalStateException("internal error");
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException staticIAE) {
|
||||||
|
try {
|
||||||
|
painlessMethod = painlessLookup.lookupPainlessMethod(typeName, false, methodName,
|
||||||
|
captured ? interfaceTypeParametersSize : interfaceTypeParametersSize - 1);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"function reference " + "[" + typeName + "::" + methodName + "/" + interfaceTypeParametersSize + "] " +
|
||||||
|
"matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " +
|
||||||
|
"not found", iae);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegateClassName = painlessMethod.javaMethod.getDeclaringClass().getName();
|
||||||
|
isDelegateInterface = painlessMethod.javaMethod.getDeclaringClass().isInterface();
|
||||||
|
|
||||||
|
if (Modifier.isStatic(painlessMethod.javaMethod.getModifiers())) {
|
||||||
|
delegateInvokeType = H_INVOKESTATIC;
|
||||||
|
} else if (isDelegateInterface) {
|
||||||
|
delegateInvokeType = H_INVOKEINTERFACE;
|
||||||
|
} else {
|
||||||
|
delegateInvokeType = H_INVOKEVIRTUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
delegateMethodName = painlessMethod.javaMethod.getName();
|
||||||
|
delegateMethodType = painlessMethod.methodType;
|
||||||
|
|
||||||
|
delegateMethodReturnType = painlessMethod.returnType;
|
||||||
|
|
||||||
|
if (delegateMethodType.parameterList().size() > painlessMethod.typeParameters.size()) {
|
||||||
|
delegateMethodParameters = new ArrayList<>(painlessMethod.typeParameters);
|
||||||
|
delegateMethodParameters.add(0, delegateMethodType.parameterType(0));
|
||||||
|
} else {
|
||||||
|
delegateMethodParameters = painlessMethod.typeParameters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location != null) {
|
||||||
|
for (int typeParameter = 0; typeParameter < interfaceTypeParametersSize; ++typeParameter) {
|
||||||
|
Class<?> from = interfaceMethod.typeParameters.get(typeParameter);
|
||||||
|
Class<?> to = delegateMethodParameters.get(numberOfCaptures + typeParameter);
|
||||||
|
AnalyzerCaster.getLegalCast(location, from, to, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interfaceMethod.returnType != void.class) {
|
||||||
|
AnalyzerCaster.getLegalCast(location, delegateMethodReturnType, interfaceMethod.returnType, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodType factoryMethodType = MethodType.methodType(targetClass,
|
||||||
|
delegateMethodType.dropParameterTypes(numberOfCaptures, delegateMethodType.parameterCount()));
|
||||||
|
delegateMethodType = delegateMethodType.dropParameterTypes(0, numberOfCaptures);
|
||||||
|
|
||||||
|
return new FunctionRef(interfaceMethodName, interfaceMethodType,
|
||||||
|
delegateClassName, isDelegateInterface, delegateInvokeType, delegateMethodName, delegateMethodType,
|
||||||
|
factoryMethodType
|
||||||
|
);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
if (location != null) {
|
||||||
|
throw location.createError(iae);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw iae;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** functional interface method name */
|
/** functional interface method name */
|
||||||
public final String interfaceMethodName;
|
public final String interfaceMethodName;
|
||||||
/** factory (CallSite) method signature */
|
|
||||||
public final MethodType factoryMethodType;
|
|
||||||
/** functional interface method signature */
|
/** functional interface method signature */
|
||||||
public final MethodType interfaceMethodType;
|
public final MethodType interfaceMethodType;
|
||||||
/** class of the delegate method to be called */
|
/** class of the delegate method to be called */
|
||||||
public final String delegateClassName;
|
public final String delegateClassName;
|
||||||
|
/** whether a call is made on a delegate interface */
|
||||||
|
public final boolean isDelegateInterface;
|
||||||
/** the invocation type of the delegate method */
|
/** the invocation type of the delegate method */
|
||||||
public final int delegateInvokeType;
|
public final int delegateInvokeType;
|
||||||
/** the name of the delegate method */
|
/** the name of the delegate method */
|
||||||
public final String delegateMethodName;
|
public final String delegateMethodName;
|
||||||
/** delegate method signature */
|
/** delegate method signature */
|
||||||
public final MethodType delegateMethodType;
|
public final MethodType delegateMethodType;
|
||||||
|
/** factory (CallSite) method signature */
|
||||||
|
public final MethodType factoryMethodType;
|
||||||
|
|
||||||
/** interface method */
|
private FunctionRef(
|
||||||
public final PainlessMethod interfaceMethod;
|
String interfaceMethodName, MethodType interfaceMethodType,
|
||||||
/** delegate method type parameters */
|
String delegateClassName, boolean isDelegateInterface,
|
||||||
public final List<Class<?>> delegateTypeParameters;
|
int delegateInvokeType, String delegateMethodName, MethodType delegateMethodType,
|
||||||
/** delegate method return type */
|
MethodType factoryMethodType) {
|
||||||
public final Class<?> delegateReturnType;
|
|
||||||
|
|
||||||
/** factory method type descriptor */
|
this.interfaceMethodName = interfaceMethodName;
|
||||||
public final String factoryDescriptor;
|
this.interfaceMethodType = interfaceMethodType;
|
||||||
/** functional interface method as type */
|
this.delegateClassName = delegateClassName;
|
||||||
public final Type interfaceType;
|
this.isDelegateInterface = isDelegateInterface;
|
||||||
/** delegate method type method as type */
|
this.delegateInvokeType = delegateInvokeType;
|
||||||
public final Type delegateType;
|
|
||||||
|
|
||||||
/** whether a call is made on a delegate interface */
|
|
||||||
public final boolean isDelegateInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new FunctionRef, which will resolve {@code type::call} from the whitelist.
|
|
||||||
* @param painlessLookup the whitelist against which this script is being compiled
|
|
||||||
* @param expected functional interface type to implement.
|
|
||||||
* @param type the left hand side of a method reference expression
|
|
||||||
* @param call the right hand side of a method reference expression
|
|
||||||
* @param numCaptures number of captured arguments
|
|
||||||
*/
|
|
||||||
public static FunctionRef resolveFromLookup(
|
|
||||||
PainlessLookup painlessLookup, Class<?> expected, String type, String call, int numCaptures) {
|
|
||||||
|
|
||||||
if ("new".equals(call)) {
|
|
||||||
return new FunctionRef(expected, painlessLookup.lookupPainlessClass(expected).functionalMethod,
|
|
||||||
lookup(painlessLookup, expected, type), numCaptures);
|
|
||||||
} else {
|
|
||||||
return new FunctionRef(expected, painlessLookup.lookupPainlessClass(expected).functionalMethod,
|
|
||||||
lookup(painlessLookup, expected, type, call, numCaptures > 0), numCaptures);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new FunctionRef (already resolved)
|
|
||||||
* @param expected functional interface type to implement
|
|
||||||
* @param interfaceMethod functional interface method
|
|
||||||
* @param delegateConstructor implementation constructor
|
|
||||||
* @param numCaptures number of captured arguments
|
|
||||||
*/
|
|
||||||
public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, PainlessConstructor delegateConstructor, int numCaptures) {
|
|
||||||
Constructor<?> javaConstructor = delegateConstructor.javaConstructor;
|
|
||||||
MethodType delegateMethodType = delegateConstructor.methodType;
|
|
||||||
|
|
||||||
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
|
|
||||||
this.factoryMethodType = MethodType.methodType(expected,
|
|
||||||
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
|
|
||||||
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
|
|
||||||
|
|
||||||
this.delegateClassName = javaConstructor.getDeclaringClass().getName();
|
|
||||||
this.isDelegateInterface = false;
|
|
||||||
this.delegateInvokeType = H_NEWINVOKESPECIAL;
|
|
||||||
this.delegateMethodName = PainlessLookupUtility.CONSTRUCTOR_NAME;
|
|
||||||
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
|
|
||||||
|
|
||||||
this.interfaceMethod = interfaceMethod;
|
|
||||||
this.delegateTypeParameters = delegateConstructor.typeParameters;
|
|
||||||
this.delegateReturnType = void.class;
|
|
||||||
|
|
||||||
this.factoryDescriptor = factoryMethodType.toMethodDescriptorString();
|
|
||||||
this.interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
|
|
||||||
this.delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new FunctionRef (already resolved)
|
|
||||||
* @param expected functional interface type to implement
|
|
||||||
* @param interfaceMethod functional interface method
|
|
||||||
* @param delegateMethod implementation method
|
|
||||||
* @param numCaptures number of captured arguments
|
|
||||||
*/
|
|
||||||
public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, PainlessMethod delegateMethod, int numCaptures) {
|
|
||||||
MethodType delegateMethodType = delegateMethod.methodType;
|
|
||||||
|
|
||||||
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
|
|
||||||
this.factoryMethodType = MethodType.methodType(expected,
|
|
||||||
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
|
|
||||||
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
|
|
||||||
|
|
||||||
this.delegateClassName = delegateMethod.javaMethod.getDeclaringClass().getName();
|
|
||||||
this.isDelegateInterface = delegateMethod.javaMethod.getDeclaringClass().isInterface();
|
|
||||||
|
|
||||||
if (Modifier.isStatic(delegateMethod.javaMethod.getModifiers())) {
|
|
||||||
this.delegateInvokeType = H_INVOKESTATIC;
|
|
||||||
} else if (delegateMethod.javaMethod.getDeclaringClass().isInterface()) {
|
|
||||||
this.delegateInvokeType = H_INVOKEINTERFACE;
|
|
||||||
} else {
|
|
||||||
this.delegateInvokeType = H_INVOKEVIRTUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.delegateMethodName = delegateMethod.javaMethod.getName();
|
|
||||||
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
|
|
||||||
|
|
||||||
this.interfaceMethod = interfaceMethod;
|
|
||||||
this.delegateTypeParameters = delegateMethod.typeParameters;
|
|
||||||
this.delegateReturnType = delegateMethod.returnType;
|
|
||||||
|
|
||||||
this.factoryDescriptor = factoryMethodType.toMethodDescriptorString();
|
|
||||||
this.interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
|
|
||||||
this.delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new FunctionRef (already resolved)
|
|
||||||
* @param expected functional interface type to implement
|
|
||||||
* @param interfaceMethod functional interface method
|
|
||||||
* @param delegateMethod implementation method
|
|
||||||
* @param numCaptures number of captured arguments
|
|
||||||
*/
|
|
||||||
public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, LocalMethod delegateMethod, int numCaptures) {
|
|
||||||
MethodType delegateMethodType = delegateMethod.methodType;
|
|
||||||
|
|
||||||
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
|
|
||||||
this.factoryMethodType = MethodType.methodType(expected,
|
|
||||||
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
|
|
||||||
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
|
|
||||||
|
|
||||||
this.delegateClassName = CLASS_NAME;
|
|
||||||
this.isDelegateInterface = false;
|
|
||||||
this.delegateInvokeType = H_INVOKESTATIC;
|
|
||||||
|
|
||||||
this.delegateMethodName = delegateMethod.name;
|
|
||||||
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
|
|
||||||
|
|
||||||
this.interfaceMethod = interfaceMethod;
|
|
||||||
this.delegateTypeParameters = delegateMethod.typeParameters;
|
|
||||||
this.delegateReturnType = delegateMethod.returnType;
|
|
||||||
|
|
||||||
this.factoryDescriptor = factoryMethodType.toMethodDescriptorString();
|
|
||||||
this.interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
|
|
||||||
this.delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new FunctionRef (low level).
|
|
||||||
* It is for runtime use only.
|
|
||||||
*/
|
|
||||||
public FunctionRef(Class<?> expected,
|
|
||||||
PainlessMethod interfaceMethod, String delegateMethodName, MethodType delegateMethodType, int numCaptures) {
|
|
||||||
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
|
|
||||||
this.factoryMethodType = MethodType.methodType(expected,
|
|
||||||
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
|
|
||||||
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
|
|
||||||
|
|
||||||
this.delegateClassName = CLASS_NAME;
|
|
||||||
this.delegateInvokeType = H_INVOKESTATIC;
|
|
||||||
this.delegateMethodName = delegateMethodName;
|
this.delegateMethodName = delegateMethodName;
|
||||||
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
|
this.delegateMethodType = delegateMethodType;
|
||||||
this.isDelegateInterface = false;
|
this.factoryMethodType = factoryMethodType;
|
||||||
|
|
||||||
this.interfaceMethod = null;
|
|
||||||
this.delegateTypeParameters = null;
|
|
||||||
this.delegateReturnType = null;
|
|
||||||
|
|
||||||
this.factoryDescriptor = null;
|
|
||||||
this.interfaceType = null;
|
|
||||||
this.delegateType = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up {@code type} from the whitelist, and returns a matching constructor.
|
|
||||||
*/
|
|
||||||
private static PainlessConstructor lookup(PainlessLookup painlessLookup, Class<?> expected, String type) {
|
|
||||||
// check its really a functional interface
|
|
||||||
// for e.g. Comparable
|
|
||||||
PainlessMethod method = painlessLookup.lookupPainlessClass(expected).functionalMethod;
|
|
||||||
if (method == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::new] " +
|
|
||||||
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface");
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup requested constructor
|
|
||||||
PainlessClass struct = painlessLookup.lookupPainlessClass(painlessLookup.canonicalTypeNameToType(type));
|
|
||||||
PainlessConstructor impl = struct.constructors.get(PainlessLookupUtility.buildPainlessConstructorKey(method.typeParameters.size()));
|
|
||||||
|
|
||||||
if (impl == null) {
|
|
||||||
throw new IllegalArgumentException("Unknown reference [" + type + "::new] matching [" + expected + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
return impl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up {@code type::call} from the whitelist, and returns a matching method.
|
|
||||||
*/
|
|
||||||
private static PainlessMethod lookup(PainlessLookup painlessLookup, Class<?> expected,
|
|
||||||
String type, String call, boolean receiverCaptured) {
|
|
||||||
// check its really a functional interface
|
|
||||||
// for e.g. Comparable
|
|
||||||
PainlessMethod method = painlessLookup.lookupPainlessClass(expected).functionalMethod;
|
|
||||||
if (method == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
|
|
||||||
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface");
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup requested method
|
|
||||||
PainlessClass struct = painlessLookup.lookupPainlessClass(painlessLookup.canonicalTypeNameToType(type));
|
|
||||||
final PainlessMethod impl;
|
|
||||||
// look for a static impl first
|
|
||||||
PainlessMethod staticImpl =
|
|
||||||
struct.staticMethods.get(PainlessLookupUtility.buildPainlessMethodKey(call, method.typeParameters.size()));
|
|
||||||
if (staticImpl == null) {
|
|
||||||
// otherwise a virtual impl
|
|
||||||
final int arity;
|
|
||||||
if (receiverCaptured) {
|
|
||||||
// receiver captured
|
|
||||||
arity = method.typeParameters.size();
|
|
||||||
} else {
|
|
||||||
// receiver passed
|
|
||||||
arity = method.typeParameters.size() - 1;
|
|
||||||
}
|
|
||||||
impl = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey(call, arity));
|
|
||||||
} else {
|
|
||||||
impl = staticImpl;
|
|
||||||
}
|
|
||||||
if (impl == null) {
|
|
||||||
throw new IllegalArgumentException("Unknown reference [" + type + "::" + call + "] matching " +
|
|
||||||
"[" + expected + "]");
|
|
||||||
}
|
|
||||||
return impl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ import static org.elasticsearch.painless.WriterConstants.DEF_TO_SHORT_EXPLICIT;
|
||||||
import static org.elasticsearch.painless.WriterConstants.DEF_TO_SHORT_IMPLICIT;
|
import static org.elasticsearch.painless.WriterConstants.DEF_TO_SHORT_IMPLICIT;
|
||||||
import static org.elasticsearch.painless.WriterConstants.DEF_UTIL_TYPE;
|
import static org.elasticsearch.painless.WriterConstants.DEF_UTIL_TYPE;
|
||||||
import static org.elasticsearch.painless.WriterConstants.INDY_STRING_CONCAT_BOOTSTRAP_HANDLE;
|
import static org.elasticsearch.painless.WriterConstants.INDY_STRING_CONCAT_BOOTSTRAP_HANDLE;
|
||||||
|
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||||
import static org.elasticsearch.painless.WriterConstants.MAX_INDY_STRING_CONCAT_ARGS;
|
import static org.elasticsearch.painless.WriterConstants.MAX_INDY_STRING_CONCAT_ARGS;
|
||||||
import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE;
|
import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE;
|
||||||
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_BOOLEAN;
|
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_BOOLEAN;
|
||||||
|
@ -439,4 +440,18 @@ public final class MethodWriter extends GeneratorAdapter {
|
||||||
invokeVirtual(type, method);
|
invokeVirtual(type, method);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void invokeLambdaCall(FunctionRef functionRef) {
|
||||||
|
invokeDynamic(
|
||||||
|
functionRef.interfaceMethodName,
|
||||||
|
functionRef.factoryMethodType.toMethodDescriptorString(),
|
||||||
|
LAMBDA_BOOTSTRAP_HANDLE,
|
||||||
|
Type.getMethodType(functionRef.interfaceMethodType.toMethodDescriptorString()),
|
||||||
|
functionRef.delegateClassName,
|
||||||
|
functionRef.delegateInvokeType,
|
||||||
|
functionRef.delegateMethodName,
|
||||||
|
Type.getMethodType(functionRef.delegateMethodType.toMethodDescriptorString()),
|
||||||
|
functionRef.isDelegateInterface ? 1 : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,13 +35,13 @@ public final class PainlessClass {
|
||||||
public final Map<String, MethodHandle> getterMethodHandles;
|
public final Map<String, MethodHandle> getterMethodHandles;
|
||||||
public final Map<String, MethodHandle> setterMethodHandles;
|
public final Map<String, MethodHandle> setterMethodHandles;
|
||||||
|
|
||||||
public final PainlessMethod functionalMethod;
|
public final PainlessMethod functionalInterfaceMethod;
|
||||||
|
|
||||||
PainlessClass(Map<String, PainlessConstructor> constructors,
|
PainlessClass(Map<String, PainlessConstructor> constructors,
|
||||||
Map<String, PainlessMethod> staticMethods, Map<String, PainlessMethod> methods,
|
Map<String, PainlessMethod> staticMethods, Map<String, PainlessMethod> methods,
|
||||||
Map<String, PainlessField> staticFields, Map<String, PainlessField> fields,
|
Map<String, PainlessField> staticFields, Map<String, PainlessField> fields,
|
||||||
Map<String, MethodHandle> getterMethodHandles, Map<String, MethodHandle> setterMethodHandles,
|
Map<String, MethodHandle> getterMethodHandles, Map<String, MethodHandle> setterMethodHandles,
|
||||||
PainlessMethod functionalMethod) {
|
PainlessMethod functionalInterfaceMethod) {
|
||||||
|
|
||||||
this.constructors = Collections.unmodifiableMap(constructors);
|
this.constructors = Collections.unmodifiableMap(constructors);
|
||||||
|
|
||||||
|
@ -54,6 +54,6 @@ public final class PainlessClass {
|
||||||
this.getterMethodHandles = Collections.unmodifiableMap(getterMethodHandles);
|
this.getterMethodHandles = Collections.unmodifiableMap(getterMethodHandles);
|
||||||
this.setterMethodHandles = Collections.unmodifiableMap(setterMethodHandles);
|
this.setterMethodHandles = Collections.unmodifiableMap(setterMethodHandles);
|
||||||
|
|
||||||
this.functionalMethod = functionalMethod;
|
this.functionalInterfaceMethod = functionalInterfaceMethod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ final class PainlessClassBuilder {
|
||||||
final Map<String, MethodHandle> getterMethodHandles;
|
final Map<String, MethodHandle> getterMethodHandles;
|
||||||
final Map<String, MethodHandle> setterMethodHandles;
|
final Map<String, MethodHandle> setterMethodHandles;
|
||||||
|
|
||||||
PainlessMethod functionalMethod;
|
PainlessMethod functionalInterfaceMethod;
|
||||||
|
|
||||||
PainlessClassBuilder() {
|
PainlessClassBuilder() {
|
||||||
constructors = new HashMap<>();
|
constructors = new HashMap<>();
|
||||||
|
@ -49,11 +49,11 @@ final class PainlessClassBuilder {
|
||||||
getterMethodHandles = new HashMap<>();
|
getterMethodHandles = new HashMap<>();
|
||||||
setterMethodHandles = new HashMap<>();
|
setterMethodHandles = new HashMap<>();
|
||||||
|
|
||||||
functionalMethod = null;
|
functionalInterfaceMethod = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
PainlessClass build() {
|
PainlessClass build() {
|
||||||
return new PainlessClass(constructors, staticMethods, methods, staticFields, fields,
|
return new PainlessClass(constructors, staticMethods, methods, staticFields, fields,
|
||||||
getterMethodHandles, setterMethodHandles, functionalMethod);
|
getterMethodHandles, setterMethodHandles, functionalInterfaceMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,14 @@ public final class PainlessLookup {
|
||||||
return classesToPainlessClasses.get(targetClass);
|
return classesToPainlessClasses.get(targetClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PainlessConstructor lookupPainlessConstructor(String targetClassName, int constructorArity) {
|
||||||
|
Objects.requireNonNull(targetClassName);
|
||||||
|
|
||||||
|
Class<?> targetClass = canonicalTypeNameToType(targetClassName);
|
||||||
|
|
||||||
|
return lookupPainlessConstructor(targetClass, constructorArity);
|
||||||
|
}
|
||||||
|
|
||||||
public PainlessConstructor lookupPainlessConstructor(Class<?> targetClass, int constructorArity) {
|
public PainlessConstructor lookupPainlessConstructor(Class<?> targetClass, int constructorArity) {
|
||||||
Objects.requireNonNull(targetClass);
|
Objects.requireNonNull(targetClass);
|
||||||
|
|
||||||
|
@ -83,6 +91,14 @@ public final class PainlessLookup {
|
||||||
return painlessConstructor;
|
return painlessConstructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PainlessMethod lookupPainlessMethod(String targetClassName, boolean isStatic, String methodName, int methodArity) {
|
||||||
|
Objects.requireNonNull(targetClassName);
|
||||||
|
|
||||||
|
Class<?> targetClass = canonicalTypeNameToType(targetClassName);
|
||||||
|
|
||||||
|
return lookupPainlessMethod(targetClass, isStatic, methodName, methodArity);
|
||||||
|
}
|
||||||
|
|
||||||
public PainlessMethod lookupPainlessMethod(Class<?> targetClass, boolean isStatic, String methodName, int methodArity) {
|
public PainlessMethod lookupPainlessMethod(Class<?> targetClass, boolean isStatic, String methodName, int methodArity) {
|
||||||
Objects.requireNonNull(targetClass);
|
Objects.requireNonNull(targetClass);
|
||||||
Objects.requireNonNull(methodName);
|
Objects.requireNonNull(methodName);
|
||||||
|
@ -111,6 +127,14 @@ public final class PainlessLookup {
|
||||||
return painlessMethod;
|
return painlessMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PainlessField lookupPainlessField(String targetClassName, boolean isStatic, String fieldName) {
|
||||||
|
Objects.requireNonNull(targetClassName);
|
||||||
|
|
||||||
|
Class<?> targetClass = canonicalTypeNameToType(targetClassName);
|
||||||
|
|
||||||
|
return lookupPainlessField(targetClass, isStatic, fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
public PainlessField lookupPainlessField(Class<?> targetClass, boolean isStatic, String fieldName) {
|
public PainlessField lookupPainlessField(Class<?> targetClass, boolean isStatic, String fieldName) {
|
||||||
Objects.requireNonNull(targetClass);
|
Objects.requireNonNull(targetClass);
|
||||||
Objects.requireNonNull(fieldName);
|
Objects.requireNonNull(fieldName);
|
||||||
|
@ -134,4 +158,20 @@ public final class PainlessLookup {
|
||||||
|
|
||||||
return painlessField;
|
return painlessField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PainlessMethod lookupFunctionalInterfacePainlessMethod(Class<?> targetClass) {
|
||||||
|
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass);
|
||||||
|
|
||||||
|
if (targetPainlessClass == null) {
|
||||||
|
throw new IllegalArgumentException("target class [" + typeToCanonicalTypeName(targetClass) + "] not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
PainlessMethod functionalInterfacePainlessMethod = targetPainlessClass.functionalInterfaceMethod;
|
||||||
|
|
||||||
|
if (functionalInterfacePainlessMethod == null) {
|
||||||
|
throw new IllegalArgumentException("target class [" + typeToCanonicalTypeName(targetClass) + "] is not a functional interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
return functionalInterfacePainlessMethod;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -875,7 +875,7 @@ public final class PainlessLookupBuilder {
|
||||||
} else if (javaMethods.size() == 1) {
|
} else if (javaMethods.size() == 1) {
|
||||||
java.lang.reflect.Method javaMethod = javaMethods.get(0);
|
java.lang.reflect.Method javaMethod = javaMethods.get(0);
|
||||||
String painlessMethodKey = buildPainlessMethodKey(javaMethod.getName(), javaMethod.getParameterCount());
|
String painlessMethodKey = buildPainlessMethodKey(javaMethod.getName(), javaMethod.getParameterCount());
|
||||||
painlessClassBuilder.functionalMethod = painlessClassBuilder.methods.get(painlessMethodKey);
|
painlessClassBuilder.functionalInterfaceMethod = painlessClassBuilder.methods.get(painlessMethodKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
|
|
||||||
package org.elasticsearch.painless.node;
|
package org.elasticsearch.painless.node;
|
||||||
|
|
||||||
import org.elasticsearch.painless.AnalyzerCaster;
|
|
||||||
import org.elasticsearch.painless.DefBootstrap;
|
import org.elasticsearch.painless.DefBootstrap;
|
||||||
import org.elasticsearch.painless.FunctionRef;
|
import org.elasticsearch.painless.FunctionRef;
|
||||||
import org.elasticsearch.painless.Globals;
|
import org.elasticsearch.painless.Globals;
|
||||||
|
@ -35,8 +34,6 @@ import org.objectweb.asm.Type;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a capturing function reference.
|
* Represents a capturing function reference.
|
||||||
*/
|
*/
|
||||||
|
@ -76,23 +73,8 @@ public final class ECapturingFunctionRef extends AExpression implements ILambda
|
||||||
defPointer = null;
|
defPointer = null;
|
||||||
// static case
|
// static case
|
||||||
if (captured.clazz != def.class) {
|
if (captured.clazz != def.class) {
|
||||||
try {
|
ref = FunctionRef.create(locals.getPainlessLookup(), locals.getMethods(), location,
|
||||||
ref = FunctionRef.resolveFromLookup(locals.getPainlessLookup(), expected,
|
expected, PainlessLookupUtility.typeToCanonicalTypeName(captured.clazz), call, 1);
|
||||||
PainlessLookupUtility.typeToCanonicalTypeName(captured.clazz), call, 1);
|
|
||||||
|
|
||||||
// check casts between the interface method and the delegate method are legal
|
|
||||||
for (int i = 0; i < ref.interfaceMethod.typeParameters.size(); ++i) {
|
|
||||||
Class<?> from = ref.interfaceMethod.typeParameters.get(i);
|
|
||||||
Class<?> to = ref.delegateTypeParameters.get(i);
|
|
||||||
AnalyzerCaster.getLegalCast(location, from, to, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ref.interfaceMethod.returnType != void.class) {
|
|
||||||
AnalyzerCaster.getLegalCast(location, ref.delegateReturnType, ref.interfaceMethod.returnType, false, true);
|
|
||||||
}
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw createError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
actual = expected;
|
actual = expected;
|
||||||
}
|
}
|
||||||
|
@ -114,17 +96,7 @@ public final class ECapturingFunctionRef extends AExpression implements ILambda
|
||||||
} else {
|
} else {
|
||||||
// typed interface, typed implementation
|
// typed interface, typed implementation
|
||||||
writer.visitVarInsn(MethodWriter.getType(captured.clazz).getOpcode(Opcodes.ILOAD), captured.getSlot());
|
writer.visitVarInsn(MethodWriter.getType(captured.clazz).getOpcode(Opcodes.ILOAD), captured.getSlot());
|
||||||
writer.invokeDynamic(
|
writer.invokeLambdaCall(ref);
|
||||||
ref.interfaceMethodName,
|
|
||||||
ref.factoryDescriptor,
|
|
||||||
LAMBDA_BOOTSTRAP_HANDLE,
|
|
||||||
ref.interfaceType,
|
|
||||||
ref.delegateClassName,
|
|
||||||
ref.delegateInvokeType,
|
|
||||||
ref.delegateMethodName,
|
|
||||||
ref.delegateType,
|
|
||||||
ref.isDelegateInterface ? 1 : 0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,22 +19,16 @@
|
||||||
|
|
||||||
package org.elasticsearch.painless.node;
|
package org.elasticsearch.painless.node;
|
||||||
|
|
||||||
import org.elasticsearch.painless.AnalyzerCaster;
|
|
||||||
import org.elasticsearch.painless.FunctionRef;
|
import org.elasticsearch.painless.FunctionRef;
|
||||||
import org.elasticsearch.painless.Globals;
|
import org.elasticsearch.painless.Globals;
|
||||||
import org.elasticsearch.painless.Locals;
|
import org.elasticsearch.painless.Locals;
|
||||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
|
||||||
import org.elasticsearch.painless.Location;
|
import org.elasticsearch.painless.Location;
|
||||||
import org.elasticsearch.painless.MethodWriter;
|
import org.elasticsearch.painless.MethodWriter;
|
||||||
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
|
|
||||||
import org.elasticsearch.painless.lookup.PainlessMethod;
|
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a function reference.
|
* Represents a function reference.
|
||||||
*/
|
*/
|
||||||
|
@ -63,39 +57,7 @@ public final class EFunctionRef extends AExpression implements ILambda {
|
||||||
defPointer = "S" + type + "." + call + ",0";
|
defPointer = "S" + type + "." + call + ",0";
|
||||||
} else {
|
} else {
|
||||||
defPointer = null;
|
defPointer = null;
|
||||||
try {
|
ref = FunctionRef.create(locals.getPainlessLookup(), locals.getMethods(), location, expected, type, call, 0);
|
||||||
if ("this".equals(type)) {
|
|
||||||
// user's own function
|
|
||||||
PainlessMethod interfaceMethod = locals.getPainlessLookup().lookupPainlessClass(expected).functionalMethod;
|
|
||||||
if (interfaceMethod == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
|
|
||||||
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface");
|
|
||||||
}
|
|
||||||
LocalMethod delegateMethod = locals.getMethod(call, interfaceMethod.typeParameters.size());
|
|
||||||
if (delegateMethod == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
|
|
||||||
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], function not found");
|
|
||||||
}
|
|
||||||
ref = new FunctionRef(expected, interfaceMethod, delegateMethod, 0);
|
|
||||||
|
|
||||||
// check casts between the interface method and the delegate method are legal
|
|
||||||
for (int i = 0; i < interfaceMethod.typeParameters.size(); ++i) {
|
|
||||||
Class<?> from = interfaceMethod.typeParameters.get(i);
|
|
||||||
Class<?> to = delegateMethod.typeParameters.get(i);
|
|
||||||
AnalyzerCaster.getLegalCast(location, from, to, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interfaceMethod.returnType != void.class) {
|
|
||||||
AnalyzerCaster.getLegalCast(location, delegateMethod.returnType, interfaceMethod.returnType, false, true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// whitelist lookup
|
|
||||||
ref = FunctionRef.resolveFromLookup(locals.getPainlessLookup(), expected, type, call, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw createError(e);
|
|
||||||
}
|
|
||||||
actual = expected;
|
actual = expected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,17 +66,7 @@ public final class EFunctionRef extends AExpression implements ILambda {
|
||||||
void write(MethodWriter writer, Globals globals) {
|
void write(MethodWriter writer, Globals globals) {
|
||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
writer.writeDebugInfo(location);
|
writer.writeDebugInfo(location);
|
||||||
writer.invokeDynamic(
|
writer.invokeLambdaCall(ref);
|
||||||
ref.interfaceMethodName,
|
|
||||||
ref.factoryDescriptor,
|
|
||||||
LAMBDA_BOOTSTRAP_HANDLE,
|
|
||||||
ref.interfaceType,
|
|
||||||
ref.delegateClassName,
|
|
||||||
ref.delegateInvokeType,
|
|
||||||
ref.delegateMethodName,
|
|
||||||
ref.delegateType,
|
|
||||||
ref.isDelegateInterface ? 1 : 0
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: don't do this: its just to cutover :)
|
// TODO: don't do this: its just to cutover :)
|
||||||
writer.push((String)null);
|
writer.push((String)null);
|
||||||
|
|
|
@ -19,11 +19,9 @@
|
||||||
|
|
||||||
package org.elasticsearch.painless.node;
|
package org.elasticsearch.painless.node;
|
||||||
|
|
||||||
import org.elasticsearch.painless.AnalyzerCaster;
|
|
||||||
import org.elasticsearch.painless.FunctionRef;
|
import org.elasticsearch.painless.FunctionRef;
|
||||||
import org.elasticsearch.painless.Globals;
|
import org.elasticsearch.painless.Globals;
|
||||||
import org.elasticsearch.painless.Locals;
|
import org.elasticsearch.painless.Locals;
|
||||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
|
||||||
import org.elasticsearch.painless.Locals.Variable;
|
import org.elasticsearch.painless.Locals.Variable;
|
||||||
import org.elasticsearch.painless.Location;
|
import org.elasticsearch.painless.Location;
|
||||||
import org.elasticsearch.painless.MethodWriter;
|
import org.elasticsearch.painless.MethodWriter;
|
||||||
|
@ -40,8 +38,6 @@ import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lambda expression node.
|
* Lambda expression node.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -122,7 +118,7 @@ public final class ELambda extends AExpression implements ILambda {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// we know the method statically, infer return type and any unknown/def types
|
// we know the method statically, infer return type and any unknown/def types
|
||||||
interfaceMethod = locals.getPainlessLookup().lookupPainlessClass(expected).functionalMethod;
|
interfaceMethod = locals.getPainlessLookup().lookupFunctionalInterfacePainlessMethod(expected);
|
||||||
if (interfaceMethod == null) {
|
if (interfaceMethod == null) {
|
||||||
throw createError(new IllegalArgumentException("Cannot pass lambda to " +
|
throw createError(new IllegalArgumentException("Cannot pass lambda to " +
|
||||||
"[" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface"));
|
"[" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface"));
|
||||||
|
@ -184,25 +180,8 @@ public final class ELambda extends AExpression implements ILambda {
|
||||||
defPointer = "Sthis." + name + "," + captures.size();
|
defPointer = "Sthis." + name + "," + captures.size();
|
||||||
} else {
|
} else {
|
||||||
defPointer = null;
|
defPointer = null;
|
||||||
try {
|
ref = FunctionRef.create(
|
||||||
LocalMethod localMethod =
|
locals.getPainlessLookup(), locals.getMethods(), location, expected, "this", desugared.name, captures.size());
|
||||||
new LocalMethod(desugared.name, desugared.returnType, desugared.typeParameters, desugared.methodType);
|
|
||||||
ref = new FunctionRef(expected, interfaceMethod, localMethod, captures.size());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw createError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check casts between the interface method and the delegate method are legal
|
|
||||||
for (int i = 0; i < interfaceMethod.typeParameters.size(); ++i) {
|
|
||||||
Class<?> from = interfaceMethod.typeParameters.get(i);
|
|
||||||
Class<?> to = desugared.parameters.get(i + captures.size()).clazz;
|
|
||||||
AnalyzerCaster.getLegalCast(location, from, to, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interfaceMethod.returnType != void.class) {
|
|
||||||
AnalyzerCaster.getLegalCast(location, desugared.returnType, interfaceMethod.returnType, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
actual = expected;
|
actual = expected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,17 +197,7 @@ public final class ELambda extends AExpression implements ILambda {
|
||||||
writer.visitVarInsn(MethodWriter.getType(capture.clazz).getOpcode(Opcodes.ILOAD), capture.getSlot());
|
writer.visitVarInsn(MethodWriter.getType(capture.clazz).getOpcode(Opcodes.ILOAD), capture.getSlot());
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.invokeDynamic(
|
writer.invokeLambdaCall(ref);
|
||||||
ref.interfaceMethodName,
|
|
||||||
ref.factoryDescriptor,
|
|
||||||
LAMBDA_BOOTSTRAP_HANDLE,
|
|
||||||
ref.interfaceType,
|
|
||||||
ref.delegateClassName,
|
|
||||||
ref.delegateInvokeType,
|
|
||||||
ref.delegateMethodName,
|
|
||||||
ref.delegateType,
|
|
||||||
ref.isDelegateInterface ? 1 : 0
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// placeholder
|
// placeholder
|
||||||
writer.push((String)null);
|
writer.push((String)null);
|
||||||
|
|
|
@ -27,7 +27,6 @@ import java.lang.invoke.LambdaConversionException;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.endsWith;
|
import static org.hamcrest.Matchers.endsWith;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
|
||||||
|
|
||||||
public class FunctionRefTests extends ScriptTestCase {
|
public class FunctionRefTests extends ScriptTestCase {
|
||||||
|
|
||||||
|
@ -193,14 +192,15 @@ public class FunctionRefTests extends ScriptTestCase {
|
||||||
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("List l = [2, 1]; l.sort(Integer::bogus); return l.get(0);");
|
exec("List l = [2, 1]; l.sort(Integer::bogus); return l.get(0);");
|
||||||
});
|
});
|
||||||
assertThat(e.getMessage(), startsWith("Unknown reference"));
|
assertThat(e.getMessage(), containsString("function reference [Integer::bogus/2] matching [java.util.Comparator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testQualifiedMethodMissing() {
|
public void testQualifiedMethodMissing() {
|
||||||
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("List l = [2, 1]; l.sort(org.joda.time.ReadableDateTime::bogus); return l.get(0);", false);
|
exec("List l = [2, 1]; l.sort(org.joda.time.ReadableDateTime::bogus); return l.get(0);", false);
|
||||||
});
|
});
|
||||||
assertThat(e.getMessage(), startsWith("Unknown reference"));
|
assertThat(e.getMessage(),
|
||||||
|
containsString("function reference [org.joda.time.ReadableDateTime::bogus/2] matching [java.util.Comparator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testClassMissing() {
|
public void testClassMissing() {
|
||||||
|
@ -223,11 +223,12 @@ public class FunctionRefTests extends ScriptTestCase {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("List l = new ArrayList(); l.add(2); l.add(1); l.add(Integer::bogus); return l.get(0);");
|
exec("List l = new ArrayList(); l.add(2); l.add(1); l.add(Integer::bogus); return l.get(0);");
|
||||||
});
|
});
|
||||||
assertThat(expected.getMessage(), containsString("Cannot convert function reference"));
|
assertThat(expected.getMessage(),
|
||||||
|
containsString("cannot convert function reference [Integer::bogus] to a non-functional interface [def]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testIncompatible() {
|
public void testIncompatible() {
|
||||||
expectScriptThrows(BootstrapMethodError.class, () -> {
|
expectScriptThrows(ClassCastException.class, () -> {
|
||||||
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::startsWith); return l.get(0);");
|
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::startsWith); return l.get(0);");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -236,28 +237,32 @@ public class FunctionRefTests extends ScriptTestCase {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("Optional.empty().orElseGet(String::startsWith);");
|
exec("Optional.empty().orElseGet(String::startsWith);");
|
||||||
});
|
});
|
||||||
assertThat(expected.getMessage(), containsString("Unknown reference"));
|
assertThat(expected.getMessage(),
|
||||||
|
containsString("function reference [String::startsWith/0] matching [java.util.function.Supplier"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWrongArityNotEnough() {
|
public void testWrongArityNotEnough() {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
|
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
|
||||||
});
|
});
|
||||||
assertTrue(expected.getMessage().contains("Unknown reference"));
|
assertThat(expected.getMessage(), containsString(
|
||||||
|
"function reference [String::isEmpty/2] matching [java.util.Comparator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWrongArityDef() {
|
public void testWrongArityDef() {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("def y = Optional.empty(); return y.orElseGet(String::startsWith);");
|
exec("def y = Optional.empty(); return y.orElseGet(String::startsWith);");
|
||||||
});
|
});
|
||||||
assertThat(expected.getMessage(), containsString("Unknown reference"));
|
assertThat(expected.getMessage(),
|
||||||
|
containsString("function reference [String::startsWith/0] matching [java.util.function.Supplier"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWrongArityNotEnoughDef() {
|
public void testWrongArityNotEnoughDef() {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("def l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
|
exec("def l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
|
||||||
});
|
});
|
||||||
assertThat(expected.getMessage(), containsString("Unknown reference"));
|
assertThat(expected.getMessage(),
|
||||||
|
containsString("function reference [String::isEmpty/2] matching [java.util.Comparator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReturnVoid() {
|
public void testReturnVoid() {
|
||||||
|
|
|
@ -184,7 +184,7 @@ public class LambdaTests extends ScriptTestCase {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("def y = Optional.empty(); return y.orElseGet(x -> x);");
|
exec("def y = Optional.empty(); return y.orElseGet(x -> x);");
|
||||||
});
|
});
|
||||||
assertTrue(expected.getMessage(), expected.getMessage().contains("Incorrect number of parameters"));
|
assertTrue(expected.getMessage(), expected.getMessage().contains("due to an incorrect number of arguments"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWrongArityNotEnough() {
|
public void testWrongArityNotEnough() {
|
||||||
|
@ -200,7 +200,7 @@ public class LambdaTests extends ScriptTestCase {
|
||||||
exec("def l = new ArrayList(); l.add(1); l.add(1); "
|
exec("def l = new ArrayList(); l.add(1); l.add(1); "
|
||||||
+ "return l.stream().mapToInt(() -> 5).sum();");
|
+ "return l.stream().mapToInt(() -> 5).sum();");
|
||||||
});
|
});
|
||||||
assertTrue(expected.getMessage().contains("Incorrect number of parameters"));
|
assertTrue(expected.getMessage(), expected.getMessage().contains("due to an incorrect number of arguments"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testLambdaInFunction() {
|
public void testLambdaInFunction() {
|
||||||
|
|
|
@ -756,7 +756,7 @@ public class PercolateQueryBuilder extends AbstractQueryBuilder<PercolateQueryBu
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <IFD extends IndexFieldData<?>> IFD getForField(MappedFieldType fieldType) {
|
public <IFD extends IndexFieldData<?>> IFD getForField(MappedFieldType fieldType) {
|
||||||
IndexFieldData.Builder builder = fieldType.fielddataBuilder(shardContext.getFullyQualifiedIndexName());
|
IndexFieldData.Builder builder = fieldType.fielddataBuilder(shardContext.getFullyQualifiedIndex().getName());
|
||||||
IndexFieldDataCache cache = new IndexFieldDataCache.None();
|
IndexFieldDataCache cache = new IndexFieldDataCache.None();
|
||||||
CircuitBreakerService circuitBreaker = new NoneCircuitBreakerService();
|
CircuitBreakerService circuitBreaker = new NoneCircuitBreakerService();
|
||||||
return (IFD) builder.build(shardContext.getIndexSettings(), fieldType, cache, circuitBreaker,
|
return (IFD) builder.build(shardContext.getIndexSettings(), fieldType, cache, circuitBreaker,
|
||||||
|
@ -764,5 +764,4 @@ public class PercolateQueryBuilder extends AbstractQueryBuilder<PercolateQueryBu
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
|
import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
|
||||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tiny helper to send http requests over netty.
|
* Tiny helper to send http requests over netty.
|
||||||
|
@ -145,7 +146,9 @@ class Netty4HttpClient implements Closeable {
|
||||||
for (HttpRequest request : requests) {
|
for (HttpRequest request : requests) {
|
||||||
channelFuture.channel().writeAndFlush(request);
|
channelFuture.channel().writeAndFlush(request);
|
||||||
}
|
}
|
||||||
latch.await(30, TimeUnit.SECONDS);
|
if (latch.await(30L, TimeUnit.SECONDS) == false) {
|
||||||
|
fail("Failed to get all expected responses.");
|
||||||
|
}
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
if (channelFuture != null) {
|
if (channelFuture != null) {
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
apply plugin: 'elasticsearch.esplugin'
|
||||||
|
|
||||||
|
esplugin {
|
||||||
|
name 'custom-suggester'
|
||||||
|
description 'An example plugin showing how to write and register a custom suggester'
|
||||||
|
classname 'org.elasticsearch.example.customsuggester.CustomSuggesterPlugin'
|
||||||
|
}
|
||||||
|
|
||||||
|
integTestCluster {
|
||||||
|
numNodes = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// this plugin has no unit tests, only rest tests
|
||||||
|
tasks.test.enabled = false
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch 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.example.customsuggester;
|
||||||
|
|
||||||
|
import org.apache.lucene.search.IndexSearcher;
|
||||||
|
import org.apache.lucene.util.CharsRefBuilder;
|
||||||
|
import org.elasticsearch.common.text.Text;
|
||||||
|
import org.elasticsearch.search.suggest.Suggest;
|
||||||
|
import org.elasticsearch.search.suggest.Suggester;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class CustomSuggester extends Suggester<CustomSuggestionContext> {
|
||||||
|
|
||||||
|
// This is a pretty dumb implementation which returns the original text + fieldName + custom config option + 12 or 123
|
||||||
|
@Override
|
||||||
|
public Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> innerExecute(
|
||||||
|
String name,
|
||||||
|
CustomSuggestionContext suggestion,
|
||||||
|
IndexSearcher searcher,
|
||||||
|
CharsRefBuilder spare) {
|
||||||
|
|
||||||
|
// Get the suggestion context
|
||||||
|
String text = suggestion.getText().utf8ToString();
|
||||||
|
|
||||||
|
// create two suggestions with 12 and 123 appended
|
||||||
|
CustomSuggestion response = new CustomSuggestion(name, suggestion.getSize(), "suggestion-dummy-value");
|
||||||
|
|
||||||
|
CustomSuggestion.Entry entry = new CustomSuggestion.Entry(new Text(text), 0, text.length(), "entry-dummy-value");
|
||||||
|
|
||||||
|
String firstOption =
|
||||||
|
String.format(Locale.ROOT, "%s-%s-%s-%s", text, suggestion.getField(), suggestion.options.get("suffix"), "12");
|
||||||
|
CustomSuggestion.Entry.Option option12 = new CustomSuggestion.Entry.Option(new Text(firstOption), 0.9f, "option-dummy-value-1");
|
||||||
|
entry.addOption(option12);
|
||||||
|
|
||||||
|
String secondOption =
|
||||||
|
String.format(Locale.ROOT, "%s-%s-%s-%s", text, suggestion.getField(), suggestion.options.get("suffix"), "123");
|
||||||
|
CustomSuggestion.Entry.Option option123 = new CustomSuggestion.Entry.Option(new Text(secondOption), 0.8f, "option-dummy-value-2");
|
||||||
|
entry.addOption(option123);
|
||||||
|
|
||||||
|
response.addTerm(entry);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch 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.example.customsuggester;
|
||||||
|
|
||||||
|
import org.elasticsearch.plugins.Plugin;
|
||||||
|
import org.elasticsearch.plugins.SearchPlugin;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CustomSuggesterPlugin extends Plugin implements SearchPlugin {
|
||||||
|
@Override
|
||||||
|
public List<SearchPlugin.SuggesterSpec<?>> getSuggesters() {
|
||||||
|
return Collections.singletonList(
|
||||||
|
new SearchPlugin.SuggesterSpec<>(
|
||||||
|
CustomSuggestionBuilder.SUGGESTION_NAME,
|
||||||
|
CustomSuggestionBuilder::new,
|
||||||
|
CustomSuggestionBuilder::fromXContent,
|
||||||
|
CustomSuggestion::new
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch 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.example.customsuggester;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.ParseField;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
import org.elasticsearch.common.text.Text;
|
||||||
|
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||||
|
import org.elasticsearch.common.xcontent.ObjectParser;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.search.suggest.Suggest;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||||
|
|
||||||
|
public class CustomSuggestion extends Suggest.Suggestion<CustomSuggestion.Entry> {
|
||||||
|
|
||||||
|
public static final int TYPE = 999;
|
||||||
|
|
||||||
|
public static final ParseField DUMMY = new ParseField("dummy");
|
||||||
|
|
||||||
|
private String dummy;
|
||||||
|
|
||||||
|
public CustomSuggestion(String name, int size, String dummy) {
|
||||||
|
super(name, size);
|
||||||
|
this.dummy = dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomSuggestion(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
dummy = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
super.writeTo(out);
|
||||||
|
out.writeString(dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWriteableName() {
|
||||||
|
return CustomSuggestionBuilder.SUGGESTION_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWriteableType() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A meaningless value used to test that plugin suggesters can add fields to their Suggestion types
|
||||||
|
*
|
||||||
|
* This can't be serialized to xcontent because Suggestions appear in xcontent as an array of entries, so there is no place
|
||||||
|
* to add a custom field. But we can still use a custom field internally and use it to define a Suggestion's behavior
|
||||||
|
*/
|
||||||
|
public String getDummy() {
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Entry newEntry() {
|
||||||
|
return new Entry();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Entry newEntry(StreamInput in) throws IOException {
|
||||||
|
return new Entry(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CustomSuggestion fromXContent(XContentParser parser, String name) throws IOException {
|
||||||
|
CustomSuggestion suggestion = new CustomSuggestion(name, -1, null);
|
||||||
|
parseEntries(parser, suggestion, Entry::fromXContent);
|
||||||
|
return suggestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Entry extends Suggest.Suggestion.Entry<CustomSuggestion.Entry.Option> {
|
||||||
|
|
||||||
|
private static final ObjectParser<Entry, Void> PARSER = new ObjectParser<>("CustomSuggestionEntryParser", true, Entry::new);
|
||||||
|
|
||||||
|
static {
|
||||||
|
declareCommonFields(PARSER);
|
||||||
|
PARSER.declareString((entry, dummy) -> entry.dummy = dummy, DUMMY);
|
||||||
|
PARSER.declareObjectArray(Entry::addOptions, (p, c) -> Option.fromXContent(p), new ParseField(OPTIONS));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String dummy;
|
||||||
|
|
||||||
|
public Entry() {}
|
||||||
|
|
||||||
|
public Entry(Text text, int offset, int length, String dummy) {
|
||||||
|
super(text, offset, length);
|
||||||
|
this.dummy = dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entry(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
dummy = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
super.writeTo(out);
|
||||||
|
out.writeString(dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Option newOption() {
|
||||||
|
return new Option();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Option newOption(StreamInput in) throws IOException {
|
||||||
|
return new Option(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* the value of dummy will always be the same, so this just tests that we can merge entries with custom fields
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void merge(Suggest.Suggestion.Entry<Option> otherEntry) {
|
||||||
|
dummy = ((Entry) otherEntry).getDummy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meaningless field used to test that plugin suggesters can add fields to their entries
|
||||||
|
*/
|
||||||
|
public String getDummy() {
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
|
builder = super.toXContent(builder, params);
|
||||||
|
builder.field(DUMMY.getPreferredName(), getDummy());
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Entry fromXContent(XContentParser parser) {
|
||||||
|
return PARSER.apply(parser, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Option extends Suggest.Suggestion.Entry.Option {
|
||||||
|
|
||||||
|
private static final ConstructingObjectParser<Option, Void> PARSER = new ConstructingObjectParser<>(
|
||||||
|
"CustomSuggestionObjectParser", true,
|
||||||
|
args -> {
|
||||||
|
Text text = new Text((String) args[0]);
|
||||||
|
float score = (float) args[1];
|
||||||
|
String dummy = (String) args[2];
|
||||||
|
return new Option(text, score, dummy);
|
||||||
|
});
|
||||||
|
|
||||||
|
static {
|
||||||
|
PARSER.declareString(constructorArg(), TEXT);
|
||||||
|
PARSER.declareFloat(constructorArg(), SCORE);
|
||||||
|
PARSER.declareString(constructorArg(), DUMMY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String dummy;
|
||||||
|
|
||||||
|
public Option() {}
|
||||||
|
|
||||||
|
public Option(Text text, float score, String dummy) {
|
||||||
|
super(text, score);
|
||||||
|
this.dummy = dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Option(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
dummy = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
super.writeTo(out);
|
||||||
|
out.writeString(dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A meaningless value used to test that plugin suggesters can add fields to their options
|
||||||
|
*/
|
||||||
|
public String getDummy() {
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* the value of dummy will always be the same, so this just tests that we can merge options with custom fields
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void mergeInto(Suggest.Suggestion.Entry.Option otherOption) {
|
||||||
|
super.mergeInto(otherOption);
|
||||||
|
dummy = ((Option) otherOption).getDummy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
|
builder = super.toXContent(builder, params);
|
||||||
|
builder.field(DUMMY.getPreferredName(), dummy);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Option fromXContent(XContentParser parser) {
|
||||||
|
return PARSER.apply(parser, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch 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.example.customsuggester;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.ParseField;
|
||||||
|
import org.elasticsearch.common.ParsingException;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
import org.elasticsearch.common.lucene.BytesRefs;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.index.query.QueryShardContext;
|
||||||
|
import org.elasticsearch.search.suggest.SuggestionBuilder;
|
||||||
|
import org.elasticsearch.search.suggest.SuggestionSearchContext;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class CustomSuggestionBuilder extends SuggestionBuilder<CustomSuggestionBuilder> {
|
||||||
|
|
||||||
|
public static final String SUGGESTION_NAME = "custom";
|
||||||
|
|
||||||
|
protected static final ParseField RANDOM_SUFFIX_FIELD = new ParseField("suffix");
|
||||||
|
|
||||||
|
private String randomSuffix;
|
||||||
|
|
||||||
|
public CustomSuggestionBuilder(String randomField, String randomSuffix) {
|
||||||
|
super(randomField);
|
||||||
|
this.randomSuffix = randomSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from a stream.
|
||||||
|
*/
|
||||||
|
public CustomSuggestionBuilder(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
this.randomSuffix = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doWriteTo(StreamOutput out) throws IOException {
|
||||||
|
out.writeString(randomSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
|
builder.field(RANDOM_SUFFIX_FIELD.getPreferredName(), randomSuffix);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWriteableName() {
|
||||||
|
return SUGGESTION_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doEquals(CustomSuggestionBuilder other) {
|
||||||
|
return Objects.equals(randomSuffix, other.randomSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int doHashCode() {
|
||||||
|
return Objects.hash(randomSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CustomSuggestionBuilder fromXContent(XContentParser parser) throws IOException {
|
||||||
|
XContentParser.Token token;
|
||||||
|
String currentFieldName = null;
|
||||||
|
String fieldname = null;
|
||||||
|
String suffix = null;
|
||||||
|
String analyzer = null;
|
||||||
|
int sizeField = -1;
|
||||||
|
int shardSize = -1;
|
||||||
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
|
if (token == XContentParser.Token.FIELD_NAME) {
|
||||||
|
currentFieldName = parser.currentName();
|
||||||
|
} else if (token.isValue()) {
|
||||||
|
if (SuggestionBuilder.ANALYZER_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||||
|
analyzer = parser.text();
|
||||||
|
} else if (SuggestionBuilder.FIELDNAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||||
|
fieldname = parser.text();
|
||||||
|
} else if (SuggestionBuilder.SIZE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||||
|
sizeField = parser.intValue();
|
||||||
|
} else if (SuggestionBuilder.SHARDSIZE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||||
|
shardSize = parser.intValue();
|
||||||
|
} else if (RANDOM_SUFFIX_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||||
|
suffix = parser.text();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ParsingException(parser.getTokenLocation(),
|
||||||
|
"suggester[custom] doesn't support field [" + currentFieldName + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we should have field name, check and copy fields over to the suggestion builder we return
|
||||||
|
if (fieldname == null) {
|
||||||
|
throw new ParsingException(parser.getTokenLocation(), "the required field option is missing");
|
||||||
|
}
|
||||||
|
CustomSuggestionBuilder builder = new CustomSuggestionBuilder(fieldname, suffix);
|
||||||
|
if (analyzer != null) {
|
||||||
|
builder.analyzer(analyzer);
|
||||||
|
}
|
||||||
|
if (sizeField != -1) {
|
||||||
|
builder.size(sizeField);
|
||||||
|
}
|
||||||
|
if (shardSize != -1) {
|
||||||
|
builder.shardSize(shardSize);
|
||||||
|
}
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SuggestionSearchContext.SuggestionContext build(QueryShardContext context) throws IOException {
|
||||||
|
Map<String, Object> options = new HashMap<>();
|
||||||
|
options.put(FIELDNAME_FIELD.getPreferredName(), field());
|
||||||
|
options.put(RANDOM_SUFFIX_FIELD.getPreferredName(), randomSuffix);
|
||||||
|
CustomSuggestionContext customSuggestionsContext = new CustomSuggestionContext(context, options);
|
||||||
|
customSuggestionsContext.setField(field());
|
||||||
|
assert text != null;
|
||||||
|
customSuggestionsContext.setText(BytesRefs.toBytesRef(text));
|
||||||
|
return customSuggestionsContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch 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.example.customsuggester;
|
||||||
|
|
||||||
|
import org.elasticsearch.index.query.QueryShardContext;
|
||||||
|
import org.elasticsearch.search.suggest.SuggestionSearchContext;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class CustomSuggestionContext extends SuggestionSearchContext.SuggestionContext {
|
||||||
|
|
||||||
|
public Map<String, Object> options;
|
||||||
|
|
||||||
|
public CustomSuggestionContext(QueryShardContext context, Map<String, Object> options) {
|
||||||
|
super(new CustomSuggester(), context);
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch 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.example.customsuggester;
|
||||||
|
|
||||||
|
import com.carrotsearch.randomizedtesting.annotations.Name;
|
||||||
|
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
||||||
|
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
|
||||||
|
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
|
||||||
|
|
||||||
|
public class CustomSuggesterClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
|
||||||
|
|
||||||
|
public CustomSuggesterClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
|
||||||
|
super(testCandidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParametersFactory
|
||||||
|
public static Iterable<Object[]> parameters() throws Exception {
|
||||||
|
return ESClientYamlSuiteTestCase.createParameters();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
# tests that the custom suggester plugin is installed
|
||||||
|
---
|
||||||
|
"plugin loaded":
|
||||||
|
- do:
|
||||||
|
cluster.state: {}
|
||||||
|
|
||||||
|
# Get master node id
|
||||||
|
- set: { master_node: master }
|
||||||
|
|
||||||
|
- do:
|
||||||
|
nodes.info: {}
|
||||||
|
|
||||||
|
- contains: { nodes.$master.plugins: { name: custom-suggester } }
|
|
@ -0,0 +1,55 @@
|
||||||
|
# tests that the custom suggester works
|
||||||
|
|
||||||
|
# the issue that prompted serializing Suggestion as a registered named writeable was not revealed until
|
||||||
|
# a user found that it would fail when reducing suggestions in a multi node envrionment
|
||||||
|
# https://github.com/elastic/elasticsearch/issues/26585
|
||||||
|
"test custom suggester":
|
||||||
|
- do:
|
||||||
|
cluster.health:
|
||||||
|
wait_for_nodes: 2
|
||||||
|
|
||||||
|
- is_true: cluster_name
|
||||||
|
- is_false: timed_out
|
||||||
|
- gte: { number_of_nodes: 2 }
|
||||||
|
- gte: { number_of_data_nodes: 2 }
|
||||||
|
|
||||||
|
- do:
|
||||||
|
indices.create:
|
||||||
|
index: test
|
||||||
|
body:
|
||||||
|
settings:
|
||||||
|
number_of_shards: 2
|
||||||
|
number_of_replicas: 0
|
||||||
|
|
||||||
|
- do:
|
||||||
|
bulk:
|
||||||
|
index: test
|
||||||
|
type: test
|
||||||
|
refresh: true
|
||||||
|
body: |
|
||||||
|
{ "index": {} }
|
||||||
|
{ "content": "these" }
|
||||||
|
{ "index": {} }
|
||||||
|
{ "content": "aren't" }
|
||||||
|
{ "index": {} }
|
||||||
|
{ "content": "actually" }
|
||||||
|
{ "index": {} }
|
||||||
|
{ "content": "used" }
|
||||||
|
|
||||||
|
- do:
|
||||||
|
search:
|
||||||
|
size: 0
|
||||||
|
index: test
|
||||||
|
body:
|
||||||
|
suggest:
|
||||||
|
test:
|
||||||
|
text: my suggestion text
|
||||||
|
custom:
|
||||||
|
field: arbitraryField
|
||||||
|
suffix: arbitrarySuffix
|
||||||
|
|
||||||
|
- match: { suggest.test.0.dummy: entry-dummy-value }
|
||||||
|
- match: { suggest.test.0.options.0.text: my suggestion text-arbitraryField-arbitrarySuffix-12 }
|
||||||
|
- match: { suggest.test.0.options.0.dummy: option-dummy-value-1 }
|
||||||
|
- match: { suggest.test.0.options.1.text: my suggestion text-arbitraryField-arbitrarySuffix-123 }
|
||||||
|
- match: { suggest.test.0.options.1.dummy: option-dummy-value-2 }
|
|
@ -1 +0,0 @@
|
||||||
5e924646d6f893bc9036939c5f2b4ecaee85e5da
|
|
|
@ -0,0 +1 @@
|
||||||
|
391de20b4e29cb3fb07d2454ace64be2c82ac91f
|
|
@ -1 +0,0 @@
|
||||||
f7b83cb2bc4b88d53961e749e1ad32f49ef017b7
|
|
|
@ -0,0 +1 @@
|
||||||
|
0569a9f220273024523799dba9dd358121b0ee09
|
|
@ -1,201 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch 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.http.nio;
|
|
||||||
|
|
||||||
import io.netty.bootstrap.Bootstrap;
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelInitializer;
|
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
|
||||||
import io.netty.channel.socket.SocketChannel;
|
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
|
||||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
import io.netty.handler.codec.http.HttpObject;
|
|
||||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
|
||||||
import io.netty.handler.codec.http.HttpRequestEncoder;
|
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
|
||||||
import io.netty.handler.codec.http.HttpResponseDecoder;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
import org.elasticsearch.common.collect.Tuple;
|
|
||||||
import org.elasticsearch.common.unit.ByteSizeUnit;
|
|
||||||
import org.elasticsearch.common.unit.ByteSizeValue;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
|
|
||||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tiny helper to send http requests over netty.
|
|
||||||
*/
|
|
||||||
class Netty4HttpClient implements Closeable {
|
|
||||||
|
|
||||||
static Collection<String> returnHttpResponseBodies(Collection<FullHttpResponse> responses) {
|
|
||||||
List<String> list = new ArrayList<>(responses.size());
|
|
||||||
for (FullHttpResponse response : responses) {
|
|
||||||
list.add(response.content().toString(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Collection<String> returnOpaqueIds(Collection<FullHttpResponse> responses) {
|
|
||||||
List<String> list = new ArrayList<>(responses.size());
|
|
||||||
for (HttpResponse response : responses) {
|
|
||||||
list.add(response.headers().get(Task.X_OPAQUE_ID));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Bootstrap clientBootstrap;
|
|
||||||
|
|
||||||
Netty4HttpClient() {
|
|
||||||
clientBootstrap = new Bootstrap().channel(NioSocketChannel.class).group(new NioEventLoopGroup());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<FullHttpResponse> get(SocketAddress remoteAddress, String... uris) throws InterruptedException {
|
|
||||||
Collection<HttpRequest> requests = new ArrayList<>(uris.length);
|
|
||||||
for (int i = 0; i < uris.length; i++) {
|
|
||||||
final HttpRequest httpRequest = new DefaultFullHttpRequest(HTTP_1_1, HttpMethod.GET, uris[i]);
|
|
||||||
httpRequest.headers().add(HOST, "localhost");
|
|
||||||
httpRequest.headers().add(Task.X_OPAQUE_ID, String.valueOf(i));
|
|
||||||
requests.add(httpRequest);
|
|
||||||
}
|
|
||||||
return sendRequests(remoteAddress, requests);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs // Safe not because it doesn't do anything with the type parameters but because it won't leak them into other methods.
|
|
||||||
public final Collection<FullHttpResponse> post(SocketAddress remoteAddress, Tuple<String, CharSequence>... urisAndBodies)
|
|
||||||
throws InterruptedException {
|
|
||||||
return processRequestsWithBody(HttpMethod.POST, remoteAddress, urisAndBodies);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final FullHttpResponse post(SocketAddress remoteAddress, FullHttpRequest httpRequest) throws InterruptedException {
|
|
||||||
Collection<FullHttpResponse> responses = sendRequests(remoteAddress, Collections.singleton(httpRequest));
|
|
||||||
assert responses.size() == 1 : "expected 1 and only 1 http response";
|
|
||||||
return responses.iterator().next();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs // Safe not because it doesn't do anything with the type parameters but because it won't leak them into other methods.
|
|
||||||
public final Collection<FullHttpResponse> put(SocketAddress remoteAddress, Tuple<String, CharSequence>... urisAndBodies)
|
|
||||||
throws InterruptedException {
|
|
||||||
return processRequestsWithBody(HttpMethod.PUT, remoteAddress, urisAndBodies);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<FullHttpResponse> processRequestsWithBody(HttpMethod method, SocketAddress remoteAddress, Tuple<String,
|
|
||||||
CharSequence>... urisAndBodies) throws InterruptedException {
|
|
||||||
Collection<HttpRequest> requests = new ArrayList<>(urisAndBodies.length);
|
|
||||||
for (Tuple<String, CharSequence> uriAndBody : urisAndBodies) {
|
|
||||||
ByteBuf content = Unpooled.copiedBuffer(uriAndBody.v2(), StandardCharsets.UTF_8);
|
|
||||||
HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, method, uriAndBody.v1(), content);
|
|
||||||
request.headers().add(HttpHeaderNames.HOST, "localhost");
|
|
||||||
request.headers().add(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
|
|
||||||
request.headers().add(HttpHeaderNames.CONTENT_TYPE, "application/json");
|
|
||||||
requests.add(request);
|
|
||||||
}
|
|
||||||
return sendRequests(remoteAddress, requests);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized Collection<FullHttpResponse> sendRequests(
|
|
||||||
final SocketAddress remoteAddress,
|
|
||||||
final Collection<HttpRequest> requests) throws InterruptedException {
|
|
||||||
final CountDownLatch latch = new CountDownLatch(requests.size());
|
|
||||||
final Collection<FullHttpResponse> content = Collections.synchronizedList(new ArrayList<>(requests.size()));
|
|
||||||
|
|
||||||
clientBootstrap.handler(new CountDownLatchHandler(latch, content));
|
|
||||||
|
|
||||||
ChannelFuture channelFuture = null;
|
|
||||||
try {
|
|
||||||
channelFuture = clientBootstrap.connect(remoteAddress);
|
|
||||||
channelFuture.sync();
|
|
||||||
|
|
||||||
for (HttpRequest request : requests) {
|
|
||||||
channelFuture.channel().writeAndFlush(request);
|
|
||||||
}
|
|
||||||
latch.await(30, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
if (channelFuture != null) {
|
|
||||||
channelFuture.channel().close().sync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
clientBootstrap.config().group().shutdownGracefully().awaitUninterruptibly();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper factory which adds returned data to a list and uses a count down latch to decide when done
|
|
||||||
*/
|
|
||||||
private static class CountDownLatchHandler extends ChannelInitializer<SocketChannel> {
|
|
||||||
|
|
||||||
private final CountDownLatch latch;
|
|
||||||
private final Collection<FullHttpResponse> content;
|
|
||||||
|
|
||||||
CountDownLatchHandler(final CountDownLatch latch, final Collection<FullHttpResponse> content) {
|
|
||||||
this.latch = latch;
|
|
||||||
this.content = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initChannel(SocketChannel ch) throws Exception {
|
|
||||||
final int maxContentLength = new ByteSizeValue(100, ByteSizeUnit.MB).bytesAsInt();
|
|
||||||
ch.pipeline().addLast(new HttpResponseDecoder());
|
|
||||||
ch.pipeline().addLast(new HttpRequestEncoder());
|
|
||||||
ch.pipeline().addLast(new HttpObjectAggregator(maxContentLength));
|
|
||||||
ch.pipeline().addLast(new SimpleChannelInboundHandler<HttpObject>() {
|
|
||||||
@Override
|
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
|
|
||||||
final FullHttpResponse response = (FullHttpResponse) msg;
|
|
||||||
content.add(response.copy());
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
||||||
super.exceptionCaught(ctx, cause);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch 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.http.nio;
|
||||||
|
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||||
|
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||||
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
|
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||||
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
|
import io.netty.handler.codec.http.HttpRequestEncoder;
|
||||||
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
|
import io.netty.handler.codec.http.HttpResponseDecoder;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.elasticsearch.action.ActionListener;
|
||||||
|
import org.elasticsearch.action.support.PlainActionFuture;
|
||||||
|
import org.elasticsearch.common.logging.Loggers;
|
||||||
|
import org.elasticsearch.common.network.NetworkService;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.unit.ByteSizeUnit;
|
||||||
|
import org.elasticsearch.common.unit.ByteSizeValue;
|
||||||
|
import org.elasticsearch.core.internal.io.IOUtils;
|
||||||
|
import org.elasticsearch.nio.BytesChannelContext;
|
||||||
|
import org.elasticsearch.nio.ChannelFactory;
|
||||||
|
import org.elasticsearch.nio.EventHandler;
|
||||||
|
import org.elasticsearch.nio.FlushOperation;
|
||||||
|
import org.elasticsearch.nio.InboundChannelBuffer;
|
||||||
|
import org.elasticsearch.nio.NioGroup;
|
||||||
|
import org.elasticsearch.nio.NioSelector;
|
||||||
|
import org.elasticsearch.nio.NioServerSocketChannel;
|
||||||
|
import org.elasticsearch.nio.NioSocketChannel;
|
||||||
|
import org.elasticsearch.nio.ReadWriteHandler;
|
||||||
|
import org.elasticsearch.nio.SocketChannelContext;
|
||||||
|
import org.elasticsearch.nio.WriteOperation;
|
||||||
|
import org.elasticsearch.tasks.Task;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.channels.ServerSocketChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
|
||||||
|
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||||
|
import static org.elasticsearch.common.util.concurrent.EsExecutors.daemonThreadFactory;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny helper to send http requests over nio.
|
||||||
|
*/
|
||||||
|
class NioHttpClient implements Closeable {
|
||||||
|
|
||||||
|
static Collection<String> returnOpaqueIds(Collection<FullHttpResponse> responses) {
|
||||||
|
List<String> list = new ArrayList<>(responses.size());
|
||||||
|
for (HttpResponse response : responses) {
|
||||||
|
list.add(response.headers().get(Task.X_OPAQUE_ID));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final NioGroup nioGroup;
|
||||||
|
private final Logger logger;
|
||||||
|
|
||||||
|
NioHttpClient() {
|
||||||
|
logger = Loggers.getLogger(NioHttpClient.class, Settings.EMPTY);
|
||||||
|
try {
|
||||||
|
nioGroup = new NioGroup(daemonThreadFactory(Settings.EMPTY, "nio-http-client"), 1,
|
||||||
|
(s) -> new EventHandler(this::onException, s));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<FullHttpResponse> get(InetSocketAddress remoteAddress, String... uris) throws InterruptedException {
|
||||||
|
Collection<HttpRequest> requests = new ArrayList<>(uris.length);
|
||||||
|
for (int i = 0; i < uris.length; i++) {
|
||||||
|
final HttpRequest httpRequest = new DefaultFullHttpRequest(HTTP_1_1, HttpMethod.GET, uris[i]);
|
||||||
|
httpRequest.headers().add(HOST, "localhost");
|
||||||
|
httpRequest.headers().add(Task.X_OPAQUE_ID, String.valueOf(i));
|
||||||
|
requests.add(httpRequest);
|
||||||
|
}
|
||||||
|
return sendRequests(remoteAddress, requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final FullHttpResponse post(InetSocketAddress remoteAddress, FullHttpRequest httpRequest) throws InterruptedException {
|
||||||
|
Collection<FullHttpResponse> responses = sendRequests(remoteAddress, Collections.singleton(httpRequest));
|
||||||
|
assert responses.size() == 1 : "expected 1 and only 1 http response";
|
||||||
|
return responses.iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onException(Exception e) {
|
||||||
|
logger.error("Exception from http client", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized Collection<FullHttpResponse> sendRequests(InetSocketAddress remoteAddress, Collection<HttpRequest> requests)
|
||||||
|
throws InterruptedException {
|
||||||
|
final CountDownLatch latch = new CountDownLatch(requests.size());
|
||||||
|
final Collection<FullHttpResponse> content = Collections.synchronizedList(new ArrayList<>(requests.size()));
|
||||||
|
|
||||||
|
ChannelFactory<NioServerSocketChannel, NioSocketChannel> factory = new ClientChannelFactory(latch, content);
|
||||||
|
|
||||||
|
NioSocketChannel nioSocketChannel = null;
|
||||||
|
try {
|
||||||
|
nioSocketChannel = nioGroup.openChannel(remoteAddress, factory);
|
||||||
|
PlainActionFuture<Void> connectFuture = PlainActionFuture.newFuture();
|
||||||
|
nioSocketChannel.addConnectListener(ActionListener.toBiConsumer(connectFuture));
|
||||||
|
connectFuture.actionGet();
|
||||||
|
|
||||||
|
for (HttpRequest request : requests) {
|
||||||
|
nioSocketChannel.getContext().sendMessage(request, (v, e) -> {});
|
||||||
|
}
|
||||||
|
if (latch.await(30L, TimeUnit.SECONDS) == false) {
|
||||||
|
fail("Failed to get all expected responses.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
} finally {
|
||||||
|
if (nioSocketChannel != null) {
|
||||||
|
nioSocketChannel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
IOUtils.closeWhileHandlingException(nioGroup::close);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClientChannelFactory extends ChannelFactory<NioServerSocketChannel, NioSocketChannel> {
|
||||||
|
|
||||||
|
private final CountDownLatch latch;
|
||||||
|
private final Collection<FullHttpResponse> content;
|
||||||
|
|
||||||
|
private ClientChannelFactory(CountDownLatch latch, Collection<FullHttpResponse> content) {
|
||||||
|
super(new RawChannelFactory(NetworkService.TCP_NO_DELAY.get(Settings.EMPTY),
|
||||||
|
NetworkService.TCP_KEEP_ALIVE.get(Settings.EMPTY),
|
||||||
|
NetworkService.TCP_REUSE_ADDRESS.get(Settings.EMPTY),
|
||||||
|
Math.toIntExact(NetworkService.TCP_SEND_BUFFER_SIZE.get(Settings.EMPTY).getBytes()),
|
||||||
|
Math.toIntExact(NetworkService.TCP_RECEIVE_BUFFER_SIZE.get(Settings.EMPTY).getBytes())));
|
||||||
|
this.latch = latch;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NioSocketChannel createChannel(NioSelector selector, java.nio.channels.SocketChannel channel) throws IOException {
|
||||||
|
NioSocketChannel nioSocketChannel = new NioSocketChannel(channel);
|
||||||
|
HttpClientHandler handler = new HttpClientHandler(nioSocketChannel, latch, content);
|
||||||
|
Consumer<Exception> exceptionHandler = (e) -> {
|
||||||
|
latch.countDown();
|
||||||
|
onException(e);
|
||||||
|
nioSocketChannel.close();
|
||||||
|
};
|
||||||
|
SocketChannelContext context = new BytesChannelContext(nioSocketChannel, selector, exceptionHandler, handler,
|
||||||
|
InboundChannelBuffer.allocatingInstance());
|
||||||
|
nioSocketChannel.setContext(context);
|
||||||
|
return nioSocketChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NioServerSocketChannel createServerChannel(NioSelector selector, ServerSocketChannel channel) {
|
||||||
|
throw new UnsupportedOperationException("Cannot create server channel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class HttpClientHandler implements ReadWriteHandler {
|
||||||
|
|
||||||
|
private final NettyAdaptor adaptor;
|
||||||
|
private final CountDownLatch latch;
|
||||||
|
private final Collection<FullHttpResponse> content;
|
||||||
|
|
||||||
|
private HttpClientHandler(NioSocketChannel channel, CountDownLatch latch, Collection<FullHttpResponse> content) {
|
||||||
|
this.latch = latch;
|
||||||
|
this.content = content;
|
||||||
|
final int maxContentLength = Math.toIntExact(new ByteSizeValue(100, ByteSizeUnit.MB).getBytes());
|
||||||
|
List<ChannelHandler> handlers = new ArrayList<>(5);
|
||||||
|
handlers.add(new HttpResponseDecoder());
|
||||||
|
handlers.add(new HttpRequestEncoder());
|
||||||
|
handlers.add(new HttpObjectAggregator(maxContentLength));
|
||||||
|
|
||||||
|
adaptor = new NettyAdaptor(handlers.toArray(new ChannelHandler[0]));
|
||||||
|
adaptor.addCloseListener((v, e) -> channel.close());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WriteOperation createWriteOperation(SocketChannelContext context, Object message, BiConsumer<Void, Exception> listener) {
|
||||||
|
assert message instanceof HttpRequest : "Expected type HttpRequest.class, found: " + message.getClass();
|
||||||
|
return new WriteOperation() {
|
||||||
|
@Override
|
||||||
|
public BiConsumer<Void, Exception> getListener() {
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketChannelContext getChannel() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<FlushOperation> writeToBytes(WriteOperation writeOperation) {
|
||||||
|
adaptor.write(writeOperation);
|
||||||
|
return pollFlushOperations();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<FlushOperation> pollFlushOperations() {
|
||||||
|
ArrayList<FlushOperation> copiedOperations = new ArrayList<>(adaptor.getOutboundCount());
|
||||||
|
FlushOperation flushOperation;
|
||||||
|
while ((flushOperation = adaptor.pollOutboundOperation()) != null) {
|
||||||
|
copiedOperations.add(flushOperation);
|
||||||
|
}
|
||||||
|
return copiedOperations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int consumeReads(InboundChannelBuffer channelBuffer) throws IOException {
|
||||||
|
int bytesConsumed = adaptor.read(channelBuffer.sliceAndRetainPagesTo(channelBuffer.getIndex()));
|
||||||
|
Object message;
|
||||||
|
while ((message = adaptor.pollInboundMessage()) != null) {
|
||||||
|
handleRequest(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesConsumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
try {
|
||||||
|
adaptor.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleRequest(Object message) {
|
||||||
|
final FullHttpResponse response = (FullHttpResponse) message;
|
||||||
|
DefaultFullHttpResponse newResponse = new DefaultFullHttpResponse(response.protocolVersion(),
|
||||||
|
response.status(),
|
||||||
|
Unpooled.copiedBuffer(response.content()),
|
||||||
|
response.headers().copy(),
|
||||||
|
response.trailingHeaders().copy());
|
||||||
|
response.release();
|
||||||
|
content.add(newResponse);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -192,7 +192,7 @@ public class NioHttpServerTransportTests extends ESTestCase {
|
||||||
xContentRegistry(), dispatcher)) {
|
xContentRegistry(), dispatcher)) {
|
||||||
transport.start();
|
transport.start();
|
||||||
final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses());
|
final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses());
|
||||||
try (Netty4HttpClient client = new Netty4HttpClient()) {
|
try (NioHttpClient client = new NioHttpClient()) {
|
||||||
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
|
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
|
||||||
request.headers().set(HttpHeaderNames.EXPECT, expectation);
|
request.headers().set(HttpHeaderNames.EXPECT, expectation);
|
||||||
HttpUtil.setContentLength(request, contentLength);
|
HttpUtil.setContentLength(request, contentLength);
|
||||||
|
@ -275,7 +275,7 @@ public class NioHttpServerTransportTests extends ESTestCase {
|
||||||
transport.start();
|
transport.start();
|
||||||
final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses());
|
final TransportAddress remoteAddress = randomFrom(transport.boundAddress().boundAddresses());
|
||||||
|
|
||||||
try (Netty4HttpClient client = new Netty4HttpClient()) {
|
try (NioHttpClient client = new NioHttpClient()) {
|
||||||
final String url = "/" + new String(new byte[maxInitialLineLength], Charset.forName("UTF-8"));
|
final String url = "/" + new String(new byte[maxInitialLineLength], Charset.forName("UTF-8"));
|
||||||
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, url);
|
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, url);
|
||||||
|
|
||||||
|
|
|
@ -47,11 +47,11 @@ public class NioPipeliningIT extends NioIntegTestCase {
|
||||||
TransportAddress[] boundAddresses = httpServerTransport.boundAddress().boundAddresses();
|
TransportAddress[] boundAddresses = httpServerTransport.boundAddress().boundAddresses();
|
||||||
TransportAddress transportAddress = randomFrom(boundAddresses);
|
TransportAddress transportAddress = randomFrom(boundAddresses);
|
||||||
|
|
||||||
try (Netty4HttpClient nettyHttpClient = new Netty4HttpClient()) {
|
try (NioHttpClient nettyHttpClient = new NioHttpClient()) {
|
||||||
Collection<FullHttpResponse> responses = nettyHttpClient.get(transportAddress.address(), requests);
|
Collection<FullHttpResponse> responses = nettyHttpClient.get(transportAddress.address(), requests);
|
||||||
assertThat(responses, hasSize(5));
|
assertThat(responses, hasSize(5));
|
||||||
|
|
||||||
Collection<String> opaqueIds = Netty4HttpClient.returnOpaqueIds(responses);
|
Collection<String> opaqueIds = NioHttpClient.returnOpaqueIds(responses);
|
||||||
assertOpaqueIdsInOrder(opaqueIds);
|
assertOpaqueIdsInOrder(opaqueIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,7 +189,10 @@ setup() {
|
||||||
|
|
||||||
@test "[SYSTEMD] start Elasticsearch with custom JVM options" {
|
@test "[SYSTEMD] start Elasticsearch with custom JVM options" {
|
||||||
assert_file_exist $ESENVFILE
|
assert_file_exist $ESENVFILE
|
||||||
local temp=`mktemp -d`
|
# The custom config directory is not under /tmp or /var/tmp because
|
||||||
|
# systemd's private temp directory functionaly means different
|
||||||
|
# processes can have different views of what's in these directories
|
||||||
|
local temp=`mktemp -p /etc -d`
|
||||||
cp "$ESCONFIG"/elasticsearch.yml "$temp"
|
cp "$ESCONFIG"/elasticsearch.yml "$temp"
|
||||||
cp "$ESCONFIG"/log4j2.properties "$temp"
|
cp "$ESCONFIG"/log4j2.properties "$temp"
|
||||||
touch "$temp/jvm.options"
|
touch "$temp/jvm.options"
|
||||||
|
|
|
@ -92,11 +92,14 @@ fi
|
||||||
|
|
||||||
@test "[$GROUP] install a sample plugin with a symlinked plugins path" {
|
@test "[$GROUP] install a sample plugin with a symlinked plugins path" {
|
||||||
# Clean up after the last time this test was run
|
# Clean up after the last time this test was run
|
||||||
rm -rf /tmp/plugins.*
|
rm -rf /var/plugins.*
|
||||||
rm -rf /tmp/old_plugins.*
|
rm -rf /var/old_plugins.*
|
||||||
|
|
||||||
rm -rf "$ESPLUGINS"
|
rm -rf "$ESPLUGINS"
|
||||||
local es_plugins=$(mktemp -d -t 'plugins.XXXX')
|
# The custom plugins directory is not under /tmp or /var/tmp because
|
||||||
|
# systemd's private temp directory functionaly means different
|
||||||
|
# processes can have different views of what's in these directories
|
||||||
|
local es_plugins=$(mktemp -p /var -d -t 'plugins.XXXX')
|
||||||
chown -R elasticsearch:elasticsearch "$es_plugins"
|
chown -R elasticsearch:elasticsearch "$es_plugins"
|
||||||
ln -s "$es_plugins" "$ESPLUGINS"
|
ln -s "$es_plugins" "$ESPLUGINS"
|
||||||
|
|
||||||
|
|
|
@ -555,7 +555,10 @@ run_elasticsearch_tests() {
|
||||||
# Move the config directory to another directory and properly chown it.
|
# Move the config directory to another directory and properly chown it.
|
||||||
move_config() {
|
move_config() {
|
||||||
local oldConfig="$ESCONFIG"
|
local oldConfig="$ESCONFIG"
|
||||||
export ESCONFIG="${1:-$(mktemp -d -t 'config.XXXX')}"
|
# The custom config directory is not under /tmp or /var/tmp because
|
||||||
|
# systemd's private temp directory functionaly means different
|
||||||
|
# processes can have different views of what's in these directories
|
||||||
|
export ESCONFIG="${1:-$(mktemp -p /etc -d -t 'config.XXXX')}"
|
||||||
echo "Moving configuration directory from $oldConfig to $ESCONFIG"
|
echo "Moving configuration directory from $oldConfig to $ESCONFIG"
|
||||||
|
|
||||||
# Move configuration files to the new configuration directory
|
# Move configuration files to the new configuration directory
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
setup:
|
setup:
|
||||||
- do:
|
- do:
|
||||||
indices.create:
|
indices.create:
|
||||||
index: test
|
index: test
|
||||||
|
body:
|
||||||
|
mappings:
|
||||||
|
test:
|
||||||
|
properties:
|
||||||
|
numeric_group: { type: integer }
|
||||||
|
group_alias: { type: alias, path: numeric_group }
|
||||||
|
|
||||||
- do:
|
- do:
|
||||||
index:
|
index:
|
||||||
index: test
|
index: test
|
||||||
|
@ -341,3 +348,25 @@ setup:
|
||||||
- match: { hits.hits.2.inner_hits.sub_hits.hits.hits.0._version: 55 }
|
- match: { hits.hits.2.inner_hits.sub_hits.hits.hits.0._version: 55 }
|
||||||
- match: { hits.hits.2.inner_hits.sub_hits.hits.hits.1._id: "4" }
|
- match: { hits.hits.2.inner_hits.sub_hits.hits.hits.1._id: "4" }
|
||||||
- match: { hits.hits.2.inner_hits.sub_hits.hits.hits.1._version: 44 }
|
- match: { hits.hits.2.inner_hits.sub_hits.hits.hits.1._version: 44 }
|
||||||
|
|
||||||
|
---
|
||||||
|
"field collapsing on a field alias":
|
||||||
|
- skip:
|
||||||
|
version: " - 6.3.99"
|
||||||
|
reason: Field aliases were introduced in 6.4.0.
|
||||||
|
- do:
|
||||||
|
search:
|
||||||
|
index: test
|
||||||
|
body:
|
||||||
|
collapse: { field: group_alias, inner_hits: { name: sub_hits } }
|
||||||
|
sort: [{ sort: desc }]
|
||||||
|
|
||||||
|
- match: { hits.total: 6 }
|
||||||
|
- length: { hits.hits: 3 }
|
||||||
|
|
||||||
|
- match: { hits.hits.0.fields.group_alias: [3] }
|
||||||
|
- match: { hits.hits.0.inner_hits.sub_hits.hits.total: 1}
|
||||||
|
- match: { hits.hits.1.fields.group_alias: [1] }
|
||||||
|
- match: { hits.hits.1.inner_hits.sub_hits.hits.total: 3}
|
||||||
|
- match: { hits.hits.2.fields.group_alias: [25] }
|
||||||
|
- match: { hits.hits.2.inner_hits.sub_hits.hits.total: 2}
|
||||||
|
|
|
@ -308,13 +308,8 @@ public final class ExceptionsHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.index = indexName;
|
this.index = indexName;
|
||||||
if (cause == null) {
|
this.reason = cause.getMessage();
|
||||||
this.reason = failure.reason();
|
this.causeType = cause.getClass();
|
||||||
this.causeType = null;
|
|
||||||
} else {
|
|
||||||
this.reason = cause.getMessage();
|
|
||||||
this.causeType = cause.getClass();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -19,39 +19,70 @@
|
||||||
|
|
||||||
package org.elasticsearch.action;
|
package org.elasticsearch.action;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.io.stream.Streamable;
|
import org.elasticsearch.common.io.stream.Streamable;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.rest.RestStatus;
|
import org.elasticsearch.rest.RestStatus;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An exception indicating that a failure occurred performing an operation on the shard.
|
* An exception indicating that a failure occurred performing an operation on the shard.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public interface ShardOperationFailedException extends Streamable, ToXContent {
|
public abstract class ShardOperationFailedException implements Streamable, ToXContent {
|
||||||
|
|
||||||
|
protected String index;
|
||||||
|
protected int shardId;
|
||||||
|
protected String reason;
|
||||||
|
protected RestStatus status;
|
||||||
|
protected Throwable cause;
|
||||||
|
|
||||||
|
protected ShardOperationFailedException() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ShardOperationFailedException(@Nullable String index, int shardId, String reason, RestStatus status, Throwable cause) {
|
||||||
|
this.index = index;
|
||||||
|
this.shardId = shardId;
|
||||||
|
this.reason = Objects.requireNonNull(reason, "reason cannot be null");
|
||||||
|
this.status = Objects.requireNonNull(status, "status cannot be null");
|
||||||
|
this.cause = Objects.requireNonNull(cause, "cause cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The index the operation failed on. Might return {@code null} if it can't be derived.
|
* The index the operation failed on. Might return {@code null} if it can't be derived.
|
||||||
*/
|
*/
|
||||||
String index();
|
@Nullable
|
||||||
|
public final String index() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The index the operation failed on. Might return {@code -1} if it can't be derived.
|
* The index the operation failed on. Might return {@code -1} if it can't be derived.
|
||||||
*/
|
*/
|
||||||
int shardId();
|
public final int shardId() {
|
||||||
|
return shardId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The reason of the failure.
|
* The reason of the failure.
|
||||||
*/
|
*/
|
||||||
String reason();
|
public final String reason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The status of the failure.
|
* The status of the failure.
|
||||||
*/
|
*/
|
||||||
RestStatus status();
|
public final RestStatus status() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cause of this failure
|
* The cause of this failure
|
||||||
*/
|
*/
|
||||||
Throwable getCause();
|
public final Throwable getCause() {
|
||||||
|
return cause;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ package org.elasticsearch.action.admin.indices.shards;
|
||||||
|
|
||||||
import com.carrotsearch.hppc.cursors.IntObjectCursor;
|
import com.carrotsearch.hppc.cursors.IntObjectCursor;
|
||||||
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
|
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.action.ActionResponse;
|
import org.elasticsearch.action.ActionResponse;
|
||||||
|
@ -248,7 +247,7 @@ public class IndicesShardStoresResponse extends ActionResponse implements ToXCon
|
||||||
return nodeId;
|
return nodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Failure readFailure(StreamInput in) throws IOException {
|
static Failure readFailure(StreamInput in) throws IOException {
|
||||||
Failure failure = new Failure();
|
Failure failure = new Failure();
|
||||||
failure.readFrom(in);
|
failure.readFrom(in);
|
||||||
return failure;
|
return failure;
|
||||||
|
|
|
@ -138,8 +138,7 @@ public class SearchPhaseExecutionException extends ElasticsearchException {
|
||||||
builder.field("grouped", group); // notify that it's grouped
|
builder.field("grouped", group); // notify that it's grouped
|
||||||
builder.field("failed_shards");
|
builder.field("failed_shards");
|
||||||
builder.startArray();
|
builder.startArray();
|
||||||
ShardOperationFailedException[] failures = params.paramAsBoolean("group_shard_failures", true) ?
|
ShardOperationFailedException[] failures = group ? ExceptionsHelper.groupBy(shardFailures) : shardFailures;
|
||||||
ExceptionsHelper.groupBy(shardFailures) : shardFailures;
|
|
||||||
for (ShardOperationFailedException failure : failures) {
|
for (ShardOperationFailedException failure : failures) {
|
||||||
builder.startObject();
|
builder.startObject();
|
||||||
failure.toXContent(builder, params);
|
failure.toXContent(builder, params);
|
||||||
|
|
|
@ -43,7 +43,7 @@ import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpect
|
||||||
/**
|
/**
|
||||||
* Represents a failure to search on a specific shard.
|
* Represents a failure to search on a specific shard.
|
||||||
*/
|
*/
|
||||||
public class ShardSearchFailure implements ShardOperationFailedException {
|
public class ShardSearchFailure extends ShardOperationFailedException {
|
||||||
|
|
||||||
private static final String REASON_FIELD = "reason";
|
private static final String REASON_FIELD = "reason";
|
||||||
private static final String NODE_FIELD = "node";
|
private static final String NODE_FIELD = "node";
|
||||||
|
@ -53,9 +53,6 @@ public class ShardSearchFailure implements ShardOperationFailedException {
|
||||||
public static final ShardSearchFailure[] EMPTY_ARRAY = new ShardSearchFailure[0];
|
public static final ShardSearchFailure[] EMPTY_ARRAY = new ShardSearchFailure[0];
|
||||||
|
|
||||||
private SearchShardTarget shardTarget;
|
private SearchShardTarget shardTarget;
|
||||||
private String reason;
|
|
||||||
private RestStatus status;
|
|
||||||
private Throwable cause;
|
|
||||||
|
|
||||||
private ShardSearchFailure() {
|
private ShardSearchFailure() {
|
||||||
|
|
||||||
|
@ -66,25 +63,18 @@ public class ShardSearchFailure implements ShardOperationFailedException {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShardSearchFailure(Exception e, @Nullable SearchShardTarget shardTarget) {
|
public ShardSearchFailure(Exception e, @Nullable SearchShardTarget shardTarget) {
|
||||||
|
super(shardTarget == null ? null : shardTarget.getFullyQualifiedIndexName(),
|
||||||
|
shardTarget == null ? -1 : shardTarget.getShardId().getId(),
|
||||||
|
ExceptionsHelper.detailedMessage(e),
|
||||||
|
ExceptionsHelper.status(ExceptionsHelper.unwrapCause(e)),
|
||||||
|
ExceptionsHelper.unwrapCause(e));
|
||||||
|
|
||||||
final Throwable actual = ExceptionsHelper.unwrapCause(e);
|
final Throwable actual = ExceptionsHelper.unwrapCause(e);
|
||||||
if (actual instanceof SearchException) {
|
if (actual instanceof SearchException) {
|
||||||
this.shardTarget = ((SearchException) actual).shard();
|
this.shardTarget = ((SearchException) actual).shard();
|
||||||
} else if (shardTarget != null) {
|
} else if (shardTarget != null) {
|
||||||
this.shardTarget = shardTarget;
|
this.shardTarget = shardTarget;
|
||||||
}
|
}
|
||||||
status = ExceptionsHelper.status(actual);
|
|
||||||
this.reason = ExceptionsHelper.detailedMessage(e);
|
|
||||||
this.cause = actual;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShardSearchFailure(String reason, SearchShardTarget shardTarget) {
|
|
||||||
this(reason, shardTarget, RestStatus.INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ShardSearchFailure(String reason, SearchShardTarget shardTarget, RestStatus status) {
|
|
||||||
this.shardTarget = shardTarget;
|
|
||||||
this.reason = reason;
|
|
||||||
this.status = status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,41 +85,6 @@ public class ShardSearchFailure implements ShardOperationFailedException {
|
||||||
return this.shardTarget;
|
return this.shardTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public RestStatus status() {
|
|
||||||
return this.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The index the search failed on.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String index() {
|
|
||||||
if (shardTarget != null) {
|
|
||||||
return shardTarget.getFullyQualifiedIndexName();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The shard id the search failed on.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int shardId() {
|
|
||||||
if (shardTarget != null) {
|
|
||||||
return shardTarget.getShardId().id();
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The reason of the failure.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String reason() {
|
|
||||||
return this.reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "shard [" + (shardTarget == null ? "_na" : shardTarget) + "], reason [" + reason + "], cause [" +
|
return "shard [" + (shardTarget == null ? "_na" : shardTarget) + "], reason [" + reason + "], cause [" +
|
||||||
|
@ -172,12 +127,10 @@ public class ShardSearchFailure implements ShardOperationFailedException {
|
||||||
if (shardTarget != null) {
|
if (shardTarget != null) {
|
||||||
builder.field(NODE_FIELD, shardTarget.getNodeId());
|
builder.field(NODE_FIELD, shardTarget.getNodeId());
|
||||||
}
|
}
|
||||||
if (cause != null) {
|
builder.field(REASON_FIELD);
|
||||||
builder.field(REASON_FIELD);
|
builder.startObject();
|
||||||
builder.startObject();
|
ElasticsearchException.generateThrowableXContent(builder, params, cause);
|
||||||
ElasticsearchException.generateThrowableXContent(builder, params, cause);
|
builder.endObject();
|
||||||
builder.endObject();
|
|
||||||
}
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,9 +178,4 @@ public class ShardSearchFailure implements ShardOperationFailedException {
|
||||||
}
|
}
|
||||||
return new ShardSearchFailure(exception, searchShardTarget);
|
return new ShardSearchFailure(exception, searchShardTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Throwable getCause() {
|
|
||||||
return cause;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,6 @@ import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.index.Index;
|
|
||||||
import org.elasticsearch.index.shard.ShardId;
|
|
||||||
import org.elasticsearch.rest.RestStatus;
|
import org.elasticsearch.rest.RestStatus;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -37,7 +35,7 @@ import java.io.IOException;
|
||||||
import static org.elasticsearch.ExceptionsHelper.detailedMessage;
|
import static org.elasticsearch.ExceptionsHelper.detailedMessage;
|
||||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||||
|
|
||||||
public class DefaultShardOperationFailedException implements ShardOperationFailedException {
|
public class DefaultShardOperationFailedException extends ShardOperationFailedException {
|
||||||
|
|
||||||
private static final String INDEX = "index";
|
private static final String INDEX = "index";
|
||||||
private static final String SHARD_ID = "shard";
|
private static final String SHARD_ID = "shard";
|
||||||
|
@ -52,56 +50,16 @@ public class DefaultShardOperationFailedException implements ShardOperationFaile
|
||||||
PARSER.declareObject(constructorArg(), (p, c) -> ElasticsearchException.fromXContent(p), new ParseField(REASON));
|
PARSER.declareObject(constructorArg(), (p, c) -> ElasticsearchException.fromXContent(p), new ParseField(REASON));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String index;
|
|
||||||
|
|
||||||
private int shardId;
|
|
||||||
|
|
||||||
private Throwable reason;
|
|
||||||
|
|
||||||
private RestStatus status;
|
|
||||||
|
|
||||||
protected DefaultShardOperationFailedException() {
|
protected DefaultShardOperationFailedException() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultShardOperationFailedException(ElasticsearchException e) {
|
public DefaultShardOperationFailedException(ElasticsearchException e) {
|
||||||
Index index = e.getIndex();
|
super(e.getIndex() == null ? null : e.getIndex().getName(), e.getShardId() == null ? -1 : e.getShardId().getId(),
|
||||||
this.index = index == null ? null : index.getName();
|
detailedMessage(e), e.status(), e);
|
||||||
ShardId shardId = e.getShardId();
|
|
||||||
this.shardId = shardId == null ? -1 : shardId.id();
|
|
||||||
this.reason = e;
|
|
||||||
this.status = e.status();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultShardOperationFailedException(String index, int shardId, Throwable reason) {
|
public DefaultShardOperationFailedException(String index, int shardId, Throwable cause) {
|
||||||
this.index = index;
|
super(index, shardId, detailedMessage(cause), ExceptionsHelper.status(cause), cause);
|
||||||
this.shardId = shardId;
|
|
||||||
this.reason = reason;
|
|
||||||
this.status = ExceptionsHelper.status(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String index() {
|
|
||||||
return this.index;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int shardId() {
|
|
||||||
return this.shardId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String reason() {
|
|
||||||
return detailedMessage(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RestStatus status() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Throwable getCause() {
|
|
||||||
return reason;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DefaultShardOperationFailedException readShardOperationFailed(StreamInput in) throws IOException {
|
public static DefaultShardOperationFailedException readShardOperationFailed(StreamInput in) throws IOException {
|
||||||
|
@ -112,24 +70,17 @@ public class DefaultShardOperationFailedException implements ShardOperationFaile
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
if (in.readBoolean()) {
|
index = in.readOptionalString();
|
||||||
index = in.readString();
|
|
||||||
}
|
|
||||||
shardId = in.readVInt();
|
shardId = in.readVInt();
|
||||||
reason = in.readException();
|
cause = in.readException();
|
||||||
status = RestStatus.readFrom(in);
|
status = RestStatus.readFrom(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
if (index == null) {
|
out.writeOptionalString(index);
|
||||||
out.writeBoolean(false);
|
|
||||||
} else {
|
|
||||||
out.writeBoolean(true);
|
|
||||||
out.writeString(index);
|
|
||||||
}
|
|
||||||
out.writeVInt(shardId);
|
out.writeVInt(shardId);
|
||||||
out.writeException(reason);
|
out.writeException(cause);
|
||||||
RestStatus.writeTo(out, status);
|
RestStatus.writeTo(out, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +96,7 @@ public class DefaultShardOperationFailedException implements ShardOperationFaile
|
||||||
builder.field("status", status.name());
|
builder.field("status", status.name());
|
||||||
if (reason != null) {
|
if (reason != null) {
|
||||||
builder.startObject("reason");
|
builder.startObject("reason");
|
||||||
ElasticsearchException.generateThrowableXContent(builder, params, reason);
|
ElasticsearchException.generateThrowableXContent(builder, params, cause);
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
}
|
}
|
||||||
return builder;
|
return builder;
|
||||||
|
|
|
@ -218,13 +218,13 @@ public class ReplicationResponse extends ActionResponse {
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ShardInfo readShardInfo(StreamInput in) throws IOException {
|
static ShardInfo readShardInfo(StreamInput in) throws IOException {
|
||||||
ShardInfo shardInfo = new ShardInfo();
|
ShardInfo shardInfo = new ShardInfo();
|
||||||
shardInfo.readFrom(in);
|
shardInfo.readFrom(in);
|
||||||
return shardInfo;
|
return shardInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Failure implements ShardOperationFailedException, ToXContentObject {
|
public static class Failure extends ShardOperationFailedException implements ToXContentObject {
|
||||||
|
|
||||||
private static final String _INDEX = "_index";
|
private static final String _INDEX = "_index";
|
||||||
private static final String _SHARD = "_shard";
|
private static final String _SHARD = "_shard";
|
||||||
|
@ -235,37 +235,18 @@ public class ReplicationResponse extends ActionResponse {
|
||||||
|
|
||||||
private ShardId shardId;
|
private ShardId shardId;
|
||||||
private String nodeId;
|
private String nodeId;
|
||||||
private Exception cause;
|
|
||||||
private RestStatus status;
|
|
||||||
private boolean primary;
|
private boolean primary;
|
||||||
|
|
||||||
public Failure(ShardId shardId, @Nullable String nodeId, Exception cause, RestStatus status, boolean primary) {
|
public Failure(ShardId shardId, @Nullable String nodeId, Exception cause, RestStatus status, boolean primary) {
|
||||||
|
super(shardId.getIndexName(), shardId.getId(), ExceptionsHelper.detailedMessage(cause), status, cause);
|
||||||
this.shardId = shardId;
|
this.shardId = shardId;
|
||||||
this.nodeId = nodeId;
|
this.nodeId = nodeId;
|
||||||
this.cause = cause;
|
|
||||||
this.status = status;
|
|
||||||
this.primary = primary;
|
this.primary = primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
Failure() {
|
Failure() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return On what index the failure occurred.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String index() {
|
|
||||||
return shardId.getIndexName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return On what shard id the failure occurred.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int shardId() {
|
|
||||||
return shardId.id();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShardId fullShardId() {
|
public ShardId fullShardId() {
|
||||||
return shardId;
|
return shardId;
|
||||||
}
|
}
|
||||||
|
@ -278,27 +259,6 @@ public class ReplicationResponse extends ActionResponse {
|
||||||
return nodeId;
|
return nodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A text description of the failure
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String reason() {
|
|
||||||
return ExceptionsHelper.detailedMessage(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The status to report if this failure was a primary failure.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public RestStatus status() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Throwable getCause() {
|
|
||||||
return cause;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Whether this failure occurred on a primary shard.
|
* @return Whether this failure occurred on a primary shard.
|
||||||
* (this only reports true for delete by query)
|
* (this only reports true for delete by query)
|
||||||
|
@ -310,6 +270,8 @@ public class ReplicationResponse extends ActionResponse {
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
shardId = ShardId.readShardId(in);
|
shardId = ShardId.readShardId(in);
|
||||||
|
super.shardId = shardId.getId();
|
||||||
|
super.index = shardId.getIndexName();
|
||||||
nodeId = in.readOptionalString();
|
nodeId = in.readOptionalString();
|
||||||
cause = in.readException();
|
cause = in.readException();
|
||||||
status = RestStatus.readFrom(in);
|
status = RestStatus.readFrom(in);
|
||||||
|
|
|
@ -126,7 +126,7 @@ public class IndexFieldMapper extends MetadataFieldMapper {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Query termQuery(Object value, @Nullable QueryShardContext context) {
|
public Query termQuery(Object value, @Nullable QueryShardContext context) {
|
||||||
if (isSameIndex(value, context.getFullyQualifiedIndexName())) {
|
if (isSameIndex(value, context.getFullyQualifiedIndex().getName())) {
|
||||||
return Queries.newMatchAllQuery();
|
return Queries.newMatchAllQuery();
|
||||||
} else {
|
} else {
|
||||||
return Queries.newMatchNoDocsQuery("Index didn't match. Index queried: " + context.index().getName() + " vs. " + value);
|
return Queries.newMatchNoDocsQuery("Index didn't match. Index queried: " + context.index().getName() + " vs. " + value);
|
||||||
|
@ -139,14 +139,14 @@ public class IndexFieldMapper extends MetadataFieldMapper {
|
||||||
return super.termsQuery(values, context);
|
return super.termsQuery(values, context);
|
||||||
}
|
}
|
||||||
for (Object value : values) {
|
for (Object value : values) {
|
||||||
if (isSameIndex(value, context.getFullyQualifiedIndexName())) {
|
if (isSameIndex(value, context.getFullyQualifiedIndex().getName())) {
|
||||||
// No need to OR these clauses - we can only logically be
|
// No need to OR these clauses - we can only logically be
|
||||||
// running in the context of just one of these index names.
|
// running in the context of just one of these index names.
|
||||||
return Queries.newMatchAllQuery();
|
return Queries.newMatchAllQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// None of the listed index names are this one
|
// None of the listed index names are this one
|
||||||
return Queries.newMatchNoDocsQuery("Index didn't match. Index queried: " + context.getFullyQualifiedIndexName()
|
return Queries.newMatchNoDocsQuery("Index didn't match. Index queried: " + context.getFullyQualifiedIndex().getName()
|
||||||
+ " vs. " + values);
|
+ " vs. " + values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,5 +189,4 @@ public class IndexFieldMapper extends MetadataFieldMapper {
|
||||||
protected void doMerge(Mapper mergeWith) {
|
protected void doMerge(Mapper mergeWith) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ public class QueryShardContext extends QueryRewriteContext {
|
||||||
private String[] types = Strings.EMPTY_ARRAY;
|
private String[] types = Strings.EMPTY_ARRAY;
|
||||||
private boolean cachable = true;
|
private boolean cachable = true;
|
||||||
private final SetOnce<Boolean> frozen = new SetOnce<>();
|
private final SetOnce<Boolean> frozen = new SetOnce<>();
|
||||||
private final String fullyQualifiedIndexName;
|
private final Index fullyQualifiedIndex;
|
||||||
|
|
||||||
public void setTypes(String... types) {
|
public void setTypes(String... types) {
|
||||||
this.types = types;
|
this.types = types;
|
||||||
|
@ -116,7 +116,8 @@ public class QueryShardContext extends QueryRewriteContext {
|
||||||
this.indexSettings = indexSettings;
|
this.indexSettings = indexSettings;
|
||||||
this.reader = reader;
|
this.reader = reader;
|
||||||
this.clusterAlias = clusterAlias;
|
this.clusterAlias = clusterAlias;
|
||||||
this.fullyQualifiedIndexName = RemoteClusterAware.buildRemoteIndexName(clusterAlias, indexSettings.getIndex().getName());
|
this.fullyQualifiedIndex = new Index(RemoteClusterAware.buildRemoteIndexName(clusterAlias, indexSettings.getIndex().getName()),
|
||||||
|
indexSettings.getIndex().getUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryShardContext(QueryShardContext source) {
|
public QueryShardContext(QueryShardContext source) {
|
||||||
|
@ -163,7 +164,7 @@ public class QueryShardContext extends QueryRewriteContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
public <IFD extends IndexFieldData<?>> IFD getForField(MappedFieldType fieldType) {
|
public <IFD extends IndexFieldData<?>> IFD getForField(MappedFieldType fieldType) {
|
||||||
return (IFD) indexFieldDataService.apply(fieldType, fullyQualifiedIndexName);
|
return (IFD) indexFieldDataService.apply(fieldType, fullyQualifiedIndex.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addNamedQuery(String name, Query query) {
|
public void addNamedQuery(String name, Query query) {
|
||||||
|
@ -275,7 +276,7 @@ public class QueryShardContext extends QueryRewriteContext {
|
||||||
public SearchLookup lookup() {
|
public SearchLookup lookup() {
|
||||||
if (lookup == null) {
|
if (lookup == null) {
|
||||||
lookup = new SearchLookup(getMapperService(),
|
lookup = new SearchLookup(getMapperService(),
|
||||||
mappedFieldType -> indexFieldDataService.apply(mappedFieldType, fullyQualifiedIndexName), types);
|
mappedFieldType -> indexFieldDataService.apply(mappedFieldType, fullyQualifiedIndex.getName()), types);
|
||||||
}
|
}
|
||||||
return lookup;
|
return lookup;
|
||||||
}
|
}
|
||||||
|
@ -426,9 +427,9 @@ public class QueryShardContext extends QueryRewriteContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the fully qualified index name including a remote cluster alias if applicable
|
* Returns the fully qualified index including a remote cluster alias if applicable, and the index uuid
|
||||||
*/
|
*/
|
||||||
public String getFullyQualifiedIndexName() {
|
public Index getFullyQualifiedIndex() {
|
||||||
return fullyQualifiedIndexName;
|
return fullyQualifiedIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,16 +37,15 @@ public class QueryShardException extends ElasticsearchException {
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryShardException(QueryShardContext context, String msg, Throwable cause, Object... args) {
|
public QueryShardException(QueryShardContext context, String msg, Throwable cause, Object... args) {
|
||||||
super(msg, cause, args);
|
this(context.getFullyQualifiedIndex(), msg, cause, args);
|
||||||
setIndex(context.getFullyQualifiedIndexName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This constructor is provided for use in unit tests where a
|
* This constructor is provided for use in unit tests where a
|
||||||
* {@link QueryShardContext} may not be available
|
* {@link QueryShardContext} may not be available
|
||||||
*/
|
*/
|
||||||
public QueryShardException(Index index, String msg, Throwable cause) {
|
public QueryShardException(Index index, String msg, Throwable cause, Object... args) {
|
||||||
super(msg, cause);
|
super(msg, cause, args);
|
||||||
setIndex(index);
|
setIndex(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
@ -127,6 +128,13 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
*/
|
*/
|
||||||
final Map<String, CheckpointState> checkpoints;
|
final Map<String, CheckpointState> checkpoints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback invoked when the global checkpoint is updated. For primary mode this occurs if the computed global checkpoint advances on
|
||||||
|
* the basis of state changes tracked here. For non-primary mode this occurs if the local knowledge of the global checkpoint advances
|
||||||
|
* due to an update from the primary.
|
||||||
|
*/
|
||||||
|
private final LongConsumer onGlobalCheckpointUpdated;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This set contains allocation IDs for which there is a thread actively waiting for the local checkpoint to advance to at least the
|
* This set contains allocation IDs for which there is a thread actively waiting for the local checkpoint to advance to at least the
|
||||||
* current global checkpoint.
|
* current global checkpoint.
|
||||||
|
@ -391,7 +399,8 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
final ShardId shardId,
|
final ShardId shardId,
|
||||||
final String allocationId,
|
final String allocationId,
|
||||||
final IndexSettings indexSettings,
|
final IndexSettings indexSettings,
|
||||||
final long globalCheckpoint) {
|
final long globalCheckpoint,
|
||||||
|
final LongConsumer onGlobalCheckpointUpdated) {
|
||||||
super(shardId, indexSettings);
|
super(shardId, indexSettings);
|
||||||
assert globalCheckpoint >= SequenceNumbers.UNASSIGNED_SEQ_NO : "illegal initial global checkpoint: " + globalCheckpoint;
|
assert globalCheckpoint >= SequenceNumbers.UNASSIGNED_SEQ_NO : "illegal initial global checkpoint: " + globalCheckpoint;
|
||||||
this.shardAllocationId = allocationId;
|
this.shardAllocationId = allocationId;
|
||||||
|
@ -400,6 +409,7 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
this.appliedClusterStateVersion = -1L;
|
this.appliedClusterStateVersion = -1L;
|
||||||
this.checkpoints = new HashMap<>(1 + indexSettings.getNumberOfReplicas());
|
this.checkpoints = new HashMap<>(1 + indexSettings.getNumberOfReplicas());
|
||||||
checkpoints.put(allocationId, new CheckpointState(SequenceNumbers.UNASSIGNED_SEQ_NO, globalCheckpoint, false, false));
|
checkpoints.put(allocationId, new CheckpointState(SequenceNumbers.UNASSIGNED_SEQ_NO, globalCheckpoint, false, false));
|
||||||
|
this.onGlobalCheckpointUpdated = Objects.requireNonNull(onGlobalCheckpointUpdated);
|
||||||
this.pendingInSync = new HashSet<>();
|
this.pendingInSync = new HashSet<>();
|
||||||
this.routingTable = null;
|
this.routingTable = null;
|
||||||
this.replicationGroup = null;
|
this.replicationGroup = null;
|
||||||
|
@ -456,7 +466,10 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
updateGlobalCheckpoint(
|
updateGlobalCheckpoint(
|
||||||
shardAllocationId,
|
shardAllocationId,
|
||||||
globalCheckpoint,
|
globalCheckpoint,
|
||||||
current -> logger.trace("updating global checkpoint from [{}] to [{}] due to [{}]", current, globalCheckpoint, reason));
|
current -> {
|
||||||
|
logger.trace("updated global checkpoint from [{}] to [{}] due to [{}]", current, globalCheckpoint, reason);
|
||||||
|
onGlobalCheckpointUpdated.accept(globalCheckpoint);
|
||||||
|
});
|
||||||
assert invariant();
|
assert invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,7 +487,7 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
allocationId,
|
allocationId,
|
||||||
globalCheckpoint,
|
globalCheckpoint,
|
||||||
current -> logger.trace(
|
current -> logger.trace(
|
||||||
"updating local knowledge for [{}] on the primary of the global checkpoint from [{}] to [{}]",
|
"updated local knowledge for [{}] on the primary of the global checkpoint from [{}] to [{}]",
|
||||||
allocationId,
|
allocationId,
|
||||||
current,
|
current,
|
||||||
globalCheckpoint));
|
globalCheckpoint));
|
||||||
|
@ -485,8 +498,8 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
final CheckpointState cps = checkpoints.get(allocationId);
|
final CheckpointState cps = checkpoints.get(allocationId);
|
||||||
assert !this.shardAllocationId.equals(allocationId) || cps != null;
|
assert !this.shardAllocationId.equals(allocationId) || cps != null;
|
||||||
if (cps != null && globalCheckpoint > cps.globalCheckpoint) {
|
if (cps != null && globalCheckpoint > cps.globalCheckpoint) {
|
||||||
ifUpdated.accept(cps.globalCheckpoint);
|
|
||||||
cps.globalCheckpoint = globalCheckpoint;
|
cps.globalCheckpoint = globalCheckpoint;
|
||||||
|
ifUpdated.accept(cps.globalCheckpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -737,8 +750,9 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
assert computedGlobalCheckpoint >= globalCheckpoint : "new global checkpoint [" + computedGlobalCheckpoint +
|
assert computedGlobalCheckpoint >= globalCheckpoint : "new global checkpoint [" + computedGlobalCheckpoint +
|
||||||
"] is lower than previous one [" + globalCheckpoint + "]";
|
"] is lower than previous one [" + globalCheckpoint + "]";
|
||||||
if (globalCheckpoint != computedGlobalCheckpoint) {
|
if (globalCheckpoint != computedGlobalCheckpoint) {
|
||||||
logger.trace("global checkpoint updated to [{}]", computedGlobalCheckpoint);
|
|
||||||
cps.globalCheckpoint = computedGlobalCheckpoint;
|
cps.globalCheckpoint = computedGlobalCheckpoint;
|
||||||
|
logger.trace("updated global checkpoint to [{}]", computedGlobalCheckpoint);
|
||||||
|
onGlobalCheckpointUpdated.accept(computedGlobalCheckpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -297,8 +297,9 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
|
|
||||||
this.checkIndexOnStartup = indexSettings.getValue(IndexSettings.INDEX_CHECK_ON_STARTUP);
|
this.checkIndexOnStartup = indexSettings.getValue(IndexSettings.INDEX_CHECK_ON_STARTUP);
|
||||||
this.translogConfig = new TranslogConfig(shardId, shardPath().resolveTranslog(), indexSettings, bigArrays);
|
this.translogConfig = new TranslogConfig(shardId, shardPath().resolveTranslog(), indexSettings, bigArrays);
|
||||||
this.replicationTracker = new ReplicationTracker(shardId, shardRouting.allocationId().getId(), indexSettings,
|
final String aId = shardRouting.allocationId().getId();
|
||||||
SequenceNumbers.UNASSIGNED_SEQ_NO);
|
this.replicationTracker =
|
||||||
|
new ReplicationTracker(shardId, aId, indexSettings, SequenceNumbers.UNASSIGNED_SEQ_NO, globalCheckpoint -> {});
|
||||||
// the query cache is a node-level thing, however we want the most popular filters
|
// the query cache is a node-level thing, however we want the most popular filters
|
||||||
// to be computed on a per-shard basis
|
// to be computed on a per-shard basis
|
||||||
if (IndexModule.INDEX_QUERY_CACHE_EVERYTHING_SETTING.get(settings)) {
|
if (IndexModule.INDEX_QUERY_CACHE_EVERYTHING_SETTING.get(settings)) {
|
||||||
|
@ -1446,10 +1447,10 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (origin == Engine.Operation.Origin.PRIMARY) {
|
if (origin == Engine.Operation.Origin.PRIMARY) {
|
||||||
verifyPrimary();
|
assert assertPrimaryMode();
|
||||||
} else {
|
} else {
|
||||||
assert origin == Engine.Operation.Origin.REPLICA;
|
assert origin == Engine.Operation.Origin.REPLICA;
|
||||||
verifyReplicationTarget();
|
assert assertReplicationTarget();
|
||||||
}
|
}
|
||||||
if (writeAllowedStates.contains(state) == false) {
|
if (writeAllowedStates.contains(state) == false) {
|
||||||
throw new IllegalIndexShardStateException(shardId, state, "operation only allowed when shard state is one of " + writeAllowedStates + ", origin [" + origin + "]");
|
throw new IllegalIndexShardStateException(shardId, state, "operation only allowed when shard state is one of " + writeAllowedStates + ", origin [" + origin + "]");
|
||||||
|
@ -1457,19 +1458,14 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyPrimary() {
|
private boolean assertPrimaryMode() {
|
||||||
if (shardRouting.primary() == false) {
|
assert shardRouting.primary() && replicationTracker.isPrimaryMode() : "shard " + shardRouting + " is not a primary shard in primary mode";
|
||||||
throw new IllegalStateException("shard " + shardRouting + " is not a primary");
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyReplicationTarget() {
|
private boolean assertReplicationTarget() {
|
||||||
final IndexShardState state = state();
|
assert replicationTracker.isPrimaryMode() == false : "shard " + shardRouting + " in primary mode cannot be a replication target";
|
||||||
if (shardRouting.primary() && shardRouting.active() && replicationTracker.isPrimaryMode()) {
|
return true;
|
||||||
// must use exception that is not ignored by replication logic. See TransportActions.isShardNotAvailableException
|
|
||||||
throw new IllegalStateException("active primary shard " + shardRouting + " cannot be a replication target before " +
|
|
||||||
"relocation hand off, state is [" + state + "]");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyNotClosed() throws IllegalIndexShardStateException {
|
private void verifyNotClosed() throws IllegalIndexShardStateException {
|
||||||
|
@ -1716,7 +1712,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
* @param checkpoint the local checkpoint for the shard
|
* @param checkpoint the local checkpoint for the shard
|
||||||
*/
|
*/
|
||||||
public void updateLocalCheckpointForShard(final String allocationId, final long checkpoint) {
|
public void updateLocalCheckpointForShard(final String allocationId, final long checkpoint) {
|
||||||
verifyPrimary();
|
assert assertPrimaryMode();
|
||||||
verifyNotClosed();
|
verifyNotClosed();
|
||||||
replicationTracker.updateLocalCheckpoint(allocationId, checkpoint);
|
replicationTracker.updateLocalCheckpoint(allocationId, checkpoint);
|
||||||
}
|
}
|
||||||
|
@ -1728,7 +1724,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
* @param globalCheckpoint the global checkpoint
|
* @param globalCheckpoint the global checkpoint
|
||||||
*/
|
*/
|
||||||
public void updateGlobalCheckpointForShard(final String allocationId, final long globalCheckpoint) {
|
public void updateGlobalCheckpointForShard(final String allocationId, final long globalCheckpoint) {
|
||||||
verifyPrimary();
|
assert assertPrimaryMode();
|
||||||
verifyNotClosed();
|
verifyNotClosed();
|
||||||
replicationTracker.updateGlobalCheckpointForShard(allocationId, globalCheckpoint);
|
replicationTracker.updateGlobalCheckpointForShard(allocationId, globalCheckpoint);
|
||||||
}
|
}
|
||||||
|
@ -1750,7 +1746,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
* @param allocationId the allocation ID of the shard for which recovery was initiated
|
* @param allocationId the allocation ID of the shard for which recovery was initiated
|
||||||
*/
|
*/
|
||||||
public void initiateTracking(final String allocationId) {
|
public void initiateTracking(final String allocationId) {
|
||||||
verifyPrimary();
|
assert assertPrimaryMode();
|
||||||
replicationTracker.initiateTracking(allocationId);
|
replicationTracker.initiateTracking(allocationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1763,7 +1759,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
* @param localCheckpoint the current local checkpoint on the shard
|
* @param localCheckpoint the current local checkpoint on the shard
|
||||||
*/
|
*/
|
||||||
public void markAllocationIdAsInSync(final String allocationId, final long localCheckpoint) throws InterruptedException {
|
public void markAllocationIdAsInSync(final String allocationId, final long localCheckpoint) throws InterruptedException {
|
||||||
verifyPrimary();
|
assert assertPrimaryMode();
|
||||||
replicationTracker.markAllocationIdAsInSync(allocationId, localCheckpoint);
|
replicationTracker.markAllocationIdAsInSync(allocationId, localCheckpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1798,7 +1794,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
* @return a map from allocation ID to the local knowledge of the global checkpoint for that allocation ID
|
* @return a map from allocation ID to the local knowledge of the global checkpoint for that allocation ID
|
||||||
*/
|
*/
|
||||||
public ObjectLongMap<String> getInSyncGlobalCheckpoints() {
|
public ObjectLongMap<String> getInSyncGlobalCheckpoints() {
|
||||||
verifyPrimary();
|
assert assertPrimaryMode();
|
||||||
verifyNotClosed();
|
verifyNotClosed();
|
||||||
return replicationTracker.getInSyncGlobalCheckpoints();
|
return replicationTracker.getInSyncGlobalCheckpoints();
|
||||||
}
|
}
|
||||||
|
@ -1808,11 +1804,12 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
* primary.
|
* primary.
|
||||||
*/
|
*/
|
||||||
public void maybeSyncGlobalCheckpoint(final String reason) {
|
public void maybeSyncGlobalCheckpoint(final String reason) {
|
||||||
verifyPrimary();
|
|
||||||
verifyNotClosed();
|
verifyNotClosed();
|
||||||
|
assert shardRouting.primary() : "only call maybeSyncGlobalCheckpoint on primary shard";
|
||||||
if (replicationTracker.isPrimaryMode() == false) {
|
if (replicationTracker.isPrimaryMode() == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
assert assertPrimaryMode();
|
||||||
// only sync if there are not operations in flight
|
// only sync if there are not operations in flight
|
||||||
final SeqNoStats stats = getEngine().getSeqNoStats(replicationTracker.getGlobalCheckpoint());
|
final SeqNoStats stats = getEngine().getSeqNoStats(replicationTracker.getGlobalCheckpoint());
|
||||||
if (stats.getMaxSeqNo() == stats.getGlobalCheckpoint()) {
|
if (stats.getMaxSeqNo() == stats.getGlobalCheckpoint()) {
|
||||||
|
@ -1838,7 +1835,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
* @return the replication group
|
* @return the replication group
|
||||||
*/
|
*/
|
||||||
public ReplicationGroup getReplicationGroup() {
|
public ReplicationGroup getReplicationGroup() {
|
||||||
verifyPrimary();
|
assert assertPrimaryMode();
|
||||||
verifyNotClosed();
|
verifyNotClosed();
|
||||||
return replicationTracker.getReplicationGroup();
|
return replicationTracker.getReplicationGroup();
|
||||||
}
|
}
|
||||||
|
@ -1850,7 +1847,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
* @param reason the reason the global checkpoint was updated
|
* @param reason the reason the global checkpoint was updated
|
||||||
*/
|
*/
|
||||||
public void updateGlobalCheckpointOnReplica(final long globalCheckpoint, final String reason) {
|
public void updateGlobalCheckpointOnReplica(final long globalCheckpoint, final String reason) {
|
||||||
verifyReplicationTarget();
|
assert assertReplicationTarget();
|
||||||
final long localCheckpoint = getLocalCheckpoint();
|
final long localCheckpoint = getLocalCheckpoint();
|
||||||
if (globalCheckpoint > localCheckpoint) {
|
if (globalCheckpoint > localCheckpoint) {
|
||||||
/*
|
/*
|
||||||
|
@ -1877,8 +1874,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
* @param primaryContext the sequence number context
|
* @param primaryContext the sequence number context
|
||||||
*/
|
*/
|
||||||
public void activateWithPrimaryContext(final ReplicationTracker.PrimaryContext primaryContext) {
|
public void activateWithPrimaryContext(final ReplicationTracker.PrimaryContext primaryContext) {
|
||||||
verifyPrimary();
|
assert shardRouting.primary() && shardRouting.isRelocationTarget() : "only primary relocation target can update allocation IDs from primary context: " + shardRouting;
|
||||||
assert shardRouting.isRelocationTarget() : "only relocation target can update allocation IDs from primary context: " + shardRouting;
|
|
||||||
assert primaryContext.getCheckpointStates().containsKey(routingEntry().allocationId().getId()) &&
|
assert primaryContext.getCheckpointStates().containsKey(routingEntry().allocationId().getId()) &&
|
||||||
getLocalCheckpoint() == primaryContext.getCheckpointStates().get(routingEntry().allocationId().getId()).getLocalCheckpoint();
|
getLocalCheckpoint() == primaryContext.getCheckpointStates().get(routingEntry().allocationId().getId()).getLocalCheckpoint();
|
||||||
synchronized (mutex) {
|
synchronized (mutex) {
|
||||||
|
@ -1892,7 +1888,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
* @return {@code true} if there is at least one shard pending in-sync, otherwise false
|
* @return {@code true} if there is at least one shard pending in-sync, otherwise false
|
||||||
*/
|
*/
|
||||||
public boolean pendingInSync() {
|
public boolean pendingInSync() {
|
||||||
verifyPrimary();
|
assert assertPrimaryMode();
|
||||||
return replicationTracker.pendingInSync();
|
return replicationTracker.pendingInSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2209,7 +2205,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
*/
|
*/
|
||||||
public void acquirePrimaryOperationPermit(ActionListener<Releasable> onPermitAcquired, String executorOnDelay, Object debugInfo) {
|
public void acquirePrimaryOperationPermit(ActionListener<Releasable> onPermitAcquired, String executorOnDelay, Object debugInfo) {
|
||||||
verifyNotClosed();
|
verifyNotClosed();
|
||||||
verifyPrimary();
|
assert shardRouting.primary() : "acquirePrimaryOperationPermit should only be called on primary shard: " + shardRouting;
|
||||||
|
|
||||||
indexShardOperationPermits.acquire(onPermitAcquired, executorOnDelay, false, debugInfo);
|
indexShardOperationPermits.acquire(onPermitAcquired, executorOnDelay, false, debugInfo);
|
||||||
}
|
}
|
||||||
|
@ -2259,7 +2255,6 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
final ActionListener<Releasable> onPermitAcquired, final String executorOnDelay,
|
final ActionListener<Releasable> onPermitAcquired, final String executorOnDelay,
|
||||||
final Object debugInfo) {
|
final Object debugInfo) {
|
||||||
verifyNotClosed();
|
verifyNotClosed();
|
||||||
verifyReplicationTarget();
|
|
||||||
if (opPrimaryTerm > pendingPrimaryTerm) {
|
if (opPrimaryTerm > pendingPrimaryTerm) {
|
||||||
synchronized (mutex) {
|
synchronized (mutex) {
|
||||||
if (opPrimaryTerm > pendingPrimaryTerm) {
|
if (opPrimaryTerm > pendingPrimaryTerm) {
|
||||||
|
@ -2312,6 +2307,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
operationPrimaryTerm);
|
operationPrimaryTerm);
|
||||||
onPermitAcquired.onFailure(new IllegalStateException(message));
|
onPermitAcquired.onFailure(new IllegalStateException(message));
|
||||||
} else {
|
} else {
|
||||||
|
assert assertReplicationTarget();
|
||||||
try {
|
try {
|
||||||
updateGlobalCheckpointOnReplica(globalCheckpoint, "operation");
|
updateGlobalCheckpointOnReplica(globalCheckpoint, "operation");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
@ -48,6 +48,7 @@ import org.elasticsearch.search.fetch.FetchSubPhase;
|
||||||
import org.elasticsearch.search.fetch.subphase.highlight.Highlighter;
|
import org.elasticsearch.search.fetch.subphase.highlight.Highlighter;
|
||||||
import org.elasticsearch.search.rescore.RescorerBuilder;
|
import org.elasticsearch.search.rescore.RescorerBuilder;
|
||||||
import org.elasticsearch.search.rescore.Rescorer;
|
import org.elasticsearch.search.rescore.Rescorer;
|
||||||
|
import org.elasticsearch.search.suggest.Suggest;
|
||||||
import org.elasticsearch.search.suggest.Suggester;
|
import org.elasticsearch.search.suggest.Suggester;
|
||||||
import org.elasticsearch.search.suggest.SuggestionBuilder;
|
import org.elasticsearch.search.suggest.SuggestionBuilder;
|
||||||
|
|
||||||
|
@ -149,31 +150,61 @@ public interface SearchPlugin {
|
||||||
* Specification for a {@link Suggester}.
|
* Specification for a {@link Suggester}.
|
||||||
*/
|
*/
|
||||||
class SuggesterSpec<T extends SuggestionBuilder<T>> extends SearchExtensionSpec<T, CheckedFunction<XContentParser, T, IOException>> {
|
class SuggesterSpec<T extends SuggestionBuilder<T>> extends SearchExtensionSpec<T, CheckedFunction<XContentParser, T, IOException>> {
|
||||||
|
|
||||||
|
private Writeable.Reader<? extends Suggest.Suggestion> suggestionReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specification of custom {@link Suggester}.
|
* Specification of custom {@link Suggester}.
|
||||||
*
|
*
|
||||||
* @param name holds the names by which this suggester might be parsed. The {@link ParseField#getPreferredName()} is special as it
|
* @param name holds the names by which this suggester might be parsed. The {@link ParseField#getPreferredName()} is special as it
|
||||||
* is the name by under which the reader is registered. So it is the name that the query should use as its
|
* is the name by under which the request builder and Suggestion response readers are registered. So it is the name that the
|
||||||
* {@link NamedWriteable#getWriteableName()} too.
|
* query and Suggestion response should use as their {@link NamedWriteable#getWriteableName()} return values too.
|
||||||
* @param reader the reader registered for this suggester's builder. Typically a reference to a constructor that takes a
|
* @param builderReader the reader registered for this suggester's builder. Typically a reference to a constructor that takes a
|
||||||
* {@link StreamInput}
|
* {@link StreamInput}
|
||||||
* @param parser the parser the reads the query suggester from xcontent
|
* @param builderParser a parser that reads the suggester's builder from xcontent
|
||||||
|
* @param suggestionReader the reader registered for this suggester's Suggestion response. Typically a reference to a constructor
|
||||||
|
* that takes a {@link StreamInput}
|
||||||
*/
|
*/
|
||||||
public SuggesterSpec(ParseField name, Writeable.Reader<T> reader, CheckedFunction<XContentParser, T, IOException> parser) {
|
public SuggesterSpec(
|
||||||
super(name, reader, parser);
|
ParseField name,
|
||||||
|
Writeable.Reader<T> builderReader,
|
||||||
|
CheckedFunction<XContentParser, T, IOException> builderParser,
|
||||||
|
Writeable.Reader<? extends Suggest.Suggestion> suggestionReader) {
|
||||||
|
|
||||||
|
super(name, builderReader, builderParser);
|
||||||
|
setSuggestionReader(suggestionReader);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specification of custom {@link Suggester}.
|
* Specification of custom {@link Suggester}.
|
||||||
*
|
*
|
||||||
* @param name the name by which this suggester might be parsed or deserialized. Make sure that the query builder returns this name
|
* @param name the name by which this suggester might be parsed or deserialized. Make sure that the query builder and Suggestion
|
||||||
* for {@link NamedWriteable#getWriteableName()}.
|
* response reader return this name for {@link NamedWriteable#getWriteableName()}.
|
||||||
* @param reader the reader registered for this suggester's builder. Typically a reference to a constructor that takes a
|
* @param builderReader the reader registered for this suggester's builder. Typically a reference to a constructor that takes a
|
||||||
* {@link StreamInput}
|
* {@link StreamInput}
|
||||||
* @param parser the parser the reads the suggester builder from xcontent
|
* @param builderParser a parser that reads the suggester's builder from xcontent
|
||||||
|
* @param suggestionReader the reader registered for this suggester's Suggestion response. Typically a reference to a constructor
|
||||||
|
* that takes a {@link StreamInput}
|
||||||
*/
|
*/
|
||||||
public SuggesterSpec(String name, Writeable.Reader<T> reader, CheckedFunction<XContentParser, T, IOException> parser) {
|
public SuggesterSpec(
|
||||||
super(name, reader, parser);
|
String name,
|
||||||
|
Writeable.Reader<T> builderReader,
|
||||||
|
CheckedFunction<XContentParser, T, IOException> builderParser,
|
||||||
|
Writeable.Reader<? extends Suggest.Suggestion> suggestionReader) {
|
||||||
|
|
||||||
|
super(name, builderReader, builderParser);
|
||||||
|
setSuggestionReader(suggestionReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSuggestionReader(Writeable.Reader<? extends Suggest.Suggestion> reader) {
|
||||||
|
this.suggestionReader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reader used to read the {@link Suggest.Suggestion} generated by this suggester
|
||||||
|
*/
|
||||||
|
public Writeable.Reader<? extends Suggest.Suggestion> getSuggestionReader() {
|
||||||
|
return this.suggestionReader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -247,13 +247,17 @@ import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
|
||||||
import org.elasticsearch.search.sort.ScoreSortBuilder;
|
import org.elasticsearch.search.sort.ScoreSortBuilder;
|
||||||
import org.elasticsearch.search.sort.ScriptSortBuilder;
|
import org.elasticsearch.search.sort.ScriptSortBuilder;
|
||||||
import org.elasticsearch.search.sort.SortBuilder;
|
import org.elasticsearch.search.sort.SortBuilder;
|
||||||
|
import org.elasticsearch.search.suggest.Suggest;
|
||||||
import org.elasticsearch.search.suggest.SuggestionBuilder;
|
import org.elasticsearch.search.suggest.SuggestionBuilder;
|
||||||
|
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
|
||||||
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
|
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
|
||||||
import org.elasticsearch.search.suggest.phrase.Laplace;
|
import org.elasticsearch.search.suggest.phrase.Laplace;
|
||||||
import org.elasticsearch.search.suggest.phrase.LinearInterpolation;
|
import org.elasticsearch.search.suggest.phrase.LinearInterpolation;
|
||||||
|
import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
|
||||||
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
|
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
|
||||||
import org.elasticsearch.search.suggest.phrase.SmoothingModel;
|
import org.elasticsearch.search.suggest.phrase.SmoothingModel;
|
||||||
import org.elasticsearch.search.suggest.phrase.StupidBackoff;
|
import org.elasticsearch.search.suggest.phrase.StupidBackoff;
|
||||||
|
import org.elasticsearch.search.suggest.term.TermSuggestion;
|
||||||
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
|
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -590,9 +594,14 @@ public class SearchModule {
|
||||||
private void registerSuggesters(List<SearchPlugin> plugins) {
|
private void registerSuggesters(List<SearchPlugin> plugins) {
|
||||||
registerSmoothingModels(namedWriteables);
|
registerSmoothingModels(namedWriteables);
|
||||||
|
|
||||||
registerSuggester(new SuggesterSpec<>("term", TermSuggestionBuilder::new, TermSuggestionBuilder::fromXContent));
|
registerSuggester(new SuggesterSpec<>(TermSuggestionBuilder.SUGGESTION_NAME,
|
||||||
registerSuggester(new SuggesterSpec<>("phrase", PhraseSuggestionBuilder::new, PhraseSuggestionBuilder::fromXContent));
|
TermSuggestionBuilder::new, TermSuggestionBuilder::fromXContent, TermSuggestion::new));
|
||||||
registerSuggester(new SuggesterSpec<>("completion", CompletionSuggestionBuilder::new, CompletionSuggestionBuilder::fromXContent));
|
|
||||||
|
registerSuggester(new SuggesterSpec<>(PhraseSuggestionBuilder.SUGGESTION_NAME,
|
||||||
|
PhraseSuggestionBuilder::new, PhraseSuggestionBuilder::fromXContent, PhraseSuggestion::new));
|
||||||
|
|
||||||
|
registerSuggester(new SuggesterSpec<>(CompletionSuggestionBuilder.SUGGESTION_NAME,
|
||||||
|
CompletionSuggestionBuilder::new, CompletionSuggestionBuilder::fromXContent, CompletionSuggestion::new));
|
||||||
|
|
||||||
registerFromPlugin(plugins, SearchPlugin::getSuggesters, this::registerSuggester);
|
registerFromPlugin(plugins, SearchPlugin::getSuggesters, this::registerSuggester);
|
||||||
}
|
}
|
||||||
|
@ -602,6 +611,10 @@ public class SearchModule {
|
||||||
SuggestionBuilder.class, suggester.getName().getPreferredName(), suggester.getReader()));
|
SuggestionBuilder.class, suggester.getName().getPreferredName(), suggester.getReader()));
|
||||||
namedXContents.add(new NamedXContentRegistry.Entry(SuggestionBuilder.class, suggester.getName(),
|
namedXContents.add(new NamedXContentRegistry.Entry(SuggestionBuilder.class, suggester.getName(),
|
||||||
suggester.getParser()));
|
suggester.getParser()));
|
||||||
|
|
||||||
|
namedWriteables.add(new NamedWriteableRegistry.Entry(
|
||||||
|
Suggest.Suggestion.class, suggester.getName().getPreferredName(), suggester.getSuggestionReader()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Highlighter> setupHighlighters(Settings settings, List<SearchPlugin> plugins) {
|
private Map<String, Highlighter> setupHighlighters(Settings settings, List<SearchPlugin> plugins) {
|
||||||
|
|
|
@ -247,6 +247,6 @@ public class CollapseBuilder implements Writeable, ToXContentObject {
|
||||||
+ field + "`, " + "only indexed field can retrieve `inner_hits`");
|
+ field + "`, " + "only indexed field can retrieve `inner_hits`");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CollapseContext(fieldType, innerHits);
|
return new CollapseContext(field, fieldType, innerHits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,26 +25,31 @@ import org.elasticsearch.index.mapper.MappedFieldType;
|
||||||
import org.elasticsearch.index.mapper.NumberFieldMapper;
|
import org.elasticsearch.index.mapper.NumberFieldMapper;
|
||||||
import org.elasticsearch.index.query.InnerHitBuilder;
|
import org.elasticsearch.index.query.InnerHitBuilder;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context used for field collapsing
|
* Context used for field collapsing
|
||||||
*/
|
*/
|
||||||
public class CollapseContext {
|
public class CollapseContext {
|
||||||
|
private final String fieldName;
|
||||||
private final MappedFieldType fieldType;
|
private final MappedFieldType fieldType;
|
||||||
private final List<InnerHitBuilder> innerHits;
|
private final List<InnerHitBuilder> innerHits;
|
||||||
|
|
||||||
public CollapseContext(MappedFieldType fieldType, InnerHitBuilder innerHit) {
|
public CollapseContext(String fieldName,
|
||||||
this.fieldType = fieldType;
|
MappedFieldType fieldType,
|
||||||
this.innerHits = Collections.singletonList(innerHit);
|
List<InnerHitBuilder> innerHits) {
|
||||||
}
|
this.fieldName = fieldName;
|
||||||
|
|
||||||
public CollapseContext(MappedFieldType fieldType, List<InnerHitBuilder> innerHits) {
|
|
||||||
this.fieldType = fieldType;
|
this.fieldType = fieldType;
|
||||||
this.innerHits = innerHits;
|
this.innerHits = innerHits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The requested field name to collapse on.
|
||||||
|
*/
|
||||||
|
public String getFieldName() {
|
||||||
|
return fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
/** The field type used for collapsing **/
|
/** The field type used for collapsing **/
|
||||||
public MappedFieldType getFieldType() {
|
public MappedFieldType getFieldType() {
|
||||||
return fieldType;
|
return fieldType;
|
||||||
|
|
|
@ -61,7 +61,7 @@ public final class DocValueFieldsFetchSubPhase implements FetchSubPhase {
|
||||||
|
|
||||||
if (context.collapse() != null) {
|
if (context.collapse() != null) {
|
||||||
// retrieve the `doc_value` associated with the collapse field
|
// retrieve the `doc_value` associated with the collapse field
|
||||||
String name = context.collapse().getFieldType().name();
|
String name = context.collapse().getFieldName();
|
||||||
if (context.docValueFieldsContext() == null) {
|
if (context.docValueFieldsContext() == null) {
|
||||||
context.docValueFieldsContext(new DocValueFieldsContext(
|
context.docValueFieldsContext(new DocValueFieldsContext(
|
||||||
Collections.singletonList(new FieldAndFormat(name, DocValueFieldsContext.USE_DEFAULT_FORMAT))));
|
Collections.singletonList(new FieldAndFormat(name, DocValueFieldsContext.USE_DEFAULT_FORMAT))));
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class InternalSearchResponse extends SearchResponseSections implements Wr
|
||||||
super(
|
super(
|
||||||
SearchHits.readSearchHits(in),
|
SearchHits.readSearchHits(in),
|
||||||
in.readBoolean() ? InternalAggregations.readAggregations(in) : null,
|
in.readBoolean() ? InternalAggregations.readAggregations(in) : null,
|
||||||
in.readBoolean() ? Suggest.readSuggest(in) : null,
|
in.readBoolean() ? new Suggest(in) : null,
|
||||||
in.readBoolean(),
|
in.readBoolean(),
|
||||||
in.readOptionalBoolean(),
|
in.readOptionalBoolean(),
|
||||||
in.readOptionalWriteable(SearchProfileShardResults::new),
|
in.readOptionalWriteable(SearchProfileShardResults::new),
|
||||||
|
@ -62,7 +62,7 @@ public class InternalSearchResponse extends SearchResponseSections implements Wr
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
hits.writeTo(out);
|
hits.writeTo(out);
|
||||||
out.writeOptionalStreamable((InternalAggregations)aggregations);
|
out.writeOptionalStreamable((InternalAggregations)aggregations);
|
||||||
out.writeOptionalStreamable(suggest);
|
out.writeOptionalWriteable(suggest);
|
||||||
out.writeBoolean(timedOut);
|
out.writeBoolean(timedOut);
|
||||||
out.writeOptionalBoolean(terminatedEarly);
|
out.writeOptionalBoolean(terminatedEarly);
|
||||||
out.writeOptionalWriteable(profileResults);
|
out.writeOptionalWriteable(profileResults);
|
||||||
|
|
|
@ -293,7 +293,7 @@ public final class QuerySearchResult extends SearchPhaseResult {
|
||||||
pipelineAggregators = in.readNamedWriteableList(PipelineAggregator.class).stream().map(a -> (SiblingPipelineAggregator) a)
|
pipelineAggregators = in.readNamedWriteableList(PipelineAggregator.class).stream().map(a -> (SiblingPipelineAggregator) a)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
if (in.readBoolean()) {
|
if (in.readBoolean()) {
|
||||||
suggest = Suggest.readSuggest(in);
|
suggest = new Suggest(in);
|
||||||
}
|
}
|
||||||
searchTimedOut = in.readBoolean();
|
searchTimedOut = in.readBoolean();
|
||||||
terminatedEarly = in.readOptionalBoolean();
|
terminatedEarly = in.readOptionalBoolean();
|
||||||
|
|
|
@ -20,18 +20,18 @@ package org.elasticsearch.search.suggest;
|
||||||
|
|
||||||
import org.apache.lucene.util.CollectionUtil;
|
import org.apache.lucene.util.CollectionUtil;
|
||||||
import org.apache.lucene.util.SetOnce;
|
import org.apache.lucene.util.SetOnce;
|
||||||
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.common.CheckedFunction;
|
import org.elasticsearch.common.CheckedFunction;
|
||||||
import org.elasticsearch.common.ParseField;
|
import org.elasticsearch.common.ParseField;
|
||||||
import org.elasticsearch.common.ParsingException;
|
import org.elasticsearch.common.ParsingException;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.elasticsearch.common.io.stream.NamedWriteable;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.io.stream.Streamable;
|
import org.elasticsearch.common.io.stream.Writeable;
|
||||||
import org.elasticsearch.common.text.Text;
|
import org.elasticsearch.common.text.Text;
|
||||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
|
||||||
import org.elasticsearch.common.xcontent.ObjectParser;
|
import org.elasticsearch.common.xcontent.ObjectParser;
|
||||||
import org.elasticsearch.common.xcontent.ToXContentFragment;
|
import org.elasticsearch.common.xcontent.ToXContentFragment;
|
||||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
@ -53,16 +53,15 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
|
||||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top level suggest result, containing the result for each suggestion.
|
* Top level suggest result, containing the result for each suggestion.
|
||||||
*/
|
*/
|
||||||
public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? extends Option>>>, Streamable, ToXContentFragment {
|
public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? extends Option>>>, Writeable, ToXContentFragment {
|
||||||
|
|
||||||
public static final String NAME = "suggest";
|
public static final String NAME = "suggest";
|
||||||
|
|
||||||
|
@ -92,6 +91,40 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
this.hasScoreDocs = filter(CompletionSuggestion.class).stream().anyMatch(CompletionSuggestion::hasScoreDocs);
|
this.hasScoreDocs = filter(CompletionSuggestion.class).stream().anyMatch(CompletionSuggestion::hasScoreDocs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Suggest(StreamInput in) throws IOException {
|
||||||
|
// in older versions, Suggestion types were serialized as Streamable
|
||||||
|
if (in.getVersion().before(Version.V_7_0_0_alpha1)) {
|
||||||
|
final int size = in.readVInt();
|
||||||
|
suggestions = new ArrayList<>(size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
Suggestion<? extends Entry<? extends Option>> suggestion;
|
||||||
|
final int type = in.readVInt();
|
||||||
|
switch (type) {
|
||||||
|
case TermSuggestion.TYPE:
|
||||||
|
suggestion = new TermSuggestion(in);
|
||||||
|
break;
|
||||||
|
case CompletionSuggestion.TYPE:
|
||||||
|
suggestion = new CompletionSuggestion(in);
|
||||||
|
break;
|
||||||
|
case PhraseSuggestion.TYPE:
|
||||||
|
suggestion = new PhraseSuggestion(in);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown suggestion type with ordinal " + type);
|
||||||
|
}
|
||||||
|
suggestions.add(suggestion);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int suggestionCount = in.readVInt();
|
||||||
|
suggestions = new ArrayList<>(suggestionCount);
|
||||||
|
for (int i = 0; i < suggestionCount; i++) {
|
||||||
|
suggestions.add(in.readNamedWriteable(Suggestion.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasScoreDocs = filter(CompletionSuggestion.class).stream().anyMatch(CompletionSuggestion::hasScoreDocs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Suggestion<? extends Entry<? extends Option>>> iterator() {
|
public Iterator<Suggestion<? extends Entry<? extends Option>>> iterator() {
|
||||||
return suggestions.iterator();
|
return suggestions.iterator();
|
||||||
|
@ -125,42 +158,20 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
return hasScoreDocs;
|
return hasScoreDocs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
final int size = in.readVInt();
|
|
||||||
suggestions = new ArrayList<>(size);
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
// TODO: remove these complicated generics
|
|
||||||
Suggestion<? extends Entry<? extends Option>> suggestion;
|
|
||||||
final int type = in.readVInt();
|
|
||||||
switch (type) {
|
|
||||||
case TermSuggestion.TYPE:
|
|
||||||
suggestion = new TermSuggestion();
|
|
||||||
break;
|
|
||||||
case CompletionSuggestion.TYPE:
|
|
||||||
suggestion = new CompletionSuggestion();
|
|
||||||
break;
|
|
||||||
case 2: // CompletionSuggestion.TYPE
|
|
||||||
throw new IllegalArgumentException("Completion suggester 2.x is not supported anymore");
|
|
||||||
case PhraseSuggestion.TYPE:
|
|
||||||
suggestion = new PhraseSuggestion();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
suggestion = new Suggestion();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
suggestion.readFrom(in);
|
|
||||||
suggestions.add(suggestion);
|
|
||||||
}
|
|
||||||
hasScoreDocs = filter(CompletionSuggestion.class).stream().anyMatch(CompletionSuggestion::hasScoreDocs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
out.writeVInt(suggestions.size());
|
// in older versions, Suggestion types were serialized as Streamable
|
||||||
for (Suggestion<?> command : suggestions) {
|
if (out.getVersion().before(Version.V_7_0_0_alpha1)) {
|
||||||
out.writeVInt(command.getWriteableType());
|
out.writeVInt(suggestions.size());
|
||||||
command.writeTo(out);
|
for (Suggestion<?> command : suggestions) {
|
||||||
|
out.writeVInt(command.getWriteableType());
|
||||||
|
command.writeTo(out);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.writeVInt(suggestions.size());
|
||||||
|
for (Suggestion<? extends Entry<? extends Option>> suggestion : suggestions) {
|
||||||
|
out.writeNamedWriteable(suggestion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,12 +206,6 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
return new Suggest(suggestions);
|
return new Suggest(suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Suggest readSuggest(StreamInput in) throws IOException {
|
|
||||||
Suggest result = new Suggest();
|
|
||||||
result.readFrom(in);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Suggestion<? extends Entry<? extends Option>>> reduce(Map<String, List<Suggest.Suggestion>> 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<>(groupedSuggestions.size());
|
List<Suggestion<? extends Entry<? extends Option>>> reduced = new ArrayList<>(groupedSuggestions.size());
|
||||||
for (java.util.Map.Entry<String, List<Suggestion>> unmergedResults : groupedSuggestions.entrySet()) {
|
for (java.util.Map.Entry<String, List<Suggestion>> unmergedResults : groupedSuggestions.entrySet()) {
|
||||||
|
@ -232,10 +237,27 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (this == other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (other == null || getClass() != other.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Objects.equals(suggestions, ((Suggest) other).suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The suggestion responses corresponding with the suggestions in the request.
|
* The suggestion responses corresponding with the suggestions in the request.
|
||||||
*/
|
*/
|
||||||
public static class Suggestion<T extends Suggestion.Entry> implements Iterable<T>, Streamable, ToXContentFragment {
|
public abstract static class Suggestion<T extends Suggestion.Entry> implements Iterable<T>, NamedWriteable, ToXContentFragment {
|
||||||
|
|
||||||
private static final String NAME = "suggestion";
|
private static final String NAME = "suggestion";
|
||||||
|
|
||||||
|
@ -252,6 +274,24 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
this.size = size; // The suggested term size specified in request, only used for merging shard responses
|
this.size = size; // The suggested term size specified in request, only used for merging shard responses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Suggestion(StreamInput in) throws IOException {
|
||||||
|
name = in.readString();
|
||||||
|
size = in.readVInt();
|
||||||
|
|
||||||
|
// this is a hack to work around slightly different serialization order of earlier versions of TermSuggestion
|
||||||
|
if (in.getVersion().before(Version.V_7_0_0_alpha1) && this instanceof TermSuggestion) {
|
||||||
|
TermSuggestion t = (TermSuggestion) this;
|
||||||
|
t.setSort(SortBy.readFromStream(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
int entriesCount = in.readVInt();
|
||||||
|
entries.clear();
|
||||||
|
for (int i = 0; i < entriesCount; i++) {
|
||||||
|
T newEntry = newEntry(in);
|
||||||
|
entries.add(newEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void addTerm(T entry) {
|
public void addTerm(T entry) {
|
||||||
entries.add(entry);
|
entries.add(entry);
|
||||||
}
|
}
|
||||||
|
@ -259,20 +299,14 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
/**
|
/**
|
||||||
* Returns a integer representing the type of the suggestion. This is used for
|
* Returns a integer representing the type of the suggestion. This is used for
|
||||||
* internal serialization over the network.
|
* internal serialization over the network.
|
||||||
|
*
|
||||||
|
* This class is now serialized as a NamedWriteable and this method only remains for backwards compatibility
|
||||||
*/
|
*/
|
||||||
public int getWriteableType() { // TODO remove this in favor of NamedWriteable
|
@Deprecated
|
||||||
|
public int getWriteableType() {
|
||||||
return TYPE;
|
return TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representing the type of the suggestion. This type is added to
|
|
||||||
* the suggestion name in the XContent response, so that it can later be used by
|
|
||||||
* REST clients to determine the internal type of the suggestion.
|
|
||||||
*/
|
|
||||||
protected String getType() {
|
|
||||||
return NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<T> iterator() {
|
public Iterator<T> iterator() {
|
||||||
return entries.iterator();
|
return entries.iterator();
|
||||||
|
@ -346,57 +380,67 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected abstract T newEntry();
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
protected abstract T newEntry(StreamInput in) throws IOException;
|
||||||
innerReadFrom(in);
|
|
||||||
int size = in.readVInt();
|
|
||||||
entries.clear();
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
T newEntry = newEntry();
|
|
||||||
newEntry.readFrom(in);
|
|
||||||
entries.add(newEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected T newEntry() {
|
|
||||||
return (T)new Entry();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void innerReadFrom(StreamInput in) throws IOException {
|
|
||||||
name = in.readString();
|
|
||||||
size = in.readVInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
innerWriteTo(out);
|
out.writeString(name);
|
||||||
|
out.writeVInt(size);
|
||||||
|
|
||||||
|
// this is a hack to work around slightly different serialization order in older versions of TermSuggestion
|
||||||
|
if (out.getVersion().before(Version.V_7_0_0_alpha1) && this instanceof TermSuggestion) {
|
||||||
|
TermSuggestion termSuggestion = (TermSuggestion) this;
|
||||||
|
termSuggestion.getSort().writeTo(out);
|
||||||
|
}
|
||||||
|
|
||||||
out.writeVInt(entries.size());
|
out.writeVInt(entries.size());
|
||||||
for (Entry<?> entry : entries) {
|
for (Entry<?> entry : entries) {
|
||||||
entry.writeTo(out);
|
entry.writeTo(out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void innerWriteTo(StreamOutput out) throws IOException {
|
@Override
|
||||||
out.writeString(name);
|
public abstract String getWriteableName();
|
||||||
out.writeVInt(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
if (params.paramAsBoolean(RestSearchAction.TYPED_KEYS_PARAM, false)) {
|
if (params.paramAsBoolean(RestSearchAction.TYPED_KEYS_PARAM, false)) {
|
||||||
// Concatenates the type and the name of the suggestion (ex: completion#foo)
|
// Concatenates the type and the name of the suggestion (ex: completion#foo)
|
||||||
builder.startArray(String.join(Aggregation.TYPED_KEYS_DELIMITER, getType(), getName()));
|
builder.startArray(String.join(Aggregation.TYPED_KEYS_DELIMITER, getWriteableName(), getName()));
|
||||||
} else {
|
} else {
|
||||||
builder.startArray(getName());
|
builder.startArray(getName());
|
||||||
}
|
}
|
||||||
for (Entry<?> entry : entries) {
|
for (Entry<?> entry : entries) {
|
||||||
|
builder.startObject();
|
||||||
entry.toXContent(builder, params);
|
entry.toXContent(builder, params);
|
||||||
|
builder.endObject();
|
||||||
}
|
}
|
||||||
builder.endArray();
|
builder.endArray();
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (this == other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other == null || getClass() != other.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Suggestion otherSuggestion = (Suggestion) other;
|
||||||
|
return Objects.equals(name, otherSuggestion.name)
|
||||||
|
&& Objects.equals(size, otherSuggestion.size)
|
||||||
|
&& Objects.equals(entries, otherSuggestion.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name, size, entries);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static Suggestion<? extends Entry<? extends Option>> fromXContent(XContentParser parser) throws IOException {
|
public static Suggestion<? extends Entry<? extends Option>> fromXContent(XContentParser parser) throws IOException {
|
||||||
ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.currentToken(), parser::getTokenLocation);
|
ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.currentToken(), parser::getTokenLocation);
|
||||||
|
@ -417,7 +461,7 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
/**
|
/**
|
||||||
* Represents a part from the suggest text with suggested options.
|
* Represents a part from the suggest text with suggested options.
|
||||||
*/
|
*/
|
||||||
public static class Entry<O extends Entry.Option> implements Iterable<O>, Streamable, ToXContentObject {
|
public abstract static class Entry<O extends Option> implements Iterable<O>, Writeable, ToXContentFragment {
|
||||||
|
|
||||||
private static final String TEXT = "text";
|
private static final String TEXT = "text";
|
||||||
private static final String OFFSET = "offset";
|
private static final String OFFSET = "offset";
|
||||||
|
@ -436,7 +480,18 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
this.length = length;
|
this.length = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Entry() {
|
protected Entry() {}
|
||||||
|
|
||||||
|
public Entry(StreamInput in) throws IOException {
|
||||||
|
text = in.readText();
|
||||||
|
offset = in.readVInt();
|
||||||
|
length = in.readVInt();
|
||||||
|
int suggestedWords = in.readVInt();
|
||||||
|
options = new ArrayList<>(suggestedWords);
|
||||||
|
for (int j = 0; j < suggestedWords; j++) {
|
||||||
|
O newOption = newOption(in);
|
||||||
|
options.add(newOption);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addOption(O option) {
|
public void addOption(O option) {
|
||||||
|
@ -534,44 +589,27 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) {
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Entry<?> entry = (Entry<?>) o;
|
Entry<?> entry = (Entry<?>) o;
|
||||||
|
return Objects.equals(length, entry.length)
|
||||||
if (length != entry.length) return false;
|
&& Objects.equals(offset, entry.offset)
|
||||||
if (offset != entry.offset) return false;
|
&& Objects.equals(text, entry.text)
|
||||||
if (!this.text.equals(entry.text)) return false;
|
&& Objects.equals(options, entry.options);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = text.hashCode();
|
return Objects.hash(text, offset, length, options);
|
||||||
result = 31 * result + offset;
|
|
||||||
result = 31 * result + length;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected abstract O newOption();
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
protected abstract O newOption(StreamInput in) throws IOException;
|
||||||
text = in.readText();
|
|
||||||
offset = in.readVInt();
|
|
||||||
length = in.readVInt();
|
|
||||||
int suggestedWords = in.readVInt();
|
|
||||||
options = new ArrayList<>(suggestedWords);
|
|
||||||
for (int j = 0; j < suggestedWords; j++) {
|
|
||||||
O newOption = newOption();
|
|
||||||
newOption.readFrom(in);
|
|
||||||
options.add(newOption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected O newOption(){
|
|
||||||
return (O) new Option();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
@ -586,40 +624,29 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
builder.startObject();
|
|
||||||
builder.field(TEXT, text);
|
builder.field(TEXT, text);
|
||||||
builder.field(OFFSET, offset);
|
builder.field(OFFSET, offset);
|
||||||
builder.field(LENGTH, length);
|
builder.field(LENGTH, length);
|
||||||
builder.startArray(OPTIONS);
|
builder.startArray(OPTIONS);
|
||||||
for (Option option : options) {
|
for (Option option : options) {
|
||||||
|
builder.startObject();
|
||||||
option.toXContent(builder, params);
|
option.toXContent(builder, params);
|
||||||
|
builder.endObject();
|
||||||
}
|
}
|
||||||
builder.endArray();
|
builder.endArray();
|
||||||
builder.endObject();
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ObjectParser<Entry<Option>, Void> PARSER = new ObjectParser<>("SuggestionEntryParser", true, Entry::new);
|
|
||||||
|
|
||||||
static {
|
|
||||||
declareCommonFields(PARSER);
|
|
||||||
PARSER.declareObjectArray(Entry::addOptions, (p,c) -> Option.fromXContent(p), new ParseField(OPTIONS));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void declareCommonFields(ObjectParser<? extends Entry<? extends Option>, Void> parser) {
|
protected static void declareCommonFields(ObjectParser<? extends Entry<? extends Option>, Void> parser) {
|
||||||
parser.declareString((entry, text) -> entry.text = new Text(text), new ParseField(TEXT));
|
parser.declareString((entry, text) -> entry.text = new Text(text), new ParseField(TEXT));
|
||||||
parser.declareInt((entry, offset) -> entry.offset = offset, new ParseField(OFFSET));
|
parser.declareInt((entry, offset) -> entry.offset = offset, new ParseField(OFFSET));
|
||||||
parser.declareInt((entry, length) -> entry.length = length, new ParseField(LENGTH));
|
parser.declareInt((entry, length) -> entry.length = length, new ParseField(LENGTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Entry<? extends Option> fromXContent(XContentParser parser) {
|
|
||||||
return PARSER.apply(parser, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains the suggested text with its document frequency and score.
|
* Contains the suggested text with its document frequency and score.
|
||||||
*/
|
*/
|
||||||
public static class Option implements Streamable, ToXContentObject {
|
public abstract static class Option implements Writeable, ToXContentFragment {
|
||||||
|
|
||||||
public static final ParseField TEXT = new ParseField("text");
|
public static final ParseField TEXT = new ParseField("text");
|
||||||
public static final ParseField HIGHLIGHTED = new ParseField("highlighted");
|
public static final ParseField HIGHLIGHTED = new ParseField("highlighted");
|
||||||
|
@ -646,7 +673,13 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
this(text, null, score);
|
this(text, null, score);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Option() {
|
public Option() {}
|
||||||
|
|
||||||
|
public Option(StreamInput in) throws IOException {
|
||||||
|
text = in.readText();
|
||||||
|
score = in.readFloat();
|
||||||
|
highlighted = in.readOptionalText();
|
||||||
|
collateMatch = in.readOptionalBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -683,14 +716,6 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
this.score = score;
|
this.score = score;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
text = in.readText();
|
|
||||||
score = in.readFloat();
|
|
||||||
highlighted = in.readOptionalText();
|
|
||||||
collateMatch = in.readOptionalBoolean();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
out.writeText(text);
|
out.writeText(text);
|
||||||
|
@ -701,45 +726,19 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
builder.startObject();
|
|
||||||
innerToXContent(builder, params);
|
|
||||||
builder.endObject();
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
|
|
||||||
builder.field(TEXT.getPreferredName(), text);
|
builder.field(TEXT.getPreferredName(), text);
|
||||||
if (highlighted != null) {
|
if (highlighted != null) {
|
||||||
builder.field(HIGHLIGHTED.getPreferredName(), highlighted);
|
builder.field(HIGHLIGHTED.getPreferredName(), highlighted);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.field(SCORE.getPreferredName(), score);
|
builder.field(SCORE.getPreferredName(), score);
|
||||||
if (collateMatch != null) {
|
if (collateMatch != null) {
|
||||||
builder.field(COLLATE_MATCH.getPreferredName(), collateMatch.booleanValue());
|
builder.field(COLLATE_MATCH.getPreferredName(), collateMatch.booleanValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final ConstructingObjectParser<Option, Void> PARSER = new ConstructingObjectParser<>("SuggestOptionParser",
|
|
||||||
true, args -> {
|
|
||||||
Text text = new Text((String) args[0]);
|
|
||||||
float score = (Float) args[1];
|
|
||||||
String highlighted = (String) args[2];
|
|
||||||
Text highlightedText = highlighted == null ? null : new Text(highlighted);
|
|
||||||
Boolean collateMatch = (Boolean) args[3];
|
|
||||||
return new Option(text, highlightedText, score, collateMatch);
|
|
||||||
});
|
|
||||||
|
|
||||||
static {
|
|
||||||
PARSER.declareString(constructorArg(), TEXT);
|
|
||||||
PARSER.declareFloat(constructorArg(), SCORE);
|
|
||||||
PARSER.declareString(optionalConstructorArg(), HIGHLIGHTED);
|
|
||||||
PARSER.declareBoolean(optionalConstructorArg(), COLLATE_MATCH);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Option fromXContent(XContentParser parser) {
|
|
||||||
return PARSER.apply(parser, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void mergeInto(Option otherOption) {
|
protected void mergeInto(Option otherOption) {
|
||||||
score = Math.max(score, otherOption.score);
|
score = Math.max(score, otherOption.score);
|
||||||
if (otherOption.collateMatch != null) {
|
if (otherOption.collateMatch != null) {
|
||||||
|
@ -751,18 +750,25 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We consider options equal if they have the same text, even if their other fields may differ
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) {
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Option that = (Option) o;
|
Option that = (Option) o;
|
||||||
return text.equals(that.text);
|
return Objects.equals(text, that.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return text.hashCode();
|
return Objects.hash(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,8 +66,7 @@ import static org.elasticsearch.search.suggest.Suggest.COMPARATOR;
|
||||||
*/
|
*/
|
||||||
public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSuggestion.Entry> {
|
public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSuggestion.Entry> {
|
||||||
|
|
||||||
public static final String NAME = "completion";
|
@Deprecated
|
||||||
|
|
||||||
public static final int TYPE = 4;
|
public static final int TYPE = 4;
|
||||||
|
|
||||||
private boolean skipDuplicates;
|
private boolean skipDuplicates;
|
||||||
|
@ -86,14 +85,18 @@ public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSug
|
||||||
this.skipDuplicates = skipDuplicates;
|
this.skipDuplicates = skipDuplicates;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public CompletionSuggestion(StreamInput in) throws IOException {
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
super(in);
|
||||||
super.readFrom(in);
|
|
||||||
if (in.getVersion().onOrAfter(Version.V_6_1_0)) {
|
if (in.getVersion().onOrAfter(Version.V_6_1_0)) {
|
||||||
skipDuplicates = in.readBoolean();
|
skipDuplicates = in.readBoolean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWriteableName() {
|
||||||
|
return CompletionSuggestionBuilder.SUGGESTION_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
|
@ -121,6 +124,17 @@ public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSug
|
||||||
return getOptions().size() > 0;
|
return getOptions().size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return super.equals(other)
|
||||||
|
&& Objects.equals(skipDuplicates, ((CompletionSuggestion) other).skipDuplicates);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(super.hashCode(), skipDuplicates);
|
||||||
|
}
|
||||||
|
|
||||||
public static CompletionSuggestion fromXContent(XContentParser parser, String name) throws IOException {
|
public static CompletionSuggestion fromXContent(XContentParser parser, String name) throws IOException {
|
||||||
CompletionSuggestion suggestion = new CompletionSuggestion(name, -1, false);
|
CompletionSuggestion suggestion = new CompletionSuggestion(name, -1, false);
|
||||||
parseEntries(parser, suggestion, CompletionSuggestion.Entry::fromXContent);
|
parseEntries(parser, suggestion, CompletionSuggestion.Entry::fromXContent);
|
||||||
|
@ -222,13 +236,13 @@ public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSug
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getType() {
|
protected Entry newEntry() {
|
||||||
return NAME;
|
return new Entry();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Entry newEntry() {
|
protected Entry newEntry(StreamInput in) throws IOException {
|
||||||
return new Entry();
|
return new Entry(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Entry extends Suggest.Suggestion.Entry<CompletionSuggestion.Entry.Option> {
|
public static final class Entry extends Suggest.Suggestion.Entry<CompletionSuggestion.Entry.Option> {
|
||||||
|
@ -237,7 +251,10 @@ public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSug
|
||||||
super(text, offset, length);
|
super(text, offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
Entry() {
|
Entry() {}
|
||||||
|
|
||||||
|
public Entry(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -245,6 +262,11 @@ public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSug
|
||||||
return new Option();
|
return new Option();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Option newOption(StreamInput in) throws IOException {
|
||||||
|
return new Option(in);
|
||||||
|
}
|
||||||
|
|
||||||
private static ObjectParser<Entry, Void> PARSER = new ObjectParser<>("CompletionSuggestionEntryParser", true,
|
private static ObjectParser<Entry, Void> PARSER = new ObjectParser<>("CompletionSuggestionEntryParser", true,
|
||||||
Entry::new);
|
Entry::new);
|
||||||
|
|
||||||
|
@ -274,6 +296,25 @@ public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSug
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Option(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
this.doc = Lucene.readScoreDoc(in);
|
||||||
|
if (in.readBoolean()) {
|
||||||
|
this.hit = SearchHit.readSearchHit(in);
|
||||||
|
}
|
||||||
|
int contextSize = in.readInt();
|
||||||
|
this.contexts = new LinkedHashMap<>(contextSize);
|
||||||
|
for (int i = 0; i < contextSize; i++) {
|
||||||
|
String contextName = in.readString();
|
||||||
|
int nContexts = in.readVInt();
|
||||||
|
Set<CharSequence> contexts = new HashSet<>(nContexts);
|
||||||
|
for (int j = 0; j < nContexts; j++) {
|
||||||
|
contexts.add(in.readString());
|
||||||
|
}
|
||||||
|
this.contexts.put(contextName, contexts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void mergeInto(Suggest.Suggestion.Entry.Option otherOption) {
|
protected void mergeInto(Suggest.Suggestion.Entry.Option otherOption) {
|
||||||
// Completion suggestions are reduced by
|
// Completion suggestions are reduced by
|
||||||
|
@ -302,7 +343,7 @@ public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSug
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
builder.field(TEXT.getPreferredName(), getText());
|
builder.field(TEXT.getPreferredName(), getText());
|
||||||
if (hit != null) {
|
if (hit != null) {
|
||||||
hit.toInnerXContent(builder, params);
|
hit.toInnerXContent(builder, params);
|
||||||
|
@ -375,26 +416,6 @@ public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSug
|
||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
this.doc = Lucene.readScoreDoc(in);
|
|
||||||
if (in.readBoolean()) {
|
|
||||||
this.hit = SearchHit.readSearchHit(in);
|
|
||||||
}
|
|
||||||
int contextSize = in.readInt();
|
|
||||||
this.contexts = new LinkedHashMap<>(contextSize);
|
|
||||||
for (int i = 0; i < contextSize; i++) {
|
|
||||||
String contextName = in.readString();
|
|
||||||
int nContexts = in.readVInt();
|
|
||||||
Set<CharSequence> contexts = new HashSet<>(nContexts);
|
|
||||||
for (int j = 0; j < nContexts; j++) {
|
|
||||||
contexts.add(in.readString());
|
|
||||||
}
|
|
||||||
this.contexts.put(contextName, contexts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
|
|
|
@ -59,10 +59,12 @@ import java.util.Objects;
|
||||||
public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSuggestionBuilder> {
|
public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSuggestionBuilder> {
|
||||||
|
|
||||||
private static final XContentType CONTEXT_BYTES_XCONTENT_TYPE = XContentType.JSON;
|
private static final XContentType CONTEXT_BYTES_XCONTENT_TYPE = XContentType.JSON;
|
||||||
static final String SUGGESTION_NAME = "completion";
|
|
||||||
static final ParseField CONTEXTS_FIELD = new ParseField("contexts", "context");
|
static final ParseField CONTEXTS_FIELD = new ParseField("contexts", "context");
|
||||||
static final ParseField SKIP_DUPLICATES_FIELD = new ParseField("skip_duplicates");
|
static final ParseField SKIP_DUPLICATES_FIELD = new ParseField("skip_duplicates");
|
||||||
|
|
||||||
|
public static final String SUGGESTION_NAME = "completion";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {
|
* {
|
||||||
* "field" : STRING
|
* "field" : STRING
|
||||||
|
|
|
@ -133,9 +133,9 @@ public final class PhraseSuggester extends Suggester<PhraseSuggestionContext> {
|
||||||
highlighted = new Text(spare.toString());
|
highlighted = new Text(spare.toString());
|
||||||
}
|
}
|
||||||
if (collatePrune) {
|
if (collatePrune) {
|
||||||
resultEntry.addOption(new Suggestion.Entry.Option(phrase, highlighted, (float) (correction.score), collateMatch));
|
resultEntry.addOption(new PhraseSuggestion.Entry.Option(phrase, highlighted, (float) (correction.score), collateMatch));
|
||||||
} else {
|
} else {
|
||||||
resultEntry.addOption(new Suggestion.Entry.Option(phrase, highlighted, (float) (correction.score)));
|
resultEntry.addOption(new PhraseSuggestion.Entry.Option(phrase, highlighted, (float) (correction.score)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -23,41 +23,55 @@ import org.elasticsearch.common.ParseField;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.text.Text;
|
import org.elasticsearch.common.text.Text;
|
||||||
|
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||||
|
import org.elasticsearch.common.xcontent.ContextParser;
|
||||||
import org.elasticsearch.common.xcontent.ObjectParser;
|
import org.elasticsearch.common.xcontent.ObjectParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.search.suggest.Suggest;
|
import org.elasticsearch.search.suggest.Suggest;
|
||||||
import org.elasticsearch.search.suggest.Suggest.Suggestion;
|
import org.elasticsearch.search.suggest.Suggest.Suggestion;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||||
|
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suggestion entry returned from the {@link PhraseSuggester}.
|
* Suggestion entry returned from the {@link PhraseSuggester}.
|
||||||
*/
|
*/
|
||||||
public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry> {
|
public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry> {
|
||||||
|
|
||||||
public static final String NAME = "phrase";
|
@Deprecated
|
||||||
public static final int TYPE = 3;
|
public static final int TYPE = 3;
|
||||||
|
|
||||||
public PhraseSuggestion() {
|
public PhraseSuggestion() {}
|
||||||
}
|
|
||||||
|
|
||||||
public PhraseSuggestion(String name, int size) {
|
public PhraseSuggestion(String name, int size) {
|
||||||
super(name, size);
|
super(name, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PhraseSuggestion(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWriteableName() {
|
||||||
|
return PhraseSuggestionBuilder.SUGGESTION_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWriteableType() {
|
public int getWriteableType() {
|
||||||
return TYPE;
|
return TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getType() {
|
protected Entry newEntry() {
|
||||||
return NAME;
|
return new Entry();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Entry newEntry() {
|
protected Entry newEntry(StreamInput in) throws IOException {
|
||||||
return new Entry();
|
return new Entry(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PhraseSuggestion fromXContent(XContentParser parser, String name) throws IOException {
|
public static PhraseSuggestion fromXContent(XContentParser parser, String name) throws IOException {
|
||||||
|
@ -66,7 +80,7 @@ public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry>
|
||||||
return suggestion;
|
return suggestion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Entry extends Suggestion.Entry<Suggestion.Entry.Option> {
|
public static class Entry extends Suggestion.Entry<PhraseSuggestion.Entry.Option> {
|
||||||
|
|
||||||
protected double cutoffScore = Double.MIN_VALUE;
|
protected double cutoffScore = Double.MIN_VALUE;
|
||||||
|
|
||||||
|
@ -75,7 +89,15 @@ public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry>
|
||||||
this.cutoffScore = cutoffScore;
|
this.cutoffScore = cutoffScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
Entry() {
|
public Entry(Text text, int offset, int length) {
|
||||||
|
super(text, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry() {}
|
||||||
|
|
||||||
|
public Entry(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
cutoffScore = in.readDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,7 +108,7 @@ public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void merge(Suggestion.Entry<Suggestion.Entry.Option> other) {
|
protected void merge(Suggestion.Entry<Option> other) {
|
||||||
super.merge(other);
|
super.merge(other);
|
||||||
// If the cluster contains both pre 0.90.4 and post 0.90.4 nodes then we'll see Suggestion.Entry
|
// If the cluster contains both pre 0.90.4 and post 0.90.4 nodes then we'll see Suggestion.Entry
|
||||||
// objects being merged with PhraseSuggestion.Entry objects. We merge Suggestion.Entry objects
|
// objects being merged with PhraseSuggestion.Entry objects. We merge Suggestion.Entry objects
|
||||||
|
@ -100,7 +122,7 @@ public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addOption(Suggestion.Entry.Option option) {
|
public void addOption(Option option) {
|
||||||
if (option.getScore() > this.cutoffScore) {
|
if (option.getScore() > this.cutoffScore) {
|
||||||
this.options.add(option);
|
this.options.add(option);
|
||||||
}
|
}
|
||||||
|
@ -110,7 +132,8 @@ public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry>
|
||||||
|
|
||||||
static {
|
static {
|
||||||
declareCommonFields(PARSER);
|
declareCommonFields(PARSER);
|
||||||
PARSER.declareObjectArray(Entry::addOptions, (p,c) -> Option.fromXContent(p), new ParseField(OPTIONS));
|
PARSER.declareObjectArray(Entry::addOptions, (ContextParser<Void, Option>) (p, c) -> Option.fromXContent(p),
|
||||||
|
new ParseField(OPTIONS));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Entry fromXContent(XContentParser parser) {
|
public static Entry fromXContent(XContentParser parser) {
|
||||||
|
@ -118,9 +141,13 @@ public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
protected Option newOption() {
|
||||||
super.readFrom(in);
|
return new Option();
|
||||||
cutoffScore = in.readDouble();
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Option newOption(StreamInput in) throws IOException {
|
||||||
|
return new Option(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -128,5 +155,56 @@ public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry>
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
out.writeDouble(cutoffScore);
|
out.writeDouble(cutoffScore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return super.equals(other)
|
||||||
|
&& Objects.equals(cutoffScore, ((Entry) other).cutoffScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(super.hashCode(), cutoffScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Option extends Suggestion.Entry.Option {
|
||||||
|
|
||||||
|
public Option() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Option(Text text, Text highlighted, float score, Boolean collateMatch) {
|
||||||
|
super(text, highlighted, score, collateMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Option(Text text, Text highlighted, float score) {
|
||||||
|
super(text, highlighted, score);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Option(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ConstructingObjectParser<Option, Void> PARSER = new ConstructingObjectParser<>("PhraseOptionParser",
|
||||||
|
true, args -> {
|
||||||
|
Text text = new Text((String) args[0]);
|
||||||
|
float score = (Float) args[1];
|
||||||
|
String highlighted = (String) args[2];
|
||||||
|
Text highlightedText = highlighted == null ? null : new Text(highlighted);
|
||||||
|
Boolean collateMatch = (Boolean) args[3];
|
||||||
|
return new Option(text, highlightedText, score, collateMatch);
|
||||||
|
});
|
||||||
|
|
||||||
|
static {
|
||||||
|
PARSER.declareString(constructorArg(), TEXT);
|
||||||
|
PARSER.declareFloat(constructorArg(), SCORE);
|
||||||
|
PARSER.declareString(optionalConstructorArg(), HIGHLIGHTED);
|
||||||
|
PARSER.declareBoolean(optionalConstructorArg(), COLLATE_MATCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Option fromXContent(XContentParser parser) {
|
||||||
|
return PARSER.apply(parser, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSuggestionBuilder> {
|
public class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSuggestionBuilder> {
|
||||||
|
|
||||||
private static final String SUGGESTION_NAME = "phrase";
|
public static final String SUGGESTION_NAME = "phrase";
|
||||||
|
|
||||||
protected static final ParseField MAXERRORS_FIELD = new ParseField("max_errors");
|
protected static final ParseField MAXERRORS_FIELD = new ParseField("max_errors");
|
||||||
protected static final ParseField RWE_LIKELIHOOD_FIELD = new ParseField("real_word_error_likelihood");
|
protected static final ParseField RWE_LIKELIHOOD_FIELD = new ParseField("real_word_error_likelihood");
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.elasticsearch.search.suggest.term;
|
package org.elasticsearch.search.suggest.term;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.common.ParseField;
|
import org.elasticsearch.common.ParseField;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
@ -28,11 +29,13 @@ import org.elasticsearch.common.xcontent.ObjectParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.search.suggest.SortBy;
|
import org.elasticsearch.search.suggest.SortBy;
|
||||||
|
import org.elasticsearch.search.suggest.Suggest;
|
||||||
import org.elasticsearch.search.suggest.Suggest.Suggestion;
|
import org.elasticsearch.search.suggest.Suggest.Suggestion;
|
||||||
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
|
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||||
|
|
||||||
|
@ -41,22 +44,29 @@ import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constru
|
||||||
*/
|
*/
|
||||||
public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
|
public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
|
||||||
|
|
||||||
public static final String NAME = "term";
|
@Deprecated
|
||||||
|
public static final int TYPE = 1;
|
||||||
|
|
||||||
public static final Comparator<Suggestion.Entry.Option> SCORE = new Score();
|
public static final Comparator<Suggestion.Entry.Option> SCORE = new Score();
|
||||||
public static final Comparator<Suggestion.Entry.Option> FREQUENCY = new Frequency();
|
public static final Comparator<Suggestion.Entry.Option> FREQUENCY = new Frequency();
|
||||||
public static final int TYPE = 1;
|
|
||||||
|
|
||||||
private SortBy sort;
|
private SortBy sort;
|
||||||
|
|
||||||
public TermSuggestion() {
|
public TermSuggestion() {}
|
||||||
}
|
|
||||||
|
|
||||||
public TermSuggestion(String name, int size, SortBy sort) {
|
public TermSuggestion(String name, int size, SortBy sort) {
|
||||||
super(name, size);
|
super(name, size);
|
||||||
this.sort = sort;
|
this.sort = sort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TermSuggestion(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
|
||||||
|
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
|
||||||
|
sort = SortBy.readFromStream(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Same behaviour as comparators in suggest module, but for SuggestedWord
|
// Same behaviour as comparators in suggest module, but for SuggestedWord
|
||||||
// Highest score first, then highest freq first, then lowest term first
|
// Highest score first, then highest freq first, then lowest term first
|
||||||
public static class Score implements Comparator<Suggestion.Entry.Option> {
|
public static class Score implements Comparator<Suggestion.Entry.Option> {
|
||||||
|
@ -103,9 +113,12 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
|
||||||
return TYPE;
|
return TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void setSort(SortBy sort) {
|
||||||
protected String getType() {
|
this.sort = sort;
|
||||||
return NAME;
|
}
|
||||||
|
|
||||||
|
public SortBy getSort() {
|
||||||
|
return sort;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -121,15 +134,17 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void innerReadFrom(StreamInput in) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.innerReadFrom(in);
|
super.writeTo(out);
|
||||||
sort = SortBy.readFromStream(in);
|
|
||||||
|
if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
|
||||||
|
sort.writeTo(out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void innerWriteTo(StreamOutput out) throws IOException {
|
public String getWriteableName() {
|
||||||
super.innerWriteTo(out);
|
return TermSuggestionBuilder.SUGGESTION_NAME;
|
||||||
sort.writeTo(out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TermSuggestion fromXContent(XContentParser parser, String name) throws IOException {
|
public static TermSuggestion fromXContent(XContentParser parser, String name) throws IOException {
|
||||||
|
@ -144,16 +159,35 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
|
||||||
return new Entry();
|
return new Entry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Entry newEntry(StreamInput in) throws IOException {
|
||||||
|
return new Entry(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return super.equals(other)
|
||||||
|
&& Objects.equals(sort, ((TermSuggestion) other).sort);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(super.hashCode(), sort);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a part from the suggest text with suggested options.
|
* Represents a part from the suggest text with suggested options.
|
||||||
*/
|
*/
|
||||||
public static class Entry extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry<TermSuggestion.Entry.Option> {
|
public static class Entry extends Suggest.Suggestion.Entry<TermSuggestion.Entry.Option> {
|
||||||
|
|
||||||
public Entry(Text text, int offset, int length) {
|
public Entry(Text text, int offset, int length) {
|
||||||
super(text, offset, length);
|
super(text, offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
Entry() {
|
public Entry() {}
|
||||||
|
|
||||||
|
public Entry(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -161,6 +195,11 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
|
||||||
return new Option();
|
return new Option();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Option newOption(StreamInput in) throws IOException {
|
||||||
|
return new Option(in);
|
||||||
|
}
|
||||||
|
|
||||||
private static ObjectParser<Entry, Void> PARSER = new ObjectParser<>("TermSuggestionEntryParser", true, Entry::new);
|
private static ObjectParser<Entry, Void> PARSER = new ObjectParser<>("TermSuggestionEntryParser", true, Entry::new);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -175,7 +214,7 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
|
||||||
/**
|
/**
|
||||||
* Contains the suggested text with its document frequency and score.
|
* Contains the suggested text with its document frequency and score.
|
||||||
*/
|
*/
|
||||||
public static class Option extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option {
|
public static class Option extends Suggest.Suggestion.Entry.Option {
|
||||||
|
|
||||||
public static final ParseField FREQ = new ParseField("freq");
|
public static final ParseField FREQ = new ParseField("freq");
|
||||||
|
|
||||||
|
@ -186,6 +225,11 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
|
||||||
this.freq = freq;
|
this.freq = freq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Option(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
freq = in.readVInt();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void mergeInto(Suggestion.Entry.Option otherOption) {
|
protected void mergeInto(Suggestion.Entry.Option otherOption) {
|
||||||
super.mergeInto(otherOption);
|
super.mergeInto(otherOption);
|
||||||
|
@ -207,12 +251,6 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
|
||||||
return freq;
|
return freq;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
freq = in.readVInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
|
@ -220,8 +258,8 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
builder = super.innerToXContent(builder, params);
|
builder = super.toXContent(builder, params);
|
||||||
builder.field(FREQ.getPreferredName(), freq);
|
builder.field(FREQ.getPreferredName(), freq);
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ import static org.elasticsearch.search.suggest.phrase.DirectCandidateGeneratorBu
|
||||||
*/
|
*/
|
||||||
public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuilder> {
|
public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuilder> {
|
||||||
|
|
||||||
private static final String SUGGESTION_NAME = "term";
|
public static final String SUGGESTION_NAME = "term";
|
||||||
|
|
||||||
private SuggestMode suggestMode = SuggestMode.MISSING;
|
private SuggestMode suggestMode = SuggestMode.MISSING;
|
||||||
private float accuracy = DEFAULT_ACCURACY;
|
private float accuracy = DEFAULT_ACCURACY;
|
||||||
|
|
|
@ -40,15 +40,11 @@ import java.util.Objects;
|
||||||
/**
|
/**
|
||||||
* Stores information about failures that occurred during shard snapshotting process
|
* Stores information about failures that occurred during shard snapshotting process
|
||||||
*/
|
*/
|
||||||
public class SnapshotShardFailure implements ShardOperationFailedException {
|
public class SnapshotShardFailure extends ShardOperationFailedException {
|
||||||
private ShardId shardId;
|
|
||||||
|
|
||||||
private String reason;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String nodeId;
|
private String nodeId;
|
||||||
|
private ShardId shardId;
|
||||||
private RestStatus status;
|
|
||||||
|
|
||||||
private SnapshotShardFailure() {
|
private SnapshotShardFailure() {
|
||||||
|
|
||||||
|
@ -74,56 +70,9 @@ public class SnapshotShardFailure implements ShardOperationFailedException {
|
||||||
* @param status rest status
|
* @param status rest status
|
||||||
*/
|
*/
|
||||||
private SnapshotShardFailure(@Nullable String nodeId, ShardId shardId, String reason, RestStatus status) {
|
private SnapshotShardFailure(@Nullable String nodeId, ShardId shardId, String reason, RestStatus status) {
|
||||||
assert reason != null;
|
super(shardId.getIndexName(), shardId.id(), reason, status, new IndexShardSnapshotFailedException(shardId, reason));
|
||||||
this.nodeId = nodeId;
|
this.nodeId = nodeId;
|
||||||
this.shardId = shardId;
|
this.shardId = shardId;
|
||||||
this.reason = reason;
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns index where failure occurred
|
|
||||||
*
|
|
||||||
* @return index
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String index() {
|
|
||||||
return this.shardId.getIndexName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns shard id where failure occurred
|
|
||||||
*
|
|
||||||
* @return shard id
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int shardId() {
|
|
||||||
return this.shardId.id();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns reason for the failure
|
|
||||||
*
|
|
||||||
* @return reason for the failure
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String reason() {
|
|
||||||
return this.reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@link RestStatus} corresponding to this failure
|
|
||||||
*
|
|
||||||
* @return REST status
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public RestStatus status() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Throwable getCause() {
|
|
||||||
return new IndexShardSnapshotFailedException(shardId, reason);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -142,7 +91,7 @@ public class SnapshotShardFailure implements ShardOperationFailedException {
|
||||||
* @param in stream input
|
* @param in stream input
|
||||||
* @return shard failure information
|
* @return shard failure information
|
||||||
*/
|
*/
|
||||||
public static SnapshotShardFailure readSnapshotShardFailure(StreamInput in) throws IOException {
|
static SnapshotShardFailure readSnapshotShardFailure(StreamInput in) throws IOException {
|
||||||
SnapshotShardFailure exp = new SnapshotShardFailure();
|
SnapshotShardFailure exp = new SnapshotShardFailure();
|
||||||
exp.readFrom(in);
|
exp.readFrom(in);
|
||||||
return exp;
|
return exp;
|
||||||
|
@ -152,6 +101,8 @@ public class SnapshotShardFailure implements ShardOperationFailedException {
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
nodeId = in.readOptionalString();
|
nodeId = in.readOptionalString();
|
||||||
shardId = ShardId.readShardId(in);
|
shardId = ShardId.readShardId(in);
|
||||||
|
super.shardId = shardId.getId();
|
||||||
|
super.index = shardId.getIndexName();
|
||||||
reason = in.readString();
|
reason = in.readString();
|
||||||
status = RestStatus.readFrom(in);
|
status = RestStatus.readFrom(in);
|
||||||
}
|
}
|
||||||
|
|
|
@ -588,7 +588,7 @@ public abstract class TcpTransport extends AbstractLifecycleComponent implements
|
||||||
* takes a {@link ConnectionProfile} that have been passed as a parameter to the public methods
|
* takes a {@link ConnectionProfile} that have been passed as a parameter to the public methods
|
||||||
* and resolves it to a fully specified (i.e., no nulls) profile
|
* and resolves it to a fully specified (i.e., no nulls) profile
|
||||||
*/
|
*/
|
||||||
static ConnectionProfile resolveConnectionProfile(@Nullable ConnectionProfile connectionProfile,
|
protected static ConnectionProfile resolveConnectionProfile(@Nullable ConnectionProfile connectionProfile,
|
||||||
ConnectionProfile defaultConnectionProfile) {
|
ConnectionProfile defaultConnectionProfile) {
|
||||||
Objects.requireNonNull(defaultConnectionProfile);
|
Objects.requireNonNull(defaultConnectionProfile);
|
||||||
if (connectionProfile == null) {
|
if (connectionProfile == null) {
|
||||||
|
|
|
@ -174,36 +174,13 @@ public class ExceptionsHelperTests extends ESTestCase {
|
||||||
return new ShardSearchFailure(queryShardException, null);
|
return new ShardSearchFailure(queryShardException, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGroupByNullCause() {
|
|
||||||
ShardOperationFailedException[] failures = new ShardOperationFailedException[] {
|
|
||||||
new ShardSearchFailure("error", createSearchShardTarget("node0", 0, "index", null)),
|
|
||||||
new ShardSearchFailure("error", createSearchShardTarget("node1", 1, "index", null)),
|
|
||||||
new ShardSearchFailure("error", createSearchShardTarget("node1", 1, "index2", null)),
|
|
||||||
new ShardSearchFailure("error", createSearchShardTarget("node2", 2, "index", "cluster1")),
|
|
||||||
new ShardSearchFailure("error", createSearchShardTarget("node1", 1, "index", "cluster1")),
|
|
||||||
new ShardSearchFailure("a different error", createSearchShardTarget("node3", 3, "index", "cluster1"))
|
|
||||||
};
|
|
||||||
|
|
||||||
ShardOperationFailedException[] groupBy = ExceptionsHelper.groupBy(failures);
|
|
||||||
assertThat(groupBy.length, equalTo(4));
|
|
||||||
String[] expectedIndices = new String[]{"index", "index2", "cluster1:index", "cluster1:index"};
|
|
||||||
String[] expectedErrors = new String[]{"error", "error", "error", "a different error"};
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (ShardOperationFailedException shardOperationFailedException : groupBy) {
|
|
||||||
assertThat(shardOperationFailedException.reason(), equalTo(expectedErrors[i]));
|
|
||||||
assertThat(shardOperationFailedException.index(), equalTo(expectedIndices[i++]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGroupByNullIndex() {
|
public void testGroupByNullIndex() {
|
||||||
ShardOperationFailedException[] failures = new ShardOperationFailedException[] {
|
ShardOperationFailedException[] failures = new ShardOperationFailedException[] {
|
||||||
new ShardSearchFailure("error", null),
|
|
||||||
new ShardSearchFailure(new IllegalArgumentException("error")),
|
new ShardSearchFailure(new IllegalArgumentException("error")),
|
||||||
new ShardSearchFailure(new ParsingException(0, 0, "error", null)),
|
new ShardSearchFailure(new ParsingException(0, 0, "error", null)),
|
||||||
};
|
};
|
||||||
|
|
||||||
ShardOperationFailedException[] groupBy = ExceptionsHelper.groupBy(failures);
|
ShardOperationFailedException[] groupBy = ExceptionsHelper.groupBy(failures);
|
||||||
assertThat(groupBy.length, equalTo(3));
|
assertThat(groupBy.length, equalTo(2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch 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;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.Nullable;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.rest.RestStatus;
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class ShardOperationFailedExceptionTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testCauseCannotBeNull() {
|
||||||
|
NullPointerException nullPointerException = expectThrows(NullPointerException.class, () -> new Failure(
|
||||||
|
randomAlphaOfLengthBetween(3, 10), randomInt(), randomAlphaOfLengthBetween(5, 10), randomFrom(RestStatus.values()), null));
|
||||||
|
assertEquals("cause cannot be null", nullPointerException.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testStatusCannotBeNull() {
|
||||||
|
NullPointerException nullPointerException = expectThrows(NullPointerException.class, () -> new Failure(
|
||||||
|
randomAlphaOfLengthBetween(3, 10), randomInt(), randomAlphaOfLengthBetween(5, 10), null, new IllegalArgumentException()));
|
||||||
|
assertEquals("status cannot be null", nullPointerException.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReasonCannotBeNull() {
|
||||||
|
NullPointerException nullPointerException = expectThrows(NullPointerException.class, () -> new Failure(
|
||||||
|
randomAlphaOfLengthBetween(3, 10), randomInt(), null, randomFrom(RestStatus.values()), new IllegalArgumentException()));
|
||||||
|
assertEquals("reason cannot be null", nullPointerException.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testIndexIsNullable() {
|
||||||
|
new Failure(null, randomInt(), randomAlphaOfLengthBetween(5, 10), randomFrom(RestStatus.values()), new IllegalArgumentException());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Failure extends ShardOperationFailedException {
|
||||||
|
|
||||||
|
Failure(@Nullable String index, int shardId, String reason, RestStatus status, Throwable cause) {
|
||||||
|
super(index, shardId, reason, status, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,8 +22,10 @@ import org.apache.lucene.search.MatchNoDocsQuery;
|
||||||
import org.apache.lucene.search.Query;
|
import org.apache.lucene.search.Query;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||||
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||||
import org.elasticsearch.common.lucene.search.Queries;
|
import org.elasticsearch.common.lucene.search.Queries;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.index.IndexSettings;
|
import org.elasticsearch.index.IndexSettings;
|
||||||
import org.elasticsearch.index.fielddata.IndexFieldData;
|
import org.elasticsearch.index.fielddata.IndexFieldData;
|
||||||
import org.elasticsearch.index.fielddata.plain.AbstractAtomicOrdinalsFieldData;
|
import org.elasticsearch.index.fielddata.plain.AbstractAtomicOrdinalsFieldData;
|
||||||
|
@ -37,6 +39,7 @@ import org.elasticsearch.test.ESTestCase;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
@ -49,24 +52,7 @@ import static org.mockito.Mockito.when;
|
||||||
public class QueryShardContextTests extends ESTestCase {
|
public class QueryShardContextTests extends ESTestCase {
|
||||||
|
|
||||||
public void testFailIfFieldMappingNotFound() {
|
public void testFailIfFieldMappingNotFound() {
|
||||||
IndexMetaData.Builder indexMetadataBuilder = new IndexMetaData.Builder("index");
|
QueryShardContext context = createQueryShardContext(IndexMetaData.INDEX_UUID_NA_VALUE, null);
|
||||||
indexMetadataBuilder.settings(Settings.builder().put("index.version.created", Version.CURRENT)
|
|
||||||
.put("index.number_of_shards", 1)
|
|
||||||
.put("index.number_of_replicas", 1)
|
|
||||||
);
|
|
||||||
IndexMetaData indexMetaData = indexMetadataBuilder.build();
|
|
||||||
IndexSettings indexSettings = new IndexSettings(indexMetaData, Settings.EMPTY);
|
|
||||||
MapperService mapperService = mock(MapperService.class);
|
|
||||||
when(mapperService.getIndexSettings()).thenReturn(indexSettings);
|
|
||||||
when(mapperService.index()).thenReturn(indexMetaData.getIndex());
|
|
||||||
final long nowInMillis = randomNonNegativeLong();
|
|
||||||
|
|
||||||
QueryShardContext context = new QueryShardContext(
|
|
||||||
0, indexSettings, null, (mappedFieldType, idxName) ->
|
|
||||||
mappedFieldType.fielddataBuilder(idxName).build(indexSettings, mappedFieldType, null, null, null)
|
|
||||||
, mapperService, null, null, xContentRegistry(), writableRegistry(), null, null,
|
|
||||||
() -> nowInMillis, null);
|
|
||||||
|
|
||||||
context.setAllowUnmappedFields(false);
|
context.setAllowUnmappedFields(false);
|
||||||
MappedFieldType fieldType = new TextFieldMapper.TextFieldType();
|
MappedFieldType fieldType = new TextFieldMapper.TextFieldType();
|
||||||
MappedFieldType result = context.failIfFieldMappingNotFound("name", fieldType);
|
MappedFieldType result = context.failIfFieldMappingNotFound("name", fieldType);
|
||||||
|
@ -91,30 +77,16 @@ public class QueryShardContextTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testClusterAlias() throws IOException {
|
public void testClusterAlias() throws IOException {
|
||||||
IndexMetaData.Builder indexMetadataBuilder = new IndexMetaData.Builder("index");
|
|
||||||
indexMetadataBuilder.settings(Settings.builder().put("index.version.created", Version.CURRENT)
|
|
||||||
.put("index.number_of_shards", 1)
|
|
||||||
.put("index.number_of_replicas", 1)
|
|
||||||
);
|
|
||||||
IndexMetaData indexMetaData = indexMetadataBuilder.build();
|
|
||||||
IndexSettings indexSettings = new IndexSettings(indexMetaData, Settings.EMPTY);
|
|
||||||
MapperService mapperService = mock(MapperService.class);
|
|
||||||
when(mapperService.getIndexSettings()).thenReturn(indexSettings);
|
|
||||||
when(mapperService.index()).thenReturn(indexMetaData.getIndex());
|
|
||||||
final long nowInMillis = randomNonNegativeLong();
|
|
||||||
|
|
||||||
Mapper.BuilderContext ctx = new Mapper.BuilderContext(indexSettings.getSettings(), new ContentPath());
|
|
||||||
IndexFieldMapper mapper = new IndexFieldMapper.Builder(null).build(ctx);
|
|
||||||
final String clusterAlias = randomBoolean() ? null : "remote_cluster";
|
final String clusterAlias = randomBoolean() ? null : "remote_cluster";
|
||||||
QueryShardContext context = new QueryShardContext(
|
QueryShardContext context = createQueryShardContext(IndexMetaData.INDEX_UUID_NA_VALUE, clusterAlias);
|
||||||
0, indexSettings, null, (mappedFieldType, indexname) ->
|
|
||||||
mappedFieldType.fielddataBuilder(indexname).build(indexSettings, mappedFieldType, null, null, mapperService)
|
|
||||||
, mapperService, null, null, xContentRegistry(), writableRegistry(), null, null,
|
Mapper.BuilderContext ctx = new Mapper.BuilderContext(context.getIndexSettings().getSettings(), new ContentPath());
|
||||||
() -> nowInMillis, clusterAlias);
|
IndexFieldMapper mapper = new IndexFieldMapper.Builder(null).build(ctx);
|
||||||
|
|
||||||
IndexFieldData<?> forField = context.getForField(mapper.fieldType());
|
IndexFieldData<?> forField = context.getForField(mapper.fieldType());
|
||||||
String expected = clusterAlias == null ? indexMetaData.getIndex().getName()
|
String expected = clusterAlias == null ? context.getIndexSettings().getIndexMetaData().getIndex().getName()
|
||||||
: clusterAlias + ":" + indexMetaData.getIndex().getName();
|
: clusterAlias + ":" + context.getIndexSettings().getIndex().getName();
|
||||||
assertEquals(expected, ((AbstractAtomicOrdinalsFieldData)forField.load(null)).getOrdinalsValues().lookupOrd(0).utf8ToString());
|
assertEquals(expected, ((AbstractAtomicOrdinalsFieldData)forField.load(null)).getOrdinalsValues().lookupOrd(0).utf8ToString());
|
||||||
Query query = mapper.fieldType().termQuery("index", context);
|
Query query = mapper.fieldType().termQuery("index", context);
|
||||||
if (clusterAlias == null) {
|
if (clusterAlias == null) {
|
||||||
|
@ -133,4 +105,32 @@ public class QueryShardContextTests extends ESTestCase {
|
||||||
assertThat(query, Matchers.instanceOf(MatchNoDocsQuery.class));
|
assertThat(query, Matchers.instanceOf(MatchNoDocsQuery.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testGetFullyQualifiedIndex() {
|
||||||
|
String clusterAlias = randomAlphaOfLengthBetween(5, 10);
|
||||||
|
String indexUuid = randomAlphaOfLengthBetween(3, 10);
|
||||||
|
QueryShardContext shardContext = createQueryShardContext(indexUuid, clusterAlias);
|
||||||
|
assertThat(shardContext.getFullyQualifiedIndex().getName(), equalTo(clusterAlias + ":index"));
|
||||||
|
assertThat(shardContext.getFullyQualifiedIndex().getUUID(), equalTo(indexUuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QueryShardContext createQueryShardContext(String indexUuid, String clusterAlias) {
|
||||||
|
IndexMetaData.Builder indexMetadataBuilder = new IndexMetaData.Builder("index");
|
||||||
|
indexMetadataBuilder.settings(Settings.builder().put("index.version.created", Version.CURRENT)
|
||||||
|
.put("index.number_of_shards", 1)
|
||||||
|
.put("index.number_of_replicas", 1)
|
||||||
|
.put(IndexMetaData.SETTING_INDEX_UUID, indexUuid)
|
||||||
|
);
|
||||||
|
IndexMetaData indexMetaData = indexMetadataBuilder.build();
|
||||||
|
IndexSettings indexSettings = new IndexSettings(indexMetaData, Settings.EMPTY);
|
||||||
|
MapperService mapperService = mock(MapperService.class);
|
||||||
|
when(mapperService.getIndexSettings()).thenReturn(indexSettings);
|
||||||
|
when(mapperService.index()).thenReturn(indexMetaData.getIndex());
|
||||||
|
final long nowInMillis = randomNonNegativeLong();
|
||||||
|
|
||||||
|
return new QueryShardContext(
|
||||||
|
0, indexSettings, null, (mappedFieldType, idxName) ->
|
||||||
|
mappedFieldType.fielddataBuilder(idxName).build(indexSettings, mappedFieldType, null, null, null)
|
||||||
|
, mapperService, null, null, NamedXContentRegistry.EMPTY, new NamedWriteableRegistry(Collections.emptyList()), null, null,
|
||||||
|
() -> nowInMillis, clusterAlias);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch 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.index.query;
|
||||||
|
|
||||||
|
import org.elasticsearch.index.Index;
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
|
||||||
|
public class QueryShardExceptionTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testCreateFromQueryShardContext() {
|
||||||
|
String indexUuid = randomAlphaOfLengthBetween(5, 10);
|
||||||
|
String clusterAlias = randomAlphaOfLengthBetween(5, 10);
|
||||||
|
QueryShardContext queryShardContext = QueryShardContextTests.createQueryShardContext(indexUuid, clusterAlias);
|
||||||
|
{
|
||||||
|
QueryShardException queryShardException = new QueryShardException(queryShardContext, "error");
|
||||||
|
assertThat(queryShardException.getIndex().getName(), equalTo(clusterAlias + ":index"));
|
||||||
|
assertThat(queryShardException.getIndex().getUUID(), equalTo(indexUuid));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QueryShardException queryShardException = new QueryShardException(queryShardContext, "error", new IllegalArgumentException());
|
||||||
|
assertThat(queryShardException.getIndex().getName(), equalTo(clusterAlias + ":index"));
|
||||||
|
assertThat(queryShardException.getIndex().getUUID(), equalTo(indexUuid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreateFromIndex() {
|
||||||
|
String indexUuid = randomAlphaOfLengthBetween(5, 10);
|
||||||
|
String indexName = randomAlphaOfLengthBetween(5, 10);
|
||||||
|
Index index = new Index(indexName, indexUuid);
|
||||||
|
QueryShardException queryShardException = new QueryShardException(index, "error", new IllegalArgumentException());
|
||||||
|
assertThat(queryShardException.getIndex().getName(), equalTo(indexName));
|
||||||
|
assertThat(queryShardException.getIndex().getUUID(), equalTo(indexUuid));
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,6 @@ import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.elasticsearch.test.AbstractBuilderTestCase.STRING_ALIAS_FIELD_NAME;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
|
||||||
|
@ -143,7 +142,7 @@ public class WildcardQueryBuilderTests extends AbstractQueryTestCase<WildcardQue
|
||||||
|
|
||||||
public void testIndexWildcard() throws IOException {
|
public void testIndexWildcard() throws IOException {
|
||||||
QueryShardContext context = createShardContext();
|
QueryShardContext context = createShardContext();
|
||||||
String index = context.getFullyQualifiedIndexName();
|
String index = context.getFullyQualifiedIndex().getName();
|
||||||
|
|
||||||
Query query = new WildcardQueryBuilder("_index", index).doToQuery(context);
|
Query query = new WildcardQueryBuilder("_index", index).doToQuery(context);
|
||||||
assertThat(query instanceof MatchAllDocsQuery, equalTo(true));
|
assertThat(query instanceof MatchAllDocsQuery, equalTo(true));
|
||||||
|
|
|
@ -47,7 +47,9 @@ import java.util.Set;
|
||||||
import java.util.concurrent.BrokenBarrierException;
|
import java.util.concurrent.BrokenBarrierException;
|
||||||
import java.util.concurrent.CyclicBarrier;
|
import java.util.concurrent.CyclicBarrier;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.LongConsumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -60,7 +62,7 @@ import static org.hamcrest.Matchers.greaterThan;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
|
||||||
public class ReplicationTrackerTests extends ESTestCase {
|
public class ReplicationTrackerTests extends ESTestCase {
|
||||||
|
|
||||||
public void testEmptyShards() {
|
public void testEmptyShards() {
|
||||||
final ReplicationTracker tracker = newTracker(AllocationId.newInitializing());
|
final ReplicationTracker tracker = newTracker(AllocationId.newInitializing());
|
||||||
assertThat(tracker.getGlobalCheckpoint(), equalTo(UNASSIGNED_SEQ_NO));
|
assertThat(tracker.getGlobalCheckpoint(), equalTo(UNASSIGNED_SEQ_NO));
|
||||||
|
@ -99,6 +101,11 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
return allocationIds.stream().map(AllocationId::getId).collect(Collectors.toSet());
|
return allocationIds.stream().map(AllocationId::getId).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateLocalCheckpoint(final ReplicationTracker tracker, final String allocationId, final long localCheckpoint) {
|
||||||
|
tracker.updateLocalCheckpoint(allocationId, localCheckpoint);
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), equalTo(tracker.getGlobalCheckpoint()));
|
||||||
|
}
|
||||||
|
|
||||||
public void testGlobalCheckpointUpdate() {
|
public void testGlobalCheckpointUpdate() {
|
||||||
final long initialClusterStateVersion = randomNonNegativeLong();
|
final long initialClusterStateVersion = randomNonNegativeLong();
|
||||||
Map<AllocationId, Long> allocations = new HashMap<>();
|
Map<AllocationId, Long> allocations = new HashMap<>();
|
||||||
|
@ -137,14 +144,14 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
assertThat(tracker.getReplicationGroup().getReplicationTargets().size(), equalTo(1));
|
assertThat(tracker.getReplicationGroup().getReplicationTargets().size(), equalTo(1));
|
||||||
initializing.forEach(aId -> markAsTrackingAndInSyncQuietly(tracker, aId.getId(), NO_OPS_PERFORMED));
|
initializing.forEach(aId -> markAsTrackingAndInSyncQuietly(tracker, aId.getId(), NO_OPS_PERFORMED));
|
||||||
assertThat(tracker.getReplicationGroup().getReplicationTargets().size(), equalTo(1 + initializing.size()));
|
assertThat(tracker.getReplicationGroup().getReplicationTargets().size(), equalTo(1 + initializing.size()));
|
||||||
allocations.keySet().forEach(aId -> tracker.updateLocalCheckpoint(aId.getId(), allocations.get(aId)));
|
allocations.keySet().forEach(aId -> updateLocalCheckpoint(tracker, aId.getId(), allocations.get(aId)));
|
||||||
|
|
||||||
assertThat(tracker.getGlobalCheckpoint(), equalTo(minLocalCheckpoint));
|
assertThat(tracker.getGlobalCheckpoint(), equalTo(minLocalCheckpoint));
|
||||||
|
|
||||||
// increment checkpoints
|
// increment checkpoints
|
||||||
active.forEach(aId -> allocations.put(aId, allocations.get(aId) + 1 + randomInt(4)));
|
active.forEach(aId -> allocations.put(aId, allocations.get(aId) + 1 + randomInt(4)));
|
||||||
initializing.forEach(aId -> allocations.put(aId, allocations.get(aId) + 1 + randomInt(4)));
|
initializing.forEach(aId -> allocations.put(aId, allocations.get(aId) + 1 + randomInt(4)));
|
||||||
allocations.keySet().forEach(aId -> tracker.updateLocalCheckpoint(aId.getId(), allocations.get(aId)));
|
allocations.keySet().forEach(aId -> updateLocalCheckpoint(tracker, aId.getId(), allocations.get(aId)));
|
||||||
|
|
||||||
final long minLocalCheckpointAfterUpdates =
|
final long minLocalCheckpointAfterUpdates =
|
||||||
allocations.entrySet().stream().map(Map.Entry::getValue).min(Long::compareTo).orElse(UNASSIGNED_SEQ_NO);
|
allocations.entrySet().stream().map(Map.Entry::getValue).min(Long::compareTo).orElse(UNASSIGNED_SEQ_NO);
|
||||||
|
@ -153,7 +160,7 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
final AllocationId extraId = AllocationId.newInitializing();
|
final AllocationId extraId = AllocationId.newInitializing();
|
||||||
|
|
||||||
// first check that adding it without the master blessing doesn't change anything.
|
// first check that adding it without the master blessing doesn't change anything.
|
||||||
tracker.updateLocalCheckpoint(extraId.getId(), minLocalCheckpointAfterUpdates + 1 + randomInt(4));
|
updateLocalCheckpoint(tracker, extraId.getId(), minLocalCheckpointAfterUpdates + 1 + randomInt(4));
|
||||||
assertNull(tracker.checkpoints.get(extraId));
|
assertNull(tracker.checkpoints.get(extraId));
|
||||||
expectThrows(IllegalStateException.class, () -> tracker.initiateTracking(extraId.getId()));
|
expectThrows(IllegalStateException.class, () -> tracker.initiateTracking(extraId.getId()));
|
||||||
|
|
||||||
|
@ -165,7 +172,7 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
|
|
||||||
// now notify for the new id
|
// now notify for the new id
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
tracker.updateLocalCheckpoint(extraId.getId(), minLocalCheckpointAfterUpdates + 1 + randomInt(4));
|
updateLocalCheckpoint(tracker, extraId.getId(), minLocalCheckpointAfterUpdates + 1 + randomInt(4));
|
||||||
markAsTrackingAndInSyncQuietly(tracker, extraId.getId(), randomInt((int) minLocalCheckpointAfterUpdates));
|
markAsTrackingAndInSyncQuietly(tracker, extraId.getId(), randomInt((int) minLocalCheckpointAfterUpdates));
|
||||||
} else {
|
} else {
|
||||||
markAsTrackingAndInSyncQuietly(tracker, extraId.getId(), minLocalCheckpointAfterUpdates + 1 + randomInt(4));
|
markAsTrackingAndInSyncQuietly(tracker, extraId.getId(), minLocalCheckpointAfterUpdates + 1 + randomInt(4));
|
||||||
|
@ -175,6 +182,64 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
assertThat(tracker.getGlobalCheckpoint(), greaterThan(minLocalCheckpoint));
|
assertThat(tracker.getGlobalCheckpoint(), greaterThan(minLocalCheckpoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testUpdateGlobalCheckpointOnReplica() {
|
||||||
|
final AllocationId active = AllocationId.newInitializing();
|
||||||
|
final ReplicationTracker tracker = newTracker(active);
|
||||||
|
final long globalCheckpoint = randomLongBetween(NO_OPS_PERFORMED, Long.MAX_VALUE - 1);
|
||||||
|
tracker.updateGlobalCheckpointOnReplica(globalCheckpoint, "test");
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), equalTo(globalCheckpoint));
|
||||||
|
final long nonUpdate = randomLongBetween(NO_OPS_PERFORMED, globalCheckpoint);
|
||||||
|
updatedGlobalCheckpoint.set(UNASSIGNED_SEQ_NO);
|
||||||
|
tracker.updateGlobalCheckpointOnReplica(nonUpdate, "test");
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), equalTo(UNASSIGNED_SEQ_NO));
|
||||||
|
final long update = randomLongBetween(globalCheckpoint, Long.MAX_VALUE);
|
||||||
|
tracker.updateGlobalCheckpointOnReplica(update, "test");
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), equalTo(update));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testMarkAllocationIdAsInSync() throws BrokenBarrierException, InterruptedException {
|
||||||
|
final long initialClusterStateVersion = randomNonNegativeLong();
|
||||||
|
Map<AllocationId, Long> activeWithCheckpoints = randomAllocationsWithLocalCheckpoints(1, 1);
|
||||||
|
Set<AllocationId> active = new HashSet<>(activeWithCheckpoints.keySet());
|
||||||
|
Map<AllocationId, Long> initializingWithCheckpoints = randomAllocationsWithLocalCheckpoints(1, 1);
|
||||||
|
Set<AllocationId> initializing = new HashSet<>(initializingWithCheckpoints.keySet());
|
||||||
|
final AllocationId primaryId = active.iterator().next();
|
||||||
|
final AllocationId replicaId = initializing.iterator().next();
|
||||||
|
final ReplicationTracker tracker = newTracker(primaryId);
|
||||||
|
tracker.updateFromMaster(initialClusterStateVersion, ids(active), routingTable(initializing, primaryId), emptySet());
|
||||||
|
final long localCheckpoint = randomLongBetween(0, Long.MAX_VALUE - 1);
|
||||||
|
tracker.activatePrimaryMode(localCheckpoint);
|
||||||
|
tracker.initiateTracking(replicaId.getId());
|
||||||
|
final CyclicBarrier barrier = new CyclicBarrier(2);
|
||||||
|
final Thread thread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
barrier.await();
|
||||||
|
tracker.markAllocationIdAsInSync(
|
||||||
|
replicaId.getId(),
|
||||||
|
randomLongBetween(NO_OPS_PERFORMED, localCheckpoint - 1));
|
||||||
|
barrier.await();
|
||||||
|
} catch (BrokenBarrierException | InterruptedException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
barrier.await();
|
||||||
|
awaitBusy(tracker::pendingInSync);
|
||||||
|
final long updatedLocalCheckpoint = randomLongBetween(1 + localCheckpoint, Long.MAX_VALUE);
|
||||||
|
// there is a shard copy pending in sync, the global checkpoint can not advance
|
||||||
|
updatedGlobalCheckpoint.set(UNASSIGNED_SEQ_NO);
|
||||||
|
tracker.updateLocalCheckpoint(primaryId.getId(), updatedLocalCheckpoint);
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), equalTo(UNASSIGNED_SEQ_NO));
|
||||||
|
// we are implicitly marking the pending in sync copy as in sync with the current global checkpoint, no advancement should occur
|
||||||
|
tracker.updateLocalCheckpoint(replicaId.getId(), localCheckpoint);
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), equalTo(UNASSIGNED_SEQ_NO));
|
||||||
|
barrier.await();
|
||||||
|
thread.join();
|
||||||
|
// now we expect that the global checkpoint would advance
|
||||||
|
tracker.markAllocationIdAsInSync(replicaId.getId(), updatedLocalCheckpoint);
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), equalTo(updatedLocalCheckpoint));
|
||||||
|
}
|
||||||
|
|
||||||
public void testMissingActiveIdsPreventAdvance() {
|
public void testMissingActiveIdsPreventAdvance() {
|
||||||
final Map<AllocationId, Long> active = randomAllocationsWithLocalCheckpoints(2, 5);
|
final Map<AllocationId, Long> active = randomAllocationsWithLocalCheckpoints(2, 5);
|
||||||
final Map<AllocationId, Long> initializing = randomAllocationsWithLocalCheckpoints(0, 5);
|
final Map<AllocationId, Long> initializing = randomAllocationsWithLocalCheckpoints(0, 5);
|
||||||
|
@ -191,14 +256,16 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(e -> !e.getKey().equals(missingActiveID))
|
.filter(e -> !e.getKey().equals(missingActiveID))
|
||||||
.forEach(e -> tracker.updateLocalCheckpoint(e.getKey().getId(), e.getValue()));
|
.forEach(e -> updateLocalCheckpoint(tracker, e.getKey().getId(), e.getValue()));
|
||||||
|
|
||||||
if (missingActiveID.equals(primaryId) == false) {
|
if (missingActiveID.equals(primaryId) == false) {
|
||||||
assertThat(tracker.getGlobalCheckpoint(), equalTo(UNASSIGNED_SEQ_NO));
|
assertThat(tracker.getGlobalCheckpoint(), equalTo(UNASSIGNED_SEQ_NO));
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), equalTo(UNASSIGNED_SEQ_NO));
|
||||||
}
|
}
|
||||||
// now update all knowledge of all shards
|
// now update all knowledge of all shards
|
||||||
assigned.forEach((aid, localCP) -> tracker.updateLocalCheckpoint(aid.getId(), localCP));
|
assigned.forEach((aid, localCP) -> updateLocalCheckpoint(tracker, aid.getId(), localCP));
|
||||||
assertThat(tracker.getGlobalCheckpoint(), not(equalTo(UNASSIGNED_SEQ_NO)));
|
assertThat(tracker.getGlobalCheckpoint(), not(equalTo(UNASSIGNED_SEQ_NO)));
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), not(equalTo(UNASSIGNED_SEQ_NO)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testMissingInSyncIdsPreventAdvance() {
|
public void testMissingInSyncIdsPreventAdvance() {
|
||||||
|
@ -213,13 +280,15 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
randomSubsetOf(randomIntBetween(1, initializing.size() - 1),
|
randomSubsetOf(randomIntBetween(1, initializing.size() - 1),
|
||||||
initializing.keySet()).forEach(aId -> markAsTrackingAndInSyncQuietly(tracker, aId.getId(), NO_OPS_PERFORMED));
|
initializing.keySet()).forEach(aId -> markAsTrackingAndInSyncQuietly(tracker, aId.getId(), NO_OPS_PERFORMED));
|
||||||
|
|
||||||
active.forEach((aid, localCP) -> tracker.updateLocalCheckpoint(aid.getId(), localCP));
|
active.forEach((aid, localCP) -> updateLocalCheckpoint(tracker, aid.getId(), localCP));
|
||||||
|
|
||||||
assertThat(tracker.getGlobalCheckpoint(), equalTo(NO_OPS_PERFORMED));
|
assertThat(tracker.getGlobalCheckpoint(), equalTo(NO_OPS_PERFORMED));
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), equalTo(NO_OPS_PERFORMED));
|
||||||
|
|
||||||
// update again
|
// update again
|
||||||
initializing.forEach((aid, localCP) -> tracker.updateLocalCheckpoint(aid.getId(), localCP));
|
initializing.forEach((aid, localCP) -> updateLocalCheckpoint(tracker, aid.getId(), localCP));
|
||||||
assertThat(tracker.getGlobalCheckpoint(), not(equalTo(UNASSIGNED_SEQ_NO)));
|
assertThat(tracker.getGlobalCheckpoint(), not(equalTo(UNASSIGNED_SEQ_NO)));
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), not(equalTo(UNASSIGNED_SEQ_NO)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInSyncIdsAreIgnoredIfNotValidatedByMaster() {
|
public void testInSyncIdsAreIgnoredIfNotValidatedByMaster() {
|
||||||
|
@ -236,7 +305,7 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
|
|
||||||
List<Map<AllocationId, Long>> allocations = Arrays.asList(active, initializing, nonApproved);
|
List<Map<AllocationId, Long>> allocations = Arrays.asList(active, initializing, nonApproved);
|
||||||
Collections.shuffle(allocations, random());
|
Collections.shuffle(allocations, random());
|
||||||
allocations.forEach(a -> a.forEach((aid, localCP) -> tracker.updateLocalCheckpoint(aid.getId(), localCP)));
|
allocations.forEach(a -> a.forEach((aid, localCP) -> updateLocalCheckpoint(tracker, aid.getId(), localCP)));
|
||||||
|
|
||||||
assertThat(tracker.getGlobalCheckpoint(), not(equalTo(UNASSIGNED_SEQ_NO)));
|
assertThat(tracker.getGlobalCheckpoint(), not(equalTo(UNASSIGNED_SEQ_NO)));
|
||||||
}
|
}
|
||||||
|
@ -271,7 +340,7 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
initializing.forEach(k -> markAsTrackingAndInSyncQuietly(tracker, k.getId(), NO_OPS_PERFORMED));
|
initializing.forEach(k -> markAsTrackingAndInSyncQuietly(tracker, k.getId(), NO_OPS_PERFORMED));
|
||||||
}
|
}
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
allocations.forEach((aid, localCP) -> tracker.updateLocalCheckpoint(aid.getId(), localCP));
|
allocations.forEach((aid, localCP) -> updateLocalCheckpoint(tracker, aid.getId(), localCP));
|
||||||
}
|
}
|
||||||
|
|
||||||
// now remove shards
|
// now remove shards
|
||||||
|
@ -281,9 +350,9 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
ids(activeToStay.keySet()),
|
ids(activeToStay.keySet()),
|
||||||
routingTable(initializingToStay.keySet(), primaryId),
|
routingTable(initializingToStay.keySet(), primaryId),
|
||||||
emptySet());
|
emptySet());
|
||||||
allocations.forEach((aid, ckp) -> tracker.updateLocalCheckpoint(aid.getId(), ckp + 10L));
|
allocations.forEach((aid, ckp) -> updateLocalCheckpoint(tracker, aid.getId(), ckp + 10L));
|
||||||
} else {
|
} else {
|
||||||
allocations.forEach((aid, ckp) -> tracker.updateLocalCheckpoint(aid.getId(), ckp + 10L));
|
allocations.forEach((aid, ckp) -> updateLocalCheckpoint(tracker, aid.getId(), ckp + 10L));
|
||||||
tracker.updateFromMaster(
|
tracker.updateFromMaster(
|
||||||
initialClusterStateVersion + 2,
|
initialClusterStateVersion + 2,
|
||||||
ids(activeToStay.keySet()),
|
ids(activeToStay.keySet()),
|
||||||
|
@ -331,7 +400,7 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
final List<Integer> elements = IntStream.rangeClosed(0, globalCheckpoint - 1).boxed().collect(Collectors.toList());
|
final List<Integer> elements = IntStream.rangeClosed(0, globalCheckpoint - 1).boxed().collect(Collectors.toList());
|
||||||
Randomness.shuffle(elements);
|
Randomness.shuffle(elements);
|
||||||
for (int i = 0; i < elements.size(); i++) {
|
for (int i = 0; i < elements.size(); i++) {
|
||||||
tracker.updateLocalCheckpoint(trackingAllocationId.getId(), elements.get(i));
|
updateLocalCheckpoint(tracker, trackingAllocationId.getId(), elements.get(i));
|
||||||
assertFalse(complete.get());
|
assertFalse(complete.get());
|
||||||
assertFalse(tracker.getTrackedLocalCheckpointForShard(trackingAllocationId.getId()).inSync);
|
assertFalse(tracker.getTrackedLocalCheckpointForShard(trackingAllocationId.getId()).inSync);
|
||||||
assertBusy(() -> assertTrue(tracker.pendingInSync.contains(trackingAllocationId.getId())));
|
assertBusy(() -> assertTrue(tracker.pendingInSync.contains(trackingAllocationId.getId())));
|
||||||
|
@ -339,7 +408,7 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
|
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
// normal path, shard catches up
|
// normal path, shard catches up
|
||||||
tracker.updateLocalCheckpoint(trackingAllocationId.getId(), randomIntBetween(globalCheckpoint, 64));
|
updateLocalCheckpoint(tracker, trackingAllocationId.getId(), randomIntBetween(globalCheckpoint, 64));
|
||||||
// synchronize with the waiting thread to mark that it is complete
|
// synchronize with the waiting thread to mark that it is complete
|
||||||
barrier.await();
|
barrier.await();
|
||||||
assertTrue(complete.get());
|
assertTrue(complete.get());
|
||||||
|
@ -355,13 +424,16 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
assertFalse(tracker.pendingInSync.contains(trackingAllocationId.getId()));
|
assertFalse(tracker.pendingInSync.contains(trackingAllocationId.getId()));
|
||||||
thread.join();
|
thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AtomicLong updatedGlobalCheckpoint = new AtomicLong(UNASSIGNED_SEQ_NO);
|
||||||
|
|
||||||
private ReplicationTracker newTracker(final AllocationId allocationId) {
|
private ReplicationTracker newTracker(final AllocationId allocationId) {
|
||||||
return new ReplicationTracker(
|
return new ReplicationTracker(
|
||||||
new ShardId("test", "_na_", 0),
|
new ShardId("test", "_na_", 0),
|
||||||
allocationId.getId(),
|
allocationId.getId(),
|
||||||
IndexSettingsModule.newIndexSettings("test", Settings.EMPTY),
|
IndexSettingsModule.newIndexSettings("test", Settings.EMPTY),
|
||||||
UNASSIGNED_SEQ_NO);
|
UNASSIGNED_SEQ_NO,
|
||||||
|
updatedGlobalCheckpoint::set);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWaitForAllocationIdToBeInSyncCanBeInterrupted() throws BrokenBarrierException, InterruptedException {
|
public void testWaitForAllocationIdToBeInSyncCanBeInterrupted() throws BrokenBarrierException, InterruptedException {
|
||||||
|
@ -488,10 +560,10 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
// the tracking allocation IDs should play no role in determining the global checkpoint
|
// the tracking allocation IDs should play no role in determining the global checkpoint
|
||||||
final Map<AllocationId, Integer> activeLocalCheckpoints =
|
final Map<AllocationId, Integer> activeLocalCheckpoints =
|
||||||
newActiveAllocationIds.stream().collect(Collectors.toMap(Function.identity(), a -> randomIntBetween(1, 1024)));
|
newActiveAllocationIds.stream().collect(Collectors.toMap(Function.identity(), a -> randomIntBetween(1, 1024)));
|
||||||
activeLocalCheckpoints.forEach((a, l) -> tracker.updateLocalCheckpoint(a.getId(), l));
|
activeLocalCheckpoints.forEach((a, l) -> updateLocalCheckpoint(tracker, a.getId(), l));
|
||||||
final Map<AllocationId, Integer> initializingLocalCheckpoints =
|
final Map<AllocationId, Integer> initializingLocalCheckpoints =
|
||||||
newInitializingAllocationIds.stream().collect(Collectors.toMap(Function.identity(), a -> randomIntBetween(1, 1024)));
|
newInitializingAllocationIds.stream().collect(Collectors.toMap(Function.identity(), a -> randomIntBetween(1, 1024)));
|
||||||
initializingLocalCheckpoints.forEach((a, l) -> tracker.updateLocalCheckpoint(a.getId(), l));
|
initializingLocalCheckpoints.forEach((a, l) -> updateLocalCheckpoint(tracker, a.getId(), l));
|
||||||
assertTrue(
|
assertTrue(
|
||||||
activeLocalCheckpoints
|
activeLocalCheckpoints
|
||||||
.entrySet()
|
.entrySet()
|
||||||
|
@ -504,6 +576,7 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
.allMatch(e -> tracker.getTrackedLocalCheckpointForShard(e.getKey().getId()).getLocalCheckpoint() == e.getValue()));
|
.allMatch(e -> tracker.getTrackedLocalCheckpointForShard(e.getKey().getId()).getLocalCheckpoint() == e.getValue()));
|
||||||
final long minimumActiveLocalCheckpoint = (long) activeLocalCheckpoints.values().stream().min(Integer::compareTo).get();
|
final long minimumActiveLocalCheckpoint = (long) activeLocalCheckpoints.values().stream().min(Integer::compareTo).get();
|
||||||
assertThat(tracker.getGlobalCheckpoint(), equalTo(minimumActiveLocalCheckpoint));
|
assertThat(tracker.getGlobalCheckpoint(), equalTo(minimumActiveLocalCheckpoint));
|
||||||
|
assertThat(updatedGlobalCheckpoint.get(), equalTo(minimumActiveLocalCheckpoint));
|
||||||
final long minimumInitailizingLocalCheckpoint = (long) initializingLocalCheckpoints.values().stream().min(Integer::compareTo).get();
|
final long minimumInitailizingLocalCheckpoint = (long) initializingLocalCheckpoints.values().stream().min(Integer::compareTo).get();
|
||||||
|
|
||||||
// now we are going to add a new allocation ID and bring it in sync which should move it to the in-sync allocation IDs
|
// now we are going to add a new allocation ID and bring it in sync which should move it to the in-sync allocation IDs
|
||||||
|
@ -635,10 +708,11 @@ public class ReplicationTrackerTests extends ESTestCase {
|
||||||
|
|
||||||
FakeClusterState clusterState = initialState();
|
FakeClusterState clusterState = initialState();
|
||||||
final AllocationId primaryAllocationId = clusterState.routingTable.primaryShard().allocationId();
|
final AllocationId primaryAllocationId = clusterState.routingTable.primaryShard().allocationId();
|
||||||
|
final LongConsumer onUpdate = updatedGlobalCheckpoint -> {};
|
||||||
ReplicationTracker oldPrimary =
|
ReplicationTracker oldPrimary =
|
||||||
new ReplicationTracker(shardId, primaryAllocationId.getId(), indexSettings, UNASSIGNED_SEQ_NO);
|
new ReplicationTracker(shardId, primaryAllocationId.getId(), indexSettings, UNASSIGNED_SEQ_NO, onUpdate);
|
||||||
ReplicationTracker newPrimary =
|
ReplicationTracker newPrimary =
|
||||||
new ReplicationTracker(shardId, primaryAllocationId.getRelocationId(), indexSettings, UNASSIGNED_SEQ_NO);
|
new ReplicationTracker(shardId, primaryAllocationId.getRelocationId(), indexSettings, UNASSIGNED_SEQ_NO, onUpdate);
|
||||||
|
|
||||||
Set<String> allocationIds = new HashSet<>(Arrays.asList(oldPrimary.shardAllocationId, newPrimary.shardAllocationId));
|
Set<String> allocationIds = new HashSet<>(Arrays.asList(oldPrimary.shardAllocationId, newPrimary.shardAllocationId));
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.lucene.store.Directory;
|
||||||
import org.apache.lucene.store.FilterDirectory;
|
import org.apache.lucene.store.FilterDirectory;
|
||||||
import org.apache.lucene.store.IOContext;
|
import org.apache.lucene.store.IOContext;
|
||||||
import org.apache.lucene.util.Constants;
|
import org.apache.lucene.util.Constants;
|
||||||
|
import org.elasticsearch.Assertions;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.action.ActionListener;
|
import org.elasticsearch.action.ActionListener;
|
||||||
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
|
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
|
||||||
|
@ -560,28 +561,20 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
ShardRouting primaryRouting = newShardRouting(replicaRouting.shardId(), replicaRouting.currentNodeId(), null,
|
ShardRouting primaryRouting = newShardRouting(replicaRouting.shardId(), replicaRouting.currentNodeId(), null,
|
||||||
true, ShardRoutingState.STARTED, replicaRouting.allocationId());
|
true, ShardRoutingState.STARTED, replicaRouting.allocationId());
|
||||||
final long newPrimaryTerm = indexShard.getPendingPrimaryTerm() + between(1, 1000);
|
final long newPrimaryTerm = indexShard.getPendingPrimaryTerm() + between(1, 1000);
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
indexShard.updateShardState(primaryRouting, newPrimaryTerm, (shard, listener) -> {
|
indexShard.updateShardState(primaryRouting, newPrimaryTerm, (shard, listener) -> {
|
||||||
assertThat(TestTranslog.getCurrentTerm(getTranslog(indexShard)), equalTo(newPrimaryTerm));
|
assertThat(TestTranslog.getCurrentTerm(getTranslog(indexShard)), equalTo(newPrimaryTerm));
|
||||||
|
latch.countDown();
|
||||||
}, 0L,
|
}, 0L,
|
||||||
Collections.singleton(indexShard.routingEntry().allocationId().getId()),
|
Collections.singleton(indexShard.routingEntry().allocationId().getId()),
|
||||||
new IndexShardRoutingTable.Builder(indexShard.shardId()).addShard(primaryRouting).build(),
|
new IndexShardRoutingTable.Builder(indexShard.shardId()).addShard(primaryRouting).build(),
|
||||||
Collections.emptySet());
|
Collections.emptySet());
|
||||||
|
latch.await();
|
||||||
} else {
|
} else {
|
||||||
indexShard = newStartedShard(true);
|
indexShard = newStartedShard(true);
|
||||||
}
|
}
|
||||||
final long primaryTerm = indexShard.getPendingPrimaryTerm();
|
final long primaryTerm = indexShard.getPendingPrimaryTerm();
|
||||||
assertEquals(0, indexShard.getActiveOperationsCount());
|
assertEquals(0, indexShard.getActiveOperationsCount());
|
||||||
if (indexShard.routingEntry().isRelocationTarget() == false) {
|
|
||||||
try {
|
|
||||||
final PlainActionFuture<Releasable> permitAcquiredFuture = new PlainActionFuture<>();
|
|
||||||
indexShard.acquireReplicaOperationPermit(primaryTerm, indexShard.getGlobalCheckpoint(), permitAcquiredFuture,
|
|
||||||
ThreadPool.Names.WRITE, "");
|
|
||||||
permitAcquiredFuture.actionGet();
|
|
||||||
fail("shard shouldn't accept operations as replica");
|
|
||||||
} catch (IllegalStateException ignored) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Releasable operation1 = acquirePrimaryOperationPermitBlockingly(indexShard);
|
Releasable operation1 = acquirePrimaryOperationPermitBlockingly(indexShard);
|
||||||
assertEquals(1, indexShard.getActiveOperationsCount());
|
assertEquals(1, indexShard.getActiveOperationsCount());
|
||||||
Releasable operation2 = acquirePrimaryOperationPermitBlockingly(indexShard);
|
Releasable operation2 = acquirePrimaryOperationPermitBlockingly(indexShard);
|
||||||
|
@ -590,6 +583,22 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
Releasables.close(operation1, operation2);
|
Releasables.close(operation1, operation2);
|
||||||
assertEquals(0, indexShard.getActiveOperationsCount());
|
assertEquals(0, indexShard.getActiveOperationsCount());
|
||||||
|
|
||||||
|
if (Assertions.ENABLED && indexShard.routingEntry().isRelocationTarget() == false) {
|
||||||
|
assertThat(expectThrows(AssertionError.class, () -> indexShard.acquireReplicaOperationPermit(primaryTerm,
|
||||||
|
indexShard.getGlobalCheckpoint(), new ActionListener<Releasable>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Releasable releasable) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Exception e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ThreadPool.Names.WRITE, "")).getMessage(), containsString("in primary mode cannot be a replication target"));
|
||||||
|
}
|
||||||
|
|
||||||
closeShards(indexShard);
|
closeShards(indexShard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -647,11 +656,11 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
logger.info("shard routing to {}", shardRouting);
|
logger.info("shard routing to {}", shardRouting);
|
||||||
|
|
||||||
assertEquals(0, indexShard.getActiveOperationsCount());
|
assertEquals(0, indexShard.getActiveOperationsCount());
|
||||||
if (shardRouting.primary() == false) {
|
if (shardRouting.primary() == false && Assertions.ENABLED) {
|
||||||
final IllegalStateException e =
|
final AssertionError e =
|
||||||
expectThrows(IllegalStateException.class,
|
expectThrows(AssertionError.class,
|
||||||
() -> indexShard.acquirePrimaryOperationPermit(null, ThreadPool.Names.WRITE, ""));
|
() -> indexShard.acquirePrimaryOperationPermit(null, ThreadPool.Names.WRITE, ""));
|
||||||
assertThat(e, hasToString(containsString("shard " + shardRouting + " is not a primary")));
|
assertThat(e, hasToString(containsString("acquirePrimaryOperationPermit should only be called on primary shard")));
|
||||||
}
|
}
|
||||||
|
|
||||||
final long primaryTerm = indexShard.getPendingPrimaryTerm();
|
final long primaryTerm = indexShard.getPendingPrimaryTerm();
|
||||||
|
|
|
@ -202,49 +202,6 @@ public class RestActionsTests extends ESTestCase {
|
||||||
new ShardId(new Index(index, IndexMetaData.INDEX_UUID_NA_VALUE), shardId), clusterAlias, OriginalIndices.NONE);
|
new ShardId(new Index(index, IndexMetaData.INDEX_UUID_NA_VALUE), shardId), clusterAlias, OriginalIndices.NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBuildBroadcastShardsHeaderNullCause() throws Exception {
|
|
||||||
ShardOperationFailedException[] failures = new ShardOperationFailedException[] {
|
|
||||||
new ShardSearchFailure("error", createSearchShardTarget("node0", 0, "index", null)),
|
|
||||||
new ShardSearchFailure("error", createSearchShardTarget("node1", 1, "index", null)),
|
|
||||||
new ShardSearchFailure("error", createSearchShardTarget("node2", 2, "index", "cluster1")),
|
|
||||||
new ShardSearchFailure("error", createSearchShardTarget("node1", 1, "index", "cluster1")),
|
|
||||||
new ShardSearchFailure("a different error", createSearchShardTarget("node3", 3, "index", "cluster1"))
|
|
||||||
};
|
|
||||||
|
|
||||||
XContentBuilder builder = JsonXContent.contentBuilder();
|
|
||||||
builder.prettyPrint();
|
|
||||||
builder.startObject();
|
|
||||||
RestActions.buildBroadcastShardsHeader(builder, ToXContent.EMPTY_PARAMS, 12, 3, 0, 9, failures);
|
|
||||||
builder.endObject();
|
|
||||||
//TODO the reason is not printed out, as a follow-up we should probably either print it out when the cause is null,
|
|
||||||
//or even better enforce that the cause can't be null
|
|
||||||
assertThat(Strings.toString(builder), equalTo("{\n" +
|
|
||||||
" \"_shards\" : {\n" +
|
|
||||||
" \"total\" : 12,\n" +
|
|
||||||
" \"successful\" : 3,\n" +
|
|
||||||
" \"skipped\" : 0,\n" +
|
|
||||||
" \"failed\" : 9,\n" +
|
|
||||||
" \"failures\" : [\n" +
|
|
||||||
" {\n" +
|
|
||||||
" \"shard\" : 0,\n" +
|
|
||||||
" \"index\" : \"index\",\n" +
|
|
||||||
" \"node\" : \"node0\"\n" +
|
|
||||||
" },\n" +
|
|
||||||
" {\n" +
|
|
||||||
" \"shard\" : 2,\n" +
|
|
||||||
" \"index\" : \"cluster1:index\",\n" +
|
|
||||||
" \"node\" : \"node2\"\n" +
|
|
||||||
" },\n" +
|
|
||||||
" {\n" +
|
|
||||||
" \"shard\" : 3,\n" +
|
|
||||||
" \"index\" : \"cluster1:index\",\n" +
|
|
||||||
" \"node\" : \"node3\"\n" +
|
|
||||||
" }\n" +
|
|
||||||
" ]\n" +
|
|
||||||
" }\n" +
|
|
||||||
"}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected NamedXContentRegistry xContentRegistry() {
|
protected NamedXContentRegistry xContentRegistry() {
|
||||||
return xContentRegistry;
|
return xContentRegistry;
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.search;
|
package org.elasticsearch.search;
|
||||||
|
|
||||||
|
import org.apache.lucene.search.IndexSearcher;
|
||||||
|
import org.apache.lucene.util.CharsRefBuilder;
|
||||||
import org.elasticsearch.common.inject.ModuleTestCase;
|
import org.elasticsearch.common.inject.ModuleTestCase;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
@ -64,8 +66,11 @@ import org.elasticsearch.search.internal.SearchContext;
|
||||||
import org.elasticsearch.search.rescore.QueryRescorerBuilder;
|
import org.elasticsearch.search.rescore.QueryRescorerBuilder;
|
||||||
import org.elasticsearch.search.rescore.RescoreContext;
|
import org.elasticsearch.search.rescore.RescoreContext;
|
||||||
import org.elasticsearch.search.rescore.RescorerBuilder;
|
import org.elasticsearch.search.rescore.RescorerBuilder;
|
||||||
import org.elasticsearch.search.suggest.CustomSuggesterSearchIT.CustomSuggestionBuilder;
|
import org.elasticsearch.search.suggest.Suggest.Suggestion;
|
||||||
|
import org.elasticsearch.search.suggest.Suggester;
|
||||||
import org.elasticsearch.search.suggest.SuggestionBuilder;
|
import org.elasticsearch.search.suggest.SuggestionBuilder;
|
||||||
|
import org.elasticsearch.search.suggest.SuggestionSearchContext;
|
||||||
|
import org.elasticsearch.search.suggest.term.TermSuggestion;
|
||||||
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
|
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -98,7 +103,8 @@ public class SearchModuleTests extends ModuleTestCase {
|
||||||
SearchPlugin registersDupeSuggester = new SearchPlugin() {
|
SearchPlugin registersDupeSuggester = new SearchPlugin() {
|
||||||
@Override
|
@Override
|
||||||
public List<SearchPlugin.SuggesterSpec<?>> getSuggesters() {
|
public List<SearchPlugin.SuggesterSpec<?>> getSuggesters() {
|
||||||
return singletonList(new SuggesterSpec<>("term", TermSuggestionBuilder::new, TermSuggestionBuilder::fromXContent));
|
return singletonList(new SuggesterSpec<>(TermSuggestionBuilder.SUGGESTION_NAME,
|
||||||
|
TermSuggestionBuilder::new, TermSuggestionBuilder::fromXContent, TermSuggestion::new));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
expectThrows(IllegalArgumentException.class, registryForPlugin(registersDupeSuggester));
|
expectThrows(IllegalArgumentException.class, registryForPlugin(registersDupeSuggester));
|
||||||
|
@ -183,9 +189,15 @@ public class SearchModuleTests extends ModuleTestCase {
|
||||||
SearchModule module = new SearchModule(Settings.EMPTY, false, singletonList(new SearchPlugin() {
|
SearchModule module = new SearchModule(Settings.EMPTY, false, singletonList(new SearchPlugin() {
|
||||||
@Override
|
@Override
|
||||||
public List<SuggesterSpec<?>> getSuggesters() {
|
public List<SuggesterSpec<?>> getSuggesters() {
|
||||||
return singletonList(new SuggesterSpec<>("custom", CustomSuggestionBuilder::new, CustomSuggestionBuilder::fromXContent));
|
return singletonList(
|
||||||
|
new SuggesterSpec<>(
|
||||||
|
TestSuggestionBuilder.SUGGESTION_NAME,
|
||||||
|
TestSuggestionBuilder::new,
|
||||||
|
TestSuggestionBuilder::fromXContent,
|
||||||
|
TestSuggestion::new));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
assertEquals(1, module.getNamedXContents().stream()
|
assertEquals(1, module.getNamedXContents().stream()
|
||||||
.filter(e -> e.categoryClass.equals(SuggestionBuilder.class) &&
|
.filter(e -> e.categoryClass.equals(SuggestionBuilder.class) &&
|
||||||
e.name.match("term", LoggingDeprecationHandler.INSTANCE)).count());
|
e.name.match("term", LoggingDeprecationHandler.INSTANCE)).count());
|
||||||
|
@ -197,7 +209,7 @@ public class SearchModuleTests extends ModuleTestCase {
|
||||||
e.name.match("completion", LoggingDeprecationHandler.INSTANCE)).count());
|
e.name.match("completion", LoggingDeprecationHandler.INSTANCE)).count());
|
||||||
assertEquals(1, module.getNamedXContents().stream()
|
assertEquals(1, module.getNamedXContents().stream()
|
||||||
.filter(e -> e.categoryClass.equals(SuggestionBuilder.class) &&
|
.filter(e -> e.categoryClass.equals(SuggestionBuilder.class) &&
|
||||||
e.name.match("custom", LoggingDeprecationHandler.INSTANCE)).count());
|
e.name.match("test", LoggingDeprecationHandler.INSTANCE)).count());
|
||||||
|
|
||||||
assertEquals(1, module.getNamedWriteables().stream()
|
assertEquals(1, module.getNamedWriteables().stream()
|
||||||
.filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.equals("term")).count());
|
.filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.equals("term")).count());
|
||||||
|
@ -206,7 +218,16 @@ public class SearchModuleTests extends ModuleTestCase {
|
||||||
assertEquals(1, module.getNamedWriteables().stream()
|
assertEquals(1, module.getNamedWriteables().stream()
|
||||||
.filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.equals("completion")).count());
|
.filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.equals("completion")).count());
|
||||||
assertEquals(1, module.getNamedWriteables().stream()
|
assertEquals(1, module.getNamedWriteables().stream()
|
||||||
.filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.equals("custom")).count());
|
.filter(e -> e.categoryClass.equals(SuggestionBuilder.class) && e.name.equals("test")).count());
|
||||||
|
|
||||||
|
assertEquals(1, module.getNamedWriteables().stream()
|
||||||
|
.filter(e -> e.categoryClass.equals(Suggestion.class) && e.name.equals("term")).count());
|
||||||
|
assertEquals(1, module.getNamedWriteables().stream()
|
||||||
|
.filter(e -> e.categoryClass.equals(Suggestion.class) && e.name.equals("phrase")).count());
|
||||||
|
assertEquals(1, module.getNamedWriteables().stream()
|
||||||
|
.filter(e -> e.categoryClass.equals(Suggestion.class) && e.name.equals("completion")).count());
|
||||||
|
assertEquals(1, module.getNamedWriteables().stream()
|
||||||
|
.filter(e -> e.categoryClass.equals(Suggestion.class) && e.name.equals("test")).count());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRegisterHighlighter() {
|
public void testRegisterHighlighter() {
|
||||||
|
@ -498,4 +519,77 @@ public class SearchModuleTests extends ModuleTestCase {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class TestSuggester extends Suggester<SuggestionSearchContext.SuggestionContext> {
|
||||||
|
@Override
|
||||||
|
protected Suggestion<? extends Suggestion.Entry<? extends Suggestion.Entry.Option>> innerExecute(
|
||||||
|
String name,
|
||||||
|
SuggestionSearchContext.SuggestionContext suggestion,
|
||||||
|
IndexSearcher searcher,
|
||||||
|
CharsRefBuilder spare) throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestSuggestionBuilder extends SuggestionBuilder<TestSuggestionBuilder> {
|
||||||
|
|
||||||
|
public static final String SUGGESTION_NAME = "test";
|
||||||
|
|
||||||
|
TestSuggestionBuilder(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doWriteTo(StreamOutput out) throws IOException {}
|
||||||
|
|
||||||
|
public static TestSuggestionBuilder fromXContent(XContentParser parser) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SuggestionSearchContext.SuggestionContext build(QueryShardContext context) throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doEquals(TestSuggestionBuilder other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int doHashCode() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWriteableName() {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestSuggestion extends Suggestion {
|
||||||
|
TestSuggestion(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Entry newEntry() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Entry newEntry(StreamInput in) throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWriteableName() {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ public class InternalAutoDateHistogramTests extends InternalMultiBucketAggregati
|
||||||
List<PipelineAggregator> pipelineAggregators,
|
List<PipelineAggregator> pipelineAggregators,
|
||||||
Map<String, Object> metaData,
|
Map<String, Object> metaData,
|
||||||
InternalAggregations aggregations) {
|
InternalAggregations aggregations) {
|
||||||
|
|
||||||
roundingInfos = AutoDateHistogramAggregationBuilder.buildRoundings(null);
|
roundingInfos = AutoDateHistogramAggregationBuilder.buildRoundings(null);
|
||||||
int nbBuckets = randomNumberOfBuckets();
|
int nbBuckets = randomNumberOfBuckets();
|
||||||
int targetBuckets = randomIntBetween(1, nbBuckets * 2 + 1);
|
int targetBuckets = randomIntBetween(1, nbBuckets * 2 + 1);
|
||||||
|
@ -137,6 +137,12 @@ public class InternalAutoDateHistogramTests extends InternalMultiBucketAggregati
|
||||||
assertEquals(expectedCounts, actualCounts);
|
assertEquals(expectedCounts, actualCounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/32215")
|
||||||
|
public void testReduceRandom() {
|
||||||
|
super.testReduceRandom();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Writeable.Reader<InternalAutoDateHistogram> instanceReader() {
|
protected Writeable.Reader<InternalAutoDateHistogram> instanceReader() {
|
||||||
return InternalAutoDateHistogram::new;
|
return InternalAutoDateHistogram::new;
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch 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.search.suggest;
|
|
||||||
|
|
||||||
import org.apache.lucene.search.IndexSearcher;
|
|
||||||
import org.apache.lucene.util.CharsRefBuilder;
|
|
||||||
import org.elasticsearch.common.text.Text;
|
|
||||||
import org.elasticsearch.index.query.QueryShardContext;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class CustomSuggester extends Suggester<CustomSuggester.CustomSuggestionsContext> {
|
|
||||||
|
|
||||||
public static final CustomSuggester INSTANCE = new CustomSuggester();
|
|
||||||
|
|
||||||
// This is a pretty dumb implementation which returns the original text + fieldName + custom config option + 12 or 123
|
|
||||||
@Override
|
|
||||||
public Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> innerExecute(String name, CustomSuggestionsContext suggestion, IndexSearcher searcher, CharsRefBuilder spare) throws IOException {
|
|
||||||
// Get the suggestion context
|
|
||||||
String text = suggestion.getText().utf8ToString();
|
|
||||||
|
|
||||||
// create two suggestions with 12 and 123 appended
|
|
||||||
Suggest.Suggestion<Suggest.Suggestion.Entry<Suggest.Suggestion.Entry.Option>> response = new Suggest.Suggestion<>(name, suggestion.getSize());
|
|
||||||
|
|
||||||
String firstSuggestion = String.format(Locale.ROOT, "%s-%s-%s-%s", text, suggestion.getField(), suggestion.options.get("suffix"), "12");
|
|
||||||
Suggest.Suggestion.Entry<Suggest.Suggestion.Entry.Option> resultEntry12 = new Suggest.Suggestion.Entry<>(new Text(firstSuggestion), 0, text.length() + 2);
|
|
||||||
response.addTerm(resultEntry12);
|
|
||||||
|
|
||||||
String secondSuggestion = String.format(Locale.ROOT, "%s-%s-%s-%s", text, suggestion.getField(), suggestion.options.get("suffix"), "123");
|
|
||||||
Suggest.Suggestion.Entry<Suggest.Suggestion.Entry.Option> resultEntry123 = new Suggest.Suggestion.Entry<>(new Text(secondSuggestion), 0, text.length() + 3);
|
|
||||||
response.addTerm(resultEntry123);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CustomSuggestionsContext extends SuggestionSearchContext.SuggestionContext {
|
|
||||||
|
|
||||||
public Map<String, Object> options;
|
|
||||||
|
|
||||||
public CustomSuggestionsContext(QueryShardContext context, Map<String, Object> options) {
|
|
||||||
super(new CustomSuggester(), context);
|
|
||||||
this.options = options;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,212 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch 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.search.suggest;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.search.SearchRequestBuilder;
|
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
|
||||||
import org.elasticsearch.common.ParseField;
|
|
||||||
import org.elasticsearch.common.ParsingException;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.lucene.BytesRefs;
|
|
||||||
import org.elasticsearch.common.util.CollectionUtils;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
|
||||||
import org.elasticsearch.index.query.QueryShardContext;
|
|
||||||
import org.elasticsearch.plugins.Plugin;
|
|
||||||
import org.elasticsearch.plugins.SearchPlugin;
|
|
||||||
import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContext;
|
|
||||||
import org.elasticsearch.test.ESIntegTestCase;
|
|
||||||
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
|
|
||||||
import org.elasticsearch.test.ESIntegTestCase.Scope;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
|
||||||
import static org.hamcrest.Matchers.is;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration test for registering a custom suggester.
|
|
||||||
*/
|
|
||||||
@ClusterScope(scope= Scope.SUITE, numDataNodes =1)
|
|
||||||
public class CustomSuggesterSearchIT extends ESIntegTestCase {
|
|
||||||
@Override
|
|
||||||
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
|
||||||
return Arrays.asList(CustomSuggesterPlugin.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
|
||||||
return Arrays.asList(CustomSuggesterPlugin.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CustomSuggesterPlugin extends Plugin implements SearchPlugin {
|
|
||||||
@Override
|
|
||||||
public List<SuggesterSpec<?>> getSuggesters() {
|
|
||||||
return singletonList(new SuggesterSpec<CustomSuggestionBuilder>("custom", CustomSuggestionBuilder::new,
|
|
||||||
CustomSuggestionBuilder::fromXContent));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testThatCustomSuggestersCanBeRegisteredAndWork() throws Exception {
|
|
||||||
createIndex("test");
|
|
||||||
client().prepareIndex("test", "test", "1").setSource(jsonBuilder()
|
|
||||||
.startObject()
|
|
||||||
.field("name", "arbitrary content")
|
|
||||||
.endObject())
|
|
||||||
.setRefreshPolicy(IMMEDIATE).get();
|
|
||||||
|
|
||||||
String randomText = randomAlphaOfLength(10);
|
|
||||||
String randomField = randomAlphaOfLength(10);
|
|
||||||
String randomSuffix = randomAlphaOfLength(10);
|
|
||||||
SuggestBuilder suggestBuilder = new SuggestBuilder();
|
|
||||||
suggestBuilder.addSuggestion("someName", new CustomSuggestionBuilder(randomField, randomSuffix).text(randomText));
|
|
||||||
SearchRequestBuilder searchRequestBuilder = client().prepareSearch("test").setTypes("test").setFrom(0).setSize(1)
|
|
||||||
.suggest(suggestBuilder);
|
|
||||||
|
|
||||||
SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();
|
|
||||||
|
|
||||||
// TODO: infer type once JI-9019884 is fixed
|
|
||||||
// TODO: see also JDK-8039214
|
|
||||||
List<Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestions =
|
|
||||||
CollectionUtils.<Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>>iterableAsArrayList(
|
|
||||||
searchResponse.getSuggest().getSuggestion("someName"));
|
|
||||||
assertThat(suggestions, hasSize(2));
|
|
||||||
assertThat(suggestions.get(0).getText().string(),
|
|
||||||
is(String.format(Locale.ROOT, "%s-%s-%s-12", randomText, randomField, randomSuffix)));
|
|
||||||
assertThat(suggestions.get(1).getText().string(),
|
|
||||||
is(String.format(Locale.ROOT, "%s-%s-%s-123", randomText, randomField, randomSuffix)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CustomSuggestionBuilder extends SuggestionBuilder<CustomSuggestionBuilder> {
|
|
||||||
protected static final ParseField RANDOM_SUFFIX_FIELD = new ParseField("suffix");
|
|
||||||
|
|
||||||
private String randomSuffix;
|
|
||||||
|
|
||||||
public CustomSuggestionBuilder(String randomField, String randomSuffix) {
|
|
||||||
super(randomField);
|
|
||||||
this.randomSuffix = randomSuffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read from a stream.
|
|
||||||
*/
|
|
||||||
public CustomSuggestionBuilder(StreamInput in) throws IOException {
|
|
||||||
super(in);
|
|
||||||
this.randomSuffix = in.readString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doWriteTo(StreamOutput out) throws IOException {
|
|
||||||
out.writeString(randomSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
|
|
||||||
builder.field(RANDOM_SUFFIX_FIELD.getPreferredName(), randomSuffix);
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getWriteableName() {
|
|
||||||
return "custom";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean doEquals(CustomSuggestionBuilder other) {
|
|
||||||
return Objects.equals(randomSuffix, other.randomSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int doHashCode() {
|
|
||||||
return Objects.hash(randomSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CustomSuggestionBuilder fromXContent(XContentParser parser) throws IOException {
|
|
||||||
XContentParser.Token token;
|
|
||||||
String currentFieldName = null;
|
|
||||||
String fieldname = null;
|
|
||||||
String suffix = null;
|
|
||||||
String analyzer = null;
|
|
||||||
int sizeField = -1;
|
|
||||||
int shardSize = -1;
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
|
||||||
if (token == XContentParser.Token.FIELD_NAME) {
|
|
||||||
currentFieldName = parser.currentName();
|
|
||||||
} else if (token.isValue()) {
|
|
||||||
if (SuggestionBuilder.ANALYZER_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
||||||
analyzer = parser.text();
|
|
||||||
} else if (SuggestionBuilder.FIELDNAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
||||||
fieldname = parser.text();
|
|
||||||
} else if (SuggestionBuilder.SIZE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
||||||
sizeField = parser.intValue();
|
|
||||||
} else if (SuggestionBuilder.SHARDSIZE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
||||||
shardSize = parser.intValue();
|
|
||||||
} else if (RANDOM_SUFFIX_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
||||||
suffix = parser.text();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new ParsingException(parser.getTokenLocation(),
|
|
||||||
"suggester[custom] doesn't support field [" + currentFieldName + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now we should have field name, check and copy fields over to the suggestion builder we return
|
|
||||||
if (fieldname == null) {
|
|
||||||
throw new ParsingException(parser.getTokenLocation(), "the required field option is missing");
|
|
||||||
}
|
|
||||||
CustomSuggestionBuilder builder = new CustomSuggestionBuilder(fieldname, suffix);
|
|
||||||
if (analyzer != null) {
|
|
||||||
builder.analyzer(analyzer);
|
|
||||||
}
|
|
||||||
if (sizeField != -1) {
|
|
||||||
builder.size(sizeField);
|
|
||||||
}
|
|
||||||
if (shardSize != -1) {
|
|
||||||
builder.shardSize(shardSize);
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SuggestionContext build(QueryShardContext context) throws IOException {
|
|
||||||
Map<String, Object> options = new HashMap<>();
|
|
||||||
options.put(FIELDNAME_FIELD.getPreferredName(), field());
|
|
||||||
options.put(RANDOM_SUFFIX_FIELD.getPreferredName(), randomSuffix);
|
|
||||||
CustomSuggester.CustomSuggestionsContext customSuggestionsContext =
|
|
||||||
new CustomSuggester.CustomSuggestionsContext(context, options);
|
|
||||||
customSuggestionsContext.setField(field());
|
|
||||||
assert text != null;
|
|
||||||
customSuggestionsContext.setText(BytesRefs.toBytesRef(text));
|
|
||||||
return customSuggestionsContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -19,9 +19,14 @@
|
||||||
|
|
||||||
package org.elasticsearch.search.suggest;
|
package org.elasticsearch.search.suggest;
|
||||||
|
|
||||||
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.common.ParseField;
|
import org.elasticsearch.common.ParseField;
|
||||||
import org.elasticsearch.common.ParsingException;
|
import org.elasticsearch.common.ParsingException;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||||
|
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.text.Text;
|
import org.elasticsearch.common.text.Text;
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
|
@ -30,6 +35,7 @@ import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
import org.elasticsearch.rest.action.search.RestSearchAction;
|
import org.elasticsearch.rest.action.search.RestSearchAction;
|
||||||
|
import org.elasticsearch.search.SearchModule;
|
||||||
import org.elasticsearch.search.suggest.Suggest.Suggestion;
|
import org.elasticsearch.search.suggest.Suggest.Suggestion;
|
||||||
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry;
|
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry;
|
||||||
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
|
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
|
||||||
|
@ -37,6 +43,7 @@ import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
|
||||||
import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
|
import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
|
||||||
import org.elasticsearch.search.suggest.term.TermSuggestion;
|
import org.elasticsearch.search.suggest.term.TermSuggestion;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.test.VersionUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -44,6 +51,7 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
|
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
|
||||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureFieldName;
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureFieldName;
|
||||||
|
@ -114,10 +122,11 @@ public class SuggestTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testToXContent() throws IOException {
|
public void testToXContent() throws IOException {
|
||||||
Option option = new Option(new Text("someText"), new Text("somethingHighlighted"), 1.3f, true);
|
PhraseSuggestion.Entry.Option option = new PhraseSuggestion.Entry.Option(new Text("someText"), new Text("somethingHighlighted"),
|
||||||
Entry<Option> entry = new Entry<>(new Text("entryText"), 42, 313);
|
1.3f, true);
|
||||||
|
PhraseSuggestion.Entry entry = new PhraseSuggestion.Entry(new Text("entryText"), 42, 313);
|
||||||
entry.addOption(option);
|
entry.addOption(option);
|
||||||
Suggestion<Entry<Option>> suggestion = new Suggestion<>("suggestionName", 5);
|
PhraseSuggestion suggestion = new PhraseSuggestion("suggestionName", 5);
|
||||||
suggestion.addTerm(entry);
|
suggestion.addTerm(entry);
|
||||||
Suggest suggest = new Suggest(Collections.singletonList(suggestion));
|
Suggest suggest = new Suggest(Collections.singletonList(suggestion));
|
||||||
BytesReference xContent = toXContent(suggest, XContentType.JSON, randomBoolean());
|
BytesReference xContent = toXContent(suggest, XContentType.JSON, randomBoolean());
|
||||||
|
@ -196,9 +205,9 @@ public class SuggestTests extends ESTestCase {
|
||||||
String secondWord = randomAlphaOfLength(10);
|
String secondWord = randomAlphaOfLength(10);
|
||||||
Text suggestionText = new Text(suggestedWord + " " + secondWord);
|
Text suggestionText = new Text(suggestedWord + " " + secondWord);
|
||||||
Text highlighted = new Text("<em>" + suggestedWord + "</em> " + secondWord);
|
Text highlighted = new Text("<em>" + suggestedWord + "</em> " + secondWord);
|
||||||
PhraseSuggestion.Entry.Option option1 = new Option(suggestionText, highlighted, 0.7f, false);
|
PhraseSuggestion.Entry.Option option1 = new PhraseSuggestion.Entry.Option(suggestionText, highlighted, 0.7f, false);
|
||||||
PhraseSuggestion.Entry.Option option2 = new Option(suggestionText, highlighted, 0.8f, true);
|
PhraseSuggestion.Entry.Option option2 = new PhraseSuggestion.Entry.Option(suggestionText, highlighted, 0.8f, true);
|
||||||
PhraseSuggestion.Entry.Option option3 = new Option(suggestionText, highlighted, 0.6f);
|
PhraseSuggestion.Entry.Option option3 = new PhraseSuggestion.Entry.Option(suggestionText, highlighted, 0.6f);
|
||||||
assertEquals(suggestionText, option1.getText());
|
assertEquals(suggestionText, option1.getText());
|
||||||
assertEquals(highlighted, option1.getHighlighted());
|
assertEquals(highlighted, option1.getHighlighted());
|
||||||
assertFalse(option1.collateMatch());
|
assertFalse(option1.collateMatch());
|
||||||
|
@ -214,4 +223,39 @@ public class SuggestTests extends ESTestCase {
|
||||||
assertTrue(option1.getScore() > 0.7f);
|
assertTrue(option1.getScore() > 0.7f);
|
||||||
assertTrue(option1.collateMatch());
|
assertTrue(option1.collateMatch());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testSerialization() throws IOException {
|
||||||
|
final Version bwcVersion = VersionUtils.randomVersionBetween(random(),
|
||||||
|
Version.CURRENT.minimumCompatibilityVersion(), Version.CURRENT);
|
||||||
|
|
||||||
|
final Suggest suggest = createTestItem();
|
||||||
|
final Suggest bwcSuggest;
|
||||||
|
|
||||||
|
NamedWriteableRegistry registry = new NamedWriteableRegistry
|
||||||
|
(new SearchModule(Settings.EMPTY, false, emptyList()).getNamedWriteables());
|
||||||
|
|
||||||
|
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||||
|
out.setVersion(bwcVersion);
|
||||||
|
suggest.writeTo(out);
|
||||||
|
try (NamedWriteableAwareStreamInput in = new NamedWriteableAwareStreamInput(out.bytes().streamInput(), registry)) {
|
||||||
|
in.setVersion(bwcVersion);
|
||||||
|
bwcSuggest = new Suggest(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(suggest, bwcSuggest);
|
||||||
|
|
||||||
|
final Suggest backAgain;
|
||||||
|
|
||||||
|
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||||
|
out.setVersion(Version.CURRENT);
|
||||||
|
bwcSuggest.writeTo(out);
|
||||||
|
try (NamedWriteableAwareStreamInput in = new NamedWriteableAwareStreamInput(out.bytes().streamInput(), registry)) {
|
||||||
|
in.setVersion(Version.CURRENT);
|
||||||
|
backAgain = new Suggest(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(suggest, backAgain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,10 +129,12 @@ public class SuggestionEntryTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testToXContent() throws IOException {
|
public void testToXContent() throws IOException {
|
||||||
Option option = new Option(new Text("someText"), new Text("somethingHighlighted"), 1.3f, true);
|
PhraseSuggestion.Entry.Option phraseOption = new PhraseSuggestion.Entry.Option(new Text("someText"),
|
||||||
Entry<Option> entry = new Entry<>(new Text("entryText"), 42, 313);
|
new Text("somethingHighlighted"),
|
||||||
entry.addOption(option);
|
1.3f, true);
|
||||||
BytesReference xContent = toXContent(entry, XContentType.JSON, randomBoolean());
|
PhraseSuggestion.Entry phraseEntry = new PhraseSuggestion.Entry(new Text("entryText"), 42, 313);
|
||||||
|
phraseEntry.addOption(phraseOption);
|
||||||
|
BytesReference xContent = toXContent(phraseEntry, XContentType.JSON, randomBoolean());
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"{\"text\":\"entryText\","
|
"{\"text\":\"entryText\","
|
||||||
+ "\"offset\":42,"
|
+ "\"offset\":42,"
|
||||||
|
@ -144,11 +146,10 @@ public class SuggestionEntryTests extends ESTestCase {
|
||||||
+ "\"collate_match\":true}"
|
+ "\"collate_match\":true}"
|
||||||
+ "]}", xContent.utf8ToString());
|
+ "]}", xContent.utf8ToString());
|
||||||
|
|
||||||
org.elasticsearch.search.suggest.term.TermSuggestion.Entry.Option termOption =
|
TermSuggestion.Entry.Option termOption = new TermSuggestion.Entry.Option(new Text("termSuggestOption"), 42, 3.13f);
|
||||||
new org.elasticsearch.search.suggest.term.TermSuggestion.Entry.Option(new Text("termSuggestOption"), 42, 3.13f);
|
TermSuggestion.Entry termEntry = new TermSuggestion.Entry(new Text("entryText"), 42, 313);
|
||||||
entry = new Entry<>(new Text("entryText"), 42, 313);
|
termEntry.addOption(termOption);
|
||||||
entry.addOption(termOption);
|
xContent = toXContent(termEntry, XContentType.JSON, randomBoolean());
|
||||||
xContent = toXContent(entry, XContentType.JSON, randomBoolean());
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"{\"text\":\"entryText\","
|
"{\"text\":\"entryText\","
|
||||||
+ "\"offset\":42,"
|
+ "\"offset\":42,"
|
||||||
|
@ -159,12 +160,11 @@ public class SuggestionEntryTests extends ESTestCase {
|
||||||
+ "\"freq\":42}"
|
+ "\"freq\":42}"
|
||||||
+ "]}", xContent.utf8ToString());
|
+ "]}", xContent.utf8ToString());
|
||||||
|
|
||||||
org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option completionOption =
|
CompletionSuggestion.Entry.Option completionOption = new CompletionSuggestion.Entry.Option(-1, new Text("completionOption"),
|
||||||
new org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option(-1, new Text("completionOption"),
|
|
||||||
3.13f, Collections.singletonMap("key", Collections.singleton("value")));
|
3.13f, Collections.singletonMap("key", Collections.singleton("value")));
|
||||||
entry = new Entry<>(new Text("entryText"), 42, 313);
|
CompletionSuggestion.Entry completionEntry = new CompletionSuggestion.Entry(new Text("entryText"), 42, 313);
|
||||||
entry.addOption(completionOption);
|
completionEntry.addOption(completionOption);
|
||||||
xContent = toXContent(entry, XContentType.JSON, randomBoolean());
|
xContent = toXContent(completionEntry, XContentType.JSON, randomBoolean());
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"{\"text\":\"entryText\","
|
"{\"text\":\"entryText\","
|
||||||
+ "\"offset\":42,"
|
+ "\"offset\":42,"
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
|
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
|
||||||
|
import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -41,7 +42,7 @@ public class SuggestionOptionTests extends ESTestCase {
|
||||||
float score = randomFloat();
|
float score = randomFloat();
|
||||||
Text highlighted = randomFrom((Text) null, new Text(randomAlphaOfLengthBetween(5, 15)));
|
Text highlighted = randomFrom((Text) null, new Text(randomAlphaOfLengthBetween(5, 15)));
|
||||||
Boolean collateMatch = randomFrom((Boolean) null, randomBoolean());
|
Boolean collateMatch = randomFrom((Boolean) null, randomBoolean());
|
||||||
return new Option(text, highlighted, score, collateMatch);
|
return new PhraseSuggestion.Entry.Option(text, highlighted, score, collateMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testFromXContent() throws IOException {
|
public void testFromXContent() throws IOException {
|
||||||
|
@ -66,7 +67,7 @@ public class SuggestionOptionTests extends ESTestCase {
|
||||||
Option parsed;
|
Option parsed;
|
||||||
try (XContentParser parser = createParser(xContentType.xContent(), mutated)) {
|
try (XContentParser parser = createParser(xContentType.xContent(), mutated)) {
|
||||||
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
|
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
|
||||||
parsed = Option.fromXContent(parser);
|
parsed = PhraseSuggestion.Entry.Option.fromXContent(parser);
|
||||||
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
|
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
|
||||||
assertNull(parser.nextToken());
|
assertNull(parser.nextToken());
|
||||||
}
|
}
|
||||||
|
@ -78,7 +79,7 @@ public class SuggestionOptionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testToXContent() throws IOException {
|
public void testToXContent() throws IOException {
|
||||||
Option option = new Option(new Text("someText"), new Text("somethingHighlighted"), 1.3f, true);
|
Option option = new PhraseSuggestion.Entry.Option(new Text("someText"), new Text("somethingHighlighted"), 1.3f, true);
|
||||||
BytesReference xContent = toXContent(option, XContentType.JSON, randomBoolean());
|
BytesReference xContent = toXContent(option, XContentType.JSON, randomBoolean());
|
||||||
assertEquals("{\"text\":\"someText\","
|
assertEquals("{\"text\":\"someText\","
|
||||||
+ "\"highlighted\":\"somethingHighlighted\","
|
+ "\"highlighted\":\"somethingHighlighted\","
|
||||||
|
|
|
@ -188,14 +188,15 @@ public class SuggestionTests extends ESTestCase {
|
||||||
public void testToXContent() throws IOException {
|
public void testToXContent() throws IOException {
|
||||||
ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap(RestSearchAction.TYPED_KEYS_PARAM, "true"));
|
ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap(RestSearchAction.TYPED_KEYS_PARAM, "true"));
|
||||||
{
|
{
|
||||||
Option option = new Option(new Text("someText"), new Text("somethingHighlighted"), 1.3f, true);
|
PhraseSuggestion.Entry.Option option = new PhraseSuggestion.Entry.Option(new Text("someText"), new Text("somethingHighlighted"),
|
||||||
Entry<Option> entry = new Entry<>(new Text("entryText"), 42, 313);
|
1.3f, true);
|
||||||
|
PhraseSuggestion.Entry entry = new PhraseSuggestion.Entry(new Text("entryText"), 42, 313);
|
||||||
entry.addOption(option);
|
entry.addOption(option);
|
||||||
Suggestion<Entry<Option>> suggestion = new Suggestion<>("suggestionName", 5);
|
PhraseSuggestion suggestion = new PhraseSuggestion("suggestionName", 5);
|
||||||
suggestion.addTerm(entry);
|
suggestion.addTerm(entry);
|
||||||
BytesReference xContent = toXContent(suggestion, XContentType.JSON, params, randomBoolean());
|
BytesReference xContent = toXContent(suggestion, XContentType.JSON, params, randomBoolean());
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"{\"suggestion#suggestionName\":[{"
|
"{\"phrase#suggestionName\":[{"
|
||||||
+ "\"text\":\"entryText\","
|
+ "\"text\":\"entryText\","
|
||||||
+ "\"offset\":42,"
|
+ "\"offset\":42,"
|
||||||
+ "\"length\":313,"
|
+ "\"length\":313,"
|
||||||
|
@ -208,7 +209,8 @@ public class SuggestionTests extends ESTestCase {
|
||||||
+ "}", xContent.utf8ToString());
|
+ "}", xContent.utf8ToString());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
Option option = new Option(new Text("someText"), new Text("somethingHighlighted"), 1.3f, true);
|
PhraseSuggestion.Entry.Option option = new PhraseSuggestion.Entry.Option(new Text("someText"), new Text("somethingHighlighted"),
|
||||||
|
1.3f, true);
|
||||||
PhraseSuggestion.Entry entry = new PhraseSuggestion.Entry(new Text("entryText"), 42, 313, 1.0);
|
PhraseSuggestion.Entry entry = new PhraseSuggestion.Entry(new Text("entryText"), 42, 313, 1.0);
|
||||||
entry.addOption(option);
|
entry.addOption(option);
|
||||||
PhraseSuggestion suggestion = new PhraseSuggestion("suggestionName", 5);
|
PhraseSuggestion suggestion = new PhraseSuggestion("suggestionName", 5);
|
||||||
|
|
|
@ -460,7 +460,7 @@ public abstract class EngineTestCase extends ESTestCase {
|
||||||
TimeValue.timeValueMinutes(5), refreshListenerList, Collections.emptyList(), indexSort, handler,
|
TimeValue.timeValueMinutes(5), refreshListenerList, Collections.emptyList(), indexSort, handler,
|
||||||
new NoneCircuitBreakerService(),
|
new NoneCircuitBreakerService(),
|
||||||
globalCheckpointSupplier == null ?
|
globalCheckpointSupplier == null ?
|
||||||
new ReplicationTracker(shardId, allocationId.getId(), indexSettings, SequenceNumbers.NO_OPS_PERFORMED) :
|
new ReplicationTracker(shardId, allocationId.getId(), indexSettings, SequenceNumbers.NO_OPS_PERFORMED, update -> {}) :
|
||||||
globalCheckpointSupplier, primaryTerm::get);
|
globalCheckpointSupplier, primaryTerm::get);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2008,8 +2008,10 @@ public final class InternalTestCluster extends TestCluster {
|
||||||
final CircuitBreakerService breakerService = getInstanceFromNode(CircuitBreakerService.class, nodeAndClient.node);
|
final CircuitBreakerService breakerService = getInstanceFromNode(CircuitBreakerService.class, nodeAndClient.node);
|
||||||
CircuitBreaker fdBreaker = breakerService.getBreaker(CircuitBreaker.FIELDDATA);
|
CircuitBreaker fdBreaker = breakerService.getBreaker(CircuitBreaker.FIELDDATA);
|
||||||
assertThat("Fielddata breaker not reset to 0 on node: " + name, fdBreaker.getUsed(), equalTo(0L));
|
assertThat("Fielddata breaker not reset to 0 on node: " + name, fdBreaker.getUsed(), equalTo(0L));
|
||||||
CircuitBreaker acctBreaker = breakerService.getBreaker(CircuitBreaker.ACCOUNTING);
|
// TODO: This is commented out while Lee looks into the failures
|
||||||
assertThat("Accounting breaker not reset to 0 on node: " + name, acctBreaker.getUsed(), equalTo(0L));
|
// See: https://github.com/elastic/elasticsearch/issues/30290
|
||||||
|
// CircuitBreaker acctBreaker = breakerService.getBreaker(CircuitBreaker.ACCOUNTING);
|
||||||
|
// assertThat("Accounting breaker not reset to 0 on node: " + name, acctBreaker.getUsed(), equalTo(0L));
|
||||||
// Anything that uses transport or HTTP can increase the
|
// Anything that uses transport or HTTP can increase the
|
||||||
// request breaker (because they use bigarrays), because of
|
// request breaker (because they use bigarrays), because of
|
||||||
// that the breaker can sometimes be incremented from ping
|
// that the breaker can sometimes be incremented from ping
|
||||||
|
|
|
@ -40,9 +40,11 @@ import org.elasticsearch.nio.NioServerSocketChannel;
|
||||||
import org.elasticsearch.nio.NioSocketChannel;
|
import org.elasticsearch.nio.NioSocketChannel;
|
||||||
import org.elasticsearch.nio.ServerChannelContext;
|
import org.elasticsearch.nio.ServerChannelContext;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
import org.elasticsearch.transport.ConnectionProfile;
|
||||||
import org.elasticsearch.transport.TcpChannel;
|
import org.elasticsearch.transport.TcpChannel;
|
||||||
import org.elasticsearch.transport.TcpServerChannel;
|
import org.elasticsearch.transport.TcpServerChannel;
|
||||||
import org.elasticsearch.transport.TcpTransport;
|
import org.elasticsearch.transport.TcpTransport;
|
||||||
|
import org.elasticsearch.transport.TransportRequestOptions;
|
||||||
import org.elasticsearch.transport.Transports;
|
import org.elasticsearch.transport.Transports;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -51,6 +53,8 @@ import java.net.StandardSocketOptions;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.ServerSocketChannel;
|
import java.nio.channels.ServerSocketChannel;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
@ -128,6 +132,34 @@ public class MockNioTransport extends TcpTransport {
|
||||||
profileToChannelFactory.clear();
|
profileToChannelFactory.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ConnectionProfile resolveConnectionProfile(ConnectionProfile connectionProfile) {
|
||||||
|
ConnectionProfile resolvedProfile = resolveConnectionProfile(connectionProfile, defaultConnectionProfile);
|
||||||
|
if (resolvedProfile.getNumConnections() <= 3) {
|
||||||
|
return resolvedProfile;
|
||||||
|
}
|
||||||
|
ConnectionProfile.Builder builder = new ConnectionProfile.Builder();
|
||||||
|
Set<TransportRequestOptions.Type> allTypesWithConnection = new HashSet<>();
|
||||||
|
Set<TransportRequestOptions.Type> allTypesWithoutConnection = new HashSet<>();
|
||||||
|
for (TransportRequestOptions.Type type : TransportRequestOptions.Type.values()) {
|
||||||
|
int numConnections = resolvedProfile.getNumConnectionsPerType(type);
|
||||||
|
if (numConnections > 0) {
|
||||||
|
allTypesWithConnection.add(type);
|
||||||
|
} else {
|
||||||
|
allTypesWithoutConnection.add(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we maintain at least the types that are supported by this profile even if we only use a single channel for them.
|
||||||
|
builder.addConnections(3, allTypesWithConnection.toArray(new TransportRequestOptions.Type[0]));
|
||||||
|
if (allTypesWithoutConnection.isEmpty() == false) {
|
||||||
|
builder.addConnections(0, allTypesWithoutConnection.toArray(new TransportRequestOptions.Type[0]));
|
||||||
|
}
|
||||||
|
builder.setHandshakeTimeout(resolvedProfile.getHandshakeTimeout());
|
||||||
|
builder.setConnectTimeout(resolvedProfile.getConnectTimeout());
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
private void exceptionCaught(NioSocketChannel channel, Exception exception) {
|
private void exceptionCaught(NioSocketChannel channel, Exception exception) {
|
||||||
onException((TcpChannel) channel, exception);
|
onException((TcpChannel) channel, exception);
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,11 @@ public class SimpleMockNioTransportTests extends AbstractSimpleTransportTestCase
|
||||||
return transportService;
|
return transportService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int channelsPerNodeConnection() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void closeConnectionChannel(Transport transport, Transport.Connection connection) throws IOException {
|
protected void closeConnectionChannel(Transport transport, Transport.Connection connection) throws IOException {
|
||||||
TcpTransport.NodeChannels channels = (TcpTransport.NodeChannels) connection;
|
TcpTransport.NodeChannels channels = (TcpTransport.NodeChannels) connection;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue