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


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


create function __search_ResetSession(session_id_ bigint unsigned) returns int unsigned
not deterministic modifies sql data
begin
    delete from searchCriterionT where sessionId = session_id_;

    return 1;
end$$$


create function search_Reset() returns int unsigned
not deterministic modifies sql data
begin
    set @1 = __search_ResetSession(search_CurrentId());
    
    -- delete those sessions which don't have corresponding records in searchSavedT
    delete from searchSessionT
    using searchSessionT
    left join searchSavedT ss on searchSessionT.id = ss.sessionId
    where ss.sessionId is null
        and searchSessionT.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
    declare session_id bigint unsigned default (select sessionId from searchSessionSidT where sid = sid_);

    if session_id is null then
        insert searchSessionT(added) values(now()); 

        set session_id = last_insert_id();

        insert searchSessionSidT(sessionId, sid) values(session_id, sid_);
    else 
        update searchSessionT set 
            added = now()
        where id = session_id;
    end if;

    set @__searchId = session_id;

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

    return session_id;
end$$$


create function __search_SetTag(
    session_id_ bigint unsigned, 
    node_id_ int unsigned, 
    force_node_ int,
    return_node_id_ int
) returns bigint unsigned
not deterministic modifies sql data
begin
    declare node_id, node_type_id int unsigned;
    declare internal_type_id varchar(10) binary;
    declare force_node int default coalesce(force_node_, 0);
    declare criterion_id bigint unsigned;

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

    if node_id is not null then 
        insert ignore searchCriterionT(sessionId, nodeId, nodeTypeId, internalTypeId)
        values (session_id_, node_id, node_type_id, internal_type_id);

        -- cannot use last_insert_id because it won't work if inserting duplicate values for sessionId and nodeId
        select sc.id into criterion_id from searchCriterionT sc where sc.sessionId = session_id_ and sc.nodeId = node_id; 
    end if;
    
    return if(coalesce(return_node_id_, 0), node_id_, criterion_id);
end$$$


create function search_SetTag(node_id_ int unsigned) returns int unsigned
not deterministic modifies sql data
begin
    set @1 = __search_SetTag(search_CurrentId(), node_id_, null, 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 criterion_id bigint unsigned default __search_SetTag(search_CurrentId(), attr_id_, null, null);

    insert searchCriterionNumericT(
        searchCriterionId,
        valueLow, 
        valueHigh)
    select 
        criterion_id,
        __attribute_ClampValueNumeric(attr_id_, value_low_), 
        __attribute_ClampValueNumeric(attr_id_, value_high_)
    from attributeT a
    where a.id = attr_id_
        and criterion_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 criterion_id bigint unsigned default __search_SetTag(search_CurrentId(), attr_id_, null, null);

    insert searchCriterionDatetimeT(
        searchCriterionId, 
        valueLow, 
        valueHigh)
    select 
        criterion_id,
        __attribute_ClampValueDatetime(attr_id_, value_low_), 
        __attribute_ClampValueDatetime(attr_id_, value_high_)
    from attributeT a
    where a.id = attr_id_
        and criterion_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_SetAttributePrice(
    attr_id_ int unsigned, 
    currency_id_ varchar(4) binary,
    value_low_ decimal(22, 6), 
    value_high_ decimal(22, 6)
) returns int unsigned
not deterministic modifies sql data
begin
    declare criterion_id bigint unsigned default __search_SetTag(search_CurrentId(), attr_id_, null, null);

    insert searchCriterionPriceT(
        searchCriterionId,
        currencyId,
        valueLow, 
        valueHigh)
    select 
        criterion_id,
        currency_id_,
        __attribute_ClampValuePrice(attr_id_, value_low_), 
        __attribute_ClampValuePrice(attr_id_, value_high_)
    from attributeT a
    where a.id = attr_id_
        and criterion_id is not null
        and a.dataTypeId = 'price'
    on duplicate key update
        searchCriterionPriceT.currencyId = currency_id_,
        searchCriterionPriceT.valueLow = __attribute_ClampValuePrice(attr_id_, value_low_),
        searchCriterionPriceT.valueHigh = __attribute_ClampValuePrice(attr_id_, value_high_);

    return 1;
end$$$


create function __search_RectifyCriteria(session_id_ bigint unsigned) returns int unsigned
not deterministic modifies sql data
begin
    -- insert parent nodes into criteria for those nodes, whose linking is singleSrc=y
    drop temporary table if exists searchRectifyCriteriaInserts;
    create temporary table searchRectifyCriteriaInserts(
        nodeId int unsigned not null primary key
    );
    
    insert ignore searchRectifyCriteriaInserts(nodeId)
    select nl.srcNodeId
    from searchCriterionT sc
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'
    join tagT dst_tag on sc.nodeId = dst_tag.id  -- only tags and attributes
    join nodeLinkT nl on nlt.id = nl.nodeLinkTypeId and sc.nodeId = nl.dstNodeId
    join tagT src_tag on nl.srcNodeId = src_tag.id
    join allowedNodeLinkByNodeV anl on nl.srcNodeId = anl.srcNodeId and nl.dstNodeId = anl.dstNodeId
    left join searchCriterionT exicriterion on sc.sessionId = exicriterion.sessionId 
        and nl.srcNodeId = exicriterion.nodeId
    where sc.sessionId = session_id_
        and nl.metric = 1
        and anl.singleSrc = 'y'
        and exicriterion.nodeId is null;
        
    insert ignore zzzDevNullSearchT(n)
    select __search_SetTag(session_id_, srci.nodeId, null, 1)
    from searchRectifyCriteriaInserts srci;

    drop temporary table searchRectifyCriteriaInserts;
    
    -- remove not needed nodes from criteria
    drop temporary table if exists searchRectifyCriteriaDeletes;
    create temporary table searchRectifyCriteriaDeletes(
        nodeId int unsigned not null primary key
    );

    -- empty attribute criteria
    insert ignore searchRectifyCriteriaDeletes(nodeId)
    select sc.nodeId
    from searchCriterionNumericT scn
    join searchCriterionT sc on scn.searchCriterionId = sc.id
    where sc.sessionId = session_id_
        and scn.valueLow is null
        and scn.valueHigh is null;

    insert ignore searchRectifyCriteriaDeletes(nodeId)
    select sc.nodeId
    from searchCriterionDatetimeT scd
    join searchCriterionT sc on scd.searchCriterionId = sc.id
    where sc.sessionId = session_id_
        and scd.valueLow is null
        and scd.valueHigh is null;

    insert ignore searchRectifyCriteriaDeletes(nodeId)
    select sc.nodeId
    from searchCriterionPriceT scp
    join searchCriterionT sc on scp.searchCriterionId = sc.id
    where sc.sessionId = session_id_
        and scp.valueLow is null
        and scp.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_SetCachedResult(session_id_ bigint unsigned) returns int unsigned
not deterministic modifies sql data
begin
    insert ignore searchResultT(sessionId, nodeId)
    select session_id_, nodeId
    from cacheSearchDefaultResultT;

    return 1;
end$$$


create function __search_SetDefaultResult(session_id_ bigint unsigned) returns int unsigned
not deterministic modifies sql data
begin 
    insert ignore searchResultT(sessionId, nodeId, collapsed)
    select session_id_, o.id, 'n'
    from objectT o
    join searchCriterionT sc on o.id = sc.nodeId
    join objectStatusT os on o.statusId = os.id
    where sc.sessionId = session_id_
        and os.public = '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 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
    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'
    join nodeLinkT nl1 on nl1.nodeLinkTypeId = nlt.id
        and nl1.dstNodeId = n.id
        and nl1.metric = 1
    join searchCriterionT sc1 on nl1.srcNodeId = sc1.nodeId and sc1.sessionId = session_id_
    left join nodeLinkT nl2 on nl2.nodeLinkTypeId = nlt.id
        and nl2.dstNodeId = n.id
        and nl2.metric = 1
    left join tagT t2 on nl2.srcNodeId = t2.id
        and t2.canSearch = 'y'
    left join nodeT n2 on t2.id = n2.id
    group by n.id
    having count(distinct n2.nodeTypeId) = count(distinct sc1.nodeTypeId);
end$$$


create procedure __search_Prepare(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
    );

    drop temporary table if exists searchObjectTemp;
    create temporary table searchObjectTemp(objectId int unsigned not null primary key);
    
    insert searchUpdateExtraObjectsData(tagId, cnt)
    select tagresult.nodeId, 0
    from searchResultT tagresult
    where tagresult.sessionId = session_id_;
end$$$


create procedure __search_Finalize(session_id_ bigint unsigned)
modifies sql data
begin
    update searchResultT sr join searchUpdateExtraObjectsData sueod on sr.nodeId = sueod.tagId set
        sr.extraObjects = sueod.cnt
    where sr.sessionId = session_id_;

    drop temporary table searchObjectTemp;

    drop temporary table searchUpdateExtraObjectsData;
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,
    count_prices_ int)
modifies sql data
begin
    declare limit_dataset int unsigned default 
        if(
            count_notnull_ > 0, 
            constI('search.limit.criteria.notempty'), 
            constI('search.limit.criteria.empty'));

    declare count_numerics int default greatest(count_numerics_, 1);
    declare count_datetimes int default greatest(count_datetimes_, 1);
    declare count_prices int default greatest(count_prices_, 1);

    if count_notnull_ > 0 then

        -- NOTE: searchObjectTemp created in __search_Prepare
        insert searchObjectTemp(objectId)
        select 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

        join nodeLinkT strict_criterion_link on nlt.id = strict_criterion_link.nodeLinkTypeId
            and n.id = strict_criterion_link.dstNodeId
            and strict_criterion_link.metric = 1
        join searchCriterionT strict_criterion on strict_criterion_link.srcNodeId = strict_criterion.nodeId                        
            and strict_criterion.nodeTypeId is not null
            and strict_criterion.internalTypeId = 'tag'

        where strict_criterion.sessionId = session_id_
            and os.public = 'y'

        group by n.id

        having count(distinct strict_criterion.nodeTypeId) = count_notnull_
        order by o.rating desc, n.changed desc
        limit limit_dataset;

    else

        -- NOTE: searchObjectTemp created in __search_Prepare
        insert searchObjectTemp(objectId)
        select 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'

        group by n.id

        order by o.rating desc, n.changed desc
        limit limit_dataset;

    end if;

    insert ignore searchResultT(sessionId, nodeId, relevancy)
    select 
        session_id_, 
        sot.objectId, 
        (
            (coalesce(
                sum(nonstrict_tag.factor) / greatest(count(nonstrict_tag.id) / greatest(count(distinct nonstrict_tag.id), 1.0), 1.0), 
                0) / sum_null_ -- sum_null_ must be sanitized to be != 0
            + (count(distinct scn.searchCriterionId) / count_numerics)
            + (count(distinct scd.searchCriterionId) / count_datetimes)
            + (count(distinct scp.searchCriterionId) / count_datetimes))
        / 4) relevancy
    from searchObjectTemp sot
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'

    left join nodeLinkT nonstrict_criterion_link on nlt.id = nonstrict_criterion_link.nodeLinkTypeId 
        and sot.objectId  = nonstrict_criterion_link.dstNodeId 
        and nonstrict_criterion_link.metric = 1
    left join searchCriterionT nonstrict_criterion on nonstrict_criterion_link.srcNodeId = nonstrict_criterion.nodeId 
        and nonstrict_criterion.sessionId = session_id_
        and nonstrict_criterion.nodeTypeId is null
        and nonstrict_criterion.internalTypeId = 'tag'
    left join tagT nonstrict_tag on nonstrict_criterion.nodeId = nonstrict_tag.id

    left join attributeValueNumericT avn on sot.objectId = avn.objectId
    left join searchCriterionT scn_ on avn.attributeId = scn_.nodeId and scn_.sessionId = session_id_
    left join searchCriterionNumericT scn on scn_.id = scn.searchCriterionId
        and avn.value >= coalesce(scn.valueLow, avn.value)
        and avn.value <= coalesce(scn.valueHigh, avn.value)

    left join attributeValueDatetimeT avd on sot.objectId = avd.objectId
    left join searchCriterionT scd_ on avd.attributeId = scd_.nodeId and scd_.sessionId = session_id_
    left join searchCriterionDatetimeT scd on scd_.id = scd.searchCriterionId
        and avd.value >= coalesce(scd.valueLow, avd.value)
        and avd.value <= coalesce(scd.valueHigh, avd.value)

    left join attributeValuePriceT avp on sot.objectId = avp.objectId
    left join searchCriterionT scp_ on avp.attributeId = scp_.nodeId and scp_.sessionId = session_id_
    left join searchCriterionPriceT scp on scp_.id = scp.searchCriterionId and avp.currencyId = scp.currencyId
        and avp.value >= coalesce(scp.valueLow, avp.value)
        and avp.value <= coalesce(scp.valueHigh, avp.value)

    group by sot.objectId;
end$$$


create procedure __search_UpdateExtraObjectsCriteria(session_id_ bigint unsigned)
modifies sql data
begin
    insert searchUpdateExtraObjectsData(tagId, cnt)
    select sr.nodeId, 1
    from searchResultT sr
    join searchCriterionT criterion on sr.sessionId = criterion.sessionId and sr.nodeId = criterion.nodeId
    where sr.sessionId = session_id_
    on duplicate key update
        searchUpdateExtraObjectsData.cnt = 1;
end$$$


create procedure __search_UpdateExtraObjectsSimple(session_id_ bigint unsigned)
modifies sql data
begin
    insert searchUpdateExtraObjectsData(tagId, cnt)
    select result_node.id, 1
    from searchResultT sr
    join tagT result_tag on sr.nodeId = result_tag.id and sr.sessionId = session_id_
    join nodeT result_node on sr.nodeId = result_node.id
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'

    join nodeLinkT result_link on nlt.id = result_link.nodeLinkTypeId
        and result_node.id = result_link.srcNodeId
        and result_link.metric = 1

    join nodeT object_node on result_link.dstNodeId = object_node.id
    join searchResultT object_result on sr.sessionId = object_result.sessionId and object_node.id = object_result.nodeId
    join objectT object on object_node.id = object.id
    join objectStatusT os on object.statusId = os.id and os.public = 'y'

    left join searchCriterionT no_criterion on sr.sessionId = no_criterion.sessionId and sr.nodeId = no_criterion.nodeId

    where no_criterion.nodeId is null

    group by result_node.id
    on duplicate key update
        searchUpdateExtraObjectsData.cnt = 1;
end$$$


create procedure __search_UpdateExtraObjectsComplex(session_id_ bigint unsigned)
modifies sql data
begin
    insert searchUpdateExtraObjectsData(tagId, cnt)
    select result_node.id, 1
    from searchResultT sr
    join tagT result_tag on sr.nodeId = result_tag.id and sr.sessionId = session_id_
    join nodeT result_node on sr.nodeId = result_node.id
    join nodeTypeT result_node_type on result_node.nodeTypeId = result_node_type.id and result_node_type.searchAnd = 'n'
    join nodeLinkTypeT nlt on nlt.isDefault = 'y'

    join nodeLinkT result_link on nlt.id = result_link.nodeLinkTypeId
        and result_node.id = result_link.srcNodeId
        and result_link.metric = 1

    join nodeT object_node on result_link.dstNodeId = object_node.id
    join objectT object on object_node.id = object.id
    join objectStatusT os on object.statusId = os.id and os.public = 'y'

    join nodeLinkT criterion_link on nlt.id = criterion_link.nodeLinkTypeId
        and object_node.id = criterion_link.dstNodeId
        and criterion_link.metric = 1
    join searchCriterionT criterion on sr.sessionId = criterion.sessionId
        and criterion_link.srcNodeId = criterion.nodeId
        and criterion.nodeTypeId != result_node.nodeTypeId

    join searchCriterionT source_criterion on sr.sessionId = source_criterion.sessionId
        and source_criterion.nodeTypeId is not null
        and source_criterion.internalTypeId = 'tag'
        and source_criterion.nodeTypeId != result_node.nodeTypeId

    join searchCriterionT type_criterion on sr.sessionId = type_criterion.sessionId and result_node.nodeTypeId = type_criterion.nodeTypeId

    left join searchCriterionT no_criterion on sr.sessionId = no_criterion.sessionId and sr.nodeId = no_criterion.nodeId

    where no_criterion.nodeId is null

    group by result_node.id
    having count(distinct source_criterion.nodeTypeId) = count(distinct criterion.nodeTypeId)
    on duplicate key update
        searchUpdateExtraObjectsData.cnt = 1;
end$$$


create procedure __search_UpdateExtraObjects(session_id_ bigint unsigned)
modifies sql data
begin
    call __search_UpdateExtraObjectsCriteria(session_id_);

    call __search_UpdateExtraObjectsSimple(session_id_);

    call __search_UpdateExtraObjectsComplex(session_id_);
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)
    );

    insert ignore 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.sortId = os.sortOptionId
    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,
        count_prices 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(*), greatest(coalesce(sum(t.factor), 0.001), 0.001) 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.id = scn.searchCriterionId
    where sc.sessionId = session_id;

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

    select count(*) into count_prices
    from searchCriterionT sc
    join searchCriterionPriceT scp on sc.id = scp.searchCriterionId
    where sc.sessionId = session_id;

    -- set search_objects = __search_CheckRequiredForObject(session_id);

    delete from searchResultT where sessionId = session_id;

    set @1 = __search_SetCachedResult(session_id);

    call __search_SearchTags(session_id, count_notnull, count_null);

    set @2 = __search_SetDefaultResult(session_id);
    
    set @3 = __search_RectifyCriteriaPost(session_id);
    
    call __search_Prepare(session_id);

    -- CONST: sum_null_tolerance_ is 0.001
    call __search_SearchObjects(session_id, count_notnull, sum_null, 0.001, count_numerics, count_datetimes, count_prices);

    call __search_UpdateExtraObjects(session_id);

    call __search_UpdateSortCache(session_id);

    call __search_Finalize(session_id);

    commit;

end$$$


create function __search_UpdateResultCache() returns int unsigned
not deterministic modifies sql data
begin
    drop temporary table if exists searchUpdateResultCacheData;
    create temporary table searchUpdateResultCacheData(nodeId int unsigned not null primary key);
    
    -- from __search_SetDefaultResult
    insert ignore searchUpdateResultCacheData(nodeId)
    select t.id
    from tagT t
    join nodeT n on t.id = n.id
    join nodeTypeT nt on n.nodeTypeId = nt.id
    join nodeRootLinkV nrl on t.id = nrl.dstNodeId and nrl.metric = 1 and nrl.intermediate = 0
    where t.canSearch = 'y'
        and (nrl.typeRequiredForObject = 'y' or nt.requiredForObject = 'y')
        and nrl.displayNode = 'y';

    -- from __search_SearchTags
    if constS('search.server.version') = '5.5' then
        insert ignore searchUpdateResultCacheData(nodeId)
        select 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 tagT nonroottag on nl.srcNodeId = nonroottag.id
                left join nodeRootT nr on nonroottag.id = nr.id
                where nl.nodeLinkTypeId = nlt.id
                    and nl.dstNodeId = n.id
                    and nl.metric = 1
                    and nr.isRoot is null);
    else 
        insert ignore searchUpdateResultCacheData(nodeId)
        select 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'
        left join (
            select nl.nodeLinkTypeId, nl.dstNodeId
            from nodeLinkT nl 
            join tagT nonroottag on nl.srcNodeId = nonroottag.id
            left join nodeRootT nr on nonroottag.id = nr.id
            where nl.metric = 1
                and nr.isRoot is null
        ) as ne on ne.nodeLinkTypeId = nlt.id and ne.dstNodeId = n.id
        where nt.requiredForObject = 'n'
            and ne.dstNodeId is null;
    end if;

    -- update search result cache table
    delete from cacheSearchDefaultResultT
    using cacheSearchDefaultResultT
    left join searchUpdateResultCacheData surcd on cacheSearchDefaultResultT.nodeId = surcd.nodeId
    where surcd.nodeId is null;

    insert ignore cacheSearchDefaultResultT(nodeId)
    select surcd.nodeId
    from searchUpdateResultCacheData surcd;

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


-- search session duplicate, save, restore and delete

create function __search_DuplicateCriterion(id_ bigint unsigned, new_session_id_ bigint unsigned) returns bigint unsigned
not deterministic modifies sql data
begin
    declare result bigint unsigned;

    insert searchCriterionT(sessionId, nodeId, nodeTypeId, internalTypeId)
    select new_session_id_, sc.nodeId, sc.nodeTypeId, sc.internalTypeId
    from searchCriterionT sc
    where sc.id = id_;

    set result = last_insert_id();

    insert searchCriterionNumericT(searchCriterionId, valueLow, valueHigh)
    select result, scn.valueLow, scn.valueHigh
    from searchCriterionNumericT scn
    where scn.searchCriterionId = id_;

    insert searchCriterionDatetimeT(searchCriterionId, valueLow, valueHigh)
    select result, scd.valueLow, scd.valueHigh
    from searchCriterionDatetimeT scd
    where scd.searchCriterionId = id_;

    insert searchCriterionPriceT(searchCriterionId, currencyId, valueLow, valueHigh)
    select result, scp.currencyId, scp.valueLow, scp.valueHigh
    from searchCriterionPriceT scp
    where scp.searchCriterionId = id_;

    return result;
end$$$


create function __search_DuplicateSession(session_id_ bigint unsigned, dest_session_id_ bigint unsigned) returns bigint unsigned
not deterministic modifies sql data
begin
    declare result bigint unsigned;

    if dest_session_id_ is null then
        insert searchSessionT(added) values(now());

        set result = last_insert_id();
    else
        set result = dest_session_id_;

        set @1 = __search_ResetSession(result);
    end if;

    drop temporary table if exists duplicateSessionCriteria;
    create temporary table duplicateSessionCriteria(id bigint unsigned not null primary key)
    select sc.id
    from searchCriterionT sc
    where sc.sessionId = session_id_;

    insert zzzDevNullBigintSearchT(n)
    select __search_DuplicateCriterion(dsc.id, result)
    from duplicateSessionCriteria dsc;

    drop temporary table duplicateSessionCriteria;

    return result;
end$$$


create function __search_FormatAttributeCriterion(
    attribute_id_ int unsigned, 
    search_criterion_id_ bigint unsigned,
    language_id_ char(3)
) returns varchar(255)
deterministic
begin
    declare result varchar(255) default '-';
    declare decimal_round, price_round int;
    declare dt_format varchar(255);

    set decimal_round = (select decimalRound from attributeNumericT where id = attribute_id_);
    if decimal_round is not null then
        select 
            concat_ws('-', 
                coalesce(round(valueLow, decimal_round), '-'),
                coalesce(round(valueHigh, decimal_round), '-')) 
        into result
        from searchCriterionNumericT 
        where searchCriterionId = search_criterion_id_;
    else
        set price_round = (select priceRound from attributePriceT where id = attribute_id_);
        if price_round is not null then 
            select 
                concat_ws('-', 
                    currencyId,
                    coalesce(round(valueLow, price_round), '-'),
                    coalesce(round(valueHigh, price_round), '-'))
            into result
            from searchCriterionPriceT
            where searchCriterionId = search_criterion_id_;
        else
            select outputFormat 
            into dt_format
            from attributeDatetimeT ad 
            join attributeDatetimeFormatTL adfl on ad.datetimeStorageId = adfl.id and adfl.languageId = language_id_
            where ad.id = attribute_id_;

            if dt_format is not null then
                select 
                    concat_ws('-', 
                        coalesce(date_format(valueLow, dt_format), '-'),
                        coalesce(date_format(valueHigh, dt_format), '-')) 
                into result
                from searchCriterionDatetimeT
                where searchCriterionId = search_criterion_id_;
            end if;
        end if;
    end if;

    return result;
end$$$


create function __search_CreateSessionName(session_id_ bigint unsigned, language_id_ char(3)) returns varchar(255)
deterministic
begin
    declare result varchar(255);
    select 
        substring(
            group_concat(
                if(
                    a.id is null, 
                    string_ConcatSmart(' ', tl.uofmPre, tl.name, tl.uofmPost),
                    concat(
                        tl.name, ' = ',
                        tl.uofmPre, 
                        ' (', __search_FormatAttributeCriterion(a.id, sc.id, language_id_), ') ', 
                        tl.uofmPost))
                order by nt.sorter, t.sorter, tl.name
                separator ', '),
            1,
            255) into result
    from searchCriterionT sc
    join nodeT n on sc.nodeId = n.id
    join nodeTypeT nt on n.nodeTypeId = nt.id
    join tagT t on n.id = t.id
    join tagTL tl on t.id = tl.id and tl.languageId = language_id_
    left join attributeT a on t.id = a.id
    where sc.sessionId = session_id_;

    return result;
end$$$


-- saves current search session for the current logged in user
-- if name_ is null, it is automatically created from search criterions
create function search_Save(name_ varchar(255)) returns bigint unsigned
not deterministic modifies sql data
begin
    declare session_id bigint unsigned;
    declare name varchar(255);

    if search_CurrentId() is null or user_CurrentId() is null then
        return 0;
    end if;

    set session_id = __search_DuplicateSession(search_CurrentId(), null);

    set name = coalesce(name_, __search_CreateSessionName(session_id, user_CurrentLanguage()));

    insert searchSavedT(sessionId, userId, name)
    values (session_id, user_CurrentId(), name);

    return session_id;
end$$$


-- restores saved session into the current one, call search_Search after this function
create function search_RestoreSaved(session_id_ bigint unsigned) returns bigint unsigned
not deterministic modifies sql data
begin 
    declare current_session_id bigint unsigned default search_CurrentId();

    if (current_session_id is null) or not exists(select 1 from searchSavedT where sessionId = session_id_) then 
        return 0;
    else
        set @1 = __search_DuplicateSession(session_id_, current_session_id);

        return session_id_;
    end if;
end$$$


create function search_DeleteSaved(session_id_ bigint unsigned) returns int unsigned
not deterministic modifies sql data
begin
    if not exists (select 1 from searchSavedT where sessionId = session_id_ and userId = user_CurrentId()) then
        return 0;
    end if;

    -- deleting from searchSessionT deletes from searchSavedT as well
    delete from searchSessionT where id = session_id_;

    return 1;
end$$$


create function search_UpdateSaved(session_id_ bigint unsigned, name_ varchar(255)) returns bigint unsigned
not deterministic modifies sql data
begin
    update searchSavedT set
        name = coalesce(name_, name)
    where sessionId = session_id_ and userId = user_CurrentId();

    return if(row_count() > 0, session_id_, 0);
end$$$
