/*****************************************************************************\
 *  pgsql_common.c - common functions for the the pgsql storage plugin.
 *****************************************************************************
 *
 *  Copyright (C) 2004-2007 The Regents of the University of California.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Danny Auble <da@llnl.gov>
 *
 *  This file is part of SLURM, a resource management program.
 *  For details, see <https://computing.llnl.gov/linux/slurm/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  SLURM 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.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  SLURM 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 SLURM; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
 *
 *  This file is patterned after jobcomp_linux.c, written by Morris Jette and
 *  Copyright (C) 2002 The Regents of the University of California.
\*****************************************************************************/

#include "pgsql_common.h"
#include <stdlib.h>

pthread_mutex_t pgsql_lock = PTHREAD_MUTEX_INITIALIZER;

extern int *destroy_pgsql_db_info(pgsql_db_info_t *db_info)
{
	if(db_info) {
		xfree(db_info->host);
		xfree(db_info->backup);
		xfree(db_info->user);
		xfree(db_info->pass);
		xfree(db_info);
	}
	return SLURM_SUCCESS;
}

extern int _create_db(char *db_name, pgsql_db_info_t *db_info)
{
	char create_line[50];
	PGconn *pgsql_db = NULL;
	char *connect_line = xstrdup_printf("dbname = 'postgres'"
					    " host = '%s'"
					    " port = '%u'"
					    " user = '%s'"
					    " password = '%s'",
					    db_info->host,
					    db_info->port,
					    db_info->user,
					    db_info->pass);

	pgsql_db = PQconnectdb(connect_line);

	if (PQstatus(pgsql_db) == CONNECTION_OK) {
		PGresult *result = NULL;
		snprintf(create_line, sizeof(create_line),
			 "create database %s", db_name);
		result = PQexec(pgsql_db, create_line);
		if (PQresultStatus(result) != PGRES_COMMAND_OK) {
			fatal("PQexec failed: %d %s\n%s",
			     PQresultStatus(result), PQerrorMessage(pgsql_db),
			      create_line);
		}
		PQclear(result);
		pgsql_close_db_connection(&pgsql_db);
	} else {
		info("Connection failed to %s", connect_line);
		fatal("Status was: %d %s",
		      PQstatus(pgsql_db), PQerrorMessage(pgsql_db));
	}
	xfree(connect_line);

	return SLURM_SUCCESS;
}

extern int pgsql_get_db_connection(PGconn **pgsql_db, char *db_name,
				   pgsql_db_info_t *db_info)
{
	int rc = SLURM_SUCCESS;
	bool storage_init = false;
	char *connect_line = xstrdup_printf("dbname = '%s'"
					    " host = '%s'"
					    " port = '%u'"
					    " user = '%s'"
					    " password = '%s'",
					    db_name,
					    db_info->host,
					    db_info->port,
					    db_info->user,
					    db_info->pass);

	while(!storage_init) {
		//debug2("pgsql connect: %s", connect_line);
		*pgsql_db = PQconnectdb(connect_line);

		if(PQstatus(*pgsql_db) != CONNECTION_OK) {
			if(!strcmp(PQerrorMessage(*pgsql_db),
				   "no password supplied")) {
				PQfinish(*pgsql_db);
				fatal("This Postgres connection needs "
				      "a password.  It doesn't appear to "
				      "like blank ones");
			}

			info("Database %s not created. Creating", db_name);
			pgsql_close_db_connection(pgsql_db);
			_create_db(db_name, db_info);

		} else {
			char *language_line = "CREATE LANGUAGE plpgsql;";
			PGresult *result = NULL;
			const char *ver;
			ver = PQparameterStatus(*pgsql_db, "server_version");
			if (atof(ver) < 8.3)
				fatal("server version 8.3 or above required");

			/* This needs to be done for the accounting_storage
			   plugin, and most likely any pgsql plugin.  */
			result = PQexec(*pgsql_db, language_line);
			if ((PQresultStatus(result) != PGRES_COMMAND_OK)
			    && strcmp("ERROR:  language \"plpgsql\" "
				      "already exists\n",
				      PQerrorMessage(*pgsql_db))) {
				fatal("PQexec failed: %d %s\n%s",
				      PQresultStatus(result),
				      PQerrorMessage(*pgsql_db),
				      language_line);
			}
			PQclear(result);

			storage_init = true;
		}
	}
	xfree(connect_line);
	return rc;
}

extern int pgsql_close_db_connection(PGconn **pgsql_db)
{
	if(pgsql_db && *pgsql_db) {
		PQfinish(*pgsql_db);
		*pgsql_db = NULL;
	}
	return SLURM_SUCCESS;
}

extern int pgsql_db_query(PGconn *pgsql_db, char *query)
{
	PGresult *result = NULL;

	if(!pgsql_db)
		fatal("You haven't inited this storage yet.");

	if(!(result = pgsql_db_query_ret(pgsql_db, query)))
		return SLURM_ERROR;

	PQclear(result);
	return SLURM_SUCCESS;
}
extern int pgsql_db_start_transaction(PGconn *pgsql_db)
{
	return pgsql_db_query(pgsql_db, "BEGIN WORK");
}

extern int pgsql_db_commit(PGconn *pgsql_db)
{
	return pgsql_db_query(pgsql_db, "COMMIT WORK");
}

extern int pgsql_db_rollback(PGconn *pgsql_db)
{
	return pgsql_db_query(pgsql_db, "ROLLBACK WORK");

}

extern PGresult *pgsql_db_query_ret(PGconn *pgsql_db, char *query)
{
	PGresult *result = NULL;

	if(!pgsql_db)
		fatal("You haven't inited this storage yet.");

	result = PQexec(pgsql_db, query);

	if(PQresultStatus(result) != PGRES_COMMAND_OK
	   && PQresultStatus(result) != PGRES_TUPLES_OK) {
		error("PQexec failed: %d %s", PQresultStatus(result),
		      PQerrorMessage(pgsql_db));
		info("query was %s", query);
		PQclear(result);
		return NULL;
	}
	return result;
}

extern int pgsql_insert_ret_id(PGconn *pgsql_db, char *sequence_name,
			       char *query)
{
	int new_id = 0;
	PGresult *result = NULL;

	slurm_mutex_lock(&pgsql_lock);
	if(pgsql_db_query(pgsql_db, query) != SLURM_ERROR)  {
		char *new_query = xstrdup_printf(
			"select last_value from %s", sequence_name);

		if((result = pgsql_db_query_ret(pgsql_db, new_query))) {
			new_id = atoi(PQgetvalue(result, 0, 0));
			PQclear(result);
		}
		xfree(new_query);
		if(!new_id) {
			/* should have new id */
			error("We should have gotten a new id: %s",
			      PQerrorMessage(pgsql_db));
		}
	}
	slurm_mutex_unlock(&pgsql_lock);

	return new_id;

}
extern int pgsql_query_ret_id(PGconn *pgsql_db, char *query)
{
	int new_id = 0;
	PGresult *result = NULL;

	slurm_mutex_lock(&pgsql_lock);
	result = pgsql_db_query_ret(pgsql_db, query);
	if (result) {
		new_id = atoi(PQgetvalue(result, 0, 0));
		PQclear(result);
	} else {
		/* should have new id */
		error("pgsql_query_ret_id() query failed: %s",
		      PQerrorMessage(pgsql_db));
	}
	slurm_mutex_unlock(&pgsql_lock);

	return new_id;

}


extern int pgsql_db_create_table(PGconn *pgsql_db, char *schema,
				 char *table_name, storage_field_t *fields,
				 char *ending)
{
	char *query = NULL;
	char *tmp = NULL;
	char *next = NULL;
	int i = 0;

	query = xstrdup_printf("create table %s.%s (", schema, table_name);
	i=0;
	while(fields && fields->name) {
		next = xstrdup_printf(" %s %s",
				      fields->name,
				      fields->options);
		if(i)
			xstrcat(tmp, ",");
		xstrcat(tmp, next);
		xfree(next);
		fields++;
		i++;
	}
	xstrcat(query, tmp);
	xfree(tmp);
	xstrcat(query, ending);

	if(pgsql_db_query(pgsql_db, query) == SLURM_ERROR) {
		xfree(query);
		return SLURM_ERROR;
	}
	xfree(query);

	return SLURM_SUCCESS;
}

extern int
pgsql_db_make_table_current(PGconn *pgsql_db, char *schema, char *table_name,
			    storage_field_t *fields)
{
	char *query = NULL, *opt_part = NULL, *temp_char = NULL;
	char *type = NULL;
	int not_null = 0;
	char *default_str = NULL;
	char* original_ptr = NULL;
	int i = 0;
	PGresult *result = NULL;
	List columns = NULL;
	ListIterator itr = NULL;
	char *col = NULL;

	DEF_TIMERS;

	query = xstrdup_printf("select column_name from "
			       "information_schema.columns where "
			       "table_name='%s' and table_schema='%s' ",
			       table_name, schema);

	if(!(result = pgsql_db_query_ret(pgsql_db, query))) {
		xfree(query);
		return SLURM_ERROR;
	}
	xfree(query);
	columns = list_create(slurm_destroy_char);
	for (i = 0; i < PQntuples(result); i++) {
		col = xstrdup(PQgetvalue(result, i, 0)); //column_name
		list_append(columns, col);
	}
	PQclear(result);
	itr = list_iterator_create(columns);
	query = xstrdup_printf("alter table %s.%s", schema, table_name);
	START_TIMER;
	i=0;
	while(fields[i].name) {
		int found = 0;
		not_null = 0;
		if(!strcasecmp("serial", fields[i].options)) {
			i++;
			continue;
		}
		opt_part = xstrdup(fields[i].options);
		original_ptr = opt_part;
		opt_part = strtok_r(opt_part, " ", &temp_char);
		if(opt_part) {
			type = xstrdup(opt_part); /* XXX: only one identifier supported */
			opt_part = strtok_r(NULL, " ", &temp_char);
			while(opt_part) {
				if(!strcasecmp("not", opt_part)) {
					opt_part = strtok_r(NULL, " ",
							    &temp_char);
					if (!strcasecmp("null", opt_part)) {
						not_null = 1;

					}
				} else if(!strcasecmp("default", opt_part)){
					opt_part = strtok_r(NULL,
							    " ", &temp_char);
					default_str = xstrdup(opt_part);
				}
				opt_part = strtok_r(NULL,
						    " ", &temp_char);
			}
		} else {
			type = xstrdup(fields[i].options);
		}
		xfree(original_ptr);
		list_iterator_reset(itr);
		while((col = list_next(itr))) {
			if(!strcmp(col, fields[i].name)) {
				list_delete_item(itr);
				found = 1;
				break;
			}
		}

		temp_char = NULL;
		if(!found) {
			info("adding column %s", fields[i].name);
			if(default_str)
				xstrfmtcat(temp_char,
					   " default %s", default_str);

			if(not_null)
				xstrcat(temp_char, " not null");

			xstrfmtcat(query,
				   " add %s %s",
				   fields[i].name, type);
			if(temp_char)
				xstrcat(query, temp_char);
			xstrcat(query, ",");
		} else {
			if(default_str)
				xstrfmtcat(temp_char,
					   " alter %s set default %s,",
					   fields[i].name, default_str);
			else
				xstrfmtcat(temp_char,
					   " alter %s drop default,",
					   fields[i].name);

			if(not_null)
				xstrfmtcat(temp_char,
					   " alter %s set not null,",
					   fields[i].name);
			else
				xstrfmtcat(temp_char,
					   " alter %s drop not null,",
					   fields[i].name);
			xstrfmtcat(query, " alter %s type %s,%s",
				   fields[i].name, type, temp_char);
		}
		xfree(temp_char);
		xfree(default_str);
		xfree(type);

		i++;
	}
	list_iterator_destroy(itr);
	list_destroy(columns);
	query[strlen(query)-1] = ';';

	//debug4("pgsql db create/alter table:\n %s", query);
	if(pgsql_db_query(pgsql_db, query)) {
		xfree(query);
		return SLURM_ERROR;
	}
	xfree(query);

	END_TIMER2("make table current");
	return SLURM_SUCCESS;
}
