com.yahoo.schema.DocumentModelBuilder 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.schema;
import com.yahoo.document.ArrayDataType;
import com.yahoo.document.CollectionDataType;
import com.yahoo.document.DataType;
import com.yahoo.document.DocumentType;
import com.yahoo.document.Field;
import com.yahoo.document.MapDataType;
import com.yahoo.document.StructDataType;
import com.yahoo.document.WeightedSetDataType;
import com.yahoo.document.annotation.AnnotationReferenceDataType;
import com.yahoo.document.annotation.AnnotationType;
import com.yahoo.documentmodel.DataTypeCollection;
import com.yahoo.documentmodel.NewDocumentReferenceDataType;
import com.yahoo.documentmodel.NewDocumentType;
import com.yahoo.documentmodel.OwnedStructDataType;
import com.yahoo.documentmodel.OwnedTemporaryType;
import com.yahoo.documentmodel.TemporaryUnknownType;
import com.yahoo.documentmodel.VespaDocumentType;
import com.yahoo.schema.document.Attribute;
import com.yahoo.schema.document.SDDocumentType;
import com.yahoo.schema.document.SDField;
import com.yahoo.schema.document.TemporaryImportedFields;
import com.yahoo.schema.document.annotation.SDAnnotationType;
import com.yahoo.schema.document.annotation.TemporaryAnnotationReferenceDataType;
import com.yahoo.vespa.documentmodel.DocumentModel;
import com.yahoo.vespa.documentmodel.FieldView;
import com.yahoo.vespa.documentmodel.SchemaDef;
import com.yahoo.vespa.documentmodel.SearchField;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author baldersheim
*/
public class DocumentModelBuilder {
private final DocumentModel model;
public DocumentModelBuilder() {
this.model = new DocumentModel();
this.model.getDocumentManager().add(VespaDocumentType.INSTANCE);
}
public DocumentModel build(Collection schemaList) {
List docList = new LinkedList<>();
for (Schema schema : schemaList) {
docList.add(schema.getDocument());
}
docList = sortDocumentTypes(docList);
addDocumentTypes(docList);
for (Collection toAdd = tryAdd(schemaList);
! toAdd.isEmpty() && (toAdd.size() < schemaList.size());
toAdd = tryAdd(schemaList)) {
schemaList = toAdd;
}
return model;
}
private List sortDocumentTypes(List docList) {
Set doneNames = new HashSet<>();
doneNames.add(SDDocumentType.VESPA_DOCUMENT.getName());
List doneList = new LinkedList<>();
List prevList = null;
List 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 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 tryAdd(Collection schemaList) {
Collection left = new ArrayList<>();
for (Schema schema : schemaList) {
try {
addToModel(schema);
} catch (RetryLaterException e) {
left.add(schema);
}
}
return left;
}
private void addToModel(Schema schema) {
// Then we add the search specific stuff
SchemaDef schemaDef = new SchemaDef(schema.getName());
addSearchFields(schema.extraFieldList(), schemaDef);
for (Field f : schema.getDocument().fieldSet()) {
addSearchField((SDField) f, schemaDef);
}
for (SDField field : schema.allConcreteFields()) {
for (Attribute attribute : field.getAttributes().values()) {
if ( ! schemaDef.getFields().containsKey(attribute.getName())) {
schemaDef.add(new SearchField(new Field(attribute.getName(), field), !field.getIndices().isEmpty(), true));
}
}
}
for (Field f : schema.getDocument().fieldSet()) {
addAlias((SDField) f, schemaDef);
}
model.getSearchManager().add(schemaDef);
}
private static void addSearchFields(Collection fields, SchemaDef schemaDef) {
for (SDField field : fields) {
addSearchField(field, schemaDef);
}
}
private static void addSearchField(SDField field, SchemaDef schemaDef) {
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()));
schemaDef.add(searchField);
// Add field to views
addToView(field.getIndices().keySet(), searchField, schemaDef);
}
private static void addAlias(SDField field, SchemaDef schemaDef) {
for (Map.Entry entry : field.getAliasToName().entrySet()) {
schemaDef.addAlias(entry.getKey(), entry.getValue());
}
}
private static void addToView(Collection views, Field field, SchemaDef schemaDef) {
for (String viewName : views) {
addToView(viewName, field, schemaDef);
}
}
private static void addToView(String viewName, Field field, SchemaDef schemaDef) {
if (schemaDef.getViews().containsKey(viewName)) {
schemaDef.getViews().get(viewName).add(field);
} else {
if (!schemaDef.getFields().containsKey(viewName)) {
FieldView view = new FieldView(viewName);
view.add(field);
schemaDef.add(view);
}
}
}
private void addDocumentTypes(List docList) {
LinkedList lst = new LinkedList<>();
for (SDDocumentType doc : docList) {
lst.add(convert(doc));
model.getDocumentManager().add(lst.getLast());
}
Map replacements = new IdentityHashMap<>();
for(NewDocumentType doc : lst) {
resolveTemporaries(doc.getAllTypes(), lst, replacements);
resolveTemporariesRecurse(doc.getContentStruct(), doc.getAllTypes(), lst, replacements);
}
for(NewDocumentType doc : lst) {
for (var entry : replacements.entrySet()) {
var old = entry.getKey();
if (doc.getDataType(old.getId()) == old) {
doc.replace(entry.getValue());
}
}
}
}
private static void resolveTemporaries(DataTypeCollection dtc,
Collection docs,
Map replacements) {
for (DataType type : dtc.getTypes()) {
resolveTemporariesRecurse(type, dtc, docs, replacements);
}
}
@SuppressWarnings("deprecation")
private static DataType resolveTemporariesRecurse(DataType type, DataTypeCollection repo,
Collection docs,
Map replacements) {
if (replacements.containsKey(type)) {
return replacements.get(type);
}
DataType original = type;
if (type instanceof TemporaryUnknownType) {
// must be a known struct or document type
DataType other = repo.getDataType(type.getId());
if (other == null || other == type) {
// maybe it is the name of a document type:
other = getDocumentType(docs, type.getName());
}
if (other == null) {
throw new IllegalArgumentException("No replacement found for temporary type: " + type);
}
type = other;
} else if (type instanceof OwnedTemporaryType) {
// must be replaced with the real struct type
DataType other = repo.getDataType(type.getId());
if (other == null || other == type) {
throw new IllegalArgumentException("No replacement found for temporary type: " + type);
}
if (other instanceof OwnedStructDataType otherOwned) {
var owned = (OwnedTemporaryType) type;
String ownedBy = owned.getOwnerName();
String otherOwnedBy = otherOwned.getOwnerName();
if (! ownedBy.equals(otherOwnedBy)) {
throw new IllegalArgumentException("Wrong document for type: " + otherOwnedBy + " but expected " + ownedBy);
}
} else {
throw new IllegalArgumentException("Found wrong sort of type: " + other + " [" + other.getClass() + "]");
}
type = other;
} else if (type instanceof DocumentType) {
DataType other = getDocumentType(docs, type.getName());
if (other != null) {
type = other;
} else if (type != DataType.DOCUMENT) {
throw new IllegalArgumentException
("Can not handle nested document definitions. Undefined document type: " + type);
}
} else if (type instanceof NewDocumentType) {
DataType other = getDocumentType(docs, type.getName());
if (other != null) {
type = other;
}
} else if (type instanceof StructDataType sdt) {
// trick avoids infinite recursion:
var old = replacements.put(original, type);
assert(old == null);
for (com.yahoo.document.Field field : sdt.getFields()) {
var ft = field.getDataType();
var newft = resolveTemporariesRecurse(ft, repo, docs, replacements);
if (ft != newft) {
// XXX deprecated:
field.setDataType(newft);
}
}
old = replacements.remove(original);
assert(old == type);
}
else if (type instanceof MapDataType mdt) {
var old_kt = mdt.getKeyType();
var old_vt = mdt.getValueType();
var kt = resolveTemporariesRecurse(old_kt, repo, docs, replacements);
var vt = resolveTemporariesRecurse(old_vt, repo, docs, replacements);
if (kt != old_kt || vt != old_vt) {
type = new MapDataType(kt, vt, mdt.getId());
}
}
else if (type instanceof ArrayDataType adt) {
var old_nt = adt.getNestedType();
var nt = resolveTemporariesRecurse(old_nt, repo, docs, replacements);
if (nt != old_nt) {
type = new ArrayDataType(nt, adt.getId());
}
}
else if (type instanceof WeightedSetDataType wdt) {
var old_nt = wdt.getNestedType();
var nt = resolveTemporariesRecurse(old_nt, repo, docs, replacements);
if (nt != old_nt) {
boolean c = wdt.createIfNonExistent();
boolean r = wdt.removeIfZero();
type = new WeightedSetDataType(nt, c, r, wdt.getId());
}
}
else if (type instanceof NewDocumentReferenceDataType rft) {
var doc = getDocumentType(docs, rft.getTargetTypeName());
type = doc.getReferenceDataType();
}
if (type != original) {
replacements.put(original, type);
}
return type;
}
private static NewDocumentType getDocumentType(Collection docs, String name) {
for (NewDocumentType doc : docs) {
if (doc.getName().equals(name)) {
return doc;
}
}
return null;
}
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) {
NewDocumentType dt = new NewDocumentType(new NewDocumentType.Name(sdoc.getName()),
sdoc.getDocumentType().contentStruct(),
sdoc.getFieldSets(),
convertDocumentReferencesToNames(sdoc.getDocumentReferences()),
convertTemporaryImportedFieldsToNames(sdoc.getTemporaryImportedFields()));
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);
}
}
var extractor = new TypeExtractor(dt);
extractor.extract(sdoc);
return dt;
}
static class TypeExtractor {
private final NewDocumentType targetDt;
Map annotationInheritance = new LinkedHashMap<>();
Map structInheritance = new LinkedHashMap<>();
private final Map
© 2015 - 2024 Weber Informatics LLC | Privacy Policy