どんな状況?
たとえば以下のようなCreateRecords()という関数があるとして、その返り値はなんらかの構造体Tを要素に持つSliceが実体のanyです。gormのCreateにそのまま渡すことができ、実行することでレコードがインサートされるものとします。
func CreateRecords() any {
(実装は省略)
}
c:=CreateRecords()
if r:=db.Create(c); r.Error!=nil {
(エラー処理)
}
db(ここでは*gorm.DBのこと)はOpenでどのデータベースのドライバーを使うかによって挙動が変わる可能性があり、今回はsqliteを使ったところCreateRecords()が返す要素数が多すぎて1度にINSERTするクエリが発行できないといった問題が発生しました。
cに含まれている要素数を絞って(*gorm.DB).Create(any)をコールすればよさそうなものの、CreateRecords()側の実装は変えたくありません(あるいはブラックボックスで変えようがないかもしれません)。加えてCreateRecords()がどんな構造体を要素に持つSliceを返すかは、実行するまでわかりません。
解答:reflectionを使う
すぐにピンと来るとは思いますが、やっぱりreflectionです。ただしもともとのcがなんらかの構造体Tを要素に持つSliceが実体のany([]T)であるということに注意する必要があります。
c:=CreateRecords()
if v:=reflect.ValueOf(c); len:=v.Len(); len>0 {
for i:=0; ; i++ {
// 例えば100レコードで分割
begin:=i*100
end:=(i+1)*100
if end>=len {
end=len
}
pv=v.Slice(begin,end).Interface()
if r:=db.Create(pv); r.Error!=nil {
log.Fatal("途中で処理とまったでー") // エラーが起きたことだけ分かればヨシと言う雑な例
}
if end==len {
break
}
}
}
reflect.Value
に対してSlice(n,m)
を呼ぶことでsomeSlice[n:m]
と同等のオペレーションが実現出来ます。ここでの返り値はSliceではなくreflect.Valueになるため、最後にInterface()
を呼んでany([]T)へと変換しています。
注意
サンプルなので雑に書いてますが、分割して流す場合は途中で止まる可能性を考慮しましょう。今回の想定では途中で止まっても問題ない(そうなった場合はエラーを通知した上で、データを消して1から再実行しても大丈夫な用途でしか使わない)ことを前提にしています。
途中で止まってしまったら困るような処理の場合は、それ用の対策をちゃんと入れましょう。