お題
職場でSQL Boilerというのを使い始めているので、何ができるかちょっと家でも試してみようと思った。
が、表題の事象が起きたので、一応、記録。
【前置き】
※GoやDockerの環境構築やSQL Boilerのインストール・コマンド使用方法などは説明しない。
※提示した環境下で起きた事象なので、異なる環境下で同様に起きるかは不明。
※対象データベースは、PostgreSQLです。
環境
# OS - Linux(Ubuntu)
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"
# 言語 - Go
$ go version
go version go1.13.9 linux/amd64
# ツール - SQL Boiler
$ sqlboiler --version
SQLBoiler v3.6.1
# DB - PostgreSQL (from Docker Image)
$ cat db/docker-compose.yml | grep image
image: postgres:12-alpine
# Docker
$ sudo docker version
Client: Docker Engine - Community
Version: 19.03.8
API version: 1.40
Go version: go1.12.17
Git commit: afacb8b7f0
Built: Wed Mar 11 01:25:46 2020
OS/Arch: linux/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 19.03.8
API version: 1.40 (minimum version 1.12)
Go version: go1.12.17
Git commit: afacb8b7f0
Built: Wed Mar 11 01:24:19 2020
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.2.13
GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429
runc:
Version: 1.0.0-rc10
GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd
docker-init:
Version: 0.18.0
GitCommit: fec3683
# Docker Compose
$ docker-compose version
docker-compose version 1.25.4, build 8d51620a
docker-py version: 4.1.0
CPython version: 3.7.5
OpenSSL version: OpenSSL 1.1.0l 10 Sep 2019
実践
対象データベース
適当に作った2〜3テーブルを相手にしてもSQL Boilerの真価はわからないだろうと思ったので、PostgreSQL Sample Databaseというのを使った。
15テーブルあり、リレーションもある程度貼ってある。
DVDレンタルのモデルらしい。
※DataGripによるDiagram生成
Model自動生成
こんな感じで必要最低限の定義だけしておく。
※あくまでローカル環境のDocker内Postgresが相手なのでパスワードも生ざらし。
$ cat sqlboiler.toml
[psql]
host = "localhost"
port = 11311
sslmode= "disable"
schema = "public"
dbname = "dvdrental"
user = "postgres"
pass = "localpass"
ちなみに、Postgresコンテナ起動用のDocker Compose定義は↓
$ cat db/docker-compose.yml
version: '3'
services:
db:
restart: always
image: postgres:12-alpine
container_name: study-sql-boiler-db-container
ports:
- "11311:5432"
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=localpass
- PGPASSWORD=localpass
- POSTGRES_DB=dvdrental
- DATABASE_HOST=localhost
volumes:
- ./docker/db/init:/docker-entrypoint-initdb.d
sky0621@sky0621-W950JU:~/work/src/github.com/sky0621/study-sql-boiler$
いざ、Model自動生成。
$ sqlboiler psql
Error: unable to initialize tables: primary key missing in tables (actor_info, customer_list, film_list, nicer_but_slower_film_list, sales_by_film_category, sales_by_store, staff_list)
あれ?
『PKがないテーブルがあると自動生成失敗』なのか・・・。
ちゃんと仕様見よう。
https://github.com/volatiletech/sqlboiler#requirements
結合テーブルに複合PKが必要というのはわかった。
コマンド実行時のエラーメッセージからして、結合テーブルでなくてもPKが必要そうではある。
よくよく見ると、以下にそうと判断できる記述はあった。
https://github.com/volatiletech/sqlboiler#diagnosing-problems
「Tables without a primary key. All tables require one.
」
ただ、対象となるDVDレンタルデータベースにあるテーブルは全てPK持っている。。。じゃあ、なんでエラー?
と思ったら、エラーメッセージに載ってる「actor_info, customer_list, film_list, nicer_but_slower_film_list, sales_by_film_category, sales_by_store, staff_list
」はテーブル名じゃない。
ビューの名前だった。
※DataGripによるDiagram生成
まあ、PKないよね。。。
というか、じゃあ、ビュー含んでたら、Model自動生成できないのか、SQL Boiler?
View以外で自動生成
SQL Boilerの設定方法を見ると、blacklist
に記載があるものは無視する様子。
というわけでブラックリストにビューの名前を列挙。
$ cat sqlboiler.toml
[psql]
〜〜 省略 〜〜
blacklist = ["actor_info", "customer_list", "film_list", "nicer_but_slower_film_list", "sales_by_film_category", "sales_by_store", "staff_list"]
リトライ。
$ sqlboiler psql
$
今度は何事もなくコマンド実行終了。
Modelは、できてる?
$ ll models/
合計 1.1M
-rw-rw-r-- 1 sky0621 sky0621 31K 4月 11 22:28 actor.go
-rw-rw-r-- 1 sky0621 sky0621 22K 4月 11 22:28 actor_test.go
-rw-rw-r-- 1 sky0621 sky0621 44K 4月 11 22:28 address.go
-rw-rw-r-- 1 sky0621 sky0621 33K 4月 11 22:28 address_test.go
-rw-rw-r-- 1 sky0621 sky0621 2.4K 4月 11 22:28 boil_main_test.go
-rw-rw-r-- 1 sky0621 sky0621 868 4月 11 22:28 boil_queries.go
-rw-rw-r-- 1 sky0621 sky0621 1.1K 4月 11 22:28 boil_queries_test.go
-rw-rw-r-- 1 sky0621 sky0621 18K 4月 11 22:28 boil_suites_test.go
-rw-rw-r-- 1 sky0621 sky0621 927 4月 11 22:28 boil_table_names.go
-rw-rw-r-- 1 sky0621 sky0621 1.4K 4月 11 22:28 boil_types.go
-rw-rw-r-- 1 sky0621 sky0621 29K 4月 11 22:28 category.go
-rw-rw-r-- 1 sky0621 sky0621 23K 4月 11 22:28 category_test.go
-rw-rw-r-- 1 sky0621 sky0621 32K 4月 11 22:28 city.go
-rw-rw-r-- 1 sky0621 sky0621 25K 4月 11 22:28 city_test.go
-rw-rw-r-- 1 sky0621 sky0621 29K 4月 11 22:28 country.go
-rw-rw-r-- 1 sky0621 sky0621 23K 4月 11 22:28 country_test.go
-rw-rw-r-- 1 sky0621 sky0621 41K 4月 11 22:28 customer.go
-rw-rw-r-- 1 sky0621 sky0621 30K 4月 11 22:28 customer_test.go
-rw-rw-r-- 1 sky0621 sky0621 46K 4月 11 22:28 film.go
-rw-rw-r-- 1 sky0621 sky0621 33K 4月 11 22:28 film_actor.go
-rw-rw-r-- 1 sky0621 sky0621 25K 4月 11 22:28 film_actor_test.go
-rw-rw-r-- 1 sky0621 sky0621 34K 4月 11 22:28 film_category.go
-rw-rw-r-- 1 sky0621 sky0621 26K 4月 11 22:28 film_category_test.go
-rw-rw-r-- 1 sky0621 sky0621 33K 4月 11 22:28 film_test.go
-rw-rw-r-- 1 sky0621 sky0621 33K 4月 11 22:28 inventory.go
-rw-rw-r-- 1 sky0621 sky0621 26K 4月 11 22:28 inventory_test.go
-rw-rw-r-- 1 sky0621 sky0621 29K 4月 11 22:28 language.go
-rw-rw-r-- 1 sky0621 sky0621 23K 4月 11 22:28 language_test.go
-rw-rw-r-- 1 sky0621 sky0621 37K 4月 11 22:28 payment.go
-rw-rw-r-- 1 sky0621 sky0621 28K 4月 11 22:28 payment_test.go
-rw-rw-r-- 1 sky0621 sky0621 5.4K 4月 11 22:28 psql_main_test.go
-rw-rw-r-- 1 sky0621 sky0621 851 4月 11 22:28 psql_suites_test.go
-rw-rw-r-- 1 sky0621 sky0621 1.6K 4月 11 22:28 psql_upsert.go
-rw-rw-r-- 1 sky0621 sky0621 41K 4月 11 22:28 rental.go
-rw-rw-r-- 1 sky0621 sky0621 32K 4月 11 22:28 rental_test.go
-rw-rw-r-- 1 sky0621 sky0621 43K 4月 11 22:28 staff.go
-rw-rw-r-- 1 sky0621 sky0621 32K 4月 11 22:28 staff_test.go
-rw-rw-r-- 1 sky0621 sky0621 32K 4月 11 22:28 store.go
-rw-rw-r-- 1 sky0621 sky0621 25K 4月 11 22:28 store_test.go
できてる。
というわけで、一応、ビューがあっても、blacklist
に記載しておけば、自動生成でエラーが起きることはない。
のだけど、、、つどつどビューを列挙していくのって。。。。
あと、ビューに対してクエリ投げたい時は、SQL BoilerのModelを使ったコードは書けないということなのかな。。。
この手のORMが大抵備えているように、Raw Queryを流すことはできるようなので、ビューを相手にするときはRaw Queryを使うということか。
う〜む。
後日談
PostgreSQL Sample Databaseで提供されているスキーマを使ったのだけど、SQL Boilerによるソース自動生成は正常終了したものの、Golangのソース的に実行するとエラーが起きる状態で自動生成されていた。。。
【エラー内容】
../models/actor.go:518:21: invalid operation: local.ActorID == foreign.ActorID (mismatched types int and int16)
../models/actor.go:540:16: cannot use o.ActorID (type int) as type int16 in assignment
../models/actor.go:561:16: cannot use o.ActorID (type int) as type int16 in assignment
../models/address.go:596:20: invalid operation: local.CityID == foreign.CityID (mismatched types int16 and int)
../models/address.go:691:23: invalid operation: local.AddressID == foreign.AddressID (mismatched types int and int16)
../models/address.go:786:23: invalid operation: local.AddressID == foreign.AddressID (mismatched types int and int16)
../models/address.go:881:23: invalid operation: local.AddressID == foreign.AddressID (mismatched types int and int16)
../models/address.go:922:11: cannot use related.CityID (type int) as type int16 in assignment
../models/address.go:950:18: cannot use o.AddressID (type int) as type int16 in assignment
../models/address.go:971:18: cannot use o.AddressID (type int) as type int16 in assignment
../models/address.go:971:18: too many errors
【原因】
例えば、actor
テーブルとfilm
テーブルのPKの型がinteger
であるのに対し、両者の結合テーブルであるfilm_actor
テーブルはの複合PKの型はsmallint
であり、異なっている。
そのため、自動生成されたGolangのソースで、一方が int
型で定義された要素、もう一方が int16
型で定義された要素となり、結合テーブルゆえ(?)に「if local.ActorID == foreign.ActorID {
」なんてコードがありビルドエラー。
今回は、たまたま使ったサンプルDBがそういうようになっているための事象だったけど、自前でテーブル定義していく時にも下手すると起こりうるなぁと。。。
とりあえず、ひたすら smallint
-> integer
に直す。(念のため、修正後のERも載せる。)
そして、再度、Modelを生成。
そうしたら、(DBが起動した状態で)例えば以下のようなコードを実行すると標準出力に actor
テーブルの中身がずらずらと吐かれた。
package main
import (
"context"
"database/sql"
"fmt"
"github.com/sky0621/study-sql-boiler/models"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "dbname=dvdrental user=postgres password=localpass sslmode=disable port=11311")
if err != nil {
panic(err)
}
ctx := context.Background()
actors, err := models.Actors().All(ctx, db)
if err != nil {
panic(err)
}
for _, actor := range actors {
fmt.Println(actor)
}
}