File Coverage

blib/lib/ORM/Tjoin.pm
Criterion Covered Total %
statement 164 188 87.2
branch 54 66 81.8
condition 29 42 69.0
subroutine 25 26 96.1
pod 0 24 0.0
total 272 346 78.6


line stmt bran cond sub pod time code
1             #
2             # DESCRIPTION
3             # PerlORM - Object relational mapper (ORM) for Perl. PerlORM is Perl
4             # library that implements object-relational mapping. Its features are
5             # much similar to those of Java's Hibernate library, but interface is
6             # much different and easier to use.
7             #
8             # AUTHOR
9             # Alexey V. Akimov
10             #
11             # COPYRIGHT
12             # Copyright (C) 2005-2006 Alexey V. Akimov
13             #
14             # This library is free software; you can redistribute it and/or
15             # modify it under the terms of the GNU Lesser General Public
16             # License as published by the Free Software Foundation; either
17             # version 2.1 of the License, or (at your option) any later version.
18             #
19             # This library is distributed in the hope that it will be useful,
20             # but WITHOUT ANY WARRANTY; without even the implied warranty of
21             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22             # Lesser General Public License for more details.
23             #
24             # You should have received a copy of the GNU Lesser General Public
25             # License along with this library; if not, write to the Free Software
26             # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
27             #
28              
29             # Instance of the class represents tree data structure,
30             # describing links between DB tables in joins.
31              
32             package ORM::Tjoin;
33              
34 5     5   26 use Carp;
  5         9  
  5         318  
35 5     5   3717 use ORM::TjoinNull;
  5         44  
  5         13234  
36              
37             $VERSION=0.81;
38              
39             ##
40             ## CLASS METHODS
41             ##
42              
43             ## use: ORM::Tjoin->new
44             ## (
45             ## class => STRING,
46             ## alias => STRING,
47             ## left_prop => STRING||undef,
48             ## prop => STRING||undef,
49             ## all_tables => BOOLEAN,
50             ## );
51             ##
52             sub new
53             {
54 53     53 0 76 my $class = shift;
55 53         168 my %arg = @_;
56 53         61 my $self;
57              
58 53 100 33     409 if( ! exists $arg{class} )
    50          
59             {
60 14         101 $self = ORM::TjoinNull->new( null_class=>$arg{null_class} );
61             }
62             elsif( !UNIVERSAL::isa( $arg{class}, 'ORM' ) || $arg{class}->_is_initial )
63             {
64 0         0 croak "Internal error! '$arg{class}' is not a valid descendant of ORM.";
65             }
66             else
67             {
68 39   100     1225 $self =
      50        
      100        
      50        
      50        
69             {
70             class => $arg{class},
71             left_prop => ( $arg{left_prop}||'id' ),
72             alias => ( $arg{alias}||undef ),
73             alias_num => undef,
74             cond => $arg{cond},
75             fingerprint => ( ($arg{left_prop}||'id').' '.($arg{alias}||'').' '.($arg{cond}||'').' '.$arg{class} ),
76             link => {},
77             };
78              
79 39 50 33     140 if( $self->{cond} && $self->{cond}->_tjoin->class ne $self->{class} )
80             {
81 0         0 croak "Join condition class '".$self->{cond}->_tjoin->class."' does not match Tjoin class '$self->{class}'";
82             }
83              
84 39         100 bless $self, $class;
85              
86 39         93 my @tables = $self->class->_db_tables;
87 39 100       482 my $primary = $self->{left_prop} eq 'id' ? '' : $self->class->_prop2table( $self->{left_prop} );
88 39         171 for( my $i=0; $i < @tables; $i++ )
89             {
90 67 100       374 $self->{class_table}{$tables[$i]} = $tables[$i] eq $primary ? -10000 : -$i;
91             }
92              
93 39 100       93 if( $arg{all_tables} )
94             {
95 5         12 %{$self->{used_table}} = %{$self->{class_table}};
  5         23  
  5         19  
96             }
97             else
98             {
99 34         119 $self->use_prop( $self->{left_prop} );
100 34 100       144 $self->use_prop( $arg{prop} ) if( $arg{prop} );
101             }
102             }
103              
104 53         215 return $self;
105             }
106              
107             sub copy
108             {
109             # Must copy:
110             #
111             # class
112             # left_prop
113             # alias
114             # alias_num
115             # fingerprint
116             # link ( copy by content )
117             # class_table ( copy by reference )
118             # used_table ( copy by content )
119             # tables ( copy by content )
120              
121 33     33 0 48 my $self = shift;
122 33         229 my $copy =
123             {
124             class => $self->{class},
125             left_prop => $self->{left_prop},
126             alias => $self->{alias},
127             alias_num => $self->{alias_num},
128             cond => $self->{cond},
129             fingerprint => $self->{fingerprint},
130             class_table => $self->{class_table},
131             };
132              
133 33 100       99 %{$copy->{used_table}} = %{$self->{used_table}} if( $self->{used_table} );
  11         40  
  11         28  
134 33 50       84 @{$copy->{tables}} = @{$self->{tables}} if( $self->{tables} );
  0         0  
  0         0  
135              
136 33         44 for my $prop ( keys %{$self->{link}} )
  33         159  
137             {
138 0         0 for my $fingerprint ( keys %{$self->{link}{$prop}} )
  0         0  
139             {
140 0         0 $copy->{link}{$prop}{$fingerprint} = $self->{link}{$prop}{$fingerprint}->copy;
141             }
142             }
143              
144 33         231 return bless $copy, ref $self;
145             }
146              
147             ##
148             ## PROPERTIES
149             ##
150              
151 279     279 0 6319 sub class { $_[0]->{class}; }
152 1     1 0 8 sub null_class { $_[0]->{class}; }
153 8     8 0 23 sub fingerprint { $_[0]->{fingerprint}; }
154              
155             sub sql_cond_str
156             {
157 1     1 0 2 my $self = shift;
158 1         2 my $sql = '';
159              
160 1 50       5 if( $self->{cond} )
161             {
162 0         0 $sql = ' AND ' . $self->{cond}->_sql_str( tjoin=>$self );
163             }
164              
165 1         4 return $sql;
166             }
167              
168             sub sql_table_list
169             {
170 12     12 0 22 my $self = shift;
171 12         19 my $nested = shift;
172 12         29 my $tables = $self->tables;
173 12         19 my $sql = '';
174              
175             #$self->assign_aliases unless( $self->{alias_num} );
176              
177 12         43 for( my $i=0; $i < @$tables; $i++ )
178             {
179 16 100       37 if( $i == 0 )
180             {
181 12 100       57 $sql .= "\n ".$self->table_as_alias( $tables->[$i] ) unless( $nested );
182             }
183             else
184             {
185 4 100 100     37 $sql .=
186             "\n".($nested||'').' '.($nested ? 'LEFT' : 'INNER')." JOIN ".$self->table_as_alias( $tables->[$i] )
187             . " ON( "
188             . $self->full_field_name( 'id', $tables->[0] ) . '='
189             . $self->full_field_name( 'id', $tables->[$i] )
190             . " )";
191             }
192             }
193              
194 12         17 for my $prop ( keys %{$self->{link}} )
  12         49  
195             {
196 1         3 for my $fingerprint ( keys %{$self->{link}{$prop}} )
  1         4  
197             {
198 1   50     18 $sql .=
199             "\n".($nested||'')." LEFT JOIN " . $self->{link}{$prop}{$fingerprint}->first_basic_table_as_alias
200             . " ON( "
201             . $self->full_field_name( $prop ) . '='
202             . $self->{link}{$prop}{$fingerprint}->full_left_field_name
203             . $self->{link}{$prop}{$fingerprint}->sql_cond_str
204             . " )";
205 1   50     20 $sql .= $self->{link}{$prop}{$fingerprint}->sql_table_list( ($nested||'').' ' );
206             }
207             }
208              
209 12         318 return $sql;
210             }
211              
212             sub text
213             {
214 0     0 0 0 my $self = shift;
215 0         0 my $nested = shift;
216 0         0 my $text;
217              
218 0         0 $text .= $self->class . "\n";
219 0         0 for my $prop ( keys %{$self->{link}} )
  0         0  
220             {
221 0         0 for my $fingerprint ( keys %{$self->{link}{$prop}} )
  0         0  
222             {
223 0         0 $text .=
224             $nested
225             . $prop
226             . ' -> '
227             . $self->{link}{$prop}{$fingerprint}->text( $nested.' ' );
228             }
229             }
230              
231 0         0 return $text;
232             }
233              
234             sub sql_select_basic_tables
235             {
236 2     2 0 4 my $self = shift;
237 2         7 my $tables = $self->tables;
238 2         3 my $sql;
239              
240             #$self->assign_aliases unless( $self->{alias_num} );
241              
242 2         5 for my $table ( @{$self->tables} )
  2         5  
243             {
244 2 50       5 $sql .= ", " if( $sql );
245 2         6 $sql .= $self->class->ORM::qt( $self->table_alias_or_name( $table ) ) . '.*';
246             }
247              
248 2         8 return $sql;
249             }
250              
251             sub corresponding_node
252             {
253 65     65 0 140 my $self = shift;
254 65         77 my $tjoin = shift;
255 65         72 my $node;
256              
257             # if( $self->class eq $tjoin->class )
258             # {
259 65         79 my $prop = (keys %{$tjoin->{link}})[0];
  65         166  
260 65 100       226 if( $prop )
261             {
262 7         9 my $fingerprint = (keys %{$tjoin->{link}{$prop}})[0];
  7         16  
263 7 50       28 if( $self->{link}{$prop}{$fingerprint} )
264             {
265 7         25 $node = $self->{link}{$prop}{$fingerprint}->corresponding_node
266             (
267             $tjoin->{link}{$prop}{$fingerprint}
268             );
269             }
270             }
271             else
272             {
273 58         82 $node = $self;
274             }
275             # }
276              
277 65         189 return $node;
278             }
279              
280             ##
281             ## TABLES PROPERTIES
282             ##
283              
284             sub tables
285             {
286 75     75 0 91 my $self = shift;
287              
288 75 100 66     247 if( ! defined $self->{tables} || ! @{$self->{tables}} )
  38         96  
289             {
290 37 100 66     97 if( defined $self->{used_table} && %{$self->{used_table}} )
  32         182  
291             {
292 32         37 @{$self->{tables}} = sort { $self->{used_table}{$a} <=> $self->{used_table}{$b} } keys %{$self->{used_table}};
  32         130  
  4         20  
  32         120  
293             }
294             else
295             {
296 5         13 $self->{tables}[0] = $self->class->_db_table( 0 );
297             }
298             }
299              
300 75         546 return $self->{tables};
301             }
302              
303 16     16 0 1261 sub tables_count { scalar @{ $_[0]->tables }; }
  16         257  
304              
305 3     3 0 59 sub select_basic_tables { $_[0]->tables; }
306 2     2 0 8 sub first_basic_table_alias { $_[0]->table_alias_or_name( $_[0]->tables->[0] ); }
307 1     1 0 10 sub first_basic_table_as_alias { $_[0]->table_as_alias( $_[0]->tables->[0] ); }
308              
309             sub table_alias
310             {
311 62     62 0 76 my $self = shift;
312 62         74 my $table = shift;
313 62         76 my $alias;
314              
315 62 100       152 if( $self->{alias_num} )
316             {
317 36 50       124 $alias = '_T'.$self->{alias_num}.($self->{alias} ? '_'.$self->{alias} : '').'_'.$table;
318             }
319              
320 62         157 return $alias;
321             }
322              
323             sub table_alias_or_name
324             {
325 4     4 0 5 my $self = shift;
326 4         9 my $table = shift;
327              
328 4   66     10 return $self->table_alias( $table ) || $table;
329             }
330              
331             sub table_as_alias
332             {
333 16     16 0 25 my $self = shift;
334 16         24 my $table = shift;
335 16         41 my $alias = $self->table_alias( $table );
336              
337 16 100       72 if( $alias )
338             {
339 8         16 $alias = $self->class->ORM::qt( $table ).' AS '.$self->class->ORM::qt( $alias );
340             }
341             else
342             {
343 8         22 $alias = $self->class->ORM::qt( $table );
344             }
345              
346 16         99 return $alias;
347             }
348              
349             sub full_field_name
350             {
351 42     42 0 63 my $self = shift;
352 42         67 my $prop = shift;
353 42   66     238 my $table = shift || ( $prop eq 'id' ? $self->tables->[0] : $self->class->_prop2table( $prop ) );
354 42         304 my $alias = $self->table_alias( $table );
355 42         55 my $name;
356              
357 42 100       70 if( $alias )
358             {
359 26         53 $name = $self->class->ORM::qt( $alias ).'.'.$self->class->ORM::qt( $prop );
360             }
361             else
362             {
363 16         39 $name = $self->class->ORM::qt( $prop );
364             }
365              
366 42         236 return $name;
367             }
368              
369             sub full_left_field_name
370             {
371 1     1 0 2 my $self = shift;
372              
373 1         4 return $self->full_field_name( $self->{left_prop}, @_ );
374             }
375              
376             ##
377             ## METHODS
378             ##
379              
380             sub use_prop
381             {
382 75     75 0 5824 my $self = shift;
383 75         134 my $prop = shift;
384              
385 75 100       235 if( $prop eq 'id' )
    100          
386             {
387             }
388             elsif( $prop eq 'class' )
389             {
390 2         6 my $table = $self->class->_db_table( 0 );
391 2 50       25 unless( exists $self->{used_table}{$table} )
392             {
393 2         13 $self->{used_table}{$table} = $self->{class_table}{$table};
394 2         7 delete $self->{tables};
395             }
396             }
397             else
398             {
399 34         107 my $table = $self->class->_prop2table( $prop );
400 34 100       417 unless( exists $self->{used_table}{$table} )
401             {
402 26         99 $self->{used_table}{$table} = $self->{class_table}{$table};
403 26         80 delete $self->{tables};
404             }
405             }
406             }
407              
408             sub assign_aliases
409             {
410 12     12 0 21 my $self = shift;
411 12   100     64 my $alias = shift||1;
412              
413 12         25 $self->{alias_num} = $alias;
414 12         20 for my $prop ( keys %{$self->{link}} )
  12         44  
415             {
416 1         2 for my $fingerprint ( keys %{$self->{link}{$prop}} )
  1         13  
417             {
418 1         13 $alias = $self->{link}{$prop}{$fingerprint}->assign_aliases( $alias+1 );
419             }
420             }
421              
422 12 100 100     121 $self->{alias_num} = undef if( $self->{alias_num} == 1 && $alias == 1 && $self->tables_count == 1 );
      100        
423              
424 12         38 return $alias;
425             }
426              
427             ## use: $tjoin->link( $prop => $tjoin );
428             ##
429             sub link
430             {
431 8     8 0 9 my $self = shift;
432 8         11 my $prop = shift;
433 8         8 my $tjoin = shift;
434              
435 8         12 $self->{alias_num} = undef;
436 8 50       15 if( $self->class->_has_prop( $prop ) )
437             {
438 8         97 $self->{link}{ $prop }{ $tjoin->fingerprint } = $tjoin;
439 8         19 $self->use_prop( $prop );
440             }
441             else
442             {
443 0         0 croak "Can't link tjoin '".$tjoin->class."' to property '$prop' (class '".$self->class."' doesn't have it)";
444             }
445             }
446              
447             sub merge
448             {
449 26     26 0 41 my $self = shift;
450 26         33 my $tjoin = shift;
451              
452 26 100       77 if( ref $tjoin ne 'ORM::TjoinNull' )
453             {
454 25 50       50 if( UNIVERSAL::isa( $self->class, $tjoin->class ) )
    0          
455             {
456             # Do nothing
457             }
458             elsif( UNIVERSAL::isa( $tjoin->class, $self->class ) )
459             {
460 0         0 $self->{class} = $tjoin->class;
461 0         0 %{$self->{class_table}} = %{$tjoin->{class_table}};
  0         0  
  0         0  
462             }
463             else
464             {
465 0         0 croak "Internal error! Can't merge, '$self->{class}' and '".$tjoin->class."' are incompatible.";
466             }
467              
468 25         48 $self->{alias_num} = undef;
469              
470 25         30 for my $table ( @{ $tjoin->tables } )
  25         56  
471             {
472 25 100       81 unless( $self->{used_table}{$table} )
473             {
474 13         32 $self->{used_table}{$table} = $self->{class_table}{$table};
475 13         32 delete $self->{tables};
476             }
477             }
478              
479 25         41 for my $prop ( keys %{$tjoin->{link}} )
  25         148  
480             {
481 7         7 for my $fingerprint ( keys %{$tjoin->{link}{$prop}} )
  7         16  
482             {
483 7 100       21 if( exists $self->{link}{$prop}{$fingerprint} )
484             {
485 6         22 $self->{link}{$prop}{$fingerprint}->merge( $tjoin->{link}{$prop}{$fingerprint} );
486             }
487             else
488             {
489 1         5 $self->link( $prop => $tjoin->{link}{$prop}{$fingerprint}->copy );
490             }
491             }
492             }
493             }
494             }