27536
relational record で auto increment なカラムの値を指定せずに insert する方法を教えてください。また、そのときに
auto increment されて insert された値も取得したいです。

1日ほどソースコードの
[`Database.Relational.Query.SQL.insertPrefixSQL`](https://github.com/khibino/haskell-
relational-record/blob/64ddb508475363d735c60d85441e686482feeadd/relational-
query/src/Database/Relational/Query/SQL.hs#L95-L98) の中身を読んでみたのですが、`Projection`
と `Pi` の役割が理解できず insert するカラムを指定する方法がよく分かりませんでした。
CC BY-SA 3.0
2016-07-10T18:16:55.347
27511
2016-07-11T11:22:00.723
153
question
haskell
relational record で auto increment なカラムの値を指定せずに insert する方法
relational-recordの仕組みについて十分に理解していないのでスマートな方法ではないかもしれませんが、要望の処理は満たせましたので回答します。

relational-recordのdefineTable*で生成される型はテーブルのすべてのカラムを含むので、  
auto incrementやdefaultが指定されているカラムの値を指定せずにINSERTしたいときは、  
自分でカラムを減らした型を定義する必要があります。

生成される型と作った型の対応を表現するのに`Pi`が必要です。  
(`Projection`との違いは、`Projection`がSQL型-Haskell型の対応を表すのに対して`Pi`はHaskell型-
Haskell型の対応を表す点でないかと思います)。  
元の型`A`から別の型`B`への対応は`Pi A B`と表現します。  
`Pi A B`はApplicativeの`<$>, <*>`によく似た`|$|, |*|`を使うと楽に作れます。

**TestTable.hs**  

```

    module TestTable where
    
    import qualified Data.Text as T
    
    import DataSource (defineTable)
    
    import Database.Relational.Query hiding (id')
    import Database.HDBC.Query.TH (makeRecordPersistableDefault)
    
    -- create table test_table (
    --   id integer primary key auto_increment,
    --   name varchar(40) not null
    --   );
    $(defineTable "test_table")
    
    data TestTable1 = TestTable1 { t1Name :: T.Text }
    $(makeRecordPersistableDefault ''TestTable1)
    
    piTestTable1 :: Pi TestTable TestTable1
    piTestTable1 = TestTable1 |$| name'
    -- `name'`はdefineTableで生成される
    
    insertAutoIncrement :: Insert TestTable1
    insertAutoIncrement = typedInsert tableOfTestTable piTestTable1
    -- `tableOfTestTable`はdefineTableで生成される
    
```

またMySQLのauto incrementで生成された値は[MySQLのC
APIもしくはSQL関数](https://dev.mysql.com/doc/refman/5.6/ja/example-auto-
increment.html)で取得できます。  
relational-record上でそのSQL関数を使うには以下のようにすればいいでしょう。  
(C APIをrelational-recordやHDBCで使う方法は分かりませんでした)。

**MySQLFunction.hs**  

```

    module MySQLFunction where
    
    import Data.Int (Int64)
    
    import Database.Relational.Query
    import Database.Relational.Query.Type (unsafeTypedQuery)
    
    selectLastInsertId :: Query () Int64
    selectLastInsertId = unsafeTypedQuery "SELECT LAST_INSERT_ID()"
    
```

以上をまとめると以下のように使えます。

**Main.hs**  

```

    module Main where
    
    import Control.Monad (forM)
    
    import DataSource (connect)
    import TestTable
    import MySQLFunction
    
    import Database.HDBC (withTransaction)
    import Database.HDBC.Record (runQuery, prepareInsert, runPreparedInsert)
    
    main :: IO ()
    main = do
        conn <- connect
        withTransaction conn $ \conn' -> do
            prepared <- prepareInsert conn' insertAutoIncrement
            forM ["Simon", "Robin", "Douglas"] $ \nm -> do
                _ <- runPreparedInsert prepared $ TestTable1 nm
                lastId <- runQuery conn' selectLastInsertId ()
                print lastId
        return ()
    
    -- > [1]
    -- > [2]
    -- > [3]
    
```
CC BY-SA 3.0
2016-07-11T11:22:00.723
27536
2016-07-11T11:22:00.723
8761
27511
answer