File Coverage

blib/lib/Fey/SQL/Fragment/Join.pm
Criterion Covered Total %
statement 95 95 100.0
branch 23 26 88.4
condition 5 6 83.3
subroutine 19 19 100.0
pod 0 5 0.0
total 142 151 94.0


line stmt bran cond sub pod time code
1             package Fey::SQL::Fragment::Join;
2              
3 27     27   127 use strict;
  27         37  
  27         1121  
4 27     27   128 use warnings;
  27         477  
  27         865  
5 27     27   151 use namespace::autoclean;
  27         32  
  27         209  
6              
7             our $VERSION = '0.43';
8              
9 27     27   2546 use Fey::Exceptions qw( param_error );
  27         42  
  27         1606  
10 27     27   163 use Fey::FakeDBI;
  27         35  
  27         545  
11 27     27   107 use Fey::Types qw( FK OuterJoinType Table WhereClause );
  27         32  
  27         213  
12 27     27   199195 use List::AllUtils qw( pairwise );
  27         99  
  27         1824  
13              
14 27     27   140 use Moose 2.1200;
  27         783  
  27         212  
15              
16             has '_table1' => (
17             is => 'ro',
18             does => 'Fey::Role::TableLike',
19             required => 1,
20             init_arg => 'table1',
21             );
22              
23             has '_table2' => (
24             is => 'ro',
25             does => 'Fey::Role::TableLike',
26             predicate => '_has_table2',
27             init_arg => 'table2',
28             );
29              
30             has '_fk' => (
31             is => 'ro',
32             isa => FK,
33             init_arg => 'fk',
34             predicate => '_has_fk',
35             );
36              
37             has '_outer_type' => (
38             is => 'ro',
39             isa => OuterJoinType,
40             predicate => '_has_outer_type',
41             init_arg => 'outer_type',
42             );
43              
44             has '_where' => (
45             is => 'ro',
46             isa => WhereClause,
47             predicate => '_has_where',
48             init_arg => 'where',
49             );
50              
51             sub BUILD {
52 98     98 0 143 my $self = shift;
53              
54 98 50 66     2979 param_error 'You cannot join two tables without a foreign key'
55             if $self->_has_table2() && !$self->_has_fk();
56              
57 98         2250 return;
58             }
59              
60             sub id {
61 97     97 0 128 my $self = shift;
62              
63             # This is a rather special case, and handling it separately makes
64             # the rest of this method simpler.
65 97 100       2626 return $self->_table1()->id()
66             unless $self->_has_table2();
67              
68 32         108 my @tables = $self->tables();
69 32 100       95 @tables = sort { $a->name() cmp $b->name() } @tables
  23         537  
70             unless $self->_is_left_or_right_outer_join();
71              
72 32         48 my @outer;
73 32 100       959 @outer = $self->_outer_type() if $self->_has_outer_type();
74              
75 32         41 my @where;
76 32 100       886 @where = $self->_where()->where_clause( 'Fey::FakeDBI', 'no WHERE' )
77             if $self->_has_where();
78              
79             return (
80 64         1336 join "\0",
81             @outer,
82 32         71 ( map { $_->id() } @tables ),
83             $self->_fk()->id(),
84             @where,
85             );
86             }
87              
88             sub _is_left_or_right_outer_join {
89 32     32   44 my $self = shift;
90              
91 32   100     991 return $self->_has_outer_type()
92             && $self->_outer_type() =~ /^(?:right|left)$/;
93             }
94              
95             sub tables {
96 253     253 0 313 my $self = shift;
97              
98 253         6765 return grep {defined} ( $self->_table1(), $self->_table2() );
  506         1063  
99             }
100              
101             sub sql_with_alias {
102 111     111 0 158 my $self = shift;
103 111         127 my $dbh = shift;
104 111         130 my $joined_ids = shift;
105              
106             my @unseen_tables
107 111         271 = grep { !$joined_ids->{ $_->id() } } $self->tables();
  143         2899  
108              
109             # This can happen in the case where we have just one table, and
110             # that table is participating in some other join.
111 111 100       285 return '' unless @unseen_tables;
112              
113 108 100       3087 return $self->_table1()->sql_with_alias($dbh)
114             unless $self->_has_table2();
115              
116 30 100       76 if ( @unseen_tables == 1 ) {
117 8         28 return $self->_join_one_table( $dbh, @unseen_tables );
118             }
119             else {
120 22         77 return $self->_join_both_tables($dbh);
121             }
122             }
123              
124             # This could produce gibberish for an OUTER JOIN, but that would mean
125             # that the query is fundamentally wrong anyway (since you can't OUTER
126             # JOIN a table you've already joined with a normal join previously).
127             sub _join_one_table {
128 8     8   14 my $self = shift;
129 8         14 my $dbh = shift;
130 8         8 my $unseen_table = shift;
131              
132 8         11 my $join = '';
133              
134 8 50       220 $join .= uc $self->_outer_type() . ' OUTER'
135             if $self->_has_outer_type();
136              
137 8 50       17 $join .= q{ } if length $join;
138 8         13 $join .= 'JOIN ';
139 8         23 $join .= $unseen_table->sql_with_alias($dbh);
140              
141 8         367 $join .= $self->_on_clause($dbh);
142 8         26 $join .= $self->_where_clause($dbh);
143 8         14 $join .= ')';
144              
145 8         25 return $join;
146             }
147              
148             sub _join_both_tables {
149 22     22   29 my $self = shift;
150 22         29 my $dbh = shift;
151              
152 22         505 my $join = $self->_table1()->sql_with_alias($dbh);
153              
154 22 100       2169 $join .= q{ } . uc $self->_outer_type() . ' OUTER'
155             if $self->_has_outer_type();
156              
157 22         56 $join .= ' JOIN ';
158 22         548 $join .= $self->_table2()->sql_with_alias($dbh);
159              
160 22         996 $join .= $self->_on_clause($dbh);
161 22         79 $join .= $self->_where_clause($dbh);
162 22         40 $join .= ')';
163              
164 22         83 return $join;
165             }
166              
167             sub _on_clause {
168 30     30   42 my $self = shift;
169 30         37 my $dbh = shift;
170              
171 30         40 my $on .= ' ON (';
172              
173 30         39 my @s = @{ $self->_fk()->source_columns() };
  30         803  
174 30         35 my @t = @{ $self->_fk()->target_columns() };
  30         655  
175              
176 30     30   276 for my $p ( pairwise { [ $a, $b ] } @s, @t ) {
  30         117  
177 30         139 $on .= $p->[0]->sql_or_alias($dbh);
178 30         1503 $on .= ' = ';
179 30         108 $on .= $p->[1]->sql_or_alias($dbh);
180             }
181              
182 30         1679 return $on;
183             }
184              
185             sub _where_clause {
186 30     30   47 my $self = shift;
187 30         34 my $dbh = shift;
188              
189 30 100       974 return '' unless $self->_has_where();
190              
191 5         133 return ' AND ' . $self->_where()->where_clause( $dbh, 'no WHERE' );
192             }
193              
194             sub bind_params {
195 16     16 0 26 my $self = shift;
196              
197 16 100       551 return unless $self->_has_where();
198              
199 1         23 return $self->_where()->bind_params();
200             }
201              
202             __PACKAGE__->meta()->make_immutable();
203              
204             1;
205              
206             # ABSTRACT: Represents a single join in a FROM clause
207              
208             __END__
209              
210             =pod
211              
212             =head1 NAME
213              
214             Fey::SQL::Fragment::Join - Represents a single join in a FROM clause
215              
216             =head1 VERSION
217              
218             version 0.43
219              
220             =head1 DESCRIPTION
221              
222             This class represents part of a C<FROM> clause, usually a join, but it
223             can also represent a single table or subselect.
224              
225             It is intended solely for internal use in L<Fey::SQL> objects, and as
226             such is not intended for public use.
227              
228             =head1 BUGS
229              
230             See L<Fey> for details on how to report bugs.
231              
232             =head1 AUTHOR
233              
234             Dave Rolsky <autarch@urth.org>
235              
236             =head1 COPYRIGHT AND LICENSE
237              
238             This software is Copyright (c) 2011 - 2015 by Dave Rolsky.
239              
240             This is free software, licensed under:
241              
242             The Artistic License 2.0 (GPL Compatible)
243              
244             =cut