| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package DBIx::Class::Tree::Mobius; |
|
2
|
|
|
|
|
|
|
# ABSTRACT: Manage trees of data using the Möbius encoding (nested intervals with continued fraction) |
|
3
|
|
|
|
|
|
|
|
|
4
|
1
|
|
|
1
|
|
679
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
27
|
|
|
5
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
35
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
1
|
|
|
1
|
|
13
|
use base qw/DBIx::Class/; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
2171
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
__PACKAGE__->mk_classdata( 'parent_virtual_column' => 'parent' ); |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
__PACKAGE__->mk_classdata( '_mobius_a_column' => 'mobius_a' ); |
|
12
|
|
|
|
|
|
|
__PACKAGE__->mk_classdata( '_mobius_b_column' => 'mobius_b' ); |
|
13
|
|
|
|
|
|
|
__PACKAGE__->mk_classdata( '_mobius_c_column' => 'mobius_c' ); |
|
14
|
|
|
|
|
|
|
__PACKAGE__->mk_classdata( '_mobius_d_column' => 'mobius_d' ); |
|
15
|
|
|
|
|
|
|
__PACKAGE__->mk_classdata( '_lft_column' => 'lft' ); |
|
16
|
|
|
|
|
|
|
__PACKAGE__->mk_classdata( '_rgt_column' => 'rgt' ); |
|
17
|
|
|
|
|
|
|
__PACKAGE__->mk_classdata( '_is_inner_column' => 'is_inner' ); |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
sub add_mobius_tree_columns { |
|
20
|
0
|
|
|
0
|
|
|
my $class = shift; |
|
21
|
0
|
|
|
|
|
|
my %column_names = @_; |
|
22
|
|
|
|
|
|
|
|
|
23
|
0
|
|
|
|
|
|
foreach my $name (qw/ mobius_a mobius_b mobius_c mobius_d lft rgt is_inner /) { |
|
24
|
0
|
0
|
|
|
|
|
next unless exists $column_names{$name}; |
|
25
|
0
|
|
|
|
|
|
my $accessor = "_${name}_column"; |
|
26
|
0
|
|
|
|
|
|
$class->$accessor( $column_names{$name} ); |
|
27
|
|
|
|
|
|
|
} |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
$class->add_columns( |
|
30
|
0
|
|
|
|
|
|
$class->_mobius_a_column => { data_type => 'INT', size => 11, is_nullable => 1, extra => { unsigned => 1} }, |
|
31
|
|
|
|
|
|
|
$class->_mobius_b_column => { data_type => 'INT', size => 11, is_nullable => 1, extra => { unsigned => 1} }, |
|
32
|
|
|
|
|
|
|
$class->_mobius_c_column => { data_type => 'INT', size => 11, is_nullable => 1, extra => { unsigned => 1} }, |
|
33
|
|
|
|
|
|
|
$class->_mobius_d_column => { data_type => 'INT', size => 11, is_nullable => 1, extra => { unsigned => 1} }, |
|
34
|
|
|
|
|
|
|
$class->_lft_column => { data_type => 'DOUBLE', is_nullable => 0, default_value => 1, extra => { unsigned => 1} }, |
|
35
|
|
|
|
|
|
|
$class->_rgt_column => { data_type => 'DOUBLE', is_nullable => 1, default_value => undef, extra => { unsigned => 1} }, |
|
36
|
|
|
|
|
|
|
$class->_is_inner_column => { data_type => "BOOLEAN", default_value => 0, is_nullable => 0 }, |
|
37
|
|
|
|
|
|
|
); |
|
38
|
|
|
|
|
|
|
|
|
39
|
0
|
|
|
|
|
|
$class->add_unique_constraint( $class->_mobius_a_column . $class->_mobius_c_column, [ $class->_mobius_a_column, $class->_mobius_c_column ] ); |
|
40
|
|
|
|
|
|
|
|
|
41
|
0
|
0
|
|
|
|
|
if ($class =~ /::([^:]+)$/) { |
|
42
|
|
|
|
|
|
|
|
|
43
|
0
|
|
|
|
|
|
$class->belongs_to( 'parent' => $1 => { |
|
44
|
|
|
|
|
|
|
"foreign.".$class->_mobius_a_column => "self.".$class->_mobius_b_column, |
|
45
|
|
|
|
|
|
|
"foreign.".$class->_mobius_c_column => "self.".$class->_mobius_d_column, |
|
46
|
|
|
|
|
|
|
}); |
|
47
|
|
|
|
|
|
|
|
|
48
|
0
|
|
|
|
|
|
$class->has_many( 'children' => $1 => { |
|
49
|
|
|
|
|
|
|
"foreign.".$class->_mobius_b_column => "self.".$class->_mobius_a_column, |
|
50
|
|
|
|
|
|
|
"foreign.".$class->_mobius_d_column => "self.".$class->_mobius_c_column, |
|
51
|
|
|
|
|
|
|
}, { cascade_delete => 0 }); |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
} |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
sub root_cond { |
|
58
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
59
|
0
|
|
|
|
|
|
return ( $self->_mobius_b_column => undef, $self->_mobius_d_column => undef ); |
|
60
|
|
|
|
|
|
|
} |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub inner_cond { |
|
63
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
64
|
0
|
|
|
|
|
|
return $self->_is_inner_column => 1 ; |
|
65
|
|
|
|
|
|
|
} |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
sub leaf_cond { |
|
68
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
69
|
0
|
|
|
|
|
|
return $self->_is_inner_column => 0 ; |
|
70
|
|
|
|
|
|
|
} |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
sub _rational { |
|
73
|
0
|
|
|
0
|
|
|
my $i = shift; |
|
74
|
|
|
|
|
|
|
|
|
75
|
0
|
0
|
|
|
|
|
return unless ($i); |
|
76
|
0
|
0
|
|
|
|
|
return ($i, 1) unless (scalar @_ > 0); |
|
77
|
|
|
|
|
|
|
|
|
78
|
0
|
|
|
|
|
|
my ($num, $den) = _rational(@_); |
|
79
|
0
|
|
|
|
|
|
return ($num * $i + $den, $num); |
|
80
|
|
|
|
|
|
|
} |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
sub _euclidean { |
|
83
|
0
|
|
|
0
|
|
|
my ($a, $c) = @_; |
|
84
|
|
|
|
|
|
|
|
|
85
|
0
|
0
|
|
|
|
|
return unless ($c); |
|
86
|
0
|
|
|
|
|
|
my $res = $a % $c; |
|
87
|
0
|
0
|
|
|
|
|
return $res == 0 ? int($a / $c) : (int($a / $c), _euclidean($c, $res)); |
|
88
|
|
|
|
|
|
|
} |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub _mobius { |
|
91
|
0
|
|
|
0
|
|
|
my $i = shift; |
|
92
|
|
|
|
|
|
|
|
|
93
|
0
|
0
|
|
|
|
|
return (1, 0, 0, 1) unless ($i); |
|
94
|
0
|
|
|
|
|
|
my ($a, $b, $c, $d) = _mobius(@_); |
|
95
|
0
|
|
|
|
|
|
return ($i * $a + $c, $i * $b + $d, $a, $b); |
|
96
|
|
|
|
|
|
|
} |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
sub _mobius_encoding { |
|
99
|
0
|
|
|
0
|
|
|
my ($a, $b, $c, $d) = _mobius(@_); |
|
100
|
0
|
0
|
|
|
|
|
return wantarray ? ($a, $b, $c, $d) : sprintf("(${a}x + $b) / (${c}x + $d)"); |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub _mobius_path { |
|
104
|
0
|
|
|
0
|
|
|
my ($a, $b, $c, $d) = @_; |
|
105
|
0
|
|
|
|
|
|
my @path = _euclidean($a, $c); |
|
106
|
0
|
0
|
|
|
|
|
return wantarray ? @path : join('.', @path); |
|
107
|
|
|
|
|
|
|
} |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub _left_right { |
|
110
|
0
|
|
|
0
|
|
|
my ($a, $b, $c, $d) = @_; |
|
111
|
0
|
|
|
|
|
|
my ($x, $y) = (($a+$b)/($c+$d), $a / $c); |
|
112
|
0
|
0
|
|
|
|
|
my ($left, $right) = $x > $y ? ($y, $x) : ($x, $y); |
|
113
|
0
|
0
|
|
|
|
|
warn("DBIx::Class::Tree::Mobius max depth has been reached.") if ($left == $right); |
|
114
|
0
|
0
|
|
|
|
|
return wantarray ? ($left, $right) : sprintf("l=%.3f, r=%.3f", $left, $right); |
|
115
|
|
|
|
|
|
|
} |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub new { |
|
118
|
0
|
|
|
0
|
|
|
my ($class, $attrs) = @_; |
|
119
|
0
|
0
|
|
|
|
|
$class = ref $class if ref $class; |
|
120
|
|
|
|
|
|
|
|
|
121
|
0
|
0
|
|
|
|
|
if (my $parent = delete($attrs->{$class->parent_virtual_column})) { |
|
122
|
|
|
|
|
|
|
# store aside explicitly parent |
|
123
|
0
|
|
|
|
|
|
my $new = $class->next::method($attrs); |
|
124
|
0
|
|
|
|
|
|
$new->{_explicit_parent} = $parent; |
|
125
|
0
|
|
|
|
|
|
return $new; |
|
126
|
|
|
|
|
|
|
} else { |
|
127
|
0
|
|
|
|
|
|
return $class->next::method($attrs); |
|
128
|
|
|
|
|
|
|
} |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
# always use the leftmost index available |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
sub _available_mobius_index { |
|
134
|
0
|
|
|
0
|
|
|
my @children = @_; |
|
135
|
|
|
|
|
|
|
|
|
136
|
0
|
|
|
|
|
|
my $count = scalar @children + 2; |
|
137
|
0
|
|
|
|
|
|
foreach my $child (@children) { |
|
138
|
0
|
|
|
|
|
|
my @mpath = $child->mobius_path(); |
|
139
|
0
|
|
|
|
|
|
my $index = pop @mpath; |
|
140
|
0
|
0
|
|
|
|
|
last if ($count > $index); |
|
141
|
0
|
|
|
|
|
|
$count--; |
|
142
|
|
|
|
|
|
|
} |
|
143
|
0
|
|
|
|
|
|
return $count; |
|
144
|
|
|
|
|
|
|
} |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
sub available_mobius_index { |
|
147
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
148
|
0
|
|
|
|
|
|
return _available_mobius_index( $self->children()->search({}, { order_by => $self->_mobius_a_column. ' DESC' } ) ); |
|
149
|
|
|
|
|
|
|
} |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
sub insert { |
|
152
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
153
|
|
|
|
|
|
|
|
|
154
|
0
|
0
|
0
|
|
|
|
if (exists $self->{_explicit_parent} |
|
155
|
|
|
|
|
|
|
and my $parent = $self->result_source->resultset->find($self->{_explicit_parent}) ) { |
|
156
|
|
|
|
|
|
|
|
|
157
|
0
|
|
|
|
|
|
my ($a, $b, $c, $d, $left, $right) = $parent->child_encoding( $parent->available_mobius_index ); |
|
158
|
|
|
|
|
|
|
|
|
159
|
0
|
|
|
|
|
|
$self->store_column( $self->_mobius_a_column => $a ); |
|
160
|
0
|
|
|
|
|
|
$self->store_column( $self->_mobius_b_column => $b ); |
|
161
|
0
|
|
|
|
|
|
$self->store_column( $self->_mobius_c_column => $c ); |
|
162
|
0
|
|
|
|
|
|
$self->store_column( $self->_mobius_d_column => $d ); |
|
163
|
0
|
|
|
|
|
|
$self->store_column( $self->_lft_column => $left ); |
|
164
|
0
|
|
|
|
|
|
$self->store_column( $self->_rgt_column => $right ); |
|
165
|
|
|
|
|
|
|
|
|
166
|
0
|
|
|
|
|
|
my $r = $self->next::method(@_); |
|
167
|
0
|
|
|
|
|
|
$parent->update({ $self->_is_inner_column => 1 } ); |
|
168
|
0
|
|
|
|
|
|
return $r; |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
} else { # attaching to root |
|
171
|
|
|
|
|
|
|
|
|
172
|
0
|
|
|
|
|
|
my $x = _available_mobius_index( $self->result_source->resultset->search( { $self->root_cond } )->search({}, { order_by => $self->_mobius_a_column. ' DESC' } ) ); |
|
173
|
|
|
|
|
|
|
|
|
174
|
0
|
|
|
|
|
|
$self->store_column( $self->_mobius_a_column => $x ); |
|
175
|
0
|
|
|
|
|
|
$self->store_column( $self->_mobius_c_column => 1 ); |
|
176
|
|
|
|
|
|
|
# normal value are b => 1 and c => 0 but it cannot work for SQL join |
|
177
|
0
|
|
|
|
|
|
$self->store_column( $self->_mobius_b_column => undef ); |
|
178
|
0
|
|
|
|
|
|
$self->store_column( $self->_mobius_d_column => undef ); |
|
179
|
0
|
|
|
|
|
|
$self->store_column( $self->_lft_column => $x ); |
|
180
|
0
|
|
|
|
|
|
$self->store_column( $self->_rgt_column => $x + 1 ); |
|
181
|
0
|
|
|
|
|
|
return $self->next::method(@_); |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
} |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
sub mobius_path { |
|
188
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
189
|
0
|
|
|
|
|
|
my ($b, $d) = ($self->get_column($self->_mobius_b_column), $self->get_column($self->_mobius_d_column)); |
|
190
|
0
|
0
|
|
|
|
|
my @path = _mobius_path( |
|
|
|
0
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
$self->get_column($self->_mobius_a_column), defined $b ? $b : 1, |
|
192
|
|
|
|
|
|
|
$self->get_column($self->_mobius_c_column), defined $d ? $d : 0, |
|
193
|
|
|
|
|
|
|
); |
|
194
|
0
|
0
|
|
|
|
|
return wantarray ? @path : join('.', @path); |
|
195
|
|
|
|
|
|
|
} |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
sub depth { |
|
198
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
199
|
0
|
|
|
|
|
|
my @path = $self->mobius_path(); |
|
200
|
0
|
|
|
|
|
|
return scalar @path; |
|
201
|
|
|
|
|
|
|
} |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
sub child_encoding { |
|
204
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
205
|
0
|
|
|
|
|
|
my $x = shift; |
|
206
|
0
|
|
|
|
|
|
my ($pb, $pd) = ($self->get_column($self->_mobius_b_column), $self->get_column($self->_mobius_d_column)); |
|
207
|
0
|
0
|
|
|
|
|
my ($a, $b, $c, $d) = ( |
|
|
|
0
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
$self->get_column($self->_mobius_a_column) * $x + ( defined $pb ? $pb : 1), |
|
209
|
|
|
|
|
|
|
$self->get_column($self->_mobius_a_column), |
|
210
|
|
|
|
|
|
|
$self->get_column($self->_mobius_c_column) * $x + ( defined $pd ? $pd : 0), |
|
211
|
|
|
|
|
|
|
$self->get_column($self->_mobius_c_column) |
|
212
|
|
|
|
|
|
|
); |
|
213
|
0
|
0
|
|
|
|
|
return wantarray ? ($a, $b, $c, $d, _left_right($a, $b, $c, $d)) : sprintf("(${a}x + $b) / (${c}x + $d)"); |
|
214
|
|
|
|
|
|
|
} |
|
215
|
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
sub root { |
|
217
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
218
|
0
|
|
|
|
|
|
return $self->result_source->resultset->search( { $self->root_cond } )->search({ |
|
219
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_lft_column => { '<' => $self->get_column($self->_rgt_column) }, |
|
220
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_rgt_column => { '>' => $self->get_column($self->_lft_column) }, |
|
221
|
|
|
|
|
|
|
}); |
|
222
|
|
|
|
|
|
|
} |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub is_root { |
|
225
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
226
|
0
|
0
|
|
|
|
|
return $self->parent ? 0 : 1; |
|
227
|
|
|
|
|
|
|
} |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
sub is_inner { |
|
230
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
231
|
0
|
0
|
|
|
|
|
return $self->get_column($self->_is_inner_column) ? 1 : 0; |
|
232
|
|
|
|
|
|
|
} |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
sub is_branch { |
|
235
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
236
|
0
|
0
|
0
|
|
|
|
return ($self->parent && $self->get_column($self->_is_inner_column)) ? 1 : 0; |
|
237
|
|
|
|
|
|
|
} |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
sub is_leaf { |
|
240
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
241
|
0
|
0
|
|
|
|
|
return $self->get_column($self->_is_inner_column) ? 0 : 1; |
|
242
|
|
|
|
|
|
|
} |
|
243
|
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
sub siblings { |
|
245
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
246
|
0
|
0
|
|
|
|
|
if (my $parent = $self->parent) { |
|
247
|
0
|
|
|
|
|
|
return $parent->children->search({ |
|
248
|
|
|
|
|
|
|
-or => { |
|
249
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_mobius_a_column => { '!=' => $self->get_column($self->_mobius_a_column) }, |
|
250
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_mobius_c_column => { '!=' => $self->get_column($self->_mobius_c_column) }, |
|
251
|
|
|
|
|
|
|
}, |
|
252
|
|
|
|
|
|
|
}); |
|
253
|
|
|
|
|
|
|
} else { |
|
254
|
0
|
|
|
|
|
|
return $self->result_source->resultset->search({ |
|
255
|
|
|
|
|
|
|
-or => { |
|
256
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_mobius_a_column => { '!=' => $self->get_column($self->_mobius_a_column) }, |
|
257
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_mobius_c_column => { '!=' => $self->get_column($self->_mobius_c_column) }, |
|
258
|
|
|
|
|
|
|
}, |
|
259
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_mobius_b_column => undef, |
|
260
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_mobius_d_column => undef |
|
261
|
|
|
|
|
|
|
}); |
|
262
|
|
|
|
|
|
|
} |
|
263
|
|
|
|
|
|
|
} |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub leaf_children { |
|
266
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
267
|
0
|
|
|
|
|
|
return $self->children->search({ $self->result_source->resultset->current_source_alias.'.'.$self->_is_inner_column => 0 }); |
|
268
|
|
|
|
|
|
|
} |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
sub inner_children { |
|
271
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
272
|
0
|
|
|
|
|
|
return $self->children->search({ $self->result_source->resultset->current_source_alias.'.'.$self->_is_inner_column => 1 }); |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
sub descendants { |
|
276
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
277
|
|
|
|
|
|
|
|
|
278
|
0
|
|
|
|
|
|
return $self->result_source->resultset->search({ |
|
279
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_lft_column => { '>' => $self->get_column($self->_lft_column) }, |
|
280
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_rgt_column => { '<' => $self->get_column($self->_rgt_column) }, |
|
281
|
|
|
|
|
|
|
}); |
|
282
|
|
|
|
|
|
|
} |
|
283
|
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
sub leaves { |
|
285
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
286
|
0
|
|
|
|
|
|
return $self->descendants->search({ $self->result_source->resultset->current_source_alias.'.'.$self->_is_inner_column => 0 }); |
|
287
|
|
|
|
|
|
|
} |
|
288
|
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
sub inner_descendants { |
|
290
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
291
|
|
|
|
|
|
|
|
|
292
|
0
|
|
|
|
|
|
return $self->descendants->search({ $self->result_source->resultset->current_source_alias.'.'.$self->_is_inner_column => 1 }); |
|
293
|
|
|
|
|
|
|
} |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
sub ancestors { |
|
296
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
297
|
|
|
|
|
|
|
|
|
298
|
0
|
|
|
|
|
|
return $self->result_source->resultset->search({ |
|
299
|
|
|
|
|
|
|
-and => { |
|
300
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_lft_column => { '<' => $self->get_column($self->_lft_column) }, |
|
301
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_rgt_column => { '>' => $self->get_column($self->_rgt_column) }, |
|
302
|
|
|
|
|
|
|
}, |
|
303
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_lft_column => { '<' => $self->get_column($self->_rgt_column) }, |
|
304
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_rgt_column => { '>' => $self->get_column($self->_lft_column) }, |
|
305
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_mobius_a_column => { '!=' => $self->get_column($self->_mobius_a_column) }, |
|
306
|
|
|
|
|
|
|
$self->result_source->resultset->current_source_alias.'.'.$self->_mobius_c_column => { '!=' => $self->get_column($self->_mobius_c_column) }, |
|
307
|
|
|
|
|
|
|
},{ order_by => $self->_lft_column.' DESC' }); |
|
308
|
|
|
|
|
|
|
} |
|
309
|
|
|
|
|
|
|
|
|
310
|
0
|
|
|
0
|
|
|
sub ascendants { return shift(@_)->ancestors(@_) } |
|
311
|
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
sub attach_child { |
|
313
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
314
|
0
|
|
|
|
|
|
my $child = shift; |
|
315
|
|
|
|
|
|
|
|
|
316
|
0
|
|
|
|
|
|
my ($a, $b, $c, $d, $left, $right) = $self->child_encoding( $self->available_mobius_index ); |
|
317
|
|
|
|
|
|
|
|
|
318
|
0
|
|
|
|
|
|
my @grandchildren = $child->children()->all(); |
|
319
|
0
|
|
|
|
|
|
foreach my $grandchild (@grandchildren) { |
|
320
|
0
|
|
|
|
|
|
$grandchild->update( { $self->_mobius_b_column => undef, $self->_mobius_d_column => undef }); |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
$child->update({ |
|
324
|
0
|
|
|
|
|
|
$self->_mobius_a_column => $a, |
|
325
|
|
|
|
|
|
|
$self->_mobius_b_column => $b, |
|
326
|
|
|
|
|
|
|
$self->_mobius_c_column => $c, |
|
327
|
|
|
|
|
|
|
$self->_mobius_d_column => $d, |
|
328
|
|
|
|
|
|
|
$self->_lft_column => $left, |
|
329
|
|
|
|
|
|
|
$self->_rgt_column => $right, |
|
330
|
|
|
|
|
|
|
}); |
|
331
|
|
|
|
|
|
|
|
|
332
|
0
|
|
|
|
|
|
foreach my $grandchild (@grandchildren) { |
|
333
|
0
|
|
|
|
|
|
$child->attach_child( $grandchild ); |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
} |
|
337
|
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
1; |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
Create a table for your tree data with the 7 special columns used by Tree::Mobius. |
|
344
|
|
|
|
|
|
|
By default, these columns are mobius_a mobius_b mobius_b and mobius_d (integer), |
|
345
|
|
|
|
|
|
|
lft and rgt (float) and inner (boolean). See the add_mobius_tree_columns method |
|
346
|
|
|
|
|
|
|
to change the default names. |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
CREATE TABLE employees ( |
|
349
|
|
|
|
|
|
|
name TEXT NOT NULL |
|
350
|
|
|
|
|
|
|
mobius_a integer(11) unsigned, |
|
351
|
|
|
|
|
|
|
mobius_b integer(11) unsigned, |
|
352
|
|
|
|
|
|
|
mobius_c integer(11) unsigned, |
|
353
|
|
|
|
|
|
|
mobius_d integer(11) unsigned, |
|
354
|
|
|
|
|
|
|
lft FLOAT unsigned NOT NULL DEFAULT '1', |
|
355
|
|
|
|
|
|
|
rgt FLOAT unsigned, |
|
356
|
|
|
|
|
|
|
inner boolean NOT NULL DEFAULT '0', |
|
357
|
|
|
|
|
|
|
); |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
In your Schema or DB class add Tree::Mobius in the component list. |
|
360
|
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
__PACKAGE__->load_components(qw( Tree::Mobius ... )); |
|
362
|
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
Call add_mobius_tree_columns. |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
package My::Employee; |
|
366
|
|
|
|
|
|
|
__PACKAGE__->add_mobius_tree_columns(); |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
That's it, now you can create and manipulate trees for your table. |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
#!/usr/bin/perl |
|
371
|
|
|
|
|
|
|
use My::Employee; |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
my $big_boss = My::Employee->create({ name => 'Larry W.' }); |
|
374
|
|
|
|
|
|
|
my $boss = My::Employee->create({ name => 'John Doe' }); |
|
375
|
|
|
|
|
|
|
my $employee = My::Employee->create({ name => 'No One' }); |
|
376
|
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
$big_boss->attach_child( $boss ); |
|
378
|
|
|
|
|
|
|
$boss->attach_child( $employee ); |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
This module provides methods for working with trees of data using a |
|
383
|
|
|
|
|
|
|
Möbius encoding, a variant of 'Nested Intervals' tree encoding using |
|
384
|
|
|
|
|
|
|
continued fraction. This a model to represent hierarchical information |
|
385
|
|
|
|
|
|
|
in a SQL database. This model takes a complementary approach of both |
|
386
|
|
|
|
|
|
|
the 'Nested Sets' model and the 'Materialized Path' model. |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
The implementation has been heavily inspired by a Vadim Tropashko's |
|
389
|
|
|
|
|
|
|
paper available online at http://arxiv.org/pdf/cs.DB/0402051 about |
|
390
|
|
|
|
|
|
|
the Möbius encoding. |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
A 'Nested Intervals' model has the same advantages that 'Nested Sets' |
|
393
|
|
|
|
|
|
|
over the 'Adjacency List', that is to say that obtaining all |
|
394
|
|
|
|
|
|
|
descendants requires only one query rather than recursive queries. |
|
395
|
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
Additionally, a 'Nested Intervals' model has two advantages over 'Nested Sets' : |
|
397
|
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
- Encoding is not volatile (no other node should be relabeled whenever |
|
399
|
|
|
|
|
|
|
a new node were inserted). |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
- There are no difficulties associated with querying ancestors. |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
The Möbius encoding is a particular encoding schema of the 'Nested |
|
404
|
|
|
|
|
|
|
Intervals' model that uses integer numbers economically to allow |
|
405
|
|
|
|
|
|
|
better tree scaling and directly encode the material path of a node |
|
406
|
|
|
|
|
|
|
using continued fraction (thus this model also relates somewhat with |
|
407
|
|
|
|
|
|
|
the 'Materialized Path' model). |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
The tradeoffs over other models is in this implementation the use of 7 |
|
410
|
|
|
|
|
|
|
SQL columns to encode each node. |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
Since the encoding is not volatile, the depth is constraint by the |
|
413
|
|
|
|
|
|
|
precision of FLOAT in the right and left column. The maximum depth |
|
414
|
|
|
|
|
|
|
reachable is 8 levels with a simple SQL FLOAT, and 21 with a SQL DOUBLE. |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
This implementation allows you to have several trees in your database. |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
=head1 METHODS |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=head2 add_mobius_tree_columns |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
Declare the name of the columns for tree encoding and add them to the schema. |
|
423
|
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
None of these columns should be modified outside if this module. |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
Multiple trees are allowed in the same table, each tree will have a unique value in the mobius_a_column. |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
=head2 attach_child |
|
429
|
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
Attach a new child to a node. |
|
431
|
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
If the child has descendants, the entire sub-tree is moved recursively. |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
=head2 insert |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
This method is an override of the DBIx::Class' method. |
|
437
|
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
The method is not meant to not be used directly but it allows one to |
|
439
|
|
|
|
|
|
|
add a parent virtual column when calling the DBIx::Class method create. |
|
440
|
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
This virtual column should be set with the primary key value of the parent. |
|
442
|
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
My::Employee->create({ name => 'Another Intern', parent => $boss->id }); |
|
444
|
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=head2 parent |
|
446
|
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
Returns a DBIx::Class Row of the parent of a node. |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=head2 children |
|
450
|
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
Returns a DBIx::Class resultset of all children (direct descendants) of a node. |
|
452
|
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
=head2 leaf_children |
|
454
|
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
Returns a DBIx::Class resultset of all children (direct descendants) of a node that do not possess any child themselves. |
|
456
|
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
=head2 inner_children |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
Returns a DBIx::Class resultset of all children (direct descendants) of a node that possess one or more child. |
|
460
|
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=head2 descendants |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
Returns a DBIx::Class resultset of all descendants of a node (direct or not). |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
=head2 leaves |
|
466
|
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
Returns a DBIx::Class resultset of all descendants of a node that do not possess any child themselves. |
|
468
|
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
=head2 inner_descendants |
|
470
|
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
Returns a DBIx::Class resultset of all descendants of a node that possess one or more child. |
|
472
|
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
=head2 ancestors |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
Returns a DBIx::Class resultset of all ancestors of a node. |
|
476
|
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
=head2 ascendants |
|
478
|
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
An alias method for ancestors. |
|
480
|
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
=head2 root |
|
482
|
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
Returns a DBIx::Class resultset containing the root ancestor of a given node. |
|
484
|
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=head2 siblings |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
Returns a DBIx::Class resultset containing all the nodes with the same parent of a given node. |
|
488
|
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
=head2 is_root |
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
Returns 1 if the node has no parent, and 0 otherwise. |
|
492
|
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=head2 is_inner |
|
494
|
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
Returns 1 if the node has at least one child, and 0 otherwise. |
|
496
|
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
=head2 is_branch |
|
498
|
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
Returns 1 if the node has at least one child and is not a root node, 0 otherwise. |
|
500
|
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
=head2 is_leaf |
|
502
|
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
Returns 1 if the node has no child, and 0 otherwise. |
|
504
|
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=head2 available_mobius_index |
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
Returns the smallest mobius index available in the subtree of a given node. |
|
508
|
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
=head2 child_encoding |
|
510
|
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
Given a mobius index, return the mobius a,b,c,d column values. |
|
512
|
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
=head2 depth |
|
514
|
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
Return the depth of a node in a tree (depth of a root node is 1). |
|
516
|
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
=for Pod::Coverage new mobius_path root_cond inner_cond leaf_cond |
|
518
|
|
|
|
|
|
|
|