4. Перемещение узла
Любое перемещение узла производится с помощью одних и тех же запросов, и требуют одних и тех же данных, поэтому правильней выделить данное действие в отдельный блок (процедуру). Для перемещения нам нужно взять следующие данные:
- данные перемещаемого узла;
- точка перемещения, куда перемещается узел
И произвести следующие действия:
- определить диапазоны смещения для ключей дерева и перемещаемого узла;
- определить вид перемещения: "вверх " - "вниз" по дереву;
- собственно, переместить узел и обновить ключи дерева
sub _move_unit {
# Получаем данные: объект и данные для перемещения
my ($self, $data) = @_;
# Проверяем возможность перемещения*
if ($data->{'near'} >= $data->{'left'} && $data->{'near'} <= $data->{'right'}) {return 0}
# Определяем смещение ключей перемещаемого узла и смещение уровня
my $skew_tree = $data->{'right'} - $data->{'left'} + 1;
my $skew_level = $data->{'level_new'} - $data->{'level'};
# Если перемещаем вверх по дереву
if ($data->{'right'} < $data->{'near'}) {
# Определяем смещение ключей для дерева
my $skew_edit = $data->{'near'} - $data->{'left'} + 1 - $skew_tree;
# Переносим узел и одновременно обновляем дерево
$self->{'DBI'}->do('UPDATE '.$self->{'table'}.
' SET '.
$self->{'left'}.' = CASE WHEN '.$self->{'right'}.' <= '.$data->{'right'}.' THEN '.
$self->{'left'}.' + '.$skew_edit.' ELSE CASE WHEN '.$self->{'left'}.' > '.$data->{'right'}.' THEN '.
$self->{'left'}.' - '.$skew_tree.' ELSE '.$self->{'left'}.' END END, '.
$self->{'level'}.' = CASE WHEN '.$self->{'right'}.' <= '.$data->{'right'}.' THEN '.
$self->{'level'}.' + '.$skew_level.' ELSE '.$self->{'level'}.' END, '.
$self->{'right'}.' = CASE WHEN '.$self->{'right'}.' <= '.$data->{'right'}.' THEN '.
$self->{'right'}.' + '.$skew_edit.' ELSE CASE WHEN '.$self->{'right'}.' <= '.$data->{'near'}.' THEN '.
$self->{'right'}.' - '.$skew_tree.' ELSE '.$self->{'right'}.' END END WHERE '.
$self->{'right'}.' > '.$data->{'left'}.' AND '.
$self->{'left'}.' <= '.$data->{'near'}.
($self->{'type'} eq 'M' ? ' AND '.$self->{'multi'}.'= \''.$data->{'multi'}.'\'' : '')
);
# Если перемещаем вниз по дереву
} else {
# Определяем смещение ключей для дерева
my $skew_edit = $data->{'near'} - $data->{'left'} + 1;
# Переносим узел и одновременно обновляем дерево
$self->{'DBI'}->do('UPDATE '.$self->{'table'}.
' SET '.
$self->{'right'}.' = CASE WHEN '.$self->{'left'}.' >= '.$data->{'left'}.' THEN '.
$self->{'right'}.' + '.$skew_edit.' ELSE CASE WHEN '.$self->{'right'}.' < '.$data->{'left'}.' THEN '.
$self->{'right'}.' + '.$skew_tree.' ELSE '.$self->{'right'}.' END END, '.
$self->{'level'}.' = CASE WHEN '.$self->{'left'}.' >= '.$data->{'left'}.' THEN '.
$self->{'level'}.' + '.$skew_level.' ELSE '.$self->{'level'}.' END, '.
$self->{'left'}.' = CASE WHEN '.$self->{'left'}.' >= '.$data->{'left'}.' THEN '.
$self->{'left'}.' + '.$skew_edit.' ELSE CASE WHEN '.$self->{'left'}.' > '.$data->{'near'}.' THEN '.
$self->{'left'}.' + '.$skew_tree.' ELSE '.$self->{'left'}.' END END WHERE '.
$self->{'right'}.' > '.$data->{'near'}.' AND '.
$self->{'left'}.' < '.$data->{'right'}.
($self->{'type'} eq 'M' ? ' AND '.$self->{'multi'}.'= \''.$data->{'multi'}.'\'' : '')
);
}
return 1
}
* Примечание: Мы не можем переместить узел "в себя" поэтому сделали соответсвующую проверку
Где:
- $data->{'left'} - левый ключ перемещаемого узла;
- $data->{'right'} - правый ключ перемещаемого узла;
- $data->{'level'} - уровень перемещаемого узла;
- $data->{'multi'} - идентификатор дерева перемещаемого узла (если мультидеревья);
- $data->{'level_new'} - уровень - куда перемещается узел;
- $data->{'near'} - "точка перемещения" будущий левый ключ перемещаемого узла, уменьшенный на единицу;
Имя процедуры начинается, с "_" и это не случайно, так как этот метод не будет вызываться из скрипта, а использоваться как внутренняя процедура модуля.
В итоге, чтобы переместить узел, нам требуется сформировать переменную $data (ссылка на хеш) и передать её в процедуру _move_unit.
4.1. Перемещение узла в подчинение другому
Для того, что бы переместить узел в подчинение другому, нам нужно взять следующие данные:
- параметры перемещаемого узла;
- параметры родительского узла (куда перемещаем) ;
- параметр перемещения узла - в начало или конец подчиненного списка перемещать.
И произвести следующие действия:
- определить параметры перемещаемого узла (если они еще не определены);
- определить параметры родительского узла;
- передать полученные данные процедуре _move_unit
sub set_unit_under {
# Получаем данные: объект, перемещаемый узел, место перемещения, порядок перемещения
my ($self, %common)= @_;
# перемещаемый узел
my $unit = $common{'unit'} || undef;
# место перемещения
my $under = $common{'under'} || undef;
# порядок перемещения (top - в начало, иначе - в конец списка)
my $order = $common{'order'} || undef;
# объявляем переменную, которую будем передавать процедуре перемещения
my $data;
# определяем данные перемещаемого узла
if ($unit) {$self = &select_unit($self, $unit)}
elsif (!$self->{'unit'}->{'id'}) {croak("NestedSets failed: Your must first select unit, for moving it!!!")}
# если место перемещения - корень дерева
if (!$under || $under eq 'none' || $under eq 'root') {
# если порядок перемещения - начало списка
if (($order && $order eq 'top') || $self->{'order'} eq 'T') {
$data->{'near'} = 0;
$data->{'level_new'} = 1
} else {
# иначе выбираем максимальное значение ключа дерева
my $sql = 'SELECT MAX('.$self->{'right'}.') AS num FROM '.$self->{'table'}.
($self->{'type'} eq 'M' ?
' WHERE '.$self->{'multi'}.'='.$self->{'unit'}->{'multi'} : '');
my $sth = $self->{'DBI'}->prepare($sql); $sth->execute();
my $row = $sth->fetchrow_hashref();
$sth->finish();
if ($row) {$data->{'near'} = $$row{'num'}; $data->{'level_new'} = 1}
else {croak("NestedSets failed: The place of moving is not determined, check up his!!!")}
}
# иначе получаем данные места перемещения
} else {
my $sql = 'SELECT '.
$self->{'left'}.' AS lk, '.
$self->{'right'}.' AS rk, '.
$self->{'level'}.' AS lv FROM '.$self->{'table'}.
' WHERE '.$self->{'id'}.' = \''.$under.'\''.
($self->{'type'} eq 'M' ?
' AND '.$self->{'multi'}.'= \''.$self->{'unit'}->{'multi'}.'\'' : '');
my $sth = $self->{'DBI'}->prepare($sql); $sth->execute();
my $row = $sth->fetchrow_hashref(); $sth->finish();
# в зависимости от порядка перемещения берем либо правый, либо левый ключ
if ($row && (($order && $order eq 'top') || $self->{'order'} eq 'T')) {
$data->{'near'} = $$row{'lk'};
$data->{'level_new'} = $$row{'lv'} + 1
} elsif ($row) {
$data->{'near'} = $$row{'rk'} - 1;
$data->{'level_new'} = $$row{'lv'} + 1
} else {croak("NestedSets failed: The place of moving is not determined, check up his!!!")}
}
# перебрасываем из объекта данные о перемещаемом узле
$data->{'left'} = $self->{'unit'}->{'left'};
$data->{'right'} = $self->{'unit'}->{'right'};
$data->{'level'} = $self->{'unit'}->{'level'};
$data->{'multi'} = $self->{'unit'}->{'multi'} || undef;
$self->{'unit'} = undef;
# перемещаем узел
&_move_unit($self, $data);
return 1
}
Вызов данного метода производится так:
...
my $unit = ... # Определяем идентификатор узла
my $under = ... # Определяем нового родителя
...
use MyModule::NestedSets;
my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
$nested->set_unit_under(unit=>$unit, under=>$under, order=>'top');
...
или так:
...
my $unit = ... # Определяем идентификатор узла
my $under = ... # Определяем нового родителя
...
use MyModule::NestedSets;
my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
$nested->select_unit($unit);
$nested->set_unit_under(under=>$under, order=>'top');
...
4.2. Перемещение узла - рядом с другим
Для того, что бы переместить узел в подчинение другому, нам нужно взять следующие данные:
- параметры перемещаемого узла;
- параметры узла рядом с каким собираемся размещать;
- параметр перемещения узла - до или после узла переместить.
И произвести следующие действия:
- определить параметры перемещаемого узла (если они еще не определены);
- определить рядом с каким собираемся размещать;
- передать полученные данные процедуре _move_unit
sub set_unit_near {
# Получаем данные: объект, перемещаемый узел, место перемещения, порядок перемещения
my ($self, %common)= @_;
# перемещаемый узел
my $unit = $common{'unit'} || undef;
# место перемещения
my $near = $common{'near'} || undef;
# порядок перемещения (top - в начало, иначе - в конец списка)
my $order = $common{'order'} || undef;
# объявляем переменную, которую будем передавать процедуре перемещения
my $data;
# определяем данные перемещаемого узла
if ($unit) {$self = &select_unit($self, $unit)}
elsif (!$self->{'unit'}->{'id'}) {croak("NestedSets failed: Your must first select unit, for moving it!!!")}
# определяем данные места перемещения - узла, рядом с которым
# будет располагаться перемещаемый узел
my $sql = 'SELECT '.
$self->{'left'}.' AS lk, '.
$self->{'right'}.' AS rk, '.
$self->{'level'}.' AS lv FROM '.$self->{'table'}.
' WHERE '.$self->{'id'}.' = \''.$near.'\''.
($self->{'type'} eq 'M' ?
' AND '.$self->{'multi'}.'= \''.$self->{'unit'}->{'multi'}.'\'' : '');
my $sth = $self->{'DBI'}->prepare($sql); $sth->execute();
my $row = $sth->fetchrow_hashref();
$sth->finish();
# в зависимости от порядка перемещения берем либо правый, либо левый ключ
if ($row && $order && $order eq 'before') {
$data->{'near'} = $$row{'lk'} - 1;
$data->{'level_new'} = $$row{'lv'}
} elsif ($row) {
$data->{'near'} = $$row{'rk'};
$data->{'level_new'} = $$row{'lv'}
} else {croak("NestedSets failed: The place of moving is not determined, check up his!!!")}
# перебрасываем из объекта данные о перемещаемом узле
$data->{'left'} = $self->{'unit'}->{'left'};
$data->{'right'} = $self->{'unit'}->{'right'};
$data->{'level'} = $self->{'unit'}->{'level'};
$data->{'multi'} = $self->{'unit'}->{'multi'} || undef;
$self->{'unit'} = undef;
# перемещаем узел
&_move_unit($self, $data);
return 1
}
Вызов данного метода производится так:
...
my $unit = ... # Определяем идентификатор узла
my $near = ... # Определяем место (узел) перемещения
...
use MyModule::NestedSets;
my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
$nested->set_unit_near(unit=>$unit, near=>$near, order=>'before');
...
или так:
...
my $unit = ... # Определяем идентификатор узла
my $near = ... # Определяем место (узел) перемещения
...
use MyModule::NestedSets;
my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
$nested->select_unit($unit);
$nested->set_unit_near(near=>$near, order=>'before');
...
|