X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FManual%2FCookbook.pod;h=2fdb3ad103d5f26926ad1959c8a8de551935784f;hb=c385ecea5ec4297f269bcc2b8db8e08f5772196d;hp=e95a95c5b2d804819b646b343218fae51639c220;hpb=d676881ffc63beab0cb32de5082c32b89705a468;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/Manual/Cookbook.pod b/lib/DBIx/Class/Manual/Cookbook.pod index e95a95c..2fdb3ad 100644 --- a/lib/DBIx/Class/Manual/Cookbook.pod +++ b/lib/DBIx/Class/Manual/Cookbook.pod @@ -9,7 +9,7 @@ DBIx::Class::Manual::Cookbook - Miscellaneous recipes =head3 Paged results 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: +paged resultset, which will fetch only a defined number of records at a time: my $rs = $schema->resultset('Artist')->search( undef, @@ -32,7 +32,7 @@ The C attribute does not have to be specified in your search: 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 +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: return $rs->pager(); @@ -70,6 +70,56 @@ This results in the following C clause: For more information on generating complex queries, see L. +=head3 Arbitrary SQL through a custom ResultSource + +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::User; + + 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_source( 'UserFriendsComplex' => $new_source ); + +Next, you can execute your complex query using bind parameters like this: + + my $friends = [ $schema->resultset( 'UserFriendsComplex' )->search( {}, + { + bind => [ 12345, 12345 ] + } + ) ]; + +... and you'll get back a perfect L. + =head3 Using specific columns When you only want specific columns from a table, you can use @@ -213,26 +263,36 @@ Then call your new method in your code: my $ordered_cds = $schema->resultset('CD')->search_cds_ordered(); +=head3 Using SQL functions on the left hand side of a comparison -=head3 Predefined searches without writing a ResultSet class +Using SQL functions on the left hand side of a comparison is generally +not a good idea since it requires a scan of the entire table. However, +it can be accomplished with C when necessary. -Alternatively you can automatically generate a DBIx::Class::ResultSet -class by using the ResultSetManager component and tagging your method -as ResultSet: +If you do not have quoting on, simply include the function in your search +specification as you would any column: - __PACKAGE__->load_components(qw/ ResultSetManager Core /); + $rs->search({ 'YEAR(date_of_birth)' => 1979 }); - sub search_cds_ordered : ResultSet { - my ($self) = @_; - return $self->search( - {}, - { order_by => 'name DESC' }, - ); - } +With quoting on, or for a more portable solution, use the C +attribute: -Then call your method in the same way from your code: + $rs->search({}, { where => \'YEAR(date_of_birth) = 1979' }); - my $ordered_cds = $schema->resultset('CD')->search_cds_ordered(); +=begin hidden + +(When the bind args ordering bug is fixed, this technique will be better +and can replace the one above.) + +With quoting on, or for a more portable solution, use the C and +C attributes: + + $rs->search({}, { + where => \'YEAR(date_of_birth) = ?', + bind => [ 1979 ] + }); + +=end hidden =head2 Using joins and prefetch @@ -265,7 +325,7 @@ it in your C attribute: join => [qw/ artist /], order_by => [qw/ artist.name /] } - }; + ); # Equivalent SQL: # SELECT cd.* FROM cd @@ -494,7 +554,7 @@ To order C<< $book->pages >> by descending page_number. =head2 Transactions As of version 0.04001, there is improved transaction support in -L and L. Here is an +L and L. Here is an example of the recommended way to use it: my $genus = $schema->resultset('Genus')->find(12); @@ -575,7 +635,9 @@ C. $attrs->{foo} = 'bar' unless defined $attrs->{foo}; - $class->next::method($attrs); + my $new = $class->next::method($attrs); + + return $new; } For more information about C, look in the L @@ -583,7 +645,7 @@ documentation. See also L for more ways to write your own base classes to do this. People looking for ways to do "triggers" with DBIx::Class are probably -just looking for this. +just looking for this. =head2 Stringification @@ -637,25 +699,50 @@ your main database class to make sure it disconnects cleanly: =head2 Schema import/export -This functionality requires you to have L (also known as -"SQL Fairy") installed. +To create a DBIx::Class schema from an existing database, use +L's C: -To create a DBIx::Class schema from an existing database: + perl -MDBIx::Class::Schema::Loader=make_schema_at,dump_to_dir:./lib -e 'make_schema_at("My::Schema", { debug => 1 }, [ "dbi:Pg:dbname=foo","postgres" ])' - sqlt --from DBI - --to DBIx::Class::File - --prefix "MySchema" > MySchema.pm +The following functionality requires you to have L +(also known as "SQL Fairy") installed. -To create a MySQL database from an existing L schema, convert the -schema to MySQL's dialect of SQL: +To create a set of database-specific .sql files for the above schema: - sqlt --from SQL::Translator::Parser::DBIx::Class - --to MySQL - --DBIx::Class "MySchema.pm" > Schema1.sql - -And import using the mysql client: + my $schema = My::Schema->connect($dsn); + $schema->create_ddl_dir(['MySQL', 'SQLite', 'PostgreSQL'], + '0.1', + '/dbscriptdir/' + ); + +By default this will create schema files in the current directory, for +MySQL, SQLite and PostgreSQL, using the $VERSION from your Schema.pm. + +To create a new database using the schema: + + my $schema = My::Schema->connect($dsn); + $schema->deploy({ add_drop_tables => 1}); + +To import created .sql files using the mysql client: + + mysql -h "host" -D "database" -u "user" -p < My_Schema_1.0_MySQL.sql + +To create C conversion scripts to update a database to a +newer version of your schema at a later point, first set a new +$VERSION in your Schema file, then: + + my $schema = My::Schema->connect($dsn); + $schema->create_ddl_dir(['MySQL', 'SQLite', 'PostgreSQL'], + '0.2', + '/dbscriptdir/', + '0.1' + ); + +This will produce new database-specific .sql files for the new version +of the schema, plus scripts to convert from version 0.1 to 0.2. This +requires that the files for 0.1 as created above are available in the +given directory to diff against. - mysql -h "host" -D "database" -u "user" -p < Schema1.sql =head2 Easy migration from class-based to schema-based setup @@ -731,19 +818,8 @@ described under C. =head3 Save the schema -Use C to transform your schema into an SQL script suitable for your -customer's database. E.g. for MySQL: - - sqlt --from SQL::Translator::Parser::DBIx::Class - --to MySQL - --DBIx::Class "MySchema.pm" > Schema1.mysql.sql - -If you need to target databases from multiple vendors, just generate an SQL -script suitable for each. To support PostgreSQL too: - - sqlt --from SQL::Translator::DBIx::Class - --to PostgreSQL - --DBIx::Class "MySchema.pm" > Schema1.pgsql.sql +Call L as above under L. =head3 Deploy to customers @@ -765,22 +841,21 @@ all part of your install. =head3 Modify the schema to change functionality -As your application evolves, it may be necessary to modify your schema to -change functionality. Once the changes are made to your schema in DBIx::Class, -export the modified schema as before, taking care not to overwrite the original: - - sqlt --from SQL::Translator::DBIx::Class - --to MySQL - --DBIx::Class "Anything.pm" > Schema2.mysql.sql - -Next, use sqlt-diff to create an SQL script that will update the customer's -database schema: - - sqlt-diff --to MySQL Schema1=MySQL Schema2=MySQL > SchemaUpdate.mysql.sql +As your application evolves, it may be necessary to modify your schema +to change functionality. Once the changes are made to your schema in +DBIx::Class, export the modified schema and the conversion scripts as +in L. =head3 Deploy update to customers -The schema update can be deployed to customers using the same method as before. +Add the L schema component to your +Schema class. This will add a new table to your database called +C which will keep track of which version is installed +and warn if the user trys to run a newer schema version than the +database thinks it has. + +Alternatively, you can send the conversion sql scripts to your +customers as above. =head2 Setting limit dialect for SQL::Abstract::Limit @@ -816,9 +891,10 @@ generator to put the quotes the correct place. =head2 Overloading methods -L uses the L package, which provides for redispatch of -method calls. You have to use calls to C to overload methods. -More information on using L with L can be found in +L uses the L package, which provides for redispatch of +method calls, useful for things like default values and triggers. You have to +use calls to C to overload methods. More information on using +L with L can be found in L. =head3 Changing one field whenever another changes @@ -912,7 +988,7 @@ method. =head2 Profiling -When you enable L's debugging it prints the SQL +When you enable L's debugging it prints the SQL executed as well as notifications of query completion and transaction begin/commit. If you'd like to profile the SQL you can subclass the L class and write your own profiling @@ -932,7 +1008,7 @@ mechanism: my $sql = shift(); my $params = @_; - print "Executing $sql: ".join(', ', @params)."\n"; + $self->print("Executing $sql: ".join(', ', @params)."\n"); $start = time(); } @@ -941,7 +1017,8 @@ mechanism: my $sql = shift(); my @params = @_; - printf("Execution took %0.4f seconds.\n", time() - $start); + my $elapsed = sprintf("%0.4f", time() - $start); + $self->print("Execution took $elapsed seconds.\n"); $start = undef; } @@ -949,8 +1026,8 @@ mechanism: You can then install that class as the debugging object: - __PACKAGE__->storage()->debugobj(new My::Profiler()); - __PACKAGE__->storage()->debug(1); + __PACKAGE__->storage->debugobj(new My::Profiler()); + __PACKAGE__->storage->debug(1); A more complicated example might involve storing each execution of SQL in an array: @@ -969,6 +1046,7 @@ array: You could then create average, high and low execution times for an SQL statement and dig down to see if certain parameters cause aberrant behavior. +You might want to check out L as well. =head2 Getting the value of the primary key for the last database insert @@ -1107,41 +1185,33 @@ B test.pl 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 -fancy objects. Luckily this is also fairly easy using -C: - - # Define a class which just returns the results as a hashref: - package My::HashRefInflator; - - ## $me is the hashref of cols/data from the immediate resultsource - ## $prefetch is a deep hashref of all the data from the prefetched - ## related sources. +fancy objects. + +To do this simply use L. + + my $rs = $schema->resultset('CD'); + + $rs->result_class('DBIx::Class::ResultClass::HashRefInflator'); + + my $hash_ref = $rs->find(1); + +Wasn't that easy? + +=head2 Get raw data for blindingly fast results - sub mk_hash { - my ($me, $rest) = @_; +If the C 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 wrapped round them. - return { %$me, - map { ($_ => mk_hash(@{$rest->{$_}})) } keys %$rest - }; - } +This is used like so:- - sub inflate_result { - my ($self, $source, $me, $prefetch) = @_; - return mk_hash($me, $prefetch); + my $cursor = $rs->cursor + while (my @vals = $cursor->next) { + # use $val[0..n] here } - # Change the object inflation to a hashref for just this resultset: - $rs->result_class('My::HashRefInflator'); - - my $datahashref = $rs->next; - foreach my $col (keys %$datahashref) { - if(!ref($datahashref->{$col})) { - # It's a plain value - } - elsif(ref($datahashref->{$col} eq 'HASH')) { - # It's a related value in a hashref - } - } +You will need to map the array offsets to particular columns (you can +use the I