summaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo
diff options
context:
space:
mode:
authorØyvind Grønnesby <oyving@verizonmedia.com>2019-10-08 13:00:36 +0200
committerØyvind Grønnesby <oyving@verizonmedia.com>2019-10-08 13:00:36 +0200
commit948500c2a4d52aef59ec18706ae4b8d793afb060 (patch)
treece305f94dbf4f90423255adc3b02fd441bcf211b /container-search/src/main/java/com/yahoo
parent0d9892df0aa99e13ff83bfdeb3de8f1e8305bdcd (diff)
parent64d8a699b668a5ed1dae0b86bd88bd3a2b40e48c (diff)
Merge remote-tracking branch 'origin/master' into ogronnesby/let-plugin-get-key-as-parameter
Conflicts: hosted-api/src/main/java/ai/vespa/hosted/api/Properties.java
Diffstat (limited to 'container-search/src/main/java/com/yahoo')
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java9
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/FastSearcher.java5
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/ClusterMonitor.java23
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java4
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Node.java27
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java22
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java10
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java96
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java51
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/SubstituteString.java64
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java3
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalMap.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java68
15 files changed, 263 insertions, 125 deletions
diff --git a/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java b/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java
index 1f621eb926c..0c8e564578b 100644
--- a/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java
@@ -7,7 +7,6 @@ import com.yahoo.component.chain.dependencies.After;
import com.yahoo.container.QrSearchersConfig;
import com.yahoo.container.handler.VipStatus;
import com.yahoo.jdisc.Metric;
-import com.yahoo.net.HostName;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.fastsearch.ClusterParams;
import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig;
@@ -27,8 +26,6 @@ import com.yahoo.vespa.config.search.DispatchConfig;
import com.yahoo.vespa.streamingvisitors.VdsStreamingSearcher;
import org.apache.commons.lang.StringUtils;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -371,6 +368,10 @@ public class ClusterSearcher extends Searcher {
}
@Override
- public void deconstruct() { }
+ public void deconstruct() {
+ if (server != null) {
+ server.shutDown();
+ }
+ }
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastSearcher.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastSearcher.java
index b0b3a7800e9..9a4913b3840 100644
--- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/FastSearcher.java
@@ -173,4 +173,9 @@ public class FastSearcher extends VespaBackEndSearcher {
return getLogger().isLoggable(Level.FINE);
}
+ @Override
+ public void shutDown() {
+ super.shutDown();
+ dispatcher.shutDown();
+ }
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java b/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java
index 8f4b49ac71e..bc3ac6cdef1 100644
--- a/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/fastsearch/VespaBackEndSearcher.java
@@ -392,4 +392,6 @@ public abstract class VespaBackEndSearcher extends PingableSearcher {
return getLogger().isLoggable(Level.FINE);
}
+ public void shutDown() { }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/ClusterMonitor.java b/container-search/src/main/java/com/yahoo/search/cluster/ClusterMonitor.java
index 22c7f59872c..a016f7d695c 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/ClusterMonitor.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/ClusterMonitor.java
@@ -9,7 +9,9 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -26,9 +28,9 @@ public class ClusterMonitor<T> {
private static Logger log = Logger.getLogger(ClusterMonitor.class.getName());
- private NodeManager<T> nodeManager;
+ private final NodeManager<T> nodeManager;
- private MonitorThread monitorThread;
+ private final MonitorThread monitorThread;
private volatile boolean shutdown = false;
@@ -119,28 +121,35 @@ public class ClusterMonitor<T> {
}
public void run() {
- log.fine("Starting cluster monitor thread");
+ log.info("Starting cluster monitor thread " + getName());
// Pings must happen in a separate thread from this to handle timeouts
// By using a cached thread pool we ensured that 1) a single thread will be used
// for all pings when there are no problems (important because it ensures that
// any thread local connections are reused) 2) a new thread will be started to execute
// new pings when a ping is not responding
- Executor pingExecutor=Executors.newCachedThreadPool(ThreadFactoryFactory.getDaemonThreadFactory("search.ping"));
+ ExecutorService pingExecutor=Executors.newCachedThreadPool(ThreadFactoryFactory.getDaemonThreadFactory("search.ping"));
while (!isInterrupted()) {
try {
Thread.sleep(configuration.getCheckInterval());
log.finest("Activating ping");
ping(pingExecutor);
}
- catch (Exception e) {
+ catch (Throwable e) {
if (shutdown && e instanceof InterruptedException) {
break;
+ } else if ( ! (e instanceof Exception) ) {
+ log.log(Level.WARNING,"Error in monitor thread, will quit", e);
+ break;
} else {
- log.log(Level.WARNING,"Error in monitor thread",e);
+ log.log(Level.WARNING,"Exception in monitor thread", e);
}
}
}
- log.fine("Stopped cluster monitor thread");
+ pingExecutor.shutdown();
+ try {
+ pingExecutor.awaitTermination(10, TimeUnit.SECONDS);
+ } catch (InterruptedException e) { }
+ log.info("Stopped cluster monitor thread " + getName());
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java b/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java
index 7369b33e82d..ddd319b7bcb 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/Dispatcher.java
@@ -195,6 +195,10 @@ public class Dispatcher extends AbstractComponent {
return Optional.empty();
}
+ public void shutDown() {
+ searchCluster.shutDown();
+ }
+
private void emitDispatchMetric(Optional<SearchInvoker> invoker) {
if (invoker.isEmpty()) {
metric.add(FDISPATCH_METRIC, 1, metricContext);
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Node.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Node.java
index b47f2fefa5b..09ad715b471 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Node.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Node.java
@@ -16,17 +16,15 @@ public class Node {
private final int key;
private int pathIndex;
private final String hostname;
- private final int fs4port;
- final int group;
+ private final int group;
private final AtomicBoolean statusIsKnown = new AtomicBoolean(false);
private final AtomicBoolean working = new AtomicBoolean(true);
private final AtomicLong activeDocuments = new AtomicLong(0);
- public Node(int key, String hostname, int fs4port, int group) {
+ public Node(int key, String hostname, int group) {
this.key = key;
this.hostname = hostname;
- this.fs4port = fs4port;
this.group = group;
}
@@ -41,14 +39,15 @@ public class Node {
public String hostname() { return hostname; }
- public int fs4port() { return fs4port; }
-
/** Returns the id of this group this node belongs to */
public int group() { return group; }
public void setWorking(boolean working) {
this.statusIsKnown.lazySet(true);
this.working.lazySet(working);
+ if ( ! working ) {
+ activeDocuments.set(0);
+ }
}
/** Returns whether this node is currently responding to requests, or null if status is not known */
@@ -57,17 +56,17 @@ public class Node {
}
/** Updates the active documents on this node */
- public void setActiveDocuments(long activeDocuments) {
+ void setActiveDocuments(long activeDocuments) {
this.activeDocuments.set(activeDocuments);
}
/** Returns the active documents on this node. If unknown, 0 is returned. */
- public long getActiveDocuments() {
- return this.activeDocuments.get();
+ long getActiveDocuments() {
+ return activeDocuments.get();
}
@Override
- public int hashCode() { return Objects.hash(hostname, fs4port); }
+ public int hashCode() { return Objects.hash(hostname, key, pathIndex, group); }
@Override
public boolean equals(Object o) {
@@ -75,11 +74,15 @@ public class Node {
if ( ! (o instanceof Node)) return false;
Node other = (Node)o;
if ( ! Objects.equals(this.hostname, other.hostname)) return false;
- if ( ! Objects.equals(this.fs4port, other.fs4port)) return false;
+ if ( ! Objects.equals(this.key, other.key)) return false;
+ if ( ! Objects.equals(this.pathIndex, other.pathIndex)) return false;
+ if ( ! Objects.equals(this.group, other.group)) return false;
+
return true;
}
@Override
- public String toString() { return "search node " + hostname + ":" + fs4port + " in group " + group; }
+ public String toString() { return "search node key = " + key + " hostname = "+ hostname + " path = " + pathIndex + " in group " + group +
+ " statusIsKnown = " + statusIsKnown.get() + " working = " + working.get() + " activeDocs = " + activeDocuments.get(); }
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java
index a55a970e8ff..3595a24ca92 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java
@@ -46,6 +46,7 @@ public class SearchCluster implements NodeManager<Node> {
private final ClusterMonitor<Node> clusterMonitor;
private final VipStatus vipStatus;
private PingFactory pingFactory;
+ private long nextLogTime = 0;
/**
* A search node on this local machine having the entire corpus, which we therefore
@@ -73,7 +74,7 @@ public class SearchCluster implements NodeManager<Node> {
}
this.groups = groupsBuilder.build();
LinkedHashMap<Integer, Group> groupIntroductionOrder = new LinkedHashMap<>();
- nodes.forEach(node -> groupIntroductionOrder.put(node.group(), groups.get(node.group)));
+ nodes.forEach(node -> groupIntroductionOrder.put(node.group(), groups.get(node.group())));
this.orderedGroups = ImmutableList.<Group>builder().addAll(groupIntroductionOrder.values()).build();
// Index nodes by host
@@ -91,6 +92,10 @@ public class SearchCluster implements NodeManager<Node> {
this.clusterMonitor = new ClusterMonitor<>(this);
}
+ public void shutDown() {
+ clusterMonitor.shutdown();
+ }
+
public void startClusterMonitoring(PingFactory pingFactory) {
this.pingFactory = pingFactory;
@@ -141,7 +146,7 @@ public class SearchCluster implements NodeManager<Node> {
}
for (DispatchConfig.Node node : dispatchConfig.node()) {
if (filter.test(node)) {
- nodesBuilder.add(new Node(node.key(), node.host(), node.fs4port(), node.group()));
+ nodesBuilder.add(new Node(node.key(), node.host(), node.group()));
}
}
return nodesBuilder.build();
@@ -409,14 +414,21 @@ public class SearchCluster implements NodeManager<Node> {
private void trackGroupCoverageChanges(int index, Group group, boolean fullCoverage, long averageDocuments) {
boolean changed = group.isFullCoverageStatusChanged(fullCoverage);
- if (changed) {
+ if (changed || (!fullCoverage && System.currentTimeMillis() > nextLogTime)) {
+ nextLogTime = System.currentTimeMillis() + 30 * 1000;
int requiredNodes = groupSize() - dispatchConfig.maxNodesDownPerGroup();
if (fullCoverage) {
log.info(() -> String.format("Group %d is now good again (%d/%d active docs, coverage %d/%d)",
index, group.getActiveDocuments(), averageDocuments, group.workingNodes(), groupSize()));
} else {
- log.warning(() -> String.format("Coverage of group %d is only %d/%d (requires %d)",
- index, group.workingNodes(), groupSize(), requiredNodes));
+ StringBuilder missing = new StringBuilder();
+ for (var node : group.nodes()) {
+ if (node.isWorking() != Boolean.TRUE) {
+ missing.append('\n').append(node.toString());
+ }
+ }
+ log.warning(() -> String.format("Coverage of group %d is only %d/%d (requires %d) (%d/%d active docs) Failed nodes are:%s",
+ index, group.workingNodes(), groupSize(), requiredNodes, group.getActiveDocuments(), averageDocuments, missing.toString()));
}
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java b/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java
index 0059b761734..1fc1e19e3ee 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/DimensionBinding.java
@@ -21,7 +21,7 @@ public class DimensionBinding {
private DimensionValues values;
/** The binding from those dimensions to values, and possibly other values */
- private Map<String,String> context;
+ private Map<String, String> context;
public static final DimensionBinding nullBinding =
new DimensionBinding(Collections.unmodifiableList(Collections.emptyList()), DimensionValues.empty, null);
@@ -195,11 +195,11 @@ public class DimensionBinding {
@Override
public String toString() {
if (isInvalid()) return "Invalid DimensionBinding";
- if (dimensions==null) return "DimensionBinding []";
- StringBuilder b=new StringBuilder("DimensionBinding [");
- for (int i=0; i<dimensions.size(); i++) {
+ if (dimensions == null) return "DimensionBinding []";
+ StringBuilder b = new StringBuilder("DimensionBinding [");
+ for (int i = 0; i < dimensions.size(); i++) {
b.append(dimensions.get(i)).append("=").append(values.get(i));
- if (i<dimensions.size()-1)
+ if (i < dimensions.size()-1)
b.append(", ");
}
b.append("]");
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java
index acca2d403be..f5f6b2d2550 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfile.java
@@ -102,7 +102,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
*/
public List<QueryProfile> inherited() {
if (isFrozen()) return inherited; // Frozen profiles always have an unmodifiable, non-null list
- if (inherited==null) return Collections.emptyList();
+ if (inherited == null) return Collections.emptyList();
return Collections.unmodifiableList(inherited);
}
@@ -474,17 +474,17 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
/** Returns this value, or its corresponding substitution string if it contains substitutions */
protected Object convertToSubstitutionString(Object value) {
- if (value==null) return value;
- if (value.getClass()!=String.class) return value;
- SubstituteString substituteString=SubstituteString.create((String)value);
- if (substituteString==null) return value;
+ if (value == null) return value;
+ if (value.getClass() != String.class) return value;
+ SubstituteString substituteString = SubstituteString.create((String)value);
+ if (substituteString == null) return value;
return substituteString;
}
/** Returns the field description of this field, or null if it is not typed */
protected FieldDescription getFieldDescription(CompoundName name, DimensionBinding binding) {
- FieldDescriptionQueryProfileVisitor visitor=new FieldDescriptionQueryProfileVisitor(name.asList());
- accept(visitor, binding,null);
+ FieldDescriptionQueryProfileVisitor visitor = new FieldDescriptionQueryProfileVisitor(name.asList());
+ accept(visitor, binding, null);
return visitor.result();
}
@@ -493,23 +493,23 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
* false if it is declared unoverridable (in instance or type), and null if this profile has no
* opinion on the matter because the value is not set in this.
*/
- Boolean isLocalOverridable(String localName,DimensionBinding binding) {
- if (localLookup(localName, binding)==null) return null; // Not set
- Boolean isLocalInstanceOverridable=isLocalInstanceOverridable(localName);
- if (isLocalInstanceOverridable!=null)
+ Boolean isLocalOverridable(String localName, DimensionBinding binding) {
+ if (localLookup(localName, binding) == null) return null; // Not set
+ Boolean isLocalInstanceOverridable = isLocalInstanceOverridable(localName);
+ if (isLocalInstanceOverridable != null)
return isLocalInstanceOverridable.booleanValue();
- if (type!=null) return type.isOverridable(localName);
+ if (type != null) return type.isOverridable(localName);
return true;
}
protected Boolean isLocalInstanceOverridable(String localName) {
- if (overridable==null) return null;
+ if (overridable == null) return null;
return overridable.get(localName);
}
- protected Object lookup(CompoundName name,boolean allowQueryProfileResult, DimensionBinding dimensionBinding) {
- SingleValueQueryProfileVisitor visitor=new SingleValueQueryProfileVisitor(name.asList(),allowQueryProfileResult);
- accept(visitor,dimensionBinding,null);
+ protected Object lookup(CompoundName name, boolean allowQueryProfileResult, DimensionBinding dimensionBinding) {
+ SingleValueQueryProfileVisitor visitor = new SingleValueQueryProfileVisitor(name.asList(), allowQueryProfileResult);
+ accept(visitor, dimensionBinding, null);
return visitor.getResult();
}
@@ -518,7 +518,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
}
void acceptAndEnter(String key, QueryProfileVisitor visitor,DimensionBinding dimensionBinding, QueryProfile owner) {
- boolean allowContent=visitor.enter(key);
+ boolean allowContent = visitor.enter(key);
accept(allowContent, visitor, dimensionBinding, owner);
if (allowContent)
visitor.leave(key);
@@ -548,25 +548,25 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
}
protected void visitVariants(boolean allowContent,QueryProfileVisitor visitor,DimensionBinding dimensionBinding) {
- if (getVariants()!=null)
+ if (getVariants() != null)
getVariants().accept(allowContent, getType(), visitor, dimensionBinding);
}
protected void visitInherited(boolean allowContent,QueryProfileVisitor visitor,DimensionBinding dimensionBinding, QueryProfile owner) {
- if (inherited==null) return;
+ if (inherited == null) return;
for (QueryProfile inheritedProfile : inherited) {
- inheritedProfile.accept(allowContent,visitor,dimensionBinding.createFor(inheritedProfile.getDimensions()), owner);
+ inheritedProfile.accept(allowContent, visitor, dimensionBinding.createFor(inheritedProfile.getDimensions()), owner);
if (visitor.isDone()) return;
}
}
private void visitContent(QueryProfileVisitor visitor,DimensionBinding dimensionBinding) {
- String contentKey=visitor.getLocalKey();
+ String contentKey = visitor.getLocalKey();
// Visit this' content
- if (contentKey!=null) { // Get only the content of the current key
- if (type!=null)
- contentKey=type.unalias(contentKey);
+ if (contentKey != null) { // Get only the content of the current key
+ if (type != null)
+ contentKey = type.unalias(contentKey);
visitor.acceptValue(contentKey, getContent(contentKey), dimensionBinding, this);
}
else { // get all content in this
@@ -590,11 +590,11 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
/** Sets the value of a node in <i>this</i> profile - the local name given must not be nested (contain dots) */
protected QueryProfile setLocalNode(String localName, Object value,QueryProfileType parentType,
DimensionBinding dimensionBinding, QueryProfileRegistry registry) {
- if (parentType!=null && type==null && !isFrozen())
- type=parentType;
+ if (parentType != null && type == null && ! isFrozen())
+ type = parentType;
- value=checkAndConvertAssignment(localName, value, registry);
- localPut(localName,value,dimensionBinding);
+ value = checkAndConvertAssignment(localName, value, registry);
+ localPut(localName, value, dimensionBinding);
return this;
}
@@ -605,20 +605,20 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
*/
static Object combineValues(Object newValue, Object existingValue) {
if (newValue instanceof QueryProfile) {
- QueryProfile newProfile=(QueryProfile)newValue;
- if ( existingValue==null || ! (existingValue instanceof QueryProfile)) {
+ QueryProfile newProfile = (QueryProfile)newValue;
+ if ( existingValue == null || ! (existingValue instanceof QueryProfile)) {
if (!isModifiable(newProfile))
- newProfile=new BackedOverridableQueryProfile(newProfile); // Make the query profile reference overridable
- newProfile.value=existingValue;
+ newProfile = new BackedOverridableQueryProfile(newProfile); // Make the query profile reference overridable
+ newProfile.value = existingValue;
return newProfile;
}
// if both are profiles:
- return combineProfiles(newProfile,(QueryProfile)existingValue);
+ return combineProfiles(newProfile, (QueryProfile)existingValue);
}
else {
if (existingValue instanceof QueryProfile) { // we need to set a non-leaf value on a query profile
- QueryProfile existingProfile=(QueryProfile)existingValue;
+ QueryProfile existingProfile = (QueryProfile)existingValue;
if (isModifiable(existingProfile)) {
existingProfile.setValue(newValue);
return null;
@@ -636,16 +636,16 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
}
private static QueryProfile combineProfiles(QueryProfile newProfile,QueryProfile existingProfile) {
- QueryProfile returnValue=null;
+ QueryProfile returnValue = null;
QueryProfile existingModifiable;
// Ensure the existing profile is modifiable
- if (existingProfile.getClass()==QueryProfile.class) {
+ if (existingProfile.getClass() == QueryProfile.class) {
existingModifiable = new BackedOverridableQueryProfile(existingProfile);
- returnValue=existingModifiable;
+ returnValue = existingModifiable;
}
else { // is an overridable wrapper
- existingModifiable=existingProfile; // May be used as-is
+ existingModifiable = existingProfile; // May be used as-is
}
// Make the existing profile inherit the new one
@@ -655,7 +655,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
existingModifiable.addInherited(newProfile);
// Remove content from the existing which the new one does not allow overrides of
- if (existingModifiable.content!=null) {
+ if (existingModifiable.content != null) {
for (String key : existingModifiable.content.unmodifiableMap().keySet()) {
if ( ! newProfile.isLocalOverridable(key, null)) {
existingModifiable.content.remove(key);
@@ -681,10 +681,10 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
* @throws IllegalArgumentException if the assignment is illegal
*/
protected Object checkAndConvertAssignment(String localName, Object value, QueryProfileRegistry registry) {
- if (type==null) return value; // no type checking
+ if (type == null) return value; // no type checking
- FieldDescription fieldDescription=type.getField(localName);
- if (fieldDescription==null) {
+ FieldDescription fieldDescription = type.getField(localName);
+ if (fieldDescription == null) {
if (type.isStrict())
throw new IllegalArgumentException("'" + localName + "' is not declared in " + type + ", and the type is strict");
return value;
@@ -710,8 +710,8 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
/** Do a variant-aware content lookup in this */
protected Object localLookup(String name, DimensionBinding dimensionBinding) {
Object node = null;
- if ( variants != null && !dimensionBinding.isNull())
- node = variants.get(name,type,true,dimensionBinding);
+ if ( variants != null && ! dimensionBinding.isNull())
+ node = variants.get(name,type,true, dimensionBinding);
if (node == null)
node = content == null ? null : content.get(name);
return node;
@@ -801,7 +801,7 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
}
/** Sets a value directly in this query profile (unless frozen) */
- private void localPut(String localName,Object value, DimensionBinding dimensionBinding) {
+ private void localPut(String localName, Object value, DimensionBinding dimensionBinding) {
ensureNotFrozen();
if (type != null)
@@ -813,17 +813,17 @@ public class QueryProfile extends FreezableSimpleComponent implements Cloneable
if (dimensionBinding.isNull()) {
Object combinedValue;
if (value instanceof QueryProfile)
- combinedValue = combineValues(value,content==null ? null : content.get(localName));
+ combinedValue = combineValues(value, content == null ? null : content.get(localName));
else
combinedValue = combineValues(value, localLookup(localName, dimensionBinding));
if (combinedValue!=null)
- content.put(localName,combinedValue);
+ content.put(localName, combinedValue);
}
else {
if (variants == null)
variants = new QueryProfileVariants(dimensionBinding.getDimensions(), this);
- variants.set(localName,dimensionBinding.getValues(),value);
+ variants.set(localName, dimensionBinding.getValues(), value);
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java
index accef7ba154..fd2852fda60 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/QueryProfileCompiler.java
@@ -33,31 +33,36 @@ public class QueryProfileCompiler {
}
public static CompiledQueryProfile compile(QueryProfile in, CompiledQueryProfileRegistry registry) {
- DimensionalMap.Builder<CompoundName, Object> values = new DimensionalMap.Builder<>();
- DimensionalMap.Builder<CompoundName, QueryProfileType> types = new DimensionalMap.Builder<>();
- DimensionalMap.Builder<CompoundName, Object> references = new DimensionalMap.Builder<>();
- DimensionalMap.Builder<CompoundName, Object> unoverridables = new DimensionalMap.Builder<>();
-
- // Resolve values for each existing variant and combine into a single data structure
- Set<DimensionBindingForPath> variants = collectVariants(CompoundName.empty, in, DimensionBinding.nullBinding);
- variants.add(new DimensionBindingForPath(DimensionBinding.nullBinding, CompoundName.empty)); // if this contains no variants
- log.fine(() -> "Compiling " + in.toString() + " having " + variants.size() + " variants");
- for (DimensionBindingForPath variant : variants) {
- log.finer(() -> " Compiling variant " + variant);
- for (Map.Entry<String, Object> entry : in.listValues(variant.path(), variant.binding().getContext(), null).entrySet()) {
- values.put(variant.path().append(entry.getKey()), variant.binding(), entry.getValue());
+ try {
+ DimensionalMap.Builder<CompoundName, Object> values = new DimensionalMap.Builder<>();
+ DimensionalMap.Builder<CompoundName, QueryProfileType> types = new DimensionalMap.Builder<>();
+ DimensionalMap.Builder<CompoundName, Object> references = new DimensionalMap.Builder<>();
+ DimensionalMap.Builder<CompoundName, Object> unoverridables = new DimensionalMap.Builder<>();
+
+ // Resolve values for each existing variant and combine into a single data structure
+ Set<DimensionBindingForPath> variants = collectVariants(CompoundName.empty, in, DimensionBinding.nullBinding);
+ variants.add(new DimensionBindingForPath(DimensionBinding.nullBinding, CompoundName.empty)); // if this contains no variants
+ log.fine(() -> "Compiling " + in.toString() + " having " + variants.size() + " variants");
+ for (DimensionBindingForPath variant : variants) {
+ log.finer(() -> " Compiling variant " + variant);
+ for (Map.Entry<String, Object> entry : in.listValues(variant.path(), variant.binding().getContext(), null).entrySet()) {
+ values.put(variant.path().append(entry.getKey()), variant.binding(), entry.getValue());
+ }
+ for (Map.Entry<CompoundName, QueryProfileType> entry : in.listTypes(variant.path(), variant.binding().getContext()).entrySet())
+ types.put(variant.path().append(entry.getKey()), variant.binding(), entry.getValue());
+ for (CompoundName reference : in.listReferences(variant.path(), variant.binding().getContext()))
+ references.put(variant.path().append(reference), variant.binding(), Boolean.TRUE); // Used as a set; value is ignored
+ for (CompoundName name : in.listUnoverridable(variant.path(), variant.binding().getContext()))
+ unoverridables.put(variant.path().append(name), variant.binding(), Boolean.TRUE); // Used as a set; value is ignored
}
- for (Map.Entry<CompoundName, QueryProfileType> entry : in.listTypes(variant.path(), variant.binding().getContext()).entrySet())
- types.put(variant.path().append(entry.getKey()), variant.binding(), entry.getValue());
- for (CompoundName reference : in.listReferences(variant.path(), variant.binding().getContext()))
- references.put(variant.path().append(reference), variant.binding(), Boolean.TRUE); // Used as a set; value is ignored
- for (CompoundName name : in.listUnoverridable(variant.path(), variant.binding().getContext()))
- unoverridables.put(variant.path().append(name), variant.binding(), Boolean.TRUE); // Used as a set; value is ignored
- }
- return new CompiledQueryProfile(in.getId(), in.getType(),
- values.build(), types.build(), references.build(), unoverridables.build(),
- registry);
+ return new CompiledQueryProfile(in.getId(), in.getType(),
+ values.build(), types.build(), references.build(), unoverridables.build(),
+ registry);
+ }
+ catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Invalid " + in, e);
+ }
}
/**
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/SubstituteString.java b/container-search/src/main/java/com/yahoo/search/query/profile/SubstituteString.java
index 3252f0f4662..446bb250856 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/SubstituteString.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/SubstituteString.java
@@ -2,6 +2,7 @@
package com.yahoo.search.query.profile;
import com.yahoo.processing.request.Properties;
+import com.yahoo.search.query.profile.compiled.CompiledQueryProfile;
import java.util.ArrayList;
import java.util.List;
@@ -22,6 +23,7 @@ public class SubstituteString {
private final List<Component> components;
private final String stringValue;
+ private final boolean hasRelative;
/**
* Returns a new SubstituteString if the given string contains substitutions, null otherwise.
@@ -35,34 +37,48 @@ public class SubstituteString {
int end = value.indexOf("}", start + 2);
if (end < 0)
throw new IllegalArgumentException("Unterminated value substitution '" + value.substring(start) + "'");
- String propertyName = value.substring(start+2,end);
- if (propertyName.indexOf("%{") >= 0)
+ String propertyName = value.substring(start + 2, end);
+ if (propertyName.contains("%{"))
throw new IllegalArgumentException("Unterminated value substitution '" + value.substring(start) + "'");
components.add(new StringComponent(value.substring(lastEnd, start)));
- components.add(new PropertyComponent(propertyName));
- lastEnd = end+1;
+ if (propertyName.startsWith("."))
+ components.add(new RelativePropertyComponent(propertyName.substring(1)));
+ else
+ components.add(new PropertyComponent(propertyName));
+ lastEnd = end + 1;
start = value.indexOf("%{", lastEnd);
}
components.add(new StringComponent(value.substring(lastEnd)));
return new SubstituteString(components, value);
}
- private SubstituteString(List<Component> components, String stringValue) {
+ public SubstituteString(List<Component> components, String stringValue) {
this.components = components;
this.stringValue = stringValue;
+ this.hasRelative = components.stream().anyMatch(component -> component instanceof RelativePropertyComponent);
}
+ /** Returns whether this has at least one relative component */
+ public boolean hasRelative() { return hasRelative; }
+
/**
- * Perform the substitution in this, by looking up in the given query profile,
+ * Perform the substitution in this, by looking up in the given properties,
* and returns the resulting string
+ *
+ * @param context the content which is used to resolve profile variants when looking up substitution values
+ * @param substitution the properties in which values to be substituted are looked up
*/
public String substitute(Map<String, String> context, Properties substitution) {
StringBuilder b = new StringBuilder();
for (Component component : components)
- b.append(component.getValue(context,substitution));
+ b.append(component.getValue(context, substitution));
return b.toString();
}
+ public List<Component> components() { return components; }
+
+ public String stringValue() { return stringValue; }
+
@Override
public int hashCode() {
return stringValue.hashCode();
@@ -81,13 +97,13 @@ public class SubstituteString {
return stringValue;
}
- private abstract static class Component {
+ public abstract static class Component {
protected abstract String getValue(Map<String, String> context, Properties substitution);
}
- private final static class StringComponent extends Component {
+ public final static class StringComponent extends Component {
private final String value;
@@ -107,7 +123,7 @@ public class SubstituteString {
}
- private final static class PropertyComponent extends Component {
+ public final static class PropertyComponent extends Component {
private final String propertyName;
@@ -116,7 +132,7 @@ public class SubstituteString {
}
@Override
- public String getValue(Map<String,String> context, Properties substitution) {
+ public String getValue(Map<String, String> context, Properties substitution) {
Object value = substitution.get(propertyName, context, substitution);
if (value == null) return "";
return String.valueOf(value);
@@ -129,4 +145,30 @@ public class SubstituteString {
}
+ /**
+ * A component where the value should be looked up in the profile containing the substitution field
+ * rather than globally
+ */
+ public final static class RelativePropertyComponent extends Component {
+
+ private final String fieldName;
+
+ public RelativePropertyComponent(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ @Override
+ public String getValue(Map<String, String> context, Properties substitution) {
+ throw new IllegalStateException("Should be resolved during compilation");
+ }
+
+ public String fieldName() { return fieldName; }
+
+ @Override
+ public String toString() {
+ return "%{" + fieldName + "}";
+ }
+
+ }
+
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
index d94d601f103..2774bd4ebf2 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
@@ -34,7 +34,7 @@ public class Binding implements Comparable<Binding> {
private final int hashCode;
@SuppressWarnings("unchecked")
- public static final Binding nullBinding= new Binding(Integer.MAX_VALUE, Collections.<String,String>emptyMap());
+ public static final Binding nullBinding = new Binding(Integer.MAX_VALUE, Collections.<String,String>emptyMap());
public static Binding createFrom(DimensionBinding dimensionBinding) {
if (dimensionBinding.getDimensions().size() > maxDimensions)
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java
index d6e93701ca1..ea85a2be242 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java
@@ -45,7 +45,8 @@ public class CompiledQueryProfile extends AbstractComponent implements Cloneable
/**
* Creates a new query profile from an id.
*/
- public CompiledQueryProfile(ComponentId id, QueryProfileType type,
+ public CompiledQueryProfile(ComponentId id,
+ QueryProfileType type,
DimensionalMap<CompoundName, Object> entries,
DimensionalMap<CompoundName, QueryProfileType> types,
DimensionalMap<CompoundName, Object> references,
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalMap.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalMap.java
index b4a1c66e4e0..2e8f5dcf91c 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalMap.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalMap.java
@@ -58,7 +58,7 @@ public class DimensionalMap<KEY, VALUE> {
public DimensionalMap<KEY, VALUE> build() {
Map<KEY, DimensionalValue<VALUE>> map = new HashMap<>();
for (Map.Entry<KEY, DimensionalValue.Builder<VALUE>> entry : entries.entrySet()) {
- map.put(entry.getKey(), entry.getValue().build());
+ map.put(entry.getKey(), entry.getValue().build(entries));
}
return new DimensionalMap<>(map);
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java
index 506472c97d1..50d0a2de46f 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/DimensionalValue.java
@@ -1,7 +1,9 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.query.profile.compiled;
+import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.query.profile.DimensionBinding;
+import com.yahoo.search.query.profile.SubstituteString;
import java.util.ArrayList;
import java.util.Collections;
@@ -58,6 +60,15 @@ public class DimensionalValue<VALUE> {
/** The minimal set of variants needed to capture all values at this key */
private Map<VALUE, Value.Builder<VALUE>> buildableVariants = new HashMap<>();
+ /** Returns the value for the given binding, or null if none */
+ public VALUE valueFor(DimensionBinding variantBinding) {
+ for (var entry : buildableVariants.entrySet()) {
+ if (entry.getValue().variants.contains(variantBinding))
+ return entry.getKey();
+ }
+ return null;
+ }
+
public void add(VALUE value, DimensionBinding variantBinding) {
// Note: We know we can index by the value because its possible types are constrained
// to what query profiles allow: String, primitives and query profiles
@@ -69,10 +80,10 @@ public class DimensionalValue<VALUE> {
variant.addVariant(variantBinding);
}
- public DimensionalValue<VALUE> build() {
+ public DimensionalValue<VALUE> build(Map<?, DimensionalValue.Builder<VALUE>> entries) {
List<Value> variants = new ArrayList<>();
for (Value.Builder buildableVariant : buildableVariants.values()) {
- variants.addAll(buildableVariant.build());
+ variants.addAll(buildableVariant.build(entries));
}
return new DimensionalValue(variants);
}
@@ -139,14 +150,17 @@ public class DimensionalValue<VALUE> {
}
/** Build a separate value object for each dimension combination which has this value */
- public List<Value<VALUE>> build() {
+ public List<Value<VALUE>> build(Map<CompoundName, DimensionalValue.Builder<VALUE>> entries) {
// Shortcut for efficiency of the normal case
- if (variants.size()==1)
- return Collections.singletonList(new Value<>(value, Binding.createFrom(variants.iterator().next())));
+ if (variants.size() == 1) {
+ return Collections.singletonList(new Value<>(substituteIfRelative(value, variants.iterator().next(), entries),
+ Binding.createFrom(variants.iterator().next())));
+ }
List<Value<VALUE>> values = new ArrayList<>(variants.size());
- for (DimensionBinding variant : variants)
- values.add(new Value<>(value, Binding.createFrom(variant)));
+ for (DimensionBinding variant : variants) {
+ values.add(new Value<>(substituteIfRelative(value, variant, entries), Binding.createFrom(variant)));
+ }
return values;
}
@@ -154,6 +168,46 @@ public class DimensionalValue<VALUE> {
return value;
}
+ @SuppressWarnings("unchecked")
+ private VALUE substituteIfRelative(VALUE value,
+ DimensionBinding variant,
+ Map<CompoundName, DimensionalValue.Builder<VALUE>> entries) {
+ if (value instanceof SubstituteString) {
+ SubstituteString substitute = (SubstituteString)value;
+ if (substitute.hasRelative()) {
+ List<SubstituteString.Component> resolvedComponents = new ArrayList<>(substitute.components().size());
+ for (SubstituteString.Component component : substitute.components()) {
+ if (component instanceof SubstituteString.RelativePropertyComponent) {
+ SubstituteString.RelativePropertyComponent relativeComponent = (SubstituteString.RelativePropertyComponent)component;
+ var substituteValues = lookupByLocalName(relativeComponent.fieldName(), entries);
+ if (substituteValues == null)
+ throw new IllegalArgumentException("Could not resolve local substitution '" +
+ relativeComponent.fieldName() + "' in variant " +
+ variant);
+ String resolved = substituteValues.valueFor(variant).toString();
+ resolvedComponents.add(new SubstituteString.StringComponent(resolved));
+ }
+ else {
+ resolvedComponents.add(component);
+ }
+ }
+ return (VALUE)new SubstituteString(resolvedComponents, substitute.stringValue());
+ }
+ }
+ return value;
+ }
+
+ private DimensionalValue.Builder<VALUE> lookupByLocalName(String localName,
+ Map<CompoundName, DimensionalValue.Builder<VALUE>> entries) {
+ for (var entry : entries.entrySet()) {
+ if (entry.getKey().last().equals(localName))
+ return entry.getValue();
+ }
+ return null;
+ }
+
}
+
}
+
}