-- --------------------------------------------------------------------------------
-- _020_nodeLink Group Routines
-- --------------------------------------------------------------------------------
DELIMITER $$$

-- intermediate caching 

-- drop function if exists __nodeLink_UpdateCache$$$
create function __nodeLink_UpdateCache() returns int unsigned
not deterministic modifies sql data
begin
    insert nodeLinkT(_id, nodeLinkTypeId, srcNodeId, dstNodeId, intermediate, intermediateRecommendSingle, intermediateRecommendMultiple, added)
    select
        nl._id,
        nl.nodeLinkTypeId,
        nl.srcNodeId,
        nl.dstNodeId,
        (nl.metric > 1 
            or exists(
                select 1 
                from nodeLinkChainV chain 
                where nl.nodeLinkTypeId = chain.nodeLinkTypeId
                    and nl.srcNodeId = chain.srcNodeId
                    and nl.dstNodeId = chain.dstNodeId)),
        if(
            anl.singleSrc = 'n', 
            nl.metric > 1, 
            nl.metric > 1 
            or (anl.essential = 'n' 
                and exists(
                    select 1 
                    from nodeLinkChainV chain 
                    where nl.nodeLinkTypeId = chain.nodeLinkTypeId
                        and nl.srcNodeId = chain.srcNodeId
                        and nl.dstNodeId = chain.dstNodeId))),
        if(
            anl.singleSrc = 'y', 
            nl.metric > 1
            or (exists(
                select 1 
                from nodeLinkChainV chain 
                where nl.nodeLinkTypeId = chain.nodeLinkTypeId
                    and nl.srcNodeId = chain.srcNodeId
                    and nl.dstNodeId = chain.dstNodeId)), 
            0),
        nl.added
    from nodeLinkT nl
    join zzzNodeWorksetT ws1 on nl.srcNodeId = ws1.nodeId
    join zzzNodeWorksetT ws2 on nl.dstNodeId = ws2.nodeId
    join allowedNodeLinkByNodeV anl on nl.srcNodeId = anl.srcNodeId and nl.dstNodeId = anl.dstNodeId
    where ws1.connId = connection_id()
        and ws1.isLinked = 1
        and ws2.connId = connection_id()
        and ws2.isLinked = 1
    on duplicate key update
        nodeLinkT.intermediate = values(intermediate),
        nodeLinkT.intermediateRecommendSingle = values(intermediateRecommendSingle),
        nodeLinkT.intermediateRecommendMultiple = values(intermediateRecommendMultiple),
        nodeLinkT.added = values(added);

    return 1;
end$$$


-- drop function if exists __nodeLink_UpdateCacheAll$$$
create function __nodeLink_UpdateCacheAll(src_node_id int unsigned, dst_node_id int unsigned) returns int unsigned
not deterministic modifies sql data
begin
    if src_node_id is null and dst_node_id is null then
        insert nodeLinkT(_id, nodeLinkTypeId, srcNodeId, dstNodeId, intermediate, intermediateRecommendSingle, intermediateRecommendMultiple, added)
        select
            nl._id,
            nl.nodeLinkTypeId,
            nl.srcNodeId,
            nl.dstNodeId,
            (nl.metric > 1 
                or exists(
                    select 1 
                    from nodeLinkChainV chain 
                    where nl.nodeLinkTypeId = chain.nodeLinkTypeId
                        and nl.srcNodeId = chain.srcNodeId
                        and nl.dstNodeId = chain.dstNodeId)),
            if(
                anl.singleSrc = 'n', 
                nl.metric > 1, 
                nl.metric > 1 
                or (anl.essential = 'n' 
                    and exists(
                        select 1 
                        from nodeLinkChainV chain 
                        where nl.nodeLinkTypeId = chain.nodeLinkTypeId
                            and nl.srcNodeId = chain.srcNodeId
                            and nl.dstNodeId = chain.dstNodeId))),
            if(
                anl.singleSrc = 'y', 
                nl.metric > 1
                or (exists(
                    select 1 
                    from nodeLinkChainV chain 
                    where nl.nodeLinkTypeId = chain.nodeLinkTypeId
                        and nl.srcNodeId = chain.srcNodeId
                        and nl.dstNodeId = chain.dstNodeId)), 
                0),
            nl.added
        from nodeLinkT nl
        join allowedNodeLinkByNodeV anl on nl.srcNodeId = anl.srcNodeId and nl.dstNodeId = anl.dstNodeId
        on duplicate key update
            nodeLinkT.intermediate = values(intermediate),
            nodeLinkT.intermediateRecommendSingle = values(intermediateRecommendSingle),
            nodeLinkT.intermediateRecommendMultiple = values(intermediateRecommendMultiple),
            nodeLinkT.added = values(added);
    else
        insert nodeLinkT(_id, nodeLinkTypeId, srcNodeId, dstNodeId, intermediate, intermediateRecommendSingle, intermediateRecommendMultiple, added)
        select
            nl._id,
            nl.nodeLinkTypeId,
            nl.srcNodeId,
            nl.dstNodeId,
            (nl.metric > 1 
                or exists(
                    select 1 
                    from nodeLinkChainV chain 
                    where nl.nodeLinkTypeId = chain.nodeLinkTypeId
                        and nl.srcNodeId = chain.srcNodeId
                        and nl.dstNodeId = chain.dstNodeId)),
            if(
                anl.singleSrc = 'n', 
                nl.metric > 1, 
                nl.metric > 1 
                or (anl.essential = 'n' 
                    and exists(
                        select 1 
                        from nodeLinkChainV chain 
                        where nl.nodeLinkTypeId = chain.nodeLinkTypeId
                            and nl.srcNodeId = chain.srcNodeId
                            and nl.dstNodeId = chain.dstNodeId))),
            if(
                anl.singleSrc = 'y', 
                nl.metric > 1
                or (exists(
                    select 1 
                    from nodeLinkChainV chain 
                    where nl.nodeLinkTypeId = chain.nodeLinkTypeId
                        and nl.srcNodeId = chain.srcNodeId
                        and nl.dstNodeId = chain.dstNodeId)), 
                0),
            nl.added
        from nodeLinkT nl
        join allowedNodeLinkByNodeV anl on nl.srcNodeId = anl.srcNodeId and nl.dstNodeId = anl.dstNodeId
        where nl.srcNodeId = coalesce(src_node_id, nl.srcNodeId)
            and nl.dstNodeId = coalesce(dst_node_id, nl.dstNodeId)
        on duplicate key update
            nodeLinkT.intermediate = values(intermediate),
            nodeLinkT.intermediateRecommendSingle = values(intermediateRecommendSingle),
            nodeLinkT.intermediateRecommendMultiple = values(intermediateRecommendMultiple),
            nodeLinkT.added = values(added);
    end if;

    return 1;
end$$$


-- caching of distinct count of type ids

create function __nodeLink_UpdateRecommendCache1() returns int unsigned
not deterministic
begin
    /*insert cacheNodeRecommendSum T(srcNodeId, dstNodeId, nodeTypeIdDistinctCount)
    select 
        srcnode.id, 
        dstnode.id, 
        count(distinct node.nodeTypeId)
    from nodeT srcnode
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'
    join allowedNodeLinkT anl on srcnode.nodeTypeId = anl.srcNodeTypeId
    join nodeT dstnode on anl.dstNodeTypeId = dstnode.nodeTypeId and srcnode.id != dstnode.id

    join nodeLinkForkSrcV links on links.nodeLinkTypeId = nlt.id
        and links.dstNodeId1 = srcnode.id
        and links.dstNodeId2 = dstnode.id
        and links.metric1 = 1
        and links.metric2 = 1
    join nodeT node on links.srcNodeId = node.id
    
    join zzzNodeWorksetT ws on srcnode.id = ws.nodeId

    where anl.singleSrc = 'n'
        and ws.connId = connection_id()
        and ws.isExact = 1
    group by srcnode.id, dstnode.id
    on duplicate key update
        cacheNodeRecommendSum T.nodeTypeIdDistinctCount = values(nodeTypeIdDistinctCount);*/

    /*insert cacheNodeRecommendSum T(srcNodeId, dstNodeId, nodeTypeIdDistinctCount)
    select 
        srcnode.id, 
        dstnode.id, 
        count(distinct node.nodeTypeId)
    from nodeT srcnode
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'
    join allowedNodeLinkT anl on srcnode.nodeTypeId = anl.srcNodeTypeId
    join nodeT dstnode on anl.dstNodeTypeId = dstnode.nodeTypeId and srcnode.id != dstnode.id

    join nodeLinkForkSrcV links on links.nodeLinkTypeId = nlt.id
        and links.dstNodeId1 = srcnode.id
        and links.dstNodeId2 = dstnode.id
        and links.metric1 = 1
        and links.metric2 = 1
    join nodeT node on links.srcNodeId = node.id
    
    join zzzNodeWorksetT ws on dstnode.id = ws.nodeId

    where anl.singleSrc = 'n'
        and ws.connId = connection_id()
        and ws.isExact = 1
    group by srcnode.id, dstnode.id
    on duplicate key update
        cacheNodeRecommendSum T.nodeTypeIdDistinctCount = values(nodeTypeIdDistinctCount);*/

    return 1;
end$$$


create function __nodeLink_UpdateRecommendCacheQueue() returns int unsigned
not deterministic
begin
    /*insert cacheNodeRecommendSum T(srcNodeId, dstNodeId, nodeTypeIdDistinctCount)
    select 
        srcnode.id, 
        dstnode.id, 
        count(distinct node.nodeTypeId)
    from nodeT srcnode
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'
    join allowedNodeLinkT anl on srcnode.nodeTypeId = anl.srcNodeTypeId
    join nodeT dstnode on anl.dstNodeTypeId = dstnode.nodeTypeId and srcnode.id != dstnode.id

    join nodeLinkForkSrcV links on links.nodeLinkTypeId = nlt.id
        and links.dstNodeId1 = srcnode.id
        and links.dstNodeId2 = dstnode.id
        and links.metric1 = 1
        and links.metric2 = 1
    join nodeT node on links.srcNodeId = node.id
    
    join __nodeQueue T nq on srcnode.id = nq.nodeId

    where anl.singleSrc = 'n'
        and nq.isLinked = 1
    group by srcnode.id, dstnode.id
    on duplicate key update
        cacheNodeRecommendSum T.nodeTypeIdDistinctCount = values(nodeTypeIdDistinctCount);

    insert cacheNodeRecommendSum T(srcNodeId, dstNodeId, nodeTypeIdDistinctCount)
    select 
        srcnode.id, 
        dstnode.id, 
        count(distinct node.nodeTypeId)
    from nodeT srcnode
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'
    join allowedNodeLinkT anl on srcnode.nodeTypeId = anl.srcNodeTypeId
    join nodeT dstnode on anl.dstNodeTypeId = dstnode.nodeTypeId and srcnode.id != dstnode.id

    join nodeLinkForkSrcV links on links.nodeLinkTypeId = nlt.id
        and links.dstNodeId1 = srcnode.id
        and links.dstNodeId2 = dstnode.id
        and links.metric1 = 1
        and links.metric2 = 1
    join nodeT node on links.srcNodeId = node.id
    
    join __nodeQueue T nq on dstnode.id = nq.nodeId

    where anl.singleSrc = 'n'
        and nq.isLinked = 1
    group by srcnode.id, dstnode.id
    on duplicate key update
        cacheNodeRecommendSum T.nodeTypeIdDistinctCount = values(nodeTypeIdDistinctCount);*/
    
    return 1;
end$$$


create function __nodeLink_UpdateRecommendCacheAll(src_node_id int unsigned, dst_node_id int unsigned) returns int unsigned
not deterministic
begin
    /*if src_node_id is not null and dst_node_id is not null then
        insert cacheNodeRecommendSum T(srcNodeId, dstNodeId, nodeTypeIdDistinctCount)
        select 
            srcnode.id, 
            dstnode.id, 
            count(distinct node.nodeTypeId)
        from nodeT srcnode
        join nodeLinkTypeT nlt on nlt.isDefault = 'y'
        join allowedNodeLinkT anl on srcnode.nodeTypeId = anl.srcNodeTypeId
        join nodeT dstnode on anl.dstNodeTypeId = dstnode.nodeTypeId and srcnode.id != dstnode.id

        join nodeLinkForkSrcV links on links.nodeLinkTypeId = nlt.id
            and links.dstNodeId1 = srcnode.id
            and links.dstNodeId2 = dstnode.id
            and links.metric1 = 1
            and links.metric2 = 1
        join nodeT node on links.srcNodeId = node.id
        
        where anl.singleSrc = 'n'
            and srcnode.id = src_node_id
            and dstnode.id = dst_node_id
        group by srcnode.id, dstnode.id
        on duplicate key update
            cacheNodeRecommendSum T.nodeTypeIdDistinctCount = values(nodeTypeIdDistinctCount);
    elseif src_node_id is not null then
        insert cacheNodeRecommendSum T(srcNodeId, dstNodeId, nodeTypeIdDistinctCount)
        select 
            srcnode.id, 
            dstnode.id, 
            count(distinct node.nodeTypeId)
        from nodeT srcnode
        join nodeLinkTypeT nlt on nlt.isDefault = 'y'
        join allowedNodeLinkT anl on srcnode.nodeTypeId = anl.srcNodeTypeId
        join nodeT dstnode on anl.dstNodeTypeId = dstnode.nodeTypeId and srcnode.id != dstnode.id

        join nodeLinkForkSrcV links on links.nodeLinkTypeId = nlt.id
            and links.dstNodeId1 = srcnode.id
            and links.dstNodeId2 = dstnode.id
            and links.metric1 = 1
            and links.metric2 = 1
        join nodeT node on links.srcNodeId = node.id
        
        where anl.singleSrc = 'n'
            and srcnode.id = src_node_id
        group by srcnode.id, dstnode.id
        on duplicate key update
            cacheNodeRecommendSum T.nodeTypeIdDistinctCount = values(nodeTypeIdDistinctCount);
    elseif dst_node_id is not null then
        insert cacheNodeRecommendSum T(srcNodeId, dstNodeId, nodeTypeIdDistinctCount)
        select 
            srcnode.id, 
            dstnode.id, 
            count(distinct node.nodeTypeId)
        from nodeT srcnode
        join nodeLinkTypeT nlt on nlt.isDefault = 'y'
        join allowedNodeLinkT anl on srcnode.nodeTypeId = anl.srcNodeTypeId
        join nodeT dstnode on anl.dstNodeTypeId = dstnode.nodeTypeId and srcnode.id != dstnode.id

        join nodeLinkForkSrcV links on links.nodeLinkTypeId = nlt.id
            and links.dstNodeId1 = srcnode.id
            and links.dstNodeId2 = dstnode.id
            and links.metric1 = 1
            and links.metric2 = 1
        join nodeT node on links.srcNodeId = node.id
        
        where anl.singleSrc = 'n'
            and dstnode.id = dst_node_id
        group by srcnode.id, dstnode.id
        on duplicate key update
            cacheNodeRecommendSum T.nodeTypeIdDistinctCount = values(nodeTypeIdDistinctCount);
    end if;*/
    
    return 1;
end$$$


-- nodelink main routines

create function nodeLink_UpdateStart() returns int unsigned
not deterministic modifies sql data
begin
    return __nodeWorkset_Start();
end$$$


create function __nodeLink_UpdateCreateExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    repeat
        insert nodeLinkT(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l1.nodeLinkTypeId, l1.srcNodeId, l2.dstNodeId, (l1.metric + l2.metric)
        from nodeLinkT l1
        join nodeLinkT l2 on l1.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId
        join zzzNodeWorksetT ws1 on l1.srcNodeId = ws1.nodeId
        join zzzNodeWorksetT ws2 on l2.dstNodeId = ws2.nodeId
        left join nodeLinkT l3 on l1.nodeLinkTypeId = l3.nodeLinkTypeId
            and l3.srcNodeId = l1.srcNodeId
            and l3.dstNodeId = l2.dstNodeId
            and l3.metric <= l1.metric + l2.metric
        where l1.srcNodeId != l2.dstNodeId
            and l1.metric > 0
            and l2.metric > 0
            and ws1.connId = connection_id()
            and ws2.connId = connection_id()
            and l3.srcNodeId is null
        on duplicate key update
            nodeLinkT.metric = least(nodeLinkT.metric, l1.metric + l2.metric);
    until row_count() < 1 end repeat;

    return 1;
end$$$


create function __nodeLink_UpdateDeleteExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    declare row_cnt int unsigned default 0;

    create temporary table if not exists zzNodeLinkDelete (
        nodeLinkTypeId int unsigned not null,
        srcNodeId int unsigned not null,
        dstNodeId int unsigned not null,
        metric int not null default 1,
        primary key(nodeLinkTypeId, srcNodeId, dstNodeId),
        unique(nodeLinkTypeId, dstNodeId, srcNodeId)
    );

    create temporary table if not exists zzNodeLinkDelete2 like zzNodeLinkDelete;
    delete from zzNodeLinkDelete;
    delete from zzNodeLinkDelete2;

    insert zzNodeLinkDelete(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
    select l.nodeLinkTypeId, l.srcNodeId, l.dstNodeId, l.metric
    from nodeLinkT l
    join zzzNodeWorksetT ws1 on l.srcNodeId = ws1.nodeId
    join zzzNodeWorksetT ws2 on l.dstNodeId = ws2.nodeId
    where l.metric > 1
        and ws1.connId = connection_id()
        and ws2.connId = connection_id()
        and not exists (
            select 1
            from nodeLinkT l1
            join nodeLinkT l2 on l1.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId
            where l1.nodeLinkTypeId = l.nodeLinkTypeId
                and l1.srcNodeId = l.srcNodeId
                and l2.dstNodeId = l.dstNodeId
                and l1.metric > 0
                and l2.metric > 0
                and l.metric = l1.metric + l2.metric);
    set row_cnt = row_count();

    while row_cnt > 0 do
        insert ignore zzNodeLinkDelete2
        select * from zzNodeLinkDelete ld;

        insert ignore zzNodeLinkDelete(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l.nodeLinkTypeId, l.srcNodeId, l.dstNodeId, l.metric
        from nodeLinkT l
        join zzzNodeWorksetT ws1 on l.srcNodeId = ws1.nodeId
        join zzzNodeWorksetT ws2 on l.dstNodeId = ws2.nodeId
        join zzNodeLinkDelete2 l1 on l.nodeLinkTypeId = l1.nodeLinkTypeId and l.srcNodeId = l1.srcNodeId
        join nodeLinkT l2 on l.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId and l.dstNodeId = l2.dstNodeId
        where l.metric > 1
            and l2.metric > 0
            and l.metric = l1.metric + l2.metric
            and ws1.connId = connection_id()
            and ws2.connId = connection_id();

        set row_cnt = row_count();

        insert ignore zzNodeLinkDelete2
        select * from zzNodeLinkDelete ld;

        insert ignore zzNodeLinkDelete(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l.nodeLinkTypeId, l.srcNodeId, l.dstNodeId, l.metric
        from nodeLinkT l
        join zzzNodeWorksetT ws1 on l.srcNodeId = ws1.nodeId
        join zzzNodeWorksetT ws2 on l.dstNodeId = ws2.nodeId
        join nodeLinkT l1 on l.nodeLinkTypeId = l1.nodeLinkTypeId and l.srcNodeId = l1.srcNodeId
        join zzNodeLinkDelete2 l2 on l.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId and l.dstNodeId = l2.dstNodeId
        where l.metric > 1
            and l1.metric > 0
            and l.metric = l1.metric + l2.metric
            and ws1.connId = connection_id()
            and ws2.connId = connection_id();

        set row_cnt = row_cnt + row_count();
    end while;

    delete from nodeLinkT
    using nodeLinkT
    join zzNodeLinkDelete l on nodeLinkT.nodeLinkTypeId = l.nodeLinkTypeId
        and nodeLinkT.srcNodeId = l.srcNodeId
        and nodeLinkT.dstNodeId = l.dstNodeId
        and nodeLinkT.metric = l.metric;

    return 1;
end$$$


create function __nodeLink_UpdateRefreshExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    declare deleted, added int unsigned;
    set deleted = __nodeLink_UpdateDeleteExtraLinks();
    set added = __nodeLink_UpdateCreateExtraLinks();
    return deleted + added;
end$$$


create function nodeLink_UpdateEnd() returns int unsigned
not deterministic modifies sql data
begin
    declare node_id int unsigned;

    if __nodeWorkset_GetLevel() = 1 then
        set @1 = __nodeLink_UpdateRefreshExtraLinks();
        
        -- FORWARD: __node_UpdateCache is defined in _030_node
        set @2 = __node_UpdateCache();
        
        set @3 = __nodeLink_UpdateCache();
        
        set @4 = __nodeLink_UpdateRecommendCache1();

        -- make an exception for edited object - if there is 1 object, update its address and ratings right away
        if (select count(*) from zzzNodeWorksetV nw join objectT o on nw.nodeId = o.id where nw.isMain = 1) = 1 then
            select o.id into node_id from zzzNodeWorksetV nw join objectT o on nw.nodeId = o.id where nw.isMain = 1 limit 1;

            -- FORWARD: __object_UpdateAddressesAll and __object_UpdateRatingsAll are defined in _080_object
            set @1 = node_id is not null and __object_UpdateAddressesAll(node_id); -- and __object_UpdateRatingsAll(node_id);
        end if;
        
        -- put into the queue for the maintenance routine to work with
        insert zzzDevNullBigintLinkT(n)
        select nodeQueue_Add(nw.nodeId, null)
        from zzzNodeWorksetV nw
        where nw.isMain = 1;

        -- set flag for maintenance routine
        set @5 = const_SetI('search.cache.update', 1);
    end if;
    set @6 = __nodeWorkset_End();
    return 1;
end$$$


create function nodeLink_IsUpdateOn() returns int unsigned
not deterministic
begin 
    return (__nodeWorkset_GetLevel() > 0);
end$$$


create function __nodeLink_UpdateWorkSet(node_id int unsigned, direction int, only_direction int, linked int) returns int unsigned
comment 'direction=-1 - downwards; direction=1 - upwards; direction=0 - both; others - undefined'
not deterministic
begin
    return __nodeWorkset_Update(node_id, direction, only_direction, linked);
end$$$


create function __nodeLink_Create(
    link_code varchar(30) binary, 
    src_id int unsigned, 
    dst_id int unsigned,
    force_workset_ int
) returns int unsigned
not deterministic modifies sql data
begin
    declare res int unsigned;
    declare force_workset int default coalesce(force_workset_, 1);

    if not exists (select 1 
                    from allowedNodeLinkT anl 
                    join nodeT src on anl.srcNodeTypeId = src.nodeTypeId 
                    join nodeT dst on anl.dstNodeTypeId = dst.nodeTypeId
                    where src.id = src_id and dst.id = dst_id) 
    then
        call raiseError2('nodeLink', 'create', src_id, dst_id, 'cannot create link between');
    end if;
    
    insert nodeLinkT(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
    select nlt.id, src_id, dst_id, 1
    from nodeLinkTypeT nlt
    join nodeLinkTypeT nltdef on nltdef.isDefault = 'y'
    where nlt.code = coalesce(link_code, nltdef.code)
        and not exists (select 1 
                        from nodeLinkT l 
                        where l.nodeLinkTypeId = nlt.id 
                            and l.srcNodeId = src_id 
                            and l.dstNodeId = dst_id 
                            and l.metric <= 1)
    on duplicate key update nodeLinkT.metric = 1;
    
    set res = row_count();
    if nodeLink_IsUpdateOn() = 1 and res > 0 then
        set @1 = __nodeLink_UpdateWorkSet(src_id, -1, if(force_workset, 0, -1), 1) and __nodeLink_UpdateWorkSet(dst_id, 1, if(force_workset, 0, 1), 1);
    end if;

    return res;
end$$$


create function __nodeLink_Delete(
    link_code varchar(30) binary, 
    src_id int unsigned, 
    dst_id int unsigned,
    force_workset_ int
) returns int unsigned
not deterministic modifies sql data
begin
    declare res int unsigned;
    declare force_workset int default coalesce(force_workset_, 1);

    delete from nodeLinkT
    using nodeLinkT
    join nodeLinkTypeT nlt on nodeLinkT.nodeLinkTypeId = nlt.id
    join nodeLinkTypeT nltdef on nltdef.isDefault = 'y'
    where nlt.code = coalesce(link_code, nltdef.code)
        and nodeLinkT.srcNodeId = src_id 
        and nodeLinkT.dstNodeId = dst_id 
        and nodeLinkT.metric = 1;

    set res = row_count();
    if nodeLink_IsUpdateOn() = 1 and res > 0 then
        set @1 = __nodeLink_UpdateWorkSet(src_id, -1, if(force_workset, 0, -1), 1) and __nodeLink_UpdateWorkSet(dst_id, 1, if(force_workset, 0, 1), 1);
    end if;

    return res;
end$$$


create function __nodeLink_CreateExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    declare row_cnt int unsigned default 0;
    repeat
        insert nodeLinkT(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l1.nodeLinkTypeId, l1.srcNodeId, l2.dstNodeId, (l1.metric + l2.metric)
        from nodeLinkT l1
        join nodeLinkT l2 on l1.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId
        left join nodeLinkT l3 on l3.nodeLinkTypeId = l1.nodeLinkTypeId 
            and l3.srcNodeId = l1.srcNodeId
            and l3.dstNodeId = l2.dstNodeId
            and l3.metric <= l1.metric + l2.metric
        where l1.srcNodeId != l2.dstNodeId
            and l1.metric > 0
            and l2.metric > 0
            and l3.srcNodeId is null
        on duplicate key update 
            nodeLinkT.metric = least(nodeLinkT.metric, l1.metric + l2.metric);

        set row_cnt = row_cnt + row_count();
    until row_count() < 1 end repeat;
    return row_cnt;
end$$$


create function __nodeLink_DeleteExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    declare row_cnt int unsigned default 0;
    create temporary table if not exists zzNodeLinkDelete (
        nodeLinkTypeId int unsigned not null,
        srcNodeId int unsigned not null, 
        dstNodeId int unsigned not null, 
        metric int not null default 1,
        primary key(nodeLinkTypeId, srcNodeId, dstNodeId),
        unique(nodeLinkTypeId, dstNodeId, srcNodeId)
    );
    
    repeat
        delete from zzNodeLinkDelete;
        
        insert zzNodeLinkDelete(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l.nodeLinkTypeId, l.srcNodeId, l.dstNodeId, l.metric
        from nodeLinkT l
        where l.metric > 1
            and not exists (
                select 1 
                from nodeLinkT l1 
                join nodeLinkT l2 on l1.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId 
                where l1.nodeLinkTypeId = l.nodeLinkTypeId
                    and l1.srcNodeId = l.srcNodeId 
                    and l2.dstNodeId = l.dstNodeId
                    and l1.metric > 0
                    and l2.metric > 0
                    and l.metric = l1.metric + l2.metric);

        insert ignore zzNodeLinkDelete(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l.nodeLinkTypeId, l.srcNodeId, l.dstNodeId, l.metric
        from nodeLinkT l
        join nodeLinkT l2 on l.nodeLinkTypeId = l2.nodeLinkTypeId 
            and l.srcNodeId = l2.srcNodeId 
            and l.dstNodeId = l2.dstNodeId 
            and l.metric > l2.metric
        where l.metric > 1;
        
        delete from nodeLinkT
        using nodeLinkT 
        join zzNodeLinkDelete l on nodeLinkT.nodeLinkTypeId = l.nodeLinkTypeId 
            and nodeLinkT.srcNodeId = l.srcNodeId 
            and nodeLinkT.dstNodeId = l.dstNodeId 
            and nodeLinkT.metric = l.metric;

        set row_cnt = row_cnt + row_count();
    until row_count() < 1 end repeat;
    return row_cnt;
end$$$


create function __nodeLink_RefreshExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    declare deleted, added int unsigned;
    set deleted = __nodeLink_DeleteExtraLinks();
    set added = __nodeLink_CreateExtraLinks();
    return deleted + added;
end$$$


create function nodeLink_Create(
    link_code varchar(30) binary, 
    src_id int unsigned, 
    dst_id int unsigned
) returns int unsigned
not deterministic modifies sql data
begin
    declare res int unsigned;
    set @1 = nodeLink_UpdateStart();
    set res = __nodeLink_Create(link_code, src_id, dst_id, null);
    set @2 = nodeLink_UpdateEnd();
    return 1;
end$$$


create function nodeLink_Create2(
    link_code varchar(30) binary, 
    src_id int unsigned, 
    dst_id int unsigned
) returns int unsigned
not deterministic modifies sql data
begin
    set @1 = nodeLink_Create(link_code, src_id, dst_id);
    return dst_id;
end$$$


create function nodeLink_Delete(
    link_code varchar(30) binary, 
    src_id int unsigned, 
    dst_id int unsigned
) returns int unsigned
not deterministic modifies sql data
begin
    declare res int unsigned;
    set @1 = nodeLink_UpdateStart();
    set res = __nodeLink_Delete(link_code, src_id, dst_id, null);
    set @2 = nodeLink_UpdateEnd();
    return 1;
end$$$


-- ---------------------------------
-- nodeLink smart linking
-- ---------------------------------

create function __nodeLink_DestinationStart() returns int unsigned
not deterministic modifies sql data
begin
    drop temporary table if exists __nodeLinkDestinationT;
    create temporary table __nodeLinkDestinationT(nodeId int unsigned not null primary key);

    return 1;
end$$$


create function __nodeLink_DestinationAdd(node_id int unsigned) returns int unsigned
not deterministic modifies sql data
begin
    insert ignore __nodeLinkDestinationT(nodeId) values (node_id);

    return 1;
end$$$


create function __nodeLink_DestinationEnd() returns int unsigned
not deterministic modifies sql data
begin
    drop temporary table if exists __nodeLinkDestinationT;

    return 1;
end$$$


create function __nodeLink_Relink(
    link_code_ varchar(30) binary, 
    src_id_ int unsigned
) returns int unsigned
comment 'links src_id and all nodes in __nodeLinkDestinationT, detects and removes unnecessary linking'
not deterministic modifies sql data
begin
    -- NOTE: __nodeLinkDestinationT table must be created and filled in by caller

    declare link_code varchar(30) binary;
    declare link_id int unsigned;
    
    select nlt.id, nlt.code into link_id, link_code
    from nodeLinkTypeT nlt join nodeLinkTypeT nltdef on nltdef.isDefault = 'y'
    where nlt.code = coalesce(link_code_, nltdef.code);
    
    drop temporary table if exists nodeLinkRelinkInserts;
    drop temporary table if exists nodeLinkRelinkSrc;
    drop temporary table if exists nodeLinkRelinkDst;

    create temporary table nodeLinkRelinkInserts(
        srcNodeId int unsigned not null,
        dstNodeId int unsigned not null,
        primary key(srcNodeId, dstNodeId)
    );

    create temporary table nodeLinkRelinkSrc(
        nodeId int unsigned not null primary key,
        nodeTypeId int unsigned not null,
        index(nodeTypeId)
    );
    
    create temporary table nodeLinkRelinkDst(
        nodeId int unsigned not null primary key,
        nodeTypeId int unsigned not null,
        index(nodeTypeId)
    );

    -- fill in table with all parents
    insert nodeLinkRelinkSrc(nodeId, nodeTypeId) 
    select n.id, n.nodeTypeId
    from nodeT n
    where n.id = src_id_;
    
    insert ignore nodeLinkRelinkSrc(nodeId, nodeTypeId)
    select nl.srcNodeId, anl.srcNodeTypeId
    from nodeLinkT nl
    join allowedNodeLinkByNodeV anl on nl.srcNodeId = anl.srcNodeId and nl.dstNodeId = anl.dstNodeId
    where nl.nodeLinkTypeId = link_id
        and nl.dstNodeId = src_id_
        and nl.metric = 1
        and anl.singleSrc = 'y';

    -- fill in table with all children
    insert nodeLinkRelinkDst(nodeId, nodeTypeId) 
    select n.id, n.nodeTypeId
    from nodeT n
    join __nodeLinkDestinationT nld on n.id = nld.nodeId;
    
    insert ignore nodeLinkRelinkDst(nodeId, nodeTypeId)
    select nl.dstNodeId, anl.dstNodeTypeId
    from nodeLinkT nl
    join allowedNodeLinkByNodeV anl on nl.srcNodeId = anl.srcNodeId and nl.dstNodeId = anl.dstNodeId
    join __nodeLinkDestinationT nld on nl.srcNodeId = nld.nodeId
    where nl.nodeLinkTypeId = link_id
        and nl.metric = 1
        and anl.singleSrc = 'y';

    -- add src and dst nodes to the list
    insert nodeLinkRelinkInserts(srcNodeId, dstNodeId)
    select anl.srcNodeId, anl.dstNodeId
    from allowedNodeLinkByNodeV anl
    join __nodeLinkDestinationT nld on anl.dstNodeId = nld.nodeId
    where anl.srcNodeId = src_id_;

    -- fill all valid combinations from src and dst sets, so all singleSrc='y' nodes will be linked
    insert ignore nodeLinkRelinkInserts(srcNodeId, dstNodeId)
    select src.nodeId, dst.nodeId
    from nodeLinkRelinkSrc src
    join allowedNodeLinkT anl on src.nodeTypeId = anl.srcNodeTypeId
    join nodeLinkRelinkDst dst on anl.dstNodeTypeId = dst.nodeTypeId
    left join nodeLinkT exilink on exilink.nodeLinkTypeId = link_id
        and src.nodeId = exilink.srcNodeId
        and dst.nodeId = exilink.dstNodeId
        and exilink.metric = 1
    where anl.singleSrc = 'y'
        and exilink.metric is null;
    
    -- go and link
    set @1 = nodeLink_UpdateStart();
    
    insert zzzDevNullLinkT(n)
    select __nodeLink_Create(link_code, nlri.srcNodeId, nlri.dstNodeId, null)
    from nodeLinkRelinkInserts nlri;

    set @2 = nodeLink_UpdateEnd();
    
    drop temporary table nodeLinkRelinkInserts;
    drop temporary table nodeLinkRelinkSrc;
    drop temporary table nodeLinkRelinkDst;

    return 1; 
end$$$


create function nodeLink_Relink(
    link_code_ varchar(30) binary, 
    src_id_ int unsigned, 
    dst_id_ int unsigned
) returns int unsigned
comment 'links src_id and dst_id, detects and removes unnecessary linking'
not deterministic modifies sql data
begin
    declare res int unsigned default 1;

    if src_id_ = dst_id_ then
        return 1;
    end if;

    set @1 = __nodeLink_DestinationStart();
    set @2 = __nodeLink_DestinationAdd(dst_id_);
    set res = __nodeLink_Relink(link_code_, src_id_);
    set @3 = __nodeLink_DestinationEnd();

    return res;
end$$$


create function nodeLink_Relink2(
    link_code_ varchar(30) binary, 
    src_id_ int unsigned, 
    dst_id_ int unsigned
) returns int unsigned
comment 'calls nodeLink_Relink and returns dst_id'
not deterministic modifies sql data
begin
    set @1 = nodeLink_Relink(link_code_, src_id_, dst_id_);
    return dst_id_;
end$$$


create function __nodeLink_Unlink(
    link_code_ varchar(30) binary, 
    src_id_ int unsigned, 
    unlink_from_src_id_ int unsigned  -- if true - unlink from src_id as well; otherwise - will keep the link to src_id_
) returns int unsigned
comment 'unlinks src_id and all nodes in __nodeLinkDestinationT'
not deterministic modifies sql data
begin
    declare link_code varchar(30) binary;
    declare link_id int unsigned;
    declare min_metric, link_to_parent int;
    declare unlink_from_src_id int unsigned default coalesce(unlink_from_src_id_, 1);
    
    select nlt.id, nlt.code into link_id, link_code
    from nodeLinkTypeT nlt join nodeLinkTypeT nltdef on nltdef.isDefault = 'y'
    where nlt.code = coalesce(link_code_, nltdef.code);
    
    drop temporary table if exists nodeLinkUnlinkDeletes;
    drop temporary table if exists nodeLinkUnlinkSrc;
    drop temporary table if exists nodeLinkUnlinkDst;

    drop temporary table if exists __nodeLinkDestination2T;

    create temporary table nodeLinkUnlinkDeletes(
        srcNodeId int unsigned not null,
        dstNodeId int unsigned not null,
        primary key(srcNodeId, dstNodeId)
    );

    create temporary table nodeLinkUnlinkSrc(
        nodeId int unsigned not null primary key,
        nodeTypeId int unsigned not null,
        index(nodeTypeId)
    );
    
    create temporary table nodeLinkUnlinkDst(
        nodeId int unsigned not null primary key,
        nodeTypeId int unsigned not null,
        index(nodeTypeId)
    );

    create temporary table __nodeLinkDestination2T like __nodeLinkDestinationT;
    insert __nodeLinkDestination2T(nodeId) select nodeId from __nodeLinkDestinationT;

    -- add nodes to remove list - child for src_id and parent for dst_id, but with special condition
    insert nodeLinkUnlinkDeletes(srcNodeId, dstNodeId)
    select nl.srcNodeId, nl.dstNodeId
    from nodeLinkT nl
    join nodeT n on nl.srcNodeId = n.id
    join __nodeLinkDestinationT nld on nl.dstNodeId = nld.nodeId
    left join nodeRootT nr on n.id = nr.id
    where nl.nodeLinkTypeId = link_id
        and nl.metric = 1
        and nr.isRoot is null
        and (unlink_from_src_id or n.id != src_id_)
        and exists (
            select 1 
            from nodeLinkT nl1
            where nl1.nodeLinkTypeId = link_id
                and nl1.srcNodeId = src_id_
                and nl1.dstNodeId = nl.srcNodeId
                and nl1.metric > 0)
    -- check if node in question is linked to other nodes of different types
        and (select count(distinct n21.nodeTypeId) 
            from nodeLinkT nl21
            join nodeT n21 on nl21.srcNodeId = n21.id
            where nl21.nodeLinkTypeId = link_id
                and nl21.dstNodeId = nl.srcNodeId
                and nl21.metric = 1) > 
            (select count(distinct n22.nodeTypeId)
            from nodeLinkForkSrcV nl22
            join nodeT n22 on nl22.srcNodeId = n22.id
            join __nodeLinkDestination2T nld2 on nl22.dstNodeId2 = nld2.nodeId
            where nl22.nodeLinkTypeId = link_id
                and nl22.srcNodeId != src_id_
                and nl22.dstNodeId1 = nl.srcNodeId
                and nl22.metric1 = 1
                and nl22.metric2 = 1);

    -- fill in table with all parents
    insert nodeLinkUnlinkSrc(nodeId, nodeTypeId) 
    select n.id, n.nodeTypeId
    from nodeT n
    left join nodeRootT nr on n.id = nr.id
    where n.id = src_id_
        and nr.isRoot is null
        and unlink_from_src_id;
    
    -- insert only intermediate nodes - children for src and parents for dst
    insert nodeLinkUnlinkSrc(nodeId, nodeTypeId)
    select nl.midNodeId, anl.srcNodeTypeId
    from nodeLinkChainTransitionV nl
    join allowedNodeLinkByNodeV anl on nl.midNodeId = anl.srcNodeId and nl.dstNodeId = anl.dstNodeId
    join nodeT n on nl.midNodeId = n.id
    join __nodeLinkDestinationT nld on nl.dstNodeId = nld.nodeId
    left join nodeRootT nr on n.id = nr.id
    where nl.nodeLinkTypeId = link_id
        and nl.srcNodeId = src_id_
        and anl.singleSrc = 'y'
        and nr.isRoot is null;

    -- fill in table with all children
    insert nodeLinkUnlinkDst(nodeId, nodeTypeId) 
    select n.id, n.nodeTypeId
    from nodeT n
    join __nodeLinkDestinationT nld on n.id = nld.nodeId;
    
    insert nodeLinkUnlinkDst(nodeId, nodeTypeId)
    select nl.dstNodeId, anl.dstNodeTypeId
    from nodeLinkT nl
    join allowedNodeLinkByNodeV anl on nl.srcNodeId = anl.srcNodeId and nl.dstNodeId = anl.dstNodeId
    join __nodeLinkDestinationT nld on nl.srcNodeId = nld.nodeId
    where nl.nodeLinkTypeId = link_id
        and nl.metric = 1
        and anl.singleSrc = 'y';

    -- add src to dst link if allowed
    insert ignore nodeLinkUnlinkDeletes(srcNodeId, dstNodeId)
    select nl.srcNodeId, nl.dstNodeId
    from nodeLinkT nl 
    join __nodeLinkDestinationT nld on nl.dstNodeId = nld.nodeId
    where nl.nodeLinkTypeId = link_id
        and nl.srcNodeId = src_id_
        and unlink_from_src_id;

    -- fill all valid combinations from src and dst sets, so all singleSrc='y' nodes will be unlinked
    insert ignore nodeLinkUnlinkDeletes(srcNodeId, dstNodeId)
    select nl.srcNodeId, nl.dstNodeId
    from nodeLinkT nl 
    join nodeLinkUnlinkSrc src on nl.srcNodeId = src.nodeId
    join nodeLinkUnlinkDst dst on nl.dstNodeId = dst.nodeId
    join allowedNodeLinkT anl on src.nodeTypeId = anl.srcNodeTypeId and dst.nodeTypeId = anl.dstNodeTypeId
    where nl.nodeLinkTypeId = link_id
        and nl.metric = 1
        and anl.singleSrc = 'y';

    -- go and unlink
    set @1 = nodeLink_UpdateStart();
    
    insert zzzDevNullLinkT(n)
    select __nodeLink_Delete(link_code, nlud.srcNodeId, nlud.dstNodeId, null)
    from nodeLinkUnlinkDeletes nlud;

    -- FORWARD: __node_LinkDefaults is defined in _03_node
    -- set @2 = __node_LinkDefaults(link_code, dst_ id_);
    
    set @3 = nodeLink_UpdateEnd();
    
    drop temporary table nodeLinkUnlinkDeletes;
    drop temporary table nodeLinkUnlinkSrc;
    drop temporary table nodeLinkUnlinkDst;

    drop temporary table __nodeLinkDestination2T;
    
    return 1;
end$$$


create function nodeLink_Unlink(
    link_code_ varchar(30) binary, 
    src_id_ int unsigned, 
    dst_id_ int unsigned
) returns int unsigned
comment 'unlinks src_id and dst_id, links dst_id to parents of src_id'
not deterministic modifies sql data
begin
    declare res int unsigned default 1;

    if src_id_ = dst_id_ then
        return 1;
    end if;

    set @1 = __nodeLink_DestinationStart();
    set @2 = __nodeLink_DestinationAdd(dst_id_);
    set res = __nodeLink_Unlink(link_code_, src_id_, 1);
    set @3 = __nodeLink_DestinationEnd();

    return res;
end$$$


create function nodeLink_Unlink2(
    link_code_ varchar(30) binary, 
    src_id_ int unsigned, 
    dst_id_ int unsigned
) returns int unsigned
comment 'calls nodeLink_Unlink and returns dst_id'
not deterministic modifies sql data
begin
    set @1 = nodeLink_Unlink(link_code_, src_id_, dst_id_);
    return dst_id_;
end$$$

-- ---------------------------------
-- nodeLink relocating nodes in tree
-- ---------------------------------

create function nodeLink_RelocateStart() returns int unsigned
not deterministic modifies sql data
begin
    set @1 = __nodeLink_DestinationStart();
    return 1;
end$$$


create function nodeLink_RelocateAddNode(node_id_ int unsigned) returns int unsigned
not deterministic modifies sql data
begin
    set @1 = __nodeLink_DestinationAdd(node_id_);
    return 1;
end$$$


create function nodeLink_RelocateEnd(new_parent_node_id_ int unsigned) returns int unsigned
not deterministic modifies sql data
begin
    -- __nodeLink_DestinationStart is called in nodeLink_RelocateStart

    insert zzzDevNullT(n)
    select __nodeLink_Unlink(null, nr.id, 0)
    from nodeRootT nr;

    set @1 = __nodeLink_Relink(null, new_parent_node_id_);
    set @2 = __nodeLink_DestinationEnd();
    return 1;
end$$$


