File Coverage

blib/lib/DBIx/QuickORM/Join.pm
Criterion Covered Total %
statement 139 192 72.4
branch 23 50 46.0
condition 12 19 63.1
subroutine 20 30 66.6
pod 0 18 0.0
total 194 309 62.7


line stmt bran cond sub pod time code
1             package DBIx::QuickORM::Join;
2 1     1   8 use strict;
  1         2  
  1         39  
3 1     1   5 use warnings;
  1         2  
  1         78  
4              
5             our $VERSION = '0.000019';
6              
7 1     1   5 use Carp qw/croak/;
  1         2  
  1         69  
8 1     1   5 use Scalar::Util qw/blessed/;
  1         3  
  1         41  
9 1     1   4 use Sub::Util qw/set_subname/;
  1         2  
  1         34  
10 1     1   1573 use DBIx::QuickORM::Join::Row;
  1         4  
  1         38  
11              
12 1     1   8 use Role::Tiny::With qw/with/;
  1         2  
  1         72  
13             with 'DBIx::QuickORM::Role::Source';
14             with 'DBIx::QuickORM::Role::Linked';
15              
16 1         5 use DBIx::QuickORM::Util::HashBase qw{
17             <schema
18             <primary_source
19             <join_as
20             <row_class
21              
22             <order
23             <lookup
24             <components
25 1     1   6 };
  1         3  
26              
27       18 0   sub primary_key { }
28       0 0   sub fields_to_omit { }
29 5     5 0 22 sub source_orm_name { 'JOIN' }
30 0     0 0 0 sub fields_list_all { croak "Not Supported" }
31              
32             sub init {
33 2     2 0 4 my $self = shift;
34 2 50       12 croak "'schema' is required" unless $self->{+SCHEMA};
35 2 50       8 croak "'primary_source' is required" unless $self->{+PRIMARY_SOURCE};
36              
37 2         7 $self->{+JOIN_AS} = 'a';
38              
39 2   50     16 $self->{+ORDER} //= [];
40 2   50     12 $self->{+LOOKUP} //= {};
41 2   50     15 $self->{+COMPONENTS} //= {};
42              
43 2         6 my $first = $self->{+JOIN_AS}++;
44 2         5 push @{$self->{+ORDER}} => $first;
  2         7  
45 2         4 push @{$self->{+LOOKUP}->{$self->{+PRIMARY_SOURCE}->source_db_moniker}} => $first;
  2         16  
46 2         11 $self->{+COMPONENTS}->{$first} = {table => $self->{+PRIMARY_SOURCE}, as => $first};
47              
48 2   50     13 $self->{+ROW_CLASS} //= 'DBIx::QuickORM::Join::Row';
49             }
50              
51             sub fracture {
52 5     5 0 4060 my $self = shift;
53 5         22 my ($in) = @_;
54              
55 5         20 my $out = [];
56              
57 5         12 for my $as (@{$self->{+ORDER}}) {
  5         25  
58 15         40 my $comp = $self->{+COMPONENTS}->{$as};
59              
60 15         23 my $not_null = 0;
61 15         36 my $link = $comp->{link};
62 15         31 my $table = $comp->{table};
63 15   100     66 my $data = {map { $not_null ||= defined($in->{$_}); m/^\Q$as\E\.(.+)$/; ($1 => $in->{$_}) } grep { m/^\Q$as\E\./ } keys %$in};
  45         149  
  45         374  
  45         206  
  135         629  
64              
65 15 100       62 next unless $not_null;
66 13         78 push @$out => {source => $table, data => $data, as => $as, link => $link};
67             }
68              
69 5         25 return $out;
70             }
71              
72             sub clone {
73 5     5 0 10 my $self = shift;
74 5         13 my %params = @_;
75              
76 5         10 my $class = blessed($self);
77              
78             return bless(
79             {
80             %$self,
81 5         19 ORDER() => [@{$self->{+ORDER}}],
82 5         21 LOOKUP() => {%{$self->{+LOOKUP}}},
83 5         39 COMPONENTS() => {%{$self->{+COMPONENTS}}},
  5         64  
84             %params,
85             },
86             $class,
87             );
88             }
89              
90             sub source_db_moniker {
91 2     2 0 6 my $self = shift;
92              
93 2         4 my $lookup = $self->{+LOOKUP};
94 2         6 my $comps = $self->{+COMPONENTS};
95              
96 2         3 my $out;
97 2         5 for my $as (@{$self->{+ORDER}}) {
  2         8  
98 7 50       21 my $comp = $comps->{$as} or die "No alias '$as'";
99 7         11 my $link = $comp->{link};
100 7         12 my $from = $comp->{from};
101 7         15 my $table = $comp->{table};
102 7   100     21 my $type = $comp->{type} // "";
103              
104 7 100       16 if ($link) {
105 5         17 my $lc = $link->local_columns;
106 5         13 my $oc = $link->other_columns;
107              
108 5         8 my @cols;
109 5         26 for (my $i = 0; $i < @$lc; $i++) {
110 5         23 push @cols => "$as.$lc->[$i] = $from.$oc->[$i]";
111             }
112              
113 5 50       22 $out .= $type =~ m/join/i ? " $type " : " $type JOIN ";
114 5         16 $out .= $table->source_db_moniker . " AS $as ON (" . join(' AND ' => @cols) . ")";
115             }
116             else {
117 2         9 $out = $table->source_db_moniker . " AS $as";
118             }
119             }
120              
121 2         7 return \$out;
122             }
123              
124             sub _field_source {
125 0     0   0 my $self = shift;
126 0         0 my ($proto, %params) = @_;
127 0         0 my ($field, $from) = reverse split /\./, $proto;
128              
129 0 0       0 if ($from) {
130 0 0       0 my $c = $self->{+COMPONENTS}->{$from} or croak "'$from' is not an alias in this join";
131 0         0 my $t = $c->{table};
132 0         0 return ($from, $t, $field);
133             }
134              
135 0         0 for my $alias (@{$self->{+ORDER}}) {
  0         0  
136 0         0 my $c = $self->{+COMPONENTS}->{$from};
137 0         0 my $t = $c->{table};
138 0 0       0 next unless $t->has_field($field);
139 0         0 return ($from, $t, $field);
140             }
141              
142 0 0       0 return undef if $params{no_fatal};
143 0         0 croak "This join does not have a '$field' field";
144             }
145              
146             sub field_type {
147 0     0 0 0 my $self = shift;
148 0         0 my ($proto) = @_;
149 0         0 my ($from, $t, $field) = $self->_field_source($proto);
150 0         0 return $t->field_type($field);
151             }
152              
153             sub field_affinity {
154 0     0 0 0 my $self = shift;
155 0         0 my ($proto, $dialect) = @_;
156 0         0 my ($from, $t, $field) = $self->_field_source($proto);
157 0         0 return $t->field_affinity($field, $dialect);
158             }
159              
160             sub has_field {
161 0     0 0 0 my $self = shift;
162 0         0 my ($proto) = @_;
163 0         0 my ($from, $t, $field) = $self->_field_source($proto, no_fatal => 1);
164 0         0 return $t->has_field($field);
165             }
166              
167             sub fields_to_fetch {
168 5     5 0 8 my $self = shift;
169              
170 5         10 my @fields;
171              
172 5         8 for my $as (@{$self->{+ORDER}}) {
  5         14  
173 14         38 my $c = $self->{+COMPONENTS}->{$as};
174 14         25 my $t = $c->{table};
175 14         22 push @fields => map { qq{$as.$_ AS "$as.$_"} } @{$t->fields_to_fetch};
  41         112  
  14         39  
176             }
177              
178 5         49 return join(', ' => @fields);
179             }
180              
181             sub links {
182 0     0 0 0 my $self = shift;
183              
184 0         0 my @out;
185              
186 0         0 for my $as (@{$self->{+ORDER}}) {
  0         0  
187 0         0 my $table = $self->{+COMPONENTS}->{$as}->{table};
188 0         0 push @out => @{$table->links};
  0         0  
189             }
190              
191 0         0 return \@out;
192             }
193              
194             sub from {
195 3     3 0 6 my $self = shift;
196 3         9 my ($from) = @_;
197              
198 3 50       14 if (my $comp = $self->{+COMPONENTS}->{$from}) {
199 0         0 return $comp->{table};
200             }
201              
202 3 50       12 if (my $as_set = $self->{+LOOKUP}->{$from}) {
203 3 50       10 croak "Ambiguous table name '$from' which has been joined to multiple times. Select an alias: " . join(', ' => @$as_set)
204             if @$as_set > 1;
205              
206 3         9 my ($as) = @$as_set;
207 3 50       11 if (my $comp = $self->{+COMPONENTS}->{$as}) {
208 3         12 return $comp->{table};
209             }
210             }
211              
212 0         0 croak "Unable to resolve '$from' it does not appear to be a table name or an alias";
213             }
214              
215             sub _join_params {
216 5     5   10 my $self = shift;
217              
218 5 50       18 return (link => $_[0]) if @_ == 1;
219 5         21 return @_;
220             }
221              
222             sub _join {
223 5     5   11 my $self = shift;
224 5         20 my %params = @_;
225              
226 5         19 $self = $self->clone;
227              
228 5 50       17 croak "$params{meth}() should not be called in void context" unless defined wantarray;
229              
230 5         11 my $as = $params{as};
231 5         11 my $link = $params{link};
232 5         12 my $from = $params{from};
233 5         10 my $type = $params{type};
234              
235 5         15 until ($as) {
236 5         15 my $try = $self->{+JOIN_AS}++;
237 5 50       14 next if $self->{+COMPONENTS}->{$try};
238 5         15 $as = $try;
239             }
240              
241 5 50       13 croak "A join has already been made using the identifier '$as'" if $self->{+COMPONENTS}->{$as};
242              
243 5 100 66     34 if ($from && !$self->{+COMPONENTS}->{$from}) {
244 3         7 my $check = $self->{+LOOKUP}->{$from};
245 3 50 33     14 croak "'$from' is not defined" unless $check && @$check;
246 3 50       11 croak "'$from' source has multiple aliases: " . join(', ' => @$check) if @$check > 1;
247 3         6 ($from) = @$check;
248             }
249              
250 5 100       14 unless ($from) {
251 2         11 my $lt = $link->local_table;
252 2 50       12 if ($lt eq $self->{+PRIMARY_SOURCE}->name) {
    0          
253 2         7 $from = $self->{+ORDER}->[0];
254             }
255             elsif (my $n = $self->{+LOOKUP}->{$lt}) {
256 0 0       0 croak "Table '$lt' has been joined multiple times, you must specify which name to use in the join" if @$n > 1;
257 0         0 $from = $n->[0];
258             }
259             else {
260 0         0 croak "Table '$lt' is not yet in the join";
261             }
262             }
263              
264 5         9 push @{$self->{+ORDER}} => $as;
  5         16  
265              
266 5         8 push @{$self->{+LOOKUP}->{$link->other_table}} => $as;
  5         25  
267              
268 5         32 $self->{+COMPONENTS}->{$as} = {
269             as => $as,
270             table => $self->schema->table($link->other_table),
271             link => $link,
272             from => $from,
273             type => $type,
274             };
275              
276 5         35 return $self;
277             }
278              
279             sub left_join {
280 0     0 0 0 my $self = shift;
281 0         0 my %params = $self->_join_params(@_);
282 0         0 $params{type} = 'LEFT';
283 0         0 return $self->_join(meth => 'left_join', %params);
284             }
285              
286             sub right_join {
287 0     0 0 0 my $self = shift;
288 0         0 my %params = $self->_join_params(@_);
289 0         0 $params{type} = 'RIGHT';
290 0         0 return $self->_join(meth => 'right_join', %params);
291             }
292              
293             sub inner_join {
294 0     0 0 0 my $self = shift;
295 0         0 my %params = $self->_join_params(@_);
296 0         0 $params{type} = 'INNER';
297 0         0 return $self->_join(meth => 'inner_join', %params);
298             }
299              
300             {
301 1     1   14 no warnings 'once';
  1         2  
  1         152  
302             *join = set_subname 'join' => sub {
303 5     5 0 10 my $self = shift;
304 5         19 my %params = $self->_join_params(@_);
305 5         23 return $self->_join(meth => 'join', %params);
306             };
307             }
308              
309             1;