Commit | Line | Data |
18fca96a |
1 | package DBIx::Class::Schema::Loader::Generic; |
a78e3fed |
2 | |
3 | use strict; |
a4a19f3c |
4 | use warnings; |
5 | |
6 | use base qw/DBIx::Class::Schema/; |
7 | |
a78e3fed |
8 | use Carp; |
9 | use Lingua::EN::Inflect; |
a4a19f3c |
10 | |
a78e3fed |
11 | require DBIx::Class::Core; |
a4a19f3c |
12 | |
e26a4023 |
13 | __PACKAGE__->mk_classaccessor('_loader_inflect'); |
14 | __PACKAGE__->mk_classaccessor('_loader_db_schema'); |
15 | __PACKAGE__->mk_classaccessor('_loader_drop_db_schema'); |
16 | __PACKAGE__->mk_classaccessor('_loader_classes' => {} ); |
17 | __PACKAGE__->mk_classaccessor('_loader_monikers' => {} ); |
66742793 |
18 | __PACKAGE__->mk_classaccessor('_loader_debug' => 0); |
a4a19f3c |
19 | |
a78e3fed |
20 | =head1 NAME |
21 | |
18fca96a |
22 | DBIx::Class::Schema::Loader::Generic - Generic DBIx::Class::Schema::Loader Implementation. |
a78e3fed |
23 | |
24 | =head1 SYNOPSIS |
25 | |
18fca96a |
26 | See L<DBIx::Class::Schema::Loader> |
a78e3fed |
27 | |
28 | =head1 DESCRIPTION |
29 | |
30 | =head2 OPTIONS |
31 | |
32 | Available constructor options are: |
33 | |
34 | =head3 additional_base_classes |
35 | |
36 | List of additional base classes your table classes will use. |
37 | |
38 | =head3 left_base_classes |
39 | |
40 | List of additional base classes, that need to be leftmost. |
41 | |
42 | =head3 additional_classes |
43 | |
44 | List of additional classes which your table classes will use. |
45 | |
46 | =head3 constraint |
47 | |
48 | Only load tables matching regex. |
49 | |
50 | =head3 exclude |
51 | |
52 | Exclude tables matching regex. |
53 | |
54 | =head3 debug |
55 | |
56 | Enable debug messages. |
57 | |
58 | =head3 dsn |
59 | |
60 | DBI Data Source Name. |
61 | |
a78e3fed |
62 | =head3 password |
63 | |
64 | Password. |
65 | |
66 | =head3 relationships |
67 | |
68 | Try to automatically detect/setup has_a and has_many relationships. |
69 | |
70 | =head3 inflect |
71 | |
72 | An hashref, which contains exceptions to Lingua::EN::Inflect::PL(). |
73 | Useful for foreign language column names. |
74 | |
75 | =head3 user |
76 | |
77 | Username. |
78 | |
79 | =head2 METHODS |
80 | |
81 | =cut |
82 | |
83 | =head3 new |
84 | |
85 | Not intended to be called directly. This is used internally by the |
18fca96a |
86 | C<new()> method in L<DBIx::Class::Schema::Loader>. |
a78e3fed |
87 | |
88 | =cut |
89 | |
a4a19f3c |
90 | sub _load_from_connection { |
a78e3fed |
91 | my ( $class, %args ) = @_; |
3385ac62 |
92 | |
e26a4023 |
93 | $class->_loader_debug(1) if $args{debug}; |
94 | $class->_loader_inflect($args{inflect}); |
95 | $class->_loader_db_schema($args{db_schema} || ''); |
96 | $class->_loader_drop_db_schema($args{drop_db_schema}); |
3385ac62 |
97 | |
a78e3fed |
98 | my $additional = $args{additional_classes} || []; |
99 | $additional = [$additional] unless ref $additional eq 'ARRAY'; |
3385ac62 |
100 | |
a78e3fed |
101 | my $additional_base = $args{additional_base_classes} || []; |
102 | $additional_base = [$additional_base] |
103 | unless ref $additional_base eq 'ARRAY'; |
3385ac62 |
104 | |
a78e3fed |
105 | my $left_base = $args{left_base_classes} || []; |
106 | $left_base = [$left_base] unless ref $left_base eq 'ARRAY'; |
3385ac62 |
107 | |
e26a4023 |
108 | my %load_classes_args = ( |
3385ac62 |
109 | additional => $additional, |
110 | additional_base => $additional_base, |
111 | left_base => $left_base, |
112 | constraint => $args{constraint} || '.*', |
113 | exclude => $args{exclude}, |
e26a4023 |
114 | ); |
115 | |
116 | $class->connection($args{dsn}, $args{user}, |
117 | $args{password}, $args{options}); |
118 | |
3385ac62 |
119 | warn qq/\### START DBIx::Class::Schema::Loader dump ###\n/ |
120 | if $class->_loader_debug; |
e26a4023 |
121 | |
122 | $class->_loader_load_classes(%load_classes_args); |
66742793 |
123 | $class->_loader_relationships if $args{relationships}; |
e26a4023 |
124 | |
3385ac62 |
125 | warn qq/\### END DBIx::Class::Schema::Loader dump ###\n/ |
126 | if $class->_loader_debug; |
a4a19f3c |
127 | $class->storage->dbh->disconnect; # XXX this should be ->storage->disconnect later? |
128 | |
129 | 1; |
a78e3fed |
130 | } |
131 | |
af6c2665 |
132 | # The original table class name during Loader, |
3385ac62 |
133 | sub _loader_find_table_class { |
a4a19f3c |
134 | my ( $class, $table ) = @_; |
e26a4023 |
135 | return $class->_loader_classes->{$table}; |
a78e3fed |
136 | } |
137 | |
af6c2665 |
138 | # Returns the moniker for a given table name, |
139 | # for use in $conn->resultset($moniker) |
fbd83464 |
140 | |
141 | =head3 moniker |
142 | |
143 | Returns the moniker for a given literal table name. Used |
144 | as $schema->resultset($moniker), etc. |
145 | |
146 | =cut |
af6c2665 |
147 | sub moniker { |
a4a19f3c |
148 | my ( $class, $table ) = @_; |
e26a4023 |
149 | return $class->_loader_monikers->{$table}; |
a78e3fed |
150 | } |
151 | |
a78e3fed |
152 | =head3 tables |
153 | |
154 | Returns a sorted list of tables. |
155 | |
156 | my @tables = $loader->tables; |
157 | |
158 | =cut |
159 | |
160 | sub tables { |
a4a19f3c |
161 | my $class = shift; |
e26a4023 |
162 | return sort keys %{ $class->_loader_monikers }; |
a78e3fed |
163 | } |
164 | |
165 | # Overload in your driver class |
3385ac62 |
166 | sub _loader_db_classes { croak "ABSTRACT METHOD" } |
a78e3fed |
167 | |
66742793 |
168 | # not a class method. |
169 | sub _loader_stringify_hash { |
170 | my $href = shift; |
171 | |
172 | return '{ ' . |
173 | join(q{, }, map("$_ => $href->{$_}", keys %$href)) |
174 | . ' }'; |
175 | } |
176 | |
a78e3fed |
177 | # Setup has_a and has_many relationships |
3385ac62 |
178 | sub _loader_make_relations { |
708c0939 |
179 | |
180 | my ( $class, $table, $other, $cond ) = @_; |
3385ac62 |
181 | my $table_class = $class->_loader_find_table_class($table); |
182 | my $other_class = $class->_loader_find_table_class($other); |
a78e3fed |
183 | |
708c0939 |
184 | my $table_relname = lc $table; |
185 | my $other_relname = lc $other; |
a78e3fed |
186 | |
e26a4023 |
187 | if(my $inflections = $class->_loader_inflect) { |
708c0939 |
188 | $table_relname = $inflections->{$table_relname} |
189 | if exists $inflections->{$table_relname}; |
a78e3fed |
190 | } |
191 | else { |
708c0939 |
192 | $table_relname = Lingua::EN::Inflect::PL($table_relname); |
193 | } |
194 | |
195 | # for single-column case, set the relname to the column name, |
196 | # to make filter accessors work |
197 | if(scalar keys %$cond == 1) { |
198 | my ($col) = keys %$cond; |
199 | $other_relname = $cond->{$col}; |
a78e3fed |
200 | } |
201 | |
708c0939 |
202 | my $rev_cond = { reverse %$cond }; |
203 | |
66742793 |
204 | my $cond_printable = _loader_stringify_hash($cond) |
205 | if $class->_loader_debug; |
206 | my $rev_cond_printable = _loader_stringify_hash($rev_cond) |
207 | if $class->_loader_debug; |
208 | |
3385ac62 |
209 | warn qq/\# Belongs_to relationship\n/ if $class->_loader_debug; |
708c0939 |
210 | |
211 | warn qq/$table_class->belongs_to( '$other_relname' => '$other_class',/ |
66742793 |
212 | . qq/$cond_printable);\n\n/ |
3385ac62 |
213 | if $class->_loader_debug; |
708c0939 |
214 | |
215 | $table_class->belongs_to( $other_relname => $other_class, $cond); |
a78e3fed |
216 | |
3385ac62 |
217 | warn qq/\# Has_many relationship\n/ if $class->_loader_debug; |
a78e3fed |
218 | |
708c0939 |
219 | warn qq/$other_class->has_many( '$table_relname' => '$table_class',/ |
66742793 |
220 | . qq/$rev_cond_printable);\n\n/ |
708c0939 |
221 | . qq/);\n\n/ |
3385ac62 |
222 | if $class->_loader_debug; |
708c0939 |
223 | |
224 | $other_class->has_many( $table_relname => $table_class, $rev_cond); |
a78e3fed |
225 | } |
226 | |
227 | # Load and setup classes |
3385ac62 |
228 | sub _loader_load_classes { |
e26a4023 |
229 | my ($class, %args) = @_; |
230 | |
231 | my $additional = join '', |
232 | map "use $_;\n", @{$args{additional}}; |
af6c2665 |
233 | |
3385ac62 |
234 | my @tables = $class->_loader_tables(); |
235 | my @db_classes = $class->_loader_db_classes(); |
a78e3fed |
236 | |
a78e3fed |
237 | foreach my $table (@tables) { |
e26a4023 |
238 | next unless $table =~ /$args{constraint}/; |
239 | next if defined $args{exclude} && $table =~ /$args{exclude}/; |
af6c2665 |
240 | |
af6c2665 |
241 | my ($db_schema, $tbl) = split /\./, $table; |
af96f52e |
242 | my $tablename = lc $table; |
a78e3fed |
243 | if($tbl) { |
e26a4023 |
244 | $tablename = $class->_loader_drop_db_schema ? $tbl : lc $table; |
af6c2665 |
245 | } |
e26a4023 |
246 | my $lc_tblname = lc $tablename; |
af6c2665 |
247 | |
3385ac62 |
248 | my $table_moniker = $class->_loader_table2moniker($db_schema, $tbl); |
65644119 |
249 | my $table_class = "$class\::$table_moniker"; |
af6c2665 |
250 | |
e26a4023 |
251 | # XXX all of this needs require/eval error checking |
a4a19f3c |
252 | $class->inject_base( $table_class, 'DBIx::Class::Core' ); |
a78e3fed |
253 | $_->require for @db_classes; |
a4a19f3c |
254 | $class->inject_base( $table_class, $_ ) for @db_classes; |
e26a4023 |
255 | $class->inject_base( $table_class, $_ ) for @{$args{additional_base}}; |
256 | eval "package $table_class;$_;" for @{$args{additional}}; |
257 | $class->inject_base( $table_class, $_ ) for @{$args{left_base}}; |
258 | |
3385ac62 |
259 | warn qq/\# Initializing table "$tablename" as "$table_class"\n/ if $class->_loader_debug; |
e26a4023 |
260 | $table_class->table($lc_tblname); |
af6c2665 |
261 | |
3385ac62 |
262 | my ( $cols, $pks ) = $class->_loader_table_info($table); |
a78e3fed |
263 | carp("$table has no primary key") unless @$pks; |
a4a19f3c |
264 | $table_class->add_columns(@$cols); |
265 | $table_class->set_primary_key(@$pks) if @$pks; |
af6c2665 |
266 | |
3385ac62 |
267 | warn qq/$table_class->table('$tablename');\n/ if $class->_loader_debug; |
a78e3fed |
268 | my $columns = join "', '", @$cols; |
3385ac62 |
269 | warn qq/$table_class->add_columns('$columns')\n/ if $class->_loader_debug; |
a78e3fed |
270 | my $primaries = join "', '", @$pks; |
3385ac62 |
271 | warn qq/$table_class->set_primary_key('$primaries')\n/ if $class->_loader_debug && @$pks; |
af6c2665 |
272 | |
65644119 |
273 | $class->register_class($table_moniker, $table_class); |
e26a4023 |
274 | $class->_loader_classes->{$lc_tblname} = $table_class; |
275 | $class->_loader_monikers->{$lc_tblname} = $table_moniker; |
a78e3fed |
276 | } |
277 | } |
278 | |
279 | # Find and setup relationships |
3385ac62 |
280 | sub _loader_relationships { |
a4a19f3c |
281 | my $class = shift; |
282 | my $dbh = $class->storage->dbh; |
708c0939 |
283 | my $quoter = $dbh->get_info(29) || q{"}; |
a4a19f3c |
284 | foreach my $table ( $class->tables ) { |
708c0939 |
285 | my $rels = {}; |
286 | my $sth = $dbh->foreign_key_info( '', |
e26a4023 |
287 | $class->_loader_db_schema, '', '', '', $table ); |
708c0939 |
288 | next if !$sth; |
289 | while(my $raw_rel = $sth->fetchrow_hashref) { |
290 | my $uk_tbl = lc $raw_rel->{UK_TABLE_NAME}; |
291 | my $uk_col = lc $raw_rel->{UK_COLUMN_NAME}; |
292 | my $fk_col = lc $raw_rel->{FK_COLUMN_NAME}; |
293 | $uk_tbl =~ s/$quoter//g; |
294 | $uk_col =~ s/$quoter//g; |
295 | $fk_col =~ s/$quoter//g; |
296 | $rels->{$uk_tbl}->{$uk_col} = $fk_col; |
297 | } |
298 | |
299 | foreach my $reltbl (keys %$rels) { |
300 | my $cond = $rels->{$reltbl}; |
3385ac62 |
301 | eval { $class->_loader_make_relations( $table, $reltbl, $cond ) }; |
708c0939 |
302 | warn qq/\# belongs_to_many failed "$@"\n\n/ |
3385ac62 |
303 | if $@ && $class->_loader_debug; |
a78e3fed |
304 | } |
305 | } |
306 | } |
307 | |
65644119 |
308 | # Make a moniker from a table |
3385ac62 |
309 | sub _loader_table2moniker { |
a4a19f3c |
310 | my ( $class, $db_schema, $table ) = @_; |
af6c2665 |
311 | |
af96f52e |
312 | my $db_schema_ns; |
af6c2665 |
313 | |
af96f52e |
314 | if($table) { |
315 | $db_schema = ucfirst lc $db_schema; |
e26a4023 |
316 | $db_schema_ns = $db_schema if(!$class->_loader_drop_db_schema); |
af96f52e |
317 | } else { |
318 | $table = $db_schema; |
a78e3fed |
319 | } |
af6c2665 |
320 | |
65644119 |
321 | my $moniker = join '', map ucfirst, split /[\W_]+/, lc $table; |
322 | $moniker = $db_schema_ns ? $db_schema_ns . $moniker : $moniker; |
af96f52e |
323 | |
65644119 |
324 | return $moniker; |
a78e3fed |
325 | } |
326 | |
327 | # Overload in driver class |
3385ac62 |
328 | sub _loader_tables { croak "ABSTRACT METHOD" } |
a78e3fed |
329 | |
3385ac62 |
330 | sub _loader_table_info { croak "ABSTRACT METHOD" } |
a78e3fed |
331 | |
332 | =head1 SEE ALSO |
333 | |
18fca96a |
334 | L<DBIx::Class::Schema::Loader> |
a78e3fed |
335 | |
336 | =cut |
337 | |
338 | 1; |