mirror of
https://github.com/postgres/postgres.git
synced 2025-04-21 12:05:57 +03:00
832 lines
19 KiB
C
832 lines
19 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* variable.c
|
|
* Routines for handling specialized SET variables.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.90 2003/11/29 19:51:48 pgsql 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"
|
|
#include "mb/pg_wchar.h"
|
|
|
|
/*
|
|
* Some systems have tzname[] but don't declare it in <time.h>. Use this
|
|
* to duplicate the test in AC_STRUCT_TIMEZONE.
|
|
*/
|
|
#ifdef HAVE_TZNAME
|
|
#ifndef tzname /* For SGI. */
|
|
extern char *tzname[];
|
|
#endif
|
|
#endif
|
|
|
|
|
|
/*
|
|
* DATESTYLE
|
|
*/
|
|
|
|
/*
|
|
* assign_datestyle: GUC assign_hook for datestyle
|
|
*/
|
|
const char *
|
|
assign_datestyle(const char *value, bool doit, bool interactive)
|
|
{
|
|
int newDateStyle = DateStyle;
|
|
int newDateOrder = DateOrder;
|
|
bool ok = true;
|
|
int scnt = 0,
|
|
ocnt = 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)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid list syntax for parameter \"datestyle\"")));
|
|
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;
|
|
scnt++;
|
|
}
|
|
else if (strcasecmp(tok, "SQL") == 0)
|
|
{
|
|
newDateStyle = USE_SQL_DATES;
|
|
scnt++;
|
|
}
|
|
else if (strncasecmp(tok, "POSTGRES", 8) == 0)
|
|
{
|
|
newDateStyle = USE_POSTGRES_DATES;
|
|
scnt++;
|
|
}
|
|
else if (strcasecmp(tok, "GERMAN") == 0)
|
|
{
|
|
newDateStyle = USE_GERMAN_DATES;
|
|
scnt++;
|
|
/* GERMAN also sets DMY, unless explicitly overridden */
|
|
if (ocnt == 0)
|
|
newDateOrder = DATEORDER_DMY;
|
|
}
|
|
else if (strcasecmp(tok, "YMD") == 0)
|
|
{
|
|
newDateOrder = DATEORDER_YMD;
|
|
ocnt++;
|
|
}
|
|
else if (strcasecmp(tok, "DMY") == 0 ||
|
|
strncasecmp(tok, "EURO", 4) == 0)
|
|
{
|
|
newDateOrder = DATEORDER_DMY;
|
|
ocnt++;
|
|
}
|
|
else if (strcasecmp(tok, "MDY") == 0 ||
|
|
strcasecmp(tok, "US") == 0 ||
|
|
strncasecmp(tok, "NONEURO", 7) == 0)
|
|
{
|
|
newDateOrder = DATEORDER_MDY;
|
|
ocnt++;
|
|
}
|
|
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;
|
|
int saveDateOrder = DateOrder;
|
|
const char *subval;
|
|
|
|
subval = assign_datestyle(GetConfigOptionResetString("datestyle"),
|
|
true, interactive);
|
|
if (scnt == 0)
|
|
newDateStyle = DateStyle;
|
|
if (ocnt == 0)
|
|
newDateOrder = DateOrder;
|
|
DateStyle = saveDateStyle;
|
|
DateOrder = saveDateOrder;
|
|
if (!subval)
|
|
{
|
|
ok = false;
|
|
break;
|
|
}
|
|
/* Here we know that our own return value is always malloc'd */
|
|
/* when doit is true */
|
|
free((char *) subval);
|
|
}
|
|
else
|
|
{
|
|
if (interactive)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("unrecognized \"datestyle\" key word: \"%s\"",
|
|
tok)));
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (scnt > 1 || ocnt > 1)
|
|
ok = false;
|
|
|
|
pfree(rawstring);
|
|
freeList(elemlist);
|
|
|
|
if (!ok)
|
|
{
|
|
if (interactive)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("conflicting \"datestyle\" 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, "Postgres");
|
|
break;
|
|
}
|
|
switch (newDateOrder)
|
|
{
|
|
case DATEORDER_YMD:
|
|
strcat(result, ", YMD");
|
|
break;
|
|
case DATEORDER_DMY:
|
|
strcat(result, ", DMY");
|
|
break;
|
|
default:
|
|
strcat(result, ", MDY");
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Finally, it's safe to assign to the global variables; the
|
|
* assignment cannot fail now.
|
|
*/
|
|
DateStyle = newDateStyle;
|
|
DateOrder = newDateOrder;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* TIMEZONE
|
|
*/
|
|
|
|
/*
|
|
* Storage for TZ env var is allocated with an arbitrary size of 64 bytes.
|
|
*/
|
|
#define TZBUF_LEN 64
|
|
|
|
static char tzbuf[TZBUF_LEN];
|
|
|
|
/*
|
|
* First time through, we remember the original environment TZ value, if any.
|
|
*/
|
|
static bool have_saved_tz = false;
|
|
static char orig_tzbuf[TZBUF_LEN];
|
|
|
|
/*
|
|
* Convenience subroutine for assigning the value of TZ
|
|
*/
|
|
static void
|
|
set_tz(const char *tz)
|
|
{
|
|
strcpy(tzbuf, "TZ=");
|
|
strncpy(tzbuf + 3, tz, sizeof(tzbuf) - 4);
|
|
if (putenv(tzbuf) != 0) /* shouldn't happen? */
|
|
elog(LOG, "could not set TZ environment variable");
|
|
tzset();
|
|
}
|
|
|
|
/*
|
|
* Remove any value of TZ we have established
|
|
*
|
|
* Note: this leaves us with *no* value of TZ in the environment, and
|
|
* is therefore only appropriate for reverting to that state, not for
|
|
* reverting to a state where TZ was set to something else.
|
|
*/
|
|
static void
|
|
clear_tz(void)
|
|
{
|
|
/*
|
|
* 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 (tzbuf[0] == 'T')
|
|
{
|
|
strcpy(tzbuf, "=");
|
|
if (putenv(tzbuf) != 0)
|
|
elog(LOG, "could not clear TZ environment variable");
|
|
tzset();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check whether tzset() succeeded
|
|
*
|
|
* Unfortunately, tzset doesn't offer any well-defined way to detect that the
|
|
* value of TZ was bad. Often it will just select UTC (GMT) as the effective
|
|
* timezone. We use the following heuristics:
|
|
*
|
|
* If tzname[1] is a nonempty string, *or* the global timezone variable is
|
|
* not zero, then tzset must have recognized the TZ value as something
|
|
* different from UTC. Return true.
|
|
*
|
|
* Otherwise, check to see if the TZ name is a known spelling of "UTC"
|
|
* (ie, appears in our internal tables as a timezone equivalent to UTC).
|
|
* If so, accept it.
|
|
*
|
|
* This will reject nonstandard spellings of UTC unless tzset() chose to
|
|
* set tzname[1] as well as tzname[0]. The glibc version of tzset() will
|
|
* do so, but on other systems we may be tightening the spec a little.
|
|
*
|
|
* Another problem is that on some platforms (eg HPUX), if tzset thinks the
|
|
* input is bogus then it will adopt the system default timezone, which we
|
|
* really can't tell is not the intended translation of the input.
|
|
*
|
|
* Still, it beats failing to detect bad TZ names at all, and a silent
|
|
* failure mode of adopting the system-wide default is much better than
|
|
* a silent failure mode of adopting UTC.
|
|
*
|
|
* NB: this must NOT ereport(ERROR). The caller must get control back so that
|
|
* it can restore the old value of TZ if we don't like the new one.
|
|
*/
|
|
static bool
|
|
tzset_succeeded(const char *tz)
|
|
{
|
|
char tztmp[TZBUF_LEN];
|
|
char *cp;
|
|
int tzval;
|
|
|
|
/*
|
|
* Check first set of heuristics to say that tzset definitely worked.
|
|
*/
|
|
#ifdef HAVE_TZNAME
|
|
if (tzname[1] && tzname[1][0] != '\0')
|
|
return true;
|
|
#endif
|
|
if (TIMEZONE_GLOBAL != 0)
|
|
return true;
|
|
|
|
/*
|
|
* Check for known spellings of "UTC". Note we must downcase the
|
|
* input before passing it to DecodePosixTimezone().
|
|
*/
|
|
StrNCpy(tztmp, tz, sizeof(tztmp));
|
|
for (cp = tztmp; *cp; cp++)
|
|
*cp = tolower((unsigned char) *cp);
|
|
if (DecodePosixTimezone(tztmp, &tzval) == 0)
|
|
if (tzval == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Check whether timezone is acceptable.
|
|
*
|
|
* What we are doing here is checking for leap-second-aware timekeeping.
|
|
* We need to reject such TZ settings because they'll wreak havoc with our
|
|
* date/time arithmetic.
|
|
*
|
|
* NB: this must NOT ereport(ERROR). The caller must get control back so that
|
|
* it can restore the old value of TZ if we don't like the new one.
|
|
*/
|
|
static bool
|
|
tz_acceptable(void)
|
|
{
|
|
struct tm tt;
|
|
time_t time2000;
|
|
|
|
/*
|
|
* To detect leap-second timekeeping, compute the time_t value for
|
|
* local midnight, 2000-01-01. Insist that this be a multiple of 60;
|
|
* any partial-minute offset has to be due to leap seconds.
|
|
*/
|
|
MemSet(&tt, 0, sizeof(tt));
|
|
tt.tm_year = 100;
|
|
tt.tm_mon = 0;
|
|
tt.tm_mday = 1;
|
|
tt.tm_isdst = -1;
|
|
time2000 = mktime(&tt);
|
|
if ((time2000 % 60) != 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* assign_timezone: GUC assign_hook for timezone
|
|
*/
|
|
const char *
|
|
assign_timezone(const char *value, bool doit, bool interactive)
|
|
{
|
|
char *result;
|
|
char *endptr;
|
|
double hours;
|
|
|
|
/*
|
|
* On first call, see if there is a TZ in the original environment.
|
|
* Save that value permanently.
|
|
*/
|
|
if (!have_saved_tz)
|
|
{
|
|
char *orig_tz = getenv("TZ");
|
|
|
|
if (orig_tz)
|
|
StrNCpy(orig_tzbuf, orig_tz, sizeof(orig_tzbuf));
|
|
else
|
|
orig_tzbuf[0] = '\0';
|
|
have_saved_tz = true;
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
* ereport, 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)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid interval value for time zone: month not allowed")));
|
|
pfree(interval);
|
|
return NULL;
|
|
}
|
|
if (doit)
|
|
{
|
|
/* Here we change from SQL to Unix sign convention */
|
|
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)
|
|
{
|
|
/* Here we change from SQL to Unix sign convention */
|
|
CTimeZone = -hours * 3600;
|
|
HasCTZSet = true;
|
|
}
|
|
}
|
|
else if (strcasecmp(value, "UNKNOWN") == 0)
|
|
{
|
|
/*
|
|
* UNKNOWN is the value shown as the "default" for TimeZone in
|
|
* guc.c. We interpret it as meaning the original TZ
|
|
* inherited from the environment. Note that if there is an
|
|
* original TZ setting, we will return that rather than
|
|
* UNKNOWN as the canonical spelling.
|
|
*/
|
|
if (doit)
|
|
{
|
|
bool ok;
|
|
|
|
/* Revert to original setting of TZ, whatever it was */
|
|
if (orig_tzbuf[0])
|
|
{
|
|
set_tz(orig_tzbuf);
|
|
ok = tzset_succeeded(orig_tzbuf) && tz_acceptable();
|
|
}
|
|
else
|
|
{
|
|
clear_tz();
|
|
ok = tz_acceptable();
|
|
}
|
|
|
|
if (ok)
|
|
HasCTZSet = false;
|
|
else
|
|
{
|
|
/* Bogus, so force UTC (equivalent to INTERVAL 0) */
|
|
CTimeZone = 0;
|
|
HasCTZSet = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Otherwise assume it is a timezone name.
|
|
*
|
|
* We have to actually apply the change before we can have any
|
|
* hope of checking it. So, save the old value in case we
|
|
* have to back out. Note that it's possible the old setting
|
|
* is in tzbuf, so we'd better copy it.
|
|
*/
|
|
char save_tzbuf[TZBUF_LEN];
|
|
char *save_tz;
|
|
bool known,
|
|
acceptable;
|
|
|
|
save_tz = getenv("TZ");
|
|
if (save_tz)
|
|
StrNCpy(save_tzbuf, save_tz, sizeof(save_tzbuf));
|
|
|
|
set_tz(value);
|
|
|
|
known = tzset_succeeded(value);
|
|
acceptable = tz_acceptable();
|
|
|
|
if (doit && known && acceptable)
|
|
{
|
|
/* Keep the changed TZ */
|
|
HasCTZSet = false;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Revert to prior TZ setting; note we haven't changed
|
|
* HasCTZSet in this path, so if we were previously using
|
|
* a fixed offset, we still are.
|
|
*/
|
|
if (save_tz)
|
|
set_tz(save_tzbuf);
|
|
else
|
|
clear_tz();
|
|
/* Complain if it was bad */
|
|
if (!known)
|
|
{
|
|
ereport(interactive ? ERROR : LOG,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("unrecognized time zone name: \"%s\"",
|
|
value)));
|
|
return NULL;
|
|
}
|
|
if (!acceptable)
|
|
{
|
|
ereport(interactive ? ERROR : LOG,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("time zone \"%s\" appears to use leap seconds",
|
|
value),
|
|
errdetail("PostgreSQL does not support leap seconds.")));
|
|
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(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)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
|
|
errmsg("SET TRANSACTION ISOLATION LEVEL must be called before any query")));
|
|
|
|
if (strcmp(value, "serializable") == 0)
|
|
{
|
|
if (doit)
|
|
XactIsoLevel = XACT_SERIALIZABLE;
|
|
}
|
|
else if (strcmp(value, "repeatable read") == 0)
|
|
{
|
|
if (doit)
|
|
XactIsoLevel = XACT_REPEATABLE_READ;
|
|
}
|
|
else if (strcmp(value, "read committed") == 0)
|
|
{
|
|
if (doit)
|
|
XactIsoLevel = XACT_READ_COMMITTED;
|
|
}
|
|
else if (strcmp(value, "read uncommitted") == 0)
|
|
{
|
|
if (doit)
|
|
XactIsoLevel = XACT_READ_UNCOMMITTED;
|
|
}
|
|
else if (strcmp(value, "default") == 0)
|
|
{
|
|
if (doit)
|
|
XactIsoLevel = DefaultXactIsoLevel;
|
|
}
|
|
else
|
|
return NULL;
|
|
|
|
return value;
|
|
}
|
|
|
|
const char *
|
|
show_XactIsoLevel(void)
|
|
{
|
|
switch (XactIsoLevel)
|
|
{
|
|
case XACT_READ_UNCOMMITTED:
|
|
return "read uncommitted";
|
|
case XACT_READ_COMMITTED:
|
|
return "read committed";
|
|
case XACT_REPEATABLE_READ:
|
|
return "repeatable read";
|
|
case XACT_SERIALIZABLE:
|
|
return "serializable";
|
|
default:
|
|
return "bogus";
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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";
|
|
}
|
|
|
|
|
|
/*
|
|
* encoding handling functions
|
|
*/
|
|
|
|
const char *
|
|
assign_client_encoding(const char *value, bool doit, bool interactive)
|
|
{
|
|
int encoding;
|
|
|
|
encoding = pg_valid_client_encoding(value);
|
|
if (encoding < 0)
|
|
return NULL;
|
|
|
|
/*
|
|
* Note: if we are in startup phase then SetClientEncoding may not be
|
|
* able to really set the encoding. In this case we will assume that
|
|
* the encoding is okay, and InitializeClientEncoding() will fix
|
|
* things once initialization is complete.
|
|
*/
|
|
if (SetClientEncoding(encoding, doit) < 0)
|
|
{
|
|
if (interactive)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("conversion between %s and %s is not supported",
|
|
value, GetDatabaseEncodingName())));
|
|
return NULL;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
|
|
/*
|
|
* SET SESSION AUTHORIZATION
|
|
*
|
|
* When resetting session auth after an error, we can't expect to do catalog
|
|
* lookups. Hence, the stored form of the value must provide a numeric userid
|
|
* that can be re-used directly. We store the string in the form of
|
|
* NAMEDATALEN 'x's, followed by T or F to indicate superuserness, followed
|
|
* by the numeric userid --- this cannot conflict with any valid user name,
|
|
* because of the NAMEDATALEN limit on names.
|
|
*/
|
|
const char *
|
|
assign_session_authorization(const char *value, bool doit, bool interactive)
|
|
{
|
|
AclId usesysid = 0;
|
|
bool is_superuser = false;
|
|
char *result;
|
|
|
|
if (strspn(value, "x") == NAMEDATALEN &&
|
|
(value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F'))
|
|
{
|
|
/* might be a saved numeric userid */
|
|
char *endptr;
|
|
|
|
usesysid = (AclId) strtoul(value + NAMEDATALEN + 1, &endptr, 10);
|
|
|
|
if (endptr != value + NAMEDATALEN + 1 && *endptr == '\0')
|
|
{
|
|
/* syntactically valid, so use the numeric user ID and flag */
|
|
is_superuser = (value[NAMEDATALEN] == 'T');
|
|
}
|
|
else
|
|
usesysid = 0;
|
|
}
|
|
|
|
if (usesysid == 0)
|
|
{
|
|
/* not a saved ID, so look it up */
|
|
HeapTuple userTup;
|
|
|
|
if (!IsTransactionState())
|
|
{
|
|
/*
|
|
* Can't do catalog lookups, so fail. The upshot of this is
|
|
* that session_authorization cannot be set in
|
|
* postgresql.conf, which seems like a good thing anyway.
|
|
*/
|
|
return NULL;
|
|
}
|
|
|
|
userTup = SearchSysCache(SHADOWNAME,
|
|
PointerGetDatum(value),
|
|
0, 0, 0);
|
|
if (!HeapTupleIsValid(userTup))
|
|
{
|
|
if (interactive)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("user \"%s\" does not exist", value)));
|
|
return NULL;
|
|
}
|
|
|
|
usesysid = ((Form_pg_shadow) GETSTRUCT(userTup))->usesysid;
|
|
is_superuser = ((Form_pg_shadow) GETSTRUCT(userTup))->usesuper;
|
|
|
|
ReleaseSysCache(userTup);
|
|
}
|
|
|
|
if (doit)
|
|
SetSessionAuthorization(usesysid, is_superuser);
|
|
|
|
result = (char *) malloc(NAMEDATALEN + 32);
|
|
if (!result)
|
|
return NULL;
|
|
|
|
memset(result, 'x', NAMEDATALEN);
|
|
|
|
snprintf(result + NAMEDATALEN, 32, "%c%lu",
|
|
is_superuser ? 'T' : 'F',
|
|
(unsigned long) usesysid);
|
|
|
|
return result;
|
|
}
|
|
|
|
const char *
|
|
show_session_authorization(void)
|
|
{
|
|
/*
|
|
* We can't use the stored string; see comments for
|
|
* assign_session_authorization
|
|
*/
|
|
return GetUserNameFromId(GetSessionUserId());
|
|
}
|