i ❤️ plpgsql
--
-- This function adds a story to a container at the specified position, or moves an existing story to a new
-- position. It also ensures that the story appears in only a single container, although that is a convenience
-- of this function, rather than a requirement of the design.
--
-- container.items is an array of container items (which might be stories, other containers, or other things)
--
-- The result is similar to creating a many-many table and indexing it, but is much faster in a large,
-- shared table with sparse data, because it requires much less I/O to find the list of contained items,
-- since we don't need to look up an index on, or update, the many-many table, at the cost of only a little
-- more complexity (ie, this function).
--
--   _tid         team ID
--   _container   container ID to add story to
--   _story       story ID to add
--   _position    position to add story (1-based)
-- 
create or replace function backlog.container_add_story(_tid int, _container int, _story int, _position int) returns void language plpgsql as $$
declare
    _backlog backlog.backlog_t;
    _new_container backlog.container_t;
    _new_item backlog.container_item_t;
    _items backlog.container_item_t[];
    _items_left backlog.container_item_t[];
    _items_right backlog.container_item_t[];

begin
    _new_item = ('STORY', _story)::backlog.container_item_t;

    -- Referential integrity check
    select backlog into _backlog from backlog.story where tid = _tid and story = _story;
    if not found then
        raise exception 'story not found: %', _story;
    end if;

    --
    -- Remove the story from any existing container(s). This is an intentional feature of this
    -- function, but a decision that might be revisited in the future; being able to add a story
    -- to multiple containers might later become a feature.
    --
    update backlog.container set items = array_remove(items, _new_item) where tid=_tid and backlog=_backlog and _new_item = any (items);

    --
    -- Now, add the story to the specific, identified container. The story was already removed, if it
    -- existed, by the previous update.
    --
    select container, items into _new_container, _items from backlog.container where tid = _tid and container = _container;
    if not found then
        raise exception 'container not found: %', _container;
    end if;

    --
    -- Compute the slice to the left and right of the position in which the story is to be
    -- added. (This could be done directly in the `select` statement, but it seems a bit clearer to do it
    -- explicity)
    --
    _items_left = _items[:_position - 1];
    _items_right = _items[_position:];

    update backlog.container set items = _items_left || _new_item || _items_right where tid = _tid and backlog=_backlog and container = _container;
end;
$$;
          
mark@plpgsql.dev