Skip to content

Instantly share code, notes, and snippets.

@frankiesardo
Last active January 8, 2016 22:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save frankiesardo/9c96f8c0e9bc0b4b47ff to your computer and use it in GitHub Desktop.
Save frankiesardo/9c96f8c0e9bc0b4b47ff to your computer and use it in GitHub Desktop.
(ns percolation.core
"Currently working for non-recursive, fully specified (no [:*]), attributes only (no limit, defaults etc.) pull api queries."
(:require [datomic.api :as d]))
(def schema
"Percolation specific schema"
[{:db/id #db/id[:db.part/db]
:db/ident :percolator/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity
:db/doc "Unique name for the percolator"
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :percolator/entity
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one
:db/doc "The entity the percolator points to"
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :percolator/conditions
:db/isComponent true
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many
:db/doc "Conditions to be satisfied to trigger the percolator"
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :percolator/attribute
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one
:db/doc "The attribute this condition is tied to"
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :percolator/reverse?
:db/valueType :db.type/boolean
:db/cardinality :db.cardinality/one
:db/doc "If the attribute for the condition is looked up in reverse mode"
:db.install/_attribute :db.part/db}])
;;; Reverse query conversion
(defn expand
"Recursively expands a pull api pattern into a percolator list of subconditions."
[pattern]
(for [p pattern
[attr inner] (if (map? p) p [[p]])
:let [reverse? (.startsWith (name attr) "_")
attr (if reverse?
(keyword (namespace attr)
(.substring (name attr) 1))
attr)]]
(merge
{:percolator/attribute attr}
(when inner
{:percolator/conditions (expand inner)})
(when reverse?
{:percolator/reverse? true}))))
(defn ->percolator
"Transforms a pull api pattern into reverse search entity that can later be queried when new facts are asserted into the db (see percolate)."
[name pattern eid]
{:percolator/name name
:percolator/entity eid
:percolator/conditions (expand pattern)})
;;; Percolation
(def rules
"Rules used in reverse search queries"
'[[(contains ?perc ?condition ?e)
[?perc :percolator/conditions ?condition]
[?perc :percolator/entity ?e]]
[(contains ?perc ?subcondition ?v)
[?e ?a ?v]
[?condition :percolator/conditions ?subcondition]
[?condition :percolator/attribute ?a]
(contains ?perc ?condition ?e)]
[(contains ?perc ?subcondition ?e)
[?e ?a ?v]
[?condition :percolator/reverse? true]
[?condition :percolator/conditions ?subcondition]
[?condition :percolator/attribute ?a]
(contains ?perc ?condition ?v)]])
(defn percolate
"Find datoms in the last transaction that changed data that a percolator is currently observing."
[db]
(let [tx (d/t->tx (d/basis-t db))
datoms (d/q '{:find [?p ?e ?attr ?v]
:in [$ % ?tx]
:where [[?e ?a ?v ?tx]
[?condition :percolator/attribute ?a]
(contains ?perc ?condition ?e)
[?perc :percolator/name ?p]
[(datomic.api/ident $ ?a) ?attr]]}
db rules tx)]
(->> (for [[p e a v] datoms]
{p [[e a v]]})
(apply merge-with concat))))
;; Mbrainz example
(comment
(def uri "datomic:free://localhost:4334/mbrainz-1968-1973")
(def conn (d/connect uri))
@(d/transact conn schema)
(def db (d/db conn))
(def concert-for-bangla-desh
(:db/id (d/pull db [:db/id] [:release/gid #uuid "f3bdff34-9a85-4adc-a014-922eef9cdaa5"])))
(def pattern
[{:release/media
[{:medium/tracks
[:track/name {:track/artists [:artist/name]}]}]}])
(def pull-result (d/pull db pattern concert-for-bangla-desh))
;;
(def percolator
(->percolator "example" pattern concert-for-bangla-desh))
@(d/transact conn
[(assoc percolator :db/id (d/tempid :db.part/user))])
(def god-planned-it-track
(ffirst (d/q '[:find ?t :where [?t :track/name "That's the Way God Planned It"]] db)))
@(d/transact conn [{:db/id god-planned-it-track
:track/name "That's the Way I Planned It"}])
(percolate (d/db conn)))
;; {"example" [[17592186072011 :track/name "That's the Way I Planned It"]]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment