LoginSignup
5
3

More than 5 years have passed since last update.

LaravelでPostgreSQLの条件付きユニーク制約を作る

Last updated at Posted at 2017-09-18

概要

ユニーク制約を付けたいが、論理削除のため、普通のやり方では、制約が作成できない。
そういったときに、下記のような条件付きユニーク制約を設定したい。

PostgreSQL
CREATE UNIQUE INDEX users_unique_email 
ON users 
USING btree (email COLLATE pg_catalog."default") 
WHERE deleted_at;

ただし、Laravelのmigrateの通常の機能では対応できないようなので拡張を検討したメモを残します。

SQLを直接実行を調査

SQLを実行できれば、対応可能だな〜と思い調べてみたところ、下記の記事を見つける。

Laravel5 マイグレーションファイルで素のSQLクエリを実行する
http://shibukixxx.hateblo.jp/entry/2015/10/09/105329

しかし、Laravelのmigrateは、実行するSQLを変数にプールしておき、
まとめて実行するようなので、この直実行は、タイミングが合わず、
テーブルが存在しないとエラーになる、残念...。

ちゃんと調べてみることにする。

現状調査

調べてみた結果、PostgreSQLのmigrateには下記のファイルが関係しているようです。

  • Illuminate\Database\Schema\Blueprint;
  • Illuminate\Database\Schema\Grammars\PostgresGrammar;

それぞれを継承して拡張してみる。

CustomBlueprint作成

「app/Migrates」フォルダを作って、そこに独自定義のクラスを作成する。

app/Migrates/CustomBlueprint.php
<?php

namespace App\Migrates;

use Illuminate\Database\Schema\Blueprint;

/**
 * Blueprintクラスの拡張
 *
 * @package App\Migrate
 */
class CustomBlueprint extends Blueprint
{
    /**
     * 部分インデックスを利用したユニーク制約
     *
     * ※ 主に論理削除などに利用
     *
     * PostgreSQL Document
     * https://www.postgresql.jp/document/9.1/html/indexes-partial.html
     *
     * @param  string   $column
     * @param  string   $index
     * @param  string   $where
     * @return \Illuminate\Support\Fluent
     */
    public function uniquePartial($column, $index, $where = null)
    {
        $index = $index ? : $this->createIndexName('unique', (array) $column);
        return $this->addCommand(
            'uniquePartial', compact('index', 'column', 'where')
        );
    }
}

CustomPgsqlGrammar作成

次に「CustomPgsqlGrammar.php」を作成する。

app/Migrates/CustomPgsqlGrammar.php
<?php

namespace App\Migrates;

use Illuminate\Support\Fluent;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Grammars\PostgresGrammar;

/**
 * PostgresGrammarクラスを拡張
 *
 * @package App\Migrate
 */
class CustomPgsqlGrammar extends PostgresGrammar
{
    /**
     * 条件付きユニーク制約を作成する
     *
     * @param  \Illuminate\Database\Schema\Blueprint  $blueprint
     * @param  \Illuminate\Support\Fluent  $command
     * @return string
     */
    public function compileUniquePartial(Blueprint $blueprint, Fluent $command)
    {
        return sprintf('create unique index %s on %s using btree (%s collate pg_catalog."default") where %s',
            $this->wrap($command->index),
            $this->wrapTable($blueprint),
            $this->wrap($command->column),
            $command->where
        );
    }
}

migrateで使ってみる

database/migrations/****_**_**_******_create_users_table.php
<?php

use Illuminate\Support\Facades\Schema,
    Illuminate\Database\Schema\Blueprint,
    Illuminate\Database\Migrations\Migration,
    App\Migrates\CustomBlueprint,
    App\Migrates\CustomPgsqlGrammar;

/**
 * 会員テーブルの作成
 */
class CreateUsersTable extends Migration
{
    /**
     * マイグレーション実行
     *
     * @access  public
     * @return  void
     */
    public function up()
    {
        DB::connection()->setSchemaGrammar(new CustomPgsqlGrammar());
        $schema = DB::connection()->getSchemaBuilder();

        $schema->blueprintResolver(function($table, $callback) {
            return new CustomBlueprint($table, $callback);
        });

        $schema->create('users', function (CustomBlueprint $table) {
            $table->bigincrements('id');
            $table->string('email');
            $table->password('password');
            $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP'));
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP'));

            // 削除日時 (論理削除フラグ)
            $table->softDeletes();

            // ユニーク制約 (論理削除対応)
            $table->uniquePartial('email', null, 'deleted_at is null');
        });
    }

    /**
     * 元に戻す
     *
     * @access  public
     * @return  void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

以上

参考サイト

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3