padrino+PostgreSQLで作ったサイトを高速全文検索対応にする

# 概要

Dockerコンテナ上で動かしているpadrino+postgresqlなサービスに全文検索機能をつけたいとおもった。
ここでは[pgroonga](https://pgroonga.github.io/ja/)を用いる。
タイトルに高速といれたのは、同種の機能拡張[pg_bigmより早い](https://www.insight-ltd.co.jp/tech_blog/db-tuning/286/)みたいなので、groongaを選定したことによる。
が、どちらかというと手軽さをとった。


## 準備

Dockerfileで指定しているpostgresイメージを、pgroongaに変更した。
PostgreSQL向けにpgroongaという[groonga](https://groonga.org/ja/)用エクステンションがあることは知っていたが、pgroongaイメージはPostgreSQLを内包しており、差し替えるだけで全文検索対応したPostgreSQLとして利用できる。


```:docker/pgroonga/Dockerfile
FROM groonga/pgroonga:2.2.6-debian-12
#FROM postgres:12
```

docker-compose.yamlはdb部分だけは↓みたいな感じ。Windows上でつかっているので、別途 `docker volume create --name pgdata1`している。

```yaml:docker-compose.yml
version: '2'

services:
  db:
    build: docker/pgroonga
    container_name: pgoongadb
    ports:
      - "15432:5432"
    environment:
      - POSTGRES_USER=sa
      - POSTGRES_PASSWORD=sa
      - POSTGRES_DB=pgdb
    volumes:
      - pgdata1:/var/lib/postgresql/data
volumes:
  pgdata1:
    external: true
```

みてのとおり、Dockerfileを準備するまでもなく postgresをgroonga/pgroongaにしただけである。
(あとで[mecab-ipadic-neologd](https://github.com/neologd/mecab-ipadic-neologd)を使うつもりでDockerfileを分けている)

2022.2.20追記：DB構築後にextensionを有効化しておく必要がある

```
CREATE EXTENSION pgroonga
```

## こまったこと

データベースは上記で準備できた。
しかし、肝心の全文検索むけインデックスをpadrinoでいい感じに作成する方法がわからなかった。
ActiveRecordのadd_indexではチュートリアルにあるようなwith～的な記述が出来ない。
事例を検索してみたが見つからない。ActiveGroongaというのもあったが、それはちょっと違う。

groongaのコミュニティに相談したところ、須藤さんよりredmineでの事例を教えていただく。
またpadrinoでもこうすれば動くよ、というアドバイスもいただき、試した。

## 解決編

padrinoのプロジェクト内にある、libフォルダに下記のようなスクリプトを配置。

```ruby:lib/migrate_plugin_add_index.rb
module Migration
  module PostgreSQLAdapterOptionable
    def add_index_options(table_name, column_name, with: nil, **options)
      result = super(table_name, column_name, **options)
      result[3] += " WITH (#{with})" if with
      result
    end
  end
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(PostgreSQLAdapterOptionable)
end
```

上記プラグインを追加した状態で、padrino g migration add_fulltext_indexとかなんとかマイグレーションを作成。

```ruby:005_add_fulltext_index.rb
class AddFulltextIndex < ActiveRecord::Migration[5.2]
  def self.up
    add_index(:contacts, :name, using: 'pgroonga', with: "tokenizer='TokenMecab(\"use_reading\", true)'")
    add_index(:contacts, :description, using: 'pgroonga', with: "tokenizer='TokenMecab'")
    add_index(:contacts, :tel, using: 'pgroonga', with: "normalizer='NormalizerNFKC100(\"unify_hyphen_and_prolonged_sound_mark\", true)',tokenizer='TokenNgram(\"loose_symbol\", true,\"loose_blank\", true)'")
  end

  def self.down
  end
end
```

rake db:migrateすると無事、以下のようなSQLが発行され、create indexされた。

```
 add_index(:contacts, :name, {:using=>"pgroonga", :with=>"tokenizer = 'TokenMecab(\"use_reading\", true)'"})
DEBUG - (47.6ms) CREATE INDEX "index_contacts_on_name" ON "contacts" USING pgroonga ("name") WITH (tokenizer = 'TokenMecab("use_reading", true)')
```

試しにテストデータをつっこんでDataGripのクエリコンソールで、「東京都生まれヒップホップ育ち」みたいなデータを持つカラムに対し検索を実行。正しくおもったとおり動作。

```sql
select * from contacts where description &@ '京都' -- > なし
select * from contacts where description &@ '東京' -- > 検索結果 ○
```


## まとめ

- Docker上で動かしているなら、イメージをpostgresからpgroongaにするだけで全文検索対応にできる 
- プラグイン用ライブラリを1ファイル置いておけばpadrinoでもadd_indexするmigrationでインデックス作成できる


調べてわからなかったらコミュニティに聞いてみるのも手。場当たり対応でなくrails(ActiveRecord)側にもリクエストしておいたほうがいいかもという話をもらって、添削してもらいつつでissueも出してみた。