[Bps-public-commit] dbix-searchbuilder branch, nulls-order, created. 1.63_01-40-gf31af72

Ruslan Zakirov ruz at bestpractical.com
Wed Apr 17 10:32:00 EDT 2013


The branch, nulls-order has been created
        at  f31af7254da09abb6e8260fcaee6ad0813b2e36b (commit)

- Log -----------------------------------------------------------------
commit f31af7254da09abb6e8260fcaee6ad0813b2e36b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Apr 16 20:37:39 2013 +0400

    Handle->NullsOrder
    
    Method to change how NULLs are sorted, for example NULLs can be
    treated as smallest values or as largest. Supported by Pg and Oracle.
    
    With a trick it's possible to add support in other DBs, but it's good
    step forward.

diff --git a/lib/DBIx/SearchBuilder.pm b/lib/DBIx/SearchBuilder.pm
index c75f42b..0113072 100755
--- a/lib/DBIx/SearchBuilder.pm
+++ b/lib/DBIx/SearchBuilder.pm
@@ -1131,6 +1131,8 @@ sub _OrderClause {
 
     return '' unless $self->{'order_by'};
 
+    my $nulls_order = $self->_Handle->NullsOrder;
+
     my $clause = '';
     foreach my $row ( @{$self->{'order_by'}} ) {
 
@@ -1141,9 +1143,11 @@ sub _OrderClause {
 		      );
         if ($rowhash{'ORDER'} && $rowhash{'ORDER'} =~ /^des/i) {
 	    $rowhash{'ORDER'} = "DESC";
+            $rowhash{'ORDER'} .= ' '. $nulls_order->{'DESC'} if $nulls_order;
         }
         else {
 	    $rowhash{'ORDER'} = "ASC";
+            $rowhash{'ORDER'} .= ' '. $nulls_order->{'ASC'} if $nulls_order;
         }
         $rowhash{'ALIAS'} = 'main' unless defined $rowhash{'ALIAS'};
 
diff --git a/lib/DBIx/SearchBuilder/Handle.pm b/lib/DBIx/SearchBuilder/Handle.pm
index fc4ff5e..078088b 100755
--- a/lib/DBIx/SearchBuilder/Handle.pm
+++ b/lib/DBIx/SearchBuilder/Handle.pm
@@ -1617,13 +1617,94 @@ sub DateTimeIntervalFunction {
 
 sub _DateTimeIntervalFunction { return 'NULL' }
 
+=head2 NullsOrder
+
+Sets order of NULLs when sorting columns when called with mode,
+but only if DB supports it. Modes:
+
+=over 4
+
+=item * small
+
+NULLs are smaller then anything else, so come first when order
+is ASC and last otherwise.
+
+=item * large
+
+NULLs are larger then anything else.
+
+=item * first
+
+NULLs are always first.
+
+=item * last
+
+NULLs are always last.
+
+=item * default
+
+Return back to DB's default behaviour.
+
+=back
+
+When called without argument returns metadata required to generate
+SQL.
+
+=cut
+
+sub NullsOrder {
+    my $self = shift;
+
+    unless ($self->HasSupportForNullsOrder) {
+        warn "No support for changing NULLs order" if @_;
+        return undef;
+    }
+
+    if ( @_ ) {
+        my $mode = shift || 'default';
+        if ( $mode eq 'default' ) {
+            delete $self->{'nulls_order'};
+        }
+        elsif ( $mode eq 'small' ) {
+            $self->{'nulls_order'} = { ASC => 'NULLS FIRST', DESC => 'NULLS LAST' };
+        }
+        elsif ( $mode eq 'large' ) {
+            $self->{'nulls_order'} = { ASC => 'NULLS LAST', DESC => 'NULLS FIRST' };
+        }
+        elsif ( $mode eq 'first' ) {
+            $self->{'nulls_order'} = { ASC => 'NULLS FIRST', DESC => 'NULLS FIRST' };
+        }
+        elsif ( $mode eq 'last' ) {
+            $self->{'nulls_order'} = { ASC => 'NULLS LAST', DESC => 'NULLS LAST' };
+        }
+        else {
+            warn "'$mode' is not supported NULLs ordering mode";
+            delete $self->{'nulls_order'};
+        }
+    }
+
+    return undef unless $self->{'nulls_order'};
+    return $self->{'nulls_order'};
+}
+
+=head2 HasSupportForNullsOrder
+
+Returns true value if DB supports adjusting NULLs order while sorting
+a column, for example C<ORDER BY Value ASC NULLS FIRST>.
+
+=cut
+
+sub HasSupportForNullsOrder {
+    return 0;
+}
+
+
 =head2 DESTROY
 
 When we get rid of the Searchbuilder::Handle, we need to disconnect from the database
 
 =cut
 
-  
 sub DESTROY {
   my $self = shift;
   $self->Disconnect if $self->{'DisconnectHandleOnDestroy'};
diff --git a/lib/DBIx/SearchBuilder/Handle/Oracle.pm b/lib/DBIx/SearchBuilder/Handle/Oracle.pm
index 318fdcc..add1ac6 100755
--- a/lib/DBIx/SearchBuilder/Handle/Oracle.pm
+++ b/lib/DBIx/SearchBuilder/Handle/Oracle.pm
@@ -442,6 +442,10 @@ sub _DateTimeIntervalFunction {
     return "ROUND(( CAST( $args{'To'} AS DATE ) - CAST( $args{'From'} AS DATE ) ) * 86400)";
 }
 
+sub HasSupportForNullsOrder {
+    return 1;
+}
+
 1;
 
 __END__
diff --git a/lib/DBIx/SearchBuilder/Handle/Pg.pm b/lib/DBIx/SearchBuilder/Handle/Pg.pm
index 70392f0..7f0cfd3 100755
--- a/lib/DBIx/SearchBuilder/Handle/Pg.pm
+++ b/lib/DBIx/SearchBuilder/Handle/Pg.pm
@@ -332,6 +332,10 @@ sub _DateTimeIntervalFunction {
     return "EXTRACT(EPOCH FROM AGE($args{'To'}, $args{'From'}))";
 }
 
+sub HasSupportForNullsOrder {
+    return 1;
+}
+
 1;
 
 __END__
diff --git a/t/02null_order.t b/t/02null_order.t
new file mode 100644
index 0000000..9feda47
--- /dev/null
+++ b/t/02null_order.t
@@ -0,0 +1,201 @@
+#!/usr/bin/perl -w
+
+
+use strict;
+use warnings;
+use Test::More;
+BEGIN { require "t/utils.pl" }
+our (@AvailableDrivers);
+
+use constant TESTS_PER_DRIVER => 11;
+
+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 );
+    unless ( $handle->HasSupportForNullsOrder ) {
+        skip "Feature is not supported by $d", TESTS_PER_DRIVER;
+    }
+    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_all = init_data( 'TestApp::User', $handle );
+    ok( $count_all,  "init users data" );
+
+	my $users_obj = TestApp::Users->new( $handle );
+    $users_obj->UnLimit;
+
+# NULLs are small
+    $handle->NullsOrder('small');
+    $users_obj->OrderBy(FIELD => 'Value', ORDER => 'ASC' );
+    is_deeply
+        [ map $_->Value, @{ $users_obj->ItemsArrayRef } ],
+        [ undef, 0, 1 ],
+    ;
+    $users_obj->OrderBy(FIELD => 'Value', ORDER => 'DESC' );
+    is_deeply
+        [ map $_->Value, @{ $users_obj->ItemsArrayRef } ],
+        [ 1, 0, undef ],
+    ;
+
+# NULLs are large
+    $handle->NullsOrder('large');
+    $users_obj->OrderBy(FIELD => 'Value', ORDER => 'ASC' );
+    is_deeply
+        [ map $_->Value, @{ $users_obj->ItemsArrayRef } ],
+        [ 0, 1, undef ],
+    ;
+    $users_obj->OrderBy(FIELD => 'Value', ORDER => 'DESC' );
+    is_deeply
+        [ map $_->Value, @{ $users_obj->ItemsArrayRef } ],
+        [ undef, 1, 0, ],
+    ;
+
+# NULLs are first
+    $handle->NullsOrder('first');
+    $users_obj->OrderBy(FIELD => 'Value', ORDER => 'ASC' );
+    is_deeply
+        [ map $_->Value, @{ $users_obj->ItemsArrayRef } ],
+        [ undef, 0, 1 ],
+    ;
+    $users_obj->OrderBy(FIELD => 'Value', ORDER => 'DESC' );
+    is_deeply
+        [ map $_->Value, @{ $users_obj->ItemsArrayRef } ],
+        [ undef, 1, 0, ],
+    ;
+
+# NULLs are last
+    $handle->NullsOrder('last');
+    $users_obj->OrderBy(FIELD => 'Value', ORDER => 'ASC' );
+    is_deeply
+        [ map $_->Value, @{ $users_obj->ItemsArrayRef } ],
+        [ 0, 1, undef ],
+    ;
+    $users_obj->OrderBy(FIELD => 'Value', ORDER => 'DESC' );
+    is_deeply
+        [ map $_->Value, @{ $users_obj->ItemsArrayRef } ],
+        [ 1, 0, undef ],
+    ;
+
+    cleanup_schema( 'TestApp', $handle );
+}} # SKIP, foreach blocks
+
+1;
+
+package TestApp;
+
+sub schema_mysql {[
+    "DROP TABLE IF EXISTS Users",
+<<EOF
+CREATE TABLE Users (
+    id integer AUTO_INCREMENT,
+    Value integer,
+    PRIMARY KEY (id)
+)
+EOF
+]}
+sub cleanup_schema_mysql { [
+    "DROP TABLE Users", 
+] }
+
+sub schema_pg {
+<<EOF;
+CREATE TEMPORARY TABLE Users (
+    id serial PRIMARY KEY,
+    Value integer
+)
+EOF
+
+}
+
+sub schema_sqlite {
+
+<<EOF;
+CREATE TABLE Users (
+    id integer primary key,
+    Value integer
+)
+EOF
+
+}
+
+sub schema_oracle { [
+    "CREATE SEQUENCE Users_seq",
+    "CREATE TABLE Users (
+        id integer CONSTRAINT Users_Key PRIMARY KEY,
+        Value integer
+    )",
+] }
+
+sub cleanup_schema_oracle { [
+    "DROP SEQUENCE Users_seq",
+    "DROP TABLE Users", 
+] }
+
+
+1;
+
+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)' }, 
+        Value =>
+        {read => 1, write => 1, type => 'int(11)' }, 
+    }
+}
+
+sub init_data {
+    return (
+    [ 'Value', ],
+    [ undef, ],
+    [ 0, ],
+    [ 1, ],
+    );
+}
+
+1;
+
+package TestApp::Users;
+
+# use TestApp::User;
+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;
+

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



More information about the Bps-public-commit mailing list