summaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/search/query/profile
diff options
context:
space:
mode:
Diffstat (limited to 'container-search/src/main/java/com/yahoo/search/query/profile')
-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
8 files changed, 199 insertions, 97 deletions
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;
+ }
+
}
+
}
+
}