match all key/val pairs - clojure

Original query:
-- :name select*-list
-- :command :query
-- :result :raw
-- :doc Select all lists.
-- parameters()
SELECT * FROM list;
I want to pass in arbitrary key/val pairs and get matching results. For example:
(select*-list db-spec {:name "Fruit" :type "Foo"})
should result in:
SELECT * FROM list
WHERE name = 'Fruit'
AND type = 'Foo';
I can think of a few ugly ways to accomplish this but it's likely I'm overlooking some nice way to do this.

JDBC has some great shortcuts out from the box. One of them is find-by-keys. It does exactly what you want: takes a map of key/value pairs and composes a set of WHERE clauses connected with AND:
(jdbc/find-by-keys db-spec :users {:name "John" :age 42 :city "Chita"})
will turn out to
select from users
where
name = 'John'
and age = 42
and city = 'Chita';

Here is an example java-jdbc.sql
(require '[java-jdbc.sql :as sql])
(jdbc/query db-spec
(sql/select * :fruit (sql/where {:appearance "ripe"})))
;; -> ({:grade 8.4, :unit "carton", :cost 12, :appearance "ripe", :name "Plum"})
Please see:
The Clojure Cookbook (I recommend buying a printed copy).
The JDBC pages at clojure-doc.org

Related

How can I update a map using the `update` function?

(def p {:name "James" :age 26})
I'm trying update method, like
(update p :name "David")
which does not work since the second argument has to be a function.
Try this:
(assoc p :name "David")
Please see this list of documentation, especially the Clojure CheatSheet! See also assoc-in and update-in as described under
Collections -> Maps
P.S. What you have there is a Clojure map value, which is different than an object in JavaScript or a JSON string.

Recursive map query using specter

Is there a simple way in specter to collect all the structure satisfying a predicate ?
(./pull '[com.rpl/specter "1.0.0"])
(use 'com.rpl.specter)
(def data {:items [{:name "Washing machine"
:subparts [{:name "Ballast" :weight 1}
{:name "Hull" :weight 2}]}]})
(reduce + (select [(walker :weight) :weight] data))
;=> 3
(select [(walker :name) :name] data)
;=> ["Washing machine"]
How can we get all the value for :name, including ["Ballast" "Hull"] ?
Here's one way, using recursive-path and stay-then-continue to do the real work. (If you omit the final :name from the path argument to select, you'll get the full “item / part maps” rather than just the :name strings.)
(def data
{:items [{:name "Washing machine"
:subparts [{:name "Ballast" :weight 1}
{:name "Hull" :weight 2}]}]})
(specter/select
[(specter/recursive-path [] p
[(specter/walker :name) (specter/stay-then-continue [:subparts p])])
:name]
data)
;= ["Washing machine" "Ballast" "Hull"]
Update: In answer to the comment below, here's a version of the above the descends into arbitrary branches of the tree, as opposed to only descending into the :subparts branch of any given node, excluding :name (which is the key whose values in the tree we want to extract and should not itself be viewed as a branching off point):
(specter/select
[(specter/recursive-path [] p
[(specter/walker :name)
(specter/stay-then-continue
[(specter/filterer #(not= :name (key %)))
(specter/walker :name)
p])])
:name]
;; adding the key `:subparts` with the value [{:name "Foo"}]
;; to the "Washing machine" map to exercise the new descent strategy
(assoc-in data [:items 0 :subparts2] [{:name "Foo"}]))
;= ["Washing machine" "Ballast" "Hull" "Foo"]
The selected? selector can be used to collect structures for which another selector matches something within the structure
From the examples at https://github.com/nathanmarz/specter/wiki/List-of-Navigators#selected
=> (select [ALL (selected? [(must :a) even?])] [{:a 0} {:a 1} {:a 2} {:a 3}])
[{:a 0} {:a 2}]
I think you could iterate on map recursively using clojure.walk package. On each step, you may check the current value for a predicate and push it into an atom to collect the result.

Accessing value from defn

I need advice,
I try to make function :
(def user-map [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email})
With new-name, new-phone, new-email are user input. But when i try to compile it, it says too many arguments to def, after change def to defn, when i try to execute user-map in REPL i get something like
#<temp_atom$user_address zenedu.mm.dbase.temp_atom$user_address#714924b5
instead of actual map.
I need to get to the map, any advice?
It sounds like perhaps you are conceptually combining the value that will be returned from calling user-map as a function with some arguments and evaluating the symbol user-map on its own.
Evaluating
(user-map "me" "123456789" "me#here.com")
Which will return a map, by looking up the var user-map in the current namespace and calling the function stored in that var with these arguments. Where evaluating just
user-map
Will simply look up the var user-map in the current namespace and return the contents of that var, which in the case where you used defn, will be the function it's self. The REPL then prints the object I'd of that function.
In your use case, you need defn to define a builder (like a constructor in Java) for the object you want. The log
#<temp_atom$user_address zenedu.mm.dbase.temp_atom$user_address#714924b5
suggests that you are using another structure user-address somewhere in the application and it looks like there is confusion between a user-map object and this user-address.
Anyway, you may be interested to have a look at defrecord that provides a convenient way to build objects with a constructor (and potentially other functions related to this object), e.g.
(defrecord user [name phone email])
defrecord provides 2 constructors ->user and map->user:
(def me (->user "abb" "0102030405" "abb#mail.com"))
(def you (map->user {:email "das#mail.com" :phone "9090909090" :name "das"}))
And you can access the properties of a user through keywords exactly like a map:
user> (:name me)
"abb"
user> (:phone you)
"9090909090"
OK, you should use defn instead of def.
But what information really varies here? The number and order of the map keys, in this case [:name :phone :email].
A generic function that will build - from a key sequence - a function that will build the map from the value sequence is
(defn map-builder [keys]
(fn [& values] (zipmap keys values)))
You can then define
(def user-map (map-builder [:name :phone :email]))
... which works as required:
(user-map "me" "123456789" "me#here.com")
;{:email "me#here.com", :phone "123456789", :name "me"}
If performance is pressing, by all means use records instead of maps, as #AbbéRésina suggests.
Putting it simply...
The error you are receiving is due to essentially passing a vector as a second value to def. If you want to use def in this instance go with...
(def user-map-other
(fn [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email}))
Here we are using an anonymous function that accepts your three parameters. Here is a link to learn more about them => http://clojuredocs.org/clojure.core/fn
To gain access to the values contained in your function we can use get in this instance.
(get (user-map-other "Ben" "999" "bbb#mail.com") :phone) => "999"
(get (user-map-other "Ben" "999" "bbb#mail.com") :name) => "Ben"
(get (user-map-other "Ben" "999" "bbb#mail.com") :email) => "bbb#mail.com"
A more concise method would be to use defn as represented below.
(defn user-map [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email})
(get (user-map "Ben" "999" "bbb#mail.com") :phone) => "999"
(get (user-map "Ben" "999" "bbb#mail.com") :name) => "Ben"
(get (user-map "Ben" "999" "bbb#mail.com") :email) => "bbb#mail.com"

Array-map example in clojure

I am learning clojure and trying to implement a problem. I am storing maps in a vector. Each map contains an id. For example [{:id 1 :name "abc"} {:id 2 :name "xyz"}]. The map also contains some more fields.
I read somewhere that, instead of using a vector to store the maps, I could use an array-map and do away with my id and store it something like {1 {:name "abc"}, 2 {:name "xyz"}}.
I tried going through the clojure docs but didn't find a good example to achieve this. Can some please help me out and give me a good example?
You can use assoc to add values to a map. assoc takes 3 args. The first arg is the map that you want to add to, 2nd arg is a key, and the third is a value. The function returns the old map with the key-value pair added.
Example:
(assoc {} 1 {:name "abc"})
returns
{1 {:name "abc"}}
Your idea is to lift the :id entry of each record-map into an index, while removing it from the map. You end up with a map of :id-less records instead of a vector of full records.
The following function lifts the key fk out of the collection of maps ms:
(defn key-by [fk ms]
(into {} (map (fn [m] [(get m fk) (dissoc m fk)]) ms)))
For example,
(key-by :id [{:id 1 :name "abc"} {:id 2 :name "xyz"}])
;{1 {:name "abc"}, 2 {:name "xyz"}}
Note:
Every record should have an :id.
Your :ids had better be distinct, or you'll lose records.
Don't depend on array-map: it's an implementation detail. A
modified version might well be a hash-map.
If you need your map sorted by key, use a sorted-map.
If you need to keep your records in insertion order, think again.

Can I specify default field aliases in Korma?

I have a MySQL field called thing_id, but I want to reference it as :thing-id in my code. I can define an entity like this:
(defentity thing
(entity-fields :id [:thing_id :thing-id]))
so that when I fetch things:
(select thing)
The MySQL field that contains an underscore is transformed:
[{:id 1 :thing-id 2}]
But I can't select with the aliased:
(select thing (where (= :thing-id 2)))
gives
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException
Unknown column 'thing.thing-id' in 'where clause'
I can fix it in each where call:
(select thing (where (= :thing_id 2)))
But I was hoping that the alias works both way. It doesn't appear to. Is there a way to set an alias that can be used in a select?
Bit too late, but here it goes...
Use a naming strategy, more here. So your spec should look like:
...
(:require [camel-snake-kebab.core :refer [->kebab-case ->snake_case]])
...
(def db-spec
{:classname "com.mysql.jdbc.Driver"
:subprotocol "mysql"
:delimiters "`"
:unsafe true
:subname db-subname
:user db-user
:password db-password
:naming {:keys ->kebab-case
:fields ->snake_case}})