File Coverage

blib/lib/DBIx/Tree/Persist.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             package DBIx::Tree::Persist;
2              
3 1     1   140404 use strict;
  1         2  
  1         41  
4 1     1   6 use warnings;
  1         1  
  1         32  
5              
6 1     1   1182 use Data::Dumper::Concise; # For Dumper().
  1         11326  
  1         90  
7              
8 1     1   8 use DBI;
  1         2  
  1         36  
9              
10 1     1   821 use DBIx::Tree::Persist::Config;
  1         4  
  1         31  
11              
12 1     1   7 use Hash::FieldHash ':all';
  1         1  
  1         170  
13              
14             fieldhash my %copy_name => 'copy_name';
15             fieldhash my %data_structure => 'data_structure';
16             fieldhash my %dbh => 'dbh';
17             fieldhash my %starting_id => 'starting_id';
18             fieldhash my %table_name => 'table_name';
19             fieldhash my %verbose => 'verbose';
20              
21 1     1   1741 use Tree;
  1         6600  
  1         35  
22 1     1   1540 use Tree::Persist;
  0            
  0            
23              
24             our $VERSION = '1.04';
25              
26             # -----------------------------------------------
27              
28             sub build_structure
29             {
30             my($self, @node) = @_;
31             my($item_data) = [];
32              
33             my(@children);
34              
35             for my $node (@node)
36             {
37             @children = $node -> children;
38              
39             if ($#children >= 0)
40             {
41             push @$item_data,
42             {
43             text => $node -> value,
44             submenu =>
45             {
46             id => 'id_' . $self -> get_id_of_node($node),
47             itemdata => $self -> build_structure(@children),
48             },
49             };
50             }
51             else
52             {
53             push @$item_data, {text => $node -> value};
54             }
55             }
56              
57             return $item_data;
58              
59             } # End of build_structure.
60              
61             # -----------------------------------------------
62             # Note: We use 0, not null, as the parent of the root.
63             # See comments to sub Create.create_one_table() for more detail.
64             # Note: This code helps me understand how to build a tree a node at a time.
65              
66             sub copy_table
67             {
68             my($self) = @_;
69             my($old_table_name) = $self -> table_name;
70             my($table_name) = $self -> copy_name;
71             my($record) = $self -> dbh -> selectall_arrayref("select * from $old_table_name order by id", {Slice => {} });
72              
73             my($id);
74             my($node);
75             my($parent_id);
76             my($row, $root_id);
77             my(%seen);
78              
79             for $row (@$record)
80             {
81             $id = $$row{id};
82             $parent_id = $$row{parent_id};
83             $node = Tree -> new($$row{value});
84             $seen{$id} = $node;
85              
86             if ($seen{$parent_id})
87             {
88             $seen{$parent_id} -> add_child($node);
89             }
90             elsif ($parent_id == 0)
91             {
92             $root_id = $id;
93             }
94             }
95              
96             # This writes null, not 0, to the database, as the parent of the root.
97              
98             my($writer) = Tree::Persist -> create_datastore
99             ({
100             class_col => 'class',
101             dbh => $self -> dbh,
102             table => $table_name,
103             tree => $seen{$root_id},
104             type => 'DB',
105             });
106              
107             } # End of copy_table.
108              
109             # --------------------------------------------------
110              
111             sub get_id_of_node
112             {
113             my($self, $node) = @_;
114             my($meta) = $node -> meta;
115             my(@key) = grep{length} keys %$meta;
116             my($id) = $$meta{$key[0]}{id};
117              
118             return $id;
119              
120             } # End of get_id_of_node;
121              
122             # -----------------------------------------------
123              
124             sub log
125             {
126             my($self, $message) = @_;
127             $message ||= '';
128              
129             if ($self -> verbose)
130             {
131             print "$message\n";
132             }
133              
134             } # End of log.
135              
136             # -----------------------------------------------
137              
138             sub new
139             {
140             my($class, %arg) = @_;
141             $arg{copy_name} ||= '';
142             $arg{dbh} ||= '';
143             $arg{data_structure} ||= 0;
144             $arg{starting_id} ||= 1;
145             $arg{table_name} ||= '';
146             $arg{verbose} ||= 0;
147             my($self) = from_hash(bless({}, $class), \%arg);
148              
149             if (! $self -> dbh)
150             {
151             my($config) = DBIx::Tree::Persist::Config -> new -> config;
152             my(@dsn) = ($$config{dsn}, $$config{username}, $$config{password});
153             my($attr) = {};
154              
155             $self -> dbh(DBI -> connect(@dsn, $attr) );
156             }
157              
158             return $self;
159              
160             } # End of new.
161              
162             # -----------------------------------------------
163              
164             sub pretty_print
165             {
166             my($self, $tree) = @_;
167              
168             my($depth);
169             my($id);
170             my($value);
171              
172             for my $node ($tree -> traverse($tree -> PRE_ORDER) )
173             {
174             $depth = $node -> depth;
175             $id = $self -> get_id_of_node($node);
176             $value = $node -> value;
177              
178             $self -> log(' ' x $depth . "$value ($id)");
179             }
180              
181             } # End of pretty_print.
182              
183             # -----------------------------------------------
184              
185             sub run
186             {
187             my($self) = @_;
188              
189             $self -> copy_name ? $self -> copy_table : $self -> traverse;
190              
191             return 0;
192              
193             } # End of run.
194              
195             # -----------------------------------------------
196              
197             sub traverse
198             {
199             my($self) = @_;
200              
201             $self -> log('Traversing table ' . $self -> table_name . ' with a starting_id of ' . $self -> starting_id);
202              
203             # Read tree from database.
204              
205             my($reader) = Tree::Persist -> connect
206             ({
207             class_col => 'class',
208             dbh => $self -> dbh,
209             id => $self -> starting_id,
210             table => $self -> table_name,
211             type => 'DB',
212             });
213             my($tree) = $reader -> tree;
214              
215             # Traverse tree.
216              
217             $self -> data_structure ? $self -> ugly_print($tree) : $self -> pretty_print($tree);
218              
219             } # End of traverse.
220              
221             # -----------------------------------------------
222              
223             sub ugly_print
224             {
225             my($self, $tree) = @_;
226              
227             $self -> log(Dumper($self -> build_structure($tree) ) );
228              
229             } # End of ugly_print.
230              
231             # -----------------------------------------------
232              
233             1;
234              
235             =pod
236              
237             =head1 NAME
238              
239             DBIx::Tree::Persist - Play with Tree and Tree::Persist a la DBIx::Tree
240              
241             =head1 Synopsis
242              
243             First, edit lib/DBIx/Tree/Persist/.htdbix.tree.persist.conf.
244              
245             Then run the scripts in this order (see scripts/test.sh):
246              
247             =over 4
248              
249             =item scripts/drop.tables.pl
250              
251             Drop tables one and two.
252              
253             Of course, you only run this after running create.tables.pl.
254              
255             =item scripts/create.tables.pl
256              
257             Create tables one and two.
258              
259             Some notes regarding the ways tables one and two are declared (in C):
260              
261             =over 4
262              
263             =item Null 'v' Not Null
264              
265             parent_id is not 'not null', because L stores a null as the parent of the root.
266              
267             =item Foreign Keys
268              
269             If parent_id is 'references two(id)', then it cannot be set to 0 for the root, because id 0 does not exist.
270              
271             However, by omitting 'references two(id)', the parent_id of the root can be (manually) set to 0, and
272             L still reads in the tree properly.
273              
274             =back
275              
276             =item scripts/populate.tables.pl
277              
278             Populate table two from the text file data/two.txt.
279              
280             The data comes from the docs for L.
281              
282             populate.tables.pl uses neither L nor L.
283              
284             The code in C uses 0 as the parent_id of the root, whereas L uses null.
285              
286             This is both to demonstrate the point made above that L handles this, and to adhere to my convention
287             to use 'not null' whenever possible. Clearly, this is not possible when it's L writing to the
288             database. Hence table two which I write can use 'not null', but table one can't use it, since table one is
289             populated by L.
290              
291             This convention is adopted from:
292              
293             Joe Celko's SQL for Smarties 2nd edition
294             Morgan Kaufmann
295             1-55860-576-2
296             Section 6.9, page 120, Design Advice for NULLs
297              
298             =item scripts/report.tables.pl
299              
300             Report the record counts from tables one and two.
301              
302             =item scripts/tree.pl -t two -v
303              
304             Traverse and print table two.
305              
306             This run uses L, and L.
307              
308             =item scripts/tree.pl -t two -c one
309              
310             Copy table two to table one.
311              
312             This run uses L, and L.
313              
314             =item scripts/tree.pl -t two -c one
315              
316             Copy table two to table one, again. Table one now contains 2 independent trees.
317              
318             =item scripts/tree.pl -t one -s 1 -v
319              
320             Traverse and print table one, starting from id = 1.
321              
322             =item scripts/tree.pl -t one -s 21 -v
323              
324             Traverse and print table one, starting from id = 21.
325              
326             The tree structures for the 2 trees printed by the last 2 commands will be the same.
327             However, since the trees are stored at different offsets within table one, the ids
328             associated with each corresponding node will be different.
329              
330             =item scripts/tree.pl -t one -d -s 1 -v
331              
332             Use the -data_structure option to call the C method, and to output
333             that structure instead of pretty-printing the tree.
334              
335             =back
336              
337             =head1 Description
338              
339             L provides sample code for playing with Tree and Tree::Persist a la DBIx::Tree.
340              
341             =head1 Distributions
342              
343             This module is available as a Unix-style distro (*.tgz).
344              
345             See L for
346             help on unpacking and installing distros.
347              
348             =head1 Method: build_structure($root)
349              
350             Returns a Perl data structure which can be turned into JSON.
351              
352             The -data_structure option to scripts/tree.pl gives you access to this feature.
353              
354             =head1 Method: copy_table()
355              
356             If copy_name is used to pass a table name to new(), sub run() calls sub copy_table().
357              
358             If copy_name is not used, sub run() calls sub traverse().
359              
360             sub copy_table() shows how to build a tree based on a linear scan of a dataset.
361              
362             =head1 Method: new()
363              
364             See scripts/tree.pl for how to pass sample parameters to new() via a command-line program.
365              
366             C takes a hash of parameters:
367              
368             =over 4
369              
370             =item copy_name => 'A table name'
371              
372             copy_name is optional.
373              
374             If specified, the code copies the data from the table named with the -t option
375             to the table named with the -c option.
376              
377             =item dbh => $dbh
378              
379             dbh is optional.
380              
381             If specified, the code uses the $dbh provided.
382              
383             If not specified, the code reads the config file lib/DBIx/Tree/Persist/.htdbix.tree.persist.conf
384             to get parameters and calls DBI -> connect() to generate a dbh.
385              
386             This is mainly used for testing. See t/test.t.
387              
388             =item starting_id => N
389              
390             starting_id is optional.
391              
392             If specified, a tree is read from the table named with the -t option, starting at the
393             id given here.
394              
395             If not specified, starting_id defaults to 1.
396              
397             =item table_name => 'A table name'
398              
399             table_name is mandatory.
400              
401             The table named with the -t option is always used as input.
402              
403             It will (probably) have been populated with scripts/populate.tables.pl.
404              
405             =item verbose => N
406              
407             verbose is optional.
408              
409             If specified and > 0, if provides more progress reports.
410              
411             If not specified, it defaults to 0, which minimizes output.
412              
413             =back
414              
415             =head1 Method: pretty_print($root)
416              
417             Print the tree nicely. This method is called from C if the -data_structure option
418             is not used.
419              
420             =head1 Method: run()
421              
422             After calling new(...), you have to call run(). See scripts/tree.pl for sample code.
423              
424             =head1 Method: traverse()
425              
426             If copy_name is used to pass a table name to new(), sub run() calls sub copy_table().
427              
428             If copy_name is not used, sub run() calls sub traverse().
429              
430             sub traverse() shows how to build a tree from a disk file, and to then process that tree.
431              
432             if the -data_structure option (to scripts/tree.pl) is used, the tree is converted to a data structure,
433             which is then printed using the C method of L.
434              
435             If the -data_structure option is not used, the tree is pretty-printed by calling the method C.
436              
437             =head1 Support
438              
439             Email the author, or log a bug on RT:
440              
441             L.
442              
443             =head1 See Also
444              
445             L. This module has its own list of See Also references.
446              
447             L. This module has its own list of See Also references.
448              
449             L.
450              
451             L.
452              
453             L.
454              
455             L.
456              
457             L.
458              
459             =head1 Author
460              
461             L was written by Ron Savage Iron@savage.net.auE> in 2010.
462              
463             Home page: L.
464              
465             =head1 Copyright
466              
467             Australian copyright (c) 2010, Ron Savage.
468              
469             All Programs of mine are 'OSI Certified Open Source Software';
470             you can redistribute them and/or modify them under the terms of
471             The Artistic License, a copy of which is available at:
472             L.
473              
474             =cut