-- --------------------------------------------------------------------------------
-- _110_search Group Routines
-- @__searchId - current search session
-- --------------------------------------------------------------------------------
DELIMITER $$$


create function search_CurrentId() returns bigint unsigned
deterministic
return @__searchId$$$


create function search_Reset() returns int unsigned
not deterministic modifies sql data
begin
    delete from searchCriterionT where sessionId = search_CurrentId();
    delete from searchSessionT where added < date_sub(now(), interval 1 day);
    return 1;
end$$$


create function search_StartSession(sid_ varchar(100) binary, reset_params_ int unsigned) returns bigint unsigned
not deterministic modifies sql data
begin
    insert searchSessionT(id, sid)
    values(uuid_short(), sid_)
    on duplicate key update searchSessionT.added = now();

    select id into @__searchId from searchSessionT where sid = sid_;

    set @1 = coalesce(reset_params_, 1) and search_Reset();

    return search_CurrentId();
end$$$


create function __search_SetTag(tag_id_ int unsigned, force_tag_ int) returns int unsigned
not deterministic modifies sql data
begin
    declare tag_id, node_type_id int unsigned;
    declare internal_type_id varchar(10) binary;
    declare force_tag int default coalesce(force_tag_, 0);

    select
        t.id, if(nt.searchAnd = 'y', null, nt.id), nt.internalTypeId -- attributes must have searchAnd = 'y'
    into
        tag_id, node_type_id, internal_type_id
    from tagT t
    join nodeT n on t.id = n.id
    join nodeTypeT nt on n.nodeTypeId = nt.id
    where t.id = tag_id_
        and (force_tag or t.canSearch = 'y');

    insert ignore searchCriterionT(sessionId, nodeId, nodeTypeId, internalTypeId)
    select search_CurrentId(), tag_id, node_type_id, internal_type_id
    from dual
    where tag_id is not null;

    return tag_id;
end$$$


create function search_SetTag(tag_id_ int unsigned) returns int unsigned
not deterministic modifies sql data
begin
    set @1 = __search_SetTag(tag_id_, null);
    return 1;
end$$$


create function search_SetAttributeNumeric(
    attr_id_ int unsigned, 
    value_low_ decimal(22, 6), 
    value_high_ decimal(22, 6)
) returns int unsigned
not deterministic modifies sql data
begin
    declare attr_id int unsigned default __search_SetTag(attr_id_, null);

    insert searchCriterionNumericT(
        sessionId, 
        nodeId, 
        valueLow, 
        valueHigh)
    select 
        search_CurrentId(), 
        a.id, 
        __attribute_ClampValueNumeric(attr_id, value_low_), 
        __attribute_ClampValueNumeric(attr_id, value_high_)
    from attributeT a
    where a.id = attr_id
        and attr_id is not null
        and a.dataTypeId = 'numeric'
    on duplicate key update
        searchCriterionNumericT.valueLow = __attribute_ClampValueNumeric(attr_id, value_low_),
        searchCriterionNumericT.valueHigh = __attribute_ClampValueNumeric(attr_id, value_high_);

    return 1;
end$$$


create function search_SetAttributeDatetime(
    attr_id_ int unsigned, 
    value_low_ datetime, 
    value_high_ datetime
) returns int unsigned
not deterministic modifies sql data
begin
    declare attr_id int unsigned default __search_SetTag(attr_id_, null);

    insert searchCriterionDatetimeT(
        sessionId, 
        nodeId, 
        valueLow, 
        valueHigh)
    select 
        search_CurrentId(), 
        a.id, 
        __attribute_ClampValueDatetime(attr_id, value_low_), 
        __attribute_ClampValueDatetime(attr_id, value_high_)
    from attributeT a
    where a.id = attr_id
        and attr_id is not null
        and a.dataTypeId = 'datetime'
    on duplicate key update
        searchCriterionDatetimeT.valueLow = __attribute_ClampValueDatetime(attr_id, value_low_),
        searchCriterionDatetimeT.valueHigh = __attribute_ClampValueDatetime(attr_id, value_high_);

    return 1;
end$$$


create function __search_RectifyCriteria(session_id_ bigint unsigned) returns int unsigned
not deterministic modifies sql data
begin
    drop temporary table if exists searchRectifyCriteriaDeletes;
    create temporary table searchRectifyCriteriaDeletes(
        nodeId int unsigned not null primary key
    ) engine=MEMORY;

--  remove tags whose parents are searchable but not included into the list
/*    insert ignore searchRectifyCriteriaDeletes(nodeId)
    select sc.nodeId
    from searchCriterionT sc
    where sc.sessionId = session_id_
        and exists (
            select 1 
            from nodeLinkOwnsV nlo1 
            join tagT t1 on nlo1.srcNodeId = t1.id
            where nlo1.dstNodeId = sc.nodeId
                and nlo1.metric = 1
                and t1.canSearch = 'y')
        and not exists (
            select 1 
            from nodeLinkOwnsV nlo2 
            join tagT t2 on nlo2.srcNodeId = t2.id
            join searchCriterionT absentc2 on t2.id = absentc2.nodeId
            where nlo2.dstNodeId = sc.nodeId
                and nlo2.metric = 1
                and t2.canSearch = 'y'
                and sc.sessionId = absentc2.sessionId);
*/
-- empty attribute criteria
    insert ignore searchRectifyCriteriaDeletes(nodeId)
    select scn.nodeId
    from searchCriterionNumericT scn
    where scn.sessionId = session_id_
        and scn.valueLow is null
        and scn.valueHigh is null;

    insert ignore searchRectifyCriteriaDeletes(nodeId)
    select scd.nodeId
    from searchCriterionDatetimeT scd
    where scd.sessionId = session_id_
        and scd.valueLow is null
        and scd.valueHigh is null;

-- remove unneeded criteria
    delete from searchCriterionT
    using searchCriterionT
    join searchRectifyCriteriaDeletes srcd on searchCriterionT.nodeId = srcd.nodeId
    where searchCriterionT.sessionId = session_id_;

    drop temporary table searchRectifyCriteriaDeletes;

    return 1;
end$$$


create function __search_RectifyCriteriaPost(session_id_ bigint unsigned) returns int unsigned
not deterministic modifies sql data
begin
    delete from searchCriterionT
    using searchCriterionT
    join tagT tag on searchCriterionT.nodeId = tag.id
    left join searchResultT sr on searchCriterionT.nodeId = sr.nodeId and searchCriterionT.sessionId = sr.sessionId
    where searchCriterionT.sessionId = session_id_
        and sr.nodeId is null;

    return 1;
end$$$


create function __search_SetDefaultResult(session_id_ bigint unsigned) returns int unsigned
not deterministic modifies sql data
begin 
    insert searchResultT(sessionId, nodeId, collapsed)
    select session_id_, t.id, 'y'
    from tagV t
    join nodeRootLinkV nrl on t.id = nrl.dstNodeId and nrl.metric = 1
    where t.canSearch = 'y'
        and (nrl.typeRequiredForObject = 'y' or t.typeRequiredForObject = 'y')
        and nrl.displayNode = 'y'
        and not exists(
            select 1
            from nodeLinkChainTransitionV nl
            where nl.nodeLinkTypeId = nrl.nodeLinkTypeId
                and nl.srcNodeId = nrl.id
                and nl.dstNodeId = t.id)
    on duplicate key update collapsed = 'y';

    return 1;
end$$$


-- must be called after __search_RectifyCriteria
create function __search_CheckRequiredForObject(session_id_ bigint unsigned) returns int unsigned
comment 'must be called after __search_RectifyCriteria'
not deterministic
begin
    declare session_id_ bigint unsigned default search_CurrentId();
    declare total_required, criterion_required, parent_required int unsigned;

    select count(*) into total_required
    from nodeTypeT
    where requiredForObject = 'y';

    select count(distinct nt.id) into criterion_required
    from searchCriterionT sc
    join nodeT n on sc.nodeId = n.id
    join nodeTypeT nt on n.nodeTypeId = nt.id
    where sc.sessionId = session_id_
        and nt.canSearch = 'y'
        and nt.requiredForObject = 'y';

    select count(distinct nt.id) into parent_required
    from searchCriterionT sc
    join nodeLinkOwnsV nlo on sc.nodeId = nlo.dstNodeId
    join nodeT n on nlo.srcNodeId = n.id
    join nodeTypeT nt on n.nodeTypeId = nt.id
    where sc.sessionId = session_id_
        and nlo.metric = 1
        and nt.canSearch = 'n'
        and nt.requiredForObject = 'y';

    return (criterion_required + parent_required) = total_required;
end$$$


create procedure __search_SearchTags(
    session_id_ bigint unsigned, 
    count_notnull_ int, 
    count_null_ int)
modifies sql data
begin
-- add searchable tags which don't have parents
    insert ignore searchResultT(sessionId, nodeId)
    select session_id_, n.id
    from nodeT n
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'
    join nodeTypeT nt on n.nodeTypeId = nt.id
    join tagT t on n.id = t.id and t.canSearch = 'y'
    where nt.requiredForObject = 'n'
        and not exists(
            select 1
            from nodeLinkT nl
            join nodeT nonrootnode on nl.srcNodeId = nonrootnode.id
            join tagT nonroottag on nonrootnode.id = nonroottag.id
            where nl.nodeLinkTypeId = nlt.id
                and nl.dstNodeId = n.id
                and nl.metric = 1
                and nonrootnode.isRoot = 'n');

    insert ignore searchResultT(sessionId, nodeId)
    select session_id_, n.id
    from nodeT n
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'
    join tagT t on n.id = t.id and t.canSearch = 'y'
    where 1=1
-- if link exists at all
        and exists(
            select 1
            from nodeLinkT nl1
            join searchCriterionT sc1 on nl1.srcNodeId = sc1.nodeId
            where sc1.sessionId = session_id_
                and nl1.nodeLinkTypeId = nlt.id
                and nl1.dstNodeId = n.id
                and nl1.metric = 1)
-- if properly linked to all non "searchAnd" tags
        and (select count(distinct n2.nodeTypeId)
                from nodeLinkT nl2
                join tagT t2 on nl2.srcNodeId = t2.id
                join nodeT n2 on t2.id = n2.id
                where nl2.nodeLinkTypeId = nlt.id
                    and nl2.dstNodeId = n.id
                    and nl2.metric = 1
                    and t2.canSearch = 'y') 
            =
            (select count(distinct sc3.nodeTypeId)
                from nodeLinkT nl3
                join searchCriterionT sc3 on nl3.srcNodeId = sc3.nodeId
                where sc3.sessionId = session_id_
                    and sc3.nodeTypeId is not null
                    and nl3.nodeLinkTypeId = nlt.id
                    and nl3.dstNodeId = n.id
                    and nl3.metric = 1);
-- if properly linked to all "searchAnd" tags
/*        and (select count(*)
                from nodeLinkDirectV nl5
                join searchCriterionT sc5 on nl5.srcNodeId = sc5.nodeId
                where sc5.sessionId = session_id_
                    and sc5.nodeTypeId is null
                    and nl5.nodeLinkTypeId = nlt.id
                    and nl5.dstNodeId = n.id
                    and nl5.metric = 1)
            = count_null_;*/
end$$$


create procedure __search_SearchObjects(
    session_id_ bigint unsigned, 
    count_notnull_ int, 
    sum_null_ double, 
    sum_null_tolerance_ double,
    count_numerics_ int, 
    count_datetimes_ int)
modifies sql data
begin
    insert ignore searchResultT(sessionId, nodeId)
    select session_id_, n.id
    from nodeT n
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'
    join objectT o on n.id = o.id
    join objectStatusT os on o.statusId = os.id
    where os.public = 'y'
-- if link exists at all
--        and exists(
--            select 1
--            from nodeLinkT nl1
--            join searchCriterionT sc1 on nl1.srcNodeId = sc1.nodeId
--            where sc1.sessionId = session_id_
--                and nl1.nodeLinkTypeId = nlt.id
--                and nl1.dstNodeId = n.id)
-- if properly linked to all non "searchAnd" tags
        and count_notnull_ =
            (select count(distinct sc3.nodeTypeId)
                from nodeLinkT nl3
                join searchCriterionT sc3 on nl3.srcNodeId = sc3.nodeId
                where sc3.sessionId = session_id_
                    and sc3.nodeTypeId is not null
                    and sc3.internalTypeId = 'tag'
                    and nl3.nodeLinkTypeId = nlt.id
                    and nl3.dstNodeId = n.id
                    and nl3.metric = 1)
-- if properly linked to all "searchAnd" tags and the sum of all factors greater than some threshold
        and (sum_null_ - sum_null_tolerance_) <
            coalesce(
                (select sum(t5.factor)
                from nodeLinkT nl5
                join searchCriterionT sc5 on nl5.srcNodeId = sc5.nodeId
                join tagT t5 on sc5.nodeId = t5.id
                where sc5.sessionId = session_id_
                    and sc5.nodeTypeId is null
                    and sc5.internalTypeId = 'tag'
                    and nl5.nodeLinkTypeId = nlt.id
                    and nl5.dstNodeId = n.id
                    and nl5.metric = 1),
                0)
-- if properly linked to all numeric tags
        and count_numerics_ = 
            (select count(*)
                from nodeLinkT nl7
                join searchCriterionNumericT scn7 on nl7.srcNodeId = scn7.nodeId
                join attributeValueNumericT avn7 on scn7.nodeId = avn7.attributeId
                where scn7.sessionId = session_id_
                    and nl7.nodeLinkTypeId = nlt.id
                    and nl7.dstNodeId = n.id
                    and nl7.metric = 1
                    and avn7.objectId = n.id
                    and avn7.value >= coalesce(scn7.valueLow, avn7.value)
                    and avn7.value <= coalesce(scn7.valueHigh, avn7.value))
-- if properly linked to all datetime tags
        and count_datetimes_ = 
            (select count(*)
                from nodeLinkT nl9
                join searchCriterionDatetimeT scd9 on nl9.srcNodeId = scd9.nodeId
                join attributeValueDatetimeT avd9 on scd9.nodeId = avd9.attributeId
                where scd9.sessionId = session_id_
                    and nl9.nodeLinkTypeId = nlt.id
                    and nl9.dstNodeId = n.id
                    and nl9.metric = 1
                    and avd9.objectId = n.id
                    and avd9.value >= coalesce(scd9.valueLow, avd9.value)
                    and avd9.value <= coalesce(scd9.valueHigh, avd9.value));
end$$$


create procedure __search_UpdateExtraObjects(session_id_ bigint unsigned)
modifies sql data
begin
    drop temporary table if exists searchUpdateExtraObjectsData;
    create temporary table searchUpdateExtraObjectsData(
        tagId int unsigned not null primary key,
        cnt int unsigned not null default 0
    ) engine=MEMORY;
    
    insert searchUpdateExtraObjectsData(tagId, cnt)
    select tagresult.nodeId, 0
    from searchResultT tagresult
    where tagresult.sessionId = session_id_;
    
-- all searchAnd tags
    insert searchUpdateExtraObjectsData(tagId, cnt)
    select tagresult.nodeId, 1
    from searchResultT tagresult
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'
    join tagT t on tagresult.nodeId = t.id
    join nodeT n on t.id = n.id
    join nodeTypeT nt on n.nodeTypeId = nt.id
    where tagresult.sessionId = session_id_
        and nt.searchAnd = 'y'
        and exists (select 1 
                from nodeLinkT nl 
                join searchResultT objectresult on nl.dstNodeId = objectresult.nodeId
                join objectT o on objectresult.nodeId = o.id
                where nl.nodeLinkTypeId = nlt.id 
                    and nl.srcNodeId = t.id 
                    and nl.metric = 1
                    and objectresult.sessionId = tagresult.sessionId)
    on duplicate key update cnt = 1;

-- all non searchAnd tags, whose type is not selected
    insert searchUpdateExtraObjectsData(tagId, cnt)
    select tagresult.nodeId, 1
    from searchResultT tagresult
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'
    join tagT t on tagresult.nodeId = t.id
    join nodeT n on t.id = n.id
    join nodeTypeT nt on n.nodeTypeId = nt.id
    where tagresult.sessionId = session_id_
        and nt.searchAnd = 'n'
        and exists (select 1 
                    from nodeLinkT nl 
                    join searchResultT objectresult on nl.dstNodeId = objectresult.nodeId
                    join objectT o on objectresult.nodeId = o.id
                    where objectresult.sessionId = tagresult.sessionId
                        and nl.nodeLinkTypeId = nlt.id 
                        and nl.srcNodeId = t.id 
                        and nl.metric = 1)
        and not exists (select 1
                    from searchCriterionT criteria
                    where criteria.sessionId = tagresult.sessionId
                        and criteria.nodeTypeId = nt.id)
    on duplicate key update cnt = 1;
    
-- all non searchAnd tags, whose type is selected, regardless if there are any objects
    insert searchUpdateExtraObjectsData(tagId, cnt)
    select tagresult.nodeId, 1
    from searchResultT tagresult
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'
    join tagT t on tagresult.nodeId = t.id
    join nodeT n on t.id = n.id
    join nodeTypeT nt on n.nodeTypeId = nt.id
    where tagresult.sessionId = session_id_
        and nt.searchAnd = 'n'
        and exists (select 1
                    from searchCriterionT criteria
                    where criteria.sessionId = tagresult.sessionId
                        and criteria.nodeTypeId = nt.id)
    on duplicate key update cnt = 1;

    update searchResultT sr join searchUpdateExtraObjectsData sueod on sr.nodeId = sueod.tagId set
        sr.extraObjects = sueod.cnt
    where sr.sessionId = session_id_;

    drop temporary table searchUpdateExtraObjectsData;
end$$$


create procedure __search_UpdateSortCache(session_id_ bigint unsigned)
modifies sql data
begin
    drop temporary table if exists searchUpdateSortCacheData;
    create temporary table searchUpdateSortCacheData(
        objectId int unsigned not null,
        attributeId int unsigned not null,
        primary key(objectId, attributeId)
    ) engine=MEMORY;

    insert searchUpdateSortCacheData(objectId, attributeId)
    select o.id, sas.id
    from searchResultV sr
    join objectT o on sr.nodeId = o.id
    join searchAttributeSortV sas on 1=1
    left join cacheObjectSortT os on o.id = os.objectId and sas.id = os.attributeId
    where os.objectId is null;
    
    insert zzzDevNullSearchT(n)
    select __object_UpdateSortCache(suscd.objectId, suscd.attributeId, null)
    from searchUpdateSortCacheData suscd;

    drop temporary table searchUpdateSortCacheData;
end$$$


create procedure search_Search()
modifies sql data
begin
    declare session_id bigint unsigned default search_CurrentId();
    declare 
        count_notnull, 
        count_null, 
        count_numerics, 
        count_datetimes int; 
--        search_objects int;
    declare sum_null double;

    start transaction;
    
    set @1 = __search_RectifyCriteria(session_id);

    select count(distinct sc.nodeTypeId) into count_notnull
    from searchCriterionT sc
    where sc.sessionId = session_id
        and sc.nodeTypeId is not null
        and sc.internalTypeId = 'tag';

    select count(*), coalesce(sum(t.factor), 0) into count_null, sum_null
    from searchCriterionT sc
    join tagT t on sc.nodeId = t.id
    where sc.sessionId = session_id
        and sc.nodeTypeId is null
        and sc.internalTypeId = 'tag';

    select count(*) into count_numerics
    from searchCriterionT sc
    join searchCriterionNumericT scn on sc.sessionId = scn.sessionId and sc.nodeId = scn.nodeId
    where sc.sessionId = session_id;

    select count(*) into count_datetimes
    from searchCriterionT sc
    join searchCriterionDatetimeT scd on sc.sessionId = scd.sessionId and sc.nodeId = scd.nodeId
    where sc.sessionId = session_id;

--    set search_objects = __search_CheckRequiredForObject(session_id);

    delete from searchResultT where sessionId = session_id;

    call __search_SearchTags(session_id, count_notnull, count_null);

    set @2 = __search_SetDefaultResult(session_id);
    
    set @3 = __search_RectifyCriteriaPost(session_id);
    
    -- CONST: sum_null_tolerance_ is 1
    call __search_SearchObjects(session_id, count_notnull, sum_null, 1, count_numerics, count_datetimes);
    
    call __search_UpdateExtraObjects(session_id);
    
    call __search_UpdateSortCache(session_id);
    
    commit;

end$$$

