asami

nivekuil 2023-09-30T06:54:38.125809Z

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

quoll 2023-09-30T15:03:54.781909Z

By joining to a statement that has that nil

quoll 2023-09-30T15:04:16.837439Z

There’s no way in a filter though

quoll 2023-09-30T15:04:59.898979Z

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

nivekuil 2023-09-30T19:11:33.543589Z

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

quoll 2023-09-30T19:15:09.924679Z

ack... that query is breaking my head

quoll 2023-09-30T19:15:18.998149Z

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

nivekuil 2023-09-30T19:15:33.346159Z

a full outer join I think

quoll 2023-09-30T19:15:51.413299Z

it definitely won't do that

quoll 2023-09-30T19:16:06.237049Z

it'll try to match on the ?e values

quoll 2023-09-30T19:16:31.896149Z

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

quoll 2023-09-30T19:16:38.092769Z

ummm, let me think

quoll 2023-09-30T19:17:51.848429Z

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

quoll 2023-09-30T19:22:21.906019Z

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?

nivekuil 2023-09-30T19:22:56.997179Z

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

quoll 2023-09-30T19:23:19.467039Z

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

quoll 2023-09-30T19:23:34.635789Z

I can't recall if it survives the projection

nivekuil 2023-09-30T19:23:40.944279Z

ah, yeah it's there

quoll 2023-09-30T19:23:43.133109Z

but that's how I keep track of columns

nivekuil 2023-09-30T19:23:45.840349Z

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

quoll 2023-09-30T19:24:15.969039Z

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

quoll 2023-09-30T19:26:35.940689Z

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

nivekuil 2023-09-30T19:29:09.217829Z

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

quoll 2023-09-30T19:29:19.444039Z

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]))

quoll 2023-09-30T19:30:52.539969Z

I think.

quoll 2023-09-30T19:32:46.527559Z

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

quoll 2023-09-30T19:32:49.058639Z

I think

quoll 2023-09-30T19:34:31.816419Z

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.

quoll 2023-09-30T19:34:50.545679Z

after that, it would go back to the usual behavior

quoll 2023-09-30T19:35:05.430849Z

what are you actually trying to do?

nivekuil 2023-09-30T19:35:16.639719Z

pretty sure I just want a full outer join 😛

nivekuil 2023-09-30T19:35:35.056899Z

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

nivekuil 2023-09-30T19:35:41.507429Z

but I use asami as a backend for odoyle rules

quoll 2023-09-30T19:35:42.984459Z

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

quoll 2023-09-30T19:35:59.390329Z

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

nivekuil 2023-09-30T19:37:08.011079Z

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

nivekuil 2023-09-30T19:39:21.482269Z

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

nivekuil 2023-09-30T19:40:29.771839Z

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

nivekuil 2023-09-30T19:40:41.279169Z

like i said, bit complicated.. but you asked

nivekuil 2023-09-30T19:42:15.464189Z

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

nivekuil 2023-09-30T19:43:47.242079Z

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

quoll 2023-09-30T19:44:14.682849Z

2 queries was my first thought 🙂

quoll 2023-09-30T19:47:58.071159Z

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.

nivekuil 2023-09-30T19:49:43.421219Z

really? how do you affirm absence

nivekuil 2023-09-30T19:50:19.838389Z

https://www.wikidata.org/wiki/Wikidata:Requests_for_comment/Unknown_Value_vs_Null_Value I guess there's a random proposal here for wd

quoll 2023-09-30T19:50:35.156499Z

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

quoll 2023-09-30T19:51:24.917669Z

There's also rdf:nil

nivekuil 2023-09-30T19:52:06.586549Z

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

quoll 2023-09-30T19:52:07.234519Z

though that's actually a list

quoll 2023-09-30T19:52:29.269629Z

well, nil is current allowed to be stored

nivekuil 2023-09-30T19:52:35.958339Z

that's true

nivekuil 2023-09-30T19:52:45.156529Z

I guess the user could do it

quoll 2023-09-30T19:53:07.231689Z

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

quoll 2023-09-30T19:53:29.242569Z

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

quoll 2023-09-30T19:53:52.989859Z

(that's a SPARQL wrapper)

quoll 2023-09-30T19:55:14.221029Z

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

quoll 2023-09-30T23:35:00.027749Z

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

❤️ 4
quoll 2023-09-30T23:35:08.021249Z

That was a LOT of work

🥳 3
quoll 2023-09-30T23:38:46.599139Z

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

quoll 2023-09-30T23:38:57.975609Z

Thank goodness I had a lot of tests

🚀 2