/*------------------------------------------------------------------------- * * 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()); }