Skip to content

Commit

Permalink
Parse Blazegraph malformed query exception (#4553)
Browse files Browse the repository at this point in the history
  • Loading branch information
olivergrabinski authored Dec 5, 2023
1 parent 349888c commit 638caa3
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package ch.epfl.bluebrain.nexus.delta.plugins.blazegraph

import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.SparqlClientError
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.SparqlClientError.WrappedHttpClientError
import ch.epfl.bluebrain.nexus.delta.sdk.http.HttpClientError

object BlazegraphErrorParser {

/**
* Attempts to extract the malformed query message from the raw exception
*/
private def parseMalformedQueryException(rawError: String): Option[String] = {
val javaExecutionExceptionMatcher = "java.util.concurrent.ExecutionException: "
val malformedSegmentMatcher = "java.util.concurrent.ExecutionException: org.openrdf.query.MalformedQueryException: "

rawError.linesIterator
.find(_.contains(malformedSegmentMatcher))
.map(str => str.replace(javaExecutionExceptionMatcher, ""))
.map(str => parseExpectedTokens(rawError).map(s => s"$str $s").getOrElse(str))
}

/**
* Attempts to extract the expected tokens from the raw exception
*/
private def parseExpectedTokens(rawError: String): Option[String] = {
val wasExpectingOneOfMatcher = "Was expecting one of:"
val errorLines = rawError.linesIterator.toList
val index = errorLines.indexWhere(_.startsWith(wasExpectingOneOfMatcher))

Option
.when(index != -1) {
errorLines
.drop(index + 1)
.takeWhile(_.trim.nonEmpty)
.map(_.replace("...", "").trim)
.mkString(", ")
}
.map { expectedTokens =>
s"$wasExpectingOneOfMatcher $expectedTokens."
}
}

/**
* Attempts to parse the raw error message. If it cannot be parsed the raw error is returned.
*/
private def parse(rawError: String): String =
parseMalformedQueryException(rawError).getOrElse(rawError)

/**
* Extract the details from the error
*/
def details(error: SparqlClientError): String =
error match {
case WrappedHttpClientError(httpError) =>
httpError match {
case HttpClientError.HttpClientStatusError(_, _, message) => parse(message)
case error => error.reason
}
case sparqlClientError => sparqlClientError.toString()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class SparqlClient(client: HttpClient, endpoint: SparqlQueryEndpoint)(implicit
val pss = new ParameterizedSparqlString
pss.setCommandText(queryString)
for {
_ <- IO(pss.asUpdate()).adaptError(e => InvalidUpdateRequest(index, queryString, e.getMessage))
_ <- IO(pss.asUpdate()).adaptError(e => InvalidUpdateRequest(index, queryString, e.getMessage.some))
queryOpt = uniqueGraph(queries).map(graph => Query("using-named-graph-uri" -> graph.toString))
formData = FormData("update" -> queryString)
reqEndpoint = endpoint(index).withQuery(queryOpt.getOrElse(Query.Empty))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.http.HttpClientError
/**
* Error that can occur when using an [[SparqlClient]]
*/
sealed abstract class SparqlClientError(val reason: String, details: Option[String])
sealed abstract class SparqlClientError(val reason: String, val details: Option[String])
extends Exception
with Product
with Serializable {
Expand All @@ -25,6 +25,8 @@ object SparqlClientError {
*/
final case class WrappedHttpClientError(http: HttpClientError) extends SparqlClientError(http.reason, http.details) {
override def getMessage: String = http.getMessage

def getOriginal: HttpClientError = http
}

/**
Expand All @@ -39,9 +41,9 @@ object SparqlClientError {
/**
* Error when trying to perform an update and the query passed is wrong.
*/
final case class InvalidUpdateRequest(index: String, queryString: String, details: String)
final case class InvalidUpdateRequest(index: String, queryString: String, override val details: Option[String])
extends SparqlClientError(
s"Attempting to update the index '$index' with a wrong query '$queryString'",
Some(details)
details
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import akka.http.scaladsl.model.StatusCodes
import ch.epfl.bluebrain.nexus.delta.kernel.Mapper
import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection
import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.BlazegraphErrorParser
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.SparqlClientError
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.rdf.RdfError.ConversionError
Expand Down Expand Up @@ -223,7 +224,9 @@ object BlazegraphViewRejection {
r match {
case ProjectContextRejection(rejection) => rejection.asJsonObject
case WrappedBlazegraphClientError(rejection) =>
obj.add(keywords.tpe, "SparqlClientError".asJson).add("details", rejection.toString().asJson)
obj
.add(keywords.tpe, "SparqlClientError".asJson)
.add("details", BlazegraphErrorParser.details(rejection).asJson)
case IncorrectRev(provided, expected) => obj.add("provided", provided.asJson).add("expected", expected.asJson)
case InvalidViewReferences(views) => obj.add("views", views.asJson)
case InvalidJsonLdFormat(_, ConversionError(details, _)) => obj.add("details", details.asJson)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ch.epfl.bluebrain.nexus.delta.kernel.search.Pagination
import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.BlazegraphViewsQuery.BlazegraphQueryContext
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.SparqlQueryResponseType.SparqlNTriples
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.{BlazegraphClient, SparqlWriteQuery}
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.{BlazegraphClient, SparqlQueryResponse, SparqlWriteQuery}
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.BlazegraphViewRejection.{ProjectContextRejection, ViewIsDeprecated}
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.BlazegraphViewValue.{AggregateBlazegraphViewValue, IndexingBlazegraphViewValue}
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.SparqlLink.{SparqlExternalLink, SparqlResourceLink}
Expand All @@ -21,6 +21,7 @@ import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.{BlazegraphViews, Blazeg
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.nxv
import ch.epfl.bluebrain.nexus.delta.rdf.graph.{Graph, NTriples}
import ch.epfl.bluebrain.nexus.delta.rdf.query.SparqlQuery
import ch.epfl.bluebrain.nexus.delta.rdf.query.SparqlQuery.SparqlConstructQuery
import ch.epfl.bluebrain.nexus.delta.sdk.ConfigFixtures
import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclSimpleCheck
Expand All @@ -42,6 +43,7 @@ import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Identity, Label, ResourceRe
import ch.epfl.bluebrain.nexus.delta.sourcing.postgres.DoobieScalaTestFixture
import ch.epfl.bluebrain.nexus.testkit.blazegraph.BlazegraphDocker
import ch.epfl.bluebrain.nexus.testkit.scalatest.ce.CatsEffectSpec
import io.circe.syntax.EncoderOps
import org.scalatest.concurrent.Eventually
import org.scalatest.{CancelAfterFailure, DoNotDiscover, Inspectors}

Expand Down Expand Up @@ -297,6 +299,29 @@ class BlazegraphViewsQuerySpec(docker: BlazegraphDocker)
)
)
}

"have a correct details when the query is malformed" in {
val proj = view1Proj1.project
val sparqlQueryWithError = SparqlQuery("SELEC")
val result = viewsQuery.query(view1Proj1.viewId, proj, sparqlQueryWithError, SparqlNTriples)

rejectionDetailsOf(result) should contain(
"org.openrdf.query.MalformedQueryException: Lexical error at line 1, column 6. Encountered: <EOF> after : \"SELEC\""
)
}

"have a correct details when the query is malformed and the error provides expected tokens" in {
val proj = view1Proj1.project
val sparqlQueryWithError = SparqlQuery("SELECT {")
val result = viewsQuery.query(view1Proj1.viewId, proj, sparqlQueryWithError, SparqlNTriples)

rejectionDetailsOf(result) should contain(
"org.openrdf.query.MalformedQueryException: Encountered \" \"{\" \"{ \"\" at line 1, column 8. Was expecting one of: \"(\", \"*\", \"distinct\", \"reduced\", <VAR1>, <VAR2>."
)
}
}

private def rejectionDetailsOf(io: IO[SparqlQueryResponse]) =
io.rejectedWith[BlazegraphViewRejection].asJson.hcursor.get[String]("details").toOption

}

0 comments on commit 638caa3

Please sign in to comment.