File Coverage

blib/lib/DBIx/QuickORM/Role/Linked.pm
Criterion Covered Total %
statement 93 96 96.8
branch 32 48 66.6
condition 33 84 39.2
subroutine 15 16 93.7
pod 0 3 0.0
total 173 247 70.0


line stmt bran cond sub pod time code
1             package DBIx::QuickORM::Role::Linked;
2 24     24   38669 use strict;
  24         64  
  24         1033  
3 24     24   137 use warnings;
  24         51  
  24         2310  
4              
5             our $VERSION = '0.000019';
6              
7 24     24   223 use Carp qw/croak/;
  24         55  
  24         1791  
8 24     24   164 use Scalar::Util qw/blessed/;
  24         60  
  24         1625  
9              
10 24     24   210 use constant 'LINKS' => '__links__';
  24         62  
  24         2717  
11 24     24   173 use constant 'BUILT' => 'built';
  24         52  
  24         1744  
12 24     24   203 use constant 'CACHE_ID' => 'cache_id';
  24         93  
  24         1591  
13 24     24   146 use constant 'BY_ALIAS' => 'by_alias';
  24         49  
  24         1536  
14 24     24   170 use constant 'BY_TABLE' => 'by_table';
  24         54  
  24         1562  
15 24     24   177 use constant 'BY_TABLE_ALIAS' => 'by_table_alias';
  24         65  
  24         1482  
16 24     24   161 use constant 'BY_TABLE_KEY' => 'by_table_key';
  24         61  
  24         1517  
17              
18 24     24   184 use Role::Tiny;
  24         53  
  24         229  
19              
20             requires qw{
21             links
22             };
23              
24       0 0   sub connection {}
25       3 0   sub from {}
26              
27             sub resolve_link {
28 22     22 0 49 my $self = shift;
29              
30 22         46 my %params;
31 22 100       92 if (@_ % 2) {
32 20         46 my $spec = shift;
33 20         78 %params = @_;
34 20         80 $params{spec} = $spec;
35             }
36             else {
37 2         10 %params = @_;
38             }
39              
40 22 50       93 return $params{link} if $params{link};
41              
42 22         59 my $spec = $params{spec};
43 22 100 100     178 return $spec if $spec && blessed($spec) && $spec->isa('DBIx::QuickORM::Link');
      66        
44              
45 17 100       60 if ($params{from}) {
46 6         22 my $s = $self->from($params{from});
47 6 100       45 return $s->resolve_link($spec, %params) if $s;
48             }
49              
50             return DBIx::QuickORM::Link->parse(
51             source => $self,
52             link => $spec,
53 14 50 0     48 connection => $params{connection} // $self->connection,
54             ) if ref $spec;
55              
56 14 50       64 my $found = $self->_link_from_name(%params) or croak "Could not resolve link";
57              
58 14 50       63 return $found unless ref($found) eq 'ARRAY';
59 14 50       116 return $found->[0] if @$found == 1;
60              
61             croak join "\n" => (
62             "Ambiguous link specification, found the following:",
63 0         0 (map { "local_table: $_->{local_table} | other_table: $_->{other_table} | key: $_->{key} | aliases: " . join(', ', @{$_->{aliases}}) } @$found),
  0         0  
  0         0  
64             '',
65             );
66             }
67              
68             sub _link_from_name {
69 14     14   32 my $self = shift;
70 14         50 my (%params) = @_;
71              
72 14         40 my $cache = $self->{+LINKS};
73 14 100 66     119 $cache = $self->{+LINKS} = {CACHE_ID() => "$self"} unless $cache && $cache->{+CACHE_ID} eq "$self";
74              
75 14 100       59 unless ($cache->{+BUILT}) {
76 5         12 my %lookup;
77 5 50       12 for my $l (sort { $a->other_table cmp $b->other_table || $a->key cmp $b->key } @{$self->links}) {
  2         23  
  5         40  
78 7         49 my $f = $lookup{$l->other_table}->{$l->key};
79 7 50       45 $lookup{$l->other_table}->{$l->key} = $f ? $f->merge($l) : $l;
80             }
81              
82 5         19 $cache->{+BY_TABLE_KEY} = \%lookup;
83              
84 5         19 for my $link (map {values %{$_}} values %lookup) {
  7         15  
  7         28  
85 7         16 push @{$cache->{+BY_TABLE}->{$link->other_table}} => $link;
  7         35  
86              
87 7         15 for my $alias (@{$link->aliases}) {
  7         29  
88 6         12 push @{$cache->{+BY_ALIAS}->{$alias}} => $link;
  6         26  
89 6   33     45 $cache->{+BY_TABLE_ALIAS}->{$link->other_table}->{$alias} //= $link;
90             }
91             }
92              
93 5         15 $cache->{+BUILT} = 1;
94             }
95              
96 14         38 my $spec = $params{spec};
97 14         38 my $table = $params{table};
98 14         32 my $alias = $params{alias};
99 14         31 my $columns = $params{columns};
100 14 50 33     98 my $key = $params{key} //= $columns ? column_key(@$columns) : undef;
101              
102 14         28 my $out;
103 14 50 0     55 $out //= $cache->{+BY_TABLE_ALIAS}->{$table}->{$alias} if $table && $alias;
      66        
104 14 50 0     68 $out //= $cache->{+BY_TABLE_ALIAS}->{$table}->{$spec} if $table && $spec && !$alias;
      66        
      33        
105 14 50 0     79 $out //= $cache->{+BY_TABLE_ALIAS}->{$spec}->{$alias} if $spec && $alias && !$table;
      66        
      33        
106              
107 14 50 0     58 $out //= $cache->{+BY_TABLE_KEY}->{$table}->{$key} if $table && $key;
      66        
108 14 0 0     49 $out //= $cache->{+BY_TABLE_KEY}->{$spec}->{$key} if $key && $spec && !$table;
      33        
      33        
109 14 50 0     54 $out //= $cache->{+BY_TABLE_KEY}->{$table}->{$spec} if $table && $spec && !$key;
      66        
      33        
110              
111 14 50 0     41 $out //= $cache->{+BY_ALIAS}->{$alias} if $alias;
112 14 100 66     170 $out //= $cache->{+BY_ALIAS}->{$spec} if $spec && !$alias;
      66        
113              
114 14 100 33     54 $out //= $cache->{+BY_TABLE}->{$table} if $table;
115 14 100 66     89 $out //= $cache->{+BY_TABLE}->{$spec} if $spec && !$table;
      66        
116              
117 14         75 return $out;
118             }
119              
120             1;
121              
122              
123             __END__
124              
125             $found //= $source->links_by_alias->{$link} if $source->can('links_by_alias');
126              
127             if ($source->can('links_by_table')) {
128             if (my $set = $source->links_by_table->{$link}) {
129             my $count = keys %$set;
130             croak "Could not find any links to table '$link'" unless $count;
131             if ($count > 1) {
132             use Data::Dumper;
133             croak "Found $count links to table '$link', you need to be more specific: " . Dumper($set);
134             }
135             ($found) = values %$set;
136             }
137             }
138              
139             sub _links { delete $_[0]->{+_LINKS} }
140              
141             sub links_by_table { $_[0]->{+LINKS} }
142              
143             sub links {
144             my $self = shift;
145             my ($table) = @_;
146              
147             my @tables = $table ? ($table) : keys %{ $self->{+LINKS} };
148              
149             return map { values %{ $self->{+LINKS}->{$_} // {}} } @tables;
150             }
151              
152             sub link {
153             my $self = shift;
154             my %params = @_;
155              
156             if (my $table = $params{table}) {
157             my $links = $self->{+LINKS}->{$table} or return undef;
158              
159             if (my $cols = $params{columns} // $params{cols}) {
160             my $key = column_key(@$cols);
161             return $links->{$key} // undef;
162             }
163              
164             for my $key (sort keys %$links) {
165             return $links->{$key} // undef;
166             }
167              
168             return undef;
169             }
170             elsif (my $alias = $params{name}) {
171             return $self->{+LINKS_BY_ALIAS}->{$alias} // undef;
172             }
173              
174             croak "Need a link name or table";
175             }
176              
177             sub parse_link {
178             my $self = shift;
179             my ($link) = @_;
180              
181             return $link if blessed($link) && $link->isa('DBIx::QuickORM::Link');
182              
183             my $ref = ref($link);
184              
185             return $self->source->links_by_alias->{$link} // croak "'$link' is not a valid link alias for table '" . $self->source->name . "'"
186             unless $ref;
187              
188             return DBIx::QuickORM::Link->parse(
189             source => $self->source,
190             connection => $self->connection,
191             link => $link,
192             );
193             }
194              
195             # TODO move this to a role, ::Row uses it too.
196             sub _parse_link {
197             my $self = shift;
198             my ($link, %params) = @_;
199              
200             return $link if blessed($link) && $link->isa('DBIx::QuickORM::Link');
201              
202             my $ref = ref($link);
203             my $found;
204              
205             unless ($ref) {
206             my $source = $self->{+SOURCE};
207             $source = $self->{+SOURCE}->from($params{from}) if $params{from} && $source->can('from');
208              
209             $found //= $source->links_by_alias->{$link} if $source->can('links_by_alias');
210              
211             if ($source->can('links_by_table')) {
212             if (my $set = $source->links_by_table->{$link}) {
213             my $count = keys %$set;
214             croak "Could not find any links to table '$link'" unless $count;
215             if ($count > 1) {
216             use Data::Dumper;
217             croak "Found $count links to table '$link', you need to be more specific: " . Dumper($set);
218             }
219             ($found) = values %$set;
220             }
221             }
222              
223             croak "Could not resolve link '$link'" unless $found;
224             }
225              
226             return DBIx::QuickORM::Link->parse(
227             source => $self->{+SOURCE},
228             connection => $self->{+CONNECTION},
229             link => $found // $link,
230             );
231             }
232              
233