SOMPO Digital Lab 開発チームブログ

安心・安全・健康に資する開発情報を発信します

entで外部キーの名前が長くなってしまった時の対処法

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で開発をするソフトウェアエンジニアを募集中です。

以下のリンクからカジュアル面談の応募ができるので、興味を持っていただけた方は是非話を聞きに来て下さい。

www.wantedly.com