line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Tree::DAG_Node::Persist; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
585
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
27
|
|
4
|
1
|
|
|
1
|
|
3
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
22
|
|
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
3
|
use Moo; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
5
|
|
7
|
|
|
|
|
|
|
|
8
|
1
|
|
|
1
|
|
213
|
use Scalar::Util 'refaddr'; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
70
|
|
9
|
|
|
|
|
|
|
|
10
|
1
|
|
|
1
|
|
10
|
use Tree::DAG_Node; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
26
|
|
11
|
|
|
|
|
|
|
|
12
|
1
|
|
|
1
|
|
3
|
use Types::Standard qw/Any Str/; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
5
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
has context => |
15
|
|
|
|
|
|
|
( |
16
|
|
|
|
|
|
|
default => sub{return '-'}, |
17
|
|
|
|
|
|
|
is => 'rw', |
18
|
|
|
|
|
|
|
isa => Str, |
19
|
|
|
|
|
|
|
required => 0, |
20
|
|
|
|
|
|
|
); |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
has context_col => |
23
|
|
|
|
|
|
|
( |
24
|
|
|
|
|
|
|
default => sub{return 'context'}, |
25
|
|
|
|
|
|
|
is => 'rw', |
26
|
|
|
|
|
|
|
isa => Str, |
27
|
|
|
|
|
|
|
required => 0, |
28
|
|
|
|
|
|
|
); |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
has dbh => |
31
|
|
|
|
|
|
|
( |
32
|
|
|
|
|
|
|
default => sub{return ''}, |
33
|
|
|
|
|
|
|
is => 'rw', |
34
|
|
|
|
|
|
|
isa => Any, |
35
|
|
|
|
|
|
|
required => 0, |
36
|
|
|
|
|
|
|
); |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
has id_col => |
39
|
|
|
|
|
|
|
( |
40
|
|
|
|
|
|
|
default => sub{return 'id'}, |
41
|
|
|
|
|
|
|
is => 'rw', |
42
|
|
|
|
|
|
|
isa => Str, |
43
|
|
|
|
|
|
|
required => 0, |
44
|
|
|
|
|
|
|
); |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
has mother_id_col => |
47
|
|
|
|
|
|
|
( |
48
|
|
|
|
|
|
|
default => sub{return 'mother_id'}, |
49
|
|
|
|
|
|
|
is => 'rw', |
50
|
|
|
|
|
|
|
isa => Str, |
51
|
|
|
|
|
|
|
required => 0, |
52
|
|
|
|
|
|
|
); |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
has name_col => |
55
|
|
|
|
|
|
|
( |
56
|
|
|
|
|
|
|
default => sub{return 'name'}, |
57
|
|
|
|
|
|
|
is => 'rw', |
58
|
|
|
|
|
|
|
isa => Str, |
59
|
|
|
|
|
|
|
required => 0, |
60
|
|
|
|
|
|
|
); |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
has table_name => |
63
|
|
|
|
|
|
|
( |
64
|
|
|
|
|
|
|
default => sub{return 'trees'}, |
65
|
|
|
|
|
|
|
is => 'rw', |
66
|
|
|
|
|
|
|
isa => Str, |
67
|
|
|
|
|
|
|
required => 0, |
68
|
|
|
|
|
|
|
); |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
has unique_id_col => |
71
|
|
|
|
|
|
|
( |
72
|
|
|
|
|
|
|
default => sub{return 'unique_id'}, |
73
|
|
|
|
|
|
|
is => 'rw', |
74
|
|
|
|
|
|
|
isa => Str, |
75
|
|
|
|
|
|
|
required => 0, |
76
|
|
|
|
|
|
|
); |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
our $VERSION = '1.12'; |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# -------------------------------------------------- |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
sub read |
83
|
|
|
|
|
|
|
{ |
84
|
2
|
|
|
2
|
0
|
909
|
my($self, $extra) = @_; |
85
|
2
|
|
|
|
|
76
|
my($table_name) = $self -> table_name; |
86
|
2
|
|
|
|
|
48
|
my($sql) = |
87
|
|
|
|
|
|
|
"select * from $table_name where " . |
88
|
|
|
|
|
|
|
$self -> context_col . |
89
|
|
|
|
|
|
|
' = ? order by ' . |
90
|
|
|
|
|
|
|
$self -> unique_id_col; |
91
|
2
|
|
|
|
|
74
|
my($record) = $self -> dbh -> selectall_arrayref($sql, {Slice => {} }, $self -> context); |
92
|
|
|
|
|
|
|
|
93
|
2
|
100
|
|
|
|
987
|
if (! $extra) |
94
|
|
|
|
|
|
|
{ |
95
|
1
|
|
|
|
|
3
|
$extra = []; |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
|
98
|
2
|
|
|
|
|
5
|
my($id); |
99
|
|
|
|
|
|
|
my($mother_id); |
100
|
0
|
|
|
|
|
0
|
my($node); |
101
|
0
|
|
|
|
|
0
|
my($row, $root_id); |
102
|
0
|
|
|
|
|
0
|
my(%seen); |
103
|
|
|
|
|
|
|
|
104
|
2
|
|
|
|
|
5
|
for $row (@$record) |
105
|
|
|
|
|
|
|
{ |
106
|
41
|
|
|
|
|
2205
|
$id = $$row{$self -> id_col}; |
107
|
41
|
|
|
|
|
1457
|
$mother_id = $$row{$self -> mother_id_col}; |
108
|
41
|
|
|
|
|
199
|
$node = Tree::DAG_Node -> new(); |
109
|
41
|
|
|
|
|
898
|
$seen{$id} = $node; |
110
|
41
|
|
|
|
|
52
|
${$node -> attributes}{id} = $id; |
|
41
|
|
|
|
|
61
|
|
111
|
41
|
|
|
|
|
191
|
${$node -> attributes}{$_} = $$row{$_} for @$extra; |
|
20
|
|
|
|
|
28
|
|
112
|
|
|
|
|
|
|
|
113
|
41
|
|
|
|
|
718
|
$node -> name($$row{$self -> name_col}); |
114
|
|
|
|
|
|
|
|
115
|
41
|
100
|
|
|
|
338
|
if ($seen{$mother_id}) |
|
|
50
|
|
|
|
|
|
116
|
|
|
|
|
|
|
{ |
117
|
39
|
|
|
|
|
70
|
$seen{$mother_id} -> add_daughter($node); |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
elsif (! $mother_id) |
120
|
|
|
|
|
|
|
{ |
121
|
2
|
|
|
|
|
4
|
$root_id = $id; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
2
|
|
|
|
|
710
|
return $seen{$root_id}; |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
} # End of read. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# -------------------------------------------------- |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub write_node |
132
|
|
|
|
|
|
|
{ |
133
|
41
|
|
|
41
|
0
|
2651
|
my($node, $opt) = @_; |
134
|
|
|
|
|
|
|
|
135
|
41
|
|
|
|
|
106
|
$$opt{unique_id}++; |
136
|
|
|
|
|
|
|
|
137
|
41
|
|
|
|
|
214
|
my($mother) = $node -> mother; |
138
|
41
|
100
|
|
|
|
541
|
my($mum_ref) = $mother ? refaddr $mother : 0; |
139
|
41
|
|
100
|
|
|
280
|
my($mum_id) = $$opt{id}{$mum_ref} || 0; |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
$$opt{sth} -> execute |
142
|
|
|
|
|
|
|
( |
143
|
|
|
|
|
|
|
$mum_id, |
144
|
|
|
|
|
|
|
$$opt{unique_id}, |
145
|
|
|
|
|
|
|
$$opt{context}, |
146
|
|
|
|
|
|
|
$node -> name, |
147
|
41
|
|
|
|
|
233
|
map{${$node -> attributes}{$_} } @{$$opt{extra} }, |
|
20
|
|
|
|
|
18
|
|
|
20
|
|
|
|
|
84
|
|
|
41
|
|
|
|
|
295164
|
|
148
|
|
|
|
|
|
|
); |
149
|
|
|
|
|
|
|
|
150
|
41
|
|
|
|
|
292401
|
my($id) = $$opt{dbh} -> last_insert_id(undef, undef, $$opt{table_name}, undef); |
151
|
41
|
|
|
|
|
245
|
my($refaddr) = refaddr $node; |
152
|
41
|
|
|
|
|
297
|
$$opt{id}{$refaddr} = $id; |
153
|
|
|
|
|
|
|
|
154
|
41
|
|
|
|
|
320
|
return 1; |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
} # End of write_node. |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# -------------------------------------------------- |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
sub write |
161
|
|
|
|
|
|
|
{ |
162
|
2
|
|
|
2
|
0
|
15441
|
my($self, $tree, $extra) = @_; |
163
|
2
|
|
|
|
|
27
|
my($table_name) = $self -> table_name; |
164
|
2
|
|
|
|
|
595
|
my($sql) = "delete from $table_name where " . $self -> context_col . ' = ?'; |
165
|
2
|
|
|
|
|
467
|
my($sth) = $self -> dbh -> prepare_cached($sql); |
166
|
|
|
|
|
|
|
|
167
|
2
|
|
|
|
|
683
|
$sth -> execute($self -> context); |
168
|
|
|
|
|
|
|
|
169
|
2
|
|
|
|
|
765
|
$sql = "insert into $table_name (" . |
170
|
|
|
|
|
|
|
$self -> mother_id_col . |
171
|
|
|
|
|
|
|
', ' . |
172
|
|
|
|
|
|
|
$self -> unique_id_col . |
173
|
|
|
|
|
|
|
', ' . |
174
|
|
|
|
|
|
|
$self -> context_col . |
175
|
|
|
|
|
|
|
', ' . |
176
|
|
|
|
|
|
|
$self -> name_col; |
177
|
|
|
|
|
|
|
|
178
|
2
|
100
|
66
|
|
|
1658
|
if ($extra && @$extra) |
179
|
|
|
|
|
|
|
{ |
180
|
1
|
|
|
|
|
5
|
$sql .= ', ' . join(', ', @$extra); |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
2
|
|
|
|
|
3
|
$sql .= ') values (?, ?, ?, ?'; |
184
|
|
|
|
|
|
|
|
185
|
2
|
100
|
66
|
|
|
12
|
if ($extra && @$extra) |
186
|
|
|
|
|
|
|
{ |
187
|
1
|
|
|
|
|
3
|
$sql .= ', ?' x @$extra; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
2
|
|
|
|
|
6
|
$sql .= ')'; |
191
|
|
|
|
|
|
|
|
192
|
2
|
|
100
|
|
|
30
|
$tree -> walk_down |
193
|
|
|
|
|
|
|
({ |
194
|
|
|
|
|
|
|
callback => \&write_node, |
195
|
|
|
|
|
|
|
context => $self -> context, |
196
|
|
|
|
|
|
|
dbh => $self -> dbh, |
197
|
|
|
|
|
|
|
_depth => 0, |
198
|
|
|
|
|
|
|
extra => $extra || [], |
199
|
|
|
|
|
|
|
id => {}, |
200
|
|
|
|
|
|
|
self => $self, |
201
|
|
|
|
|
|
|
sth => $self -> dbh -> prepare_cached($sql), |
202
|
|
|
|
|
|
|
table_name => $self -> table_name, |
203
|
|
|
|
|
|
|
unique_id => 0, |
204
|
|
|
|
|
|
|
}); |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
} # End of write. |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
# ----------------------------------------------- |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
1; |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=pod |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=head1 NAME |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
Tree::DAG_Node::Persist - Persist multiple trees in a single db table, preserving child order |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=head1 Synopsis |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
my($master) = Tree::DAG_Node::Persist -> new |
221
|
|
|
|
|
|
|
( |
222
|
|
|
|
|
|
|
context => 'Master', |
223
|
|
|
|
|
|
|
context_col => 'context', |
224
|
|
|
|
|
|
|
dbh => $dbh, |
225
|
|
|
|
|
|
|
id_col => 'id', |
226
|
|
|
|
|
|
|
mother_id_col => 'mother_id', |
227
|
|
|
|
|
|
|
name_col => 'name', |
228
|
|
|
|
|
|
|
table_name => $table_name, |
229
|
|
|
|
|
|
|
unique_id_col => 'unique_id', |
230
|
|
|
|
|
|
|
); |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
my($tree) = build_tree; # Somehow... See the FAQ for help. |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
$master -> write($tree); |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
my($shrub) = $master -> read; |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# Prune $shrub by adding/deleting its nodes... |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
my($offshoot) = Tree::DAG_Node::Persist -> new |
241
|
|
|
|
|
|
|
( |
242
|
|
|
|
|
|
|
context => 'Offshoot', # Don't use Master or it'll overwrite $tree in the db. |
243
|
|
|
|
|
|
|
dbh => $dbh, |
244
|
|
|
|
|
|
|
); |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
$offshoot -> write($shrub); |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
=head1 Description |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
L reads/writes multiple trees from/to a single database table, where those |
251
|
|
|
|
|
|
|
trees are built using L. |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
See the L for details of the table structure. |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=head1 Distributions |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
This module is available as a Unix-style distro (*.tgz). |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
See L |
260
|
|
|
|
|
|
|
for help on unpacking and installing distros. |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=head1 Installing the module |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
Install L as you would for any C module: |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
Run: |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
cpanm Tree::DAG_Node::Persist |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
or run: |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
sudo cpan Tree::DAG_Node::Persist |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
or unpack the distro, and then either: |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
perl Build.PL |
277
|
|
|
|
|
|
|
./Build |
278
|
|
|
|
|
|
|
./Build test |
279
|
|
|
|
|
|
|
sudo ./Build install |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
or: |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
perl Makefile.PL |
284
|
|
|
|
|
|
|
make (or dmake) |
285
|
|
|
|
|
|
|
make test |
286
|
|
|
|
|
|
|
make install |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
=head1 Method: context([$new_value]) |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
Get or set the value to be used in the 'context' column when the tree is written to or read from |
291
|
|
|
|
|
|
|
the database. |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
=head1 Method: context_col([$new_value]) |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
Get or set the value to be used as the name of the 'context' column when the tree is written to or |
296
|
|
|
|
|
|
|
read from the database. |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
=head1 Method: dbh([$new_value]) |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
Get or set the value to be used as the database handle when the tree is written to or read from the |
301
|
|
|
|
|
|
|
database. |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=head1 Method: id_col([$new_value]) |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
Get or set the value to be used as the name of the 'id' column when the tree is written to or read |
306
|
|
|
|
|
|
|
from the database. |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=head1 Method: mother_id_col([$new_value]) |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
Get or set the value to be used as the name of the 'mother_id' column when the tree is written to |
311
|
|
|
|
|
|
|
or read from the database. |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=head1 Method: name_col([$new_value]) |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
Get or set the value to be used as the name of the 'name' column when the tree is written to or |
316
|
|
|
|
|
|
|
read from the database. |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=head1 Method: new({...}) |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
Returns a new object of type C. |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
Key-value pairs in the hashref: |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
=over 4 |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
=item context => $a_string |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
This is the value to be used in the 'context' column when the tree is written to or read from the |
329
|
|
|
|
|
|
|
database. |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
This key is optional. |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
It defaults to '-'. |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
=item context_col => $a_string |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
This is the name to be used for the 'context' column when the tree is written to or read from the |
338
|
|
|
|
|
|
|
database. |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
This key is optional. |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
If defaults to 'context'. |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
=item dbh => A database handle |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
This is the database handle to use. |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
This key-value pair is mandatory. |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
There is no default. |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
=item id_col => $a_string |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
This is the name to be used for the 'id' column when the tree is written to or read from the |
355
|
|
|
|
|
|
|
database. |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
This key is optional. |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
If defaults to 'id'. |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
=item mother_id_col => $a_string |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
This is the name to be used for the 'mother_id' column when the tree is written to or read from the |
364
|
|
|
|
|
|
|
database. |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
This key is optional. |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
If defaults to 'mother_id'. |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
=item name_col => $a_string |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
This is the name to be used for the 'name' column when the tree is written to the database. |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
This key is optional. |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
If defaults to 'name'. |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=item table_name => $a_string |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
This is the name of the database table used for reading and writing trees. |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
This key is optional. |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
If defaults to 'trees'. |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=item unique_id_col => $a_string |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
This is the name to be used for the 'unique_id' column when the tree is written to or read from the |
389
|
|
|
|
|
|
|
database. |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
This key is optional. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
If defaults to 'unique_id'. |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=back |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=head1 Method: table name([$new_value]) |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
Get or set the value to be used as the name of the table when the tree is written to or read from |
400
|
|
|
|
|
|
|
the database. |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
=head1 Method: unique_id_col([$new_value]) |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
Get or set the value to be used as the name of the 'unique_id' column when the tree is written to |
405
|
|
|
|
|
|
|
or read from the database. |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
=head1 Method: read([$extra]) |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
Returns a tree of type L read from the database. |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
If the optional parameter $extra is provided, then it is assumed to be an arrayref of field names. |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
C is used in conjunction with C. See that method for more |
414
|
|
|
|
|
|
|
details. |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
This code shows how to save and restore an attribute of each node called 'page_id'. |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
Note: In this code, the [] indicate an arrayref, not optional parameters. |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
$object -> write($tree, ['page_id']); |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
$shrub = $object -> read(['page_id']); |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
The test program t/test.t demonstrates usage of this feature. |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=head1 Method: write_node($node, {...}) |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
This method is called by write(), and - naturally - you'll never call it directly. |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
=head1 Method: write($tree[, $extra]) |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
Writes a tree of type L to the database. |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
If the optional parameter $extra is provided, then it is assumed to be an arrayref of field names: |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
=over 4 |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
=item o Each field's name is the name of a column in the table |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
=item o Each field's value is extracted from the attributes of the node, via the field's name |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
=item o The (field name => field value) pairs are written to each record in the table |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
=back |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
In particular note that you can store - in a single table - trees which both do and don't have extra |
447
|
|
|
|
|
|
|
fields. |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
Just ensure the definition of each extra column is flexible enough to handle these alternatives. |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
The test program t/test.t demonstrates usage of this feature. |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
This method does not return a meaningful value. |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
=head1 FAQ |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
=over 4 |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=item What is the required table structure? |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
Firstly, note that the column names used here are the defaults. By supplying suitable parameters |
462
|
|
|
|
|
|
|
to C, or calling the appropriate method, you can use any column names you wish. |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
As a minimum, you must have these fields in the table used to hold the trees: |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
id $primary_key, |
467
|
|
|
|
|
|
|
mother_id integer not null, |
468
|
|
|
|
|
|
|
unique_id integer not null, |
469
|
|
|
|
|
|
|
context varchar(255) not null, |
470
|
|
|
|
|
|
|
name varchar(255) not null |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
You can generate the $primary_key text using L, as is done in t/test.t. |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
=item What is id? |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
Strictly speaking, the id field does not have to be a primary key, but it must be unique, because |
477
|
|
|
|
|
|
|
it's used as a hash key when a tree is read in from the database. |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
The value of id is stored in each node when the tree is read in, whereas the values of context and |
480
|
|
|
|
|
|
|
unique_id are not. |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
The id of a node can be recovered from the 'attribute' hashref associated with any node, using the |
483
|
|
|
|
|
|
|
code: |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
my($id) = ${$node -> attribute}{id} || 0; |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
Of course, this id (in the 'attribute' hashref) only exists if the tree has been written to the |
488
|
|
|
|
|
|
|
database and read back in. For a brand-new node, which has never been saved, there is no id value by |
489
|
|
|
|
|
|
|
default, hence the '|| 0'. Naturally, you're free to jam some sort of value in there yourself. |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
=item What is mother_id? |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
It is the id of the node which is the mother of the 'current' node. Using 'mother' rather than |
494
|
|
|
|
|
|
|
'parent', and 'daughter' rather than 'child', is terminology I have adopted from L. |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
The mother_id of the root of each tree is 0, allowing you to use 'not null' on the definition of |
497
|
|
|
|
|
|
|
mother_id. |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
This 'not null' convention is adopted from: |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
Joe Celko's SQL for Smarties 2nd edition |
502
|
|
|
|
|
|
|
Morgan Kaufmann |
503
|
|
|
|
|
|
|
1-55860-576-2 |
504
|
|
|
|
|
|
|
Section 6.9, page 120, Design Advice for NULLs |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
The mother_id of a node can be recovered from the 'attribute' hashref associated with any node, |
507
|
|
|
|
|
|
|
using the code: |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
my($mother) = $node -> mother; |
510
|
|
|
|
|
|
|
my($id) = $mother ? ${$mother -> attribute}{id} : 0; |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=item What is unique_id? |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
For a given tree (in the database), each node has the same value for context, but a unique value |
515
|
|
|
|
|
|
|
for unique_id. |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
The reason the id field is not used for this, is that nodes in one tree may be deleted, so that when |
518
|
|
|
|
|
|
|
a second tree is written to the database, if the database reuses ids, the order of ids no longer |
519
|
|
|
|
|
|
|
means anything. |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
The module writes a node to the database before it writes that node's children. By generating a |
522
|
|
|
|
|
|
|
unique value as the nodes are written, the module guarantees a node's unique_id will be less that |
523
|
|
|
|
|
|
|
the unique_ids of each of its children. |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
Then, when the nodes are read back in, the database is used to sort the nodes using their unique_id |
526
|
|
|
|
|
|
|
as the sort key. |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
In this manner, the order of children belonging to a node is preserved. |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
The field unique_id is only unique for a given tree (in the database). The root of each tree has a |
531
|
|
|
|
|
|
|
unique_id of 1. |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
The value of id is stored in each node when the tree is read in, whereas the value of context and |
534
|
|
|
|
|
|
|
unique_id are not. |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
=item What is context? |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
You give each tree some sort of identifying string, which is stored in the context field. |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
For a given tree, all nodes must have the same value for this context field. |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
Reading a tree means reading all records whose context matches the value you provide. |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
Writing a tree means: |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
=over 4 |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
=item * Delete |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
All records whose context matches the value you provide are deleted. |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
=item * Insert |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
All nodes in the tree are inserted in the table. |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
=back |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
The reason for this 2-step process is to avoid depending on ids provided by the database, which may |
559
|
|
|
|
|
|
|
be reused after records are deleted. |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
By inserting the tree afresh each time, we can ensure the unique_id values for the given tree are |
562
|
|
|
|
|
|
|
generated in such a way that when the records are read back in, sorted by unique_id, each mother |
563
|
|
|
|
|
|
|
node is read before any of its daughters. This makes it easy to insert the incoming data into a new |
564
|
|
|
|
|
|
|
tree in a reliable manner, and to guarantee daughter nodes have their order preseved throughout the |
565
|
|
|
|
|
|
|
write-then-read cycle. |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
The value of id is stored in each node when the tree is read in, whereas the value of context and |
568
|
|
|
|
|
|
|
unique_id are not. |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
=item What is name? |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
Each node can have any name you wish. See L for details. |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
The name of a node can be recovered with the name method associated with any node, using the code: |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
my($name) = $node -> name; |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
=item How do I build a tree from a text file? |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
See sub build_tree() in t/test.t, and where it's called from. |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
=item How do I process a single node? |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
See sub find_junk() or sub find_node() in t/test.t, and where they're called from. |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
=item How do I pretty-print a tree? |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
See sub pretty_print() in t/test.t, and where it's called from. |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
=item How do I run t/test.t? |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
You can set the environment variables $DBI_DSN, $DBI_USER and $DBI_PASS, and the program will use a |
593
|
|
|
|
|
|
|
table called 'menus'. The I table name is 'trees'. |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
Or, if $DBI_DSN has no value, the program will use SQLite and a default file (i.e. database) name, |
596
|
|
|
|
|
|
|
in the temp directory. |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
=back |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
=head1 Machine-Readable Change Log |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
The file Changes was converted into Changelog.ini by L. |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
=head1 See Also |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
L. This module has its own list of See Also references. |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
L. This module has its own list of See Also references. |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
L. |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
L. |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
L. |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
L. |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
Thanx to the author(s) of Tree::Persist, for various ideas implemented in this module. |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
L. |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
=head1 Repository |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
L. |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
=head1 License |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
This library is free software; you can redistribute it |
629
|
|
|
|
|
|
|
and/or modify it under the same terms as Perl 5.10.0. |
630
|
|
|
|
|
|
|
|
631
|
|
|
|
|
|
|
For more details, see the full text of the licenses at |
632
|
|
|
|
|
|
|
http://www.perlfoundation.org/artistic_license_1_0, |
633
|
|
|
|
|
|
|
and http://www.gnu.org/licenses/gpl-2.0.html. |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
=head1 Support |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
Email the author, or log a bug on RT: |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
L. |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
=head1 Author |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
L was written by Ron Savage Iron@savage.net.auE> in 2010. |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
Home page: L. |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
=head1 Copyright |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
Australian copyright (c) 2010, Ron Savage. |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
All Programs of mine are 'OSI Certified Open Source Software'; |
652
|
|
|
|
|
|
|
you can redistribute them and/or modify them under the terms of |
653
|
|
|
|
|
|
|
The Artistic License, a copy of which is available at: |
654
|
|
|
|
|
|
|
http://www.opensource.org/licenses/index.html |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
=cut |
657
|
|
|
|
|
|
|
|