如何为datomic编写可重用的规范查询

时间:2013-02-07 13:04:05

标签: clojure datomic

我在编写我认为可重用的一般数据库查询时遇到了麻烦。

例如,从这篇帖子is there a canonical way to grab all idents from a particular datomic partition?开始,我安装了以下架构

{[63 :account/password] 
 [64 :account/firstName] 
 [65 :account/lastName] 
 [62 :account/username]
 [69 :email/prority]
 [68 :email/address]}

我希望有一个只显示具有给定命名空间的属性的函数。

此函数显示“:account”命名空间中的所有属性

(d/q '[:find ?e ?ident :where
        [?e :db/ident ?ident]
        [_ :db.install/attribute ?e]
        [(.toString ?ident) ?val]
        [(.startsWith ?val ":account")]] (d/db *conn*))

;; => [62 :account/username] [63 :account/password]  
;;    [64 :account/firstName]  [65 :account/lastName]

但是,当我想编写一个可以输入的函数时,我必须在任何地方加上引号才能使它工作。

(defn get-ns-attrs [?ns db]
  (d/q [':find '?e '?ident ':where
         ['?e ':db/ident '?ident]
         ['_ ':db.install/attribute '?e]
         [(list '.toString '?ident) '?val]
         [(list '.startsWith '?val (str ":" ?ns))]] db))

(get-ns-attrs "account" (d/db *conn*))
;; => [62 :account/username] [63 :account/password]  
;;    [64 :account/firstName]  [65 :account/lastName]

(get-ns-attrs "email" (d/db *conn*))
;; => [69 :email/prority] [68 :email/address]

有更好的方法吗?

------更新-------

此处的完整代码供人们使用:

(ns schema.start
  (:require [datomic.api :as d])
  (:use [clojure.pprint :only [pprint]]))

(def *uri* "datomic:mem://login-profile")
(d/create-database *uri*)
(def *conn* (d/connect *uri*))

(defn boolean? [x]
  (instance? java.lang.Boolean x))

(defn db-pair [attr kns val f]
  (list (keyword (str "db/" (name attr)))
        (f val kns)))

(defn db-enum [val kns]
  (keyword (str "db." (name kns) "/" (name val))))

(def DB-KNS
  {:ident        {:required true
                  :check keyword?}
   :type         {:required true
                  :check #{:keyword :string :boolean :long :bigint :float
                           :double :bigdec :ref :instant :uuid :uri :bytes}
                  :attr :valueType
                  :fn db-enum}
   :cardinality  {:required true
                  :check #{:one :many}
                  :fn db-enum}
   :unique       {:check #{:value :identity}
                  :fn db-enum}
   :doc          {:check string?}
   :index        {:check boolean?}
   :fulltext     {:check boolean?}
   :component?   {:check keyword?}
   :no-history   {:check boolean?}})

(defn process-kns [m kns params res]
  (let [val (m kns)]
    (cond (nil? val)
          (if (:required params)
            (throw (Exception. (str "key " kns " is a required key")))
            res)

          :else
          (let [chk  (or (:check params) (constantly true))
                f    (or (:fn params) (fn [x & xs] x))
                attr (or (:attr params) kns)]
            (if (chk val)
              (apply assoc res (db-pair attr kns val f))
              (throw (Exception. (str "value " val " failed check"))))))))

(defn schema [m]
  (loop [db-kns# DB-KNS
         output  {}]
    (if-let [entry (first db-kns#)]
      (recur (rest db-kns#)
             (process-kns m (first entry) (second entry) output))
      (assoc output
        :db.install/_attribute :db.part/db
        :db/id (d/tempid :db.part/db)))))

(def account-schema
  [(schema {:ident       :account/username
            :type        :string
            :cardinality :one
            :unique      :value
            :doc         "The username associated with the account"})
   (schema {:ident       :account/password
            :type        :string
            :cardinality :one
            :doc         "The password associated with the account"})
   (schema {:ident       :account/firstName
            :type        :string
            :cardinality :one
            :doc         "The first name of the user"})
   (schema {:ident       :account/lastName
            :type        :string
            :cardinality :one
            :doc         "The first name of the user"})
   (schema {:ident       :account/otherEmails
            :type        :ref
            :cardinality :many
            :doc         "Other email address of the user"})
   (schema {:ident       :account/primaryEmail
            :type        :ref
            :cardinality :one
            :doc         "The primary email address of the user"})])

(def email-schema
  [(schema {:ident       :email/address
            :type        :string
            :cardinality :one
            :unique      :value
            :doc         "An email address"})
   (schema {:ident       :email/priority
            :type        :long
            :cardinality :one
            :doc         "An email address's priority"})])

(d/transact *conn* account-schema)
(d/transact *conn* email-schema)

(defn get-ns-attrs1 [?ns db]
  (d/q [':find '?e '?ident ':where
        ['?e ':db/ident '?ident]
        ['_ ':db.install/attribute '?e]
        [(list '.toString '?ident) '?val]
        [(list '.startsWith '?val (str ":" ?ns))]] db))

(defn get-ns-attrs2 [?ns db]
  (d/q '[:find ?e ?ident :where
        [?e :db/ident ?ident]
        [_ :db.install/attribute ?e]
        [(.toString ?ident) ?val]
        [(.startsWith ?val ~(str ":" ?ns))]] db))


(get-ns-attrs1 "account" (d/db *conn*))
(get-ns-attrs1 "email" (d/db *conn*))

(get-ns-attrs2 "account" (d/db *conn*))
(get-ns-attrs2 "email" (d/db *conn*))

2 个答案:

答案 0 :(得分:5)

经过多一点阅读后,我发现:in关键字是所有这一切的关键。示例在教程的“高级查询”部分中给出 - http://docs.datomic.com/tutorial.html

这是列出:account命名空间

中所有属性的等效查询
(d/q '[:find ?e ?ident ?ns :in $ ?ns :where
        [?e :db/ident ?ident]
        [_ :db.install/attribute ?e]
        [(.toString ?ident) ?val]
        [(.startsWith ?val ?ns)]] 
     (d/db *conn*)
     "account")
;; => #<HashSet [[68 :account/firstName], [67 :account/password], [71 :account/primaryEmail], [66 :account/username], [69 :account/lastName], [70 :account/otherEmails]]>

这是函数中的等价物

(defn get-ns-attrs [_ns db]
  (d/q '[:find ?e ?ident :in $ ?ns :where
         [?e :db/ident ?ident]
         [_ :db.install/attribute ?e]
         [(.toString ?ident) ?val]
         [(.startsWith ?val ?ns) ]] db (str _ns)))

(get-ns-attrs :account (d/db *conn*))

;; => #<HashSet [[68 :account/firstName], [67 :account/password], [71 :account/primaryEmail], [66 :account/username], [69 :account/lastName], [70 :account/otherEmails]]>

如果您需要更多模块化,可以使用%进一步细分该功能以传递一组规则:

(def rule-nsAttrs
  '[[nsAttrs ?e ?ident ?ns]
    [?e :db/ident ?ident]
    [_ :db.install/attribute ?e]
    [(.toString ?ident) ?val]
    [(.startsWith ?val ?ns)]])

(defn get-ns-attrs [_ns db]
  (d/q '[:find ?e ?ident :in $ % ?ns :where
         (nsAttrs ?e ?ident ?ns)]
       (d/db *conn*)
       [rule-nsAttrs]
       (str _ns)))

(get-ns-attrs :account (d/db *conn*))
;; => #<HashSet [[68 :account/firstName], [67 :account/password], [71 :account/primaryEmail], [66 :account/username], [69 :account/lastName], [70 :account/otherEmails]]>

答案 1 :(得分:5)

使用Clojure的namespace

可以更简单地解决

(d/q '[:find ?name :in $ ?ns 
       :where [_ :db.install/attribute ?a] 
              [?a :db/ident ?name] 
              [(namespace ?name) ?attr-ns] 
              [(= ?attr-ns ?ns)]] (d/db *conn*) "account")

返回:

#{[:account/password]
  [:account/firstName]
  [:account/lastName]
  [:account/username]}