com.yahoo.vespa.model.application.validation.change.search.IndexingScriptChangeValidator Maven / Gradle / Ivy
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.application.validation.change.search;
import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.schema.Schema;
import com.yahoo.schema.document.ImmutableSDField;
import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import com.yahoo.vespa.indexinglanguage.expressions.Expression;
import com.yahoo.vespa.indexinglanguage.expressions.OutputExpression;
import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression;
import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction;
import com.yahoo.vespa.model.application.validation.change.VespaReindexAction;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
/**
* Validates the indexing script changes in all fields in the current and next search model.
*
* @author geirst
*/
public class IndexingScriptChangeValidator {
private final ClusterSpec.Id id;
private final Schema currentSchema;
private final Schema nextSchema;
public IndexingScriptChangeValidator(ClusterSpec.Id id, Schema currentSchema, Schema nextSchema) {
this.id = id;
this.currentSchema = currentSchema;
this.nextSchema = nextSchema;
}
public List validate() {
List result = new ArrayList<>();
for (ImmutableSDField nextField : new LinkedHashSet<>(nextSchema.allConcreteFields())) {
String fieldName = nextField.getName();
ImmutableSDField currentField = currentSchema.getConcreteField(fieldName);
if (currentField != null) {
validateScripts(currentField, nextField).ifPresent(result::add);
}
else if (nextField.isExtraField()) {
result.add(VespaReindexAction.of(id,
null,
"Non-document field '" + nextField.getName() +
"' added; this may be populated by reindexing"));
}
}
return result;
}
private Optional validateScripts(ImmutableSDField currentField, ImmutableSDField nextField) {
ScriptExpression currentScript = currentField.getIndexingScript();
ScriptExpression nextScript = nextField.getIndexingScript();
if ( ! equalScripts(currentScript, nextScript)) {
ChangeMessageBuilder messageBuilder = new ChangeMessageBuilder(nextField.getName());
new IndexingScriptChangeMessageBuilder(currentSchema, currentField, nextSchema, nextField).populate(messageBuilder);
messageBuilder.addChange("indexing script", currentScript.toString(), nextScript.toString());
return Optional.of(VespaReindexAction.of(id, ValidationId.indexingChange, messageBuilder.build()));
}
return Optional.empty();
}
static boolean equalScripts(ScriptExpression currentScript, ScriptExpression nextScript) {
// Output expressions are specifying in which context a field value is used (attribute, index, summary),
// and do not affect how the field value is generated in the indexing doc proc.
// The output expressions are therefore removed before doing the comparison.
// Validating the addition / removal of attribute and index aspects are handled in other validators.
return removeOutputExpressions(currentScript).equals(removeOutputExpressions(nextScript));
}
private static Expression removeOutputExpressions(ScriptExpression script) {
return new OutputExpressionRemover().convert(script);
}
private static class OutputExpressionRemover extends ExpressionConverter {
@Override
protected boolean shouldConvert(Expression exp) {
return exp instanceof OutputExpression;
}
@Override
protected Expression doConvert(Expression exp) {
if (exp instanceof OutputExpression) {
return null;
}
return exp;
}
}
}