2012-01-28 76 views
2

甜而簡單,永久連接如何工作?考慮以下模型:由Yesod中的selectOneMany擋住

Person 
    number Int 
    numberOfEyes Int 
    firstName FirstnamesId 
    lastName LastnamesId 
Lastnames 
    lastname String 
Firstnames 
    firstname String 

假設我只有一個人的號碼,我如何檢索他的全名和他的眼睛的數量?

我試圖通過haskellers.org source尋找,但找不到任何連接的例子。我還檢查了yesod書中的chapter on joins,但它只是讓我的眼睛旋轉。我的Haskell知識水平非常低,所以要溫和。

回答

3

這裏有兩種方法具有相同的結果類型:

  1. 新類型化的SQL的基礎上,從菲利普·萊薩的「esqueleto」包,它是持久的基礎
  2. 和以前rawSql方式

    只需加1或2作爲參數


對於定界符quasiquoter
{- file prova.hs-} 
{-# LANGUAGE QuasiQuotes, TemplateHaskell, TypeFamilies, OverloadedStrings #-} 
{-# LANGUAGE GADTs, FlexibleContexts, ConstraintKinds, ScopedTypeVariables #-} 
import Prelude hiding (catch) 
import Control.Exception 
import Database.Persist 
import Database.Persist.Sqlite 
import Database.Persist.TH 
import Control.Monad.IO.Class (liftIO) 
import Data.Text (Text) 
import Database.Persist.Quasi 
import Database.Esqueleto as Esql 
import Database.Persist.GenericSql (SqlPersist, rawSql) 
import Control.Monad.Logger (MonadLogger) 
import Control.Monad.Trans.Resource (MonadResourceBase) 
import System.Environment (getProgName, getArgs) 
import System.Exit (exitSuccess, exitWith, ExitCode(..)) 
import Text.Printf (printf) 

import QQStr(str) -- heredoc quasiquoter module 

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persist| 
Person 
    number Int 
    numberOfEyes Int 
    firstName FirstnamesId 
    lastName LastnamesId 
    UniquePersonNumber number 
    deriving Show 

Lastnames 
    lastname String 
    deriving Show 

Firstnames 
    firstname String 
    deriving Show 
|] 

-- the esqueleto way 

-- with this type annotation it could be run in a yesod handler with ''runDB'' 
getPersonInfoByNumber :: (PersistQuery SqlPersist m, MonadLogger m, MonadResourceBase m) => Int -> SqlPersist m (Maybe (Int, String, String)) 
getPersonInfoByNumber pNumber = do 
    result <- select $ from $ \(fn `InnerJoin` p `InnerJoin` ln) -> do 
      on ((p ^. PersonFirstName) Esql.==. (fn ^. FirstnamesId)) 
      on ((p ^. PersonLastName) Esql.==. (ln ^. LastnamesId)) 
      where_ ((p ^. PersonNumber) Esql.==. val pNumber) 
      return (p , fn, ln) 

    case result of 
     [(Entity _ p, Entity _ fn, Entity _ ln)] -> return $ Just (personNumberOfEyes p, firstnamesFirstname fn, lastnamesLastname ln) 
     _ -> return Nothing 

-- the rawSql way 

stmt = [str|SELECT ??, ??, ?? 
          FROM Person, Lastnames, Firstnames 
          ON Person.firstName = Firstnames.id 
          AND Person.lastName = Lastnames.id 
          WHERE Person.number = ? 
         |] 

getPersonInfoByNumberRaw :: (PersistQuery SqlPersist m, MonadLogger m, MonadResourceBase m) => Int -> SqlPersist m (Maybe (Int, String, String)) 
getPersonInfoByNumberRaw pNumber = do 
    result <- rawSql stmt [toPersistValue pNumber] 

    case result of 
     [(Entity _ p, Entity _ fn, Entity _ ln)] -> return $ Just (personNumberOfEyes p, firstnamesFirstname fn, lastnamesLastname ln) 
     _ -> return Nothing 


main :: IO() 
main = do 
    args <- getArgs 
    nomProg <- getProgName 
    case args of 
     [] -> do 
      printf "%s: just specify 1 for esqueleto or 2 for rawSql.\n" nomProg 
      exitWith (ExitFailure 1) 

     [arg] -> (withSqliteConn ":memory:" $ runSqlConn $ do 
       runMigration migrateAll 

       let myNumber = 5 
       fnId <- insert $ Firstnames "John" 
       lnId <- insert $ Lastnames "Doe" 

       -- in case of insert collision, because of UniquePersonNumber constraint 
       -- insertUnique does not throw exceptions, returns success in a Maybe result 
       -- insert would throw an exception 

       maybePersId <- insertUnique $ Person {personNumber = myNumber, personNumberOfEyes=2, 
               personFirstName = fnId, personLastName = lnId} 

       info <- case arg of 
          "1" -> getPersonInfoByNumber myNumber 
          _ -> getPersonInfoByNumberRaw myNumber 
       liftIO $ putStrLn $ show info 
      ) 
       `catch` (\(excep::SomeException) -> 
           putStrLn $ "AppSqlError: " ++ show excep) 

額外的模塊

module QQStr(str) where 

import Prelude 
import Language.Haskell.TH 
import Language.Haskell.TH.Quote 

str = QuasiQuoter { quoteExp = stringE, quotePat = undefined 
        , quoteType = undefined, quoteDec = undefined } 

執行:

[email protected]:~/webs/yesod/prova$ ./cabal-dev/bin/prova 1 
Migrating: CREATE TABLE "Person"("id" INTEGER PRIMARY KEY,"number" INTEGER NOT NULL,"numberOfEyes" INTEGER NOT NULL,"firstName" INTEGER NOT NULL REFERENCES "Firstnames","lastName" INTEGER NOT NULL REFERENCES "Lastnames") 
Migrating: CREATE TABLE "Lastnames"("id" INTEGER PRIMARY KEY,"lastname" VARCHAR NOT NULL) 
Migrating: CREATE TABLE "Firstnames"("id" INTEGER PRIMARY KEY,"firstname" VARCHAR NOT NULL) 
Just (2,"John","Doe") 
+0

因爲我已經從哈斯克爾移動了,但我會去的肢體,並認爲這是不正確的測試。 :) – andreasm 2012-09-19 07:25:09