/* Dynamic loading of the database backend.
 * We use GLib's multiplatform dl() wrapper
 * to open up libdbmysql or libdbpgsql and
 * populate the global 'db' structure.
 *
 * (c) 2005 Aaron Stone <aaron@serendipity.cx>
 *
 * 
 */

#include "dbmail.h"
#define THIS_MODULE "db"

static db_func_t *db = NULL;

extern db_param_t _db_params;

/* Returns:
 *  1 on modules unsupported
 *  0 on success
 * -1 on failure to load module
 * -2 on missing symbols
 * -3 on memory error
 */
int db_load_driver(void)
{
	GModule *module;
	char *lib = NULL;
	char *driver = NULL;

	if (!g_module_supported()) {
		TRACE(TRACE_FATAL, "loadable modules unsupported on this platform");
		return 1;
	}

	db = g_new0(db_func_t,1);

	if (strcasecmp(_db_params.driver, "PGSQL") == 0)
		driver = "pgsql";
	else if (strcasecmp(_db_params.driver, "POSTGRESQL") == 0)
		driver = "pgsql";
	else if (strcasecmp(_db_params.driver, "MYSQL") == 0)
		driver = "mysql";
	else if (strcasecmp(_db_params.driver, "SQLITE") == 0)
		driver = "sqlite";
	else
		TRACE(TRACE_FATAL, "unsupported driver: %s, please choose from MySQL, PGSQL, SQLite",
				_db_params.driver);

	field_t library_dir;
	config_get_value("library_directory", "DBMAIL", library_dir);
	if (strlen(library_dir) == 0) {
		TRACE(TRACE_DEBUG, "no value for library_directory, using default [%s]", DEFAULT_LIBRARY_DIR);
		snprintf(library_dir, sizeof(field_t), "%s", DEFAULT_LIBRARY_DIR);
	} else {
		TRACE(TRACE_DEBUG, "library_directory is [%s]", library_dir);
	}

	/* Try local build area, then dbmail lib paths, then system lib path. */
	int i;
	char *lib_path[] = { library_dir, NULL };

	/* Note that the limit here *includes* the NULL. This is intentional,
	 * to allow g_module_build_path to try the current working directory. */
	for (i = 0; i < 2; i++) {
		lib = g_module_build_path(lib_path[i], driver);
		module = g_module_open(lib, 0); // non-lazy bind.

		TRACE(TRACE_DEBUG, "looking for %s as %s", driver, lib);
		g_free(lib);

		if (!module)
			TRACE(TRACE_INFO, "cannot load %s", g_module_error());
		if (module)
			break;
	}

	/* If the list is exhausted without opening a module, we'll catch it. */
	if (!module) {
		TRACE(TRACE_FATAL, "could not load db module - turn up debug level for details");
		return -1;
	}

	if (!g_module_symbol(module, "db_connect",             (gpointer)&db->connect             )
	||  !g_module_symbol(module, "db_disconnect",          (gpointer)&db->disconnect          )
	||  !g_module_symbol(module, "db_check_connection",    (gpointer)&db->check_connection    )
	||  !g_module_symbol(module, "db_query",               (gpointer)&db->query               )
	||  !g_module_symbol(module, "db_insert_result",       (gpointer)&db->insert_result       )
	||  !g_module_symbol(module, "db_num_rows",            (gpointer)&db->num_rows            )
	||  !g_module_symbol(module, "db_num_fields",          (gpointer)&db->num_fields          )
	||  !g_module_symbol(module, "db_get_result",          (gpointer)&db->get_result          )
	||  !g_module_symbol(module, "db_free_result",         (gpointer)&db->free_result         )
	||  !g_module_symbol(module, "db_escape_string",       (gpointer)&db->escape_string       )
	||  !g_module_symbol(module, "db_escape_binary",       (gpointer)&db->escape_binary       )
	||  !g_module_symbol(module, "db_do_cleanup",          (gpointer)&db->do_cleanup          )
	||  !g_module_symbol(module, "db_get_length",          (gpointer)&db->get_length          )
	||  !g_module_symbol(module, "db_get_affected_rows",   (gpointer)&db->get_affected_rows   )
	||  !g_module_symbol(module, "db_get_sql",             (gpointer)&db->get_sql             )
	||  !g_module_symbol(module, "db_set_result_set",      (gpointer)&db->set_result_set      )) {
		TRACE(TRACE_FATAL, "cannot find function %s", g_module_error());
		return -2;
	}

	return 0;
}

/* This is the first db_* call anybody should make. */
int db_connect(void)
{
	db_load_driver();
	return db->connect();
}

/* But sometimes this gets called after help text or an
 * error but without a matching db_connect before it. */
int db_disconnect(void)
{
	if (!db) return 0;
	db->disconnect();
	g_free(db);
	return 0;
}

int db_check_connection(void)
	{ return db->check_connection(); }
int db_query(const char *the_query)
{
	/* Timing information stuffed in here so as
	 * to be common across all databases. */
	int result;
	time_t before, after;

	before = time(NULL);
	
	result = db->query(the_query);

	after = time(NULL);

	if (before == (time_t)-1 || after == (time_t)-1) {
		/* Can't log because time(2) failed. */
	} else {
		/* This is signed on the chance that ntpd ran during the query
		 * so it might look like it went back in time. */
		int elapsed = (int)((time_t) (after - before));
		TRACE(TRACE_DEBUG, "last query took [%d] seconds", elapsed);
		if (elapsed > (int)_db_params.query_time_info)
			TRACE(TRACE_INFO, "slow query [%s] took [%d] seconds", the_query, elapsed);
		if (elapsed > (int)_db_params.query_time_message)
			TRACE(TRACE_MESSAGE, "slow query [%s] took [%d] seconds", the_query, elapsed);
		if (elapsed > (int)_db_params.query_time_warning)
			TRACE(TRACE_WARNING, "slow query [%s] took [%d] seconds", the_query, elapsed);
	}

	return result;
}
u64_t db_insert_result(const char *sequence_identifier)
	{ return db->insert_result(sequence_identifier); }
unsigned db_num_rows(void)
	{ return db->num_rows(); }
unsigned db_num_fields(void)
	{ return db->num_fields(); }
const char * db_get_result(unsigned row, unsigned field)
	{ return db->get_result(row, field); }
void db_free_result(void)
	{ return db->free_result(); }
unsigned long db_escape_string(char *to,
		const char *from, unsigned long length)
	{ return db->escape_string(to, from, length); }
unsigned long db_escape_binary(char *to,
		const char *from, unsigned long length)
	{ return db->escape_binary(to, from, length); }
int db_do_cleanup(const char **tables, int num_tables)
	{ return db->do_cleanup(tables, num_tables); }
u64_t db_get_length(unsigned row, unsigned field)
	{ return db->get_length(row, field); }
u64_t db_get_affected_rows(void)
	{ return db->get_affected_rows(); }
void db_set_result_set(void *res)
	{ db->set_result_set(res); }
const char * db_get_sql(sql_fragment_t frag)
	{ return db->get_sql(frag); }



syntax highlighted by Code2HTML, v. 0.9.1