как писать многократно используемые канонические запросы для datomic

У меня возникают проблемы с написанием общих атомарных запросов, которые я считаю пригодными для повторного использования.

Например, после этого сообщения есть ли канонический способ получить все идентификаторы из определенного раздела datomic?, у меня установлена ​​следующая схема

{[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*))

person zcaudate    schedule 07.02.2013    source источник


Ответы (2)


Немного почитав, я понял, что ключевое слово :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]]>
person zcaudate    schedule 09.02.2013

можно решить немного проще с помощью namespace Clojure. :

(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]}
person tolitius    schedule 12.06.2013