GraphQL inverse traversal

So once I loaded starwars.ttl I am able to execute a query like:

{
    Human{
        name
        appearsIn{
            name
        }
    }
}

produces nicely:

{
  "data": [
    {
      "name": "Luke Skywalker",
      "appearsIn": [
        {
          "name": "Return of the Jedi"
        },
        {
          "name": "A New Hope"
        },
        {
          "name": "The Empire Strikes Back"
        }
      ]
    },
    {
      "name": "Han Solo",
      "appearsIn": [
        {
          "name": "A New Hope"
        },
        {
          "name": "The Empire Strikes Back"
        },
        {
          "name": "Return of the Jedi"
        }
      ]
    },
    {
      "name": "Leia Organa",
      "appearsIn": [
        {
          "name": "The Empire Strikes Back"
        },
        {
          "name": "A New Hope"
        },
        {
          "name": "Return of the Jedi"
        }
      ]
    },
    {
      "name": "Darth Vader",
      "appearsIn": [
        {
          "name": "Return of the Jedi"
        },
        {
          "name": "A New Hope"
        },
        {
          "name": "The Empire Strikes Back"
        }
      ]
    },
    {
      "name": "Wilhuff Tarkin",
      "appearsIn": {
        "name": "A New Hope"
      }
    }
  ]
}
  • Now how can I get the inverse - show me all Humans for each episode?
    owl:inverseOf is nice but how can use it? Shall I add a few turtle statements to starwars.ttl to define the inverse relation?

Regards,
Radu

Hi Radu,

You would just need to add one triple to your dataset, in fact: :featuresCharacter owl:inverseOf :appearsIn .

If you add that, enable reasoning, and slightly rewrite your query, you can get what you're looking for:

As a follow-up, if you want to limit the query to just Humans, you can do that too:

Thank you Stephen,

Your suggestion makes total sense. I will be trying out.

To leverage the new HTTP API shall I use "sparql_update" as documented below or do a POST host:port/db/default with what headers for ttl file?

### [SPARQL update](https://www.stardog.com/docs/#_sparql_update)

GET | POST /{db}/update → text/boolean


The SPARQL endpoint for updating the database with SPARQL Update. The valid Accept types are  `application/sparql-update`   `or application/x-www-form-urlencoded` . Response is the result of the update operation as text, eg  `true`  or  `false` .

So I was able to load ttl via api using:

  • 'content-type': "application/x-turtle"
  • and using HTTP POST host:port/Test3?default endpoint

I've appended the last triple as follows:

data = original_starwars_ttl + """
:featuresCharacter owl:inverseOf :appearsIn ."""
response = requests.request("POST", url, data = data, headers=headers, verify=False)
print(response.status_code)

But this graphql in Stardog Studio returns zero data :frowning:

{
    Episode{
        name
        featuresCharacter{
            name
        }
    }
}

A brand new database.

  • What is wrong?

Thanks,
Radu

Are you enabling reasoning when running the query (the toggle to the right of the Show Plan button)?

Wow! - magic - "Reasoning" On button - did the trick - thank you. Now I am getting:

{
  "data": [
    {
      "name": "A New Hope",
      "featuresCharacter": [
        {
          "name": "C-3PO"
        },
        {
          "name": "R2-D2"
        },
        {
          "name": "Leia Organa"
        },
        {
          "name": "Wilhuff Tarkin"
        },
        {
          "name": "Darth Vader"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Luke Skywalker"
        }
      ]
    },
    {
      "name": "The Empire Strikes Back",
      "featuresCharacter": [
        {
          "name": "Darth Vader"
        },
        {
          "name": "C-3PO"
        },
        {
          "name": "R2-D2"
        },
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Leia Organa"
        },
        {
          "name": "Han Solo"
        }
      ]
    },
    {
      "name": "Return of the Jedi",
      "featuresCharacter": [
        {
          "name": "R2-D2"
        },
        {
          "name": "Darth Vader"
        },
        {
          "name": "Leia Organa"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "C-3PO"
        }
      ]
    }
  ]
}

Zach,

Ok trying to create the inverse relation in JSON-LD per speck at https://w3c.github.io/json-ld-syntax/#reverse-properties. Added @reverse ex:containedIn inside the context - the rest stayed the same. Once I have added the json-ld - there is no triple added - that would say ... owl:reversOf ...

  • Does Stardog honor @reverse directive in JSON-LD?
{
  "@context": {
    "dc11": "http://purl.org/dc/elements/1.1/",
    "ex": "http://example.org/vocab#",
    "xsd": "http://www.w3.org/2001/XMLSchema#",
    "ex:contains": {
      "@type": "@id"
    },
    "ex:containedIn": { 
        "@reverse": "http://example.org/vocab#contains" 
    }
  }

This example is useful - loaded into Stardog - but still trying to figure out the graphql query returning zero rows with reasoning on:
{
ex_children{
name
}
}

That's a good question. I guess that would require creating an owl:inverseOf. I would first check to see if that axiom is being created after importing the json-ld and then if it was make sure that it is contained in a graph, or default graph that Stardog will be using for reasoning. If it's there and in the correct graph it should work.

Zach,

I don't think the the owl:inverseOf has been generated during json-ld data add - see results below. You can try it yourself, right?

Again the above select * results are after importing the following:

{
  "@context": { "name": "http://example.com/vocab#name",
    "children": { "@reverse": "http://example.com/vocab#parent" }
  },
  "@id": "#homer",
  "name": "Homer",
  "children": [
    {
      "@id": "#bart",
      "name": "Bart"
    }, {
      "@id": "#lisa",
      "name": "Lisa"
    }
  ]
}
  • Any resolution yet?

Hi Radu,

According to the example you pasted from the spec, Stardog is behaving correctly. You can verify this in the example box by switching it over to Turtle or Statements.

From reading the spec itself, @reverse does not have to do with inferencing/reasoning at all; it is merely a way to define relationships as o -> p -> s instead of s -> p -> o.

If you want to use inferencing to see this relationship, you can define explicit owl:inverseOf relationships in your JSON-LD:

{
  "@context": {
    "dc11": "http://purl.org/dc/elements/1.1/",
    "ex": "http://example.org/vocab#",
    "xsd": "http://www.w3.org/2001/XMLSchema#",
    "owl": "http://www.w3.org/2002/07/owl#",
    "ex:contains": {
      "@type": "@id"
    },
    "ex:containedIn": {
        "owl:inverseOf": "http://example.org/vocab#contains"
    }
  }
}

Stephen,

I have loaded this json-ld:

{
  "@context": {
    "dc11": "http://purl.org/dc/elements/1.1/",
    "ex": "http://example.org/vocab#",
    "xsd": "http://www.w3.org/2001/XMLSchema#",
    "owl": "http://www.w3.org/2002/07/owl#",
    "ex:contains": {
      "@type": "@id"
    },
    "ex:containedIn": {
        "owl:inverseOf": "http://example.org/vocab#contains"
    }
  },
  "@graph": [
    {
      "@id": "http://example.org/library",
      "@type": "ex:Library",
      "dc11:title": "My Library",
      "ex:contains": "http://example.org/library/the-republic"
    },
    {
      "@id": "http://example.org/library/the-republic",
      "@type": "ex:Book",
      "dc11:creator": "Plato",
      "dc11:title": "The Republic",
      "ex:contains": "http://example.org/library/the-republic#introduction"
    },
    {
      "@id": "http://example.org/library/the-republic#introduction",
      "@type": "ex:Chapter",
      "dc11:description": "An introductory chapter on The Republic.",
      "dc11:title": "The Introduction"
    }
  ]
}

but the graphql returns no rows:

{
    ex_Book {        
        dc11_title
        ex_contains{
            dc11_title
        }
        ex_containedIn{
            dc11_title
        }
    }
}

however this returns one row:

{
    ex_Book {        
        dc11_title
        ex_contains{
            dc11_title
        }
    }
}

Hi Radu,

JSON-LD seems to be kind of finicky when it comes to specifying literals versus IRIs, and where you have to do it. I have rewritten your JSON-LD to be what you're looking for by moving the owl:inverseOf definition into the @graph instead of the @context:

{
  "@context": {
    "dc11": "http://purl.org/dc/elements/1.1/",
    "ex": "http://example.org/vocab#",
    "xsd": "http://www.w3.org/2001/XMLSchema#",
    "owl": "http://www.w3.org/2002/07/owl#",
    "ex:contains": {
      "@type": "@id"
    }
  },
  "@graph": [
    {
      "@id": "ex:containedIn",
      "owl:inverseOf": {
        "@id": "ex:contains"
      }
    },
    {
      "@id": "http://example.org/library",
      "@type": "ex:Library",
      "dc11:title": "My Library",
      "ex:contains": "http://example.org/library/the-republic"
    },
    {
      "@id": "http://example.org/library/the-republic",
      "@type": "ex:Book",
      "dc11:creator": "Plato",
      "dc11:title": "The Republic",
      "ex:contains": "http://example.org/library/the-republic#introduction"
    },
    {
      "@id": "http://example.org/library/the-republic#introduction",
      "@type": "ex:Chapter",
      "dc11:description": "An introductory chapter on The Republic.",
      "dc11:title": "The Introduction"
    }
  ]
}

This gives me what I think is the result you're looking for, though I need to enable reasoning to do it:

You may want to use the CLI stardog graphql explain command to help you out. It will show you the SPARQL query generated by Stardog to fulfill the provided graphql query.