add recipes for schema import/export + versioning
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Manual / Cookbook.pod
CommitLineData
3b44ccc6 1=head1 NAME
9c82c181 2
40dbc108 3DBIx::Class::Manual::Cookbook - Miscellaneous recipes
ee38fa40 4
40dbc108 5=head1 RECIPES
2913b2d3 6
bade79c4 7=head2 Searching
faf62551 8
bade79c4 9=head3 Paged results
faf62551 10
bade79c4 11When you expect a large number of results, you can ask L<DBIx::Class> for a
12paged resultset, which will fetch only a small number of records at a time:
faf62551 13
bade79c4 14 my $rs = $schema->resultset('Artist')->search(
15 {},
16 {
17 page => 1, # page to return (defaults to 1)
18 rows => 10, # number of results per page
19 },
20 );
faf62551 21
bade79c4 22 return $rs->all(); # all records for page 1
faf62551 23
bade79c4 24The C<page> attribute does not have to be specified in your search:
25
26 my $rs = $schema->resultset('Artist')->search(
27 {},
28 {
29 rows => 10,
30 }
31 );
faf62551 32
bade79c4 33 return $rs->page(1); # DBIx::Class::ResultSet containing first 10 records
faf62551 34
35In either of the above cases, you can return a L<Data::Page> object for the
bade79c4 36resultset (suitable for use in e.g. a template) using the C<pager> method:
faf62551 37
bade79c4 38 return $rs->pager();
faf62551 39
bade79c4 40=head3 Complex WHERE clauses
2913b2d3 41
40dbc108 42Sometimes you need to formulate a query using specific operators:
43
ea6309e2 44 my @albums = $schema->resultset('Album')->search({
35d4fe78 45 artist => { 'like', '%Lamb%' },
46 title => { 'like', '%Fear of Fours%' },
47 });
40dbc108 48
49This results in something like the following C<WHERE> clause:
50
35d4fe78 51 WHERE artist LIKE '%Lamb%' AND title LIKE '%Fear of Fours%'
40dbc108 52
53Other queries might require slightly more complex logic:
54
ea6309e2 55 my @albums = $schema->resultset('Album')->search({
35d4fe78 56 -or => [
57 -and => [
58 artist => { 'like', '%Smashing Pumpkins%' },
59 title => 'Siamese Dream',
60 ],
61 artist => 'Starchildren',
62 ],
63 });
40dbc108 64
65This results in the following C<WHERE> clause:
66
35d4fe78 67 WHERE ( artist LIKE '%Smashing Pumpkins%' AND title = 'Siamese Dream' )
68 OR artist = 'Starchildren'
40dbc108 69
70For more information on generating complex queries, see
71L<SQL::Abstract/WHERE CLAUSES>.
ee38fa40 72
bade79c4 73=head3 Using specific columns
faf62551 74
bade79c4 75When you only want selected columns from a table, you can use C<cols> to
76specify which ones you need:
faf62551 77
bade79c4 78 my $rs = $schema->resultset('Artist')->search(
79 {},
80 {
81 cols => [qw/ name /]
82 }
83 );
faf62551 84
bade79c4 85 # Equivalent SQL:
86 # SELECT artist.name FROM artist
faf62551 87
bade79c4 88=head3 Using database functions or stored procedures
faf62551 89
bade79c4 90The combination of C<select> and C<as> can be used to return the result of a
91database function or stored procedure as a column value. You use C<select> to
92specify the source for your column value (e.g. a column name, function, or
93stored procedure name). You then use C<as> to set the column name you will use
94to access the returned value:
faf62551 95
bade79c4 96 my $rs = $schema->resultset('Artist')->search(
97 {},
98 {
99 select => [ 'name', { LENGTH => 'name' } ],
100 as => [qw/ name name_length /],
101 }
102 );
faf62551 103
bade79c4 104 # Equivalent SQL:
105 # SELECT name name, LENGTH( name ) name_length
106 # FROM artist
faf62551 107
108If your alias exists as a column in your base class (i.e. it was added with
bade79c4 109C<add_columns>), you just access it as normal. Our C<Artist> class has a C<name>
110column, so we just use the C<name> accessor:
faf62551 111
bade79c4 112 my $artist = $rs->first();
113 my $name = $artist->name();
faf62551 114
115If on the other hand the alias does not correspond to an existing column, you
bade79c4 116can get the value using the C<get_column> accessor:
faf62551 117
bade79c4 118 my $name_length = $artist->get_column('name_length');
faf62551 119
bade79c4 120If you don't like using C<get_column>, you can always create an accessor for
faf62551 121any of your aliases using either of these:
122
bade79c4 123 # Define accessor manually:
124 sub name_length { shift->get_column('name_length'); }
faf62551 125
bade79c4 126 # Or use DBIx::Class::AccessorGroup:
127 __PACKAGE__->mk_group_accessors('column' => 'name_length');
faf62551 128
bade79c4 129=head3 SELECT DISTINCT with multiple columns
faf62551 130
bade79c4 131 my $rs = $schema->resultset('Foo')->search(
132 {},
133 {
134 select => [
135 { distinct => [ $source->columns ] }
136 ],
137 as => [ $source->columns ]
138 }
139 );
faf62551 140
bade79c4 141=head3 SELECT COUNT(DISTINCT colname)
6607ee1b 142
bade79c4 143 my $rs = $schema->resultset('Foo')->search(
144 {},
145 {
146 select => [
147 { count => { distinct => 'colname' } }
148 ],
149 as => [ 'count' ]
150 }
151 );
6607ee1b 152
bade79c4 153=head3 Grouping results
154
155L<DBIx::Class> supports C<GROUP BY> as follows:
156
157 my $rs = $schema->resultset('Artist')->search(
158 {},
159 {
160 join => [qw/ cds /],
161 select => [ 'name', { count => 'cds.cdid' } ],
162 as => [qw/ name cd_count /],
163 group_by => [qw/ name /]
164 }
165 );
6607ee1b 166
bade79c4 167 # Equivalent SQL:
168 # SELECT name, COUNT( cds.cdid ) FROM artist me
169 # LEFT JOIN cd cds ON ( cds.artist = me.artistid )
170 # GROUP BY name
6607ee1b 171
87980de7 172=head2 Using joins and prefetch
173
bade79c4 174You can use the C<join> attribute to allow searching on, or sorting your
175results by, one or more columns in a related table. To return all CDs matching
176a particular artist name:
ea6309e2 177
bade79c4 178 my $rs = $schema->resultset('CD')->search(
179 {
180 'artist.name' => 'Bob Marley'
181 },
182 {
183 join => [qw/artist/], # join the artist table
184 }
185 );
186
187 # Equivalent SQL:
188 # SELECT cd.* FROM cd
189 # JOIN artist ON cd.artist = artist.id
190 # WHERE artist.name = 'Bob Marley'
191
192If required, you can now sort on any column in the related tables by including
193it in your C<order_by> attribute:
194
195 my $rs = $schema->resultset('CD')->search(
196 {
197 'artist.name' => 'Bob Marley'
198 },
199 {
200 join => [qw/ artist /],
201 order_by => [qw/ artist.name /]
202 }
203 };
ea6309e2 204
bade79c4 205 # Equivalent SQL:
206 # SELECT cd.* FROM cd
207 # JOIN artist ON cd.artist = artist.id
208 # WHERE artist.name = 'Bob Marley'
209 # ORDER BY artist.name
ea6309e2 210
bade79c4 211Note that the C<join> attribute should only be used when you need to search or
212sort using columns in a related table. Joining related tables when you only
213need columns from the main table will make performance worse!
ea6309e2 214
bade79c4 215Now let's say you want to display a list of CDs, each with the name of the
216artist. The following will work fine:
ea6309e2 217
bade79c4 218 while (my $cd = $rs->next) {
219 print "CD: " . $cd->title . ", Artist: " . $cd->artist->name;
220 }
ea6309e2 221
bade79c4 222There is a problem however. We have searched both the C<cd> and C<artist> tables
223in our main query, but we have only returned data from the C<cd> table. To get
224the artist name for any of the CD objects returned, L<DBIx::Class> will go back
225to the database:
ea6309e2 226
bade79c4 227 SELECT artist.* FROM artist WHERE artist.id = ?
ea6309e2 228
229A statement like the one above will run for each and every CD returned by our
230main query. Five CDs, five extra queries. A hundred CDs, one hundred extra
231queries!
232
bade79c4 233Thankfully, L<DBIx::Class> has a C<prefetch> attribute to solve this problem.
234This allows you to fetch results from a related table as well as the main table
ea6309e2 235for your class:
236
bade79c4 237 my $rs = $schema->resultset('CD')->search(
238 {
239 'artist.name' => 'Bob Marley'
240 },
241 {
242 join => [qw/ artist /],
243 order_by => [qw/ artist.name /],
244 prefetch => [qw/ artist /] # return artist data too!
245 }
246 );
ea6309e2 247
bade79c4 248 # Equivalent SQL (note SELECT from both "cd" and "artist"):
249 # SELECT cd.*, artist.* FROM cd
250 # JOIN artist ON cd.artist = artist.id
251 # WHERE artist.name = 'Bob Marley'
252 # ORDER BY artist.name
ea6309e2 253
254The code to print the CD list remains the same:
255
bade79c4 256 while (my $cd = $rs->next) {
257 print "CD: " . $cd->title . ", Artist: " . $cd->artist->name;
258 }
ea6309e2 259
bade79c4 260L<DBIx::Class> has now prefetched all matching data from the C<artist> table,
ea6309e2 261so no additional SQL statements are executed. You now have a much more
262efficient query.
263
bade79c4 264Note that as of L<DBIx::Class> 0.04, C<prefetch> cannot be used with
265C<has_many> relationships. You will get an error along the lines of "No
266accessor for prefetched ..." if you try.
ea6309e2 267
bade79c4 268Also note that C<prefetch> should only be used when you know you will
ea6309e2 269definitely use data from a related table. Pre-fetching related tables when you
270only need columns from the main table will make performance worse!
271
bade79c4 272=head3 Multi-step joins
ea6309e2 273
274Sometimes you want to join more than one relationship deep. In this example,
bade79c4 275we want to find all C<Artist> objects who have C<CD>s whose C<LinerNotes>
276contain a specific string:
277
278 # Relationships defined elsewhere:
279 # Artist->has_many('cds' => 'CD', 'artist');
280 # CD->has_one('liner_notes' => 'LinerNotes', 'cd');
281
282 my $rs = $schema->resultset('Artist')->search(
283 {
284 'liner_notes.notes' => { 'like', '%some text%' },
285 },
286 {
287 join => {
288 'cds' => 'liner_notes'
289 }
290 }
291 );
ea6309e2 292
bade79c4 293 # Equivalent SQL:
294 # SELECT artist.* FROM artist
295 # JOIN ( cd ON artist.id = cd.artist )
296 # JOIN ( liner_notes ON cd.id = liner_notes.cd )
297 # WHERE liner_notes.notes LIKE '%some text%'
ea6309e2 298
299Joins can be nested to an arbitrary level. So if we decide later that we
300want to reduce the number of Artists returned based on who wrote the liner
301notes:
302
bade79c4 303 # Relationship defined elsewhere:
304 # LinerNotes->belongs_to('author' => 'Person');
305
306 my $rs = $schema->resultset('Artist')->search(
307 {
308 'liner_notes.notes' => { 'like', '%some text%' },
309 'author.name' => 'A. Writer'
310 },
311 {
312 join => {
313 'cds' => {
314 'liner_notes' => 'author'
ea6309e2 315 }
bade79c4 316 }
317 }
318 );
ea6309e2 319
bade79c4 320 # Equivalent SQL:
321 # SELECT artist.* FROM artist
322 # JOIN ( cd ON artist.id = cd.artist )
323 # JOIN ( liner_notes ON cd.id = liner_notes.cd )
324 # JOIN ( author ON author.id = liner_notes.author )
325 # WHERE liner_notes.notes LIKE '%some text%'
326 # AND author.name = 'A. Writer'
87980de7 327
328=head2 Transactions
329
330As of version 0.04001, there is improved transaction support in
35d4fe78 331L<DBIx::Class::Storage::DBI>. Here is an example of the recommended
40dbc108 332way to use it:
87980de7 333
35d4fe78 334 my $genus = Genus->find(12);
335 eval {
336 MyDB->txn_begin;
337 $genus->add_to_species({ name => 'troglodyte' });
338 $genus->wings(2);
339 $genus->update;
340 cromulate($genus); # Can have a nested transation
341 MyDB->txn_commit;
342 };
343 if ($@) {
344 # Rollback might fail, too
87980de7 345 eval {
35d4fe78 346 MyDB->txn_rollback
87980de7 347 };
35d4fe78 348 }
87980de7 349
40dbc108 350Currently, a nested commit will do nothing and a nested rollback will
35d4fe78 351die. The code at each level must be sure to call rollback in the case
40dbc108 352of an error, to ensure that the rollback will propagate to the top
35d4fe78 353level and be issued. Support for savepoints and for true nested
40dbc108 354transactions (for databases that support them) will hopefully be added
355in the future.
ee38fa40 356
130c6439 357=head2 Many-to-many relationships
ee38fa40 358
ea6309e2 359This is straightforward using L<DBIx::Class::Relationship::ManyToMany>:
360
bade79c4 361 package My::DB;
362 # ... set up connection ...
363
364 package My::User;
365 use base 'My::DB';
366 __PACKAGE__->table('user');
367 __PACKAGE__->add_columns(qw/id name/);
368 __PACKAGE__->set_primary_key('id');
369 __PACKAGE__->has_many('user_address' => 'My::UserAddress', 'user');
370 __PACKAGE__->many_to_many('addresses' => 'user_address', 'address');
371
372 package My::UserAddress;
373 use base 'My::DB';
374 __PACKAGE__->table('user_address');
375 __PACKAGE__->add_columns(qw/user address/);
376 __PACKAGE__->set_primary_key(qw/user address/);
377 __PACKAGE__->belongs_to('user' => 'My::User');
378 __PACKAGE__->belongs_to('address' => 'My::Address');
379
380 package My::Address;
381 use base 'My::DB';
382 __PACKAGE__->table('address');
383 __PACKAGE__->add_columns(qw/id street town area_code country/);
384 __PACKAGE__->set_primary_key('id');
385 __PACKAGE__->has_many('user_address' => 'My::UserAddress', 'address');
386 __PACKAGE__->many_to_many('users' => 'user_address', 'user');
387
388 $rs = $user->addresses(); # get all addresses for a user
389 $rs = $address->users(); # get all users for an address
390
391=head2 Setting default values for a row
a00e1684 392
35d4fe78 393It's as simple as overriding the C<new> method. Note the use of
40dbc108 394C<next::method>.
a00e1684 395
35d4fe78 396 sub new {
397 my ( $class, $attrs ) = @_;
40dbc108 398
35d4fe78 399 $attrs->{foo} = 'bar' unless defined $attrs->{foo};
40dbc108 400
35d4fe78 401 $class->next::method($attrs);
402 }
a00e1684 403
25af00d7 404=head2 Stringification
405
40dbc108 406Employ the standard stringification technique by using the C<overload>
35d4fe78 407module. Replace C<foo> with the column/method of your choice.
25af00d7 408
35d4fe78 409 use overload '""' => 'foo', fallback => 1;
25af00d7 410
bade79c4 411=head2 Disconnecting cleanly
412
413If you find yourself quitting an app with Control-C a lot during
414development, you might like to put the following signal handler in
415your main database class to make sure it disconnects cleanly:
416
417 $SIG{INT} = sub {
418 __PACKAGE__->storage->dbh->disconnect;
419 };
420
362500af 421=head2 Schema import/export
422
423This functionality requires you to have L<SQL::Translator> (also known as
424"SQL Fairy") installed.
425
426To create a DBIx::Class schema from an existing database:
427
428 sqlt --from DBI
429 --to DBIx::Class::File
430 --prefix "MySchema" > MySchema.pm
431
432To create a MySQL database from an existing L<DBIx::Class> schema, convert the
433schema to MySQL's dialect of SQL:
434
435 sqlt --from DBIx::Class --to MySQL --DBIx::Class "MySchema.pm" > Schema1.sql
436
437And import using the mysql client:
438
439 mysql -h "host" -D "database" -u "user" -p < Schema1.sql
440
441=head2 Schema versioning
442
443The following example shows simplistically how you might use DBIx::Class to
444deploy versioned schemas to your customers. The basic process is as follows:
445
4461) Create a DBIx::Class schema
4472) Save the schema
4483) Deploy to customers
4494) Modify schema to change functionality
4505) Deploy update to customers
451
452=head3 Create a DBIx::Class schema
453
454This can either be done manually, or generated from an existing database as
455described under C<Schema import/export>.
456
457=head3 Save the schema
458
459Use C<sqlt> to transform your schema into an SQL script suitable for your
460customer's database. E.g. for MySQL:
461
462 sqlt --from DBIx::Class
463 --to MySQL
464 --DBIx::Class "MySchema.pm" > Schema1.mysql.sql
465
466If you need to target databases from multiple vendors, just generate an SQL
467script suitable for each. To support PostgreSQL too:
468
469 sqlt --from DBIx::Class
470 --to PostgreSQL
471 --DBIx::Class "MySchema.pm" > Schema1.pgsql.sql
472
473=head3 Deploy to customers
474
475There are several ways you could deploy your schema. These are probably
476beyond the scope of this recipe, but might include:
477
4781) Require customer to apply manually using their RDBMS.
4792) Package along with your app, making database dump/schema update/tests
480all part of your install.
481
482=head3 Modify the schema to change functionality
483
484As your application evolves, it may be necessary to modify your schema to
485change functionality. Once the changes are made to your schema in DBIx::Class,
486export the modified schema as before, taking care not to overwrite the original:
487
488 sqlt --from DBIx::Class
489 --to MySQL
490 --DBIx::Class "Anything.pm" > Schema2.mysql.sql
491
492Next, use sqlt-diff to create an SQL script that will update the customer's
493database schema:
494
495 sqlt-diff --to MySQL Schema1=MySQL Schema2=MySQL > SchemaUpdate.mysql.sql
496
497=head3 Deploy update to customers
498
499The schema update can be deployed to customers using the same method as before.
500
40dbc108 501=cut