Go言語のクエリビルダ−をタイプセーフに扱いたい
こんにちは。エンジニアの島袋です。
今回のテーマは表題の通りです。
TL;DR
- クエリビルダにおけるテーブル名、カラム名の指定がタイプセーフではないため
- 正確なテーブル名、カラム名を出力する関数を自動生成するコマンドを作り
- クエリビルダをタイプセーフに扱えるようにした
Go言語におけるクエリビルダー
Go言語にはORMとしてメソッドチェインでSQLを組み立てるクエリビルダーと呼ばれるタイプのライブラリがいくつか存在します。
クエリビルダーでSQLを構築する場合、以下のようなコードになります。(dbr利用の場合)
var ret []string | |
sess.Select("id").From("users").Load(&ret) |
クエリビルダは直感的にSQLが組み立てられるため、学習コストが低く扱い易いのですが、一つ問題があります。
上記の通り、カラム名やテーブル名を文字列で指定するため、タイプセーフではありません。
テーブル名やカラム名をtypoしてしまうと、クエリビルダによって生成されたSQLは実行時にエラーとなります。
これを解消するためには、正確なテーブル名、カラム名を出力してくれる関数があれば良さそうです。
そこで、データベースのメタデータからGo言語のソースコードを生成するCLIツール tsg を作ってみました。
(javaだとquerydslとかjooqとかコード生成機能付きのクエリビルダーがあるんですけどね)
tsg
tsgは以下のように使います。
(hostはlocalhost, portは5432がデフォルト)
- u : データベースのユーザ名
- pass : データベースのパスワード
- d : コード生成対象のデータベース
tsg -u username --pass=password -d database_name |
コマンド実行時、以下のテーブルがあったとすると、
- テーブル名 : users
- カラム名 : id
- カラム名 : name
以下のソースコードが出力されます。
package main | |
import "fmt" | |
var ( | |
VUsers Users | |
) | |
func init() { | |
VUsers = Users{ | |
original: "users", | |
ID: Column{name: "id"}, | |
Name: Column{name: "name"}, | |
} | |
} | |
type Column struct { | |
name string | |
} | |
func (c Column) N() string { | |
return c.name | |
} | |
type Users struct { | |
original string | |
ID Column | |
Name Column | |
} | |
func (t Users) N() string { | |
return t.original | |
} | |
func (t Users) A(aliasName string) Users { | |
return Users{ | |
original: aliasName, | |
ID: Column{name: fmt.Sprintf("%s.%s", aliasName, "id")}, | |
Name: Column{name: fmt.Sprintf("%s.%s", aliasName, "name")}, | |
} | |
} |
このコードを利用すると、冒頭のクエリビルダ−のサンプルは以下のように修正できます。
dbr.Select(VUsers.Name.N()).From(VUsers.N()).Load(&m) |
上記のように、関数から正確なテーブル名、カラム名が取得できることが保証されていれば、Goのビルドが通った時点でテーブル名、カラム名について正確なSQLが発行されることが保証されます。
(SQLが意図通りかどうかは別)
さらに、エディタの自動補完機能を利用すると関数のtypo自体も防げます。
(文字列を直接指定する場合、エディタの自動補完機能に頼ることができません)
生成される構造体、関数の機能
下記のコードのコメントアウト部分のように、N()でテーブル名、カラム名が出力されます。 テーブルに別名を与える場合はA()を実行し、そのままメソッドチェインでつなげてN()を実行します。
VUsers.N() // output "users" | |
VUsers.Name.N() // output "name" | |
VUsers.ID.N() // output "id" | |
VUsers.A("hoge").N() // output "hoge" | |
VUsers.A("hoge").Name.N() // output "hoge.name" | |
VUsers.A("hoge").ID.N() // output "hoge.id" |
未実装の機能
サポートしてるRDBが現状postgresqlのみです。
また、出力するソースコードにコメントがないため、golintを使っていると大量に警告が出ます。
加えて出力後のソースコードが整形されていないため、手動でgofmtをかける必要があります。
終わりに
今回のツールによりクエリビルダーをタイプセーフに扱うことができるようになりました。
(週末に作ったのでまだ実務で使っていませんが)
実はまだクエリビルダ−を便利に扱う上で課題がありまして、現状select文の結果をマッピングするための構造体は自分で手書きで作成する必要があります。
単一のテーブルに対応するだけでよければ、今回のように自動生成することも難しくはないのですが、問題はテーブルをjoinするケースです。
クエリビルダーにより出力されたSQLを見ないと、どのテーブルのどのカラムが必要なのか分からないため、事前にselect結果をマッピングする構造体を自動生成するということができません。
このあたり、何かうまいソリューションはないものでしょうか。。。。
コメント