Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix(gravsearch): Keep rdf:type knora-api:Resource when needed. (#1835)
  • Loading branch information
Benjamin Geer committed Mar 24, 2021
1 parent 30cb086 commit e561d94
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 11 deletions.
Expand Up @@ -20,8 +20,9 @@
package org.knora.webapi.messages.util.search.gravsearch.prequery

import org.knora.webapi.ApiV2Schema
import org.knora.webapi.exceptions.AssertionException
import org.knora.webapi.feature.{Feature, FeatureFactory, FeatureFactoryConfig}
import org.knora.webapi.messages.OntologyConstants
import org.knora.webapi.messages.{OntologyConstants, SmartIri}
import org.knora.webapi.messages.util.search._
import org.knora.webapi.messages.util.search.gravsearch.types.{
GravsearchTypeInspectionResult,
Expand Down Expand Up @@ -73,17 +74,91 @@ object GravsearchQueryOptimisationFactory extends FeatureFactory {
if (featureFactoryConfig.getToggle("gravsearch-dependency-optimisation").isEnabled) {
new ReorderPatternsByDependencyOptimisationFeature(typeInspectionResult, querySchema).optimiseQueryPatterns(
new RemoveEntitiesInferredFromPropertyOptimisationFeature(typeInspectionResult, querySchema)
.optimiseQueryPatterns(patterns)
.optimiseQueryPatterns(
new RemoveRedundantKnoraApiResourceOptimisationFeature(typeInspectionResult, querySchema)
.optimiseQueryPatterns(patterns))
)
} else {
new RemoveEntitiesInferredFromPropertyOptimisationFeature(typeInspectionResult, querySchema)
.optimiseQueryPatterns(patterns)
.optimiseQueryPatterns(
new RemoveRedundantKnoraApiResourceOptimisationFeature(typeInspectionResult, querySchema)
.optimiseQueryPatterns(patterns))
}
}
}
}
}

/**
* Removes a statement with rdf:type knora-api:Resource if there is another rdf:type statement with the same subject
* and a different type.
*
* @param typeInspectionResult the type inspection result.
* @param querySchema the query schema.
*/
class RemoveRedundantKnoraApiResourceOptimisationFeature(typeInspectionResult: GravsearchTypeInspectionResult,
querySchema: ApiV2Schema)
extends GravsearchQueryOptimisationFeature(typeInspectionResult, querySchema)
with Feature {

/**
* If the specified statement has rdf:type with an IRI as object, returns that IRI, otherwise None.
*/
private def getObjOfRdfType(statementPattern: StatementPattern): Option[SmartIri] = {
statementPattern.pred match {
case predicateIriRef: IriRef =>
if (predicateIriRef.iri.toString == OntologyConstants.Rdf.Type) {
statementPattern.obj match {
case iriRef: IriRef => Some(iriRef.iri)
case _ => None
}
} else {
None
}

case _ => None
}
}

override def optimiseQueryPatterns(patterns: Seq[QueryPattern]): Seq[QueryPattern] = {
// Make a Set of subjects that have rdf:type statements whose objects are not knora-api:Resource.
val rdfTypesBySubj: Set[Entity] = patterns
.foldLeft(Set.empty[Entity]) {
case (acc, queryPattern: QueryPattern) =>
queryPattern match {
case statementPattern: StatementPattern =>
getObjOfRdfType(statementPattern) match {
case Some(typeIri) =>
if (!OntologyConstants.KnoraApi.KnoraApiV2ResourceIris.contains(typeIri.toString)) {
acc + statementPattern.subj
} else {
acc
}

case None => acc
}

case _ => acc
}
}

patterns.filterNot {
case statementPattern: StatementPattern =>
// If this statement has rdf:type knora-api:Resource, and we also have another rdf:type statement
// with the same subject and a different type, remove this statement.
getObjOfRdfType(statementPattern) match {
case Some(typeIri) =>
OntologyConstants.KnoraApi.KnoraApiV2ResourceIris
.contains(typeIri.toString) && rdfTypesBySubj.contains(statementPattern.subj)

case None => false
}

case _ => false
}
}
}

/**
* Optimises a query by removing `rdf:type` statements that are known to be redundant. A redundant
* `rdf:type` statement gives the type of a variable whose type is already restricted by its
Expand Down Expand Up @@ -131,7 +206,8 @@ class RemoveEntitiesInferredFromPropertyOptimisationFeature(typeInspectionResult
// Yes. Is this an rdf:type statement?
if (predicateIriRef.iri.toString == OntologyConstants.Rdf.Type) {
// Yes. Is the subject a typeable entity?
val subjectAsTypeableEntity = GravsearchTypeInspectionUtil.maybeTypeableEntity(statementPattern.subj)
val subjectAsTypeableEntity: Option[TypeableEntity] =
GravsearchTypeInspectionUtil.maybeTypeableEntity(statementPattern.subj)

subjectAsTypeableEntity match {
case Some(typeableEntity) =>
Expand Down
Expand Up @@ -119,7 +119,7 @@ class AnnotationReadingGravsearchTypeInspector(nextInspector: Option[GravsearchT
extends WhereVisitor[Vector[GravsearchTypeAnnotation]] {
override def visitStatementInWhere(statementPattern: StatementPattern,
acc: Vector[GravsearchTypeAnnotation]): Vector[GravsearchTypeAnnotation] = {
if (GravsearchTypeInspectionUtil.isAnnotationStatement(statementPattern)) {
if (GravsearchTypeInspectionUtil.canBeAnnotationStatement(statementPattern)) {
acc :+ annotationStatementToAnnotation(statementPattern, querySchema)
} else {
acc
Expand Down
Expand Up @@ -173,10 +173,10 @@ object GravsearchTypeInspectionUtil {
private class AnnotationRemovingWhereTransformer extends WhereTransformer {
override def transformStatementInWhere(statementPattern: StatementPattern,
inputOrderBy: Seq[OrderCriterion]): Seq[QueryPattern] = {
if (!isAnnotationStatement(statementPattern)) {
Seq(statementPattern)
} else {
if (mustBeAnnotationStatement(statementPattern)) {
Seq.empty[QueryPattern]
} else {
Seq(statementPattern)
}
}

Expand Down Expand Up @@ -209,12 +209,43 @@ object GravsearchTypeInspectionUtil {
}

/**
* Determines whether a statement pattern represents a Gravsearch type annotation.
* Determines whether a statement pattern must represent a Gravsearch type annotation.
*
* @param statementPattern the statement pattern.
* @return `true` if the statement pattern must represent a type annotation.
*/
def mustBeAnnotationStatement(statementPattern: StatementPattern): Boolean = {
// Does the statement have rdf:type knora-api:Resource (which is not necessarily a Gravsearch type annotation)?
def hasRdfTypeKnoraApiResource: Boolean = {
statementPattern.pred match {
case predIriRef: IriRef =>
if (predIriRef.iri.toString == OntologyConstants.Rdf.Type) {
statementPattern.obj match {
case objIriRef: IriRef =>
OntologyConstants.KnoraApi.KnoraApiV2ResourceIris.contains(objIriRef.iri.toString)

case _ => false
}
} else {
false
}

case _ => false
}
}

// If the statement can be a type annotation and doesn't have rdf:type knora-api:Resource, return true.
// Otherwise, return false.
canBeAnnotationStatement(statementPattern) && !hasRdfTypeKnoraApiResource
}

/**
* Determines whether a statement pattern can represent a Gravsearch type annotation.
*
* @param statementPattern the statement pattern.
* @return `true` if the statement pattern represents a type annotation.
* @return `true` if the statement pattern can represent a type annotation.
*/
def isAnnotationStatement(statementPattern: StatementPattern): Boolean = {
def canBeAnnotationStatement(statementPattern: StatementPattern): Boolean = {

/**
* Returns `true` if an entity is an IRI representing a type that is valid for use in a type annotation.
Expand Down
Expand Up @@ -3050,6 +3050,18 @@ class NonTriplestoreSpecificGravsearchToPrequeryTransformerSpec extends CoreSpec
useDistinct = true
)

val queryWithKnoraApiResource: String =
"""PREFIX knora-api: <http://api.knora.org/ontology/knora-api/v2#>
|CONSTRUCT {
| ?resource knora-api:isMainResource true .
| ?resource ?p ?text .
|} WHERE {
| ?resource a knora-api:Resource .
| ?resource ?p ?text .
| ?p knora-api:objectType knora-api:TextValue .
| FILTER knora-api:matchText(?text, "der")
|}""".stripMargin

"The NonTriplestoreSpecificGravsearchToPrequeryGenerator object" should {

"transform an input query with an optional property criterion without removing the rdf:type statement" in {
Expand Down Expand Up @@ -3311,5 +3323,24 @@ class NonTriplestoreSpecificGravsearchToPrequeryTransformerSpec extends CoreSpec

assert(transformedQuery == transformedQueryToReorderWithCycle)
}

"not remove rdf:type knora-api:Resource if it's needed" in {
val transformedQuery =
QueryHandler.transformQuery(queryWithKnoraApiResource, responderData, settings, defaultFeatureFactoryConfig)

assert(
transformedQuery.whereClause.patterns.contains(StatementPattern(
subj = QueryVariable(variableName = "resource"),
pred = IriRef(
iri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type".toSmartIri,
propertyPathOperator = None
),
obj = IriRef(
iri = "http://www.knora.org/ontology/knora-base#Resource".toSmartIri,
propertyPathOperator = None
),
namedGraph = None
)))
}
}
}

0 comments on commit e561d94

Please sign in to comment.