diff options
author | Arne H Juul <arnej@yahooinc.com> | 2022-02-25 14:54:09 +0000 |
---|---|---|
committer | Arne H Juul <arnej@yahooinc.com> | 2022-02-25 15:21:30 +0000 |
commit | 442a36dab875212162d202095e9b618eb773eff3 (patch) | |
tree | 628b92f93573a7e97429f69957d698e6eda1d492 | |
parent | 2c3f8d3f2034697446b18c804eb714e9071b2474 (diff) |
simple inheritance resolving
5 files changed, 182 insertions, 0 deletions
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/parser/InheritanceResolver.java b/config-model/src/main/java/com/yahoo/searchdefinition/parser/InheritanceResolver.java new file mode 100644 index 00000000000..655285c8462 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/searchdefinition/parser/InheritanceResolver.java @@ -0,0 +1,105 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.searchdefinition.parser; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Class resolving some inheritance relationships. + * + * @author arnej27959 + **/ +public class InheritanceResolver { + + private final Map<String, ParsedSchema> parsedSchemas; + private final Map<String, ParsedDocument> parsedDocs = new HashMap<>(); + + public InheritanceResolver(Map<String, ParsedSchema> parsedSchemas) { + this.parsedSchemas = parsedSchemas; + } + + private void inheritanceCycleCheck(ParsedSchema schema, List<String> seen) { + String name = schema.name(); + if (seen.contains(name)) { + seen.add(name); + throw new IllegalArgumentException("Inheritance cycle for schemas: " + + String.join(" -> ", seen)); + } + seen.add(name); + for (ParsedSchema parent : schema.getResolvedInherits()) { + inheritanceCycleCheck(parent, seen); + } + seen.remove(name); + } + + private void resolveSchemaInheritance() { + for (ParsedSchema schema : parsedSchemas.values()) { + for (String inherit : schema.getInherited()) { + var parent = parsedSchemas.get(inherit); + if (parent == null) { + throw new IllegalArgumentException("schema "+schema.name()+" inherits from unavailable schema "+inherit); + } + schema.resolveInherit(inherit, parent); + } + } + } + + private void checkSchemaCycles() { + List<String> seen = new ArrayList<>(); + for (ParsedSchema schema : parsedSchemas.values()) { + inheritanceCycleCheck(schema, seen); + } + } + + private void resolveDocumentInheritance() { + for (ParsedSchema schema : parsedSchemas.values()) { + if (! schema.hasDocument()) { + // TODO: is schema without a document even valid? + continue; + } + ParsedDocument doc = schema.getDocument(); + var old = parsedDocs.put(doc.name(), doc); + assert(old == null); + } + for (ParsedDocument doc : parsedDocs.values()) { + for (String inherit : doc.getInherited()) { + var parentDoc = parsedDocs.get(inherit); + if (parentDoc == null) { + throw new IllegalArgumentException("document "+doc.name()+" inherits from unavailable document "+inherit); + } + doc.resolveInherit(inherit, parentDoc); + } + } + } + + private void inheritanceCycleCheck(ParsedDocument document, List<String> seen) { + String name = document.name(); + if (seen.contains(name)) { + seen.add(name); + throw new IllegalArgumentException("Inheritance cycle for documents: " + + String.join(" -> ", seen)); + } + seen.add(name); + for (ParsedDocument parent : document.getResolvedInherits()) { + inheritanceCycleCheck(parent, seen); + } + seen.remove(name); + } + + private void checkDocumentCycles() { + List<String> seen = new ArrayList<>(); + for (ParsedDocument doc : parsedDocs.values()) { + inheritanceCycleCheck(doc, seen); + } + } + + public void resolveInheritance() { + resolveSchemaInheritance(); + resolveDocumentInheritance(); + checkSchemaCycles(); + checkDocumentCycles(); + } + +} diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/parser/IntermediateCollection.java b/config-model/src/main/java/com/yahoo/searchdefinition/parser/IntermediateCollection.java index f96d59df150..0ba34287293 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/parser/IntermediateCollection.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/parser/IntermediateCollection.java @@ -139,4 +139,8 @@ public class IntermediateCollection { } } + void resolveInternalConnections() { + var resolver = new InheritanceResolver(parsedSchemas); + resolver.resolveInheritance(); + } } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/parser/ParsedDocument.java b/config-model/src/main/java/com/yahoo/searchdefinition/parser/ParsedDocument.java index 0b7d0507161..bf30654abc7 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/parser/ParsedDocument.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/parser/ParsedDocument.java @@ -17,6 +17,7 @@ import java.util.Optional; **/ public class ParsedDocument extends ParsedBlock { private final List<String> inherited = new ArrayList<>(); + private final Map<String, ParsedDocument> resolvedInherits = new HashMap(); private final Map<String, ParsedField> docFields = new HashMap<>(); private final Map<String, ParsedStruct> docStructs = new HashMap<>(); private final Map<String, ParsedAnnotation> docAnnotations = new HashMap<>(); @@ -27,6 +28,7 @@ public class ParsedDocument extends ParsedBlock { List<String> getInherited() { return List.copyOf(inherited); } List<ParsedAnnotation> getAnnotations() { return List.copyOf(docAnnotations.values()); } + List<ParsedDocument> getResolvedInherits() { return List.copyOf(resolvedInherits.values()); } List<ParsedField> getFields() { return List.copyOf(docFields.values()); } List<ParsedStruct> getStructs() { return List.copyOf(docStructs.values()); } @@ -51,5 +53,12 @@ public class ParsedDocument extends ParsedBlock { } public String toString() { return "document "+name(); } + + void resolveInherit(String name, ParsedDocument parsed) { + verifyThat(inherited.contains(name), "resolveInherit for non-inherited name", name); + verifyThat(name.equals(parsed.name()), "resolveInherit name mismatch for", name); + verifyThat(! resolvedInherits.containsKey(name), "double resolveInherit for", name); + resolvedInherits.put(name, parsed); + } } diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/parser/ParsedSchema.java b/config-model/src/main/java/com/yahoo/searchdefinition/parser/ParsedSchema.java index 1916f236aa9..bf448b31dd2 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/parser/ParsedSchema.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/parser/ParsedSchema.java @@ -36,6 +36,7 @@ public class ParsedSchema extends ParsedBlock { private final List<OnnxModel> onnxModels = new ArrayList<>(); private final List<RankingConstant> rankingConstants = new ArrayList<>(); private final List<String> inherited = new ArrayList<>(); + private final Map<String, ParsedSchema> resolvedInherits = new HashMap(); private final Map<String, ParsedAnnotation> extraAnnotations = new HashMap<>(); private final Map<String, ParsedDocumentSummary> docSums = new HashMap<>(); private final Map<String, ParsedField> extraFields = new HashMap<>(); @@ -64,6 +65,7 @@ public class ParsedSchema extends ParsedBlock { List<RankingConstant> getRankingConstants() { return List.copyOf(rankingConstants); } List<String> getInherited() { return List.copyOf(inherited); } Map<String, ParsedRankProfile> getRankProfiles() { return Map.copyOf(rankProfiles); } + List<ParsedSchema> getResolvedInherits() { return List.copyOf(resolvedInherits.values()); } void addAnnotation(ParsedAnnotation annotation) { String annName = annotation.name(); @@ -136,4 +138,12 @@ public class ParsedSchema extends ParsedBlock { "already has stemming", defaultStemming, "cannot also set", value); defaultStemming = value; } + + void resolveInherit(String name, ParsedSchema parsed) { + verifyThat(inherited.contains(name), "resolveInherit for non-inherited name", name); + verifyThat(name.equals(parsed.name()), "resolveInherit name mismatch for", name); + verifyThat(! resolvedInherits.containsKey(name), "double resolveInherit for", name); + resolvedInherits.put(name, parsed); + } + } diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/parser/IntermediateCollectionTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/parser/IntermediateCollectionTestCase.java index 891360474c1..1ee7ae4937a 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/parser/IntermediateCollectionTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/parser/IntermediateCollectionTestCase.java @@ -131,4 +131,58 @@ public class IntermediateCollectionTestCase { assertTrue(ex.getMessage().startsWith("Failed parsing rank-profile from src/test/examples/structoutsideofdocument.sd: ")); } + @Test + public void can_resolve_document_inheritance() throws Exception { + var collection = new IntermediateCollection(); + collection.addSchemaFromFile("src/test/derived/deriver/child.sd"); + collection.addSchemaFromFile("src/test/derived/deriver/grandparent.sd"); + collection.addSchemaFromFile("src/test/derived/deriver/parent.sd"); + collection.resolveInternalConnections(); + var schemes = collection.getParsedSchemas(); + assertEquals(schemes.size(), 3); + var childDoc = schemes.get("child").getDocument(); + var inherits = childDoc.getResolvedInherits(); + assertEquals(inherits.size(), 1); + var parentDoc = inherits.get(0); + assertEquals(parentDoc.name(), "parent"); + inherits = parentDoc.getResolvedInherits(); + assertEquals(inherits.size(), 1); + assertEquals(inherits.get(0).name(), "grandparent"); + } + + @Test + public void can_detect_schema_inheritance_cycles() throws Exception { + var collection = new IntermediateCollection(); + collection.addSchemaFromString("schema foo inherits bar {}"); + collection.addSchemaFromString("schema bar inherits qux {}"); + collection.addSchemaFromString("schema qux inherits foo {}"); + assertEquals(collection.getParsedSchemas().size(), 3); + var ex = assertThrows(IllegalArgumentException.class, () -> + collection.resolveInternalConnections()); + assertTrue(ex.getMessage().startsWith("Inheritance cycle for schemas: ")); + } + + @Test + public void can_detect_document_inheritance_cycles() throws Exception { + var collection = new IntermediateCollection(); + collection.addSchemaFromString("schema foo { document foo inherits bar {} }"); + collection.addSchemaFromString("schema bar { document bar inherits qux {} }"); + collection.addSchemaFromString("schema qux { document qux inherits foo {} }"); + assertEquals(collection.getParsedSchemas().size(), 3); + var ex = assertThrows(IllegalArgumentException.class, () -> + collection.resolveInternalConnections()); + assertTrue(ex.getMessage().startsWith("Inheritance cycle for documents: ")); + } + + @Test + public void can_detect_missing_doc() throws Exception { + var collection = new IntermediateCollection(); + collection.addSchemaFromString("schema foo { document foo inherits bar {} }"); + collection.addSchemaFromString("schema qux { document qux inherits foo {} }"); + assertEquals(collection.getParsedSchemas().size(), 2); + var ex = assertThrows(IllegalArgumentException.class, () -> + collection.resolveInternalConnections()); + assertEquals("document foo inherits from unavailable document bar", ex.getMessage()); + } + } |