DBIx::Class::Schema::Loader::Oracle 作りました
「DBIx::Class::Loader::Oracle 作りました(多分ちゃんと動いてる・・・)」で書きましたが、今の主流?は DBIx::Class::Loader ではなくて DBIx::Class::Schema::Loader を使うらしい。
ってことで、DBIx::Class::Loader::Oracle の他に DBIx::Class::Schema::Loader::Oracle も作りました。結構、中の作り方が違うので苦労しました。一応動作確認済みですが、_load_relationships 周りの試験をあまりしてないので、本当に version 0.01 って感じ。
ダウンロードはこちら → DBIx-Class-Schema-Loader-Oracle-0.01.tar.gz
話は変わって、何って言うかやっぱりオープンソース全盛期の今ってデータベースも MySQL or PostgreSQL が使われる場合が多いわけで、開発される Perl モジュールもやっぱりその辺が多い感じ。LL + Oracle ってかなりマイナーなんだなぁ〜と思う今日この頃。DBIx や CDBI まわりで Oracle 関連が極端に少ないのがその証拠。Oracle も Geek に目を向けてもらえるように無料バージョン Oracle をもっと閾を低くして MySQL 並に自由に使えるようにしたほうがいいのになぁ・・・
それにしても、LL 使ってるとやっぱデータベースは MySQL が便利だなぁ〜と思ったり。もろもろの保証や環境を考えて本業においては Oracle を使うのがベターな案件なわけですが・・・。(※なんて状況でも Java に逝きたくなかったり・・・)
さて、最後に話を元に戻して DBIx::Class::Schema::Loader::Oracle のソース。こんな感じ。
package DBIx::Class::Schema::Loader::DBI::Oracle;
use strict;
use warnings;
use base 'DBIx::Class::Schema::Loader::DBI';
use Class::C3;
sub _table_columns {
my ($self, $table) = @_;
my $dbh = $self->schema->storage->dbh;
my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0");
$sth->execute;
return \@{$sth->{NAME_lc}};
}
sub _tables_list {
my $self = shift;
my $dbh = $self->schema->storage->dbh;
my @tables;
for my $table ( $dbh->tables(undef, $self->db_schema, '%', 'TABLE') ) { #catalog, schema, table, type
my $quoter = $dbh->get_info(29);
$table =~ s/$quoter//g;
# remove "user." (schema) prefixes
$table =~ s/\w+\.//;
next if $table eq 'PLAN_TABLE';
$table = lc $table;
push @tables, $1
if $table =~ /\A(\w+)\z/;
}
return @tables;
}
sub _table_uniq_info {
my ($self, $table) = @_;
my @uniqs;
my $dbh = $self->schema->storage->dbh;
my $sth = $dbh->prepare_cached(
qq{SELECT constraint_name, ucc.column_name FROM user_constraints JOIN user_cons_columns ucc USING (constraint_name) WHERE ucc.table_name=? AND constraint_type='U'}
,{}, 1);
$sth->execute(uc $table);
my %constr_names;
while(my $constr = $sth->fetchrow_arrayref) {
my $constr_name = $constr->[0];
my $constr_def = $constr->[1];
$constr_name =~ s/\Q$self->{_quoter}\E//;
$constr_def =~ s/\Q$self->{_quoter}\E//;
push @{$constr_names{$constr_name}}, lc $constr_def;
}
map {
push(@uniqs, [ lc $_ => $constr_names{$_} ]);
} keys %constr_names;
return \@uniqs;
}
sub _table_pk_info {
my ( $self, $table ) = @_;
return $self->SUPER::_table_pk_info(uc $table);
}
sub _table_fk_info {
my ($self, $table) = @_;
my $dbh = $self->schema->storage->dbh;
my $sth = $dbh->foreign_key_info( '', '', '', '',
$self->db_schema, uc $table );
return [] if !$sth;
my %rels;
my $i = 1; # for unnamed rels, which hopefully have only 1 column ...
while(my $raw_rel = $sth->fetchrow_arrayref) {
my $uk_tbl = lc $raw_rel->[2];
my $uk_col = lc $raw_rel->[3];
my $fk_col = lc $raw_rel->[7];
my $relid = ($raw_rel->[11] || ( "__dcsld__" . $i++ ));
$uk_tbl =~ s/\Q$self->{_quoter}\E//g;
$uk_col =~ s/\Q$self->{_quoter}\E//g;
$fk_col =~ s/\Q$self->{_quoter}\E//g;
$relid =~ s/\Q$self->{_quoter}\E//g;
$rels{$relid}->{tbl} = $uk_tbl;
$rels{$relid}->{cols}->{$uk_col} = $fk_col;
}
my @rels;
foreach my $relid (keys %rels) {
push(@rels, {
remote_columns => [ keys %{$rels{$relid}->{cols}} ],
local_columns => [ values %{$rels{$relid}->{cols}} ],
remote_table => $rels{$relid}->{tbl},
});
}
return \@rels;
}
1;
で検証するためのスクリプトはこんな感じです。多分ちゃんと動いていると思います。
package Test::SchemaLoader;
use strict;
use warnings;
use base 'DBIx::Class::Schema::Loader';
__PACKAGE__->loader_options(
db_schema => 'TEST',
relationships => 1,
debug => 1,
);
__PACKAGE__->connection( "dbi:Oracle:testdb", "test", "test123" );
use strict;
use warnings;
use Data::Dumper;
my $schema = "Test::SchemaLoader";
my $loader = $schema->loader;
my @tables = $loader->tables;
print Dumper(\@tables);


コメントやシェアをお願いします!
tamashiro
drkさん
コメントありがとうございます。
> 不具合あったら教えて頂ければ幸いです。
そうですね、こちらの環境に依存した問題かもしれませんが、以下のように、一部の関数を上書きして使用しています。
これを書いたのは少し前なので、具体的に何が悪かったかは良く覚えてないのですが。。
# 一部の関数を改造する
{
use DBIx::Class::Schema::Loader::DBI::Oracle;
package DBIx::Class::Schema::Loader::DBI::Oracle;
# オリジナルではテーブル情報を取得出来ないため
undef &_tables_list;
*_tables_list = sub {
my $self = shift;
my $dbh = $self->schema->storage->dbh;
@{$dbh->selectcol_arrayref('SELECT table_name FROM user_tables')};
};
# オリジナルではプライマリキー情報を取得出来ないため
undef &_table_pk_info;
*_table_pk_info = sub {
my $self = shift;
my $table = shift;
my $dbh = $self->schema->storage->dbh;
$dbh->selectcol_arrayref(q{
SELECT LOWER(column_name)
FROM user_ind_columns
WHERE index_name IN (
SELECT constraint_name
FROM user_constraints
WHERE
constraint_type=? AND
table_name=?
)
}, undef, 'P', uc $table);
};
}
あと、もう1点、もしご存知であれば教えていただけないでしょうか。
DBIx::Class::Schema::Loaderでmany_to_manyは作成出来ないでしょうか?
マッチングテーブルは複数あるのですが、どれもhas_mamyしか作成されません。
※というか、よく考えると、「Foreign Keyが複数あるから、それはマッチングテーブルだ」とは言えないから、Loaderが自分で判断する事は出来ないですね。。
でも、そのテーブルが
- カラム2つのみで、
- どちらもForeign Keyで、
- しかも複数プライマリキーになっているか、ユニーク制約があれば、
これは間違いなく、マッチングテーブルですよね。
この場合は、勝手にmany_to_manyを作成してくれると良いんですけどね。あっ、もちろんdrkさんに言っているのではないので。。
drk
tamashiroさん>逆に勉強になりました。外部キー周りの動作検証をあまり取っていないのでバグがないか心配だったりします・・・(汗
不具合あったら教えて頂ければ幸いです。どうぞ宜しくお願い致します。
tamashiro
いろいろ調べてみたところ、"people"問題が解決しました。
せっかくコメント欄を使わせていただいたのにすみません。
make_schema_at関数に、"inflect_plural"で任意の関数を指定する事が出来たので、ここに
inflect_plural => sub { return shift() . 's' },
とする事で対処出来ました。
tamashiro
いつも有益な情報の提供をありがとうございます。
今まさにDBIx::Class + Oracleに挑戦しているところなので、非常に助かっております。
さて、DBIx::Class::Schema::Loader + DBIx-Class-Schema-Loader-DBI-Oracle-0.01でスキーマを作成しているのですが、"person"が"people"になってしまうのを"persons"とさせたいと考えています。もし方法をご存知であれば教えていただけませんでしょうか。
例えば"company"テーブルの外部テーブルに"person"がある場合、companyのhas_manyに指定されるアクセサ名が"people"になってしまいます。また、他にも(ん〜、良い例が浮かびませんが、)"hotchkiss"等、"s"で終わるテーブルはアクセサ名に"s"が付きません。
おそらくLingua::EN::Inflectの影響かと思うのですが、理想は単純に末尾に"s"を付けるだけ、としたいのです。出来れば「標準的な」方法でこれを行う事は出来ないでしょうか?