Skip to content

Commit

Permalink
Wrap possibly expensive graph operations into cedes (#5243)
Browse files Browse the repository at this point in the history
Co-authored-by: Simon Dumas <simon.dumas@epfl.ch>
  • Loading branch information
imsdu and Simon Dumas authored Nov 22, 2024
1 parent d6e8277 commit d7a192f
Show file tree
Hide file tree
Showing 26 changed files with 94 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ object GraphResourceToNTriples extends Pipe {

def graphToNTriples(graphResource: GraphResource): IO[Option[NTriples]] = {
val graph = graphResource.graph ++ graphResource.metadataGraph
IO
.fromEither(graph.toNTriples)
.map(triples => Option.when(!triples.isEmpty)(triples))
graph.toNTriples.map(triples => Option.when(!triples.isEmpty)(triples))
}

override def apply(element: SuccessElem[GraphResource]): IO[Elem[NTriples]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ class BlazegraphClientSpec(docker: BlazegraphDocker)

private def nTriples(id: String = genString(), label: String = genString(), value: String = genString()) = {
val json = jsonContentOf("sparql/example.jsonld", "id" -> id, "label" -> label, "value" -> value)
ExpandedJsonLd(json).accepted.toGraph.flatMap(_.toNTriples).rightValue
ExpandedJsonLd(json).accepted.toGraph.flatMap(_.toNTriples).accepted
}

private def nTriplesNested(id: String, label: String, name: String, title: String) = {
val json =
jsonContentOf("sparql/example-nested.jsonld", "id" -> id, "label" -> label, "name" -> name, "title" -> title)
ExpandedJsonLd(json).accepted.toGraph.flatMap(_.toNTriples).rightValue
ExpandedJsonLd(json).accepted.toGraph.flatMap(_.toNTriples).accepted
}

private def expectedResult(id: String, label: String, value: String) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,11 @@ class BlazegraphViewsQuerySpec(docker: BlazegraphDocker)

private def createNTriples(view: ViewRef*): NTriples =
view.foldLeft(NTriples.empty) { (ntriples, view) =>
createGraphs(view).foldLeft(ntriples)(_ ++ _.toNTriples.rightValue)
createGraphs(view).foldLeft(ntriples)(_ ++ _.toNTriples.accepted)
}

private def createTriples(view: ViewRef): Seq[NTriples] =
createGraphs(view).map(_.toNTriples.rightValue)
createGraphs(view).map(_.toNTriples.accepted)

private def sparqlResourceLinkFor(resourceId: Iri, path: Iri) =
SparqlResourceLink(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class GraphResourceToDocumentSuite extends NexusSuite with Fixtures with JsonAss
"""

private val expanded = ExpandedJsonLd.expanded(expandedJson).rightValue
private val graph = Graph(expanded).rightValue
private val graph = Graph(expanded).accepted
private val metadataGraph = graph

private val context = ContextValue.fromFile("contexts/elasticsearch-indexing.json").accepted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,22 +178,20 @@ final case class Graph private (rootNode: IriOrBNode, value: DatasetGraph) { sel
/**
* Attempts to convert the current Graph to the N-Triples format: https://www.w3.org/TR/n-triples/
*/
def toNTriples: Either[RdfError, NTriples] =
tryOrRdfError(
def toNTriples: IO[NTriples] =
tryExpensiveIO(
RDFWriter.create().lang(Lang.NTRIPLES).source(collapseGraphs).asString(),
Lang.NTRIPLES.getName
)
.map(NTriples(_, rootNode))
).map(NTriples(_, rootNode))

/**
* Attempts to convert the current Graph to the N-Quads format: https://www.w3.org/TR/n-quads/
*/
def toNQuads: Either[RdfError, NQuads] =
tryOrRdfError(
def toNQuads: IO[NQuads] =
tryExpensiveIO(
RDFWriter.create().lang(Lang.NQUADS).source(value).asString(),
Lang.NQUADS.getName
)
.map(NQuads(_, rootNode))
).map(NQuads(_, rootNode))

/**
* Transform the current graph to a new one via the provided construct query
Expand Down Expand Up @@ -247,11 +245,11 @@ final case class Graph private (rootNode: IriOrBNode, value: DatasetGraph) { sel

if (rootNode.isBNode)
for {
expanded <- IO.fromEither(api.fromRdf(replace(rootNode, fakeId).value))
expanded <- api.fromRdf(replace(rootNode, fakeId).value)
framed <- computeCompacted(fakeId, expanded.asJson)
} yield framed.replaceId(self.rootNode)
else
IO.fromEither(api.fromRdf(value)).flatMap(expanded => computeCompacted(rootNode, expanded.asJson))
api.fromRdf(value).flatMap(expanded => computeCompacted(rootNode, expanded.asJson))
}

/**
Expand Down Expand Up @@ -337,10 +335,10 @@ object Graph {
*/
final def apply(
expanded: ExpandedJsonLd
)(implicit api: JsonLdApi, options: JsonLdOptions): Either[RdfError, Graph] =
)(implicit api: JsonLdApi, options: JsonLdOptions): IO[Graph] =
(expanded.obj(keywords.graph), expanded.rootId) match {
case (Some(_), _: BNode) =>
Left(UnexpectedJsonLd("Expected named graph, but root @id not found"))
IO.raiseError(UnexpectedJsonLd("Expected named graph, but root @id not found"))
case (Some(_), iri: Iri) =>
api.toRdf(expanded.json).map(g => Graph(iri, g))
case (None, _: BNode) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ final case class CompactedJsonLd private (rootId: IriOrBNode, ctx: ContextValue,
opts: JsonLdOptions,
api: JsonLdApi,
resolution: RemoteContextResolution
): IO[Graph] =
toExpanded.flatMap(expanded => IO.fromEither(expanded.toGraph))
): IO[Graph] = toExpanded.flatMap(_.toGraph)

/**
* Merges the current document with the passed one, overriding the fields on the current with the passed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,7 @@ final case class ExpandedJsonLd private (rootId: IriOrBNode, obj: JsonObject) ex
/**
* Converts the current document to a [[Graph]]
*/
def toGraph(implicit
opts: JsonLdOptions,
api: JsonLdApi
): Either[RdfError, Graph] = Graph(self)
def toGraph(implicit opts: JsonLdOptions, api: JsonLdApi): IO[Graph] = Graph(self)

/**
* Converts the current document to an ''A''
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api

import cats.effect.IO
import ch.epfl.bluebrain.nexus.delta.rdf.{ExplainResult, RdfError}
import ch.epfl.bluebrain.nexus.delta.rdf.ExplainResult
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, JsonLdContext, RemoteContextResolution}
import io.circe.{Json, JsonObject}
import org.apache.jena.sparql.core.DatasetGraph
Expand Down Expand Up @@ -35,9 +35,9 @@ trait JsonLdApi {
frame: Json
)(implicit opts: JsonLdOptions, rcr: RemoteContextResolution): IO[JsonObject]

private[rdf] def toRdf(input: Json)(implicit opts: JsonLdOptions): Either[RdfError, DatasetGraph]
private[rdf] def toRdf(input: Json)(implicit opts: JsonLdOptions): IO[DatasetGraph]

private[rdf] def fromRdf(input: DatasetGraph)(implicit opts: JsonLdOptions): Either[RdfError, Seq[JsonObject]]
private[rdf] def fromRdf(input: DatasetGraph)(implicit opts: JsonLdOptions): IO[Seq[JsonObject]]

private[rdf] def context(
value: ContextValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ package ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api
import cats.effect.IO
import cats.syntax.all._
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.rdf.{ExplainResult, RdfError}
import ch.epfl.bluebrain.nexus.delta.rdf.RdfError.{ConversionError, RemoteContextCircularDependency, RemoteContextError, UnexpectedJsonLd, UnexpectedJsonLdContext}
import ch.epfl.bluebrain.nexus.delta.rdf.implicits._
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.JsonLdJavaApi.{tryExpensiveIO, tryOrRdfError}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.JsonLdApiConfig.ErrorHandling
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.JsonLdJavaApi.tryExpensiveIO
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context._
import ch.epfl.bluebrain.nexus.delta.rdf.{ExplainResult, RdfError}
import com.github.jsonldjava.core.JsonLdError.Error.RECURSIVE_CONTEXT_INCLUSION
import com.github.jsonldjava.core.{Context, DocumentLoader, JsonLdError, JsonLdOptions => JsonLdJavaOptions, JsonLdProcessor}
import com.github.jsonldjava.utils.JsonUtils
import io.circe.syntax._
import io.circe.{parser, Json, JsonObject}
import org.apache.jena.query.DatasetFactory
import org.apache.jena.riot.RDFFormat.{JSONLD_EXPAND_FLAT => EXPAND}
import org.apache.jena.riot.system.ErrorHandlerFactory
import org.apache.jena.riot._
import org.apache.jena.riot.system.ErrorHandlerFactory
import org.apache.jena.sparql.core.DatasetGraph

import scala.annotation.nowarn
Expand Down Expand Up @@ -72,7 +72,7 @@ final class JsonLdJavaApi(config: JsonLdApiConfig) extends JsonLdApi {
framedObj <- IO.fromEither(toJsonObjectOrErr(framed))
} yield framedObj

override private[rdf] def toRdf(input: Json)(implicit opts: JsonLdOptions): Either[RdfError, DatasetGraph] = {
override private[rdf] def toRdf(input: Json)(implicit opts: JsonLdOptions): IO[DatasetGraph] = {
val c = new JsonLDReadContext()
c.setOptions(toOpts())
val ds = DatasetFactory.create
Expand All @@ -90,18 +90,17 @@ final class JsonLdJavaApi(config: JsonLdApiConfig) extends JsonLdApi {
}
}
val builder = opts.base.fold(initBuilder)(base => initBuilder.base(base.toString))
tryOrRdfError(builder.parse(ds.asDatasetGraph()), "toRdf").as(ds.asDatasetGraph())
tryExpensiveIO(builder.parse(ds.asDatasetGraph()), "toRdf").as(ds.asDatasetGraph())
}

override private[rdf] def fromRdf(
input: DatasetGraph
)(implicit opts: JsonLdOptions): Either[RdfError, Seq[JsonObject]] = {
)(implicit opts: JsonLdOptions): IO[Seq[JsonObject]] = {
val c = new JsonLDWriteContext()
c.setOptions(toOpts())
for {
expanded <- tryOrRdfError(RDFWriter.create.format(EXPAND).source(input).context(c).asString(), "fromRdf")
expandedSeqObj <- toSeqJsonObjectOrErr(expanded)
} yield expandedSeqObj
tryExpensiveIO(RDFWriter.create.format(EXPAND).source(input).context(c).asString(), "fromRdf").flatMap { expanded =>
IO.fromEither(toSeqJsonObjectOrErr(expanded))
}
}

override private[rdf] def context(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@ trait JsonLdEncoder[A] {
def ntriples(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[NTriples] =
for {
graph <- graph(value)
ntriples <- IO.fromEither(graph.toNTriples)
} yield ntriples
graph(value).flatMap(_.toNTriples)

/**
* Converts a value of type ''A'' to [[NQuads]] format.
Expand All @@ -74,10 +71,7 @@ trait JsonLdEncoder[A] {
def nquads(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[NQuads] =
for {
graph <- graph(value)
ntriples <- IO.fromEither(graph.toNQuads)
} yield ntriples
graph(value).flatMap(_.toNQuads)

/**
* Converts a value of type ''A'' to [[Graph]]
Expand All @@ -88,10 +82,7 @@ trait JsonLdEncoder[A] {
def graph(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[Graph] =
for {
expanded <- expand(value)
graph <- IO.fromEither(expanded.toGraph)
} yield graph
expand(value).flatMap(_.toGraph)

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,37 +22,37 @@ class GraphSpec extends CatsEffectSpec with GraphHelpers with CirceLiteral {
val expandedJson = jsonContentOf("expanded.json")
val expanded = ExpandedJsonLd.expanded(expandedJson).rightValue
val nquads = NQuads(contentOf("nquads.nq"), iri)
val graph = Graph(expanded).rightValue
val graph = Graph(expanded).accepted
val bnode = bNode(graph)
val iriSubject = subject(iri)
val rootBNode = BNode.random
val expandedNoIdJson = expandedJson.removeAll(keywords.id -> iri)
val expandedNoId = ExpandedJsonLd.expanded(expandedNoIdJson).rightValue.replaceId(rootBNode)
val graphNoId = Graph(expandedNoId).rightValue
val graphNoId = Graph(expandedNoId).accepted
val bnodeNoId = bNode(graphNoId)
val namedGraph = Graph(
ExpandedJsonLd.expanded(jsonContentOf("graph/expanded-multiple-roots-namedgraph.json")).rightValue
).rightValue
).accepted

val name = predicate(schema.name)
val birthDate = predicate(schema + "birthDate")

"be created from expanded jsonld" in {
Graph(expanded).rightValue.triples.size shouldEqual 16
Graph(expanded).accepted.triples.size shouldEqual 16
}

"be created from n-quads" in {
Graph(nquads).rightValue.triples.size shouldEqual 16
}

"be created from expanded jsonld with a root blank node" in {
Graph(expandedNoId).rightValue.triples.size shouldEqual 16
Graph(expandedNoId).accepted.triples.size shouldEqual 16
}

"replace its root node" in {
val iri2 = iri"http://example.com/newid"
val subject2 = subject(iri2)
val graph = Graph(expanded).rightValue
val graph = Graph(expanded).accepted
val graph2 = graph.replaceRootNode(iri2)
val expected = graph.triples.map { case (s, p, o) => (if (s == iriSubject) subject2 else s, p, o) }
graph2.rootNode shouldEqual iri2
Expand Down Expand Up @@ -107,7 +107,7 @@ class GraphSpec extends CatsEffectSpec with GraphHelpers with CirceLiteral {

"be converted to NTriples" in {
val expected = contentOf("ntriples.nt", "bnode" -> bnode.rdfFormat, "rootNode" -> iri.rdfFormat)
graph.toNTriples.rightValue.toString should equalLinesUnordered(expected)
graph.toNTriples.accepted.toString should equalLinesUnordered(expected)
}

"be created from NTriples" in {
Expand All @@ -120,22 +120,22 @@ class GraphSpec extends CatsEffectSpec with GraphHelpers with CirceLiteral {

"be converted to NTriples from a named graph" in {
val expected = contentOf("graph/multiple-roots-namedgraph.nt")
namedGraph.toNTriples.rightValue.toString should equalLinesUnordered(expected)
namedGraph.toNTriples.accepted.toString should equalLinesUnordered(expected)
}

"be converted to NTriples with a root blank node" in {
val expected = contentOf("ntriples.nt", "bnode" -> bnodeNoId.rdfFormat, "rootNode" -> rootBNode.rdfFormat)
graphNoId.toNTriples.rightValue.toString should equalLinesUnordered(expected)
graphNoId.toNTriples.accepted.toString should equalLinesUnordered(expected)
}

"be converted to NQuads" in {
val expected = contentOf("ntriples.nt", "bnode" -> bnode.rdfFormat, "rootNode" -> iri.rdfFormat)
graph.toNQuads.rightValue.toString should equalLinesUnordered(expected)
graph.toNQuads.accepted.toString should equalLinesUnordered(expected)
}

"be converted to NQuads from a named graph" in {
val expected = contentOf("graph/multiple-roots-namedgraph.nq")
namedGraph.toNQuads.rightValue.toString should equalLinesUnordered(expected)
namedGraph.toNQuads.accepted.toString should equalLinesUnordered(expected)
}

"be created from NQuads with a named graph" in {
Expand Down Expand Up @@ -185,7 +185,7 @@ class GraphSpec extends CatsEffectSpec with GraphHelpers with CirceLiteral {
"failed to be converted to compacted JSON-LD from a multiple root" in {
val expandedJson = jsonContentOf("graph/expanded-multiple-roots.json")
val expanded = ExpandedJsonLd(expandedJson).accepted
Graph(expanded).leftValue shouldEqual UnexpectedJsonLd("Expected named graph, but root @id not found")
Graph(expanded).rejected shouldEqual UnexpectedJsonLd("Expected named graph, but root @id not found")
}

"be converted to compacted JSON-LD from a named graph" in {
Expand Down Expand Up @@ -265,7 +265,7 @@ class GraphSpec extends CatsEffectSpec with GraphHelpers with CirceLiteral {
"raise an error with a strict parser when an iri is invalid" in {
val expandedJson = jsonContentOf("expanded-invalid-iri.json")
val expanded = ExpandedJsonLd.expanded(expandedJson).rightValue
Graph(expanded).leftValue shouldEqual ConversionError(
Graph(expanded).rejected shouldEqual ConversionError(
"Bad IRI: < http://nexus.example.com/myid> Spaces are not legal in URIs/IRIs.",
"toRdf"
)
Expand All @@ -274,7 +274,7 @@ class GraphSpec extends CatsEffectSpec with GraphHelpers with CirceLiteral {
"not raise an error with a lenient parser when an iri is invalid" in {
val expandedJson = jsonContentOf("expanded-invalid-iri.json")
val expanded = ExpandedJsonLd.expanded(expandedJson).rightValue
Graph(expanded)(JsonLdJavaApi.lenient, JsonLdOptions.defaults).rightValue
Graph(expanded)(JsonLdJavaApi.lenient, JsonLdOptions.defaults).accepted
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ class CompactedJsonLdSpec extends CatsEffectSpec with Fixtures with GraphHelpers
val graph = compacted.toGraph.accepted
val expected = contentOf("ntriples.nt", "bnode" -> bNode(graph).rdfFormat, "rootNode" -> iri.rdfFormat)
graph.rootNode shouldEqual iri
graph.toNTriples.rightValue.toString should equalLinesUnordered(expected)
graph.toNTriples.accepted.toString should equalLinesUnordered(expected)
}

"be converted to graph with a root blank node" in {
val compacted = CompactedJsonLd(rootBNode, context, expandedNoId).accepted
val graph = compacted.toGraph.accepted
val expected = contentOf("ntriples.nt", "bnode" -> bNode(graph).rdfFormat, "rootNode" -> rootBNode.rdfFormat)
graph.rootNode shouldEqual rootBNode
graph.toNTriples.rightValue.toString should equalLinesUnordered(expected)
graph.toNTriples.accepted.toString should equalLinesUnordered(expected)
}

"be merged with another compacted document" in {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ class ExpandedJsonLdSpec extends CatsEffectSpec with Fixtures with GraphHelpers

"be converted to graph" in {
val expanded = ExpandedJsonLd(compacted).accepted
val graph = expanded.toGraph.rightValue
val graph = expanded.toGraph.accepted
val expected = contentOf("ntriples.nt", "bnode" -> bNode(graph).rdfFormat, "rootNode" -> iri.rdfFormat)
graph.rootNode shouldEqual iri
graph.toNTriples.rightValue.toString should equalLinesUnordered(expected)
graph.toNTriples.accepted.toString should equalLinesUnordered(expected)
}

"add @id value" in {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ValidateShaclSuite extends NexusSuite {
private val schemaGraph = toGraph(schema).accepted
private val dataGraph = toGraph(data).accepted

private def toGraph(json: Json) = ExpandedJsonLd(json).map(_.toGraph).rethrow
private def toGraph(json: Json) = ExpandedJsonLd(json).flatMap(_.toGraph)

test("Validate data from schema model") {
shaclValidation(dataGraph, schemaGraph, reportDetails = true).assert(_.conformsWithTargetedNodes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ValidationReportSpec extends CatsEffectSpec {
RemoteContextResolution.fixed(contexts.shacl -> shaclResolvedCtx)

private def resource(json: Json): Resource = {
val g = Graph(ExpandedJsonLd(json).accepted).rightValue.value
val g = Graph(ExpandedJsonLd(json).accepted).accepted.value
DatasetFactory.wrap(g).getDefaultModel.createResource()
}

Expand Down
Loading

0 comments on commit d7a192f

Please sign in to comment.