/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** 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 Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

#include "dbconn.h"
#include "zbxcommon.h"
#include "zbxdb.h"
#include "zbx_dbversion_constants.h"
#include "zbxjson.h"
#include "zbxtypes.h"
#if defined(HAVE_POSTGRESQL)
#	include "zbxstr.h"
#endif

/******************************************************************************
 *                                                                            *
 * Purpose: For PostgreSQL, MySQL and MariaDB:                                *
 *          stoires DBMS version as integer: MMmmuu                           *
 *          M = major version part                                            *
 *          m = minor version part                                            *
 *          u = patch version part                                            *
 *                                                                            *
 * Example: if the original DB version was 1.2.34 then 10234 is set           *
 *                                                                            *
 ******************************************************************************/
static zbx_uint32_t	ZBX_DB_SVERSION = ZBX_DBVERSION_UNDEFINED;

zbx_uint32_t	db_get_server_version(void)
{
	return ZBX_DB_SVERSION;
}

#if defined(HAVE_POSTGRESQL)
static int 	ZBX_TIMESCALE_COMPRESSION_AVAILABLE = OFF;
static int	ZBX_TSDB_VERSION = -1;
#elif defined (HAVE_MYSQL)
static int	ZBX_MARIADB_SFORK = OFF;
#endif

/*********************************************************************************
 *                                                                               *
 * Purpose: determine if a vendor database(MySQL, MariaDB, PostgreSQL,           *
 *          ElasticDB) version satisfies Zabbix requirements                     *
 *                                                                               *
 * Parameters: database                - [IN] database name                      *
 *             current_version         - [IN] detected numeric version           *
 *             min_version             - [IN] minimum required numeric version   *
 *             max_version             - [IN] maximum required numeric version   *
 *             min_supported_version   - [IN] minimum supported numeric version  *
 *                                                                               *
 * Return value: resulting status flag                                           *
 *                                                                               *
 *********************************************************************************/
int	zbx_db_version_check(const char *database, zbx_uint32_t current_version, zbx_uint32_t min_version,
		zbx_uint32_t max_version, zbx_uint32_t min_supported_version)
{
	int	flag;

	if (ZBX_DBVERSION_UNDEFINED == current_version)
	{
		flag = DB_VERSION_FAILED_TO_RETRIEVE;
		zabbix_log(LOG_LEVEL_WARNING, "Failed to retrieve %s version", database);
	}
	else if (min_version > current_version && ZBX_DBVERSION_UNDEFINED != min_version)
	{
		flag = DB_VERSION_LOWER_THAN_MINIMUM;
		zabbix_log(LOG_LEVEL_WARNING, "Unsupported DB! %s version %lu is older than %lu",
				database, (unsigned long)current_version, (unsigned long)min_version);
	}
	else if (max_version < current_version && ZBX_DBVERSION_UNDEFINED != max_version)
	{
		flag = DB_VERSION_HIGHER_THAN_MAXIMUM;
		zabbix_log(LOG_LEVEL_WARNING, "Unsupported DB! %s version %lu is newer than %lu",
				database, (unsigned long)current_version, (unsigned long)max_version);
	}
	else if (min_supported_version > current_version && ZBX_DBVERSION_UNDEFINED != min_supported_version)
	{
		flag = DB_VERSION_NOT_SUPPORTED_ERROR;
		/* log message must be handled by server or proxy */
	}
	else
		flag = DB_VERSION_SUPPORTED;

	return flag;
}

/******************************************************************************
 *                                                                            *
 * Purpose: prepare json for front-end with the DB current, minimum and       *
 *          maximum versions and a flag that indicates if the version         *
 *          satisfies the requirements, and other information as well as      *
 *          information about DB extension in a similar way                   *
 *                                                                            *
 * Parameters:  json                     - [IN/OUT] json data                 *
 *              info                     - [IN] info to serialize             *
 *                                                                            *
 ******************************************************************************/
void	zbx_db_version_json_create(struct zbx_json *json, struct zbx_db_version_info_t *info)
{
	zbx_json_addobject(json, NULL);
	zbx_json_addstring(json, "database", info->database, ZBX_JSON_TYPE_STRING);

	if (DB_VERSION_FAILED_TO_RETRIEVE != info->flag)
		zbx_json_addstring(json, "current_version", info->friendly_current_version, ZBX_JSON_TYPE_STRING);

	zbx_json_addstring(json, "min_version", info->friendly_min_version, ZBX_JSON_TYPE_STRING);
	zbx_json_addstring(json, "max_version", info->friendly_max_version, ZBX_JSON_TYPE_STRING);
	zbx_json_addint64(json, "history_pk", info->history_pk);

	if (NULL != info->friendly_min_supported_version)
	{
		zbx_json_addstring(json, "min_supported_version", info->friendly_min_supported_version,
				ZBX_JSON_TYPE_STRING);
	}

	zbx_json_addint64(json, "flag", info->flag);
	zbx_json_close(json);

	if (NULL != info->extension)
	{
		zbx_json_addobject(json, NULL);
		zbx_json_addstring(json, "database", info->extension, ZBX_JSON_TYPE_STRING);

		if (DB_VERSION_FAILED_TO_RETRIEVE != info->ext_flag)
		{
			zbx_json_addstring(json, "current_version",
					info->ext_friendly_current_version, ZBX_JSON_TYPE_STRING);
		}

		zbx_json_addstring(json, "min_version", info->ext_friendly_min_version, ZBX_JSON_TYPE_STRING);
		zbx_json_addstring(json, "max_version", info->ext_friendly_max_version, ZBX_JSON_TYPE_STRING);
		zbx_json_addstring(json, "min_supported_version", info->ext_friendly_min_supported_version,
				ZBX_JSON_TYPE_STRING);

		zbx_json_addint64(json, "flag", info->ext_flag);
		zbx_json_addint64(json, "extension_err_code", info->ext_err_code);
#ifdef HAVE_POSTGRESQL
		if (0 == zbx_strcmp_null(info->extension, ZBX_DB_EXTENSION_TIMESCALEDB))
		{
			if (ON == zbx_tsdb_get_compression_availability())
			{
				zbx_json_addstring(json, "compression_availability", "true", ZBX_JSON_TYPE_INT);
			}
			else
			{
				zbx_json_addstring(json, "compression_availability", "false", ZBX_JSON_TYPE_INT);
			}

			zbx_json_addint64(json, "compressed_chunks_history", info->history_compressed_chunks);
			zbx_json_addint64(json, "compressed_chunks_trends", info->trends_compressed_chunks);
		}
#endif
		zbx_json_close(json);
	}
}


/***************************************************************************************************************
 *                                                                                                             *
 * Purpose: retrieves the DB version info, including numeric version value                                     *
 *                                                                                                             *
 *          For PostgreSQL:                                                                                    *
 *          numeric version is available from the API                                                          *
 *                                                                                                             *
 *          For MySQL and MariaDB:                                                                             *
 *          numeric version is available from the API, but also the additional processing is required          *
 *          to determine if it is a MySQL or MariaDB and save this result as well                              *
 *                                                                                                             *
 *                                                                                                             *
 **************************************************************************************************************/
void	zbx_dbconn_extract_version_info(zbx_dbconn_t *db, struct zbx_db_version_info_t *version_info)
{
#define RIGHT2(x)	((int)((zbx_uint32_t)(x) - ((zbx_uint32_t)((x)/100))*100))
#if defined(HAVE_MYSQL)
	int		client_major_version, client_minor_version, client_release_version, server_major_version,
			server_minor_version, server_release_version;
	const char	*info;

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

	if (NULL != (info = mysql_get_server_info(db->conn)) && NULL != strstr(info, "MariaDB"))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "MariaDB fork detected");
		ZBX_MARIADB_SFORK = ON;
	}

	if (ON == ZBX_MARIADB_SFORK && NULL != info && 6 == sscanf(info, "%d.%d.%d-%d.%d.%d-MariaDB",
			&client_major_version, &client_minor_version, &client_release_version, &server_major_version,
			&server_minor_version, &server_release_version))
	{
		ZBX_DB_SVERSION = (zbx_uint32_t)(server_major_version * 10000 + server_minor_version * 100 +
				server_release_version);
		zabbix_log(LOG_LEVEL_DEBUG, "MariaDB subversion detected");
	}
	else
		ZBX_DB_SVERSION = (zbx_uint32_t)mysql_get_server_version(db->conn);

	version_info->current_version = ZBX_DB_SVERSION;
	version_info->friendly_current_version = zbx_dsprintf(NULL, "%d.%.2d.%.2d",
			RIGHT2(ZBX_DB_SVERSION/10000), RIGHT2(ZBX_DB_SVERSION/100),
			RIGHT2(ZBX_DB_SVERSION));

	if (ZBX_MARIADB_SFORK)
	{
		version_info->database = "MariaDB";

		version_info->min_version = ZBX_MARIADB_MIN_VERSION;
		version_info->max_version = ZBX_MARIADB_MAX_VERSION;
		version_info->min_supported_version = ZBX_MARIADB_MIN_SUPPORTED_VERSION;

		version_info->friendly_min_version = ZBX_MARIADB_MIN_VERSION_STR;
		version_info->friendly_max_version = ZBX_MARIADB_MAX_VERSION_STR;
		version_info->friendly_min_supported_version = ZBX_MARIADB_MIN_SUPPORTED_VERSION_STR;
	}
	else
	{
		version_info->database = "MySQL";

		version_info->min_version = ZBX_MYSQL_MIN_VERSION;
		version_info->max_version = ZBX_MYSQL_MAX_VERSION;
		version_info->min_supported_version = ZBX_MYSQL_MIN_SUPPORTED_VERSION;

		version_info->friendly_min_version = ZBX_MYSQL_MIN_VERSION_STR;
		version_info->friendly_max_version = ZBX_MYSQL_MAX_VERSION_STR;
		version_info->friendly_min_supported_version = ZBX_MYSQL_MIN_SUPPORTED_VERSION_STR;
	}

	version_info->flag = zbx_db_version_check(version_info->database, version_info->current_version,
			version_info->min_version, version_info->max_version, version_info->min_supported_version);

#elif defined(HAVE_POSTGRESQL)
	zbx_uint32_t major;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
	ZBX_DB_SVERSION = (zbx_uint32_t)PQserverVersion(db->conn);

	major = ZBX_DB_SVERSION/10000;

	version_info->database = "PostgreSQL";

	version_info->current_version = ZBX_DB_SVERSION;
	version_info->min_version = ZBX_POSTGRESQL_MIN_VERSION;
	version_info->max_version = ZBX_POSTGRESQL_MAX_VERSION;
	version_info->min_supported_version = ZBX_POSTGRESQL_MIN_SUPPORTED_VERSION;

	if (10 > major)
	{
		version_info->friendly_current_version = zbx_dsprintf(NULL, "%" PRIu32 ".%d.%d", major,
				RIGHT2(ZBX_DB_SVERSION/100), RIGHT2(ZBX_DB_SVERSION));
	}
	else
	{
		version_info->friendly_current_version = zbx_dsprintf(NULL, "%" PRIu32 ".%d", major,
				RIGHT2(ZBX_DB_SVERSION));
	}

	version_info->friendly_min_version = ZBX_POSTGRESQL_MIN_VERSION_STR;
	version_info->friendly_max_version = ZBX_POSTGRESQL_MAX_VERSION_STR;
	version_info->friendly_min_supported_version = ZBX_POSTGRESQL_MIN_SUPPORTED_VERSION_STR;

	version_info->flag = zbx_db_version_check(version_info->database, version_info->current_version,
			version_info->min_version, version_info->max_version, version_info->min_supported_version);

#else
	ZBX_UNUSED(db);
	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
	version_info->flag = DB_VERSION_SUPPORTED;
	version_info->friendly_current_version = NULL;
#endif
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() version:%lu", __func__, (unsigned long)ZBX_DB_SVERSION);
}

#ifdef HAVE_POSTGRESQL

static int	dbconn_tsdb_table_has_compressed_chunks(zbx_dbconn_t *db, const char *table_names)
{
	zbx_db_result_t	result;
	int		ret;

	result = zbx_dbconn_select(db, "select null from timescaledb_information.chunks"
			" where hypertable_name in (%s) and is_compressed='t'", table_names);

	if ((zbx_db_result_t)ZBX_DB_DOWN == result)
	{
		ret = FAIL;
		goto out;
	}

	if (NULL != zbx_db_fetch(result))
		ret = SUCCEED;
	else
		ret = FAIL;
out:
	zbx_db_free_result(result);

	return ret;
}

void	zbx_dbconn_tsdb_extract_compressed_chunk_flags(zbx_dbconn_t *db, struct zbx_db_version_info_t *version_info)
{
#define ZBX_TSDB_HISTORY_TABLES "'history_uint','history_log','history_str','history_text','history'"
#define ZBX_TSDB_TRENDS_TABLES "'history_bin','trends','trends_uint'"

	version_info->history_compressed_chunks =
			(SUCCEED == dbconn_tsdb_table_has_compressed_chunks(db, ZBX_TSDB_HISTORY_TABLES)) ? 1 : 0;

	version_info->trends_compressed_chunks =
			(SUCCEED == dbconn_tsdb_table_has_compressed_chunks(db, ZBX_TSDB_TRENDS_TABLES)) ? 1 : 0;

#undef ZBX_TSDB_HISTORY_TABLES
#undef ZBX_TSDB_TRENDS_TABLES
}

/***************************************************************************************************************
 *                                                                                                             *
 * Purpose: retrieves TimescaleDB extension info, including license string and numeric version value           *
 *                                                                                                             *
 **************************************************************************************************************/
void	zbx_dbconn_tsdb_info_extract(zbx_dbconn_t *db, struct zbx_db_version_info_t *version_info)
{
	int	tsdb_ver;

	if (0 != zbx_strcmp_null(version_info->extension, ZBX_DB_EXTENSION_TIMESCALEDB))
		return;

	tsdb_ver = zbx_dbconn_tsdb_get_version(db);

	version_info->ext_current_version = (zbx_uint32_t)tsdb_ver;
	version_info->ext_min_version = ZBX_TIMESCALE_MIN_VERSION;
	version_info->ext_max_version = ZBX_TIMESCALE_MAX_VERSION;
	version_info->ext_min_supported_version = ZBX_TIMESCALE_MIN_SUPPORTED_VERSION;

	version_info->ext_friendly_current_version = zbx_dsprintf(NULL, "%d.%d.%d", RIGHT2(tsdb_ver/10000),
			RIGHT2(tsdb_ver/100), RIGHT2(tsdb_ver));

	version_info->ext_friendly_min_version = ZBX_TIMESCALE_MIN_VERSION_STR;
	version_info->ext_friendly_max_version = ZBX_TIMESCALE_MAX_VERSION_STR;
	version_info->ext_friendly_min_supported_version = ZBX_TIMESCALE_MIN_SUPPORTED_VERSION_STR;

	version_info->ext_flag = zbx_db_version_check(version_info->extension, version_info->ext_current_version,
			version_info->ext_min_version, version_info->ext_max_version,
			version_info->ext_min_supported_version);

	zabbix_log(LOG_LEVEL_DEBUG, "TimescaleDB version: [%d]", tsdb_ver);
}

/******************************************************************************
 *                                                                            *
 * Purpose: returns TimescaleDB (TSDB) version as integer: MMmmuu             *
 *          M = major version part                                            *
 *          m = minor version part                                            *
 *          u = patch version part                                            *
 *                                                                            *
 * Example: TSDB 1.5.1 version will be returned as 10501                      *
 *                                                                            *
 * Return value: TSDB version or 0 if unknown or the extension not installed  *
 *                                                                            *
 ******************************************************************************/
int	zbx_dbconn_tsdb_get_version(zbx_dbconn_t *db)
{
	int		ver, major, minor, patch;
	zbx_db_result_t	result;
	zbx_db_row_t	row;

	if (-1 == ZBX_TSDB_VERSION)
	{
		/* catalog pg_extension not available */
		if (90001 > ZBX_DB_SVERSION)
		{
			ver = ZBX_TSDB_VERSION = 0;
			goto out;
		}

		result = zbx_dbconn_select(db, "select extversion from pg_extension where extname = 'timescaledb'");

		/* database down, can re-query in the next call */
		if ((zbx_db_result_t)ZBX_DB_DOWN == result)
		{
			ver = 0;
			goto out;
		}

		/* extension is not installed */
		if (NULL == result)
		{
			ver = ZBX_TSDB_VERSION = 0;
			goto out;
		}

		if (NULL != (row = zbx_db_fetch(result)) &&
				3 == sscanf((const char*)row[0], "%d.%d.%d", &major, &minor, &patch))
		{
			ver = major * 10000;
			ver += minor * 100;
			ver += patch;
			ZBX_TSDB_VERSION = ver;
		}
		else
			ver = ZBX_TSDB_VERSION = 0;

		zbx_db_free_result(result);
	}
	else
		ver = ZBX_TSDB_VERSION;
out:
	return ver;
}

/******************************************************************************
 *                                                                            *
 * Purpose: sets TimescaleDB (TSDB) compression availability                  *
 *                                                                            *
 * Parameters:  compression_availabile - [IN] compression availability        *
 *              0 (OFF): compression is not available                         *
 *              1 (ON): compression is available                              *
 *                                                                            *
 ******************************************************************************/
void	zbx_tsdb_set_compression_availability(int compression_availabile)
{
	ZBX_TIMESCALE_COMPRESSION_AVAILABLE = compression_availabile;
}

/******************************************************************************
 *                                                                            *
 * Purpose: retrieves TimescaleDB (TSDB) compression availability             *
 *                                                                            *
 * Return value: compression availability as as integer                       *
 *               0 (OFF): compression is not available                        *
 *               1 (ON): compression is available                             *
 *                                                                            *
 ******************************************************************************/
int	zbx_tsdb_get_compression_availability(void)
{
	return ZBX_TIMESCALE_COMPRESSION_AVAILABLE;
}

#endif