asami

Bart Kleijngeld 2023-02-04T09:41:43.424879Z

I may have asked this before, but how would one go about querying RDF lists? In SPARQL you can use property paths to achieve this relatively easily:

SELECT *
WHERE {
    :myNodeShape (sh:and/rdf:rest*/rdf:first)* ?otherShapes .
}
AFAIK Asami supports * and + , but not property paths (`/`). Is that right? Any tips on how to deal with this?

quoll 2023-02-06T20:36:42.928899Z

That will get you down nested lists, which asami can do, but I’m guessing that’s not what you want?

quoll 2023-02-06T20:39:00.135919Z

:find 
:where [:myNodeShape sh:and ?head]
       [?head :rdf/rest* ?list]
       [?list :rdf/first ?otherShapes]

quoll 2023-02-06T20:42:05.619779Z

This binds ?head to the first list node. Using :rdf/rest* means that ?list will bind to every node in the list, including the ?head node (which is zero steps away from the node bound to ?head). For each one of these nodes, you get the :rdf/first value, which is the data in the list.

quoll 2023-02-06T20:45:38.359869Z

Your original query is a property path that is very strange… Step along sh:and. From there, follow zero or more rdf:rest steps (which will be everything in the list), and then follow the rdf:first step to the entry in the list. From each of these list elements, recurse. i.e. follow a sh:and to the head of a list, and traverse to it’s nodes, recursing on each of these as well.

quoll 2023-02-06T20:46:24.010179Z

I had never considered nested transitive closures. This will be interesting to encode when I get to that part of SPARQL 😬

Bart Kleijngeld 2023-02-06T22:19:38.749509Z

Hmm reading back I might have butchered the example a bit. I'll look at it closer tomorrow, it's bed time here now. But indeed, nested transitivity is probably a cool and powerful thing to support anyways :)

Bart Kleijngeld 2023-02-08T10:53:13.762469Z

@quoll To provide a little more context: I want to recursively fetch all node shapes being referred to through sh:and. So if I have:

:AShape
  a sh:NodeShape ;
  sh:and ( :BShape :CShape )
.

:BShape
  a sh:NodeShape ;
  sh:and ( :DShape )
.

:CShape a sh:NodeShape .
:DShape a sh:NodeShape .
:EShape a sh:NodeShape .
I'd like to obtain:
:AShape
:BShape
:CShape
:DShape

Bart Kleijngeld 2023-02-08T10:54:41.172009Z

> From there, follow zero or more rdf:rest steps (which will be everything in the list), and then follow the rdf:first step to the entry in the list. Also, following rdf:rest* does not give everything in the list, but everything but the first element, right?

quoll 2023-02-08T15:04:04.887359Z

No, rdf:rest* is the entire list. rdf:rest+ is everything by the first element

quoll 2023-02-08T15:04:05.058049Z

OK, if you have nested shapes, then are you going for an arbitrary level of nesting, or just 1 level?

Bart Kleijngeld 2023-02-08T16:35:43.707919Z

Arbitrary level of nesting was the idea

quoll 2023-02-08T16:36:21.406739Z

Then your original query was actually correct. And I need to add that functionality

👍 1
quoll 2023-02-08T16:37:29.367179Z

In the meantime, I’d use a magic sets approach

Bart Kleijngeld 2023-02-08T16:40:04.411119Z

I see now I was indeed mistaken in how I read the rdf:rest*. What is the magic set approach? Something "Google"-able suffices

quoll 2023-02-08T16:41:39.217779Z

INSERT {?outer internal:includes ?inner}
WHERE {
  ?outer a sh:NodeShape ;
         sh:and/rdf:rest*/rdf:first ?inner
}
Then you can do simple queries of:
SELECT ?other
{ :myNodeShape internal:includes ?other }

quoll 2023-02-08T16:42:16.658469Z

It just means that you instantiate extra edges that meet the requirements of what you’re looking for

quoll 2023-02-08T16:44:36.980849Z

You can do it without instantiation if you use subqueries… Which you can actually do with Asami, but I never showed anyone how. It’s not (yet) part of the query syntax. Instead, you feed the results of one query into the input of the next

Bart Kleijngeld 2023-02-08T16:51:10.503529Z

Ah right, so those are SPARQL queries ^. And I use these to CONSTRUCT (instead of INSERT, right?) triples like you suggest, and then fetching them is straightforward, also in Asami. I get the gist at least

quoll 2023-02-08T16:53:19.020139Z

Ah, wait… I forgot that the INSERT/WHERE id still only doing a single step. Sigh. You can loop on that in code, but it’s messy

quoll 2023-02-08T17:00:11.961879Z

I’m in a meeting, so I only have half my attention on the problem. Consequently, the following is probably wrong. But I would use this kind of approach:

(loop [nodes #{:myNodeShape}]
  (let [part (q '[:find [?inner ...]
                  :in $ [?node ...]
                  :where [?node :a :sh/NodeShape]
                         [?node :sh/and ?list]
                         [?list :rdf/rest* ?node]
                         [?node :rdf/first ?inner]]
                my-db nodes)
        next-nodes (into nodes part)]
    (if (= next-nodes nodes)
      nodes
      (recur next-nodes))))

quoll 2023-02-08T17:01:03.620589Z

The idea is, use the output of a query as the input of the next query. That’s how subqueries usually work too (you can thread queries into each other with ->>)

quoll 2023-02-08T17:02:38.196609Z

And that’s how I do transitivity. I collect things in sets, and loop on the query. When the set doesn’t change, you know you’ve finished the search

quoll 2023-02-08T17:03:28.846569Z

It’s a little more low-level, which allows me to filter out things that have already been seen, reducing the size of each step, but that’s just an optimization

quoll 2023-02-08T17:05:51.392889Z

I’ve corrected it now, but the :in clause needs “relations bindings” because that’s the result format from a prior query

quoll 2023-02-08T17:06:14.211359Z

i.e. [[?nodes]] needs the double brackets

Bart Kleijngeld 2023-02-08T17:27:24.770309Z

I'm going to have to read this a few times and mull over it somewhat to understand it in detail. But I understand the gist, and I see a solution in this indeed. Thanks, I'm learning a lot as always!

quoll 2023-02-08T17:33:19.414509Z

I just realized… I needed to extract results, (which I could do, by mapping first across the output), but I also needed to wrap nodes in a seq, since it’s not actually a prior result. It’s just easier to project the results into a seq instead. Fixed.

quoll 2023-02-08T17:33:35.272039Z

See? I told you I only had half of my attention on this. (I’m very sorry)

Bart Kleijngeld 2023-02-08T17:35:20.291099Z

Haha don't be sorry. Grateful for your help, even during costly meetings 😉

👍 1