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   116 use strict;
  27         43  
  27         1100  
4 27     27   111 use warnings;
  27         377  
  27         783  
5 27     27   111 use namespace::autoclean;
  27         32  
  27         183  
6              
7             our $VERSION = '0.42';
8              
9 27     27   2312 use Fey::Exceptions qw( param_error );
  27         39  
  27         1516  
10 27     27   122 use Fey::FakeDBI;
  27         33  
  27         548  
11 27     27   112 use Fey::Types qw( FK OuterJoinType Table WhereClause );
  27         34  
  27         196  
12 27     27   189534 use List::AllUtils qw( pairwise );
  27         53  
  27         1649  
13              
14 27     27   131 use Moose 0.90;
  27         685  
  27         197  
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 148 my $self = shift;
53              
54 98 50 66     2865 param_error 'You cannot join two tables without a foreign key'
55             if $self->_has_table2() && !$self->_has_fk();
56              
57 98         2180 return;
58             }
59              
60             sub id {
61 97     97 0 135 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       2693 return $self->_table1()->id()
66             unless $self->_has_table2();
67              
68 32         109 my @tables = $self->tables();
69 32 100       113 @tables = sort { $a->name() cmp $b->name() } @tables
  23         464  
70             unless $self->_is_left_or_right_outer_join();
71              
72 32         62 my @outer;
73 32 100       898 @outer = $self->_outer_type() if $self->_has_outer_type();
74              
75 32         60 my @where;
76 32 100       839 @where = $self->_where()->where_clause( 'Fey::FakeDBI', 'no WHERE' )
77             if $self->_has_where();
78              
79             return (
80 64         1239 join "\0",
81             @outer,
82 32         81 ( map { $_->id() } @tables ),
83             $self->_fk()->id(),
84             @where,
85             );
86             }
87              
88             sub _is_left_or_right_outer_join {
89 32     32   55 my $self = shift;
90              
91 32   100     1003 return $self->_has_outer_type()
92             && $self->_outer_type() =~ /^(?:right|left)$/;
93             }
94              
95             sub tables {
96 253     253 0 271 my $self = shift;
97              
98 253         6479 return grep {defined} ( $self->_table1(), $self->_table2() );
  506         990  
99             }
100              
101             sub sql_with_alias {
102 111     111 0 143 my $self = shift;
103 111         125 my $dbh = shift;
104 111         117 my $joined_ids = shift;
105              
106             my @unseen_tables
107 111         250 = grep { !$joined_ids->{ $_->id() } } $self->tables();
  143         2747  
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       284 return '' unless @unseen_tables;
112              
113 108 100       3014 return $self->_table1()->sql_with_alias($dbh)
114             unless $self->_has_table2();
115              
116 30 100       90 if ( @unseen_tables == 1 ) {
117 8         36 return $self->_join_one_table( $dbh, @unseen_tables );
118             }
119             else {
120 22         85 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   25 my $self = shift;
129 8         11 my $dbh = shift;
130 8         17 my $unseen_table = shift;
131              
132 8         17 my $join = '';
133              
134 8 50       232 $join .= uc $self->_outer_type() . ' OUTER'
135             if $self->_has_outer_type();
136              
137 8 50       24 $join .= q{ } if length $join;
138 8         18 $join .= 'JOIN ';
139 8         25 $join .= $unseen_table->sql_with_alias($dbh);
140              
141 8         377 $join .= $self->_on_clause($dbh);
142 8         25 $join .= $self->_where_clause($dbh);
143 8         19 $join .= ')';
144              
145 8         26 return $join;
146             }
147              
148             sub _join_both_tables {
149 22     22   51 my $self = shift;
150 22         44 my $dbh = shift;
151              
152 22         495 my $join = $self->_table1()->sql_with_alias($dbh);
153              
154 22 100       2288 $join .= q{ } . uc $self->_outer_type() . ' OUTER'
155             if $self->_has_outer_type();
156              
157 22         47 $join .= ' JOIN ';
158 22         498 $join .= $self->_table2()->sql_with_alias($dbh);
159              
160 22         1031 $join .= $self->_on_clause($dbh);
161 22         81 $join .= $self->_where_clause($dbh);
162 22         46 $join .= ')';
163              
164 22         81 return $join;
165             }
166              
167             sub _on_clause {
168 30     30   45 my $self = shift;
169 30         36 my $dbh = shift;
170              
171 30         41 my $on .= ' ON (';
172              
173 30         48 my @s = @{ $self->_fk()->source_columns() };
  30         843  
174 30         63 my @t = @{ $self->_fk()->target_columns() };
  30         653  
175              
176 30     30   340 for my $p ( pairwise { [ $a, $b ] } @s, @t ) {
  30         108  
177 30         167 $on .= $p->[0]->sql_or_alias($dbh);
178 30         1427 $on .= ' = ';
179 30         104 $on .= $p->[1]->sql_or_alias($dbh);
180             }
181              
182 30         1544 return $on;
183             }
184              
185             sub _where_clause {
186 30     30   46 my $self = shift;
187 30         41 my $dbh = shift;
188              
189 30 100       929 return '' unless $self->_has_where();
190              
191 5         115 return ' AND ' . $self->_where()->where_clause( $dbh, 'no WHERE' );
192             }
193              
194             sub bind_params {
195 16     16 0 20 my $self = shift;
196              
197 16 100       471 return unless $self->_has_where();
198              
199 1         22 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.42
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