[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