/**************************************************************************************************
	$Id: sql.c,v 1.22 2005/04/20 16:40:25 bboy Exp $

	Copyright (C) 2002-2005  Don Moore <bboy@bboy.net>

	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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "mydns.h"

SQL *sql;															/* Global SQL connection information */

/* Saved connection information for reconnecting */
static char *_sql_user = NULL;
static char *_sql_password = NULL;
static char *_sql_host = NULL;
static char *_sql_database = NULL;


/**************************************************************************************************
	SQL_OPEN
	Connect to the database.  Errors fatal.
**************************************************************************************************/
void
sql_open(char *user, char *password, char *host, char *database)
{
	char *portp = NULL;
	unsigned int port = 0;

	if (host && (portp = strchr(host, ':')))
	{
		port = atoi(portp + 1);
		*portp = '\0';
	}

	/* Save connection information so that we can reconnect if necessary */
	if (_sql_user) Free(_sql_user);
	if (_sql_password) Free(_sql_password);
	if (_sql_host) Free(_sql_host);
	if (_sql_database) Free(_sql_database);
	_sql_user = user ? strdup(user) : NULL;
	_sql_password = password ? strdup(password) : NULL;
	_sql_host = host ? strdup(host) : NULL;
	_sql_database = database ? strdup(database) : NULL;

#if USE_PGSQL
	sql = PQsetdbLogin(host, portp, NULL, NULL, database, user, password);
	if (PQstatus(sql) == CONNECTION_BAD)
	{
		char *errmsg = PQerrorMessage(sql), *c, out[512];

		/* Save the first error message so that the user gets the error message they "expect" */
		for (c = errmsg; *c; c++)
			if (*c == '\r' || *c == '\n')
				*c = ' ';
		strtrim(errmsg);

		snprintf(out, sizeof(out), "%s %s: %s (errno=%d)",
					_("Error connecting to PostgreSQL server at"), host, errmsg, errno);

		if (sql)
			PQfinish(sql);
		/* Try login via UNIX socket before failing, per Lee Brotherston <lee@nerds.org.uk> */
		sql = PQsetdbLogin(NULL, NULL, NULL, NULL, database, user, password);
		if (PQstatus(sql) == CONNECTION_BAD)
			Errx("%s", out);
	}
#else
	sql = NULL;
	if (!(sql = mysql_init(NULL)))
		Err(_("Unable to allocate MySQL data structure"));
#if MYSQL_VERSION_ID > 32349
	mysql_options(sql, MYSQL_READ_DEFAULT_GROUP, "client");
#endif
	if (!(mysql_real_connect(sql, host, user, password, database, port, NULL, 0)))
		ErrSQL(sql, _("Error connecting to MySQL server at %s"), host);
#endif

	if (portp)
		*portp = ':';
}
/*--- sql_open() --------------------------------------------------------------------------------*/


/**************************************************************************************************
	SQL_REOPEN
	Attempt to close and reopen the database connection.
**************************************************************************************************/
void
sql_reopen(void)
{
	SQL *new_sql = NULL;
	char *portp = NULL;
	unsigned int port = 0;

	if (_sql_host && (portp = strchr(_sql_host, ':')))
	{
		port = atoi(portp + 1);
		*portp = '\0';
	}

#if USE_PGSQL
	new_sql = PQsetdbLogin(_sql_host, portp, NULL, NULL, _sql_database, _sql_user, _sql_password);
	if (PQstatus(new_sql) == CONNECTION_BAD)
	{
		if (new_sql)
			PQfinish(new_sql);
		/* Try login via UNIX socket before failing, per Lee Brotherston <lee@nerds.org.uk> */
		new_sql = PQsetdbLogin(NULL, NULL, NULL, NULL, _sql_database, _sql_user, _sql_password);
		if (PQstatus(new_sql) == CONNECTION_BAD)
		{
			if (new_sql)
				PQfinish(new_sql);
			return;
		}
	}
#else
	if (!(new_sql = mysql_init(NULL)))
		return;
#if MYSQL_VERSION_ID > 32349
	mysql_options(new_sql, MYSQL_READ_DEFAULT_GROUP, "client");
#endif
	if (!(mysql_real_connect(new_sql, _sql_host, _sql_user, _sql_password, _sql_database, port, NULL, 0)))
	{
		mysql_close(new_sql);
		return;
	}
#endif

	sql_close(sql);
	sql = new_sql;

	if (portp)
		*portp = ':';
}
/*--- sql_reopen() ------------------------------------------------------------------------------*/


/**************************************************************************************************
	SQL_ISTABLE
	Returns 1 if the specified table exists in the current database, or 0 if it does not.
**************************************************************************************************/
int
sql_istable(SQL *sqlConn, const char *tablename)
{
	unsigned char *xtablename;
#if !USE_PGSQL
	SQL_RES *res;
#endif
	int rv = 0;

	if (!(xtablename = calloc(strlen(tablename) * 2 + 1, sizeof(unsigned char))))
		Err(_("out of memory"));
	sql_escstr(sqlConn, xtablename, (uchar *)tablename, strlen(tablename));

#if USE_PGSQL
	if (sql_count(sqlConn, "SELECT COUNT(*) FROM pg_class"
		 " WHERE (relkind='r' OR relkind='v') AND relname='%s'", xtablename) > 0)
		rv = 1;
#else
	if ((res = sql_queryf(sqlConn, "SHOW TABLES LIKE '%s'", xtablename)))
	{
		if (sql_num_rows(res) > 0)
			rv = 1;
		sql_free(res);
	}
#endif

	Free(xtablename);
	return (rv);
}
/*--- sql_istable() -----------------------------------------------------------------------------*/


/**************************************************************************************************
	SQL_ISCOLUMN
	Returns 1 if the specified column exists in the current database, or 0 if it does not.
**************************************************************************************************/
int
sql_iscolumn(SQL *sqlConn, const char *tablename, const char *columnname)
{
	unsigned char *xtablename, *xcolumnname;
#if !USE_PGSQL
	SQL_RES *res;
#endif
	int rv = 0;

	if (!(xtablename = calloc(strlen(tablename) * 2 + 1, sizeof(unsigned char))))
		Err(_("out of memory"));
	if (!(xcolumnname = calloc(strlen(columnname) * 2 + 1, sizeof(unsigned char))))
		Err(_("out of memory"));
	sql_escstr(sqlConn, xtablename, (uchar *)tablename, strlen(tablename));
	sql_escstr(sqlConn, xcolumnname, (uchar *)columnname, strlen(columnname));

#if USE_PGSQL
	if (sql_count(sqlConn,
			"SELECT COUNT(*)"
			" FROM pg_class,pg_attribute"
			" WHERE (relkind='r' OR relkind='v')"
			" AND relname='%s'"
			" AND attrelid=oid"
			" AND attname='%s'", xtablename, xcolumnname) > 0)
		rv = 1;
#else
	if ((res = sql_queryf(sqlConn, "SHOW COLUMNS FROM %s LIKE '%s'", xtablename, xcolumnname)))
	{
		if (sql_num_rows(res) > 0)
			rv = 1;
		sql_free(res);
	}
#endif

	Free(xtablename);
	Free(xcolumnname);
	return (rv);
}
/*--- sql_iscolumn() ----------------------------------------------------------------------------*/


/**************************************************************************************************
	_SQL_CLOSE
**************************************************************************************************/
void
_sql_close(SQL *sqlConn)
{
#if USE_PGSQL
	PQfinish(sqlConn);
#else
	mysql_close(sqlConn);
#endif
}
/*--- _sql_close() ------------------------------------------------------------------------------*/


/**************************************************************************************************
	SQL_NRQUERY
	Issues an SQL query that does not return a result.  Returns 0 on success, -1 on error.
**************************************************************************************************/
int
sql_nrquery(SQL *sqlConn, const char *query, size_t querylen)
{
#if USE_PGSQL
	{
		ExecStatusType q_rv = PGRES_COMMAND_OK;
		PGresult *result = NULL;

		result = PQexec(sqlConn, query);
		q_rv = PQresultStatus(result);

		if (q_rv == PGRES_COMMAND_OK)
		{
			PQclear(result);
			return (0);
		}
		else
		{
			/* WarnSQL(sqlConn, _("%s: error during query"), PQresStatus(PQresultStatus(result))); */
			PQclear(result);
			return (-1);
		}
	}
#else
	if (mysql_real_query(sqlConn, query, querylen))
		return (-1);
#endif

	return (0);
}
/*--- sql_nrquery() -----------------------------------------------------------------------------*/


/**************************************************************************************************
	SQL_QUERY
	Returns a query's result, or NULL on error.
**************************************************************************************************/
SQL_RES *
sql_query(SQL *sqlConn, const char *query, size_t querylen)
{
	SQL_RES *res = NULL;

#if USE_PGSQL
	{
		ExecStatusType q_rv = PGRES_COMMAND_OK;
		PGresult *result = NULL;

		result = PQexec(sqlConn, query);
		q_rv = PQresultStatus(result);

		if (q_rv == PGRES_TUPLES_OK)
		{
			if (!(res = malloc(sizeof(SQL_RES))))
			{
				PQclear(result);
				return (NULL);
			}
			res->result = result;
			res->tuples = PQntuples(result);
			res->fields = PQnfields(result);
			res->current_tuple = 0;
			if (!(res->current_row = malloc(res->fields * sizeof(unsigned char *))))
			{
				Free(res);
				PQclear(result);
				return (NULL);
			}
		}
		else if (q_rv == PGRES_COMMAND_OK)
		{
			PQclear(result);
			return (NULL);
		}
		else
		{
			/* WarnSQL(sqlConn, _("%s: error during query"), PQresStatus(PQresultStatus(result))); */
			PQclear(result);
			return (NULL);
		}
	}
#else
	if (mysql_real_query(sqlConn, query, querylen) || !(res = mysql_store_result(sqlConn)))
		return (NULL);
#endif

	return (res);
}
/*--- sql_query() -------------------------------------------------------------------------------*/


/**************************************************************************************************
	SQL_QUERYF
	Like sql_query, but accepts varargs format.
**************************************************************************************************/
SQL_RES *
sql_queryf(SQL *sqlConn, const char *fmt, ...)
{
	va_list ap;
	char buf[DNS_QUERYBUFSIZ];
	size_t buflen;

	va_start(ap, fmt);
	buflen = vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);

	return sql_query(sqlConn, buf, buflen);
}
/*--- sql_queryf() ------------------------------------------------------------------------------*/


/**************************************************************************************************
	SQL_COUNT
	Provided a statement like "SELECT COUNT(*)..." returns the count returned, or -1 if an error
	occurred.
**************************************************************************************************/
long
sql_count(SQL *sqlConn, const char *fmt, ...)
{
	va_list ap;
	char buf[DNS_QUERYBUFSIZ];
	size_t buflen;
	SQL_RES *res = NULL;
	long rv = 0;

	va_start(ap, fmt);
	buflen = vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);

	if (!(res = sql_query(sqlConn, buf, buflen)))
		return (-1);
	if (sql_num_rows(res))
#if USE_PGSQL
		rv = atol(PQgetvalue(res->result, 0, 0));
#else
	{
		MYSQL_ROW row;

		if ((row = mysql_fetch_row(res)))
			rv = atol(row[0]);
	}
#endif
	sql_free(res);
	return (rv);
}
/*--- sql_count() -------------------------------------------------------------------------------*/


/**************************************************************************************************
	SQL_NUM_ROWS
	Returns the number of rows in a result set.
**************************************************************************************************/
long
sql_num_rows(SQL_RES *res)
{
#if USE_PGSQL
	return res->tuples;
#else
	return mysql_num_rows(res);
#endif
}
/*--- sql_num_rows() ----------------------------------------------------------------------------*/


/**************************************************************************************************
	SQL_GETROW
	Returns the next row from the result, or NULL if no more rows exist.
**************************************************************************************************/
SQL_ROW
sql_getrow(SQL_RES *res)
{
#if USE_PGSQL
	register int n;

	if (res->current_tuple >= res->tuples)
		return (NULL);
	for (n = 0; n < res->fields; n++)
		res->current_row[n] = PQgetvalue(res->result, res->current_tuple, n);
	res->current_tuple++;
	return (res->current_row);
#else
	return mysql_fetch_row(res);
#endif
}
/*--- sql_getrow() ------------------------------------------------------------------------------*/


/**************************************************************************************************
	SQL_ESCSTR
	Escapes a string to make it suitable for an SQL query.
**************************************************************************************************/
void
sql_escstr(SQL *sqlConn, char *dest, char *src, size_t srclen)
{
#if USE_PGSQL
	PQescapeString(dest, src, srclen);
#else
	mysql_real_escape_string(sqlConn, dest, src, srclen);
#endif
}
/*--- sql_escstr() ------------------------------------------------------------------------------*/


/**************************************************************************************************
	_SQL_FREE
	Free an SQL result.
**************************************************************************************************/
void
_sql_free(SQL_RES *res)
{
#if USE_PGSQL
	Free(res->current_row);
	PQclear(res->result);
	Free(res);
#else
	mysql_free_result(res);
#endif
}
/*--- _sql_free() -------------------------------------------------------------------------------*/

/* vi:set ts=3: */


syntax highlighted by Code2HTML, v. 0.9.1