mirror of
https://github.com/postgres/postgres.git
synced 2025-04-21 12:05:57 +03:00
The purpose of this change is to eliminate the need for every caller of SearchSysCache, SearchSysCacheCopy, SearchSysCacheExists, GetSysCacheOid, and SearchSysCacheList to know the maximum number of allowable keys for a syscache entry (currently 4). This will make it far easier to increase the maximum number of keys in a future release should we choose to do so, and it makes the code shorter, too. Design and review by Tom Lane.
917 lines
21 KiB
C
917 lines
21 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* variable.c
|
|
* Routines for handling specialized SET variables.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.133 2010/02/14 18:42:14 rhaas Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "access/xact.h"
|
|
#include "catalog/pg_authid.h"
|
|
#include "commands/variable.h"
|
|
#include "miscadmin.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/syscache.h"
|
|
#include "utils/snapmgr.h"
|
|
#include "mb/pg_wchar.h"
|
|
|
|
/*
|
|
* DATESTYLE
|
|
*/
|
|
|
|
/*
|
|
* assign_datestyle: GUC assign_hook for datestyle
|
|
*/
|
|
const char *
|
|
assign_datestyle(const char *value, bool doit, GucSource source)
|
|
{
|
|
int newDateStyle = DateStyle;
|
|
int newDateOrder = DateOrder;
|
|
bool have_style = false;
|
|
bool have_order = false;
|
|
bool ok = true;
|
|
char *rawstring;
|
|
char *result;
|
|
List *elemlist;
|
|
ListCell *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);
|
|
list_free(elemlist);
|
|
ereport(GUC_complaint_elevel(source),
|
|
(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 (pg_strcasecmp(tok, "ISO") == 0)
|
|
{
|
|
if (have_style && newDateStyle != USE_ISO_DATES)
|
|
ok = false; /* conflicting styles */
|
|
newDateStyle = USE_ISO_DATES;
|
|
have_style = true;
|
|
}
|
|
else if (pg_strcasecmp(tok, "SQL") == 0)
|
|
{
|
|
if (have_style && newDateStyle != USE_SQL_DATES)
|
|
ok = false; /* conflicting styles */
|
|
newDateStyle = USE_SQL_DATES;
|
|
have_style = true;
|
|
}
|
|
else if (pg_strncasecmp(tok, "POSTGRES", 8) == 0)
|
|
{
|
|
if (have_style && newDateStyle != USE_POSTGRES_DATES)
|
|
ok = false; /* conflicting styles */
|
|
newDateStyle = USE_POSTGRES_DATES;
|
|
have_style = true;
|
|
}
|
|
else if (pg_strcasecmp(tok, "GERMAN") == 0)
|
|
{
|
|
if (have_style && newDateStyle != USE_GERMAN_DATES)
|
|
ok = false; /* conflicting styles */
|
|
newDateStyle = USE_GERMAN_DATES;
|
|
have_style = true;
|
|
/* GERMAN also sets DMY, unless explicitly overridden */
|
|
if (!have_order)
|
|
newDateOrder = DATEORDER_DMY;
|
|
}
|
|
else if (pg_strcasecmp(tok, "YMD") == 0)
|
|
{
|
|
if (have_order && newDateOrder != DATEORDER_YMD)
|
|
ok = false; /* conflicting orders */
|
|
newDateOrder = DATEORDER_YMD;
|
|
have_order = true;
|
|
}
|
|
else if (pg_strcasecmp(tok, "DMY") == 0 ||
|
|
pg_strncasecmp(tok, "EURO", 4) == 0)
|
|
{
|
|
if (have_order && newDateOrder != DATEORDER_DMY)
|
|
ok = false; /* conflicting orders */
|
|
newDateOrder = DATEORDER_DMY;
|
|
have_order = true;
|
|
}
|
|
else if (pg_strcasecmp(tok, "MDY") == 0 ||
|
|
pg_strcasecmp(tok, "US") == 0 ||
|
|
pg_strncasecmp(tok, "NONEURO", 7) == 0)
|
|
{
|
|
if (have_order && newDateOrder != DATEORDER_MDY)
|
|
ok = false; /* conflicting orders */
|
|
newDateOrder = DATEORDER_MDY;
|
|
have_order = true;
|
|
}
|
|
else if (pg_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, source);
|
|
if (!have_style)
|
|
newDateStyle = DateStyle;
|
|
if (!have_order)
|
|
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
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("unrecognized \"datestyle\" key word: \"%s\"",
|
|
tok)));
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pfree(rawstring);
|
|
list_free(elemlist);
|
|
|
|
if (!ok)
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(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
|
|
*/
|
|
|
|
/*
|
|
* assign_timezone: GUC assign_hook for timezone
|
|
*/
|
|
const char *
|
|
assign_timezone(const char *value, bool doit, GucSource source)
|
|
{
|
|
char *result;
|
|
char *endptr;
|
|
double hours;
|
|
|
|
/*
|
|
* Check for INTERVAL 'foo'
|
|
*/
|
|
if (pg_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(ERROR), 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)
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid interval value for time zone: month not allowed")));
|
|
pfree(interval);
|
|
return NULL;
|
|
}
|
|
if (interval->day != 0)
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid interval value for time zone: day not allowed")));
|
|
pfree(interval);
|
|
return NULL;
|
|
}
|
|
if (doit)
|
|
{
|
|
/* Here we change from SQL to Unix sign convention */
|
|
#ifdef HAVE_INT64_TIMESTAMP
|
|
CTimeZone = -(interval->time / USECS_PER_SEC);
|
|
#else
|
|
CTimeZone = -interval->time;
|
|
#endif
|
|
|
|
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 * SECS_PER_HOUR;
|
|
HasCTZSet = true;
|
|
}
|
|
}
|
|
else if (pg_strcasecmp(value, "UNKNOWN") == 0)
|
|
{
|
|
/*
|
|
* UNKNOWN is the value shown as the "default" for TimeZone in
|
|
* guc.c. We interpret it as being a complete no-op; we don't
|
|
* change the timezone setting. Note that if there is a known
|
|
* timezone setting, we will return that name rather than UNKNOWN
|
|
* as the canonical spelling.
|
|
*
|
|
* During GUC initialization, since the timezone library isn't set
|
|
* up yet, pg_get_timezone_name will return NULL and we will leave
|
|
* the setting as UNKNOWN. If this isn't overridden from the
|
|
* config file then pg_timezone_initialize() will eventually
|
|
* select a default value from the environment.
|
|
*/
|
|
if (doit)
|
|
{
|
|
const char *curzone = pg_get_timezone_name(session_timezone);
|
|
|
|
if (curzone)
|
|
value = curzone;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Otherwise assume it is a timezone name, and try to load it.
|
|
*/
|
|
pg_tz *new_tz;
|
|
|
|
new_tz = pg_tzset(value);
|
|
|
|
if (!new_tz)
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("unrecognized time zone name: \"%s\"",
|
|
value)));
|
|
return NULL;
|
|
}
|
|
|
|
if (!tz_acceptable(new_tz))
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(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 (doit)
|
|
{
|
|
/* Save the changed TZ */
|
|
session_timezone = new_tz;
|
|
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.
|
|
*/
|
|
if (HasCTZSet)
|
|
{
|
|
result = (char *) malloc(64);
|
|
if (!result)
|
|
return NULL;
|
|
snprintf(result, 64, "%.5f",
|
|
(double) (-CTimeZone) / (double) SECS_PER_HOUR);
|
|
}
|
|
else
|
|
result = strdup(value);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* show_timezone: GUC show_hook for timezone
|
|
*/
|
|
const char *
|
|
show_timezone(void)
|
|
{
|
|
const char *tzn;
|
|
|
|
if (HasCTZSet)
|
|
{
|
|
Interval interval;
|
|
|
|
interval.month = 0;
|
|
interval.day = 0;
|
|
#ifdef HAVE_INT64_TIMESTAMP
|
|
interval.time = -(CTimeZone * USECS_PER_SEC);
|
|
#else
|
|
interval.time = -CTimeZone;
|
|
#endif
|
|
|
|
tzn = DatumGetCString(DirectFunctionCall1(interval_out,
|
|
IntervalPGetDatum(&interval)));
|
|
}
|
|
else
|
|
tzn = pg_get_timezone_name(session_timezone);
|
|
|
|
if (tzn != NULL)
|
|
return tzn;
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
|
|
/*
|
|
* LOG_TIMEZONE
|
|
*
|
|
* For log_timezone, we don't support the interval-based methods of setting a
|
|
* zone, which are only there for SQL spec compliance not because they're
|
|
* actually useful.
|
|
*/
|
|
|
|
/*
|
|
* assign_log_timezone: GUC assign_hook for log_timezone
|
|
*/
|
|
const char *
|
|
assign_log_timezone(const char *value, bool doit, GucSource source)
|
|
{
|
|
char *result;
|
|
|
|
if (pg_strcasecmp(value, "UNKNOWN") == 0)
|
|
{
|
|
/*
|
|
* UNKNOWN is the value shown as the "default" for log_timezone in
|
|
* guc.c. We interpret it as being a complete no-op; we don't change
|
|
* the timezone setting. Note that if there is a known timezone
|
|
* setting, we will return that name rather than UNKNOWN as the
|
|
* canonical spelling.
|
|
*
|
|
* During GUC initialization, since the timezone library isn't set up
|
|
* yet, pg_get_timezone_name will return NULL and we will leave the
|
|
* setting as UNKNOWN. If this isn't overridden from the config file
|
|
* then pg_timezone_initialize() will eventually select a default
|
|
* value from the environment.
|
|
*/
|
|
if (doit)
|
|
{
|
|
const char *curzone = pg_get_timezone_name(log_timezone);
|
|
|
|
if (curzone)
|
|
value = curzone;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Otherwise assume it is a timezone name, and try to load it.
|
|
*/
|
|
pg_tz *new_tz;
|
|
|
|
new_tz = pg_tzset(value);
|
|
|
|
if (!new_tz)
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("unrecognized time zone name: \"%s\"",
|
|
value)));
|
|
return NULL;
|
|
}
|
|
|
|
if (!tz_acceptable(new_tz))
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(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 (doit)
|
|
{
|
|
/* Save the changed TZ */
|
|
log_timezone = new_tz;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 = strdup(value);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* show_log_timezone: GUC show_hook for log_timezone
|
|
*/
|
|
const char *
|
|
show_log_timezone(void)
|
|
{
|
|
const char *tzn;
|
|
|
|
tzn = pg_get_timezone_name(log_timezone);
|
|
|
|
if (tzn != NULL)
|
|
return tzn;
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
|
|
/*
|
|
* SET TRANSACTION ISOLATION LEVEL
|
|
*/
|
|
|
|
const char *
|
|
assign_XactIsoLevel(const char *value, bool doit, GucSource source)
|
|
{
|
|
if (FirstSnapshotSet)
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
|
|
errmsg("SET TRANSACTION ISOLATION LEVEL must be called before any query")));
|
|
/* source == PGC_S_OVERRIDE means do it anyway, eg at xact abort */
|
|
if (source != PGC_S_OVERRIDE)
|
|
return NULL;
|
|
}
|
|
else if (IsSubTransaction())
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
|
|
errmsg("SET TRANSACTION ISOLATION LEVEL must not be called in a subtransaction")));
|
|
/* source == PGC_S_OVERRIDE means do it anyway, eg at xact abort */
|
|
if (source != PGC_S_OVERRIDE)
|
|
return NULL;
|
|
}
|
|
|
|
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, GucSource source)
|
|
{
|
|
/* Can't really roll back on error, so ignore non-interactive setting */
|
|
if (doit && source >= PGC_S_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, GucSource source)
|
|
{
|
|
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)
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(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 oid
|
|
* 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 oid, followed by a comma, followed by the role name.
|
|
* This cannot be confused with a plain role name because of the NAMEDATALEN
|
|
* limit on names, so we can tell whether we're being passed an initial
|
|
* role name or a saved/restored value. (NOTE: we rely on guc.c to have
|
|
* properly truncated any incoming value, but not to truncate already-stored
|
|
* values. See GUC_IS_NAME processing.)
|
|
*/
|
|
extern char *session_authorization_string; /* in guc.c */
|
|
|
|
const char *
|
|
assign_session_authorization(const char *value, bool doit, GucSource source)
|
|
{
|
|
Oid roleid = InvalidOid;
|
|
bool is_superuser = false;
|
|
const char *actual_rolename = NULL;
|
|
char *result;
|
|
|
|
if (strspn(value, "x") == NAMEDATALEN &&
|
|
(value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F'))
|
|
{
|
|
/* might be a saved userid string */
|
|
Oid savedoid;
|
|
char *endptr;
|
|
|
|
savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10);
|
|
|
|
if (endptr != value + NAMEDATALEN + 1 && *endptr == ',')
|
|
{
|
|
/* syntactically valid, so break out the data */
|
|
roleid = savedoid;
|
|
is_superuser = (value[NAMEDATALEN] == 'T');
|
|
actual_rolename = endptr + 1;
|
|
}
|
|
}
|
|
|
|
if (roleid == InvalidOid)
|
|
{
|
|
/* not a saved ID, so look it up */
|
|
HeapTuple roleTup;
|
|
|
|
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;
|
|
}
|
|
|
|
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(value));
|
|
if (!HeapTupleIsValid(roleTup))
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("role \"%s\" does not exist", value)));
|
|
return NULL;
|
|
}
|
|
|
|
roleid = HeapTupleGetOid(roleTup);
|
|
is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
|
|
actual_rolename = value;
|
|
|
|
ReleaseSysCache(roleTup);
|
|
}
|
|
|
|
if (doit)
|
|
SetSessionAuthorization(roleid, is_superuser);
|
|
|
|
result = (char *) malloc(NAMEDATALEN + 32 + strlen(actual_rolename));
|
|
if (!result)
|
|
return NULL;
|
|
|
|
memset(result, 'x', NAMEDATALEN);
|
|
|
|
sprintf(result + NAMEDATALEN, "%c%u,%s",
|
|
is_superuser ? 'T' : 'F',
|
|
roleid,
|
|
actual_rolename);
|
|
|
|
return result;
|
|
}
|
|
|
|
const char *
|
|
show_session_authorization(void)
|
|
{
|
|
/*
|
|
* Extract the user name from the stored string; see
|
|
* assign_session_authorization
|
|
*/
|
|
const char *value = session_authorization_string;
|
|
Oid savedoid;
|
|
char *endptr;
|
|
|
|
Assert(strspn(value, "x") == NAMEDATALEN &&
|
|
(value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F'));
|
|
|
|
savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10);
|
|
|
|
Assert(endptr != value + NAMEDATALEN + 1 && *endptr == ',');
|
|
|
|
return endptr + 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* SET ROLE
|
|
*
|
|
* 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 oid
|
|
* that can be re-used directly. We implement this exactly like SET
|
|
* SESSION AUTHORIZATION.
|
|
*
|
|
* The SQL spec requires "SET ROLE NONE" to unset the role, so we hardwire
|
|
* a translation of "none" to InvalidOid.
|
|
*/
|
|
extern char *role_string; /* in guc.c */
|
|
|
|
const char *
|
|
assign_role(const char *value, bool doit, GucSource source)
|
|
{
|
|
Oid roleid = InvalidOid;
|
|
bool is_superuser = false;
|
|
const char *actual_rolename = value;
|
|
char *result;
|
|
|
|
if (strspn(value, "x") == NAMEDATALEN &&
|
|
(value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F'))
|
|
{
|
|
/* might be a saved userid string */
|
|
Oid savedoid;
|
|
char *endptr;
|
|
|
|
savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10);
|
|
|
|
if (endptr != value + NAMEDATALEN + 1 && *endptr == ',')
|
|
{
|
|
/* syntactically valid, so break out the data */
|
|
roleid = savedoid;
|
|
is_superuser = (value[NAMEDATALEN] == 'T');
|
|
actual_rolename = endptr + 1;
|
|
}
|
|
}
|
|
|
|
if (roleid == InvalidOid &&
|
|
strcmp(actual_rolename, "none") != 0)
|
|
{
|
|
/* not a saved ID, so look it up */
|
|
HeapTuple roleTup;
|
|
|
|
if (!IsTransactionState())
|
|
{
|
|
/*
|
|
* Can't do catalog lookups, so fail. The upshot of this is that
|
|
* role cannot be set in postgresql.conf, which seems like a good
|
|
* thing anyway.
|
|
*/
|
|
return NULL;
|
|
}
|
|
|
|
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(value));
|
|
if (!HeapTupleIsValid(roleTup))
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("role \"%s\" does not exist", value)));
|
|
return NULL;
|
|
}
|
|
|
|
roleid = HeapTupleGetOid(roleTup);
|
|
is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
|
|
|
|
ReleaseSysCache(roleTup);
|
|
|
|
/*
|
|
* Verify that session user is allowed to become this role
|
|
*/
|
|
if (!is_member_of_role(GetSessionUserId(), roleid))
|
|
{
|
|
ereport(GUC_complaint_elevel(source),
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("permission denied to set role \"%s\"",
|
|
value)));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (doit)
|
|
SetCurrentRoleId(roleid, is_superuser);
|
|
|
|
result = (char *) malloc(NAMEDATALEN + 32 + strlen(actual_rolename));
|
|
if (!result)
|
|
return NULL;
|
|
|
|
memset(result, 'x', NAMEDATALEN);
|
|
|
|
sprintf(result + NAMEDATALEN, "%c%u,%s",
|
|
is_superuser ? 'T' : 'F',
|
|
roleid,
|
|
actual_rolename);
|
|
|
|
return result;
|
|
}
|
|
|
|
const char *
|
|
show_role(void)
|
|
{
|
|
/*
|
|
* Extract the role name from the stored string; see assign_role
|
|
*/
|
|
const char *value = role_string;
|
|
Oid savedoid;
|
|
char *endptr;
|
|
|
|
/* This special case only applies if no SET ROLE has been done */
|
|
if (value == NULL || strcmp(value, "none") == 0)
|
|
return "none";
|
|
|
|
Assert(strspn(value, "x") == NAMEDATALEN &&
|
|
(value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F'));
|
|
|
|
savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10);
|
|
|
|
Assert(endptr != value + NAMEDATALEN + 1 && *endptr == ',');
|
|
|
|
/*
|
|
* Check that the stored string still matches the effective setting, else
|
|
* return "none". This is a kluge to deal with the fact that SET SESSION
|
|
* AUTHORIZATION logically resets SET ROLE to NONE, but we cannot set the
|
|
* GUC role variable from assign_session_authorization (because we haven't
|
|
* got enough info to call set_config_option).
|
|
*/
|
|
if (savedoid != GetCurrentRoleId())
|
|
return "none";
|
|
|
|
return endptr + 1;
|
|
}
|