Skip to content

Instantly share code, notes, and snippets.

@deltam
Created October 21, 2012 09:31
Show Gist options
  • Save deltam/3926491 to your computer and use it in GitHub Desktop.
Save deltam/3926491 to your computer and use it in GitHub Desktop.
Kormaのリレーションを試してみる

Kormaのリレーションを試してみる

author: @deltam

Colojure用DBアクセスライブラリ、Kormaの検索系機能の紹介とリレーション機能の実験。

Korma 0.3.0-beta11

https://github.com/korma/Korma

http://sqlkorma.com

テーブル定義

簡単なブログシステム

Userテーブル

Postテーブル: User_idカラムでユーザを持つ

User 1->N Post の関係

CREATE TABLE `User` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` text,
  `email` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

CREATE TABLE `Post` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `title` text,
  `text` text,
  `createdAt` timestamp NULL DEFAULT NULL,
  `updatedAt` timestamp NULL DEFAULT NULL,
  `User_id` int(11) unsigned DEFAULT NULL,
  `name` text, /* リレーションのテスト用 */
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;                                                                                                                 

Kormaでエンティティを定義

project.clj:

(defproject korma-sample "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.3.0"]
                 [korma "0.3.0-beta9"]
                 [mysql/mysql-connector-java "5.1.13"]]
  :plugins [[lein-swank "1.4.4"]])

core.clj:

(ns korma-sample.core
  (:use [korma db core]))

;; DBアクセスの定義
(defdb mydb
  (mysql
   {:db "kormatest"
    :user "korma"
    :password "korma"}))

;; Userテーブル
(defentity user
  (has-many post)
  (database mydb))

;; Postテーブル
(defentity post
  (belongs-to user)
  (database mydb))

基本的なSelect文

user> (select user)
[{:email "[email protected]", :name "deltam", :id 1}                                                                                                                    
 {:email "[email protected]", :name "dio", :id 2}                                                                                                                          
 {:email "[email protected]", :name "qtaro", :id 3}]                                                                                                           

Where句:

user> (clojure.pprint/pprint
       (select user
         (where (= :id 1))))
[{:email "[email protected]", :name "deltam", :id 1}]                                                                                                                   

Where句でSQL関数を使う場合:

user> (select post
        (where (< :createdAt (sqlfn NOW)))))
[{:name "kkk",                                                                                                                                                         
  :User_id 1,                                                                                                                                                          
  :updatedAt #<Timestamp 2012-09-17 20:18:15.0>,                                                                                                                       
  :createdAt #<Timestamp 2012-09-17 20:18:07.0>,                                                                                                                       
  :text "テスト・テスト",                                                                                                                                              
  :title "kormaをつかってみた",                                                                                                                                        
  :id 1}                                                                                                                                                               
 {:name "kkk",                                                                                                                                                         
  :User_id 1,                                                                                                                                                          
  :updatedAt #<Timestamp 2012-09-18 20:18:15.0>,                                                                                                                       
  :createdAt #<Timestamp 2012-09-18 20:18:07.0>,                                                                                                                       
  :text "テスト・テスト",                                                                                                                                              
  :title "kormaをつかってみた",                                                                                                                                        
  :id 2}]

Where句はこういう書き方もできる(中置記法っぽい)

(select post
  (where {:createdAt [< (sqlfn now)]})))

いろいろめんどくさいのでWhere句をSQLそのまま書きたい場合

(select post
  (where (raw "17=DAY(createdAt)")))

じっさいどんなSQLが流れてるの? -> sql-only, dry-runを使う

(sql-only
    (select post
      (where (raw "17=DAY(createdAt)"))))

"SELECT `post`.* FROM `post` WHERE 17=DAY(createdAt)"

(dry-run
    (select post
      (where (raw "18=DAY(createdAt)"))))

dry run :: SELECT `post`.* FROM `post` WHERE 18=DAY(createdAt) :: []                                                                                                   
[{:id 1}]

リレーションを使う

user> (select post
        (with user))

[{:text "テスト・テスト",                                                                                                                                          
  :name "kkk",                                                                                                                                                     
  :id_2 1,                                                                                                                                                         
  :title "kormaをつかってみた",                                                                                                                                    
  :User_id 1,                                                                                                                                                      
  :createdAt #<Timestamp 2012-09-17 20:18:07.0>,                                                                                                                   
  :email "[email protected]",                                                                                                                                       
  :updatedAt #<Timestamp 2012-09-17 20:18:15.0>,                                                                                                                   
  :id 1,                                                                                                                                                           
  :name_2 "deltam"}                                                                                                                                                
 {:text "テスト・テスト",                                                                                                                                          
  :name "kkk",                                                                                                                                                     
  :id_2 1,                                                                                                                                                         
  :title "kormaをつかってみた",                                                                                                                                    
  :User_id 1,                                                                                                                                                      
  :createdAt #<Timestamp 2012-09-18 20:18:07.0>,                                                                                                                   
  :email "[email protected]",                                                                                                                                       
  :updatedAt #<Timestamp 2012-09-18 20:18:15.0>,                                                                                                                   
  :id 2,                                                                                                                                                           
  :name_2 "deltam"}]                                                                                                                                               

残念ながら、いまのバージョンのbelongs-toではリレーション先カラムとリレーション元カラムが混じってしまう。UserとPostに同じカラムがあった場合は、リレーション先のカラムがcolumn_2など変更される様子。

次にhas-manyではどうなるか。

user> (select user
        (with post))
  
({:post                                                                                                                                                            
  [{:name "kkk",                                                                                                                                                   
    :User_id 1,                                                                                                                                                    
    :updatedAt #<Timestamp 2012-09-17 20:18:15.0>,                                                                                                                 
    :createdAt #<Timestamp 2012-09-17 20:18:07.0>,                                                                                                                 
    :text "テスト・テスト",                                                                                                                                        
    :title "kormaをつかってみた",                                                                                                                                  
    :id 1}                                                                                                                                                         
   {:name "kkk",                                                                                                                                                   
    :User_id 1,                                                                                                                                                    
    :updatedAt #<Timestamp 2012-09-18 20:18:15.0>,                                                                                                                 
    :createdAt #<Timestamp 2012-09-18 20:18:07.0>,                                                                                                                 
    :text "テスト・テスト",                                                                                                                                        
    :title "kormaをつかってみた",                                                                                                                                  
    :id 2}],                                                                                                                                                       
  :email "[email protected]",                                                                                                                                       
  :name "deltam",                                                                                                                                                  
  :id 1}                                                                                                                                                           
 {:post [], :email "[email protected]", :name "dio", :id 2}                                                                                                            
 {:post [], :email "[email protected]", :name "qtaro", :id 3})

こっちはうまく行ってるみたいです。

dry-runしてみると2回クエリ発行しているみたい。

(dry-run (select user (with post)))

dry run :: SELECT `user`.* FROM `user` :: []                                                                                                                           
dry run :: SELECT `post`.* FROM `post` WHERE (`post`.`user_id` = ?) :: [1]                                                                                             
({:post [{:id 1}], :id 1})

明示的にJoinをしたほうが良いのか?

(select user
  (join post (= :post.User_id :user.id)))
[{:email "[email protected]", :name "deltam", :id 1}                                                                                                                    
 {:email "[email protected]", :name "deltam", :id 1}                                                                                                                    
 {:email "[email protected]", :name "dio", :id 2}                                                                                                                          
 {:email "[email protected]", :name "qtaro", :id 3}]

なぜかJoin結果が来ない。。。

(sql-only
  (select user
    (join post (= :post.User_id :user.id))))
    
"SELECT `user`.* FROM `user` LEFT JOIN `post` ON `post`.`User_id` = `user`.`id`"                                                                                       

UserテーブルのカラムしかSelectされてなかったので、select対象のフィールド追加。

(select user
  (fields (raw "*"))
  (join post (= :post.User_id :user.id))
  (where (not= nil :post.id)))

[{:text "テスト・テスト",                                                                                                                                          
  :name "deltam",                                                                                                                                                  
  :id_2 1,                                                                                                                                                         
  :title "kormaをつかってみた",                                                                                                                                    
  :User_id 1,                                                                                                                                                      
  :createdAt #<Timestamp 2012-09-17 20:18:07.0>,                                                                                                                   
  :email "[email protected]",                                                                                                                                       
  :updatedAt #<Timestamp 2012-09-17 20:18:15.0>,                                                                                                                   
  :id 1,                                                                                                                                                           
  :name_2 "kkk"}                                                                                                                                                   
 {:text "テスト・テスト",                                                                                                                                          
  :name "deltam",                                                                                                                                                  
  :id_2 2,                                                                                                                                                         
  :title "kormaをつかってみた",                                                                                                                                    
  :User_id 1,                                                                                                                                                      
  :createdAt #<Timestamp 2012-09-18 20:18:07.0>,                                                                                                                   
  :email "[email protected]",                                                                                                                                       
  :updatedAt #<Timestamp 2012-09-18 20:18:15.0>,                                                                                                                   
  :id 1,                                                                                                                                                           
  :name_2 "kkk"}]

無事Joinはできました。

まとめじゃないけど

リレーション機能は発展途上ですが、全体的に筋が良い感じがする。

いまのうちに貢献しとくと将来的にモテるのでは。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment