File Coverage

blib/lib/Tree/Persist/DB/SelfReferential.pm
Criterion Covered Total %
statement 101 101 100.0
branch 15 16 93.7
condition n/a
subroutine 10 10 100.0
pod n/a
total 126 127 99.2


line stmt bran cond sub pod time code
1             package Tree::Persist::DB::SelfReferential;
2              
3 4     4   2738 use strict;
  4         7  
  4         123  
4 4     4   15 use warnings;
  4         6  
  4         159  
5              
6 4     4   15 use base qw( Tree::Persist::DB );
  4         5  
  4         1815  
7              
8 4     4   25 use Module::Runtime;
  4         6  
  4         29  
9              
10 4     4   137 use Scalar::Util qw( blessed refaddr );
  4         8  
  4         4483  
11              
12             our $VERSION = '1.12';
13              
14             my(%defaults) =
15             (
16             id_col => 'id',
17             parent_id_col => 'parent_id',
18             value_col => 'value',
19             # class_col => 'class',
20             );
21              
22             # ----------------------------------------------
23              
24             sub _init
25             {
26 10     10   14 my($class) = shift;
27 10         10 my($opts) = @_;
28 10         38 my($self) = $class -> SUPER::_init( $opts );
29 10         17 $self->{_id} = $opts->{id};
30              
31 10         36 while ( my ($name, $val) = each %defaults )
32             {
33 30 100       115 $self->{ "_${name}" } = $opts->{ $name } ? $opts->{ $name } : $val;
34             }
35              
36 10 100       23 if ( exists $opts->{class_col} )
37             {
38 7         11 $self->{_class_col} = $opts->{class_col};
39             }
40              
41 10         18 return $self;
42              
43             } # End of _init.
44              
45             # ----------------------------------------------
46              
47             sub _reload
48             {
49 6     6   6 my($self) = shift;
50 6         10 my(%sql) = $self->_build_sql;
51 6         45 my($sth) = $self->{_dbh} -> prepare( $sql{ fetch } );
52              
53 6         543 $sth -> execute( $self->{_id} );
54              
55 6         57 my ($id, $parent_id, $value, $class) = $sth -> fetchrow_array();
56              
57 6         23 $sth -> finish;
58              
59 6         18 my($tree) = Module::Runtime::use_module($class) -> new( $value );
60 6         11080 my($ref_addr) = refaddr $self;
61              
62 6         32 $tree->meta->{$ref_addr}{id} = $id;
63 6         48 $tree->meta->{$ref_addr}{parent_id} = $parent_id;
64              
65 6         28 my(@parents) = ( $tree );
66              
67 6         5 my($node);
68             my($sth_child);
69              
70 6         19 while ( my $parent = shift @parents )
71             {
72 9         45 $sth_child = $self->{_dbh} -> prepare( $sql{ fetch_children } );
73              
74 9         662 $sth_child -> execute( $parent -> meta->{$ref_addr}{id} );
75              
76 9         206 $sth_child -> bind_columns( \my ($id, $value, $class) );
77              
78 9         235 while ($sth_child -> fetch)
79             {
80 3         11 $node = Module::Runtime::use_module($class) -> new( $value );
81              
82 3         142 $parent -> add_child( $node );
83              
84 3         370 $node->meta->{$ref_addr}{id} = $id;
85 3         20 $node->meta->{$ref_addr}{parent_id} = $parent_id;
86              
87 3         28 push @parents, $node;
88             }
89              
90 9         36 $sth_child -> finish;
91             }
92              
93 6         30 $self -> _set_tree( $tree );
94              
95 6         127 return $self;
96              
97             } # End of _reload.
98              
99             # ----------------------------------------------
100              
101             sub _create
102             {
103 8     8   19 my($self) = shift;
104 8         10 my($tree) = shift;
105 8 100       33 $tree = $self -> tree if (! $tree);
106 8         13 my($dbh) = $self->{_dbh};
107 8         20 my(%sql) = $self->_build_sql;
108             my($next_id) = do
109 8         12 {
110 8         78 my($sth) = $dbh->prepare( $sql{next_id} );
111              
112 8         2071 $sth->execute;
113 8         236 $sth->fetchrow_array;
114             };
115 8         27 my($ref_addr) = refaddr $self;
116 8         43 my($sth) = $dbh->prepare( $sql{create_node} );
117 8         545 my $traversal = $tree -> traverse( $tree -> LEVEL_ORDER );
118              
119 8         201 my($node_id);
120             my($parent_id);
121              
122 8         20 while ( my $node = $traversal->() )
123             {
124             $node_id
125             = $node -> meta->{$ref_addr}{id}
126 22         1475275 = $next_id++;
127              
128             $parent_id
129             = $node -> meta->{$ref_addr}{parent_id}
130 22         312 = eval { $node -> parent -> meta->{$ref_addr}{id} };
  22         87  
131              
132 22 100       325 if ( $self->{_class_col} )
133             {
134 17         73 $sth->execute($node_id, $parent_id, $node->value, blessed( $node ) );
135             }
136             else {
137 5         8 $sth->execute($node_id, $parent_id, $node->value);
138             }
139             }
140              
141 8         187736 $sth -> finish;
142              
143 8         194 return $self;
144              
145             } # End of _create.
146              
147             # ----------------------------------------------
148              
149             sub _commit
150             {
151 7     7   8 my($self) = shift;
152 7         13 my($dbh) = $self->{_dbh};
153 7         17 my(%sql) = $self -> _build_sql;
154 7         25 my($ref_addr) = refaddr $self;
155              
156 7         7 my($sth);
157              
158 7         8 for my $change ( @{$self->{_changes}} )
  7         19  
159             {
160 7 100       32 if ( $change->{action} eq 'change_value' )
    100          
    50          
161             {
162 2         27 $sth = $dbh->prepare_cached( $sql{set_value} );
163              
164 2         179 $sth -> execute($change->{new_value}, $change->{node}->meta->{$ref_addr}{id});
165 2         165 $sth -> finish;
166             }
167             elsif ( $change->{action} eq 'add_child' )
168             {
169 4         5 for my $child ( @{$change->{children}} )
  4         9  
170             {
171 4         13 $self -> _create( $child );
172             }
173             }
174             elsif ( $change->{action} eq 'remove_child' )
175             {
176 1         2 for my $child ( @{$change->{children}} )
  1         4  
177             {
178 1         7 $sth = $dbh -> prepare_cached( $sql{set_parent} );
179              
180 1         101 $sth -> execute(undef, $child -> meta->{$ref_addr}{id});
181 1         67 $sth -> finish;
182             }
183             }
184             }
185              
186 7         38 return $self;
187              
188             } # End of _commit.
189              
190             # ----------------------------------------------
191              
192             sub _build_sql
193             {
194 21     21   25 my($self) = shift;
195 21         152 my(%sql) =
196             (
197             next_id => <<"__END_SQL__",
198             SELECT coalesce(MAX($self->{_id_col}),0) + 1
199             FROM $self->{_table}
200             __END_SQL__
201             set_value => <<"__END_SQL__",
202             UPDATE $self->{_table}
203             SET $self->{_value_col} = ?
204             WHERE $self->{_id_col} = ?
205             __END_SQL__
206             set_parent => <<"__END_SQL__",
207             UPDATE $self->{_table}
208             SET $self->{_parent_id_col} = ?
209             WHERE $self->{_id_col} = ?
210             __END_SQL__
211             );
212              
213 21 100       39 if ( $self->{_class_col} )
214             {
215 18         69 $sql{fetch} = <<"__END_SQL__";
216             SELECT $self->{_id_col} AS id
217             ,$self->{_parent_id_col} AS parent_id
218             ,$self->{_value_col} AS value
219             ,$self->{_class_col} AS class
220             FROM $self->{_table} AS tree
221             WHERE tree.$self->{_id_col} = ?
222             __END_SQL__
223              
224 18         60 $sql{fetch_children} = <<"__END_SQL__";
225             SELECT $self->{_id_col} AS id
226             ,$self->{_value_col} AS value
227             ,$self->{_class_col} AS class
228             FROM $self->{_table} AS tree
229             WHERE tree.$self->{_parent_id_col} = ?
230             __END_SQL__
231              
232 18         61 $sql{create_node} = <<"__END_SQL__";
233             INSERT INTO $self->{_table} (
234             $self->{_id_col}
235             ,$self->{_parent_id_col}
236             ,$self->{_value_col}
237             ,$self->{_class_col}
238             ) VALUES ( ?, ?, ?, ? )
239             __END_SQL__
240             }
241             else
242             {
243 3         20 $sql{fetch} = <<"__END_SQL__";
244             SELECT $self->{_id_col} AS id
245             ,$self->{_parent_id_col} AS parent_id
246             ,$self->{_value_col} AS value
247             ,'$self->{_class}' AS class
248             FROM $self->{_table} AS tree
249             WHERE tree.$self->{_id_col} = ?
250             __END_SQL__
251              
252 3         16 $sql{fetch_children} = <<"__END_SQL__";
253             SELECT $self->{_id_col} AS id
254             ,$self->{_value_col} AS value
255             ,'$self->{_class}' AS class
256             FROM $self->{_table} AS tree
257             WHERE tree.$self->{_parent_id_col} = ?
258             __END_SQL__
259              
260 3         14 $sql{create_node} = <<"__END_SQL__";
261             INSERT INTO $self->{_table} (
262             $self->{_id_col}
263             ,$self->{_parent_id_col}
264             ,$self->{_value_col}
265             ) VALUES ( ?, ?, ? )
266             __END_SQL__
267             }
268              
269 21         127 return %sql;
270              
271             } # End of _build_sq.
272              
273             # ----------------------------------------------
274              
275             1;
276              
277             __END__