summaryrefslogtreecommitdiffstats
path: root/config-model
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@oath.com>2018-02-03 17:17:47 +0100
committerJon Bratseth <bratseth@oath.com>2018-02-03 17:17:47 +0100
commitab84971140f1782c0c7a693dd45c324766094c7c (patch)
tree84816cf83b25b11e527a52e1402fe94fb37ef7f4 /config-model
parentb729ea38f6ed4e23975c54321132c59627a4c6e1 (diff)
Unify canonicalization
Diffstat (limited to 'config-model')
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/FeatureNames.java120
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java1
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/TypeMapContext.java32
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java2
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/FeatureNamesTestCase.java58
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java2
6 files changed, 212 insertions, 3 deletions
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/FeatureNames.java b/config-model/src/main/java/com/yahoo/searchdefinition/FeatureNames.java
new file mode 100644
index 00000000000..dd03cb8b2a7
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/FeatureNames.java
@@ -0,0 +1,120 @@
+/*
+ * // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ *
+ *
+ */
+package com.yahoo.searchdefinition;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * Utility methods for query, document and constant rank feature names
+ *
+ * @author bratseth
+ */
+public class FeatureNames {
+
+ private static final Pattern identifierRegexp = Pattern.compile("[A-Za-z0-9_][A-Za-z0-9_-]*");
+
+ /**
+ * <p>Returns the given query, document or constant feature in canonical form.
+ * A feature name consists of a feature type name (query, attribute or constant),
+ * followed by one argument enclosed in quotes.
+ * The argument may be an identifier or any string single or double quoted.</p>
+ *
+ * <p>Argument string values may not contain comma, single quote nor double quote characters.</p>
+ *
+ * <p><i>The canonical form use no quotes for arguments which are identifiers, and double quotes otherwise.</i></p>
+ *
+ * <p>Note that the above definition is not true for features in general, which accept any ranking expression
+ * as argument.</p>
+ *
+ * @throws IllegalArgumentException if the feature name is not valid
+ */
+ // Note that this implementation is more general than what is described above:
+ // It accepts any number of arguments and an optional output
+ public static String canonicalize(String feature) {
+ return canonicalizeIfValid(feature).orElseThrow(() ->
+ new IllegalArgumentException("A feature name must be on the form query(name), attribute(name) or " +
+ "constant(name), but was '" + feature + "'"
+ ));
+ }
+
+ /**
+ * Canonicalizes the given argument as in canonicalize, but returns empty instead of throwing an exception if
+ * the argument is not a valid feature
+ */
+ public static Optional<String> canonicalizeIfValid(String feature) {
+ int startParenthesis = feature.indexOf('(');
+ if (startParenthesis < 0)
+ return Optional.empty();
+ int endParenthesis = feature.lastIndexOf(')');
+ String featureType = feature.substring(0, startParenthesis);
+ if ( ! ( featureType.equals("query") || featureType.equals("attribute") || featureType.equals("constant")))
+ return Optional.empty();
+ if (startParenthesis < 1) return Optional.of(feature); // No arguments
+ if (endParenthesis < startParenthesis)
+ return Optional.empty();
+ String argumentString = feature.substring(startParenthesis + 1, endParenthesis);
+ List<String> canonicalizedArguments =
+ Arrays.stream(argumentString.split(","))
+ .map(FeatureNames::canonicalizeArgument)
+ .collect(Collectors.toList());
+ return Optional.of(featureType + "(" +
+ canonicalizedArguments.stream().collect(Collectors.joining(",")) +
+ feature.substring(endParenthesis));
+ }
+
+ /** Canomicalizes a single argument */
+ private static String canonicalizeArgument(String argument) {
+ if (argument.startsWith("'")) {
+ if ( ! argument.endsWith("'"))
+ throw new IllegalArgumentException("Feature arguments starting by a single quote " +
+ "must end by a single quote, but was \"" + argument + "\"");
+ argument = argument.substring(1, argument.length() - 1);
+ }
+ if (argument.startsWith("\"")) {
+ if ( ! argument.endsWith("\""))
+ throw new IllegalArgumentException("Feature arguments starting by a double quote " +
+ "must end by a double quote, but was '" + argument + "'");
+ argument = argument.substring(1, argument.length() - 1);
+ }
+ if (identifierRegexp.matcher(argument).matches())
+ return argument;
+ else
+ return "\"" + argument + "\"";
+ }
+
+ public static String asConstantFeature(String constantName) {
+ return canonicalize("constant(\"" + constantName + "\")");
+ }
+
+ public static String asAttributeFeature(String attributeName) {
+ return canonicalize("attribute(\"" + attributeName + "\")");
+ }
+
+ public static String asQueryFeature(String propertyName) {
+ return canonicalize("query(\"" + propertyName + "\")");
+ }
+
+ /**
+ * Returns the single argument of the given feature name, without any quotes,
+ * or empty if it is not a valid query, attribute or constant feature name
+ */
+ public static Optional<String> argumentOf(String feature) {
+ return canonicalizeIfValid(feature).map(f -> {
+ int startParenthesis = f.indexOf("(");
+ int endParenthesis = f.indexOf(")");
+ String possiblyQuotedArgument = f.substring(startParenthesis + 1, endParenthesis);
+ if (possiblyQuotedArgument.startsWith("\""))
+ return possiblyQuotedArgument.substring(1, possiblyQuotedArgument.length() - 1);
+ else
+ return possiblyQuotedArgument;
+ });
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java
index b185680d41c..135d983c1ca 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java
@@ -16,7 +16,6 @@ import com.yahoo.searchdefinition.parser.ParseException;
import com.yahoo.searchlib.rankingexpression.ExpressionFunction;
import com.yahoo.searchlib.rankingexpression.FeatureList;
import com.yahoo.searchlib.rankingexpression.RankingExpression;
-import com.yahoo.searchlib.rankingexpression.evaluation.FeatureNames;
import com.yahoo.searchlib.rankingexpression.evaluation.TensorValue;
import com.yahoo.searchlib.rankingexpression.evaluation.TypeMapContext;
import com.yahoo.searchlib.rankingexpression.evaluation.Value;
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/TypeMapContext.java b/config-model/src/main/java/com/yahoo/searchdefinition/TypeMapContext.java
new file mode 100644
index 00000000000..40e9db1413f
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/TypeMapContext.java
@@ -0,0 +1,32 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.tensor.TensorType;
+import com.yahoo.tensor.evaluation.TypeContext;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A context which only contains type information.
+ *
+ * @author bratseth
+ */
+public class TypeMapContext implements TypeContext {
+
+ private final Map<String, TensorType> featureTypes = new HashMap<>();
+
+ public void setType(String name, TensorType type) {
+ featureTypes.put(FeatureNames.canonicalize(name), type);
+ }
+
+ @Override
+ public TensorType getType(String name) {
+ return featureTypes.get(FeatureNames.canonicalize(name));
+ }
+
+ /** Returns an unmodifiable map of the bindings in this */
+ public Map<String, TensorType> bindings() { return Collections.unmodifiableMap(featureTypes); }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java
index 3ee64094274..c121cdfdd9a 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java
@@ -6,6 +6,7 @@ import com.yahoo.search.query.profile.types.FieldDescription;
import com.yahoo.search.query.profile.types.FieldType;
import com.yahoo.search.query.profile.types.QueryProfileType;
import com.yahoo.search.query.profile.types.TensorFieldType;
+import com.yahoo.searchdefinition.FeatureNames;
import com.yahoo.searchdefinition.RankProfile;
import com.yahoo.searchdefinition.RankProfileRegistry;
import com.yahoo.searchdefinition.Search;
@@ -14,7 +15,6 @@ import com.yahoo.searchdefinition.document.ImportedField;
import com.yahoo.searchdefinition.document.ImportedFields;
import com.yahoo.searchdefinition.document.SDField;
import com.yahoo.searchdefinition.processing.Processor;
-import com.yahoo.searchlib.rankingexpression.evaluation.FeatureNames;
import com.yahoo.vespa.model.container.search.QueryProfiles;
import java.util.Map;
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/FeatureNamesTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/FeatureNamesTestCase.java
new file mode 100644
index 00000000000..1f60ad870ec
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/FeatureNamesTestCase.java
@@ -0,0 +1,58 @@
+/*
+ * // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ *
+ *
+ */
+package com.yahoo.searchdefinition;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Tests rank feature names.
+ *
+ * @author bratseth
+ */
+public class FeatureNamesTestCase {
+
+ @Test
+ public void testCanonicalization() {
+ assertFalse(FeatureNames.canonicalizeIfValid("foo").isPresent());
+ assertEquals("query(bar)", FeatureNames.canonicalize("query(bar)"));
+ assertEquals("query(bar)", FeatureNames.canonicalize("query('bar')"));
+ assertEquals("constant(bar)", FeatureNames.canonicalize("constant(\"bar\")"));
+ assertEquals("query(\"ba.r\")", FeatureNames.canonicalize("query(ba.r)"));
+ assertEquals("query(\"ba.r\")", FeatureNames.canonicalize("query('ba.r')"));
+ assertEquals("attribute(\"ba.r\")", FeatureNames.canonicalize("attribute(\"ba.r\")"));
+ }
+
+ @Test
+ public void testArgument() {
+ assertFalse(FeatureNames.argumentOf("foo(bar)").isPresent());
+ assertFalse(FeatureNames.argumentOf("foo(bar.baz)").isPresent());
+ assertEquals("bar", FeatureNames.argumentOf("query(bar)").get());
+ assertEquals("bar.baz", FeatureNames.argumentOf("query(bar.baz)").get());
+ assertEquals("bar", FeatureNames.argumentOf("attribute(bar)").get());
+ assertEquals("bar.baz", FeatureNames.argumentOf("attribute(bar.baz)").get());
+ assertEquals("bar", FeatureNames.argumentOf("constant(bar)").get());
+ assertEquals("bar.baz", FeatureNames.argumentOf("constant(bar.baz)").get());
+ }
+
+ @Test
+ public void testConstantFeature() {
+ assertEquals("constant(\"foo/bar\")", FeatureNames.asConstantFeature("foo/bar"));
+ }
+
+ @Test
+ public void testAttributeFeature() {
+ assertEquals("attribute(foo)", FeatureNames.asAttributeFeature("foo"));
+ }
+
+ @Test
+ public void testQueryFeature() {
+ assertEquals("query(\"foo.bar\")", FeatureNames.asQueryFeature("foo.bar"));
+ }
+
+}
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java
index 6458f3b9a6d..28e1f310078 100644
--- a/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/processing/TensorTransformTestCase.java
@@ -100,7 +100,7 @@ public class TensorTransformTestCase extends SearchDefinitionTestCase {
String rankProperty = rankPropertyExpression.getFirst();
if (rankProperty.equals("rankingExpression(firstphase).rankingScript")) {
String rankExpression = censorBindingHash(rankPropertyExpression.getSecond().replace(" ",""));
- System.out.println("--> " + rankExpression);
+ System.out.println("expression is --> " + rankExpression);
return rankExpression.equals(transformedExpression);
}
}