gormのtime.Time型ゼロ値Updateの色々なパターン試してみた

はじめに

gormでTime型のゼロ値更新時にどんなSQLが走っているのか気になったので色々なパターンでどんなSQLが走っているのか確認してみた。

今回使用したコードはgithubにあげてある。

準備

main.goに以下のコードを準備した

package main

import (
    "time"

    "github.com/jinzhu/gorm"
    "github.com/lib/pq"
    sqlmock "gopkg.in/DATA-DOG/go-sqlmock.v1"
)

type repository struct {
    Conn *gorm.DB
}

// Repository repository interface
type Repository interface {
    Update(model interface{}, value map[string]interface{}) error
}

// NewRepository mount repository
func NewRepository(db *gorm.DB) Repository {
    return &repository{
        Conn: db,
    }
}

func main() {
    db, _ := connectDB("mock db")
    defer db.Close()
    db.LogMode(true)
    r := NewRepository(db)
}

func connectDB(dsn string) (*gorm.DB, sqlmock.Sqlmock) {
    var db *gorm.DB
    _, mock, err := sqlmock.NewWithDSN(dsn)
    if err != nil {
        panic("Got an unexpected error.")
    }
    db, err = gorm.Open("sqlmock", dsn)
    if err != nil {
        panic("Got an unexpected error.")
    }
    return db, mock
}

func (m *repository) Update(model interface{}, param map[string]interface{}) error {
    err := m.Conn.Model(model).Updates(param).Error
    if err != nil {
        return err
    }
    return nil
}

パターン1

github.com/lib/pqのNullTimeを使用した場合

// Sample sample struct
type Sample struct {
    ID       int
    JoinDate pq.NullTime
}

func main() {
    ...
    sample := &Sample{}
    value := map[string]interface{}{"id": 1, "join_date": pq.NullTime{Time: time.Now(), Valid: false}}
    r.Update(sample, value)
}

結果は以下のようになった

UPDATE "samples" SET "id" = '1', "join_date" = NULL WHERE "samples","id" = '1'

パターン2

ポインタのTime型に対してpq.NullTimeでUpdateをかけた場合

// Sample sample struct
type Sample struct {
    ID       int
    JoinDate *time.Time
}

func main() {
    ...
    sample := &Sample{}
    value := map[string]interface{}{"id": 2, "join_date": pq.NullTime{Time: time.Now(), Valid: false}}
    r.Update(sample, value)
}

結果は以下のようになった

UPDATE "samples" SET "id" = '2', "join_date" = '0001-01-01 00:00:00' WHERE "samples"."id" = '2'

パターン3、4,5

NullTimeのTimeがPointerのstructに対してUpdateをかけた場合

NullTime

// NullTime pointer time null time struct
type NullTime struct {
    Time  *time.Time
    Valid bool
}

// Scan 
func (nt NullTime) Scan(value interface{}) error {
    ...
}

// Value
func (nt NullTime) Value() (driver.Value, error) {
    ...
}

パターン3

// Sample sample struct
type Sample struct {
    ID       int
    JoinDate NullTime
}

func main() {
    ...
    sample := &Sample{}
    value := map[string]interface{}{"id": 3, "join_date": NullTime{Time: nil, Valid: true}}
    r.Update(sample, value)
}

結果は以下のようになった

UPDATE "samples" SET "id" = '3' WHERE "samples"."id" = '3'

パターン4

// Sample sample struct
type Sample struct {
    ID       int
    JoinDate NullTime
}

func main() {
    ...
    sample := &Sample{}
    value := map[string]interface{}{"id": 4, "join_date": NullTime{Time: nil, Valid: false}}
    r.Update(sample, value)
}

結果は以下のようになった

UPDATE "samples" SET "id" = '4' WHERE "samples"."id" = '4'

パターン5

// Sample sample struct
type Sample struct {
    ID       int
    JoinDate NullTime
}

func main() {
    ...
    now := time.Now()
    sample := &Sample{}
    value := map[string]interface{}{"id": 5, "join_date": NullTime{Time: &now, Valid: true}}
    r.Update(sample, value)
}

結果は以下のようになった

UPDATE "samples" SET "id" = '5' WHERE "samples"."id" = '5'

パターン6

pq.NullTimeに対してpq.NullTimeの値を設定しないでUpdateをかける

// Sample sample struct
type Sample struct {
    ID       int
    JoinDate pq.NullTime
}

func main() {
    ...
    sample := &Sample{}
    value := map[string]interface{}{"id": 6, "join_date": pq.NullTime{Valid: false}}
    r.Update(sample, value)
}

結果は以下のようになった

UPDATE "samples" SET "id" = '6' WHERE "samples"."id" = '6'

結論

Time型のカラムをNull Updateするには、pq.NullTimeのような型を使用し、pq.NullTime{Time: 何かしらの時間, Valid: false}でUpdateをかける必要があるようだ