asami 2023-09-30

is there a way to distinguish between a nil because of (optional ...) and an explicit nil?

By joining to a statement that has that nil

There’s no way in a filter though

One day. I need to filter on patterns soon because I need an “exists” operation for SPARQL

i'm somehow even more confused.. an example:

(d/transact conn {:tx-triples [[1 :foo 1]
                               [1 :bar nil]
                               [2 :foo 2]]})
  
  (d/q '[:find ?foo ?bar
         :where 
         (optional [?e :foo ?foo])
         (optional [?e :bar ?bar])]
       conn)
that query returns two tuples with the same value, but I'd like to distinguish between the two entities

ack... that query is breaking my head

what are you trying to do with it???????

a full outer join I think

it definitely won't do that

it'll try to match on the ?e values

oh, sorry... I was thinking you meant outer product

ummm, let me think

So... the first one will probably return ^{:cols [?e ?foo]} [[1 1] [2 2]]

Then joining this with optional will match on the ?e. So then for ?e=1 it will return ^{:cols [?e ?foo ?bar]} [[1 1 nil]] as a full binding. For ?e=2 it will return ^{:cols [?e ?foo ?bar]} [[2 2 nil]] where it extrapolates to fill in that binding because it's optional. So the result would be ^{:cols [?e ?foo ?bar]} [[1 1 nil] [2 2 nil]] . Is that what you get?

what's the syntax you're using? the raw output is ([1 nil] [2 nil])

^{:cols [?e ?foo ?bar]} is the metadata

I can't recall if it survives the projection

ah, yeah it's there

but that's how I keep track of columns

though i don't query for ?e but looks right yeah

projection is where I take the find clause and pull out just the columns that were asked for

If you want a full outer product you'll want to union between two optionals

just the outer join. I did try

(d/q '[:find ?foo ?bar
               :where 
               (or (optional [?e :foo ?foo])
                   [(identity :undefined) ?foo])
               
               (or (optional [?e :bar ?bar])
                   [(identity :undefined) ?bar])]
             conn)
but i don't know what that ended up doing

ideally, you'd have something that selected the object types you wanted, then you could have something like:

:find ?foo ?bar
:where [?e :type :a_type]
       (or (optional [?e :foo ?foo]) (optional [?e :bar ?bar]))

I think that binds the variable (`?foo` or ?bar) first, then looks for a matching ?e, which it doesn't find, so returns nil for each value. Then it tries to join on the 2, and fails to find a match

optional was designed to expand your already existing bindings. So if you've selected people, then it can be used to add their email, but return nil if they don't have one. You're using optional when no binding exits. That's going to return everything that matches the binding. At least for the first one.

after that, it would go back to the usual behavior

what are you actually trying to do?

pretty sure I just want a full outer join 😛

or you mean higher level than that? it's a bit compilcated

but I use asami as a backend for odoyle rules

OK, that's a SQL operation. I tend to think of these operations in terms of what you're actually trying to do

so, do you know what you're trying to do?

odoyle rules can be added dynamically. a rule fires when all its clauses match, but if we add a rule dynamically, we can run into a problem where the state it would match on was added before the rule was added. so we need to hydrate from somewhere

if the full match can be hydrated, the default inner join works. but we actually need partial hydration. consider if the fact [1 :foo "foo"] comes in, then the subscription [:what [_ :foo foo] [_ :bar bar]] queries asami, it doesn't fully match, so it would get hydrated nothing! so now [1 :bar "bar"] would not cause the subscription to fire even though asami knows enough facts to satisfy the match

so wrapping everything in optional solves partial hydration but also can give wrong hydration, since we have to distinguish between facts with nil values and facts that were never asserted

like i said, bit complicated.. but you asked

higher level than that, this enables reactive datalog-driven UI

all that said the easy workaround is to do two queries with/without optional and diff them, was just wondering if there's a better way to do it

2 queries was my first thought 🙂

The only other thing that comes to mind is updating Asami to return an "unbound" value for optional. The thing about optional was that I was trying to copy SPARQL, and SPARQL doesn't allow for nil values.

really? how do you affirm absence

In RDF? Typically with a predicate that indicates that something is not present

There's also rdf:nil

would it be more correct to reify nil instead of unbound then

though that's actually a list

well, nil is current allowed to be stored

that's true

I guess the user could do it

and you'll see it in normal results. Only unions and optionals can have unbound. And I really should split that apart

in fact, now that I say it, I will need this when I bring in Twylyte

(that's a SPARQL wrapper)

(def unbound (Object.))
Then project will need to convert these to nil, and isBound can test for equality.

Yay! Raphael 0.3.0 is done! Now I can integrate it into Asami!

❤️ 4

That was a LOT of work

🥳 3

The commit says: Showing 8 changed files with 994 additions and 883 deletions.

Thank goodness I had a lot of tests

🚀 2