Требование обычное: сделать максимально прозрачный алгоритм для сохранения целостности структуры и управления деревом.
Исходные данные
Создаем простую таблицу без излишеств:
Правда, что плохо в ltree - приходится создавать 2 индекса на поле path, для того, что бы можно было осуществлять как по ltree типу так и по text:
В качестве элементов материализованного пути возьмем id.
Традиционно, для поддержания целостности данных я использую триггера. Соответсвенно их и буду описывать.
Триггер на вставку
Основной сложностью вставки материализованного пути на базе id является то, что id у нас не известен до момента вставки, поэтому собрать путь заранее не представляется возможным. Эту проблему можно решить создав триггер на вставку записи, в котором мы будем переопределять метериализованный путь вставляемого узла относительно входных данных. Входные данные у нас могут быть двух видов: ID родительского узла или материализованный путь родительского узла.
Сразу бросается в глаза постоянное принудительное приведение значений переменных к определенному типу (::text). Это важно, так как ltree может работать только со своим типом данных и типом text.
Триггер на обновление
С обновлением тоже есть определенные сложности, так как при перемещении узла, нам потребуется обновить все подчиненные ветви, что приведет к повторному вызову триггера на обновление.
Так как нет прямой связи родитель-потомок, то FOREIGN KEY навесить не получится, но можно производить обновление рекурсивно, если применять изменения не на всех потомков, а только потомков следующего уровня вложенности:
Так же как и в триггере на вставку производится жесткая типизация данных.
Триггер на удаление
Тут вообще все просто: рекурсивное удаление всех детей:
На этом все. Никаких сложностей, даже при отсутствии прямого наследования (Adjacency List).