2013-11-27 2 views
6

Это вопрос, который является самым идиоматическим вопросом в Clojure.clojure-variable-names для database_column_names

Я использую Cassandra для своей БД, с Алией, поскольку мой водитель Clojure (как Кассандра, так и Алия работают феноменально хорошо - не может быть счастливее).

Проблема заключается в следующем: Cassandra использует символы подчеркивания (не тире) в именах столбцов, а Clojure предпочитает тире подчеркивания. Таким образом, «пользовательский ключ» в Clojure является «user_key» в Cassandra. Как лучше всего обращаться с отображением имен столбцов Cassandra на переменные Clojure?

Поскольку я использую подготовленные операторы для своих запросов CQL, я думаю, что тот факт, что имена столбцов содержат символы подчеркивания, а не тире, - это больше, чем детализация реализации, которые нужно абстрагировать - я часто ставил запросы CQL как строки в мой код Clojure, и я думаю, что важно представлять CQL так, как есть на самом деле. Я рассмотрел подходы, которые автоматически изменяют тире, чтобы подчеркнуть строки запроса, так что есть Clojure-версия CQL, которая отображается в Cassandra-версии CQL, но это кажется неприемлемым уровнем абстракции. Кроме того, вам нужно будет использовать символы подчеркивания, когда вы запускаете запросы CQL непосредственно в Cassandra для устранения неполадок, поэтому вам нужно сохранить два разных представления имен столбцов в вашей голове. Похоже на неправильный подход.

подхода я кончался взятие должно выполнить отображение в деструктурирующей карте Clojure, как это:

(let [{user-key :user_key, user-name :user_name} 
    (conn/exec-1-row-ps "select user_key,user_name from users limit 1")]) 

(«Конны/Exec-1-рядного пса» моя функция удобства, что просто просматривает строку CQL на карте и использует предварительно подготовленный оператор, если он присутствует, или же готовит оператор и заносит его на карту, а затем выполняет подготовленный оператор и возвращает первую строку набора результатов или бросает исключение, если возвращается более одной строки).

, если я использую более кратким {: клавиши []} метод деструктурирующие, то я застрял с подчеркиванием в моих имен переменных Clojure:

(let [{:keys [user_key user_name]} ... 

Это был первый подход, который я попробовал, но становится уродливым очень быстро, поскольку переменные имена с подчеркиваниями просачиваются через код и идут голова к голове с тире с тире. Смешение.

Столкнувшись с этой проблемой в течение длительного времени, делая преобразование на карте деструктурирования, где Clojure «имя переменной» и Cassandra «column_name» являются бок о бок, похоже на лучшее решение. Это также позволяет мне расширять short_col_nms до более описательных переменных-имен, когда захочу.

Это имеет некоторое сходство с отображением, которое Clojure делает с подчеркиваниями в именах файлов, в тире в пространствах имен, поэтому кажется, что существует некоторый прецедент для выполнения такого сопоставления. В случае имени файла/пространства имен Clojure автоматически выполняет сопоставление, и поэтому, возможно, прямым аналогом будет версия {: keys []} деструктурирования, которая отображает тире, чтобы подчеркнуть.

Я относительный новичок в Clojure, поэтому я понимаю, что могут быть лучшие способы сделать это. Отсюда мой вопрос.

Одним из улучшений, которые я рассмотрел, является создание макроса, который динамически создает карту деструктурирования во время компиляции. Но я не знаю, как написать макрос, который работает в начале процесса компиляции.

ответ

0

После обновления моей Clojure макро-фу, ответ я нашел, чтобы использовать макрос, который делает деструктурирующие, включая преобразование из snake_case в Кебаб-дело , для меня.

Дополнительным преимуществом использования макроса является то, что я также могу выполнить базовую проверку времени моего имени и параметров столбца CQL. Валидация очень простая, но она уловит 90% ошибок, которые я обычно делаю.

Вот макро. Этот макрос обрабатывает только однострочные результаты (что для меня в Кассандре более 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# 
     [email protected]))) 

и вот пример использования макроса:

(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)) 

и MACROEXPAND-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]))]) 

Вот еще один пример с выходом из РЕПЛ :

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> 

и macr oexpand-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"]))]) 
2

Если вы считаете свои данные древовидной структурой (из n уровней), и вам нужно заменить «подчеркивание» на символ «тире» ключей древовидной структуры, вы можете попытаться решить эту функцию с помощью библиотеки предназначен для: clojure.walk

на самом деле clojure.walk приносит схожую функциональность keywordize-keys

(defn keywordize-keys 
    "Recursively transforms all map keys from strings to keywords." 
    {:added "1.1"} 
    [m] 
    (let [f (fn [[k v]] (if (string? k) [(keyword k) v] [k v]))] 
    ;; only apply to maps 
    (postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m))) 

Тогда вы только должны изменить функцию keyword для clojure.string/replace функции

и это результат:

(defn underscore-to-dash-string-keys 
    "Recursively transforms all map keys from strings to keywords." 
    {:added "1.1"} 
    [m] 
    (let [f (fn [[k v]] (if (string? k) [(clojure.string/replace k "_" "-") v] [k v]))] 
    ;; only apply to maps 
    (clojure.walk/postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m))) 


(underscore-to-dash-string-keys {"_a" 1 "_b" 2 "_c" 3}) 

=> {"-a" 1, "-b" 2, "-c" 3} 

относящиеся к этому вопросу: Как лучше обрабатывать отображение имен столбцов Cassandra переменных Clojure? Я думаю, что здесь хорошо обсуждается In Clojure, how to destructure all the keys of a map?

+0

Если вопрос был, как преобразовать ключи карты результатов Cassandra из подчеркиваний в тире, я бы сделал это следующим образом: '(defn underscore-to-dash-string-keys [m] (в {} (для [[kv] m] [(clojure.string/replace k \ _ \ -) ​​v]))) '.но вопрос в том, каков идиоматический способ обработки имен столбцов с подчеркиванием? Если ваш ответ «просто преобразуйте символы подчеркивания в тире», то, как я сказал выше, я думаю, что это слишком мало скрывает реализацию Cassandra. –

+0

Мое решение также будет обертывать вложенные структуры данных cassandra (но на самом деле я не знаю, может ли это быть реальным случаем). – tangrammer

+0

и на самом деле вы получаете ключи ключей от Cassandra, поэтому мое решение должно быть: '(defn underscore-to-dash-string-keys [m] (в {} (для [[kv] m] [ (-> k name (clojure.string/replace \ _ \ -) ​​keyword) v]))) ' –

5

camel-snake-kebab имеет хороший чистый интерфейс для такого рода преобразований.

Из примеров:

(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" 
+0

Я не знал о верблюжьей змейке-шашлыке - это отличный инструмент для совершения конверсий, Спасибо за совет. Но я все еще ищу ответ на (философский) вопрос о том, где лучше всего сделать преобразование из snake_case в kebab-case (и любить идею назвать этот случай с кебабом). «Как» кажется простым - вы можете сделать это в Clojure или с помощью такого инструмента. Я по-прежнему считаю, что сопоставление идентификаторов snake_ и kebab на карте деструктуризации в коде Clojure - лучший способ, потому что он четко демаркирует границу между двумя доменами. –

1

Вы можете похоронить преобразование между дефисом и подчеркиванием в вашем CQL и избежать кошмара munging Clojure ключевых слов, если вы хотите, используя идентификаторы в кавычках, особенно, если вы используете подготовленные заявления с Аллой, так Alia supports named parameter bindings for prepared statements с версии 2.6.

Если вы посмотрите на CQL grammar вы заметите самую первую строку:

идентификатор :: = любой кавычках или без идентификатора, исключая зарезервированный ключевые слова

Идентификатор является соответствие маркер регулярное выражение [A-Za-Z] [A-Za-Z0-9 _] *

Некоторые из этих идентификаторов зарезервированы в качестве ключевых слов (SELECT, AS, IN, и т.д.)

Однако есть еще один класс идентификатора - цитируемый - который может содержать любой символ, включая дефисы, и никогда не рассматривается как зарезервированный.

Существует второй тип идентификаторов называются идентификаторы в кавычках определяются ограждающим произвольная последовательность символов в двойных кавычках ("). Цитируемые идентификаторы никогда не ключевые слова

В Выборе синтаксиса у вас есть возможность выбора поля в качестве идентификатора.

выбор-лист :: = селектор (как идентификатор)

Если вы выбрали для выбора х AS цитируемым идентификатора можно перевести подчеркивания на дефис:

т.е. "SELECT user_id AS \"user-id\" from a_table

Исполнительного этот запрос через Алле~d приведут к Clojure карты с ключом: идентификатором пользователя и некоторым значением ,

Аналогично при выполнении операции, когда вы хотите, чтобы связать значение параметра, грамматика:

переменная :: = «?» | Идентификатор ':'

Переменная может быть анонимной (знак вопроса (?)) или именоваться (идентификатор, которому предшествует :). Оба объявляют переменные связывания для подготовленных операторов ''

Хотя это может показаться немного напуганным.

т.е.

INSERT into a_table (user_id) VALUES (:\"user-id\") 

или

SELECT * from a_table WHERE user_id = :\"user-id\" 

Оба этих запросов, выполненных с Аллой могут быть переданы карту, содержащую: идентификатор пользователя, а значение будет связано правильно.

При таком подходе вы можете иметь дело с переводом дефиса/подчеркивания целиком на CQL.

 Смежные вопросы

  • Нет связанных вопросов^_^