Open den1k opened 3 years ago
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
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!
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.
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).
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.)
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.
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
How would one query relationships? For example what when todos have sub-todos:
This can be normalized and added to the session one by one:
But how does one query it??