File Coverage

blib/lib/DBIx/Tree.pm
Criterion Covered Total %
statement 116 176 65.9
branch 36 64 56.2
condition 24 47 51.0
subroutine 9 12 75.0
pod 1 4 25.0
total 186 303 61.3


line stmt bran cond sub pod time code
1             package DBIx::Tree;
2              
3 1     1   31953 use strict;
  1         3  
  1         44  
4 1     1   5 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         1  
  1         87  
5              
6             require Exporter;
7 1     1   5 use Carp;
  1         2  
  1         83  
8 1     1   5 use DBI;
  1         2  
  1         2519  
9              
10             @ISA = qw(Exporter);
11             # Items to export into callers namespace by default. Note: do not export
12             # names by default without a very good reason. Use EXPORT_OK instead.
13             # Do not simply export all your public functions/methods/constants.
14             @EXPORT = qw();
15             @EXPORT_OK = qw();
16              
17             ( $VERSION ) = '$Revision: 1.96 $' =~ /(?:\$Revision:\s+)?(\S+)/;
18              
19             # Preloaded methods go here.
20              
21             # Constructor.
22             #
23             sub new {
24              
25 4     4 0 449164 my $proto = shift;
26 4   33     27 my $class = ref($proto) || $proto;
27 4         9 my $self = {};
28 4         14 bless ($self, $class);
29              
30 4         26 my %args = @_;
31              
32 4         21 $self->{dbh} = $args{connection};
33              
34             # $self->{dbh}->trace(1);
35 4         45 $self->{dbh}->{RaiseError} = 1;
36              
37 4         12 $self->{table} = $args{table};
38 4         10 $self->{method} = $args{method};
39 4         10 $self->{post_method} = $args{post_method};
40 4   100     20 $self->{sth} = $args{sth} || $args{sql};
41              
42 4         7 my $columns = $args{columns};
43 4         5 $self->{columns} = $columns;
44 4         11 $self->{id_column} = $columns->[0];
45 4         14 $self->{data_column} = $columns->[1];
46 4         10 $self->{parent_id_column} = $columns->[2];
47 4 50       19 $self->{order_column} = $columns->[3] if $#$columns > 2;
48 4   33     27 $self->{order_column} ||= $self->{data_column};
49 4 50       12 $self->{order_direction} = $columns->[4] if $#$columns > 3;
50 4   50     87 $self->{order_direction}||= ''; # hush undefined warnings
51              
52 4   50     14 $self->{start_id} = $args{start_id} || 1;
53 4   50     18 $self->{threshold} = $args{threshold} || 1;
54 4         9 $self->{match_data} = $args{match_data};
55 4         8 $self->{limit} = $args{limit};
56              
57 4   50     26 $self->{recursive} = $args{recursive} || 1;
58              
59 4         16 return $self;
60             }
61              
62             sub do_query {
63              
64 0     0 0 0 my $self = shift;
65              
66 0 0       0 carp "do_query() is now a private function - you need not call it yourself"
67             if $^W;
68              
69 0         0 $self->_do_query(@_);
70             }
71              
72             sub _do_query {
73              
74 95     95   501 my ($self, $parentid, $id, $level) = @_;
75              
76 95         104 my $sth;
77              
78 95 100       268 unless ($sth = $self->{sth}) {
    100          
79 32         33 my $columns = join(', ', @{ $self->{columns} } );
  32         85  
80              
81 32         77 my $sql = "SELECT $columns FROM " . ($self->{table});
82 32 100       70 if ( $self->{match_data} ) {
83 6         14 $sql .= " WHERE $self->{data_column} like '$self->{match_data}%'";
84             }
85 32         63 $sql .= ' order by ' . $self->{order_column} . ' ' .
86             $self->{order_direction} ;
87 32 100       61 if ( $self->{limit} ) {
88 4         7 $sql .= " LIMIT $self->{limit_left}";
89             }
90 32         154 $sth = $self->{dbh}->prepare($sql);
91             } elsif (!ref $self->{sth}) {
92 42         368 $sth = $self->{dbh}->prepare($self->{sth});
93             } else {
94 21         33 $sth = $self->{sth};
95             }
96              
97 95 100 100     8506 if (defined($parentid) || defined($id)) {
98             # need to modify the statement
99 94         797 my $sql = $sth->{Statement};
100 94         247 my $conj = 'WHERE';
101              
102 94         498 my ($where, $extra);
103 94 100       538 if ($sql =~ m/\s+WHERE\s+(.*)/i) {
104 6         13 $where = $1;
105 6         40 ($extra) = $where =~ m/((?:GROUP\s+BY|ORDER\s+BY|LIMIT).*)/si;
106 6         35 $where =~ s/((?:GROUP\s+BY|ORDER\s+BY|LIMIT).*)//si;
107 6         34 $sql =~ s/\s+WHERE\s+.*//; # strip where/extra off sql
108             } else {
109 88         146 $where = '';
110 88         1030 ($extra) = $sql =~ m/((?:GROUP\s+BY|ORDER\s+BY|LIMIT).*)/si;
111 88         748 $sql =~ s/((?:GROUP\s+BY|ORDER\s+BY|LIMIT).*)//si;
112             }
113              
114 94 100       287 if ($where) {
115 6         14 $where = "$conj ( $where )";
116 6         10 $conj = 'AND';
117             }
118              
119 94 100       186 if (defined $parentid) {
120 87         183 $where .= "$conj $self->{parent_id_column} = ?";
121 87         110 $conj = ' AND';
122             }
123              
124 94 100       553 if (defined $id) {
125 7         17 $where .= "$conj $self->{id_column} = ?";
126 7         10 $conj = ' AND';
127             }
128              
129 94         208 $sql .= " $where $extra";
130 94         434 $sth = $self->{dbh}->prepare_cached($sql);
131             }
132              
133 95 100       12542 my $rc = $sth->execute(defined $parentid ? $parentid : (),
    100          
134             defined $id ? $id : ()
135             );
136 95 50       245 if (!$rc) {
137 0         0 carp("Could not issue query: $DBI::errstr");
138 0         0 return 0;
139             }
140              
141 95         857 $self->{data} = $sth->fetchall_arrayref({});
142 95 50       7089 $sth->finish if $sth->{Active};
143              
144 95 100 100     651 if (!defined($level) || ($level >= $self->{threshold}) ) {
145 93         120 $self->{limit_left} -= @{$self->{data}};
  93         171  
146             }
147 95 100       223 $self->{limit_left} = 0 if $self->{limit_left} < 0;
148              
149 95         164 1; # return success
150              
151             }
152              
153             sub tree {
154              
155 0 0   0 0 0 carp("tree() use is deprecated; use traverse() instead.\n")
156             if $^W;
157              
158 0         0 my $self = shift;
159 0         0 return $self->traverse(@_);
160             }
161              
162             sub traverse {
163              
164 7     7 1 797 my $self = shift;
165              
166             # allow local arguments to override defaults set in constructor:
167 7         19 my %args = @_;
168 7         29 while (my ($key, $val) = each %args) {
169 5         23 ($self->{$key}, $args{$key}) = ($args{$key}, $self->{$key})
170             }
171              
172             # reset limit counter:
173 7         14 $self->{limit_left} = $self->{limit};
174              
175 7         9 my $rc;
176 7 50 33     27 unless ($self->{recursive} || ($self->{threshold} gt 1 && $self->{limit}) ) {
177 0         0 $rc = $self->_traverse_linear;
178             } else {
179 7         19 $rc = $self->_traverse_recursive;
180             }
181              
182             # restore object defaults:
183 7         28 while (my ($key, $val) = each %args) {
184 5         23 ($self->{$key}, $args{$key}) = ($args{$key}, $self->{$key})
185             }
186              
187 7         21 return $rc;
188             }
189              
190             sub _traverse_recursive {
191              
192 7     7   12 my $self = shift;
193              
194 7         157 $self->_handle_node($self->{start_id}, undef, [], [], 1);
195             }
196              
197             sub _handle_node {
198              
199 87     87   170 my ($self,
200             $id,
201             $item,
202             $parentids,
203             $parentnames,
204             $level) = @_;
205              
206 87 100       229 unless (defined $item) {
207 7         25 $self->_do_query(undef, $id, $level); # special root finding invocation
208 7         19 $item = $self->{data}->[0]->{$self->{data_column}};
209             }
210              
211             # $item is not defined when the constructor is called with:
212             # o match_data = 'Some value', and
213             # o start_id = Some value, and (presumably)
214             # o The id of the match data is not start_id.
215             # In this case, the above special call for finding the root does
216             # not return a valid value for item.
217              
218 87 100 66     2059 if (defined($item) && $self->{method} && ($level >= $self->{threshold}) )
      100        
219             {
220 84         234 $self->{method}->
221             ( item => $item,
222             level => $level,
223             id => $id,
224             parent_id => $parentids,
225             parent_name => $parentnames );
226             }
227              
228 87         1395 $self->_do_query($id, undef, $level);
229 87         94 push @{$parentids}, $id;
  87         161  
230 87         97 push @{$parentnames}, $item;
  87         131  
231              
232 87         92 for my $child (@{$self->{data}}) {
  87         181  
233 80         294 $self->_handle_node($child->{$self->{id_column}},
234             $child->{$self->{data_column}},
235             $parentids,
236             $parentnames,
237             $level+1);
238             }
239              
240 87         188 pop @{$parentids};
  87         117  
241 87         108 pop @{$parentnames};
  87         107  
242              
243 87 50 66     506 if (defined($item) && $self->{post_method} && ($level >= $self->{threshold}) )
      33        
244             {
245 0           $self->{post_method}->
246             ( item => $item,
247             level => $level,
248             id => $id,
249             parent_id => $parentids,
250             parent_name => $parentnames );
251             }
252             }
253              
254             sub _traverse_linear {
255              
256 0     0     my $self = shift;
257              
258 0           $self->_do_query();
259              
260 0           my ($current, @order, @stack);
261              
262 0           my (%id_cols, %id_pnts);
263              
264 0           my $i = -1;
265 0           foreach my $aitem (@{ $self->{data} }) {
  0            
266 0           $i++;
267 0 0         if ( defined $aitem->{$self->{parent_id_column}} ) {
268 0           push @{ $id_pnts{ $aitem->{$self->{parent_id_column}} } }, $aitem->{$self->{id_column}};
  0            
269             }
270 0 0         if ( defined $aitem->{$self->{id_column}} ) {
271 0           $id_cols{ $aitem->{$self->{id_column}} } = $i;
272             }
273             }
274              
275 0           my $level = 1;
276              
277             # this non-recursive algorithm requires the use of a stack in order
278             # to process each element. After each element is processed, it is
279             # removed from the stack and its children on the next level are
280             # added to the stack. Then it starts all over again until we run out
281             # of elements.
282              
283 0           push @order, $self->{start_id};
284 0           push @stack, 1;
285              
286             # $level starts out at 1. Every time we run out of items to process
287             # at the current level (if $levelFound == 0) $level is
288             # decremented. If we get to 0, we have run out of items to process,
289             # and can call it quits.
290              
291 0           my (@parent_id, @parent_name);
292              
293 0           while ($level) {
294              
295             # search the stack for an item whose level matches $level.
296              
297 0           my $levelFound = 0;
298 0           my $i = -1;
299 0           foreach my $index (@stack) {
300 0           $i++;
301 0 0         if ($index == $level) {
302              
303             # if we have found something whose level is equal to $level,
304             # set the variable $current so we can refer to it later. Also,
305             # set the flag $levelFound
306              
307 0           $current = $order[$i];
308 0           $levelFound = 1;
309              
310             # since we've found record we don't need it on stack
311              
312 0           splice(@order,$i,1);
313 0           splice(@stack,$i,1);
314              
315 0           last;
316             }
317             }
318              
319             # if we found something at the current level, its id will be in
320             # $current, so let's process it. Otherwise, we drop through this,
321             # decrement $level, and if $level is not 0, start the process over
322             # again.
323              
324 0 0         if ($levelFound) {
325              
326             ######################################
327             #
328             # loop through the array of rows until we find the record with
329             # the id that matches $current. This is the id of the item we
330             # pulled off of $stack
331             #
332             ######################################
333 0           my $item;
334              
335 0           my $aryitem = $id_cols{ $current };
336 0 0         if (defined $aryitem) {
337              
338             ###############################
339             #
340             # the data column is used to get $item, which is the label
341             # in the tree diagram.
342             #
343             # The cartid property is the id of the shopping cart that
344             # was created in the new method
345             #
346             ###############################
347 0           $item = $self->{data}->[$aryitem]->{$self->{data_column}};
348              
349             ###############################
350             #
351             # if the calling program defined a target script, define
352             # this item on the tree as a hyperlink. include variables
353             # for id and cartid.
354             #
355             # Otherwise, just add the item as it is.
356             #
357             ###############################
358 0 0 0       $self->{method}->
359             ( item => $item,
360             level => $level,
361             id => $current,
362             parent_id => \@parent_id,
363             parent_name => \@parent_name )
364             if ($self->{method} && $level >= $self->{threshold});
365              
366              
367             }
368              
369             #################################
370             #
371             # add all the children (if any) of the current item to the stack
372             #
373             ###############################
374              
375 0           my $aitem = $id_pnts{ $current };
376 0 0         if (defined $aitem) {
377 0           foreach my $id ( @{ $aitem } ) {
  0            
378 0           push @stack, $level + 1;
379 0           push @order, $id;
380             }
381             }
382              
383 0 0 0       if ($item && $current) {
384 0           push @parent_id, $current;
385 0           push @parent_name, $item;
386             }
387 0           $level++ ;
388              
389             } else {
390              
391 0           my $current = pop @parent_id;
392 0           my $item = pop @parent_name;
393              
394 0 0 0       if ($self->{post_method} && ($level >= $self->{threshold}) )
395             {
396 0           $self->{post_method}->
397             ( item => $item,
398             level => $level,
399             id => $current,
400             parent_id => \@parent_id,
401             parent_name => \@parent_name );
402             }
403              
404 0           $level--;
405             }
406              
407             }
408              
409 0           return 1;
410              
411             }
412              
413             # Autoload methods go after =cut, and are processed by the autosplit
414             # program.
415              
416             1;
417             __END__