use MooseX::Types::Moose(':all');
use Scalar::Util('reftype');
use Data::Dumper;
+use Catalyst::Controller::DBIC::API::Validator;
use namespace::autoclean;
use Catalyst::Controller::DBIC::API::JoinBuilder;
-
=attribute_private search_validator
A Catalyst::Controller::DBIC::API::Validator instance used solely to validate search parameters
=cut
-with 'MooseX::Role::BuildInstanceOf' =>
-{
- 'target' => 'Catalyst::Controller::DBIC::API::Validator',
- 'prefix' => 'search_validator',
-};
-
=attribute_private select_validator
A Catalyst::Controller::DBIC::API::Validator instance used solely to validate select parameters
=cut
-with 'MooseX::Role::BuildInstanceOf' =>
-{
- 'target' => 'Catalyst::Controller::DBIC::API::Validator',
- 'prefix' => 'select_validator',
-};
-
=attribute_private prefetch_validator
A Catalyst::Controller::DBIC::API::Validator instance used solely to validate prefetch parameters
=cut
-with 'MooseX::Role::BuildInstanceOf' =>
-{
- 'target' => 'Catalyst::Controller::DBIC::API::Validator',
- 'prefix' => 'prefetch_validator',
-};
+has [qw( search_validator select_validator )] => (
+ is => 'ro',
+ isa => 'Catalyst::Controller::DBIC::API::Validator',
+ lazy => 1,
+ builder => '_build_validator',
+);
+
+sub _build_validator {
+ return Catalyst::Controller::DBIC::API::Validator->new;
+}
parameter static => ( isa => Bool, default => 0 );
role {
-
+
my $p = shift;
-
+
if($p->static)
{
- requires qw/check_has_relation check_column_relation/;
+ requires qw/check_has_relation check_column_relation prefetch_allows /;
}
else
{
predicate => 'has_page',
);
+=attribute_public offset is ro, isa: Int
+
+offset specifies where to start the paged result (think SQL LIMIT)
+
+=cut
+
+ has 'offset' =>
+ (
+ is => 'ro',
+ writer => '_set_offset',
+ isa => Int,
+ predicate => 'has_offset',
+ );
+
=attribute_public ordered_by is: ro, isa: L<Catalyst::Controller::DBIC::API::Types/OrderedBy>
ordered_by is passed to ->search to determine sorting
(
is => 'ro',
writer => '_set_prefetch',
- isa => Prefetch,
+ isa => Prefetch,
default => sub { $p->static ? [] : undef },
coerce => 1,
trigger => sub
{
my ($self, $new) = @_;
- if($self->has_prefetch_allows and @{$self->prefetch_allows})
- {
- foreach my $pf (@$new)
- {
- if(HashRef->check($pf))
- {
- die qq|'${\Dumper($pf)}' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}|
- unless $self->prefetch_validator->validate($pf)->[0];
- }
- else
- {
- die qq|'$pf' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}|
- unless $self->prefetch_validator->validate({$pf => 1})->[0];
- }
- }
- }
- else
- {
- return if not defined($new);
- die 'Prefetching is not allowed' if @$new;
- }
- },
- );
-
-=attribute_public prefetch_allows is: ro, isa: ArrayRef[ArrayRef|Str|HashRef]
-
-prefetch_allows limits what relations may be prefetched when executing searches with joins. 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.
-
-Like the synopsis in DBIC::API shows, you can declare a "template" of what is allowed (by using an '*'). Each element passed in, will be converted into a Data::DPath and added to the validator.
- prefetch_allows => [ 'cds', { cds => tracks }, { cds => producers } ] # to be explicit
- prefetch_allows => [ 'cds', { cds => '*' } ] # wildcard means the same thing
-
-=cut
-
- has prefetch_allows =>
- (
- is => 'ro',
- writer => '_set_prefetch_allows',
- isa => ArrayRef[ArrayRef|Str|HashRef],
- default => sub { [ ] },
- predicate => 'has_prefetch_allows',
- trigger => sub
- {
- my ($self, $new) = @_;
-
- sub check_rel {
- my ($self, $rel, $static) = @_;
- if(ArrayRef->check($rel))
- {
- foreach my $rel_sub (@$rel)
- {
- $self->check_rel($rel_sub, $static);
- }
- }
- elsif(HashRef->check($rel))
+ foreach my $pf (@$new)
+ {
+ if(HashRef->check($pf))
{
- while(my($k,$v) = each %$rel)
- {
- $self->check_has_relation($k, $v, undef, $static);
- }
- $self->prefetch_validator->load($rel);
+ die qq|'${\Dumper($pf)}' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}|
+ unless $self->prefetch_validator->validate($pf)->[0];
}
else
{
- $self->check_has_relation($rel, undef, undef, $static);
- $self->prefetch_validator->load($rel);
+ die qq|'$pf' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}|
+ unless $self->prefetch_validator->validate({$pf => 1})->[0];
}
}
-
- foreach my $rel (@$new)
- {
- $self->check_rel($rel, $p->static);
- }
},
);
},
);
-=attribute_public search is: ro, isa: HashRef
+=attribute_public search is: ro, isa: L<Catalyst::Controller::DBIC::API::Types/SearchParameters>
search contains the raw search parameters. Upon setting, a trigger will fire to format them, set search_parameters, and set search_attributes.
trigger => sub
{
my ($self, $new) = @_;
-
+
if($self->has_search_exposes and @{$self->search_exposes})
{
foreach my $foo (@$new)
}
}
}
-
+
my ($search_parameters, $search_attributes) = $self->generate_parameters_attributes($new);
$self->_set_search_parameters($search_parameters);
$self->_set_search_attributes($search_attributes);
},
);
-=attribute_public search_parameters is:ro, isa: L<Catalyst::Controller::DBIC::API/Types/SearchParameters>
+=attribute_public search_parameters is:ro, isa: L<Catalyst::Controller::DBIC::API::Types/SearchParameters>
search_parameters stores the formatted search parameters that will be passed to ->search
default => sub { $p->static ? [] : undef },
coerce => 1,
trigger => sub
- {
+ {
my ($self, $new) = @_;
if($self->has_select_exposes)
{
is => 'ro',
isa => HashRef,
writer => '_set_request_data',
+ predicate => 'has_request_data',
trigger => sub
{
my ($self, $new) = @_;
my $controller = $self->_controller;
+ return unless defined($new) && keys %$new;
$self->_set_prefetch($new->{$controller->prefetch_arg}) if exists $new->{$controller->prefetch_arg};
$self->_set_select($new->{$controller->select_arg}) if exists $new->{$controller->select_arg};
$self->_set_as($new->{$controller->as_arg}) if exists $new->{$controller->as_arg};
$self->_set_grouped_by($new->{$controller->grouped_by_arg}) if exists $new->{$controller->grouped_by_arg};
$self->_set_ordered_by($new->{$controller->ordered_by_arg}) if exists $new->{$controller->ordered_by_arg};
- $self->_set_search($new->{$controller->search_arg}) if exists $new->{$controller->search_arg};
$self->_set_count($new->{$controller->count_arg}) if exists $new->{$controller->count_arg};
$self->_set_page($new->{$controller->page_arg}) if exists $new->{$controller->page_arg};
+ $self->_set_offset($new->{$controller->offset_arg}) if exists $new->{$controller->offset_arg};
+ $self->_set_search($new->{$controller->search_arg}) if exists $new->{$controller->search_arg};
}
);
method format_search_parameters => sub
{
- $DB::single = 1;
my ($self, $params) = @_;
-
+
my $genparams = [];
foreach my $param (@$params)
method generate_column_parameters => sub
{
- $DB::single = 1;
my ($self, $source, $param, $join, $base) = @_;
$base ||= 'me';
- my $search_params;
+ my $search_params = {};
# build up condition
foreach my $column (keys %$param)
{
- if($source->has_relationship($column))
+ if ($source->has_relationship($column))
{
+ # check if the value isn't a hashref
unless (ref($param->{$column}) && reftype($param->{$column}) eq 'HASH')
{
$search_params->{join('.', $base, $column)} = $param->{$column};
next;
}
- %$search_params =
- %{
+ $search_params = { %$search_params, %{
$self->generate_column_parameters
(
$source->related_source($column),
Catalyst::Controller::DBIC::API::JoinBuilder->new(parent => $join, name => $column),
$column
)
- };
+ }};
}
- else
+ elsif ($source->has_column($column))
{
$search_params->{join('.', $base, $column)} = $param->{$column};
}
+ # might be a sql function instead of a column name
+ # e.g. {colname => {like => '%foo%'}}
+ else
+ {
+ # but only if it's not a hashref
+ unless (ref($param->{$column}) && reftype($param->{$column}) eq 'HASH') {
+ $search_params->{join('.', $base, $column)} = $param->{$column};
+ }
+ else {
+ die "$column is neither a relationship nor a column\n";
+ }
+ }
}
return $search_params;
method generate_parameters_attributes => sub
{
- $DB::single = 1;
my ($self, $args) = @_;
return ( $self->format_search_parameters($args), $self->search_attributes );
{
my ($self, $args) = @_;
my $static = $self->_controller;
- my $search_attributes =
+ my $search_attributes =
{
group_by => $self->grouped_by || ((scalar(@{$static->grouped_by})) ? $static->grouped_by : undef),
order_by => $self->ordered_by || ((scalar(@{$static->ordered_by})) ? $static->ordered_by : undef),
as => $self->as || ((scalar(@{$static->as})) ? $static->as : undef),
prefetch => $self->prefetch || $static->prefetch || undef,
rows => $self->count || $static->count,
- page => $self->page,
+ page => $static->page,
+ offset => $self->offset,
join => $self->build_joins,
};
- $search_attributes =
- {
+ if($self->has_page)
+ {
+ $search_attributes->{page} = $self->page;
+ }
+ elsif(!$self->has_page && defined($search_attributes->{offset}) && defined($search_attributes->{rows}))
+ {
+ $search_attributes->{page} = $search_attributes->{offset} / $search_attributes->{rows} + 1;
+ delete $search_attributes->{offset};
+ }
+
+
+ $search_attributes =
+ {
map { @$_ }
grep
{
- defined($_->[1])
- ?
+ defined($_->[1])
+ ?
(ref($_->[1]) && reftype($_->[1]) eq 'HASH' && keys %{$_->[1]})
|| (ref($_->[1]) && reftype($_->[1]) eq 'ARRAY' && @{$_->[1]})
|| length($_->[1])
if ($search_attributes->{page} && !$search_attributes->{rows}) {
die 'list_page can only be used with list_count';
}
-
+
if ($search_attributes->{select}) {
# make sure all columns have an alias to avoid ambiguous issues
# but allow non strings (eg. hashrefs for db procs like 'count')
}
return $search_attributes;
-
+
};
};