statistics_info support
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / DBI.pm
1 package DBIx::Class::Schema::Loader::DBI;
2
3 use strict;
4 use warnings;
5 use base qw/DBIx::Class::Schema::Loader::Base/;
6 use Class::C3;
7 use Carp::Clan qw/^DBIx::Class/;
8 use UNIVERSAL::require;
9
10 our $VERSION = '0.03999_01';
11
12 =head1 NAME
13
14 DBIx::Class::Schema::Loader::DBI - DBIx::Class::Schema::Loader DBI Implementation.
15
16 =head1 SYNOPSIS
17
18 See L<DBIx::Class::Schema::Loader::Base>
19
20 =head1 DESCRIPTION
21
22 This is the base class for L<DBIx::Class::Schema::Loader::Base> classes for
23 DBI-based storage backends, and implements the common functionality between them.
24
25 See L<DBIx::Class::Schema::Loader::Base> for the available options.
26
27 =head1 METHODS
28
29 =head2 new
30
31 Overlays L<DBIx::Class::Schema::Loader::Base/new> to do some DBI-specific
32 things.
33
34 =cut
35
36 sub new {
37     my $self = shift->next::method(@_);
38
39     # rebless to vendor-specific class if it exists and loads
40     my $dbh = $self->schema->storage->dbh;
41     my $driver = $dbh->{Driver}->{Name};
42     my $subclass = 'DBIx::Class::Schema::Loader::DBI::' . $driver;
43     $subclass->require;
44     if($@ && $@ !~ /^Can't locate /) {
45         croak "Failed to require $subclass: $@";
46     }
47     elsif(!$@) {
48         bless $self, "DBIx::Class::Schema::Loader::DBI::${driver}";
49     }
50
51     # Set up the default quoting character and name seperators
52     $self->{_quoter} = $self->schema->storage->sql_maker->quote_char
53                     || $dbh->get_info(29)
54                     || q{"};
55
56     $self->{_namesep} = $self->schema->storage->sql_maker->name_sep
57                      || $dbh->get_info(41)
58                      || q{.};
59
60     # For our usage as regex matches, concatenating multiple quoter
61     # values works fine (e.g. s/\Q<>\E// if quoter was [ '<', '>' ])
62     if( ref $self->{_quoter} eq 'ARRAY') {
63         $self->{_quoter} = join(q{}, @{$self->{_quoter}});
64     }
65
66     $self->_setup;
67
68     $self;
69 }
70
71 # Override this in vendor modules to do things at the end of ->new()
72 sub _setup { }
73
74 # Returns an array of table names
75 sub _tables_list { 
76     my $self = shift;
77
78     my $dbh = $self->schema->storage->dbh;
79     my @tables = $dbh->tables(undef, $self->db_schema, '%', '%');
80     s/\Q$self->{_quoter}\E//g for @tables;
81     s/^.*\Q$self->{_namesep}\E// for @tables;
82
83     return @tables;
84 }
85
86 =head2 load
87
88 We override L<DBIx::Class::Schema::Loader::Base/load> here to hook in our localized settings for C<$dbh> error handling.
89
90 =cut
91
92 sub load {
93     my $self = shift;
94
95     local $self->schema->storage->dbh->{RaiseError} = 1;
96     local $self->schema->storage->dbh->{PrintError} = 0;
97     $self->next::method(@_);
98 }
99
100 # Returns an arrayref of column names
101 sub _table_columns {
102     my ($self, $table) = @_;
103
104     my $dbh = $self->schema->storage->dbh;
105
106     if($self->{db_schema}) {
107         $table = $self->{db_schema} . $self->{_namesep} . $table;
108     }
109
110     my $sth = $dbh->prepare($self->schema->storage->sql_maker->select($table, undef, \'1 = 0'));
111     $sth->execute;
112     return \@{$sth->{NAME_lc}};
113 }
114
115 # Returns arrayref of pk col names
116 sub _table_pk_info { 
117     my ($self, $table) = @_;
118
119     my $dbh = $self->schema->storage->dbh;
120
121     my @primary = map { lc } $dbh->primary_key('', $self->db_schema, $table);
122     s/\Q$self->{_quoter}\E//g for @primary;
123
124     return \@primary;
125 }
126
127 # Override this for vendor-specific uniq info
128 sub _table_uniq_info {
129     my ($self, $table) = @_;
130
131     my $dbh = $self->schema->storage->dbh;
132     if(!$dbh->can('statistics_info')) {
133         warn "No UNIQUE constraint information can be gathered for this vendor";
134         return [];
135     }
136
137     my %indices;
138     my $sth = $dbh->statistics_info(undef, $self->db_schema, $table, 1, 1);
139     while(my $row = $sth->fetchrow_hashref) {
140         # skip table-level stats, conditional indexes, and any index missing
141         #  critical fields
142         next if $row->{TYPE} eq 'table'
143             || defined $row->{FILTER_CONDITION}
144             || !$row->{INDEX_NAME}
145             || !defined $row->{ORDINAL_POSITION}
146             || !$row->{COLUMN_NAME};
147
148         $indices{$row->{INDEX_NAME}}->{$row->{ORDINAL_POSITION}} = $row->{COLUMN_NAME};
149     }
150
151     my @retval;
152     foreach my $index_name (keys %indices) {
153         my $index = $indices{$index_name};
154         push(@retval, [ $index_name => [
155             map { $index->{$_} }
156                 sort keys %$index
157         ]]);
158     }
159
160     return \@retval;
161 }
162
163 # Find relationships
164 sub _table_fk_info {
165     my ($self, $table) = @_;
166
167     my $dbh = $self->schema->storage->dbh;
168     my $sth = $dbh->foreign_key_info( '', '', '', '',
169         $self->db_schema, $table );
170     return [] if !$sth;
171
172     my %rels;
173
174     my $i = 1; # for unnamed rels, which hopefully have only 1 column ...
175     while(my $raw_rel = $sth->fetchrow_arrayref) {
176         my $uk_tbl  = $raw_rel->[2];
177         my $uk_col  = lc $raw_rel->[3];
178         my $fk_col  = lc $raw_rel->[7];
179         my $relid   = ($raw_rel->[11] || ( "__dcsld__" . $i++ ));
180         $uk_tbl =~ s/\Q$self->{_quoter}\E//g;
181         $uk_col =~ s/\Q$self->{_quoter}\E//g;
182         $fk_col =~ s/\Q$self->{_quoter}\E//g;
183         $relid  =~ s/\Q$self->{_quoter}\E//g;
184         $rels{$relid}->{tbl} = $uk_tbl;
185         $rels{$relid}->{cols}->{$uk_col} = $fk_col;
186     }
187
188     my @rels;
189     foreach my $relid (keys %rels) {
190         push(@rels, {
191             remote_columns => [ keys   %{$rels{$relid}->{cols}} ],
192             local_columns  => [ values %{$rels{$relid}->{cols}} ],
193             remote_table   => $rels{$relid}->{tbl},
194         });
195     }
196
197     return \@rels;
198 }
199
200 # ported in from DBIx::Class::Storage::DBI:
201 sub _columns_info_for {
202     my ($self, $table) = @_;
203
204     my $dbh = $self->schema->storage->dbh;
205
206     if ($dbh->can('column_info')) {
207         my %result;
208         eval {
209             my $sth = $dbh->column_info( undef, $self->db_schema, $table, '%' );
210             $sth->execute();
211             while ( my $info = $sth->fetchrow_hashref() ){
212                 my %column_info;
213                 $column_info{data_type}   = $info->{TYPE_NAME};
214                 $column_info{size}      = $info->{COLUMN_SIZE};
215                 $column_info{is_nullable}   = $info->{NULLABLE} ? 1 : 0;
216                 $column_info{default_value} = $info->{COLUMN_DEF};
217                 my $col_name = $info->{COLUMN_NAME};
218                 $col_name =~ s/^\"(.*)\"$/$1/;
219
220                 $result{$col_name} = \%column_info;
221             }
222         };
223       return \%result if !$@ && scalar keys %result;
224     }
225
226     if($self->db_schema) {
227         $table = $self->db_schema . $self->{_namesep} . $table;
228     }
229     my %result;
230     my $sth = $dbh->prepare($self->schema->storage->sql_maker->select($table, undef, \'1 = 0'));
231     $sth->execute;
232     my @columns = @{$sth->{NAME_lc}};
233     for my $i ( 0 .. $#columns ){
234         my %column_info;
235         $column_info{data_type} = $sth->{TYPE}->[$i];
236         $column_info{size} = $sth->{PRECISION}->[$i];
237         $column_info{is_nullable} = $sth->{NULLABLE}->[$i] ? 1 : 0;
238
239         if ($column_info{data_type} =~ m/^(.*?)\((.*?)\)$/) {
240             $column_info{data_type} = $1;
241             $column_info{size}    = $2;
242         }
243
244         $result{$columns[$i]} = \%column_info;
245     }
246     $sth->finish;
247
248     foreach my $col (keys %result) {
249         my $colinfo = $result{$col};
250         my $type_num = $colinfo->{data_type};
251         my $type_name;
252         if(defined $type_num && $dbh->can('type_info')) {
253             my $type_info = $dbh->type_info($type_num);
254             $type_name = $type_info->{TYPE_NAME} if $type_info;
255             $colinfo->{data_type} = $type_name if $type_name;
256         }
257     }
258
259     return \%result;
260 }
261
262 =head1 SEE ALSO
263
264 L<DBIx::Class::Schema::Loader>
265
266 =cut
267
268 1;