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; |