File Coverage

blib/lib/Pinto/Difference.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             # ABSTRACT: Compute difference between two revisions
2              
3             package Pinto::Difference;
4              
5 2     2   52040 use Moose;
  0            
  0            
6             use MooseX::StrictConstructor;
7             use MooseX::Types::Moose qw(ArrayRef Bool);
8             use MooseX::MarkAsMethods ( autoclean => 1 );
9              
10             use Pinto::DifferenceEntry;
11             use Pinto::Constants qw(:diff);
12             use Pinto::Types qw(DiffStyle);
13             use Pinto::Util qw(itis default_diff_style);
14              
15             use overload ( q{""} => 'to_string' );
16              
17             #------------------------------------------------------------------------------
18              
19             our $VERSION = '0.09995'; # VERSION
20              
21             #------------------------------------------------------------------------------
22              
23             has left => (
24             is => 'ro',
25             isa => 'Pinto::Schema::Result::Revision',
26             required => 1,
27             );
28              
29             has right => (
30             is => 'ro',
31             isa => 'Pinto::Schema::Result::Revision',
32             required => 1,
33             );
34              
35             has entries => (
36             traits => [qw(Array)],
37             handles => { entries => 'elements' },
38             isa => ArrayRef ['Pinto::DifferenceEntry'],
39             builder => '_build_diffs',
40             init_arg => undef,
41             lazy => 1,
42             );
43              
44             has additions => (
45             traits => [qw(Array)],
46             handles => { additions => 'elements' },
47             isa => ArrayRef ['Pinto::DifferenceEntry'],
48             default => sub { [ grep { $_->op eq '+' } shift->entries ] },
49             init_arg => undef,
50             lazy => 1,
51             );
52              
53             has deletions => (
54             traits => [qw(Array)],
55             handles => { deletions => 'elements' },
56             isa => ArrayRef ['Pinto::DifferenceEntry'],
57             default => sub { [ grep { $_->op eq '-' } shift->entries ] },
58             init_arg => undef,
59             lazy => 1,
60             );
61              
62             has is_different => (
63             is => 'ro',
64             isa => Bool,
65             init_arg => undef,
66             default => sub { shift->entries > 0 },
67             lazy => 1,
68             );
69              
70             has style => (
71             is => 'ro',
72             isa => DiffStyle,
73             default => \&default_diff_style,
74             );
75              
76             #------------------------------------------------------------------------------
77              
78             around BUILDARGS => sub {
79             my $orig = shift;
80             my $class = shift;
81             my $args = $class->$orig(@_);
82              
83             # The left and right attributes can also be Stack objects.
84             # In those cases, we just use the head revision of the Stack
85              
86             for my $side (qw(left right)) {
87             if ( $args->{$side}->isa('Pinto::Schema::Result::Stack') ) {
88             $args->{$side} = $args->{$side}->head;
89             }
90             }
91              
92             return $args;
93             };
94              
95             #------------------------------------------------------------------------------
96              
97             sub _build_diffs {
98             my ($self) = @_;
99              
100             # We want to find the registrations that are "different" in either
101             # side. Two registrations are the same if they have the same values in
102             # the package, distribution, and is_pinned columns. So we use these
103             # columns to construct the keys of a hash. The value is the id of
104             # the registration. For a concise diff, we just use the distribution
105             # and is_pinned columns, which effectively groups the records so there
106             # is only one diff entry per distribution. In that case, the package
107             # referenced by the registration won't be meaningful.
108              
109             my @fields = $self->style eq $PINTO_DIFF_STYLE_DETAILED
110             ? qw(distribution package is_pinned)
111             : qw(distribution is_pinned);
112              
113             my $cb = sub {
114             my $value = $_[0]->id;
115             my $key = join '|', map { $_[0]->get_column($_) } @fields;
116             return ( $key => $value );
117             };
118              
119             my $attrs = { select => [ 'id', @fields ] };
120             my %left = $self->left->registrations( {}, $attrs )->as_hash($cb);
121             my %right = $self->right->registrations( {}, $attrs )->as_hash($cb);
122              
123             # Now that we have hashes representing the left and right, we use
124             # the keys as "sets" and compute the difference between them. Keys
125             # present on the right but not on the left have been added. And
126             # those present on left but not on the right have been deleted.
127              
128             my @add_ids = @right{ grep { not exists $left{$_} } keys %right };
129             my @del_ids = @left{ grep { not exists $right{$_} } keys %left };
130              
131             # Now we have the ids of all the registrations that were added or
132             # deleted between the left and right revisions. We use those ids to
133             # requery the database and construct full objects for each of them.
134              
135             my @adds = $self->_create_entries( '+', $self->right, \@add_ids );
136             my @dels = $self->_create_entries( '-', $self->left, \@del_ids );
137              
138             # Strictly speaking, the registrations are an unordered list. But
139             # the diff is more readable if we group registrations together by
140             # distribution name.
141              
142             my @diffs = sort @dels, @adds;
143              
144             return \@diffs;
145             }
146              
147             #------------------------------------------------------------------------------
148              
149             sub _create_entries {
150             my ( $self, $type, $side, $ids ) = @_;
151              
152             # The number of ids is potentially pretty big (1000's) and we
153             # can't use that many values in an IN clause. So we insert all
154             # those ids into a temporary table.
155              
156             my $tmp_tbl = "__diff_${$}__";
157             my $dbh = $self->right->result_source->schema->storage->dbh;
158             $dbh->do("CREATE TEMP TABLE $tmp_tbl (reg INTEGER NOT NULL)");
159              
160             my $sth = $dbh->prepare("INSERT INTO $tmp_tbl VALUES( ? )");
161             $sth->execute($_) for @{$ids};
162              
163             # Now fetch the actual Registration objects (with all their
164             # related objects) for each id in the temp table. Finally,
165             # map all the Registrations into DifferenceEntry objects.
166              
167             my $where = { 'me.id' => { in => \"SELECT reg from $tmp_tbl" } };
168             my $reg_rs = $side->registrations($where)->with_distribution->with_package;
169              
170             my @entries = map { Pinto::DifferenceEntry->new( op => $type,
171             registration => $_ ) } $reg_rs->all;
172              
173             $dbh->do("DROP TABLE $tmp_tbl");
174              
175             return @entries;
176             }
177              
178             #------------------------------------------------------------------------------
179              
180             sub foreach {
181             my ( $self, $cb ) = @_;
182              
183             $cb->($_) for $self->entries;
184              
185             return $self;
186             }
187              
188             #------------------------------------------------------------------------------
189              
190             sub to_string {
191             my ($self) = @_;
192              
193             my $format = $self->style eq $PINTO_DIFF_STYLE_CONCISE
194             ? '%o[%F] %a/%f'
195             : '';
196              
197             return join("\n", map {$_->to_string($format) } $self->entries) . "\n";
198             }
199              
200             #------------------------------------------------------------------------------
201              
202             __PACKAGE__->meta->make_immutable;
203              
204             #------------------------------------------------------------------------------
205             1;
206              
207             __END__