SQL関連のGoライブラリー紹介


GunosyのGoエンジニア、Gregと申します。GoConなどでお会いしたことがあるかもしれません。１日目の記事で松本さんがいくつかのライブラリーを紹介しましたが、もう少し詳しく説明しようと思っています。日本語不自由で申し訳ないです！

## SQLのデータ型

API開発での多くのフローはこういう感じだと思います。

* DBからデータを取る
* Goが扱えるstructなどにデータを読み込む
* データを編集してDBに保存
* データをJSONにしてクライアントに送る

データがシンプルな形であれば、標準パッケージだけで済みますが、たとえばNULL可の値などが入ると、扱いが若干面倒くさくなるので、以下のライブラリーを作りました。

#### guregu/null

https://github.com/guregu/null

SQLのNULL許容型をJSONに吐き出してるなら幸せになるライブラリーです。nullとzeroというパッケージがあって、nullはnull値とzero値を別々に扱って、zeroはゼロ値とnull値を一緒にします。好きなstructに入れるだけでこういう感じになります。

```go
type ImportantData struct {
    NullyString   null.String
    ZeroingString zero.String
}


```

##### JSONエンコード

| DBでの値 | `null.Int`のJSON      | `zero.Int`のJSON |  
| ------- | --------------------- | --------------- |
| NULL    | null                　 | 0               |
| 0       | 0                     | 0               |
| 123     | 123                   | 123             |

| DBでの値 | `null.String`のJSON      | `zero.String`のJSON |
| ------- | --------------------- | --------------- |
| NULL    | null                  | ""              |
| ""      | ""                    | ""              |
| "hello" | "hello"               | "hello"         |



##### DBに保存すると…
| 値      | `null.Int`            | `zero.Int`       |
| ------- | --------------------- | --------------- |
| NULL    | NULL                  | NULL            |
| 0       | 0                     | NULL            |
| 123     |  123                  | 123             |

| 値 | `null.String`      | `zero.String` |
| ------- | --------------------- | --------------- |
| NULL    | NULL                  | NULL            |
| ""      | ""                    | NULL            |
| "hello" | "hello"               | "hello"         |

##### なぜ作ったのか
`sql.NullInt64`など、標準パッケージにありますが、JSONエンコードすると、こうなります。

```json
{
  "myNullableInt": {
     "Int64": 1234,
     "Valid": true
  }
}
```

使い物にならないですね。ポインターを使って`*int`にしたら綺麗にJSONエンコードされますが、`invalid memory address or nil pointer dereference`が出やすくなり、扱いが面倒になります。


### guregu/toki

https://github.com/guregu/toki

tokiはSQLのTIME型を対応してくれるとても小さなライブラリーです。`toki.Time`とNULL許容の`toki.NullTime`があります。`guregu/null`のように、structに入れるだけどDBとJSONのやりとりがシンプルになります。

##### なぜ作ったのか
`time.Time`も文字列も使えますが、`time.Time`はタイムゾーンなど、複雑なデータが含まれたため、バグがちょいちょい出ていました。文字列はできるだけテキスト以外に使いたくないですね。


## SQLのテスト

### guregu/mogi

https://github.com/guregu/null

SQLサーバーを立てずにテストができる、SQLのモックドライバーです。たとえば、このコードとテストがあるとします。

#### モデル
```go
var db *sql.DB

type Beer struct {
    ID   int64
    Name string
    Pct  float32
}

func GetBeer(id int64) (beer Beer, err error) {
    query := `SELECT id, name, pct FROM beer WHERE id = ?`
    err = db.QueryRow(query, id).Scan(&beer.ID, &beer.Name, &beer.Pct)
    return
}
```

#### テスト
```go
var beerFixture = Beer{
    ID:   42,
    Name: "Yona Yona Ale",
    Pct:  5.5,
}

func TestGetBeer(t *testing.T) {
    beer, err := GetBeer(42)
    if err != nil {
        t.Fatal("err should be nil, but is:", err)
    }
    // Here's a lazy way to compare our results and our expectations.
    if !reflect.DeepEqual(beer, beerFixture) {
        t.Errorf("%#v ≠ %#v", beer, beerFixture)
    }
}
```

SQLサーバーに接続して、`beerFixture`のデータを入れれば通りますが、モックしてみましょう。

まず、mogiに"接続"しましょう。

```go
import (
    "database/sql"

    "github.com/guregu/mogi"
)

func init() {
    db, _ = sql.Open("mogi", "")
    mogi.Verbose(true) // stubされてないクエリーをログ
}
```

そして、stubを入れましょう。

```go
func TestGetBeer(t *testing.T) {
    defer mogi.Reset() // 最後にReset()しないとstubが残されてしまう
    mogi.Select("id", "name", "pct").StubCSV(`42,Yona Yona Ale,5.5`)
    ...
}
```

`mogi.Select(...)`にstubしたいコラムを渡します。しかし、`id = 42`以外のレコードや、`beer`以外のテーブルのレコードに反応してしまいますので、もう少し精密にしましょう。

```go
mogi.Select("id", "name", "pct").
        From("beer").
        Where("id", 42).
        StubCSV(`42,Yona Yona Ale,5.5`)
```

完璧です。これでいいテストが書けました。

SELECT以外のSQL文や非同期処理対応など、いろいろできるので、ぜひ試してみてください。

## 最後に

ここまで読んでくれてありがとうございます。プルリクエストはいつでも受けますので、一緒にGoとSQLの世界を広げましょう。