Support for -value op in search (for pg arrays and stuff)
Peter Rabbitson [Thu, 2 Dec 2010 10:37:02 +0000 (11:37 +0100)]
Changes
lib/DBIx/Class.pm
lib/DBIx/Class/SQLMaker.pm
t/72pg.t
t/sqlmaker/op_value.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index 49e5617..9e8df50 100644 (file)
--- a/Changes
+++ b/Changes
@@ -8,6 +8,8 @@ Revision history for DBIx::Class
         - Deprecate legacy $rs->search( %condition ) syntax
         - NULL is now supplied unquoted to all debug-objects, in order to
           differentiate between a real NULL and the string 'NULL'
+        - New search() condition operator -value used to pass complex bind
+          values to DBI: search({ array_col => { -value => [1,2,3] }})
         - +columns now behaves just like columns by not stripping a
           fully-qualified 'as' spec (i.e. foo.bar results in $obj->foo->bar)
 
index 7de97c0..792e938 100644 (file)
@@ -318,6 +318,8 @@ jon: Jon Schutz <jjschutz@cpan.org>
 
 jshirley: J. Shirley <jshirley@gmail.com>
 
+kaare: Kaare Rasmussen
+
 konobi: Scott McWhirter
 
 lukes: Luke Saunders <luke.saunders@gmail.com>
index 6ea68ba..6db2aba 100644 (file)
@@ -26,6 +26,8 @@ Currently the enhancements to L<SQL::Abstract> are:
 
 =item * The -ident operator
 
+=item * The -value operator
+
 =back
 
 =cut
@@ -84,13 +86,14 @@ sub __max_int { 0xFFFFFFFF };
 sub new {
   my $self = shift->next::method(@_);
 
-  # use the same coderef, it is prepared to handle both cases
-  push @{$self->{special_ops}}, {
-    regex => qr/^ ident $/xi, handler => '_where_op_IDENT',
-  };
-  push @{$self->{unary_ops}}, {
-    regex => qr/^ ident $/xi, handler => '_where_op_IDENT',
-  };
+  # use the same coderefs, they are prepared to handle both cases
+  my @extra_dbic_syntax = (
+    { regex => qr/^ ident $/xi, handler => '_where_op_IDENT' },
+    { regex => qr/^ value $/xi, handler => '_where_op_VALUE' },
+  );
+
+  push @{$self->{special_ops}}, @extra_dbic_syntax;
+  push @{$self->{unary_ops}}, @extra_dbic_syntax;
 
   $self;
 }
@@ -102,7 +105,7 @@ sub _where_op_IDENT {
     croak "-$op takes a single scalar argument (a quotable identifier)";
   }
 
-  # in case we are called as a top level special op
+  # in case we are called as a top level special op (no '=')
   my $lhs = shift;
 
   $_ = $self->_convert($self->_quote($_)) for ($lhs, $rhs);
@@ -113,6 +116,30 @@ sub _where_op_IDENT {
   ;
 }
 
+sub _where_op_VALUE {
+  my $self = shift;
+  my ($op, $rhs) = splice @_, -2;
+
+  # in case we are called as a top level special op (no '=')
+  my $lhs = shift;
+
+  my @bind = [
+    ($lhs || $self->{_nested_func_lhs} || croak "Unable to find bindtype for -value $rhs"),
+    $rhs
+  ];
+
+  return $lhs
+    ? (
+      $self->_convert($self->_quote($lhs)) . ' = ' . $self->_convert('?'),
+      @bind
+    )
+    : (
+      $self->_convert('?'),
+      @bind,
+    )
+  ;
+}
+
 # Handle limit-dialect selection
 sub select {
   my ($self, $table, $fields, $where, $rs_attrs, $limit, $offset) = @_;
index f9f61c4..2394bed 100644 (file)
--- a/t/72pg.t
+++ b/t/72pg.t
@@ -198,7 +198,7 @@ for my $use_insert_returning ($test_server_supports_insert_returning
 
     use strict;
     use warnings;
-    use base 'DBIx::Class::Core';
+    use base 'DBICTest::BaseResult';
 
     __PACKAGE__->table('dbic_t_schema.array_test');
     __PACKAGE__->add_columns(qw/id arrayfield/);
@@ -209,43 +209,89 @@ for my $use_insert_returning ($test_server_supports_insert_returning
   SKIP: {
     skip "Need DBD::Pg 2.9.2 or newer for array tests", 4 if $DBD::Pg::VERSION < 2.009002;
 
+    my $arr_rs = $schema->resultset('ArrayTest');
+
     lives_ok {
-      $schema->resultset('ArrayTest')->create({
+      $arr_rs->create({
         arrayfield => [1, 2],
       });
     } 'inserting arrayref as pg array data';
 
     lives_ok {
-      $schema->resultset('ArrayTest')->update({
+      $arr_rs->update({
         arrayfield => [3, 4],
       });
     } 'updating arrayref as pg array data';
 
-    $schema->resultset('ArrayTest')->create({
+    $arr_rs->create({
       arrayfield => [5, 6],
     });
 
-    my $afield_rs = $schema->resultset('ArrayTest')->search({
-      arrayfield => \[ '= ?' => [arrayfield => [3, 4]] ],   #Todo anything less ugly than this?
-    });
+    # Search using arrays
+    lives_ok {
+      is_deeply (
+        $arr_rs->search({ arrayfield => { -value => [3,4] } })->first->arrayfield,
+        [3,4],
+        'Array value matches'
+      );
+    } 'searching by arrayref';
 
-    my $count;
     lives_ok {
-      $count = $afield_rs->count
-    } 'comparing arrayref to pg array data does not blow up';
-    is($count, 1, 'comparing arrayref to pg array data gives correct result');
+      is_deeply (
+        $arr_rs->search({ arrayfield => { '=' => { -value => [3,4] }} })->first->arrayfield,
+        [3,4],,
+        'Array value matches explicit equal'
+      );
+    } 'searching by arrayref (explicit equal sign)';
 
-    TODO: {
-      local $TODO = 'No introspection of scalarref conditions :(';
-      my $row = $afield_rs->create({});
+    lives_ok {
+      is_deeply (
+        $arr_rs->search({ arrayfield => { '>' => { -value => [3,1] }} })->first->arrayfield,
+        [3,4],
+        'Array value matches greater than'
+      );
+    } 'searching by arrayref (greater than)';
+
+    lives_ok {
+      is (
+        $arr_rs->search({ arrayfield => { '>' => { -value => [3,7] }} })->count,
+        1,
+        'Greater than search found [5,6]',
+      );
+    } 'searching by arrayref (greater than)';
+
+    # Find using arrays
+    lives_ok {
+      is_deeply (
+        $arr_rs->find({ arrayfield => { -value => [3,4] } })->arrayfield,
+        [3,4],
+        'Array value matches implicit equal'
+      );
+    } 'find by arrayref';
+
+    lives_ok {
+      is_deeply (
+        $arr_rs->find({ arrayfield => { '=' => { -value => [3,4] }} })->arrayfield,
+        [3,4],
+        'Array value matches explicit equal'
+      );
+    } 'find by arrayref (equal)';
+
+    # test inferred condition for creation
+    TODO: for my $cond (
+      { -value => [3,4] },
+      \[ '= ?' => [arrayfield => [3, 4]] ],
+    ) {
+      local $TODO = 'No introspection of complex conditions :(';
+      my $arr_rs_cond = $arr_rs->search({ arrayfield => $cond });
+
+      my $row = $arr_rs_cond->create({});
       is_deeply ($row->arrayfield, [3,4], 'Array value taken from $rs condition');
       $row->discard_changes;
       is_deeply ($row->arrayfield, [3,4], 'Array value made it to storage');
     }
   }
 
-
-
 ########## Case check
 
   BEGIN {
diff --git a/t/sqlmaker/op_value.t b/t/sqlmaker/op_value.t
new file mode 100644 (file)
index 0000000..ceb441e
--- /dev/null
@@ -0,0 +1,36 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+use lib qw(t/lib);
+use DBIC::SqlMakerTest;
+
+use_ok('DBICTest');
+
+my $schema = DBICTest->init_schema();
+
+my $sql_maker = $schema->storage->sql_maker;
+
+for my $q ('', '"') {
+
+  $sql_maker->quote_char($q);
+
+  is_same_sql_bind (
+    \[ $sql_maker->select ('artist', '*', { arr1 => { -value => [1,2] }, arr2 => { '>', { -value => [3,4] } }, field => [5,6] } ) ],
+    "SELECT *
+      FROM ${q}artist${q}
+      WHERE ${q}arr1${q} = ? AND
+            ${q}arr2${q} > ? AND
+            ( ${q}field${q} = ? OR ${q}field${q} = ? )
+    ",
+    [
+      [ arr1 => [1,2] ],
+      [ arr2 => [3,4] ],
+      [ field => 5 ],
+      [ field => 6 ],
+    ],
+  );
+}
+
+done_testing;