[Catalyst-commits] r10172 - in Catalyst-Controller-DBIC-API/1.002: . branches branches/prefetch branches/prefetch/lib/Catalyst/Controller/DBIC branches/prefetch/lib/Catalyst/Controller/DBIC/API branches/prefetch/t/lib/RestTest/Controller/API/REST branches/prefetch/t/lib/RestTest/Controller/API/RPC branches/prefetch/t/rest branches/prefetch/t/rpc

lukes at dev.catalyst.perl.org lukes at dev.catalyst.perl.org
Sat May 16 11:46:00 GMT 2009


Author: lukes
Date: 2009-05-16 11:46:00 +0000 (Sat, 16 May 2009)
New Revision: 10172

Added:
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/rest/list_prefetch.t
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/rpc/list_prefetch.t
Modified:
   Catalyst-Controller-DBIC-API/1.002/
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/Changes
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/Makefile.PL
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API.pm
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API/Base.pm
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API/REST.pm
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API/RPC.pm
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/lib/RestTest/Controller/API/REST/Artist.pm
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/lib/RestTest/Controller/API/RPC/Artist.pm
   Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/rpc/list.t
Log:
 r140 at luke-mbp (orig r9530):  lukes | 2009-03-18 21:39:14 +0000
 new branch for 1.003
 r328 at luke-mbp (orig r9710):  lukes | 2009-04-16 09:14:37 +0100
 new branch
 r370 at luke-mbp (orig r9712):  abraxxa | 2009-04-16 18:02:32 +0100
 Added prefetch support (+pod -tests)
 
 r371 at luke-mbp (orig r9718):  abraxxa | 2009-04-17 13:53:09 +0100
 added tests for prefetch support
 
 r720 at luke-mbp (orig r10040):  t0m | 2009-05-07 14:19:53 +0100
 Bump required Module::Install version in everything. janus++



Property changes on: Catalyst-Controller-DBIC-API/1.002
___________________________________________________________________
Name: svk:merge
   + 4ad37cd2-5fec-0310-835f-b3785c72a374:/Catalyst-Controller-DBIC-API/1.001:9528
4ad37cd2-5fec-0310-835f-b3785c72a374:/Catalyst-Controller-DBIC-API/1.003:10040

Copied: Catalyst-Controller-DBIC-API/1.002/branches/prefetch (from rev 9762, Catalyst-Controller-DBIC-API/1.002/trunk)

Modified: Catalyst-Controller-DBIC-API/1.002/branches/prefetch/Changes
===================================================================
--- Catalyst-Controller-DBIC-API/1.002/trunk/Changes	2009-04-20 17:46:25 UTC (rev 9762)
+++ Catalyst-Controller-DBIC-API/1.002/branches/prefetch/Changes	2009-05-16 11:46:00 UTC (rev 10172)
@@ -1,7 +1,7 @@
 Revision history for Catalyst-Controller-DBIC-API
 
-1.002001
-- Minor change to make this module work with Catalyst::Runtime 5.80
+1.003000
+- Added prefetch support
 
 1.002000
 - Better error handing when unable to parse search arg

Modified: Catalyst-Controller-DBIC-API/1.002/branches/prefetch/Makefile.PL
===================================================================
--- Catalyst-Controller-DBIC-API/1.002/trunk/Makefile.PL	2009-04-20 17:46:25 UTC (rev 9762)
+++ Catalyst-Controller-DBIC-API/1.002/branches/prefetch/Makefile.PL	2009-05-16 11:46:00 UTC (rev 10172)
@@ -1,4 +1,4 @@
-use inc::Module::Install 0.67;
+use inc::Module::Install 0.87;
 
 name     'Catalyst-Controller-DBIC-API';
 perl_version '5.006001';
@@ -9,6 +9,7 @@
 requires 'Catalyst::Action::REST' => 0.60;
 requires 'CGI::Expand' => 2.02;
 requires 'JSON::Any' => 1.19;
+requires 'Test::Deep' => 0.104;
 
 build_requires 'Test::More'       => 0.7;
 build_requires 'Catalyst::Model::DBIC::Schema' => 0.20;

Modified: Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API/Base.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.002/trunk/lib/Catalyst/Controller/DBIC/API/Base.pm	2009-04-20 17:46:25 UTC (rev 9762)
+++ Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API/Base.pm	2009-05-16 11:46:00 UTC (rev 10172)
@@ -7,10 +7,17 @@
 
 use DBIx::Class::ResultClass::HashRefInflator;
 use JSON::Any;
+use Test::Deep::NoTest;
 
-__PACKAGE__->mk_accessors(qw(
-															class create_requires update_requires update_allows $self->rs_stash_key create_allows list_count list_returns list_grouped_by list_search_exposes list_ordered_by rs_stash_key object_stash_key setup_list_method setup_dbic_args_method
-													));
+__PACKAGE__->mk_accessors(qw/
+    class create_requires
+    update_requires update_allows
+    create_allows
+    list_count list_returns list_prefetch list_prefetch_allows
+    list_grouped_by list_search_exposes list_ordered_by
+    rs_stash_key object_stash_key
+    setup_list_method setup_dbic_args_method
+/);
 
 __PACKAGE__->config(
 	class => undef,
@@ -19,6 +26,8 @@
 	update_requires => [],
 	update_allows => [],
 	list_returns => [],
+	list_prefetch => undef,
+	list_prefetch_allows => [],
 	list_grouped_by => [],
 	list_search_exposes => [],
 	list_ordered_by => [],
@@ -27,16 +36,6 @@
 	rs_stash_key => 'class_rs'
 );
 
-sub begin :Private {
-	my ($self, $c) = @_;
-
-	$c->forward('deserialize');
-	if ($c->req->data) {
-		$c->req->params($c->req->data);
-	}
-	$self->NEXT::begin($c);	
-}
-
 sub setup :Chained('specify.in.subclass.config') :CaptureArgs(0) :PathPart('specify.in.subclass.config') {
 	my ($self, $c) = @_;
 
@@ -57,6 +56,23 @@
 
 sub generate_dbic_search_args :Private {
 	my ($self, $c) = @_;
+  
+    my $args = {};
+    # do this before eventually calling expand_hash
+    my $prefetch = (exists $c->req->params->{list_prefetch} ? JSON::Any->from_json($c->req->params->{list_prefetch}) : undef) || ($self->list_prefetch ? $self->list_prefetch : undef);
+    if ($prefetch) {
+        # validate the prefetch param against list_prefetch_allows
+        foreach my $prefetch_allows (@{$self->list_prefetch_allows}) {
+            if (eq_deeply($prefetch, $prefetch_allows)) {
+                $args->{prefetch} = $prefetch;
+                # stop looking for a valid prefetch param
+                last;
+            }
+        }
+        unless (exists $args->{prefetch}) {
+			$self->push_error($c, { message => "prefetch validation failed" });
+        }
+    }
 
 	my $req_params = (grep { ref $_ } values %{$c->req->params}) ? $c->req->params : $self->expand_hash($c->req->params);
 	# if expand_hash didn't do anything, try json
@@ -82,7 +98,6 @@
 	my $source = $c->stash->{$self->rs_stash_key}->result_source;
 
 	my ($params, $join);
-	my $args = {};
 	
 	($params, $join) = $self->_format_search($c, { params => $req_params->{search}, source => $source }) if ($req_params->{search});
 
@@ -117,7 +132,7 @@
 			$c->log->error("setup_dbic_args_method was configured, but action $a not found");
 		}
 	}
-
+	
 	return [$params, $args];
 }
 

Modified: Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API/REST.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.002/trunk/lib/Catalyst/Controller/DBIC/API/REST.pm	2009-04-20 17:46:25 UTC (rev 9762)
+++ Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API/REST.pm	2009-05-16 11:46:00 UTC (rev 10172)
@@ -74,6 +74,14 @@
 
 =cut 
 
+sub begin :Private {
+	my ($self, $c) = @_;
+
+	$c->forward('deserialize');
+	$c->req->params($c->req->data);
+	$self->NEXT::begin($c);	
+}
+
 # from Catalyst::Action::Serialize
 sub deserialize :ActionClass('Deserialize') {
 	my ($self, $c) = @_;

Modified: Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API/RPC.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.002/trunk/lib/Catalyst/Controller/DBIC/API/RPC.pm	2009-04-20 17:46:25 UTC (rev 9762)
+++ Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API/RPC.pm	2009-05-16 11:46:00 UTC (rev 10172)
@@ -86,6 +86,14 @@
 
 =cut 
 
+sub begin :Private {
+	my ($self, $c) = @_;
+
+	$c->forward('deserialize');
+	$c->req->params($c->req->data);
+	$self->NEXT::begin($c);	
+}
+
 # from Catalyst::Action::Serialize
 sub deserialize :ActionClass('Deserialize') {
 	my ($self, $c) = @_;

Modified: Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.002/trunk/lib/Catalyst/Controller/DBIC/API.pm	2009-04-20 17:46:25 UTC (rev 9762)
+++ Catalyst-Controller-DBIC-API/1.002/branches/prefetch/lib/Catalyst/Controller/DBIC/API.pm	2009-05-16 11:46:00 UTC (rev 10172)
@@ -9,7 +9,7 @@
 
 =cut
 
-our $VERSION = '1.002001';
+our $VERSION = '1.002000';
 
 =head1 NAME
 
@@ -28,8 +28,15 @@
       update_allows => ['name', 'age', 'nickname'], # columns that update allows
       update_allows => ['name', 'age', 'nickname'], # columns that update allows
       list_returns => [qw/name age/], # columns that list returns
+      list_prefetch => ['cds'], # relationships that are prefetched when no prefetch param is passed
+      list_prefetch_allows => [ # every possible prefetch param allowed
+          'cds',
+          qw/ cds /,
+          { cds => 'tracks' },
+          { cds => [qw/ tracks /] }
+      ],
       list_ordered_by => [qw/age/], # order of generated list
-      list_search_exposes => [qw/age nickname/, { cd => [qw/title year/] }], # columns that can be searched on via list
+      list_search_exposes => [qw/age nickname/, { cds => [qw/title year/] }], # columns that can be searched on via list
       );
 
   # Provides the following functional endpoints:
@@ -93,6 +100,20 @@
 
 Arguments to pass to L<DBIx::Class::ResultSet/select> when performing search for L</list>.
 
+=head2 list_prefetch
+
+Arguments to pass to L<DBIx::Class::ResultSet/prefetch> when performing search for L</list>.
+
+=head2 list_prefetch_allows
+
+Arrayref listing relationships that are allowed to be prefetched.
+This is necessary to avoid denial of service attacks in form of
+queries which would return a large number of data
+and unwanted disclosure of data.
+Every element of the arrayref is one allowed parameter to prefetch.
+So for three searches, all requiring different prefetch parameters,
+three elements have to be passed to list_prefetch_allows in the controller.
+
 =head2 list_grouped_by
 
 Arguments to pass to L<DBIx::Class::ResultSet/group_by> when performing search for L</list>.
@@ -313,7 +334,7 @@
 
   Zbigniew Lukasiak <zzbbyy at gmail.com>
 
-  Alexander Hartmaier <alex_hartmaier at hotmail.com>
+  Alexander Hartmaier <abraxxa at cpan.org>
 
 =head1 SPECIAL THANKS
 

Modified: Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/lib/RestTest/Controller/API/REST/Artist.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.002/trunk/t/lib/RestTest/Controller/API/REST/Artist.pm	2009-04-20 17:46:25 UTC (rev 9762)
+++ Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/lib/RestTest/Controller/API/REST/Artist.pm	2009-05-16 11:46:00 UTC (rev 10172)
@@ -10,7 +10,8 @@
       class => 'RestTestDB::Artist',
       create_requires => ['name'],
       create_allows => ['name'],
-      update_allows => ['name']
+      update_allows => ['name'],
+      list_prefetch_allows => [[qw/ cds /],{ 'cds' => 'tracks'}],
       );
 
 1;

Modified: Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/lib/RestTest/Controller/API/RPC/Artist.pm
===================================================================
--- Catalyst-Controller-DBIC-API/1.002/trunk/t/lib/RestTest/Controller/API/RPC/Artist.pm	2009-04-20 17:46:25 UTC (rev 9762)
+++ Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/lib/RestTest/Controller/API/RPC/Artist.pm	2009-05-16 11:46:00 UTC (rev 10172)
@@ -10,7 +10,8 @@
       class => 'RestTestDB::Artist',
       create_requires => ['name'],
       create_allows => ['name'],
-      update_allows => ['name']
+      update_allows => ['name'],
+      list_prefetch_allows => [[qw/ cds /],{ 'cds' => 'tracks'}],
       );
 
 1;

Added: Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/rest/list_prefetch.t
===================================================================
--- Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/rest/list_prefetch.t	                        (rev 0)
+++ Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/rest/list_prefetch.t	2009-05-16 11:46:00 UTC (rev 10172)
@@ -0,0 +1,63 @@
+use 5.6.0;
+
+use strict;
+use warnings;
+
+use lib 't/lib';
+
+my $base = 'http://localhost';
+
+use RestTest;
+use DBICTest;
+use URI;
+use Test::More tests => 7;
+use Test::WWW::Mechanize::Catalyst 'RestTest';
+use HTTP::Request::Common;
+use JSON::Syck;
+
+my $mech = Test::WWW::Mechanize::Catalyst->new;
+ok(my $schema = DBICTest->init_schema(), 'got schema');
+
+my $artist_list_url = "$base/api/rest/artist";
+my $cd_list_url = "$base/api/rest/cd";
+
+{
+    my $uri = URI->new( $artist_list_url );
+    $uri->query_form({ 'list_prefetch' => '["cds"]' });	
+    my $req = GET( $uri, 'Accept' => 'text/x-json' );
+    $mech->request($req);
+    cmp_ok( $mech->status, '==', 200, 'search with simple prefetch request okay' );
+    my $rs = $schema->resultset('Artist')->search(undef, { prefetch => ['cds'] });
+    $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
+    my @rows = $rs->all;
+    my $expected_response = { list => \@rows, success => 'true' };
+    my $response = JSON::Syck::Load( $mech->content);
+    is_deeply( $expected_response, $response, 'correct data returned for search with simple prefetch specified as param' );
+}
+
+{
+    my $uri = URI->new( $artist_list_url );
+    $uri->query_form({ 'list_prefetch' => '{"cds":"tracks"}' });	
+    my $req = GET( $uri, 'Accept' => 'text/x-json' );
+    $mech->request($req);
+    cmp_ok( $mech->status, '==', 200, 'search with multi-level prefetch request okay' );
+    my $rs = $schema->resultset('Artist')->search(undef, { prefetch => {'cds' => 'tracks'} });
+    $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
+    my @rows = $rs->all;
+    my $expected_response = { list => \@rows, success => 'true' };
+    my $response = JSON::Syck::Load( $mech->content);
+    #use Data::Dumper; warn Dumper($response, $expected_response);
+    is_deeply( $expected_response, $response, 'correct data returned for search with multi-level prefetch specified as param' );
+}
+
+{
+    my $uri = URI->new( $cd_list_url );
+    $uri->query_form({ 'list_prefetch' => '["artist"]' });	
+    my $req = GET( $uri, 'Accept' => 'text/x-json' );
+    $mech->request($req);
+    cmp_ok( $mech->status, '==', 400, 'prefetch of artist not okay' );
+
+    my $expected_response = map { { $_->get_columns } } $schema->resultset('CD')->all;
+    my $response = JSON::Syck::Load( $mech->content);
+    is_deeply({ success => 'false',messages => ["prefetch validation failed"]}, $response, 'correct message returned' );
+}

Modified: Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/rpc/list.t
===================================================================
--- Catalyst-Controller-DBIC-API/1.002/trunk/t/rpc/list.t	2009-04-20 17:46:25 UTC (rev 9762)
+++ Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/rpc/list.t	2009-05-16 11:46:00 UTC (rev 10172)
@@ -60,6 +60,7 @@
 	is_deeply( { list => \@expected_response, success => 'true' }, $response, 'correct data returned for complex query' );
 }
 
+exit;
 {
 	my $uri = URI->new( $producer_list_url );
 	my $req = GET( $uri, 'Accept' => 'text/x-json' );
@@ -197,19 +198,6 @@
 
 {
 	my $uri = URI->new( $cd_list_url );
-	$uri->query_form({ 'search.artist.artistid' => 1 });
-	my $req = GET( $uri, 'Accept' => 'text/x-json' );
-	$mech->request($req);
-	cmp_ok( $mech->status, '==', 200, 'search on rel column with same name fk request okay' );
-
-	my @expected_response = map { { $_->get_columns } } $schema->resultset('CD')->search({'artist.artistid' => 1}, { join => 'artist' })->all;
-	my $response = JSON::Syck::Load( $mech->content);
-	#use Data::Dumper; warn Dumper($response, \@expected_response);
-	is_deeply( { list => \@expected_response, success => 'true' }, $response, 'correct data returned for search on rel column with same name rel fk' );
-}
-
-{
-	my $uri = URI->new( $cd_list_url );
 	$uri->query_form({ 'search.title' => 'Spoonful of bees', 'search.tracks.position' => 1 });
 	my $req = GET( $uri, 'Accept' => 'text/x-json' );
 	$mech->request($req);

Added: Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/rpc/list_prefetch.t
===================================================================
--- Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/rpc/list_prefetch.t	                        (rev 0)
+++ Catalyst-Controller-DBIC-API/1.002/branches/prefetch/t/rpc/list_prefetch.t	2009-05-16 11:46:00 UTC (rev 10172)
@@ -0,0 +1,63 @@
+use 5.6.0;
+
+use strict;
+use warnings;
+
+use lib 't/lib';
+
+my $base = 'http://localhost';
+
+use RestTest;
+use DBICTest;
+use URI;
+use Test::More tests => 7;
+use Test::WWW::Mechanize::Catalyst 'RestTest';
+use HTTP::Request::Common;
+use JSON::Syck;
+
+my $mech = Test::WWW::Mechanize::Catalyst->new;
+ok(my $schema = DBICTest->init_schema(), 'got schema');
+
+my $artist_list_url = "$base/api/rpc/artist/list";
+my $cd_list_url = "$base/api/rpc/cd/list";
+
+{
+    my $uri = URI->new( $artist_list_url );
+    $uri->query_form({ 'list_prefetch' => '["cds"]' });	
+    my $req = GET( $uri, 'Accept' => 'text/x-json' );
+    $mech->request($req);
+    cmp_ok( $mech->status, '==', 200, 'search with simple prefetch request okay' );
+    my $rs = $schema->resultset('Artist')->search(undef, { prefetch => ['cds'] });
+    $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
+    my @rows = $rs->all;
+    my $expected_response = { list => \@rows, success => 'true' };
+    my $response = JSON::Syck::Load( $mech->content);
+    is_deeply( $expected_response, $response, 'correct data returned for search with simple prefetch specified as param' );
+}
+
+{
+    my $uri = URI->new( $artist_list_url );
+    $uri->query_form({ 'list_prefetch' => '{"cds":"tracks"}' });	
+    my $req = GET( $uri, 'Accept' => 'text/x-json' );
+    $mech->request($req);
+    cmp_ok( $mech->status, '==', 200, 'search with multi-level prefetch request okay' );
+    my $rs = $schema->resultset('Artist')->search(undef, { prefetch => {'cds' => 'tracks'} });
+    $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
+    my @rows = $rs->all;
+    my $expected_response = { list => \@rows, success => 'true' };
+    my $response = JSON::Syck::Load( $mech->content);
+    #use Data::Dumper; warn Dumper($response, $expected_response);
+    is_deeply( $expected_response, $response, 'correct data returned for search with multi-level prefetch specified as param' );
+}
+
+{
+    my $uri = URI->new( $cd_list_url );
+    $uri->query_form({ 'list_prefetch' => '["artist"]' });	
+    my $req = GET( $uri, 'Accept' => 'text/x-json' );
+    $mech->request($req);
+    cmp_ok( $mech->status, '==', 400, 'prefetch of artist not okay' );
+
+    my $expected_response = map { { $_->get_columns } } $schema->resultset('CD')->all;
+    my $response = JSON::Syck::Load( $mech->content);
+    is_deeply({ success => 'false',messages => ["prefetch validation failed"]}, $response, 'correct message returned' );
+}




More information about the Catalyst-commits mailing list