summaryrefslogtreecommitdiffstats
path: root/config-model/src/main/java/com/yahoo/searchdefinition
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /config-model/src/main/java/com/yahoo/searchdefinition
Publish
Diffstat (limited to 'config-model/src/main/java/com/yahoo/searchdefinition')
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/ConstantTensorTransformer.java74
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/DefaultRankProfile.java140
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/DocumentModelBuilder.java415
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplier.java31
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForSearch.java21
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForStructs.java48
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/FieldSets.java65
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/Index.java203
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/MacroInliner.java39
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java968
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/RankProfileRegistry.java90
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/SDDocumentTypeOrderer.java126
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/Search.java555
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java429
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/UnprocessingSearchBuilder.java40
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/UnproperSearch.java31
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/UnrankedRankProfile.java28
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java157
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/Derived.java136
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java184
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/Deriver.java82
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/Exportable.java26
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/FieldRankSettings.java79
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/FieldResultTransform.java54
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/Index.java64
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java559
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java231
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexingScript.java146
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/Juniperrc.java61
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeRankTypeDefinition.java44
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeRankTypeDefinitionSet.java93
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeTable.java72
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java62
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/RawRankProfile.java374
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/SearchOrderer.java105
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/Summaries.java37
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClass.java144
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClassField.java110
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryMap.java111
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/VsmFields.java275
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/VsmSummary.java107
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/package-info.java5
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/IndexStructureValidator.java50
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validation.java12
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validator.java23
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java320
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/BooleanIndexDefinition.java73
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/FieldSet.java42
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/Matching.java151
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/NormalizeLevel.java87
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/RankType.java45
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/Ranking.java68
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/SDDocumentType.java316
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java770
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/Sorting.java64
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/Stemming.java72
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDDocumentType.java13
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDField.java19
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/TypedKey.java20
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/annotation/SDAnnotationType.java35
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/annotation/TemporaryAnnotationReferenceDataType.java26
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/AliasOperation.java40
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/AttributeOperation.java153
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/BodyOperation.java14
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/BoldingOperation.java24
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperation.java11
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperationContainer.java13
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/HeaderOperation.java14
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IdOperation.java33
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java114
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingOperation.java53
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingRewriteOperation.java12
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/MatchOperation.java53
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/NormalizingOperation.java32
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/QueryCommandOperation.java23
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/RankOperation.java35
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/RankTypeOperation.java41
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SortingOperation.java91
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/StemmingOperation.java24
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/StructFieldOperation.java50
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldLongOperation.java81
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldOperation.java44
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldShortOperation.java30
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryToOperation.java39
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/WeightOperation.java23
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/WeightedSetOperation.java63
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/parser/SimpleCharStream.java16
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/AddExtraFieldsToDocument.java78
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/AttributeProperties.java70
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/AttributesImplicitWord.java47
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/Bolding.java46
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/BuiltInFieldSets.java49
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/CreatePositionZCurve.java201
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/DeprecateAttributePrefetch.java31
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/DisallowComplexMapAndWsetKeyTypes.java43
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/DiversitySettingsValidator.java58
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/ExactMatch.java98
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/FieldSetValidity.java94
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/FilterFieldNames.java70
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/ImplicitSummaries.java222
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/ImplicitSummaryFields.java38
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexFieldNames.java43
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexSettingsNonFieldNames.java47
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexTo2FieldSet.java33
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingInputs.java110
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingOutputs.java141
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingValidation.java149
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingValues.java69
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/IntegerIndex2Attribute.java86
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/LiteralBoost.java79
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/MakeAliases.java60
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/MakeDefaultSummaryTheSuperSet.java47
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchConsistency.java67
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchPhaseSettingsValidator.java92
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/MultifieldIndexHarmonizer.java82
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/NGramMatch.java76
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/OptimizeIlscript.java33
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/PredicateProcessor.java138
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java84
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/Processor.java131
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/ReservedDocumentNames.java37
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/SearchMustHaveDocument.java28
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/SetLanguage.java55
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/SetRankTypeEmptyOnFilters.java30
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/SortingSettings.java40
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/StringSettingsOnNonStringFields.java40
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryConsistency.java102
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryDynamicStructsArrays.java45
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryFieldsMustHaveValidSource.java73
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryNamesFieldCollisions.java57
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/TagType.java45
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java63
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/TextMatch.java122
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/TypedTransformProvider.java61
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/UriHack.java67
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/UrlFieldValidator.java32
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/ValidateFieldTypes.java69
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/ValidateFieldWithIndexSettingsCreatesIndex.java42
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/WordMatch.java38
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/IndexCommandResolver.java62
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/MultiFieldResolver.java33
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java85
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankTypeResolver.java46
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/StemmingResolver.java48
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/package-info.java14
145 files changed, 13999 insertions, 0 deletions
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/ConstantTensorTransformer.java b/config-model/src/main/java/com/yahoo/searchdefinition/ConstantTensorTransformer.java
new file mode 100644
index 00000000000..98eb0a4b77c
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/ConstantTensorTransformer.java
@@ -0,0 +1,74 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.searchlib.rankingexpression.evaluation.TensorValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.NameNode;
+import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode;
+import com.yahoo.searchlib.rankingexpression.transform.ExpressionTransformer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Transforms named references to constant tensors with the rank feature 'constant'.
+ *
+ * @author <a href="mailto:geirst@yahoo-inc.com">Geir Storli</a>
+ */
+class ConstantTensorTransformer extends ExpressionTransformer {
+
+ private final Map<String, Value> constants;
+ private final Map<String, String> rankPropertiesOutput;
+
+ public ConstantTensorTransformer(Map<String, Value> constants,
+ Map<String, String> rankPropertiesOutput) {
+ this.constants = constants;
+ this.rankPropertiesOutput = rankPropertiesOutput;
+ }
+
+ @Override
+ public ExpressionNode transform(ExpressionNode node) {
+ if (node instanceof ReferenceNode) {
+ return transformFeature((ReferenceNode) node);
+ } else if (node instanceof CompositeNode) {
+ return transformChildren((CompositeNode) node);
+ } else {
+ return node;
+ }
+ }
+
+ private ExpressionNode transformFeature(ReferenceNode node) {
+ if (!node.getArguments().isEmpty()) {
+ return transformArguments(node);
+ } else {
+ return transformConstantReference(node);
+ }
+ }
+
+ private ExpressionNode transformArguments(ReferenceNode node) {
+ List<ExpressionNode> arguments = node.getArguments().expressions();
+ List<ExpressionNode> transformedArguments = new ArrayList<>(arguments.size());
+ for (ExpressionNode argument : arguments) {
+ transformedArguments.add(transform(argument));
+ }
+ return node.setArguments(transformedArguments);
+ }
+
+ private ExpressionNode transformConstantReference(ReferenceNode node) {
+ Value value = constants.get(node.getName());
+ if (value == null || !(value instanceof TensorValue)) {
+ return node;
+ }
+ TensorValue tensorValue = (TensorValue)value;
+ String featureName = "constant(" + node.getName() + ")";
+ String tensorType = (tensorValue.getType().isPresent() ? tensorValue.getType().get().toString() : "tensor");
+ rankPropertiesOutput.put(featureName + ".value", tensorValue.toString());
+ rankPropertiesOutput.put(featureName + ".type", tensorType);
+ return new ReferenceNode("constant", Arrays.asList(new NameNode(node.getName())), null);
+ }
+
+} \ No newline at end of file
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/DefaultRankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/DefaultRankProfile.java
new file mode 100644
index 00000000000..c0dcf27c88d
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/DefaultRankProfile.java
@@ -0,0 +1,140 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.searchdefinition.document.SDField;
+
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * The rank profile containing default settings. This is derived from the fields
+ * whenever this is accessed.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class DefaultRankProfile extends RankProfile {
+
+ /**
+ * Creates a new rank profile
+ *
+ * @param rankProfileRegistry The {@link com.yahoo.searchdefinition.RankProfileRegistry} to use for storing and looking up rank profiles.
+ */
+ public DefaultRankProfile(Search search, RankProfileRegistry rankProfileRegistry) {
+ super("default", search, rankProfileRegistry);
+ }
+
+ /**
+ * Does nothing, the default rank profile can not inherit anything
+ */
+ // TODO: Why not? If that's the case, then fail attempts at it
+ public void setInherited(String inheritedName) {
+ }
+
+ /**
+ * Returns null, the default rank profile can not inherit anything
+ */
+ public String getInheritedName() {
+ return null;
+ }
+
+ /**
+ * Returns the rank boost value of the given field
+ */
+ public RankSetting getRankSetting(String fieldOrIndex,RankSetting.Type type) {
+ RankSetting setting=super.getRankSetting(fieldOrIndex,type);
+ if (setting!=null) return setting;
+
+ SDField field=getSearch().getField(fieldOrIndex);
+ if (field!=null) {
+ setting=toRankSetting(field,type);
+ if (setting!=null)
+ return setting;
+ }
+
+ Index index=getSearch().getIndex(fieldOrIndex);
+ if (index!=null) {
+ setting=toRankSetting(index,type);
+ if (setting!=null)
+ return setting;
+ }
+
+ return null;
+ }
+
+ private RankSetting toRankSetting(SDField field,RankSetting.Type type) {
+ if (type.equals(RankSetting.Type.WEIGHT) && field.getWeight()>0 && field.getWeight()!=100)
+ return new RankSetting(field.getName(),type,field.getWeight());
+ if (type.equals(RankSetting.Type.RANKTYPE))
+ return new RankSetting(field.getName(),type,field.getRankType());
+ if (type.equals(RankSetting.Type.LITERALBOOST) && field.getLiteralBoost()>0)
+ return new RankSetting(field.getName(),type,field.getLiteralBoost());
+
+ // Index level setting really
+ if (type.equals(RankSetting.Type.PREFERBITVECTOR) && field.getRanking().isFilter()) {
+ return new RankSetting(field.getName(), type, true);
+ }
+
+ return null;
+ }
+
+ private RankSetting toRankSetting(Index index, RankSetting.Type type) {
+ /* TODO: Add support for indexes by adding a ranking object to the index
+ if (type.equals(RankSetting.Type.PREFERBITVECTOR) && index.isPreferBitVector()) {
+ return new RankSetting(index.getName(), type, new Boolean(true));
+ }
+ */
+ return null;
+ }
+
+ /**
+ * Returns the names of the fields which have a rank boost setting
+ * explicitly in this profile or in fields
+ */
+ public Set<RankSetting> rankSettings() {
+ Set<RankSetting> settings=new LinkedHashSet<>(20);
+ settings.addAll(this.rankSettings);
+ for (SDField field : getSearch().allFieldsList() ) {
+ addSetting(field,RankSetting.Type.WEIGHT,settings);
+ addSetting(field,RankSetting.Type.RANKTYPE,settings);
+ addSetting(field,RankSetting.Type.LITERALBOOST,settings);
+ addSetting(field,RankSetting.Type.PREFERBITVECTOR,settings);
+ }
+
+ // Foer settings that really pertains to indexes do the explicit indexes too
+ for (Index index : getSearch().getExplicitIndices()) {
+ addSetting(index,RankSetting.Type.PREFERBITVECTOR,settings);
+ }
+ return settings;
+ }
+
+ private void addSetting(SDField field,RankSetting.Type type,Set<RankSetting> settings) {
+ if (type.isIndexLevel()) {
+ addIndexSettings(field,type,settings);
+ }
+ else {
+ RankSetting setting=toRankSetting(field,type);
+ if (setting==null) return;
+ settings.add(setting);
+ }
+ }
+
+ private void addIndexSettings(SDField field,RankSetting.Type type,Set<RankSetting> settings) {
+ for (Iterator i = field.getFieldNameAsIterator(); i.hasNext(); ) {
+ String indexName=(String)i.next();
+ Index explicitIndex=field.getIndex(indexName);
+
+ // TODO: Make a ranking object in the index override the field level ranking object
+ if (type.equals(RankSetting.Type.PREFERBITVECTOR) && field.getRanking().isFilter()) {
+ settings.add(new RankSetting(indexName, type, true));
+ }
+ }
+ }
+
+ private void addSetting(Index index,RankSetting.Type type,Set<RankSetting> settings) {
+ RankSetting setting=toRankSetting(index,type);
+ if (setting==null) return;
+ settings.add(setting);
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/DocumentModelBuilder.java b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentModelBuilder.java
new file mode 100644
index 00000000000..4fd7048159e
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/DocumentModelBuilder.java
@@ -0,0 +1,415 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.document.*;
+import com.yahoo.document.annotation.AnnotationReferenceDataType;
+import com.yahoo.document.annotation.AnnotationType;
+import com.yahoo.documentmodel.DataTypeCollection;
+import com.yahoo.documentmodel.NewDocumentType;
+import com.yahoo.documentmodel.VespaDocumentType;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.document.annotation.SDAnnotationType;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.annotation.TemporaryAnnotationReferenceDataType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.documentmodel.DocumentModel;
+import com.yahoo.vespa.documentmodel.FieldView;
+import com.yahoo.vespa.documentmodel.SearchDef;
+import com.yahoo.vespa.documentmodel.SearchField;
+
+import java.util.*;
+
+/**
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public class DocumentModelBuilder {
+
+ public static class RetryLaterException extends IllegalArgumentException {
+ public RetryLaterException(String message) {
+ super(message);
+ }
+ }
+ private DocumentModel model;
+ private final Map<NewDocumentType, List<SDDocumentType>> scratchInheritsMap = new HashMap<>();
+ public DocumentModelBuilder(DocumentModel model) {
+ this.model = model;
+ model.getDocumentManager().add(VespaDocumentType.INSTANCE);
+ }
+ public boolean valid() {
+ return scratchInheritsMap.isEmpty();
+ }
+ public void addToModel(Collection<Search> searchList) {
+ List<SDDocumentType> docList = new LinkedList<>();
+ for (Search search : searchList) {
+ docList.add(search.getDocument());
+ }
+ docList = sortDocumentTypes(docList);
+ addDocumentTypes(docList);
+ for (Collection<Search> toAdd = tryAdd(searchList);
+ !toAdd.isEmpty() && (toAdd.size() < searchList.size()); toAdd = tryAdd(searchList)) {
+ searchList = toAdd;
+ }
+ }
+
+ private List<SDDocumentType> sortDocumentTypes(List<SDDocumentType> docList) {
+ Set<String> doneNames = new HashSet<>();
+ doneNames.add(SDDocumentType.VESPA_DOCUMENT.getName());
+ List<SDDocumentType> doneList = new LinkedList<>();
+ List<SDDocumentType> prevList = null;
+ List<SDDocumentType> nextList = docList;
+ while (prevList == null || nextList.size() < prevList.size()) {
+ prevList = nextList;
+ nextList = new LinkedList<>();
+ for (SDDocumentType doc : prevList) {
+ boolean isDone = true;
+ for (SDDocumentType inherited : doc.getInheritedTypes()) {
+ if (!doneNames.contains(inherited.getName())) {
+ isDone = false;
+ break;
+ }
+ }
+ if (isDone) {
+ doneNames.add(doc.getName());
+ doneList.add(doc);
+ } else {
+ nextList.add(doc);
+ }
+ }
+ }
+ if (!nextList.isEmpty()) {
+ throw new IllegalArgumentException("Could not resolve inheritance of document types " +
+ toString(prevList) + ".");
+ }
+ return doneList;
+ }
+
+ private static String toString(List<SDDocumentType> lst) {
+ StringBuilder out = new StringBuilder();
+ for (int i = 0, len = lst.size(); i < len; ++i) {
+ out.append("'").append(lst.get(i).getName()).append("'");
+ if (i < len - 2) {
+ out.append(", ");
+ } else if (i < len - 1) {
+ out.append(" and ");
+ }
+ }
+ return out.toString();
+ }
+
+ private Collection<Search> tryAdd(Collection<Search> searchList) {
+ Collection<Search> left = new ArrayList<>();
+ for (Search search : searchList) {
+ try {
+ addToModel(search);
+ } catch (RetryLaterException e) {
+ left.add(search);
+ }
+ }
+ return left;
+ }
+ public void addToModel(Search search) {
+ // Then we add the search specific stuff
+ SearchDef searchDef = new SearchDef(search.getName());
+ addSearchFields(search.extraFieldList(), searchDef);
+ for (Field f : search.getDocument().fieldSet()) {
+ addSearchField((SDField) f, searchDef);
+ }
+ for(SDField field : search.allFieldsList()) {
+ for(Attribute attribute : field.getAttributes().values()) {
+ if (!searchDef.getFields().containsKey(attribute.getName())) {
+ searchDef.add(new SearchField(new Field(attribute.getName(), field), !field.getIndices().isEmpty(), true));
+ }
+ }
+ }
+
+ for (Field f : search.getDocument().fieldSet()) {
+ addAlias((SDField) f, searchDef);
+ }
+ model.getSearchManager().add(searchDef);
+ }
+ private static void addSearchFields(Collection<SDField> fields, SearchDef searchDef) {
+ for (SDField field : fields) {
+ addSearchField(field, searchDef);
+ }
+ }
+ private static void addSearchField(SDField field, SearchDef searchDef) {
+
+ SearchField searchField =
+ new SearchField(field,
+ field.getIndices().containsKey(field.getName()) && field.getIndices().get(field.getName()).getType().equals(Index.Type.VESPA),
+ field.getAttributes().containsKey(field.getName()));
+ searchDef.add(searchField);
+
+ // Add field to views
+ addToView(field.getIndices().keySet(), searchField, searchDef);
+ }
+
+ private static void addAlias(SDField field, SearchDef searchDef) {
+ for (Map.Entry<String, String> entry : field.getAliasToName().entrySet()) {
+ searchDef.addAlias(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private static void addToView(Collection<String> views, Field field, SearchDef searchDef) {
+ for (String viewName : views) {
+ addToView(viewName, field, searchDef);
+ }
+ }
+
+ private static void addToView(String viewName, Field field, SearchDef searchDef) {
+ if (searchDef.getViews().containsKey(viewName)) {
+ searchDef.getViews().get(viewName).add(field);
+ } else {
+ if (!searchDef.getFields().containsKey(viewName)) {
+ FieldView view = new FieldView(viewName);
+ view.add(field);
+ searchDef.add(view);
+ }
+ }
+ }
+ private void addDocumentTypes(List<SDDocumentType> docList) {
+ LinkedList<NewDocumentType> lst = new LinkedList<>();
+ for (SDDocumentType doc : docList) {
+ lst.add(convert(doc));
+ model.getDocumentManager().add(lst.getLast());
+ }
+ for(NewDocumentType doc : lst) {
+ resolveTemporaries(doc.getAllTypes(), lst);
+ }
+ }
+ private static void resolveTemporaries(DataTypeCollection dtc, Collection<NewDocumentType> docs) {
+ for (DataType type : dtc.getTypes()) {
+ resolveTemporariesRecurse(type, dtc, docs);
+ }
+ }
+ private static DataType resolveTemporariesRecurse(DataType type, DataTypeCollection repo,
+ Collection<NewDocumentType> docs) {
+ if (type instanceof TemporaryStructuredDataType) {
+ NewDocumentType docType = getDocumentType(docs, type.getId());
+ if (docType != null) {
+ type = docType;
+ return type;
+ }
+ DataType real = repo.getDataType(type.getId());
+ if (real == null) {
+ throw new NullPointerException("Can not find type '" + type.toString() + "', impossible.");
+ }
+ type = real;
+ } else if (type instanceof StructDataType) {
+ StructDataType dt = (StructDataType) type;
+ for (com.yahoo.document.Field field : dt.getFields()) {
+ if (field.getDataType() != type) {
+ field.setDataType(resolveTemporariesRecurse(field.getDataType(), repo, docs));
+ }
+ }
+ } else if (type instanceof MapDataType) {
+ MapDataType t = (MapDataType) type;
+ t.setKeyType(resolveTemporariesRecurse(t.getKeyType(), repo, docs));
+ t.setValueType(resolveTemporariesRecurse(t.getValueType(), repo, docs));
+ } else if (type instanceof CollectionDataType) {
+ CollectionDataType t = (CollectionDataType) type;
+ t.setNestedType(resolveTemporariesRecurse(t.getNestedType(), repo, docs));
+ }
+ return type;
+ }
+
+ private static NewDocumentType getDocumentType(Collection<NewDocumentType> docs, int id) {
+ for (NewDocumentType doc : docs) {
+ if (doc.getId() == id) {
+ return doc;
+ }
+ }
+ return null;
+ }
+
+ private static void specialHandleAnnotationReference(NewDocumentType docType, Field field) {
+ DataType fieldType = specialHandleAnnotationReferenceRecurse(docType, field.getName(), field.getDataType());
+ if (fieldType == null) {
+ return;
+ }
+ field.setDataType(fieldType);
+ }
+
+ private static DataType specialHandleAnnotationReferenceRecurse(NewDocumentType docType, String fieldName,
+ DataType dataType)
+ {
+ if (dataType instanceof TemporaryAnnotationReferenceDataType) {
+ TemporaryAnnotationReferenceDataType refType = (TemporaryAnnotationReferenceDataType)dataType;
+ if (refType.getId() != 0) {
+ return null;
+ }
+ AnnotationType target = docType.getAnnotationType(refType.getTarget());
+ if (target == null) {
+ throw new RetryLaterException("Annotation '" + refType.getTarget() + "' in reference '" + fieldName +
+ "' does not exist.");
+ }
+ dataType = new AnnotationReferenceDataType(target);
+ addType(docType, dataType);
+ return dataType;
+ } else if (dataType instanceof MapDataType) {
+ MapDataType mapType = (MapDataType)dataType;
+ DataType valueType = specialHandleAnnotationReferenceRecurse(docType, fieldName, mapType.getValueType());
+ if (valueType == null) {
+ return null;
+ }
+ mapType = mapType.clone();
+ mapType.setValueType(valueType);
+ addType(docType, mapType);
+ return mapType;
+ } else if (dataType instanceof CollectionDataType) {
+ CollectionDataType lstType = (CollectionDataType)dataType;
+ DataType nestedType = specialHandleAnnotationReferenceRecurse(docType, fieldName, lstType.getNestedType());
+ if (nestedType == null) {
+ return null;
+ }
+ lstType = lstType.clone();
+ lstType.setNestedType(nestedType);
+ addType(docType, lstType);
+ return lstType;
+ }
+ return null;
+ }
+
+ private static StructDataType handleStruct(NewDocumentType dt, SDDocumentType type) {
+ StructDataType s = new StructDataType(type.getName());
+ for (Field f : type.getDocumentType().getHeaderType().getFieldsThisTypeOnly()) {
+ specialHandleAnnotationReference(dt, f);
+ s.addField(f);
+ }
+ for (StructDataType inherited : type.getDocumentType().getHeaderType().getInheritedTypes()) {
+ s.inherit(inherited);
+ }
+ extractNestedTypes(dt, s);
+ addType(dt, s);
+ return s;
+ }
+
+ private static StructDataType handleStruct(NewDocumentType dt, StructDataType s) {
+ for (Field f : s.getFieldsThisTypeOnly()) {
+ specialHandleAnnotationReference(dt, f);
+ }
+ extractNestedTypes(dt, s);
+ addType(dt, s);
+ return s;
+ }
+ private static boolean anyParentsHavePayLoad(SDAnnotationType sa, SDDocumentType sdoc) {
+ if (sa.getInherits() != null) {
+ AnnotationType tmp = sdoc.findAnnotation(sa.getInherits());
+ SDAnnotationType inherited = (SDAnnotationType) tmp;
+ return ((inherited.getSdDocType() != null) || anyParentsHavePayLoad(inherited, sdoc));
+ }
+ return false;
+ }
+ private NewDocumentType convert(SDDocumentType sdoc) {
+ Map<AnnotationType, String> annotationInheritance = new HashMap<>();
+ Map<StructDataType, String> structInheritance = new HashMap<>();
+ NewDocumentType dt = new NewDocumentType(new NewDocumentType.Name(sdoc.getName()),
+ sdoc.getDocumentType().getHeaderType(),
+ sdoc.getDocumentType().getBodyType(),
+ sdoc.getFieldSets());
+ for (SDDocumentType n : sdoc.getInheritedTypes()) {
+ NewDocumentType.Name name = new NewDocumentType.Name(n.getName());
+ NewDocumentType inherited = model.getDocumentManager().getDocumentType(name);
+ if (inherited != null) {
+ dt.inherit(inherited);
+ }
+ }
+ for (SDDocumentType type : sdoc.getTypes()) {
+ if (type.isStruct()) {
+ handleStruct(dt, type);
+ } else {
+ throw new IllegalArgumentException("Data type '" + sdoc.getName() + "' is not a struct => tostring='" + sdoc.toString() + "'.");
+ }
+ }
+ for (AnnotationType annotation : sdoc.getAnnotations()) {
+ dt.add(annotation);
+ }
+ for (AnnotationType annotation : sdoc.getAnnotations()) {
+ SDAnnotationType sa = (SDAnnotationType) annotation;
+ if (annotation.getInheritedTypes().isEmpty() && (sa.getInherits() != null) ) {
+ annotationInheritance.put(annotation, sa.getInherits());
+ }
+ if (annotation.getDataType() == null) {
+ if (sa.getSdDocType() != null) {
+ StructDataType s = handleStruct(dt, sa.getSdDocType());
+ annotation.setDataType(s);
+ if ((sa.getInherits() != null)) {
+ structInheritance.put(s, "annotation."+sa.getInherits());
+ }
+ } else if (sa.getInherits() != null) {
+ StructDataType s = new StructDataType("annotation."+annotation.getName());
+ if (anyParentsHavePayLoad(sa, sdoc)) {
+ annotation.setDataType(s);
+ addType(dt, s);
+ }
+ structInheritance.put(s, "annotation."+sa.getInherits());
+ }
+ }
+ }
+ for (Map.Entry<AnnotationType, String> e : annotationInheritance.entrySet()) {
+ e.getKey().inherit(dt.getAnnotationType(e.getValue()));
+ }
+ for (Map.Entry<StructDataType, String> e : structInheritance.entrySet()) {
+ StructDataType s = (StructDataType)dt.getDataType(e.getValue());
+ if (s != null) {
+ e.getKey().inherit(s);
+ }
+ }
+ handleStruct(dt, sdoc.getDocumentType().getHeaderType());
+ handleStruct(dt, sdoc.getDocumentType().getBodyType());
+
+ extractDataTypesFromFields(dt, sdoc.fieldSet());
+ return dt;
+ }
+ private static void extractDataTypesFromFields(NewDocumentType dt, Collection<Field> fields) {
+ for (Field f : fields) {
+ DataType type = f.getDataType();
+ if (testAddType(dt, type)) {
+ extractNestedTypes(dt, type);
+ addType(dt, type);
+ }
+ }
+ }
+ private static void extractNestedTypes(NewDocumentType dt, DataType type) {
+ if (type instanceof StructDataType) {
+ StructDataType tmp = (StructDataType) type;
+ extractDataTypesFromFields(dt, tmp.getFieldsThisTypeOnly());
+ } else if (type instanceof DocumentType) {
+ throw new IllegalArgumentException("Can not handle nested document definitions. In document type '" + dt.getName().toString() +
+ "', we can not define document type '" + type.toString());
+ } else if (type instanceof CollectionDataType) {
+ CollectionDataType tmp = (CollectionDataType) type;
+ extractNestedTypes(dt, tmp.getNestedType());
+ addType(dt, tmp.getNestedType());
+ } else if (type instanceof MapDataType) {
+ MapDataType tmp = (MapDataType) type;
+ extractNestedTypes(dt, tmp.getKeyType());
+ extractNestedTypes(dt, tmp.getValueType());
+ addType(dt, tmp.getKeyType());
+ addType(dt, tmp.getValueType());
+ } else if (type instanceof TemporaryAnnotationReferenceDataType) {
+ throw new IllegalArgumentException(type.toString());
+ }
+ }
+ private static boolean testAddType(NewDocumentType dt, DataType type) { return internalAddType(dt, type, true); }
+ private static boolean addType(NewDocumentType dt, DataType type) { return internalAddType(dt, type, false); }
+ private static boolean internalAddType(NewDocumentType dt, DataType type, boolean dryRun) {
+ DataType oldType = dt.getDataTypeRecursive(type.getId());
+ if (oldType == null) {
+ if ( ! dryRun) {
+ dt.add(type);
+ }
+ return true;
+ } else if ((type instanceof StructDataType) && (oldType instanceof StructDataType)) {
+ StructDataType s = (StructDataType) type;
+ StructDataType os = (StructDataType) oldType;
+ if ((os.getFieldCount() == 0) && (s.getFieldCount() > os.getFieldCount())) {
+ if ( ! dryRun) {
+ dt.replace(type);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplier.java b/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplier.java
new file mode 100644
index 00000000000..9cb3dea36a8
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplier.java
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.document.Field;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class FieldOperationApplier {
+ public void process(SDDocumentType sdoc) {
+ if (!sdoc.isStruct()) {
+ apply(sdoc);
+ }
+ }
+
+ protected void apply(SDDocumentType type) {
+ for (Field field : type.fieldSet()) {
+ apply(field);
+ }
+ }
+
+ protected void apply(Field field) {
+ if (field instanceof SDField) {
+ SDField sdField = (SDField) field;
+ sdField.applyOperations();
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForSearch.java b/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForSearch.java
new file mode 100644
index 00000000000..301d78bbaec
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForSearch.java
@@ -0,0 +1,21 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.document.Field;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class FieldOperationApplierForSearch extends FieldOperationApplier {
+ @Override
+ public void process(SDDocumentType sdoc) {
+ //Do nothing
+ }
+
+ public void process(Search search) {
+ for (Field field : search.extraFieldList()) {
+ apply(field);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForStructs.java b/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForStructs.java
new file mode 100644
index 00000000000..5bef71ac920
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/FieldOperationApplierForStructs.java
@@ -0,0 +1,48 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.document.DataType;
+import com.yahoo.document.Field;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.document.SDField;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class FieldOperationApplierForStructs extends FieldOperationApplier {
+ @Override
+ public void process(SDDocumentType sdoc) {
+ for (SDDocumentType type : sdoc.getAllTypes()) {
+ if (type.isStruct()) {
+ apply(type);
+ copyFields(type, sdoc);
+ }
+ }
+ }
+
+ private void copyFields(SDDocumentType structType, SDDocumentType sdoc) {
+ //find all fields in OTHER types that have this type:
+ List<SDDocumentType> list = new ArrayList<>();
+ list.add(sdoc);
+ list.addAll(sdoc.getTypes());
+ for (SDDocumentType anyType : list) {
+ Iterator<Field> fields = anyType.fieldIterator();
+ while (fields.hasNext()) {
+ SDField field = (SDField) fields.next();
+ DataType structUsedByField = field.getFirstStructRecursive();
+ if (structUsedByField == null) {
+ continue;
+ }
+ if (structUsedByField.getName().equals(structType.getName())) {
+ //this field is using this type!!
+ field.populateWithStructFields(sdoc, field.getName(), field.getDataType(), field.isHeader(), 0);
+ field.populateWithStructMatching(sdoc, field.getName(), field.getDataType(), field.getMatching());
+ }
+ }
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/FieldSets.java b/config-model/src/main/java/com/yahoo/searchdefinition/FieldSets.java
new file mode 100644
index 00000000000..503977a8f49
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/FieldSets.java
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.yahoo.searchdefinition.document.FieldSet;
+
+/**
+ * The field sets owned by a {@link Search}
+ * Both built in and user defined.
+ * @author vegardh
+ *
+ */
+public class FieldSets {
+
+ private final Map<String, FieldSet> userFieldSets = new LinkedHashMap<>();
+ private final Map<String, FieldSet> builtInFieldSets = new LinkedHashMap<>();
+
+ /**
+ * Adds an entry to user field sets, creating entries as needed
+ *
+ * @param setName name of a field set
+ * @param field field to add to field set
+ */
+ public void addUserFieldSetItem(String setName, String field) {
+ if (userFieldSets.get(setName) == null) {
+ // First entry in this set
+ userFieldSets.put(setName, new FieldSet(setName));
+ }
+ userFieldSets.get(setName).addFieldName(field);
+ }
+
+ /**
+ * Adds an entry to built in field sets, creating entries as needed
+ *
+ * @param setName name of a field set
+ * @param field field to add to field set
+ */
+ public void addBuiltInFieldSetItem(String setName, String field) {
+ if (builtInFieldSets.get(setName) == null) {
+ // First entry in this set
+ builtInFieldSets.put(setName, new FieldSet(setName));
+ }
+ builtInFieldSets.get(setName).addFieldName(field);
+ }
+
+ /**
+ * The built in field sets, unmodifiable
+ * @return built in field sets
+ */
+ public Map<String, FieldSet> builtInFieldSets() {
+ return Collections.unmodifiableMap(builtInFieldSets);
+ }
+
+ /**
+ * The user defined field sets, unmodifiable
+ * @return user field sets
+ */
+ public Map<String, FieldSet> userFieldSets() {
+ return Collections.unmodifiableMap(userFieldSets);
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/Index.java b/config-model/src/main/java/com/yahoo/searchdefinition/Index.java
new file mode 100644
index 00000000000..9ed7d8677b9
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/Index.java
@@ -0,0 +1,203 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.searchdefinition.document.BooleanIndexDefinition;
+import com.yahoo.searchdefinition.document.RankType;
+import com.yahoo.searchdefinition.document.Stemming;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * An index definition in a search definition.
+ * Two indices are equal if they have the same name and the same settings, except
+ * alias settings (which are excluded).
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class Index implements Cloneable, Serializable {
+
+ public static enum Type {
+ VESPA("vespa");
+ private String name;
+ private Type(String name) { this.name = name; }
+ public String getName() { return name; }
+
+ }
+
+ // Please see hashCode, equals and copy when adding attributes to this
+
+ /** The search definition-unique name of this index */
+ private String name;
+
+ /** The rank type of this index */
+ private RankType rankType=null;
+
+ /** Whether this index supports prefix search */
+ private boolean prefix;
+
+ /** The list of aliases (Strings) to this index name */
+ private Set<String> aliases=new java.util.LinkedHashSet<>(1);
+
+ /**
+ * The stemming setting of this field, or null to use the default.
+ * Default is determined by the owning search definition.
+ */
+ private Stemming stemming=null;
+
+ /** Whether the content of this index is normalized */
+ private boolean normalized=true;
+
+ /** The set of all searchable fields which should be searched with this index. May not null */
+ public Set<String> matchGroup=new LinkedHashSet<>();
+
+ private Type type = Type.VESPA;
+
+ /** The boolean index definition, if set */
+ private BooleanIndexDefinition boolIndex;
+
+ public Index(String name) {
+ this(name, false);
+ }
+
+ public Index(String name, boolean prefix) {
+ this.name=name;
+ this.prefix=prefix;
+ }
+
+ public void setName(String name) { this.name=name; }
+
+ public String getName() { return name; }
+
+ /** Sets the rank type of this field */
+ public void setRankType(RankType rankType) { this.rankType=rankType; }
+
+ /** Returns the rank type of this field, or null if nothing is set */
+ public RankType getRankType() { return rankType; }
+
+ /** Return the stemming setting of this index, may be null */
+ public Stemming getStemming() { return stemming; }
+
+ /**
+ * Returns the (unmodifiable) set of searchable fields which should be searched
+ * when this index is searched. This is useful to specify that some attributes should be
+ * searched as well when an index is searched.
+ * This set is either empty, or if set contains both the name of this index, and some other
+ * indexes.
+ */
+ public Set<String> getMatchGroup() { return Collections.unmodifiableSet(matchGroup); }
+
+ /** Adds a searchable field name to be searched when this index is searched */
+ public void addToMatchGroup(String name) {
+ if (name.equals(this.name)) return;
+ if (matchGroup.size()==0) matchGroup.add(this.name);
+ matchGroup.add(name);
+ }
+
+ /**
+ * Whether this field should be stemmed in this search definition,
+ * this is never null
+ */
+ public Stemming getStemming(Search search) {
+ if (stemming!=null)
+ return stemming;
+ else
+ return search.getStemming();
+ }
+
+ /**
+ * Sets how this field should be stemmed, or set to null to use the default.
+ */
+ public void setStemming(Stemming stemming) { this.stemming=stemming; }
+
+ /** Returns whether this index supports prefix search, default is false */
+ public boolean isPrefix() { return prefix; }
+
+ /** Sets whether this index supports prefix search */
+ public void setPrefix(boolean prefix) { this.prefix=prefix; }
+
+ /** Adds an alias to this index name */
+ public void addAlias(String alias) {
+ aliases.add(alias);
+ }
+
+ /** Returns a read-only iterator of the aliases (Strings) to this index name */
+ public Iterator<String> aliasIterator() {
+ return Collections.unmodifiableSet(aliases).iterator();
+ }
+
+ public int hashCode() {
+ return name.hashCode() + ( prefix ? 17 : 0 );
+ }
+
+ public boolean equals(Object object) {
+ if ( ! (object instanceof Index)) return false;
+
+ Index other=(Index)object;
+ return
+ this.name.equals(other.name) &&
+ this.prefix==other.prefix &&
+ this.stemming==other.stemming &&
+ this.normalized==other.normalized;
+ }
+
+ public String toString() {
+ String rankTypeName=rankType==null ? "(none)" : rankType.name();
+ return
+ "index '" + name +
+ "' [ranktype: " + rankTypeName +
+ ", prefix: " + prefix + "]";
+ }
+
+ /** Makes a deep copy of this index */
+ public Object clone() {
+ try {
+ Index copy=(Index)super.clone();
+ copy.aliases=new LinkedHashSet<>(this.aliases);
+ return copy;
+ }
+ catch (CloneNotSupportedException e) {
+ throw new RuntimeException("Programming error",e);
+ }
+ }
+
+ public Index copy() {
+ return (Index)clone();
+ }
+
+ /**
+ * The index engine type
+ * @return the type
+ */
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * Sets the index engine type
+ * @param type a index engine type
+ */
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+ /**
+ * The boolean index definition
+ * @return the boolean index definition
+ */
+ public BooleanIndexDefinition getBooleanIndexDefiniton() {
+ return boolIndex;
+ }
+
+ /**
+ * Sets the boolean index definition
+ * @param def boolean index definition
+ */
+ public void setBooleanIndexDefiniton(BooleanIndexDefinition def) {
+ boolIndex = def;
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/MacroInliner.java b/config-model/src/main/java/com/yahoo/searchdefinition/MacroInliner.java
new file mode 100644
index 00000000000..a248ca30a8e
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/MacroInliner.java
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode;
+import com.yahoo.searchlib.rankingexpression.transform.ExpressionTransformer;
+
+import java.util.Map;
+
+/**
+ * Inlines macros in ranking expressions
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+class MacroInliner extends ExpressionTransformer {
+
+ private final Map<String, RankProfile.Macro> macros;
+
+ public MacroInliner(Map<String, RankProfile.Macro> macros) {
+ this.macros = macros;
+ }
+
+ @Override
+ public ExpressionNode transform(ExpressionNode node) {
+ if (node instanceof ReferenceNode)
+ return transformFeatureNode((ReferenceNode)node);
+ if (node instanceof CompositeNode)
+ return transformChildren((CompositeNode)node);
+ return node;
+ }
+
+ private ExpressionNode transformFeatureNode(ReferenceNode feature) {
+ RankProfile.Macro macro = macros.get(feature.getName());
+ if (macro == null) return feature;
+ return transform(macro.getRankingExpression().getRoot()); // inline recursively and return
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java
new file mode 100644
index 00000000000..3e943377611
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java
@@ -0,0 +1,968 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.search.query.ranking.Diversity;
+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.TensorValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode;
+import com.yahoo.searchlib.rankingexpression.transform.ConstantDereferencer;
+import com.yahoo.searchlib.rankingexpression.transform.Simplifier;
+import com.yahoo.config.application.api.ApplicationPackage;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Represents a rank profile - a named set of ranking settings
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class RankProfile implements Serializable, Cloneable {
+
+ /** The search definition-unique name of this rank profile */
+ private final String name;
+
+ /** The search definition owning this profile, or null if none */
+ private Search search=null;
+
+ /** The name of the rank profile inherited by this */
+ private String inheritedName = null;
+
+ /** The match settings of this profile */
+ protected MatchPhaseSettings matchPhaseSettings = null;
+
+ /** The rank settings of this profile */
+ protected Set<RankSetting> rankSettings = new java.util.LinkedHashSet<>();
+
+ /** The ranking expression to be used for first phase */
+ private RankingExpression firstPhaseRanking= null;
+
+ /** The ranking expression to be used for second phase */
+ private RankingExpression secondPhaseRanking = null;
+
+ /** Number of hits to be reranked in second phase, -1 means use default */
+ private int rerankCount = -1;
+
+ /** Mysterious attribute */
+ private int keepRankCount = -1;
+
+ private int numThreadsPerSearch = -1;
+ private int numSearchPartitions = -1;
+
+ private double termwiseLimit = 1.0;
+
+ /** The drop limit used to drop hits with rank score less than or equal to this value */
+ private double rankScoreDropLimit = -Double.MAX_VALUE;
+
+ private Set<ReferenceNode> summaryFeatures;
+
+ private Set<ReferenceNode> rankFeatures;
+
+ /** The properties of this - a multimap */
+ private Map<String, List<RankProperty>> rankProperties = new LinkedHashMap<>();
+
+ private Boolean ignoreDefaultRankFeatures=null;
+
+ private String secondPhaseRankingString=null;
+
+ private String firstPhaseRankingString=null;
+
+ private Map<String, Macro> macros= new LinkedHashMap<>();
+
+ private Set<String> filterFields = new HashSet<>();
+
+ private final RankProfileRegistry rankProfileRegistry;
+
+ /** Constants in ranking expressions */
+ private Map<String, Value> constants = new HashMap<>();
+
+ private final TypeSettings attributeTypes = new TypeSettings();
+
+ private final TypeSettings queryFeatureTypes = new TypeSettings();
+
+ /**
+ * Creates a new rank profile
+ *
+ * @param name the name of the new profile
+ * @param search the search definition owning this profile
+ * @param rankProfileRegistry The {@link com.yahoo.searchdefinition.RankProfileRegistry} to use for storing and looking up rank profiles.
+ */
+ public RankProfile(String name, Search search, RankProfileRegistry rankProfileRegistry) {
+ this.name = name;
+ this.search = search;
+ this.rankProfileRegistry = rankProfileRegistry;
+ }
+
+ public String getName() { return name; }
+
+ /**
+ * Returns the search definition owning this, or null if none
+ *
+ * @return The search definition.
+ */
+ public Search getSearch() {
+ return search;
+ }
+
+ /**
+ * Sets the name of the rank profile this inherits. Both rank profiles must be present in the same search
+ * definition
+ *
+ * @param inheritedName The name of the profile that this inherits from.
+ */
+ public void setInherited(String inheritedName) {
+ this.inheritedName = inheritedName;
+ }
+
+ /**
+ * Returns the name of the profile this one inherits, or null if none is inherited
+ *
+ * @return The inherited name.
+ */
+ public String getInheritedName() {
+ return inheritedName;
+ }
+
+ /**
+ * Returns the inherited rank profile, or null if there is none
+ *
+ * @return The inherited profile.
+ */
+ public RankProfile getInherited() {
+ if (getSearch()==null) return getInheritedFromRegistry(inheritedName);
+ RankProfile inheritedInThisSearch = rankProfileRegistry.getRankProfile(search, inheritedName);
+ if (inheritedInThisSearch!=null) return inheritedInThisSearch;
+ return getInheritedFromRegistry(inheritedName);
+ }
+
+ private RankProfile getInheritedFromRegistry(String inheritedName) {
+ for (RankProfile r : rankProfileRegistry.allRankProfiles()) {
+ if (r.getName().equals(inheritedName)) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether this profile inherits (directly or indirectly) the given profile
+ *
+ * @param name The profile name to compare this to.
+ * @return Whether or not this inherits from the named profile.
+ */
+ public boolean inherits(String name) {
+ RankProfile parent = getInherited();
+ while (parent != null) {
+ if (parent.getName().equals(name))
+ return true;
+ parent = parent.getInherited();
+ }
+ return false;
+ }
+
+ /**
+ * change match settings
+ * @param settings The new match settings
+ **/
+ public void setMatchPhaseSettings(MatchPhaseSettings settings) {
+ settings.checkValid();
+ this.matchPhaseSettings = settings;
+ }
+
+ public MatchPhaseSettings getMatchPhaseSettings() {
+ MatchPhaseSettings settings = this.matchPhaseSettings;
+ if (settings != null ) return settings;
+ if (getInherited() != null) return getInherited().getMatchPhaseSettings();
+ return null;
+ }
+
+ public void addRankSetting(RankSetting rankSetting) {
+ rankSettings.add(rankSetting);
+ }
+
+ public void addRankSetting(String fieldName, RankSetting.Type type, Object value) {
+ addRankSetting(new RankSetting(fieldName, type, value));
+ }
+
+ /**
+ * Returns the a rank setting of a field, or null if there is no such rank setting in this profile
+ *
+ * @param field The field whose settings to return.
+ * @param type The type that the field is required to be.
+ * @return The rank setting found, or null.
+ */
+ public RankSetting getDeclaredRankSetting(String field, RankSetting.Type type) {
+ for (Iterator<RankSetting> i = declaredRankSettingIterator(); i.hasNext();) {
+ RankSetting setting = i.next();
+ if (setting.getFieldName().equals(field) &&
+ setting.getType().equals(type)) {
+ return setting;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a rank setting of field or index, or null if there is no such rank setting in this profile or one it
+ * inherits
+ *
+ * @param field The field whose settings to return.
+ * @param type The type that the field is required to be.
+ * @return The rank setting found, or null.
+ */
+ public RankSetting getRankSetting(String field, RankSetting.Type type) {
+ RankSetting rankSetting = getDeclaredRankSetting(field, type);
+ if (rankSetting != null) return rankSetting;
+
+ if (getInherited() != null) return getInherited().getRankSetting(field, type);
+
+ return null;
+ }
+
+ /**
+ * Returns the rank settings in this rank profile
+ *
+ * @return An iterator for the declared rank setting.
+ */
+ public Iterator<RankSetting> declaredRankSettingIterator() {
+ return Collections.unmodifiableSet(rankSettings).iterator();
+ }
+
+ /**
+ * Returns all settings in this profile or any profile it inherits
+ *
+ * @return An iterator for all rank settings of this.
+ */
+ public Iterator<RankSetting> rankSettingIterator() {
+ return rankSettings().iterator();
+ }
+
+ /**
+ * Returns a snapshot of the rank settings of this and everything it inherits
+ * Changes to the returned set will not be reflected in this rank profile.
+ */
+ public Set<RankSetting> rankSettings() {
+ Set<RankSetting> allSettings = new LinkedHashSet<>(rankSettings);
+ RankProfile parent = getInherited();
+ if (parent != null)
+ allSettings.addAll(parent.rankSettings());
+
+ return allSettings;
+ }
+
+ public void addConstant(String name, Value value) {
+ constants.put(name, value.freeze());
+ }
+
+ public void addConstantTensor(String name, TensorValue value) {
+ addConstant(name, value);
+ }
+
+ /**
+ * Returns an unmodifiable view of the constants to use in this.
+ */
+ public Map<String, Value> getConstants() {
+ if (constants.isEmpty())
+ return getInherited() != null ? getInherited().getConstants() : Collections.<String,Value>emptyMap();
+ if (getInherited() == null || getInherited().getConstants().isEmpty())
+ return Collections.unmodifiableMap(constants);
+
+ Map<String, Value> combinedConstants = new HashMap<>(getInherited().getConstants());
+ combinedConstants.putAll(constants);
+ return combinedConstants;
+ }
+
+ public void addAttributeType(String attributeName, String attributeType) {
+ attributeTypes.addType(attributeName, attributeType);
+ }
+
+ public Map<String, String> getAttributeTypes() {
+ return attributeTypes.getTypes();
+ }
+
+ public void addQueryFeatureType(String queryFeature, String queryFeatureType) {
+ queryFeatureTypes.addType(queryFeature, queryFeatureType);
+ }
+
+ public Map<String, String> getQueryFeatureTypes() {
+ return queryFeatureTypes.getTypes();
+ }
+
+ /**
+ * Returns the ranking expression to use by this. This expression must not be edited.
+ * Returns null if no expression is set.
+ */
+ public RankingExpression getFirstPhaseRanking() {
+ if (firstPhaseRanking!=null) return firstPhaseRanking;
+ if (getInherited()!=null) return getInherited().getFirstPhaseRanking();
+ return null;
+ }
+
+ public void setFirstPhaseRanking(RankingExpression rankingExpression) {
+ this.firstPhaseRanking=rankingExpression;
+ }
+
+ /**
+ * Returns the ranking expression to use by this. This expression must not be edited.
+ * Returns null if no expression is set.
+ */
+ public RankingExpression getSecondPhaseRanking() {
+ if (secondPhaseRanking!=null) return secondPhaseRanking;
+ if (getInherited()!=null) return getInherited().getSecondPhaseRanking();
+ return null;
+ }
+
+ public void setSecondPhaseRanking(RankingExpression rankingExpression) {
+ this.secondPhaseRanking=rankingExpression;
+ }
+
+ /**
+ * Called by parser to store the expression string, for delayed evaluation
+ * @param exp ranking expression for second phase
+ */
+ public void setSecondPhaseRankingString(String exp) {
+ this.secondPhaseRankingString = exp;
+ }
+
+ /**
+ * Called by parser to store the expression string, for delayed evaluation
+ * @param exp ranking expression for first phase
+ */
+ public void setFirstPhaseRankingString(String exp) {
+ this.firstPhaseRankingString = exp;
+ }
+
+ /** Returns a read-only view of the summary features to use in this profile. This is never null */
+ public Set<ReferenceNode> getSummaryFeatures() {
+ if (summaryFeatures!=null) return Collections.unmodifiableSet(summaryFeatures);
+ if (getInherited()!=null) return getInherited().getSummaryFeatures();
+ return Collections.emptySet();
+ }
+
+ public void addSummaryFeature(ReferenceNode feature) {
+ if (summaryFeatures==null)
+ summaryFeatures=new LinkedHashSet<>();
+ summaryFeatures.add(feature);
+ }
+
+ /**
+ * Adds the content of the given feature list to the internal list of summary features.
+ *
+ * @param features The features to add.
+ */
+ public void addSummaryFeatures(FeatureList features) {
+ for (ReferenceNode feature : features) {
+ addSummaryFeature(feature);
+ }
+ }
+
+ /** Returns a read-only view of the rank features to use in this profile. This is never null */
+ public Set<ReferenceNode> getRankFeatures() {
+ if (rankFeatures != null) return Collections.unmodifiableSet(rankFeatures);
+ if (getInherited() != null) return getInherited().getRankFeatures();
+ return Collections.emptySet();
+ }
+
+ public void addRankFeature(ReferenceNode feature) {
+ if (rankFeatures==null)
+ rankFeatures=new LinkedHashSet<>();
+ rankFeatures.add(feature);
+ }
+
+ /**
+ * Adds the content of the given feature list to the internal list of rank features.
+ *
+ * @param features The features to add.
+ */
+ public void addRankFeatures(FeatureList features) {
+ for (ReferenceNode feature : features) {
+ addRankFeature(feature);
+ }
+ }
+
+ /** Returns a read only flattened list view of the rank properties to use in this profile. This is never null. */
+ public List<RankProperty> getRankProperties() {
+ List<RankProperty> properties = new ArrayList<>();
+ for (List<RankProperty> propertyList : getRankPropertyMap().values()) {
+ properties.addAll(propertyList);
+ }
+ return Collections.unmodifiableList(properties);
+ }
+
+ /** Returns a read only map view of the rank properties to use in this profile. This is never null. */
+ public Map<String, List<RankProperty>> getRankPropertyMap() {
+ if (rankProperties.size() == 0 && getInherited() == null) return Collections.emptyMap();
+ if (rankProperties.size() == 0) return getInherited().getRankPropertyMap();
+ if (getInherited() == null) return Collections.unmodifiableMap(rankProperties);
+
+ // Neither is null
+ Map<String, List<RankProperty>> combined = new LinkedHashMap<>(getInherited().getRankPropertyMap());
+ combined.putAll(rankProperties); // Don't combine values across inherited properties
+ return Collections.unmodifiableMap(combined);
+ }
+
+ public void addRankProperty(String name, String parameter) {
+ addRankProperty(new RankProperty(name, parameter));
+ }
+
+ public void addRankProperty(RankProperty rankProperty) {
+ // Just the usual multimap semantics here
+ List<RankProperty> properties = rankProperties.get(rankProperty.getName());
+ if (properties == null) {
+ properties = new ArrayList<>(1);
+ rankProperties.put(rankProperty.getName(), properties);
+ }
+ properties.add(rankProperty);
+ }
+
+ public String toString() {
+ return "rank profile " + getName();
+ }
+
+ public int getRerankCount() {
+ if (rerankCount>=0) return rerankCount;
+ if (getInherited()!=null) return getInherited().getRerankCount();
+ return -1;
+ }
+
+ public int getNumThreadsPerSearch() {
+ return numThreadsPerSearch;
+ }
+
+ public void setNumThreadsPerSearch(int numThreads) {
+ this.numThreadsPerSearch = numThreads;
+ }
+
+ public void setNumSearchPartitions(int numSearchPartitions) {
+ this.numSearchPartitions = numSearchPartitions;
+ }
+
+ public int getNumSearchPartitions() { return numSearchPartitions; }
+
+ public double getTermwiseLimit() { return termwiseLimit; }
+ public void setTermwiseLimit(double termwiseLimit) { this.termwiseLimit = termwiseLimit; }
+
+ /** Sets the rerank count. Set to -1 to use inherited */
+ public void setRerankCount(int rerankCount) {
+ this.rerankCount = rerankCount;
+ }
+
+ /** Whether we should ignore the default rank features. Set to null to use inherited */
+ public void setIgnoreDefaultRankFeatures(Boolean ignoreDefaultRankFeatures) {
+ this.ignoreDefaultRankFeatures = ignoreDefaultRankFeatures;
+ }
+
+ public boolean getIgnoreDefaultRankFeatures() {
+ if (ignoreDefaultRankFeatures!=null) return ignoreDefaultRankFeatures;
+ return (getInherited()!=null) && getInherited().getIgnoreDefaultRankFeatures();
+ }
+
+ /**
+ * Returns the string form of the second phase ranking expression.
+ * @return string form of second phase ranking expression
+ */
+ public String getSecondPhaseRankingString() {
+ if (secondPhaseRankingString != null) return secondPhaseRankingString;
+ if (getInherited() != null) return getInherited().getSecondPhaseRankingString();
+ return null;
+ }
+
+ /**
+ * Returns the string form of the first phase ranking expression.
+ * @return string form of first phase ranking expression
+ */
+ public String getFirstPhaseRankingString() {
+ if (firstPhaseRankingString != null) return firstPhaseRankingString;
+ if (getInherited() != null) return getInherited().getFirstPhaseRankingString();
+ return null;
+ }
+
+ public void addMacro(String name, boolean inline) {
+ macros.put(name, new Macro(name, inline));
+ }
+
+ /** Returns an unmodifiable view of the macros in this */
+ public Map<String, Macro> getMacros() {
+ if (macros.size() == 0 && getInherited()==null) return Collections.emptyMap();
+ if (macros.size() == 0) return getInherited().getMacros();
+ if (getInherited() == null) return Collections.unmodifiableMap(macros);
+
+ // Neither is null
+ Map<String, Macro> allMacros = new LinkedHashMap<>(getInherited().getMacros());
+ allMacros.putAll(macros);
+ return Collections.unmodifiableMap(allMacros);
+
+ }
+
+ public int getKeepRankCount() {
+ if (keepRankCount>=0) return keepRankCount;
+ if (getInherited()!=null) return getInherited().getKeepRankCount();
+ return -1;
+ }
+
+ public void setKeepRankCount(int rerankArraySize) {
+ this.keepRankCount = rerankArraySize;
+ }
+
+ public double getRankScoreDropLimit() {
+ if (rankScoreDropLimit>-Double.MAX_VALUE) return rankScoreDropLimit;
+ if (getInherited()!=null) return getInherited().getRankScoreDropLimit();
+ return rankScoreDropLimit;
+ }
+
+ public void setRankScoreDropLimit(double rankScoreDropLimit) {
+ this.rankScoreDropLimit = rankScoreDropLimit;
+ }
+
+ public Set<String> filterFields() {
+ return filterFields;
+ }
+
+ /**
+ * Returns all filter fields in this profile and any profile it inherits.
+ * @return the set of all filter fields
+ */
+ public Set<String> allFilterFields() {
+ RankProfile parent = getInherited();
+ Set<String> retval = new LinkedHashSet<>();
+ if (parent != null) {
+ retval.addAll(parent.allFilterFields());
+ }
+ retval.addAll(filterFields());
+ return retval;
+ }
+
+ /**
+ * Will take the parser-set textual ranking expressions and turn into objects
+ */
+ public void parseExpressions() {
+ try {
+ parseRankingExpressions();
+ parseMacros();
+ } catch (ParseException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Passes the contents of macros on to parser. Then put all the implied rank properties
+ * from those macros into the profile's props map.
+ */
+ private void parseMacros() throws ParseException {
+ for (Map.Entry<String, Macro> e : getMacros().entrySet()) {
+ String macroName = e.getKey();
+ Macro macro = e.getValue();
+ RankingExpression expr = parseRankingExpression(macroName, macro.getTextualExpression());
+ macro.setRankingExpression(expr);
+ macro.setTextualExpression(expr.getRoot().toString());
+ }
+ }
+
+ /**
+ * Passes ranking expressions on to parser
+ * @throws ParseException if either of the ranking expressions could not be parsed
+ */
+ private void parseRankingExpressions() throws ParseException {
+ if (getFirstPhaseRankingString() != null)
+ setFirstPhaseRanking(parseRankingExpression("firstphase", getFirstPhaseRankingString()));
+ if (getSecondPhaseRankingString() != null)
+ setSecondPhaseRanking(parseRankingExpression("secondphase", getSecondPhaseRankingString()));
+ }
+
+ private RankingExpression parseRankingExpression(String expName, String exp) throws ParseException {
+ if (exp.trim().length() == 0)
+ throw new ParseException("Encountered an empty ranking expression in " + getName()+ ", " + expName + ".");
+
+ try {
+ RankingExpression expression = new RankingExpression(openRankingExpressionReader(expName, exp.trim()));
+ expression.setName(expName);
+ return expression;
+ }
+ catch (com.yahoo.searchlib.rankingexpression.parser.ParseException e) {
+ ParseException exception = new ParseException("Could not parse ranking expression '" + exp.trim() +
+ "' in " + getName()+ ", " + expName + ".");
+ throw (ParseException)exception.initCause(e);
+ }
+ }
+
+ private Reader openRankingExpressionReader(String expName, String expression) {
+ if ( ! expression.startsWith("file:")) return new StringReader(expression);
+
+ String fileName = expression.substring("file:".length()).trim();
+ if ( ! fileName.endsWith(ApplicationPackage.RANKEXPRESSION_NAME_SUFFIX))
+ fileName = fileName + ApplicationPackage.RANKEXPRESSION_NAME_SUFFIX;
+
+ final File file = new File(fileName);
+ if ( ! (file.isAbsolute()) && file.getPath().contains("/")) // See ticket 4102122
+ throw new IllegalArgumentException("In " + getName() +", " + expName + ", ranking references file '" + file +
+ "' in subdirectory, which is not supported.");
+
+ return search.getRankingExpression(fileName);
+ }
+
+ /** Shallow clones this */
+ @Override
+ public RankProfile clone() {
+ try {
+ // Note: This treats RankingExpression in Macros as immutables even though they are not
+ RankProfile clone = (RankProfile)super.clone();
+ clone.rankSettings = new LinkedHashSet<>(this.rankSettings);
+ clone.matchPhaseSettings = this.matchPhaseSettings; // hmm?
+ clone.summaryFeatures = summaryFeatures != null ? new LinkedHashSet<>(this.summaryFeatures) : null;
+ clone.rankFeatures = rankFeatures != null ? new LinkedHashSet<>(this.rankFeatures) : null;
+ clone.rankProperties = new LinkedHashMap<>(this.rankProperties);
+ clone.macros = new LinkedHashMap<>(this.macros);
+ clone.filterFields = new HashSet<>(this.filterFields);
+ clone.constants = new HashMap<>(this.constants);
+ return clone;
+ }
+ catch (CloneNotSupportedException e) {
+ throw new RuntimeException("Won't happen", e);
+ }
+ }
+
+ /**
+ * Returns a copy of this where the content is optimized for execution.
+ * Compiled profiles should never be modified.
+ */
+ public RankProfile compile() {
+ try {
+ RankProfile compiled = this.clone();
+ compiled.compileThis();
+ return compiled;
+ }
+ catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Rank profile '" + getName() + "' is invalid", e);
+ }
+ }
+
+ private void compileThis() {
+ parseExpressions();
+
+ checkNameCollisions(getMacros(), getConstants());
+
+ Map<String, Macro> compiledMacros = new LinkedHashMap<>();
+ for (Map.Entry<String, Macro> macroEntry : getMacros().entrySet()) {
+ Macro compiledMacro = macroEntry.getValue().clone();
+ compiledMacro.setRankingExpression(compile(macroEntry.getValue().getRankingExpression(),
+ getConstants(), Collections.<String, Macro>emptyMap()));
+ compiledMacros.put(macroEntry.getKey(), compiledMacro);
+ }
+ macros = compiledMacros;
+ Map<String, Macro> inlineMacros = keepInline(compiledMacros);
+ firstPhaseRanking = compile(this.getFirstPhaseRanking(), getConstants(), inlineMacros);
+ secondPhaseRanking = compile(this.getSecondPhaseRanking(), getConstants(), inlineMacros);
+ }
+
+ private void checkNameCollisions(Map<String, Macro> macros, Map<String, Value> constants) {
+ for (Map.Entry<String, Macro> macroEntry : macros.entrySet()) {
+ if (constants.get(macroEntry.getKey()) != null)
+ throw new IllegalArgumentException("Cannot have both a constant and macro named '" + macroEntry.getKey() + "'");
+ }
+ }
+
+ private Map<String, Macro> keepInline(Map<String, Macro> macros) {
+ Map<String, Macro> inlineMacros = new HashMap<>();
+ for (Map.Entry<String, Macro> entry : macros.entrySet())
+ if (entry.getValue().getInline())
+ inlineMacros.put(entry.getKey(), entry.getValue());
+ return inlineMacros;
+ }
+
+ private RankingExpression compile(RankingExpression expression,
+ Map<String, Value> constants,
+ Map<String, Macro> inlineMacros) {
+ if (expression == null) return null;
+ Map<String, String> rankPropertiesOutput = new HashMap<>();
+ expression = new ConstantDereferencer(constants).transform(expression);
+ expression = new ConstantTensorTransformer(constants, rankPropertiesOutput).transform(expression);
+ expression = new MacroInliner(inlineMacros).transform(expression);
+ expression = new Simplifier().transform(expression);
+ for (Map.Entry<String, String> rankProperty : rankPropertiesOutput.entrySet()) {
+ addRankProperty(rankProperty.getKey(), rankProperty.getValue());
+ }
+ return expression;
+ }
+
+ /**
+ * A rank setting. The identity of a rank setting is its field name and type (not value).
+ * A rank setting is immutable.
+ */
+ public static class RankSetting implements Serializable {
+
+ private String fieldName;
+
+ private Type type;
+
+ /** The rank value */
+ private Object value;
+
+ public enum Type {
+
+ RANKTYPE("rank-type"),
+ LITERALBOOST("literal-boost"),
+ WEIGHT("weight"),
+ PREFERBITVECTOR("preferbitvector",true);
+
+ private String name;
+
+ /** True if this setting really pertains to an index, not a field within an index */
+ private boolean isIndexLevel;
+
+ private Type(String name) {
+ this(name,false);
+ }
+
+ private Type(String name,boolean isIndexLevel) {
+ this.name = name;
+ this.isIndexLevel=isIndexLevel;
+ }
+
+ /** True if this setting really pertains to an index, not a field within an index */
+ public boolean isIndexLevel() { return isIndexLevel; }
+
+ /** @return The name of this type */
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ return "type: " + name;
+ }
+
+ }
+
+ public RankSetting(String fieldName, RankSetting.Type type, Object value) {
+ this.fieldName = fieldName;
+ this.type = type;
+ this.value = value;
+ }
+
+ public String getFieldName() { return fieldName; }
+
+ public Type getType() { return type; }
+
+ public Object getValue() { return value; }
+
+ /** @return The value as an int, or a negative value if it is not an integer */
+ public int getIntValue() {
+ if (value instanceof Integer) {
+ return ((Integer)value);
+ }
+ else {
+ return -1;
+ }
+ }
+
+ public int hashCode() {
+ return fieldName.hashCode() + 17 * type.hashCode();
+ }
+
+ public boolean equals(Object object) {
+ if (!(object instanceof RankSetting)) {
+ return false;
+ }
+ RankSetting other = (RankSetting)object;
+ return
+ fieldName.equals(other.fieldName) &&
+ type.equals(other.type);
+ }
+
+ public String toString() {
+ return type + " setting " + fieldName + ": " + value;
+ }
+
+ }
+
+ /** A rank property. Rank properties are Value Objects */
+ public static class RankProperty implements Serializable {
+
+ private String name;
+ private String value;
+
+ public RankProperty(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() { return name; }
+
+ public String getValue() { return value; }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode() + 17*value.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (! (object instanceof RankProperty)) return false;
+ RankProperty other=(RankProperty)object;
+ return (other.name.equals(this.name) && other.value.equals(this.value));
+ }
+
+ @Override
+ public String toString() {
+ return name + " = " + value;
+ }
+
+ }
+
+ /**
+ * Represents a declared macro in the profile. It is, after parsing, transformed into ExpressionMacro
+ *
+ * @author vegardh
+ */
+ public static class Macro implements Serializable, Cloneable {
+
+ private String name=null;
+ private String textualExpression=null;
+ private RankingExpression expression=null;
+ private List<String> formalParams = new ArrayList<>();
+
+ /** True if this should be inlined into calling expressions. Useful for very cheap macros. */
+ private final boolean inline;
+
+ public Macro(String name, boolean inline) {
+ this.name = name;
+ this.inline = inline;
+ }
+
+ public void addParam(String name) {
+ formalParams.add(name);
+ }
+
+ public List<String> getFormalParams() {
+ return formalParams;
+ }
+
+ public String getTextualExpression() {
+ return textualExpression;
+ }
+
+ public void setTextualExpression(String textualExpression) {
+ this.textualExpression = textualExpression;
+ }
+
+ public void setRankingExpression(RankingExpression expr) {
+ this.expression=expr;
+ }
+
+ public RankingExpression getRankingExpression() {
+ return expression;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean getInline() {
+ return inline && formalParams.size() == 0; // only inline no-arg macros;
+ }
+
+ public ExpressionFunction toExpressionMacro() {
+ return new ExpressionFunction(getName(), getFormalParams(), getRankingExpression());
+ }
+
+ @Override
+ public Macro clone() {
+ try {
+ return (Macro)super.clone();
+ }
+ catch (CloneNotSupportedException e) {
+ throw new RuntimeException("Won't happen", e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "macro " + getName() + ": " + expression;
+ }
+
+ }
+
+ public static final class DiversitySettings {
+ private String attribute = null;
+ private int minGroups = 0;
+ private double cutoffFactor = 10;
+ private Diversity.CutoffStrategy cutoffStrategy = Diversity.CutoffStrategy.loose;
+
+ public void setAttribute(String value) { attribute = value; }
+ public void setMinGroups(int value) { minGroups = value; }
+ public void setCutoffFactor(double value) { cutoffFactor = value; }
+ public void setCutoffStrategy(Diversity.CutoffStrategy strategy) { cutoffStrategy = strategy; }
+ public void setCutoffStrategy(String strategy) { cutoffStrategy = Diversity.CutoffStrategy.valueOf(strategy); }
+ public String getAttribute() { return attribute; }
+ public int getMinGroups() { return minGroups; }
+ public double getCutoffFactor() { return cutoffFactor; }
+ public Diversity.CutoffStrategy getCutoffStrategy() { return cutoffStrategy; }
+ public void checkValid() {
+ if (attribute == null || attribute.isEmpty()) {
+ throw new IllegalArgumentException("'diversity' did not set non-empty diversity attribute name.");
+ }
+ if (minGroups <= 0) {
+ throw new IllegalArgumentException("'diversity' did not set min-groups > 0");
+ }
+ if (cutoffFactor < 1.0) {
+ throw new IllegalArgumentException("diversity.cutoff.factor must be larger or equal to 1.0.");
+ }
+ }
+ }
+
+ public static class MatchPhaseSettings {
+ private String attribute = null;
+ private boolean ascending = false;
+ private int maxHits = 0; // try to get this many hits before degrading the match phase
+ private double maxFilterCoverage = 1.0; // Max coverage of original corpus that will trigger the filter.
+ private DiversitySettings diversity = null;
+ private double evaluationPoint = 0.20;
+ private double prePostFilterTippingPoint = 1.0;
+
+ public void setDiversity(DiversitySettings value) {
+ value.checkValid();
+ diversity = value;
+ }
+ public void setAscending(boolean value) { ascending = value; }
+ public void setAttribute(String value) { attribute = value; }
+ public void setMaxHits(int value) { maxHits = value; }
+ public void setMaxFilterCoverage(double value) { maxFilterCoverage = value; }
+ public void setEvaluationPoint(double evaluationPoint) { this.evaluationPoint = evaluationPoint; }
+ public void setPrePostFilterTippingPoint(double prePostFilterTippingPoint) { this.prePostFilterTippingPoint = prePostFilterTippingPoint; }
+
+ public boolean getAscending() { return ascending; }
+ public String getAttribute() { return attribute; }
+ public int getMaxHits() { return maxHits; }
+ public double getMaxFilterCoverage() { return maxFilterCoverage; }
+ public DiversitySettings getDiversity() { return diversity; }
+ public double getEvaluationPoint() { return evaluationPoint; }
+ public double getPrePostFilterTippingPoint() { return prePostFilterTippingPoint; }
+
+ public void checkValid() {
+ if (attribute == null) {
+ throw new IllegalArgumentException("match-phase did not set any attribute");
+ }
+ if (! (maxHits > 0)) {
+ throw new IllegalArgumentException("match-phase did not set max-hits > 0");
+ }
+ }
+
+ }
+
+ public static class TypeSettings {
+
+ private final Map<String, String> types = new HashMap<>();
+
+ public void addType(String name, String type) {
+ types.put(name, type);
+ }
+
+ public Map<String, String> getTypes() {
+ return Collections.unmodifiableMap(types);
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfileRegistry.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfileRegistry.java
new file mode 100644
index 00000000000..a9ee3c3cc5c
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfileRegistry.java
@@ -0,0 +1,90 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import java.util.*;
+
+/**
+ * Mapping from name to {@link RankProfile} as well as a reverse mapping of {@link RankProfile} to {@link Search}.
+ * Having both of these mappings consolidated here will make it easier to remove dependencies on these mappings at
+ * run time, since it is essentially only used when building rank profile config at deployment time.
+ *
+ * TODO: Reconsider the difference between local and global maps. Right now, the local maps might better be
+ * served from a different class owned by SearchBuilder.
+ *
+ * @author lulf
+ * @since 5.20
+ */
+public class RankProfileRegistry {
+
+ private final Map<RankProfile, Search> rankProfileToSearch = new LinkedHashMap<>();
+ private final Map<Search, Map<String, RankProfile>> rankProfiles = new LinkedHashMap<>();
+ /* These rank profiles can be overridden: 'default' rank profile, as that is documented to work. And 'unranked'. */
+ static final Set<String> overridableRankProfileNames = new HashSet<>(Arrays.asList("default", "unranked"));
+
+ public RankProfileRegistry() {
+ }
+
+ public static RankProfileRegistry createRankProfileRegistryWithBuiltinRankProfiles(Search search) {
+ RankProfileRegistry rankProfileRegistry = new RankProfileRegistry();
+ rankProfileRegistry.addRankProfile(new DefaultRankProfile(search, rankProfileRegistry));
+ rankProfileRegistry.addRankProfile(new UnrankedRankProfile(search, rankProfileRegistry));
+ return rankProfileRegistry;
+ }
+
+ /**
+ * Adds a rank profile to this registry
+ *
+ * @param rankProfile the rank profile to add
+ */
+ public void addRankProfile(RankProfile rankProfile) {
+ if (!rankProfiles.containsKey(rankProfile.getSearch())) {
+ rankProfiles.put(rankProfile.getSearch(), new LinkedHashMap<>());
+ }
+ checkForDuplicateRankProfile(rankProfile);
+ rankProfiles.get(rankProfile.getSearch()).put(rankProfile.getName(), rankProfile);
+ rankProfileToSearch.put(rankProfile, rankProfile.getSearch());
+ }
+
+ private void checkForDuplicateRankProfile(RankProfile rankProfile) {
+ final String rankProfileName = rankProfile.getName();
+ RankProfile existingRangProfileWithSameName = rankProfiles.get(rankProfile.getSearch()).get(rankProfileName);
+ if (existingRangProfileWithSameName == null) return;
+
+ if (!overridableRankProfileNames.contains(rankProfileName)) {
+ throw new IllegalArgumentException("Cannot add rank profile '" + rankProfileName + "' in search definition '"
+ + rankProfile.getSearch().getName() + "', since it already exists");
+ }
+ }
+
+ /**
+ * Returns a named rank profile, null if the search definition doesn't have one with the given name
+ *
+ * @param search The {@link Search} that owns the rank profile.
+ * @param name The name of the rank profile
+ * @return The RankProfile to return.
+ */
+ public RankProfile getRankProfile(Search search, String name) {
+ return rankProfiles.get(search).get(name);
+ }
+
+ /**
+ * Rank profiles that are collected across clusters.
+ * @return A set of global {@link RankProfile} instances.
+ */
+ public Set<RankProfile> allRankProfiles() {
+ return rankProfileToSearch.keySet();
+ }
+
+ /**
+ * Rank profiles that are collected for a given search definition
+ * @param search {@link Search} to get rank profiles for.
+ * @return A collection of local {@link RankProfile} instances.
+ */
+ public Collection<RankProfile> localRankProfiles(Search search) {
+ Map<String, RankProfile> mapping = rankProfiles.get(search);
+ if (mapping == null) {
+ return Collections.emptyList();
+ }
+ return mapping.values();
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/SDDocumentTypeOrderer.java b/config-model/src/main/java/com/yahoo/searchdefinition/SDDocumentTypeOrderer.java
new file mode 100644
index 00000000000..7d8a87cee8c
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/SDDocumentTypeOrderer.java
@@ -0,0 +1,126 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.document.*;
+import com.yahoo.document.annotation.AnnotationReferenceDataType;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.document.TemporarySDDocumentType;
+
+import java.util.*;
+import java.util.logging.Level;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class SDDocumentTypeOrderer {
+ private Map<DataTypeName, SDDocumentType> createdSDTypes = new LinkedHashMap<>();
+ private Set<Integer> seenTypes = new LinkedHashSet<>();
+ List<SDDocumentType> processingOrder = new LinkedList<>();
+ private DeployLogger deployLogger;
+
+ public SDDocumentTypeOrderer(List<SDDocumentType> sdTypes, DeployLogger deployLogger) {
+ this.deployLogger = deployLogger;
+ for (SDDocumentType type : sdTypes) {
+ createdSDTypes.put(type.getDocumentName(), type);
+ }
+ DocumentTypeManager dtm = new DocumentTypeManager();
+ for (DataType type : dtm.getDataTypes()) {
+ seenTypes.add(type.getId());
+ }
+
+ }
+
+ List<SDDocumentType> getOrdered() { return processingOrder; }
+
+ public void process() {
+ for (SDDocumentType type : createdSDTypes.values()) {
+ process(type);
+ }
+ }
+ private void process(SDDocumentType type) {
+ List<DataTypeName> toReplace = new ArrayList<>();
+ for (SDDocumentType sdoc : type.getInheritedTypes()) {
+ if (sdoc instanceof TemporarySDDocumentType) {
+ toReplace.add(sdoc.getDocumentName());
+ }
+ }
+ for (DataTypeName name : toReplace) {
+ SDDocumentType inherited = createdSDTypes.get(name);
+ if (inherited == null) {
+ throw new IllegalStateException("Document type '" + name + "' not found.");
+ }
+ process(inherited);
+ type.inherit(inherited);
+ }
+ visit(type);
+ }
+
+ private void visit(SDDocumentType docOrStruct) {
+ int id;
+ if (docOrStruct.isStruct()) {
+ id = new StructDataType(docOrStruct.getName()).getId();
+ } else {
+ id = new DocumentType(docOrStruct.getName()).getId();
+ }
+
+ if (seenTypes.contains(id)) {
+ return;
+ } else {
+ seenTypes.add((new StructDataType(docOrStruct.getName()).getId()));
+ }
+
+
+ for (Field field : docOrStruct.fieldSet()) {
+ if (!seenTypes.contains(field.getDataType().getId())) {
+ //we haven't seen this before, do it
+ visit(field.getDataType());
+ }
+ }
+ processingOrder.add(docOrStruct);
+ }
+
+ private SDDocumentType find(String name) {
+ SDDocumentType sdDocType = createdSDTypes.get(new DataTypeName(name));
+ if (sdDocType != null) {
+ return sdDocType;
+ }
+ for(SDDocumentType sdoc : createdSDTypes.values()) {
+ for (SDDocumentType stype : sdoc.getTypes()) {
+ if (stype.getName().equals(name)) {
+ return stype;
+ }
+ }
+ }
+ return null;
+ }
+ private void visit(DataType type) {
+ if (type instanceof StructuredDataType) {
+ StructuredDataType structType = (StructuredDataType) type;
+ SDDocumentType sdDocType = find(structType.getName());
+ if (sdDocType == null) {
+ throw new IllegalArgumentException("Could not find struct '" + type.getName() + "'.");
+ }
+ visit(sdDocType);
+ return;
+ }
+
+ if (type instanceof MapDataType) {
+ MapDataType mType = (MapDataType) type;
+ visit(mType.getValueType());
+ visit(mType.getKeyType());
+ } else if (type instanceof WeightedSetDataType) {
+ WeightedSetDataType wType = (WeightedSetDataType) type;
+ visit(wType.getNestedType());
+ } else if (type instanceof CollectionDataType) {
+ CollectionDataType cType = (CollectionDataType) type;
+ visit(cType.getNestedType());
+ } else if (type instanceof AnnotationReferenceDataType) {
+ //do nothing
+ } else if (type instanceof PrimitiveDataType) {
+ //do nothing
+ } else {
+ deployLogger.log(Level.WARNING, "Unknown type : " + type);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/Search.java b/config-model/src/main/java/com/yahoo/searchdefinition/Search.java
new file mode 100644
index 00000000000..43c6bb4b441
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/Search.java
@@ -0,0 +1,555 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.document.*;
+import com.yahoo.searchdefinition.document.annotation.SDAnnotationType;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+
+import java.io.Reader;
+import java.io.Serializable;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * <p>A search definition describes (or uses) some document types, defines how these are turned into a relevancy tuned
+ * index through indexing and how data from documents should be served at search time.</p> <p>The identity of this
+ * class is its name.</p>
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+// TODO: Make a class owned by this, for each of these responsibilities:
+// Managing indexes, managing attributes, managing summary classes.
+// Ensure that after the processing step, all implicit instances of the above types are explicitly represented
+public class Search implements Serializable {
+
+ private static final Logger log = Logger.getLogger(Search.class.getName());
+ private static final String SD_DOC_FIELD_NAME = "sddocname";
+ private static final List<String> RESERVED_NAMES = Arrays.asList(
+ "index", "index_url", "summary", "attribute", "select_input", "host", "documentid",
+ "position", "split_foreach", "tokenize", "if", "else", "switch", "case", SD_DOC_FIELD_NAME, "relevancy");
+
+ /**
+ * @return True if the given field name is a reserved name.
+ */
+ public static boolean isReservedName(String name) {
+ return RESERVED_NAMES.contains(name);
+ }
+
+ // Field sets
+ private FieldSets fieldSets = new FieldSets();
+
+ // Whether or not this object has been processed.
+ private boolean processed;
+
+ // The unique name of this search definition.
+ private String name;
+
+ // True if this doesn't define a search, just some documents.
+ private boolean documentsOnly = false;
+
+ // The stemming setting of this search definition. Default is SHORTEST.
+ private Stemming stemming = Stemming.SHORTEST;
+
+ // Documents contained in this definition.
+ private SDDocumentType docType;
+
+ // The extra fields of this search definition.
+ private Map<String, SDField> fields = new LinkedHashMap<>();
+
+ // The explicitly defined indices of this search definition.
+ private Map<String, Index> indices = new LinkedHashMap<>();
+
+ // The explicitly defined summaries of this search definition.
+ private Map<String, DocumentSummary> summaries = new LinkedHashMap<>();
+ // _Must_ preserve order
+
+ private ApplicationPackage sourceApplication;
+
+ /**
+ * Creates a search definition which just holds a set of documents which should not (here, directly) be searchable
+ */
+ protected Search() {
+ documentsOnly = true;
+ }
+
+ /**
+ * Creates a proper search definition
+ * @param name of the the searchdefinition
+ * @param sourceApplication the application containing this
+ */
+ public Search(String name, ApplicationPackage sourceApplication) {
+ this.sourceApplication = sourceApplication;
+ this.name = name;
+ }
+
+ protected void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns true if this doesn't define a search, just some documents
+ *
+ * @return if the searchdefinition only has documents
+ */
+ public boolean isDocumentsOnly() {
+ return documentsOnly;
+ }
+
+ /**
+ * Sets the stemming default of fields. Default is ALL
+ *
+ * @param stemming set default stemming for this searchdefinition
+ * @throws NullPointerException if this is attempted set to null
+ */
+ public void setStemming(Stemming stemming) {
+ if (stemming == null) {
+ throw new NullPointerException("The stemming setting of a search definition " +
+ "can not be null");
+ }
+ this.stemming = stemming;
+ }
+
+ /**
+ * Returns whether fields should be stemmed by default or not. Default is ALL. This is never null.
+ *
+ * @return the default stemming for this searchdefinition
+ */
+ public Stemming getStemming() {
+ return stemming;
+ }
+
+ /**
+ * Adds a document type which is defined in this search definition
+ *
+ * @param document the document type to add
+ */
+ public void addDocument(SDDocumentType document) {
+ if (docType != null) {
+ throw new IllegalArgumentException("Searchdefinition cannot have more than one document");
+ }
+ docType = document;
+ }
+
+ /**
+ * Gets a document from this search definition
+ *
+ * @param name the name of the document to return
+ * @return the contained or used document type, or null if there is no such document
+ */
+ public SDDocumentType getDocument(String name) {
+ if (docType != null && name.equals(docType.getName())) {
+ return docType;
+ }
+ return null;
+ }
+
+ /**
+ * @return true if the document has been added.
+ */
+ public boolean hasDocument() {
+ return docType != null;
+ }
+
+ /**
+ * @return The document in this search.
+ */
+ public SDDocumentType getDocument() {
+ return docType;
+ }
+
+ /**
+ * Returns a list of all the fields of this search definition, that is all fields in all documents, in the documents
+ * they inherit, and all extra fields. The caller receives ownership to the list - subsequent changes to it will not
+ * impact this Search
+ *
+ * @return the list of fields in this searchdefinition
+ */
+ public List<SDField> allFieldsList() {
+ List<SDField> allFields = new ArrayList<>();
+ allFields.addAll(extraFieldList());
+ for (Field field : docType.fieldSet()) {
+ allFields.add((SDField)field);
+ }
+ return allFields;
+ }
+
+ /**
+ * Returns the content of a ranking expression file
+ */
+ public Reader getRankingExpression(String fileName) {
+ return sourceApplication.getRankingExpression(fileName);
+ }
+
+ /**
+ * Returns a field defined in this search definition or one if its documents. Fields in this search definition takes
+ * precedence over document fields having the same name
+ *
+ * @param name of the field
+ * @return the SDField representing the field
+ */
+ public SDField getField(String name) {
+ SDField field = getExtraField(name);
+ if (field != null) {
+ return field;
+ }
+ return (SDField)docType.getField(name);
+ }
+
+ /**
+ * Returns a field defined in one of the documents of this search definition. This does <b>not</b> include the extra
+ * fields defined outside of a document (those accessible through the getExtraField() method).
+ *
+ * @param name The name of the field to return.
+ * @return The named field, or null if not found.
+ */
+ public SDField getDocumentField(String name) {
+ return (SDField)docType.getField(name);
+ }
+
+ /**
+ * Adds an extra field of this search definition not contained in a document
+ *
+ * @param field to add to the searchdefinitions list of external fields.
+ */
+ public void addExtraField(SDField field) {
+ if (fields.containsKey(field.getName())) {
+ log.warning("Duplicate field " + field.getName() + " in search definition " + getName());
+ } else {
+ field.setIsExtraField(true);
+ fields.put(field.getName(), field);
+ }
+ }
+
+ public Collection<SDField> extraFieldList() {
+ return fields.values();
+ }
+ public Collection<SDField> allExtraFields() {
+ Map<String, SDField> extraFields = new TreeMap<>();
+ for (Field field : docType.fieldSet()) {
+ SDField sdField = (SDField) field;
+ if (sdField.isExtraField()) {
+ extraFields.put(sdField.getName(), sdField);
+ }
+ }
+ for (SDField field : extraFieldList()) {
+ extraFields.put(field.getName(), field);
+ }
+ return extraFields.values();
+ }
+
+ /**
+ * Returns a field by name, or null if it is not present
+ *
+ * @param fieldName the name of the external field to get
+ * @return the SDField of this name
+ */
+ public SDField getExtraField(String fieldName) {
+ return fields.get(fieldName);
+ }
+
+ /**
+ * Adds an explicitly defined index to this search definition
+ *
+ * @param index the index to add
+ */
+ public void addIndex(Index index) {
+ indices.put(index.getName(), index);
+ }
+
+ /**
+ * <p>Returns an index, or null if no index with this name has had some <b>explicit settings</b> applied. Even if
+ * this returns null, the index may be implicitly defined by an indexing statement.</p>
+ * <p>This will return the
+ * index whether it is defined on this search or on one of its fields</p>
+ *
+ * @param name the name of the index to get
+ * @return the index requested
+ */
+ public Index getIndex(String name) {
+ List<Index> sameIndices = new ArrayList<>(1);
+ Index searchIndex = indices.get(name);
+ if (searchIndex != null) {
+ sameIndices.add(searchIndex);
+ }
+
+ for (SDField field : allFieldsList()) {
+ Index index = field.getIndex(name);
+ if (index != null) {
+ sameIndices.add(index);
+ }
+ }
+ if (sameIndices.size() == 0) {
+ return null;
+ }
+ if (sameIndices.size() == 1) {
+ return sameIndices.get(0);
+ }
+ return consolidateIndices(sameIndices);
+ }
+
+ public boolean existsIndex(String name) {
+ if (indices.get(name) != null) {
+ return true;
+ }
+ for (SDField field : allFieldsList()) {
+ if (field.existsIndex(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Consolidates a set of index settings for the same index into one
+ *
+ * @param indices The list of indexes to consolidate.
+ * @return The consolidated index
+ */
+ private Index consolidateIndices(List<Index> indices) {
+ Index first = indices.get(0);
+ Index consolidated = new Index(first.getName());
+ consolidated.setRankType(first.getRankType());
+ consolidated.setType(first.getType());
+ for (Index current : indices) {
+ if (current.isPrefix()) {
+ consolidated.setPrefix(true);
+ }
+
+ if (consolidated.getRankType() == null) {
+ consolidated.setRankType(current.getRankType());
+ } else {
+ if (current.getRankType() != null &&
+ !consolidated.getRankType().equals(current.getRankType()))
+ {
+ log.warning("Conflicting rank type settings for " +
+ first.getName() + " in " + this + ", using " +
+ consolidated.getRankType());
+ }
+ }
+
+ for (Iterator<String> j = current.aliasIterator(); j.hasNext();) {
+ consolidated.addAlias(j.next());
+ }
+ }
+ return consolidated;
+ }
+
+ /**
+ * All explicitly defined indices, both on this search definition itself (returned first) and all its fields
+ *
+ * @return The list of explicit defined indexes.
+ */
+ public List<Index> getExplicitIndices() {
+ List<Index> allIndices = new ArrayList<>(indices.values());
+ for (SDField field : allFieldsList()) {
+ for (Index index : field.getIndices().values()) {
+ allIndices.add(index);
+ }
+ }
+ return Collections.unmodifiableList(allIndices);
+ }
+
+ /**
+ * Adds an explicitly defined summary to this search definition
+ *
+ * @param summary The summary to add.
+ */
+ public void addSummary(DocumentSummary summary) {
+ summaries.put(summary.getName(), summary);
+ }
+
+ /**
+ * <p>Returns a summary class defined by this search definition, or null if no summary with this name is defined.
+ * The default summary, named "default" is always present.</p>
+ *
+ * @param name the name of the summary to get.
+ * @return Summary found.
+ */
+ public DocumentSummary getSummary(String name) {
+ return summaries.get(name);
+ }
+
+ /**
+ * Returns the first explicit instance found of a summary field with this name, or null if not present (implicitly
+ * or explicitly) in any summary class.
+ *
+ * @param name The name of the summaryfield to get.
+ * @return SummaryField to return.
+ */
+ public SummaryField getSummaryField(String name) {
+ for (DocumentSummary summary : summaries.values()) {
+ SummaryField summaryField = summary.getSummaryField(name);
+ if (summaryField != null) {
+ return summaryField;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the first explicit instance found of a summary field with this name, or null if not present explicitly in
+ * any summary class
+ *
+ * @param name Thge name of the explicit summary field to get.
+ * @return The SummaryField found.
+ */
+ public SummaryField getExplicitSummaryField(String name) {
+ for (DocumentSummary summary : summaries.values()) {
+ SummaryField summaryField = summary.getSummaryField(name);
+ if (summaryField != null && !summaryField.isImplicit()) {
+ return summaryField;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Summaries defined by fields of this search definition. The default summary, named "default", is always the first
+ * one in the returned iterator.
+ *
+ * @return The map of document summaries.
+ */
+ public Map<String, DocumentSummary> getSummaries() {
+ return summaries;
+ }
+
+ /**
+ * <p>Returns all summary fields, of all document summaries, which has the given field as source. If there are
+ * multiple summary fields with the same name, the last one will be used (they should all have the same content, if
+ * this is a valid search definition).</p> <p>The map gets owned by the receiver.</p>
+ *
+ * @param field The source field.
+ * @return The map of summary fields found.
+ */
+ public Map<String, SummaryField> getSummaryFields(SDField field) {
+ Map<String, SummaryField> summaryFields = new java.util.LinkedHashMap<>();
+ for (DocumentSummary documentSummary : summaries.values()) {
+ for (SummaryField summaryField : documentSummary.getSummaryFields()) {
+ if (summaryField.hasSource(field.getName())) {
+ summaryFields.put(summaryField.getName(), summaryField);
+ }
+ }
+ }
+ return summaryFields;
+ }
+
+ /**
+ * <p>Returns one summary field for each summary field name. If there are multiple summary fields with the same
+ * name, the last one will be used. Multiple fields of the same name should all have the same content in a valid
+ * search definition, except from the destination set. So this method can be used for all summary handling except
+ * processing the destination set.</p> <p>The map gets owned by the receiver.</p>
+ *
+ * @return Map of unique summary fields
+ */
+ public Map<String, SummaryField> getUniqueNamedSummaryFields() {
+ Map<String, SummaryField> summaryFields = new java.util.LinkedHashMap<>();
+ for (DocumentSummary documentSummary : summaries.values()) {
+ for (SummaryField summaryField : documentSummary.getSummaryFields()) {
+ summaryFields.put(summaryField.getName(), summaryField);
+ }
+ }
+ return summaryFields;
+ }
+
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ /**
+ * Returns the first occurence of an attribute having this name, or null if none
+ *
+ * @param name Name of attribute
+ * @return The Attribute with given name.
+ */
+ public Attribute getAttribute(String name) {
+ for (SDField field : allFieldsList()) {
+ Attribute attribute = field.getAttributes().get(name);
+ if (attribute != null) {
+ return attribute;
+ }
+ }
+ return null;
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof Search)) {
+ return false;
+ }
+
+ Search other = (Search)o;
+ return getName().equals(other.getName());
+ }
+
+ public String toString() {
+ return "search definition '" + getName() + "'";
+ }
+
+ public boolean isAccessingDiskSummary(SummaryField field) {
+ if (!field.getTransform().isInMemory()) {
+ return true;
+ }
+ if (field.getSources().size() == 0) {
+ return isAccessingDiskSummary(getName());
+ }
+ for (SummaryField.Source source : field.getSources()) {
+ if (isAccessingDiskSummary(source.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isAccessingDiskSummary(String source) {
+ SDField field = getField(source);
+ if (field == null) {
+ return false;
+ }
+ if (field.doesSummarying() && !field.doesAttributing()) {
+ return true;
+ }
+ return false;
+ }
+
+ public void process() {
+ if (processed) {
+ throw new IllegalStateException("Search '" + getName() + "' already processed.");
+ }
+ processed = true;
+ }
+
+ public boolean isProcessed() {
+ return processed;
+ }
+
+ /**
+ * The field set settings for this search
+ *
+ * @return field set settings for this
+ */
+ public FieldSets fieldSets() {
+ return fieldSets;
+ }
+
+ /**
+ * For adding structs defined in document scope
+ *
+ * @param dt The struct to add.
+ * @return self, for chaining
+ */
+ public Search addType(SDDocumentType dt) {
+ docType.addType(dt); // TODO This is a very very dirty thing. It must go
+ return this;
+ }
+
+ public Search addAnnotation(SDAnnotationType dt) {
+ docType.addAnnotation(dt);
+ return this;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java
new file mode 100644
index 00000000000..9c9bc559dc3
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/SearchBuilder.java
@@ -0,0 +1,429 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.model.application.provider.BaseDeployLogger;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.document.DocumentTypeManager;
+import com.yahoo.io.IOUtils;
+import com.yahoo.io.reader.NamedReader;
+import com.yahoo.search.query.profile.QueryProfileRegistry;
+import com.yahoo.searchdefinition.derived.SearchOrderer;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.parser.ParseException;
+import com.yahoo.searchdefinition.parser.SDParser;
+import com.yahoo.searchdefinition.parser.SimpleCharStream;
+import com.yahoo.searchdefinition.parser.TokenMgrError;
+import com.yahoo.searchdefinition.processing.Processing;
+import com.yahoo.vespa.documentmodel.DocumentModel;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+
+/**
+ * Helper class for importing {@link Search} objects in an unambiguous way. The pattern for using this is to 1) Import
+ * all available search definitions, using the importXXX() methods, 2) provide the available rank types and rank
+ * expressions, using the setRankXXX() methods, 3) invoke the {@link #build()} method, and 4) retrieve the built
+ * search objects using the {@link #getSearch(String)} method.
+ *
+ * @author TODO: Who created this?
+ */
+// TODO: This should be cleaned up and more or maybe completely taken over by MockApplicationPackage
+public class SearchBuilder {
+
+ private final DocumentTypeManager docTypeMgr = new DocumentTypeManager();
+ private List<Search> searchList = new LinkedList<>();
+ private ApplicationPackage app = null;
+ private boolean isBuilt = false;
+ private DocumentModel model = new DocumentModel();
+ private RankProfileRegistry rankProfileRegistry = new RankProfileRegistry();
+
+ public SearchBuilder() {
+ this.app = MockApplicationPackage.createEmpty();
+ }
+
+ public SearchBuilder(ApplicationPackage app) {
+ this.app = app;
+ }
+
+ public SearchBuilder(ApplicationPackage app, RankProfileRegistry rankProfileRegistry) {
+ this.app = app;
+ this.rankProfileRegistry = rankProfileRegistry;
+ }
+
+ public SearchBuilder(RankProfileRegistry rankProfileRegistry) {
+ this(MockApplicationPackage.createEmpty(), rankProfileRegistry);
+ }
+
+ /**
+ * Import search definition.
+ *
+ * @param fileName The name of the file to import.
+ * @param deployLogger Logger for deploy messages.
+ * @return The name of the imported object.
+ * @throws IOException Thrown if the file can not be read for some reason.
+ * @throws ParseException Thrown if the file does not contain a valid search definition. ```
+ */
+ public String importFile(String fileName, DeployLogger deployLogger) throws IOException, ParseException {
+ File file = new File(fileName);
+ return importString(IOUtils.readFile(file), file.getAbsoluteFile().getParent(), deployLogger);
+ }
+
+ /**
+ * Import search definition.
+ *
+ * @param fileName The name of the file to import.
+ * @return The name of the imported object.
+ * @throws IOException Thrown if the file can not be read for some reason.
+ * @throws ParseException Thrown if the file does not contain a valid search definition.
+ */
+ public String importFile(String fileName) throws IOException, ParseException {
+ return importFile(fileName, new BaseDeployLogger());
+ }
+ public String importFile(Path file) throws IOException, ParseException {
+ return importFile(file.toString(), new BaseDeployLogger());
+ }
+
+ /**
+ * Reads and parses the search definition string provided by the given reader. Once all search definitions have been
+ * imported, call {@link #build()}.
+ *
+ * @param reader The reader whose content to import.
+ * @param searchDefDir The path to use when resolving file references.
+ * @return The name of the imported object.
+ * @throws ParseException Thrown if the file does not contain a valid search definition.
+ */
+ public String importReader(NamedReader reader, String searchDefDir, DeployLogger deployLogger) throws IOException, ParseException {
+ return importString(IOUtils.readAll(reader), searchDefDir, deployLogger);
+ }
+
+ /**
+ * See #{@link #importReader}
+ *
+ * Convenience, should only be used for testing as logs will be swallowed.
+ */
+ public String importReader(NamedReader reader, String searchDefDir) throws IOException, ParseException {
+ return importString(IOUtils.readAll(reader), searchDefDir, new BaseDeployLogger());
+ }
+
+ /**
+ * Import search definition.
+ *
+ * @param str the string to parse.
+ * @return the name of the imported object.
+ * @throws ParseException thrown if the file does not contain a valid search definition.
+ */
+ public String importString(String str) throws ParseException {
+ return importString(str, null, new BaseDeployLogger());
+ }
+
+ private String importString(String str, String searchDefDir, DeployLogger deployLogger) throws ParseException {
+ Search search;
+ SimpleCharStream stream = new SimpleCharStream(str);
+ try {
+ search = new SDParser(stream, deployLogger, app, rankProfileRegistry).search(docTypeMgr, searchDefDir);
+ } catch (TokenMgrError e) {
+ throw new ParseException("Unknown symbol: " + e.getMessage());
+ } catch (ParseException pe) {
+ throw new ParseException(stream.formatException(pe.getMessage()));
+ }
+ return importRawSearch(search);
+ }
+
+ /**
+ * Registers the given search object to the internal list of objects to be processed during {@link #build()}. A
+ * {@link Search} object is considered to be "raw" if it has not already been processed. This is the case for most
+ * programmatically constructed search objects used in unit tests.
+ *
+ * @param rawSearch The object to import.
+ * @return The name of the imported object.
+ * @throws IllegalArgumentException Thrown if the given search object has already been processed.
+ */
+ public String importRawSearch(Search rawSearch) {
+ if (rawSearch.getName() == null) {
+ throw new IllegalArgumentException("Search has no name.");
+ }
+ String rawName = rawSearch.getName();
+ if (rawSearch.isProcessed()) {
+ throw new IllegalArgumentException("A search definition with a search section called '" + rawName +
+ "' has already been processed.");
+ }
+ for (Search search : searchList) {
+ if (rawName.equals(search.getName())) {
+ throw new IllegalArgumentException("A search definition with a search section called '" + rawName +
+ "' has already been added.");
+ }
+ }
+ searchList.add(rawSearch);
+ return rawName;
+ }
+
+ /**
+ * Registers the given search object to the internal list of objects to be processed during {@link #build()}. A
+ * {@link Search} object is considered to be "processed" if it has not already been processed. This is the case for most
+ * programmatically constructed search objects used in unit tests.
+ *
+ * @param processed The object to import.
+ * @return The name of the imported object.
+ * @throws IllegalArgumentException Thrown if the given search object has already been processed.
+ */
+ public String importProcessedSearch(Search processed) {
+ if (processed.getName() == null) {
+ throw new IllegalArgumentException("Search has no name.");
+ }
+ String rawName = processed.getName();
+ if (!processed.isProcessed()) {
+ throw new IllegalArgumentException("A search definition with a search section called '" + rawName +
+ "' has not been processed.");
+ }
+ for (Search search : searchList) {
+ if (rawName.equals(search.getName())) {
+ throw new IllegalArgumentException("A search definition with a search section called '" + rawName +
+ "' has already been added.");
+ }
+ }
+ searchList.add(processed);
+ return rawName;
+ }
+
+ /**
+ * Only for testing.
+ *
+ * Processes and finalizes the imported search definitions so that they become available through the {@link
+ * #getSearch(String)} method.
+ *
+ * @throws IllegalStateException Thrown if this method has already been called.
+ */
+ public void build() {
+ build(new BaseDeployLogger(), new QueryProfiles());
+ }
+
+ /**
+ * Processes and finalizes the imported search definitions so that they become available through the {@link
+ * #getSearch(String)} method.
+ *
+ * @throws IllegalStateException Thrown if this method has already been called.
+ * @param deployLogger The logger to use during build
+ * @param queryProfiles The query profiles contained in the application this search is part of.
+ */
+ public void build(DeployLogger deployLogger, QueryProfiles queryProfiles) {
+ if (isBuilt) {
+ throw new IllegalStateException("Searches already built.");
+ }
+ List<Search> built = new ArrayList<>();
+ List<SDDocumentType> sdocs = new ArrayList<>();
+ sdocs.add(SDDocumentType.VESPA_DOCUMENT);
+ for (Search search : searchList) {
+ if (search.hasDocument()) {
+ sdocs.add(search.getDocument());
+ }
+ }
+ SDDocumentTypeOrderer orderer = new SDDocumentTypeOrderer(sdocs, deployLogger);
+ orderer.process();
+ for (SDDocumentType sdoc : orderer.getOrdered()) {
+ new FieldOperationApplierForStructs().process(sdoc);
+ new FieldOperationApplier().process(sdoc);
+ }
+
+ DocumentModelBuilder builder = new DocumentModelBuilder(model);
+ for (Search search : new SearchOrderer().order(searchList)) {
+ new FieldOperationApplierForSearch().process(search);
+ // These two needed for a couple of old unit tests, ideally these are just read from app
+ process(search, deployLogger, queryProfiles);
+ built.add(search);
+ }
+ builder.addToModel(searchList);
+ if ( ! builder.valid() ) {
+ throw new IllegalArgumentException("Impossible to build a correct model.");
+ }
+ searchList = built;
+ isBuilt = true;
+ }
+
+ /**
+ * Processes and returns the given {@link Search} object. This method has been factored out of the {@link
+ * #build()} method so that subclasses can choose not to build anything.
+ *
+ * @param search The object to build.
+ */
+ protected void process(Search search, DeployLogger deployLogger, QueryProfiles queryProfiles) {
+ Processing.process(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ /**
+ * Convenience method to call {@link #getSearch(String)} when there is only a single {@link Search} object
+ * built. This method will never return null.
+ *
+ * @return The build object.
+ * @throws IllegalStateException Thrown if there is not exactly one search.
+ */
+ public Search getSearch() {
+ if ( ! isBuilt) throw new IllegalStateException("Searches not built.");
+ if (searchList.size() != 1)
+ throw new IllegalStateException("This call only works if we have 1 search definition. Search definitions: " + searchList);
+
+ return searchList.get(0);
+ }
+
+ public DocumentModel getModel() {
+ return model;
+ }
+
+ /**
+ * Returns the built {@link Search} object that has the given name. If the name is unknown, this method will simply
+ * return null.
+ *
+ * @param name the name of the search definition to return,
+ * or null to return the only one or throw an exception if there are multiple to choose from
+ * @return the built object, or null if none with this name
+ * @throws IllegalStateException if {@link #build()} has not been called.
+ */
+ public Search getSearch(String name) {
+ if ( ! isBuilt) throw new IllegalStateException("Searches not built.");
+ if (name == null) return getSearch();
+
+ for (Search search : searchList)
+ if (search.getName().equals(name)) return search;
+ return null;
+ }
+
+ /**
+ * Convenience method to return a list of all built {@link Search} objects.
+ *
+ * @return The list of built searches.
+ */
+ public List<Search> getSearchList() {
+ return new ArrayList<>(searchList);
+ }
+
+ /**
+ * Convenience factory method to import and build a {@link Search} object from a string.
+ *
+ * @param sd The string to build from.
+ * @return The built {@link SearchBuilder} object.
+ * @throws ParseException Thrown if there was a problem parsing the string.
+ */
+ public static SearchBuilder createFromString(String sd) throws ParseException {
+ SearchBuilder builder = new SearchBuilder(MockApplicationPackage.createEmpty());
+ builder.importString(sd);
+ builder.build();
+ return builder;
+ }
+
+ /**
+ * Convenience factory method to import and build a {@link Search} object from a file. Only for testing.
+ *
+ * @param fileName the file to build from
+ * @return the built {@link SearchBuilder} object
+ * @throws IOException if there was a problem reading the file.
+ * @throws ParseException if there was a problem parsing the file content.
+ */
+ public static SearchBuilder createFromFile(String fileName) throws IOException, ParseException {
+ return createFromFile(fileName, new BaseDeployLogger(), new RankProfileRegistry());
+ }
+
+ /**
+ * Convenience factory method to import and build a {@link Search} object from a file.
+ *
+ * @param fileName the file to build from.
+ * @param deployLogger logger for deploy messages.
+ * @param rankProfileRegistry registry for rank profiles.
+ * @return the built {@link SearchBuilder} object.
+ * @throws IOException if there was a problem reading the file.
+ * @throws ParseException if there was a problem parsing the file content.
+ */
+ public static SearchBuilder createFromFile(String fileName, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry)
+ throws IOException, ParseException {
+ SearchBuilder builder = new SearchBuilder(MockApplicationPackage.createEmpty(), rankProfileRegistry);
+ builder.importFile(fileName);
+ builder.build(deployLogger, new QueryProfiles());
+ return builder;
+ }
+
+ public static SearchBuilder createFromDirectory(String dir) throws IOException, ParseException {
+ return createFromDirectory(dir, new RankProfileRegistry());
+ }
+ public static SearchBuilder createFromDirectory(String dir, RankProfileRegistry rankProfileRegistry) throws IOException, ParseException {
+ SearchBuilder builder = new SearchBuilder(MockApplicationPackage.fromSearchDefinitionDirectory(dir), rankProfileRegistry);
+ for (Iterator<Path> i = Files.list(new File(dir).toPath()).filter(p -> p.getFileName().toString().endsWith(".sd")).iterator(); i.hasNext(); ) {
+ builder.importFile(i.next());
+ }
+ builder.build(new BaseDeployLogger(), new QueryProfiles());
+ return builder;
+ }
+
+ /**
+ * Convenience factory method to import and build a {@link Search} object from a file. Only for testing.
+ *
+ * @param fileName The file to build from.
+ * @return The built {@link Search} object.
+ * @throws IOException Thrown if there was a problem reading the file.
+ * @throws ParseException Thrown if there was a problem parsing the file content.
+ */
+ public static Search buildFromFile(String fileName) throws IOException, ParseException {
+ return buildFromFile(fileName, new BaseDeployLogger(), new RankProfileRegistry());
+ }
+
+ /**
+ * Convenience factory method to import and build a {@link Search} object from a file.
+ *
+ * @param fileName The file to build from.
+ * @param rankProfileRegistry Registry for rank profiles.
+ * @return The built {@link Search} object.
+ * @throws IOException Thrown if there was a problem reading the file.
+ * @throws ParseException Thrown if there was a problem parsing the file content.
+ */
+ public static Search buildFromFile(String fileName, RankProfileRegistry rankProfileRegistry)
+ throws IOException, ParseException {
+ return buildFromFile(fileName, new BaseDeployLogger(), rankProfileRegistry);
+ }
+
+ /**
+ * Convenience factory method to import and build a {@link Search} object from a file.
+ *
+ * @param fileName The file to build from.
+ * @param deployLogger Logger for deploy messages.
+ * @param rankProfileRegistry Registry for rank profiles.
+ * @return The built {@link Search} object.
+ * @throws IOException Thrown if there was a problem reading the file.
+ * @throws ParseException Thrown if there was a problem parsing the file content.
+ */
+ public static Search buildFromFile(String fileName, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry)
+ throws IOException, ParseException {
+ return createFromFile(fileName, deployLogger, rankProfileRegistry).getSearch();
+ }
+
+ /**
+ * Convenience factory method to import and build a {@link Search} object from a raw object.
+ *
+ * @param rawSearch The raw object to build from.
+ * @return The built {@link SearchBuilder} object.
+ * @see #importRawSearch(Search)
+ */
+ public static SearchBuilder createFromRawSearch(Search rawSearch, RankProfileRegistry rankProfileRegistry) {
+ SearchBuilder builder = new SearchBuilder(rankProfileRegistry);
+ builder.importRawSearch(rawSearch);
+ builder.build();
+ return builder;
+ }
+
+ /**
+ * Convenience factory method to import and build a {@link Search} object from a raw object.
+ *
+ * @param rawSearch The raw object to build from.
+ * @return The built {@link Search} object.
+ * @see #importRawSearch(Search)
+ */
+ public static Search buildFromRawSearch(Search rawSearch, RankProfileRegistry rankProfileRegistry) {
+ return createFromRawSearch(rawSearch, rankProfileRegistry).getSearch();
+ }
+
+ public RankProfileRegistry getRankProfileRegistry() {
+ return rankProfileRegistry;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/UnprocessingSearchBuilder.java b/config-model/src/main/java/com/yahoo/searchdefinition/UnprocessingSearchBuilder.java
new file mode 100644
index 00000000000..3df0c1f9953
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/UnprocessingSearchBuilder.java
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.searchdefinition.parser.ParseException;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.io.IOException;
+
+/**
+ * A SearchBuilder that does not run the processing chain for searches
+ *
+ */
+public class UnprocessingSearchBuilder extends SearchBuilder {
+
+ public UnprocessingSearchBuilder(ApplicationPackage app, RankProfileRegistry rankProfileRegistry) {
+ super(app, rankProfileRegistry);
+ }
+
+ public UnprocessingSearchBuilder() {
+ super();
+ }
+
+ public UnprocessingSearchBuilder(RankProfileRegistry rankProfileRegistry) {
+ super(rankProfileRegistry);
+ }
+
+ @Override
+ public void process(Search search, DeployLogger deployLogger, QueryProfiles queryProfiles) {
+ // empty
+ }
+
+ public static Search buildUnprocessedFromFile(String fileName) throws IOException, ParseException {
+ SearchBuilder builder = new UnprocessingSearchBuilder();
+ builder.importFile(fileName);
+ builder.build();
+ return builder.getSearch();
+ }
+} \ No newline at end of file
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/UnproperSearch.java b/config-model/src/main/java/com/yahoo/searchdefinition/UnproperSearch.java
new file mode 100644
index 00000000000..beab254bd89
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/UnproperSearch.java
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+import com.yahoo.searchdefinition.document.SDDocumentType;
+
+/**
+ * A search that was derived from an sd file containing no search element(s), only
+ * document specifications.
+ *
+ * @author vegardh
+ *
+ */
+ // Award for best class name goes to ...
+public class UnproperSearch extends Search {
+ // This class exists because the parser accepts SD files without search { ... , and
+ // there are unit tests using it too, BUT there are many nullpointer bugs if you try to
+ // deploy such a file. Using this class to try to catch those.
+ // TODO: Throw away this when we properly support doc-only SD files.
+
+ public UnproperSearch() {
+ // empty
+ }
+
+ @Override
+ public void addDocument(SDDocumentType docType) {
+ if (getName() == null) {
+ setName(docType.getName());
+ }
+ super.addDocument(docType);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/UnrankedRankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/UnrankedRankProfile.java
new file mode 100644
index 00000000000..f465112fbaf
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/UnrankedRankProfile.java
@@ -0,0 +1,28 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition;
+
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+
+/**
+ * A low-cost ranking profile to use for watcher queries etc.
+ *
+ * @author <a href="mailto:vegardh@yahoo-inc.com">Vegard Havdal</a>
+ */
+public class UnrankedRankProfile extends RankProfile {
+
+ public UnrankedRankProfile(Search search, RankProfileRegistry rankProfileRegistry) {
+ super("unranked", search, rankProfileRegistry);
+ try {
+ RankingExpression exp = new RankingExpression("value(0)");
+ this.setFirstPhaseRanking(exp);
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("Could not parse the ranking expression 'value(0)' when setting up " +
+ "the 'unranked' rank profile");
+ }
+ this.setIgnoreDefaultRankFeatures(true);
+ this.setKeepRankCount(0);
+ this.setRerankCount(0);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java
new file mode 100644
index 00000000000..1130e5630a3
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java
@@ -0,0 +1,157 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.Ranking;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Sorting;
+import com.yahoo.vespa.config.search.AttributesConfig;
+import com.yahoo.config.subscription.ConfigInstanceUtil;
+import com.yahoo.vespa.indexinglanguage.expressions.ToPositionExpression;
+
+import java.util.*;
+
+/**
+ * The set of all attribute fields defined by a search definition
+ *
+ * @author <a href="mailto:bratseth@overture.com">Jon S Bratseth</a>
+ */
+public class AttributeFields extends Derived implements AttributesConfig.Producer {
+ private Map<String, Attribute> attributes = new java.util.LinkedHashMap<>();
+
+ /**
+ * Flag indicating if a position-attribute has been found
+ */
+ private boolean hasPosition = false;
+
+ public AttributeFields(Search search) {
+ derive(search);
+ }
+
+ /**
+ * Derives everything from a field
+ */
+ protected void derive(SDField field, Search search) {
+ if (field.usesStructOrMap() &&
+ !field.getDataType().equals(PositionDataType.INSTANCE) &&
+ !field.getDataType().equals(DataType.getArray(PositionDataType.INSTANCE)))
+ {
+ return; // Ignore struct fields for indexed search (only implemented for streaming search)
+ }
+ deriveAttributes(field);
+ }
+
+ /**
+ * Return an attribute by name, or null if it doesn't exist
+ */
+ public Attribute getAttribute(String attributeName) {
+ return attributes.get(attributeName);
+ }
+
+ public boolean containsAttribute(String attributeName) {
+ return getAttribute(attributeName) != null;
+ }
+
+ /**
+ * Derives one attribute. TODO: Support non-default named attributes
+ */
+ private void deriveAttributes(SDField field) {
+ for (Attribute fieldAttribute : field.getAttributes().values()) {
+ Attribute attribute = getAttribute(fieldAttribute.getName());
+ if (attribute == null) {
+ attributes.put(fieldAttribute.getName(), fieldAttribute);
+ attribute = getAttribute(fieldAttribute.getName());
+ }
+ Ranking ranking = field.getRanking();
+ if (ranking != null && ranking.isFilter()) {
+ attribute.setEnableBitVectors(true);
+ attribute.setEnableOnlyBitVector(true);
+ }
+ }
+
+ if (field.containsExpression(ToPositionExpression.class)) {
+ // TODO: Move this check to processing and remove this
+ if (hasPosition) {
+ throw new IllegalArgumentException("Can not specify more than one " +
+ "set of position attributes per " + "field: " + field.getName());
+ }
+ hasPosition = true;
+ }
+ }
+
+ /**
+ * Returns a read only attribute iterator
+ */
+ public Iterator attributeIterator() {
+ return attributes().iterator();
+ }
+
+ public Collection<Attribute> attributes() {
+ return Collections.unmodifiableCollection(attributes.values());
+ }
+
+ public String toString() {
+ return "attributes " + getName();
+ }
+
+ protected String getDerivedName() {
+ return "attributes";
+ }
+
+ private Map<String, AttributesConfig.Attribute.Builder> toMap(List<AttributesConfig.Attribute.Builder> ls) {
+ Map<String, AttributesConfig.Attribute.Builder> ret = new LinkedHashMap<>();
+ for (AttributesConfig.Attribute.Builder builder : ls) {
+ ret.put((String) ConfigInstanceUtil.getField(builder, "name"), builder);
+ }
+ return ret;
+ }
+
+ @Override
+ public void getConfig(AttributesConfig.Builder builder) {
+ for (Attribute attribute : attributes.values()) {
+ AttributesConfig.Attribute.Builder aaB = new AttributesConfig.Attribute.Builder()
+ .name(attribute.getName())
+ .datatype(AttributesConfig.Attribute.Datatype.Enum.valueOf(attribute.getType().getExportAttributeTypeName()))
+ .collectiontype(AttributesConfig.Attribute.Collectiontype.Enum.valueOf(attribute.getCollectionType().getName()));
+ if (attribute.isRemoveIfZero()) {
+ aaB.removeifzero(true);
+ }
+ if (attribute.isCreateIfNonExistent()) {
+ aaB.createifnonexistent(true);
+ }
+ aaB.enablebitvectors(attribute.isEnabledBitVectors());
+ aaB.enableonlybitvector(attribute.isEnabledOnlyBitVector());
+ if (attribute.isFastSearch()) {
+ aaB.fastsearch(true);
+ }
+ if (attribute.isFastAccess()) {
+ aaB.fastaccess(true);
+ }
+ if (attribute.isHuge()) {
+ aaB.huge(true);
+ }
+ if (attribute.getSorting().isDescending()) {
+ aaB.sortascending(false);
+ }
+ if (attribute.getSorting().getFunction() != Sorting.Function.UCA) {
+ aaB.sortfunction(AttributesConfig.Attribute.Sortfunction.Enum.valueOf(attribute.getSorting().getFunction().toString()));
+ }
+ if (attribute.getSorting().getStrength() != Sorting.Strength.PRIMARY) {
+ aaB.sortstrength(AttributesConfig.Attribute.Sortstrength.Enum.valueOf(attribute.getSorting().getStrength().toString()));
+ }
+ if (!attribute.getSorting().getLocale().isEmpty()) {
+ aaB.sortlocale(attribute.getSorting().getLocale());
+ }
+ aaB.arity(attribute.arity());
+ aaB.lowerbound(attribute.lowerBound());
+ aaB.upperbound(attribute.upperBound());
+ aaB.densepostinglistthreshold(attribute.densePostingListThreshold());
+ if (attribute.tensorType().isPresent()) {
+ aaB.tensortype(attribute.tensorType().get().toString());
+ }
+ builder.attribute(aaB);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/Derived.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Derived.java
new file mode 100644
index 00000000000..643eeb23c87
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Derived.java
@@ -0,0 +1,136 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.document.Field;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.config.ConfigInstance.Builder;
+import com.yahoo.io.IOUtils;
+import com.yahoo.searchdefinition.Index;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.text.StringUtilities;
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+
+/**
+ * Superclass of all derived configurations
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public abstract class Derived implements Exportable {
+
+ private String name;
+
+ public String getName() { return name; }
+
+ protected final void setName(String name) { this.name=name; }
+
+ /**
+ * Derives the content of this configuration. This
+ * default calls derive(Document) for each document
+ * and derive(SDField) for each search definition level field
+ * AND sets the name of this to the name of the input search definition
+ */
+ protected void derive(Search search) {
+ setName(search.getName());
+ derive(search.getDocument(), search);
+ for (Index index : search.getExplicitIndices()) {
+ derive(index, search);
+ }
+ for (SDField field : search.allExtraFields() ) {
+ derive(field,search);
+ }
+ }
+
+
+ /**
+ * Derives the content of this configuration. This
+ * default calls derive(SDField) for each document field
+ */
+ protected void derive(SDDocumentType document,Search search) {
+ for (Field field : document.fieldSet()) {
+ SDField sdField = (SDField) field;
+ if (!sdField.isExtraField()) {
+ derive(sdField, search);
+ }
+ }
+ }
+
+ /**
+ * Derives the content of this configuration. This
+ * default does nothing.
+ */
+ protected void derive(SDField field,Search search) {}
+
+ /**
+ * Derives the content of this configuration. This
+ * default does nothing.
+ */
+ protected void derive(Index index, Search search) {
+ }
+
+ protected abstract String getDerivedName();
+
+ /** Returns the value of getName if true, the given number as a string otherwise */
+ protected String getIndex(int number,boolean labels) {
+ return labels ? getName() : String.valueOf(number);
+ }
+
+ /**
+ * Exports this derived configuration to its .cfg file
+ * in toDirectory
+ *
+ * @param toDirectory the directory to export to, or null
+ *
+ */
+ public final void export(String toDirectory)
+ throws IOException {
+ Writer writer=null;
+ try {
+ String fileName=getDerivedName() + ".cfg";
+ if (toDirectory!=null) writer=IOUtils.createWriter(toDirectory + "/" + fileName,false);
+ try {
+ exportBuilderConfig(writer);
+ } catch (ClassNotFoundException | InstantiationException
+ | IllegalAccessException | NoSuchMethodException
+ | SecurityException | IllegalArgumentException
+ | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ finally {
+ if (writer!=null) IOUtils.closeWriter(writer);
+ }
+ }
+
+ /**
+ * Checks what this is a producer of, instantiate that and export to writer
+ */
+ // TODO move to ReflectionUtil, and move that to unexported pkg
+ private void exportBuilderConfig(Writer writer) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException, IOException {
+ for (Class<?> intf : getClass().getInterfaces()) {
+ if (ConfigInstance.Producer.class.isAssignableFrom(intf)) {
+ Class<?> configClass = intf.getEnclosingClass();
+ String builderClassName = configClass.getCanonicalName()+"$Builder";
+ Class<?> builderClass = Class.forName(builderClassName);
+ ConfigInstance.Builder builder = (Builder) builderClass.newInstance();
+ Method getConfig = getClass().getMethod("getConfig", builderClass);
+ getConfig.invoke(this, builder);
+ ConfigInstance inst = (ConfigInstance) configClass.getConstructor(builderClass).newInstance(builder);
+ List<String> payloadL = ConfigInstance.serialize(inst);
+ String payload = StringUtilities.implodeMultiline(payloadL);
+ writer.write(payload);
+ }
+ }
+ }
+
+ @Override
+ public String getFileName() {
+ return getDerivedName() + ".cfg";
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java
new file mode 100644
index 00000000000..973e4e6100c
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/DerivedConfiguration.java
@@ -0,0 +1,184 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.config.ConfigInstance;
+import com.yahoo.config.model.application.provider.BaseDeployLogger;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.document.DocumenttypesConfig;
+import com.yahoo.document.config.DocumentmanagerConfig;
+import com.yahoo.io.IOUtils;
+import com.yahoo.protect.Validator;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.derived.validation.Validation;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+
+/**
+ * A set of all derived configuration of a search definition. Use this as a facade to individual configurations when
+ * necessary.
+ *
+ * @author bratseth
+ */
+public class DerivedConfiguration {
+
+ private Search search;
+ private Summaries summaries;
+ private SummaryMap summaryMap;
+ private Juniperrc juniperrc;
+ private AttributeFields attributeFields;
+ private RankProfileList rankProfileList;
+ private IndexingScript indexingScript;
+ private IndexInfo indexInfo;
+ private VsmFields streamingFields;
+ private VsmSummary streamingSummary;
+ private IndexSchema indexSchema;
+
+ /**
+ * Creates a complete derived configuration from a search definition.
+ *
+ * @param search The search to derive a configuration from. Derived objects will be snapshots, but this argument is
+ * live. Which means that this object will be inconsistent when the given search definition is later
+ * modified.
+ * @param rankProfileRegistry a {@link com.yahoo.searchdefinition.RankProfileRegistry}
+ */
+ public DerivedConfiguration(Search search, RankProfileRegistry rankProfileRegistry) {
+ this(search, null, new BaseDeployLogger(), rankProfileRegistry);
+ }
+
+ /**
+ * Creates a complete derived configuration snapshot from a search definition.
+ *
+ * @param search The search to derive a configuration from. Derived objects will be snapshots, but this
+ * argument is live. Which means that this object will be inconsistent when the given
+ * search definition is later modified.
+ * @param abstractSearchList Search definition this one inherits from, only superclass configuration should be
+ * generated. Null or empty list if there is none.
+ * @param deployLogger a {@link DeployLogger} for logging when
+ * doing operations on this
+ * @param rankProfileRegistry a {@link com.yahoo.searchdefinition.RankProfileRegistry}
+ */
+ public DerivedConfiguration(Search search, List<Search> abstractSearchList, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry) {
+ Validator.ensureNotNull("Search definition", search);
+ if (!search.isProcessed()) {
+ throw new IllegalArgumentException("Search '" + search.getName() + "' not processed.");
+ }
+ this.search = search;
+ if (!search.isDocumentsOnly()) {
+ streamingFields = new VsmFields(search);
+ streamingSummary = new VsmSummary(search);
+ }
+ if (abstractSearchList != null) {
+ for (Search abstractSearch : abstractSearchList) {
+ if (!abstractSearch.isProcessed()) {
+ throw new IllegalArgumentException("Search '" + search.getName() + "' not processed.");
+ }
+ }
+ }
+ if (!search.isDocumentsOnly()) {
+ summaries = new Summaries(search, deployLogger);
+ summaryMap = new SummaryMap(search, summaries);
+ juniperrc = new Juniperrc(search);
+ attributeFields = new AttributeFields(search);
+ rankProfileList = new RankProfileList(search, attributeFields, rankProfileRegistry);
+ indexingScript = new IndexingScript(search);
+ indexInfo = new IndexInfo(search);
+ indexSchema = new IndexSchema(search);
+ }
+ Validation.validate(this, search);
+ }
+
+ /**
+ * Exports a complete set of configuration-server format config files.
+ *
+ * @param toDirectory the directory to export to, current dir if null
+ * @throws IOException if exporting fails, some files may still be created
+ */
+ public void export(String toDirectory) throws IOException {
+ if (!search.isDocumentsOnly()) {
+ summaries.export(toDirectory);
+ summaryMap.export(toDirectory);
+ juniperrc.export(toDirectory);
+ attributeFields.export(toDirectory);
+ streamingFields.export(toDirectory);
+ streamingSummary.export(toDirectory);
+ indexSchema.export(toDirectory);
+ rankProfileList.export(toDirectory);
+ indexingScript.export(toDirectory);
+ indexInfo.export(toDirectory);
+ }
+ }
+
+ public static void exportDocuments(DocumentmanagerConfig.Builder documentManagerCfg, String toDirectory) throws IOException {
+ exportCfg(new DocumentmanagerConfig(documentManagerCfg), toDirectory + "/" + "documentmanager.cfg");
+ }
+
+ public static void exportDocuments(DocumenttypesConfig.Builder documentTypesCfg, String toDirectory) throws IOException {
+ exportCfg(new DocumenttypesConfig(documentTypesCfg), toDirectory + "/" + "documenttypes.cfg");
+ }
+
+ private static void exportCfg(ConfigInstance instance, String fileName) throws IOException {
+ Writer writer = null;
+ try {
+ writer = IOUtils.createWriter(fileName, false);
+ if (writer != null) {
+ writer.write(instance.toString());
+ writer.write("\n");
+ }
+ } finally {
+ if (writer != null) {
+ IOUtils.closeWriter(writer);
+ }
+ }
+ }
+
+ public Summaries getSummaries() {
+ return summaries;
+ }
+
+ public AttributeFields getAttributeFields() {
+ return attributeFields;
+ }
+
+ public IndexingScript getIndexingScript() {
+ return indexingScript;
+ }
+
+ public IndexInfo getIndexInfo() {
+ return indexInfo;
+ }
+
+ public void setIndexingScript(IndexingScript script) {
+ this.indexingScript = script;
+ }
+
+ public Search getSearch() {
+ return search;
+ }
+
+ public RankProfileList getRankProfileList() {
+ return rankProfileList;
+ }
+
+ public VsmSummary getVsmSummary() {
+ return streamingSummary;
+ }
+
+ public VsmFields getVsmFields() {
+ return streamingFields;
+ }
+
+ public IndexSchema getIndexSchema() {
+ return indexSchema;
+ }
+
+ public Juniperrc getJuniperrc() {
+ return juniperrc;
+ }
+
+ public SummaryMap getSummaryMap() {
+ return summaryMap;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/Deriver.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Deriver.java
new file mode 100644
index 00000000000..76bf4492788
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Deriver.java
@@ -0,0 +1,82 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+import com.yahoo.document.DocumenttypesConfig;
+import com.yahoo.document.config.DocumentmanagerConfig;
+import com.yahoo.searchdefinition.SearchBuilder;
+import com.yahoo.searchdefinition.UnprocessingSearchBuilder;
+import com.yahoo.searchdefinition.parser.ParseException;
+import com.yahoo.vespa.configmodel.producers.DocumentManager;
+import com.yahoo.vespa.configmodel.producers.DocumentTypes;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Auxiliary facade for deriving configs from search definitions
+ *
+ * @author bratseth
+ */
+public class Deriver {
+
+ /**
+ * Derives only document manager.
+ *
+ *
+ * @param sdFileNames The name of the search definition files to derive from.
+ * @param toDir The directory to write configuration to.
+ * @return The list of Search objects, possibly "unproper ones", from sd files containing only document
+ */
+ public static SearchBuilder deriveDocuments(List<String> sdFileNames, String toDir) {
+ SearchBuilder builder = getUnprocessingSearchBuilder(sdFileNames);
+ DocumentmanagerConfig.Builder documentManagerCfg = new DocumentManager().produce(builder.getModel(), new DocumentmanagerConfig.Builder());
+ try {
+ DerivedConfiguration.exportDocuments(documentManagerCfg, toDir);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ return builder;
+ }
+
+ public static SearchBuilder getSearchBuilder(List<String> sds) {
+ SearchBuilder builder = new SearchBuilder();
+ try {
+ for (String s : sds) {
+ builder.importFile(s);
+ }
+ } catch (ParseException | IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ builder.build();
+ return builder;
+ }
+
+ public static SearchBuilder getUnprocessingSearchBuilder(List<String> sds) {
+ SearchBuilder builder = new UnprocessingSearchBuilder();
+ try {
+ for (String s : sds) {
+ builder.importFile(s);
+ }
+ } catch (ParseException | IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ builder.build();
+ return builder;
+ }
+
+ public static DocumentmanagerConfig.Builder getDocumentManagerConfig(String sd) {
+ return getDocumentManagerConfig(Collections.singletonList(sd));
+ }
+
+ public static DocumentmanagerConfig.Builder getDocumentManagerConfig(List<String> sds) {
+ return new DocumentManager().produce(getSearchBuilder(sds).getModel(), new DocumentmanagerConfig.Builder());
+ }
+
+ public static DocumenttypesConfig.Builder getDocumentTypesConfig(String sd) {
+ return getDocumentTypesConfig(Collections.singletonList(sd));
+ }
+
+ public static DocumenttypesConfig.Builder getDocumentTypesConfig(List<String> sds) {
+ return new DocumentTypes().produce(getSearchBuilder(sds).getModel(), new DocumenttypesConfig.Builder());
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/Exportable.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Exportable.java
new file mode 100644
index 00000000000..bcc295b91e0
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Exportable.java
@@ -0,0 +1,26 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+/**
+ * Classes exportable to configurations
+ *
+ * @author <a href="mailto:bratseth@overture.com">bratseth</a>
+ */
+public interface Exportable {
+
+ /**
+ * Exports the configuration of this object
+ *
+ *
+ * @param toDirectory the directory to export to, does not write to disk if null
+ * @throws java.io.IOException if exporting fails, some files may still be created
+ */
+ public void export(String toDirectory) throws java.io.IOException;
+
+ /**
+ * The (short) name of the exported file
+ * @return a String with the (short) name of the exported file
+ */
+ public String getFileName();
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/FieldRankSettings.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/FieldRankSettings.java
new file mode 100644
index 00000000000..9dfc1bc2af7
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/FieldRankSettings.java
@@ -0,0 +1,79 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * The rank settings of a field used for native rank features.
+ *
+ * @author <a href="mailto:geirst@yahoo-inc.com">Geir Storli</a>
+ */
+public class FieldRankSettings {
+
+ private static final Logger logger = Logger.getLogger(FieldRankSettings.class.getName());
+
+ private String fieldName;
+
+ private final Map<String, NativeTable> tables = new LinkedHashMap<>();
+
+ public FieldRankSettings(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ public void addTable(NativeTable table) {
+ NativeTable existing = tables.get(table.getType().getName());
+ if (existing != null) {
+ logger.info("Using already specified rank table " + existing + " for field " + fieldName + ", not " + table);
+ return;
+ }
+ tables.put(table.getType().getName(), table);
+ }
+
+ public static boolean isIndexFieldTable(NativeTable table) {
+ return isFieldMatchTable(table) || isProximityTable(table);
+ }
+
+ public static boolean isAttributeFieldTable(NativeTable table) {
+ return isAttributeMatchTable(table);
+ }
+
+ private static boolean isFieldMatchTable(NativeTable table) {
+ return (table.getType().equals(NativeTable.Type.FIRST_OCCURRENCE) ||
+ table.getType().equals(NativeTable.Type.OCCURRENCE_COUNT));
+ }
+
+ private static boolean isAttributeMatchTable(NativeTable table) {
+ return (table.getType().equals(NativeTable.Type.WEIGHT));
+ }
+
+ private static boolean isProximityTable(NativeTable table) {
+ return (table.getType().equals(NativeTable.Type.PROXIMITY) ||
+ table.getType().equals(NativeTable.Type.REVERSE_PROXIMITY));
+ }
+
+ public Map<String,String> deriveRankProperties(int part) {
+ Map<String,String> ret = new LinkedHashMap<>();
+ int i = part;
+ for (Iterator<NativeTable> itr = tables.values().iterator(); itr.hasNext(); ++i) {
+ NativeTable table = itr.next();
+ if (isFieldMatchTable(table)) {
+ ret.put("nativeFieldMatch." + table.getType().getName() + "." + fieldName + ".part" + i, table.getName());
+ }
+ if (isAttributeMatchTable(table)) {
+ ret.put("nativeAttributeMatch." + table.getType().getName() + "." + fieldName + ".part" + i, table.getName());
+ }
+ if (isProximityTable(table)) {
+ ret.put("nativeProximity." + table.getType().getName() + "." + fieldName + ".part" + i, table.getName());
+ }
+ }
+ return ret;
+ }
+
+ public String toString() {
+ return "rank settings of field " + fieldName;
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/FieldResultTransform.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/FieldResultTransform.java
new file mode 100644
index 00000000000..d61e57a621e
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/FieldResultTransform.java
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+
+/**
+ * The result transformation of a named field
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class FieldResultTransform {
+
+ private String fieldName;
+
+ private SummaryTransform transform;
+
+ private String argument;
+
+ public FieldResultTransform(String fieldName,SummaryTransform transform,String argument) {
+ this.fieldName=fieldName;
+ this.transform=transform;
+ this.argument = argument;
+ }
+
+ public String getFieldName() { return fieldName; }
+
+ public SummaryTransform getTransform() { return transform; }
+
+ public void setTransform(SummaryTransform transform) { this.transform=transform; }
+
+ /** Returns the argument of this (used as input to the backend docsum rewriter) */
+ public String getArgument() { return argument; }
+
+ public int hashCode() {
+ return fieldName.hashCode() + 11*transform.hashCode() + 17* argument.hashCode();
+ }
+
+ public boolean equals(Object o) {
+ if (! (o instanceof FieldResultTransform)) return false;
+ FieldResultTransform other=(FieldResultTransform)o;
+
+ return
+ this.fieldName.equals(other.fieldName) &&
+ this.transform.equals(other.transform) &&
+ this.argument.equals(other.argument);
+ }
+
+ public String toString() {
+ String sourceString="";
+ if ( ! argument.equals(fieldName))
+ sourceString=" (argument: " + argument + ")";
+ return "field " + fieldName + ": " + transform + sourceString;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/Index.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Index.java
new file mode 100644
index 00000000000..cab4176d696
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Index.java
@@ -0,0 +1,64 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.document.CollectionDataType;
+import com.yahoo.document.DataType;
+import com.yahoo.document.NumericDataType;
+import com.yahoo.document.datatypes.*;
+
+/**
+ * A type of an index structure
+ *
+ * @author bratseth
+ */
+public class Index {
+
+ /** The index type enumeration */
+ public static class Type {
+
+ public static final Type TEXT=new Type("text");
+ public static final Type INT64=new Type("long");
+ public static final Type BOOLEANTREE=new Type("booleantree");
+
+ private String name;
+
+ private Type(String name) {
+ this.name=name;
+ }
+
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ public String getName() { return name; }
+
+ public boolean equals(Object other) {
+ if ( ! (other instanceof Type)) return false;
+ return this.name.equals(((Type)other).name);
+ }
+
+ public String toString() {
+ return "type: " + name;
+ }
+
+ }
+
+ /** Sets the right index type from a field type */
+ public static Type convertType(DataType fieldType) {
+ FieldValue fval = fieldType.createFieldValue();
+ if (fieldType instanceof NumericDataType) {
+ return Type.INT64;
+ } else if (fval instanceof StringFieldValue) {
+ return Type.TEXT;
+ } else if (fval instanceof Raw) {
+ return Type.BOOLEANTREE;
+ } else if (fval instanceof PredicateFieldValue) {
+ return Type.BOOLEANTREE;
+ } else if (fieldType instanceof CollectionDataType) {
+ return convertType(((CollectionDataType) fieldType).getNestedType());
+ } else {
+ throw new IllegalArgumentException("Don't know which index type to " +
+ "convert " + fieldType + " to");
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java
new file mode 100644
index 00000000000..e98ee662b3a
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexInfo.java
@@ -0,0 +1,559 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.Index;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.*;
+import com.yahoo.searchdefinition.processing.ExactMatch;
+import com.yahoo.searchdefinition.processing.NGramMatch;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.search.config.IndexInfoConfig;
+
+import java.util.*;
+
+/**
+ * Per-index commands which should be applied to queries prior to searching
+ *
+ * @author bratseth
+ */
+public class IndexInfo extends Derived implements IndexInfoConfig.Producer {
+
+ private static final String CMD_ATTRIBUTE = "attribute";
+ private static final String CMD_DEFAULT_POSITION = "default-position";
+ private static final String CMD_DYNTEASER = "dynteaser";
+ private static final String CMD_FULLURL = "fullurl";
+ private static final String CMD_HIGHLIGHT = "highlight";
+ private static final String CMD_INDEX = "index";
+ private static final String CMD_LOWERCASE = "lowercase";
+ private static final String CMD_MATCH_GROUP = "match-group ";
+ private static final String CMD_NORMALIZE = "normalize";
+ private static final String CMD_STEM = "stem";
+ private static final String CMD_URLHOST = "urlhost";
+ private static final String CMD_WORD = "word";
+ private static final String CMD_PLAIN_TOKENS = "plain-tokens";
+ private static final String CMD_MULTIVALUE = "multivalue";
+ private static final String CMD_FAST_SEARCH = "fast-search";
+ private static final String CMD_PREDICATE_BOUNDS = "predicate-bounds";
+ private static final String CMD_NUMERICAL = "numerical";
+ private Set<IndexCommand> commands = new java.util.LinkedHashSet<>();
+ private Map<String, String> aliases = new java.util.LinkedHashMap<>();
+ private Map<String, FieldSet> fieldSets;
+ private Search search;
+
+ public IndexInfo(Search search) {
+ this.fieldSets = search.fieldSets().userFieldSets();
+ addIndexCommand("sddocname", CMD_INDEX);
+ addIndexCommand("sddocname", CMD_WORD);
+ derive(search);
+ }
+
+ protected void derive(Search search) {
+ super.derive(search); // Derive per field
+ this.search = search;
+ // Populate fieldsets with actual field objects, bit late to do that here but
+ for (FieldSet fs : fieldSets.values()) {
+ for (String fieldName : fs.getFieldNames()) {
+ fs.fields().add(search.getField(fieldName));
+ }
+ }
+ // Must follow, because index settings overrides field settings
+ for (Index index : search.getExplicitIndices()) {
+ derive(index, search);
+ }
+
+ // Commands for summary fields
+ // TODO: Move to fieldinfo and implement differently. This is not right
+ for (SummaryField summaryField : search.getUniqueNamedSummaryFields().values()) {
+ if (summaryField.getTransform().isTeaser()) {
+ addIndexCommand(summaryField.getName(), CMD_DYNTEASER);
+ }
+ if (summaryField.getTransform().isBolded()) {
+ addIndexCommand(summaryField.getName(), CMD_HIGHLIGHT);
+ }
+ }
+ }
+
+ protected void derive(Index index, Search search) {
+ if (index.getMatchGroup().size() > 0) {
+ addIndexCommand(index.getName(), CMD_MATCH_GROUP + toSpaceSeparated(index.getMatchGroup()));
+ }
+ }
+
+ private String toSpaceSeparated(Collection c) {
+ StringBuffer b = new StringBuffer();
+ for (Iterator i = c.iterator(); i.hasNext();) {
+ b.append(i.next());
+ if (i.hasNext()) {
+ b.append(" ");
+ }
+ }
+ return b.toString();
+ }
+
+ protected void derive(SDField field, Search search) {
+ if (field.getDataType().equals(DataType.PREDICATE)) {
+ Index index = field.getIndex(field.getName());
+ if (index != null) {
+ BooleanIndexDefinition options = index.getBooleanIndexDefiniton();
+ if (options.hasLowerBound() || options.hasUpperBound()) {
+ addIndexCommand(field.getName(), CMD_PREDICATE_BOUNDS + " [" +
+ (options.hasLowerBound() ? Long.toString(options.getLowerBound()) : "") + ".." +
+ (options.hasUpperBound() ? Long.toString(options.getUpperBound()) : "") + "]");
+ }
+ }
+ }
+
+ // Field level aliases
+ for (Map.Entry<String, String> e : field.getAliasToName().entrySet()) {
+ String alias = e.getKey();
+ String name = e.getValue();
+ addIndexAlias(alias, name);
+ }
+ if (field.usesStructOrMap()) {
+ for (SDField structField : field.getStructFields()) {
+ derive(structField, search); // Recursion
+ }
+ }
+
+ if (field.getDataType().equals(PositionDataType.INSTANCE) ||
+ field.getDataType().equals(DataType.getArray(PositionDataType.INSTANCE)))
+ {
+ addIndexCommand(field.getName(), CMD_DEFAULT_POSITION);
+ }
+
+ addIndexCommand(field, CMD_INDEX); // List the indices
+
+ if (field.doesIndexing() || field.doesLowerCasing()) {
+ addIndexCommand(field, CMD_LOWERCASE);
+ }
+
+ if (field.getDataType().isMultivalue()) {
+ addIndexCommand(field, CMD_MULTIVALUE);
+ }
+
+ if (field.doesAttributing() && !field.doesIndexing()) {
+ addIndexCommand(field.getName(), CMD_ATTRIBUTE);
+ Attribute attribute = field.getAttributes().get(field.getName());
+ if (attribute != null && attribute.isFastSearch())
+ addIndexCommand(field.getName(), CMD_FAST_SEARCH);
+ } else if (field.doesIndexing()) {
+ if (stemSomehow(field, search)) {
+ addIndexCommand(field, stemCmd(field, search), new StemmingOverrider(this, search));
+ }
+ if (normalizeAccents(field)) {
+ addIndexCommand(field, CMD_NORMALIZE);
+ }
+ }
+
+ if (isUriField(field)) {
+ addUriIndexCommands(field);
+ }
+
+ if (field.getDataType() instanceof NumericDataType) {
+ addIndexCommand(field, CMD_NUMERICAL);
+ }
+
+ // Explicit commands
+ for (String command : field.getQueryCommands()) {
+ addIndexCommand(field, command);
+ }
+
+ }
+
+ static String stemCmd(SDField field, Search search) {
+ return CMD_STEM + ":" + field.getStemming(search).toStemMode();
+ }
+
+ private boolean stemSomehow(SDField field, Search search) {
+ if (field.getStemming(search).equals(Stemming.NONE)) return false;
+ return isTypeOrNested(field, DataType.STRING);
+ }
+
+ private boolean normalizeAccents(SDField field) {
+ return field.getNormalizing().doRemoveAccents() && isTypeOrNested(field, DataType.STRING);
+ }
+
+ private boolean isTypeOrNested(SDField field, DataType type) {
+ return field.getDataType().equals(type) || field.getDataType().equals(DataType.getArray(type)) ||
+ field.getDataType().equals(DataType.getWeightedSet(type));
+ }
+
+ private boolean isUriField(Field field) {
+ DataType fieldType = field.getDataType();
+ if (DataType.URI.equals(fieldType)) {
+ return true;
+ }
+ if (fieldType instanceof CollectionDataType &&
+ DataType.URI.equals(((CollectionDataType)fieldType).getNestedType()))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ private void addUriIndexCommands(SDField field) {
+ String fieldName = field.getName();
+ addIndexCommand(fieldName, CMD_FULLURL);
+ addIndexCommand(fieldName, CMD_LOWERCASE);
+ addIndexCommand(fieldName + "." + fieldName, CMD_FULLURL);
+ addIndexCommand(fieldName + "." + fieldName, CMD_LOWERCASE);
+ addIndexCommand(fieldName + ".path", CMD_FULLURL);
+ addIndexCommand(fieldName + ".path", CMD_LOWERCASE);
+ addIndexCommand(fieldName + ".query", CMD_FULLURL);
+ addIndexCommand(fieldName + ".query", CMD_LOWERCASE);
+ addIndexCommand(fieldName + ".hostname", CMD_URLHOST);
+ addIndexCommand(fieldName + ".hostname", CMD_LOWERCASE);
+
+ // XXX hack
+ Index index = field.getIndex("hostname");
+ if (index != null) {
+ addIndexCommand(index, CMD_URLHOST);
+ }
+ }
+
+ /**
+ * Sets a command for all indices of a field
+ */
+ private void addIndexCommand(Index index, String command) {
+ addIndexCommand(index.getName(), command);
+ }
+
+ /**
+ * Sets a command for all indices of a field
+ */
+ private void addIndexCommand(SDField field, String command) {
+ addIndexCommand(field, command, null);
+ }
+
+ /**
+ * Sets a command for all indices of a field
+ */
+ private void addIndexCommand(SDField field, String command, IndexOverrider overrider) {
+ if (overrider == null || !overrider.override(field.getName(), command, field)) {
+ addIndexCommand(field.getName(), command);
+ }
+ }
+
+ private void addIndexCommand(String indexName, String command) {
+ commands.add(new IndexCommand(indexName, command));
+ }
+
+ private void addIndexAlias(String alias, String indexName) {
+ aliases.put(alias, indexName);
+ }
+
+ /**
+ * Returns whether a particular command is prsent in this index info
+ */
+ public boolean hasCommand(String indexName, String command) {
+ return commands.contains(new IndexCommand(indexName, command));
+ }
+
+ private boolean notInCommands(String index) {
+ for (IndexCommand command : commands) {
+ if (command.getIndex().equals(index)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void getConfig(IndexInfoConfig.Builder builder) {
+ IndexInfoConfig.Indexinfo.Builder iiB = new IndexInfoConfig.Indexinfo.Builder();
+ iiB.name(getName());
+ for (IndexCommand command : commands) {
+ iiB.command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(command.getIndex())
+ .command(command.getCommand()));
+ }
+ // Make user defined field sets searchable
+ for (FieldSet fieldSet : fieldSets.values()) {
+ if (notInCommands(fieldSet.getName())) {
+ addFieldSetCommands(iiB, fieldSet);
+ }
+ }
+
+ for (Map.Entry<String, String> e : aliases.entrySet()) {
+ iiB.alias(
+ new IndexInfoConfig.Indexinfo.Alias.Builder()
+ .alias(e.getKey())
+ .indexname(e.getValue()));
+ }
+ builder.indexinfo(iiB);
+ }
+
+ // TODO: This implementation is completely brain dead.
+ // Move it to the FieldSetValidity processor (and rename it) as that already has to look at this.
+ // Also add more explicit testing of this, e.g to indexinfo_fieldsets in ExportingTestCase. - Jon
+ private void addFieldSetCommands(IndexInfoConfig.Indexinfo.Builder iiB, FieldSet fieldSet) {
+ // Explicit query commands on the field set, overrides everything.
+ if (!fieldSet.queryCommands().isEmpty()) {
+ for (String qc : fieldSet.queryCommands()) {
+ iiB.command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(fieldSet.getName())
+ .command(qc));
+ }
+ return;
+ }
+ boolean anyIndexing = false;
+ boolean anyAttributing = false;
+ boolean anyLowerCasing = false;
+ boolean anyStemming = false;
+ boolean anyNormalizing = false;
+ String stemmingCommand = null;
+ Matching fieldSetMatching = fieldSet.getMatching(); // null if no explicit matching
+ // First a pass over the fields to read some params to decide field settings implicitly:
+ for (SDField field : fieldSet.fields()) {
+ if (field.doesIndexing()) {
+ anyIndexing = true;
+ }
+ if (field.doesAttributing()) {
+ anyAttributing = true;
+ }
+ if (field.doesIndexing() || field.doesLowerCasing()) {
+ anyLowerCasing = true;
+ }
+ if (stemming(field)) {
+ anyStemming = true;
+ stemmingCommand = CMD_STEM + ":" + getEffectiveStemming(field).toStemMode();
+ }
+ if (field.getNormalizing().doRemoveAccents()) {
+ anyNormalizing = true;
+ }
+ if (fieldSetMatching == null && field.getMatching().getType() != Matching.defaultType)
+ fieldSetMatching = field.getMatching();
+ }
+ if (anyIndexing && anyAttributing && fieldSet.getMatching() == null) {
+ // We have both attributes and indexes and no explicit match setting ->
+ // use default matching as that at least works if the data in the attribute consists
+ // of single tokens only.
+ fieldSetMatching = new Matching();
+ }
+ if (anyLowerCasing) {
+ iiB.command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(fieldSet.getName())
+ .command(CMD_LOWERCASE));
+ }
+ if (hasMultiValueField(fieldSet)) {
+ iiB.command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(fieldSet.getName())
+ .command(CMD_MULTIVALUE));
+ }
+ if (anyIndexing) {
+ iiB.command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(fieldSet.getName())
+ .command(CMD_INDEX));
+ if ( ! isExactMatch(fieldSetMatching)) {
+ if (anyStemming) {
+ iiB.command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(fieldSet.getName())
+ .command(stemmingCommand));
+ }
+ if (anyNormalizing)
+ iiB.command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(fieldSet.getName())
+ .command(CMD_NORMALIZE));
+ }
+ } else {
+ // Assume only attribute fields
+ iiB
+ .command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(fieldSet.getName())
+ .command(CMD_ATTRIBUTE))
+ .command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(fieldSet.getName())
+ .command(CMD_INDEX));
+ }
+ if (fieldSetMatching != null) {
+ // Explicit matching set on fieldset
+ if (fieldSetMatching.getType().equals(Matching.Type.EXACT)) {
+ String term = fieldSetMatching.getExactMatchTerminator();
+ if (term==null) term=ExactMatch.DEFAULT_EXACT_TERMINATOR;
+ iiB.command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(fieldSet.getName())
+ .command("exact "+term));
+ } else if (fieldSetMatching.getType().equals(Matching.Type.WORD)) {
+ iiB.command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(fieldSet.getName())
+ .command(CMD_WORD));
+ } else if (fieldSetMatching.getType().equals(Matching.Type.GRAM)) {
+ iiB.command(
+ new IndexInfoConfig.Indexinfo.Command.Builder()
+ .indexname(fieldSet.getName())
+ .command("ngram "+(fieldSetMatching.getGramSize()>0 ? fieldSetMatching.getGramSize() : NGramMatch.DEFAULT_GRAM_SIZE)));
+ } else if (fieldSetMatching.getType().equals(Matching.Type.TEXT)) {
+
+ }
+
+ }
+
+ }
+
+ private boolean hasMultiValueField(FieldSet fieldSet) {
+ for (SDField field : fieldSet.fields()) {
+ if (field.getDataType().isMultivalue())
+ return true;
+ }
+ return false;
+ }
+
+ private Stemming getEffectiveStemming(SDField field) {
+ Stemming active = field.getStemming(search);
+ if (field.getIndex(field.getName()) != null) {
+ if (field.getIndex(field.getName()).getStemming()!=null) {
+ active = field.getIndex(field.getName()).getStemming();
+ }
+ }
+ if (active != null) {
+ return active;
+ }
+ // assume default
+ return Stemming.SHORTEST;
+ }
+
+ private boolean stemming(SDField field) {
+ if (field.getStemming() != null) {
+ return !field.getStemming().equals(Stemming.NONE);
+ }
+ if (search.getStemming()==Stemming.NONE) return false;
+ if (field.getIndex(field.getName())==null) return true;
+ if (field.getIndex(field.getName()).getStemming()==null) return true;
+ return !(field.getIndex(field.getName()).getStemming().equals(Stemming.NONE));
+ }
+
+ private boolean isExactMatch(Matching m) {
+ if (m==null) return false;
+ if (m.getType().equals(Matching.Type.EXACT)) return true;
+ if (m.getType().equals(Matching.Type.WORD)) return true;
+ return false;
+ }
+
+ /**
+ * Returns a read only iterator over the index commands of this
+ */
+ public Iterator<IndexCommand> indexCommandIterator() {
+ return Collections.unmodifiableSet(commands).iterator();
+ }
+
+ protected String getDerivedName() {
+ return "index-info";
+ }
+
+ /**
+ * An index command. Null commands are also represented, to detect consistency issues. This is an (immutable) value
+ * object.
+ */
+ public static class IndexCommand {
+
+ private String index;
+
+ private String command;
+
+ public IndexCommand(String index, String command) {
+ this.index = index;
+ this.command = command;
+ }
+
+ public String getIndex() {
+ return index;
+ }
+
+ public String getCommand() {
+ return command;
+ }
+
+ /**
+ * Returns true if this is the null command (do nothing)
+ */
+ public boolean isNull() {
+ return command.equals("");
+ }
+
+ public int hashCode() {
+ return index.hashCode() + 17 * command.hashCode();
+ }
+
+ public boolean equals(Object object) {
+ if (!(object instanceof IndexCommand)) {
+ return false;
+ }
+
+ IndexCommand other = (IndexCommand)object;
+ return
+ other.index.equals(this.index) &&
+ other.command.equals(this.command);
+ }
+
+ public String toString() {
+ return "index command " + command + " on index " + index;
+ }
+
+ }
+
+ /**
+ * A command which may override the command setting of a field for a particular index
+ */
+ private static abstract class IndexOverrider {
+
+ protected IndexInfo owner;
+
+ public IndexOverrider(IndexInfo owner) {
+ this.owner = owner;
+ }
+
+ /**
+ * Override the setting of this index for this field, returns true if overriden, false if this index should be
+ * set according to the field
+ */
+ public abstract boolean override(String indexName, String command, SDField field);
+
+ }
+
+ private static class StemmingOverrider extends IndexOverrider {
+
+ private Search search;
+
+ public StemmingOverrider(IndexInfo owner, Search search) {
+ super(owner);
+ this.search = search;
+ }
+
+ public boolean override(String indexName, String command, SDField field) {
+ if (search == null) {
+ return false;
+ }
+
+ Index index = search.getIndex(indexName);
+ if (index == null) {
+ return false;
+ }
+
+ Stemming indexStemming = index.getStemming();
+ if (indexStemming == null) {
+ return false;
+ }
+
+ if (Stemming.NONE.equals(indexStemming)) {
+ // Add nothing
+ } else {
+ owner.addIndexCommand(indexName, CMD_STEM + ":" + indexStemming.toStemMode());
+ }
+ return true;
+ }
+
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java
new file mode 100644
index 00000000000..05b8a1bf5e7
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexSchema.java
@@ -0,0 +1,231 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.BooleanIndexDefinition;
+import com.yahoo.searchdefinition.document.FieldSet;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.config.search.IndexschemaConfig;
+
+import java.util.*;
+
+/**
+ * Deriver of indexschema config containing information of all index fields with name and data type.
+ *
+ * @author <a href="mailto:geirst@yahoo-inc.com">Geir Storli</a>
+ */
+public class IndexSchema extends Derived implements IndexschemaConfig.Producer {
+
+ private final List<IndexField> fields = new ArrayList<>();
+ private final Map<String, FieldCollection> collections = new LinkedHashMap<>();
+ private final Map<String, FieldSet> fieldSets = new LinkedHashMap<>();
+
+ public IndexSchema(Search search) {
+ fieldSets.putAll(search.fieldSets().userFieldSets());
+ derive(search);
+ }
+
+ public boolean containsField(String fieldName) {
+ return fields.stream().anyMatch(field -> field.getName().equals(fieldName));
+ }
+
+ @Override
+ protected void derive(Search search) {
+ super.derive(search);
+ }
+
+ private void deriveIndexFields(SDField field, Search search) {
+ if (!field.doesIndexing() &&
+ !field.isIndexStructureField())
+ {
+ return;
+ }
+ List<Field> lst = flattenField(field);
+ if (lst.isEmpty()) {
+ return;
+ }
+ String fieldName = field.getName();
+ for (Field flatField : lst) {
+ deriveIndexFields(flatField, search);
+ }
+ if (lst.size() > 1) {
+ FieldSet fieldSet = new FieldSet(fieldName);
+ for (Field flatField : lst) {
+ fieldSet.addFieldName(flatField.getName());
+ }
+ fieldSets.put(fieldName, fieldSet);
+ }
+ }
+
+ private void deriveIndexFields(Field field, Search search) {
+ IndexField toAdd = new IndexField(field.getName(), Index.convertType(field.getDataType()), field.getDataType());
+ com.yahoo.searchdefinition.Index definedIndex = search.getIndex(field.getName());
+ if (definedIndex != null) {
+ toAdd.setIndexSettings(definedIndex);
+ }
+ fields.add(toAdd);
+ addFieldToCollection(field.getName(), field.getName()); // implicit
+ }
+
+ private FieldCollection getCollection(String collectionName) {
+ FieldCollection retval = collections.get(collectionName);
+ if (retval == null) {
+ collections.put(collectionName, new FieldCollection(collectionName));
+ return collections.get(collectionName);
+ }
+ return retval;
+ }
+
+ private void addFieldToCollection(String fieldName, String collectionName) {
+ FieldCollection collection = getCollection(collectionName);
+ collection.fields.add(fieldName);
+ }
+
+ @Override
+ protected void derive(SDField field, Search search) {
+ if (field.usesStructOrMap()) {
+ return; // unsupported
+ }
+ deriveIndexFields(field, search);
+ }
+
+ @Override
+ protected String getDerivedName() {
+ return "indexschema";
+ }
+
+ @Override
+ public void getConfig(IndexschemaConfig.Builder icB) {
+ for (int i = 0; i < fields.size(); ++i) {
+ IndexField f = fields.get(i);
+ IndexschemaConfig.Indexfield.Builder ifB = new IndexschemaConfig.Indexfield.Builder()
+ .name(f.getName())
+ .datatype(IndexschemaConfig.Indexfield.Datatype.Enum.valueOf(f.getType()))
+ .prefix(f.hasPrefix())
+ .phrases(f.hasPhrases())
+ .positions(f.hasPositions());
+ if (f.getSdType() !=null && !f.getSdType().equals(com.yahoo.searchdefinition.Index.Type.VESPA)) {
+ ifB.indextype(IndexschemaConfig.Indexfield.Indextype.Enum.valueOf(f.getSdType().toString()));
+ }
+ if (!f.getCollectionType().equals("SINGLE")) {
+ ifB.collectiontype(IndexschemaConfig.Indexfield.Collectiontype.Enum.valueOf(f.getCollectionType()));
+ }
+ icB.indexfield(ifB);
+ }
+ for (FieldSet fieldSet : fieldSets.values()) {
+ IndexschemaConfig.Fieldset.Builder fsB = new IndexschemaConfig.Fieldset.Builder()
+ .name(fieldSet.getName());
+ for (String f : fieldSet.getFieldNames()) {
+ fsB.field(new IndexschemaConfig.Fieldset.Field.Builder()
+ .name(f));
+ }
+ icB.fieldset(fsB);
+ }
+ }
+
+ static List<Field> flattenField(Field field) {
+ DataType fieldType = field.getDataType();
+ if (fieldType.getPrimitiveType() != null){
+ return Collections.singletonList(field);
+ }
+ if (fieldType instanceof ArrayDataType) {
+ boolean header = field.isHeader();
+ List<Field> ret = new LinkedList<>();
+ Field innerField = new Field(field.getName(), ((ArrayDataType)fieldType).getNestedType(), header);
+ for (Field flatField : flattenField(innerField)) {
+ ret.add(new Field(flatField.getName(), DataType.getArray(flatField.getDataType()), header));
+ }
+ return ret;
+ }
+ if (fieldType instanceof StructuredDataType) {
+ List<Field> ret = new LinkedList<>();
+ String fieldName = field.getName();
+ for (Field childField : ((StructuredDataType)fieldType).getFields()) {
+ for (Field flatField : flattenField(childField)) {
+ ret.add(new Field(fieldName + "." + flatField.getName(), flatField));
+ }
+ }
+ return ret;
+ }
+ throw new UnsupportedOperationException(fieldType.getName());
+ }
+
+ public List<IndexField> getFields() {
+ return fields;
+ }
+
+ /**
+ * Representation of an index field with name and data type.
+ */
+ public static class IndexField {
+ private String name;
+ private Index.Type type;
+ private com.yahoo.searchdefinition.Index.Type sdType; // The index type in "user intent land"
+ private DataType sdFieldType;
+ private boolean prefix = false;
+ private boolean phrases = false; // TODO dead, but keep a while to ensure config compatibility?
+ private boolean positions = true;// TODO dead, but keep a while to ensure config compatibility?
+ private BooleanIndexDefinition boolIndex = null;
+
+ public IndexField(String name, Index.Type type, DataType sdFieldType) {
+ this.name = name;
+ this.type = type;
+ this.sdFieldType = sdFieldType;
+ }
+ public void setIndexSettings(com.yahoo.searchdefinition.Index index) {
+ if (type.equals(Index.Type.TEXT)) {
+ prefix = index.isPrefix();
+ }
+ sdType = index.getType();
+ boolIndex = index.getBooleanIndexDefiniton();
+ }
+ public String getName() { return name; }
+ public Index.Type getRawType() { return type; }
+ public String getType() {
+ return type.equals(Index.Type.INT64)
+ ? "INT64"
+ : type.equals(Index.Type.BOOLEANTREE)
+ ? "BOOLEANTREE"
+ : "STRING";
+ }
+ public String getCollectionType() {
+ return (sdFieldType == null)
+ ? "SINGLE"
+ : (sdFieldType instanceof WeightedSetDataType)
+ ? "WEIGHTEDSET"
+ : (sdFieldType instanceof ArrayDataType)
+ ? "ARRAY"
+ : "SINGLE";
+ }
+ public boolean hasPrefix() { return prefix; }
+ public boolean hasPhrases() { return phrases; }
+ public boolean hasPositions() { return positions; }
+
+ public BooleanIndexDefinition getBooleanIndexDefinition() {
+ return boolIndex;
+ }
+
+ /**
+ * The user set index type
+ * @return the type
+ */
+ public com.yahoo.searchdefinition.Index.Type getSdType() {
+ return sdType;
+ }
+ }
+
+ /**
+ * Representation of a collection of fields (aka index, physical view).
+ */
+ @SuppressWarnings({ "UnusedDeclaration" })
+ private static class FieldCollection {
+
+ private final String name;
+ private final List<String> fields = new ArrayList<>();
+
+ FieldCollection(String name) {
+ this.name = name;
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexingScript.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexingScript.java
new file mode 100644
index 00000000000..94e4dec567f
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/IndexingScript.java
@@ -0,0 +1,146 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.*;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.indexinglanguage.ExpressionVisitor;
+import com.yahoo.vespa.indexinglanguage.expressions.*;
+import com.yahoo.vespa.configdefinition.IlscriptsConfig;
+import com.yahoo.vespa.configdefinition.IlscriptsConfig.Ilscript.Builder;
+
+import java.util.*;
+
+/**
+ * An indexing language script derived from a search definition. An indexing script contains a set of indexing
+ * statements, organized in a composite structure of indexing code snippets.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public final class IndexingScript extends Derived implements IlscriptsConfig.Producer {
+
+ private final List<String> docFields = new LinkedList<>();
+ private final List<Expression> expressions = new LinkedList<>();
+
+ public IndexingScript(Search search) {
+ derive(search);
+ }
+
+ protected void derive(SDField field, Search search) {
+ if (!field.isExtraField()) {
+ docFields.add(field.getName());
+ }
+ if (field.usesStructOrMap() &&
+ !field.getDataType().equals(PositionDataType.INSTANCE) &&
+ !field.getDataType().equals(DataType.getArray(PositionDataType.INSTANCE)))
+ {
+ return; // unsupported
+ }
+ ScriptExpression script = field.getIndexingScript();
+ if (!script.isEmpty()) {
+ expressions.add(new StatementExpression(new ClearStateExpression(),
+ new GuardExpression(script)));
+ }
+ }
+
+ public Iterable<Expression> expressions() {
+ return Collections.unmodifiableCollection(expressions);
+ }
+
+ @Override
+ public String getDerivedName() {
+ return "ilscripts";
+ }
+
+ @Override
+ public void getConfig(IlscriptsConfig.Builder configBuilder) {
+ IlscriptsConfig.Ilscript.Builder ilscriptBuilder = new IlscriptsConfig.Ilscript.Builder();
+ ilscriptBuilder.doctype(getName());
+ for (String fieldName : docFields) {
+ ilscriptBuilder.docfield(fieldName);
+ }
+ addContentInOrder(ilscriptBuilder);
+ configBuilder.ilscript(ilscriptBuilder);
+ }
+
+ private void addContentInOrder(IlscriptsConfig.Ilscript.Builder ilscriptBuilder) {
+ ArrayList<Expression> later = new ArrayList<>();
+ Set<String> touchedFields = new HashSet<String>();
+ for (Expression exp : expressions) {
+ FieldScanVisitor fieldFetcher = new FieldScanVisitor();
+ if (modifiesSelf(exp)) {
+ later.add(exp);
+ } else {
+ ilscriptBuilder.content(exp.toString());
+ }
+ fieldFetcher.visit(exp);
+ touchedFields.addAll(fieldFetcher.touchedFields());
+ }
+ for (Expression exp : later) {
+ ilscriptBuilder.content(exp.toString());
+ }
+ generateSyntheticStatementsForUntouchedFields(ilscriptBuilder, touchedFields);
+ }
+
+ private void generateSyntheticStatementsForUntouchedFields(Builder ilscriptBuilder, Set<String> touchedFields) {
+ Set<String> fieldsWithSyntheticStatements = new HashSet<String>(docFields);
+ fieldsWithSyntheticStatements.removeAll(touchedFields);
+ List<String> orderedFields = new ArrayList<String>(fieldsWithSyntheticStatements);
+ Collections.sort(orderedFields);
+ for (String fieldName : orderedFields) {
+ StatementExpression copyField = new StatementExpression(new InputExpression(fieldName),
+ new PassthroughExpression(fieldName));
+ ilscriptBuilder.content(copyField.toString());
+ }
+ }
+
+ private boolean modifiesSelf(Expression exp) {
+ MyExpVisitor visitor = new MyExpVisitor();
+ visitor.visit(exp);
+ return visitor.modifiesSelf();
+ }
+
+ private class MyExpVisitor extends ExpressionVisitor {
+ private String inputField = null;
+ private String outputField = null;
+
+ public boolean modifiesSelf() { return outputField != null && outputField.equals(inputField); }
+
+ @Override
+ protected void doVisit(Expression expression) {
+ if (modifiesSelf()) {
+ return;
+ }
+ if (expression instanceof InputExpression) {
+ inputField = ((InputExpression) expression).getFieldName();
+ }
+ if (expression instanceof OutputExpression) {
+ outputField = ((OutputExpression) expression).getFieldName();
+ }
+ }
+ }
+
+ private static class FieldScanVisitor extends ExpressionVisitor {
+ List<String> touchedFields = new ArrayList<String>();
+ List<String> candidates = new ArrayList<String>();
+
+ @Override
+ protected void doVisit(Expression exp) {
+ if (exp instanceof OutputExpression) {
+ touchedFields.add(((OutputExpression) exp).getFieldName());
+ }
+ if (exp instanceof InputExpression) {
+ candidates.add(((InputExpression) exp).getFieldName());
+ }
+ if (exp instanceof ZCurveExpression) {
+ touchedFields.addAll(candidates);
+ }
+ }
+
+ Collection<String> touchedFields() {
+ Collection<String> output = touchedFields;
+ touchedFields = null; // deny re-use to try and avoid obvious bugs
+ return output;
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/Juniperrc.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Juniperrc.java
new file mode 100644
index 00000000000..9ef0ddbc723
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Juniperrc.java
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import com.yahoo.vespa.config.search.summary.JuniperrcConfig;
+
+import java.util.Set;
+
+/**
+ * Generated juniperrc-config for controlling juniper.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Juniperrc extends Derived implements JuniperrcConfig.Producer {
+
+ // List of all fields that should be bolded.
+ private Set<String> boldingFields = new java.util.LinkedHashSet<>();
+
+ /**
+ * Constructs a new juniper rc instance for a given search object. This will derive the configuration automatically,
+ * so there is no need to call {@link #derive(com.yahoo.searchdefinition.Search)}.
+ *
+ * @param search The search model to use for deriving.
+ */
+ public Juniperrc(Search search) {
+ derive(search);
+ }
+
+ // Inherit doc from Derived.
+ @Override
+ protected void derive(Search search) {
+ super.derive(search);
+ for (SummaryField summaryField : search.getUniqueNamedSummaryFields().values()) {
+ if (summaryField.getTransform() == SummaryTransform.BOLDED) {
+ boldingFields.add(summaryField.getName());
+ }
+ }
+ }
+
+ // Inherit doc from Derived.
+ protected String getDerivedName() {
+ return "juniperrc";
+ }
+
+ @Override
+ public void getConfig(JuniperrcConfig.Builder builder) {
+ if (boldingFields.size() != 0) {
+ builder.prefix(true);
+ for (String name : boldingFields) {
+ builder.override(new JuniperrcConfig.Override.Builder()
+ .fieldname(name)
+ .length(65536)
+ .max_matches(1)
+ .min_length(8192)
+ .surround_max(65536));
+ }
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeRankTypeDefinition.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeRankTypeDefinition.java
new file mode 100644
index 00000000000..c1176807519
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeRankTypeDefinition.java
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.searchdefinition.document.RankType;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * The definition of a rank type used for native rank features.
+ *
+ * @author <a href="mailto:geirst@yahoo-inc.com">Geir Storli</a>
+ */
+public class NativeRankTypeDefinition {
+
+ /** The type this defines */
+ private RankType type;
+
+ /** The rank tables of this rank type */
+ private List<NativeTable> rankTables = new java.util.ArrayList<>();
+
+ public NativeRankTypeDefinition(RankType type) {
+ this.type = type;
+ }
+
+ public RankType getType() {
+ return type;
+ }
+
+ public void addTable(NativeTable table) {
+ rankTables.add(table);
+ }
+
+ /** Returns an unmodifiable list of the tables in this type definition */
+ public Iterator<NativeTable> rankSettingIterator() {
+ return Collections.unmodifiableList(rankTables).iterator();
+ }
+
+ public String toString() {
+ return "native definition of rank type '" + type + "'";
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeRankTypeDefinitionSet.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeRankTypeDefinitionSet.java
new file mode 100644
index 00000000000..18856627b70
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeRankTypeDefinitionSet.java
@@ -0,0 +1,93 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.searchdefinition.document.RankType;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * A set of rank type definitions used for native rank features.
+ *
+ * @author <a href="mailto:geirst@yahoo-inc.com">Geir Storli</a>
+ */
+public class NativeRankTypeDefinitionSet {
+
+ /** The name of this rank definition set */
+ private String name;
+
+ /** The unmodifiable rank type implementations in this set */
+ private final Map<RankType, NativeRankTypeDefinition> typeDefinitions;
+
+ /** Returns the default rank type (about) */
+ public static RankType getDefaultRankType() { return RankType.ABOUT; }
+
+ public NativeRankTypeDefinitionSet(String name) {
+ this.name = name;
+
+ Map<RankType, NativeRankTypeDefinition> typeDefinitions = new java.util.LinkedHashMap<>();
+ typeDefinitions.put(RankType.IDENTITY, createIdentityRankType(RankType.IDENTITY));
+ typeDefinitions.put(RankType.ABOUT, createAboutRankType(RankType.ABOUT));
+ typeDefinitions.put(RankType.TAGS, createTagsRankType(RankType.TAGS));
+ typeDefinitions.put(RankType.EMPTY, createEmptyRankType(RankType.EMPTY));
+ this.typeDefinitions = Collections.unmodifiableMap(typeDefinitions);
+ }
+
+ private NativeRankTypeDefinition createEmptyRankType(RankType type) {
+ NativeRankTypeDefinition rank = new NativeRankTypeDefinition(type);
+ rank.addTable(new NativeTable(NativeTable.Type.FIRST_OCCURRENCE, "linear(0,0)"));
+ rank.addTable(new NativeTable(NativeTable.Type.OCCURRENCE_COUNT, "linear(0,0)"));
+ rank.addTable(new NativeTable(NativeTable.Type.PROXIMITY, "linear(0,0)"));
+ rank.addTable(new NativeTable(NativeTable.Type.REVERSE_PROXIMITY, "linear(0,0)"));
+ rank.addTable(new NativeTable(NativeTable.Type.WEIGHT, "linear(0,0)"));
+ return rank;
+ }
+
+ private NativeRankTypeDefinition createAboutRankType(RankType type) {
+ NativeRankTypeDefinition rank = new NativeRankTypeDefinition(type);
+ rank.addTable(new NativeTable(NativeTable.Type.FIRST_OCCURRENCE, "expdecay(8000,12.50)"));
+ rank.addTable(new NativeTable(NativeTable.Type.OCCURRENCE_COUNT, "loggrowth(1500,4000,19)"));
+ rank.addTable(new NativeTable(NativeTable.Type.PROXIMITY, "expdecay(500,3)"));
+ rank.addTable(new NativeTable(NativeTable.Type.REVERSE_PROXIMITY, "expdecay(400,3)"));
+ rank.addTable(new NativeTable(NativeTable.Type.WEIGHT, "linear(1,0)"));
+ return rank;
+ }
+
+ private NativeRankTypeDefinition createIdentityRankType(RankType type) {
+ NativeRankTypeDefinition rank = new NativeRankTypeDefinition(type);
+ rank.addTable(new NativeTable(NativeTable.Type.FIRST_OCCURRENCE, "expdecay(100,12.50)"));
+ rank.addTable(new NativeTable(NativeTable.Type.OCCURRENCE_COUNT, "loggrowth(1500,4000,19)"));
+ rank.addTable(new NativeTable(NativeTable.Type.PROXIMITY, "expdecay(5000,3)"));
+ rank.addTable(new NativeTable(NativeTable.Type.REVERSE_PROXIMITY, "expdecay(3000,3)"));
+ rank.addTable(new NativeTable(NativeTable.Type.WEIGHT, "linear(1,0)"));
+ return rank;
+ }
+
+ private NativeRankTypeDefinition createTagsRankType(RankType type) {
+ NativeRankTypeDefinition rank = new NativeRankTypeDefinition(type);
+ rank.addTable(new NativeTable(NativeTable.Type.FIRST_OCCURRENCE, "expdecay(8000,12.50)"));
+ rank.addTable(new NativeTable(NativeTable.Type.OCCURRENCE_COUNT, "loggrowth(1500,4000,19)"));
+ rank.addTable(new NativeTable(NativeTable.Type.PROXIMITY, "expdecay(500,3)"));
+ rank.addTable(new NativeTable(NativeTable.Type.REVERSE_PROXIMITY, "expdecay(400,3)"));
+ rank.addTable(new NativeTable(NativeTable.Type.WEIGHT, "loggrowth(38,50,1)"));
+ return rank;
+ }
+
+ /**
+ * Returns a rank type definition if given an existing rank type name,
+ * or null if given a rank type which has no native implementation (meaning somebody forgot to add it),
+ */
+ public NativeRankTypeDefinition getRankTypeDefinition(RankType type) {
+ if (type == RankType.DEFAULT)
+ type = getDefaultRankType();
+ return typeDefinitions.get(type);
+ }
+
+ /** Returns an unmodifiable map of the type definitions in this */
+ public Map<RankType, NativeRankTypeDefinition> types() { return typeDefinitions; }
+
+ public String toString() {
+ return "native rank type definitions " + name;
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeTable.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeTable.java
new file mode 100644
index 00000000000..512d4a37647
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/NativeTable.java
@@ -0,0 +1,72 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+/**
+ * A named rank table of a certain type.
+ *
+ * @author <a href="mailto:geirst@yahoo-inc.com">Geir Storli</a>
+ */
+public class NativeTable {
+
+ private String name;
+
+ private Type type;
+
+ /** A table type enumeration */
+ public static class Type {
+
+ public static Type FIRST_OCCURRENCE = new Type("firstOccurrenceTable");
+ public static Type OCCURRENCE_COUNT = new Type("occurrenceCountTable");
+ public static Type WEIGHT = new Type("weightTable");
+ public static Type PROXIMITY = new Type("proximityTable");
+ public static Type REVERSE_PROXIMITY = new Type("reverseProximityTable");
+
+ private String name;
+
+ private Type(String name) {
+ this.name = name;
+ }
+
+ public String getName() { return name; }
+
+ public boolean equals(Object object) {
+ if (!(object instanceof Type)) {
+ return false;
+ }
+ Type other = (Type)object;
+ return this.name.equals(other.name);
+ }
+
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ public String toString() {
+ return getName();
+ }
+ }
+
+ public NativeTable(Type type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ public Type getType() { return type; }
+
+ public String getName() { return name; }
+
+ public int hashCode() {
+ return type.hashCode() + 17*name.hashCode();
+ }
+
+ public boolean equals(Object object) {
+ if (! (object instanceof NativeTable)) return false;
+ NativeTable other = (NativeTable)object;
+ return other.getName().equals(this.getName()) && other.getType().equals(this.getType());
+ }
+
+ public String toString() {
+ return getType() + ": " + getName();
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java
new file mode 100644
index 00000000000..183bfd6ddd4
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java
@@ -0,0 +1,62 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.vespa.config.search.RankProfilesConfig;
+import com.yahoo.searchdefinition.RankProfile;
+import com.yahoo.searchdefinition.Search;
+import java.util.Map;
+
+/**
+ * The derived rank profiles of a search definition
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class RankProfileList extends Derived implements RankProfilesConfig.Producer {
+
+ private RawRankProfile defaultProfile;
+
+ private Map<String, RawRankProfile> rankProfiles=new java.util.LinkedHashMap<>();
+
+ /**
+ * Creates a rank profile
+ *
+ * @param search the search definition this is a rank profile from
+ * @param attributeFields the attribute fields to create a ranking for
+ */
+ public RankProfileList(Search search, AttributeFields attributeFields, RankProfileRegistry rankProfileRegistry) {
+ setName(search.getName());
+ deriveRankProfiles(rankProfileRegistry, search, attributeFields);
+ }
+
+ private void deriveRankProfiles(RankProfileRegistry rankProfileRegistry, Search search, AttributeFields attributeFields) {
+ defaultProfile = new RawRankProfile(rankProfileRegistry.getRankProfile(search, "default"), attributeFields);
+ rankProfiles.put(defaultProfile.getName(), defaultProfile);
+
+ for (RankProfile rank : rankProfileRegistry.localRankProfiles(search)) {
+ if ("default".equals(rank.getName()))
+ continue;
+ RawRankProfile rawRank=new RawRankProfile(rank, attributeFields);
+ rankProfiles.put(rawRank.getName(), rawRank);
+ }
+ }
+
+ public Map<String, RawRankProfile> getRankProfiles() {
+ return rankProfiles;
+ }
+
+ /** @return A named raw rank profile, or null if it is not present */
+ public RawRankProfile getRankProfile(String name) {
+ return rankProfiles.get(name);
+ }
+
+ @Override
+ public String getDerivedName() { return "rank-profiles"; }
+
+ @Override
+ public void getConfig(RankProfilesConfig.Builder builder) {
+ for (RawRankProfile rank : rankProfiles.values() ) {
+ rank.getConfig(builder);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/RawRankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RawRankProfile.java
new file mode 100644
index 00000000000..cf3b10ccadd
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RawRankProfile.java
@@ -0,0 +1,374 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.searchdefinition.document.RankType;
+import com.yahoo.searchdefinition.RankProfile;
+import com.yahoo.searchlib.rankingexpression.ExpressionFunction;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode;
+import com.yahoo.searchlib.rankingexpression.rule.SerializationContext;
+import com.yahoo.vespa.config.search.RankProfilesConfig;
+import java.util.*;
+
+/**
+ * A rank profile derived from a search definition, containing exactly the features available natively in the server
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class RawRankProfile implements RankProfilesConfig.Producer {
+
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ return " rank profile " + name;
+ }
+
+ @Override
+ public void getConfig(RankProfilesConfig.Builder builder) {
+ RankProfilesConfig.Rankprofile.Builder b = new RankProfilesConfig.Rankprofile.Builder().name(getName());
+ getRankProperties(b);
+ builder.rankprofile(b);
+ }
+
+ private void getRankProperties(RankProfilesConfig.Rankprofile.Builder b) {
+ RankProfilesConfig.Rankprofile.Fef.Builder fefB = new RankProfilesConfig.Rankprofile.Fef.Builder();
+ for (Map.Entry<String, Object> e : configProperties.entrySet()) {
+ String key = e.getKey().replaceFirst(".part\\d+$", "");
+ String val = e.getValue().toString();
+ fefB.property(new RankProfilesConfig.Rankprofile.Fef.Property.Builder().name(key).value(val));
+ }
+ b.fef(fefB);
+ }
+
+ // TODO: These are to expose coupling between the strings used here and elsewhere
+ public final static String summaryFeatureFefPropertyPrefix = "vespa.summary.feature";
+ public final static String rankFeatureFefPropertyPrefix = "vespa.dump.feature";
+
+ /**
+ * Returns an immutable view of the config properties this returns
+ */
+ public Map<String, Object> configProperties() {
+ return Collections.unmodifiableMap(configProperties);
+ }
+
+ private final Map<String, Object> configProperties;
+
+ /**
+ * Creates a raw rank profile from the given rank profile
+ */
+ public RawRankProfile(RankProfile rankProfile, AttributeFields attributeFields) {
+ this.name = rankProfile.getName();
+ configProperties = new Deriver(rankProfile, attributeFields).derive();
+ }
+
+ private static class Deriver {
+
+ /**
+ * The field rank settings of this profile
+ */
+ private Map<String, FieldRankSettings> fieldRankSettings = new java.util.LinkedHashMap<>();
+
+ private RankingExpression firstPhaseRanking = null;
+
+ private RankingExpression secondPhaseRanking = null;
+
+ private Set<ReferenceNode> summaryFeatures = new LinkedHashSet<>();
+
+ private Set<ReferenceNode> rankFeatures = new LinkedHashSet<>();
+
+ private List<RankProfile.RankProperty> rankProperties = new ArrayList<>();
+
+ /**
+ * Rank properties for weight settings to make these available to feature executors
+ */
+ private List<RankProfile.RankProperty> boostAndWeightRankProperties = new ArrayList<>();
+
+ private boolean ignoreDefaultRankFeatures = false;
+
+ private RankProfile.MatchPhaseSettings matchPhaseSettings = null;
+
+ private int rerankCount = -1;
+ private int keepRankCount = -1;
+ private int numThreadsPerSearch = -1;
+ private int numSearchPartitions = -1;
+ private double termwiseLimit = 1.0;
+ private double rankScoreDropLimit = -Double.MAX_VALUE;
+
+ /**
+ * The rank type definitions used to derive settings for the native rank features
+ */
+ private NativeRankTypeDefinitionSet nativeRankTypeDefinitions = new NativeRankTypeDefinitionSet("default");
+
+ private RankProfile rankProfile;
+
+ private Set<String> filterFields = new java.util.LinkedHashSet<>();
+
+ /**
+ * Creates a raw rank profile from the given rank profile
+ */
+ public Deriver(RankProfile rankProfile, AttributeFields attributeFields) {
+ this.rankProfile = rankProfile.compile();
+ deriveRankingFeatures(this.rankProfile);
+ deriveRankTypeSetting(this.rankProfile, attributeFields);
+ deriveFilterFields(this.rankProfile);
+ deriveWeightProperties(this.rankProfile);
+ }
+
+ private void deriveFilterFields(RankProfile rp) {
+ filterFields.addAll(rp.allFilterFields());
+ }
+
+ public void deriveRankingFeatures(RankProfile rankProfile) {
+ firstPhaseRanking = rankProfile.getFirstPhaseRanking();
+ secondPhaseRanking = rankProfile.getSecondPhaseRanking();
+ summaryFeatures = new LinkedHashSet<>(rankProfile.getSummaryFeatures());
+ rankFeatures = rankProfile.getRankFeatures();
+ rerankCount = rankProfile.getRerankCount();
+ matchPhaseSettings = rankProfile.getMatchPhaseSettings();
+ numThreadsPerSearch = rankProfile.getNumThreadsPerSearch();
+ numSearchPartitions = rankProfile.getNumSearchPartitions();
+ termwiseLimit = rankProfile.getTermwiseLimit();
+ keepRankCount = rankProfile.getKeepRankCount();
+ rankScoreDropLimit = rankProfile.getRankScoreDropLimit();
+ ignoreDefaultRankFeatures = rankProfile.getIgnoreDefaultRankFeatures();
+ rankProperties = new ArrayList<>(rankProfile.getRankProperties());
+ derivePropertiesAndSummaryFeaturesFromMacros(rankProfile.getMacros());
+ }
+
+ private void derivePropertiesAndSummaryFeaturesFromMacros(Map<String, RankProfile.Macro> macros) {
+ if (macros.isEmpty()) return;
+ Map<String, ExpressionFunction> expressionMacros = new LinkedHashMap<>();
+ for (Map.Entry<String, RankProfile.Macro> macro : macros.entrySet()) {
+ expressionMacros.put(macro.getKey(), macro.getValue().toExpressionMacro());
+ }
+
+ Map<String, String> macroProperties = new LinkedHashMap<>();
+ macroProperties.putAll(deriveMacroProperties(expressionMacros));
+ if (firstPhaseRanking != null) {
+ macroProperties.putAll(firstPhaseRanking.getRankProperties(new ArrayList<>(expressionMacros.values())));
+ }
+ if (secondPhaseRanking != null) {
+ macroProperties.putAll(secondPhaseRanking.getRankProperties(new ArrayList<>(expressionMacros.values())));
+ }
+ for (Map.Entry<String, String> e : macroProperties.entrySet()) {
+ rankProperties.add(new RankProfile.RankProperty(e.getKey(), e.getValue()));
+ }
+ SerializationContext context = new SerializationContext(expressionMacros.values(), null, macroProperties);
+ replaceMacroSummaryFeatures(context);
+ }
+
+ private Map<String, String> deriveMacroProperties(Map<String, ExpressionFunction> eMacros) {
+ SerializationContext context = new SerializationContext(eMacros);
+ for (Map.Entry<String, ExpressionFunction> e : eMacros.entrySet()) {
+ String script = e.getValue().getBody().getRoot().toString(context, null, null);
+ context.addFunctionSerialization(RankingExpression.propertyName(e.getKey()), script);
+ }
+ return context.serializedFunctions();
+ }
+
+ private void replaceMacroSummaryFeatures(SerializationContext context) {
+ if (summaryFeatures == null) return;
+ Map<String, ReferenceNode> macroSummaryFeatures = new LinkedHashMap<>();
+ for (Iterator<ReferenceNode> i = summaryFeatures.iterator(); i.hasNext(); ) {
+ ReferenceNode referenceNode = i.next();
+ // Is the feature a macro?
+ if (context.getFunction(referenceNode.getName()) != null) {
+ context.addFunctionSerialization(RankingExpression.propertyName(referenceNode.getName()),
+ referenceNode.toString(context, null, null));
+ ReferenceNode newReferenceNode = new ReferenceNode("rankingExpression(" + referenceNode.getName() + ")", referenceNode.getArguments().expressions(), referenceNode.getOutput());
+ macroSummaryFeatures.put(referenceNode.getName(), newReferenceNode);
+ i.remove(); // Will add the expanded one in next block
+ }
+ }
+ // Then, replace the summary features that were macros
+ for (Map.Entry<String, ReferenceNode> e : macroSummaryFeatures.entrySet()) {
+ summaryFeatures.add(e.getValue());
+ }
+ }
+
+ private void deriveWeightProperties(RankProfile rankProfile) {
+
+ for (RankProfile.RankSetting setting : rankProfile.rankSettings()) {
+ if (!setting.getType().equals(RankProfile.RankSetting.Type.WEIGHT)) {
+ continue;
+ }
+ boostAndWeightRankProperties.add(new RankProfile.RankProperty("vespa.fieldweight." + setting.getFieldName(),
+ String.valueOf(setting.getIntValue())));
+ }
+ }
+
+ /**
+ * Adds the type boosts from a rank profile
+ */
+ private void deriveRankTypeSetting(RankProfile rankProfile, AttributeFields attributeFields) {
+ for (Iterator<RankProfile.RankSetting> i = rankProfile.rankSettingIterator(); i.hasNext(); ) {
+ RankProfile.RankSetting setting = i.next();
+ if (!setting.getType().equals(RankProfile.RankSetting.Type.RANKTYPE)) continue;
+
+ deriveNativeRankTypeSetting(setting.getFieldName(), (RankType) setting.getValue(), attributeFields,
+ hasDefaultRankTypeSetting(rankProfile, setting.getFieldName()));
+ }
+ }
+
+ public void deriveNativeRankTypeSetting(String fieldName, RankType rankType, AttributeFields attributeFields, boolean isDefaultSetting) {
+ if (isDefaultSetting) return;
+
+ NativeRankTypeDefinition definition = nativeRankTypeDefinitions.getRankTypeDefinition(rankType);
+ if (definition == null) throw new IllegalArgumentException("In field '" + fieldName + "': " +
+ rankType + " is known but has no implementation. " +
+ "Supported rank types: " +
+ nativeRankTypeDefinitions.types().keySet());
+
+ FieldRankSettings settings = deriveFieldRankSettings(fieldName);
+ for (Iterator<NativeTable> i = definition.rankSettingIterator(); i.hasNext(); ) {
+ NativeTable table = i.next();
+ // only add index field tables if we are processing an index field and
+ // only add attribute field tables if we are processing an attribute field
+ if ((FieldRankSettings.isIndexFieldTable(table) && attributeFields.getAttribute(fieldName) == null) ||
+ (FieldRankSettings.isAttributeFieldTable(table) && attributeFields.getAttribute(fieldName) != null)) {
+ settings.addTable(table);
+ }
+ }
+ }
+
+ private boolean hasDefaultRankTypeSetting(RankProfile rankProfile, String fieldName) {
+ RankProfile.RankSetting setting =
+ rankProfile.getRankSetting(fieldName, RankProfile.RankSetting.Type.RANKTYPE);
+ return setting != null && setting.getValue().equals(RankType.DEFAULT);
+ }
+
+ public FieldRankSettings deriveFieldRankSettings(String fieldName) {
+ FieldRankSettings settings = fieldRankSettings.get(fieldName);
+ if (settings == null) {
+ settings = new FieldRankSettings(fieldName);
+ fieldRankSettings.put(fieldName, settings);
+ }
+ return settings;
+ }
+
+ /**
+ * Derives the properties this produces. Equal keys are suffixed with .part0 etc, remove when exporting to file
+ *
+ * @return map of the derived properties
+ */
+ public Map<String, Object> derive() {
+ Map<String, Object> props = new LinkedHashMap<>();
+ int i = 0;
+ for (RankProfile.RankProperty property : rankProperties) {
+ if ("rankingExpression(firstphase).rankingScript".equals(property.getName())) {
+ // Could have been set by macro expansion. Set expressions, then skip this property.
+ try {
+ firstPhaseRanking = new RankingExpression(property.getValue());
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("Could not parse second phase expression", e);
+ }
+ continue;
+ }
+ if ("rankingExpression(secondphase).rankingScript".equals(property.getName())) {
+ try {
+ secondPhaseRanking = new RankingExpression(property.getValue());
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("Could not parse second phase expression", e);
+ }
+ continue;
+ }
+ props.put(property.getName() + ".part" + i, property.getValue());
+ i++;
+ }
+ props.putAll(deriveRankingPhaseRankProperties(firstPhaseRanking, "firstphase"));
+ props.putAll(deriveRankingPhaseRankProperties(secondPhaseRanking, "secondphase"));
+ for (FieldRankSettings settings : fieldRankSettings.values()) {
+ props.putAll(settings.deriveRankProperties(i));
+ }
+ i = 0;
+ for (RankProfile.RankProperty property : boostAndWeightRankProperties) {
+ props.put(property.getName() + ".part" + i, property.getValue());
+ i++;
+ }
+ i = 0;
+ for (ReferenceNode feature : summaryFeatures) {
+ props.put(summaryFeatureFefPropertyPrefix + ".part" + i, feature.toString());
+ i++;
+ }
+ i = 0;
+ for (ReferenceNode feature : rankFeatures) {
+ props.put(rankFeatureFefPropertyPrefix + ".part" + i, feature.toString());
+ i++;
+ }
+ if (numThreadsPerSearch > 0) {
+ props.put("vespa.matching.numthreadspersearch", numThreadsPerSearch + "");
+ }
+ if (numSearchPartitions >= 0) {
+ props.put("vespa.matching.numsearchpartitions", numSearchPartitions + "");
+ }
+ if (termwiseLimit < 1.0) {
+ props.put("vespa.matching.termwise_limit", termwiseLimit + "");
+ }
+ if (matchPhaseSettings != null) {
+ props.put("vespa.matchphase.degradation.attribute", matchPhaseSettings.getAttribute());
+ props.put("vespa.matchphase.degradation.ascendingorder", matchPhaseSettings.getAscending() + "");
+ props.put("vespa.matchphase.degradation.maxhits", matchPhaseSettings.getMaxHits() + "");
+ props.put("vespa.matchphase.degradation.maxfiltercoverage", matchPhaseSettings.getMaxFilterCoverage() + "");
+ props.put("vespa.matchphase.degradation.samplepercentage", matchPhaseSettings.getEvaluationPoint() + "");
+ props.put("vespa.matchphase.degradation.postfiltermultiplier", matchPhaseSettings.getPrePostFilterTippingPoint() + "");
+ RankProfile.DiversitySettings diversitySettings = rankProfile.getMatchPhaseSettings().getDiversity();
+ if (diversitySettings != null) {
+ props.put("vespa.matchphase.diversity.attribute", diversitySettings.getAttribute());
+ props.put("vespa.matchphase.diversity.mingroups", diversitySettings.getMinGroups());
+ props.put("vespa.matchphase.diversity.cutoff.factor", diversitySettings.getCutoffFactor());
+ props.put("vespa.matchphase.diversity.cutoff.strategy", diversitySettings.getCutoffStrategy());
+ }
+ }
+ if (rerankCount > -1) {
+ props.put("vespa.hitcollector.heapsize", rerankCount + "");
+ }
+ if (keepRankCount > -1) {
+ props.put("vespa.hitcollector.arraysize", keepRankCount + "");
+ }
+ if (rankScoreDropLimit > -Double.MAX_VALUE) {
+ props.put("vespa.hitcollector.rankscoredroplimit", rankScoreDropLimit + "");
+ }
+ if (ignoreDefaultRankFeatures) {
+ props.put("vespa.dump.ignoredefaultfeatures", true);
+ }
+ Iterator filterFieldsIterator = filterFields.iterator();
+ while (filterFieldsIterator.hasNext()) {
+ String fieldName = (String) filterFieldsIterator.next();
+ props.put("vespa.isfilterfield." + fieldName + ".part42", true);
+ }
+ for (Map.Entry<String, String> attributeType : rankProfile.getAttributeTypes().entrySet()) {
+ props.put("vespa.type.attribute." + attributeType.getKey(), attributeType.getValue());
+ }
+ for (Map.Entry<String, String> queryFeatureType : rankProfile.getQueryFeatureTypes().entrySet()) {
+ props.put("vespa.type.query." + queryFeatureType.getKey(), queryFeatureType.getValue());
+ }
+ if (props.size() >= 1000000) throw new RuntimeException("Too many rank properties");
+ return props;
+ }
+
+ private Map<String, String> deriveRankingPhaseRankProperties(RankingExpression expression, String phase) {
+ Map<String, String> ret = new LinkedHashMap<>();
+ if (expression == null) {
+ return ret;
+ }
+ String name = expression.getName();
+ if ("".equals(name)) {
+ name = phase;
+ }
+ if (expression.getRoot() instanceof ReferenceNode) {
+ ret.put("vespa.rank." + phase, expression.getRoot().toString());
+ } else {
+ ret.put("vespa.rank." + phase, "rankingExpression(" + name + ")");
+ ret.put("rankingExpression(" + name + ").rankingScript", expression.getRoot().toString());
+ }
+ return ret;
+ }
+
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/SearchOrderer.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SearchOrderer.java
new file mode 100644
index 00000000000..69133e31fb3
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SearchOrderer.java
@@ -0,0 +1,105 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.document.DataTypeName;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.Search;
+
+import java.util.*;
+
+/**
+ * <p>A class which can reorder a list of search definitions such that any supertype
+ * always preceed any subtype. Subject to this condition the given order
+ * is preserved (the minimal reordering is done).</p>
+ *
+ * <p>This class is <b>not</b> multithread safe. Only one ordering must be done
+ * at the time in any instance.</p>
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class SearchOrderer {
+
+ /** A map from DataTypeName to the Search defining them */
+ private Map<DataTypeName, Search> documentNameToSearch=new java.util.HashMap<>();
+
+ /**
+ * Reorders the given list of search definitions such that any supertype
+ * always preceed any subtype. Subject to this condition the given order
+ * is preserved (the minimal reordering is done).
+ *
+ * @return a new list containing the same search instances in the right order
+ */
+ public List<Search> order(List<Search> unordered) {
+ Collections.sort(unordered, new Comparator<Search>() {
+ @Override
+ public int compare(Search lhs, Search rhs) {
+ return lhs.getName().compareTo(rhs.getName());
+ }
+ });
+
+ // No, this is not a fast algorithm...
+ indexOnDocumentName(unordered);
+ List<Search> ordered=new java.util.ArrayList<>(unordered.size());
+ List<Search> moveOutwards=new java.util.ArrayList<>();
+ for (Search search: unordered) {
+ if (containsInherited(ordered,search)) {
+ addOrdered(ordered,search,moveOutwards);
+ }
+ else {
+ moveOutwards.add(search);
+ }
+ }
+
+ // Any leftovers means we have search definitions with undefined inheritants.
+ // This is warned about elsewhere.
+ ordered.addAll(moveOutwards);
+
+ documentNameToSearch.clear();
+ return ordered;
+ }
+
+ private void addOrdered(List<Search> ordered,Search search,List<Search> moveOutwards) {
+ ordered.add(search);
+ Search eligibleMove;
+ do {
+ eligibleMove=removeFirstEligibleMoveOutwards(moveOutwards,ordered);
+ if (eligibleMove!=null)
+ ordered.add(eligibleMove);
+ } while (eligibleMove!=null);
+ }
+
+ /** Removes and returns the first search from the move list which can now be added, or null if none */
+ private Search removeFirstEligibleMoveOutwards(List<Search> moveOutwards,List<Search> ordered) {
+ for (Search move : moveOutwards) {
+ if (containsInherited(ordered,move)) {
+ moveOutwards.remove(move);
+ return move;
+ }
+ }
+ return null;
+ }
+
+ private boolean containsInherited(List<Search> list,Search search) {
+ if (search.getDocument() == null) {
+ return true;
+ }
+ for (SDDocumentType sdoc : search.getDocument().getInheritedTypes() ) {
+ DataTypeName inheritedName=sdoc.getDocumentName();
+ if ("document".equals(inheritedName.getName())) continue;
+ Search inheritedSearch=documentNameToSearch.get(inheritedName);
+ if (!list.contains(inheritedSearch))
+ return false;
+ }
+ return true;
+ }
+
+ private void indexOnDocumentName(List<Search> searches) {
+ documentNameToSearch.clear();
+ for (Search search : searches) {
+ if (search.getDocument() != null) {
+ documentNameToSearch.put(search.getDocument().getDocumentName(),search);
+ }
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/Summaries.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Summaries.java
new file mode 100644
index 00000000000..357e0d40f49
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/Summaries.java
@@ -0,0 +1,37 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.config.search.SummaryConfig;
+import java.util.List;
+
+/**
+ * A list of derived summaries
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class Summaries extends Derived implements SummaryConfig.Producer {
+
+ private List<SummaryClass> summaries=new java.util.ArrayList<>(1);
+
+ public Summaries(Search search, DeployLogger deployLogger) {
+ // Make sure the default is first
+ summaries.add(new SummaryClass(search,search.getSummary("default"), deployLogger));
+ for (DocumentSummary summary : search.getSummaries().values()) {
+ if (!summary.getName().equals("default"))
+ summaries.add(new SummaryClass(search,summary, deployLogger));
+ }
+ }
+
+ protected String getDerivedName() { return "summary"; }
+
+ @Override
+ public void getConfig(SummaryConfig.Builder builder) {
+ builder.defaultsummaryid(summaries.isEmpty() ? -1 : summaries.get(0).hashCode());
+ for (SummaryClass summaryClass : summaries) {
+ builder.classes(summaryClass.getSummaryClassConfig());
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClass.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClass.java
new file mode 100644
index 00000000000..d21523caea2
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClass.java
@@ -0,0 +1,144 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.config.model.application.provider.BaseDeployLogger;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.document.DataType;
+import com.yahoo.prelude.fastsearch.DocsumDefinitionSet;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.config.search.SummaryConfig;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import java.util.logging.Level;
+
+/**
+ * A summary derived from a search definition.
+ * Each summary definition have at least one summary, the default
+ * which has the same name as the search definition.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class SummaryClass extends Derived {
+
+ /** True if this summary class needs to access summary information on disk */
+ private boolean accessingDiskSummary=false;
+
+ /** The summary fields of this indexed by name */
+ private Map<String,SummaryClassField> fields = new java.util.LinkedHashMap<>();
+
+ private DeployLogger deployLogger = new BaseDeployLogger();
+
+ private final Random random = new Random(7);
+
+ /**
+ * Creates a summary class from a search definition summary
+ *
+ * @param deployLogger a {@link DeployLogger}
+ */
+ public SummaryClass(Search search, DocumentSummary summary, DeployLogger deployLogger) {
+ this.deployLogger = deployLogger;
+ deriveName(summary);
+ deriveFields(search,summary);
+ deriveImplicitFields(summary);
+ }
+
+ private void deriveName(DocumentSummary summary) {
+ setName(summary.getName());
+ }
+
+ /** MUST be called after all other fields are added */
+ private void deriveImplicitFields(DocumentSummary summary) {
+ if (summary.getName().equals("default")) {
+ addField("documentid", DataType.STRING);
+ }
+ }
+
+ private void deriveFields(Search search, DocumentSummary summary) {
+ for (SummaryField summaryField : summary.getSummaryFields()) {
+ if (!accessingDiskSummary && search.isAccessingDiskSummary(summaryField)) {
+ accessingDiskSummary = true;
+ }
+ addField(summaryField.getName(), summaryField.getDataType(), summaryField.getTransform());
+ }
+ }
+
+ private void addField(String name, DataType type) {
+ addField(name, type, null);
+ }
+
+ private void addField(String name, DataType type, SummaryTransform transform) {
+ if (fields.containsKey(name)) {
+ SummaryClassField sf = fields.get(name);
+ if (!SummaryClassField.convertDataType(type, transform).equals(sf.getType())) {
+ deployLogger.log(Level.WARNING, "Conflicting definition of field " + name + ". " +
+ "Declared as type " + sf.getType() + " and " +
+ type);
+ }
+ } else {
+ fields.put(name, new SummaryClassField(name, type, transform));
+ }
+ }
+
+
+ /** Returns an iterator of the fields of this summary. Removes on this iterator removes the field from this summary */
+ public Iterator<SummaryClassField> fieldIterator() {
+ return fields.values().iterator();
+ }
+
+ public void addField(SummaryClassField field) {
+ fields.put(field.getName(),field);
+ }
+
+ /** Returns the writable map of fields of this summary */ // TODO: Make read only, move writers to iterator/addField
+ public Map<String,SummaryClassField> getFields() { return fields; }
+
+ public SummaryClassField getField(String name) {
+ return fields.get(name);
+ }
+
+ public int getFieldCount() { return fields.size(); }
+
+ public int hashCode() {
+ int number = 1;
+ int hash = getName().hashCode();
+ for (Iterator i = fieldIterator(); i.hasNext(); ) {
+ SummaryClassField field = (SummaryClassField)i.next();
+ hash += number * (field.getName().hashCode() +
+ 17*field.getType().getName().hashCode());
+ number++;
+ }
+ if (hash < 0)
+ hash *= -1;
+ return hash;
+ }
+
+ public SummaryConfig.Classes.Builder getSummaryClassConfig() {
+ SummaryConfig.Classes.Builder classBuilder = new SummaryConfig.Classes.Builder();
+ int id = hashCode();
+ if (id == DocsumDefinitionSet.SLIME_MAGIC_ID) {
+ deployLogger.log(Level.WARNING, "Summary class '" + getName() + "' hashes to the SLIME_MAGIC_ID '" + id +
+ "'. This is unlikely but I autofix it for you by adding a random number.");
+ id += random.nextInt();
+ }
+ classBuilder.
+ id(id).
+ name(getName());
+ for (SummaryClassField field : fields.values() ) {
+ classBuilder.fields(new SummaryConfig.Classes.Fields.Builder().
+ name(field.getName()).
+ type(field.getType().getName()));
+ }
+ return classBuilder;
+ }
+
+ protected String getDerivedName() { return "summary"; }
+
+ public String toString() {
+ return "summary class " + getName();
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClassField.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClassField.java
new file mode 100644
index 00000000000..bb1dd87f314
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryClassField.java
@@ -0,0 +1,110 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.document.CollectionDataType;
+import com.yahoo.document.DataType;
+import com.yahoo.document.MapDataType;
+import com.yahoo.document.datatypes.*;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+
+/**
+ * A summary field derived from a search definition
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class SummaryClassField {
+
+ private final String name;
+
+ private final Type type;
+
+ /** The summary field type enumeration */
+ public enum Type {
+
+ BYTE("byte"),
+ SHORT("short"),
+ INTEGER("integer"),
+ INT64("int64"),
+ FLOAT("float"),
+ DOUBLE("double"),
+ STRING("string"),
+ DATA("data"),
+ LONGSTRING("longstring"),
+ LONGDATA("longdata"),
+ XMLSTRING("xmlstring"),
+ FEATUREDATA("featuredata"),
+ JSONSTRING("jsonstring");
+
+ private String name;
+
+ private Type(String name) {
+ this.name = name;
+ }
+
+ /** Returns the name of this type */
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ return "type: " + name;
+ }
+ }
+
+ public SummaryClassField(String name, DataType type, SummaryTransform transform) {
+ this.name = name;
+ this.type = convertDataType(type, transform);
+ }
+
+ public String getName() { return name; }
+
+ public Type getType() { return type; }
+
+ /** Converts to the right summary field type from a field datatype and a transform*/
+ public static Type convertDataType(DataType fieldType, SummaryTransform transform) {
+ FieldValue fval = fieldType.createFieldValue();
+ if (fval instanceof StringFieldValue) {
+ if (transform != null && transform.equals(SummaryTransform.RANKFEATURES)) {
+ return Type.FEATUREDATA;
+ } else if (transform != null && transform.equals(SummaryTransform.SUMMARYFEATURES)) {
+ return Type.FEATUREDATA;
+ } else {
+ return Type.LONGSTRING;
+ }
+ } else if (fval instanceof IntegerFieldValue) {
+ return Type.INTEGER;
+ } else if (fval instanceof LongFieldValue) {
+ return Type.INT64;
+ } else if (fval instanceof FloatFieldValue) {
+ return Type.FLOAT;
+ } else if (fval instanceof DoubleFieldValue) {
+ return Type.DOUBLE;
+ } else if (fval instanceof ByteFieldValue) {
+ return Type.BYTE;
+ } else if (fval instanceof Raw) {
+ return Type.DATA;
+ } else if (fval instanceof Struct) {
+ return Type.JSONSTRING;
+ } else if (fval instanceof PredicateFieldValue) {
+ return Type.STRING;
+ } else if (fval instanceof TensorFieldValue) {
+ return Type.JSONSTRING;
+ } else if (fieldType instanceof CollectionDataType) {
+ if (transform != null && transform.equals(SummaryTransform.POSITIONS)) {
+ return Type.XMLSTRING;
+ } else {
+ return Type.JSONSTRING;
+ }
+ } else if (fieldType instanceof MapDataType) {
+ return Type.JSONSTRING;
+ } else {
+ throw new IllegalArgumentException("Don't know which summary type to " +
+ "convert " + fieldType + " to");
+ }
+ }
+
+ public String toString() {
+ return "summary class field " + name;
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryMap.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryMap.java
new file mode 100644
index 00000000000..d59c671e6a5
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/SummaryMap.java
@@ -0,0 +1,111 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import com.yahoo.vespa.config.search.SummarymapConfig;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A summary map (describing search-time summary field transformations)
+ * derived from a search definition
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class SummaryMap extends Derived implements SummarymapConfig.Producer {
+
+ private Map<String,FieldResultTransform> resultTransforms = new java.util.LinkedHashMap<>();
+
+ /** Crateate a summary map from a search definition */
+ public SummaryMap(Search search, Summaries summaries) {
+ derive(search, summaries);
+ }
+
+ protected void derive(Search search, Summaries summaries) {
+ // TODO: This should really derive from the 'summaries' argument. Bug?
+ for (DocumentSummary documentSummary : search.getSummaries().values()) {
+ derive(documentSummary);
+ }
+ super.derive(search);
+ }
+
+ protected void derive(SDField field, Search search) {
+ }
+
+ private void derive(DocumentSummary documentSummary) {
+ for (SummaryField summaryField : documentSummary.getSummaryFields()) {
+ if (summaryField.getTransform()== SummaryTransform.NONE) continue;
+
+ if (summaryField.getTransform()==SummaryTransform.ATTRIBUTE ||
+ summaryField.getTransform()==SummaryTransform.DISTANCE ||
+ summaryField.getTransform()==SummaryTransform.GEOPOS ||
+ summaryField.getTransform()==SummaryTransform.POSITIONS) {
+ resultTransforms.put(summaryField.getName(),new FieldResultTransform(summaryField.getName(),
+ summaryField.getTransform(),
+ summaryField.getSingleSource()));
+ } else {
+ // Note: Currently source mapping is handled in the indexing statement,
+ // by creating a summary field for each of the values
+ // This works, but is suboptimal. We could consolidate to a minimal set and
+ // use the right value from the minimal set as the third parameter here,
+ // and add "override" commands to multiple static values
+ resultTransforms.put(summaryField.getName(),new FieldResultTransform(summaryField.getName(),
+ summaryField.getTransform(),
+ summaryField.getName()));
+ }
+ }
+ }
+
+ /** Returns a read-only iterator of the FieldResultTransforms of this summary map */
+ public Iterator resultTransformIterator() {
+ return Collections.unmodifiableCollection(resultTransforms.values()).iterator();
+ }
+
+ protected String getDerivedName() { return "summarymap"; }
+
+ /** Returns the command name of a transform */
+ private String getCommand(SummaryTransform transform) {
+ if (transform.equals(SummaryTransform.DISTANCE))
+ return "absdist";
+ else if (transform.isDynamic())
+ return "dynamicteaser";
+ else
+ return transform.getName();
+ }
+
+ /**
+ * Does this summary command name stand for a dynamic transform?
+ * We need this because some model information is shared through configs instead of model - see usage
+ */
+ public static boolean isDynamicCommand(String commandName) {
+ return (commandName.equals("dynamicteaser") || commandName.equals("smartsummary"));
+ }
+
+ @Override
+ public void getConfig(SummarymapConfig.Builder builder) {
+ builder.defaultoutputclass(-1);
+ for (FieldResultTransform frt : resultTransforms.values()) {
+ SummarymapConfig.Override.Builder oB = new SummarymapConfig.Override.Builder()
+ .field(frt.getFieldName())
+ .command(getCommand(frt.getTransform()));
+ if (frt.getTransform().isDynamic() ||
+ frt.getTransform().equals(SummaryTransform.ATTRIBUTE) ||
+ frt.getTransform().equals(SummaryTransform.DISTANCE) ||
+ frt.getTransform().equals(SummaryTransform.GEOPOS) ||
+ frt.getTransform().equals(SummaryTransform.POSITIONS) ||
+ frt.getTransform().equals(SummaryTransform.TEXTEXTRACTOR))
+ {
+ oB.arguments(frt.getArgument());
+ } else {
+ oB.arguments("");
+ }
+ builder.override(oB);
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/VsmFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/VsmFields.java
new file mode 100644
index 00000000000..65698675884
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/VsmFields.java
@@ -0,0 +1,275 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.document.*;
+import com.yahoo.document.datatypes.*;
+import com.yahoo.searchdefinition.FieldSets;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.FieldSet;
+import com.yahoo.searchdefinition.document.Matching;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.config.search.vsm.VsmfieldsConfig;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Vertical streaming matcher field specification
+ */
+public class VsmFields extends Derived implements VsmfieldsConfig.Producer {
+
+ private final Map<String, StreamingField> fields=new LinkedHashMap<>();
+ private final Map<String, StreamingDocumentType> doctypes=new LinkedHashMap<>();
+
+ public VsmFields(Search search) {
+ addSearchdefinition(search);
+ }
+
+ private void addSearchdefinition(Search search) {
+ derive(search);
+ }
+
+ protected void derive(SDDocumentType document,Search search) {
+ super.derive(document, search);
+ StreamingDocumentType docType=getDocumentType(document.getName());
+ if (docType == null) {
+ docType = new StreamingDocumentType(document.getName(), search.fieldSets());
+ doctypes.put(document.getName(), docType);
+ }
+ for (Object o : document.fieldSet()) {
+ derive(docType, (SDField) o);
+ }
+ }
+
+ protected void derive(StreamingDocumentType document, SDField field) {
+ if (field.usesStructOrMap()) {
+ for (SDField structField : field.getStructFields()) {
+ derive(document, structField); // Recursion
+ }
+ } else {
+
+ if (! (field.doesIndexing() || field.doesSummarying() || field.doesAttributing()) )
+ return;
+
+ StreamingField streamingField=new StreamingField(field);
+ addField(streamingField.getName(),streamingField);
+ deriveIndices(document, field, streamingField);
+ }
+ }
+
+ private void deriveIndices(StreamingDocumentType document, SDField field, StreamingField streamingField) {
+ if (field.doesIndexing()) {
+ addFieldToIndices(document, field.getName(), streamingField);
+ } else if (field.doesAttributing()) {
+ for (String indexName : field.getAttributes().keySet()) {
+ addFieldToIndices(document, indexName, streamingField);
+ }
+ }
+ }
+
+ private void addFieldToIndices(StreamingDocumentType document, String indexName, StreamingField streamingField) {
+ if (indexName.contains(".")) {
+ addFieldToIndices(document, indexName.substring(0,indexName.lastIndexOf(".")), streamingField); // Recursion
+ }
+ document.addIndexField(indexName, streamingField.getName());
+ }
+
+ private void addField(String name, StreamingField field) {
+ fields.put(name, field);
+ }
+
+ /** Returns a streaming index, or null if there is none with this name */
+ public StreamingDocumentType getDocumentType(String name) {
+ return doctypes.get(name);
+ }
+
+ public String getDerivedName() {
+ return "vsmfields";
+ }
+
+ @Override
+ public void getConfig(VsmfieldsConfig.Builder vsB) {
+ for (StreamingField streamingField : fields.values()) {
+ vsB.fieldspec(streamingField.getFieldSpecConfig());
+ }
+ for (StreamingDocumentType streamingDocType : doctypes.values()) {
+ vsB.documenttype(streamingDocType.getDocTypeConfig());
+ }
+ }
+
+ private static class StreamingField {
+
+ private final String name;
+
+ /** Whether this field does prefix matching by default */
+ private final Matching matching;
+
+ /** The type of this field */
+ private final Type type;
+
+ private final boolean isAttribute;
+
+ /** The streaming field type enumeration */
+ public static class Type {
+
+ public static Type INT8=new Type("int8","INT8");
+ public static Type INT16=new Type("int16","INT16");
+ public static Type INT32=new Type("int32","INT32");
+ public static Type INT64=new Type("int64","INT64");
+ public static Type FLOAT=new Type("float","FLOAT");
+ public static Type DOUBLE=new Type("double","DOUBLE");
+ public static Type STRING=new Type("string","AUTOUTF8");
+ public static Type UNSEARCHABLESTRING=new Type("string","NONE");
+
+ private String name;
+
+ private String searchMethod;
+
+ private Type(String name,String searchMethod) {
+ this.name=name;
+ this.searchMethod=searchMethod;
+ }
+
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ /** Returns the name of this type */
+ public String getName() { return name; }
+
+ public String getSearchMethod() { return searchMethod; }
+
+ public boolean equals(Object other) {
+ if ( ! (other instanceof Type)) return false;
+ return this.name.equals(((Type)other).name);
+ }
+
+ public String toString() {
+ return "type: " + name;
+ }
+
+ }
+
+ public StreamingField(SDField field) {
+ this(field.getName(),field.getDataType(),field.getMatching(), field.doesAttributing());
+ }
+
+ private StreamingField(String name,DataType sourceType, Matching matching, boolean isAttribute) {
+ this.name = name;
+ this.type = convertType(sourceType);
+ this.matching = matching;
+ this.isAttribute = isAttribute;
+ }
+
+ /** Converts to the right index type from a field datatype */
+ private static Type convertType(DataType fieldType) {
+ FieldValue fval = fieldType.createFieldValue();
+ if (fieldType.equals(DataType.FLOAT)) {
+ return Type.FLOAT;
+ } else if (fieldType.equals(DataType.LONG)) {
+ return Type.INT64;
+ } else if (fieldType.equals(DataType.DOUBLE)) {
+ return Type.DOUBLE;
+ } else if (fieldType.equals(DataType.BYTE)) {
+ return Type.INT8;
+ } else if (fieldType instanceof NumericDataType) {
+ return Type.INT32;
+ } else if (fval instanceof StringFieldValue) {
+ return Type.STRING;
+ } else if (fval instanceof Raw) {
+ return Type.STRING;
+ } else if (fval instanceof PredicateFieldValue) {
+ return Type.UNSEARCHABLESTRING;
+ } else if (fval instanceof TensorFieldValue) {
+ return Type.UNSEARCHABLESTRING;
+ } else if (fieldType instanceof CollectionDataType) {
+ return convertType(((CollectionDataType) fieldType).getNestedType());
+ } else {
+ throw new IllegalArgumentException("Don't know which streaming" +
+ " field type to " + "convert " + fieldType + " to");
+ }
+ }
+
+ public String getName() { return name; }
+
+ public VsmfieldsConfig.Fieldspec.Builder getFieldSpecConfig() {
+ VsmfieldsConfig.Fieldspec.Builder fB = new VsmfieldsConfig.Fieldspec.Builder();
+ String matchingName = matching.getType().getName();
+ if (matching.getType().equals(Matching.Type.TEXT))
+ matchingName = "";
+ if (matching.getType() != Matching.Type.EXACT) {
+ if (matching.isPrefix()) {
+ matchingName = "prefix";
+ } else if (matching.isSubstring()) {
+ matchingName = "substring";
+ } else if (matching.isSuffix()) {
+ matchingName = "suffix";
+ }
+ }
+ if (type != Type.STRING) {
+ matchingName = "";
+ }
+ fB.name(getName())
+ .searchmethod(VsmfieldsConfig.Fieldspec.Searchmethod.Enum.valueOf(type.getSearchMethod()))
+ .arg1(matchingName)
+ .fieldtype(isAttribute
+ ? VsmfieldsConfig.Fieldspec.Fieldtype.ATTRIBUTE
+ : VsmfieldsConfig.Fieldspec.Fieldtype.INDEX);
+ if (matching.maxLength() != null) {
+ fB.maxlength(matching.maxLength());
+ }
+ return fB;
+ }
+
+ public boolean equals(Object o) {
+ if (o.getClass().equals(getClass())) {
+ StreamingField sf = (StreamingField)o;
+ return name.equals(sf.name) &&
+ matching.equals(sf.matching) &&
+ type.equals(sf.type);
+ }
+ return false;
+ }
+
+ }
+
+ private static class StreamingDocumentType {
+ private final String name;
+ private final Map<String, FieldSet> fieldSets = new LinkedHashMap<>();
+ private final Map<String, FieldSet> userFieldSets;
+
+ public StreamingDocumentType(String name, FieldSets fieldSets) {
+ this.name=name;
+ userFieldSets = fieldSets.userFieldSets();
+ }
+
+ public VsmfieldsConfig.Documenttype.Builder getDocTypeConfig() {
+ VsmfieldsConfig.Documenttype.Builder dtB = new VsmfieldsConfig.Documenttype.Builder();
+ dtB.name(name);
+ Map<String, FieldSet> all = new LinkedHashMap<>();
+ all.putAll(fieldSets);
+ all.putAll(userFieldSets);
+ for (Map.Entry<String, FieldSet> e : all.entrySet()) {
+ VsmfieldsConfig.Documenttype.Index.Builder indB = new VsmfieldsConfig.Documenttype.Index.Builder();
+ indB.name(e.getValue().getName());
+ for (String field : e.getValue().getFieldNames()) {
+ indB.field(new VsmfieldsConfig.Documenttype.Index.Field.Builder().name(field));
+ }
+ dtB.index(indB);
+ }
+ return dtB;
+ }
+
+ public String getName() { return name; }
+
+ public void addIndexField(String indexName, String fieldName) {
+ FieldSet fs = fieldSets.get(indexName);
+ if (fs == null) {
+ fs = new FieldSet(indexName);
+ fieldSets.put(indexName, fs);
+ }
+ fs.addFieldName(fieldName);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/VsmSummary.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/VsmSummary.java
new file mode 100644
index 00000000000..aaf376f5cd9
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/VsmSummary.java
@@ -0,0 +1,107 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.document.PositionDataType;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.config.search.vsm.VsmsummaryConfig;
+
+import java.util.*;
+
+/**
+ * Vertical streaming matcher summary specification
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class VsmSummary extends Derived implements VsmsummaryConfig.Producer {
+ private Map<SummaryField, List<String>> summaryMap = new java.util.LinkedHashMap<>(1);
+
+ public VsmSummary(Search search) {
+ derive(search);
+ }
+
+ @Override
+ protected void derive(Search search) {
+ // Use the default class, as it is the superset
+ derive(search, search.getSummary("default"));
+ }
+
+ private void derive(Search search, DocumentSummary documentSummary) {
+ if (documentSummary==null) return;
+ for (SummaryField summaryField : documentSummary.getSummaryFields()) {
+ List<String> from = toStringList(summaryField.sourceIterator());
+
+ if (doMapField(search, summaryField)) {
+ SDField sdField = search.getField(summaryField.getName());
+ if (sdField != null && PositionDataType.INSTANCE.equals(sdField.getDataType())) {
+ summaryMap.put(summaryField, Collections.singletonList(summaryField.getName()));
+ } else {
+ summaryMap.put(summaryField, from);
+ }
+ }
+ }
+ }
+
+ /**
+ * Don't include field in map if sources are the same as the struct sub fields for the SDField.
+ * But do map if not all do summarying.
+ * Don't map if not struct either.
+ * @param summaryField a {@link SummaryField}
+ */
+ private boolean doMapField(Search search, SummaryField summaryField) {
+ SDField sdField = search.getField(summaryField.getName());
+ SDDocumentType document = search.getDocument();
+ if (sdField==null || ((document != null) && (document.getField(summaryField.getName()) == sdField))) {
+ return true;
+ }
+ if (summaryField.getVsmCommand().equals(SummaryField.VsmCommand.FLATTENJUNIPER)) {
+ return true;
+ }
+ if (!sdField.usesStructOrMap()) {
+ return !(sdField.getName().equals(summaryField.getName()));
+ }
+ if (summaryField.getSourceCount()==sdField.getStructFields().size()) {
+ for (SummaryField.Source source : summaryField.getSources()) {
+ if (!sdField.getStructFields().contains(new SDField(search.getDocument(), source.getName(), sdField.getDataType()))) { // equals() uses just name
+ return true;
+ }
+ if (sdField.getStructField(source.getName())!=null && !sdField.getStructField(source.getName()).doesSummarying()) {
+ return true;
+ }
+ }
+ // The sources in the summary field are the same as the sub-fields in the SD field.
+ // All sub fields do summarying.
+ // Don't map.
+ return false;
+ }
+ return true;
+ }
+
+ private List<String> toStringList(Iterator i) {
+ List<String> ret = new ArrayList<>();
+ while (i.hasNext()) {
+ ret.add(i.next().toString());
+ }
+ return ret;
+ }
+
+ public String getDerivedName() {
+ return "vsmsummary";
+ }
+
+ @Override
+ public void getConfig(VsmsummaryConfig.Builder vB) {
+ for (Map.Entry<SummaryField, List<String>> entry : summaryMap.entrySet()) {
+ VsmsummaryConfig.Fieldmap.Builder fmB = new VsmsummaryConfig.Fieldmap.Builder().summary(entry.getKey().getName());
+ for (String field : entry.getValue()) {
+ fmB.document(new VsmsummaryConfig.Fieldmap.Document.Builder().field(field));
+ }
+ fmB.command(VsmsummaryConfig.Fieldmap.Command.Enum.valueOf(entry.getKey().getVsmCommand().toString()));
+ vB.fieldmap(fmB);
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/package-info.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/package-info.java
new file mode 100644
index 00000000000..4e63729ea34
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.searchdefinition.derived;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/IndexStructureValidator.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/IndexStructureValidator.java
new file mode 100644
index 00000000000..360b4d0d637
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/IndexStructureValidator.java
@@ -0,0 +1,50 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived.validation;
+
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.derived.DerivedConfiguration;
+import com.yahoo.searchdefinition.derived.IndexingScript;
+import com.yahoo.vespa.indexinglanguage.ExpressionVisitor;
+import com.yahoo.vespa.indexinglanguage.expressions.*;
+
+/**
+ * @author <a href="mailto:mlidal@yahoo-inc.com">Mathias M Lidal</a>
+ */
+public class IndexStructureValidator extends Validator {
+
+ public IndexStructureValidator(DerivedConfiguration config, Search search) {
+ super(config, search);
+ }
+
+ public void validate() {
+ IndexingScript script = config.getIndexingScript();
+ for (Expression exp : script.expressions()) {
+ new OutputVisitor(search.getDocument(), exp).visit(exp);
+ }
+ }
+
+ private static class OutputVisitor extends ExpressionVisitor {
+
+ final SDDocumentType docType;
+ final Expression exp;
+
+ public OutputVisitor(SDDocumentType docType, Expression exp) {
+ this.docType = docType;
+ this.exp = exp;
+ }
+
+ @Override
+ protected void doVisit(Expression exp) {
+ if (!(exp instanceof OutputExpression)) {
+ return;
+ }
+ String fieldName = ((OutputExpression)exp).getFieldName();
+ if (docType.getField(fieldName) != null) {
+ return;
+ }
+ throw new IllegalArgumentException("Indexing expression '" + this.exp + "' refers to field '" +
+ fieldName + "' which does not exist in the index structure.");
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validation.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validation.java
new file mode 100644
index 00000000000..9b01881ddd8
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validation.java
@@ -0,0 +1,12 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived.validation;
+
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.derived.DerivedConfiguration;
+
+public class Validation {
+
+ public static void validate(DerivedConfiguration config, Search search) {
+ new IndexStructureValidator(config, search).validate();
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validator.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validator.java
new file mode 100644
index 00000000000..8c7c5afcb15
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/validation/Validator.java
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.derived.validation;
+
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.derived.DerivedConfiguration;
+
+import java.util.logging.Logger;
+
+/**
+ * @author mathiasm
+ */
+public abstract class Validator {
+ protected DerivedConfiguration config;
+ protected Search search;
+
+ protected Validator(DerivedConfiguration config, Search search) {
+ this.config = config;
+ this.search = search;
+ }
+
+ public abstract void validate();
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java
new file mode 100644
index 00000000000..35f6a41f0d8
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java
@@ -0,0 +1,320 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import com.yahoo.document.*;
+import com.yahoo.document.datatypes.*;
+import com.yahoo.tensor.TensorType;
+
+import java.io.Serializable;
+import java.util.LinkedHashSet;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * A search-time document attribute (per-document in-memory value).
+ * This belongs to the field defining the attribute.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public final class Attribute implements Cloneable, Serializable {
+
+ // Remember to change hashCode and equals when you add new fields
+
+ private String name;
+
+ private Type type;
+ private CollectionType collectionType;
+
+ /** True if only the enum information should be read from this attribute
+ * (i.e. the actual values are not relevant, only which documents have the
+ * same values) Used for collapsing and unique.
+ */
+ private boolean removeIfZero = false;
+ private boolean createIfNonExistent = false;
+ private boolean enableBitVectors = false;
+ private boolean enableOnlyBitVector = false;
+
+ private boolean fastSearch = false;
+ private boolean fastAccess = false;
+ private boolean huge = false;
+ private int arity = BooleanIndexDefinition.DEFAULT_ARITY;
+ private long lowerBound = BooleanIndexDefinition.DEFAULT_LOWER_BOUND;
+ private long upperBound = BooleanIndexDefinition.DEFAULT_UPPER_BOUND;
+ private double densePostingListThreshold = BooleanIndexDefinition.DEFAULT_DENSE_POSTING_LIST_THRESHOLD;
+ private Optional<TensorType> tensorType = Optional.empty();
+
+ private boolean isPosition = false;
+ private final Sorting sorting = new Sorting();
+
+ /** The aliases for this attribute */
+ private final Set<String> aliases = new LinkedHashSet<>();
+
+ /**
+ * True if this attribute should be returned during first pass of search.
+ * Null means make the default decision for this kind of attribute
+ */
+ private Boolean prefetch = null;
+
+ /** The attribute type enumeration */
+ public enum Type {
+ BYTE("byte", "INT8"),
+ SHORT("short", "INT16"),
+ INTEGER("integer", "INT32"),
+ LONG("long", "INT64"),
+ FLOAT("float", "FLOAT"),
+ DOUBLE("double", "DOUBLE"),
+ STRING("string", "STRING"),
+ PREDICATE("predicate", "PREDICATE"),
+ TENSOR("tensor", "TENSOR");
+
+ private final String myName; // different from what name() returns.
+ private final String exportAttributeTypeName;
+
+ private Type(String name, String exportAttributeTypeName) {
+ this.myName=name;
+ this.exportAttributeTypeName = exportAttributeTypeName;
+ }
+
+ public String getName() { return myName; }
+ public String getExportAttributeTypeName() { return exportAttributeTypeName; }
+
+ public String toString() {
+ return "type: " + myName;
+ }
+ }
+
+ /** The attribute collection type enumeration */
+ public enum CollectionType {
+
+ SINGLE("SINGLE"),
+ ARRAY("ARRAY"),
+ WEIGHTEDSET ("WEIGHTEDSET");
+
+ private final String name;
+
+ private CollectionType(String name) {
+ this.name=name;
+ }
+
+ public String getName() { return name; }
+
+ public String toString() {
+ return "collectiontype: " + name;
+ }
+ }
+
+ /** Creates an attribute with default settings */
+ public Attribute(String name,DataType fieldType) {
+ this(name,convertDataType(fieldType), convertCollectionType(fieldType));
+ setRemoveIfZero(fieldType instanceof WeightedSetDataType ? ((WeightedSetDataType)fieldType).removeIfZero() : false);
+ setCreateIfNonExistent(fieldType instanceof WeightedSetDataType ? ((WeightedSetDataType)fieldType).createIfNonExistent() : false);
+ }
+
+ public Attribute(String name,Type type, CollectionType collectionType) {
+ this.name=name;
+ setType(type);
+ setCollectionType(collectionType);
+ }
+
+ /**
+ * <p>Returns whether this attribute should be included in the "attributeprefetch" summary
+ * which is returned to the Qrs by prefetchAttributes, used by blending, uniquing etc.
+ *
+ * <p>Single value attributes are prefetched by default if summary is true.
+ * Multi value attributes are not.</p>
+ */
+ public boolean isPrefetch() {
+ if (prefetch!=null) return prefetch.booleanValue();
+
+ if (CollectionType.SINGLE.equals(collectionType)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /** Returns the prefetch value of this, null if the default is used. */
+ public Boolean getPrefetchValue() { return prefetch; }
+
+ public boolean isRemoveIfZero() { return removeIfZero; }
+ public boolean isCreateIfNonExistent(){ return createIfNonExistent; }
+ public boolean isEnabledBitVectors() { return enableBitVectors; }
+ public boolean isEnabledOnlyBitVector() { return enableOnlyBitVector; }
+ public boolean isFastSearch() { return fastSearch; }
+ public boolean isFastAccess() { return fastAccess; }
+ public boolean isHuge() { return huge; }
+ public boolean isPosition() { return isPosition; }
+
+ public int arity() { return arity; }
+ public long lowerBound() { return lowerBound; }
+ public long upperBound() { return upperBound; }
+ public double densePostingListThreshold() { return densePostingListThreshold; }
+ public Optional<TensorType> tensorType() { return tensorType; }
+
+ public Sorting getSorting() { return sorting; }
+
+ public void setRemoveIfZero(boolean remove) { this.removeIfZero = remove; }
+ public void setCreateIfNonExistent(boolean create) { this.createIfNonExistent = create; }
+
+ /**
+ * Sets whether this should be included in the "attributeprefetch" document summary.
+ * True or false to override default, null to use default
+ */
+ public void setPrefetch(Boolean prefetch) { this.prefetch = prefetch; }
+ public void setEnableBitVectors(boolean enableBitVectors) { this.enableBitVectors = enableBitVectors; }
+ public void setEnableOnlyBitVector(boolean enableOnlyBitVector) { this.enableOnlyBitVector = enableOnlyBitVector; }
+ public void setFastSearch(boolean fastSearch) { this.fastSearch = fastSearch; }
+ public void setHuge(boolean huge) { this.huge = huge; }
+ public void setFastAccess(boolean fastAccess) { this.fastAccess = fastAccess; }
+ public void setPosition(boolean position) { this.isPosition = position; }
+ public void setArity(int arity) { this.arity = arity; }
+ public void setLowerBound(long lowerBound) { this.lowerBound = lowerBound; }
+ public void setUpperBound(long upperBound) { this.upperBound = upperBound; }
+ public void setDensePostingListThreshold(double threshold) { this.densePostingListThreshold = threshold; }
+ public void setTensorType(TensorType tensorType) { this.tensorType = Optional.of(tensorType); }
+
+ public String getName() { return name; }
+ public Type getType() { return type; }
+ public CollectionType getCollectionType() { return collectionType; }
+
+ public void setName(String name) { this.name=name; }
+ private void setType(Type type) { this.type=type; }
+ public void setCollectionType(CollectionType type) { this.collectionType=type; }
+
+ /** Converts to the right attribute type from a field datatype */
+ public static Type convertDataType(DataType fieldType) {
+ FieldValue fval = fieldType.createFieldValue();
+ if (fval instanceof StringFieldValue) {
+ return Type.STRING;
+ } else if (fval instanceof IntegerFieldValue) {
+ return Type.INTEGER;
+ } else if (fval instanceof LongFieldValue) {
+ return Type.LONG;
+ } else if (fval instanceof FloatFieldValue) {
+ return Type.FLOAT;
+ } else if (fval instanceof DoubleFieldValue) {
+ return Type.DOUBLE;
+ } else if (fval instanceof ByteFieldValue) {
+ return Type.BYTE;
+ } else if (fval instanceof Raw) {
+ return Type.BYTE;
+ } else if (fval instanceof PredicateFieldValue) {
+ return Type.PREDICATE;
+ } else if (fval instanceof TensorFieldValue) {
+ return Type.TENSOR;
+ } else if (fieldType instanceof CollectionDataType) {
+ return convertDataType(((CollectionDataType) fieldType).getNestedType());
+ } else {
+ throw new IllegalArgumentException("Don't know which attribute type to " +
+ "convert " + fieldType + " to");
+ }
+ }
+
+ /** Converts to the right attribute type from a field datatype */
+ public static CollectionType convertCollectionType(DataType fieldType) {
+ if (fieldType instanceof ArrayDataType) {
+ return CollectionType.ARRAY;
+ } else if (fieldType instanceof WeightedSetDataType) {
+ return CollectionType.WEIGHTEDSET;
+ } else if (fieldType instanceof PrimitiveDataType) {
+ return CollectionType.SINGLE;
+ } else {
+ throw new IllegalArgumentException("Field " + fieldType + " not supported in convertCollectionType");
+ }
+ }
+
+ /** Converts to the right field type from an attribute type */
+ public static DataType convertAttrType(Type attrType) {
+ if (attrType== Type.STRING) {
+ return DataType.STRING;
+ } else if (attrType== Type.INTEGER) {
+ return DataType.INT;
+ } else if (attrType== Type.LONG) {
+ return DataType.LONG;
+ } else if (attrType== Type.FLOAT) {
+ return DataType.FLOAT;
+ } else if (attrType== Type.DOUBLE) {
+ return DataType.DOUBLE;
+ } else if (attrType == Type.BYTE) {
+ return DataType.BYTE;
+ } else if (attrType == Type.PREDICATE) {
+ return DataType.PREDICATE;
+ } else if (attrType == Type.TENSOR) {
+ return DataType.TENSOR;
+ } else {
+ throw new IllegalArgumentException("Don't know which attribute type to " +
+ "convert " + attrType + " to");
+ }
+ }
+
+ public DataType getDataType() {
+ DataType dataType = Attribute.convertAttrType(type);
+ if (collectionType.equals(Attribute.CollectionType.ARRAY)) {
+ return DataType.getArray(dataType);
+ } else if (collectionType.equals(Attribute.CollectionType.WEIGHTEDSET)) {
+ return DataType.getWeightedSet(dataType, createIfNonExistent, removeIfZero);
+ } else {
+ return dataType;
+ }
+ }
+
+ public int hashCode() {
+ return name.hashCode() +
+ type.hashCode() +
+ collectionType.hashCode() +
+ sorting.hashCode() +
+ (isPrefetch() ? 13 : 0) +
+ (fastSearch ? 17 : 0) +
+ (removeIfZero ? 47 : 0) +
+ (createIfNonExistent ? 53 : 0) +
+ (isPosition ? 61 : 0) +
+ (huge ? 67 : 0) +
+ (enableBitVectors ? 71 : 0) +
+ (enableOnlyBitVector ? 73 : 0) +
+ tensorType.hashCode();
+ }
+
+ public boolean equals(Object object) {
+ if (! (object instanceof Attribute)) return false;
+
+ Attribute other=(Attribute)object;
+ if (!this.name.equals(other.name)) return false;
+ return isCompatible(other);
+ }
+
+ /** Returns whether these attributes describes the same entity, even if they have different names */
+ public boolean isCompatible(Attribute other) {
+ if ( ! this.type.equals(other.type)) return false;
+ if ( ! this.collectionType.equals(other.collectionType)) return false;
+ if (this.isPrefetch() != other.isPrefetch()) return false;
+ if (this.removeIfZero != other.removeIfZero) return false;
+ if (this.createIfNonExistent != other.createIfNonExistent) return false;
+ if (this.enableBitVectors != other.enableBitVectors) return false;
+ if (this.enableOnlyBitVector != other.enableOnlyBitVector) return false;
+ // if (this.noSearch != other.noSearch) return false; No backend consequences so compatible for now
+ if (this.fastSearch != other.fastSearch) return false;
+ if (this.huge != other.huge) return false;
+ if ( ! this.sorting.equals(other.sorting)) return false;
+ if (!this.tensorType.equals(other.tensorType)) return false;
+
+ return true;
+ }
+
+ public @Override Attribute clone() {
+ try {
+ return (Attribute)super.clone();
+ }
+ catch (CloneNotSupportedException e) {
+ throw new RuntimeException("Programming error");
+ }
+ }
+
+ public String toString() {
+ return "attribute '" + name + "' (" + type + ")";
+ }
+
+ public Set<String> getAliases() {
+ return aliases;
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/BooleanIndexDefinition.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/BooleanIndexDefinition.java
new file mode 100644
index 00000000000..fc5494d0f53
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/BooleanIndexDefinition.java
@@ -0,0 +1,73 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import java.util.OptionalDouble;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+
+/**
+ * Encapsulates values required for native implementation of boolean search.
+ *
+ * @author <a href="mailto:lesters@yahoo-inc.com">Lester Solbakken</a>
+ * @since 5.2
+ */
+public final class BooleanIndexDefinition
+{
+ public static final int DEFAULT_ARITY = 8;
+ public static final long DEFAULT_UPPER_BOUND = Long.MAX_VALUE;
+ public static final long DEFAULT_LOWER_BOUND = Long.MIN_VALUE;
+ public static final double DEFAULT_DENSE_POSTING_LIST_THRESHOLD = 0.4;
+
+ private final OptionalInt arity; // mandatory field value
+ private final OptionalLong lowerBound;
+ private final OptionalLong upperBound;
+ private final OptionalDouble densePostingListThreshold;
+
+ public BooleanIndexDefinition(OptionalInt arity, OptionalLong lowerBound,
+ OptionalLong upperBound, OptionalDouble densePostingListThreshold) {
+ this.arity = arity;
+ this.lowerBound = lowerBound;
+ this.upperBound = upperBound;
+ this.densePostingListThreshold = densePostingListThreshold;
+ }
+
+ public int getArity() {
+ return arity.getAsInt();
+ }
+
+ public boolean hasArity() {
+ return arity.isPresent();
+ }
+
+ public long getLowerBound() {
+ return lowerBound.orElse(DEFAULT_LOWER_BOUND);
+ }
+
+ public boolean hasLowerBound() {
+ return lowerBound.isPresent();
+ }
+
+ public long getUpperBound() {
+ return upperBound.orElse(DEFAULT_UPPER_BOUND);
+ }
+
+ public boolean hasUpperBound() {
+ return upperBound.isPresent();
+ }
+
+ public double getDensePostingListThreshold() {
+ return densePostingListThreshold.orElse(DEFAULT_DENSE_POSTING_LIST_THRESHOLD);
+ }
+
+ public boolean hasDensePostingListThreshold() {
+ return densePostingListThreshold.isPresent();
+ }
+
+ @Override
+ public String toString() {
+ return "BooleanIndexDefinition [arity=" + arity + ", lowerBound="
+ + lowerBound + ", upperBound=" + upperBound + ", densePostingListThreshold="
+ + densePostingListThreshold + "]";
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/FieldSet.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/FieldSet.java
new file mode 100644
index 00000000000..040a798d3b3
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/FieldSet.java
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Searchable collection of fields.
+ *
+ * @author balder
+ */
+public class FieldSet {
+
+ private final String name;
+ private final Set<String> queryCommands = new LinkedHashSet<>();
+ private final Set<String> fieldNames = new TreeSet<>();
+ private final Set<SDField> fields = new TreeSet<>();
+ private Matching matching = null;
+
+ public FieldSet(String name) { this.name = name; }
+ public String getName() { return name; }
+ public FieldSet addFieldName(String field) { fieldNames.add(field); return this; }
+ public Set<String> getFieldNames() { return fieldNames; }
+ public Set<SDField> fields() { return fields; }
+
+ public Set<String> queryCommands() {
+ return queryCommands;
+ }
+
+ public void setMatching(Matching matching) {
+ this.matching = matching;
+ }
+
+ public Matching getMatching() {
+ return matching;
+ }
+
+ @Override
+ public String toString() { return "fieldset '" + name + "'"; }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/Matching.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/Matching.java
new file mode 100644
index 00000000000..3c90bcef513
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/Matching.java
@@ -0,0 +1,151 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import java.io.Serializable;
+
+/**
+ * Defines how a field should be matched.
+ * Matching objects can be compared based on their content, but they are <i>not</i> immutable.
+ *
+ * @author bratseth
+ */
+public class Matching implements Cloneable, Serializable {
+
+ public static final Type defaultType = Type.TEXT;
+
+ public static enum Type {
+ TEXT("text"),
+ WORD("word"),
+ EXACT("exact"),
+ GRAM("gram");
+ private String name;
+ Type(String name) { this.name = name; }
+ public String getName() { return name; }
+ }
+
+ /** Which match algorithm is used by this matching setup */
+ public enum Algorithm {
+ NORMAL("normal"),
+ PREFIX("prefix"),
+ SUBSTRING("substring"),
+ SUFFIX("suffix");
+ private String name;
+ Algorithm(String name) { this.name = name; }
+ public String getName() { return name; }
+ }
+
+ private Type type = Type.TEXT;
+
+ /** The basic match algorithm */
+ private Algorithm algorithm = Algorithm.NORMAL;
+
+ private boolean typeUserSet = false;
+
+ private boolean algorithmUserSet = false;
+
+ /** The gram size is the n in n-gram, or -1 if not set. Should only be set with gram matching. */
+ private int gramSize=-1;
+
+ /** Maximum number of characters to consider when searching in this field. Used for limiting resources, especially in streaming search. */
+ private Integer maxLength;
+
+ private String exactMatchTerminator=null;
+
+ /** Creates a matching of type "text" */
+ public Matching() {}
+
+ public Matching(Type type) {
+ this.type = type;
+ }
+
+ public Type getType() { return type; }
+
+ public void setType(Type type) {
+ this.type = type;
+ typeUserSet = true;
+ }
+
+ public Integer maxLength() { return maxLength; }
+ public Matching maxLength(int maxLength) { this.maxLength = maxLength; return this; }
+ public boolean isTypeUserSet() { return typeUserSet; }
+
+ public Algorithm getAlgorithm() { return algorithm; }
+
+ public void setAlgorithm(Algorithm algorithm) {
+ this.algorithm = algorithm;
+ algorithmUserSet = true;
+ }
+
+ public boolean isAlgorithmUserSet() { return algorithmUserSet; }
+
+ public boolean isPrefix() { return algorithm == Algorithm.PREFIX; }
+
+ public boolean isSubstring() { return algorithm == Algorithm.SUBSTRING; }
+
+ public boolean isSuffix() { return algorithm == Algorithm.SUFFIX; }
+
+ /** Returns the gram size, or -1 if not set. Should only be set with gram matching. */
+ public int getGramSize() { return gramSize; }
+
+ public void setGramSize(int gramSize) { this.gramSize=gramSize; }
+
+ /**
+ * Merge data from another matching object
+ */
+ public void merge(Matching m) {
+ if (m.isAlgorithmUserSet()) {
+ this.setAlgorithm(m.getAlgorithm());
+ }
+ if (m.isTypeUserSet()) {
+ this.setType(m.getType());
+ if (m.getType()==Type.GRAM)
+ gramSize=m.gramSize;
+ }
+ if (m.getExactMatchTerminator() != null) {
+ this.setExactMatchTerminator(m.getExactMatchTerminator());
+ }
+ }
+
+ /**
+ * If exact matching is used, this returns the terminator string
+ * which terminates an exact matched sequence in queries. If exact
+ * matching is not used, or no terminator is set, this is null
+ */
+ public String getExactMatchTerminator() { return exactMatchTerminator; }
+
+ /**
+ * Sets the terminator string which terminates an exact matched
+ * sequence in queries (used if type is EXACT).
+ */
+ public void setExactMatchTerminator(String exactMatchTerminator) {
+ this.exactMatchTerminator = exactMatchTerminator;
+ }
+
+ public String toString() {
+ return type + " matching [" + (type==Type.GRAM ? "gram size " + gramSize : "supports " + algorithm) + "], [exact-terminator "+exactMatchTerminator+"]";
+ }
+
+ public Matching clone() {
+ try {
+ return (Matching)super.clone();
+ }
+ catch (CloneNotSupportedException e) {
+ throw new RuntimeException("Programming error");
+ }
+ }
+
+ public boolean equals(Object o) {
+ if (! (o instanceof Matching)) return false;
+
+ Matching other=(Matching)o;
+ if ( ! other.type.equals(this.type)) return false;
+ if ( ! other.algorithm.equals(this.algorithm)) return false;
+ if ( this.exactMatchTerminator==null && other.exactMatchTerminator!=null) return false;
+ if ( this.exactMatchTerminator!=null && ( ! this.exactMatchTerminator.equals(other.exactMatchTerminator)) )
+ return false;
+ if ( gramSize!=other.gramSize) return false;
+ return true;
+ }
+
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/NormalizeLevel.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/NormalizeLevel.java
new file mode 100644
index 00000000000..945593d550b
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/NormalizeLevel.java
@@ -0,0 +1,87 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+/**
+ * class representing the character normalization
+ * we want to do on query and indexed text.
+ * Levels are strict subsets, so doing accent
+ * removal means doing codepoint normalizing
+ * and case normalizing also.
+ */
+// TODO: Missing author
+public class NormalizeLevel {
+
+ /**
+ * The current levels are as follows:
+ * NONE: no changes to input text
+ * CODEPOINT: convert text into Unicode
+ * Normalization Form Compatibility Composition
+ * LOWERCASE: also convert text into lowercase letters
+ * ACCENT: do both above and remove accents on characters
+ */
+ public enum Level {
+ NONE, CODEPOINT, LOWERCASE, ACCENT
+ }
+
+ private boolean userSpecified = false;
+ private Level level = Level.ACCENT;
+
+ /**
+ * Returns whether accents should be removed from text
+ */
+ public boolean doRemoveAccents() { return level == Level.ACCENT; }
+
+ /**
+ * Construct a default (full) normalizelevel,
+ */
+ public NormalizeLevel() {}
+
+ /**
+ * Construct for a specific level, possibly user specified
+ *
+ * @param level which level to use
+ * @param fromUser whether this was specified by the user
+ */
+ public NormalizeLevel(Level level, boolean fromUser) {
+ this.level = level;
+ this.userSpecified = fromUser;
+ }
+
+ /**
+ * Change the current level to CODEPOINT as inferred
+ * by other features' needs. If the current level
+ * was user specified it will not change; also this
+ * will not increase the level.
+ */
+ public void inferCodepoint() {
+ if (userSpecified) {
+ // ignore inferred changes if user specified something
+ return;
+ }
+ // do not increase level
+ if (level != Level.NONE) level = Level.CODEPOINT;
+ }
+
+ /**
+ * Change the current level to LOWERCASE as inferred
+ * by other features' needs. If the current level
+ * was user specified it will not change; also this
+ * will not increase the level.
+ */
+ public void inferLowercase() {
+ if (userSpecified) {
+ // ignore inferred changes if user specified something
+ return;
+ }
+ // do not increase level
+ if (level == Level.NONE) return;
+ if (level == Level.CODEPOINT) return;
+
+ level = Level.LOWERCASE;
+ }
+
+ public Level getLevel() {
+ return level;
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/RankType.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/RankType.java
new file mode 100644
index 00000000000..f2b0b6a9164
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/RankType.java
@@ -0,0 +1,45 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * The rank type of a field. For now this is just a container of a string name.
+ * This class is immutable.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public enum RankType {
+
+ /** *implicit* default: No type has been set. */
+ DEFAULT,
+
+ // Rank types which can be set explicitly. These are defined for Vespa in NativeRankTypeDefinitionSet
+ IDENTITY, ABOUT, TAGS, EMPTY;
+
+ @Override
+ public String toString() {
+ return "rank type " + name().toLowerCase();
+ }
+
+ /**
+ * Returns the rank type from a string, regardless of its case.
+ *
+ * @param rankTypeName a rank type name in any casing
+ * @return the rank type found
+ * @throws IllegalArgumentException if not found
+ */
+ public static RankType fromString(String rankTypeName) {
+ try {
+ return RankType.valueOf(rankTypeName.toUpperCase());
+ }
+ catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Unknown rank type '" + rankTypeName + "'. Supported rank types are " +
+ "'identity', 'about', 'tags' and 'empty'.");
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/Ranking.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/Ranking.java
new file mode 100644
index 00000000000..9e0bda7fd5a
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/Ranking.java
@@ -0,0 +1,68 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import java.io.Serializable;
+
+/**
+ * The rank settings given in a rank clause in the search definition.
+ *
+ * @author <a href="mailto:vehardh@yahoo-inc.com">Vegard Havdal</a>
+ */
+public class Ranking implements Cloneable, Serializable {
+
+ private boolean literal = false;
+ private boolean filter = false;
+ private boolean normal = false;
+
+ /**
+ * <p>Returns whether literal (non-stemmed, non-normalized) forms of the words should
+ * be indexed in a separate index which is searched by a automatically added rank term
+ * during searches.</p>
+ *
+ * <p>Default is false.</p>
+ */
+ public boolean isLiteral() { return literal; }
+
+ public void setLiteral(boolean literal) { this.literal = literal; }
+
+ /**
+ * <p>Returns whether this is a filter. Filters will only tell if they are matched or not,
+ * no detailed relevance information will be available about the match.</p>
+ *
+ * <p>Matching a filter is much cheaper for the search engine than matching a regular field.</p>
+ *
+ * <p>Default is false.</p>
+ */
+ public boolean isFilter() { return filter && !normal; }
+
+ public void setFilter(boolean filter) { this.filter = filter; }
+
+ /** Whether user has explicitly requested normal (non-filter) behavior */
+ public boolean isNormal() { return normal; }
+ public void setNormal(boolean n) { this.normal = n; }
+
+ /** Returns true if the given rank settings are the same */
+ public @Override boolean equals(Object o) {
+ if ( ! (o instanceof Ranking)) return false;
+
+ Ranking other=(Ranking)o;
+ if (this.filter != other.filter) return false;
+ if (this.literal != other.literal) return false;
+ if (this.normal != other.normal) return false;
+ return true;
+ }
+
+ public @Override String toString() {
+ return "rank settings [filter: " + filter + ", literal: " + literal + ", normal: "+normal+"]";
+ }
+
+ public @Override Ranking clone() {
+ try {
+ return (Ranking)super.clone();
+ }
+ catch (CloneNotSupportedException e) {
+ throw new RuntimeException("Programming error",e);
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/SDDocumentType.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDDocumentType.java
new file mode 100644
index 00000000000..6292dc9ef72
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDDocumentType.java
@@ -0,0 +1,316 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import com.yahoo.document.*;
+import com.yahoo.document.annotation.AnnotationType;
+import com.yahoo.document.annotation.AnnotationTypeRegistry;
+import com.yahoo.documentmodel.NewDocumentType;
+import com.yahoo.documentmodel.VespaDocumentType;
+import com.yahoo.searchdefinition.FieldSets;
+import com.yahoo.searchdefinition.Search;
+
+import java.io.Serializable;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ <p>A document definition is a list of fields. Documents may inherit other documents,
+ implicitly acquiring their fields as it's own. If a document is not set to inherit
+ any document, it will always inherit the document "document.0".</p>
+
+ @author <a href="thomasg@yahoo-inc.com">Thomas Gundersen</a>
+ @author <a href="bratseth@yahoo-inc.com">Jon S Bratseth</a>
+*/
+public class SDDocumentType implements Cloneable, Serializable {
+ public static final SDDocumentType VESPA_DOCUMENT;
+ private Map<DataTypeName, SDDocumentType> inheritedTypes = new HashMap<>();
+ private Map<NewDocumentType.Name, SDDocumentType> ownedTypes = new HashMap<>();
+ private AnnotationTypeRegistry annotationTypes = new AnnotationTypeRegistry();
+ private DocumentType docType;
+ private DataType structType;
+ // The field sets here are set from the processing step in SD, to ensure that the full Search and this SDDocumentType is built first.
+ private FieldSets fieldSets;
+
+ static {
+ VESPA_DOCUMENT = new SDDocumentType(VespaDocumentType.INSTANCE.getFullName().getName());
+ VESPA_DOCUMENT.addType(createSDDocumentType(PositionDataType.INSTANCE));
+ }
+
+ public SDDocumentType clone() throws CloneNotSupportedException {
+ SDDocumentType type = (SDDocumentType) super.clone();
+ type.docType = docType.clone();
+ type.inheritedTypes.putAll(inheritedTypes);
+ type.structType = structType;
+ return type;
+ }
+
+ /**
+ * For adding structs defined in document scope
+ *
+ * @param dt The struct to add.
+ * @return self, for chaining
+ */
+ public SDDocumentType addType(SDDocumentType dt) {
+ NewDocumentType.Name name = new NewDocumentType.Name(dt.getName());
+ if (getType(name) != null) {
+ throw new IllegalArgumentException("Data type '" + name.toString() + "' has already been used.");
+ }
+ if (name.getName() == docType.getName()) {
+ throw new IllegalArgumentException("Data type '" + name.toString() + "' can not have same name as its defining document.");
+ }
+ ownedTypes.put(name, dt);
+ return this;
+ }
+ public final SDDocumentType getOwnedType(String name) {
+ return getOwnedType(new NewDocumentType.Name(name));
+ }
+ public SDDocumentType getOwnedType(DataTypeName name) {
+ return getOwnedType(name.getName());
+ }
+
+ public SDDocumentType getOwnedType(NewDocumentType.Name name) {
+ return ownedTypes.get(name);
+ }
+
+ public final SDDocumentType getType(String name) {
+ return getType(new NewDocumentType.Name(name));
+ }
+
+ public SDDocumentType getType(NewDocumentType.Name name) {
+ SDDocumentType type = ownedTypes.get(name);
+ if (type == null) {
+ for (SDDocumentType inherited : inheritedTypes.values()) {
+ type = inherited.getType(name);
+ if (type != null) {
+ return type;
+ }
+ }
+ }
+ return type;
+ }
+
+ public SDDocumentType addAnnotation(AnnotationType annotation) {
+ annotationTypes.register(annotation);
+ return this;
+ }
+
+ /**
+ * Access to all owned datatypes.
+ * @return all types
+ */
+ public Collection<SDDocumentType> getTypes() { return ownedTypes.values(); }
+ public Collection<AnnotationType> getAnnotations() { return annotationTypes.getTypes().values(); }
+ public AnnotationType findAnnotation(String name) { return annotationTypes.getType(name); }
+
+ public Collection<SDDocumentType> getAllTypes() {
+ Collection<SDDocumentType> list = new ArrayList<>();
+ list.addAll(getTypes());
+ for (SDDocumentType inherited : inheritedTypes.values()) {
+ list.addAll(inherited.getAllTypes());
+ }
+ return list;
+ }
+
+ /**
+ * Creates a new document type.
+ * The document type id will be generated as a hash from the document type name.
+ *
+ * @param name The name of the new document type
+ */
+ public SDDocumentType(String name) {
+ this(name,null);
+ }
+
+ public SDDocumentType(DataTypeName name) {
+ this(name.getName());
+ }
+
+ /**
+ * Creates a new document type.
+ * The document type id will be generated as a hash from the document type name.
+ *
+ * @param name The name of the new document type
+ * @param search check for type ID collisions in this search definition
+ */
+ public SDDocumentType(String name, Search search) {
+ docType = new DocumentType(name);
+ docType.getHeaderType().setCompressionConfig(new CompressionConfig());
+ docType.getBodyType().setCompressionConfig(new CompressionConfig());
+ validateId(search);
+ inherit(VESPA_DOCUMENT);
+ }
+
+ public boolean isStruct() { return getStruct() != null; }
+ public DataType getStruct() { return structType; }
+ public SDDocumentType setStruct(DataType structType) {
+ if (structType != null) {
+ this.structType = structType;
+ inheritedTypes.clear();
+ } else {
+ if (docType.getHeaderType() != null) {
+ this.structType = docType.getHeaderType();
+ inheritedTypes.clear();
+ } else {
+ throw new IllegalArgumentException("You can not set a null struct");
+ }
+ }
+ return this;
+ }
+
+ public String getName() { return docType.getName(); }
+ public DataTypeName getDocumentName() { return docType.getDataTypeName(); }
+ public DocumentType getDocumentType() { return docType; }
+
+ public void inherit(DataTypeName name) {
+ if ( ! inheritedTypes.containsKey(name)) {
+ inheritedTypes.put(name, new TemporarySDDocumentType(name));
+ }
+ }
+
+ public void inherit(SDDocumentType type) {
+ if (type != null) {
+ if (!inheritedTypes.containsKey(type.getDocumentName()) || (inheritedTypes.get(type.getDocumentName()) instanceof TemporarySDDocumentType)) {
+ inheritedTypes.put(type.getDocumentName(), type);
+ }
+ }
+ }
+
+ public Collection<SDDocumentType> getInheritedTypes() { return inheritedTypes.values(); }
+
+ protected void validateId(Search search) {
+ if (search == null) return;
+ if (search.getDocument(getName()) == null) return;
+ SDDocumentType doc = search.getDocument();
+ throw new IllegalArgumentException("Failed creating document type '" + getName() + "', " +
+ "document type '" + doc.getName() + "' already uses ID '" + doc.getName() + "'");
+ }
+
+ public void setFieldId(SDField field, int id) {
+ field.setId(id, docType);
+ }
+
+ /** Override getField, as it may need to ask inherited types that isn't registered in document type.
+ * @param name Name of field to get.
+ * @return The field found.
+ */
+ public Field getField(String name) {
+ if (name.contains(".")) {
+ String superFieldName = name.substring(0,name.indexOf("."));
+ String subFieldName = name.substring(name.indexOf(".")+1);
+ Field f = docType.getField(superFieldName);
+ if (f != null) {
+ if (f instanceof SDField) {
+ SDField superField = (SDField)f;
+ return superField.getStructField(subFieldName);
+ } else {
+ throw new IllegalArgumentException("Field "+f.getName()+" is not SDField");
+ }
+ }
+ }
+ Field f = docType.getField(name);
+ if (f == null) {
+ for(SDDocumentType parent : inheritedTypes.values()) {
+ f = parent.getField(name);
+ if (f != null) return f;
+ }
+ }
+ return f;
+ }
+
+ public void addField(Field field) {
+ verifyInheritance(field);
+ for (Iterator<Field> i = docType.fieldIteratorThisTypeOnly(); i.hasNext(); ) {
+ if (field.getName().equalsIgnoreCase((i.next()).getName())) {
+ throw new IllegalArgumentException("Duplicate (case insensitively) " + field + " in " + this);
+ }
+ }
+ docType.addField(field);
+ }
+
+ /**
+ * This is SD parse-time inheritance check.
+ *
+ * @param field The field being verified.
+ */
+ private void verifyInheritance(Field field) {
+ for (SDDocumentType parent : inheritedTypes.values()) {
+ for (Field pField : parent.fieldSet()) {
+ if (pField.getName().equals(field.getName())) {
+ if (!pField.getDataType().equals(field.getDataType())) {
+ throw new IllegalArgumentException("For search '"+getName()+"', field '"+field.getName()+"': " +
+ "datatype can't be different from that of same field in supertype '"+parent.getName()+"'.");
+ }
+ }
+ }
+ }
+ }
+
+ public SDField addField(String string, DataType dataType) {
+ SDField field = new SDField(this, string, dataType);
+ addField(field);
+ return field;
+ }
+
+ public Field addField(String string, DataType dataType, boolean header, int code) {
+ SDField field = new SDField(this, string, code, dataType, header);
+ addField(field);
+ return field;
+ }
+
+ private Map<String, Field> fieldsInherited() {
+ Map<String, Field> map = new LinkedHashMap<>();
+ for (SDDocumentType parent : inheritedTypes.values()) {
+ for (Field field : parent.fieldSet()) {
+ map.put(field.getName(), field);
+ }
+ }
+ return map;
+ }
+
+ public Set<Field> fieldSet() {
+ Map<String, Field> map = fieldsInherited();
+ Iterator<Field> it = docType.fieldIteratorThisTypeOnly();
+ while (it.hasNext()) {
+ Field field = it.next();
+ map.put(field.getName(), field);
+ }
+ return new LinkedHashSet<>(map.values());
+ }
+
+ public Iterator<Field> fieldIterator() {
+ return fieldSet().iterator();
+ }
+
+ public int getFieldCount() {
+ return docType.getFieldCount();
+ }
+
+ public String toString() {
+ return "SD document type '" + docType.getName() + "'";
+ }
+
+ private static SDDocumentType createSDDocumentType(StructDataType structType) {
+ SDDocumentType docType = new SDDocumentType(structType.getName());
+ for (Field field : structType.getFields()) {
+ docType.addField(new SDField(docType, field.getName(), field.getDataType()));
+ }
+ docType.setStruct(structType);
+ return docType;
+ }
+
+ /**
+ * The field sets defined for this type and its {@link Search}
+ * @return fieldsets
+ */
+ public FieldSets getFieldSets() {
+ return fieldSets;
+ }
+
+ /**
+ * Sets the field sets for this
+ * @param fieldSets field sets to set for this object
+ */
+ public void setFieldSets(FieldSets fieldSets) {
+ this.fieldSets = fieldSets;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java
new file mode 100644
index 00000000000..b379abd63ec
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/SDField.java
@@ -0,0 +1,770 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import com.yahoo.document.*;
+import com.yahoo.language.Linguistics;
+import com.yahoo.language.simple.SimpleLinguistics;
+import com.yahoo.searchdefinition.Index;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.fieldoperation.FieldOperation;
+import com.yahoo.searchdefinition.fieldoperation.FieldOperationContainer;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.indexinglanguage.ExpressionSearcher;
+import com.yahoo.vespa.indexinglanguage.ExpressionVisitor;
+import com.yahoo.vespa.indexinglanguage.ScriptParserContext;
+import com.yahoo.vespa.indexinglanguage.expressions.*;
+import com.yahoo.vespa.indexinglanguage.parser.IndexingInput;
+import com.yahoo.vespa.indexinglanguage.parser.ParseException;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * The field class represents a document field. It is used in
+ * the Document class to get and set fields. Each SDField has
+ * a name, a numeric ID, a data type, and a boolean that says whether it's
+ * a header field. The numeric ID is used when the fields are stored
+ * in serialized form.
+ *
+ * @author bratseth
+ */
+public class SDField extends Field implements TypedKey, FieldOperationContainer, Serializable {
+
+ /** Use this field for modifying index-structure, even if it doesn't
+ have any indexing code */
+ private boolean indexStructureField = false;
+
+ /** The indexing statements to be applied to this value during indexing */
+ private ScriptExpression indexingScript = new ScriptExpression();
+
+ /** The default rank type for indices of this field */
+ private RankType rankType = RankType.DEFAULT;
+
+ /** Rank settings in a "rank" block for the field. */
+ private Ranking ranking = new Ranking();
+
+ /**
+ * The literal boost of this field. This boost is added to a rank score
+ * when a query term matched as query term exactly (unnormalized and unstemmed).
+ * Non-positive boosts causes no boosting, 0 allows boosts
+ * to be specified in other rank profiles, while negative values
+ * turns the capability off.
+ */
+ private int literalBoost=-1;
+
+ /** The weight of this field. This is a percentage,
+ * so 100 is default to provide the identity transform. */
+ private int weight=100;
+
+ /**
+ * Indicates what kind of matching should be done on this field
+ */
+ private Matching matching=new Matching();
+
+ /** Attribute settings, or null if there are none */
+ private Map<String, Attribute> attributes = new TreeMap<>();
+
+ /**
+ * The stemming setting of this field, or null to use the default.
+ * Default is determined by the owning search definition.
+ */
+ private Stemming stemming=null;
+
+ /** How content of this field should be accent normalized etc. */
+ private NormalizeLevel normalizing = new NormalizeLevel();
+
+ /** Extra query commands of this field */
+ private List<String> queryCommands=new java.util.ArrayList<>(0);
+
+ /** Summary fields defined in this field */
+ private Map<String,SummaryField> summaryFields = new java.util.LinkedHashMap<>(0);
+
+ /** The explicitly index settings on this field */
+ private Map<String, Index> indices=new java.util.LinkedHashMap<>();
+
+ /** True if body or header is set explicitly for this field */
+ private boolean headerOrBodyDefined = false;
+
+ private boolean idOverride = false;
+
+ /** Struct fields defined in this field */
+ private Map<String,SDField> structFields = new java.util.LinkedHashMap<>(0);
+
+ /** The document that this field was declared in, or null*/
+ protected SDDocumentType ownerDocType = null;
+
+ /** The aliases declared for this field. May pertain to indexes or attributes */
+ private Map<String, String> aliasToName = new HashMap<>();
+
+ /** Pending operations that must be applied after parsing, due to use of not-yet-defined structs. */
+ private List<FieldOperation> pendingOperations = new LinkedList<>();
+
+ private boolean isExtraField = false;
+
+ /**
+ Creates a new field. This method is only used to create reserved fields
+ @param name The name of the field
+ @param dataType The datatype of the field
+ @param isHeader Whether this is a "header" field or a "content" field
+ (true = "header").
+ */
+ protected SDField(SDDocumentType repo, String name, int id, DataType dataType, boolean isHeader, boolean populate) {
+ super(name, id, dataType, isHeader);
+ populate(populate, repo, name, dataType, isHeader);
+ }
+
+ public SDField(SDDocumentType repo, String name, int id, DataType dataType, boolean isHeader) {
+ this(repo, name, id, dataType, isHeader, true);
+ }
+
+ /**
+ Creates a new field.
+
+ @param name The name of the field
+ @param dataType The datatype of the field
+ @param isHeader Whether this is a "header" field or a "content" field
+ (true = "header").
+ */
+ public SDField(SDDocumentType repo, String name, DataType dataType, boolean isHeader, boolean populate) {
+ super(name,dataType,isHeader);
+ populate(populate, repo, name, dataType, isHeader);
+ }
+
+ private void populate(boolean populate, SDDocumentType repo, String name, DataType dataType, boolean isHeader) {
+ populate(populate,repo, name, dataType, isHeader, null, 0);
+ }
+
+ private void populate(boolean populate, SDDocumentType repo, String name, DataType dataType, boolean isHeader, Matching fieldMatching, int recursion) {
+ if (populate || (dataType instanceof MapDataType)) {
+ populateWithStructFields(repo, name, dataType, isHeader, recursion);
+ populateWithStructMatching(repo, name, dataType, fieldMatching);
+ }
+ }
+
+ public SDField(String name, DataType dataType, boolean isHeader) {
+ this(null, name, dataType, isHeader, true);
+ }
+
+ /**
+ Creates a new field.
+
+ @param name The name of the field
+ @param dataType The datatype of the field
+ @param isHeader Whether this is a "header" field or a "content" field
+ (true = "header").
+ @param owner the owning document (used to check for id collisions)
+ */
+ protected SDField(SDDocumentType repo, String name, DataType dataType, boolean isHeader, SDDocumentType owner, boolean populate) {
+ super(name, dataType, isHeader, owner == null ? null : owner.getDocumentType());
+ this.ownerDocType=owner;
+ populate(populate, repo, name, dataType, isHeader);
+ }
+
+ /**
+ Creates a new field.
+
+ @param name The name of the field
+ @param dataType The datatype of the field
+ @param isHeader Whether this is a "header" field or a "content" field
+ (true = "header").
+ @param owner The owning document (used to check for id collisions)
+ @param fieldMatching The matching object to set for the field
+ */
+ protected SDField(SDDocumentType repo, String name, DataType dataType, boolean isHeader, SDDocumentType owner, Matching fieldMatching, boolean populate, int recursion) {
+ super(name, dataType, isHeader, owner == null ? null : owner.getDocumentType());
+ this.ownerDocType=owner;
+ if (fieldMatching != null) {
+ this.setMatching(fieldMatching);
+ }
+ populate(populate, repo, name, dataType, isHeader, fieldMatching, recursion);
+ }
+
+ /**
+ Constructor for <b>header</b> fields
+
+ @param name The name of the field
+ @param dataType The datatype of the field
+ */
+ public SDField(SDDocumentType repo, String name, DataType dataType) {
+ this(repo, name,dataType,true, true);
+ }
+ public SDField(String name, DataType dataType) {
+ this(null, name,dataType);
+ }
+
+ public void setIsExtraField(boolean isExtra) {
+ isExtraField = isExtra;
+ }
+
+ public boolean isExtraField() {
+ return isExtraField;
+ }
+
+ public boolean doesAttributing() {
+ return containsExpression(AttributeExpression.class);
+ }
+
+ public boolean doesIndexing() {
+ return containsExpression(IndexExpression.class);
+ }
+
+ public boolean doesSummarying() {
+ if (usesStruct()) {
+ for (SDField structField : getStructFields()) {
+ if (structField.doesSummarying()) {
+ return true;
+ }
+ }
+ }
+ return containsExpression(SummaryExpression.class);
+ }
+
+ public boolean doesLowerCasing() {
+ return containsExpression(LowerCaseExpression.class);
+ }
+
+ public <T extends Expression> boolean containsExpression(Class<T> searchFor) {
+ return findExpression(searchFor) != null;
+ }
+
+ public <T extends Expression> T findExpression(Class<T> searchFor) {
+ return new ExpressionSearcher<>(searchFor).searchIn(indexingScript);
+ }
+
+ public void addSummaryFieldSources(SummaryField summaryField) {
+ if (usesStruct()) {
+ /*
+ * How this works for structs: When at least one sub-field in a struct is to
+ * be used for summary, that whole struct field is included in summary.cfg. Then,
+ * vsmsummary.cfg specifies the sub-fields used for each struct field.
+ * So we recurse into each struct, adding the destination classes set for each sub-field
+ * to the main summary-field for the struct field.
+ */
+ for (SDField structField : getStructFields()) {
+ for (SummaryField sumF : structField.getSummaryFields()) {
+ for (String dest : sumF.getDestinations()) {
+ summaryField.addDestination(dest);
+ }
+ }
+ structField.addSummaryFieldSources(summaryField);
+ }
+ } else {
+ if (doesSummarying()) {
+ summaryField.addSource(getName());
+ }
+ }
+ }
+
+ public void populateWithStructFields(SDDocumentType sdoc, String name, DataType dataType, boolean isHeader, int recursion) {
+ DataType dt = getFirstStructOrMapRecursive();
+ if (dt == null) {
+ return;
+ }
+ if (dataType instanceof MapDataType) {
+ MapDataType mdt = (MapDataType) dataType;
+
+ SDField keyField = new SDField(sdoc, name.concat(".key"), mdt.getKeyType(),
+ isHeader, getOwnerDocType(), new Matching(), true, recursion + 1);
+ structFields.put("key", keyField);
+
+ SDField valueField = new SDField(sdoc, name.concat(".value"), mdt.getValueType(),
+ isHeader, getOwnerDocType(), new Matching(), true, recursion + 1);
+ structFields.put("value", valueField);
+ } else {
+ if (recursion >= 10) {
+ return;
+ }
+ if (dataType instanceof CollectionDataType) {
+ dataType = ((CollectionDataType)dataType).getNestedType();
+ }
+ SDDocumentType subType = sdoc != null ? sdoc.getType(dataType.getName()) : null;
+ if (subType == null) {
+ throw new IllegalArgumentException("Could not find struct '" + dataType.getName() + "'.");
+ }
+ for (Field field : subType.fieldSet()) {
+ SDField subField = new SDField(sdoc, name.concat(".").concat(field.getName()), field.getDataType(),
+ isHeader, subType, new Matching(), true, recursion + 1);
+ structFields.put(field.getName(), subField);
+ }
+ }
+ }
+
+ public void populateWithStructMatching(SDDocumentType sdoc, String name, DataType dataType,
+ Matching superFieldMatching) {
+ DataType dt = getFirstStructOrMapRecursive();
+ if (dt != null) {
+ if (dataType instanceof MapDataType) {
+ MapDataType mdt = (MapDataType) dataType;
+
+ Matching keyFieldMatching = new Matching();
+ if (superFieldMatching != null) {
+ keyFieldMatching.merge(superFieldMatching);
+ }
+ SDField keyField = structFields.get(name.concat(".key"));
+ if (keyField != null) {
+ keyField.populateWithStructMatching(sdoc, name.concat(".key"), mdt.getKeyType(), keyFieldMatching);
+ keyField.setMatching(keyFieldMatching);
+ }
+
+ Matching valueFieldMatching = new Matching();
+ if (superFieldMatching != null) {
+ valueFieldMatching.merge(superFieldMatching);
+ }
+ SDField valueField = structFields.get(name.concat(".value"));
+ if (valueField != null) {
+ valueField.populateWithStructMatching(sdoc, name.concat(".value"), mdt.getValueType(),
+ valueFieldMatching);
+ valueField.setMatching(valueFieldMatching);
+ }
+
+ } else {
+
+ if (dataType instanceof CollectionDataType) {
+ dataType = ((CollectionDataType)dataType).getNestedType();
+ }
+ SDDocumentType subType = sdoc != null ? sdoc.getType(dataType.getName()) : null;
+ if (subType != null) {
+ for (Field f : subType.fieldSet()) {
+ if (f instanceof SDField) {
+ SDField field = (SDField)f;
+ Matching subFieldMatching = new Matching();
+ if (superFieldMatching != null) {
+ subFieldMatching.merge(superFieldMatching);
+ }
+ subFieldMatching.merge(field.getMatching());
+ SDField subField = structFields.get(field.getName());
+ if (subField != null) {
+ subField.populateWithStructMatching(sdoc, name.concat(".").concat(field.getName()), field.getDataType(),
+ subFieldMatching);
+ subField.setMatching(subFieldMatching);
+ }
+ } else {
+ throw new IllegalArgumentException("Field in struct is not SDField " + f.getName());
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("Could not find struct " + dataType.getName());
+ }
+ }
+ }
+ }
+
+ public void addOperation(FieldOperation op) {
+ pendingOperations.add(op);
+ }
+
+ public void applyOperations(SDField field) {
+ if (pendingOperations.isEmpty()) {
+ return;
+ }
+ ListIterator<FieldOperation> ops = pendingOperations.listIterator();
+ while (ops.hasNext()) {
+ FieldOperation op = ops.next();
+ ops.remove();
+ op.apply(field);
+ }
+ }
+
+ public void applyOperations() {
+ applyOperations(this);
+ }
+
+ public void setId(int fieldId, DocumentType owner) {
+ super.setId(fieldId, owner);
+ idOverride = true;
+ }
+
+ public StructDataType getFirstStructRecursive() {
+ DataType dataType = getDataType();
+ while (true) { // Currently no nesting of collections
+ if (dataType instanceof CollectionDataType) {
+ dataType = ((CollectionDataType)dataType).getNestedType();
+ } else if (dataType instanceof MapDataType) {
+ dataType = ((MapDataType)dataType).getValueType();
+ } else {
+ break;
+ }
+ }
+ return (dataType instanceof StructDataType) ? (StructDataType)dataType : null;
+ }
+
+ public DataType getFirstStructOrMapRecursive() {
+ DataType dataType = getDataType();
+ while (dataType instanceof CollectionDataType) { // Currently no nesting of collections
+ dataType = ((CollectionDataType)dataType).getNestedType();
+ }
+ return (dataType instanceof StructDataType || dataType instanceof MapDataType) ? dataType : null;
+ }
+
+ public boolean usesStruct() {
+ DataType dt = getFirstStructRecursive();
+ return (dt != null);
+ }
+
+ public boolean usesStructOrMap() {
+ DataType dt = getFirstStructOrMapRecursive();
+ return (dt != null);
+ }
+
+ /** Parse an indexing expression which will use the simple linguistics implementatino suitable for testing */
+ public void parseIndexingScript(String script) {
+ parseIndexingScript(script, new SimpleLinguistics());
+ }
+
+ public void parseIndexingScript(String script, Linguistics linguistics) {
+ try {
+ ScriptParserContext config = new ScriptParserContext(linguistics);
+ config.setInputStream(new IndexingInput(script));
+ setIndexingScript(ScriptExpression.newInstance(config));
+ } catch (ParseException e) {
+ throw new RuntimeException("Failed to parser script '" + script + "'.", e);
+ }
+ }
+
+ /** Sets the indexing script of this, or null to not use a script */
+ public void setIndexingScript(ScriptExpression exp) {
+ if (exp == null) {
+ exp = new ScriptExpression();
+ }
+ indexingScript = exp;
+ if (indexingScript.isEmpty()) {
+ return; // TODO: This causes empty expressions not to be propagate to struct fields!! BAD BAD BAD!!
+ }
+ if (!usesStructOrMap()) {
+ new ExpressionVisitor() {
+
+ @Override
+ protected void doVisit(Expression exp) {
+ if (!(exp instanceof AttributeExpression)) {
+ return;
+ }
+ String fieldName = ((AttributeExpression)exp).getFieldName();
+ if (fieldName == null) {
+ fieldName = getName();
+ }
+ Attribute attribute = attributes.get(fieldName);
+ if (attribute == null) {
+ addAttribute(new Attribute(fieldName, getDataType()));
+ }
+ }
+ }.visit(indexingScript);
+ }
+ for (SDField structField : getStructFields()) {
+ structField.setIndexingScript(exp);
+ }
+ }
+
+ public ScriptExpression getIndexingScript() {
+ return indexingScript;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void setDataType(DataType type) {
+ if (type.equals(DataType.URI)) { // Different defaults, naturally
+ normalizing.inferLowercase();
+ stemming=Stemming.NONE;
+ }
+ this.dataType = type;
+ if (!idOverride) {
+ this.fieldId = calculateIdV7(null);
+ }
+ }
+
+ public boolean isIndexStructureField() {
+ return indexStructureField;
+ }
+
+ public void setIndexStructureField(boolean indexStructureField) {
+ this.indexStructureField = indexStructureField;
+ }
+
+ /**
+ * Returns an iterator of the index names this should index to
+ * (whether set explicitly or not)
+ */
+ public Iterator<String> getFieldNameAsIterator() { // TODO: Replace usage by getName
+ return Collections.singletonList(getName()).iterator();
+ }
+
+ /** Returns 1 if this is indexed, 0 if it is not indexed */ // TODO: Replace by a boolean method, or something, see hasIndex
+ public int getIndexToCount() {
+ if (getIndexingScript() == null) return 0;
+ if (!doesIndexing()) return 0;
+
+ return 1;
+ }
+
+ /** Sets the literal boost of this field */
+ public void setLiteralBoost(int literalBoost) { this.literalBoost=literalBoost; }
+
+ /**
+ * Returns the literal boost of this field. This boost is added to a literal score
+ * when a query term matched as query term exactly (unnormalized and unstemmed).
+ * Default is non-positive.
+ */
+ public int getLiteralBoost() { return literalBoost; }
+
+ /** Sets the weight of this field */
+ public void setWeight(int weight) { this.weight=weight; }
+
+ /** Returns the weight of this field, or 0 if nothing is set */
+ public int getWeight() { return weight; }
+
+ /**
+ * Returns what kind of matching type should be applied.
+ */
+ public Matching getMatching() { return matching; }
+
+ /**
+ * Sets what kind of matching type should be applied.
+ * (Token matching is default, PREFIX, SUBSTRING, SUFFIX are alternatives)
+ */
+ public void setMatching(Matching matching) { this.matching=matching; }
+
+ /**
+ * Set the matching type for this field and all subfields.
+ */
+ // TODO: When this is not the same as getMatching().setthis we have a potential for inconsistency. Find the right
+ // Matching object for struct fields as lookup time instead.
+ public void setMatchingType(Matching.Type type) {
+ this.getMatching().setType(type);
+ for (SDField structField : getStructFields()) {
+ structField.setMatchingType(type);
+ }
+ }
+
+ /**
+ * Set matching algorithm for this field and all subfields.
+ */
+ // TODO: When this is not the same as getMatching().setthis we have a potential for inconsistency. Find the right
+ // Matching object for struct fields as lookup time instead.
+ public void setMatchingAlgorithm(Matching.Algorithm algorithm) {
+ this.getMatching().setAlgorithm(algorithm);
+ for (SDField structField : getStructFields()) {
+ structField.getMatching().setAlgorithm(algorithm);
+ }
+ }
+
+ /** Adds an explicit index defined in this field */
+ public void addIndex(Index index) {
+ indices.put(index.getName(),index);
+ }
+
+ /**
+ * Returns an index, or null if no index with this name has had
+ * some <b>explicit settings</b> applied in this field (even if this returns null,
+ * the index may be implicitly defined by an indexing statement)
+ */
+ public Index getIndex(String name) {
+ return indices.get(name);
+ }
+
+ /**
+ * Returns an index if this field has one (implicitly or
+ * explicitly) targeting the given name.
+ */
+ public boolean existsIndex(String name) {
+ if (indices.get(name) != null) return true;
+ if (name.equals(getName())) {
+ if (doesIndexing()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Defined indices on this field
+ * @return defined indices on this
+ */
+ public Map<String, Index> getIndices() {
+ return indices;
+ }
+
+ /**
+ * Sets the default rank type of this fields indices, and sets this rank type
+ * to all indices explicitly defined here which has no index set.
+ * (This complex behavior is dues to the fact than we would prefer to have rank types
+ * per field, not per index)
+ */
+ public void setRankType(RankType rankType) {
+ this.rankType=rankType;
+ for (Index index : getIndices().values()) {
+ if (index.getRankType()==null)
+ index.setRankType(rankType);
+ }
+
+ }
+
+ /** Returns the rank settings set in a "rank" block for this field. This is never null. */
+ public Ranking getRanking() { return ranking; }
+
+ /** Returns the default rank type of indices of this field, or null if nothing is set */
+ public RankType getRankType() { return this.rankType; }
+
+ /**
+ * <p>Returns the search-time attribute settings of this field
+ * or null if none is set.</p>
+ *
+ * <p>TODO: Make unmodifiable.</p>
+ */
+ public Map<String, Attribute> getAttributes() { return attributes; }
+
+ public void addAttribute(Attribute attribute) {
+ String name = attribute.getName();
+ if (name == null || "".equals(name)) {
+ name = getName();
+ attribute.setName(name);
+ }
+ attributes.put(attribute.getName(),attribute);
+ }
+
+ /**
+ * Returns the stemming setting of this field.
+ * Default is determined by the owning search definition.
+ *
+ * @return the stemming setting of this, or null, to use the default
+ */
+ public Stemming getStemming() { return stemming; }
+
+ /**
+ * Whether this field should be stemmed in this search definition
+ */
+ public Stemming getStemming(Search search) {
+ if (stemming!=null)
+ return stemming;
+ else
+ return search.getStemming();
+ }
+
+ /**
+ * Sets how this field should be stemmed, or set to null to use the default.
+ */
+ public void setStemming(Stemming stemming) {
+ this.stemming=stemming;
+ }
+
+ /**
+ * List of static summary fields
+ * @return list of static summary fields
+ */
+ public Collection<SummaryField> getSummaryFields() { return summaryFields.values(); }
+
+ /**
+ * Add summary field
+ */
+ public void addSummaryField(SummaryField summaryField) {
+ summaryFields.put(summaryField.getName(),summaryField);
+ }
+
+ /**
+ * Returns a summary field defined (implicitly or explicitly) by this field.
+ * Returns null if there is no such summary field defined.
+ */
+ public SummaryField getSummaryField(String name) {
+ return summaryFields.get(name);
+ }
+
+ /**
+ * Returns a summary field defined (implicitly or explicitly) by this field.
+ *
+ * @param create true to create the summary field and add it to this field before returning if it is missing
+ * @return the summary field, or null if not present and create is false
+ */
+ public SummaryField getSummaryField(String name,boolean create) {
+ SummaryField summaryField=summaryFields.get(name);
+ if (summaryField==null && create) {
+ summaryField=new SummaryField(name, getDataType());
+ addSummaryField(summaryField);
+ }
+ return summaryFields.get(name);
+ }
+
+ /** Returns list of static struct fields */
+ public Collection<SDField> getStructFields() { return structFields.values(); }
+
+ /**
+ * Returns a struct field defined in this field,
+ * potentially traversing into nested structs.
+ * Returns null if there is no such struct field defined.
+ */
+ public SDField getStructField(String name) {
+ if (name.contains(".")) {
+ String superFieldName = name.substring(0,name.indexOf("."));
+ String subFieldName = name.substring(name.indexOf(".")+1);
+ SDField superField = structFields.get(superFieldName);
+ if (superField != null) {
+ return superField.getStructField(subFieldName);
+ }
+ return null;
+ }
+ return structFields.get(name);
+ }
+
+ /**
+ * Returns how the content of this field should be accent normalized etc
+ */
+ public NormalizeLevel getNormalizing() { return normalizing; }
+
+ /**
+ * Change how the content of this field should be accent normalized etc
+ */
+ public void setNormalizing(NormalizeLevel level) { normalizing = level; }
+
+ public void addQueryCommand(String name) {
+ queryCommands.add(name);
+ }
+
+ public boolean hasQueryCommand(String name) {
+ return queryCommands.contains(name);
+ }
+
+ /**
+ * A list of query commands
+ * @return a list of strings with query commands.
+ */
+ public List<String> getQueryCommands() {
+ return queryCommands;
+ }
+
+
+ public boolean isHeaderOrBodyDefined() {
+ return headerOrBodyDefined;
+ }
+
+ public void setHeaderOrBodyDefined(boolean headerOrBodySetExplicitly) {
+ this.headerOrBodyDefined = headerOrBodySetExplicitly;
+ }
+
+ /**
+ * The document that this field was declared in, or null
+ *
+ */
+ public SDDocumentType getOwnerDocType() {
+ return ownerDocType;
+ }
+
+ /**
+ * Two fields are equal if they have the same name
+ * No, they are not.
+ */
+ public boolean equals(Object other) {
+ if ( ! (other instanceof SDField)) return false;
+ return super.equals(other);
+ }
+
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ public String toString() {
+ return "field '" + getName() + "'";
+ }
+
+ /** The aliases declared for this field */
+ public Map<String, String> getAliasToName() {
+ return aliasToName;
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/Sorting.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/Sorting.java
new file mode 100644
index 00000000000..a0511dec4b0
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/Sorting.java
@@ -0,0 +1,64 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import java.io.Serializable;
+
+/**
+ * A search-time document attribute sort specification(per-document in-memory value).
+ * This belongs to the attribute or field(implicitt attribute).
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public final class Sorting implements Cloneable, Serializable {
+
+ // Remember to change hashCode and equals when you add new fields
+ public enum Function {UCA, RAW, LOWERCASE}
+ public enum Strength {PRIMARY, SECONDARY, TERTIARY, QUATERNARY, IDENTICAL}
+ private boolean ascending = true;
+ private Function function = Function.UCA;
+ private String locale = "";
+ private Strength strength = Strength.PRIMARY;
+
+ public boolean isAscending() { return ascending; }
+ public boolean isDescending() { return ! ascending; }
+ public String getLocale() { return locale; }
+ public Function getFunction() { return function; }
+ public Strength getStrength() { return strength; }
+
+ public void setAscending() { ascending = true; }
+ public void setDescending() { ascending = false; }
+ public void setFunction(Function function) { this.function = function; }
+ public void setLocale(String locale) { this.locale = locale; }
+ public void setStrength(Strength strength) { this.strength = strength; }
+
+ public int hashCode() {
+ return locale.hashCode() +
+ strength.hashCode() +
+ function.hashCode() +
+ (isDescending() ? 13 : 0);
+ }
+
+ public boolean equals(Object object) {
+ if (! (object instanceof Sorting)) return false;
+
+ Sorting other=(Sorting)object;
+ return this.locale.equals(other.locale) &&
+ (ascending == other.ascending) &&
+ (function == other.function) &&
+ (strength == other.strength);
+ }
+
+ public @Override Sorting clone() {
+ try {
+ return (Sorting)super.clone();
+ }
+ catch (CloneNotSupportedException e) {
+ throw new RuntimeException("Programming error");
+ }
+ }
+
+ public String toString() {
+ return "sorting '" + (isAscending() ? '+' : '-') + function.toString() + "(" + strength.toString() + ", " + locale + ")";
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/Stemming.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/Stemming.java
new file mode 100644
index 00000000000..f471201f55e
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/Stemming.java
@@ -0,0 +1,72 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import com.yahoo.language.process.StemMode;
+
+import java.util.logging.Logger;
+
+/**
+ * <p>The stemming setting of a field. This describes how the search engine
+ * should transform content of this field into base forms (stems) to increase
+ * recall (find "car" when you search for "cars" etc.).</p>
+ *
+ * @author bratseth
+ */
+public enum Stemming {
+
+ /** No stemming */
+ NONE("none"),
+
+ /** Stem as much as possible */
+ ALL("all"),
+
+ /** select shortest possible stem */
+ SHORTEST("shortest"),
+
+ /** index (and query?) multiple stems */
+ MULTIPLE("multiple");
+
+ private static Logger log=Logger.getLogger(Stemming.class.getName());
+
+ private final String name;
+
+ /**
+ * Returns the stemming object for the given string.
+ * The legal stemming names are the stemming constants in any capitalization.
+ *
+ * @throws IllegalArgumentException if there is no stemming type with the given name
+ */
+ public static Stemming get(String stemmingName) {
+ try {
+ Stemming stemming = Stemming.valueOf(stemmingName.toUpperCase());
+ if (stemming.equals(ALL)) {
+ log.warning("note: stemming ALL is the same as stemming mode SHORTEST");
+ stemming = SHORTEST;
+ }
+ return stemming;
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("'" + stemmingName + "' is not a valid stemming setting");
+ }
+ }
+
+ private Stemming(String name) {
+ this.name = name;
+ }
+
+ public String getName() { return name; }
+
+ public String toString() {
+ return "stemming " + name;
+ }
+
+ public StemMode toStemMode() {
+ if (this == Stemming.SHORTEST) {
+ return StemMode.SHORTEST;
+ }
+ if (this == Stemming.MULTIPLE) {
+ return StemMode.ALL;
+ }
+ return StemMode.NONE;
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDDocumentType.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDDocumentType.java
new file mode 100644
index 00000000000..193ec75acd0
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDDocumentType.java
@@ -0,0 +1,13 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import com.yahoo.document.DataTypeName;
+
+/**
+ * @author balder
+ */
+public class TemporarySDDocumentType extends SDDocumentType {
+ public TemporarySDDocumentType(DataTypeName name) {
+ super(name);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDField.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDField.java
new file mode 100644
index 00000000000..00012d65434
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/TemporarySDField.java
@@ -0,0 +1,19 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import com.yahoo.document.DataType;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class TemporarySDField extends SDField {
+
+ public TemporarySDField(String name, DataType dataType, boolean isHeader, SDDocumentType owner) {
+ super(owner, name, dataType, isHeader, owner, false);
+ }
+
+ public TemporarySDField(String name, DataType dataType) {
+ super(null, name, dataType, true, false);
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/TypedKey.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/TypedKey.java
new file mode 100644
index 00000000000..6a10ebe642d
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/TypedKey.java
@@ -0,0 +1,20 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document;
+
+import com.yahoo.document.DataType;
+
+/**
+ * Common interface for various typed key (or field definitions).
+ * Used by code which wants to use common algorithms for dealing with typed keys, like the logical mapping
+ *
+ * @author bratseth
+ */
+public interface TypedKey {
+
+ String getName();
+
+ void setDataType(DataType type);
+
+ DataType getDataType();
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/annotation/SDAnnotationType.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/annotation/SDAnnotationType.java
new file mode 100644
index 00000000000..0f8e200c4c6
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/annotation/SDAnnotationType.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document.annotation;
+
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.document.annotation.AnnotationType;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class SDAnnotationType extends AnnotationType {
+ private SDDocumentType sdDocType;
+ private String inherits;
+
+ public SDAnnotationType(String name) {
+ super(name);
+ }
+
+ public SDAnnotationType(String name, SDDocumentType dataType, String inherits) {
+ super(name);
+ this.sdDocType = dataType;
+ this.inherits = inherits;
+ }
+
+ public SDDocumentType getSdDocType() {
+ return sdDocType;
+ }
+
+ public String getInherits() {
+ return inherits;
+ }
+
+ public void inherit(String inherits) {
+ this.inherits = inherits;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/annotation/TemporaryAnnotationReferenceDataType.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/annotation/TemporaryAnnotationReferenceDataType.java
new file mode 100644
index 00000000000..5b82b068908
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/annotation/TemporaryAnnotationReferenceDataType.java
@@ -0,0 +1,26 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.document.annotation;
+
+import com.yahoo.document.annotation.AnnotationReferenceDataType;
+import com.yahoo.document.annotation.AnnotationType;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class TemporaryAnnotationReferenceDataType extends AnnotationReferenceDataType {
+ private final String target;
+
+ public TemporaryAnnotationReferenceDataType(String target) {
+ this.target = target;
+ }
+
+ public String getTarget() {
+ return target;
+ }
+
+ @Override
+ public void setAnnotationType(AnnotationType type) {
+ super.setName("annotationreference<" + type.getName() + ">");
+ super.setAnnotationType(type);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/AliasOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/AliasOperation.java
new file mode 100644
index 00000000000..60aeb3d50cc
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/AliasOperation.java
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class AliasOperation implements FieldOperation {
+ private String aliasedName;
+ private String alias;
+
+ public AliasOperation(String aliasedName, String alias) {
+ this.aliasedName = aliasedName;
+ this.alias = alias;
+ }
+
+ public String getAliasedName() {
+ return aliasedName;
+ }
+
+ public void setAliasedName(String aliasedName) {
+ this.aliasedName = aliasedName;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+
+ public void apply(SDField field) {
+ if (aliasedName == null) {
+ aliasedName = field.getName();
+ }
+ field.getAliasToName().put(alias, aliasedName);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/AttributeOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/AttributeOperation.java
new file mode 100644
index 00000000000..19ee857c627
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/AttributeOperation.java
@@ -0,0 +1,153 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.tensor.TensorType;
+
+import java.util.Optional;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class AttributeOperation implements FieldOperation, FieldOperationContainer {
+ private final String name;
+ private Boolean huge;
+ private Boolean fastSearch;
+ private Boolean fastAccess;
+ private Boolean prefetch;
+ private Boolean enableBitVectors;
+ private Boolean enableOnlyBitVector;
+ //TODO: Husk sorting!!
+ private boolean doAlias = false;
+ private String alias;
+ private String aliasedName;
+ private Optional<TensorType> tensorType = Optional.empty();
+
+ public AttributeOperation(String name) {
+ this.name = name;
+ }
+
+ public void addOperation(FieldOperation op) {
+ //TODO: Implement this method:
+
+ }
+
+ public void applyOperations(SDField field) {
+ //TODO: Implement this method:
+
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Boolean getHuge() {
+ return huge;
+ }
+
+ public void setHuge(Boolean huge) {
+ this.huge = huge;
+ }
+
+ public Boolean getFastSearch() {
+ return fastSearch;
+ }
+
+ public void setFastSearch(Boolean fastSearch) {
+ this.fastSearch = fastSearch;
+ }
+
+ public Boolean getFastAccess() {
+ return fastAccess;
+ }
+
+ public void setFastAccess(Boolean fastAccess) {
+ this.fastAccess = fastAccess;
+ }
+
+ public Boolean getPrefetch() {
+ return prefetch;
+ }
+
+ public void setPrefetch(Boolean prefetch) {
+ this.prefetch = prefetch;
+ }
+
+ public Boolean getEnableBitVectors() {
+ return enableBitVectors;
+ }
+
+ public void setEnableBitVectors(Boolean enableBitVectors) {
+ this.enableBitVectors = enableBitVectors;
+ }
+
+ public Boolean getEnableOnlyBitVector() {
+ return enableOnlyBitVector;
+ }
+
+ public void setEnableOnlyBitVector(Boolean enableOnlyBitVector) {
+ this.enableOnlyBitVector = enableOnlyBitVector;
+ }
+
+ public boolean isDoAlias() {
+ return doAlias;
+ }
+
+ public void setDoAlias(boolean doAlias) {
+ this.doAlias = doAlias;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+
+ public String getAliasedName() {
+ return aliasedName;
+ }
+
+ public void setAliasedName(String aliasedName) {
+ this.aliasedName = aliasedName;
+ }
+
+ public void setTensorType(TensorType tensorType) {
+ this.tensorType = Optional.of(tensorType);
+ }
+
+ public void apply(SDField field) {
+ Attribute attribute = field.getAttributes().get(name);
+ if (attribute == null) {
+ attribute = new Attribute(name, field.getDataType());
+ field.addAttribute(attribute);
+ }
+
+ if (huge != null) {
+ attribute.setHuge(huge);
+ }
+ if (fastSearch != null) {
+ attribute.setFastSearch(fastSearch);
+ }
+ if (fastAccess != null) {
+ attribute.setFastAccess(fastAccess);
+ }
+ if (prefetch != null) {
+ attribute.setPrefetch(prefetch);
+ }
+ if (enableBitVectors != null) {
+ attribute.setEnableBitVectors(enableBitVectors);
+ }
+ if (enableOnlyBitVector != null) {
+ attribute.setEnableOnlyBitVector(enableOnlyBitVector);
+ }
+ if (doAlias) {
+ field.getAliasToName().put(alias, aliasedName);
+ }
+ if (tensorType.isPresent()) {
+ attribute.setTensorType(tensorType.get());
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/BodyOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/BodyOperation.java
new file mode 100644
index 00000000000..b830407ea2e
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/BodyOperation.java
@@ -0,0 +1,14 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class BodyOperation implements FieldOperation {
+ public void apply(SDField field) {
+ field.setHeader(false);
+ field.setHeaderOrBodyDefined(true);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/BoldingOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/BoldingOperation.java
new file mode 100644
index 00000000000..f6c7277869e
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/BoldingOperation.java
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.documentmodel.SummaryField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class BoldingOperation implements FieldOperation {
+
+ private final boolean bold;
+
+ public BoldingOperation(boolean bold) {
+ this.bold = bold;
+ }
+
+ public void apply(SDField field) {
+ SummaryField summaryField = field.getSummaryField(field.getName(), true);
+ summaryField.addSource(field.getName());
+ summaryField.addDestination("default");
+ summaryField.setTransform(bold ? summaryField.getTransform().bold() : summaryField.getTransform().unbold());
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperation.java
new file mode 100644
index 00000000000..20caf0b13c7
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperation.java
@@ -0,0 +1,11 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public interface FieldOperation {
+ public void apply(SDField field);
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperationContainer.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperationContainer.java
new file mode 100644
index 00000000000..30d16e369b1
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/FieldOperationContainer.java
@@ -0,0 +1,13 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public interface FieldOperationContainer {
+ public void addOperation(FieldOperation op);
+ public void applyOperations(SDField field);
+ public String getName();
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/HeaderOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/HeaderOperation.java
new file mode 100644
index 00000000000..48d2711b39e
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/HeaderOperation.java
@@ -0,0 +1,14 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class HeaderOperation implements FieldOperation {
+ public void apply(SDField field) {
+ field.setHeader(true);
+ field.setHeaderOrBodyDefined(true);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IdOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IdOperation.java
new file mode 100644
index 00000000000..25318e2db85
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IdOperation.java
@@ -0,0 +1,33 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class IdOperation implements FieldOperation {
+ private SDDocumentType document;
+ private int fieldId;
+
+ public SDDocumentType getDocument() {
+ return document;
+ }
+
+ public void setDocument(SDDocumentType document) {
+ this.document = document;
+ }
+
+ public int getFieldId() {
+ return fieldId;
+ }
+
+ public void setFieldId(int fieldId) {
+ this.fieldId = fieldId;
+ }
+
+ public void apply(SDField field) {
+ document.setFieldId(field, fieldId);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java
new file mode 100644
index 00000000000..8ab065ce419
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java
@@ -0,0 +1,114 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.Index;
+import com.yahoo.searchdefinition.Index.Type;
+import com.yahoo.searchdefinition.document.BooleanIndexDefinition;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Stemming;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import java.util.OptionalDouble;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class IndexOperation implements FieldOperation {
+ private String indexName;
+ private Optional<Boolean> prefix = Optional.empty();
+ private List<String> aliases = new LinkedList<>();
+ private Optional<String> stemming = Optional.empty();
+ private Optional<Type> type = Optional.empty();
+
+ private OptionalInt arity = OptionalInt.empty(); // For predicate data type in boolean search
+ private OptionalLong lowerBound = OptionalLong.empty();
+ private OptionalLong upperBound = OptionalLong.empty();
+ private OptionalDouble densePostingListThreshold = OptionalDouble.empty();
+
+ public String getIndexName() {
+ return indexName;
+ }
+
+ public void setIndexName(String indexName) {
+ this.indexName = indexName;
+ }
+
+ public boolean getPrefix() {
+ return prefix.get();
+ }
+
+ public void setPrefix(Boolean prefix) {
+ this.prefix = Optional.of(prefix);
+ }
+
+ public void addAlias(String alias) {
+ aliases.add(alias);
+ }
+
+ public String getStemming() {
+ return stemming.get();
+ }
+
+ public void setStemming(String stemming) {
+ this.stemming = Optional.of(stemming);
+ }
+
+ public void apply(SDField field) {
+ Index index = field.getIndex(indexName);
+
+ if (index == null) {
+ index = new Index(indexName);
+ field.addIndex(index);
+ }
+
+ applyToIndex(index);
+ }
+
+ public void applyToIndex(Index index) {
+ if (prefix.isPresent()) {
+ index.setPrefix(prefix.get());
+ }
+ for (String alias : aliases) {
+ index.addAlias(alias);
+ }
+ if (stemming.isPresent()) {
+ index.setStemming(Stemming.get(stemming.get()));
+ }
+ if (type.isPresent()) {
+ index.setType(type.get());
+ }
+ if (arity.isPresent() || lowerBound.isPresent() ||
+ upperBound.isPresent() || densePostingListThreshold.isPresent()) {
+ index.setBooleanIndexDefiniton(
+ new BooleanIndexDefinition(arity, lowerBound, upperBound, densePostingListThreshold));
+ }
+ }
+
+ public Type getType() {
+ return type.get();
+ }
+
+ public void setType(Type type) {
+ this.type = Optional.of(type);
+ }
+
+ public void setArity(int arity) {
+ this.arity = OptionalInt.of(arity);
+ }
+
+ public void setLowerBound(long value) {
+ this.lowerBound = OptionalLong.of(value);
+ }
+
+ public void setUpperBound(long value) {
+ this.upperBound = OptionalLong.of(value);
+ }
+
+ public void setDensePostingListThreshold(double densePostingListThreshold) {
+ this.densePostingListThreshold = OptionalDouble.of(densePostingListThreshold);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingOperation.java
new file mode 100644
index 00000000000..af3852bffb8
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingOperation.java
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.language.Linguistics;
+import com.yahoo.language.simple.SimpleLinguistics;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.parser.ParseException;
+import com.yahoo.searchdefinition.parser.SimpleCharStream;
+import com.yahoo.vespa.indexinglanguage.ScriptParserContext;
+import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.StatementExpression;
+import com.yahoo.vespa.indexinglanguage.linguistics.AnnotatorConfig;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class IndexingOperation implements FieldOperation {
+
+ private final ScriptExpression script;
+
+ public IndexingOperation(ScriptExpression script) {
+ this.script = script;
+ }
+
+ public void apply(SDField field) {
+ field.setIndexingScript(script);
+ }
+
+ /** Creates an indexing operation which will use the simple linguistics implementation suitable for testing */
+ public static IndexingOperation fromStream(SimpleCharStream input, boolean multiLine) throws ParseException {
+ return fromStream(input, multiLine, new SimpleLinguistics());
+ }
+
+ public static IndexingOperation fromStream(SimpleCharStream input, boolean multiLine, Linguistics linguistics)
+ throws ParseException {
+ ScriptParserContext config = new ScriptParserContext(linguistics);
+ config.setAnnotatorConfig(new AnnotatorConfig());
+ config.setInputStream(input);
+ ScriptExpression exp;
+ try {
+ if (multiLine) {
+ exp = ScriptExpression.newInstance(config);
+ } else {
+ exp = new ScriptExpression(StatementExpression.newInstance(config));
+ }
+ } catch (com.yahoo.vespa.indexinglanguage.parser.ParseException e) {
+ ParseException t = new ParseException("Error reported by IL parser: " + e.getMessage());
+ t.initCause(e);
+ throw t;
+ }
+ return new IndexingOperation(exp);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingRewriteOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingRewriteOperation.java
new file mode 100644
index 00000000000..a416895a6a7
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexingRewriteOperation.java
@@ -0,0 +1,12 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class IndexingRewriteOperation implements FieldOperation {
+ public void apply(SDField field) {
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/MatchOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/MatchOperation.java
new file mode 100644
index 00000000000..9128778b7ea
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/MatchOperation.java
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.Matching;
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class MatchOperation implements FieldOperation {
+ private Matching.Type matchingType;
+ private Integer gramSize;
+ private Integer maxLength;
+ private Matching.Algorithm matchingAlgorithm;
+ private String exactMatchTerminator;
+
+ public void setMatchingType(Matching.Type matchingType) {
+ this.matchingType = matchingType;
+ }
+
+ public void setGramSize(Integer gramSize) {
+ this.gramSize = gramSize;
+ }
+ public void setMaxLength(Integer maxLength) {
+ this.maxLength = maxLength;
+ }
+
+ public void setMatchingAlgorithm(Matching.Algorithm matchingAlgorithm) {
+ this.matchingAlgorithm = matchingAlgorithm;
+ }
+
+ public void setExactMatchTerminator(String exactMatchTerminator) {
+ this.exactMatchTerminator = exactMatchTerminator;
+ }
+
+ public void apply(SDField field) {
+ if (matchingType != null) {
+ field.setMatchingType(matchingType);
+ }
+ if (gramSize != null) {
+ field.getMatching().setGramSize(gramSize);
+ }
+ if (maxLength != null) {
+ field.getMatching().maxLength(maxLength);
+ }
+ if (matchingAlgorithm != null) {
+ field.setMatchingAlgorithm(matchingAlgorithm);
+ }
+ if (exactMatchTerminator != null) {
+ field.getMatching().setExactMatchTerminator(exactMatchTerminator);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/NormalizingOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/NormalizingOperation.java
new file mode 100644
index 00000000000..e0d15cabbe0
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/NormalizingOperation.java
@@ -0,0 +1,32 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.NormalizeLevel;
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class NormalizingOperation implements FieldOperation {
+ private NormalizeLevel.Level level;
+
+ public NormalizingOperation(String setting) {
+ if ("none".equals(setting)) {
+ this.level = NormalizeLevel.Level.NONE;
+ } else if ("codepoint".equals(setting)) {
+ this.level = NormalizeLevel.Level.CODEPOINT;
+ } else if ("lowercase".equals(setting)) {
+ this.level = NormalizeLevel.Level.LOWERCASE;
+ } else if ("accent".equals(setting)) {
+ this.level = NormalizeLevel.Level.ACCENT;
+ } else if ("all".equals(setting)) {
+ this.level = NormalizeLevel.Level.ACCENT;
+ } else {
+ throw new IllegalArgumentException("invalid normalizing setting: "+setting);
+ }
+ }
+
+ public void apply(SDField field) {
+ field.setNormalizing(new NormalizeLevel(level, true));
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/QueryCommandOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/QueryCommandOperation.java
new file mode 100644
index 00000000000..e4af5660e7b
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/QueryCommandOperation.java
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class QueryCommandOperation implements FieldOperation {
+ private List<String> queryCommands = new java.util.ArrayList<>(0);
+
+ public void addQueryCommand(String name) {
+ queryCommands.add(name);
+ }
+
+ public void apply(SDField field) {
+ for (String command : queryCommands) {
+ field.addQueryCommand(command);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/RankOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/RankOperation.java
new file mode 100644
index 00000000000..42b1e5514a1
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/RankOperation.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class RankOperation implements FieldOperation {
+
+ private Boolean literal = null;
+ private Boolean filter = null;
+ private Boolean normal = null;
+
+ public Boolean getLiteral() { return literal; }
+ public void setLiteral(Boolean literal) { this.literal = literal; }
+
+ public Boolean getFilter() { return filter; }
+ public void setFilter(Boolean filter) { this.filter = filter; }
+
+ public Boolean getNormal() { return normal; }
+ public void setNormal(Boolean n) { this.normal = n; }
+
+ public void apply(SDField field) {
+ if (literal != null) {
+ field.getRanking().setLiteral(literal);
+ }
+ if (filter != null) {
+ field.getRanking().setFilter(filter);
+ }
+ if (normal != null) {
+ field.getRanking().setNormal(normal);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/RankTypeOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/RankTypeOperation.java
new file mode 100644
index 00000000000..640ccc48818
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/RankTypeOperation.java
@@ -0,0 +1,41 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.RankType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Index;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class RankTypeOperation implements FieldOperation {
+ private String indexName;
+ private RankType type;
+
+ public String getIndexName() {
+ return indexName;
+ }
+ public void setIndexName(String indexName) {
+ this.indexName = indexName;
+ }
+
+ public RankType getType() {
+ return type;
+ }
+ public void setType(RankType type) {
+ this.type = type;
+ }
+
+ public void apply(SDField field) {
+ if (indexName == null) {
+ field.setRankType(type); // Set default if the index is not specified.
+ } else {
+ Index index = field.getIndex(indexName);
+ if (index == null) {
+ index = new Index(indexName);
+ field.addIndex(index);
+ }
+ index.setRankType(type);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SortingOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SortingOperation.java
new file mode 100644
index 00000000000..e9225f0def5
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SortingOperation.java
@@ -0,0 +1,91 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Sorting;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class SortingOperation implements FieldOperation {
+ private String attributeName;
+ private Boolean ascending;
+ private Boolean descending;
+ private Sorting.Function function;
+ private Sorting.Strength strength;
+ private String locale;
+
+ public SortingOperation(String attributeName) {
+ this.attributeName = attributeName;
+ }
+
+ public String getAttributeName() {
+ return attributeName;
+ }
+
+ public Boolean getAscending() {
+ return ascending;
+ }
+
+ public void setAscending() {
+ this.ascending = true;
+ }
+
+ public Boolean getDescending() {
+ return descending;
+ }
+
+ public void setDescending() {
+ this.descending = true;
+ }
+
+ public Sorting.Function getFunction() {
+ return function;
+ }
+
+ public void setFunction(Sorting.Function function) {
+ this.function = function;
+ }
+
+ public Sorting.Strength getStrength() {
+ return strength;
+ }
+
+ public void setStrength(Sorting.Strength strength) {
+ this.strength = strength;
+ }
+
+ public String getLocale() {
+ return locale;
+ }
+
+ public void setLocale(String locale) {
+ this.locale = locale;
+ }
+
+ public void apply(SDField field) {
+ Attribute attribute = field.getAttributes().get(attributeName);
+ if (attribute == null) {
+ attribute = new Attribute(attributeName, field.getDataType());
+ field.addAttribute(attribute);
+ }
+ Sorting sorting = attribute.getSorting();
+
+ if (ascending != null) {
+ sorting.setAscending();
+ }
+ if (descending != null) {
+ sorting.setDescending();
+ }
+ if (function != null) {
+ sorting.setFunction(function);
+ }
+ if (strength != null) {
+ sorting.setStrength(strength);
+ }
+ if (locale != null) {
+ sorting.setLocale(locale);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/StemmingOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/StemmingOperation.java
new file mode 100644
index 00000000000..19b760386a1
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/StemmingOperation.java
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Stemming;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class StemmingOperation implements FieldOperation {
+ private String setting;
+
+ public String getSetting() {
+ return setting;
+ }
+
+ public void setSetting(String setting) {
+ this.setting = setting;
+ }
+
+ public void apply(SDField field) {
+ field.setStemming(Stemming.get(setting));
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/StructFieldOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/StructFieldOperation.java
new file mode 100644
index 00000000000..f0c3d964934
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/StructFieldOperation.java
@@ -0,0 +1,50 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class StructFieldOperation implements FieldOperation, FieldOperationContainer {
+ private String structFieldName;
+ private List<FieldOperation> pendingOperations = new LinkedList<>();
+
+ public StructFieldOperation(String structFieldName) {
+ this.structFieldName = structFieldName;
+ }
+
+ public void apply(SDField field) {
+ SDField structField = field.getStructField(structFieldName);
+ if (structField == null ) {
+ throw new IllegalArgumentException("Struct field '" + structFieldName + "' has not been defined in struct " +
+ "for field '" + field.getName() + "'.");
+ }
+
+ applyOperations(structField);
+ }
+
+ public void addOperation(FieldOperation op) {
+ pendingOperations.add(op);
+ }
+
+ public void applyOperations(SDField field) {
+ if (pendingOperations.isEmpty()) {
+ return;
+ }
+ ListIterator<FieldOperation> ops = pendingOperations.listIterator();
+ while (ops.hasNext()) {
+ FieldOperation op = ops.next();
+ ops.remove();
+ op.apply(field);
+ }
+ }
+
+ public String getName() {
+ return structFieldName;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldLongOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldLongOperation.java
new file mode 100644
index 00000000000..f320ef649b4
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldLongOperation.java
@@ -0,0 +1,81 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.document.DataType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.documentmodel.SummaryField;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class SummaryInFieldLongOperation extends SummaryInFieldOperation {
+ private DataType type;
+ private Boolean bold;
+ private Set<String> destinations = new java.util.LinkedHashSet<>();
+ private List<SummaryField.Property> properties = new ArrayList<>();
+
+ public SummaryInFieldLongOperation(String name) {
+ super(name);
+ }
+
+ public SummaryInFieldLongOperation() {
+ super(null);
+ }
+
+ public void setType(DataType type) {
+ this.type = type;
+ }
+
+ public void setBold(Boolean bold) {
+ this.bold = bold;
+ }
+
+ public void addDestination(String destination) {
+ destinations.add(destination);
+ }
+
+ public Iterator<String> destinationIterator() {
+ return destinations.iterator();
+ }
+
+
+ public void addProperty(SummaryField.Property property) {
+ properties.add(property);
+ }
+
+ public void apply(SDField field) {
+ if (type == null) {
+ type = field.getDataType();
+ }
+ SummaryField summary = new SummaryField(name, type);
+ applyToSummary(summary);
+ field.addSummaryField(summary);
+ }
+
+ public void applyToSummary(SummaryField summary) {
+ if (transform != null) {
+ summary.setTransform(transform);
+ }
+
+ if (bold != null) {
+ summary.setTransform(bold ? summary.getTransform().bold() : summary.getTransform().unbold());
+ }
+
+ for (SummaryField.Source source : sources) {
+ summary.addSource(source);
+ }
+
+ for (String destination : destinations) {
+ summary.addDestination(destination);
+ }
+
+ for (SummaryField.Property prop : properties) {
+ summary.addProperty(prop.getName(), prop.getValue());
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldOperation.java
new file mode 100644
index 00000000000..e1abbcc93b5
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldOperation.java
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public abstract class SummaryInFieldOperation implements FieldOperation {
+ protected String name;
+ protected SummaryTransform transform;
+ protected Set<SummaryField.Source> sources = new java.util.LinkedHashSet<>();
+
+ public SummaryInFieldOperation(String name) {
+ this.name = name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setTransform(SummaryTransform transform) {
+ this.transform = transform;
+ }
+
+ public SummaryTransform getTransform() {
+ return transform;
+ }
+
+ public void addSource(String name) {
+ sources.add(new SummaryField.Source(name));
+ }
+
+ public void addSource(SummaryField.Source source) {
+ sources.add(source);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldShortOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldShortOperation.java
new file mode 100644
index 00000000000..1ffb8d9a83e
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryInFieldShortOperation.java
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.documentmodel.SummaryField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class SummaryInFieldShortOperation extends SummaryInFieldOperation {
+ public SummaryInFieldShortOperation(String name) {
+ super(name);
+ }
+
+ public void apply(SDField field) {
+ SummaryField ret = field.getSummaryField(name);
+ if (ret == null) {
+ ret = new SummaryField(name, field.getDataType());
+ ret.addSource(field.getName());
+ ret.addDestination("default");
+ }
+ ret.setImplicit(false);
+
+ ret.setTransform(transform);
+ for (SummaryField.Source source : sources) {
+ ret.addSource(source);
+ }
+ field.addSummaryField(ret);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryToOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryToOperation.java
new file mode 100644
index 00000000000..f392b2fdcc8
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/SummaryToOperation.java
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.documentmodel.SummaryField;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class SummaryToOperation implements FieldOperation {
+ private Set<String> destinations = new java.util.LinkedHashSet<>();
+ private String name;
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void addDestination(String destination) {
+ destinations.add(destination);
+ }
+
+ public void apply(SDField field) {
+ SummaryField summary;
+ summary = field.getSummaryField(name);
+ if (summary == null) {
+ summary = new SummaryField(field);
+ summary.addSource(field.getName());
+ summary.addDestination("default");
+ field.addSummaryField(summary);
+ }
+ summary.setImplicit(false);
+
+ for (String destination : destinations) {
+ summary.addDestination(destination);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/WeightOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/WeightOperation.java
new file mode 100644
index 00000000000..3cbd557b850
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/WeightOperation.java
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.SDField;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class WeightOperation implements FieldOperation {
+ private int weight;
+
+ public int getWeight() {
+ return weight;
+ }
+
+ public void setWeight(int weight) {
+ this.weight = weight;
+ }
+
+ public void apply(SDField field) {
+ field.setWeight(weight);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/WeightedSetOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/WeightedSetOperation.java
new file mode 100644
index 00000000000..c1721a4dbb5
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/WeightedSetOperation.java
@@ -0,0 +1,63 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.fieldoperation;
+
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.document.DataType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.document.WeightedSetDataType;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public class WeightedSetOperation implements FieldOperation {
+
+ private Boolean createIfNonExistent;
+ private Boolean removeIfZero;
+
+ public Boolean getCreateIfNonExistent() {
+ return createIfNonExistent;
+ }
+
+ public void setCreateIfNonExistent(Boolean createIfNonExistent) {
+ this.createIfNonExistent = createIfNonExistent;
+ }
+
+ public Boolean getRemoveIfZero() {
+ return removeIfZero;
+ }
+
+ public void setRemoveIfZero(Boolean removeIfZero) {
+ this.removeIfZero = removeIfZero;
+ }
+
+ public void apply(SDField field) {
+ WeightedSetDataType ctype = (WeightedSetDataType) field.getDataType();
+
+ if (createIfNonExistent != null) {
+ field.setDataType(DataType.getWeightedSet(ctype.getNestedType(), createIfNonExistent,
+ ctype.removeIfZero()));
+ }
+
+ ctype = (WeightedSetDataType) field.getDataType();
+ if (removeIfZero != null) {
+ field.setDataType(DataType.getWeightedSet(ctype.getNestedType(),
+ ctype.createIfNonExistent(), removeIfZero));
+ }
+
+ ctype = (WeightedSetDataType) field.getDataType();
+ for (Object o : field.getAttributes().values()) {
+ Attribute attribute = (Attribute) o;
+ attribute.setRemoveIfZero(ctype.removeIfZero());
+ attribute.setCreateIfNonExistent(ctype.createIfNonExistent());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "WeightedSetOperation{" +
+ "createIfNonExistent=" + createIfNonExistent +
+ ", removeIfZero=" + removeIfZero +
+ '}';
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/parser/SimpleCharStream.java b/config-model/src/main/java/com/yahoo/searchdefinition/parser/SimpleCharStream.java
new file mode 100644
index 00000000000..b925b4f2caa
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/parser/SimpleCharStream.java
@@ -0,0 +1,16 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.parser;
+
+import com.yahoo.javacc.FastCharStream;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+@SuppressWarnings("deprecation")
+public class SimpleCharStream extends FastCharStream implements com.yahoo.searchdefinition.parser.CharStream,
+ com.yahoo.vespa.indexinglanguage.parser.CharStream
+{
+ public SimpleCharStream(String input) {
+ super(input);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddExtraFieldsToDocument.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddExtraFieldsToDocument.java
new file mode 100644
index 00000000000..5d39c70e3b9
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AddExtraFieldsToDocument.java
@@ -0,0 +1,78 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.DataType;
+import com.yahoo.document.Field;
+import com.yahoo.document.PositionDataType;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * This processor creates a {@link com.yahoo.searchdefinition.document.SDDocumentType} for each {@link Search} object which holds all the data that search
+ * associates with a document described in a search definition file. This includes all extra fields, summary fields and
+ * implicit fields. All non-indexed and non-summary fields are discarded.
+ */
+public class AddExtraFieldsToDocument extends Processor {
+
+ public AddExtraFieldsToDocument(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ SDDocumentType document = search.getDocument();
+ if (document != null) {
+ for (Field field : search.extraFieldList()) {
+ addSdField(search, document, (SDField)field);
+ }
+ for (SummaryField field : search.getSummary("default").getSummaryFields()) {
+ addSummaryField(search, document, field);
+ }
+ }
+ }
+
+ private void addSdField(Search search, SDDocumentType document, SDField field) {
+ if (field.getIndexToCount() == 0 && field.getAttributes().isEmpty()) {
+ return;
+ }
+ for (Attribute atr : field.getAttributes().values()) {
+ if (atr.getName().equals(field.getName() + "_position")) {
+ DataType type = PositionDataType.INSTANCE;
+ if (atr.getCollectionType().equals(Attribute.CollectionType.ARRAY)) {
+ type = DataType.getArray(type);
+ }
+ addField(search, document, new SDField(document, atr.getName(), type));
+ } else if (!atr.getName().equals(field.getName())) {
+ addField(search, document, new SDField(document, atr.getName(), atr.getDataType()));
+ }
+ }
+ addField(search, document, field);
+ }
+
+ private void addSummaryField(Search search, SDDocumentType document, SummaryField field) {
+ Field docField = document.getField(field.getName());
+ if (docField == null) {
+ SDField newField = search.getField(field.getName());
+ if (newField == null) {
+ newField = new SDField(document, field.getName(), field.getDataType(), field.isHeader(), true);
+ newField.setIsExtraField(true);
+ }
+ document.addField(newField);
+ } else if (!docField.getDataType().equals(field.getDataType())) {
+ throw newProcessException(search, field, "Summary field has conflicting type.");
+ }
+ }
+
+ private void addField(Search search, SDDocumentType document, Field field) {
+ if (document.getField(field.getName()) != null) {
+ throw newProcessException(search, field, "Field shadows another.");
+ }
+ document.addField(field);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AttributeProperties.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AttributeProperties.java
new file mode 100644
index 00000000000..9df77c09373
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AttributeProperties.java
@@ -0,0 +1,70 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Checks that attribute properties only are set for attributes that have data (are created by an indexing statement).
+ *
+ * @author <a href="musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class AttributeProperties extends Processor {
+
+ public AttributeProperties(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ String fieldName = field.getName();
+
+ // For each attribute, check if the attribute has been created
+ // by an indexing statement.
+ for (Attribute attribute : field.getAttributes().values()) {
+ if (attributeCreated(field, attribute.getName())) {
+ continue;
+ }
+ // Check other fields or statements that may have created this attribute.
+ boolean created = false;
+ for (SDField f : search.allFieldsList()) {
+ // Checking against the field we are looking at
+ if (!f.getName().equals(fieldName)) {
+ if (attributeCreated(f, attribute.getName())) {
+ created = true;
+ break;
+ }
+ }
+ }
+ if (!created) {
+ throw new IllegalArgumentException("Attribute '" + attribute.getName() + "' in field '" +
+ field.getName() + "' is not created by the indexing statement");
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the attribute has been created bye an indexing statement in this field.
+ *
+ * @param field a searchdefinition field
+ * @param attributeName name of the attribute
+ * @return true if the attribute has been created by this field, else false
+ */
+ static boolean attributeCreated(SDField field, String attributeName) {
+ if (!field.doesAttributing()) {
+ return false;
+ }
+ for (Attribute attribute : field.getAttributes().values()) {
+ if (attribute.getName().equals(attributeName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/AttributesImplicitWord.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AttributesImplicitWord.java
new file mode 100644
index 00000000000..774160793cb
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/AttributesImplicitWord.java
@@ -0,0 +1,47 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.DataType;
+import com.yahoo.searchdefinition.document.Matching;
+import com.yahoo.document.NumericDataType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Fields that derive to attribute(s) and no indices should use the WORD indexing form,
+ * in a feeble attempt to match the most peoples expectations as closely as possible.
+ *
+ * @author vegardh
+ *
+ */
+public class AttributesImplicitWord extends Processor {
+
+ public AttributesImplicitWord(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (fieldImplicitlyWordMatch(field)) {
+ field.getMatching().setType(Matching.Type.WORD);
+ }
+ }
+ }
+
+ private boolean fieldImplicitlyWordMatch(SDField field) {
+ // numeric types should not trigger exact-match query parsing
+ DataType dt = field.getDataType().getPrimitiveType();
+ if (dt != null && dt instanceof NumericDataType) {
+ return false;
+ }
+ return (field.getIndexToCount() == 0
+ && field.getAttributes().size() > 0
+ && field.getIndices().isEmpty()
+ && !field.getMatching().isTypeUserSet());
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/Bolding.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/Bolding.java
new file mode 100644
index 00000000000..2e2e665eb34
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/Bolding.java
@@ -0,0 +1,46 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.DataType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Checks that bolding or dynamic summary is turned on only for text fields. Throws exception if it is turned on for any
+ * other fields (otherwise will cause indexing failure)
+ *
+ * @author <a href="musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class Bolding extends Processor {
+
+ public Bolding(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ for (SummaryField summary : field.getSummaryFields()) {
+ if (summary.getTransform().isBolded() &&
+ !((summary.getDataType() == DataType.STRING) || (summary.getDataType() == DataType.URI)))
+ {
+ throw new IllegalArgumentException("'bolding: on' for non-text field " +
+ "'" + field.getName() + "'" +
+ " (" + summary.getDataType() + ")" +
+ " is not allowed");
+ } else if (summary.getTransform().isDynamic() &&
+ !((summary.getDataType() == DataType.STRING) || (summary.getDataType() == DataType.URI)))
+ {
+ throw new IllegalArgumentException("'summary: dynamic' for non-text field " +
+ "'" + field.getName() + "'" +
+ " (" + summary.getDataType() + ")" +
+ " is not allowed");
+ }
+ }
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/BuiltInFieldSets.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/BuiltInFieldSets.java
new file mode 100644
index 00000000000..4ad9b5304a5
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/BuiltInFieldSets.java
@@ -0,0 +1,49 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.Field;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Adds field sets for 1) fields defined inside document type 2) fields inside search but outside document
+ * @author vegardh
+ *
+ */
+public class BuiltInFieldSets extends Processor {
+
+ private static final String DOC_FIELDSET_NAME = "[document]";
+ public static final String SEARCH_FIELDSET_NAME = "[search]"; // Public due to oddities in position handling.
+ public static final String INTERNAL_FIELDSET_NAME = "[internal]"; // This one populated from misc places
+
+ public BuiltInFieldSets(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ addDocumentFieldSet();
+ addSearchFieldSet();
+ // "Hook" the field sets on search onto the document types, since we will include them
+ // on the document configs
+ search.getDocument().setFieldSets(search.fieldSets());
+ }
+
+ private void addSearchFieldSet() {
+ for (SDField searchField : search.extraFieldList()) {
+ search.fieldSets().addBuiltInFieldSetItem(SEARCH_FIELDSET_NAME, searchField.getName());
+ }
+ }
+
+ private void addDocumentFieldSet() {
+ for (Field docField : search.getDocument().fieldSet()) {
+ search.fieldSets().addBuiltInFieldSetItem(DOC_FIELDSET_NAME, docField.getName());
+ }
+ }
+
+
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/CreatePositionZCurve.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/CreatePositionZCurve.java
new file mode 100644
index 00000000000..0aabd4dc192
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/CreatePositionZCurve.java
@@ -0,0 +1,201 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.ArrayDataType;
+import com.yahoo.document.DataType;
+import com.yahoo.document.PositionDataType;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
+import com.yahoo.vespa.indexinglanguage.expressions.AttributeExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.Expression;
+import com.yahoo.vespa.indexinglanguage.expressions.ForEachExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.StatementExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.SummaryExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.ZCurveExpression;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Level;
+
+/**
+ * Adds a "fieldName.zcurve" long attribute and a "fieldName.distance" summary field to all position type fields.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class CreatePositionZCurve extends Processor {
+
+ public CreatePositionZCurve(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ DataType fieldType = field.getDataType();
+ if ( ! isSupportedPositionType(fieldType)) continue;
+
+ if (field.doesIndexing()) {
+ fail(search, field, "Indexing of data type '" + fieldType.getName() + "' is not supported, " +
+ "replace 'index' statement with 'attribute'.");
+ }
+
+ if ( ! field.doesAttributing()) continue;
+
+ boolean doesSummary = field.doesSummarying();
+
+ String fieldName = field.getName();
+ field.getAttributes().remove(fieldName);
+
+ String zName = PositionDataType.getZCurveFieldName(fieldName);
+ SDField zCurveField = createZCurveField(field, zName);
+ search.addExtraField(zCurveField);
+ search.fieldSets().addBuiltInFieldSetItem(BuiltInFieldSets.INTERNAL_FIELDSET_NAME, zCurveField.getName());
+
+ // configure summary
+ Collection<String> summaryTo = removeSummaryTo(field);
+ ensureCompatibleSummary(field, zName,
+ PositionDataType.getPositionSummaryFieldName(fieldName),
+ DataType.getArray(DataType.STRING), // will become "xmlstring"
+ SummaryTransform.POSITIONS, summaryTo);
+ ensureCompatibleSummary(field, zName,
+ PositionDataType.getDistanceSummaryFieldName(fieldName),
+ DataType.INT,
+ SummaryTransform.DISTANCE, summaryTo);
+ // clear indexing script
+ field.setIndexingScript(null);
+ SDField posX = field.getStructField(PositionDataType.FIELD_X);
+ if (posX != null) {
+ posX.setIndexingScript(null);
+ }
+ SDField posY = field.getStructField(PositionDataType.FIELD_Y);
+ if (posY != null) {
+ posY.setIndexingScript(null);
+ }
+ if (doesSummary) ensureCompatibleSummary(field, zName,
+ field.getName(),
+ field.getDataType(),
+ SummaryTransform.GEOPOS, summaryTo);
+ }
+ }
+
+ private SDField createZCurveField(SDField inputField, String fieldName) {
+ if (search.getField(fieldName) != null || search.getAttribute(fieldName) != null) {
+ throw newProcessException(search, null, "Incompatible position attribute '" + fieldName +
+ "' already created.");
+ }
+ boolean isArray = inputField.getDataType() instanceof ArrayDataType;
+ SDField field = new SDField(fieldName, isArray ? DataType.getArray(DataType.LONG) : DataType.LONG);
+ Attribute attribute = new Attribute(fieldName, Attribute.Type.LONG, isArray ? Attribute.CollectionType.ARRAY :
+ Attribute.CollectionType.SINGLE);
+ attribute.setPosition(true);
+ attribute.setFastSearch(true);
+ field.addAttribute(attribute);
+
+ ScriptExpression script = inputField.getIndexingScript();
+ script = (ScriptExpression)new RemoveSummary(inputField.getName()).convert(script);
+ script = (ScriptExpression)new PerformZCurve(field, fieldName).convert(script);
+ field.setIndexingScript(script);
+ return field;
+ }
+
+ private void ensureCompatibleSummary(SDField field, String sourceName, String summaryName, DataType summaryType,
+ SummaryTransform summaryTransform, Collection<String> summaryTo) {
+ SummaryField summary = search.getSummaryField(summaryName);
+ if (summary == null) {
+ summary = new SummaryField(summaryName, summaryType, summaryTransform);
+ summary.addDestination("default");
+ summary.addDestinations(summaryTo);
+ field.addSummaryField(summary);
+ } else if (!summary.getDataType().equals(summaryType)) {
+ fail(search, field, "Incompatible summary field '" + summaryName + "' type "+summary.getDataType()+" already created.");
+ } else if (summary.getTransform() == SummaryTransform.NONE) {
+ summary.setTransform(summaryTransform);
+ summary.addDestination("default");
+ summary.addDestinations(summaryTo);
+ } else if (summary.getTransform() != summaryTransform) {
+ deployLogger.log(Level.WARNING, "Summary field " + summaryName + " has wrong transform: " + summary.getTransform());
+ return;
+ }
+ SummaryField.Source source = new SummaryField.Source(sourceName);
+ summary.getSources().clear();
+ summary.addSource(source);
+ }
+
+ private Set<String> removeSummaryTo(SDField field) {
+ Set<String> summaryTo = new HashSet<>();
+ Collection<SummaryField> summaryFields = field.getSummaryFields();
+ for (SummaryField summary : summaryFields) {
+ summaryTo.addAll(summary.getDestinations());
+ }
+ summaryFields.clear();
+ return summaryTo;
+ }
+
+ private static boolean isSupportedPositionType(DataType dataType) {
+ if (dataType instanceof ArrayDataType) {
+ dataType = ((ArrayDataType)dataType).getNestedType();
+ }
+ return dataType.equals(PositionDataType.INSTANCE);
+ }
+
+ private static class RemoveSummary extends ExpressionConverter {
+
+ final String find;
+
+ RemoveSummary(String find) {
+ this.find = find;
+ }
+
+ @Override
+ protected boolean shouldConvert(Expression exp) {
+ if (!(exp instanceof SummaryExpression)) {
+ return false;
+ }
+ String fieldName = ((SummaryExpression)exp).getFieldName();
+ return fieldName == null || fieldName.equals(find);
+ }
+
+ @Override
+ protected Expression doConvert(Expression exp) {
+ return null;
+ }
+ }
+
+ private static class PerformZCurve extends ExpressionConverter {
+
+ final String find;
+ final String replace;
+ final boolean isArray;
+
+ PerformZCurve(SDField find, String replace) {
+ this.find = find.getName();
+ this.replace = replace;
+ this.isArray = find.getDataType() instanceof ArrayDataType;
+ }
+
+ @Override
+ protected boolean shouldConvert(Expression exp) {
+ if (!(exp instanceof AttributeExpression)) {
+ return false;
+ }
+ String fieldName = ((AttributeExpression)exp).getFieldName();
+ return fieldName == null || fieldName.equals(find);
+ }
+
+ @Override
+ protected Expression doConvert(Expression exp) {
+ return new StatementExpression(
+ isArray ? new ForEachExpression(new ZCurveExpression()) :
+ new ZCurveExpression(), new AttributeExpression(replace));
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/DeprecateAttributePrefetch.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/DeprecateAttributePrefetch.java
new file mode 100644
index 00000000000..6d5b3ff8936
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/DeprecateAttributePrefetch.java
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+public class DeprecateAttributePrefetch extends Processor {
+
+ public DeprecateAttributePrefetch(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ for (Attribute a : field.getAttributes().values()) {
+ if (Boolean.TRUE.equals(a.getPrefetchValue())) {
+ warn(search, field, "Attribute prefetch is deprecated. Use an explicitly defined document summary with all desired fields defined as attribute.");
+ }
+ if (Boolean.FALSE.equals(a.getPrefetchValue())) {
+ warn(search, field, "Attribute prefetch is deprecated. no-prefetch can be removed.");
+ }
+ }
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/DisallowComplexMapAndWsetKeyTypes.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/DisallowComplexMapAndWsetKeyTypes.java
new file mode 100644
index 00000000000..dbe83143189
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/DisallowComplexMapAndWsetKeyTypes.java
@@ -0,0 +1,43 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.DataType;
+import com.yahoo.document.MapDataType;
+import com.yahoo.document.PrimitiveDataType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.document.WeightedSetDataType;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Non-primitive key types for map and weighted set forbidden (though OK in document model)
+ * @author vegardh
+ *
+ */
+public class DisallowComplexMapAndWsetKeyTypes extends Processor {
+
+ public DisallowComplexMapAndWsetKeyTypes(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ // TODO also traverse struct types to search for bad map or wset types there. Do this after document manager is fixed, do
+ // not start using the static stuff on SDDocumentTypes any more.
+ for (SDField field : search.allFieldsList()) {
+ if (field.getDataType() instanceof WeightedSetDataType) {
+ DataType nestedType = ((WeightedSetDataType)field.getDataType()).getNestedType();
+ if (!(nestedType instanceof PrimitiveDataType)) {
+ fail(search, field, "Weighted set must have a primitive key type.");
+ }
+ } else if (field.getDataType() instanceof MapDataType) {
+ if (!(((MapDataType)field.getDataType()).getKeyType() instanceof PrimitiveDataType)) {
+ fail(search, field, "Map key type must be a primitive type");
+ }
+ }
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/DiversitySettingsValidator.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/DiversitySettingsValidator.java
new file mode 100644
index 00000000000..49701f3e443
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/DiversitySettingsValidator.java
@@ -0,0 +1,58 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfile;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Created by balder on 3/10/15.
+ */
+public class DiversitySettingsValidator extends Processor {
+ public DiversitySettingsValidator(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (RankProfile rankProfile : rankProfileRegistry.localRankProfiles(search)) {
+ if (rankProfile.getMatchPhaseSettings() != null && rankProfile.getMatchPhaseSettings().getDiversity() != null) {
+ validate(rankProfile, rankProfile.getMatchPhaseSettings().getDiversity());
+ }
+ }
+ }
+ private void validate(RankProfile rankProfile, RankProfile.DiversitySettings settings) {
+ String attributeName = settings.getAttribute();
+ new AttributeValidator(search.getName(), rankProfile.getName(),
+ search.getAttribute(attributeName), attributeName).validate();
+ }
+
+ private static class AttributeValidator extends MatchPhaseSettingsValidator.AttributeValidator {
+ public AttributeValidator(String searchName, String rankProfileName, Attribute attribute, String attributeName) {
+ super(searchName, rankProfileName, attribute, attributeName);
+ }
+
+ protected void validateThatAttributeIsSingleAndNotPredicate() {
+ if (!attribute.getCollectionType().equals(Attribute.CollectionType.SINGLE) ||
+ attribute.getType().equals(Attribute.Type.PREDICATE))
+ {
+ failValidation("must be single value numeric, or enumerated attribute, but it is '"
+ + attribute.getDataType().getName() + "'");
+ }
+ }
+
+ @Override
+ public void validate() {
+ validateThatAttributeExists();
+ validateThatAttributeIsSingleAndNotPredicate();
+ }
+
+ @Override
+ public String getValidationType() {
+ return "diversity";
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ExactMatch.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ExactMatch.java
new file mode 100644
index 00000000000..d53be4bd18d
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ExactMatch.java
@@ -0,0 +1,98 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Matching;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Stemming;
+import com.yahoo.vespa.indexinglanguage.ExpressionSearcher;
+import com.yahoo.vespa.indexinglanguage.expressions.*;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * The implementation of exact matching
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ExactMatch extends Processor {
+
+ public static final String DEFAULT_EXACT_TERMINATOR = "@@";
+
+ public ExactMatch(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ Matching.Type matching = field.getMatching().getType();
+ if (matching.equals(Matching.Type.EXACT) ||
+ matching.equals(Matching.Type.WORD))
+ {
+ implementExactMatch(field, search);
+ } else if (field.getMatching().getExactMatchTerminator() != null) {
+ warn(search, field, "exact-terminator requires 'exact' matching to have any effect.");
+ }
+ }
+ }
+
+ private void implementExactMatch(SDField field, Search search) {
+ field.setStemming(Stemming.NONE);
+ field.getNormalizing().inferLowercase();
+
+ if (field.getMatching().getType().equals(Matching.Type.WORD)) {
+ field.addQueryCommand("word");
+ } else { // exact
+ String exactTerminator = DEFAULT_EXACT_TERMINATOR;
+ if (field.getMatching().getExactMatchTerminator() != null &&
+ ! field.getMatching().getExactMatchTerminator().equals(""))
+ {
+ exactTerminator = field.getMatching().getExactMatchTerminator();
+ } else {
+ warn(search, field,
+ "With 'exact' matching, an exact-terminator is needed (using \""
+ + exactTerminator +"\" as terminator)");
+ }
+ field.addQueryCommand("exact " + exactTerminator);
+
+ // The following part illustrates how nice it would have been with canonical
+ // representation of indices
+ if (field.doesIndexing()) {
+ exactMatchSettingsForField(field);
+ }
+ }
+ ScriptExpression script = field.getIndexingScript();
+ if (new ExpressionSearcher<>(IndexExpression.class).containedIn(script)) {
+ field.setIndexingScript((ScriptExpression)new MyProvider(search).convert(field.getIndexingScript()));
+ }
+ }
+
+ private void exactMatchSettingsForField(SDField field) {
+ field.getRanking().setFilter(true);
+ }
+
+ private static class MyProvider extends TypedTransformProvider {
+
+ MyProvider(Search search) {
+ super(ExactExpression.class, search);
+ }
+
+ @Override
+ protected boolean requiresTransform(Expression exp, DataType fieldType) {
+ return exp instanceof OutputExpression;
+ }
+
+ @Override
+ protected Expression newTransform(DataType fieldType) {
+ Expression exp = new ExactExpression();
+ if (fieldType instanceof CollectionDataType) {
+ exp = new ForEachExpression(exp);
+ }
+ return exp;
+ }
+ }
+}
+
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/FieldSetValidity.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/FieldSetValidity.java
new file mode 100644
index 00000000000..e809fe90c7f
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/FieldSetValidity.java
@@ -0,0 +1,94 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.FieldSet;
+import com.yahoo.searchdefinition.document.Matching;
+import com.yahoo.searchdefinition.document.NormalizeLevel;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Stemming;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Computes the right "index commands" for each fieldset in a search definition.
+ *
+ *
+ * @author vegardh
+ * @author bratseth
+ */
+public class FieldSetValidity extends Processor {
+
+ public FieldSetValidity(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (FieldSet fieldSet : search.fieldSets().userFieldSets().values()) {
+ checkFieldNames(search, fieldSet);
+ checkMatching(search, fieldSet);
+ checkNormalization(search, fieldSet);
+ checkStemming(search, fieldSet);
+ }
+ }
+
+ private void checkFieldNames(Search search, FieldSet fieldSet) {
+ for (String fld : fieldSet.getFieldNames()) {
+ SDField field = search.getField(fld);
+ if (field == null) {
+ throw new IllegalArgumentException("For search '" + search.getName() + "': Field '"+ fld + "' in " + fieldSet + " does not exist.");
+ }
+ }
+ }
+
+ private void checkMatching(Search search, FieldSet fieldSet) {
+ Matching fsMatching = null;
+ for (String fld : fieldSet.getFieldNames()) {
+ SDField field = search.getField(fld);
+ Matching fieldMatching = field.getMatching();
+ if (fsMatching==null) {
+ fsMatching = fieldMatching;
+ } else {
+ if (fsMatching!=null && !fsMatching.equals(fieldMatching)) {
+ warn(search, field, "The matching settings for the fields in " + fieldSet + " are inconsistent (explicitly or because of field type). This may lead to recall and ranking issues.");
+ return;
+ }
+ }
+ }
+ }
+
+ private void checkNormalization(Search search, FieldSet fieldSet) {
+ NormalizeLevel.Level fsNorm = null;
+ for (String fld : fieldSet.getFieldNames()) {
+ SDField field = search.getField(fld);
+ NormalizeLevel.Level fieldNorm = field.getNormalizing().getLevel();
+ if (fsNorm==null) {
+ fsNorm = fieldNorm;
+ } else {
+ if (fsNorm!=null && !fsNorm.equals(fieldNorm)) {
+ warn(search, field, "The normalization settings for the fields in " + fieldSet + " are inconsistent (explicitly or because of field type). This may lead to recall and ranking issues.");
+ return;
+ }
+ }
+ }
+ }
+
+ private void checkStemming(Search search, FieldSet fieldSet) {
+ Stemming fsStemming = null;
+ for (String fld : fieldSet.getFieldNames()) {
+ SDField field = search.getField(fld);
+ Stemming fieldStemming = field.getStemming();
+ if (fsStemming==null) {
+ fsStemming = fieldStemming;
+ } else {
+ if (fsStemming!=null && !fsStemming.equals(fieldStemming)) {
+ warn(search, field, "The stemming settings for the fields in the fieldset '"+fieldSet.getName()+"' are inconsistent (explicitly or because of field type). This may lead to recall and ranking issues.");
+ return;
+ }
+ }
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/FilterFieldNames.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/FilterFieldNames.java
new file mode 100644
index 00000000000..1df8c642750
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/FilterFieldNames.java
@@ -0,0 +1,70 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.RankType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.RankProfile;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.logging.Level;
+
+/**
+ * Takes the fields and indexes that are of type rank filter, and stores those names on all rank profiles
+ * @author vegardh
+ *
+ */
+public class FilterFieldNames extends Processor {
+
+ public FilterFieldNames(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField f : search.allFieldsList()) {
+ if (f.getRanking().isFilter()) {
+ filterField(f.getName());
+ }
+ }
+ for (RankProfile profile : rankProfileRegistry.localRankProfiles(search)) {
+ Set<String> filterFields = new LinkedHashSet<>();
+ findFilterFields(search, profile, filterFields);
+ for (Iterator<String> itr = filterFields.iterator(); itr.hasNext(); ) {
+ String fieldName = itr.next();
+ profile.filterFields().add(fieldName);
+ profile.addRankSetting(fieldName, RankProfile.RankSetting.Type.RANKTYPE, RankType.EMPTY);
+ }
+ }
+ }
+
+ private void filterField(String f) {
+ for (RankProfile rp : rankProfileRegistry.localRankProfiles(search)) {
+ rp.filterFields().add(f);
+ }
+ }
+
+ private void findFilterFields(Search search, RankProfile profile, Set<String> filterFields) {
+ for (Iterator<RankProfile.RankSetting> itr = profile.declaredRankSettingIterator(); itr.hasNext(); ) {
+ RankProfile.RankSetting setting = itr.next();
+ if (setting.getType().equals(RankProfile.RankSetting.Type.PREFERBITVECTOR) &&
+ ((Boolean)setting.getValue()).booleanValue())
+ {
+ String fieldName = setting.getFieldName();
+ if (search.getField(fieldName) != null) {
+ if (!profile.filterFields().contains(fieldName)) {
+ filterFields.add(fieldName);
+ }
+ } else {
+ deployLogger.log(Level.WARNING, "For rank profile '" + profile.getName() + "': Cannot apply rank filter setting to unexisting field '" + fieldName + "'");
+ }
+ }
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImplicitSummaries.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImplicitSummaries.java
new file mode 100644
index 00000000000..fa7725843b1
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImplicitSummaries.java
@@ -0,0 +1,222 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import java.util.logging.Level;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.document.PositionDataType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Makes implicitly defined summaries into explicit summaries
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ImplicitSummaries extends Processor {
+
+ public ImplicitSummaries(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ DocumentSummary defaultSummary=search.getSummary("default");
+ if (defaultSummary==null) {
+ defaultSummary=new DocumentSummary("default");
+ search.addSummary(defaultSummary);
+ }
+
+ for (SDField field : search.allFieldsList()) {
+ collectSummaries(field,search);
+ }
+
+ for (DocumentSummary documentSummary : search.getSummaries().values()) {
+ documentSummary.purgeImplicits();
+ }
+ }
+
+ private void addSummaryFieldSources(SummaryField summaryField, SDField sdField) {
+ sdField.addSummaryFieldSources(summaryField);
+ }
+
+ private void collectSummaries(SDField field,Search search) {
+ SummaryField addedSummaryField=null;
+
+ // Implicit
+ String fieldName = field.getName();
+ SummaryField fieldSummaryField=field.getSummaryField(fieldName);
+ if (fieldSummaryField == null && field.doesSummarying()) {
+ fieldSummaryField=new SummaryField(fieldName, field.getDataType());
+ fieldSummaryField.setImplicit(true);
+ addSummaryFieldSources(fieldSummaryField, field);
+ fieldSummaryField.addDestination("default");
+ field.addSummaryField(fieldSummaryField);
+ addedSummaryField = fieldSummaryField;
+ }
+ if (fieldSummaryField != null) {
+ for (String dest : fieldSummaryField.getDestinations()) {
+ DocumentSummary summary = search.getSummary(dest);
+ if (summary != null) {
+ summary.add(fieldSummaryField);
+ }
+ }
+ }
+
+ // Attribute prefetch
+ for (Attribute attribute : field.getAttributes().values()) {
+ if (attribute.getName().equals(fieldName)) {
+ if (addedSummaryField != null) {
+ addedSummaryField.setTransform(SummaryTransform.ATTRIBUTE);
+ }
+ if (attribute.isPrefetch()) {
+ addPrefetchAttribute(attribute, field, search);
+ }
+ }
+ }
+
+ // Position attributes
+ if (field.doesSummarying()) {
+ for (Attribute attribute : field.getAttributes().values()) {
+ if (!attribute.isPosition()) {
+ continue;
+ }
+ DocumentSummary attributePrefetchSummary = getOrCreateAttributePrefetchSummary(search);
+ attributePrefetchSummary.add(field.getSummaryField(PositionDataType.getDistanceSummaryFieldName(fieldName)));
+ attributePrefetchSummary.add(field.getSummaryField(PositionDataType.getPositionSummaryFieldName(fieldName)));
+ }
+ }
+
+ // Explicits
+ for (SummaryField summaryField : field.getSummaryFields()) {
+ // Make sure we fetch from attribute here too
+ Attribute attribute=field.getAttributes().get(fieldName);
+ if (attribute!=null && summaryField.getTransform()==SummaryTransform.NONE) {
+ summaryField.setTransform(SummaryTransform.ATTRIBUTE);
+ }
+
+ if (isValid(summaryField,search)) {
+ addToDestinations(summaryField, search);
+ }
+ }
+
+ }
+
+ private DocumentSummary getOrCreateAttributePrefetchSummary(Search search) {
+ DocumentSummary summary = search.getSummary("attributeprefetch");
+ if (summary == null) {
+ summary = new DocumentSummary("attributeprefetch");
+ search.addSummary(summary);
+ }
+ return summary;
+ }
+
+
+ private void addPrefetchAttribute(Attribute attribute,SDField field,Search search) {
+ if (attribute.getPrefetchValue()==null) { // Prefetch by default - unless any summary makes this dynamic
+ // Check if there is an implicit dynamic definition
+ SummaryField fieldSummaryField=field.getSummaryField(attribute.getName());
+ if (fieldSummaryField!=null && fieldSummaryField.getTransform().isDynamic()) return;
+
+ // Check if an explicit class makes it dynamic (first is enough, as all must be the same, checked later)
+ SummaryField explicitSummaryField=search.getExplicitSummaryField(attribute.getName());
+ if (explicitSummaryField!=null && explicitSummaryField.getTransform().isDynamic()) return;
+ }
+
+ DocumentSummary summary = getOrCreateAttributePrefetchSummary(search);
+ SummaryField attributeSummaryField = new SummaryField(attribute.getName(), attribute.getDataType());
+ attributeSummaryField.addSource(attribute.getName());
+ attributeSummaryField.addDestination("attributeprefetch");
+ attributeSummaryField.setTransform(SummaryTransform.ATTRIBUTE);
+ summary.add(attributeSummaryField);
+ }
+
+ // Returns whether this is valid. Warns if invalid and ignorable. Throws if not ignorable.
+ private boolean isValid(SummaryField summaryField,Search search) {
+ if (summaryField.getTransform() == SummaryTransform.DISTANCE ||
+ summaryField.getTransform() == SummaryTransform.POSITIONS)
+ {
+ int sourceCount = summaryField.getSourceCount();
+ if (sourceCount != 1) {
+ throw newProcessException(search.getName(), summaryField.getName(),
+ "Expected 1 source field, got " + sourceCount + ".");
+ }
+ String sourceName = summaryField.getSingleSource();
+ if (search.getAttribute(sourceName) == null) {
+ throw newProcessException(search.getName(), summaryField.getName(),
+ "Summary source attribute '" + sourceName + "' not found.");
+ }
+ return true;
+ }
+
+ String fieldName = summaryField.getSourceField();
+ SDField sourceField = search.getField(fieldName);
+ if (sourceField == null) {
+ throw newProcessException(search, summaryField, "Source field '" + fieldName + "' does not exist.");
+ }
+ if (!sourceField.doesSummarying() &&
+ !summaryField.getTransform().equals(SummaryTransform.ATTRIBUTE) &&
+ !summaryField.getTransform().equals(SummaryTransform.GEOPOS))
+ {
+ // Summary transform attribute may indicate that the ilscript was rewritten to remove summary
+ // by another search that uses this same field in inheritance.
+ deployLogger.log(Level.WARNING, "Ignoring " + summaryField + ": " + sourceField +
+ " is not creating a summary value in its indexing statement");
+ return false;
+ }
+
+ if (summaryField.getTransform().isDynamic()
+ && summaryField.getName().equals(sourceField.getName())
+ && sourceField.doesAttributing()) {
+ Attribute attribute=sourceField.getAttributes().get(sourceField.getName());
+ if (attribute!=null) {
+ String destinations="document summary 'default'";
+ if (summaryField.getDestinations().size()>0) {
+ destinations = "document summaries " + summaryField.getDestinations();
+ }
+ deployLogger.log(Level.WARNING, "Will fetch the disk summary value of " + sourceField + " in " + destinations +
+ " since this summary field uses a dynamic summary value (snippet/bolding): Dynamic summaries and bolding " +
+ "is not supported with summary values fetched from in-memory attributes yet. If you want to see partial updates " +
+ "to this attribute, remove any bolding and dynamic snippeting from this field");
+ // Note: The dynamic setting has already overridden the attribute map setting,
+ // so we do not need to actually do attribute.setSummary(false) here
+ // Also, we can not do this, since it makes it impossible to fetch this attribute
+ // in another summary
+ }
+ }
+
+ return true;
+ }
+
+ private void addToDestinations(SummaryField summaryField,Search search) {
+ if (summaryField.getDestinations().size()==0) {
+ addToDestination("default",summaryField,search);
+ }
+ else {
+ for (String destinationName : summaryField.getDestinations())
+ addToDestination(destinationName,summaryField,search);
+ }
+ }
+
+ private void addToDestination(String destinationName,SummaryField summaryField,Search search) {
+ DocumentSummary destination=search.getSummary(destinationName);
+ if (destination==null) {
+ destination=new DocumentSummary(destinationName);
+ search.addSummary(destination);
+ destination.add(summaryField);
+ }
+ else {
+ SummaryField existingField=
+ destination.getSummaryField(summaryField.getName());
+ SummaryField merged=summaryField.mergeWith(existingField);
+ destination.add(merged);
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImplicitSummaryFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImplicitSummaryFields.java
new file mode 100644
index 00000000000..b95f2469cd8
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ImplicitSummaryFields.java
@@ -0,0 +1,38 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.DataType;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * This processor adds all implicit summary fields to all registered document summaries. If another field has already
+ * been registered with one of the implicit names, this processor will throw an {@link IllegalStateException}.
+ */
+public class ImplicitSummaryFields extends Processor {
+
+ public ImplicitSummaryFields(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (DocumentSummary docsum : search.getSummaries().values()) {
+ addField(docsum, new SummaryField("rankfeatures", DataType.STRING, SummaryTransform.RANKFEATURES));
+ addField(docsum, new SummaryField("summaryfeatures", DataType.STRING, SummaryTransform.SUMMARYFEATURES));
+ }
+ }
+
+ private void addField(DocumentSummary docsum, SummaryField field) {
+ if (docsum.getSummaryField(field.getName()) != null) {
+ throw new IllegalStateException("Summary class '" + docsum.getName() + "' uses reserved field name '" +
+ field.getName() + "'.");
+ }
+ docsum.add(field);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexFieldNames.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexFieldNames.java
new file mode 100644
index 00000000000..2c18c58d3dc
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexFieldNames.java
@@ -0,0 +1,43 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Because of the way the parser works (allowing any token as identifier),
+ * it is not practical to limit the syntax of field names there, do it here.
+ * Important to disallow dash, has semantic in IL.
+ * @author vegardh
+ *
+ */
+public class IndexFieldNames extends Processor {
+
+ private static final String FIELD_NAME_REGEXP = "[a-zA-Z]\\w*";
+
+ public IndexFieldNames(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (!field.getName().matches(FIELD_NAME_REGEXP) && !legalDottedPositionField(field)) {
+ fail(search, field, " Not a legal field name. Legal expression: " + FIELD_NAME_REGEXP);
+ }
+ }
+ }
+
+ /**
+ * In {@link CreatePositionZCurve} we add some .position and .distance fields for pos fields. Make an exception for those for now. For 6.0, rename
+ * to _position and _distance and delete this method.
+ * @param field an {@link com.yahoo.searchdefinition.document.SDField}
+ * @return true if allowed
+ */
+ private boolean legalDottedPositionField(SDField field) {
+ return field.getName().endsWith(".position") || field.getName().endsWith(".distance");
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexSettingsNonFieldNames.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexSettingsNonFieldNames.java
new file mode 100644
index 00000000000..fecc5e2309d
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexSettingsNonFieldNames.java
@@ -0,0 +1,47 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Index;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.Iterator;
+
+/**
+ * Fail if:
+ * 1) There are index: settings without explicit index names (name same as field name)
+ * 2) All the index-to indexes differ from the field name.
+ * @author vegardh
+ *
+ */
+public class IndexSettingsNonFieldNames extends Processor {
+
+ public IndexSettingsNonFieldNames(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ boolean fieldNameUsed = false;
+ for (Iterator i = field.getFieldNameAsIterator(); i.hasNext();) {
+ String iName = (String)(i.next());
+ if (iName.equals(field.getName())) {
+ fieldNameUsed = true;
+ }
+ }
+ if (!fieldNameUsed) {
+ for (Index index : field.getIndices().values()) {
+ if (index.getName().equals(field.getName())) {
+ throw new IllegalArgumentException("Error in " + field + " in " + search +
+ ": When all index names differ from field name, index parameter settings must specify index name explicitly.");
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexTo2FieldSet.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexTo2FieldSet.java
new file mode 100644
index 00000000000..3148273ddc1
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexTo2FieldSet.java
@@ -0,0 +1,33 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * @author balder
+ */
+public class IndexTo2FieldSet extends Processor {
+
+ public IndexTo2FieldSet(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ processField(field);
+ }
+ }
+ private void processField(SDField field) {
+ if (field.usesStructOrMap()) {
+ for (SDField subField : field.getStructFields()) {
+ processField(subField);
+ }
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingInputs.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingInputs.java
new file mode 100644
index 00000000000..c2e889f7993
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingInputs.java
@@ -0,0 +1,110 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
+import com.yahoo.vespa.indexinglanguage.ExpressionVisitor;
+import com.yahoo.vespa.indexinglanguage.expressions.Expression;
+import com.yahoo.vespa.indexinglanguage.expressions.InputExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.StatementExpression;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * <p>This processor modifies all indexing scripts so that they input the value of the owning field by default. It also
+ * ensures that all fields used as input exist.</p>
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class IndexingInputs extends Processor {
+
+ public IndexingInputs(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ ScriptExpression script = field.getIndexingScript();
+ if (script == null) {
+ continue;
+ }
+ String fieldName = field.getName();
+ script = (ScriptExpression)new DefaultToCurrentField(fieldName).convert(script);
+ script = (ScriptExpression)new EnsureInputExpression(fieldName).convert(script);
+ new VerifyInputExpression(search, field).visit(script);
+ field.setIndexingScript(script);
+ }
+ }
+
+ private static class DefaultToCurrentField extends ExpressionConverter {
+
+ final String fieldName;
+
+ DefaultToCurrentField(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ @Override
+ protected boolean shouldConvert(Expression exp) {
+ return exp instanceof InputExpression && ((InputExpression)exp).getFieldName() == null;
+ }
+
+ @Override
+ protected Expression doConvert(Expression exp) {
+ return new InputExpression(fieldName);
+ }
+ }
+
+ private static class EnsureInputExpression extends ExpressionConverter {
+
+ final String fieldName;
+
+ EnsureInputExpression(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ @Override
+ protected boolean shouldConvert(Expression exp) {
+ return exp instanceof StatementExpression;
+ }
+
+ @Override
+ protected Expression doConvert(Expression exp) {
+ if (exp.requiredInputType() != null) {
+ return new StatementExpression(new InputExpression(fieldName), exp);
+ } else {
+ return exp;
+ }
+ }
+ }
+
+ private class VerifyInputExpression extends ExpressionVisitor {
+
+ private final Search search;
+ private final SDField field;
+
+ public VerifyInputExpression(Search search, SDField field) {
+ this.search = search;
+ this.field = field;
+ }
+
+ @Override
+ protected void doVisit(Expression exp) {
+ if (!(exp instanceof InputExpression)) {
+ return;
+ }
+ SDDocumentType docType = search.getDocument();
+ String inputField = ((InputExpression)exp).getFieldName();
+ if (docType.getField(inputField) != null) {
+ return;
+ }
+ fail(search, field, "Indexing script refers to field '" + inputField + "' which does not exist " +
+ "in document type '" + docType.getName() + "'.");
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingOutputs.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingOutputs.java
new file mode 100644
index 00000000000..55e7dabf9ba
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingOutputs.java
@@ -0,0 +1,141 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.DataType;
+import com.yahoo.document.Field;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
+import com.yahoo.vespa.indexinglanguage.expressions.*;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.*;
+
+/**
+ * <p>This processor modifies all indexing scripts so that they output to the owning field by default. It also prevents
+ * any output expression from writing to any field except for the owning field. Finally, for <tt>SummaryExpression</tt>,
+ * this processor expands to write all appropriate summary fields.</p>
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class IndexingOutputs extends Processor {
+
+ public IndexingOutputs(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ ScriptExpression script = field.getIndexingScript();
+ if (script == null) {
+ continue;
+ }
+ Set<String> summaryFields = new TreeSet<>();
+ findSummaryTo(search, field, summaryFields, summaryFields);
+ MyConverter converter = new MyConverter(search, field, summaryFields);
+ field.setIndexingScript((ScriptExpression)converter.convert(script));
+ }
+ }
+
+ public void findSummaryTo(Search search, SDField field, Set<String> dynamicSummary,
+ Set<String> staticSummary) {
+ Map<String, SummaryField> summaryFields = search.getSummaryFields(field);
+ if (summaryFields.isEmpty()) {
+ fillSummaryToFromField(field, dynamicSummary, staticSummary);
+ } else {
+ fillSummaryToFromSearch(search, field, summaryFields, dynamicSummary, staticSummary);
+ }
+ }
+
+ private void fillSummaryToFromSearch(Search search, SDField field, Map<String, SummaryField> summaryFields,
+ Set<String> dynamicSummary, Set<String> staticSummary) {
+ for (SummaryField summaryField : summaryFields.values()) {
+ fillSummaryToFromSummaryField(search, field, summaryField, dynamicSummary, staticSummary);
+ }
+ }
+
+ private void fillSummaryToFromSummaryField(Search search, SDField field, SummaryField summaryField, Set<String> dynamicSummary, Set<String> staticSummary) {
+ SummaryTransform summaryTransform = summaryField.getTransform();
+ String summaryName = summaryField.getName();
+ if (summaryTransform.isDynamic() && summaryField.getSourceCount() > 2) {
+ // Avoid writing to summary fields that have more than a single input field, as that is handled by the
+ // summary rewriter in the search core.
+ return;
+ }
+ if (summaryTransform.isDynamic()) {
+ DataType fieldType = field.getDataType();
+ if (fieldType != DataType.URI && fieldType != DataType.STRING) {
+ warn(search, field, "Dynamic summaries are only supported for fields of type " +
+ "string, ignoring summary field '" + summaryField.getName() +
+ "' for sd field '" + field.getName() + "' of type " +
+ fieldType.getName() + ".");
+ return;
+ }
+ dynamicSummary.add(summaryName);
+ } else if (summaryTransform != SummaryTransform.ATTRIBUTE) {
+ staticSummary.add(summaryName);
+ }
+ }
+
+ private static void fillSummaryToFromField(SDField field, Set<String> dynamicSummary, Set<String> staticSummary) {
+ for (SummaryField summaryField : field.getSummaryFields()) {
+ String summaryName = summaryField.getName();
+ if (summaryField.getTransform().isDynamic()) {
+ dynamicSummary.add(summaryName);
+ } else {
+ staticSummary.add(summaryName);
+ }
+ }
+ }
+
+ private class MyConverter extends ExpressionConverter {
+
+ final Search search;
+ final Field field;
+ final Set<String> summaryFields;
+
+ MyConverter(Search search, Field field, Set<String> summaryFields) {
+ this.search = search;
+ this.field = field;
+ this.summaryFields = summaryFields.isEmpty() ? Collections.singleton(field.getName()) : summaryFields;
+ }
+
+ @Override
+ protected boolean shouldConvert(Expression exp) {
+ if (!(exp instanceof OutputExpression)) {
+ return false;
+ }
+ String fieldName = ((OutputExpression)exp).getFieldName();
+ if (fieldName == null) {
+ return true; // inject appropriate field name
+ }
+ if (!fieldName.equals(field.getName())) {
+ fail(search, field, "Indexing expression '" + exp + "' attempts to write to a field other than '" +
+ field.getName() + "'.");
+ }
+ return false;
+ }
+
+ @Override
+ protected Expression doConvert(Expression exp) {
+ List<Expression> ret = new LinkedList<>();
+ if (exp instanceof AttributeExpression) {
+ ret.add(new AttributeExpression(field.getName()));
+ } else if (exp instanceof IndexExpression) {
+ ret.add(new IndexExpression(field.getName()));
+ } else if (exp instanceof SummaryExpression) {
+ for (String fieldName : summaryFields) {
+ ret.add(new SummaryExpression(fieldName));
+ }
+ } else {
+ throw new UnsupportedOperationException(exp.getClass().getName());
+ }
+ return new StatementExpression(ret);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingValidation.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingValidation.java
new file mode 100644
index 00000000000..af04deb5347
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingValidation.java
@@ -0,0 +1,149 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
+import com.yahoo.vespa.indexinglanguage.expressions.*;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class IndexingValidation extends Processor {
+
+ public IndexingValidation(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ VerificationContext ctx = new VerificationContext(new MyAdapter(search));
+ for (SDField field : search.allFieldsList()) {
+ ScriptExpression script = field.getIndexingScript();
+ try {
+ script.verify(ctx);
+ MyConverter converter = new MyConverter();
+ for (StatementExpression exp : script) {
+ converter.convert(exp); // TODO: stop doing this explicitly when visiting a script does not branch
+ }
+ } catch (VerificationException e) {
+ fail(search, field, "For expression '" + e.getExpression() + "': " + e.getMessage());
+ }
+ }
+ }
+
+ private static class MyConverter extends ExpressionConverter {
+
+ final Set<String> outputs = new HashSet<>();
+ final Set<String> prevNames = new HashSet<>();
+
+ @Override
+ protected ExpressionConverter branch() {
+ MyConverter ret = new MyConverter();
+ ret.outputs.addAll(outputs);
+ ret.prevNames.addAll(prevNames);
+ return ret;
+ }
+
+ @Override
+ protected boolean shouldConvert(Expression exp) {
+ if (exp instanceof OutputExpression) {
+ String fieldName = ((OutputExpression)exp).getFieldName();
+ if (outputs.contains(fieldName) && !prevNames.contains(fieldName)) {
+ throw new VerificationException(exp, "Attempting to assign conflicting values to field '" +
+ fieldName + "'.");
+ }
+ outputs.add(fieldName);
+ prevNames.add(fieldName);
+ }
+ if (exp.createdOutputType() != null) {
+ prevNames.clear();
+ }
+ return false;
+ }
+
+ @Override
+ protected Expression doConvert(Expression exp) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private static class MyAdapter implements FieldTypeAdapter {
+
+ final Search search;
+
+ public MyAdapter(Search search) {
+ this.search = search;
+ }
+
+ @Override
+ public DataType getInputType(Expression exp, String fieldName) {
+ SDField field = search.getDocumentField(fieldName);
+ if (field == null) {
+ throw new VerificationException(exp, "Input field '" + fieldName + "' not found.");
+ }
+ return field.getDataType();
+ }
+
+ @Override
+ public void tryOutputType(Expression exp, String fieldName, DataType valueType) {
+ String fieldDesc;
+ DataType fieldType;
+ if (exp instanceof AttributeExpression) {
+ Attribute attribute = search.getAttribute(fieldName);
+ if (attribute == null) {
+ throw new VerificationException(exp, "Attribute '" + fieldName + "' not found.");
+ }
+ fieldDesc = "attribute";
+ fieldType = attribute.getDataType();
+ } else if (exp instanceof IndexExpression) {
+ SDField field = search.getField(fieldName);
+ if (field == null) {
+ throw new VerificationException(exp, "Index field '" + fieldName + "' not found.");
+ }
+ fieldDesc = "index field";
+ fieldType = field.getDataType();
+ } else if (exp instanceof SummaryExpression) {
+ SummaryField field = search.getSummaryField(fieldName);
+ if (field == null) {
+ throw new VerificationException(exp, "Summary field '" + fieldName + "' not found.");
+ }
+ fieldDesc = "summary field";
+ fieldType = field.getDataType();
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ if (!fieldType.isAssignableFrom(valueType) &&
+ !fieldType.isAssignableFrom(createCompatType(valueType)))
+ {
+ throw new VerificationException(exp, "Can not assign " + valueType.getName() + " to " + fieldDesc +
+ " '" + fieldName + "' which is " + fieldType.getName() + ".");
+ }
+ }
+
+ private static DataType createCompatType(DataType origType) {
+ if (origType instanceof ArrayDataType) {
+ return DataType.getArray(createCompatType(((ArrayDataType)origType).getNestedType()));
+ } else if (origType instanceof MapDataType) {
+ MapDataType mapType = (MapDataType)origType;
+ return DataType.getMap(createCompatType(mapType.getKeyType()),
+ createCompatType(mapType.getValueType()));
+ } else if (origType instanceof WeightedSetDataType) {
+ return DataType.getWeightedSet(createCompatType(((WeightedSetDataType)origType).getNestedType()));
+ } else if (origType == PositionDataType.INSTANCE) {
+ return DataType.LONG;
+ } else {
+ return origType;
+ }
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingValues.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingValues.java
new file mode 100644
index 00000000000..ff8be71f8c3
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IndexingValues.java
@@ -0,0 +1,69 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.Field;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
+import com.yahoo.vespa.indexinglanguage.expressions.Expression;
+import com.yahoo.vespa.indexinglanguage.expressions.InputExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.OutputExpression;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class IndexingValues extends Processor {
+
+ public IndexingValues(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (Field field : search.getDocument().fieldSet()) {
+ SDField sdField = (SDField)field;
+ if (!sdField.isExtraField()) {
+ new RequireThatDocumentFieldsAreImmutable(field).convert(sdField.getIndexingScript());
+ }
+ }
+ }
+
+ private class RequireThatDocumentFieldsAreImmutable extends ExpressionConverter {
+
+ final Field field;
+ Expression mutatedBy;
+
+ RequireThatDocumentFieldsAreImmutable(Field field) {
+ this.field = field;
+ }
+
+ @Override
+ public ExpressionConverter branch() {
+ return clone();
+ }
+
+ @Override
+ protected boolean shouldConvert(Expression exp) {
+ if (exp instanceof OutputExpression && mutatedBy != null) {
+ throw newProcessException(search, field,
+ "Indexing expression '" + mutatedBy + "' modifies the value of the " +
+ "document field '" + field.getName() + "'. This is no longer supported -- " +
+ "declare such fields outside the document.");
+ }
+ if (exp instanceof InputExpression && ((InputExpression)exp).getFieldName().equals(field.getName())) {
+ mutatedBy = null;
+ } else if (exp.createdOutputType() != null) {
+ mutatedBy = exp;
+ }
+ return false;
+ }
+
+ @Override
+ protected Expression doConvert(Expression exp) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/IntegerIndex2Attribute.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IntegerIndex2Attribute.java
new file mode 100644
index 00000000000..a29896ce3e8
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/IntegerIndex2Attribute.java
@@ -0,0 +1,86 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.NumericDataType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Index;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
+import com.yahoo.vespa.indexinglanguage.ExpressionVisitor;
+import com.yahoo.vespa.indexinglanguage.expressions.AttributeExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.Expression;
+import com.yahoo.vespa.indexinglanguage.expressions.IndexExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Replaces the 'index' statement of all numerical fields to 'attribute' because we no longer support numerical indexes.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public class IntegerIndex2Attribute extends Processor {
+
+ public IntegerIndex2Attribute(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (field.doesIndexing() && field.getDataType().getPrimitiveType() instanceof NumericDataType) {
+ // Avoid changing for example RISE fields
+ if (field.getIndex(field.getName()) != null && !(field.getIndex(field.getName()).getType().equals(Index.Type.VESPA))) continue;
+ ScriptExpression script = field.getIndexingScript();
+ Set<String> attributeNames = new HashSet<>();
+ new MyVisitor(attributeNames).visit(script);
+ field.setIndexingScript((ScriptExpression)new MyConverter(attributeNames).convert(script));
+ warn(search, field, "Changed to attribute because numerical indexes (field has type "+field.getDataType().getName()+") is not currently supported." +
+ " Index-only settings may fail. Ignore this warning for streaming search.");
+ }
+ }
+ }
+
+ private static class MyVisitor extends ExpressionVisitor {
+
+ final Set<String> attributeNames;
+
+ public MyVisitor(Set<String> attributeNames) {
+ this.attributeNames = attributeNames;
+ }
+
+ @Override
+ protected void doVisit(Expression exp) {
+ if (exp instanceof AttributeExpression) {
+ attributeNames.add(((AttributeExpression)exp).getFieldName());
+ }
+ }
+ }
+
+ private static class MyConverter extends ExpressionConverter {
+
+ final Set<String> attributeNames;
+
+ public MyConverter(Set<String> attributeNames) {
+ this.attributeNames = attributeNames;
+ }
+
+ @Override
+ protected boolean shouldConvert(Expression exp) {
+ return exp instanceof IndexExpression;
+ }
+
+ @Override
+ protected Expression doConvert(Expression exp) {
+ String indexName = ((IndexExpression)exp).getFieldName();
+ if (attributeNames.contains(indexName)) {
+ return null;
+ }
+ return new AttributeExpression(indexName);
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/LiteralBoost.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/LiteralBoost.java
new file mode 100644
index 00000000000..e4e96bb51ab
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/LiteralBoost.java
@@ -0,0 +1,79 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.RankProfile;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.Iterator;
+
+/**
+ * Expresses literal boosts in terms of extra indices with rank boost.
+ * One extra index named <i>indexname</i>_exact is added for each index having
+ * a fields with literal-boosts of zero or more (zero to support other
+ * rank profiles setting a literal boost). Complete boost values in to fields
+ * are translated to rank boosts to the implementation indices.
+ * These indices has no positional
+ * or phrase support and contains concatenated versions of each field value
+ * of complete-boosted fields indexed to <i>indexname</i>. A search for indexname
+ * will be rewritten to also search <i>indexname</i>_exaxt
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class LiteralBoost extends Processor {
+
+ public LiteralBoost(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ /** Adds extra search fields and indices to express literal boosts */
+ @Override
+ public void process() {
+ checkRankModifierRankType(search);
+ addLiteralBoostsToFields(search);
+ reduceFieldLiteralBoosts(search);
+ }
+
+ /** Checks if literal boost is given using rank: , and set the actual literal boost accordingly. */
+ private void checkRankModifierRankType(Search search) {
+ for (SDField field : search.allFieldsList()) {
+ if (field.getLiteralBoost() > -1) continue; // Let explicit value take precedence
+ if (field.getRanking().isLiteral())
+ field.setLiteralBoost(100);
+ }
+ }
+
+ /**
+ * Ensures there are field boosts for all literal boosts mentioned in rank profiles.
+ * This is required because boost indices will only be generated by looking
+ * at field boosts
+ */
+ private void addLiteralBoostsToFields(Search search) {
+ Iterator i = matchingRankSettingsIterator(search, RankProfile.RankSetting.Type.LITERALBOOST);
+ while (i.hasNext()) {
+ RankProfile.RankSetting setting = (RankProfile.RankSetting)i.next();
+ SDField field = search.getField(setting.getFieldName());
+ if (field == null) continue;
+ if (field.getLiteralBoost() < 0)
+ field.setLiteralBoost(0);
+ }
+ }
+
+ private void reduceFieldLiteralBoosts(Search search) {
+ for (SDField field : search.allFieldsList()) {
+ if (field.getLiteralBoost()<0) continue;
+ reduceFieldLiteralBoost(field,search);
+ }
+ }
+
+ private void reduceFieldLiteralBoost(SDField field,Search search) {
+ SDField literalField = addField(search, field, "literal",
+ "{ input " + field.getName() + " | tokenize | index " + field.getName() + "_literal; }",
+ "literal-boost");
+ literalField.setWeight(field.getWeight() + field.getLiteralBoost());
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/MakeAliases.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MakeAliases.java
new file mode 100644
index 00000000000..856b0cf3348
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MakeAliases.java
@@ -0,0 +1,60 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Index;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Takes the aliases set on field by parser and sets them on correct Index or Attribute
+ * @author vegardh
+ *
+ */
+public class MakeAliases extends Processor {
+
+ public MakeAliases(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ List<String> usedAliases = new ArrayList<>();
+ for (SDField field : search.allFieldsList()) {
+ for (Map.Entry<String, String> e : field.getAliasToName().entrySet()) {
+ String alias = e.getKey();
+ String name = e.getValue();
+ String errMsg = "For search '"+search.getName()+"': alias '"+alias+"' ";
+ if (search.existsIndex(alias)) {
+ throw new IllegalArgumentException(errMsg+"is illegal since it is the name of an index.");
+ }
+ if (search.getAttribute(alias)!=null) {
+ throw new IllegalArgumentException(errMsg+"is illegal since it is the name of an attribute.");
+ }
+ if (usedAliases.contains(alias)) {
+ throw new IllegalArgumentException(errMsg+"specified more than once.");
+ }
+ usedAliases.add(alias);
+
+ Index index = field.getIndex(name);
+ Attribute attribute = field.getAttributes().get(name);
+ if (index != null) {
+ index.addAlias(alias); // alias will be for index in this case, since it is the one used in a search
+ } else if (attribute != null && !field.doesIndexing()) {
+ attribute.getAliases().add(alias);
+ } else {
+ index = new Index(name);
+ index.addAlias(alias);
+ field.addIndex(index);
+ }
+ }
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/MakeDefaultSummaryTheSuperSet.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MakeDefaultSummaryTheSuperSet.java
new file mode 100644
index 00000000000..a9f013daa98
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MakeDefaultSummaryTheSuperSet.java
@@ -0,0 +1,47 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * <p>All summary fields which are not attributes
+ * must currently be present in the default summary class,
+ * since the default summary class also defines the docsum.dat format.
+ * This processor adds any missing summaries to the default summary.
+ * When that is decoupled from the actual summaries returned, this
+ * processor can be removed. Note: the StreamingSummary also takes advantage of
+ * the fact that default is the superset.</p>
+ *
+ * <p>All other summary logic should work unchanged without this processing step
+ * except that IndexStructureValidator.validateSummaryFields must be changed to
+ * consider all summaries, not just the default, i.e change to
+ * if (search.getSummaryField(expr.getFieldName()) == null)</p>
+ *
+ * <p>This must be done after other summary processors.</p>
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public class MakeDefaultSummaryTheSuperSet extends Processor {
+
+ public MakeDefaultSummaryTheSuperSet(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ DocumentSummary defaultSummary=search.getSummary("default");
+ for (SummaryField summaryField : search.getUniqueNamedSummaryFields().values() ) {
+ if (defaultSummary.getSummaryField(summaryField.getName()) != null) continue;
+ if (summaryField.getTransform() == SummaryTransform.ATTRIBUTE) continue;
+
+ defaultSummary.add(summaryField.clone());
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchConsistency.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchConsistency.java
new file mode 100644
index 00000000000..f21670e6f78
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchConsistency.java
@@ -0,0 +1,67 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.Matching;
+import com.yahoo.searchdefinition.document.Matching.Type;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.indexinglanguage.ExpressionVisitor;
+import com.yahoo.vespa.indexinglanguage.expressions.Expression;
+import com.yahoo.vespa.indexinglanguage.expressions.IndexExpression;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Warn on inconsistent match settings for any index
+ *
+ * @author vegardh
+ */
+public class MatchConsistency extends Processor {
+
+ public MatchConsistency(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ Map<String, Matching.Type> types = new HashMap<>();
+ for (SDField field : search.allFieldsList()) {
+ new MyVisitor(search, field, types).visit(field.getIndexingScript());
+ }
+ }
+
+ void checkMatching(Search search, SDField field, Map<String, Type> types, String indexTo) {
+ Type prevType = types.get(indexTo);
+ if (prevType == null) {
+ types.put(indexTo, field.getMatching().getType());
+ } else if (!field.getMatching().getType().equals(prevType)) {
+ warn(search, field, "The matching type for index '" + indexTo + "' (got " + field.getMatching().getType() +
+ ") is inconsistent with that given for the same index in a previous field (had " +
+ prevType + ").");
+ }
+ }
+
+ private class MyVisitor extends ExpressionVisitor {
+
+ final Search search;
+ final SDField field;
+ final Map<String, Type> types;
+
+ public MyVisitor(Search search, SDField field, Map<String, Type> types) {
+ this.search = search;
+ this.field = field;
+ this.types = types;
+ }
+
+ @Override
+ protected void doVisit(Expression exp) {
+ if (exp instanceof IndexExpression) {
+ checkMatching(search, field, types, ((IndexExpression)exp).getFieldName());
+ }
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchPhaseSettingsValidator.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchPhaseSettingsValidator.java
new file mode 100644
index 00000000000..efceeca0af0
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MatchPhaseSettingsValidator.java
@@ -0,0 +1,92 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfile;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Validates the match phase settings for all registered rank profiles.
+ *
+ * @author <a href="mailto:geirst@yahoo-inc.com">Geir Storli</a>
+ */
+public class MatchPhaseSettingsValidator extends Processor {
+
+ public MatchPhaseSettingsValidator(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (RankProfile rankProfile : rankProfileRegistry.localRankProfiles(search)) {
+ RankProfile.MatchPhaseSettings settings = rankProfile.getMatchPhaseSettings();
+ if (settings != null) {
+ validateMatchPhaseSettings(rankProfile, settings);
+ }
+ }
+ }
+
+ private void validateMatchPhaseSettings(RankProfile rankProfile, RankProfile.MatchPhaseSettings settings) {
+ String attributeName = settings.getAttribute();
+ new AttributeValidator(search.getName(), rankProfile.getName(),
+ search.getAttribute(attributeName), attributeName).validate();
+ }
+
+ public static class AttributeValidator {
+
+ private final String searchName;
+ private final String rankProfileName;
+ protected final Attribute attribute;
+ private final String attributeName;
+
+ public AttributeValidator(String searchName, String rankProfileName, Attribute attribute, String attributeName) {
+ this.searchName = searchName;
+ this.rankProfileName = rankProfileName;
+ this.attribute = attribute;
+ this.attributeName = attributeName;
+ }
+
+ public void validate() {
+ validateThatAttributeExists();
+ validateThatAttributeIsSingleNumeric();
+ validateThatAttributeIsFastSearch();
+ }
+
+ protected void validateThatAttributeExists() {
+ if (attribute == null) {
+ failValidation("does not exists");
+ }
+ }
+
+ protected void validateThatAttributeIsSingleNumeric() {
+ if (!attribute.getCollectionType().equals(Attribute.CollectionType.SINGLE) ||
+ attribute.getType().equals(Attribute.Type.STRING) ||
+ attribute.getType().equals(Attribute.Type.PREDICATE))
+ {
+ failValidation("must be single value numeric, but it is '"
+ + attribute.getDataType().getName() + "'");
+ }
+ }
+
+ protected void validateThatAttributeIsFastSearch() {
+ if (!attribute.isFastSearch()) {
+ failValidation("must be fast-search, but it is not");
+ }
+ }
+
+ protected void failValidation(String what) {
+ throw new IllegalArgumentException(createMessagePrefix() + what);
+ }
+
+ public String getValidationType() { return "match-phase"; }
+
+ private String createMessagePrefix() {
+ return "In search definition '" + searchName +
+ "', rank-profile '" + rankProfileName +
+ "': " + getValidationType() + " attribute '" + attributeName + "' ";
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/MultifieldIndexHarmonizer.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MultifieldIndexHarmonizer.java
new file mode 100644
index 00000000000..61a11fd3a4e
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/MultifieldIndexHarmonizer.java
@@ -0,0 +1,82 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.processing.multifieldresolver.IndexCommandResolver;
+import com.yahoo.searchdefinition.processing.multifieldresolver.RankTypeResolver;
+import com.yahoo.searchdefinition.processing.multifieldresolver.StemmingResolver;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Ensures that there are no conflicting types or field settings
+ * in multifield indices, either by changing settings or by splitting
+ * conflicting fields in multiple ones with different settings.
+ *
+ * @author bratseth
+ */
+public class MultifieldIndexHarmonizer extends Processor {
+
+ /** A map from index names to a List of fields going to that index */
+ private Map<String,List<SDField>> indexToFields=new java.util.HashMap<>();
+
+ public MultifieldIndexHarmonizer(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ populateIndexToFields(search);
+ resolveAllConflicts(search);
+ }
+
+ private void populateIndexToFields(Search search) {
+ for (SDField field : search.allFieldsList() ) {
+ if (!field.doesIndexing()) {
+ continue;
+ }
+ for (Iterator j = field.getFieldNameAsIterator(); j.hasNext();) {
+ String indexName = (String)j.next();
+ addIndexField(indexName, field);
+ }
+ }
+ }
+
+ private void addIndexField(String indexName,SDField field) {
+ List<SDField> fields=indexToFields.get(indexName);
+ if (fields==null) {
+ fields=new java.util.ArrayList<>();
+ indexToFields.put(indexName,fields);
+ }
+ fields.add(field);
+ }
+
+ private void resolveAllConflicts(Search search) {
+ for (Map.Entry<String, List<SDField>> entry : indexToFields.entrySet()) {
+ String indexName = entry.getKey();
+ List<SDField> fields = entry.getValue();
+ if (fields.size() == 1) continue; // It takes two to make a conflict
+ resolveConflicts(indexName, fields, search);
+ }
+ }
+
+ /**
+ * Resolves all conflicts for one index
+ *
+ * @param indexName the name of the index in question
+ * @param fields all the fields indexed to this index
+ * @param search the search definition having this
+ */
+ private void resolveConflicts(String indexName,List<SDField> fields,Search search) {
+ new StemmingResolver(indexName, fields, search, deployLogger).resolve();
+ new IndexCommandResolver(indexName, fields, search, deployLogger).resolve();
+ new RankTypeResolver(indexName, fields, search, deployLogger).resolve();
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/NGramMatch.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/NGramMatch.java
new file mode 100644
index 00000000000..8e5122cc158
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/NGramMatch.java
@@ -0,0 +1,76 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Matching;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Stemming;
+import com.yahoo.vespa.indexinglanguage.expressions.*;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * The implementation of "gram" matching - splitting the incoming text and the queries into
+ * n-grams for matching. This will also validate the gram settings.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class NGramMatch extends Processor {
+
+ public static final int DEFAULT_GRAM_SIZE = 2;
+
+ public NGramMatch(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (field.getMatching().getType().equals(Matching.Type.GRAM))
+ implementGramMatch(search, field);
+ else if (field.getMatching().getGramSize()>=0)
+ throw new IllegalArgumentException("gram-size can only be set when the matching mode is 'gram'");
+ }
+ }
+
+ private void implementGramMatch(Search search, SDField field) {
+ if (field.doesAttributing())
+ throw new IllegalArgumentException("gram matching is not supported with attributes, use 'index' not 'attribute' in indexing");
+
+ int n = field.getMatching().getGramSize();
+ if (n<0)
+ n=DEFAULT_GRAM_SIZE; // not set - use default gram size
+ if (n==0)
+ throw new IllegalArgumentException("Illegal gram size in " + field + ": Must be at least 1");
+ field.getNormalizing().inferCodepoint();
+ field.setStemming(Stemming.NONE); // not compatible with stemming and normalizing
+ field.addQueryCommand("ngram " + n);
+ field.setIndexingScript((ScriptExpression)new MyProvider(search, n).convert(field.getIndexingScript()));
+ }
+
+ private static class MyProvider extends TypedTransformProvider {
+
+ final int ngram;
+
+ MyProvider(Search search, int ngram) {
+ super(NGramExpression.class, search);
+ this.ngram = ngram;
+ }
+
+ @Override
+ protected boolean requiresTransform(Expression exp, DataType fieldType) {
+ return exp instanceof OutputExpression;
+ }
+
+ @Override
+ protected Expression newTransform(DataType fieldType) {
+ Expression exp = new NGramExpression(null, ngram);
+ if (fieldType instanceof CollectionDataType) {
+ exp = new ForEachExpression(exp);
+ }
+ return exp;
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/OptimizeIlscript.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/OptimizeIlscript.java
new file mode 100644
index 00000000000..078e9400ec5
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/OptimizeIlscript.java
@@ -0,0 +1,33 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.indexinglanguage.ExpressionOptimizer;
+import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Run ExpressionOptimizer on all scripts, to get rid of expressions that have no effect.
+ */
+public class OptimizeIlscript extends Processor {
+ public OptimizeIlscript(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ ScriptExpression script = field.getIndexingScript();
+ if (script == null) {
+ continue;
+ }
+ field.setIndexingScript((ScriptExpression)new ExpressionOptimizer().convert(script));
+ if (!field.getIndexingScript().toString().equals(script.toString())) {
+ warn(search, field, "Rewrote ilscript from:\n" + script.toString() + "\nto\n" + field.getIndexingScript().toString());
+ }
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/PredicateProcessor.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/PredicateProcessor.java
new file mode 100644
index 00000000000..a156a98a584
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/PredicateProcessor.java
@@ -0,0 +1,138 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.DataType;
+import com.yahoo.document.datatypes.IntegerFieldValue;
+import com.yahoo.document.datatypes.LongFieldValue;
+import com.yahoo.searchdefinition.Index;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.BooleanIndexDefinition;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
+import com.yahoo.vespa.indexinglanguage.expressions.*;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Validates the predicate fields.
+ *
+ * @author <a href="mailto:lesters@yahoo-inc.com">Lester Solbakken</a>
+ */
+public class PredicateProcessor extends Processor {
+
+ public PredicateProcessor(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (field.getDataType() == DataType.PREDICATE) {
+ if (field.doesIndexing()) {
+ fail(search, field, "Use 'attribute' instead of 'index'. This will require a refeed if you have upgraded.");
+ }
+ if (field.doesAttributing()) {
+ Attribute attribute = field.getAttributes().get(field.getName());
+ for (Index index : field.getIndices().values()) {
+ BooleanIndexDefinition booleanDefinition = index.getBooleanIndexDefiniton();
+ if (booleanDefinition == null || !booleanDefinition.hasArity()) {
+ fail(search, field, "Missing arity value in predicate field.");
+ }
+ if (booleanDefinition.getArity() < 2) {
+ fail(search, field, "Invalid arity value in predicate field, must be greater than 1.");
+ }
+ double threshold = booleanDefinition.getDensePostingListThreshold();
+ if (threshold <= 0 || threshold > 1) {
+ fail(search, field, "Invalid dense-posting-list-threshold value in predicate field. " +
+ "Value must be in range (0..1].");
+ }
+
+ attribute.setArity(booleanDefinition.getArity());
+ attribute.setLowerBound(booleanDefinition.getLowerBound());
+ attribute.setUpperBound(booleanDefinition.getUpperBound());
+
+ attribute.setDensePostingListThreshold(threshold);
+ addPredicateOptimizationIlScript(field, booleanDefinition);
+ }
+ DocumentSummary summary = search.getSummary("attributeprefetch");
+ if (summary != null) {
+ summary.remove(attribute.getName());
+ }
+ for (SummaryField summaryField : search.getSummaryFields(field).values()) {
+ summaryField.setTransform(SummaryTransform.NONE);
+ }
+ }
+ } else if (field.getDataType().getPrimitiveType() == DataType.PREDICATE) {
+ fail(search, field, "Collections of predicates are not allowed.");
+ } else if (field.getDataType() == DataType.RAW && field.doesIndexing()) {
+ fail(search, field, "Indexing of RAW fields are not supported. If you are using RAW fields for boolean search, use predicate data type instead.");
+ } else {
+ // if field is not a predicate, disallow predicate-related index parameters
+ for (Index index : field.getIndices().values()) {
+ if (index.getBooleanIndexDefiniton() != null) {
+ BooleanIndexDefinition def = index.getBooleanIndexDefiniton();
+ if (def.hasArity()) {
+ fail(search, field, "Arity parameter is used only for predicate type fields.");
+ } else if (def.hasLowerBound() || def.hasUpperBound()) {
+ fail(search, field, "Parameters lower-bound and upper-bound are used only for predicate type fields.");
+ } else if (def.hasDensePostingListThreshold()) {
+ fail(search, field, "Parameter dense-posting-list-threshold is used only for predicate type fields.");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void addPredicateOptimizationIlScript(SDField field, BooleanIndexDefinition booleanIndexDefiniton) {
+ Expression script = field.getIndexingScript();
+ if (script == null) {
+ return;
+ }
+ script = new StatementExpression(makeSetPredicateVariablesScript(booleanIndexDefiniton), script);
+
+ ExpressionConverter converter = new PredicateOutputTransformer(search);
+ field.setIndexingScript(new ScriptExpression((StatementExpression)converter.convert(script)));
+ }
+
+ private Expression makeSetPredicateVariablesScript(BooleanIndexDefinition options) {
+ List<Expression> expressions = new ArrayList<>();
+ expressions.add(new SetValueExpression(new IntegerFieldValue(options.getArity())));
+ expressions.add(new SetVarExpression("arity"));
+ if (options.hasLowerBound()) {
+ expressions.add(new SetValueExpression(new LongFieldValue(options.getLowerBound())));
+ expressions.add(new SetVarExpression("lower_bound"));
+ }
+ if (options.hasUpperBound()) {
+ expressions.add(new SetValueExpression(new LongFieldValue(options.getUpperBound())));
+ expressions.add(new SetVarExpression("upper_bound"));
+ }
+ return new StatementExpression(expressions);
+ }
+
+ private static class PredicateOutputTransformer extends TypedTransformProvider {
+
+ PredicateOutputTransformer(Search search) {
+ super(OptimizePredicateExpression.class, search);
+ }
+
+ @Override
+ protected boolean requiresTransform(Expression exp, DataType fieldType) {
+ return exp instanceof OutputExpression && fieldType == DataType.PREDICATE;
+ }
+
+ @Override
+ protected Expression newTransform(DataType fieldType) {
+ return new OptimizePredicateExpression();
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java
new file mode 100644
index 00000000000..74f359602bd
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processing.java
@@ -0,0 +1,84 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.processing.multifieldresolver.RankProfileTypeSettingsProcessor;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Executor of processors. This defines the right order of processor execution.
+ *
+ * @author bratseth
+ */
+public class Processing {
+
+ /**
+ * Runs all search processors on the given {@link Search} object. These will modify the search object, <b>possibly
+ * exchanging it with another</b>, as well as its document types.
+ *
+ * @param search The search to process.
+ * @param deployLogger The log to log messages and warnings for application deployment to
+ * @param rankProfileRegistry a {@link com.yahoo.searchdefinition.RankProfileRegistry}
+ * @param queryProfiles The query profiles contained in the application this search is part of.
+ */
+ public static void process(Search search,
+ DeployLogger deployLogger,
+ RankProfileRegistry rankProfileRegistry,
+ QueryProfiles queryProfiles) {
+ search.process();
+ new UrlFieldValidator(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new BuiltInFieldSets(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new SearchMustHaveDocument(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new ReservedDocumentNames(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new IndexFieldNames(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new IntegerIndex2Attribute(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new MakeAliases(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new SetLanguage(search, deployLogger, rankProfileRegistry, queryProfiles).process(); // Needs to come before UriHack, see ticket 6405470
+ new UriHack(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new LiteralBoost(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new IndexTo2FieldSet(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new TagType(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new IndexingInputs(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new OptimizeIlscript(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new ValidateFieldWithIndexSettingsCreatesIndex(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new AttributesImplicitWord(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new CreatePositionZCurve(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new WordMatch(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new DeprecateAttributePrefetch(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new ImplicitSummaries(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new ImplicitSummaryFields(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new SummaryConsistency(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new SummaryNamesFieldCollisions(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new SummaryFieldsMustHaveValidSource(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new MakeDefaultSummaryTheSuperSet(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new Bolding(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new AttributeProperties(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new SetRankTypeEmptyOnFilters(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new IndexSettingsNonFieldNames(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new SummaryDynamicStructsArrays(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new StringSettingsOnNonStringFields(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new IndexingOutputs(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new ExactMatch(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new NGramMatch(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new TextMatch(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new MultifieldIndexHarmonizer(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new FilterFieldNames(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new MatchConsistency(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new ValidateFieldTypes(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new DisallowComplexMapAndWsetKeyTypes(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new SortingSettings(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new FieldSetValidity(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new AddExtraFieldsToDocument(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new PredicateProcessor(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new MatchPhaseSettingsValidator(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new DiversitySettingsValidator(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new TensorFieldProcessor(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new RankProfileTypeSettingsProcessor(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+
+ // These two should be last.
+ new IndexingValidation(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ new IndexingValues(search, deployLogger, rankProfileRegistry, queryProfiles).process();
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processor.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processor.java
new file mode 100644
index 00000000000..a3e0484b2fd
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/Processor.java
@@ -0,0 +1,131 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.Index;
+import com.yahoo.searchdefinition.RankProfile;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.RankType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Stemming;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+
+/**
+ * Abstract superclass of all search definition processors.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon S Bratseth</a>
+ */
+public abstract class Processor {
+
+ protected final Search search;
+ protected DeployLogger deployLogger;
+ protected final RankProfileRegistry rankProfileRegistry;
+ protected final QueryProfiles queryProfiles;
+
+ /**
+ * Base constructor
+ * @param search the search to process
+ * @param deployLogger Logger du use when logging deploy output.
+ * @param rankProfileRegistry Registry with all rank profiles, used for lookup and insertion.
+ * @param queryProfiles The query profiles contained in the application this search is part of.
+ */
+ public Processor(Search search, DeployLogger deployLogger,
+ RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ this.search = search;
+ this.deployLogger = deployLogger;
+ this.rankProfileRegistry = rankProfileRegistry;
+ this.queryProfiles = queryProfiles;
+ }
+
+ /**
+ * Processes the input search definition by <b>modifying</b> the input search and its documents, and returns the
+ * input search definition.
+ */
+ public abstract void process();
+
+ /**
+ * Convenience method for adding a no-strings-attached implementation field for a regular field
+ *
+ * @param search the search definition in question
+ * @param field the field to add an implementation field for
+ * @param suffix the suffix of the added implementation field (without the underscore)
+ * @param indexing the indexing statement of the field
+ * @param queryCommand the query command of the original field, or null if none
+ * @return the implementation field which is added to the search
+ */
+ protected SDField addField(Search search, SDField field, String suffix, String indexing, String queryCommand) {
+ SDField implementationField = search.getField(field.getName() + "_" + suffix);
+ if (implementationField != null) {
+ deployLogger.log(Level.WARNING, "Implementation field " + implementationField + " added twice");
+ } else {
+ implementationField = new SDField(search.getDocument(), field.getName() + "_" + suffix, DataType.STRING);
+ }
+ implementationField.setRankType(RankType.EMPTY);
+ implementationField.setStemming(Stemming.NONE);
+ implementationField.getNormalizing().inferCodepoint();
+ implementationField.parseIndexingScript(indexing);
+ for (Iterator i = field.getFieldNameAsIterator(); i.hasNext();) {
+ String indexName = (String)i.next();
+ String implementationIndexName = indexName + "_" + suffix;
+ Index implementationIndex = new Index(implementationIndexName);
+ search.addIndex(implementationIndex);
+ }
+ if (queryCommand != null) {
+ field.addQueryCommand(queryCommand);
+ }
+ search.addExtraField(implementationField);
+ search.fieldSets().addBuiltInFieldSetItem(BuiltInFieldSets.INTERNAL_FIELDSET_NAME, implementationField.getName());
+ return implementationField;
+ }
+
+ /**
+ * Returns an iterator of all the rank settings with given type in all the rank profiles in this search
+ * definition.
+ */
+ protected Iterator<RankProfile.RankSetting> matchingRankSettingsIterator(
+ Search search, RankProfile.RankSetting.Type type)
+ {
+ List<RankProfile.RankSetting> someRankSettings = new java.util.ArrayList<>();
+
+ for (RankProfile profile : rankProfileRegistry.localRankProfiles(search)) {
+ for (Iterator j = profile.declaredRankSettingIterator(); j.hasNext(); ) {
+ RankProfile.RankSetting setting = (RankProfile.RankSetting)j.next();
+ if (setting.getType().equals(type)) {
+ someRankSettings.add(setting);
+ }
+ }
+ }
+ return someRankSettings.iterator();
+ }
+
+ protected String formatError(String searchName, String fieldName, String msg) {
+ return "For search '" + searchName + "', field '" + fieldName + "': " + msg;
+ }
+
+ protected RuntimeException newProcessException(String searchName, String fieldName, String msg) {
+ return new IllegalArgumentException(formatError(searchName, fieldName, msg));
+ }
+
+ protected RuntimeException newProcessException(Search search, Field field, String msg) {
+ return newProcessException(search.getName(), field.getName(), msg);
+ }
+
+ public void fail(Search search, Field field, String msg) {
+ throw newProcessException(search, field, msg);
+ }
+
+ protected void warn(String searchName, String fieldName, String msg) {
+ String fullMsg = formatError(searchName, fieldName, msg);
+ deployLogger.log(Level.WARNING, fullMsg);
+ }
+
+ protected void warn(Search search, Field field, String msg) {
+ warn(search.getName(), field.getName(), msg);
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ReservedDocumentNames.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ReservedDocumentNames.java
new file mode 100644
index 00000000000..74bf2d35bac
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ReservedDocumentNames.java
@@ -0,0 +1,37 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.SDDocumentType;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ReservedDocumentNames extends Processor {
+
+ private static final Set<String> RESERVED_NAMES = new HashSet<>();
+ static {
+ for (SDDocumentType dataType : SDDocumentType.VESPA_DOCUMENT.getTypes()) {
+ RESERVED_NAMES.add(dataType.getName());
+ }
+ }
+
+ public ReservedDocumentNames(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ String docName = search.getDocument().getName();
+ if (RESERVED_NAMES.contains(docName)) {
+ throw new IllegalArgumentException("For search '" + search.getName() + "': Document name '" + docName +
+ "' is reserved.");
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/SearchMustHaveDocument.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SearchMustHaveDocument.java
new file mode 100644
index 00000000000..5b45aecc257
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SearchMustHaveDocument.java
@@ -0,0 +1,28 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * A search must have a document definition of the same name inside of it, otherwise crashes may occur as late as
+ * during feeding
+ * @author vegardh
+ *
+ */
+public class SearchMustHaveDocument extends Processor {
+
+ public SearchMustHaveDocument(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ if (search.getDocument()==null) {
+ throw new IllegalArgumentException("For search '" + search.getName() + "': A search specification must have an equally named document inside of it.");
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/SetLanguage.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SetLanguage.java
new file mode 100644
index 00000000000..764bb1602a7
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SetLanguage.java
@@ -0,0 +1,55 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.DataType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.indexinglanguage.expressions.SetLanguageExpression;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Check that no text field appears before a field that sets language.
+ *
+ * @author <a href="mailto:gunnarga@yahoo-inc.com">Gunnar Gauslaa Bergem</a>
+ */
+public class SetLanguage extends Processor {
+
+ public SetLanguage(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ List<String> textFieldsWithoutLanguage = new ArrayList<>();
+
+ for (SDField field : search.allFieldsList()) {
+ if (fieldMustComeAfterLanguageSettingField(field)) {
+ textFieldsWithoutLanguage.add(field.getName());
+ }
+ if (field.containsExpression(SetLanguageExpression.class) && !textFieldsWithoutLanguage.isEmpty()) {
+ StringBuffer fieldString = new StringBuffer();
+ for (String fieldName : textFieldsWithoutLanguage) {
+ fieldString.append(fieldName).append(" ");
+ }
+ warn(search, field, "Field '" + field.getName() + "' sets the language for this document, " +
+ "and should be defined as the first field in the searchdefinition. If you have both header and body fields, this field "+
+ "should be header, if you require it to affect subsequent header fields and/or any body fields. " +
+ "Preceding text fields that will not have their language set: " +
+ fieldString.toString() +
+ " (This warning is omitted for any subsequent fields that also do set_language.)");
+ return;
+ }
+ }
+ }
+
+ private boolean fieldMustComeAfterLanguageSettingField(SDField field) {
+ return (!field.containsExpression(SetLanguageExpression.class) &&
+ (field.getDataType() == DataType.STRING));
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/SetRankTypeEmptyOnFilters.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SetRankTypeEmptyOnFilters.java
new file mode 100644
index 00000000000..bc676b64a71
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SetRankTypeEmptyOnFilters.java
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.RankType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * All rank: filter fields should have rank type empty.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class SetRankTypeEmptyOnFilters extends Processor {
+
+ public SetRankTypeEmptyOnFilters(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (field.getRanking().isFilter()) {
+ field.setRankType(RankType.EMPTY);
+ }
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/SortingSettings.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SortingSettings.java
new file mode 100644
index 00000000000..6e77f48b4a7
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SortingSettings.java
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Sorting;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Validate conflicting settings for sorting
+ * @author vegardh
+ *
+ */
+public class SortingSettings extends Processor {
+
+ public SortingSettings(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ for (Attribute attribute : field.getAttributes().values()) {
+ Sorting sorting = attribute.getSorting();
+ if (sorting.getFunction()!=Sorting.Function.UCA) {
+ if (sorting.getStrength()!=null && sorting.getStrength()!=Sorting.Strength.PRIMARY) {
+ warn(search, field, "Sort strength only works for sort function 'uca'.");
+ }
+ if (sorting.getLocale()!=null && !"".equals(sorting.getLocale())) {
+ warn(search, field, "Sort locale only works for sort function 'uca'.");
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/StringSettingsOnNonStringFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/StringSettingsOnNonStringFields.java
new file mode 100644
index 00000000000..48c9af5556a
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/StringSettingsOnNonStringFields.java
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.CollectionDataType;
+import com.yahoo.document.NumericDataType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+public class StringSettingsOnNonStringFields extends Processor {
+
+ public StringSettingsOnNonStringFields(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (!doCheck(field)) continue;
+ if (field.getMatching().isTypeUserSet()) {
+ warn(search, field, "Matching type "+field.getMatching().getType()+" is only allowed for string fields.");
+ }
+ if (field.getRanking().isLiteral()) {
+ warn(search, field, "Rank type literal only applies to string fields");
+ }
+ }
+ }
+
+ private boolean doCheck(SDField field) {
+ if (field.getDataType() instanceof NumericDataType) return true;
+ if (field.getDataType() instanceof CollectionDataType) {
+ if (((CollectionDataType)field.getDataType()).getNestedType() instanceof NumericDataType) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryConsistency.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryConsistency.java
new file mode 100644
index 00000000000..20eeb5f0810
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryConsistency.java
@@ -0,0 +1,102 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.document.WeightedSetDataType;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Ensure that summary field transforms for fields having the same name
+ * are consistent across summary classes
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class SummaryConsistency extends Processor {
+
+ public SummaryConsistency(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (DocumentSummary summary : search.getSummaries().values()) {
+ if (summary.getName().equals("default")) continue;
+ for (SummaryField summaryField : summary.getSummaryFields() ) {
+ assertConsistency(summaryField,search);
+ makeAttributeTransformIfAppropriate(summaryField,search);
+ }
+ }
+ }
+
+ /** If the source is an attribute, make this use the attribute transform */
+ private void makeAttributeTransformIfAppropriate(SummaryField summaryField,Search search) {
+ if (summaryField.getTransform()!=SummaryTransform.NONE) return;
+ Attribute attribute=search.getAttribute(summaryField.getSingleSource());
+ if (attribute==null) return;
+ summaryField.setTransform(SummaryTransform.ATTRIBUTE);
+ }
+
+ private void assertConsistency(SummaryField summaryField,Search search) {
+ SummaryField existingDefault=search.getSummary("default").getSummaryField(summaryField.getName()); // Compare to default
+ if (existingDefault!=null) {
+ assertConsistentTypes(existingDefault,summaryField);
+ makeConsistentWithDefaultOrThrow(existingDefault,summaryField);
+ }
+ else {
+ // If no default, compare to whichever definition of the field
+ SummaryField existing=search.getExplicitSummaryField(summaryField.getName());
+ if (existing==null) return;
+ assertConsistentTypes(existing,summaryField);
+ makeConsistentOrThrow(existing,summaryField,search);
+ }
+ }
+
+ private void assertConsistentTypes(SummaryField field1,SummaryField field2) {
+ if (field1.getDataType() instanceof WeightedSetDataType && field2.getDataType() instanceof WeightedSetDataType &&
+ ((WeightedSetDataType)field1.getDataType()).getNestedType().equals(((WeightedSetDataType)field2.getDataType()).getNestedType()))
+ return; // Disregard create-if-nonexistent and create-if-zero distinction
+ if ( ! field1.getDataType().equals(field2.getDataType()))
+ throw new IllegalArgumentException(field1.toLocateString() + " is inconsistent with " + field2.toLocateString() + ": All declarations of the same summary field must have the same type");
+ }
+
+ private void makeConsistentOrThrow(SummaryField field1, SummaryField field2,Search search) {
+ if (field2.getTransform()==SummaryTransform.ATTRIBUTE && field1.getTransform()==SummaryTransform.NONE) {
+ Attribute attribute=search.getAttribute(field1.getName());
+ if (attribute != null) {
+ field1.setTransform(SummaryTransform.ATTRIBUTE);
+ }
+ }
+
+ if (field2.getTransform().equals(SummaryTransform.NONE)) {
+ field2.setTransform(field1.getTransform());
+ }
+ else { // New field sets an explicit transform - must be the same
+ assertEqualTransform(field1,field2);
+ }
+ }
+ private void makeConsistentWithDefaultOrThrow(SummaryField defaultField,SummaryField newField) {
+ if (newField.getTransform().equals(SummaryTransform.NONE)) {
+ newField.setTransform(defaultField.getTransform());
+ }
+ else { // New field sets an explicit transform - must be the same
+ assertEqualTransform(defaultField,newField);
+ }
+ }
+
+
+ private void assertEqualTransform(SummaryField field1,SummaryField field2) {
+ if (!field2.getTransform().equals(field1.getTransform())) {
+ throw new IllegalArgumentException("Conflicting summary transforms. " + field2 +" is already defined as " +
+ field1 + ". A field with the same name " +
+ "can not have different transforms in different summary classes");
+ }
+ }
+
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryDynamicStructsArrays.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryDynamicStructsArrays.java
new file mode 100644
index 00000000000..f97d5345cab
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryDynamicStructsArrays.java
@@ -0,0 +1,45 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Fail if:
+ * An SD field explicitly says summary:dynamic , but the field is wset, array or struct.
+ * If there is an explicitly defined summary class, saying dynamic in one of its summary
+ * fields is always legal.
+ * @author vegardh
+ *
+ */
+public class SummaryDynamicStructsArrays extends Processor {
+
+ public SummaryDynamicStructsArrays(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ DataType type = field.getDataType();
+ if (type instanceof ArrayDataType || type instanceof WeightedSetDataType
+ || type instanceof StructDataType) {
+ for (SummaryField sField : field.getSummaryFields()) {
+ if (sField.getTransform().equals(SummaryTransform.DYNAMICTEASER)) {
+ throw new IllegalArgumentException("For field '"+field.getName()+"': dynamic summary is illegal " +
+ "for fields of type struct, array or weighted set. Use an explicit summary class with explicit summary fields sourcing from" +
+ " the array/struct/weighted set.");
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryFieldsMustHaveValidSource.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryFieldsMustHaveValidSource.java
new file mode 100644
index 00000000000..0d76ada0d52
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryFieldsMustHaveValidSource.java
@@ -0,0 +1,73 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryTransform;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Verifies that the source fields actually refers to a valid field.
+ *
+ * @author balder
+ *
+ */
+public class SummaryFieldsMustHaveValidSource extends Processor {
+ SummaryFieldsMustHaveValidSource(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+ @Override
+ public void process() {
+ for (DocumentSummary summary : search.getSummaries().values()) {
+ for (SummaryField summaryField : summary.getSummaryFields()) {
+ if (summaryField.getSources().isEmpty()) {
+ if ((summaryField.getTransform() != SummaryTransform.RANKFEATURES) &&
+ (summaryField.getTransform() != SummaryTransform.SUMMARYFEATURES))
+ {
+ verifySource(summaryField.getName(), summaryField, summary);
+ }
+ } else if (summaryField.getSourceCount() == 1) {
+ verifySource(summaryField.getSingleSource(), summaryField, summary);
+ } else {
+ for (SummaryField.Source source : summaryField.getSources()) {
+ if ( ! source.getName().equals(summaryField.getName()) ) {
+ verifySource(source.getName(), summaryField, summary);
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ private boolean isValid(String source, SummaryField summaryField, DocumentSummary summary) {
+ return isDocumentField(source) ||
+ (isNotInThisSummaryClass(summary, source) && isSummaryField(source)) ||
+ (isInThisSummaryClass(summary, source) && !source.equals(summaryField.getName())) ||
+ ("documentid".equals(source));
+ }
+
+ private void verifySource(String source, SummaryField summaryField, DocumentSummary summary) {
+ if ( ! isValid(source, summaryField, summary) ) {
+ throw new IllegalArgumentException("For search '" + search.getName() + "', summary class '" + summary.getName() + "'," +
+ " summary field '" + summaryField.getName() + "': there is no valid source '" + source + "'.");
+ }
+ }
+
+ private boolean isNotInThisSummaryClass(DocumentSummary summary, String name) {
+ return summary.getSummaryField(name) == null;
+ }
+ private boolean isInThisSummaryClass(DocumentSummary summary, String name) {
+ return summary.getSummaryField(name) != null;
+ }
+ private boolean isDocumentField(String name) {
+ return search.getField(name) != null;
+ }
+
+ private boolean isSummaryField(String name) {
+ return search.getSummaryField(name) != null;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryNamesFieldCollisions.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryNamesFieldCollisions.java
new file mode 100644
index 00000000000..8d99611d697
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/SummaryNamesFieldCollisions.java
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.yahoo.collections.Pair;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.documentmodel.SummaryField.Source;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Verifies that equally named summary fields in different summary classes don't use different fields for source.
+ * The summarymap config doesn't model this.
+ *
+ * @author vegardh
+ *
+ */
+public class SummaryNamesFieldCollisions extends Processor {
+
+ public SummaryNamesFieldCollisions(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ Map<String, Pair<String, String>> fieldToClassAndSource = new HashMap<>();
+ for (DocumentSummary summary : search.getSummaries().values()) {
+ if ("default".equals(summary.getName())) continue;
+ for (SummaryField summaryField : summary.getSummaryFields() ) {
+ if (summaryField.isImplicit()) continue;
+ Pair<String, String> prevClassAndSource = fieldToClassAndSource.get(summaryField.getName());
+ for (Source source : summaryField.getSources()) {
+ if (prevClassAndSource!=null) {
+ String prevClass = prevClassAndSource.getFirst();
+ String prevSource = prevClassAndSource.getSecond();
+ if (!prevClass.equals(summary.getName())) {
+ if (!prevSource.equals(source.getName())) {
+ throw new IllegalArgumentException("For search '"+search.getName()+"', summary class '"+summary.getName()+"'," +
+ " summary field '"+summaryField.getName()+"':" +
+ " Can not use source '"+source.getName()+"' for this summary field, an equally named field in summary class '" +
+ prevClass + "' uses a different source: '"+prevSource+"'.");
+ }
+ }
+ } else {
+ fieldToClassAndSource.put(summaryField.getName(), new Pair<>(summary.getName(), source.getName()));
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/TagType.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TagType.java
new file mode 100644
index 00000000000..3e211f4f632
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TagType.java
@@ -0,0 +1,45 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Matching;
+import com.yahoo.searchdefinition.document.RankType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * The implementation of the tag datatype
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class TagType extends Processor {
+
+ public TagType(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (field.getDataType() instanceof WeightedSetDataType && ((WeightedSetDataType)field.getDataType()).isTag()) {
+ implementTagType(field);
+ }
+ }
+ }
+
+ private void implementTagType(SDField field) {
+ field.setDataType(DataType.getWeightedSet(DataType.STRING,true,true));
+ // Don't set matching and ranking if this field is not attribute nor index
+ if (!field.doesIndexing() && !field.doesAttributing()) return;
+ Matching m = field.getMatching();
+ if (!m.isTypeUserSet()) {
+ m.setType(Matching.Type.WORD);
+ }
+ if (field.getRankType()==null || field.getRankType()== RankType.DEFAULT)
+ field.setRankType((RankType.TAGS));
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java
new file mode 100644
index 00000000000..eb4efbe377f
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java
@@ -0,0 +1,63 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.document.DataType;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Class that processes and validates tensor fields.
+ *
+ * @author <a href="geirst@yahoo-inc.com">Geir Storli</a>
+ */
+public class TensorFieldProcessor extends Processor {
+
+ public TensorFieldProcessor(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (field.getDataType() == DataType.TENSOR) {
+ warnUseOfTensorFieldAsAttribute(field);
+ validateIndexingScripsForTensorField(field);
+ validateAttributeSettingForTensorField(field);
+ } else {
+ validateDataTypeForField(field);
+ }
+ }
+ }
+
+ private void warnUseOfTensorFieldAsAttribute(SDField field) {
+ if (field.doesAttributing()) {
+ // TODO (geirst): Remove when no longer beta
+ warn(search, field, "An attribute of type 'tensor' is currently beta, and re-feeding data between Vespa versions might be required.");
+ }
+ }
+
+ private void validateIndexingScripsForTensorField(SDField field) {
+ if (field.doesIndexing()) {
+ fail(search, field, "A field of type 'tensor' cannot be specified as an 'index' field.");
+ }
+ }
+
+ private void validateAttributeSettingForTensorField(SDField field) {
+ if (field.doesAttributing()) {
+ Attribute attribute = field.getAttributes().get(field.getName());
+ if (attribute != null && attribute.isFastSearch()) {
+ fail(search, field, "An attribute of type 'tensor' cannot be 'fast-search'.");
+ }
+ }
+ }
+
+ private void validateDataTypeForField(SDField field) {
+ if (field.getDataType().getPrimitiveType() == DataType.TENSOR) {
+ fail(search, field, "A field with collection type of tensor is not supported. Use simple type 'tensor' instead.");
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/TextMatch.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TextMatch.java
new file mode 100644
index 00000000000..21723639ece
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TextMatch.java
@@ -0,0 +1,122 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.CollectionDataType;
+import com.yahoo.document.DataType;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Matching;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Stemming;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
+import com.yahoo.vespa.indexinglanguage.ExpressionVisitor;
+import com.yahoo.vespa.indexinglanguage.expressions.Expression;
+import com.yahoo.vespa.indexinglanguage.expressions.ForEachExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.IndexExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.OutputExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.SummaryExpression;
+import com.yahoo.vespa.indexinglanguage.expressions.TokenizeExpression;
+import com.yahoo.vespa.indexinglanguage.linguistics.AnnotatorConfig;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TextMatch extends Processor {
+
+ public TextMatch(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (field.getMatching().getType() != Matching.Type.TEXT) {
+ continue;
+ }
+ ScriptExpression script = field.getIndexingScript();
+ if (script == null) {
+ continue;
+ }
+ DataType fieldType = field.getDataType();
+ if (fieldType instanceof CollectionDataType) {
+ fieldType = ((CollectionDataType)fieldType).getNestedType();
+ }
+ if (fieldType != DataType.STRING) {
+ continue;
+ }
+ Set<String> dynamicSummary = new TreeSet<>();
+ Set<String> staticSummary = new TreeSet<>();
+ new IndexingOutputs(search, deployLogger, rankProfileRegistry, queryProfiles).findSummaryTo(search, field, dynamicSummary, staticSummary);
+ MyVisitor visitor = new MyVisitor(dynamicSummary);
+ visitor.visit(script);
+ if (!visitor.requiresTokenize) {
+ continue;
+ }
+ ExpressionConverter converter = new MyStringTokenizer(search, findAnnotatorConfig(search, field));
+ field.setIndexingScript((ScriptExpression)converter.convert(script));
+ }
+ }
+
+ private static AnnotatorConfig findAnnotatorConfig(Search search, SDField field) {
+ AnnotatorConfig ret = new AnnotatorConfig();
+ Stemming activeStemming = field.getStemming();
+ if (activeStemming == null) {
+ activeStemming = search.getStemming();
+ }
+ ret.setStemMode(activeStemming.toStemMode());
+ ret.setRemoveAccents(field.getNormalizing().doRemoveAccents());
+ return ret;
+ }
+
+ private static class MyVisitor extends ExpressionVisitor {
+
+ final Set<String> dynamicSummaryFields;
+ boolean requiresTokenize = false;
+
+ MyVisitor(Set<String> dynamicSummaryFields) {
+ this.dynamicSummaryFields = dynamicSummaryFields;
+ }
+
+ @Override
+ protected void doVisit(Expression exp) {
+ if (exp instanceof IndexExpression) {
+ requiresTokenize = true;
+ }
+ if (exp instanceof SummaryExpression &&
+ dynamicSummaryFields.contains(((SummaryExpression)exp).getFieldName()))
+ {
+ requiresTokenize = true;
+ }
+ }
+ }
+
+ private static class MyStringTokenizer extends TypedTransformProvider {
+
+ final AnnotatorConfig annotatorCfg;
+
+ MyStringTokenizer(Search search, AnnotatorConfig annotatorCfg) {
+ super(TokenizeExpression.class, search);
+ this.annotatorCfg = annotatorCfg;
+ }
+
+ @Override
+ protected boolean requiresTransform(Expression exp, DataType fieldType) {
+ return exp instanceof OutputExpression;
+ }
+
+ @Override
+ protected Expression newTransform(DataType fieldType) {
+ Expression exp = new TokenizeExpression(null, annotatorCfg);
+ if (fieldType instanceof CollectionDataType) {
+ exp = new ForEachExpression(exp);
+ }
+ return exp;
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/TypedTransformProvider.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TypedTransformProvider.java
new file mode 100644
index 00000000000..0fa9cbfa05f
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TypedTransformProvider.java
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.document.DataType;
+import com.yahoo.document.Field;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.vespa.indexinglanguage.ValueTransformProvider;
+import com.yahoo.vespa.indexinglanguage.expressions.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class TypedTransformProvider extends ValueTransformProvider {
+
+ private final Search search;
+ private DataType fieldType;
+
+ TypedTransformProvider(Class<? extends Expression> transformClass, Search search) {
+ super(transformClass);
+ this.search = search;
+ }
+
+ @Override
+ protected final boolean requiresTransform(Expression exp) {
+ if (exp instanceof OutputExpression) {
+ String fieldName = ((OutputExpression)exp).getFieldName();
+ if (exp instanceof AttributeExpression) {
+ Attribute attribute = search.getAttribute(fieldName);
+ if (attribute == null) {
+ throw new IllegalArgumentException("Attribute '" + fieldName + "' not found.");
+ }
+ fieldType = attribute.getDataType();
+ } else if (exp instanceof IndexExpression) {
+ Field field = search.getField(fieldName);
+ if (field == null) {
+ throw new IllegalArgumentException("Index field '" + fieldName + "' not found.");
+ }
+ fieldType = field.getDataType();
+ } else if (exp instanceof SummaryExpression) {
+ Field field = search.getSummaryField(fieldName);
+ if (field == null) {
+ throw new IllegalArgumentException("Summary field '" + fieldName + "' not found.");
+ }
+ fieldType = field.getDataType();
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ return requiresTransform(exp, fieldType);
+ }
+
+ @Override
+ protected final Expression newTransform() {
+ return newTransform(fieldType);
+ }
+
+ protected abstract boolean requiresTransform(Expression exp, DataType fieldType);
+
+ protected abstract Expression newTransform(DataType fieldType);
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/UriHack.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/UriHack.java
new file mode 100644
index 00000000000..8f9b88b0268
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/UriHack.java
@@ -0,0 +1,67 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.document.*;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Stemming;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author balder
+ */
+public class UriHack extends Processor {
+ private static final List<String> URL_SUFFIX =
+ Arrays.asList("scheme", "host", "port", "path", "query", "fragment", "hostname");
+ public UriHack(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (field.doesIndexing()) {
+ DataType fieldType = field.getDataType();
+ if (fieldType instanceof CollectionDataType) {
+ fieldType = ((CollectionDataType)fieldType).getNestedType();
+ }
+ if (fieldType == DataType.URI) {
+ processField(search, field);
+ }
+ }
+ }
+ }
+
+ private void processField(Search search, SDField uriField) {
+ String uriName = uriField.getName();
+ uriField.setStemming(Stemming.NONE);
+ DataType generatedType = DataType.STRING;
+ if (uriField.getDataType() instanceof ArrayDataType) {
+ generatedType = new ArrayDataType(DataType.STRING);
+ } else if (uriField.getDataType() instanceof WeightedSetDataType) {
+ WeightedSetDataType wdt = (WeightedSetDataType) uriField.getDataType();
+ generatedType = new WeightedSetDataType(DataType.STRING, wdt.createIfNonExistent(), wdt.removeIfZero());
+ }
+ for (String suffix : URL_SUFFIX) {
+ String partName = uriName + "." + suffix;
+ // I wonder if this is explicit in qrs or implicit in backend?
+ // search.addFieldSetItem(uriName, partName);
+ SDField partField = new SDField(partName, generatedType, true);
+ partField.setIndexStructureField(uriField.doesIndexing());
+ partField.setRankType(uriField.getRankType());
+ partField.setStemming(Stemming.NONE);
+ partField.getNormalizing().inferLowercase();
+ if (uriField.getIndex(suffix) != null) {
+ partField.addIndex(uriField.getIndex(suffix));
+ }
+ search.addExtraField(partField);
+ search.fieldSets().addBuiltInFieldSetItem(BuiltInFieldSets.INTERNAL_FIELDSET_NAME, partField.getName());
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/UrlFieldValidator.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/UrlFieldValidator.java
new file mode 100644
index 00000000000..b01a82b0131
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/UrlFieldValidator.java
@@ -0,0 +1,32 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.document.DataType;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * @author bratseth
+ */
+public class UrlFieldValidator extends Processor {
+
+ public UrlFieldValidator(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if ( ! field.getDataType().equals(DataType.URI)) continue;
+
+ if (field.doesAttributing())
+ throw new IllegalArgumentException("Error in " + field + " in " + search + ": " +
+ "uri type fields cannot be attributes");
+ }
+
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ValidateFieldTypes.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ValidateFieldTypes.java
new file mode 100644
index 00000000000..38c7338be3c
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ValidateFieldTypes.java
@@ -0,0 +1,69 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.document.DataType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.documentmodel.DocumentSummary;
+import com.yahoo.vespa.documentmodel.SummaryField;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This Processor checks to make sure all fields with the same name have the same {@link DataType}. This check
+ * explicitly disregards whether a field is an index field, an attribute or a summary field. This is a requirement if we
+ * hope to move to a model where index fields, attributes and summary fields share a common field class.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ValidateFieldTypes extends Processor {
+
+ public ValidateFieldTypes(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ String searchName = search.getName();
+ Map<String, DataType> fieldTypes = new HashMap<>();
+ for (SDField field : search.allFieldsList()) {
+ checkFieldType(searchName, "index field", field.getName(), field.getDataType(), fieldTypes);
+ for (Map.Entry<String, Attribute> entry : field.getAttributes().entrySet()) {
+ checkFieldType(searchName, "attribute", entry.getKey(), entry.getValue().getDataType(), fieldTypes);
+ }
+ }
+ for (DocumentSummary summary : search.getSummaries().values()) {
+ for (SummaryField field : summary.getSummaryFields()) {
+ checkFieldType(searchName, "summary field", field.getName(), field.getDataType(), fieldTypes);
+ }
+ }
+ }
+
+ private void checkFieldType(String searchName, String fieldDesc, String fieldName, DataType fieldType,
+ Map<String, DataType> fieldTypes)
+ {
+ DataType prevType = fieldTypes.get(fieldName);
+ if (prevType == null) {
+ fieldTypes.put(fieldName, fieldType);
+ } else if (!equalTypes(prevType, fieldType)) {
+ throw newProcessException(searchName, fieldName, "Duplicate field name with different types. Expected " + prevType.getName() + " for " + fieldDesc +
+ " '" + fieldName + "', got " + fieldType.getName() + ".");
+ }
+ }
+
+ private boolean equalTypes(DataType d1, DataType d2) {
+ if ("tag".equals(d1.getName())) {
+ return "tag".equals(d2.getName()) || "WeightedSet<string>".equals(d2.getName());
+ }
+ if ("tag".equals(d2.getName())) {
+ return "tag".equals(d1.getName()) || "WeightedSet<string>".equals(d1.getName());
+ }
+ return d1.equals(d2);
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/ValidateFieldWithIndexSettingsCreatesIndex.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ValidateFieldWithIndexSettingsCreatesIndex.java
new file mode 100644
index 00000000000..a5c7d25532d
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/ValidateFieldWithIndexSettingsCreatesIndex.java
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.Matching;
+import com.yahoo.searchdefinition.document.Ranking;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * Check that fields with index settings actually creates an index or attribute
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ValidateFieldWithIndexSettingsCreatesIndex extends Processor {
+
+ public ValidateFieldWithIndexSettingsCreatesIndex(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ Matching defaultMatching = new Matching();
+ Ranking defaultRanking = new Ranking();
+ for (SDField field : search.allFieldsList()) {
+ if (field.doesIndexing()) {
+ continue;
+ }
+ if (field.doesAttributing()) {
+ continue;
+ }
+ if (!(field.getRanking().equals(defaultRanking))) {
+ fail(search, field, "Fields which are not creating an index or attribute can not contain rank settings.");
+ }
+ if (!(field.getMatching().equals(defaultMatching))) {
+ fail(search, field, "Fields which are not creating an index or attribute can not contain match settings.");
+ }
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/WordMatch.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/WordMatch.java
new file mode 100644
index 00000000000..24b811fc7fc
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/WordMatch.java
@@ -0,0 +1,38 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.document.Matching;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Stemming;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+/**
+ * The implementation of word matching - with word matching the field is assumed to contain a single "word" - some
+ * contiguous sequence of word and number characters - but without changing the data at the indexing side (as with text
+ * matching) to enforce this. Word matching is thus almost like exact matching on the indexing side (no action taken),
+ * and like text matching on the query side. This may be suitable for attributes, where people both expect the data to
+ * be left as in the input document, and trivially written queries to work by default. However, this may easily lead to
+ * data which cannot be matched at all as the indexing and query side does not agree.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class WordMatch extends Processor {
+
+ public WordMatch(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ public void process() {
+ for (SDField field : search.allFieldsList()) {
+ if (!field.getMatching().getType().equals(Matching.Type.WORD)) {
+ continue;
+ }
+ field.setStemming(Stemming.NONE);
+ field.getNormalizing().inferLowercase();
+ field.addQueryCommand("word");
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/IndexCommandResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/IndexCommandResolver.java
new file mode 100644
index 00000000000..158515fd05e
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/IndexCommandResolver.java
@@ -0,0 +1,62 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing.multifieldresolver;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+
+/**
+ * Resolver-class for harmonizing index-commands in multifield indexes
+ */
+public class IndexCommandResolver extends MultiFieldResolver {
+
+ /** Commands which don't have to be harmonized between fields */
+ private static List<String> ignoredCommands = new ArrayList<>();
+
+ /** Commands which must be harmonized between fields */
+ private static List<String> harmonizedCommands = new ArrayList<>();
+
+ static {
+ String[] ignore = { "complete-boost", "literal-boost", "highlight" };
+ ignoredCommands.addAll(Arrays.asList(ignore));
+ String[] harmonize = { "stemming", "normalizing" };
+ harmonizedCommands.addAll(Arrays.asList(harmonize));
+ }
+
+ public IndexCommandResolver(String indexName, List<SDField> fields, Search search, DeployLogger logger) {
+ super(indexName, fields, search, logger);
+ }
+
+ /**
+ * Check index-commands for each field, report and attempt to fix any
+ * inconsistencies
+ */
+ public void resolve() {
+ for (SDField field : fields) {
+ for (String command : field.getQueryCommands()) {
+ if (!ignoredCommands.contains(command))
+ checkCommand(command);
+ }
+ }
+ }
+
+ private void checkCommand(String command) {
+ for (SDField field : fields) {
+ if (!field.hasQueryCommand(command)) {
+ if (harmonizedCommands.contains(command)) {
+ deployLogger.log(Level.WARNING, command + " must be added to all fields going to the same index (" + indexName + ")" +
+ ", adding to field " + field.getName());
+ field.addQueryCommand(command);
+ } else {
+ deployLogger.log(Level.WARNING, "All fields going to the same index should have the same query-commands. Field \'" + field.getName() +
+ "\' doesn't contain command \'" + command+"\'");
+ }
+ }
+ }
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/MultiFieldResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/MultiFieldResolver.java
new file mode 100644
index 00000000000..943a788fc75
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/MultiFieldResolver.java
@@ -0,0 +1,33 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing.multifieldresolver;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+import java.util.List;
+
+/**
+ * Abstract superclass of all multifield conflict resolvers
+ */
+public abstract class MultiFieldResolver {
+
+ protected String indexName;
+ protected List<SDField> fields;
+ protected Search search;
+
+ protected DeployLogger deployLogger;
+
+ public MultiFieldResolver(String indexName, List<SDField> fields, Search search, DeployLogger logger) {
+ this.indexName = indexName;
+ this.fields = fields;
+ this.search = search;
+ this.deployLogger = logger;
+ }
+
+ /**
+ * Checks the list of fields for specific conflicts, and reports and/or
+ * attempts to correct them
+ */
+ public abstract void resolve();
+
+}
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
new file mode 100644
index 00000000000..d98dd97ff83
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankProfileTypeSettingsProcessor.java
@@ -0,0 +1,85 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing.multifieldresolver;
+
+import com.yahoo.config.application.api.DeployLogger;
+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.RankProfile;
+import com.yahoo.searchdefinition.RankProfileRegistry;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.Attribute;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.processing.Processor;
+import com.yahoo.vespa.model.container.search.QueryProfiles;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class that processes a search instance and sets type settings on all rank profiles.
+ *
+ * Currently, type settings are limited to the type of tensor attribute fields and tensor query features.
+ *
+ * @author <a href="geirst@yahoo-inc.com">Geir Storli</a>
+ */
+public class RankProfileTypeSettingsProcessor extends Processor {
+
+ private static final Pattern queryFeaturePattern = Pattern.compile("query\\((\\w+)\\)$");
+
+ public RankProfileTypeSettingsProcessor(Search search, DeployLogger deployLogger, RankProfileRegistry rankProfileRegistry, QueryProfiles queryProfiles) {
+ super(search, deployLogger, rankProfileRegistry, queryProfiles);
+ }
+
+ @Override
+ public void process() {
+ processAttributeFields();
+ processQueryProfileTypes();
+
+ }
+
+ private void processAttributeFields() {
+ for (SDField field : search.allFieldsList()) {
+ Attribute attribute = field.getAttributes().get(field.getName());
+ if (attribute != null && attribute.tensorType().isPresent()) {
+ addAttributeTypeToRankProfiles(attribute.getName(), attribute.tensorType().get().toString());
+ }
+ }
+ }
+
+ private void addAttributeTypeToRankProfiles(String attributeName, String attributeType) {
+ for (RankProfile profile : rankProfileRegistry.allRankProfiles()) {
+ profile.addAttributeType(attributeName, attributeType);
+ }
+ }
+
+ private void processQueryProfileTypes() {
+ for (QueryProfileType queryProfileType : queryProfiles.getRegistry().getTypeRegistry().allComponents()) {
+ for (Map.Entry<String, FieldDescription> fieldDescEntry : queryProfileType.fields().entrySet()) {
+ processFieldDescription(fieldDescEntry.getValue());
+ }
+ }
+ }
+
+ private void processFieldDescription(FieldDescription fieldDescription) {
+ String fieldName = fieldDescription.getName();
+ FieldType fieldType = fieldDescription.getType();
+ if (fieldType instanceof TensorFieldType) {
+ TensorFieldType tensorFieldType = (TensorFieldType)fieldType;
+ Matcher matcher = queryFeaturePattern.matcher(fieldName);
+ if (tensorFieldType.type().isPresent() && matcher.matches()) {
+ String queryFeature = matcher.group(1);
+ addQueryFeatureTypeToRankProfiles(queryFeature, tensorFieldType.type().get().toString());
+ }
+ }
+ }
+
+ private void addQueryFeatureTypeToRankProfiles(String queryFeature, String queryFeatureType) {
+ for (RankProfile profile : rankProfileRegistry.allRankProfiles()) {
+ profile.addQueryFeatureType(queryFeature, queryFeatureType);
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankTypeResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankTypeResolver.java
new file mode 100644
index 00000000000..787a70862a9
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/RankTypeResolver.java
@@ -0,0 +1,46 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing.multifieldresolver;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.searchdefinition.document.RankType;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.Search;
+
+import java.util.List;
+import java.util.logging.Level;
+
+/**
+ * Checks if fields have defined different rank types for the same
+ * index (typically in an index-to statement), and if they have
+ * output a warning and use the first ranktype.
+ *
+ * @author <a href="musum@yahoo-inc.com">Harald Musum</a>
+ */
+public class RankTypeResolver extends MultiFieldResolver {
+
+ public RankTypeResolver(String indexName, List<SDField> fields, Search search, DeployLogger logger) {
+ super(indexName, fields, search, logger);
+ }
+
+ public void resolve() {
+ RankType rankType = null;
+ if (fields.size() > 0) {
+ boolean first = true;
+ for (SDField field : fields) {
+ if (first) {
+ rankType = fields.get(0).getRankType();
+ first = false;
+ } else if (!field.getRankType().equals(rankType)) {
+ deployLogger.log(Level.WARNING, "In field '" + field.getName() + "' " +
+ field.getRankType() + " for index '" + indexName +
+ "' conflicts with " + rankType +
+ " defined for the same index in field '" +
+ field.getName() + "'. Using " +
+ rankType + ".");
+ field.setRankType(rankType);
+ }
+ }
+ }
+ }
+}
+
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/StemmingResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/StemmingResolver.java
new file mode 100644
index 00000000000..f1e1899391d
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/multifieldresolver/StemmingResolver.java
@@ -0,0 +1,48 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchdefinition.processing.multifieldresolver;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.language.Linguistics;
+import com.yahoo.searchdefinition.Index;
+import com.yahoo.searchdefinition.Search;
+import com.yahoo.searchdefinition.document.SDField;
+import com.yahoo.searchdefinition.document.Stemming;
+import com.yahoo.searchdefinition.processing.BuiltInFieldSets;
+import com.yahoo.vespa.indexinglanguage.expressions.*;
+import com.yahoo.vespa.indexinglanguage.linguistics.AnnotatorConfig;
+
+import java.util.List;
+import java.util.logging.Level;
+
+/**
+ * Class resolving conflicts when fields with different stemming-settings are
+ * combined into the same index
+ */
+public class StemmingResolver extends MultiFieldResolver {
+
+ public StemmingResolver(String indexName, List<SDField> fields, Search search, DeployLogger logger) {
+ super(indexName, fields, search, logger);
+ }
+
+ @Override
+ public void resolve() {
+ checkStemmingForIndexFields(indexName, fields);
+ }
+
+ private void checkStemmingForIndexFields(String indexName, List<SDField> fields) {
+ Stemming stemming = null;
+ SDField stemmingField = null;
+ for (SDField field : fields) {
+ if (stemming == null && stemmingField==null) {
+ stemming = field.getStemming(search);
+ stemmingField = field;
+ } else if (stemming != field.getStemming(search)) {
+ deployLogger.log(Level.WARNING, "Field '" + field.getName() + "' has " + field.getStemming(search) +
+ ", whereas field '" + stemmingField.getName() + "' has " + stemming +
+ ". All fields indexing to the index '" + indexName + "' must have the same stemming." +
+ " This should be corrected as it will make indexing fail in a few cases.");
+ }
+ }
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/package-info.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/package-info.java
new file mode 100644
index 00000000000..9915e11b28d
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/package-info.java
@@ -0,0 +1,14 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * Classes in this package (processors) implements some search
+ * definition features by reducing them to simpler features.
+ * The processors are run after parsing of the search definition,
+ * before creating the derived model.
+ *
+ * For simplicity, features should always be implemented here
+ * rather than in the derived model if possible.
+ *
+ * New processors must be added to the list in Processing.
+ */
+@com.yahoo.api.annotations.PackageMarker
+package com.yahoo.searchdefinition.processing;