DBIx::Class + Oracle でプライマリーキーを auto increment する方法

Oracle には、MySQL の auto_increment 属性に該当する機能がありません。実現するには、次のサイトで紹介されているように、SEQUENCE と TRIGGER を組み合わせるというのが常套手段のようです。

でも、Perl の DBIx::Class を使えばラクチンです!例えば、次のようなクラスを作ったとします。

MyApp/Schema/Result/Post.pm

package MyApp::Schema::Result::Post;

use strict;
use warnings;

use base 'DBIx::Class';

__PACKAGE__->load_components( "Core" );
__PACKAGE__->table("post");
__PACKAGE__->add_columns(
    id => {
        data_type => 'NUMBER',
        size => 11,
        is_nullable => 0,
        is_auto_increment => 1,
    },
    title => {
        data_type => 'VARCHAR2',
        size => 500,
    },
);
__PACKAGE__->set_primary_key("id");

1;

15行目で is_auto_increment => 1 という指定をしています。この状態で、DBIx::Class::Schema の deploy メソッドを実行すると、次のように TABLE と同時に SEQUENCE と TRIGGER を自動的に作成してくれます。

CREATE SEQUENCE sq_post_id;

CREATE TABLE post (
  id number(11) NOT NULL,
  title varchar2(500),
  PRIMARY KEY (id)
);

CREATE OR REPLACE TRIGGER ai_post_id
BEFORE INSERT ON post
FOR EACH ROW WHEN (
  new.id IS NULL OR new.id = 0
)
BEGIN
  SELECT sq_post_id.NEXTVAL
  INTO :new.id
  FROM dual;
END;

実際の処理は、SQL::Translator::Producer::Oracle が担当しているようです。

ちなみに、add_columns() でカラムを定義する時に、data_type => ‘timestamp’ とすると、同様に次のような TRIGGER が作成され、レコードの追加および更新時に自動的に SYSDATE をセットしてくれます。

CREATE OR REPLACE TRIGGER ts_post_updated_at
BEFORE INSERT OR UPDATE ON post
FOR EACH ROW WHEN (new.updated_at IS NULL)
BEGIN
  SELECT sysdate INTO :new.updated_at FROM dual;
END;

仕事で Oracle を使うことになり、「えー!auto_increment ないのー!」と困っていたのですが、おかげで楽に実装できました。欲を言えば、作成する SEQUENCE の START WITH を指定できたり、レコードの追加のときだけ SYSDATE をセットしてくれる TRIGGER が自動生成できれば、もっとありがたいんだけどなぁ。


About this entry