GORM bulk insert

f:id:pigggg:20210815130708p:plain  

GORM

  • golang の ORM
  • 直感的に扱えはするので◯ gorm.io
     

    GORM で bulk insert

    これ
    gorm.io

    大量のレコードを効率的に挿入するには、Createメソッドにスライスを渡します。 GORMはすべてのデータを挿入し、主キーの値をバックフィルするための単一のSQL文を生成し、フックメソッドも起動されます。

 
GORMのCreateメソッドは insert に該当するのですが、そこに Slice を入れるだけで勝手にBulkになるのはありがたい。

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
  user.ID // 1,2,3
}

 
CreateInBatches というのもあり。
実行するサイズを指定して insert ができるっぽい。
大量データを扱うときは必要になるかも。ちゃんと用意されててすごい。

var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}

// batch size 100
db.CreateInBatches(users, 100)

Golang for loop

f:id:pigggg:20210814222605p:plain

よく使ってるやつ 

for index, value := range []string{"a", "b", "c"} {
  fmt.Println(index, value)
}

map を回すとき

for key, value := range map[string]string{"a": "a1", "b": "b1", "c": "c1"} {
  fmt.Println(key, value)
}

 
あとは continue, break は他の言語同様存在する
 
私は使う場面に遭遇してないが、goto文もあるらしい

i := int64(0)
loop:
  if i < 10 {
    i++
    goto loop
  }

 
ついでに無限loopをしたいなら

for {
  fmt.Println("hogehoge")
}

こんな感じ。

Golang gzip response → struct

f:id:pigggg:20210814004554p:plain  

gzip圧縮されたresponseをhoge struct にいれるまで

import (
  "net/http"
  "compress/gzip"
  "bytes"
  "encoding/json"
)

func main() { 
  // どこかへGetRequest
  req, err := http.NewRequest(http.MethodGet, "https://hogehoge/foo/bar.jp", nil)
  if err != nil {
    panic(err)
  }
  req.Header.Set("Accept-Encoding", "deflate, gzip")

  client := new(http.Client)
  resp, err = client.Do(req)
  if err != nil {
    panic(err)
  }
  if resp.StatusCode != http.StatusOK {
    panic("なにかしら失敗")
  }

  reader, _ = gzip.NewReader(resp.Body)
  wb := new(bytes.Buffer)
  io.Copy(wb, reader)
 
  var bean *hoge
  err = json.Unmarshal(wb.Bytes(), &bean)
  if err != nil {
    panic(err)
  }

  println(bean)
}

type hoge struct {
  ...
}

 

Accept-Encoding

Accept-Encoding ヘッダの目的は、クライアントがサポートしている圧縮方式をサーバーに教えることです。 サーバーは送られてきた Accept-Encoding ヘッダの値を見て、クライアントに合う圧縮アルゴリズムでコンテンツを圧縮して返却してあげれば良いというわけです。

 
今回は gzip 圧縮してresponseちょうだい ということなので、 Accept-Encoding : deflate, gzip
 
 

elementary OS 6 'Odin' が出たらしい

f:id:pigggg:20210811210729p:plain   
 
おすすめ記事になんか出てきた。
elementary OS を使っているので帰ったら早速DLしよう。
betanews.com  
英語は読めないのでDeepLで翻訳

なぜ、デスクトップパソコンのユーザーは、LinuxベースのOSを使わないのでしょうか?ソフトウェアの互換性はさておき、変化や未知への恐れがあるか> らだ。ユーザーがWindowsから乗り換えるには、かなり単純な作業である必要があります。以前は、Linuxディストリビューションをインストールするだ> けでも大変な作業でした。しかし最近では、ディストリビューションにもよりますが、Windowsをインストールするよりも早くて簡単な場合もあります。  
   
Linux初心者にとって、インストールしたディストリビューションは、直感的なデスクトップ環境で使いやすいものでなければなりません。私はGNOMEの大ファンですが、当然のことながら、すべての人(特にLinux初心者)がGNOMEを好むわけではありません。あるLinuxベースのデスクトップOSは、す> べての人が利用しやすいことに重点を置いています--elementary OSです。このディストリビューションは洗練されており、使いやすさを重視しています。熟練者にも初心者にもお勧めのディストリビューションです。本日、コードネーム「Odin」と呼ばれるelementary OS 6がダウンロード可能になり、エキサイティングな変更が加えられました。 
 
「elementary OS 6は、最先端のサンドボックス技術を活用して、プライバシーとセキュリティの保護を技術的なレベルで実現しています。OS 6では、> すべてのAppCenterアプリがFlatpakとしてパッケージ化され、配布されます。Flatpakは最新のコンテナ形式で、アプリ同士や機密データを隔離することができます。OS 6では、すべてのAppCenterアプリがFlatpakとして配布されます。さらに、elementary OS 6ではポータルを利用して、アプリ同士の相互作用やデータを管理することができます。アプリは、明確に定義された方法で、明示的に許可を求めなければなりません。 
   
さらにBlaede氏は、「Elementary OS 6がFlatpaksに全面的に対応したことで、AppCenterもそれに合わせてアップデートされました。サードパーティ> のAppCenterアプリは、これまでもレビュー、承認、キュレーションを行ってきましたが、プライバシーとセキュリティをさらに強化するために、サンドボックス化されたFlatpakとしても配布されるようになりました。リストからアプリがインストールされると、AppCenterでは完了時にアプリ内通知が表> 示されるようになり、より早くアプリを開くことができるようになりました。アプリのヘッダーのデザインが改善され、ボタンのコントラストも改善され> ました。また、AppCenterの通知では、インストールされたアプリやアップデートの言語やコンテクストバッジが改善され、より豊かなコンテキストが提供されるようになりました。" 
 
elementary OS 6のアップデートについては、こちらをご覧ください。変更点のリストは非常に広範囲にわたっており、開発者がこのOSに非常に情熱を> 持っていることがわかるので、ぜひ一読されることをお勧めします。ISOをダウンロードしたい場合は、ここから入手できます。プロのアドバイス:開発> 者はダウンロードに寄付を求めていますが、0ドルを選択することもできます。 

 
ふむ。セキュリティ面が強化された…的な…?
とりあえず開発者のblog blog.elementary.io   
Twitterでもめちゃ言ってる

Golang で日付操作(UTC→JST)

f:id:pigggg:20210811201006p:plain

UTCJSTに変えたくなった。

やったこと

package main

import (
    "fmt"
    "time"
)

const (
  ISO8601Format = "2006-01-02T15:04:05Z"
)

func main() {
  // UTC
  value := "2021-08-10T13:41:32Z"
  jst, _ := time.LoadLocation("Asia/Tokyo")
  r, _ := time.ParseInLocation(ISO8601Format, value, jst)

  fmt.Println(r)
  // 2021-08-10 13:41:32 +0900 JST
}

locationは変わっているが、+9時間されてない。
本当はこうなってほしい。

2021-08-10 22:41:32 +0900 JST

 

こう変えた

func main() {
  value := "2021-08-10T13:41:32Z"
  jst, _ := time.LoadLocation("Asia/Tokyo")

  r, _ := time.Parse(ISO8601Format, value)
  r2 := r.In(jst)

  fmt.Println(r)  // 2021-08-10 13:41:32 +0000 UTC
  fmt.Println(r2) // 2021-08-10 22:41:32 +0900 JST
}

意図通りにはなった。
が time.In は何をしているのか...
 

time.In は何をしているのか

// In returns a copy of t representing the same time instant, but
// with the copy's location information set to loc for display
// purposes.
//
// In panics if loc is nil.
func (t Time) In(loc *Location) Time {
  if loc == nil {
    panic("time: missing Location in call to Time.In")
  }
  t.setLoc(loc)
  return t
}

 
ほぉ。

Inは、同じ時間の瞬間を表すtのコピーを返しますが、表示のためにコピーの位置情報をlocに設定します。 locがnilの場合、Inはパニックになります。

Copyした time に位置情報を設定しているらしい。
ただ、中身は time.setLoc をしているだけ。
 

time.setLoc は何をしているのか

// setLoc sets the location associated with the time.
func (t *Time) setLoc(loc *Location) {
    if loc == &utcLoc {
        loc = nil
    }
    t.stripMono()
    t.loc = loc
}

 
ロケーションを設定しているだけのよう…

setLocは、時間に関連付けられたロケーションを設定します。  

time.stripMono とは…
 

time.stripMono は何をしているのか

// stripMono strips the monotonic clock reading in t.
func (t *Time) stripMono() {
    if t.wall&hasMonotonic != 0 {
        t.ext = t.sec()
        t.wall &= nsecMask
    }
}

 

stripMonoは、tに読み込まれた単調なクロックをストリップします。  

なるほどね。全然わからん。
 

動かしながらみる

Parseした段階ではこう

r, _ := time.Parse(ISO8601Format, value)

f:id:pigggg:20210811202908p:plain Parseした際のデフォルトTimezoneはUTCなのでこの段階では +0000
 
setLoc にきた段階ではこう

func (t *Time) setLoc(loc *Location) {

f:id:pigggg:20210811203102p:plain 変わらない。
 
stripMono に来た段階でも

if t.wall&hasMonotonic != 0

この分岐に入らず終わったので変わらなかった
 
時間が変わったのは setLoc のここ

t.loc = loc

f:id:pigggg:20210811203338p:plain Copy した(?) time に location を追加したところで +0900 された
 
う〜ん・・・
とりあえず time の loc に jst 入れると変わる。
 

time.loc

// loc specifies the Location that should be used to
// determine the minute, hour, month, day, and year
// that correspond to this Time.
// The nil location means UTC.
// All UTC times are represented with loc==nil, never loc==&utcLoc.
loc *Location

locは、このTimeに対応する分、時間、月、日、年を決定するために使用すべきLocationを指定します。nilのLocationはUTCを意味します。全てのUTCタイムはloc==nilで表され、決してloc==&utcLocではありません。

へぇ。location == nilUTC と…
 
そもそも time.ParseInLocation で jst 設定したときはなぜ変わらなかった??

  jst, _ := time.LoadLocation("Asia/Tokyo")
  r, _ := time.ParseInLocation(ISO8601Format, value, jst)

 

time.ParseInLocation は何をしているのか

// ParseInLocation is like Parse but differs in two important ways.
// First, in the absence of time zone information, Parse interprets a time as UTC;
// ParseInLocation interprets the time as in the given location.
// Second, when given a zone offset or abbreviation, Parse tries to match it
// against the Local location; ParseInLocation uses the given location.
func ParseInLocation(layout, value string, loc *Location) (Time, error) {
    return parse(layout, value, loc, loc)
}

実質 time.parse している  
 

time.parse は何をしているのか

pase 関数は長すぎて載せないが、今回の例だとここ

return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil

defaultLocation は

  jst, _ := time.LoadLocation("Asia/Tokyo")

なので、jst がセットされている。なのになぜ変わらないのだろう。
f:id:pigggg:20210811205327p:plain
Date() のなかでも loc は入っている。
 
そして Date() のなかで setLoc() もしている

func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time {
  if loc == nil {
    panic("time: missing Location in call to Date")
  }
  ...
  t := unixTime(unix, int32(nsec))
  t.setLoc(loc)
  return t
}

   
腑に落ちない…  

golang nil map で panic

f:id:pigggg:20210810213822p:plain

よくやらかすやつ

var testMap map[int64]string

testMap[100] = "あいうえお"
testMap[101] = "かきくけこ"

// panic: assignment to entry in nil map

なぜ?

初期化(メモリの確保)をしていないから
言われれば当然…

解決策

マップリテラルで初期化

2行になるから自分はやらない。

var testMap map[int64]string
testMap = map[int64]string{}

testMap[100] = "あいうえお"
testMap[101] = "かきくけこ"

fmt.Println(testMap)
// map[100:あいうえお 101:かきくけこ]

makeで初期化

こっちの方が馴染みある

testMap := make(map[int64]string)
// testMap = map[int64]string{}

testMap[100] = "あいうえお"
testMap[101] = "かきくけこ"

fmt.Println(testMap)
// map[100:あいうえお 101:かきくけこ]

make の方は第2引数にキャパシティを指定できる。指定しない場合は適当なキャパシティが自動で確保される。
これはマップリテラルでも同様らしい。

makeの説明にはこう書いている

// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
//  Slice: The size specifies the length. The capacity of the slice is
//  equal to its length. A second integer argument may be provided to
//  specify a different capacity; it must be no smaller than the
//  length. For example, make([]int, 0, 10) allocates an underlying array
//  of size 10 and returns a slice of length 0 and capacity 10 that is
//  backed by this underlying array.
//  Map: An empty map is allocated with enough space to hold the
//  specified number of elements. The size may be omitted, in which case
//  a small starting size is allocated.
//  Channel: The channel's buffer is initialized with the specified
//  buffer capacity. If zero, or the size is omitted, the channel is
//  unbuffered.
func make(t Type, size ...IntegerType) Type
...

英語読めないのでDeepL変換。

make組み込み関数は、slice、map、chan(のみ)のいずれかの型のオブジェクトを割り当て、初期化します。newと同様に、第1引数は値ではなく型です。newとは異なり、makeの戻り値の型は引数の型と同じで、そのポインタではありません。

The make built-in function allocates and initializes an object of type slice, map, or chan (only). Like new, the first argument is a type, not a value. Unlike new, make's return type is the same as the type of its argument, not a pointer to it.

マップ: 空のマップは、指定された数の要素を格納するのに十分なスペースが割り当てられます。サイズを省略することもでき、その場合は小さなスタートサイズが割り当てられます。

Map: An empty map is allocated with enough space to hold the specified number of elements. The size may be omitted, in which case a small starting size is allocated.

"適当なキャパシティ"というか最小のキャパシティが割り当てられるっぽい。

golang 小ネタ

f:id:pigggg:20210805000638p:plain

goでの前方一致と後方一致

imports "strings"

prefix := "hoge"
suffix := "bar"
str := "hoge foo bar"

strings.HasPrefix(str, prefix) // true
strings.HasSuffix(str, suffix) // true
  

strings の中身

func HasPrefix(s, prefix string) bool {
  return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
}

func HasSuffix(s, suffix string) bool {
  return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}

HasPrefix

対象文字列の長さとprefixの長さを比較して、対象文字列のほうが長かった
かつ
対象文字列の0番目からprefixの長さ(バイト長)分までを取って、引数のprefixと比較

HasSuffix

対象文字列の長さとsuffixの長さを比較して、対象文字列のほうが長かった
かつ
対象文字列長からsuffix長を引いた添字から対象文字列の最後までの切り出し、suffixと比較