DROP PROCEDURE IF EXISTS {table_name}_count_levels $$

CREATE PROCEDURE {table_name}_count_levels(processed_tree_id INT)
-- Пересчитывает уровни нод у указанного дерева
BEGIN
    DECLARE current_level INT DEFAULT 0;
    DECLARE found INT DEFAULT 0;
    DECLARE maxint INT DEFAULT (1 << 31) - 1;
    -- сбрасываем level в неиcпользуемое значение. NULL нельзя
    UPDATE {table_name} SET level=maxint WHERE tree_id=processed_tree_id;
    UPDATE {table_name} SET level=0 WHERE tree_id=processed_tree_id AND parent_id IS NULL;

    c: LOOP
        -- спускаемся по уровням с 0 вниз, проставляя уровень потомкам тех, кому уже проставили на прошлой итерации
        SELECT 1 INTO found FROM {table_name} WHERE tree_id=processed_tree_id AND level=maxint LIMIT 1;
        IF found THEN
             SET current_level := current_level + 1;

             UPDATE {table_name} child_tree, {table_name} parent_tree SET child_tree.level=current_level
             WHERE parent_tree.tree_id=processed_tree_id AND parent_tree.level < maxint
               AND child_tree.level=maxint AND child_tree.parent_id=parent_tree.id;
             SET found := 0;
        ELSE
             LEAVE c;
        END IF;
    END LOOP;
END $$


DROP PROCEDURE IF EXISTS {table_name}_count_descendants $$

CREATE PROCEDURE {table_name}_count_descendants(processed_tree_id INT)
-- рассчитывает количество потомков у каждой ноды указанного дерева. нужно для упрощения расчета lft и rght
BEGIN
    DECLARE current_level INT DEFAULT 0;

    UPDATE {table_name} SET descendants_count = 0 WHERE tree_id=processed_tree_id;
    SELECT MAX(level) INTO current_level FROM {table_name} WHERE tree_id=processed_tree_id;

    WHILE current_level > 0 DO
        -- Поднимаемся с самого нижнего уровня вверх. Количество потомков у ноды -
        -- это количество непосредственных детей плюс сумма потомков у каждого из них.

        UPDATE {table_name}, (SELECT parent_id, COUNT(id) as amount, SUM(descendants_count) as total
            FROM {table_name}
            WHERE level=current_level AND tree_id=processed_tree_id
            GROUP BY parent_id) children
        SET {table_name}.descendants_count = children.amount + children.total
        WHERE {table_name}.id=children.parent_id;

        SET current_level := current_level -1;
    END WHILE;

END $$


DROP PROCEDURE IF EXISTS {table_name}_count_left_right_at_level $$

CREATE PROCEDURE {table_name}_count_left_right_at_level(
    processed_tree_id INT,
    current_level INT,
    INOUT current_parent_id INT,
    INOUT current_lft INT,
    INOUT current_rght INT
)
-- рассчитывает параметры lft и rght на указанном уровне дерева.
-- потребовалось вынести в отдельную процедуру, потому что курсор зависит от уровня, а менять его по ходу дела нельзя
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE node_id INT DEFAULT 0;
    DECLARE node_parent_id INT DEFAULT 0;
    DECLARE node_lft INT DEFAULT 0;
    DECLARE node_rght INT DEFAULT 0;
    DECLARE node_descendants_count INT DEFAULT 0;
    DECLARE curs CURSOR FOR SELECT id, parent_id, lft, rght, descendants_count from {table_name}
        WHERE level = current_level AND tree_id=processed_tree_id ORDER BY tree_id, parent_id, id;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    OPEN curs;

    read_loop: LOOP
        FETCH curs INTO node_id, node_parent_id, node_lft, node_rght, node_descendants_count;
        IF done THEN
            LEAVE read_loop;
        END IF;

        -- рассчитываем параметры для ноды
        IF current_parent_id <> node_parent_id THEN
            SET current_rght := node_lft;
            SET current_parent_id := node_parent_id;
        END IF;
        SET current_lft := current_rght + 1;
        SET current_rght := current_lft + node_descendants_count * 2 + 1;

        -- кладем параметры в ноду и в непосредственных потомков, чтобы рассчитать на следующем уровне
        UPDATE {table_name} SET lft = current_lft, rght = current_rght WHERE id = node_id or parent_id = node_id;
        END LOOP;
    CLOSE curs;
END $$


DROP PROCEDURE IF EXISTS {table_name}_count_left_right $$

CREATE PROCEDURE {table_name}_count_left_right(processed_tree_id INT)
-- рассчитывает параметры lft и rght у указанного дерева.
BEGIN
    DECLARE max_level INT DEFAULT 0;
    DECLARE current_level INT DEFAULT 0;
    DECLARE current_parent_id INT DEFAULT 0;
    DECLARE current_lft INT DEFAULT 0;
    DECLARE current_rght INT DEFAULT 0;

    UPDATE {table_name} SET lft=0, rght=0 WHERE tree_id=processed_tree_id;

    SELECT MAX(level) INTO max_level FROM {table_name} WHERE tree_id=processed_tree_id;
    WHILE current_level <= max_level DO
        call {table_name}_count_left_right_at_level(processed_tree_id, current_level, current_parent_id, current_lft, current_rght);
        SET current_level := current_level + 1;
    END WHILE;
END
