Golangを使用したMySQLで設定したデータ型の注意点

## 概要

今回はMySQLを使用して、作成したテーブルのカラムのデータ型で`time.Time`型を指定した際の注意点を共有します。

## 前提

ディレクトリ構造

```
.
├── models
│   ├── base.go
│   └── users.go
├── docker-compose.yml
├── Dockerfile
├── go.mod
├── go.sum
└── main.go
```

今回はDockerを使用して環境構築をしています。

```Dockerfile
FROM golang:1.19.0-alpine3.16
WORKDIR /app
COPY . .
```

```docker-compose.yml
version: '3.9'
services:
  db:
    image: mysql:8.0
    container_name: mysql
    env_file: .env
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DB_NAME}
    volumes:
      - ./mysql:/var/db/mysql
  web:
    build:
      context: .
    container_name: web
    volumes:
      - .:/app
    depends_on:
      - db
volumes:
  mysql:
```

今回は下記のような**usersテーブル**を作成します。

|usersテーブル|型|
|:-:|:-:|
|id|INT|
|name|VARCHAR(32)|
|created_at|DATETIME|

```models/base.go
package models

import (
	"database/sql"
	"fmt"
	"log"

	"github.com/soicchi/golang-todo-app/config"

	"github.com/google/uuid"
	_ "github.com/go-sql-driver/mysql"
)

var Db *sql.DB

var err error

const (
	tableNameUser = "users"
)

func init() {
	Db, err = sql.Open("mysql", "root:password@tcp(mysql)/db")
	if err != nil {
		log.Fatalln(err)
	}

	err = Db.Ping()
	if err != nil {
		log.Fatalln(err)
	}
	log.Println("データベース接続完了")
```

データベースの`password`等は今回は簡易的に表すために平文で記載しています。

本来は環境変数を設定し、configファイルから参照する方が良いです。

次にユーザーの構造体を定義し、ユーザーを取得する関数を作成します。

``` models/users.go
package models

import (
	"log"
	"time"
)

type User struct {
	ID int
	Name string
	CreatedAt time.Time
}

func GetUser(id int) (user User, err error) {
	user = User{}
	cmd := `select * from users where id=?`
	err = Db.QueryRow(cmd, id).Scan(
		&user.ID,
		&user.Name,
		&user.CreatedAt,
	)

	return user, err
```

## エラー内容

では、main.goを下記のように記載した上で実行してみましょう。

```main.go
package main

import (
	"fmt"
	"log"

	"github.com/soicchi/golang-todo-app/models"
)

func main() {
	fmt.Println(models.Db)

	user, err := models.GetUser(2)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(user)
}
```

すると下記のようなエラーが出ました。

```
2022/08/14 01:26:11 main.go:33: sql: Scan error on column index 2, name "created_at": unsupported Scan, storing driver.Value type []uint8 into type *time.Time
exit status 1
```

どうやら`created_at`の型が原因でエラーが出ているようです。

## 解決方法

ドキュメントをみると下記のように書いてありました。

>The default internal output type of MySQL DATE and DATETIME values is []byte which allows you to scan the value into a []byte, string or sql.RawBytes variable in your program.
However, many want to scan MySQL DATE and DATETIME values into time.Time variables, which is the logical equivalent in Go to DATE and DATETIME in MySQL. You can do that by changing the internal output type from []byte to time.Time with the DSN parameter parseTime=true. You can set the default time.Time location with the loc DSN parameter.

https://github.com/go-sql-driver/mysql#timetime-support

どうやら`time.Time`型を指定した場合にMySQLの接続情報にパラメータとして`parseTime=true`を含めないといけないようです。

なので先ほどの`models/base.go`を下記のように変更します。

```models/base.go
 package models

import (
	"database/sql"
	"fmt"
	"log"

	"github.com/soicchi/golang-todo-app/config"

	"github.com/google/uuid"
	_ "github.com/go-sql-driver/mysql"
)

var Db *sql.DB

var err error

const (
	tableNameUser = "users"
)

func init() {
	Db, err = sql.Open("mysql", "root:password@tcp(mysql)/db?parseTime=true")  // <- 変更点
	if err != nil {
		log.Fatalln(err)
	}

	err = Db.Ping()
	if err != nil {
		log.Fatalln(err)
	}
	log.Println("データベース接続完了")
```

もう一度実行してみると

```
2022/08/14 01:32:12 base.go:34: データベース接続完了
{2 f0e083e0-1b24-11ed-8afc-0242c0a87003 test test@test.com 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 2022-08-13 16:28:19 +0000 UTC}
```

ユーザー情報を取得できました。