Fork me on GitHub
#datomic
<
2021-09-12
>
schmee14:09:48

I have the following query, which find all the quantities of products in storage and subtracts any reservations:

(defn find-quantities-for-product [product-eid]
  (d/q '[:find ?sid ?pid ?q
         :keys storage/id product/id quantity
         :in $ [?p ...]
         :where
         [?sp :storage.product/product ?p]
         [?s :storage/products ?sp]
         [?s :storage/id ?sid]
         [?p :product/id ?pid]
         [?sp :storage.product/quantity ?sq]

         [?r :reservation/product ?p]
         [?r :reservation/storage ?s]
         [?r :reservation/quantity ?rq]

         [(- ?sq ?rq) ?q]]
       @db
       product-eid))
since Datalog unifies everything, this means that if something is in storage somewhere, but does not have a reservation, it does not get included in the result. I’ve tried every imaginable combination of or, or-join and get-else to accomplish something like “if there is a reservation, get the amount for it, otherwise consider it 0”. Is there a way to do this or do I need to do two queries and manually combine the results?

Fredrik16:09:58

Would something like this work?

(def reservations-rules
  '[[(reservation-quantity ?p ?s ?rq)
     [?r :reservation/product ?p]
     [?r :reservation/storage ?s]
     [?r :reservation/quantity ?rq]]
    [(reservation-quantity ?p ?s ?rq)
     (not-join [?p]
               [?r :reservation/product ?p]
               [?r :reservation/storage ?s])
     [(ground 0) ?rq]]])

(defn find-quantities-for-product [product-eid]
  (d/q '[:find ?sid ?pid ?q
         :keys storage/id product/id quantity
         :in $ % [?p ...]
         :where
         [?sp :storage.product/product ?p]
         [?s :storage/products ?sp]
         [?s :storage/id ?sid]
         [?p :product/id ?pid]
         [?sp :storage.product/quantity ?sq]

         (reservation-quantity ?p ?s ?rq)

         [(- ?sq ?rq) ?q]]
       (d/db conn-mem2) reservations-rules product-eid))

Fredrik16:09:27

Assuming I got your schema right and trusting a few tests, I think this should work. But I'd also consider splitting it up into multiple queries and move the logic from the query into the code.

favila20:09:52

nm, yes, those are the rules I would suggest too.

Fredrik20:09:47

Sooner or later, all variables inside a not will need unification. If I understand the semantics correctly, then in this case, the difference between not and not-join is that when invoking the second version of the rule reservation-quantity , we don't want to (indeed cannot) unify ?r .

favila21:09:04

I think he still needs to unify on ?s

favila21:09:43

depends on his schema, but it looks like a reservation is found by matching a storage and a product

Fredrik21:09:59

?s is unified before calling reservation-quantity

Fredrik21:09:43

So either the first version of the rule succeeds, with a unified reservation, or the second rule succeeds, but (and this is why using or wouldn't work) both versions of the rule never succeed at the same time

Fredrik21:09:55

Your observation on the schema is the assumption I made when testing this too. As an aside, if the product entities referenced the reservations directly, this query would have been a lot easier to write.

schmee21:09:48

adding ?s to the not-join gives exactly what I need! for my understanding, is it possible to write this without rules? or is this “either or” behavior exclusive to rules? thank you very much @U024X3V2YN4 and @U09R86PA4! 🙏

favila21:09:46

You can use or+and. This is just sugar for rules

favila21:09:28

The xor behavior is from the two implementations having mutually exclusive matches

favila21:09:16

They have the same two clauses, but one not-join-s them the other doesn’t

schmee21:09:19

yeah, just tried with or/and it gives the same result :thumbsup:

Fredrik21:09:20

@U3L6TFEJF where did you need to add the ?s ? Inside not-join [?p ?s] ?

schmee21:09:11

gotcha, I’ll commit the not-join pattern to my brain! one more question for my understanding: why doesn’t or-join work here? intuitively I’m asking for “this value or 0”, so it seems I’m not understanding what or-join means in Datomic fully :thinking_face:

schmee21:09:43

ahh: > With or clauses, you can express that one or more logic variables inside a query satisfy *at least one* of a set of predicates.

Fredrik21:09:50

You are wondering why this doesn't work?

(or-join [rq]
         (and [?r :reservation/product ?p]
              [?r :reservation/storage ?s]
              [?r :reservation/quantity ?rq])
         [(ground 0) ?rq])

Fredrik21:09:48

If a product has a reservation, it will return the quantity both with and without the reservation applied

schmee21:09:49

yeah, I was thinking about or as “this or that, but not both”, but reading the docs it’s clear that it matches at least one

Fredrik21:09:57

Exactly 😉

schmee21:09:32

clarity achieved, cheers to you both! 🌸

Fredrik21:09:40

For my own clarity: Why was adding ?s in the not-join needed?

schmee22:09:44

for whatever reason, without ?s in the not-join products in storages with no reservation don’t get included in the result

Fredrik22:09:46

Of course, that makes sense. Not having ?s in the vector means the unification of ?s to only those storages with reservations "escapes" the not-join .

👍 2
ghadi13:09:03

always always always pass the db as the primary argument to a function that queries a database.

👍 2
schmee15:09:57

@U050ECB92 yessir, this is still very much at the sketching stage so I’m taking some liberties :thumbsup:

xlfe23:09:20

Is there a way to use fulltext search on a heterogeneous tuple with some string types? I can transact the following schema, but can't figure out how to get fulltext to match the entity

{ :db/ident       :patient/name                                                                       
  :db/valueType   :db.type/tuple                                                              
  :db/cardinality :db.cardinality/many
  :db/fulltext    true                                                                        
  :db/tupleTypes  [                                                                           
                   :db.type/long   
                   :db.type/keyword
                   :db.type/string 
                   :db.type/string 
                   :db.type/string 
                   :db.type/string 
                   :db.type/instant                                                           
                   :db.type/instant]}

xlfe23:09:27

My tests would seem to indicate dispite the succesful schema txn, there is no fulltext index generated for entities with :patient/name

Fredrik00:09:11

It seems that fulltext only applies to :db/valueType :db.type/string .

Fredrik00:09:29

I hope others can come up with a better workaround, but if you want to keep the tuple model I can suggest something like this:

(def schema [{:db/ident :
              :db/valueType :db.type/long
              :db/cardinality :db.cardinality/one}
             {:db/ident :
              :db/valueType :db.type/keyword
              :db/cardinality :db.cardinality/one}
             {:db/ident :
              :db/valueType :db.type/string
              :db/cardinality :db.cardinality/one
              :db/fulltext true}
             {:db/ident :
              :db/valueType :db.type/string
              :db/cardinality :db.cardinality/one
              :db/fulltext true}
             {:db/ident :
              :db/valueType :db.type/string
              :db/cardinality :db.cardinality/one
              :db/fulltext true}
             {:db/ident :
              :db/valueType :db.type/string
              :db/cardinality :db.cardinality/one
              :db/fulltext true}
             {:db/ident :
              :db/valueType :db.type/instant
              :db/cardinality :db.cardinality/one}
             {:db/ident :
              :db/valueType :db.type/instant
              :db/cardinality :db.cardinality/one}
             {:db/ident       :patient/name
              :db/valueType   :db.type/tuple
              :db/cardinality :db.cardinality/one
              :db/fulltext    true
              :db/tupleAttrs  [:
                               :
                               :
                               :
                               :
                               :
                               :
                               :

Fredrik00:09:19

Then you can run queries like this:

[:find ?long
      :where
      [(fulltext $ : "s1") [[?e ?val]]]
      [?e :patient/name ?pn]
      [(untuple ?pn) [?long]]]