/*
 ** Zabbix
 ** Copyright (C) 2001-2023 Zabbix SIA
 **
 ** This program is free software; you can redistribute it and/or modify
 ** it under the terms of the GNU General Public License as published by
 ** the Free Software Foundation; either version 2 of the License, or
 ** (at your option) any later version.
 **
 ** This program is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 ** GNU General Public License for more details.
 **
 ** You should have received a copy of the GNU General Public License
 ** along with this program; if not, write to the Free Software
 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 **/

#include "zbxserver.h"

#include "log.h"
#include "zbxcachevalue.h"
#include "evalfunc.h"
#include "zbxeval.h"
#include "expression.h"
#include "zbxnum.h"
#include "zbxparam.h"
#include "zbxsysinfo.h"
#include "zbx_host_constants.h"
#include "zbx_item_constants.h"

#define ZBX_ITEM_QUERY_UNSET		0x0000

#define ZBX_ITEM_QUERY_HOST_SELF	0x0001
#define ZBX_ITEM_QUERY_HOST_ONE		0x0002
#define ZBX_ITEM_QUERY_HOST_ANY		0x0004

#define ZBX_ITEM_QUERY_KEY_ONE		0x0010
#define ZBX_ITEM_QUERY_KEY_SOME		0x0020
#define ZBX_ITEM_QUERY_KEY_ANY		0x0040
#define ZBX_ITEM_QUERY_FILTER		0x0100

#define ZBX_ITEM_QUERY_ERROR		0x8000

#define ZBX_ITEM_QUERY_MANY		(ZBX_ITEM_QUERY_HOST_ANY |\
					ZBX_ITEM_QUERY_KEY_SOME | ZBX_ITEM_QUERY_KEY_ANY |\
					ZBX_ITEM_QUERY_FILTER)

#define ZBX_ITEM_QUERY_ITEM_ANY		(ZBX_ITEM_QUERY_HOST_ANY | ZBX_ITEM_QUERY_KEY_ANY)

/* one item query data - index in hostkeys items */
typedef struct
{
	int	dcitem_hk_index;
}
zbx_expression_query_one_t;

/* many items query data - matching itemids */
typedef struct
{
	zbx_vector_uint64_t	itemids;
}
zbx_expression_query_many_t;

/* expression item query */
typedef struct
{
	/* query flags, see ZBX_ITEM_QUERY_* defines */
	zbx_uint32_t		flags;

	/* the item query /host/key?[filter] */
	zbx_item_query_t	ref;

	/* the query error */
	char			*error;

	/* the expression item query data, zbx_expression_query_one_t or zbx_expression_query_many_t */
	void			*data;
}
zbx_expression_query_t;

/* group - hostids cache */
typedef struct
{
	char			*name;
	zbx_vector_uint64_t	hostids;
}
zbx_expression_group_t;

/* item - tags cache */
typedef struct
{
	zbx_uint64_t		itemid;
	zbx_vector_ptr_t	tags;
}
zbx_expression_item_t;

static void	expression_query_free_one(zbx_expression_query_one_t *query)
{
	zbx_free(query);
}

static void	expression_query_free_many(zbx_expression_query_many_t *query)
{
	zbx_vector_uint64_destroy(&query->itemids);
	zbx_free(query);
}

static void	expression_query_free(zbx_expression_query_t *query)
{
	zbx_eval_clear_query(&query->ref);

	if (ZBX_ITEM_QUERY_ERROR == query->flags)
		zbx_free(query->error);
	else if (0 != (query->flags & ZBX_ITEM_QUERY_MANY))
		expression_query_free_many((zbx_expression_query_many_t*) query->data);
	else
		expression_query_free_one((zbx_expression_query_one_t*) query->data);

	zbx_free(query);
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if key parameter is a wildcard '*'                          *
 *                                                                            *
 ******************************************************************************/
static int	test_key_param_wildcard_cb(const char *data, int key_type, int level, int num, int quoted,
		void *cb_data, char **param)
{
	ZBX_UNUSED(key_type);
	ZBX_UNUSED(num);
	ZBX_UNUSED(quoted);
	ZBX_UNUSED(param);

	if (0 == level)
		return SUCCEED;

	if ('*' == data[0] && '\0' == data[1])
	{
		*(int *)cb_data = 1;
		return FAIL;
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: create expression item query from item query /host/key?[filter]   *
 *                                                                            *
 * Parameters: itemquery - [IN] the item query                                *
 *                                                                            *
 * Return value: The created expression item query.                           *
 *                                                                            *
 ******************************************************************************/
static zbx_expression_query_t*	expression_create_query(const char *itemquery)
{
	zbx_expression_query_t	*query;

	query = (zbx_expression_query_t *)zbx_malloc(NULL, sizeof(zbx_expression_query_t));
	memset(query, 0, sizeof(zbx_expression_query_t));

	query->flags = ZBX_ITEM_QUERY_UNSET;

	if (0 != zbx_eval_parse_query(itemquery, strlen(itemquery), &query->ref))
	{
		if (NULL == query->ref.host)
			query->flags |= ZBX_ITEM_QUERY_HOST_SELF;
		else if ('*' == *query->ref.host)
			query->flags |= ZBX_ITEM_QUERY_HOST_ANY;
		else
			query->flags |= ZBX_ITEM_QUERY_HOST_ONE;

		if (NULL != query->ref.filter)
			query->flags |= ZBX_ITEM_QUERY_FILTER;

		if ('*' == *query->ref.key)
		{
			query->flags |= ZBX_ITEM_QUERY_KEY_ANY;
		}
		else if (NULL != strchr(query->ref.key, '*'))
		{
			int	wildcard = 0;

			zbx_replace_key_params_dyn(&query->ref.key, ZBX_KEY_TYPE_ITEM, test_key_param_wildcard_cb,
					&wildcard, NULL, 0);

			if (0 != wildcard)
				query->flags |= ZBX_ITEM_QUERY_KEY_SOME;
			else
				query->flags |= ZBX_ITEM_QUERY_KEY_ONE;
		}
		else
			query->flags |= ZBX_ITEM_QUERY_KEY_ONE;
	}

	return query;
}

static void	expression_group_free(zbx_expression_group_t *group)
{
	zbx_free(group->name);
	zbx_vector_uint64_destroy(&group->hostids);
	zbx_free(group);
}

/******************************************************************************
 *                                                                            *
 * Purpose: get group from cache by name                                      *
 *                                                                            *
 * Parameters: eval - [IN] the evaluation data                                *
 *             name - [IN] the group name                                     *
 *                                                                            *
 * Return value: The cached group.                                            *
 *                                                                            *
 * Comments: Cache group if necessary.                                        *
 *                                                                            *
 ******************************************************************************/
static zbx_expression_group_t	*expression_get_group(zbx_expression_eval_t *eval, const char *name)
{
	int			i;
	zbx_expression_group_t	*group;

	for (i = 0; i < eval->groups.values_num; i++)
	{
		group = (zbx_expression_group_t *)eval->groups.values[i];

		if (0 == strcmp(group->name, name))
			return group;
	}

	group = (zbx_expression_group_t *)zbx_malloc(NULL, sizeof(zbx_expression_group_t));
	group->name = zbx_strdup(NULL, name);
	zbx_vector_uint64_create(&group->hostids);
	zbx_dc_get_hostids_by_group_name(name, &group->hostids);
	zbx_vector_ptr_append(&eval->groups, group);

	return group;
}

/******************************************************************************
 *                                                                            *
 * Purpose: get item from cache by itemid                                     *
 *                                                                            *
 * Parameters: eval    - [IN] the evaluation data                             *
 *             itemid  - [IN] the item identifier                             *
 *                                                                            *
 * Return value: The cached item.                                             *
 *                                                                            *
 * Comments: Cache item if necessary.                                         *
 *                                                                            *
 ******************************************************************************/
static zbx_expression_item_t	*expression_get_item(zbx_expression_eval_t *eval, zbx_uint64_t itemid)
{
	int		i;
	zbx_expression_item_t	*item;

	for (i = 0; i < eval->itemtags.values_num; i++)
	{
		item = (zbx_expression_item_t *)eval->itemtags.values[i];

		if (item->itemid == itemid)
			return item;
	}

	item = (zbx_expression_item_t *)zbx_malloc(NULL, sizeof(zbx_expression_item_t));
	item->itemid = itemid;
	zbx_vector_ptr_create(&item->tags);
	zbx_dc_get_item_tags(itemid, &item->tags);
	zbx_vector_ptr_append(&eval->itemtags, item);

	return item;
}

static void	expression_item_free(zbx_expression_item_t *item)
{
	zbx_vector_ptr_clear_ext(&item->tags, (zbx_clean_func_t) zbx_free_item_tag);
	zbx_vector_ptr_destroy(&item->tags);
	zbx_free(item);
}

/******************************************************************************
 *                                                                            *
 * Purpose: initialize one item query                                         *
 *                                                                            *
 * Parameters: eval  - [IN] the evaluation data                               *
 *             query - [IN] the query to initialize                           *
 *                                                                            *
 ******************************************************************************/
static void	expression_init_query_one(zbx_expression_eval_t *eval, zbx_expression_query_t *query)
{
	zbx_expression_query_one_t	*data;

	data = (zbx_expression_query_one_t *)zbx_malloc(NULL, sizeof(zbx_expression_query_one_t));
	data->dcitem_hk_index = eval->one_num++;
	query->data = data;
}

/******************************************************************************
 *                                                                            *
 * Purpose: replace wildcards '*'in key parameters with % and escape existing *
 *          %, \ characters for SQL like operation                            *
 *                                                                            *
 ******************************************************************************/
static int	replace_key_param_wildcard_cb(const char *data, int key_type, int level, int num, int quoted, void *cb_data,
		char **param)
{
	char	*tmp;

	ZBX_UNUSED(key_type);
	ZBX_UNUSED(num);
	ZBX_UNUSED(cb_data);

	if (0 == level)
		return SUCCEED;

	if ('*' == data[0] && '\0' == data[1])
	{
		*param = zbx_strdup(NULL, "%");
		return SUCCEED;
	}

	if (NULL == strchr(data, '%') && NULL == strchr(data, '\\'))
		return SUCCEED;

	tmp = zbx_strdup(NULL, data);
	zbx_unquote_key_param(tmp);
	*param = zbx_dyn_escape_string(tmp, "\\%%");
	zbx_free(tmp);

	/* escaping cannot result in unquotable parameter */
	if (FAIL == zbx_quote_key_param(param, quoted))
	{
		THIS_SHOULD_NEVER_HAPPEN;
		zbx_free(*param);
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if item key matches the pattern                             *
 *                                                                            *
 * Parameters: item_key - [IN] the item key to match                          *
 *             pattern  - [IN] the pattern                                    *
 *                                                                            *
 ******************************************************************************/
static int	expression_match_item_key(const char *item_key, const AGENT_REQUEST *pattern)
{
	AGENT_REQUEST	key;
	int		i, ret = FAIL;

	zbx_init_agent_request(&key);

	if (SUCCEED != zbx_parse_item_key(item_key, &key))
		goto out;

	if (pattern->nparam != key.nparam)
		goto out;

	if (0 != strcmp(pattern->key, key.key))
		goto out;

	for (i = 0; i < key.nparam; i++)
	{
		if (0 == strcmp(pattern->params[i], "*"))
			continue;

		if (0 != strcmp(pattern->params[i], key.params[i]))
			goto out;
	}

	ret = SUCCEED;
out:
	zbx_free_agent_request(&key);

	return ret;
}

typedef struct
{
	zbx_uint64_t	itemid;
	zbx_uint64_t	hostid;
	zbx_expression_eval_t	*eval;
}
zbx_expression_eval_many_t;

/******************************************************************************
 *                                                                            *
 * Purpose: get itemids + hostids of items that might match query based on    *
 *          host, key and filter groups                                       *
 *                                                                            *
 * Parameters: eval            - [IN] the evaluation data                     *
 *             query           - [IN] the expression item query               *
 *             groups          - [IN] the groups in filter template           *
 *             filter_template - [IN] the group filter template with {index}  *
 *                                    placeholders referring to a group in    *
 *                                    groups vector                           *
 *             itemhosts       - [out] itemid+hostid pairs matching query     *
 *                                                                            *
 ******************************************************************************/
static void	expression_get_item_candidates(zbx_expression_eval_t *eval, const zbx_expression_query_t *query,
		const zbx_vector_str_t *groups, const char *filter_template, zbx_vector_uint64_pair_t *itemhosts)
{
	DB_RESULT	result;
	DB_ROW		row;
	char		*sql = NULL, *esc, *clause = "where";
	size_t		sql_alloc = 0, sql_offset = 0;
	AGENT_REQUEST	pattern;

	zbx_strcpy_alloc(&sql, &sql_alloc, &sql_offset, "select i.itemid,i.hostid");

	if (0 != (query->flags & ZBX_ITEM_QUERY_KEY_SOME))
	{
		zbx_init_agent_request(&pattern);
		if (SUCCEED != zbx_parse_item_key(query->ref.key, &pattern))
		{
			THIS_SHOULD_NEVER_HAPPEN;
			zbx_free(sql);
			return;
		}

		zbx_strcpy_alloc(&sql, &sql_alloc, &sql_offset, ",i.key_");
	}

	zbx_strcpy_alloc(&sql, &sql_alloc, &sql_offset, " from items i");

	if (0 != (query->flags & ZBX_ITEM_QUERY_HOST_ONE))
	{
		esc = zbx_db_dyn_escape_string(query->ref.host);
		zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, ",hosts h"
				" where h.hostid=i.hostid"
				" and h.host='%s'", esc);
		zbx_free(esc);
		clause = "and";
	}
	else if (0 != (query->flags & ZBX_ITEM_QUERY_HOST_SELF))
	{
		zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, " where i.hostid=" ZBX_FS_UI64,
				eval->hostid);
		clause = "and";
	}

	if (0 != (query->flags & ZBX_ITEM_QUERY_KEY_SOME))
	{
		char	*key;

		key = zbx_strdup(NULL, query->ref.key);
		zbx_replace_key_params_dyn(&key, ZBX_KEY_TYPE_ITEM, replace_key_param_wildcard_cb, NULL, NULL, 0);

		esc = zbx_db_dyn_escape_string(key);
		zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, " %s i.key_ like '%s'", clause, esc);
		zbx_free(esc);
		zbx_free(key);
		clause = "and";
	}
	else if (0 != (query->flags & ZBX_ITEM_QUERY_KEY_ONE))
	{
		esc = zbx_db_dyn_escape_string(query->ref.key);
		zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, " %s i.key_='%s'", clause, esc);
		zbx_free(esc);
		clause = "and";
	}

	if (0 != (query->flags & ZBX_ITEM_QUERY_FILTER) && NULL != filter_template && '\0' != *filter_template)
	{
		zbx_uint64_t		index;
		int			pos = 0, last_pos = 0;
		zbx_token_t		token;
		zbx_expression_group_t	*group;

		zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset, " %s ", clause);

		for (; SUCCEED == zbx_token_find(filter_template, pos, &token, ZBX_TOKEN_SEARCH_FUNCTIONID); pos++)
		{
			if (ZBX_TOKEN_OBJECTID != token.type)
				continue;

			if (SUCCEED != zbx_is_uint64_n(filter_template + token.loc.l + 1, token.loc.r - token.loc.l - 1,
					&index) && (int)index < groups->values_num)
			{
				continue;
			}

			group = expression_get_group(eval, groups->values[index]);

			zbx_strncpy_alloc(&sql, &sql_alloc, &sql_offset, filter_template + last_pos,
					token.loc.l - last_pos);

			if (' ' == sql[sql_offset - 1])
				sql_offset--;

			if (0 < group->hostids.values_num)
			{
				zbx_db_add_condition_alloc(&sql, &sql_alloc, &sql_offset, "i.hostid", group->hostids.values,
						group->hostids.values_num);
			}
			else
				zbx_strcpy_alloc(&sql, &sql_alloc, &sql_offset, " 1=0");

			last_pos = token.loc.r + 1;
			pos = token.loc.r;
		}

		if ('\0' != filter_template[last_pos])
			zbx_strcpy_alloc(&sql, &sql_alloc, &sql_offset, filter_template + last_pos);
	}

	result = zbx_db_select("%s", sql);

	while (NULL != (row = zbx_db_fetch(result)))
	{
		zbx_uint64_pair_t	pair;

		if (0 == (query->flags & ZBX_ITEM_QUERY_KEY_SOME) ||
				(NULL != pattern.key && SUCCEED == expression_match_item_key(row[2], &pattern)))
		{
			ZBX_STR2UINT64(pair.first, row[0]);
			ZBX_STR2UINT64(pair.second, row[1]);
			zbx_vector_uint64_pair_append(itemhosts, pair);
		}
	}
	zbx_db_free_result(result);

	if (0 != (query->flags & ZBX_ITEM_QUERY_KEY_SOME))
		zbx_free_agent_request(&pattern);

	zbx_free(sql);
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if the item matches the tag                                 *
 *                                                                            *
 * Parameters: item - [IN] the item with tags                                 *
 *             tag  - [IN] the tag to match in format <tag name>[:<tag value>]*
 *                                                                            *
 * Return value: SUCCEED - the item matches the specified tag                 *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	expression_item_check_tag(zbx_expression_item_t *item, const char *tag)
{
	int		i;
	size_t		taglen;
	const char	*value;

	if (NULL != (value = strchr(tag, ':')))
	{
		taglen = (value - tag);
		value++;
	}
	else
		taglen = strlen(tag);

	for (i = 0; i < item->tags.values_num; i++)
	{
		zbx_item_tag_t	*itemtag = (zbx_item_tag_t *)item->tags.values[i];

		if (taglen != strlen(itemtag->tag.tag) || 0 != memcmp(tag, itemtag->tag.tag, taglen))
			continue;

		if (NULL == value)
			return SUCCEED;

		if (0 == strcmp(itemtag->tag.value, value))
			return SUCCEED;
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate filter function                                          *
 *                                                                            *
 * Parameters: name     - [IN] the function name (not zero terminated)        *
 *             len      - [IN] the function name length                       *
 *             args_num - [IN] the number of function arguments               *
 *             args     - [IN] an array of the function arguments.            *
 *             data     - [IN] the caller data used for function evaluation   *
 *             ts       - [IN] the function execution time                    *
 *             value    - [OUT] the function return value                     *
 *             error    - [OUT] the error message if function failed          *
 *                                                                            *
 * Return value: SUCCEED - the function was evaluated successfully            *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: The group/tag comparisons in filter are converted to function    *
 *           calls that are evaluated by this callback.                       *
 *                                                                            *
 ******************************************************************************/
static int	expression_eval_filter(const char *name, size_t len, int args_num, const zbx_variant_t *args,
		void *data, const zbx_timespec_t *ts, zbx_variant_t *value, char **error)
{
	zbx_expression_eval_many_t	*many = (zbx_expression_eval_many_t *)data;

	ZBX_UNUSED(ts);
	ZBX_UNUSED(len);

	if (1 != args_num)
	{
		*error = zbx_strdup(NULL, "invalid number of arguments");
		return FAIL;
	}

	if (ZBX_VARIANT_STR != args[0].type)
	{
		*error = zbx_strdup(NULL, "invalid argument flags");
		return FAIL;
	}

	if (0 == strncmp(name, "group", ZBX_CONST_STRLEN("group")))
	{
		zbx_expression_group_t *group;

		group = expression_get_group(many->eval, args[0].data.str);

		if (FAIL != zbx_vector_uint64_bsearch(&group->hostids, many->hostid, ZBX_DEFAULT_UINT64_COMPARE_FUNC))
			zbx_variant_set_dbl(value, 1);
		else
			zbx_variant_set_dbl(value, 0);

		return SUCCEED;
	}
	else if (0 == strncmp(name, "tag", ZBX_CONST_STRLEN("tag")))
	{
		zbx_expression_item_t	*item;

		item = expression_get_item(many->eval, many->itemid);

		if (SUCCEED == expression_item_check_tag(item, args[0].data.str))
			zbx_variant_set_dbl(value, 1);
		else
			zbx_variant_set_dbl(value, 0);

		return SUCCEED;
	}
	else
	{
		*error = zbx_strdup(NULL, "unknown function");
		return FAIL;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: initialize many item query                                        *
 *                                                                            *
 * Parameters: eval    - [IN] the evaluation data                             *
 *             query   - [IN] the query to initialize                         *
 *                                                                            *
 ******************************************************************************/
static void	expression_init_query_many(zbx_expression_eval_t *eval, zbx_expression_query_t *query)
{
	zbx_expression_query_many_t	*data;
	char				*error = NULL, *errmsg = NULL, *filter_template = NULL;
	int				i, ret = FAIL;
	zbx_eval_context_t		ctx;
	zbx_vector_uint64_pair_t	itemhosts;
	zbx_vector_str_t		groups;
	zbx_vector_uint64_t		itemids;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() /%s/%s?[%s]", __func__, ZBX_NULL2EMPTY_STR(query->ref.host),
			ZBX_NULL2EMPTY_STR(query->ref.key), ZBX_NULL2EMPTY_STR(query->ref.filter));

	zbx_eval_init(&ctx);

	zbx_vector_uint64_create(&itemids);
	zbx_vector_uint64_pair_create(&itemhosts);
	zbx_vector_str_create(&groups);

	if (ZBX_ITEM_QUERY_ITEM_ANY == (query->flags & ZBX_ITEM_QUERY_ITEM_ANY))
	{
		error = zbx_strdup(NULL, "item query must have at least a host or an item key defined");
		goto out;
	}

	if (0 != (query->flags & ZBX_ITEM_QUERY_FILTER))
	{
		if (SUCCEED != zbx_eval_parse_expression(&ctx, query->ref.filter, ZBX_EVAL_PARSE_QUERY_EXPRESSION,
				&errmsg))
		{
			error = zbx_dsprintf(NULL, "failed to parse item query filter: %s", errmsg);
			zbx_free(errmsg);
			goto out;
		}

		zbx_eval_prepare_filter(&ctx);

		if (FAIL == zbx_eval_get_group_filter(&ctx, &groups, &filter_template, &errmsg))
		{
			error = zbx_dsprintf(NULL, "failed to extract groups from item filter: %s", errmsg);
			zbx_free(errmsg);
			goto out;
		}
	}

	expression_get_item_candidates(eval, query, &groups, filter_template, &itemhosts);

	if (0 != (query->flags & ZBX_ITEM_QUERY_FILTER))
	{
		zbx_expression_eval_many_t	eval_data;
		zbx_variant_t		filter_value;

		eval_data.eval = eval;

		for (i = 0; i < itemhosts.values_num; i++)
		{
			eval_data.itemid = itemhosts.values[i].first;
			eval_data.hostid = itemhosts.values[i].second;

			if (SUCCEED != zbx_eval_execute_ext(&ctx, NULL, expression_eval_filter, NULL, (void *)&eval_data,
					&filter_value, &errmsg))
			{
				zabbix_log(LOG_LEVEL_DEBUG, "failed to evaluate item query filter: %s", errmsg);
				zbx_free(errmsg);
				continue;
			}

			if (SUCCEED != zbx_variant_convert(&filter_value, ZBX_VARIANT_DBL))
			{
				zabbix_log(LOG_LEVEL_DEBUG, "unexpected item query filter evaluation result:"
						" value:\"%s\" of type \"%s\"", zbx_variant_value_desc(&filter_value),
						zbx_variant_type_desc(&filter_value));

				zbx_variant_clear(&filter_value);
				continue;
			}

			if (SUCCEED != zbx_double_compare(filter_value.data.dbl, 0))
				zbx_vector_uint64_append(&itemids, eval_data.itemid);
		}
	}
	else
	{
		for (i = 0; i < itemhosts.values_num; i++)
			zbx_vector_uint64_append(&itemids, itemhosts.values[i].first);
	}

	if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG))
	{
		for (i = 0; i < itemids.values_num; i++)
			zabbix_log(LOG_LEVEL_DEBUG, "%s() itemid:" ZBX_FS_UI64, __func__, itemids.values[i]);
	}

	data = (zbx_expression_query_many_t *)zbx_malloc(NULL, sizeof(zbx_expression_query_many_t));
	data->itemids = itemids;
	query->data = data;
	eval->many_num++;

	ret = SUCCEED;
out:
	if (0 != (query->flags & ZBX_ITEM_QUERY_FILTER) && SUCCEED == zbx_eval_status(&ctx))
		zbx_eval_clear(&ctx);

	if (SUCCEED != ret)
	{
		query->error = error;
		query->flags = ZBX_ITEM_QUERY_ERROR;
		zbx_vector_uint64_destroy(&itemids);
	}

	zbx_free(filter_template);

	zbx_vector_uint64_pair_destroy(&itemhosts);

	zbx_vector_str_clear_ext(&groups, zbx_str_free);
	zbx_vector_str_destroy(&groups);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() items:%d", __func__, (SUCCEED == ret ? data->itemids.values_num : -1));
}

/******************************************************************************
 *                                                                            *
 * Purpose: cache items used in one item queries                              *
 *                                                                            *
 * Parameters: eval - [IN] the evaluation data                                *
 *                                                                            *
 ******************************************************************************/
static void	expression_cache_dcitems_hk(zbx_expression_eval_t *eval)
{
	int	i;

	eval->hostkeys = (zbx_host_key_t *)zbx_malloc(NULL, sizeof(zbx_host_key_t) * eval->one_num);
	eval->dcitems_hk = (DC_ITEM *)zbx_malloc(NULL, sizeof(DC_ITEM) * eval->one_num);
	eval->errcodes_hk = (int *)zbx_malloc(NULL, sizeof(int) * eval->one_num);

	for (i = 0; i < eval->queries.values_num; i++)
	{
		zbx_expression_query_t	*query = (zbx_expression_query_t *)eval->queries.values[i];
		zbx_expression_query_one_t	*data;

		if (0 != (query->flags & ZBX_ITEM_QUERY_MANY) || ZBX_ITEM_QUERY_ERROR == query->flags)
			continue;

		data = (zbx_expression_query_one_t *)query->data;

		eval->hostkeys[data->dcitem_hk_index].host = query->ref.host;
		eval->hostkeys[data->dcitem_hk_index].key = query->ref.key;
	}

	DCconfig_get_items_by_keys(eval->dcitems_hk, eval->hostkeys, eval->errcodes_hk, eval->one_num);
}

/******************************************************************************
 *                                                                            *
 * Purpose: dcitem reference vector lookup functions                          *
 *                                                                            *
 ******************************************************************************/
static	int	compare_dcitems_by_itemid(const void *d1, const void *d2)
{
	DC_ITEM	*dci1 = *(DC_ITEM **)d1;
	DC_ITEM	*dci2 = *(DC_ITEM **)d2;

	ZBX_RETURN_IF_NOT_EQUAL(dci1->itemid, dci2->itemid);

	return 0;
}

static int	expression_find_dcitem_by_itemid(const void *d1, const void *d2)
{
	zbx_uint64_t	itemid = **(zbx_uint64_t **)d1;
	DC_ITEM		*dci = *(DC_ITEM **)d2;

	ZBX_RETURN_IF_NOT_EQUAL(itemid, dci->itemid);

	return 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: cache items used in many item queries                             *
 *                                                                            *
 * Parameters: eval - [IN] the evaluation data                                *
 *                                                                            *
 ******************************************************************************/
static void	expression_cache_dcitems(zbx_expression_eval_t *eval)
{
	int			i, j;
	zbx_vector_uint64_t	itemids;

	zbx_vector_uint64_create(&itemids);

	if (0 != eval->one_num)
	{
		for (i = 0; i < eval->one_num; i++)
		{
			if (SUCCEED != eval->errcodes_hk[i])
				continue;

			zbx_vector_ptr_append(&eval->dcitem_refs, &eval->dcitems_hk[i]);
		}

		zbx_vector_ptr_sort(&eval->dcitem_refs, compare_dcitems_by_itemid);
	}

	for (i = 0; i < eval->queries.values_num; i++)
	{
		zbx_expression_query_t	*query = (zbx_expression_query_t *)eval->queries.values[i];
		zbx_expression_query_many_t	*data;

		if (0 == (query->flags & ZBX_ITEM_QUERY_MANY))
			continue;

		data = (zbx_expression_query_many_t *)query->data;

		for (j = 0; j < data->itemids.values_num; j++)
			zbx_vector_uint64_append(&itemids, data->itemids.values[j]);
	}

	zbx_vector_uint64_sort(&itemids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);
	zbx_vector_uint64_uniq(&itemids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);

	for (i = 0; i < itemids.values_num;)
	{
		if (FAIL != zbx_vector_ptr_bsearch(&eval->dcitem_refs, &itemids.values[i], expression_find_dcitem_by_itemid))
		{
			zbx_vector_uint64_remove(&itemids, i);
			continue;
		}
		i++;
	}

	if (0 != (eval->dcitems_num = itemids.values_num))
	{
		zbx_vector_uint64_sort(&itemids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);

		eval->dcitems = (DC_ITEM *)zbx_malloc(NULL, sizeof(DC_ITEM) * itemids.values_num);
		eval->errcodes = (int *)zbx_malloc(NULL, sizeof(int) * itemids.values_num);

		DCconfig_get_items_by_itemids(eval->dcitems, itemids.values, eval->errcodes, itemids.values_num);

		for (i = 0; i < itemids.values_num; i++)
		{
			if (SUCCEED != eval->errcodes[i])
				continue;

			zbx_vector_ptr_append(&eval->dcitem_refs, &eval->dcitems[i]);
		}

		zbx_vector_ptr_sort(&eval->dcitem_refs, compare_dcitems_by_itemid);
	}

	zbx_vector_uint64_destroy(&itemids);
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate historical function for one item query                   *
 *                                                                            *
 * Parameters: eval     - [IN] the evaluation data                            *
 *             query    - [IN] the item query                                 *
 *             name     - [IN] the function name (not zero terminated)        *
 *             len      - [IN] the function name length                       *
 *             args_num - [IN] the number of function arguments               *
 *             args     - [IN] an array of the function arguments.            *
 *             data     - [IN] the caller data used for function evaluation   *
 *             ts       - [IN] the function execution time                    *
 *             value    - [OUT] the function return value                     *
 *             error    - [OUT] the error message if function failed          *
 *                                                                            *
 * Return value: SUCCEED - the function was executed successfully             *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	expression_eval_one(zbx_expression_eval_t *eval, zbx_expression_query_t *query, const char *name,
		size_t len, int args_num, const zbx_variant_t *args, const zbx_timespec_t *ts, zbx_variant_t *value, char **error)
{
	char			func_name[MAX_STRING_LEN], *params = NULL;
	size_t			params_alloc = 0, params_offset = 0;
	DC_ITEM			*item;
	int			i, ret = FAIL;
	zbx_expression_query_one_t	*data;
	DC_EVALUATE_ITEM		evaluate_item;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() %.*s(/%s/%s?[%s],...)", __func__, (int )len, name,
			ZBX_NULL2EMPTY_STR(query->ref.host), ZBX_NULL2EMPTY_STR(query->ref.key),
			ZBX_NULL2EMPTY_STR(query->ref.filter));

	data = (zbx_expression_query_one_t *)query->data;

	if (SUCCEED != eval->errcodes_hk[data->dcitem_hk_index])
	{
		*error = zbx_dsprintf(NULL, "item \"/%s/%s\" does not exist",
				eval->hostkeys[data->dcitem_hk_index].host, eval->hostkeys[data->dcitem_hk_index].key);
		goto out;
	}

	item = &eval->dcitems_hk[data->dcitem_hk_index];

	/* do not evaluate if the item is disabled or belongs to a disabled host */

	if (ITEM_STATUS_ACTIVE != item->status)
	{
		*error = zbx_dsprintf(NULL, "item \"/%s/%s\" is disabled", eval->hostkeys[data->dcitem_hk_index].host,
				eval->hostkeys[data->dcitem_hk_index].key);
		goto out;
	}

	if (HOST_STATUS_MONITORED != item->host.status)
	{
		*error = zbx_dsprintf(NULL, "host \"%s\" is not monitored", eval->hostkeys[data->dcitem_hk_index].host);
		goto out;
	}

	memcpy(func_name, name, len);
	func_name[len] = '\0';

	/* If the item is NOTSUPPORTED then evaluation is allowed for:   */
	/*   - functions white-listed in evaluatable_for_notsupported(). */
	/*     Their values can be evaluated to regular numbers even for */
	/*     NOTSUPPORTED items. */
	/*   - other functions. Result of evaluation is ZBX_UNKNOWN.     */

	if (ITEM_STATE_NOTSUPPORTED == item->state && FAIL == zbx_evaluatable_for_notsupported(func_name))
	{
		/* compose and store 'unknown' message for future use */
		*error = zbx_dsprintf(NULL, "item \"/%s/%s\" is not supported",
				eval->hostkeys[data->dcitem_hk_index].host, eval->hostkeys[data->dcitem_hk_index].key);
		goto out;
	}

	evaluate_item.itemid = item->itemid;
	evaluate_item.value_type = item->value_type;
	evaluate_item.proxy_hostid = item->host.proxy_hostid;
	evaluate_item.host = item->host.host;
	evaluate_item.key_orig = item->key_orig;

	if (0 == args_num)
	{
		ret = evaluate_function(value, &evaluate_item, func_name, "", ts, error);
		goto out;
	}

	for (i = 0; i < args_num; i++)
	{
		if (0 != i)
			zbx_chrcpy_alloc(&params, &params_alloc, &params_offset, ',');

		switch (args[i].type)
		{
			case ZBX_VARIANT_DBL:
				zbx_snprintf_alloc(&params, &params_alloc, &params_offset, ZBX_FS_DBL64,
						args[i].data.dbl);
				break;
			case ZBX_VARIANT_STR:
				zbx_strquote_alloc(&params, &params_alloc, &params_offset, args[i].data.str);
				break;
			case ZBX_VARIANT_UI64:
				zbx_snprintf_alloc(&params, &params_alloc, &params_offset, ZBX_FS_UI64,
						args[i].data.ui64);
				break;
			case ZBX_VARIANT_NONE:
				break;
			default:
				*error = zbx_dsprintf(NULL, " unsupported argument #%d type \"%s\"", i + 1,
						zbx_variant_type_desc(&args[i]));
				goto out;
		}
	}

	ret = evaluate_function(value, &evaluate_item, func_name, ZBX_NULL2EMPTY_STR(params), ts, error);
out:
	zbx_free(params);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s value:%s flags:%s", __func__, zbx_result_string(ret),
			zbx_variant_value_desc(value), zbx_variant_type_desc(value));

	return ret;
}

#define ZBX_VALUE_FUNC_UNKNOWN	0
#define ZBX_VALUE_FUNC_MIN	1
#define ZBX_VALUE_FUNC_AVG	2
#define ZBX_VALUE_FUNC_MAX	3
#define ZBX_VALUE_FUNC_SUM	4
#define ZBX_VALUE_FUNC_COUNT	5
#define ZBX_VALUE_FUNC_LAST	6
#define ZBX_ITEM_FUNC_EXISTS	7
#define ZBX_ITEM_FUNC_ITEMCOUNT	8
#define ZBX_ITEM_FUNC_BPERCENTL	9
#define ZBX_MIXVALUE_FUNC_BRATE	10

#define MATCH_STRING(x, name, len)	ZBX_CONST_STRLEN(x) == len && 0 == memcmp(name, x, len)

static int	get_function_by_name(const char *name, size_t len)
{

	if (MATCH_STRING("avg_foreach", name, len))
		return ZBX_VALUE_FUNC_AVG;

	if (MATCH_STRING("count_foreach", name, len))
		return ZBX_VALUE_FUNC_COUNT;

	if (MATCH_STRING("last_foreach", name, len))
		return ZBX_VALUE_FUNC_LAST;

	if (MATCH_STRING("max_foreach", name, len))
		return ZBX_VALUE_FUNC_MAX;

	if (MATCH_STRING("min_foreach", name, len))
		return ZBX_VALUE_FUNC_MIN;

	if (MATCH_STRING("sum_foreach", name, len))
		return ZBX_VALUE_FUNC_SUM;

	if (MATCH_STRING("exists_foreach", name, len))
		return ZBX_ITEM_FUNC_EXISTS;

	if (MATCH_STRING("item_count", name, len))
		return ZBX_ITEM_FUNC_ITEMCOUNT;

	if (MATCH_STRING("bucket_percentile", name, len))
		return ZBX_ITEM_FUNC_BPERCENTL;

	if (MATCH_STRING("bucket_rate_foreach", name, len))
		return ZBX_MIXVALUE_FUNC_BRATE;

	return ZBX_VALUE_FUNC_UNKNOWN;
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculate minimum value from the history value vector             *
 *                                                                            *
 * Parameters: values      - [IN] a vector containing history values          *
 *             value_type  - [IN] the type of values. Only float/uint64       *
 *                           values are supported.                            *
 *             result      - [OUT] the resulting value                        *
 *                                                                            *
 ******************************************************************************/
static void	evaluate_history_func_min(zbx_vector_history_record_t *values, int value_type, double *result)
{
	int	i;

	if (ITEM_VALUE_TYPE_UINT64 == value_type)
	{
		*result = (double)values->values[0].value.ui64;

		for (i = 1; i < values->values_num; i++)
			if ((double)values->values[i].value.ui64 < *result)
				*result = (double)values->values[i].value.ui64;
	}
	else
	{
		*result = values->values[0].value.dbl;

		for (i = 1; i < values->values_num; i++)
			if (values->values[i].value.dbl < *result)
				*result = values->values[i].value.dbl;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculate maximum value from the history value vector             *
 *                                                                            *
 * Parameters: values      - [IN] a vector containing history values          *
 *             value_type  - [IN] the type of values. Only float/uint64       *
 *                           values are supported.                            *
 *             result      - [OUT] the resulting value                        *
 *                                                                            *
 ******************************************************************************/
static void	evaluate_history_func_max(zbx_vector_history_record_t *values, int value_type, double *result)
{
	int	i;

	if (ITEM_VALUE_TYPE_UINT64 == value_type)
	{
		*result = (double)values->values[0].value.ui64;

		for (i = 1; i < values->values_num; i++)
			if ((double)values->values[i].value.ui64 > *result)
				*result = (double)values->values[i].value.ui64;
	}
	else
	{
		*result = values->values[0].value.dbl;

		for (i = 1; i < values->values_num; i++)
			if (values->values[i].value.dbl > *result)
				*result = values->values[i].value.dbl;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculate sum of values from the history value vector             *
 *                                                                            *
 * Parameters: values      - [IN] a vector containing history values          *
 *             value_type  - [IN] the type of values. Only float/uint64       *
 *                           values are supported.                            *
 *             result      - [OUT] the resulting value                        *
 *                                                                            *
 ******************************************************************************/
static void	evaluate_history_func_sum(zbx_vector_history_record_t *values, int value_type, double *result)
{
	int	i;

	*result = 0;

	if (ITEM_VALUE_TYPE_UINT64 == value_type)
	{
		for (i = 0; i < values->values_num; i++)
			*result += (double)values->values[i].value.ui64;
	}
	else
	{
		for (i = 0; i < values->values_num; i++)
			*result += values->values[i].value.dbl;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculate average value of values from the history value vector   *
 *                                                                            *
 * Parameters: values      - [IN] a vector containing history values          *
 *             value_type  - [IN] the type of values. Only float/uint64       *
 *                           values are supported.                            *
 *             result      - [OUT] the resulting value                        *
 *                                                                            *
 ******************************************************************************/
static void	evaluate_history_func_avg(zbx_vector_history_record_t *values, int value_type, double *result)
{
	evaluate_history_func_sum(values, value_type, result);
	*result /= values->values_num;
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculate number of values in value vector                        *
 *                                                                            *
 * Parameters: values      - [IN] a vector containing history values          *
 *             value_type  - [IN] the type of values. Only float/uint64       *
 *                           values are supported.                            *
 *             result      - [OUT] the resulting value                        *
 *                                                                            *
 ******************************************************************************/
static void	evaluate_history_func_count(zbx_vector_history_record_t *values, double *result)
{
	*result = (double)values->values_num;
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculate the last (newest) value in value vector                 *
 *                                                                            *
 * Parameters: values      - [IN] a vector containing history values          *
 *             result      - [OUT] the resulting value                        *
 *                                                                            *
 ******************************************************************************/
static void	evaluate_history_func_last(zbx_vector_history_record_t *values, int value_type, double *result)
{
	if (ITEM_VALUE_TYPE_UINT64 == value_type)
		*result = (double)values->values[0].value.ui64;
	else
		*result = values->values[0].value.dbl;
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculate function with values from value vector                  *
 *                                                                            *
 * Parameters: values      - [IN] a vector containing history values          *
 *             value_type  - [IN] the type of values. Only float/uint64       *
 *                           values are supported.                            *
 *             func        - [IN] the function to calculate. Only             *
 *                           ZBX_VALUE_FUNC_MIN, ZBX_VALUE_FUNC_AVG,          *
 *                           ZBX_VALUE_FUNC_MAX, ZBX_VALUE_FUNC_SUM,          *
 *                           ZBX_VALUE_FUNC_COUNT, ZBX_VALUE_FUNC_LAST        *
 *                           functions are supported.                         *
 *             result      - [OUT] the resulting value                        *
 *                                                                            *
 ******************************************************************************/
static void	evaluate_history_func(zbx_vector_history_record_t *values, int value_type, int func,
		double *result)
{
	switch (func)
	{
		case ZBX_VALUE_FUNC_MIN:
			evaluate_history_func_min(values, value_type, result);
			break;
		case ZBX_VALUE_FUNC_AVG:
			evaluate_history_func_avg(values, value_type, result);
			break;
		case ZBX_VALUE_FUNC_MAX:
			evaluate_history_func_max(values, value_type, result);
			break;
		case ZBX_VALUE_FUNC_SUM:
			evaluate_history_func_sum(values, value_type, result);
			break;
		case ZBX_VALUE_FUNC_COUNT:
			evaluate_history_func_count(values, result);
			break;
		case ZBX_VALUE_FUNC_LAST:
			evaluate_history_func_last(values, value_type, result);
			break;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: get item from cache by itemid                                     *
 *                                                                            *
 * Parameters: eval    - [IN] the evaluation data                             *
 *             itemid  - [IN] the item identifier                             *
 *                                                                            *
 * Return value: The cached item.                                             *
 *                                                                            *
 ******************************************************************************/
static DC_ITEM	*get_dcitem(zbx_vector_ptr_t *dcitem_refs, zbx_uint64_t itemid)
{
	int	index;

	if (FAIL == (index = zbx_vector_ptr_bsearch(dcitem_refs, &itemid, expression_find_dcitem_by_itemid)))
		return NULL;

	return dcitem_refs->values[index];
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate functions 'exists_foreach' and 'item_count'              *
 *          for multiple items                                                *
 *                                                                            *
 * Parameters: eval      - [IN] the evaluation data                           *
 *             query     - [IN] the calculated item query                     *
 *             item_func - [IN] the function id                               *
 *             value     - [OUT] the function return value                    *
 *                                                                            *
 * Return value: SUCCEED - the function was executed successfully             *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static void	expression_eval_exists(zbx_expression_eval_t *eval, zbx_expression_query_t *query, int item_func,
		zbx_variant_t *value)
{
	zbx_expression_query_many_t	*data;
	int				i;
	zbx_vector_dbl_t		results;

	zbx_vector_dbl_create(&results);

	data = (zbx_expression_query_many_t *)query->data;

	for (i = 0; i < data->itemids.values_num; i++)
	{
		DC_ITEM	*dcitem;

		if (NULL == (dcitem = get_dcitem(&eval->dcitem_refs, data->itemids.values[i])))
			continue;

		if (ITEM_STATUS_ACTIVE != dcitem->status)
			continue;

		if (HOST_STATUS_MONITORED != dcitem->host.status)
			continue;

		zbx_vector_dbl_append(&results, 1);
	}

	if (ZBX_ITEM_FUNC_EXISTS == item_func)
	{
		zbx_vector_dbl_t	*v;

		v = (zbx_vector_dbl_t *)zbx_malloc(NULL, sizeof(zbx_vector_dbl_t));
		*v = results;
		zbx_variant_set_dbl_vector(value, v);
	}
	else
	{
		zbx_variant_set_ui64(value, (zbx_uint64_t)results.values_num);
		zbx_vector_dbl_destroy(&results);
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate functions 'bucket_rate_foreach' for 'histogram_quantile' *
 *          and evaluate functions 'bucket_percentile'                        *
 *                                                                            *
 * Parameters: eval      - [IN] the evaluation data                           *
 *             query     - [IN] the calculated item query                     *
 *             args_num  - [IN] the number of function arguments              *
 *             args      - [IN] an array of the function arguments.           *
 *             data      - [IN] the caller data used for function evaluation  *
 *             ts        - [IN] the function execution time                   *
 *             item_func - [IN] the function id                               *
 *             value     - [OUT] the function return value                    *
 *             error     - [OUT] the error message if function failed         *
 *                                                                            *
 * Return value: SUCCEED - the function was executed successfully             *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	expression_eval_bucket_rate(zbx_expression_eval_t *eval, zbx_expression_query_t *query,
		int args_num, const zbx_variant_t *args, const zbx_timespec_t *ts, int item_func, zbx_variant_t *value,
		char **error)
{
	zbx_expression_query_many_t	*data;
	int				i, pos, ret = FAIL;
	zbx_vector_dbl_t		*results = NULL;
	double				percentage, result;
	char				*param = NULL;
	const char			*log_fn = (ZBX_ITEM_FUNC_BPERCENTL == item_func ?
							"bucket_percentile" : "bucket_rate_foreach");

	if (1 > args_num || 2 < args_num || (ZBX_ITEM_FUNC_BPERCENTL == item_func && 2 != args_num))
	{
		*error = zbx_strdup(NULL, "invalid number of function parameters");
		goto err;
	}

	if (ZBX_VARIANT_STR == args[0].type)
	{
		param = zbx_strdup(NULL, args[0].data.str);
	}
	else
	{
		zbx_variant_t	arg;

		zbx_variant_copy(&arg, &args[0]);

		if (SUCCEED != zbx_variant_convert(&arg, ZBX_VARIANT_STR))
		{
			zbx_variant_clear(&arg);
			*error = zbx_strdup(NULL, "invalid second parameter");
			goto err;
		}

		param = zbx_strdup(NULL, arg.data.str);
		zbx_variant_clear(&arg);
	}

	if (ZBX_ITEM_FUNC_BPERCENTL == item_func)
	{
		if (ZBX_VARIANT_DBL == args[1].type)
		{
			percentage = args[1].data.dbl;
		}
		else
		{
			zbx_variant_t	val_copy;

			zbx_variant_copy(&val_copy, &args[1]);

			if (SUCCEED != zbx_variant_convert(&val_copy, ZBX_VARIANT_DBL))
			{
				zbx_variant_clear(&val_copy);
				*error = zbx_strdup(NULL, "invalid third parameter");
				goto err;
			}

			percentage = val_copy.data.dbl;
		}

		if (100 < percentage || 0 > percentage)
		{
			*error = zbx_strdup(NULL, "invalid value of percentile");
			goto err;
		}

		pos = 1;
	}
	else if (2 == args_num)
	{
		if (ZBX_VARIANT_STR == args[1].type)
		{
			if (SUCCEED != zbx_is_ushort(args[1].data.str, &pos) || 0 >= pos)
			{
				*error = zbx_strdup(NULL, "invalid third parameter");
				goto err;
			}
		}
		else if (ZBX_VARIANT_UI64 == args[1].type)
		{
			if (0 >= (pos = (int)args[1].data.ui64))
			{
				*error = zbx_strdup(NULL, "invalid third parameter");
				goto err;
			}
		}
		else
		{
			*error = zbx_strdup(NULL, "invalid third parameter");
			goto err;
		}
	}
	else
	{
		pos = 1;
	}

	data = (zbx_expression_query_many_t *)query->data;
	results = (zbx_vector_dbl_t *)zbx_malloc(NULL, sizeof(zbx_vector_dbl_t));
	zbx_vector_dbl_create(results);

	for (i = 0; i < data->itemids.values_num; i++)
	{
		DC_ITEM		*dcitem;
		zbx_variant_t	rate;
		double		le;
		char		bucket[ZBX_MAX_DOUBLE_LEN + 1];

		if (NULL == (dcitem = get_dcitem(&eval->dcitem_refs, data->itemids.values[i])))
			continue;

		if (ITEM_STATUS_ACTIVE != dcitem->status)
			continue;

		if (HOST_STATUS_MONITORED != dcitem->host.status)
			continue;

		if (ITEM_VALUE_TYPE_FLOAT != dcitem->value_type && ITEM_VALUE_TYPE_UINT64 != dcitem->value_type)
			continue;

		if (0 != zbx_get_key_param(dcitem->key_orig, pos, bucket, sizeof(bucket)))
			continue;

		zbx_strupper(bucket);

		if (0 == strcmp(bucket, "+INF") || 0 == strcmp(bucket, "INF"))
			le = ZBX_INFINITY;
		else if (SUCCEED != zbx_is_double(bucket, &le))
			continue;

		if (SUCCEED != (ret = zbx_evaluate_RATE(&rate, dcitem, param, ts, error)))
			goto err;

		zbx_vector_dbl_append(results, le);
		zbx_vector_dbl_append(results, rate.data.dbl);
	}

	if (ZBX_MIXVALUE_FUNC_BRATE == item_func)
	{
		zbx_variant_set_dbl_vector(value, results);
		results = NULL;
		ret = SUCCEED;
	}
	else if (ZBX_ITEM_FUNC_BPERCENTL == item_func && SUCCEED == (
			ret = zbx_eval_calc_histogram_quantile(percentage / 100, results, log_fn, &result, error)))
	{
		zbx_variant_set_dbl(value, result);
	}
err:
	zbx_free(param);

	if (NULL != results)
	{
		zbx_vector_dbl_destroy(results);
		zbx_free(results);
	}

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate historical function for multiple items (aggregate checks)*
 *                                                                            *
 * Parameters: eval     - [IN] the evaluation data                            *
 *             query    - [IN] the calculated item query                      *
 *             name     - [IN] the function name (not zero terminated)        *
 *             len      - [IN] the function name length                       *
 *             args_num - [IN] the number of function arguments               *
 *             args     - [IN] an array of the function arguments.            *
 *             data     - [IN] the caller data used for function evaluation   *
 *             ts       - [IN] the function execution time                    *
 *             value    - [OUT] the function return value                     *
 *             error    - [OUT] the error message if function failed          *
 *                                                                            *
 * Return value: SUCCEED - the function was executed successfully             *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	expression_eval_many(zbx_expression_eval_t *eval, zbx_expression_query_t *query, const char *name,
		size_t len, int args_num, const zbx_variant_t *args, const zbx_timespec_t *ts, zbx_variant_t *value,
		char **error)
{
	zbx_expression_query_many_t	*data;
	int				ret = FAIL, item_func, count, seconds, i;
	zbx_vector_history_record_t	values;
	zbx_vector_dbl_t		*results_vector;
	double				result;
	zbx_variant_t			arg;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() %.*s(/%s/%s?[%s],...)", __func__, (int)len, name,
			ZBX_NULL2EMPTY_STR(query->ref.host), ZBX_NULL2EMPTY_STR(query->ref.key),
			ZBX_NULL2EMPTY_STR(query->ref.filter));

	ZBX_UNUSED(args_num);

	data = (zbx_expression_query_many_t *)query->data;
	item_func = get_function_by_name(name, len);

	switch (item_func)
	{
		case ZBX_ITEM_FUNC_EXISTS:
		case ZBX_ITEM_FUNC_ITEMCOUNT:
			if (0 != args_num)
			{
				*error = zbx_strdup(NULL, "invalid number of function parameters");
				goto out;
			}

			expression_eval_exists(eval, query, item_func, value);
			ret = SUCCEED;
			goto out;
		case ZBX_VALUE_FUNC_LAST:
			count = 1;
			seconds = 0;
			break;
		case ZBX_VALUE_FUNC_MIN:
		case ZBX_VALUE_FUNC_AVG:
		case ZBX_VALUE_FUNC_MAX:
		case ZBX_VALUE_FUNC_SUM:
		case ZBX_VALUE_FUNC_COUNT:
			if (1 != args_num)
			{
				*error = zbx_strdup(NULL, "invalid number of function parameters");
				goto out;
			}

			if (ZBX_VARIANT_STR == args[0].type)
			{
				if (FAIL == zbx_is_time_suffix(args[0].data.str, &seconds, ZBX_LENGTH_UNLIMITED))
				{
					*error = zbx_strdup(NULL, "invalid second parameter");
					goto out;
				}
			}
			else
			{
				zbx_variant_copy(&arg, &args[0]);

				if (SUCCEED != zbx_variant_convert(&arg, ZBX_VARIANT_DBL))
				{
					zbx_variant_clear(&arg);
					*error = zbx_strdup(NULL, "invalid second parameter");
					goto out;
				}

				seconds = arg.data.dbl;
				zbx_variant_clear(&arg);
			}
			count = 0;

			break;
		case ZBX_ITEM_FUNC_BPERCENTL:
		case ZBX_MIXVALUE_FUNC_BRATE:
			ret = expression_eval_bucket_rate(eval, query, args_num, args, ts, item_func, value, error);
			goto out;
		default:
			*error = zbx_strdup(NULL, "unsupported function");
			goto out;
	}

	results_vector = (zbx_vector_dbl_t *)zbx_malloc(NULL, sizeof(zbx_vector_dbl_t));
	zbx_vector_dbl_create(results_vector);

	for (i = 0; i < data->itemids.values_num; i++)
	{
		DC_ITEM	*dcitem;

		if (NULL == (dcitem = get_dcitem(&eval->dcitem_refs, data->itemids.values[i])))
			continue;

		if (ITEM_STATUS_ACTIVE != dcitem->status)
			continue;

		if (HOST_STATUS_MONITORED != dcitem->host.status)
			continue;

		if (ITEM_VALUE_TYPE_FLOAT != dcitem->value_type && ITEM_VALUE_TYPE_UINT64 != dcitem->value_type)
			continue;

		zbx_history_record_vector_create(&values);

		if (SUCCEED == zbx_vc_get_values(dcitem->itemid, dcitem->value_type, &values, seconds, count, ts))
		{
			if (0 < values.values_num)
			{
				evaluate_history_func(&values, dcitem->value_type, item_func, &result);
				zbx_vector_dbl_append(results_vector, result);
			}
			else if (ZBX_VALUE_FUNC_COUNT == item_func)
				zbx_vector_dbl_append(results_vector, 0);
		}

		zbx_history_record_vector_destroy(&values, dcitem->value_type);
	}

	zbx_variant_set_dbl_vector(value, results_vector);

	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s value:%s flags:%s", __func__, zbx_result_string(ret),
			zbx_variant_value_desc(value), zbx_variant_type_desc(value));

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate historical function                                      *
 *                                                                            *
 * Parameters: name     - [IN] the function name (not zero terminated)        *
 *             len      - [IN] the function name length                       *
 *             args_num - [IN] the number of function arguments               *
 *             args     - [IN] an array of the function arguments.            *
 *             data     - [IN] the caller data used for function evaluation   *
 *             ts       - [IN] the function execution time                    *
 *             value    - [OUT] the function return value                     *
 *             error    - [OUT] the error message if function failed          *
 *                                                                            *
 * Return value: SUCCEED - the function was executed successfully             *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	expression_eval_history(const char *name, size_t len, int args_num, const zbx_variant_t *args,
		void *data, const zbx_timespec_t *ts, zbx_variant_t *value, char **error)
{
	int			ret = FAIL;
	zbx_expression_eval_t	*eval;
	zbx_expression_query_t	*query;
	char			*errmsg = NULL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() function:%.*s", __func__, (int )len, name);

	zbx_variant_set_none(value);

	if (0 == args_num)
	{
		*error = zbx_strdup(NULL, "Cannot evaluate function: invalid number of arguments");
		goto out;
	}

	if (len >= MAX_STRING_LEN)
	{
		*error = zbx_strdup(NULL, "Cannot evaluate function: name too long");
		goto out;
	}

	eval = (zbx_expression_eval_t *)data;

	/* the historical function item query argument is replaced with corresponding itemrefs index */
	query = (zbx_expression_query_t *)eval->queries.values[(int) args[0].data.ui64];

	if (ZBX_ITEM_QUERY_ERROR == query->flags)
	{
		*error = zbx_dsprintf(NULL, "Cannot evaluate function: %s", query->error);
		goto out;
	}

	if (0 == (query->flags & ZBX_ITEM_QUERY_MANY))
	{
		ret = expression_eval_one(eval, query, name, len, args_num - 1, args + 1, ts, value, &errmsg);
	}
	else if (ZBX_EXPRESSION_AGGREGATE == eval->mode)
	{
		ret = expression_eval_many(eval, query, name, len, args_num - 1, args + 1, ts, value, &errmsg);
	}
	else
	{
		errmsg = zbx_strdup(NULL, "aggregate queries are not supported");
		ret = FAIL;
	}

	if (SUCCEED != ret)
	{
		*error = zbx_dsprintf(NULL, "Cannot evaluate function: %s", errmsg);
		zbx_free(errmsg);
	}
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s error:%s", __func__, zbx_result_string(ret),
			ZBX_NULL2EMPTY_STR(*error));

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate common function                                          *
 *                                                                            *
 * Parameters: name     - [IN] the function name (not zero terminated)        *
 *             len      - [IN] the function name length                       *
 *             args_num - [IN] the number of function arguments               *
 *             args     - [IN] an array of the function arguments.            *
 *             data     - [IN] the caller data used for function evaluation   *
 *             ts       - [IN] the function execution time                    *
 *             value    - [OUT] the function return value                     *
 *             error    - [OUT] the error message if function failed          *
 *                                                                            *
 * Return value: SUCCEED - the function was executed successfully             *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: There are no custom common functions in expressions items, but   *
 *           it's used to check for /host/key query quoting errors instead.   *
 *                                                                            *
 ******************************************************************************/
static int	expression_eval_common(const char *name, size_t len, int args_num, const zbx_variant_t *args,
		void *data, const zbx_timespec_t *ts, zbx_variant_t *value, char **error)
{
	ZBX_UNUSED(data);
	ZBX_UNUSED(ts);
	ZBX_UNUSED(value);

	if (SUCCEED != zbx_is_trigger_function(name, len))
	{
		*error = zbx_strdup(NULL, "Cannot evaluate formula: unsupported function");
		return FAIL;
	}

	if (0 == args_num)
	{
		*error = zbx_strdup(NULL, "Cannot evaluate function: invalid number of arguments");
		return FAIL;
	}

	if (ZBX_VARIANT_STR == args[0].type)
	{
		zbx_item_query_t query;

		if (0 != zbx_eval_parse_query(args[0].data.str, strlen(args[0].data.str), &query))
		{
			zbx_eval_clear_query(&query);
			*error = zbx_strdup(NULL, "Cannot evaluate function: quoted item query argument");
			return FAIL;
		}
	}
	else if (ZBX_VARIANT_DBL_VECTOR == args[0].type)
	{
		return SUCCEED;
	}

	*error = zbx_strdup(NULL, "Cannot evaluate function: invalid first argument");
	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: initialize expression evaluation data                             *
 *                                                                            *
 * Parameters: eval     - [IN] the evaluation data                            *
 *             mode     - [IN] ZBX_EXPRESSION_NORMAL - support only single    *
 *                             item queries                                   *
 *                             ZBX_EXPRESSION_AGGREGATE - support aggregate   *
 *                             item queries                                   *
 *             ctx      - [IN] the parsed expression                          *
 *                                                                            *
 ******************************************************************************/
void	zbx_expression_eval_init(zbx_expression_eval_t *eval, int mode, zbx_eval_context_t *ctx)
{
	int			i;
	zbx_expression_query_t	*query;
	zbx_vector_str_t	filters;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	zbx_vector_str_create(&filters);
	zbx_eval_extract_item_refs(ctx, &filters);

	zbx_vector_ptr_create(&eval->queries);
	zbx_vector_ptr_create(&eval->groups);
	zbx_vector_ptr_create(&eval->itemtags);
	zbx_vector_ptr_create(&eval->dcitem_refs);

	eval->ctx = ctx;
	eval->mode = mode;
	eval->one_num = 0;
	eval->many_num = 0;
	eval->dcitems_num = 0;
	eval->hostid = 0;

	for (i = 0; i < filters.values_num; i++)
	{
		query = expression_create_query(filters.values[i]);
		zbx_vector_ptr_append(&eval->queries, query);

		if (ZBX_ITEM_QUERY_UNSET == query->flags)
		{
			query->error = zbx_strdup(NULL, "invalid item query filter");
			query->flags = ZBX_ITEM_QUERY_ERROR;
		}
	}

	zbx_vector_str_clear_ext(&filters, zbx_str_free);
	zbx_vector_str_destroy(&filters);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

/******************************************************************************
 *                                                                            *
 * Purpose: free resources allocated by expression evaluation data            *
 *                                                                            *
 * Parameters: eval     - [IN] the evaluation data                            *
 *                                                                            *
 ******************************************************************************/
void	zbx_expression_eval_clear(zbx_expression_eval_t *eval)
{
	if (0 != eval->one_num)
	{
		DCconfig_clean_items(eval->dcitems_hk, eval->errcodes_hk, eval->one_num);
		zbx_free(eval->dcitems_hk);
		zbx_free(eval->errcodes_hk);
		zbx_free(eval->hostkeys);
	}

	if (0 != eval->dcitems_num)
	{
		DCconfig_clean_items(eval->dcitems, eval->errcodes, eval->dcitems_num);
		zbx_free(eval->dcitems);
		zbx_free(eval->errcodes);
	}

	zbx_vector_ptr_destroy(&eval->dcitem_refs);

	zbx_vector_ptr_clear_ext(&eval->itemtags, (zbx_clean_func_t) expression_item_free);
	zbx_vector_ptr_destroy(&eval->itemtags);

	zbx_vector_ptr_clear_ext(&eval->groups, (zbx_clean_func_t) expression_group_free);
	zbx_vector_ptr_destroy(&eval->groups);

	zbx_vector_ptr_clear_ext(&eval->queries, (zbx_clean_func_t) expression_query_free);
	zbx_vector_ptr_destroy(&eval->queries);
}

/******************************************************************************
*                                                                             *
* Purpose: resolve calculated item formula with an empty(default host) and    *
*          macro host references, like:                                       *
*          ( two forward slashes , {HOST.HOST}) to host names                 *
*                                                                             *
* Parameters: eval - [IN] the evaluation expression                           *
*             item - [IN] the calculated item which defines the evaluation    *
*                         expression                                          *
*                                                                             *
*******************************************************************************/
void	zbx_expression_eval_resolve_item_hosts(zbx_expression_eval_t *eval, const DC_ITEM *item)
{
	int	i;

	eval->hostid = item->host.hostid;

	for (i = 0; i < eval->queries.values_num; i++)
	{
		zbx_expression_query_t	*query = (zbx_expression_query_t *)eval->queries.values[i];

		if (0 != (ZBX_ITEM_QUERY_HOST_SELF & query->flags) || 0 == strcmp(query->ref.host, "{HOST.HOST}"))
			query->ref.host = zbx_strdup(query->ref.host, item->host.host);
	}
}

/******************************************************************************
 *                                                                            *
 * Function: zbx_expression_eval_resolve_filter_macros                        *
 *                                                                            *
 * Purpose: resolve calculated item formula macros in filter                  *
 *                                                                            *
 * Parameters: eval - [IN] the evaluation data                                *
 *             item - [IN] the calculated item                                *
 *                                                                            *
 ******************************************************************************/
void	zbx_expression_eval_resolve_filter_macros(zbx_expression_eval_t *eval, const DC_ITEM *item)
{
	int			i;
	zbx_dc_um_handle_t	*um_handle;

	um_handle = zbx_dc_open_user_macros();

	for (i = 0; i < eval->queries.values_num; i++)
	{
		zbx_expression_query_t	*query = (zbx_expression_query_t *)eval->queries.values[i];

		zbx_substitute_simple_macros(NULL, NULL, NULL, NULL, NULL, NULL, item, NULL, NULL, NULL, NULL, NULL,
				&query->ref.filter, MACRO_TYPE_QUERY_FILTER, NULL, 0);
	}

	zbx_dc_close_user_macros(um_handle);
}

typedef struct
{
	int	num;
	char	*macro;
}
zbx_macro_index_t;

static int	macro_index_compare(const void *d1, const void *d2)
{
	const int	*i1 = *(const int **)d1;
	const int	*i2 = *(const int **)d2;

	return *i1 - *i2;
}

static void	macro_index_free(zbx_macro_index_t *index)
{
	zbx_free(index->macro);
	zbx_free(index);
}

static int	resolve_expression_query_macro(const zbx_db_trigger *trigger, int request, int func_num,
		zbx_expression_query_t *query, char **entity, zbx_vector_ptr_t *indices)
{
	int			id;
	zbx_macro_index_t	*index;

	if (FAIL == (id = zbx_vector_ptr_search(indices, &func_num, macro_index_compare)))
	{
		index = (zbx_macro_index_t *)zbx_malloc(NULL, sizeof(zbx_macro_index_t));
		index->num = func_num;
		index->macro = NULL;
		DBget_trigger_value(trigger, &index->macro, func_num, request);
		zbx_vector_ptr_append(indices, index);
	}
	else
		index = (zbx_macro_index_t *)indices->values[id];

	if (NULL == index->macro)
	{
		query->flags = ZBX_ITEM_QUERY_ERROR;
		query->error = zbx_dsprintf(NULL, ZBX_REQUEST_HOST_HOST == request ? "invalid host \"%s\"" :
				"invalid item key \"%s\"", ZBX_NULL2EMPTY_STR(*entity));
		return FAIL;
	}

	*entity = zbx_strdup(*entity, index->macro);

	return SUCCEED;
}

/******************************************************************************
*                                                                             *
* Purpose: resolve expression with an empty host macro (default host),        *
*          macro host references and item key references, like:               *
*          (two forward slashes, {HOST.HOST}, {HOST.HOST<N>},                 *
*          {ITEM.KEY} and {ITEM.KEY<N>}) to host names and item keys          *
*                                                                             *
* Parameters: eval    - [IN/OUT] the evaluation expression                    *
*             trigger - [IN] trigger which defines the evaluation expression  *
*                                                                             *
*******************************************************************************/
void	zbx_expression_eval_resolve_trigger_hosts_items(zbx_expression_eval_t *eval, const zbx_db_trigger *trigger)
{
	int			i, func_num;
	zbx_vector_ptr_t	hosts, item_keys;

	zbx_vector_ptr_create(&hosts);
	zbx_vector_ptr_create(&item_keys);

	for (i = 0; i < eval->queries.values_num; i++)
	{
		zbx_expression_query_t	*query = (zbx_expression_query_t *)eval->queries.values[i];

		/* resolve host */

		if (0 != (ZBX_ITEM_QUERY_HOST_ONE & query->flags))
			func_num = zbx_expr_macro_index(query->ref.host);
		else if (0 != (ZBX_ITEM_QUERY_HOST_SELF & query->flags))
			func_num = 1;
		else
			func_num = -1;

		if (-1 != func_num && FAIL == resolve_expression_query_macro(trigger, ZBX_REQUEST_HOST_HOST, func_num,
				query, &query->ref.host, &hosts))
		{
			continue;
		}

		/* resolve item key */

		if (0 != (ZBX_ITEM_QUERY_KEY_ONE & query->flags) &&
				-1 != (func_num = zbx_expr_macro_index(query->ref.key)))
		{
			resolve_expression_query_macro(trigger, ZBX_REQUEST_ITEM_KEY, func_num, query, &query->ref.key,
					&item_keys);
		}
	}

	zbx_vector_ptr_clear_ext(&hosts, (zbx_clean_func_t)macro_index_free);
	zbx_vector_ptr_clear_ext(&item_keys, (zbx_clean_func_t)macro_index_free);
	zbx_vector_ptr_destroy(&hosts);
	zbx_vector_ptr_destroy(&item_keys);
}

/******************************************************************************
 *                                                                            *
 * Purpose: execute expression containing history functions                   *
 *                                                                            *
 * Parameters: eval  - [IN] the evaluation data                               *
 *             ts    - [IN] the calculated item                               *
 *             value - [OUT] the expression evaluation result                 *
 *             error - [OUT] the error message                                *
 *                                                                            *
 * Return value: SUCCEED - the expression was evaluated successfully.         *
 *               FAIL    - otherwise.                                         *
 *                                                                            *
 ******************************************************************************/
int	zbx_expression_eval_execute(zbx_expression_eval_t *eval, const zbx_timespec_t *ts, zbx_variant_t *value,
		char **error)
{
	int	i, ret;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG))
	{
		char	*expression = NULL;

		zbx_eval_compose_expression(eval->ctx, &expression);
		zabbix_log(LOG_LEVEL_DEBUG, "%s() expression:'%s'", __func__, expression);
		zbx_free(expression);
	}

	for (i = 0; i < eval->queries.values_num; i++)
	{
		zbx_expression_query_t	*query = (zbx_expression_query_t *)eval->queries.values[i];

		if (ZBX_ITEM_QUERY_ERROR != query->flags)
		{
			if (0 != (query->flags & ZBX_ITEM_QUERY_MANY))
				expression_init_query_many(eval, query);
			else
				expression_init_query_one(eval, query);
		}
	}

	/* cache items for functions using one item queries */
	if (0 != eval->one_num)
		expression_cache_dcitems_hk(eval);

	/* cache items for functions using many item queries */
	if (0 != eval->many_num)
		expression_cache_dcitems(eval);

	zbx_variant_set_none(value);

	ret = zbx_eval_execute_ext(eval->ctx, ts, expression_eval_common, expression_eval_history, (void *)eval, value,
			error);

	zbx_vc_flush_stats();

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s value:%s error:%s", __func__, zbx_result_string(ret),
			zbx_variant_value_desc(value), ZBX_NULL2EMPTY_STR(*error));

	return ret;
}