[Vapor]FluentでPostgreSQLにインデックス貼るときの罠

VaporでDBさわるときのORMとして用意されているのがFluentですが...
そのFluentを通してインデックスを貼ってみるとprepareが動かなかったのです。
ってことで

Fluentでインデックス貼るときの罠

です。

Vapor Framework v2.4.0 / Fluent v2.4.2

Fluentを使ってのインデックスの貼り方

何かしらのDBを扱っていると使いたくなるインデックス。

例えばItemというテーブルにseqというカラムがあってそこにインデックスを貼りたいですよと...
(並びを自由に変更できるときなんかにやります。。。よね?(笑))

そういうときも、Fluentはちゃんとインデックスも対応しているので安心です。
使い方としては...

extension Item: Preparation {
    public static func prepare(_ database: Database) throws {
        // 定義のコード
        ...
        
        // add index
        try database.index("seq", for: Item.self)
    }
}

こんな感じです。お手軽でありがたいです。

問題

prepare自体はさくっと終わったので、いざコマンドを叩きましょう!
いつものようにvapor run prepareタタターーン!!!!

posgresql Cannot create index: '_fluent_idx_seq' already exists

エラーどーん!!
なぜなのか?🤔
実は他のテーブルに同じカラム名seqがあって、そこにも同じようにインデックスをはってあったのです。
それでインデックス名が重複エラーになっていると...

これにはPostgreSQLの仕様がおおきくて、スキーマ単位でインデックス名は一意じゃなければならないのです。
たとえ別テーブルであっても。。です
(MySQLなんかはテーブル自体に差し込むので、テーブル内で一意だったらいいんですけどね...)

でもちょっとこれはこまりますよねぇ...例に上げた感じでテーブルは違えどカラム名は同じでインデックス貼るケースというのはそこまでレアなケースではないはず

すこし深掘りしてみる

ほんとかな?なにかあるのでは?とコードをおってみました。

public var name: String {
    let list = fields.joined(separator: "_")
    return "_fluent_idx_\(list)"
}

https://github.com/vapor/fluent/blob/master/Sources/Fluent/Schema/Index.swift#L9

うーむ...。どうやらやっぱりそうみたいだ。

対応するぞ

それでは、インデックス貼る時に被らないような名前をつける?
try database.index("item_seq", for: TableName.self)
こういう感じで...
駄目です🙅
item_seqなんぞというカラムはありません!というエラーにかわるだけです。

ではいったい....

諦めてカラム名を変える

です...僕が行き着いた答えは。
被らないように。。。Fluentの命名ルールに則ってもスキーマ内で一意になるように。。。。😇

※いやテーブルぐらい自分でSQL発行してやればええやんけ!っていうアレは無しで。。Fluentのお話をしているのです!(笑)

作業もある程度進んだ頃合いで、そいやインデックスはるかーってなって、「あ!?カラム名かえなきゃだと。。。!」ってなるとかなり凹むので、
Vapor/Fluent+PostgreSQLでやろうとしている人はテーブル設計はここらへんも考えて置くと幸せになれそうです。
インデックスをはるカラムにはテーブル名を含めておくとか(冗長になるけど)

  • item.item_seq
  • weapon.weapon_seq

みたいな?

まぁそのうち修正はされるはずです...。

参考

Fluent - Vapor Docs