/usr/local/CPAN/DBI-Easy/DBI/Easy/Record/Collection.pm
package DBI::Easy::Record::Collection;
use Class::Easy;
use base qw(DBI::Easy);
our $wrapper = 1;
has 'filter', is => 'rw', default => {};
has 'join_table', is => 'rw';
sub new_record {
my $self = shift;
my $params = shift || {};
my $rec_pack = $self->record_package;
my $rec = $rec_pack->new ({%$params, %{$self->filter || {}}});
}
sub natural_join {
my $self = shift;
my $join = join ' ', map {'natural join ' . $_->table_quoted} @_;
$self->join_table ($join);
}
sub make_sql_and_bind {
my $self = shift;
my $method = shift;
my $set;
my $where = {};
my $suffix = '';
my $bind_suffix;
my %args;
# legacy syntax
if (! defined $_[0] or ref $_[0]) {
$set = shift;
$where = shift || {};
$suffix = shift || '';
$bind_suffix = shift;
%args = (@_);
} else {
%args = (@_);
$where = delete $args{where} || {};
$set = delete $args{set};
$suffix = delete $args{suffix} || '';
$bind_suffix = delete $args{bind};
}
# if we call collection method from package name, we must create collection
# object automatically
$self = $self->new
unless ref $self;
my $filter = $self->filter;
my %params = (
where => [
$self->fields_to_columns ($filter),
$self->fields_to_columns ($where)
],
suffix => $suffix,
%args
);
if ($method eq 'sql_update') {
$params{set} = $self->fields_to_columns ($set);
}
# use Data::Dumper;
# warn "$method => " . Dumper \%params;
my ($select, $bind) = $self->$method (%params);
push @$bind, @{$bind_suffix || []};
debug 'sql: \'', $select, '\' => ', defined $bind ? join ', ', @$bind : '[empty]';
return ($select, $bind);
}
# legacy
sub list {
my $self = shift;
my $where = shift || {};
my $suffix = shift || '';
my $bind_suffix = shift || [];
my %params = @_;
return $self->records (where => $where, suffix => [$suffix, @$bind_suffix], %params);
}
sub records {
my $self = shift;
my $where;
my %params;
if (ref $_[0]) {
$where = shift;
%params = @_;
} else {
%params = @_;
$where = delete $params{where} || {};
}
my $suffix = '';
my $bind_suffix = [];
#TODO: REGRESSION FIX !!!
if ($params{suffix} and ref $params{suffix} and ref $params{suffix} eq 'ARRAY') {
$suffix = shift @{$params{suffix}} || '';
$bind_suffix = delete $params{suffix};
} elsif ($params{suffix}) {
$suffix = delete $params{suffix} || '';
}
my @fetch_params = $self->make_sql_and_bind ('sql_select', undef, $where, $suffix, $bind_suffix, %params);
if ($params{fetch_handler} and ref $params{fetch_handler} eq 'CODE') {
debug "fetch by record";
$self->fetch_handled (@fetch_params, sub {
my $row = shift;
my $rec = $self->record_package->new (column_values => $row);
return $params{fetch_handler}->($rec);
});
} else {
my $db_result = $self->fetch_arrayref (@fetch_params);
debug "result count: ", $#$db_result+1;
$self->columns_to_fields_in_place ($db_result);
return $db_result;
}
}
sub list_of_record_hashes {
my $self = shift;
my $records = $self->records (@_);
my $list_of_hashes = [map {$_->hash} @$records];
return $list_of_hashes;
}
sub update {
my $self = shift;
my ($sql, $bind) = $self->make_sql_and_bind ('sql_update', @_);
my $db_result = $self->no_fetch ($sql, $bind);
debug "rows affected: ", $db_result;
return $db_result;
}
sub count {
my $self = shift;
my ($select, $bind);
if (ref $_[0] or @_ % 2) { # make_sql_and_bind (set, where, suffix, bind)
($select, $bind) = $self->make_sql_and_bind ('sql_select_count', undef, @_);
} else { # make_sql_and_bind (set => set, where => where, ...)
($select, $bind) = $self->make_sql_and_bind ('sql_select_count', @_);
}
my $db_result = $self->fetch_single ($select, $bind);
debug "result count: ", $db_result;
return $db_result;
}
sub delete {
my $self = shift;
my ($select, $bind);
if (ref $_[0] or @_ % 2) { # make_sql_and_bind (set, where, suffix, bind)
($select, $bind) = $self->make_sql_and_bind ('sql_delete', undef, @_);
} else { # make_sql_and_bind (set => set, where => where, ...)
($select, $bind) = $self->make_sql_and_bind ('sql_delete', @_);
}
my $db_result = $self->no_fetch ($select, $bind);
debug "rows affected: ", $db_result;
return $db_result;
}
sub tree {
my $self = shift;
my $keys = shift;
my $where = shift;
my $suffix = shift;
my $ref = ref $self;
my $where_w_filter = $where;
my $filter = $self->filter;
$where_w_filter = {%$where, %$filter}
if defined $filter and ref $filter eq 'HASH';
my $where_prefixed = $self->fields_to_columns ($where_w_filter);
my ($select, $bind) = $self->sql_select (where => $where_prefixed, suffix => $suffix);
# warn $select, ' => ', defined $bind ? join ', ', @$bind : '[empty]';
my $db_result = $self->fetch_hashref ($select, $keys, $bind);
#my $db_result = $self->fetch_arrayref ($select, $bind);
$self->columns_to_fields_in_place ($db_result, $keys);
return $db_result;
}
sub item {
my $self = shift;
my $where = shift;
my $suffix = shift || '';
my $result = $self->list ($where, $suffix . ' limit 1');
# programmer must be warned about multiple values
return $result->[0];
}
sub new_record_from_request {
my $self = shift;
my $request = shift;
my $rec_pack = $self->record_package;
my $rec = $rec_pack->new ({%{$self->filter}});
$rec->apply_request_params ($request);
return $rec;
}
sub columns_to_fields_in_place {
my $self = shift;
my $rows = shift;
my $rec_pack = $self->record_package;
if (UNIVERSAL::isa ($rows, 'ARRAY')) {
foreach my $row_counter (0 .. $#$rows) {
my $row = $rows->[$row_counter];
$rows->[$row_counter] = $rec_pack->new (column_values => $row);
}
} elsif (UNIVERSAL::isa ($rows, 'HASH')) {
foreach my $row_key (keys %$rows) {
my $row = $rows->{$row_key};
$rows->{$row_key} = $rec_pack->new (column_values => $row);
}
}
}
our $MAX_LIMIT = 300;
sub ordered_list {
my $self = shift;
my $order = shift;
my $dir = shift;
my $limit = shift;
my $start = shift;
my $filter = shift;
my $bind = shift || [];
my $fields = $self->fields;
my $sort_col;
if (exists $fields->{$order}) {
$sort_col = $fields->{$order}->{quoted_column_name};
} elsif ($self->_pk_) {
# we assume primary key ordering unless ordered column known
$sort_col = $fields->{$self->_pk_}->{quoted_column_name};
}
if ($dir =~ /^(asc|desc)$/i) {
$dir = lc($1);
} else {
$dir = ''; # default sort
}
# When using LIMIT, it is important to use an ORDER BY clause that
# constrains the result rows into a unique order. Otherwise you will
# get an unpredictable subset of the query's rows. You might be asking
# for the tenth through twentieth rows, but tenth through twentieth
# in what ordering? The ordering is unknown, unless you specified ORDER BY.
if (!$sort_col or $start !~ /\d+/ or $limit !~ /\d+/) {
return {
count => 0,
error => "ordering-undefined"
};
}
$start =~ s/(\d+)/$1/;
$limit =~ s/(\d+)/$1/;
my $count = $self->count ($filter, '', $bind);
if ($start > $count) {
$start = $count - $limit;
$start = 0 if $start < 0;
}
if ($limit > $MAX_LIMIT or ! $limit > 0) { # try undef -)
$limit = $MAX_LIMIT;
}
my $suffix = "order by $sort_col $dir limit $limit offset $start";
# debug "suffix: $suffix";
my $list = $self->list ($filter, $suffix, $bind);
return {
items => $list,
total_count => $count,
version => 1,
};
}
# page_size, count, page_num, pages_to_show
sub pager {
my $self = shift;
my $param = shift;
my $page_size = $param->{page_size} || 20;
my %pager;
my $number_of_pages = int(($param->{count} + $page_size - 1) / $page_size);
$pager{pager_needed} = ($param->{count} > $page_size);
unless ($pager{pager_needed}) {
return;
}
my $page_number = $param->{page_num} || 0;
my $pages_to_show = $param->{pages_to_show} || 10;
my $quarter_to_show = int ($pages_to_show / 4);
my @pages;
if ($param->{count} <= $pages_to_show) {
return [1 .. $param->{count}];
}
if ($page_number <= $quarter_to_show * 2 + 1) {
return [
1 .. $quarter_to_show * 3 + 1,
undef,
$number_of_pages - $quarter_to_show + 1 .. $number_of_pages
];
}
if ($page_number >= $number_of_pages - ($quarter_to_show * 2 + 1)) {
return [
1 .. $quarter_to_show,
undef,
$number_of_pages - ($quarter_to_show * 3 + 1) .. $number_of_pages
];
}
return [
1 .. $quarter_to_show,
undef,
$page_number - $quarter_to_show .. $page_number + $quarter_to_show,
undef,
$number_of_pages - $quarter_to_show + 1 .. $number_of_pages
];
}
1;