[Go言語] gormのクエリ条件をイテレーション内で指定する際は結果の格納先を初期化するべきという話
GoのORMライブラリgorm(http://doc.gorm.io/)でqueryをしたときの挙動でハマった.
コードは次の通り.
package main import ( "fmt" "log" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/postgres" ) type Employee struct { gorm.Model Num string LN string FN string } func main() { connStr := "postgres://example:example@localhost:5432/example?sslmode=disable" db, err := gorm.Open("postgres", connStr) if err != nil { log.Fatal(err) } defer db.Close() // db.LogMode(true) db.AutoMigrate(&Employee{}) emps := []Employee{ Employee{Num: "001", LN: "ichi", FN: "taro"}, Employee{Num: "002", LN: "ninomiya", FN: "kinjiro"}, Employee{Num: "003", LN: "minowa", FN: "akihiro"}, Employee{Num: "004", LN: "yon", FN: "jun"}, Employee{Num: "005", LN: "godaigo", FN: "tenno"}, } for _, e := range emps { db.NewRecord(e) db.Create(&e) } nums := []string{"001", "004", "005"} var emp Employee for _, n := range nums { notFound := db.Where("num = ?", n).First(&emp).RecordNotFound() if notFound { continue } fmt.Printf("%s %s\n", emp.LN, emp.FN) } return }
nums
スライス内に記載した従業員番号に該当する従業員の名前を表示するというもの.
狙いとしては
ichi taro yon jun godaigo tenno
という結果が欲しかったのだが, 実際は次の通り, 初めの一人しかヒットしていない.
ichi taro
db.LogMode(true)
のコメントを外してログを見てみると, nums
のイテレーションにより, 次のクエリが発行されていた.
SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND ((num = '001')) ORDER BY "employees"."id" ASC LIMIT 1 SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND "employees"."id" = '1' AND ((num = '004')) ORDER BY "employees"."id" ASC LIMIT 1 SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND "employees"."id" = '1' AND ((num = '005')) ORDER BY "employees"."id" ASC LIMIT 1
どうやら, 従業員番号"001"でヒットしたユーザ(id=1)データが
&emp
ポインタに残っていると, これを引数にとるFirst
メソッド
はid=1のユーザの中から探してしまうようだ.
id=1かつ従業員番号"004"の従業員はいないので, スルーされてしまう.
このFirst
メソッドは, プライマリキー順にサーチし, 該当の条件に一致する列を返す.
実装は次の通り.
// First find first record that match given conditions, order by primary key func (s *DB) First(out interface{}, where ...interface{}) *DB { newScope := s.NewScope(out) newScope.Search.Limit(1) return newScope.Set("gorm:order_by_primary_key", "ASC"). inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db }
s.parent.callbacks.queries
をコールしてWHERE句へ条件を追加していき, 結果DB
への参照を返す.
イテレーションごとに, クエリ結果を格納する変数(ポインタ)を初期化してやれば, クエリ条件も初期化され, 望みの結果が得られた.
次のように, emp
の宣言をfor
ループの内部で行うよう修正.
nums := []string{"001", "004", "005"} // var emp Employee for _, n := range nums { var emp Employee notFound := db.Where("num = ?", n).First(&emp).RecordNotFound() if notFound { continue } ...
実行されたクエリ
SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND ((num = '001')) ORDER BY "employees"."id" ASC LIMIT 1 SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND ((num = '004')) ORDER BY "employees"."id" ASC LIMIT 1 SELECT * FROM "employees" WHERE "employees"."deleted_at" IS NULL AND ((num = '005')) ORDER BY "employees"."id" ASC LIMIT 1
結果
ichi taro yon jun godaigo tenno
Dockerコンテナをホスト側のcronで実行する
やりたいこと
- とあるプログラムを実行するDockerコンテナを毎日定刻に起動したい。
- 処理が完了したらコンテナは消去したい。
つまり、次のコマンドをcronで実行したい。
$ docker run -it --rm my_image my_command
やったこと
crontabにそのまま書けばいいじゃん!
と思ったが、そうは東京医科歯科大学。
$ crontab -e 0 7 * * * docker run -it --rm my_image my_command
定刻になっても、うんともすんともしない。
まずは、crontabの実行環境で、dockerコマンドにパスが通ってんのかが気になった。
$ which docker /usr/bin/docker $ crontab -e * * * * * echo $PATH > /tmp/env.txt
結果は、
$ cat /tmp/env.txt PATH=/usr/bin:/bin
.bash_profile
に書いてあるようなユーザ定義の環境変数は受け継がれていないが、
少なくともdockerコマンドへのパスは通っているようだ。
デフォルトの/var/log/cronログファイルには、実行結果までは出力されない。
そこで、エラーチェックのために、次のように設定した。
* * * * * docker run -it --rm my_image my_command > /tmp/cron.log 2>&1
結果は、
$ cat /tmp/cron.log the input device is not a TTY
となっていた。 実端末(TTY)からの入力でないことで怒られている。
原因はイカの通り。
cronで指定されたコマンドは実端末から実行される訳ではない。
実際、
$ crontab -e * * * * * tty > /tmp/tty.txt
とすると、
$ cat /tmp/tty.txt not a tty
となる。
docker runコマンドの-itオプションは、
実行時の端末をコンテナ内のプロセスに割り当てるものであるため、
割り当てるべき端末がない、と怒っていたのだ。
結論
crontabには、次のように書けば良いです。(毎朝7時に実行したい場合)
0 7 * * * docker run --rm my_image my_command
こうしてできたのが、↓のbotです。
以上です。