Chr*_*ant 6 macros clojure destructuring cassandra
这是"Clojure中最具惯用性"的问题.
我正在使用Cassandra作为我的DB,Alia作为我的Clojure司机(Cassandra和Alia工作非常好 - 不能更快乐).
问题是这样的:Cassandra在列名中使用下划线(而不是破折号),而Clojure更喜欢使用破折号来强调下划线.所以Clojure中的"user-key"在Cassandra中是"user_key".如何最好地处理Cassandra列名称到Clojure变量的映射?
因为我正在为我的CQL查询使用预准备语句,所以我认为列名称包含下划线而不是破折号这一事实不仅仅是要抽象出来的实现细节 - 我经常将CQL查询作为字符串放入我的Clojure代码中,而且我认为代表CQL实际上很重要.我已经考虑了在查询字符串中自动将短划线转换为下划线的方法,因此有一个Clo的Clojure版本被映射到CQL的Cassandra版本,但这似乎是一个不合适的抽象级别.此外,当你直接在Cassandra中运行CQL查询进行故障排除时,你仍然需要使用下划线,因此你需要在脑中保留两个不同的列名表示.似乎是错误的方法.
我最终采用的方法是在Clojure解构映射中执行映射,如下所示:
(let [{user-key :user_key, user-name :user_name}
(conn/exec-1-row-ps "select user_key,user_name from users limit 1")] )
Run Code Online (Sandbox Code Playgroud)
("conn/exec-1-row-ps"是我的便捷函数,只是在地图中查找CQL字符串,并使用先前准备的语句(如果存在),或者准备语句并将其存储在地图中,并且然后执行预准备语句并返回结果集的第一行,如果返回多行,则抛出异常).
如果我使用更简洁的{:keys []}解构方法,那么我会在我的Clojure变量名中使用下划线:
(let [{:keys [user_key user_name]} ...
Run Code Online (Sandbox Code Playgroud)
这是我尝试过的第一种方法,但它变得非常快,因为带有下划线的变量名称会渗透到代码中,并与带有破折号的变量名称进行正面交锋.混乱.
在很长一段时间内遇到这个问题,在解构图中进行转换,其中Clojure"变量名"和Cassandra"column_name"并排感觉就像是最好的解决方案.它还允许我在需要时将short_col_nms扩展为更具描述性的变量名称.
这与Clojure在文件名中强调命名空间中的破折号的映射有一些相似之处,所以感觉就像这样进行映射有一些先例.在文件名/命名空间的情况下,Clojure自动执行映射,因此直接模拟可能是{:keys []}解构的版本,将破折号映射到下划线.
我是Clojure的新手,所以我意识到可能有更好的方法来做到这一点.因此我的问题.
我考虑过的一个改进是编写一个在编译时动态构建解构映射的宏.但我不知道如何编写一个在编译过程早期运行的宏.
在骆驼蛇串有一个干净的界面,这些类型的转换.
从示例:
(use 'camel-snake-kebab)
(->CamelCase 'flux-capacitor)
; => 'FluxCapacitor
(->SNAKE_CASE "I am constant")
; => "I_AM_CONSTANT"
(->kebab-case :object_id)
; => :object-id
(->HTTP-Header-Case "x-ssl-cipher")
; => "X-SSL-Cipher"
Run Code Online (Sandbox Code Playgroud)
升级到我的 Clojure 宏 fu 后,我发现的答案是使用一个为我进行解构的宏,包括从 Snake_case 到 kebab-case 的转换。
使用宏的另一个优点是我还可以对 CQL 列名称和参数进行一些基本的编译时验证。验证非常基础,但它可以捕获我通常犯的 90% 的令人头疼的错误。
这是宏。该宏仅处理单行结果情况(对于我来说,Cassandra 中的情况超过 50%)。我将使用一组单独的宏来处理多行结果。
(defmacro with-single-row-cql-selects
"given a vector of one or more maps of the form:
{:bindings [title doc-key version]
:cql \"SELECT * from dtl_blog_entries where blog_key=? and n=?\"
:params [ blog-key (int n) ]}
evaluates body with the symbols in :bindings bound to the results of the CQL in :cql executed with the params in :params
the CQL should be 'single-row' CQL that returns only one row. in any case, the macro will take only the first row of the results1
notes:
1) the macro handles the conversion from kebab-case (Clojure) to snake_case (Cassandra) automagically. specify your bindings using camel-case
2) to bind to a different symbol than the variable name, use the form symbol-name:column-name in the bindings vector, e.g.:
{:bindings [blog-name:title]
:cql \"select title from dtl_blogs where blog_key=? and comm_key=? and user_key=?\"
:params [ blog-key comm-key user-key]}
3) the macro will do very basic compile-time checking of your cql, including
a) validating that you have the same number of '?'s in your cql as params
b) validating that the column names corresponding to the bindings are present in the CQL (or that this is a 'select *' query)
"
[select-bindings & body]
(let [let-bindings#
(into []
(letfn ((make-vec#
;; puts a single element into a vector, passes a vector straight through, and complains if v is some other kind of collection
[v#]
(cond
;; missing, just use an empty vector
(not v#) []
(vector? v#) v#
(coll? v#) (throw (IllegalArgumentException. (str v# " should be a vector")))
:else [v#])))
(apply concat
(for [{:keys [cql params bindings]} select-bindings]
(let [vec-bindings# (make-vec# bindings)
vec-params# (make-vec# params)
binding-names# (map #(-> % name (clojure.string/split #":" ) first symbol) vec-bindings#)
col-names# (map #(-> (or (-> % name (clojure.string/split #":" ) second ) %)
(clojure.string/replace \- \_) ) vec-bindings#)
destructuring-map# (zipmap binding-names# (map keyword col-names#))
fn-call# `(first (prep-and-exec ~cql ~vec-params#))]
;; do some *very basic* validation to catch the some common typos / head slappers
(when (empty? vec-bindings#)
(throw (IllegalArgumentException. "you must provide at least one binding")))
;; check that there are as many ?s as there are params
(let [cql-param-count (count (re-seq #"\?" cql))]
(when (not= cql-param-count (count vec-params#))
(throw (IllegalArgumentException. (str "you have " cql-param-count
" param placeholders '?' in your cql, but "
(count vec-params#) " params defined; cql: " cql ", params:" vec-params#)))))
;; validate that the col-names are present
(when (empty? (re-seq #"(?i)\s*select\s+\*\s+from" cql)) ;; if a 'select *' query, no validation possible
(doseq [c col-names#]
(when (empty? (re-seq (re-pattern (str "[\\s,]" c "[\\s,]")) cql))
(throw (IllegalArgumentException. ( str "column " c " is not present in the CQL"))))))
[destructuring-map# fn-call#])))))]
`(let ~let-bindings#
~@body)))
Run Code Online (Sandbox Code Playgroud)
这是宏的使用示例:
(conn/with-single-row-cql-selects
[{:bindings [blog-title]
:cql "select blog_title from dtl_blogs where blog_key=? and comm_key=? and user_key=?"
:params [ blog-key comm-key user-key]}]
(println "blog title is " blog-title))
Run Code Online (Sandbox Code Playgroud)
和宏expand-1(减去 println):
(clojure.core/let [{blog-title :blog_title} (clojure.core/first
(dreamtolearn.db.conn/prep-and-exec
"select blog_title from dtl_blogs where blog_key=? and comm_key=? and user_key=?"
[blog-key
comm-key
user-key]))])
Run Code Online (Sandbox Code Playgroud)
这是 REPL 输出的另一个示例:
dreamtolearn.db.conn> (with-conn
(with-single-row-cql-selects
[{:cql "select * from dtl_users limit 1"
:bindings [user-key name date-created]}
{:cql "select badges,founder_user_key,has_p_img from dtl_communities where comm_key=?"
:bindings [badges founder-user-key has-profile-image:has-p-img]
:params "5LMO8372ZDKHF798RKGNA57O3"}]
(println "user-key: " user-key " name: " name " date-created: " date-created " badges: " badges
" founder-user-key: " founder-user-key " has-profile-image: " has-profile-image)))
user-key: 9MIGXXW2QJWPGL0WJL4X0NGWX name: Fred Frennant date-created: 1385131440791 badges: comm-0 founder-user-key: F2V3YJKBEDGOLLG11KTMPJ02QD has-profile-image: true
nil
dreamtolearn.db.conn>
Run Code Online (Sandbox Code Playgroud)
和宏expand-1:
(clojure.core/let [{date-created :date_created,
name :name,
user-key :user_key} (clojure.core/first
(dreamtolearn.db.conn/prep-and-exec
"select * from dtl_users limit 1"
[]))
{has-profile-image :has_p_img,
founder-user-key :founder_user_key,
badges :badges} (clojure.core/first
(dreamtolearn.db.conn/prep-and-exec
"select badges,founder_user_key,has_p_img from dtl_communities where comm_key=?"
["5LMO8372ZDKHF798RKGNA57O3"]))])
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1346 次 |
最近记录: |