[Bps-public-commit] dbix-searchbuilder branch, functions-in-searches, created. 1.63-20-gc7178fa

Ruslan Zakirov ruz at bestpractical.com
Sun Feb 3 06:39:47 EST 2013


The branch, functions-in-searches has been created
        at  c7178fa1e26861d119792623c508c5abc10feefa (commit)

- Log -----------------------------------------------------------------
commit 6c248ed069029cf82ea073ec2c4adf4bdfffd03f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri May 20 21:16:11 2011 +0400

    FIELD is not related to OPERATOR and QUOTEVALUE
    
    if operator is provided then do process "LIKE" like operators
    
    QUOTEVALUE has nothing to do with presence of FIELD argument

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index d042d27..2e3196d 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -824,8 +824,7 @@ sub Limit {
     #since we're changing the search criteria, we need to redo the search
     $self->RedoSearch();
 
-    if ( $args{'FIELD'} ) {
-
+    if ( $args{'OPERATOR'} ) {
         #If it's a like, we supply the %s around the search term
         if ( $args{'OPERATOR'} =~ /LIKE/i ) {
             $args{'VALUE'} = "%" . $args{'VALUE'} . "%";
@@ -837,7 +836,9 @@ sub Limit {
             $args{'VALUE'}    = "%" . $args{'VALUE'};
         }
         $args{'OPERATOR'} =~ s/(?:MATCHES|ENDSWITH|STARTSWITH)/LIKE/i;
+    }
 
+    {
         #if we're explicitly told not to to quote the value or
         # we're doing an IS or IS NOT (null), don't quote the operator.
 

commit 4a26e2d31f82051d3071f8d2e5f520fa7a0015c1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri May 20 21:25:09 2011 +0400

    if operator is 'IS [NOT]' then value is NULL
    
    just force NULL, otherwise it's always a SQL error.

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 2e3196d..5cf5360 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -836,6 +836,11 @@ sub Limit {
             $args{'VALUE'}    = "%" . $args{'VALUE'};
         }
         $args{'OPERATOR'} =~ s/(?:MATCHES|ENDSWITH|STARTSWITH)/LIKE/i;
+
+        if ( $args{'OPERATOR'} =~ /IS/i ) {
+            $args{'VALUE'} = 'NULL';
+            $args{'QUOTEVALUE'} = 0;
+        }
     }
 
     {

commit b6161ea45e4261b38c440020b74d7b4c5c4e72f5
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat May 21 00:17:08 2011 +0400

    if we asked to quote then quote
    
    we processed IS[ NOT] operator earlier and took
    care of QUOTEVALUE argument

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 5cf5360..d0580c0 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -843,13 +843,11 @@ sub Limit {
         }
     }
 
-    {
+    if ( $args{'QUOTEVALUE'} ) {
         #if we're explicitly told not to to quote the value or
         # we're doing an IS or IS NOT (null), don't quote the operator.
 
-        if ( $args{'QUOTEVALUE'} && $args{'OPERATOR'} !~ /IS/i ) {
-            $args{'VALUE'} = $self->_Handle->dbh->quote( $args{'VALUE'} );
-        }
+        $args{'VALUE'} = $self->_Handle->dbh->quote( $args{'VALUE'} );
     }
 
     my $Alias = $self->_GenericRestriction(%args);

commit 0bdd520d41c29eded232adf6382e6b60efaf0304
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue May 24 17:02:07 2011 +0400

    indent

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index d0580c0..154b797 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -814,10 +814,9 @@ sub Limit {
     unless ( $args{'ENTRYAGGREGATOR'} ) {
         if ( $args{'LEFTJOIN'} ) {
             $args{'ENTRYAGGREGATOR'} = 'AND';
-            } else {
-
+        } else {
             $args{'ENTRYAGGREGATOR'} = 'OR';
-            }
+        }
     }
 
 

commit 88e8b75906eb0d0d2cc1c11d8b9a38274f27f285
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue May 24 17:02:25 2011 +0400

    drop pointless comments

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 154b797..b793b12 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -892,7 +892,7 @@ sub _GenericRestriction {
         $args{'ALIAS'} = $args{'LEFTJOIN'};
     }
 
-    # {{{ if there's no alias set, we need to set it
+    # if there's no alias set, we need to set it
 
     unless ( $args{'ALIAS'} ) {
 
@@ -906,17 +906,12 @@ sub _GenericRestriction {
             $args{'ALIAS'} = 'main';
         }
 
-        # {{{ if we're joining, we need to work out the table alias
-
+        # if we're joining, we need to work out the table alias
         else {
             $args{'ALIAS'} = $self->NewAlias( $args{'TABLE'} );
         }
-
-        # }}}
     }
 
-    # }}}
-
     # Set this to the name of the field and the alias, unless we've been
     # handed a subclause name
 

commit d690a13d033a827bdf9d639bb18cf1e0860a6841
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue May 24 23:31:10 2011 +0400

    extract combining function with field into a method

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index b793b12..d4685be 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -1570,26 +1570,8 @@ sub Column {
     $args{'ALIAS'} ||= 'main';
 
     my $name;
-    if ( $args{FIELD} && $args{FUNCTION} ) {
-        $name = $args{'ALIAS'} .'.'. $args{'FIELD'};
-
-        my $func = $args{FUNCTION};
-        if ( $func =~ /^DISTINCT\s*COUNT$/i ) {
-            $name = "COUNT(DISTINCT $name)";
-        }
-        # If we want to substitute 
-        elsif ($func =~ s/\?/$name/g) {
-            $name = $func;
-        }
-        # If we want to call a simple function on the column
-        elsif ($func !~ /\(/)  {
-            $name = "\U$func\E($name)";
-        } else {
-            $name = $func;
-        }
-    }
-    elsif ( $args{FUNCTION} ) {
-        $name = $args{FUNCTION};
+    if ( $args{FUNCTION} ) {
+        $name = $self->CombineFunctionWithField( %args );
     }
     elsif ( $args{FIELD} ) {
         $name = $args{'ALIAS'} .'.'. $args{'FIELD'};
@@ -1620,6 +1602,37 @@ sub Column {
     return $column;
 }
 
+sub CombineFunctionWithField {
+    my $self = shift;
+    my %args = (
+        FUNCTION => undef,
+        ALIAS    => undef,
+        FIELD    => undef,
+        @_
+    );
+
+    my $func = $args{'FUNCTION'};
+    return $func unless $args{'FIELD'};
+
+    my $field = ($args{'ALIAS'} || 'main') .'.'. $args{'FIELD'};
+
+    if ( $func =~ /^DISTINCT\s*COUNT$/i ) {
+        $func = "COUNT(DISTINCT $field)";
+    }
+
+    # If we want to substitute
+    elsif ( $func =~ s/\?/$field/g ) {
+        # no need to do anything, we already replaced
+    }
+
+    # If we want to call a simple function on the column
+    elsif ( $func !~ /\(/ )  {
+        $func = "\U$func\E($field)";
+    }
+
+    return $func;
+}
+
 
 
 

commit 7de45176471c8781edf3809c890a0acdf53d6143
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue May 24 23:43:25 2011 +0400

    make it possible to pass FUNCTION into Limit method

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index d4685be..00a1da1 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -799,9 +799,10 @@ sub Limit {
     my $self = shift;
     my %args = (
         TABLE           => $self->Table,
+        ALIAS           => undef,
         FIELD           => undef,
+        FUNCTION        => undef,
         VALUE           => undef,
-        ALIAS           => undef,
         QUOTEVALUE      => 1,
         ENTRYAGGREGATOR => undef,
         CASESENSITIVE   => undef,
@@ -872,6 +873,7 @@ sub _GenericRestriction {
     my $self = shift;
     my %args = ( TABLE           => $self->Table,
                  FIELD           => undef,
+                 FUNCTION        => undef,
                  VALUE           => undef,
                  ALIAS           => undef,
                  LEFTJOIN        => undef,
@@ -945,6 +947,10 @@ sub _GenericRestriction {
 
     }
 
+    if ( $args{'FUNCTION'} ) {
+        $QualifiedField = $self->CombineFunctionWithField( %args );
+    }
+
     my $clause = {
         field => $QualifiedField,
         op => $args{'OPERATOR'},

commit 09abefdd351783215ef0aa8b737c77da16cbcaf1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jun 2 02:23:54 2011 +0400

    extend GroupBy's FUNCTION arg with CombineFunctionWithField
    
    In old code when FUNCTION is provided then FIELD and ALIAS
    arguments are ignored. CombineFunctionWithField behaves
    in the old way if FIELD is empty.

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 00a1da1..8f08416 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -1160,7 +1160,7 @@ sub _GroupClause {
 		      );
         if ($rowhash{'FUNCTION'} ) {
             $clause .= ($clause ? ", " : " ");
-            $clause .= $rowhash{'FUNCTION'};
+            $clause .= $self->CombineFunctionWithField( %rowhash );
 
         }
         elsif ( ($rowhash{'ALIAS'}) and

commit 93a596817d088dbc61f74f9d0aaff0301b22b472
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jun 2 15:37:17 2011 +0400

    basic tests for group by

diff --git a/t/01searches.t b/t/01searches.t
index 9fdfc45..664fcb7 100644
--- a/t/01searches.t
+++ b/t/01searches.t
@@ -7,7 +7,7 @@ use Test::More;
 BEGIN { require "t/utils.pl" }
 our (@AvailableDrivers);
 
-use constant TESTS_PER_DRIVER => 117;
+use constant TESTS_PER_DRIVER => 122;
 
 my $total = scalar(@AvailableDrivers) * TESTS_PER_DRIVER;
 plan tests => $total;
@@ -183,8 +183,36 @@ SKIP: {
 	TODO: {
         local $TODO = 'we leave order_by after clean slate, fixing this results in many RT failures';
         is_deeply( $users_obj, $clean_obj, 'after CleanSlate looks like new object');
+    }
+
+    {
+	    $users_obj = TestApp::Users->new( $handle );
+        $users_obj->UnLimit;
+        $users_obj->GroupBy({FUNCTION => 'Login'});
+        $users_obj->OrderBy(FIELD => 'Login', ORDER => 'desc');
+        $users_obj->Column(FIELD => 'Login');
+        is( $users_obj->Count, $count_all, "group by / order by finds right amount");
+        $first_rec = $users_obj->First;
+        isa_ok( $first_rec, 'DBIx::SearchBuilder::Record', 'First returns record object' );
+        is( $first_rec->Login, 'obra', 'login is correct' );
+    }
+    {
 	    $users_obj = TestApp::Users->new( $handle );
+        $users_obj->UnLimit;
+        $users_obj->GroupBy({FUNCTION => 'SUBSTR(Login, 1, 1)', });
+        $users_obj->Column(FIELD => 'Login', FUNCTION => 'SUBSTR(Login, 1, 1)');
+        my @list = sort map $_->Login, @{ $users_obj->ItemsArrayRef };
+        is_deeply( \@list, [qw(a c g o)], 'correct values' );
     }
+    {
+	    $users_obj = TestApp::Users->new( $handle );
+        $users_obj->UnLimit;
+        $users_obj->GroupBy({FUNCTION => 'SUBSTR(?, 1, 1)', FIELD => 'Login'});
+        $users_obj->Column(FIELD => 'Login', FUNCTION => 'SUBSTR(?, 1, 1)');
+        my @list = sort map $_->Login, @{ $users_obj->ItemsArrayRef };
+        is_deeply( \@list, [qw(a c g o)], 'correct values' );
+    }
+    $users_obj = TestApp::Users->new( $handle );
 
 # Let's play a little with ENTRYAGGREGATOR
     # EA defaults to OR for the same field

commit e87defa5c1d15e5811bc4c9c52f833b9c71175d0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jun 2 15:38:21 2011 +0400

    no need in variable outside the loop

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 8f08416..1298944 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -1150,10 +1150,9 @@ sub _GroupClause {
     my $self = shift;
     return '' unless $self->{'group_by'};
 
-    my $row;
     my $clause;
 
-    foreach $row ( @{$self->{'group_by'}} ) {
+    foreach my $row ( @{$self->{'group_by'}} ) {
         my %rowhash = ( ALIAS => 'main',
 			FIELD => undef,
 			%$row

commit 73740d1d1711da8498e15f14f5a7e16c9da2450e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jun 2 15:42:27 2011 +0400

    simplify operations with $clause, no functional changes

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 1298944..5f2409f 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -1150,33 +1150,28 @@ sub _GroupClause {
     my $self = shift;
     return '' unless $self->{'group_by'};
 
-    my $clause;
-
+    my $clause = '';
     foreach my $row ( @{$self->{'group_by'}} ) {
         my %rowhash = ( ALIAS => 'main',
 			FIELD => undef,
 			%$row
 		      );
         if ($rowhash{'FUNCTION'} ) {
-            $clause .= ($clause ? ", " : " ");
+            $clause .= ', ' if $clause;
             $clause .= $self->CombineFunctionWithField( %rowhash );
 
         }
         elsif ( ($rowhash{'ALIAS'}) and
              ($rowhash{'FIELD'}) ) {
+            $clause .= ', ' if $clause;
 
-            $clause .= ($clause ? ", " : " ");
             $clause .= $rowhash{'ALIAS'} . ".";
             $clause .= $rowhash{'FIELD'};
         }
     }
 
-    if ($clause) {
-	return " GROUP BY" . $clause . " ";
-    }
-    else {
-	return '';
-    }
+    return '' unless $clause;
+    return " GROUP BY $clause ";
 }
 
 =head2 NewAlias

commit 6c6304130ba92bb6f9c9ed879a7553afb50c1ea4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jun 2 16:09:19 2011 +0400

    no need in %rowhash that is just copy of $row

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 5f2409f..289308f 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -1152,21 +1152,13 @@ sub _GroupClause {
 
     my $clause = '';
     foreach my $row ( @{$self->{'group_by'}} ) {
-        my %rowhash = ( ALIAS => 'main',
-			FIELD => undef,
-			%$row
-		      );
-        if ($rowhash{'FUNCTION'} ) {
+        if ( $row->{'FUNCTION'} ) {
             $clause .= ', ' if $clause;
-            $clause .= $self->CombineFunctionWithField( %rowhash );
-
+            $clause .= $self->CombineFunctionWithField( %$row );
         }
-        elsif ( ($rowhash{'ALIAS'}) and
-             ($rowhash{'FIELD'}) ) {
+        elsif ( $row->{'FIELD'} ) {
             $clause .= ', ' if $clause;
-
-            $clause .= $rowhash{'ALIAS'} . ".";
-            $clause .= $rowhash{'FIELD'};
+            $clause .= ($row->{'ALIAS'}||'main') .'.'. $row->{'FIELD'};
         }
     }
 

commit 0670a12730da1d0af0d2a7b81bb143235a83b04a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jun 2 22:02:49 2011 +0400

    process FIELD without FUNCTION in CombineFunctionWithField
    
    if FIELD is not set then return FUNCTION or undef
    
    if FUNCTION is not set then return ALIAS.FIELD
    
    otherwise process like before

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 289308f..5fa5c7d 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -1603,11 +1603,14 @@ sub CombineFunctionWithField {
         @_
     );
 
-    my $func = $args{'FUNCTION'};
-    return $func unless $args{'FIELD'};
+    unless ( $args{'FIELD'} ) {
+        return $args{'FUNCTION'} || undef;
+    }
 
     my $field = ($args{'ALIAS'} || 'main') .'.'. $args{'FIELD'};
+    return $field unless $args{'FUNCTION'};
 
+    my $func = $args{'FUNCTION'};
     if ( $func =~ /^DISTINCT\s*COUNT$/i ) {
         $func = "COUNT(DISTINCT $field)";
     }

commit 4d26f383e9ea413e261ef9862aae5ea2e4131484
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jun 2 22:21:39 2011 +0400

    use new CombineFunctionWithField, shrink code

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 5fa5c7d..315c86f 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -1152,14 +1152,11 @@ sub _GroupClause {
 
     my $clause = '';
     foreach my $row ( @{$self->{'group_by'}} ) {
-        if ( $row->{'FUNCTION'} ) {
-            $clause .= ', ' if $clause;
-            $clause .= $self->CombineFunctionWithField( %$row );
-        }
-        elsif ( $row->{'FIELD'} ) {
-            $clause .= ', ' if $clause;
-            $clause .= ($row->{'ALIAS'}||'main') .'.'. $row->{'FIELD'};
-        }
+        my $part = $self->CombineFunctionWithField( %$row )
+            or next;
+
+        $clause .= ', ' if $clause;
+        $clause .= $part;
     }
 
     return '' unless $clause;
@@ -1561,16 +1558,7 @@ sub Column {
 
     $args{'ALIAS'} ||= 'main';
 
-    my $name;
-    if ( $args{FUNCTION} ) {
-        $name = $self->CombineFunctionWithField( %args );
-    }
-    elsif ( $args{FIELD} ) {
-        $name = $args{'ALIAS'} .'.'. $args{'FIELD'};
-    }
-    else {
-        $name = 'NULL';
-    }
+    my $name = $self->CombineFunctionWithField( %args ) || 'NULL';
 
     my $column;
     if (

commit b214830b2f3abbb5a90c79a92e43c285bd4f4c10
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jun 2 23:05:12 2011 +0400

    change how we use CombineFunctionWithField in Limit
    
    apply it unconditionaly as now it handles FIELD just fine
    
    apply it earlier as CaseInsensetivity relate code can wrap
    field into a function

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 315c86f..e57b774 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -917,8 +917,7 @@ sub _GenericRestriction {
     # Set this to the name of the field and the alias, unless we've been
     # handed a subclause name
 
-    my $QualifiedField = $args{'ALIAS'} . "." . $args{'FIELD'};
-    my $ClauseId = $args{'SUBCLAUSE'} || $QualifiedField;
+    my $ClauseId = $args{'SUBCLAUSE'} || ($args{'ALIAS'} . "." . $args{'FIELD'});
 
     # If we're trying to get a leftjoin restriction, lets set
     # $restriction to point htere. otherwise, lets construct normally
@@ -935,6 +934,8 @@ sub _GenericRestriction {
         $restriction = $self->{'restrictions'}{ $ClauseId } ||= [];
     }
 
+    my $QualifiedField = $self->CombineFunctionWithField( %args );
+
     # If it's a new value or we're overwriting this sort of restriction,
 
     if ( $self->_Handle->CaseSensitive && defined $args{'VALUE'} && $args{'VALUE'} ne ''  && $args{'VALUE'} ne "''" && ($args{'OPERATOR'} !~/IS/ && $args{'VALUE'} !~ /^null$/i)) {
@@ -947,10 +948,6 @@ sub _GenericRestriction {
 
     }
 
-    if ( $args{'FUNCTION'} ) {
-        $QualifiedField = $self->CombineFunctionWithField( %args );
-    }
-
     my $clause = {
         field => $QualifiedField,
         op => $args{'OPERATOR'},

commit a3fc5bf80e90331b2ea4978a76799cda094ff094
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jun 3 03:16:29 2011 +0400

    don't turn NULL into NULL(ALIAS.FIELD)

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index e57b774..5515197 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -1606,7 +1606,7 @@ sub CombineFunctionWithField {
     }
 
     # If we want to call a simple function on the column
-    elsif ( $func !~ /\(/ )  {
+    elsif ( $func !~ /\(/ && lc($func) ne 'null' )  {
         $func = "\U$func\E($field)";
     }
 

commit 043abf1292302b5e43eaa9fc278db54db2da82d6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jun 3 03:43:29 2011 +0400

    documentation for CombineFunctionWithField

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 5515197..73b0263 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -1579,6 +1579,58 @@ sub Column {
     return $column;
 }
 
+=head2 CombineFunctionWithField
+
+Takes a hash with three optional arguments: FUNCTION, FIELD and ALIAS.
+
+Returns SQL with all three arguments combined according to the following
+rules.
+
+=over 4
+
+=item * FUNCTION or undef returned when FIELD is not provided
+
+=item * 'main' ALIAS is used if not provided
+
+=item * ALIAS.FIELD returned when FUNCTION is not provided
+
+=item * NULL returned if FUNCTION is 'NULL'
+
+=item * If FUNCTION contains '?' (question marks) then they are
+replaced with ALIAS.FIELD and result returned.
+
+=item * If FUNCTION has no '(' (opening parenthesis) then
+ALIAS.FIELD is appended in parentheses and returned.
+
+=back
+
+Examples:
+
+    ()
+    undef
+
+    (FUNCTION => 'FOO')
+    'FOO'
+
+    (FIELD => 'foo')
+    'main.foo'
+
+    (ALIAS => 'bar', FIELD => 'foo')
+    'bar.foo'
+
+    (FUNCTION => 'FOO(?, ?)', FIELD => 'bar')
+    'FOO(main.bar, main.bar)'
+
+    (FUNCTION => 'FOO', FIELD => 'bar')
+    'FOO(main.bar)'
+
+    (FUNCTION => 'NULL', FIELD => 'bar')
+    'NULL'
+
+=cut
+
+
+
 sub CombineFunctionWithField {
     my $self = shift;
     my %args = (

commit 1942aea26b1b3a6c343867e982264fc1bb5451c8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jun 3 03:57:50 2011 +0400

    docs for subs using CombineFunctionWithField

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index 73b0263..dd7922d 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -727,6 +727,11 @@ in the expression. )
 
 Column to be checked against.
 
+=item FUNCTION
+
+Function that should be checked against or applied to the FIELD before
+check. See L</CombineFunctionWithField> for rules.
+
 =item VALUE
 
 Should always be set and will always be quoted. 
@@ -1125,7 +1130,8 @@ sub _OrderClause {
 
 =head2 GroupByCols ARRAY_OF_HASHES
 
-Each hash contains the keys ALIAS and FIELD. ALIAS defaults to 'main' if ignored.
+Each hash contains the keys FIELD, FUNCTION and ALIAS. Hash
+combined into SQL with L</CombineFunctionWithField>.
 
 =cut
 
@@ -1492,8 +1498,7 @@ arguments:
 
 =item FIELD
 
-Column name to fetch or apply function to.  This can be omitted if a
-FUNCTION is given which is not a function of a field.
+Column name to fetch or apply function to.
 
 =item ALIAS
 
@@ -1501,29 +1506,11 @@ Alias of a table the field is in; defaults to C<main>
 
 =item FUNCTION
 
-A SQL function that should be selected as the result.  If a FIELD is
-provided, then it is inserted into the function according to the
-following rules:
-
-=over 4
-
-=item *
-
-If the text of the function contains a '?' (question mark), then it is
-replaced with qualified FIELD.
-
-=item *
-
-If the text of the function has no '(' (opening parenthesis), then the
-qualified FIELD is appended in parentheses to the text.
-
-=item *
-
-Otherwise, the function is inserted verbatim, with no substitution.
+A SQL function that should be selected instead of FIELD or applied to it.
 
 =back
 
-=back
+Above arguments combined according to L</CombineFunctionWithField>.
 
 If a FIELD is provided and it is in this table (ALIAS is 'main'), then
 the column named FIELD and can be accessed as usual by accessors:

commit c7178fa1e26861d119792623c508c5abc10feefa
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jun 3 04:19:45 2011 +0400

    tests for FUNCTION argument in methods

diff --git a/t/02searches_function.t b/t/02searches_function.t
new file mode 100644
index 0000000..86a1847
--- /dev/null
+++ b/t/02searches_function.t
@@ -0,0 +1,377 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Test::More;
+
+BEGIN { require "t/utils.pl" }
+our (@AvailableDrivers);
+
+use constant TESTS_PER_DRIVER => 18;
+
+my $total = scalar(@AvailableDrivers) * TESTS_PER_DRIVER;
+plan tests => $total;
+
+foreach my $d ( @AvailableDrivers ) {
+SKIP: {
+    unless( has_schema( 'TestApp', $d ) ) {
+        skip "No schema for '$d' driver", TESTS_PER_DRIVER;
+    }
+    unless( should_test( $d ) ) {
+        skip "ENV is not defined for driver '$d'", TESTS_PER_DRIVER;
+    }
+
+    my $handle = get_handle( $d );
+    connect_handle( $handle );
+    isa_ok($handle->dbh, 'DBI::db');
+
+    my $ret = init_schema( 'TestApp', $handle );
+    isa_ok($ret, 'DBI::st', "Inserted the schema. got a statement handle back");
+
+    my $count_users = init_data( 'TestApp::User', $handle );
+    ok( $count_users,  "init users data" );
+    my $count_groups = init_data( 'TestApp::Group', $handle );
+    ok( $count_groups,  "init groups data" );
+    my $count_us2gs = init_data( 'TestApp::UsersToGroup', $handle );
+    ok( $count_us2gs,  "init users&groups relations data" );
+
+    my $clean_obj = TestApp::Users->new( $handle );
+
+diag "FUNCTION with ? in Limit" if $ENV{'TEST_VERBOSE'};
+{
+    my $users_obj = $clean_obj->Clone;
+    $users_obj->Limit( FUNCTION => 'SUBSTR(?, 1, 1)', FIELD => 'Login', VALUE => 'I' );
+    is( $users_obj->Count, 1, "only one value" );
+    is( $users_obj->First->Login, 'Ivan', "three users are members of the groups" );
+}
+
+diag "make sure case insensitive works" if $ENV{'TEST_VERBOSE'}; 
+{
+    my $users_obj = $clean_obj->Clone;
+    $users_obj->Limit( FUNCTION => 'SUBSTR(?, 1, 1)', FIELD => 'Login', VALUE => 'i' );
+    is( $users_obj->Count, 1, "only one value" );
+    is( $users_obj->First->Login, 'Ivan', "three users are members of the groups" );
+}
+
+diag "FUNCTION without ?, but with () in Limit" if $ENV{'TEST_VERBOSE'};
+{
+    my $users_obj = $clean_obj->Clone;
+    $users_obj->Limit( FUNCTION => 'SUBSTR(main.Login, 1, 1)', FIELD => 'Login', VALUE => 'I' );
+    is( $users_obj->Count, 1, "only one value" );
+    is( $users_obj->First->Login, 'Ivan', "three users are members of the groups" );
+}
+
+diag "FUNCTION with ? in Column" if $ENV{'TEST_VERBOSE'};
+{
+    my $users_obj = $clean_obj->Clone;
+    $users_obj->UnLimit;
+    $users_obj->Column(FIELD => 'id');
+    my $alias = $users_obj->Column(FIELD => 'Login', FUNCTION => 'SUBSTR(?, 1, 1)');
+    is( $alias, 'Login' );
+
+    is_deeply(
+        [sort map $_->Login, @{ $users_obj->ItemsArrayRef } ],
+        [sort qw(a B I j)],
+        'correct values',
+    );
+}
+
+diag "FUNCTION without ?, but with () in Column" if $ENV{'TEST_VERBOSE'};
+{
+    my $users_obj = $clean_obj->Clone;
+    $users_obj->UnLimit;
+    $users_obj->Column(FIELD => 'id');
+    my $alias = $users_obj->Column(FIELD => 'Login', FUNCTION => 'SUBSTR(main.Login, 1, 1)');
+    is( $alias, 'Login' );
+
+    is_deeply(
+        [sort map $_->Login, @{ $users_obj->ItemsArrayRef } ],
+        [sort qw(a B I j)],
+        'correct values',
+    );
+}
+
+diag "NULL FUNCTION in Column" if $ENV{'TEST_VERBOSE'};
+{
+    my $users_obj = $clean_obj->Clone;
+    $users_obj->UnLimit;
+    $users_obj->Column(FIELD => 'id');
+    $users_obj->Column(FIELD => 'Login', FUNCTION => 'NULL');
+    is_deeply(
+        [ map $_->Login, @{ $users_obj->ItemsArrayRef } ],
+        [(undef)x4],
+        'correct values',
+    );
+}
+
+diag "FUNCTION w/0 ? and () in Column" if $ENV{'TEST_VERBOSE'};
+{
+    my $users_obj = $clean_obj->Clone;
+    $users_obj->UnLimit;
+    my $u2g_alias = $users_obj->Join(
+        TYPE   => 'LEFT',
+        FIELD1 => 'id',
+        TABLE2 => 'UsersToGroups',
+        FIELD2 => 'UserId',
+    );
+    $users_obj->GroupBy({FIELD => 'Login'});
+    $users_obj->Column(FIELD => 'Login');
+    my $column_alias = $users_obj->Column(FIELD => 'id', ALIAS => $u2g_alias, FUNCTION => 'COUNT');
+    isnt( $column_alias, 'id' );
+
+    is_deeply(
+        { map { $_->Login => $_->_Value($column_alias) } @{ $users_obj->ItemsArrayRef } },
+        { Ivan => 2, john => 1, Bob => 0, aurelia => 1 },
+        'correct values',
+    );
+}
+
+    cleanup_schema( 'TestApp', $handle );
+
+}} # SKIP, foreach blocks
+
+1;
+
+
+package TestApp;
+sub schema_sqlite {
+[
+q{
+CREATE TABLE Users (
+    id integer primary key,
+    Login varchar(36)
+) },
+q{
+CREATE TABLE UsersToGroups (
+    id integer primary key,
+    UserId  integer,
+    GroupId integer
+) },
+q{
+CREATE TABLE Groups (
+    id integer primary key,
+    Name varchar(36)
+) },
+]
+}
+
+sub schema_mysql {
+[
+q{
+CREATE TEMPORARY TABLE Users (
+    id integer primary key AUTO_INCREMENT,
+    Login varchar(36)
+) },
+q{
+CREATE TEMPORARY TABLE UsersToGroups (
+    id integer primary key AUTO_INCREMENT,
+    UserId  integer,
+    GroupId integer
+) },
+q{
+CREATE TEMPORARY TABLE Groups (
+    id integer primary key AUTO_INCREMENT,
+    Name varchar(36)
+) },
+]
+}
+
+sub schema_pg {
+[
+q{
+CREATE TEMPORARY TABLE Users (
+    id serial primary key,
+    Login varchar(36)
+) },
+q{
+CREATE TEMPORARY TABLE UsersToGroups (
+    id serial primary key,
+    UserId integer,
+    GroupId integer
+) },
+q{
+CREATE TEMPORARY TABLE Groups (
+    id serial primary key,
+    Name varchar(36)
+) },
+]
+}
+
+sub schema_oracle { [
+    "CREATE SEQUENCE Users_seq",
+    "CREATE TABLE Users (
+        id integer CONSTRAINT Users_Key PRIMARY KEY,
+        Login varchar(36)
+    )",
+    "CREATE SEQUENCE UsersToGroups_seq",
+    "CREATE TABLE UsersToGroups (
+        id integer CONSTRAINT UsersToGroups_Key PRIMARY KEY,
+        UserId integer,
+        GroupId integer
+    )",
+    "CREATE SEQUENCE Groups_seq",
+    "CREATE TABLE Groups (
+        id integer CONSTRAINT Groups_Key PRIMARY KEY,
+        Name varchar(36)
+    )",
+] }
+
+sub cleanup_schema_oracle { [
+    "DROP SEQUENCE Users_seq",
+    "DROP TABLE Users", 
+    "DROP SEQUENCE Groups_seq",
+    "DROP TABLE Groups", 
+    "DROP SEQUENCE UsersToGroups_seq",
+    "DROP TABLE UsersToGroups", 
+] }
+
+package TestApp::User;
+
+use base $ENV{SB_TEST_CACHABLE}?
+    qw/DBIx::SearchBuilder::Record::Cachable/:
+    qw/DBIx::SearchBuilder::Record/;
+
+sub _Init {
+    my $self = shift;
+    my $handle = shift;
+    $self->Table('Users');
+    $self->_Handle($handle);
+}
+
+sub _ClassAccessible {
+    {   
+        
+        id =>
+        {read => 1, type => 'int(11)'}, 
+        Login => 
+        {read => 1, write => 1, type => 'varchar(36)'},
+
+    }
+}
+
+sub init_data {
+    return (
+    [ 'Login' ],
+
+    [ 'Ivan' ],
+    [ 'john' ],
+    [ 'Bob' ],
+    [ 'aurelia' ],
+    );
+}
+
+package TestApp::Users;
+
+use base qw/DBIx::SearchBuilder/;
+
+sub _Init {
+    my $self = shift;
+    $self->SUPER::_Init( Handle => shift );
+    $self->Table('Users');
+}
+
+sub NewItem
+{
+    my $self = shift;
+    return TestApp::User->new( $self->_Handle );
+}
+
+1;
+
+package TestApp::Group;
+
+use base $ENV{SB_TEST_CACHABLE}?
+    qw/DBIx::SearchBuilder::Record::Cachable/:
+    qw/DBIx::SearchBuilder::Record/;
+
+sub _Init {
+    my $self = shift;
+    my $handle = shift;
+    $self->Table('Groups');
+    $self->_Handle($handle);
+}
+
+sub _ClassAccessible {
+    {   
+        id =>
+        {read => 1, type => 'int(11)'}, 
+        Name => 
+        {read => 1, write => 1, type => 'varchar(36)'},
+    }
+}
+
+sub init_data {
+    return (
+    [ 'Name' ],
+
+    [ 'Developers' ],
+    [ 'Sales' ],
+    [ 'Support' ],
+    );
+}
+
+package TestApp::Groups;
+
+use base qw/DBIx::SearchBuilder/;
+
+sub _Init {
+    my $self = shift;
+    $self->SUPER::_Init( Handle => shift );
+    $self->Table('Groups');
+}
+
+sub NewItem { return TestApp::Group->new( (shift)->_Handle ) }
+
+1;
+
+package TestApp::UsersToGroup;
+
+use base $ENV{SB_TEST_CACHABLE}?
+    qw/DBIx::SearchBuilder::Record::Cachable/:
+    qw/DBIx::SearchBuilder::Record/;
+
+sub _Init {
+    my $self = shift;
+    my $handle = shift;
+    $self->Table('UsersToGroups');
+    $self->_Handle($handle);
+}
+
+sub _ClassAccessible {
+    {   
+        
+        id =>
+        {read => 1, type => 'int(11)'}, 
+        UserId =>
+        {read => 1, type => 'int(11)'}, 
+        GroupId =>
+        {read => 1, type => 'int(11)'}, 
+    }
+}
+
+sub init_data {
+    return (
+    [ 'GroupId',    'UserId' ],
+# dev group
+    [ 1,        1 ],
+    [ 1,        2 ],
+    [ 1,        4 ],
+# sales
+#    [ 2,        0 ],
+# support
+    [ 3,        1 ],
+    );
+}
+
+package TestApp::UsersToGroups;
+
+use base qw/DBIx::SearchBuilder/;
+
+sub _Init {
+    my $self = shift;
+    $self->Table('UsersToGroups');
+    return $self->SUPER::_Init( Handle => shift );
+}
+
+sub NewItem { return TestApp::UsersToGroup->new( (shift)->_Handle ) }
+
+1;

-----------------------------------------------------------------------



More information about the Bps-public-commit mailing list