Let’s see how Molecule queries compare to Datalog queries used with the Datomic database.
Code examples are taken from the Seattle tutorial.
Follow along in the code from which we will pick a few examples.
The most basic query is to ask for entities with some attribute values:
// Datalog
[:find ?b ?c (distinct ?d)
:where [?a :Community/name ?b]
[?a :Community/url ?c]
[?a :Community/category ?d]]
In Molecule we use the Namespace name and add the Attribute names:
// Molecule
Community.name.url.category
Datalog has a :find
and :where
section similar to select
and where
in the SQL world. The :find
section defines which values to return and the :where
section defines one or more clauses filtering the result set.
In this case we asked for the values of variable ?b
, ?c
and ?d
each one bound in its where clause. With molecule we use the three attribute names all associated with the Community
namespace.
Let’s query by an enumerated value for the type
attribute:
[:find ?b
:where [?a :Community/name ?b]
[?a :Community/type ":Community.type/twitter"]]
Community.name.type_("twitter")
Note how we add an underscore to the type
attribute to tell Molecule that we want to omit returning this value in the result set (since it will have the value “twitter” for all returned entities). We then call it a “tacit” attribute.
Since the type
attribute is defined as an enumeration, Molecule checks the “twitter” value at compile time against the defined enumeration values that we have definied in our schema for the Community
namespace to ensure that “twitter” is one of the enums. If it is not, our molecule won’t compile and we’ll get an error showing the available enum values.
For a cardinality-many attribute like category
Datalog applies logical OR with Datalog rules as input:
[:find ?b
:in $ %
:where [?a :Community/name ?b]
(rule1 ?a)]
INPUTS:
List(
1 datomic.db.Db@xxx
2 [[(rule1 ?a) [?a :Community/category "news"]]
[(rule1 ?a) [?a :Community/category "arts"]]]
)
In Molecule we can apply or
logic directly:
Community.name.category_("news" or "arts")
Alternatively use comma:
Community.name.category_("news", "arts")
Getting Community name and the region it is in.
[:find ?b ?e2
:where [?a :Community/name ?b]
[?a :Community/neighborhood ?c]
[?c :Neighborhood/district ?d]
[?d :District/region ?e]
[?e :db/ident ?e1]
[(.getName ^clojure.lang.Keyword ?e1) ?e2]]
Community.name.Neighborhood.District.region
Community input molecule awaiting some type value
[:find ?b ?c2
:in $ ?c
:where [?a :Community/name ?b]
[?a :Community/type ?c]
[?c :db/ident ?c1]
[(.getName ^clojure.lang.Keyword ?c1) ?c2]]
INPUTS:
List(
1 datomic.db.Db@xxx
2 :Community.type/twitter
)
val communitiesOfType = m(Community.name.type(?))
val twitterCommunities = communitiesOfType("twitter")
Multiple input values for one attribute - logical OR
[:find ?b ?c2
:in $ ?c
:where [?a :Community/name ?b]
[?a :Community/type ?c]
[?c :db/ident ?c1]
[(.getName ^clojure.lang.Keyword ?c1) ?c2]]
INPUTS:
List(
1 datomic.db.Db@xxx
2 :Community.type/twitter
)
m(Community.name.`type`(?)).apply("facebook_page" or "twitter")
Single tuple of input values for two attributes - logical AND
[:find ?b
:in $ [[ ?c ?d ]]
:where [?a :Community/name ?b]
[?a :Community/type ?c]
[?a :Community/orgtype ?d]]
INPUTS:
List(
1 datomic.db.Db@xxx
2 [[:Community.type/email_list, :Community.orgtype/community]]
)
m(Community.name.type_(?).orgtype_(?))("email_list" and "community")
Multiple tuple of input values for two attributes - logical AND
[:find ?b ?c2 ?d2
:in $ [[ ?c ?d ]]
:where [?a :Community/name ?b]
[?a :Community/type ?c]
[?c :db/ident ?c1]
[(.getName ^clojure.lang.Keyword ?c1) ?c2]
[?a :Community/orgtype ?d]
[?d :db/ident ?d1]
[(.getName ^clojure.lang.Keyword ?d1) ?d2]]
INPUTS:
List(
1 datomic.db.Db@xxx
2 [[:Community.type/email_list, :Community.orgtype/community],
[:Community.type/website, :Community.orgtype/commercial]]
)
m(Community.name.`type`(?).orgtype(?))
.apply(Seq(("email_list", "community"), ("website", "commercial")))
[:find ?b
:where [?a :Community/name ?b]
[(.compareTo ^String ?b "C") ?b2]
[(< ?b2 0)]]
Community.name.<("C")
[:find ?b
:where [(fulltext $ :Community/name "Wallingford") [[ ?a ?b ]]]]
Community.name.contains("Wallingford")
Fulltext search on many-attribute (category
)
[:find ?b (distinct ?d)
:where [?a :Community/name ?b]
[?a :Community/type ":Community.type/website"]
[(fulltext $ :Community/category "food") [[ ?a ?d ]]]]
Community.name.type_("website").category.contains("food")
Social media communities
[:find ?b
:in $ %
:where [?a :Community/name ?b]
(rule1 ?a)]
INPUTS:
List(
1 datomic.db.Db@xxx
2 [[(rule1 ?a) [?a :Community/type ":Community.type/twitter"]]
[(rule1 ?a) [?a :Community/type ":Community.type/facebook_page"]]]
)
Community.name.type_("twitter" or "facebook_page")
Social media communities in southern regions
[:find ?b
:in $ %
:where [?a :Community/name ?b]
(rule1 ?a)
[?a :Community/neighborhood ?d]
[?d :Neighborhood/district ?e]
(rule2 ?e)]
INPUTS:
List(
1 datomic.db.Db@xxx
2 [[(rule1 ?a) [?a :Community/type ":Community.type/twitter"]]
[(rule1 ?a) [?a :Community/type ":Community.type/facebook_page"]]
[(rule2 ?e) [?e :District/region ":District.region/sw"]]
[(rule2 ?e) [?e :District/region ":District.region/s"]]
[(rule2 ?e) [?e :District/region ":District.region/se"]]]
)
Community.name.type_("twitter" or "facebook_page")
.Neighborhood.District.region_("sw" or "s" or "se")
Parameterized
[:find ?b
:in $ %
:where [?a :Community/name ?b]
[?a :Community/type ?c]
[?a :Community/neighborhood ?d]
[?d :Neighborhood/district ?e]
[?e :District/region ?f]
(rule1 ?a)
(rule2 ?e)]
INPUTS:
List(
1 datomic.db.Db@xxx
2 [[(rule1 ?a) [?a :Community/type ":Community.type/twitter"]]
[(rule1 ?a) [?a :Community/type ":Community.type/facebook_page"]]
[(rule2 ?e) [?e :District/region ":District.region/sw"]]
[(rule2 ?e) [?e :District/region ":District.region/s"]]
[(rule2 ?e) [?e :District/region ":District.region/se"]]]
)
m(Community.name.type_(?).Neighborhood.District.region_(?))
.apply(("twitter" or "facebook_page") and ("sw" or "s" or "se")
[:find ?b
:where [?a :db/txInstant ?b]]
Db.txInstant
List(
List( :db/add, #db/id[:db.part/user -1000001], :Community/name , AAA )
List( :db/add, #db/id[:db.part/user -1000001], :Community/url , myUrl )
List( :db/add, #db/id[:db.part/user -1000001], :Community/type , :Community.type/twitter )
List( :db/add, #db/id[:db.part/user -1000001], :Community/orgtype , :Community.orgtype/personal )
List( :db/add, #db/id[:db.part/user -1000001], :Community/category , my )
List( :db/add, #db/id[:db.part/user -1000001], :Community/category , favorites )
List( :db/add, #db/id[:db.part/user -1000001], :Community/neighborhood, #db/id[:db.part/user -1000002] )
List( :db/add, #db/id[:db.part/user -1000002], :Neighborhood/name , myNeighborhood )
List( :db/add, #db/id[:db.part/user -1000002], :Neighborhood/district , #db/id[:db.part/user -1000003] )
List( :db/add, #db/id[:db.part/user -1000003], :District/name , myDistrict )
List( :db/add, #db/id[:db.part/user -1000003], :District/region , :District.region/nw )
)
Community
.name("AAA")
.url("myUrl")
.`type`("twitter")
.orgtype("personal")
.category("my", "favorites")
.Neighborhood.name("myNeighborhood")
.District.name("myDistrict").region("nw").save
Multiple entities:
List(
List( :db/add, #db/id[:db.part/user -1000001], :Community/name , DDD Blogging Georgetown )
List( :db/add, #db/id[:db.part/user -1000001], :Community/url , http://www.blogginggeorgetown.com/ )
List( :db/add, #db/id[:db.part/user -1000001], :Community/type , :Community.type/blog )
List( :db/add, #db/id[:db.part/user -1000001], :Community/orgtype , :Community.orgtype/commercial )
List( :db/add, #db/id[:db.part/user -1000001], :Community/category , DD cat 1 )
List( :db/add, #db/id[:db.part/user -1000001], :Community/category , DD cat 2 )
List( :db/add, #db/id[:db.part/user -1000001], :Community/neighborhood, #db/id[:db.part/user -1000002] )
List( :db/add, #db/id[:db.part/user -1000002], :Neighborhood/name , DD Georgetown )
List( :db/add, #db/id[:db.part/user -1000002], :Neighborhood/district , #db/id[:db.part/user -1000003] )
List( :db/add, #db/id[:db.part/user -1000003], :District/name , Greater Duwamish )
List( :db/add, #db/id[:db.part/user -1000003], :District/region , :District.region/s )
List( :db/add, #db/id[:db.part/user -1000004], :Community/name , DDD Interbay District Blog )
List( :db/add, #db/id[:db.part/user -1000004], :Community/url , http://interbayneighborhood.neighborlogs.com/ )
List( :db/add, #db/id[:db.part/user -1000004], :Community/type , :Community.type/blog )
List( :db/add, #db/id[:db.part/user -1000004], :Community/orgtype , :Community.orgtype/community )
List( :db/add, #db/id[:db.part/user -1000004], :Community/category , DD cat 3 )
List( :db/add, #db/id[:db.part/user -1000004], :Community/neighborhood, #db/id[:db.part/user -1000005] )
List( :db/add, #db/id[:db.part/user -1000005], :Neighborhood/name , DD Interbay )
List( :db/add, #db/id[:db.part/user -1000005], :Neighborhood/district , #db/id[:db.part/user -1000006] )
List( :db/add, #db/id[:db.part/user -1000006], :District/name , Magnolia/Queen Anne )
List( :db/add, #db/id[:db.part/user -1000006], :District/region , :District.region/w )
)
Community.name.url.`type`.orgtype.category.Neighborhood.name.District.name.region insert List(
("DDD Blogging Georgetown", "http://www.blogginggeorgetown.com/", "blog", "commercial", Set("DD cat 1", "DD cat 2"), "DD Georgetown", "Greater Duwamish", "s"),
("DDD Interbay District Blog", "http://interbayneighborhood.neighborlogs.com/", "blog", "community", Set("DD cat 3"), "DD Interbay", "Magnolia/Queen Anne", "w")
)
Updating one-attribute
List(
List( :db/add, 17592186045649, :Community/name, belltown 2 )
List( :db/add, 17592186045649, :Community/url , url 2 )
)
Community(belltownId).name("belltown 2").url("url 2").update
Updating many-attribute
List(
List( :db/retract, 17592186045649, :Community/category, news )
List( :db/add , 17592186045649, :Community/category, Cool news )
)
Community(belltownId).category("news" -> "Cool news").update
Update multiple values of many-attribute
List(
List( :db/retract, 17592186045649, :Community/category, Cool news )
List( :db/add , 17592186045649, :Community/category, Super cool news )
List( :db/retract, 17592186045649, :Community/category, events )
List( :db/add , 17592186045649, :Community/category, Super cool events )
)
Community(belltownId).category(
"Cool news" -> "Super cool news",
"events" -> "Super cool events"
).update
Update multiple values of many-attribute
List(
List( :db/retract, 17592186045649, :Community/category, Cool news )
List( :db/add , 17592186045649, :Community/category, Super cool news )
List( :db/retract, 17592186045649, :Community/category, events )
List( :db/add , 17592186045649, :Community/category, Super cool events )
)
Community(belltownId).category(
"Cool news" -> "Super cool news",
"events" -> "Super cool events"
).update
Add a value to a many-attribute
List(
List( :db/add, 17592186045649, :Community/category, extra category )
)
Community(belltownId).category.assert("extra category").update
Remove value from a many-attribute
List(
List( :db/retract, 17592186045649, :Community/category, Super cool events )
)
Community(belltownId).category.retract("Super cool events").update
Mixing updates and deletes
List(
List( :db/add , 17592186045649, :Community/name , belltown 3 )
List( :db/retract, 17592186045649, :Community/url , http://www.belltownpeople.com/ )
List( :db/retract, 17592186045649, :Community/category, events )
List( :db/retract, 17592186045649, :Community/category, news )
)
Community(belltownId).name("belltown 3").url().category().update