/*
** SellaNMS
** $Id: storage.cxx,v 1.23 2005/08/14 03:52:44 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static char rcsid[] = "$Id: storage.cxx,v 1.23 2005/08/14 03:52:44 sella Exp $";

#include "storage.h"
#include "loader.h"
#include "common/util.h"
#include "connection_pool.h"
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <regex.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stl/slist>
#include <stl/string>

extern bool __libstorage_initialized;
extern pthread_mutex_t __libstorage_global_mutex;
extern ConnectionPool* pool;

Storage::Storage():queryCount(0),conn(NULL),debug(false),resultSet(NULL) {
	init();

#if 0
	LMSG("libstorage is using %s", dbi_version());

	if (pool == NULL) {
		pool = new ConnectionPool();
		if (pool == NULL) {
			LMSG("Failed to allocate memory for ConnectionPool.");
			abort();
		}
	}
#endif
}

Storage::Storage(Storage const &src):queryCount(0),conn(NULL),debug(false),resultSet(NULL) {
	LMSG("ERROR: Storage copy contructor called. Deep copy is not implemented. Aborting...");
	abort();
}

Storage::~Storage() {
	/* Free results and close all connections. */
	clear();

	fini();
}

RESULT Storage::setConfig(ConfigNode &config) {
	assert(this->config.empty());
	assert(pool != NULL);
	assert(this->conn == NULL);

	/* Keep an internal copy of the storage portion of the config node. */
	if (!config.assignNode("system storage", this->config) || this->config.empty()) {
		LMSG("Storage configuration is missing (system storage).");

		return FAILURE;
	}

	if (TRACE_ISSET(TRACE_SQL)) {
		this->debug = true;
	} else {
		this->debug = this->config.isNode("debug");
	}

	if (pool->setConfig(config) != SUCCESS) {
		return FAILURE;
	}

	return pool->requestConnection(this->conn, false);
}

void Storage::setDebug(bool debug) {
	this->debug = debug;
}

bool Storage::isConfigured() {
	return !(this->config.empty());
}

void Storage::clear() {
	/* Free the resultSet. */
	if (resultSet) {
		if (dbi_result_free(resultSet) != 0) {
			LMSG("ERROR: Storage::clear() - Call to dbi_result_free() returned in error.");
		} else {
			resultSet = NULL;
		}
	}

	/* Close all connections and empty the list. */
	if (this->conn) {
		pool->releaseConnection(this->conn);
	}
}

RESULT Storage::queryf(StorageData &data, const char *sql, ...) {
	RESULT result = SUCCESS;

	char *buf;
	va_list ap;
	unsigned long len;

	assert(&data != NULL);

	if (sql == NULL || sql[0] == '\0') {
		return FAILURE;
	}

	len = strlen(sql) + 32768;

	/* Do printf formatting. */
	va_start(ap, sql);
	buf = (char*) malloc(len);
	vsnprintf(buf, len, sql, ap);
	va_end(ap);

	/* Call normal query. */
	result = query(data, buf);

	/* Free asprintf(). */
	free(buf);

	return result;
}

RESULT Storage::query(StorageData &data, const char *sql) {
	RESULT result = SUCCESS;
	string str;

	/* Object must be initialized prior to making queries. */
	assert(&data != NULL);
	assert(__libstorage_initialized == true);
	assert(conn != NULL);
	assert(conn->list.size() > 0);

	bool quick = true;
	dbi_result result_set = NULL, sub_result_set = NULL;
	slist<dbi_conn>::iterator it;

	queryCount++;

	if (sql == NULL || sql[0] == '\0') {
		return FAILURE;
	}

	/* If 2+ connections and not a SELECT, don't use quick. */
	if (conn->list.size() > 1 && toupper(sql[0]) != 'S') {
		quick = false;
	} else if (this->debug) {
		str = sql;
		SubStringReplace(str, "\t", "");
		LMSG("SQL: %s", str.c_str());
	}

	/* Do query while locked. */
	pthread_mutex_lock(conn->mutex);
	if (conn->list.size() < 1) {
		LMSG("Call to Storage::query() with no active connections.");
		pthread_mutex_unlock(conn->mutex);
		usleep(USLEEP_LONGLOOP_TIME);

		return FAILURE;
	}

	it = conn->list.begin();
//	LMSG("Doing query: %lu, %08x, Con: %lu, %s", pthread_self(), *it, conn->list.size(), sql);
	if ((result_set = dbi_conn_query(*it, sql)) == NULL) {
		LMSG("Failure querying primary connection.");
		conn->failed = true;
		printError(*it);

		result = FAILURE;
	} else {
		/* Assign new ResultSet and mutex to StorageData. */
		data.setResultSet(result_set, conn->list.front(), conn->mutex);

		if (!quick) {
			/* For INSERT, UPDATE and DELETE, we must process remaining connections. */
			do {
				++it;
				if ((sub_result_set = dbi_conn_query(*it, sql)) == NULL) {
					LMSG("Failure querying secondary connection.");
					result = FAILURE;
				} else {
					dbi_result_free(sub_result_set);
				}
			} while (it != conn->list.end());
		}

		if (this->debug) {
			LMSG("SQL - Rows: %lu", (toupper(sql[0]) != 'S') ? dbi_result_get_numrows_affected(result_set) : dbi_result_get_numrows(result_set));
		}
	}

	pthread_mutex_unlock(conn->mutex);

	return result;
}

RESULT Storage::getDbList(slist<string> &db_list, const char* pattern) {
	RESULT result = SUCCESS;

	const char *db;
	dbi_result result_set;

	/* Object must be initialized prior to making queries. */
	assert(__libstorage_initialized == true);

	/* There must be connections to query something. */
	assert(conn != NULL);
	assert(conn->list.size() > 0);

	/* Do query while locked. */
	pthread_mutex_lock(conn->mutex);
	if (conn->list.size() < 1) {
		LMSG("Call to Storage::getDbList() with no active connections.");
		pthread_mutex_unlock(conn->mutex);

		return FAILURE;
	}

	result_set = dbi_conn_get_db_list(conn->list.front(), pattern);
	pthread_mutex_unlock(conn->mutex);

	if (result_set) {
		/* Empty it. */
		db_list.clear();

		while (dbi_result_next_row(result_set)) {
			/* Returns a pointer to resultSet internals. Do not deallocate. */
			db = dbi_result_get_string_idx(result_set, 1);
			if (db) {
				db_list.push_front(db);
			}
		}

		dbi_result_free(result_set);
	}

	return result;
}

RESULT Storage::getTableList(slist<string> &table_list, const char* db, const char* pattern) {
	RESULT result = SUCCESS;

	const char *table;
	dbi_result result_set;

	/* Object must be initialized prior to making queries. */
	assert(__libstorage_initialized == true);

	/* There must be connections to query something. */
	assert(conn != NULL);
	assert(conn->list.size() > 0);

	/* Do query while locked. */
	pthread_mutex_lock(conn->mutex);
	if (conn->list.size() < 1) {
		LMSG("Call to Storage::getTableList() with no active connections.");
		pthread_mutex_unlock(conn->mutex);
		
		return FAILURE;
	}

	result_set = dbi_conn_get_table_list(conn->list.front(), db, pattern);
	pthread_mutex_unlock(conn->mutex);

	if (result_set) {
		/* Empty it. */
		table_list.clear();

		while (dbi_result_next_row(result_set)) {
			/* Returns a pointer to resultSet internals. Do not deallocate. */
			table = dbi_result_get_string_idx(result_set, 1);
			if (table) {
				table_list.push_front(table);
			}
		}

		dbi_result_free(result_set);
	}

	return result;
}

void Storage::printError(dbi_conn connection) {
	int ret_val;
	const char *err = NULL;

	ret_val = dbi_conn_error(connection, &err);
	if (err) {
		LMSG("dbi_error[%d]: '%s'", ret_val, err);
	} else {
		LMSG("dbi_error[%d]: No error text available.", ret_val);
	}
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim600: fdm=marker
** vim: noet ts=3 sw=3
*/
