Skip to content

Instantly share code, notes, and snippets.

@klinovp
Last active October 7, 2021 07:34
Show Gist options
  • Save klinovp/e4c4594143ed91e10c44226535eae698 to your computer and use it in GitHub Desktop.
Save klinovp/e4c4594143ed91e10c44226535eae698 to your computer and use it in GitHub Desktop.
Generate an RDF list from simple data

This query generates an RDF list from an RDF graph that is a linear path. To set up initial data:

stardog query test "insert data { :a :p :b . :b :p :c . :c :p :d }"

The list generation query:

construct {
  ?start rdf:first ?s ;
         rdf:rest ?end .
  ?end rdf:first ?o .
  ?last rdf:rest rdf:nil .
}
where {
  { 
    ?s :p ?o
    bind(bnode(str(?s)) as ?start)
    bind(bnode(str(?o)) as ?end)
  }
  UNION {
    ?s :p ?o
    FILTER NOT EXISTS { ?o :p [] }
    BIND(bnode(str(?o)) as ?last)
  }
}

The result (note the pretty Turtle formatting to validate the well-formedness):

stardog query test -f PRETTY_TURTLE ~/cp/tmp/list.rq 
@prefix ...

( :a :b :c :d ) .

PS. The awkward bit is appending the rdf:nil terminator, probably can be done more efficiently...

@klinovp
Copy link
Author

klinovp commented Oct 7, 2021

Turns out this example isn't 100% spec-compliant because the bnode() function is scoped to solution. What this means is that when the query generates the next edge of the RDF list, it gets a new bnode for the end of the previous edge, not the bnode that was generated on the previous step. This breaks the continuity of the list.

I have created a ticket for Stardog to fix this but it makes this task very difficult (if not impossible) in pure SPARQL 1.1. It'd be easy to achieve without bnodes (ie using auto-generated IRIs) or using a SPARQL Update sequence where the 2nd query would turn the auto-generated IRIs created by the 1st into bnodes.

Thanks John Walker for spotting this.

@jaw111
Copy link

jaw111 commented Oct 7, 2021

Just for the lolz, here's how you can do it as a SPARQL Update procedure:

prefix : <http://example.com/>

insert data {
  :a :p :b .
  :b :p :c .
  :c :p :d .
};

# add bnode pointer to distinct subjects and objects of predicate p
insert {
  [] rdf:first ?x .
}
where {
  select distinct ?x {
    { ?x :p [] }
    union
    { [] :p ?x }
  }
};

# replace predicate p by relations between bnode pointers
delete {
  ?s :p ?o .
}
insert {
  ?s_ rdf:rest ?o_ .
}
where {
  ?s :p ?o .
  ?s_ rdf:first ?s .
  ?o_ rdf:first ?o .
};

# add the nil terminator
insert {
  ?s rdf:rest rdf:nil .
}
where {
  ?s rdf:first [] .
  minus {
    ?s rdf:rest []
  }
};

The result of that should be ( :a :b :c :d ).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment