Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gravsearch): Keep rdf:type knora-api:Resource when needed (DSP-1407) #1835

Merged
merged 1 commit into from Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
)))
}
}
}