SOMPO Digital Lab ソフトウェアエンジニアの木村です。
前回の記事に続いて、Goのentに関する記事です。小ネタです。
entが名付ける識別子
entで外部キーの定義をしていると、生成される識別子(テーブル名、外部キー名など)がとても長くなってしまうことがあります。
entの外部キーは以下のように定義できます。
ここではBlog
テーブルからUser
テーブルのid
カラムへ外部へ外部キーを作成しています。
// Fields of the User. func (User) Fields() []ent.Field { return []end.Field{ field.String("id") } } // Edges of the Blog. func (Blog) Edges() []ent.Edge { return []ent.Edge{ edge.To( "user_has_blog", User.Type, ).StorageKey(edge.Column("user_id")) }
この時、生成される外部キーの名前は 参照元のテーブルの名前 + 参照先のテーブルの名前 + 参照先のテーブルのカラムの名前
となります。
つまり上の定義で言うと、blog_user_user_has_blog
という名前になります。
この時、テーブル名、カラム名が長い場合は以下のように。
// Edges of the TotemoNnagaaaaaaaaaaaaaaaaaaaaiTable. func (TotemoNagaaaaaaaaaaaaaaaaaaaaiTable) Field() []ent.Field { return []ent.Field{ field.String("id") } } // Edges of the NankaSugooooooooooooooooooooiNagaiTable. func (NankaSugooooooooooooooooooooiNagaiTable) Edges() []ent.Edge { return []ent.Edge{ edge.To( "totemo_nagaaaaaaaaaaaaaaaaaaaai_table_has_nanka_sugooooooooooooooooooooi_nagai_table", TotemoNagaaaaaaaaaaaaaaaaaaaaiTable.Type, ).StorageKey(edge.Column("totemo_nagaaaaaaaaaaaaaaaaaaaai_table_id")) }
この時、外部キーの名前はやはり参照元のテーブルの名前 + 参照先のテーブルの名前 + edgeに着けた名前
ので以下のようになります。
nanka_sugooooooooooooooooooooi_nagai_table_totemo_nagaaaaaaaaaaaaaaaaaaaai_table_totemo_nagaaaaaaaaaaaaaaaaaaaai_table_has_nanka_sugooooooooooooooooooooi_nagai_table
とても長いですね。
RDBのオブジェクトの名前長制限
このようにテーブル名、カラム名が長くなると問題が起こります。
基本的にRDBではオブジェクトの識別子の長さに制限があります。
例えばPostgreSQLであれば、デフォルトでは63byteです。
参考: https://www.postgresql.jp/document/12/html/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
システムは
NAMEDATALEN
-1バイトより長い識別子を使いません。 より長い名前をコマンドで書くことはできますが、短く切られてしまいます。 デフォルトではNAMEDATALEN
は64なので、識別子は最長で63バイトです。 この制限が問題になる場合は、src/include/pg_config_manual.h
内のNAMEDATALEN
定数の値を変更して増やすことができます。
これはシステムカタログ pg_type
のレコードでも確認できます
select typlen from pg_type where typname = 'name'; typlen| ------+ 64|
なので上記の外部キーの名前は64文字目以降が切り捨てられてnanka_sugooooooooooooooooooooi_nagai_table_totemo__nagaaaaaaaaaa
となってしまいます。
entのマイグレーション生成機能
entではコマンドラインツールを用いるとGoで書いた定義と、実際のデータベースのスキーマを比較して差分をマイグレーションファイルとして作成してくれる機能があります。
この時、マイグレーションファイルに出力される外部キーの名前はいくら長かろうと切り捨てられません。なので今回の場合はマイグレーションファイルは以下のように生成されます(可読性のため一部改行を入れています)。
ALTER TABLE "nanka_sugooooooooooooooooooooi_nagai_table" ADD CONSTRAINT "nanka_sugooooooooooooooooooooi_nagai_table_totemo_nagaaaaaaaaaaaaaaaaaaaai_table_totemo_nagaaaaaaaaaaaaaaaaaaaai_table_has_nanka_sugooooooooooooooooooooi_nagai_table" FOREIGN KEY ("totemo_nagaaaaaaaaaaaaaaaaaaaai_table_id") REFERENCES "totemo_nagaaaaaaaaaaaaaaaaaaaai_table" ("id");
つまり何が問題なのか
マイグレーションファイル上は64文字以上の長い外部キー名が定義されていますが、それを適用すると63文字に切り詰められます。
このため、以下のようなことが発生します。
🧑💻エンジニア「外部キーの定義を書いた。コマンドラインツールを実行してマイグレーションファイルを作るぞ」
🤖コマンドラインツール「マイグレーションファイルを作成しました」
💽データベース「外部キーの名前が長すぎるので、名前を切り詰めて外部キーを生成します」
🧑💻エンジニア「先程の外部キーとは別の定義を書いた。コマンドラインツールを実行してマイグレーションファイルを作るぞ」
🤖コマンドラインツール「 (DBの外部キーと比較して)nanka_sugooooooooooooooooooooi_nagai_table_totemo_nagaaaaaaaaaaaaaaaaaaaai_table_totemo_nagaaaaaaaaaaaaaaaaaaaai_table_has_nanka_sugooooooooooooooooooooi_nagai_table
という外部キーはまだ存在しないな。これは新しく外部キーを生成する必要があるな。(外部キーの新規定義も含めた)マイグレーションファイルを作成しました」
これではマイグレーションファイルを生成するたびに、1度作られた外部キーを再度作成するような内容が含まれてしまいます。
どうすれば良いのか?
Goで外部キーを定義するときに、外部キーの名前を明示的に指定することができます。
これには edge.Symbol
を用いて以下のようにします。
// Edges of the NankaSugooooooooooooooooooooiNagaiTable. func (NankaSugooooooooooooooooooooiNagaiTable) Edges() []ent.Edge { return []ent.Edge{ edge.To( "totemo_nagaaaaaaaaaaaaaaaaaaaai_table_has_nanka_sugooooooooooooooooooooi_nagai_table", TotemoNagaaaaaaaaaaaaaaaaaaaaiTable.Type, ).StorageKey( edge.Column("totemo_nagaaaaaaaaaaaaaaaaaaaai_table_id"), edge.Symbol("fk_mijikai"), ) }
この時、外部キーは fk_mijikai
となります。
このように明示的に外部キーの名前を指定して、テーブルの識別子長を超えないようにしてあげることで、コマンドラインツールと実DB間の不要な差分をなくすことができます。
まとめ
entで識別子の名前が長くなってしまう問題と、その対策について取り上げました。
今回この記事を書こうと思った理由として、 edge.Symbol
の存在があまり知られていないのではないかというのがありました。
edge.Symbol
の機能については、公式のドキュメントやentgo.io/entの中では触れられていません。
分かりづらいですがentgo.io/ent/schema/edgeの中で少し触れられている程度です。
このブログが少しでも問題解決の手助けとなれば幸いです。
SOMPO Digital Labでは一緒に働くソフトウェアエンジニアを募集しています
弊社ではGoで開発をするソフトウェアエンジニアを募集中です。
以下のリンクからカジュアル面談の応募ができるので、興味を持っていただけた方は是非話を聞きに来て下さい。