SQL LIKE operator in datomic - datomic

I want to run a sql query, that given a search keyword, will find all users, where their name matches that pattern. i.e in raw SQL something like WHERE users.name LIKE "%foo%"
How owuld I go about doing that?
Current structure of query ->
(defn find-users [db, search]
(->> (d/q '[:find ?u :where
[?u :user/uuid ?id]
[?u :user/name ..]
db)
(map first)))

This is what I use. Maybe you can adapt it to your needs.
(defn find-items "Full text search titles and descriptions for [search-term]" [search-term]
(let [keyys [:item-id :title :description]
rules '[[(finditem ?item ?term) [(fulltext $ :item/title ?term) [[?item ?name]]]]
[(finditem ?item ?term) [(fulltext $ :item/description ?term) [[?item ?name]]]]]
items (d/q '[:find ?item ?title ?description
:in $ ?term %
:where
(finditem ?item ?term)
[?item :item/title ?title]
[?item :item/description ?description]]
(d/db db/CONN)
search-term rules)]
(map #(zipmap keyys %) items)))
This uses rules which you can have a read about here: http://docs.datomic.com/query.html. Rules work as a pretty good SQL OR equivalent which is how I'm searching for a needle in two haystacks in the above example.

Use the fulltext function. The following example from the Datomic docs illustrates it well:
;; query
[:find ?entity ?name ?tx ?score
:in $ ?search
:where [(fulltext $ :artist/name ?search) [[?entity ?name ?tx ?score]]]]
;; inputs
db, "Jane"
;; result
#{[17592186047274 "Jane Birkin" 2839 0.625]
[17592186046687 "Jane" 2267 1.0]
[17592186047500 "Mary Jane Hooper" 3073 0.5]}
You'll also want to index the fields you're searching for fulltext search in your schema with :db/fulltext true

Related

Find all entities that are missing a particular attribute

In my schema I have the attribute :base/type that is supposed to exist for every entity created. To check that this is indeed true, I'm trying to find entities where it is missing:
[:find [?entities ...]
:in $ :where
[(missing? $ ?entities :base/type)]]
Unfortunately this gives me back:
Execution error (Exceptions$IllegalArgumentExceptionInfo) at datomic.error/arg (error.clj:57).
:db.error/insufficient-binding [?entities] not bound in expression clause: [(missing? $ ?entities :base/type)]
How should this query be constructed?
This is because your query is too general. You need at least one positive clause in where statement if you use query API. You can access raw index to get the result, though. If you have enough RAM, you can:
(def snapshot (d/db connection)) ; your db snapshot
(def all-datoms (d/datoms snapshot :eavt))
(def base-type-id (:db/id (d/entity snapshot :base/type))
(def entities (group-by #(.e %) all-datoms))
(def entities-without-base-type (map
(comp #(into {} %) (partial d/entity snapshot) first)
(filter (fn [[k l]]
(empty? (filter #(= base-type-id (.a %))
l)))
entities)))
(def only-relevant-entities (filter #(not (or (:db/ident %) (:db/txInstant %))) entities-without-base-type))
only-relevant-entities
The last filter is to get rid of attribute definitions and transactions (they are stored as datoms in the db as well!).
If you have too many entities you can chunk datoms using the async atoms API.
Using ::singer/songs as an example attribute, this is how to do the query:
[:find [?entities ...]
:in $ :where
[?entities ::singer/songs ?s]
[(missing? $ ?entities :base/type)]]
Unfortunately (in my answer) many such queries would be required until the whole database has been covered. So another query with ::song/composers, etc...

Parameterized and case insensitive query in datalog (datomic)

I want to compose a query that takes a firstname as input parameter and returns all matching records. A match should be case insensitive. As an example I'd like to extract all the people named Douglass. Parameterized, but case sensitive this would be :
(d/q '[:find (pull ?e [*])
:in $ ?par
:where
[?e :person/firstname ?par]
] db "Douglass")
The following query will yield all matches regardless the case, but is not parameterized (the ?par is not used yet, but a placeholder for the parameterized query) :
(d/q '[:find (pull ?e [*])
:in $ ?par
:where
[?e :person/firstname ?bfn]
[(re-find (re-pattern "(?i)DouGLASS") ?bfn)]
] db "")
But I'm not able to combine them. A - probably naive - approach is throwing Unable to resolve symbol: ?par in this context :
(d/q '[:find (pull ?e [*])
:in $ ?par
:where
[?e :person/firstname ?bfn]
[ (re-find (re-pattern (str "(?i)" ?par)) ?bfn)]
] db "Douglass")
So : how to pass the firstname for this case?
As described here, the issue is that function expressions in Datomic Datalog do not nest. You could break it out like this (using a tested query against the mbrainz database).
(d/q '[:find ?name ?year
:in $ ?match
:where [(str "(?i)" ?match) ?matcher]
[(re-pattern ?matcher) ?regex]
[(re-find ?regex ?aname)]
[?a :artist/name ?aname]
[?r :release/artists ?a]
[?r :release/name ?name]
[?r :release/year ?year]]
(d/db conn) "pink floyd")
Returns:
#{["Point Me at the Sky" 1972]
["Any Colour You Like" 1972]
["The Dark Side of the Moon" 1973]
["Obscured by Clouds" 1972]
...
You could also write your own function and call it from Datalog.

How can I query the entire schema that is stored inside of a datomic database?

I have a datomic database that I have updated with multiple schemas. I am looking to get the entire schema back as a map seperating each schema. This is the query I have currently:
(d/q '[:find ?id
:where [:db.part/db :db.install/attribute ?p]
[?p :db/ident ?id]] db)
You want to query to find everything with :db.install/attribute, you can see an example in this gist as well as the datomic java examples repo on github.
I've reproduced the example from the gist here:
(require '[datomic.api :as d] 'clojure.pprint)
;; You can run this with bin/run in the $DATOMIC_DIR
(println "Printing database schema...")
(def conn (d/connect (first *command-line-args*))) ;; call with db-uri as arg
; Find and pretty-print each attribute in schema
(let [db (d/db conn)]
(clojure.pprint/pprint
(map #(->> % first (d/entity db) d/touch)
(d/q '[:find ?v
:where [_ :db.install/attribute ?v]]
db))))

Query to list all partitions in Datomic

What is a query to list all partitions of a Datomic database?
This should return
[[:db.part/db] [:db.part/tx] [:db.part/user] .... ]
where .... is all of the user defined partitions.
You should be able to get a list of all partitions in the database by searching for all entities associated with the :db.part/db entity via the :db.install/partition attribute:
(ns myns
(:require [datomic.api :as d]))
(defn get-partitions [db]
(d/q '[:find ?ident :where [:db.part/db :db.install/partition ?p]
[?p :db/ident ?ident]]
db))
Note
The current version of Datomic (build 0.8.3524) has a shortcoming such that :db.part/tx and :db.part/user (two of the three built-in partitions) are treated specially and aren't actually associated with :db.part/db via :db.install/partition, so the result of the above query function won't include the two.
This problem is going to be addressed in one of the future builds of Datomic. In the meantime, you should take care of including :db.part/tx and :db.part/user in the result set yourself.
1st method - using query
=> (q '[:find ?i :where
[:db.part/db :db.install/partition ?p] [?p :db/ident ?i]]
(db conn))
2nd method - from db object
(filter #(instance? datomic.db.Partition %) (:elements (db conn)))
The second method returns sequence of datomic.db.Partition objects which may be useful if we want to get additional info about the partition.
Both methods have known bug/inconsistency: they don't return :db.part/tx and :db.part/user built-in partitions.

Using a database function in a datomic query

I'm trying to do an 'outer join' in Datomic via the REST API. From https://github.com/Datomic/day-of-datomic/blob/master/tutorial/social_news.clj I have taken the final example:
(defn maybe
"Returns the set of attr for e, or nil if e does not possess
any values for attr."
[db e attr]
(seq (map :a (d/datoms db :eavt e attr))))
;; find all users
(q '[:find ?e ?upvote
:where
[?e :user/email]
[(user/maybe $ ?e :user/upVotes) ?upvote]]
(db conn))
I inserted the maybe function into my database, and it can be queried thus:
[:find ?n ?v :in $ :where [?e ?a ?v] [?a :db/ident :db/fn] [?e :db/ident ?n]]
returns
:maybe #db/fn{:code "(seq (map :a (d/datoms db :eavt e attr)))", :params [db e attr], :requires [], :imports [], :lang :clojure}
However, I am unable to work out how to call the function in a query. I have an :data/user attribute on some transactions, which I want to get the value for where it exists. Here's the query I'm trying to run; I would like :maybe to be the database function defined above.
[:find ?attr ?v ?when ?who :where
[17592186045423 ?a ?v ?tx true]
[?a :db/ident ?attr]
[(:maybe $ ?tx :data/user) ?who]
[?tx :db/txInstant ?when]]
I'm pretty sure I'm missing something pretty obvious, but I've been stuck on this for a day now. Thanks for any help!
You need to use d/invoke. So your example would look like this:
[:find ?attr ?v ?when ?who :where
[17592186045423 ?a ?v ?tx true]
[?a :db/ident ?attr]
[(d/invoke $ :maybe ?tx :data/user) ?who]
[?tx :db/txInstant ?when]]
According to the Query Doc of Datomic, you can use any pure function in queries. You don't have to install them first. Functions you have to install are transaction functions.
Query functions don't need to be installed because they run in your application like all other functions. Datomic has nothing like a database server that executes queries. Queries are always executed in your application by the Peer Library.
The only type of functions you need to install are transaction functions because they run inside the Transactor. The Transactor is a single, special process that handles all the writes in Datomic.

Resources