package MY :: NestedSets;
# All for an adult, without compromise ;-)
use strict;
use warnings;
our $ VERSION = '0.0.1';
# Determine the variables that will be used inside the package
our $ dbh = undef;
our $ tables = {
default => {# Table name
fields => {# Table fields
id => 'id', # Actually ID, you never know who will call
left_key => 'left_key', # Left key
right_key => 'right_key', # Right key
level => 'level', # Level
parent_id => 'parent_id', # parent ID
tree => 'tree' # tree id
},
multi => 1, # Tells us that there are several trees in the table
},
};
sub dbh {
# The first value can be the name of the package or the class of the package, if we still manage to create it
# therefore we cut it off now and then, we don’t have a class.
shift if $ _ [0] && ($ _ [0] eq __PACKAGE__ || (ref $ _ [0] && ref $ _ [0] eq __PACKAGE__));
$ dbh = $ _ [0] if $ _ [0];
return $ dbh;
}
sub set_table_params {
shift if $ _ [0] && ($ _ [0] eq __PACKAGE__ || (ref $ _ [0] && ref $ _ [0] eq __PACKAGE__));
# Set our fields for a specific table
my ($ table_name, $ params) = @_;
$ tables -> {$ table_name} = $ params;
return $ tables;
}
#! / usr / bin / perl
use strict; use warnings;
use lib '../lib';
use MY :: NestedSets;
use DBI;
use Data :: Dumper;
# ------------------------------------------------- -------------------------------------------------- -----
# INIT
my $ dbh = DBI-> connect ('dbi: mysql: database = test; host = localhost; port = 3306', 'user', 'pass');
my $ table_name = 'test_nested_sets';
my% f = (
id => 'ids',
left_key => 'lk',
right_key => 'rk',
level => 'lv',
parent_id => 'pi',
tree => 'tr',
);
$ dbh-> do ("DROP TABLE` $ table_name`; ");
my $ query = "CREATE TABLE` $ table_name` (
`$ f {id}` int (11) NOT NULL auto_increment,
`$ f {left_key}` int (11) NOT NULL default '0',
`$ f {right_key}` int (11) NOT NULL default '0',
`$ f {level}` int (11) NOT NULL default '0',
`$ f {parent_id}` int (11) NOT NULL default '0',
`$ f {tree}` int (11) NOT NULL default '1',
`field1` VARCHAR (100),
PRIMARY KEY (`$ f {id}`)
) ENGINE = MyISAM; ";
$ dbh-> do ($ query);
MY :: NestedSets-> dbh ($ dbh);
MY :: NestedSets-> set_table_params ($ table_name => {fields => \% f, multi => 1});
...
sub insert {
# We distribute the incoming data in places, and, accordingly, we check if we have enough
shift if $ _ [0] && ($ _ [0] eq __PACKAGE__ || (ref $ _ [0] && ref $ _ [0] eq __PACKAGE__));
my ($ table_name, $ new) = @_;
return {success => 0, error => 'Bad income data!'} unless $ dbh && $ table_name && $ new && ref $ new && ref $ new eq 'HASH';
# We find that for the table and take its additional attributes and synonyms of the fields
my $ table = $ tables -> {$ table_name} || $ tables -> {default};
my $ f = $ table -> {fields};
my $ result_flags = {is_last_unit => undef};
# Determine the initial data of the keys of the tree
$ new -> {$ f -> {left_key}} || = 0;
$ new -> {$ f -> {right_key}} = undef;
$ new -> {$ f -> {level}} = undef;
$ new -> {$ f -> {parent_id}} || = 0;
# Determine the keys if we have set or changed the parent node
if ($ new -> {$ f -> {parent_id}}) {
my $ sql = 'SELECT'.
($ table -> {multi}? $ f -> {tree}. 'AS tree,': '').
$ f -> {right_key}. ' AS left_key, '.
$ f -> {level}. ' + 1 AS level '.
'FROM'. $ Table_name.
'WHERE'. $ F -> {id}. ' = '. $ new -> {$ f -> {parent_id}};
# That would be clear, this is a request (in square brackets is not a mandatory expression):
# SELECT [tree AS tree,] right_key AS left_key, level + 1 AS level FROM $ table_name WHERE id = $ parent_id;
my $ sth = $ dbh-> prepare ($ sql); $ sth-> execute || return {success => 0, error => $ dbh-> errstr};
my $ row = $ sth-> fetchrow_hashref ();
$ sth-> finish;
# Parent node found, means overriding key values
if ($ row) {
$ new -> {$ f -> {tree}} = $ row -> {tree} || undef;
$ new -> {$ f -> {left_key}} = $ row -> {left_key};
$ new -> {$ f -> {level}} = $ row -> {level};
} else {
# Parent node not found, then parent_id is left, reset it
$ new -> {$ f -> {parent_id}} = 0;
$ new -> {$ f -> {level}} = 0;
}
}
# Determine the keys if we have the left key, but the parent node is not specified, or not found
if (! $ new -> {$ f -> {parent_id}} && $ new -> {$ f -> {left_key}}) {
# It is important! $ tree parameter is required if multi-trees
return {success => 0, error => 'No tree value!'} unless $ new -> {$ f -> {tree}} && $ table -> {multi};
# At first I wanted to use SQL :: Abstract, but I didn’t like it, describing complex queries is more difficult and longer
# Find a node by the left or right key
my $ sql = 'SELECT'.
$ f -> {id}. ' AS id, '.
$ f -> {left_key}. ' AS left_key, '.
$ f -> {right_key}. ' AS right_key, '.
$ f -> {level}. ' AS level, '.
$ f -> {parent_id}. ' AS parent_id '.
'FROM'. $ Table_name.
'WHERE'.
($ table -> {multi}? $ f -> {tree}. '='. $ new -> {$ f -> {tree}}. 'AND': '').
'('. $ f -> {left_key}. '='. $ new -> {$ f -> {left_key}}. 'OR'.
$ f -> {right_key}. ' = '. $ new -> {$ f -> {left_key}}.') LIMIT 1 ';
# Request is readable:
# SELECT
# id AS id,
# left_key AS left_key,
# right_key AS right_key,
# level AS level,
# parent_id AS parent_id
# FROM $ table_name
# WHERE
# [tree = $ tree AND]
# (left_key = $ left_key OR right_key = $ left_key)
# LIMIT 1;
my $ sth = $ dbh-> prepare ($ sql); $ sth-> execute || return {success => 0, error => $ dbh-> errstr};
my $ row = $ sth-> fetchrow_hashref ();
$ sth-> finish;
# We found the node by the left key, therefore, we will have a new node in front of the found one
if ($ row && $ row -> {left_key} == $ new -> {$ f -> {left_key}}) {
$ new -> {$ f -> {parent_id}} = $ row -> {parent_id};
$ new -> {$ f -> {level}} = $ row -> {level};
# We found the node by the right key, therefore, we will have a new node under the found
} elsif ($ row) {
$ new -> {$ f -> {parent_id}} = $ row -> {id};
$ new -> {$ f -> {level}} = $ row -> {level} + 1;
} else {
# Again such a crap, indicated completely left data. It would be nice to swear, but for now we ignore these shoals,
# as we can handle ourselves without this data
$ new -> {$ f -> {left_key}} = undef;
}
}
# Actually, we could not get the insertion point, or simply it was not specified.
# We will insert at the end of the tree, so updating the existing nodes is not required, therefore we will make the appropriate flag:
unless ($ new -> {$ f -> {left_key}}) {
$ result_flags -> {is_last_unit} = 1;
# This is important again! $ tree parameter is required if multi-trees.
# In general, it was possible to check this at the very beginning, but this parameter is not necessary if we specified parent_id,
# then the value of the key tree is determined by it.
return {success => 0, error => 'No tree value!'} unless $ new -> {$ f -> {tree}} && $ table -> {multi};
# It's all simple, determine the maximum right key and enjoy
my $ sql = 'SELECT MAX ('. $ f -> {right_key}. ') + 1 AS left_key
FROM '. $ Table_name.
($ table -> {multi}? 'WHERE'. $ f -> {tree}. '='. $ new -> {$ f -> {tree}: '');
# Request is readable:
# SELECT MAX (right_key) + 1 AS left_key,
# FROM $ table_name
# [WHERE tree = $ tree];
my $ sth = $ dbh-> prepare ($ sql); $ sth-> execute || return {success => 0, error => $ dbh-> errstr};
my $ row = $ sth-> fetchrow_hashref ();
$ sth-> finish;
# But the joy may not be complete, since there may be no nodes at all
$ new -> {$ f -> {left_key}} = $ row -> {left_key} || one;
$ new -> {$ f -> {parent_id}} = 0;
$ new -> {$ f -> {level}} = 0;
}
# Well, we have decided on the destination, you can do the key breaking in the tree:
unless ($ result_flags -> {is_last_unit}) {
my $ query = 'UPDATE'. $ table_name.
'SET'. $ F -> {left_key}. ' = CASE
WHEN '. $ F -> {left_key}.' > = '. $ new -> {$ f -> {left_key}}.'
THEN '. $ F -> {left_key}.' + 2 ELSE '. $ F -> {left_key}.' END,
'. $ f -> {right_key}.' = '. $ f -> {right_key}.' + 2
WHERE '.
($ table -> {multi}? $ f -> {tree}. '='. $ new -> {$ f -> {tree}}. 'AND': '').
$ f -> {right_key}. ' > = '. $ new -> {$ f -> {left_key}};
# Request is readable:
# UPDATE $ table_name
# SET
# left_key = CASE WHEN left_key> = $ left_key
# THEN left_key + 2
# ELSE left_key
# END,
# right_key = right_key + 2
# WHERE [tree = $ tree AND] right_key> = $ left_key;
$ dbh-> do ($ query) || return {success => 0, error => $ dbh-> errstr};
}
# Now, actually, why did we come here:
# The right key is calculated
$ new -> {$ f -> {right_key}} = $ new -> {$ f -> {left_key}} + 1;
# Putting keys
$ new -> {$ f -> {tree}} = $ new -> {$ f -> {tree}} if $ table -> {multi};
# It is necessary to display the fields in a certain order
my @fields = keys% {$ new};
# here we quota non numeric and empty lines and push in the order of @fields
# and yes, they still need to be checked before they got here, at least for the presence of double quotes
my @values ​​= map {defined $ new -> {$ _} && $ new -> {$ _} = ~ / ^ \ d + $ /? $ new -> {$ _}: '"'. $ new -> {$ _}. '"'} @fields;
# Actually INSERT
my $ query = 'INSERT INTO'. $ table_name. ' ('. (join', ', @fields).') VALUES ('. (join', ', @values).') ';
$ dbh-> do ($ query) || return {success => 0, error => $ dbh-> errstr};
# And here's what to return - a separate question, we, alas, cannot return the inserted row without a sample,
# since the table may contain default field values, and we did not specify them in INSERT.
# Do the same SELECT
my $ sql = 'SELECT * FROM'. $ table_name. ' ORDER BY '. $ F -> {id}.' DESC LIMIT 1 ';
my $ sth = $ dbh-> prepare ($ sql); $ sth-> execute || return {success => 0, error => $ dbh-> errstr};
my $ row = $ sth-> fetchrow_hashref;
$ sth-> finish;
return {success => 1, row => $ row};
}
...
my $ tree = 1;
# ------------------------------------------------- -------------------------------------------------- --------------------
# INSERT
# Record without coordinates
my $ insert = MY :: NestedSets-> insert ($ table_name, {field1 => 'row1 -'. $ tree, tr => $ tree});
warn Dumper $ insert;
# Record with parent
$ insert = MY :: NestedSets-> insert ($ table_name, {field1 => 'row2 -'. $ tree, pi => $ insert -> {row} -> {ids}, tr => $ tree});
warn Dumper $ insert;
# Records with left_key
$ insert = MY :: NestedSets-> insert ($ table_name, {field1 => 'row3 -'. $ tree, lk => 1, tr => $ tree});
warn Dumper $ insert;
$ insert = MY :: NestedSets-> insert ($ table_name, {field1 => 'row4 -'. $ tree, lk => 4, tr => $ tree});
warn Dumper $ insert;
# Incorrect settings
$ insert = MY :: NestedSets-> insert ($ table_name, {field1 => 'row5 -'. $ tree, pi => 1000, tr => $ tree});
warn Dumper $ insert;
$ insert = MY :: NestedSets-> insert ($ table_name, {field1 => 'row6 -'. $ tree, lk => 100, tr => $ tree});
warn Dumper $ insert;
...
sub update {
# We distribute the incoming data in places, well, and, accordingly, we check if we have enough
shift if $ _ [0] && ($ _ [0] eq __PACKAGE__ || (ref $ _ [0] && ref $ _ [0] eq __PACKAGE__));
my ($ table_name, $ new) = @_;
return {success => 0, error => 'Bad income data!'} unless $ dbh && $ table_name && $ new && ref $ new && ref $ new eq 'HASH';
# We find that for the table and take its additional attributes and synonyms of the fields
my $ table = $ tables -> {$ table_name} || $ tables -> {default};
my $ f = $ table -> {fields};
return {success => 0, error => 'Bad income data!'} unless $ new -> {$ f -> {id}};
# Remove fields that can not be changed independently
delete $ new -> {$ f -> {right_key}};
delete $ new -> {$ f -> {tree}};
delete $ new -> {$ f -> {level}};
my $ tmp_left_key = $ new -> {$ f -> {left_key}};
my $ result_flags = {it_is_moving => undef};
# Further dilemma. To accept the changes, we need to have the raw data.
# In this case, we don’t know what source data we had, and what fields really changed,
# therefore we do a selection of our variable node
my $ sql = 'SELECT * FROM'. $ table_name. ' WHERE '. $ F -> {id}.' = '. $ new -> {$ f -> {id}};
my $ sth = $ dbh-> prepare ($ sql); $ sth-> execute || return {success => 0, error => $ dbh-> errstr};
my $ old = $ sth-> fetchrow_hashref;
$ sth-> finish;
return {success => 0, error => 'No old unit!'} unless $ old;
# Calculate new node coordinates
# Determine keys if we have a parent node changed
if (defined $ new -> {$ f -> {parent_id}} && $ new -> {$ f -> {parent_id}}! = $ old -> {$ f -> {parent_id}}) {
if ($ new -> {$ f -> {parent_id}}> 0) {
my $ sql = 'SELECT'.
($ table -> {multi}? $ f -> {tree}. 'AS tree,': '').
$ f -> {right_key}. ' AS left_key, '.
$ f -> {level}. ' + 1 AS level '.
'FROM'. $ Table_name.
'WHERE'. $ F -> {id}. ' = '. $ new -> {$ f -> {parent_id}};
# That would be clear, this is a request (in square brackets is not a mandatory expression):
# SELECT [tree AS tree,] right_key AS left_key, level + 1 AS level FROM $ table_name WHERE id = $ parent_id;
my $ sth = $ dbh-> prepare ($ sql); $ sth-> execute || return {success => 0, error => $ dbh-> errstr};
my $ row = $ sth-> fetchrow_hashref ();
$ sth-> finish;
# Parent node found, means override key values
if ($ row) {
$ new -> {$ f -> {tree}} = $ row -> {tree} if $ table -> {multi};
$ new -> {$ f -> {left_key}} = $ row -> {left_key};
$ new -> {$ f -> {level}} = $ row -> {level};
$ result_flags -> {it_is_moving} = 1;
} else {
# Parent node not found, then parent_id is left, reset it
$ new -> {$ f -> {parent_id}} = $ old -> {$ f -> {parent_id}};
}
} else {
# Transfer to the top level
# It's all simple, determine the maximum right key and enjoy
my $ sql = 'SELECT MAX ('. $ f -> {right_key}. ') + 1 AS left_key
FROM '. $ Table_name.
($ table -> {multi}? 'WHERE'. $ f -> {tree}. '='. $ old -> {$ f -> {tree}: '');
# Request is readable:
# SELECT MAX (right_key) + 1 AS left_key,
# FROM $ table_name
# [WHERE tree = $ tree];
my $ sth = $ dbh-> prepare ($ sql); $ sth-> execute || return {success => 0, error => $ dbh-> errstr};
my $ row = $ sth-> fetchrow_hashref ();
$ sth-> finish;
$ new -> {$ f -> {left_key}} = $ row -> {left_key};
$ new -> {$ f -> {parent_id}} = 0;
$ new -> {$ f -> {level}} = 0;
}
}
# Determine the keys if we have the left key set but the parent node is not specified, or not found
if ($ tmp_left_key && $ new -> {$ f -> {left_key}} && # left_key was specified
$ new -> {$ f -> {left_key}} == $ tmp_left_key && # parent_id has not changed
$ tmp_left_key! = $ old -> {$ f -> {left_key}}) {# left_key changed
# At first I wanted to use SQL :: Abstract, but I didn’t like it, describing complex queries is more difficult and longer
# Find a node by the left or right key
my $ sql = 'SELECT'.
$ f -> {id}. ' AS id, '.
$ f -> {left_key}. ' AS left_key, '.
$ f -> {right_key}. ' AS right_key, '.
$ f -> {level}. ' AS level, '.
$ f -> {parent_id}. ' AS parent_id '.
'FROM'. $ Table_name.
'WHERE'.
($ table -> {multi}? $ f -> {tree}. '='. $ old -> {$ f -> {tree}}. 'AND': '').
'('. $ f -> {left_key}. '='. $ new -> {$ f -> {left_key}}. 'OR'.
$ f -> {right_key}. ' = '. $ new -> {$ f -> {left_key}}.') LIMIT 1 ';
# Request is readable:
# SELECT
# id AS id,
# left_key AS left_key,
# right_key AS right_key,
# level AS level,
# parent_id AS parent_id
# FROM $ table_name
# WHERE
# [tree = $ tree AND]
# (left_key = $ left_key OR right_key = $ left_key)
# LIMIT 1;
my $ sth = $ dbh-> prepare ($ sql); $ sth-> execute || return {success => 0, error => $ dbh-> errstr};
my $ row = $ sth-> fetchrow_hashref ();
$ sth-> finish;
# We found the node by the left key, therefore, we will have a new node in front of the found one
if ($ row && $ row -> {left_key} == $ new -> {$ f -> {left_key}}) {
$ new -> {$ f -> {parent_id}} = $ row -> {parent_id};
$ new -> {$ f -> {level}} = $ row -> {level};
# We found the node by the right key, therefore, we will have a new node under the found
} elsif ($ row) {
$ new -> {$ f -> {parent_id}} = $ row -> {id};
$ new -> {$ f -> {level}} = $ row -> {level} + 1;
} else {
# Again such a crap, indicated completely left data. Although there is an option that we put the node the very first
# then this is not a mistake. But in other cases, just ignore the movement.
$ new -> {$ f -> {left_key}} = $ new -> {$ f -> {left_key}} && $ new -> {$ f -> {left_key}} == 1? 1: $ old -> {$ f -> {left_key}};
}
}
# Now that we know what our left key is, we can check if we are sending inside
if ($ new -> {$ f -> {left_key}}> $ old -> {$ f -> {left_key}} && $ new -> {$ f -> {left_key}} <$ old -> {$ f -> {right_key}}) {
return {success => 0, error => 'Can not move unit inside'};
}
# We figured out the coordinates, the only thing is, We look, and do we even have changes on the tree?
if ($ new -> {$ f -> {left_key}} && $ new -> {$ f -> {left_key}}! = $ old -> {$ f -> {left_key}}) {
# Determine level and tree offsets
my $ skew_level = $ new -> {$ f -> {level}} - $ old -> {$ f -> {level}};
my $ skew_tree = $ old -> {$ f -> {right_key}} - $ old -> {$ f -> {left_key}} + 1;
# Move down the tree
if ($ new -> {$ f -> {left_key}}> $ old -> {$ f -> {left_key}}) {
my $ skew_edit = $ new -> {$ f -> {left_key}} - $ old -> {$ f -> {left_key}} - $ skew_tree;
my $ query = 'UPDATE'. $ table_name.
'SET'. $ F -> {left_key}. ' = CASE WHEN '. $ F -> {right_key}.' <= '. $ old -> {$ f -> {right_key}}.'
THEN '. $ F -> {left_key}.' + '. $ skew_edit.'
ELSE CASE WHEN '. $ F -> {left_key}.' > '. $ old -> {$ f -> {right_key}}.'
THEN '. $ F -> {left_key}.' - '. $ skew_tree.'
ELSE '. $ F -> {left_key}.'
END
END,
'. $ f -> {level}.' = CASE WHEN '. $ F -> {right_key}.' <= '. $ old -> {$ f -> {right_key}}.'
THEN '. $ F -> {level}.' + '. $ skew_level.'
ELSE '. $ F -> {level}.'
END,
'. $ f -> {right_key}.' = CASE WHEN '. $ F -> {right_key}.' <= '. $ old -> {$ f -> {right_key}}.'
THEN '. $ F -> {right_key}.' + '. $ skew_edit.'
ELSE CASE WHEN '. $ F -> {right_key}.' <'. $ new -> {$ f -> {left_key}}.'
THEN '. $ F -> {right_key}.' - '. $ skew_tree.'
ELSE '. $ F -> {right_key}.'
END
END
WHERE
'. ($ table -> {multi}? $ f -> {tree}.' = '. $ old -> {$ f -> {tree}.' AND ':' ').
$ f -> {right_key}. ' > '. $ old -> {$ f -> {left_key}}.' AND '.
$ f -> {left_key}. ' <'. $ new -> {$ f -> {left_key}}.'; ';
$ dbh-> do ($ query) || return {success => 0, error => $ dbh-> errstr};
$ new -> {$ f -> {left_key}} = $ new -> {$ f -> {left_key}} - $ skew_tree;
} else {
# Move up the tree
my $ skew_edit = $ new -> {$ f -> {left_key}} - $ old -> {$ f -> {left_key}};
my $ query = 'UPDATE'. $ table_name. '
SET
'. $ f -> {right_key}.' = CASE WHEN '. $ F -> {left_key}.' > = '. $ old -> {$ f -> {left_key}}.'
THEN '. $ F -> {right_key}.' + '. $ skew_edit.'
ELSE CASE WHEN '. $ F -> {right_key}.' <'. $ old -> {$ f -> {left_key}}.'
THEN '. $ F -> {right_key}.' + '. $ skew_tree.'
ELSE '. $ F -> {right_key}.'
END
END,
'. $ f -> {level}.' = CASE WHEN '. $ F -> {left_key}.' > = '. $ old -> {$ f -> {left_key}}.'
THEN '. $ F -> {level}.' + '. $ skew_level.'
ELSE '. $ F -> {level}.'
END,
'. $ f -> {left_key}.' = CASE WHEN '. $ F -> {left_key}.' > = '. $ old -> {$ f -> {left_key}}.'
THEN '. $ F -> {left_key}.' + '. $ skew_edit.'
ELSE CASE WHEN '. $ F -> {left_key}.' > = '. $ new -> {$ f -> {left_key}}.'
THEN '. $ F -> {left_key}.' + '. $ skew_tree.'
ELSE '. $ F -> {left_key}.'
END
END
WHERE
'. ($ table -> {multi}? $ f -> {tree}.' = '. $ old -> {$ f -> {tree}.' AND ':' ').
$ f -> {right_key}. ' > = '. $ new -> {$ f -> {left_key}}.' AND '.
$ f -> {left_key}. ' <'. $ old -> {$ f -> {right_key}}.'; ';
$ dbh-> do ($ query) || return {success => 0, error => $ dbh-> errstr};
}
}
# To start, leave in $ new only those fields that have really changed, and which we generally have:
my @sets = ();
foreach my $ key (keys% {$ new}) {
# There is no such field
delete $ new -> {$ key}, next exists $ old -> {$ key};
# The content field has not changed
delete $ new -> {$ key}, next if $ old -> {$ key} && $ new -> {$ key} && $ new -> {$ key} eq $ old -> {$ key};
# The field without content and did not change
delete $ new -> {$ key}, next if! $ old -> {$ key} &&! $ new -> {$ key};
# ID will not change, but remove just in case
delete $ new -> {$ key}, next if $ key eq $ f -> {id};
# same, no value check
push @sets, $ key. '='. (defined $ new -> {$ key} && $ new -> {$ key} = ~ / ^ \ d + $ /? $ new -> {$ key}: '"'. $ new -> {$ key}. '"');
}
# Update modified fields
my $ query = 'UPDATE'. $ table_name.
'SET'. (Join ',', @sets).
'WHERE'. $ F -> {id}. ' = '. $ old -> {$ f -> {id}};
$ dbh-> do ($ query) || return {success => 0, error => $ dbh-> errstr};
# Again, we request a string after UPDATE, is it anyway what triggers have been updated?
$ sql = 'SELECT * FROM'. $ table_name. ' WHERE '. $ F -> {id}.' = '. $ old -> {$ f -> {id}}.' LIMIT 1 ';
$ sth = $ dbh-> prepare ($ sql); $ sth-> execute || return {success => 0, error => $ dbh-> errstr};
my $ row = $ sth-> fetchrow_hashref;
$ sth-> finish;
return {success => 1, row => $ row};
}
# ------------------------------------------------- -------------------------------------------------- --------------------
# UPDATE
# Move down the tree
my $ update = MY :: NestedSets-> update ($ table_name, {field1 => 'row-u-1 -'. $ tree, ids => 1, lk => 10, tr => $ tree});
warn Dumper $ update;
# Move up the tree
$ update = MY :: NestedSets-> update ($ table_name, {field1 => 'row-u-4 -'. $ tree, ids => 6, lk => 1, tr => $ tree});
warn Dumper $ update;
# Change parent
$ update = MY :: NestedSets-> update ($ table_name, {field1 => 'row-u-8 -'. $ tree, ids => 2, pi => 5, tr => $ tree});
warn Dumper $ update;
sub delete {
# We distribute the incoming data in places, and, accordingly, we check if we have enough
shift if $ _ [0] && ($ _ [0] eq __PACKAGE__ || (ref $ _ [0] && ref $ _ [0] eq __PACKAGE__));
my ($ table_name, $ id, $ flag) = @_;
return {success => 0, error => 'Bad income data!'} unless $ dbh && $ table_name && $ id;
# We find that for the table and take its additional attributes and synonyms of the fields
my $ table = $ tables -> {$ table_name} || $ tables -> {default};
my $ f = $ table -> {fields};
# Since we are not limited, as in triggers, in the number and volume of transmitted parameters,
# implementation of deletion will be double: deleting the entire branch and deleting one tree node
# by default, delete the entire branch
$ flag = {cascade => 'cascade', one => 'one'} -> {$ flag || 'cascade'} || 'cascade';
# Select the node to be deleted, and we need only 3 fields: tree, left_key and right_key
# Although we can pass it as a parameter, but you never know, we could have changed the keys before that,
# a tree from this crumble.
my $ sql = 'SELECT'.
($ table -> {multi}? $ f -> {tree}. 'AS tree,': '').
$ f -> {parent_id}. ' AS parent_id, '.
$ f -> {level}. ' AS level, '.
$ f -> {left_key}. ' AS left_key, '.
$ f -> {right_key}. ' AS right_key '.
'FROM'. $ Table_name.
'WHERE'. $ F -> {id}. ' = '. $ id;
my $ sth = $ dbh-> prepare ($ sql); $ sth-> execute || return {success => 0, error => $ dbh-> errstr};
my $ old = $ sth-> fetchrow_hashref ();
$ sth-> finish;
return {success => 0, error => 'No old unit!'} unless $ old;
if ($ flag eq 'cascade') {
# Remove branch
my $ query = 'DELETE FROM'. $ table_name.
'WHERE'.
($ table -> {multi}? $ f -> {tree}. '='. $ old -> {tree}. 'AND': '').
$ f -> {left_key}. ' > = '. $ old -> {left_key}.' AND '.
$ f -> {right_key}. ' <= '. $ old -> {right_key};
$ dbh-> do ($ query) || return {success => 0, error => $ dbh-> errstr};
# Remove the gap in the keys:
my $ skew_tree = $ old -> {right_key} - $ old -> {left_key} + 1;
$ query = 'UPDATE'. $ table_name.
'SET'. $ F -> {left_key}. ' = CASE WHEN '. $ F -> {left_key}.' > '. $ old -> {left_key}.'
THEN '. $ F -> {left_key}.' - '. $ skew_tree.'
ELSE '. $ F -> {left_key}.'
END, '.
$ f -> {right_key}. ' = '. $ f -> {right_key}.' - '. $ skew_tree.
'WHERE'.
($ table -> {multi}? $ f -> {tree}. '='. $ old -> {tree}. 'AND': '').
$ f -> {right_key}. ' > '. $ old -> {right_key}.'; ';
# Request in readable form:
# UPDATE $ table_name
# SET left_key = CASE WHEN left_key> OLD.left_key
# THEN left_key - $ skew_tree
# ELSE left_key
# END,
# right_key = right_key - $ skew_tree
# WHERE
# [tree = OLD.tree AND]
# right_key> OLD.right_key;
$ dbh-> do ($ query) || return {success => 0, error => $ dbh-> errstr};
} else {
# Remove the node
my $ query = 'DELETE FROM'. $ table_name. ' WHERE '. $ F -> {id}.' = '. $ id.' LIMIT 1 '; it is not enough
$ dbh-> do ($ query) || return {success => 0, error => $ dbh-> errstr};
# Remove the gap and rebuild the subordinate branch
$ query = 'UPDATE'. $ table_name.
'SET'. $ F -> {left_key}. ' = CASE WHEN '. $ F -> {left_key}.' <'. $ old -> {left_key}.'
THEN '. $ F -> {left_key}.'
ELSE CASE WHEN '. $ F -> {right_key}.' <'. $ old -> {right_key}.'
THEN '. $ F -> {left_key}.' - one
ELSE '. $ F -> {left_key}.' - 2
END
END, '.
$ f -> {parent_id}. ' = CASE WHEN '. $ F -> {right_key}.' <'. $ old -> {right_key}.
'AND'. $ F -> {level}. ' = '. $ old -> {level}.' + 1
THEN '. $ Old -> {parent_id}.'
ELSE '. $ F -> {parent_id}.'
END, '.
$ f -> {level}. ' = CASE WHEN '. $ F -> {right_key}.' <'. $ old -> {right_key}.'
THEN '. $ F -> {level}.' - one
ELSE '. $ F -> {level}.'
END, '.
$ f -> {right_key}. ' = CASE WHEN '. $ F -> {right_key}.' <'. $ old -> {right_key}.'
THEN '. $ F -> {right_key}.' - one
ELSE '. $ F -> {right_key}.' - 2
END
WHERE '.
($ table -> {multi}? $ f -> {tree}. '='. $ old -> {tree}. 'AND': '').
'('. $ f -> {right_key}. '>'. $ old -> {right_key}. 'OR
('. $ f -> {left_key}.'> '. $ old -> {left_key}.' AND '. $ f -> {right_key}.' <'. $ old -> {right_key}.')) ; ';
# Request in readable form:
# UPDATE $ table_name
# SET left_key = CASE WHEN left_key <OLD.left_key
# THEN left_key
# ELSE CASE WHEN right_key <OLD.right_key
# THEN left_key - 1
# ELSE left_key - 2
# END
# END,
# parent_id = CASE WHEN right_key <OLD.right_key AND `level` = OLD.level + 1
# THEN OLD.parent_id
# ELSE parent_id
# END,
# `level` = CASE WHEN right_key <OLD.right_key
# THEN `level` - 1
# ELSE `level`
# END,
# right_key = CASE WHEN right_key <OLD.right_key
# THEN right_key - 1
# ELSE right_key - 2
# END
# WHERE
# [tree = OLD.tree AND]
# (right_key> OLD.right_key OR
# (left_key> OLD.left_key AND right_key <OLD.right_key));
$ dbh-> do ($ query) || return {success => 0, error => $ dbh-> errstr};
}
return {sucess => 1};
}
my $ delete = MY :: NestedSets-> delete ($ table_name, 2);
$ delete = MY :: NestedSets-> delete ($ table_name, 3, 'one');
$ delete = MY :: NestedSets-> delete ($ table_name, 4);
Source: https://habr.com/ru/post/65495/
All Articles