From: Daniel Westermann-Clark Date: Sat, 21 Jan 2006 20:07:06 +0000 (+0000) Subject: - Move search examples under a new =head2; additional examples go under a =head3 X-Git-Tag: v0.05005~117^2~23 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=bade79c4db3f32db885572fc8588301dbe7762ff;p=dbsrgits%2FDBIx-Class.git - Move search examples under a new =head2; additional examples go under a =head3 - Make indentation consistent with code - "blah" -> C --- diff --git a/lib/DBIx/Class/Manual/Cookbook.pod b/lib/DBIx/Class/Manual/Cookbook.pod index 9bdce36..6acd286 100644 --- a/lib/DBIx/Class/Manual/Cookbook.pod +++ b/lib/DBIx/Class/Manual/Cookbook.pod @@ -4,38 +4,40 @@ DBIx::Class::Manual::Cookbook - Miscellaneous recipes =head1 RECIPES -=head2 Paged results +=head2 Searching -When you expect a large number of results, you can ask DBIx::Class for a paged -resultset, which will fetch only a small number of records at a time: +=head3 Paged results - $rs = $schema->resultset('Artist')->search( - {}, - { - page => 1, # page to return (defaults to 1) - rows => 10, # number of results per page - }, - ); +When you expect a large number of results, you can ask L for a +paged resultset, which will fetch only a small number of records at a time: - $rs->all(); # return all records for page 1 + my $rs = $schema->resultset('Artist')->search( + {}, + { + page => 1, # page to return (defaults to 1) + rows => 10, # number of results per page + }, + ); -The "page" attribute does not have to be specified in your search: + return $rs->all(); # all records for page 1 - $rs = $schema->resultset('Artist')->search( - {}, - { - rows => 10, - } - ); +The C attribute does not have to be specified in your search: + + my $rs = $schema->resultset('Artist')->search( + {}, + { + rows => 10, + } + ); - $rs->page(1); # return DBIx::Class::ResultSet containing first 10 records + return $rs->page(1); # DBIx::Class::ResultSet containing first 10 records In either of the above cases, you can return a L object for the -resultset (suitable for use in a TT template etc) using the pager() method: +resultset (suitable for use in e.g. a template) using the C method: - $pager = $rs->pager(); + return $rs->pager(); -=head2 Complex searches +=head3 Complex WHERE clauses Sometimes you need to formulate a query using specific operators: @@ -68,269 +70,260 @@ This results in the following C clause: For more information on generating complex queries, see L. -=head2 Disconnecting cleanly - -If you find yourself quitting an app with Control-C a lot during -development, you might like to put the following signal handler in -your main database class to make sure it disconnects cleanly: - - $SIG{INT} = sub { - __PACKAGE__->storage->dbh->disconnect; - }; - -=head2 Using cols +=head3 Using specific columns -When you only want selected columns from a table, you can use "cols" to -specify which ones you need (you could also use "select", but "cols" is the -recommended way): +When you only want selected columns from a table, you can use C to +specify which ones you need: - $rs = $schema->resultset('Artist')->search( - {}, - { - cols => [qw/ name /] - } - ); + my $rs = $schema->resultset('Artist')->search( + {}, + { + cols => [qw/ name /] + } + ); - # e.g. - # SELECT artist.name FROM artist + # Equivalent SQL: + # SELECT artist.name FROM artist -=head2 Using select and as +=head3 Using database functions or stored procedures -The combination of "select" and "as" is probably most useful when you want to -return the result of a function or stored procedure as a column value. You use -"select" to specify the source for your column value (e.g. a column name, -function or stored procedure name). You then use "as" to set the column name -you will use to access the returned value: +The combination of C to +specify the source for your column value (e.g. a column name, function, or +stored procedure name). You then use C to set the column name you will use +to access the returned value: - $rs = $schema->resultset('Artist')->search( - {}, - { - select => [ 'name', { LENGTH => 'name' } ], - as => [qw/ name name_length /], - } - ); + my $rs = $schema->resultset('Artist')->search( + {}, + { + select => [ 'name', { LENGTH => 'name' } ], + as => [qw/ name name_length /], + } + ); - # e.g. - # SELECT name name, LENGTH( name ) name_length - # FROM artist + # Equivalent SQL: + # SELECT name name, LENGTH( name ) name_length + # FROM artist If your alias exists as a column in your base class (i.e. it was added with -add_columns()), you just access it as normal. Our Artist class has a "name" -column, so we just use the "name" accessor: +C), you just access it as normal. Our C class has a C +column, so we just use the C accessor: - my $artist = $rs->first(); - my $name = $artist->name(); + my $artist = $rs->first(); + my $name = $artist->name(); If on the other hand the alias does not correspond to an existing column, you -can get the value using the get_column() accessor: +can get the value using the C accessor: - my $name_length = $artist->get_column('name_length'); + my $name_length = $artist->get_column('name_length'); -If you don't like using "get_column()", you can always create an accessor for +If you don't like using C, you can always create an accessor for any of your aliases using either of these: - # define accessor manually - sub name_length { shift->get_column('name_length'); } + # Define accessor manually: + sub name_length { shift->get_column('name_length'); } - # or use DBIx::Class::AccessorGroup - __PACKAGE__->mk_group_accessors('column' => 'name_length'); - -=head2 SELECT DISTINCT with multiple columns - - $rs = $schema->resultset('Foo')->search( - {}, - { - select => [ - { distinct => [ $source->columns ] } - ], - as => [ $source->columns ] - } - ); + # Or use DBIx::Class::AccessorGroup: + __PACKAGE__->mk_group_accessors('column' => 'name_length'); -=head2 SELECT COUNT(DISTINCT colname) +=head3 SELECT DISTINCT with multiple columns - $rs = $schema->resultset('Foo')->search( - {}, - { - select => [ - { count => { distinct => 'colname' } } - ], - as => [ 'count' ] - } - ); + my $rs = $schema->resultset('Foo')->search( + {}, + { + select => [ + { distinct => [ $source->columns ] } + ], + as => [ $source->columns ] + } + ); -=head2 Grouping results +=head3 SELECT COUNT(DISTINCT colname) -DBIx::Class supports GROUP BY as follows: + my $rs = $schema->resultset('Foo')->search( + {}, + { + select => [ + { count => { distinct => 'colname' } } + ], + as => [ 'count' ] + } + ); - $rs = $schema->resultset('Artist')->search( - {}, - { - join => [qw/ cds /], - select => [ 'name', { count => 'cds.cdid' } ], - as => [qw/ name cd_count /], - group_by => [qw/ name /] - } - ); +=head3 Grouping results + +L supports C as follows: + + my $rs = $schema->resultset('Artist')->search( + {}, + { + join => [qw/ cds /], + select => [ 'name', { count => 'cds.cdid' } ], + as => [qw/ name cd_count /], + group_by => [qw/ name /] + } + ); - # e.g. - # SELECT name, COUNT( cds.cdid ) FROM artist me - # LEFT JOIN cd cds ON ( cds.artist = me.artistid ) - # GROUP BY name + # Equivalent SQL: + # SELECT name, COUNT( cds.cdid ) FROM artist me + # LEFT JOIN cd cds ON ( cds.artist = me.artistid ) + # GROUP BY name =head2 Using joins and prefetch -You can use the "join" attribute to allow searching on, or sorting your -results by, one or more columns in a related table. To return -all CDs matching a particular artist name: +You can use the C attribute to allow searching on, or sorting your +results by, one or more columns in a related table. To return all CDs matching +a particular artist name: - my $rs = $schema->resultset('CD')->search( - { - 'artist.name' => 'Bob Marley' - }, - { - join => [qw/artist/], # join the artist table - } - ); - - # equivalent SQL: - # SELECT cd.* FROM cd - # JOIN artist ON cd.artist = artist.id - # WHERE artist.name = 'Bob Marley' - -If required, you can now sort on any column in the related table(s) by -including it in your "order_by" attribute: - - my $rs = $schema->resultset('CD')->search( - { - 'artist.name' => 'Bob Marley' - }, - { - join => [qw/ artist /], - order_by => [qw/ artist.name /] - } - }; + my $rs = $schema->resultset('CD')->search( + { + 'artist.name' => 'Bob Marley' + }, + { + join => [qw/artist/], # join the artist table + } + ); + + # Equivalent SQL: + # SELECT cd.* FROM cd + # JOIN artist ON cd.artist = artist.id + # WHERE artist.name = 'Bob Marley' + +If required, you can now sort on any column in the related tables by including +it in your C attribute: + + my $rs = $schema->resultset('CD')->search( + { + 'artist.name' => 'Bob Marley' + }, + { + join => [qw/ artist /], + order_by => [qw/ artist.name /] + } + }; - # equivalent SQL: - # SELECT cd.* FROM cd - # JOIN artist ON cd.artist = artist.id - # WHERE artist.name = 'Bob Marley' - # ORDER BY artist.name + # Equivalent SQL: + # SELECT cd.* FROM cd + # JOIN artist ON cd.artist = artist.id + # WHERE artist.name = 'Bob Marley' + # ORDER BY artist.name -Note that the "join" attribute should only be used when you need to search or -sort using columns in a related table. Joining related tables when you -only need columns from the main table will make performance worse! +Note that the C attribute should only be used when you need to search or +sort using columns in a related table. Joining related tables when you only +need columns from the main table will make performance worse! -Now let's say you want to display a list of CDs, each with the name of -the artist. The following will work fine: +Now let's say you want to display a list of CDs, each with the name of the +artist. The following will work fine: - while (my $cd = $rs->next) { - print "CD: " . $cd->title . ", Artist: " . $cd->artist->name; - } + while (my $cd = $rs->next) { + print "CD: " . $cd->title . ", Artist: " . $cd->artist->name; + } -There is a problem however. We have searched both cd and artist tables in our -main query, but we have only returned data from the cd table. To get the artist -name for any of the CD objects returned, DBIx::Class will go back to the -database: +There is a problem however. We have searched both the C and C tables +in our main query, but we have only returned data from the C table. To get +the artist name for any of the CD objects returned, L will go back +to the database: - SELECT artist.* FROM artist WHERE artist.id = ? + SELECT artist.* FROM artist WHERE artist.id = ? A statement like the one above will run for each and every CD returned by our main query. Five CDs, five extra queries. A hundred CDs, one hundred extra queries! -Thankfully, DBIx::Class has a "prefetch" attribute to solve this problem. This -allows you to fetch results from a related table as well as the main table +Thankfully, L has a C attribute to solve this problem. +This allows you to fetch results from a related table as well as the main table for your class: - my $rs = $schema->resultset('CD')->search( - { - 'artist.name' => 'Bob Marley' - }, - { - join => [qw/ artist /], - order_by => [qw/ artist.name /], - prefetch => [qw/ artist /] # return artist data too! - } - ); + my $rs = $schema->resultset('CD')->search( + { + 'artist.name' => 'Bob Marley' + }, + { + join => [qw/ artist /], + order_by => [qw/ artist.name /], + prefetch => [qw/ artist /] # return artist data too! + } + ); - # equivalent SQL (note SELECT from both "cd" and "artist") - # SELECT cd.*, artist.* FROM cd - # JOIN artist ON cd.artist = artist.id - # WHERE artist.name = 'Bob Marley' - # ORDER BY artist.name + # Equivalent SQL (note SELECT from both "cd" and "artist"): + # SELECT cd.*, artist.* FROM cd + # JOIN artist ON cd.artist = artist.id + # WHERE artist.name = 'Bob Marley' + # ORDER BY artist.name The code to print the CD list remains the same: - while (my $cd = $rs->next) { - print "CD: " . $cd->title . ", Artist: " . $cd->artist->name; - } + while (my $cd = $rs->next) { + print "CD: " . $cd->title . ", Artist: " . $cd->artist->name; + } -DBIx::Class has now prefetched all matching data from the "artist" table, +L has now prefetched all matching data from the C table, so no additional SQL statements are executed. You now have a much more efficient query. -Note that as of DBIx::Class 0.04, "prefetch" cannot be used with has_many -relationships. You will get an error along the lines of "No accessor for -prefetched ..." if you try. +Note that as of L 0.04, C cannot be used with +C relationships. You will get an error along the lines of "No +accessor for prefetched ..." if you try. -Note that "prefetch" should only be used when you know you will +Also note that C should only be used when you know you will definitely use data from a related table. Pre-fetching related tables when you only need columns from the main table will make performance worse! -=head2 Multi-step joins +=head3 Multi-step joins Sometimes you want to join more than one relationship deep. In this example, -we want to find all Artist objects who have CDs whose LinerNotes contain a -specific string: - - # Artist->has_many('cds' => 'CD', 'artist'); - # CD->has_one('liner_notes' => 'LinerNotes', 'cd'); - - $rs = $schema->resultset('Artist')->search( - { - 'liner_notes.notes' => { 'like', '%some text%' }, - }, - { - join => { - 'cds' => 'liner_notes' - } - } - ); +we want to find all C objects who have Cs whose C +contain a specific string: + + # Relationships defined elsewhere: + # Artist->has_many('cds' => 'CD', 'artist'); + # CD->has_one('liner_notes' => 'LinerNotes', 'cd'); + + my $rs = $schema->resultset('Artist')->search( + { + 'liner_notes.notes' => { 'like', '%some text%' }, + }, + { + join => { + 'cds' => 'liner_notes' + } + } + ); - # equivalent SQL - # SELECT artist.* FROM artist - # JOIN ( cd ON artist.id = cd.artist ) - # JOIN ( liner_notes ON cd.id = liner_notes.cd ) - # WHERE liner_notes.notes LIKE '%some text%' + # Equivalent SQL: + # SELECT artist.* FROM artist + # JOIN ( cd ON artist.id = cd.artist ) + # JOIN ( liner_notes ON cd.id = liner_notes.cd ) + # WHERE liner_notes.notes LIKE '%some text%' Joins can be nested to an arbitrary level. So if we decide later that we want to reduce the number of Artists returned based on who wrote the liner notes: - # LinerNotes->belongs_to('author' => 'Person'); - - $rs = $schema->resultset('Artist')->search( - { - 'liner_notes.notes' => { 'like', '%some text%' }, - 'author.name' => 'A. Writer' - }, - { - join => { - 'cds' => { - 'liner_notes' => 'author' - } - } + # Relationship defined elsewhere: + # LinerNotes->belongs_to('author' => 'Person'); + + my $rs = $schema->resultset('Artist')->search( + { + 'liner_notes.notes' => { 'like', '%some text%' }, + 'author.name' => 'A. Writer' + }, + { + join => { + 'cds' => { + 'liner_notes' => 'author' } - ); + } + } + ); - # equivalent SQL - # SELECT artist.* FROM artist - # JOIN ( cd ON artist.id = cd.artist ) - # JOIN ( liner_notes ON cd.id = liner_notes.cd ) - # JOIN ( author ON author.id = liner_notes.author ) - # WHERE liner_notes.notes LIKE '%some text%' - # AND author.name = 'A. Writer' + # Equivalent SQL: + # SELECT artist.* FROM artist + # JOIN ( cd ON artist.id = cd.artist ) + # JOIN ( liner_notes ON cd.id = liner_notes.cd ) + # JOIN ( author ON author.id = liner_notes.author ) + # WHERE liner_notes.notes LIKE '%some text%' + # AND author.name = 'A. Writer' =head2 Transactions @@ -365,37 +358,37 @@ in the future. This is straightforward using L: - package My::DB; - # set up connection here... - - package My::User; - use base 'My::DB'; - __PACKAGE__->table('user'); - __PACKAGE__->add_columns(qw/id name/); - __PACKAGE__->set_primary_key('id'); - __PACKAGE__->has_many('user_address' => 'My::UserAddress', 'user'); - __PACKAGE__->many_to_many('addresses' => 'user_address', 'address'); - - package My::UserAddress; - use base 'My::DB'; - __PACKAGE__->table('user_address'); - __PACKAGE__->add_columns(qw/user address/); - __PACKAGE__->set_primary_key(qw/user address/); - __PACKAGE__->belongs_to('user' => 'My::User'); - __PACKAGE__->belongs_to('address' => 'My::Address'); - - package My::Address; - use base 'My::DB'; - __PACKAGE__->table('address'); - __PACKAGE__->add_columns(qw/id street town area_code country/); - __PACKAGE__->set_primary_key('id'); - __PACKAGE__->has_many('user_address' => 'My::UserAddress', 'address'); - __PACKAGE__->many_to_many('users' => 'user_address', 'user'); - - $rs = $user->addresses(); # get all addresses for a user - $rs = $address->users(); # get all users for an address - -=head2 Setting default values + package My::DB; + # ... set up connection ... + + package My::User; + use base 'My::DB'; + __PACKAGE__->table('user'); + __PACKAGE__->add_columns(qw/id name/); + __PACKAGE__->set_primary_key('id'); + __PACKAGE__->has_many('user_address' => 'My::UserAddress', 'user'); + __PACKAGE__->many_to_many('addresses' => 'user_address', 'address'); + + package My::UserAddress; + use base 'My::DB'; + __PACKAGE__->table('user_address'); + __PACKAGE__->add_columns(qw/user address/); + __PACKAGE__->set_primary_key(qw/user address/); + __PACKAGE__->belongs_to('user' => 'My::User'); + __PACKAGE__->belongs_to('address' => 'My::Address'); + + package My::Address; + use base 'My::DB'; + __PACKAGE__->table('address'); + __PACKAGE__->add_columns(qw/id street town area_code country/); + __PACKAGE__->set_primary_key('id'); + __PACKAGE__->has_many('user_address' => 'My::UserAddress', 'address'); + __PACKAGE__->many_to_many('users' => 'user_address', 'user'); + + $rs = $user->addresses(); # get all addresses for a user + $rs = $address->users(); # get all users for an address + +=head2 Setting default values for a row It's as simple as overriding the C method. Note the use of C. @@ -415,4 +408,14 @@ module. Replace C with the column/method of your choice. use overload '""' => 'foo', fallback => 1; +=head2 Disconnecting cleanly + +If you find yourself quitting an app with Control-C a lot during +development, you might like to put the following signal handler in +your main database class to make sure it disconnects cleanly: + + $SIG{INT} = sub { + __PACKAGE__->storage->dbh->disconnect; + }; + =cut