/*
** 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 "zbxcommon.h"
#include "zbxdb.h"
#include "zbxalgo.h"
#include "zbxdbschema.h"
#include "zbxtypes.h"

static zbx_dbconn_t	*dbconn;
static int		db_autoincrement;

void	zbx_db_init_autoincrement_options(void)
{
	db_autoincrement = 1;
}

/******************************************************************************
 *                                                                            *
 * Purpose: connect to the database                                           *
 *                                                                            *
 * Parameters: flag - ZBX_DB_CONNECT_ONCE (try once and return the result),   *
 *                    ZBX_DB_CONNECT_EXIT (exit on failure) or                *
 *                    ZBX_DB_CONNECT_NORMAL (retry until connected)           *
 *                                                                            *
 * Return value: ZBX_DB_OK - successfully connected                           *
 *               ZBX_DB_DOWN - database is down                               *
 *               ZBX_DB_FAIL - failed to connect                              *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_connect(int flag)
{
	int		ret;
	zbx_dbconn_t	*db;

	if (NULL != dbconn)
		THIS_SHOULD_NEVER_HAPPEN;

	db = zbx_dbconn_create();

	(void)zbx_dbconn_set_connect_options(db, flag);
	zbx_dbconn_set_autoincrement(db, db_autoincrement);
	ret = zbx_dbconn_open(db);

	dbconn = db;

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: close database connection                                         *
 *                                                                            *
 ******************************************************************************/
void	zbx_db_close(void)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return;
	}

	zbx_dbconn_free(dbconn);
	dbconn = NULL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: start a transaction                                               *
 *                                                                            *
 * Comments: do nothing if DB does not support transactions                   *
 *                                                                            *
 ******************************************************************************/
void	zbx_db_begin(void)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return;
	}

	(void)zbx_dbconn_begin(dbconn);

	return;
}

/******************************************************************************
 *                                                                            *
 * Purpose: commit a transaction                                              *
 *                                                                            *
 * Comments: do nothing if DB does not support transactions                   *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_commit(void)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return ZBX_DB_FAIL;
	}

	return zbx_dbconn_commit(dbconn);
}

/******************************************************************************
 *                                                                            *
 * Purpose: rollback a transaction                                            *
 *                                                                            *
 * Comments: do nothing if DB does not support transactions                   *
 *                                                                            *
 ******************************************************************************/
void	zbx_db_rollback(void)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return;
	}

	(void)zbx_dbconn_rollback(dbconn);
}

/******************************************************************************
 *                                                                            *
 * Purpose: commit or rollback a transaction depending on a parameter value   *
 *                                                                            *
 * Comments: do nothing if DB does not support transactions                   *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_end(int ret)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return FAIL;
	}

	return zbx_dbconn_end(dbconn, ret);
}

/******************************************************************************
 *                                                                            *
 * Purpose: execute a non-select statement                                    *
 *                                                                            *
 * Comments: retry until DB is up                                             *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_execute(const char *fmt, ...)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return FAIL;
	}

	va_list	args;
	int	rc;

	va_start(args, fmt);
	rc = zbx_dbconn_vexecute(dbconn, fmt, args);
	va_end(args);

	return rc;
}

/******************************************************************************
 *                                                                            *
 * Purpose: execute a non-select statement                                    *
 *                                                                            *
 * Comments: don't retry if DB is down                                        *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_execute_once(const char *fmt, ...)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return FAIL;
	}

	va_list	args;
	int	rc, options;

	va_start(args, fmt);
	options = zbx_dbconn_set_connect_options(dbconn, ZBX_DB_CONNECT_ONCE);
	rc = zbx_dbconn_vexecute(dbconn, fmt, args);
	zbx_dbconn_set_connect_options(dbconn, options);
	va_end(args);

	return rc;
}

/******************************************************************************
 *                                                                            *
 * Purpose: execute a select statement                                        *
 *                                                                            *
 * Comments: retry until DB is up                                             *
 *                                                                            *
 ******************************************************************************/
zbx_db_result_t	zbx_db_select(const char *fmt, ...)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return NULL;
	}

	va_list		args;
	zbx_db_result_t	rc;

	va_start(args, fmt);
	rc = zbx_dbconn_vselect(dbconn, fmt, args);
	va_end(args);

	return rc;
}

/******************************************************************************
 *                                                                            *
 * Purpose: execute a select statement                                        *
 *                                                                            *
 * Comments: retry until DB is up                                             *
 *                                                                            *
 ******************************************************************************/
zbx_db_result_t	zbx_db_vselect(const char *fmt, va_list args)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return NULL;
	}

	return zbx_dbconn_vselect(dbconn, fmt, args);
}

/******************************************************************************
 *                                                                            *
 * Purpose: execute a select statement and get the first N entries            *
 *                                                                            *
 * Comments: retry until DB is up                                             *
 *                                                                            *
 ******************************************************************************/
zbx_db_result_t	zbx_db_select_n(const char *query, int n)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return NULL;
	}

	return zbx_dbconn_select_n(dbconn, query, n);
}

/******************************************************************************
 *                                                                            *
 * Purpose: get next id for requested table                                   *
 *                                                                            *
 ******************************************************************************/
zbx_uint64_t	zbx_db_get_maxid_num(const char *tablename, int num)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return 0;
	}

	return zbx_dbconn_get_maxid_num(dbconn, tablename, num);
}

/******************************************************************************
 *                                                                            *
 * Purpose: prepare for database bulk insert operation                        *
 *                                                                            *
 ******************************************************************************/
void	zbx_db_insert_prepare_dyn(zbx_db_insert_t *self, const zbx_db_table_t *table, const zbx_db_field_t **fields,
		int fields_num)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return;
	}

	zbx_dbconn_prepare_insert_dyn(dbconn, self, table, fields, fields_num);
}

/******************************************************************************
 *                                                                            *
 * Purpose: prepare for database bulk insert operation                        *
 *                                                                            *
 ******************************************************************************/
void	zbx_db_insert_prepare(zbx_db_insert_t *self, const char *table, ...)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return;
	}

	va_list	args;

	va_start(args, table);
	zbx_dbconn_prepare_vinsert(dbconn, self, table, args);
	va_end(args);
}

/******************************************************************************
 *                                                                            *
 * Purpose: connects to DB and tries to detect DB version                     *
 *                                                                            *
 ******************************************************************************/
void	zbx_db_extract_version_info(struct zbx_db_version_info_t *version_info)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return;
	}

	zbx_dbconn_extract_version_info(dbconn, version_info);
}

#ifdef HAVE_POSTGRESQL

void	zbx_tsdb_extract_compressed_chunk_flags(struct zbx_db_version_info_t *version_info)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return;
	}

	zbx_dbconn_tsdb_extract_compressed_chunk_flags(dbconn, version_info);
}

/***************************************************************************************************************
 *                                                                                                             *
 * Purpose: retrieves TimescaleDB extension info, including license string and numeric version value           *
 *                                                                                                             *
 **************************************************************************************************************/
void	zbx_tsdb_info_extract(struct zbx_db_version_info_t *version_info)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return;
	}

	zbx_dbconn_tsdb_info_extract(dbconn, version_info);
}

/******************************************************************************
 *                                                                            *
 * 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_tsdb_get_version(void)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return 0;
	}

	return zbx_dbconn_tsdb_get_version(dbconn);
}

#endif

/******************************************************************************
 *                                                                            *
 * Purpose: get last error set by database                                    *
 *                                                                            *
 * Return value: last database error message                                  *
 *                                                                            *
 ******************************************************************************/
const char	*zbx_db_last_strerr(void)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return NULL;
	}

	return zbx_dbconn_last_strerr(dbconn);
}

/******************************************************************************
 *                                                                            *
 * Purpose: get last error code returned by database                          *
 *                                                                            *
 * Return value: last database error code                                     *
 *                                                                            *
 ******************************************************************************/
zbx_err_codes_t	zbx_db_last_errcode(void)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return 0;
	}

	return zbx_dbconn_last_errcode(dbconn);
}

/******************************************************************************
 *                                                                            *
 * Purpose: locks a record in a table by its primary key and an optional      *
 *          constraint field                                                  *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_lock_record(const char *table, zbx_uint64_t id, const char *add_field, zbx_uint64_t add_id)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return FAIL;
	}

	return zbx_dbconn_lock_record(dbconn, table, id, add_field, add_id);
}

/******************************************************************************
 *                                                                            *
 * Purpose: locks a records in a table by its primary key                     *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_lock_records(const char *table, const zbx_vector_uint64_t *ids)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return FAIL;
	}

	return zbx_dbconn_lock_records(dbconn, table, ids);
}

/******************************************************************************
 *                                                                            *
 * Purpose: locks a records in a table by field name                          *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_lock_ids(const char *table_name, const char *field_name, zbx_vector_uint64_t *ids)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return FAIL;
	}

	return zbx_dbconn_lock_ids(dbconn, table_name, field_name, ids);
}

/*********************************************************************************
 *                                                                               *
 * Purpose: verify that Zabbix can work with DB extension                        *
 *                                                                               *
 *********************************************************************************/
int	zbx_db_check_extension(struct zbx_db_version_info_t *info, int allow_unsupported)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return FAIL;
	}

	return zbx_dbconn_check_extension(dbconn, info, allow_unsupported);
}


/******************************************************************************
 *                                                                            *
 * Purpose: flush SQL request                                                 *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_flush_overflowed_sql(char *sql, size_t sql_offset)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return ZBX_DB_FAIL;
	}

	return zbx_dbconn_flush_overflowed_sql(dbconn, sql, sql_offset);
}

/******************************************************************************
 *                                                                            *
 * Purpose: execute a set of SQL statements IF it is big enough               *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_execute_overflowed_sql(char **sql, size_t *sql_alloc, size_t *sql_offset)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return ZBX_DB_FAIL;
	}

	return zbx_dbconn_execute_overflowed_sql(dbconn, sql, sql_alloc, sql_offset, NULL);
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if table exists                                             *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_table_exists(const char *table_name)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return ZBX_DB_FAIL;
	}

	return zbx_dbconn_table_exists(dbconn, table_name);
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if table field exists                                       *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_field_exists(const char *table_name, const char *field_name)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return ZBX_DB_FAIL;
	}

	return zbx_dbconn_field_exists(dbconn, table_name, field_name);
}

#if !defined(HAVE_SQLITE3)
/******************************************************************************
 *                                                                            *
 * Purpose: check if table trigger exists                                     *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_trigger_exists(const char *table_name, const char *trigger_name)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return ZBX_DB_FAIL;
	}

	return zbx_dbconn_trigger_exists(dbconn, table_name, trigger_name);
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if table index exists                                       *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_index_exists(const char *table_name, const char *index_name)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return ZBX_DB_FAIL;
	}

	return zbx_dbconn_index_exists(dbconn, table_name, index_name);
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if table primary key exists                                 *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_pk_exists(const char *table_name)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return ZBX_DB_FAIL;
	}

	return zbx_dbconn_pk_exists(dbconn, table_name);
}
#endif /* !defined(HAVE_SQLITE3) */

/******************************************************************************
 *                                                                            *
 * Parameters: sql - [IN] sql statement                                       *
 *             ids - [OUT] sorted list of selected uint64 values              *
 *                                                                            *
 ******************************************************************************/
void	zbx_db_select_uint64(const char *sql, zbx_vector_uint64_t *ids)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return;
	}

	zbx_dbconn_select_uint64(dbconn, sql, ids);
}

/******************************************************************************
 *                                                                            *
 * Purpose: execute query with large number of primary key matches in smaller *
 *          batches (last batch is not executed)                              *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_prepare_multiple_query(const char *query, const char *field_name, const zbx_vector_uint64_t *ids,
		char **sql, size_t *sql_alloc, size_t *sql_offset)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return ZBX_DB_FAIL;
	}

	return zbx_dbconn_prepare_multiple_query(dbconn, query, field_name, ids, sql, sql_alloc, sql_offset);
}

/******************************************************************************
 *                                                                            *
 * Purpose: execute query with large number of primary key matches in smaller *
 *          batches                                                           *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_execute_multiple_query(const char *query, const char *field_name, const zbx_vector_uint64_t *ids)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return ZBX_DB_FAIL;
	}

	return zbx_dbconn_execute_multiple_query(dbconn, query, field_name, ids);
}


/******************************************************************************
 *                                                                            *
 * Purpose: execute query with large number of primary key matches in smaller *
 *          batches, values will be supplied as strings                       *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_execute_multiple_query_str(const char *query, const char *field_name, const zbx_vector_uint64_t *ids)
{
	if (NULL == dbconn)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		return ZBX_DB_FAIL;
	}

	return zbx_dbconn_execute_multiple_query_str(dbconn, query, field_name, ids);
}

/******************************************************************************
 *                                                                            *
 * Purpose: prepare large SQL query with uint based IDs                       *
 *                                                                            *
 * Parameters: query      - [IN] large query object                           *
 *             sql        - [IN/OUT] first part of the query, can be modified *
 *                              or reallocated                                *
 *             sql_alloc  - [IN/OUT] size of allocated sql string             *
 *             sql_offset - [IN/OUT] length of the sql string                 *
 *             field      - [IN] ID field name                                *
 *             ids        - [IN] vector of IDs                                *
 *                                                                            *
 * Comments: Large query object 'borrows' the sql buffer with the query part, *
 *           meaning:                                                         *
 *             - caller must not free/modify this sql buffer while the        *
 *               prepared large query object is being used                    *
 *             - caller must free this sql buffer afterwards - it's not freed *
 *               when large query object is cleared.                          *
 *                                                                            *
 ******************************************************************************/
void	zbx_db_large_query_prepare_uint(zbx_db_large_query_t *query, char **sql, size_t *sql_alloc,
		size_t *sql_offset, const char *field, const zbx_vector_uint64_t *ids)
{
	if (NULL == dbconn)
	{
		memset(query, 0, sizeof(zbx_db_large_query_t));
		THIS_SHOULD_NEVER_HAPPEN;
		return;
	}

	zbx_dbconn_large_query_prepare_uint(query, dbconn, sql, sql_alloc, sql_offset, field, ids);
}

/******************************************************************************
 *                                                                            *
 * Purpose: prepare large SQL query with string based IDs                     *
 *                                                                            *
 * Parameters: query      - [IN] large query object                           *
 *             sql        - [IN/OUT] first part of the query, can be modified *
 *                              or reallocated                                *
 *             sql_alloc  - [IN/OUT] size of allocated sql string             *
 *             sql_offset - [IN/OUT] length of the sql string                 *
 *             field      - [IN] ID field name                                *
 *             ids        - [IN] vector of IDs                                *
 *                                                                            *
 * Comments: Large query object 'borrows' the sql buffer with the query part, *
 *           meaning:                                                         *
 *             - caller must not free/modify this sql buffer while the        *
 *               prepared large query object is being used                    *
 *             - caller must free this sql buffer afterwards - it's not freed *
 *               when large query object is cleared.                          *
 *                                                                            *
 ******************************************************************************/
void	zbx_db_large_query_prepare_str(zbx_db_large_query_t *query, char **sql, size_t *sql_alloc,
		size_t *sql_offset, const char *field, const zbx_vector_str_t *ids)
{
	if (NULL == dbconn)
	{
		memset(query, 0, sizeof(zbx_db_large_query_t));
		THIS_SHOULD_NEVER_HAPPEN;
		return;
	}

	zbx_dbconn_large_query_prepare_str(query, dbconn, sql, sql_alloc, sql_offset, field, ids);
}

/******************************************************************************
 *                                                                            *
 * Purpose: set sql statement to be appended to each batch                    *
 *                                                                            *
 * Parameters: query      - [IN] large query object                           *
 *             sql        - [IN] sql statement to append                      *
 *                                                                            *
 ******************************************************************************/
void	zbx_db_large_query_append_sql(zbx_db_large_query_t *query, const char *sql)
{
	zbx_dbconn_large_query_append_sql(query, sql);
}