File Coverage

blib/lib/Class/ReluctantORM/SQL/From/Join.pm
Criterion Covered Total %
statement 30 101 29.7
branch 0 16 0.0
condition 0 14 0.0
subroutine 10 27 37.0
pod 14 14 100.0
total 54 172 31.4


line stmt bran cond sub pod time code
1             package Class::ReluctantORM::SQL::From::Join;
2              
3             =head1 NAME
4              
5             Class::ReluctantORM::SQL::From::Join - Represent a JOIN in a SQL statement
6              
7             =head1 SYNOPSIS
8              
9             use Class::ReluctantORM::SQL::Aliases;
10              
11             # Make three kinds of joins
12             my $join1 = Join->new('INNER', $left_rel, $right_rel, $criterion);
13             my $join2 = Join->new('LEFT OUTER', $left_rel, $right_rel, $criterion);
14             my $join3 = Join->new('CROSS', $left_rel, $right_rel, $criterion);
15              
16             # Make a tree of joins - (a INNER JOIN b) INNER JOIN c
17             my $join4 = Join->new('INNER', $table_a, $table_b, $criterion);
18             my $join5 = Join->new('INNER', $join4, $table_c, $criterion);
19              
20             # Use it in a FROM clause
21             my $from = From->new($join5);
22              
23              
24             =head1 DESCRIPTION
25              
26             Represents a JOIN in a SQL statement. Inherits from Class::ReluctantORM::SQL::From::Relation .
27              
28             Each JOIN has two children, a left relation and a right relation.
29             In addition, there is a Criterion that represents the join condition, and a type that represents the JOIN type.
30              
31             RIGHT OUTER joins are not supported. Transform them into LEFT OUTERs.
32              
33             NATURAL joins are not supported, because the Criterion must be explicit.
34              
35             =cut
36              
37 1     1   6 use strict;
  1         2  
  1         30  
38 1     1   5 use warnings;
  1         2  
  1         25  
39              
40 1     1   6 use Class::ReluctantORM::Exception;
  1         2  
  1         20  
41 1     1   5 use Data::Dumper;
  1         1  
  1         56  
42 1     1   5 use Class::ReluctantORM::Utilities qw(install_method check_args);
  1         8  
  1         55  
43 1     1   6 use Scalar::Util qw(blessed);
  1         3  
  1         90  
44              
45             our $DEBUG ||= 0;
46              
47 1     1   8 use base 'Class::ReluctantORM::SQL::From::Relation';
  1         2  
  1         104  
48 1     1   7 use Class::ReluctantORM::SQL::Aliases;
  1         3  
  1         145  
49 1     1   7 use Class::ReluctantORM::SQL::Column;
  1         2  
  1         16  
50 1     1   29 use Class::ReluctantORM::SQL::Table;
  1         2  
  1         7  
51              
52              
53             =head1 CONSTRUCTORS
54              
55             =cut
56              
57             =head2 $join = Join->new($type, $left_rel, $right_rel, $crit, [$relationship]);
58              
59             Creates a new Join.
60              
61             $type must be one of INNER, LEFT OUTER, or CROSS.
62              
63             $left_rel and $right_rel are Relation subclasses (this includes
64             Tables, Joins, and SubQueries).
65              
66             $crit is a Criterion specifying the join condition(s).
67              
68             $relationship is an optional Relationship. This is used as a hint when resolving ambiguities in the SQL, and is optional.
69              
70             =cut
71              
72             sub new {
73 0     0 1   my $class = shift;
74 0 0         if (@_ < 4) { Class::ReluctantORM::Exception::Param::Missing->croak(); }
  0            
75 0 0         if (@_ > 4) { Class::ReluctantORM::Exception::Param::Spurious->croak(); }
  0            
76              
77 0           my $self = bless {}, $class;
78 0           $self->type(shift);
79 0           $self->left_relation(shift);
80 0           $self->right_relation(shift);
81 0           $self->criterion(shift);
82 0           $self->relationship(shift);
83              
84 0           return $self;
85             }
86              
87             =head2 $clone = $join->clone();
88              
89             Makes a deep copy of the Join object. All SQL objects are clone()'d, but annotations (such as Relationships) are not.
90              
91             =cut
92              
93             sub clone {
94 0     0 1   my $self = shift;
95 0           my $class = ref $self;
96 0           my $other = $class->new(
97             $self->type(),
98             $self->left_relation()->clone(),
99             $self->right_relation()->clone(),
100             $self->criterion()->clone(),
101             );
102 0           $other->relationship($self->relationship());
103 0           return $other;
104             }
105              
106             =head1 ACCESSORS AND MUTATORS
107              
108             =cut
109              
110             =head2 $join->alias(...);
111              
112             =head2 $join->has_column(...);
113              
114             =head2 $join->columns(...);
115              
116             =head2 $join->tables();
117              
118             =head2 $join->knows_any_columns(...);
119              
120             =head2 $join->knows_all_columns(...);
121              
122             =head2 $join->pretty_print(...);
123              
124             These methods are inherited from Relation.
125              
126             =cut
127              
128              
129             =head2 @rel = $join->child_relations();
130              
131             Returns a two-element array with the left and right relations. Required by the Relation interface.
132              
133             =cut
134              
135             sub child_relations {
136 0     0 1   my $self = shift;
137 0           return ($self->left_relation, $self->right_relation);
138             }
139              
140             =head2 $join->criterion($crit);
141              
142             =head2 $crit = $join->criterion();
143              
144             Reads or sets the join condition as a Class::ReluctantORM::SQL::Where::Criterion .
145              
146             =cut
147              
148             __PACKAGE__->mk_accessors(qw(criterion));
149              
150             =head2 $bool = $join->is_leaf_relation();
151              
152             Always returns false for this class. Required by the Relation interface.
153              
154             =cut
155              
156 0     0 1   sub is_leaf_relation { return 0; }
157              
158             =head2 $bool = $rel->is_join();
159              
160             All objects of this class return true. The class adds this method to its parent class, making all other subclasses of return false.
161              
162             =cut
163              
164 0     0     install_method('Class::ReluctantORM::SQL::From::Relation', 'is_join', sub { return 0; });
165 0     0 1   sub is_join { return 1; }
166              
167              
168             =head2 $join->left_relation($rel);
169              
170             =head2 $rel = $join->left_relation();
171              
172             Reads or sets the left-hand relation of the join condition a Class::ReluctantORM::SQL::From::Relation .
173              
174             =cut
175              
176             sub left_relation {
177 0     0 1   my $self = shift;
178 0           return $self->__relation_accessor('left', @_);
179             }
180              
181             =head2 $r = $join->relationship();
182              
183             =head2 $join->relationship($relationship);
184              
185             Reads or sets auxiliary relationship data, a Class::ReluctantORM::Relationship.
186              
187             =cut
188              
189             __PACKAGE__->mk_accessors(qw(relationship));
190              
191             =head2 $join->right_relation($rel);
192              
193             =head2 $rel = $join->right_relation();
194              
195             Reads or sets the right-hand relation of the join condition a Class::ReluctantORM::SQL::From::Relation .
196              
197             =cut
198              
199             sub right_relation {
200 0     0 1   my $self = shift;
201 0           return $self->__relation_accessor('right', @_);
202             }
203             sub __relation_accessor {
204 0     0     my $self = shift;
205 0           my $side = shift;
206 0           $side .= '_relation';
207              
208 0 0         if (@_) {
209 0           my $rel = shift;
210 0 0 0       unless (blessed($rel) && $rel->isa(Relation)) { Class::ReluctantORM::Exception::Param::WrongType->croak(expected => Relation, frames => 2, value => $rel); }
  0            
211 0           $rel->parent_relation($self);
212 0           $self->set($side, $rel);
213             }
214 0           return $self->get($side);
215              
216             }
217              
218             =head2 $join->type($type);
219              
220             =head2 $type = $join->type();
221              
222             Reads or sets the join type - one of INNER, LEFT OUTER, or CROSS.
223              
224             =cut
225              
226             our %JOIN_TYPES = map { $_ => 1 } ('INNER', 'LEFT OUTER', 'CROSS');
227              
228             sub type {
229 0     0 1   my $self = shift;
230 0 0         if (@_) {
231 0           my $type = uc(shift);
232 0 0         unless (exists $JOIN_TYPES{$type}) {
233 0           Class::ReluctantORM::Exception::Param::BadValue->croak(
234             error => 'Type must be one of ' . (join ', ', keys %JOIN_TYPES),
235             param => 'type',
236             value => $type,
237             );
238             }
239 0           $self->set('type', $type);
240             }
241 0           return $self->get('type');
242             }
243              
244             sub knows_all_columns {
245 0     0 1   my $self = shift;
246 0   0       return $self->left_relation->knows_all_columns && $self->right_relation->knows_all_columns;
247             }
248              
249             sub knows_any_columns {
250 0     0 1   my $self = shift;
251 0   0       return $self->left_relation->knows_any_columns || $self->right_relation->knows_any_columns;
252             }
253              
254             sub tables {
255 0     0 1   my $self = shift;
256 0           my %opts = check_args(args => \@_, optional => [qw(exclude_subqueries)]);
257 0           return ($self->left_relation->tables(%opts), $self->right_relation->tables(%opts));
258             }
259              
260             sub columns {
261 0     0 1   my $self = shift;
262 0 0         unless ($self->knows_any_columns) { Class::ReluctantORM::Exception::SQL::AmbiguousReference->croak('Cannot call columns when knows_any_columns is false'); }
  0            
263 0           return ($self->left_relation->columns, $self->right_relation->columns);
264             }
265              
266             sub has_column {
267 0     0 1   my $self = shift;
268              
269 0 0         unless ($self->knows_any_columns) { Class::ReluctantORM::Exception::SQL::AmbiguousReference->croak('Cannot call has_columns when knows_any_columns is false'); }
  0            
270 0           my $col_name = shift;
271              
272 0   0       return $self->left_relation->has_column($col_name) || $self->right_relation->has_column($col_name);
273              
274             }
275              
276             sub pretty_print {
277 0     0 1   my $self = shift;
278 0           my %args = @_;
279 0   0       my $prefix = $args{prefix} || '';
280 0           my $str = $prefix . $self->type . ' JOIN ON ' . $self->criterion->pretty_print(one_line => 1) . "\n";
281 0           $str .= $self->left_relation->pretty_print(prefix => $prefix . ' | ');
282 0           $str .= $self->right_relation->pretty_print(prefix => $prefix . ' ` ');
283 0           return $str;
284             }
285              
286              
287             sub __break_links {
288 0     0     my $rel = shift;
289              
290             # We maintain links both ways - parent to child and child to parent. Break them.
291 0           foreach my $crel ($rel->child_relations) {
292 0           $crel->__break_links();
293             }
294 0           $rel->set('parent_ref', undef);
295 0           $rel->criterion->__break_links();
296             }
297              
298              
299             =head1 AUTHOR
300              
301             Clinton Wolfe
302              
303             =cut
304              
305              
306             1;