diff --git a/librisxl-tools/elasticsearch/libris_config.json b/librisxl-tools/elasticsearch/libris_config.json index 5454a2a41a..4b904a3478 100644 --- a/librisxl-tools/elasticsearch/libris_config.json +++ b/librisxl-tools/elasticsearch/libris_config.json @@ -191,24 +191,6 @@ } } }, - "hasTitle": { - "type": "nested", - "include_in_parent": true, - "properties": { - "@type": {"type": "keyword"}, - "__mainTitle": { - "type": "text", - "analyzer": "softmatcher", - "copy_to": "_all", - "fields": { - "keyword": { - "type": "icu_collation_keyword", - "language": "sv" - } - } - } - } - }, "reverseLinks": { "properties": { "@type": { @@ -408,8 +390,10 @@ "contribution", "instanceOf.contribution", "@reverse.instanceOf.contribution", + "hasPart.contribution", "instanceOf.hasPart.contribution", + "relationship.entity.contribution", "instanceOf.relationship.entity.contribution" ], @@ -419,6 +403,50 @@ } } }, + { + "hasTitle_template": { + "path_match": [ + "hasTitle", + "instanceOf.hasTitle", + "@reverse.instanceOf.hasTitle", + + "translationOf.hasTitle", + "instanceOf.translationOf.hasTitle", + + "hasPart.hasTitle", + "instanceOf.hasPart.hasTitle", + "@reverse.instanceOf.hasPart.hasTitle", + + "hasPart.translationOf.hasTitle", + "instanceOf.hasPart.translationOf.hasTitle", + + "relationship.entity.hasTitle", + "instanceOf.relationship.entity.hasTitle", + "@reverse.instanceOf.relationship.entity.hasTitle", + + "seriesMembership.inSeries.instanceOf.hasTitle", + "@reverse.instanceOf.seriesMembership.inSeries.instanceOf.hasTitle" + ], + "mapping": { + "type": "nested", + "include_in_parent": true, + "properties": { + "@type": {"type": "keyword"}, + "__mainTitle": { + "type": "text", + "analyzer": "softmatcher", + "copy_to": "_all", + "fields": { + "keyword": { + "type": "icu_collation_keyword", + "language": "sv" + } + } + } + } + } + } + }, { "identifiedBy_template": { "path_match": ["identifiedBy", "@reverse.instanceOf.identifiedBy"], diff --git a/whelk-core/src/main/groovy/whelk/search2/querytree/Path.java b/whelk-core/src/main/groovy/whelk/search2/querytree/Path.java index af56dc743c..2def7b4ea0 100644 --- a/whelk-core/src/main/groovy/whelk/search2/querytree/Path.java +++ b/whelk-core/src/main/groovy/whelk/search2/querytree/Path.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -61,7 +62,7 @@ public List getAltSelectors(JsonLd jsonLd, Collection rdfSubje altSelectors.add(new Path(l)); } }); - return excludeInverseIntegralRoundTrips(altSelectors); + return altSelectors; } private List> getAltPaths(List tail, JsonLd jsonLd, Collection rdfSubjectTypes, boolean allowIncompatible) { @@ -71,27 +72,24 @@ private List> getAltPaths(List tail, Js var next = tail.getFirst(); var newTail = tail.subList(1, tail.size()); var nextAltSelectors = next.getAltSelectors(jsonLd, rdfSubjectTypes, allowIncompatible); + + var shouldRecurse = (!newTail.isEmpty() && newTail.getFirst().isComposite()) + || (next instanceof Property p && "hasItem".equals(p.name())); + + if (shouldRecurse) { + return nextAltSelectors.stream() + .flatMap(s -> getAltPaths(newTail, jsonLd, next.range(), allowIncompatible).stream() + .map(altPath -> concat(s.path(), altPath))) + .toList(); + } + return nextAltSelectors.stream() - .flatMap(s -> getAltPaths(newTail, jsonLd, next.range(), allowIncompatible).stream() - .map(altPath -> Stream.concat(s.path().stream(), altPath.stream()))) - .map(Stream::toList) + .map(s -> concat(s.path(), newTail)) .toList(); } - private List excludeInverseIntegralRoundTrips(List altSelectors) { - List res = new ArrayList<>(); - for (Selector s : altSelectors) { - List integralsInPath = s.path().stream() - .filter(Property.IntegralProperty.class::isInstance) - .map(Property.IntegralProperty.class::cast) - .toList(); - boolean hasInversePair = integralsInPath.stream() - .anyMatch(integral -> integralsInPath.stream().anyMatch(integral::isInverseOf)); - if (!hasInversePair) { - res.add(s); - } - } - return res; + private List concat(List left, List right) { + return Stream.concat(left.stream(), right.stream()).toList(); } @Override diff --git a/whelk-core/src/test/groovy/whelk/search2/querytree/SelectorSpec.groovy b/whelk-core/src/test/groovy/whelk/search2/querytree/SelectorSpec.groovy index af9465a711..1de3bbd087 100644 --- a/whelk-core/src/test/groovy/whelk/search2/querytree/SelectorSpec.groovy +++ b/whelk-core/src/test/groovy/whelk/search2/querytree/SelectorSpec.groovy @@ -30,28 +30,28 @@ class SelectorSpec extends Specification { p.getAltSelectors(jsonLd, types, true).collect { it.path().collect { it.toString() }.join(".") } == result where: - _p | types | result - "p1" | [] | ["p1"] - "p1" | ["T1"] | ["instanceOf.p1", "p1"] - "p1" | ["T2"] | ["hasInstance.p1", "p1"] - "p1" | ["T1", "T2"] | ["hasInstance.p1", "instanceOf.p1", "p1"] - "p1" | ["T3"] | ["p1"] - "p7" | ["T1"] | ["p7"] - "p7" | ["T2"] | ["hasInstance.p7"] - "p7" | ["T1", "T2"] | ["hasInstance.p7", "p7"] - "p7" | ["T3"] | ["p7"] - "p8" | ["T1"] | ["instanceOf.p8"] - "p8" | ["T2"] | ["p8"] - "p8" | ["T1", "T2"] | ["instanceOf.p8", "p8"] - "p8" | ["T3"] | ["p8"] - "p9" | ["T1"] | ["p9"] - "p9" | ["T2"] | ["p9"] - "p9" | ["T1", "T2"] | ["p9"] - "p9" | ["T3"] | ["p9"] - "hasInstance.p7" | ["T2"] | ["hasInstance.p7"] - "type" | ["T2"] | ["rdf:type"] - "instanceOf.p8" | ["T1"] | ["instanceOf.p8"] - "type" | ["T1"] | ["rdf:type"] - "p7.p14" | ["T2"] | ["hasInstance.p7.hasComponent.p14", "hasInstance.p7.p14"] + _p | types | result + "p1" | [] | ["p1"] + "p1" | ["T1"] | ["instanceOf.p1", "p1"] + "p1" | ["T2"] | ["hasInstance.p1", "p1"] + "p1" | ["T1", "T2"] | ["hasInstance.p1", "instanceOf.p1", "p1"] + "p1" | ["T3"] | ["p1"] + "hasItem" | ["T1"] | ["hasItem"] + "hasItem" | ["T2"] | ["hasInstance.hasItem"] + "hasItem" | ["T1", "T2"] | ["hasInstance.hasItem", "hasItem"] + "hasItem" | ["T3"] | ["hasItem"] + "p8" | ["T1"] | ["instanceOf.p8"] + "p8" | ["T2"] | ["p8"] + "p8" | ["T1", "T2"] | ["instanceOf.p8", "p8"] + "p8" | ["T3"] | ["p8"] + "p9" | ["T1"] | ["p9"] + "p9" | ["T2"] | ["p9"] + "p9" | ["T1", "T2"] | ["p9"] + "p9" | ["T3"] | ["p9"] + "hasInstance.hasItem" | ["T2"] | ["hasInstance.hasItem"] + "type" | ["T2"] | ["rdf:type"] + "instanceOf.p8" | ["T1"] | ["instanceOf.p8"] + "type" | ["T1"] | ["rdf:type"] + "hasItem.p14" | ["T2"] | ["hasInstance.hasItem.hasComponent.p14", "hasInstance.hasItem.p14"] } } diff --git a/whelk-core/src/test/groovy/whelk/search2/querytree/TestData.groovy b/whelk-core/src/test/groovy/whelk/search2/querytree/TestData.groovy index 5d99d14674..8283a1794f 100644 --- a/whelk-core/src/test/groovy/whelk/search2/querytree/TestData.groovy +++ b/whelk-core/src/test/groovy/whelk/search2/querytree/TestData.groovy @@ -36,6 +36,7 @@ class TestData { 'rdf:type' : ['rdf:type'] as Set, 'instanceof' : ['instanceOf'] as Set, 'hasinstance' : ['hasInstance'] as Set, + 'hasitem' : ['hasItem'] as Set, 'p' : ['p', 'p1'] as Set, 'plabel' : ['p2', 'p3'] as Set, 'pp' : ['p3', 'p4'] as Set, @@ -227,6 +228,12 @@ class TestData { 'domain' : ['@id': 'T4'], 'range' : ['@id': 'T4'] ], + [ + '@id' : 'hasItem', + '@type' : 'DatatypeProperty', + 'domain': [['@id': 'T1']], + 'range' : [['@id': 'T4']] + ], [ '@id' : 'librissearch:workCategory', 'category' : ['@id': "https://id.kb.se/ns/librissearch/composite"],