From: Marc Mims Date: Thu, 4 Oct 2007 13:48:06 +0000 (-0700) Subject: - Doc patch for using an SQL function on the left side of a comparison. X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=1c133e22c6e3f2701e057ee4f486f1fcaffe8428;p=dbsrgits%2FDBIx-Class-Historic.git - Doc patch for using an SQL function on the left side of a comparison. - Which lead to the discovery of a bind args ordering bug. TODO tests added for that. - Fixed =for hidden with =begin hidden / =end hidden elsewhere. - Added myself to the contributors list. --- diff --git a/lib/DBIx/Class.pm b/lib/DBIx/Class.pm index 854df8e..745f857 100644 --- a/lib/DBIx/Class.pm +++ b/lib/DBIx/Class.pm @@ -261,6 +261,8 @@ sc_: Just Another Perl Hacker scotty: Scotty Allen +semifor: Marc Mims + sszabo: Stephan Szabo Todd Lipcon diff --git a/lib/DBIx/Class/Manual/Cookbook.pod b/lib/DBIx/Class/Manual/Cookbook.pod index 9a96f34..2fdb3ad 100644 --- a/lib/DBIx/Class/Manual/Cookbook.pod +++ b/lib/DBIx/Class/Manual/Cookbook.pod @@ -263,6 +263,37 @@ 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 + +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. + +If you do not have quoting on, simply include the function in your search +specification as you would any column: + + $rs->search({ 'YEAR(date_of_birth)' => 1979 }); + +With quoting on, or for a more portable solution, use the C +attribute: + + $rs->search({}, { where => \'YEAR(date_of_birth) = 1979' }); + +=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 You can use the C attribute to allow searching on, or sorting your diff --git a/lib/DBIx/Class/Manual/FAQ.pod b/lib/DBIx/Class/Manual/FAQ.pod index d372115..434a1b1 100644 --- a/lib/DBIx/Class/Manual/FAQ.pod +++ b/lib/DBIx/Class/Manual/FAQ.pod @@ -232,6 +232,25 @@ and not: my $interval = "now() - interval '12 hours'"; ->search({last_attempt => { '<' => \$interval } }) +=item .. search with an SQL function on the left hand side? + +To use an SQL function on the left hand side of a comparison: + + ->search({}, { where => \'YEAR(date_of_birth)=1979' }); + +=begin hidden + +(When the bind arg ordering bug is fixed, the previous example can be +replaced with the following.) + + ->search({}, { where => \'YEAR(date_of_birth)=?', bind => [ 1979 ] }); + +=end hidden + +Or, if you have quoting off: + + ->search({ 'YEAR(date_of_birth' => 1979 }); + =item .. find more help on constructing searches? Behind the scenes, DBIx::Class uses L to help construct diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index a7dfe42..4a15e22 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -264,6 +264,13 @@ sub search_rs { Pass a literal chunk of SQL to be added to the conditional part of the resultset query. +CAVEAT: C is provided for Class::DBI compatibility and should +only be used in that context. There are known problems using C +in chained queries; it can result in bind values in the wrong order. See +L and +L for seaching techniques that do not +require C. + =cut sub search_literal { diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index 81f050c..79988b8 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -484,7 +484,9 @@ DEPRECATED. You probably wanted compose_namespace. Actually, you probably just wanted to call connect. -=for hidden due to deprecation +=begin hidden + +(hidden due to deprecation) Calls L to the target namespace, calls L with @db_info on the new schema, @@ -498,6 +500,8 @@ L and use the resulting schema object to operate on L objects with L for more information. +=end hidden + =cut { diff --git a/t/47bind_attribute.t b/t/47bind_attribute.t new file mode 100644 index 0000000..afc5b1a --- /dev/null +++ b/t/47bind_attribute.t @@ -0,0 +1,82 @@ +use strict; +use warnings; + +use Test::More; +use lib qw(t/lib); +use DBICTest; + +my $schema = DBICTest->init_schema; + +BEGIN { + eval "use DBD::SQLite"; + plan $@ + ? ( skip_all => 'needs DBD::SQLite for testing' ) + : ( tests => 7 ); +} + +### $schema->storage->debug(1); + +my $where_bind = { + where => \'name like ?', + bind => [ 'Cat%' ], +}; + +my $rs; + +TODO: { + local $TODO = 'bind args order needs fixing (semifor)'; + + # First, the simple cases... + $rs = $schema->resultset('Artist')->search( + { artistid => 1 }, + $where_bind, + ); + + is ( $rs->count, 1, 'where/bind combined' ); + + $rs= $schema->resultset('Artist')->search({}, $where_bind) + ->search({ artistid => 1}); + + is ( $rs->count, 1, 'where/bind first' ); + + $rs = $schema->resultset('Artist')->search({ artistid => 1}) + ->search({}, $where_bind); + + is ( $rs->count, 1, 'where/bind last' ); +} + +# More complex cases, based primarily on the Cookbook +# "Arbitrary SQL through a custom ResultSource" technique, +# which seems to be the only place the bind attribute is +# documented. Breaking this technique probably breaks existing +# application code. +my $source = DBICTest::Artist->result_source_instance; +my $new_source = $source->new($source); +$new_source->source_name('Complex'); + +$new_source->name(\<<''); +( select a.*, cd.cdid as cdid, cd.title as title, cd.year as year + from artist a + join cd on cd.artist=a.artistid + where cd.year=?) + +$schema->register_source('Complex' => $new_source); + +$rs = $schema->resultset('Complex')->search({}, { bind => [ 1999 ] }); +is ( $rs->count, 1, 'cookbook arbitrary sql example' ); + +$rs = $schema->resultset('Complex')->search({ 'artistid' => 1 }, { bind => [ 1999 ] }); +is ( $rs->count, 1, '...coobook + search condition' ); + +$rs = $schema->resultset('Complex')->search({}, { bind => [ 1999 ] }) + ->search({ 'artistid' => 1 }); +is ( $rs->count, 1, '...cookbook (bind first) + chained search' ); + +TODO: { + local $TODO = 'bind args order needs fixing (semifor)'; + $rs = $schema->resultset('Complex')->search({}, { bind => [ 1999 ] }) + ->search({ 'artistid' => 1 }, { + where => \'title like ?', + bind => [ 'Spoon%' ] }); + is ( $rs->count, 1, '...cookbook + chained search with extra bind' ); +}