Goのdatabase/sqlパッケージとGORMのコードリーディング

## 今日やること

[database/sql](https://pkg.go.dev/database/sql)パッケージを素朴に使う。
その上で[GORM](https://gorm.io/ja_JP/docs/index.html)のコードを読む。

## Version
- Mysql 8.X
- Go 1.18

## database/sqlパッケージを使う。

### データベースとテーブル作成
```sql

CREATE DATABASE example_go_sql;
CREATE TABLE users (
  id   SERIAL,
  name VARCHAR(20)
);
```

### main.go
- 複数行取得 `func queryRows`
- 一行取得 `func queryRow`
- インサート `func create`
- トランザクションの使用(Update) `func txUpdate`

を行います。

```go
package main

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

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

func main() {
	db := open()
	defer db.Close()

	configDB(db)

	ctx := context.TODO()
	conn := connection(db, ctx)
	defer conn.Close()

	queryRows(conn)
	queryRow(conn)
	create(conn, "hey")
	txUpdate(conn)
}

func open() *sql.DB {
	db, err := sql.Open("mysql", "root:sample@tcp(127.0.0.1:3306)/example_go_sql")
	if err != nil {
		log.Fatalln(err)
	}
	return db
}

func configDB(db *sql.DB) {
	db.SetConnMaxLifetime(0)
	db.SetMaxIdleConns(50)
	db.SetMaxOpenConns(50)
}

func connection(db *sql.DB, ctx context.Context) *sql.Conn {
	conn, err := db.Conn(ctx)
	if err != nil {
		log.Fatalln(err)
	}
	return conn
}

func queryRows(conn *sql.Conn) {
	ctx := context.TODO()
	rows, err := conn.QueryContext(ctx, "SELECT * FROM users WHERE id > ?", 0)
	if err != nil {
		log.Fatalln(err)
	}
	defer rows.Close()

	for rows.Next() {
		var id, name string
		err := rows.Scan(&id, &name)
		if err != nil {
			log.Fatalln(err)
		}

		fmt.Println(id, name)
	}
	if err := rows.Err(); err != nil {
		log.Fatalln(err)
	}
}

func queryRow(conn *sql.Conn) {
	ctx := context.TODO()
	row := conn.QueryRowContext(ctx, "SELECT * FROM users WHERE id > ?", 0)
	var id, name string
	err := row.Scan(&id, &name)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(id, name)
}

func create(conn *sql.Conn, name string) {
	ctx := context.TODO()
	res, err := conn.ExecContext(ctx, "INSERT INTO users (name) VALUES(?)", name)
	if err != nil {
		log.Fatalln(err)
	}
	updated, err := res.RowsAffected()
	if err != nil {
		log.Fatalln(err)
	}
	inserted, err := res.LastInsertId()
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(inserted, updated)

}

func txUpdate(conn *sql.Conn) {
	ctx := context.TODO()
	tx, err := conn.BeginTx(ctx, nil)
	if err != nil {
		log.Fatalln(err)
	}

	_, execErr := tx.ExecContext(ctx, "UPDATE users SET name = ? WHERE id = ?", "updated", 1)
	if execErr != nil {
		if rollbackErr := tx.Rollback(); rollbackErr != nil {
			log.Fatalf("update failed: %v, unable to rollback: %v\n", execErr, rollbackErr)
		}
		log.Fatalf("update failed: %v", execErr)
	}
	if err := tx.Commit(); err != nil {
		log.Fatal(err)
	}
}
```

## database/sqlを使用した感想
```go
	row := conn.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", 1)
	var id, name string
	row.Scan(&id, &name)
```

変数へのアサインがめんどくさいです。
上のコードスニペットの様に、
（愚直にやると）カラムの分だけ人間が変数を定義する羽目になります。

一方で普段使用しているGORMは次のコードスニペットの様に実行できて、構造体を指定するだけです。

```go
	var user User
	db.WithContext(ctx).First(&user)
```


**GORM便利！！**

## GORMのコードを読む

GORMが便利ということはわかりました。
あとはGORMがこの便利なインタフェースを、どう実現しているか知れれば完璧ですね。

今回はタグv1.23.8のコードを読みます

https://github.com/go-gorm/gorm/tree/v1.23.8

### Scanで検索

database/sqlではScanを使用して変数にアサインをしました。
このScanメソッドは[定義](https://pkg.go.dev/database/sql#Row.Scan)を見ると
任意の数のパラメーターを渡すことができます。

GORMでは当然 `Scan(slice...)` のように呼び出されていると推測し

`Scan(.+...)` で検索します

```
$ ag "Scan\(.+\.\.\."
interfaces.go
81:	Scan(dest ...interface{}) error

scan.go
67:	db.AddError(rows.Scan(values...))
125:			db.AddError(rows.Scan(values...))
145:			db.AddError(rows.Scan(values...))
```

結果2つのファイルで合計4行ヒットしました。
このうちinterface.goのものは明らかにinterface定義なのでscan.goを見ていきます。

scan.goでは次のメソッドがScanを呼び出していました。
[func scanIntoStruct](https://github.com/go-gorm/gorm/blob/v1.23.8/scan.go#L53)
[func Scan](https://github.com/go-gorm/gorm/blob/v1.23.8/scan.go#L107)

メソッド名的に `scanIntoStruct` 今回探していた関数です。

```
func (db *DB) scanIntoStruct(rows Rows, reflectValue reflect.Value, values []interface{}, fields []*schema.Field, joinFields [][2]*schema.Field) {
	for idx, field := range fields {
		if field != nil {
			values[idx] = field.NewValuePool.Get()
		} else if len(fields) == 1 {
			if reflectValue.CanAddr() {
				values[idx] = reflectValue.Addr().Interface()
			} else {
				values[idx] = reflectValue.Interface()
			}
		}
	}

	db.RowsAffected++
	db.AddError(rows.Scan(values...))

	joinedSchemaMap := make(map[*schema.Field]interface{}, 0)
	for idx, field := range fields {
		if field != nil {
			if len(joinFields) == 0 || joinFields[idx][0] == nil {
				db.AddError(field.Set(db.Statement.Context, reflectValue, values[idx]))
			} else {
				joinSchema := joinFields[idx][0]
				relValue := joinSchema.ReflectValueOf(db.Statement.Context, reflectValue)
				if relValue.Kind() == reflect.Ptr {
					if _, ok := joinedSchemaMap[joinSchema]; !ok {
						if value := reflect.ValueOf(values[idx]).Elem(); value.Kind() == reflect.Ptr && value.IsNil() {
							continue
						}

						relValue.Set(reflect.New(relValue.Type().Elem()))
						joinedSchemaMap[joinSchema] = nil
					}
				}
				db.AddError(joinFields[idx][1].Set(db.Statement.Context, relValue, values[idx]))
			}

			// release data to pool
			field.NewValuePool.Put(values[idx])
		}
	}
}
```
https://github.com/go-gorm/gorm/blob/v1.23.8/scan.go#L53-L94

scanIntoStructメソッド内では `values` に読み込んだ行データをセットしています。

このvaluesに入った値は `field.Set` の引数に渡されています。

このvaluesが何かは一旦宿題として`field.Set` を見ていきます。

```
	Set                    func(context.Context, reflect.Value, interface{}) error
```

https://github.com/go-gorm/gorm/blob/v1.23.8/schema/field.go#L87

schema.Field 構造体にSetという名前で関数型のフィールドがありました。

このSetフィールドは`setupValuerAndSetter`メソッドでセットされています。

```
func (field *Field) setupValuerAndSetter() {
```

https://github.com/go-gorm/gorm/blob/v1.23.8/schema/field.go#L437

fieldに応じた型の種類によってswitch文でSetフィールドをセットしています。
Setフィールドにセットされる関数は `SetBool` などの型に応じたメソッドを呼び
構造体に値をセットしています。


```
	switch field.FieldType.Kind() {
	case reflect.Bool:
		field.Set = func(ctx context.Context, value reflect.Value, v interface{}) error {
			switch data := v.(type) {
			case **bool:
				if data != nil && *data != nil {
					field.ReflectValueOf(ctx, value).SetBool(**data)
				} else {
					field.ReflectValueOf(ctx, value).SetBool(false)
				}
			case bool:
				field.ReflectValueOf(ctx, value).SetBool(data)
			case int64:
				field.ReflectValueOf(ctx, value).SetBool(data > 0)
			case string:
				b, _ := strconv.ParseBool(data)
				field.ReflectValueOf(ctx, value).SetBool(b)
			default:
				return fallbackSetter(ctx, value, v, field.Set)
			}
			return nil
		}
```


https://github.com/go-gorm/gorm/blob/v1.23.8/schema/field.go#L583-L604

あとは `scanIntoStruct` メソッドに渡ってくる `fields` がどう初期化されているかですが
これも一旦宿題とします。


## reflectパッケージについてちょっとだけ

reflectパッケージのValue構造体にある `SetXXX` メソッドについてコードで補足します。

```
type (
       User struct {
               Name string
       }
)

func main() {
       var u User

       v := reflect.ValueOf(&u)
       f := reflect.Indirect(v).Field(0)
       f.SetString("nishi")
       fmt.Println(u.Name) // nishi
}
```

このようにreflectパッケージを使えば、構造体のフィールド名を使用せずに
構造体のフィールドを変更できます。

私もreflectパッケージについてはあまり詳しくないので、いつか調べてまた記事にします。

## まとめ

今回は database/sql パッケージを触りました。
普段使う便利ライブラリもGoの標準パッケージを内部的に使用していることがほとんどです。
GORMのような便利ライブラリがどう標準パッケージを利用しているかをコードを読んで理解しました。(ざっくり)
まだまだ知らないことがいっぱいあって、記事のネタに困らなくて良いですね。