- 投稿日:2019-09-27T22:00:55+09:00
golang-migrate/migrateパッケージを使ってみる
Go言語勉強継続中。
SQLite3を埋め込みで使うことを想定し、バージョンアップなどに伴うマイグレーションを外部ファイルなどに依存せずに行えるパッケージを探していた。
golang-migrate/migrateの評判がよさそうだったのだが、外部に依存しないデータソースとしてはgo-bindata(既にメンテナンスがなされてないようであまり使われていないらしい)しかなかったので、string配列をデータソースとするsource.Driverを書いてみることにした。package main import ( "database/sql" "errors" "io" "io/ioutil" "log" "os" "strings" "github.com/golang-migrate/migrate" "github.com/golang-migrate/migrate/database" "github.com/golang-migrate/migrate/database/sqlite3" "github.com/golang-migrate/migrate/source" _ "github.com/mattn/go-sqlite3" ) var sqls = []string{ `CREATE TABLE T1 (F1, F2)`, `ALTER TABLE T1 ADD F3`, `ALTER TABLE T1 ADD F4`, `ALTER TABLE T1 ADD F5`, } func main() { var err error var db *sql.DB db, err = sql.Open("sqlite3", "./test.sqlite") if err != nil { log.Println(err) } defer db.Close() var dbdriver database.Driver dbdriver, err = sqlite3.WithInstance(db, &sqlite3.Config{MigrationsTable: "", DatabaseName: "test.sqlite"}) if err != nil { log.Println(err) } var srcdriver source.Driver srcdriver, err = WithInstance(sqls) if err != nil { log.Println(err) } var m *migrate.Migrate m, err = migrate.NewWithInstance("strings", srcdriver, "sqlite3", dbdriver) if err != nil { log.Println(err) } err = m.Migrate(uint(len(sqls) - 1)) if err != nil && err != migrate.ErrNoChange { log.Println(err) } ... } var notImplementedError = errors.New("not implemented") type StringDriver []string func WithInstance(sqls []string) (source.Driver, error) { ss := StringDriver(sqls) return &ss, nil } func (a *StringDriver) Open(url string) (source.Driver, error) { return nil, notImplementedError } func (a *StringDriver) Close() error { return nil } func (a *StringDriver) First() (version uint, err error) { if len(*a) > 0 { return 0, nil } return 0, os.ErrNotExist } func (a *StringDriver) Prev(version uint) (prevVersion uint, err error) { return 0, notImplementedError } func (a *StringDriver) Next(version uint) (nextVersion uint, err error) { if version+1 < uint(len(*a)) { return version + 1, nil } return 0, os.ErrNotExist } func (a *StringDriver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { return ioutil.NopCloser(strings.NewReader((*a)[version])), "", nil } func (a *StringDriver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { return nil, "", notImplementedError }埋め込み用途で使うことしか考えていないし、戻すことも考えていないので
Prev
などは未実装です。
あとは機能を増やす時にsqls
に足していけばいけます。
- 投稿日:2019-09-27T18:25:57+09:00
Go の -race option は内部で何をしているのか。何を検知しないのか。
-race
をつけてCIを通しているのにAPIがデータ競合で落ちてしまいました。調べていたら-race
がそもそも何をしているかに行き着いたので簡単に共有します。-race とは
-race
はコンパイラフラッグの一種で競合を検知するのに便利です。例えば下記のコードはmapへの読み書きが同時に起こってパニックするコードです。これをgo run main.go -race
のように実行するとwarningを出してくれます。package main import ( "fmt" "strconv" "time" ) func main() { m := make(map[string]int) go func() { for i := 0; i < 1000; i++ { m[strconv.Itoa(i)] = i // write } }() go func() { for i := 0; i < 1000; i++ { fmt.Println(i, m[strconv.Itoa(i)]) // read } }() time.Sleep(time.Second * 5) }下記のように実行するとWarningを出します。
$ go run -race main.go-race はそもそも何をしているのか
内部では
C/C++
用の競合検出ライブラリが使われています。簡単に言うと、競合を実行時に検出するコードを出力することができるライブラリです。
https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManualもちろん
Go
のコンパイラはこれをサポートしており、実際にruntime/race
のREADME.md
にはThreadSanitizer
が使われていることが明記されています。https://github.com/golang/go/tree/master/src/runtime/race
untime/race package contains the data race detector runtime library. It is based on ThreadSanitizer race detector, that is currently a part of the LLVM project (http://llvm.org/git/compiler-rt.git).
コンパイル時に実際にどのようなコードが仕込まれているかを見てみましょう。例えば下記のコードをビルドした場合をみてみます。
package main func Inc(x *int) { *x++ }普通にビルドすると下記のようにコンパイルされます。
... pcdata $2, $1 pcdata $0, $1 movq "".x+8(SP), AX pcdata $2, $0 incq (AX) ...
-race
をつけると下記のようにコンパイルされます。... pcdata $2, $1 movq "".x+32(SP), AX testb AL, (AX) pcdata $2, $0 movq AX, (SP) call runtime.raceread(SB) pcdata $2, $1 movq "".x+32(SP), AX movq (AX), CX movq CX, ""..autotmp_4+8(SP) pcdata $2, $0 movq AX, (SP) call runtime.racewrite(SB) movq ""..autotmp_4+8(SP), AX incq AX pcdata $2, $2 pcdata $0, $1 movq "".x+32(SP), CX ...コンパイラーが
call runtime.raceread(SB)
のように同時に到達可能な各メモリー位置に読み取りおよび書き込みを検知する命令を追加しています。ご覧の通り、-race
をつけると命令が増えてパフォーマンスが落ちるのでビルドしたバイナリを本番に乗っけるのはやめましょう。go build -race でプログラムが遅くなってた話ちなみにGoコンパイラが吐き出すアセンブリは
Compiler Explorer
というサービスで簡単に確認できて便利です。-raceを使ってるのに競合が起きる理由
-raceでは競合を検知するための命令を追加しているので、そこに到達しないと競合を検知しません。つまりテスト時に-raceをつけてPASSしたからと言って競合が発生しないとは言えません。
Goのドキュメントにも明示されていました。
https://golang.org/doc/articles/race_detector.html#How_To_UseTo start, run your tests using the race detector (go test -race). The race detector only finds races that happen at runtime, so it can't find races in code paths that are not executed. If your tests have incomplete coverage, you may find more races by running a binary built with -race under a realistic workload.
テストカバレッジが低い場合はテストだけで全ての競合が消せたと判断せずに、実際に
-race
をつけてビルドしてみて動かしてみることが大切です。Referece
Golang race detection
https://krakensystems.co/blog/2019/golang-race-detection
- 投稿日:2019-09-27T18:14:06+09:00
Golang - ディレクトリ構成と独自パッケージのimportについて
追記:GoModules(バージョン管理)を使用した場合の方法を整理しましたのでGoのバージョンが1.11以上の方はそちらを参照してください
Golang - Go Modulesで開発環境の用意するはじめに
環境作成の際に、深く考えず環境変数
$GOPATH
に~/go
を設定していましたが、作業していると自身で定義したパッケージの参照でエラーが発生し、保存時にフォーマットかけるようにしてるためimportが消えたりと思ったような動作をしなかったため構成を見直すことにしました。GOPATH
パッケージをimportするときの解決先や、
go get
やgo install
をしたときのパッケージやバイナリのインストール先がGOPATHで指定したディレクトリになります。GOPATH配下は以下の三つのディレクトリで構成されます。ちなみにGOPATHが未指定の場合は
$HOME/go
になります。./GOPATH ├── ./bin // 実行ファイルが格納されるディレクトリ(go installコマンドで自動生成されます) ├── ./pkg // ビルドしたパッケージオブジェクトが格納されるディレクトリ(go buildをはじめとする各種コマンドで自動生成されます) └── ./src // パッケージごとのソースコードを配置するディレクトリディレクトリ構成
src配下にプロジェクトのディレクトリを配置することろまでは間違いなさそうですが、どうやら
src/github.com/username/project
で作成するのが良さそうです。
ずっと悩んでいたのですが以下の記事のおかげで解決できました!!githubに公開されている外部パッケージを取得する際に
go get github.com/XXXXXX
コマンドで取得できますが
そうするとディレクトリの構成が上記のsrc/github.com/username/repository
になるようですね!ということでできた構成が以下の通りです
プロジェクトのディレクトリ配下は Standard Go Project Layout を参考にしました。└── GolangProjects // GOPATHに設定 ├── bin ├── pkg └── src └── github.com └── github-username // githubのユーザー └── project-sample // プロジェクトごとのディレクトリ ├── Makefile ├── api ├── assets ├── build ├── cmd ├── configs ├── deployments ├── docs ├── examples ├── githooks ├── init ├── internal ├── pkg ├── scripts ├── test ├── third_party ├── tools ├── vendor ├── web └── website
import
上記の構成にした場合、独自パッケージのimportは以下の通りです
import "github.com/github-username/project-sample/パッケージ名"いくつか参考にgithubで公開されているフレームワークを見てみましたが上記の通りになっていました
Docker環境の場合
私は以下の環境で開発を行おうかと思っているのですが、そこでもう一点ハマりました。
Golang - DockerとVSCodeで開発環境を用意する
上記構成のproject-sampleで
Remote-Containers: Add Development Container Configuration Files
してしまうと以下の理由から独自パッケージのimportでエラーが発生しました
- 仮想環境上のワークスペースがproject-sample以下になってしまう (github.com/user-name/が参照できない)
リポジトリに反映して
go get
すれば、GOPATHにパッケージがインストールされるのでエラーは解消しますが、それはかなりめんどくさそうと思ったので以下の通りに対応することでとりあえずエラーは回避できました
GolangProjectsディレクトリで
Remote-Containers: Add Development Container Configuration Files
Docker上のGOPATHにローカルのGOPATHをマウント(workspaceFolderも合わせて変更しています)
devcontainer.json"workspaceFolder": "/go", "workspaceMount": "src=ローカルの$GOPATH,dst=/go,type=bind",今回かなり手探りで調査したので誤っている点や、もっとこういう構成の方がいいよー!って構成があればぜひ教えていただきたいです!!
- 投稿日:2019-09-27T15:41:25+09:00
Go deferとpanicと俺
package main import ( "fmt" ) func main() { defer fmt.Println("①最初に書いたけど、これが最後") // 関数も指定できるよ defer deferFunc() // これは無名関数だよ defer func() { fmt.Println("③deferって複数指定するとスタックなんだね") }() // panicから復旧してみるよ defer func() { e := recover() fmt.Println("④recover関数で復旧するよ:" , e) }() panic("ぱにっぱにっ。 ぱにっぱにっ。 ぱにっぱにっぱにっく。") } func deferFunc() { defer fmt.Println("②2番目に書いたけど、これが①より先") }
- 投稿日:2019-09-27T11:25:03+09:00
オブジェクト関係マッピング(gormを使ってみた話)
入社1年目の渡邉です。
最近学んだオブジェクト関係マッピング(ORM:Object-relational mapping)が、
すげぇ分かりやすいって時代遅れながら書かせて頂きます。いつの時代も新しいことを考える人は凄いなと思います(笑)
最近はGo言語をよく書いてるので、Go言語でORを触れるgormを使います。
SQL文を意識した場合
DDL.sqlCREATE TABLE users ( age INTEGER, name TEXT );db.goimport ( "database/sql" "go/build" "io/ioutil" "strings" ) func CreateTable(db *sql.DB) { execFile(db, "./DDL.sql") } func execFile(db *sql.DB, path string) { statements, _ := ioutil.ReadFile(build.Default.GOPATH + path) for _, statement := range strings.Split(string(statements), ";\n") { _, err := db.Exec(statement) if err != nil { panic(err.Error()) } } }Tableを新規に作成したいところで CreateTable() を実行するようにします。
※DDL: https://wa3.i-3-i.info/word15639.html
ORMの場合
db.goimport ( "time" "github.com/jinzhu/gorm" ) type Users struct { age int name string } func CreateTable(db *gorm.DB) { db.CreateTable(&Users{}) }Tableを新規に作成したいところで CreateTable() を実行するようにします。
個人的には直感かつ短いコードで書けて分かりやすい。感想
gorm、めっちゃ分かりやすいなと使い方を知ってから思います。
・ORMを使うのか?
・SQL文を扱うのか?メリット・デメリットに関してはまだまだ詰めきれてませんが、使っていって学びを深めようと思います。
関連記事
【GORM】Go言語でORM触ってみた(2019/09/05更新)
https://qiita.com/chan-p/items/cf3e007b82cc7fce2d81諸説あり
O/Rマッピングは百害あって一利なし!(2018年11月17日に更新)
https://qiita.com/gomiryo/items/6d448c500749f91242d2
- 投稿日:2019-09-27T11:25:03+09:00
オブジェクト関係マッピング(gormを使ってみた)
入社1年目の渡邉です。
最近学んだオブジェクト関係マッピング(ORM:Object-relational mapping)が、
すげぇ分かりやすいって時代遅れながら書かせて頂きます。いつの時代も新しいことを考える人は凄いなと思います(笑)
最近はGo言語をよく書いてるので、Go言語でORを触れるgormを使います。
SQL文を意識した場合
DDL.sqlCREATE TABLE users ( age INTEGER, name TEXT );db.goimport ( "database/sql" "go/build" "io/ioutil" "strings" ) func CreateTable(db *sql.DB) { execFile(db, "./DDL.sql") } func execFile(db *sql.DB, path string) { statements, _ := ioutil.ReadFile(build.Default.GOPATH + path) for _, statement := range strings.Split(string(statements), ";\n") { _, err := db.Exec(statement) if err != nil { panic(err.Error()) } } }Tableを新規に作成したいところで CreateTable() を実行するようにします。
※DDL: https://wa3.i-3-i.info/word15639.html
ORMの場合
gorm を入れておきましょう
$ go get github.com/jinzhu/gormdbファイルの中身
db.goimport ( "time" "github.com/jinzhu/gorm" ) type Users struct { age int name string } func CreateTable(db *gorm.DB) { db.CreateTable(&Users{}) }Tableを新規に作成したいところで CreateTable() を実行するようにします。
個人的には直感かつ短いコードで書けて分かりやすい。感想
gorm、めっちゃ分かりやすいなと使い方を知ってから思います。
・ORMを使うのか?
・SQL文を扱うのか?メリット・デメリットに関してはまだまだ詰めきれてませんが、使っていって学びを深めようと思います。
関連記事
【GORM】Go言語でORM触ってみた(2019/09/05更新)
https://qiita.com/chan-p/items/cf3e007b82cc7fce2d81GORMというORMが超絶便利だった件
https://medium.com/@taka.abc.hiko/gorm%E3%81%A8%E3%81%84%E3%81%86orm%E3%81%8C%E8%B6%85%E7%B5%B6%E4%BE%BF%E5%88%A9%E3%81%A0%E3%81%A3%E3%81%9F%E4%BB%B6-8d279489c38f諸説あり
O/Rマッピングは百害あって一利なし!(2018年11月17日に更新)
https://qiita.com/gomiryo/items/6d448c500749f91242d2
- 投稿日:2019-09-27T09:19:14+09:00
即時関数 使用例 (Go言語で)
はじめに
入門書で言語の基礎を学んでいる時に即時関数ってどんな時に使うのかピンと来なかったのですが、インターンで使う機会があったので残しておこうと思います。
実際はJavaScriptを書いている時に使ったのですが、慣れる為にGo言語で書き換えました。変数の初期値を引数によって変える
変数を作って、
if,switch
等で分岐して作られている変数に代入は面倒だなぁ~って時に使いました。main.gopackage main import "fmt" func main() { country := "日本" greeting := (func(country string) string { switch country { case "日本": return "こんにちは" case "アメリカ": return "Hello" default: return "xxxxx" } })(country) fmt.Println(country) // 日本 fmt.Println(greeting) // こんにちは }おわりに
変数の初期値を引数によって変えようって時の書き方ってこれ良いのでしょうか?即時関数を使っていい場面なのか?
初心者すぎる疑問ばかりです。もっとコードを書いていき最適を考えられるようになりたいです。
- 投稿日:2019-09-27T02:50:40+09:00
【Go, golang】Microsoft ExcelでもNumbersでもcsvファイルを文字化けしないように作成する。
前置き
Goでcsvを扱う記事はQiitaでも色々ありますが、半分自分のメモ代わりということで。
Microsoft Excelでも、Numbersでもcsvファイルを開いた時、文字化けしないファイルを作って出力するようにします。
サンプルは、比較的見たこともあるものかと思います。環境
go version: 1.11.4
os: MacOSサンプルコード
package main import ( "encoding/csv" "os" "log" "bufio" ) func main() { // ファイルがあれば開く、なければ新しく作成するようにしています。 file, err := os.OpenFile("new_company.csv", os.O_RDWR|os.O_CREATE, 0755) if err != nil { log.Fatal(err) } // Excelでも見れるようにするには、BOM付きUTF8にする必要があるので、fileの先頭にBOMを付与。 bw := bufio.NewWriter(file) bw.Write([]byte{0xEF, 0xBB, 0xBF}) // csvデータ records := [][]string{ {"id", "name", "address"}, {"1", "Softbank", "日本のどこか"}, {"2", "au", "多分日本のどこか"}, {"3", "Docomo", "おそらく日本のどこか"}, } w := csv.NewWriter(bw) // w.Writeで1行づつやってもいいですが、一括でデータを取り込みできるので、今回はWriteAllにしました。 // 必要に応じて使ってください。なお、WriteAllの場合、w.Flush()をしなくて良いのでわずかですがコード短縮になる(...?) w.WriteAll(records) if err := w.Error(); err != nil { log.Fatal(err) } // 最後にfileを閉じます。 if err := file.Close(); err != nil { log.Fatal(err) } }個人的ポイント
bufioを使ってBOMの付与
https://qiita.com/bbq-all-stars/items/03fa27eeb6f46b580525
や
https://pinzolo.github.io/2017/03/29/utf8-csv-with-bom-on-golang.html
を参考にしつつ試してみました。やはりcsvデータとなるデータの前にBOMを付与するところでしょうか。
どんな感じになったか
上記サンプルコードで、
go run main.go
してファイルを生成した後、
lessコマンドで中身をみてみると、<U+FEFF>id,name,address 1,Softbank,日本のどこか 2,au,多分日本のどこか 3,Docomo,おそらく日本のどこか new_company.csv (END)のように、先頭にBOMが付いているのがわかります(catコマンドだと付いているかわからない)。
Cot Editorでファイルを開いてみると...
エンコーティングのところが「Unicode(UTF-8)B...」(これ、BOM付きってことです。見難いですが...)となっていて、BOM付きなことがわかります。MacのMicrosoft Excelで開いてみると、最初にテキストファイルウィザードのウィンドウは表示されますが、
区切り文字の取り扱いとかを選んで「完了」を押すと、
こんな感じでみることができます。...よかった。
- 投稿日:2019-09-27T02:06:11+09:00
【川原のGo言語&Elixir奮闘記】#3 今日はGoで並行機能遊び
Hello world私だよ。
前回でHello worldまで行けたよ。
今日はGoの並行機能で遊んでいくよ。
以下のプログラムを作るよ。
package main import ( "fmt" "time" ) func readword(ch chan string) { fmt.Println("なんか入力して~") var word string fmt.Scanf("%s", &word) ch <- word } func timeout(t chan bool) { time.Sleep(10 * time.Second) t <- true } func main() { t := make(chan bool) go timeout(t) ch := make(chan string) go readword(ch) select { case word := <-ch: fmt.Println("承り~", word) case <-t: fmt.Println("はい、時間切れ~") } }ここで重要なのは、"go"ルーチンと"select”っていう文だよ。
両方の"go"ルーチンからの戻り値を"select”で受け付けているよ。
これを使うことによってプログラム上で非同期を表しているよ。
今日はGoの並行機能で遊んだよ。
最近、左目の上裏がちょっと痛いよ。
また来週。
- 投稿日:2019-09-27T00:31:36+09:00
go で float32 を JSON にするとちょっと意外なことになる
まあタイトルのとおり。
ちょっと意外なことがあったので。go の場合
まずはソースコード:
package main import ( "encoding/json" "fmt" ) type Hoge struct { Foo float32 Bar float64 } func main() { hoge := Hoge{Foo: 0xa0000000, Bar: 0xa0000000} j, _ := json.Marshal(hoge) fmt.Println(string(j)) }実行すると、出力は以下のようになる:
{"Foo":2684354600,"Bar":2684354560}ぱっとみわかりにくいけれど、 float32 に由来する Foo は下二桁が
00
なのに対し、float64 に由来する Bar は、下二桁が60
になっている。同じ
0xa0000000
を入れているのに違う値になる。困る。C# の場合
こちらもソースコードから。
using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; [DataContract] public class Hoge { [DataMember] public float foo { get; set; } [DataMember] public double bar { get; set; } } internal class JsonSerializer { public static DataContractJsonSerializer Serializer<t>() { System.Type type = typeof(t); return new DataContractJsonSerializer(type); } } partial class Program { static void Main(string[] args) { var t = new Hoge(); t.foo = 0xa0000000L; t.bar = 0xa0000000L; using (var fs = System.Console.OpenStandardOutput()) { JsonSerializer.Serializer<Hoge>().WriteObject(fs, t); } } }結果は次の通り:
{"bar":2684354560,"foo":2.68435456E+09}出力する書式は異なるものの、実質的に同じ値になっている。
これなら困らない。Java + Gson の場合
やっぱりソースコードから。
import com.google.gson.Gson; class Hoge { public float foo; public double bar; } public class Main { public static void main(String[] args) { Hoge hoge = new Hoge(); hoge.foo = 0xa0000000L; hoge.bar = 0xa0000000L; Gson gson = new Gson(); System.out.println(gson.toJson(hoge)); } }Java には標準となる JSON Serializer がない模様なので Gson を使ってみた。
出力結果は:
{"foo":2.68435456E9,"bar":2.68435456E9}出力形式も値も同じ。
困らない。node.js の場合
ここでもまずはソースコード:
o = { foo: new Float32Array([0xa0000000]), bar: new Float64Array([0xa0000000]) } process.stdout.write(JSON.stringify(o));出力はこうなる:
{"foo":{"0":2684354560},"bar":{"0":2684354560}}Float32Array とかって配列にならないんだへー、みたいな気持ちにはなるが、Float32Array でも Float64Array でもおなじになっている。
まとめ
32bit および 64bit の浮動小数点型変数に
0xa0000000
を入れて、それを JSON にしてみた。
結果は下表の通り。
言語など 型 JSON内の表現 JSON内の表現を16進数で表現したもの go float32 2684354600
0xa0000028
go float64 2684354560
0xa0000000
C# float 2.68435456E+09
0xa0000000
C# double 2684354560
0xa0000000
Java+Gson float 2.68435456E9
0xa0000000
Java+Gson double 2.68435456E9
0xa0000000
node.js Float32Array 2684354560
0xa0000000
node.js Float64Array 2684354560
0xa0000000
go で float32 型の値を JSON にした場合だけ、
0xa0000000
にならない。
困る。対策は「float32 を使わない」なんだけど、float32 をわざわざ使っているんだからなんか理由があるに違いなくて、簡単にはやめられないよね。
どうしたものか。追記
というわけで、ライブラリ書いた。