From: Jess Robinson Date: Wed, 5 Aug 2009 12:57:52 +0000 (+0000) Subject: Minty's conversion of cookbook "arbitrary sql" to use ResultSource::View, plus some... X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=b46642502590b35e47f30ed5b3584e94ba4f27af;p=dbsrgits%2FDBIx-Class-Historic.git Minty's conversion of cookbook "arbitrary sql" to use ResultSource::View, plus some examples in ::View itself. Some style tweaks of mine --- diff --git a/lib/DBIx/Class/Manual/Cookbook.pod b/lib/DBIx/Class/Manual/Cookbook.pod index 050a433..d1e559c 100644 --- a/lib/DBIx/Class/Manual/Cookbook.pod +++ b/lib/DBIx/Class/Manual/Cookbook.pod @@ -19,6 +19,8 @@ paged resultset, which will fetch only a defined number of records at a time: return $rs->all(); # all records for page 1 + return $rs->page(2); # records for page 2 + You can get a L object for the resultset (suitable for use in e.g. a template) using the C method: @@ -59,12 +61,12 @@ 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 +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 @@ -73,14 +75,16 @@ 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; + 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 @@ -88,7 +92,7 @@ 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; + my $top_cd = $cd_rs->search ({}, { order_by => 'rating', rows => 1 })->single; =back @@ -98,79 +102,46 @@ Sometimes you have to run arbitrary SQL because your query is too complex (e.g. it contains Unions, Sub-Selects, Stored Procedures, etc.) or has to be optimized for your database in a special way, but you still want to get the results as a L. -The recommended way to accomplish this is by defining a separate ResultSource -for your query. You can then inject complete SQL statements using a scalar -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::Result::User; +The recommended way to accomplish this is by defining a separate +L for your query. + package My::Schema::Result::UserFriendsComplex; + use strict; + use warnings; use base qw/DBIx::Class/; - # ->load_components, ->table, ->add_columns, etc. - - # Make a new ResultSource based on the User class - my $source = __PACKAGE__->result_source_instance(); - my $new_source = $source->new( $source ); - $new_source->source_name( 'UserFriendsComplex' ); - - # 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! - $new_source->name( \<register_extra_source( 'UserFriendsComplex' => $new_source ); + use DBIx::Class::ResultSource::View; + + __PACKAGE__->load_components('Core'); + __PACKAGE__->table_class('DBIx::Class::ResultSource::View'); + + # ->table, ->add_columns, etc. + + __PACKAGE__->result_source_instance->is_virtual(1); + __PACKAGE__->result_source_instance->view_definition(q[ + SELECT u.* FROM user u + INNER JOIN user_friends f ON u.id = f.user_id + WHERE f.friend_user_id = ? + UNION + SELECT u.* FROM user u + INNER JOIN user_friends f ON u.id = f.friend_user_id + WHERE f.user_id = ? + ]); Next, you can execute your complex query using bind parameters like this: - my $friends = [ $schema->resultset( 'UserFriendsComplex' )->search( {}, + my $friends = $schema->resultset( 'UserFriendsComplex' )->search( {}, { bind => [ 12345, 12345 ] } - ) ]; + ); ... 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 ResultSource class), you -can achieve the same with subclassing the ResultSource class and defining the -new ResultSource there: - - package My::Schema::Result::UserFriendsComplex; - - 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__->result_source_instance->name( \<load_components('Core'); @@ -28,17 +29,30 @@ DBIx::Class::ResultSource::View - ResultSource object representing a view __PACKAGE__->result_source_instance->is_virtual(1); __PACKAGE__->result_source_instance->view_definition( "SELECT cdid, artist, title FROM cd WHERE year ='2000'" - ); + ); + __PACKAGE__->add_columns( + 'cdid' => { + data_type => 'integer', + is_auto_increment => 1, + }, + 'artist' => { + data_type => 'integer', + }, + 'title' => { + data_type => 'varchar', + size => 100, + }, + ); =head1 DESCRIPTION View object that inherits from L -This class extends ResultSource to add basic view support. +This class extends ResultSource to add basic view support. -A view has a L, which contains an SQL query. The -query cannot have parameters. It may contain JOINs, sub selects and -any other SQL your database supports. +A view has a L, which contains a SQL query. The query can +only have parameters if L is set to true. It may contain JOINs, +sub selects and any other SQL your database supports. View definition SQL is deployed to your database on L unless you set L to true. @@ -50,6 +64,37 @@ Virtual views (L true), are assumed to not exist in your database as a real view. The L in this case replaces the view name in a FROM clause in a subselect. +=head1 EXAMPLES + +Having created the MyDB::Schema::Year2000CDs schema as shown in the SYNOPSIS +above, you can then: + + $2000_cds = $schema->resultset('Year2000CDs') + ->search() + ->all(); + $count = $schema->resultset('Year2000CDs') + ->search() + ->count(); + +If you modified the schema to include a placeholder + + __PACKAGE__->result_source_instance->view_definition( + "SELECT cdid, artist, title FROM cd WHERE year ='?'" + ); + +and ensuring you have is_virtual set to true: + + __PACKAGE__->result_source_instance->is_virtual(1); + +You could now say: + + $2001_cds = $schema->resultset('Year2000CDs') + ->search({}, { bind => [2001] }) + ->all(); + $count = $schema->resultset('Year2000CDs') + ->search({}, { bind => [2001] }) + ->count(); + =head1 SQL EXAMPLES =over