diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile index 2cdec15..cbfb068 100644 --- a/.gitpod.Dockerfile +++ b/.gitpod.Dockerfile @@ -1,24 +1,25 @@ FROM gitpod/workspace-base:latest -ENV GO_VERSION=1.22.0 +USER gitpod +ENV GO_VERSION=1.24.0 # For ref, see: https://github.com/gitpod-io/workspace-images/blob/61df77aad71689504112e1087bb7e26d45a43d10/chunks/lang-go/Dockerfile#L10 ENV GOPATH=$HOME/go-packages ENV GOROOT=$HOME/go ENV PATH=$GOROOT/bin:$GOPATH/bin:$PATH -RUN curl -fsSL https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz | tar xzs && \ - go install -v github.com/uudashr/gopkgs/cmd/gopkgs@v2 && \ - go install -v github.com/ramya-rao-a/go-outline@latest && \ - go install -v github.com/cweill/gotests/gotests@latest && \ - go install -v github.com/fatih/gomodifytags@latest && \ - go install -v github.com/josharian/impl@latest && \ - go install -v github.com/haya14busa/goplay/cmd/goplay@latest && \ - go install -v github.com/go-delve/delve/cmd/dlv@latest && \ - go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest && \ - go install -v honnef.co/go/tools/cmd/staticcheck@latest && \ - go install -v golang.org/x/tools/gopls@latest && \ - printf '%s\n' 'export GOPATH=/workspace/go' \ +RUN curl -fsSL https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz | tar xzs +RUN go install github.com/uudashr/gopkgs/cmd/gopkgs@v2 +RUN go install github.com/ramya-rao-a/go-outline@latest +RUN go install github.com/cweill/gotests/gotests@latest +RUN go install github.com/fatih/gomodifytags@latest +RUN go install github.com/josharian/impl@latest +RUN go install github.com/haya14busa/goplay/cmd/goplay@latest +RUN go install github.com/go-delve/delve/cmd/dlv@latest +RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +RUN go install honnef.co/go/tools/cmd/staticcheck@latest +RUN go install golang.org/x/tools/gopls@latest +RUN printf '%s\n' 'export GOPATH=/workspace/go' \ 'export PATH=$GOPATH/bin:$PATH' > $HOME/.bashrc.d/300-go -RUN sudo apt update && sudo apt install -y universal-ctags tree nkf sqlite3 \ No newline at end of file +RUN sudo apt update && sudo apt install -y universal-ctags tree nkf wamerican miller diff --git a/.gitpod.yml b/.gitpod.yml index b4f5f14..15f33f2 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -17,3 +17,4 @@ tasks: vscode: extensions: - golang.go + - TakumiI.markdowntable diff --git a/10.ColumnTypes/Taskfile.yml b/10.ColumnTypes/Taskfile.yml new file mode 100644 index 0000000..234d788 --- /dev/null +++ b/10.ColumnTypes/Taskfile.yml @@ -0,0 +1,13 @@ +# https://taskfile.dev + +version: "3" + +vars: + DBFILE: chinook.db + +tasks: + default: + cmds: + - cp -f ../{{.DBFILE}} . + - echo "PRAGMA table_info(tracks)" | sqlite3 -header -table ./{{.DBFILE}} + - go run main.go diff --git a/10.ColumnTypes/main.go b/10.ColumnTypes/main.go new file mode 100644 index 0000000..94f1dd6 --- /dev/null +++ b/10.ColumnTypes/main.go @@ -0,0 +1,96 @@ +package main + +import ( + "database/sql" + "log" + + _ "github.com/mattn/go-sqlite3" +) + +func init() { + log.SetFlags(0) +} + +// 10.ColumnTypes +// +// *sql.DB.Query() などで *sql.Rows を取得した際に +// カラム名を知りたい場合は *sql.Rows.Columns() を利用するが +// *sql.Rows.Columns() は、カラム名だけしか取得できない。 +// +// カラムに対する補足情報を取得するには *sql.Rows.ColumnTypes() を利用する。 +// +// https://pkg.go.dev/database/sql@go1.22.0#Rows.ColumnTypes +// +// 結果は []*sql.ColumnType で返ってくる。 +// SELECTで指定した並びで設定されている。 +// +// 上記ドキュメントには以下の記載がある。 +// +// >ColumnTypes returns column information such as column type, length, and nullable. +// Some information may not be available from some drivers. +// +// >ColumnTypesは、カラム・タイプ、長さ、Nullableなどのカラム情報を返す。 +// ドライバによっては利用できない情報もあります。 +func main() { + if err := run(); err != nil { + log.Panic(err) + } +} + +func run() error { + var ( + db *sql.DB + err error + ) + + db, err = sql.Open("sqlite3", "./chinook.db") + if err != nil { + return err + } + defer db.Close() + + var ( + rows *sql.Rows + ) + + rows, err = db.Query("SELECT * FROM tracks LIMIT 1") + if err != nil { + return err + } + defer rows.Close() + + var ( + cols []*sql.ColumnType + toI = func(v int64, ok bool) int64 { + if !ok { + return 0 + } + + return v + } + toB = func(v bool, ok bool) bool { + if !ok { + return false + } + + return v + } + ) + + cols, err = rows.ColumnTypes() + if err != nil { + return err + } + + for _, c := range cols { + log.Printf( + "NAME=%-15s\tTYPE=%-20s\tLENGTH=%-10d\tNOTNULL=%-10v\tSCAN TYPE=%v", + c.Name(), + c.DatabaseTypeName(), + toI(c.Length()), + toB(c.Nullable()), + c.ScanType()) + } + + return nil +} diff --git a/11.NextResultSet/Taskfile.yml b/11.NextResultSet/Taskfile.yml new file mode 100644 index 0000000..0dfce79 --- /dev/null +++ b/11.NextResultSet/Taskfile.yml @@ -0,0 +1,12 @@ +# https://taskfile.dev + +version: "3" + +vars: + DBFILE: chinook.db + +tasks: + default: + cmds: + - cp -f ../{{.DBFILE}} . + - go run main.go diff --git a/11.NextResultSet/main.go b/11.NextResultSet/main.go new file mode 100644 index 0000000..5fda1dc --- /dev/null +++ b/11.NextResultSet/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "strings" + + "github.com/devlights/sqlmap" + "github.com/k0kubun/pp/v3" + + _ "github.com/mattn/go-sqlite3" +) + +func init() { + log.SetFlags(0) +} + +// 11.NextResultSet +// +// データベースドライバ側の実装が行われている場合は +// 一度に複数の結果セットを取得することができる。 +// その場合、*sql.Rows.NextResultSet() を呼び出し +// 次のカーソルに進める。 +// +// - https://pkg.go.dev/database/sql@go1.22.0#example-DB.Query-MultipleResultSets +// +// 本リポジトリで利用している github.com/mattn/go-sqlite3 は、複数の結果セットに +// 対応していないことが作者さんのブログ記事にて記載されている。 +// +// - https://mattn.kaoriya.net/software/lang/go/20161106232834.htm +// +// 以下、作者さんのブログ記事より引用。 +// +// > go-sqlite3 もサポートする予定でしたが、sqlite3 の複数クエリ実行は golang の複数結果セットが期待する物と異なる為、現状は実装を見送りました。 +// +// そのため、以下は実装のみを記載しておく。 +// +// 恐らく、PostgreSQLとMySQLのドライバは対応しているので可能なはず。 +// (MySQLは、multiStatements=trueの指定が必要) +func main() { + if err := run(); err != nil { + log.Panic(err) + } +} + +func run() error { + var ( + db *sql.DB + err error + ) + + db, err = sql.Open("sqlite3", "./chinook.db") + if err != nil { + return err + } + defer db.Close() + + var ( + query string + sb strings.Builder + ) + + sb.WriteString("SELECT ArtistId,Name FROM artists LIMIT 2;") + sb.WriteString("SELECT TrackId,Name FROM tracks LIMIT 2;") + query = sb.String() + + var ( + rows *sql.Rows + ) + + rows, err = db.Query(query) + if err != nil { + return err + } + defer rows.Close() + + var ( + m []map[string]any + ) + + // + // First Result + // + m, err = sqlmap.MapRows(rows) + if err != nil { + return err + } + pp.Println(m) + + // + // 次の結果セットへ + // + if !rows.NextResultSet() { + return fmt.Errorf("rows.NextResultSet() returns false") + } + + // + // Second Result + // + m, err = sqlmap.MapRows(rows) + if err != nil { + return err + } + pp.Println(m) + + return nil +} diff --git a/12.RowsScanDynamic/Taskfile.yml b/12.RowsScanDynamic/Taskfile.yml new file mode 100644 index 0000000..0dfce79 --- /dev/null +++ b/12.RowsScanDynamic/Taskfile.yml @@ -0,0 +1,12 @@ +# https://taskfile.dev + +version: "3" + +vars: + DBFILE: chinook.db + +tasks: + default: + cmds: + - cp -f ../{{.DBFILE}} . + - go run main.go diff --git a/12.RowsScanDynamic/main.go b/12.RowsScanDynamic/main.go new file mode 100644 index 0000000..852fe16 --- /dev/null +++ b/12.RowsScanDynamic/main.go @@ -0,0 +1,162 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + + _ "github.com/mattn/go-sqlite3" +) + +const ( + driver = "sqlite3" + datasource = "./chinook.db" +) + +var ( + db *sql.DB +) + +func main() { + log.SetFlags(0) + + // + // 12.RowsScanDynamic + // + // sql.Query()で取得できる *sql.Rows から、カラム情報を取得する場合 + // 02.Query/main.go にある通り、正しい順序でデータ格納先となる変数を + // ポインタで渡す必要がある。 + // + // しかし、ちょっとしたツールなどをパパッと作成したい時に + // この仕様は若干面倒臭い。そのような場合は以下のようにすると + // 巷でよく見る「行データがマップになってて、それのリスト」という + // データ構造で処理することもできる。 + // + // 本サンプルの処理は [sqlmap](https://github.com/devlights/sqlmap) + // として公開している。 + // + if err := run(); err != nil { + log.Fatal(err) + } + + /* + $ task + task: [default] cp -f ../chinook.db . + task: [default] go run main.go + map[ArtistId:275 Name:Philip Glass Ensemble] + map[ArtistId:274 Name:Nash Ensemble] + map[ArtistId:273 Name:C. Monteverdi, Nigel Rogers - Chiaroscuro; London Baroque; London Cornett & Sackbu] + map[ArtistId:272 Name:Emerson String Quartet] + map[ArtistId:271 Name:Mela Tenenbaum, Pro Musica Prague & Richard Kapp] + */ +} + +func run() error { + // + // 普通にクエリ発行 + // + var ( + err error + ) + if err = open(); err != nil { + return fmt.Errorf("open(): %w", err) + } + + const ( + QUERY = "SELECT ArtistId, Name FROM artists ORDER BY ArtistId DESC LIMIT 5" + ) + var ( + rows *sql.Rows + ) + if rows, err = db.Query(QUERY); err != nil { + return fmt.Errorf("db.Query: %w", err) + } + defer rows.Close() + + // + // マッピング + // + var ( + results []map[string]any + ) + if results, err = mapRows(rows); err != nil { + return fmt.Errorf("mapRow: %w", err) + } + + // + // 表示 + // + for _, r := range results { + log.Printf("%v", r) + } + + return nil +} + +func open() error { + var ( + err error + ) + if db, err = sql.Open(driver, datasource); err != nil { + return err + } + + if err = db.Ping(); err != nil { + return err + } + + return nil +} + +func mapRows(rows *sql.Rows) ([]map[string]any, error) { + // + // 結果のカラム名リストを取得 + // + var ( + cols []string + err error + ) + if cols, err = rows.Columns(); err != nil { + return nil, err + } + + // + // 結果をマッピング + // + var ( + results []map[string]any + ) + for rows.Next() { + // + // *sql.Rows.Scan() には、ポインタを渡す必要があるため + // 予め値の器を用意し、更にそのポインタのリストを構築 + // + var ( + cv = make([]any, len(cols)) // 各カラム値の格納用 + cp = make([]any, len(cols)) // ポインタリスト + ) + for i := 0; i < len(cols); i++ { + cp[i] = &cv[i] + } + + // + // 可変長引数の展開演算子(Spread Operator) を使って一気に指定 + // + if err = rows.Scan(cp...); err != nil { + return nil, err + } + + var ( + result = make(map[string]any) + value *any + ) + for i, c := range cols { + value = cp[i].(*any) + result[c] = *value + } + + results = append(results, result) + } + + return results, nil +} diff --git a/README.md b/README.md index 738a2e9..930aed7 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ - [Golang with database operations](https://dev.to/burrock/golang-with-database-operations-3jl0) - [Go言語でSQLite3を使う](https://zenn.dev/teasy/articles/go-sqlite3-sample) - [Go ORMs Compared](https://dev.to/encore/go-orms-compared-2c8g) +- [Go database/sql の操作ガイドあったんかい](https://sourjp.github.io/posts/go-db/) ### ドライバやORMなど @@ -35,6 +36,8 @@ - [textql](https://github.com/dinedal/textql) - [xlsxsql](https://github.com/noborus/xlsxsql) - [trdsql](https://github.com/noborus/trdsql) +- [scan](https://github.com/blockloop/scan) - [gorm](https://github.com/go-gorm/gorm) - [ent](https://github.com/ent/ent) - [sqlmap](https://github.com/devlights/sqlmap) +- [upper/db](https://github.com/upper/db) diff --git a/go.mod b/go.mod index 18d07b6..191e724 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,15 @@ module github.com/devlights/try-golang-database go 1.22 require github.com/mattn/go-sqlite3 v1.14.22 + +require ( + github.com/devlights/sqlmap v0.1.1 + github.com/k0kubun/pp/v3 v3.2.0 +) + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/go.sum b/go.sum index e8d092a..e626a56 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,33 @@ +github.com/devlights/sqlmap v0.1.1 h1:z4HKpzBgd7VoARtvNu49TudILid2NS2pzhRwmEiZtdU= +github.com/devlights/sqlmap v0.1.1/go.mod h1:R6JOMjK30hRREaqdTdoI1i8isahrPsvbgdicGLhS7cQ= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= +github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= +modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=