I'm trying to filter through a database, based on the keyword patter match.
To do this, I wrote ->
(defn find-users
[db keyword]
(if (>= (count keyword) 3)
(let [login-pattern (login-pattern keyword)]
(->> (d/datoms db :aevt :user/name)
(filter #(re-matches login-pattern (:v %)))
(map #(d/entity db (:e %)))))
[]))
But, I'm getting a StackOverflow error.
I think it's because of (map #(d/entity db (:e %)))
When I plainly do (map :e), the function works.
I'm a bit confused as to why the Stackoverflow would happen though, the queries I'm performing with map :v are returning only a few entities.
What's happening here?
I'm a little curious as to why not just use query? You can use predicate expression clauses for the same purpose that you've built the filter/map scenario above. See "Expression Clauses" at: http://docs.datomic.com/query.html
(defn find-users
[db keyword]
(if (>= (count keyword) 3)
(map #(d/entity db (first %))
(d/q '[:find ?e
:in $ ?login-pattern
:where
[?e :user/name ?name]
[(re-matches ?login-pattern ?name)]]
db
(login-pattern keyword)))
[]))
The query engine is likely to handle the size of intermediate results better than raw sequence manipulations.
Related
I'm trying to handle following DSL:
(simple-query
(is :category "car/audi/80")
(is :price 15000))
that went quite smooth, so I added one more thing - options passed to the query:
(simple-query {:page 1 :limit 100}
(is :category "car/audi/80")
(is :price 15000))
and now I have a problem how to handle this case in most civilized way. as you can see simple-query may get hash-map as a first element (followed by long list of criteria) or may have no hash-mapped options at all. moreover, I would like to have defaults as a default set of options in case when some (or all) of them are not provided explicite in query.
this is what I figured out:
(def ^{:dynamic true} *defaults* {:page 1
:limit 50})
(defn simple-query [& body]
(let [opts (first body)
[params criteria] (if (map? opts)
[(merge *defaults* opts) (rest body)]
[*defaults* body])]
(execute-query params criteria)))
I feel it's kind of messy. any idea how to simplify this construction?
To solve this problem in my own code, I have a handy function I'd like you to meet... take-when.
user> (defn take-when [pred [x & more :as fail]]
(if (pred x) [x more] [nil fail]))
#'user/take-when
user> (take-when map? [{:foo :bar} 1 2 3])
[{:foo :bar} (1 2 3)]
user> (take-when map? [1 2 3])
[nil [1 2 3]]
So we can use this to implement a parser for your optional map first argument...
user> (defn maybe-first-map [& args]
(let [defaults {:foo :bar}
[maybe-map args] (take-when map? args)
options (merge defaults maybe-map)]
... ;; do work
))
So as far as I'm concerned, your proposed solution is more or less spot on, I would just clean it up by factoring out parser for grabbing the options map (here into my take-when helper) and by factoring out the merging of defaults into its own binding statement.
As a general matter, using a dynamic var for storing configurations is an antipattern due to potential missbehavior when evaluated lazily.
What about something like this?
(defn simple-query
[& body]
(if (map? (first body))
(execute-query (merge *defaults* (first body)) (rest body))
(execute-query *defaults* body)))
If I'm using a Clojure atom to keep count of something (votes for example), I can do this:
(def votes (atom {}))
(defn vote! [candidate]
(swap! votes update-in [candidate] (fnil inc 0)))
(vote! "Abraham Lincoln")
(vote! "Abraham Lincoln")
(vote! "Winston Churchill")
votes ;=> {"Abraham Lincoln" 2, "Winston Churchill" 2}
Here, update-in neatly transforms the value at the given key, without having to look it up first.
How can I accomplish the same in Datomic? I could do something like this...
(defn vote! [db candidate]
(let [[e v] (first (q '[:find ?e ?v
:in $ ?candidate
:where [[?e :name ?candidate]
[?e :votes ?v]] db candidate)
(transact! conn [{:db/id e :votes (inc v)}]))
But it seems a bit cumbersome, having to run the query, return the value and then transact with the new value. Is there a more idiomatic way of doing this (like a swap! or update-in)?
To me it seems most idiomatic to record the fact of the vote, then make a count query when you want to know the total. Depending on the needs of your application, this approach supports things like checking for double votes, vote change/retraction, counts in the last 24 hours, ...
I'm trying to figure out the best way to troll a namespace for functions that contain a specific bit of metadata. I've come up with a solution, but it feels a little awkward and I'm not at all sure I'm going about it the right way. There's a second component to this as well: I don't just want the names of the functions, I want to find them and then execute them. Here's a snippet of what I'm doing presently:
(defn wrap-routes
[req from-ns]
(let [publics (ns-publics from-ns)
routes (->>
(keys publics)
(map #(meta (% publics)))
(filter #(= (:route-handler %) true))
(map #(:name %)))
resp (first
(->>
(map #((% publics) req) routes)
(filter #(:status %))))]
(or resp not-found)))
As you can see, I'm doing all sorts of gymnastics to see if my metadata is attached to any functions in a given namespace and then am doing extra work after that to get the actual function back. I'm sure there must be a better way. So my question is, how would you do this?
(defn wrap-routes [req from-ns]
(or (first (filter :status
(for [[name f] (ns-publics from-ns)
:when (:route-handler (meta f))]
(f req))))
not-found))
You can do something like this:
(defn wrap-routes
[req from-ns]
(->> (ns-publics from-ns)
(filter #(:route-handler (meta (%1 1))))
(map #((%1 1) req))
(filter #(:status %))
first
(#(or % not-found))))
I am working in clojure with a java class which provides a retrieval API for a domain specific binary file holding a series of records.
The java class is initialized with a file and then provides a .query method which returns an instance of an inner class which has only one method .next, thus not playing nicely with the usual java collections API. Neither the outer nor inner class implements any interface.
The .query method may return null instead of the inner class. The .next method returns a record string or null if no further records are found, it may return null immediately upon the first call.
How do I make this java API work well from within clojure without writing further java classes?
The best I could come up with is:
(defn get-records
[file query-params]
(let [tr (JavaCustomFileReader. file)]
(if-let [inner-iter (.query tr query-params)] ; .query may return null
(loop [it inner-iter
results []]
(if-let [record (.next it)]
(recur it (conj results record))
results))
[])))
This gives me a vector of results to work with the clojure seq abstractions. Are there other ways to expose a seq from the java API, either with lazy-seq or using protocols?
Without dropping to lazy-seq:
(defn record-seq
[q]
(take-while (complement nil?) (repeatedly #(.next q))))
Instead of (complement nil?) you could also just use identity if .next does not return boolean false.
(defn record-seq
[q]
(take-while identity (repeatedly #(.next q))))
I would also restructure a little bit the entry points.
(defn query
[rdr params]
(when-let [q (.query rdr params)]
(record-seq q)))
(defn query-file
[file params]
(with-open [rdr (JavaCustomFileReader. file)]
(doall (query rdr params))))
Seems like a good fit for lazy-seq:
(defn query [file query]
(.query (JavaCustomFileReader. file) query))
(defn record-seq [query]
(when query
(when-let [v (.next query)]
(cons v (lazy-seq (record-seq query))))))
;; usage:
(record-seq (query "filename" "query params"))
Your code is not lazy as it would be if you were using Iterable but you can fill the gap with lazy-seq as follows.
(defn query-seq [q]
(lazy-seq
(when-let [val (.next q)]
(cons val (query-seq q)))))
Maybe you shoul wrap the query method to protect yourself from the first null value as well.
I'm having problems with the datomic tutorial at the "Revisiting the past" section http://datomic.com/company/resources/tutorial.html
For the two queries below:
query = "[:find ?c :where [?c :community/name]]";
db_asOf_schema = conn.db().asOf(schema_tx_date);
System.out.println(Peer.q(query, db_asOf_schema).size()); // 0
db_since_data = conn.db().since(data_tx_date);
System.out.println(Peer.q(query, db_since_data).size()); // 0
I have tried these commands in clojure, but cannot get them working as described in the tutorial:
(since (db conn) (java.util.Date.) )
;; It should return 0 but returns the whole database instead
(def ts (q '[:find ?when :where [?tx :db/txInstant ?when]] (db conn)))
(count (since (db conn) (ffirst (reverse (sort ts))))))
;; returns 13, but should return 0
(count (as-of (db conn) (ffirst (sort ts)))))
;; returns 13, but should return 0
I'm not too sure is this is the right behaviour, is there anything I'm doing wrong?
If you are working through the Seattle tutorial in Clojure, probably the most important single thing to know is that working Clojure code is included in the Datomic distribution. The filename is samples/seattle/getting-started.clj, and you can simply follow along at the REPL.
Two observations on the Clojure code in your question:
The since function is documented to return a database value, not a number, so the behavior you are seeing is as expected. In order to see what is in the database, you need to issue a query.
Databases do not have any documented semantics for the Clojure count function, so you should not call count on them. Again, if you want to see what is in the database, you need to issue a query, e.g.
;; Find all transaction times, sort them in reverse order
(def tx-instants (reverse (sort (q '[:find ?when :where [_ :db/txInstant ?when]]
(db conn)))))
;; pull out two most recent transactions, most recent loaded
;; seed data, second most recent loaded schema
(def data-tx-date (ffirst tx-instants))
(def schema-tx-date (first (second tx-instants)))
;; make query to find all communities
(def communities-query '[:find ?c :where [?c :community/name]])
;; find all communities as of schema transaction
(let [db-asof-schema (-> conn db (d/as-of schema-tx-date))]
(println (count (seq (q communities-query db-asof-schema)))))
;; find all communities as of seed data transaction
(let [db-asof-data (-> conn db (d/as-of data-tx-date))]
(println (count (seq (q communities-query db-asof-data)))))
;; find all communities since seed data transaction
(let [db-since-data (-> conn db (d/since data-tx-date))]
(println (count (seq (q communities-query db-since-data)))))
Hope this helps. There is also a Datomic google group if you have more questions.