Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relationships? #3

Open
den1k opened this issue Dec 11, 2020 · 7 comments
Open

Relationships? #3

den1k opened this issue Dec 11, 2020 · 7 comments

Comments

@den1k
Copy link

den1k commented Dec 11, 2020

How would one query relationships? For example what when todos have sub-todos:

{:id        :todo1
 :text      "do this"
 :sub-todos [{:id   :todo2
              :text "but first do this!"}]}

This can be normalized and added to the session one by one:

[{:id        :todo1
  :text      "do this"
  :sub-todos [[:id :todo2]]}
 {:id   :todo2
  :text "but first do this!"}]

But how does one query it??

@oakes
Copy link
Owner

oakes commented Dec 11, 2020

I would do it like this:

(require '[odoyle.rules :as o])

(def rules
  (o/ruleset
    {::todo
     [:what
      [id ::text text]
      :then-finally
      (->> (o/query-all o/*session* ::todo)
           (reduce #(assoc %1 (:id %2) %2) {})
           (o/insert! ::todos ::by-id))]

     ::todo-with-sub-todos
     [:what
      [id ::text text]
      [id ::sub-todos sub-todos]]

     ::update-sub-todos
     [:what
      [id ::sub-todo-ids sub-todo-ids]
      [::todos ::by-id id->todo]
      :then
      (->> (mapv id->todo sub-todo-ids)
           (o/insert! id ::sub-todos))]}))

(def initial-session
  (reduce o/add-rule (o/->session) rules))

(-> initial-session
    (o/insert 1 {::text "do this"
                 ::sub-todo-ids [2]})
    (o/insert 2 ::text "but first do this!")
    o/fire-rules
    (o/query-all ::todo-with-sub-todos)
    println)

;; [{:id 1, :text "do this", :sub-todos [{:id 2, :text "but first do this!"}]}]

I'm saving all the todos as a derived fact (i added :then-finally recently, see the README for explanation). The ::update-sub-todos rule will ensure that the sub todos always match the sub-todo-ids that are inserted.

With rules it kind of turns "querying" inside out but it accomplishes the same thing :D

@den1k
Copy link
Author

den1k commented Dec 11, 2020

Very interesting! This is probably too granular for users who want to express relationships all over their programs. Do you think it would make sense performance-wise to build an in-memory graph-db on top of odoyle-rules?

Having performant rules on top of a graph-db that can derive facts on itself and cause side effects is highly exciting!

@oakes
Copy link
Owner

oakes commented Dec 11, 2020

I've been thinking about it recently actually. The ::by-id derived fact is a simple example of creating an "index" (in the case, a map of ids -> todos) that is constantly kept up to date. But it is completely recomputed when new todos are inserted, so if you have thousands of todos it could become slow.

I'd like to figure out how to update that map incrementally, but i haven't come up with a good approach yet. That would allow it to possibly behave like a normal database, maintaining all sorts of indexes of facts and only recomputing when necessary. That would be really neat.

@oakes
Copy link
Owner

oakes commented Jan 6, 2021

FYI for 0.6.0 i wrote a longer write-up about this topic, which shows how to do this recursively (sub-todos with their own sub-todos).

@jtrunick
Copy link

From looking at Datomic it appears you can have a one-to-many relationship by specifying db.cardinality/many in which case the id's will be repeated for each entry of the "many" attribute. I was expecting something similar with O'Doyle, and was surprised it appears different in that regard. I was hoping to stay more in "triplet-land" and not need to resort to Clojure as much. (Newb at this stuff, so my understanding may be incorrect.)

@oakes
Copy link
Owner

oakes commented Jan 28, 2023

Right, in odoyle the triplets must have a unique id+attribute combo; that is a strict requirement. You are technically still in triplet land; you're just inserting data structures derived from the original triplets as new triplets. You won't be able to avoid resorting to Clojure; in fact, using it more often is one of the goals. See the very end of the writeup I mentioned above.

@flow-danny
Copy link

flow-danny commented Jun 17, 2023

If o/insert and o/insert! accepted functions as values (similar to swap!) then incremental updates would be possible by conj and disj the relationship, without first having to query-all

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants