私はデータベースに自作のUUIDを作る関数が欲しいと思った

# なにこれ
私は Ruby on Rails でコードを書くことが好きなのだ。で、Rails で「ユニークでかつソート可能な ID」を発行してくれる関数を書いていたとき、悟ったのだ。これは「データベースにさせるべきで、Rails がやるべきコード」ではないのだと。

# やりたかったこと、やらねばいけないこと
- ソートできる UUID v4 をデータベースに作らせる
    - UUID v1 から時間だけをもらう
    - UUID v4 から匿名性とバリアント情報をもらう
- Rails で save したら自動的に id が書き込まれている状態になっていること
- PostgreSQL で関数を書くのは「はじめて♡」なので、動きそうなコードを探してコピペして無理やり動かす
    - もちろん、こんなことはプログラマとして「あってはならない」ことなのは百も承知しとるわ！
- コードの品質
    - リファクタリングしないといけない
        - 自分で書いたコードなのに、反吐がでる、がやる気がしない..
    - テストコードを書かないといけない
        - 時間とランダムを含んだ返り値に対してどうテストコードを書けば良いのか？ 
- 負荷テスト
    - 生成コストがどんなものか、計算していない
    - 本当に目的が達成できているのか、分析していない
- 目をつぶったこと
    - DBマスタが増えたときに、時間に対して ID が適切にソートされているかについて
    - 将来 UUID v6 が出てきた時に、関数の置き換えだけで対応できるかについて

# 💩コード注意
Rails から SQL 文を発行して 関数をつくる。

```ruby
class GenFakeUuids < ActiveRecord::Migration[6.0]
  def change
    reversible do |dir|
      dir.up do
        execute <<~"SQL"
                    CREATE OR REPLACE FUNCTION gen_fake_uuid()
                    RETURNS UUID AS $$
                    DECLARE
                      tlow varchar;
                      tmid varchar;              
                      thig varchar;
                      last varchar;
                      abcd varchar;
                    BEGIN
                      tmid := uuid_generate_v1()::varchar;
                      tlow := substring(tmid from '^.{8}');
                      thig := substring(tmid from '.{4}-1.{3}-[89ab]');
                      tmid := substring(thig from '^.{4}');
                      thig := substring(thig from '-1.{3}');
                      thig := substring(thig from '.{3}$');
                      last := substring((gen_random_uuid()::varchar) from '.{4}-.{12}$');
                      abcd := (thig || tmid || tlow);
                      abcd := left(abcd, 12) || '4' || right(tlow, 3) || last;
                      RETURN uuid('{' || abcd || '}');
                    END;
                    $$ LANGUAGE plpgsql;
        SQL
      end
      dir.down do
        execute <<-SQL
        DROP FUNCTION IF EXISTS gen_fake_uuid();
        SQL
      end
    end
  end
end
```

お試しで使うモデルを作成するコード

```ruby
class CreateStaffs < ActiveRecord::Migration[6.0]
  def change
    reversible do |dir|
      dir.up do
        execute <<~"SQL"
                    CREATE TABLE staffs (id uuid DEFAULT gen_fake_uuid(), email varchar NOT NULL, cname varchar NOT NULL, created_at timestamp(6) NOT NULL, updated_at timestamp(6) NOT NULL, PRIMARY KEY (id)) PARTITION BY HASH (id);
                    #{(0..99).map{|num| "CREATE TABLE staffs_partition_#{num} PARTITION OF staffs FOR VALUES WITH(MODULUS 100,REMAINDER #{num});"}.join}
        SQL
      end
      dir.down do
        execute <<-SQL
                   DROP TABLE staffs;
        SQL
      end
    end
  end
end
```
irb で動作チェック

```irb
irb(main):013:0> (1..10000).each{|num| Staff.create(email: "abc-#{num}", cname: "efg-#{num}") }
irb(main):014:0> Staff.first.id
  Staff Load (1.1ms)  SELECT "staffs".* FROM "staffs" ORDER BY "staffs"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> "1ea8460b-2b38-1318-b24c-a33a16b7c474"irb(main):010:0> Staff.first.id
  Staff Load (6.7ms)  SELECT "staffs".* FROM "staffs" ORDER BY "staffs"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> "1ea84621-3a0d-459e-90ae-5b7104152e56"
irb(main):011:0> Staff.second.id
  Staff Load (2.0ms)  SELECT "staffs".* FROM "staffs" ORDER BY "staffs"."id" ASC LIMIT $1 OFFSET $2  [["LIMIT", 1], ["OFFSET", 1]]
=> "1ea84621-3a12-44cc-b036-3902b9413296"
irb(main):012:0> Staff.last.id
  Staff Load (2.1ms)  SELECT "staffs".* FROM "staffs" ORDER BY "staffs"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> "1ea84621-ab15-400c-8044-815ebd9d12fa"
irb(main):014:0> Staff.first.id < Staff.last.id
  Staff Load (2.1ms)  SELECT "staffs".* FROM "staffs" ORDER BY "staffs"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Staff Load (1.6ms)  SELECT "staffs".* FROM "staffs" ORDER BY "staffs"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> true
irb(main):015:0> Staff.second.id < Staff.last.id
  Staff Load (2.3ms)  SELECT "staffs".* FROM "staffs" ORDER BY "staffs"."id" ASC LIMIT $1 OFFSET $2  [["LIMIT", 1], ["OFFSET", 1]]
  Staff Load (1.6ms)  SELECT "staffs".* FROM "staffs" ORDER BY "staffs"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> true
```
まぁ、これを見る感じ良いっぽいですね。

# おわりに
不眠症がひどく、寝れていないイライラを打破するために書いた。PostgreSQL でいつか関数を書こうと思っていたので、良い経験となったと思う。