[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です。
以上です。