/*-------------------------------------------------------------------------
 *
 * variable.c
 *		Routines for handling specialized SET variables.
 *
 *
 * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/commands/variable.c,v 1.68 2002/06/11 13:40:50 wieck Exp $
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <ctype.h>
#include <time.h>

#include "access/xact.h"
#include "catalog/pg_shadow.h"
#include "commands/variable.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/syscache.h"
#include "utils/tqual.h"

#ifdef MULTIBYTE
#include "mb/pg_wchar.h"
#else
/* Grand unified hard-coded badness */
#define pg_get_client_encoding_name()  "SQL_ASCII"
#define GetDatabaseEncodingName() "SQL_ASCII"
#endif


/*
 * DATESTYLE
 */

/*
 * assign_datestyle: GUC assign_hook for datestyle
 */
const char *
assign_datestyle(const char *value, bool doit, bool interactive)
{
	int			newDateStyle = DateStyle;
	bool		newEuroDates = EuroDates;
	bool		ok = true;
	int			dcnt = 0,
				ecnt = 0;
	char	   *rawstring;
	char	   *result;
	List	   *elemlist;
	List	   *l;

	/* Need a modifiable copy of string */
	rawstring = pstrdup(value);

	/* Parse string into list of identifiers */
	if (!SplitIdentifierString(rawstring, ',', &elemlist))
	{
		/* syntax error in list */
		pfree(rawstring);
		freeList(elemlist);
		if (interactive)
			elog(ERROR, "SET DATESTYLE: invalid list syntax");
		return NULL;
	}

	foreach(l, elemlist)
	{
		char	   *tok = (char *) lfirst(l);

		/* Ugh. Somebody ought to write a table driven version -- mjl */

		if (strcasecmp(tok, "ISO") == 0)
		{
			newDateStyle = USE_ISO_DATES;
			dcnt++;
		}
		else if (strcasecmp(tok, "SQL") == 0)
		{
			newDateStyle = USE_SQL_DATES;
			dcnt++;
		}
		else if (strncasecmp(tok, "POSTGRESQL", 8) == 0)
		{
			newDateStyle = USE_POSTGRES_DATES;
			dcnt++;
		}
		else if (strcasecmp(tok, "GERMAN") == 0)
		{
			newDateStyle = USE_GERMAN_DATES;
			dcnt++;
			if ((ecnt > 0) && (!newEuroDates))
				ok = false;
			newEuroDates = TRUE;
		}
		else if (strncasecmp(tok, "EURO", 4) == 0)
		{
			newEuroDates = TRUE;
			ecnt++;
		}
		else if (strcasecmp(tok, "US") == 0
				 || strncasecmp(tok, "NONEURO", 7) == 0)
		{
			newEuroDates = FALSE;
			ecnt++;
			if ((dcnt > 0) && (newDateStyle == USE_GERMAN_DATES))
				ok = false;
		}
		else if (strcasecmp(tok, "DEFAULT") == 0)
		{
			/*
			 * Easiest way to get the current DEFAULT state is to fetch
			 * the DEFAULT string from guc.c and recursively parse it.
			 *
			 * We can't simply "return assign_datestyle(...)" because we
			 * need to handle constructs like "DEFAULT, ISO".
			 */
			int			saveDateStyle = DateStyle;
			bool		saveEuroDates = EuroDates;
			const char *subval;

			subval = assign_datestyle(GetConfigOptionResetString("datestyle"),
									  true, interactive);
			newDateStyle = DateStyle;
			newEuroDates = EuroDates;
			DateStyle = saveDateStyle;
			EuroDates = saveEuroDates;
			if (!subval)
			{
				ok = false;
				break;
			}
			/* Here we know that our own return value is always malloc'd */
			/* when doit is true */
			free((char *) subval);
			dcnt++;
			ecnt++;
		}
		else
		{
			if (interactive)
				elog(ERROR, "SET DATESTYLE: unrecognized keyword %s", tok);
			ok = false;
			break;
		}
	}

	if (dcnt > 1 || ecnt > 1)
		ok = false;

	pfree(rawstring);
	freeList(elemlist);

	if (!ok)
	{
		if (interactive)
			elog(ERROR, "SET DATESTYLE: conflicting specifications");
		return NULL;
	}

	/*
	 * If we aren't going to do the assignment, just return OK indicator.
	 */
	if (!doit)
		return value;

	/*
	 * Prepare the canonical string to return.  GUC wants it malloc'd.
	 */
	result = (char *) malloc(32);
	if (!result)
		return NULL;

	switch (newDateStyle)
	{
		case USE_ISO_DATES:
			strcpy(result, "ISO");
			break;
		case USE_SQL_DATES:
			strcpy(result, "SQL");
			break;
		case USE_GERMAN_DATES:
			strcpy(result, "GERMAN");
			break;
		default:
			strcpy(result, "POSTGRESQL");
			break;
	}
	strcat(result, newEuroDates ? ", EURO" : ", US");

	/*
	 * Finally, it's safe to assign to the global variables;
	 * the assignment cannot fail now.
	 */
	DateStyle = newDateStyle;
	EuroDates = newEuroDates;

	return result;
}

/*
 * show_datestyle: GUC show_hook for datestyle
 */
const char *
show_datestyle(void)
{
	static char		buf[64];

	switch (DateStyle)
	{
		case USE_ISO_DATES:
			strcpy(buf, "ISO");
			break;
		case USE_SQL_DATES:
			strcpy(buf, "SQL");
			break;
		case USE_GERMAN_DATES:
			strcpy(buf, "German");
			break;
		default:
			strcpy(buf, "Postgres");
			break;
	};
	strcat(buf, " with ");
	strcat(buf, ((EuroDates) ? "European" : "US (NonEuropean)"));
	strcat(buf, " conventions");

	return buf;
}


/*
 * TIMEZONE
 */

/*
 * Storage for TZ env var is allocated with an arbitrary size of 64 bytes.
 */
static char tzbuf[64];

/*
 * assign_timezone: GUC assign_hook for timezone
 */
const char *
assign_timezone(const char *value, bool doit, bool interactive)
{
	char	   *result;
	char	   *endptr;
	double		hours;

	/*
	 * Check for INTERVAL 'foo'
	 */
	if (strncasecmp(value, "interval", 8) == 0)
	{
		const char *valueptr = value;
		char	   *val;
		Interval   *interval;

		valueptr += 8;
		while (isspace((unsigned char) *valueptr))
			valueptr++;
		if (*valueptr++ != '\'')
			return NULL;
		val = pstrdup(valueptr);
		/* Check and remove trailing quote */
		endptr = strchr(val, '\'');
		if (!endptr || endptr[1] != '\0')
		{
			pfree(val);
			return NULL;
		}
		*endptr = '\0';
		/*
		 * Try to parse it.  XXX an invalid interval format will result in
		 * elog, which is not desirable for GUC.  We did what we could to
		 * guard against this in flatten_set_variable_args, but a string
		 * coming in from postgresql.conf might contain anything.
		 */
		interval = DatumGetIntervalP(DirectFunctionCall3(interval_in,
														 CStringGetDatum(val),
														 ObjectIdGetDatum(InvalidOid),
														 Int32GetDatum(-1)));
		pfree(val);
		if (interval->month != 0)
		{
			if (interactive)
				elog(ERROR, "SET TIME ZONE: illegal INTERVAL; month not allowed");
			pfree(interval);
			return NULL;
		}
		if (doit)
		{
			CTimeZone = interval->time;
			HasCTZSet = true;
		}
		pfree(interval);
	}
	else
	{
		/*
		 * Try it as a numeric number of hours (possibly fractional).
		 */
		hours = strtod(value, &endptr);
		if (endptr != value && *endptr == '\0')
		{
			if (doit)
			{
				CTimeZone = hours * 3600;
				HasCTZSet = true;
			}
		}
		else if (strcasecmp(value, "UNKNOWN") == 0)
		{
			/*
			 * Clear any TZ value we may have established.
			 *
			 * unsetenv() works fine, but is BSD, not POSIX, and is not
			 * available under Solaris, among others. Apparently putenv()
			 * called as below clears the process-specific environment
			 * variables.  Other reasonable arguments to putenv() (e.g.
			 * "TZ=", "TZ", "") result in a core dump (under Linux anyway).
			 * - thomas 1998-01-26
			 */
			if (doit)
			{
				if (tzbuf[0] == 'T')
				{
					strcpy(tzbuf, "=");
					if (putenv(tzbuf) != 0)
						elog(ERROR, "Unable to clear TZ environment variable");
					tzset();
				}
				HasCTZSet = false;
			}
		}
		else
		{
			/*
			 * Otherwise assume it is a timezone name.
			 *
			 * XXX unfortunately we have no reasonable way to check whether a
			 * timezone name is good, so we have to just assume that it is.
			 */
			if (doit)
			{
				strcpy(tzbuf, "TZ=");
				strncat(tzbuf, value, sizeof(tzbuf)-4);
				if (putenv(tzbuf) != 0)	/* shouldn't happen? */
					elog(LOG, "assign_timezone: putenv failed");
				tzset();
				HasCTZSet = false;
			}
		}
	}

	/*
	 * If we aren't going to do the assignment, just return OK indicator.
	 */
	if (!doit)
		return value;

	/*
	 * Prepare the canonical string to return.  GUC wants it malloc'd.
	 */
	result = (char *) malloc(sizeof(tzbuf));
	if (!result)
		return NULL;

	if (HasCTZSet)
	{
		snprintf(result, sizeof(tzbuf), "%.5f",
				 (double) CTimeZone / 3600.0);
	}
	else if (tzbuf[0] == 'T')
	{
		strcpy(result, tzbuf + 3);
	}
	else
	{
		strcpy(result, "UNKNOWN");
	}

	return result;
}

/*
 * show_timezone: GUC show_hook for timezone
 */
const char *
show_timezone(void)
{
	char	   *tzn;

	if (HasCTZSet)
	{
		Interval	interval;

		interval.month = 0;
		interval.time = CTimeZone;

		tzn = DatumGetCString(DirectFunctionCall1(interval_out,
												  IntervalPGetDatum(&interval)));
	}
	else
		tzn = getenv("TZ");

	if (tzn != NULL)
		return tzn;

	return "unknown";
}


/*
 * SET TRANSACTION ISOLATION LEVEL
 */

const char *
assign_XactIsoLevel(const char *value, bool doit, bool interactive)
{
	if (doit && interactive && SerializableSnapshot != NULL)
		elog(ERROR, "SET TRANSACTION ISOLATION LEVEL must be called before any query");

	if (strcmp(value, "serializable") == 0)
		{ if (doit) XactIsoLevel = XACT_SERIALIZABLE; }
	else if (strcmp(value, "read committed") == 0)
		{ if (doit) XactIsoLevel = XACT_READ_COMMITTED; }
	else if (strcmp(value, "default") == 0)
		{ if (doit) XactIsoLevel = DefaultXactIsoLevel; }
	else
		return NULL;

	return value;
}

const char *
show_XactIsoLevel(void)
{
	if (XactIsoLevel == XACT_SERIALIZABLE)
		return "SERIALIZABLE";
	else
		return "READ COMMITTED";
}


/*
 * Random number seed
 */

bool
assign_random_seed(double value, bool doit, bool interactive)
{
	/* Can't really roll back on error, so ignore non-interactive setting */
	if (doit && interactive)
		DirectFunctionCall1(setseed, Float8GetDatum(value));
	return true;
}

const char *
show_random_seed(void)
{
	return "unavailable";
}


/*
 * MULTIBYTE-related functions
 *
 * If MULTIBYTE support was not compiled, we still allow these variables
 * to exist, but you can't set them to anything but "SQL_ASCII".  This
 * minimizes interoperability problems between non-MB servers and MB-enabled
 * clients.
 */

const char *
assign_client_encoding(const char *value, bool doit, bool interactive)
{
#ifdef MULTIBYTE
	int			encoding;
	int			old_encoding = 0;

	encoding = pg_valid_client_encoding(value);
	if (encoding < 0)
		return NULL;
	/*
	 * Ugly API here ... can't test validity without setting new encoding...
	 */
	if (!doit)
		old_encoding = pg_get_client_encoding();
	if (pg_set_client_encoding(encoding) < 0)
	{
		if (interactive)
			elog(ERROR, "Conversion between %s and %s is not supported",
				 value, GetDatabaseEncodingName());
		return NULL;
	}
	if (!doit)
		pg_set_client_encoding(old_encoding);
#else
	if (strcasecmp(value, pg_get_client_encoding_name()) != 0)
		return NULL;
#endif

	return value;
}


const char *
assign_server_encoding(const char *value, bool doit, bool interactive)
{
	if (interactive)
		elog(ERROR, "SET SERVER_ENCODING is not supported");
	/* Pretend never to fail in noninteractive case */
	return value;
}

const char *
show_server_encoding(void)
{
	return GetDatabaseEncodingName();
}


/*
 * SET SESSION AUTHORIZATION
 *
 * Note: when resetting session auth after an error, we can't expect to do
 * catalog lookups.  Hence, the stored form of the value is always a numeric
 * userid that can be re-used directly.
 */
const char *
assign_session_authorization(const char *value, bool doit, bool interactive)
{
	Oid			usesysid;
	char	   *endptr;
	char	   *result;

	usesysid = (Oid) strtoul(value, &endptr, 10);

	if (endptr != value && *endptr == '\0' && OidIsValid(usesysid))
	{
		/* use the numeric user ID */
	}
	else
	{
		HeapTuple	userTup;

		userTup = SearchSysCache(SHADOWNAME,
								 PointerGetDatum(value),
								 0, 0, 0);
		if (!HeapTupleIsValid(userTup))
		{
			if (interactive)
				elog(ERROR, "user \"%s\" does not exist", value);
			return NULL;
		}

		usesysid = ((Form_pg_shadow) GETSTRUCT(userTup))->usesysid;

		ReleaseSysCache(userTup);
	}

	if (doit)
		SetSessionAuthorization(usesysid);

	result = (char *) malloc(32);
	if (!result)
		return NULL;

	snprintf(result, 32, "%lu", (unsigned long) usesysid);

	return result;
}

const char *
show_session_authorization(void)
{
	return GetUserNameFromId(GetSessionUserId());
}