X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FManual%2FCookbook.pod;h=574a8e1eab1b4bd021b8a8aa94cb149b5dd96081;hb=d29565e0c545871399b0732e7781ea445e7d5b8c;hp=0552a037b9a8136424a635907f354d6721ada428;hpb=d2f3e87bbb670790a5572e1f7b92f33cc57585ab;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/Manual/Cookbook.pod b/lib/DBIx/Class/Manual/Cookbook.pod index 0552a03..574a8e1 100644 --- a/lib/DBIx/Class/Manual/Cookbook.pod +++ b/lib/DBIx/Class/Manual/Cookbook.pod @@ -19,19 +19,8 @@ paged resultset, which will fetch only a defined number of records at a time: return $rs->all(); # all records for page 1 -The C attribute does not have to be specified in your search: - - my $rs = $schema->resultset('Artist')->search( - undef, - { - rows => 10, - } - ); - - return $rs->page(1); # DBIx::Class::ResultSet containing first 10 records - -In either of the above cases, you can get a L object for the -resultset (suitable for use in e.g. a template) using the C method: +You can get a L object for the resultset (suitable for use +in e.g. a template) using the C method: return $rs->pager(); @@ -68,6 +57,41 @@ This results in the following C clause: For more information on generating complex queries, see L. +=head2 Retrieve one and only one row from a resultset + +Sometimes you need only the first "top" row of a resultset. While this can be +easily done with L<< $rs->first|DBIx::Class::ResultSet/first >>, it is suboptimal, +as a full blown cursor for the resultset will be created and then immediately +destroyed after fetching the first row object. +L<< $rs->single|DBIx::Class::ResultSet/single >> is +designed specifically for this case - it will grab the first returned result +without even instantiating a cursor. + +Before replacing all your calls to C with C please observe the +following CAVEATS: + +=over + +=item * +While single() takes a search condition just like search() does, it does +_not_ accept search attributes. However one can always chain a single() to +a search(): + + my $top_cd = $cd_rs -> search({}, { order_by => 'rating' }) -> single; + + +=item * +Since single() is the engine behind find(), it is designed to fetch a +single row per database query. Thus a warning will be issued when the +underlying SELECT returns more than one row. Sometimes however this usage +is valid: i.e. we have an arbitrary number of cd's but only one of them is +at the top of the charts at any given time. If you know what you are doing, +you can silence the warning by explicitly limiting the resultset size: + + my $top_cd = $cd_rs -> search ({}, { order_by => 'rating', rows => 1 }) -> single; + +=back + =head2 Arbitrary SQL through a custom ResultSource Sometimes you have to run arbitrary SQL because your query is too complex @@ -81,7 +105,7 @@ reference (this is a feature of L). Say you want to run a complex custom query on your user data, here's what you have to add to your User class: - package My::Schema::User; + package My::Schema::Result::User; use base qw/DBIx::Class/; @@ -106,7 +130,7 @@ you have to add to your User class: SQL # Finally, register your new ResultSource with your Schema - My::Schema->register_source( 'UserFriendsComplex' => $new_source ); + My::Schema->register_extra_source( 'UserFriendsComplex' => $new_source ); Next, you can execute your complex query using bind parameters like this: @@ -116,7 +140,36 @@ Next, you can execute your complex query using bind parameters like this: } ) ]; -... and you'll get back a perfect L. +... and you'll get back a perfect L (except, of course, +that you cannot modify the rows it contains, ie. cannot call L, +L, ... on it). + +If you prefer to have the definitions of these custom ResultSources in separate +files (instead of stuffing all of them into the same resultset class), you can +achieve the same with subclassing the resultset class and defining the +ResultSource there: + + package My::Schema::Result::UserFriendsComplex; + + use My::Schema::Result::User; + use base qw/My::Schema::Result::User/; + + __PACKAGE__->table('dummy'); # currently must be called before anything else + + # Hand in your query as a scalar reference + # It will be added as a sub-select after FROM, + # so pay attention to the surrounding brackets! + __PACKAGE__->name( \<resultset('Foo')->search( + my $rs = $schema->resultset('Artist')->search( {}, { - select => [ - { distinct => [ $source->columns ] } - ], - as => [ $source->columns ] # remember 'as' is not the same as SQL AS :-) + columns => [ qw/artistid name rank/ ], + distinct => 1 + } + ); + + my $rs = $schema->resultset('Artist')->search( + {}, + { + columns => [ qw/artistid name rank/ ], + group_by => [ qw/artistid name rank/ ], } ); - my $count = $rs->next->get_column('count'); + # Equivalent SQL: + # SELECT me.artistid, me.name, me.rank + # FROM artist me + # GROUP BY artistid, name, rank =head2 SELECT COUNT(DISTINCT colname) - my $rs = $schema->resultset('Foo')->search( + my $rs = $schema->resultset('Artist')->search( + {}, + { + columns => [ qw/name/ ], + distinct => 1 + } + ); + + my $rs = $schema->resultset('Artist')->search( {}, { - select => [ - { count => { distinct => 'colname' } } - ], - as => [ 'count' ] + columns => [ qw/name/ ], + group_by => [ qw/name/ ], } ); + my $count = $rs->count; + + # Equivalent SQL: + # SELECT COUNT( DISTINCT( me.name ) ) FROM artist me + =head2 Grouping results L supports C as follows: @@ -216,21 +289,73 @@ L supports C as follows: {}, { join => [qw/ cds /], - select => [ 'name', { count => 'cds.cdid' } ], + select => [ 'name', { count => 'cds.id' } ], as => [qw/ name cd_count /], group_by => [qw/ name /] } ); # Equivalent SQL: - # SELECT name, COUNT( cds.cdid ) FROM artist me - # LEFT JOIN cd cds ON ( cds.artist = me.artistid ) + # SELECT name, COUNT( cd.id ) FROM artist + # LEFT JOIN cd ON artist.id = cd.artist # GROUP BY name Please see L documentation if you are in any way unsure about the use of the attributes above (C< join >, C< select >, C< as > and C< group_by >). +=head2 Subqueries (EXPERIMENTAL) + +You can write subqueries relatively easily in DBIC. + + my $inside_rs = $schema->resultset('Artist')->search({ + name => [ 'Billy Joel', 'Brittany Spears' ], + }); + + my $rs = $schema->resultset('CD')->search({ + artist_id => { 'IN' => $inside_rs->get_column('id')->as_query }, + }); + +The usual operators ( =, !=, IN, NOT IN, etc) are supported. + +B: You have to explicitly use '=' when doing an equality comparison. +The following will B work: + + my $rs = $schema->resultset('CD')->search({ + artist_id => $inside_rs->get_column('id')->as_query, + }); + +=head3 Support + +Subqueries are supported in the where clause (first hashref), and in the +from, select, and +select attributes. + +=head3 Correlated subqueries + + my $cdrs = $schema->resultset('CD'); + my $rs = $cdrs->search({ + year => { + '=' => $cdrs->search( + { artistid => { '=' => \'me.artistid' } }, + { alias => 'inner' } + )->get_column('year')->max_rs->as_query, + }, + }); + +That creates the following SQL: + + SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track + FROM cd me + WHERE year = ( + SELECT MAX(inner.year) + FROM cd inner + WHERE artistid = me.artistid + ) + +=head3 EXPERIMENTAL + +Please note that subqueries are considered an experimental feature. + =head2 Predefined searches You can write your own L class by inheriting from it @@ -255,8 +380,15 @@ and define often used searches as methods: To use your resultset, first tell DBIx::Class to create an instance of it for you, in your My::DBIC::Schema::CD class: + # class definition as normal + __PACKAGE__->load_components(qw/ Core /); + __PACKAGE__->table('cd'); + + # tell DBIC to use the custom ResultSet class __PACKAGE__->resultset_class('My::DBIC::ResultSet::CD'); +Note that C must be called after C and C, or you will get errors about missing methods. + Then call your new method in your code: my $ordered_cds = $schema->resultset('CD')->search_cds_ordered(); @@ -305,7 +437,7 @@ a particular artist name: 'artist.name' => 'Bob Marley' }, { - join => [qw/artist/], # join the artist table + join => 'artist', # join the artist table } ); @@ -322,7 +454,7 @@ it in your C attribute: 'artist.name' => 'Bob Marley' }, { - join => [qw/ artist /], + join => 'artist', order_by => [qw/ artist.name /] } ); @@ -363,9 +495,9 @@ This allows you to fetch results from related tables in advance: 'artist.name' => 'Bob Marley' }, { - join => [qw/ artist /], + join => 'artist', order_by => [qw/ artist.name /], - prefetch => [qw/ artist /] # return artist data too! + prefetch => 'artist' # return artist data too! } ); @@ -385,13 +517,38 @@ 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 L 0.05999_01, C I be used with -C relationships. - 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 Multiple joins + +In the examples above, the C attribute was a scalar. If you +pass an array reference instead, you can join to multiple tables. In +this example, we want to limit the search further, using +C: + + # Relationships defined elsewhere: + # CD->belongs_to('artist' => 'Artist'); + # CD->has_one('liner_notes' => 'LinerNotes', 'cd'); + my $rs = $schema->resultset('CD')->search( + { + 'artist.name' => 'Bob Marley' + 'liner_notes.notes' => { 'like', '%some text%' }, + }, + { + join => [qw/ artist liner_notes /], + order_by => [qw/ artist.name /], + } + ); + + # Equivalent SQL: + # SELECT cd.*, artist.*, liner_notes.* FROM cd + # JOIN artist ON cd.artist = artist.id + # JOIN liner_notes ON cd.id = liner_notes.cd + # WHERE artist.name = 'Bob Marley' + # ORDER BY artist.name + =head2 Multi-step joins Sometimes you want to join more than one relationship deep. In this example, @@ -415,8 +572,8 @@ contain a specific string: # Equivalent SQL: # SELECT artist.* FROM artist - # JOIN ( cd ON artist.id = cd.artist ) - # JOIN ( liner_notes ON cd.id = liner_notes.cd ) + # LEFT JOIN cd ON artist.id = cd.artist + # LEFT 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 @@ -442,15 +599,42 @@ notes: # 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 ) + # LEFT JOIN cd ON artist.id = cd.artist + # LEFT JOIN liner_notes ON cd.id = liner_notes.cd + # LEFT JOIN author ON author.id = liner_notes.author # WHERE liner_notes.notes LIKE '%some text%' # AND author.name = 'A. Writer' +=head2 Multi-step and multiple joins + +With various combinations of array and hash references, you can join +tables in any combination you desire. For example, to join Artist to +CD and Concert, and join CD to LinerNotes: + + # Relationships defined elsewhere: + # Artist->has_many('concerts' => 'Concert', 'artist'); + + my $rs = $schema->resultset('Artist')->search( + { }, + { + join => [ + { + cds => 'liner_notes' + }, + 'concerts' + ], + } + ); + + # Equivalent SQL: + # SELECT artist.* FROM artist + # LEFT JOIN cd ON artist.id = cd.artist + # LEFT JOIN liner_notes ON cd.id = liner_notes.cd + # LEFT JOIN concert ON artist.id = concert.artist + =head2 Multi-step prefetch -From 0.04999_05 onwards, C can be nested more than one relationship +C can be nested more than one relationship deep using the same syntax as a multi-step join: my $rs = $schema->resultset('Tag')->search( @@ -464,8 +648,8 @@ deep using the same syntax as a multi-step join: # Equivalent SQL: # SELECT tag.*, cd.*, artist.* FROM tag - # JOIN cd ON tag.cd = cd.cdid - # JOIN artist ON cd.artist = artist.artistid + # JOIN cd ON tag.cd = cd.id + # JOIN artist ON cd.artist = artist.id Now accessing our C and C relationships does not need additional SQL statements: @@ -490,8 +674,7 @@ method. AKA getting last_insert_id -If you are using PK::Auto (which is a core component as of 0.07), this is -straightforward: +Thanks to the core component PK::Auto, this is straightforward: my $foo = $rs->create(\%blah); # do more stuff @@ -506,7 +689,7 @@ Employ the standard stringification technique by using the C module. To make an object stringify itself as a single column, use something -like this (replace C with the column/method of your choice): +like this (replace C with the column/method of your choice): use overload '""' => sub { shift->name}, fallback => 1; @@ -550,6 +733,48 @@ Just use C instead, then check C: # do whatever else you wanted if it was a new row } +=head2 Static sub-classing DBIx::Class result classes + +AKA adding additional relationships/methods/etc. to a model for a +specific usage of the (shared) model. + +B + + package My::App::Schema; + + use base DBIx::Class::Schema; + + # load subclassed classes from My::App::Schema::Result/ResultSet + __PACKAGE__->load_namespaces; + + # load classes from shared model + load_classes({ + 'My::Shared::Model::Result' => [qw/ + Foo + Bar + /]}); + + 1; + +B + + package My::App::Schema::Result::Baz; + + use strict; + use warnings; + use base My::Shared::Model::Result::Baz; + + # WARNING: Make sure you call table() again in your subclass, + # otherwise DBIx::Class::ResultSourceProxy::Table will not be called + # and the class name is not correctly registered as a source + __PACKAGE__->table('baz'); + + sub additional_method { + return "I'm an additional method only needed by this app"; + } + + 1; + =head2 Dynamic Sub-classing DBIx::Class proxy classes AKA multi-class object inflation from one table @@ -573,16 +798,18 @@ below: B - package DB::Schema; + package My::Schema; use base qw/DBIx::Class::Schema/; - __PACKAGE__->load_classes(qw/User/); + __PACKAGE__->load_namespaces; + + 1; B - package DB::Schema::User; + package My::Schema::Result::User; use strict; use warnings; @@ -615,13 +842,15 @@ B print "I am a regular user.\n"; return ; } + + 1; + - - package DB::Schema::User::Admin; + package My::Schema::Result::User::Admin; use strict; use warnings; - use base qw/DB::Schema::User/; + use base qw/My::Schema::Result::User/; sub hello { @@ -633,13 +862,15 @@ B { print "I am doing admin stuff\n"; return ; - } + } + + 1; B test.pl use warnings; use strict; - use DB::Schema; + use My::Schema; my $user_data = { email => 'someguy@place.com', password => 'pass1', @@ -649,7 +880,7 @@ B test.pl password => 'pass2', admin => 1 }; - my $schema = DB::Schema->connection('dbi:Pg:dbname=test'); + my $schema = My::Schema->connection('dbi:Pg:dbname=test'); $schema->resultset('User')->create( $user_data ); $schema->resultset('User')->create( $admin_data ); @@ -671,7 +902,7 @@ B test.pl ### The statement below will print print "I can do admin stuff\n" if $admin->can('do_admin_stuff'); -=head2 Skip object creation for faster results +=head2 Skip row object creation for faster results DBIx::Class is not built for speed, it's built for convenience and ease of use, but sometimes you just need to get the data, and skip the @@ -684,17 +915,22 @@ To do this simply use L. $rs->result_class('DBIx::Class::ResultClass::HashRefInflator'); my $hash_ref = $rs->find(1); - + Wasn't that easy? - + +Beware, changing the Result class using +L will replace any existing class +completely including any special components loaded using +load_components, eg L. + =head2 Get raw data for blindingly fast results If the L solution above is not fast enough for you, you can use a DBIx::Class to return values -exactly as they come out of the data base with none of the convenience methods +exactly as they come out of the database with none of the convenience methods wrapped round them. -This is used like so:- +This is used like so: my $cursor = $rs->cursor while (my @vals = $cursor->next) { @@ -702,13 +938,13 @@ This is used like so:- } You will need to map the array offsets to particular columns (you can -use the I +and C instead of C + + my $rs = $schema->resultset('Dual')->search(undef, + { select => [ 'sydate' ], + as => [ 'now' ] + }, + ); + +All you have to do now is be careful how you access your resultset, the below +will not work because there is no column called 'now' in the Dual table class + + while (my $dual = $rs->next) { + print $dual->now."\n"; + } + # Can't locate object method "now" via package "MyAppDB::Dual" at headshot.pl line 23. + +You could of course use 'dummy' in C instead of 'now', or C to +your Dual class for whatever you wanted to select from dual, but that's just +silly, instead use C + + while (my $dual = $rs->next) { + print $dual->get_column('now')."\n"; + } + +Or use C + + my $cursor = $rs->cursor; + while (my @vals = $cursor->next) { + print $vals[0]."\n"; + } + +In case you're going to use this "trick" together with L or +L a table called "dual" will be created in your +current schema. This would overlap "sys.dual" and you could not fetch "sysdate" or +"sequence.nextval" anymore from dual. To avoid this problem, just tell +L to not create table dual: + + my $sqlt_args = { + add_drop_table => 1, + parser_args => { sources => [ grep $_ ne 'Dual', schema->sources ] }, + }; + $schema->create_ddl_dir( [qw/Oracle/], undef, './sql', undef, $sqlt_args ); + +Or use L + + $rs->result_class('DBIx::Class::ResultClass::HashRefInflator'); + while ( my $dual = $rs->next ) { + print $dual->{now}."\n"; + } + +Here are some example C