File Coverage

blib/lib/DBIx/Tree/MaterializedPath/Node.pm
Criterion Covered Total %
statement 572 579 98.7
branch 151 162 93.2
condition 16 19 84.2
subroutine 73 73 100.0
pod 25 25 100.0
total 837 858 97.5


line stmt bran cond sub pod time code
1             package DBIx::Tree::MaterializedPath::Node;
2              
3 18     18   123 use warnings;
  18         38  
  18         669  
4 18     18   97 use strict;
  18         37  
  18         615  
5              
6 18     18   100 use Carp;
  18         28  
  18         1729  
7 18     18   24227 use SQL::Abstract;
  18         220331  
  18         734  
8              
9 18     18   19393 use Readonly;
  18         72925  
  18         1414  
10              
11             Readonly::Scalar my $EMPTY_STRING => q{};
12              
13 18     18   13319 use DBIx::Tree::MaterializedPath::PathMapper;
  18         104  
  18         670  
14 18     18   13216 use DBIx::Tree::MaterializedPath::TreeRepresentation;
  18         54  
  18         646  
15              
16             =head1 NAME
17              
18             DBIx::Tree::MaterializedPath::Node - node objects for "materialized path" trees
19              
20             =head1 VERSION
21              
22             Version 0.06
23              
24             =cut
25              
26 18     18   108 use version 0.74; our $VERSION = qv('0.06');
  18         305  
  18         183  
27              
28             =head1 SYNOPSIS
29              
30             my $node = DBIx::Tree::MaterializedPath::Node->new( $root );
31              
32             =head1 DESCRIPTION
33              
34             This module implements nodes for a "materialized path"
35             parent/child tree.
36              
37             B Normally nodes would not be created independently - create
38             a tree first using
39             L
40             and then create/manipulate its children.
41              
42             =head1 METHODS
43              
44             =head2 new
45              
46             my $node = DBIx::Tree::MaterializedPath::Node->new( $root );
47              
48             C initializes a node in the tree.
49              
50             C expects a single argument, which must be a
51             L
52             object representing the root of the tree that this node belongs to.
53              
54             =cut
55              
56             sub new
57             {
58 429     429 1 17437 my ($class, $root, @args) = @_;
59              
60 429 100       1814 croak 'Missing tree root' unless $root;
61 428 100       4725 eval { ref($root) && $root->isa('DBIx::Tree::MaterializedPath') }
62 428 100       967 or do { croak 'Invalid tree root: not a "DBIx::Tree::MaterializedPath"' };
  2         227  
63              
64 426 100       1596 my $options = ref $args[0] eq 'HASH' ? $args[0] : {@args};
65              
66 426   66     2709 my $self = bless {}, ref($class) || $class;
67              
68 426         1821 $self->{_root} = $root;
69 426         843 $self->{_is_root} = 0;
70              
71 426         1287 $self->_init($options);
72              
73 426 100       1868 $self->_load_from_hashref($options->{data}, $options->{ignore_empty_hash})
74             if $options->{data};
75              
76 426         1408 return $self;
77             }
78              
79             sub _init
80             {
81 473     473   905 my ($self, $options) = @_;
82              
83 473         1002 $self->{_deleted} = 0;
84 473         1161 $self->{_node_sql} = {};
85              
86 473         5218 return;
87             }
88              
89             =head2 is_root
90              
91             Returns true if this node is the root of the tree
92              
93             =cut
94              
95             sub is_root
96             {
97 15     15 1 43 my ($self) = @_;
98 15         295 return $self->{_is_root};
99             }
100              
101             =head2 get_root
102              
103             Returns root of the tree
104              
105             =cut
106              
107             sub get_root
108             {
109 38     38 1 81 my ($self) = @_;
110 38         153 return $self->{_root};
111             }
112              
113             =head2 is_same_node_as
114              
115             $node->is_same_node_as( $other_node )
116              
117             Returns true if this node is the same as the specified node,
118             based on whether the two nodes have the same path.
119              
120             =cut
121              
122             sub is_same_node_as
123             {
124 25     25 1 3341 my ($self, $node) = @_;
125              
126 25 100       353 croak 'Missing node to compare with' unless $node;
127 24 100       253 eval { ref($node) && $node->isa('DBIx::Tree::MaterializedPath::Node') }
128             or
129 24 100       37 do { croak 'Invalid node: not a "DBIx::Tree::MaterializedPath::Node"' };
  2         288  
130              
131 22         76 return $self->_path eq $node->_path;
132             }
133              
134             =head2 is_ancestor_of
135              
136             $node->is_ancestor_of( $other_node )
137              
138             Returns true if this node is an ancestor of the specified node.
139              
140             Returns false if this node is the same as the specified node.
141              
142             =cut
143              
144             sub is_ancestor_of
145             {
146 17     17 1 7356 my ($self, $node) = @_;
147              
148 17 100       499 croak 'Missing node' unless $node;
149 16 100       176 eval { ref($node) && $node->isa('DBIx::Tree::MaterializedPath::Node') }
150             or
151 16 100       24 do { croak 'Invalid node: not a "DBIx::Tree::MaterializedPath::Node"' };
  2         498  
152              
153 14         43 my $mapper = $self->{_root}->{_pathmapper};
154 14         51 return $mapper->is_ancestor_of($self->_path, $node->_path);
155             }
156              
157             =head2 is_descendant_of
158              
159             $node->is_descendant_of( $other_node )
160              
161             Returns true if this node is a descendant of the specified node.
162              
163             Returns false if this node is the same as the specified node.
164              
165             =cut
166              
167             sub is_descendant_of
168             {
169 16     16 1 2153 my ($self, $node) = @_;
170              
171 16 100       158 croak 'Missing node' unless $node;
172 15 100       136 eval { ref($node) && $node->isa('DBIx::Tree::MaterializedPath::Node') }
173             or
174 15 100       24 do { croak 'Invalid node: not a "DBIx::Tree::MaterializedPath::Node"' };
  2         189  
175              
176 13         39 my $mapper = $self->{_root}->{_pathmapper};
177 13         33 return $mapper->is_descendant_of($self->_path, $node->_path);
178             }
179              
180             =head2 depth
181              
182             Returns the depth of this node in the tree.
183              
184             The root node is at depth zero.
185              
186             =cut
187              
188             sub depth
189             {
190 17     17 1 64 my ($self) = @_;
191 17         65 return $self->{_root}->{_pathmapper}->depth($self->_path);
192             }
193              
194             #
195             # Map the path to the node into the format that is stored in
196             # the database:
197             #
198             sub _map_path
199             {
200 60     60   37782 my ($self, $path) = @_;
201 60         550 return $self->{_root}->{_pathmapper}->map($path);
202             }
203              
204             #
205             # Map the path to the node from the format that is stored in
206             # the database:
207             #
208             sub _unmap_path
209             {
210 1     1   4 my ($self, $path) = @_;
211 1         5 return $self->{_root}->{_pathmapper}->unmap($path);
212             }
213              
214             #
215             # Private methods to load a row from the database into the object:
216             #
217              
218             sub _load_from_db_using_id
219             {
220 26     26   44 my ($self, $id) = @_;
221              
222 26         48 my $sql_key = 'SELECT_STAR_FROM_TABLE_WHERE_ID_EQ_X_LIMIT_1';
223 26         113 my $sql = $self->{_root}->_cached_sql($sql_key);
224              
225 26         82 $self->_load_from_db_using_sql($sql, $id);
226              
227 26         49 return;
228             }
229              
230             sub _load_from_db_using_path
231             {
232 39     39   96 my ($self, $path) = @_;
233              
234 39         92 my $sql_key = 'SELECT_STAR_FROM_TABLE_WHERE_PATH_EQ_X_LIMIT_1';
235 39         277 my $sql = $self->{_root}->_cached_sql($sql_key);
236              
237 39         411 $self->_load_from_db_using_sql($sql, $path);
238              
239 4         14 return;
240             }
241              
242             sub _load_id_from_db_using_path
243             {
244 238     238   780 my ($self, $path) = @_;
245              
246 238         469 my $sql_key = 'SELECT_ID_FROM_TABLE_WHERE_PATH_EQ_X_LIMIT_1';
247 238         1223 my $sql = $self->{_root}->_cached_sql($sql_key);
248              
249 238         832 $self->_load_from_db_using_sql($sql, $path);
250              
251 238         402 return;
252             }
253              
254             sub _load_from_db_using_sql
255             {
256 303     303   1760 my ($self, $sql, @bind_params) = @_;
257              
258 303         1487 my $sth = $self->{_root}->_cached_sth($sql);
259 303         21164 $sth->execute(@bind_params);
260 303         8132 my $row = $sth->fetchrow_hashref();
261 303         2102 $sth->finish; # in case more than one row was returned
262 303 100       13615 croak qq{No row [$sql]} unless defined $row;
263 268         1021 $self->_load_from_hashref($row);
264              
265 268         748 return;
266             }
267              
268             sub _load_from_hashref
269             {
270 729     729   1284 my ($self, $data, $ignore_empty_hash) = @_;
271              
272 729         1257 my $root = $self->{_root};
273 729         1642 my $id_col = $root->{_id_column_name};
274 729         1189 my $path_col = $root->{_path_column_name};
275              
276 729 100       3143 $self->{_id} = delete $data->{$id_col} if exists $data->{$id_col};
277 729 100       3245 $self->{_path} = delete $data->{$path_col} if exists $data->{$path_col};
278              
279 729 100       2072 if ($ignore_empty_hash)
280             {
281 23 50       30 return unless keys %{$data};
  23         92  
282             }
283              
284 706         1578 $self->{_data} = $data;
285              
286 706         2066 return;
287             }
288              
289             sub _insert_into_db_from_hashref
290             {
291 238     238   954 my ($self, $data) = @_;
292              
293 238         674 my $root = $self->{_root};
294              
295 238         796 my $path_col = $root->{_path_column_name};
296 238 50       1016 croak 'Cannot insert without path' unless $data->{$path_col};
297              
298 238         547 my $sqlmaker = $root->{_sqlmaker};
299 238         1860 my ($sql, @bind_params) =
300             $sqlmaker->insert($self->{_root}->{_table_name}, $data);
301              
302 238         88677 my $sth;
303 238         1185 eval { $sth = $root->_cached_sth($sql); 1; }
  238         932  
304 238 50       475 or do { croak 'Node data probably contains invalid column name(s)'; };
  0         0  
305              
306 238         2281409 $sth->execute(@bind_params);
307              
308             # Need to load newly-created id from database:
309 238         1884 $self->_load_id_from_db_using_path($data->{$path_col});
310              
311             # Update in-memory copy to stay consistent with database:
312 238         656 $self->_load_from_hashref($data);
313              
314 238         4614 return;
315             }
316              
317             #
318             # Private accessors:
319             #
320              
321             sub _id
322             {
323 5     5   36147 my ($self) = @_;
324 5         30 return $self->{_id};
325             }
326              
327             #
328             # (Optionally sets and) returns the path stored with the node.
329             # Setting the path will update the row in the database.
330             # Getting the path will return the path stored in the node,
331             # or will query the database using the node ID if the path has
332             # not yet been loaded.
333             #
334             sub _path
335             {
336 467     467   1172 my ($self, $path) = @_;
337              
338 467         1080 my $id = $self->{_id};
339              
340 467 100       2483 if ($path)
    50          
341             {
342 23         48 my $sql_key = 'UPDATE_TABLE_SET_PATH_EQ_X_WHERE_ID_EQ_X';
343 23         41 my $root = $self->{_root};
344 23         78 my $sql = $root->_cached_sql($sql_key);
345 23         83 my $sth = $root->_cached_sth($sql);
346              
347 23         12600 $sth->execute($path, $id);
348              
349             # Update in-memory copy to stay consistent with database:
350 23         77 $self->{_path} = $path;
351             }
352             elsif (!exists $self->{_path})
353             {
354 0         0 $self->_load_from_db_using_id($id);
355             }
356              
357 467         1960 return $self->{_path};
358             }
359              
360             =head2 table_name
361              
362             Returns the name of the database table in which this tree data
363             is stored. Useful when creating JOIN-type queries across multiple
364             tables for use with L.
365              
366             =cut
367              
368             sub table_name
369             {
370 1     1 1 3 my ($self) = @_;
371 1         6 return $self->{_root}->{_table_name};
372             }
373              
374             =head2 data
375              
376             $node->data( $optional_data_hashref )
377              
378             (Optionally sets and) returns a hashref of metadata stored with the
379             node. Setting data will update the row in the database. Getting
380             data will return the data stored in the node, or will query the
381             database using the node ID if the node data has not yet been loaded.
382              
383             If setting data, note that each key of the hash must correspond
384             to a column of the same name that already exists in the database
385             table.
386              
387             Will croak if data is not a HASHREF or is an empty hash.
388              
389             Will croak if the data hash contains keys which match either the
390             L
391             or the
392             L
393             as specified in the
394             L
395             constructor.
396              
397             =cut
398              
399             sub data
400             {
401 207     207 1 35057 my ($self, $data) = @_;
402              
403 207 100       958 if ($data)
    100          
404             {
405 10 100       197 croak 'Node data must be a HASHREF' unless ref($data) eq 'HASH';
406 9 100       12 croak 'Node data is empty' unless keys %{$data};
  9         136  
407              
408 8         21 my $root = $self->{_root};
409              
410 8         22 my $id_col = $root->{_id_column_name};
411 8 100       128 croak qq{Node data cannot overwrite id column "$id_col"}
412             if exists $data->{$id_col};
413              
414 7         16 my $path_col = $root->{_path_column_name};
415 7 100       171 croak qq{Node data cannot overwrite path column "$path_col"}
416             if exists $data->{$path_col};
417              
418 6         16 my $sqlmaker = $root->{_sqlmaker};
419 6         23 my $where = {$id_col => $self->{_id}};
420 6         45 my ($sql, @bind_params) =
421             $sqlmaker->update($root->{_table_name}, $data, $where);
422              
423 6         2177 my $sth;
424 6         31 eval { $sth = $root->_cached_sth($sql); 1; }
  5         20  
425 6 100       12 or do { croak 'Node data probably contains invalid column name(s)'; };
  1         431  
426              
427 5         63165 $sth->execute(@bind_params);
428              
429             # Update in-memory copy to stay consistent with database:
430 5         1703 $self->_load_from_hashref($data);
431             }
432             elsif (!exists $self->{_data})
433             {
434 23         72 $self->_load_from_db_using_id($self->{_id});
435             }
436              
437 202         1280 return $self->{_data};
438             }
439              
440             =head2 refresh_data
441              
442             Queries the database using the node ID to refresh the in-memory
443             copy of the node data. Returns a hashref of data stored with the
444             node.
445              
446             B Setting node metadata via L will keep the database
447             and in-memory copies of the node metadata in sync. Only use the
448             C method if you think this node's metadata in the
449             database may have changed out from under you.
450              
451             =cut
452              
453             sub refresh_data
454             {
455 3     3 1 9 my ($self) = @_;
456              
457 3         9 my $id = $self->{_id};
458 3         26 $self->_load_from_db_using_id($id);
459              
460 3         10 return $self->{_data};
461             }
462              
463             =head2 add_children
464              
465             $node->add_children( @children )
466              
467             Add one or more child nodes below this node. Returns a
468             reference to a list of the newly-created node objects.
469             New nodes will be created to the right of any existing children
470             (i.e. ordered after any existing children).
471              
472             I<@children> should be a list (or listref) of hashrefs, where
473             each hashref contains the metadata for a child to be added.
474              
475             B Children with no metadata can be added by passing empty
476             hashrefs, e.g.:
477              
478             $node->add_children({}, {}, {})
479              
480             =cut
481              
482             sub add_children
483             {
484 100     100 1 51372 my ($self, @args) = @_;
485              
486 100 100       1035 croak 'No input data' unless defined $args[0];
487              
488 99 100       597 my $children = ref $args[0] eq 'ARRAY' ? $args[0] : [@args];
489              
490 99         641 $self->_validate_new_children_data($children);
491              
492 94         684 my $next_path = $self->_next_child_path();
493              
494 94         324 my $nodes;
495              
496             my $func = sub {
497 94     94   591 ($nodes, $next_path) = $self->_add_children($next_path, $children);
498 94         696 };
499              
500 94         530 eval { $self->{_root}->_do_transaction($func); 1; }
  94         1346  
501 94 50       220 or do { croak "add_children() aborted: $@"; };
  0         0  
502              
503 94         1823 return $nodes;
504             }
505              
506             =head2 add_child
507              
508             $node->add_child( $child )
509              
510             Add a child node below this node. Returns the newly-created
511             node object.
512              
513             I<$child> should be a hashref representing the child to add.
514              
515             This is just a wrapper for L.
516              
517             =cut
518              
519             sub add_child
520             {
521 1     1 1 3 my ($self, $child) = @_;
522 1         8 my $children = $self->add_children([$child]);
523 1         28 return $children->[0];
524             }
525              
526             =head2 add_children_at_left
527              
528             $node->add_children_at_left( @children )
529              
530             Add one or more child nodes below this node, as the left-most
531             children (i.e. ordered before any existing children). Returns
532             a reference to a list of the newly-created node objects.
533              
534             B
535             end of the list (via L), since in
536             this case the paths for any existing children (and all of their
537             descendants) will need to be updated.>
538              
539             I<@children> should be a list (or listref) of hashrefs, where
540             each hashref contains the metadata for a child to be added.
541              
542             B Children with no metadata can be added by passing empty
543             hashrefs, e.g.:
544              
545             $node->add_children_at_left({}, {}, {})
546              
547             =cut
548              
549             sub add_children_at_left
550             {
551 10     10 1 7792 my ($self, @args) = @_;
552              
553 10 100       311 croak 'No input data' unless defined $args[0];
554              
555 9 100       79 my $children = ref $args[0] eq 'ARRAY' ? $args[0] : [@args];
556              
557             # Need to validate new children data first, so we don't
558             # needlessly go through the trouble of reparenting existing
559             # children if the input data is bad:
560 9         37 $self->_validate_new_children_data($children);
561              
562 4         9 my $num_new_children = scalar @{$children};
  4         12  
563              
564 4         45 my $descendants = $self->get_descendants();
565              
566 4         91 my $root = $self->{_root};
567 4         14 my $mapper = $root->{_pathmapper};
568              
569 4         15 my $first_child_path = $mapper->first_child_path($self->_path);
570 4         13 my $next_path = $first_child_path;
571              
572 4         25 my $initial_depth = $self->depth + 1;
573              
574 4         18 my $nodes;
575              
576             my $func = sub {
577              
578             # Need to reparent any existing children first (i.e.
579             # shift them to the right), so that the paths of the
580             # newly-created children don't collide:
581 4 100   4   23 if ($descendants->has_nodes)
582             {
583 2         23 $next_path =
584             $mapper->next_child_path($next_path, $num_new_children);
585              
586             my $coderef = sub {
587 9         16 my ($node, $parent, $context) = @_;
588              
589 9 100       18 if ($node->depth == $initial_depth)
590             {
591 5         14 $node->_path($next_path);
592              
593 5         19 $next_path = $mapper->next_child_path($next_path);
594             }
595             else
596             {
597 4         14 $node->_reparent($parent);
598             }
599 2         13 };
600              
601 2         9 $descendants->traverse($coderef);
602             }
603              
604 4         19 ($nodes, $next_path) =
605             $self->_add_children($first_child_path, $children);
606 4         178 };
607              
608 4         20 eval { $root->_do_transaction($func); 1; }
  4         47  
609 4 50       13 or do { croak "add_children_at_left() aborted: $@"; };
  0         0  
610              
611 4         142 return $nodes;
612             }
613              
614             sub _validate_new_children_data
615             {
616 108     108   285 my ($self, $children) = @_;
617              
618 108 100       197 croak 'Input children list is empty' unless @{$children};
  108         1334  
619              
620 106         432 my $root = $self->{_root};
621 106         331 my $id_col = $root->{_id_column_name};
622 106         369 my $path_col = $root->{_path_column_name};
623              
624             # Remember any metadata column names we encounter
625             # while checking each of the new children:
626 106         418 my %columns = ();
627              
628 106         415 foreach my $data (@{$children})
  106         508  
629             {
630 212 100       1402 croak 'Node data must be a HASHREF' unless ref($data) eq 'HASH';
631              
632 210 100       1239 croak qq{Node data cannot overwrite id column "$id_col"}
633             if exists $data->{$id_col};
634              
635 208 100       1157 croak qq{Node data cannot overwrite path column "$path_col"}
636             if exists $data->{$path_col};
637              
638 206         326 foreach (keys %{$data})
  206         917  
639             {
640 205         1144 $columns{$_} = 1;
641             }
642             }
643              
644             # Make sure any metadata column names we encountered
645             # actually exist, by trying to prepare a database handle
646             # which queries for each of them:
647              
648 100         665 my @columns = sort keys %columns;
649 100 100       929 if (@columns)
650             {
651 99         453 my $sql_key = 'VALIDATE_' . join($EMPTY_STRING, @columns);
652 99         913 my $sql = $self->{_root}->_cached_sql($sql_key, \@columns);
653              
654 99         537 eval { my $sth = $root->_cached_sth($sql); 1; }
  97         495  
655 99 100       313 or do { croak 'Node data probably contains invalid column name(s)'; };
  2         1193  
656             }
657              
658 98         373 return;
659             }
660              
661             sub _add_children
662             {
663 98     98   244 my ($self, $next_path, $children) = @_;
664              
665 98         227 my $root = $self->{_root};
666 98         266 my $path_col = $root->{_path_column_name};
667 98         247 my $mapper = $root->{_pathmapper};
668              
669 98         348 my @nodes = ();
670              
671 98         197 foreach my $data (@{$children})
  98         349  
672             {
673 204         1126 my $child = DBIx::Tree::MaterializedPath::Node->new($root);
674 204         392 my $child_data = {%{$data}, $path_col => $next_path};
  204         973  
675 204         856 $child->_insert_into_db_from_hashref($child_data);
676 204         561 push @nodes, $child;
677              
678 204         966 $next_path = $mapper->next_child_path($next_path);
679             }
680              
681 98         628 return (\@nodes, $next_path);
682             }
683              
684             =head2 get_parent
685              
686             Returns this node's parent node, or undef if this node is
687             the root.
688              
689             =cut
690              
691             sub get_parent
692             {
693 12     12 1 10487 my ($self) = @_;
694              
695 12 100       55 return if $self->{_is_root};
696              
697 11         24 my $sql_key = 'SELECT_STAR_FROM_TABLE_WHERE_PATH_FINDS_PARENT';
698              
699 11         35 my ($sql, $bind_params) = $self->_cached_node_sql_info($sql_key);
700              
701 11         65 my $sth = $self->{_root}->_cached_sth($sql);
702 11         20 $sth->execute(@{$bind_params});
  11         1402  
703              
704 11         374 my $row = $sth->fetchrow_hashref();
705 11         71 $sth->finish; # in case more than one row was returned
706 11 50       35 croak qq{No row [$sql]} unless defined $row;
707              
708 11         65 my $parent =
709             DBIx::Tree::MaterializedPath::Node->new($self->{_root}, {data => $row});
710              
711 11         55 return $parent;
712             }
713              
714             sub _reparent
715             {
716 10     10   18 my ($self, $parent) = @_;
717              
718 10   33     28 $parent ||= $self->get_parent;
719              
720 10         25 my $parent_path = $parent->_path;
721 10         19 my $prefix_length = length $parent_path;
722 10         21 my $path = $self->_path;
723 10         23 substr($path, 0, $prefix_length) = $parent_path;
724 10         33 $self->_path($path);
725              
726 10         35 return;
727             }
728              
729             =head2 get_children
730              
731             $node->get_children( $options_hashref )
732              
733             Returns a reference to a (possibly empty) ordered list of direct
734             child nodes.
735              
736             By default, any node metadata stored in the database is retrieved
737             by the database SELECT and is populated in each of the
738             corresponding node objects.
739              
740             If the optional parameters hashref contains a true value for
741             "B", then the metadata will not be retrieved from the
742             database until the L method is called on a given
743             node.
744              
745             =cut
746              
747             sub get_children
748             {
749 9     9 1 666 my ($self, @args) = @_;
750              
751 9 100       50 my $options = ref $args[0] eq 'HASH' ? $args[0] : {@args};
752              
753 9 100       32 my $delay_load = $options->{delay_load} ? 1 : 0;
754              
755 9 100       29 my $sql_key =
756             $delay_load
757             ? 'SELECT_IDPATH_FROM_TABLE_WHERE_PATH_FINDS_CHILDREN'
758             : 'SELECT_STAR_FROM_TABLE_WHERE_PATH_FINDS_CHILDREN';
759              
760 9         41 my ($sql, $bind_params) = $self->_cached_node_sql_info($sql_key);
761              
762 9         50 my $sth = $self->{_root}->_cached_sth($sql);
763 9         21 $sth->execute(@{$bind_params});
  9         2827  
764 9         120 my $column_names = $sth->{NAME}; # immediately after execute()
765              
766 9         223 my $rows = $sth->fetchall_arrayref; # fetch array of arrays
767              
768 9         70 return $self->_nodes_from_listrefs($rows, $column_names, $delay_load);
769             }
770              
771             =head2 get_siblings
772              
773             $node->get_siblings( $options_hashref )
774              
775             Returns a reference to an ordered list of sibling nodes.
776              
777             B The list will always contain at least one node, i.e.
778             the current node on which the method is being called.
779              
780             By default, any node metadata stored in the database is retrieved
781             by the database SELECT and is populated in each of the
782             corresponding node objects.
783              
784             If the optional parameters hashref contains a true value for
785             "B", then the metadata will not be retrieved from the
786             database until the L method is called on a given
787             node.
788              
789             =cut
790              
791             sub get_siblings
792             {
793 7     7 1 66 my ($self, @args) = @_;
794              
795 7 100       35 my $options = ref $args[0] eq 'HASH' ? $args[0] : {@args};
796              
797 7 100       26 my $delay_load = $options->{delay_load} ? 1 : 0;
798              
799 7 100       23 my $sql_key =
800             $delay_load
801             ? 'SELECT_IDPATH_FROM_TABLE_WHERE_PATH_FINDS_SIBLINGS'
802             : 'SELECT_STAR_FROM_TABLE_WHERE_PATH_FINDS_SIBLINGS';
803              
804 7         27 my ($sql, $bind_params) = $self->_cached_node_sql_info($sql_key);
805              
806 7         37 my $sth = $self->{_root}->_cached_sth($sql);
807 7         15 $sth->execute(@{$bind_params});
  7         1570  
808 7         99 my $column_names = $sth->{NAME}; # immediately after execute()
809              
810 7         187 my $rows = $sth->fetchall_arrayref; # fetch array of arrays
811              
812 7         57 return $self->_nodes_from_listrefs($rows, $column_names, $delay_load);
813             }
814              
815             =head2 get_siblings_to_the_right
816              
817             $node->get_siblings_to_the_right( $options_hashref )
818              
819             Returns a reference to an ordered list of any sibling nodes
820             to the right of this node.
821              
822             B The list will B contain the current node.
823              
824             By default, any node metadata stored in the database is retrieved
825             by the database SELECT and is populated in each of the
826             corresponding node objects.
827              
828             If the optional parameters hashref contains a true value for
829             "B", then the metadata will not be retrieved from the
830             database until the L method is called on a given
831             node.
832              
833             =cut
834              
835             sub get_siblings_to_the_right
836             {
837 15     15 1 2628 my ($self, @args) = @_;
838 15         61 return $self->_get_siblings_to_one_side('RIGHT', @args);
839             }
840              
841             =head2 get_siblings_to_the_left
842              
843             $node->get_siblings_to_the_left( $options_hashref )
844              
845             Returns a reference to an ordered list of any sibling nodes
846             to the left of this node.
847              
848             B The list will B contain the current node.
849              
850             By default, any node metadata stored in the database is retrieved
851             by the database SELECT and is populated in each of the
852             corresponding node objects.
853              
854             If the optional parameters hashref contains a true value for
855             "B", then the metadata will not be retrieved from the
856             database until the L method is called on a given
857             node.
858              
859             =cut
860              
861             sub get_siblings_to_the_left
862             {
863 5     5 1 1570 my ($self, @args) = @_;
864 5         19 return $self->_get_siblings_to_one_side('LEFT', @args);
865             }
866              
867             sub _get_siblings_to_one_side
868             {
869 20     20   40 my ($self, $side, @args) = @_;
870              
871 20 100       116 my $options = ref $args[0] eq 'HASH' ? $args[0] : {@args};
872              
873 20 100       85 $side = 'RIGHT' unless $side eq 'LEFT';
874              
875 20 100       140 my $delay_load = $options->{delay_load} ? 1 : 0;
876              
877 20 100       75 my $sql_key =
878             $delay_load
879             ? 'SELECT_IDPATH_FROM_TABLE_WHERE_PATH_FINDS_SIBLINGS_TO_THE_' . $side
880             : 'SELECT_STAR_FROM_TABLE_WHERE_PATH_FINDS_SIBLINGS_TO_THE_' . $side;
881              
882 20         74 my ($sql, $bind_params) = $self->_cached_node_sql_info($sql_key);
883              
884 20         200 my $sth = $self->{_root}->_cached_sth($sql);
885 20         38 $sth->execute(@{$bind_params});
  20         4234  
886 20         244 my $column_names = $sth->{NAME}; # immediately after execute()
887              
888 20         317 my $rows = $sth->fetchall_arrayref; # fetch array of arrays
889              
890 20         80 return $self->_nodes_from_listrefs($rows, $column_names, $delay_load);
891             }
892              
893             #
894             # Given an array of listrefs, create an array of nodes:
895             #
896             sub _nodes_from_listrefs
897             {
898 47     47   114 my ($self, $listrefs, $column_names, $delay_load) = @_;
899              
900 47         95 my $root = $self->{_root};
901              
902 47         78 my @column_names = @{$column_names};
  47         168  
903              
904 47         94 my @nodes = ();
905              
906 47         73 foreach my $listref (@{$listrefs})
  47         122  
907             {
908 64         126 my %data = map { $_ => shift @{$listref} } @column_names;
  178         222  
  178         961  
909 64         452 my $node = DBIx::Tree::MaterializedPath::Node->new($root,
910             {data => \%data, ignore_empty_hash => $delay_load});
911 64         260 push @nodes, $node;
912             }
913              
914 47         372 return \@nodes;
915             }
916              
917             =head2 get_descendants
918              
919             $node->get_descendants( $options_hashref )
920              
921             Returns a
922             L
923             object, which in turn can be used to
924             L
925             this node's descendants.
926              
927             By default, any node metadata stored in the database is retrieved
928             by the database SELECT and is populated in each of the
929             corresponding node objects.
930              
931             If the optional parameters hashref contains a true value for
932             "B", then the metadata will not be retrieved from the
933             database until the L method is called on a given
934             node.
935              
936             See
937             L<|DBIx::Tree::MaterializedPath::TreeRepresentation|DBIx::Tree::MaterializedPath::TreeRepresentation>
938             for information on
939             L.
940              
941             =cut
942              
943             sub get_descendants
944             {
945 34     34 1 3399 my ($self, @args) = @_;
946              
947 34 50       202 my $options = ref $args[0] eq 'HASH' ? $args[0] : {@args};
948              
949 34 100       150 my $delay_load = $options->{delay_load} ? 1 : 0;
950              
951 34 100       122 my $sql_key =
952             $delay_load
953             ? 'SELECT_IDPATH_FROM_TABLE_WHERE_PATH_FINDS_DESCENDANTS'
954             : 'SELECT_STAR_FROM_TABLE_WHERE_PATH_FINDS_DESCENDANTS';
955              
956 34         344 my ($sql, $bind_params) = $self->_cached_node_sql_info($sql_key);
957              
958 34         245 my $sth = $self->{_root}->_cached_sth($sql);
959 34         73 $sth->execute(@{$bind_params});
  34         12580  
960 34         1390 my $column_names = $sth->{NAME}; # immediately after execute()
961              
962 34         986 my $rows = $sth->fetchall_arrayref; # fetch array of arrays
963              
964 34         419 my $tree_representation =
965             DBIx::Tree::MaterializedPath::TreeRepresentation->new($self,
966             $column_names, $rows, {ignore_empty_hash => $delay_load});
967              
968 34         216 return $tree_representation;
969             }
970              
971             =head2 delete_descendants
972              
973             Delete any descendant nodes below this node.
974              
975             =cut
976              
977             sub delete_descendants
978             {
979 6     6 1 386 my ($self) = @_;
980              
981 6         22 my $sql_key = 'DELETE_FROM_TABLE_WHERE_PATH_FINDS_DESCENDANTS';
982              
983 6         39 my ($sql, $bind_params) = $self->_cached_node_sql_info($sql_key);
984              
985 6         58 my $sth = $self->{_root}->_cached_sth($sql);
986 6         15 $sth->execute(@{$bind_params});
  6         27454  
987              
988 6         63 return;
989             }
990              
991             =head2 delete
992              
993             Delete this node and any descendant nodes below it.
994              
995             If this node has any siblings to the right, the paths for those
996             siblings (and for all of their descendants, if any) will be
997             updated.
998              
999             B
1000              
1001             B The root node of the tree cannot be deleted.
1002              
1003             =cut
1004              
1005             sub delete ## no critic (Subroutines::ProhibitBuiltinHomonyms)
1006             {
1007 6     6 1 342 my ($self) = @_;
1008              
1009 6 100       368 croak 'Can\'t delete root node' if $self->{_is_root};
1010              
1011 5         20 my $root = $self->{_root};
1012 5         22 my $mapper = $root->{_pathmapper};
1013              
1014 5         28 my $siblings = $self->get_siblings_to_the_right();
1015              
1016 5         18 my $deleted_path = $self->_path;
1017 5         9 my $next_path = $deleted_path;
1018              
1019             my $func = sub {
1020              
1021             # first, delete node and its descendants:
1022 5     5   104 $self->_delete;
1023              
1024             # Next, need to reparent any siblings to the right,
1025             # as well as their descendants (if any):
1026              
1027             my $coderef = sub {
1028 3         5 my ($node, $parent, $context) = @_;
1029 3         9 $node->_reparent($parent);
1030 5         39 };
1031              
1032 5         12 foreach my $sibling (@{$siblings})
  5         31  
1033             {
1034 3         16 my $descendants = $sibling->get_descendants();
1035              
1036 3         12 $sibling->_path($next_path);
1037              
1038 3         14 $next_path = $mapper->next_child_path($next_path);
1039              
1040 3         20 $descendants->traverse($coderef);
1041             }
1042 5         114 };
1043              
1044 5         25 eval { $root->_do_transaction($func); 1; }
  5         36  
1045 5 50       15 or do { croak "delete() aborted: $@"; };
  0         0  
1046              
1047 5         106 return;
1048             }
1049              
1050             sub _delete
1051             {
1052 5     5   14 my ($self) = @_;
1053              
1054 5         12 my $sql_key = 'DELETE_FROM_TABLE_WHERE_PATH_FINDS_DESCENDANTS_AND_SELF';
1055              
1056 5         15 my ($sql, $bind_params) = $self->_cached_node_sql_info($sql_key);
1057              
1058 5         25 my $sth = $self->{_root}->_cached_sth($sql);
1059 5         12 $sth->execute(@{$bind_params});
  5         2780  
1060              
1061 5         19 $self->{_deleted} = 1;
1062              
1063 5         17 return;
1064             }
1065              
1066             =head2 find
1067              
1068             $node->find( $options_hashref )
1069              
1070             Given an L-style I clause,
1071             returns a reference to a (possibly empty) ordered list of
1072             descendant nodes that match.
1073              
1074             C accepts a hashref of arguments:
1075              
1076             =over 4
1077              
1078             =item B
1079              
1080             B
1081              
1082             An L-style I clause
1083              
1084             =item B
1085              
1086             An arrayref of additional table names to include in the SELECT
1087             statement, if the WHERE clause queries across any tables other
1088             than the main table in which the tree resides.
1089              
1090             B If querying across additional tables, make sure that
1091             the column names referenced in the WHERE clause are correctly
1092             prefixed by the table in which they live.
1093              
1094             =item B
1095              
1096             An arrayref of column names to order the results by. If specified,
1097             this will override the default ordering by path (i.e. the order the
1098             node's descendants would be traversed).
1099              
1100             =item B
1101              
1102             By default, any node metadata stored in the database is retrieved
1103             by the database SELECT and is populated in each of the
1104             corresponding node objects.
1105              
1106             If the options hashref contains a true value for
1107             "B", then the metadata will not be retrieved from the
1108             database until the L method is called on a given
1109             node.
1110              
1111             =back
1112              
1113             For example, if you have metadata columns in your tree table
1114             named "name" and "title", you could do queries like so:
1115              
1116             # name = ?
1117             #
1118             $nodes = $node->find(where => {
1119             name => 'exact text',
1120             });
1121              
1122             # name like ?
1123             #
1124             $nodes = $node->find(where => {
1125             name => {-like => '%text'},
1126             });
1127              
1128             # (name = ?) OR (name = ?)
1129             #
1130             $nodes = $node->find(where => {
1131             name => ['this', 'that'],
1132             });
1133              
1134             # (name = ?) AND (title = ?)
1135             #
1136             $nodes = $node->find(where => {
1137             name => 'this',
1138             title => 'that',
1139             });
1140              
1141             # (name = ?) OR (title = ?)
1142             #
1143             # Note: "where" is an arrayref, not a hashref!
1144             #
1145             $nodes = $node->find(where => [
1146             {name => 'this'},
1147             {title => 'that'},
1148             ]);
1149              
1150             # (name like ?) AND (name != ?)
1151             #
1152             $nodes = $node->find(where => {
1153             name => [
1154             -and =>
1155             {-like => '%text'},
1156             {'!=' => 'bad text},
1157             ],
1158             });
1159              
1160             You can also do JOIN queries across tables using the C
1161             parameter. Suppose you have a "movies" table with columns for
1162             "id" and "title", and that your tree table has a metadata column
1163             named "movie_id" which corresponds to the "id" column in the
1164             "movies" table. You could do queries like so:
1165              
1166             my $table = $node->table_name; # the table the tree lives in
1167              
1168             # (movies.title like ?) AND (movies.id = my_tree.movie_id)
1169             #
1170             # Note the literal backslash before "= $table.movie_id"...
1171             #
1172             $nodes = $node->find(extra_tables => ['movies'],
1173             where => {
1174             'movies.title' => {-like => 'text%'},
1175             'movies.id' => \"= $table.movie_id",
1176             });
1177              
1178             =cut
1179              
1180             sub find
1181             {
1182 13     13 1 68801 my ($self, @args) = @_;
1183              
1184 13 100       68 my $options = ref $args[0] eq 'HASH' ? $args[0] : {@args};
1185              
1186 13 100       352 croak 'Missing WHERE data' unless $options->{where};
1187              
1188 11 100       34 my $delay_load = $options->{delay_load} ? 1 : 0;
1189              
1190 11         42 my $path = $self->_path;
1191 11         28 my $root = $self->{_root};
1192 11         25 my $table = $root->{_table_name};
1193              
1194             # Need column names prefixed by table in case user's WHERE does
1195             # a query across tables:
1196 11         33 my $id_col = $table . q{.} . $root->{_id_column_name};
1197 11         28 my $path_col = $table . q{.} . $root->{_path_column_name};
1198              
1199 11         22 my $mapper = $root->{_pathmapper};
1200 11         22 my $sqlmaker = $root->{_sqlmaker};
1201              
1202 11   100     69 my $tables = $options->{extra_tables} || [];
1203 11         22 push @{$tables}, $table;
  11         29  
1204              
1205             # This returns the equivalent of WHERE_PATH_FINDS_DESCENDANTS:
1206 11         63 my $descendants_where = $mapper->descendants_where_struct($path_col, $path);
1207              
1208             # Now mix in user's requested WHERE struct:
1209 11         52 my $where = [-and => [$descendants_where, [$options->{where}]]];
1210              
1211 11 100       33 my $columns = $delay_load ? [$id_col, $path_col] : q{*};
1212              
1213 11   100     63 my $order_by = $options->{order_by} || [$path_col];
1214              
1215 11         58 my ($sql, @bind_params) =
1216             $sqlmaker->select($tables, $columns, $where, $order_by);
1217              
1218 11         20554 my $sth = $self->{_root}->_cached_sth($sql);
1219              
1220 11         2089 $sth->execute(@bind_params);
1221 11         122 my $column_names = $sth->{NAME}; # immediately after execute()
1222              
1223 11         220 my $rows = $sth->fetchall_arrayref; # fetch array of arrays
1224              
1225 11         57 return $self->_nodes_from_listrefs($rows, $column_names, $delay_load);
1226             }
1227              
1228             =head2 swap_node
1229              
1230             $node->swap_node( $other_node )
1231              
1232             Swap locations (i.e. paths) between this node and the specified
1233             node. The nodes being swapped can be at different depths in the
1234             tree.
1235              
1236             B
1237             place.> E.g. swapping "B" and "E" in the tree below:
1238              
1239             A
1240             ___|_____
1241             | |
1242             B E
1243             _|_ ___|___
1244             | | | | |
1245             C D F I J
1246             _|_
1247             | |
1248             G H
1249              
1250             results in:
1251              
1252             A
1253             ___|_____
1254             | |
1255             E B
1256             _|_ ___|___
1257             | | | | |
1258             C D F I J
1259             _|_
1260             | |
1261             G H
1262              
1263             B The root node of the tree cannot be swapped with another
1264             node.
1265              
1266             =cut
1267              
1268             sub swap_node
1269             {
1270 7     7 1 4553 my ($self, $node) = @_;
1271              
1272 7 100       271 croak 'Missing node to swap with' unless $node;
1273 6 100       189 eval { ref($node) && $node->isa('DBIx::Tree::MaterializedPath::Node') }
1274             or
1275 6 100       10 do { croak 'Invalid node: not a "DBIx::Tree::MaterializedPath::Node"' };
  2         298  
1276              
1277 4 100 100     241 croak 'Can\'t swap root node' if $self->{_is_root} || $node->{_is_root};
1278              
1279 2 100       8 return if $self->is_same_node_as($node);
1280              
1281             my $func = sub {
1282 1     1   4 my ($node1, $node2) = @_;
1283 1         4 my $path1 = $node1->_path;
1284 1         5 my $path2 = $node2->_path;
1285 1         5 $node1->_path($path2);
1286 1         7 $node2->_path($path1);
1287 1         12 };
1288              
1289 1         9 eval { $self->{_root}->_do_transaction($func, $self, $node); 1; }
  1         11  
1290 1 50       4 or do { croak "swap_node() aborted: $@"; };
  0         0  
1291              
1292 1         14 return;
1293             }
1294              
1295             =head2 swap_subtree
1296              
1297             $node->swap_subtree( $other_node )
1298              
1299             Swap this node (and all of its children) with the specified node
1300             (and all of its children). The nodes being swapped can be at
1301             different depths in the tree.
1302              
1303             Any children of the nodes being swapped will move with them.
1304             E.g. swapping "B" and "E" in the tree below:
1305              
1306             A
1307             ___|_____
1308             | |
1309             B E
1310             _|_ ___|___
1311             | | | | |
1312             C D F I J
1313             _|_
1314             | |
1315             G H
1316              
1317             results in:
1318              
1319             A
1320             ___|_____
1321             | |
1322             E B
1323             ___|___ _|_
1324             | | | | |
1325             F I J C D
1326             _|_
1327             | |
1328             G H
1329              
1330             B Because subtrees are being swapped, a node cannot be
1331             swapped with one of its own ancestors or descendants.
1332              
1333             B The root node of the tree cannot be swapped with another
1334             node.
1335              
1336             =cut
1337              
1338             sub swap_subtree
1339             {
1340 9     9 1 6636 my ($self, $node) = @_;
1341              
1342 9 100       240 croak 'Missing node to swap with' unless $node;
1343 8 100       85 eval { ref($node) && $node->isa('DBIx::Tree::MaterializedPath::Node') }
1344             or
1345 8 100       11 do { croak 'Invalid node: not a "DBIx::Tree::MaterializedPath::Node"' };
  2         242  
1346              
1347 6 100 100     28 croak 'Can\'t swap root node' if $self->is_root || $node->is_root;
1348              
1349 4 100       14 return if $self->is_same_node_as($node);
1350              
1351 3 100 100     12 croak 'Can\'t swap node with ancestor/descendant'
1352             if $self->is_ancestor_of($node) || $self->is_descendant_of($node);
1353              
1354             my $func = sub {
1355 1     1   3 my ($node1, $node2) = @_;
1356              
1357             # Get descendants *before* swapping:
1358 1         6 my $descendants1 = $node1->get_descendants();
1359 1         5 my $descendants2 = $node2->get_descendants();
1360              
1361             # Swap the node paths:
1362 1         5 my $path1 = $node1->_path;
1363 1         3 my $path2 = $node2->_path;
1364 1         4 $node1->_path($path2);
1365 1         6 $node2->_path($path1);
1366              
1367             # Now update descendants using new paths:
1368             my $coderef = sub {
1369 3         7 my ($node, $parent, $context) = @_;
1370 3         50 $node->_reparent($parent);
1371 1         8 };
1372 1         8 $descendants1->traverse($coderef);
1373 1         5 $descendants2->traverse($coderef);
1374 1         9 };
1375              
1376 1         7 eval { $self->{_root}->_do_transaction($func, $self, $node); 1; }
  1         9  
1377 1 50       3 or do { croak "swap_subtree() aborted: $@"; };
  0         0  
1378              
1379 1         19 return;
1380             }
1381              
1382             =head2 clone
1383              
1384             $new_node = $node->clone
1385              
1386             Create a clone of an existing node object.
1387              
1388             =cut
1389              
1390 18     18   118930 use Clone ();
  18         62593  
  18         37680  
1391              
1392             sub clone
1393             {
1394 1     1 1 12 my ($self) = @_;
1395              
1396 1         817 my $clone = Clone::clone($self);
1397              
1398             # Fix up database handles that Clone::clone() might have broken:
1399 1         15 $clone->{_root} = $self->{_root};
1400              
1401 1         4 return $clone;
1402             }
1403              
1404             #
1405             # Query for and return the path to the last child of this node:
1406             #
1407             sub _last_child_path
1408             {
1409 94     94   617 my ($self) = @_;
1410              
1411 94         213 my $sql_key = 'SELECT_PATH_FROM_TABLE_WHERE_PATH_FINDS_LAST_CHILD';
1412              
1413 94         1922 my ($sql, $bind_params) = $self->_cached_node_sql_info($sql_key);
1414              
1415 94         633 my $sth = $self->{_root}->_cached_sth($sql);
1416              
1417 94         235 $sth->execute(@{$bind_params});
  94         26774  
1418              
1419 94         1188 my $row = $sth->fetch();
1420 94         502 $sth->finish; # in case more than one row was returned
1421 94 100       663 return (defined $row) ? $row->[0] : $EMPTY_STRING;
1422             }
1423              
1424             #
1425             # Return the path to where the next child of this node would be added:
1426             #
1427             sub _next_child_path
1428             {
1429 94     94   215 my ($self) = @_;
1430              
1431 94         312 my $mapper = $self->{_root}->{_pathmapper};
1432              
1433 94         644 my $last_child_path = $self->_last_child_path();
1434 94 100       322 if ($last_child_path)
1435             {
1436 2         20 return $mapper->next_child_path($last_child_path);
1437             }
1438             else
1439             {
1440 92         346 return $mapper->first_child_path($self->_path);
1441             }
1442             }
1443              
1444             ###################################################################
1445              
1446             #
1447             # Manage a cache of generated SQL:
1448             #
1449              
1450             sub _cached_node_sql_info
1451             {
1452 186     186   454 my ($self, $sql_key, $args) = @_;
1453              
1454 186         816 my $path = $self->_path;
1455              
1456             # The cache is keyed on both the SQL key string as well
1457             # as the path, so if the node path changes new SQL
1458             # will be generated for it:
1459 186         1040 my $sql_info = $self->{_node_sql}->{$sql_key}->{$path};
1460 186 100       768 unless ($sql_info)
1461             {
1462 179         624 my $func = "_cached_node_sql_info_$sql_key";
1463 179         1180 $sql_info = $self->$func($path, $args);
1464 179         1005 $self->{_node_sql}->{$sql_key}->{$path} = $sql_info;
1465             }
1466 186         922 return ($sql_info->{sql}, $sql_info->{bind_params});
1467             }
1468              
1469             sub _cached_node_sql_info_SELECT_PATH_FROM_TABLE_WHERE_PATH_FINDS_LAST_CHILD
1470             {
1471 92     92   271 my ($self, $path, $args) = @_;
1472              
1473 92         299 my $root = $self->{_root};
1474 92         258 my $table = $root->{_table_name};
1475 92         193 my $path_col = $root->{_path_column_name};
1476 92         227 my $pathmapper = $root->{_pathmapper};
1477 92         930 my ($where, @bind_params) = $pathmapper->child_where($path_col, $path);
1478              
1479 92         1419 my $sql = "SELECT $path_col FROM $table $where"
1480             . " ORDER BY $path_col DESC LIMIT 1";
1481              
1482             return {
1483 92         7545 sql => $sql,
1484             bind_params => \@bind_params,
1485             };
1486             }
1487              
1488             sub _cached_node_sql_info_DELETE_FROM_TABLE_WHERE_PATH_FINDS_DESCENDANTS
1489             {
1490 6     6   23 my ($self, $path, $args) = @_;
1491              
1492 6         22 my $root = $self->{_root};
1493 6         21 my $table = $root->{_table_name};
1494 6         18 my $path_col = $root->{_path_column_name};
1495 6         17 my $pathmapper = $root->{_pathmapper};
1496 6         52 my ($where, @bind_params) =
1497             $pathmapper->descendants_where($path_col, $path);
1498              
1499 6         1258 my $sql = "DELETE FROM $table $where";
1500              
1501             return {
1502 6         42 sql => $sql,
1503             bind_params => \@bind_params,
1504             };
1505             }
1506              
1507             sub _cached_node_sql_info_DELETE_FROM_TABLE_WHERE_PATH_FINDS_DESCENDANTS_AND_SELF
1508             {
1509 5     5   14 my ($self, $path, $args) = @_;
1510              
1511 5         11 my $root = $self->{_root};
1512 5         8 my $table = $root->{_table_name};
1513 5         11 my $path_col = $root->{_path_column_name};
1514 5         10 my $pathmapper = $root->{_pathmapper};
1515 5         33 my ($where, @bind_params) =
1516             $pathmapper->descendants_and_self_where($path_col, $path);
1517              
1518 5         21 my $sql = "DELETE FROM $table $where";
1519              
1520             return {
1521 5         28 sql => $sql,
1522             bind_params => \@bind_params,
1523             };
1524             }
1525              
1526             sub _cached_node_sql_info_SELECT_STAR_FROM_TABLE_WHERE_PATH_FINDS_PARENT
1527             {
1528 11     11   20 my ($self, $path, $args) = @_;
1529              
1530 11         19 my $root = $self->{_root};
1531 11         37 my $table = $root->{_table_name};
1532 11         21 my $path_col = $root->{_path_column_name};
1533 11         21 my $pathmapper = $root->{_pathmapper};
1534 11         56 my ($where, @bind_params) = $pathmapper->parent_where($path_col, $path);
1535              
1536 11         37 my $sql = "SELECT * FROM $table $where LIMIT 1";
1537              
1538             return {
1539 11         53 sql => $sql,
1540             bind_params => \@bind_params,
1541             };
1542             }
1543              
1544             sub _cached_node_sql_info_SELECT_IDPATH_FROM_TABLE_WHERE_PATH_FINDS_CHILDREN
1545             {
1546 2     2   5 my ($self, $path, $args) = @_;
1547              
1548 2         4 my $root = $self->{_root};
1549 2         94 my $table = $root->{_table_name};
1550 2         5 my $id_col = $root->{_id_column_name};
1551 2         3 my $path_col = $root->{_path_column_name};
1552 2         5 my $pathmapper = $root->{_pathmapper};
1553 2         9 my ($where, @bind_params) = $pathmapper->child_where($path_col, $path);
1554              
1555 2         10 my $sql = "SELECT $id_col, $path_col FROM $table $where ORDER BY $path_col";
1556              
1557             return {
1558 2         8 sql => $sql,
1559             bind_params => \@bind_params,
1560             };
1561             }
1562              
1563             sub _cached_node_sql_info_SELECT_STAR_FROM_TABLE_WHERE_PATH_FINDS_CHILDREN
1564             {
1565 6     6   17 my ($self, $path, $args) = @_;
1566              
1567 6         18 my $root = $self->{_root};
1568 6         19 my $table = $root->{_table_name};
1569 6         17 my $path_col = $root->{_path_column_name};
1570 6         16 my $pathmapper = $root->{_pathmapper};
1571 6         42 my ($where, @bind_params) = $pathmapper->child_where($path_col, $path);
1572              
1573 6         28 my $sql = "SELECT * FROM $table $where ORDER BY $path_col";
1574              
1575             return {
1576 6         34 sql => $sql,
1577             bind_params => \@bind_params,
1578             };
1579             }
1580              
1581             sub _cached_node_sql_info_SELECT_IDPATH_FROM_TABLE_WHERE_PATH_FINDS_SIBLINGS
1582             {
1583 2     2   9 my ($self, $path, $args) = @_;
1584              
1585 2         5 my $root = $self->{_root};
1586 2         5 my $table = $root->{_table_name};
1587 2         4 my $id_col = $root->{_id_column_name};
1588 2         5 my $path_col = $root->{_path_column_name};
1589 2         4 my $pathmapper = $root->{_pathmapper};
1590 2         11 my ($where, @bind_params) = $pathmapper->sibling_where($path_col, $path);
1591              
1592 2         19 my $sql = "SELECT $id_col, $path_col FROM $table $where ORDER BY $path_col";
1593              
1594             return {
1595 2         10 sql => $sql,
1596             bind_params => \@bind_params,
1597             };
1598             }
1599              
1600             sub _cached_node_sql_info_SELECT_STAR_FROM_TABLE_WHERE_PATH_FINDS_SIBLINGS
1601             {
1602 4     4   10 my ($self, $path, $args) = @_;
1603              
1604 4         12 my $root = $self->{_root};
1605 4         11 my $table = $root->{_table_name};
1606 4         10 my $path_col = $root->{_path_column_name};
1607 4         9 my $pathmapper = $root->{_pathmapper};
1608 4         27 my ($where, @bind_params) = $pathmapper->sibling_where($path_col, $path);
1609              
1610 4         17 my $sql = "SELECT * FROM $table $where ORDER BY $path_col";
1611              
1612             return {
1613 4         25 sql => $sql,
1614             bind_params => \@bind_params,
1615             };
1616             }
1617              
1618             sub _cached_node_sql_info_SELECT_IDPATH_FROM_TABLE_WHERE_PATH_FINDS_SIBLINGS_TO_THE_RIGHT
1619             {
1620 2     2   5 my ($self, $path, $args) = @_;
1621              
1622 2         5 my $root = $self->{_root};
1623 2         5 my $table = $root->{_table_name};
1624 2         5 my $id_col = $root->{_id_column_name};
1625 2         5 my $path_col = $root->{_path_column_name};
1626 2         4 my $pathmapper = $root->{_pathmapper};
1627 2         13 my ($where, @bind_params) =
1628             $pathmapper->sibling_to_the_right_where($path_col, $path);
1629              
1630 2         11 my $sql = "SELECT $id_col, $path_col FROM $table $where ORDER BY $path_col";
1631              
1632             return {
1633 2         11 sql => $sql,
1634             bind_params => \@bind_params,
1635             };
1636             }
1637              
1638             sub _cached_node_sql_info_SELECT_STAR_FROM_TABLE_WHERE_PATH_FINDS_SIBLINGS_TO_THE_RIGHT
1639             {
1640 12     12   28 my ($self, $path, $args) = @_;
1641              
1642 12         34 my $root = $self->{_root};
1643 12         29 my $table = $root->{_table_name};
1644 12         29 my $path_col = $root->{_path_column_name};
1645 12         28 my $pathmapper = $root->{_pathmapper};
1646 12         82 my ($where, @bind_params) =
1647             $pathmapper->sibling_to_the_right_where($path_col, $path);
1648              
1649 12         47 my $sql = "SELECT * FROM $table $where ORDER BY $path_col";
1650              
1651             return {
1652 12         68 sql => $sql,
1653             bind_params => \@bind_params,
1654             };
1655             }
1656              
1657             sub _cached_node_sql_info_SELECT_IDPATH_FROM_TABLE_WHERE_PATH_FINDS_SIBLINGS_TO_THE_LEFT
1658             {
1659 1     1   3 my ($self, $path, $args) = @_;
1660              
1661 1         4 my $root = $self->{_root};
1662 1         3 my $table = $root->{_table_name};
1663 1         4 my $id_col = $root->{_id_column_name};
1664 1         4 my $path_col = $root->{_path_column_name};
1665 1         3 my $pathmapper = $root->{_pathmapper};
1666 1         7 my ($where, @bind_params) =
1667             $pathmapper->sibling_to_the_left_where($path_col, $path);
1668              
1669 1         6 my $sql = "SELECT $id_col, $path_col FROM $table $where ORDER BY $path_col";
1670              
1671             return {
1672 1         6 sql => $sql,
1673             bind_params => \@bind_params,
1674             };
1675             }
1676              
1677             sub _cached_node_sql_info_SELECT_STAR_FROM_TABLE_WHERE_PATH_FINDS_SIBLINGS_TO_THE_LEFT
1678             {
1679 4     4   9 my ($self, $path, $args) = @_;
1680              
1681 4         10 my $root = $self->{_root};
1682 4         11 my $table = $root->{_table_name};
1683 4         8 my $path_col = $root->{_path_column_name};
1684 4         7 my $pathmapper = $root->{_pathmapper};
1685 4         22 my ($where, @bind_params) =
1686             $pathmapper->sibling_to_the_left_where($path_col, $path);
1687              
1688 4         16 my $sql = "SELECT * FROM $table $where ORDER BY $path_col";
1689              
1690             return {
1691 4         20 sql => $sql,
1692             bind_params => \@bind_params,
1693             };
1694             }
1695              
1696             sub _cached_node_sql_info_SELECT_IDPATH_FROM_TABLE_WHERE_PATH_FINDS_DESCENDANTS
1697             {
1698 1     1   4 my ($self, $path, $args) = @_;
1699              
1700 1         2 my $root = $self->{_root};
1701 1         3 my $table = $root->{_table_name};
1702 1         2 my $id_col = $root->{_id_column_name};
1703 1         2 my $path_col = $root->{_path_column_name};
1704 1         2 my $pathmapper = $root->{_pathmapper};
1705 1         5 my ($where, @bind_params) =
1706             $pathmapper->descendants_where($path_col, $path);
1707              
1708 1         7 my $sql = "SELECT $id_col, $path_col FROM $table $where ORDER BY $path_col";
1709              
1710             return {
1711 1         5 sql => $sql,
1712             bind_params => \@bind_params,
1713             };
1714             }
1715              
1716             sub _cached_node_sql_info_SELECT_STAR_FROM_TABLE_WHERE_PATH_FINDS_DESCENDANTS
1717             {
1718 31     31   73 my ($self, $path, $args) = @_;
1719              
1720 31         88 my $root = $self->{_root};
1721 31         101 my $table = $root->{_table_name};
1722 31         85 my $path_col = $root->{_path_column_name};
1723 31         80 my $pathmapper = $root->{_pathmapper};
1724 31         249 my ($where, @bind_params) =
1725             $pathmapper->descendants_where($path_col, $path);
1726              
1727 31         140 my $sql = "SELECT * FROM $table $where ORDER BY $path_col";
1728              
1729             return {
1730 31         164 sql => $sql,
1731             bind_params => \@bind_params,
1732             };
1733             }
1734              
1735             ###################################################################
1736              
1737             1;
1738              
1739             __END__